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
26package javax.swing.plaf.metal;
27
28import sun.swing.SwingUtilities2;
29import java.awt.*;
30import java.awt.event.*;
31import javax.swing.*;
32import javax.swing.border.*;
33import javax.swing.event.InternalFrameEvent;
34import java.util.EventListener;
35import java.beans.PropertyChangeListener;
36import java.beans.PropertyChangeEvent;
37import 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
47public 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     */
113    public MetalInternalFrameTitlePane(JInternalFrame f) {
114        super( f );
115    }
116
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
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
139    protected void uninstallDefaults() {
140        super.uninstallDefaults();
141        if (wasClosable != frame.isClosable()) {
142            frame.setClosable(wasClosable);
143        }
144    }
145
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     */
174    protected void assembleSystemMenu() {}
175
176    /**
177     * Override the parent's method to do nothing. Metal frames do not
178     * have system menus.
179     */
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     */
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     */
192    protected void addSubComponents() {
193        add(iconButton);
194        add(maxButton);
195        add(closeButton);
196    }
197
198    protected PropertyChangeListener createPropertyChangeListener() {
199        return new MetalPropertyChangeHandler();
200    }
201
202    protected LayoutManager createLayout() {
203        return new MetalTitlePaneLayout();
204    }
205
206    class MetalPropertyChangeHandler
207        extends BasicInternalFrameTitlePane.PropertyChangeHandler
208    {
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 {
226        public void addLayoutComponent(String name, Component c) {}
227        public void removeLayoutComponent(Component c) {}
228        public Dimension preferredLayoutSize(Container c)  {
229            return minimumLayoutSize(c);
230        }
231
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
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     */
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
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     */
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 dependant upon the JInternalFrame being shown in
522     * a <code>JOptionPane</code>.
523     */
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