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