Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/macosx/classes/com/apple/laf/AquaProgressBarUI.java
38831 views
/*1* Copyright (c) 2011, 2012, 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.awt.event.*;29import java.awt.geom.AffineTransform;30import java.beans.*;3132import javax.swing.*;33import javax.swing.event.*;34import javax.swing.plaf.*;3536import sun.swing.SwingUtilities2;3738import apple.laf.JRSUIStateFactory;39import apple.laf.JRSUIConstants.*;40import apple.laf.JRSUIState.ValueState;4142import com.apple.laf.AquaUtilControlSize.*;43import com.apple.laf.AquaUtils.RecyclableSingleton;4445public class AquaProgressBarUI extends ProgressBarUI implements ChangeListener, PropertyChangeListener, AncestorListener, Sizeable {46private static final boolean ADJUSTTIMER = true;4748protected static final RecyclableSingleton<SizeDescriptor> sizeDescriptor = new RecyclableSingleton<SizeDescriptor>() {49@Override50protected SizeDescriptor getInstance() {51return new SizeDescriptor(new SizeVariant(146, 20)) {52public SizeVariant deriveSmall(final SizeVariant v) { v.alterMinSize(0, -6); return super.deriveSmall(v); }53};54}55};56static SizeDescriptor getSizeDescriptor() {57return sizeDescriptor.get();58}5960protected Size sizeVariant = Size.REGULAR;6162protected Color selectionForeground;6364private Animator animator;65protected boolean isAnimating;66protected boolean isCircular;6768protected final AquaPainter<ValueState> painter = AquaPainter.create(JRSUIStateFactory.getProgressBar());6970protected JProgressBar progressBar;7172public static ComponentUI createUI(final JComponent x) {73return new AquaProgressBarUI();74}7576protected AquaProgressBarUI() { }7778public void installUI(final JComponent c) {79progressBar = (JProgressBar)c;80installDefaults();81installListeners();82}8384public void uninstallUI(final JComponent c) {85uninstallDefaults();86uninstallListeners();87stopAnimationTimer();88progressBar = null;89}9091protected void installDefaults() {92progressBar.setOpaque(false);93LookAndFeel.installBorder(progressBar, "ProgressBar.border");94LookAndFeel.installColorsAndFont(progressBar, "ProgressBar.background", "ProgressBar.foreground", "ProgressBar.font");95selectionForeground = UIManager.getColor("ProgressBar.selectionForeground");96}9798protected void uninstallDefaults() {99LookAndFeel.uninstallBorder(progressBar);100}101102protected void installListeners() {103progressBar.addChangeListener(this); // Listen for changes in the progress bar's data104progressBar.addPropertyChangeListener(this); // Listen for changes between determinate and indeterminate state105progressBar.addAncestorListener(this);106AquaUtilControlSize.addSizePropertyListener(progressBar);107}108109protected void uninstallListeners() {110AquaUtilControlSize.removeSizePropertyListener(progressBar);111progressBar.removeAncestorListener(this);112progressBar.removePropertyChangeListener(this);113progressBar.removeChangeListener(this);114}115116public void stateChanged(final ChangeEvent e) {117progressBar.repaint();118}119120public void propertyChange(final PropertyChangeEvent e) {121final String prop = e.getPropertyName();122if ("indeterminate".equals(prop)) {123if (!progressBar.isIndeterminate()) return;124stopAnimationTimer();125// start the animation thread126if (progressBar.isDisplayable()) {127startAnimationTimer();128}129}130131if ("JProgressBar.style".equals(prop)) {132isCircular = "circular".equalsIgnoreCase(e.getNewValue() + "");133progressBar.repaint();134}135}136137// listen for Ancestor events to stop our timer when we are no longer visible138// <rdar://problem/5405035> JProgressBar: UI in Aqua look and feel causes memory leaks139public void ancestorRemoved(final AncestorEvent e) {140stopAnimationTimer();141}142143public void ancestorAdded(final AncestorEvent e) {144if (!progressBar.isIndeterminate()) return;145if (progressBar.isDisplayable()) {146startAnimationTimer();147}148}149150public void ancestorMoved(final AncestorEvent e) { }151152public void paint(final Graphics g, final JComponent c) {153revalidateAnimationTimers(); // revalidate to turn on/off timers when values change154155painter.state.set(getState(c));156painter.state.set(isHorizontal() ? Orientation.HORIZONTAL : Orientation.VERTICAL);157painter.state.set(isAnimating ? Animating.YES : Animating.NO);158159if (progressBar.isIndeterminate()) {160if (isCircular) {161painter.state.set(Widget.PROGRESS_SPINNER);162painter.paint(g, c, 2, 2, 16, 16);163return;164}165166painter.state.set(Widget.PROGRESS_INDETERMINATE_BAR);167paint(g);168return;169}170171painter.state.set(Widget.PROGRESS_BAR);172painter.state.setValue(checkValue(progressBar.getPercentComplete()));173paint(g);174}175176static double checkValue(final double value) {177return Double.isNaN(value) ? 0 : value;178}179180protected void paint(final Graphics g) {181// this is questionable. We may want the insets to mean something different.182final Insets i = progressBar.getInsets();183final int width = progressBar.getWidth() - (i.right + i.left);184final int height = progressBar.getHeight() - (i.bottom + i.top);185186painter.paint(g, progressBar, i.left, i.top, width, height);187188if (progressBar.isStringPainted() && !progressBar.isIndeterminate()) {189paintString(g, i.left, i.top, width, height);190}191}192193protected State getState(final JComponent c) {194if (!c.isEnabled()) return State.INACTIVE;195if (!AquaFocusHandler.isActive(c)) return State.INACTIVE;196return State.ACTIVE;197}198199protected void paintString(final Graphics g, final int x, final int y, final int width, final int height) {200if (!(g instanceof Graphics2D)) return;201202final Graphics2D g2 = (Graphics2D)g;203final String progressString = progressBar.getString();204g2.setFont(progressBar.getFont());205final Point renderLocation = getStringPlacement(g2, progressString, x, y, width, height);206final Rectangle oldClip = g2.getClipBounds();207208if (isHorizontal()) {209g2.setColor(selectionForeground);210SwingUtilities2.drawString(progressBar, g2, progressString, renderLocation.x, renderLocation.y);211} else { // VERTICAL212// We rotate it -90 degrees, then translate it down since we are going to be bottom up.213final AffineTransform savedAT = g2.getTransform();214g2.transform(AffineTransform.getRotateInstance(0.0f - (Math.PI / 2.0f), 0, 0));215g2.translate(-progressBar.getHeight(), 0);216217// 0,0 is now the bottom left of the viewable area, so we just draw our image at218// the render location since that calculation knows about rotation.219g2.setColor(selectionForeground);220SwingUtilities2.drawString(progressBar, g2, progressString, renderLocation.x, renderLocation.y);221222g2.setTransform(savedAT);223}224225g2.setClip(oldClip);226}227228/**229* Designate the place where the progress string will be painted. This implementation places it at the center of the230* progress bar (in both x and y). Override this if you want to right, left, top, or bottom align the progress231* string or if you need to nudge it around for any reason.232*/233protected Point getStringPlacement(final Graphics g, final String progressString, int x, int y, int width, int height) {234final FontMetrics fontSizer = progressBar.getFontMetrics(progressBar.getFont());235final int stringWidth = fontSizer.stringWidth(progressString);236237if (!isHorizontal()) {238// Calculate the location for the rotated text in real component coordinates.239// swapping x & y and width & height240final int oldH = height;241height = width;242width = oldH;243244final int oldX = x;245x = y;246y = oldX;247}248249return new Point(x + Math.round(width / 2 - stringWidth / 2), y + ((height + fontSizer.getAscent() - fontSizer.getLeading() - fontSizer.getDescent()) / 2) - 1);250}251252static Dimension getCircularPreferredSize() {253return new Dimension(20, 20);254}255256public Dimension getPreferredSize(final JComponent c) {257if (isCircular) {258return getCircularPreferredSize();259}260261final FontMetrics metrics = progressBar.getFontMetrics(progressBar.getFont());262263final Dimension size = isHorizontal() ? getPreferredHorizontalSize(metrics) : getPreferredVerticalSize(metrics);264final Insets insets = progressBar.getInsets();265266size.width += insets.left + insets.right;267size.height += insets.top + insets.bottom;268return size;269}270271protected Dimension getPreferredHorizontalSize(final FontMetrics metrics) {272final SizeVariant variant = getSizeDescriptor().get(sizeVariant);273final Dimension size = new Dimension(variant.w, variant.h);274if (!progressBar.isStringPainted()) return size;275276// Ensure that the progress string will fit277final String progString = progressBar.getString();278final int stringWidth = metrics.stringWidth(progString);279if (stringWidth > size.width) {280size.width = stringWidth;281}282283// This uses both Height and Descent to be sure that284// there is more than enough room in the progress bar285// for everything.286// This does have a strange dependency on287// getStringPlacememnt() in a funny way.288final int stringHeight = metrics.getHeight() + metrics.getDescent();289if (stringHeight > size.height) {290size.height = stringHeight;291}292return size;293}294295protected Dimension getPreferredVerticalSize(final FontMetrics metrics) {296final SizeVariant variant = getSizeDescriptor().get(sizeVariant);297final Dimension size = new Dimension(variant.h, variant.w);298if (!progressBar.isStringPainted()) return size;299300// Ensure that the progress string will fit.301final String progString = progressBar.getString();302final int stringHeight = metrics.getHeight() + metrics.getDescent();303if (stringHeight > size.width) {304size.width = stringHeight;305}306307// This is also for completeness.308final int stringWidth = metrics.stringWidth(progString);309if (stringWidth > size.height) {310size.height = stringWidth;311}312return size;313}314315public Dimension getMinimumSize(final JComponent c) {316if (isCircular) {317return getCircularPreferredSize();318}319320final Dimension pref = getPreferredSize(progressBar);321322// The Minimum size for this component is 10.323// The rationale here is that there should be at least one pixel per 10 percent.324if (isHorizontal()) {325pref.width = 10;326} else {327pref.height = 10;328}329330return pref;331}332333public Dimension getMaximumSize(final JComponent c) {334if (isCircular) {335return getCircularPreferredSize();336}337338final Dimension pref = getPreferredSize(progressBar);339340if (isHorizontal()) {341pref.width = Short.MAX_VALUE;342} else {343pref.height = Short.MAX_VALUE;344}345346return pref;347}348349public void applySizeFor(final JComponent c, final Size size) {350painter.state.set(sizeVariant = size == Size.MINI ? Size.SMALL : sizeVariant); // CUI doesn't support mini progress bars right now351}352353protected void startAnimationTimer() {354if (animator == null) animator = new Animator();355animator.start();356isAnimating = true;357}358359protected void stopAnimationTimer() {360if (animator != null) animator.stop();361isAnimating = false;362}363364private final Rectangle fUpdateArea = new Rectangle(0, 0, 0, 0);365private final Dimension fLastSize = new Dimension(0, 0);366protected Rectangle getRepaintRect() {367int height = progressBar.getHeight();368int width = progressBar.getWidth();369370if (isCircular) {371return new Rectangle(20, 20);372}373374if (fLastSize.height == height && fLastSize.width == width) {375return fUpdateArea;376}377378int x = 0;379int y = 0;380fLastSize.height = height;381fLastSize.width = width;382383final int maxHeight = getMaxProgressBarHeight();384385if (isHorizontal()) {386final int excessHeight = height - maxHeight;387y += excessHeight / 2;388height = maxHeight;389} else {390final int excessHeight = width - maxHeight;391x += excessHeight / 2;392width = maxHeight;393}394395fUpdateArea.setBounds(x, y, width, height);396397return fUpdateArea;398}399400protected int getMaxProgressBarHeight() {401return getSizeDescriptor().get(sizeVariant).h;402}403404protected boolean isHorizontal() {405return progressBar.getOrientation() == SwingConstants.HORIZONTAL;406}407408protected void revalidateAnimationTimers() {409if (progressBar.isIndeterminate()) return;410411if (!isAnimating) {412startAnimationTimer(); // only starts if supposed to!413return;414}415416final BoundedRangeModel model = progressBar.getModel();417final double currentValue = model.getValue();418if ((currentValue == model.getMaximum()) || (currentValue == model.getMinimum())) {419stopAnimationTimer();420}421}422423protected void repaint() {424final Rectangle repaintRect = getRepaintRect();425if (repaintRect == null) {426progressBar.repaint();427return;428}429430progressBar.repaint(repaintRect);431}432433protected class Animator implements ActionListener {434private static final int MINIMUM_DELAY = 5;435private Timer timer;436private long previousDelay; // used to tune the repaint interval437private long lastCall; // the last time actionPerformed was called438private int repaintInterval;439440public Animator() {441repaintInterval = UIManager.getInt("ProgressBar.repaintInterval");442443// Make sure repaintInterval is reasonable.444if (repaintInterval <= 0) repaintInterval = 100;445}446447protected void start() {448previousDelay = repaintInterval;449lastCall = 0;450451if (timer == null) {452timer = new Timer(repaintInterval, this);453} else {454timer.setDelay(repaintInterval);455}456457if (ADJUSTTIMER) {458timer.setRepeats(false);459timer.setCoalesce(false);460}461462timer.start();463}464465protected void stop() {466timer.stop();467}468469public void actionPerformed(final ActionEvent e) {470if (!ADJUSTTIMER) {471repaint();472return;473}474475final long time = System.currentTimeMillis();476477if (lastCall > 0) {478// adjust nextDelay479int nextDelay = (int)(previousDelay - time + lastCall + repaintInterval);480if (nextDelay < MINIMUM_DELAY) {481nextDelay = MINIMUM_DELAY;482}483484timer.setInitialDelay(nextDelay);485previousDelay = nextDelay;486}487488timer.start();489lastCall = time;490491repaint();492}493}494}495496497