JXTransformer.java Source code

Java tutorial

Introduction

Here is the source code for JXTransformer.java

Source

    /*
     * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
     * Santa Clara, California 95054, U.S.A. All rights reserved.
     *
     * This library is free software; you can redistribute it and/or
     * modify it under the terms of the GNU Lesser General Public
     * License as published by the Free Software Foundation; either
     * version 2.1 of the License, or (at your option) any later version.
     *
     * This 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
     * Lesser General Public License for more details.
     *
     * You should have received a copy of the GNU Lesser General Public
     * License along with this library; if not, write to the Free Software
     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     */

    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.*;
    import java.awt.geom.AffineTransform;
    import java.awt.geom.NoninvertibleTransformException;
    import java.awt.geom.Point2D;
    import java.util.Map;
    import java.util.HashMap;

/**
 * Container which can transform its children, for example:<br>
 * <pre>
 * JButton button = new JButton("Hello");
 * JXTransformer t = new JXTransformer(button);
 * t.rotate(Math.PI/2);</pre> 
 * 
 * <strong>Note:</strong>
 * This component was designed to transform simple components 
 * like JButton, JLabel etc.   
 * 
 * @author Alexander Potochkin
 * 
 * https://swinghelper.dev.java.net/
 * http://weblogs.java.net/blog/alexfromsun/ 
 */
public class JXTransformer extends JPanel {
    private Component glassPane = new MagicGlassPane();
    private Component view;
    private Rectangle visibleRect;
    private Map<?,?> renderingHints;

    private AffineTransform at;

    public JXTransformer() {
        this(null);
    }

    public JXTransformer(JComponent view) {
        this(view, new AffineTransform());
    }

    public JXTransformer(JComponent view, AffineTransform at) {
        super(null);
        setTransform(at);
        super.addImpl(glassPane, null, 0);
        setView(view);
        Handler handler = new Handler();
        addHierarchyBoundsListener(handler);
        addComponentListener(handler);        
    }

    public Component getView() {
        return view;
    }

    public void setView(Component view) {
        if (getView() != null) {
            super.remove(getView());
        }
        if (view != null) {
            super.addImpl(view, null, 1);
        }
        this.view = view;
        doLayout();
        revalidate();
        repaint();
    }

    public Map<?,?> getRenderingHints() {
        if (renderingHints == null) {
            return null;
        }
        return new HashMap<Object,Object>(renderingHints);
    }

    public void setRenderingHints(Map<?,?> renderingHints) {
        if (renderingHints == null) {
            this.renderingHints = null;
        } else {
            this.renderingHints = new HashMap<Object,Object>(renderingHints);
        }
        repaint();
    }

    protected void addImpl(Component comp, Object constraints, int index) {
        setView(comp);
    }

    public void remove(int index) {
        Component c = getComponent(index);
        if (c == view) {
            view = null;
            super.remove(index);
        } else if (c == glassPane) {
            throw new IllegalArgumentException("GlassPane can't be removed");
        } else {
            throw new AssertionError("Unknown component with index " + index);
        }
    }

    public void removeAll() {
        remove(view);
    }

    //This is important
    public boolean isOptimizedDrawingEnabled() {
        return false;
    }

    public void setLayout(LayoutManager mgr) {
        if (mgr != null) {
            throw new IllegalArgumentException("Only null layout is supported");
        }
        super.setLayout(mgr);
    }

    public void doLayout() {
        if (view != null) {
            view.setSize(view.getPreferredSize());
            visibleRect = getVisibleRect();
            view.setLocation(visibleRect.x, visibleRect.y);
        }
        glassPane.setLocation(0, 0);
        glassPane.setSize(getWidth(), getHeight());
    }

    public Dimension getPreferredSize() {
        if (isPreferredSizeSet()) {
            return super.getPreferredSize();
        }
        Dimension size = getTransformedSize().getSize();
        Insets insets = getInsets();
        size.width += insets.left + insets.right;
        size.height += insets.top + insets.bottom;
        return size;
    }

    private Rectangle getTransformedSize() {
        if (view != null) {
            Dimension viewSize = view.getSize();
            Rectangle viewRect = new Rectangle(viewSize);
            return at.createTransformedShape(viewRect).getBounds();
        }
        return new Rectangle(super.getPreferredSize());
    }

    public void paint(Graphics g) {
        //repaint the whole transformer in case the view component was repainted
        Rectangle clipBounds = g.getClipBounds();        
        if (clipBounds != null && !clipBounds.equals(visibleRect)) {
            repaint();
        }
        //clear the background
        g.setColor(getBackground());
        g.fillRect(0, 0, getWidth(), getHeight());

        if (view != null && at.getDeterminant() != 0) {
            Graphics2D g2 = (Graphics2D) g.create();
            Insets insets = getInsets();
            Rectangle bounds = getBounds();
            
            //don't forget about insets
            bounds.x += insets.left;
            bounds.y += insets.top;
            bounds.width -= insets.left + insets.right;
            bounds.height -= insets.top + insets.bottom;
            double centerX1 = bounds.getCenterX();
            double centerY1 = bounds.getCenterY();

            Rectangle tb = getTransformedSize();
            double centerX2 = tb.getCenterX();
            double centerY2 = tb.getCenterY();

            //set antialiasing by default
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            if (renderingHints != null) {
                g2.addRenderingHints(renderingHints);
            }
            //translate it to the center of the view component again
            double tx = centerX1 - centerX2 - getX();
            double ty = centerY1 - centerY2 - getY();
            g2.translate((int) tx, (int) ty);
            g2.transform(at);
            view.paint(g2);
            g2.dispose();
        }
        //paint the border
        paintBorder(g);
    }

    private class MagicGlassPane extends JPanel {
        private Component mouseEnteredComponent;
        private Component mouseDraggedComponent;
        private Component mouseCurrentComponent;

        public MagicGlassPane() {
            super(null);
            setOpaque(false);
            enableEvents(AWTEvent.MOUSE_EVENT_MASK);
            enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);
            enableEvents(AWTEvent.MOUSE_WHEEL_EVENT_MASK);
            ToolTipManager.sharedInstance().registerComponent(this);
        }

        private MouseEvent transformMouseEvent(MouseEvent event) {
            if (event == null) {
                throw new IllegalArgumentException("MouseEvent is null");
            }
            MouseEvent newEvent;
            if (event instanceof MouseWheelEvent) {
                MouseWheelEvent mouseWheelEvent = (MouseWheelEvent) event;
                newEvent = new MouseWheelEvent(mouseWheelEvent.getComponent(), mouseWheelEvent.getID(),
                        mouseWheelEvent.getWhen(), mouseWheelEvent.getModifiers(),
                        mouseWheelEvent.getX(), mouseWheelEvent.getY(),
                        mouseWheelEvent.getClickCount(), mouseWheelEvent.isPopupTrigger(),
                        mouseWheelEvent.getScrollType(), mouseWheelEvent.getScrollAmount(),
                        mouseWheelEvent.getWheelRotation());
            } else {
                newEvent = new MouseEvent(event.getComponent(), event.getID(),
                        event.getWhen(), event.getModifiers(),
                        event.getX(), event.getY(),
                        event.getClickCount(), event.isPopupTrigger(), event.getButton());
            }
            if (view != null && at.getDeterminant() != 0) {
                Rectangle viewBounds = getTransformedSize();
                Insets insets = JXTransformer.this.getInsets();
                int xgap = (getWidth() - (viewBounds.width + insets.left + insets.right)) / 2;
                int ygap = (getHeight() - (viewBounds.height + insets.top + insets.bottom)) / 2;
                
                double x = newEvent.getX() + viewBounds.getX() - insets.left;
                double y = newEvent.getY() + viewBounds.getY() - insets.top;
                Point2D p = new Point2D.Double(x - xgap, y - ygap);

                Point2D tp;
                try {
                    tp = at.inverseTransform(p, null);
                } catch (NoninvertibleTransformException ex) {
                    //can't happen, we check it before
                    throw new AssertionError("NoninvertibleTransformException");
                }
                //Use transformed coordinates to get the current component
                mouseCurrentComponent =
                        SwingUtilities.getDeepestComponentAt(view, (int) tp.getX(), (int) tp.getY());
                if (mouseCurrentComponent == null) {
                    mouseCurrentComponent = JXTransformer.this;
                }
                Component tempComponent = mouseCurrentComponent;
                if (mouseDraggedComponent != null) {
                    tempComponent = mouseDraggedComponent;
                }

                Point point = SwingUtilities.convertPoint(view, (int) tp.getX(), (int) tp.getY(), tempComponent);
                newEvent.setSource(tempComponent);
                newEvent.translatePoint(point.x - event.getX(), point.y - event.getY());
            }
            return newEvent;
        }

        protected void processMouseEvent(MouseEvent e) {
            MouseEvent transformedEvent = transformMouseEvent(e);
            switch (e.getID()) {
                case MouseEvent.MOUSE_ENTERED:
                    if (mouseDraggedComponent == null || mouseCurrentComponent == mouseDraggedComponent) {
                        dispatchMouseEvent(transformedEvent);
                    }
                    break;
                case MouseEvent.MOUSE_EXITED:
                    if (mouseEnteredComponent != null) {
                        dispatchMouseEvent(createEnterExitEvent(mouseEnteredComponent, MouseEvent.MOUSE_EXITED, e));
                        mouseEnteredComponent = null;
                    }
                    break;
                case MouseEvent.MOUSE_RELEASED:
                    if (mouseDraggedComponent != null && e.getButton() == MouseEvent.BUTTON1) {
                        transformedEvent.setSource(mouseDraggedComponent);
                        mouseDraggedComponent = null;
                    }
                    dispatchMouseEvent(transformedEvent);
                    break;
                default:
                    dispatchMouseEvent(transformedEvent);
            }
            super.processMouseEvent(e);
        }

        private void dispatchMouseEvent(MouseEvent event) {
            MouseListener[] mouseListeners =
                    event.getComponent().getMouseListeners();
            for (MouseListener listener : mouseListeners) {
                //skip all ToolTipManager's related listeners
                if (!listener.getClass().getName().startsWith("javax.swing.ToolTipManager")) {
                    switch (event.getID()) {
                        case MouseEvent.MOUSE_PRESSED:
                            listener.mousePressed(event);
                            break;
                        case MouseEvent.MOUSE_RELEASED:
                            listener.mouseReleased(event);
                            break;
                        case MouseEvent.MOUSE_CLICKED:
                            listener.mouseClicked(event);
                            break;
                        case MouseEvent.MOUSE_EXITED:
                            listener.mouseExited(event);
                            break;
                        case MouseEvent.MOUSE_ENTERED:
                            listener.mouseEntered(event);
                            break;
                        default:
                            throw new AssertionError();
                    }
                }
            }
        }

