package org.openflexo.swing;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Map.Entry;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import org.openflexo.model.ModelContext;
import org.openflexo.model.exceptions.ModelDefinitionException;
import org.openflexo.model.factory.ModelFactory;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
public class MultiSplitPane extends JPanel {
private static final int INSETS_SIZE = 4;
private class MouseDrag extends MouseAdapter {
private boolean dragging = false;
private Divider divider;
private int available;
private Node previous;
private Node next;
private Point last;
@Override
public void mousePressed(MouseEvent e) {
last = new Point(e.getX(), e.getY());
last = SwingUtilities.convertPoint((Component) e.getSource(), last, MultiSplitPane.this);
divider = getDividerAt(last);
if (divider != null) {
previous = getLayout().getNodeForComponent(divider.previous);
next = getLayout().getNodeForComponent(divider.next);
}
dragging = divider != null && previous != null && next != null;
if (dragging) {
setSize(previous, divider.previous);
setSize(next, divider.next);
if (getModel().isRowLayout()) {
available = getWidth() - getLayout().getRequiredSize() /*- getComponentCount() * INSETS_SIZE*/;
} else {
available = getHeight() - getLayout().getRequiredSize() /*- getComponentCount() * INSETS_SIZE*/;
}
}
}
private void setSize(Node node, Component comp) {
if (node.getPercentage() < 0) {
if (getModel().isRowLayout()) {
node.setSize(comp.getWidth());
} else {
node.setSize(comp.getHeight());
}
}
}
@Override
public void mouseReleased(MouseEvent e) {
performDrag(e);
dragging = false;
divider = null;
previous = null;
next = null;
}
@Override
public void mouseDragged(MouseEvent e) {
performDrag(e);
}
private void performDrag(MouseEvent e) {
if (dragging) {
Point p = new Point(e.getX(), e.getY());
int offset;
if (divider.isVertical()) {
offset = p.x - last.x;
} else {
offset = p.y - last.y;
}
updateNodeForComponent(offset, previous, divider.previous);
updateNodeForComponent(-offset, next, divider.next);
}
}
private void updateNodeForComponent(int offset, Node node, Component comp) {
if (node.getPercentage() < 0) {
node.setSize(node.getSize() + offset);
} else {
int size;
if (getModel().isRowLayout()) {
size = comp.getWidth();
} else {
size = comp.getHeight();
}
if (available > 0) {
System.err.println(available + " " + size + " " + offset);
double percentage = (double) (size + offset) / available;
System.err.println(node.getName() + " " + node.getPercentage() + " " + percentage);
node.setPercentage(Math.max(percentage, 0));
}
}
getLayout().setConstraintForComponent(comp, node);
revalidate();
}
@Override
public void mouseEntered(MouseEvent e) {
updateCursor(e);
}
@Override
public void mouseExited(MouseEvent e) {
updateCursor(e);
}
@Override
public void mouseMoved(MouseEvent e) {
updateCursor(e);
}
private void updateCursor(MouseEvent e) {
Point point = new Point(e.getX(), e.getY());
point = SwingUtilities.convertPoint((Component) e.getSource(), point, MultiSplitPane.this);
Divider d = getDividerAt(point);
if (d != null) {
if (getModel().isRowLayout()) {
setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
} else {
setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
}
} else {
setCursor(Cursor.getDefaultCursor());
}
}
}
@Override
public void doLayout() {
super.doLayout();
for (Component c : getComponents()) {
System.err.println(c.getSize());
}
}
private class Divider {
public final Component previous;
public final Component next;
public Divider(Component previous, Component next) {
super();
this.previous = previous;
this.next = next;
}
public boolean isVertical() {
return getModel().isRowLayout();
}
public boolean isHorizontal() {
return !isVertical();
}
}
private final MultiSplitLayout layout;
public MultiSplitPane(Node model) {
setLayout(new MultiSplitLayout(model));
this.layout = (MultiSplitLayout) super.getLayout();
MouseDrag drag = new MouseDrag();
addMouseListener(drag);
addMouseMotionListener(drag);
}
@Override
protected void addImpl(Component comp, Object constraints, int index) {
if (constraints instanceof String) {
String name = (String) constraints;
getLayout().addLayoutComponent(name, comp);
Node node = getLayout().getNodeForName(name);
if (node != null) {
super.addImpl(comp, getLayout().getConstraintForComponent(comp, node), node.getParent().getChildren().indexOf(node));
} else {
throw new IllegalArgumentException("No such node with name " + name + " found in model");
}
} else {
throw new IllegalArgumentException("Constraints cannot be null and must be a String");
}
}
@Override
public MultiSplitLayout getLayout() {
return layout;
}
public Node getModel() {
return layout.getModel();
}
public void setModel(Node model) {
getLayout().setModel(model);
}
public Divider getDividerAt(Point p) {
return getDividerAt(p.x, p.y);
}
public Divider getDividerAt(int x, int y) {
if (getComponentAt(x, y) != MultiSplitPane.this) {
return null;
}
Component previous = null;
Component next = null;
if (getModel().isRowLayout()) {
for (int i = 0; i < INSETS_SIZE * 2; i++) {
previous = getComponentAt(x - i, y);
if (previous != null && previous != MultiSplitPane.this) {
break;
}
}
if (previous != null) {
for (int i = 0; i < INSETS_SIZE * 2; i++) {
next = getComponentAt(x + i, y);
if (next != null && next != MultiSplitPane.this) {
break;
}
}
}
} else {
for (int i = 0; i < INSETS_SIZE * 2; i++) {
previous = getComponentAt(x, y - i);
if (previous != null && previous != MultiSplitPane.this) {
break;
}
}
if (previous != null) {
for (int i = 0; i < INSETS_SIZE * 2; i++) {
next = getComponentAt(x, y + i);
if (next != null && next != MultiSplitPane.this) {
break;
}
}
}
}
if (next != null && next != MultiSplitPane.this && previous != null && previous != MultiSplitPane.this) {
return new Divider(previous, next);
}
return null;
}
public class MultiSplitLayout extends GridBagLayout implements LayoutManager {
private static final int PRECISION = 1000000; // 1e6
private BiMap<String, Component> components = HashBiMap.create();
private Node model;
private int requiredSize = 0;
public MultiSplitLayout(Node model) {
if (model == null) {
throw new NullPointerException();
}
setModel(model);
}
public Node getModel() {
return model;
}
public void setModel(Node model) {
if (model == null) {
throw new NullPointerException("Model cannot be null");
}
this.model = model;
updateConstraints();
}
public Node getNodeForComponent(Component comp) {
String name = components.inverse().get(comp);
if (name != null) {
return getNodeForName(name);
} else {
return null;
}
}
public Node getNodeForName(String name) {
for (Node n : model.getChildren()) {
if (n.getName().equals(name)) {
return n;
}
}
return null;
}
public final int getRequiredSize() {
return requiredSize;
}
public void update() {
updateRequiredSize();
updateConstraints();
}
private void updateRequiredSize() {
requiredSize = 0;
for (Node n : model.getChildren()) {
if (n.getPercentage() < 0) {
if (n.getSize() > 0) {
requiredSize += n.getSize();
}
}
}
}
private void updateConstraints() {
for (Node n : model.getChildren()) {
Component component = components.get(n.getName());
if (component != null) {
setConstraintForComponent(component, n);
}
}
}
private void setConstraintForComponent(Component comp, Node n) {
comp.setVisible(n.isVisible());
if (n.isVisible()) {
setConstraints(comp, getConstraintForComponent(comp, n));
} else {
super.removeLayoutComponent(comp);
}
}
private GridBagConstraints getConstraintForComponent(Component comp, Node n) {
GridBagConstraints gbc = new GridBagConstraints();
boolean rowLayout = n.getParent().isRowLayout();
if (rowLayout) {
gbc.gridx = n.getParent().getChildren().indexOf(n);
gbc.gridy = 0;
} else {
gbc.gridx = 0;
gbc.gridy = n.getParent().getChildren().indexOf(n);
}
gbc.fill = GridBagConstraints.BOTH;
if (n.getPercentage() >= 0) {
comp.setPreferredSize(new Dimension(0, 0));
if (rowLayout) {
gbc.weightx = n.getPercentage() * PRECISION;
gbc.weighty = 1.0;
} else {
gbc.weightx = 1.0;
gbc.weighty = n.getPercentage() * PRECISION;
}
} else {
if (n.getSize() > 0) {
// Only width or height will be used in the layout process
comp.setPreferredSize(new Dimension(n.getSize(), n.getSize()));
}
}
if (n.getChildren().size() == 0) {
gbc.insets = new Insets(INSETS_SIZE, INSETS_SIZE, INSETS_SIZE, INSETS_SIZE);
}
System.err.println(n.getName() + " " + gbc.weightx + " " + gbc.weighty);
return gbc;
}
@Override
public void addLayoutComponent(String name, Component comp) {
if (name == null) {
throw new IllegalArgumentException("Component name cannot be null");
}
Component c = components.get(name);
if (c != null && c != comp) {
throw new IllegalArgumentException("This container alread has a component for name " + name);
}
String old = components.inverse().remove(comp);
components.put(name, comp);
Node node = getNodeForName(name);
if (node != null) {
setConstraintForComponent(comp, node);
}
}
@Override
public void removeLayoutComponent(Component comp) {
super.removeLayoutComponent(comp);
for (Entry<String, Component> e : components.entrySet()) {
if (e.getValue() == comp) {
comp.setPreferredSize(null);
components.remove(e.getKey());
break;
}
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
try {
initTestUI();
} catch (ModelDefinitionException e) {
e.printStackTrace();
}
}
});
}
protected static void initTestUI() throws ModelDefinitionException {
JFrame frame = new JFrame("Test multi split");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ModelFactory factory = new ModelFactory(new ModelContext(Node.class));
Node root = factory.newInstance(Node.class);
root.setName("root");
root.setRowLayout(true);
Node left = getVerticalSplit(factory, "left");
Node center = getVerticalSplit(factory, "center");
Node right = getVerticalSplit(factory, "right");
root.addChild(left);
root.addChild(center);
root.addChild(right);
MultiSplitPane splitPane = new MultiSplitPane(root);
splitPane.setBorder(BorderFactory.createLineBorder(Color.GREEN));
MultiSplitPane leftPane = new MultiSplitPane(left);
leftPane.setBorder(BorderFactory.createLineBorder(Color.RED));
MultiSplitPane centerPane = new MultiSplitPane(center);
centerPane.setBorder(BorderFactory.createLineBorder(Color.BLUE));
MultiSplitPane rightPane = new MultiSplitPane(right);
rightPane.setBorder(BorderFactory.createLineBorder(Color.BLACK));
splitPane.add(left.getName(), leftPane);
splitPane.add(center.getName(), centerPane);
splitPane.add(right.getName(), rightPane);
initSplit(left, leftPane);
initSplit(center, centerPane);
initSplit(right, rightPane);
frame.add(splitPane);
frame.setSize(1200, 600);
frame.setVisible(true);
}
private static void initSplit(Node node, MultiSplitPane splitPane) {
for (Node child : node.getChildren()) {
JButton button = new JButton(child.getName());
splitPane.add(child.getName(), button);
}
}
private static Node getVerticalSplit(ModelFactory factory, String name) {
Node node = factory.newInstance(Node.class);
node.setName(name);
node.setRowLayout(false);
node.setPercentage(1.0);
for (int i = 0; i < 3; i++) {
Node child = factory.newInstance(Node.class);
child.setName(name + "-" + i);
child.setPercentage(1.0 / 3);
node.addChild(child);
}
return node;
}
}