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

ModernDocking.api.DockingAPI Maven / Gradle / Ivy

There is a newer version: 0.11.6
Show newest version
package ModernDocking.api;

import ModernDocking.Dockable;
import ModernDocking.DockableStyle;
import ModernDocking.DockingRegion;
import ModernDocking.event.DockingListener;
import ModernDocking.event.MaximizeListener;
import ModernDocking.exception.DockableRegistrationFailureException;
import ModernDocking.exception.NotDockedException;
import ModernDocking.floating.FloatListener;
import ModernDocking.internal.*;
import ModernDocking.layouts.WindowLayout;
import ModernDocking.ui.ToolbarLocation;

import javax.swing.*;
import java.awt.*;
import java.util.*;

/**
 * Single instance of the docking framework. Useful when a single JVM is to host multiple instances of an application
 * and docking should be handled separately for each of them
 */
public class DockingAPI {
    private final DockingInternal internals = new DockingInternal(this);

    // the applications main frame
    private final Window mainWindow;

    // this may look unused, but we need to create an instance of it to make it work
    private final ActiveDockableHighlighter activeDockableHighlighter = new ActiveDockableHighlighter(this);

    private final AppStatePersister appStatePersister = new AppStatePersister(this);

    // map of all the root panels in the application
    private  final Map rootPanels = new HashMap<>();

    private final AppStateAPI appState = new AppStateAPI(this);
    private final DockingStateAPI dockingState = new DockingStateAPI(this);

    private final LayoutPersistenceAPI layoutPersistence = new LayoutPersistenceAPI(this);

    private boolean deregistering = false;

    public AppStateAPI getAppState() {
        return appState;
    }

    public DockingStateAPI getDockingState() {
        return dockingState;
    }

    public LayoutPersistenceAPI getLayoutPersistence() {
        return layoutPersistence;
    }

    protected DockingAPI(Window mainWindow) {
        this.mainWindow = mainWindow;

        // listen for L&F changes so that we can update dockable panels properly when not displayed
        UIManager.addPropertyChangeListener(e -> {
            if ("lookAndFeel".equals(e.getPropertyName())) {
                SwingUtilities.invokeLater(internals::updateLAF);
            }
        });
    }

    /**
     * Get a map of RootDockingPanels to their Windows
     *
     * @return map of root panels
     */
    public Map getRootPanels() {
        return rootPanels;
    }

    /**
     * Get the main window instance
     *
     * @return main window
     */
    public Window getMainWindow() {
        return mainWindow;
    }

    /**
     * register a dockable with the framework
     *
     * @param dockable Dockable to register
     */
    public void registerDockable(Dockable dockable) {
        internals.registerDockable(dockable);
    }

    /**
     * Dockables must be deregistered so it can be properly disposed
     *
     * @param dockable Dockable to deregister
     */
    public void deregisterDockable(Dockable dockable) {
        deregistering = true;

        try {
            // make sure we undock the dockable from the UI before deregistering
            undock(dockable);

            internals.deregisterDockable(dockable);
        }
        finally {
            deregistering = false;
        }
    }

    /**
     * Deregister all dockables that have been registered. This action will also undock all dockables.
     */
    public void deregisterAllDockables() {
        deregistering = true;

        try {
            Set windows = new HashSet<>(getRootPanels().keySet());

            for (Window window : windows) {
                DockingComponentUtils.undockComponents(this, window);

                // only dispose this window if we created it
                if (window instanceof FloatingFrame) {
                    window.dispose();
                }
            }

            for (Dockable dockable : internals.getDockables()) {
                deregisterDockable(dockable);
            }
        }
        finally {
            deregistering = false;
        }
    }

    /**
     * registration function for DockingPanel
     *
     * @param panel Panel to register
     * @param parent The parent frame of the panel
     */
    public void registerDockingPanel(RootDockingPanelAPI panel, JFrame parent) {
        if (rootPanels.containsKey(parent)) {
            throw new DockableRegistrationFailureException("RootDockingPanel already registered for frame: " + parent);
        }

        if (rootPanels.containsValue(panel)) {
            // we already checked above that we have this value
            //noinspection OptionalGetWithoutIsPresent
            Window window = rootPanels.entrySet().stream()
                    .filter(entry -> entry.getValue() == panel)
                    .findFirst()
                    .map(Map.Entry::getKey)
                    .get();

            throw new DockableRegistrationFailureException("RootDockingPanel already registered for window: " + window);
        }

        rootPanels.put(parent, panel);
        FloatListener.registerDockingWindow(this, parent, panel);

        appStatePersister.addWindow(parent);
    }

