mirror of
https://github.com/openjdk/jdk.git
synced 2026-02-18 22:35:12 +00:00
505 lines
17 KiB
Java
505 lines
17 KiB
Java
/*
|
|
* Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
package com.apple.laf;
|
|
|
|
import java.awt.*;
|
|
import java.awt.event.*;
|
|
import java.awt.geom.AffineTransform;
|
|
import java.beans.*;
|
|
|
|
import javax.swing.*;
|
|
import javax.swing.event.*;
|
|
import javax.swing.plaf.*;
|
|
|
|
import sun.swing.SwingUtilities2;
|
|
|
|
import apple.laf.JRSUIStateFactory;
|
|
import apple.laf.JRSUIConstants.*;
|
|
import apple.laf.JRSUIState.ValueState;
|
|
|
|
import com.apple.laf.AquaUtilControlSize.*;
|
|
import com.apple.laf.AquaUtils.RecyclableSingleton;
|
|
|
|
public class AquaProgressBarUI extends ProgressBarUI implements ChangeListener, PropertyChangeListener, AncestorListener, Sizeable {
|
|
private static final boolean ADJUSTTIMER = true;
|
|
|
|
private static final RecyclableSingleton<SizeDescriptor> sizeDescriptor = new RecyclableSingleton<SizeDescriptor>() {
|
|
@Override
|
|
protected SizeDescriptor getInstance() {
|
|
return new SizeDescriptor(new SizeVariant(146, 20)) {
|
|
public SizeVariant deriveSmall(final SizeVariant v) { v.alterMinSize(0, -6); return super.deriveSmall(v); }
|
|
};
|
|
}
|
|
};
|
|
static SizeDescriptor getSizeDescriptor() {
|
|
return sizeDescriptor.get();
|
|
}
|
|
|
|
protected Size sizeVariant = Size.REGULAR;
|
|
|
|
protected Color selectionForeground;
|
|
|
|
private Animator animator;
|
|
protected boolean isAnimating;
|
|
protected boolean isCircular;
|
|
|
|
protected final AquaPainter<ValueState> painter = AquaPainter.create(JRSUIStateFactory.getProgressBar());
|
|
|
|
protected JProgressBar progressBar;
|
|
|
|
public static ComponentUI createUI(final JComponent x) {
|
|
return new AquaProgressBarUI();
|
|
}
|
|
|
|
protected AquaProgressBarUI() { }
|
|
|
|
public void installUI(final JComponent c) {
|
|
progressBar = (JProgressBar)c;
|
|
installDefaults();
|
|
installListeners();
|
|
}
|
|
|
|
public void uninstallUI(final JComponent c) {
|
|
uninstallDefaults();
|
|
uninstallListeners();
|
|
stopAnimationTimer();
|
|
progressBar = null;
|
|
}
|
|
|
|
protected void installDefaults() {
|
|
progressBar.setOpaque(false);
|
|
LookAndFeel.installBorder(progressBar, "ProgressBar.border");
|
|
LookAndFeel.installColorsAndFont(progressBar, "ProgressBar.background", "ProgressBar.foreground", "ProgressBar.font");
|
|
selectionForeground = UIManager.getColor("ProgressBar.selectionForeground");
|
|
}
|
|
|
|
protected void uninstallDefaults() {
|
|
LookAndFeel.uninstallBorder(progressBar);
|
|
}
|
|
|
|
protected void installListeners() {
|
|
progressBar.addChangeListener(this); // Listen for changes in the progress bar's data
|
|
progressBar.addPropertyChangeListener(this); // Listen for changes between determinate and indeterminate state
|
|
progressBar.addAncestorListener(this);
|
|
AquaUtilControlSize.addSizePropertyListener(progressBar);
|
|
}
|
|
|
|
protected void uninstallListeners() {
|
|
AquaUtilControlSize.removeSizePropertyListener(progressBar);
|
|
progressBar.removeAncestorListener(this);
|
|
progressBar.removePropertyChangeListener(this);
|
|
progressBar.removeChangeListener(this);
|
|
}
|
|
|
|
public void stateChanged(final ChangeEvent e) {
|
|
progressBar.repaint();
|
|
}
|
|
|
|
public void propertyChange(final PropertyChangeEvent e) {
|
|
final String prop = e.getPropertyName();
|
|
if ("indeterminate".equals(prop)) {
|
|
if (!progressBar.isIndeterminate()) return;
|
|
stopAnimationTimer();
|
|
// start the animation thread
|
|
if (progressBar.isDisplayable()) {
|
|
startAnimationTimer();
|
|
}
|
|
}
|
|
|
|
if ("JProgressBar.style".equals(prop)) {
|
|
isCircular = "circular".equalsIgnoreCase(e.getNewValue() + "");
|
|
progressBar.repaint();
|
|
}
|
|
}
|
|
|
|
// listen for Ancestor events to stop our timer when we are no longer visible
|
|
// <rdar://problem/5405035> JProgressBar: UI in Aqua look and feel causes memory leaks
|
|
public void ancestorRemoved(final AncestorEvent e) {
|
|
stopAnimationTimer();
|
|
}
|
|
|
|
public void ancestorAdded(final AncestorEvent e) {
|
|
if (!progressBar.isIndeterminate()) return;
|
|
if (progressBar.isDisplayable()) {
|
|
startAnimationTimer();
|
|
}
|
|
}
|
|
|
|
public void ancestorMoved(final AncestorEvent e) { }
|
|
|
|
public void paint(final Graphics g, final JComponent c) {
|
|
revalidateAnimationTimers(); // revalidate to turn on/off timers when values change
|
|
|
|
painter.state.set(getState(c));
|
|
painter.state.set(isHorizontal() ? Orientation.HORIZONTAL : Orientation.VERTICAL);
|
|
painter.state.set(isAnimating ? Animating.YES : Animating.NO);
|
|
|
|
if (progressBar.isIndeterminate()) {
|
|
if (isCircular) {
|
|
painter.state.set(Widget.PROGRESS_SPINNER);
|
|
painter.paint(g, c, 2, 2, 16, 16);
|
|
return;
|
|
}
|
|
|
|
painter.state.set(Widget.PROGRESS_INDETERMINATE_BAR);
|
|
paint(g);
|
|
return;
|
|
}
|
|
|
|
painter.state.set(Widget.PROGRESS_BAR);
|
|
painter.state.setValue(checkValue(progressBar.getPercentComplete()));
|
|
paint(g);
|
|
}
|
|
|
|
static double checkValue(final double value) {
|
|
return Double.isNaN(value) ? 0 : value;
|
|
}
|
|
|
|
protected void paint(final Graphics g) {
|
|
// this is questionable. We may want the insets to mean something different.
|
|
final Insets i = progressBar.getInsets();
|
|
final int width = progressBar.getWidth() - (i.right + i.left);
|
|
final int height = progressBar.getHeight() - (i.bottom + i.top);
|
|
|
|
Graphics2D g2 = (Graphics2D) g;
|
|
final AffineTransform savedAT = g2.getTransform();
|
|
if (!progressBar.getComponentOrientation().isLeftToRight()) {
|
|
//Scale operation: Flips component about pivot
|
|
//Translate operation: Moves component back into original position
|
|
g2.scale(-1, 1);
|
|
g2.translate(-progressBar.getWidth(), 0);
|
|
}
|
|
painter.paint(g, progressBar, i.left, i.top, width, height);
|
|
|
|
g2.setTransform(savedAT);
|
|
if (progressBar.isStringPainted() && !progressBar.isIndeterminate()) {
|
|
paintString(g, i.left, i.top, width, height);
|
|
}
|
|
}
|
|
|
|
protected State getState(final JComponent c) {
|
|
if (!c.isEnabled()) return State.INACTIVE;
|
|
if (!AquaFocusHandler.isActive(c)) return State.INACTIVE;
|
|
return State.ACTIVE;
|
|
}
|
|
|
|
protected void paintString(final Graphics g, final int x, final int y, final int width, final int height) {
|
|
if (!(g instanceof Graphics2D)) return;
|
|
|
|
final Graphics2D g2 = (Graphics2D)g;
|
|
final String progressString = progressBar.getString();
|
|
g2.setFont(progressBar.getFont());
|
|
final Point renderLocation = getStringPlacement(g2, progressString, x, y, width, height);
|
|
final Rectangle oldClip = g2.getClipBounds();
|
|
|
|
if (isHorizontal()) {
|
|
g2.setColor(selectionForeground);
|
|
SwingUtilities2.drawString(progressBar, g2, progressString, renderLocation.x, renderLocation.y);
|
|
} else { // VERTICAL
|
|
// We rotate it -90 degrees, then translate it down since we are going to be bottom up.
|
|
final AffineTransform savedAT = g2.getTransform();
|
|
g2.transform(AffineTransform.getRotateInstance(0.0f - (Math.PI / 2.0f), 0, 0));
|
|
g2.translate(-progressBar.getHeight(), 0);
|
|
|
|
// 0,0 is now the bottom left of the viewable area, so we just draw our image at
|
|
// the render location since that calculation knows about rotation.
|
|
g2.setColor(selectionForeground);
|
|
SwingUtilities2.drawString(progressBar, g2, progressString, renderLocation.x, renderLocation.y);
|
|
|
|
g2.setTransform(savedAT);
|
|
}
|
|
|
|
g2.setClip(oldClip);
|
|
}
|
|
|
|
/**
|
|
* Designate the place where the progress string will be painted. This implementation places it at the center of the
|
|
* progress bar (in both x and y). Override this if you want to right, left, top, or bottom align the progress
|
|
* string or if you need to nudge it around for any reason.
|
|
*/
|
|
protected Point getStringPlacement(final Graphics g, final String progressString, int x, int y, int width, int height) {
|
|
final FontMetrics fontSizer = progressBar.getFontMetrics(progressBar.getFont());
|
|
final int stringWidth = fontSizer.stringWidth(progressString);
|
|
|
|
if (!isHorizontal()) {
|
|
// Calculate the location for the rotated text in real component coordinates.
|
|
// swapping x & y and width & height
|
|
final int oldH = height;
|
|
height = width;
|
|
width = oldH;
|
|
|
|
final int oldX = x;
|
|
x = y;
|
|
y = oldX;
|
|
}
|
|
|
|
return new Point(x + Math.round(width / 2 - stringWidth / 2), y + ((height + fontSizer.getAscent() - fontSizer.getLeading() - fontSizer.getDescent()) / 2) - 1);
|
|
}
|
|
|
|
static Dimension getCircularPreferredSize() {
|
|
return new Dimension(20, 20);
|
|
}
|
|
|
|
public Dimension getPreferredSize(final JComponent c) {
|
|
if (isCircular) {
|
|
return getCircularPreferredSize();
|
|
}
|
|
|
|
final FontMetrics metrics = progressBar.getFontMetrics(progressBar.getFont());
|
|
|
|
final Dimension size = isHorizontal() ? getPreferredHorizontalSize(metrics) : getPreferredVerticalSize(metrics);
|
|
final Insets insets = progressBar.getInsets();
|
|
|
|
size.width += insets.left + insets.right;
|
|
size.height += insets.top + insets.bottom;
|
|
return size;
|
|
}
|
|
|
|
protected Dimension getPreferredHorizontalSize(final FontMetrics metrics) {
|
|
final SizeVariant variant = getSizeDescriptor().get(sizeVariant);
|
|
final Dimension size = new Dimension(variant.w, variant.h);
|
|
if (!progressBar.isStringPainted()) return size;
|
|
|
|
// Ensure that the progress string will fit
|
|
final String progString = progressBar.getString();
|
|
final int stringWidth = metrics.stringWidth(progString);
|
|
if (stringWidth > size.width) {
|
|
size.width = stringWidth;
|
|
}
|
|
|
|
// This uses both Height and Descent to be sure that
|
|
// there is more than enough room in the progress bar
|
|
// for everything.
|
|
// This does have a strange dependency on
|
|
// getStringPlacememnt() in a funny way.
|
|
final int stringHeight = metrics.getHeight() + metrics.getDescent();
|
|
if (stringHeight > size.height) {
|
|
size.height = stringHeight;
|
|
}
|
|
return size;
|
|
}
|
|
|
|
protected Dimension getPreferredVerticalSize(final FontMetrics metrics) {
|
|
final SizeVariant variant = getSizeDescriptor().get(sizeVariant);
|
|
final Dimension size = new Dimension(variant.h, variant.w);
|
|
if (!progressBar.isStringPainted()) return size;
|
|
|
|
// Ensure that the progress string will fit.
|
|
final String progString = progressBar.getString();
|
|
final int stringHeight = metrics.getHeight() + metrics.getDescent();
|
|
if (stringHeight > size.width) {
|
|
size.width = stringHeight;
|
|
}
|
|
|
|
// This is also for completeness.
|
|
final int stringWidth = metrics.stringWidth(progString);
|
|
if (stringWidth > size.height) {
|
|
size.height = stringWidth;
|
|
}
|
|
return size;
|
|
}
|
|
|
|
public Dimension getMinimumSize(final JComponent c) {
|
|
if (isCircular) {
|
|
return getCircularPreferredSize();
|
|
}
|
|
|
|
final Dimension pref = getPreferredSize(progressBar);
|
|
|
|
// The Minimum size for this component is 10.
|
|
// The rationale here is that there should be at least one pixel per 10 percent.
|
|
if (isHorizontal()) {
|
|
pref.width = 10;
|
|
} else {
|
|
pref.height = 10;
|
|
}
|
|
|
|
return pref;
|
|
}
|
|
|
|
public Dimension getMaximumSize(final JComponent c) {
|
|
if (isCircular) {
|
|
return getCircularPreferredSize();
|
|
}
|
|
|
|
final Dimension pref = getPreferredSize(progressBar);
|
|
|
|
if (isHorizontal()) {
|
|
pref.width = Short.MAX_VALUE;
|
|
} else {
|
|
pref.height = Short.MAX_VALUE;
|
|
}
|
|
|
|
return pref;
|
|
}
|
|
|
|
public void applySizeFor(final JComponent c, final Size size) {
|
|
painter.state.set(sizeVariant = size == Size.MINI ? Size.SMALL : sizeVariant); // CUI doesn't support mini progress bars right now
|
|
}
|
|
|
|
protected void startAnimationTimer() {
|
|
if (animator == null) animator = new Animator();
|
|
animator.start();
|
|
isAnimating = true;
|
|
}
|
|
|
|
protected void stopAnimationTimer() {
|
|
if (animator != null) animator.stop();
|
|
isAnimating = false;
|
|
}
|
|
|
|
private final Rectangle fUpdateArea = new Rectangle(0, 0, 0, 0);
|
|
private final Dimension fLastSize = new Dimension(0, 0);
|
|
protected Rectangle getRepaintRect() {
|
|
int height = progressBar.getHeight();
|
|
int width = progressBar.getWidth();
|
|
|
|
if (isCircular) {
|
|
return new Rectangle(20, 20);
|
|
}
|
|
|
|
if (fLastSize.height == height && fLastSize.width == width) {
|
|
return fUpdateArea;
|
|
}
|
|
|
|
int x = 0;
|
|
int y = 0;
|
|
fLastSize.height = height;
|
|
fLastSize.width = width;
|
|
|
|
final int maxHeight = getMaxProgressBarHeight();
|
|
|
|
if (isHorizontal()) {
|
|
final int excessHeight = height - maxHeight;
|
|
y += excessHeight / 2;
|
|
height = maxHeight;
|
|
} else {
|
|
final int excessHeight = width - maxHeight;
|
|
x += excessHeight / 2;
|
|
width = maxHeight;
|
|
}
|
|
|
|
fUpdateArea.setBounds(x, y, width, height);
|
|
|
|
return fUpdateArea;
|
|
}
|
|
|
|
protected int getMaxProgressBarHeight() {
|
|
return getSizeDescriptor().get(sizeVariant).h;
|
|
}
|
|
|
|
protected boolean isHorizontal() {
|
|
return progressBar.getOrientation() == SwingConstants.HORIZONTAL;
|
|
}
|
|
|
|
protected void revalidateAnimationTimers() {
|
|
if (progressBar.isIndeterminate()) return;
|
|
|
|
if (!isAnimating) {
|
|
startAnimationTimer(); // only starts if supposed to!
|
|
return;
|
|
}
|
|
|
|
final BoundedRangeModel model = progressBar.getModel();
|
|
final double currentValue = model.getValue();
|
|
if ((currentValue == model.getMaximum()) || (currentValue == model.getMinimum())) {
|
|
stopAnimationTimer();
|
|
}
|
|
}
|
|
|
|
protected void repaint() {
|
|
final Rectangle repaintRect = getRepaintRect();
|
|
if (repaintRect == null) {
|
|
progressBar.repaint();
|
|
return;
|
|
}
|
|
|
|
progressBar.repaint(repaintRect);
|
|
}
|
|
|
|
protected class Animator implements ActionListener {
|
|
private static final int MINIMUM_DELAY = 5;
|
|
private Timer timer;
|
|
private long previousDelay; // used to tune the repaint interval
|
|
private long lastCall; // the last time actionPerformed was called
|
|
private int repaintInterval;
|
|
|
|
public Animator() {
|
|
repaintInterval = UIManager.getInt("ProgressBar.repaintInterval");
|
|
|
|
// Make sure repaintInterval is reasonable.
|
|
if (repaintInterval <= 0) repaintInterval = 100;
|
|
}
|
|
|
|
protected void start() {
|
|
previousDelay = repaintInterval;
|
|
lastCall = 0;
|
|
|
|
if (timer == null) {
|
|
timer = new Timer(repaintInterval, this);
|
|
} else {
|
|
timer.setDelay(repaintInterval);
|
|
}
|
|
|
|
if (ADJUSTTIMER) {
|
|
timer.setRepeats(false);
|
|
timer.setCoalesce(false);
|
|
}
|
|
|
|
timer.start();
|
|
}
|
|
|
|
protected void stop() {
|
|
timer.stop();
|
|
}
|
|
|
|
public void actionPerformed(final ActionEvent e) {
|
|
if (!ADJUSTTIMER) {
|
|
repaint();
|
|
return;
|
|
}
|
|
|
|
final long time = System.currentTimeMillis();
|
|
|
|
if (lastCall > 0) {
|
|
// adjust nextDelay
|
|
int nextDelay = (int)(previousDelay - time + lastCall + repaintInterval);
|
|
if (nextDelay < MINIMUM_DELAY) {
|
|
nextDelay = MINIMUM_DELAY;
|
|
}
|
|
|
|
timer.setInitialDelay(nextDelay);
|
|
previousDelay = nextDelay;
|
|
}
|
|
|
|
timer.start();
|
|
lastCall = time;
|
|
|
|
repaint();
|
|
}
|
|
}
|
|
}
|