/*
* Copyright (c) 2005-2016 Substance 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 Substance Kirill Grouchnikov 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 java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Container;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Formatter;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.UIResource;
import org.pushingpixels.lafwidget.animation.effects.GhostPaintingUtils;
import org.pushingpixels.lafwidget.utils.RenderingUtils;
import org.pushingpixels.lafwidget.utils.TrackableThread;
import org.pushingpixels.substance.api.DecorationAreaType;
import org.pushingpixels.substance.api.SubstanceColorScheme;
import org.pushingpixels.substance.api.SubstanceConstants.SubstanceWidgetType;
import org.pushingpixels.substance.api.SubstanceLookAndFeel;
import org.pushingpixels.substance.api.SubstanceSkin;
import org.pushingpixels.substance.api.skin.SkinInfo;
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;
/**
* Title pane for <b>Substance</b> 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;
/**
* 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.
*/
protected JButton toggleButton;
/**
* Button used to minimize the frame
*/
protected JButton minimizeButton;
/**
* Button used to close the frame.
*/
protected 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 logfile name for the heap status panel. Can be <code>null</code> - in
* this case the {@link HeapStatusThread} will not write heap information.
*/
private static String heapStatusLogfileName;
/**
* The heap status panel of <code>this</code> title pane.
*/
protected HeapStatusPanel heapStatusPanel;
/**
* The heap status toggle menu item of <code>this</code> title pane.
*/
protected JCheckBoxMenuItem heapStatusMenuItem;
/**
* Listens on changes to <code>componentOrientation</code> and
* {@link SubstanceLookAndFeel#WINDOW_MODIFIED} properties.
*/
protected PropertyChangeListener propertyListener;
/**
* Client property to mark every child to be either leading or trailing. The
* value must be one of {@link ExtraComponentKind}.
*
* @see #markExtraComponent(JComponent, ExtraComponentKind)
* @see #getTitleTextRectangle()
*/
protected static final String EXTRA_COMPONENT_KIND = "substancelaf.internal.titlePane.extraComponentKind";
/**
* The application icon to be displayed.
*/
protected Image appIcon;
/**
* Enumerates the types of children components.
*
* @author Kirill Grouchnikov
*/
protected enum ExtraComponentKind {
/**
* Leading child components (left on LTR and right on RTL).
*/
LEADING,
/**
* Trailing child components (right on LTR and left on RTL).
*/
TRAILING
}
/**
* Panel that shows heap status and allows running the garbage collector.
*
* @author Kirill Grouchnikov
*/
public static class HeapStatusPanel extends JPanel {
/**
* The current heap size in kilobytes.
*/
private int currHeapSizeKB;
/**
* The current used portion of heap in kilobytes.
*/
private int currTakenHeapSizeKB;
/**
* History of used heap portion (in percents). Each value is in 0.0-1.0
* range.
*/
private LinkedList<Double> graphValues;
/**
* Creates new heap status panel.
*/
public HeapStatusPanel() {
this.graphValues = new LinkedList<Double>();
HeapStatusThread.getInstance();
}
/**
* Updates the values for <code>this</code> heap status panel.
*
* @param currHeapSizeKB
* The current heap size in kilobytes.
* @param currTakenHeapSizeKB
* The current used portion of heap in kilobytes.
*/
public synchronized void updateStatus(int currHeapSizeKB, int currTakenHeapSizeKB) {
this.currHeapSizeKB = currHeapSizeKB;
this.currTakenHeapSizeKB = currTakenHeapSizeKB;
double newGraphValue = (double) currTakenHeapSizeKB / (double) currHeapSizeKB;
this.graphValues.addLast(newGraphValue);
this.repaint();
}
/*
* (non-Javadoc)
*
* @see javax.swing.JComponent#paint(java.awt.Graphics)
*/
@Override
public synchronized void paint(Graphics g) {
Graphics2D graphics = (Graphics2D) g.create();
SubstanceColorScheme scheme = SubstanceCoreUtilities.getSkin(this)
.getActiveColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE);
graphics.setColor(scheme.getDarkColor());
int w = this.getWidth();
int h = this.getHeight();
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
float borderThickness = SubstanceSizeUtils.getBorderStrokeWidth();
graphics.setStroke(new BasicStroke(borderThickness, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
graphics.draw(new Rectangle2D.Float(borderThickness / 2.0f, borderThickness / 2.0f,
w - borderThickness, h - borderThickness));
graphics.setColor(scheme.getExtraLightColor());
graphics.draw(new Rectangle2D.Float(borderThickness, borderThickness,
w - 2 * borderThickness, h - 2 * borderThickness));
graphics.setStroke(new BasicStroke(1.0f));
while (this.graphValues.size() > (w - 2))
this.graphValues.removeFirst();
int xOff = w - this.graphValues.size() - 1;
graphics.setColor(scheme.getMidColor());
int count = 0;
for (double value : this.graphValues) {
int valueH = (int) (value * (h - 2));
graphics.drawLine(xOff + count, h - 1 - valueH, xOff + count, h - 2);
count++;
}
graphics.setFont(UIManager.getFont("Panel.font"));
FontMetrics fm = graphics.getFontMetrics();
StringBuffer longFormat = new StringBuffer();
Formatter longFormatter = new Formatter(longFormat);
longFormatter.format("%.1fMB / %.1fMB", this.currTakenHeapSizeKB / 1024.f,
this.currHeapSizeKB / 1024.f);
longFormatter.close();
int strW = fm.stringWidth(longFormat.toString());
int strH = fm.getAscent() + fm.getDescent();
graphics.setColor(scheme.getForegroundColor());
RenderingUtils.installDesktopHints(graphics, this);
if (strW < (w - 5)) {
graphics.drawString(longFormat.toString(), (w - strW) / 2, (h + strH) / 2 - 2);
} else {
String shortFormat = (this.currTakenHeapSizeKB / 1024) + "MB / "
+ (this.currHeapSizeKB / 1024) + "MB";
strW = fm.stringWidth(shortFormat);
graphics.drawString(shortFormat, (w - strW) / 2, (h + strH) / 2 - 2);
}
graphics.dispose();
}
/**
* Returns the preferred width of this panel.
*
* @return Preferred width of this panel.
*/
public int getPreferredWidth() {
BufferedImage dummy = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = dummy.createGraphics();
RenderingUtils.installDesktopHints(g2d, this);
g2d.setFont(UIManager.getFont("Panel.font"));
FontMetrics fm = g2d.getFontMetrics();
int result = fm.stringWidth("100.9MB / 200.9MB");
g2d.dispose();
return result;
}
}
/**
* Thread for heap status panel.
*/
public static class HeapStatusThread extends TrackableThread {
/**
* Current heap size in kilobytes.
*/
private int heapSizeKB;
/**
* Current used portion of heap in kilobytes.
*/
private int takenHeapSizeKB;
/**
* All heap status panels.
*/
private static Set<WeakReference<HeapStatusPanel>> panels = new HashSet<WeakReference<HeapStatusPanel>>();
/**
* Single instance of <code>this</code> thread.
*/
private static HeapStatusThread instance;
/**
* Formatter object (for logfile).
*/
private SimpleDateFormat format;
/**
* Signifies whether a stop request has been issued on <code>this</code>
* thread using the {@link #requestStop()} call.
*/
private boolean isStopRequested;
/**
* Simple constructor. Defined private for singleton.
*
* @see #getInstance()
*/
private HeapStatusThread() {
this.format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss.SSS");
this.isStopRequested = false;
this.setName("Substance heap status");
}
/**
* Gets singleton instance of <code>this</code> thread.
*
* @return Singleton instance of <code>this</code> thread.
*/
public synchronized static HeapStatusThread getInstance() {
if (HeapStatusThread.instance == null) {
HeapStatusThread.instance = new HeapStatusThread();
HeapStatusThread.instance.start();
}
return HeapStatusThread.instance;
}
/**
* Registers new heap status panel with <code>this</code> thread.
*
* @param panel
* Heap statuc panel.
*/
public static synchronized void registerPanel(HeapStatusPanel panel) {
panels.add(new WeakReference<HeapStatusPanel>(panel));
}
/**
* Unregisters new heap status panel from <code>this</code> thread.
*
* @param panel
* Heap statuc panel.
*/
public static synchronized void unregisterPanel(HeapStatusPanel panel) {
for (Iterator<WeakReference<HeapStatusPanel>> it = panels.iterator(); it.hasNext();) {
WeakReference<HeapStatusPanel> ref = it.next();
HeapStatusPanel currPanel = ref.get();
if (panel == currPanel) {
it.remove();
return;
}
}
}
/**
* Updates the values of heap status.
*/
private synchronized void updateHeapCounts() {
long heapSize = Runtime.getRuntime().totalMemory();
long heapFreeSize = Runtime.getRuntime().freeMemory();
this.heapSizeKB = (int) (heapSize / 1024);
this.takenHeapSizeKB = (int) ((heapSize - heapFreeSize) / 1024);
}
/*
* (non-Javadoc)
*
* @see java.lang.Thread#run()
*/
@Override
public void run() {
while (!this.isStopRequested) {
try {
// update every 0.5 seconds
Thread.sleep(500);
} catch (InterruptedException ie) {
}
if (!SubstanceWidgetManager.getInstance()
.isAllowedAnywhere(SubstanceWidgetType.TITLE_PANE_HEAP_STATUS))
continue;
this.updateHeapCounts();
for (Iterator<WeakReference<HeapStatusPanel>> it = panels.iterator(); it
.hasNext();) {
WeakReference<HeapStatusPanel> refPanel = it.next();
HeapStatusPanel panel = refPanel.get();
if (panel == null) {
// prune
it.remove();
continue;
}
panel.updateStatus(this.heapSizeKB, this.takenHeapSizeKB);
}
// see if need to put info in log file
if (SubstanceTitlePane.heapStatusLogfileName != null) {
try (PrintWriter pw = new PrintWriter(
new FileWriter(SubstanceTitlePane.heapStatusLogfileName, true))) {
pw.println(this.format.format(new Date()) + " " + this.takenHeapSizeKB
+ "KB / " + this.heapSizeKB + "KB");
} catch (IOException ioe) {
}
}
}
}
@Override
protected void requestStop() {
this.isStopRequested = true;
HeapStatusThread.instance = null;
}
}
/**
* 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());
SubstanceLookAndFeel.setDecorationType(this, DecorationAreaType.PRIMARY_TITLE_PANE);
this.setForeground(SubstanceColorUtilities.getForegroundColor(SubstanceCoreUtilities
.getSkin(this).getBackgroundColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE)));
// SubstanceColorSchemeUtilities
// .getColorScheme(this, ComponentState.ACTIVE)));
}
/**
* Uninstalls the necessary state.
*/
public void uninstall() {
this.uninstallListeners();
this.window = null;
HeapStatusThread.unregisterPanel(this.heapStatusPanel);
// 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.heapStatusPanel != null) {
for (MouseListener listener : this.heapStatusPanel.getMouseListeners())
this.heapStatusPanel.removeMouseListener(listener);
HeapStatusThread.unregisterPanel(this.heapStatusPanel);
this.remove(this.heapStatusPanel);
}
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 (SubstanceLookAndFeel.WINDOW_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 <code>JRootPane</code> this was created for.
*/
@Override
public JRootPane getRootPane() {
return this.rootPane;
}
/**
* Returns the decoration style of the <code>JRootPane</code>.
*
* @return Decoration style of the <code>JRootPane</code>.
*/
protected int getWindowDecorationStyle() {
return this.getRootPane().getWindowDecorationStyle();
}
/*
* (non-Javadoc)
*
* @see java.awt.Component#addNotify()
*/
@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();
}
/*
* (non-Javadoc)
*
* @see java.awt.Component#removeNotify()
*/
@Override
public void removeNotify() {
super.removeNotify();
this.uninstall();
this.window = null;
}
/**
* Adds any sub-Components contained in the <code>SubstanceTitlePane</code>.
*/
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);
this.heapStatusPanel = new HeapStatusPanel();
this.markExtraComponent(this.heapStatusPanel, ExtraComponentKind.TRAILING);
this.add(this.heapStatusPanel);
boolean isHeapStatusPanelShowing = SubstanceWidgetManager.getInstance()
.isAllowed(rootPane, SubstanceWidgetType.TITLE_PANE_HEAP_STATUS);
this.heapStatusPanel.setVisible(isHeapStatusPanelShowing);
this.heapStatusPanel
.setPreferredSize(new Dimension(80, this.getPreferredSize().height));
this.heapStatusPanel.setToolTipText(SubstanceCoreUtilities.getResourceBundle(rootPane)
.getString("Tooltip.heapStatusPanel"));
this.heapStatusPanel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
System.gc();
}
});
HeapStatusThread.registerPanel(this.heapStatusPanel);
} 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() {
this.setFont(UIManager.getFont("InternalFrame.titleFont", this.getLocale()));
}
/**
* Returns the <code>JMenuBar</code> displaying the appropriate system menu
* items.
*
* @return <code>JMenuBar</code> displaying the appropriate system menu
* items.
*/
protected 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());
this.markExtraComponent(this.menuBar, ExtraComponentKind.LEADING);
return this.menuBar;
}
/**
* Create the <code>Action</code>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 <code>JMenu</code> displaying the appropriate menu items for
* manipulating the Frame.
*
* @return <code>JMenu</code> 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 <code>JMenuItem</code>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(SubstanceCoreUtilities.getResourceBundle(rootPane)
.getString("SystemMenu.skins"));
Map<String, SkinInfo> allSkins = SubstanceLookAndFeel.getAllSkins();
for (Map.Entry<String, SkinInfo> skinEntry : allSkins.entrySet()) {
final String skinClassName = skinEntry.getValue().getClassName();
JMenuItem jmiSkin = new JMenuItem(skinEntry.getKey());
jmiSkin.addActionListener((ActionEvent e) -> SwingUtilities
.invokeLater(() -> SubstanceLookAndFeel.setSkin(skinClassName)));
skinMenu.add(jmiSkin);
}
menu.add(skinMenu);
}
menu.addSeparator();
menu.add(this.closeAction);
}
/**
* Returns a <code>JButton</code> 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);
this.markExtraComponent(button, ExtraComponentKind.TRAILING);
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);
this.closeButton.putClientProperty(SubstanceLookAndFeel.FLAT_PROPERTY, Boolean.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);
this.minimizeButton.putClientProperty(SubstanceLookAndFeel.FLAT_PROPERTY, Boolean.TRUE);
this.minimizeButton.setToolTipText(SubstanceCoreUtilities.getResourceBundle(rootPane)
.getString("SystemMenu.iconify"));
this.toggleButton = this.createTitleButton();
this.toggleButton.setAction(this.restoreAction);
this.toggleButton.setBorder(null);
this.toggleButton.setText(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(SubstanceCoreUtilities.getResourceBundle(rootPane)
.getString("SystemMenu.maximize"));
this.toggleButton.setFocusable(false);
this.toggleButton.putClientProperty(SubstanceLookAndFeel.FLAT_PROPERTY, Boolean.TRUE);
}
syncCloseButtonTooltip();
}
/**
* Returns the <code>LayoutManager</code> that should be installed on the
* <code>SubstanceTitlePane</code>.
*
* @return Layout manager.
*/
protected LayoutManager createLayout() {
return new TitlePaneLayout();
}
/**
* Updates state dependant upon the Window's active state.
*
* @param isActive
* if <code>true</code>, 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 <code>updateRegardless</code> is true
* and the state has not changed, this will update anyway.
*
* @param state
* Window state.
* @param updateRegardless
* if <code>true</code>, the update is done in any case.
*/
private void setState(int state, boolean updateRegardless) {
Window w = this.getWindow();
if ((w != 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(SubstanceCoreUtilities
.getResourceBundle(rootPane).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(SubstanceCoreUtilities
.getResourceBundle(rootPane).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 <code>icon</code>, and
* Action <code>action</code>.
*
* @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
* <code>JRootPane</code> is not contained in a <code>Frame</code>.
*
* @return Frame.
*/
private Frame getFrame() {
Window window = this.getWindow();
if (window instanceof Frame) {
return (Frame) window;
}
return null;
}
/**
* Returns the <code>Window</code> the <code>JRootPane</code> is contained
* in. This will return null if there is no parent ancestor of the
* <code>JRootPane</code>.
*
* @return Window.
*/
private Window getWindow() {
return this.window;
}
/**
* Returns the String to display as the title.
*
* @return Display title.
*/
private String getTitle() {
Window w = this.getWindow();
if (w instanceof Frame) {
return ((Frame) w).getTitle();
}
if (w instanceof Dialog) {
return ((Dialog) w).getTitle();
}
return null;
}
/*
* (non-Javadoc)
*
* @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
*/
@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());
}
final JRootPane rootPane = this.getRootPane();
Window window = this.getWindow();
boolean leftToRight = (window == null) ? rootPane.getComponentOrientation().isLeftToRight()
: 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);
int xOffset = 0;
String theTitle = this.getTitle();
if (theTitle != null) {
Rectangle titleTextRect = this.getTitleTextRectangle();
FontMetrics fm = rootPane.getFontMetrics(g.getFont());
int titleWidth = titleTextRect.width - 20;
String clippedTitle = SubstanceCoreUtilities.clipString(fm, titleWidth, theTitle);
// show tooltip with full title only if necessary
if (theTitle.equals(clippedTitle)) {
this.setToolTipText(null);
} else {
this.setToolTipText(theTitle);
}
theTitle = clippedTitle;
if (leftToRight)
xOffset = titleTextRect.x;
else
xOffset = titleTextRect.x + titleTextRect.width - fm.stringWidth(theTitle);
}
Graphics2D graphics = (Graphics2D) g.create();
Font font = SubstanceLookAndFeel.getFontPolicy().getFontSet("Substance", null)
.getWindowTitleFont();
graphics.setFont(font);
BackgroundPaintingUtils.update(graphics, SubstanceTitlePane.this, false);
// DecorationPainterUtils.paintDecorationBackground(graphics,
// SubstanceTitlePane.this, false);
// draw the title (if needed)
if (theTitle != null) {
FontMetrics fm = rootPane.getFontMetrics(graphics.getFont());
int yOffset = ((height - 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, theTitle, width,
height, xOffset, yOffset);
}
GhostPaintingUtils.paintGhostImages(this, graphics);
// long end = System.nanoTime();
// System.out.println(end - start);
graphics.dispose();
}
/**
* Computes the rectangle of the title text. This method looks at all the
* children components of the title pane, grouping them by leading and
* trailing (based on {@link #EXTRA_COMPONENT_KIND} client property). The
* title text rectangle is the space between the leading group and the
* trailing group.
*
* @return Rectangle of the title text.
* @throws IllegalStateException
* If at least one child component of this title pane is not
* marked with the {@link #EXTRA_COMPONENT_KIND} client
* property.
* @see #markExtraComponent(JComponent, ExtraComponentKind)
* @see #EXTRA_COMPONENT_KIND
*/
protected Rectangle getTitleTextRectangle() {
JRootPane rootPane = this.getRootPane();
Window window = this.getWindow();
boolean leftToRight = (window == null) ? rootPane.getComponentOrientation().isLeftToRight()
: window.getComponentOrientation().isLeftToRight();
if (leftToRight) {
int maxLeadingX = 0;
int minTrailingX = this.getWidth();
for (int i = 0; i < this.getComponentCount(); i++) {
Component child = this.getComponent(i);
if (!child.isVisible())
continue;
if (child instanceof JComponent) {
ExtraComponentKind kind = (ExtraComponentKind) ((JComponent) child)
.getClientProperty(EXTRA_COMPONENT_KIND);
if (kind == null) {
throw new IllegalStateException(
"Title pane child " + child.getClass().getName()
+ " is not marked as leading or trailing");
}
if (kind == ExtraComponentKind.LEADING) {
int cx = child.getX() + child.getWidth();
if (cx > maxLeadingX)
maxLeadingX = cx;
} else {
int cx = child.getX();
if (cx < minTrailingX)
minTrailingX = cx;
}
}
}
int start = maxLeadingX + 10;
int end = minTrailingX - 5;
return new Rectangle(start, 0, end - start, this.getHeight());
} else {
int minLeadingX = this.getWidth();
int maxTrailingX = 0;
for (int i = 0; i < this.getComponentCount(); i++) {
Component child = this.getComponent(i);
if (!child.isVisible())
continue;
if (child instanceof JComponent) {
ExtraComponentKind kind = (ExtraComponentKind) ((JComponent) child)
.getClientProperty(EXTRA_COMPONENT_KIND);
if (kind == null) {
throw new IllegalStateException(
"Title pane child " + child.getClass().getName()
+ " is not marked as leading or trailing");
}
if (kind == ExtraComponentKind.LEADING) {
int cx = child.getX();
if (cx < minLeadingX)
minLeadingX = cx;
} else {
int cx = child.getX() + child.getWidth();
if (cx > maxTrailingX)
maxTrailingX = cx;
}
}
}
int start = maxTrailingX + 5;
int end = minLeadingX - 10;
return new Rectangle(start, 0, end - start, this.getHeight());
}
}
/**
* Actions used to <code>close</code> the <code>Window</code>.
*/
private class CloseAction extends AbstractAction {
/**
* Creates a new close action.
*/
public CloseAction() {
super(SubstanceCoreUtilities.getResourceBundle(rootPane).getString("SystemMenu.close"),
SubstanceImageCreator.getCloseIcon(
SubstanceCoreUtilities.getSkin(rootPane)
.getActiveColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE),
SubstanceCoreUtilities.getSkin(rootPane).getBackgroundColorScheme(
DecorationAreaType.PRIMARY_TITLE_PANE)));
}
public void actionPerformed(ActionEvent e) {
Window window = SubstanceTitlePane.this.getWindow();
if (window != null) {
window.dispatchEvent(new WindowEvent(window, WindowEvent.WINDOW_CLOSING));
}
}
}
/**
* Actions used to <code>iconfiy</code> the <code>Frame</code>.
*/
private class IconifyAction extends AbstractAction {
/**
* Creates a new iconify action.
*/
public IconifyAction() {
super(SubstanceCoreUtilities.getResourceBundle(rootPane)
.getString("SystemMenu.iconify"),
SubstanceImageCreator.getMinimizeIcon(
SubstanceCoreUtilities.getSkin(rootPane)
.getActiveColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE),
SubstanceCoreUtilities.getSkin(rootPane).getBackgroundColorScheme(
DecorationAreaType.PRIMARY_TITLE_PANE)));
}
public void actionPerformed(ActionEvent e) {
Frame frame = SubstanceTitlePane.this.getFrame();
if (frame != null) {
frame.setExtendedState(SubstanceTitlePane.this.state | Frame.ICONIFIED);
}
}
}
/**
* Actions used to <code>restore</code> the <code>Frame</code>.
*/
private class RestoreAction extends AbstractAction {
/**
* Creates a new restore action.
*/
public RestoreAction() {
super(SubstanceCoreUtilities.getResourceBundle(rootPane)
.getString("SystemMenu.restore"),
SubstanceImageCreator.getRestoreIcon(
SubstanceCoreUtilities.getSkin(rootPane)
.getActiveColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE),
SubstanceCoreUtilities.getSkin(rootPane).getBackgroundColorScheme(
DecorationAreaType.PRIMARY_TITLE_PANE)));
}
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 <code>restore</code> the <code>Frame</code>.
*/
private class MaximizeAction extends AbstractAction {
/**
* Creates a new maximize action.
*/
public MaximizeAction() {
super(SubstanceCoreUtilities.getResourceBundle(rootPane)
.getString("SystemMenu.maximize"),
SubstanceImageCreator.getMaximizeIcon(
SubstanceCoreUtilities.getSkin(rootPane)
.getActiveColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE),
SubstanceCoreUtilities.getSkin(rootPane)
.getEnabledColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE)));
}
public void actionPerformed(ActionEvent e) {
Frame frame = SubstanceTitlePane.this.getFrame();
if (frame != null) {
if (frame instanceof JFrame) {
SubstanceRootPaneUI rpUI = (SubstanceRootPaneUI) ((JFrame) frame).getRootPane()
.getUI();
rpUI.setMaximized();
}
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 <code>JRootPane</code>.
*/
public class SubstanceMenuBar extends JMenuBar {
@Override
public void paint(Graphics g) {
if (appIcon != null) {
int scaleFactor = SubstanceCoreUtilities.isHiDpiAwareImage(appIcon) ? 2 : 1;
g.drawImage(appIcon, 0, 0, appIcon.getWidth(null) / scaleFactor,
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 {
/*
* (non-Javadoc)
*
* @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
* java.awt.Component)
*/
public void addLayoutComponent(String name, Component c) {
}
/*
* (non-Javadoc)
*
* @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
*/
public void removeLayoutComponent(Component c) {
}
/*
* (non-Javadoc)
*
* @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
*/
public Dimension preferredLayoutSize(Container c) {
int height = this.computeHeight();
return new Dimension(height, height);
}
/**
* Computes title pane height.
*
* @return Title pane height.
*/
private int computeHeight() {
FontMetrics fm = SubstanceTitlePane.this.rootPane
.getFontMetrics(SubstanceTitlePane.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);
return finalHeight;
}
/*
* (non-Javadoc)
*
* @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
*/
public Dimension minimumLayoutSize(Container c) {
return this.preferredLayoutSize(c);
}
/*
* (non-Javadoc)
*
* @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
*/
public void layoutContainer(Container c) {
boolean leftToRight = (SubstanceTitlePane.this.window == null)
? SubstanceTitlePane.this.getRootPane().getComponentOrientation()
.isLeftToRight()
: SubstanceTitlePane.this.window.getComponentOrientation().isLeftToRight();
int w = SubstanceTitlePane.this.getWidth();
int x;
int y = 3;
int spacing;
int buttonHeight;
int buttonWidth;
if ((SubstanceTitlePane.this.closeButton != null)
&& (SubstanceTitlePane.this.closeButton.getIcon() != null)) {
buttonHeight = SubstanceTitlePane.this.closeButton.getIcon().getIconHeight();
buttonWidth = SubstanceTitlePane.this.closeButton.getIcon().getIconWidth();
} else {
buttonHeight = SubstanceSizeUtils.getTitlePaneIconSize();
buttonWidth = SubstanceSizeUtils.getTitlePaneIconSize();
}
y = (getHeight() - buttonHeight) / 2;
// assumes all buttons have the same dimensions
// these dimensions include the borders
x = leftToRight ? w : 0;
spacing = 5;
x = leftToRight ? spacing : w - buttonWidth - spacing;
if (SubstanceTitlePane.this.menuBar != null) {
SubstanceTitlePane.this.menuBar.setBounds(x, y, buttonWidth, buttonHeight);
// System.out.println(menuBar.getBounds());
}
x = leftToRight ? w : 0;
spacing = 3;
x += leftToRight ? -spacing - buttonWidth : spacing;
if (SubstanceTitlePane.this.closeButton != null) {
SubstanceTitlePane.this.closeButton.setBounds(x, y, buttonWidth, buttonHeight);
}
if (!leftToRight)
x += buttonWidth;
if (SubstanceTitlePane.this.getWindowDecorationStyle() == JRootPane.FRAME) {
if (Toolkit.getDefaultToolkit().isFrameStateSupported(Frame.MAXIMIZED_BOTH)) {
if (SubstanceTitlePane.this.toggleButton.getParent() != null) {
spacing = 10;
x += leftToRight ? -spacing - buttonWidth : spacing;
SubstanceTitlePane.this.toggleButton.setBounds(x, y, buttonWidth,
buttonHeight);
if (!leftToRight) {
x += buttonWidth;
}
}
}
if ((SubstanceTitlePane.this.minimizeButton != null)
&& (SubstanceTitlePane.this.minimizeButton.getParent() != null)) {
spacing = 2;
x += leftToRight ? -spacing - buttonWidth : spacing;
SubstanceTitlePane.this.minimizeButton.setBounds(x, y, buttonWidth,
buttonHeight);
if (!leftToRight) {
x += buttonWidth;
}
}
if ((SubstanceTitlePane.this.heapStatusPanel != null)
&& SubstanceTitlePane.this.heapStatusPanel.isVisible()) {
spacing = 5;
x += leftToRight
? (-spacing
- SubstanceTitlePane.this.heapStatusPanel.getPreferredWidth())
: spacing;
SubstanceTitlePane.this.heapStatusPanel.setBounds(x, 1,
SubstanceTitlePane.this.heapStatusPanel.getPreferredWidth(),
SubstanceTitlePane.this.getHeight() - 3);
}
}
// buttonsWidth = leftToRight ? w - x : x;
}
}
/**
* PropertyChangeListener installed on the Window. Updates the necessary
* state as the state of the Window changes.
*/
private class PropertyChangeHandler implements PropertyChangeListener {
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.repaint();
SubstanceTitlePane.this.setToolTipText((String) pce.getNewValue());
} else if ("componentOrientation" == name) {
revalidate();
repaint();
} else if ("iconImage" == 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);
}
}
/**
* Sets location for heap status logfile. Relevant if
* {@link #setCanHaveHeapStatusPanel(boolean)} was called with
* <code>true</code>.
*
* @param heapStatusLogfileName
* Logfile for the heap status panel.
*/
public static void setHeapStatusLogfileName(String heapStatusLogfileName) {
SubstanceTitlePane.heapStatusLogfileName = heapStatusLogfileName;
}
/**
* Synchronizes the tooltip of the close button.
*/
protected void syncCloseButtonTooltip() {
if (SubstanceCoreUtilities.isRootPaneModified(this.getRootPane())) {
this.closeButton.setToolTipText(
SubstanceCoreUtilities.getResourceBundle(rootPane).getString("SystemMenu.close")
+ " [" + SubstanceCoreUtilities.getResourceBundle(rootPane)
.getString("Tooltip.contentsNotSaved")
+ "]");
} else {
this.closeButton.setToolTipText(SubstanceCoreUtilities.getResourceBundle(rootPane)
.getString("SystemMenu.close"));
}
this.closeButton.repaint();
}
/**
* Marks the specified child component with the specified extra component
* kind.
*
* @param comp
* Child component.
* @param kind
* Extra kind.
* @see #getTitleTextRectangle()
* @see #EXTRA_COMPONENT_KIND
*/
protected void markExtraComponent(JComponent comp, ExtraComponentKind kind) {
comp.putClientProperty(EXTRA_COMPONENT_KIND, kind);
}
/**
* Updates the application icon.
*/
private void updateAppIcon() {
Window window = getWindow();
if (window == null) {
this.appIcon = null;
return;
}
java.util.List<Image> iconImages = window.getIconImages();
if (iconImages.size() == 0) {
this.appIcon = null;
} else {
int prefSize = SubstanceSizeUtils.getTitlePaneIconSize();
this.appIcon = SubstanceCoreUtilities.getScaledIconImage(iconImages, prefSize,
prefSize);
}
}
public AbstractButton getCloseButton() {
return this.closeButton;
}
}