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.
 248                     getTextUIDrawing(frame).getStringWidth(
 249                                frame, fm, frameTitle) : 0;
 250             int title_length = frameTitle != null ? frameTitle.length() : 0;
 251 
 252             if (title_length > 2) {
 253                 int subtitle_w = SwingUtilities2.getTextUIDrawing(frame)
 254                         .getStringWidth(frame, fm,
 255                                      frame.getTitle().substring(0, 2) + "...");
 256                 width += (title_w < subtitle_w) ? title_w : subtitle_w;
 257             }
 258             else {
 259                 width += title_w;
 260             }
 261 
 262             // Compute height.
 263             int height;
 264             if (isPalette) {
 265                 height = paletteTitleHeight;
 266             } else {
 267                 int fontHeight = fm.getHeight();
 268                 fontHeight += 7;
 269                 Icon icon = frame.getFrameIcon();
 270                 int iconHeight = 0;
 271                 if (icon != null) {
 272                     // SystemMenuBar forces the icon to be 16x16 or less.
 273                     iconHeight = Math.min(icon.getIconHeight(), 16);
 274                 }
 275                 iconHeight += 5;
 276                 height = Math.max(fontHeight, iconHeight);
 277             }
 278 
 279             return new Dimension(width, height);
 280         }
 281 
 282         public void layoutContainer(Container c) {
 283             boolean leftToRight = MetalUtils.isLeftToRight(frame);
 284 
 285             int w = getWidth();
 286             int x = leftToRight ? w : 0;
 287             int y = 2;
 288             int spacing;
 289 
 290             // assumes all buttons have the same dimensions
 291             // these dimensions include the borders
 292             int buttonHeight = closeButton.getIcon().getIconHeight();
 293             int buttonWidth = closeButton.getIcon().getIconWidth();
 294 
 295             if(frame.isClosable()) {
 296                 if (isPalette) {
 297                     spacing = 3;
 298                     x += leftToRight ? -spacing -(buttonWidth+2) : spacing;
 299                     closeButton.setBounds(x, y, buttonWidth+2, getHeight()-4);
 300                     if( !leftToRight ) x += (buttonWidth+2);
 301                 } else {
 302                     spacing = 4;
 303                     x += leftToRight ? -spacing -buttonWidth : spacing;
 304                     closeButton.setBounds(x, y, buttonWidth, buttonHeight);
 305                     if( !leftToRight ) x += buttonWidth;
 306                 }
 307             }
 308 
 309             if(frame.isMaximizable() && !isPalette ) {
 310                 spacing = frame.isClosable() ? 10 : 4;
 311                 x += leftToRight ? -spacing -buttonWidth : spacing;
 312                 maxButton.setBounds(x, y, buttonWidth, buttonHeight);
 313                 if( !leftToRight ) x += buttonWidth;
 314             }
 315 
 316             if(frame.isIconifiable() && !isPalette ) {
 317                 spacing = frame.isMaximizable() ? 2
 318                           : (frame.isClosable() ? 10 : 4);
 319                 x += leftToRight ? -spacing -buttonWidth : spacing;
 320                 iconButton.setBounds(x, y, buttonWidth, buttonHeight);
 321                 if( !leftToRight ) x += buttonWidth;
 322             }
 323 
 324             buttonsWidth = leftToRight ? w - x : x;
 325         }
 326     }
 327 
 328     /**
 329      * Paints palette.
 330      *
 331      * @param g a instance of {@code Graphics}
 332      */
 333     public void paintPalette(Graphics g)  {
 334         boolean leftToRight = MetalUtils.isLeftToRight(frame);
 335 
 336         int width = getWidth();
 337         int height = getHeight();
 338 
 339         if (paletteBumps == null) {
 340             paletteBumps
 341                 = new MetalBumps(0, 0,
 342                                  MetalLookAndFeel.getPrimaryControlHighlight(),
 343                                  MetalLookAndFeel.getPrimaryControlInfo(),
 344                                  MetalLookAndFeel.getPrimaryControlShadow() );
 345         }
 346 
 347         Color background = MetalLookAndFeel.getPrimaryControlShadow();
 348         Color darkShadow = MetalLookAndFeel.getPrimaryControlDarkShadow();
 349 
 350         g.setColor(background);
 351         g.fillRect(0, 0, width, height);
 352 
 353         g.setColor( darkShadow );
 354         g.drawLine ( 0, height - 1, width, height -1);
 355 
 356         int xOffset = leftToRight ? 4 : buttonsWidth + 4;
 357         int bumpLength = width - buttonsWidth -2*4;
 358         int bumpHeight = getHeight()  - 4;
 359         paletteBumps.setBumpArea( bumpLength, bumpHeight );
 360         paletteBumps.paintIcon( this, g, xOffset, 2);
 361     }
 362 
 363     public void paintComponent(Graphics g)  {
 364         if(isPalette) {
 365             paintPalette(g);
 366             return;
 367         }
 368 
 369         boolean leftToRight = MetalUtils.isLeftToRight(frame);
 370         boolean isSelected = frame.isSelected();
 371 
 372         int width = getWidth();
 373         int height = getHeight();
 374 
 375         Color background = null;
 376         Color foreground = null;
 377         Color shadow = null;
 378 
 379         MetalBumps bumps;
 380         String gradientKey;
 381 
 382         if (isSelected) {
 383             if (!MetalLookAndFeel.usingOcean()) {
 384                 closeButton.setContentAreaFilled(true);
 385                 maxButton.setContentAreaFilled(true);
 386                 iconButton.setContentAreaFilled(true);
 387             }
 388             if (selectedBackgroundKey != null) {
 389                 background = UIManager.getColor(selectedBackgroundKey);
 390             }
 391             if (background == null) {
 392                 background = MetalLookAndFeel.getWindowTitleBackground();
 393             }
 394             if (selectedForegroundKey != null) {
 395                 foreground = UIManager.getColor(selectedForegroundKey);
 396             }
 397             if (selectedShadowKey != null) {
 398                 shadow = UIManager.getColor(selectedShadowKey);
 399             }
 400             if (shadow == null) {
 401                 shadow = MetalLookAndFeel.getPrimaryControlDarkShadow();
 402             }
 403             if (foreground == null) {
 404                 foreground = MetalLookAndFeel.getWindowTitleForeground();
 405             }
 406             activeBumps.setBumpColors(activeBumpsHighlight, activeBumpsShadow,
 407                         UIManager.get("InternalFrame.activeTitleGradient") !=
 408                                       null ? null : background);
 409             bumps = activeBumps;
 410             gradientKey = "InternalFrame.activeTitleGradient";
 411         } else {
 412             if (!MetalLookAndFeel.usingOcean()) {
 413                 closeButton.setContentAreaFilled(false);
 414                 maxButton.setContentAreaFilled(false);
 415                 iconButton.setContentAreaFilled(false);
 416             }
 417             background = MetalLookAndFeel.getWindowTitleInactiveBackground();
 418             foreground = MetalLookAndFeel.getWindowTitleInactiveForeground();
 419             shadow = MetalLookAndFeel.getControlDarkShadow();
 420             bumps = inactiveBumps;
 421             gradientKey = "InternalFrame.inactiveTitleGradient";
 422         }
 423 
 424         if (!MetalUtils.drawGradient(this, g, gradientKey, 0, 0, width,
 425                                      height, true)) {
 426             g.setColor(background);
 427             g.fillRect(0, 0, width, height);
 428         }
 429 
 430         g.setColor( shadow );
 431         g.drawLine ( 0, height - 1, width, height -1);
 432         g.drawLine ( 0, 0, 0 ,0);
 433         g.drawLine ( width - 1, 0 , width -1, 0);
 434 
 435 
 436         int titleLength;
 437         int xOffset = leftToRight ? 5 : width - 5;
 438         String frameTitle = frame.getTitle();
 439 
 440         Icon icon = frame.getFrameIcon();
 441         if ( icon != null ) {
 442             if( !leftToRight )
 443                 xOffset -= icon.getIconWidth();
 444             int iconY = ((height / 2) - (icon.getIconHeight() /2));
 445             icon.paintIcon(frame, g, xOffset, iconY);
 446             xOffset += leftToRight ? icon.getIconWidth() + 5 : -5;
 447         }
 448 
 449         if(frameTitle != null) {
 450             Font f = getFont();
 451             g.setFont(f);
 452             FontMetrics fm = SwingUtilities2.getFontMetrics(frame, g, f);
 453             int fHeight = fm.getHeight();
 454 
 455             g.setColor(foreground);
 456 
 457             int yOffset = ( (height - fm.getHeight() ) / 2 ) + fm.getAscent();
 458 
 459             Rectangle rect = new Rectangle(0, 0, 0, 0);
 460             if (frame.isIconifiable()) { rect = iconButton.getBounds(); }
 461             else if (frame.isMaximizable()) { rect = maxButton.getBounds(); }
 462             else if (frame.isClosable()) { rect = closeButton.getBounds(); }
 463             int titleW;
 464 
 465             if( leftToRight ) {
 466               if (rect.x == 0) {
 467                 rect.x = frame.getWidth()-frame.getInsets().right-2;
 468               }
 469               titleW = rect.x - xOffset - 4;
 470               frameTitle = getTitle(frameTitle, fm, titleW);
 471             } else {
 472               titleW = xOffset - rect.x - rect.width - 4;
 473               frameTitle = getTitle(frameTitle, fm, titleW);
 474               xOffset -= SwingUtilities2.getTextUIDrawing(frame)
 475                       .getStringWidth(frame, fm, frameTitle);
 476             }
 477 
 478             titleLength = SwingUtilities2.getTextUIDrawing(frame)
 479                     .getStringWidth(frame, fm, frameTitle);
 480             SwingUtilities2.getTextUIDrawing(frame)
 481                     .drawString(frame, g, frameTitle, xOffset, yOffset);
 482                                 xOffset += leftToRight ? titleLength + 5 : -5;
 483         }
 484 
 485         int bumpXOffset;
 486         int bumpLength;
 487         if( leftToRight ) {
 488             bumpLength = width - buttonsWidth - xOffset - 5;
 489             bumpXOffset = xOffset;
 490         } else {
 491             bumpLength = xOffset - buttonsWidth - 5;
 492             bumpXOffset = buttonsWidth + 5;
 493         }
 494         int bumpYOffset = 3;
 495         int bumpHeight = getHeight() - (2 * bumpYOffset);
 496         bumps.setBumpArea( bumpLength, bumpHeight );
 497         bumps.paintIcon(this, g, bumpXOffset, bumpYOffset);
 498     }
 499 
 500     /**
 501      * If {@code b} is {@code true}, sets palette icons.
 502      *
 503      * @param b if {@code true}, sets palette icons
 504      */
 505     public void setPalette(boolean b) {
 506         isPalette = b;
 507 
 508         if (isPalette) {
 509             closeButton.setIcon(paletteCloseIcon);
 510          if( frame.isMaximizable() )
 511                 remove(maxButton);
 512             if( frame.isIconifiable() )
 513                 remove(iconButton);
 514         } else {
 515             closeButton.setIcon(closeIcon);
 516             if( frame.isMaximizable() )
 517                 add(maxButton);
 518             if( frame.isIconifiable() )
 519                 add(iconButton);
 520         }
 521         revalidate();
 522         repaint();
 523     }
 524 
 525     /**
 526      * Updates any state dependant upon the JInternalFrame being shown in
 527      * a <code>JOptionPane</code>.
 528      */
 529     private void updateOptionPaneState() {
 530         int type = -2;
 531         boolean closable = wasClosable;
 532         Object obj = frame.getClientProperty("JInternalFrame.messageType");
 533 
 534         if (obj == null) {
 535             // Don't change the closable state unless in an JOptionPane.
 536             return;
 537         }
 538         if (obj instanceof Integer) {
 539             type = ((Integer) obj).intValue();
 540         }
 541         switch (type) {
 542         case JOptionPane.ERROR_MESSAGE:
 543             selectedBackgroundKey =
 544                               "OptionPane.errorDialog.titlePane.background";
 545             selectedForegroundKey =
 546                               "OptionPane.errorDialog.titlePane.foreground";
 547             selectedShadowKey = "OptionPane.errorDialog.titlePane.shadow";
 548             closable = false;
 549             break;
 550         case JOptionPane.QUESTION_MESSAGE:
 551             selectedBackgroundKey =
 552                             "OptionPane.questionDialog.titlePane.background";
 553             selectedForegroundKey =
 554                     "OptionPane.questionDialog.titlePane.foreground";
 555             selectedShadowKey =
 556                           "OptionPane.questionDialog.titlePane.shadow";
 557             closable = false;
 558             break;
 559         case JOptionPane.WARNING_MESSAGE:
 560             selectedBackgroundKey =
 561                               "OptionPane.warningDialog.titlePane.background";
 562             selectedForegroundKey =
 563                               "OptionPane.warningDialog.titlePane.foreground";
 564             selectedShadowKey = "OptionPane.warningDialog.titlePane.shadow";
 565             closable = false;
 566             break;
 567         case JOptionPane.INFORMATION_MESSAGE:
 568         case JOptionPane.PLAIN_MESSAGE:
 569             selectedBackgroundKey = selectedForegroundKey = selectedShadowKey =
 570                                     null;
 571             closable = false;
 572             break;
 573         default:
 574             selectedBackgroundKey = selectedForegroundKey = selectedShadowKey =
 575                                     null;
 576             break;
 577         }
 578         if (closable != frame.isClosable()) {
 579             frame.setClosable(closable);
 580         }
 581     }
 582 }