diff --git a/src/main/java/mdlaf/MaterialLookAndFeel.java b/src/main/java/mdlaf/MaterialLookAndFeel.java index eceb7d7..73f8aba 100644 --- a/src/main/java/mdlaf/MaterialLookAndFeel.java +++ b/src/main/java/mdlaf/MaterialLookAndFeel.java @@ -50,7 +50,7 @@ import mdlaf.components.progressbar.MaterialProgressBarUI; import mdlaf.components.radiobutton.MaterialRadioButtonUI; import mdlaf.components.radiobuttonmenuitem.MaterialRadioButtonMenuItemUI; -import mdlaf.components.rootpane.MaterialRootPaneUI; +import mdlaf.components.rootpane.MaterialRootPaneUIv2; import mdlaf.components.scrollbar.MaterialScrollBarUI; import mdlaf.components.separator.MaterialSeparatorUI; import mdlaf.components.slider.MaterialSliderUI; @@ -120,7 +120,7 @@ public class MaterialLookAndFeel extends MetalLookAndFeel { private static final String internalFrameUI = MaterialInternalFrameUI.class.getCanonicalName(); private static final String textAreaUI = MaterialTextAreaUI.class.getCanonicalName(); private static final String editorPane = MaterialEditorPaneUI.class.getCanonicalName(); - private static final String rootPane = MaterialRootPaneUI.class.getCanonicalName(); + private static final String rootPane = MaterialRootPaneUIv2.class.getCanonicalName(); private static final String optionPaneUI = MaterialOptionPaneUI.class.getCanonicalName(); private static final String colorChooserUI = MaterialColorChooser.class.getCanonicalName(); private static final String splitPaneUI = MaterialSplitPaneUI.class.getCanonicalName(); diff --git a/src/main/java/mdlaf/components/rootpane/MaterialRootPaneUI.java b/src/main/java/mdlaf/components/rootpane/MaterialRootPaneUI.java index 2b51c16..f1ee626 100644 --- a/src/main/java/mdlaf/components/rootpane/MaterialRootPaneUI.java +++ b/src/main/java/mdlaf/components/rootpane/MaterialRootPaneUI.java @@ -37,6 +37,7 @@ * @author Terry Kellerman // This code is inside the Open JDK * @author https://github.com/vincenzopalazzo */ +@Deprecated public class MaterialRootPaneUI extends BasicRootPaneUI { protected static final String[] borderKeys = diff --git a/src/main/java/mdlaf/components/rootpane/MaterialRootPaneUIv2.java b/src/main/java/mdlaf/components/rootpane/MaterialRootPaneUIv2.java new file mode 100644 index 0000000..1579316 --- /dev/null +++ b/src/main/java/mdlaf/components/rootpane/MaterialRootPaneUIv2.java @@ -0,0 +1,408 @@ +/* + * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package mdlaf.components.rootpane; + +import java.awt.*; +import java.beans.PropertyChangeEvent; +import javax.swing.*; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.basic.BasicRootPaneUI; +import mdlaf.components.titlepane.MaterialTitlePane; +import mdlaf.utils.FlatWindowResizer; + +/** + * @author Terry Kellerman // This code is inside the Open JDK + * @author https://github.com/vincenzopalazzo + */ +public class MaterialRootPaneUIv2 extends BasicRootPaneUI { + + protected static final String[] borderKeys = + new String[] { + null, + "RootPane.frameBorder", + "RootPane.plainDialogBorder", + "RootPane.informationDialogBorder", + "RootPane.errorDialogBorder", + "RootPane.colorChooserDialogBorder", + "RootPane.fileChooserDialogBorder", + "RootPane.questionDialogBorder", + "RootPane.warningDialogBorder" + }; + + public static ComponentUI createUI(JComponent c) { + return new MaterialRootPaneUIv2(); + } + + protected Window window; + protected JComponent titlePane; + protected LayoutManager layoutManager; + protected LayoutManager savedOldLayout; + protected JRootPane root; + protected FlatWindowResizer windowResizer; + + public MaterialRootPaneUIv2() { + super(); + } + + @Override + protected void installListeners(JRootPane root) { + super.installListeners(root); + } + + @Override + protected void uninstallListeners(JRootPane root) { + super.uninstallListeners(root); + } + + @Override + public void installUI(JComponent c) { + super.installUI(c); + root = (JRootPane) c; + root.setBackground(UIManager.getColor("RootPane.background")); + windowResizer = createWindowResizer(); + int style = root.getWindowDecorationStyle(); + if (style != JRootPane.NONE) { + installClientDecorations(root); + } + } + + @Override + public void uninstallUI(JComponent c) { + super.uninstallUI(c); + uninstallClientDecorations(root); + + layoutManager = null; + root = null; + } + + protected FlatWindowResizer createWindowResizer() { + return new FlatWindowResizer.WindowResizer(root); + } + + protected void uninstallBorder(JRootPane root) { + LookAndFeel.uninstallBorder(root); + } + + protected void installLayout(JRootPane root) { + if (layoutManager == null) { + layoutManager = createLayoutManager(); + } + savedOldLayout = root.getLayout(); + root.setLayout(layoutManager); + } + + protected void uninstallLayout(JRootPane root) { + if (savedOldLayout != null) { + root.setLayout(savedOldLayout); + savedOldLayout = null; + } + } + + protected void installClientDecorations(JRootPane root) { + installBorder(root); + JComponent titlePane = createTitlePane(root); + setTitlePane(root, titlePane); + installLayout(root); + if (window != null) { + root.revalidate(); + root.repaint(); + } + } + + protected void uninstallClientDecorations(JRootPane root) { + uninstallBorder(root); + setTitlePane(root, null); + uninstallLayout(root); + int style = root.getWindowDecorationStyle(); + if (style == JRootPane.NONE) { + root.repaint(); + root.revalidate(); + } + if (window != null) { + window.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + window = null; + } + + protected JComponent createTitlePane(JRootPane root) { + return new MaterialTitlePane(root); + } + + protected LayoutManager createLayoutManager() { + return new MaterialLayout(); + } + + protected void setTitlePane(JRootPane root, JComponent titlePane) { + JLayeredPane layeredPane = root.getLayeredPane(); + JComponent oldTitlePane = getTitlePane(); + + if (oldTitlePane != null) { + oldTitlePane.setVisible(false); + layeredPane.remove(oldTitlePane); + } + if (titlePane != null) { + layeredPane.add(titlePane, JLayeredPane.FRAME_CONTENT_LAYER); + titlePane.setVisible(true); + } + this.titlePane = titlePane; + } + + protected JComponent getTitlePane() { + return titlePane; + } + + 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 is potentially more than needs to be done, + // but it rarely happens and makes the install/uninstall process + // simpler. MetalTitlePane also assumes it will be recreated if + // the decoration style changes. + uninstallClientDecorations(root); + if (style != JRootPane.NONE) { + installClientDecorations(root); + } + } + } + + protected static class MaterialLayout implements LayoutManager2 { + + 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 MaterialRootPaneUIv2)) { + JComponent titlePane = ((MaterialRootPaneUIv2) 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 + tpWidth + i.top + i.bottom); + } + + 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 MaterialRootPaneUIv2)) { + JComponent titlePane = ((MaterialRootPaneUIv2) 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 + tpWidth + i.top + i.bottom); + } + + 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 MaterialRootPaneUIv2)) { + JComponent titlePane = ((MaterialRootPaneUIv2) 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); + if (maxHeight != Integer.MAX_VALUE) { + maxHeight = cpHeight + mbHeight + tpHeight + i.top + i.bottom; + } + + int maxWidth = Math.max(Math.max(cpWidth, mbWidth), tpWidth); + if (maxWidth != Integer.MAX_VALUE) { + maxWidth += i.left + i.right; + } + + return new Dimension(maxWidth, maxHeight); + } + + 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); + } + if (root.getWindowDecorationStyle() != JRootPane.NONE + && (root.getUI() instanceof MaterialRootPaneUIv2)) { + JComponent titlePane = ((MaterialRootPaneUIv2) root.getUI()).getTitlePane(); + if (titlePane != null) { + Dimension tpd = titlePane.getPreferredSize(); + if (tpd != null) { + int tpHeight = tpd.height; + titlePane.setBounds(0, 0, w, tpHeight); + nextY += 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) { + // Dimension cpd = root.getContentPane().getPreferredSize(); + root.getContentPane().setBounds(0, nextY, w, h < nextY ? 0 : h - nextY); + } + } + + public void addLayoutComponent(String name, Component comp) {} + + public void removeLayoutComponent(Component comp) {} + + public void addLayoutComponent(Component comp, Object constraints) {} + + public float getLayoutAlignmentX(Container target) { + return 0.0f; + } + + public float getLayoutAlignmentY(Container target) { + return 0.0f; + } + + public void invalidateLayout(Container target) {} + } + + protected void installBorder(JRootPane root) { + int style = root.getWindowDecorationStyle(); + + if (style == JRootPane.NONE) { + LookAndFeel.uninstallBorder(root); + } else { + LookAndFeel.installBorder(root, borderKeys[style]); + } + } +} diff --git a/src/main/java/mdlaf/components/titlepane/MaterialTitlePane.java b/src/main/java/mdlaf/components/titlepane/MaterialTitlePane.java new file mode 100644 index 0000000..5035c80 --- /dev/null +++ b/src/main/java/mdlaf/components/titlepane/MaterialTitlePane.java @@ -0,0 +1,877 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package mdlaf.components.titlepane; + +import java.awt.*; +import java.awt.event.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.List; +import javax.accessibility.AccessibleContext; +import javax.swing.*; +import javax.swing.plaf.UIResource; +import mdlaf.components.button.MaterialButtonUI; +import mdlaf.utils.MaterialDrawingUtils; +import mdlaf.utils.MaterialManagerListener; +import mdlaf.utils.WrapperSwingUtilities; + +/** + * @author Konstantin Bulenkov this code is copyed by OpenJDK + * @author https://github.com/users/vincenzopalazzo + */ +public class MaterialTitlePane extends JComponent { + + private static final int IMAGE_HEIGHT = 16; + private static final int IMAGE_WIDTH = 16; + + private PropertyChangeListener myPropertyChangeListener; + private Action myCloseAction; + private Action myIconifyAction; + private Action myRestoreAction; + private Action myMaximizeAction; + private JButton myToggleButton; + private JButton myIconifyButton; + private JButton myCloseButton; + private Icon myMaximizeIcon; + private Icon myMinimizeIcon; + private Image mySystemIcon; + private MaterialHandler myWindowListener; + private Window myWindow; + private JRootPane myRootPane; + private int myState; + + private Color myInactiveBackground = UIManager.getColor("Material.inactiveCaption"); + private Color myInactiveForeground = UIManager.getColor("Material.inactiveCaptionText"); + private Color myInactiveShadow = UIManager.getColor("Material.inactiveCaptionBorder"); + private Color myActiveBackground = null; + private Color myActiveForeground = null; + private Color myActiveShadow = null; + + public MaterialTitlePane(JRootPane root) { + this.myRootPane = root; + myState = -1; + myWindow = getWindow(); + installSubcomponents(); + determineColors(); + installDefaults(); + setLayout(createLayout()); + } + + protected void uninstall() { + uninstallListeners(); + myWindow = null; + removeAll(); + } + + protected void installListeners() { + if (myWindow != null) { + myWindowListener = createWindowListener(); + myWindow.addWindowListener(myWindowListener); + myPropertyChangeListener = createWindowPropertyChangeListener(); + myWindow.addPropertyChangeListener(myPropertyChangeListener); + addMouseListener(myWindowListener); + addMouseMotionListener(myWindowListener); + myWindow.addWindowFocusListener(myWindowListener); + myWindow.addWindowListener(myWindowListener); + } + } + + protected void uninstallListeners() { + if (myWindow != null) { + myWindow.removeWindowListener(myWindowListener); + myWindow.removePropertyChangeListener(myPropertyChangeListener); + myWindow.removeMouseListener(myWindowListener); + myWindow.removeMouseMotionListener(myWindowListener); + myWindow.removeWindowFocusListener(myWindowListener); + myWindow.removeWindowListener(myWindowListener); + } + } + + protected MaterialHandler createWindowListener() { + if (myWindow == null) return null; + return new MaterialHandler(myWindow); + } + + protected PropertyChangeListener createWindowPropertyChangeListener() { + return new PropertyChangeHandler(); + } + + public JRootPane getRootPane() { + return myRootPane; + } + + protected int getWindowDecorationStyle() { + return getRootPane().getWindowDecorationStyle(); + } + + public void addNotify() { + super.addNotify(); + uninstallListeners(); + + myWindow = SwingUtilities.getWindowAncestor(this); + if (myWindow != null) { + if (myWindow instanceof Frame) { + setState(((Frame) myWindow).getExtendedState()); + } else { + setState(0); + } + setActive(myWindow.isActive()); + installListeners(); + updateSystemIcon(); + } + } + + public void removeNotify() { + super.removeNotify(); + uninstallListeners(); + myWindow = null; + } + + protected void installSubcomponents() { + int decorationStyle = getWindowDecorationStyle(); + if (decorationStyle == JRootPane.FRAME) { + createActions(); + createButtons(); + add(myIconifyButton); + add(myToggleButton); + add(myCloseButton); + } else if (decorationStyle == JRootPane.PLAIN_DIALOG + || decorationStyle == JRootPane.INFORMATION_DIALOG + || decorationStyle == JRootPane.ERROR_DIALOG + || decorationStyle == JRootPane.COLOR_CHOOSER_DIALOG + || decorationStyle == JRootPane.FILE_CHOOSER_DIALOG + || decorationStyle == JRootPane.QUESTION_DIALOG + || decorationStyle == JRootPane.WARNING_DIALOG) { + createActions(); + createButtons(); + initMaterialButtonClose(); + myCloseButton.setFocusable(false); + myCloseButton.setVisible(true); + add(myCloseButton); + } + } + + /** This is method for init style button into JDialog */ + protected void initMaterialButtonClose() { + MaterialManagerListener.removeAllMaterialMouseListener(myCloseButton); + myCloseButton.setBackground(UIManager.getColor("OptionPane.errorDialog.titlePane.background")); + myCloseButton.setAction(myCloseAction); + } + + protected void determineColors() { + switch (getWindowDecorationStyle()) { + case JRootPane.FRAME: + myActiveBackground = UIManager.getColor("Material.activeCaption"); + myActiveForeground = UIManager.getColor("Material.activeCaptionText"); + myActiveShadow = UIManager.getColor("Material.activeCaptionBorder"); + break; + case JRootPane.ERROR_DIALOG: + myActiveBackground = UIManager.getColor("OptionPane.errorDialog.titlePane.background"); + myActiveForeground = UIManager.getColor("OptionPane.errorDialog.titlePane.foreground"); + myActiveShadow = UIManager.getColor("OptionPane.errorDialog.titlePane.shadow"); + break; + case JRootPane.QUESTION_DIALOG: + myActiveBackground = UIManager.getColor("OptionPane.questionDialog.titlePane.background"); + myActiveForeground = UIManager.getColor("OptionPane.questionDialog.titlePane.foreground"); + myActiveShadow = UIManager.getColor("OptionPane.questionDialog.titlePane.shadow"); + break; + case JRootPane.COLOR_CHOOSER_DIALOG: + myActiveBackground = UIManager.getColor("OptionPane.questionDialog.titlePane.background"); + myActiveForeground = UIManager.getColor("OptionPane.questionDialog.titlePane.foreground"); + myActiveShadow = UIManager.getColor("OptionPane.questionDialog.titlePane.shadow"); + break; + case JRootPane.FILE_CHOOSER_DIALOG: + myActiveBackground = UIManager.getColor("OptionPane.questionDialog.titlePane.background"); + myActiveForeground = UIManager.getColor("OptionPane.questionDialog.titlePane.foreground"); + myActiveShadow = myActiveBackground; + break; + case JRootPane.WARNING_DIALOG: + myActiveBackground = UIManager.getColor("OptionPane.warningDialog.titlePane.background"); + myActiveForeground = UIManager.getColor("OptionPane.warningDialog.titlePane.foreground"); + myActiveShadow = UIManager.getColor("OptionPane.warningDialog.titlePane.shadow"); + break; + case JRootPane.PLAIN_DIALOG: + myActiveBackground = UIManager.getColor("OptionPane.questionDialog.titlePane.background"); + myActiveForeground = UIManager.getColor("OptionPane.questionDialog.titlePane.foreground"); + myActiveShadow = UIManager.getColor("OptionPane.questionDialog.titlePane.shadow"); + break; + case JRootPane.INFORMATION_DIALOG: + myActiveBackground = UIManager.getColor("OptionPane.errorDialog.titlePane.background"); + myActiveForeground = UIManager.getColor("OptionPane.errorDialog.titlePane.foreground"); + myActiveShadow = UIManager.getColor("OptionPane.errorDialog.titlePane.shadow"); + break; + default: + myActiveBackground = UIManager.getColor("Material.activeCaption"); + myActiveForeground = UIManager.getColor("Material.activeCaptionText"); + myActiveShadow = UIManager.getColor("Material.activeCaptionBorder"); + break; + } + } + + protected void installDefaults() { + setFont(UIManager.getFont("InternalFrame.titleFont", getLocale())); + } + + protected void close() { + Window window = getWindow(); + if (window != null) { + window.dispatchEvent(new WindowEvent(window, WindowEvent.WINDOW_CLOSING)); + } + } + + protected void iconify() { + Frame frame = getFrame(); + if (frame != null) { + frame.setExtendedState(myState | Frame.ICONIFIED); + } + } + + protected void maximize() { + Frame frame = getFrame(); + if (frame != null) { + frame.setExtendedState(myState | Frame.MAXIMIZED_BOTH); + } + } + + protected void restore() { + Frame frame = getFrame(); + if (frame == null) { + return; + } + if ((myState & Frame.ICONIFIED) != 0) { + frame.setExtendedState(myState & ~Frame.ICONIFIED); + } else { + frame.setExtendedState(myState & ~Frame.MAXIMIZED_BOTH); + } + } + + protected void createActions() { + myCloseAction = new CloseAction(); + if (getWindowDecorationStyle() == JRootPane.FRAME) { + myIconifyAction = new IconifyAction(); + myRestoreAction = new RestoreAction(); + myMaximizeAction = new MaximizeAction(); + } + } + + protected JMenu createMenu() { + JMenu menu = new JMenu(""); + if (getWindowDecorationStyle() == JRootPane.FRAME) { + addMenuItems(menu); + } + return menu; + } + + protected void addMenuItems(JMenu menu) { + menu.add(myRestoreAction); + menu.add(myIconifyAction); + if (Toolkit.getDefaultToolkit().isFrameStateSupported(Frame.MAXIMIZED_BOTH)) { + menu.add(myMaximizeAction); + } + menu.add(new JSeparator()); + menu.add(myCloseAction); + } + + protected static JButton createButton(String accessibleName, Icon icon, Action action) { + JButton button = new TitlePaneButton(Color.BLACK, true); + button.putClientProperty("paintActive", Boolean.TRUE); + button.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY, accessibleName); + button.setAction(action); + button.setIcon(icon); + return button; + } + + @Override + public void updateUI() { + super.updateUI(); + + if (getWindowDecorationStyle() == JRootPane.FRAME) { + myMaximizeIcon = UIManager.getIcon("InternalFrame.maximizeIcon"); + myMinimizeIcon = UIManager.getIcon("InternalFrame.minimizeIcon"); + Icon iconClose = UIManager.getIcon("InternalFrame.closeIcon"); + myCloseButton.setIcon(iconClose); + myIconifyButton.setIcon(myMinimizeIcon); + myToggleButton.setIcon(myMaximizeIcon); + } + } + + protected void createButtons() { + myCloseButton = + createButton("Close", UIManager.getIcon("InternalFrame.closeIcon"), myCloseAction); + + if (getWindowDecorationStyle() == JRootPane.FRAME) { + myMaximizeIcon = UIManager.getIcon("InternalFrame.maximizeIcon"); + myMinimizeIcon = UIManager.getIcon("InternalFrame.minimizeIcon"); + + myIconifyButton = + createButton("Iconify", UIManager.getIcon("InternalFrame.iconifyIcon"), myIconifyAction); + myToggleButton = createButton("Maximize", myMaximizeIcon, myRestoreAction); + + myCloseButton.setBackground(myActiveBackground); + myIconifyButton.setBackground(myActiveBackground); + myToggleButton.setBackground(myActiveBackground); + } + } + + protected LayoutManager createLayout() { + return new TitlePaneLayout(); + } + + protected void setActive(boolean active) { + myCloseButton.putClientProperty("paintActive", active); + + if (getWindowDecorationStyle() == JRootPane.FRAME) { + myIconifyButton.putClientProperty("paintActive", active); + myToggleButton.putClientProperty("paintActive", active); + } + + getRootPane().repaint(); + } + + protected void setState(int state) { + setState(state, false); + } + + protected void setState(int state, boolean updateRegardless) { + Window wnd = getWindow(); + + if (wnd != null && getWindowDecorationStyle() == JRootPane.FRAME) { + if (myState == state && !updateRegardless) { + return; + } + Frame frame = getFrame(); + + if (frame != null) { + JRootPane rootPane = getRootPane(); + + if (((state & Frame.MAXIMIZED_BOTH) != 0) + && (rootPane.getBorder() == null || (rootPane.getBorder() instanceof UIResource)) + && frame.isShowing()) { + rootPane.setBorder(null); + } else if ((state & Frame.MAXIMIZED_BOTH) == 0) { + // This is a croak, if state becomes bound, this can + // be nuked. + // rootPaneUI.installBorder(rootPane); + } + if (frame.isResizable()) { + if ((state & Frame.MAXIMIZED_BOTH) != 0) { + updateToggleButton(myRestoreAction, myMinimizeIcon); + myMaximizeAction.setEnabled(false); + myRestoreAction.setEnabled(true); + } else { + updateToggleButton(myMaximizeAction, myMaximizeIcon); + myMaximizeAction.setEnabled(true); + myRestoreAction.setEnabled(false); + } + if (myToggleButton.getParent() == null || myIconifyButton.getParent() == null) { + add(myToggleButton); + add(myIconifyButton); + revalidate(); + repaint(); + } + myToggleButton.setText(null); + } else { + myMaximizeAction.setEnabled(false); + myRestoreAction.setEnabled(false); + if (myToggleButton.getParent() != null) { + remove(myToggleButton); + revalidate(); + repaint(); + } + } + } else { + // Not contained in a Frame + myMaximizeAction.setEnabled(false); + myRestoreAction.setEnabled(false); + myIconifyAction.setEnabled(false); + remove(myToggleButton); + remove(myIconifyButton); + revalidate(); + repaint(); + } + myCloseAction.setEnabled(true); + myState = state; + } + } + + protected void updateToggleButton(Action action, Icon icon) { + myToggleButton.setAction(action); + myToggleButton.setIcon(icon); + myToggleButton.setText(null); + } + + protected Frame getFrame() { + Window window = getWindow(); + + if (window instanceof Frame) { + return (Frame) window; + } + return null; + } + + protected Window getWindow() { + return myWindow; + } + + protected String getTitle() { + Window w = getWindow(); + + if (w instanceof Frame) { + return ((Frame) w).getTitle(); + } else if (w instanceof Dialog) { + return ((Dialog) w).getTitle(); + } + return null; + } + + protected void paintComponent(Graphics g) { + if (getFrame() != null) { + setState(getFrame().getExtendedState()); + } + JRootPane rootPane = getRootPane(); + Window window = getWindow(); + boolean leftToRight = + (window == null) + ? rootPane.getComponentOrientation().isLeftToRight() + : window.getComponentOrientation().isLeftToRight(); + boolean isSelected = (window == null) ? true : window.isActive(); + int width = getWidth(); + int height = getHeight(); + + Color background; + Color foreground; + Color darkShadow; + + if (isSelected) { + background = myActiveBackground; + foreground = myActiveForeground; + darkShadow = myActiveShadow; + } else { + background = myInactiveBackground; + foreground = myInactiveForeground; + darkShadow = myInactiveShadow; + } + + g.setColor(background); + g.fillRect(0, 0, width, height); + + g.setColor(darkShadow); + g.drawLine(0, height - 1, width, height - 1); + g.drawLine(0, 0, 0, 0); + g.drawLine(width - 1, 0, width - 1, 0); + + int xOffset = leftToRight ? 5 : width - 5; + + if (getWindowDecorationStyle() == JRootPane.FRAME) { + xOffset += leftToRight ? IMAGE_WIDTH + 5 : -IMAGE_WIDTH - 5; + } + + String theTitle = getTitle(); + if (theTitle != null) { + g = MaterialDrawingUtils.getAliasedGraphics(g); + FontMetrics fm = g.getFontMetrics(rootPane.getFont()); + + g.setColor(foreground); + + int yOffset = ((height - fm.getHeight()) / 2) + fm.getAscent(); + + Rectangle rect = new Rectangle(0, 0, 0, 0); + if (myIconifyButton != null && myIconifyButton.getParent() != null) { + rect = myIconifyButton.getBounds(); + } + int titleW; + + if (leftToRight) { + if (rect.x == 0) { + rect.x = window.getWidth() - window.getInsets().right - 2; + } + titleW = rect.x - xOffset - 4; + theTitle = + WrapperSwingUtilities.getInstance().getClippedString(rootPane, fm, theTitle, titleW); + // theTitle = BasicGraphicsUtils.getClippedString(rootPane, fm, theTitle, titleW); + } else { + titleW = xOffset - rect.x - rect.width - 4; + theTitle = + WrapperSwingUtilities.getInstance().getClippedString(rootPane, fm, theTitle, titleW); + xOffset -= fm.stringWidth(theTitle); + } + // int titleLength = SwingUtilities2.stringWidth(rootPane, fm, theTitle); + int titleLength = fm.stringWidth(theTitle); + g.drawString(theTitle, xOffset, yOffset); + xOffset += leftToRight ? titleLength + 5 : -5; + } + } + + protected class CloseAction extends AbstractAction { + public CloseAction() { + super(UIManager.getString("MaterialTitlePane.closeTitle", getLocale())); + } + + public void actionPerformed(ActionEvent e) { + close(); + } + } + + protected class IconifyAction extends AbstractAction { + public IconifyAction() { + super(UIManager.getString("MaterialTitlePane.iconifyTitle", getLocale())); + } + + public void actionPerformed(ActionEvent e) { + iconify(); + } + } + + protected class RestoreAction extends AbstractAction { + public RestoreAction() { + super(UIManager.getString("MaterialTitlePane.restoreTitle", getLocale())); + } + + public void actionPerformed(ActionEvent e) { + restore(); + } + } + + protected class MaximizeAction extends AbstractAction { + public MaximizeAction() { + super(UIManager.getString("MaterialTitlePane.maximizeTitle", getLocale())); + } + + public void actionPerformed(ActionEvent e) { + maximize(); + } + } + + protected void activeChanged(boolean active) { + boolean hasEmbeddedMenuBar = + myRootPane.getJMenuBar() != null && myRootPane.getJMenuBar().isVisible(); + Color background = active ? myActiveBackground : myInactiveBackground; + Color foreground = active ? myActiveForeground : myInactiveForeground; + Color titleForeground = foreground; + + setBackground(background); + myWindow.setForeground(titleForeground); + // myIconifyButton.setForeground(foreground); + // maximizeButton.setForeground(foreground); + myIconifyButton.setForeground(foreground); + myCloseButton.setForeground(foreground); + + // titleLabel.setHorizontalAlignment(hasEmbeddedMenuBar ? SwingConstants.CENTER : + // SwingConstants.LEADING); + + // this is necessary because hover/pressed colors are derived from background color + myIconifyButton.setBackground(background); + // maximizeButton.setBackground(background); + myToggleButton.setBackground(background); + myCloseButton.setBackground(background); + } + + protected class TitlePaneLayout implements LayoutManager { + + public void addLayoutComponent(String name, Component c) {} + + public void removeLayoutComponent(Component c) {} + + public Dimension preferredLayoutSize(Container c) { + int height = computeHeight(); + //noinspection SuspiciousNameCombination + return new Dimension(height, height); + } + + public Dimension minimumLayoutSize(Container c) { + return preferredLayoutSize(c); + } + + private int computeHeight() { + FontMetrics fm = myRootPane.getFontMetrics(getFont()); + int fontHeight = fm.getHeight(); + fontHeight += 7; + int iconHeight = 0; + if (getWindowDecorationStyle() == JRootPane.FRAME) { + iconHeight = IMAGE_HEIGHT; + } + + return Math.max(fontHeight, iconHeight); + } + + public void layoutContainer(Container c) { + boolean leftToRight = + (myWindow == null) + ? getRootPane().getComponentOrientation().isLeftToRight() + : myWindow.getComponentOrientation().isLeftToRight(); + + int w = getWidth(); + int x; + int y = 3; + int spacing; + int buttonHeight; + int buttonWidth; + + if (myCloseButton != null && myCloseButton.getIcon() != null) { + buttonHeight = myCloseButton.getIcon().getIconHeight(); + buttonWidth = myCloseButton.getIcon().getIconWidth(); + } else { + buttonHeight = IMAGE_HEIGHT; + buttonWidth = IMAGE_WIDTH; + } + + x = leftToRight ? w : 0; + + spacing = 5; + x = leftToRight ? spacing : w - buttonWidth - spacing; + + x = leftToRight ? w : 0; + spacing = 4; + x += leftToRight ? -spacing - buttonWidth : spacing; + if (myCloseButton != null) { + myCloseButton.setBounds(x, y, buttonWidth, buttonHeight); + } + + if (!leftToRight) x += buttonWidth; + + if (getWindowDecorationStyle() == JRootPane.FRAME) { + if (Toolkit.getDefaultToolkit().isFrameStateSupported(Frame.MAXIMIZED_BOTH)) { + if (myToggleButton.getParent() != null) { + spacing = 10; + x += leftToRight ? -spacing - buttonWidth : spacing; + myToggleButton.setBounds(x, y, buttonWidth, buttonHeight); + if (!leftToRight) { + x += buttonWidth; + } + } + } + + if (myIconifyButton != null && myIconifyButton.getParent() != null) { + spacing = 2; + x += leftToRight ? -spacing - buttonWidth : spacing; + myIconifyButton.setBounds(x, y, buttonWidth, buttonHeight); + if (!leftToRight) { + x += buttonWidth; + } + } + } + } + } + + protected class PropertyChangeHandler implements PropertyChangeListener { + public void propertyChange(PropertyChangeEvent pce) { + String name = pce.getPropertyName(); + + if ("resizable".equals(name) || "state".equals(name)) { + Frame frame = getFrame(); + + if (frame != null) { + setState(frame.getExtendedState(), true); + } + if ("resizable".equals(name)) { + getRootPane().repaint(); + } + } else if ("title".equals(name)) { + repaint(); + } else if ("componentOrientation".equals(name)) { + revalidate(); + repaint(); + } else if ("iconImage".equals(name)) { + updateSystemIcon(); + revalidate(); + repaint(); + } + } + } + + protected void updateSystemIcon() { + Window window = getWindow(); + if (window == null) { + mySystemIcon = null; + return; + } + + List icons = window.getIconImages(); + assert icons != null; + + if (icons.size() == 0) { + mySystemIcon = null; + } else if (icons.size() == 1) { + mySystemIcon = icons.get(0); + } else { + mySystemIcon = icons.get(0); + // TODO: find cross-platofrm replacement for this? + // mySystemIcon = SunToolkit.getScaledIconImage(icons, IMAGE_WIDTH, IMAGE_HEIGHT); + } + } + + protected class MaterialHandler extends WindowAdapter + implements PropertyChangeListener, MouseListener, MouseMotionListener, ComponentListener { + + private Window window; + + public MaterialHandler(Window window) { + this.window = window; + } + + @Override + public void propertyChange(PropertyChangeEvent e) { + /* switch( e.getPropertyName() ) { + case "title": + titleLabel.setText( getWindowTitle() ); + break; + + case "resizable": + if( window instanceof Frame ) + frameStateChanged(); + break; + + case "iconImage": + updateIcon(); + break; + } */ + } + + @Override + public void windowActivated(WindowEvent e) { + // activeChanged( true ); + // repaintWindowBorder(); + } + + @Override + public void windowDeactivated(WindowEvent e) { + // activeChanged( false ); + // repaintWindowBorder(); + } + + @Override + public void windowStateChanged(WindowEvent e) { + // frameStateChanged(); + } + + // ---- interface MouseListener ---- + + private Point dragOffset; + + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton(e)) { + if (e.getSource() == getTitle()) { + close(); + } else if (window instanceof Frame && ((Frame) window).isResizable()) { + Frame frame = (Frame) window; + if ((frame.getExtendedState() & Frame.MAXIMIZED_BOTH) != 0) restore(); + else maximize(); + } + } + } + + @Override + public void mousePressed(MouseEvent e) { + if (window == null) return; // should newer occur + dragOffset = SwingUtilities.convertPoint(MaterialTitlePane.this, e.getPoint(), window); + } + + @Override + public void mouseReleased(MouseEvent e) {} + + @Override + public void mouseEntered(MouseEvent e) {} + + @Override + public void mouseExited(MouseEvent e) {} + + @Override + public void mouseDragged(MouseEvent e) { + if (window == null) return; // should newer occur + if (window instanceof Frame) { + Frame frame = (Frame) window; + int state = frame.getExtendedState(); + if ((state & Frame.MAXIMIZED_BOTH) != 0) { + int maximizedWidth = window.getWidth(); + + // restore window size, which also moves window to pre-maximized location + frame.setExtendedState(state & ~Frame.MAXIMIZED_BOTH); + + // fix drag offset to ensure that window remains under mouse position + // for the case that dragging starts in the right area of the maximized window + int restoredWidth = window.getWidth(); + int center = restoredWidth / 2; + if (dragOffset.x > center) { + // this is same/similar to what Windows 10 does + if (dragOffset.x > maximizedWidth - center) + dragOffset.x = restoredWidth - (maximizedWidth - dragOffset.x); + else dragOffset.x = center; + } + } + } + // compute new window location + int newX = e.getXOnScreen() - dragOffset.x; + int newY = e.getYOnScreen() - dragOffset.y; + + if (newX == window.getX() && newY == window.getY()) return; + // move window + window.setLocation(newX, newY); + } + + @Override + public void mouseMoved(MouseEvent e) {} + + @Override + public void componentResized(ComponentEvent e) {} + + @Override + public void componentShown(ComponentEvent e) { + // necessary for the case that the frame is maximized before it is shown + // frameStateChanged(); + } + + @Override + public void componentMoved(ComponentEvent e) {} + + @Override + public void componentHidden(ComponentEvent e) {} + } + + protected static class TitlePaneButton extends JButton { + + protected Color background; + protected Boolean state; + + public TitlePaneButton(Color background, Boolean state) { + this.background = background; + this.state = state; + } + + @Override + protected void init(String text, Icon icon) { + super.init(text, icon); + setUI(new TitlePaneButtonUI()); + } + + private static class TitlePaneButtonUI extends MaterialButtonUI { + + @Override + public void installUI(JComponent c) { + mouseHoverEnabled = false; + super.installUI(c); + c.setFocusable(false); + c.setOpaque(true); + c.setBackground(background); + c.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + c.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + } + + @Override + protected void paintButtonPressed(Graphics g, AbstractButton b) {} + } + } +} diff --git a/src/main/java/mdlaf/utils/FlatWindowResizer.java b/src/main/java/mdlaf/utils/FlatWindowResizer.java new file mode 100644 index 0000000..ae6a15b --- /dev/null +++ b/src/main/java/mdlaf/utils/FlatWindowResizer.java @@ -0,0 +1,567 @@ +package mdlaf.utils; +/* + * Copyright 2020 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import static java.awt.Cursor.*; +import static javax.swing.SwingConstants.*; + +import java.awt.*; +import java.awt.event.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.function.Supplier; +import javax.swing.*; + +/** + * Resizes frames, dialogs or internal frames. + * + *

Could also be used to implement resize support for any Swing component by creating a new + * subclass. + * + * @author Karl Tauber + */ +public abstract class FlatWindowResizer implements PropertyChangeListener, ComponentListener { + protected static final Integer WINDOW_RESIZER_LAYER = JLayeredPane.DRAG_LAYER + 1; + + protected final JComponent resizeComp; + + protected final int borderDragThickness = 5; + protected final int cornerDragWidth = 5; + protected final boolean honorFrameMinimumSizeOnResize = + UIManager.getBoolean("RootPane.honorFrameMinimumSizeOnResize"); + protected final boolean honorDialogMinimumSizeOnResize = + UIManager.getBoolean("RootPane.honorDialogMinimumSizeOnResize"); + + protected final DragBorderComponent topDragComp; + protected final DragBorderComponent bottomDragComp; + protected final DragBorderComponent leftDragComp; + protected final DragBorderComponent rightDragComp; + + protected FlatWindowResizer(JComponent resizeComp) { + this.resizeComp = resizeComp; + + topDragComp = createDragBorderComponent(NW_RESIZE_CURSOR, N_RESIZE_CURSOR, NE_RESIZE_CURSOR); + bottomDragComp = createDragBorderComponent(SW_RESIZE_CURSOR, S_RESIZE_CURSOR, SE_RESIZE_CURSOR); + leftDragComp = createDragBorderComponent(NW_RESIZE_CURSOR, W_RESIZE_CURSOR, SW_RESIZE_CURSOR); + rightDragComp = createDragBorderComponent(NE_RESIZE_CURSOR, E_RESIZE_CURSOR, SE_RESIZE_CURSOR); + + Container cont = + (resizeComp instanceof JRootPane) ? ((JRootPane) resizeComp).getLayeredPane() : resizeComp; + Object cons = (cont instanceof JLayeredPane) ? WINDOW_RESIZER_LAYER : null; + cont.add(topDragComp, cons, 0); + cont.add(bottomDragComp, cons, 1); + cont.add(leftDragComp, cons, 2); + cont.add(rightDragComp, cons, 3); + + resizeComp.addComponentListener(this); + resizeComp.addPropertyChangeListener("ancestor", this); + + if (resizeComp.isDisplayable()) addNotify(); + } + + protected DragBorderComponent createDragBorderComponent( + int leadingResizeDir, int centerResizeDir, int trailingResizeDir) { + return new DragBorderComponent(leadingResizeDir, centerResizeDir, trailingResizeDir); + } + + public void uninstall() { + removeNotify(); + + resizeComp.removeComponentListener(this); + resizeComp.removePropertyChangeListener("ancestor", this); + + Container cont = topDragComp.getParent(); + cont.remove(topDragComp); + cont.remove(bottomDragComp); + cont.remove(leftDragComp); + cont.remove(rightDragComp); + } + + public void doLayout() { + if (!topDragComp.isVisible()) return; + + int x = 0; + int y = 0; + int width = resizeComp.getWidth(); + int height = resizeComp.getHeight(); + if (width == 0 || height == 0) return; + + Insets resizeInsets = getResizeInsets(); + int thickness = borderDragThickness; + int topThickness = Math.max(resizeInsets.top, thickness); + int bottomThickness = Math.max(resizeInsets.bottom, thickness); + int leftThickness = Math.max(resizeInsets.left, thickness); + int rightThickness = Math.max(resizeInsets.right, thickness); + int y2 = y + topThickness; + int height2 = height - topThickness - bottomThickness; + + // set bounds of drag components + topDragComp.setBounds(x, y, width, topThickness); + bottomDragComp.setBounds(x, y + height - bottomThickness, width, bottomThickness); + leftDragComp.setBounds(x, y2, leftThickness, height2); + rightDragComp.setBounds(x + width - rightThickness, y2, rightThickness, height2); + + // set corner drag widths + int cornerDelta = cornerDragWidth - borderDragThickness; + topDragComp.setCornerDragWidths(leftThickness + cornerDelta, rightThickness + cornerDelta); + bottomDragComp.setCornerDragWidths(leftThickness + cornerDelta, rightThickness + cornerDelta); + leftDragComp.setCornerDragWidths(cornerDelta, cornerDelta); + rightDragComp.setCornerDragWidths(cornerDelta, cornerDelta); + } + + protected Insets getResizeInsets() { + return new Insets(0, 0, 0, 0); + } + + protected void addNotify() { + updateVisibility(); + } + + protected void removeNotify() { + updateVisibility(); + } + + protected void updateVisibility() { + boolean visible = isWindowResizable(); + if (visible == topDragComp.isVisible()) return; + + topDragComp.setVisible(visible); + bottomDragComp.setVisible(visible); + leftDragComp.setVisible(visible); + + // The east component is not hidden, instead its bounds are set to 0,0,1,1 and + // it is disabled. This is necessary so that DragBorderComponent.paintComponent() is invoked. + rightDragComp.setEnabled(visible); + if (visible) { + rightDragComp.setVisible(true); // necessary because it is initially invisible + doLayout(); + } else rightDragComp.setBounds(0, 0, 1, 1); + } + + boolean isDialog() { + return false; + } + + protected abstract boolean isWindowResizable(); + + protected abstract Rectangle getWindowBounds(); + + protected abstract void setWindowBounds(Rectangle r); + + protected abstract boolean honorMinimumSizeOnResize(); + + protected abstract Dimension getWindowMinimumSize(); + + protected void beginResizing(int direction) {} + + protected void endResizing() {} + + // ---- interface PropertyChangeListener ---- + + @Override + public void propertyChange(PropertyChangeEvent e) { + switch (e.getPropertyName()) { + case "ancestor": + if (e.getNewValue() != null) addNotify(); + else removeNotify(); + break; + + case "resizable": + updateVisibility(); + break; + } + } + + // ---- interface ComponentListener ---- + + @Override + public void componentResized(ComponentEvent e) { + doLayout(); + } + + @Override + public void componentMoved(ComponentEvent e) {} + + @Override + public void componentShown(ComponentEvent e) {} + + @Override + public void componentHidden(ComponentEvent e) {} + + // ---- class WindowResizer ------------------------------------------------ + + /** Resizes frames and dialogs. */ + public static class WindowResizer extends FlatWindowResizer implements WindowStateListener { + protected Window window; + + public WindowResizer(JRootPane rootPane) { + super(rootPane); + } + + @Override + protected void addNotify() { + Container parent = resizeComp.getParent(); + window = (parent instanceof Window) ? (Window) parent : null; + if (window instanceof Frame) { + window.addPropertyChangeListener("resizable", this); + window.addWindowStateListener(this); + } + + super.addNotify(); + } + + @Override + protected void removeNotify() { + if (window instanceof Frame) { + window.removePropertyChangeListener("resizable", this); + window.removeWindowStateListener(this); + } + window = null; + + super.removeNotify(); + } + + @Override + protected boolean isWindowResizable() { + // if( FlatUIUtils.isFullScreen( resizeComp ) ) + if (false) return false; + if (window instanceof Frame) + return ((Frame) window).isResizable() + && (((Frame) window).getExtendedState() & Frame.MAXIMIZED_BOTH) == 0; + if (window instanceof Dialog) return ((Dialog) window).isResizable(); + return false; + } + + @Override + protected Rectangle getWindowBounds() { + return window.getBounds(); + } + + @Override + protected void setWindowBounds(Rectangle r) { + window.setBounds(r); + + // immediately layout drag border components + doLayout(); + + if (Toolkit.getDefaultToolkit().isDynamicLayoutActive()) { + window.validate(); + resizeComp.repaint(); + } + } + + @Override + protected boolean honorMinimumSizeOnResize() { + return (honorFrameMinimumSizeOnResize && window instanceof Frame) + || (honorDialogMinimumSizeOnResize && window instanceof Dialog); + } + + @Override + protected Dimension getWindowMinimumSize() { + return window.getMinimumSize(); + } + + @Override + boolean isDialog() { + return window instanceof Dialog; + } + + @Override + public void windowStateChanged(WindowEvent e) { + updateVisibility(); + } + } + + // ---- class InternalFrameResizer ----------------------------------------- + + /** Resizes internal frames. */ + public static class InternalFrameResizer extends FlatWindowResizer { + protected final Supplier desktopManager; + + public InternalFrameResizer(JInternalFrame frame, Supplier desktopManager) { + super(frame); + this.desktopManager = desktopManager; + + frame.addPropertyChangeListener("resizable", this); + } + + @Override + public void uninstall() { + getFrame().removePropertyChangeListener("resizable", this); + + super.uninstall(); + } + + private JInternalFrame getFrame() { + return (JInternalFrame) resizeComp; + } + + @Override + protected Insets getResizeInsets() { + return getFrame().getInsets(); + } + + @Override + protected boolean isWindowResizable() { + return getFrame().isResizable(); + } + + @Override + protected Rectangle getWindowBounds() { + return getFrame().getBounds(); + } + + @Override + protected void setWindowBounds(Rectangle r) { + desktopManager.get().resizeFrame(getFrame(), r.x, r.y, r.width, r.height); + } + + @Override + protected boolean honorMinimumSizeOnResize() { + return true; + } + + @Override + protected Dimension getWindowMinimumSize() { + return getFrame().getMinimumSize(); + } + + @Override + protected void beginResizing(int direction) { + desktopManager.get().beginResizingFrame(getFrame(), direction); + } + + @Override + protected void endResizing() { + desktopManager.get().endResizingFrame(getFrame()); + } + } + + // ---- class DragBorderComponent ------------------------------------------ + + protected class DragBorderComponent extends JComponent + implements MouseListener, MouseMotionListener { + private final int leadingResizeDir; + private final int centerResizeDir; + private final int trailingResizeDir; + + private int resizeDir = -1; + + private int leadingCornerDragWidth; + private int trailingCornerDragWidth; + + // offsets of mouse position to window edges + private int dragLeftOffset; + private int dragRightOffset; + private int dragTopOffset; + private int dragBottomOffset; + + protected DragBorderComponent( + int leadingResizeDir, int centerResizeDir, int trailingResizeDir) { + this.leadingResizeDir = leadingResizeDir; + this.centerResizeDir = centerResizeDir; + this.trailingResizeDir = trailingResizeDir; + + setResizeDir(centerResizeDir); + setVisible(false); + + addMouseListener(this); + addMouseMotionListener(this); + } + + void setCornerDragWidths(int leading, int trailing) { + leadingCornerDragWidth = leading; + trailingCornerDragWidth = trailing; + } + + protected void setResizeDir(int resizeDir) { + if (this.resizeDir == resizeDir) return; + this.resizeDir = resizeDir; + + setCursor(getPredefinedCursor(resizeDir)); + } + + @Override + public Dimension getPreferredSize() { + int thickness = borderDragThickness; + return new Dimension(thickness, thickness); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintChildren(g); + + // for dialogs: necessary because Dialog.setResizable() does not fire events + // for frames: necessary because GraphicsDevice.setFullScreenWindow() does not fire events + updateVisibility(); + + /*debug + int width = getWidth(); + int height = getHeight(); + + g.setColor( java.awt.Color.blue ); + boolean topOrBottom = (centerResizeDir == N_RESIZE_CURSOR || centerResizeDir == S_RESIZE_CURSOR); + if( topOrBottom ) { + g.drawLine( leadingCornerDragWidth, 0, leadingCornerDragWidth, height ); + g.drawLine( width - trailingCornerDragWidth, 0, width - trailingCornerDragWidth, height ); + } else { + g.drawLine( 0, leadingCornerDragWidth, width, leadingCornerDragWidth ); + g.drawLine( 0, height - trailingCornerDragWidth, width, height - trailingCornerDragWidth ); + } + + g.setColor( java.awt.Color.red ); + g.drawRect( 0, 0, width - 1, height - 1 ); + debug*/ + } + + @Override + public void mouseClicked(MouseEvent e) {} + + @Override + public void mousePressed(MouseEvent e) { + if (!isWindowResizable()) return; + + int xOnScreen = e.getXOnScreen(); + int yOnScreen = e.getYOnScreen(); + Rectangle windowBounds = getWindowBounds(); + + // compute offsets of mouse position to window edges + dragLeftOffset = xOnScreen - windowBounds.x; + dragTopOffset = yOnScreen - windowBounds.y; + dragRightOffset = windowBounds.x + windowBounds.width - xOnScreen; + dragBottomOffset = windowBounds.y + windowBounds.height - yOnScreen; + + int direction = 0; + switch (resizeDir) { + case N_RESIZE_CURSOR: + direction = NORTH; + break; + case S_RESIZE_CURSOR: + direction = SOUTH; + break; + case W_RESIZE_CURSOR: + direction = WEST; + break; + case E_RESIZE_CURSOR: + direction = EAST; + break; + case NW_RESIZE_CURSOR: + direction = NORTH_WEST; + break; + case NE_RESIZE_CURSOR: + direction = NORTH_EAST; + break; + case SW_RESIZE_CURSOR: + direction = SOUTH_WEST; + break; + case SE_RESIZE_CURSOR: + direction = SOUTH_EAST; + break; + } + beginResizing(direction); + } + + @Override + public void mouseReleased(MouseEvent e) { + if (!isWindowResizable()) return; + + dragLeftOffset = dragRightOffset = dragTopOffset = dragBottomOffset = 0; + + endResizing(); + } + + @Override + public void mouseEntered(MouseEvent e) {} + + @Override + public void mouseExited(MouseEvent e) {} + + @Override + public void mouseMoved(MouseEvent e) { + boolean topOrBottom = + (centerResizeDir == N_RESIZE_CURSOR || centerResizeDir == S_RESIZE_CURSOR); + int xy = topOrBottom ? e.getX() : e.getY(); + int wh = topOrBottom ? getWidth() : getHeight(); + + setResizeDir( + xy <= leadingCornerDragWidth + ? leadingResizeDir + : (xy >= wh - trailingCornerDragWidth ? trailingResizeDir : centerResizeDir)); + } + + @Override + public void mouseDragged(MouseEvent e) { + if (!isWindowResizable()) return; + + int xOnScreen = e.getXOnScreen(); + int yOnScreen = e.getYOnScreen(); + + // Get current window bounds and compute new bounds based them. + // This is necessary because window manager may alter window bounds while resizing. + // E.g. when having two monitors with different scale factors and resizing + // a window on first screen to the second screen, then the window manager may + // decide at some point that the window should be only on second screen + // and adjusts its bounds. + Rectangle oldBounds = getWindowBounds(); + Rectangle newBounds = new Rectangle(oldBounds); + + // compute new window bounds + + // top + if (resizeDir == N_RESIZE_CURSOR + || resizeDir == NW_RESIZE_CURSOR + || resizeDir == NE_RESIZE_CURSOR) { + newBounds.y = yOnScreen - dragTopOffset; + newBounds.height += (oldBounds.y - newBounds.y); + } + + // bottom + if (resizeDir == S_RESIZE_CURSOR + || resizeDir == SW_RESIZE_CURSOR + || resizeDir == SE_RESIZE_CURSOR) + newBounds.height = (yOnScreen + dragBottomOffset) - newBounds.y; + + // left + if (resizeDir == W_RESIZE_CURSOR + || resizeDir == NW_RESIZE_CURSOR + || resizeDir == SW_RESIZE_CURSOR) { + newBounds.x = xOnScreen - dragLeftOffset; + newBounds.width += (oldBounds.x - newBounds.x); + } + + // right + if (resizeDir == E_RESIZE_CURSOR + || resizeDir == NE_RESIZE_CURSOR + || resizeDir == SE_RESIZE_CURSOR) + newBounds.width = (xOnScreen + dragRightOffset) - newBounds.x; + + // apply minimum window size + Dimension minimumSize = honorMinimumSizeOnResize() ? getWindowMinimumSize() : null; + if (minimumSize == null) minimumSize = new Dimension(150, 50); + if (newBounds.width < minimumSize.width) { + if (newBounds.x != oldBounds.x) newBounds.x -= (minimumSize.width - newBounds.width); + newBounds.width = minimumSize.width; + } + if (newBounds.height < minimumSize.height) { + if (newBounds.y != oldBounds.y) newBounds.y -= (minimumSize.height - newBounds.height); + newBounds.height = minimumSize.height; + } + + // set window bounds + if (!newBounds.equals(oldBounds)) setWindowBounds(newBounds); + } + } +} diff --git a/src/test/java/integration/gui/mock/DemoGUITest.java b/src/test/java/integration/gui/mock/DemoGUITest.java index 3576331..75e5cc5 100644 --- a/src/test/java/integration/gui/mock/DemoGUITest.java +++ b/src/test/java/integration/gui/mock/DemoGUITest.java @@ -52,7 +52,7 @@ public class DemoGUITest extends JFrame { static { try { JDialog.setDefaultLookAndFeelDecorated(true); - JFrame.setDefaultLookAndFeelDecorated(false); // not support yet + JFrame.setDefaultLookAndFeelDecorated(true); // not support yet UIManager.setLookAndFeel(new MaterialLookAndFeel(new MaterialLiteTheme())); // UIManager.setLookAndFeel(new MaterialLookAndFeel(new DarkStackOverflowTheme())); UIManager.put(