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