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

org.flexdock.docking.defaults.DockingSplitPane 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.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.AbstractAction;
import javax.swing.JSplitPane;
import javax.swing.plaf.basic.BasicSplitPaneUI;

import org.flexdock.docking.DockingConstants;
import org.flexdock.docking.DockingManager;
import org.flexdock.docking.DockingPort;
import org.flexdock.util.DockingUtility;
import org.flexdock.util.SwingUtility;

/**
 * @author Christopher Butler
 */
@SuppressWarnings(value = { "serial" })
public class DockingSplitPane extends JSplitPane implements DockingConstants {
    protected DockingPort dockingPort;

    protected String region;

    protected boolean dividerLocDetermined;

    protected boolean controllerInTopLeft;

    protected double initialDividerRatio = .5;

    protected double percent = -1;

    private int dividerHashCode = -1;

    private boolean constantPercent;

    /**
     * Creates a new {@code DockingSplitPane} for the specified
     * {@code DockingPort} with the understanding that the resulting
     * {@code DockingSplitPane} will be used for docking a {@code Dockable} into
     * the {@code DockingPort's} specified {@code region}. Neither {@code port}
     * or {@code region} may be {@code null}. {@code region} must be a valid
     * docking region as defined by {@code isValidDockingRegion(String region)}.
     *
     * @param port
     *            the {@code DockingPort} for which this
     *            {@code DockingSplitPane} is to be created.
     * @param region
     *            the region within the specified {@code DockingPort} for which
     *            this {@code DockingSplitPane} is to be created.
     * @throws IllegalArgumentException
     *             if either {@code port} is {@code null} or }region} is
     *             {@code null} or invalid.
     * @see DockingManager#isValidDockingRegion(String)
     */
    public DockingSplitPane(DockingPort port, String region) {
        if (port == null) {
            throw new IllegalArgumentException("'port' cannot be null.");
        }
        if (!DockingManager.isValidDockingRegion(region)) {
            throw new IllegalArgumentException("'" + region
                    + "' is not a valid region.");
        }

        this.region = region;
        this.dockingPort = port;
        // the controlling item is in the topLeft if our new item (represented
        // by the "region" string) is NOT in the topLeft.
        controllerInTopLeft = !DockingUtility.isRegionTopLeft(region);

        // set the proper resize weight
        int weight = controllerInTopLeft ? 1 : 0;
        setResizeWeight(weight);

        addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, new PropertyChangeListener() {

                @Override
                public void propertyChange(PropertyChangeEvent pce) {
                    if (constantPercent && getUI() instanceof BasicSplitPaneUI) {
                        BasicSplitPaneUI ui = (BasicSplitPaneUI) getUI();
                        if (dividerHashCode != ui.getDivider().hashCode()) {
                            dividerHashCode = ui.getDivider().hashCode();
                            ui.getDivider().addMouseListener(new MouseAdapter() {

                                @Override
                                    public void mouseReleased(MouseEvent e) {
                                        DockingSplitPane.this.percent = SwingUtility.getDividerProportion(DockingSplitPane.this);
                                        DockingSplitPane.this.setResizeWeight(percent);
                                    }
                                });
                        }
                    }
                }
            });

        getActionMap().put("toggleFocus", new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    SwingUtility.toggleFocus(+1);
                }
            });
    }

    @Override
    public void setBounds(int x, int y, int w, int h) {
        super.setBounds(x, y, w, h);
        if (constantPercent) {
            setResizeWeight(percent);
            super.setDividerLocation((int) (percent * getSplitSize()));
        }
    }

    public void setConstantPercent(boolean cstPercent) {
        if (cstPercent != constantPercent) {
            constantPercent = cstPercent;
        }
    }

    @Override
    public void resetToPreferredSizes() {
        Insets i = getInsets();

        if (getOrientation() == VERTICAL_SPLIT) {
            int h = getHeight() - i.top - i.bottom - getDividerSize();
            int topH = getTopComponent().getPreferredSize().height;
            int bottomH = getBottomComponent().getPreferredSize().height;
            int extraSpace = h - topH - bottomH;

            // we have more space than necessary; resize to give each at least
            // preferred size
            if (extraSpace >= 0) {
                setDividerLocation(i.top + topH
                                   + ((int) (extraSpace * getResizeWeight() + .5)));
            }

            // TODO implement shrinking excess space to ensure that one has
            // preferred and nothing more
        } else {
            int w = getWidth() - i.left - i.right - getDividerSize();
            int leftH = getLeftComponent().getPreferredSize().width;
            int rightH = getRightComponent().getPreferredSize().width;
            int extraSpace = w - leftH - rightH;

            // we have more space than necessary; resize to give each at least
            // preferred size
            if (extraSpace >= 0) {
                setDividerLocation(i.left + leftH
                                   + ((int) (extraSpace * getResizeWeight() + .5)));
            }

            // TODO implement shrinking excess space to ensure that one has
            // preferred and nothing more
        }
    }

    private int getSplitSize() {
        return getOrientation() == JSplitPane.HORIZONTAL_SPLIT ? getWidth() : getHeight();
    }

    @Override
    public void setDividerLocation(double percent) {
        this.percent = percent;
        super.setDividerLocation(percent);
        setResizeWeight(percent);
    }

    public double getPercent() {
        if (constantPercent) {
            return percent;
        }

        return -1;
    }

    protected boolean isDividerSizeProperlyDetermined() {
        if (getDividerLocation() != 0) {
            return true;
        }
        return dividerLocDetermined;
    }

    /**
     * Returns the 'oldest' {@code Component} to have been added to this
     * {@code DockingSplitPane} as a result of a docking operation. A
     * {@code DockingSplitPane} is created based upon the need to share space
     * within a {@code DockingPort} between two {@code Dockables}. This happens
     * when a new {@code Dockable} is introduced into an outer region of a
     * {@code DockingPort} that already contains a {@code Dockable}. The
     * {@code Dockable} that was in the {@code DockingPort} prior to splitting
     * the layout is the 'elder' {@code Component} and, in many circumstances,
     * may be used to control initial divider location and resize weight.
     * 

* If this split pane contains {@code DockingPorts} as its child components, * then this method will return the {@code Component} determined by calling * {@code getDockedComponent()} for the {@code DockingPort} in this split * pane's elder region. *

* The elder region of this {@code DockingSplitPane} is determined using the * value returned from {@code getRegion()}, where {@code getRegion()} * indicates the docking region of the 'new' {@code Dockable} for this * {@code DockingSplitPane}. * * @return the 'oldest' {@code Component} to have been added to this * {@code DockingSplitPane} as a result of a docking operation. * @see #getRegion() * @see DockingPort#getDockedComponent() */ public Component getElderComponent() { Component c = controllerInTopLeft ? getLeftComponent() : getRightComponent(); if (c instanceof DockingPort) { c = ((DockingPort) c).getDockedComponent(); } return c; } /** * Returns the docking region for which this {@code DockingSplitPane} was * created. A {@code DockingSplitPane} is created based upon the need to * share space within a {@code DockingPort} between two {@code Dockables}. * This happens when a new {@code Dockable} is introduced into an outer * region of a {@code DockingPort} that already contains a {@code Dockable}. * This method returns that outer region for which this * {@code DockingSplitPane} was created and may be used to control the * orientation of the split pane. The region returned by this method will be * the same passed into the {@code DockingSplitPane} constructor on * instantiation. * * @return the docking region for which this {@code DockingSplitPane} was * created. * @see #DockingSplitPane(DockingPort, String) */ public String getRegion() { return region; } /** * Indicates whether the 'oldest' {@code Component} to have been added to * this {@code DockingSplitPane} as a result of a docking operation is in * the TOP or LEFT side of the split pane. A {@code DockingSplitPane} is * created based upon the need to share space within a {@code DockingPort} * between two {@code Dockables}. This happens when a new {@code Dockable} * is introduced into an outer region of a {@code DockingPort} that already * contains a {@code Dockable}. The {@code Dockable} that was in the * {@code DockingPort} prior to splitting the layout is the 'elder' * {@code Component} and is returned by {@code getElderComponent()}. This * method indicates whether or not that {@code Component} is in the TOP or * LEFT side of this {@code DockingSplitPane}. *

* The elder region of this {@code DockingSplitPane} is determined using the * value returned from {@code getRegion()}, where {@code getRegion()} * indicates the docking region of the 'new' {@code Dockable} for this * {@code DockingSplitPane}. * * @return {@code true} if the 'oldest' {@code Component} to have been added * to this {@code DockingSplitPane} is in the TOP or LEFT side of * the split pane; {@code false} otherwise. * @see #getElderComponent() * @see #getRegion() */ public boolean isElderTopLeft() { return controllerInTopLeft; } /** * Overridden to ensure proper divider location on initial rendering. * Sometimes, a split divider location is set as a proportion before the * split pane itself has been fully realized in the container hierarchy. * This results in a layout calculation based on a proportion of zero width * or height, rather than the desired proportion of width or height after * the split pane has been fully rendered. This method ensures that default * {@code JSplitPane} layout behavior is deferred until after the initial * dimensions of this split pane have been properly determined. * * @see java.awt.Container#doLayout() * @see JSplitPane#setDividerLocation(double) */ @Override public void doLayout() { // if they setup the docking configuration while the application // was first starting up, then the dividerLocation was calculated before // the container tree was visible, sized, validated, etc, so it'll be // stuck at zero. in that case, redetermine the divider location now // that we have valid container bounds with which to work. if (!isDividerSizeProperlyDetermined()) { // make sure this can only run once so we don't get a StackOverflow dividerLocDetermined = true; setDividerLocation(initialDividerRatio); } // continue the layout super.doLayout(); } /** * Releases any internal references to external objects to aid garbage * collection. This method is {@code public} and may be invoked manually for * proactive memory management. Otherwise, this method is invoked by this * {@code DockingSplitPane's} {@code finalize()} method. */ public void cleanup() { dockingPort = null; } /** * Sets the initial divider ration for creating split panes. The default * value is {@code 0.5}. * * @exception IllegalArgumentException * if {@code ratio} is less than 0.0 or greater than 1.0. * @param ratio * a ratio for determining weighting between the two sides of a * split pane. */ public void setInitialDividerRatio(double ratio) { if (ratio < 0.0 || ratio > 1.0) { throw new IllegalArgumentException("ratio (" + ratio + ") must be between [0.0,1,0] inclusive"); } initialDividerRatio = ratio; } @Override protected void finalize() throws Throwable { cleanup(); super.finalize(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy