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 }