// -*- 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); } } }