/*
* Created on Apr 14, 2005
*/
package org.flexdock.dockbar;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Iterator;
import java.util.WeakHashMap;
import javax.swing.JLayeredPane;
import javax.swing.SwingUtilities;
import org.flexdock.dockbar.activation.ActivationQueue;
import org.flexdock.dockbar.activation.ActiveDockableHandler;
import org.flexdock.dockbar.activation.Animation;
import org.flexdock.dockbar.event.ActivationListener;
import org.flexdock.dockbar.event.DockablePropertyChangeHandler;
import org.flexdock.dockbar.event.DockbarEvent;
import org.flexdock.dockbar.event.DockbarEventHandler;
import org.flexdock.dockbar.event.DockbarListener;
import org.flexdock.dockbar.event.DockbarTracker;
import org.flexdock.dockbar.layout.DockbarLayout;
import org.flexdock.docking.Dockable;
import org.flexdock.docking.DockingManager;
import org.flexdock.docking.floating.frames.DockingFrame;
import org.flexdock.docking.props.PropertyChangeListenerFactory;
import org.flexdock.docking.state.DockingPath;
import org.flexdock.docking.state.DockingState;
import org.flexdock.docking.state.MinimizationManager;
import org.flexdock.event.EventManager;
import org.flexdock.perspective.RestorationManager;
import org.flexdock.util.RootWindow;
import org.flexdock.util.Utilities;
/**
* @author Christopher Butler
* @author Bobby Rosenberger
* @author Mateusz Szczap
*/
public class DockbarManager {
private static final WeakHashMap MANAGERS_BY_WINDOW = new WeakHashMap();
public static final Integer DOCKBAR_LAYER = new Integer(JLayeredPane.PALETTE_LAYER.intValue()-5);
public static final int DEFAULT_EDGE = MinimizationManager.LEFT;
private static String dockbarManagerClassName;
private static DockbarManager currentManager;
protected WeakReference windowRef;
protected Dockbar leftBar;
protected Dockbar rightBar;
protected Dockbar bottomBar;
protected ViewPane viewPane;
protected DockbarLayout dockbarLayout;
private ActivationListener activationListener;
private HashMap dockables;
private int activeEdge = MinimizationManager.UNSPECIFIED_LAYOUT_CONSTRAINT;
private String activeDockableId;
private boolean animating;
private boolean dragging;
static {
Class c = DockingManager.class;
EventManager.addHandler(new DockbarEventHandler());
DockbarTracker.register();
// setup to listen for Dockable property change events
PropertyChangeListenerFactory.addFactory(new DockablePropertyChangeHandler.Factory());
// update behavior of active Dockable changes
EventManager.addListener(new ActiveDockableHandler());
}
public static DockbarManager getInstance(Component c) {
RootWindow window = RootWindow.getRootContainer(c);
return getInstance(window);
}
public static DockbarManager getInstance(RootWindow window) {
if(window==null)
return null;
// DockingFrames should not be allowed to contain dockbars.
// This may change in the future, but for now if our window is a
// DockingFrame, reroute to its owner.
Component root = window.getRootContainer();
if(root instanceof DockingFrame) {
root = ((DockingFrame)root).getOwner();
return getInstance(root);
}
DockbarManager mgr = (DockbarManager)MANAGERS_BY_WINDOW.get(window);
if(mgr==null) {
mgr = createDockbarManager(window);
synchronized(MANAGERS_BY_WINDOW) {
MANAGERS_BY_WINDOW.put(window, mgr);
}
mgr.install();
}
if(currentManager==null)
currentManager = mgr;
return mgr;
}
/**
* Creates a new DockbarManager instance. In the case that a dockbarManager class name
* has been set the class will be instantiated by reflection. If no classname is set a
* org.flexdock.dockbar.DockbarManager will be created.
*
* @param window RootWindow for which the DockbarManager will be created
* @return new DockbarManager instance
* @see DockbarManager#setDockbarManager(String)
*/
private static DockbarManager createDockbarManager(RootWindow window) {
if (dockbarManagerClassName == null)
return new DockbarManager(window);
DockbarManager mgr = null;
try {
Class clazz = Class.forName(dockbarManagerClassName);
Constructor constructor = clazz.getConstructor(new Class[] {RootWindow.class});
mgr = (DockbarManager)constructor.newInstance(new Object[] {window});
} catch(Exception e) {
throw new RuntimeException(e);
}
return mgr;
}
/**
* Sets a custom DockbarManager class which will be used to create new DockbarManager
* instances.
*
* @param className Classname of your custom DockbarManager.
*/
public static void setDockbarManager(String className) {
dockbarManagerClassName = className;
}
public static DockbarManager getCurrent(Dockable dockable) {
if(dockable==null)
return null;
synchronized(MANAGERS_BY_WINDOW) {
for(Iterator it=MANAGERS_BY_WINDOW.values().iterator(); it.hasNext();) {
DockbarManager mgr = (DockbarManager)it.next();
if(mgr.isOwner(dockable))
return mgr;
}
}
return null;
}
public static void windowChanged(Component newWindow) {
currentManager = getInstance(newWindow);
}
public static DockbarManager getCurrent() {
return currentManager;
}
public static void addListener(DockbarListener listener) {
EventManager.addListener(listener);
}
public static void activate(String dockableId, boolean locked) {
Dockable dockable = DockingManager.getDockable(dockableId);
activate(dockable, locked);
}
public static void activate(Dockable dockable, boolean locked) {
if(dockable==null)
return;
DockbarManager mgr = getCurrent(dockable);
if(mgr==null || !mgr.contains(dockable))
return;
mgr.setActiveDockable(dockable);
if(locked)
mgr.getActivationListener().lockViewpane();
}
protected DockbarManager(RootWindow window) {
dockbarLayout = new DockbarLayout(this);
activationListener = new ActivationListener(this);
leftBar = new Dockbar(this, MinimizationManager.LEFT);
rightBar = new Dockbar(this, MinimizationManager.RIGHT);
bottomBar = new StatusDockbar(this, MinimizationManager.BOTTOM);
viewPane = new ViewPane(this);
windowRef = new WeakReference(window);
dockables = new HashMap();
}
public RootWindow getWindow() {
return (RootWindow)windowRef.get();
}
protected void install() {
RootWindow window = getWindow();
if(window==null)
return;
JLayeredPane layerPane = window.getLayeredPane();
boolean changed = install(leftBar, layerPane);
changed = install(rightBar, layerPane) || changed;
changed = install(bottomBar, layerPane) || changed;
changed = install(viewPane, layerPane) || changed;
if(changed) {
layerPane.addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent evt) {
if(evt.getSource() instanceof JLayeredPane)
revalidate();
}
});
}
revalidate();
}
private boolean install(Component c, JLayeredPane layerPane) {
if(c.getParent()!=layerPane) {
if(c.getParent()!=null)
c.getParent().remove(c);
layerPane.add(c, DOCKBAR_LAYER);
return true;
}
return false;
}
public Dockbar getBottomBar() {
return bottomBar;
}
public Dockbar getLeftBar() {
return leftBar;
}
public Dockbar getRightBar() {
return rightBar;
}
public ViewPane getViewPane() {
return viewPane;
}
public void revalidate() {
EventQueue.invokeLater(new Runnable() {
public void run() {
validate();
}
});
}
public void validate() {
toggleDockbars();
dockbarLayout.layout();
viewPane.revalidate();
}
private void toggleDockbars() {
leftBar.setVisible(leftBar.getComponentCount()!=0);
rightBar.setVisible(rightBar.getComponentCount()!=0);
bottomBar.setVisible(bottomBar.getComponentCount()!=0);
}
private int findDockbarEdge(Dockable dockable) {
RootWindow window = RootWindow.getRootContainer(dockable.getComponent());
if(window==null)
return DEFAULT_EDGE;
// get the dockable component and it's containing content pane
Component cmp = dockable.getComponent();
Container contentPane = window.getContentPane();
// get the bounds of the content pane and dockable, translating the dockable into the
// content pane's axes
Rectangle contentRect = new Rectangle(0, 0, contentPane.getWidth(), contentPane.getHeight());
Rectangle dockRect = SwingUtilities.convertRectangle(cmp.getParent(), cmp.getBounds(), contentPane);
// get the center of the dockable
Point dockCenter = new Point(dockRect.x + (dockRect.width/2), dockRect.y + (dockRect.height/2));
// get the center left, right, and bottom points
Point leftCenter = new Point(0, contentRect.height/2);
Point bottomCenter = new Point(contentRect.width/2, contentRect.height);
Point rightCenter = new Point(contentRect.width, contentRect.height/2);
// calculate the absolute distance from dockable center to each of the edge
// center points. whichever is the shortest, that is the edge the dockable is
// 'closest' to and that will be the edge we'll return
double min = Math.abs(dockCenter.distance(leftCenter));
int edge = MinimizationManager.LEFT;
double delta = Math.abs(dockCenter.distance(rightCenter));
if(delta<min) {
min = delta;
edge = MinimizationManager.RIGHT;
}
delta = Math.abs(dockCenter.distance(bottomCenter));
if(delta<min) {
min = delta;
edge = MinimizationManager.BOTTOM;
}
return edge;
}
public int getEdge(String dockableId) {
Dockable dockable = DockingManager.getDockable(dockableId);
return getEdge(dockable);
}
public int getEdge(Dockable dockable) {
Dockbar dockbar = getDockbar(dockable);
if(dockbar==leftBar)
return MinimizationManager.LEFT;
if(dockbar==rightBar)
return MinimizationManager.RIGHT;
if(dockbar==bottomBar)
return MinimizationManager.BOTTOM;
return MinimizationManager.UNSPECIFIED_LAYOUT_CONSTRAINT;
}
public Dockbar getDockbar(Dockable dockable) {
if(dockable==null)
return null;
if(leftBar.contains(dockable))
return leftBar;
if(rightBar.contains(dockable))
return rightBar;
if(bottomBar.contains(dockable))
return bottomBar;
return null;
}
public Dockbar getDockbar(int edge) {
edge = Dockbar.getValidOrientation(edge);
switch(edge) {
case MinimizationManager.RIGHT:
return rightBar;
case MinimizationManager.BOTTOM:
return bottomBar;
default:
return leftBar;
}
}
public void minimize(Dockable dockable) {
if(dockable==null)
return;
int edge = DEFAULT_EDGE;
RootWindow window = getWindow();
if(window!=null && DockingManager.isDocked(dockable)) {
edge = findDockbarEdge(dockable);
}
minimize(dockable, edge);
}
public void minimize(Dockable dockable, int edge) {
if(dockable==null)
return;
if(isDockingCancelled(dockable, edge))
return;
// install the dockable
edge = Dockbar.getValidOrientation(edge);
install(dockable, edge);
// store the dockable id
dockables.put(dockable.getPersistentId(), new Integer(edge));
// send event notification
DockbarEvent evt = new DockbarEvent(dockable, DockbarEvent.MINIMIZE_COMPLETED, edge);
EventManager.dispatch(evt);
}
public void reAdd(Dockable dockable) {
// can't re-add if the dockable is null, or we already contain it
if(dockable==null || contains(dockable))
return;
Integer edge = (Integer)dockables.get(dockable.getPersistentId());
if(edge!=null)
install(dockable, edge.intValue());
}
private void install(Dockable dockable, int edge) {
Dockbar dockbar = getDockbar(edge);
// undock the dockable
DockingManager.undock(dockable);
// place in the dockbar
dockbar.dock(dockable);
// make sure they can't drag the dockable while it's in the dockbar
dockable.getDockingProperties().setDockingEnabled(false);
// indicate that the dockable is minimized
DockingState info = DockingManager.getLayoutManager().getDockingState(dockable);
info.setMinimizedConstraint(edge);
revalidate();
}
private boolean isDockingCancelled(Dockable dockable, int edge) {
DockbarEvent evt = new DockbarEvent(dockable, DockbarEvent.MINIMIZE_STARTED, edge);
EventManager.dispatch(evt);
return evt.isConsumed();
}
public void restore(final Dockable dockable) {
if(dockable == null) {
return;
}
// now restore to the current layout
final DockingState dockingState = DockingManager.getDockingState(dockable);
final DockingPath dockingPath = dockingState.getPath();
boolean restoreResult = false;
if (dockingPath != null) {
restoreResult = dockingPath.restore(dockable);
} else {
restoreResult = RestorationManager.getInstance().restore(dockable);
}
if (restoreResult) {
// remove the dockable from the dockbar
remove(dockable);
// remove the dockable reference
dockables.remove(dockable.getPersistentId());
}
}
public boolean remove(Dockable dockable) {
if(dockable==null)
return false;
if(getActiveDockable()==dockable)
setActiveDockable((Dockable)null);
Dockbar dockbar = getDockbar(dockable);
if(dockbar==null)
return false;
dockbar.undock(dockable);
// restore drag capability to the dockable after removing
// from the dockbar
dockable.getDockingProperties().setDockingEnabled(true);
revalidate();
return true;
}
public int getActiveEdge() {
synchronized(this) {
return activeEdge;
}
}
private void setActiveEdge(int edge) {
synchronized(this) {
activeEdge = edge;
}
}
private Dockbar getActiveDockbar() {
int edge = getActiveEdge();
switch(edge) {
case MinimizationManager.TOP:
return bottomBar;
case MinimizationManager.RIGHT:
return rightBar;
default:
return leftBar;
}
}
public String getActiveDockableId() {
synchronized(this) {
return activeDockableId;
}
}
private void setActiveDockableId(String id) {
synchronized(this) {
activeDockableId = id;
}
}
public Dockable getActiveDockable() {
String dockingId = getActiveDockableId();
Dockable dockable = DockingManager.getDockable(dockingId);
return dockable;
}
public Cursor getResizeCursor() {
return viewPane.getResizeCursor();
}
public boolean isActive() {
return getActiveDockable()!=null;
}
public void setActiveDockable(String dockableId) {
Dockable dockable = DockingManager.getDockable(dockableId);
setActiveDockable(dockable);
}
public void setActiveDockable(Dockable dockable) {
// if we're not currently docked to any particular edge, then
// we cannot activate the specified dockable. instead, set the
// active dockable to null
final int newEdge = getEdge(dockable);
if(newEdge==MinimizationManager.UNSPECIFIED_LAYOUT_CONSTRAINT)
dockable = null;
// check for dockable changes
Dockable oldDockable = getActiveDockable();
final String newDockableId = dockable==null? null: dockable.getPersistentId();
String currentlyActiveId = getActiveDockableId();
boolean changed = Utilities.isChanged(currentlyActiveId, newDockableId);
// check for edge changes
changed = changed || newEdge!=getActiveEdge();
// if nothing has changed, then we're done
if(changed) {
viewPane.setLocked(false);
setActiveEdge(newEdge);
setActiveDockableId(newDockableId);
startAnimation(oldDockable, dockable, newDockableId, newEdge);
}
}
private void dispatchEvent(Dockable oldDockable, Dockable newDockable) {
// dispatch to event listeners
int evtType = DockbarEvent.EXPANDED;
if(newDockable==null && oldDockable!=null) {
newDockable = oldDockable;
evtType = DockbarEvent.COLLAPSED;
}
if(newDockable!=null) {
DockbarEvent evt = new DockbarEvent(newDockable, evtType, getActiveEdge());
EventManager.dispatch(evt);
}
}
private void startAnimation(final Dockable oldDockable, final Dockable newDockable, final String newDockableId, final int newEdge) {
Animation deactivation = oldDockable==null? null: new Animation(this, true);
Runnable updater1 = new Runnable() {
public void run() {
setActiveEdge(newEdge);
setActiveDockableId(newDockableId);
viewPane.updateOrientation();
viewPane.updateContents();
}
};
Animation activation = newDockableId==null? null: new Animation(this, false);
Runnable updater2 = new Runnable() {
public void run() {
viewPane.setPrefSize(ViewPane.UNSPECIFIED_PREFERRED_SIZE);
viewPane.updateOrientation();
viewPane.updateContents();
revalidate();
// dispatch event notification
dispatchEvent(oldDockable, newDockable);
}
};
ActivationQueue queue = new ActivationQueue(this, deactivation, updater1, activation, updater2);
queue.start();
}
public int getPreferredViewpaneSize() {
return dockbarLayout.getDesiredViewpaneSize();
}
public boolean isAnimating() {
return animating;
}
public void setAnimating(boolean animating) {
this.animating = animating;
}
public boolean isDragging() {
return dragging;
}
public void setDragging(boolean dragging) {
this.dragging = dragging;
}
public ActivationListener getActivationListener() {
return activationListener;
}
public boolean contains(Dockable dockable) {
return getDockbar(dockable)!=null;
}
private boolean isOwner(Dockable dockable) {
return dockable==null? false: dockables.containsKey(dockable.getPersistentId());
}
public DockbarLayout getLayout() {
return dockbarLayout;
}
}