    /**
     * Register a RootDockingPanel
     *
     * @param panel RootDockingPanel to register
     * @param parent The parent JDialog of the panel
     */
    public void registerDockingPanel(RootDockingPanelAPI panel, JDialog parent) {
        if (rootPanels.containsKey(parent)) {
            throw new DockableRegistrationFailureException("RootDockingPanel already registered for frame: " + parent);
        }

        if (rootPanels.containsValue(panel)) {
            // we already checked above that we have this value
            //noinspection OptionalGetWithoutIsPresent
            Window window = rootPanels.entrySet().stream()
                    .filter(entry -> entry.getValue() == panel)
                    .findFirst()
                    .map(Map.Entry::getKey)
                    .get();

            throw new DockableRegistrationFailureException("RootDockingPanel already registered for window: " + window);
        }

        rootPanels.put(parent, panel);
        FloatListener.registerDockingWindow(this, parent, panel);

        appStatePersister.addWindow(parent);
    }

    /**
     * Deregister a docking root panel
     *
     * @param parent The parent of the panel that we're deregistering
     */
    public void deregisterDockingPanel(Window parent) {
        if (rootPanels.containsKey(parent)) {
            RootDockingPanelAPI root = rootPanels.get(parent);

            DockingComponentUtils.undockComponents(this, root);
        }

        rootPanels.remove(parent);
        FloatListener.deregisterDockingWindow(parent);

        appStatePersister.removeWindow(parent);
    }

    /**
     * Deregister all registered panels. Additionally, dispose any windows created by Modern Docking.
     */
    public void deregisterAllDockingPanels() {
        Set windows = new HashSet<>(getRootPanels().keySet());

        for (Window window : windows) {
            deregisterDockingPanel(window);

            // only dispose this window if we created it
            if (window instanceof FloatingFrame) {
                window.dispose();
            }
        }
    }

    /**
     * allows the user to configure pinning per window. by default pinning is only enabled on the frames the docking framework creates
     *
     * @param window The window to configure pinning on
     * @param layer The layout to use for pinning in the JLayeredPane
     * @param allow Whether pinning is allowed on this Window
     */
    public void configurePinning(Window window, int layer, boolean allow) {
        if (!rootPanels.containsKey(window)) {
            throw new DockableRegistrationFailureException("No root panel for window has been registered.");
        }

        RootDockingPanelAPI root = DockingComponentUtils.rootForWindow(this, window);
        root.setPinningSupported(allow);
        root.setPinningLayer(layer);
    }

    /**
     * Check if pinning is allowed for a dockable
     *
     * @param dockable Dockable to check
     * @return Whether the dockable can be pinned
     */
    public boolean pinningAllowed(Dockable dockable) {
        RootDockingPanelAPI root = DockingComponentUtils.rootForWindow(this, DockingComponentUtils.findWindowForDockable(this, dockable));

        return dockable.isPinningAllowed() && root.isPinningSupported();
    }

