javaswingmouseeventjlayer

Mapping mouse event coordinates


I've implemented a JLayer<JPanel> component which paint a zoomed Graphics of itself, so all his descending components will be zoomed too. This JLayer is applied as ContentPane to a JFrame component.

The main problem is that all the zoom applies, indeed, only to the graphic and the actual size and position of the components remain the same. This implies that all the mouse events happen in the wrong position respectively to what the user see.

I've king of tweaked it defining a GlassPane JComponent at the top of the frame which has it's own MouseInputAdapter which redispatch the MouseEvents to the underlying components using SwingUtilities.getDeepestComponentAt(). This is accomplished by creating a new MouseEvent with the mouse coordinates mapped depending on the zoom value. (obtained modifying the How to use RootPanes tutorial)

This method is obviously not satisfying because a lot of events simply can't be fired (for example the MOUSE_ENTERED events fired by all the descending components). On the other hand using the LayerUI.paint() override implies that i have to have something which remap all the mouse coordinates.

  1. Is there a way to map the mouse coordinates without breaking the inner MouseEvent processing?

    or

  2. Is there another way to zoom in the bitmap of the components while modifying also the real position and size? I've kind of tried this but calling the setSize() of an inner component seems to someway call the LayerUi.paint() a second time so all that i get is some bigger graphic disconnected from the actual widget position


