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: MathComponentUI.java,v 950f3d880cc1 2010/08/09 21:18:26 max $ */
018
019 package net.sourceforge.jeuclid.swing;
020
021 import java.awt.Color;
022 import java.awt.Dimension;
023 import java.awt.Graphics;
024 import java.awt.Graphics2D;
025 import java.awt.Insets;
026 import java.awt.Point;
027 import java.awt.geom.Point2D;
028 import java.beans.PropertyChangeEvent;
029 import java.beans.PropertyChangeListener;
030 import java.lang.ref.Reference;
031 import java.lang.ref.SoftReference;
032 import java.util.HashMap;
033 import java.util.List;
034 import java.util.Map;
035
036 import javax.swing.JComponent;
037 import javax.swing.LookAndFeel;
038 import javax.swing.SwingConstants;
039 import javax.swing.border.Border;
040 import javax.swing.plaf.ComponentUI;
041
042 import net.sourceforge.jeuclid.LayoutContext;
043 import net.sourceforge.jeuclid.layout.JEuclidView;
044
045 import org.w3c.dom.Node;
046
047 /**
048 * See http://today.java.net/pub/a/today/2007/02/22/how-to-write-custom-swing-
049 * component.html for details.
050 *
051 * @version $Revision: 950f3d880cc1 $
052 *
053 */
054 public class MathComponentUI extends ComponentUI implements
055 PropertyChangeListener {
056
057 // /**
058 // * Logger for this class
059 // */
060 // Currently Unused
061 // private static final Log LOGGER =
062 // LogFactory.getLog(MathComponentUI.class);
063
064 private final Map<JMathComponent, Reference<ViewContext>> contextCache = new HashMap<JMathComponent, Reference<ViewContext>>();
065
066 /**
067 * Default constructor.
068 */
069 public MathComponentUI() {
070 super();
071 // nothing to do.
072 }
073
074 private JEuclidView getJeuclidView(final Graphics g, final JComponent c) {
075 JMathComponent jc = (JMathComponent) c;
076 ViewContext cache = null;
077 Reference<ViewContext> ref = contextCache.get(jc);
078 if (ref != null) {
079 cache = ref.get();
080 }
081
082 if (cache == null) {
083 cache = new ViewContext(jc);
084 contextCache.put(jc, new SoftReference<ViewContext>(cache));
085 }
086
087 return cache.getJeculidView((Graphics2D) g);
088 }
089
090 /** {@inheritDoc} */
091 @Override
092 public void paint(final Graphics g, final JComponent c) {
093 JEuclidView jEuclidView = this.getJeuclidView(g, c);
094 final Dimension dim = this.calculatePreferredSize(c, jEuclidView);
095 final Point start = this.getStartPointWithBordersAndAdjustDimension(c,
096 dim);
097 this.paintBackground(g, c, dim, start);
098
099 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 }