    /**
     * docks a dockable to the center of the given window
     * 

* NOTE: This will only work if the window root docking node is empty. Otherwise, this does nothing. * * @param persistentID The persistentID of the dockable to dock * @param window The window to dock into */ public void dock(String persistentID, Window window) { dock(internals.getDockable(persistentID), window, DockingRegion.CENTER); } /** * docks a dockable to the center of the given window *

* NOTE: This will only work if the window root docking node is empty. Otherwise, this does nothing. * * @param dockable The dockable to dock * @param window The window to dock into */ public void dock(Dockable dockable, Window window) { dock(dockable, window, DockingRegion.CENTER); } /** * docks a dockable into the specified region of the root of the window with 25% divider proportion * * @param persistentID The persistentID of the dockable to dock * @param window The window to dock into * @param region The region to dock into */ public void dock(String persistentID, Window window, DockingRegion region) { dock(internals.getDockable(persistentID), window, region, 0.25); } /** * docks a dockable into the specified region of the root of the window with 25% divider proportion *

* NOTE: Nothing will be done if docking to the CENTER of the window and the window root panel is not empty * * @param dockable The dockable to dock * @param window The window to dock into * @param region The region to dock into */ public void dock(Dockable dockable, Window window, DockingRegion region) { RootDockingPanelAPI root = rootPanels.get(window); if (root == null) { throw new DockableRegistrationFailureException("Window does not have a RootDockingPanel: " + window); } if (!root.isEmpty() && region == DockingRegion.CENTER) { // can't dock here, so stop return; } dock(dockable, window, region, 0.25); } /** * docks a dockable into the specified region of the window with the specified divider proportion * * @param persistentID The persistentID of the dockable to dock * @param window The window to dock into * @param region The region to dock into * @param dividerProportion The proportion to use if docking in a split pane */ public void dock(String persistentID, Window window, DockingRegion region, double dividerProportion) { dock(internals.getDockable(persistentID), window, region, dividerProportion); } /** * docks a dockable into the specified region of the window with the specified divider proportion * * @param dockable The dockable to dock * @param window The window to dock into * @param region The region to dock into * @param dividerProportion The proportion to use if docking in a split pane */ public void dock(Dockable dockable, Window window, DockingRegion region, double dividerProportion) { RootDockingPanelAPI root = rootPanels.get(window); if (root == null) { throw new DockableRegistrationFailureException("Window does not have a RootDockingPanel: " + window); } // if the source is already docked we need to undock it before docking it again, otherwise we might steal it from its UI parent if (isDocked(dockable)) { DockableWrapper wrapper = internals.getWrapper(dockable); wrapper.getParent().undock(dockable); // don't fire an undocked event for this one } root.dock(dockable, region, dividerProportion); internals.getWrapper(dockable).setWindow(window); // fire a docked event when the component is actually added DockingListeners.fireDockedEvent(dockable); appState.persist(); } /** * docks the target to the source in the specified region with 50% divider proportion * * @param sourcePersistentID The persistentID of the source dockable to dock the target dockable to * @param targetPersistentID The persistentID of the target dockable * @param region The region on the source dockable to dock into */ public void dock(String sourcePersistentID, String targetPersistentID, DockingRegion region) { dock(internals.getDockable(sourcePersistentID), internals.getDockable(targetPersistentID), region, 0.5); } /** * docks the target to the source in the specified region with 50% divider proportion * * @param sourcePersistentID The persistentID of the source dockable to dock the target dockable to * @param target The target dockable * @param region The region on the source dockable to dock into */ public void dock(String sourcePersistentID, Dockable target, DockingRegion region) { dock(internals.getDockable(sourcePersistentID), target, region, 0.5); } /** * docks the target to the source in the specified region with 50% divider proportion * * @param source The source dockable to dock the target dockable to * @param targetPersistentID The persistentID of the target dockable * @param region The region on the source dockable to dock into */ public void dock(Dockable source, String targetPersistentID, DockingRegion region) { dock(source, internals.getDockable(targetPersistentID), region, 0.5); } /** * docks the target to the source in the specified region with 50% divider proportion * * @param source The source dockable to dock the target dockable to * @param target The target dockable * @param region The region on the source dockable to dock into */ public void dock(Dockable source, Dockable target, DockingRegion region) { dock(source, target, region, 0.5); } /** * docks the target to the source in the specified region with the specified divider proportion * * @param sourcePersistentID The persistentID of the source dockable to dock the target dockable to * @param targetPersistentID The persistentID of the target dockable * @param region The region on the source dockable to dock into * @param dividerProportion The proportion to use if docking in a split pane */ public void dock(String sourcePersistentID, String targetPersistentID, DockingRegion region, double dividerProportion) { dock(internals.getDockable(sourcePersistentID), internals.getDockable(targetPersistentID), region, dividerProportion); } /** * docks the target to the source in the specified region with the specified divider proportion * * @param source The source dockable to dock the target dockable to * @param target The target dockable * @param region The region on the source dockable to dock into * @param dividerProportion The proportion to use if docking in a split pane */ public void dock(Dockable source, Dockable target, DockingRegion region, double dividerProportion) { if (!isDocked(target)) { throw new NotDockedException(target); } // if the source is already docked we need to undock it before docking it again, otherwise we might steal it from its UI parent if (isDocked(source)) { DockableWrapper wrapper = internals.getWrapper(source); wrapper.getParent().undock(source); } DockableWrapper wrapper = internals.getWrapper(target); wrapper.getParent().dock(source, region, dividerProportion); internals.getWrapper(source).setWindow(wrapper.getWindow()); DockingListeners.fireDockedEvent(source); appState.persist(); } /** * create a new FloatingFrame window for the given dockable, undock it from its current frame (if there is one) and dock it into the new frame * * @param persistentID The persistent ID of the dockable to float in a new window */ public void newWindow(String persistentID) { newWindow(internals.getDockable(persistentID)); } /** * create a new FloatingFrame window for the given dockable, undock it from its current frame (if there is one) and dock it into the new frame * * @param dockable The dockable to float in a new window */ public void newWindow(Dockable dockable) { DisplayPanel displayPanel = internals.getWrapper(dockable).getDisplayPanel(); if (isDocked(dockable)) { Point location = displayPanel.getLocationOnScreen(); Dimension size = displayPanel.getSize(); newWindow(dockable, location, size); } else { FloatingFrame frame = new FloatingFrame(this); dock(dockable, frame); frame.pack(); frame.setLocationRelativeTo(getMainWindow()); } } /** * Create a new FloatingFrame window for the given dockable, undock it from its current frame (if there is one) and dock it into the new frame * * @param persistentID The persistent ID of the dockable to float in a new window * @param location The screen location to display the new frame at * @param size The size of the new frame */ public void newWindow(String persistentID, Point location, Dimension size) { newWindow(internals.getDockable(persistentID), location, size); } /** * Create a new FloatingFrame window for the given dockable, undock it from its current frame (if there is one) and dock it into the new frame * * @param dockable The dockable to float in a new window * @param location The screen location to display the new frame at * @param size The size of the new frame */ public void newWindow(Dockable dockable, Point location, Dimension size) { FloatingFrame frame = new FloatingFrame(this, dockable, location, size, JFrame.NORMAL); undock(dockable); dock(dockable, frame); SwingUtilities.invokeLater(() -> bringToFront(dockable)); } /** * bring the specified dockable to the front if it is in a tabbed panel * * @param dockable Dockable to bring to the front */ public void bringToFront(Dockable dockable) { if (!isDocked(dockable)) { throw new NotDockedException(dockable); } Window window = DockingComponentUtils.findWindowForDockable(this, dockable); window.setAlwaysOnTop(true); window.setAlwaysOnTop(false); if (internals.getWrapper(dockable).getParent() instanceof DockedTabbedPanel) { DockedTabbedPanel tabbedPanel = (DockedTabbedPanel) internals.getWrapper(dockable).getParent(); tabbedPanel.bringToFront(dockable); } } /** * undock a dockable * * @param persistentID The persistentID of the dockable to undock */ public void undock(String persistentID) { undock(internals.getDockable(persistentID)); } /** * undock a dockable * * @param dockable The dockable to undock */ public void undock(Dockable dockable) { if (!isDocked(dockable)) { // nothing to undock return; } Window window = DockingComponentUtils.findWindowForDockable(this, dockable); RootDockingPanelAPI root = DockingComponentUtils.rootForWindow(this, window); DockableWrapper wrapper = internals.getWrapper(dockable); wrapper.setRoot(root); if (isUnpinned(dockable)) { root.undock(dockable); wrapper.setParent(null); wrapper.setUnpinned(false); } else { wrapper.getParent().undock(dockable); } wrapper.setWindow(null); DockingListeners.fireUndockedEvent(dockable); // make sure that can dispose this window, and we're not floating the last dockable in it if (window != null && root != null && canDisposeWindow(window) && root.isEmpty() && !FloatListener.isFloating) { deregisterDockingPanel(window); window.dispose(); } appState.persist(); // force this dockable to dock again if we're not floating it if (!dockable.isClosable() && !FloatListener.isFloating && !deregistering) { dock(dockable, mainWindow); } } /** * check if a dockable is currently docked * * @param persistentID The persistentID of the dockable to check * @return Whether the dockable is docked */ public boolean isDocked(String persistentID) { return isDocked(internals.getDockable(persistentID)); } /** * check if a dockable is currently docked * * @param dockable The dockable to check * @return Whether the dockable is docked */ public boolean isDocked(Dockable dockable) { return internals.getWrapper(dockable).getParent() != null; } /** * check if a dockable is currently in the unpinned state * * @param persistentID The persistentID of the dockable to check * @return Whether the dockable is unpinned */ public boolean isUnpinned(String persistentID) { return isUnpinned(internals.getDockable(persistentID)); } /** * check if a dockable is currently in the unpinned state * * @param dockable The dockable to check * @return Whether the dockable is unpinned */ public boolean isUnpinned(Dockable dockable) { return internals.getWrapper(dockable).isUnpinned(); } /** * check if the window can be disposed. Windows can be disposed if they are not the main window and are not maximized * * @param window Window to check * @return Boolean indicating if the specified Window can be disposed */ public boolean canDisposeWindow(Window window) { // don't dispose of any docking windows that are JDialogs if (window instanceof JDialog) { return false; } if (dockingState.maximizeRestoreLayout.containsKey(window)) { return false; } // only dispose this window if we created it return window instanceof FloatingFrame; } /** * checks if a dockable is currently maximized * * @param dockable The dockable to check * @return Whether the dockable is maximized */ public boolean isMaximized(Dockable dockable) { return internals.getWrapper(dockable).isMaximized(); } /** * maximizes a dockable * * @param dockable Dockable to maximize */ public void maximize(Dockable dockable) { Window window = DockingComponentUtils.findWindowForDockable(this, dockable); RootDockingPanelAPI root = DockingComponentUtils.rootForWindow(this, window); // can only maximize one panel per root if (!dockingState.maximizeRestoreLayout.containsKey(window) && root != null) { internals.getWrapper(dockable).setMaximized(true); DockingListeners.fireMaximizeEvent(dockable, true); WindowLayout layout = dockingState.getWindowLayout(window); layout.setMaximizedDockable(dockable.getPersistentID()); dockingState.maximizeRestoreLayout.put(window, layout); DockingComponentUtils.undockComponents(this, root); dock(dockable, window); } } /** * minimize a dockable if it is currently maximized * * @param dockable Dockable to minimize */ public void minimize(Dockable dockable) { Window window = DockingComponentUtils.findWindowForDockable(this, dockable); // can only minimize if already maximized if (dockingState.maximizeRestoreLayout.containsKey(window)) { internals.getWrapper(dockable).setMaximized(false); DockingListeners.fireMaximizeEvent(dockable, false); dockingState.restoreWindowLayout(window, dockingState.maximizeRestoreLayout.get(window)); dockingState.maximizeRestoreLayout.remove(window); internals.fireDockedEventForFrame(window); } } /** * pin a dockable. only valid if the dockable is unpinned * * @param dockable Dockable to pin */ public void pinDockable(Dockable dockable) { Window window = DockingComponentUtils.findWindowForDockable(this, dockable); RootDockingPanelAPI root = DockingComponentUtils.rootForWindow(this, window); if (internals.getWrapper(dockable).isUnpinned()) { root.setDockablePinned(dockable); internals.getWrapper(dockable).setUnpinned(false); DockingListeners.firePinnedEvent(dockable); } } /** * unpin a dockable. only valid if the dockable is pinned * @param dockable Dockable to unpin */ public void unpinDockable(Dockable dockable) { if (isUnpinned(dockable)) { return; } Window window = DockingComponentUtils.findWindowForDockable(this, dockable); RootDockingPanelAPI root = DockingComponentUtils.rootForWindow(this, window); Component component = (Component) dockable; Point posInFrame = component.getLocation(); SwingUtilities.convertPointToScreen(posInFrame, component.getParent()); SwingUtilities.convertPointFromScreen(posInFrame, root); posInFrame.x += component.getWidth() / 2; posInFrame.y += component.getHeight() / 2; boolean allowedSouth = dockable.getPinningStyle() == DockableStyle.BOTH || dockable.getPinningStyle() == DockableStyle.HORIZONTAL; int westDist = posInFrame.x; int eastDist = window.getWidth() - posInFrame.x; int southDist = window.getHeight() - posInFrame.y; boolean east = eastDist <= westDist; boolean south = southDist < westDist && southDist < eastDist; ToolbarLocation location; if (south && allowedSouth) { location = ToolbarLocation.SOUTH; } else if (east) { location = ToolbarLocation.EAST; } else { location = ToolbarLocation.WEST; } unpinDockable(dockable, location); } /** * unpin a dockable. only valid if the dockable is pinned * @param dockable Dockable to unpin * @param location Toolbar location to unpin the dockable to */ public void unpinDockable(Dockable dockable, ToolbarLocation location) { Window window = DockingComponentUtils.findWindowForDockable(this, dockable); RootDockingPanelAPI root = DockingComponentUtils.rootForWindow(this, window); unpinDockable(dockable, location, window, root); } /** * unpin a dockable. only valid if the dockable is pinned * @param dockable Dockable to unpin * @param location Toolbar location to unpin the dockable to */ public void unpinDockable(Dockable dockable, ToolbarLocation location, Window window, RootDockingPanelAPI root) { if (isUnpinned(dockable)) { return; } Component component = (Component) dockable; Point posInFrame = component.getLocation(); SwingUtilities.convertPointToScreen(posInFrame, component.getParent()); SwingUtilities.convertPointFromScreen(posInFrame, root); posInFrame.x += component.getWidth() / 2; posInFrame.y += component.getHeight() / 2; if (!root.isPinningSupported()) { return; } undock(dockable); // reset the window, undocking the dockable sets it to null internals.getWrapper(dockable).setWindow(window); internals.getWrapper(dockable).setUnpinned(true); root.setDockableUnpinned(dockable, location); DockingListeners.fireUnpinnedEvent(dockable); DockingListeners.fireHiddenEvent(dockable); } /** * display a dockable * * @param persistentID The persistentID of the dockable to display */ public void display(String persistentID) { display(internals.getDockable(persistentID)); } /** * Display a dockable *

* if the dockable is already docked, then bringToFront is called. * if it is not docked, then dock is called, docking it with dockables of the same type * * @param dockable The dockable to display */ public void display(Dockable dockable) { if (isDocked(dockable)) { bringToFront(dockable); } else { // go through all the dockables and find the first one that is the same type Optional firstOfType = DockingComponentUtils.findFirstDockableOfType(this, dockable.getType()); if (firstOfType.isPresent()) { dock(dockable, firstOfType.get(), DockingRegion.CENTER); } else { // if we didn't find any dockables of the same type, we'll dock to north // TODO this is a bit boring. we should have a better way to do this dock(dockable, mainWindow, DockingRegion.NORTH); } } } /** * update the tab text on a dockable if it is in a tabbed panel * * @param persistentID The persistentID of the dockable to update */ public void updateTabInfo(String persistentID) { updateTabInfo(internals.getDockable(persistentID)); } /** * update the tab text on a dockable if it is in a tabbed panel * * @param dockable The dockable to update */ public void updateTabInfo(Dockable dockable) { if (!isDocked(dockable)) { // if the dockable isn't docked then we don't have to do anything to update its tab text return; } DockableWrapper wrapper = internals.getWrapper(dockable); wrapper.getHeaderUI().update(); DockingPanel parent = wrapper.getParent(); if (parent instanceof DockedTabbedPanel) { ((DockedTabbedPanel) parent).updateTabInfo(dockable); } } /** * Add a new maximize listener. Will be called when a dockable is maximized * * @param listener Listener to add */ public void addMaximizeListener(MaximizeListener listener) { DockingListeners.addMaximizeListener(listener); } /** * Remove a previously added maximize listener. No-op if the listener isn't in the list * * @param listener Listener to remove */ public void removeMaximizeListener(MaximizeListener listener) { DockingListeners.removeMaximizeListener(listener); } public void addDockingListener(DockingListener listener) { DockingListeners.addDockingListener(listener); } public void removeDockingListener(DockingListener listener) { DockingListeners.removeDockingListener(listener); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy