Java tutorial
/* * Copyright Miroslav Pokorny * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package rocket.widget.client.slider; import rocket.event.client.ChangeEventListener; import rocket.event.client.Event; import rocket.event.client.EventBitMaskConstants; import rocket.event.client.EventPreviewAdapter; import rocket.event.client.FocusEventListener; import rocket.event.client.MouseDownEvent; import rocket.event.client.MouseEventAdapter; import rocket.event.client.MouseMoveEvent; import rocket.event.client.MouseOutEvent; import rocket.event.client.MouseUpEvent; import rocket.selection.client.Selection; import rocket.style.client.ComputedStyle; import rocket.style.client.Css; import rocket.style.client.CssUnit; import rocket.style.client.InlineStyle; import rocket.util.client.Checker; import rocket.widget.client.CompositeWidget; import rocket.widget.client.Panel; import rocket.widget.client.Widgets; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.EventPreview; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.Widget; /** * This slider allows the handle to be dragged or moved in two directions. As * such it holds two values one for the horizontal and the other the vertical * plane. * * @author Miroslav Pokorny (mP) */ public class FloatingSlider extends CompositeWidget { public FloatingSlider() { super(); } protected Widget createWidget() { return this.createPanel(); } protected Panel getPanel() { return (Panel) this.getWidget(); } protected Panel createPanel() { return new InternalSliderPanel() { @Override protected String getBackgroundStyleName() { return FloatingSlider.this.getBackgroundStyleName(); } @Override protected String getHandleStyleName() { return FloatingSlider.this.getHandleStyleName(); } }; } public Widget getHandle() { return this.getPanel().get(Constants.HANDLE_WIDGET_INDEX); } public void setHandle(final Widget handle) { final Panel panel = this.getPanel(); panel.remove(Constants.HANDLE_WIDGET_INDEX); panel.insert(handle, Constants.HANDLE_WIDGET_INDEX); } protected Widget createHandle() { return Widgets.createHtml(); } protected String getHandleStyleName() { return Constants.FLOATING_SLIDER_HANDLE_STYLE; } public Widget getBackground() { return this.getPanel().get(Constants.BACKGROUND_WIDGET_INDEX); } public void setBackground(final Widget background) { final Panel panel = this.getPanel(); panel.remove(Constants.BACKGROUND_WIDGET_INDEX); panel.insert(background, Constants.BACKGROUND_WIDGET_INDEX); } protected Widget createBackground() { return Widgets.createHtml(); } protected String getBackgroundStyleName() { return Constants.FLOATING_SLIDER_BACKGROUND_STYLE; } protected void afterCreateWidget() { super.afterCreateWidget(); this.getEventListenerDispatcher().addMouseEventListener(new MouseEventAdapter() { public void onMouseDown(final MouseDownEvent event) { FloatingSlider.this.onMouseDown(event); } }); this.setHandle(this.createHandle()); this.setBackground(this.createBackground()); } protected String getInitialStyleName() { return Constants.FLOATING_SLIDER_STYLE; } protected int getSunkEventsBitMask() { return EventBitMaskConstants.FOCUS_EVENTS | EventBitMaskConstants.CHANGE | EventBitMaskConstants.MOUSE_EVENTS; } /** * Sub classes must invoke this method if they choose to override. */ protected void onAttach() { final int x = this.getXValue(); final int y = this.getYValue(); super.onAttach(); this.setXValue(x); this.setYValue(y); } /** * Dispatches the event to the respective handler depending on whether the * handle or the slider background was clicked. * * @param event * The cause event */ protected void onMouseDown(final MouseDownEvent event) { Checker.notNull("parameter:event", event); while (true) { final Element target = event.getTarget(); // check if the handle widget has been clicked... if (this.getHandle().getElement().isOrHasChild(target)) { this.onHandleMouseDown(event); break; } // was the slider background itself clicked ? if (this.getElement().isOrHasChild(target)) { this.onBackgroundMouseDown(event); break; } // unknown target do nothing... break; } } protected void onBackgroundMouseDown(final MouseDownEvent event) { Checker.notNull("parameter:event", event); final Widget handle = this.getHandle(); final Element widget = this.getElement(); final int mouseX = event.getPageX() - DOM.getAbsoluteLeft(widget) - handle.getOffsetWidth() / 2; final int mouseY = event.getPageY() - DOM.getAbsoluteTop(widget) - handle.getOffsetHeight() / 2; this.onBackgroundMouseDown(mouseX, mouseY); final HandleSlidingTimer timer = this.getTimer(); timer.setMouseX(mouseX); timer.setMouseY(mouseY); } /** * Initiates the dragging of the handle until it is released. * * @param event */ protected void onHandleMouseDown(final MouseDownEvent event) { Checker.notNull("parameter:event", event); if (false == this.hasDraggingEventPreview()) { Selection.clearAnySelectedText(); Selection.disableTextSelection(); final EventPreview eventPreview = this.createDraggingEventPreview(); this.setDraggingEventPreview(eventPreview); DOM.addEventPreview(eventPreview); this.getHandle().addStyleName(this.getDraggingStyle()); } } protected String getDraggingStyle() { return Constants.FLOATING_SLIDER_DRAGGING_STYLE; } /** * The EventPreview object that is following the handle whilst it is being * dragged. */ private EventPreview draggingEventPreview; protected EventPreview getDraggingEventPreview() { Checker.notNull("field:draggingEventPreview", draggingEventPreview); return this.draggingEventPreview; } protected boolean hasDraggingEventPreview() { return null != this.draggingEventPreview; } protected void setDraggingEventPreview(final EventPreview draggingEventPreview) { Checker.notNull("parameter:draggingEventPreview", draggingEventPreview); this.draggingEventPreview = draggingEventPreview; } protected void clearDraggingEventPreview() { this.draggingEventPreview = null; } /** * This EventPreview anonymous class merely delegates to * {@link #onDraggingEventPreview(Event)} * * @return */ protected EventPreview createDraggingEventPreview() { return new EventPreviewAdapter() { protected void onMouseMove(final MouseMoveEvent event) { FloatingSlider.this.onHandleMouseMove(event); } protected void onMouseUp(final MouseUpEvent event) { FloatingSlider.this.onHandleMouseUp(event); } }; } /** * This method is called when the mouse button is let go whilst dragging the * slider handle. * * @param event */ protected void onHandleMouseUp(final MouseUpEvent event) { this.getHandle().removeStyleName(this.getDraggingStyle()); DOM.removeEventPreview(this.getDraggingEventPreview()); this.clearDraggingEventPreview(); Selection.enableTextSelection(); } protected void onBackgroundMouseDown(final int mouseX, final int mouseY) { final Element handle = this.getHandle().getElement(); final InlineStyle inlineStyle = InlineStyle.getInlineStyle(handle); final ComputedStyle computedStyle = ComputedStyle.getComputedStyle(handle); boolean killTimer = false; final int x = this.getXValue(); final int handleX = computedStyle.getInteger(Css.LEFT, CssUnit.PX, 0); int deltaX = mouseX - handleX; if (0 != deltaX) { if (deltaX < 0) { this.onBeforeHandleXMouseDown(); } else { this.onAfterHandleXMouseDown(); } final int handleXAfter = computedStyle.getInteger(Css.LEFT, CssUnit.PX, 0); final int deltaXAfter = mouseX - handleXAfter; if (deltaX < 0 ^ deltaXAfter < 0) { this.setXValue(x); inlineStyle.setInteger(Css.LEFT, mouseX, CssUnit.PX); deltaX = 0; } } final int y = this.getYValue(); final int handleY = computedStyle.getInteger(Css.TOP, CssUnit.PX, 0); int deltaY = mouseY - handleY; if (0 != deltaY) { if (deltaY < 0) { this.onBeforeHandleYMouseDown(); } else { this.onAfterHandleYMouseDown(); } final int handleYAfter = computedStyle.getInteger(Css.TOP, CssUnit.PX, 0); final int deltaYAfter = mouseY - handleYAfter; if (deltaY < 0 ^ deltaYAfter < 0) { this.setYValue(y); inlineStyle.setInteger(Css.TOP, mouseY, CssUnit.PX); deltaY = 0; } } if (killTimer || deltaX == 0 && deltaY == 0) { this.clearTimer(); } } /** * Decreases the xValue of this slider ensuring that it does not underflow * the minimum xValue of this slider. */ protected void onBeforeHandleXMouseDown() { final int newValue = Math.max(0, this.getXValue() - this.getDeltaX()); final Element handle = this.getHandle().getElement(); final InlineStyle inlineStyle = InlineStyle.getInlineStyle(handle); final int left = inlineStyle.getInteger(Css.LEFT, CssUnit.PX, 0); this.setXValue(newValue); inlineStyle.setInteger(Css.LEFT, left - 1, CssUnit.PX); } /** * Increases the xValue of this slider ensuring that it does not exceed the * maximum xValue of this slider. */ protected void onAfterHandleXMouseDown() { final int newValue = Math.min(this.getXValue() + this.getDeltaX(), this.getMaximumXValue()); final InlineStyle handle = InlineStyle.getInlineStyle(this.getHandle().getElement()); final int left = handle.getInteger(Css.LEFT, CssUnit.PX, 0); this.setXValue(newValue); handle.setInteger(Css.LEFT, left + 1, CssUnit.PX); } /** * Decreases the yValue of this slider ensuring that it does not underflow * the minimum yValue of this slider. */ protected void onBeforeHandleYMouseDown() { final int newValue = Math.max(0, this.getYValue() - this.getDeltaY()); final InlineStyle handle = InlineStyle.getInlineStyle(this.getHandle().getElement()); final int top = handle.getInteger(Css.TOP, CssUnit.PX, 0); this.setYValue(newValue); handle.setInteger(Css.TOP, top - 1, CssUnit.PX); } /** * Increases the yValue of this slider ensuring that it does not exceed the * maximum yValue of this slider. */ protected void onAfterHandleYMouseDown() { final int newValue = Math.min(this.getYValue() + this.getDeltaY(), this.getMaximumYValue()); final InlineStyle handle = InlineStyle.getInlineStyle(this.getHandle().getElement()); final int top = handle.getInteger(Css.TOP, CssUnit.PX, 0); this.setYValue(newValue); handle.setInteger(Css.TOP, top + 1, CssUnit.PX); } protected void onHandleMouseMove(final MouseMoveEvent event) { Checker.notNull("parameter:event", event); final Element sliderElement = this.getElement(); final int widgetX = sliderElement.getAbsoluteLeft(); final int widgetY = sliderElement.getAbsoluteTop(); final int sliderWidth = this.getOffsetWidth(); final int sliderHeight = this.getOffsetHeight(); final Widget handle = this.getHandle(); final Element handleElement = handle.getElement(); final InlineStyle handleInlineStyle = InlineStyle.getInlineStyle(handleElement); final int handleWidth = handle.getOffsetWidth(); final int handleHeight = handle.getOffsetHeight(); // make mouse coordinates relative to top/left of slider. int mouseX = event.getPageX() - widgetX; mouseX = Math.max(0, Math.min(mouseX, sliderWidth - handleWidth / 2)); final int newX = this.updateSliderValue(mouseX, this.getMaximumXValue(), sliderWidth, handleWidth); this.setXValue(newX); int left = mouseX - handleWidth / 2; handleInlineStyle.setInteger(Css.LEFT, left, CssUnit.PX); int mouseY = event.getPageY() - widgetY; mouseY = Math.max(0, Math.min(mouseY, sliderHeight - handleHeight / 2)); final int newY = this.updateSliderValue(mouseY, this.getMaximumYValue(), sliderHeight, handleHeight); this.setYValue(newY); final int top = mouseY - handleHeight / 2; handleInlineStyle.setInteger(Css.TOP, top, CssUnit.PX); event.cancelBubble(true); event.stop();// stops text selection in Opera } protected int updateSliderValue(final int mouseCoordinate, final int maximumValue, final int sliderLength, final int handleLength) { final int range = sliderLength - handleLength; int value = mouseCoordinate; if (value < 0) { value = 0; } if (value > range) { value = range; } return (int) ((float) value / range * maximumValue + 0.5f); } /** * A timer is used to simulate multiple clicks when holding down the mouse * button */ private HandleSlidingTimer timer; protected HandleSlidingTimer getTimer() { if (false == this.hasTimer()) { final HandleSlidingTimer timer = new HandleSlidingTimer(); timer.scheduleRepeating(this.getMouseDownRepeatRate()); this.setTimer(timer); } Checker.notNull("field:timer", timer); return timer; } protected boolean hasTimer() { return null != this.timer; } protected void setTimer(final HandleSlidingTimer timer) { Checker.notNull("parameter:timer", timer); this.timer = timer; } /** * Clears any active timer. */ protected void clearTimer() { if (this.hasTimer()) { this.timer.cancel(); } this.timer = null; } /** * This timer is activated whenever the user clicks and holds on a * background portion of the slider. Every mouseDownRepeatRate the slider * moves towards to the target point. */ private class HandleSlidingTimer extends Timer { public void run() { FloatingSlider.this.onBackgroundMouseDown(this.getMouseX(), this.getMouseY()); } private int mouseX; int getMouseX() { return mouseX; } void setMouseX(final int mouseX) { this.mouseX = mouseX; } private int mouseY; int getMouseY() { return mouseY; } void setMouseY(final int mouseY) { this.mouseY = mouseY; } } /** * This value in milliseconds controls the repetition of mouse down events * within the background area of the slider. Smaller values result in a * faster glide of the handle towards the mouse, whilst larger values result * in a slow movement. */ private int mouseDownRepeatRate; public int getMouseDownRepeatRate() { Checker.greaterThan("field:mouseDownRepeatRate", 0, mouseDownRepeatRate); return this.mouseDownRepeatRate; } public void setMouseDownRepeatRate(final int mouseDownRepeatRate) { Checker.greaterThan("parameter:mouseDownRepeatRate", 0, mouseDownRepeatRate); this.mouseDownRepeatRate = mouseDownRepeatRate; } /** * If the mouse has moved away from the slider cancel any active timer. * * @param event */ protected void onMouseOut(final MouseOutEvent event) { this.clearTimer(); } /** * This method handles any mouse up events. * * @param event */ protected void onMouseUp(final MouseUpEvent event) { this.clearTimer(); } // WIDGET ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: private int xValue; public int getXValue() { return this.xValue; } public void setXValue(final int xValue) { final int maximumValue = this.getMaximumXValue(); Checker.between("parameter:xValue", xValue, 0, maximumValue + 1); this.xValue = xValue; final Widget handle = this.getHandle(); final int sliderLength = this.getOffsetWidth() - handle.getOffsetWidth(); final int newLeft = Math.round(1.0f * this.getXValue() * sliderLength / this.getMaximumXValue()); final InlineStyle handleInlineStyle = InlineStyle.getInlineStyle(handle.getElement()); handleInlineStyle.setString(Css.POSITION, "absolute"); handleInlineStyle.setInteger(Css.LEFT, newLeft, CssUnit.PX); this.getEventListenerDispatcher().getChangeEventListeners().fireChange(this); } /** * The maximum xValue of the slider. The minimum xValue is defaulted to 0. * Clients must adjust this xValue if they wish to use a different range of * values. */ private int maximumXValue; public int getMaximumXValue() { Checker.greaterThanOrEqual("field:maximumXValue", 0, maximumXValue); return this.maximumXValue; } public void setMaximumXValue(final int maximumXValue) { Checker.greaterThanOrEqual("parameter:maximumXValue", 0, maximumXValue); this.maximumXValue = maximumXValue; } /** * The amount the xValue jumps. The xValue must be 1 or more. */ private int deltaX; public int getDeltaX() { Checker.greaterThan("field:deltaX", 0, deltaX); return this.deltaX; } public void setDeltaX(final int xDelta) { Checker.greaterThan("parameter:deltaX", 0, xDelta); this.deltaX = xDelta; } /** * The current yValue of the slider */ private int yValue; public int getYValue() { return this.yValue; } public void setYValue(final int yValue) { final int maximumValue = this.getMaximumYValue(); Checker.between("parameter:yValue", yValue, 0, maximumValue + 1); this.yValue = yValue; final Widget handle = this.getHandle(); final int sliderLength = this.getOffsetHeight() - handle.getOffsetHeight(); final int newTop = Math.round(1.0f * this.getYValue() * sliderLength / this.getMaximumYValue()); final InlineStyle handleInlineStyle = InlineStyle.getInlineStyle(handle.getElement()); handleInlineStyle.setString(Css.POSITION, "absolute"); handleInlineStyle.setInteger(Css.TOP, newTop, CssUnit.PX); this.getEventListenerDispatcher().getChangeEventListeners().fireChange(this); } /** * The maximum xValue of the slider. The minimum xValue is defaulted to 0. * Clients must adjust this xValue if they wish to use a different range of * values. */ private int maximumYValue; public int getMaximumYValue() { Checker.greaterThanOrEqual("field:maximumYValue", 0, this.maximumYValue); return this.maximumYValue; } public void setMaximumYValue(final int maximumYValue) { Checker.greaterThanOrEqual("parameter:maximumYValue", 0, maximumYValue); this.maximumYValue = maximumYValue; } /** * The amount the yValue jumps. The yValue must be 1 or more. */ private int deltaY; public int getDeltaY() { Checker.greaterThan("field:deltaY", 0, deltaY); return this.deltaY; } public void setDeltaY(final int yDelta) { Checker.greaterThan("parameter:deltaY", 0, yDelta); this.deltaY = yDelta; } public void addChangeEventListener(final ChangeEventListener changeEventListener) { this.getEventListenerDispatcher().addChangeEventListener(changeEventListener); } public void removeChangeEventListener(final ChangeEventListener changeEventListener) { this.getEventListenerDispatcher().removeChangeEventListener(changeEventListener); } public void addFocusEventListener(final FocusEventListener focusEventListener) { this.getEventListenerDispatcher().addFocusEventListener(focusEventListener); } public void removeFocusEventListener(final FocusEventListener focusEventListener) { this.getEventListenerDispatcher().removeFocusEventListener(focusEventListener); } public String toString() { return super.toString() + ", values: " + xValue + "," + yValue; } }