001    /*
002     * Copyright 2002 - 2007 JEuclid, http://jeuclid.sf.net
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    /* $Id: Mmultiscripts.java,v 9af8f7182adc 2009/09/04 14:21:09 max $ */
018    
019    package net.sourceforge.jeuclid.elements.presentation.script;
020    
021    import java.awt.geom.Dimension2D;
022    import java.util.ArrayList;
023    import java.util.List;
024    
025    import net.sourceforge.jeuclid.LayoutContext;
026    import net.sourceforge.jeuclid.elements.JEuclidElement;
027    import net.sourceforge.jeuclid.elements.support.Dimension2DImpl;
028    import net.sourceforge.jeuclid.elements.support.ElementListSupport;
029    import net.sourceforge.jeuclid.elements.support.MathMLNodeListImpl;
030    import net.sourceforge.jeuclid.layout.LayoutInfo;
031    import net.sourceforge.jeuclid.layout.LayoutStage;
032    import net.sourceforge.jeuclid.layout.LayoutView;
033    
034    import org.apache.batik.dom.AbstractDocument;
035    import org.w3c.dom.Node;
036    import org.w3c.dom.mathml.MathMLElement;
037    import org.w3c.dom.mathml.MathMLMultiScriptsElement;
038    import org.w3c.dom.mathml.MathMLNodeList;
039    
040    /**
041     * Prescripts and Tensor Indices.
042     * 
043     * @version $Revision: 9af8f7182adc $
044     */
045    
046    public final class Mmultiscripts extends AbstractScriptElement implements
047            MathMLMultiScriptsElement {
048        /**
049         * The XML element from this class.
050         */
051        public static final String ELEMENT = "mmultiscripts";
052    
053        private static final long serialVersionUID = 1L;
054    
055        // /**
056        // * Logger for this class
057        // */
058        // currently unused
059        // private static final Log LOGGER = LogFactory
060        // .getLog(MathMultiScripts.class);
061        private static final int STATE_POSTSUB = 0;
062    
063        private static final int STATE_POSTSUPER = 1;
064    
065        private static final int STATE_PRESUB = 2;
066    
067        private static final int STATE_PRESUPER = 3;
068    
069        private final List<JEuclidElement> postsubscripts = new ArrayList<JEuclidElement>();
070    
071        private final List<JEuclidElement> postsuperscripts = new ArrayList<JEuclidElement>();
072    
073        private final List<JEuclidElement> presubscripts = new ArrayList<JEuclidElement>();
074    
075        private final List<JEuclidElement> presuperscripts = new ArrayList<JEuclidElement>();
076    
077        private boolean inRewriteChildren;
078    
079        /**
080         * Default constructor. Sets MathML Namespace.
081         * 
082         * @param qname
083         *            Qualified name.
084         * @param odoc
085         *            Owner Document.
086         */
087        public Mmultiscripts(final String qname, final AbstractDocument odoc) {
088            super(qname, odoc);
089    
090            this.inRewriteChildren = false;
091        }
092    
093        /** {@inheritDoc} */
094        @Override
095        protected Node newNode() {
096            return new Mmultiscripts(this.nodeName, this.ownerDocument);
097        }
098    
099        /** {@inheritDoc} */
100        @Override
101        public void changeHook() {
102            super.changeHook();
103            if (!this.inRewriteChildren) {
104                this.parseChildren();
105            }
106        }
107    
108        private void parseChildren() {
109            this.presubscripts.clear();
110            this.presuperscripts.clear();
111            this.postsubscripts.clear();
112            this.postsuperscripts.clear();
113    
114            final int count = this.getMathElementCount();
115    
116            int state = Mmultiscripts.STATE_POSTSUB;
117            for (int i = 1; i < count; i++) {
118                final JEuclidElement child = this.getMathElement(i);
119                if (child instanceof Mprescripts) {
120                    state = Mmultiscripts.STATE_PRESUB;
121                } else {
122                    if (state == Mmultiscripts.STATE_POSTSUB) {
123                        this.postsubscripts.add(child);
124                        state = Mmultiscripts.STATE_POSTSUPER;
125                    } else if (state == Mmultiscripts.STATE_POSTSUPER) {
126                        this.postsuperscripts.add(child);
127                        state = Mmultiscripts.STATE_POSTSUB;
128                    } else if (state == Mmultiscripts.STATE_PRESUB) {
129                        this.presubscripts.add(child);
130                        state = Mmultiscripts.STATE_PRESUPER;
131                    } else {
132                        this.presuperscripts.add(child);
133                        state = Mmultiscripts.STATE_PRESUB;
134                    }
135                }
136            }
137            if (this.postsuperscripts.size() < this.postsubscripts.size()) {
138                this.postsuperscripts.add((JEuclidElement) this.getOwnerDocument()
139                        .createElement(None.ELEMENT));
140            }
141            if (this.presuperscripts.size() < this.presubscripts.size()) {
142                this.presuperscripts.add((JEuclidElement) this.getOwnerDocument()
143                        .createElement(None.ELEMENT));
144            }
145        }
146    
147        /** {@inheritDoc} */
148        @Override
149        protected void layoutStageInvariant(final LayoutView view,
150                final LayoutInfo info, final LayoutStage stage,
151                final LayoutContext context) {
152            final JEuclidElement base = this.getBase();
153            final LayoutInfo baseInfo = view.getInfo(base);
154            final LayoutContext now = this.applyLocalAttributesToContext(context);
155    
156            final String subScriptshift = this.getSubscriptshift();
157            final String superScriptshift = this.getSuperscriptshift();
158    
159            final ScriptSupport.ShiftInfo totalShiftInfo = this
160                    .calculateTotalShift(view, stage, baseInfo, now,
161                            subScriptshift, superScriptshift);
162            float posX = 0.0f;
163            final float subBaselineShift = totalShiftInfo.getSubShift();
164            final float superBaselineShift = totalShiftInfo.getSuperShift();
165            for (int i = 0; i < this.presubscripts.size(); i++) {
166                final LayoutInfo subInfo = view.getInfo(this.presubscripts.get(i));
167                final LayoutInfo superInfo = view.getInfo(this.presuperscripts
168                        .get(i));
169                subInfo.moveTo(posX, subBaselineShift, stage);
170                superInfo.moveTo(posX, -superBaselineShift, stage);
171                posX += Math
172                        .max(subInfo.getWidth(stage), superInfo.getWidth(stage));
173            }
174            baseInfo.moveTo(posX, 0.0f, stage);
175            posX += baseInfo.getWidth(stage);
176            for (int i = 0; i < this.postsubscripts.size(); i++) {
177                final LayoutInfo subInfo = view.getInfo(this.postsubscripts.get(i));
178                final LayoutInfo superInfo = view.getInfo(this.postsuperscripts
179                        .get(i));
180                subInfo.moveTo(posX, subBaselineShift, stage);
181                superInfo.moveTo(posX, -superBaselineShift, stage);
182                posX += Math
183                        .max(subInfo.getWidth(stage), superInfo.getWidth(stage));
184            }
185    
186            final Dimension2D noborder = new Dimension2DImpl(0.0f, 0.0f);
187            ElementListSupport.fillInfoFromChildren(view, info, this, stage,
188                    noborder, noborder);
189        }
190    
191        private ScriptSupport.ShiftInfo calculateTotalShift(final LayoutView view,
192                final LayoutStage stage, final LayoutInfo baseInfo,
193                final LayoutContext now, final String subScriptshift,
194                final String superScriptshift) {
195            final ScriptSupport.ShiftInfo totalShiftInfo = new ScriptSupport.ShiftInfo(
196                    0.0f, 0.0f);
197    
198            for (int i = 0; i < this.presubscripts.size(); i++) {
199                final LayoutInfo subInfo = view.getInfo(this.presubscripts.get(i));
200                final LayoutInfo superInfo = view.getInfo(this.presuperscripts
201                        .get(i));
202                final ScriptSupport.ShiftInfo shiftInfo = ScriptSupport
203                        .calculateScriptShfits(stage, now, subScriptshift,
204                                superScriptshift, baseInfo, subInfo, superInfo);
205                totalShiftInfo.max(shiftInfo);
206            }
207            for (int i = 0; i < this.postsubscripts.size(); i++) {
208                final LayoutInfo subInfo = view.getInfo(this.postsubscripts.get(i));
209                final LayoutInfo superInfo = view.getInfo(this.postsuperscripts
210                        .get(i));
211                final ScriptSupport.ShiftInfo shiftInfo = ScriptSupport
212                        .calculateScriptShfits(stage, now, subScriptshift,
213                                superScriptshift, baseInfo, subInfo, superInfo);
214                totalShiftInfo.max(shiftInfo);
215            }
216            return totalShiftInfo;
217        }
218    
219        /** {@inheritDoc} */
220        @Override
221        public boolean hasChildPrescripts(final JEuclidElement child) {
222            return child.isSameNode(this.getBase())
223                    && (this.getNumprescriptcolumns() > 0);
224        }
225    
226        /** {@inheritDoc} */
227        @Override
228        public boolean hasChildPostscripts(final JEuclidElement child,
229                final LayoutContext context) {
230            return child.isSameNode(this.getBase())
231                    && (this.getNumscriptcolumns() > 0);
232        }
233    
234        /** {@inheritDoc} */
235        public JEuclidElement getBase() {
236            final JEuclidElement base = this.getMathElement(0);
237            if (base == null) {
238                return (JEuclidElement) this.getOwnerDocument().createElement(
239                        None.ELEMENT);
240            } else {
241                return base;
242            }
243        }
244    
245        /** {@inheritDoc} */
246        public void setBase(final MathMLElement base) {
247            this.setMathElement(0, base);
248        }
249    
250        /** {@inheritDoc} */
251        public int getNumprescriptcolumns() {
252            return this.presubscripts.size();
253        }
254    
255        /** {@inheritDoc} */
256        public int getNumscriptcolumns() {
257            return this.postsubscripts.size();
258        }
259    
260        /** {@inheritDoc} */
261        public MathMLElement getPreSubScript(final int colIndex) {
262            return this.presubscripts.get(colIndex - 1);
263        }
264    
265        /** {@inheritDoc} */
266        public MathMLElement getPreSuperScript(final int colIndex) {
267            return this.presuperscripts.get(colIndex - 1);
268        }
269    
270        /** {@inheritDoc} */
271        public MathMLNodeList getPrescripts() {
272            final int presubsize = this.presubscripts.size();
273            final List<Node> list = new ArrayList<Node>(2 * presubsize);
274            for (int i = 0; i < presubsize; i++) {
275                list.add(this.presubscripts.get(i));
276                list.add(this.presuperscripts.get(i));
277            }
278            return new MathMLNodeListImpl(list);
279        }
280    
281        /** {@inheritDoc} */
282        public MathMLNodeList getScripts() {
283            final int postsubsize = this.postsubscripts.size();
284            final List<Node> list = new ArrayList<Node>(2 * postsubsize);
285            for (int i = 0; i < postsubsize; i++) {
286                list.add(this.postsubscripts.get(i));
287                list.add(this.postsuperscripts.get(i));
288            }
289            return new MathMLNodeListImpl(list);
290        }
291    
292        /** {@inheritDoc} */
293        public MathMLElement getSubScript(final int colIndex) {
294            if ((colIndex < 1) || (colIndex > this.postsubscripts.size())) {
295                return null;
296            }
297            return this.postsubscripts.get(colIndex - 1);
298        }
299    
300        /** {@inheritDoc} */
301        public MathMLElement getSuperScript(final int colIndex) {
302            if ((colIndex < 1) || (colIndex > this.postsuperscripts.size())) {
303                return null;
304            }
305            return this.postsuperscripts.get(colIndex - 1);
306        }
307    
308        private void rewriteChildren() {
309            this.inRewriteChildren = true;
310    
311            final org.w3c.dom.NodeList childList = this.getChildNodes();
312            final int len = childList.getLength();
313            // start at 1 since 0 is the base!
314            for (int i = 1; i < len; i++) {
315                this.removeChild(childList.item(1));
316            }
317            if (len == 0) {
318                this.addMathElement((JEuclidElement) this.getOwnerDocument()
319                        .createElement(None.ELEMENT));
320            }
321            for (int i = 0; i < this.postsubscripts.size(); i++) {
322                this.addMathElement(this.postsubscripts.get(i));
323                this.addMathElement(this.postsuperscripts.get(i));
324            }
325            final int numprescripts = this.presubscripts.size();
326            if (numprescripts > 0) {
327                this.addMathElement((Mprescripts) this.getOwnerDocument()
328                        .createElement(Mprescripts.ELEMENT));
329                for (int i = 0; i < numprescripts; i++) {
330                    this.addMathElement(this.presubscripts.get(i));
331                    this.addMathElement(this.presuperscripts.get(i));
332                }
333            }
334            this.inRewriteChildren = false;
335        }
336    
337        /** {@inheritDoc} */
338        public MathMLElement insertPreSubScriptBefore(final int colIndex,
339                final MathMLElement newScript) {
340            final int targetIndex;
341            if (colIndex == 0) {
342                targetIndex = this.presubscripts.size();
343            } else {
344                targetIndex = colIndex - 1;
345            }
346            this.presubscripts.add(targetIndex, (JEuclidElement) newScript);
347            this.presuperscripts.add(targetIndex, (JEuclidElement) this
348                    .getOwnerDocument().createElement(None.ELEMENT));
349            this.rewriteChildren();
350            return newScript;
351        }
352    
353        /** {@inheritDoc} */
354        public MathMLElement insertPreSuperScriptBefore(final int colIndex,
355                final MathMLElement newScript) {
356            final int targetIndex;
357            if (colIndex == 0) {
358                targetIndex = this.presubscripts.size();
359            } else {
360                targetIndex = colIndex - 1;
361            }
362            this.presubscripts.add(targetIndex, (JEuclidElement) this
363                    .getOwnerDocument().createElement(None.ELEMENT));
364            this.presuperscripts.add(targetIndex, (JEuclidElement) newScript);
365            this.rewriteChildren();
366            return newScript;
367        }
368    
369        /** {@inheritDoc} */
370        public MathMLElement insertSubScriptBefore(final int colIndex,
371                final MathMLElement newScript) {
372            final int targetIndex;
373            if (colIndex == 0) {
374                targetIndex = this.postsubscripts.size();
375            } else {
376                targetIndex = colIndex - 1;
377            }
378            this.postsubscripts.add(targetIndex, (JEuclidElement) newScript);
379            this.postsuperscripts.add(targetIndex, (JEuclidElement) this
380                    .getOwnerDocument().createElement(None.ELEMENT));
381            this.rewriteChildren();
382            return newScript;
383        }
384    
385        /** {@inheritDoc} */
386        public MathMLElement insertSuperScriptBefore(final int colIndex,
387                final MathMLElement newScript) {
388            final int targetIndex;
389            if (colIndex == 0) {
390                targetIndex = this.postsubscripts.size();
391            } else {
392                targetIndex = colIndex - 1;
393            }
394            this.postsubscripts.add(targetIndex, (JEuclidElement) this
395                    .getOwnerDocument().createElement(None.ELEMENT));
396            this.postsuperscripts.add(targetIndex, (JEuclidElement) newScript);
397            this.rewriteChildren();
398            return newScript;
399        }
400    
401        /** {@inheritDoc} */
402        public MathMLElement setPreSubScriptAt(final int colIndex,
403                final MathMLElement newScript) {
404            final int targetCol = colIndex - 1;
405            if (targetCol == this.presubscripts.size()) {
406                return this.insertPreSubScriptBefore(0, newScript);
407            } else {
408                this.presubscripts.set(targetCol, (JEuclidElement) newScript);
409                this.rewriteChildren();
410                return newScript;
411            }
412        }
413    
414        /** {@inheritDoc} */
415        public MathMLElement setPreSuperScriptAt(final int colIndex,
416                final MathMLElement newScript) {
417            final int targetCol = colIndex - 1;
418            if (targetCol == this.presuperscripts.size()) {
419                return this.insertPreSuperScriptBefore(0, newScript);
420            } else {
421                this.presuperscripts.set(targetCol, (JEuclidElement) newScript);
422                this.rewriteChildren();
423                return newScript;
424            }
425        }
426    
427        /** {@inheritDoc} */
428        public MathMLElement setSubScriptAt(final int colIndex,
429                final MathMLElement newScript) {
430            final int targetCol = colIndex - 1;
431            if (targetCol == this.postsubscripts.size()) {
432                return this.insertSubScriptBefore(0, newScript);
433            } else {
434                this.postsubscripts.set(targetCol, (JEuclidElement) newScript);
435                this.rewriteChildren();
436                return newScript;
437            }
438        }
439    
440        /** {@inheritDoc} */
441        public MathMLElement setSuperScriptAt(final int colIndex,
442                final MathMLElement newScript) {
443            final int targetCol = colIndex - 1;
444            if (targetCol == this.postsuperscripts.size()) {
445                return this.insertSuperScriptBefore(0, newScript);
446            } else {
447                this.postsuperscripts.set(targetCol, (JEuclidElement) newScript);
448                this.rewriteChildren();
449                return newScript;
450            }
451        }
452    
453    }