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

org.pushingpixels.substance.internal.ui.SubstanceRootPaneUI Maven / Gradle / Ivy

There is a newer version: 4.5.0
Show newest version
/*
 * Copyright (c) 2005-2020 Radiance Kirill Grouchnikov. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 * 
 *  o Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer. 
 *     
 *  o Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution. 
 *     
 *  o Neither the name of the copyright holder nor the names of
 *    its contributors may be used to endorse or promote products derived 
 *    from this software without specific prior written permission. 
 *     
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */
package org.pushingpixels.substance.internal.ui;

import org.pushingpixels.substance.api.SubstanceSkin;
import org.pushingpixels.substance.api.SubstanceWidget;
import org.pushingpixels.substance.internal.SubstanceSynapse;
import org.pushingpixels.substance.internal.SubstanceWidgetRepository;
import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
import org.pushingpixels.substance.internal.utils.*;

import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
import javax.swing.event.MouseInputListener;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicRootPaneUI;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Set;

/**
 * UI for root panes in Substance  look and feel.
 * 
 * @author Kirill Grouchnikov
 * @author Larry Salibra (fix for defect 198)
 */
public class SubstanceRootPaneUI extends BasicRootPaneUI {
    private enum CursorState {
        EXITED, ENTERED, NIL
    }

    /**
     * The amount of space (in pixels) that the cursor is changed on.
     */
    private static final int CORNER_DRAG_WIDTH = 16;

    /**
     * Region from edges that dragging is active from.
     */
    private static final int BORDER_DRAG_THICKNESS = 5;

    /**
     * Window the JRootPane is in.
     */
    private Window window;

    /**
     * JComponent providing window decorations. This will be null if not providing
     * window decorations.
     */
    private SubstanceTitlePane titlePane;

    private boolean isContentExtendingIntoTitlePane;

    /**
     * MouseInputListener that is added to the parent Window the
     * JRootPane is contained in.
     */
    private MouseInputListener substanceMouseInputListener;

    /**
     * Mouse listener on the title pane (dragging).
     */
    private MouseInputListener substanceTitleMouseInputListener;

    /**
     * The LayoutManager that is set on the JRootPane.
     */
    private LayoutManager layoutManager;

    /**
     * LayoutManager of the JRootPane before we replaced it.
     */
    private LayoutManager savedOldLayout;

    /**
     * JRootPane providing the look and feel for.
     */
    private JRootPane root;

    /**
     * Window listener that stops all Substance thread when the last frame is disposed.
     */
    private WindowListener substanceWindowListener;

    /**
     * The current window.
     */
    private Window substanceCurrentWindow;

    /**
     * Hierarchy listener to keep track of the associated top-level window.
     */
    private HierarchyListener substanceHierarchyListener;

    /**
     * Component listener to keep track of the primary graphics configuration (for recomputing the
     * maximized bounds) - fix for defect 213.
     */
    private ComponentListener substanceWindowComponentListener;

    /**
     * The graphics configuration that contains the top-left corner of the window (fix for defect
     * 213).
     */
    private GraphicsConfiguration currentRootPaneGC;

    private PropertyChangeListener substancePropertyChangeListener;

