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