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.getTextUIDrawing(frame)
 213                     .drawString(frame, g, text,
 214                                 x + startXPosition + iconWidth,
 215                                 y + baseline + 1);
 216 
 217             if (isSelected || fIsUtility) {
 218                 g.setColor(selectedTextColor);
 219             } else {
 220                 g.setColor(notSelectedTextColor);
 221             }
 222 
 223             SwingUtilities2.getTextUIDrawing(frame)
 224                     .drawString(frame, g, text,
 225                                 x + startXPosition + iconWidth,
 226                                 y + baseline);
 227             g.setFont(f);
 228         }
 229 
 230         // sja fix x & y
 231         final int iconYPostion = (metrics.titleBarHeight - getIconHeight(frame)) / 2;
 232         paintTitleIcon(g, frame, x + startXPosition, y + iconYPostion);
 233     }
 234 
 235     public int getWhichButtonHit(final JInternalFrame frame, final int x, final int y) {
 236         int buttonHit = -1;
 237 
 238         final Insets i = frame.getInsets();
 239         int startX = i.left + metrics.leftSidePadding - 1;
 240         if (isInsideYButtonArea(i, y) && x >= startX) {
 241             if (x <= (startX + metrics.buttonWidth)) {
 242                 if (frame.isClosable()) {
 243                     buttonHit = kCloseButton;
 244                 }
 245             } else {
 246                 startX += metrics.buttonWidth + metrics.buttonPadding;
 247                 if (x >= startX && x <= (startX + metrics.buttonWidth)) {
 248                     if (frame.isIconifiable()) {
 249                         buttonHit = kIconButton;
 250                     }
 251                 } else {
 252                     startX += metrics.buttonWidth + metrics.buttonPadding;
 253                     if (x >= startX && x <= (startX + metrics.buttonWidth)) {
 254                         if (frame.isMaximizable()) {
 255                             buttonHit = kGrowButton;
 256                         }
 257                     }
 258                 }
 259             }
 260         }
 261 
 262         return buttonHit;
 263     }
 264 
 265     public void doButtonAction(final JInternalFrame frame, final int whichButton) {
 266         switch (whichButton) {
 267             case kCloseButton:
 268                 frame.doDefaultCloseAction();
 269                 break;
 270 
 271             case kIconButton:
 272                 if (frame.isIconifiable()) {
 273                     if (!frame.isIcon()) {
 274                         try {
 275                             frame.setIcon(true);
 276                         } catch(final PropertyVetoException e1) {}
 277                     } else {
 278                         try {
 279                             frame.setIcon(false);
 280                         } catch(final PropertyVetoException e1) {}
 281                     }
 282                 }
 283                 break;
 284 
 285             case kGrowButton:
 286                 if (frame.isMaximizable()) {
 287                     if (!frame.isMaximum()) {
 288                         try {
 289                             frame.setMaximum(true);
 290                         } catch(final PropertyVetoException e5) {}
 291                     } else {
 292                         try {
 293                             frame.setMaximum(false);
 294                         } catch(final PropertyVetoException e6) {}
 295                     }
 296                 }
 297                 break;
 298 
 299             default:
 300                 System.err.println("AquaInternalFrameBorder should never get here!!!!");
 301                 Thread.dumpStack();
 302                 break;
 303         }
 304     }
 305 
 306     public boolean isInsideYButtonArea(final Insets i, final int y) {
 307         final int startY = (i.top - metrics.titleBarHeight / 2) - (metrics.buttonHeight / 2) - 1;
 308         final int endY = startY + metrics.buttonHeight;
 309         return y >= startY && y <= endY;
 310     }
 311 
 312     public boolean getWithinRolloverArea(final Insets i, final int x, final int y) {
 313         final int startX = i.left + metrics.leftSidePadding;
 314         final int endX = startX + fThisButtonSpan;
 315         return isInsideYButtonArea(i, y) && x >= startX && x <= endX;
 316     }
 317 
 318     protected void paintTitleIcon(final Graphics g, final JInternalFrame frame, final int x, final int y) {
 319         Icon icon = frame.getFrameIcon();
 320         if (icon == null) icon = UIManager.getIcon("InternalFrame.icon");
 321         if (icon == null) return;
 322 
 323         // Resize to 16x16 if necessary.
 324         if (icon instanceof ImageIcon && (icon.getIconWidth() > sMaxIconWidth || icon.getIconHeight() > sMaxIconHeight)) {
 325             final Image img = ((ImageIcon)icon).getImage();
 326             ((ImageIcon)icon).setImage(img.getScaledInstance(sMaxIconWidth, sMaxIconHeight, Image.SCALE_SMOOTH));
 327         }
 328 
 329         icon.paintIcon(frame, g, x, y);
 330     }
 331 
 332     protected int getIconWidth(final JInternalFrame frame) {
 333         int width = 0;
 334 
 335         Icon icon = frame.getFrameIcon();
 336         if (icon == null) {
 337             icon = UIManager.getIcon("InternalFrame.icon");
 338         }
 339 
 340         if (icon != null && icon instanceof ImageIcon) {
 341             // Resize to 16x16 if necessary.
 342             width = Math.min(icon.getIconWidth(), sMaxIconWidth);
 343         }
 344 
 345         return width;
 346     }
 347 
 348     protected int getIconHeight(final JInternalFrame frame) {
 349         int height = 0;
 350 
 351         Icon icon = frame.getFrameIcon();
 352         if (icon == null) {
 353             icon = UIManager.getIcon("InternalFrame.icon");
 354         }
 355 
 356         if (icon != null && icon instanceof ImageIcon) {
 357             // Resize to 16x16 if necessary.
 358             height = Math.min(icon.getIconHeight(), sMaxIconHeight);
 359         }
 360 
 361         return height;
 362     }
 363 
 364     public void drawWindowTitle(final Graphics g, final JInternalFrame frame, final int inX, final int inY, final int inW, final int inH) {
 365         final int x = inX;
 366         final int y = inY;
 367         final int w = inW;
 368         int h = inH;
 369 
 370         h = metrics.titleBarHeight + inH;
 371 
 372         // paint the background
 373         titleBarPainter.state.set(frame.isSelected() ? State.ACTIVE : State.INACTIVE);
 374         titleBarPainter.paint(g, frame, x, y, w, h);
 375 
 376         // now the title and the icon
 377         paintTitleContents(g, frame, x, y, w, h);
 378 
 379         // finally the widgets
 380         drawAllWidgets(g, frame); // rollover is last attribute
 381     }
 382 
 383     // Component could be a JInternalFrame or a JDesktopIcon
 384     void paintBorder(final JInternalFrame frame, final Component c, final Graphics g, final int x, final int y, final int w, final int h) {
 385         if (fBorderInsets == null) getBorderInsets(c);
 386         // Set the contentRect - inset by border size
 387         setInBounds(x + fBorderInsets.left, y + fBorderInsets.top, w - (fBorderInsets.right + fBorderInsets.left), h - (fBorderInsets.top + fBorderInsets.bottom));
 388 
 389         // Set parameters
 390         setMetrics(frame, c);
 391 
 392         // Draw the frame
 393         drawWindowTitle(g, frame, x, y, w, h);
 394     }
 395 
 396     // defaults to false
 397     boolean isDirty(final JInternalFrame frame) {
 398         final Object dirty = frame.getClientProperty("windowModified");
 399         if (dirty == null || dirty == Boolean.FALSE) return false;
 400         return true;
 401     }
 402 
 403     // Border interface
 404     public Insets getBorderInsets(final Component c) {
 405         if (fBorderInsets == null) fBorderInsets = new Insets(0, 0, 0, 0);
 406 
 407         // Paranoia check
 408         if (!(c instanceof JInternalFrame)) return fBorderInsets;
 409 
 410         final JInternalFrame frame = (JInternalFrame)c;
 411 
 412         // Set the contentRect to an arbitrary value (in case the current real one is too small)
 413         setInBounds(0, 0, kContentTester, kContentTester);
 414 
 415         // Set parameters
 416         setMetrics(frame, c);
 417 
 418         fBorderInsets.left = 0;
 419         fBorderInsets.top = metrics.titleBarHeight;
 420         fBorderInsets.right = 0;
 421         fBorderInsets.bottom = 0;
 422 
 423         return fBorderInsets;
 424     }
 425 
 426     public void repaintButtonArea(final JInternalFrame frame) {
 427         final Insets i = frame.getInsets();
 428         final int x = i.left + metrics.leftSidePadding;
 429         final int y = i.top - metrics.titleBarHeight + 1;
 430         frame.repaint(x, y, fThisButtonSpan, metrics.titleBarHeight - 2);
 431     }
 432 
 433     // Draw all the widgets this frame supports
 434     void drawAllWidgets(final Graphics g, final JInternalFrame frame) {
 435         int x = metrics.leftSidePadding;
 436         int y = (metrics.titleBarHeight - metrics.buttonHeight) / 2 - metrics.titleBarHeight;
 437 
 438         final Insets insets = frame.getInsets();
 439         x += insets.left;
 440         y += insets.top + metrics.downShift;
 441 
 442         final AquaInternalFrameUI ui = (AquaInternalFrameUI)frame.getUI();
 443         final int buttonPressedIndex = ui.getWhichButtonPressed();
 444         final boolean overButton = ui.getMouseOverPressedButton();
 445         final boolean rollover = ui.getRollover();
 446 
 447         final boolean frameSelected = frame.isSelected() || fIsUtility;
 448         final boolean generalActive = rollover || frameSelected;
 449 
 450         final boolean dirty = isDirty(frame);
 451 
 452         paintButton(g, frame, x, y, kCloseButton, buttonPressedIndex, overButton, frame.isClosable(), generalActive, rollover, dirty);
 453 
 454         x += metrics.buttonPadding + metrics.buttonWidth;
 455         paintButton(g, frame, x, y, kIconButton, buttonPressedIndex, overButton, frame.isIconifiable(), generalActive, rollover, false);
 456 
 457         x += metrics.buttonPadding + metrics.buttonWidth;
 458         paintButton(g, frame, x, y, kGrowButton, buttonPressedIndex, overButton, frame.isMaximizable(), generalActive, rollover, false);
 459     }
 460 
 461     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) {
 462         widgetPainter.state.set(getWidget(frame, buttonType));
 463         widgetPainter.state.set(getState(buttonPressedIndex == buttonType && overButton, anyRollover, active, enabled));
 464         widgetPainter.state.set(dirty ? BooleanValue.YES : BooleanValue.NO);
 465         widgetPainter.paint(g, frame, x, y, metrics.buttonWidth, metrics.buttonHeight);
 466     }
 467 
 468     static Widget getWidget(final JInternalFrame frame, final int buttonType) {
 469         switch (buttonType) {
 470             case kIconButton: return Widget.TITLE_BAR_COLLAPSE_BOX;
 471             case kGrowButton: return Widget.TITLE_BAR_ZOOM_BOX;
 472         }
 473 
 474         return Widget.TITLE_BAR_CLOSE_BOX;
 475     }
 476 
 477     static State getState(final boolean pressed, final boolean rollover, final boolean active, final boolean enabled) {
 478         if (!enabled) return State.DISABLED;
 479         if (!active) return State.INACTIVE;
 480         if (pressed) return State.PRESSED;
 481         if (rollover) return State.ROLLOVER;
 482         return State.ACTIVE;
 483     }
 484 
 485     protected void setMetrics(final JInternalFrame frame, final Component window) {
 486         final String title = frame.getTitle();
 487         final FontMetrics fm = frame.getFontMetrics(UIManager.getFont("InternalFrame.titleFont"));
 488         int titleWidth = 0;
 489         int titleHeight = fm.getAscent();
 490         if (title != null) {
 491             titleWidth = SwingUtilities.computeStringWidth(fm, title);
 492         }
 493         // Icon space
 494         final Icon icon = frame.getFrameIcon();
 495         if (icon != null) {
 496             titleWidth += icon.getIconWidth();
 497             titleHeight = Math.max(titleHeight, icon.getIconHeight());
 498         }
 499     }
 500 
 501     protected int getTitleHeight() {
 502         return metrics.titleBarHeight;
 503     }
 504 }