Path: blob/master/src/java.desktop/macosx/classes/com/apple/laf/AquaInternalFrameBorder.java
41154 views
/*1* Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.apple.laf;2627import java.awt.*;28import java.beans.PropertyVetoException;2930import javax.swing.*;31import javax.swing.border.Border;32import javax.swing.plaf.UIResource;3334import sun.swing.SwingUtilities2;3536import apple.laf.*;37import apple.laf.JRSUIConstants.*;38import apple.laf.JRSUIState.TitleBarHeightState;3940import com.apple.laf.AquaUtils.RecyclableSingleton;41import com.apple.laf.AquaInternalFrameBorderMetrics;42import java.awt.geom.AffineTransform;4344public class AquaInternalFrameBorder implements Border, UIResource {45private static final int kCloseButton = 0;46private static final int kIconButton = 1;47private static final int kGrowButton = 2;4849private static final int sMaxIconWidth = 15;50private static final int sMaxIconHeight = sMaxIconWidth;51private static final int sAfterButtonPad = 11;52private static final int sAfterIconPad = 5;53private static final int sRightSideTitleClip = 0;5455private static final int kContentTester = 100; // For getting region insets5657private static final RecyclableSingleton<AquaInternalFrameBorder> documentWindowFrame = new RecyclableSingleton<AquaInternalFrameBorder>() {58protected AquaInternalFrameBorder getInstance() {59return new AquaInternalFrameBorder(WindowType.DOCUMENT);60}61};62protected static AquaInternalFrameBorder window() {63return documentWindowFrame.get();64}6566private static final RecyclableSingleton<AquaInternalFrameBorder> utilityWindowFrame = new RecyclableSingleton<AquaInternalFrameBorder>() {67protected AquaInternalFrameBorder getInstance() {68return new AquaInternalFrameBorder(WindowType.UTILITY);69}70};71protected static AquaInternalFrameBorder utility() {72return utilityWindowFrame.get();73}7475private static final RecyclableSingleton<AquaInternalFrameBorder> dialogWindowFrame = new RecyclableSingleton<AquaInternalFrameBorder>() {76protected AquaInternalFrameBorder getInstance() {77return new AquaInternalFrameBorder(WindowType.DOCUMENT);78}79};80protected static AquaInternalFrameBorder dialog() {81return dialogWindowFrame.get();82}8384private final AquaInternalFrameBorderMetrics metrics;8586private final int fThisButtonSpan;87private final int fThisLeftSideTotal;8889private final boolean fIsUtility;9091// Instance variables92private final WindowType fWindowKind; // Which kind of window to draw93private Insets fBorderInsets; // Cached insets object9495private Color selectedTextColor;96private Color notSelectedTextColor;9798private Rectangle fInBounds; // Cached bounds rect object99100protected final AquaPainter<TitleBarHeightState> titleBarPainter = AquaPainter.create(JRSUIStateFactory.getTitleBar());101protected final AquaPainter<JRSUIState> widgetPainter = AquaPainter.create(JRSUIState.getInstance());102103protected AquaInternalFrameBorder(final WindowType kind) {104fWindowKind = kind;105106titleBarPainter.state.set(WindowClipCorners.YES);107if (fWindowKind == WindowType.UTILITY) {108fIsUtility = true;109metrics = AquaInternalFrameBorderMetrics.getMetrics(true);110111widgetPainter.state.set(WindowType.UTILITY);112titleBarPainter.state.set(WindowType.UTILITY);113} else {114fIsUtility = false;115metrics = AquaInternalFrameBorderMetrics.getMetrics(false);116117widgetPainter.state.set(WindowType.DOCUMENT);118titleBarPainter.state.set(WindowType.DOCUMENT);119}120titleBarPainter.state.setValue(metrics.titleBarHeight);121titleBarPainter.state.set(WindowTitleBarSeparator.YES);122widgetPainter.state.set(AlignmentVertical.CENTER);123124fThisButtonSpan = (metrics.buttonWidth * 3) + (metrics.buttonPadding * 2);125fThisLeftSideTotal = metrics.leftSidePadding + fThisButtonSpan + sAfterButtonPad;126}127128public void setColors(final Color inSelectedTextColor, final Color inNotSelectedTextColor) {129selectedTextColor = inSelectedTextColor;130notSelectedTextColor = inNotSelectedTextColor;131}132133// Utility to lazy-init and fill in fInBounds134protected void setInBounds(final int x, final int y, final int w, final int h) {135if (fInBounds == null) fInBounds = new Rectangle();136137fInBounds.x = x;138fInBounds.y = y;139fInBounds.width = w;140fInBounds.height = h;141}142143// Border interface144public boolean isBorderOpaque() {145return false;146}147148// Border interface149public 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 object151paintBorder((JInternalFrame)c, c, g, x, y, w, h);152}153154protected void paintTitleContents(final Graphics g, final JInternalFrame frame, final int x, final int y, final int w, final int h) {155final boolean isSelected = frame.isSelected();156final Font f = g.getFont();157158g.setFont(metrics.font);159160// Center text vertically.161final FontMetrics fm = g.getFontMetrics();162final int baseline = (metrics.titleBarHeight + fm.getAscent() - fm.getLeading() - fm.getDescent()) / 2;163164// max button is the rightmost so use it165final int usedWidth = fThisLeftSideTotal + sRightSideTitleClip;166int iconWidth = getIconWidth(frame);167if (iconWidth > 0) iconWidth += sAfterIconPad;168169final int totalWidth = w;170171// window title looks like: | 0 0 0(sAfterButtonPad)IconWidth Title(right pad) |172final int availTextWidth = totalWidth - usedWidth - iconWidth - sAfterButtonPad;173174final String title = frame.getTitle();175176String text = title;177int totalTextWidth = 0;178179int startXPosition = fThisLeftSideTotal;180boolean wasTextShortened = false;181// shorten the string to fit in the182if (text != null && !text.isEmpty()) {183totalTextWidth = SwingUtilities.computeStringWidth(fm, text);184final String clipString = "\u2026";185if (totalTextWidth > availTextWidth) {186wasTextShortened = true;187totalTextWidth = SwingUtilities.computeStringWidth(fm, clipString);188int nChars;189for (nChars = 0; nChars < text.length(); nChars++) {190final int nextCharWidth = fm.charWidth(text.charAt(nChars));191if ((totalTextWidth + nextCharWidth) > availTextWidth) {192break;193}194totalTextWidth += nextCharWidth;195}196text = text.substring(0, nChars) + clipString;197}198199if (!wasTextShortened) {200// center it!201startXPosition = (totalWidth - (totalTextWidth + iconWidth)) / 2;202if (startXPosition < fThisLeftSideTotal) {203startXPosition = fThisLeftSideTotal;204}205}206207if (isSelected || fIsUtility) {208g.setColor(Color.lightGray);209} else {210g.setColor(Color.white);211}212SwingUtilities2.drawString(frame, g, text, x + startXPosition + iconWidth, y + baseline + 1);213214if (isSelected || fIsUtility) {215g.setColor(selectedTextColor);216} else {217g.setColor(notSelectedTextColor);218}219220SwingUtilities2.drawString(frame, g, text, x + startXPosition + iconWidth, y + baseline);221g.setFont(f);222}223224// sja fix x & y225final int iconYPostion = (metrics.titleBarHeight - getIconHeight(frame)) / 2;226paintTitleIcon(g, frame, x + startXPosition, y + iconYPostion);227}228229public int getWhichButtonHit(final JInternalFrame frame, final int x, final int y) {230int buttonHit = -1;231232final Insets i = frame.getInsets();233int startX = i.left + metrics.leftSidePadding - 1;234if (isInsideYButtonArea(i, y) && x >= startX) {235if (x <= (startX + metrics.buttonWidth)) {236if (frame.isClosable()) {237buttonHit = kCloseButton;238}239} else {240startX += metrics.buttonWidth + metrics.buttonPadding;241if (x >= startX && x <= (startX + metrics.buttonWidth)) {242if (frame.isIconifiable()) {243buttonHit = kIconButton;244}245} else {246startX += metrics.buttonWidth + metrics.buttonPadding;247if (x >= startX && x <= (startX + metrics.buttonWidth)) {248if (frame.isMaximizable()) {249buttonHit = kGrowButton;250}251}252}253}254}255256return buttonHit;257}258259public void doButtonAction(final JInternalFrame frame, final int whichButton) {260switch (whichButton) {261case kCloseButton:262frame.doDefaultCloseAction();263break;264265case kIconButton:266if (frame.isIconifiable()) {267if (!frame.isIcon()) {268try {269frame.setIcon(true);270} catch(final PropertyVetoException e1) {}271} else {272try {273frame.setIcon(false);274} catch(final PropertyVetoException e1) {}275}276}277break;278279case kGrowButton:280if (frame.isMaximizable()) {281if (!frame.isMaximum()) {282try {283frame.setMaximum(true);284} catch(final PropertyVetoException e5) {}285} else {286try {287frame.setMaximum(false);288} catch(final PropertyVetoException e6) {}289}290}291break;292293default:294System.err.println("AquaInternalFrameBorder should never get here!!!!");295Thread.dumpStack();296break;297}298}299300public boolean isInsideYButtonArea(final Insets i, final int y) {301final int startY = (i.top - metrics.titleBarHeight / 2) - (metrics.buttonHeight / 2) - 1;302final int endY = startY + metrics.buttonHeight;303return y >= startY && y <= endY;304}305306public boolean getWithinRolloverArea(final Insets i, final int x, final int y) {307final int startX = i.left + metrics.leftSidePadding;308final int endX = startX + fThisButtonSpan;309return isInsideYButtonArea(i, y) && x >= startX && x <= endX;310}311312protected void paintTitleIcon(final Graphics g, final JInternalFrame frame,313final int x, final int y) {314315Icon icon = frame.getFrameIcon();316if (icon == null) {317icon = UIManager.getIcon("InternalFrame.icon");318}319320if (icon == null) {321return;322}323324if (icon.getIconWidth() > sMaxIconWidth325|| icon.getIconHeight() > sMaxIconHeight) {326final Graphics2D g2 = (Graphics2D) g;327final AffineTransform savedAT = g2.getTransform();328double xScaleFactor = (double) sMaxIconWidth / icon.getIconWidth();329double yScaleFactor = (double) sMaxIconHeight / icon.getIconHeight();330331//Coordinates are after a translation hence relative origin shifts332g2.translate(x, y);333334//scaling factor is needed to scale while maintaining aspect ratio335double scaleMaintainAspectRatio = Math.min(xScaleFactor, yScaleFactor);336337//minimum value is taken to set to a maximum Icon Dimension338g2.scale(scaleMaintainAspectRatio, scaleMaintainAspectRatio);339340icon.paintIcon(frame, g2, 0, 0);341g2.setTransform(savedAT);342343} else {344icon.paintIcon(frame, g, x, y);345}346}347348protected int getIconWidth(final JInternalFrame frame) {349int width = 0;350351Icon icon = frame.getFrameIcon();352if (icon == null) {353icon = UIManager.getIcon("InternalFrame.icon");354}355if (icon != null) {356width = Math.min(icon.getIconWidth(), sMaxIconWidth);357}358359return width;360}361362protected int getIconHeight(final JInternalFrame frame) {363int height = 0;364365Icon icon = frame.getFrameIcon();366if (icon == null) {367icon = UIManager.getIcon("InternalFrame.icon");368}369if (icon != null) {370height = Math.min(icon.getIconHeight(), sMaxIconHeight);371}372373return height;374}375376public void drawWindowTitle(final Graphics g, final JInternalFrame frame, final int inX, final int inY, final int inW, final int inH) {377final int x = inX;378final int y = inY;379final int w = inW;380int h = inH;381382h = metrics.titleBarHeight + inH;383384// paint the background385titleBarPainter.state.set(frame.isSelected() ? State.ACTIVE : State.INACTIVE);386titleBarPainter.paint(g, frame, x, y, w, h);387388// now the title and the icon389paintTitleContents(g, frame, x, y, w, h);390391// finally the widgets392drawAllWidgets(g, frame); // rollover is last attribute393}394395// Component could be a JInternalFrame or a JDesktopIcon396void paintBorder(final JInternalFrame frame, final Component c, final Graphics g, final int x, final int y, final int w, final int h) {397if (fBorderInsets == null) getBorderInsets(c);398// Set the contentRect - inset by border size399setInBounds(x + fBorderInsets.left, y + fBorderInsets.top, w - (fBorderInsets.right + fBorderInsets.left), h - (fBorderInsets.top + fBorderInsets.bottom));400401// Set parameters402setMetrics(frame, c);403404// Draw the frame405drawWindowTitle(g, frame, x, y, w, h);406}407408// defaults to false409boolean isDirty(final JInternalFrame frame) {410final Object dirty = frame.getClientProperty("windowModified");411if (dirty == null || dirty == Boolean.FALSE) return false;412return true;413}414415// Border interface416public Insets getBorderInsets(final Component c) {417if (fBorderInsets == null) fBorderInsets = new Insets(0, 0, 0, 0);418419// Paranoia check420if (!(c instanceof JInternalFrame)) return fBorderInsets;421422final JInternalFrame frame = (JInternalFrame)c;423424// Set the contentRect to an arbitrary value (in case the current real one is too small)425setInBounds(0, 0, kContentTester, kContentTester);426427// Set parameters428setMetrics(frame, c);429430fBorderInsets.left = 0;431fBorderInsets.top = metrics.titleBarHeight;432fBorderInsets.right = 0;433fBorderInsets.bottom = 0;434435return fBorderInsets;436}437438public void repaintButtonArea(final JInternalFrame frame) {439final Insets i = frame.getInsets();440final int x = i.left + metrics.leftSidePadding;441final int y = i.top - metrics.titleBarHeight + 1;442frame.repaint(x, y, fThisButtonSpan, metrics.titleBarHeight - 2);443}444445// Draw all the widgets this frame supports446void drawAllWidgets(final Graphics g, final JInternalFrame frame) {447int x = metrics.leftSidePadding;448int y = (metrics.titleBarHeight - metrics.buttonHeight) / 2 - metrics.titleBarHeight;449450final Insets insets = frame.getInsets();451x += insets.left;452y += insets.top + metrics.downShift;453454final AquaInternalFrameUI ui = (AquaInternalFrameUI)frame.getUI();455final int buttonPressedIndex = ui.getWhichButtonPressed();456final boolean overButton = ui.getMouseOverPressedButton();457final boolean rollover = ui.getRollover();458459final boolean frameSelected = frame.isSelected() || fIsUtility;460final boolean generalActive = rollover || frameSelected;461462final boolean dirty = isDirty(frame);463464paintButton(g, frame, x, y, kCloseButton, buttonPressedIndex, overButton, frame.isClosable(), generalActive, rollover, dirty);465466x += metrics.buttonPadding + metrics.buttonWidth;467paintButton(g, frame, x, y, kIconButton, buttonPressedIndex, overButton, frame.isIconifiable(), generalActive, rollover, false);468469x += metrics.buttonPadding + metrics.buttonWidth;470paintButton(g, frame, x, y, kGrowButton, buttonPressedIndex, overButton, frame.isMaximizable(), generalActive, rollover, false);471}472473public 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) {474widgetPainter.state.set(getWidget(frame, buttonType));475widgetPainter.state.set(getState(buttonPressedIndex == buttonType && overButton, anyRollover, active, enabled));476widgetPainter.state.set(dirty ? BooleanValue.YES : BooleanValue.NO);477widgetPainter.paint(g, frame, x, y, metrics.buttonWidth, metrics.buttonHeight);478}479480static Widget getWidget(final JInternalFrame frame, final int buttonType) {481switch (buttonType) {482case kIconButton: return Widget.TITLE_BAR_COLLAPSE_BOX;483case kGrowButton: return Widget.TITLE_BAR_ZOOM_BOX;484}485486return Widget.TITLE_BAR_CLOSE_BOX;487}488489static State getState(final boolean pressed, final boolean rollover, final boolean active, final boolean enabled) {490if (!enabled) return State.DISABLED;491if (!active) return State.INACTIVE;492if (pressed) return State.PRESSED;493if (rollover) return State.ROLLOVER;494return State.ACTIVE;495}496497protected void setMetrics(final JInternalFrame frame, final Component window) {498final String title = frame.getTitle();499final FontMetrics fm = frame.getFontMetrics(UIManager.getFont("InternalFrame.titleFont"));500int titleWidth = 0;501int titleHeight = fm.getAscent();502if (title != null) {503titleWidth = SwingUtilities.computeStringWidth(fm, title);504}505// Icon space506final Icon icon = frame.getFrameIcon();507if (icon != null) {508titleWidth += icon.getIconWidth();509titleHeight = Math.max(titleHeight, icon.getIconHeight());510}511}512513protected int getTitleHeight() {514return metrics.titleBarHeight;515}516}517518519