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 }