/*
* Copyright (c) 2005 FlexDock Development Team. All rights reserved.
*
* 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
* 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.
*/
package org.flexdock.perspective;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.EventQueue;
import java.awt.Window;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import javax.swing.SwingUtilities;
import org.flexdock.docking.Dockable;
import org.flexdock.docking.DockingConstants;
import org.flexdock.docking.DockingManager;
import org.flexdock.docking.DockingPort;
import org.flexdock.docking.event.hierarchy.DockingPortTracker;
import org.flexdock.docking.state.DockingState;
import org.flexdock.docking.state.FloatManager;
import org.flexdock.docking.state.LayoutManager;
import org.flexdock.docking.state.LayoutNode;
import org.flexdock.docking.state.PersistenceException;
import org.flexdock.event.EventManager;
import org.flexdock.event.RegistrationEvent;
import org.flexdock.perspective.event.LayoutEventHandler;
import org.flexdock.perspective.event.PerspectiveEvent;
import org.flexdock.perspective.event.PerspectiveEventHandler;
import org.flexdock.perspective.event.PerspectiveListener;
import org.flexdock.perspective.event.RegistrationHandler;
import org.flexdock.perspective.persist.FilePersistenceHandler;
import org.flexdock.perspective.persist.PersistenceHandler;
import org.flexdock.perspective.persist.PerspectiveModel;
import org.flexdock.util.RootWindow;
import org.flexdock.util.Utilities;
/**
* @author Mateusz Szczap
*/
public class PerspectiveManager implements LayoutManager {
public static final String EMPTY_PERSPECTIVE = "PerspectiveManager.EMPTY_PERSPECTIVE";
public static final String DEFAULT_PERSISTENCE_KEY_VALUE = "perspectiveFile.data";
private static PerspectiveManager SINGLETON = new PerspectiveManager();
private static DockingStateListener UPDATE_LISTENER = new DockingStateListener();
private HashMap m_perspectives = new HashMap();
private PerspectiveFactory perspectiveFactory;
private String m_defaultPerspective;
private String m_currentPerspective;
private PersistenceHandler m_persistHandler;
private boolean restoreFloatingOnLoad;
private String m_defaultPersistenceKey;
static {
initialize();
}
private static void initialize() {
// TODO: Add logic to add and remove event handlers based on whether
// the perspective manager is currently installed. Right now, we're
// just referencing DockingManager.class to ensure the class is properly
// initialized before we add our event handlers. This should be
// called indirectly form within DockingManager, and we should have
// uninstall capability as well.
Class c = DockingManager.class;
EventManager.addHandler(new RegistrationHandler());
EventManager.addHandler(PerspectiveEventHandler.getInstance());
EventManager.addHandler(new LayoutEventHandler());
EventManager.addListener(UPDATE_LISTENER);
String pKey = System.getProperty(DockingConstants.DEFAULT_PERSISTENCE_KEY);
setPersistenceHandler(FilePersistenceHandler.createDefault(DEFAULT_PERSISTENCE_KEY_VALUE));
getInstance().setDefaultPersistenceKey(pKey);
}
public static PerspectiveManager getInstance() {
return SINGLETON;
}
public static void setFactory(PerspectiveFactory factory) {
getInstance().perspectiveFactory = factory;
}
public static void setPersistenceHandler(PersistenceHandler handler) {
getInstance().m_persistHandler = handler;
}
public static PersistenceHandler getPersistenceHandler() {
return getInstance().m_persistHandler;
}
private PerspectiveManager() {
setDefaultPerspective(EMPTY_PERSPECTIVE);
loadPerspective(m_defaultPerspective, (DockingPort)null);
}
public void add(Perspective perspective) {
add(perspective, false);
}
public void add(Perspective perspective, boolean isDefault) {
if (perspective == null) throw new NullPointerException("perspective cannot be null");
m_perspectives.put(perspective.getPersistentId(), perspective);
if(isDefault)
setDefaultPerspective(perspective.getPersistentId());
EventManager.dispatch(new RegistrationEvent(perspective, this, true));
}
public void remove(String perspectiveId) {
if (perspectiveId == null) throw new NullPointerException("perspectiveId cannot be null");
Perspective perspective = getPerspective(perspectiveId);
if (perspective == null)
return;
m_perspectives.remove(perspectiveId);
//set defaultPerspective
if(m_defaultPerspective.equals(perspectiveId))
setDefaultPerspective(EMPTY_PERSPECTIVE);
EventManager.dispatch(new RegistrationEvent(perspective, this, false));
}
public Perspective getPerspective(String perspectiveId) {
if (perspectiveId == null)
return null;
Perspective perspective = (Perspective) m_perspectives.get(perspectiveId);
if(perspective==null) {
perspective = createPerspective(perspectiveId);
if(perspective!=null) {
add(perspective);
}
}
return perspective;
}
public Perspective createPerspective(String perspectiveId) {
if(EMPTY_PERSPECTIVE.equals(perspectiveId))
return new Perspective(EMPTY_PERSPECTIVE, EMPTY_PERSPECTIVE) {
public void load(DockingPort port, boolean defaultSetting) {
// noop
}
};
Perspective p = null;
if (perspectiveFactory != null) {
p = perspectiveFactory.getPerspective(perspectiveId);
//this code ensures that perspective factory create perspectives that return the correct id
//otherwise a NPE appears extremely far away in the code during the first docking operation
if (!p.getPersistentId().equals(perspectiveId)) {
//TODO create a good exception for this
throw new IllegalStateException("Factory created perspective does not match intended ID: " + perspectiveId);
}
}
return p;
}
public Perspective[] getPerspectives() {
synchronized(m_perspectives) {
ArrayList list = new ArrayList(m_perspectives.values());
return (Perspective[])list.toArray(new Perspective[0]);
}
}
public void addListener(PerspectiveListener perspectiveListener) {
EventManager.addListener(perspectiveListener);
}
public void removeListener(PerspectiveListener perspectiveListener) {
EventManager.removeListener(perspectiveListener);
}
public PerspectiveListener[] getPerspectiveListeners() {
return PerspectiveEventHandler.getInstance().getListeners();
}
public void setDefaultPerspective(String perspectiveId) {
m_defaultPerspective = perspectiveId;
}
public void setCurrentPerspective(String perspectiveId) {
setCurrentPerspective(perspectiveId, false);
}
public String getCurrentPerspectiveName() {
return m_currentPerspective;
}
private void setCurrentPerspectiveName(String name) {
m_currentPerspective = "".equals(name)? null: name;
}
public void setCurrentPerspective(String perspectiveId, boolean asDefault) {
perspectiveId = perspectiveId==null? m_defaultPerspective: perspectiveId;
setCurrentPerspectiveName(perspectiveId);
if(asDefault)
setDefaultPerspective(perspectiveId);
}
public Perspective getDefaultPerspective() {
return getPerspective(m_defaultPerspective);
}
public Perspective getCurrentPerspective() {
return getPerspective(getCurrentPerspectiveName());
}
public DockingState getDockingState(Dockable dockable) {
return getCurrentPerspective().getDockingState(dockable);
}
public DockingState getDockingState(String dockable) {
return getCurrentPerspective().getDockingState(dockable);
}
public DockingState getDockingState(Dockable dockable, boolean load) {
return getCurrentPerspective().getDockingState(dockable, load);
}
public DockingState getDockingState(String dockable, boolean load) {
return getCurrentPerspective().getDockingState(dockable, load);
}
public FloatManager getFloatManager() {
return getCurrentPerspective().getLayout();
}
public void reset() {
RootWindow[] windows = DockingManager.getDockingWindows();
if(windows.length!=0)
reset(windows[0].getRootContainer());
}
public void reset(Component window) {
if(window==null) {
reset();
} else {
DockingPort port = DockingManager.getRootDockingPort(window);
reset(port);
}
}
public void reset(DockingPort rootPort) {
loadPerspectiveImpl(getCurrentPerspectiveName(), rootPort, true);
}
/**
* PerspectiveManager#getMainApplicationWindow returns the first
* window where #getOwner == null. This is especially a problem for apps with
* multiple frames. To display a perspective for a specified window
* it is highly recommended to use #reload(Window w) instead of #reload()
* which is the same as DockingManager#restoreLayout().
* You can use #restoreLayout when the application does not need multiple
* independent docking windows.
*/
public void reload(Window w) {
reload(w, true);
}
// use to load parentless frames
public void reload(Window w, boolean reset) {
String current = getCurrentPerspectiveName();
// if the current perspective is null, use the default value
String key = current == null ? m_defaultPerspective : current;
// null-out the current perspective name to force a reload
// otherwise, the loadPerspective() call will short-circuit since
// it'll detect that the requested perspective is already loaded.
setCurrentPerspectiveName(null);
DockingPort port = DockingManager.getRootDockingPort(w);
Perspective[] perspectives = getPerspectives();
for (int i = 0; i < perspectives.length; i++) {
String id = perspectives[i].getPersistentId();
if (!id.equals(EMPTY_PERSPECTIVE)) {
//TODO reset layout, maybe there is a better way
if (reset)
perspectives[i].getLayout().setRestorationLayout(null);
//p.unload();
//p.reset(port);
}
}
loadPerspectiveImpl(key, port, reset);
// if perspective load fails, then rollback the perspective name
// to its previous value (instead of null)
if(!Utilities.isEqual(getCurrentPerspectiveName(), key))
setCurrentPerspectiveName(current);
}
public void restore(Window w) throws IOException, PersistenceException {
reload(w, true);
load();
reload(w, false);
/*DockingPort port = DockingManager.getRootDockingPort(w);
String current = getCurrentPerspectiveName();
String key = current == null ? m_defaultPerspective : current;
setCurrentPerspectiveName(null);
loadPerspectiveImpl(key, port, false);
if(!Utilities.isEqual(getCurrentPerspectiveName(), key))
setCurrentPerspectiveName(current);*/
}
public void reload() {
String current = getCurrentPerspectiveName();
// if the current perspective is null, the use the default value
String key = current==null? m_defaultPerspective: current;
// null-out the current perspective name to force a reload.
// otherwise, the loadPerspective() call will short-circuit since
// it'll detect that the requested perspective is already loaded.
setCurrentPerspectiveName(null);
// load the perspective
loadPerspective(key);
// if the perspective load failed, then rollback the perspective name
// to its previous value (instead of null)
if(!Utilities.isEqual(getCurrentPerspectiveName(), key))
setCurrentPerspectiveName(current);
}
public void loadPerspective() {
loadPerspective(m_defaultPerspective);
}
public void loadPerspectiveAsDefault(String perspectiveId) {
loadPerspectiveAsDefault(perspectiveId, false);
}
public void loadPerspectiveAsDefault(String perspectiveId, boolean reset) {
if(perspectiveId!=null)
setDefaultPerspective(perspectiveId);
loadPerspective(perspectiveId, reset);
}
public void loadPerspective(String perspectiveId) {
loadPerspective(perspectiveId, false);
}
public void loadPerspective(String perspectiveId, boolean reset) {
RootWindow window = getMainApplicationWindow();
if(window!=null) {
loadPerspective(perspectiveId, window.getRootContainer(), reset);
return;
}
DockingPort rootPort = findMainDockingPort();
if(rootPort!=null)
loadPerspective(perspectiveId, rootPort, reset);
}
public void loadPerspective(String perspectiveId, Component window) {
loadPerspective(perspectiveId, window, false);
}
public void loadPerspective(String perspectiveId, Component window, boolean reset) {
if(window==null) {
loadPerspective(perspectiveId, reset);
return;
}
DockingPort port = DockingManager.getRootDockingPort(window);
loadPerspective(perspectiveId, port, reset);
}
public void loadPerspective(String perspectiveId, DockingPort rootPort) {
loadPerspective(perspectiveId, rootPort, false);
}
public void loadPerspective(String perspectiveId, DockingPort rootPort, boolean reset) {
if(perspectiveId==null || perspectiveId.equals(getCurrentPerspectiveName()))
return;
loadPerspectiveImpl(perspectiveId, rootPort, reset);
}
private void loadPerspectiveImpl(String perspectiveId, final DockingPort rootPort, boolean reset) {
if(perspectiveId==null)
return;
Perspective current = getCurrentPerspective();
final Perspective perspective = getPerspective(perspectiveId);
// remember the current layout state so we'll be able to
// restore when we switch back
if(current!=null) {
cacheLayoutState(current, rootPort);
current.unload();
}
// if the new perspective isn't available, then we're done
if(perspective==null)
return;
synchronized(this) {
setCurrentPerspectiveName(perspectiveId);
if(reset) {
perspective.reset(rootPort);
EventManager.dispatch(new PerspectiveEvent(perspective, current,
PerspectiveEvent.RESET));
} else {
perspective.load(rootPort);
EventManager.dispatch(new PerspectiveEvent(perspective, current,
PerspectiveEvent.CHANGED));
}
}
EventQueue.invokeLater(new Runnable() {
public void run() {
cacheLayoutState(perspective, rootPort);
}
});
}
private void cacheLayoutState(Perspective p, DockingPort port) {
if(p!=null)
p.cacheLayoutState(port);
}
public LayoutNode createLayout(DockingPort port) {
return LayoutBuilder.getInstance().createLayout(port);
}
public boolean display(Dockable dockable) {
return RestorationManager.getInstance().restore(dockable);
}
static void setDockingStateListening(boolean enabled) {
UPDATE_LISTENER.setEnabled(enabled);
}
static boolean isDockingStateListening() {
return UPDATE_LISTENER.isEnabled();
}
static void clear(DockingPort port) {
if(port!=null) {
boolean currState = isDockingStateListening();
setDockingStateListening(false);
port.clear();
setDockingStateListening(currState);
}
}
static void updateDockingStates(final Dockable[] dockables) {
if(dockables==null)
return;
EventQueue.invokeLater(new Runnable() {
public void run() {
for(int i=0; i<dockables.length; i++) {
UPDATE_LISTENER.updateState(dockables[i]);
}
}
});
}
public synchronized boolean store() throws IOException, PersistenceException {
return store(null);
}
public synchronized boolean store(String persistenceKey) throws IOException, PersistenceException {
if(m_persistHandler==null)
return false;
DockingPort rootPort = findMainDockingPort();
cacheLayoutState(getCurrentPerspective(), rootPort);
Perspective[] items = getPerspectives();
for(int i=0; i<items.length; i++) {
items[i] = (Perspective)items[i].clone();
}
PerspectiveModel info = new PerspectiveModel(m_defaultPerspective, getCurrentPerspectiveName(), items);
String pKey = persistenceKey==null? m_defaultPersistenceKey: persistenceKey;
return m_persistHandler.store(pKey, info);
}
public synchronized boolean load() throws IOException, PersistenceException {
return load(null);
}
public synchronized boolean load(String persistenceKey) throws IOException, PersistenceException {
if(m_persistHandler==null)
return false;
String pKey = persistenceKey==null? m_defaultPersistenceKey: persistenceKey;
PerspectiveModel info = m_persistHandler.load(pKey);
if(info==null)
return false;
Perspective[] perspectives = info.getPerspectives();
m_perspectives.clear();
for(int i=0; i<perspectives.length; i++) {
add(perspectives[i]);
}
setDefaultPerspective(info.getDefaultPerspective());
setCurrentPerspectiveName(info.getCurrentPerspective());
return true;
}
public static boolean isRestoreFloatingOnLoad() {
return getInstance().restoreFloatingOnLoad;
}
public static void setRestoreFloatingOnLoad(boolean restoreFloatingOnLoad) {
getInstance().restoreFloatingOnLoad = restoreFloatingOnLoad;
}
//FIXME returns wrong window (first found) for multiple frames
public static RootWindow getMainApplicationWindow() {
RootWindow[] windows = DockingManager.getDockingWindows();
// if the DockingManager couldn't resolve any windows using the
// standard mechanism, we can try our own custom search
if(windows.length==0)
windows = resolveDockingWindows();
// TODO: fix this code to keep track of the proper dialog owner
RootWindow window = null;
for(int i=0; i<windows.length; i++) {
window = windows[i];
if(window.getOwner()==null)
break;
}
return window;
}
private static RootWindow[] resolveDockingWindows() {
// locate all the root dockingports
Set rootPorts = DockingPortTracker.getRootDockingPorts();
ArrayList windows = new ArrayList(rootPorts.size());
// for each dockingPort, resolve its root window
for(Iterator it=rootPorts.iterator(); it.hasNext();) {
DockingPort port = (DockingPort)it.next();
RootWindow window = RootWindow.getRootContainer((Component)port);
if(window!=null)
windows.add(window);
}
return (RootWindow[])windows.toArray(new RootWindow[0]);
}
public static DockingPort getMainDockingPort() {
RootWindow window = getMainApplicationWindow();
return window==null? null: DockingManager.getRootDockingPort(window.getRootContainer());
}
public boolean restore(boolean loadFromStorage) throws IOException, PersistenceException {
boolean loaded = loadFromStorage? load(): true;
reload();
return loaded;
}
public String getDefaultPersistenceKey() {
return m_defaultPersistenceKey;
}
public void setDefaultPersistenceKey(String key) {
m_defaultPersistenceKey = key;
}
private DockingPort findMainDockingPort() {
Set rootPorts = DockingPortTracker.getRootDockingPorts();
DockingPort rootPort = null;
for(Iterator it=rootPorts.iterator(); it.hasNext();) {
DockingPort port = (DockingPort)it.next();
Window win = SwingUtilities.getWindowAncestor((Component)port);
if(win instanceof Dialog)
continue;
rootPort = port;
break;
}
return rootPort;
}
}