001    /**
002     * Copyright (c) 2008-2009, Piet Blok
003     * All rights reserved.
004     *
005     * Redistribution and use in source and binary forms, with or without
006     * modification, are permitted provided that the following conditions
007     * are met:
008     *
009     *   * Redistributions of source code must retain the above copyright
010     *     notice, this list of conditions and the following disclaimer.
011     *   * Redistributions in binary form must reproduce the above
012     *     copyright notice, this list of conditions and the following
013     *     disclaimer in the documentation and/or other materials provided
014     *     with the distribution.
015     *   * Neither the name of the copyright holder nor the names of the
016     *     contributors may be used to endorse or promote products derived
017     *     from this software without specific prior written permission.
018     *
019     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
020     * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
021     * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
022     * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
023     * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
024     * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
025     * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
026     * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
027     * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
028     * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
029     * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
030     */
031    
032    package org.pbjar.jxlayer.plaf.misc;
033    
034    import java.awt.Color;
035    import java.awt.Graphics2D;
036    import java.awt.MultipleGradientPaint;
037    import java.awt.Paint;
038    import java.awt.RadialGradientPaint;
039    import java.awt.RenderingHints;
040    import java.awt.Shape;
041    import java.awt.Toolkit;
042    import java.awt.event.AWTEventListener;
043    import java.awt.event.ActionEvent;
044    import java.awt.event.MouseEvent;
045    import java.awt.geom.AffineTransform;
046    import java.awt.geom.Ellipse2D;
047    import java.awt.geom.Point2D;
048    import java.util.ArrayList;
049    import java.util.List;
050    
051    import javax.swing.AbstractAction;
052    import javax.swing.Action;
053    import javax.swing.JComponent;
054    import javax.swing.JOptionPane;
055    import javax.swing.SwingUtilities;
056    
057    import org.jdesktop.jxlayer.JXLayer;
058    
059    /**
060     * Shows a magnification glass on top of a component.
061     * 
062     * @author Piet Blok
063     */
064    public class MagnifierUI extends
065            GeneralLayerUI<JComponent, MagnifierUI.MagnifierState> {
066    
067        /**
068         * Holds state information.
069         */
070        protected static class MagnifierState {
071    
072            private int radius = radiusOptions[6];
073    
074            private double magnifyingFactor = magnificationOptions[5];
075    
076            private Point2D point = new Point2D.Double();
077    
078            /**
079             * Get the magnifying factor.
080             * 
081             * @return the magnifying factor
082             * @see #setMagnifyingFactor(double)
083             */
084            public double getMagnifyingFactor() {
085                return magnifyingFactor;
086            }
087    
088            /**
089             * Get the mouse point.
090             * 
091             * @return return the mouse point
092             * @see #setPoint(Point2D)
093             */
094            public Point2D getPoint() {
095                return point;
096            }
097    
098            /**
099             * Get the radius.
100             * 
101             * @return the radius
102             * @see #setRadius(int)
103             */
104            public int getRadius() {
105                return radius;
106            }
107    
108            /**
109             * Set the magnifying factor.
110             * 
111             * @param magnifyingFactor
112             *            the new magnifying factor
113             * @see #getMagnifyingFactor()
114             */
115            public void setMagnifyingFactor(double magnifyingFactor) {
116                this.magnifyingFactor = magnifyingFactor;
117            }
118    
119            /**
120             * Set the mouse point.
121             * 
122             * @param point
123             *            the new mouse point
124             * @see #getPoint()
125             */
126            public void setPoint(Point2D point) {
127                this.point.setLocation(point);
128            }
129    
130            /**
131             * Set the radius.
132             * 
133             * @param radius
134             *            the new radius
135             * @see #getRadius()
136             */
137            public void setRadius(int radius) {
138                this.radius = radius;
139            }
140    
141        }
142    
143        private static final Integer[] radiusOptions = new Integer[] { 20, 40, 60,
144                80, 100, 120, 140, 160, 180, 200 };
145    
146        private static final Double[] magnificationOptions = new Double[] { 0.25,
147                0.5, 0.75, 1.0, 2.0, 4.0, 8.0, 16.0 };
148    
149        /**
150         * Create an instance with {@link AWTEventListener} disabled.
151         */
152        public MagnifierUI() {
153            this(false);
154        }
155    
156        /**
157         * Create an instance.
158         * <p>
159         * Via the enableAWTEventListener argument, {@link AWTEventListener} can be
160         * enabled or disabled.
161         * </p>
162         * 
163         * @param enableAWTEventListener
164         *            if {@code true}, {@link AWTEventListener} will be enabled.
165         *            Specifying {@code true} Solves a problem with non editable
166         *            {@code JTextComponent}s.
167         * @throws SecurityException
168         *             when enableAWTEventListener is {@code true} and if a security
169         *             manager exists and its {@code checkPermission} method doesn't
170         *             allow the operation.
171         * @see Toolkit#addAWTEventListener(AWTEventListener, long)
172         * */
173        public MagnifierUI(boolean enableAWTEventListener) {
174            super(enableAWTEventListener);
175        }
176    
177        /**
178         * Return {@link Action}s that:
179         * <ol>
180         * <li>Set magnification factor.</li>
181         * <li>Set the glass radius.</li>
182         * </ol>
183         */
184        @Override
185        public List<Action> getActions(final JXLayer<JComponent> layer) {
186            ArrayList<Action> actionList = new ArrayList<Action>();
187            actionList.addAll(super.getActions(layer));
188            /*
189             * Set the magnifying factor
190             */
191            actionList.add(new AbstractAction("Set magnification factor") {
192    
193                private static final long serialVersionUID = 1L;
194    
195                @Override
196                public void actionPerformed(ActionEvent event) {
197                    MagnifierState state = getStateObject(layer);
198                    Double current = state.getMagnifyingFactor();
199                    int optionIndex = JOptionPane.showOptionDialog(layer,
200                            "Choose a magnification factor",
201                            "Choose magnification", JOptionPane.OK_CANCEL_OPTION,
202                            JOptionPane.QUESTION_MESSAGE, null,
203                            magnificationOptions, current);
204                    if (optionIndex != JOptionPane.CLOSED_OPTION) {
205                        state
206                                .setMagnifyingFactor(magnificationOptions[optionIndex]);
207                    }
208                }
209            });
210            /*
211             * Set the glass radius
212             */
213            actionList.add(new AbstractAction("Set glass radius") {
214    
215                private static final long serialVersionUID = 1L;
216    
217                @Override
218                public void actionPerformed(ActionEvent event) {
219                    MagnifierState state = getStateObject(layer);
220                    Integer current = state.getRadius();
221                    int optionIndex = JOptionPane.showOptionDialog(layer,
222                            "Choose a glass radius", "Choose glass radius",
223                            JOptionPane.OK_CANCEL_OPTION,
224                            JOptionPane.QUESTION_MESSAGE, null, radiusOptions,
225                            current);
226                    if (optionIndex != JOptionPane.CLOSED_OPTION) {
227                        state.setRadius(radiusOptions[optionIndex]);
228                    }
229                }
230            });
231    
232            return actionList;
233        }
234    
235        private Paint createPaint(Ellipse2D glass, boolean transparent) {
236            Point2D center = new Point2D.Double(glass.getCenterX(), glass
237                    .getCenterY());
238            float radius = (float) (glass.getCenterX() - glass.getX());
239            Point2D focus = new Point2D.Double(center.getX() - 0.5 * radius, center
240                    .getY()
241                    - 0.5 * radius);
242            Color[] colors = new Color[] {
243                    transparent ? new Color(255, 255, 255, 128) : Color.WHITE,
244                    transparent ? new Color(0, 255, 255, 32) : Color.CYAN };
245            float[] fractions = new float[] { 0f, 1f };
246            RadialGradientPaint paint = new RadialGradientPaint(center, radius,
247                    focus, fractions, colors,
248                    MultipleGradientPaint.CycleMethod.NO_CYCLE);
249            return paint;
250        }
251    
252        @Override
253        protected MagnifierState createStateObject(JXLayer<JComponent> layer) {
254            return new MagnifierState();
255        }
256    
257        @Override
258        protected void paintLayer(Graphics2D g2, JXLayer<JComponent> layer) {
259            super.paintLayer(g2, layer);
260            MagnifierState state = getStateObject(layer);
261            Point2D point = state.getPoint();
262            double scale = state.getMagnifyingFactor();
263            double baseRadius = state.getRadius();
264            double scaledRadius = (baseRadius / scale);
265            double strokeAdjust = 0.5;
266            double drawSize = 2 * (baseRadius + strokeAdjust);
267            double clipSize = 2 * scaledRadius;
268            Ellipse2D drawGlass = new Ellipse2D.Double(-strokeAdjust,
269                    -strokeAdjust, drawSize, drawSize);
270            Ellipse2D clipGlass = new Ellipse2D.Double(0, 0, clipSize, clipSize);
271            g2.setRenderingHint(RenderingHints.KEY_RENDERING,
272                    RenderingHints.VALUE_RENDER_QUALITY);
273            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
274                    RenderingHints.VALUE_ANTIALIAS_ON);
275            g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
276                    RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
277            g2.translate(point.getX() - baseRadius, point.getY() - baseRadius);
278            Color oldColor = g2.getColor();
279            g2.setPaint(createPaint(drawGlass, false));
280            g2.fill(drawGlass);
281            g2.setColor(oldColor);
282            g2.draw(drawGlass);
283            AffineTransform oldTransform = g2.getTransform();
284            Shape oldClip = g2.getClip();
285            g2.scale(scale, scale);
286            g2.clip(clipGlass);
287            g2.translate(scaledRadius - point.getX(), scaledRadius - point.getY());
288            // layer.paint(g2);
289            super.paintLayer(g2, layer);
290            g2.setTransform(oldTransform);
291            g2.setClip(oldClip);
292            g2.setPaint(createPaint(drawGlass, true));
293            g2.fill(drawGlass);
294        }
295    
296        @Override
297        protected void processMouseEvent(MouseEvent e, JXLayer<JComponent> layer) {
298            super.processMouseEvent(e, layer);
299            MagnifierState state = getStateObject(layer);
300            state.setPoint(SwingUtilities.convertPoint(e.getComponent(), e
301                    .getPoint(), layer));
302            setDirty(true);
303        }
304    
305        @Override
306        protected void processMouseMotionEvent(MouseEvent e,
307                JXLayer<JComponent> layer) {
308            super.processMouseMotionEvent(e, layer);
309            MagnifierState state = getStateObject(layer);
310            state.setPoint(SwingUtilities.convertPoint(e.getComponent(), e
311                    .getPoint(), layer));
312            setDirty(true);
313        }
314    }