Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Allow nested JSplitPanes to control parent JSplitPanes

Below is the code for a simple layout created using several nested JSplitPanes.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSplitPane;

public class CDBurner extends JFrame {
    private static final long serialVersionUID = -6027473114929970648L;
    JSplitPane main, folder, rest;
    JPanel centeral, folders, favourites, tasks;
    JLabel label;

    private CDBurner() {
        super("Dan's CD Burner");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new GridLayout(1, 1));
        getContentPane().setBackground(Color.black);

        createLayout();

        pack();
        setMinimumSize(getSize());
        setExtendedState(getExtendedState() | JFrame.MAXIMIZED_BOTH);
        setVisible(true);
        requestFocus();
    }

    private void createLayout() {
        createPanels();
        rest = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, centeral, tasks);
        rest.setResizeWeight(1);
        rest.setContinuousLayout(true);
        rest.setOneTouchExpandable(true);
        folder = new JSplitPane(JSplitPane.VERTICAL_SPLIT, favourites, folders);
        folder.setResizeWeight(0.35);
        folder.setContinuousLayout(true);
        folder.setOneTouchExpandable(true);
        main = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, folder, rest);
        main.setResizeWeight(0);
        main.setContinuousLayout(true);
        main.setOneTouchExpandable(true);
        getContentPane().add(main);
    }

    private void createPanels() {
        createFolders();
        createCenter();
        createTaskSpool();
        createFavourites();
    }

    private void createFolders() {
        folders = new JPanel(new GridLayout(1, 1));
        label = new JLabel("Folder");
        folders.setMinimumSize(new Dimension(300, 100));
        folders.add(label);
    }

    private void createCenter() {
        centeral = new JPanel(new GridLayout(1, 1));
        label = new JLabel("Central");
        centeral.add(label);
        centeral.setMinimumSize(new Dimension(300, 100));
    }

    private void createTaskSpool() {
        tasks = new JPanel(new GridLayout(1, 1));
        label = new JLabel("Task");
        tasks.setMinimumSize(new Dimension(300, 100));
        tasks.add(label);
    }

    private void createFavourites() {
        favourites = new JPanel(new GridLayout(1, 1));
        label = new JLabel("Fav");
        favourites.setMinimumSize(new Dimension(300, 100));
        favourites.add(label);
    }

    public static void main(String[] args) {
        new CDBurner();
    }
}

Due to the line rest.setResizeWeight(1); you can drag the main (JSplitPane) divider to the right and it will shrink the tasks JPanel to the until both the JPanels in rest are the minimum size. However, if you try to do the opposite nothing happens. See images below to see problem.

If the gui looks like this, you can drag the main divider.

enter image description here

And you will get the result of this.

enter image description here

However if it looks like this and you try to drag the rest divider nothing happens.

enter image description here

This is because both sides of the rest JSplitPane are already at their minimum size.

The Question
How can I make it so that when I drag the rest divider it effects the main JSplitPane so that both the operations shown in the images above are possible?

like image 876
Dan Avatar asked Nov 05 '16 16:11

Dan


2 Answers

As far as I know the behaviour you describe is normal behaviour for divided views. Have a look on how the dividers of professional applications like Eclipse behave.

Anyway I created a workaround for your desired behaviour by controlling the movements of your rest JSplitPane divider programmatically. You simply need to add these lines of code after creating your rest JSplitPane in your createLayout() method.

SplitPaneUI spui = rest.getUI();
if (spui instanceof BasicSplitPaneUI) {           
    ((BasicSplitPaneUI) spui).getDivider().addMouseMotionListener(new MouseAdapter(){         
        @Override
        public void mouseDragged(MouseEvent E){
            int newPos = E.getPoint().x;                    
            if(newPos < 0 && folder.getSize().width > 300 && centeral.getSize().width <= 300){
                main.setDividerLocation(main.getDividerLocation() + newPos);
                validate();                     
            }
            if(centeral.getSize().width > 300 && tasks.getSize().width > 300){
                rest.setDividerLocation(rest.getDividerLocation() + newPos);
                validate(); 
            }
        }
    });
}

explanation:
In the first lines we take the UI object of the divider of JSplitPane and add a custom MouseMotionListener to it. We need the UI object because the real JSplitPane doesn't get any mouse events directly.
Inside the listener we override the mouseDragged method and receive the actual X-position of the mouse while its dragging the divider.
If its a negative value we are dragging the divider to the left. In this case we also want the main divider to move if the center panel has already shrunk to its minium size of 300 and the left folder panel is still bigger than its minimum size.
The rest divider should be movable if central and the task panel on the right are still bigger than their minimum size of 300.

like image 129
ArcticLord Avatar answered Sep 27 '22 15:09

ArcticLord


  • Here is one possible implementation using JLayer
    • This example only considers the case of JSplitPane.HORIZONTAL_SPLIT
    • Note: this is just presented as an idea, would need a lot of work to make it usable
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.BasicSplitPaneDivider;

