Path: blob/master/src/java.desktop/macosx/classes/com/apple/laf/AquaProgressBarUI.java
41154 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;4748private 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);185186Graphics2D g2 = (Graphics2D) g;187final AffineTransform savedAT = g2.getTransform();188if (!progressBar.getComponentOrientation().isLeftToRight()) {189//Scale operation: Flips component about pivot190//Translate operation: Moves component back into original position191g2.scale(-1, 1);192g2.translate(-progressBar.getWidth(), 0);193}194painter.paint(g, progressBar, i.left, i.top, width, height);195196g2.setTransform(savedAT);197if (progressBar.isStringPainted() && !progressBar.isIndeterminate()) {198paintString(g, i.left, i.top, width, height);199}200}201202protected State getState(final JComponent c) {203if (!c.isEnabled()) return State.INACTIVE;204if (!AquaFocusHandler.isActive(c)) return State.INACTIVE;205return State.ACTIVE;206}207208protected void paintString(final Graphics g, final int x, final int y, final int width, final int height) {209if (!(g instanceof Graphics2D)) return;210211final Graphics2D g2 = (Graphics2D)g;212final String progressString = progressBar.getString();213g2.setFont(progressBar.getFont());214final Point renderLocation = getStringPlacement(g2, progressString, x, y, width, height);215final Rectangle oldClip = g2.getClipBounds();216217if (isHorizontal()) {218g2.setColor(selectionForeground);219SwingUtilities2.drawString(progressBar, g2, progressString, renderLocation.x, renderLocation.y);220} else { // VERTICAL221// We rotate it -90 degrees, then translate it down since we are going to be bottom up.222final AffineTransform savedAT = g2.getTransform();223g2.transform(AffineTransform.getRotateInstance(0.0f - (Math.PI / 2.0f), 0, 0));224g2.translate(-progressBar.getHeight(), 0);225226// 0,0 is now the bottom left of the viewable area, so we just draw our image at227// the render location since that calculation knows about rotation.228g2.setColor(selectionForeground);229SwingUtilities2.drawString(progressBar, g2, progressString, renderLocation.x, renderLocation.y);230231g2.setTransform(savedAT);232}233234g2.setClip(oldClip);235}236237/**238* Designate the place where the progress string will be painted. This implementation places it at the center of the239* progress bar (in both x and y). Override this if you want to right, left, top, or bottom align the progress240* string or if you need to nudge it around for any reason.241*/242protected Point getStringPlacement(final Graphics g, final String progressString, int x, int y, int width, int height) {243final FontMetrics fontSizer = progressBar.getFontMetrics(progressBar.getFont());244final int stringWidth = fontSizer.stringWidth(progressString);245246if (!isHorizontal()) {247// Calculate the location for the rotated text in real component coordinates.248// swapping x & y and width & height249final int oldH = height;250height = width;251width = oldH;252253final int oldX = x;254x = y;255y = oldX;256}257258return new Point(x + Math.round(width / 2 - stringWidth / 2), y + ((height + fontSizer.getAscent() - fontSizer.getLeading() - fontSizer.getDescent()) / 2) - 1);259}260261static Dimension getCircularPreferredSize() {262return new Dimension(20, 20);263}264265public Dimension getPreferredSize(final JComponent c) {266if (isCircular) {267return getCircularPreferredSize();268}269270final FontMetrics metrics = progressBar.getFontMetrics(progressBar.getFont());271272final Dimension size = isHorizontal() ? getPreferredHorizontalSize(metrics) : getPreferredVerticalSize(metrics);273final Insets insets = progressBar.getInsets();274275size.width += insets.left + insets.right;276size.height += insets.top + insets.bottom;277return size;278}279280protected Dimension getPreferredHorizontalSize(final FontMetrics metrics) {281final SizeVariant variant = getSizeDescriptor().get(sizeVariant);282final Dimension size = new Dimension(variant.w, variant.h);283if (!progressBar.isStringPainted()) return size;284285// Ensure that the progress string will fit286final String progString = progressBar.getString();287final int stringWidth = metrics.stringWidth(progString);288if (stringWidth > size.width) {289size.width = stringWidth;290}291292// This uses both Height and Descent to be sure that293// there is more than enough room in the progress bar294// for everything.295// This does have a strange dependency on296// getStringPlacememnt() in a funny way.297final int stringHeight = metrics.getHeight() + metrics.getDescent();298if (stringHeight > size.height) {299size.height = stringHeight;300}301return size;302}303304protected Dimension getPreferredVerticalSize(final FontMetrics metrics) {305final SizeVariant variant = getSizeDescriptor().get(sizeVariant);306final Dimension size = new Dimension(variant.h, variant.w);307if (!progressBar.isStringPainted()) return size;308309// Ensure that the progress string will fit.310final String progString = progressBar.getString();311final int stringHeight = metrics.getHeight() + metrics.getDescent();312if (stringHeight > size.width) {313size.width = stringHeight;314}315316// This is also for completeness.317final int stringWidth = metrics.stringWidth(progString);318if (stringWidth > size.height) {319size.height = stringWidth;320}321return size;322}323324public Dimension getMinimumSize(final JComponent c) {325if (isCircular) {326return getCircularPreferredSize();327}328329final Dimension pref = getPreferredSize(progressBar);330331// The Minimum size for this component is 10.332// The rationale here is that there should be at least one pixel per 10 percent.333if (isHorizontal()) {334pref.width = 10;335} else {336pref.height = 10;337}338339return pref;340}341342public Dimension getMaximumSize(final JComponent c) {343if (isCircular) {344return getCircularPreferredSize();345}346347final Dimension pref = getPreferredSize(progressBar);348349if (isHorizontal()) {350pref.width = Short.MAX_VALUE;351} else {352pref.height = Short.MAX_VALUE;353}354355return pref;356}357358public void applySizeFor(final JComponent c, final Size size) {359painter.state.set(sizeVariant = size == Size.MINI ? Size.SMALL : sizeVariant); // CUI doesn't support mini progress bars right now360}361362protected void startAnimationTimer() {363if (animator == null) animator = new Animator();364animator.start();365isAnimating = true;366}367368protected void stopAnimationTimer() {369if (animator != null) animator.stop();370isAnimating = false;371}372373private final Rectangle fUpdateArea = new Rectangle(0, 0, 0, 0);374private final Dimension fLastSize = new Dimension(0, 0);375protected Rectangle getRepaintRect() {376int height = progressBar.getHeight();377int width = progressBar.getWidth();378379if (isCircular) {380return new Rectangle(20, 20);381}382383if (fLastSize.height == height && fLastSize.width == width) {384return fUpdateArea;385}386387int x = 0;388int y = 0;389fLastSize.height = height;390fLastSize.width = width;391392final int maxHeight = getMaxProgressBarHeight();393394if (isHorizontal()) {395final int excessHeight = height - maxHeight;396y += excessHeight / 2;397height = maxHeight;398} else {399final int excessHeight = width - maxHeight;400x += excessHeight / 2;401width = maxHeight;402}403404fUpdateArea.setBounds(x, y, width, height);405406return fUpdateArea;407}408409protected int getMaxProgressBarHeight() {410return getSizeDescriptor().get(sizeVariant).h;411}412413protected boolean isHorizontal() {414return progressBar.getOrientation() == SwingConstants.HORIZONTAL;415}416417protected void revalidateAnimationTimers() {418if (progressBar.isIndeterminate()) return;419420if (!isAnimating) {421startAnimationTimer(); // only starts if supposed to!422return;423}424425final BoundedRangeModel model = progressBar.getModel();426final double currentValue = model.getValue();427if ((currentValue == model.getMaximum()) || (currentValue == model.getMinimum())) {428stopAnimationTimer();429}430}431432protected void repaint() {433final Rectangle repaintRect = getRepaintRect();434if (repaintRect == null) {435progressBar.repaint();436return;437}438439progressBar.repaint(repaintRect);440}441442protected class Animator implements ActionListener {443private static final int MINIMUM_DELAY = 5;444private Timer timer;445private long previousDelay; // used to tune the repaint interval446private long lastCall; // the last time actionPerformed was called447private int repaintInterval;448449public Animator() {450repaintInterval = UIManager.getInt("ProgressBar.repaintInterval");451452// Make sure repaintInterval is reasonable.453if (repaintInterval <= 0) repaintInterval = 100;454}455456protected void start() {457previousDelay = repaintInterval;458lastCall = 0;459460if (timer == null) {461timer = new Timer(repaintInterval, this);462} else {463timer.setDelay(repaintInterval);464}465466if (ADJUSTTIMER) {467timer.setRepeats(false);468timer.setCoalesce(false);469}470471timer.start();472}473474protected void stop() {475timer.stop();476}477478public void actionPerformed(final ActionEvent e) {479if (!ADJUSTTIMER) {480repaint();481return;482}483484final long time = System.currentTimeMillis();485486if (lastCall > 0) {487// adjust nextDelay488int nextDelay = (int)(previousDelay - time + lastCall + repaintInterval);489if (nextDelay < MINIMUM_DELAY) {490nextDelay = MINIMUM_DELAY;491}492493timer.setInitialDelay(nextDelay);494previousDelay = nextDelay;495}496497timer.start();498lastCall = time;499500repaint();501}502}503}504505506