1 /*
2  * Copyright (c) 2002, 2013, 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.synth;
27 
28 import java.awt.*;
29 import java.awt.event.*;
30 import javax.swing.*;
31 import javax.swing.plaf.*;
32 import javax.swing.plaf.basic.BasicInternalFrameTitlePane;
33 import java.beans.PropertyChangeListener;
34 import java.beans.PropertyChangeEvent;
35 import java.beans.PropertyVetoException;
36 import sun.swing.SwingUtilities2;
37 
38 /**
39  * The class that manages a synth title bar
40  *
41  * @author David Kloba
42  * @author Joshua Outwater
43  * @author Steve Wilson
44  */
45 class SynthInternalFrameTitlePane extends BasicInternalFrameTitlePane
46         implements SynthUI, PropertyChangeListener {
47 
48     protected JPopupMenu systemPopupMenu;
49     protected JButton menuButton;
50 
51     private SynthStyle style;
52     private int titleSpacing;
53     private int buttonSpacing;
54     // Alignment for the title, one of SwingConstants.(LEADING|TRAILING|CENTER)
55     private int titleAlignment;
56 
SynthInternalFrameTitlePane(JInternalFrame f)57     public SynthInternalFrameTitlePane(JInternalFrame f) {
58         super(f);
59     }
60 
getUIClassID()61     public String getUIClassID() {
62         return "InternalFrameTitlePaneUI";
63     }
64 
getContext(JComponent c)65     public SynthContext getContext(JComponent c) {
66         return getContext(c, getComponentState(c));
67     }
68 
getContext(JComponent c, int state)69     public SynthContext getContext(JComponent c, int state) {
70         return SynthContext.getContext(c, style, state);
71     }
72 
getRegion(JComponent c)73     private Region getRegion(JComponent c) {
74         return SynthLookAndFeel.getRegion(c);
75     }
76 
getComponentState(JComponent c)77     private int getComponentState(JComponent c) {
78         if (frame != null) {
79             if (frame.isSelected()) {
80                 return SELECTED;
81             }
82         }
83         return SynthLookAndFeel.getComponentState(c);
84     }
85 
addSubComponents()86     protected void addSubComponents() {
87         menuButton.setName("InternalFrameTitlePane.menuButton");
88         iconButton.setName("InternalFrameTitlePane.iconifyButton");
89         maxButton.setName("InternalFrameTitlePane.maximizeButton");
90         closeButton.setName("InternalFrameTitlePane.closeButton");
91 
92         add(menuButton);
93         add(iconButton);
94         add(maxButton);
95         add(closeButton);
96     }
97 
installListeners()98     protected void installListeners() {
99         super.installListeners();
100         frame.addPropertyChangeListener(this);
101         addPropertyChangeListener(this);
102     }
103 
uninstallListeners()104     protected void uninstallListeners() {
105         frame.removePropertyChangeListener(this);
106         removePropertyChangeListener(this);
107         super.uninstallListeners();
108     }
109 
updateStyle(JComponent c)110     private void updateStyle(JComponent c) {
111         SynthContext context = getContext(this, ENABLED);
112         SynthStyle oldStyle = style;
113         style = SynthLookAndFeel.updateStyle(context, this);
114         if (style != oldStyle) {
115             maxIcon =
116                 style.getIcon(context,"InternalFrameTitlePane.maximizeIcon");
117             minIcon =
118                 style.getIcon(context,"InternalFrameTitlePane.minimizeIcon");
119             iconIcon =
120                 style.getIcon(context,"InternalFrameTitlePane.iconifyIcon");
121             closeIcon =
122                 style.getIcon(context,"InternalFrameTitlePane.closeIcon");
123             titleSpacing = style.getInt(context,
124                               "InternalFrameTitlePane.titleSpacing", 2);
125             buttonSpacing = style.getInt(context,
126                               "InternalFrameTitlePane.buttonSpacing", 2);
127             String alignString = (String)style.get(context,
128                               "InternalFrameTitlePane.titleAlignment");
129             titleAlignment = SwingConstants.LEADING;
130             if (alignString != null) {
131                 alignString = alignString.toUpperCase();
132                 if (alignString.equals("TRAILING")) {
133                     titleAlignment = SwingConstants.TRAILING;
134                 }
135                 else if (alignString.equals("CENTER")) {
136                     titleAlignment = SwingConstants.CENTER;
137                 }
138             }
139         }
140         context.dispose();
141     }
142 
installDefaults()143     protected void installDefaults() {
144         super.installDefaults();
145         updateStyle(this);
146     }
147 
uninstallDefaults()148     protected void uninstallDefaults() {
149         SynthContext context = getContext(this, ENABLED);
150         style.uninstallDefaults(context);
151         context.dispose();
152         style = null;
153         JInternalFrame.JDesktopIcon di = frame.getDesktopIcon();
154         if(di != null && di.getComponentPopupMenu() == systemPopupMenu) {
155             // Release link to systemMenu from the JInternalFrame
156             di.setComponentPopupMenu(null);
157         }
158         super.uninstallDefaults();
159     }
160 
161     private static class JPopupMenuUIResource extends JPopupMenu implements
162         UIResource { }
163 
assembleSystemMenu()164     protected void assembleSystemMenu() {
165         systemPopupMenu = new JPopupMenuUIResource();
166         addSystemMenuItems(systemPopupMenu);
167         enableActions();
168         menuButton = createNoFocusButton();
169         updateMenuIcon();
170         menuButton.addMouseListener(new MouseAdapter() {
171             public void mousePressed(MouseEvent e) {
172                 try {
173                     frame.setSelected(true);
174                 } catch(PropertyVetoException pve) {
175                 }
176                 showSystemMenu();
177             }
178         });
179         JPopupMenu p = frame.getComponentPopupMenu();
180         if (p == null || p instanceof UIResource) {
181             frame.setComponentPopupMenu(systemPopupMenu);
182         }
183         if (frame.getDesktopIcon() != null) {
184             p = frame.getDesktopIcon().getComponentPopupMenu();
185             if (p == null || p instanceof UIResource) {
186                 frame.getDesktopIcon().setComponentPopupMenu(systemPopupMenu);
187             }
188         }
189         setInheritsPopupMenu(true);
190     }
191 
addSystemMenuItems(JPopupMenu menu)192     protected void addSystemMenuItems(JPopupMenu menu) {
193         JMenuItem mi = menu.add(restoreAction);
194         mi.setMnemonic(getButtonMnemonic("restore"));
195         mi = menu.add(moveAction);
196         mi.setMnemonic(getButtonMnemonic("move"));
197         mi = menu.add(sizeAction);
198         mi.setMnemonic(getButtonMnemonic("size"));
199         mi = menu.add(iconifyAction);
200         mi.setMnemonic(getButtonMnemonic("minimize"));
201         mi = menu.add(maximizeAction);
202         mi.setMnemonic(getButtonMnemonic("maximize"));
203         menu.add(new JSeparator());
204         mi = menu.add(closeAction);
205         mi.setMnemonic(getButtonMnemonic("close"));
206     }
207 
getButtonMnemonic(String button)208     private static int getButtonMnemonic(String button) {
209         try {
210             return Integer.parseInt(UIManager.getString(
211                     "InternalFrameTitlePane." + button + "Button.mnemonic"));
212         } catch (NumberFormatException e) {
213             return -1;
214         }
215     }
216 
showSystemMenu()217     protected void showSystemMenu() {
218         Insets insets = frame.getInsets();
219         if (!frame.isIcon()) {
220             systemPopupMenu.show(frame, menuButton.getX(), getY() + getHeight());
221         } else {
222             systemPopupMenu.show(menuButton,
223                 getX() - insets.left - insets.right,
224                 getY() - systemPopupMenu.getPreferredSize().height -
225                     insets.bottom - insets.top);
226         }
227     }
228 
229     // SynthInternalFrameTitlePane has no UI, we'll invoke paint on it.
paintComponent(Graphics g)230     public void paintComponent(Graphics g) {
231         SynthContext context = getContext(this);
232         SynthLookAndFeel.update(context, g);
233         context.getPainter().paintInternalFrameTitlePaneBackground(context,
234                           g, 0, 0, getWidth(), getHeight());
235         paint(context, g);
236         context.dispose();
237     }
238 
paint(SynthContext context, Graphics g)239     protected void paint(SynthContext context, Graphics g) {
240         String title = frame.getTitle();
241 
242         if (title != null) {
243             SynthStyle style = context.getStyle();
244 
245             g.setColor(style.getColor(context, ColorType.TEXT_FOREGROUND));
246             g.setFont(style.getFont(context));
247 
248             // Center text vertically.
249             FontMetrics fm = SwingUtilities2.getFontMetrics(frame, g);
250             int baseline = (getHeight() + fm.getAscent() - fm.getLeading() -
251                             fm.getDescent()) / 2;
252             JButton lastButton = null;
253             if (frame.isIconifiable()) {
254                 lastButton = iconButton;
255             }
256             else if (frame.isMaximizable()) {
257                 lastButton = maxButton;
258             }
259             else if (frame.isClosable()) {
260                 lastButton = closeButton;
261             }
262             int maxX;
263             int minX;
264             boolean ltr = SynthLookAndFeel.isLeftToRight(frame);
265             int titleAlignment = this.titleAlignment;
266             if (ltr) {
267                 if (lastButton != null) {
268                     maxX = lastButton.getX() - titleSpacing;
269                 }
270                 else {
271                     maxX = frame.getWidth() - frame.getInsets().right -
272                            titleSpacing;
273                 }
274                 minX = menuButton.getX() + menuButton.getWidth() +
275                        titleSpacing;
276             }
277             else {
278                 if (lastButton != null) {
279                     minX = lastButton.getX() + lastButton.getWidth() +
280                            titleSpacing;
281                 }
282                 else {
283                     minX = frame.getInsets().left + titleSpacing;
284                 }
285                 maxX = menuButton.getX() - titleSpacing;
286                 if (titleAlignment == SwingConstants.LEADING) {
287                     titleAlignment = SwingConstants.TRAILING;
288                 }
289                 else if (titleAlignment == SwingConstants.TRAILING) {
290                     titleAlignment = SwingConstants.LEADING;
291                 }
292             }
293             String clippedTitle = getTitle(title, fm, maxX - minX);
294             if (clippedTitle == title) {
295                 // String fit, align as necessary.
296                 if (titleAlignment == SwingConstants.TRAILING) {
297                     minX = maxX - style.getGraphicsUtils(context).
298                         computeStringWidth(context, g.getFont(), fm, title);
299                 }
300                 else if (titleAlignment == SwingConstants.CENTER) {
301                     int width = style.getGraphicsUtils(context).
302                            computeStringWidth(context, g.getFont(), fm, title);
303                     minX = Math.max(minX, (getWidth() - width) / 2);
304                     minX = Math.min(maxX - width, minX);
305                 }
306             }
307             style.getGraphicsUtils(context).paintText(
308                 context, g, clippedTitle, minX, baseline - fm.getAscent(), -1);
309         }
310     }
311 
paintBorder(SynthContext context, Graphics g, int x, int y, int w, int h)312     public void paintBorder(SynthContext context, Graphics g, int x,
313                             int y, int w, int h) {
314         context.getPainter().paintInternalFrameTitlePaneBorder(context,
315                                                             g, x, y, w, h);
316     }
317 
createLayout()318     protected LayoutManager createLayout() {
319         SynthContext context = getContext(this);
320         LayoutManager lm =
321             (LayoutManager)style.get(context, "InternalFrameTitlePane.titlePaneLayout");
322         context.dispose();
323         return (lm != null) ? lm : new SynthTitlePaneLayout();
324     }
325 
propertyChange(PropertyChangeEvent evt)326     public void propertyChange(PropertyChangeEvent evt) {
327         if (evt.getSource() == this) {
328             if (SynthLookAndFeel.shouldUpdateStyle(evt)) {
329                 updateStyle(this);
330             }
331         }
332         else {
333             // Changes for the internal frame
334             if (evt.getPropertyName() == JInternalFrame.FRAME_ICON_PROPERTY) {
335                 updateMenuIcon();
336             }
337         }
338     }
339 
340     /**
341      * Resets the menuButton icon to match that of the frame.
342      */
updateMenuIcon()343     private void updateMenuIcon() {
344         Icon frameIcon = frame.getFrameIcon();
345         SynthContext context = getContext(this);
346         if (frameIcon != null) {
347             Dimension maxSize = (Dimension)context.getStyle().get(context,
348                                 "InternalFrameTitlePane.maxFrameIconSize");
349             int maxWidth = 16;
350             int maxHeight = 16;
351             if (maxSize != null) {
352                 maxWidth = maxSize.width;
353                 maxHeight = maxSize.height;
354             }
355             if ((frameIcon.getIconWidth() > maxWidth ||
356                      frameIcon.getIconHeight() > maxHeight) &&
357                     (frameIcon instanceof ImageIcon)) {
358                 frameIcon = new ImageIcon(((ImageIcon)frameIcon).
359                              getImage().getScaledInstance(maxWidth, maxHeight,
360                              Image.SCALE_SMOOTH));
361             }
362         }
363         context.dispose();
364         menuButton.setIcon(frameIcon);
365     }
366 
367 
368     class SynthTitlePaneLayout implements LayoutManager {
addLayoutComponent(String name, Component c)369         public void addLayoutComponent(String name, Component c) {}
removeLayoutComponent(Component c)370         public void removeLayoutComponent(Component c) {}
preferredLayoutSize(Container c)371         public Dimension preferredLayoutSize(Container c)  {
372             return minimumLayoutSize(c);
373         }
374 
minimumLayoutSize(Container c)375         public Dimension minimumLayoutSize(Container c) {
376             SynthContext context = getContext(
377                              SynthInternalFrameTitlePane.this);
378             int width = 0;
379             int height = 0;
380 
381             int buttonCount = 0;
382             Dimension pref;
383 
384             if (frame.isClosable()) {
385                 pref = closeButton.getPreferredSize();
386                 width += pref.width;
387                 height = Math.max(pref.height, height);
388                 buttonCount++;
389             }
390             if (frame.isMaximizable()) {
391                 pref = maxButton.getPreferredSize();
392                 width += pref.width;
393                 height = Math.max(pref.height, height);
394                 buttonCount++;
395             }
396             if (frame.isIconifiable()) {
397                 pref = iconButton.getPreferredSize();
398                 width += pref.width;
399                 height = Math.max(pref.height, height);
400                 buttonCount++;
401             }
402             pref = menuButton.getPreferredSize();
403             width += pref.width;
404             height = Math.max(pref.height, height);
405 
406             width += Math.max(0, (buttonCount - 1) * buttonSpacing);
407 
408             FontMetrics fm = SynthInternalFrameTitlePane.this.getFontMetrics(
409                                           getFont());
410             SynthGraphicsUtils graphicsUtils = context.getStyle().
411                                        getGraphicsUtils(context);
412             String frameTitle = frame.getTitle();
413             int title_w = frameTitle != null ? graphicsUtils.
414                                computeStringWidth(context, fm.getFont(),
415                                fm, frameTitle) : 0;
416             int title_length = frameTitle != null ? frameTitle.length() : 0;
417 
418             // Leave room for three characters in the title.
419             if (title_length > 3) {
420                 int subtitle_w = graphicsUtils.computeStringWidth(context,
421                     fm.getFont(), fm, frameTitle.substring(0, 3) + "...");
422                 width += (title_w < subtitle_w) ? title_w : subtitle_w;
423             } else {
424                 width += title_w;
425             }
426 
427             height = Math.max(fm.getHeight() + 2, height);
428 
429             width += titleSpacing + titleSpacing;
430 
431             Insets insets = getInsets();
432             height += insets.top + insets.bottom;
433             width += insets.left + insets.right;
434             context.dispose();
435             return new Dimension(width, height);
436         }
437 
center(Component c, Insets insets, int x, boolean trailing)438         private int center(Component c, Insets insets, int x,
439                            boolean trailing) {
440             Dimension pref = c.getPreferredSize();
441             if (trailing) {
442                 x -= pref.width;
443             }
444             c.setBounds(x, insets.top +
445                         (getHeight() - insets.top - insets.bottom -
446                          pref.height) / 2, pref.width, pref.height);
447             if (pref.width > 0) {
448                 if (trailing) {
449                     return x - buttonSpacing;
450                 }
451                 return x + pref.width + buttonSpacing;
452             }
453             return x;
454         }
455 
layoutContainer(Container c)456         public void layoutContainer(Container c) {
457             Insets insets = c.getInsets();
458             Dimension pref;
459 
460             if (SynthLookAndFeel.isLeftToRight(frame)) {
461                 center(menuButton, insets, insets.left, false);
462                 int x = getWidth() - insets.right;
463                 if (frame.isClosable()) {
464                     x = center(closeButton, insets, x, true);
465                 }
466                 if (frame.isMaximizable()) {
467                     x = center(maxButton, insets, x, true);
468                 }
469                 if (frame.isIconifiable()) {
470                     x = center(iconButton, insets, x, true);
471                 }
472             }
473             else {
474                 center(menuButton, insets, getWidth() - insets.right,
475                        true);
476                 int x = insets.left;
477                 if (frame.isClosable()) {
478                     x = center(closeButton, insets, x, false);
479                 }
480                 if (frame.isMaximizable()) {
481                     x = center(maxButton, insets, x, false);
482                 }
483                 if (frame.isIconifiable()) {
484                     x = center(iconButton, insets, x, false);
485                 }
486             }
487         }
488     }
489 
createNoFocusButton()490     private JButton createNoFocusButton() {
491         JButton button = new JButton();
492         button.setFocusable(false);
493         button.setMargin(new Insets(0,0,0,0));
494         return button;
495     }
496 }
497