1 /* 2 * Copyright (c) 1998, 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 javax.swing.plaf.metal; 27 28 import sun.swing.SwingUtilities2; 29 import java.awt.*; 30 import java.awt.event.*; 31 import javax.swing.*; 32 import javax.swing.border.*; 33 import javax.swing.event.InternalFrameEvent; 34 import java.util.EventListener; 35 import java.beans.PropertyChangeListener; 36 import java.beans.PropertyChangeEvent; 37 import javax.swing.plaf.basic.BasicInternalFrameTitlePane; 38 39 40 /** 41 * Class that manages a JLF title bar 42 * @author Steve Wilson 43 * @author Brian Beck 44 * @since 1.3 45 */ 46 @SuppressWarnings("serial") // Superclass is not serializable across versions 47 public class MetalInternalFrameTitlePane extends BasicInternalFrameTitlePane { 48 49 /** 50 * The value {@code isPalette} 51 */ 52 protected boolean isPalette = false; 53 54 /** 55 * The palette close icon. 56 */ 57 protected Icon paletteCloseIcon; 58 59 /** 60 * The height of the palette title. 61 */ 62 protected int paletteTitleHeight; 63 64 private static final Border handyEmptyBorder = new EmptyBorder(0,0,0,0); 65 66 /** 67 * Key used to lookup Color from UIManager. If this is null, 68 * <code>getWindowTitleBackground</code> is used. 69 */ 70 private String selectedBackgroundKey; 71 /** 72 * Key used to lookup Color from UIManager. If this is null, 73 * <code>getWindowTitleForeground</code> is used. 74 */ 75 private String selectedForegroundKey; 76 /** 77 * Key used to lookup shadow color from UIManager. If this is null, 78 * <code>getPrimaryControlDarkShadow</code> is used. 79 */ 80 private String selectedShadowKey; 81 /** 82 * Boolean indicating the state of the <code>JInternalFrame</code>s 83 * closable property at <code>updateUI</code> time. 84 */ 85 private boolean wasClosable; 86 87 int buttonsWidth = 0; 88 89 MetalBumps activeBumps 90 = new MetalBumps( 0, 0, 91 MetalLookAndFeel.getPrimaryControlHighlight(), 92 MetalLookAndFeel.getPrimaryControlDarkShadow(), 93 (UIManager.get("InternalFrame.activeTitleGradient") != null) ? null : 94 MetalLookAndFeel.getPrimaryControl() ); 95 MetalBumps inactiveBumps 96 = new MetalBumps( 0, 0, 97 MetalLookAndFeel.getControlHighlight(), 98 MetalLookAndFeel.getControlDarkShadow(), 99 (UIManager.get("InternalFrame.inactiveTitleGradient") != null) ? null : 100 MetalLookAndFeel.getControl() ); 101 MetalBumps paletteBumps; 102 103 private Color activeBumpsHighlight = MetalLookAndFeel. 104 getPrimaryControlHighlight(); 105 private Color activeBumpsShadow = MetalLookAndFeel. 106 getPrimaryControlDarkShadow(); 107 108 /** 109 * Constructs a new instance of {@code MetalInternalFrameTitlePane} 110 * 111 * @param f an instance of {@code JInternalFrame} 112 */ 113 public MetalInternalFrameTitlePane(JInternalFrame f) { 114 super( f ); 115 } 116 117 public void addNotify() { 118 super.addNotify(); 119 // This is done here instead of in installDefaults as I was worried 120 // that the BasicInternalFrameUI might not be fully initialized, and 121 // that if this resets the closable state the BasicInternalFrameUI 122 // Listeners that get notified might be in an odd/uninitialized state. 123 updateOptionPaneState(); 124 } 125 126 protected void installDefaults() { 127 super.installDefaults(); 128 setFont( UIManager.getFont("InternalFrame.titleFont") ); 129 paletteTitleHeight 130 = UIManager.getInt("InternalFrame.paletteTitleHeight"); 131 paletteCloseIcon = UIManager.getIcon("InternalFrame.paletteCloseIcon"); 132 wasClosable = frame.isClosable(); 133 selectedForegroundKey = selectedBackgroundKey = null; 134 if (MetalLookAndFeel.usingOcean()) { 135 setOpaque(true); 136 } 137 } 138 139 protected void uninstallDefaults() { 140 super.uninstallDefaults(); 141 if (wasClosable != frame.isClosable()) { 142 frame.setClosable(wasClosable); 143 } 144 } 145 146 protected void createButtons() { 147 super.createButtons(); 148 149 Boolean paintActive = frame.isSelected() ? Boolean.TRUE:Boolean.FALSE; 150 iconButton.putClientProperty("paintActive", paintActive); 151 iconButton.setBorder(handyEmptyBorder); 152 153 maxButton.putClientProperty("paintActive", paintActive); 154 maxButton.setBorder(handyEmptyBorder); 155 156 closeButton.putClientProperty("paintActive", paintActive); 157 closeButton.setBorder(handyEmptyBorder); 158 159 // The palette close icon isn't opaque while the regular close icon is. 160 // This makes sure palette close buttons have the right background. 161 closeButton.setBackground(MetalLookAndFeel.getPrimaryControlShadow()); 162 163 if (MetalLookAndFeel.usingOcean()) { 164 iconButton.setContentAreaFilled(false); 165 maxButton.setContentAreaFilled(false); 166 closeButton.setContentAreaFilled(false); 167 } 168 } 169 170 /** 171 * Override the parent's method to do nothing. Metal frames do not 172 * have system menus. 173 */ 174 protected void assembleSystemMenu() {} 175 176 /** 177 * Override the parent's method to do nothing. Metal frames do not 178 * have system menus. 179 */ 180 protected void addSystemMenuItems(JMenu systemMenu) {} 181 182 /** 183 * Override the parent's method to do nothing. Metal frames do not 184 * have system menus. 185 */ 186 protected void showSystemMenu() {} 187 188 /** 189 * Override the parent's method avoid creating a menu bar. Metal frames 190 * do not have system menus. 191 */ 192 protected void addSubComponents() { 193 add(iconButton); 194 add(maxButton); 195 add(closeButton); 196 } 197 198 protected PropertyChangeListener createPropertyChangeListener() { 199 return new MetalPropertyChangeHandler(); 200 } 201 202 protected LayoutManager createLayout() { 203 return new MetalTitlePaneLayout(); 204 } 205 206 class MetalPropertyChangeHandler 207 extends BasicInternalFrameTitlePane.PropertyChangeHandler 208 { 209 public void propertyChange(PropertyChangeEvent evt) { 210 String prop = evt.getPropertyName(); 211 if( prop.equals(JInternalFrame.IS_SELECTED_PROPERTY) ) { 212 Boolean b = (Boolean)evt.getNewValue(); 213 iconButton.putClientProperty("paintActive", b); 214 closeButton.putClientProperty("paintActive", b); 215 maxButton.putClientProperty("paintActive", b); 216 } 217 else if ("JInternalFrame.messageType".equals(prop)) { 218 updateOptionPaneState(); 219 frame.repaint(); 220 } 221 super.propertyChange(evt); 222 } 223 } 224 225 class MetalTitlePaneLayout extends TitlePaneLayout { 226 public void addLayoutComponent(String name, Component c) {} 227 public void removeLayoutComponent(Component c) {} 228 public Dimension preferredLayoutSize(Container c) { 229 return minimumLayoutSize(c); 230 } 231 232 public Dimension minimumLayoutSize(Container c) { 233 // Compute width. 234 int width = 30; 235 if (frame.isClosable()) { 236 width += 21; 237 } 238 if (frame.isMaximizable()) { 239 width += 16 + (frame.isClosable() ? 10 : 4); 240 } 241 if (frame.isIconifiable()) { 242 width += 16 + (frame.isMaximizable() ? 2 : 243 (frame.isClosable() ? 10 : 4)); 244 } 245 FontMetrics fm = frame.getFontMetrics(getFont()); 246 String frameTitle = frame.getTitle(); 247 int title_w = frameTitle != null ? SwingUtilities2.stringWidth( 248 frame, fm, frameTitle) : 0; 249 int title_length = frameTitle != null ? frameTitle.length() : 0; 250 251 if (title_length > 2) { 252 int subtitle_w = SwingUtilities2.stringWidth(frame, fm, 253 frame.getTitle().substring(0, 2) + "..."); 254 width += (title_w < subtitle_w) ? title_w : subtitle_w; 255 } 256 else { 257 width += title_w; 258 } 259 260 // Compute height. 261 int height; 262 if (isPalette) { 263 height = paletteTitleHeight; 264 } else { 265 int fontHeight = fm.getHeight(); 266 fontHeight += 7; 267 Icon icon = frame.getFrameIcon(); 268 int iconHeight = 0; 269 if (icon != null) { 270 // SystemMenuBar forces the icon to be 16x16 or less. 271 iconHeight = Math.min(icon.getIconHeight(), 16); 272 } 273 iconHeight += 5; 274 height = Math.max(fontHeight, iconHeight); 275 } 276 277 return new Dimension(width, height); 278 } 279 280 public void layoutContainer(Container c) { 281 boolean leftToRight = MetalUtils.isLeftToRight(frame); 282 283 int w = getWidth(); 284 int x = leftToRight ? w : 0; 285 int y = 2; 286 int spacing; 287 288 // assumes all buttons have the same dimensions 289 // these dimensions include the borders 290 int buttonHeight = closeButton.getIcon().getIconHeight(); 291 int buttonWidth = closeButton.getIcon().getIconWidth(); 292 293 if(frame.isClosable()) { 294 if (isPalette) { 295 spacing = 3; 296 x += leftToRight ? -spacing -(buttonWidth+2) : spacing; 297 closeButton.setBounds(x, y, buttonWidth+2, getHeight()-4); 298 if( !leftToRight ) x += (buttonWidth+2); 299 } else { 300 spacing = 4; 301 x += leftToRight ? -spacing -buttonWidth : spacing; 302 closeButton.setBounds(x, y, buttonWidth, buttonHeight); 303 if( !leftToRight ) x += buttonWidth; 304 } 305 } 306 307 if(frame.isMaximizable() && !isPalette ) { 308 spacing = frame.isClosable() ? 10 : 4; 309 x += leftToRight ? -spacing -buttonWidth : spacing; 310 maxButton.setBounds(x, y, buttonWidth, buttonHeight); 311 if( !leftToRight ) x += buttonWidth; 312 } 313 314 if(frame.isIconifiable() && !isPalette ) { 315 spacing = frame.isMaximizable() ? 2 316 : (frame.isClosable() ? 10 : 4); 317 x += leftToRight ? -spacing -buttonWidth : spacing; 318 iconButton.setBounds(x, y, buttonWidth, buttonHeight); 319 if( !leftToRight ) x += buttonWidth; 320 } 321 322 buttonsWidth = leftToRight ? w - x : x; 323 } 324 } 325 326 /** 327 * Paints palette. 328 * 329 * @param g a instance of {@code Graphics} 330 */ 331 public void paintPalette(Graphics g) { 332 boolean leftToRight = MetalUtils.isLeftToRight(frame); 333 334 int width = getWidth(); 335 int height = getHeight(); 336 337 if (paletteBumps == null) { 338 paletteBumps 339 = new MetalBumps(0, 0, 340 MetalLookAndFeel.getPrimaryControlHighlight(), 341 MetalLookAndFeel.getPrimaryControlInfo(), 342 MetalLookAndFeel.getPrimaryControlShadow() ); 343 } 344 345 Color background = MetalLookAndFeel.getPrimaryControlShadow(); 346 Color darkShadow = MetalLookAndFeel.getPrimaryControlDarkShadow(); 347 348 g.setColor(background); 349 g.fillRect(0, 0, width, height); 350 351 g.setColor( darkShadow ); 352 g.drawLine ( 0, height - 1, width, height -1); 353 354 int xOffset = leftToRight ? 4 : buttonsWidth + 4; 355 int bumpLength = width - buttonsWidth -2*4; 356 int bumpHeight = getHeight() - 4; 357 paletteBumps.setBumpArea( bumpLength, bumpHeight ); 358 paletteBumps.paintIcon( this, g, xOffset, 2); 359 } 360 361 public void paintComponent(Graphics g) { 362 if(isPalette) { 363 paintPalette(g); 364 return; 365 } 366 367 boolean leftToRight = MetalUtils.isLeftToRight(frame); 368 boolean isSelected = frame.isSelected(); 369 370 int width = getWidth(); 371 int height = getHeight(); 372 373 Color background = null; 374 Color foreground = null; 375 Color shadow = null; 376 377 MetalBumps bumps; 378 String gradientKey; 379 380 if (isSelected) { 381 if (!MetalLookAndFeel.usingOcean()) { 382 closeButton.setContentAreaFilled(true); 383 maxButton.setContentAreaFilled(true); 384 iconButton.setContentAreaFilled(true); 385 } 386 if (selectedBackgroundKey != null) { 387 background = UIManager.getColor(selectedBackgroundKey); 388 } 389 if (background == null) { 390 background = MetalLookAndFeel.getWindowTitleBackground(); 391 } 392 if (selectedForegroundKey != null) { 393 foreground = UIManager.getColor(selectedForegroundKey); 394 } 395 if (selectedShadowKey != null) { 396 shadow = UIManager.getColor(selectedShadowKey); 397 } 398 if (shadow == null) { 399 shadow = MetalLookAndFeel.getPrimaryControlDarkShadow(); 400 } 401 if (foreground == null) { 402 foreground = MetalLookAndFeel.getWindowTitleForeground(); 403 } 404 activeBumps.setBumpColors(activeBumpsHighlight, activeBumpsShadow, 405 UIManager.get("InternalFrame.activeTitleGradient") != 406 null ? null : background); 407 bumps = activeBumps; 408 gradientKey = "InternalFrame.activeTitleGradient"; 409 } else { 410 if (!MetalLookAndFeel.usingOcean()) { 411 closeButton.setContentAreaFilled(false); 412 maxButton.setContentAreaFilled(false); 413 iconButton.setContentAreaFilled(false); 414 } 415 background = MetalLookAndFeel.getWindowTitleInactiveBackground(); 416 foreground = MetalLookAndFeel.getWindowTitleInactiveForeground(); 417 shadow = MetalLookAndFeel.getControlDarkShadow(); 418 bumps = inactiveBumps; 419 gradientKey = "InternalFrame.inactiveTitleGradient"; 420 } 421 422 if (!MetalUtils.drawGradient(this, g, gradientKey, 0, 0, width, 423 height, true)) { 424 g.setColor(background); 425 g.fillRect(0, 0, width, height); 426 } 427 428 g.setColor( shadow ); 429 g.drawLine ( 0, height - 1, width, height -1); 430 g.drawLine ( 0, 0, 0 ,0); 431 g.drawLine ( width - 1, 0 , width -1, 0); 432 433 434 int titleLength; 435 int xOffset = leftToRight ? 5 : width - 5; 436 String frameTitle = frame.getTitle(); 437 438 Icon icon = frame.getFrameIcon(); 439 if ( icon != null ) { 440 if( !leftToRight ) 441 xOffset -= icon.getIconWidth(); 442 int iconY = ((height / 2) - (icon.getIconHeight() /2)); 443 icon.paintIcon(frame, g, xOffset, iconY); 444 xOffset += leftToRight ? icon.getIconWidth() + 5 : -5; 445 } 446 447 if(frameTitle != null) { 448 Font f = getFont(); 449 g.setFont(f); 450 FontMetrics fm = SwingUtilities2.getFontMetrics(frame, g, f); 451 int fHeight = fm.getHeight(); 452 453 g.setColor(foreground); 454 455 int yOffset = ( (height - fm.getHeight() ) / 2 ) + fm.getAscent(); 456 457 Rectangle rect = new Rectangle(0, 0, 0, 0); 458 if (frame.isIconifiable()) { rect = iconButton.getBounds(); } 459 else if (frame.isMaximizable()) { rect = maxButton.getBounds(); } 460 else if (frame.isClosable()) { rect = closeButton.getBounds(); } 461 int titleW; 462 463 if( leftToRight ) { 464 if (rect.x == 0) { 465 rect.x = frame.getWidth()-frame.getInsets().right-2; 466 } 467 titleW = rect.x - xOffset - 4; 468 frameTitle = getTitle(frameTitle, fm, titleW); 469 } else { 470 titleW = xOffset - rect.x - rect.width - 4; 471 frameTitle = getTitle(frameTitle, fm, titleW); 472 xOffset -= SwingUtilities2.stringWidth(frame, fm, frameTitle); 473 } 474 475 titleLength = SwingUtilities2.stringWidth(frame, fm, frameTitle); 476 SwingUtilities2.drawString(frame, g, frameTitle, xOffset, yOffset); 477 xOffset += leftToRight ? titleLength + 5 : -5; 478 } 479 480 int bumpXOffset; 481 int bumpLength; 482 if( leftToRight ) { 483 bumpLength = width - buttonsWidth - xOffset - 5; 484 bumpXOffset = xOffset; 485 } else { 486 bumpLength = xOffset - buttonsWidth - 5; 487 bumpXOffset = buttonsWidth + 5; 488 } 489 int bumpYOffset = 3; 490 int bumpHeight = getHeight() - (2 * bumpYOffset); 491 bumps.setBumpArea( bumpLength, bumpHeight ); 492 bumps.paintIcon(this, g, bumpXOffset, bumpYOffset); 493 } 494 495 /** 496 * If {@code b} is {@code true}, sets palette icons. 497 * 498 * @param b if {@code true}, sets palette icons 499 */ 500 public void setPalette(boolean b) { 501 isPalette = b; 502 503 if (isPalette) { 504 closeButton.setIcon(paletteCloseIcon); 505 if( frame.isMaximizable() ) 506 remove(maxButton); 507 if( frame.isIconifiable() ) 508 remove(iconButton); 509 } else { 510 closeButton.setIcon(closeIcon); 511 if( frame.isMaximizable() ) 512 add(maxButton); 513 if( frame.isIconifiable() ) 514 add(iconButton); 515 } 516 revalidate(); 517 repaint(); 518 } 519 520 /** 521 * Updates any state dependant upon the JInternalFrame being shown in 522 * a <code>JOptionPane</code>. 523 */ 524 private void updateOptionPaneState() { 525 int type = -2; 526 boolean closable = wasClosable; 527 Object obj = frame.getClientProperty("JInternalFrame.messageType"); 528 529 if (obj == null) { 530 // Don't change the closable state unless in an JOptionPane. 531 return; 532 } 533 if (obj instanceof Integer) { 534 type = ((Integer) obj).intValue(); 535 } 536 switch (type) { 537 case JOptionPane.ERROR_MESSAGE: 538 selectedBackgroundKey = 539 "OptionPane.errorDialog.titlePane.background"; 540 selectedForegroundKey = 541 "OptionPane.errorDialog.titlePane.foreground"; 542 selectedShadowKey = "OptionPane.errorDialog.titlePane.shadow"; 543 closable = false; 544 break; 545 case JOptionPane.QUESTION_MESSAGE: 546 selectedBackgroundKey = 547 "OptionPane.questionDialog.titlePane.background"; 548 selectedForegroundKey = 549 "OptionPane.questionDialog.titlePane.foreground"; 550 selectedShadowKey = 551 "OptionPane.questionDialog.titlePane.shadow"; 552 closable = false; 553 break; 554 case JOptionPane.WARNING_MESSAGE: 555 selectedBackgroundKey = 556 "OptionPane.warningDialog.titlePane.background"; 557 selectedForegroundKey = 558 "OptionPane.warningDialog.titlePane.foreground"; 559 selectedShadowKey = "OptionPane.warningDialog.titlePane.shadow"; 560 closable = false; 561 break; 562 case JOptionPane.INFORMATION_MESSAGE: 563 case JOptionPane.PLAIN_MESSAGE: 564 selectedBackgroundKey = selectedForegroundKey = selectedShadowKey = 565 null; 566 closable = false; 567 break; 568 default: 569 selectedBackgroundKey = selectedForegroundKey = selectedShadowKey = 570 null; 571 break; 572 } 573 if (closable != frame.isClosable()) { 574 frame.setClosable(closable); 575 } 576 } 577 }