diff --git a/libsrc/substance-fix/src/org/pushingpixels/substance/internal/utils/SubstanceTitlePane.java b/libsrc/substance-fix/src/org/pushingpixels/substance/internal/utils/SubstanceTitlePane.java
new file mode 100644
index 000000000..bc483a5f3
--- /dev/null
+++ b/libsrc/substance-fix/src/org/pushingpixels/substance/internal/utils/SubstanceTitlePane.java
@@ -0,0 +1,1821 @@
+/*
+ * Copyright (c) 2005-2010 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.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.Toolkit;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+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.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.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.SubstanceConstants.SubstanceWidgetType;
+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 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;
+
+ /**
+ * 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 null - in
+ * this case the {@link HeapStatusThread} will not write heap information.
+ */
+ private static String heapStatusLogfileName;
+
+ /**
+ * The heap status panel of this title pane.
+ */
+ protected HeapStatusPanel heapStatusPanel;
+
+ /**
+ * The heap status toggle menu item of this title pane.
+ */
+ protected JCheckBoxMenuItem heapStatusMenuItem;
+
+ /**
+ * Listens on changes to componentOrientation 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 graphValues;
+
+ /**
+ * Creates new heap status panel.
+ */
+ public HeapStatusPanel() {
+ this.graphValues = new LinkedList();
+ HeapStatusThread.getInstance();
+ }
+
+ /**
+ * Updates the values for this 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.drawRect(0, 0, w - 1, h - 1);
+
+ graphics.setColor(scheme.getExtraLightColor());
+ graphics.fillRect(1, 1, w - 2, h - 2);
+
+ 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);
+ 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> panels = new HashSet>();
+
+ /**
+ * Single instance of this thread.
+ */
+ private static HeapStatusThread instance;
+
+ /**
+ * Formatter object (for logfile).
+ */
+ private SimpleDateFormat format;
+
+ /**
+ * Signifies whether a stop request has been issued on this
+ * 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 this thread.
+ *
+ * @return Singleton instance of this 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 this thread.
+ *
+ * @param panel
+ * Heap statuc panel.
+ */
+ public static synchronized void registerPanel(HeapStatusPanel panel) {
+ panels.add(new WeakReference(panel));
+ }
+
+ /**
+ * Unregisters new heap status panel from this thread.
+ *
+ * @param panel
+ * Heap statuc panel.
+ */
+ public static synchronized void unregisterPanel(HeapStatusPanel panel) {
+ for (Iterator> it = panels
+ .iterator(); it.hasNext();) {
+ WeakReference 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> it = panels
+ .iterator(); it.hasNext();) {
+ WeakReference 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) {
+ PrintWriter pw = null;
+ try {
+ pw = new PrintWriter(new FileWriter(
+ SubstanceTitlePane.heapStatusLogfileName, true));
+ pw.println(this.format.format(new Date()) + " "
+ + this.takenHeapSizeKB + "KB / "
+ + this.heapSizeKB + "KB");
+ } catch (IOException ioe) {
+
+ } finally {
+ if (pw != null) {
+ pw.close();
+ }
+ }
+ }
+ }
+ }
+
+ @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 = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(final PropertyChangeEvent evt) {
+ if (SubstanceLookAndFeel.WINDOW_MODIFIED.equals(evt
+ .getPropertyName())) {
+ syncCloseButtonTooltip();
+ // if (Boolean.TRUE.equals(evt.getNewValue())) {
+ // SubstanceTitlePane.this.closeButton
+ // .setToolTipText(SubstanceLookAndFeel
+ // .getLabelBundle().getString(
+ // "SystemMenu.close")
+ // + " ["
+ // + SubstanceLookAndFeel
+ // .getLabelBundle()
+ // .getString(
+ // "Tooltip.contentsNotSaved")
+ // + "]");
+ // } else {
+ // SubstanceTitlePane.this.closeButton
+ // .setToolTipText(SubstanceLookAndFeel
+ // .getLabelBundle().getString(
+ // "SystemMenu.close"));
+ // }
+ // SubstanceTitlePane.this.closeButton.repaint();
+ }
+
+ if ("componentOrientation".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ 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.
+ */
+ 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 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);
+
+ 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 JMenuBar displaying the appropriate system menu
+ * items.
+ *
+ * @return JMenuBar 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 Actions 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 JMenuItems 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 allSkins = SubstanceLookAndFeel.getAllSkins();
+ for (Map.Entry skinEntry : allSkins.entrySet()) {
+ final String skinClassName = skinEntry.getValue()
+ .getClassName();
+ JMenuItem jmiSkin = new JMenuItem(skinEntry.getKey());
+ jmiSkin.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ SubstanceLookAndFeel.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);
+
+ 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);
+ // this.closeButton.setToolTipText(SubstanceLookAndFeel
+ // .getLabelBundle().getString(
+ // "SystemMenu.close"));
+
+ Icon closeIcon = new TransitionAwareIcon(closeButton,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
+ return 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,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(
+ SubstanceColorScheme scheme) {
+ return 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,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(
+ SubstanceColorScheme scheme) {
+ return 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 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) {
+ 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,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(
+ SubstanceColorScheme scheme) {
+ return 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,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(
+ SubstanceColorScheme scheme) {
+ return 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 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() {
+ Window window = this.getWindow();
+
+ if (window instanceof Frame) {
+ return (Frame) window;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the Window the JRootPane is contained
+ * in. This will return null if there is no parent ancestor of the
+ * JRootPane.
+ *
+ * @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();
+
+ SubstanceTextUtilities.paintTextWithDropShadow(this, graphics,
+ SubstanceColorUtilities.getForegroundColor(scheme),
+ 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 close the Window.
+ */
+ 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)));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Window window = SubstanceTitlePane.this.getWindow();
+
+ 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.
+ */
+ 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)));
+ }
+
+ @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.
+ */
+ 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)));
+ }
+
+ @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.
+ */
+ 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)));
+ }
+
+ @Override
+ 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 JRootPane.
+ */
+ public class SubstanceMenuBar extends JMenuBar {
+ @Override
+ public void paint(Graphics g) {
+ if (appIcon != null) {
+ g.drawImage(appIcon, 0, 0, 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)
+ */
+ @Override
+ public void addLayoutComponent(String name, Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
+ */
+ @Override
+ public void removeLayoutComponent(Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
+ */
+ @Override
+ 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)
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container c) {
+ return this.preferredLayoutSize(c);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ 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 {
+ @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.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
+ * true.
+ *
+ * @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 icons = window.getIconImages();
+
+ if (icons.size() == 0) {
+ this.appIcon = null;
+ } else {
+ int prefSize = SubstanceSizeUtils.getTitlePaneIconSize();
+ this.appIcon = SubstanceCoreUtilities.getScaledIconImage(icons,
+ prefSize, prefSize);
+ }
+ }
+
+ public AbstractButton getCloseButton() {
+ return this.closeButton;
+ }
+}