VAqua.src.org.violetlib.aqua.AquaRootPaneUI Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vaqua Show documentation
Show all versions of vaqua Show documentation
An improved native Swing look and feel for macOS
The newest version!
/*
* Changes Copyright (c) 2015-2023 Alan Snyder.
* All rights reserved.
*
* You may not use, copy or modify this file, except in compliance with the license agreement. For details see
* accompanying license terms.
*/
/*
* Copyright (c) 2011, 2012, 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 org.violetlib.aqua;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.MenuBarUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicRootPaneUI;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static org.violetlib.aqua.AquaVibrantSupport.NO_VIBRANT_STYLE;
import static org.violetlib.aqua.AquaVibrantSupport.WINDOW_BACKGROUND_STYLE;
public class AquaRootPaneUI extends BasicRootPaneUI implements AquaComponentUI,
SystemPropertyChangeManager.SystemPropertyChangeListener {
public final static String AQUA_WINDOW_STYLE_KEY = "Aqua.windowStyle";
public final static String AQUA_WINDOW_TOP_MARGIN_KEY = "Aqua.windowTopMargin";
public final static String AQUA_WINDOW_BOTTOM_MARGIN_KEY = "Aqua.windowBottomMargin";
public final static String AQUA_WINDOW_REPRESENTED_FILENAME_KEY = "Aqua.windowRepresentedFilename";
// Internally, an appearance is represented by an instance of AquaAppearance.
// Externally, the colors are made available as a map from string to color.
static final boolean sUseScreenMenuBar = AquaUtils.getScreenMenuBarProperty();
public static ComponentUI createUI(JComponent c)
{
return new AquaRootPaneUI();
}
private static boolean forceActiveWindowDisplay;
protected JRootPane rootPane;
protected WindowHierarchyListener hierarchyListener;
protected AncestorListener ancestorListener;
protected PropertyChangeListener windowStylePropertyChangeListener;
protected WindowListener windowListener;
protected ContainerListener containerListener;
protected boolean isInitialized;
protected AquaCustomStyledWindow customStyledWindow;
protected int vibrantStyle = NO_VIBRANT_STYLE;
protected boolean vibrantStyleIsExplicitlySet;
/**
Set the debugging option to force all windows to display as active. Used to compare a Java window with a native
active window.
*/
public static void setForceActiveWindowDisplay(boolean b)
{
if (b != forceActiveWindowDisplay) {
forceActiveWindowDisplay = b;
Window[] windows = Window.getWindows();
for (Window w : windows) {
JRootPane rp = AquaUtils.getRootPane(w);
if (rp != null) {
AquaRootPaneUI ui = AquaUtils.getUI(rp, AquaRootPaneUI.class);
if (ui != null) {
boolean shouldBeActive = b || w.isActive();
boolean isActiveStyle = Boolean.TRUE.equals(rp.getClientProperty(AquaFocusHandler.FRAME_ACTIVE_PROPERTY));
if (shouldBeActive != isActiveStyle) {
rp.repaint();
AquaFocusHandler.updateComponentTreeUIActivation(rp, shouldBeActive);
}
}
}
}
}
}
public void installUI(JComponent c)
{
rootPane = (JRootPane) c;
super.installUI(c);
// for OutOfMemoryError swapping menus.
// Listen for layered pane/JMenuBar updates if the screen menu bar is active.
if (sUseScreenMenuBar) {
containerListener = new MyContainerListener();
JRootPane root = (JRootPane) c;
root.addContainerListener(containerListener);
}
configureWindowIfPossible();
configureLayeredPane();
configure();
}
public void uninstallUI(JComponent c)
{
c.removeAncestorListener(ancestorListener);
disposeCustomWindowStyle();
removeVisualEffectView();
unconfigureLayeredPane();
if (sUseScreenMenuBar) {
JRootPane root = (JRootPane) c;
root.removeContainerListener(containerListener);
containerListener = null;
}
isInitialized = false;
vibrantStyle = NO_VIBRANT_STYLE;
vibrantStyleIsExplicitlySet = false;
super.uninstallUI(c);
}
@Override
protected void installListeners(JRootPane root)
{
super.installListeners(root);
hierarchyListener = new WindowHierarchyListener();
root.addHierarchyListener(hierarchyListener);
AquaAppearances.register(root);
ancestorListener = new MyAncestorListener();
root.addAncestorListener(ancestorListener);
windowListener = new MyWindowListener();
AppearanceManager.installListeners(root);
}
@Override
protected void uninstallListeners(JRootPane root)
{
AppearanceManager.uninstallListeners(root);
AquaAppearances.unregister(root);
root.removeHierarchyListener(hierarchyListener);
hierarchyListener = null;
root.removeAncestorListener(ancestorListener);
ancestorListener = null;
Window w = getWindow();
if (w != null) {
w.removeWindowListener(windowListener);
}
windowListener = null;
super.uninstallListeners(root);
if (windowStylePropertyChangeListener != null) {
root.removePropertyChangeListener(windowStylePropertyChangeListener);
windowStylePropertyChangeListener = null;
}
}
@Override
public void systemPropertyChanged(JComponent c, Object type) {
if (type.equals(AquaAppearances.APPEARANCE_CHANGE_TYPE)) {
updateAppearances(null, false);
}
}
private class MyContainerListener
implements ContainerListener
{
/**
If the screen menu bar is active we need to listen to the layered pane of the root pane
because it holds the JMenuBar. So, if a new layered pane was added, listen to it.
If a new JMenuBar was added, tell the menu bar UI, because it will need to update the menu bar.
*/
public void componentAdded(ContainerEvent e)
{
if (e.getContainer() instanceof JRootPane) {
JRootPane root = (JRootPane) e.getContainer();
if (e.getChild() == root.getLayeredPane()) {
configureLayeredPane();
}
} else {
if (e.getChild() instanceof JMenuBar) {
JMenuBar jmb = (JMenuBar) e.getChild();
MenuBarUI mbui = jmb.getUI();
if (mbui.getClass().getName().equals("com.apple.laf.AquaMenuBarUI")) {
Window owningWindow = SwingUtilities.getWindowAncestor(jmb);
// Could be a JDialog, and may have been added to a JRootPane not yet in a window.
if (owningWindow instanceof JFrame) {
JFrame fr = (JFrame) owningWindow;
AquaUtils.setScreenMenuBar(fr, mbui);
}
}
}
}
}
/**
Likewise, when the layered pane is removed from the root pane, stop listening to it.
If the JMenuBar is removed, tell the menu bar UI to clear the menu bar.
*/
public void componentRemoved(ContainerEvent e)
{
if (e.getContainer() instanceof JRootPane) {
JRootPane root = (JRootPane) e.getContainer();
if (e.getChild() == root.getLayeredPane()) {
unconfigureLayeredPane();
}
} else {
if (e.getChild() instanceof JMenuBar) {
JMenuBar jmb = (JMenuBar) e.getChild();
MenuBarUI mbui = jmb.getUI();
if (mbui.getClass().getName().equals("com.apple.laf.AquaMenuBarUI")) {
Window owningWindow = SwingUtilities.getWindowAncestor(jmb);
// Could be a JDialog, and may have been added to a JRootPane not yet in a window.
if (owningWindow instanceof JFrame) {
JFrame fr = (JFrame) owningWindow;
AquaUtils.clearScreenMenuBar(fr, mbui);
}
}
}
}
}
}
/**
Invoked when a property changes on the root pane.
*/
public void propertyChange(PropertyChangeEvent e)
{
super.propertyChange(e);
String prop = e.getPropertyName();
if (AquaVibrantSupport.BACKGROUND_STYLE_KEY.equals(prop)) {
Object o = e.getNewValue();
updateVibrantStyleFromProperty(o);
} else if (AQUA_WINDOW_STYLE_KEY.equals(prop) || AQUA_WINDOW_TOP_MARGIN_KEY.equals(prop) || AQUA_WINDOW_BOTTOM_MARGIN_KEY.equals(prop)) {
reconfigureCustomWindowStyle();
} else if (AQUA_WINDOW_REPRESENTED_FILENAME_KEY.equals(prop)) {
configureRepresentedFilename();
} else if ("ancestor".equals(prop)) {
Component parent = rootPane.getParent();
if (parent instanceof Window) {
Window w = (Window) parent;
KeyWindowPatch.applyPatchIfNeeded(w);
}
} else if (AppearanceManager.AQUA_APPEARANCE_NAME_KEY.equals(prop)) {
configureSpecifiedWindowAppearance(true);
}
}
protected class MyAncestorListener
implements AncestorListener
{
/**
This is sort of like viewDidMoveToWindow:. When the root pane is put into a window
this method gets called for the notification.
We need to set up the listener relationship so we can pick up activation events.
And, if a JMenuBar was added before the root pane was added to the window, we now need
to notify the menu bar UI.
*/
public void ancestorAdded(AncestorEvent event)
{
// this is so we can handle window activated and deactivated events so
// our swing controls can color/enable/disable/focus draw correctly
Window w = getWindow();
if (w != null) {
// We get this message even when a dialog is opened and the owning window is a window
// that could already be listened to. We should only be a listener once.
w.removeWindowListener(windowListener);
w.addWindowListener(windowListener);
}
}
/**
If the JRootPane was removed from the window we should clear the screen menu bar.
That's a non-trivial problem, because you need to know which window the JRootPane was in
before it was removed. By the time ancestorRemoved was called, the JRootPane has already been removed
*/
public void ancestorRemoved(AncestorEvent event)
{
}
public void ancestorMoved(AncestorEvent event)
{
}
}
private class MyWindowListener
extends WindowAdapter
{
public void windowActivated(WindowEvent e)
{
updateWindowActivation(e, true);
}
public void windowDeactivated(WindowEvent e)
{
updateWindowActivation(e, false);
}
}
private static void updateWindowActivation(WindowEvent e, boolean active)
{
if (forceActiveWindowDisplay) {
active = true;
}
Component c = (Component) e.getSource();
c.repaint(); // much faster to make one repaint call rather than repainting individual components
AquaFocusHandler.updateComponentTreeUIActivation(c, active);
}
@Override
public final void update(Graphics g, JComponent c)
{
if (!isInitialized) {
configure();
}
AquaAppearance appearance = AppearanceManager.registerCurrentAppearance(c);
if (c.isOpaque() || vibrantStyle >= 0) {
// Need a special case here. A dark mode textured window has a vibrant background (needed for a unified
// title/toolbar), but the content area is opaque.
int eraserMode = AquaUtils.ERASE_IF_TEXTURED | AquaUtils.ERASE_IF_VIBRANT;
if (appearance.isDark() && customStyledWindow != null && customStyledWindow.isTextured()) {
eraserMode = 0;
}
AquaUtils.fillRect(g, c, eraserMode);
}
if (customStyledWindow != null) {
customStyledWindow.paintMarginBackgrounds(g);
}
paint(g, c);
}
protected class WindowHierarchyListener
implements HierarchyListener
{
@Override
public void hierarchyChanged(HierarchyEvent e)
{
long flags = e.getChangeFlags();
if ((flags & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0) {
if (rootPane.getParent() != null && rootPane.getParent().isDisplayable()) {
configureWindowIfPossible();
configure();
} else {
unconfigure();
}
}
}
}
/**
Configure or reconfigure the window based on root pane client properties and popup client properties, if
appropriate. This method has no effect if the root pane parent is not displayable.
*/
public void configure()
{
if (rootPane.getParent() != null && rootPane.getParent().isDisplayable()) {
isInitialized = true;
configureSpecifiedWindowAppearance(false);
updateAppearances(null, false);
updatePopupStyle(rootPane);
reconfigureCustomWindowStyle();
updateVisualEffectView();
configureRepresentedFilename();
} else {
reconfigureCustomWindowStyle();
}
}
private void configureSpecifiedWindowAppearance(boolean resetIfNull)
{
Window w = getWindow();
if (w != null && w.isDisplayable()) {
String appearanceName = getSpecifiedWindowAppearance();
if (appearanceName != null || resetIfNull) {
AquaUtils.setWindowAppearance(w, appearanceName);
}
}
}
private @Nullable String getSpecifiedWindowAppearance()
{
Object o = rootPane.getClientProperty(AppearanceManager.AQUA_APPEARANCE_NAME_KEY);
if (o instanceof String) {
return (String) o;
}
return null;
}
private void unconfigure()
{
removeVisualEffectView();
Window w = getWindow();
if (w != null) {
configureEmbeddedDialogPatchIfNeeded(w);
}
}
/**
This method is called when the native window discovers that it has been assigned a different appearance,
meaning an appearance with a different appearance name.
@param appearanceName The new appearance name.
*/
public void windowAppearanceChanged(@NotNull String appearanceName)
{
updateAppearances(appearanceName, false);
}
/**
Update the appearances in the component tree based on the most current information.
*/
protected void updateAppearances(@Nullable String appearanceName, boolean forceInitialization)
{
Window w = getWindow();
if (w != null) {
updateWindowAppearances(w, appearanceName, forceInitialization);
}
}
protected void updateWindowAppearances(@NotNull Window w,
@Nullable String appearanceName,
boolean forceInitialization)
{
if (appearanceName == null) {
appearanceName = AquaUtils.getWindowEffectiveAppearanceName(w);
}
if (appearanceName != null) {
AquaAppearance appearance = AquaAppearances.get(appearanceName);
AquaAppearance oldAppearance = (AquaAppearance) rootPane.getClientProperty(AppearanceManager.AQUA_APPEARANCE_KEY);
if (appearance != oldAppearance) {
if (false) {
// debug
String windowName = AquaUtils.getWindowNameForDebugging(w);
String message = "Updating appearances for window " + windowName + ": ";
if (oldAppearance == null) {
message += "initial appearance " + appearanceName;
} else {
String oldAppearanceName = oldAppearance.getName();
if (appearanceName.equals(oldAppearanceName)) {
message += "updated appearance " + appearanceName;
} else {
message += oldAppearanceName + " -> " + appearanceName;
}
}
Utils.logDebug(message);
}
rootPane.repaint();
AppearanceManager.setRootPaneRegisteredAppearance(rootPane, appearance); // a callback will be invoked
} else if (forceInitialization) {
configureWindow(w);
appearanceHasChanged();
}
}
}
private void configureWindowIfPossible() {
Component parent = rootPane.getParent();
if (parent instanceof Window) {
Window w = (Window) parent;
configureWindow(w);
}
}
protected void configureWindow(@NotNull Window w) {
configureEmbeddedDialogPatchIfNeeded(w);
if (w.isDisplayable()) {
if (OSXSystemProperties.OSVersion >= 1014 && !vibrantStyleIsExplicitlySet) {
int vibrantStyle = getDefaultWindowVibrantStyle();
updateVibrantStyle(vibrantStyle, false);
}
if (!AquaColors.isPriority(rootPane.getBackground())) {
rootPane.setBackground(null);
}
Color c = AquaUtils.getWindowBackground(rootPane);
AquaUtils.setBackgroundCarefully(w, c);
// Make sure that the default window foreground is not considered an application priority.
Color fc = w.getForeground();
if (!(fc instanceof UIResource)) {
w.setForeground(new ColorUIResource(fc));
}
}
}
protected void configureEmbeddedDialogPatchIfNeeded(@NotNull Window w)
{
// Workaround for dialogs created by embedded components.
// See related issue JDK-8320438.
if (w instanceof Dialog) {
Dialog d = (Dialog) w;
if (AquaUtils.windowHasEmbeddedOwner(w)) {
String s = d.isModal() ? " modal" : "";
Utils.logDebug("Configuring" + s + " dialog with embedded owner");
// Note: a dialog may be disposed and later reanimated, which requires updating the fix.
if (d.isVisible() && d.isDisplayable()) {
AquaUtils.fixWindowWithEmbeddedOwner(w, AquaUtils.NSModalPanelWindowLevel);
}
}
}
}
protected int getDefaultWindowVibrantStyle() {
// Non-textured windows use a vibrant background in dark mode
// Textured windows use a vibrant background in dark mode but reveal it only in the title/toolbar
AquaAppearance appearance = AppearanceManager.getAppearance(rootPane);
if (appearance.isDark()) {
return WINDOW_BACKGROUND_STYLE;
}
return NO_VIBRANT_STYLE;
}
protected void appearanceHasChanged() {
rootPane.repaint();
}
@Override
public void appearanceChanged(@NotNull JComponent c, @NotNull AquaAppearance appearance) {
// This method is called when the registered appearance of the root pane is changed.
Component parent = rootPane.getParent();
if (parent instanceof Window) {
Window w = (Window) parent;
configureWindow(w);
}
appearanceHasChanged();
}
@Override
public void activeStateChanged(@NotNull JComponent c, boolean isActive) {
// This method is called when the active frame client property changes.
Component parent = rootPane.getParent();
if (parent instanceof Window) {
Window w = (Window) parent;
configureWindow(w);
}
}
protected void updatePopupStyle(JRootPane rp) {
Window w = SwingUtilities.getWindowAncestor(rp);
if (w != null && w.getType() == Window.Type.POPUP) {
Container cp = rp.getContentPane();
if (cp != null) {
int count = cp.getComponentCount();
if (count == 1) {
Component c = cp.getComponent(0);
if (c instanceof JComponent) {
JComponent jc = (JComponent) c;
Object o = jc.getClientProperty(AquaVibrantSupport.POPUP_BACKGROUND_STYLE_KEY);
updateVibrantStyleFromProperty(o);
o = jc.getClientProperty(AquaVibrantSupport.POPUP_CORNER_RADIUS_KEY);
configurePopup(o);
}
}
}
}
}
protected void configureRepresentedFilename() {
Window w = getWindow();
if (w != null && w.isDisplayable()) {
Object o = rootPane.getClientProperty(AQUA_WINDOW_REPRESENTED_FILENAME_KEY);
String filename = "";
if (o instanceof String) {
filename = (String) o;
}
AquaUtils.setWindowRepresentedFilename(w, filename);
}
}
protected void configureLayeredPane()
{
JLayeredPane layeredPane = rootPane.getLayeredPane();
AppearanceManager.installListeners(layeredPane);
if (sUseScreenMenuBar) {
layeredPane.addContainerListener(containerListener);
}
}
protected void unconfigureLayeredPane()
{
JLayeredPane layeredPane = rootPane.getLayeredPane();
AppearanceManager.uninstallListeners(layeredPane);
if (sUseScreenMenuBar) {
layeredPane.removeContainerListener(containerListener);
}
}
protected void updateVibrantStyleFromProperty(Object o) {
int style = AquaVibrantSupport.parseVibrantStyle(o, false);
updateVibrantStyle(style, style != NO_VIBRANT_STYLE);
}
protected void updateVibrantStyle(int style, boolean isSet)
{
int oldVibrantStyle = vibrantStyle;
vibrantStyle = style;
vibrantStyleIsExplicitlySet = isSet;
if (vibrantStyle != oldVibrantStyle && isInitialized) {
updateVisualEffectView();
}
}
protected void configurePopup(Object o) {
float radius = 0;
if (o != null) {
if (o instanceof String) {
String s = (String) o;
if (s.equals("default")) {
radius = 6;
}
} else if (o instanceof Number) {
radius = ((Number) o).floatValue();
}
if (radius < 0) {
radius = 0;
}
}
Window w = SwingUtilities.getWindowAncestor(rootPane);
AquaUtils.configurePopup(w, radius);
}
protected @Nullable Window getWindow() {
return SwingUtilities.getWindowAncestor(rootPane);
}
protected void updateVisualEffectView() {
Window w = getWindow();
if (w != null && !isEmbeddedView(w)) {
if (false) {
Utils.logDebug("Updating visual effect view: " + vibrantStyle);
}
if (vibrantStyle >= 0) {
try {
AquaVibrantSupport.addFullWindowVibrantView(w, vibrantStyle);
} catch (IllegalArgumentException | UnsupportedOperationException ex) {
Utils.logError("Unable to install visual effect view", ex);
}
} else {
AquaVibrantSupport.removeFullWindowVibrantView(w);
// TBD: cannot really undo this
}
}
}
protected boolean isEmbeddedView(@NotNull Window w) {
String name = w.getClass().getSimpleName();
return name.contains("CViewEmbeddedFrame");
}
protected void removeVisualEffectView() {
Window w = getWindow();
if (w != null) {
AquaVibrantSupport.removeFullWindowVibrantView(w);
}
}
protected void reconfigureCustomWindowStyle() {
Window w = getWindow();
int style = getCustomWindowStyle();
if (style < 0 || w == null) {
disposeCustomWindowStyle();
if (w != null) {
configureWindow(w);
}
} else if (w.isDisplayable()) {
int topMarginHeight = getTopMarginHeight();
int bottomMarginHeight = getBottomMarginHeight();
if (customStyledWindow != null && !customStyledWindow.isValid(style, topMarginHeight, bottomMarginHeight)) {
customStyledWindow = customStyledWindow.reconfigure(style, topMarginHeight, bottomMarginHeight);
configureWindow(w);
} else if (customStyledWindow == null) {
try {
customStyledWindow = new AquaCustomStyledWindow(w, style, topMarginHeight, bottomMarginHeight);
configureWindow(w);
} catch (AquaCustomStyledWindow.RequiredToolBarNotFoundException ex) {
// This exception would be thrown if the window style is set before adding the tool bar to the
// content pane, which would not be an error.
} catch (IllegalArgumentException ex) {
AquaUtils.syslog("Unable to install custom window style: " + ex.getMessage());
}
}
} else {
AquaCustomStyledWindow.preconfigureWindowStyle(w, style);
}
}
private void disposeCustomWindowStyle() {
if (customStyledWindow != null) {
customStyledWindow.dispose();
customStyledWindow = null;
}
}
public AquaCustomStyledWindow getCustomStyledWindow() {
return customStyledWindow;
}
public static @Nullable String getWindowStyleKey(@NotNull JRootPane rp) {
Object o = rp.getClientProperty(AQUA_WINDOW_STYLE_KEY);
return o instanceof String ? (String) o : null;
}
protected int getCustomWindowStyle() {
String key = getWindowStyleKey(rootPane);
if (key != null) {
if ("unifiedToolBar".equals(key)) {
return AquaCustomStyledWindow.STYLE_UNIFIED;
}
if ("texturedToolBar".equals(key)) {
return AquaCustomStyledWindow.STYLE_TEXTURED_HIDDEN;
}
if ("combinedToolBar".equals(key)) {
return AquaCustomStyledWindow.STYLE_COMBINED;
}
if ("overlayTitleBar".equals(key)) {
return AquaCustomStyledWindow.STYLE_OVERLAY;
}
if ("transparentTitleBar".equals(key)) {
return AquaCustomStyledWindow.STYLE_TRANSPARENT;
}
if ("noTitleBar".equals(key)) {
return AquaCustomStyledWindow.STYLE_HIDDEN;
}
if ("undecorated".equals(key)) {
return AquaCustomStyledWindow.STYLE_UNDECORATED;
}
}
return -1;
}
protected int getTopMarginHeight() {
Integer n = AquaUtils.getIntegerProperty(rootPane, AquaRootPaneUI.AQUA_WINDOW_TOP_MARGIN_KEY);
return n != null ? n : -1;
}
protected int getBottomMarginHeight() {
Integer n = AquaUtils.getIntegerProperty(rootPane, AquaRootPaneUI.AQUA_WINDOW_BOTTOM_MARGIN_KEY);
return n != null ? n : -1;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy