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 }