View Javadoc

1   /*
2    * Copyright 2002 - 2008 JEuclid, http://jeuclid.sf.net
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  /* $Id: MathComponentUI.java,v 950f3d880cc1 2010/08/09 21:18:26 max $ */
18  
19  package net.sourceforge.jeuclid.swing;
20  
21  import java.awt.Color;
22  import java.awt.Dimension;
23  import java.awt.Graphics;
24  import java.awt.Graphics2D;
25  import java.awt.Insets;
26  import java.awt.Point;
27  import java.awt.geom.Point2D;
28  import java.beans.PropertyChangeEvent;
29  import java.beans.PropertyChangeListener;
30  import java.lang.ref.Reference;
31  import java.lang.ref.SoftReference;
32  import java.util.HashMap;
33  import java.util.List;
34  import java.util.Map;
35  
36  import javax.swing.JComponent;
37  import javax.swing.LookAndFeel;
38  import javax.swing.SwingConstants;
39  import javax.swing.border.Border;
40  import javax.swing.plaf.ComponentUI;
41  
42  import net.sourceforge.jeuclid.LayoutContext;
43  import net.sourceforge.jeuclid.layout.JEuclidView;
44  
45  import org.w3c.dom.Node;
46  
47  /**
48   * See http://today.java.net/pub/a/today/2007/02/22/how-to-write-custom-swing-
49   * component.html for details.
50   * 
51   * @version $Revision: 950f3d880cc1 $
52   * 
53   */
54  public class MathComponentUI extends ComponentUI implements
55          PropertyChangeListener {
56  
57      // /**
58      // * Logger for this class
59      // */
60      // Currently Unused
61      // private static final Log LOGGER =
62      // LogFactory.getLog(MathComponentUI.class);
63  
64      private final Map<JMathComponent, Reference<ViewContext>> contextCache = new HashMap<JMathComponent, Reference<ViewContext>>();
65  
66      /**
67       * Default constructor.
68       */
69      public MathComponentUI() {
70          super();
71          // nothing to do.
72      }
73  
74      private JEuclidView getJeuclidView(final Graphics g, final JComponent c) {
75          JMathComponent jc = (JMathComponent) c;
76          ViewContext cache = null;
77          Reference<ViewContext> ref = contextCache.get(jc);
78          if (ref != null) {
79              cache = ref.get();
80          }
81  
82          if (cache == null) {
83              cache = new ViewContext(jc);
84              contextCache.put(jc, new SoftReference<ViewContext>(cache));
85          }
86  
87          return cache.getJeculidView((Graphics2D) g);
88      }
89  
90      /** {@inheritDoc} */
91      @Override
92      public void paint(final Graphics g, final JComponent c) {
93          JEuclidView jEuclidView = this.getJeuclidView(g, c);
94          final Dimension dim = this.calculatePreferredSize(c, jEuclidView);
95          final Point start = this.getStartPointWithBordersAndAdjustDimension(c,
96                  dim);
97          this.paintBackground(g, c, dim, start);
98  
99          final Point2D alignOffset = this.calculateAlignmentOffset(
100                 (JMathComponent) c, jEuclidView, dim);
101         jEuclidView.draw((Graphics2D) g, (float) alignOffset.getX() + start.x,
102                 (float) alignOffset.getY() + start.y);
103 
104     }
105 
106     /** {@inheritDoc} */
107     @Override
108     public void update(final Graphics g, final JComponent c) {
109         if (c.isOpaque()) {
110             g.setColor(c.getBackground());
111             g.fillRect(0, 0, c.getWidth(), c.getHeight());
112         }
113         this.paint(g, c);
114     }
115 
116     private Point2D calculateAlignmentOffset(JMathComponent jc,
117             JEuclidView jEuclidView, final Dimension dim) {
118         final float xo;
119         if ((jc.getHorizontalAlignment() == SwingConstants.LEADING)
120                 || (jc.getHorizontalAlignment() == SwingConstants.LEFT)) {
121             xo = 0.0f;
122         } else if ((jc.getHorizontalAlignment() == SwingConstants.TRAILING)
123                 || (jc.getHorizontalAlignment() == SwingConstants.RIGHT)) {
124             xo = dim.width - jEuclidView.getWidth();
125         } else {
126             xo = (dim.width - jEuclidView.getWidth()) / 2.0f;
127         }
128         final float yo;
129         if (jc.getVerticalAlignment() == SwingConstants.TOP) {
130             yo = jEuclidView.getAscentHeight();
131         } else if (jc.getVerticalAlignment() == SwingConstants.BOTTOM) {
132             yo = dim.height - jEuclidView.getDescentHeight();
133         } else {
134             yo = (dim.height + jEuclidView.getAscentHeight() - jEuclidView
135                     .getDescentHeight()) / 2.0f;
136         }
137         return new Point2D.Float(xo, yo);
138     }
139 
140     private void paintBackground(final Graphics g, JComponent c,
141             final Dimension dim, final Point start) {
142         final Color back = this.getRealBackgroundColor(c);
143         if (back != null) {
144             g.setColor(back);
145             g.fillRect(start.x, start.y, dim.width, dim.height);
146         }
147     }
148 
149     private Point getStartPointWithBordersAndAdjustDimension(JComponent c,
150             final Dimension dim) {
151         Point start = new Point(0, 0);
152         final Border border = c.getBorder();
153         if (border != null) {
154             final Insets insets = border.getBorderInsets(c);
155             if (insets != null) {
156                 dim.width -= insets.left + insets.right;
157                 dim.height -= insets.top + insets.bottom;
158                 start = new Point(insets.left, insets.top);
159             }
160         }
161         return start;
162     }
163 
164     private Color getRealBackgroundColor(JComponent c) {
165         Color back = c.getBackground();
166         if (c.isOpaque()) {
167             if (back == null) {
168                 back = Color.WHITE;
169             }
170             // Remove Alpha
171             back = new Color(back.getRGB());
172         }
173         return back;
174     }
175 
176     /** {@inheritDoc} */
177     @Override
178     public void installUI(final JComponent c) {
179         if (c instanceof JMathComponent) {
180             c.addPropertyChangeListener(this);
181             this.installDefaults(c);
182         } else {
183             throw new IllegalArgumentException(
184                     "This UI can only be installed on a JMathComponent");
185         }
186     }
187 
188     /**
189      * Configures the default properties from L&F.
190      * 
191      * @param c
192      *            the component
193      */
194     protected void installDefaults(final JComponent c) {
195         // LookAndFeel.installColorsAndFont(c, "Label.background",
196         // "Label.foreground", "Label.font");
197         LookAndFeel.installProperty(c, "opaque", Boolean.FALSE);
198     }
199 
200     /** {@inheritDoc} */
201     @Override
202     public void uninstallUI(final JComponent c) {
203         c.removePropertyChangeListener(this);
204         this.contextCache.remove(c);
205     }
206 
207     /** {@inheritDoc} */
208     public void propertyChange(final PropertyChangeEvent evt) {
209         this.contextCache.remove(evt.getSource());
210     }
211 
212     /** {@inheritDoc} */
213     @Override
214     public Dimension getPreferredSize(final JComponent c) {
215         return this.getMathComponentSize(c);
216     }
217 
218     /**
219      * Retrieve the preferred size of the math component.
220      * 
221      * @param c
222      *            the math component to measure
223      * @return the preferred size.
224      */
225     private Dimension getMathComponentSize(final JComponent c) {
226         JEuclidView jEuclidView = this.getJeuclidView(c.getGraphics(), c);
227         return this.calculatePreferredSize(c, jEuclidView);
228     }
229 
230     private Dimension calculatePreferredSize(final JComponent c,
231             JEuclidView jEuclidView) {
232         Dimension retVal;
233         retVal = new Dimension((int) Math.ceil(jEuclidView.getWidth()),
234                 (int) Math.ceil(jEuclidView.getAscentHeight()
235                         + jEuclidView.getDescentHeight()));
236 
237         final Border border = c.getBorder();
238         if (border != null) {
239             final Insets insets = border.getBorderInsets(c);
240             if (insets != null) {
241                 retVal.width += insets.left + insets.right;
242                 retVal.height += insets.top + insets.bottom;
243             }
244         }
245         return retVal;
246     }
247 
248     /** {@inheritDoc} */
249     @Override
250     public Dimension getMaximumSize(final JComponent c) {
251         return this.getMathComponentSize(c);
252     }
253 
254     /** {@inheritDoc} */
255     @Override
256     public Dimension getMinimumSize(final JComponent c) {
257         return this.getMathComponentSize(c);
258     }
259 
260     /**
261      * Get vector of {@link JEuclidView.NodeRect} at a particular mouse
262      * position.
263      * 
264      * @param mathComponent
265      *            MathComponent to look in.
266      * @param x
267      *            x-coord
268      * @param y
269      *            y-coord
270      * @return list of nodes with rendering information
271      */
272     public List<JEuclidView.NodeRect> getNodesAt(JMathComponent mathComponent,
273             final float x, final float y) {
274         JEuclidView jEuclidView = getJeuclidView(mathComponent.getGraphics(),
275                 mathComponent);
276         final Point2D point = this.calculateAlignmentOffset(mathComponent,
277                 jEuclidView, mathComponent.getSize());
278         return jEuclidView.getNodesAt(x, y, (float) point.getX(),
279                 (float) point.getY());
280     }
281 
282     private static class ViewContext {
283         final Node document;
284         final LayoutContext layoutContext;
285         final Map<Graphics2D, JEuclidView> jeuclidViews = new HashMap<Graphics2D, JEuclidView>();
286 
287         public ViewContext(JMathComponent jMathComponent) {
288             this.document = jMathComponent.getDocument();
289             this.layoutContext = jMathComponent.getParameters();
290         }
291 
292         public JEuclidView getJeculidView(Graphics2D g2d) {
293             JEuclidView jeuclidView = jeuclidViews.get(g2d);
294             if (jeuclidView == null) {
295                 jeuclidView = new JEuclidView(document, layoutContext, g2d);
296                 jeuclidViews.put(g2d, jeuclidView);
297             }
298             return jeuclidView;
299         }
300     }
301 
302 }