// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2009-2011 Google, All Rights reserved
// Copyright 2011-2012 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0
package com.google.appinventor.client.explorer.commands;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.widgetideas.client.ResizableWidget;
import com.google.gwt.widgetideas.client.ResizableWidgetCollection;
/**
* A widget that displays progress on an arbitrary scale.
*
* <h3>CSS Style Rules</h3>
* <ul class='css'>
* <li>.gwt-ProgressBar-shell { primary style } </li>
* <li>.gwt-ProgressBar-shell .gwt-ProgressBar-bar { the actual progress bar }
* </li>
* <li>.gwt-ProgressBar-shell .gwt-ProgressBar-text { text on the bar } </li>
* <li>.gwt-ProgressBar-shell .gwt-ProgressBar-text-firstHalf { applied to text
* when progress is less than 50 percent } </li>
* <li>.gwt-ProgressBar-shell .gwt-ProgressBar-text-secondHalf { applied to
* text when progress is greater than 50 percent } </li>
* </ul>
*/
public class MiniProgressBar extends Widget implements ResizableWidget {
private static final String DEFAULT_TEXT_CLASS_NAME =
"gwt-ProgressBar-text";
private String textClassName = DEFAULT_TEXT_CLASS_NAME;
private String textFirstHalfClassName = DEFAULT_TEXT_CLASS_NAME + "-firstHalf";
private String textSecondHalfClassName = DEFAULT_TEXT_CLASS_NAME + "-secondHalf";
/**
* A formatter used to format the text displayed in the progress bar widget.
*/
public abstract static class TextFormatter {
/**
* Generate the text to display in the ProgressBar based on the current
* value.
*
* Override this method to change the text displayed within the ProgressBar.
*
* @param bar the progress bar
* @param curProgress the current progress
* @return the text to display in the progress bar
*/
protected abstract String getText(MiniProgressBar bar, double curProgress);
}
/**
* The bar element that displays the progress.
*/
private Element barElement;
/**
* The current progress.
*/
private double curProgress;
/**
* The maximum progress.
*/
private double maxProgress;
/**
* The minimum progress.
*/
private double minProgress;
/**
* A boolean that determines if the text is visible.
*/
private boolean textVisible = true;
/**
* The element that displays text on the page.
*/
private Element textElement;
/**
* The current text formatter.
*/
private TextFormatter textFormatter;
/**
* Create a progress bar with default range of 0 to 100.
*/
public MiniProgressBar() {
this(0.0, 100.0, 0.0);
}
/**
* Create a progress bar with an initial progress and a default range of 0 to
* 100.
*
* @param curProgress the current progress
*/
public MiniProgressBar(double curProgress) {
this(0.0, 100.0, curProgress);
}
/**
* Create a progress bar within the given range.
*
* @param minProgress the minimum progress
* @param maxProgress the maximum progress
*/
public MiniProgressBar(double minProgress, double maxProgress) {
this(minProgress, maxProgress, 0.0);
}
/**
* Create a progress bar within the given range starting at the specified
* progress amount.
*
* @param minProgress the minimum progress
* @param maxProgress the maximum progress
* @param curProgress the current progress
*/
public MiniProgressBar(double minProgress, double maxProgress, double curProgress) {
this(minProgress, maxProgress, curProgress, null);
}
/**
* Create a progress bar within the given range starting at the specified
* progress amount.
*
* @param minProgress the minimum progress
* @param maxProgress the maximum progress
* @param curProgress the current progress
* @param textFormatter the text formatter
*/
public MiniProgressBar(double minProgress, double maxProgress,
double curProgress, TextFormatter textFormatter) {
this.minProgress = minProgress;
this.maxProgress = maxProgress;
this.curProgress = curProgress;
setTextFormatter(textFormatter);
// Create the outer shell
setElement(DOM.createDiv());
DOM.setStyleAttribute(getElement(), "position", "relative");
setStyleName("gwt-ProgressBar-shell");
// Create the bar element
barElement = DOM.createDiv();
DOM.appendChild(getElement(), barElement);
DOM.setStyleAttribute(barElement, "height", "100%");
setBarStyleName("gwt-ProgressBar-bar");
// Create the text element
textElement = DOM.createDiv();
DOM.appendChild(getElement(), textElement);
DOM.setStyleAttribute(textElement, "position", "absolute");
DOM.setStyleAttribute(textElement, "top", "0px");
//Set the current progress
setProgress(curProgress);
}
/**
* Get the maximum progress.
*
* @return the maximum progress
*/
public double getMaxProgress() {
return maxProgress;
}
/**
* Get the minimum progress.
*
* @return the minimum progress
*/
public double getMinProgress() {
return minProgress;
}
/**
* Get the current percent complete, relative to the minimum and maximum
* values. The percent will always be between 0.0 - 1.0.
*
* @return the current percent complete
*/
public double getPercent() {
// If we have no range
if (maxProgress <= minProgress) {
return 0.0;
}
// Calculate the relative progress
double percent = (curProgress - minProgress) / (maxProgress - minProgress);
return Math.max(0.0, Math.min(1.0, percent));
}
/**
* Get the current progress.
*
* @return the current progress
*/
public double getProgress() {
return curProgress;
}
/**
* Get the text formatter.
*
* @return the text formatter
*/
public TextFormatter getTextFormatter() {
return textFormatter;
}
/**
* Check whether the text is visible or not.
*
* @return true if the text is visible
*/
public boolean isTextVisible() {
return textVisible;
}
/**
* This method is called when the dimensions of the parent element change.
* Subclasses should override this method as needed.
*
* Move the text to the center of the progress bar.
*
* @param width the new client width of the element
* @param height the new client height of the element
*/
public void onResize(int width, int height) {
if (textVisible) {
int textWidth = DOM.getElementPropertyInt(textElement, "offsetWidth");
int left = (width / 2) - (textWidth / 2);
DOM.setStyleAttribute(textElement, "left", left + "px");
}
}
/**
* Redraw the progress bar when something changes the layout.
*/
public void redraw() {
if (isAttached()) {
int width = DOM.getElementPropertyInt(getElement(), "clientWidth");
int height = DOM.getElementPropertyInt(getElement(), "clientHeight");
onResize(width, height);
}
}
public void setBarStyleName(String barClassName) {
DOM.setElementProperty(barElement, "className", barClassName);
}
/**
* Set the maximum progress. If the minimum progress is more than the current
* progress, the current progress is adjusted to be within the new range.
*
* @param maxProgress the maximum progress
*/
public void setMaxProgress(double maxProgress) {
this.maxProgress = maxProgress;
curProgress = Math.min(curProgress, maxProgress);
resetProgress();
}
/**
* Set the minimum progress. If the minimum progress is more than the current
* progress, the current progress is adjusted to be within the new range.
*
* @param minProgress the minimum progress
*/
public void setMinProgress(double minProgress) {
this.minProgress = minProgress;
curProgress = Math.max(curProgress, minProgress);
resetProgress();
}
/**
* Set the current progress.
*
* @param curProgress the current progress
*/
public void setProgress(double curProgress) {
this.curProgress = Math.max(minProgress, Math.min(maxProgress, curProgress));
// Calculate percent complete
int percent = (int) (100 * getPercent());
DOM.setStyleAttribute(barElement, "width", percent + "%");
DOM.setElementProperty(textElement, "innerHTML", generateText(curProgress));
updateTextStyle(percent);
// Realign the text
redraw();
}
public void setTextFirstHalfStyleName(String textFirstHalfClassName) {
this.textFirstHalfClassName = textFirstHalfClassName;
onTextStyleChange();
}
/**
* Set the text formatter.
*
* @param textFormatter the text formatter
*/
public void setTextFormatter(TextFormatter textFormatter) {
this.textFormatter = textFormatter;
}
public void setTextSecondHalfStyleName(String textSecondHalfClassName) {
this.textSecondHalfClassName = textSecondHalfClassName;
onTextStyleChange();
}
public void setTextStyleName(String textClassName) {
this.textClassName = textClassName;
onTextStyleChange();
}
/**
* Sets whether the text is visible over the bar.
*
* @param textVisible True to show text, false to hide it
*/
public void setTextVisible(boolean textVisible) {
this.textVisible = textVisible;
if (this.textVisible) {
DOM.setStyleAttribute(textElement, "display", "");
redraw();
} else {
DOM.setStyleAttribute(textElement, "display", "none");
}
}
/**
* Generate the text to display within the progress bar. Override this
* function to change the default progress percent to a more informative
* message, such as the number of kilobytes downloaded.
*
* @param curProgress the current progress
* @return the text to display in the progress bar
*/
protected String generateText(double curProgress) {
if (textFormatter != null) {
return textFormatter.getText(this, curProgress);
} else {
return (int) (100 * getPercent()) + "%";
}
}
/**
* Get the bar element.
*
* @return the bar element
*/
protected Element getBarElement() {
return barElement;
}
/**
* Get the text element.
*
* @return the text element
*/
protected Element getTextElement() {
return textElement;
}
/**
* This method is called immediately after a widget becomes attached to the
* browser's document.
*/
@Override
protected void onLoad() {
// Reset the position attribute of the parent element
DOM.setStyleAttribute(getElement(), "position", "relative");
ResizableWidgetCollection.get().add(this);
redraw();
}
@Override
protected void onUnload() {
ResizableWidgetCollection.get().remove(this);
}
/**
* Reset the progress text based on the current min and max progress range.
*/
protected void resetProgress() {
setProgress(getProgress());
}
private void onTextStyleChange() {
int percent = (int) (100 * getPercent());
updateTextStyle(percent);
}
private void updateTextStyle(int percent) {
// Set the style depending on the size of the bar
if (percent < 50) {
DOM.setElementProperty(textElement, "className",
textClassName + " " + textFirstHalfClassName);
} else {
DOM.setElementProperty(textElement, "className",
textClassName + " " + textSecondHalfClassName);
}
}
}