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