1

I am developing an application that tries to zoom a generic window (JFrame or JDialog).

I found the problem that when a JSlider component is to be zoomed, the visual pointer Component does not zoom, even though the bounds of the JSlider itself have been changed.

I have looked for a function in the JSlider public API a function that returns an object of base class Component, that can be modified, but I have not found any.
Also the function jSlider1.getComponentCount() returns 0.
I have also looked inside the SliderUI class in case there was a suitable function there, without success.
Does anyone know the correct way to access the visual pointer Component of the JSlider? Or at least how to set its size, for being able to zoom it?

1 Answers1

1

As I previously said in the comment, I finally decided to override JSlider and MetalSliderUI classes. At least this is compatible with Java-8, as the code I started from was the one of oracle JDK-8.

The code for the example of use of the new the classes is the following.

New interface:

public interface ZoomInterface
{
    public void setZoomFactor( double zoomFactor );
    public double getZoomFactor();
}

New overriden classes: ZoomMetalSliderUI:

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JSlider;
import javax.swing.plaf.metal.MetalLookAndFeel;
import javax.swing.plaf.metal.MetalSliderUI;

/**
 *
 * @author Francisco Javier Rojas Garrido <frojasg1@hotmail.com>
 */
public class ZoomMetalSliderUI extends MetalSliderUI implements ZoomInterface
{
    protected double _zoomFactor = 1.0D;

    public ZoomMetalSliderUI()
    {
        super();
    }

    @Override
    public Dimension getThumbSize()
    {
        Dimension result = ViewFunctions.instance().getNewDimension( super.getThumbSize(), null, _zoomFactor );

        return( result );
    }

    @Override
    public void setZoomFactor(double zoomFactor)
    {
        _zoomFactor = zoomFactor;
    }

    @Override
    public double getZoomFactor()
    {
        return( _zoomFactor );
    }

    @Override
    public void paint( Graphics g, JComponent c )
    {
        calculateGeometry();
        super.paint(g, c);
    }

    @Override
    public void paintThumb(Graphics g)  {
        Rectangle knobBounds = thumbRect;

//        g.translate( knobBounds.x, knobBounds.y );

        Icon icon = null;
        if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
            icon = horizThumbIcon;
        }
        else {
            icon = vertThumbIcon;
        }

        BufferedImage bi = new BufferedImage( icon.getIconWidth(), icon.getIconHeight(),
                                                BufferedImage.TYPE_INT_ARGB );
        Graphics g1 = bi.getGraphics();

        if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
            horizThumbIcon.paintIcon( slider, bi.getGraphics(), 0, 0 );
        }
        else {
            vertThumbIcon.paintIcon( slider, bi.getGraphics(), 0, 0 );
        }

        Rectangle tr = new Rectangle( 0, 0, icon.getIconWidth(), icon.getIconHeight() );
        Rectangle newRectangle = ViewFunctions.instance().getNewRectangle(tr, null, _zoomFactor );
        BufferedImage bi_tx = ImageFunctions.resizeImage(bi, (int)newRectangle.getWidth(),
                                                        (int)newRectangle.getHeight(),
                                                        null, null, null );

        Point center = ViewFunctions.instance().getCenter(knobBounds);
        g.drawImage( bi_tx, center.x - newRectangle.width / 2,
                            center.y - newRectangle.height / 2,
                            center.x + newRectangle.width / 2,
                            center.y + newRectangle.height / 2,
                            0,
                            0,
                            bi_tx.getWidth(),
                            bi_tx.getHeight(),
                            null );
//        g.translate( -knobBounds.x, -knobBounds.y );


    }


    public void paintTrack(Graphics g)  {
/*        if (MetalLookAndFeel.usingOcean()) {
            oceanPaintTrack(g);
            return;
        }
*/
        Color trackColor = !slider.isEnabled() ? MetalLookAndFeel.getControlShadow() :
                           slider.getForeground();

//        boolean leftToRight = MetalUtils.isLeftToRight(slider);
        boolean leftToRight = isLeftToRight(slider);

        g.translate( trackRect.x, trackRect.y );

        int trackLeft = 0;
        int trackTop = 0;
        int trackRight;
        int trackBottom;

        // Draw the track
        if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
            trackBottom = (trackRect.height - 1) - getThumbOverhang();
            trackTop = trackBottom - (getTrackWidth() - 1);
            trackRight = trackRect.width - 1;
        }
        else {
            if (leftToRight) {
                trackLeft = (trackRect.width - getThumbOverhang()) -
                                                         getTrackWidth();
                trackRight = (trackRect.width - getThumbOverhang()) - 1;
            }
            else {
                trackLeft = getThumbOverhang();
                trackRight = getThumbOverhang() + getTrackWidth() - 1;
            }
            trackBottom = trackRect.height - 1;
        }

        if ( slider.isEnabled() ) {
            g.setColor( MetalLookAndFeel.getControlDarkShadow() );
            g.drawRect( trackLeft, trackTop,
                        (trackRight - trackLeft) - 1, (trackBottom - trackTop) - 1 );

            g.setColor( MetalLookAndFeel.getControlHighlight() );
            g.drawLine( trackLeft + 1, trackBottom, trackRight, trackBottom );
            g.drawLine( trackRight, trackTop + 1, trackRight, trackBottom );

            g.setColor( MetalLookAndFeel.getControlShadow() );
            g.drawLine( trackLeft + 1, trackTop + 1, trackRight - 2, trackTop + 1 );
            g.drawLine( trackLeft + 1, trackTop + 1, trackLeft + 1, trackBottom - 2 );
        }
        else {
            g.setColor( MetalLookAndFeel.getControlShadow() );
            g.drawRect( trackLeft, trackTop,
                        (trackRight - trackLeft) - 1, (trackBottom - trackTop) - 1 );
        }

        // Draw the fill
        if ( filledSlider ) {
            int middleOfThumb;
            int fillTop;
            int fillLeft;
            int fillBottom;
            int fillRight;

            if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
                middleOfThumb = thumbRect.x + (thumbRect.width / 2);
                middleOfThumb -= trackRect.x; // To compensate for the g.translate()
                fillTop = !slider.isEnabled() ? trackTop : trackTop + 1;
                fillBottom = !slider.isEnabled() ? trackBottom - 1 : trackBottom - 2;

                if ( !drawInverted() ) {
                    fillLeft = !slider.isEnabled() ? trackLeft : trackLeft + 1;
                    fillRight = middleOfThumb;
                }
                else {
                    fillLeft = middleOfThumb;
                    fillRight = !slider.isEnabled() ? trackRight - 1 : trackRight - 2;
                }
            }
            else {
                middleOfThumb = thumbRect.y + (thumbRect.height / 2);
                middleOfThumb -= trackRect.y; // To compensate for the g.translate()
                fillLeft = !slider.isEnabled() ? trackLeft : trackLeft + 1;
                fillRight = !slider.isEnabled() ? trackRight - 1 : trackRight - 2;

                if ( !drawInverted() ) {
                    fillTop = middleOfThumb;
                    fillBottom = !slider.isEnabled() ? trackBottom - 1 : trackBottom - 2;
                }
                else {
                    fillTop = !slider.isEnabled() ? trackTop : trackTop + 1;
                    fillBottom = middleOfThumb;
                }
            }

            if ( slider.isEnabled() ) {
                g.setColor( slider.getBackground() );
                g.drawLine( fillLeft, fillTop, fillRight, fillTop );
                g.drawLine( fillLeft, fillTop, fillLeft, fillBottom );

                g.setColor( MetalLookAndFeel.getControlShadow() );
                g.fillRect( fillLeft + 1, fillTop + 1,
                            fillRight - fillLeft, fillBottom - fillTop );
            }
            else {
                g.setColor( MetalLookAndFeel.getControlShadow() );
                g.fillRect(fillLeft, fillTop, fillRight - fillLeft, fillBottom - fillTop);
            }
        }

        g.translate( -trackRect.x, -trackRect.y );
    }

    static boolean isLeftToRight( Component c ) {
        return c.getComponentOrientation().isLeftToRight();
    }
}

ZoomJSlider:

import javax.swing.BoundedRangeModel;
import javax.swing.JSlider;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.metal.MetalSliderUI;

/**
 *
 * @author Francisco Javier Rojas Garrido <frojasg1@hotmail.com>
 */
public class ZoomJSlider extends JSlider implements ZoomInterface
{
    protected double _zoomFactor = 1.0D;

    public ZoomJSlider() {
        super();
    }

    public ZoomJSlider(int orientation) {
        super(orientation);
    }

    public ZoomJSlider(int min, int max) {
        super(min, max);
    }

    public ZoomJSlider(int min, int max, int value) {
        super(min, max, value);
    }

    public ZoomJSlider(int orientation, int min, int max, int value)
    {
        super(orientation, min, max, value);
    }

    public ZoomJSlider(BoundedRangeModel brm)
    {
        super( brm );
    }

    public void switchToZoomUI()
    {
        ComponentUI compUi = getUI();
        ComponentUI newUi = null;

        if( ( compUi instanceof MetalSliderUI ) &&
            !( compUi instanceof ZoomMetalSliderUI ) )
        {
            newUi = new ZoomMetalSliderUI();
        }

        if( newUi != null )
        {
            setUI(newUi);
            ( (ZoomInterface) newUi ).setZoomFactor(_zoomFactor);
        }
    }

    @Override
    public void setZoomFactor( double zoomFactor )
    {
        _zoomFactor = zoomFactor;
        ComponentUI compUi = getUI();

        if( compUi instanceof ZoomInterface )
        {
            ZoomInterface zi = (ZoomInterface) compUi;
            zi.setZoomFactor(zoomFactor);
        }

        repaint();
    }

    @Override
    public double getZoomFactor()
    {
        return( _zoomFactor );
    }
}

Main class:

import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.plaf.metal.MetalLookAndFeel;

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author Francisco Javier Rojas Garrido <frojasg1@hotmail.com>
 */
public class Main
{
    public static void main(String args[]) {
        /* Set the Nimbus look and feel */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
         */
        try {
            javax.swing.UIManager.setLookAndFeel(MetalLookAndFeel.class.getName());

        } catch (ClassNotFoundException ex) {
            Logger.getLogger(MetalLookAndFeel.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            Logger.getLogger(MetalLookAndFeel.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            Logger.getLogger(MetalLookAndFeel.class.getName()).log(Level.SEVERE, null, ex);
        } catch (UnsupportedLookAndFeelException ex) {
            Logger.getLogger(MetalLookAndFeel.class.getName()).log(Level.SEVERE, null, ex);
        }
        //</editor-fold>

        /* Create and display the form */
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                try
                {
                    new Window().setVisible(true);
                }
                catch( Throwable th )
                {
                    th.printStackTrace();
                }
            }
        });
    }
}

Window class:

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import javax.swing.JFrame;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author Francisco Javier Rojas Garrido <frojasg1@hotmail.com>
 */
public class Window extends JFrame implements ChangeListener
{
    protected Dimension _dimen = new Dimension( 250, 30 );
    protected ZoomJSlider _jSlider = null;
    protected javax.swing.JPanel _jPanel1;

    public Window()
    {
        super( );
        initComponents();

        setListeners();
    }

    protected void initComponents()
    {
        _jPanel1 = new javax.swing.JPanel();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        getContentPane().setLayout(null);

        _jPanel1.setLayout(null);
        _jSlider = new ZoomJSlider( 50, 200, 100 );
        _jPanel1.add( _jSlider );
        _jSlider.switchToZoomUI();
        _jSlider.setBounds( 20, 30, (int) _dimen.getWidth(), (int) _dimen.getHeight() );

        getContentPane().add(_jPanel1);
        _jPanel1.setBounds(0, 0, 600, 120);

        setSize( 650, 170 );
        setLocation( getCenteredLocationForComponent( this ) );
    }

    protected void setListeners()
    {
        _jSlider.addChangeListener( this );
    }

    @Override
    public void stateChanged( ChangeEvent ce )
    {
        double zoomFactor = _jSlider.getValue() / 100.0D;
        _jSlider.setZoomFactor( zoomFactor );

        Dimension size = ViewFunctions.instance().getNewDimension(_dimen, null, zoomFactor);
        _jSlider.setBounds( _jSlider.getX(), _jSlider.getY(),
                            (int) size.getWidth(), (int) size.getHeight() );
    }

    public static Point getCenteredLocationForComponent( Component comp )
    {
        int width = java.awt.Toolkit.getDefaultToolkit().getScreenSize().width;
        int height = java.awt.Toolkit.getDefaultToolkit().getScreenSize().height;
        Point result = new Point( width/2 - comp.getWidth()/2, height/2 - comp.getHeight()/2 );

        return( result );
    }
}

Other classes:

import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;

/**
 *
 * @author Usuario
 */
public class ImageFunctions
{
    public static BufferedImage resizeImage( BufferedImage original, int width, int height, Integer switchColorFrom,
                                            Integer switchColorTo, Integer alphaForPixelsDifferentFromColorFrom ) throws IllegalArgumentException
    {
        int alpha = 0xFF000000;
        if( alphaForPixelsDifferentFromColorFrom != null )
            alpha = ( (alphaForPixelsDifferentFromColorFrom) & 0xFF ) << 24;

        if( ( width < 1 ) || ( height < 1 ) )       throw( new IllegalArgumentException( "Bad size for image.   Width: " + width + ". Height: " + height ) );

        BufferedImage result = new BufferedImage( width, height, BufferedImage.TYPE_INT_ARGB );

        double factorX = ((double) original.getWidth()) / width ;
        double factorY = ( (double) original.getHeight() ) / height;

        int[] pixels = getRGB( 0, 0, original.getWidth(), original.getHeight(), original );

        double transformedY = 0.5d;
        for( int tY = 0; tY < height; transformedY += 1, tY++ )
        {
            int originalOffsetY = (int) ( Math.floor(transformedY*factorY) ) * original.getWidth();
            double transformedX = 0.5D;
            for( int tX = 0; tX < width; transformedX += 1, tX++ )
            {
                int originalX = (int) ( Math.floor( transformedX*factorX ) );

                int pixelColor = pixels[ originalOffsetY+originalX ];

                if( ( switchColorFrom != null ) && ( pixelColor == switchColorFrom ) )
                {
                    if( switchColorTo != null ) pixelColor = switchColorTo;
                    else                        pixelColor = pixelColor & 0xFFFFFF;
                }
                else if( alphaForPixelsDifferentFromColorFrom != null )
                {
                    pixelColor = pixelColor & 0xFFFFFF | alpha;
                }

                result.setRGB( tX, tY, pixelColor );
            }
        }

        return( result );
    }


    /**
    * Returns an array of integer pixels in the default RGB color model
    * (TYPE_INT_ARGB) and default sRGB color space,
    * from a portion of the image data.  Color conversion takes
    * place if the default model does not match the image
    * <code>ColorModel</code>.  There are only 8-bits of precision for
    * each color component in the returned data when
    * using this method.  With a specified coordinate (x,&nbsp;y) in the
    * image, the ARGB pixel can be accessed in this way:
    * </p>
    *
    * <pre>
    *    pixel   = rgbArray[offset + (y-startY)*scansize + (x-startX)]; </pre>
    *
    * <p>
    *
    * An <code>ArrayOutOfBoundsException</code> may be thrown
    * if the region is not in bounds.
    * However, explicit bounds checking is not guaranteed.
    *
    * @param startX      the starting X coordinate
    * @param startY      the starting Y coordinate
    * @param w           width of region
    * @param h           height of region
    * @param rgbArray    if not <code>null</code>, the rgb pixels are
    *          written here
    * @param offset      offset into the <code>rgbArray</code>
    * @param scansize    scanline stride for the <code>rgbArray</code>
    * @return            array of RGB pixels.
    * @see #setRGB(int, int, int)
    * @see #setRGB(int, int, int, int, int[], int, int)
    */
    public static int[] getRGB(int startX, int startY, int w, int h,
                                BufferedImage bi )
    {
        ColorModel colorModel = bi.getColorModel();

        Raster raster = bi.getRaster();
//      WritableRaster raster = colorModel.createCompatibleWritableRaster( bi.getWidth(), bi.getHeight() );

        int scansize = w;
        int offset =0;

        int yoff  = offset;
        int off;
        Object data;
        int nbands = raster.getNumBands();
        int dataType = raster.getDataBuffer().getDataType();
        switch (dataType)
        {
            case DataBuffer.TYPE_BYTE:
            data = new byte[nbands];
                break;
            case DataBuffer.TYPE_USHORT:
                data = new short[nbands];
                break;
            case DataBuffer.TYPE_INT:
                data = new int[nbands];
                break;
            case DataBuffer.TYPE_FLOAT:
                data = new float[nbands];
                break;
            case DataBuffer.TYPE_DOUBLE:
                data = new double[nbands];
                break;
            default:
                throw new IllegalArgumentException("Unknown data buffer type: "+
                                                    dataType);
        }

        int[] rgbArray = new int[offset+h*scansize];

        for (int y = startY; y < startY+h; y++, yoff+=scansize)
        {
            off = yoff;
            for (int x = startX; x < startX+w; x++)
            {
                if( (x>=0) && (x<bi.getWidth()) && (y>=0) && (y<bi.getHeight() ) )
                {
                    rgbArray[off++] = colorModel.getRGB(raster.getDataElements( x,
                                                                                y,
                                                                                data));
                }
                else
                {
                    rgbArray[off++] = 0;
                }
           }
       }

        return rgbArray;
    }

}

public class IntegerFunctions
{
    public static int max( int i1, int i2 )
    {
        return( i1>i2 ? i1 : i2 );
    }

    public static int min( int i1, int i2 )
    {
        return( i1<i2 ? i1 : i2 );
    }

    public static int abs( int ii )
    {
        return( ii>=0 ? ii : -ii );
    }

    public static int sgn( int ii )
    {
        return( ii>0 ? 1 : ( ii<0 ? -1 : 0 ) );
    }
}

import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;

/**
 *
 * @author Usuario
 */
public class ViewFunctions
{
    protected static ViewFunctions _instance;

    public static ViewFunctions instance()
    {
        if( _instance == null )
            _instance = new ViewFunctions();
        return( _instance );
    }

    public Dimension getNewDimension( Dimension dim, Insets insets, double zoomFactor )
    {
        Dimension result = null;

        if( dim != null )
        {
            if( insets == null )
                insets = new Insets(0,0,0,0);

            int insetsWidth = insets.left + insets.right;
            int insetsHeight = insets.top + insets.bottom;
            result = new Dimension( (int) ( zoomFactor * ( dim.getWidth() - insetsWidth ) + insetsWidth ),
                                    (int) ( zoomFactor * ( dim.getHeight() - insetsHeight ) + insetsHeight ) );
        }

        return( result );
    }

    public Rectangle getNewRectangle( Rectangle rect, Insets insets, double zoomFactor )
    {
        Rectangle result = null;

        if( rect != null )
        {
            if( insets == null )
                insets = new Insets(0,0,0,0);

            int insetsWidth = insets.left + insets.right;
            int insetsHeight = insets.top + insets.bottom;
            result = new Rectangle( (int) ( zoomFactor * ( rect.getX() + insets.left ) - insets.left  ),
                                        (int) ( zoomFactor * ( rect.getY() + insets.top ) - insets.top ),
                                        (int) ( zoomFactor * ( rect.getWidth()  - insetsWidth ) + insetsWidth ),
                                        (int) ( zoomFactor * ( rect.getHeight() - insetsHeight ) + insetsHeight ) );
        }

        return( result );
    }

    public Insets getNewInsets( Insets insets, double zoomFactor )
    {
        Insets result = null;

        if( insets != null )
        {
            result = new Insets( (int) ( insets.top * zoomFactor ),
                                    (int) ( insets.left * zoomFactor ),
                                    (int) ( insets.bottom * zoomFactor ),
                                    (int) ( insets.right * zoomFactor ) );
        }

        return( result );
    }

    public Point getCenter( Rectangle rect )
    {
        Point result = null;

        if( rect != null )
        {
            result = new Point( (int) ( rect.getX() + rect.getWidth() / 2 ),
                                (int) ( rect.getY() + rect.getHeight() / 2 ) );
        }

        return( result );
    }

    public Rectangle calculateNewBounds( Rectangle originalBounds, Insets insets, Point center, double zoomFactor )
    {
        Rectangle result = null;

        if( originalBounds != null )
        {
            if( insets == null )
                insets = new Insets( 0, 0, 0, 0 );

            int newX = (int) originalBounds.getX();
            int newY = (int) originalBounds.getY();
            if( center != null )
            {
                newX = (int) ( center.getX() - insets.left + ( originalBounds.getX() - center.getX() + insets.left ) * zoomFactor );
                newY = (int) ( center.getY()  - insets.top + ( originalBounds.getY() - center.getY() + insets.top ) * zoomFactor );
            }

            result = new Rectangle( newX,
                                    newY,
                                    (int) ( ( originalBounds.getWidth() - insets.left - insets.right ) * zoomFactor + insets.left + insets.right ),
                                    (int) ( ( originalBounds.getHeight() - insets.top - insets.bottom ) * zoomFactor + insets.top + insets.bottom )
                                    );
        }

        return( result );
    }
}