/*
* Ext GWT - Ext for GWT
* Copyright(c) 2007-2009, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/
package com.extjs.gxt.ui.client.widget;
import com.extjs.gxt.ui.client.core.El;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.DragEvent;
import com.extjs.gxt.ui.client.event.DragListener;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.SliderEvent;
import com.extjs.gxt.ui.client.fx.Draggable;
import com.extjs.gxt.ui.client.util.Format;
import com.extjs.gxt.ui.client.util.Point;
import com.extjs.gxt.ui.client.util.Util;
import com.extjs.gxt.ui.client.widget.tips.Tip;
import com.extjs.gxt.ui.client.widget.tips.ToolTipConfig;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
/**
* Slider component.
*
* <dl>
* <dt><b>Events:</b></dt>
*
* <dd><b>BeforeChange</b> : SliderEvent(slider, oldValue, newValue)<br>
* <div>Fires before the slider value is changed. Listeners can cancel the
* action by calling {@link BaseEvent#setCancelled(boolean)}.</div>
* <ul>
* <li>slider : this</li>
* <li>oldValue : the old value which the slider was previously</li>
* <li>newValue : the new value which the slider is being changed to</li>
* </ul>
* </dd>
*
* <dd><b>Change</b> : SliderEvent(slider, oldValue, newValue)<br>
* <div>Fires when the slider value is changed.</div>
* <ul>
* <li>slider : this</li>
* <li>newValue : the new value which the slider has been changed to</li>
* </ul>
* </dd>
*
* </dl>
*/
public class Slider extends BoxComponent {
class Thumb extends Component {
public Thumb() {
baseStyle = "x-slider-thumb";
}
@Override
protected void onRender(Element target, int index) {
setElement(DOM.createDiv(), target, index);
super.onRender(target, index);
addStyleOnOver(getElement(), "x-slider-thumb-over");
sinkEvents(Event.ONMOUSEOVER | Event.ONMOUSEOUT);
}
}
private boolean clickToChange = true;
private Draggable drag;
private boolean draggable = true;
private El endEl;
private int halfThumb;
private int increment = 10;
private El innerEl;
private int maxValue = 100;
private String message = "{0}";
private int minValue = 0;
private Thumb thumb;
private Tip tip;
private boolean useTip = true;
private int value = 0;
private boolean vertical;
/**
* Creates a new slider.
*/
public Slider() {
baseStyle = "x-slider";
}
@Override
public El getFocusEl() {
return innerEl.lastChild();
}
/**
* Returns the increment.
*
* @return the increment
*/
public int getIncrement() {
return increment;
}
/**
* Returns the max value (defaults to 100).
*
* @return the max value
*/
public int getMaxValue() {
return maxValue;
}
/**
* Returns the tool tip message.
*
* @return the tool tip message
*/
public String getMessage() {
return message;
}
/**
* Returns the minimum value (defaults to 0).
*
* @return the minimum value
*/
public int getMinValue() {
return minValue;
}
/**
* Returns the current value.
*
* @return the current value
*/
public int getValue() {
return value;
}
/**
* Returns whether whether or not clicking on the Slider axis will change the
* slider.
*
* @return true to allow axis clicks
*/
public boolean isClickToChange() {
return clickToChange;
}
/**
* Returns true if the slider is draggable.
*
* @return true if draggable
*/
public boolean isDraggable() {
return draggable;
}
/**
* Returns true if tips are enabled.
*
* @return true if tips are enabled
*/
public boolean isUseTip() {
return useTip;
}
public boolean isVertical() {
return vertical;
}
@Override
public void onComponentEvent(ComponentEvent ce) {
super.onComponentEvent(ce);
switch (ce.getEventTypeInt()) {
case Event.ONKEYDOWN:
onKeyDown(ce);
break;
case Event.ONCLICK:
onClick(ce);
break;
}
}
/**
* Determines whether or not clicking on the slider axis will change the
* slider (defaults to true).
*
* @param clickToChange true to allow the slider axis to be clicked
*/
public void setClickToChange(boolean clickToChange) {
this.clickToChange = clickToChange;
}
/**
* True to allow the slider to be dragged (default to true).
*
* @param draggable true to enable dragging
*/
public void setDraggable(boolean draggable) {
this.draggable = draggable;
}
/**
* How many units to change the slider when adjusting by drag and drop. Use
* this option to enable 'snapping' (default to 10).
*
* @param increment the increment
*/
public void setIncrement(int increment) {
this.increment = increment;
}
/**
* Sets the max value (default to 100).
*
* @param maxValue the max value
*/
public void setMaxValue(int maxValue) {
this.maxValue = maxValue;
}
/**
* Sets the tool tip message (defaults to '{0}'). "{0} will be substituted
* with the current slider value.
*
* @param message the tool tip message
*/
public void setMessage(String message) {
this.message = message;
}
/**
* Sets the minimum value (defaults to 0).
*
* @param minValue the minimum value
*/
public void setMinValue(int minValue) {
this.minValue = minValue;
}
/**
* True to enable tool tips (default to true).
*
* @param useTip true to enable tool tips
*/
public void setUseTip(boolean useTip) {
this.useTip = useTip;
}
/**
* Sets the current value.
*
* @param value the value
*/
public void setValue(int value) {
setValue(value, false);
}
/**
* Sets the current value.
*
* @param value the value
* @param supressEvent true to suppress the change event
*/
public void setValue(int value, boolean supressEvent) {
value = normalizeValue(value);
if (this.value != value) {
SliderEvent se = new SliderEvent(this);
se.setOldValue(this.value);
se.setNewValue(value);
if (supressEvent || fireEvent(Events.BeforeChange, se)) {
this.value = value;
if (rendered) {
moveThumb(translateValue(value));
if (useTip) {
thumb.setToolTip(getToolTipConfig(value));
}
}
if (!supressEvent) {
fireEvent(Events.Change, se);
}
}
}
}
/**
* True to orient the slider vertically (defaults to false).
*
* @param vertical true for vertical
*/
public void setVertical(boolean vertical) {
this.vertical = vertical;
}
@Override
protected void afterRender() {
super.afterRender();
if (isDraggable()) {
drag = new Draggable(thumb);
drag.setConstrainVertical(!vertical);
drag.setConstrainHorizontal(vertical);
drag.setMoveAfterProxyDrag(false);
drag.setProxy(new El(DOM.createDiv()));
drag.setStartDragDistance(0);
drag.addDragListener(new DragListener() {
@Override
public void dragCancel(DragEvent de) {
onDragCancel(de);
}
@Override
public void dragEnd(DragEvent de) {
onDragEnd(de);
}
@Override
public void dragMove(DragEvent de) {
onDragMove(de);
}
@Override
public void dragStart(DragEvent de) {
onDragStart(de);
}
});
}
int value = this.value;
this.value = value == Integer.MAX_VALUE ? value - 1 : value + 1;
setValue(value, true);
}
protected int constrain(int value) {
return Util.constrain(value, minValue, maxValue);
}
@Override
protected ComponentEvent createComponentEvent(Event event) {
return new SliderEvent(this, event);
}
@Override
protected void doAttachChildren() {
super.doAttachChildren();
thumb.onAttach();
}
@Override
protected void doDetachChildren() {
super.doDetachChildren();
thumb.onDetach();
}
protected int doSnap(int v) {
if (increment == 1) {
return v;
}
int m = v % increment;
if (m != 0) {
v -= m;
if (m * 2 > increment) {
v += increment;
} else if (m * 2 < -increment) {
v -= increment;
}
}
return v;
}
protected double getRatio() {
int v = maxValue - minValue;
if (vertical) {
int h = innerEl.getHeight();
return v == 0 ? h : ((double) h / v);
} else {
int w = innerEl.getWidth();
return v == 0 ? w : ((double) w / v);
}
}
protected void moveThumb(int v) {
if (vertical) {
thumb.el().setStyleAttribute("bottom", v + "px");
} else {
thumb.el().setLeft(v);
}
}
protected int normalizeValue(int value) {
value = doSnap(value);
value = constrain(value);
return value;
}
protected void onAttach() {
super.onAttach();
el().repaint();
}
protected void onClick(ComponentEvent ce) {
if (isClickToChange() && ce.getTarget() != thumb.el().dom) {
if (vertical) {
setValue(reverseValue(ce.getClientY() - innerEl.getTop(false)));
} else {
setValue(reverseValue(ce.getClientX() - innerEl.getLeft(false)));
}
}
focus();
}
protected void onDragCancel(DragEvent de) {
onDragEnd(de);
}
protected void onDragEnd(DragEvent de) {
thumb.el().removeStyleName("x-slider-thumb-drag");
if (useTip) {
tip.hide();
}
}
protected void onDragMove(DragEvent de) {
if (vertical) {
setValue(reverseValue(de.getClientY() - innerEl.getTop(false)));
} else {
setValue(reverseValue(de.getClientX() - halfThumb - innerEl.getLeft(false)));
}
updateTip();
}
protected void onDragStart(DragEvent de) {
thumb.el().addStyleName("x-slider-thumb-drag");
thumb.el().setStyleAttribute("position", "");
if (useTip) {
thumb.getToolTip().hide();
updateTip();
}
}
protected void onKeyDown(ComponentEvent ce) {
int keyCode = ce.getKeyCode();
switch (keyCode) {
case KeyCodes.KEY_LEFT:
case KeyCodes.KEY_DOWN:
ce.stopEvent();
if (ce.isControlKey()) {
setValue(minValue);
} else {
setValue(value - increment);
}
break;
case KeyCodes.KEY_RIGHT:
case KeyCodes.KEY_UP:
ce.stopEvent();
if (ce.isControlKey()) {
setValue(maxValue);
} else {
setValue(value + increment);
}
break;
default:
ce.preventDefault();
}
}
@Override
protected void onRender(Element target, int index) {
setElement(DOM.createDiv(), target, index);
super.onRender(target, index);
addStyleName(vertical ? "x-slider-vert" : "x-slider-horz");
endEl = new El(DOM.createDiv());
endEl.addStyleName("x-slider-end");
innerEl = new El(DOM.createDiv());
innerEl.addStyleName("x-slider-inner");
endEl.appendChild(innerEl.dom);
El focusEl = new El(DOM.createAnchor());
focusEl.addStyleName("x-slider-focus");
focusEl.setElementAttribute("href", "#");
focusEl.setElementAttribute("hidefocus", "on");
focusEl.setElementAttribute("tabIndex", "-1");
innerEl.appendChild(focusEl.dom);
swallowEvent(Events.OnClick, focusEl.dom, true);
el().appendChild(endEl.dom);
thumb = new Thumb();
thumb.render(innerEl.dom, 0);
halfThumb = (vertical ? thumb.el().getHeight() : thumb.el().getWidth()) / 2;
sinkEvents(Event.ONKEYDOWN | Event.ONCLICK);
if (useTip) {
tip = new Tip();
tip.setHeading("");
tip.setMinWidth(20);
}
}
@Override
protected void onResize(int width, int height) {
if (vertical) {
el().setHeight(width - el().getPadding("t"));
innerEl.setHeight(height - el().getPadding("t") - endEl.getPadding("b"));
} else {
el().setWidth(width - el().getPadding("l"));
innerEl.setWidth(width - el().getPadding("l") - endEl.getPadding("r"));
}
syncThumb();
}
protected int reverseValue(int pos) {
double ratio = getRatio();
if (vertical) {
return (int) ((minValue + innerEl.getHeight() - pos) / ratio);
} else {
return (int) ((pos + halfThumb + (minValue * ratio)) / ratio);
}
}
protected void syncThumb() {
if (rendered) {
moveThumb(translateValue(value));
}
}
protected int translateValue(int v) {
double ratio = getRatio();
return (int) ((v * ratio) - (minValue * ratio) - halfThumb);
}
protected void updateTip() {
if (useTip) {
if (!tip.isRendered()) {
tip.showAt(-100, -100);
}
tip.getBody().update(onFormatValue(value));
Point p = tip.el().getAlignToXY(thumb.el().dom, vertical ? "r-l?" : "b-t?",
vertical ? new int[] {-5, 0} : new int[] {0, -5});
tip.showAt(p.x, p.y);
}
}
protected ToolTipConfig getToolTipConfig(int value) {
ToolTipConfig t = new ToolTipConfig();
t.setDismissDelay(0);
t.setText( onFormatValue(value));
return t;
}
protected String onFormatValue(int value) {
return Format.substitute(getMessage(), value);
}
}