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 import java.awt.geom.AffineTransform;
  44 
  45 public class AquaInternalFrameBorder implements Border, UIResource {
  46     private static final int kCloseButton = 0;
  47     private static final int kIconButton = 1;
  48     private static final int kGrowButton = 2;
  49 
  50     private static final int sMaxIconWidth = 15;
  51     private static final int sMaxIconHeight = sMaxIconWidth;
  52     private static final int sAfterButtonPad = 11;
  53     private static final int sAfterIconPad = 5;
  54     private static final int sRightSideTitleClip = 0;
  55 
  56     private static final int kContentTester = 100; // For getting region insets
  57 
  58     static final RecyclableSingleton<AquaInternalFrameBorder> documentWindowFrame = new RecyclableSingleton<AquaInternalFrameBorder>() {
  59         protected AquaInternalFrameBorder getInstance() {
  60             return new AquaInternalFrameBorder(WindowType.DOCUMENT);
  61         }
  62     };
  63     protected static AquaInternalFrameBorder window() {
  64         return documentWindowFrame.get();
  65     }
  66 
  67     static final RecyclableSingleton<AquaInternalFrameBorder> utilityWindowFrame = new RecyclableSingleton<AquaInternalFrameBorder>() {
  68         protected AquaInternalFrameBorder getInstance() {
  69             return new AquaInternalFrameBorder(WindowType.UTILITY);
  70         }
  71     };
  72     protected static AquaInternalFrameBorder utility() {
  73         return utilityWindowFrame.get();
  74     }
  75 
  76     static final RecyclableSingleton<AquaInternalFrameBorder> dialogWindowFrame = new RecyclableSingleton<AquaInternalFrameBorder>() {
  77         protected AquaInternalFrameBorder getInstance() {
  78             return new AquaInternalFrameBorder(WindowType.DOCUMENT);
  79         }
  80     };
  81     protected static AquaInternalFrameBorder dialog() {
  82         return dialogWindowFrame.get();
  83     }
  84 
  85     private final AquaInternalFrameBorderMetrics metrics;
  86 
  87     private final int fThisButtonSpan;
  88     private final int fThisLeftSideTotal;
  89 
  90     private final boolean fIsUtility;
  91 
  92     // Instance variables
  93     private final WindowType fWindowKind; // Which kind of window to draw
  94     private Insets fBorderInsets; // Cached insets object
  95 
  96     private Color selectedTextColor;
  97     private Color notSelectedTextColor;
  98 
  99     private Rectangle fInBounds; // Cached bounds rect object
 100 
 101     protected final AquaPainter<TitleBarHeightState> titleBarPainter = AquaPainter.create(JRSUIStateFactory.getTitleBar());
 102     protected final AquaPainter<JRSUIState> widgetPainter = AquaPainter.create(JRSUIState.getInstance());
 103 
 104     protected AquaInternalFrameBorder(final WindowType kind) {
 105         fWindowKind = kind;
 106 
 107         titleBarPainter.state.set(WindowClipCorners.YES);
 108         if (fWindowKind == WindowType.UTILITY) {
 109             fIsUtility = true;
 110             metrics = AquaInternalFrameBorderMetrics.getMetrics(true);
 111 
 112             widgetPainter.state.set(WindowType.UTILITY);
 113             titleBarPainter.state.set(WindowType.UTILITY);
 114         } else {
 115             fIsUtility = false;
 116             metrics = AquaInternalFrameBorderMetrics.getMetrics(false);
 117 
 118             widgetPainter.state.set(WindowType.DOCUMENT);
 119             titleBarPainter.state.set(WindowType.DOCUMENT);
 120         }
 121         titleBarPainter.state.setValue(metrics.titleBarHeight);
 122         titleBarPainter.state.set(WindowTitleBarSeparator.YES);
 123         widgetPainter.state.set(AlignmentVertical.CENTER);
 124 
 125         fThisButtonSpan = (metrics.buttonWidth * 3) + (metrics.buttonPadding * 2);
 126         fThisLeftSideTotal = metrics.leftSidePadding + fThisButtonSpan + sAfterButtonPad;
 127     }
 128 
 129     public void setColors(final Color inSelectedTextColor, final Color inNotSelectedTextColor) {
 130         selectedTextColor = inSelectedTextColor;
 131         notSelectedTextColor = inNotSelectedTextColor;
 132     }
 133 
 134     // Utility to lazy-init and fill in fInBounds
 135     protected void setInBounds(final int x, final int y, final int w, final int h) {
 136         if (fInBounds == null) fInBounds = new Rectangle();
 137 
 138         fInBounds.x = x;
 139         fInBounds.y = y;
 140         fInBounds.width = w;
 141         fInBounds.height = h;
 142     }
 143 
 144     // Border interface
 145     public boolean isBorderOpaque() {
 146         return false;
 147     }
 148 
 149     // Border interface
 150     public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int w, final int h) {
 151         // For expanded InternalFrames, the frame & component are the same object
 152         paintBorder((JInternalFrame)c, c, g, x, y, w, h);
 153     }
 154 
 155     protected void paintTitleContents(final Graphics g, final JInternalFrame frame, final int x, final int y, final int w, final int h) {
 156         final boolean isSelected = frame.isSelected();
 157         final Font f = g.getFont();
 158 
 159         g.setFont(metrics.font);
 160 
 161         // Center text vertically.
 162         final FontMetrics fm = g.getFontMetrics();
 163         final int baseline = (metrics.titleBarHeight + fm.getAscent() - fm.getLeading() - fm.getDescent()) / 2;
 164 
 165         // max button is the rightmost so use it
 166         final int usedWidth = fThisLeftSideTotal + sRightSideTitleClip;
 167         int iconWidth = getIconWidth(frame);
 168         if (iconWidth > 0) iconWidth += sAfterIconPad;
 169 
 170         final int totalWidth = w;
 171 
 172         // window title looks like: | 0 0 0(sAfterButtonPad)IconWidth Title(right pad) |
 173         final int availTextWidth = totalWidth - usedWidth - iconWidth - sAfterButtonPad;
 174 
 175         final String title = frame.getTitle();
 176 
 177         String text = title;
 178         int totalTextWidth = 0;
 179 
 180         int startXPosition = fThisLeftSideTotal;
 181         boolean wasTextShortened = false;
 182         // shorten the string to fit in the
 183         if ((text != null) && !(text.equals(""))) {
 184             totalTextWidth = SwingUtilities.computeStringWidth(fm, text);
 185             final String clipString = "\u2026";
 186             if (totalTextWidth > availTextWidth) {
 187                 wasTextShortened = true;
 188                 totalTextWidth = SwingUtilities.computeStringWidth(fm, clipString);
 189                 int nChars;
 190                 for (nChars = 0; nChars < text.length(); nChars++) {
 191                     final int nextCharWidth = fm.charWidth(text.charAt(nChars));
 192                     if ((totalTextWidth + nextCharWidth) > availTextWidth) {
 193                         break;
 194                     }
 195                     totalTextWidth += nextCharWidth;
 196                 }
 197                 text = text.substring(0, nChars) + clipString;
 198             }
 199 
 200             if (!wasTextShortened) {
 201                 // center it!
 202                 startXPosition = (totalWidth - (totalTextWidth + iconWidth)) / 2;
 203                 if (startXPosition < fThisLeftSideTotal) {
 204                     startXPosition = fThisLeftSideTotal;
 205                 }
 206             }
 207 
 208             if (isSelected || fIsUtility) {
 209                 g.setColor(Color.lightGray);
 210             } else {
 211                 g.setColor(Color.white);
 212             }
 213             SwingUtilities2.getTextUIDrawing(frame)
 214                     .drawString(frame, g, text,
 215                                 x + startXPosition + iconWidth,
 216                                 y + baseline + 1);
 217 
 218             if (isSelected || fIsUtility) {
 219                 g.setColor(selectedTextColor);
 220             } else {
 221                 g.setColor(notSelectedTextColor);
 222             }
 223 
 224             SwingUtilities2.getTextUIDrawing(frame)
 225                     .drawString(frame, g, text,
 226                                 x + startXPosition + iconWidth,
 227                                 y + baseline);
 228             g.setFont(f);
 229         }
 230 
 231         // sja fix x & y
 232         final int iconYPostion = (metrics.titleBarHeight - getIconHeight(frame)) / 2;
 233         paintTitleIcon(g, frame, x + startXPosition, y + iconYPostion);
 234     }
 235 
 236     public int getWhichButtonHit(final JInternalFrame frame, final int x, final int y) {
 237         int buttonHit = -1;
 238 
 239         final Insets i = frame.getInsets();
 240         int startX = i.left + metrics.leftSidePadding - 1;
 241         if (isInsideYButtonArea(i, y) && x >= startX) {
 242             if (x <= (startX + metrics.buttonWidth)) {
 243                 if (frame.isClosable()) {
 244                     buttonHit = kCloseButton;
 245                 }
 246             } else {
 247                 startX += metrics.buttonWidth + metrics.buttonPadding;
 248                 if (x >= startX && x <= (startX + metrics.buttonWidth)) {
 249                     if (frame.isIconifiable()) {
 250                         buttonHit = kIconButton;
 251                     }
 252                 } else {
 253                     startX += metrics.buttonWidth + metrics.buttonPadding;
 254                     if (x >= startX && x <= (startX + metrics.buttonWidth)) {
 255                         if (frame.isMaximizable()) {
 256                             buttonHit = kGrowButton;
 257                         }
 258                     }
 259                 }
 260             }
 261         }
 262 
 263         return buttonHit;
 264     }
 265 
 266     public void doButtonAction(final JInternalFrame frame, final int whichButton) {
 267         switch (whichButton) {
 268             case kCloseButton:
 269                 frame.doDefaultCloseAction();
 270                 break;
 271 
 272             case kIconButton:
 273                 if (frame.isIconifiable()) {
 274                     if (!frame.isIcon()) {
 275                         try {
 276                             frame.setIcon(true);
 277                         } catch(final PropertyVetoException e1) {}
 278                     } else {
 279                         try {
 280                             frame.setIcon(false);
 281                         } catch(final PropertyVetoException e1) {}
 282                     }
 283                 }
 284                 break;
 285 
 286             case kGrowButton:
 287                 if (frame.isMaximizable()) {
 288                     if (!frame.isMaximum()) {
 289                         try {
 290                             frame.setMaximum(true);
 291                         } catch(final PropertyVetoException e5) {}
 292                     } else {
 293                         try {
 294                             frame.setMaximum(false);
 295                         } catch(final PropertyVetoException e6) {}
 296                     }
 297                 }
 298                 break;
 299 
 300             default:
 301                 System.err.println("AquaInternalFrameBorder should never get here!!!!");
 302                 Thread.dumpStack();
 303                 break;
 304         }
 305     }
 306 
 307     public boolean isInsideYButtonArea(final Insets i, final int y) {
 308         final int startY = (i.top - metrics.titleBarHeight / 2) - (metrics.buttonHeight / 2) - 1;
 309         final int endY = startY + metrics.buttonHeight;
 310         return y >= startY && y <= endY;
 311     }
 312 
 313     public boolean getWithinRolloverArea(final Insets i, final int x, final int y) {
 314         final int startX = i.left + metrics.leftSidePadding;
 315         final int endX = startX + fThisButtonSpan;
 316         return isInsideYButtonArea(i, y) && x >= startX && x <= endX;
 317     }
 318 
 319     protected void paintTitleIcon(final Graphics g, final JInternalFrame frame,
 320             final int x, final int y) {
 321 
 322         Icon icon = frame.getFrameIcon();
 323         if (icon == null) {
 324             icon = UIManager.getIcon("InternalFrame.icon");
 325         }
 326 
 327         if (icon == null) {
 328             return;
 329         }
 330 
 331         if (icon.getIconWidth() > sMaxIconWidth
 332                 || icon.getIconHeight() > sMaxIconHeight) {
 333             final Graphics2D g2 = (Graphics2D) g;
 334             final AffineTransform savedAT = g2.getTransform();
 335             double xScaleFactor = (double) sMaxIconWidth / icon.getIconWidth();
 336             double yScaleFactor = (double) sMaxIconHeight / icon.getIconHeight();
 337 
 338             //Coordinates are after a translation hence relative origin shifts
 339             g2.translate(x, y);
 340 
 341             //scaling factor is needed to scale while maintaining aspect ratio
 342             double scaleMaintainAspectRatio = Math.min(xScaleFactor, yScaleFactor);
 343 
 344             //minimum value is taken to set to a maximum Icon Dimension
 345             g2.scale(scaleMaintainAspectRatio, scaleMaintainAspectRatio);
 346 
 347             icon.paintIcon(frame, g2, 0, 0);
 348             g2.setTransform(savedAT);
 349 
 350         } else {
 351             icon.paintIcon(frame, g, x, y);
 352         }
 353     }
 354 
 355     protected int getIconWidth(final JInternalFrame frame) {
 356         int width = 0;
 357 
 358         Icon icon = frame.getFrameIcon();
 359         if (icon == null) {
 360             icon = UIManager.getIcon("InternalFrame.icon");
 361         }
 362         if (icon != null) {
 363             width = Math.min(icon.getIconWidth(), sMaxIconWidth);
 364         }
 365 
 366         return width;
 367     }
 368 
 369     protected int getIconHeight(final JInternalFrame frame) {
 370         int height = 0;
 371 
 372         Icon icon = frame.getFrameIcon();
 373         if (icon == null) {
 374             icon = UIManager.getIcon("InternalFrame.icon");
 375         }
 376         if (icon != null) {
 377             height = Math.min(icon.getIconHeight(), sMaxIconHeight);
 378         }
 379 
 380         return height;
 381     }
 382 
 383     public void drawWindowTitle(final Graphics g, final JInternalFrame frame, final int inX, final int inY, final int inW, final int inH) {
 384         final int x = inX;
 385         final int y = inY;
 386         final int w = inW;
 387         int h = inH;
 388 
 389         h = metrics.titleBarHeight + inH;
 390 
 391         // paint the background
 392         titleBarPainter.state.set(frame.isSelected() ? State.ACTIVE : State.INACTIVE);
 393         titleBarPainter.paint(g, frame, x, y, w, h);
 394 
 395         // now the title and the icon
 396         paintTitleContents(g, frame, x, y, w, h);
 397 
 398         // finally the widgets
 399         drawAllWidgets(g, frame); // rollover is last attribute
 400     }
 401 
 402     // Component could be a JInternalFrame or a JDesktopIcon
 403     void paintBorder(final JInternalFrame frame, final Component c, final Graphics g, final int x, final int y, final int w, final int h) {
 404         if (fBorderInsets == null) getBorderInsets(c);
 405         // Set the contentRect - inset by border size
 406         setInBounds(x + fBorderInsets.left, y + fBorderInsets.top, w - (fBorderInsets.right + fBorderInsets.left), h - (fBorderInsets.top + fBorderInsets.bottom));
 407 
 408         // Set parameters
 409         setMetrics(frame, c);
 410 
 411         // Draw the frame
 412         drawWindowTitle(g, frame, x, y, w, h);
 413     }
 414 
 415     // defaults to false
 416     boolean isDirty(final JInternalFrame frame) {
 417         final Object dirty = frame.getClientProperty("windowModified");
 418         if (dirty == null || dirty == Boolean.FALSE) return false;
 419         return true;
 420     }
 421 
 422     // Border interface
 423     public Insets getBorderInsets(final Component c) {
 424         if (fBorderInsets == null) fBorderInsets = new Insets(0, 0, 0, 0);
 425 
 426         // Paranoia check
 427         if (!(c instanceof JInternalFrame)) return fBorderInsets;
 428 
 429         final JInternalFrame frame = (JInternalFrame)c;
 430 
 431         // Set the contentRect to an arbitrary value (in case the current real one is too small)
 432         setInBounds(0, 0, kContentTester, kContentTester);
 433 
 434         // Set parameters
 435         setMetrics(frame, c);
 436 
 437         fBorderInsets.left = 0;
 438         fBorderInsets.top = metrics.titleBarHeight;
 439         fBorderInsets.right = 0;
 440         fBorderInsets.bottom = 0;
 441 
 442         return fBorderInsets;
 443     }
 444 
 445     public void repaintButtonArea(final JInternalFrame frame) {
 446         final Insets i = frame.getInsets();
 447         final int x = i.left + metrics.leftSidePadding;
 448         final int y = i.top - metrics.titleBarHeight + 1;
 449         frame.repaint(x, y, fThisButtonSpan, metrics.titleBarHeight - 2);
 450     }
 451 
 452     // Draw all the widgets this frame supports
 453     void drawAllWidgets(final Graphics g, final JInternalFrame frame) {
 454         int x = metrics.leftSidePadding;
 455         int y = (metrics.titleBarHeight - metrics.buttonHeight) / 2 - metrics.titleBarHeight;
 456 
 457         final Insets insets = frame.getInsets();
 458         x += insets.left;
 459         y += insets.top + metrics.downShift;
 460 
 461         final AquaInternalFrameUI ui = (AquaInternalFrameUI)frame.getUI();
 462         final int buttonPressedIndex = ui.getWhichButtonPressed();
 463         final boolean overButton = ui.getMouseOverPressedButton();
 464         final boolean rollover = ui.getRollover();
 465 
 466         final boolean frameSelected = frame.isSelected() || fIsUtility;
 467         final boolean generalActive = rollover || frameSelected;
 468 
 469         final boolean dirty = isDirty(frame);
 470 
 471         paintButton(g, frame, x, y, kCloseButton, buttonPressedIndex, overButton, frame.isClosable(), generalActive, rollover, dirty);
 472 
 473         x += metrics.buttonPadding + metrics.buttonWidth;
 474         paintButton(g, frame, x, y, kIconButton, buttonPressedIndex, overButton, frame.isIconifiable(), generalActive, rollover, false);
 475 
 476         x += metrics.buttonPadding + metrics.buttonWidth;
 477         paintButton(g, frame, x, y, kGrowButton, buttonPressedIndex, overButton, frame.isMaximizable(), generalActive, rollover, false);
 478     }
 479 
 480     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) {
 481         widgetPainter.state.set(getWidget(frame, buttonType));
 482         widgetPainter.state.set(getState(buttonPressedIndex == buttonType && overButton, anyRollover, active, enabled));
 483         widgetPainter.state.set(dirty ? BooleanValue.YES : BooleanValue.NO);
 484         widgetPainter.paint(g, frame, x, y, metrics.buttonWidth, metrics.buttonHeight);
 485     }
 486 
 487     static Widget getWidget(final JInternalFrame frame, final int buttonType) {
 488         switch (buttonType) {
 489             case kIconButton: return Widget.TITLE_BAR_COLLAPSE_BOX;
 490             case kGrowButton: return Widget.TITLE_BAR_ZOOM_BOX;
 491         }
 492 
 493         return Widget.TITLE_BAR_CLOSE_BOX;
 494     }
 495 
 496     static State getState(final boolean pressed, final boolean rollover, final boolean active, final boolean enabled) {
 497         if (!enabled) return State.DISABLED;
 498         if (!active) return State.INACTIVE;
 499         if (pressed) return State.PRESSED;
 500         if (rollover) return State.ROLLOVER;
 501         return State.ACTIVE;
 502     }
 503 
 504     protected void setMetrics(final JInternalFrame frame, final Component window) {
 505         final String title = frame.getTitle();
 506         final FontMetrics fm = frame.getFontMetrics(UIManager.getFont("InternalFrame.titleFont"));
 507         int titleWidth = 0;
 508         int titleHeight = fm.getAscent();
 509         if (title != null) {
 510             titleWidth = SwingUtilities.computeStringWidth(fm, title);
 511         }
 512         // Icon space
 513         final Icon icon = frame.getFrameIcon();
 514         if (icon != null) {
 515             titleWidth += icon.getIconWidth();
 516             titleHeight = Math.max(titleHeight, icon.getIconHeight());
 517         }
 518     }
 519 
 520     protected int getTitleHeight() {
 521         return metrics.titleBarHeight;
 522     }
 523 }