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     };
window()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     };
utility()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     };
dialog()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 
AquaInternalFrameBorder(final WindowType kind)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 
setColors(final Color inSelectedTextColor, final Color inNotSelectedTextColor)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
setInBounds(final int x, final int y, final int w, final int h)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
isBorderOpaque()145     public boolean isBorderOpaque() {
146         return false;
147     }
148 
149     // Border interface
paintBorder(final Component c, final Graphics g, final int x, final int y, final int w, final int h)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 
paintTitleContents(final Graphics g, final JInternalFrame frame, final int x, final int y, final int w, final int h)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 
getWhichButtonHit(final JInternalFrame frame, final int x, final int y)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 
doButtonAction(final JInternalFrame frame, final int whichButton)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 
isInsideYButtonArea(final Insets i, final int y)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 
getWithinRolloverArea(final Insets i, final int x, final int y)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 
paintTitleIcon(final Graphics g, final JInternalFrame frame, final int x, final int y)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 
getIconWidth(final JInternalFrame frame)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 
getIconHeight(final JInternalFrame frame)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 
drawWindowTitle(final Graphics g, final JInternalFrame frame, final int inX, final int inY, final int inW, final int inH)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
paintBorder(final JInternalFrame frame, final Component c, final Graphics g, final int x, final int y, final int w, final int h)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
isDirty(final JInternalFrame frame)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
getBorderInsets(final Component c)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 
repaintButtonArea(final JInternalFrame frame)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
drawAllWidgets(final Graphics g, final JInternalFrame frame)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 
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)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 
getWidget(final JInternalFrame frame, final int buttonType)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 
getState(final boolean pressed, final boolean rollover, final boolean active, final boolean enabled)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 
setMetrics(final JInternalFrame frame, final Component window)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 
getTitleHeight()514     protected int getTitleHeight() {
515         return metrics.titleBarHeight;
516     }
517 }
518