1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package com.vaadin.client.ui;
17
18 import com.google.gwt.core.client.Scheduler;
19 import com.google.gwt.dom.client.Element;
20 import com.google.gwt.dom.client.Style.Display;
21 import com.google.gwt.dom.client.Style.Overflow;
22 import com.google.gwt.dom.client.Style.Unit;
23 import com.google.gwt.event.dom.client.KeyCodes;
24 import com.google.gwt.event.logical.shared.ValueChangeEvent;
25 import com.google.gwt.event.logical.shared.ValueChangeHandler;
26 import com.google.gwt.event.shared.HandlerRegistration;
27 import com.google.gwt.user.client.Command;
28 import com.google.gwt.user.client.DOM;
29 import com.google.gwt.user.client.Event;
30 import com.google.gwt.user.client.Window;
31 import com.google.gwt.user.client.ui.HasValue;
32 import com.vaadin.client.ApplicationConnection;
33 import com.vaadin.client.BrowserInfo;
34 import com.vaadin.client.WidgetUtil;
35 import com.vaadin.shared.ui.slider.SliderOrientation;
36
37 public class VSliderPatched extends SimpleFocusablePanel
38 implements Field, HasValue<Double>, SubPartAware {
39
40 public static final String CLASSNAME = "v-slider";
41
42
43
44
45
46 private static final int MIN_SIZE = 50;
47
48 protected ApplicationConnection client;
49
50 protected String id;
51
52 protected boolean disabled;
53 protected boolean readonly;
54
55 private int acceleration = 1;
56 protected double min;
57 protected double max;
58 protected int resolution;
59 protected Double value;
60 protected SliderOrientation orientation = SliderOrientation.HORIZONTAL;
61
62
63 private final Element base;
64 private static final int BASE_BORDER_WIDTH = 1;
65
66
67 private final Element handle;
68
69
70 private final Element smaller;
71
72
73 private final Element bigger;
74
75
76 private boolean dragging = false;
77
78 private VLazyExecutor delayedValueUpdater = new VLazyExecutor(100, () -> {
79 fireValueChanged();
80 acceleration = 1;
81 });
82
83 public VSliderPatched() {
84 super();
85
86 base = DOM.createDiv();
87 handle = DOM.createDiv();
88 smaller = DOM.createDiv();
89 bigger = DOM.createDiv();
90
91 setStyleName(CLASSNAME);
92
93 getElement().appendChild(bigger);
94 getElement().appendChild(smaller);
95 getElement().appendChild(base);
96 base.appendChild(handle);
97
98
99 smaller.getStyle().setDisplay(Display.NONE);
100 bigger.getStyle().setDisplay(Display.NONE);
101
102 sinkEvents(Event.MOUSEEVENTS | Event.ONMOUSEWHEEL | Event.KEYEVENTS
103 | Event.FOCUSEVENTS | Event.TOUCHEVENTS);
104 }
105
106 @Override
107 public void setStyleName(String style) {
108 updateStyleNames(style, false);
109 }
110
111 @Override
112 public void setStylePrimaryName(String style) {
113 updateStyleNames(style, true);
114 }
115
116 protected void updateStyleNames(String styleName,
117 boolean isPrimaryStyleName) {
118
119 removeStyleName(getStylePrimaryName() + "-vertical");
120
121 if (isPrimaryStyleName) {
122 super.setStylePrimaryName(styleName);
123 } else {
124 super.setStyleName(styleName);
125 }
126
127 base.setClassName(getStylePrimaryName() + "-base");
128 handle.setClassName(getStylePrimaryName() + "-handle");
129 smaller.setClassName(getStylePrimaryName() + "-smaller");
130 bigger.setClassName(getStylePrimaryName() + "-bigger");
131
132 if (isVertical()) {
133 addStyleName(getStylePrimaryName() + "-vertical");
134 }
135 }
136
137
138 public void buildBase() {
139 final String styleAttribute = isVertical() ? "height" : "width";
140 final String oppositeStyleAttribute = isVertical() ? "width" : "height";
141 final String domProperty = isVertical() ? "offsetHeight"
142 : "offsetWidth";
143
144
145 base.getStyle().clearProperty(oppositeStyleAttribute);
146
147
148
149
150
151
152
153
154
155
156 if (getElement().hasParentElement()) {
157 final Element p = getElement();
158 if (p.getPropertyInt(domProperty) > MIN_SIZE) {
159 if (isVertical()) {
160 setHeight();
161 } else {
162 base.getStyle().clearProperty(styleAttribute);
163 }
164 } else {
165
166
167 base.getStyle().setPropertyPx(styleAttribute, MIN_SIZE);
168 Scheduler.get().scheduleDeferred(new Command() {
169
170 @Override
171 public void execute() {
172 final Element p = getElement();
173 if (p.getPropertyInt(domProperty) > MIN_SIZE + 5
174 || propertyNotNullOrEmpty(styleAttribute, p)) {
175 if (isVertical()) {
176 setHeight();
177 } else {
178 base.getStyle().clearProperty(styleAttribute);
179 }
180
181 setValue(value, false);
182 }
183 }
184
185
186 private boolean propertyNotNullOrEmpty(
187 final String styleAttribute, final Element p) {
188 return p.getStyle().getProperty(styleAttribute) != null
189 && !p.getStyle().getProperty(styleAttribute)
190 .isEmpty();
191 }
192 });
193 }
194 }
195
196 if (!isVertical()) {
197
198 Scheduler.get().scheduleDeferred(() -> {
199 buildHandle();
200 setValue(value, false);
201 });
202 } else {
203 buildHandle();
204 setValue(value, false);
205 }
206
207
208 }
209
210 void buildHandle() {
211 final String handleAttribute = isVertical() ? "marginTop"
212 : "marginLeft";
213 final String oppositeHandleAttribute = isVertical() ? "marginLeft"
214 : "marginTop";
215
216 handle.getStyle().setProperty(handleAttribute, "0");
217
218
219 handle.getStyle().clearProperty(oppositeHandleAttribute);
220 }
221
222 @Override
223 public void onBrowserEvent(Event event) {
224 if (disabled || readonly) {
225 return;
226 }
227 final Element targ = DOM.eventGetTarget(event);
228 final Element slider = getElement();
229
230 if (DOM.eventGetType(event) == Event.ONMOUSEWHEEL) {
231 processMouseWheelEvent(event);
232 } else if (dragging || targ == handle || targ == base || targ == slider) {
233 processHandleEvent(event);
234 } else if (targ == smaller) {
235 decreaseValue(true);
236 } else if (targ == bigger) {
237 increaseValue(true);
238 } else if (DOM.eventGetType(event) == Event.MOUSEEVENTS) {
239 processBaseEvent(event);
240 } else if (BrowserInfo.get().isGecko()
241 && DOM.eventGetType(event) == Event.ONKEYPRESS
242 || !BrowserInfo.get().isGecko()
243 && DOM.eventGetType(event) == Event.ONKEYDOWN) {
244
245 if (handleNavigation(event.getKeyCode(), event.getCtrlKey(),
246 event.getShiftKey())) {
247
248 delayedValueUpdater.trigger();
249
250 DOM.eventPreventDefault(event);
251 DOM.eventCancelBubble(event, true);
252 }
253 } else if (targ.equals(getElement())
254 && DOM.eventGetType(event) == Event.ONFOCUS) {
255 } else if (targ.equals(getElement())
256 && DOM.eventGetType(event) == Event.ONBLUR) {
257 } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) {
258 }
259 if (WidgetUtil.isTouchEvent(event)) {
260 event.preventDefault();
261 event.stopPropagation();
262 }
263 }
264
265 private void processMouseWheelEvent(final Event event) {
266 final int dir = DOM.eventGetMouseWheelVelocityY(event);
267
268 if (dir < 0) {
269 increaseValue(false);
270 } else {
271 decreaseValue(false);
272 }
273
274 delayedValueUpdater.trigger();
275
276 DOM.eventPreventDefault(event);
277 DOM.eventCancelBubble(event, true);
278 }
279
280 private void processHandleEvent(Event event) {
281 switch (DOM.eventGetType(event)) {
282 case Event.ONMOUSEDOWN:
283 case Event.ONTOUCHSTART:
284 if (!disabled && !readonly) {
285 focus();
286 dragging = true;
287 handle.setClassName(getStylePrimaryName() + "-handle");
288 handle.addClassName(getStylePrimaryName() + "-handle-active");
289
290 DOM.setCapture(getElement());
291 DOM.eventPreventDefault(event);
292 DOM.eventCancelBubble(event, true);
293 setValueByEvent(event, true);
294 event.stopPropagation();
295 }
296 break;
297 case Event.ONMOUSEMOVE:
298 case Event.ONTOUCHMOVE:
299 if (dragging) {
300 setValueByEvent(event, true);
301 event.stopPropagation();
302 }
303 break;
304 case Event.ONTOUCHEND:
305 case Event.ONMOUSEUP:
306 dragging = false;
307 handle.setClassName(getStylePrimaryName() + "-handle");
308 DOM.releaseCapture(getElement());
309 setValueByEvent(event, true);
310 event.stopPropagation();
311 break;
312 default:
313 break;
314 }
315 }
316
317 private void processBaseEvent(Event event) {
318 if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) {
319 if (!disabled && !readonly && !dragging) {
320 setValueByEvent(event, true);
321 DOM.eventCancelBubble(event, true);
322 }
323 }
324 }
325
326 private void decreaseValue(boolean updateToServer) {
327 setValue(new Double(value.doubleValue() - Math.pow(10, -resolution)),
328 updateToServer);
329 }
330
331 private void increaseValue(boolean updateToServer) {
332 setValue(new Double(value.doubleValue() + Math.pow(10, -resolution)),
333 updateToServer);
334 }
335
336 private void setValueByEvent(Event event, boolean updateToServer) {
337 double v = min;
338
339 final int coord = getEventPosition(event);
340
341 final int handleSize, baseSize, baseOffset;
342 if (isVertical()) {
343 handleSize = handle.getOffsetHeight();
344 baseSize = base.getOffsetHeight();
345 baseOffset = base.getAbsoluteTop() - Window.getScrollTop()
346 - handleSize / 2;
347 } else {
348 handleSize = handle.getOffsetWidth();
349 baseSize = base.getOffsetWidth();
350 baseOffset = base.getAbsoluteLeft() - Window.getScrollLeft()
351 + handleSize / 2;
352 }
353
354 if (isVertical()) {
355 v = (baseSize - (coord - baseOffset))
356 / (double) (baseSize - handleSize) * (max - min) + min;
357 } else {
358 v = (coord - baseOffset) / (double) (baseSize - handleSize)
359 * (max - min) + min;
360 }
361
362 if (v < min) {
363 v = min;
364 } else if (v > max) {
365 v = max;
366 }
367
368 setValue(v, updateToServer);
369 }
370
371
372
373
374
375
376
377
378 protected int getEventPosition(Event event) {
379 if (isVertical()) {
380 return WidgetUtil.getTouchOrMouseClientY(event);
381 } else {
382 return WidgetUtil.getTouchOrMouseClientX(event);
383 }
384 }
385
386 public void iLayout() {
387 if (isVertical()) {
388 setHeight();
389 }
390
391 setValue(value, false);
392 }
393
394 private void setHeight() {
395
396 base.getStyle().setHeight(0, Unit.PX);
397 base.getStyle().setOverflow(Overflow.HIDDEN);
398 int h = getElement().getOffsetHeight();
399 if (h < MIN_SIZE) {
400 h = MIN_SIZE;
401 }
402 base.getStyle().setHeight(h, Unit.PX);
403 base.getStyle().clearOverflow();
404 }
405
406 private void fireValueChanged() {
407 ValueChangeEvent.fire(VSliderPatched.this, value);
408 }
409
410
411
412
413
414
415
416
417
418
419
420
421 public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
422
423
424 if (ctrl) {
425 return false;
426 }
427
428 if (keycode == getNavigationUpKey() && isVertical()
429 || keycode == getNavigationRightKey() && !isVertical()) {
430 if (shift) {
431 for (int a = 0; a < acceleration; a++) {
432 increaseValue(false);
433 }
434 acceleration++;
435 } else {
436 increaseValue(false);
437 }
438 return true;
439 } else if (keycode == getNavigationDownKey() && isVertical()
440 || keycode == getNavigationLeftKey() && !isVertical()) {
441 if (shift) {
442 for (int a = 0; a < acceleration; a++) {
443 decreaseValue(false);
444 }
445 acceleration++;
446 } else {
447 decreaseValue(false);
448 }
449 return true;
450 }
451
452 return false;
453 }
454
455
456
457
458
459
460
461
462 protected int getNavigationUpKey() {
463 return KeyCodes.KEY_UP;
464 }
465
466
467
468
469
470
471
472
473 protected int getNavigationDownKey() {
474 return KeyCodes.KEY_DOWN;
475 }
476
477
478
479
480
481
482
483
484 protected int getNavigationLeftKey() {
485 return KeyCodes.KEY_LEFT;
486 }
487
488
489
490
491
492
493
494
495 protected int getNavigationRightKey() {
496 return KeyCodes.KEY_RIGHT;
497 }
498
499 public void setConnection(ApplicationConnection client) {
500 this.client = client;
501 }
502
503 public void setId(String id) {
504 this.id = id;
505 }
506
507 public void setDisabled(boolean disabled) {
508 this.disabled = disabled;
509 }
510
511 public void setReadOnly(boolean readonly) {
512 this.readonly = readonly;
513 }
514
515 private boolean isVertical() {
516 return orientation == SliderOrientation.VERTICAL;
517 }
518
519 public void setOrientation(SliderOrientation orientation) {
520 if (this.orientation != orientation) {
521 this.orientation = orientation;
522 updateStyleNames(getStylePrimaryName(), true);
523 }
524 }
525
526 public void setMinValue(double value) {
527 min = value;
528 }
529
530 public void setMaxValue(double value) {
531 max = value;
532 }
533
534 public void setResolution(int resolution) {
535 this.resolution = resolution;
536 }
537
538 @Override
539 public HandlerRegistration addValueChangeHandler(
540 ValueChangeHandler<Double> handler) {
541 return addHandler(handler, ValueChangeEvent.getType());
542 }
543
544 @Override
545 public Double getValue() {
546 return value;
547 }
548
549 @Override
550 public void setValue(Double value) {
551 if (value < min) {
552 value = min;
553 } else if (value > max) {
554 value = max;
555 }
556
557
558 final String styleAttribute = isVertical() ? "marginTop" : "marginLeft";
559 final String domProperty = isVertical() ? "offsetHeight"
560 : "offsetWidth";
561 final int handleSize = handle.getPropertyInt(domProperty);
562 final int baseSize = base.getPropertyInt(domProperty)
563 - 2 * BASE_BORDER_WIDTH;
564
565 final int range = baseSize - handleSize;
566 double v = value.doubleValue();
567
568
569 if (resolution > 0) {
570 v = Math.round(v * Math.pow(10, resolution));
571 v = v / Math.pow(10, resolution);
572 } else {
573 v = Math.round(v);
574 }
575 final double valueRange = max - min;
576 double p = 0;
577 if (valueRange > 0) {
578 p = range * ((v - min) / valueRange);
579 }
580 if (p < 0) {
581 p = 0;
582 }
583 if (isVertical()) {
584 p = range - p;
585 }
586 final double pos = p;
587
588 handle.getStyle().setPropertyPx(styleAttribute, (int) Math.round(pos));
589
590
591 this.value = new Double(v);
592 }
593
594 @Override
595 public void setValue(Double value, boolean fireEvents) {
596 if (value == null) {
597 return;
598 }
599
600 setValue(value);
601
602 if (fireEvents) {
603 fireValueChanged();
604 }
605 }
606
607 @Override
608 public com.google.gwt.user.client.Element getSubPartElement(
609 String subPart) {
610 return null;
611 }
612
613 @Override
614 public String getSubPartName(
615 com.google.gwt.user.client.Element subElement) {
616 return null;
617 }
618 }