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;
 262                 int iconHeight;
 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 = menu.add(restoreAction);
 308         mi.setMnemonic(getButtonMnemonic("restore"));
 309         mi = menu.add(moveAction);
 310         mi.setMnemonic(getButtonMnemonic("move"));
 311         mi = menu.add(sizeAction);
 312         mi.setMnemonic(getButtonMnemonic("size"));
 313         mi = menu.add(iconifyAction);
 314         mi.setMnemonic(getButtonMnemonic("minimize"));
 315         mi = menu.add(maximizeAction);
 316         mi.setMnemonic(getButtonMnemonic("maximize"));
 317         menu.add(new JSeparator());
 318         mi = menu.add(closeAction);
 319         mi.setMnemonic(getButtonMnemonic("close"));
 320     }
 321 
 322     private static int getButtonMnemonic(String button) {
 323         try {
 324             return Integer.parseInt(UIManager.getString(
 325                     "InternalFrameTitlePane." + button + "Button.mnemonic"));
 326         } catch (NumberFormatException e) {
 327             return -1;
 328         }
 329     }
 330 
 331     protected void showSystemMenu(){
 332         showSystemPopupMenu(systemLabel);
 333     }
 334 
 335     private void showSystemPopupMenu(Component invoker){
 336         Dimension dim = new Dimension();
 337         Border border = frame.getBorder();
 338         if (border != null) {
 339             dim.width += border.getBorderInsets(frame).left +
 340                 border.getBorderInsets(frame).right;
 341             dim.height += border.getBorderInsets(frame).bottom +
 342                 border.getBorderInsets(frame).top;
 343         }
 344         if (!frame.isIcon()) {
 345             systemPopupMenu.show(invoker,
 346                 getX() - dim.width,
 347                 getY() + getHeight() - dim.height);
 348         } else {
 349             systemPopupMenu.show(invoker,
 350                 getX() - dim.width,
 351                 getY() - systemPopupMenu.getPreferredSize().height -
 352                      dim.height);
 353         }
 354     }
 355 
 356     protected PropertyChangeListener createPropertyChangeListener() {
 357         return new WindowsPropertyChangeHandler();
 358     }
 359 
 360     protected LayoutManager createLayout() {
 361         return new WindowsTitlePaneLayout();
 362     }
 363 
 364     public class WindowsTitlePaneLayout extends BasicInternalFrameTitlePane.TitlePaneLayout {
 365         private Insets captionMargin = null;
 366         private Insets contentMargin = null;
 367         private XPStyle xp = XPStyle.getXP();
 368 
 369         WindowsTitlePaneLayout() {
 370             if (xp != null) {
 371                 Component c = WindowsInternalFrameTitlePane.this;
 372                 captionMargin = xp.getMargin(c, Part.WP_CAPTION, null, Prop.CAPTIONMARGINS);
 373                 contentMargin = xp.getMargin(c, Part.WP_CAPTION, null, Prop.CONTENTMARGINS);
 374             }
 375             if (captionMargin == null) {
 376                 captionMargin = new Insets(0, 2, 0, 2);
 377             }
 378             if (contentMargin == null) {
 379                 contentMargin = new Insets(0, 0, 0, 0);
 380             }
 381         }
 382 
 383         private int layoutButton(JComponent button, Part part,
 384                                  int x, int y, int w, int h, int gap,
 385                                  boolean leftToRight) {
 386             if (!leftToRight) {
 387                 x -= w;
 388             }
 389             button.setBounds(x, y, w, h);
 390             if (leftToRight) {
 391                 x += w + 2;
 392             } else {
 393                 x -= 2;
 394             }
 395             return x;
 396         }
 397 
 398         public void layoutContainer(Container c) {
 399             boolean leftToRight = WindowsGraphicsUtils.isLeftToRight(frame);
 400             int x, y;
 401             int w = getWidth();
 402             int h = getHeight();
 403 
 404             // System button
 405             // Note: this icon is square, but the buttons aren't always.
 406             int iconSize = (xp != null) ? (h-2)*6/10 : h-4;
 407             if (xp != null) {
 408                 x = (leftToRight) ? captionMargin.left + 2 : w - captionMargin.right - 2;
 409             } else {
 410                 x = (leftToRight) ? captionMargin.left : w - captionMargin.right;
 411             }
 412             y = (h - iconSize) / 2;
 413             layoutButton(systemLabel, Part.WP_SYSBUTTON,
 414                          x, y, iconSize, iconSize, 0,
 415                          leftToRight);
 416 
 417             // Right hand buttons
 418             if (xp != null) {
 419                 x = (leftToRight) ? w - captionMargin.right - 2 : captionMargin.left + 2;
 420                 y = 1;  // XP seems to ignore margins and offset here
 421                 if (frame.isMaximum()) {
 422                     y += 1;
 423                 } else {
 424                     y += 5;
 425                 }
 426             } else {
 427                 x = (leftToRight) ? w - captionMargin.right : captionMargin.left;
 428                 y = (h - buttonHeight) / 2;
 429             }
 430 
 431             if(frame.isClosable()) {
 432                 x = layoutButton(closeButton, Part.WP_CLOSEBUTTON,
 433                                  x, y, buttonWidth, buttonHeight, 2,
 434                                  !leftToRight);
 435             }
 436 
 437             if(frame.isMaximizable()) {
 438                 x = layoutButton(maxButton, Part.WP_MAXBUTTON,
 439                                  x, y, buttonWidth, buttonHeight, (xp != null) ? 2 : 0,
 440                                  !leftToRight);
 441             }
 442 
 443             if(frame.isIconifiable()) {
 444                 layoutButton(iconButton, Part.WP_MINBUTTON,
 445                              x, y, buttonWidth, buttonHeight, 0,
 446                              !leftToRight);
 447             }
 448         }
 449     } // end WindowsTitlePaneLayout
 450 
 451     public class WindowsPropertyChangeHandler extends PropertyChangeHandler {
 452         public void propertyChange(PropertyChangeEvent evt) {
 453             String prop = evt.getPropertyName();
 454 
 455             // Update the internal frame icon for the system menu.
 456             if (JInternalFrame.FRAME_ICON_PROPERTY.equals(prop) &&
 457                     systemLabel != null) {
 458                 systemLabel.setIcon(frame.getFrameIcon());
 459             }
 460 
 461             super.propertyChange(evt);
 462         }
 463     }
 464 
 465     /**
 466      * A versatile Icon implementation which can take an array of Icon
 467      * instances (typically <code>ImageIcon</code>s) and choose one that gives the best
 468      * quality for a given Graphics2D scale factor when painting.
 469      * <p>
 470      * The class is public so it can be instantiated by UIDefaults.ProxyLazyValue.
 471      * <p>
 472      * Note: We assume here that icons are square.
 473      */
 474     public static class ScalableIconUIResource implements Icon, UIResource {
 475         // We can use an arbitrary size here because we scale to it in paintIcon()
 476         private static final int SIZE = 16;
 477 
 478         private Icon[] icons;
 479 
 480         /**
 481          * @params objects an array of Icon or UIDefaults.LazyValue
 482          * <p>
 483          * The constructor is public so it can be called by UIDefaults.ProxyLazyValue.
 484          */
 485         public ScalableIconUIResource(Object[] objects) {
 486             this.icons = new Icon[objects.length];
 487 
 488             for (int i = 0; i < objects.length; i++) {
 489                 if (objects[i] instanceof UIDefaults.LazyValue) {
 490                     icons[i] = (Icon)((UIDefaults.LazyValue)objects[i]).createValue(null);
 491                 } else {
 492                     icons[i] = (Icon)objects[i];
 493                 }
 494             }
 495         }
 496 
 497         /**
 498          * @return the <code>Icon</code> closest to the requested size
 499          */
 500         protected Icon getBestIcon(int size) {
 501             if (icons != null && icons.length > 0) {
 502                 int bestIndex = 0;
 503                 int minDiff = Integer.MAX_VALUE;
 504                 for (int i=0; i < icons.length; i++) {
 505                     Icon icon = icons[i];
 506                     int iconSize;
 507                     if (icon != null && (iconSize = icon.getIconWidth()) > 0) {
 508                         int diff = Math.abs(iconSize - size);
 509                         if (diff < minDiff) {
 510                             minDiff = diff;
 511                             bestIndex = i;
 512                         }
 513                     }
 514                 }
 515                 return icons[bestIndex];
 516             } else {
 517                 return null;
 518             }
 519         }
 520 
 521         public void paintIcon(Component c, Graphics g, int x, int y) {
 522             Graphics2D g2d = (Graphics2D)g.create();
 523             // Calculate how big our drawing area is in pixels
 524             // Assume we are square
 525             int size = getIconWidth();
 526             double scale = g2d.getTransform().getScaleX();
 527             Icon icon = getBestIcon((int)(size * scale));
 528             int iconSize;
 529             if (icon != null && (iconSize = icon.getIconWidth()) > 0) {
 530                 // Set drawing scale to make icon act true to our reported size
 531                 double drawScale = size / (double)iconSize;
 532                 g2d.translate(x, y);
 533                 g2d.scale(drawScale, drawScale);
 534                 icon.paintIcon(c, g2d, 0, 0);
 535             }
 536             g2d.dispose();
 537         }
 538 
 539         public int getIconWidth() {
 540             return SIZE;
 541         }
 542 
 543         public int getIconHeight() {
 544             return SIZE;
 545         }
 546     }
 547 }