Skip to main content

Extending JSplitPane to create layers

1 reply [Last post]
acechase
Offline
Joined: 2003-12-02
Points: 0

Hi,

I've put together a component that allows for an arbitrary number of "layers" to be added to it which can each be adjusted like a JSplitPane. To do this, I just keep adding a new JSplitPane to the top component of the last JSplitPane added. Everything works great except for the allowable drag boundaries for the divider.

I want the divider to be able to be dragged up and down for each splitpane layer the same as they can be dragged for a single JSplitPane. This works fine dragging upward, because the bottom component at any point sees all the space above it as the top half of itself. But dragging down does not work. This is because to drag down, the JSplitPane itself only extends to the top of the component below it, so I can't drag past that point. I've posted an example of the code at the end of this post, so you can check it out to make more sense of it.

So, I'm looking for a way to override the drag code for dragging the divider. I need to be able to drag it past the bottom boundary. Any suggestions?

Thanks,
Andrew

</p>
<p>import java.awt.BorderLayout;<br />
import java.awt.Component;<br />
import java.awt.Container;<br />
import java.util.ArrayList;<br />
import java.util.Iterator;<br />
import java.util.List;</p>
<p>import javax.swing.JButton;<br />
import javax.swing.JFrame;<br />
import javax.swing.JPanel;<br />
import javax.swing.JScrollPane;<br />
import javax.swing.JSplitPane;<br />
import javax.swing.border.EmptyBorder;</p>
<p>/**<br />
 * This component is designed to work similar to a JSplitPane but for<br />
 * an arbitrary number of splits, rather than just two. Internally, the component<br />
 * uses nested JSplitPanes to add multiple layers.<br />
 * @author achase<br />
 *<br />
 *<br />
 */<br />
public class LayeredPane extends JPanel {</p>
<p>    private List splitList = new ArrayList();<br />
    private int orientation = JSplitPane.VERTICAL_SPLIT;<br />
    private JSplitPane lastSplit = null;<br />
    private Component lastComponent = null;<br />
    private boolean isEmpty = true;</p>
<p>    /**<br />
     * Make a new LayeredPane with the specified orientation, which<br />
     * needs to be either JSplitPane.VERTICAL_SPLIT or<br />
     * JSplitPane.HORIZONTAL_SPLIT<br />
     *<br />
     * @param orientation the orientation of the layers<br />
     */<br />
    public LayeredPane(int orientation) {<br />
        super(new BorderLayout());<br />
        if (orientation != JSplitPane.VERTICAL_SPLIT<br />
            && orientation != JSplitPane.HORIZONTAL_SPLIT) {<br />
            throw new IllegalArgumentException(<br />
                "Invalid parameter to LayeredPane, must be"<br />
                    + "either JSplitPane.VERTICAL_SPLIT or JSplitPane.HORIZONTAL_SPLIT");<br />
        }<br />
        this.orientation = orientation;<br />
    }</p>
<p>    public void removeLayer(Component component) {<br />
        Component toRemove = null;<br />
        Component toKeep = null;<br />
        for (Iterator iter = splitList.iterator(); iter.hasNext();) {<br />
            JSplitPane sp = (JSplitPane) iter.next();<br />
            sp.setBorder(new EmptyBorder(0, 0, 0, 0));<br />
            Component tc = sp.getTopComponent();<br />
            Component bc = sp.getBottomComponent();<br />
            if (component.equals(tc)) {<br />
                toRemove = tc;<br />
                toKeep = bc;<br />
                break;<br />
            } else if (component.equals(bc)) {<br />
                toRemove = bc;<br />
                toKeep = tc;<br />
                break;<br />
            }<br />
        }<br />
        if (toRemove != null && toKeep != null) {<br />
            Container parent = toRemove.getParent().getParent();<br />
            parent.remove(toRemove.getParent());<br />
            parent.add(toKeep);<br />
        }<br />
    }</p>
<p>    /**<br />
     * Add a layer to the LayeredPane that contains the component specified.<br />
     * The new layer will be added as the topmost, or leftmost component in the LayeredPane<br />
     * @param component the object to put in the new layer.<br />
     */<br />
    public void addLayer(Component component) {<br />
        if (isEmpty) {<br />
            add(component);<br />
            lastComponent = component;<br />
            isEmpty = false;<br />
            return;<br />
        } else if (lastSplit == null) {<br />
            JSplitPane sp = new JSplitPane(orientation);<br />
            sp.setBorder(new EmptyBorder(0, 0, 0, 0));<br />
            sp.setBottomComponent(lastComponent);<br />
            sp.setTopComponent(component);<br />
            lastSplit = sp;<br />
            splitList.add(sp);<br />
            lastComponent = component;<br />
            this.add(sp, BorderLayout.CENTER);<br />
            return;<br />
        } else {<br />
            JSplitPane splitPane = new JSplitPane(orientation);<br />
            splitPane.setBorder(new EmptyBorder(0, 0, 0, 0));<br />
            lastSplit.setTopComponent(splitPane);<br />
            splitPane.setBottomComponent(lastComponent);<br />
            splitPane.setTopComponent(component);<br />
            splitList.add(splitPane);<br />
            lastSplit = splitPane;<br />
            lastComponent = component;<br />
        }<br />
    }</p>
<p>	static int counter = 0;<br />
	public static JPanel getPanel(){<br />
		final JPanel panel = new JPanel();<br />
		final JButton button = new JButton("I am number " + counter++);<br />
		panel.add(button);<br />
		return panel;<br />
	}</p>
<p>    public static void main(String[] args) {<br />
        JFrame frame = new JFrame("Test Frame");<br />
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);<br />
        LayeredPane lp = new LayeredPane(JSplitPane.VERTICAL_SPLIT);<br />
        JScrollPane scroll = new JScrollPane(lp);<br />
        frame.getContentPane().add(scroll);<br />
        JPanel[] panels = new JPanel[6];<br />
        for (int i = 0; i < panels.length; i++) {<br />
            panels[i] = getPanel();<br />
            lp.addLayer(panels[i]);<br />
        }<br />
        frame.setBounds(0, 0, 600, 600);<br />
        frame.setVisible(true);<br />
    }<br />
}<br />

Message was edited by: acechase

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
acechase
Offline
Joined: 2003-12-02
Points: 0

Hi,
For those of you who follow comp.lang.java.gui, sorry for the cross-post.

I've updated the example code to be a little more complete. Now the buttons on the top of the JFrame will add and remove layers, it turns out the add/removeLayers methods I had written were buggy for the edge cases. Here's the code, cut-and-paste ready, just compile and run to see the new component. Any suggestions on how to resize the layers would be very appreciated.

Thanks,

Andrew

[code]
import java.awt.BorderLayout;

import java.awt.Component;

import java.awt.Container;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import javax.swing.JButton;

import javax.swing.JFrame;

import javax.swing.JPanel;

import javax.swing.JScrollPane;

import javax.swing.JSplitPane;

/**

* This component is designed to work similar to a JSplitPane but for

* an arbitrary number of splits, rather than just two. Internally, the component

* uses nested JSplitPanes to add multiple layers.

* @author achase

*

*

*/

public class LayeredPane extends JPanel {

private int orientation = JSplitPane.VERTICAL_SPLIT;

//the top-level splitpane.

private JSplitPane mainSplit = null;

//keep track of components that get added to the layeredPane without a splitpane

private Component nakedComponent = null;

private boolean isEmpty = true;

/**

* Make a new LayeredPane with the specified orientation, which

* needs to be either JSplitPane.VERTICAL_SPLIT or

* JSplitPane.HORIZONTAL_SPLIT

*

* @param orientation the orientation of the layers

*/

public LayeredPane(int orientation) {

super(new BorderLayout());

if (orientation != JSplitPane.VERTICAL_SPLIT

&& orientation != JSplitPane.HORIZONTAL_SPLIT) {

throw new IllegalArgumentException(

"Invalid parameter to LayeredPane, must be"

+ "either JSplitPane.VERTICAL_SPLIT or JSplitPane.HORIZONTAL_SPLIT");

}

this.orientation = orientation;

}

public void doLayout() {

updateLayout();

super.doLayout();

}

/**

* Add a layer to the LayeredPane that contains the component specified.

* The new layer will be added as the topmost, or leftmost component in the LayeredPane

* @param component the object to put in the new layer.

*/

public void addLayer(Component component) {

//if the whole thing is empty, just add the component straight to the panel

if (isEmpty) {

add(component);

nakedComponent = component;

isEmpty = false;

}

//If the lastSplit was null, then no split has been added to the panel yet,

//create a new splitpane and add top and bottom components to it

else if (mainSplit == null) {

mainSplit = new JSplitPane(orientation);

mainSplit.setBottomComponent(nakedComponent);

mainSplit.setTopComponent(component);

this.add(mainSplit, BorderLayout.CENTER);

}

//If a splitpane already exists, then create a new splitpane, add it to the

//top component of the last splitpane, and move the existing top component from

//the last splitpane into the bottom of the new splitpane.

else {

Component topComponent = mainSplit;

JSplitPane lastSplit = mainSplit;

while (topComponent instanceof JSplitPane) {

lastSplit = (JSplitPane) topComponent;

topComponent = ((JSplitPane) topComponent).getTopComponent();

}

if (topComponent == null) {

lastSplit.setTopComponent(component);

} else {

//now topComponent should be the deepest nested component.

JSplitPane newSplit = new JSplitPane(orientation);

JSplitPane nestedSplit = (JSplitPane) topComponent.getParent();

//the topComponent will be removed from its old splitpane and added

//as the bottom of the new one

newSplit.setBottomComponent(topComponent);

//now add the new component to the top of the new splitpane

newSplit.setTopComponent(component);

//and finally, add the new splitPane to the space the topComponent

//previously occupied

nestedSplit.setTopComponent(newSplit);

}

}

updateLayout();

repaint();

}

public synchronized void removeLayer(Component component) {

Container parent = component.getParent();

if (parent instanceof JSplitPane) {

//first get both top and bottom component and find out which

//one is being removed

Component top = ((JSplitPane) parent).getTopComponent();

Component bottom = ((JSplitPane) parent).getBottomComponent();

Component sibling = null;

if (top.equals(component)) {

sibling = bottom;

} else if (bottom.equals(component)) {

sibling = top;

} else {

System.err.println(

"neither top or bottom component matched"

+ "the component to be removed");

}

//now remove the splitpane and add the sibling back onto the layeredPane

parent.getParent().remove(parent);

//if the parent removed was the mainsplit, then set mainsplit to null

if (parent.equals(mainSplit)) {

if (sibling instanceof JSplitPane) {

mainSplit = (JSplitPane) sibling;

add(mainSplit);

} else {

mainSplit = null;

isEmpty = true;

if(sibling != null){

addLayer(sibling);

}

}

}

else if (sibling != null) {

addLayer(sibling);

}

} else {

parent.remove(component);

//there no longer is a naked component, it has been removed.

nakedComponent = null;

isEmpty = true;

}

repaint();

updateLayout();

}

int test = 0;

public void updateLayout() {

int height = this.getHeight();

int layerHeight =

(int) ((double) height / (double) (getNumberOfSplits() + 1));

int location = height - layerHeight;

Component lastSplit = mainSplit;

while(lastSplit instanceof JSplitPane){

((JSplitPane)lastSplit).setDividerSize(3);

((JSplitPane)lastSplit).setDividerLocation(location);

location -= layerHeight;

lastSplit = ((JSplitPane)lastSplit).getTopComponent();

}

revalidate();

System.out.println("layed out " + test++);

}

/**

* Get the number of JSplitPanes nested within this component

* @return the number of JSplitPanes nested in this component

*/

public int getNumberOfSplits(){

if(mainSplit == null){

return 0;

}

//start at 0 which will skip counting the mainSplit, that way, when the while

//loop kicks out because lastSplit is not a JSplitPane the counter does not

//have to be decremented

int splitCount = 0;

Component lastSplit = mainSplit;

while(lastSplit instanceof JSplitPane){

lastSplit = ((JSplitPane)lastSplit).getTopComponent();

splitCount++;

}

return splitCount;

}

static int counter = 0;

public JPanel getPanel() {

final JButton button = new JButton("hello " + counter++);

final JPanel panel = new JPanel() {

public String toString() {

return button.getText();

}

};

panel.add(button);

button.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent ae) {

JSplitPane sp = (JSplitPane) panel.getParent();

System.out.println("resize wght: " + sp.getResizeWeight());

System.out.println(

"divider max: "

+ sp.getMaximumDividerLocation()

+ "min: "

+ sp.getMinimumDividerLocation());

System.out.println("panel height: " + panel.getHeight());

updateLayout();

}

});

updateLayout();

return panel;

}

private JPanel getTestPanel() {

final JPanel panel = new JPanel();

final JPanel[] panels = new JPanel[6];

for (int i = 0; i < panels.length; i++) {

panels[i] = getPanel();

JButton button = new JButton("add/remove " + i);

final int index = i;

button.addActionListener(new ActionListener() {

boolean added = false;

public void actionPerformed(ActionEvent evt) {

if (!added) {

LayeredPane.this.addLayer(panels[index]);

} else {

LayeredPane.this.removeLayer(panels[index]);

}

added = !added;

}

});

panel.add(button);

}

return panel;

}

public static void main(String[] args) {

JFrame frame = new JFrame("Test Frame");

frame.getContentPane().setLayout(new BorderLayout());

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

LayeredPane lp = new LayeredPane(JSplitPane.VERTICAL_SPLIT);

JScrollPane scroll = new JScrollPane(lp);

frame.getContentPane().add(scroll);

frame.getContentPane().add(lp.getTestPanel(), BorderLayout.NORTH);

frame.setBounds(0, 0, 600, 600);

frame.setVisible(true);

}

}
[/code]