Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.pushingpixels.substance.internal.utils.SubstanceTitlePane Maven / Gradle / Ivy
/*
* 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.utils;
import org.pushingpixels.neon.api.NeonCortex;
import org.pushingpixels.substance.api.SubstanceCortex;
import org.pushingpixels.substance.api.SubstanceCortex.ComponentOrParentChainScope;
import org.pushingpixels.substance.api.SubstanceSkin;
import org.pushingpixels.substance.api.SubstanceSlices;
import org.pushingpixels.substance.api.SubstanceSlices.DecorationAreaType;
import org.pushingpixels.substance.api.colorscheme.SubstanceColorScheme;
import org.pushingpixels.substance.api.skin.SkinInfo;
import org.pushingpixels.substance.internal.SubstanceSynapse;
import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
import org.pushingpixels.substance.internal.ui.SubstanceButtonUI;
import org.pushingpixels.substance.internal.ui.SubstanceRootPaneUI;
import org.pushingpixels.substance.internal.utils.icon.SubstanceIconFactory;
import org.pushingpixels.substance.internal.utils.icon.TransitionAwareIcon;
import org.pushingpixels.substance.internal.widget.animation.effects.GhostPaintingUtils;
import javax.swing.*;
import javax.swing.plaf.UIResource;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Map;
/**
* Title pane for Substance look and feel.
*
* @author Kirill Grouchnikov
*/
public class SubstanceTitlePane extends JComponent {
/**
* PropertyChangeListener added to the JRootPane.
*/
private PropertyChangeListener propertyChangeListener;
/**
* JMenuBar, typically renders the system menu items.
*/
protected JMenuBar menuBar;
private boolean isControlOnlyMode;
private int preferredHeight;
/**
* Action used to close the Window.
*/
private Action closeAction;
/**
* Action used to iconify the Frame.
*/
private Action iconifyAction;
/**
* Action to restore the Frame size.
*/
private Action restoreAction;
/**
* Action to restore the Frame size.
*/
private Action maximizeAction;
/**
* Button used to maximize or restore the frame.
*/
private JButton toggleButton;
/**
* Button used to minimize the frame
*/
private JButton minimizeButton;
/**
* Button used to close the frame.
*/
private JButton closeButton;
/**
* Listens for changes in the state of the Window listener to update the state of the widgets.
*/
private WindowListener windowListener;
/**
* Window we're currently in.
*/
protected Window window;
/**
* JRootPane rendering for.
*/
protected JRootPane rootPane;
/**
* Buffered Frame.state property. As state isn't bound, this is kept to determine when to avoid
* updating widgets.
*/
private int state;
/**
* SubstanceRootPaneUI that created us.
*/
private SubstanceRootPaneUI rootPaneUI;
/**
* The heap status toggle menu item of this
title pane.
*/
protected JCheckBoxMenuItem heapStatusMenuItem;
/**
* Listens on changes to componentOrientation
and
* {@link SubstanceSynapse#CONTENTS_MODIFIED} properties.
*/
private PropertyChangeListener propertyListener;
/**
* The application icon to be displayed.
*/
private Image appIcon;
/**
* Creates a new title pane.
*
* @param root
* Root pane.
* @param ui
* Root pane UI.
*/
public SubstanceTitlePane(JRootPane root, SubstanceRootPaneUI ui) {
this.rootPane = root;
this.rootPaneUI = ui;
this.state = -1;
this.installSubcomponents();
this.installDefaults();
this.setLayout(this.createLayout());
this.setToolTipText(this.getTitle());
ComponentOrParentChainScope.setDecorationType(this, DecorationAreaType.PRIMARY_TITLE_PANE);
this.setForeground(SubstanceColorUtilities.getForegroundColor(SubstanceCoreUtilities
.getSkin(this).getBackgroundColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE)));
}
/**
* Uninstalls the necessary state.
*/
public void uninstall() {
this.uninstallListeners();
this.window = null;
// Swing bug (?) - the updateComponentTree never gets to the
// system menu (and in our case we have radio menu items with
// rollover listeners). Fix for defect 109 - memory leak on skin
// switch
if ((this.menuBar != null) && (this.menuBar.getMenuCount() > 0)) {
this.menuBar.getUI().uninstallUI(this.menuBar);
SubstanceCoreUtilities.uninstallMenu(this.menuBar.getMenu(0));
}
if (this.menuBar != null) {
this.menuBar.removeAll();
}
this.removeAll();
}
/**
* Installs the necessary listeners.
*/
private void installListeners() {
if (this.window != null) {
this.windowListener = new WindowHandler();
this.window.addWindowListener(this.windowListener);
this.propertyChangeListener = new PropertyChangeHandler();
this.window.addPropertyChangeListener(this.propertyChangeListener);
}
// Property change listener for pulsating close button
// when window has been marked as changed.
// Fix for defect 109 - memory leak on skin change.
this.propertyListener = (final PropertyChangeEvent evt) -> {
if (SubstanceSynapse.CONTENTS_MODIFIED.equals(evt.getPropertyName())) {
syncCloseButtonTooltip();
}
if ("componentOrientation".equals(evt.getPropertyName())) {
SwingUtilities.invokeLater(() -> {
if (SubstanceTitlePane.this.menuBar != null) {
SubstanceTitlePane.this.menuBar.applyComponentOrientation(
(ComponentOrientation) evt.getNewValue());
}
});
}
};
// Wire it on the frame itself and its root pane.
this.rootPane.addPropertyChangeListener(this.propertyListener);
if (this.getFrame() != null)
this.getFrame().addPropertyChangeListener(this.propertyListener);
}
/**
* Uninstalls the necessary listeners.
*/
private void uninstallListeners() {
if (this.window != null) {
this.window.removeWindowListener(this.windowListener);
this.windowListener = null;
this.window.removePropertyChangeListener(this.propertyChangeListener);
this.propertyChangeListener = null;
}
// Fix for defect 109 - memory leak on skin change.
this.rootPane.removePropertyChangeListener(this.propertyListener);
if (this.getFrame() != null) {
this.getFrame().removePropertyChangeListener(this.propertyListener);
}
this.propertyListener = null;
}
/**
* Returns the JRootPane
this was created for.
*/
@Override
public JRootPane getRootPane() {
return this.rootPane;
}
/**
* Returns the decoration style of the JRootPane
.
*
* @return Decoration style of the JRootPane
.
*/
private int getWindowDecorationStyle() {
return this.getRootPane().getWindowDecorationStyle();
}
@Override
public void addNotify() {
super.addNotify();
this.uninstallListeners();
this.window = SwingUtilities.getWindowAncestor(this);
if (this.window != null) {
this.setActive(this.window.isActive());
if (this.window instanceof Frame) {
this.setState(((Frame) this.window).getExtendedState());
} else {
this.setState(0);
}
if (this.getComponentCount() == 0) {
// fix for issue 385 - add the sub-components uninstalled
// in the removeNotify. This happens when a decorated
// dialog has been disposed and then reshown.
this.installSubcomponents();
}
this.installListeners();
}
this.setToolTipText(this.getTitle());
this.updateAppIcon();
}
@Override
public void removeNotify() {
super.removeNotify();
this.uninstall();
this.window = null;
}
/**
* Adds any sub-Components contained in the SubstanceTitlePane
.
*/
private void installSubcomponents() {
int decorationStyle = this.getWindowDecorationStyle();
if (decorationStyle == JRootPane.FRAME) {
this.createActions();
this.menuBar = this.createMenuBar();
if (this.menuBar != null) {
this.add(this.menuBar);
}
this.createButtons();
this.add(this.minimizeButton);
this.add(this.toggleButton);
this.add(this.closeButton);
} 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)) {
this.createActions();
this.createButtons();
this.add(this.closeButton);
}
}
}
/**
* Installs the fonts and necessary properties.
*/
private void installDefaults() {
Font font = SubstanceCortex.GlobalScope.getFontPolicy().getFontSet().getWindowTitleFont();
this.setFont(font);
}
/**
* Returns the JMenuBar
displaying the appropriate system menu items.
*
* @return JMenuBar
displaying the appropriate system menu items.
*/
private JMenuBar createMenuBar() {
this.menuBar = new SubstanceMenuBar();
this.menuBar.setFocusable(false);
this.menuBar.setBorderPainted(true);
this.menuBar.add(this.createMenu());
this.menuBar.setOpaque(false);
// support for RTL
this.menuBar.applyComponentOrientation(this.rootPane.getComponentOrientation());
SubstanceSlices.TitleIconHorizontalGravity iconGravity = SubstanceTitlePaneUtilities
.getTitlePaneIconGravity();
SubstanceTitlePaneUtilities.markTitlePaneExtraComponent(this.menuBar,
(iconGravity == SubstanceSlices.TitleIconHorizontalGravity.NEXT_TO_TITLE)
? SubstanceTitlePaneUtilities.ExtraComponentKind.WITH_TITLE
: SubstanceTitlePaneUtilities.ExtraComponentKind.LEADING);
return this.menuBar;
}
/**
* Create the Action
s that get associated with the buttons and menu items.
*/
private void createActions() {
this.closeAction = new CloseAction();
if (this.getWindowDecorationStyle() == JRootPane.FRAME) {
this.iconifyAction = new IconifyAction();
this.restoreAction = new RestoreAction();
this.maximizeAction = new MaximizeAction();
}
}
/**
* Returns the JMenu
displaying the appropriate menu items for manipulating the
* Frame.
*
* @return JMenu
displaying the appropriate menu items for manipulating the Frame.
*/
private JMenu createMenu() {
JMenu menu = new JMenu("");
menu.setOpaque(false);
menu.setBackground(null);
if (this.getWindowDecorationStyle() == JRootPane.FRAME) {
this.addMenuItems(menu);
}
return menu;
}
/**
* Adds the necessary JMenuItem
s to the specified menu.
*
* @param menu
* Menu.
*/
private void addMenuItems(JMenu menu) {
menu.add(this.restoreAction);
menu.add(this.iconifyAction);
if (Toolkit.getDefaultToolkit().isFrameStateSupported(Frame.MAXIMIZED_BOTH)) {
menu.add(this.maximizeAction);
}
if (SubstanceCoreUtilities.toShowExtraWidgets(rootPane)) {
menu.addSeparator();
JMenu skinMenu = new JMenu(
SubstanceCortex.GlobalScope.getLabelBundle().getString("SystemMenu.skins"));
Map allSkins = SubstanceCortex.GlobalScope.getAllSkins();
for (Map.Entry skinEntry : allSkins.entrySet()) {
final String skinClassName = skinEntry.getValue().getClassName();
JMenuItem jmiSkin = new JMenuItem(skinEntry.getKey());
jmiSkin.addActionListener((ActionEvent e) -> SwingUtilities
.invokeLater(() -> SubstanceCortex.GlobalScope.setSkin(skinClassName)));
skinMenu.add(jmiSkin);
}
menu.add(skinMenu);
}
menu.addSeparator();
menu.add(this.closeAction);
}
/**
* Returns a JButton
appropriate for placement on the TitlePane.
*
* @return Title button.
*/
private JButton createTitleButton() {
JButton button = new SubstanceTitleButton();
button.setFocusPainted(false);
button.setFocusable(false);
button.setOpaque(true);
SubstanceTitlePaneUtilities.markTitlePaneExtraComponent(button,
SubstanceTitlePaneUtilities.getTitlePaneControlButtonKind(this.rootPane));
return button;
}
/**
* Creates the Buttons that will be placed on the TitlePane.
*/
private void createButtons() {
this.closeButton = this.createTitleButton();
this.closeButton.setAction(this.closeAction);
this.closeButton.setText(null);
this.closeButton.setBorder(null);
Icon closeIcon = new TransitionAwareIcon(closeButton,
(SubstanceColorScheme scheme) -> SubstanceIconFactory.getTitlePaneIcon(
SubstanceIconFactory.IconKind.CLOSE, scheme,
SubstanceCoreUtilities.getSkin(rootPane)
.getBackgroundColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE)),
"substance.titlePane.closeIcon");
this.closeButton.setIcon(closeIcon);
this.closeButton.setFocusable(false);
SubstanceCortex.ComponentOrParentScope.setFlatBackground(this.closeButton, true);
this.closeButton.putClientProperty(SubstanceButtonUI.IS_TITLE_CLOSE_BUTTON, Boolean.TRUE);
if (this.getWindowDecorationStyle() == JRootPane.FRAME) {
this.minimizeButton = this.createTitleButton();
this.minimizeButton.setAction(this.iconifyAction);
this.minimizeButton.setText(null);
this.minimizeButton.setBorder(null);
Icon minIcon = new TransitionAwareIcon(this.minimizeButton,
(SubstanceColorScheme scheme) -> SubstanceIconFactory.getTitlePaneIcon(
SubstanceIconFactory.IconKind.MINIMIZE, scheme,
SubstanceCoreUtilities.getSkin(rootPane).getBackgroundColorScheme(
DecorationAreaType.PRIMARY_TITLE_PANE)),
"substance.titlePane.minIcon");
this.minimizeButton.setIcon(minIcon);
this.minimizeButton.setFocusable(false);
SubstanceCortex.ComponentOrParentScope.setFlatBackground(this.minimizeButton, true);
this.minimizeButton.setToolTipText(
SubstanceCortex.GlobalScope.getLabelBundle().getString("SystemMenu.iconify"));
this.toggleButton = this.createTitleButton();
this.toggleButton.setAction(this.restoreAction);
this.toggleButton.setText(null);
this.toggleButton.setBorder(null);
Icon maxIcon = new TransitionAwareIcon(this.toggleButton,
(SubstanceColorScheme scheme) -> SubstanceIconFactory.getTitlePaneIcon(
SubstanceIconFactory.IconKind.MAXIMIZE, scheme,
SubstanceCoreUtilities.getSkin(rootPane).getBackgroundColorScheme(
DecorationAreaType.PRIMARY_TITLE_PANE)),
"substance.titlePane.maxIcon");
this.toggleButton.setIcon(maxIcon);
this.toggleButton.setToolTipText(
SubstanceCortex.GlobalScope.getLabelBundle().getString("SystemMenu.maximize"));
this.toggleButton.setFocusable(false);
SubstanceCortex.ComponentOrParentScope.setFlatBackground(this.toggleButton, true);
}
syncCloseButtonTooltip();
}
/**
* Returns the LayoutManager
that should be installed on the
* SubstanceTitlePane
.
*
* @return Layout manager.
*/
protected LayoutManager createLayout() {
return new TitlePaneLayout();
}
/**
* Updates state dependant upon the Window's active state.
*
* @param isActive
* if true
, the window is in active state.
*/
private void setActive(boolean isActive) {
this.getRootPane().repaint();
}
/**
* Sets the state of the Window.
*
* @param state
* Window state.
*/
private void setState(int state) {
this.setState(state, false);
}
/**
* Sets the state of the window. If updateRegardless
is true and the state has not
* changed, this will update anyway.
*
* @param state
* Window state.
* @param updateRegardless
* if true
, the update is done in any case.
*/
private void setState(int state, boolean updateRegardless) {
if ((this.window != null) && (this.getWindowDecorationStyle() == JRootPane.FRAME)) {
if ((this.state == state) && !updateRegardless) {
return;
}
Frame frame = this.getFrame();
if (frame != null) {
final JRootPane rootPane = this.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.
this.rootPaneUI.installBorder(rootPane);
}
}
if (frame.isResizable()) {
if ((state & Frame.MAXIMIZED_BOTH) != 0) {
Icon restoreIcon = new TransitionAwareIcon(this.toggleButton,
(SubstanceColorScheme scheme) -> SubstanceIconFactory
.getTitlePaneIcon(SubstanceIconFactory.IconKind.RESTORE,
scheme,
SubstanceCoreUtilities.getSkin(rootPane)
.getBackgroundColorScheme(
DecorationAreaType.PRIMARY_TITLE_PANE)),
"substance.titlePane.restoreIcon");
this.updateToggleButton(this.restoreAction, restoreIcon);
this.toggleButton.setToolTipText(SubstanceCortex.GlobalScope
.getLabelBundle().getString("SystemMenu.restore"));
this.maximizeAction.setEnabled(false);
this.restoreAction.setEnabled(true);
} else {
Icon maxIcon = new TransitionAwareIcon(this.toggleButton,
(SubstanceColorScheme scheme) -> SubstanceIconFactory
.getTitlePaneIcon(SubstanceIconFactory.IconKind.MAXIMIZE,
scheme,
SubstanceCoreUtilities.getSkin(rootPane)
.getBackgroundColorScheme(
DecorationAreaType.PRIMARY_TITLE_PANE)),
"substance.titlePane.maxIcon");
this.updateToggleButton(this.maximizeAction, maxIcon);
this.toggleButton.setToolTipText(SubstanceCortex.GlobalScope
.getLabelBundle().getString("SystemMenu.maximize"));
this.maximizeAction.setEnabled(true);
this.restoreAction.setEnabled(false);
}
if ((this.toggleButton.getParent() == null)
|| (this.minimizeButton.getParent() == null)) {
this.add(this.toggleButton);
this.add(this.minimizeButton);
this.revalidate();
this.repaint();
}
this.toggleButton.setText(null);
} else {
this.maximizeAction.setEnabled(false);
this.restoreAction.setEnabled(false);
if (this.toggleButton.getParent() != null) {
this.remove(this.toggleButton);
this.revalidate();
this.repaint();
}
}
} else {
// Not contained in a Frame
this.maximizeAction.setEnabled(false);
this.restoreAction.setEnabled(false);
this.iconifyAction.setEnabled(false);
this.remove(this.toggleButton);
this.remove(this.minimizeButton);
this.revalidate();
this.repaint();
}
this.closeAction.setEnabled(true);
this.state = state;
}
}
/**
* Updates the toggle button to contain the Icon icon
, and Action
* action
.
*
* @param action
* Action.
* @param icon
* Icon.
*/
private void updateToggleButton(Action action, Icon icon) {
this.toggleButton.setAction(action);
this.toggleButton.setIcon(icon);
this.toggleButton.setText(null);
}
/**
* Returns the Frame rendering in. This will return null if the JRootPane
is not
* contained in a Frame
.
*
* @return Frame.
*/
private Frame getFrame() {
if (this.window instanceof Frame) {
return (Frame) window;
}
return null;
}
/**
* Returns the String to display as the title.
*
* @return Display title.
*/
private String getTitle() {
if (this.window instanceof Frame) {
return ((Frame) this.window).getTitle();
}
if (this.window instanceof Dialog) {
return ((Dialog) this.window).getTitle();
}
return null;
}
private String getDisplayTitle() {
String theTitle = this.getTitle();
if (theTitle == null) {
return null;
}
Font font = SubstanceCortex.GlobalScope.getFontPolicy().getFontSet()
.getWindowTitleFont();
Rectangle titleTextRect = SubstanceTitlePaneUtilities.getTitlePaneTextRectangle(this,
(this.window != null) ? this.window : this.getRootPane());
FontMetrics fm = SubstanceMetricsUtilities.getFontMetrics(font);
int titleWidth = titleTextRect.width - 20;
String clippedTitle = SubstanceCoreUtilities.clipString(fm, titleWidth, theTitle);
return clippedTitle;
}
@Override
public void paintComponent(Graphics g) {
// long start = System.nanoTime();
// As state isn't bound, we need a convenience place to check
// if it has changed. Changing the state typically changes the
if (this.getFrame() != null) {
this.setState(this.getFrame().getExtendedState());
}
if (this.isControlOnlyMode) {
return;
}
final JRootPane rootPane = this.getRootPane();
boolean leftToRight = (this.window == null)
? rootPane.getComponentOrientation().isLeftToRight()
: this.window.getComponentOrientation().isLeftToRight();
int width = this.getWidth();
int height = this.getHeight();
SubstanceSkin skin = SubstanceCoreUtilities.getSkin(rootPane);
if (skin == null) {
SubstanceCoreUtilities.traceSubstanceApiUsage(this,
"Substance delegate used when Substance is not the current LAF");
}
SubstanceColorScheme scheme = skin
.getEnabledColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE);
String theTitle = this.getTitle();
String displayTitle = getDisplayTitle();
Graphics2D graphics = (Graphics2D) g.create();
BackgroundPaintingUtils.update(graphics, SubstanceTitlePane.this, false);
Font font = SubstanceCortex.GlobalScope.getFontPolicy().getFontSet()
.getWindowTitleFont();
graphics.setFont(font);
if (displayTitle != null) {
Rectangle titleTextRect = SubstanceTitlePaneUtilities.getTitlePaneTextRectangle(this,
(this.window != null) ? this.window : this.getRootPane());
FontMetrics fm = SubstanceMetricsUtilities.getFontMetrics(font);
int displayTitleWidth = fm.stringWidth(displayTitle);
// show tooltip with full title only if necessary
if (theTitle.equals(displayTitle)) {
this.setToolTipText(null);
} else {
this.setToolTipText(theTitle);
}
int xOffset = 0;
SubstanceSlices.HorizontalGravity titleTextGravity = SubstanceTitlePaneUtilities
.getTitlePaneTextGravity();
switch (titleTextGravity) {
case LEADING:
xOffset = leftToRight ? titleTextRect.x
: titleTextRect.x + titleTextRect.width - displayTitleWidth;
break;
case TRAILING:
xOffset = leftToRight
? titleTextRect.x + titleTextRect.width - displayTitleWidth
: titleTextRect.x;
break;
default:
xOffset = titleTextRect.x + (titleTextRect.width - displayTitleWidth) / 2;
}
int yOffset = titleTextRect.y + (int) ((titleTextRect.getHeight() - fm.getHeight()) / 2)
+ fm.getAscent();
SubstanceColorScheme fillScheme = skin
.getBackgroundColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE);
Color echoColor = !fillScheme.isDark() ? fillScheme.getUltraDarkColor()
: fillScheme.getUltraLightColor();
SubstanceTextUtilities.paintTextWithDropShadow(this, graphics,
SubstanceColorUtilities.getForegroundColor(scheme), echoColor, displayTitle,
width, height, xOffset, yOffset);
}
GhostPaintingUtils.paintGhostImages(this, graphics);
// long end = System.nanoTime();
// System.out.println(end - start);
graphics.dispose();
}
/**
* Actions used to close
the Window
.
*/
private class CloseAction extends AbstractAction {
/**
* Creates a new close action.
*/
public CloseAction() {
super(SubstanceCortex.GlobalScope.getLabelBundle().getString("SystemMenu.close"),
SubstanceImageCreator.getCloseIcon(
SubstanceCoreUtilities.getSkin(rootPane)
.getActiveColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE),
SubstanceCoreUtilities.getSkin(rootPane).getBackgroundColorScheme(
DecorationAreaType.PRIMARY_TITLE_PANE)));
}
@Override
public void actionPerformed(ActionEvent e) {
if (window != null) {
window.dispatchEvent(new WindowEvent(window, WindowEvent.WINDOW_CLOSING));
}
}
}
/**
* Actions used to iconfiy
the Frame
.
*/
private class IconifyAction extends AbstractAction {
/**
* Creates a new iconify action.
*/
private IconifyAction() {
super(SubstanceCortex.GlobalScope.getLabelBundle().getString("SystemMenu.iconify"),
SubstanceImageCreator.getMinimizeIcon(
SubstanceCoreUtilities.getSkin(rootPane)
.getActiveColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE),
SubstanceCoreUtilities.getSkin(rootPane).getBackgroundColorScheme(
DecorationAreaType.PRIMARY_TITLE_PANE)));
}
@Override
public void actionPerformed(ActionEvent e) {
Frame frame = SubstanceTitlePane.this.getFrame();
if (frame != null) {
frame.setExtendedState(SubstanceTitlePane.this.state | Frame.ICONIFIED);
}
}
}
/**
* Actions used to restore
the Frame
.
*/
private class RestoreAction extends AbstractAction {
/**
* Creates a new restore action.
*/
private RestoreAction() {
super(SubstanceCortex.GlobalScope.getLabelBundle().getString("SystemMenu.restore"),
SubstanceImageCreator.getRestoreIcon(
SubstanceCoreUtilities.getSkin(rootPane)
.getActiveColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE),
SubstanceCoreUtilities.getSkin(rootPane).getBackgroundColorScheme(
DecorationAreaType.PRIMARY_TITLE_PANE)));
}
@Override
public void actionPerformed(ActionEvent e) {
Frame frame = SubstanceTitlePane.this.getFrame();
if (frame == null) {
return;
}
if ((SubstanceTitlePane.this.state & Frame.ICONIFIED) != 0) {
frame.setExtendedState(SubstanceTitlePane.this.state & ~Frame.ICONIFIED);
} else {
frame.setExtendedState(SubstanceTitlePane.this.state & ~Frame.MAXIMIZED_BOTH);
}
}
}
/**
* Actions used to restore
the Frame
.
*/
private class MaximizeAction extends AbstractAction {
/**
* Creates a new maximize action.
*/
private MaximizeAction() {
super(SubstanceCortex.GlobalScope.getLabelBundle().getString("SystemMenu.maximize"),
SubstanceImageCreator.getMaximizeIcon(
SubstanceCoreUtilities.getSkin(rootPane)
.getActiveColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE),
SubstanceCoreUtilities.getSkin(rootPane)
.getEnabledColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE)));
}
@Override
public void actionPerformed(ActionEvent e) {
Frame frame = SubstanceTitlePane.this.getFrame();
if (frame != null) {
frame.setExtendedState(SubstanceTitlePane.this.state | Frame.MAXIMIZED_BOTH);
}
}
}
/**
* Class responsible for drawing the system menu. Looks up the image to draw from the Frame
* associated with the JRootPane
.
*/
public class SubstanceMenuBar extends JMenuBar {
@Override
public void paint(Graphics g) {
if (appIcon != null) {
float scaleFactor = (float) NeonCortex.getScaleFactor();
g.drawImage(appIcon, 0, 0, (int) (appIcon.getWidth(null) / scaleFactor),
(int) (appIcon.getHeight(null) / scaleFactor), null);
} else {
Icon icon = UIManager.getIcon("InternalFrame.icon");
if (icon != null) {
icon.paintIcon(this, g, 0, 0);
}
}
}
@Override
public Dimension getMinimumSize() {
return this.getPreferredSize();
}
@Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
int iSize = SubstanceSizeUtils.getTitlePaneIconSize();
return new Dimension(Math.max(iSize, size.width), Math.max(size.height, iSize));
}
}
/**
* Layout manager for the title pane.
*
* @author Kirill Graphics
*/
protected class TitlePaneLayout implements LayoutManager {
@Override
public void addLayoutComponent(String name, Component c) {
}
@Override
public void removeLayoutComponent(Component c) {
}
@Override
public Dimension preferredLayoutSize(Container c) {
int height = getPaneHeight();
return new Dimension(height, height);
}
@Override
public Dimension minimumLayoutSize(Container c) {
return this.preferredLayoutSize(c);
}
@Override
public void layoutContainer(Container c) {
JRootPane rootPane = getRootPane();
boolean leftToRight = rootPane.getComponentOrientation().isLeftToRight();
boolean controlButtonsOnRight = SubstanceTitlePaneUtilities
.areTitlePaneControlButtonsOnRight(rootPane);
int w = SubstanceTitlePane.this.getWidth();
int x;
int spacing;
// assumes all buttons have the same dimensions
// these dimensions include the borders
int buttonSize = getControlButtonSize();
SubstanceSlices.VerticalGravity buttonGroupVerticalGravity = SubstanceTitlePaneUtilities
.getTitleControlButtonGroupVerticalGravity(rootPane);
final int height = getHeight();
final int y;
switch (buttonGroupVerticalGravity) {
case TOP:
y = 0;
break;
case BOTTOM:
y = height - buttonSize;
break;
default:
y = (height - buttonSize) / 2;
}
x = leftToRight ? w : 0;
SubstanceSlices.TitleIconHorizontalGravity iconHorizontalGravity = isControlOnlyMode
? SubstanceSlices.TitleIconHorizontalGravity.NONE
: SubstanceTitlePaneUtilities.getTitlePaneIconGravity();
SubstanceSlices.HorizontalGravity titleTextHorizontalGravity = SubstanceTitlePaneUtilities
.getTitlePaneTextGravity();
if (SubstanceTitlePane.this.menuBar != null) {
spacing = SubstanceSizeUtils.getTitlePaneHorizontalPadding(
SubstanceSizeUtils.getComponentFontSize(SubstanceTitlePane.this));
int menuBarLeft;
switch (iconHorizontalGravity) {
case OPPOSITE_CONTROL_BUTTONS:
menuBarLeft = controlButtonsOnRight ? spacing : w - buttonSize - spacing;
break;
case NEXT_TO_TITLE:
Rectangle titleRect = SubstanceTitlePaneUtilities.getTitlePaneTextRectangle(
SubstanceTitlePane.this, (window != null) ? window : getRootPane());
String displayTitle = getDisplayTitle();
Font font = SubstanceCortex.GlobalScope.getFontPolicy()
.getFontSet().getWindowTitleFont();
int displayTitleWidth = SubstanceMetricsUtilities.getFontMetrics(font)
.stringWidth(displayTitle);
switch (titleTextHorizontalGravity) {
case LEADING:
menuBarLeft = leftToRight ? titleRect.x - buttonSize - spacing
: titleRect.x + titleRect.width + spacing;
break;
case TRAILING:
menuBarLeft = leftToRight
? titleRect.x + titleRect.width - displayTitleWidth
- buttonSize - spacing
: titleRect.x + titleRect.width + spacing;
break;
default:
int displayTitleLeft = titleRect.x
+ (titleRect.width - displayTitleWidth) / 2;
menuBarLeft = leftToRight ? displayTitleLeft - buttonSize - spacing
: displayTitleLeft + displayTitleWidth + spacing;
}
break;
default:
menuBarLeft = -1;
}
if (menuBarLeft >= 0) {
SubstanceTitlePane.this.menuBar.setVisible(true);
SubstanceTitlePane.this.menuBar.setBounds(menuBarLeft, y, buttonSize,
buttonSize);
} else {
SubstanceTitlePane.this.menuBar.setVisible(false);
}
}
x = controlButtonsOnRight ? w : 0;
spacing = getControlButtonsSmallGap();
x += controlButtonsOnRight ? -spacing - buttonSize : spacing;
if (SubstanceTitlePane.this.closeButton != null) {
SubstanceTitlePane.this.closeButton.setBounds(x, y, buttonSize, buttonSize);
}
if (!controlButtonsOnRight)
x += buttonSize;
if (SubstanceTitlePane.this.getWindowDecorationStyle() == JRootPane.FRAME) {
if (Toolkit.getDefaultToolkit().isFrameStateSupported(Frame.MAXIMIZED_BOTH)) {
if (SubstanceTitlePane.this.toggleButton.getParent() != null) {
spacing = getControlButtonsLargeGap();
x += controlButtonsOnRight ? -spacing - buttonSize : spacing;
SubstanceTitlePane.this.toggleButton.setBounds(x, y, buttonSize,
buttonSize);
if (!controlButtonsOnRight) {
x += buttonSize;
}
}
}
if ((SubstanceTitlePane.this.minimizeButton != null)
&& (SubstanceTitlePane.this.minimizeButton.getParent() != null)) {
spacing = getControlButtonsSmallGap();
x += controlButtonsOnRight ? -spacing - buttonSize : spacing;
SubstanceTitlePane.this.minimizeButton.setBounds(x, y, buttonSize, buttonSize);
if (!controlButtonsOnRight) {
x += buttonSize;
}
}
}
}
}
/**
* PropertyChangeListener installed on the Window. Updates the necessary state as the state of
* the Window changes.
*/
private class PropertyChangeHandler implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent pce) {
String name = pce.getPropertyName();
// Frame.state isn't currently bound.
if ("resizable".equals(name) || "state".equals(name)) {
Frame frame = SubstanceTitlePane.this.getFrame();
if (frame != null) {
SubstanceTitlePane.this.setState(frame.getExtendedState(), true);
}
if ("resizable".equals(name)) {
SubstanceTitlePane.this.getRootPane().repaint();
}
} else {
if ("title".equals(name)) {
SubstanceTitlePane.this.setToolTipText((String) pce.getNewValue());
revalidate();
repaint();
} else if ("componentOrientation".equals(name)) {
revalidate();
repaint();
} else if ("iconImage".equals(name)) {
updateAppIcon();
revalidate();
repaint();
}
}
}
}
/**
* WindowListener installed on the Window, updates the state as necessary.
*/
private class WindowHandler extends WindowAdapter {
@Override
public void windowActivated(WindowEvent ev) {
SubstanceTitlePane.this.setActive(true);
}
@Override
public void windowDeactivated(WindowEvent ev) {
SubstanceTitlePane.this.setActive(false);
}
}
/**
* Synchronizes the tooltip of the close button.
*/
private void syncCloseButtonTooltip() {
if (SubstanceCoreUtilities.isRootPaneModified(this.getRootPane())) {
this.closeButton.setToolTipText(
SubstanceCortex.GlobalScope.getLabelBundle().getString("SystemMenu.close")
+ " [" + SubstanceCortex.GlobalScope.getLabelBundle()
.getString("Tooltip.contentsNotSaved")
+ "]");
} else {
this.closeButton.setToolTipText(
SubstanceCortex.GlobalScope.getLabelBundle().getString("SystemMenu.close"));
}
this.closeButton.repaint();
}
/**
* Updates the application icon.
*/
private void updateAppIcon() {
if (this.window == null) {
this.appIcon = null;
return;
}
java.util.List iconImages = window.getIconImages();
if (iconImages.size() == 0) {
this.appIcon = null;
} else {
int prefSize = getControlButtonSize();
this.appIcon = SubstanceCoreUtilities.getScaledIconImage(iconImages, prefSize,
prefSize);
}
}
public AbstractButton getCloseButton() {
return this.closeButton;
}
private int getControlButtonSize() {
if ((this.closeButton != null) && (this.closeButton.getIcon() != null)) {
return this.closeButton.getIcon().getIconHeight();
} else {
return SubstanceSizeUtils.getTitlePaneIconSize();
}
}
private int getControlButtonsSmallGap() {
return (int) SubstanceSizeUtils.getAdjustedSize(
SubstanceSizeUtils.getComponentFontSize(this), 3, 2, 1);
}
private int getControlButtonsLargeGap() {
return 10;
}
public void setControlOnlyMode() {
this.isControlOnlyMode = true;
this.setOpaque(false);
this.revalidate();
this.repaint();
}
public void setPreferredHeight(int preferredHeight) {
this.preferredHeight = preferredHeight;
this.revalidate();
this.repaint();
}
public JButton createControlButton() {
JButton result = createTitleButton();
int prefSize = getControlButtonSize();
result.setPreferredSize(new Dimension(prefSize, prefSize));
SubstanceCortex.ComponentOrParentScope.setFlatBackground(result, true);
ComponentOrParentChainScope.setDecorationType(result,
DecorationAreaType.PRIMARY_TITLE_PANE);
return result;
}
private int getPaneHeight() {
FontMetrics fm = SubstanceMetricsUtilities.getFontMetrics(this.getFont());
int fontHeight = fm.getHeight();
fontHeight += 7;
int iconHeight = 0;
if (SubstanceTitlePane.this.getWindowDecorationStyle() == JRootPane.FRAME) {
iconHeight = SubstanceSizeUtils.getTitlePaneIconSize();
}
int finalHeight = Math.max(fontHeight, iconHeight);
finalHeight = Math.max(finalHeight, SubstanceTitlePane.this.preferredHeight);
return finalHeight;
}
public Insets getControlInsets() {
boolean controlButtonsOnRight = SubstanceTitlePaneUtilities
.areTitlePaneControlButtonsOnRight(getRootPane());
int buttonSize = getControlButtonSize();
// Close button and its offset
int controlInsets = getControlButtonsSmallGap();
controlInsets += buttonSize;
if (this.getWindowDecorationStyle() == JRootPane.FRAME) {
if (Toolkit.getDefaultToolkit().isFrameStateSupported(Frame.MAXIMIZED_BOTH)) {
// Toggle / maximize button
if (this.toggleButton.getParent() != null) {
controlInsets += getControlButtonsLargeGap();
controlInsets += buttonSize;
}
}
if ((this.minimizeButton != null) && (this.minimizeButton.getParent() != null)) {
// Minimize button
controlInsets += getControlButtonsSmallGap();
controlInsets += buttonSize;
}
}
int height = getPaneHeight();
SubstanceSlices.VerticalGravity buttonGroupVerticalGravity = SubstanceTitlePaneUtilities
.getTitleControlButtonGroupVerticalGravity(rootPane);
final int y;
switch (buttonGroupVerticalGravity) {
case TOP:
y = 0;
break;
case BOTTOM:
y = height - buttonSize;
break;
default:
y = (height - buttonSize) / 2;
}
int leftInset = controlButtonsOnRight ? 0 : controlInsets;
int rightInset = controlButtonsOnRight ? controlInsets : 0;
int topInset = y;
int bottomInset = height - y - buttonSize;
return new Insets(topInset, leftInset, bottomInset, rightInset);
}
}