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