/* 
 * This code is part of Project Waterloo from King's College London
 * 
 *
 * Copyright King's College London  2011-
 * 
 * @author Malcolm Lidierth, King's College London  [Contact]
 * 
 * Project Waterloo 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 3 of the License, or
 * (at your option) any later version.
 *
 * Project Waterloo 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 program.  If not, see .
 *
 */
package kcl.waterloo.graphics;

import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.SpringLayout;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;
import kcl.waterloo.defaults.Colors;
import kcl.waterloo.graphics.images.Images;
import kcl.waterloo.math.ArrayMath;

/**
 * Provides a mouse-changeable region of interest that can be added to a graph.
 *
 * GJResizablePanel instances should be instantiated using the static factory
 * method:
 * {@code GJResizablePanel.createInstance(GJGraph gr, Rectangle2D.Double r);}
 *
 * GJResizablePanel instance mouse-interaction does not support rotation/zoom of
 * the view in the parent graph container.
 *
 * @author ML
 */
public class GJResizablePanel extends GJBasicPanel
        implements MouseListener, MouseMotionListener, ComponentListener, PropertyChangeListener {

    private final JLabel panelNW = new JLabel(Images.getIcon("arrow_corner.png"));
    private final JLabel panelNE = new JLabel(Images.getIcon("arrow_corner.png"));
    private final JLabel panelSW = new JLabel(Images.getIcon("arrow_corner.png"));
    private final JLabel panelSE = new JLabel(Images.getIcon("arrow_corner.png"));
    private final JLabel panelS = new JLabel(Images.getIcon("arrow_updown.png"));
    private final JLabel panelN = new JLabel(Images.getIcon("arrow_updown.png"));
    private final JLabel panelE = new JLabel(Images.getIcon("arrow_leftright.png"));
    private final JLabel panelW = new JLabel(Images.getIcon("arrow_leftright.png"));
    private static int sz = 10;
    private Point2D dragStart = null;
    //private Rectangle2D.Double axesRectangle = null;
    protected Rectangle2D.Double pixelRectangle = null;
    protected int minSiz = sz * 3;

    /**
     * Public constructor for XML serialization/de-serialization. Do not use -
     * call the factory createInstance methods to create instances.
     */
    public GJResizablePanel() {
        setBorder(new LineBorder(Colors.getColor("DARKGREEN"), 2, true));
        setBackground(new Color(0f, 1f, 0f, 0.1f));
        SpringLayout layout = new SpringLayout();
        setLayout(layout);
        add(panelNW);
        add(panelNE);
        add(panelSW);
        add(panelSE);
        add(panelN);
        add(panelS);
        add(panelE);
        add(panelW);

        layout.putConstraint(SpringLayout.EAST, panelNE, 0, SpringLayout.EAST, this);
        layout.putConstraint(SpringLayout.WEST, panelNE, -sz, SpringLayout.EAST, this);
        layout.putConstraint(SpringLayout.NORTH, panelNE, 0, SpringLayout.NORTH, this);
        layout.putConstraint(SpringLayout.SOUTH, panelNE, sz, SpringLayout.NORTH, this);

        layout.putConstraint(SpringLayout.EAST, panelSE, 0, SpringLayout.EAST, this);
        layout.putConstraint(SpringLayout.WEST, panelSE, -sz, SpringLayout.EAST, this);
        layout.putConstraint(SpringLayout.SOUTH, panelSE, 0, SpringLayout.SOUTH, this);
        layout.putConstraint(SpringLayout.NORTH, panelSE, -sz, SpringLayout.SOUTH, this);

        layout.putConstraint(SpringLayout.WEST, panelNW, 0, SpringLayout.WEST, this);
        layout.putConstraint(SpringLayout.EAST, panelNW, sz, SpringLayout.WEST, this);
        layout.putConstraint(SpringLayout.NORTH, panelNW, 0, SpringLayout.NORTH, this);
        layout.putConstraint(SpringLayout.SOUTH, panelNW, sz, SpringLayout.NORTH, this);

        layout.putConstraint(SpringLayout.WEST, panelSW, 0, SpringLayout.WEST, this);
        layout.putConstraint(SpringLayout.EAST, panelSW, sz, SpringLayout.WEST, this);
        layout.putConstraint(SpringLayout.SOUTH, panelSW, 0, SpringLayout.SOUTH, this);
        layout.putConstraint(SpringLayout.NORTH, panelSW, -sz, SpringLayout.SOUTH, this);

        layout.putConstraint(SpringLayout.HORIZONTAL_CENTER, panelN, 0, SpringLayout.HORIZONTAL_CENTER, this);
        layout.putConstraint(SpringLayout.NORTH, panelN, 0, SpringLayout.NORTH, this);
        layout.putConstraint(SpringLayout.SOUTH, panelN, sz, SpringLayout.NORTH, this);

        layout.putConstraint(SpringLayout.HORIZONTAL_CENTER, panelS, 0, SpringLayout.HORIZONTAL_CENTER, this);
        layout.putConstraint(SpringLayout.NORTH, panelS, -sz, SpringLayout.SOUTH, this);
        layout.putConstraint(SpringLayout.SOUTH, panelS, 0, SpringLayout.SOUTH, this);

        layout.putConstraint(SpringLayout.VERTICAL_CENTER, panelW, 0, SpringLayout.VERTICAL_CENTER, this);
        layout.putConstraint(SpringLayout.EAST, panelW, sz, SpringLayout.WEST, this);
        layout.putConstraint(SpringLayout.WEST, panelW, 0, SpringLayout.WEST, this);

        layout.putConstraint(SpringLayout.VERTICAL_CENTER, panelE, 0, SpringLayout.VERTICAL_CENTER, this);
        layout.putConstraint(SpringLayout.EAST, panelE, 0, SpringLayout.EAST, this);
        layout.putConstraint(SpringLayout.WEST, panelE, -sz, SpringLayout.EAST, this);

        setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        panelNE.setCursor(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR));
        panelNW.setCursor(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR));
        panelSE.setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
        panelSW.setCursor(Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR));
        panelS.setCursor(Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR));
        panelN.setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
        panelE.setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
        panelW.setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR));

        this.addMouseListener(this);
        panelNW.addMouseListener(this);
        panelNE.addMouseListener(this);
        panelSW.addMouseListener(this);
        panelSE.addMouseListener(this);
        panelS.addMouseListener(this);
        panelE.addMouseListener(this);
        panelN.addMouseListener(this);
        panelW.addMouseListener(this);

        this.addMouseMotionListener(this);
        panelNW.addMouseMotionListener(this);
        panelNE.addMouseMotionListener(this);
        panelSW.addMouseMotionListener(this);
        panelSE.addMouseMotionListener(this);
        panelS.addMouseMotionListener(this);
        panelE.addMouseMotionListener(this);
        panelN.addMouseMotionListener(this);
        panelW.addMouseMotionListener(this);

    }

    /**
     * Factory method for creating an instance in the center of the specified
     * graph.
     *
     * @param gr the target graph
     * @return
     */
    public static GJResizablePanel createInstance(JComponent gr) {
        Rectangle2D r = gr.getBounds();
        return createInstance(gr, new Rectangle2D.Double(r.getCenterX() - (0.1 * r.getWidth()),
                r.getCenterY() + (0.1 * r.getHeight()),
                0.2 * r.getWidth(),
                0.2 * r.getHeight()));
    }

    /**
     * Factory method for creating an instance.
     *
     * @param gr the target graph
     * @param r a Rectangle2D in axes units
     * @return
     */
    public static GJResizablePanel createInstance(JComponent gr, Rectangle2D.Double r) {
        GJResizablePanel roi = new GJResizablePanel();
        roi = (GJResizablePanel) gr.add(roi);
        //roi.axesRectangle = r;
        roi.pixelRectangle = r;
        roi.updateConstraints();
        gr.addPropertyChangeListener(roi);
        gr.addComponentListener(roi);
        gr.revalidate();
        gr.repaint();
        return roi;
    }

    private void reposition(MouseEvent e, double dX, double dY) {
        Rectangle2D.Double r = getPixelRectangle();
        if (e == null || e.getSource().equals(this)) {
            r.setRect(r.getX() + dX, r.getY() + dY,
                    r.getWidth(), r.getHeight());
        } else if (e.getSource().equals(this.panelNE)) {
            r.setRect(r.getX(), r.getY() + dY,
                    r.getWidth() + dX, r.getHeight() - dY);
        } else if (e.getSource().equals(this.panelNW)) {
            r.setRect(r.getX() + dX, r.getY() + dY,
                    r.getWidth() - dX, r.getHeight() - dY);
        } else if (e.getSource().equals(this.panelSE)) {
            r.setRect(r.getX(), r.getY(),
                    r.getWidth() + dX, r.getHeight() + dY);
        } else if (e.getSource().equals(this.panelSW)) {
            r.setRect(r.getX() + dX, r.getY(),
                    r.getWidth() - dX, r.getHeight() + dY);
        } else if (e.getSource().equals(this.panelN)) {
            r.setRect(r.getX(), r.getY() + dY,
                    r.getWidth(), r.getHeight() - dY);
        } else if (e.getSource().equals(this.panelS)) {
            r.setRect(r.getX(), r.getY(),
                    r.getWidth(), r.getHeight() + dY);
        } else if (e.getSource().equals(this.panelE)) {
            r.setRect(r.getX(), r.getY(),
                    r.getWidth() + dX, r.getHeight());
        } else if (e.getSource().equals(this.panelW)) {
            r.setRect(r.getX() + dX, r.getY(),
                    r.getWidth() - dX, r.getHeight());
        }
        r.setRect(r.x, r.y, Math.max(r.width, minSiz), Math.max(r.height, minSiz));
        setPixelRectangle(r);
        //axesRectangle = ((GJAbstractGraph) getParent()).transformPixelToPosition(pixelRectangle);
    }

    @Override
    public void revalidate() {
        super.revalidate();
    }

    /**
     * updateConstraints sets the initial constraints for this object in the
     * parent container.
     */
    void updateConstraints() {
        if (getParent() != null && pixelRectangle != null) {
            SpringLayout layout = (SpringLayout) getParent().getLayout();
            layout.putConstraint(SpringLayout.WEST, this, (int) pixelRectangle.getX(), SpringLayout.WEST, getParent());
            layout.putConstraint(SpringLayout.NORTH, this, (int) pixelRectangle.getY(), SpringLayout.NORTH, getParent());
            layout.putConstraint(SpringLayout.EAST, this, (int) (pixelRectangle.getX() + pixelRectangle.getWidth()), SpringLayout.WEST, getParent());
            layout.putConstraint(SpringLayout.SOUTH, this, (int) (pixelRectangle.getY() + pixelRectangle.getHeight()), SpringLayout.NORTH, getParent());
        }
    }


    /**
     * Returns a copy of the pixel-based bounds for this instance.
     *
     * @return the pixelRectangle
     */
    public Rectangle2D.Double getPixelRectangle() {
        if (pixelRectangle == null) {
            return null;
        } else {
            return (Rectangle2D.Double) pixelRectangle.clone();
        }
    }

    /**
     * Sets the pixel-based bounds for this instance together with the
     * corresponding axes-units based bounds. This method should only be used
     * when the instance is parented by a graph.
     *
     * @param pixelRectangle the pixelRectangle to set
     */
    public void setPixelRectangle(Rectangle2D.Double pixelRectangle) {
        if (pixelRectangle != null) {
            this.pixelRectangle = (Rectangle2D.Double) pixelRectangle.clone();
            updateConstraints();
            revalidate();
        }
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    @Override
    public void mousePressed(MouseEvent e) {
        e = SwingUtilities.convertMouseEvent((Component) e.getSource(), e, this.getParent());
        dragStart = e.getPoint();
    }

    @Override
    public void mouseReleased(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

    @Override
    public final void mouseDragged(MouseEvent e) {
        MouseEvent e2 = SwingUtilities.convertMouseEvent((Component) e.getSource(), e, this.getParent());
        double dX = e2.getX() - dragStart.getX();
        double dY = e2.getY() - dragStart.getY();
        reposition(e, dX, dY);
        dragStart = e2.getPoint();
        componentMoved(null);
    }

    @Override
    public void mouseMoved(MouseEvent e) {
    }

    @Override
    public void componentResized(ComponentEvent e) {
        this.updateConstraints();
        revalidate();
        repaint();
    }

    @Override
    public void componentMoved(ComponentEvent e) {
        componentResized(e);
    }

    @Override
    public void componentShown(ComponentEvent e) {
        //componentResized(e);
    }

    @Override
    public void componentHidden(ComponentEvent e) {
    }

    /**
     * PropertyChangeListener to be installed in the parent graph. This provided
     * support for changing the transform/reversal of axes.
     *
     * @param evt PropertyChangeEvent
     */
    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if (evt.getSource() instanceof GJAbstractGraph) {
            if (evt.getPropertyName().equals("XTransform") || evt.getPropertyName().equals("YTransform")
                    || evt.getPropertyName().equals("axesBounds")) {
                Rectangle2D.Double old = pixelRectangle;
                if (ArrayMath.any(ArrayMath.isNaN(new double[]{pixelRectangle.x, pixelRectangle.y, pixelRectangle.width, pixelRectangle.height}))) {
                    // If the transformed roi contains NaNs, restore the previous value and set the roi
                    // visibility off. 
                    pixelRectangle = old;
                    setVisible(false);
                } else {
                    // Restore visibility if no NaNs.
                    setVisible(true);
                }
                updateConstraints();
                revalidate();
                repaint();
            }
        }
    }

    /**
     * Removes the icons and places a space character in the label's text field
     * (to maintain non-zero size).
     */
    public void removeIcons() {
        panelNE.setIcon(null);
        panelNW.setIcon(null);
        panelSW.setIcon(null);
        panelSE.setIcon(null);
        panelN.setIcon(null);
        panelS.setIcon(null);
        panelE.setIcon(null);
        panelW.setIcon(null);
        panelNE.setText(" ");
        panelNW.setText(" ");
        panelSW.setText(" ");
        panelSE.setText(" ");
        panelN.setText(" ");
        panelS.setText(" ");
        panelE.setText(" ");
        panelW.setText(" ");
    }
}

 

Ads help maintain this website.