All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alee.extended.magnifier.MagnifierGlass Maven / Gradle / Ivy

/*
 * This file is part of WebLookAndFeel library.
 *
 * WebLookAndFeel library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * WebLookAndFeel library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with WebLookAndFeel library.  If not, see .
 */

package com.alee.extended.magnifier;

import com.alee.global.StyleConstants;
import com.alee.managers.glasspane.GlassPaneManager;
import com.alee.managers.glasspane.WebGlassPane;
import com.alee.utils.*;
import com.alee.utils.ninepatch.NinePatchIcon;
import com.alee.utils.swing.WebTimer;

import javax.swing.*;
import java.awt.*;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;

/**
 * Custom component that allows you to display a magnifier within your application window.
 * That magnifier can zoom in any part of the UI displayed within the window.
 * 

* JComponent is used instead of separate window for three reasons: * 1. It is easy to sink events from JComponent to underlying UI elements * 2. It is easy to snapshot Swing UI, but magnifying screen parts will not work like that * 3. Not all OS support window per-pixel translucency which will limit the usage of this feature *

* This component might be extended in future to support windowed mode, but for now it is limited to Swing window bounds. * * @author Mikle Garin */ public class MagnifierGlass extends JComponent { /** * todo 1. Extend common panel and provide shade painting there * todo 2. Add custom background painter that will display zoomed area */ /** * Icons. */ public static final ImageIcon cursorIcon = new ImageIcon ( MagnifierGlass.class.getResource ( "icons/cursor.png" ) ); /** * Zoom area size. */ protected Dimension size = new Dimension ( 159, 159 ); /** * Zoom area shape type. */ protected MagnifierType type = MagnifierType.rectangular; /** * Magnifier position. */ protected MagnifierPosition position = MagnifierPosition.nearCursor; /** * Magnifier shade width. */ protected int shadeWidth = 25; /** * Rectangular magnifier round. */ protected int round = 10; /** * Zoom factor. */ protected int zoomFactor; /** * Whether or not dummy cursor should be displayed on magnified image. * Note that displayed cursor will not represent exact system cursor. */ protected boolean displayDummyCursor = true; /** * Dummy cursor opacity. */ protected float dummyCursorOpacity = 0.5f; /** * Milliseconds to forcefully update magnifier buffer. * Set to zero to disable forced updates. */ protected long forceUpdateFrequency = 100; /** * Runtime variables. */ protected JComponent zoomProvider; protected Cursor defaultCursor; protected BufferedImage buffer; protected BufferedImage view; protected AWTEventListener listener; protected WebTimer forceUpdater; protected Icon shadeIcon; protected int lastShadeWidth; protected boolean rendered; /** * Constructs magnifier that will work over the specified component. * Note that zoom will only display elements which are contained inside of the specified component. */ public MagnifierGlass () { this ( 4 ); } /** * Constructs magnifier that will work over the specified component. * Note that zoom will only display elements which are contained inside of the specified component. */ public MagnifierGlass ( final int zoomFactor ) { super (); setOpaque ( false ); setBackground ( new Color ( 237, 237, 237 ) ); // Initial zoom factor this.zoomFactor = zoomFactor; // Magnifier is initially disabled setEnabled ( false ); // Forceful updater forceUpdater = new WebTimer ( forceUpdateFrequency, new ActionListener () { @Override public void actionPerformed ( final ActionEvent e ) { updatePreview (); } } ); forceUpdater.setUseEventDispatchThread ( true ); forceUpdater.setUseDaemonThread ( true ); forceUpdater.setRepeats ( true ); // AWT event listener listener = new AWTEventListener () { @Override public void eventDispatched ( final AWTEvent event ) { // Process events only if this magnifier is enabled if ( isEnabled () ) { // Forcing later update within EDT // We are already within EDT here, but this is necessary // UI update will look laggy due to event handling specifics without this SwingUtilities.invokeLater ( new Runnable () { @Override public void run () { updatePreview (); restartForceUpdater (); } } ); } } }; } /** * Returns zoom area size. * * @return zoom area size */ public Dimension getZoomAreaSize () { return size; } /** * Sets zoom area size. * * @param size zoom area size */ public void setZoomAreaSize ( final Dimension size ) { if ( !CompareUtils.equals ( this.size, size ) ) { this.size = size; updatePreview (); } } /** * Returns zoom area shape type. * * @return zoom area shape type */ public MagnifierType getType () { return type; } /** * Sets zoom area shape type. * * @param type zoom area shape type */ public void setType ( final MagnifierType type ) { if ( this.type != type ) { this.type = type; updatePreview (); } } /** * Returns magnifier position. * * @return magnifier position */ public MagnifierPosition getPosition () { return position; } /** * Sets magnifier position. * * @param position magnifier position */ public void setPosition ( final MagnifierPosition position ) { if ( this.position != position ) { if ( position == MagnifierPosition.staticComponent ) { disposeFromGlassPane (); } else { displayOnGlassPane (); } this.position = position; updatePreview (); } } /** * Returns magnifier shade width. * * @return magnifier shade width */ public int getShadeWidth () { return shadeWidth; } /** * Sets magnifier shade width. * * @param shadeWidth magnifier shade width */ public void setShadeWidth ( final int shadeWidth ) { if ( this.shadeWidth != shadeWidth ) { this.shadeWidth = shadeWidth; updatePreview (); } } /** * Returns rectangular magnifier round. * * @return rectangular magnifier round */ public int getRound () { return round; } /** * Sets rectangular magnifier round. * * @param round rectangular magnifier round */ public void setRound ( final int round ) { if ( this.round != round ) { this.round = round; updatePreview (); } } /** * Returns zoom factor. * * @return zoom factor */ public int getZoomFactor () { return zoomFactor; } /** * Sets zoom factor. * * @param zoomFactor zoom factor */ public void setZoomFactor ( final int zoomFactor ) { if ( this.zoomFactor != zoomFactor ) { this.zoomFactor = zoomFactor; updatePreview (); } } /** * Returns whether or not dummy cursor should be displayed on magnified image. * * @return true if dummy cursor should be displayed on magnified image, false otherwise */ public boolean isDisplayDummyCursor () { return displayDummyCursor; } /** * Sets whether or not dummy cursor should be displayed on magnified image. * * @param display whether or not dummy cursor should be displayed on magnified image */ public void setDisplayDummyCursor ( final boolean display ) { if ( this.displayDummyCursor != display ) { this.displayDummyCursor = display; updatePreview (); } } /** * Returns dummy cursor opacity. * * @return dummy cursor opacity */ public float getDummyCursorOpacity () { return dummyCursorOpacity; } /** * Sets dummy cursor opacity. * * @param opacity dummy cursor opacity */ public void setDummyCursorOpacity ( final float opacity ) { if ( this.dummyCursorOpacity != opacity ) { this.dummyCursorOpacity = opacity; updatePreview (); } } /** * Returns milliseconds to forcefully update magnifier buffer. * * @return milliseconds to forcefully update magnifier buffer */ public long getForceUpdateFrequency () { return forceUpdateFrequency; } /** * Sets milliseconds to forcefully update magnifier buffer. * * @param forceUpdateFrequency milliseconds to forcefully update magnifier buffer */ public void setForceUpdateFrequency ( final long forceUpdateFrequency ) { if ( this.forceUpdateFrequency != forceUpdateFrequency ) { this.forceUpdateFrequency = forceUpdateFrequency; restartForceUpdater (); } } /** * Properly restarts */ protected void restartForceUpdater () { if ( isEnabled () && forceUpdateFrequency > 0 ) { forceUpdater.setDelay ( forceUpdateFrequency ); forceUpdater.restart (); } else { forceUpdater.stop (); } } /** * Returns UI buffer image size. * * @return UI buffer image size */ protected Dimension getBufferSize () { return new Dimension ( size.width / zoomFactor, size.height / zoomFactor ); } /** * Updates magnified UI preview. */ protected void updatePreview () { if ( isEnabled () ) { // Updating buffer image if something has changed final Dimension bs = getBufferSize (); if ( buffer == null || buffer.getWidth () != bs.width || buffer.getHeight () != bs.height ) { buffer = ImageUtils.createCompatibleImage ( bs.width, bs.height, Transparency.TRANSLUCENT ); } // Filling-in image content final Point mp = MouseInfo.getPointerInfo ().getLocation (); final Rectangle gb = SwingUtils.getBoundsOnScreen ( zoomProvider ); if ( gb.contains ( mp ) ) { // Rendering UI snapshot final Graphics2D g2d = buffer.createGraphics (); g2d.setBackground ( StyleConstants.transparent ); g2d.clearRect ( 0, 0, bs.width, bs.height ); final int x = mp.x - gb.x - bs.width / 2; final int y = mp.y - gb.y - bs.height / 2; g2d.translate ( -x, -y ); zoomProvider.paintAll ( g2d ); if ( displayDummyCursor ) { g2d.translate ( x, y ); GraphicsUtils.setupAlphaComposite ( g2d, dummyCursorOpacity ); g2d.drawImage ( cursorIcon.getImage (), bs.width / 2, bs.height / 2, null ); } g2d.dispose (); // Marking as rendered rendered = true; } else if ( rendered ) { // Clearing buffer final Graphics2D g2d = buffer.createGraphics (); g2d.setBackground ( StyleConstants.transparent ); g2d.clearRect ( 0, 0, bs.width, bs.height ); g2d.dispose (); // Marking as non-rendered rendered = false; } // Updating magnifier location if ( position == MagnifierPosition.atCursor ) { // Displaying magnifier straight on top of the cursor final WebGlassPane glassPane = GlassPaneManager.getGlassPane ( zoomProvider ); final Point rel = SwingUtils.getRelativeLocation ( zoomProvider, glassPane ); final Dimension ps = getPreferredSize (); final int mx = mp.x - gb.x + rel.x - ps.width / 2; final int my = mp.y - gb.y + rel.y - ps.height / 2; MagnifierGlass.this.setBounds ( mx, my, ps.width, ps.height ); } else if ( position == MagnifierPosition.nearCursor ) { final WebGlassPane glassPane = GlassPaneManager.getGlassPane ( zoomProvider ); final Rectangle bos = SwingUtils.getBoundsOnScreen ( glassPane ); final Point rmp = new Point ( mp.x - bos.x, mp.y - bos.y ); final Dimension ps = getPreferredSize (); final int mx; if ( rmp.x - ps.width / 2 < 0 ) { mx = 0; } else if ( rmp.x + ps.width / 2 > bos.width ) { mx = bos.width - ps.width; } else { mx = rmp.x - ps.width / 2; } final int my; if ( rmp.y + ps.height > bos.height && bos.height - rmp.y < rmp.y ) { my = rmp.y - ps.height; } else { my = rmp.y; } MagnifierGlass.this.setBounds ( mx, my, ps.width, ps.height ); } // Updating cursor on the window SwingUtils.getWindowAncestor ( zoomProvider ) .setCursor ( position == MagnifierPosition.atCursor ? SystemUtils.getTransparentCursor () : defaultCursor ); // Repainting magnifier // This is required in addition to bounds change repaint // Otherwise magnifier will not be properly updated when bounds do not change repaint (); } else if ( buffer != null ) { // Restoring cursor SwingUtils.getWindowAncestor ( zoomProvider ).setCursor ( defaultCursor ); // Resetting buffer if magnifier is hidden buffer.flush (); buffer = null; } } @Override protected void paintComponent ( final Graphics g ) { if ( isEnabled () && rendered ) { final Graphics2D g2d = ( Graphics2D ) g; GraphicsUtils.setupAntialias ( g2d ); // Fill and border shapes final Shape fillShape; final Shape borderShape; if ( type == MagnifierType.rectangular ) { fillShape = new RoundRectangle2D.Double ( shadeWidth, shadeWidth, size.width, size.height, round, round ); borderShape = new RoundRectangle2D.Double ( shadeWidth, shadeWidth, size.width - 1, size.height - 1, round, round ); } else { fillShape = new Ellipse2D.Double ( shadeWidth, shadeWidth, size.width, size.height ); borderShape = new Ellipse2D.Double ( shadeWidth, shadeWidth, size.width - 1, size.height - 1 ); } // Painting background g2d.setPaint ( getBackground () ); g2d.fill ( fillShape ); // Painting buffer final Shape oldClip = GraphicsUtils.setupClip ( g2d, fillShape, round > 0 ); g2d.drawImage ( buffer, shadeWidth, shadeWidth, size.width, size.height, null ); GraphicsUtils.restoreClip ( g2d, oldClip, round > 0 ); // Painting shade final Icon icon = getShadeIcon (); if ( icon instanceof NinePatchIcon ) { ( ( NinePatchIcon ) icon ).paintIcon ( g2d, 0, 0, getWidth (), getHeight () ); } else { icon.paintIcon ( this, g2d, 0, 0 ); } // Painting border g2d.setPaint ( Color.GRAY ); g2d.draw ( borderShape ); } } /** * Returns custom shade icon. * * @return custom shade icon */ protected Icon getShadeIcon () { if ( type == MagnifierType.rectangular ) { if ( shadeIcon == null || shadeWidth != lastShadeWidth || !( shadeIcon instanceof NinePatchIcon ) ) { shadeIcon = NinePatchUtils.createShadeIcon ( shadeWidth, round, 0.75f ); lastShadeWidth = shadeWidth; } } else { if ( shadeIcon == null || shadeWidth != lastShadeWidth || !( shadeIcon instanceof ImageIcon ) ) { final Dimension ps = getPreferredSize (); final Ellipse2D.Double shape = new Ellipse2D.Double ( shadeWidth, shadeWidth, size.width, size.height ); shadeIcon = new ImageIcon ( ImageUtils.createShadeImage ( ps.width, ps.height, shape, shadeWidth, 0.75f, true ) ); lastShadeWidth = shadeWidth; } } return shadeIcon; } /** * Initializes or disposes magnifier on the specified window. * * @param window magnifier window */ public void displayOrDispose ( final Window window ) { displayOrDispose ( getZoomProvider ( window ) ); } /** * Initializes or disposes magnifier on the window where specified component is located. * * @param component magnifier provider */ public void displayOrDispose ( final JComponent component ) { if ( isEnabled () ) { dispose (); } else { display ( component ); } } /** * Initializes magnifier on the specified window. * * @param window magnifier window */ public void display ( final Window window ) { display ( getZoomProvider ( window ) ); } /** * Initializes magnifier on the window where specified component is located. * * @param component magnifier provider */ public void display ( final JComponent component ) { // Performing various checks if ( component == null ) { // Component must be provided throw new IllegalArgumentException ( "Provided component must not be null" ); } if ( !component.isShowing () ) { // Checking that component is currently displayed throw new IllegalArgumentException ( "Provided component is not displayed on screen: " + component ); } if ( SwingUtils.getRootPane ( component ) == null ) { // Checking rootpane existance throw new IllegalArgumentException ( "Provided component is not placed within any window: " + component ); } // Changing visibility flag setEnabled ( true ); // Retrieving component that will be providing us the area to use magnifier within zoomProvider = component instanceof JRootPane ? ( ( JRootPane ) component ).getLayeredPane () : component; defaultCursor = SwingUtils.getWindowAncestor ( zoomProvider ).getCursor (); // Updating buffer image updatePreview (); // Displaying magnifier displayOnGlassPane (); // Starting force updater restartForceUpdater (); // Adding global AWT event listeners Toolkit.getDefaultToolkit ().addAWTEventListener ( listener, AWTEvent.MOUSE_EVENT_MASK ); Toolkit.getDefaultToolkit ().addAWTEventListener ( listener, AWTEvent.MOUSE_MOTION_EVENT_MASK ); Toolkit.getDefaultToolkit ().addAWTEventListener ( listener, AWTEvent.MOUSE_WHEEL_EVENT_MASK ); Toolkit.getDefaultToolkit ().addAWTEventListener ( listener, AWTEvent.KEY_EVENT_MASK ); } /** * Hides magnifier. */ public void dispose () { // Changing visibility flag setEnabled ( false ); // Updating buffer image updatePreview (); // Hiding magnifier disposeFromGlassPane (); // Stopping force updater restartForceUpdater (); // Removing global AWT event listeners Toolkit.getDefaultToolkit ().removeAWTEventListener ( listener ); // Cleaning up zoomProvider = null; defaultCursor = null; } /** * Returns whether or not this magnifier is currently displayed. * Be aware that this method only says whether or not this magnifier is active. * Actual visibility state can be retrieved through {@link #isShowing()} method like in any other Swing component. * * @return true if this magnifier is currently displayed, false otherwise */ public boolean isDisplayed () { return isEnabled (); } /** * Returns zoom provider component for the specified window. * * @param window window to retrieve zoom provider for * @return zoom provider component for the specified window */ protected JComponent getZoomProvider ( final Window window ) { final JComponent component; if ( window instanceof JWindow ) { component = ( ( JWindow ) window ).getLayeredPane (); } else if ( window instanceof JDialog ) { component = ( ( JDialog ) window ).getLayeredPane (); } else if ( window instanceof JFrame ) { component = ( ( JFrame ) window ).getLayeredPane (); } else { // Other types of window are not supported as they do not have layered pane throw new IllegalArgumentException ( "Provided window must contain JLayeredPane" ); } return component; } /** * Display magnifier on the glass pane. */ protected void displayOnGlassPane () { if ( isEnabled () ) { GlassPaneManager.getGlassPane ( zoomProvider ).showComponent ( this ); } } /** * Dispose magnifier from the glass pane. */ protected void disposeFromGlassPane () { if ( isEnabled () ) { GlassPaneManager.getGlassPane ( zoomProvider ).hideComponent ( this ); } } @Override public Dimension getPreferredSize () { return isEnabled () ? new Dimension ( size.width + shadeWidth * 2, size.height + shadeWidth * 2 ) : new Dimension ( 0, 0 ); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy