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    }