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

org.jdesktop.swingx.JXMultiSplitPane Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2004 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
 */
package org.jdesktop.swingx;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.io.Serializable;

import javax.accessibility.AccessibleContext;
import javax.accessibility.AccessibleRole;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.Painter;
import javax.swing.event.MouseInputAdapter;

import org.jdesktop.beans.JavaBean;
import org.jdesktop.swingx.MultiSplitLayout.Divider;
import org.jdesktop.swingx.MultiSplitLayout.Node;
import org.jdesktop.swingx.painter.AbstractPainter;

/**
 * All properties in this class are bound: when a properties value
 * is changed, all PropertyChangeListeners are fired.
 * 
 * @author Hans Muller
 * @author Luan O'Carroll
 */
@JavaBean
public class JXMultiSplitPane extends JPanel implements BackgroundPaintable {

    private AccessibleContext accessibleContext = null;
    private boolean continuousLayout = true;
    private DividerPainter dividerPainter = new DefaultDividerPainter();
    private Painter backgroundPainter;
    private boolean paintBorderInsets;

    /**
     * Creates a MultiSplitPane with it's LayoutManager set to 
     * to an empty MultiSplitLayout.
     */
    public JXMultiSplitPane() {
        this(new MultiSplitLayout());
    }

    /**
     * Creates a MultiSplitPane.
     * @param layout the new split pane's layout
     */
    public JXMultiSplitPane( MultiSplitLayout layout ) {
        super(layout);
        InputHandler inputHandler = new InputHandler();
        addMouseListener(inputHandler);
        addMouseMotionListener(inputHandler);
        addKeyListener(inputHandler);
        setFocusable(true);
    }
    
    /** 
     * A convenience method that returns the layout manager cast 
     * to MutliSplitLayout.
     * 
     * @return this MultiSplitPane's layout manager
     * @see java.awt.Container#getLayout
     * @see #setModel
     */
    public final MultiSplitLayout getMultiSplitLayout() {
        return (MultiSplitLayout)getLayout();
    }

    /** 
     * A convenience method that sets the MultiSplitLayout model.
     * Equivalent to getMultiSplitLayout.setModel(model)
     * 
     * @param model the root of the MultiSplitLayout model
     * @see #getMultiSplitLayout
     * @see MultiSplitLayout#setModel
     */
    public final void setModel(Node model) {
        getMultiSplitLayout().setModel(model);
    }

    /** 
     * A convenience method that sets the MultiSplitLayout dividerSize
     * property. Equivalent to 
     * getMultiSplitLayout().setDividerSize(newDividerSize).
     * 
     * @param dividerSize the value of the dividerSize property
     * @see #getMultiSplitLayout
     * @see MultiSplitLayout#setDividerSize
     */
    public final void setDividerSize(int dividerSize) {
        getMultiSplitLayout().setDividerSize(dividerSize);
    }

    /** 
     * A convenience method that returns the MultiSplitLayout dividerSize
     * property. Equivalent to 
     * getMultiSplitLayout().getDividerSize().
     * @return dividerSize
     * 
     * @see #getMultiSplitLayout
     * @see MultiSplitLayout#getDividerSize
     */
    public final int getDividerSize() {
        return getMultiSplitLayout().getDividerSize();
    }

    /**
     * Sets the value of the continuousLayout property.
     * If true, then the layout is revalidated continuously while
     * a divider is being moved.  The default value of this property
     * is true.
     *
     * @param continuousLayout value of the continuousLayout property
     * @see #isContinuousLayout
     */
    public void setContinuousLayout(boolean continuousLayout) {
        boolean oldContinuousLayout = isContinuousLayout();
        this.continuousLayout = continuousLayout;
        firePropertyChange("continuousLayout", oldContinuousLayout, isContinuousLayout());
    }

    /**
     * Returns true if dragging a divider only updates
     * the layout when the drag gesture ends (typically, when the 
     * mouse button is released).
     *
     * @return the value of the continuousLayout property
     * @see #setContinuousLayout
     */
    public boolean isContinuousLayout() {
        return continuousLayout;
    }

    /** 
     * Returns the Divider that's currently being moved, typically
     * because the user is dragging it, or null.
     * 
     * @return the Divider that's being moved or null.
     */
    public Divider activeDivider() {
        return dragDivider;
    }

    /**
     * Draws a single Divider.  Typically used to specialize the
     * way the active Divider is painted.  
     * 
     * @see #getDividerPainter
     * @see #setDividerPainter
     */
    public static abstract class DividerPainter extends AbstractPainter {}

    private class DefaultDividerPainter extends DividerPainter implements Serializable {
        @Override
		protected void doPaint(Graphics2D g, Divider divider, int width, int height) {
			if ((divider == activeDivider()) && !isContinuousLayout()) {
				g.setColor(Color.black);
				g.fillRect(0, 0, width, height);
			}
		}
    }

    /** 
     * The DividerPainter that's used to paint Dividers on this MultiSplitPane.
     * This property may be null.
     * 
     * @return the value of the dividerPainter Property
     * @see #setDividerPainter
     */
    public DividerPainter getDividerPainter() {
    return dividerPainter;
    }

    /** 
     * Sets the DividerPainter that's used to paint Dividers on this 
     * MultiSplitPane.  The default DividerPainter only draws
     * the activeDivider (if there is one) and then, only if 
     * continuousLayout is false.  The value of this property is 
     * used by the paintChildren method: Dividers are painted after
     * the MultiSplitPane's children have been rendered so that 
     * the activeDivider can appear "on top of" the children.
     * 
     * @param dividerPainter the value of the dividerPainter property, can be null
     * @see #paintChildren
     * @see #activeDivider
     */
    public void setDividerPainter(DividerPainter dividerPainter) {
        DividerPainter old = getDividerPainter();
        this.dividerPainter = dividerPainter;
        firePropertyChange("dividerPainter", old, getDividerPainter());
    }

    /**
     * Calls the UI delegate's paint method, if the UI delegate
     * is non-null.  We pass the delegate a copy of the
     * Graphics object to protect the rest of the
     * paint code from irrevocable changes
     * (for example, Graphics.translate).
     * 

* If you override this in a subclass you should not make permanent * changes to the passed in Graphics. For example, you * should not alter the clip Rectangle or modify the * transform. If you need to do these operations you may find it * easier to create a new Graphics from the passed in * Graphics and manipulate it. Further, if you do not * invoker super's implementation you must honor the opaque property, * that is * if this component is opaque, you must completely fill in the background * in a non-opaque color. If you do not honor the opaque property you * will likely see visual artifacts. *

* The passed in Graphics object might * have a transform other than the identify transform * installed on it. In this case, you might get * unexpected results if you cumulatively apply * another transform. * * @param g the Graphics object to protect * @see #paint(Graphics) * @see javax.swing.plaf.ComponentUI */ @Override protected void paintComponent(Graphics g) { if (backgroundPainter == null) { super.paintComponent(g); } else { if (isOpaque()) { super.paintComponent(g); } Graphics2D g2 = (Graphics2D) g.create(); try { SwingXUtilities.paintBackground(this, g2); } finally { g2.dispose(); } getUI().paint(g, this); } } /** * Specifies a Painter to use to paint the background of this JXPanel. * If p is not null, then setOpaque(false) will be called * as a side effect. A component should not be opaque if painters are * being used, because Painters may paint transparent pixels or not * paint certain pixels, such as around the border insets. */ @Override public void setBackgroundPainter(Painter p) { Painter old = getBackgroundPainter(); this.backgroundPainter = p; if (p != null) { setOpaque(false); } firePropertyChange("backgroundPainter", old, getBackgroundPainter()); repaint(); } @Override public Painter getBackgroundPainter() { return backgroundPainter; } /** * {@inheritDoc} */ @Override public boolean isPaintBorderInsets() { return paintBorderInsets; } /** * {@inheritDoc} */ @Override public void setPaintBorderInsets(boolean paintBorderInsets) { boolean oldValue = isPaintBorderInsets(); this.paintBorderInsets = paintBorderInsets; firePropertyChange("paintBorderInsets", oldValue, isPaintBorderInsets()); } /** * Uses the DividerPainter (if any) to paint each Divider that * overlaps the clip Rectangle. This is done after the call to * super.paintChildren() so that Dividers can be * rendered "on top of" the children. *

* {@inheritDoc} */ @Override protected void paintChildren(Graphics g) { super.paintChildren(g); DividerPainter dp = getDividerPainter(); Rectangle clipR = g.getClipBounds(); if ((dp != null) && (clipR != null)) { MultiSplitLayout msl = getMultiSplitLayout(); if ( msl.hasModel()) { for(Divider divider : msl.dividersThatOverlap(clipR)) { Rectangle bounds = divider.getBounds(); Graphics cg = g.create( bounds.x, bounds.y, bounds.width, bounds.height ); try { dp.paint((Graphics2D)cg, divider, bounds.width, bounds.height ); } finally { cg.dispose(); } } } } } private boolean dragUnderway = false; private MultiSplitLayout.Divider dragDivider = null; private Rectangle initialDividerBounds = null; private boolean oldFloatingDividers = true; private int dragOffsetX = 0; private int dragOffsetY = 0; private int dragMin = -1; private int dragMax = -1; private void startDrag(int mx, int my) { requestFocusInWindow(); MultiSplitLayout msl = getMultiSplitLayout(); MultiSplitLayout.Divider divider = msl.dividerAt(mx, my); if (divider != null) { MultiSplitLayout.Node prevNode = divider.previousSibling(); MultiSplitLayout.Node nextNode = divider.nextSibling(); if ((prevNode == null) || (nextNode == null)) { dragUnderway = false; } else { initialDividerBounds = divider.getBounds(); dragOffsetX = mx - initialDividerBounds.x; dragOffsetY = my - initialDividerBounds.y; dragDivider = divider; Rectangle prevNodeBounds = prevNode.getBounds(); Rectangle nextNodeBounds = nextNode.getBounds(); if (dragDivider.isVertical()) { dragMin = prevNodeBounds.x; dragMax = nextNodeBounds.x + nextNodeBounds.width; dragMax -= dragDivider.getBounds().width; if ( msl.getLayoutMode() == MultiSplitLayout.USER_MIN_SIZE_LAYOUT ) dragMax -= msl.getUserMinSize(); } else { dragMin = prevNodeBounds.y; dragMax = nextNodeBounds.y + nextNodeBounds.height; dragMax -= dragDivider.getBounds().height; if ( msl.getLayoutMode() == MultiSplitLayout.USER_MIN_SIZE_LAYOUT ) dragMax -= msl.getUserMinSize(); } if ( msl.getLayoutMode() == MultiSplitLayout.USER_MIN_SIZE_LAYOUT ) { dragMin = dragMin + msl.getUserMinSize(); } else { if (dragDivider.isVertical()) { dragMin = Math.max( dragMin, dragMin + getMinNodeSize(msl,prevNode).width ); dragMax = Math.min( dragMax, dragMax - getMinNodeSize(msl,nextNode).width ); Dimension maxDim = getMaxNodeSize(msl,prevNode); if ( maxDim != null ) dragMax = Math.min( dragMax, prevNodeBounds.x + maxDim.width ); } else { dragMin = Math.max( dragMin, dragMin + getMinNodeSize(msl,prevNode).height ); dragMax = Math.min( dragMax, dragMax - getMinNodeSize(msl,nextNode).height ); Dimension maxDim = getMaxNodeSize(msl,prevNode); if ( maxDim != null ) dragMax = Math.min( dragMax, prevNodeBounds.y + maxDim.height ); } } oldFloatingDividers = getMultiSplitLayout().getFloatingDividers(); getMultiSplitLayout().setFloatingDividers(false); dragUnderway = true; } } else { dragUnderway = false; } } /** * Set the maximum node size. This method can be overridden to limit the * size of a node during a drag operation on a divider. When implementing * this method in a subclass the node instance should be checked, for * example: * * class MyMultiSplitPane extends JXMultiSplitPane * { * protected Dimension getMaxNodeSize( MultiSplitLayout msl, Node n ) * { * if (( n instanceof Leaf ) && ((Leaf)n).getName().equals( "top" )) * return msl.maximumNodeSize( n ); * return null; * } * } * * @param msl the MultiSplitLayout used by this pane * @param n the node being resized * @return the maximum size or null (by default) to ignore the maximum size. */ protected Dimension getMaxNodeSize( MultiSplitLayout msl, Node n ) { return null; } /** * Set the minimum node size. This method can be overridden to limit the * size of a node during a drag operation on a divider. * @param msl the MultiSplitLayout used by this pane * @param n the node being resized * @return the maximum size or null (by default) to ignore the maximum size. */ protected Dimension getMinNodeSize( MultiSplitLayout msl, Node n ) { return msl.minimumNodeSize(n); } private void repaintDragLimits() { Rectangle damageR = dragDivider.getBounds(); if (dragDivider.isVertical()) { damageR.x = dragMin; damageR.width = dragMax - dragMin; } else { damageR.y = dragMin; damageR.height = dragMax - dragMin; } repaint(damageR); } private void updateDrag(int mx, int my) { if (!dragUnderway) { return; } Rectangle oldBounds = dragDivider.getBounds(); Rectangle bounds = new Rectangle(oldBounds); if (dragDivider.isVertical()) { bounds.x = mx - dragOffsetX; bounds.x = Math.max(bounds.x, dragMin ); bounds.x = Math.min(bounds.x, dragMax); } else { bounds.y = my - dragOffsetY; bounds.y = Math.max(bounds.y, dragMin ); bounds.y = Math.min(bounds.y, dragMax); } dragDivider.setBounds(bounds); if (isContinuousLayout()) { revalidate(); repaintDragLimits(); } else { repaint(oldBounds.union(bounds)); } } private void clearDragState() { dragDivider = null; initialDividerBounds = null; oldFloatingDividers = true; dragOffsetX = dragOffsetY = 0; dragMin = dragMax = -1; dragUnderway = false; } private void finishDrag(int x, int y) { if (dragUnderway) { clearDragState(); if (!isContinuousLayout()) { revalidate(); repaint(); } } setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } private void cancelDrag() { if (dragUnderway) { dragDivider.setBounds(initialDividerBounds); getMultiSplitLayout().setFloatingDividers(oldFloatingDividers); setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); repaint(); revalidate(); clearDragState(); } } private void updateCursor(int x, int y, boolean show) { if (dragUnderway) { return; } int cursorID = Cursor.DEFAULT_CURSOR; if (show) { MultiSplitLayout.Divider divider = getMultiSplitLayout().dividerAt(x, y); if (divider != null) { cursorID = (divider.isVertical()) ? Cursor.E_RESIZE_CURSOR : Cursor.N_RESIZE_CURSOR; } } setCursor(Cursor.getPredefinedCursor(cursorID)); } private class InputHandler extends MouseInputAdapter implements KeyListener { @Override public void mouseEntered(MouseEvent e) { updateCursor(e.getX(), e.getY(), true); } @Override public void mouseMoved(MouseEvent e) { updateCursor(e.getX(), e.getY(), true); } @Override public void mouseExited(MouseEvent e) { updateCursor(e.getX(), e.getY(), false); } @Override public void mousePressed(MouseEvent e) { startDrag(e.getX(), e.getY()); } @Override public void mouseReleased(MouseEvent e) { finishDrag(e.getX(), e.getY()); } @Override public void mouseDragged(MouseEvent e) { updateDrag(e.getX(), e.getY()); } @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { cancelDrag(); } } @Override public void keyReleased(KeyEvent e) { } @Override public void keyTyped(KeyEvent e) { } } @Override public AccessibleContext getAccessibleContext() { if( accessibleContext == null ) { accessibleContext = new AccessibleMultiSplitPane(); } return accessibleContext; } protected class AccessibleMultiSplitPane extends AccessibleJPanel { @Override public AccessibleRole getAccessibleRole() { return AccessibleRole.SPLIT_PANE; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy