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