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