/* * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ 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.Window; 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 = ((Window)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() { @Override 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() { @Override 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() { @Override public void run() { setActiveEdge(newEdge); setActiveDockableId(newDockableId); viewPane.updateOrientation(); viewPane.updateContents(); } }; Animation activation = newDockableId==null? null: new Animation(this, false); Runnable updater2 = new Runnable() { @Override 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; } }