    /**
     * Cursor used to track the cursor set by the user. This is initially
     * Cursor.DEFAULT_CURSOR.
     */
    private Cursor lastCursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);

    /**
     * Optimization to speed up the {@link SubstanceCoreUtilities#getSkin(Component)}
     * implementation.
     */
    private static int rootPanesWithCustomSkin = 0;

    private Set> lafWidgets;

    /**
     * Creates a UI for a JRootPane.
     * 
     * @param comp
     *            the JRootPane the RootPaneUI will be created for
     * @return the RootPaneUI implementation for the passed in JRootPane
     */
    public static ComponentUI createUI(JComponent comp) {
        SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
        return new SubstanceRootPaneUI();
    }

    protected SubstanceRootPaneUI() {
    }

    /**
     * Invokes supers implementation of installUI to install the necessary state onto
     * the passed in JRootPane to render the metal look and feel implementation of
     * RootPaneUI. If the windowDecorationStyle property of the
     * JRootPane is other than JRootPane.NONE, this will add a custom
     * Component to render the widgets to JRootPane, as well as installing
     * a custom Border and LayoutManager on the JRootPane.
     * 
     * @param c
     *            the JRootPane to install state onto
     */
    @Override
    public void installUI(JComponent c) {
        this.lafWidgets = SubstanceWidgetRepository.getRepository().getMatchingWidgets(c);

        super.installUI(c);

        this.root = (JRootPane) c;
        int style = this.root.getWindowDecorationStyle();
        if (style != JRootPane.NONE) {
            this.installClientDecorations(this.root);
        }

        if (SubstanceCoreUtilities.isRootPaneModified(this.root)) {
            propagateModificationState();
        }

        if (this.root.getClientProperty(SubstanceSynapse.ROOT_PANE_SKIN) instanceof SubstanceSkin) {
            rootPanesWithCustomSkin++;
        }

        for (SubstanceWidget lafWidget : this.lafWidgets) {
            lafWidget.installUI();
        }
    }

    /**
     * Invokes super implementation to uninstall any of its state. This will also reset the
     * LayoutManager of the JRootPane. If a Component has
     * been added to the JRootPane to render the window decoration style, this method
     * will remove it. Similarly, this will revert the Border and LayoutManager of the
     * JRootPane to what it was before installUI was invoked.
     * 
     * @param c
     *            the JRootPane to uninstall state from
     */
    @Override
    public void uninstallUI(JComponent c) {
        super.uninstallUI(c);
        this.uninstallClientDecorations(this.root);

        this.layoutManager = null;
        this.substanceMouseInputListener = null;

        if (this.root.getClientProperty(SubstanceSynapse.ROOT_PANE_SKIN) instanceof SubstanceSkin) {
            rootPanesWithCustomSkin--;
        }

        this.root = null;

        for (SubstanceWidget lafWidget : this.lafWidgets) {
            lafWidget.uninstallUI();
        }
    }

    /**
     * Installs the appropriate Border onto the JRootPane.
     * 
     * @param root
     *            Root pane.
     */
    public void installBorder(JRootPane root) {
        int style = root.getWindowDecorationStyle();

        if (style == JRootPane.NONE) {
            LookAndFeel.uninstallBorder(root);
        } else {
            LookAndFeel.installBorder(root, "RootPane.border");
        }
    }

    /**
     * Removes any border that may have been installed.
     * 
     * @param root
     *            Root pane.
     */
    private void uninstallBorder(JRootPane root) {
        LookAndFeel.uninstallBorder(root);
    }

    @Override
    protected void installDefaults(JRootPane c) {
        super.installDefaults(c);
        // support for per-window skins
        Color backgr = c.getBackground();
        if ((backgr == null) || (backgr instanceof UIResource)) {
            Color backgroundFillColor = SubstanceColorUtilities.getBackgroundFillColor(c);
            // fix for issue 244 - set the root pane BG color
            if (backgroundFillColor != null) {
                c.setBackground(new ColorUIResource(backgroundFillColor));
            }
        }

        for (SubstanceWidget lafWidget : this.lafWidgets) {
            lafWidget.installDefaults();
        }
    }

    @Override
    protected void uninstallDefaults(JRootPane root) {
        for (SubstanceWidget lafWidget : this.lafWidgets) {
            lafWidget.uninstallDefaults();
        }

        super.uninstallDefaults(root);
    }

    @Override
    public void update(Graphics g, JComponent c) {
        if (!SubstanceCoreUtilities.isCurrentLookAndFeel())
            return;

        // fix for issue 244 - paint the entire root pane so that it
        // picks the correct watermark
        if (SubstanceCoreUtilities.isOpaque(c)) {
            BackgroundPaintingUtils.update(g, c, false);
        }
        super.paint(g, c);
    }

    /**
     * Installs the necessary Listeners on the parent Window, if there is one.
     * 

* This takes the parent so that cleanup can be done from removeNotify, at which * point the parent hasn't been reset yet. * * * @param root * Root pane. * @param parent * The parent of the JRootPane */ private void installWindowListeners(JRootPane root, Component parent) { if (parent instanceof Window) { this.window = (Window) parent; } else { this.window = SwingUtilities.getWindowAncestor(parent); } if (this.window != null) { if (this.substanceMouseInputListener == null) { this.substanceMouseInputListener = this.createWindowMouseInputListener(root); } this.window.addMouseListener(this.substanceMouseInputListener); this.window.addMouseMotionListener(this.substanceMouseInputListener); if (this.titlePane != null) { if (this.substanceTitleMouseInputListener == null) { this.substanceTitleMouseInputListener = new TitleMouseInputHandler(); } this.titlePane.addMouseMotionListener(this.substanceTitleMouseInputListener); this.titlePane.addMouseListener(this.substanceTitleMouseInputListener); } } } /** * Uninstalls the necessary Listeners on the Window the Listeners were last * installed on. * * @param root * Root pane. */ private void uninstallWindowListeners(JRootPane root) { if (this.window != null) { this.window.removeMouseListener(this.substanceMouseInputListener); this.window.removeMouseMotionListener(this.substanceMouseInputListener); } if (this.titlePane != null) { this.titlePane.removeMouseListener(this.substanceTitleMouseInputListener); this.titlePane.removeMouseMotionListener(this.substanceTitleMouseInputListener); } } /** * Installs the appropriate LayoutManager on the JRootPane to render the window * decorations. * * @param root * Root pane. */ private void installLayout(JRootPane root) { if (this.layoutManager == null) { this.layoutManager = this.createLayoutManager(); } this.savedOldLayout = root.getLayout(); root.setLayout(this.layoutManager); } @Override protected void installListeners(final JRootPane root) { super.installListeners(root); this.substanceHierarchyListener = (HierarchyEvent e) -> { Component parent = root.getParent(); if (parent == null) { // fix for defect 271 - check for null parent // as early as possible return; } if (MemoryAnalyzer.isRunning()) { MemoryAnalyzer.enqueueUsage("Root pane @" + root.hashCode() + "\n" + SubstanceCoreUtilities.getHierarchy(parent)); } if (parent.getClass().getName().startsWith("org.jdesktop.jdic.tray") || (parent .getClass().getName().compareTo("javax.swing.Popup$HeavyWeightWindow") == 0)) { // Workaround for bug 240 - using JDIC system tray // menu results in an HierarchyEvent being fired right // after a MouseEvent. Somehow, the // EventQueue.getCurrentEvent() returns the HierarchyEvent // even when the MouseEvent is being processed, resulting // in zeroed modifiers set on the ActionEvent passed // to the action listeners on that menu item. SwingUtilities.invokeLater(() -> { root.removeHierarchyListener(substanceHierarchyListener); substanceHierarchyListener = null; }); } Window currWindow = null; if (parent instanceof Window) { currWindow = (Window) parent; } else { currWindow = SwingUtilities.getWindowAncestor(parent); } if (substanceWindowListener != null) { substanceCurrentWindow.removeWindowListener(substanceWindowListener); substanceWindowListener = null; } if (substanceWindowComponentListener != null) { substanceCurrentWindow.removeComponentListener(substanceWindowComponentListener); substanceWindowComponentListener = null; } if (currWindow != null) { // fix for bug 116 - stopping threads when all frames // are not displayable substanceWindowListener = new WindowAdapter() { @Override public void windowClosed(WindowEvent e) { SubstanceCoreUtilities.testWindowCloseThreadingViolation(e.getWindow()); SwingUtilities.invokeLater(() -> { Frame[] frames = Frame.getFrames(); for (Frame frame : frames) { if (frame.isDisplayable()) return; } SubstanceCoreUtilities.stopThreads(); }); } }; if (!(parent instanceof JInternalFrame)) { currWindow.addWindowListener(substanceWindowListener); } // fix for defect 213 - maximizing frame under multiple // screens shouldn't always use insets of the primary // screen. substanceWindowComponentListener = new ComponentAdapter() { @Override public void componentMoved(ComponentEvent e) { this.processNewPosition(); } @Override public void componentResized(ComponentEvent e) { this.processNewPosition(); } protected void processNewPosition() { SwingUtilities.invokeLater(() -> { if (window == null) return; if (!window.isShowing() || !window.isDisplayable()) { currentRootPaneGC = null; return; } GraphicsEnvironment ge = GraphicsEnvironment .getLocalGraphicsEnvironment(); GraphicsDevice[] gds = ge.getScreenDevices(); if (gds.length == 1) return; Point midLoc = new Point( window.getLocationOnScreen().x + window.getWidth() / 2, window.getLocationOnScreen().y + window.getHeight() / 2); for (GraphicsDevice gd : gds) { GraphicsConfiguration gc = gd.getDefaultConfiguration(); Rectangle bounds = gc.getBounds(); if (bounds.contains(midLoc)) { if (gc != currentRootPaneGC) { currentRootPaneGC = gc; } break; } } }); } }; // fix for defect 225 - install the listener only on // JFrames. if (parent instanceof JFrame) { currWindow.addComponentListener(substanceWindowComponentListener); } SubstanceRootPaneUI.this.window = currWindow; } substanceCurrentWindow = currWindow; }; root.addHierarchyListener(this.substanceHierarchyListener); this.substancePropertyChangeListener = (PropertyChangeEvent evt) -> { if (SubstanceSynapse.CONTENTS_MODIFIED.equals(evt.getPropertyName())) { propagateModificationState(); } }; root.addPropertyChangeListener(this.substancePropertyChangeListener); for (SubstanceWidget lafWidget : this.lafWidgets) { lafWidget.installListeners(); } } @Override protected void uninstallListeners(JRootPane root) { // fix for bug 116 - stopping threads when all frames are // not displayable if (this.window != null) { this.window.removeWindowListener(this.substanceWindowListener); this.substanceWindowListener = null; this.window.removeComponentListener(this.substanceWindowComponentListener); this.substanceWindowComponentListener = null; } root.removeHierarchyListener(this.substanceHierarchyListener); this.substanceHierarchyListener = null; root.removePropertyChangeListener(this.substancePropertyChangeListener); this.substancePropertyChangeListener = null; for (SubstanceWidget lafWidget : this.lafWidgets) { lafWidget.uninstallListeners(); } super.uninstallListeners(root); } @Override protected void installComponents(JRootPane root) { super.installComponents(root); for (SubstanceWidget lafWidget : this.lafWidgets) { lafWidget.installComponents(); } } @Override protected void uninstallComponents(JRootPane root) { for (SubstanceWidget lafWidget : this.lafWidgets) { lafWidget.uninstallComponents(); } super.uninstallComponents(root); } /** * Uninstalls the previously installed LayoutManager. * * @param root * Root pane. */ private void uninstallLayout(JRootPane root) { if (this.savedOldLayout != null) { root.setLayout(this.savedOldLayout); this.savedOldLayout = null; } } /** * Installs the necessary state onto the JRootPane to render client decorations. This is ONLY * invoked if the JRootPane has a decoration style other than * JRootPane.NONE. * * @param root * Root pane. */ private void installClientDecorations(JRootPane root) { this.installBorder(root); SubstanceTitlePane titlePane = this.createTitlePane(root); this.setTitlePane(root, titlePane); if (Boolean.TRUE.equals(root .getClientProperty(SubstanceSynapse.ROOT_PANE_CONTENTS_EXTENDS_INTO_TITLE_PANE))) { extendContentIntoTitlePane(); } Object preferredTitlePaneHeight = root .getClientProperty(SubstanceSynapse.ROOT_PANE_PREFERRED_TITLE_PANE_HEIGHT); if (preferredTitlePaneHeight != null) { setPreferredTitlePaneHeight((Integer) preferredTitlePaneHeight); } this.installWindowListeners(root, root.getParent()); this.installLayout(root); if (this.window != null) { root.revalidate(); root.repaint(); } } /** * Uninstalls any state that installClientDecorations has installed. *

* NOTE: This may be called if you haven't installed client decorations yet (ie before * installClientDecorations has been invoked). * * @param root * Root pane. */ private void uninstallClientDecorations(JRootPane root) { this.uninstallBorder(root); this.uninstallWindowListeners(root); this.setTitlePane(root, null); this.uninstallLayout(root); // We have to revalidate/repaint root if the style is JRootPane.NONE // only. When we needs to call revalidate/repaint with other styles // the installClientDecorations is always called after this method // immediately and it will cause the revalidate/repaint at the proper // time. int style = root.getWindowDecorationStyle(); if (style == JRootPane.NONE) { root.repaint(); root.revalidate(); } // Reset the cursor, as we may have changed it to a resize cursor if (this.window != null) { this.window.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } this.window = null; } /** * Returns the JComponent to render the window decoration style. * * @param root * Root pane. * @return The title pane component. */ protected SubstanceTitlePane createTitlePane(JRootPane root) { return new SubstanceTitlePane(root, this); } /** * Returns a MouseListener that will be added to the Window containing * the JRootPane. * * @param root * Root pane. * @return Window mouse listener. */ private MouseInputListener createWindowMouseInputListener(JRootPane root) { return new MouseInputHandler(); } /** * Returns a LayoutManager that will be set on the JRootPane. * * @return Layout manager. */ protected LayoutManager createLayoutManager() { return new SubstanceRootLayout(); } /** * Sets the window title pane -- the JComponent used to provide a plaf a way to override the * native operating system's window title pane with one whose look and feel are controlled by * the plaf. The plaf creates and sets this value; the default is null, implying a native * operating system window title pane. * * @param root * Root pane * @param titlePane * The JComponent to use for the window title pane. */ private void setTitlePane(JRootPane root, SubstanceTitlePane titlePane) { JLayeredPane layeredPane = root.getLayeredPane(); if (this.titlePane != null) { // fix for defect 109 - memory leak on skin change this.titlePane.uninstall(); layeredPane.remove(this.titlePane); } if (titlePane != null) { layeredPane.add(titlePane, JLayeredPane.FRAME_CONTENT_LAYER); titlePane.setVisible(true); } this.titlePane = titlePane; } /** * Returns the JComponent rendering the title pane. If this returns null, it * implies there is no need to render window decorations. This method is for internal use * only. * * @see #setTitlePane * @return Title pane. */ public JComponent getTitlePane() { return this.titlePane; } public JButton createTitlePaneControlButton() { if (this.titlePane == null) { return null; } return this.titlePane.createControlButton(); } public Insets getTitlePaneControlInsets() { if (this.titlePane == null) { return null; } return this.titlePane.getControlInsets(); } public void extendContentIntoTitlePane() { if (this.titlePane == null) { throw new IllegalStateException("This root pane has not been marked to be decorated"); } this.isContentExtendingIntoTitlePane = true; this.titlePane.setControlOnlyMode(); Container titlePaneParent = this.titlePane.getParent(); titlePaneParent.setComponentZOrder(this.titlePane, 0); titlePaneParent.invalidate(); } public void setPreferredTitlePaneHeight(int preferredTitlePaneHeight) { if (this.titlePane == null) { throw new IllegalStateException("This root pane has not been marked to be decorated"); } this.titlePane.setPreferredHeight(preferredTitlePaneHeight); this.titlePane.getParent().invalidate(); } /** * Returns the JRootPane we're providing the look and feel for. * * @return The associated root pane. */ protected JRootPane getRootPane() { return this.root; } @Override public void propertyChange(PropertyChangeEvent e) { super.propertyChange(e); String propertyName = e.getPropertyName(); if (propertyName == null) { return; } if (propertyName.equals("windowDecorationStyle")) { JRootPane root = (JRootPane) e.getSource(); int style = root.getWindowDecorationStyle(); this.uninstallClientDecorations(root); if (style != JRootPane.NONE) { this.installClientDecorations(root); } } if (propertyName.equals("ancestor")) { this.uninstallWindowListeners(this.root); if (((JRootPane) e.getSource()).getWindowDecorationStyle() != JRootPane.NONE) { this.installWindowListeners(this.root, this.root.getParent()); } } if (propertyName.equals("background")) { SubstanceCoreUtilities.getTitlePaneComponent(window) .setBackground((Color) e.getNewValue()); } if (propertyName.equals(SubstanceSynapse.ROOT_PANE_SKIN)) { SubstanceSkin oldValue = (SubstanceSkin) e.getOldValue(); SubstanceSkin newValue = (SubstanceSkin) e.getNewValue(); if ((oldValue == null) && (newValue != null)) { rootPanesWithCustomSkin++; } if ((oldValue != null) && (newValue == null)) { rootPanesWithCustomSkin--; } } } /** * A custom layout manager that is responsible for the layout of layeredPane, glassPane, menuBar * and titlePane, if one has been installed. */ protected class SubstanceRootLayout implements LayoutManager2 { /** * Returns the amount of space the layout would like to have. * * * aram the Container for which this layout manager is being used * * @return a Dimension object containing the layout's preferred size */ @Override public Dimension preferredLayoutSize(Container parent) { Dimension cpd, mbd, tpd; int cpWidth = 0; int cpHeight = 0; int mbWidth = 0; int mbHeight = 0; int tpWidth = 0; int tpHeight = 0; Insets i = parent.getInsets(); JRootPane root = (JRootPane) parent; if (root.getContentPane() != null) { cpd = root.getContentPane().getPreferredSize(); } else { cpd = root.getSize(); } if (cpd != null) { cpWidth = cpd.width; cpHeight = cpd.height; } if (root.getJMenuBar() != null) { mbd = root.getJMenuBar().getPreferredSize(); if (mbd != null) { mbWidth = mbd.width; mbHeight = mbd.height; } } if ((root.getWindowDecorationStyle() != JRootPane.NONE) && (root.getUI() instanceof SubstanceRootPaneUI)) { JComponent titlePane = ((SubstanceRootPaneUI) root.getUI()).getTitlePane(); if (titlePane != null) { tpd = titlePane.getPreferredSize(); if (tpd != null) { tpWidth = tpd.width; tpHeight = tpd.height; } } } return new Dimension(Math.max(Math.max(cpWidth, mbWidth), tpWidth) + i.left + i.right, cpHeight + mbHeight + tpHeight + i.top + i.bottom); } /** * Returns the minimum amount of space the layout needs. * * * aram the Container for which this layout manager is being used * * @return a Dimension object containing the layout's minimum size */ @Override public Dimension minimumLayoutSize(Container parent) { Dimension cpd, mbd, tpd; int cpWidth = 0; int cpHeight = 0; int mbWidth = 0; int mbHeight = 0; int tpWidth = 0; int tpHeight = 0; Insets i = parent.getInsets(); JRootPane root = (JRootPane) parent; if (root.getContentPane() != null) { cpd = root.getContentPane().getMinimumSize(); } else { cpd = root.getSize(); } if (cpd != null) { cpWidth = cpd.width; cpHeight = cpd.height; } if (root.getJMenuBar() != null) { mbd = root.getJMenuBar().getMinimumSize(); if (mbd != null) { mbWidth = mbd.width; mbHeight = mbd.height; } } if ((root.getWindowDecorationStyle() != JRootPane.NONE) && (root.getUI() instanceof SubstanceRootPaneUI)) { JComponent titlePane = ((SubstanceRootPaneUI) root.getUI()).getTitlePane(); if (titlePane != null) { tpd = titlePane.getMinimumSize(); if (tpd != null) { tpWidth = tpd.width; tpHeight = tpd.height; } } } return new Dimension(Math.max(Math.max(cpWidth, mbWidth), tpWidth) + i.left + i.right, cpHeight + mbHeight + tpHeight + i.top + i.bottom); } /** * Returns the maximum amount of space the layout can use. * * * aram the Container for which this layout manager is being used * * @return a Dimension object containing the layout's maximum size */ @Override public Dimension maximumLayoutSize(Container target) { Dimension cpd, mbd, tpd; int cpWidth = Integer.MAX_VALUE; int cpHeight = Integer.MAX_VALUE; int mbWidth = Integer.MAX_VALUE; int mbHeight = Integer.MAX_VALUE; int tpWidth = Integer.MAX_VALUE; int tpHeight = Integer.MAX_VALUE; Insets i = target.getInsets(); JRootPane root = (JRootPane) target; if (root.getContentPane() != null) { cpd = root.getContentPane().getMaximumSize(); if (cpd != null) { cpWidth = cpd.width; cpHeight = cpd.height; } } if (root.getJMenuBar() != null) { mbd = root.getJMenuBar().getMaximumSize(); if (mbd != null) { mbWidth = mbd.width; mbHeight = mbd.height; } } if ((root.getWindowDecorationStyle() != JRootPane.NONE) && (root.getUI() instanceof SubstanceRootPaneUI)) { JComponent titlePane = ((SubstanceRootPaneUI) root.getUI()).getTitlePane(); if (titlePane != null) { tpd = titlePane.getMaximumSize(); if (tpd != null) { tpWidth = tpd.width; tpHeight = tpd.height; } } } int maxHeight = Math.max(Math.max(cpHeight, mbHeight), tpHeight); // Only overflows if 3 real non-MAX_VALUE heights, sum to > // MAX_VALUE // Only will happen if sums to more than 2 billion units. Not // likely. if (maxHeight != Integer.MAX_VALUE) { maxHeight = cpHeight + mbHeight + tpHeight + i.top + i.bottom; } int maxWidth = Math.max(Math.max(cpWidth, mbWidth), tpWidth); // Similar overflow comment as above if (maxWidth != Integer.MAX_VALUE) { maxWidth += i.left + i.right; } return new Dimension(maxWidth, maxHeight); } /** * Instructs the layout manager to perform the layout for the specified container. * * * aram the Container for which this layout manager is being used */ @Override public void layoutContainer(Container parent) { JRootPane root = (JRootPane) parent; Rectangle b = root.getBounds(); Insets i = root.getInsets(); int nextY = 0; int w = b.width - i.right - i.left; int h = b.height - i.top - i.bottom; if (root.getLayeredPane() != null) { root.getLayeredPane().setBounds(i.left, i.top, w, h); } if (root.getGlassPane() != null) { root.getGlassPane().setBounds(i.left, i.top, w, h); } // Note: This is laying out the children in the layeredPane, // technically, these are not our children. if ((root.getWindowDecorationStyle() != JRootPane.NONE) && (root.getUI() instanceof SubstanceRootPaneUI)) { if (titlePane != null) { Dimension tpd = titlePane.getPreferredSize(); if (tpd != null) { int tpHeight = tpd.height; if (!isContentExtendingIntoTitlePane) { titlePane.setBounds(0, 0, w, tpHeight); nextY += tpHeight; } else { boolean controlButtonsOnRight = SubstanceTitlePaneUtilities .areTitlePaneControlButtonsOnRight(root); Insets titlePaneControlInsets = titlePane.getControlInsets(); int titlePaneWidth = controlButtonsOnRight ? titlePaneControlInsets.right : titlePaneControlInsets.left; int titlePaneX = controlButtonsOnRight ? w - titlePaneWidth : 0; titlePane.setBounds(titlePaneX, 0, titlePaneWidth, tpHeight); } } } } if (root.getJMenuBar() != null) { Dimension mbd = root.getJMenuBar().getPreferredSize(); root.getJMenuBar().setBounds(0, nextY, w, mbd.height); nextY += mbd.height; } if (root.getContentPane() != null) { root.getContentPane().setBounds(0, nextY, w, h < nextY ? 0 : h - nextY); } } @Override public void addLayoutComponent(String name, Component comp) { } @Override public void removeLayoutComponent(Component comp) { } @Override public void addLayoutComponent(Component comp, Object constraints) { } @Override public float getLayoutAlignmentX(Container target) { return 0.0f; } @Override public float getLayoutAlignmentY(Container target) { return 0.0f; } @Override public void invalidateLayout(Container target) { } } /** * Maps from positions to cursor type. Refer to calculateCorner and calculatePosition for * details of this. */ private static final int[] cursorMapping = new int[] { Cursor.NW_RESIZE_CURSOR, Cursor.NW_RESIZE_CURSOR, Cursor.N_RESIZE_CURSOR, Cursor.NE_RESIZE_CURSOR, Cursor.NE_RESIZE_CURSOR, Cursor.NW_RESIZE_CURSOR, 0, 0, 0, Cursor.NE_RESIZE_CURSOR, Cursor.W_RESIZE_CURSOR, 0, 0, 0, Cursor.E_RESIZE_CURSOR, Cursor.SW_RESIZE_CURSOR, 0, 0, 0, Cursor.SE_RESIZE_CURSOR, Cursor.SW_RESIZE_CURSOR, Cursor.SW_RESIZE_CURSOR, Cursor.S_RESIZE_CURSOR, Cursor.SE_RESIZE_CURSOR, Cursor.SE_RESIZE_CURSOR }; /** * MouseInputHandler is responsible for handling resize/moving of the Window. It sets the cursor * directly on the Window when then mouse moves over a hot spot. */ private class MouseInputHandler implements MouseInputListener { /** * Set to true if the drag operation is moving the window. */ private boolean isMovingWindow; private boolean isMousePressed; /** * Used to determine the corner the resize is occuring from. */ private int dragCursor; /** * X location the mouse went down on for a drag operation. */ private int dragOffsetX; /** * Y location the mouse went down on for a drag operation. */ private int dragOffsetY; /** * Width of the window when the drag started. */ private int dragWidth; /** * Height of the window when the drag started. */ private int dragHeight; /** * PrivilegedExceptionAction needed by mouseDragged method to obtain new location of window * on screen during the drag. */ private final PrivilegedExceptionAction getLocationAction = () -> MouseInfo.getPointerInfo().getLocation(); @Override public void mousePressed(MouseEvent ev) { JRootPane rootPane = SubstanceRootPaneUI.this.getRootPane(); this.isMousePressed = true; if ((titlePane == null) || (rootPane.getWindowDecorationStyle() == JRootPane.NONE)) { return; } Point dragWindowOffset = ev.getPoint(); Window w = (Window) ev.getSource(); if (w != null) { w.toFront(); } Frame f = null; Dialog d = null; if (w instanceof Frame) { f = (Frame) w; } else if (w instanceof Dialog) { d = (Dialog) w; } int frameState = (f != null) ? f.getExtendedState() : 0; if (isMouseEventInExtendedTitlePane(ev)) { if ((((f != null) && ((frameState & Frame.MAXIMIZED_BOTH) == 0)) || (d != null)) && (dragWindowOffset.y >= SubstanceRootPaneUI.BORDER_DRAG_THICKNESS) && (dragWindowOffset.x >= SubstanceRootPaneUI.BORDER_DRAG_THICKNESS) && (dragWindowOffset.x < w.getWidth() - SubstanceRootPaneUI.BORDER_DRAG_THICKNESS)) { this.isMovingWindow = true; this.dragOffsetX = dragWindowOffset.x; this.dragOffsetY = dragWindowOffset.y; } } else if (((f != null) && f.isResizable() && ((frameState & Frame.MAXIMIZED_BOTH) == 0)) || ((d != null) && d.isResizable())) { this.dragOffsetX = dragWindowOffset.x; this.dragOffsetY = dragWindowOffset.y; this.dragWidth = w.getWidth(); this.dragHeight = w.getHeight(); this.dragCursor = this .getCursor(this.calculateCorner(w, dragWindowOffset.x, dragWindowOffset.y)); } } @Override public void mouseReleased(MouseEvent ev) { if ((this.dragCursor != 0) && (SubstanceRootPaneUI.this.window != null) && !SubstanceRootPaneUI.this.window.isValid()) { // Some Window systems validate as you resize, others won't, // thus the check for validity before repainting. SubstanceRootPaneUI.this.window.validate(); SubstanceRootPaneUI.this.getRootPane().repaint(); } this.isMousePressed = false; this.isMovingWindow = false; this.dragCursor = 0; } @Override public void mouseMoved(MouseEvent ev) { JRootPane root = SubstanceRootPaneUI.this.getRootPane(); if (root.getWindowDecorationStyle() == JRootPane.NONE) { return; } Window w = (Window) ev.getSource(); Frame f = null; Dialog d = null; if (w instanceof Frame) { f = (Frame) w; } else if (w instanceof Dialog) { d = (Dialog) w; } // Update the cursor int cursor = this.getCursor(this.calculateCorner(w, ev.getX(), ev.getY())); boolean isFrameResizable = (f != null) && (f.isResizable() && (f.getExtendedState() & Frame.MAXIMIZED_BOTH) == 0); boolean isDialogResizable = (d != null) && d.isResizable(); if ((cursor != 0) && (isFrameResizable || isDialogResizable)) { w.setCursor(Cursor.getPredefinedCursor(cursor)); } else { w.setCursor(SubstanceRootPaneUI.this.lastCursor); SubstanceRootPaneUI.this.lastCursor = null; } } /** * Adjusts the bounds. * * @param bounds * Original bounds. * @param min * Minimum dimension. * @param deltaX * Delta X. * @param deltaY * Delta Y. * @param deltaWidth * Delta width. * @param deltaHeight * Delta height. */ private void adjust(Rectangle bounds, Dimension min, int deltaX, int deltaY, int deltaWidth, int deltaHeight) { bounds.x += deltaX; bounds.y += deltaY; bounds.width += deltaWidth; bounds.height += deltaHeight; if (min != null) { if (bounds.width < min.width) { int correction = min.width - bounds.width; if (deltaX != 0) { bounds.x -= correction; } bounds.width = min.width; } if (bounds.height < min.height) { int correction = min.height - bounds.height; if (deltaY != 0) { bounds.y -= correction; } bounds.height = min.height; } } } @Override public void mouseDragged(MouseEvent ev) { Window w = (Window) ev.getSource(); Point pt = ev.getPoint(); if (this.isMovingWindow) { Point windowPt; try { windowPt = AccessController.doPrivileged(this.getLocationAction); windowPt.x = windowPt.x - this.dragOffsetX; windowPt.y = windowPt.y - this.dragOffsetY; w.setLocation(windowPt); } catch (PrivilegedActionException e) { } } else if (this.dragCursor != 0) { Rectangle r = w.getBounds(); Rectangle startBounds = new Rectangle(r); Dimension min = w.getMinimumSize(); switch (this.dragCursor) { case Cursor.E_RESIZE_CURSOR: this.adjust(r, min, 0, 0, pt.x + (this.dragWidth - this.dragOffsetX) - r.width, 0); break; case Cursor.S_RESIZE_CURSOR: this.adjust(r, min, 0, 0, 0, pt.y + (this.dragHeight - this.dragOffsetY) - r.height); break; case Cursor.N_RESIZE_CURSOR: this.adjust(r, min, 0, pt.y - this.dragOffsetY, 0, -(pt.y - this.dragOffsetY)); break; case Cursor.W_RESIZE_CURSOR: this.adjust(r, min, pt.x - this.dragOffsetX, 0, -(pt.x - this.dragOffsetX), 0); break; case Cursor.NE_RESIZE_CURSOR: this.adjust(r, min, 0, pt.y - this.dragOffsetY, pt.x + (this.dragWidth - this.dragOffsetX) - r.width, -(pt.y - this.dragOffsetY)); break; case Cursor.SE_RESIZE_CURSOR: this.adjust(r, min, 0, 0, pt.x + (this.dragWidth - this.dragOffsetX) - r.width, pt.y + (this.dragHeight - this.dragOffsetY) - r.height); break; case Cursor.NW_RESIZE_CURSOR: this.adjust(r, min, pt.x - this.dragOffsetX, pt.y - this.dragOffsetY, -(pt.x - this.dragOffsetX), -(pt.y - this.dragOffsetY)); break; case Cursor.SW_RESIZE_CURSOR: this.adjust(r, min, pt.x - this.dragOffsetX, 0, -(pt.x - this.dragOffsetX), pt.y + (this.dragHeight - this.dragOffsetY) - r.height); break; default: break; } if (!r.equals(startBounds)) { w.setBounds(r); // Defer repaint/validate on mouseReleased unless dynamic // layout is active. if (Toolkit.getDefaultToolkit().isDynamicLayoutActive()) { w.validate(); SubstanceRootPaneUI.this.getRootPane().repaint(); } } } } private CursorState cursorState = CursorState.NIL; @Override public void mouseEntered(MouseEvent ev) { if (isMousePressed) { return; } Window w = (Window) ev.getSource(); if ((SubstanceRootPaneUI.this.lastCursor == null) && (cursorState != CursorState.ENTERED)) { // fix for defect 107 SubstanceRootPaneUI.this.lastCursor = w.getCursor(); } cursorState = CursorState.ENTERED; this.mouseMoved(ev); } @Override public void mouseExited(MouseEvent ev) { if (isMousePressed) { return; } Window w = (Window) ev.getSource(); w.setCursor(SubstanceRootPaneUI.this.lastCursor); SubstanceRootPaneUI.this.lastCursor = null; cursorState = CursorState.EXITED; } @Override public void mouseClicked(MouseEvent ev) { Window w = (Window) ev.getSource(); Frame f = null; if (w instanceof Frame) { f = (Frame) w; } else { return; } // fix for issue 444 - ignore double clicks when the title pane // is not showing (for example under JRootPane.NONE decoration // style). if (titlePane == null) return; // Point convertedPoint = SwingUtilities.convertPoint(w, ev.getPoint(), // windowTitlePane); int state = f.getExtendedState(); if (isMouseEventInExtendedTitlePane(ev)) { if (((ev.getClickCount() % 2) == 0) && (ev.getButton() == MouseEvent.BUTTON1)) { if (f.isResizable()) { if ((state & Frame.MAXIMIZED_BOTH) != 0) { f.setExtendedState(state & ~Frame.MAXIMIZED_BOTH); } else { f.setExtendedState(state | Frame.MAXIMIZED_BOTH); } } } } } /** * Returns the corner that contains the point x, y, or -1 if the * position doesn't match a corner. * * @param w * Window. * @param x * X coordinate. * @param y * Y coordinate. * @return Corner that contains the specified point. */ private int calculateCorner(Window w, int x, int y) { Insets insets = w.getInsets(); int xPosition = this.calculatePosition(x - insets.left, w.getWidth() - insets.left - insets.right); int yPosition = this.calculatePosition(y - insets.top, w.getHeight() - insets.top - insets.bottom); if ((xPosition == -1) || (yPosition == -1)) { return -1; } return yPosition * 5 + xPosition; } /** * Returns the Cursor to render for the specified corner. This returns 0 if the corner * doesn't map to a valid Cursor * * @param corner * Corner * @return Cursor to render for the specified corner. */ private int getCursor(int corner) { if (corner == -1) { return 0; } return SubstanceRootPaneUI.cursorMapping[corner]; } /** * Returns an integer indicating the position of spot in width. * The return value will be: 0 if < BORDER_DRAG_THICKNESS 1 if < CORNER_DRAG_WIDTH 2 if >= * CORNER_DRAG_WIDTH && < width - BORDER_DRAG_THICKNESS 3 if >= width - CORNER_DRAG_WIDTH 4 * if >= width - BORDER_DRAG_THICKNESS 5 otherwise * * @param spot * Spot. * @param width * Width. * @return The position of spot in width. */ private int calculatePosition(int spot, int width) { if (spot < SubstanceRootPaneUI.BORDER_DRAG_THICKNESS) { return 0; } if (spot < SubstanceRootPaneUI.CORNER_DRAG_WIDTH) { return 1; } if (spot >= (width - SubstanceRootPaneUI.BORDER_DRAG_THICKNESS)) { return 4; } if (spot >= (width - SubstanceRootPaneUI.CORNER_DRAG_WIDTH)) { return 3; } return 2; } } /** * Mouse handler on the title pane. * * @author Kirill Grouchnikov */ private class TitleMouseInputHandler extends MouseInputAdapter { /** * Pointer location when the mouse was pressed for a drag relative to the upper-lefthand * corner of the window. */ private Point dragOffset = new Point(0, 0); @Override public void mousePressed(MouseEvent ev) { JRootPane rootPane = SubstanceRootPaneUI.this.getRootPane(); if (rootPane.getWindowDecorationStyle() == JRootPane.NONE) { return; } Point dragWindowOffset = ev.getPoint(); Component source = (Component) ev.getSource(); // Point convertedDragWindowOffset = SwingUtilities.convertPoint(source, // dragWindowOffset, // getTitlePane()); dragWindowOffset = SwingUtilities.convertPoint(source, dragWindowOffset, SubstanceRootPaneUI.this.window); if (isMouseEventInExtendedTitlePane(ev)) { // if (getTitlePane() != null && getTitlePane().contains(convertedDragWindowOffset)) // { if (SubstanceRootPaneUI.this.window != null) { SubstanceRootPaneUI.this.window.toFront(); dragOffset = dragWindowOffset; } } } @Override public void mouseDragged(MouseEvent ev) { Component source = (Component) ev.getSource(); // Point pt = SwingUtilities.convertPoint(source, ev.getPoint(), // SubstanceRootPaneUI.this.window); // fix for issue 198 Point eventLocationOnScreen = ev.getLocationOnScreen(); if (eventLocationOnScreen == null) { eventLocationOnScreen = new Point(ev.getX() + source.getLocationOnScreen().x, ev.getY() + source.getLocationOnScreen().y); } // Fix for issue 192 - disable dragging maximized frame. if (SubstanceRootPaneUI.this.window instanceof Frame) { Frame f = (Frame) SubstanceRootPaneUI.this.window; int frameState = (f != null) ? f.getExtendedState() : 0; if ((f != null) && ((frameState & Frame.MAXIMIZED_BOTH) == 0)) { SubstanceRootPaneUI.this.window.setLocation( eventLocationOnScreen.x - dragOffset.x, eventLocationOnScreen.y - dragOffset.y); } } else { // fix for issue 193 - allow dragging decorated dialogs. SubstanceRootPaneUI.this.window.setLocation(eventLocationOnScreen.x - dragOffset.x, eventLocationOnScreen.y - dragOffset.y); } } @Override public void mouseClicked(MouseEvent ev) { Frame f = null; if (SubstanceRootPaneUI.this.window instanceof Frame) { f = (Frame) SubstanceRootPaneUI.this.window; } else { return; } // Point convertedPoint = SwingUtilities.convertPoint(SubstanceRootPaneUI.this.window, // ev.getPoint(), SubstanceRootPaneUI.this.getTitlePane()); int state = f.getExtendedState(); if (isMouseEventInExtendedTitlePane(ev)) { if (((ev.getClickCount() % 2) == 0) && (ev.getButton() == MouseEvent.BUTTON1)) { if (f.isResizable()) { if ((state & Frame.MAXIMIZED_BOTH) != 0) { f.setExtendedState(state & ~Frame.MAXIMIZED_BOTH); } else { f.setExtendedState(state | Frame.MAXIMIZED_BOTH); } return; } } } } } private void propagateModificationState() { if (this.titlePane != null) { this.titlePane.getCloseButton().putClientProperty( SubstanceSynapse.CONTENTS_MODIFIED, root.getClientProperty(SubstanceSynapse.CONTENTS_MODIFIED)); return; } JInternalFrame jif = (JInternalFrame) SwingUtilities .getAncestorOfClass(JInternalFrame.class, this.root); if (jif != null) { SubstanceInternalFrameUI internalFrameUI = (SubstanceInternalFrameUI) jif.getUI(); internalFrameUI.setWindowModified(Boolean.TRUE .equals(root.getClientProperty(SubstanceSynapse.CONTENTS_MODIFIED))); } } public static boolean hasCustomSkinOnAtLeastOneRootPane() { return (rootPanesWithCustomSkin > 0); } private boolean isMouseEventInExtendedTitlePane(MouseEvent ev) { Point point = ev.getPoint(); Component source = (Component) ev.getSource(); if (this.titlePane == null) { return false; } if (!isContentExtendingIntoTitlePane) { return (this.titlePane != null && this.titlePane.contains(SwingUtilities.convertPoint(source, point, this.titlePane))); } else { // Built-in title pane is not going to be full-width. Work with layered pane // and its insets JLayeredPane layeredPane = this.root.getLayeredPane(); Insets layeredPaneInsets = layeredPane.getInsets(); Point convertedPoint = SwingUtilities.convertPoint(source, point, layeredPane); convertedPoint.x += layeredPaneInsets.left; convertedPoint.y += layeredPaneInsets.top; Rectangle titlePaneRect = new Rectangle(0, 0, layeredPane.getWidth() - layeredPaneInsets.left - layeredPaneInsets.right, this.titlePane.getHeight()); return titlePaneRect.contains(convertedPoint); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy