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 }