public class CDBurner2 extends JFrame {
  JSplitPane main, folder, rest;
  JPanel centeral, folders, favourites, tasks;
  JLabel label;

  private CDBurner2() {
    super("Dan's CD Burner");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    createLayout();
    setExtendedState(getExtendedState() | JFrame.MAXIMIZED_BOTH);
    setVisible(true);
  }

  private void createLayout() {
    createPanels();
    rest = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, centeral, tasks);
    rest.setResizeWeight(1);
    rest.setContinuousLayout(true);
    rest.setOneTouchExpandable(true);

    folder = new JSplitPane(JSplitPane.VERTICAL_SPLIT, favourites, folders);
    folder.setResizeWeight(0.35);
    folder.setContinuousLayout(true);
    folder.setOneTouchExpandable(true);

    main = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, folder, rest);
    main.setResizeWeight(0);
    main.setContinuousLayout(true);
    main.setOneTouchExpandable(true);

    //getContentPane().add(main);
    getContentPane().add(new JLayer<JSplitPane>(
        main, new SplitPaneDividerDragLayerUI()));
  }

  private void createPanels() {
    createFolders();
    createCenter();
    createTaskSpool();
    createFavourites();
  }

  private void createFolders() {
    folders = new JPanel(new GridLayout(1, 1));
    label = new JLabel("Folder");
    folders.setMinimumSize(new Dimension(300, 100));
    folders.add(label);
  }

  private void createCenter() {
    centeral = new JPanel(new GridLayout(1, 1));
    label = new JLabel("Central");
    centeral.add(label);
    centeral.setMinimumSize(new Dimension(300, 240));
    centeral.setPreferredSize(new Dimension(600, 240));
  }

  private void createTaskSpool() {
    tasks = new JPanel(new GridLayout(1, 1));
    label = new JLabel("Task");
    tasks.setMinimumSize(new Dimension(300, 240));
    tasks.setPreferredSize(new Dimension(600, 240));
    tasks.add(label);
  }

  private void createFavourites() {
    favourites = new JPanel(new GridLayout(1, 1));
    label = new JLabel("Fav");
    favourites.setMinimumSize(new Dimension(300, 100));
    favourites.add(label);
  }

  public static void main(String... args) {
    EventQueue.invokeLater(() -> new CDBurner2());
  }
}

class SplitPaneDividerDragLayerUI extends LayerUI<JSplitPane> {
  private int dividerLocation;
  private final Point startPt = new Point();
  @Override public void installUI(JComponent c) {
    super.installUI(c);
    if (c instanceof JLayer) {
      ((JLayer) c).setLayerEventMask(
          AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
    }
  }
  @Override public void uninstallUI(JComponent c) {
    if (c instanceof JLayer) {
      ((JLayer) c).setLayerEventMask(0);
    }
    super.uninstallUI(c);
  }
  @Override protected void processMouseEvent(
      MouseEvent e, JLayer<? extends JSplitPane> l) {
    super.processMouseEvent(e, l);
    JSplitPane splitPane = l.getView();
    Component c = e.getComponent();
    if (e.getID() == MouseEvent.MOUSE_PRESSED && c instanceof BasicSplitPaneDivider) {
      JSplitPane sp = (JSplitPane) SwingUtilities.getUnwrappedParent(c);
      if (!splitPane.equals(sp) && sp.getOrientation() == JSplitPane.HORIZONTAL_SPLIT) {
        startPt.setLocation(SwingUtilities.convertPoint(c, e.getPoint(), sp));
        dividerLocation = splitPane.getDividerLocation();
      }
    }
  }
  @Override protected void processMouseMotionEvent(
      MouseEvent e, JLayer<? extends JSplitPane> l) {
    super.processMouseEvent(e, l);
    JSplitPane splitPane = l.getView();
    Component c = e.getComponent();
    if (e.getID() == MouseEvent.MOUSE_DRAGGED && c instanceof BasicSplitPaneDivider) {
      JSplitPane sp = (JSplitPane) SwingUtilities.getUnwrappedParent(c);
      if (!splitPane.equals(sp) && sp.getOrientation() == JSplitPane.HORIZONTAL_SPLIT) {
        Point pt = SwingUtilities.convertPoint(c, e.getPoint(), sp);
        int delta = pt.x - startPt.x;
        startPt.setLocation(pt);
        if (delta < 0 && sp.getMinimumDividerLocation() == sp.getDividerLocation()) {
          //System.out.println("delta: " + delta);
          dividerLocation = Math.max(
              splitPane.getMinimumDividerLocation(), dividerLocation + delta);
          splitPane.setDividerLocation(dividerLocation);
          EventQueue.invokeLater(() -> {
            sp.setDividerLocation(sp.getMinimumDividerLocation());
          });
        }
      }
    }
  }
}
like image 28
aterai Avatar answered Sep 27 '22 16:09

aterai