1 /*
2  * Copyright (C) 2008, 2011 Emweb bv, Herent, Belgium.
3  *
4  * See the LICENSE file for terms of use.
5  */
6 #include "Wt/WApplication.h"
7 #include "Wt/WContainerWidget.h"
8 #include "Wt/WEnvironment.h"
9 #include "Wt/WPaintDevice.h"
10 #include "Wt/WPaintedWidget.h"
11 #include "Wt/WPainter.h"
12 #include "Wt/WSlider.h"
13 #include "Wt/WStringStream.h"
14 #include "Wt/WBootstrap5Theme.h"
15 
16 #include "DomElement.h"
17 #include "WebUtils.h"
18 
19 #include <memory>
20 
21 /*
22  * FIXME: move styling to the theme classes
23  */
24 namespace Wt {
25 
26 const Wt::WFlags<WSlider::TickPosition> WSlider::NoTicks = None;
27 const Wt::WFlags<WSlider::TickPosition> WSlider::TicksBothSides
28   = WSlider::TickPosition::TicksAbove | WSlider::TickPosition::TicksBelow;
29 
30 const char *WSlider::INPUT_SIGNAL = "input";
31 
32 class PaintedSlider final : public WPaintedWidget
33 {
34 public:
35   PaintedSlider(WSlider *slider);
36   virtual ~PaintedSlider();
37 
38   void connectSlots();
39   void updateState();
40   void updateSliderPosition();
41   void doUpdateDom(DomElement& element, bool all);
42 
43   void sliderResized(const WLength& width, const WLength& height);
44 
45 protected:
46   virtual void paintEvent(WPaintDevice *paintDevice) override;
47 
48 private:
49   WSlider *slider_;
50 
51   JSignal<int> sliderReleased_;
52   JSlot mouseDownJS_, mouseMovedJS_, mouseUpJS_;
53 
54   std::unique_ptr<WInteractWidget> handle_, fill_;
55 
range()56   int range() const { return slider_->maximum() - slider_->minimum(); }
57   double w() const;
58   double h() const;
59 
60   void onSliderClick(const WMouseEvent& event);
61   void onSliderReleased(int u);
62 };
63 
paintEvent(WPaintDevice * paintDevice)64 void PaintedSlider::paintEvent(WPaintDevice *paintDevice)
65 {
66   int tickInterval = slider_->tickInterval();
67   int r = range();
68 
69   if (r == 0) {
70     // Empty range, don't paint anything
71     return;
72   }
73 
74   if (tickInterval == 0)
75     tickInterval = r / 2;
76 
77   int numTicks = tickInterval == 0 ? 2 : r / tickInterval + 1;
78   if (numTicks < 1)
79     return;
80 
81   int w = 0, h = 0;
82 
83   switch (slider_->orientation()) {
84   case Orientation::Horizontal:
85     w = (int)paintDevice->width().toPixels();
86     h = (int)paintDevice->height().toPixels();
87     break;
88   case Orientation::Vertical:
89     w = (int)paintDevice->height().toPixels();
90     h = (int)paintDevice->width().toPixels();
91   }
92 
93   double tickStep = ((double)w + 10 - slider_->handleWidth()) / (numTicks - 1);
94 
95   WPainter painter(paintDevice);
96 
97   for (int i = 0; i < numTicks; ++i) {
98     int v = slider_->minimum() + i * tickInterval;
99     int x = -5 + slider_->handleWidth()/2 + (int) (i * tickStep);
100 
101     switch (slider_->orientation()) {
102     case Orientation::Horizontal:
103       slider_->paintTick(painter, v, x, h/2);
104       break;
105     case Orientation::Vertical:
106       slider_->paintTick(painter, v, h/2, w - x);
107     }
108   }
109 }
110 
PaintedSlider(WSlider * slider)111 PaintedSlider::PaintedSlider(WSlider *slider)
112   : WPaintedWidget(),
113     slider_(slider),
114     sliderReleased_(this, "released")
115 {
116   setStyleClass("Wt-slider-bg");
117 
118   slider_->addStyleClass
119     (std::string("Wt-slider-")
120      + (slider_->orientation() == Orientation::Horizontal ? "h" : "v"));
121 
122   if (slider_->positionScheme() == PositionScheme::Static) {
123     slider_->setPositionScheme(PositionScheme::Relative);
124     slider_->setOffsets(0, Side::Left | Side::Top);
125   }
126 
127   auto fill = std::unique_ptr<WInteractWidget>(new WContainerWidget());
128   manageWidget(fill_, std::move(fill));
129   auto handle = slider_->createHandle();
130   manageWidget(handle_, std::move(handle));
131 
132   fill_->setPositionScheme(PositionScheme::Absolute);
133   fill_->setStyleClass("fill");
134 
135   handle_->setPositionScheme(PositionScheme::Absolute);
136   handle_->setStyleClass("handle");
137 
138   handle_->setCanReceiveFocus(true);
139   slider_->setCanReceiveFocus(true);
140 
141   connectSlots();
142 }
143 
connectSlots()144 void PaintedSlider::connectSlots()
145 {
146   if (Wt::WApplication::instance()->environment().ajax()) {
147     handle_->mouseWentDown().connect(mouseDownJS_);
148     handle_->touchStarted().connect(mouseDownJS_);
149     handle_->mouseMoved().connect(mouseMovedJS_);
150     handle_->touchMoved().connect(mouseMovedJS_);
151     handle_->mouseWentUp().connect(mouseUpJS_);
152     handle_->touchEnded().connect(mouseUpJS_);
153 
154     slider_->clicked().connect(this, &PaintedSlider::onSliderClick);
155 
156     sliderReleased_.connect(this, &PaintedSlider::onSliderReleased);
157   }
158 }
159 
~PaintedSlider()160 PaintedSlider::~PaintedSlider()
161 {
162   manageWidget(fill_, std::unique_ptr<WInteractWidget>());
163   manageWidget(handle_, std::unique_ptr<WInteractWidget>());
164 }
165 
w()166 double PaintedSlider::w() const
167 {
168   return width().toPixels()
169     + (slider_->orientation() == Orientation::Horizontal ? 10 : 0);
170 }
171 
h()172 double PaintedSlider::h() const
173 {
174   return height().toPixels()
175     + (slider_->orientation() == Orientation::Vertical ? 10 : 0);
176 }
177 
updateState()178 void PaintedSlider::updateState()
179 {
180   bool rtl = WApplication::instance()->layoutDirection() ==
181     LayoutDirection::RightToLeft;
182 
183   Orientation o = slider_->orientation();
184 
185   if (o == Orientation::Horizontal) {
186     handle_->resize(slider_->handleWidth(), h());
187     handle_->setOffsets(0, Side::Top);
188   } else {
189     handle_->resize(w(), slider_->handleWidth());
190     handle_->setOffsets(0, Side::Left);
191   }
192 
193   double l = o == Orientation::Horizontal ? w() : h();
194   double pixelsPerUnit = (l - slider_->handleWidth()) / range();
195 
196   std::string dir;
197   std::string size;
198   if (o == Orientation::Horizontal) {
199     dir = rtl ? "right" : "left";
200     size = "width";
201   } else {
202     dir = "top";
203     size = "height";
204   }
205 
206   char u = (o == Orientation::Horizontal ? 'x' : 'y');
207 
208   double max = l - slider_->handleWidth();
209   bool horizontal = o == Orientation::Horizontal;
210 
211   char buf[30]; // Buffer for round_js_str
212 
213   /*
214    * Note: cancelling the mouseDown event prevents the selection behaviour
215    */
216   WStringStream mouseDownJS;
217   mouseDownJS << "obj.setAttribute('down', " WT_CLASS
218 	      <<                     ".widgetCoordinates(obj, event)." << u
219 	      <<                  ");";
220 
221   WStringStream computeD; // = 'u' position relative to background, corrected for slider
222   computeD << "var objh = " << handle_->jsRef() << ","
223 	   <<     "objf = " << fill_->jsRef() << ","
224 	   <<     "objb = " << slider_->jsRef() << ","
225 	   <<     "page_u = WT.pageCoordinates(event)." << u << ","
226 	   <<     "widget_page_u = WT.widgetPageCoordinates(objb)." << u << ","
227 	   <<     "pos = page_u - widget_page_u,"
228 	   <<     "rtl = " << rtl << ","
229 	   <<     "horizontal = " << horizontal << ";"
230 	   <<     "if (rtl && horizontal)";
231   computeD <<       "pos = " << Utils::round_js_str(l, 3, buf) << " - pos;";
232   computeD <<     "var d = pos - down;";
233 
234   WStringStream mouseMovedJS;
235   mouseMovedJS << "var down = obj.getAttribute('down');"
236 	       << "var WT = " WT_CLASS ";"
237 	       << "if (down != null && down != '') {"
238 	       <<    computeD.str();
239   mouseMovedJS <<   "d = Math.max(0, Math.min(d, " << Utils::round_js_str(max, 3, buf) << "));";
240   mouseMovedJS <<   "var v = Math.round(d/" << Utils::round_js_str(pixelsPerUnit, 3, buf) << ");";
241   mouseMovedJS <<   "var intd = v*" << Utils::round_js_str(pixelsPerUnit, 3, buf) << ";";
242   mouseMovedJS <<   "if (Math.abs(WT.pxself(objh, '" << dir
243 	       <<                 "') - intd) > 1) {"
244 	       <<     "objf.style." << size << " = ";
245   if (o == Orientation::Vertical) {
246     mouseMovedJS << '(' << Utils::round_js_str(max, 3, buf);
247     mouseMovedJS << " - intd + " << (slider_->handleWidth() / 2)
248 		 << ")";
249   } else
250     mouseMovedJS << "intd + " << (slider_->handleWidth() / 2);
251   mouseMovedJS <<       " + 'px';"
252 	       <<     "objh.style." << dir << " = intd + 'px';"
253 	       <<     "var vs = ";
254   if (o == Orientation::Horizontal)
255     mouseMovedJS << "v + " << slider_->minimum();
256   else
257     mouseMovedJS << slider_->maximum() << " - v";
258   mouseMovedJS <<     ";"
259 	       <<     "var f = objb.onValueChange;"
260 	       <<     "if (f) f(vs);";
261 
262   if (slider_->sliderMoved().needsUpdate(true)) {
263 #ifndef WT_TARGET_JAVA
264     mouseMovedJS << slider_->sliderMoved().createCall({"vs"});
265 #else
266     mouseMovedJS << slider_->sliderMoved().createCall("vs");
267 #endif
268   }
269 
270   mouseMovedJS <<   "}"
271 	       << "}";
272 
273   WStringStream mouseUpJS;
274   mouseUpJS << "var down = obj.getAttribute('down');"
275 	    << "var WT = " WT_CLASS ";"
276 	    << "if (down != null && down != '') {"
277 	    <<    computeD.str()
278 	    <<   "d += " << (slider_->handleWidth() / 2) << ";"
279 #ifndef WT_TARGET_JAVA
280 	    <<    sliderReleased_.createCall({"Math.round(d)"})
281 #else
282 	    <<    sliderReleased_.createCall("Math.round(d)")
283 #endif
284 	    <<   "obj.removeAttribute('down');"
285 	    << "}";
286 
287   bool enabled = !slider_->isDisabled();
288 
289   mouseDownJS_.setJavaScript(std::string("function(obj, event) {")
290 			     + (enabled ? mouseDownJS.str() : "")
291 			     + "}");
292   mouseMovedJS_.setJavaScript(std::string("function(obj, event) {")
293 			      + (enabled ? mouseMovedJS.str() : "")
294 			      + "}");
295   mouseUpJS_.setJavaScript(std::string("function(obj, event) {")
296 			   + (enabled ? mouseUpJS.str() : "")
297 			   + "}");
298 
299   update();
300   updateSliderPosition();
301 }
302 
doUpdateDom(DomElement & element,bool all)303 void PaintedSlider::doUpdateDom(DomElement& element, bool all)
304 {
305   if (all) {
306     WApplication *app = WApplication::instance();
307 
308     DomElement *west = DomElement::createNew(DomElementType::DIV);
309     west->setProperty(Property::Class, "Wt-w");
310     element.addChild(west);
311 
312     DomElement *east = DomElement::createNew(DomElementType::DIV);
313     east->setProperty(Property::Class, "Wt-e");
314     element.addChild(east);
315 
316     element.addChild(createSDomElement(app));
317     element.addChild(fill_->createSDomElement(app));
318     element.addChild(handle_->createSDomElement(app));
319   }
320 }
321 
sliderResized(const WLength & width,const WLength & height)322 void PaintedSlider::sliderResized(const WLength& width, const WLength& height)
323 {
324   if (slider_->orientation() == Orientation::Horizontal) {
325     WLength w = width;
326     if (!w.isAuto())
327       w = WLength(w.toPixels() - 10);
328 
329     resize(w, height);
330   } else {
331     WLength h = height;
332     if (!h.isAuto())
333       h = WLength(h.toPixels() - 10);
334 
335     resize(width, h);
336   }
337 
338   updateState();
339 }
340 
onSliderClick(const WMouseEvent & event)341 void PaintedSlider::onSliderClick(const WMouseEvent& event)
342 {
343   int x = event.widget().x;
344   int y = event.widget().y;
345 
346   if (WApplication::instance()->layoutDirection() ==
347       LayoutDirection::RightToLeft)
348     x = (int)(w() - x);
349 
350   onSliderReleased(slider_->orientation() == Orientation::Horizontal ? x : y);
351 }
352 
onSliderReleased(int u)353 void PaintedSlider::onSliderReleased(int u)
354 {
355   if (slider_->orientation() == Orientation::Horizontal)
356     u -= slider_->handleWidth() / 2;
357   else
358     u = (int)h() - (u + slider_->handleWidth() / 2);
359 
360   double l = (slider_->orientation() == Orientation::Horizontal) ? w() : h();
361 
362   double pixelsPerUnit = (l - slider_->handleWidth()) / range();
363 
364   double v = std::max(slider_->minimum(),
365 		      std::min(slider_->maximum(),
366 			       slider_->minimum()
367 			       + (int)((double)u / pixelsPerUnit + 0.5)));
368 
369   // TODO changed() ?
370   slider_->sliderMoved().emit(static_cast<int>(v));
371 
372   slider_->setValue(static_cast<int>(v));
373   slider_->valueChanged().emit(slider_->value());
374 
375   updateSliderPosition();
376 }
377 
updateSliderPosition()378 void PaintedSlider::updateSliderPosition()
379 {
380   double l = (slider_->orientation() == Orientation::Horizontal) ? w() : h();
381   double pixelsPerUnit = (l - slider_->handleWidth()) / range();
382 
383   double u = ((double)slider_->value() - slider_->minimum()) * pixelsPerUnit;
384 
385   if (slider_->orientation() == Orientation::Horizontal) {
386     handle_->setOffsets(u, Side::Left);
387     fill_->setWidth(u + slider_->handleWidth() / 2);
388   } else {
389     handle_->setOffsets(h() - slider_->handleWidth() - u, Side::Top);
390     fill_->setHeight(u + slider_->handleWidth() / 2);
391   }
392   handle_->setFocus(true);
393 }
394 
WSlider()395 WSlider::WSlider()
396   : orientation_(Orientation::Horizontal),
397     tickInterval_(0),
398     tickPosition_(None),
399     preferNative_(false),
400     changed_(false),
401     changedConnected_(false),
402     handleWidth_(20),
403     minimum_(0),
404     maximum_(99),
405     value_(0),
406     sliderMoved_(this, "moved", true)
407 {
408   resize(150, 50);
409 }
410 
WSlider(Orientation orientation)411 WSlider::WSlider(Orientation orientation)
412   : orientation_(orientation),
413     tickInterval_(0),
414     tickPosition_(None),
415     preferNative_(false),
416     changed_(false),
417     changedConnected_(false),
418     handleWidth_(20),
419     minimum_(0),
420     maximum_(99),
421     value_(0),
422     sliderMoved_(this, "moved", true)
423 {
424   if (orientation == Orientation::Horizontal)
425     resize(150, 50);
426   else
427     resize(50, 150);
428 }
429 
~WSlider()430 WSlider::~WSlider()
431 {
432   manageWidget(paintedSlider_, std::unique_ptr<PaintedSlider>());
433 }
434 
input()435 EventSignal<>& WSlider::input()
436 {
437   return *voidEventSignal(INPUT_SIGNAL, true);
438 }
439 
enableAjax()440 void WSlider::enableAjax()
441 {
442   if (paintedSlider_)
443     paintedSlider_->connectSlots();
444 }
445 
setNativeControl(bool nativeControl)446 void WSlider::setNativeControl(bool nativeControl)
447 {
448   preferNative_ = nativeControl;
449 }
450 
nativeControl()451 bool WSlider::nativeControl() const
452 {
453   if (preferNative_) {
454     const WEnvironment& env = WApplication::instance()->environment();
455     if ((env.agentIsChrome() &&
456 	 static_cast<unsigned int>(env.agent()) >=
457 	 static_cast<unsigned int>(UserAgent::Chrome5))
458 	|| (env.agentIsSafari() &&
459 	    static_cast<unsigned int>(env.agent()) >=
460 	    static_cast<unsigned int>(UserAgent::Safari4))
461 	|| (env.agentIsOpera() &&
462 	    static_cast<unsigned int>(env.agent()) >=
463       static_cast<unsigned int>(UserAgent::Opera10))
464   || (env.agentIsGecko() &&
465       static_cast<unsigned int>(env.agent()) >=
466       static_cast<unsigned int>(UserAgent::Firefox5_0)))
467       return true;
468   }
469 
470   return false;
471 }
472 
resize(const WLength & width,const WLength & height)473 void WSlider::resize(const WLength& width, const WLength& height)
474 {
475   // Quick transform rotate fix
476   if (orientation() == Orientation::Vertical) {
477     auto app = WApplication::instance();
478     auto bs5Theme = std::dynamic_pointer_cast<WBootstrap5Theme>(app->theme());
479     if (bs5Theme) {
480       WLength w = width, h = height;
481       WLength size = WLength(std::max(w.toPixels(), h.toPixels()));
482       WFormWidget::resize(size, size);
483     }
484   } else {
485     WFormWidget::resize(width, height);
486   }
487 
488   if (paintedSlider_)
489     paintedSlider_->sliderResized(width, height);
490 }
491 
layoutSizeChanged(int width,int height)492 void WSlider::layoutSizeChanged(int width, int height)
493 {
494   WFormWidget::resize(WLength::Auto, WLength::Auto);
495 
496   if (paintedSlider_) {
497     paintedSlider_->sliderResized(width, height);
498   }
499 }
500 
setOrientation(Orientation orientation)501 void WSlider::setOrientation(Orientation orientation)
502 {
503   orientation_ = orientation;
504 
505   if (paintedSlider_)
506     paintedSlider_->updateState();
507 }
508 
setTickPosition(WFlags<TickPosition> tickPosition)509 void WSlider::setTickPosition(WFlags<TickPosition> tickPosition)
510 {
511   tickPosition_ = tickPosition;
512 
513   if (paintedSlider_)
514     paintedSlider_->updateState();
515 }
516 
setTickInterval(int tickInterval)517 void WSlider::setTickInterval(int tickInterval)
518 {
519   tickInterval_ = tickInterval;
520 
521   if (paintedSlider_)
522     paintedSlider_->updateState();
523 }
524 
setHandleWidth(int handleWidth)525 void WSlider::setHandleWidth(int handleWidth)
526 {
527   handleWidth_ = handleWidth;
528 }
529 
createHandle()530 std::unique_ptr<WInteractWidget> WSlider::createHandle()
531 {
532   return std::unique_ptr<WInteractWidget>(new WContainerWidget());
533 }
534 
update()535 void WSlider::update()
536 {
537   if (paintedSlider_)
538     paintedSlider_->updateState();
539   else {
540     changed_ = true;
541     repaint();
542   }
543 }
544 
setMinimum(int minimum)545 void WSlider::setMinimum(int minimum)
546 {
547   minimum_ = minimum;
548   value_ = std::max(minimum_, value_);
549   maximum_ = std::max(minimum_ + 1, maximum_);
550 
551   update();
552 }
553 
setMaximum(int maximum)554 void WSlider::setMaximum(int maximum)
555 {
556   maximum_ = maximum;
557   value_ = std::min(maximum_, value_);
558   minimum_ = std::min(maximum_ - 1, minimum_);
559 
560   update();
561 }
562 
setRange(int minimum,int maximum)563 void WSlider::setRange(int minimum, int maximum)
564 {
565   minimum_ = minimum;
566   maximum_ = maximum;
567   value_ = std::min(maximum_, std::max(minimum_, value_));
568 
569   update();
570 }
571 
setValue(int value)572 void WSlider::setValue(int value)
573 {
574   value_ = std::min(maximum_, std::max(minimum_, value));
575 
576   if (paintedSlider_)
577     paintedSlider_->updateSliderPosition();
578   else {
579     update();
580     onChange();
581   }
582 }
583 
signalConnectionsChanged()584 void WSlider::signalConnectionsChanged()
585 {
586   WFormWidget::signalConnectionsChanged();
587 
588   update();
589 }
590 
onChange()591 void WSlider::onChange()
592 {
593   valueChanged_.emit(value_);
594   sliderMoved_.emit(value_);
595 }
596 
domElementType()597 DomElementType WSlider::domElementType() const
598 {
599   return paintedSlider_ ? DomElementType::DIV : DomElementType::INPUT;
600 }
601 
render(WFlags<RenderFlag> flags)602 void WSlider::render(WFlags<RenderFlag> flags)
603 {
604   /*
605    * In theory we are a bit late here to decide what we want to become:
606    * somebody could already have asked the domElementType()
607    */
608   if (flags.test(RenderFlag::Full)) {
609     bool useNative = nativeControl();
610 
611     if (!useNative) {
612       if (!paintedSlider_) {
613         auto paintedSlider = std::make_unique<PaintedSlider>(this);
614         manageWidget(paintedSlider_, std::move(paintedSlider));
615         paintedSlider_->sliderResized(width(), height());
616       }
617     }
618 
619     setLayoutSizeAware(!useNative);
620     setFormObject(useNative);
621   }
622 
623   WFormWidget::render(flags);
624 }
625 
updateDom(DomElement & element,bool all)626 void WSlider::updateDom(DomElement& element, bool all)
627 {
628   if (paintedSlider_)
629     paintedSlider_->doUpdateDom(element, all);
630   else {
631     if (all || changed_) {
632       element.setAttribute("type", "range");
633       element.setProperty(Wt::Property::Value, std::to_string(value_));
634       element.setAttribute("min", std::to_string(minimum_));
635       element.setAttribute("max", std::to_string(maximum_));
636 
637       if (!changedConnected_
638 	  && (valueChanged_.isConnected() || sliderMoved_.isConnected())) {
639 	changedConnected_ = true;
640         changed().connect(this, &WSlider::onChange);
641       } else if (!inputConnected_ && (valueChanged_.isConnected() || sliderMoved_.isConnected())) {
642 	changedConnected_ = true;
643 	input().connect(this, &WSlider::onChange);
644       }
645 
646       changed_ = false;
647     }
648   }
649 
650   WFormWidget::updateDom(element, all);
651 }
652 
setFormData(const FormData & formData)653 void WSlider::setFormData(const FormData& formData)
654 {
655   // if the value was updated through the API, then ignore the update from
656   // the browser, this happens when an action generated multiple events,
657   // and we do not want to revert the changes made through the API
658   if (changed_ || isReadOnly())
659     return;
660 
661   if (!Utils::isEmpty(formData.values)) {
662     const std::string& value = formData.values[0];
663     try {
664       value_ = Utils::stoi(value);
665     } catch (std::exception& e) { }
666   }
667 }
668 
valueText()669 WT_USTRING WSlider::valueText() const
670 {
671   return WT_USTRING::fromUTF8(std::to_string(value_));
672 }
673 
setValueText(const WT_USTRING & value)674 void WSlider::setValueText(const WT_USTRING& value)
675 {
676   try {
677     value_ = Utils::stoi(value.toUTF8());
678   } catch (std::exception& e) {
679   }
680 }
681 
setDisabled(bool disabled)682 void WSlider::setDisabled(bool disabled)
683 {
684   if (paintedSlider_)
685     paintedSlider_->setDisabled(disabled);
686 
687   WFormWidget::setDisabled(disabled);
688 
689   if (paintedSlider_)
690     paintedSlider_->updateState();
691 }
692 
paintTick(WPainter & painter,int value,int x,int y)693 void WSlider::paintTick(WPainter& painter, int value, int x, int y)
694 {
695   if (!tickPosition_.empty()) {
696     int h = 0;
697 
698     if (orientation_ == Orientation::Horizontal)
699       h = (int)painter.device()->height().toPixels();
700     else
701       h = (int)painter.device()->width().toPixels();
702 
703     WPen pen;
704     pen.setColor(WColor(0xd7, 0xd7, 0xd7));
705     pen.setCapStyle(PenCapStyle::Flat);
706     pen.setWidth(1);
707     painter.setPen(pen);
708 
709     int y1 = h / 4;
710     int y2 = h / 2 - 4;
711     int y3 = h / 2 + 4;
712     int y4 = h - h/4;
713 
714     switch (orientation_) {
715     case Orientation::Horizontal:
716       if (tickPosition_.test(WSlider::TickPosition::TicksAbove))
717 	painter.drawLine(x + 0.5, y1, x + 0.5, y2);
718       if (tickPosition_.test(WSlider::TickPosition::TicksBelow))
719 	painter.drawLine(x + 0.5, y3, x + 0.5, y4);
720 
721       break;
722     case Orientation::Vertical:
723       if (tickPosition_.test(WSlider::TickPosition::TicksAbove))
724 	painter.drawLine(y1, y + 0.5, y2, y + 0.5);
725       if (tickPosition_.test(WSlider::TickPosition::TicksBelow))
726 	painter.drawLine(y3, y + 0.5, y4, y + 0.5);
727     }
728   }
729 }
730 
731 }
732