All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geomajas.graphics.client.widget.SliderBar Maven / Gradle / Ivy

The newest version!
/*
 * This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
 *
 * Copyright 2008-2014 Geosparc nv, http://www.geosparc.com/, Belgium.
 *
 * The program is available in open source according to the Apache
 * License, Version 2.0. All contributions in this program are covered
 * by the Geomajas Contributors License Agreement. For full licensing
 * details, see LICENSE.txt in the project root.
 */
package org.geomajas.graphics.client.widget;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.RequiresResize;

/**
 * A widget that allows the user to select a value within a range of possible values using a sliding bar that responds
 * to mouse events.
 * 
 * 

Keyboard Events

*

* SliderBar listens for the following key events. Holding down a key will repeat the action until the key is released. *

    *
  • left arrow - shift left one step
  • *
  • right arrow - shift right one step
  • *
  • ctrl+left arrow - jump left 10% of the distance
  • *
  • ctrl+right arrow - jump right 10% of the distance
  • *
  • home - jump to min value
  • *
  • end - jump to max value
  • *
  • space - jump to middle value
  • *
*

* *

CSS Style Rules

*
    *
  • .gwt-SliderBar-shell { primary style }
  • *
  • .gwt-SliderBar-shell-focused { primary style when focused }
  • *
  • .gwt-SliderBar-shell gwt-SliderBar-line { the line that the knob moves along }
  • *
  • .gwt-SliderBar-shell gwt-SliderBar-line-sliding { the line that the knob moves along when sliding }
  • *
  • .gwt-SliderBar-shell .gwt-SliderBar-knob { the sliding knob }
  • *
  • .gwt-SliderBar-shell .gwt-SliderBar-knob-sliding { the sliding knob when sliding }
  • *
  • * .gwt-SliderBar-shell .gwt-SliderBar-tick { the ticks along the line }
  • *
  • .gwt-SliderBar-shell .gwt-SliderBar-label { the text labels along the line }
  • *
* * @author turbomanage * @author Emiel Ackermann */ public class SliderBar extends FocusPanel implements RequiresResize, HasValue, HasValueChangeHandlers { private static final String SLIDER_LEFT = "transparency-SliderBar-left"; private static final String SLIDER_RIGHT = "transparency-SliderBar-right"; private static final String SLIDER_LINE = "transparency-SliderBar-line"; private static final String SLIDER_KNOB = "transparency-SliderBar-knob"; private static final String SLIDER_LINE_SLIDING = "transparency-SliderBar-line-sliding"; private static final String SLIDER_KNOB_SLIDING = "transparency-SliderBar-knob-sliding"; private static final String SLIDER_TICK = "transparency-SliderBar-tick"; private static final String SLIDER_TICK_SLIDING = "transparency-SliderBar-tick-disabled"; private static final String SLIDER_SHELL = "transparency-SliderBar-shell"; private static final String SLIDER_LINE_DISABLED = "transparency-SliderBar-line-disabled"; private static final String SLIDER_PANEL = "transparency-SliderBar-panel"; /** * A formatter used to format the labels displayed in the widget. */ public interface LabelFormatter { /** * Generate the text to display in each label based on the label's value. * * Override this method to change the text displayed within the SliderBar. * * @param slider * the Slider bar * @param value * the value the label displays * @return the text to display for the label */ String formatLabel(SliderBar slider, double value); } /** * The timer used to continue to shift the knob as the user holds down one of the left/right arrow keys. Only IE * auto-repeats, so we just keep catching the events. */ private class KeyTimer extends Timer { /** * A bit indicating that this is the first run. */ private boolean firstRun = true; /** * The delay between shifts, which shortens as the user holds down the button. */ private int repeatDelay = 30; /** * A bit indicating whether we are shifting to a higher or lower value. */ private boolean shiftRight; /** * The number of steps to shift with each press. */ private int multiplier = 1; /** * This method will be called when a timer fires. Override it to implement the timer's logic. */ @Override public void run() { // Highlight the knob on first run if (firstRun) { firstRun = false; startSliding(true, false); } // Slide the slider bar if (shiftRight) { setCurrentValue(curValue + multiplier * stepSize); } else { setCurrentValue(curValue - multiplier * stepSize); } // Repeat this timer until cancelled by keyup event schedule(repeatDelay); } /** * Schedules a timer to elapse in the future. * * @param delayMillis * how long to wait before the timer elapses, in milliseconds * @param shiftRight * whether to shift up or not * @param multiplier * the number of steps to shift */ public void schedule(int delayMillis, boolean shiftRight, int multiplier) { firstRun = true; this.shiftRight = shiftRight; this.multiplier = multiplier; super.schedule(delayMillis); } } /** * The current value. */ private double curValue; /** * The knob that slides across the line. */ private Button knobImage = new Button(); /** * The timer used to continue to shift the knob if the user holds down a key. */ private KeyTimer keyTimer = new KeyTimer(); /** * The elements used to display labels above the ticks. */ private List labelElements = new ArrayList(); /** * The formatter used to generate label text. */ private LabelFormatter labelFormatter; /** * The line that the knob moves over. */ private Element lineElement; /** * The offset between the edge of the shell and the line. */ private int lineLeftOffset; /** * The maximum slider value. */ private double maxValue; /** * The minimum slider value. */ private double minValue; /** * The number of labels to show. */ private int numLabels; /** * The number of tick marks to show. */ private int numTicks; /** * A bit indicating whether or not we are currently sliding the slider bar due to keyboard events. */ private boolean slidingKeyboard; /** * A bit indicating whether or not we are currently sliding the slider bar due to mouse events. */ private boolean slidingMouse; /** * A bit indicating whether or not the slider is enabled */ private boolean enabled = true; /** * The size of the increments between knob positions. */ private double stepSize; /** * The elements used to display tick marks, which are the vertical lines along the slider bar. */ private List tickElements = new ArrayList(); /** * Create a slider bar. * * @param minValue * the minimum value in the range * @param maxValue * the maximum value in the range */ public SliderBar(double minValue, double maxValue) { this(minValue, maxValue, null); } /** * Create a slider bar. * * @param minValue * the minimum value in the range * @param maxValue * the maximum value in the range * @param labelFormatter * the label formatter */ public SliderBar(double minValue, double maxValue, LabelFormatter labelFormatter) { super(); this.minValue = minValue; this.maxValue = maxValue; setLabelFormatter(labelFormatter); // Create the outer shell DOM.setStyleAttribute(getElement(), "position", "relative"); setStyleName(SLIDER_SHELL); // Create the line lineElement = DOM.createDiv(); DOM.appendChild(getElement(), lineElement); DOM.setStyleAttribute(lineElement, "position", "absolute"); DOM.setElementProperty(lineElement, "className", SLIDER_LINE); // Create the knob Element knobElement = knobImage.getElement(); DOM.appendChild(getElement(), knobElement); DOM.setStyleAttribute(knobElement, "position", "absolute"); DOM.setStyleAttribute(knobElement, "backgroundImage", "url('" + GWT.getModuleBaseURL() + "image/slider.gif')"); DOM.setElementProperty(knobElement, "className", SLIDER_KNOB); sinkEvents(Event.MOUSEEVENTS | Event.KEYEVENTS | Event.FOCUSEVENTS); // workaround to render properly when parent Widget does not // implement ProvidesResize since DOM doesn't provide element // height and width until onModuleLoad() finishes. Scheduler.get().scheduleDeferred(new ScheduledCommand() { @Override public void execute() { onResize(); } }); } public HandlerRegistration addValueChangeHandler(ValueChangeHandler handler) { return addHandler(handler, ValueChangeEvent.getType()); } /** * Return the current value. * * @return the current value */ public double getCurrentValue() { return curValue; } /** * Return the label formatter. * * @return the label formatter */ public LabelFormatter getLabelFormatter() { return labelFormatter; } /** * Return the max value. * * @return the max value */ public double getMaxValue() { return maxValue; } /** * Return the minimum value. * * @return the minimum value */ public double getMinValue() { return minValue; } /** * Return the number of labels. * * @return the number of labels */ public int getNumLabels() { return numLabels; } /** * Return the number of ticks. * * @return the number of ticks */ public int getNumTicks() { return numTicks; } /** * Return the step size. * * @return the step size */ public double getStepSize() { return stepSize; } /** * Return the total range between the minimum and maximum values. * * @return the total range */ public double getTotalRange() { if (minValue > maxValue) { return 0; } else { return maxValue - minValue; } } public Double getValue() { return curValue; } /** * @return Gets whether this widget is enabled */ public boolean isEnabled() { return enabled; } /** * Listen for events that will move the knob. * * @param event * the event that occurred */ @Override public void onBrowserEvent(Event event) { super.onBrowserEvent(event); if (enabled) { switch (DOM.eventGetType(event)) { // Unhighlight and cancel keyboard events case Event.ONBLUR: keyTimer.cancel(); if (slidingMouse) { DOM.releaseCapture(getElement()); slidingMouse = false; slideKnob(event); stopSliding(true, true); } else if (slidingKeyboard) { slidingKeyboard = false; stopSliding(true, true); } unhighlight(); break; // Highlight on focus case Event.ONFOCUS: highlight(); break; // Mousewheel events case Event.ONMOUSEWHEEL: int velocityY = DOM.eventGetMouseWheelVelocityY(event); DOM.eventPreventDefault(event); if (velocityY > 0) { shiftRight(1); } else { shiftLeft(1); } break; // Shift left or right on key press case Event.ONKEYDOWN: if (!slidingKeyboard) { int multiplier = 1; if (DOM.eventGetCtrlKey(event)) { multiplier = (int) (getTotalRange() / stepSize / 10); } switch (DOM.eventGetKeyCode(event)) { case KeyCodes.KEY_HOME: DOM.eventPreventDefault(event); setCurrentValue(minValue); break; case KeyCodes.KEY_END: DOM.eventPreventDefault(event); setCurrentValue(maxValue); break; case KeyCodes.KEY_LEFT: DOM.eventPreventDefault(event); slidingKeyboard = true; startSliding(false, true); shiftLeft(multiplier); keyTimer.schedule(400, false, multiplier); break; case KeyCodes.KEY_RIGHT: DOM.eventPreventDefault(event); slidingKeyboard = true; startSliding(false, true); shiftRight(multiplier); keyTimer.schedule(400, true, multiplier); break; case 32: DOM.eventPreventDefault(event); setCurrentValue(minValue + getTotalRange() / 2); break; } } break; // Stop shifting on key up case Event.ONKEYUP: keyTimer.cancel(); if (slidingKeyboard) { slidingKeyboard = false; stopSliding(true, true); } break; // Mouse Events case Event.ONMOUSEDOWN: setFocus(true); slidingMouse = true; DOM.setCapture(getElement()); startSliding(true, true); DOM.eventPreventDefault(event); slideKnob(event); break; case Event.ONMOUSEUP: if (slidingMouse) { DOM.releaseCapture(getElement()); slidingMouse = false; slideKnob(event); stopSliding(true, true); } break; case Event.ONMOUSEMOVE: if (slidingMouse) { slideKnob(event); } break; } } } /** * This method is called when the dimensions of the parent element change. Subclasses should override this method as * needed. * * @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) { // Center the line in the shell int lineWidth = lineElement.getOffsetWidth(); lineLeftOffset = (width / 2) - (lineWidth / 2); DOM.setStyleAttribute(lineElement, "left", lineLeftOffset + "px"); // Draw the other components drawLabels(); drawTicks(); drawKnob(); } /** * Redraw the progress bar when something changes the layout. */ public void redraw() { if (isAttached()) { int width = getElement().getClientWidth(); int height = getElement().getClientHeight(); onResize(width, height); } } /** * Set the current value and fire the onValueChange event. * * @param curValue * the current value */ public void setCurrentValue(double curValue) { setCurrentValue(curValue, true); } /** * Set the current value and optionally fire the onValueChange event. * * @param curValue * the current value * @param fireEvent * fire the onValue change event if true */ public void setCurrentValue(double curValue, boolean fireEvent) { // Confine the value to the range this.curValue = Math.max(minValue, Math.min(maxValue, curValue)); double remainder = (this.curValue - minValue) % stepSize; this.curValue -= remainder; // Go to next step if more than halfway there if ((remainder > (stepSize / 2)) && ((this.curValue + stepSize) <= maxValue)) { this.curValue += stepSize; } // Redraw the knob drawKnob(); // Fire the ValueChangeEvent if (fireEvent) { ValueChangeEvent.fire(this, this.curValue); } } /** * Sets whether this widget is enabled. * * @param enabled * true to enable the widget, false to disable it */ public void setEnabled(boolean enabled) { this.enabled = enabled; if (enabled) { DOM.setElementProperty(lineElement, "className", SLIDER_LINE); } else { DOM.setElementProperty(lineElement, "className", SLIDER_LINE + " " + SLIDER_LINE_DISABLED); } redraw(); } /** * Set the label formatter. * * @param labelFormatter * the label formatter */ public void setLabelFormatter(LabelFormatter labelFormatter) { this.labelFormatter = labelFormatter; } /** * Set the max value. * * @param maxValue * the current value */ public void setMaxValue(double maxValue) { this.maxValue = maxValue; drawLabels(); resetCurrentValue(); } /** * Set the minimum value. * * @param minValue * the current value */ public void setMinValue(double minValue) { this.minValue = minValue; drawLabels(); resetCurrentValue(); } /** * Set the number of labels to show on the line. Labels indicate the value of the slider at that point. Use this * method to enable labels. * * If you set the number of labels equal to the total range divided by the step size, you will get a properly * aligned "jumping" effect where the knob jumps between labels. * * Note that the number of labels displayed will be one more than the number you specify, so specify 1 labels to * show labels on either end of the line. In other words, numLabels is really the number of slots between the * labels. * * setNumLabels(0) will disable labels. * * @param numLabels * the number of labels to show */ public void setNumLabels(int numLabels) { this.numLabels = numLabels; drawLabels(); } /** * Set the number of ticks to show on the line. A tick is a vertical line that represents a division of the overall * line. Use this method to enable ticks. * * If you set the number of ticks equal to the total range divided by the step size, you will get a properly aligned * "jumping" effect where the knob jumps between ticks. * * Note that the number of ticks displayed will be one more than the number you specify, so specify 1 tick to show * ticks on either end of the line. In other words, numTicks is really the number of slots between the ticks. * * setNumTicks(0) will disable ticks. * * @param numTicks * the number of ticks to show */ public void setNumTicks(int numTicks) { this.numTicks = numTicks; drawTicks(); } /** * Set the step size. * * @param stepSize * the current value */ public void setStepSize(double stepSize) { this.stepSize = stepSize; resetCurrentValue(); } public void setValue(Double value) { setCurrentValue(value, false); } public void setValue(Double value, boolean fireEvent) { setCurrentValue(value, fireEvent); } /** * Shift to the left (smaller value). * * @param numSteps * the number of steps to shift */ public void shiftLeft(int numSteps) { setCurrentValue(getCurrentValue() - numSteps * stepSize); } /** * Shift to the right (greater value). * * @param numSteps * the number of steps to shift */ public void shiftRight(int numSteps) { setCurrentValue(getCurrentValue() + numSteps * stepSize); } /** * Format the label to display above the ticks * * Override this method in a subclass to customize the format. By default, this method returns the integer portion * of the value. * * @param value * the value at the label * @return the text to put in the label */ protected String formatLabel(double value) { if (labelFormatter != null) { return labelFormatter.formatLabel(this, value); } else { return (int) (10 * value) / 10.0 + ""; } } /** * Get the percentage of the knob's position relative to the size of the line. The return value will be between 0.0 * and 1.0. * * @return the current percent complete */ protected double getKnobPercent() { // If we have no range if (maxValue <= minValue) { return 0; } // Calculate the relative progress double percent = (curValue - minValue) / (maxValue - minValue); return Math.max(0.0, Math.min(1.0, percent)); } /** * This method is called immediately after a widget becomes attached to the browser's document. */ @Override protected void onLoad() { // Draw the other components drawLabels(); drawTicks(); drawKnob(); // Reset the position attribute of the parent element DOM.setStyleAttribute(getElement(), "position", "relative"); } /** * Draw the knob where it is supposed to be relative to the line. */ private void drawKnob() { // Abort if not attached if (!isAttached()) { return; } // Move the knob to the correct position Element knobElement = knobImage.getElement(); int lineWidth = lineElement.getOffsetWidth(); int knobWidth = knobElement.getOffsetWidth(); int knobLeftOffset = (int) (lineLeftOffset + (getKnobPercent() * lineWidth) - (knobWidth / 2)); knobLeftOffset = Math.min(knobLeftOffset, lineLeftOffset + lineWidth - (knobWidth / 2) - 1); DOM.setStyleAttribute(knobElement, "left", knobLeftOffset + "px"); } /** * Draw the labels along the line. NOT USED IN KEYSTONE */ private void drawLabels() { // Abort if not attached if (!isAttached()) { return; } // Draw the labels int lineWidth = lineElement.getOffsetWidth(); if (numLabels > 0) { // Create the labels or make them visible for (int i = 0; i <= numLabels; i++) { Element label = null; if (i < labelElements.size()) { label = labelElements.get(i); } else { // Create the new label label = DOM.createDiv(); DOM.setStyleAttribute(label, "position", "absolute"); DOM.setStyleAttribute(label, "display", "none"); if (enabled) { DOM.setElementProperty(label, "className", "ks-SliderBar-label"); } else { DOM.setElementProperty(label, "className", "ks-SliderBar-label-disabled"); } DOM.appendChild(getElement(), label); labelElements.add(label); } // Set the label text double value = minValue + (getTotalRange() * i / numLabels); DOM.setStyleAttribute(label, "visibility", "hidden"); DOM.setStyleAttribute(label, "display", ""); DOM.setElementProperty(label, "innerHTML", formatLabel(value)); // Move to the left so the label width is not clipped by the // shell DOM.setStyleAttribute(label, "left", "0px"); // Position the label and make it visible int labelWidth = label.getOffsetWidth(); int labelLeftOffset = lineLeftOffset + (lineWidth * i / numLabels) - (labelWidth / 2); // labelLeftOffset = Math.min(labelLeftOffset, lineLeftOffset + lineWidth - labelWidth); // labelLeftOffset = Math.max(labelLeftOffset, lineLeftOffset); DOM.setStyleAttribute(label, "left", labelLeftOffset + "px"); DOM.setStyleAttribute(label, "visibility", "visible"); } // Hide unused labels for (int i = (numLabels + 1); i < labelElements.size(); i++) { DOM.setStyleAttribute(labelElements.get(i), "display", "none"); } } else { // Hide all labels for (Element elem : labelElements) { DOM.setStyleAttribute(elem, "display", "none"); } } } /** * Draw the tick along the line. */ private void drawTicks() { // Abort if not attached if (!isAttached()) { return; } // Draw the ticks int lineWidth = lineElement.getOffsetWidth(); if (numTicks > 0) { // Create the ticks or make them visible for (int i = 0; i <= numTicks; i++) { Element tick = null; if (i < tickElements.size()) { tick = tickElements.get(i); } else { // Create the new tick tick = DOM.createDiv(); DOM.setStyleAttribute(tick, "position", "absolute"); DOM.setStyleAttribute(tick, "display", "none"); DOM.appendChild(getElement(), tick); tickElements.add(tick); } if (enabled) { DOM.setElementProperty(tick, "className", SLIDER_TICK); } else { DOM.setElementProperty(tick, "className", SLIDER_TICK + " " + SLIDER_TICK_SLIDING); } // Position the tick and make it visible DOM.setStyleAttribute(tick, "visibility", "hidden"); DOM.setStyleAttribute(tick, "display", ""); int tickWidth = tick.getOffsetWidth(); int tickLeftOffset = lineLeftOffset + (lineWidth * i / numTicks) - (tickWidth / 2); tickLeftOffset = Math.min(tickLeftOffset, lineLeftOffset + lineWidth - tickWidth); DOM.setStyleAttribute(tick, "left", tickLeftOffset + "px"); DOM.setStyleAttribute(tick, "visibility", "visible"); } // Hide unused ticks for (int i = (numTicks + 1); i < tickElements.size(); i++) { DOM.setStyleAttribute(tickElements.get(i), "display", "none"); } } else { // Hide all ticks for (Element elem : tickElements) { DOM.setStyleAttribute(elem, "display", "none"); } } } /** * Highlight this widget. */ private void highlight() { String styleName = getStylePrimaryName(); DOM.setElementProperty(getElement(), "className", styleName + " " + styleName + "-focused"); } /** * Reset the progress to constrain the progress to the current range and redraw the knob as needed. */ private void resetCurrentValue() { setCurrentValue(getCurrentValue()); } /** * Slide the knob to a new location. * * @param event * the mouse event */ private void slideKnob(Event event) { int x = DOM.eventGetClientX(event); if (x > 0) { int lineWidth = lineElement.getOffsetWidth(); int lineLeft = lineElement.getAbsoluteLeft(); double percent = (double) (x - lineLeft) / lineWidth * 1.0; setCurrentValue(getTotalRange() * percent + minValue, true); } } /** * Start sliding the knob. * * @param highlight * true to change the style * @param fireEvent * true to fire the event */ private void startSliding(boolean highlight, boolean fireEvent) { if (highlight) { DOM.setElementProperty(lineElement, "className", SLIDER_LINE + " " + SLIDER_LINE_SLIDING); DOM.setElementProperty(knobImage.getElement(), "className", SLIDER_KNOB + " " + SLIDER_KNOB_SLIDING); } } /** * Stop sliding the knob. * * @param unhighlight * true to change the style * @param fireEvent * true to fire the event */ private void stopSliding(boolean unhighlight, boolean fireEvent) { if (unhighlight) { DOM.setElementProperty(lineElement, "className", SLIDER_LINE); DOM.setElementProperty(knobImage.getElement(), "className", SLIDER_KNOB); } } /** * Unhighlight this widget. */ private void unhighlight() { DOM.setElementProperty(getElement(), "className", getStylePrimaryName()); } @Override public void onResize() { redraw(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy