/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Tiny Look and Feel * * * * (C) Copyright 2003 - 2007 Hans Bickel * * * * For licensing information and credits, please refer to the * * comment in file de.muntjak.tinylookandfeel.TinyLookAndFeel * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ package de.muntjak.tinylookandfeel; import java.awt.Component; 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.Image; import java.awt.Insets; 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.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Locale; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JRootPane; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.plaf.UIResource; import de.muntjak.tinylookandfeel.controlpanel.ControlPanel; /* * @(#)TinyTitlePane.java 1.10 01/12/03 * * Copyright 2002 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ /* * Class that manages a JLF awt.Window-descendant class's title bar. * <p> * This class assumes it will be created with a particular window * decoration style, and that if the style changes, a new one will * be created. * * @version 1.10 12/03/01 * @author Terry Kellerman * @since 1.4 */ /** * TinyTitlePane * * @version 1.0 * @author Hans Bickel */ public class TinyTitlePane extends JComponent { private static final int IMAGE_HEIGHT = 16; private static final int IMAGE_WIDTH = 16; private static TinyWindowButtonUI iconButtonUI; private static TinyWindowButtonUI maxButtonUI; private static TinyWindowButtonUI closeButtonUI; /** * PropertyChangeListener added to the JRootPane. */ private PropertyChangeListener propertyChangeListener; /** * JMenuBar, typically renders the system menu items. */ private 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. */ private JButton toggleButton; /** * Button used to maximize or restore the Frame. */ private JButton iconifyButton; /** * Button used to maximize or restore the Frame. */ private JButton closeButton; /** * Listens for changes in the state of the Window listener to update * the state of the widgets. */ private WindowListener windowListener; private ComponentListener windowMoveListener; /** * Window we're currently in. */ private Window window; /** * JRootPane rendering for. */ private JRootPane rootPane; /** * Room remaining in title for bumps. */ private int buttonsWidth; /** * Buffered Frame.state property. As state isn't bound, this is kept * to determine when to avoid updating widgets. */ private int state; /** * RootPaneUI that created us. */ private TinyRootPaneUI rootPaneUI; public TinyTitlePane(JRootPane root, TinyRootPaneUI ui) { rootPane = root; rootPaneUI = ui; state = -1; installSubcomponents(); installDefaults(); setLayout(createLayout()); } /** * Uninstalls the necessary state. */ private void uninstall() { uninstallListeners(); window = null; removeAll(); } /** * Installs the necessary listeners. */ private void installListeners() { if(window != null) { windowListener = createWindowListener(); window.addWindowListener(windowListener); propertyChangeListener = createWindowPropertyChangeListener(); window.addPropertyChangeListener(propertyChangeListener); windowMoveListener = new WindowMoveListener(); window.addComponentListener(windowMoveListener); } } /** * Uninstalls the necessary listeners. */ private void uninstallListeners() { if(window != null) { window.removeWindowListener(windowListener); window.removePropertyChangeListener(propertyChangeListener); window.removeComponentListener(windowMoveListener); } } /** * Returns the <code>WindowListener</code> to add to the * <code>Window</code>. */ private WindowListener createWindowListener() { return new WindowHandler(); } /** * Returns the <code>PropertyChangeListener</code> to install on * the <code>Window</code>. */ private PropertyChangeListener createWindowPropertyChangeListener() { return new PropertyChangeHandler(); } /** * Returns the <code>JRootPane</code> this was created for. */ public JRootPane getRootPane() { return rootPane; } /** * Returns the decoration style of the <code>JRootPane</code>. */ private int getWindowDecorationStyle() { return getRootPane().getWindowDecorationStyle(); } public void addNotify() { super.addNotify(); uninstallListeners(); window = SwingUtilities.getWindowAncestor(this); if(window != null) { if(window instanceof Frame) { setState(((Frame)window).getExtendedState()); } else { setState(0); } setActive(window.isActive()); installListeners(); } } public void removeNotify() { super.removeNotify(); uninstallListeners(); window = null; } /** * Adds any sub-Components contained in the <code>TinyTitlePane</code>. */ private void installSubcomponents() { if(getWindowDecorationStyle() == JRootPane.FRAME) { createActions(); menuBar = createMenuBar(); add(menuBar); createButtons(); add(iconifyButton); add(toggleButton); add(closeButton); iconifyButton.putClientProperty("externalFrameButton", Boolean.TRUE); toggleButton.putClientProperty("externalFrameButton", Boolean.TRUE); closeButton.putClientProperty("externalFrameButton", Boolean.TRUE); } else if(getWindowDecorationStyle() != JRootPane.NONE) { createActions(); createButtons(); add(closeButton); closeButton.putClientProperty("externalFrameButton", Boolean.FALSE); } } /** * Installs the fonts and necessary properties on the TinyTitlePane. */ private void installDefaults() { setFont(UIManager.getFont("InternalFrame.titleFont", getLocale())); } /** * Uninstalls any previously installed UI values. */ private void uninstallDefaults() { } /** * Returns the <code>JMenuBar</code> displaying the appropriate * system menu items. */ protected JMenuBar createMenuBar() { menuBar = new SystemMenuBar(); menuBar.setFocusable(false); menuBar.setBorderPainted(true); menuBar.add(createMenu()); return menuBar; } /** * Closes the Window. */ private void close() { Window window = getWindow(); if(window != null) { window.dispatchEvent(new WindowEvent( window, WindowEvent.WINDOW_CLOSING)); } } /** * Iconifies the Frame. */ private void iconify() { Frame frame = getFrame(); if(frame != null) { frame.setExtendedState(frame.getExtendedState() | Frame.ICONIFIED); } } /** * Maximizes the Frame. */ private void maximize() { Frame frame = getFrame(); if(frame != null) { setMaximizeBounds(frame); frame.setExtendedState(frame.getExtendedState() | Frame.MAXIMIZED_BOTH); } } protected void setMaximizeBounds(Frame frame) { if(frame.getMaximizedBounds() != null) return; Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(frame.getGraphicsConfiguration()); Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); // spare any Systemmenus or Taskbars or ??... int x = screenInsets.top; int y = screenInsets.left; int w = screenSize.width - x - screenInsets.right; int h = screenSize.height - y - screenInsets.bottom; Rectangle maxBounds = new Rectangle(x, y, w, h); frame.setMaximizedBounds(maxBounds); } /** * Restores the Frame size. */ private void restore() { Frame frame = getFrame(); if(frame == null) { return; } if((frame.getExtendedState() & Frame.ICONIFIED) == Frame.ICONIFIED) { frame.setExtendedState(state & ~Frame.ICONIFIED); } else { frame.setExtendedState(state & ~Frame.MAXIMIZED_BOTH); } } /** * Create the <code>Action</code>s that get associated with the * buttons and menu items. */ private void createActions() { closeAction = new CloseAction(); iconifyAction = new IconifyAction(); restoreAction = new RestoreAction(); maximizeAction = new MaximizeAction(); } /** * Returns the <code>JMenu</code> displaying the appropriate menu items * for manipulating the Frame. */ private JMenu createMenu() { JMenu menu = new JMenu(""); if(getWindowDecorationStyle() == JRootPane.FRAME) { addMenuItems(menu); // we use this property to prevent the Menu from drawing rollovers menu.putClientProperty("isSystemMenu", Boolean.TRUE); } return menu; } /** * Adds the necessary <code>JMenuItem</code>s to the passed in menu. */ private void addMenuItems(JMenu menu) { Locale locale = getRootPane().getLocale(); JMenuItem mi = menu.add(restoreAction); int mnemonic = getInt("MetalTitlePane.restoreMnemonic", -1); if(mnemonic != -1) { mi.setMnemonic(mnemonic); } mi = menu.add(iconifyAction); mnemonic = getInt("MetalTitlePane.iconifyMnemonic", -1); if(mnemonic != -1) { mi.setMnemonic(mnemonic); } if(Toolkit.getDefaultToolkit().isFrameStateSupported(Frame.MAXIMIZED_BOTH)) { mi = menu.add(maximizeAction); mnemonic = getInt("MetalTitlePane.maximizeMnemonic", -1); if(mnemonic != -1) { mi.setMnemonic(mnemonic); } } menu.addSeparator(); mi = menu.add(closeAction); mnemonic = getInt("MetalTitlePane.closeMnemonic", -1); if(mnemonic != -1) { mi.setMnemonic(mnemonic); } } /** * Creates the buttons of the title pane and initializes their actions. */ protected void createButtons() { if(iconButtonUI == null) { iconButtonUI = TinyWindowButtonUI.createButtonUIForType(TinyWindowButtonUI.MINIMIZE); maxButtonUI = TinyWindowButtonUI.createButtonUIForType(TinyWindowButtonUI.MAXIMIZE); closeButtonUI = TinyWindowButtonUI.createButtonUIForType(TinyWindowButtonUI.CLOSE); } iconifyButton = new SpecialUIButton(iconButtonUI); iconifyButton.setAction(iconifyAction); iconifyButton.setRolloverEnabled(true); toggleButton = new SpecialUIButton(maxButtonUI); toggleButton.setAction(maximizeAction); toggleButton.setRolloverEnabled(true); closeButton = new SpecialUIButton(closeButtonUI); closeButton.setAction(closeAction); closeButton.setRolloverEnabled(true); closeButton.getAccessibleContext().setAccessibleName("Close"); iconifyButton.getAccessibleContext().setAccessibleName("Iconify"); toggleButton.getAccessibleContext().setAccessibleName("Maximize"); if(TinyLookAndFeel.controlPanelInstantiated) { ControlPanel.setWindowButtons(new JButton[] {iconifyButton, toggleButton, closeButton}); } } /** * Returns the <code>LayoutManager</code> that should be installed on * the <code>TinyTitlePane</code>. */ private LayoutManager createLayout() { return new TitlePaneLayout(); } /** * Updates state dependant upon the Window's active state. */ private void setActive(boolean isActive) { if(getWindowDecorationStyle() == JRootPane.FRAME) { Boolean activeB = isActive ? Boolean.TRUE : Boolean.FALSE; iconifyButton.putClientProperty("paintActive", activeB); closeButton.putClientProperty("paintActive", activeB); toggleButton.putClientProperty("paintActive", activeB); iconifyButton.setEnabled(isActive); closeButton.setEnabled(isActive); toggleButton.setEnabled(isActive); } // Repaint the whole thing as the Borders that are used have // different colors for active vs inactive getRootPane().repaint(); } /** * Sets the state of the Window. */ private void setState(int state) { setState(state, false); } /** * Sets the state of the window. If <code>updateRegardless</code> is * true and the state has not changed, this will update anyway. */ private void setState(int state, boolean updateRegardless) { Window w = getWindow(); if(w != null && getWindowDecorationStyle() == JRootPane.FRAME) { if(this.state == state && !updateRegardless) { return; } Frame frame = getFrame(); if(frame != null) { JRootPane rootPane = getRootPane(); if((state & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH && (rootPane.getBorder() == null || (rootPane.getBorder() instanceof UIResource)) && frame.isShowing()) { //rootPane.setBorder(null); } else if((state & Frame.MAXIMIZED_BOTH) != Frame.MAXIMIZED_BOTH) { // This is a croak, if state becomes bound, this can // be nuked. //rootPaneUI.installBorder(rootPane); } if(frame.isResizable()) { if((state & Frame.MAXIMIZED_VERT) == Frame.MAXIMIZED_VERT || (state & Frame.MAXIMIZED_HORIZ) == Frame.MAXIMIZED_HORIZ) { updateToggleButton(restoreAction); maximizeAction.setEnabled(false); restoreAction.setEnabled(true); } else { updateToggleButton(maximizeAction); maximizeAction.setEnabled(true); restoreAction.setEnabled(false); } if(toggleButton.getParent() == null || iconifyButton.getParent() == null) { add(toggleButton); add(iconifyButton); revalidate(); repaint(); } toggleButton.setText(null); } else { maximizeAction.setEnabled(false); restoreAction.setEnabled(false); if(toggleButton.getParent() != null) { remove(toggleButton); revalidate(); repaint(); } } } else { // Not contained in a Frame maximizeAction.setEnabled(false); restoreAction.setEnabled(false); iconifyAction.setEnabled(false); remove(toggleButton); remove(iconifyButton); revalidate(); repaint(); } closeAction.setEnabled(true); this.state = state; } } /** * Updates the toggle button to contain the Icon <code>icon</code>, and * Action <code>action</code>. */ private void updateToggleButton(Action action) { toggleButton.setAction(action); toggleButton.setText(null); } /** * Returns the Frame rendering in. This will return null if the * <code>JRootPane</code> is not contained in a <code>Frame</code>. */ private Frame getFrame() { Window window = getWindow(); if(window instanceof Frame) { return (Frame)window; } return null; } /** * Returns the <code>Window</code> the <code>JRootPane</code> is * contained in. This will return null if there is no parent ancestor * of the <code>JRootPane</code>. */ private Window getWindow() { return window; } /** * Returns the String to display as the title. */ private String getTitle() { Window w = getWindow(); if(w instanceof Frame) { return ((Frame)w).getTitle(); } else if(w instanceof Dialog) { return ((Dialog)w).getTitle(); } return null; } public boolean isSelected() { Window window = getWindow(); return (window == null) ? true : window.isActive(); } public boolean isFrameMaximized() { Frame frame = getFrame(); if(frame != null) { return ((frame.getExtendedState() & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH); } return false; } /** * Renders the TitlePane ( => paints the title). */ public void paintComponent(Graphics g) { // As state isn't bound, we need a convenience place to check // if it has changed. if(getFrame() != null) { setState(getFrame().getExtendedState()); } Window window = getWindow(); boolean leftToRight = (window == null) ? getRootPane().getComponentOrientation().isLeftToRight() : window.getComponentOrientation().isLeftToRight(); boolean isSelected = (window == null) ? true : window.isActive(); int width = getWidth(); int height = getHeight(); int xOffset = leftToRight ? 5 : width - 5; if(getWindowDecorationStyle() == JRootPane.FRAME) { xOffset += leftToRight ? IMAGE_WIDTH + 5 : - IMAGE_WIDTH - 5; } String theTitle = getTitle(); if(theTitle != null) { Font f = UIManager.getFont("InternalFrame.normalTitleFont"); FontMetrics fm = g.getFontMetrics(); int yOffset = ( (height - fm.getHeight() ) / 2 ) + fm.getAscent(); Rectangle rect = new Rectangle(0, 0, 0, 0); if(iconifyButton != null && iconifyButton.getParent() != null) { rect = iconifyButton.getBounds(); } int titleW; if( leftToRight ) { if(rect.x == 0) { rect.x = window.getWidth() - window.getInsets().right-2; } titleW = rect.x - xOffset - 4; theTitle = clippedText(theTitle, fm, titleW); } else { titleW = xOffset - rect.x - rect.width - 4; theTitle = clippedText(theTitle, fm, titleW); xOffset -= SwingUtilities.computeStringWidth(fm, theTitle); } int titleLength = SwingUtilities.computeStringWidth(fm, theTitle); if(isSelected) { g.setColor(Theme.frameTitleColor[Theme.style].getColor()); g.drawString(theTitle, xOffset, yOffset); } else { // for an inactive window g.setColor(Theme.frameTitleDisabledColor[Theme.style].getColor()); g.drawString(theTitle, xOffset, yOffset); } xOffset += leftToRight ? titleLength + 5 : -5; } } /** * Convenience method to clip the passed in text to the specified * size. */ private String clippedText(String text, FontMetrics fm, int availTextWidth) { if((text == null) || (text.equals(""))) { return ""; } int textWidth = SwingUtilities.computeStringWidth(fm, text); String clipString = "..."; if(textWidth > availTextWidth) { int totalWidth = SwingUtilities.computeStringWidth(fm, clipString); int nChars; for(nChars = 0; nChars < text.length(); nChars++) { totalWidth += fm.charWidth(text.charAt(nChars)); if(totalWidth > availTextWidth) { break; } } text = text.substring(0, nChars) + clipString; } return text; } private int getInt(Object key, int defaultValue) { Object value = UIManager.get(key); if(value instanceof Integer) { return ((Integer)value).intValue(); } if(value instanceof String) { try { return Integer.parseInt((String)value); } catch (NumberFormatException nfe) {} } return defaultValue; } /** * Actions used to <code>close</code> the <code>Window</code>. */ private class CloseAction extends AbstractAction { public CloseAction() { super(UIManager.getString("MetalTitlePane.closeTitle", getLocale())); } public void actionPerformed(ActionEvent e) { close(); } } /** * Actions used to <code>iconfiy</code> the <code>Frame</code>. */ private class IconifyAction extends AbstractAction { public IconifyAction() { super(UIManager.getString("MetalTitlePane.iconifyTitle", getLocale())); } public void actionPerformed(ActionEvent e) { iconify(); } } /** * Actions used to <code>restore</code> the <code>Frame</code>. */ private class RestoreAction extends AbstractAction { public RestoreAction() { super(UIManager.getString ("MetalTitlePane.restoreTitle", getLocale())); } public void actionPerformed(ActionEvent e) { restore(); } } /** * Actions used to <code>restore</code> the <code>Frame</code>. */ private class MaximizeAction extends AbstractAction { public MaximizeAction() { super(UIManager.getString( "MetalTitlePane.maximizeTitle", getLocale())); } public void actionPerformed(ActionEvent e) { maximize(); } } /** * Class responsible for drawing the system menu. Looks up the * image to draw from the Frame associated with the * <code>JRootPane</code>. */ private class SystemMenuBar extends JMenuBar { public void paint(Graphics g) { Frame frame = getFrame(); Image image = (frame != null) ? frame.getIconImage() : null; if(image != null) { g.drawImage(image, 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, null); } else { Icon icon = UIManager.getIcon("InternalFrame.icon"); if(icon != null) { icon.paintIcon(this, g, 0, 0); } } } public Dimension getMinimumSize() { return getPreferredSize(); } public Dimension getPreferredSize() { Icon icon = UIManager.getIcon("InternalFrame.icon"); if(icon != null) { return new Dimension(icon.getIconWidth(), icon.getIconHeight()); } Dimension size = super.getPreferredSize(); return new Dimension(Math.max(IMAGE_WIDTH, size.width), Math.max(size.height, IMAGE_HEIGHT)); } } /** * This inner class is marked "public" due to a compiler bug. * This class should be treated as a "protected" inner class. * Instantiate it only within subclasses of <Foo>. */ private class TitlePaneLayout implements LayoutManager { public void addLayoutComponent(String name, Component c) {} public void removeLayoutComponent(Component c) {} public Dimension preferredLayoutSize(Container c) { return new Dimension(TinyLookAndFeel.MINIMUM_FRAME_WIDTH, computeHeight()); } public Dimension minimumLayoutSize(Container c) { return preferredLayoutSize(c); } private int computeHeight() { if(getFrame() instanceof JFrame) { setMaximizeBounds(getFrame()); return Theme.frameTitleHeight[Theme.derivedStyle[Theme.style]]; } else { return Theme.frameInternalTitleHeight[Theme.derivedStyle[Theme.style]]; } } public void layoutContainer(Container c) { if(getWindowDecorationStyle() == JRootPane.NONE) { buttonsWidth = 0; return; } boolean leftToRight = (window == null) ? getRootPane().getComponentOrientation().isLeftToRight() : window.getComponentOrientation().isLeftToRight(); int w = getWidth(); int x; int spacing; int buttonHeight; int buttonWidth; if(closeButton != null) { buttonHeight = closeButton.getPreferredSize().height; buttonWidth = closeButton.getPreferredSize().width; } else { buttonHeight = IMAGE_HEIGHT; buttonWidth = IMAGE_WIDTH; } int y = (getHeight() - buttonHeight) / 2 + 1; if(Theme.derivedStyle[Theme.style] == Theme.W99_STYLE) { y += 1; } // 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(menuBar != null) { // this is a JFrame menuBar.setBounds(x, y, buttonWidth, buttonHeight); } x = leftToRight ? w : 0; spacing = 2; x += leftToRight ? -spacing -buttonWidth : spacing; if(closeButton != null) { closeButton.setBounds(x, y, buttonWidth, buttonHeight); } if( !leftToRight ) x += buttonWidth; if(Toolkit.getDefaultToolkit().isFrameStateSupported(Frame.MAXIMIZED_BOTH)) { if(toggleButton.getParent() != null) { x += leftToRight ? -spacing -buttonWidth : spacing; toggleButton.setBounds(x, y, buttonWidth, buttonHeight); if(!leftToRight) { x += buttonWidth; } } } if(iconifyButton != null && iconifyButton.getParent() != null) { x += leftToRight ? -spacing -buttonWidth : spacing; iconifyButton.setBounds(x, y, buttonWidth, buttonHeight); if(!leftToRight) { x += buttonWidth; } } buttonsWidth = leftToRight ? w - x : x; } } /** * PropertyChangeListener installed on the Window. Updates the necessary * state as the state of the Window changes. */ private class PropertyChangeHandler implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent pce) { String name = pce.getPropertyName(); // Frame.state isn't currently bound. if("resizable".equals(name) || "state".equals(name)) { Frame frame = getFrame(); if(frame != null) { setState(frame.getExtendedState(), true); } if("resizable".equals(name)) { getRootPane().repaint(); } } else if("title".equals(name)) { repaint(); } else if("componentOrientation".equals(name)) { revalidate(); repaint(); } } } /** * WindowListener installed on the Window, updates the state as necessary. */ private class WindowHandler extends WindowAdapter { public void windowActivated(WindowEvent ev) { setActive(true); } public void windowDeactivated(WindowEvent ev) { setActive(false); } } class WindowMoveListener extends ComponentAdapter { public void componentMoved(ComponentEvent e) { if(getWindowDecorationStyle() == JRootPane.NONE) return; // paint the non-opaque upper edges Window w = getWindow(); if(!w.isShowing()) return; w.repaint(0, 0, w.getWidth(), 5); } public void componentResized(ComponentEvent e) { if(getWindowDecorationStyle() == JRootPane.NONE) return; // paint the non-opaque upper edges Window w = getWindow(); if(!w.isShowing()) return; w.repaint(0, 0, w.getWidth(), 5); } } }