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

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

There is a newer version: 1.6.8
Show newest version
/*
 * $Id$
 *
 * 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.JPanel;
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;
import org.jdesktop.swingx.painter.Painter;

/**
 *
 * 

* 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(). * * @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 - 2024 Weber Informatics LLC | Privacy Policy