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

org.flexdock.docking.defaults.DefaultDockingStrategy Maven / Gradle / Ivy

The newest version!
/*
 * 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.docking.defaults;

import java.awt.Component;
import java.awt.Container;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Map;

import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
import javax.swing.plaf.basic.BasicSplitPaneDivider;
import javax.swing.plaf.basic.BasicSplitPaneUI;

import org.flexdock.docking.Dockable;
import org.flexdock.docking.DockingConstants;
import org.flexdock.docking.DockingManager;
import org.flexdock.docking.DockingPort;
import org.flexdock.docking.DockingStrategy;
import org.flexdock.docking.RegionChecker;
import org.flexdock.docking.drag.DragManager;
import org.flexdock.docking.drag.DragOperation;
import org.flexdock.docking.event.DockingEvent;
import org.flexdock.docking.floating.frames.DockingFrame;
import org.flexdock.docking.floating.frames.FloatingDockingPort;
import org.flexdock.docking.state.FloatManager;
import org.flexdock.event.EventManager;
import org.flexdock.util.DockingUtility;
import org.flexdock.util.RootWindow;
import org.flexdock.util.SwingUtility;

/**
 * @author Christopher Butler
 */
public class DefaultDockingStrategy implements DockingStrategy,
    DockingConstants {

    public static final String PREFERRED_PROPORTION = "DefaultDockingStrategy.PREFERRED_PROPORTION";

    private static double defaultResizeWeight = -1;
    private static boolean constantPercent;

    /**
     * Returns the specified {@code Dockable's} sibling {@code Dockable} within
     * the current docking layout. This method checks the parent
     * {@code DockingPort} of a given {@code Dockable} to see if it is split
     * equally with another {@code Dockable}. If so, the immediate sibling
     * {@code Dockable} is returned. If there are more than two
     * {@code Dockables} within the split layout, then the closest sibling
     * region is determined and this method dispatches to
     * {@code getSibling(Dockable dockable, String region)}.
     * 

* If the specified {@code Dockable} is {@code null}, or there are no * siblings available in the docking layout, then this methdo returns a * {@code null} reference. If the specified {@code Dockable} is not * currently docked within a {@code DockingPort}, then this method returns * a {@code null} reference. * * @param dockable * the {@code Dockable} whose sibling is to be returned * @return the sibling of the specified {@code Dockable} within the current * docking layout. * @see Dockable#getDockingPort() * @see #getSibling(Dockable, String) */ public static Dockable getSibling(Dockable dockable) { if (dockable == null) { return null; } DockingPort port = dockable.getDockingPort(); String startRegion = findRegion(dockable.getComponent()); String region = DockingUtility.flipRegion(startRegion); Dockable sibling = findDockable(port, dockable.getComponent(), region, startRegion); return sibling; } /** * Returns the sibling {@code Dockable} relative to the specified * {@code Dockable}'s supplied region in the current docking layout. If * {@code dockable} is {@code null} or {@code region} is either invalid or * equal to {@code CENTER_REGION}, then this method returns a {@code null} * reference. *

* If the specified {@code Dockable} is in a {@code DockingPort} that * equally splits the layout between two {@code Dockable}s in a fashion that * matches up with the specified region, then the immediate sibling * {@code Dockable} is returned. Otherwise, a fuzzy search is performed * throughout the docking layout for a {@code Dockable} that "looks like" it * is docked to the supplied region of the specified {@code Dockable} from a * visual standpoint. *

* For instance, a docking layout may consist of four quadrants {@code dockable1} * (top-left), {@code dockable2} (top-right), {@code dockable3} * (bottom-left) and {@code dockable4} (bottom-right). The layout is built * by docking {@code dockable2} to the {@code EAST_REGION} of {@code dockable1}, * {@code dockable3} to the {@code SOUTH_REGION} of {@code dockable1}, and * {@code dockable4} to the {@code SOUTH_REGION} of {@code dockable2}. * Within this layout, {@code dockable1} and {@code dockable3} are immediate * siblings, as are {@code dockable2} and {@code dockable4}. Thus, * requesting sibling NORTH_REGION of {@code dockable3} will easily yield * {@code dockable1}. However, {@code dockable3} has no immediate * {@code EAST_REGION} sibling. In this case, a fuzzy search through the * layout is performed to determine the visual sibling, and this method * returns {@code dockable4}. Likewise, this method will return a * {@code null} reference for the {@code WEST_REGION} sibling of * {@code dockable3}, since there are no {@code Dockable}s in the visual layout * to the west of this {@code Dockable}. * * @param dockable * the {@code Dockable} whose sibling is to be returned * @param region * the region of the specified {@code Dockable} whose visual * sibling is to be returned * @return the {@code Dockable} in the supplied region relative to the * specified {@code Dockable} */ public static Dockable getSibling(Dockable dockable, String region) { if (dockable == null || !DockingManager.isValidDockingRegion(region) || CENTER_REGION.equals(region)) { return null; } DockingPort port = dockable.getDockingPort(); String startRegion = findRegion(dockable.getComponent()); Dockable sibling = findDockable(port, dockable.getComponent(), region, startRegion); return sibling; } private static Dockable findDockable(DockingPort port, Component self, String region, String startRegion) { if (port == null) { return null; } Component docked = port.getDockedComponent(); // if we're not a split port, then there is no concept of 'outer // regions'. // jump up a level to find the parent split port if (!(docked instanceof JSplitPane)) { DockingPort superPort = DockingManager .getDockingPort((Component) port); return findDockable(superPort, self, region, startRegion); } Component sibling = port.getComponent(region); if (sibling == self) { if (!(self instanceof JSplitPane)) { DockingPort superPort = DockingManager .getDockingPort((Component) port); return findDockable(superPort, self, region, startRegion); } return null; } if (sibling instanceof JSplitPane) { // go one level deeper DockingPort subPort = DockingManager.getDockingPort(sibling); Component other = port.getComponent(DockingUtility .flipRegion(region)); String subRegion = findSubRegion((JSplitPane) sibling, other, region, startRegion); return findDockable(subPort, self, subRegion, startRegion); } // if we have no direct sibling in the specified region, the jump // up a level. if (sibling == null) { DockingPort superPort = DockingManager .getDockingPort((Component) port); self = port.getDockedComponent(); return findDockable(superPort, self, region, startRegion); } return DockingManager.getDockable(sibling); } private static String findSubRegion(JSplitPane split, Component other, String targetRegion, String baseRegion) { String region = DockingUtility.translateRegionAxis(split, targetRegion); if (!(other instanceof JSplitPane)) { return region; } boolean translated = !targetRegion.equals(region); if (translated && !DockingUtility.isAxisEquivalent(region, baseRegion)) { region = DockingUtility.flipRegion(region); } return region; } /** * Returns the docking region within the current split docking layout * containing the specified {@code Component}. If {@code comp} is * {@code null}, then a {@code null} reference is returned. If {@code comp} * is not in a split layout, then {@code CENTER_REGION} is returned. *

* This method resolves the associated {@code Dockable} and * {@code DockingPort} for the specified {@code Component} and backtracks * through the docking layout to find a split layout. If a split layout is * found, then the region retured by this method is calculated relative to * its sibling in the layout. * * @param comp * the {@code Component} whose region is to be returned * @return the region of the current split layout containing the specified * {@code Dockable} */ public static String findRegion(Component comp) { if (comp == null) { return null; } DockingPort port = DockingManager.getDockingPort(comp); Component docked = port.getDockedComponent(); if (!(docked instanceof JSplitPane)) { // we didn't find a split pane, to check the grandparent dockingport DockingPort superPort = DockingManager .getDockingPort((Component) port); // if there was no grandparent DockingPort, then we're stuck with // the docked // component we already found. this can happen on the root // dockingport. docked = superPort == null ? docked : superPort .getDockedComponent(); } if (!(docked instanceof JSplitPane)) { return CENTER_REGION; } JSplitPane split = (JSplitPane) docked; boolean horiz = split.getOrientation() == JSplitPane.HORIZONTAL_SPLIT; Component left = split.getLeftComponent(); if (left == port) { return horiz ? WEST_REGION : NORTH_REGION; } return horiz ? EAST_REGION : SOUTH_REGION; } /** * Docks the specified {@code Dockable} into the supplied {@code region} of * the specified {@code DockingPort}. This method is meant for programmatic * docking, as opposed to realtime, event-based docking operations. As such, * it defers processing to * {@code dock(Dockable dockable, DockingPort port, String region, DragOperation token)}, * passing a {@code null} argument for the {@code DragOperation} parameter. * This implies that there is no event-based drag operation currently in * progress to control the semantics of the docking operation, only that an * attempt should be made to dock the specified {@code Dockable} into the * specified {@code DockingPort}. *

* This method will return {@code false} if {@code dockable} or {@code port} * are {@code null}, or if {@code region} is not a valid region according * to the specified {@code DockingPort}. If a {@code Dockable} is currently * docked within the specified {@code DockingPort}, then that * {@code Dockable's} territorial properties are also checked and this * method may return {@code false} if the territory is blocked. Finally, * this method will return {@code false} if the specified {@code Dockable} * is already docked within the supplied region of the specified * {@code DockingPort}. * * @param dockable * the {@code Dockable} we wish to dock * @param port * the {@code DockingPort} into which we wish to dock * @param region * the region of the specified {@code DockingPort} into which we * wish to dock. * @return {@code true} if the docking operation was successful, * {@code false}. otherwise. * @see #dock(Dockable, DockingPort, String, DragOperation) * @see Dockable#getDockingProperties() * @see org.flexdock.docking.props.DockablePropertySet#isTerritoryBlocked(String) */ @Override public boolean dock(Dockable dockable, DockingPort port, String region) { return dock(dockable, port, region, null); } /** * Docks the specified {@code Dockable} into the supplied {@code region} of * the specified {@code DockingPort}. This method is meant for realtime, * event-based docking based on an in-progress drag operation. It is not * recommended for developers to call this method programmatically, except * to pass in a {@code null} {@code DragOperation} argument. * *

* The {@code DragOperation} parameter, if present, will control the * semantics of the docking operation based upon current mouse position, * drag threshold, and a customizable drag context {@code Map}. For * instance, the {@code DragOperation} may contain information regarding the * {@code Dockable} over which the mouse is currently hovered, whether the * user is attempting to drag a {@code Dockable} outside the bounds of any * existing windows (perhaps in an attempt to float the {@code Dockable}), * or whether the current distance offset from the original drag point * sufficiently warrants a valid docking operation. *

* If the {@code DragOperation} is {@code null}, then this method will * attempt to programmatically dock the specified {@code Dockable} into the * supplied {@code region} of the specified {@code DockingPort} without * regard to external event-based criteria. This is in accordance with the * behavior specified by * {@code dock(Dockable dockable, DockingPort port, String region)}. * * This method will return {@code false} if {@code dockable} or {@code port} * are {@code null}, or if {@code region} is not a valid region according * to the specified {@code DockingPort}. If a {@code Dockable} is currently * docked within the specified {@code DockingPort}, then that * {@code Dockable's} territorial properties are also checked and this * method may return {@code false} if the territory is blocked. If a * {@code DragOperation} is present, then this method will return * {@code false} if the required drag threshold has not been exceeded. * Finally, this method will return {@code false} if the specified * {@code Dockable} is already docked within the supplied region of the * specified {@code DockingPort}. * * @param dockable * the {@code Dockable} we wish to dock * @param port * the {@code DockingPort} into which we wish to dock * @param region * the region of the specified {@code DockingPort} into which we * wish to dock. * @return {@code true} if the docking operation was successful, * {@code false}. otherwise. * @see #dock(Dockable, DockingPort, String, DragOperation) * @see Dockable#getDockingProperties() * @see org.flexdock.docking.props.DockablePropertySet#isTerritoryBlocked(String) */ @Override public boolean dock(Dockable dockable, DockingPort port, String region, DragOperation operation) { if (!isDockingPossible(dockable, port, region, operation)) { return false; } if (!dragThresholdElapsed(operation)) { return false; } // cache the old parent DockingPort oldPort = dockable.getDockingPort(); // perform the drop operation. DockingResults results = dropComponent(dockable, port, region, operation); // perform post-drag operations DockingPort newPort = results.dropTarget; int evtType = results.success ? DockingEvent.DOCKING_COMPLETE : DockingEvent.DOCKING_CANCELED; Map dragContext = DragManager.getDragContext(dockable); DockingEvent evt = new DockingEvent(dockable, oldPort, newPort, evtType, dragContext); // populate DockingEvent status info evt.setRegion(region); evt.setOverWindow(operation == null ? true : operation.isOverWindow()); // notify the old docking port, new dockingport,and dockable Object[] evtTargets = { oldPort, newPort, dockable }; EventManager.dispatch(evt, evtTargets); return results.success; } protected boolean dragThresholdElapsed(DragOperation token) { if (token == null || token.isPseudoDrag() || token.getStartTime() == -1) { return true; } long elapsed = System.currentTimeMillis() - token.getStartTime(); // make sure the elapsed time of the drag is at least over .2 seconds. // otherwise, we'll probably be responding to inadvertent clicks (maybe // double-clicks) return elapsed > 200; } protected boolean isDockingPossible(Dockable dockable, DockingPort port, String region, DragOperation token) { // superclass blocks docking if the 'port' or 'region' are null. If // we've dragged outside // the bounds of the parent frame, then both of these will be null. This // is expected here and // we intend to float in this case. if (isFloatable(dockable, token)) { return true; } // check to see if we're already floating and we're trying to drop into // the // same dialog. DockingPort oldPort = DockingManager.getDockingPort(dockable); if (oldPort instanceof FloatingDockingPort && oldPort == port) { // only allow this situation if we're not the *last* dockable // in the viewport. if we're removing the last dockable, then // the dialog will disappear before we redock, and we don't want // this // to happen. FloatingDockingPort floatingDockingPort = (FloatingDockingPort) oldPort; if (floatingDockingPort.getDockableCount() == 1) { return false; } } if (dockable == null || dockable.getComponent() == null || port == null) { return false; } if (!DockingManager.isValidDockingRegion(region)) { return false; } Dockable docked = DockingManager.getDockable(port.getDockedComponent()); if (docked == null) { return true; } // don't allow them to dock into this region if the territory there is // blocked. if (docked.getDockingProperties().isTerritoryBlocked(region) .booleanValue()) { return false; } // check to see if we're already docked into this region. // get the parent dockingPort. Container container = docked.getComponent().getParent(); // now get the grandparent dockingport DockingPort grandparent = DockingManager.getDockingPort(container); // if we don't share the grandparent dockingport, then we're definitely // not split in the same dockingport // across different region. in this case, it's ok to proceed with the // dock if (grandparent == null) { return true; } Component currentlyInRegion = grandparent.getComponent(region); // block docking if we're already the component docked within the // specified region if (currentlyInRegion == dockable.getComponent()) { return false; } return true; } protected boolean isFloatable(Dockable dockable, DragOperation token) { // can't float null objects if (dockable == null || dockable.getComponent() == null || token == null) { return false; } // can't float on a fake drag operation if (token.isPseudoDrag()) { return false; } // TODO: break this check out into a separate DropPolicy class. // should be any customizable criteria, not hardcoded to checking // for being outside the bounds of a window if (token.isOverWindow()) { return false; } return true; } protected DockingResults dropComponent(Dockable dockable, DockingPort target, String region, DragOperation token) { if (isFloatable(dockable, token)) { return floatComponent(dockable, target, token); } DockingResults results = new DockingResults(target, false); if (UNKNOWN_REGION.equals(region) || target == null) { return results; } Component docked = target.getDockedComponent(); Component dockableCmp = dockable.getComponent(); if (dockableCmp != null && dockableCmp == docked) { // don't allow docking the same component back into the same port return results; } // obtain a reference to the content pane that holds the target // DockingPort. // MUST happen before undock(), in case the undock() operation removes // the // target DockingPort from the container tree. Container contentPane = SwingUtility.getContentPane((Component) target); Point contentPaneLocation = token == null ? null : token .getCurrentMouse(contentPane); // undock the current Dockable instance from it's current parent // container undock(dockable); // when the original parent reevaluates its container tree after // undocking, it checks to see how // many immediate child components it has. split layouts and tabbed // interfaces may be managed by // intermediate wrapper components. When undock() is called, the docking // port // may decide that some of its intermedite wrapper components are no // longer needed, and it may get // rid of them. this isn't a hard rule, but it's possible for any given // DockingPort implementation. // In this case, the target we had resolved earlier may have been // removed from the component tree // and may no longer be valid. to be safe, we'll resolve the target // docking port again and see if // it has changed. if so, we'll adopt the resolved port as our new // target. if (contentPaneLocation != null && contentPane != null) { results.dropTarget = DockingUtility.findDockingPort(contentPane, contentPaneLocation); target = results.dropTarget; } results.success = target.dock(dockableCmp, region); SwingUtility.revalidate((Component) target); return results; } /** * Undocks the specified {@code Dockable} from it's parent * {@code DockingPort}. If {@code dockable} is {@code null} or is not * currently docked within a {@code DockingPort}, then this method returns * {@code false}. * * @param dockable * the {@code Dockable} to be undocked. * @return {@code true} if the undocking operation was successful, * {@code false} otherwise. * @see #dock(Dockable, DockingPort, String) */ @Override public boolean undock(Dockable dockable) { if (dockable == null) { return false; } Component dragSrc = dockable.getComponent(); Container parent = dragSrc.getParent(); RootWindow rootWin = RootWindow.getRootContainer(parent); // if there's no parent container, then we really don't have anything // from which to to // undock this component, now do we? if (parent == null) { return false; } boolean success = false; DockingPort dockingPort = DockingUtility.getParentDockingPort(dragSrc); // notify that we are about to undock Map dragContext = DragManager.getDragContext(dockable); DockingEvent dockingEvent = new DockingEvent(dockable, dockingPort, dockingPort, DockingEvent.UNDOCKING_STARTED, dragContext); EventManager.dispatch(dockingEvent); // if(dockingEvent.isConsumed()) // return false; if (dockingPort != null) { // if 'dragSrc' is currently docked, then undock it instead of using // a // simple remove(). this will allow the DockingPort to do any of its // own // cleanup operations associated with component removal. success = dockingPort.undock(dragSrc); } else { // otherwise, just remove the component parent.remove(dragSrc); success = true; } if (rootWin != null) { SwingUtility.revalidate(rootWin.getContentPane()); SwingUtility.repaint(rootWin.getContentPane()); } if (success) { dockingEvent = new DockingEvent(dockable, dockingPort, dockingPort, DockingEvent.UNDOCKING_COMPLETE, dragContext); // notify the docking port and dockable Object[] evtTargets = { dockingPort, dockable }; EventManager.dispatch(dockingEvent, evtTargets); } return success; } protected DockingResults floatComponent(Dockable dockable, DockingPort target, DragOperation token) { // otherwise, setup a new DockingFrame and retarget to the CENTER region DockingResults results = new DockingResults(target, false); // determine the bounds of the new frame Point screenLoc = token.getCurrentMouse(true); SwingUtility.add(screenLoc, token.getMouseOffset()); Rectangle screenBounds = dockable.getComponent().getBounds(); screenBounds.setLocation(screenLoc); // create the frame FloatManager mgr = DockingManager.getFloatManager(); DockingFrame frame = mgr.floatDockable(dockable, dockable .getComponent(), screenBounds); // grab a reference to the frame's dockingPort for posterity results.dropTarget = frame.getDockingPort(); results.success = true; return results; } protected static class DockingResults { public DockingResults(DockingPort port, boolean status) { dropTarget = port; success = status; } public DockingPort dropTarget; public boolean success; } /** * Returns a new {@code DefaultDockingPort} with characteristics similar to * the specified base {@code DockingPort}. If the base {@code DockingPort} * is a {@code DefaultDockingPort}, then the returned {@code DockingPort} * will share the base {@code DockingPort's} border manager and tabbed * drag-source flag. The returned {@code DockingPort's} {@code isRoot()} * method will return {@code false}. * * @param base * the {@code DockingPort} off of which to base the returned * {@code DockingPort} * @return a new {@code DefaultDockingPort} with characteristics similar to * the specified base {@code DockingPort}. * @see DefaultDockingPort#getBorderManager() * @see DefaultDockingPort#setBorderManager(BorderManager) * @see DefaultDockingPort#isTabsAsDragSource() * @see DefaultDockingPort#setTabsAsDragSource(boolean) * @see DefaultDockingPort#setRoot(boolean) */ @Override public DockingPort createDockingPort(DockingPort base) { DockingPort port = createDockingPortImpl(base); if (port instanceof DefaultDockingPort && base instanceof DefaultDockingPort) { DefaultDockingPort newPort = (DefaultDockingPort) port; DefaultDockingPort ddp = (DefaultDockingPort) base; newPort.setBorderManager(ddp.getBorderManager()); newPort.setTabsAsDragSource(ddp.isTabsAsDragSource()); newPort.setRoot(false); } return port; } protected DockingPort createDockingPortImpl(DockingPort base) { return new DefaultDockingPort(); } /** * Returns a new {@code DockingSplitPane} based on the specified * {@code DockingPort}. and region. Creation of the * {@code DockingSplitPane} is deferred to an internal protected method to * allow for overriding by subclasses. A client property is set on the * returned split pane with the key DockingConstants.REGION to indicate the * creation region of the split pane for non-{@code DockingSplitPanes} * returned by overriding subclasses. *

* This method determines the "elder" component of the split pane by * checking whether the new creation region is in the TOP or LEFT * (NORTH_REGION or WEST_REGION). If the creation region, representing where * the new {@code Dockable} will be docked, is not in the top or * left, then the elder {@code Component} in the split pane must be. This * information is used to initialize the resize weight of the split pane, * setting resize weight to {@code 1} if the elder is in the top or left of * the split pane and {@code 0} if not. This gives the elder * {@code Component} in the resulting split pane priority in the layout with * resizing the split pane. *

* If the creation region is {@code NORTH_REGION} or {@code SOUTH_REGION}, * the returned split pane is initialized with a {@code VERTICAL_SPLIT} * orientation; otherwise a {@code HORIZONTAL_SPLIT} orientation is used. *

* Before returning, the border is removed from the split pane, its divider * size is set to 3, and if possible the border is removed from the split * pane divider. This is to avoid an excessive compound border effect for * embedded {@code Components} within the split pane that may have their own * borders. * * @param base * the {@code DockingPort} off of which the returned * {@code JSplitPane} will be based. * @param region * the region within the base {@code DockingPort} used to * determine the orientation of the returned {@code JSplitPane}. * @return a new {@code DockingSplitPane} based on the specified * {@code DockingPort}. and region. * @see DockingSplitPane#DockingSplitPane(DockingPort, String) * @see #createSplitPaneImpl(DockingPort, String) * @see JSplitPane#setResizeWeight(double) */ @Override public JSplitPane createSplitPane(DockingPort base, String region, float percent) { JSplitPane split = createSplitPaneImpl(base, region); split.setVisible(false); // mark the creation region on the split pane SwingUtility.putClientProperty(split, DockingConstants.REGION, region); double resizeWeight; if (defaultResizeWeight == -1) { // the creation region represents the "new" region, not the elder // region. // so if the creation region is NOT in the top left, then the elder // region is. boolean elderInTopLeft = !DockingUtility.isRegionTopLeft(region); resizeWeight = elderInTopLeft ? 1 : 0; } else { resizeWeight = defaultResizeWeight; } split.setResizeWeight(resizeWeight); if (constantPercent && split instanceof DockingSplitPane) { ((DockingSplitPane) split).setConstantPercent(true); } // determine the orientation int orientation = JSplitPane.HORIZONTAL_SPLIT; if (NORTH_REGION.equals(region) || SOUTH_REGION.equals(region)) { orientation = JSplitPane.VERTICAL_SPLIT; } split.setOrientation(orientation); // remove the border from the split pane split.setBorder(null); // set the divider size for a more reasonable, less bulky look split.setDividerSize(3); split.setOneTouchExpandable(false); //zw // check the UI. If we can't work with the UI any further, then // exit here. if (!(split.getUI() instanceof BasicSplitPaneUI)) { return split; } // grab the divider from the UI and remove the border from it final BasicSplitPaneDivider divider = ((BasicSplitPaneUI) split.getUI()) .getDivider(); if (divider != null) { divider.setBorder(null); divider.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2) { // TODO should be not override, but placed logic here ((JSplitPane) divider.getParent()) .resetToPreferredSizes(); } } }); } if (percent != -1) { split.setDividerLocation(percent); } split.setVisible(true); return split; } /** * Returns a new {@code DockingSplitPane} based on the specified * {@code DockingPort}. and region. Creation of the * {@code DockingSplitPane} is deferred to an internal protected method to * allow for overriding by subclasses. A client property is set on the * returned split pane with the key DockingConstants.REGION to indicate the * creation region of the split pane for non-{@code DockingSplitPanes} * returned by overriding subclasses. *

* This method determines the "elder" component of the split pane by * checking whether the new creation region is in the TOP or LEFT * (NORTH_REGION or WEST_REGION). If the creation region, representing where * the new {@code Dockable} will be docked, is not in the top or * left, then the elder {@code Component} in the split pane must be. This * information is used to initialize the resize weight of the split pane, * setting resize weight to {@code 1} if the elder is in the top or left of * the split pane and {@code 0} if not. This gives the elder * {@code Component} in the resulting split pane priority in the layout with * resizing the split pane. *

* If the creation region is {@code NORTH_REGION} or {@code SOUTH_REGION}, * the returned split pane is initialized with a {@code VERTICAL_SPLIT} * orientation; otherwise a {@code HORIZONTAL_SPLIT} orientation is used. *

* Before returning, the border is removed from the split pane, its divider * size is set to 3, and if possible the border is removed from the split * pane divider. This is to avoid an excessive compound border effect for * embedded {@code Components} within the split pane that may have their own * borders. * * @param base * the {@code DockingPort} off of which the returned * {@code JSplitPane} will be based. * @param region * the region within the base {@code DockingPort} used to * determine the orientation of the returned {@code JSplitPane}. * @return a new {@code DockingSplitPane} based on the specified * {@code DockingPort}. and region. * @see DockingSplitPane#DockingSplitPane(DockingPort, String) * @see #createSplitPaneImpl(DockingPort, String) * @see JSplitPane#setResizeWeight(double) */ @Override public JSplitPane createSplitPane(DockingPort base, String region) { return createSplitPane(base, region, -1f); } protected JSplitPane createSplitPaneImpl(DockingPort base, String region) { return new DockingSplitPane(base, region); } /** * Returns the initial divider location to be used by the specified * {@code JSplitPane} when it is embedded within the specified * {@code DockingPort}. It is assumed that the {@code JSplitPane} parameter * is embedded within the specified {@code DockingPort}, is validated, * visible, and its dimensions are non-zero. *

* This method gets the "size" of the specified {@code DockingPort} based on * the orientation of the split pane (width for horizontal split, * height for vertical split) minus the {@code DockingPort's} * insets. It then dispatches to * {@code getDividerProportion(DockingPort port, JSplitPane splitPane)} to * determine the preferred proportion of the split pane divider. The * returned value for this method is the product of the {@code DockingPort} * size and the split proportion. *

* If either {@code port} or {@code splitPane} parameters are {@code null}, * then this method returns {@code 0}. * * @param port * the {@code DockingPort} that contains the specified * {@code JSplitPane}. * @param splitPane * the {@code JSplitPane} whose initial divider location is to be * determined. * @return the desired divider location of the supplied {@code JSplitPane}. * @see DockingStrategy#getInitialDividerLocation(DockingPort, JSplitPane) * @see #getDividerProportion(DockingPort, JSplitPane) */ @Override public int getInitialDividerLocation(DockingPort port, JSplitPane splitPane) { if (port == null || splitPane == null) { return 0; } Container dockingPort = (Container) port; Insets in = dockingPort.getInsets(); boolean vert = splitPane.getOrientation() == JSplitPane.VERTICAL_SPLIT; int inset = vert ? in.top + in.bottom : in.left + in.right; // get the dimensions of the DockingPort, minus the insets int portSize = vert ? dockingPort.getHeight() : dockingPort.getWidth(); portSize -= inset; // get the divider proportion for the split pane and multiply by the // port size double proportion = getDividerProportion(port, splitPane); if (proportion < 0 || proportion > 1) { proportion = 0.5d; } return (int) (portSize * proportion); } /** * Returns the desired divider proportion of the specified * {@code JSplitPane} after rendering. This method assumes that the * {@code JSplitPane} parameter is, or will be embedded within the specified * {@code DockingPort}. This method does not assume that the * {@code JSplitPane} has been validated and that it's current dimensions * are non-zero. *

* If either {@code port} or {@code splitPane} parameters are {@code null}, * then this method returns the default value of * {@code RegionChecker.DEFAULT_SIBLING_SIZE}. Otherwise the "elder" * component within the {@code JSplitPane} is determined to see if it is * contained within a sub-{@code DockingPort}. If the "elder" * {@code Component} cannot be determined, or it is not contained within a * sub-{@code DockingPort}, then the default value of * {@code RegionChecker.DEFAULT_SIBLING_SIZE} is returned. *

* If the "elder" {@code Component} is successfully resolved inside a sub-{@code DockingPort}, * then a check is done on the sub-port for the client property * {@code DefaultDockingStrategy.PREFERRED_PROPORTION}. If this value is * found, then the primitive float version of it is returned. *

* Failing these checks, the {@code Dockable} is resolved for the "elder" * {@code Component} in the specified {@code JSplitPane} via * {@code DockingManager.getDockable(Component comp)}. If no * {@code Dockable} can be found, then * {@code RegionChecker.DEFAULT_SIBLING_SIZE} is returned. *

* Otherwise, the {@code DockingPortPropertySet} is retrieved from the * specified {@code DockingPort} and its {@code getRegionChecker()} method * is called. {@code getSiblingSize(Component c, String region)} is invoked * on the returned {@code RegionChecker} passing the "elder" * {@code Component} in the split pane and the creation region resolved for * the specified {@code JSplitPane}. This resolves the preferred sibling * size for the elder {@code Dockable} component. If the elder * {@code Component} is in the top/left of the split pane, then * {@code 1F-prefSize} is returned. Otherwise, the preferred sibling size is * returned. * * @param port * the {@code DockingPort} that contains, or will contain the * specified {@code JSplitPane}. * @param splitPane * the {@code JSplitPane} whose initial divider location is to be * determined. * @return the desired divider proportion of the supplied {@code JSplitPane}. * @see RegionChecker#DEFAULT_SIBLING_SIZE * @see #PREFERRED_PROPORTION * @see DockingManager#getDockable(Component) * @see RegionChecker#getSiblingSize(Component, String) */ @Override public double getDividerProportion(DockingPort port, JSplitPane splitPane) { if (port == null || splitPane == null) { return DockingManager.getDefaultSiblingSize(); } Component elder = getElderComponent(splitPane); if (elder == null) { return DockingManager.getDefaultSiblingSize(); } Float prefProp = getPreferredProportion(splitPane, elder); if (prefProp != null) { return prefProp.doubleValue(); } if (elder instanceof DockingSplitPane) { elder = ((DockingSplitPane) elder).getElderComponent(); } Dockable dockable = DockingManager.getDockable(elder); if (dockable != null) { // DockingSplitPane splitter = (DockingSplitPane)splitPane; RegionChecker rc = port.getDockingProperties().getRegionChecker(); float prefSize = rc.getSiblingSize(dockable.getComponent(), getCreationRegion(splitPane)); return isElderTopLeft(splitPane) ? 1f - prefSize : prefSize; // return prefSize; } return DockingManager.getDefaultSiblingSize(); } protected String getCreationRegion(JSplitPane splitPane) { if (splitPane instanceof DockingSplitPane) { return ((DockingSplitPane) splitPane).getRegion(); } return (String) SwingUtility.getClientProperty(splitPane, DockingConstants.REGION); } protected boolean isElderTopLeft(JSplitPane splitPane) { if (splitPane instanceof DockingSplitPane) { return ((DockingSplitPane) splitPane).isElderTopLeft(); } String region = getCreationRegion(splitPane); // creation region represents the "new" region, not the "elder" region. // so if the "new" region is NOT the topLeft, then the "elder" is. return !DockingUtility.isRegionTopLeft(region); } protected Float getPreferredProportion(JSplitPane splitPane, Component controller) { // 'controller' is inside a dockingPort. re-reference to the parent // dockingPort. Container controllerPort = controller.getParent(); return getPreferredProportion(controllerPort); } protected Component getElderComponent(JSplitPane splitPane) { if (splitPane instanceof DockingSplitPane) { return ((DockingSplitPane) splitPane).getElderComponent(); } boolean inTopLeft = isElderTopLeft(splitPane); Component comp = inTopLeft ? splitPane.getLeftComponent() : splitPane .getRightComponent(); if (comp instanceof DockingPort) { comp = ((DockingPort) comp).getDockedComponent(); } return comp; } protected static Float getPreferredProportion(Component c) { return c == null ? null : (Float) SwingUtility.getClientProperty(c, PREFERRED_PROPORTION); } public static void setDefaultResizeWeight(double rw) { defaultResizeWeight = rw; } public static void keepConstantPercentage(boolean cstPercent) { constantPercent = cstPercent; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy