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 }