From fae5e23468ca3983ff5a86d3e096ecd970e4763d Mon Sep 17 00:00:00 2001 From: Andreas Kuhtz Date: Mon, 16 Jan 2023 16:03:44 +0100 Subject: [PATCH] Fix flash TitleBar if notification is set on DockKey. --- .../swing/docking/DockViewTitleBar.java | 1448 +++++++++-------- .../swing/docking/ui/DockViewTitleBarUI.java | 1150 +++++++------ 2 files changed, 1402 insertions(+), 1196 deletions(-) diff --git a/src/main/java/com/vlsolutions/swing/docking/DockViewTitleBar.java b/src/main/java/com/vlsolutions/swing/docking/DockViewTitleBar.java index 4fe76ad..2f65f93 100644 --- a/src/main/java/com/vlsolutions/swing/docking/DockViewTitleBar.java +++ b/src/main/java/com/vlsolutions/swing/docking/DockViewTitleBar.java @@ -18,42 +18,89 @@ package com.vlsolutions.swing.docking; -import javax.swing.*; -import java.awt.*; -import java.awt.event.*; -import java.beans.*; -import java.awt.event.MouseEvent; import java.awt.Component; +import java.awt.Container; +import java.awt.KeyboardFocusManager; import java.awt.Point; - -/** A title bar, associated to a DockView (container of a single user component). +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.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.KeyStroke; +import javax.swing.UIManager; + +/** + * A title bar, associated to a DockView (container of a single user component). *

* Here is an example of a title bar : . *

* DockViewTitleBar is able to display the following properties of a DockKey : *

*

* This title bar supports buttons used for docking features : - * - * - * - * - * - * - * - * + *
function Version 1.1Version 2.0
maximize
restore
hide
dock
close
float (detach)n/a
attachn/a
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * *
functionVersion 1.1Version 2.0
maximize
restore
hide
dock
close
float (detach)n/a
attachn/a
* *

- * The buttons managed have no effect on the state of the dockable : they just fire - * property change events, and it is the responsibility of the DockableContainer to - * listen to those events and to relay the operation to the docking desktop. + * The buttons managed have no effect on the state of the dockable : they just fire property change events, and it is + * the responsibility of the DockableContainer to listen to those events and to relay the operation to the docking + * desktop. *

* Note : the UI Delegate of the DockViewTitleBar is the {@link com.vlsolutions.swing.docking.ui.DockViewTitleBarUI} * @@ -62,648 +109,721 @@ */ public class DockViewTitleBar extends JPanel implements DockableDragSource { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; + + /** Property name designating the autohide button selection */ + public static final String PROPERTY_AUTOHIDE = "DockTitle.AUTOHIDE"; + + /** Property name designating the close button selection */ + public static final String PROPERTY_CLOSED = "DockTitle.CLOSED"; + + /** Property name designating a drag gesture beginning */ + public static final String PROPERTY_DRAGGED = "DockTitle.DRAGGED"; + + /** Property name designating the maximized button selection */ + public static final String PROPERTY_MAXIMIZED = "DockTitle.MAXIMIZED"; + + /** Property name designating the float button selection */ + public static final String PROPERTY_FLOAT = "DockTitle.FLOAT"; + + private static final String uiClassID = "DockViewTitleBarUI"; + + private static final String CLOSE_TEXT = UIManager.getString("DockViewTitleBar.closeButtonText"); + + private static final String ICONIFY_TEXT = UIManager.getString("DockViewTitleBar.minimizeButtonText"); + + private static final String RESTORE_TEXT = UIManager.getString("DockViewTitleBar.restoreButtonText"); + + private static final String MAXIMIZE_TEXT = UIManager.getString("DockViewTitleBar.maximizeButtonText"); + + private static final String FLOAT_TEXT = UIManager.getString("DockViewTitleBar.floatButtonText"); + + private static final String ATTACH_TEXT = UIManager.getString("DockViewTitleBar.attachButtonText"); + + private static Icon closeIcon = UIManager.getIcon("DockViewTitleBar.menu.close"); + + private static Icon maximizeIcon = UIManager.getIcon("DockViewTitleBar.menu.maximize"); + + private static Icon restoreIcon = UIManager.getIcon("DockViewTitleBar.menu.restore"); + + private static Icon hideIcon = UIManager.getIcon("DockViewTitleBar.menu.hide"); + + private static Icon dockIcon = UIManager.getIcon("DockViewTitleBar.menu.dock"); + + private static Icon floatIcon = UIManager.getIcon("DockViewTitleBar.menu.float"); + + private static Icon attachIcon = UIManager.getIcon("DockViewTitleBar.menu.attach"); + + // ------------ member fields -------------- + + // this creation tricks to manage the UI class requiring access to the components + // before they are created + private JLabel titleLabel = getTitleLabel(); + + private JButton closeButton = getCloseButton(); + + private JButton dockButton = getHideOrDockButton(); + + private JButton maximizeButton = getMaximizeOrRestoreButton(); + + private JButton floatButton = getFloatButton(); + + private boolean active; - /** Property name designating the autohide button selection */ - public static final String PROPERTY_AUTOHIDE = "DockTitle.AUTOHIDE"; + private ActionListener actionListener = new ActionListener() { - /** Property name designating the close button selection */ - public static final String PROPERTY_CLOSED = "DockTitle.CLOSED"; - - /** Property name designating a drag gesture beginning */ - public static final String PROPERTY_DRAGGED = "DockTitle.DRAGGED"; - - /** Property name designating the maximized button selection */ - public static final String PROPERTY_MAXIMIZED = "DockTitle.MAXIMIZED"; - - /** Property name designating the float button selection */ - public static final String PROPERTY_FLOAT = "DockTitle.FLOAT"; - - private static final String uiClassID = "DockViewTitleBarUI"; - - private static final String CLOSE_TEXT = UIManager.getString("DockViewTitleBar.closeButtonText"); - private static final String ICONIFY_TEXT = UIManager.getString("DockViewTitleBar.minimizeButtonText"); - private static final String RESTORE_TEXT = UIManager.getString("DockViewTitleBar.restoreButtonText"); - private static final String MAXIMIZE_TEXT = UIManager.getString("DockViewTitleBar.maximizeButtonText"); - private static final String FLOAT_TEXT = UIManager.getString("DockViewTitleBar.floatButtonText"); - private static final String ATTACH_TEXT = UIManager.getString("DockViewTitleBar.attachButtonText"); - - private static Icon closeIcon = UIManager.getIcon("DockViewTitleBar.menu.close"); - private static Icon maximizeIcon = UIManager.getIcon("DockViewTitleBar.menu.maximize"); - private static Icon restoreIcon = UIManager.getIcon("DockViewTitleBar.menu.restore"); - private static Icon hideIcon = UIManager.getIcon("DockViewTitleBar.menu.hide"); - private static Icon dockIcon = UIManager.getIcon("DockViewTitleBar.menu.dock"); - private static Icon floatIcon = UIManager.getIcon("DockViewTitleBar.menu.float"); - private static Icon attachIcon = UIManager.getIcon("DockViewTitleBar.menu.attach"); - - // ------------ member fields -------------- - - // this creation tricks to manage the UI class requiring access to the components - // before they are created - private JLabel titleLabel = getTitleLabel(); - - private JButton closeButton = getCloseButton(); - private JButton dockButton = getHideOrDockButton(); - private JButton maximizeButton = getMaximizeOrRestoreButton(); - private JButton floatButton = getFloatButton(); - private boolean active; - - private ActionListener actionListener = new ActionListener() { - - public void actionPerformed(ActionEvent e) { - if(e.getActionCommand().equals("dock")) { - dockAction(); - } else if(e.getActionCommand().equals("close")) { - closeAction(); - } else if(e.getActionCommand().equals("maximize")) { - maximizeAction(); - } else if(e.getActionCommand().equals("float")) { - floatAction(); - } - } - }; - - private Dockable target; // the component this title is for - - /** used to have a state of blinking (notification) */ - private boolean isNotification = false; - - private JPopupMenu currentPopUp = null; - - /** current blinking count (the limit to the notification timer) */ - private int blinkCount = 0; - - private int MAX_BLINKS = UIManager.getInt("DockingDesktop.notificationBlinkCount"); - - /** reacts to single and double click on title bar */ - private MouseListener titleMouseListener = new MouseAdapter() { - - public void mouseClicked(MouseEvent e) { - if(e.getClickCount() == 2) { - maximizeAction(); - } else { - //requestFocus(); - // replaced by a better focus behaviour for 1.2 - target.getComponent().requestFocus(); - } - } - - public void mousePressed(MouseEvent e) { - if(e.isPopupTrigger()) { - checkForPopUp(e); - } - } - - public void mouseReleased(MouseEvent e) { - if(e.isPopupTrigger()) { - checkForPopUp(e); - } - } - - }; - - /** listen to the key changes */ - private PropertyChangeListener dockKeyListener = new PropertyChangeListener() { - - public void propertyChange(PropertyChangeEvent e) { - String pName = e.getPropertyName(); - if(pName.equals(DockKey.PROPERTY_ICON)) { - titleLabel.setIcon((Icon) e.getNewValue()); - } else if(pName.equals(DockKey.PROPERTY_NAME)) { - titleLabel.setText((String) e.getNewValue()); - revalidate(); - } else if(pName.equals(DockKey.PROPERTY_TOOLTIP)) { - setToolTipText((String) e.getNewValue()); - } else if(pName.equals(DockKey.PROPERTY_NOTIFICATION)) { - // attract user attention - boolean isOn = ((Boolean) e.getNewValue()).booleanValue(); - if(isOn && ! isActive()) { - if(notificationTimer == null) { - notificationTimer = new javax.swing.Timer(1000, new ActionListener() { - - public void actionPerformed(ActionEvent actionEvent) { - setNotification(! isNotification); - if(! isNotification) { - blinkCount++; - if(blinkCount >= MAX_BLINKS) { - blinkCount = 0; - notificationTimer.stop(); // enough blinking - } - } - } - }); - } - notificationTimer.restart(); - } else { - if(notificationTimer != null) { - notificationTimer.stop(); - blinkCount = 0; - } - setNotification(false); - } - } - /*else if (pName.equals(DockKey.PROPERTY_DOCKABLE_STATE)){ - * - int newState = ((Integer)e.getNewValue()).intValue(); - int oldState = ((Integer)e.getOldValue()).intValue(); - switch (newState){ - case DockableState.HIDDEN : - setAutoHide(true); - break; - case DockableState.MAXIMIZED : - setMaximized(true); - break; - case DockableState.DOCKED : - if (oldState == DockableState.HIDDEN){ - setAutoHide(false); - } - break; - default: - //@todo : managed the floatable state - // nothing to track here - } - }*/ - } - }; - - /** Timer used to trigger repaint event for notification (blinking title bar) */ - private javax.swing.Timer notificationTimer; - - /** singleton for keyboard management */ - @SuppressWarnings("unused") - private static FocusHighlighter focusHighlighter = new FocusHighlighter(); //2005/11/10 - - private DockingDesktop desktop; - - /** Constructs an empty title bar (no dockable yet associated). - */ - public DockViewTitleBar() { - this(null); - } - - /** Constructs a title bar for the specified dockable. - *

- * Warning : a DockViewTitleBar can be used with multiple dockables (this is the case - * for example in autohide borders, where a single titlebar is shared by all hidden dockables - * (shown only when one is expanding). - */ - public DockViewTitleBar(Dockable dockable) { - setDockable(dockable); - - closeButton.setText(""); - dockButton.setText(""); - maximizeButton.setText(""); - closeButton.setActionCommand("close"); - dockButton.setActionCommand("dock"); - maximizeButton.setActionCommand("maximize"); - floatButton.setActionCommand("float"); - - closeButton.addActionListener(actionListener); - dockButton.addActionListener(actionListener); - maximizeButton.addActionListener(actionListener); - floatButton.addActionListener(actionListener); - - this.addMouseListener(titleMouseListener); - - //maximizeButton.addMouseListener(new ButtonRolloverEffect()); - - } - - /** Notification of completion of layout. - *

This hook can be used to insert customized buttons without otherwise - * having to fully replace the UI delegate - */ - public void finishLayout() { - - } - - /** Returns the desktop associated to this title bar, if one has been registered - * with #installDocking(DockingDesktop), or null. - */ - public DockingDesktop getDesktop() { - return desktop; - } - - /** Overriden as a means to unregister internal listeners, do not call directly */ - public void removeNotify() { - super.removeNotify(); - /* KeyboardFocusManager.getCurrentKeyboardFocusManager().removePropertyChangeListener( - focusHighlighter); 2005/11/10: now it's shared between title bars*/ - if(target != null) { - DockKey k = target.getDockKey(); - if(k != null) { - k.removePropertyChangeListener(dockKeyListener);//2007/04/16 - } - } - - } - - /** Returns the label used to display the dockkey name. - *

Shouldn't be used to update the title : the best way is to update the - * DockKey (property listener ensure the labels and buttons stay in sync). - * - *@since 2.0 - */ - public JLabel getTitleLabel() { - if(titleLabel == null) { - titleLabel = new JLabel(); - } - return titleLabel; - } - - public JButton getCloseButton() { - if(closeButton == null) { - closeButton = new JButton(); - } - return closeButton; - } - - /** returns the button used for hiding or docking the view. - *

- * As hiding and docking are mutually exclusive, the same button is used for both purposes - */ - public JButton getHideOrDockButton() { - if(dockButton == null) { - dockButton = new JButton(); - } - return dockButton; - } - - /** returns the button used for maximizing or restoring the view. - *

- * As those operations are mutually exclusive, the same button is used for both purposes - */ - public JButton getMaximizeOrRestoreButton() { - if(maximizeButton == null) { - maximizeButton = new JButton(); - } - return maximizeButton; - } - - /** returns the button used for floating (detach) the view. - */ - public JButton getFloatButton() { - if(floatButton == null) { - floatButton = new JButton(); - } - return floatButton; - } - - private void setNotification(boolean notification) { - boolean old = this.isNotification; - this.isNotification = notification; - firePropertyChange("titlebar.notification", old, notification); - } - - private boolean isAutoHide() { - return target.getDockKey().getLocation() == DockableState.Location.HIDDEN; - } - - private boolean isMaximized() { - return target.getDockKey().getLocation() == DockableState.Location.MAXIMIZED; - } - - private void dockAction() { - boolean old = isAutoHide(); - firePropertyChange(PROPERTY_AUTOHIDE, old, ! old); - } - - private void maximizeAction() { - // we use the visible property of the button to check if maximization - // is enabled or not - if(target.getDockKey().getLocation() == DockableState.Location.FLOATING) { - // ignore : floating cannot be maximized - return; - } - if(target.getDockKey().isMaximizeEnabled()) { - //if (maximizeButton.isVisible()){ //2008/04/17 - boolean old = isMaximized(); - // we don't really change the maximized property, - // which will be updated if the desktop accepts this maximization - /* We do not use fireVetoableChange, to keep maximization processing outside - * the title bar (everything is driven par the dockingdesktop and the - * SingleDockableContainer - */ - firePropertyChange(PROPERTY_MAXIMIZED, old, ! old); - } - } - - private void closeAction() { - firePropertyChange(PROPERTY_CLOSED, false, true); - } - - private void floatAction() { - if(target.getDockKey().getLocation() == DockableState.Location.FLOATING) { - firePropertyChange(PROPERTY_FLOAT, true, false); - } else { - firePropertyChange(PROPERTY_FLOAT, false, true); - } - } - - /** This method is invoked to hide the pop-up that could still be visible - * (To avoid a visible pop-up for an invisible component) - */ - public void closePopUp() { - if(currentPopUp != null) { - currentPopUp.setVisible(false); - // @todo uncertain about correct gc of the pop-up in that case - currentPopUp = null; - } - } - - /** {@inheritDoc} - * @since 2.0 - */ - public String getUIClassID() { - return uiClassID; - } - - /** Returns true if the dockable is the currently active one. - *

- * There is at most one active dockable for a dekstop, and it there is one, - * it is the one which contains the keybord focused component. - * */ - public boolean isActive() { - return active; - } - - /** Updates the active property. - * A title bar is active when the dockable it is for is ancestor of - * the keybord focused component. - * */ - public void setActive(boolean active) { - boolean old = this.active; - this.active = active; - firePropertyChange("active", old, active); - } - - /** Changes the dockable this title bar is for */ - public void setDockable(Dockable dockable) { - Dockable old = target; - if(target != null && target != dockable) { - target.getDockKey().removePropertyChangeListener(dockKeyListener); - } - if(dockable != null) { - this.target = dockable; - - DockKey key = dockable.getDockKey(); - titleLabel.setText(key.getName()); - titleLabel.setIcon(key.getIcon()); - setToolTipText(key.getTooltip()); - revalidate(); - - /*dockButton.setVisible(key.isAutoHideEnabled()); - - this.autoHide = key.getLocation() == DockableState.HIDDEN; - - setDockButtonAsAutoHide(autoHide); - - closeButton.setVisible(key.isCloseEnabled()); - maximizeButton.setVisible(key.isMaximizeEnabled() && !autoHide);*/ - - key.addPropertyChangeListener(dockKeyListener); - - } - firePropertyChange("dockable", old, dockable); - } - - /** {@inheritDoc} */ - public Dockable getDockable() { - return target; - } - - /** {@inheritDoc} */ - public boolean startDragComponent(Point p) { - // disable DnD for some cases : - // - child of a compound dockable, in hidden state - // - child of a maximized compund dockable - // - maximized dockable - DockableState.Location targetLocation = target.getDockKey().getLocation(); - if(targetLocation == DockableState.Location.HIDDEN) { - if(DockingUtilities.isChildOfCompoundDockable(target)) { - // nested hidden dockables cannot be drag and dropped - return false; - } - } else if(targetLocation == DockableState.Location.DOCKED) { - boolean isChildOfMaximizedContainer = false; - if(desktop != null) { - Dockable max = desktop.getMaximizedDockable(); - if(max != null && max.getComponent().getParent().isAncestorOf(this)) { - isChildOfMaximizedContainer = true; - } - } - if(isChildOfMaximizedContainer) { - return false; - } - } else if(targetLocation == DockableState.Location.MAXIMIZED) { - return false; - } - - // notify our listeners that drag has begun - firePropertyChange(PROPERTY_DRAGGED, false, true); - return true; - } - - /** Returns a readeable String representing this title bar */ - public String toString() { - if(target != null) { - return "DockViewTitleBar of [" + target.getDockKey() + "]"; - } else { - return "DockViewTitleBar"; - } - } - - private JMenuItem createPopUpItem(String text, Icon icon, String tooltip, String actionCommand, KeyStroke accelerator) { - JMenuItem menuItem = new JMenuItem(text, icon); - menuItem.setActionCommand(actionCommand); - menuItem.addActionListener(actionListener); - if(accelerator != null) { - menuItem.setAccelerator(accelerator); - } - return menuItem; - } - - private void checkForPopUp(MouseEvent e) { - JPopupMenu popup = new JPopupMenu(target.getDockKey().getName()); - // first add the standard menu - DockKey key = target.getDockKey(); - switch(key.getLocation()) { - case DOCKED: - initDockedPopUp(popup); - break; - case HIDDEN: - initAutoHidePopUp(popup); - break; - case MAXIMIZED: - initMaximizedPopUp(popup); - break; - case FLOATING: - initFloatingPopUp(popup); - break; - default: - // nothing to do - } - - DockableActionCustomizer customizer = target.getDockKey().getActionCustomizer(); - if(customizer != null && customizer.isSingleDockableTitleBarPopUpCustomizer()) { - if(popup.getComponentCount() > 0) { - popup.addSeparator(); - } - customizer.visitSingleDockableTitleBarPopUp(popup, target); - } - if(popup.getComponentCount() > 0) { - popup.show(DockViewTitleBar.this, e.getX(), e.getY()); - this.currentPopUp = popup; - } - } - - /** Init the popup displayed as the title bar contextual menu */ - protected void initMaximizedPopUp(JPopupMenu popup) { - - popup.add(createPopUpItem(RESTORE_TEXT, restoreIcon, RESTORE_TEXT, "maximize", (KeyStroke) UIManager.get("DockingDesktop.maximizeActionAccelerator"))); - } - - protected void initAutoHidePopUp(JPopupMenu popup) { - if(DockingUtilities.isChildOfCompoundDockable(target)) { - // restore option not allowed for children of a compound dockable - } else { - popup.add(createPopUpItem(RESTORE_TEXT, dockIcon, RESTORE_TEXT, "dock", (KeyStroke) UIManager.get("DockingDesktop.dockActionAccelerator"))); - } - if(target.getDockKey().isCloseEnabled()) { - popup.add(createPopUpItem(CLOSE_TEXT, closeIcon, CLOSE_TEXT, "close", (KeyStroke) UIManager.get("DockingDesktop.closeActionAccelerator"))); - } - } - - protected void initDockedPopUp(JPopupMenu popup) { - DockKey key = target.getDockKey(); - if(key.isAutoHideEnabled()) { - popup.add(createPopUpItem(ICONIFY_TEXT, hideIcon, ICONIFY_TEXT, "dock", (KeyStroke) UIManager.get("DockingDesktop.dockActionAccelerator"))); - } - if(key.isFloatEnabled()) { - popup.add(createPopUpItem(FLOAT_TEXT, floatIcon, FLOAT_TEXT, "float", (KeyStroke) UIManager.get("DockingDesktop.floatActionAccelerator"))); - } - if(key.isMaximizeEnabled()) { - popup.add(createPopUpItem(MAXIMIZE_TEXT, maximizeIcon, MAXIMIZE_TEXT, "maximize", (KeyStroke) UIManager.get("DockingDesktop.maximizeActionAccelerator"))); - } - if(key.isCloseEnabled()) { - popup.add(createPopUpItem(CLOSE_TEXT, closeIcon, CLOSE_TEXT, "close", (KeyStroke) UIManager.get("DockingDesktop.closeActionAccelerator"))); - } - } - - /** Init the popup displayed as the title bar contextual menu */ - protected void initFloatingPopUp(JPopupMenu popup) { - if(DockingUtilities.isChildOfCompoundDockable(target)) { - // attach option not allowed for children of a compound dockable - } else { - popup.add(createPopUpItem(ATTACH_TEXT, attachIcon, ATTACH_TEXT, "float", (KeyStroke) UIManager.get("DockingDesktop.floatActionAccelerator"))); - } - } - - /** Returns the container of the dockable's component */ - public Container getDockableContainer() { - // easy - return getParent(); - } - - public void installDocking(DockingDesktop desktop) { - this.desktop = desktop; - } - - public void uninstallDocking(DockingDesktop desktop) { - //System.out.println("uninstallDocking TITLE on " + target.getDockKey()); - this.desktop = null; - setUI(null); //2007/11/14 - } - - /** notifies the source when the drag operation has ended (by a drop or cancelled) - * @since 2.1.3 - */ - public void endDragComponent(boolean dropped) { - // nothing more to do - } - - /* used to highlight the title bar when its parent is ancestor of the - * focused component - */ - private static class FocusHighlighter implements PropertyChangeListener { - - private DockViewTitleBar activeTitleBar; - - FocusHighlighter() { - // this is a singleton so we register here for keyboard focus events properties - KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener("focusOwner", this); - } - - // focusOwner - public void propertyChange(PropertyChangeEvent e) { - Component c = (Component) e.getNewValue(); - Component ancestor = c; - // is the focus contained in a single dockable container - while(ancestor != null && ! (ancestor instanceof SingleDockableContainer)) { - ancestor = ancestor.getParent(); - } - if(ancestor != null) { - if(ancestor instanceof DockView) { - DockView view = (DockView) ancestor; - DockViewTitleBar tb = view.getTitleBar(); - if(tb == activeTitleBar) { - // no view change in focus - } else if(tb != null) { - if(activeTitleBar != null) { - activeTitleBar.setActive(false); - } - tb.setActive(true); - // reset notification (blinking) - Dockable target = tb.target; - if(target != null && target.getDockKey() != null) { //2007/02/27 fixed NPE - target.getDockKey().setNotification(false); - } - } else { // tb == null && tb != activeTitleBar - activeTitleBar.setActive(false); - } - activeTitleBar = tb; - } else if(ancestor instanceof AutoHideExpandPanel) { - DockViewTitleBar tb = ((AutoHideExpandPanel) ancestor).getTitleBar(); - if(tb == activeTitleBar) { - // no view change in focus - } else if(tb != null) { - if(activeTitleBar != null) { - activeTitleBar.setActive(false); - } - tb.setActive(true); - // reset notification (blinking) - tb.target.getDockKey().setNotification(false); - } else { // tb == null && tb != activeTitleBar - activeTitleBar.setActive(false); - } - activeTitleBar = tb; - } else { - if(activeTitleBar != null) { - activeTitleBar.setActive(false); - } - activeTitleBar = null; - } - } else { - if(activeTitleBar != null) { - activeTitleBar.setActive(false); - } - activeTitleBar = null; - } - - /* // try to find if the component taking the focus is the ancestor - if (activeTitleBar != null){ - Container parent = activeTitleBar.getParent(); - if (parent != null && c != null && parent.isAncestorOf(c)) { - if (!active) { - setActive(true); - } - // reset notification (blinking) - target.getDockKey().setNotification(false); - } else if (active) { - setActive(false); - } - */ - } - } + @Override + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("dock")) { + dockAction(); + } + else if (e.getActionCommand().equals("close")) { + closeAction(); + } + else if (e.getActionCommand().equals("maximize")) { + maximizeAction(); + } + else if (e.getActionCommand().equals("float")) { + floatAction(); + } + } + }; + + private Dockable target; // the component this title is for + + /** used to have a state of blinking (notification) */ + private boolean isNotification = false; + + private JPopupMenu currentPopUp = null; + + /** current blinking count (the limit to the notification timer) */ + private int blinkCount = 0; + + private int MAX_BLINKS = UIManager.getInt("DockingDesktop.notificationBlinkCount"); + + /** reacts to single and double click on title bar */ + private MouseListener titleMouseListener = new MouseAdapter() { + + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + maximizeAction(); + } + else { + // requestFocus(); + // replaced by a better focus behaviour for 1.2 + target.getComponent().requestFocus(); + } + } + + @Override + public void mousePressed(MouseEvent e) { + if (e.isPopupTrigger()) { + checkForPopUp(e); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (e.isPopupTrigger()) { + checkForPopUp(e); + } + } + + }; + + /** listen to the key changes */ + private PropertyChangeListener dockKeyListener = new PropertyChangeListener() { + + @Override + public void propertyChange(PropertyChangeEvent e) { + String pName = e.getPropertyName(); + if (pName.equals(DockKey.PROPERTY_ICON)) { + titleLabel.setIcon((Icon) e.getNewValue()); + } + else if (pName.equals(DockKey.PROPERTY_NAME)) { + titleLabel.setText((String) e.getNewValue()); + revalidate(); + } + else if (pName.equals(DockKey.PROPERTY_TOOLTIP)) { + setToolTipText((String) e.getNewValue()); + } + else if (pName.equals(DockKey.PROPERTY_NOTIFICATION)) { + // attract user attention + boolean isOn = ((Boolean) e.getNewValue()).booleanValue(); + if (isOn && !isActive()) { + if (notificationTimer == null) { + notificationTimer = new javax.swing.Timer(1000, new ActionListener() { + + @Override + public void actionPerformed(ActionEvent actionEvent) { + setNotification(!isNotification); + if (!isNotification) { + blinkCount++; + if (blinkCount >= MAX_BLINKS) { + blinkCount = 0; + notificationTimer.stop(); // enough blinking + + if (target != null) { + DockKey k = target.getDockKey(); + if (k != null) { + k.setNotification(false); + } + } + } + } + } + }); + } + notificationTimer.restart(); + } + else { + if (notificationTimer != null) { + notificationTimer.stop(); + blinkCount = 0; + } + setNotification(false); + } + } + /* + * else if (pName.equals(DockKey.PROPERTY_DOCKABLE_STATE)){ + * + * int newState = ((Integer)e.getNewValue()).intValue(); int oldState = + * ((Integer)e.getOldValue()).intValue(); switch (newState){ case DockableState.HIDDEN : setAutoHide(true); + * break; case DockableState.MAXIMIZED : setMaximized(true); break; case DockableState.DOCKED : if (oldState + * == DockableState.HIDDEN){ setAutoHide(false); } break; default: //@todo : managed the floatable state // + * nothing to track here } } + */ + } + }; + + /** Timer used to trigger repaint event for notification (blinking title bar) */ + private javax.swing.Timer notificationTimer; + + /** singleton for keyboard management */ + @SuppressWarnings("unused") + private static FocusHighlighter focusHighlighter = new FocusHighlighter(); // 2005/11/10 + + private DockingDesktop desktop; + + /** + * Constructs an empty title bar (no dockable yet associated). + */ + public DockViewTitleBar() { + this(null); + } + + /** + * Constructs a title bar for the specified dockable. + *

+ * Warning : a DockViewTitleBar can be used with multiple dockables (this is the case for example in autohide + * borders, where a single titlebar is shared by all hidden dockables (shown only when one is expanding). + */ + public DockViewTitleBar(Dockable dockable) { + setDockable(dockable); + + closeButton.setText(""); + dockButton.setText(""); + maximizeButton.setText(""); + closeButton.setActionCommand("close"); + dockButton.setActionCommand("dock"); + maximizeButton.setActionCommand("maximize"); + floatButton.setActionCommand("float"); + + closeButton.addActionListener(actionListener); + dockButton.addActionListener(actionListener); + maximizeButton.addActionListener(actionListener); + floatButton.addActionListener(actionListener); + + this.addMouseListener(titleMouseListener); + + // maximizeButton.addMouseListener(new ButtonRolloverEffect()); + + } + + /** + * Notification of completion of layout. + *

+ * This hook can be used to insert customized buttons without otherwise having to fully replace the UI delegate + */ + public void finishLayout() { + + } + + /** + * Returns the desktop associated to this title bar, if one has been registered with + * #installDocking(DockingDesktop), or null. + */ + public DockingDesktop getDesktop() { + return desktop; + } + + /** Overriden as a means to unregister internal listeners, do not call directly */ + @Override + public void removeNotify() { + super.removeNotify(); + /* + * KeyboardFocusManager.getCurrentKeyboardFocusManager().removePropertyChangeListener( focusHighlighter); + * 2005/11/10: now it's shared between title bars + */ + if (target != null) { + DockKey k = target.getDockKey(); + if (k != null) { + k.removePropertyChangeListener(dockKeyListener);// 2007/04/16 + } + } + + } + + /** + * Returns the label used to display the dockkey name. + *

+ * Shouldn't be used to update the title : the best way is to update the DockKey (property listener ensure the + * labels and buttons stay in sync). + * + * @since 2.0 + */ + public JLabel getTitleLabel() { + if (titleLabel == null) { + titleLabel = new JLabel(); + } + return titleLabel; + } + + public JButton getCloseButton() { + if (closeButton == null) { + closeButton = new JButton(); + } + return closeButton; + } + + /** + * returns the button used for hiding or docking the view. + *

+ * As hiding and docking are mutually exclusive, the same button is used for both purposes + */ + public JButton getHideOrDockButton() { + if (dockButton == null) { + dockButton = new JButton(); + } + return dockButton; + } + + /** + * returns the button used for maximizing or restoring the view. + *

+ * As those operations are mutually exclusive, the same button is used for both purposes + */ + public JButton getMaximizeOrRestoreButton() { + if (maximizeButton == null) { + maximizeButton = new JButton(); + } + return maximizeButton; + } + + /** + * returns the button used for floating (detach) the view. + */ + public JButton getFloatButton() { + if (floatButton == null) { + floatButton = new JButton(); + } + return floatButton; + } + + private void setNotification(boolean notification) { + boolean old = this.isNotification; + this.isNotification = notification; + firePropertyChange("titlebar.notification", old, notification); + } + + private boolean isAutoHide() { + return target.getDockKey().getLocation() == DockableState.Location.HIDDEN; + } + + private boolean isMaximized() { + return target.getDockKey().getLocation() == DockableState.Location.MAXIMIZED; + } + + private void dockAction() { + boolean old = isAutoHide(); + firePropertyChange(PROPERTY_AUTOHIDE, old, !old); + } + + private void maximizeAction() { + // we use the visible property of the button to check if maximization + // is enabled or not + if (target.getDockKey().getLocation() == DockableState.Location.FLOATING) { + // ignore : floating cannot be maximized + return; + } + if (target.getDockKey().isMaximizeEnabled()) { + // if (maximizeButton.isVisible()){ //2008/04/17 + boolean old = isMaximized(); + // we don't really change the maximized property, + // which will be updated if the desktop accepts this maximization + /* + * We do not use fireVetoableChange, to keep maximization processing outside the title bar (everything is + * driven par the dockingdesktop and the SingleDockableContainer + */ + firePropertyChange(PROPERTY_MAXIMIZED, old, !old); + } + } + + private void closeAction() { + firePropertyChange(PROPERTY_CLOSED, false, true); + } + + private void floatAction() { + if (target.getDockKey().getLocation() == DockableState.Location.FLOATING) { + firePropertyChange(PROPERTY_FLOAT, true, false); + } + else { + firePropertyChange(PROPERTY_FLOAT, false, true); + } + } + + /** + * This method is invoked to hide the pop-up that could still be visible (To avoid a visible pop-up for an invisible + * component) + */ + public void closePopUp() { + if (currentPopUp != null) { + currentPopUp.setVisible(false); + // @todo uncertain about correct gc of the pop-up in that case + currentPopUp = null; + } + } + + /** + * {@inheritDoc} + * + * @since 2.0 + */ + @Override + public String getUIClassID() { + return uiClassID; + } + + /** + * Returns true if the dockable is the currently active one. + *

+ * There is at most one active dockable for a dekstop, and it there is one, it is the one which contains the keybord + * focused component. + */ + public boolean isActive() { + return active; + } + + /** + * Updates the active property. A title bar is active when the dockable it is for is ancestor of the keybord focused + * component. + */ + public void setActive(boolean active) { + boolean old = this.active; + this.active = active; + firePropertyChange("active", old, active); + } + + /** Changes the dockable this title bar is for */ + public void setDockable(Dockable dockable) { + Dockable old = target; + if (target != null && target != dockable) { + target.getDockKey().removePropertyChangeListener(dockKeyListener); + } + if (dockable != null) { + this.target = dockable; + + DockKey key = dockable.getDockKey(); + titleLabel.setText(key.getName()); + titleLabel.setIcon(key.getIcon()); + setToolTipText(key.getTooltip()); + revalidate(); + + /* + * dockButton.setVisible(key.isAutoHideEnabled()); + * + * this.autoHide = key.getLocation() == DockableState.HIDDEN; + * + * setDockButtonAsAutoHide(autoHide); + * + * closeButton.setVisible(key.isCloseEnabled()); maximizeButton.setVisible(key.isMaximizeEnabled() && + * !autoHide); + */ + + key.addPropertyChangeListener(dockKeyListener); + + } + firePropertyChange("dockable", old, dockable); + } + + /** {@inheritDoc} */ + @Override + public Dockable getDockable() { + return target; + } + + /** {@inheritDoc} */ + @Override + public boolean startDragComponent(Point p) { + // disable DnD for some cases : + // - child of a compound dockable, in hidden state + // - child of a maximized compund dockable + // - maximized dockable + DockableState.Location targetLocation = target.getDockKey().getLocation(); + if (targetLocation == DockableState.Location.HIDDEN) { + if (DockingUtilities.isChildOfCompoundDockable(target)) { + // nested hidden dockables cannot be drag and dropped + return false; + } + } + else if (targetLocation == DockableState.Location.DOCKED) { + boolean isChildOfMaximizedContainer = false; + if (desktop != null) { + Dockable max = desktop.getMaximizedDockable(); + if (max != null && max.getComponent().getParent().isAncestorOf(this)) { + isChildOfMaximizedContainer = true; + } + } + if (isChildOfMaximizedContainer) { + return false; + } + } + else if (targetLocation == DockableState.Location.MAXIMIZED) { + return false; + } + + // notify our listeners that drag has begun + firePropertyChange(PROPERTY_DRAGGED, false, true); + return true; + } + + /** Returns a readeable String representing this title bar */ + @Override + public String toString() { + if (target != null) { + return "DockViewTitleBar of [" + target.getDockKey() + "]"; + } + else { + return "DockViewTitleBar"; + } + } + + private JMenuItem createPopUpItem( + String text, Icon icon, String tooltip, String actionCommand, KeyStroke accelerator) { + JMenuItem menuItem = new JMenuItem(text, icon); + menuItem.setActionCommand(actionCommand); + menuItem.addActionListener(actionListener); + if (accelerator != null) { + menuItem.setAccelerator(accelerator); + } + return menuItem; + } + + private void checkForPopUp(MouseEvent e) { + JPopupMenu popup = new JPopupMenu(target.getDockKey().getName()); + // first add the standard menu + DockKey key = target.getDockKey(); + switch (key.getLocation()) { + case DOCKED: + initDockedPopUp(popup); + break; + case HIDDEN: + initAutoHidePopUp(popup); + break; + case MAXIMIZED: + initMaximizedPopUp(popup); + break; + case FLOATING: + initFloatingPopUp(popup); + break; + default: + // nothing to do + } + + DockableActionCustomizer customizer = target.getDockKey().getActionCustomizer(); + if (customizer != null && customizer.isSingleDockableTitleBarPopUpCustomizer()) { + if (popup.getComponentCount() > 0) { + popup.addSeparator(); + } + customizer.visitSingleDockableTitleBarPopUp(popup, target); + } + if (popup.getComponentCount() > 0) { + popup.show(DockViewTitleBar.this, e.getX(), e.getY()); + this.currentPopUp = popup; + } + } + + /** Init the popup displayed as the title bar contextual menu */ + protected void initMaximizedPopUp(JPopupMenu popup) { + + popup + .add(createPopUpItem(RESTORE_TEXT, restoreIcon, RESTORE_TEXT, "maximize", + (KeyStroke) UIManager.get("DockingDesktop.maximizeActionAccelerator"))); + } + + protected void initAutoHidePopUp(JPopupMenu popup) { + if (DockingUtilities.isChildOfCompoundDockable(target)) { + // restore option not allowed for children of a compound dockable + } + else { + popup + .add(createPopUpItem(RESTORE_TEXT, dockIcon, RESTORE_TEXT, "dock", + (KeyStroke) UIManager.get("DockingDesktop.dockActionAccelerator"))); + } + if (target.getDockKey().isCloseEnabled()) { + popup + .add(createPopUpItem(CLOSE_TEXT, closeIcon, CLOSE_TEXT, "close", + (KeyStroke) UIManager.get("DockingDesktop.closeActionAccelerator"))); + } + } + + protected void initDockedPopUp(JPopupMenu popup) { + DockKey key = target.getDockKey(); + if (key.isAutoHideEnabled()) { + popup + .add(createPopUpItem(ICONIFY_TEXT, hideIcon, ICONIFY_TEXT, "dock", + (KeyStroke) UIManager.get("DockingDesktop.dockActionAccelerator"))); + } + if (key.isFloatEnabled()) { + popup + .add(createPopUpItem(FLOAT_TEXT, floatIcon, FLOAT_TEXT, "float", + (KeyStroke) UIManager.get("DockingDesktop.floatActionAccelerator"))); + } + if (key.isMaximizeEnabled()) { + popup + .add(createPopUpItem(MAXIMIZE_TEXT, maximizeIcon, MAXIMIZE_TEXT, "maximize", + (KeyStroke) UIManager.get("DockingDesktop.maximizeActionAccelerator"))); + } + if (key.isCloseEnabled()) { + popup + .add(createPopUpItem(CLOSE_TEXT, closeIcon, CLOSE_TEXT, "close", + (KeyStroke) UIManager.get("DockingDesktop.closeActionAccelerator"))); + } + } + + /** Init the popup displayed as the title bar contextual menu */ + protected void initFloatingPopUp(JPopupMenu popup) { + if (DockingUtilities.isChildOfCompoundDockable(target)) { + // attach option not allowed for children of a compound dockable + } + else { + popup + .add(createPopUpItem(ATTACH_TEXT, attachIcon, ATTACH_TEXT, "float", + (KeyStroke) UIManager.get("DockingDesktop.floatActionAccelerator"))); + } + } + + /** Returns the container of the dockable's component */ + @Override + public Container getDockableContainer() { + // easy + return getParent(); + } + + public void installDocking(DockingDesktop desktop) { + this.desktop = desktop; + } + + public void uninstallDocking(DockingDesktop desktop) { + // System.out.println("uninstallDocking TITLE on " + target.getDockKey()); + this.desktop = null; + setUI(null); // 2007/11/14 + } + + /** + * notifies the source when the drag operation has ended (by a drop or cancelled) + * + * @since 2.1.3 + */ + @Override + public void endDragComponent(boolean dropped) { + // nothing more to do + } + + /* + * used to highlight the title bar when its parent is ancestor of the focused component + */ + private static class FocusHighlighter implements PropertyChangeListener { + + private DockViewTitleBar activeTitleBar; + + FocusHighlighter() { + // this is a singleton so we register here for keyboard focus events properties + KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener("focusOwner", this); + } + + // focusOwner + @Override + public void propertyChange(PropertyChangeEvent e) { + Component c = (Component) e.getNewValue(); + Component ancestor = c; + // is the focus contained in a single dockable container + while (ancestor != null && !(ancestor instanceof SingleDockableContainer)) { + ancestor = ancestor.getParent(); + } + if (ancestor != null) { + if (ancestor instanceof DockView) { + DockView view = (DockView) ancestor; + DockViewTitleBar tb = view.getTitleBar(); + if (tb == activeTitleBar) { + // no view change in focus + } + else if (tb != null) { + if (activeTitleBar != null) { + activeTitleBar.setActive(false); + } + tb.setActive(true); + // reset notification (blinking) + Dockable target = tb.target; + if (target != null && target.getDockKey() != null) { // 2007/02/27 fixed NPE + target.getDockKey().setNotification(false); + } + } + else { // tb == null && tb != activeTitleBar + activeTitleBar.setActive(false); + } + activeTitleBar = tb; + } + else if (ancestor instanceof AutoHideExpandPanel) { + DockViewTitleBar tb = ((AutoHideExpandPanel) ancestor).getTitleBar(); + if (tb == activeTitleBar) { + // no view change in focus + } + else if (tb != null) { + if (activeTitleBar != null) { + activeTitleBar.setActive(false); + } + tb.setActive(true); + // reset notification (blinking) + tb.target.getDockKey().setNotification(false); + } + else { // tb == null && tb != activeTitleBar + activeTitleBar.setActive(false); + } + activeTitleBar = tb; + } + else { + if (activeTitleBar != null) { + activeTitleBar.setActive(false); + } + activeTitleBar = null; + } + } + else { + if (activeTitleBar != null) { + activeTitleBar.setActive(false); + } + activeTitleBar = null; + } + + /* + * // try to find if the component taking the focus is the ancestor if (activeTitleBar != null){ Container + * parent = activeTitleBar.getParent(); if (parent != null && c != null && parent.isAncestorOf(c)) { if + * (!active) { setActive(true); } // reset notification (blinking) + * target.getDockKey().setNotification(false); } else if (active) { setActive(false); } + */ + } + } } diff --git a/src/main/java/com/vlsolutions/swing/docking/ui/DockViewTitleBarUI.java b/src/main/java/com/vlsolutions/swing/docking/ui/DockViewTitleBarUI.java index 3b80d9e..1e5895e 100644 --- a/src/main/java/com/vlsolutions/swing/docking/ui/DockViewTitleBarUI.java +++ b/src/main/java/com/vlsolutions/swing/docking/ui/DockViewTitleBarUI.java @@ -51,7 +51,8 @@ import javax.swing.plaf.ComponentUI; import javax.swing.plaf.PanelUI; -/** A UI for the {@link com.vlsolutions.swing.docking.DockViewTitleBar}. +/** + * A UI for the {@link com.vlsolutions.swing.docking.DockViewTitleBar}. * * @author Lilian Chamontin, VLSolutions * @author Andrew Carlson, HypnoRabbit Studios @@ -61,536 +62,621 @@ */ public class DockViewTitleBarUI extends PanelUI implements PropertyChangeListener { - /* hack to use custom painting except on mac os (ugly opacity effects) */ - private static boolean useCustomPaint = !System.getProperty("os.name").contains("OS X") - && !UIManager.getBoolean("DockViewTitleBar.disableCustomPaint"); - - private static Color panelColor = UIManager.getColor("Panel.background"); - @SuppressWarnings("unused") - private static Color highlight = UIManager.getColor("VLDocking.highlight"); - @SuppressWarnings("unused") - private static Color shadow = UIManager.getColor("VLDocking.shadow"); - - private static Icon closeIcon = UIManager.getIcon("DockViewTitleBar.close"); - private static Icon closeIconRollover = UIManager.getIcon("DockViewTitleBar.close.rollover"); - private static Icon closeIconPressed = UIManager.getIcon("DockViewTitleBar.close.pressed"); - - private static Icon maximizeIcon = UIManager.getIcon("DockViewTitleBar.maximize"); - private static Icon maximizeIconRollover = UIManager.getIcon("DockViewTitleBar.maximize.rollover"); - private static Icon maximizeIconPressed = UIManager.getIcon("DockViewTitleBar.maximize.pressed"); - - private static Icon restoreIcon = UIManager.getIcon("DockViewTitleBar.restore"); - private static Icon restoreIconRollover = UIManager.getIcon("DockViewTitleBar.restore.rollover"); - private static Icon restoreIconPressed = UIManager.getIcon("DockViewTitleBar.restore.pressed"); - - private static Icon hideIcon = UIManager.getIcon("DockViewTitleBar.hide"); - private static Icon hideIconRollover = UIManager.getIcon("DockViewTitleBar.hide.rollover"); - private static Icon hideIconPressed = UIManager.getIcon("DockViewTitleBar.hide.pressed"); - - private static Icon dockIcon = UIManager.getIcon("DockViewTitleBar.dock"); - private static Icon dockIconRollover = UIManager.getIcon("DockViewTitleBar.dock.rollover"); - private static Icon dockIconPressed = UIManager.getIcon("DockViewTitleBar.dock.pressed"); - - private static Icon floatIcon = UIManager.getIcon("DockViewTitleBar.float"); - private static Icon floatIconRollover = UIManager.getIcon("DockViewTitleBar.float.rollover"); - private static Icon floatIconPressed = UIManager.getIcon("DockViewTitleBar.float.pressed"); - - private static Icon attachIcon = UIManager.getIcon("DockViewTitleBar.attach"); - private static Icon attachIconRollover = UIManager.getIcon("DockViewTitleBar.attach.rollover"); - private static Icon attachIconPressed = UIManager.getIcon("DockViewTitleBar.attach.pressed"); - - // label resources taken from BasicInternalFrameUI... - private static final String CLOSE_TEXT = UIManager.getString("DockViewTitleBar.closeButtonText"); - private static final String ICONIFY_TEXT = UIManager.getString("DockViewTitleBar.minimizeButtonText"); - private static final String RESTORE_TEXT = UIManager.getString("DockViewTitleBar.restoreButtonText"); - private static final String MAXIMIZE_TEXT = UIManager.getString("DockViewTitleBar.maximizeButtonText"); - private static final String FLOAT_TEXT = UIManager.getString("DockViewTitleBar.floatButtonText"); - private static final String ATTACH_TEXT = UIManager.getString("DockViewTitleBar.attachButtonText"); - - private static Color selectedTitleColor = UIManager.getColor("InternalFrame.activeTitleBackground"); - private static Color selectedTextColor = UIManager.getColor("InternalFrame.activeTitleForeground"); - private static Color notSelectedTitleColor = UIManager.getColor("InternalFrame.inactiveTitleBackground"); - private static Color notSelectedTextColor = UIManager.getColor("InternalFrame.inactiveTitleForeground"); - - // flags to hide/show buttons in the title bar (they are always visible in the contextual menu, but might - // take too much space on the titles (for example a minimum set could be hide/float/close - // as maximize is accessed by double click) - private boolean isCloseButtonDisplayed = UIManager.getBoolean("DockViewTitleBar.isCloseButtonDisplayed"); - private boolean isHideButtonDisplayed = UIManager.getBoolean("DockViewTitleBar.isHideButtonDisplayed"); - private boolean isDockButtonDisplayed = UIManager.getBoolean("DockViewTitleBar.isDockButtonDisplayed"); - private boolean isMaximizeButtonDisplayed = UIManager.getBoolean("DockViewTitleBar.isMaximizeButtonDisplayed"); - private boolean isRestoreButtonDisplayed = UIManager.getBoolean("DockViewTitleBar.isRestoreButtonDisplayed"); - private boolean isFloatButtonDisplayed = UIManager.getBoolean("DockViewTitleBar.isFloatButtonDisplayed"); - private boolean isAttachButtonDisplayed = UIManager.getBoolean("DockViewTitleBar.isAttachButtonDisplayed"); - - protected DockViewTitleBar titleBar; - - /* This ancestor listener is required as buttons may change according to container hierarchy. - * The first example is when a dockable is added to a floating + compund dockable, the attach - * button (which usually becomes visible as the dockable is in the floating state) has to be hidden - * (tech choice : we don't want to allow attaching a single child of a compound dockable) - * - */ - private AncestorListener ancestorListener = new AncestorListener() { - - public void ancestorAdded(AncestorEvent event) { - configureButtons(titleBar); - } - - public void ancestorMoved(AncestorEvent event) {} - - public void ancestorRemoved(AncestorEvent event) {} - }; - - public DockViewTitleBarUI(DockViewTitleBar tb) { - this.titleBar = tb; - } - - public static ComponentUI createUI(JComponent c) { - return new DockViewTitleBarUI((DockViewTitleBar) c); - } - - public void installUI(JComponent c) { - super.installUI(c); - installTitleBorder(c); - - BoxLayout box = new BoxLayout(titleBar, BoxLayout.X_AXIS); - titleBar.setLayout(box); - titleBar.setOpaque(true); - - titleBar.addPropertyChangeListener(this); - titleBar.addAncestorListener(ancestorListener); - installButtons(); - installLabel(); - Dockable d = titleBar.getDockable(); - if(d != null) { - titleBar.getDockable().getDockKey().addPropertyChangeListener(this); - configureButtons(titleBar); - } - c.setCursor(Cursor.getDefaultCursor()); // needs this to avoid artifacts with floating dockable borders. - - } - - protected void layoutTitleBar() { - titleBar.removeAll(); - - titleBar.add(Box.createHorizontalStrut(4)); // keep the label not to close to the border - - titleBar.add(titleBar.getTitleLabel()); - titleBar.add(Box.createHorizontalGlue()); - int height = UIManager.getInt("DockViewTitleBar.height"); - if(height > 0) { - titleBar.add(Box.createRigidArea(new Dimension(4, height))); // fixed height - } - titleBar.add(titleBar.getHideOrDockButton()); - titleBar.add(titleBar.getFloatButton()); - titleBar.add(titleBar.getMaximizeOrRestoreButton()); - titleBar.add(titleBar.getCloseButton()); - // simple layout hook for extenders - titleBar.finishLayout(); - - titleBar.revalidate(); - - } - - public void uninstallUI(JComponent c) { - super.uninstallUI(c); - uninstallTitleBorder(c); - titleBar.removePropertyChangeListener(this); - titleBar.removeAncestorListener(ancestorListener); - Dockable d = titleBar.getDockable(); - if(d != null) { - d.getDockKey().removePropertyChangeListener(this); - } - } - - /** Installs default on the titlebar label */ - protected void installLabel() { - JLabel titleLabel = titleBar.getTitleLabel(); - Font f = UIManager.getFont("DockViewTitleBar.titleFont"); - titleLabel.setFont(f); - titleLabel.setForeground(notSelectedTextColor); - titleBar.setBackground(notSelectedTitleColor); - - // adjust minimum size because of BoxLayout usage (doesn't allow resize under minimum size) - //titleLabel.setMinimumSize(new Dimension(30, titleLabel.getPreferredSize().height)); - } - - /** Installs the default background of the title bar */ - protected void installBackground() { - titleBar.setBackground(notSelectedTitleColor); - } - - /** configure the title bar buttons */ - protected void installButtons() { - JButton closeButton = titleBar.getCloseButton(); - JButton dockButton = titleBar.getHideOrDockButton(); - JButton maximizeButton = titleBar.getMaximizeOrRestoreButton(); - JButton floatButton = titleBar.getFloatButton(); - - // temporary workaround for nimbus look and feel : nimbus adds a large border - // around buttons which doesn't follow other LAF behaviour. - Border emptyBorder = BorderFactory.createEmptyBorder(0, 2, 0, 2); - closeButton.setBorder(emptyBorder); - dockButton.setBorder(emptyBorder); - maximizeButton.setBorder(emptyBorder); - floatButton.setBorder(emptyBorder); - - //Insets buttonMargin = new Insets(3, 5, 3, 5); - Insets buttonMargin = new Insets(0, 2, 0, 2); - closeButton.setMargin(buttonMargin); - dockButton.setMargin(buttonMargin); - maximizeButton.setMargin(buttonMargin); - floatButton.setMargin(buttonMargin); - - dockButton.setRolloverEnabled(true); - dockButton.setBorderPainted(false); - dockButton.setFocusable(false); - dockButton.setContentAreaFilled(false); - - closeButton.setRolloverEnabled(true); - closeButton.setBorderPainted(false); - closeButton.setFocusable(false); - closeButton.setContentAreaFilled(false); - - maximizeButton.setRolloverEnabled(true); - maximizeButton.setBorderPainted(false); - maximizeButton.setFocusable(false); - maximizeButton.setContentAreaFilled(false); - - floatButton.setRolloverEnabled(true); - floatButton.setBorderPainted(false); - floatButton.setFocusable(false); - floatButton.setContentAreaFilled(false); - - } - - /** Listen to property changes in the DockKey or the title bar */ - public void propertyChange(PropertyChangeEvent e) { - String pName = e.getPropertyName(); - //System.out.println("property change " + pName); - if(pName.equals("dockable")) { - Dockable old = (Dockable) e.getOldValue(); - if(old != null) { - old.getDockKey().removePropertyChangeListener(this); - } - Dockable newDockable = ((Dockable) e.getNewValue()); - if(newDockable != null) { - configureButtons(titleBar); - newDockable.getDockKey().addPropertyChangeListener(this); - } - } else if(pName.equals(DockKey.PROPERTY_AUTOHIDEABLE)) { - boolean isAutoHideable = ((Boolean) e.getNewValue()).booleanValue(); - boolean isMaximized = titleBar.getDockable().getDockKey().getLocation() == DockableState.Location.MAXIMIZED; - if(isAutoHideable) { - if(! isMaximized) { - titleBar.getHideOrDockButton().setVisible(true); - configureHideButton(titleBar.getHideOrDockButton()); - } - } else { - titleBar.getHideOrDockButton().setVisible(false); - } - } else if(pName.equals(DockKey.PROPERTY_MAXIMIZABLE)) { - boolean isMaximizeable = ((Boolean) e.getNewValue()).booleanValue(); - boolean isHidden = titleBar.getDockable().getDockKey().getLocation() == DockableState.Location.HIDDEN; - if(isMaximizeable) { - if(! isHidden) { - titleBar.getMaximizeOrRestoreButton().setVisible(true); - configureMaximizeButton(titleBar.getMaximizeOrRestoreButton()); - } - } else { - titleBar.getMaximizeOrRestoreButton().setVisible(false); - } - } else if(pName.equals(DockKey.PROPERTY_CLOSEABLE)) { - boolean isCloseable = ((Boolean) e.getNewValue()).booleanValue(); - boolean isMaximized = titleBar.getDockable().getDockKey().getLocation() == DockableState.Location.MAXIMIZED; - if(isCloseable) { - if(! isMaximized) { - titleBar.getCloseButton().setVisible(true); - configureCloseButton(titleBar.getCloseButton()); - } - } else { - titleBar.getCloseButton().setVisible(false); - } - } else if(pName.equals(DockKey.PROPERTY_FLOATABLE)) { - boolean isFloatable = ((Boolean) e.getNewValue()).booleanValue(); - boolean isMaximized = titleBar.getDockable().getDockKey().getLocation() == DockableState.Location.MAXIMIZED; - if(isFloatable) { - if(! isMaximized) { - titleBar.getFloatButton().setVisible(true); - configureFloatButton(titleBar.getFloatButton()); - } - } else { - titleBar.getFloatButton().setVisible(false); - } - } else if(pName.equals(DockKey.PROPERTY_DOCKABLE_STATE)) { - configureButtons(titleBar); - } else if(pName.equals("active")) { - boolean isActive = ((Boolean) e.getNewValue()).booleanValue(); - if(isActive) { - titleBar.getTitleLabel().setForeground(selectedTextColor); - titleBar.setBackground(selectedTitleColor); - } else { - titleBar.getTitleLabel().setForeground(notSelectedTextColor); - titleBar.setBackground(notSelectedTitleColor); - } - titleBar.repaint(); - } else if(pName.equals("titlebar.notification")) { - boolean notification = ((Boolean) e.getNewValue()).booleanValue(); - if(notification) { - titleBar.setBackground(UIManager.getColor("DockingDesktop.notificationColor")); - titleBar.setOpaque(true); - } else { - if(titleBar.isActive()) { - titleBar.setBackground(selectedTitleColor); - } else { - titleBar.setBackground(notSelectedTitleColor); - } - titleBar.setOpaque(false); - } - titleBar.repaint(); - } else if(pName.equals(DockKey.PROPERTY_NAME)) { - titleBar.repaint(); - } else if(pName.equals("clientProperty.visibleTitleBar")) { // 2006/12/01 - boolean v = Boolean.TRUE.equals(e.getNewValue()); - titleBar.setVisible(v); - } - } - - /** Update the buttons to track state changes (for example, the maximize button can become "restore" - * when the view is maximized. - */ - protected void configureButtons(DockViewTitleBar tb) { - layoutTitleBar(); - - DockKey key = tb.getDockable().getDockKey(); - DockableState.Location location = key.getLocation(); - JButton closeButton = titleBar.getCloseButton(); - JButton maxBtn = titleBar.getMaximizeOrRestoreButton(); - JButton hideBtn = titleBar.getHideOrDockButton(); - JButton floatBtn = titleBar.getFloatButton(); - - switch(location) { - case DOCKED: - if(key.isCloseEnabled() && isCloseButtonDisplayed) { - closeButton.setVisible(true); - configureCloseButton(closeButton); - } else { - closeButton.setVisible(false); - } - if(key.isMaximizeEnabled() && isMaximizeButtonDisplayed) { - maxBtn.setVisible(true); - configureMaximizeButton(maxBtn); - } else { - maxBtn.setVisible(false); - } - - boolean isChildOfMaximizedContainer = false; - DockingDesktop desk = titleBar.getDesktop(); - if(desk != null) { - Dockable max = desk.getMaximizedDockable(); - if(max != null && max.getComponent().getParent().isAncestorOf(titleBar)) { - isChildOfMaximizedContainer = true; - } - } - - if(key.isAutoHideEnabled() && isHideButtonDisplayed && ! isChildOfMaximizedContainer) { - hideBtn.setVisible(true); - configureHideButton(hideBtn); - } else { - hideBtn.setVisible(false); - } - if(key.isFloatEnabled() && isFloatButtonDisplayed && ! isChildOfMaximizedContainer) { - floatBtn.setVisible(true); - configureFloatButton(floatBtn); - } else { - floatBtn.setVisible(false); - } - - titleBar.revalidate(); - break; - case HIDDEN: - if(key.isCloseEnabled() && isCloseButtonDisplayed) { - closeButton.setVisible(true); - configureCloseButton(closeButton); - } else { - closeButton.setVisible(false); - } - // maximize not allowed when in autohide mode - maxBtn.setVisible(false); - - boolean isChildOfCompound = DockingUtilities.isChildOfCompoundDockable(tb.getDockable()); - - // idem for float - if(key.isFloatEnabled() && isFloatButtonDisplayed && ! isChildOfCompound) { - floatBtn.setVisible(true); - configureFloatButton(floatBtn); - } else { - floatBtn.setVisible(false); - } - // hide becomes dock - if(isChildOfCompound) { - //tb.getParent().getParent() instanceof AutoHideExpandPanel - // V2.1 : only when not nested - hideBtn.setVisible(false); - } else { - if(isDockButtonDisplayed && key.isAutoHideEnabled()) { // 2007/01/08 - hideBtn.setVisible(true); - configureDockButton(hideBtn); - } else { - hideBtn.setVisible(false); - } - } - titleBar.revalidate(); - break; - case MAXIMIZED: - closeButton.setVisible(false); - floatBtn.setVisible(false); - // maxBtn becomes restore - if(isRestoreButtonDisplayed) { - configureRestoreButton(maxBtn); - maxBtn.setVisible(true); - } else { - maxBtn.setVisible(false); - } - hideBtn.setVisible(false); - titleBar.revalidate(); - break; - case FLOATING: - closeButton.setVisible(false); - maxBtn.setVisible(false); - hideBtn.setVisible(false); - if(DockingUtilities.isChildOfCompoundDockable(tb.getDockable())) { - // cannot attach a compound dockable directly - floatBtn.setVisible(false); - } else { - floatBtn.setVisible(isAttachButtonDisplayed); - configureAttachButton(floatBtn); - } - break; - default: - // not interesting - } - - } - - /** installs the icons and tooltip suitable for a close button */ - protected void configureCloseButton(JButton btn) { - btn.setIcon(closeIcon); - btn.setRolloverIcon(closeIconRollover); - btn.setPressedIcon(closeIconPressed); - btn.setRolloverSelectedIcon(closeIconRollover); - btn.setToolTipText(CLOSE_TEXT); - } - - /** installs the icons and tooltip suitable for a maximize button */ - protected void configureMaximizeButton(JButton btn) { - btn.setIcon(maximizeIcon); - btn.setRolloverIcon(maximizeIconRollover); - btn.setPressedIcon(maximizeIconPressed); - btn.setRolloverSelectedIcon(maximizeIconRollover); - btn.setToolTipText(MAXIMIZE_TEXT); - } - - /** installs the icons and tooltip suitable for a restore button. - */ - protected void configureRestoreButton(JButton btn) { - btn.setIcon(restoreIcon); - btn.setRolloverIcon(restoreIconRollover); - btn.setPressedIcon(restoreIconPressed); - btn.setRolloverSelectedIcon(restoreIconRollover); - btn.setToolTipText(RESTORE_TEXT); - } - - /** installs the icons and tooltip suitable for a hide button */ - protected void configureHideButton(JButton btn) { - btn.setIcon(hideIcon); - btn.setRolloverIcon(hideIconRollover); - btn.setPressedIcon(hideIconPressed); - btn.setRolloverSelectedIcon(hideIconRollover); - btn.setToolTipText(ICONIFY_TEXT); - } - - /** installs the icons and tooltip suitable for a dock button */ - protected void configureDockButton(JButton btn) { - btn.setIcon(dockIcon); - btn.setRolloverIcon(dockIconRollover); - btn.setPressedIcon(dockIconPressed); - btn.setRolloverSelectedIcon(dockIconRollover); - btn.setToolTipText(RESTORE_TEXT); - } - - /** installs the icons and tooltip suitable for a float button */ - protected void configureFloatButton(JButton btn) { - btn.setIcon(floatIcon); - btn.setRolloverIcon(floatIconRollover); - btn.setPressedIcon(floatIconPressed); - btn.setRolloverSelectedIcon(floatIconRollover); - btn.setToolTipText(FLOAT_TEXT); - } - - /** installs the icons and tooltip suitable for an attach button */ - protected void configureAttachButton(JButton btn) { - btn.setIcon(attachIcon); - btn.setRolloverIcon(attachIconRollover); - btn.setPressedIcon(attachIconPressed); - btn.setRolloverSelectedIcon(attachIconRollover); - btn.setToolTipText(ATTACH_TEXT); - } - - /** installs the border of the title bar */ - protected void installTitleBorder(JComponent c) { - Border b = UIManager.getBorder("DockViewTitleBar.border"); - c.setBorder(b); - - } - - /** uninstalls the icons and tooltip suitable for a close button */ - protected void uninstallTitleBorder(JComponent c) { - c.setBorder(null); - } - - /** Custom title bar painting : uses a gradient from the background color - * to the control highlight color. - */ - public void paint(Graphics g, JComponent c) { - - DockViewTitleBar tb = (DockViewTitleBar) c; - if(useCustomPaint) { - Graphics2D g2 = (Graphics2D) g.create(); - g2.setColor(panelColor); - g2.fillRect(0, 0, tb.getWidth(), tb.getHeight()); // emptyborder doesn't repaint - - //TODO: Optimize Look and Feel specific color selecting - Insets i = tb.getInsets(); - if(tb.isActive()) { - if(UIManager.getLookAndFeel().getName().contains("Substance")) { - g2.setColor(UIManager.getColor("controlLtHighlight")); //Active substance - } else { - g2.setColor(UIManager.getColor("controlShadow")); //Active other - } - } else { - g2.setColor(UIManager.getColor("Panel.background")); - } - Rectangle r = tb.getTitleLabel().getBounds(); - int w = r.x + r.width; - g2.fillRect(i.left, i.top, w, tb.getHeight() - i.top - i.bottom); - // gradient paint after the label text (to ensure readability) - if(tb.isActive()) { - if(UIManager.getLookAndFeel().getName().contains("Substance")) { - g2.setPaint(new GradientPaint(i.left + w, 0, UIManager.getColor("controlLtHighlight"), tb.getWidth(), 0, UIManager.getColor("controlHighlight"))); //Active substance - } else { - g2.setPaint(new GradientPaint(i.left + w, 0, UIManager.getColor("controlShadow"), tb.getWidth(), 0, UIManager.getColor("controlHighlight"))); //Active other - } - } else { - g2.setPaint(new GradientPaint(i.left + w, 0, UIManager.getColor("Panel.background"), tb.getWidth(), 0, UIManager.getColor("controlHighlight"))); //panelColor)); - } - g2.fillRect(i.left + w, i.top, tb.getWidth() - w - i.left - i.right, tb.getHeight() - i.top - i.bottom); - - g2.dispose(); - } - super.paint(g, c); - - } + /* hack to use custom painting except on mac os (ugly opacity effects) */ + private static boolean useCustomPaint = + !System.getProperty("os.name").contains("OS X") && !UIManager.getBoolean("DockViewTitleBar.disableCustomPaint"); + + private static Color panelColor = UIManager.getColor("Panel.background"); + + @SuppressWarnings("unused") + private static Color highlight = UIManager.getColor("VLDocking.highlight"); + + @SuppressWarnings("unused") + private static Color shadow = UIManager.getColor("VLDocking.shadow"); + + private static Icon closeIcon = UIManager.getIcon("DockViewTitleBar.close"); + + private static Icon closeIconRollover = UIManager.getIcon("DockViewTitleBar.close.rollover"); + + private static Icon closeIconPressed = UIManager.getIcon("DockViewTitleBar.close.pressed"); + + private static Icon maximizeIcon = UIManager.getIcon("DockViewTitleBar.maximize"); + + private static Icon maximizeIconRollover = UIManager.getIcon("DockViewTitleBar.maximize.rollover"); + + private static Icon maximizeIconPressed = UIManager.getIcon("DockViewTitleBar.maximize.pressed"); + + private static Icon restoreIcon = UIManager.getIcon("DockViewTitleBar.restore"); + + private static Icon restoreIconRollover = UIManager.getIcon("DockViewTitleBar.restore.rollover"); + + private static Icon restoreIconPressed = UIManager.getIcon("DockViewTitleBar.restore.pressed"); + + private static Icon hideIcon = UIManager.getIcon("DockViewTitleBar.hide"); + + private static Icon hideIconRollover = UIManager.getIcon("DockViewTitleBar.hide.rollover"); + + private static Icon hideIconPressed = UIManager.getIcon("DockViewTitleBar.hide.pressed"); + + private static Icon dockIcon = UIManager.getIcon("DockViewTitleBar.dock"); + + private static Icon dockIconRollover = UIManager.getIcon("DockViewTitleBar.dock.rollover"); + + private static Icon dockIconPressed = UIManager.getIcon("DockViewTitleBar.dock.pressed"); + + private static Icon floatIcon = UIManager.getIcon("DockViewTitleBar.float"); + + private static Icon floatIconRollover = UIManager.getIcon("DockViewTitleBar.float.rollover"); + + private static Icon floatIconPressed = UIManager.getIcon("DockViewTitleBar.float.pressed"); + + private static Icon attachIcon = UIManager.getIcon("DockViewTitleBar.attach"); + + private static Icon attachIconRollover = UIManager.getIcon("DockViewTitleBar.attach.rollover"); + + private static Icon attachIconPressed = UIManager.getIcon("DockViewTitleBar.attach.pressed"); + + // label resources taken from BasicInternalFrameUI... + private static final String CLOSE_TEXT = UIManager.getString("DockViewTitleBar.closeButtonText"); + + private static final String ICONIFY_TEXT = UIManager.getString("DockViewTitleBar.minimizeButtonText"); + + private static final String RESTORE_TEXT = UIManager.getString("DockViewTitleBar.restoreButtonText"); + + private static final String MAXIMIZE_TEXT = UIManager.getString("DockViewTitleBar.maximizeButtonText"); + + private static final String FLOAT_TEXT = UIManager.getString("DockViewTitleBar.floatButtonText"); + + private static final String ATTACH_TEXT = UIManager.getString("DockViewTitleBar.attachButtonText"); + + private static Color selectedTitleColor = UIManager.getColor("InternalFrame.activeTitleBackground"); + + private static Color selectedTextColor = UIManager.getColor("InternalFrame.activeTitleForeground"); + + private static Color notSelectedTitleColor = UIManager.getColor("InternalFrame.inactiveTitleBackground"); + + private static Color notSelectedTextColor = UIManager.getColor("InternalFrame.inactiveTitleForeground"); + + // flags to hide/show buttons in the title bar (they are always visible in the contextual menu, but might + // take too much space on the titles (for example a minimum set could be hide/float/close + // as maximize is accessed by double click) + private boolean isCloseButtonDisplayed = UIManager.getBoolean("DockViewTitleBar.isCloseButtonDisplayed"); + + private boolean isHideButtonDisplayed = UIManager.getBoolean("DockViewTitleBar.isHideButtonDisplayed"); + + private boolean isDockButtonDisplayed = UIManager.getBoolean("DockViewTitleBar.isDockButtonDisplayed"); + + private boolean isMaximizeButtonDisplayed = UIManager.getBoolean("DockViewTitleBar.isMaximizeButtonDisplayed"); + + private boolean isRestoreButtonDisplayed = UIManager.getBoolean("DockViewTitleBar.isRestoreButtonDisplayed"); + + private boolean isFloatButtonDisplayed = UIManager.getBoolean("DockViewTitleBar.isFloatButtonDisplayed"); + + private boolean isAttachButtonDisplayed = UIManager.getBoolean("DockViewTitleBar.isAttachButtonDisplayed"); + + protected DockViewTitleBar titleBar; + + /* + * This ancestor listener is required as buttons may change according to container hierarchy. The first example is + * when a dockable is added to a floating + compund dockable, the attach button (which usually becomes visible as + * the dockable is in the floating state) has to be hidden (tech choice : we don't want to allow attaching a single + * child of a compound dockable) + * + */ + private AncestorListener ancestorListener = new AncestorListener() { + + @Override + public void ancestorAdded(AncestorEvent event) { + configureButtons(titleBar); + } + + @Override + public void ancestorMoved(AncestorEvent event) { + } + + @Override + public void ancestorRemoved(AncestorEvent event) { + } + }; + + public DockViewTitleBarUI(DockViewTitleBar tb) { + this.titleBar = tb; + } + + public static ComponentUI createUI(JComponent c) { + return new DockViewTitleBarUI((DockViewTitleBar) c); + } + + @Override + public void installUI(JComponent c) { + super.installUI(c); + installTitleBorder(c); + + BoxLayout box = new BoxLayout(titleBar, BoxLayout.X_AXIS); + titleBar.setLayout(box); + titleBar.setOpaque(true); + + titleBar.addPropertyChangeListener(this); + titleBar.addAncestorListener(ancestorListener); + installButtons(); + installLabel(); + Dockable d = titleBar.getDockable(); + if (d != null) { + titleBar.getDockable().getDockKey().addPropertyChangeListener(this); + configureButtons(titleBar); + } + c.setCursor(Cursor.getDefaultCursor()); // needs this to avoid artifacts with floating dockable borders. + + } + + protected void layoutTitleBar() { + titleBar.removeAll(); + + titleBar.add(Box.createHorizontalStrut(4)); // keep the label not to close to the border + + titleBar.add(titleBar.getTitleLabel()); + titleBar.add(Box.createHorizontalGlue()); + int height = UIManager.getInt("DockViewTitleBar.height"); + if (height > 0) { + titleBar.add(Box.createRigidArea(new Dimension(4, height))); // fixed height + } + titleBar.add(titleBar.getHideOrDockButton()); + titleBar.add(titleBar.getFloatButton()); + titleBar.add(titleBar.getMaximizeOrRestoreButton()); + titleBar.add(titleBar.getCloseButton()); + // simple layout hook for extenders + titleBar.finishLayout(); + + titleBar.revalidate(); + + } + + @Override + public void uninstallUI(JComponent c) { + super.uninstallUI(c); + uninstallTitleBorder(c); + titleBar.removePropertyChangeListener(this); + titleBar.removeAncestorListener(ancestorListener); + Dockable d = titleBar.getDockable(); + if (d != null) { + d.getDockKey().removePropertyChangeListener(this); + } + } + + /** Installs default on the titlebar label */ + protected void installLabel() { + JLabel titleLabel = titleBar.getTitleLabel(); + Font f = UIManager.getFont("DockViewTitleBar.titleFont"); + titleLabel.setFont(f); + titleLabel.setForeground(notSelectedTextColor); + titleBar.setBackground(notSelectedTitleColor); + + // adjust minimum size because of BoxLayout usage (doesn't allow resize under minimum size) + // titleLabel.setMinimumSize(new Dimension(30, titleLabel.getPreferredSize().height)); + } + + /** Installs the default background of the title bar */ + protected void installBackground() { + titleBar.setBackground(notSelectedTitleColor); + } + + /** configure the title bar buttons */ + protected void installButtons() { + JButton closeButton = titleBar.getCloseButton(); + JButton dockButton = titleBar.getHideOrDockButton(); + JButton maximizeButton = titleBar.getMaximizeOrRestoreButton(); + JButton floatButton = titleBar.getFloatButton(); + + // temporary workaround for nimbus look and feel : nimbus adds a large border + // around buttons which doesn't follow other LAF behaviour. + Border emptyBorder = BorderFactory.createEmptyBorder(0, 2, 0, 2); + closeButton.setBorder(emptyBorder); + dockButton.setBorder(emptyBorder); + maximizeButton.setBorder(emptyBorder); + floatButton.setBorder(emptyBorder); + + // Insets buttonMargin = new Insets(3, 5, 3, 5); + Insets buttonMargin = new Insets(0, 2, 0, 2); + closeButton.setMargin(buttonMargin); + dockButton.setMargin(buttonMargin); + maximizeButton.setMargin(buttonMargin); + floatButton.setMargin(buttonMargin); + + dockButton.setRolloverEnabled(true); + dockButton.setBorderPainted(false); + dockButton.setFocusable(false); + dockButton.setContentAreaFilled(false); + + closeButton.setRolloverEnabled(true); + closeButton.setBorderPainted(false); + closeButton.setFocusable(false); + closeButton.setContentAreaFilled(false); + + maximizeButton.setRolloverEnabled(true); + maximizeButton.setBorderPainted(false); + maximizeButton.setFocusable(false); + maximizeButton.setContentAreaFilled(false); + + floatButton.setRolloverEnabled(true); + floatButton.setBorderPainted(false); + floatButton.setFocusable(false); + floatButton.setContentAreaFilled(false); + + } + + /** Listen to property changes in the DockKey or the title bar */ + @Override + public void propertyChange(PropertyChangeEvent e) { + String pName = e.getPropertyName(); + // System.out.println("property change " + pName); + if (pName.equals("dockable")) { + Dockable old = (Dockable) e.getOldValue(); + if (old != null) { + old.getDockKey().removePropertyChangeListener(this); + } + Dockable newDockable = ((Dockable) e.getNewValue()); + if (newDockable != null) { + configureButtons(titleBar); + newDockable.getDockKey().addPropertyChangeListener(this); + } + } + else if (pName.equals(DockKey.PROPERTY_AUTOHIDEABLE)) { + boolean isAutoHideable = ((Boolean) e.getNewValue()).booleanValue(); + boolean isMaximized = titleBar.getDockable().getDockKey().getLocation() == DockableState.Location.MAXIMIZED; + if (isAutoHideable) { + if (!isMaximized) { + titleBar.getHideOrDockButton().setVisible(true); + configureHideButton(titleBar.getHideOrDockButton()); + } + } + else { + titleBar.getHideOrDockButton().setVisible(false); + } + } + else if (pName.equals(DockKey.PROPERTY_MAXIMIZABLE)) { + boolean isMaximizeable = ((Boolean) e.getNewValue()).booleanValue(); + boolean isHidden = titleBar.getDockable().getDockKey().getLocation() == DockableState.Location.HIDDEN; + if (isMaximizeable) { + if (!isHidden) { + titleBar.getMaximizeOrRestoreButton().setVisible(true); + configureMaximizeButton(titleBar.getMaximizeOrRestoreButton()); + } + } + else { + titleBar.getMaximizeOrRestoreButton().setVisible(false); + } + } + else if (pName.equals(DockKey.PROPERTY_CLOSEABLE)) { + boolean isCloseable = ((Boolean) e.getNewValue()).booleanValue(); + boolean isMaximized = titleBar.getDockable().getDockKey().getLocation() == DockableState.Location.MAXIMIZED; + if (isCloseable) { + if (!isMaximized) { + titleBar.getCloseButton().setVisible(true); + configureCloseButton(titleBar.getCloseButton()); + } + } + else { + titleBar.getCloseButton().setVisible(false); + } + } + else if (pName.equals(DockKey.PROPERTY_FLOATABLE)) { + boolean isFloatable = ((Boolean) e.getNewValue()).booleanValue(); + boolean isMaximized = titleBar.getDockable().getDockKey().getLocation() == DockableState.Location.MAXIMIZED; + if (isFloatable) { + if (!isMaximized) { + titleBar.getFloatButton().setVisible(true); + configureFloatButton(titleBar.getFloatButton()); + } + } + else { + titleBar.getFloatButton().setVisible(false); + } + } + else if (pName.equals(DockKey.PROPERTY_DOCKABLE_STATE)) { + configureButtons(titleBar); + } + else if (pName.equals("active")) { + boolean isActive = ((Boolean) e.getNewValue()).booleanValue(); + if (isActive) { + titleBar.getTitleLabel().setForeground(selectedTextColor); + titleBar.setBackground(selectedTitleColor); + } + else { + titleBar.getTitleLabel().setForeground(notSelectedTextColor); + titleBar.setBackground(notSelectedTitleColor); + } + titleBar.repaint(); + } + else if (pName.equals("titlebar.notification")) { + boolean notification = ((Boolean) e.getNewValue()).booleanValue(); + if (notification) { + titleBar.setBackground(UIManager.getColor("DockingDesktop.notificationColor")); + titleBar.putClientProperty("titlebar.notification", true); + titleBar.setOpaque(true); + } + else { + if (titleBar.isActive()) { + titleBar.setBackground(selectedTitleColor); + } + else { + titleBar.setBackground(notSelectedTitleColor); + } + titleBar.putClientProperty("titlebar.notification", false); + titleBar.setOpaque(false); + } + titleBar.repaint(); + } + else if (pName.equals(DockKey.PROPERTY_NAME)) { + titleBar.repaint(); + } + else if (pName.equals("clientProperty.visibleTitleBar")) { // 2006/12/01 + boolean v = Boolean.TRUE.equals(e.getNewValue()); + titleBar.setVisible(v); + } + } + + /** + * Update the buttons to track state changes (for example, the maximize button can become "restore" when the view is + * maximized. + */ + protected void configureButtons(DockViewTitleBar tb) { + layoutTitleBar(); + + DockKey key = tb.getDockable().getDockKey(); + DockableState.Location location = key.getLocation(); + JButton closeButton = titleBar.getCloseButton(); + JButton maxBtn = titleBar.getMaximizeOrRestoreButton(); + JButton hideBtn = titleBar.getHideOrDockButton(); + JButton floatBtn = titleBar.getFloatButton(); + + switch (location) { + case DOCKED: + if (key.isCloseEnabled() && isCloseButtonDisplayed) { + closeButton.setVisible(true); + configureCloseButton(closeButton); + } + else { + closeButton.setVisible(false); + } + if (key.isMaximizeEnabled() && isMaximizeButtonDisplayed) { + maxBtn.setVisible(true); + configureMaximizeButton(maxBtn); + } + else { + maxBtn.setVisible(false); + } + + boolean isChildOfMaximizedContainer = false; + DockingDesktop desk = titleBar.getDesktop(); + if (desk != null) { + Dockable max = desk.getMaximizedDockable(); + if (max != null && max.getComponent().getParent().isAncestorOf(titleBar)) { + isChildOfMaximizedContainer = true; + } + } + + if (key.isAutoHideEnabled() && isHideButtonDisplayed && !isChildOfMaximizedContainer) { + hideBtn.setVisible(true); + configureHideButton(hideBtn); + } + else { + hideBtn.setVisible(false); + } + if (key.isFloatEnabled() && isFloatButtonDisplayed && !isChildOfMaximizedContainer) { + floatBtn.setVisible(true); + configureFloatButton(floatBtn); + } + else { + floatBtn.setVisible(false); + } + + titleBar.revalidate(); + break; + case HIDDEN: + if (key.isCloseEnabled() && isCloseButtonDisplayed) { + closeButton.setVisible(true); + configureCloseButton(closeButton); + } + else { + closeButton.setVisible(false); + } + // maximize not allowed when in autohide mode + maxBtn.setVisible(false); + + boolean isChildOfCompound = DockingUtilities.isChildOfCompoundDockable(tb.getDockable()); + + // idem for float + if (key.isFloatEnabled() && isFloatButtonDisplayed && !isChildOfCompound) { + floatBtn.setVisible(true); + configureFloatButton(floatBtn); + } + else { + floatBtn.setVisible(false); + } + // hide becomes dock + if (isChildOfCompound) { + // tb.getParent().getParent() instanceof AutoHideExpandPanel + // V2.1 : only when not nested + hideBtn.setVisible(false); + } + else { + if (isDockButtonDisplayed && key.isAutoHideEnabled()) { // 2007/01/08 + hideBtn.setVisible(true); + configureDockButton(hideBtn); + } + else { + hideBtn.setVisible(false); + } + } + titleBar.revalidate(); + break; + case MAXIMIZED: + closeButton.setVisible(false); + floatBtn.setVisible(false); + // maxBtn becomes restore + if (isRestoreButtonDisplayed) { + configureRestoreButton(maxBtn); + maxBtn.setVisible(true); + } + else { + maxBtn.setVisible(false); + } + hideBtn.setVisible(false); + titleBar.revalidate(); + break; + case FLOATING: + closeButton.setVisible(false); + maxBtn.setVisible(false); + hideBtn.setVisible(false); + if (DockingUtilities.isChildOfCompoundDockable(tb.getDockable())) { + // cannot attach a compound dockable directly + floatBtn.setVisible(false); + } + else { + floatBtn.setVisible(isAttachButtonDisplayed); + configureAttachButton(floatBtn); + } + break; + default: + // not interesting + } + + } + + /** installs the icons and tooltip suitable for a close button */ + protected void configureCloseButton(JButton btn) { + btn.setIcon(closeIcon); + btn.setRolloverIcon(closeIconRollover); + btn.setPressedIcon(closeIconPressed); + btn.setRolloverSelectedIcon(closeIconRollover); + btn.setToolTipText(CLOSE_TEXT); + } + + /** installs the icons and tooltip suitable for a maximize button */ + protected void configureMaximizeButton(JButton btn) { + btn.setIcon(maximizeIcon); + btn.setRolloverIcon(maximizeIconRollover); + btn.setPressedIcon(maximizeIconPressed); + btn.setRolloverSelectedIcon(maximizeIconRollover); + btn.setToolTipText(MAXIMIZE_TEXT); + } + + /** + * installs the icons and tooltip suitable for a restore button. + */ + protected void configureRestoreButton(JButton btn) { + btn.setIcon(restoreIcon); + btn.setRolloverIcon(restoreIconRollover); + btn.setPressedIcon(restoreIconPressed); + btn.setRolloverSelectedIcon(restoreIconRollover); + btn.setToolTipText(RESTORE_TEXT); + } + + /** installs the icons and tooltip suitable for a hide button */ + protected void configureHideButton(JButton btn) { + btn.setIcon(hideIcon); + btn.setRolloverIcon(hideIconRollover); + btn.setPressedIcon(hideIconPressed); + btn.setRolloverSelectedIcon(hideIconRollover); + btn.setToolTipText(ICONIFY_TEXT); + } + + /** installs the icons and tooltip suitable for a dock button */ + protected void configureDockButton(JButton btn) { + btn.setIcon(dockIcon); + btn.setRolloverIcon(dockIconRollover); + btn.setPressedIcon(dockIconPressed); + btn.setRolloverSelectedIcon(dockIconRollover); + btn.setToolTipText(RESTORE_TEXT); + } + + /** installs the icons and tooltip suitable for a float button */ + protected void configureFloatButton(JButton btn) { + btn.setIcon(floatIcon); + btn.setRolloverIcon(floatIconRollover); + btn.setPressedIcon(floatIconPressed); + btn.setRolloverSelectedIcon(floatIconRollover); + btn.setToolTipText(FLOAT_TEXT); + } + + /** installs the icons and tooltip suitable for an attach button */ + protected void configureAttachButton(JButton btn) { + btn.setIcon(attachIcon); + btn.setRolloverIcon(attachIconRollover); + btn.setPressedIcon(attachIconPressed); + btn.setRolloverSelectedIcon(attachIconRollover); + btn.setToolTipText(ATTACH_TEXT); + } + + /** installs the border of the title bar */ + protected void installTitleBorder(JComponent c) { + Border b = UIManager.getBorder("DockViewTitleBar.border"); + c.setBorder(b); + + } + + /** uninstalls the icons and tooltip suitable for a close button */ + protected void uninstallTitleBorder(JComponent c) { + c.setBorder(null); + } + + /** + * Custom title bar painting : uses a gradient from the background color to the control highlight color. + */ + @Override + public void paint(Graphics g, JComponent c) { + + DockViewTitleBar tb = (DockViewTitleBar) c; + if (useCustomPaint) { + Graphics2D g2 = (Graphics2D) g.create(); + g2.setColor(panelColor); + g2.fillRect(0, 0, tb.getWidth(), tb.getHeight()); // emptyborder doesn't repaint + + Color background = UIManager.getColor("Panel.background"); + if (titleBar.getClientProperty("titlebar.notification") == Boolean.TRUE) { + background = UIManager.getColor("DockingDesktop.notificationColor"); + } + + // TODO: Optimize Look and Feel specific color selecting + Insets i = tb.getInsets(); + if (tb.isActive()) { + if (UIManager.getLookAndFeel().getName().contains("Substance")) { + g2.setColor(UIManager.getColor("controlLtHighlight")); // Active substance + } + else { + g2.setColor(UIManager.getColor("controlShadow")); // Active other + } + } + else { + g2.setColor(background); + } + Rectangle r = tb.getTitleLabel().getBounds(); + int w = r.x + r.width; + g2.fillRect(i.left, i.top, w, tb.getHeight() - i.top - i.bottom); + // gradient paint after the label text (to ensure readability) + if (tb.isActive()) { + if (UIManager.getLookAndFeel().getName().contains("Substance")) { + g2 + .setPaint(new GradientPaint(i.left + w, 0, UIManager.getColor("controlLtHighlight"), + tb.getWidth(), 0, UIManager.getColor("controlHighlight"))); // Active substance + } + else { + g2 + .setPaint(new GradientPaint(i.left + w, 0, UIManager.getColor("controlShadow"), tb.getWidth(), + 0, UIManager.getColor("controlHighlight"))); // Active other + } + } + else { + g2 + .setPaint(new GradientPaint(i.left + w, 0, /* UIManager.getColor("Panel.background") */ background, + tb.getWidth(), 0, UIManager.getColor("controlHighlight"))); // panelColor)); + } + g2.fillRect(i.left + w, i.top, tb.getWidth() - w - i.left - i.right, tb.getHeight() - i.top - i.bottom); + + g2.dispose(); + } + super.paint(g, c); + + } }