alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

// $Id: Splitter.java,v 1.11 2004/09/21 19:03:28 mvw Exp $
// Copyright (c) 2003 The Regents of the University of California. All
// Rights Reserved. Permission to use, copy, modify, and distribute this
// software and its documentation without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph appear in all copies.  This software program and
// documentation are copyrighted by The Regents of the University of
// California. The software program and documentation are supplied "AS
// IS", without any accompanying services from The Regents. The Regents
// does not warrant that the operation of the program will be
// uninterrupted or error-free. The end-user understands that the program
// was developed for research purposes and is advised not to rely
// exclusively on the program for any reason.  IN NO EVENT SHALL THE
// UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
// SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
// UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

/*
 * Splitter.java
 */
package org.argouml.swingext;

import java.awt.*;
import java.awt.event.*;

import javax.swing.JComponent;
import javax.swing.JSplitPane;
import javax.swing.plaf.SplitPaneUI;
import javax.swing.plaf.basic.BasicSplitPaneDivider;
import javax.swing.plaf.basic.BasicSplitPaneUI;

/**
 * Acts as a seperator between components and will automatically
 * resize those components when the splitter is moved by dragging the
 * mouse across it.
 *
 * @author Bob Tarling
 *
 * TODO: Bring splitter to top when not dynamic resize
 * TODO: Add constructor and getter/setter for dynamic resize
 *
 * TODO: Implement the setLocation method, anything currently calling
 * setLocation should then call super.setLocation.
 */
public class Splitter extends JComponent {

    /**
     * The orientation for a horizontal splitter
     */
    private static final Orientation HORIZONTAL_SPLIT = 
        Horizontal.getInstance();
    
    /**
     * The orientation for a vertical splitter
     */
    private static final Orientation VERTICAL_SPLIT = 
        Vertical.getInstance();

    /** The side of the splitter of the component to be hidden on 
     * a quick hide action. */
    protected static final int NONE = -1;
    /** The side of the splitter to be hidden on a quick hide action: WEST */
    protected static final int WEST = 0;
    /** The side of the splitter to be hidden on a quick hide action: EAST */
    protected static final int EAST = 1;
    /** The side of the splitter to be hidden on a quick hide action: NORTH */
    protected static final int NORTH = 0;
    /** The side of the splitter to be hidden on a quick hide action: SOUTH */
    protected static final int SOUTH = 1;

    /**
     * The orientation of this splitter. Orientation does not
     * represent the shape of the splitter but rather the layout of
     * the objects being seperated by the splitter.  In other words a
     * horizontal splitter seperates components layed out in a
     * horizontal row.
     */
    private Orientation orientation;

    private int lastPosition;
    private int lastLength;
    
    /**
     * Is quick hide available and if so which component ahould it hide
     */
    private int quickHide = NONE;
    
    /**
     * True if a component has been hidden by using the quick hide
     * process of the Splitter
     */ 
    private boolean panelHidden = false;

    /**
     * True if components are resized dymically when the plitter is
     * dragged. If false then components are only resized when the
     * splitter is dropped by releasing the mouse.
     */ 
    private boolean dynamicResize = true;

    /**
     * The 2 components which the splitter is designed to seperate
     */
    private Component sideComponent[] = new Component[2];

    /**
     * The standard width of a splitter
     */
    private int splitterSize = 10;

    /**
     * The quick hide buttons
     */
    private ArrowButton buttonNorth = null;
    
    /**
     * The quick hide buttons
     */
    private ArrowButton buttonSouth = null;
    
    /**
     * Component which knows how to paint the split divider.
    **/
    private BasicSplitPaneDivider divider = null;
    
    /**
     * Padding around the JSplitPane that is not included in the divider
    **/
    private static final int DIVIDER_PADDING = 4;
    
    /**
     * Minimum size of the splitter in pixels. Must be at least this size
     * to properly display the toggle buttons.
    **/
    private static final int MIN_SPLITTER_SIZE = 5;

    /**
     * The constructor
     *
     * @param o A Horizontal or Vertical Orientation object to
     * indicate whether this splitter is designed to seperate
     * components laid out horizontally or vertically.
     */ 
    public Splitter(Orientation o) {
        super();
        
        this.orientation = o;

        // Create a JSplitPane for the purpose of extracting the
        // divider UI and determining the splitter size.
        JSplitPane splitpane;
        if (o == HORIZONTAL_SPLIT) {
            splitpane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true);
            splitterSize =
		Math.max(splitpane.getPreferredSize().width - DIVIDER_PADDING,
			 MIN_SPLITTER_SIZE);
        } else {
            splitpane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true);
            splitterSize =
		Math.max(splitpane.getPreferredSize().height - DIVIDER_PADDING,
			 MIN_SPLITTER_SIZE);
        }

        setLayout(new SerialLayout(o.getPerpendicular()));
        setSize(splitterSize, splitterSize);
        setPreferredSize(this.getSize());

        // Get the BasicSplitPaneDivider if the current look and feel
        // is based on the Basic look and feel. If not, the splitter
        // will still work, but the divider area will appear empty. 
        SplitPaneUI ui = splitpane.getUI();
        if (ui instanceof BasicSplitPaneUI)	{
            divider = ((BasicSplitPaneUI) ui).createDefaultDivider();
            divider.setSize(getSize());
        }

        setCursor(o.getCursor());
        
        MyMouseListener myMouseListener = new MyMouseListener();
        addMouseListener(myMouseListener);
        addMouseMotionListener(myMouseListener);       
    }

    /**
     * Register a component to be resized by this splitter.
     *
     * @param side the side of the splitter to place the component
     * being one of the constants NORTH, SOUTH, EAST or WEST
     *
     * @param comp the component to be resized
     */
    public void registerComponent(int side, Component comp)
    {
        this.sideComponent[side] = comp;
        setVisible(this.sideComponent[WEST] != null
		   && this.sideComponent[EAST] != null);
    }

    /**
     * Get a registered component.
     *
     * @param side the side of the splitter of the component to
     * return, being one of the constants NORTH, SOUTH, EAST or WEST
     * @return the registered component
     */
    public Component getRegisteredComponent(int side)
    {
        return sideComponent[side];
    }

    /**
     * Change the quick hide action. If quick hide is turned on then
     * an arrow button appears on the splitter to allow the user to
     * instantly reposition the splitter to hide one of the
     * components.
     *
     * @param side the side of the splitter of the component to be
     * hidden on a quick hide action. This being one of the constants
     * NORTH, SOUTH, EAST, WEST or NONE.
     */
    public void setQuickHide(int side) {
        quickHide = side;
        // Only create the arrow buttons the first time they are required.
        if (side != NONE && buttonNorth == null) {
            buttonNorth = orientation.getStartArrowButton();
            buttonSouth = orientation.getEndArrowButton();
            
            ActionListener al = new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    toggleHide();
                }
            };
            buttonNorth.addActionListener(al);
            buttonSouth.addActionListener(al);
            add(buttonNorth);
            add(buttonSouth);
        }
        showButtons();
    }

    /*
     * Show the correct button symbol. The arrow button should point
     * in the direction that the splitter will move on the button
     * press. This will be towards the component to hide or if already
     * hidden it should point away from the hidden component.
     */
    private void showButtons() {
        if (buttonNorth != null) {
            if (panelHidden) {
                buttonNorth.setVisible(quickHide == SOUTH);
                buttonSouth.setVisible(quickHide == NORTH);
            }
            else {
                buttonNorth.setVisible(quickHide == NORTH);
                buttonSouth.setVisible(quickHide == SOUTH);
            }
        }
    }

    /**
     * Hide or restore the component currently selected as the quick
     * hide component.
     */
    public void toggleHide()
    {
        if (quickHide == NONE) return;
        
        int position = 0;

        if (panelHidden) {
            position = lastPosition;
            if (quickHide == EAST) {
                position = orientation.getPosition(this) - lastLength;
            }
        }
        else if (quickHide == EAST) {
            position =
		orientation.getPosition(this)
		+ orientation.getLength(sideComponent[EAST]);
        }

        lastLength = orientation.getLength(sideComponent[quickHide]);
        lastPosition = orientation.getPosition(this);

        setLocation(orientation.setPosition(getLocation(), position));

        resizeComponents(position - lastPosition);

        panelHidden = !panelHidden;
        showButtons();
    }

    /**
     * Resizes the divider delegate when this component is resized.
     *
     * @see java.awt.Component#setSize(java.awt.Dimension)
     */
    public void setSize(Dimension d) {
        super.setSize(d);
        if (divider != null) {
            divider.setSize(d);
        }
    }

    /**
     * Resizes the divider delegate when this component is resized.
     *
     * @see java.awt.Component#setSize(int, int)
     */
    public void setSize(int width, int height) {
        super.setSize(width, height);
        if (divider != null) {
            divider.setSize(width, height);
        }
    }

    /**
     * Delegates painting to the UI component responsible for the split pane
     * divider.
     *
     * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
     */
    public void paintComponent(Graphics g) {
        if (divider != null) {
            divider.paint(g);
        }
    }	
	
    /*
     * Attempt to move the splitter by a given amount. It may not be
     * possible to move the splitter as far as requested because it
     * may result in one of the components having a negative size or
     * breaking it min/max size.
     *
     * @param movement the distance in pixels to move the splitter
     * from its current position.
     *
     * @return the actual number of pixels the splitter was moved.
     */
    private int moveSplitter(int movement) {
        if (sideComponent[WEST] != null && sideComponent[EAST] != null) {

            int restrictedMovement = 0;
            
            if (movement >= 0) {
                restrictedMovement =
		    restrictMovement(sideComponent[WEST],
				     sideComponent[EAST],
				     movement,
				     -1);
            }
            else {
                restrictedMovement =
		    restrictMovement(sideComponent[EAST],
				     sideComponent[WEST],
				     movement,
				     1);
            }

            setLocation(orientation.addToPosition(getLocation(),
						  restrictedMovement));

            return restrictedMovement;
        }
        return 0;
    }

    /**
     * Resize and reposition the components according to the movement
     * of the splitter
     *
     * @param movement the distance the splitter has moved.
     */
    private void resizeComponents(int movement) {
        sideComponent[NORTH]
	    .setSize(orientation.addLength(sideComponent[NORTH].getSize(),
					   movement));
        sideComponent[SOUTH]
	    .setSize(orientation.subtractLength(sideComponent[SOUTH].getSize(),
						movement));
        sideComponent[SOUTH].
	    setLocation(orientation
			.addToPosition(sideComponent[SOUTH].getLocation(),
				       movement));
        sideComponent[NORTH].validate();
        sideComponent[SOUTH].validate();
    }
    
    /**
     * calculates any restriction of movement based on the min/max values of the
     * registered components.
     *
     * @param growingComponent The component that is expanding as
     * the result of a splitter move.
     * @param shrinkingComponent The component that is shrinking
     * as the result of a splitter move.
     * @param movement           The number of pixels of the attempted move
     * @param sign The direction of the move -ve or +ve (-1 or +1)
     */
    private int restrictMovement(Component growingComponent,
				 Component shrinkingComponent,
				 int movement, int sign)
    {

        Dimension maxSize = growingComponent.getMaximumSize();
        int maxLength = orientation.getLength(maxSize);
        int currentLength = orientation.getLength(growingComponent);

        if (currentLength + movement * sign > maxLength) {
            movement = (currentLength - maxLength) * sign;
        }

        Dimension minSize = shrinkingComponent.getMinimumSize();
        int minLength = orientation.getLength(minSize);
        currentLength = orientation.getLength(shrinkingComponent);

        if (currentLength + movement * sign < minLength) {
            movement = (minLength - currentLength) * sign;
        }

        return movement;
    }

    /**
     * The mouse listener to detect mouse interaction with this splitter
     */
    private class MyMouseListener
	implements MouseMotionListener, MouseListener
    {
        /**
         * When the mouse is pressed the splitter position is recorded
         * so that the the difference in position can be calculated
         * when the mouse is released.
         */
        private int positionOfSplitterWhenPressed;
        
        /**
         * A value is recorded here when the mouse is pressed on the
         * splitter. This allows the position of the mouse on the
         * splitter to remain consistent when the splitter is moved.
         */
        private int mousePositionOnSplitterWhenPressed;

        /**
         * On a mouse release make sure that components are repositioned.
         */
        public void mouseReleased(MouseEvent me)
        {
            if (!dynamicResize) {
                moveSplitter(orientation.getPosition(me)
			     - mousePositionOnSplitterWhenPressed);
                resizeComponents(orientation.getPosition(getLocation())
				 - positionOfSplitterWhenPressed);
            }
            mousePositionOnSplitterWhenPressed = 0;
            positionOfSplitterWhenPressed = 0;
        }

        /**
         * On a mouse drag attempt to reposition splitter.
         */
        public void mouseDragged(MouseEvent me)
        {
            int mouseMovement =
		orientation.getPosition(me)
		- mousePositionOnSplitterWhenPressed;
            int restrictedMovement = moveSplitter(mouseMovement);
            if (restrictedMovement == 0) return;
            
            if (dynamicResize) {
                resizeComponents(restrictedMovement);
            }
            if (panelHidden) {
                panelHidden = false;
                showButtons();
            }
        }

        /**
         * On a mouse press record the position of the splitter and
         * the position of the mouse on the splitter.
         *
         */
        public void mousePressed(MouseEvent me)
        {
            mousePositionOnSplitterWhenPressed = orientation.getPosition(me);
            positionOfSplitterWhenPressed =
		orientation.getPosition(getLocation());
        }

        /**
         * On a double click either hide or show the component
         * selected for quick hide.
         *
         */
        public void mouseClicked(MouseEvent me)
        {
            if (me.getClickCount() == 2
		&& sideComponent[WEST] != null
		&& sideComponent[EAST] != null)
	    {
                toggleHide();
            }
        }

        /**
         * Empty method to satisy interface only, there is
         * no action when mouse enters splitter area
         */
        public void mouseEntered(MouseEvent me)
        {
        }

        /**
         * Empty method to satisy interface only, there is
         * no action when mouse leaves splitter area
         */
        public void mouseExited(MouseEvent me)
        {
        }

        /**
         * Empty method to satisy interface only, there is
         * no action when mouse moves through splitter area
         */
        public void mouseMoved(MouseEvent me)
        {
        }
    }
}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.