Solution

  • This is what i've done so far thanks to MadProgrammer and PBar extensions, working with JLayer in java 8.

    The TransformUI include in itself the PBar's org.pbjar.jxlayer.plaf.ext.MouseEventUI and org.pbjar.jxlayer.plaf.ext.TransformUI:

    package jzoom.transform;
    
    import java.awt.AWTEvent;
    import java.awt.Component;
    import java.awt.Container;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Point;
    import java.awt.event.MouseEvent;
    import java.awt.event.MouseWheelEvent;
    import java.awt.geom.AffineTransform;
    import java.awt.geom.NoninvertibleTransformException;
    import java.util.HashSet;
    import java.util.Set;
    
    import javax.swing.JComponent;
    import javax.swing.JLayer;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    import javax.swing.plaf.LayerUI;
    
    /**
     * 
     * This UI apply an {@link AffineTransform} to all the visible objects, and apply the inversion
     *  of the same transform to all the mouse event's coordinates
     * 
     * @author Andrea.Maracci based on the pbjar JXLayer extension
     *
     */
    public class TransformUI extends LayerUI<JComponent> {
        private static final long serialVersionUID = 1L;
    
        private Component lastEnteredTarget, lastPressedTarget;
    
        private final Set<JComponent> originalDoubleBuffered = new HashSet<JComponent>();
        private AffineTransform transform = new AffineTransform();
        private JLayer<JComponent> installedLayer;
        private boolean dispatchingMode = false;
    
        /**
         * Process the mouse events and map the mouse coordinates inverting the internal affine transformation.
         * 
         * @param
         * event the event to be dispatched
         * layer the layer this LayerUI is set to
         * 
         */
        @Override
        public void eventDispatched(AWTEvent event, final JLayer<? extends JComponent> layer) {
            if (event instanceof MouseEvent) {
                MouseEvent mouseEvent = (MouseEvent) event;
                /*
                 * The if discriminate between generated and original event.
                 * Removing it cause a stack overflow caused by the event being redispatched to this class.
                 */
                if (!dispatchingMode) {
                    // Process an original mouse event
                    dispatchingMode = true;
                    try {
                        redispatchMouseEvent(mouseEvent, layer);
                    } finally {
                        dispatchingMode = false;
                    }
                } else {
                    /*
                     * Process generated mouse events
                     * Added a check, because on mouse entered or exited, the cursor
                     * may be set to specific dragging cursors.
                     */
                    if (MouseEvent.MOUSE_ENTERED == mouseEvent.getID() || MouseEvent.MOUSE_EXITED == mouseEvent.getID()) {
                        layer.getGlassPane().setCursor(null);
                    } else {
                        Component component = mouseEvent.getComponent();
                        layer.getGlassPane().setCursor(component.getCursor());
                    }
                }
            } else {
                super.eventDispatched(event, layer);
            }
            layer.repaint();
        }
    
        /**
         * Set the affine transformation applied to the graphics
         * @param transform the transformation
         */
        public void setTransform(AffineTransform transform) {
            if (transform != null) {
                this.transform = transform;
            }
        }
    
        /**
         * Return the affine transformation applied to the graphics
         * @return the transformation
         */
        public AffineTransform getTransform() {
            return transform;
        }
    
        /**
         * Paint the specified component {@code c} applying the transformation on it's graphic
         * 
         * @param
         * g - the Graphics context in which to paint
         * c - the component being painted
         */
        @Override
        public void paint(Graphics g, JComponent c) {
            if (g instanceof Graphics2D) {
                Graphics2D g2 = (Graphics2D) g.create();
                JLayer<? extends JComponent> l = (JLayer<? extends JComponent>) c;
                g2.transform(transform);
                paintLayer(g2, l);
                g2.dispose();
            }
        }
    
        /**
         * Paint the view decorated by the JLayer {@code layer} and the JLayer itself
         * 
         * @param g2
         * @param layer the layer this LayerUI is set to
         */
        private final void paintLayer(Graphics2D g2, JLayer<? extends JComponent> layer) {
            JComponent view = layer.getView();
            if (view != null) {
                if (view.getX() < 0 || view.getY() < 0) {
                    setToNoDoubleBuffering(view);
                    g2.translate(view.getX(), view.getY());
                    view.paint(g2);
                    for (JComponent jComp : originalDoubleBuffered) {
                        jComp.setDoubleBuffered(true);
                    }
                    originalDoubleBuffered.clear();
                    return;
                }
            }
            layer.paint(g2);
        }
    
        /**
         * Disable the double buffering for the {@code component} and for all of it's children
         * 
         * @param component
         */
        private void setToNoDoubleBuffering(Component component) {
            if (component instanceof JComponent) {
                JComponent jComp = (JComponent) component;
                if (jComp.isDoubleBuffered()) {
                    originalDoubleBuffered.add(jComp);
                    jComp.setDoubleBuffered(false);
                }
            }
            if (component instanceof Container) {
                Container container = (Container) component;
                for (int index = 0; index < container.getComponentCount(); index++) {
                    setToNoDoubleBuffering(container.getComponent(index));
                }
            }
        }
    
        /**
         * {@inheritDoc}
         */
        @Override
        public void uninstallUI(JComponent component) {
            if (!(component instanceof JLayer<?>)) {
                throw new IllegalArgumentException(
                        this.getClass().getName() + " invalid class, must be a JLayer component");
            }
            JLayer<JComponent> jlayer = (JLayer<JComponent>) component;
            jlayer.setLayerEventMask(0);
            super.uninstallUI(component);
        }
    
        /**
         * {@inheritDoc}
         */
        @Override
        public void installUI(JComponent component) throws IllegalStateException {
            super.installUI(component);
            if (installedLayer != null) {
                throw new IllegalStateException(this.getClass().getName() + " cannot be shared between multiple layers");
            }
            if (!(component instanceof JLayer<?>)) {
                throw new IllegalArgumentException(
                        this.getClass().getName() + " invalid class, must be a JLayer component");
            }
            // component.getClass().getDeclaringClass();
            installedLayer = (JLayer<JComponent>) component;
            installedLayer.setLayerEventMask(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK
                    | AWTEvent.MOUSE_WHEEL_EVENT_MASK | AWTEvent.KEY_EVENT_MASK | AWTEvent.FOCUS_EVENT_MASK);
        }
    
        /**
         * Process the mouse events and map the mouse coordinates inverting the internal affine transformation.
         * It consume the original event, calculates the mapped mouse coordinates and find the real target of the mouse event.
         * It than create a new event with the correct informations in it and redispatch it to the target event
         * 
         * @param originalEvent the event to be dispatched
         * @param layer the layer this LayerUI is set to
         */
        private void redispatchMouseEvent(MouseEvent originalEvent, JLayer<? extends JComponent> layer) {
            if (layer.getView() != null) {
                if (originalEvent.getComponent() != layer.getGlassPane()) {
                    originalEvent.consume();
                }
                MouseEvent newEvent = null;
    
                Point realPoint = calculateTargetPoint(layer, originalEvent);
                Component realTarget = getTarget(layer, realPoint);
    
                // Component realTarget =
                // SwingUtilities.getDeepestComponentAt(layer.getView(),
                // realPoint.x, realPoint.y);
    
                if (realTarget != null) {
                    //System.out.println(realTarget.getClass().getName());
                    realTarget = getListeningComponent(originalEvent, realTarget);
                }
    
                switch (originalEvent.getID()) {
                case MouseEvent.MOUSE_PRESSED:
                    newEvent = transformMouseEvent(layer, originalEvent, realTarget, realPoint);
                    if (newEvent != null) {
                        lastPressedTarget = newEvent.getComponent();
                    }
                    break;
                case MouseEvent.MOUSE_RELEASED:
                    newEvent = transformMouseEvent(layer, originalEvent, lastPressedTarget, realPoint);
                    lastPressedTarget = null;
                    break;
                case MouseEvent.MOUSE_CLICKED:
                    newEvent = transformMouseEvent(layer, originalEvent, realTarget, realPoint);
                    lastPressedTarget = null;
                    break;
                case MouseEvent.MOUSE_MOVED:
                    newEvent = transformMouseEvent(layer, originalEvent, realTarget, realPoint);
                    generateEnterExitEvents(layer, originalEvent, realTarget, realPoint);
                    break;
                case MouseEvent.MOUSE_ENTERED:
                    generateEnterExitEvents(layer, originalEvent, realTarget, realPoint);
                    break;
                case MouseEvent.MOUSE_EXITED:
                    generateEnterExitEvents(layer, originalEvent, realTarget, realPoint);
                    break;
                case MouseEvent.MOUSE_DRAGGED:
                    newEvent = transformMouseEvent(layer, originalEvent, lastPressedTarget, realPoint);
                    generateEnterExitEvents(layer, originalEvent, realTarget, realPoint);
                    break;
                case (MouseEvent.MOUSE_WHEEL):
                    // redispatchMouseWheelEvent((MouseWheelEvent) originalEvent,
                    // realTarget, realPoint);
                    newEvent = transformMouseWheelEvent(layer, (MouseWheelEvent) originalEvent, realTarget, realPoint);
                    break;/**/
                }
                dispatchMouseEvent(newEvent);
            }
        }
    
        /**
         * Apply the inverse transformation to {@code point}
         * 
         * @param layer the layer this LayerUI is set to
         * @param point the starting point
         * @return the transformed point
         */
        private Point transformPoint(JLayer<? extends JComponent> layer, Point point) {
            if (transform != null) {
                try {
                    transform.inverseTransform(point, point);
                } catch (NoninvertibleTransformException e) {
                    e.printStackTrace();
                }
            }
            return point;
        }
    
        /**
         * Find the deepest component in the AWT hierarchy
         * 
         * @param layer  the layer to which this UI is installed
         * @param targetPoint the point in layer's coordinates
         * @return the component in the specified point
         */
        private Component getTarget(JLayer<? extends JComponent> layer, Point targetPoint) {
            Component view = layer.getView();
            if (view == null) {
                return null;
            } else {
                Point viewPoint = SwingUtilities.convertPoint(layer, targetPoint, view);
                return SwingUtilities.getDeepestComponentAt(view, viewPoint.x, viewPoint.y);
            }
        }
    
        /**
         * Convert the {@code mouseEvent}'s coordinates to the {@code layer}'s space
         * @param layer the layer this LayerUI is set to
         * @param mouseEvent the original mouse event
         * @return the {@code mouseEvent}'s point transformed to the {@code layer}'s coordinate space
         */
        private Point calculateTargetPoint(JLayer<? extends JComponent> layer,
                MouseEvent mouseEvent) {
            Point point = mouseEvent.getPoint();
            //SwingUtilities.convertPointToScreen(point, mouseEvent.getComponent());
            //SwingUtilities.convertPointFromScreen(point, layer);
            point = SwingUtilities.convertPoint(mouseEvent.getComponent(), point, layer);
            return transformPoint(layer, point);
    
            }
    
        private MouseEvent transformMouseEvent(JLayer<? extends JComponent> layer, MouseEvent mouseEvent, Component target, Point realPoint) {
            return transformMouseEvent( layer, mouseEvent, target, realPoint, mouseEvent.getID());
        }
    
        /**
         * Create the new event to being dispatched
         */
        private MouseEvent transformMouseEvent(JLayer<? extends JComponent> layer, MouseEvent mouseEvent, Component target, Point targetPoint, int id) {
            if (target == null) {
                return null;
            } else {
                Point newPoint = SwingUtilities.convertPoint(layer, targetPoint, target);
                return new MouseEvent(target, //
                        id, //
                        mouseEvent.getWhen(), //
                        mouseEvent.getModifiers(), //
                        newPoint.x, //
                        newPoint.y, //
                        mouseEvent.getClickCount(), //
                        mouseEvent.isPopupTrigger(), //
                        mouseEvent.getButton());
            }
        }
    
        /**
         * Create the new mouse wheel event to being dispached
         */
        private MouseWheelEvent transformMouseWheelEvent( JLayer<? extends JComponent> layer, MouseWheelEvent mouseWheelEvent, Component target,
                Point targetPoint) {
            if (target == null) {
                return null;
            } else {
                Point newPoint = SwingUtilities.convertPoint(layer, targetPoint, target);
                return new MouseWheelEvent(target, //
                        mouseWheelEvent.getID(), //
                        mouseWheelEvent.getWhen(), //
                        mouseWheelEvent.getModifiers(), //
                        newPoint.x, //
                        newPoint.y, //
                        mouseWheelEvent.getClickCount(), //
                        mouseWheelEvent.isPopupTrigger(), //
                        mouseWheelEvent.getScrollType(), //
                        mouseWheelEvent.getScrollAmount(), //
                        mouseWheelEvent.getWheelRotation() //
                );
            }
        }
    
        /**
         * dispatch the {@code mouseEvent}
         * @param mouseEvent the event to be dispatched
         */
        private void dispatchMouseEvent(MouseEvent mouseEvent) {
            if (mouseEvent != null) {
                Component target = mouseEvent.getComponent();
                target.dispatchEvent(mouseEvent);
            }
        }
    
        /**
         * Get the listening component associated to the {@code component}'s {@code event}
         */
        private Component getListeningComponent(MouseEvent event, Component component) {
            switch (event.getID()) {
            case (MouseEvent.MOUSE_CLICKED):
            case (MouseEvent.MOUSE_ENTERED):
            case (MouseEvent.MOUSE_EXITED):
            case (MouseEvent.MOUSE_PRESSED):
            case (MouseEvent.MOUSE_RELEASED):
                return getMouseListeningComponent(component);
            case (MouseEvent.MOUSE_DRAGGED):
            case (MouseEvent.MOUSE_MOVED):
                return getMouseMotionListeningComponent(component);
            case (MouseEvent.MOUSE_WHEEL):
                return getMouseWheelListeningComponent(component);
            }
            return null;
        }
    
        /**
         * Cycles through the {@code component}'s parents to find the {@link Component} with associated {@link MouseListener}
         */
        private Component getMouseListeningComponent(Component component) {
            if (component.getMouseListeners().length > 0) {
                return component;
            } else {
                Container parent = component.getParent();
                if (parent != null) {
                    return getMouseListeningComponent(parent);
                } else {
                    return null;
                }
            }
        }
    
        /**
         * Cycles through the {@code component}'s parents to find the {@link Component} with associated {@link MouseMotionListener}
         */
        private Component getMouseMotionListeningComponent(Component component) {
            /*
             * Mouse motion events may result in MOUSE_ENTERED and MOUSE_EXITED.
             * 
             * Therefore, components with MouseListeners registered should be
             * returned as well.
             */
            if (component.getMouseMotionListeners().length > 0 || component.getMouseListeners().length > 0) {
                return component;
            } else {
                Container parent = component.getParent();
                if (parent != null) {
                    return getMouseMotionListeningComponent(parent);
                } else {
                    return null;
                }
            }
        }
    
        /**
         * Cycles through the {@code component}'s parents to find the {@link Component} with associated {@link MouseWheelListener}
         */
        private Component getMouseWheelListeningComponent(Component component) {
            if (component.getMouseWheelListeners().length > 0) {
                return component;
            } else {
                Container parent = component.getParent();
                if (parent != null) {
                    return getMouseWheelListeningComponent(parent);
                } else {
                    return null;
                }
            }
        }
    
        /**
         * Generate a {@code MOUSE_ENTERED} and {@code MOUSE_EXITED} event when the target component is changed
         */
        private void generateEnterExitEvents( JLayer<? extends JComponent> layer,MouseEvent originalEvent, Component newTarget, Point realPoint) {
            if (lastEnteredTarget != newTarget) {
                dispatchMouseEvent(
                        transformMouseEvent(layer, originalEvent, lastEnteredTarget, realPoint, MouseEvent.MOUSE_EXITED));
                lastEnteredTarget = newTarget;
                //System.out.println("Last " + lastEnteredTarget.getClass().getName());
                dispatchMouseEvent(
                        transformMouseEvent(layer, originalEvent, lastEnteredTarget, realPoint, MouseEvent.MOUSE_ENTERED));
            }
        }
    }
    

    ZoomPanel is a JPanel which include a JLayer with a TransformUI in it. The JLayer contains a JPanel with a SpringLayout in which the constraints are updated with the scale factor to layout correctly the components (JXLayer had it's own LayoutManager but in JLayer you can't set it)

    package jzoom.transform;
    
    import ...
    
    public class ZoomPanel extends JPanel {
    
        private static final long serialVersionUID = 1L;
    
        private AffineTransform transform;
        private TransformUI layerUI;
        private JLayer<JComponent> layer;
    
        private SpringLayout layout;
        private JPanel springPanel;
    
        private Container view = null;
    
        public ZoomPanel() {
            this(null);
        }
    
        public ZoomPanel(Container view) {
    
            setLayout(new BorderLayout());
    
            this.view = view;
            transform = new AffineTransform();
    
            layout = new SpringLayout();
            springPanel = new JPanel(layout);
            if (view != null) {
                updateConstraints();
                springPanel.add(view);
            }
    
            layerUI = new TransformUI();
            layerUI.setTransform(transform);
            layer = new JLayer<JComponent>(springPanel, layerUI);
    
            super.add(layer);
    
        }
    
        private void updateConstraints() {
            Spring width = layout.getConstraint(SpringLayout.WIDTH, springPanel);
            Spring height = layout.getConstraint(SpringLayout.HEIGHT, springPanel);
    
            SpringLayout.Constraints constraints = layout.getConstraints(view);
            constraints.setX(Spring.constant(0));
            constraints.setY(Spring.constant(0));
            constraints.setWidth(Spring.scale(width, (float) (1 / transform.getScaleX())));
            constraints.setHeight(Spring.scale(height, (float) (1 / transform.getScaleX())));
        }
    
        public void setView(Container view) {
            if (this.view != null) {
                throw new IllegalStateException(
                        this.getClass().getName() + " cannot be shared between multiple containers");
            }
    
            if (view != null) {
                this.view = view;
                updateConstraints();
                springPanel.add(view);
            } else {
                throw new IllegalArgumentException("Can't set a null view");
            }
        }
    
        public double getScale() {
            return transform.getScaleX();
        }
    
        public void zoomIn() {
            setScale(transform.getScaleX() + 0.1);
        }
    
        public void zoomOut() {
            setScale(transform.getScaleX() - 0.1);
        }
    
        public void setScale(double scale) {
            if (!(scale < 1)) {
                transform.setToIdentity();
                transform.scale(scale, scale);
                updateConstraints();
                springPanel.updateUI();
            }
        }
    
        protected Component addToView(Component comp, Object constraints, int index) {
            if (view != null) {
                view.add(comp, constraints, index);
                return comp;
            }
    
            if (comp instanceof Container) {
                setView((Container) comp);
                return view;
            }
    
            throw new IllegalStateException("You need to add or set a Container view before adding Components");
        }
    
        @Override
        public Component add(Component comp) {
            // TODO Auto-generated method stub
            return addToView(comp, null, this.getComponentCount());
        }
    
        @Override
        public Component add(Component comp, int index) {
            // TODO Auto-generated method stub
            return addToView(comp, null, index);
        }
    
        @Override
        public void add(Component comp, Object constraints) {
            // TODO Auto-generated method stub
            addToView(comp, constraints, this.getComponentCount());
        }
    
        @Override
        public void add(Component comp, Object constraints, int index) {
            // TODO Auto-generated method stub
            addToView(comp, constraints, index);
        }
    
        private void inspectView(Container view) {
            PrintStream ps = null;
            try {
                ps = new PrintStream("C:\\Users\\andrea.maracci\\Documents\\sicraReflectionTemp.txt");
                inspectView(view, 0, ps);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } finally {
                if (ps != null) {
                    ps.close();
                }
            }
        }
    
        private static void inspectView(Component component, Integer level, PrintStream ps) {
            for (Integer i = 0; i < level; i++) {
                ps.print("\t");
            }
            ps.print(level + ")");
            ps.println("Inspecting " + component.getClass().getName());
    
            int accessibleCount = 0;
            if (component.getAccessibleContext() != null) {
                accessibleCount = component.getAccessibleContext().getAccessibleChildrenCount();
                if (accessibleCount > 0) {
                    ps.println("*********************************************ACCESSIBLE CONTEXT*********************************************");
                    for (int i = 0; i < accessibleCount; i++) {
                        ps.println(i + ") " + component.getAccessibleContext().getAccessibleChild(i).getClass().getName());
                    }
                    ps.println("************************************************************************************************************");
                }
            }
    
            if (component instanceof JComponent) {
                JComponent jComponent = ((JComponent)component);
    
                if (jComponent.getComponentCount() > 0) {
                    Component[] children = jComponent.getComponents();
                    for (Component child : children) {
                        inspectView(child, ++level, ps);
                    }
                }
            }
            --level;
        }
    }
    

    And here is an horrible test program

    package jzoom.test;
    
    import ...
    
    public class TestFinal {
        public static void main(String[] args) {
            new TestFinal();
        }
    
        public TestFinal() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
    
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (InstantiationException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (UnsupportedLookAndFeelException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.setLayout(new BorderLayout());
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setMinimumSize(new Dimension(400,500));
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public class TestPane extends JPanel {
    
            private JLayer<JComponent> layer;
            private TransformUI layerUI;
            private JPanel content;
            private AffineTransform transform = new AffineTransform();
            private ZoomPanel zoomPanel;
    
            public TestPane() {
    
                content = new JPanel(new GridBagLayout());
                // content = new JPanel(new XYLayout());
                GridBagConstraints gbc = new GridBagConstraints();
                gbc.gridy = 0;
                gbc.weighty = 0;
                gbc.weightx = 0;
                gbc.fill = GridBagConstraints.HORIZONTAL;
                JLabel label = new JLabel("Hello");
                JTextField field = new JTextField("World", 20);
    
                content.add(label, gbc);
                gbc.weightx = 1;
                content.add(field, gbc);
                // content.add(label, new XYConstraints(50, 20, 50, 22));
                // content.add(field, new XYConstraints(100, 20, 200, 22));
    
                gbc.gridy++;
                gbc.gridwidth = 2;
    
                final JSlider slider = new JSlider(100, 200);
                slider.setValue(100);
                slider.addChangeListener(new ChangeListener() {
    
                    @Override
                    public void stateChanged(ChangeEvent e) {
                        int value = slider.getValue();
                        double scale = value / 100d;
                        zoomPanel.setScale(scale);
                    }
                });
                content.add(slider, gbc);
                // content.add(slider, new XYConstraints(75, 50, 200, 50));
    
                gbc.gridy++;
                gbc.gridwidth = 2;
                gbc.weighty = 1;
                gbc.fill = GridBagConstraints.BOTH;
                JTextArea textArea = new JTextArea();
                textArea.setEditable(true);
                textArea.setText(
                        "pollofritto\npalma\npalmipedone\ncaccoletta\namammata\na\nasd\nasdgfag\nasdafa\nasdfasf\nadsfasdf\nadfadsf\nadsfdasf\nasdfdas\npollofritto\npalma\npalmipedone\ncaccoletta\namammata\na\nasd\nasdgfag\nasdafa\nasdfasf\nadsfasdf\nadfadsf\nadsfdasf\nasdfdas");
                // textArea.setPreferredSize(new Dimensions());
                JScrollPane scrollPane = new JScrollPane(textArea);
                scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
                scrollPane.setPreferredSize(new Dimension(200, 75));
                content.add(scrollPane, gbc);
    
                gbc.gridy++;
                gbc.gridwidth = 2;
                gbc.weighty = 0;
                gbc.weightx = 1;
                gbc.fill = GridBagConstraints.HORIZONTAL;
                String[] petStrings = { "Bird", "Cat", "Dog", "Rabbit", "Pig" };
                JComboBox petList = new JComboBox(petStrings);
                content.add(petList, gbc);
    
                JButton zoomIn = new JButton("Zoom In");
                // zoomIn.addMouseListener(new ZoomMouseListener());
                zoomIn.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        // System.out.println("FIRST");
                        // layerUI.zoomIn();
    
                        //double zoom = transform.getScaleX();
                        //transform.setToIdentity();
                        //transform.scale(zoom + 0.1, zoom + 0.1);
                        zoomPanel.zoomIn();
    
                        // jLayer.repaint();
                    }
                });
    
                JButton zoomOut = new JButton("Zoom Out");
                zoomOut.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        zoomPanel.zoomOut();
    
                        //double zoom = transform.getScaleX();
                        //transform.setToIdentity();
                        //transform.scale(zoom - 0.1, zoom - 0.1);
                        // jLayer.repaint();
                    }
                });
                gbc.gridy++;
                gbc.gridx = 0;
                gbc.gridwidth = 0;
                gbc.anchor = GridBagConstraints.LINE_END;
    
                // content.add(zoomOut, new XYConstraints(50, 120, 100, 25));
                // content.add(zoomIn, new XYConstraints(170, 120, 100, 25));
    
                JPanel button = new JPanel();
                button.setLayout(new BoxLayout(button, BoxLayout.LINE_AXIS));
                button.add(zoomOut);
                button.add(zoomIn);
                gbc.fill = GridBagConstraints.NONE;
                content.add(button, gbc);
    
                setLayout(new BorderLayout());
                setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
    
                zoomPanel = new ZoomPanel();
                zoomPanel.setView(content);
                add(zoomPanel);
            }
        }
    }