        protected void processMouseMotionEvent(MouseEvent e) {
            MouseEvent transformedEvent = transformMouseEvent(e);
            if (mouseEnteredComponent == null) {
                mouseEnteredComponent = mouseCurrentComponent;
            }
            switch (e.getID()) {
                case MouseEvent.MOUSE_MOVED:
                    if (mouseCurrentComponent != mouseEnteredComponent) {
                        dispatchMouseEvent(createEnterExitEvent(mouseEnteredComponent, MouseEvent.MOUSE_EXITED, e));
                        dispatchMouseEvent(createEnterExitEvent(mouseCurrentComponent, MouseEvent.MOUSE_ENTERED, e));
                    }
                    break;
                case MouseEvent.MOUSE_DRAGGED:
                    if (mouseDraggedComponent == null) {
                        mouseDraggedComponent = mouseEnteredComponent;
                    }
                    if (mouseEnteredComponent == mouseDraggedComponent && mouseCurrentComponent != mouseDraggedComponent) {
                        dispatchMouseEvent(createEnterExitEvent(mouseDraggedComponent, MouseEvent.MOUSE_EXITED, e));
                    } else if (mouseEnteredComponent != mouseDraggedComponent && mouseCurrentComponent == mouseDraggedComponent) {
                        dispatchMouseEvent(createEnterExitEvent(mouseDraggedComponent, MouseEvent.MOUSE_ENTERED, e));
                    }
                    if (mouseDraggedComponent != null) {
                        transformedEvent.setSource(mouseDraggedComponent);
                    }
                    break;
            }
            mouseEnteredComponent = mouseCurrentComponent;
            //dispatch MouseMotionEvent
            MouseMotionListener[] mouseMotionListeners =
                    transformedEvent.getComponent().getMouseMotionListeners();
            for (MouseMotionListener listener : mouseMotionListeners) {
                //skip all ToolTipManager's related listeners
                if (!listener.getClass().getName().startsWith("javax.swing.ToolTipManager")) {
                    switch (transformedEvent.getID()) {
                        case MouseEvent.MOUSE_MOVED:
                            listener.mouseMoved(transformedEvent);
                            break;
                        case MouseEvent.MOUSE_DRAGGED:
                            listener.mouseDragged(transformedEvent);
                            break;
                        default:
                            throw new AssertionError();
                    }
                }
            }
            super.processMouseMotionEvent(e);
        }

        protected void processMouseWheelEvent(MouseWheelEvent e) {
            MouseWheelEvent transformedEvent = (MouseWheelEvent) transformMouseEvent(e);
            MouseWheelListener[] mouseWheelListeners =
                    transformedEvent.getComponent().getMouseWheelListeners();
            for (MouseWheelListener listener : mouseWheelListeners) {
                listener.mouseWheelMoved(transformedEvent);
            }
            super.processMouseWheelEvent(e);
        }

        public String getToolTipText(MouseEvent event) {
            if (mouseEnteredComponent instanceof JComponent) {
                return ((JComponent) mouseEnteredComponent).getToolTipText();
            }
            return null;
        }

        private MouseEvent createEnterExitEvent(Component c, int eventId, MouseEvent mouseEvent) {
            return new MouseEvent(c, eventId, mouseEvent.getWhen(), 0,
                    mouseEvent.getX(), mouseEvent.getY(), 0,
                    false, MouseEvent.NOBUTTON);
        }

        public String toString() {
            return "GlassPane";
        }
    }

    /**
     * This class helps view component to be in the visible area;
     * this is important when transformer is inside JScrollPane 
     */     
    private class Handler extends ComponentAdapter implements HierarchyBoundsListener {
        public void componentMoved(ComponentEvent e) {
            update();
        }

        public void ancestorMoved(HierarchyEvent e) {
            update();
        }

        public void ancestorResized(HierarchyEvent e) {
            update();
        }

        private void update() {
            if (!getVisibleRect().equals(visibleRect)) {
                revalidate();
            }
        }
    }

    /**
     * Never returns null
     */
    public AffineTransform getTransform() {
        return new AffineTransform(at);
    }

    public void setTransform(AffineTransform at) {
        if (at == null) {
            throw new IllegalArgumentException("AffineTransform is null");
        }
        this.at = new AffineTransform(at);
        revalidate();
        repaint();
    }

    public void rotate(double theta) {
        AffineTransform transform = getTransform();
        transform.rotate(theta);
        setTransform(transform);
    }

    public void scale(double sx, double sy) {
        AffineTransform transform = getTransform();
        transform.scale(sx, sy);
        setTransform(transform);
    }

    public void shear(double sx, double sy) {
        AffineTransform transform = getTransform();
        transform.shear(sx, sy);
        setTransform(transform);
    }
}
    /////////////
    /*
     * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
     * Santa Clara, California 95054, U.S.A. All rights reserved.
     *
     * This library is free software; you can redistribute it and/or
     * modify it under the terms of the GNU Lesser General Public
     * License as published by the Free Software Foundation; either
     * version 2.1 of the License, or (at your option) any later version.
     *
     * This 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
     * Lesser General Public License for more details.
     *
     * You should have received a copy of the GNU Lesser General Public
     * License along with this library; if not, write to the Free Software
     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     */

    import javax.swing.*;
    import javax.swing.border.TitledBorder;
    import javax.swing.event.ChangeEvent;
    import javax.swing.event.ChangeListener;
    import java.awt.*;
    import java.awt.event.*;
    import java.awt.geom.AffineTransform;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Vector;

    /*
     * @author Alexander Potochkin
     * 
     * https://swinghelper.dev.java.net/
     * http://weblogs.java.net/blog/alexfromsun/ 
     */
    class TransformerDemo extends JFrame implements ChangeListener {

        private List<JXTransformer> transformers = new ArrayList<JXTransformer>();
        private JSlider rotationSlider;
        private JSlider scalingSlider;
        private JSlider shearingSlider;

        public TransformerDemo() {
            super("Transformer demo");
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

            JMenuBar bar = new JMenuBar();
            JMenu lafMenu = new JMenu("LaF");

            JMenuItem winLaf = new JMenuItem("Windows LaF");
            lafMenu.add(winLaf);
            winLaf.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    setLaf("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
                }
            });
            JMenuItem motifLaf = new JMenuItem("Motif LaF");
            lafMenu.add(motifLaf);
            motifLaf.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    setLaf("com.sun.java.swing.plaf.motif.MotifLookAndFeel");
                }
            });
            bar.add(lafMenu);
            JMenuItem metalLaf = new JMenuItem("Metal LaF");
            lafMenu.add(metalLaf);
            metalLaf.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    setLaf("javax.swing.plaf.metal.MetalLookAndFeel");
                }
            });

            JMenu settingsMenu = new JMenu("Settings");
            settingsMenu.setMnemonic(KeyEvent.VK_S);
            JMenuItem item = new JMenuItem("Reset sliders", KeyEvent.VK_R);
            item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.ALT_MASK));
            item.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    rotationSlider.setValue(0);
                    scalingSlider.setValue(100);
                    shearingSlider.setValue(0);
                }
            });
            settingsMenu.add(item);
            bar.add(settingsMenu);
            setJMenuBar(bar);

            JPanel panel = new JPanel(new BorderLayout());
            panel.add(createDemoPanel());
            panel.add(createStressTestPanel(), BorderLayout.EAST);
            add(new JScrollPane(panel));
            add(new JScrollPane(createToolPanel()), BorderLayout.SOUTH);
            pack();
        }

        private void setLaf(String laf) {
            try {
                UIManager.setLookAndFeel(laf);
                SwingUtilities.updateComponentTreeUI(this);
            } catch (Exception e) {
                e.printStackTrace();
            }
            for (JXTransformer t : transformers) {
                t.revalidate();
                t.repaint();
            }
        }

        private JPanel createStressTestPanel() {
            JPanel panel = new JPanel();
            TitledBorder titledBorder = BorderFactory.createTitledBorder("Stress test (with tooltips)");
            titledBorder.setTitleJustification(TitledBorder.CENTER);
            panel.setBorder(titledBorder);
            JButton lowerButton = new JButton("Button");
            lowerButton.setLayout(new FlowLayout());
            lowerButton.setToolTipText("Lower button");
            JButton middleButton = new JButton();
            middleButton.setToolTipText("Middle button");
            middleButton.setLayout(new FlowLayout());
            lowerButton.add(middleButton);
            JButton upperButton = new JButton("Upper button");
            upperButton.setToolTipText("Upper button");
            middleButton.add(upperButton);
            panel.add(createTransformer(lowerButton));
            return panel;
        }

        private JPanel createDemoPanel() {
            JPanel buttonPanel = new JPanel(new GridLayout(3, 2));
            TitledBorder titledBorder = BorderFactory.createTitledBorder("Try three sliders below !");
            Font titleFont = titledBorder.getTitleFont();
            titledBorder.setTitleFont(titleFont.deriveFont(titleFont.getSize2D() + 10));
            titledBorder.setTitleJustification(TitledBorder.CENTER);
            buttonPanel.setBorder(titledBorder);
            JButton b = new JButton("JButton");
            b.setPreferredSize(new Dimension(100, 50));
            buttonPanel.add(createTransformer(b));

            Vector<String> v = new Vector<String>();
            v.add("One");
            v.add("Two");
            v.add("Three");
            JList list = new JList(v);
            buttonPanel.add(createTransformer(list));

            buttonPanel.add(createTransformer(new JCheckBox("JCheckBox")));

            JSlider slider = new JSlider(0, 100);
            slider.setLabelTable(slider.createStandardLabels(25, 0));
            slider.setPaintLabels(true);
            slider.setPaintTicks(true);
            slider.setMajorTickSpacing(10);
            buttonPanel.add(createTransformer(slider));

            buttonPanel.add(createTransformer(new JRadioButton("JRadioButton")));
            final JLabel label = new JLabel("JLabel");
            label.addMouseListener(new MouseAdapter() {
                public void mouseEntered(MouseEvent e) {
                    Font font = label.getFont();
                    label.setFont(font.deriveFont(font.getSize2D() + 10));
                }

                public void mouseExited(MouseEvent e) {
                    Font font = label.getFont();
                    label.setFont(font.deriveFont(font.getSize2D() - 10));
                }
            });
            buttonPanel.add(createTransformer(label));

            return buttonPanel;
        }

        private JXTransformer createTransformer(JComponent c) {
            JXTransformer t = new JXTransformer(c);
            transformers.add(t);
            return t;
        }

        private JPanel createToolPanel() {
            JPanel panel = new JPanel(new GridLayout(1, 0));
            rotationSlider = new JSlider(-180, 180, 0);
            rotationSlider.setLabelTable(rotationSlider.createStandardLabels(90, -180));
            rotationSlider.setPaintLabels(true);
            rotationSlider.setPaintTicks(true);
            rotationSlider.setMajorTickSpacing(45);
            rotationSlider.addChangeListener(this);
            rotationSlider.setBorder(BorderFactory.createTitledBorder("Rotate"));
            panel.add(rotationSlider);

            shearingSlider = new JSlider(-10, 10, 0);
            shearingSlider.setLabelTable(shearingSlider.createStandardLabels(5, -10));
            shearingSlider.setPaintLabels(true);
            shearingSlider.setPaintTicks(true);
            shearingSlider.setMajorTickSpacing(2);
            shearingSlider.addChangeListener(this);
            shearingSlider.setBorder(BorderFactory.createTitledBorder("Shear"));
            panel.add(shearingSlider);

            scalingSlider = new JSlider(50, 150, 100);
            scalingSlider.setLabelTable(scalingSlider.createStandardLabels(25, 50));
            scalingSlider.setPaintLabels(true);
            scalingSlider.setPaintTicks(true);
            scalingSlider.setMajorTickSpacing(10);
            scalingSlider.addChangeListener(this);
            scalingSlider.setBorder(BorderFactory.createTitledBorder("Scale"));
            panel.add(scalingSlider);
            return panel;
        }

        public void stateChanged(ChangeEvent e) {
            AffineTransform at = new AffineTransform();
            at.rotate(rotationSlider.getValue() * Math.PI / 180);
            double scale = scalingSlider.getValue() / 100.0;
            at.scale(scale, scale);
            double shear = shearingSlider.getValue() / 10.0;
            at.shear(shear, 0);
            for (JXTransformer t : transformers) {
                t.setTransform(at);
            }
        }

        public static void main(String[] args) {
            TransformerDemo demo = new TransformerDemo();
            demo.setSize(800, 600);
            demo.setVisible(true);
        }
    }