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 }