1 /*
2  * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package javax.swing.plaf.metal;
27 
28 import sun.swing.SwingUtilities2;
29 import java.awt.*;
30 import java.awt.event.*;
31 import javax.swing.*;
32 import javax.swing.border.*;
33 import javax.swing.event.InternalFrameEvent;
34 import java.util.EventListener;
35 import java.beans.PropertyChangeListener;
36 import java.beans.PropertyChangeEvent;
37 import javax.swing.plaf.basic.BasicInternalFrameTitlePane;
38 
39 
40 /**
41  * Class that manages a JLF title bar
42  * @author Steve Wilson
43  * @author Brian Beck
44  * @since 1.3
45  */
46 @SuppressWarnings("serial") // Superclass is not serializable across versions
47 public class MetalInternalFrameTitlePane  extends BasicInternalFrameTitlePane {
48 
49     /**
50      * The value {@code isPalette}
51      */
52     protected boolean isPalette = false;
53 
54     /**
55      * The palette close icon.
56      */
57     protected Icon paletteCloseIcon;
58 
59     /**
60      * The height of the palette title.
61      */
62     protected int paletteTitleHeight;
63 
64     private static final Border handyEmptyBorder = new EmptyBorder(0,0,0,0);
65 
66     /**
67      * Key used to lookup Color from UIManager. If this is null,
68      * <code>getWindowTitleBackground</code> is used.
69      */
70     private String selectedBackgroundKey;
71     /**
72      * Key used to lookup Color from UIManager. If this is null,
73      * <code>getWindowTitleForeground</code> is used.
74      */
75     private String selectedForegroundKey;
76     /**
77      * Key used to lookup shadow color from UIManager. If this is null,
78      * <code>getPrimaryControlDarkShadow</code> is used.
79      */
80     private String selectedShadowKey;
81     /**
82      * Boolean indicating the state of the <code>JInternalFrame</code>s
83      * closable property at <code>updateUI</code> time.
84      */
85     private boolean wasClosable;
86 
87     int buttonsWidth = 0;
88 
89     MetalBumps activeBumps
90         = new MetalBumps( 0, 0,
91                           MetalLookAndFeel.getPrimaryControlHighlight(),
92                           MetalLookAndFeel.getPrimaryControlDarkShadow(),
93           (UIManager.get("InternalFrame.activeTitleGradient") != null) ? null :
94                           MetalLookAndFeel.getPrimaryControl() );
95     MetalBumps inactiveBumps
96         = new MetalBumps( 0, 0,
97                           MetalLookAndFeel.getControlHighlight(),
98                           MetalLookAndFeel.getControlDarkShadow(),
99         (UIManager.get("InternalFrame.inactiveTitleGradient") != null) ? null :
100                           MetalLookAndFeel.getControl() );
101     MetalBumps paletteBumps;
102 
103     private Color activeBumpsHighlight = MetalLookAndFeel.
104                              getPrimaryControlHighlight();
105     private Color activeBumpsShadow = MetalLookAndFeel.
106                              getPrimaryControlDarkShadow();
107 
108     /**
109      * Constructs a new instance of {@code MetalInternalFrameTitlePane}
110      *
111      * @param f an instance of {@code JInternalFrame}
112      */
MetalInternalFrameTitlePane(JInternalFrame f)113     public MetalInternalFrameTitlePane(JInternalFrame f) {
114         super( f );
115     }
116 
addNotify()117     public void addNotify() {
118         super.addNotify();
119         // This is done here instead of in installDefaults as I was worried
120         // that the BasicInternalFrameUI might not be fully initialized, and
121         // that if this resets the closable state the BasicInternalFrameUI
122         // Listeners that get notified might be in an odd/uninitialized state.
123         updateOptionPaneState();
124     }
125 
installDefaults()126     protected void installDefaults() {
127         super.installDefaults();
128         setFont( UIManager.getFont("InternalFrame.titleFont") );
129         paletteTitleHeight
130             = UIManager.getInt("InternalFrame.paletteTitleHeight");
131         paletteCloseIcon = UIManager.getIcon("InternalFrame.paletteCloseIcon");
132         wasClosable = frame.isClosable();
133         selectedForegroundKey = selectedBackgroundKey = null;
134         if (MetalLookAndFeel.usingOcean()) {
135             setOpaque(true);
136         }
137     }
138 
uninstallDefaults()139     protected void uninstallDefaults() {
140         super.uninstallDefaults();
141         if (wasClosable != frame.isClosable()) {
142             frame.setClosable(wasClosable);
143         }
144     }
145 
createButtons()146     protected void createButtons() {
147         super.createButtons();
148 
149         Boolean paintActive = frame.isSelected() ? Boolean.TRUE:Boolean.FALSE;
150         iconButton.putClientProperty("paintActive", paintActive);
151         iconButton.setBorder(handyEmptyBorder);
152 
153         maxButton.putClientProperty("paintActive", paintActive);
154         maxButton.setBorder(handyEmptyBorder);
155 
156         closeButton.putClientProperty("paintActive", paintActive);
157         closeButton.setBorder(handyEmptyBorder);
158 
159         // The palette close icon isn't opaque while the regular close icon is.
160         // This makes sure palette close buttons have the right background.
161         closeButton.setBackground(MetalLookAndFeel.getPrimaryControlShadow());
162 
163         if (MetalLookAndFeel.usingOcean()) {
164             iconButton.setContentAreaFilled(false);
165             maxButton.setContentAreaFilled(false);
166             closeButton.setContentAreaFilled(false);
167         }
168     }
169 
170     /**
171      * Override the parent's method to do nothing. Metal frames do not
172      * have system menus.
173      */
assembleSystemMenu()174     protected void assembleSystemMenu() {}
175 
176     /**
177      * Override the parent's method to do nothing. Metal frames do not
178      * have system menus.
179      */
addSystemMenuItems(JMenu systemMenu)180     protected void addSystemMenuItems(JMenu systemMenu) {}
181 
182     /**
183      * Override the parent's method to do nothing. Metal frames do not
184      * have system menus.
185      */
showSystemMenu()186     protected void showSystemMenu() {}
187 
188     /**
189      * Override the parent's method avoid creating a menu bar. Metal frames
190      * do not have system menus.
191      */
addSubComponents()192     protected void addSubComponents() {
193         add(iconButton);
194         add(maxButton);
195         add(closeButton);
196     }
197 
createPropertyChangeListener()198     protected PropertyChangeListener createPropertyChangeListener() {
199         return new MetalPropertyChangeHandler();
200     }
201 
createLayout()202     protected LayoutManager createLayout() {
203         return new MetalTitlePaneLayout();
204     }
205 
206     class MetalPropertyChangeHandler
207         extends BasicInternalFrameTitlePane.PropertyChangeHandler
208     {
propertyChange(PropertyChangeEvent evt)209         public void propertyChange(PropertyChangeEvent evt) {
210             String prop = evt.getPropertyName();
211             if( prop.equals(JInternalFrame.IS_SELECTED_PROPERTY) ) {
212                 Boolean b = (Boolean)evt.getNewValue();
213                 iconButton.putClientProperty("paintActive", b);
214                 closeButton.putClientProperty("paintActive", b);
215                 maxButton.putClientProperty("paintActive", b);
216             }
217             else if ("JInternalFrame.messageType".equals(prop)) {
218                 updateOptionPaneState();
219                 frame.repaint();
220             }
221             super.propertyChange(evt);
222         }
223     }
224 
225     class MetalTitlePaneLayout extends TitlePaneLayout {
addLayoutComponent(String name, Component c)226         public void addLayoutComponent(String name, Component c) {}
removeLayoutComponent(Component c)227         public void removeLayoutComponent(Component c) {}
preferredLayoutSize(Container c)228         public Dimension preferredLayoutSize(Container c)  {
229             return minimumLayoutSize(c);
230         }
231 
minimumLayoutSize(Container c)232         public Dimension minimumLayoutSize(Container c) {
233             // Compute width.
234             int width = 30;
235             if (frame.isClosable()) {
236                 width += 21;
237             }
238             if (frame.isMaximizable()) {
239                 width += 16 + (frame.isClosable() ? 10 : 4);
240             }
241             if (frame.isIconifiable()) {
242                 width += 16 + (frame.isMaximizable() ? 2 :
243                     (frame.isClosable() ? 10 : 4));
244             }
245             FontMetrics fm = frame.getFontMetrics(getFont());
246             String frameTitle = frame.getTitle();
247             int title_w = frameTitle != null ? SwingUtilities2.stringWidth(
248                                frame, fm, frameTitle) : 0;
249             int title_length = frameTitle != null ? frameTitle.length() : 0;
250 
251             if (title_length > 2) {
252                 int subtitle_w = SwingUtilities2.stringWidth(frame, fm,
253                                      frame.getTitle().substring(0, 2) + "...");
254                 width += (title_w < subtitle_w) ? title_w : subtitle_w;
255             }
256             else {
257                 width += title_w;
258             }
259 
260             // Compute height.
261             int height;
262             if (isPalette) {
263                 height = paletteTitleHeight;
264             } else {
265                 int fontHeight = fm.getHeight();
266                 fontHeight += 7;
267                 Icon icon = frame.getFrameIcon();
268                 int iconHeight = 0;
269                 if (icon != null) {
270                     // SystemMenuBar forces the icon to be 16x16 or less.
271                     iconHeight = Math.min(icon.getIconHeight(), 16);
272                 }
273                 iconHeight += 5;
274                 height = Math.max(fontHeight, iconHeight);
275             }
276 
277             return new Dimension(width, height);
278         }
279 
layoutContainer(Container c)280         public void layoutContainer(Container c) {
281             boolean leftToRight = MetalUtils.isLeftToRight(frame);
282 
283             int w = getWidth();
284             int x = leftToRight ? w : 0;
285             int y = 2;
286             int spacing;
287 
288             // assumes all buttons have the same dimensions
289             // these dimensions include the borders
290             int buttonHeight = closeButton.getIcon().getIconHeight();
291             int buttonWidth = closeButton.getIcon().getIconWidth();
292 
293             if(frame.isClosable()) {
294                 if (isPalette) {
295                     spacing = 3;
296                     x += leftToRight ? -spacing -(buttonWidth+2) : spacing;
297                     closeButton.setBounds(x, y, buttonWidth+2, getHeight()-4);
298                     if( !leftToRight ) x += (buttonWidth+2);
299                 } else {
300                     spacing = 4;
301                     x += leftToRight ? -spacing -buttonWidth : spacing;
302                     closeButton.setBounds(x, y, buttonWidth, buttonHeight);
303                     if( !leftToRight ) x += buttonWidth;
304                 }
305             }
306 
307             if(frame.isMaximizable() && !isPalette ) {
308                 spacing = frame.isClosable() ? 10 : 4;
309                 x += leftToRight ? -spacing -buttonWidth : spacing;
310                 maxButton.setBounds(x, y, buttonWidth, buttonHeight);
311                 if( !leftToRight ) x += buttonWidth;
312             }
313 
314             if(frame.isIconifiable() && !isPalette ) {
315                 spacing = frame.isMaximizable() ? 2
316                           : (frame.isClosable() ? 10 : 4);
317                 x += leftToRight ? -spacing -buttonWidth : spacing;
318                 iconButton.setBounds(x, y, buttonWidth, buttonHeight);
319                 if( !leftToRight ) x += buttonWidth;
320             }
321 
322             buttonsWidth = leftToRight ? w - x : x;
323         }
324     }
325 
326     /**
327      * Paints palette.
328      *
329      * @param g a instance of {@code Graphics}
330      */
paintPalette(Graphics g)331     public void paintPalette(Graphics g)  {
332         boolean leftToRight = MetalUtils.isLeftToRight(frame);
333 
334         int width = getWidth();
335         int height = getHeight();
336 
337         if (paletteBumps == null) {
338             paletteBumps
339                 = new MetalBumps(0, 0,
340                                  MetalLookAndFeel.getPrimaryControlHighlight(),
341                                  MetalLookAndFeel.getPrimaryControlInfo(),
342                                  MetalLookAndFeel.getPrimaryControlShadow() );
343         }
344 
345         Color background = MetalLookAndFeel.getPrimaryControlShadow();
346         Color darkShadow = MetalLookAndFeel.getPrimaryControlDarkShadow();
347 
348         g.setColor(background);
349         g.fillRect(0, 0, width, height);
350 
351         g.setColor( darkShadow );
352         g.drawLine ( 0, height - 1, width, height -1);
353 
354         int xOffset = leftToRight ? 4 : buttonsWidth + 4;
355         int bumpLength = width - buttonsWidth -2*4;
356         int bumpHeight = getHeight()  - 4;
357         paletteBumps.setBumpArea( bumpLength, bumpHeight );
358         paletteBumps.paintIcon( this, g, xOffset, 2);
359     }
360 
paintComponent(Graphics g)361     public void paintComponent(Graphics g)  {
362         if(isPalette) {
363             paintPalette(g);
364             return;
365         }
366 
367         boolean leftToRight = MetalUtils.isLeftToRight(frame);
368         boolean isSelected = frame.isSelected();
369 
370         int width = getWidth();
371         int height = getHeight();
372 
373         Color background = null;
374         Color foreground = null;
375         Color shadow = null;
376 
377         MetalBumps bumps;
378         String gradientKey;
379 
380         if (isSelected) {
381             if (!MetalLookAndFeel.usingOcean()) {
382                 closeButton.setContentAreaFilled(true);
383                 maxButton.setContentAreaFilled(true);
384                 iconButton.setContentAreaFilled(true);
385             }
386             if (selectedBackgroundKey != null) {
387                 background = UIManager.getColor(selectedBackgroundKey);
388             }
389             if (background == null) {
390                 background = MetalLookAndFeel.getWindowTitleBackground();
391             }
392             if (selectedForegroundKey != null) {
393                 foreground = UIManager.getColor(selectedForegroundKey);
394             }
395             if (selectedShadowKey != null) {
396                 shadow = UIManager.getColor(selectedShadowKey);
397             }
398             if (shadow == null) {
399                 shadow = MetalLookAndFeel.getPrimaryControlDarkShadow();
400             }
401             if (foreground == null) {
402                 foreground = MetalLookAndFeel.getWindowTitleForeground();
403             }
404             activeBumps.setBumpColors(activeBumpsHighlight, activeBumpsShadow,
405                         UIManager.get("InternalFrame.activeTitleGradient") !=
406                                       null ? null : background);
407             bumps = activeBumps;
408             gradientKey = "InternalFrame.activeTitleGradient";
409         } else {
410             if (!MetalLookAndFeel.usingOcean()) {
411                 closeButton.setContentAreaFilled(false);
412                 maxButton.setContentAreaFilled(false);
413                 iconButton.setContentAreaFilled(false);
414             }
415             background = MetalLookAndFeel.getWindowTitleInactiveBackground();
416             foreground = MetalLookAndFeel.getWindowTitleInactiveForeground();
417             shadow = MetalLookAndFeel.getControlDarkShadow();
418             bumps = inactiveBumps;
419             gradientKey = "InternalFrame.inactiveTitleGradient";
420         }
421 
422         if (!MetalUtils.drawGradient(this, g, gradientKey, 0, 0, width,
423                                      height, true)) {
424             g.setColor(background);
425             g.fillRect(0, 0, width, height);
426         }
427 
428         g.setColor( shadow );
429         g.drawLine ( 0, height - 1, width, height -1);
430         g.drawLine ( 0, 0, 0 ,0);
431         g.drawLine ( width - 1, 0 , width -1, 0);
432 
433 
434         int titleLength;
435         int xOffset = leftToRight ? 5 : width - 5;
436         String frameTitle = frame.getTitle();
437 
438         Icon icon = frame.getFrameIcon();
439         if ( icon != null ) {
440             if( !leftToRight )
441                 xOffset -= icon.getIconWidth();
442             int iconY = ((height / 2) - (icon.getIconHeight() /2));
443             icon.paintIcon(frame, g, xOffset, iconY);
444             xOffset += leftToRight ? icon.getIconWidth() + 5 : -5;
445         }
446 
447         if(frameTitle != null) {
448             Font f = getFont();
449             g.setFont(f);
450             FontMetrics fm = SwingUtilities2.getFontMetrics(frame, g, f);
451             int fHeight = fm.getHeight();
452 
453             g.setColor(foreground);
454 
455             int yOffset = ( (height - fm.getHeight() ) / 2 ) + fm.getAscent();
456 
457             Rectangle rect = new Rectangle(0, 0, 0, 0);
458             if (frame.isIconifiable()) { rect = iconButton.getBounds(); }
459             else if (frame.isMaximizable()) { rect = maxButton.getBounds(); }
460             else if (frame.isClosable()) { rect = closeButton.getBounds(); }
461             int titleW;
462 
463             if( leftToRight ) {
464               if (rect.x == 0) {
465                 rect.x = frame.getWidth()-frame.getInsets().right-2;
466               }
467               titleW = rect.x - xOffset - 4;
468               frameTitle = getTitle(frameTitle, fm, titleW);
469             } else {
470               titleW = xOffset - rect.x - rect.width - 4;
471               frameTitle = getTitle(frameTitle, fm, titleW);
472               xOffset -= SwingUtilities2.stringWidth(frame, fm, frameTitle);
473             }
474 
475             titleLength = SwingUtilities2.stringWidth(frame, fm, frameTitle);
476             SwingUtilities2.drawString(frame, g, frameTitle, xOffset, yOffset);
477             xOffset += leftToRight ? titleLength + 5  : -5;
478         }
479 
480         int bumpXOffset;
481         int bumpLength;
482         if( leftToRight ) {
483             bumpLength = width - buttonsWidth - xOffset - 5;
484             bumpXOffset = xOffset;
485         } else {
486             bumpLength = xOffset - buttonsWidth - 5;
487             bumpXOffset = buttonsWidth + 5;
488         }
489         int bumpYOffset = 3;
490         int bumpHeight = getHeight() - (2 * bumpYOffset);
491         bumps.setBumpArea( bumpLength, bumpHeight );
492         bumps.paintIcon(this, g, bumpXOffset, bumpYOffset);
493     }
494 
495     /**
496      * If {@code b} is {@code true}, sets palette icons.
497      *
498      * @param b if {@code true}, sets palette icons
499      */
setPalette(boolean b)500     public void setPalette(boolean b) {
501         isPalette = b;
502 
503         if (isPalette) {
504             closeButton.setIcon(paletteCloseIcon);
505          if( frame.isMaximizable() )
506                 remove(maxButton);
507             if( frame.isIconifiable() )
508                 remove(iconButton);
509         } else {
510             closeButton.setIcon(closeIcon);
511             if( frame.isMaximizable() )
512                 add(maxButton);
513             if( frame.isIconifiable() )
514                 add(iconButton);
515         }
516         revalidate();
517         repaint();
518     }
519 
520     /**
521      * Updates any state dependent upon the JInternalFrame being shown in
522      * a <code>JOptionPane</code>.
523      */
updateOptionPaneState()524     private void updateOptionPaneState() {
525         int type = -2;
526         boolean closable = wasClosable;
527         Object obj = frame.getClientProperty("JInternalFrame.messageType");
528 
529         if (obj == null) {
530             // Don't change the closable state unless in an JOptionPane.
531             return;
532         }
533         if (obj instanceof Integer) {
534             type = ((Integer) obj).intValue();
535         }
536         switch (type) {
537         case JOptionPane.ERROR_MESSAGE:
538             selectedBackgroundKey =
539                               "OptionPane.errorDialog.titlePane.background";
540             selectedForegroundKey =
541                               "OptionPane.errorDialog.titlePane.foreground";
542             selectedShadowKey = "OptionPane.errorDialog.titlePane.shadow";
543             closable = false;
544             break;
545         case JOptionPane.QUESTION_MESSAGE:
546             selectedBackgroundKey =
547                             "OptionPane.questionDialog.titlePane.background";
548             selectedForegroundKey =
549                     "OptionPane.questionDialog.titlePane.foreground";
550             selectedShadowKey =
551                           "OptionPane.questionDialog.titlePane.shadow";
552             closable = false;
553             break;
554         case JOptionPane.WARNING_MESSAGE:
555             selectedBackgroundKey =
556                               "OptionPane.warningDialog.titlePane.background";
557             selectedForegroundKey =
558                               "OptionPane.warningDialog.titlePane.foreground";
559             selectedShadowKey = "OptionPane.warningDialog.titlePane.shadow";
560             closable = false;
561             break;
562         case JOptionPane.INFORMATION_MESSAGE:
563         case JOptionPane.PLAIN_MESSAGE:
564             selectedBackgroundKey = selectedForegroundKey = selectedShadowKey =
565                                     null;
566             closable = false;
567             break;
568         default:
569             selectedBackgroundKey = selectedForegroundKey = selectedShadowKey =
570                                     null;
571             break;
572         }
573         if (closable != frame.isClosable()) {
574             frame.setClosable(closable);
575         }
576     }
577 }
578