/** * Copyright (c) 2008-2009, Piet Blok * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of the copyright holder nor the names of the * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package jav.gui.layer; import java.awt.Color; import java.awt.Graphics2D; import java.awt.MultipleGradientPaint; import java.awt.Paint; import java.awt.RadialGradientPaint; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.MouseEvent; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.List; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JComponent; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import org.jdesktop.jxlayer.JXLayer; /** * Shows a magnification glass on top of a component. * * @author Piet Blok */ public class MagnifierUI extends GeneralLayerUI<JComponent, MagnifierUI.MagnifierState> { /** * */ private static final long serialVersionUID = 1L; /** * Holds state information. */ protected static class MagnifierState { private int radius = radiusOptions[6]; private double magnifyingFactor = magnificationOptions[3]; private Point2D point = new Point2D.Double(); /** * Get the magnifying factor. * * @return the magnifying factor * @see #setMagnifyingFactor(double) */ public double getMagnifyingFactor() { return magnifyingFactor; } /** * Get the mouse point. * * @return return the mouse point * @see #setPoint(Point2D) */ public Point2D getPoint() { return point; } /** * Get the radius. * * @return the radius * @see #setRadius(int) */ public int getRadius() { return radius; } /** * Set the magnifying factor. * * @param magnifyingFactor * the new magnifying factor * @see #getMagnifyingFactor() */ public void setMagnifyingFactor(double magnifyingFactor) { this.magnifyingFactor = magnifyingFactor; } /** * Set the mouse point. * * @param point * the new mouse point * @see #getPoint() */ public void setPoint(Point2D point) { this.point.setLocation(point); } /** * Set the radius. * * @param radius * the new radius * @see #getRadius() */ public void setRadius(int radius) { this.radius = radius; } } private static final Integer[] radiusOptions = new Integer[] { 20, 40, 60, 80, 100, 120, 140, 160, 180, 200 }; private static final Double[] magnificationOptions = new Double[] { 0.25, 0.5, 0.75, 1.0, 2.0, 4.0, 8.0, 16.0 }; /** * Return {@link Action}s that: * <ol> * <li>Set magnification factor.</li> * <li>Set the glass radius.</li> * </ol> */ @Override public List<Action> getActions(final JXLayer<? extends JComponent> layer) { ArrayList<Action> actionList = new ArrayList<Action>(); actionList.addAll(super.getActions(layer)); /* * Set the magnifying factor */ actionList.add(new AbstractAction("Set magnification factor") { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent event) { MagnifierState state = getStateObject(layer); Double current = state.getMagnifyingFactor(); int optionIndex = JOptionPane.showOptionDialog(layer, "Choose a magnification factor", "Choose magnification", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, magnificationOptions, current); if (optionIndex != JOptionPane.CLOSED_OPTION) { state .setMagnifyingFactor(magnificationOptions[optionIndex]); } } }); /* * Set the glass radius */ actionList.add(new AbstractAction("Set glass radius") { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent event) { MagnifierState state = getStateObject(layer); Integer current = state.getRadius(); int optionIndex = JOptionPane.showOptionDialog(layer, "Choose a glass radius", "Choose glass radius", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, radiusOptions, current); if (optionIndex != JOptionPane.CLOSED_OPTION) { state.setRadius(radiusOptions[optionIndex]); } } }); return actionList; } private Paint createPaint(Ellipse2D glass, boolean transparent) { Point2D center = new Point2D.Double(glass.getCenterX(), glass .getCenterY()); float radius = (float) (glass.getCenterX() - glass.getX()); Point2D focus = new Point2D.Double(center.getX() - 0.5 * radius, center .getY() - 0.5 * radius); Color[] colors = new Color[] { transparent ? new Color(255, 255, 255, 128) : Color.WHITE, transparent ? new Color(0, 255, 255, 32) : Color.CYAN }; float[] fractions = new float[] { 0f, 1f }; RadialGradientPaint paint = new RadialGradientPaint(center, radius, focus, fractions, colors, MultipleGradientPaint.CycleMethod.NO_CYCLE); return paint; } @Override protected MagnifierState createStateObject(JXLayer<? extends JComponent> layer) { return new MagnifierState(); } @Override protected void paintLayer(Graphics2D g2, JXLayer<? extends JComponent> layer) { super.paintLayer(g2, layer); MagnifierState state = getStateObject(layer); Point2D point = state.getPoint(); double scale = state.getMagnifyingFactor(); double baseRadius = state.getRadius(); double scaledRadius = (baseRadius / scale); double strokeAdjust = 0.5; double drawSize = 2 * (baseRadius + strokeAdjust); double clipSize = 2 * scaledRadius; Ellipse2D drawGlass = new Ellipse2D.Double(-strokeAdjust, -strokeAdjust, drawSize, drawSize); Ellipse2D clipGlass = new Ellipse2D.Double(0, 0, clipSize, clipSize); g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); g2.translate(point.getX() - baseRadius, point.getY() - baseRadius); Color oldColor = g2.getColor(); g2.setPaint(createPaint(drawGlass, false)); g2.fill(drawGlass); g2.setColor(oldColor); g2.draw(drawGlass); AffineTransform oldTransform = g2.getTransform(); Shape oldClip = g2.getClip(); g2.scale(scale, scale); g2.clip(clipGlass); g2.translate(scaledRadius - point.getX(), scaledRadius - point.getY()); // layer.paint(g2); super.paintLayer(g2, layer); g2.setTransform(oldTransform); g2.setClip(oldClip); g2.setPaint(createPaint(drawGlass, true)); g2.fill(drawGlass); } @Override protected void processMouseEvent(MouseEvent e, JXLayer<? extends JComponent> layer) { super.processMouseEvent(e, layer); MagnifierState state = getStateObject(layer); state.setPoint(SwingUtilities.convertPoint(e.getComponent(), e .getPoint(), layer)); setDirty(true); } @Override protected void processMouseMotionEvent(MouseEvent e, JXLayer<? extends JComponent> layer) { super.processMouseMotionEvent(e, layer); MagnifierState state = getStateObject(layer); state.setPoint(SwingUtilities.convertPoint(e.getComponent(), e .getPoint(), layer)); setDirty(true); } }