001    /*
002     * Copyright 2002 - 2008 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: JMathComponent.java,v f1695c1926a6 2010/08/09 21:09:55 max $ */
018    
019    package net.sourceforge.jeuclid.swing;
020    
021    import java.awt.Color;
022    import java.awt.Font;
023    import java.io.IOException;
024    import java.util.Arrays;
025    import java.util.Collections;
026    import java.util.List;
027    import java.util.Map;
028    
029    import javax.swing.JComponent;
030    import javax.swing.SwingConstants;
031    import javax.swing.UIManager;
032    import javax.xml.parsers.ParserConfigurationException;
033    
034    import net.sourceforge.jeuclid.DOMBuilder;
035    import net.sourceforge.jeuclid.MathMLParserSupport;
036    import net.sourceforge.jeuclid.MathMLSerializer;
037    import net.sourceforge.jeuclid.MutableLayoutContext;
038    import net.sourceforge.jeuclid.context.LayoutContextImpl;
039    import net.sourceforge.jeuclid.context.Parameter;
040    import net.sourceforge.jeuclid.elements.generic.DocumentElement;
041    import net.sourceforge.jeuclid.elements.support.ClassLoaderSupport;
042    
043    import org.apache.commons.logging.Log;
044    import org.apache.commons.logging.LogFactory;
045    import org.w3c.dom.Document;
046    import org.w3c.dom.Node;
047    import org.xml.sax.SAXException;
048    
049    /**
050     * Displays MathML content in a Swing Component.
051     * <p>
052     * There are two properties which expose the actual content, accessible though
053     * {@link #getDocument()} / {@link #setDocument(org.w3c.dom.Node)} for content
054     * already available as a DOM model, and {@link #getContent()} and
055     * {@link #setContent(String)} for content available as a String.
056     * <p>
057     * This class exposes most of the rendering parameters as standard bean
058     * attributes. If you need to set additional attributes, you may use the
059     * {@link #setParameter(Parameter, Object)} function.
060     * <p>
061     * Please use only the attributes exposed through the attached
062     * {@link JMathComponentBeanInfo} class. Additional attributes, such as
063     * {@link #getFont()} and {@link #setFont(Font)} are provided for Swing
064     * compatibility, but they may not work exactly as expected.
065     * 
066     * @see net.sourceforge.jeuclid.awt.MathComponent
067     * @version $Revision: f1695c1926a6 $
068     */
069    public final class JMathComponent extends JComponent implements
070            SwingConstants {
071    
072        private static final String FONT_SEPARATOR = ",";
073    
074        /**
075         * Logger for this class
076         */
077        private static final Log LOGGER = LogFactory.getLog(JMathComponent.class);
078    
079        /** */
080        private static final long serialVersionUID = 1L;
081    
082        private static String uiClassId;
083    
084        private static Class<?> mathComponentUIClass;
085    
086        private Node document;
087    
088        private int horizontalAlignment = SwingConstants.CENTER;
089    
090        private final MutableLayoutContext parameters = new LayoutContextImpl(
091                LayoutContextImpl.getDefaultLayoutContext());
092    
093        private int verticalAlignment = SwingConstants.CENTER;
094    
095        /**
096         * cursor listener instance.
097         */
098        private final CursorListener cursorListener;
099    
100        /**
101         * Default constructor.
102         */
103        public JMathComponent() {
104            this(null);
105        }
106    
107        /**
108         * Default constructor with cursor listener.
109         * 
110         * @param listener
111         *            cursor listener instance
112         */
113        public JMathComponent(final CursorListener listener) {
114            this.cursorListener = listener;
115    
116            final JMathComponentMouseListener mouseListener = new JMathComponentMouseListener(
117                    this);
118            this.addMouseListener(mouseListener);
119    
120            this.updateUI();
121            this.fontCompat();
122            this.setDocument(new DocumentElement());
123        }
124    
125        /**
126         * gets cursor listener instance.
127         * 
128         * @return cursor listener instance
129         */
130        public CursorListener getCursorListener() {
131            return this.cursorListener;
132        }
133    
134        /**
135         * Provide compatibility for standard get/setFont() operations.
136         */
137        private void fontCompat() {
138            final String fontName = this.getFontsSerif().split(
139                    JMathComponent.FONT_SEPARATOR)[0];
140            final float fontSize = this.getFontSize();
141            super.setFont(new Font(fontName, 0, (int) fontSize));
142        }
143    
144        /**
145         * Tries to return the content as a String.
146         * <p>
147         * This transforms the internal DOM tree back into a string, which may is
148         * not guaranteed to be the literally same as the original content.
149         * However, it will represent the same XML document.
150         * 
151         * @return the content string.
152         */
153        public String getContent() {
154            return MathMLSerializer.serializeDocument(this.getDocument(), false,
155                    false);
156        }
157    
158        /**
159         * @return the document
160         */
161        public Node getDocument() {
162            return this.document;
163        }
164    
165        private static String join(final List<String> list) {
166            boolean first = true;
167            final StringBuilder b = new StringBuilder();
168            for (final String s : list) {
169                if (first) {
170                    first = false;
171                } else {
172                    b.append(JMathComponent.FONT_SEPARATOR);
173                }
174                b.append(s);
175            }
176            return b.toString();
177        }
178    
179        /**
180         * Font list for Doublestruck. Please see
181         * {@link Parameter#FontsDoublestruck} for an explanation of this
182         * parameter.
183         * 
184         * @return The list for Doublestruck.
185         * @see Parameter#FontsDoublestruck
186         */
187        @SuppressWarnings("unchecked")
188        public String getFontsDoublestruck() {
189            return JMathComponent.join((List<String>) this.parameters
190                    .getParameter(Parameter.FONTS_DOUBLESTRUCK));
191        }
192    
193        /**
194         * Font list for Fraktur. Please see {@link Parameter#FontsFraktur} for an
195         * explanation of this parameter.
196         * 
197         * @return The list for Fraktur.
198         * @see Parameter#FontsFraktur
199         */
200        @SuppressWarnings("unchecked")
201        public String getFontsFraktur() {
202            return JMathComponent.join((List<String>) this.parameters
203                    .getParameter(Parameter.FONTS_FRAKTUR));
204        }
205    
206        /**
207         * @return the fontSize
208         */
209        public float getFontSize() {
210            return (Float) this.parameters.getParameter(Parameter.MATHSIZE);
211        }
212    
213        /**
214         * Font list for Monospaced. Please see {@link Parameter#FontsMonospaced}
215         * for an explanation of this parameter.
216         * 
217         * @return The list for monospaced.
218         * @see Parameter#FontsMonospaced
219         */
220        @SuppressWarnings("unchecked")
221        public String getFontsMonospaced() {
222            return JMathComponent.join((List<String>) this.parameters
223                    .getParameter(Parameter.FONTS_MONOSPACED));
224        }
225    
226        /**
227         * Font list for Sans-Serif. Please see {@link Parameter#FontsSanserif}
228         * for an explanation of this parameter.
229         * 
230         * @return The list for sansserif.
231         * @see Parameter#FontsSanserif
232         */
233        @SuppressWarnings("unchecked")
234        public String getFontsSanserif() {
235            return JMathComponent.join((List<String>) this.parameters
236                    .getParameter(Parameter.FONTS_SANSSERIF));
237        }
238    
239        /**
240         * Font list for Script. Please see {@link Parameter#FontsScript} for an
241         * explanation of this parameter.
242         * 
243         * @return The list for Script.
244         * @see Parameter#FontsScript
245         */
246        @SuppressWarnings("unchecked")
247        public String getFontsScript() {
248            return JMathComponent.join((List<String>) this.parameters
249                    .getParameter(Parameter.FONTS_SCRIPT));
250        }
251    
252        /**
253         * Font list for Serif (the default MathML font). Please see
254         * {@link Parameter#FontsSerif} for an explanation of this parameter.
255         * 
256         * @return The list for serif.
257         * @see Parameter#FontsSerif
258         */
259        @SuppressWarnings("unchecked")
260        public String getFontsSerif() {
261            return JMathComponent.join((List<String>) this.parameters
262                    .getParameter(Parameter.FONTS_SERIF));
263        }
264    
265        /** {@inheritDoc} */
266        @Override
267        public Color getForeground() {
268            return (Color) this.parameters.getParameter(Parameter.MATHCOLOR);
269        }
270    
271        /**
272         * Horizontal alignment, as defined by
273         * {@link javax.swing.JLabel#getHorizontalAlignment()}.
274         * <p>
275         * Supported are: {@link SwingConstants#LEADING},
276         * {@link SwingConstants#LEFT}, {@link SwingConstants#CENTER},
277         * {@link SwingConstants#TRAILING}, {@link SwingConstants#RIGHT}.
278         * 
279         * @return the horizontalAlignment
280         * @see javax.swing.JLabel#getHorizontalAlignment()
281         */
282        public int getHorizontalAlignment() {
283            return this.horizontalAlignment;
284        }
285    
286        /**
287         * @return the UI implementation.
288         */
289        public MathComponentUI getUI() {
290            return (MathComponentUI) this.ui;
291        }
292    
293        /**
294         * @return The default UI class
295         */
296        @Override
297        public String getUIClassID() {
298            return JMathComponent.uiClassId;
299        }
300    
301        /**
302         * Vertical alignment, as defined by
303         * {@link javax.swing.JLabel#getVerticalAlignment()}.
304         * <p>
305         * Supported are: {@link SwingConstants#TOP},
306         * {@link SwingConstants#CENTER}, {@link SwingConstants#BOTTOM}.
307         * 
308         * @return the verticalAlignment
309         * @see javax.swing.JLabel#getVerticalAlignment()
310         */
311        public int getVerticalAlignment() {
312            return this.verticalAlignment;
313        }
314    
315        private void reval() {
316            this.repaint();
317            this.revalidate();
318        }
319    
320        /** {@inheritDoc} */
321        @Override
322        public void setBackground(final Color c) {
323            super.setBackground(c);
324            this.reval();
325        }
326    
327        /**
328         * Set the content from a String containing the MathML content.
329         * 
330         * @param contentString
331         *            the content to set.
332         */
333        public void setContent(final String contentString) {
334            try {
335                final Document stdDomNode = MathMLParserSupport.parseString(contentString); 
336                final DocumentElement jEuclidDom = DOMBuilder.getInstance().createJeuclidDom(stdDomNode,
337                        true, true);
338                this.setDocument(jEuclidDom);
339            } catch (final SAXException e) {
340                throw new IllegalArgumentException(e);
341            } catch (final ParserConfigurationException e) {
342                throw new IllegalArgumentException(e);
343            } catch (final IOException e) {
344                throw new IllegalArgumentException(e);
345            }
346    
347        }
348    
349        /**
350         * Enables, or disables the debug mode.
351         * 
352         * @param dbg
353         *            Debug mode.
354         */
355        public void setDebug(final boolean dbg) {
356            this.setParameter(Parameter.DEBUG, dbg);
357        }
358    
359        /**
360         * @param doc
361         *            the document to set
362         */
363        public void setDocument(final Node doc) {
364            final Node oldValue = this.document;
365            this.firePropertyChange("document", oldValue, doc);
366            this.document = doc;
367            if (doc != oldValue) {
368                this.revalidate();
369                this.repaint();
370            }
371        }
372    
373        /**
374         * Font emulator for standard component behavior.
375         * <p>
376         * Emulates the standard setFont function by setting the font Size and
377         * adding the font to the front of the serif font list.
378         * <p>
379         * Please use the separate setters if possible.
380         * 
381         * @param f
382         *            font to set.
383         * @see #setFontSize(float)
384         * @see #setFontsSerif(String)
385         * @deprecated use separate setters.
386         */
387        @Deprecated
388        @Override
389        public void setFont(final Font f) {
390            super.setFont(f);
391            this.setFontSize(f.getSize2D());
392            this.setFontsSerif(f.getFamily() + JMathComponent.FONT_SEPARATOR
393                    + this.getFontsSerif());
394        }
395    
396        private List<String> splitFonts(final String list) {
397            return Arrays.asList(list.split(JMathComponent.FONT_SEPARATOR));
398        }
399    
400        /**
401         * Font list for Doublestruck. Please see
402         * {@link Parameter#FONTS_DOUBLESTRUCK} for an explanation of this
403         * parameter.
404         * 
405         * @param newFonts
406         *            new list for Doublestruck (comma seraparated).
407         * @see Parameter#FONTS_DOUBLESTRUCK
408         */
409        public void setFontsDoublestruck(final String newFonts) {
410            this.setParameter(Parameter.FONTS_DOUBLESTRUCK, this
411                    .splitFonts(newFonts));
412        }
413    
414        /**
415         * Font list for Fraktur. Please see {@link Parameter#FONTS_FRAKTUR} for
416         * an explanation of this parameter.
417         * 
418         * @param newFonts
419         *            new list for Fraktur (comma seraparated).
420         * @see Parameter#FONTS_FRAKTUR
421         */
422        public void setFontsFraktur(final String newFonts) {
423            this.setParameter(Parameter.FONTS_FRAKTUR, this.splitFonts(newFonts));
424        }
425    
426        /**
427         * Sets a generic rendering parameter.
428         * 
429         * @param key
430         *            Key for the parameter
431         * @param newValue
432         *            newValue
433         */
434        public void setParameter(final Parameter key, final Object newValue) {
435            this.setParameters(Collections.singletonMap(key, newValue));
436        }
437    
438        /**
439         * Sets generic rendering parameters.
440         * 
441         * @param newValues
442         *            map of parameter keys to new values
443         */
444        public void setParameters(final Map<Parameter, Object> newValues) {
445            for (final Map.Entry<Parameter, Object> entry : newValues.entrySet()) {
446                final Parameter key = entry.getKey();
447                final Object oldValue = this.parameters.getParameter(key);
448                this.parameters.setParameter(key, entry.getValue());
449                this.firePropertyChange(key.name(), oldValue, this.parameters
450                        .getParameter(key));
451            }
452            this.revalidate();
453            this.repaint();
454        }
455    
456        /**
457         * sets the font size used.
458         * 
459         * @param fontSize
460         *            the font size.
461         */
462        public void setFontSize(final float fontSize) {
463            this.setParameter(Parameter.MATHSIZE, fontSize);
464        }
465    
466        /**
467         * Font list for Monospaced. Please see {@link Parameter#FONTS_MONOSPACED}
468         * for an explanation of this parameter.
469         * 
470         * @param newFonts
471         *            new list for Monospaced (comma seraparated).
472         * @see Parameter#FONTS_MONOSPACED
473         */
474        public void setFontsMonospaced(final String newFonts) {
475            this.setParameter(Parameter.FONTS_MONOSPACED, this
476                    .splitFonts(newFonts));
477        }
478    
479        /**
480         * Font list for Sans-Serif. Please see {@link Parameter#FONTS_SANSSERIF}
481         * for an explanation of this parameter.
482         * 
483         * @param newFonts
484         *            new list for sansserif (comma seraparated).
485         * @see Parameter#FONTS_SANSSERIF
486         */
487        public void setFontsSanserif(final String newFonts) {
488            this.setParameter(Parameter.FONTS_SANSSERIF, this
489                    .splitFonts(newFonts));
490        }
491    
492        /**
493         * Font list for Script. Please see {@link Parameter#FONTS_SCRIPT} for an
494         * explanation of this parameter.
495         * 
496         * @param newFonts
497         *            new list for Script (comma seraparated).
498         * @see Parameter#FONTS_SCRIPT
499         */
500        public void setFontsScript(final String newFonts) {
501            this.setParameter(Parameter.FONTS_SCRIPT, this.splitFonts(newFonts));
502        }
503    
504        /**
505         * Font list for Serif (the default MathML font). Please see
506         * {@link Parameter#FONTS_SERIF} for an explanation of this parameter.
507         * 
508         * @param newFonts
509         *            new list for serif (comma seraparated).
510         * @see Parameter#FONTS_SERIF
511         */
512        public void setFontsSerif(final String newFonts) {
513            this.setParameter(Parameter.FONTS_SERIF, this.splitFonts(newFonts));
514            this.fontCompat();
515        }
516    
517        /** {@inheritDoc} */
518        @Override
519        public void setForeground(final Color fg) {
520            super.setForeground(fg);
521            this.setParameter(Parameter.MATHCOLOR, fg);
522        }
523    
524        /**
525         * Horizontal alignment, as defined by
526         * {@link javax.swing.JLabel#setHorizontalAlignment(int)}.
527         * <p>
528         * Supported are: {@link SwingConstants#LEADING},
529         * {@link SwingConstants#LEFT}, {@link SwingConstants#CENTER},
530         * {@link SwingConstants#TRAILING}, {@link SwingConstants#RIGHT}.
531         * 
532         * @param hAlignment
533         *            the horizontalAlignment to set
534         * @see javax.swing.JLabel#setHorizontalAlignment(int)
535         */
536        public void setHorizontalAlignment(final int hAlignment) {
537            this.horizontalAlignment = hAlignment;
538        }
539    
540        /** {@inheritDoc} */
541        @Override
542        public void setOpaque(final boolean opaque) {
543            super.setOpaque(opaque);
544            this.reval();
545        }
546    
547        /**
548         * Vertical alignment, as defined by
549         * {@link javax.swing.JLabel#setVerticalAlignment(int)}.
550         * <p>
551         * Supported are: {@link SwingConstants#TOP},
552         * {@link SwingConstants#CENTER}, {@link SwingConstants#BOTTOM}.
553         * 
554         * @param vAlignment
555         *            the verticalAlignment to set
556         * @see javax.swing.JLabel#setVerticalAlignment(int)
557         */
558        public void setVerticalAlignment(final int vAlignment) {
559            this.verticalAlignment = vAlignment;
560        }
561    
562        /** {@inheritDoc} */
563        @Override
564        public void updateUI() {
565            if (UIManager.get(this.getUIClassID()) == null) {
566                try {
567                    this
568                            .setUI((MathComponentUI) JMathComponent.mathComponentUIClass
569                                    .newInstance());
570                } catch (final InstantiationException e) {
571                    JMathComponent.LOGGER.warn(e.getMessage());
572                } catch (final IllegalAccessException e) {
573                    JMathComponent.LOGGER.warn(e.getMessage());
574                }
575            } else {
576                this.setUI(UIManager.getUI(this));
577            }
578        }
579    
580        /**
581         * @return the parameters
582         */
583        public MutableLayoutContext getParameters() {
584            return this.parameters;
585        }
586    
587        /** {@inheritDoc} */
588        @Override
589        public void setSize(final int width, final int height) {
590            // TODO Auto-generated method stub
591            super.setSize(width, height);
592        }
593    
594        static {
595            Class<?> uiClass;
596            String id;
597            try {
598                uiClass = ClassLoaderSupport.getInstance().loadClass(
599                        "net.sourceforge.jeuclid.swing.MathComponentUI16");
600                id = "MathComponentUI16";
601            } catch (final ClassNotFoundException t) {
602                uiClass = MathComponentUI.class;
603                id = "MathComponentUI";
604            }
605            JMathComponent.uiClassId = id;
606            JMathComponent.mathComponentUIClass = uiClass;
607        }
608    
609    }