1 /*
   2  * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.apple.laf;
  27 
  28 import java.awt.*;
  29 import java.beans.PropertyVetoException;
  30 
  31 import javax.swing.*;
  32 import javax.swing.border.Border;
  33 import javax.swing.plaf.UIResource;
  34 
  35 import sun.swing.SwingUtilities2;
  36 
  37 import apple.laf.*;
  38 import apple.laf.JRSUIConstants.*;
  39 import apple.laf.JRSUIState.TitleBarHeightState;
  40 
  41 import com.apple.laf.AquaUtils.RecyclableSingleton;
  42 import com.apple.laf.AquaInternalFrameBorderMetrics;
  43 
  44 public class AquaInternalFrameBorder implements Border, UIResource {
  45     private static final int kCloseButton = 0;
  46     private static final int kIconButton = 1;
  47     private static final int kGrowButton = 2;
  48 
  49     private static final int sMaxIconWidth = 15;
  50     private static final int sMaxIconHeight = sMaxIconWidth;
  51     private static final int sAfterButtonPad = 11;
  52     private static final int sAfterIconPad = 5;
  53     private static final int sRightSideTitleClip = 0;
  54 
  55     private static final int kContentTester = 100; // For getting region insets
  56 
  57     static final RecyclableSingleton<AquaInternalFrameBorder> documentWindowFrame = new RecyclableSingleton<AquaInternalFrameBorder>() {
  58         protected AquaInternalFrameBorder getInstance() {
  59             return new AquaInternalFrameBorder(WindowType.DOCUMENT);
  60         }
  61     };
  62     protected static AquaInternalFrameBorder window() {
  63         return documentWindowFrame.get();
  64     }
  65 
  66     static final RecyclableSingleton<AquaInternalFrameBorder> utilityWindowFrame = new RecyclableSingleton<AquaInternalFrameBorder>() {
  67         protected AquaInternalFrameBorder getInstance() {
  68             return new AquaInternalFrameBorder(WindowType.UTILITY);
  69         }
  70     };
  71     protected static AquaInternalFrameBorder utility() {
  72         return utilityWindowFrame.get();
  73     }
  74 
  75     static final RecyclableSingleton<AquaInternalFrameBorder> dialogWindowFrame = new RecyclableSingleton<AquaInternalFrameBorder>() {
  76         protected AquaInternalFrameBorder getInstance() {
  77             return new AquaInternalFrameBorder(WindowType.DOCUMENT);
  78         }
  79     };
  80     protected static AquaInternalFrameBorder dialog() {
  81         return dialogWindowFrame.get();
  82     }
  83 
  84     private final AquaInternalFrameBorderMetrics metrics;
  85 
  86     private final int fThisButtonSpan;
  87     private final int fThisLeftSideTotal;
  88 
  89     private final boolean fIsUtility;
  90 
  91     // Instance variables
  92     private final WindowType fWindowKind; // Which kind of window to draw
  93     private Insets fBorderInsets; // Cached insets object
  94 
  95     private Color selectedTextColor;
  96     private Color notSelectedTextColor;
  97 
  98     private Rectangle fInBounds; // Cached bounds rect object
  99 
 100     protected final AquaPainter<TitleBarHeightState> titleBarPainter = AquaPainter.create(JRSUIStateFactory.getTitleBar());
 101     protected final AquaPainter<JRSUIState> widgetPainter = AquaPainter.create(JRSUIState.getInstance());
 102 
 103     protected AquaInternalFrameBorder(final WindowType kind) {
 104         fWindowKind = kind;
 105 
 106         titleBarPainter.state.set(WindowClipCorners.YES);
 107         if (fWindowKind == WindowType.UTILITY) {
 108             fIsUtility = true;
 109             metrics = AquaInternalFrameBorderMetrics.getMetrics(true);
 110 
 111             widgetPainter.state.set(WindowType.UTILITY);
 112             titleBarPainter.state.set(WindowType.UTILITY);
 113         } else {
 114             fIsUtility = false;
 115             metrics = AquaInternalFrameBorderMetrics.getMetrics(false);
 116 
 117             widgetPainter.state.set(WindowType.DOCUMENT);
 118             titleBarPainter.state.set(WindowType.DOCUMENT);
 119         }
 120         titleBarPainter.state.setValue(metrics.titleBarHeight);
 121         titleBarPainter.state.set(WindowTitleBarSeparator.YES);
 122         widgetPainter.state.set(AlignmentVertical.CENTER);
 123 
 124         fThisButtonSpan = (metrics.buttonWidth * 3) + (metrics.buttonPadding * 2);
 125         fThisLeftSideTotal = metrics.leftSidePadding + fThisButtonSpan + sAfterButtonPad;
 126     }
 127 
 128     public void setColors(final Color inSelectedTextColor, final Color inNotSelectedTextColor) {
 129         selectedTextColor = inSelectedTextColor;
 130         notSelectedTextColor = inNotSelectedTextColor;
 131     }
 132 
 133     // Utility to lazy-init and fill in fInBounds
 134     protected void setInBounds(final int x, final int y, final int w, final int h) {
 135         if (fInBounds == null) fInBounds = new Rectangle();
 136 
 137         fInBounds.x = x;
 138         fInBounds.y = y;
 139         fInBounds.width = w;
 140         fInBounds.height = h;
 141     }
 142 
 143     // Border interface
 144     public boolean isBorderOpaque() {
 145         return false;
 146     }
 147 
 148     // Border interface
 149     public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int w, final int h) {
 150         // For expanded InternalFrames, the frame & component are the same object
 151         paintBorder((JInternalFrame)c, c, g, x, y, w, h);
 152     }
 153 
 154     protected void paintTitleContents(final Graphics g, final JInternalFrame frame, final int x, final int y, final int w, final int h) {
 155         final boolean isSelected = frame.isSelected();
 156         final Font f = g.getFont();
 157 
 158         g.setFont(metrics.font);
 159 
 160         // Center text vertically.
 161         final FontMetrics fm = g.getFontMetrics();
 162         final int baseline = (metrics.titleBarHeight + fm.getAscent() - fm.getLeading() - fm.getDescent()) / 2;
 163 
 164         // max button is the rightmost so use it
 165         final int usedWidth = fThisLeftSideTotal + sRightSideTitleClip;
 166         int iconWidth = getIconWidth(frame);
 167         if (iconWidth > 0) iconWidth += sAfterIconPad;
 168 
 169         final int totalWidth = w;
 170 
 171         // window title looks like: | 0 0 0(sAfterButtonPad)IconWidth Title(right pad) |
 172         final int availTextWidth = totalWidth - usedWidth - iconWidth - sAfterButtonPad;
 173 
 174         final String title = frame.getTitle();
 175 
 176         String text = title;
 177         int totalTextWidth = 0;
 178 
 179         int startXPosition = fThisLeftSideTotal;
 180         boolean wasTextShortened = false;
 181         // shorten the string to fit in the
 182         if ((text != null) && !(text.equals(""))) {
 183             totalTextWidth = SwingUtilities.computeStringWidth(fm, text);
 184             final String clipString = "\u2026";
 185             if (totalTextWidth > availTextWidth) {
 186                 wasTextShortened = true;
 187                 totalTextWidth = SwingUtilities.computeStringWidth(fm, clipString);
 188                 int nChars;
 189                 for (nChars = 0; nChars < text.length(); nChars++) {
 190                     final int nextCharWidth = fm.charWidth(text.charAt(nChars));
 191                     if ((totalTextWidth + nextCharWidth) > availTextWidth) {
 192                         break;
 193                     }
 194                     totalTextWidth += nextCharWidth;
 195                 }
 196                 text = text.substring(0, nChars) + clipString;
 197             }
 198 
 199             if (!wasTextShortened) {
 200                 // center it!
 201                 startXPosition = (totalWidth - (totalTextWidth + iconWidth)) / 2;
 202                 if (startXPosition < fThisLeftSideTotal) {
 203                     startXPosition = fThisLeftSideTotal;
 204                 }
 205             }
 206 
 207             if (isSelected || fIsUtility) {
 208                 g.setColor(Color.lightGray);
 209             } else {
 210                 g.setColor(Color.white);
 211             }
 212             SwingUtilities2.drawString(frame, g, text, x + startXPosition + iconWidth, y + baseline + 1);
 213 
 214             if (isSelected || fIsUtility) {
 215                 g.setColor(selectedTextColor);
 216             } else {
 217                 g.setColor(notSelectedTextColor);
 218             }
 219 
 220             SwingUtilities2.drawString(frame, g, text, x + startXPosition + iconWidth, y + baseline);
 221             g.setFont(f);
 222         }
 223 
 224         // sja fix x & y
 225         final int iconYPostion = (metrics.titleBarHeight - getIconHeight(frame)) / 2;
 226         paintTitleIcon(g, frame, x + startXPosition, y + iconYPostion);
 227     }
 228 
 229     public int getWhichButtonHit(final JInternalFrame frame, final int x, final int y) {
 230         int buttonHit = -1;
 231 
 232         final Insets i = frame.getInsets();
 233         int startX = i.left + metrics.leftSidePadding - 1;
 234         if (isInsideYButtonArea(i, y) && x >= startX) {
 235             if (x <= (startX + metrics.buttonWidth)) {
 236                 if (frame.isClosable()) {
 237                     buttonHit = kCloseButton;
 238                 }
 239             } else {
 240                 startX += metrics.buttonWidth + metrics.buttonPadding;
 241                 if (x >= startX && x <= (startX + metrics.buttonWidth)) {
 242                     if (frame.isIconifiable()) {
 243                         buttonHit = kIconButton;
 244                     }
 245                 } else {
 246                     startX += metrics.buttonWidth + metrics.buttonPadding;
 247                     if (x >= startX && x <= (startX + metrics.buttonWidth)) {
 248                         if (frame.isMaximizable()) {
 249                             buttonHit = kGrowButton;
 250                         }
 251                     }
 252                 }
 253             }
 254         }
 255 
 256         return buttonHit;
 257     }
 258 
 259     public void doButtonAction(final JInternalFrame frame, final int whichButton) {
 260         switch (whichButton) {
 261             case kCloseButton:
 262                 frame.doDefaultCloseAction();
 263                 break;
 264 
 265             case kIconButton:
 266                 if (frame.isIconifiable()) {
 267                     if (!frame.isIcon()) {
 268                         try {
 269                             frame.setIcon(true);
 270                         } catch(final PropertyVetoException e1) {}
 271                     } else {
 272                         try {
 273                             frame.setIcon(false);
 274                         } catch(final PropertyVetoException e1) {}
 275                     }
 276                 }
 277                 break;
 278 
 279             case kGrowButton:
 280                 if (frame.isMaximizable()) {
 281                     if (!frame.isMaximum()) {
 282                         try {
 283                             frame.setMaximum(true);
 284                         } catch(final PropertyVetoException e5) {}
 285                     } else {
 286                         try {
 287                             frame.setMaximum(false);
 288                         } catch(final PropertyVetoException e6) {}
 289                     }
 290                 }
 291                 break;
 292 
 293             default:
 294                 System.err.println("AquaInternalFrameBorder should never get here!!!!");
 295                 Thread.dumpStack();
 296                 break;
 297         }
 298     }
 299 
 300     public boolean isInsideYButtonArea(final Insets i, final int y) {
 301         final int startY = (i.top - metrics.titleBarHeight / 2) - (metrics.buttonHeight / 2) - 1;
 302         final int endY = startY + metrics.buttonHeight;
 303         return y >= startY && y <= endY;
 304     }
 305 
 306     public boolean getWithinRolloverArea(final Insets i, final int x, final int y) {
 307         final int startX = i.left + metrics.leftSidePadding;
 308         final int endX = startX + fThisButtonSpan;
 309         return isInsideYButtonArea(i, y) && x >= startX && x <= endX;
 310     }
 311 
 312     protected void paintTitleIcon(final Graphics g, final JInternalFrame frame, final int x, final int y) {
 313         Icon icon = frame.getFrameIcon();
 314         if (icon == null) icon = UIManager.getIcon("InternalFrame.icon");
 315         if (icon == null) return;
 316 
 317         // Resize to 16x16 if necessary.
 318         if (icon instanceof ImageIcon && (icon.getIconWidth() > sMaxIconWidth || icon.getIconHeight() > sMaxIconHeight)) {
 319             final Image img = ((ImageIcon)icon).getImage();
 320             ((ImageIcon)icon).setImage(img.getScaledInstance(sMaxIconWidth, sMaxIconHeight, Image.SCALE_SMOOTH));
 321         }
 322 
 323         icon.paintIcon(frame, g, x, y);
 324     }
 325 
 326     protected int getIconWidth(final JInternalFrame frame) {
 327         int width = 0;
 328 
 329         Icon icon = frame.getFrameIcon();
 330         if (icon == null) {
 331             icon = UIManager.getIcon("InternalFrame.icon");
 332         }
 333 
 334         if (icon != null && icon instanceof ImageIcon) {
 335             // Resize to 16x16 if necessary.
 336             width = Math.min(icon.getIconWidth(), sMaxIconWidth);
 337         }
 338 
 339         return width;
 340     }
 341 
 342     protected int getIconHeight(final JInternalFrame frame) {
 343         int height = 0;
 344 
 345         Icon icon = frame.getFrameIcon();
 346         if (icon == null) {
 347             icon = UIManager.getIcon("InternalFrame.icon");
 348         }
 349 
 350         if (icon != null && icon instanceof ImageIcon) {
 351             // Resize to 16x16 if necessary.
 352             height = Math.min(icon.getIconHeight(), sMaxIconHeight);
 353         }
 354 
 355         return height;
 356     }
 357 
 358     public void drawWindowTitle(final Graphics g, final JInternalFrame frame, final int inX, final int inY, final int inW, final int inH) {
 359         final int x = inX;
 360         final int y = inY;
 361         final int w = inW;
 362         int h = inH;
 363 
 364         h = metrics.titleBarHeight + inH;
 365 
 366         // paint the background
 367         titleBarPainter.state.set(frame.isSelected() ? State.ACTIVE : State.INACTIVE);
 368         titleBarPainter.paint(g, frame, x, y, w, h);
 369 
 370         // now the title and the icon
 371         paintTitleContents(g, frame, x, y, w, h);
 372 
 373         // finally the widgets
 374         drawAllWidgets(g, frame); // rollover is last attribute
 375     }
 376 
 377     // Component could be a JInternalFrame or a JDesktopIcon
 378     void paintBorder(final JInternalFrame frame, final Component c, final Graphics g, final int x, final int y, final int w, final int h) {
 379         if (fBorderInsets == null) getBorderInsets(c);
 380         // Set the contentRect - inset by border size
 381         setInBounds(x + fBorderInsets.left, y + fBorderInsets.top, w - (fBorderInsets.right + fBorderInsets.left), h - (fBorderInsets.top + fBorderInsets.bottom));
 382 
 383         // Set parameters
 384         setMetrics(frame, c);
 385 
 386         // Draw the frame
 387         drawWindowTitle(g, frame, x, y, w, h);
 388     }
 389 
 390     // defaults to false
 391     boolean isDirty(final JInternalFrame frame) {
 392         final Object dirty = frame.getClientProperty("windowModified");
 393         if (dirty == null || dirty == Boolean.FALSE) return false;
 394         return true;
 395     }
 396 
 397     // Border interface
 398     public Insets getBorderInsets(final Component c) {
 399         if (fBorderInsets == null) fBorderInsets = new Insets(0, 0, 0, 0);
 400 
 401         // Paranoia check
 402         if (!(c instanceof JInternalFrame)) return fBorderInsets;
 403 
 404         final JInternalFrame frame = (JInternalFrame)c;
 405 
 406         // Set the contentRect to an arbitrary value (in case the current real one is too small)
 407         setInBounds(0, 0, kContentTester, kContentTester);
 408 
 409         // Set parameters
 410         setMetrics(frame, c);
 411 
 412         fBorderInsets.left = 0;
 413         fBorderInsets.top = metrics.titleBarHeight;
 414         fBorderInsets.right = 0;
 415         fBorderInsets.bottom = 0;
 416 
 417         return fBorderInsets;
 418     }
 419 
 420     public void repaintButtonArea(final JInternalFrame frame) {
 421         final Insets i = frame.getInsets();
 422         final int x = i.left + metrics.leftSidePadding;
 423         final int y = i.top - metrics.titleBarHeight + 1;
 424         frame.repaint(x, y, fThisButtonSpan, metrics.titleBarHeight - 2);
 425     }
 426 
 427     // Draw all the widgets this frame supports
 428     void drawAllWidgets(final Graphics g, final JInternalFrame frame) {
 429         int x = metrics.leftSidePadding;
 430         int y = (metrics.titleBarHeight - metrics.buttonHeight) / 2 - metrics.titleBarHeight;
 431 
 432         final Insets insets = frame.getInsets();
 433         x += insets.left;
 434         y += insets.top + metrics.downShift;
 435 
 436         final AquaInternalFrameUI ui = (AquaInternalFrameUI)frame.getUI();
 437         final int buttonPressedIndex = ui.getWhichButtonPressed();
 438         final boolean overButton = ui.getMouseOverPressedButton();
 439         final boolean rollover = ui.getRollover();
 440 
 441         final boolean frameSelected = frame.isSelected() || fIsUtility;
 442         final boolean generalActive = rollover || frameSelected;
 443 
 444         final boolean dirty = isDirty(frame);
 445 
 446         paintButton(g, frame, x, y, kCloseButton, buttonPressedIndex, overButton, frame.isClosable(), generalActive, rollover, dirty);
 447 
 448         x += metrics.buttonPadding + metrics.buttonWidth;
 449         paintButton(g, frame, x, y, kIconButton, buttonPressedIndex, overButton, frame.isIconifiable(), generalActive, rollover, false);
 450 
 451         x += metrics.buttonPadding + metrics.buttonWidth;
 452         paintButton(g, frame, x, y, kGrowButton, buttonPressedIndex, overButton, frame.isMaximizable(), generalActive, rollover, false);
 453     }
 454 
 455     public void paintButton(final Graphics g, final JInternalFrame frame, final int x, final int y, final int buttonType, final int buttonPressedIndex, final boolean overButton, final boolean enabled, final boolean active, final boolean anyRollover, final boolean dirty) {
 456         widgetPainter.state.set(getWidget(frame, buttonType));
 457         widgetPainter.state.set(getState(buttonPressedIndex == buttonType && overButton, anyRollover, active, enabled));
 458         widgetPainter.state.set(dirty ? BooleanValue.YES : BooleanValue.NO);
 459         widgetPainter.paint(g, frame, x, y, metrics.buttonWidth, metrics.buttonHeight);
 460     }
 461 
 462     static Widget getWidget(final JInternalFrame frame, final int buttonType) {
 463         switch (buttonType) {
 464             case kIconButton: return Widget.TITLE_BAR_COLLAPSE_BOX;
 465             case kGrowButton: return Widget.TITLE_BAR_ZOOM_BOX;
 466         }
 467 
 468         return Widget.TITLE_BAR_CLOSE_BOX;
 469     }
 470 
 471     static State getState(final boolean pressed, final boolean rollover, final boolean active, final boolean enabled) {
 472         if (!enabled) return State.DISABLED;
 473         if (!active) return State.INACTIVE;
 474         if (pressed) return State.PRESSED;
 475         if (rollover) return State.ROLLOVER;
 476         return State.ACTIVE;
 477     }
 478 
 479     protected void setMetrics(final JInternalFrame frame, final Component window) {
 480         final String title = frame.getTitle();
 481         final FontMetrics fm = frame.getFontMetrics(UIManager.getFont("InternalFrame.titleFont"));
 482         int titleWidth = 0;
 483         int titleHeight = fm.getAscent();
 484         if (title != null) {
 485             titleWidth = SwingUtilities.computeStringWidth(fm, title);
 486         }
 487         // Icon space
 488         final Icon icon = frame.getFrameIcon();
 489         if (icon != null) {
 490             titleWidth += icon.getIconWidth();
 491             titleHeight = Math.max(titleHeight, icon.getIconHeight());
 492         }
 493     }
 494 
 495     protected int getTitleHeight() {
 496         return metrics.titleBarHeight;
 497     }
 498 }