All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.flexdock.perspective.PerspectiveManager Maven / Gradle / Ivy

The newest version!
/*
 * 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 final PerspectiveManager SINGLETON = new PerspectiveManager();
    private static final DockingStateListener UPDATE_LISTENER = new DockingStateListener();

    private HashMap perspectives = new HashMap();
    private PerspectiveFactory perspectiveFactory;
    private String defaultPerspective;
    private String currentPerspective;
    private PersistenceHandler persistHandler;
    private boolean restoreFloatingOnLoad;
    private String 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().persistHandler = handler;
    }

    public static PersistenceHandler getPersistenceHandler() {
        return getInstance().persistHandler;
    }


    private PerspectiveManager() {
        setDefaultPerspective(EMPTY_PERSPECTIVE);
        loadPerspective(this.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");
        }

        this.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;
        }

        this.perspectives.remove(perspectiveId);

        //set defaultPerspective
        if(this.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) this.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(this.perspectives) {
            ArrayList list = new ArrayList(this.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) {
        this.defaultPerspective = perspectiveId;
    }

    public void setCurrentPerspective(String perspectiveId) {
        setCurrentPerspective(perspectiveId, false);
    }

    public String getCurrentPerspectiveName() {
        return this.currentPerspective;
    }

    private void setCurrentPerspectiveName(String name) {
        this.currentPerspective = "".equals(name)? null: name;
    }

    public void setCurrentPerspective(String perspectiveId, boolean asDefault) {
        perspectiveId = perspectiveId==null? this.defaultPerspective: perspectiveId;
        setCurrentPerspectiveName(perspectiveId);
        if(asDefault) {
            setDefaultPerspective(perspectiveId);
        }
    }

    public Perspective getDefaultPerspective() {
        return getPerspective(this.defaultPerspective);
    }

    public Perspective getCurrentPerspective() {
        return getPerspective(getCurrentPerspectiveName());
    }


    @Override
    public DockingState getDockingState(Dockable dockable) {
        return getCurrentPerspective().getDockingState(dockable);
    }

    @Override
    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);
    }


    @Override
    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 ? this.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 ? this.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? this.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(this.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() {
            @Override
            public void run() {
                cacheLayoutState(perspective, rootPort);
            }
        });
    }

    private void cacheLayoutState(Perspective p, DockingPort port) {
        if(p!=null) {
            p.cacheLayoutState(port);
        }
    }



    @Override
    public LayoutNode createLayout(DockingPort port) {
        return LayoutBuilder.getInstance().createLayout(port);
    }

    @Override
    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() {
            @Override
            public void run() {
                for(int i=0; i




© 2015 - 2025 Weber Informatics LLC | Privacy Policy