1 /*
   2  * Copyright (c) 2001, 2006, 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 com.sun.java.swing.plaf.windows;
  27 
  28 import sun.swing.SwingUtilities2;
  29 
  30 import javax.swing.*;
  31 import javax.swing.border.*;
  32 import javax.swing.UIManager;
  33 import javax.swing.plaf.*;
  34 import javax.swing.plaf.basic.BasicInternalFrameTitlePane;
  35 import java.awt.*;
  36 import java.awt.event.*;
  37 import java.beans.PropertyChangeEvent;
  38 import java.beans.PropertyChangeListener;
  39 import java.beans.PropertyVetoException;
  40 
  41 import static com.sun.java.swing.plaf.windows.TMSchema.*;
  42 import static com.sun.java.swing.plaf.windows.XPStyle.Skin;
  43 
  44 public class WindowsInternalFrameTitlePane extends BasicInternalFrameTitlePane {
  45     private Color selectedTitleGradientColor;
  46     private Color notSelectedTitleGradientColor;
  47     private JPopupMenu systemPopupMenu;
  48     private JLabel systemLabel;
  49 
  50     private Font titleFont;
  51     private int titlePaneHeight;
  52     private int buttonWidth, buttonHeight;
  53     private boolean hotTrackingOn;
  54 
  55     public WindowsInternalFrameTitlePane(JInternalFrame f) {
  56         super(f);
  57     }
  58 
  59     protected void addSubComponents() {
  60         add(systemLabel);
  61         add(iconButton);
  62         add(maxButton);
  63         add(closeButton);
  64     }
  65 
  66     protected void installDefaults() {
  67         super.installDefaults();
  68 
  69         titlePaneHeight = UIManager.getInt("InternalFrame.titlePaneHeight");
  70         buttonWidth     = UIManager.getInt("InternalFrame.titleButtonWidth")  - 4;
  71         buttonHeight    = UIManager.getInt("InternalFrame.titleButtonHeight") - 4;
  72 
  73         Object obj      = UIManager.get("InternalFrame.titleButtonToolTipsOn");
  74         hotTrackingOn = (obj instanceof Boolean) ? (Boolean)obj : true;
  75 
  76 
  77         if (XPStyle.getXP() != null) {
  78             // Fix for XP bug where sometimes these sizes aren't updated properly
  79             // Assume for now that height is correct and derive width using the
  80             // ratio from the uxtheme part
  81             buttonWidth = buttonHeight;
  82             Dimension d = XPStyle.getPartSize(Part.WP_CLOSEBUTTON, State.NORMAL);
  83             if (d != null && d.width != 0 && d.height != 0) {
  84                 buttonWidth = (int) ((float) buttonWidth * d.width / d.height);
  85             }
  86         } else {
  87             buttonWidth += 2;
  88             selectedTitleGradientColor =
  89                     UIManager.getColor("InternalFrame.activeTitleGradient");
  90             notSelectedTitleGradientColor =
  91                     UIManager.getColor("InternalFrame.inactiveTitleGradient");
  92             Color activeBorderColor =
  93                     UIManager.getColor("InternalFrame.activeBorderColor");
  94             setBorder(BorderFactory.createLineBorder(activeBorderColor, 1));
  95         }
  96     }
  97 
  98     protected void uninstallListeners() {
  99         // Get around protected method in superclass
 100         super.uninstallListeners();
 101     }
 102 
 103     protected void createButtons() {
 104         super.createButtons();
 105         if (XPStyle.getXP() != null) {
 106             iconButton.setContentAreaFilled(false);
 107             maxButton.setContentAreaFilled(false);
 108             closeButton.setContentAreaFilled(false);
 109         }
 110     }
 111 
 112     protected void setButtonIcons() {
 113         super.setButtonIcons();
 114 
 115         if (!hotTrackingOn) {
 116             iconButton.setToolTipText(null);
 117             maxButton.setToolTipText(null);
 118             closeButton.setToolTipText(null);
 119         }
 120     }
 121 
 122 
 123     public void paintComponent(Graphics g)  {
 124         XPStyle xp = XPStyle.getXP();
 125 
 126         paintTitleBackground(g);
 127 
 128         String title = frame.getTitle();
 129         if (title != null) {
 130             boolean isSelected = frame.isSelected();
 131             Font oldFont = g.getFont();
 132             Font newFont = (titleFont != null) ? titleFont : getFont();
 133             g.setFont(newFont);
 134 
 135             // Center text vertically.
 136             FontMetrics fm = SwingUtilities2.getFontMetrics(frame, g, newFont);
 137             int baseline = (getHeight() + fm.getAscent() - fm.getLeading() -
 138                     fm.getDescent()) / 2;
 139 
 140             int titleX;
 141             Rectangle r = new Rectangle(0, 0, 0, 0);
 142             if (frame.isIconifiable())  r = iconButton.getBounds();
 143             else if (frame.isMaximizable())  r = maxButton.getBounds();
 144             else if (frame.isClosable())  r = closeButton.getBounds();
 145             int titleW;
 146 
 147             if(WindowsGraphicsUtils.isLeftToRight(frame) ) {
 148                 if (r.x == 0)  r.x = frame.getWidth()-frame.getInsets().right;
 149                     titleX = systemLabel.getX() + systemLabel.getWidth() + 2;
 150                     if (xp != null) {
 151                         titleX += 2;
 152                     }
 153                     titleW = r.x - titleX - 3;
 154                     title = getTitle(frame.getTitle(), fm, titleW);
 155             } else {
 156                 titleX = systemLabel.getX() - 2
 157                          - SwingUtilities2.stringWidth(frame,fm,title);
 158             }
 159             if (xp != null) {
 160                 String shadowType = null;
 161                 if (isSelected) {
 162                     shadowType = xp.getString(this, Part.WP_CAPTION,
 163                                               State.ACTIVE, Prop.TEXTSHADOWTYPE);
 164                 }
 165                 if ("single".equalsIgnoreCase(shadowType)) {
 166                     Point shadowOffset = xp.getPoint(this, Part.WP_WINDOW, State.ACTIVE,
 167                                                      Prop.TEXTSHADOWOFFSET);
 168                     Color shadowColor  = xp.getColor(this, Part.WP_WINDOW, State.ACTIVE,
 169                                                      Prop.TEXTSHADOWCOLOR, null);
 170                     if (shadowOffset != null && shadowColor != null) {
 171                         g.setColor(shadowColor);
 172                         SwingUtilities2.drawString(frame, g, title,
 173                                      titleX + shadowOffset.x,
 174                                      baseline + shadowOffset.y);
 175                     }
 176                 }
 177             }
 178             g.setColor(isSelected ? selectedTextColor : notSelectedTextColor);
 179             SwingUtilities2.drawString(frame, g, title, titleX, baseline);
 180             g.setFont(oldFont);
 181         }
 182     }
 183 
 184     public Dimension getPreferredSize() {
 185         return getMinimumSize();
 186     }
 187 
 188     public Dimension getMinimumSize() {
 189         Dimension d = new Dimension(super.getMinimumSize());
 190         d.height = titlePaneHeight + 2;
 191 
 192         XPStyle xp = XPStyle.getXP();
 193         if (xp != null) {
 194             // Note: Don't know how to calculate height on XP,
 195             // the captionbarheight is 25 but native caption is 30 (maximized 26)
 196             if (frame.isMaximum()) {
 197                 d.height -= 1;
 198             } else {
 199                 d.height += 3;
 200             }
 201         }
 202         return d;
 203     }
 204 
 205     protected void paintTitleBackground(Graphics g) {
 206         XPStyle xp = XPStyle.getXP();
 207         if (xp != null) {
 208             Part part = frame.isIcon() ? Part.WP_MINCAPTION
 209                                        : (frame.isMaximum() ? Part.WP_MAXCAPTION
 210                                                             : Part.WP_CAPTION);
 211             State state = frame.isSelected() ? State.ACTIVE : State.INACTIVE;
 212             Skin skin = xp.getSkin(this, part);
 213             skin.paintSkin(g, 0,  0, getWidth(), getHeight(), state);
 214         } else {
 215             Boolean gradientsOn = (Boolean)LookAndFeel.getDesktopPropertyValue(
 216                 "win.frame.captionGradientsOn", Boolean.valueOf(false));
 217             if (gradientsOn.booleanValue() && g instanceof Graphics2D) {
 218                 Graphics2D g2 = (Graphics2D)g;
 219                 Paint savePaint = g2.getPaint();
 220 
 221                 boolean isSelected = frame.isSelected();
 222                 int w = getWidth();
 223 
 224                 if (isSelected) {
 225                     GradientPaint titleGradient = new GradientPaint(0,0,
 226                             selectedTitleColor,
 227                             (int)(w*.75),0,
 228                             selectedTitleGradientColor);
 229                     g2.setPaint(titleGradient);
 230                 } else {
 231                     GradientPaint titleGradient = new GradientPaint(0,0,
 232                             notSelectedTitleColor,
 233                             (int)(w*.75),0,
 234                             notSelectedTitleGradientColor);
 235                     g2.setPaint(titleGradient);
 236                 }
 237                 g2.fillRect(0, 0, getWidth(), getHeight());
 238                 g2.setPaint(savePaint);
 239             } else {
 240                 super.paintTitleBackground(g);
 241             }
 242         }
 243     }
 244 
 245     protected void assembleSystemMenu() {
 246         systemPopupMenu = new JPopupMenu();
 247         addSystemMenuItems(systemPopupMenu);
 248         enableActions();
 249         systemLabel = new JLabel(frame.getFrameIcon()) {
 250             protected void paintComponent(Graphics g) {
 251                 int x = 0;
 252                 int y = 0;
 253                 int w = getWidth();
 254                 int h = getHeight();
 255                 g = g.create();  // Create scratch graphics
 256                 if (isOpaque()) {
 257                     g.setColor(getBackground());
 258                     g.fillRect(0, 0, w, h);
 259                 }
 260                 Icon icon = getIcon();
 261                 int iconWidth = 0;
 262                 int iconHeight = 0;
 263                 if (icon != null &&
 264                     (iconWidth = icon.getIconWidth()) > 0 &&
 265                     (iconHeight = icon.getIconHeight()) > 0) {
 266 
 267                     // Set drawing scale to make icon scale to our desired size
 268                     double drawScale;
 269                     if (iconWidth > iconHeight) {
 270                         // Center icon vertically
 271                         y = (h - w*iconHeight/iconWidth) / 2;
 272                         drawScale = w / (double)iconWidth;
 273                     } else {
 274                         // Center icon horizontally
 275                         x = (w - h*iconWidth/iconHeight) / 2;
 276                         drawScale = h / (double)iconHeight;
 277                     }
 278                     ((Graphics2D)g).translate(x, y);
 279                     ((Graphics2D)g).scale(drawScale, drawScale);
 280                     icon.paintIcon(this, g, 0, 0);
 281                 }
 282                 g.dispose();
 283             }
 284         };
 285         systemLabel.addMouseListener(new MouseAdapter() {
 286             public void mouseClicked(MouseEvent e) {
 287                 if (e.getClickCount() == 2 && frame.isClosable() &&
 288                     !frame.isIcon()) {
 289                     systemPopupMenu.setVisible(false);
 290                     frame.doDefaultCloseAction();
 291                 }
 292                 else {
 293                     super.mouseClicked(e);
 294                 }
 295             }
 296             public void mousePressed(MouseEvent e) {
 297                 try {
 298                     frame.setSelected(true);
 299                 } catch(PropertyVetoException pve) {
 300                 }
 301                 showSystemPopupMenu(e.getComponent());
 302             }
 303         });
 304     }
 305 
 306     protected void addSystemMenuItems(JPopupMenu menu) {
 307         JMenuItem mi = (JMenuItem)menu.add(restoreAction);
 308         mi.setMnemonic('R');
 309         mi = (JMenuItem)menu.add(moveAction);
 310         mi.setMnemonic('M');
 311         mi = (JMenuItem)menu.add(sizeAction);
 312         mi.setMnemonic('S');
 313         mi = (JMenuItem)menu.add(iconifyAction);
 314         mi.setMnemonic('n');
 315         mi = (JMenuItem)menu.add(maximizeAction);
 316         mi.setMnemonic('x');
 317         systemPopupMenu.add(new JSeparator());
 318         mi = (JMenuItem)menu.add(closeAction);
 319         mi.setMnemonic('C');
 320     }
 321 
 322     protected void showSystemMenu(){
 323         showSystemPopupMenu(systemLabel);
 324     }
 325 
 326     private void showSystemPopupMenu(Component invoker){
 327         Dimension dim = new Dimension();
 328         Border border = frame.getBorder();
 329         if (border != null) {
 330             dim.width += border.getBorderInsets(frame).left +
 331                 border.getBorderInsets(frame).right;
 332             dim.height += border.getBorderInsets(frame).bottom +
 333                 border.getBorderInsets(frame).top;
 334         }
 335         if (!frame.isIcon()) {
 336             systemPopupMenu.show(invoker,
 337                 getX() - dim.width,
 338                 getY() + getHeight() - dim.height);
 339         } else {
 340             systemPopupMenu.show(invoker,
 341                 getX() - dim.width,
 342                 getY() - systemPopupMenu.getPreferredSize().height -
 343                      dim.height);
 344         }
 345     }
 346 
 347     protected PropertyChangeListener createPropertyChangeListener() {
 348         return new WindowsPropertyChangeHandler();
 349     }
 350 
 351     protected LayoutManager createLayout() {
 352         return new WindowsTitlePaneLayout();
 353     }
 354 
 355     public class WindowsTitlePaneLayout extends BasicInternalFrameTitlePane.TitlePaneLayout {
 356         private Insets captionMargin = null;
 357         private Insets contentMargin = null;
 358         private XPStyle xp = XPStyle.getXP();
 359 
 360         WindowsTitlePaneLayout() {
 361             if (xp != null) {
 362                 Component c = WindowsInternalFrameTitlePane.this;
 363                 captionMargin = xp.getMargin(c, Part.WP_CAPTION, null, Prop.CAPTIONMARGINS);
 364                 contentMargin = xp.getMargin(c, Part.WP_CAPTION, null, Prop.CONTENTMARGINS);
 365             }
 366             if (captionMargin == null) {
 367                 captionMargin = new Insets(0, 2, 0, 2);
 368             }
 369             if (contentMargin == null) {
 370                 contentMargin = new Insets(0, 0, 0, 0);
 371             }
 372         }
 373 
 374         private int layoutButton(JComponent button, Part part,
 375                                  int x, int y, int w, int h, int gap,
 376                                  boolean leftToRight) {
 377             if (!leftToRight) {
 378                 x -= w;
 379             }
 380             button.setBounds(x, y, w, h);
 381             if (leftToRight) {
 382                 x += w + 2;
 383             } else {
 384                 x -= 2;
 385             }
 386             return x;
 387         }
 388 
 389         public void layoutContainer(Container c) {
 390             boolean leftToRight = WindowsGraphicsUtils.isLeftToRight(frame);
 391             int x, y;
 392             int w = getWidth();
 393             int h = getHeight();
 394 
 395             // System button
 396             // Note: this icon is square, but the buttons aren't always.
 397             int iconSize = (xp != null) ? (h-2)*6/10 : h-4;
 398             if (xp != null) {
 399                 x = (leftToRight) ? captionMargin.left + 2 : w - captionMargin.right - 2;
 400             } else {
 401                 x = (leftToRight) ? captionMargin.left : w - captionMargin.right;
 402             }
 403             y = (h - iconSize) / 2;
 404             layoutButton(systemLabel, Part.WP_SYSBUTTON,
 405                          x, y, iconSize, iconSize, 0,
 406                          leftToRight);
 407 
 408             // Right hand buttons
 409             if (xp != null) {
 410                 x = (leftToRight) ? w - captionMargin.right - 2 : captionMargin.left + 2;
 411                 y = 1;  // XP seems to ignore margins and offset here
 412                 if (frame.isMaximum()) {
 413                     y += 1;
 414                 } else {
 415                     y += 5;
 416                 }
 417             } else {
 418                 x = (leftToRight) ? w - captionMargin.right : captionMargin.left;
 419                 y = (h - buttonHeight) / 2;
 420             }
 421 
 422             if(frame.isClosable()) {
 423                 x = layoutButton(closeButton, Part.WP_CLOSEBUTTON,
 424                                  x, y, buttonWidth, buttonHeight, 2,
 425                                  !leftToRight);
 426             }
 427 
 428             if(frame.isMaximizable()) {
 429                 x = layoutButton(maxButton, Part.WP_MAXBUTTON,
 430                                  x, y, buttonWidth, buttonHeight, (xp != null) ? 2 : 0,
 431                                  !leftToRight);
 432             }
 433 
 434             if(frame.isIconifiable()) {
 435                 layoutButton(iconButton, Part.WP_MINBUTTON,
 436                              x, y, buttonWidth, buttonHeight, 0,
 437                              !leftToRight);
 438             }
 439         }
 440     } // end WindowsTitlePaneLayout
 441 
 442     public class WindowsPropertyChangeHandler extends PropertyChangeHandler {
 443         public void propertyChange(PropertyChangeEvent evt) {
 444             String prop = (String)evt.getPropertyName();
 445 
 446             // Update the internal frame icon for the system menu.
 447             if (JInternalFrame.FRAME_ICON_PROPERTY.equals(prop) &&
 448                     systemLabel != null) {
 449                 systemLabel.setIcon(frame.getFrameIcon());
 450             }
 451 
 452             super.propertyChange(evt);
 453         }
 454     }
 455 
 456     /**
 457      * A versatile Icon implementation which can take an array of Icon
 458      * instances (typically <code>ImageIcon</code>s) and choose one that gives the best
 459      * quality for a given Graphics2D scale factor when painting.
 460      * <p>
 461      * The class is public so it can be instantiated by UIDefaults.ProxyLazyValue.
 462      * <p>
 463      * Note: We assume here that icons are square.
 464      */
 465     public static class ScalableIconUIResource implements Icon, UIResource {
 466         // We can use an arbitrary size here because we scale to it in paintIcon()
 467         private static final int SIZE = 16;
 468 
 469         private Icon[] icons;
 470 
 471         /**
 472          * @params objects an array of Icon or UIDefaults.LazyValue
 473          * <p>
 474          * The constructor is public so it can be called by UIDefaults.ProxyLazyValue.
 475          */
 476         public ScalableIconUIResource(Object[] objects) {
 477             this.icons = new Icon[objects.length];
 478 
 479             for (int i = 0; i < objects.length; i++) {
 480                 if (objects[i] instanceof UIDefaults.LazyValue) {
 481                     icons[i] = (Icon)((UIDefaults.LazyValue)objects[i]).createValue(null);
 482                 } else {
 483                     icons[i] = (Icon)objects[i];
 484                 }
 485             }
 486         }
 487 
 488         /**
 489          * @return the <code>Icon</code> closest to the requested size
 490          */
 491         protected Icon getBestIcon(int size) {
 492             if (icons != null && icons.length > 0) {
 493                 int bestIndex = 0;
 494                 int minDiff = Integer.MAX_VALUE;
 495                 for (int i=0; i < icons.length; i++) {
 496                     Icon icon = icons[i];
 497                     int iconSize;
 498                     if (icon != null && (iconSize = icon.getIconWidth()) > 0) {
 499                         int diff = Math.abs(iconSize - size);
 500                         if (diff < minDiff) {
 501                             minDiff = diff;
 502                             bestIndex = i;
 503                         }
 504                     }
 505                 }
 506                 return icons[bestIndex];
 507             } else {
 508                 return null;
 509             }
 510         }
 511 
 512         public void paintIcon(Component c, Graphics g, int x, int y) {
 513             Graphics2D g2d = (Graphics2D)g.create();
 514             // Calculate how big our drawing area is in pixels
 515             // Assume we are square
 516             int size = getIconWidth();
 517             double scale = g2d.getTransform().getScaleX();
 518             Icon icon = getBestIcon((int)(size * scale));
 519             int iconSize;
 520             if (icon != null && (iconSize = icon.getIconWidth()) > 0) {
 521                 // Set drawing scale to make icon act true to our reported size
 522                 double drawScale = size / (double)iconSize;
 523                 g2d.translate(x, y);
 524                 g2d.scale(drawScale, drawScale);
 525                 icon.paintIcon(c, g2d, 0, 0);
 526             }
 527             g2d.dispose();
 528         }
 529 
 530         public int getIconWidth() {
 531             return SIZE;
 532         }
 533 
 534         public int getIconHeight() {
 535             return SIZE;
 536         }
 537     }
 538 }