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

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

There is a newer version: 1.2.14
Show newest version
/*
 * 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.api.annotations.NotNull;
import com.alee.api.jdk.Objects;
import com.alee.laf.WebLookAndFeel;
import com.alee.managers.glasspane.GlassPaneManager;
import com.alee.managers.glasspane.WebGlassPane;
import com.alee.managers.language.LM;
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;
import java.text.DecimalFormat;
import java.util.Map;

/**
 * Custom component that allows you to display a magnifier within your application window.
 * {@link MagnifierGlass} can zoom in any part of the UI displayed within the window.
 *
 * {@link JComponent} is used instead of {@link Window} for three reasons:
 * 1. It is easy to sink events from {@link JComponent} to underlying UI elements
 * 2. It is easy to snapshot UI elements, 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
     * todo 3. Replace cursor icon with set icon
     */

    /**
     * Icons.
     */
    protected static final ImageIcon cursorIcon = new ImageIcon ( MagnifierGlass.class.getResource ( "icons/cursor.png" ) );

    /**
     * Milliseconds display format.
     */
    protected static final DecimalFormat msFormat = new DecimalFormat ( "0.00" );

    /**
     * Zoom area size.
     */
    @NotNull
    protected Dimension size;

    /**
     * Zoom area shape type.
     */
    @NotNull
    protected MagnifierType type;

    /**
     * {@link MagnifierGlass} position.
     */
    @NotNull
    protected MagnifierPosition position;

    /**
     * {@link MagnifierGlass} shade width.
     */
    protected int shadeWidth;

    /**
     * {@link MagnifierGlass} corners round.
     */
    protected int round;

    /**
     * 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;

    /**
     * Whether or not captured area painting time metrics should be displayed.
     */
    protected boolean displayTimeMetrics;

    /**
     * Dummy cursor opacity.
     */
    protected float dummyCursorOpacity;

    /**
     * Milliseconds to forcefully update {@link MagnifierGlass} buffer.
     * Set to zero to disable forced updates.
     */
    protected long forceUpdateFrequency;

    /**
     * Runtime variables.
     */
    protected transient JComponent zoomProvider;
    protected transient Cursor defaultCursor;
    protected transient BufferedImage buffer;
    protected transient Long time;
    protected transient BufferedImage view;
    protected transient Icon shadeIcon;
    protected transient int lastShadeWidth;
    protected transient boolean rendered;
    protected transient final AWTEventListener listener;
    protected transient final WebTimer forceUpdater;

    /**
     * Constructs new {@link MagnifierGlass} 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 new {@link MagnifierGlass} that will work over the specified component.
     * Note that zoom will only display elements which are contained inside of the specified component.
     *
     * @param zoomFactor zoom factor
     */
    public MagnifierGlass ( final int zoomFactor )
    {
        super ();
        setOpaque ( false );
        setBackground ( new Color ( 237, 237, 237 ) );

        // Initial settings
        this.size = new Dimension ( 159, 159 );
        this.type = MagnifierType.rectangular;
        this.position = MagnifierPosition.nearCursor;
        this.shadeWidth = 25;
        this.round = 10;
        this.zoomFactor = zoomFactor;
        this.displayDummyCursor = true;
        this.displayTimeMetrics = true;
        this.dummyCursorOpacity = 0.5f;
        this.forceUpdateFrequency = 100;

        // Magnifier is initially disabled
        setEnabled ( false );

        // Forceful updater
        forceUpdater = new WebTimer ( forceUpdateFrequency, new ActionListener ()
        {
            @Override
            public void actionPerformed ( @NotNull final ActionEvent e )
            {
                updatePreview ();
            }
        } );
        forceUpdater.setUseEventDispatchThread ( true );
        forceUpdater.setUseDaemonThread ( true );
        forceUpdater.setRepeats ( true );

        // AWT event listener
        listener = new AWTEventListener ()
        {
            @Override
            public void eventDispatched ( @NotNull 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
                    CoreSwingUtils.invokeLater ( new Runnable ()
                    {
                        @Override
                        public void run ()
                        {
                            // Updating preview
                            updatePreview ();

                            // Restarting forceful updater to delay next update
                            restartForceUpdater ();
                        }
                    } );
                }
            }
        };
    }

    /**
     * Returns zoom area size.
     *
     * @return zoom area size
     */
    @NotNull
    public Dimension getZoomAreaSize ()
    {
        return size;
    }

    /**
     * Sets zoom area size.
     *
     * @param size zoom area size
     */
    public void setZoomAreaSize ( @NotNull final Dimension size )
    {
        if ( Objects.notEquals ( this.size, size ) )
        {
            this.size = size;
            updatePreview ();
        }
    }

    /**
     * Returns zoom area shape type.
     *
     * @return zoom area shape type
     */
    @NotNull
    public MagnifierType getType ()
    {
        return type;
    }

    /**
     * Sets zoom area shape type.
     *
     * @param type zoom area shape type
     */
    public void setType ( @NotNull final MagnifierType type )
    {
        if ( this.type != type )
        {
            this.type = type;
            updatePreview ();
        }
    }

    /**
     * Returns {@link MagnifierGlass} position.
     *
     * @return {@link MagnifierGlass} position
     */
    @NotNull
    public MagnifierPosition getPosition ()
    {
        return position;
    }

    /**
     * Sets {@link MagnifierGlass} position.
     *
     * @param position {@link MagnifierGlass} position
     */
    public void setPosition ( @NotNull final MagnifierPosition position )
    {
        if ( this.position != position )
        {
            if ( position == MagnifierPosition.staticComponent )
            {
                disposeFromGlassPane ();
            }
            else
            {
                displayOnGlassPane ();
            }
            this.position = position;
            updatePreview ();
        }
    }

    /**
     * Returns {@link MagnifierGlass} shade width.
     *
     * @return {@link MagnifierGlass} shade width
     */
    public int getShadeWidth ()
    {
        return shadeWidth;
    }

    /**
     * Sets {@link MagnifierGlass} shade width.
     *
     * @param shadeWidth {@link MagnifierGlass} shade width
     */
    public void setShadeWidth ( final int shadeWidth )
    {
        if ( this.shadeWidth != shadeWidth )
        {
            this.shadeWidth = shadeWidth;
            updatePreview ();
        }
    }

    /**
     * Returns {@link MagnifierGlass} corners round.
     *
     * @return {@link MagnifierGlass} corners round
     */
    public int getRound ()
    {
        return round;
    }

    /**
     * Sets {@link MagnifierGlass} corners round.
     *
     * @param round {@link MagnifierGlass} corners 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 {@code true} if dummy cursor should be displayed on magnified image, {@code 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 whether or not captured area painting time metrics should be displayed.
     *
     * @return {@code true} if captured area painting time metrics should be displayed, {@code false} otherwise
     */
    public boolean isDisplayTimeMetrics ()
    {
        return displayTimeMetrics;
    }

    /**
     * Sets whether or not captured area painting time metrics should be displayed.
     *
     * @param display whether or not captured area painting time metrics should be displayed
     */
    public void setDisplayTimeMetrics ( final boolean display )
    {
        if ( this.displayTimeMetrics != display )
        {
            this.displayTimeMetrics = 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 {@link MagnifierGlass} buffer.
     *
     * @return milliseconds to forcefully update {@link MagnifierGlass} buffer
     */
    public long getForceUpdateFrequency ()
    {
        return forceUpdateFrequency;
    }

    /**
     * Sets milliseconds to forcefully update {@link MagnifierGlass} buffer.
     *
     * @param forceUpdateFrequency milliseconds to forcefully update {@link MagnifierGlass} buffer
     */
    public void setForceUpdateFrequency ( final long forceUpdateFrequency )
    {
        if ( this.forceUpdateFrequency != forceUpdateFrequency )
        {
            this.forceUpdateFrequency = forceUpdateFrequency;
            restartForceUpdater ();
        }
    }

    /**
     * Properly restarts forceful preview updater.
     */
    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
     */
    @NotNull
    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.OPAQUE );
            }

            // Filling-in image content
            final Point mp = CoreSwingUtils.getMouseLocation ();
            final Rectangle gb = CoreSwingUtils.getBoundsOnScreen ( zoomProvider, false );
            if ( gb.contains ( mp ) )
            {
                // Configuring graphics
                final Graphics2D g2d = buffer.createGraphics ();
                g2d.setBackground ( Color.WHITE );
                g2d.clearRect ( 0, 0, bs.width, bs.height );
                g2d.setClip ( 0, 0, bs.width, bs.height );

                // Translating graphics to the magnifier position on the UI
                final int x = mp.x - gb.x - bs.width / 2;
                final int y = mp.y - gb.y - bs.height / 2;
                g2d.translate ( -x, -y );

                // Rendering UI snapshot
                time = System.nanoTime ();
                zoomProvider.paintAll ( g2d );
                time = System.nanoTime () - time;

                // Adding dummy cursor
                if ( isDisplayDummyCursor () )
                {
                    g2d.translate ( x, y );
                    GraphicsUtils.setupAlphaComposite ( g2d, dummyCursorOpacity );
                    g2d.drawImage ( cursorIcon.getImage (), bs.width / 2, bs.height / 2, null );
                }

                // Disposing graphics
                g2d.dispose ();

                // Marking as rendered
                rendered = true;
            }
            else if ( rendered )
            {
                // Clearing buffer
                final Graphics2D g2d = buffer.createGraphics ();
                g2d.setBackground ( Color.WHITE );
                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 = CoreSwingUtils.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 = CoreSwingUtils.getBoundsOnScreen ( glassPane, false );
                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
            final Cursor cursor = position == MagnifierPosition.atCursor ? SystemUtils.getTransparentCursor () : defaultCursor;
            CoreSwingUtils.getNonNullWindowAncestor ( zoomProvider ).setCursor ( cursor );

            // 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
            CoreSwingUtils.getNonNullWindowAncestor ( zoomProvider ).setCursor ( defaultCursor );

            // Resetting buffer if magnifier is hidden
            buffer.flush ();
            buffer = null;
        }
    }

    @Override
    protected void paintComponent ( @NotNull 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 time metrics
            if ( isDisplayTimeMetrics () )
            {
                final Map taa = SwingUtils.setupTextAntialias ( g2d );
                g2d.setColor ( Color.BLACK );
                final double ms = ( double ) time / TimeUtils.nsInMillisecond;
                final String text = msFormat.format ( ms ) + " " + LM.get ( "weblaf.time.units.short.millisecond" );
                final int x = getComponentOrientation ().isLeftToRight () ? shadeWidth + 5 :
                        shadeWidth + size.width - g2d.getFontMetrics ().stringWidth ( text ) - 5;
                final int y = shadeWidth + size.height - 5;
                g2d.drawString ( text, x, y );
                SwingUtils.restoreTextAntialias ( g2d, taa );
            }

            // 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
     */
    @NotNull
    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.createShadowImage ( ps.width, ps.height, shape, shadeWidth, 0.75f, true ) );
                lastShadeWidth = shadeWidth;
            }
        }
        return shadeIcon;
    }

    /**
     * Initializes or disposes {@link MagnifierGlass} on the specified {@link Window}.
     *
     * @param window {@link Window} for {@link MagnifierGlass}
     */
    public void displayOrDispose ( @NotNull final Window window )
    {
        displayOrDispose ( getZoomProvider ( window ) );
    }

    /**
     * Initializes or disposes {@link MagnifierGlass} on the {@link Window} where specified component is located.
     *
     * @param component {@link JComponent} for {@link MagnifierGlass}
     */
    public void displayOrDispose ( @NotNull final JComponent component )
    {
        if ( !isEnabled () )
        {
            display ( component );
        }
        else
        {
            dispose ();
        }
    }

    /**
     * Initializes {@link MagnifierGlass} on the specified {@link Window}.
     *
     * @param window {@link Window} for {@link MagnifierGlass}
     */
    public void display ( @NotNull final Window window )
    {
        display ( getZoomProvider ( window ) );
    }

    /**
     * Initializes {@link MagnifierGlass} on the {@link Window} where specified component is located.
     *
     * @param component {@link JComponent} for {@link MagnifierGlass}
     */
    public void display ( @NotNull final JComponent component )
    {
        // Event Dispatch Thread check
        WebLookAndFeel.checkEventDispatchThread ();

        // Performing various checks
        if ( !component.isShowing () )
        {
            // Checking that component is currently displayed
            throw new IllegalArgumentException ( "Provided component is not displayed on screen: " + 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 = CoreSwingUtils.getNonNullWindowAncestor ( 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 );
    }

    /**
     * Disposes {@link MagnifierGlass}.
     */
    public void dispose ()
    {
        // Event Dispatch Thread check
        WebLookAndFeel.checkEventDispatchThread ();

        // 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 {@link MagnifierGlass} is currently displayed.
     * Be aware that this method only says whether or not this {@link MagnifierGlass} is active.
     * Actual visibility state can be retrieved through {@link #isShowing()} method like in any other Swing component.
     *
     * @return {@code true} if this {@link MagnifierGlass} is currently displayed, {@code 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
     */
    @NotNull
    protected JComponent getZoomProvider ( @NotNull 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
        {
            throw new IllegalArgumentException ( "Provided window must contain JLayeredPane" );
        }
        return component;
    }

    /**
     * Display {@link MagnifierGlass} on the glass pane.
     */
    protected void displayOnGlassPane ()
    {
        if ( isEnabled () )
        {
            GlassPaneManager.getGlassPane ( zoomProvider ).showComponent ( this );
        }
    }

    /**
     * Dispose {@link MagnifierGlass} from the glass pane.
     */
    protected void disposeFromGlassPane ()
    {
        if ( isEnabled () )
        {
            GlassPaneManager.getGlassPane ( zoomProvider ).hideComponent ( this );
        }
    }

    @NotNull
    @Override
    public Dimension getPreferredSize ()
    {
        return isEnabled () ? new Dimension ( size.width + shadeWidth * 2, size.height + shadeWidth * 2 ) : new Dimension ( 0, 0 );
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy