com.actelion.research.gui.dock.JDockingPanel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openchemlib Show documentation
Show all versions of openchemlib Show documentation
Open Source Chemistry Library
package com.actelion.research.gui.dock;
import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.*;
public class JDockingPanel extends JPanel implements ActionListener {
private static final long serialVersionUID = 0x20070720;
public static final int DOCK_CENTER = 0;
public static final int DOCK_TOP = 1;
public static final int DOCK_LEFT = 2;
public static final int DOCK_BOTTOM = 3;
public static final int DOCK_RIGHT = 4;
public static final int ALLOWED_DRAG_DROP_ACTIONS = DnDConstants.ACTION_MOVE; // currently no copy
private TreeMap mDockableMap;
private TreeMap mLeafMap;
private Dockable mPreviousTargetDockable;
private TreeRoot mTreeRoot;
private TreeLeaf mTargetLeaf;
private int mTargetPosition,mPreviousTargetPosition;
private GhostPreview mPreview;
private Dockable mMaximizedView;
private Vector mDividerChangeListeners;
/**
* Creates a docking panel to which any Dockables may be added by
* the respective dock methods.
*/
public JDockingPanel() {
mDockableMap = new TreeMap<>();
mLeafMap = new TreeMap<>();
mTargetPosition = -1;
mPreview = new GhostPreview();
// Unfortunately, this cannot be used to catch drag events of default JTable with active DropTarget
// Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.MOUSE_MOTION_EVENT_MASK);
setLayout(new OverlayLayout(this));
new DropTarget(this, ALLOWED_DRAG_DROP_ACTIONS, new DropTargetAdapter() {
@Override
public void dragOver(DropTargetDragEvent dtde) {
Dockable dockable = getDraggedDockable(dtde);
if (dockable != null) {
Point p = dtde.getLocation();
Rectangle b = dockable.getHeader().getBounds();
p.translate(b.x, b.y); // make p relative to Dockable
updatePreview(p, dockable);
}
}
@Override
public void drop(DropTargetDropEvent dtde) {
Transferable transferable = dtde.getTransferable();
if (mTargetPosition == -1
|| !transferable.isDataFlavorSupported(TransferableDockable.DF_DOCKABLE_DEF)) {
// TODO do we need this? fireDockableSelected(mDraggedDockable);
dtde.rejectDrop();
}
else {
dtde.acceptDrop(ALLOWED_DRAG_DROP_ACTIONS);
try {
String draggedTitle = (String)transferable.getTransferData(TransferableDockable.DF_DOCKABLE_DEF);
for (int i=0; i();
mDividerChangeListeners.add(l);
if (mTreeRoot != null)
mTreeRoot.setDividerChangeListeners(mDividerChangeListeners);
}
public void removeDividerChangeLister(DividerChangeListener l) {
if (mDividerChangeListeners != null) {
mDividerChangeListeners.remove(l);
if (mTreeRoot != null)
mTreeRoot.setDividerChangeListeners(mDividerChangeListeners);
}
}
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().startsWith("close_")) {
String title = e.getActionCommand().substring(6);
undock(title, false);
}
else if (e.getActionCommand().startsWith("max_")) {
String title = e.getActionCommand().substring(4);
maximize(title, null);
}
}
/**
* Toggle the maximization state of the dockable in the docking panel.
* If it is maximizing, then a toolbar may be provided that is shown in the maximized state.
* Note that a null toolbar causes the maximized state to use the default toolbar with
* maximize and close buttons only.
* @param maximizeToolBar null for default or toolbar that shall be shown in maximized state
*/
public void maximize(String title, JToolBar maximizeToolBar) {
Dockable dockable = mDockableMap.get(title);
if (mMaximizedView != null) {
remove(mMaximizedView);
dockable.endBorrowContent();
mTreeRoot.getChild().getComponent().setVisible(true);
mMaximizedView = null;
validate();
}
else if (!(mTreeRoot.getComponent() instanceof Dockable)) {
mTreeRoot.getChild().getComponent().setVisible(false);
selectDockable(dockable);
JComponent content = dockable.borrowContent();
// create dummy dockable to hold the maximized view
mMaximizedView = new Dockable(this, content, title, maximizeToolBar);
mMaximizedView.setPopupProvider(dockable.getPopupProvider());
mMaximizedView.setVisible(true);
add(mMaximizedView);
validate();
}
}
/**
* Sends ActionEvent to listeners when the user interactively selected another dockable
* @param dockable
*/
protected void fireDockableSelected(Dockable dockable) {
actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "selected_"+dockable.getTitle()));
}
private void updatePreview(Point p, Dockable draggedDockable) {
mTargetPosition = -1;
Dockable targetDockable = null;
for (Dockable d:mDockableMap.values()) {
if (d.isVisible()) {
Rectangle bounds = getAbsoluteBounds(d.getContent());
if (bounds.contains(p)) {
targetDockable = d;
mTargetPosition = getPosition(p, bounds);
break;
}
}
}
updatePreview(draggedDockable, targetDockable);
}
/**
* Calculates bounds of the dockable relative to this JDockingPanel
* @param jc
* @return
*/
public Rectangle getAbsoluteBounds(Component jc) {
Rectangle b = jc.getBounds();
Component c = jc.getParent();
while (c != this) {
Point p = c.getLocation();
b.translate(p.x, p.y);
c = c.getParent();
}
return b;
}
private int getPosition(Point p, Rectangle bounds) {
int x = p.x - bounds.x;
int y = p.y - bounds.y;
if (x > bounds.width/4 && x < bounds.width*3/4
&& y > bounds.height/4 && y < bounds.height*3/4)
return DOCK_CENTER;
boolean isTopOrRight = (((double)x / (double)bounds.width) > ((double)y / (double)bounds.height));
boolean isTopOrLeft = (((double)x / (double)bounds.width) + ((double)y / (double)bounds.height) < 1.0);
if (isTopOrRight)
return isTopOrLeft ? DOCK_TOP : DOCK_RIGHT;
return isTopOrLeft ? DOCK_LEFT : DOCK_BOTTOM;
}
private void updatePreview(Dockable draggedDockable, Dockable targetDockable) {
if (targetDockable == null) {
mTargetPosition = -1;
}
else if (targetDockable == draggedDockable) {
TreeLeaf sourceLeaf = mLeafMap.get(draggedDockable.getTitle());
if (mTargetPosition == DOCK_CENTER
|| sourceLeaf.getDockableCount() == 1)
mTargetPosition = -1;
else
mTargetLeaf = sourceLeaf;
}
else {
mTargetLeaf = mLeafMap.get(targetDockable.getTitle());
}
if (mPreviousTargetDockable != targetDockable
|| mPreviousTargetPosition != mTargetPosition) {
if (mTargetPosition != -1)
mPreview.createPreview(draggedDockable, targetDockable, mTargetPosition, this);
mPreviousTargetDockable = targetDockable;
mPreviousTargetPosition = mTargetPosition;
repaint();
}
}
@Override
public void paint(Graphics g) {
super.paint(g);
if (mTargetPosition != -1)
mPreview.drawPreview((Graphics2D)g);
}
/* private void performDrag() {
undock(mDraggedDockable.getTitle(), true);
dock(mDraggedDockable, mTargetPosition, mTargetLeaf, 0.5, true);
} */
/**
* May be overriden to catch user initiated drag&drop
* @param movedDockableName
* @param targetDockableName
* @param targetPosition
*/
public void relocateView(String movedDockableName, String targetDockableName, int targetPosition, float dividerPosition) {
Dockable movedDockable = getDockable(movedDockableName);
undock(movedDockableName, true);
dock(movedDockable, targetPosition, mLeafMap.get(targetDockableName), dividerPosition, true);
}
/**
* Dock the dockable to the center of the selected view.
* @param dockable
*/
public void dock(Dockable dockable) {
dock(dockable, DOCK_CENTER);
}
/**
* Dock the Dockable at the given position to the selected view.
* @param dockable
* @param position
*/
public void dock(Dockable dockable, int position) {
dock(dockable, position, getSelectedLeaf(), 0.5, false);
}
/**
* Dock the dockable either as a new root dockable or
* to the treeElement that contains dockable specified as part dockInfo.
* The dockInfo parameter is either "root" or has the following format:
* "title[\tposition[\tdividerlocation]]" with position is one of center,top,left,bottom,right
* and dividerlocation is between 0.0 and 1.0 giving the percentage of space given to the left/top
* element.
* @param dockable
* @param dockInfo encoded position and parent obtainable by getDockInfo(title)
*/
public void dock(Dockable dockable, String dockInfo) {
if (dockInfo == null || dockInfo.equals("root")) {
dock(dockable, DOCK_CENTER, getSelectedLeaf(), Double.NaN, false);
return;
}
int index1 = dockInfo.indexOf('\t');
int index2 = dockInfo.indexOf('\t', index1+1);
String title = dockInfo.substring(0, index1);
String p = (index2 == -1) ? dockInfo.substring(index1+1) : dockInfo.substring(index1+1, index2);
String dp = (index2 == -1) ? null : dockInfo.substring(index2);
int position = "center".equals(p) ? DOCK_CENTER
: "top".equals(p) ? DOCK_TOP
: "left".equals(p) ? DOCK_LEFT
: "bottom".equals(p) ? DOCK_BOTTOM
: "right".equals(p) ? DOCK_RIGHT
: -1;
double dividerPosition = (dp != null) ? Double.parseDouble(dp)
: (position == DOCK_CENTER) ? Double.NaN : 0.5;
dock(dockable, position, mLeafMap.get(title), dividerPosition, false);
}
/**
* Dock the dockable at the given position to the treeElement's component.
* If component is the first to dock then treeElement may be null.
* @param dockable
* @param position DOCK_CENTER,DOCK_LEFT,...
* @param treeLeaf may be null, if the dockable is the first in the JDockingPanel
* @param dividerPosition position of divider of new JSplitPane if position!=DOCK_CENTER
*/
private void dock(Dockable dockable, int position, TreeLeaf treeLeaf, double dividerPosition, boolean isDragging) {
mDockableMap.put(dockable.getTitle(), dockable);
if (treeLeaf == null) {
TreeLeaf leaf = new TreeLeaf(dockable, this, isDragging);
mTreeRoot = new TreeRoot(this, leaf);
mLeafMap.put(dockable.getTitle(), leaf);
}
else if (position == DOCK_CENTER) {
treeLeaf.addContent(dockable, isDragging);
mLeafMap.put(dockable.getTitle(), treeLeaf);
}
else {
TreeLeaf newLeaf = new TreeLeaf(dockable, this, isDragging);
TreeContainer parent = treeLeaf.getParent();
TreeFork treeFork = new TreeFork(treeLeaf, newLeaf, position, dividerPosition, mDividerChangeListeners);
parent.replaceChildElement(treeLeaf, treeFork);
mLeafMap.put(dockable.getTitle(), newLeaf);
}
selectDockable(dockable);
validate();
//System.out.println("*****Status after dock()");
//mTreeRoot.printStatus();
}
public void undock(String title) {
undock(title, false);
}
public void undock(String title, boolean isDragging) {
Dockable dockable = mDockableMap.get(title);
if (dockable != null) {
if(mDockableMap.size()==1) {
undockAll();
return;
}
boolean isSelected = dockable.isSelected();
TreeLeaf leaf = mLeafMap.get(title);
mLeafMap.remove(title);
if (leaf.removeContent(dockable, isDragging)) {
if (isSelected) {
TreeLeaf newSelected = mLeafMap.get(mLeafMap.firstKey());
if (newSelected != null)
newSelected.setSelected(true);
}
}
else {
if (isSelected)
leaf.setSelected(true);
}
mDockableMap.remove(title);
validate();
repaint();
}
//System.out.println("*****Status after undock()");
//mTreeRoot.printStatus();
}
public void undockAll() {
mTreeRoot = null;
mDockableMap.clear();
mLeafMap.clear();
removeAll();
validate();
repaint();
}
public Set getDockableTitles() {
return mDockableMap.keySet();
}
public Collection getDockables() {
return mDockableMap.values();
}
public Dockable getDockable(String title) {
return mDockableMap.get(title);
}
public String getTitle(Dockable dockable) {
for (String title:mDockableMap.keySet())
if (mDockableMap.get(title) == dockable)
return title;
return null;
}
public int getDockableCount() {
return mDockableMap.size();
}
/**
* returns the persistence information necessary to recreate the current docking state.
* To recreate a docking state create an empty JDockingPanel and call dock(dockable, dockInfo)
* once for every dockable keeping the order of the title[] array and pass the respective
* dockInfo from the array returned by this function.
* @return dockInfo[] array with a docking instruction for every dockable
*/
public String[] getDockInfoSequence() {
if (mTreeRoot == null)
return null;
return mTreeRoot.createStateInfo().toArray(new String[0]);
}
/**
* @param title
* @return true if a dockable with the given title exists and if it is the selected component of a tabbed pane
*/
public boolean isInFrontInTabbedPane(String title) {
Dockable dockable = mDockableMap.get(title);
if (dockable == null)
return false;
Component parent = dockable.getParent();
if (parent instanceof JTabbedPane)
return (dockable == ((JTabbedPane)parent).getSelectedComponent());
return false;
}
/**
* @param title
*/
public void setToFrontInTabbedPane(String title) {
Dockable dockable = mDockableMap.get(title);
if (dockable != null) {
Component parent = dockable.getParent();
if (parent instanceof JTabbedPane)
((JTabbedPane)parent).setSelectedComponent(dockable);
}
}
/**
* Checks, whether a dockable with the given title exists, and whether it is visible (selected if in a JTabbedPane)
* and whether it is in the left or right branch (depending on parameter left) of a JSplitPane.
* @param title
* @param left if true, then
* @return true the respective dockable is visible and in the left part of a JSplitPane
*/
public boolean isVisibleInSplitPane(String title, boolean left) {
Component view = mDockableMap.get(title);
if (view == null)
return false;
Component pane = view.getParent();
if (pane instanceof JTabbedPane) {
if (view != ((JTabbedPane)pane).getSelectedComponent())
return false;
view = pane;
pane = pane.getParent();
}
if (pane instanceof JSplitPane)
return (left && view == ((JSplitPane)pane).getLeftComponent())
|| (!left && view == ((JSplitPane)pane).getRightComponent());
return false;
}
/**
* changes a title of a docked Dockable
* @param oldTitle existing title
* @param newTitle must be a unique title and must not contain a TAB
* @return true in case of success
*/
public boolean changeTitle(String oldTitle, String newTitle) {
if (mDockableMap.containsKey(newTitle) || newTitle.indexOf('\t') != -1)
return false;
Dockable dockable = mDockableMap.get(oldTitle);
mDockableMap.remove(oldTitle);
mDockableMap.put(newTitle, dockable);
dockable.setTitle(newTitle);
TreeLeaf leaf = mLeafMap.get(oldTitle);
leaf.changeTitle(oldTitle, newTitle);
mLeafMap.remove(oldTitle);
mLeafMap.put(newTitle, leaf);
if (mMaximizedView != null
&& mMaximizedView.getTitle().equals(oldTitle))
mMaximizedView.setTitle(newTitle);
return true;
}
/**
* Returns the Dockable that contains the given content.
* @param content the content to be looked for
* @return content owning Dockable or null if content is not found
*/
public Dockable getDockable(Component content) {
if (content != null)
for (Dockable d:mDockableMap.values())
if (d.getContent() == content)
return d;
return null;
}
/**
* Returns the currently active Dockable, i.e. the one with the
* highlighted header area.
* @return active Dockable or null if there are no Dockables
*/
public Dockable getSelectedDockable() {
for (Dockable d:mDockableMap.values())
if (d.isSelected())
return d;
return null;
}
public Dockable getDraggedDockable(DropTargetDragEvent dtde) {
Transferable transferable = dtde.getTransferable();
if (transferable.isDataFlavorSupported(TransferableDockable.DF_DOCKABLE_DEF)) {
try {
return getDockable((String)transferable.getTransferData(TransferableDockable.DF_DOCKABLE_DEF));
}
catch (Exception e) {}
}
return null;
}
/**
* This is called when a Dockable's visibility changes, i.e.
* - when a new Dockable is docked
* - when a new Dockable is undocked
* - when the user drags & docks a Dockable
* - when the user actively switches tabs in a tabbed pane
* It may be overridden in order to react on a visibility change due
* to the user selecting a different tab of dragging & docking a
* Dockable.
* @param dockable
* @param isVisible
*/
public void visibilityChanged(Dockable dockable, boolean isVisible) {
dockable.notifyVisibility(isVisible);
}
public void selectDockable(Dockable dockable) {
if (dockable != null && !isMaximized()) {
for (Dockable d:mDockableMap.values())
d.setSelected(d == dockable);
TreeLeaf leaf = mLeafMap.get(dockable.getTitle());
if (leaf != null)
leaf.setSelectedDockable(dockable);
}
}
private TreeLeaf getSelectedLeaf() {
for (TreeLeaf leaf:mLeafMap.values())
if (leaf.isSelected())
return leaf;
return null;
}
/**
* @return whether one of the dockable views is currently maximized
*/
public boolean isMaximized() {
return (mMaximizedView != null);
}
public void redistribute() {
List dockables = new ArrayList(getDockables());
Collections.sort(dockables, new Comparator() {
public int compare(Dockable o1, Dockable o2) {
return o1.getTitle().compareTo(o2.getTitle());
}
});
if(dockables.size()<=1)
return;
mTreeRoot = null;
mLeafMap.clear();
mDockableMap.clear();
removeAll();
validate();
for (Dockable dockable : dockables) {
dockable.borrowContent();
}
if(dockables.size()>0) {
Dockable dockable = dockables.get(0);
TreeLeaf leaf = new TreeLeaf(dockable, this, false);
mTreeRoot = new TreeRoot(this, leaf);
mLeafMap.put(dockable.getTitle(), leaf);
mDockableMap.put(dockable.getTitle(), dockable);
redistribute(dockables, 0, dockables.size(), leaf);
}
for (Dockable dockable : dockables) {
dockable.endBorrowContent();
}
validate();
repaint();
}
private void redistribute(List dockables, int startIndex, int endIndex, TreeLeaf topLeft) {
//int size = (endIndex-startIndex + 3)/4;
//if(size>1 && size%2==1) size++;
//size = (int)(Math.sqrt(size)+.99);
//size*= size;
//System.out.println("JDockingPanel.redistribute() "+startIndex+" "+endIndex+" size="+size);
int size1 = startIndex + (endIndex-startIndex+3)/4;
int size2 = size1 + (endIndex-size1+2)/3;
int size3 = size2 + (endIndex-size2+1)/2;
System.out.println(startIndex+" "+size1+" "+size2+" "+size3+" "+endIndex );
TreeLeaf topRight = null;
TreeLeaf bottomLeft = null;
TreeLeaf bottomRight = null;
if(size2>size1 && size2startIndex && size1size2 && size3startIndex+1)
redistribute(dockables, startIndex, size1, topLeft);
if (size2>size1+1)
redistribute(dockables, size1, size2, topRight);
if (size3>size2+1)
redistribute(dockables, size2, size3, bottomLeft);
if (endIndex>size3+1)
redistribute(dockables, size3, endIndex, bottomRight);
}
}