1 /*
2  * @(#)Quaqua14TitlePane.java  2.1  2008-05-10
3  *
4  * Copyright (c) 2006-2008 Werner Randelshofer
5  * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
6  * All rights reserved.
7  *
8  * The copyright of this software is owned by Werner Randelshofer.
9  * You may not use, copy or modify this software, except in
10  * accordance with the license agreement you entered into with
11  * Werner Randelshofer. For details see accompanying license terms.
12  */
13 
14 package ch.randelshofer.quaqua;
15 
16 import ch.randelshofer.quaqua.color.TextureColor;
17 import java.awt.*;
18 import java.awt.event.*;
19 import java.beans.*;
20 import javax.swing.*;
21 import javax.swing.border.*;
22 import javax.swing.plaf.*;
23 import javax.swing.plaf.basic.*;
24 import java.awt.geom.*;
25 
26 /**
27  * Quaqua14TitlePane.
28  *
29  * @author  Werner Randelshofer
30  * @version 2.1 2008-05-10 Added support for property "Window.documentModified".
31  * <br>2.0.1 2008-04-08 Changed ... to … .
32  * <br>2.0 2007-11-01 Rewritten for Leopard support.
33  * <br>1.0.1 2006-06-11 Minor tweaking.
34  * <br>1.0 February 12, 2006 Created.
35  */
36 public class Quaqua14TitlePane extends JComponent {
37     private static final Border handyEmptyBorder = new EmptyBorder(0,0,0,0);
38     private static final int IMAGE_HEIGHT = 16;
39     private static final int IMAGE_WIDTH = 16;
40 
41     /**
42      * PropertyChangeListener added to the Window.
43      */
44     private PropertyChangeListener windowPropertyListener;
45     /**
46      * PropertyChangeListener added to the JRootPane.
47      */
48     private PropertyChangeListener rootPropertyListener;
49 
50     /**
51      * JMenuBar, typically renders the system menu items.
52      */
53     private JMenuBar menuBar;
54     /**
55      * Action used to close the Window.
56      */
57     private Action closeAction;
58 
59     /**
60      * Action used to iconify the Frame.
61      */
62     private Action iconifyAction;
63 
64     /**
65      * Action to restore the Frame size.
66      */
67     private Action restoreAction;
68 
69     /**
70      * Action to restore the Frame size.
71      */
72     private Action maximizeAction;
73 
74     /**
75      * Button used to maximize or restore the Frame.
76      */
77     private JButton toggleButton;
78 
79     /**
80      * Button used to maximize or restore the Frame.
81      */
82     private JButton iconifyButton;
83 
84     /**
85      * Button used to maximize or restore the Frame.
86      */
87     private JButton closeButton;
88 
89     /**
90      * Icon used for toggleButton when window is normal size.
91      */
92     private Icon maximizeIcon;
93 
94     /**
95      * Icon used for toggleButton when window is maximized.
96      */
97     private Icon minimizeIcon;
98 
99     /**
100      * Listens for changes in the state of the Window listener to update
101      * the state of the widgets.
102      */
103     private WindowListener windowListener;
104 
105     /**
106      * Window we're currently in.
107      */
108     private Window window;
109 
110     /**
111      * JRootPane rendering for.
112      */
113     private JRootPane rootPane;
114 
115     /**
116      * Room remaining in title for bumps.
117      */
118     private int buttonsWidth;
119 
120     /**
121      * Buffered Frame.state property. As state isn't bound, this is kept
122      * to determine when to avoid updating widgets.
123      */
124     private int state;
125 
126     /**
127      * Quaqua14RootPaneUI that created us.
128      */
129     private Quaqua14RootPaneUI rootPaneUI;
130 
131 
132     // Colors
133     private Color inactiveBackground = UIManager.getColor("inactiveCaption");
134     private Color inactiveForeground = UIManager.getColor("inactiveCaptionText");
135     private Color inactiveShadow = new Color(0xb3b3b3);
136     private Color activeBackground = null;
137     private Color activeForeground = null;
138     private Color activeShadow = null;
139 
140     /* Components used for "arming" the window buttons.
141      */
142     private JComponent armer1, armer2;
143 
Quaqua14TitlePane(JRootPane root, Quaqua14RootPaneUI ui)144     public Quaqua14TitlePane(JRootPane root, Quaqua14RootPaneUI ui) {
145         this.rootPane = root;
146         rootPaneUI = ui;
147 
148         state = -1;
149 
150         rootPropertyListener = createRootPropertyChangeListener();
151         rootPane.addPropertyChangeListener(rootPropertyListener);
152 
153         installSubcomponents();
154         updateIconsAndTextures();
155         installDefaults();
156         setLayout(createLayout());
157     }
158 
159     /**
160      * Uninstalls the necessary state.
161      */
uninstall()162     private void uninstall() {
163         uninstallListeners();
164         window = null;
165         removeAll();
166     }
167 
168     /**
169      * Installs the necessary listeners.
170      */
installListeners()171     private void installListeners() {
172         if (window != null) {
173             windowListener = createWindowListener();
174             window.addWindowListener(windowListener);
175             windowPropertyListener = createWindowPropertyChangeListener();
176             window.addPropertyChangeListener(windowPropertyListener);
177         }
178     }
179 
180     /**
181      * Uninstalls the necessary listeners.
182      */
uninstallListeners()183     private void uninstallListeners() {
184         if (window != null) {
185             window.removeWindowListener(windowListener);
186             window.removePropertyChangeListener(windowPropertyListener);
187         }
188     }
189 
190     /**
191      * Returns the <code>WindowListener</code> to add to the
192      * <code>Window</code>.
193      */
createWindowListener()194     private WindowListener createWindowListener() {
195         return new WindowHandler();
196     }
197 
198     /**
199      * Returns the <code>PropertyChangeListener</code> to install on
200      * the <code>Window</code>.
201      */
createWindowPropertyChangeListener()202     private PropertyChangeListener createWindowPropertyChangeListener() {
203         return new WindowPropertyHandler();
204     }
205     /**
206      * Returns the <code>PropertyChangeListener</code> to install on
207      * the <code>Window</code>.
208      */
createRootPropertyChangeListener()209     private PropertyChangeListener createRootPropertyChangeListener() {
210         return new RootPropertyHandler();
211     }
212 
213     /**
214      * Returns the <code>JRootPane</code> this was created for.
215      */
getRootPane()216     public JRootPane getRootPane() {
217         return rootPane;
218     }
219 
220     /**
221      * Returns the decoration style of the <code>JRootPane</code>.
222      */
getWindowDecorationStyle()223     private int getWindowDecorationStyle() {
224         return getRootPane().getWindowDecorationStyle();
225     }
226 
addNotify()227     public void addNotify() {
228         super.addNotify();
229 
230         uninstallListeners();
231 
232         window = SwingUtilities.getWindowAncestor(this);
233         if (window != null) {
234             if (window instanceof Frame) {
235                 setState(((Frame)window).getExtendedState());
236             } else {
237                 setState(0);
238             }
239             setActive(window.isActive());
240             installListeners();
241         }
242     }
243 
removeNotify()244     public void removeNotify() {
245         super.removeNotify();
246 
247         uninstallListeners();
248         window = null;
249     }
250 
251     /**
252      * Adds any sub-Components contained in the <code>Quaqua14TitlePane</code>.
253      */
installSubcomponents()254     private void installSubcomponents() {
255         //if (getWindowDecorationStyle() == JRootPane.FRAME) {
256         createActions();
257         createButtons();
258         add(iconifyButton);
259         add(toggleButton);
260         add(closeButton);
261         //}
262     }
263 
264     /**
265      * Installs the fonts and necessary properties on the Quaqua14TitlePane.
266      */
installDefaults()267     private void installDefaults() {
268         // We get the font from the root pane.
269         //setFont(UIManager.getFont("InternalFrame.titleFont", getLocale()));
270 
271         // make title pane translucent
272     }
273 
274     /**
275      * Uninstalls any previously installed UI values.
276      */
uninstallDefaults()277     private void uninstallDefaults() {
278     }
279 
280     /**
281      * Closes the Window.
282      */
close()283     private void close() {
284         Window window = getWindow();
285 
286         if (window != null) {
287             window.dispatchEvent(new WindowEvent(
288                     window, WindowEvent.WINDOW_CLOSING));
289         }
290     }
291 
292     /**
293      * Iconifies the Frame.
294      */
iconify()295     private void iconify() {
296         Frame frame = getFrame();
297         if (frame != null) {
298             frame.setExtendedState(state | Frame.ICONIFIED);
299         }
300     }
301 
302     /**
303      * Maximizes the Frame.
304      */
maximize()305     private void maximize() {
306         Frame frame = getFrame();
307         if (frame != null) {
308             frame.setExtendedState(state | Frame.MAXIMIZED_BOTH);
309         }
310     }
311 
312     /**
313      * Restores the Frame size.
314      */
restore()315     private void restore() {
316         Frame frame = getFrame();
317 
318         if (frame == null) {
319             return;
320         }
321 
322         if ((state & Frame.ICONIFIED) != 0) {
323             frame.setExtendedState(state & ~Frame.ICONIFIED);
324         } else {
325             frame.setExtendedState(state & ~Frame.MAXIMIZED_BOTH);
326         }
327     }
328 
329     /**
330      * Create the <code>Action</code>s that get associated with the
331      * buttons and menu items.
332      */
createActions()333     private void createActions() {
334         closeAction = new CloseAction();
335         iconifyAction = new IconifyAction();
336         restoreAction = new RestoreAction();
337         maximizeAction = new MaximizeAction();
338     }
339 
340     /**
341      * Returns a <code>JButton</code> appropriate for placement on the
342      * TitlePane.
343      */
createTitleButton()344     private JButton createTitleButton() {
345         JButton button = new JButton();
346 
347         button.setFocusPainted(false);
348         button.setFocusable(false);
349         button.setOpaque(false);
350         return button;
351     }
352 
353     /**
354      * Creates the Buttons that will be placed on the TitlePane.
355      */
createButtons()356     private void createButtons() {
357         closeButton = createTitleButton();
358         closeButton.setAction(closeAction);
359         closeButton.setText(null);
360         closeButton.putClientProperty("paintActive", Boolean.TRUE);
361         closeButton.setBorder(handyEmptyBorder);
362         closeButton.getAccessibleContext().setAccessibleName("Close");
363 
364 
365         iconifyButton = createTitleButton();
366         iconifyButton.setAction(iconifyAction);
367         iconifyButton.setText(null);
368         iconifyButton.putClientProperty("paintActive", Boolean.TRUE);
369         iconifyButton.setBorder(handyEmptyBorder);
370         iconifyButton.getAccessibleContext().setAccessibleName("Iconify");
371 
372         toggleButton = createTitleButton();
373         toggleButton.setAction(restoreAction);
374         toggleButton.putClientProperty("paintActive", Boolean.TRUE);
375         toggleButton.setBorder(handyEmptyBorder);
376         toggleButton.getAccessibleContext().setAccessibleName("Maximize");
377 
378         armer1 = new JPanel(null);
379         armer1.setOpaque(false);
380         armer2 = new JPanel(null);
381         armer2.setOpaque(false);
382 
383         MouseListener buttonArmer = new MouseAdapter() {
384             public void mouseEntered(MouseEvent evt) {
385                 closeButton.putClientProperty("paintRollover", Boolean.TRUE);
386                 iconifyButton.putClientProperty("paintRollover", Boolean.TRUE);
387                 toggleButton.putClientProperty("paintRollover", Boolean.TRUE);
388                 closeButton.repaint();
389                 iconifyButton.repaint();
390                 toggleButton.repaint();
391             }
392             public void mouseExited(MouseEvent e) {
393                 closeButton.putClientProperty("paintRollover", Boolean.FALSE);
394                 iconifyButton.putClientProperty("paintRollover", Boolean.FALSE);
395                 toggleButton.putClientProperty("paintRollover", Boolean.FALSE);
396                 closeButton.repaint();
397                 iconifyButton.repaint();
398                 toggleButton.repaint();
399             }
400         };
401         closeButton.addMouseListener(buttonArmer);
402         iconifyButton.addMouseListener(buttonArmer);
403         toggleButton.addMouseListener(buttonArmer);
404         armer1.addMouseListener(buttonArmer);
405         armer2.addMouseListener(buttonArmer);
406     }
407 
408     /**
409      * Update all textures and icons for the new font used by the JRootPane.
410      */
updateIconsAndTextures()411     private void updateIconsAndTextures() {
412         int size = getFont().getSize();
413         boolean isVertical = isVertical();
414 
415         Icon closeIcon, iconifyIcon;
416         String prefix = "InternalFrame.";
417         String suffix;
418         if (size <= 9) {
419             suffix = ".mini";
420         } else if (size <= 11) {
421             suffix = ".small";
422         } else {
423             suffix = "";
424         }
425         maximizeIcon = UIManager.getIcon(prefix+"maximizeIcon"+suffix);
426         minimizeIcon = UIManager.getIcon(prefix+"maximizeIcon"+suffix);
427         closeIcon = UIManager.getIcon(prefix+"closeIcon"+suffix);
428         iconifyIcon = UIManager.getIcon(prefix+"iconifyIcon"+suffix);
429         activeBackground = UIManager.getColor(prefix+((isVertical) ? "vTitlePaneBackground" : "titlePaneBackground")+suffix);
430         activeForeground = UIManager.getColor(prefix+"titlePaneForeground"+suffix);
431         activeShadow = UIManager.getColor(prefix+"titlePaneShadow"+suffix);
432 
433 
434         closeButton.setIcon(closeIcon);
435         iconifyButton.setIcon(iconifyIcon);
436         toggleButton.setIcon(maximizeIcon);
437     }
438 
439 
440     /**
441      * Returns the <code>LayoutManager</code> that should be installed on
442      * the <code>Quaqua14TitlePane</code>.
443      */
createLayout()444     private LayoutManager createLayout() {
445         return new TitlePaneLayout();
446     }
447 
448     /**
449      * Updates state dependant upon the Window's active state.
450      */
setActive(boolean isActive)451     private void setActive(boolean isActive) {
452         //  if (getWindowDecorationStyle() == JRootPane.FRAME) {
453         Boolean activeB = isActive ? Boolean.TRUE : Boolean.FALSE;
454 
455         iconifyButton.putClientProperty("paintActive", activeB);
456         closeButton.putClientProperty("paintActive", activeB);
457         toggleButton.putClientProperty("paintActive", activeB);
458         // }
459         // Repaint the whole thing as the Borders that are used have
460         // different colors for active vs inactive
461         getRootPane().repaint();
462     }
463 
464     /**
465      * Sets the state of the Window.
466      */
setState(int state)467     private void setState(int state) {
468         setState(state, false);
469     }
470 
471     /**
472      * Sets the state of the window. If <code>updateRegardless</code> is
473      * true and the state has not changed, this will update anyway.
474      */
setState(int state, boolean updateRegardless)475     private void setState(int state, boolean updateRegardless) {
476         Window w = getWindow();
477 
478         // if (w != null && getWindowDecorationStyle() == JRootPane.FRAME) {
479         if (this.state == state && !updateRegardless) {
480             return;
481         }
482         boolean isPalette = (Boolean) rootPane.getClientProperty("Quaqua.RootPane.isPalette")  == Boolean.TRUE;
483         Frame frame = getFrame();
484 
485         if (frame != null  && ! isPalette) {
486             JRootPane rootPane = getRootPane();
487 
488             if (((state & Frame.MAXIMIZED_BOTH) != 0) &&
489                     (rootPane.getBorder() == null ||
490                     (rootPane.getBorder() instanceof UIResource)) &&
491                     frame.isShowing()) {
492                 rootPane.setBorder(null);
493             } else if ((state & Frame.MAXIMIZED_BOTH) == 0) {
494                 // This is a croak, if state becomes bound, this can
495                 // be nuked.
496                 rootPaneUI.installBorder(rootPane);
497             }
498             if (armer1.getParent() == null) {
499                 add(armer1);
500             }
501             if (armer2.getParent() == null) {
502                 add(armer2);
503             }
504             //if (frame.isResizable()) {
505                 if ((state & Frame.MAXIMIZED_BOTH) != 0) {
506                     updateToggleButton(restoreAction, minimizeIcon);
507                     maximizeAction.setEnabled(false);
508                     restoreAction.setEnabled(true);
509                 } else {
510                     updateToggleButton(maximizeAction, maximizeIcon);
511                     maximizeAction.setEnabled(true);
512                     restoreAction.setEnabled(false);
513                 }
514                 if (toggleButton.getParent() == null ||
515                         iconifyButton.getParent() == null) {
516                     add(toggleButton);
517                     add(iconifyButton);
518                     revalidate();
519                     repaint();
520                 }
521                 toggleButton.setText(null);
522                 toggleButton.setEnabled(frame.isResizable() && Toolkit.getDefaultToolkit().isFrameStateSupported(Frame.MAXIMIZED_BOTH));
523                 /*
524             } else {
525                 maximizeAction.setEnabled(false);
526                 restoreAction.setEnabled(false);
527                 if (toggleButton.getParent() != null) {
528                     remove(toggleButton);
529                     revalidate();
530                     repaint();
531                 }*/
532             //}
533         } else {
534             // Not contained in a Frame
535             maximizeAction.setEnabled(false);
536             restoreAction.setEnabled(false);
537             iconifyAction.setEnabled(false);
538             remove(armer1);
539             remove(armer2);
540             remove(toggleButton);
541             remove(iconifyButton);
542             revalidate();
543             repaint();
544         }
545         closeAction.setEnabled(true);
546         this.state = state;
547         //}
548     }
549 
550     /**
551      * Updates the toggle button to contain the Icon <code>icon</code>, and
552      * Action <code>action</code>.
553      */
updateToggleButton(Action action, Icon icon)554     private void updateToggleButton(Action action, Icon icon) {
555         toggleButton.setAction(action);
556         toggleButton.setIcon(icon);
557         toggleButton.setText(null);
558         toggleButton.setEnabled(
559                 Toolkit.getDefaultToolkit().isFrameStateSupported(
560                     Frame.MAXIMIZED_BOTH));
561     }
562 
563     /**
564      * Returns the Frame rendering in. This will return null if the
565      * <code>JRootPane</code> is not contained in a <code>Frame</code>.
566      */
getFrame()567     private Frame getFrame() {
568         Window window = getWindow();
569 
570         if (window instanceof Frame) {
571             return (Frame)window;
572         }
573         return null;
574     }
575 
576     /**
577      * Returns the Dialog rendering in. This will return null if the
578      * <code>JRootPane</code> is not contained in a <code>Frame</code>.
579      */
getDialog()580     private Dialog getDialog() {
581         Window window = getWindow();
582 
583         if (window instanceof Dialog) {
584             return (Dialog) window;
585         }
586         return null;
587     }
588 
getFont()589     public Font getFont() {
590         return rootPane.getFont();
591         /*
592         Window window = getWindow();
593         return (window != null) ? window.getFont() : rootPane.getFont();
594          */
595     }
596 
597     /**
598      * Returns the <code>Window</code> the <code>JRootPane</code> is
599      * contained in. This will return null if there is no parent ancestor
600      * of the <code>JRootPane</code>.
601      */
getWindow()602     private Window getWindow() {
603         return window;
604     }
605 
606     /**
607      * Returns the String to display as the title.
608      */
getTitle()609     private String getTitle() {
610         Window w = getWindow();
611 
612         if (w instanceof Frame) {
613             return ((Frame)w).getTitle();
614         } else if (w instanceof Dialog) {
615             return ((Dialog)w).getTitle();
616         }
617         return null;
618     }
619 
620     /**
621      * Renders the TitlePane.
622      */
paintComponent(Graphics gr)623     public void paintComponent(Graphics gr)  {
624         Graphics2D g = (Graphics2D) gr;
625 
626 
627         Object oldHints = QuaquaUtilities.beginGraphics(g);
628 
629         // As state isn't bound, we need a convenience place to check
630         // if it has changed. Changing the state typically changes the
631         if (getFrame() != null) {
632             setState(getFrame().getExtendedState());
633         }
634         Window window = getWindow();
635 
636         JRootPane rootPane = getRootPane();
637         boolean isVertical = isVertical();
638 
639         boolean leftToRight = (window == null) ?
640             rootPane.getComponentOrientation().isLeftToRight() :
641             window.getComponentOrientation().isLeftToRight();
642 
643         boolean isPalette = (Boolean) rootPane.getClientProperty("Quaqua.RootPane.isPalette")  == Boolean.TRUE;
644         boolean isSelected = (window == null) ? false : window.isActive();
645         isSelected |= isPalette;
646 
647         int width = getWidth();
648         int height = getHeight();
649 
650         Color background;
651         Color foreground;
652         Color darkShadow;
653         Border titleBorder;
654 
655         int fontSize = getFont().getSize();
656         String suffix = (fontSize <= 9) ? ".mini" :
657             (fontSize <= 11) ? ".small" : "";
658         if (isVertical()) {
659             suffix = ".vertical"+suffix;
660         }
661 
662         if (isSelected) {
663             background = activeBackground;
664             foreground = activeForeground;
665             darkShadow = activeShadow;
666             titleBorder = ((Border[]) UIManager.get("Frame.titlePaneBorders"+suffix))[0];
667         } else {
668             background = inactiveBackground;
669             foreground = inactiveForeground;
670             darkShadow = inactiveShadow;
671             titleBorder = ((Border[]) UIManager.get("Frame.titlePaneBorders"+suffix))[1];
672         }
673 
674         if (/*isPalette ||*/ titleBorder == null) {
675             g.setPaint(TextureColor.getPaint(background, window));
676             g.fillRect(0, 0, width, height);
677             g.setPaint(TextureColor.getPaint(darkShadow, window));
678             if (isVertical) {
679                 g.drawLine( width - 1, 0, width - 1, height);
680             } else {
681                 g.drawLine( 0, height - 1, width, height -1);
682             }
683         } else {
684             // Make background fully transparent (0 alpha)
685             g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
686             g.fillRect(0,0,getWidth(),getHeight());
687             g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
688             titleBorder.paintBorder(this, g, 0, 0, width, height);
689         }
690 
691         int xOffset = leftToRight ? 5 : width - 5;
692 
693         // if (getWindowDecorationStyle() == JRootPane.FRAME) {
694         xOffset += leftToRight ? IMAGE_WIDTH + 5 : - IMAGE_WIDTH - 5;
695         // }
696 
697         String theTitle = getTitle();
698 
699         if (theTitle != null) {
700             Font f = getFont();
701             FontMetrics fm = g.getFontMetrics();
702 
703             int yOffset = ( (height - fm.getHeight() ) / 2 ) + fm.getAscent();
704 
705             Rectangle rect = new Rectangle(0, 0, 0, 0);
706             if (iconifyButton != null && iconifyButton.getParent() != null) {
707                 rect = iconifyButton.getBounds();
708             }
709             Rectangle toggleRect = new Rectangle(0, 0, 0, 0);
710             if (toggleButton != null && toggleButton.getParent() != null) {
711                 toggleRect = toggleButton.getBounds();
712             }
713             Rectangle closeRect = new Rectangle(0, 0, 0, 0);
714             if (closeButton != null && closeButton.getParent() != null) {
715                 closeRect = closeButton.getBounds();
716             }
717             int titleW;
718 
719             if (isVertical) {
720                 titleW = height - Math.max(toggleRect.y+toggleRect.height,closeRect.y+closeRect.height);
721                 theTitle = clippedText(theTitle, fm, titleW);
722                 xOffset = ( (width - fm.getHeight() ) / 2 ) + fm.getAscent();
723 
724                 int titleLength = SwingUtilities.computeStringWidth(fm, theTitle);
725                 AffineTransform at = g.getTransform();
726                 int y = Math.max(Math.max(closeRect.y + closeRect.height,toggleRect.y + toggleRect.height) + 5, (getHeight() - titleLength) / 2) + titleLength;
727                 g.rotate((float) (Math.PI / -2d), xOffset, y);
728                 if (UIManager.getColor("Frame.titlePaneEmbossForeground") != null) {
729                     g.setPaint(UIManager.getColor("Frame.titlePaneEmbossForeground"));
730                     g.drawString(theTitle, xOffset, y + 1);
731                 }
732                 g.setPaint(TextureColor.getPaint(foreground, window));
733                 g.drawString(theTitle, xOffset, y);
734                 g.setTransform(at);
735             } else {
736                 if( leftToRight ) {
737                     if (rect.x == 0) {
738                         rect.x = window.getWidth() - window.getInsets().right-2;
739                     }
740                     titleW = getWidth() - Math.max(toggleRect.x+toggleRect.width,closeRect.x+closeRect.width) - 10;
741                     theTitle = clippedText(theTitle, fm, titleW);
742                 } else {
743                     titleW = getWidth() - Math.max(toggleRect.x+toggleRect.width,closeRect.x+closeRect.width) - 10;
744                     theTitle = clippedText(theTitle, fm, titleW);
745                     xOffset -= SwingUtilities.computeStringWidth(fm, theTitle);
746                 }
747                 int titleLength = SwingUtilities.computeStringWidth(fm, theTitle);
748                 if (UIManager.getColor("Frame.titlePaneEmbossForeground") != null) {
749                     g.setPaint(UIManager.getColor("Frame.titlePaneEmbossForeground"));
750                     g.drawString( theTitle, Math.max(Math.max(closeRect.x + closeRect.width, toggleRect.x + toggleRect.width) + 5, (getWidth() - titleLength) / 2), yOffset + 1);
751                 }
752                 g.setPaint(TextureColor.getPaint(foreground, window));
753                 g.drawString( theTitle, Math.max(Math.max(closeRect.x + closeRect.width, toggleRect.x + toggleRect.width) + 5, (getWidth() - titleLength) / 2), yOffset );
754                 xOffset += leftToRight ? titleLength + 5  : -5;
755             }
756         }
757         /*
758         Composite savedComposite = g.getComposite();
759         g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC));
760         g.setColor(new Color(0x00ff00,true));
761         g.fill(new Polygon(new int[] {0,10,0}, new int[] {0,0,10}, 3));
762         g.setComposite(savedComposite);
763          */
764         QuaquaUtilities.endGraphics(g, oldHints);
765     }
766 
767     /**
768      * Convenience method to clip the passed in text to the specified
769      * size.
770      */
clippedText(String text, FontMetrics fm, int availTextWidth)771     private String clippedText(String text, FontMetrics fm,
772             int availTextWidth) {
773         if ((text == null) || (text.equals("")))  {
774             return "";
775         }
776         int textWidth = SwingUtilities.computeStringWidth(fm, text);
777         String clipString = "…";
778         if (textWidth > availTextWidth) {
779             int totalWidth = SwingUtilities.computeStringWidth(fm, clipString);
780             int nChars;
781             for(nChars = 0; nChars < text.length(); nChars++) {
782                 totalWidth += fm.charWidth(text.charAt(nChars));
783                 if (totalWidth > availTextWidth) {
784                     break;
785                 }
786             }
787             text = text.substring(0, nChars) + clipString;
788         }
789         return text;
790     }
791 
isVertical()792     private boolean isVertical() {
793         return rootPane.getClientProperty("Quaqua.RootPane.isVertical") == Boolean.TRUE;
794     }
795 
796     /**
797      * Actions used to <code>close</code> the <code>Window</code>.
798      */
799     private class CloseAction extends AbstractAction {
CloseAction()800         public CloseAction() {
801             super(UIManager.getString("Quaqua14TitlePane.closeTitle",
802                     getLocale()));
803         }
804 
actionPerformed(ActionEvent e)805         public void actionPerformed(ActionEvent e) {
806             close();
807         }
808     }
809 
810 
811     /**
812      * Actions used to <code>iconfiy</code> the <code>Frame</code>.
813      */
814     private class IconifyAction extends AbstractAction {
IconifyAction()815         public IconifyAction() {
816             super(UIManager.getString("Quaqua14TitlePane.iconifyTitle",
817                     getLocale()));
818         }
819 
actionPerformed(ActionEvent e)820         public void actionPerformed(ActionEvent e) {
821             iconify();
822         }
823     }
824 
825 
826     /**
827      * Actions used to <code>restore</code> the <code>Frame</code>.
828      */
829     private class RestoreAction extends AbstractAction {
RestoreAction()830         public RestoreAction() {
831             super(UIManager.getString
832                     ("Quaqua14TitlePane.restoreTitle", getLocale()));
833         }
834 
actionPerformed(ActionEvent e)835         public void actionPerformed(ActionEvent e) {
836             restore();
837         }
838     }
839 
840 
841     /**
842      * Actions used to <code>restore</code> the <code>Frame</code>.
843      */
844     private class MaximizeAction extends AbstractAction {
MaximizeAction()845         public MaximizeAction() {
846             super(UIManager.getString("Quaqua14TitlePane.maximizeTitle",
847                     getLocale()));
848         }
849 
actionPerformed(ActionEvent e)850         public void actionPerformed(ActionEvent e) {
851             maximize();
852         }
853     }
854 
855 
856     /**
857      * Class responsible for drawing the system menu. Looks up the
858      * image to draw from the Frame associated with the
859      * <code>JRootPane</code>.
860      */
861     private class SystemMenuBar extends JMenuBar {
paint(Graphics gr)862         public void paint(Graphics gr) {
863             Graphics2D g = (Graphics2D) gr;
864             Frame frame = getFrame();
865 
866             if (isOpaque()) {
867                 g.setPaint(TextureColor.getPaint(getBackground(), this));
868                 g.fillRect(0, 0, getWidth(), getHeight());
869             }
870             Image image = (frame != null) ? frame.getIconImage() : null;
871 
872             if (image != null) {
873                 g.drawImage(image, 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, null);
874             } else {
875                 Icon icon = UIManager.getIcon("InternalFrame.icon");
876 
877                 if (icon != null) {
878                     icon.paintIcon(this, g, 0, 0);
879                 }
880             }
881         }
getMinimumSize()882         public Dimension getMinimumSize() {
883             return getPreferredSize();
884         }
getPreferredSize()885         public Dimension getPreferredSize() {
886             Dimension size = super.getPreferredSize();
887 
888             return new Dimension(Math.max(IMAGE_WIDTH, size.width),
889                     Math.max(size.height, IMAGE_HEIGHT));
890         }
891     }
892 
893     /**
894      * This inner class is marked &quot;public&quot; due to a compiler bug.
895      * This class should be treated as a &quot;protected&quot; inner class.
896      * Instantiate it only within subclasses of <Foo>.
897      */
898     private class TitlePaneLayout implements LayoutManager {
addLayoutComponent(String name, Component c)899         public void addLayoutComponent(String name, Component c) {}
removeLayoutComponent(Component c)900         public void removeLayoutComponent(Component c) {}
preferredLayoutSize(Container c)901         public Dimension preferredLayoutSize(Container c)  {
902             int height = computeHeight();
903             return new Dimension(height, height);
904         }
905 
minimumLayoutSize(Container c)906         public Dimension minimumLayoutSize(Container c) {
907             return preferredLayoutSize(c);
908         }
909 
computeHeight()910         private int computeHeight() {
911             int fontSize = getFont().getSize();
912             if (fontSize <= 9) {
913                 return 12;
914             } else if (fontSize <= 11) {
915                 return 16;
916             } else {
917                 return 22;
918             }
919             /*
920             FontMetrics fm
921             = Toolkit.getDefaultToolkit().getFontMetrics(getFont());
922             int fontHeight = fm.getHeight();
923 
924 
925             fontHeight += 2;
926             int iconHeight = 0;
927             // if (getWindowDecorationStyle() == JRootPane.FRAME) {
928             iconHeight = IMAGE_HEIGHT;
929             // }
930 
931             int finalHeight = Math.max( fontHeight, iconHeight );
932             return finalHeight;
933              */
934         }
935 
936 
layoutContainer(Container c)937         public void layoutContainer(Container c) {
938             JRootPane rootPane = getRootPane();
939             boolean isVertical = isVertical();
940             if (isVertical) {
941                 layoutVerticalContainer(c);
942             } else {
943                 layoutHorizontalContainer(c);
944             }
945         }
layoutHorizontalContainer(Container c)946         public void layoutHorizontalContainer(Container c) {
947             int fontSize = getFont().getSize();
948             boolean leftToRight;
949             if (window == null) {
950                 leftToRight = !getRootPane().getComponentOrientation().isLeftToRight();
951             } else {
952                 leftToRight = !window.getComponentOrientation().isLeftToRight();
953             }
954 
955             int w = getWidth();
956             int x;
957             int y;
958             int spacing;
959             int buttonHeight;
960             int buttonWidth;
961 
962             if (fontSize <= 9) {
963                 y = 1;
964             } else if (fontSize <= 11) {
965                 y = 1;
966             } else {
967                 y = 4;
968             }
969 
970             if (closeButton != null && closeButton.getIcon() != null) {
971                 buttonHeight = closeButton.getIcon().getIconHeight();
972                 buttonWidth = closeButton.getIcon().getIconWidth();
973             } else {
974                 buttonHeight = IMAGE_HEIGHT;
975                 buttonWidth = IMAGE_WIDTH;
976             }
977 
978             // assumes all buttons have the same dimensions
979             // these dimensions include the borders
980 
981             x = leftToRight ? w : 0;
982 
983             spacing = 5;
984             x = leftToRight ? spacing : w - buttonWidth - spacing;
985             if (menuBar != null) {
986                 menuBar.setBounds(x, y, buttonWidth, buttonHeight);
987             }
988 
989             x = leftToRight ? w : 0;
990             spacing = (fontSize <= 11) ? 5 : 7;
991             x += leftToRight ? -spacing -buttonWidth : spacing;
992 
993             if (closeButton != null) {
994                 closeButton.setBounds(x, y, buttonWidth, buttonHeight);
995             }
996 
997             if( !leftToRight ) x += buttonWidth;
998 
999             if (iconifyButton != null &&
1000                     iconifyButton.getParent() != null) {
1001                 spacing = 5;
1002                 armer1.setBounds(x, y, spacing, buttonHeight);
1003                 x += leftToRight ? -spacing -buttonWidth : spacing;
1004                 iconifyButton.setBounds(x, y, buttonWidth, buttonHeight);
1005                 if (!leftToRight) {
1006                     x += buttonWidth;
1007                 }
1008             } else {
1009                 armer1.setBounds(0,0,0,0);
1010             }
1011 
1012             if (Toolkit.getDefaultToolkit().isFrameStateSupported(
1013                     Frame.MAXIMIZED_BOTH)) {
1014                 if (toggleButton.getParent() != null) {
1015                     spacing = 5;
1016                     armer2.setBounds(x, y, spacing, buttonHeight);
1017                     x += leftToRight ? -spacing -buttonWidth : spacing;
1018                     toggleButton.setBounds(x, y, buttonWidth, buttonHeight);
1019                     if (!leftToRight) {
1020                         x += buttonWidth;
1021                     }
1022                 }
1023             } else {
1024                 armer2.setBounds(0,0,0,0);
1025             }
1026 
1027             buttonsWidth = leftToRight ? w - x : x;
1028         }
1029     }
layoutVerticalContainer(Container c)1030     public void layoutVerticalContainer(Container c) {
1031         int h = getHeight();
1032         int x;
1033         int y;
1034         int spacing;
1035         int buttonHeight;
1036         int buttonWidth;
1037 
1038         int fontSize = getFont().getSize();
1039         if (fontSize <= 9) {
1040             x = 2;
1041         } else if (fontSize <= 11) {
1042             x = 1;
1043         } else {
1044             x = 2;
1045         }
1046 
1047         if (closeButton != null && closeButton.getIcon() != null) {
1048             buttonHeight = closeButton.getIcon().getIconHeight();
1049             buttonWidth = closeButton.getIcon().getIconWidth();
1050         } else {
1051             buttonHeight = IMAGE_HEIGHT;
1052             buttonWidth = IMAGE_WIDTH;
1053         }
1054 
1055         // assumes all buttons have the same dimensions
1056         // these dimensions include the borders
1057 
1058         spacing = 5;
1059         y = spacing;
1060         if (menuBar != null) {
1061             menuBar.setBounds(x, y, buttonWidth, buttonHeight);
1062         }
1063 
1064         if (fontSize <= 9) {
1065             y = 0;
1066         } else if (fontSize <= 11) {
1067             y = 0;
1068         } else {
1069             y = 4;
1070         }
1071         spacing = 2;
1072         y += spacing;
1073 
1074         if (closeButton != null) {
1075             closeButton.setBounds(x, y, buttonWidth, buttonHeight);
1076         }
1077 
1078         y += buttonHeight;
1079 
1080         if (iconifyButton != null &&
1081                 iconifyButton.getParent() != null) {
1082             spacing = 5;
1083             armer1.setBounds(x, y, buttonWidth, spacing);
1084             y += spacing;
1085             iconifyButton.setBounds(x, y, buttonWidth, buttonHeight);
1086             y += buttonHeight;
1087         } else {
1088             armer1.setBounds(0,0,0,0);
1089         }
1090 
1091         if (Toolkit.getDefaultToolkit().isFrameStateSupported(
1092                 Frame.MAXIMIZED_BOTH)) {
1093             if (toggleButton.getParent() != null) {
1094                 spacing = 5;
1095                 armer2.setBounds(x, y, buttonWidth, spacing);
1096                 y += spacing;
1097                 toggleButton.setBounds(x, y, buttonWidth, buttonHeight);
1098                 y += buttonHeight;
1099             }
1100         } else {
1101             armer2.setBounds(0,0,0,0);
1102         }
1103 
1104         buttonsWidth = x + buttonWidth;
1105 
1106     }
1107 
1108 
1109 
1110     /**
1111      * PropertyChangeListener installed on the JRootPane. Updates the necessary
1112      * state as the state of the JRootPane changes.
1113      */
1114     private class RootPropertyHandler implements PropertyChangeListener {
propertyChange(PropertyChangeEvent pce)1115         public void propertyChange(PropertyChangeEvent pce) {
1116             String name = pce.getPropertyName();
1117             if ("Quaqua.RootPane.isVertical".equals(name)) {
1118                 updateIconsAndTextures();
1119                 revalidate();
1120                 repaint();
1121             } else if ("Window.documentModified".equals(name) || "windowModified".equals(name)) {
1122                 closeButton.setSelected(((Boolean) pce.getNewValue()).booleanValue());
1123             } else if ("font".equals(name)) {
1124                 updateIconsAndTextures();
1125                 revalidate();
1126                 repaint();
1127             }
1128 
1129         }
1130     }
1131     /**
1132      * PropertyChangeListener installed on the Window. Updates the necessary
1133      * state as the state of the Window changes.
1134      */
1135     private class WindowPropertyHandler implements PropertyChangeListener {
propertyChange(PropertyChangeEvent pce)1136         public void propertyChange(PropertyChangeEvent pce) {
1137             String name = pce.getPropertyName();
1138             // Frame.state isn't currently bound.
1139             if ("resizable".equals(name) || "state".equals(name)) {
1140                 Frame frame = getFrame();
1141                 if (frame != null) {
1142                     setState(frame.getExtendedState(), true);
1143                 }
1144                 if ("resizable".equals(name)) {
1145                     getRootPane().repaint();
1146                 }
1147             } else if ("title".equals(name)) {
1148                 repaint();
1149             } else if ("Quaqua.RootPane.isVertical".equals(name)) {
1150                 updateIconsAndTextures();
1151                 revalidate();
1152                 repaint();
1153             } else if ("componentOrientation".equals(name)) {
1154                 revalidate();
1155                 repaint();
1156        } else if (name.equals("JComponent.sizeVariant")) {
1157             QuaquaUtilities.applySizeVariant(rootPane);
1158             }
1159         }
1160     }
1161 
1162 
1163     /**
1164      * WindowListener installed on the Window, updates the state as necessary.
1165      */
1166     private class WindowHandler extends WindowAdapter {
windowActivated(WindowEvent ev)1167         public void windowActivated(WindowEvent ev) {
1168             setActive(true);
1169         }
1170 
windowDeactivated(WindowEvent ev)1171         public void windowDeactivated(WindowEvent ev) {
1172             setActive(false);
1173         }
1174     }
1175 }
1176