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 }