/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program 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
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.gui.animation;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
/**
* {@link Animation} that displays the progress of a {@link ProgressProvider}. If the progress is 0
* a {@link IndeterminateAnimation} is displayed.
*
* @author Gisa Schaefer
* @since 7.1.0
*/
public class ProgressAnimation implements Animation {
/** the maximal angle */
private static final int FULL_ANGLE = 360;
/** the angle where the arc starts */
private static final int START_ANGLE = 90;
/**
* the maximal text width, width of the maximal square inside a circle with radius
* IndeterminateAnimation.OUTER_RADIUS minus a padding
*/
private static final int MAXIMAL_TEXT_WIDTH = (int) Math.sqrt(Math.pow(2.0 * IndeterminateAnimation.OUTER_RADIUS, 2) / 2)
- 9;
/** the line width of the arc line, an even number */
private static final int LINE_WIDTH = IndeterminateAnimation.RADIUS_SMALL_CIRCLES / 2 * 2;
/** the stroke used for the arc */
private static final BasicStroke STROKE = new BasicStroke(LINE_WIDTH);
/** the width of the arc when it is full */
private static final int CIRCLE_WIDTH = 2 * IndeterminateAnimation.OUTER_RADIUS - LINE_WIDTH;
private static final Color ANIMATION_COLOR = IndeterminateAnimation.SPINNER_COLORS[0];
/** the default font for the text inside the arc */
private static final Font DEFAULT_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 17);
/** the font with the maximal size such that it fits into the arc */
private static Font platformSpecificFont;
/** the text width of a two-digit number inside the arc */
private static double textWidth = MAXIMAL_TEXT_WIDTH;
/** the text width of a number inside the arc */
private static double textHeight = MAXIMAL_TEXT_WIDTH;
static {
initializeFontMeasures();
}
private final IndeterminateAnimation indeterminateAnimation;
private final ProgressProvider progressProvider;
private int lastDrawnProgress = -1;
public ProgressAnimation(ProgressProvider progressProvider) {
indeterminateAnimation = new IndeterminateAnimation();
this.progressProvider = progressProvider;
}
@Override
public boolean isRedrawRequired() {
int currentProgress = progressProvider.getProgress();
if (currentProgress == 0) {
return indeterminateAnimation.isRedrawRequired();
}
if (currentProgress != lastDrawnProgress) {
return true;
}
return false;
}
@Override
public void draw(Graphics2D graphics) {
int currentProgress = progressProvider.getProgress();
if (currentProgress == 0) {
indeterminateAnimation.draw(graphics);
} else {
lastDrawnProgress = currentProgress;
// store properties
final Color color = graphics.getColor();
final Stroke stroke = graphics.getStroke();
final Font font = graphics.getFont();
final Object renderingHint = graphics.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL);
// draw arc
graphics.setColor(ANIMATION_COLOR);
graphics.setStroke(STROKE);
// rendering hint that prevents arc from wobbling
graphics.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
graphics.drawArc(-CIRCLE_WIDTH / 2, -CIRCLE_WIDTH / 2, CIRCLE_WIDTH, CIRCLE_WIDTH, START_ANGLE,
-FULL_ANGLE * currentProgress / 100);
// print progress if not 100
if (currentProgress < 100) {
graphics.setFont(platformSpecificFont);
final String text = "" + currentProgress;
double textX = -textWidth / 2;
if (currentProgress < 10) {
textX = textX / 2;
}
graphics.drawString(text, (int) textX, (int) textHeight / 2);
}
// restore properties
graphics.setColor(color);
graphics.setStroke(stroke);
graphics.setFont(font);
graphics.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, renderingHint);
}
}
@Override
public Rectangle getBounds() {
return indeterminateAnimation.getBounds();
}
/**
* Finds the font that fits into the progress arc. Stores the associated height and width.
*/
private static void initializeFontMeasures() {
platformSpecificFont = DEFAULT_FONT;
Graphics2D testGraphics = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB).createGraphics();
float[] possibleFontSizes = { 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10 };
for (float size : possibleFontSizes) {
Font deriveFont = DEFAULT_FONT.deriveFont(size);
testGraphics.setFont(deriveFont);
String testString = "54";
Rectangle2D stringBounds = deriveFont.getStringBounds(testString, testGraphics.getFontRenderContext());
if (stringBounds.getWidth() <= MAXIMAL_TEXT_WIDTH) {
platformSpecificFont = deriveFont;
textWidth = stringBounds.getWidth();
textHeight = deriveFont.createGlyphVector(testGraphics.getFontRenderContext(), testString).getVisualBounds()
.getHeight();
break;
}
}
}
}