1 /* 2 * @(#)Quaqua14TitlePane.java 2.1 2008-05-10 3 * 4 * Copyright (c) 2006-2008 Werner Randelshofer 5 * Staldenmattweg 2, Immensee, CH-6405, Switzerland. 6 * All rights reserved. 7 * 8 * The copyright of this software is owned by Werner Randelshofer. 9 * You may not use, copy or modify this software, except in 10 * accordance with the license agreement you entered into with 11 * Werner Randelshofer. For details see accompanying license terms. 12 */ 13 14 package ch.randelshofer.quaqua; 15 16 import ch.randelshofer.quaqua.color.TextureColor; 17 import java.awt.*; 18 import java.awt.event.*; 19 import java.beans.*; 20 import javax.swing.*; 21 import javax.swing.border.*; 22 import javax.swing.plaf.*; 23 import javax.swing.plaf.basic.*; 24 import java.awt.geom.*; 25 26 /** 27 * Quaqua14TitlePane. 28 * 29 * @author Werner Randelshofer 30 * @version 2.1 2008-05-10 Added support for property "Window.documentModified". 31 * <br>2.0.1 2008-04-08 Changed ... to … . 32 * <br>2.0 2007-11-01 Rewritten for Leopard support. 33 * <br>1.0.1 2006-06-11 Minor tweaking. 34 * <br>1.0 February 12, 2006 Created. 35 */ 36 public class Quaqua14TitlePane extends JComponent { 37 private static final Border handyEmptyBorder = new EmptyBorder(0,0,0,0); 38 private static final int IMAGE_HEIGHT = 16; 39 private static final int IMAGE_WIDTH = 16; 40 41 /** 42 * PropertyChangeListener added to the Window. 43 */ 44 private PropertyChangeListener windowPropertyListener; 45 /** 46 * PropertyChangeListener added to the JRootPane. 47 */ 48 private PropertyChangeListener rootPropertyListener; 49 50 /** 51 * JMenuBar, typically renders the system menu items. 52 */ 53 private JMenuBar menuBar; 54 /** 55 * Action used to close the Window. 56 */ 57 private Action closeAction; 58 59 /** 60 * Action used to iconify the Frame. 61 */ 62 private Action iconifyAction; 63 64 /** 65 * Action to restore the Frame size. 66 */ 67 private Action restoreAction; 68 69 /** 70 * Action to restore the Frame size. 71 */ 72 private Action maximizeAction; 73 74 /** 75 * Button used to maximize or restore the Frame. 76 */ 77 private JButton toggleButton; 78 79 /** 80 * Button used to maximize or restore the Frame. 81 */ 82 private JButton iconifyButton; 83 84 /** 85 * Button used to maximize or restore the Frame. 86 */ 87 private JButton closeButton; 88 89 /** 90 * Icon used for toggleButton when window is normal size. 91 */ 92 private Icon maximizeIcon; 93 94 /** 95 * Icon used for toggleButton when window is maximized. 96 */ 97 private Icon minimizeIcon; 98 99 /** 100 * Listens for changes in the state of the Window listener to update 101 * the state of the widgets. 102 */ 103 private WindowListener windowListener; 104 105 /** 106 * Window we're currently in. 107 */ 108 private Window window; 109 110 /** 111 * JRootPane rendering for. 112 */ 113 private JRootPane rootPane; 114 115 /** 116 * Room remaining in title for bumps. 117 */ 118 private int buttonsWidth; 119 120 /** 121 * Buffered Frame.state property. As state isn't bound, this is kept 122 * to determine when to avoid updating widgets. 123 */ 124 private int state; 125 126 /** 127 * Quaqua14RootPaneUI that created us. 128 */ 129 private Quaqua14RootPaneUI rootPaneUI; 130 131 132 // Colors 133 private Color inactiveBackground = UIManager.getColor("inactiveCaption"); 134 private Color inactiveForeground = UIManager.getColor("inactiveCaptionText"); 135 private Color inactiveShadow = new Color(0xb3b3b3); 136 private Color activeBackground = null; 137 private Color activeForeground = null; 138 private Color activeShadow = null; 139 140 /* Components used for "arming" the window buttons. 141 */ 142 private JComponent armer1, armer2; 143 Quaqua14TitlePane(JRootPane root, Quaqua14RootPaneUI ui)144 public Quaqua14TitlePane(JRootPane root, Quaqua14RootPaneUI ui) { 145 this.rootPane = root; 146 rootPaneUI = ui; 147 148 state = -1; 149 150 rootPropertyListener = createRootPropertyChangeListener(); 151 rootPane.addPropertyChangeListener(rootPropertyListener); 152 153 installSubcomponents(); 154 updateIconsAndTextures(); 155 installDefaults(); 156 setLayout(createLayout()); 157 } 158 159 /** 160 * Uninstalls the necessary state. 161 */ uninstall()162 private void uninstall() { 163 uninstallListeners(); 164 window = null; 165 removeAll(); 166 } 167 168 /** 169 * Installs the necessary listeners. 170 */ installListeners()171 private void installListeners() { 172 if (window != null) { 173 windowListener = createWindowListener(); 174 window.addWindowListener(windowListener); 175 windowPropertyListener = createWindowPropertyChangeListener(); 176 window.addPropertyChangeListener(windowPropertyListener); 177 } 178 } 179 180 /** 181 * Uninstalls the necessary listeners. 182 */ uninstallListeners()183 private void uninstallListeners() { 184 if (window != null) { 185 window.removeWindowListener(windowListener); 186 window.removePropertyChangeListener(windowPropertyListener); 187 } 188 } 189 190 /** 191 * Returns the <code>WindowListener</code> to add to the 192 * <code>Window</code>. 193 */ createWindowListener()194 private WindowListener createWindowListener() { 195 return new WindowHandler(); 196 } 197 198 /** 199 * Returns the <code>PropertyChangeListener</code> to install on 200 * the <code>Window</code>. 201 */ createWindowPropertyChangeListener()202 private PropertyChangeListener createWindowPropertyChangeListener() { 203 return new WindowPropertyHandler(); 204 } 205 /** 206 * Returns the <code>PropertyChangeListener</code> to install on 207 * the <code>Window</code>. 208 */ createRootPropertyChangeListener()209 private PropertyChangeListener createRootPropertyChangeListener() { 210 return new RootPropertyHandler(); 211 } 212 213 /** 214 * Returns the <code>JRootPane</code> this was created for. 215 */ getRootPane()216 public JRootPane getRootPane() { 217 return rootPane; 218 } 219 220 /** 221 * Returns the decoration style of the <code>JRootPane</code>. 222 */ getWindowDecorationStyle()223 private int getWindowDecorationStyle() { 224 return getRootPane().getWindowDecorationStyle(); 225 } 226 addNotify()227 public void addNotify() { 228 super.addNotify(); 229 230 uninstallListeners(); 231 232 window = SwingUtilities.getWindowAncestor(this); 233 if (window != null) { 234 if (window instanceof Frame) { 235 setState(((Frame)window).getExtendedState()); 236 } else { 237 setState(0); 238 } 239 setActive(window.isActive()); 240 installListeners(); 241 } 242 } 243 removeNotify()244 public void removeNotify() { 245 super.removeNotify(); 246 247 uninstallListeners(); 248 window = null; 249 } 250 251 /** 252 * Adds any sub-Components contained in the <code>Quaqua14TitlePane</code>. 253 */ installSubcomponents()254 private void installSubcomponents() { 255 //if (getWindowDecorationStyle() == JRootPane.FRAME) { 256 createActions(); 257 createButtons(); 258 add(iconifyButton); 259 add(toggleButton); 260 add(closeButton); 261 //} 262 } 263 264 /** 265 * Installs the fonts and necessary properties on the Quaqua14TitlePane. 266 */ installDefaults()267 private void installDefaults() { 268 // We get the font from the root pane. 269 //setFont(UIManager.getFont("InternalFrame.titleFont", getLocale())); 270 271 // make title pane translucent 272 } 273 274 /** 275 * Uninstalls any previously installed UI values. 276 */ uninstallDefaults()277 private void uninstallDefaults() { 278 } 279 280 /** 281 * Closes the Window. 282 */ close()283 private void close() { 284 Window window = getWindow(); 285 286 if (window != null) { 287 window.dispatchEvent(new WindowEvent( 288 window, WindowEvent.WINDOW_CLOSING)); 289 } 290 } 291 292 /** 293 * Iconifies the Frame. 294 */ iconify()295 private void iconify() { 296 Frame frame = getFrame(); 297 if (frame != null) { 298 frame.setExtendedState(state | Frame.ICONIFIED); 299 } 300 } 301 302 /** 303 * Maximizes the Frame. 304 */ maximize()305 private void maximize() { 306 Frame frame = getFrame(); 307 if (frame != null) { 308 frame.setExtendedState(state | Frame.MAXIMIZED_BOTH); 309 } 310 } 311 312 /** 313 * Restores the Frame size. 314 */ restore()315 private void restore() { 316 Frame frame = getFrame(); 317 318 if (frame == null) { 319 return; 320 } 321 322 if ((state & Frame.ICONIFIED) != 0) { 323 frame.setExtendedState(state & ~Frame.ICONIFIED); 324 } else { 325 frame.setExtendedState(state & ~Frame.MAXIMIZED_BOTH); 326 } 327 } 328 329 /** 330 * Create the <code>Action</code>s that get associated with the 331 * buttons and menu items. 332 */ createActions()333 private void createActions() { 334 closeAction = new CloseAction(); 335 iconifyAction = new IconifyAction(); 336 restoreAction = new RestoreAction(); 337 maximizeAction = new MaximizeAction(); 338 } 339 340 /** 341 * Returns a <code>JButton</code> appropriate for placement on the 342 * TitlePane. 343 */ createTitleButton()344 private JButton createTitleButton() { 345 JButton button = new JButton(); 346 347 button.setFocusPainted(false); 348 button.setFocusable(false); 349 button.setOpaque(false); 350 return button; 351 } 352 353 /** 354 * Creates the Buttons that will be placed on the TitlePane. 355 */ createButtons()356 private void createButtons() { 357 closeButton = createTitleButton(); 358 closeButton.setAction(closeAction); 359 closeButton.setText(null); 360 closeButton.putClientProperty("paintActive", Boolean.TRUE); 361 closeButton.setBorder(handyEmptyBorder); 362 closeButton.getAccessibleContext().setAccessibleName("Close"); 363 364 365 iconifyButton = createTitleButton(); 366 iconifyButton.setAction(iconifyAction); 367 iconifyButton.setText(null); 368 iconifyButton.putClientProperty("paintActive", Boolean.TRUE); 369 iconifyButton.setBorder(handyEmptyBorder); 370 iconifyButton.getAccessibleContext().setAccessibleName("Iconify"); 371 372 toggleButton = createTitleButton(); 373 toggleButton.setAction(restoreAction); 374 toggleButton.putClientProperty("paintActive", Boolean.TRUE); 375 toggleButton.setBorder(handyEmptyBorder); 376 toggleButton.getAccessibleContext().setAccessibleName("Maximize"); 377 378 armer1 = new JPanel(null); 379 armer1.setOpaque(false); 380 armer2 = new JPanel(null); 381 armer2.setOpaque(false); 382 383 MouseListener buttonArmer = new MouseAdapter() { 384 public void mouseEntered(MouseEvent evt) { 385 closeButton.putClientProperty("paintRollover", Boolean.TRUE); 386 iconifyButton.putClientProperty("paintRollover", Boolean.TRUE); 387 toggleButton.putClientProperty("paintRollover", Boolean.TRUE); 388 closeButton.repaint(); 389 iconifyButton.repaint(); 390 toggleButton.repaint(); 391 } 392 public void mouseExited(MouseEvent e) { 393 closeButton.putClientProperty("paintRollover", Boolean.FALSE); 394 iconifyButton.putClientProperty("paintRollover", Boolean.FALSE); 395 toggleButton.putClientProperty("paintRollover", Boolean.FALSE); 396 closeButton.repaint(); 397 iconifyButton.repaint(); 398 toggleButton.repaint(); 399 } 400 }; 401 closeButton.addMouseListener(buttonArmer); 402 iconifyButton.addMouseListener(buttonArmer); 403 toggleButton.addMouseListener(buttonArmer); 404 armer1.addMouseListener(buttonArmer); 405 armer2.addMouseListener(buttonArmer); 406 } 407 408 /** 409 * Update all textures and icons for the new font used by the JRootPane. 410 */ updateIconsAndTextures()411 private void updateIconsAndTextures() { 412 int size = getFont().getSize(); 413 boolean isVertical = isVertical(); 414 415 Icon closeIcon, iconifyIcon; 416 String prefix = "InternalFrame."; 417 String suffix; 418 if (size <= 9) { 419 suffix = ".mini"; 420 } else if (size <= 11) { 421 suffix = ".small"; 422 } else { 423 suffix = ""; 424 } 425 maximizeIcon = UIManager.getIcon(prefix+"maximizeIcon"+suffix); 426 minimizeIcon = UIManager.getIcon(prefix+"maximizeIcon"+suffix); 427 closeIcon = UIManager.getIcon(prefix+"closeIcon"+suffix); 428 iconifyIcon = UIManager.getIcon(prefix+"iconifyIcon"+suffix); 429 activeBackground = UIManager.getColor(prefix+((isVertical) ? "vTitlePaneBackground" : "titlePaneBackground")+suffix); 430 activeForeground = UIManager.getColor(prefix+"titlePaneForeground"+suffix); 431 activeShadow = UIManager.getColor(prefix+"titlePaneShadow"+suffix); 432 433 434 closeButton.setIcon(closeIcon); 435 iconifyButton.setIcon(iconifyIcon); 436 toggleButton.setIcon(maximizeIcon); 437 } 438 439 440 /** 441 * Returns the <code>LayoutManager</code> that should be installed on 442 * the <code>Quaqua14TitlePane</code>. 443 */ createLayout()444 private LayoutManager createLayout() { 445 return new TitlePaneLayout(); 446 } 447 448 /** 449 * Updates state dependant upon the Window's active state. 450 */ setActive(boolean isActive)451 private void setActive(boolean isActive) { 452 // if (getWindowDecorationStyle() == JRootPane.FRAME) { 453 Boolean activeB = isActive ? Boolean.TRUE : Boolean.FALSE; 454 455 iconifyButton.putClientProperty("paintActive", activeB); 456 closeButton.putClientProperty("paintActive", activeB); 457 toggleButton.putClientProperty("paintActive", activeB); 458 // } 459 // Repaint the whole thing as the Borders that are used have 460 // different colors for active vs inactive 461 getRootPane().repaint(); 462 } 463 464 /** 465 * Sets the state of the Window. 466 */ setState(int state)467 private void setState(int state) { 468 setState(state, false); 469 } 470 471 /** 472 * Sets the state of the window. If <code>updateRegardless</code> is 473 * true and the state has not changed, this will update anyway. 474 */ setState(int state, boolean updateRegardless)475 private void setState(int state, boolean updateRegardless) { 476 Window w = getWindow(); 477 478 // if (w != null && getWindowDecorationStyle() == JRootPane.FRAME) { 479 if (this.state == state && !updateRegardless) { 480 return; 481 } 482 boolean isPalette = (Boolean) rootPane.getClientProperty("Quaqua.RootPane.isPalette") == Boolean.TRUE; 483 Frame frame = getFrame(); 484 485 if (frame != null && ! isPalette) { 486 JRootPane rootPane = getRootPane(); 487 488 if (((state & Frame.MAXIMIZED_BOTH) != 0) && 489 (rootPane.getBorder() == null || 490 (rootPane.getBorder() instanceof UIResource)) && 491 frame.isShowing()) { 492 rootPane.setBorder(null); 493 } else if ((state & Frame.MAXIMIZED_BOTH) == 0) { 494 // This is a croak, if state becomes bound, this can 495 // be nuked. 496 rootPaneUI.installBorder(rootPane); 497 } 498 if (armer1.getParent() == null) { 499 add(armer1); 500 } 501 if (armer2.getParent() == null) { 502 add(armer2); 503 } 504 //if (frame.isResizable()) { 505 if ((state & Frame.MAXIMIZED_BOTH) != 0) { 506 updateToggleButton(restoreAction, minimizeIcon); 507 maximizeAction.setEnabled(false); 508 restoreAction.setEnabled(true); 509 } else { 510 updateToggleButton(maximizeAction, maximizeIcon); 511 maximizeAction.setEnabled(true); 512 restoreAction.setEnabled(false); 513 } 514 if (toggleButton.getParent() == null || 515 iconifyButton.getParent() == null) { 516 add(toggleButton); 517 add(iconifyButton); 518 revalidate(); 519 repaint(); 520 } 521 toggleButton.setText(null); 522 toggleButton.setEnabled(frame.isResizable() && Toolkit.getDefaultToolkit().isFrameStateSupported(Frame.MAXIMIZED_BOTH)); 523 /* 524 } else { 525 maximizeAction.setEnabled(false); 526 restoreAction.setEnabled(false); 527 if (toggleButton.getParent() != null) { 528 remove(toggleButton); 529 revalidate(); 530 repaint(); 531 }*/ 532 //} 533 } else { 534 // Not contained in a Frame 535 maximizeAction.setEnabled(false); 536 restoreAction.setEnabled(false); 537 iconifyAction.setEnabled(false); 538 remove(armer1); 539 remove(armer2); 540 remove(toggleButton); 541 remove(iconifyButton); 542 revalidate(); 543 repaint(); 544 } 545 closeAction.setEnabled(true); 546 this.state = state; 547 //} 548 } 549 550 /** 551 * Updates the toggle button to contain the Icon <code>icon</code>, and 552 * Action <code>action</code>. 553 */ updateToggleButton(Action action, Icon icon)554 private void updateToggleButton(Action action, Icon icon) { 555 toggleButton.setAction(action); 556 toggleButton.setIcon(icon); 557 toggleButton.setText(null); 558 toggleButton.setEnabled( 559 Toolkit.getDefaultToolkit().isFrameStateSupported( 560 Frame.MAXIMIZED_BOTH)); 561 } 562 563 /** 564 * Returns the Frame rendering in. This will return null if the 565 * <code>JRootPane</code> is not contained in a <code>Frame</code>. 566 */ getFrame()567 private Frame getFrame() { 568 Window window = getWindow(); 569 570 if (window instanceof Frame) { 571 return (Frame)window; 572 } 573 return null; 574 } 575 576 /** 577 * Returns the Dialog rendering in. This will return null if the 578 * <code>JRootPane</code> is not contained in a <code>Frame</code>. 579 */ getDialog()580 private Dialog getDialog() { 581 Window window = getWindow(); 582 583 if (window instanceof Dialog) { 584 return (Dialog) window; 585 } 586 return null; 587 } 588 getFont()589 public Font getFont() { 590 return rootPane.getFont(); 591 /* 592 Window window = getWindow(); 593 return (window != null) ? window.getFont() : rootPane.getFont(); 594 */ 595 } 596 597 /** 598 * Returns the <code>Window</code> the <code>JRootPane</code> is 599 * contained in. This will return null if there is no parent ancestor 600 * of the <code>JRootPane</code>. 601 */ getWindow()602 private Window getWindow() { 603 return window; 604 } 605 606 /** 607 * Returns the String to display as the title. 608 */ getTitle()609 private String getTitle() { 610 Window w = getWindow(); 611 612 if (w instanceof Frame) { 613 return ((Frame)w).getTitle(); 614 } else if (w instanceof Dialog) { 615 return ((Dialog)w).getTitle(); 616 } 617 return null; 618 } 619 620 /** 621 * Renders the TitlePane. 622 */ paintComponent(Graphics gr)623 public void paintComponent(Graphics gr) { 624 Graphics2D g = (Graphics2D) gr; 625 626 627 Object oldHints = QuaquaUtilities.beginGraphics(g); 628 629 // As state isn't bound, we need a convenience place to check 630 // if it has changed. Changing the state typically changes the 631 if (getFrame() != null) { 632 setState(getFrame().getExtendedState()); 633 } 634 Window window = getWindow(); 635 636 JRootPane rootPane = getRootPane(); 637 boolean isVertical = isVertical(); 638 639 boolean leftToRight = (window == null) ? 640 rootPane.getComponentOrientation().isLeftToRight() : 641 window.getComponentOrientation().isLeftToRight(); 642 643 boolean isPalette = (Boolean) rootPane.getClientProperty("Quaqua.RootPane.isPalette") == Boolean.TRUE; 644 boolean isSelected = (window == null) ? false : window.isActive(); 645 isSelected |= isPalette; 646 647 int width = getWidth(); 648 int height = getHeight(); 649 650 Color background; 651 Color foreground; 652 Color darkShadow; 653 Border titleBorder; 654 655 int fontSize = getFont().getSize(); 656 String suffix = (fontSize <= 9) ? ".mini" : 657 (fontSize <= 11) ? ".small" : ""; 658 if (isVertical()) { 659 suffix = ".vertical"+suffix; 660 } 661 662 if (isSelected) { 663 background = activeBackground; 664 foreground = activeForeground; 665 darkShadow = activeShadow; 666 titleBorder = ((Border[]) UIManager.get("Frame.titlePaneBorders"+suffix))[0]; 667 } else { 668 background = inactiveBackground; 669 foreground = inactiveForeground; 670 darkShadow = inactiveShadow; 671 titleBorder = ((Border[]) UIManager.get("Frame.titlePaneBorders"+suffix))[1]; 672 } 673 674 if (/*isPalette ||*/ titleBorder == null) { 675 g.setPaint(TextureColor.getPaint(background, window)); 676 g.fillRect(0, 0, width, height); 677 g.setPaint(TextureColor.getPaint(darkShadow, window)); 678 if (isVertical) { 679 g.drawLine( width - 1, 0, width - 1, height); 680 } else { 681 g.drawLine( 0, height - 1, width, height -1); 682 } 683 } else { 684 // Make background fully transparent (0 alpha) 685 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR)); 686 g.fillRect(0,0,getWidth(),getHeight()); 687 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); 688 titleBorder.paintBorder(this, g, 0, 0, width, height); 689 } 690 691 int xOffset = leftToRight ? 5 : width - 5; 692 693 // if (getWindowDecorationStyle() == JRootPane.FRAME) { 694 xOffset += leftToRight ? IMAGE_WIDTH + 5 : - IMAGE_WIDTH - 5; 695 // } 696 697 String theTitle = getTitle(); 698 699 if (theTitle != null) { 700 Font f = getFont(); 701 FontMetrics fm = g.getFontMetrics(); 702 703 int yOffset = ( (height - fm.getHeight() ) / 2 ) + fm.getAscent(); 704 705 Rectangle rect = new Rectangle(0, 0, 0, 0); 706 if (iconifyButton != null && iconifyButton.getParent() != null) { 707 rect = iconifyButton.getBounds(); 708 } 709 Rectangle toggleRect = new Rectangle(0, 0, 0, 0); 710 if (toggleButton != null && toggleButton.getParent() != null) { 711 toggleRect = toggleButton.getBounds(); 712 } 713 Rectangle closeRect = new Rectangle(0, 0, 0, 0); 714 if (closeButton != null && closeButton.getParent() != null) { 715 closeRect = closeButton.getBounds(); 716 } 717 int titleW; 718 719 if (isVertical) { 720 titleW = height - Math.max(toggleRect.y+toggleRect.height,closeRect.y+closeRect.height); 721 theTitle = clippedText(theTitle, fm, titleW); 722 xOffset = ( (width - fm.getHeight() ) / 2 ) + fm.getAscent(); 723 724 int titleLength = SwingUtilities.computeStringWidth(fm, theTitle); 725 AffineTransform at = g.getTransform(); 726 int y = Math.max(Math.max(closeRect.y + closeRect.height,toggleRect.y + toggleRect.height) + 5, (getHeight() - titleLength) / 2) + titleLength; 727 g.rotate((float) (Math.PI / -2d), xOffset, y); 728 if (UIManager.getColor("Frame.titlePaneEmbossForeground") != null) { 729 g.setPaint(UIManager.getColor("Frame.titlePaneEmbossForeground")); 730 g.drawString(theTitle, xOffset, y + 1); 731 } 732 g.setPaint(TextureColor.getPaint(foreground, window)); 733 g.drawString(theTitle, xOffset, y); 734 g.setTransform(at); 735 } else { 736 if( leftToRight ) { 737 if (rect.x == 0) { 738 rect.x = window.getWidth() - window.getInsets().right-2; 739 } 740 titleW = getWidth() - Math.max(toggleRect.x+toggleRect.width,closeRect.x+closeRect.width) - 10; 741 theTitle = clippedText(theTitle, fm, titleW); 742 } else { 743 titleW = getWidth() - Math.max(toggleRect.x+toggleRect.width,closeRect.x+closeRect.width) - 10; 744 theTitle = clippedText(theTitle, fm, titleW); 745 xOffset -= SwingUtilities.computeStringWidth(fm, theTitle); 746 } 747 int titleLength = SwingUtilities.computeStringWidth(fm, theTitle); 748 if (UIManager.getColor("Frame.titlePaneEmbossForeground") != null) { 749 g.setPaint(UIManager.getColor("Frame.titlePaneEmbossForeground")); 750 g.drawString( theTitle, Math.max(Math.max(closeRect.x + closeRect.width, toggleRect.x + toggleRect.width) + 5, (getWidth() - titleLength) / 2), yOffset + 1); 751 } 752 g.setPaint(TextureColor.getPaint(foreground, window)); 753 g.drawString( theTitle, Math.max(Math.max(closeRect.x + closeRect.width, toggleRect.x + toggleRect.width) + 5, (getWidth() - titleLength) / 2), yOffset ); 754 xOffset += leftToRight ? titleLength + 5 : -5; 755 } 756 } 757 /* 758 Composite savedComposite = g.getComposite(); 759 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC)); 760 g.setColor(new Color(0x00ff00,true)); 761 g.fill(new Polygon(new int[] {0,10,0}, new int[] {0,0,10}, 3)); 762 g.setComposite(savedComposite); 763 */ 764 QuaquaUtilities.endGraphics(g, oldHints); 765 } 766 767 /** 768 * Convenience method to clip the passed in text to the specified 769 * size. 770 */ clippedText(String text, FontMetrics fm, int availTextWidth)771 private String clippedText(String text, FontMetrics fm, 772 int availTextWidth) { 773 if ((text == null) || (text.equals(""))) { 774 return ""; 775 } 776 int textWidth = SwingUtilities.computeStringWidth(fm, text); 777 String clipString = "…"; 778 if (textWidth > availTextWidth) { 779 int totalWidth = SwingUtilities.computeStringWidth(fm, clipString); 780 int nChars; 781 for(nChars = 0; nChars < text.length(); nChars++) { 782 totalWidth += fm.charWidth(text.charAt(nChars)); 783 if (totalWidth > availTextWidth) { 784 break; 785 } 786 } 787 text = text.substring(0, nChars) + clipString; 788 } 789 return text; 790 } 791 isVertical()792 private boolean isVertical() { 793 return rootPane.getClientProperty("Quaqua.RootPane.isVertical") == Boolean.TRUE; 794 } 795 796 /** 797 * Actions used to <code>close</code> the <code>Window</code>. 798 */ 799 private class CloseAction extends AbstractAction { CloseAction()800 public CloseAction() { 801 super(UIManager.getString("Quaqua14TitlePane.closeTitle", 802 getLocale())); 803 } 804 actionPerformed(ActionEvent e)805 public void actionPerformed(ActionEvent e) { 806 close(); 807 } 808 } 809 810 811 /** 812 * Actions used to <code>iconfiy</code> the <code>Frame</code>. 813 */ 814 private class IconifyAction extends AbstractAction { IconifyAction()815 public IconifyAction() { 816 super(UIManager.getString("Quaqua14TitlePane.iconifyTitle", 817 getLocale())); 818 } 819 actionPerformed(ActionEvent e)820 public void actionPerformed(ActionEvent e) { 821 iconify(); 822 } 823 } 824 825 826 /** 827 * Actions used to <code>restore</code> the <code>Frame</code>. 828 */ 829 private class RestoreAction extends AbstractAction { RestoreAction()830 public RestoreAction() { 831 super(UIManager.getString 832 ("Quaqua14TitlePane.restoreTitle", getLocale())); 833 } 834 actionPerformed(ActionEvent e)835 public void actionPerformed(ActionEvent e) { 836 restore(); 837 } 838 } 839 840 841 /** 842 * Actions used to <code>restore</code> the <code>Frame</code>. 843 */ 844 private class MaximizeAction extends AbstractAction { MaximizeAction()845 public MaximizeAction() { 846 super(UIManager.getString("Quaqua14TitlePane.maximizeTitle", 847 getLocale())); 848 } 849 actionPerformed(ActionEvent e)850 public void actionPerformed(ActionEvent e) { 851 maximize(); 852 } 853 } 854 855 856 /** 857 * Class responsible for drawing the system menu. Looks up the 858 * image to draw from the Frame associated with the 859 * <code>JRootPane</code>. 860 */ 861 private class SystemMenuBar extends JMenuBar { paint(Graphics gr)862 public void paint(Graphics gr) { 863 Graphics2D g = (Graphics2D) gr; 864 Frame frame = getFrame(); 865 866 if (isOpaque()) { 867 g.setPaint(TextureColor.getPaint(getBackground(), this)); 868 g.fillRect(0, 0, getWidth(), getHeight()); 869 } 870 Image image = (frame != null) ? frame.getIconImage() : null; 871 872 if (image != null) { 873 g.drawImage(image, 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, null); 874 } else { 875 Icon icon = UIManager.getIcon("InternalFrame.icon"); 876 877 if (icon != null) { 878 icon.paintIcon(this, g, 0, 0); 879 } 880 } 881 } getMinimumSize()882 public Dimension getMinimumSize() { 883 return getPreferredSize(); 884 } getPreferredSize()885 public Dimension getPreferredSize() { 886 Dimension size = super.getPreferredSize(); 887 888 return new Dimension(Math.max(IMAGE_WIDTH, size.width), 889 Math.max(size.height, IMAGE_HEIGHT)); 890 } 891 } 892 893 /** 894 * This inner class is marked "public" due to a compiler bug. 895 * This class should be treated as a "protected" inner class. 896 * Instantiate it only within subclasses of <Foo>. 897 */ 898 private class TitlePaneLayout implements LayoutManager { addLayoutComponent(String name, Component c)899 public void addLayoutComponent(String name, Component c) {} removeLayoutComponent(Component c)900 public void removeLayoutComponent(Component c) {} preferredLayoutSize(Container c)901 public Dimension preferredLayoutSize(Container c) { 902 int height = computeHeight(); 903 return new Dimension(height, height); 904 } 905 minimumLayoutSize(Container c)906 public Dimension minimumLayoutSize(Container c) { 907 return preferredLayoutSize(c); 908 } 909 computeHeight()910 private int computeHeight() { 911 int fontSize = getFont().getSize(); 912 if (fontSize <= 9) { 913 return 12; 914 } else if (fontSize <= 11) { 915 return 16; 916 } else { 917 return 22; 918 } 919 /* 920 FontMetrics fm 921 = Toolkit.getDefaultToolkit().getFontMetrics(getFont()); 922 int fontHeight = fm.getHeight(); 923 924 925 fontHeight += 2; 926 int iconHeight = 0; 927 // if (getWindowDecorationStyle() == JRootPane.FRAME) { 928 iconHeight = IMAGE_HEIGHT; 929 // } 930 931 int finalHeight = Math.max( fontHeight, iconHeight ); 932 return finalHeight; 933 */ 934 } 935 936 layoutContainer(Container c)937 public void layoutContainer(Container c) { 938 JRootPane rootPane = getRootPane(); 939 boolean isVertical = isVertical(); 940 if (isVertical) { 941 layoutVerticalContainer(c); 942 } else { 943 layoutHorizontalContainer(c); 944 } 945 } layoutHorizontalContainer(Container c)946 public void layoutHorizontalContainer(Container c) { 947 int fontSize = getFont().getSize(); 948 boolean leftToRight; 949 if (window == null) { 950 leftToRight = !getRootPane().getComponentOrientation().isLeftToRight(); 951 } else { 952 leftToRight = !window.getComponentOrientation().isLeftToRight(); 953 } 954 955 int w = getWidth(); 956 int x; 957 int y; 958 int spacing; 959 int buttonHeight; 960 int buttonWidth; 961 962 if (fontSize <= 9) { 963 y = 1; 964 } else if (fontSize <= 11) { 965 y = 1; 966 } else { 967 y = 4; 968 } 969 970 if (closeButton != null && closeButton.getIcon() != null) { 971 buttonHeight = closeButton.getIcon().getIconHeight(); 972 buttonWidth = closeButton.getIcon().getIconWidth(); 973 } else { 974 buttonHeight = IMAGE_HEIGHT; 975 buttonWidth = IMAGE_WIDTH; 976 } 977 978 // assumes all buttons have the same dimensions 979 // these dimensions include the borders 980 981 x = leftToRight ? w : 0; 982 983 spacing = 5; 984 x = leftToRight ? spacing : w - buttonWidth - spacing; 985 if (menuBar != null) { 986 menuBar.setBounds(x, y, buttonWidth, buttonHeight); 987 } 988 989 x = leftToRight ? w : 0; 990 spacing = (fontSize <= 11) ? 5 : 7; 991 x += leftToRight ? -spacing -buttonWidth : spacing; 992 993 if (closeButton != null) { 994 closeButton.setBounds(x, y, buttonWidth, buttonHeight); 995 } 996 997 if( !leftToRight ) x += buttonWidth; 998 999 if (iconifyButton != null && 1000 iconifyButton.getParent() != null) { 1001 spacing = 5; 1002 armer1.setBounds(x, y, spacing, buttonHeight); 1003 x += leftToRight ? -spacing -buttonWidth : spacing; 1004 iconifyButton.setBounds(x, y, buttonWidth, buttonHeight); 1005 if (!leftToRight) { 1006 x += buttonWidth; 1007 } 1008 } else { 1009 armer1.setBounds(0,0,0,0); 1010 } 1011 1012 if (Toolkit.getDefaultToolkit().isFrameStateSupported( 1013 Frame.MAXIMIZED_BOTH)) { 1014 if (toggleButton.getParent() != null) { 1015 spacing = 5; 1016 armer2.setBounds(x, y, spacing, buttonHeight); 1017 x += leftToRight ? -spacing -buttonWidth : spacing; 1018 toggleButton.setBounds(x, y, buttonWidth, buttonHeight); 1019 if (!leftToRight) { 1020 x += buttonWidth; 1021 } 1022 } 1023 } else { 1024 armer2.setBounds(0,0,0,0); 1025 } 1026 1027 buttonsWidth = leftToRight ? w - x : x; 1028 } 1029 } layoutVerticalContainer(Container c)1030 public void layoutVerticalContainer(Container c) { 1031 int h = getHeight(); 1032 int x; 1033 int y; 1034 int spacing; 1035 int buttonHeight; 1036 int buttonWidth; 1037 1038 int fontSize = getFont().getSize(); 1039 if (fontSize <= 9) { 1040 x = 2; 1041 } else if (fontSize <= 11) { 1042 x = 1; 1043 } else { 1044 x = 2; 1045 } 1046 1047 if (closeButton != null && closeButton.getIcon() != null) { 1048 buttonHeight = closeButton.getIcon().getIconHeight(); 1049 buttonWidth = closeButton.getIcon().getIconWidth(); 1050 } else { 1051 buttonHeight = IMAGE_HEIGHT; 1052 buttonWidth = IMAGE_WIDTH; 1053 } 1054 1055 // assumes all buttons have the same dimensions 1056 // these dimensions include the borders 1057 1058 spacing = 5; 1059 y = spacing; 1060 if (menuBar != null) { 1061 menuBar.setBounds(x, y, buttonWidth, buttonHeight); 1062 } 1063 1064 if (fontSize <= 9) { 1065 y = 0; 1066 } else if (fontSize <= 11) { 1067 y = 0; 1068 } else { 1069 y = 4; 1070 } 1071 spacing = 2; 1072 y += spacing; 1073 1074 if (closeButton != null) { 1075 closeButton.setBounds(x, y, buttonWidth, buttonHeight); 1076 } 1077 1078 y += buttonHeight; 1079 1080 if (iconifyButton != null && 1081 iconifyButton.getParent() != null) { 1082 spacing = 5; 1083 armer1.setBounds(x, y, buttonWidth, spacing); 1084 y += spacing; 1085 iconifyButton.setBounds(x, y, buttonWidth, buttonHeight); 1086 y += buttonHeight; 1087 } else { 1088 armer1.setBounds(0,0,0,0); 1089 } 1090 1091 if (Toolkit.getDefaultToolkit().isFrameStateSupported( 1092 Frame.MAXIMIZED_BOTH)) { 1093 if (toggleButton.getParent() != null) { 1094 spacing = 5; 1095 armer2.setBounds(x, y, buttonWidth, spacing); 1096 y += spacing; 1097 toggleButton.setBounds(x, y, buttonWidth, buttonHeight); 1098 y += buttonHeight; 1099 } 1100 } else { 1101 armer2.setBounds(0,0,0,0); 1102 } 1103 1104 buttonsWidth = x + buttonWidth; 1105 1106 } 1107 1108 1109 1110 /** 1111 * PropertyChangeListener installed on the JRootPane. Updates the necessary 1112 * state as the state of the JRootPane changes. 1113 */ 1114 private class RootPropertyHandler implements PropertyChangeListener { propertyChange(PropertyChangeEvent pce)1115 public void propertyChange(PropertyChangeEvent pce) { 1116 String name = pce.getPropertyName(); 1117 if ("Quaqua.RootPane.isVertical".equals(name)) { 1118 updateIconsAndTextures(); 1119 revalidate(); 1120 repaint(); 1121 } else if ("Window.documentModified".equals(name) || "windowModified".equals(name)) { 1122 closeButton.setSelected(((Boolean) pce.getNewValue()).booleanValue()); 1123 } else if ("font".equals(name)) { 1124 updateIconsAndTextures(); 1125 revalidate(); 1126 repaint(); 1127 } 1128 1129 } 1130 } 1131 /** 1132 * PropertyChangeListener installed on the Window. Updates the necessary 1133 * state as the state of the Window changes. 1134 */ 1135 private class WindowPropertyHandler implements PropertyChangeListener { propertyChange(PropertyChangeEvent pce)1136 public void propertyChange(PropertyChangeEvent pce) { 1137 String name = pce.getPropertyName(); 1138 // Frame.state isn't currently bound. 1139 if ("resizable".equals(name) || "state".equals(name)) { 1140 Frame frame = getFrame(); 1141 if (frame != null) { 1142 setState(frame.getExtendedState(), true); 1143 } 1144 if ("resizable".equals(name)) { 1145 getRootPane().repaint(); 1146 } 1147 } else if ("title".equals(name)) { 1148 repaint(); 1149 } else if ("Quaqua.RootPane.isVertical".equals(name)) { 1150 updateIconsAndTextures(); 1151 revalidate(); 1152 repaint(); 1153 } else if ("componentOrientation".equals(name)) { 1154 revalidate(); 1155 repaint(); 1156 } else if (name.equals("JComponent.sizeVariant")) { 1157 QuaquaUtilities.applySizeVariant(rootPane); 1158 } 1159 } 1160 } 1161 1162 1163 /** 1164 * WindowListener installed on the Window, updates the state as necessary. 1165 */ 1166 private class WindowHandler extends WindowAdapter { windowActivated(WindowEvent ev)1167 public void windowActivated(WindowEvent ev) { 1168 setActive(true); 1169 } 1170 windowDeactivated(WindowEvent ev)1171 public void windowDeactivated(WindowEvent ev) { 1172 setActive(false); 1173 } 1174 } 1175 } 1176