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 }