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