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