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 }