/*
* Created on May 17, 2005
*/
package org.flexdock.perspective;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Rectangle;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import org.flexdock.docking.Dockable;
import org.flexdock.docking.DockingManager;
import org.flexdock.docking.DockingPort;
import org.flexdock.docking.floating.frames.DockingFrame;
import org.flexdock.docking.state.DockingState;
import org.flexdock.docking.state.FloatManager;
import org.flexdock.docking.state.FloatingGroup;
import org.flexdock.docking.state.LayoutNode;
import org.flexdock.event.EventManager;
import org.flexdock.perspective.event.LayoutEvent;
import org.flexdock.perspective.event.LayoutListener;
import org.flexdock.perspective.event.RegistrationEvent;
import org.flexdock.util.DockingUtility;
import org.flexdock.util.RootWindow;
import org.flexdock.util.SwingUtility;
/**
* @author Christopher Butler
*/
public class Layout implements Cloneable, FloatManager, Serializable {
private HashMap dockingInfo; // contains DockingState objects
private Hashtable floatingGroups; // contains FloatingGroup objects
private LayoutNode restorationLayout;
private transient ArrayList layoutListeners;
public Layout() {
this(new HashMap(), new ArrayList(), new Hashtable());
}
private Layout(HashMap info, ArrayList listeners, Hashtable floatGroups) {
dockingInfo = info;
layoutListeners = listeners;
floatingGroups = floatGroups;
}
private ArrayList getLayoutListeners() {
if(layoutListeners==null)
layoutListeners = new ArrayList();
return layoutListeners;
}
public void addListener(LayoutListener listener) {
if(listener!=null) {
synchronized(getLayoutListeners()) {
getLayoutListeners().add(listener);
}
}
}
public void removeListener(LayoutListener listener) {
if(listener!=null) {
synchronized(getLayoutListeners()) {
getLayoutListeners().remove(listener);
}
}
}
public LayoutListener[] getListeners() {
return (LayoutListener[])getLayoutListeners().toArray(new LayoutListener[0]);
}
public void add(Dockable dockable) {
String key = dockable==null? null: dockable.getPersistentId();
add(key);
}
public void add(String dockableId) {
if(dockableId==null)
return;
DockingState info = null;
synchronized(dockingInfo) {
// return if we're already managing this dockable
if (dockingInfo.containsKey(dockableId)) {
return;
}
// create and add dockingstateinfo here
info = new DockingState(dockableId);
dockingInfo.put(dockableId, info);
}
EventManager.dispatch(new RegistrationEvent(info, this, true));
}
public DockingState remove(String dockableId) {
if(dockableId==null)
return null;
DockingState info = null;
synchronized(dockingInfo) {
info = (DockingState)dockingInfo.remove(dockableId);
}
// dispatch event notification if we actually removed something
if(info!=null)
EventManager.dispatch(new RegistrationEvent(info, this, false));
return info;
}
public boolean contains(Dockable dockable) {
return dockable==null? false: contains(dockable.getPersistentId());
}
public boolean contains(String dockable) {
return dockable==null? false: dockingInfo.containsKey(dockable);
}
public Dockable getDockable(String id) {
if(dockingInfo.containsKey(id))
return DockingManager.getDockable(id);
return null;
}
public Dockable[] getDockables() {
ArrayList list = new ArrayList(dockingInfo.size());
for(Iterator it=dockingInfo.keySet().iterator(); it.hasNext();) {
String dockingId = (String)it.next();
Dockable d = DockingManager.getDockable(dockingId);
if(d!=null)
list.add(d);
}
return (Dockable[])list.toArray(new Dockable[0]);
}
public DockingState getDockingState(String dockableId) {
return getDockingState(dockableId, false);
}
public DockingState getDockingState(Dockable dockable) {
return getDockingState(dockable, false);
}
public DockingState getDockingState(Dockable dockable, boolean load) {
if(dockable==null)
return null;
return getDockingState(dockable.getPersistentId(), load);
}
public DockingState getDockingState(String dockableId, boolean load) {
if(dockableId==null)
return null;
if(load) {
Dockable dockable = DockingManager.getDockable(dockableId);
if(dockable!=null) {
isMaintained(dockable);
}
}
Object obj = dockingInfo.get(dockableId);
return (DockingState)obj;
}
public void setDockingState(String dockableId, DockingState dockingState) {
if(dockableId==null || dockingState == null) {
return;
}
this.dockingInfo.put(dockableId, dockingState);
}
public void apply(DockingPort dockingPort) {
Component comp = (Component)dockingPort;
if(comp==null || !isInitialized())
// if(comp==null || comp.getParent()==null || !isInitialized())
return;
// clear out the existing components
PerspectiveManager.clear(dockingPort);
// restore the layout
boolean listening = PerspectiveManager.isDockingStateListening();
PerspectiveManager.setDockingStateListening(false);
try {
dockingPort.importLayout(restorationLayout);
} finally {
PerspectiveManager.setDockingStateListening(listening);
}
// not restore floating and minimized layouts
Dockable[] dockables = getDockables();
// if there is no active window into which to restore our minimized
// dockables, then we'll have to defer restoration until a window appears.
ArrayList deferredMinimizedDockables = new ArrayList();
boolean deferMinimized = SwingUtility.getActiveWindow()==null;
boolean restoreFloatOnLoad = PerspectiveManager.isRestoreFloatingOnLoad();
for(int i=0; i<dockables.length; i++) {
Dockable dockable = dockables[i];
if(DockingUtility.isMinimized(dockable)) {
if(deferMinimized) {
deferredMinimizedDockables.add(dockable);
} else {
RestorationManager.getInstance().restore(dockable);
}
} else if(restoreFloatOnLoad && DockingUtility.isFloating(dockable)) {
RestorationManager.getInstance().restore(dockable);
}
}
// if necessary, defer minimized restoration until after a valid window
// has been resolved
restoreDeferredMinimizedDockables(deferredMinimizedDockables);
// send notification
LayoutEvent evt = new LayoutEvent(this, null, null, LayoutEvent.LAYOUT_APPLIED);
EventManager.dispatch(evt);
}
private void restoreDeferredMinimizedDockables(final ArrayList deferred) {
if(deferred==null || deferred.size()==0)
return;
EventQueue.invokeLater(new Runnable() {
public void run() {
restoreMinimizedDockables(deferred);
}
});
}
private void restoreMinimizedDockables(ArrayList dockables) {
if(SwingUtility.getActiveWindow()==null) {
restoreDeferredMinimizedDockables(dockables);
return;
}
for(Iterator it=dockables.iterator(); it.hasNext();) {
Dockable dockable = (Dockable)it.next();
RestorationManager.getInstance().restore(dockable);
}
}
private boolean isMaintained(Dockable dockable) {
if(dockable==null)
return false;
if(!contains(dockable))
add(dockable);
return true;
}
public void hide(Dockable dockable) {
if(!isMaintained(dockable))
return;
boolean hidden = false;
if(DockingManager.isDocked((Dockable)dockable)) {
hidden = DockingManager.undock(dockable);
} else if (DockingUtility.isMinimized(dockable)) {
hidden = DockingManager.getMinimizeManager().close(dockable);
}
if(hidden) {
LayoutEvent evt = new LayoutEvent(this, null, dockable.getPersistentId(), LayoutEvent.DOCKABLE_HIDDEN);
EventManager.dispatch(evt);
}
}
public void show(Dockable dockable, DockingPort dockingPort) {
if(!isMaintained(dockable) || DockingManager.isDocked(dockable))
return;
}
public Object clone() {
synchronized(this) {
ArrayList listeners = (ArrayList)getLayoutListeners().clone();
HashMap infoMap = (HashMap)dockingInfo.clone();
for(Iterator it=dockingInfo.keySet().iterator(); it.hasNext();) {
String key = (String)it.next();
DockingState info = getDockingState(key);
infoMap.put(key, info.clone());
}
Hashtable floatTable = (Hashtable)floatingGroups.clone();
for(Iterator it=floatingGroups.keySet().iterator(); it.hasNext();) {
Object key = it.next();
FloatingGroup group = (FloatingGroup)floatingGroups.get(key);
floatTable.put(key, group.clone());
}
// note, we're using a shallow copy of the listener list.
// it's okay that we share listener references, since we want the
// cloned Layout to have the same listeners.
Layout clone = new Layout(infoMap, listeners, floatTable);
LayoutNode restoreNode = restorationLayout==null? null: (LayoutNode)restorationLayout.clone();
clone.restorationLayout = restoreNode;
return clone;
}
}
private DockingFrame getDockingFrame(Dockable dockable, Component frameOwner) {
FloatingGroup group = getGroup(dockable);
if(group==null)
group = new FloatingGroup(getFloatingGroup(dockable));
DockingFrame frame = group.getFrame();
if(frame==null) {
frame = DockingFrame.create(frameOwner, group.getName());
group.setFrame(frame);
floatingGroups.put(group.getName(), group);
}
return frame;
}
public DockingFrame floatDockable(Dockable dockable, Component frameOwner, Rectangle screenBounds) {
if(dockable==null || screenBounds==null)
return null;
// create the frame
DockingFrame frame = getDockingFrame(dockable, frameOwner);
if(screenBounds!=null)
frame.setBounds(screenBounds);
// undock the current Dockable instance from it's current parent container
DockingManager.undock(dockable);
// add to the floating frame
frame.addDockable(dockable);
// display and return
if(!frame.isVisible())
frame.setVisible(true);
return frame;
}
public DockingFrame floatDockable(Dockable dockable, Component frameOwner) {
FloatingGroup group = getGroup(dockable);
Rectangle bounds = group==null? null: group.getBounds();
if(bounds==null) {
if(dockable.getComponent().isValid()) {
bounds = dockable.getComponent().getBounds();
} else
bounds = new Rectangle(0, 0, 200, 200);
Rectangle ownerBounds = frameOwner instanceof DockingFrame?
((DockingFrame)frameOwner).getOwner().getBounds():
RootWindow.getRootContainer(frameOwner).getRootContainer().getBounds();
int x = (ownerBounds.x + ownerBounds.width/2) - bounds.width/2;
int y = (ownerBounds.y + ownerBounds.height/2) - bounds.height/2;
bounds.setLocation(x, y);
}
return floatDockable(dockable, frameOwner, bounds);
}
public FloatingGroup getGroup(Dockable dockable) {
if(dockable==null)
return null;
String groupId = getFloatingGroup(dockable);
return getGroup(groupId);
}
public String[] getFloatingGroupIds() {
return (String[]) this.floatingGroups.keySet().toArray(new String[] {});
}
public FloatingGroup getGroup(String groupId) {
return groupId==null? null: (FloatingGroup)floatingGroups.get(groupId);
}
public void addFloatingGroup(FloatingGroup floatingGroup) {
if (floatingGroup == null) {
return;
}
floatingGroups.put(floatingGroup.getName(), floatingGroup);
}
public void addToGroup(Dockable dockable, String groupId) {
// floating groups are mutually exclusive
removeFromGroup(dockable);
FloatingGroup group = getGroup(groupId);
if(dockable!=null && group!=null) {
group.addDockable(dockable.getPersistentId());
setFloatingGroup(dockable, group.getName());
}
}
public void removeFromGroup(Dockable dockable) {
FloatingGroup group = getGroup(dockable);
if(dockable!=null) {
if(group!=null)
group.removeDockable(dockable.getPersistentId());
setFloatingGroup(dockable, null);
}
// if the group is empty, dispose of it so we don't have
// any memory leaks
if(group!=null && group.getDockableCount()==0) {
floatingGroups.remove(group.getName());
group.destroy();
}
}
private String getFloatingGroup(Dockable dockable) {
DockingState info = getDockingState(dockable, false);
return info.getFloatingGroup();
}
private void setFloatingGroup(Dockable dockable, String group) {
DockingState info = getDockingState(dockable, false);
info.setFloatingGroup(group);
}
public boolean isInitialized() {
return restorationLayout!=null;
}
public LayoutNode getRestorationLayout() {
return restorationLayout;
}
public void setRestorationLayout(LayoutNode restorationLayout) {
this.restorationLayout = restorationLayout;
}
void update(LayoutSequence sequence) {
List states = sequence.getDockingStates();
synchronized(dockingInfo) {
for(Iterator it=states.iterator(); it.hasNext();) {
DockingState info = (DockingState)it.next();
dockingInfo.put(info.getDockableId(), info);
}
}
}
}