1 /*
2  * Copyright (c) 2002, 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.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 @SuppressWarnings("serial") // Superclass is not serializable across versions
46 class SynthInternalFrameTitlePane extends BasicInternalFrameTitlePane
47         implements SynthUI, PropertyChangeListener {
48 
49     protected JPopupMenu systemPopupMenu;
50     protected JButton menuButton;
51 
52     private SynthStyle style;
53     private int titleSpacing;
54     private int buttonSpacing;
55     // Alignment for the title, one of SwingConstants.(LEADING|TRAILING|CENTER)
56     private int titleAlignment;
57 
SynthInternalFrameTitlePane(JInternalFrame f)58     public SynthInternalFrameTitlePane(JInternalFrame f) {
59         super(f);
60     }
61 
getUIClassID()62     public String getUIClassID() {
63         return "InternalFrameTitlePaneUI";
64     }
65 
getContext(JComponent c)66     public SynthContext getContext(JComponent c) {
67         return getContext(c, getComponentState(c));
68     }
69 
getContext(JComponent c, int state)70     public SynthContext getContext(JComponent c, int state) {
71         return SynthContext.getContext(c, style, state);
72     }
73 
getRegion(JComponent c)74     private Region getRegion(JComponent c) {
75         return SynthLookAndFeel.getRegion(c);
76     }
77 
getComponentState(JComponent c)78     private int getComponentState(JComponent c) {
79         if (frame != null) {
80             if (frame.isSelected()) {
81                 return SELECTED;
82             }
83         }
84         return SynthLookAndFeel.getComponentState(c);
85     }
86 
addSubComponents()87     protected void addSubComponents() {
88         menuButton.setName("InternalFrameTitlePane.menuButton");
89         iconButton.setName("InternalFrameTitlePane.iconifyButton");
90         maxButton.setName("InternalFrameTitlePane.maximizeButton");
91         closeButton.setName("InternalFrameTitlePane.closeButton");
92 
93         add(menuButton);
94         add(iconButton);
95         add(maxButton);
96         add(closeButton);
97     }
98 
installListeners()99     protected void installListeners() {
100         super.installListeners();
101         frame.addPropertyChangeListener(this);
102         addPropertyChangeListener(this);
103     }
104 
uninstallListeners()105     protected void uninstallListeners() {
106         frame.removePropertyChangeListener(this);
107         removePropertyChangeListener(this);
108         super.uninstallListeners();
109     }
110 
updateStyle(JComponent c)111     private void updateStyle(JComponent c) {
112         SynthContext context = getContext(this, ENABLED);
113         SynthStyle oldStyle = style;
114         style = SynthLookAndFeel.updateStyle(context, this);
115         if (style != oldStyle) {
116             maxIcon =
117                 style.getIcon(context,"InternalFrameTitlePane.maximizeIcon");
118             minIcon =
119                 style.getIcon(context,"InternalFrameTitlePane.minimizeIcon");
120             iconIcon =
121                 style.getIcon(context,"InternalFrameTitlePane.iconifyIcon");
122             closeIcon =
123                 style.getIcon(context,"InternalFrameTitlePane.closeIcon");
124             titleSpacing = style.getInt(context,
125                               "InternalFrameTitlePane.titleSpacing", 2);
126             buttonSpacing = style.getInt(context,
127                               "InternalFrameTitlePane.buttonSpacing", 2);
128             String alignString = (String)style.get(context,
129                               "InternalFrameTitlePane.titleAlignment");
130             titleAlignment = SwingConstants.LEADING;
131             if (alignString != null) {
132                 alignString = alignString.toUpperCase();
133                 if (alignString.equals("TRAILING")) {
134                     titleAlignment = SwingConstants.TRAILING;
135                 }
136                 else if (alignString.equals("CENTER")) {
137                     titleAlignment = SwingConstants.CENTER;
138                 }
139             }
140         }
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         style = null;
152         JInternalFrame.JDesktopIcon di = frame.getDesktopIcon();
153         if(di != null && di.getComponentPopupMenu() == systemPopupMenu) {
154             // Release link to systemMenu from the JInternalFrame
155             di.setComponentPopupMenu(null);
156         }
157         super.uninstallDefaults();
158     }
159 
160     @SuppressWarnings("serial") // Superclass is not serializable across versions
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     }
237 
paint(SynthContext context, Graphics g)238     protected void paint(SynthContext context, Graphics g) {
239         String title = frame.getTitle();
240 
241         if (title != null) {
242             SynthStyle style = context.getStyle();
243 
244             g.setColor(style.getColor(context, ColorType.TEXT_FOREGROUND));
245             g.setFont(style.getFont(context));
246 
247             // Center text vertically.
248             FontMetrics fm = SwingUtilities2.getFontMetrics(frame, g);
249             int baseline = (getHeight() + fm.getAscent() - fm.getLeading() -
250                             fm.getDescent()) / 2;
251             JButton lastButton = null;
252             if (frame.isIconifiable()) {
253                 lastButton = iconButton;
254             }
255             else if (frame.isMaximizable()) {
256                 lastButton = maxButton;
257             }
258             else if (frame.isClosable()) {
259                 lastButton = closeButton;
260             }
261             int maxX;
262             int minX;
263             boolean ltr = SynthLookAndFeel.isLeftToRight(frame);
264             int titleAlignment = this.titleAlignment;
265             if (ltr) {
266                 if (lastButton != null) {
267                     maxX = lastButton.getX() - titleSpacing;
268                 }
269                 else {
270                     maxX = frame.getWidth() - frame.getInsets().right -
271                            titleSpacing;
272                 }
273                 minX = menuButton.getX() + menuButton.getWidth() +
274                        titleSpacing;
275             }
276             else {
277                 if (lastButton != null) {
278                     minX = lastButton.getX() + lastButton.getWidth() +
279                            titleSpacing;
280                 }
281                 else {
282                     minX = frame.getInsets().left + titleSpacing;
283                 }
284                 maxX = menuButton.getX() - titleSpacing;
285                 if (titleAlignment == SwingConstants.LEADING) {
286                     titleAlignment = SwingConstants.TRAILING;
287                 }
288                 else if (titleAlignment == SwingConstants.TRAILING) {
289                     titleAlignment = SwingConstants.LEADING;
290                 }
291             }
292             String clippedTitle = getTitle(title, fm, maxX - minX);
293             if (clippedTitle == title) {
294                 // String fit, align as necessary.
295                 if (titleAlignment == SwingConstants.TRAILING) {
296                     minX = maxX - style.getGraphicsUtils(context).
297                         computeStringWidth(context, g.getFont(), fm, title);
298                 }
299                 else if (titleAlignment == SwingConstants.CENTER) {
300                     int width = style.getGraphicsUtils(context).
301                            computeStringWidth(context, g.getFont(), fm, title);
302                     minX = Math.max(minX, (getWidth() - width) / 2);
303                     minX = Math.min(maxX - width, minX);
304                 }
305             }
306             style.getGraphicsUtils(context).paintText(
307                 context, g, clippedTitle, minX, baseline - fm.getAscent(), -1);
308         }
309     }
310 
paintBorder(SynthContext context, Graphics g, int x, int y, int w, int h)311     public void paintBorder(SynthContext context, Graphics g, int x,
312                             int y, int w, int h) {
313         context.getPainter().paintInternalFrameTitlePaneBorder(context,
314                                                             g, x, y, w, h);
315     }
316 
createLayout()317     protected LayoutManager createLayout() {
318         SynthContext context = getContext(this);
319         LayoutManager lm =
320             (LayoutManager)style.get(context, "InternalFrameTitlePane.titlePaneLayout");
321         return (lm != null) ? lm : new SynthTitlePaneLayout();
322     }
323 
propertyChange(PropertyChangeEvent evt)324     public void propertyChange(PropertyChangeEvent evt) {
325         if (evt.getSource() == this) {
326             if (SynthLookAndFeel.shouldUpdateStyle(evt)) {
327                 updateStyle(this);
328             }
329         }
330         else {
331             // Changes for the internal frame
332             if (evt.getPropertyName() == JInternalFrame.FRAME_ICON_PROPERTY) {
333                 updateMenuIcon();
334             }
335         }
336     }
337 
338     /**
339      * Resets the menuButton icon to match that of the frame.
340      */
updateMenuIcon()341     private void updateMenuIcon() {
342         Icon frameIcon = frame.getFrameIcon();
343         SynthContext context = getContext(this);
344         if (frameIcon != null) {
345             Dimension maxSize = (Dimension)context.getStyle().get(context,
346                                 "InternalFrameTitlePane.maxFrameIconSize");
347             int maxWidth = 16;
348             int maxHeight = 16;
349             if (maxSize != null) {
350                 maxWidth = maxSize.width;
351                 maxHeight = maxSize.height;
352             }
353             if ((frameIcon.getIconWidth() > maxWidth ||
354                      frameIcon.getIconHeight() > maxHeight) &&
355                     (frameIcon instanceof ImageIcon)) {
356                 frameIcon = new ImageIcon(((ImageIcon)frameIcon).
357                              getImage().getScaledInstance(maxWidth, maxHeight,
358                              Image.SCALE_SMOOTH));
359             }
360         }
361         menuButton.setIcon(frameIcon);
362     }
363 
364 
365     class SynthTitlePaneLayout implements LayoutManager {
addLayoutComponent(String name, Component c)366         public void addLayoutComponent(String name, Component c) {}
removeLayoutComponent(Component c)367         public void removeLayoutComponent(Component c) {}
preferredLayoutSize(Container c)368         public Dimension preferredLayoutSize(Container c)  {
369             return minimumLayoutSize(c);
370         }
371 
minimumLayoutSize(Container c)372         public Dimension minimumLayoutSize(Container c) {
373             SynthContext context = getContext(
374                              SynthInternalFrameTitlePane.this);
375             int width = 0;
376             int height = 0;
377 
378             int buttonCount = 0;
379             Dimension pref;
380 
381             if (frame.isClosable()) {
382                 pref = closeButton.getPreferredSize();
383                 width += pref.width;
384                 height = Math.max(pref.height, height);
385                 buttonCount++;
386             }
387             if (frame.isMaximizable()) {
388                 pref = maxButton.getPreferredSize();
389                 width += pref.width;
390                 height = Math.max(pref.height, height);
391                 buttonCount++;
392             }
393             if (frame.isIconifiable()) {
394                 pref = iconButton.getPreferredSize();
395                 width += pref.width;
396                 height = Math.max(pref.height, height);
397                 buttonCount++;
398             }
399             pref = menuButton.getPreferredSize();
400             width += pref.width;
401             height = Math.max(pref.height, height);
402 
403             width += Math.max(0, (buttonCount - 1) * buttonSpacing);
404 
405             FontMetrics fm = SynthInternalFrameTitlePane.this.getFontMetrics(
406                                           getFont());
407             SynthGraphicsUtils graphicsUtils = context.getStyle().
408                                        getGraphicsUtils(context);
409             String frameTitle = frame.getTitle();
410             int title_w = frameTitle != null ? graphicsUtils.
411                                computeStringWidth(context, fm.getFont(),
412                                fm, frameTitle) : 0;
413             int title_length = frameTitle != null ? frameTitle.length() : 0;
414 
415             // Leave room for three characters in the title.
416             if (title_length > 3) {
417                 int subtitle_w = graphicsUtils.computeStringWidth(context,
418                     fm.getFont(), fm, frameTitle.substring(0, 3) + "...");
419                 width += (title_w < subtitle_w) ? title_w : subtitle_w;
420             } else {
421                 width += title_w;
422             }
423 
424             height = Math.max(fm.getHeight() + 2, height);
425 
426             width += titleSpacing + titleSpacing;
427 
428             Insets insets = getInsets();
429             height += insets.top + insets.bottom;
430             width += insets.left + insets.right;
431             return new Dimension(width, height);
432         }
433 
center(Component c, Insets insets, int x, boolean trailing)434         private int center(Component c, Insets insets, int x,
435                            boolean trailing) {
436             Dimension pref = c.getPreferredSize();
437             if (trailing) {
438                 x -= pref.width;
439             }
440             c.setBounds(x, insets.top +
441                         (getHeight() - insets.top - insets.bottom -
442                          pref.height) / 2, pref.width, pref.height);
443             if (pref.width > 0) {
444                 if (trailing) {
445                     return x - buttonSpacing;
446                 }
447                 return x + pref.width + buttonSpacing;
448             }
449             return x;
450         }
451 
layoutContainer(Container c)452         public void layoutContainer(Container c) {
453             Insets insets = c.getInsets();
454             Dimension pref;
455 
456             if (SynthLookAndFeel.isLeftToRight(frame)) {
457                 center(menuButton, insets, insets.left, false);
458                 int x = getWidth() - insets.right;
459                 if (frame.isClosable()) {
460                     x = center(closeButton, insets, x, true);
461                 }
462                 if (frame.isMaximizable()) {
463                     x = center(maxButton, insets, x, true);
464                 }
465                 if (frame.isIconifiable()) {
466                     x = center(iconButton, insets, x, true);
467                 }
468             }
469             else {
470                 center(menuButton, insets, getWidth() - insets.right,
471                        true);
472                 int x = insets.left;
473                 if (frame.isClosable()) {
474                     x = center(closeButton, insets, x, false);
475                 }
476                 if (frame.isMaximizable()) {
477                     x = center(maxButton, insets, x, false);
478                 }
479                 if (frame.isIconifiable()) {
480                     x = center(iconButton, insets, x, false);
481                 }
482             }
483         }
484     }
485 
createNoFocusButton()486     private JButton createNoFocusButton() {
487         JButton button = new JButton();
488         button.setFocusable(false);
489         button.setMargin(new Insets(0,0,0,0));
490         return button;
491     }
492 }
493