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