diff options
11 files changed, 1654 insertions, 0 deletions
diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VSlider.java b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VSlider.java new file mode 100644 index 0000000000..5030892efd --- /dev/null +++ b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VSlider.java @@ -0,0 +1,685 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * 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 com.vaadin.v7.client.ui; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.dom.client.Style.Overflow; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.event.dom.client.KeyCodes; +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.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.HasValue; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.ui.Field; +import com.vaadin.client.ui.SimpleFocusablePanel; +import com.vaadin.client.ui.SubPartAware; +import com.vaadin.client.ui.VLazyExecutor; +import com.vaadin.client.ui.VOverlay; +import com.vaadin.v7.shared.ui.slider.SliderOrientation; + +public class VSlider extends SimpleFocusablePanel + implements Field, HasValue<Double>, SubPartAware { + + public static final String CLASSNAME = "v-slider"; + + /** + * Minimum size (width or height, depending on orientation) of the slider + * base. + */ + private static final int MIN_SIZE = 50; + + protected ApplicationConnection client; + + protected String id; + + protected boolean immediate; + protected boolean disabled; + protected boolean readonly; + + private int acceleration = 1; + protected double min; + protected double max; + protected int resolution; + protected Double value; + protected SliderOrientation orientation = SliderOrientation.HORIZONTAL; + + private final HTML feedback = new HTML("", false); + private final VOverlay feedbackPopup = new VOverlay(true, false) { + { + setOwner(VSlider.this); + } + + @Override + public void show() { + super.show(); + updateFeedbackPosition(); + } + }; + + /* DOM element for slider's base */ + private final Element base; + private final int BASE_BORDER_WIDTH = 1; + + /* DOM element for slider's handle */ + private final Element handle; + + /* DOM element for decrement arrow */ + private final Element smaller; + + /* DOM element for increment arrow */ + private final Element bigger; + + /* Temporary dragging/animation variables */ + private boolean dragging = false; + + private VLazyExecutor delayedValueUpdater = new VLazyExecutor(100, + new ScheduledCommand() { + + @Override + public void execute() { + fireValueChanged(); + acceleration = 1; + } + }); + + public VSlider() { + super(); + + base = DOM.createDiv(); + handle = DOM.createDiv(); + smaller = DOM.createDiv(); + bigger = DOM.createDiv(); + + setStyleName(CLASSNAME); + + getElement().appendChild(bigger); + getElement().appendChild(smaller); + getElement().appendChild(base); + base.appendChild(handle); + + // Hide initially + smaller.getStyle().setDisplay(Display.NONE); + bigger.getStyle().setDisplay(Display.NONE); + + sinkEvents(Event.MOUSEEVENTS | Event.ONMOUSEWHEEL | Event.KEYEVENTS + | Event.FOCUSEVENTS | Event.TOUCHEVENTS); + + feedbackPopup.setWidget(feedback); + } + + @Override + public void setStyleName(String style) { + updateStyleNames(style, false); + } + + @Override + public void setStylePrimaryName(String style) { + updateStyleNames(style, true); + } + + protected void updateStyleNames(String styleName, + boolean isPrimaryStyleName) { + + feedbackPopup.removeStyleName(getStylePrimaryName() + "-feedback"); + removeStyleName(getStylePrimaryName() + "-vertical"); + + if (isPrimaryStyleName) { + super.setStylePrimaryName(styleName); + } else { + super.setStyleName(styleName); + } + + feedbackPopup.addStyleName(getStylePrimaryName() + "-feedback"); + base.setClassName(getStylePrimaryName() + "-base"); + handle.setClassName(getStylePrimaryName() + "-handle"); + smaller.setClassName(getStylePrimaryName() + "-smaller"); + bigger.setClassName(getStylePrimaryName() + "-bigger"); + + if (isVertical()) { + addStyleName(getStylePrimaryName() + "-vertical"); + } + } + + public void setFeedbackValue(double value) { + feedback.setText(String.valueOf(value)); + } + + private void updateFeedbackPosition() { + if (isVertical()) { + feedbackPopup.setPopupPosition( + handle.getAbsoluteLeft() + handle.getOffsetWidth(), + handle.getAbsoluteTop() + handle.getOffsetHeight() / 2 + - feedbackPopup.getOffsetHeight() / 2); + } else { + feedbackPopup.setPopupPosition( + handle.getAbsoluteLeft() + handle.getOffsetWidth() / 2 + - feedbackPopup.getOffsetWidth() / 2, + handle.getAbsoluteTop() - feedbackPopup.getOffsetHeight()); + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void buildBase() { + final String styleAttribute = isVertical() ? "height" : "width"; + final String oppositeStyleAttribute = isVertical() ? "width" : "height"; + final String domProperty = isVertical() ? "offsetHeight" + : "offsetWidth"; + + // clear unnecessary opposite style attribute + base.getStyle().clearProperty(oppositeStyleAttribute); + + /* + * To resolve defect #13681 we should not return from method buildBase() + * if slider has no parentElement, because such operations as + * buildHandle() and setValues(), which are needed for Slider, are + * called at the end of method buildBase(). And these methods will not + * be called if there is no parentElement. So, instead of returning from + * method buildBase() if there is no parentElement "if condition" is + * applied to call code for parentElement only in case it exists. + */ + if (getElement().hasParentElement()) { + final Element p = getElement(); + if (p.getPropertyInt(domProperty) > MIN_SIZE) { + if (isVertical()) { + setHeight(); + } else { + base.getStyle().clearProperty(styleAttribute); + } + } else { + // Set minimum size and adjust after all components have + // (supposedly) been drawn completely. + base.getStyle().setPropertyPx(styleAttribute, MIN_SIZE); + Scheduler.get().scheduleDeferred(new Command() { + + @Override + public void execute() { + final Element p = getElement(); + if (p.getPropertyInt(domProperty) > MIN_SIZE + 5 + || propertyNotNullOrEmpty(styleAttribute, p)) { + if (isVertical()) { + setHeight(); + } else { + base.getStyle().clearProperty(styleAttribute); + } + // Ensure correct position + setValue(value, false); + } + } + + // Style has non empty property + private boolean propertyNotNullOrEmpty( + final String styleAttribute, final Element p) { + return p.getStyle().getProperty(styleAttribute) != null + && !p.getStyle().getProperty(styleAttribute) + .isEmpty(); + } + }); + } + } + + if (!isVertical()) { + // Draw handle with a delay to allow base to gain maximum width + Scheduler.get().scheduleDeferred(new Command() { + @Override + public void execute() { + buildHandle(); + setValue(value, false); + } + }); + } else { + buildHandle(); + setValue(value, false); + } + + // TODO attach listeners for focusing and arrow keys + } + + void buildHandle() { + final String handleAttribute = isVertical() ? "marginTop" + : "marginLeft"; + final String oppositeHandleAttribute = isVertical() ? "marginLeft" + : "marginTop"; + + handle.getStyle().setProperty(handleAttribute, "0"); + + // clear unnecessary opposite handle attribute + handle.getStyle().clearProperty(oppositeHandleAttribute); + } + + @Override + public void onBrowserEvent(Event event) { + if (disabled || readonly) { + return; + } + final Element targ = DOM.eventGetTarget(event); + + if (DOM.eventGetType(event) == Event.ONMOUSEWHEEL) { + processMouseWheelEvent(event); + } else if (dragging || targ == handle) { + processHandleEvent(event); + } else if (targ == smaller) { + decreaseValue(true); + } else if (targ == bigger) { + increaseValue(true); + } else if (DOM.eventGetType(event) == Event.MOUSEEVENTS) { + processBaseEvent(event); + } else if (BrowserInfo.get().isGecko() + && DOM.eventGetType(event) == Event.ONKEYPRESS + || !BrowserInfo.get().isGecko() + && DOM.eventGetType(event) == Event.ONKEYDOWN) { + + if (handleNavigation(event.getKeyCode(), event.getCtrlKey(), + event.getShiftKey())) { + + feedbackPopup.show(); + + delayedValueUpdater.trigger(); + + DOM.eventPreventDefault(event); + DOM.eventCancelBubble(event, true); + } + } else if (targ.equals(getElement()) + && DOM.eventGetType(event) == Event.ONFOCUS) { + feedbackPopup.show(); + } else if (targ.equals(getElement()) + && DOM.eventGetType(event) == Event.ONBLUR) { + feedbackPopup.hide(); + } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) { + feedbackPopup.show(); + } + if (WidgetUtil.isTouchEvent(event)) { + event.preventDefault(); // avoid simulated events + event.stopPropagation(); + } + } + + private void processMouseWheelEvent(final Event event) { + final int dir = DOM.eventGetMouseWheelVelocityY(event); + + if (dir < 0) { + increaseValue(false); + } else { + decreaseValue(false); + } + + delayedValueUpdater.trigger(); + + DOM.eventPreventDefault(event); + DOM.eventCancelBubble(event, true); + } + + private void processHandleEvent(Event event) { + switch (DOM.eventGetType(event)) { + case Event.ONMOUSEDOWN: + case Event.ONTOUCHSTART: + if (!disabled && !readonly) { + focus(); + feedbackPopup.show(); + dragging = true; + handle.setClassName(getStylePrimaryName() + "-handle"); + handle.addClassName(getStylePrimaryName() + "-handle-active"); + + DOM.setCapture(getElement()); + DOM.eventPreventDefault(event); // prevent selecting text + DOM.eventCancelBubble(event, true); + event.stopPropagation(); + } + break; + case Event.ONMOUSEMOVE: + case Event.ONTOUCHMOVE: + if (dragging) { + setValueByEvent(event, false); + updateFeedbackPosition(); + event.stopPropagation(); + } + break; + case Event.ONTOUCHEND: + feedbackPopup.hide(); + case Event.ONMOUSEUP: + // feedbackPopup.hide(); + dragging = false; + handle.setClassName(getStylePrimaryName() + "-handle"); + DOM.releaseCapture(getElement()); + setValueByEvent(event, true); + event.stopPropagation(); + break; + default: + break; + } + } + + private void processBaseEvent(Event event) { + if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) { + if (!disabled && !readonly && !dragging) { + setValueByEvent(event, true); + DOM.eventCancelBubble(event, true); + } + } + } + + private void decreaseValue(boolean updateToServer) { + setValue(new Double(value.doubleValue() - Math.pow(10, -resolution)), + updateToServer); + } + + private void increaseValue(boolean updateToServer) { + setValue(new Double(value.doubleValue() + Math.pow(10, -resolution)), + updateToServer); + } + + private void setValueByEvent(Event event, boolean updateToServer) { + double v = min; // Fallback to min + + final int coord = getEventPosition(event); + + final int handleSize, baseSize, baseOffset; + if (isVertical()) { + handleSize = handle.getOffsetHeight(); + baseSize = base.getOffsetHeight(); + baseOffset = base.getAbsoluteTop() - Window.getScrollTop() + - handleSize / 2; + } else { + handleSize = handle.getOffsetWidth(); + baseSize = base.getOffsetWidth(); + baseOffset = base.getAbsoluteLeft() - Window.getScrollLeft() + + handleSize / 2; + } + + if (isVertical()) { + v = (baseSize - (coord - baseOffset)) + / (double) (baseSize - handleSize) * (max - min) + min; + } else { + v = (coord - baseOffset) / (double) (baseSize - handleSize) + * (max - min) + min; + } + + if (v < min) { + v = min; + } else if (v > max) { + v = max; + } + + setValue(v, updateToServer); + } + + /** + * TODO consider extracting touches support to an impl class specific for + * webkit (only browser that really supports touches). + * + * @param event + * @return + */ + protected int getEventPosition(Event event) { + if (isVertical()) { + return WidgetUtil.getTouchOrMouseClientY(event); + } else { + return WidgetUtil.getTouchOrMouseClientX(event); + } + } + + public void iLayout() { + if (isVertical()) { + setHeight(); + } + // Update handle position + setValue(value, false); + } + + private void setHeight() { + // Calculate decoration size + base.getStyle().setHeight(0, Unit.PX); + base.getStyle().setOverflow(Overflow.HIDDEN); + int h = getElement().getOffsetHeight(); + if (h < MIN_SIZE) { + h = MIN_SIZE; + } + base.getStyle().setHeight(h, Unit.PX); + base.getStyle().clearOverflow(); + } + + private void fireValueChanged() { + ValueChangeEvent.fire(VSlider.this, value); + } + + /** + * Handles the keyboard events handled by the Slider + * + * @param event + * The keyboard event received + * @return true iff the navigation event was handled + */ + public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) { + + // No support for ctrl moving + if (ctrl) { + return false; + } + + if (keycode == getNavigationUpKey() && isVertical() + || keycode == getNavigationRightKey() && !isVertical()) { + if (shift) { + for (int a = 0; a < acceleration; a++) { + increaseValue(false); + } + acceleration++; + } else { + increaseValue(false); + } + return true; + } else if (keycode == getNavigationDownKey() && isVertical() + || keycode == getNavigationLeftKey() && !isVertical()) { + if (shift) { + for (int a = 0; a < acceleration; a++) { + decreaseValue(false); + } + acceleration++; + } else { + decreaseValue(false); + } + return true; + } + + return false; + } + + /** + * Get the key that increases the vertical slider. By default it is the up + * arrow key but by overriding this you can change the key to whatever you + * want. + * + * @return The keycode of the key + */ + protected int getNavigationUpKey() { + return KeyCodes.KEY_UP; + } + + /** + * Get the key that decreases the vertical slider. By default it is the down + * arrow key but by overriding this you can change the key to whatever you + * want. + * + * @return The keycode of the key + */ + protected int getNavigationDownKey() { + return KeyCodes.KEY_DOWN; + } + + /** + * Get the key that decreases the horizontal slider. By default it is the + * left arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationLeftKey() { + return KeyCodes.KEY_LEFT; + } + + /** + * Get the key that increases the horizontal slider. By default it is the + * right arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationRightKey() { + return KeyCodes.KEY_RIGHT; + } + + public void setConnection(ApplicationConnection client) { + this.client = client; + } + + public void setId(String id) { + this.id = id; + } + + public void setImmediate(boolean immediate) { + this.immediate = immediate; + } + + public void setDisabled(boolean disabled) { + this.disabled = disabled; + } + + public void setReadOnly(boolean readonly) { + this.readonly = readonly; + } + + private boolean isVertical() { + return orientation == SliderOrientation.VERTICAL; + } + + public void setOrientation(SliderOrientation orientation) { + if (this.orientation != orientation) { + this.orientation = orientation; + updateStyleNames(getStylePrimaryName(), true); + } + } + + public void setMinValue(double value) { + min = value; + } + + public void setMaxValue(double value) { + max = value; + } + + public void setResolution(int resolution) { + this.resolution = resolution; + } + + @Override + public HandlerRegistration addValueChangeHandler( + ValueChangeHandler<Double> handler) { + return addHandler(handler, ValueChangeEvent.getType()); + } + + @Override + public Double getValue() { + return value; + } + + @Override + public void setValue(Double value) { + if (value < min) { + value = min; + } else if (value > max) { + value = max; + } + + // Update handle position + final String styleAttribute = isVertical() ? "marginTop" : "marginLeft"; + final String domProperty = isVertical() ? "offsetHeight" + : "offsetWidth"; + final int handleSize = handle.getPropertyInt(domProperty); + final int baseSize = base.getPropertyInt(domProperty) + - 2 * BASE_BORDER_WIDTH; + + final int range = baseSize - handleSize; + double v = value.doubleValue(); + + // Round value to resolution + if (resolution > 0) { + v = Math.round(v * Math.pow(10, resolution)); + v = v / Math.pow(10, resolution); + } else { + v = Math.round(v); + } + final double valueRange = max - min; + double p = 0; + if (valueRange > 0) { + p = range * ((v - min) / valueRange); + } + if (p < 0) { + p = 0; + } + if (isVertical()) { + p = range - p; + } + final double pos = p; + + handle.getStyle().setPropertyPx(styleAttribute, (int) Math.round(pos)); + + // Update value + this.value = new Double(v); + setFeedbackValue(v); + } + + @Override + public void setValue(Double value, boolean fireEvents) { + if (value == null) { + return; + } + + setValue(value); + + if (fireEvents) { + fireValueChanged(); + } + } + + @Override + public com.google.gwt.user.client.Element getSubPartElement( + String subPart) { + if (subPart.equals("popup")) { + feedbackPopup.show(); + return feedbackPopup.getElement(); + } + return null; + } + + @Override + public String getSubPartName( + com.google.gwt.user.client.Element subElement) { + if (feedbackPopup.getElement().isOrHasChild(subElement)) { + return "popup"; + } + return null; + } +} diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/slider/SliderConnector.java b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/slider/SliderConnector.java new file mode 100644 index 0000000000..29432d61b6 --- /dev/null +++ b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/slider/SliderConnector.java @@ -0,0 +1,96 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * 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 com.vaadin.v7.client.ui.slider; + +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.vaadin.client.communication.RpcProxy; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.layout.ElementResizeEvent; +import com.vaadin.client.ui.layout.ElementResizeListener; +import com.vaadin.shared.ui.Connect; +import com.vaadin.v7.client.ui.AbstractFieldConnector; +import com.vaadin.v7.client.ui.VSlider; +import com.vaadin.v7.shared.ui.slider.SliderServerRpc; +import com.vaadin.v7.shared.ui.slider.SliderState; + +@Connect(com.vaadin.v7.ui.Slider.class) +public class SliderConnector extends AbstractFieldConnector + implements ValueChangeHandler<Double> { + + protected SliderServerRpc rpc = RpcProxy.create(SliderServerRpc.class, + this); + + private final ElementResizeListener resizeListener = new ElementResizeListener() { + + @Override + public void onElementResize(ElementResizeEvent e) { + getWidget().iLayout(); + } + }; + + @Override + public void init() { + super.init(); + getWidget().setConnection(getConnection()); + getWidget().addValueChangeHandler(this); + + getLayoutManager().addElementResizeListener(getWidget().getElement(), + resizeListener); + } + + @Override + public void onUnregister() { + super.onUnregister(); + getLayoutManager().removeElementResizeListener(getWidget().getElement(), + resizeListener); + } + + @Override + public VSlider getWidget() { + return (VSlider) super.getWidget(); + } + + @Override + public SliderState getState() { + return (SliderState) super.getState(); + } + + @Override + public void onValueChange(ValueChangeEvent<Double> event) { + getState().value = event.getValue(); + rpc.valueChanged(event.getValue()); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + getWidget().setId(getConnectorId()); + getWidget().setImmediate(getState().immediate); + getWidget().setDisabled(!isEnabled()); + getWidget().setReadOnly(isReadOnly()); + getWidget().setOrientation(getState().orientation); + getWidget().setMinValue(getState().minValue); + getWidget().setMaxValue(getState().maxValue); + getWidget().setResolution(getState().resolution); + getWidget().setValue(getState().value, false); + + getWidget().buildBase(); + getWidget().setTabIndex(getState().tabIndex); + } + +} diff --git a/compatibility-server/src/main/java/com/vaadin/v7/ui/Slider.java b/compatibility-server/src/main/java/com/vaadin/v7/ui/Slider.java new file mode 100644 index 0000000000..81f17e0b79 --- /dev/null +++ b/compatibility-server/src/main/java/com/vaadin/v7/ui/Slider.java @@ -0,0 +1,399 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * 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 com.vaadin.v7.ui; + +import java.util.Collection; + +import org.jsoup.nodes.Attributes; +import org.jsoup.nodes.Element; + +import com.vaadin.ui.declarative.DesignAttributeHandler; +import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.v7.shared.ui.slider.SliderOrientation; +import com.vaadin.v7.shared.ui.slider.SliderServerRpc; +import com.vaadin.v7.shared.ui.slider.SliderState;; + +/** + * A component for selecting a numerical value within a range. + * + * @author Vaadin Ltd. + */ +@Deprecated +public class Slider extends AbstractField<Double> { + + private SliderServerRpc rpc = new SliderServerRpc() { + + @Override + public void valueChanged(double value) { + + /* + * Client side updates the state before sending the event so we need + * to make sure the cached state is updated to match the client. If + * we do not do this, a reverting setValue() call in a listener will + * not cause the new state to be sent to the client. + * + * See #12133. + */ + getUI().getConnectorTracker().getDiffState(Slider.this).put("value", + value); + + try { + setValue(value, true); + } catch (final ValueOutOfBoundsException e) { + // Convert to nearest bound + double out = e.getValue().doubleValue(); + if (out < getState().minValue) { + out = getState().minValue; + } + if (out > getState().maxValue) { + out = getState().maxValue; + } + Slider.super.setValue(new Double(out), false); + } + } + + }; + + /** + * Default slider constructor. Sets all values to defaults and the slide + * handle at minimum value. + * + */ + public Slider() { + super(); + registerRpc(rpc); + super.setValue(new Double(getState().minValue)); + } + + /** + * Create a new slider with the caption given as parameter. + * + * The range of the slider is set to 0-100 and only integer values are + * allowed. + * + * @param caption + * The caption for this slider (e.g. "Volume"). + */ + public Slider(String caption) { + this(); + setCaption(caption); + } + + /** + * Create a new slider with the given range and resolution. + * + * @param min + * The minimum value of the slider + * @param max + * The maximum value of the slider + * @param resolution + * The number of digits after the decimal point. + */ + public Slider(double min, double max, int resolution) { + this(); + setResolution(resolution); + setMax(max); + setMin(min); + } + + /** + * Create a new slider with the given range that only allows integer values. + * + * @param min + * The minimum value of the slider + * @param max + * The maximum value of the slider + */ + public Slider(int min, int max) { + this(); + setMin(min); + setMax(max); + setResolution(0); + } + + /** + * Create a new slider with the given caption and range that only allows + * integer values. + * + * @param caption + * The caption for the slider + * @param min + * The minimum value of the slider + * @param max + * The maximum value of the slider + */ + public Slider(String caption, int min, int max) { + this(min, max); + setCaption(caption); + } + + @Override + public SliderState getState() { + return (SliderState) super.getState(); + } + + @Override + public SliderState getState(boolean markAsDirty) { + return (SliderState) super.getState(markAsDirty); + } + + /** + * Gets the maximum slider value + * + * @return the largest value the slider can have + */ + public double getMax() { + return getState(false).maxValue; + } + + /** + * Set the maximum slider value. If the current value of the slider is + * larger than this, the value is set to the new maximum. + * + * @param max + * The new maximum slider value + */ + public void setMax(double max) { + double roundedMax = getRoundedValue(max); + getState().maxValue = roundedMax; + + if (getMin() > roundedMax) { + getState().minValue = roundedMax; + } + + if (getValue() > roundedMax) { + setValue(roundedMax); + } + } + + /** + * Gets the minimum slider value + * + * @return the smallest value the slider can have + */ + public double getMin() { + return getState(false).minValue; + } + + /** + * Set the minimum slider value. If the current value of the slider is + * smaller than this, the value is set to the new minimum. + * + * @param min + * The new minimum slider value + */ + public void setMin(double min) { + double roundedMin = getRoundedValue(min); + getState().minValue = roundedMin; + + if (getMax() < roundedMin) { + getState().maxValue = roundedMin; + } + + if (getValue() < roundedMin) { + setValue(roundedMin); + } + } + + /** + * Get the current orientation of the slider (horizontal or vertical). + * + * @return {@link SliderOrientation#HORIZONTAL} or + * {@link SliderOrientation#VERTICAL} + */ + public SliderOrientation getOrientation() { + return getState(false).orientation; + } + + /** + * Set the orientation of the slider. + * + * @param orientation + * The new orientation, either + * {@link SliderOrientation#HORIZONTAL} or + * {@link SliderOrientation#VERTICAL} + */ + public void setOrientation(SliderOrientation orientation) { + getState().orientation = orientation; + } + + /** + * Get the current resolution of the slider. The resolution is the number of + * digits after the decimal point. + * + * @return resolution + */ + public int getResolution() { + return getState(false).resolution; + } + + /** + * Set a new resolution for the slider. The resolution is the number of + * digits after the decimal point. + * + * @throws IllegalArgumentException + * if resolution is negative. + * + * @param resolution + */ + public void setResolution(int resolution) { + if (resolution < 0) { + throw new IllegalArgumentException( + "Cannot set a negative resolution to Slider"); + } + getState().resolution = resolution; + } + + /** + * Sets the value of the slider. + * + * @param value + * The new value of the slider. + * @param repaintIsNotNeeded + * If true, client-side is not requested to repaint itself. + * @throws ValueOutOfBoundsException + * If the given value is not inside the range of the slider. + * @see #setMin(double) {@link #setMax(double)} + */ + @Override + protected void setValue(Double value, boolean repaintIsNotNeeded) { + double newValue = getRoundedValue(value); + + if (getMin() > newValue || getMax() < newValue) { + throw new ValueOutOfBoundsException(newValue); + } + + getState().value = newValue; + super.setValue(newValue, repaintIsNotNeeded); + } + + private double getRoundedValue(Double value) { + final double v = value.doubleValue(); + final int resolution = getResolution(); + + double ratio = Math.pow(10, resolution); + if (v >= 0) { + return Math.floor(v * ratio) / ratio; + } else { + return Math.ceil(v * ratio) / ratio; + } + } + + @Override + public void setValue(Double newFieldValue) { + super.setValue(newFieldValue); + getState().value = newFieldValue; + } + + /* + * Overridden to keep the shared state in sync with the AbstractField + * internal value. Should be removed once AbstractField is refactored to use + * shared state. + * + * See tickets #10921 and #11064. + */ + @Override + protected void setInternalValue(Double newValue) { + super.setInternalValue(newValue); + if (newValue == null) { + newValue = 0.0; + } + getState().value = newValue; + } + + /** + * Thrown when the value of the slider is about to be set to a value that is + * outside the valid range of the slider. + * + * @author Vaadin Ltd. + * + */ + public class ValueOutOfBoundsException extends RuntimeException { + + private final Double value; + + /** + * Constructs an <code>ValueOutOfBoundsException</code> with the + * specified detail message. + * + * @param valueOutOfBounds + */ + public ValueOutOfBoundsException(Double valueOutOfBounds) { + super(String.format("Value %s is out of bounds: [%s, %s]", + valueOutOfBounds, getMin(), getMax())); + value = valueOutOfBounds; + } + + /** + * Gets the value that is outside the valid range of the slider. + * + * @return the value that is out of bounds + */ + public Double getValue() { + return value; + } + } + + @Override + public Class<Double> getType() { + return Double.class; + } + + @Override + public void clear() { + super.setValue(Double.valueOf(getState().minValue)); + } + + @Override + public boolean isEmpty() { + // Slider is never really "empty" + return false; + } + + @Override + public void readDesign(Element design, DesignContext context) { + super.readDesign(design, context); + Attributes attr = design.attributes(); + if (attr.hasKey("vertical")) { + setOrientation(SliderOrientation.VERTICAL); + } + if (attr.hasKey("value")) { + Double newFieldValue = DesignAttributeHandler.readAttribute("value", + attr, Double.class); + setValue(newFieldValue, false, true); + } + } + + @Override + public void writeDesign(Element design, DesignContext context) { + super.writeDesign(design, context); + if (getOrientation() == SliderOrientation.VERTICAL) { + design.attr("vertical", true); + } + Slider defaultSlider = context.getDefaultInstance(this); + DesignAttributeHandler.writeAttribute(this, "value", + design.attributes(), defaultSlider); + } + + @Override + protected Collection<String> getCustomAttributes() { + Collection<String> result = super.getCustomAttributes(); + result.add("orientation"); + result.add("vertical"); + return result; + } +} diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/component/slider/SliderDeclarativeTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/component/slider/SliderDeclarativeTest.java new file mode 100644 index 0000000000..dcfb415810 --- /dev/null +++ b/compatibility-server/src/test/java/com/vaadin/tests/server/component/slider/SliderDeclarativeTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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 com.vaadin.tests.server.component.slider; + +import org.junit.Test; + +import com.vaadin.shared.ui.slider.SliderOrientation; +import com.vaadin.tests.design.DeclarativeTestBase; +import com.vaadin.ui.Slider; + +/** + * Tests declarative support for implementations of {@link Slider}. + * + * @since + * @author Vaadin Ltd + */ +public class SliderDeclarativeTest extends DeclarativeTestBase<Slider> { + + @Test + public void testDefault() { + String design = "<vaadin-slider>"; + + Slider expected = new Slider(); + + testRead(design, expected); + testWrite(design, expected); + } + + @Test + public void testHorizontal() { + String design = "<vaadin-slider min=10 max=20 resolution=1 value=12.3>"; + + Slider expected = new Slider(); + expected.setMin(10.0); + expected.setMax(20.0); + expected.setResolution(1); + expected.setValue(12.3); + + testRead(design, expected); + testWrite(design, expected); + } + + @Test + public void testVertical() { + String design = "<vaadin-slider vertical>"; + + Slider expected = new Slider(); + expected.setOrientation(SliderOrientation.VERTICAL); + + testRead(design, expected); + testWrite(design, expected); + } + + @Test + public void testReadOnlyValue() { + String design = "<vaadin-slider readonly min=10 max=20 resolution=1 value=12.3>"; + + Slider expected = new Slider(); + expected.setMin(10.0); + expected.setMax(20.0); + expected.setResolution(1); + expected.setValue(12.3); + expected.setReadOnly(true); + + testRead(design, expected); + testWrite(design, expected); + } +} diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/component/slider/SliderTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/component/slider/SliderTest.java new file mode 100644 index 0000000000..8c093fdf72 --- /dev/null +++ b/compatibility-server/src/test/java/com/vaadin/tests/server/component/slider/SliderTest.java @@ -0,0 +1,135 @@ +package com.vaadin.tests.server.component.slider; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.ui.Slider; + +public class SliderTest { + + @Test + public void minCannotBeLargerThanMax() { + Slider slider = new Slider(); + + slider.setMax(100); + slider.setMin(101); + + assertThat(slider.getMin(), is(101.0)); + assertThat(slider.getMax(), is(101.0)); + } + + @Test + public void maxCannotBeSmallerThanMin() { + Slider slider = new Slider(); + + slider.setMin(50); + slider.setMax(10); + + assertThat(slider.getMax(), is(10.0)); + assertThat(slider.getMin(), is(10.0)); + } + + @Test + public void valueOutOfBoundsExceptionMessageContainsBounds() { + Slider slider = new Slider(); + + try { + + slider.setValue(-1.0); + } catch (Slider.ValueOutOfBoundsException e) { + assertThat(e.getMessage(), + containsString("Value -1.0 is out of bounds: [0.0, 100.0]")); + } + } + + @Test + public void valueIsSet() { + Slider slider = new Slider(); + + slider.setValue(5.0); + + assertThat(slider.getValue(), is(5.0)); + } + + @Test + public void valueCannotBeOutOfBounds() { + Slider s = new Slider(0, 10); + + try { + s.setValue(20.0); + Assert.fail("Should throw out of bounds exception"); + } catch (Slider.ValueOutOfBoundsException e) { + // TODO: handle exception + } + } + + @Test + public void valueCanHaveLargePrecision() { + Slider slider = new Slider(); + slider.setResolution(20); + + slider.setValue(99.01234567891234567890123456789); + + assertThat(slider.getValue(), is(99.01234567891234567890123456789)); + } + + @Test + public void doublesCanBeUsedAsLimits() { + Slider slider = new Slider(1.5, 2.5, 1); + + assertThat(slider.getMin(), is(1.5)); + assertThat(slider.getValue(), is(1.5)); + assertThat(slider.getMax(), is(2.5)); + } + + @Test + public void valuesGreaterThanIntMaxValueCanBeUsed() { + double minValue = (double) Integer.MAX_VALUE + 1; + + Slider s = new Slider(minValue, minValue + 1, 0); + + assertThat(s.getValue(), is(minValue)); + } + + @Test + public void negativeValuesCanBeUsed() { + Slider slider = new Slider(-0.7, 1.0, 0); + + slider.setValue(-0.4); + + assertThat(slider.getValue(), is(-0.0)); + } + + @Test + public void boundariesAreRounded() { + Slider slider = new Slider(1.5, 2.5, 0); + + slider.setValue(1.0); + + assertThat(slider.getValue(), is(1.0)); + assertThat(slider.getMin(), is(1.0)); + assertThat(slider.getMax(), is(2.0)); + } + + @Test + public void valueWithSmallerPrecisionCanBeUsed() { + Slider slider = new Slider(0, 100, 10); + + slider.setValue(1.2); + + assertThat(slider.getValue(), is(1.2)); + } + + @Test + public void valueWithLargerPrecisionCanBeUsed() { + Slider slider = new Slider(0, 100, 2); + + slider.setValue(1.2345); + + assertThat(slider.getValue(), is(1.23)); + } +} diff --git a/compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/slider/SliderOrientation.java b/compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/slider/SliderOrientation.java new file mode 100644 index 0000000000..a2bc62b25b --- /dev/null +++ b/compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/slider/SliderOrientation.java @@ -0,0 +1,20 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * 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 com.vaadin.v7.shared.ui.slider; + +public enum SliderOrientation { + HORIZONTAL, VERTICAL; +} diff --git a/compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/slider/SliderServerRpc.java b/compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/slider/SliderServerRpc.java new file mode 100644 index 0000000000..a7ec28f1ee --- /dev/null +++ b/compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/slider/SliderServerRpc.java @@ -0,0 +1,29 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * 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 com.vaadin.v7.shared.ui.slider; + +import com.vaadin.shared.communication.ServerRpc; + +public interface SliderServerRpc extends ServerRpc { + + /** + * Invoked when the value of a variable has changed. Slider listeners are + * notified if the slider value has changed. + * + * @param value + */ + public void valueChanged(double value); +} diff --git a/compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/slider/SliderState.java b/compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/slider/SliderState.java new file mode 100644 index 0000000000..bccee93e67 --- /dev/null +++ b/compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/slider/SliderState.java @@ -0,0 +1,43 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * 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 com.vaadin.v7.shared.ui.slider; + +import com.vaadin.shared.annotations.NoLayout; +import com.vaadin.v7.shared.AbstractFieldState; + +public class SliderState extends AbstractFieldState { + { + primaryStyleName = "v-slider"; + } + + @NoLayout + public double value; + + @NoLayout + public double maxValue = 100; + @NoLayout + public double minValue = 0; + + /** + * The number of fractional digits that are considered significant. Must be + * non-negative. + */ + @NoLayout + public int resolution = 0; + + public SliderOrientation orientation = SliderOrientation.HORIZONTAL; + +} diff --git a/uitest/src/main/java/com/vaadin/v7/tests/components/slider/SliderFeedback.java b/uitest/src/main/java/com/vaadin/v7/tests/components/slider/SliderFeedback.java new file mode 100644 index 0000000000..66b4cbf509 --- /dev/null +++ b/uitest/src/main/java/com/vaadin/v7/tests/components/slider/SliderFeedback.java @@ -0,0 +1,43 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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 com.vaadin.v7.tests.components.slider; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Slider; + +public class SliderFeedback extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + Slider slider = new Slider(0, 5); + slider.setWidth(800, Unit.PIXELS); + slider.setMin(0); + slider.setMax(1e12); + addComponent(slider); + } + + @Override + protected String getTestDescription() { + return "Slider feedback popup should display the correct value"; + } + + @Override + protected Integer getTicketNumber() { + return 18192; + } + +} diff --git a/uitest/src/main/java/com/vaadin/v7/tests/components/slider/SliderTest.java b/uitest/src/main/java/com/vaadin/v7/tests/components/slider/SliderTest.java new file mode 100644 index 0000000000..305ce91fbe --- /dev/null +++ b/uitest/src/main/java/com/vaadin/v7/tests/components/slider/SliderTest.java @@ -0,0 +1,79 @@ +package com.vaadin.v7.tests.components.slider; + +import java.util.LinkedHashMap; + +import com.vaadin.tests.components.abstractfield.LegacyAbstractFieldTest; +import com.vaadin.v7.shared.ui.slider.SliderOrientation; +import com.vaadin.v7.ui.Slider; + +public class SliderTest extends LegacyAbstractFieldTest<Slider> { + + private Command<Slider, Double> minCommand = new Command<Slider, Double>() { + @Override + public void execute(Slider c, Double value, Object data) { + c.setMin(value); + } + }; + + private Command<Slider, Double> maxCommand = new Command<Slider, Double>() { + @Override + public void execute(Slider c, Double value, Object data) { + c.setMax(value); + } + }; + + private Command<Slider, SliderOrientation> orientationCommand = new Command<Slider, SliderOrientation>() { + @Override + public void execute(Slider c, SliderOrientation value, Object data) { + c.setOrientation(value); + } + }; + private Command<Slider, Integer> resolutionCommand = new Command<Slider, Integer>() { + @Override + public void execute(Slider c, Integer value, Object data) { + c.setResolution(value); + } + }; + + @Override + protected Class<Slider> getTestClass() { + return Slider.class; + } + + @Override + protected void createActions() { + super.createActions(); + + createMinSelect(CATEGORY_FEATURES); + createMaxSelect(CATEGORY_FEATURES); + createResolutionSelect(CATEGORY_FEATURES); + createOrientationSelect(CATEGORY_FEATURES); + } + + private void createResolutionSelect(String category) { + createSelectAction("Resolution", category, createIntegerOptions(10), + "1", resolutionCommand); + + } + + private void createOrientationSelect(String category) { + LinkedHashMap<String, SliderOrientation> options = new LinkedHashMap<String, SliderOrientation>(); + options.put("Horizontal", SliderOrientation.HORIZONTAL); + options.put("Vertical", SliderOrientation.VERTICAL); + createSelectAction("Orientation", category, options, "Horizontal", + orientationCommand); + + } + + private void createMaxSelect(String category) { + createSelectAction("Max", category, createDoubleOptions(100), "0", + maxCommand); + } + + private void createMinSelect(String category) { + createSelectAction("Min", category, createDoubleOptions(100), "0", + minCommand); + + } + +} diff --git a/uitest/src/test/java/com/vaadin/v7/tests/components/slider/SliderFeedbackTest.java b/uitest/src/test/java/com/vaadin/v7/tests/components/slider/SliderFeedbackTest.java new file mode 100644 index 0000000000..4dd556fc27 --- /dev/null +++ b/uitest/src/test/java/com/vaadin/v7/tests/components/slider/SliderFeedbackTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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 com.vaadin.v7.tests.components.slider; + +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.interactions.Actions; + +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class SliderFeedbackTest extends MultiBrowserTest { + + @Test + public void testValueGreaterThanMaxInt() { + openTestURL(); + + WebElement handle = findElement(By.className("v-slider-handle")); + new Actions(driver).dragAndDropBy(handle, 400, 0).perform(); + testBench().waitForVaadin(); + + double value = Double.valueOf( + findElement(By.className("v-slider-feedback")).getText()); + + // Allow for some tolerance due to, you guessed it, IE8 + assertLessThan("Unexpected feedback value {1} < {0}", 505000000000.0, + value); + assertGreater("Unexpected feedback value {1} > {0}", 510000000000.0, + value); + } +} |