1 /*
2 * scrollbar: A scroll bar
3 *
4 * Copyright 2012-2020 Stephan Haller <nomad@froevel.de>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 *
21 *
22 */
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27
28 #include <libxfdashboard/scrollbar.h>
29
30 #include <glib/gi18n-lib.h>
31
32 #include <libxfdashboard/stylable.h>
33 #include <libxfdashboard/compat.h>
34 #include <libxfdashboard/debug.h>
35
36
37 /* Define this class in GObject system */
38 struct _XfdashboardScrollbarPrivate
39 {
40 /* Properties related */
41 ClutterOrientation orientation;
42 gfloat value;
43 gfloat valueRange;
44 gfloat range;
45 gfloat pageSizeFactor;
46 gfloat spacing;
47 gfloat sliderWidth;
48 gfloat sliderRadius;
49 ClutterColor *sliderColor;
50
51 /* Instance related */
52 ClutterContent *slider;
53 ClutterSize lastViewportSize;
54 ClutterSize lastSliderSize;
55 gfloat sliderPosition, sliderSize;
56 gfloat dragAlignment;
57 ClutterInputDevice *dragDevice;
58 guint signalButtonReleasedID;
59 guint signalMotionEventID;
60 };
61
62 G_DEFINE_TYPE_WITH_PRIVATE(XfdashboardScrollbar,
63 xfdashboard_scrollbar,
64 XFDASHBOARD_TYPE_BACKGROUND)
65
66 /* Properties */
67 enum
68 {
69 PROP_0,
70
71 PROP_ORIENTATION,
72 PROP_VALUE,
73 PROP_VALUE_RANGE,
74 PROP_RANGE,
75 PROP_PAGE_SIZE_FACTOR,
76 PROP_SPACING,
77 PROP_SLIDER_WIDTH,
78 PROP_SLIDER_RADIUS,
79 PROP_SLIDER_COLOR,
80
81 PROP_LAST
82 };
83
84 static GParamSpec* XfdashboardScrollbarProperties[PROP_LAST]={ 0, };
85
86 /* Signals */
87 enum
88 {
89 SIGNAL_VALUE_CHANGED,
90
91 SIGNAL_LAST
92 };
93
94 static guint XfdashboardScrollbarSignals[SIGNAL_LAST]={ 0, };
95
96 /* IMPLEMENTATION: Private variables and methods */
97
98 /* Get value from coord */
_xfdashboard_scrollbar_get_value_from_coord(XfdashboardScrollbar * self,gfloat inX,gfloat inY,gfloat inAlignment)99 static gfloat _xfdashboard_scrollbar_get_value_from_coord(XfdashboardScrollbar *self,
100 gfloat inX,
101 gfloat inY,
102 gfloat inAlignment)
103 {
104 XfdashboardScrollbarPrivate *priv;
105 gfloat coord;
106 gfloat width;
107 gfloat value;
108
109 g_return_val_if_fail(XFDASHBOARD_IS_SCROLLBAR(self), 0.0f);
110 g_return_val_if_fail(inAlignment>=0.0f && inAlignment<=1.0f, 0.0f);
111
112 priv=self->priv;
113
114 /* Get coordinate for calculation depending on orientation and
115 * subtract spacing
116 */
117 if(priv->orientation==CLUTTER_ORIENTATION_HORIZONTAL)
118 {
119 coord=inX;
120 width=priv->lastSliderSize.width;
121 }
122 else
123 {
124 coord=inY;
125 width=priv->lastSliderSize.height;
126 }
127
128 coord-=priv->spacing;
129
130 /* Apply alignment */
131 coord-=(priv->sliderSize*inAlignment);
132
133 /* Calculate new value to set by coordinate */
134 value=(coord/width)*priv->range;
135 value=MAX(0.0f, value);
136 value=MIN(value, priv->range-priv->valueRange);
137
138 /* Return calculated value */
139 return(value);
140 }
141
142 /* Pointer moved inside scroll bar (usually called after button-pressed event) */
_xfdashboard_scrollbar_on_motion_event(ClutterActor * inActor,ClutterEvent * inEvent,gpointer inUserData)143 static gboolean _xfdashboard_scrollbar_on_motion_event(ClutterActor *inActor,
144 ClutterEvent *inEvent,
145 gpointer inUserData)
146 {
147 XfdashboardScrollbar *self;
148 XfdashboardScrollbarPrivate *priv;
149 gfloat eventX, eventY;
150 gfloat x, y;
151 gfloat value;
152
153 g_return_val_if_fail(XFDASHBOARD_IS_SCROLLBAR(inActor), FALSE);
154 g_return_val_if_fail(inEvent, FALSE);
155
156 self=XFDASHBOARD_SCROLLBAR(inActor);
157 priv=self->priv;
158
159 /* Get coords where event happened */
160 clutter_event_get_coords(inEvent, &eventX, &eventY);
161 if(!clutter_actor_transform_stage_point(inActor, eventX, eventY, &x, &y)) return(FALSE);
162
163 /* Set value from motion */
164 value=_xfdashboard_scrollbar_get_value_from_coord(self, x, y, priv->dragAlignment);
165 xfdashboard_scrollbar_set_value(self, value);
166
167 return(CLUTTER_EVENT_STOP);
168 }
169
170 /* User released button in scroll bar */
_xfdashboard_scrollbar_on_button_released(ClutterActor * inActor,ClutterEvent * inEvent,gpointer inUserData)171 static gboolean _xfdashboard_scrollbar_on_button_released(ClutterActor *inActor,
172 ClutterEvent *inEvent,
173 gpointer inUserData)
174 {
175 XfdashboardScrollbar *self;
176 XfdashboardScrollbarPrivate *priv;
177 gfloat eventX, eventY;
178 gfloat x, y;
179 gfloat value;
180
181 g_return_val_if_fail(XFDASHBOARD_IS_SCROLLBAR(inActor), FALSE);
182 g_return_val_if_fail(inEvent, FALSE);
183
184 self=XFDASHBOARD_SCROLLBAR(inActor);
185 priv=self->priv;
186
187 /* If user did not release a left-click do nothing */
188 if(clutter_event_get_button(inEvent)!=1) return(FALSE);
189
190 /* Release exclusive handling of pointer events and
191 * disconnect motion and button-release events
192 */
193 if(priv->dragDevice)
194 {
195 clutter_input_device_ungrab(priv->dragDevice);
196 priv->dragDevice=NULL;
197 }
198
199 if(priv->signalMotionEventID)
200 {
201 g_signal_handler_disconnect(inActor, priv->signalMotionEventID);
202 priv->signalMotionEventID=0L;
203 }
204
205 if(priv->signalButtonReleasedID)
206 {
207 g_signal_handler_disconnect(inActor, priv->signalButtonReleasedID);
208 priv->signalButtonReleasedID=0L;
209 }
210
211 /* Remove style */
212 xfdashboard_stylable_remove_pseudo_class(XFDASHBOARD_STYLABLE(self), "pressed");
213
214 /* Get coords where event happened */
215 clutter_event_get_coords(inEvent, &eventX, &eventY);
216 if(!clutter_actor_transform_stage_point(inActor, eventX, eventY, &x, &y)) return(FALSE);
217
218 /* Set new value */
219 value=_xfdashboard_scrollbar_get_value_from_coord(self, x, y, priv->dragAlignment);
220 xfdashboard_scrollbar_set_value(self, value);
221
222 return(CLUTTER_EVENT_STOP);
223 }
224
225 /* User pressed button in scroll bar */
_xfdashboard_scrollbar_on_button_pressed(ClutterActor * inActor,ClutterEvent * inEvent,gpointer inUserData)226 static gboolean _xfdashboard_scrollbar_on_button_pressed(ClutterActor *inActor,
227 ClutterEvent *inEvent,
228 gpointer inUserData)
229 {
230 XfdashboardScrollbar *self;
231 XfdashboardScrollbarPrivate *priv;
232 gfloat eventX, eventY;
233 gfloat x, y;
234 gfloat value;
235 gfloat dragOffset;
236
237 g_return_val_if_fail(XFDASHBOARD_IS_SCROLLBAR(inActor), FALSE);
238 g_return_val_if_fail(inEvent, FALSE);
239
240 self=XFDASHBOARD_SCROLLBAR(inActor);
241 priv=self->priv;
242
243 /* If user left-clicked into scroll bar adjust value to point
244 * where the click happened
245 */
246 if(clutter_event_get_button(inEvent)!=1) return(FALSE);
247
248 /* Get coords where event happened. If coords are not in bar handle set value
249 * otherwise only determine offset used in event handlers and do not adjust
250 * value as this would cause a movement of bar handle */
251 clutter_event_get_coords(inEvent, &eventX, &eventY);
252 if(!clutter_actor_transform_stage_point(inActor, eventX, eventY, &x, &y)) return(FALSE);
253
254 value=_xfdashboard_scrollbar_get_value_from_coord(self, x, y, 0.0f);
255 if(value<priv->value || value>=(priv->value+priv->valueRange))
256 {
257 /* Set new value */
258 value=_xfdashboard_scrollbar_get_value_from_coord(self, x, y, 0.5f);
259 xfdashboard_scrollbar_set_value(self, value);
260 return(FALSE);
261 }
262
263 /* Remember event values for drag'n'drop of slider */
264 dragOffset=-(priv->spacing+priv->sliderPosition);
265 if(priv->orientation==CLUTTER_ORIENTATION_HORIZONTAL) dragOffset+=x;
266 else dragOffset+=y;
267
268 priv->dragAlignment=dragOffset/priv->sliderSize;
269
270 /* Connect signals for motion and button-release events */
271 priv->signalMotionEventID=g_signal_connect_after(inActor,
272 "motion-event",
273 G_CALLBACK(_xfdashboard_scrollbar_on_motion_event),
274 NULL);
275 priv->signalButtonReleasedID=g_signal_connect_after(inActor,
276 "button-release-event",
277 G_CALLBACK(_xfdashboard_scrollbar_on_button_released),
278 NULL);
279
280 /* Add style */
281 xfdashboard_stylable_add_pseudo_class(XFDASHBOARD_STYLABLE(self), "pressed");
282
283 /* Handle pointer events exclusively */
284 priv->dragDevice=clutter_event_get_device(inEvent);
285 clutter_input_device_grab(priv->dragDevice, inActor);
286
287 return(CLUTTER_EVENT_STOP);
288 }
289
290 /* A scroll event occured in scroll bar (e.g. by mouse-wheel) */
_xfdashboard_scrollbar_on_scroll_event(ClutterActor * inActor,ClutterEvent * inEvent,gpointer inUserData)291 static gboolean _xfdashboard_scrollbar_on_scroll_event(ClutterActor *inActor,
292 ClutterEvent *inEvent,
293 gpointer inUserData)
294 {
295 XfdashboardScrollbar *self;
296 XfdashboardScrollbarPrivate *priv;
297 gfloat value;
298 gfloat directionFactor;
299
300 g_return_val_if_fail(XFDASHBOARD_IS_SCROLLBAR(inActor), FALSE);
301 g_return_val_if_fail(inEvent, FALSE);
302
303 self=XFDASHBOARD_SCROLLBAR(inActor);
304 priv=self->priv;
305
306 /* Get direction of scroll event */
307 switch(clutter_event_get_scroll_direction(inEvent))
308 {
309 case CLUTTER_SCROLL_UP:
310 case CLUTTER_SCROLL_LEFT:
311 directionFactor=-priv->pageSizeFactor;
312 break;
313
314 case CLUTTER_SCROLL_DOWN:
315 case CLUTTER_SCROLL_RIGHT:
316 directionFactor=priv->pageSizeFactor;
317 break;
318
319 /* Unhandled directions */
320 default:
321 XFDASHBOARD_DEBUG(self, ACTOR,
322 "Cannot handle scroll direction %d in %s",
323 clutter_event_get_scroll_direction(inEvent),
324 G_OBJECT_TYPE_NAME(self));
325 return(CLUTTER_EVENT_PROPAGATE);
326 }
327
328 /* Calculate new value by increasing or decreasing value by value-range
329 * of slider and adjust new value to fit into range (respecting value-range)
330 */
331 value=priv->value+(priv->valueRange*directionFactor);
332 value=MAX(value, 0);
333 value=MIN(priv->range-priv->valueRange, value);
334
335 /* Set new value */
336 xfdashboard_scrollbar_set_value(self, value);
337
338 return(CLUTTER_EVENT_STOP);
339 }
340
341 /* Rectangle canvas should be redrawn */
_xfdashboard_scrollbar_on_draw_slider(XfdashboardScrollbar * self,cairo_t * inContext,int inWidth,int inHeight,gpointer inUserData)342 static gboolean _xfdashboard_scrollbar_on_draw_slider(XfdashboardScrollbar *self,
343 cairo_t *inContext,
344 int inWidth,
345 int inHeight,
346 gpointer inUserData)
347 {
348 XfdashboardScrollbarPrivate *priv;
349 gdouble radius;
350 gdouble top, left, bottom, right;
351 gdouble barValueRange;
352
353 g_return_val_if_fail(XFDASHBOARD_IS_SCROLLBAR(self), TRUE);
354 g_return_val_if_fail(CLUTTER_IS_CANVAS(inUserData), TRUE);
355
356 priv=self->priv;
357
358 /* Clear current contents of the canvas */
359 cairo_save(inContext);
360 cairo_set_operator(inContext, CAIRO_OPERATOR_CLEAR);
361 cairo_paint(inContext);
362 cairo_restore(inContext);
363
364 cairo_set_operator(inContext, CAIRO_OPERATOR_OVER);
365
366 /* Set color for slider */
367 if(priv->sliderColor) clutter_cairo_set_source_color(inContext, priv->sliderColor);
368
369 /* Determine radius for rounded corners */
370 radius=MIN(priv->sliderRadius, inWidth/2.0f);
371 radius=MIN(radius, inHeight/2.0f);
372
373 /* Calculate bounding coordinates for slider and viewport */
374 priv->lastViewportSize.width=inWidth;
375 priv->lastViewportSize.height=inHeight;
376 priv->lastSliderSize.width=MAX(0, inWidth-(2*priv->spacing));
377 priv->lastSliderSize.height=MAX(0, inHeight-(2*priv->spacing));
378
379 if(priv->orientation==CLUTTER_ORIENTATION_HORIZONTAL)
380 {
381 if(priv->range > priv->lastViewportSize.width)
382 {
383 priv->sliderSize=(priv->lastViewportSize.width/priv->range)*priv->lastSliderSize.width;
384 }
385 else priv->sliderSize=priv->lastSliderSize.width;
386
387 barValueRange=(priv->sliderSize/priv->lastSliderSize.width)*priv->range;
388
389 priv->sliderPosition=MAX(0, (priv->value/priv->range)*priv->lastSliderSize.width);
390 priv->sliderPosition=MIN(priv->sliderPosition, priv->lastSliderSize.width);
391 if(priv->sliderPosition+priv->sliderSize>priv->lastSliderSize.width) priv->sliderPosition=priv->lastSliderSize.width-priv->sliderSize;
392
393 top=priv->spacing;
394 bottom=priv->lastSliderSize.height;
395 left=priv->sliderPosition;
396 right=priv->sliderPosition+priv->sliderSize;
397 }
398 else
399 {
400 if(priv->range > priv->lastViewportSize.height)
401 {
402 priv->sliderSize=(priv->lastViewportSize.height/priv->range)*priv->lastSliderSize.height;
403 }
404 else priv->sliderSize=priv->lastSliderSize.height;
405
406 barValueRange=(priv->sliderSize/priv->lastSliderSize.height)*priv->range;
407
408 priv->sliderPosition=MAX(0, (priv->value/priv->range)*priv->lastSliderSize.height);
409 priv->sliderPosition=MIN(priv->sliderPosition, priv->lastSliderSize.height);
410 if(priv->sliderPosition+priv->sliderSize>priv->lastSliderSize.height) priv->sliderPosition=priv->lastSliderSize.height-priv->sliderSize;
411
412 left=priv->spacing;
413 right=priv->lastSliderSize.width;
414 top=priv->sliderPosition;
415 bottom=priv->sliderPosition+priv->sliderSize;
416 }
417
418 /* Draw slider */
419 if(radius>0.0f)
420 {
421 cairo_move_to(inContext, left, top+radius);
422 cairo_arc(inContext, left+radius, top+radius, radius, G_PI, G_PI*1.5);
423
424 cairo_line_to(inContext, right-radius, top);
425 cairo_arc(inContext, right-radius, top+radius, radius, G_PI*1.5, 0);
426
427 cairo_line_to(inContext, right, bottom-radius);
428 cairo_arc(inContext, right-radius, bottom-radius, radius, 0, G_PI/2.0);
429
430 cairo_line_to(inContext, left+radius, bottom);
431 cairo_arc(inContext, left+radius, bottom-radius, radius, G_PI/2.0, G_PI);
432
433 cairo_line_to(inContext, left, radius);
434 }
435 else
436 {
437 cairo_rectangle(inContext, left, top, right, bottom);
438 }
439
440 cairo_fill(inContext);
441
442 /* Set value if changed */
443 if(barValueRange!=priv->valueRange)
444 {
445 /* Set value */
446 priv->valueRange=barValueRange;
447
448 /* Notify about property change */
449 g_object_notify_by_pspec(G_OBJECT(self), XfdashboardScrollbarProperties[PROP_VALUE_RANGE]);
450
451 /* Adjust value to fit into range (respecting value-range) if needed */
452 if(priv->value+priv->valueRange>priv->range)
453 {
454 xfdashboard_scrollbar_set_value(self, priv->range-priv->valueRange);
455 }
456 }
457
458 /* Done drawing */
459 return(CLUTTER_EVENT_STOP);
460 }
461
462 /* IMPLEMENTATION: ClutterActor */
463
464 /* Get preferred width/height */
_xfdashboard_scrollbar_get_preferred_height(ClutterActor * self,gfloat inForWidth,gfloat * outMinHeight,gfloat * outNaturalHeight)465 static void _xfdashboard_scrollbar_get_preferred_height(ClutterActor *self,
466 gfloat inForWidth,
467 gfloat *outMinHeight,
468 gfloat *outNaturalHeight)
469 {
470 XfdashboardScrollbarPrivate *priv=XFDASHBOARD_SCROLLBAR(self)->priv;
471 gfloat minHeight, naturalHeight;
472
473 minHeight=naturalHeight=0.0f;
474
475 /* Determine sizes */
476 if(priv->orientation==CLUTTER_ORIENTATION_HORIZONTAL)
477 {
478 minHeight=naturalHeight=(2*priv->spacing)+priv->sliderWidth;
479 }
480 else
481 {
482 /* Call parent's class method to determine sizes */
483 ClutterActorClass *actorClass=CLUTTER_ACTOR_CLASS(xfdashboard_scrollbar_parent_class);
484
485 if(actorClass->get_preferred_height)
486 {
487 actorClass->get_preferred_height(self,
488 inForWidth,
489 &minHeight,
490 &naturalHeight);
491 }
492 }
493
494 /* Store sizes computed */
495 if(outMinHeight) *outMinHeight=minHeight;
496 if(outNaturalHeight) *outNaturalHeight=naturalHeight;
497 }
498
_xfdashboard_scrollbar_get_preferred_width(ClutterActor * self,gfloat inForHeight,gfloat * outMinWidth,gfloat * outNaturalWidth)499 static void _xfdashboard_scrollbar_get_preferred_width(ClutterActor *self,
500 gfloat inForHeight,
501 gfloat *outMinWidth,
502 gfloat *outNaturalWidth)
503 {
504 XfdashboardScrollbarPrivate *priv=XFDASHBOARD_SCROLLBAR(self)->priv;
505 gfloat minWidth, naturalWidth;
506
507 minWidth=naturalWidth=0.0f;
508
509 /* Determine sizes */
510 if(priv->orientation==CLUTTER_ORIENTATION_VERTICAL)
511 {
512 minWidth=naturalWidth=(2*priv->spacing)+priv->sliderWidth;
513 }
514 else
515 {
516 /* Call parent's class method to determine sizes */
517 ClutterActorClass *actorClass=CLUTTER_ACTOR_CLASS(xfdashboard_scrollbar_parent_class);
518
519 if(actorClass->get_preferred_width)
520 {
521 actorClass->get_preferred_width(self,
522 inForHeight,
523 &minWidth,
524 &naturalWidth);
525 }
526 }
527
528 /* Store sizes computed */
529 if(outMinWidth) *outMinWidth=minWidth;
530 if(outNaturalWidth) *outNaturalWidth=naturalWidth;
531 }
532
533 /* Allocate position and size of actor and its children */
_xfdashboard_scrollbar_allocate(ClutterActor * self,const ClutterActorBox * inBox,ClutterAllocationFlags inFlags)534 static void _xfdashboard_scrollbar_allocate(ClutterActor *self,
535 const ClutterActorBox *inBox,
536 ClutterAllocationFlags inFlags)
537 {
538 XfdashboardScrollbarPrivate *priv=XFDASHBOARD_SCROLLBAR(self)->priv;
539
540 /* Chain up to store the allocation of the actor */
541 CLUTTER_ACTOR_CLASS(xfdashboard_scrollbar_parent_class)->allocate(self, inBox, inFlags);
542
543 /* Set size of slider canvas */
544 clutter_canvas_set_size(CLUTTER_CANVAS(priv->slider),
545 clutter_actor_box_get_width(inBox),
546 clutter_actor_box_get_height(inBox));
547 }
548
549 /* IMPLEMENTATION: GObject */
550
551 /* Dispose this object */
_xfdashboard_scrollbar_dispose(GObject * inObject)552 static void _xfdashboard_scrollbar_dispose(GObject *inObject)
553 {
554 XfdashboardScrollbar *self=XFDASHBOARD_SCROLLBAR(inObject);
555 XfdashboardScrollbarPrivate *priv=self->priv;
556
557 /* Release allocated resources */
558 if(priv->sliderColor)
559 {
560 clutter_color_free(priv->sliderColor);
561 priv->sliderColor=NULL;
562 }
563
564 if(priv->slider)
565 {
566 g_object_unref(priv->slider);
567 priv->slider=NULL;
568 }
569
570 if(priv->dragDevice)
571 {
572 clutter_input_device_ungrab(priv->dragDevice);
573 priv->dragDevice=NULL;
574 }
575
576 if(priv->signalMotionEventID)
577 {
578 g_signal_handler_disconnect(self, priv->signalMotionEventID);
579 priv->signalMotionEventID=0L;
580 }
581
582 if(priv->signalButtonReleasedID)
583 {
584 g_signal_handler_disconnect(self, priv->signalButtonReleasedID);
585 priv->signalButtonReleasedID=0L;
586 }
587
588 /* Call parent's class dispose method */
589 G_OBJECT_CLASS(xfdashboard_scrollbar_parent_class)->dispose(inObject);
590 }
591
592 /* Set/get properties */
_xfdashboard_scrollbar_set_property(GObject * inObject,guint inPropID,const GValue * inValue,GParamSpec * inSpec)593 static void _xfdashboard_scrollbar_set_property(GObject *inObject,
594 guint inPropID,
595 const GValue *inValue,
596 GParamSpec *inSpec)
597 {
598 XfdashboardScrollbar *self=XFDASHBOARD_SCROLLBAR(inObject);
599
600 switch(inPropID)
601 {
602 case PROP_ORIENTATION:
603 xfdashboard_scrollbar_set_orientation(self, g_value_get_enum(inValue));
604 break;
605
606 case PROP_VALUE:
607 xfdashboard_scrollbar_set_value(self, g_value_get_float(inValue));
608 break;
609
610 case PROP_RANGE:
611 xfdashboard_scrollbar_set_range(self, g_value_get_float(inValue));
612 break;
613
614 case PROP_PAGE_SIZE_FACTOR:
615 xfdashboard_scrollbar_set_page_size_factor(self, g_value_get_float(inValue));
616 break;
617
618 case PROP_SPACING:
619 xfdashboard_scrollbar_set_spacing(self, g_value_get_float(inValue));
620 break;
621
622 case PROP_SLIDER_WIDTH:
623 xfdashboard_scrollbar_set_slider_width(self, g_value_get_float(inValue));
624 break;
625
626 case PROP_SLIDER_RADIUS:
627 xfdashboard_scrollbar_set_slider_radius(self, g_value_get_float(inValue));
628 break;
629
630 case PROP_SLIDER_COLOR:
631 xfdashboard_scrollbar_set_slider_color(self, clutter_value_get_color(inValue));
632 break;
633
634 default:
635 G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
636 break;
637 }
638 }
639
_xfdashboard_scrollbar_get_property(GObject * inObject,guint inPropID,GValue * outValue,GParamSpec * inSpec)640 static void _xfdashboard_scrollbar_get_property(GObject *inObject,
641 guint inPropID,
642 GValue *outValue,
643 GParamSpec *inSpec)
644 {
645 XfdashboardScrollbar *self=XFDASHBOARD_SCROLLBAR(inObject);
646
647 switch(inPropID)
648 {
649 case PROP_ORIENTATION:
650 g_value_set_enum(outValue, self->priv->orientation);
651 break;
652
653 case PROP_VALUE:
654 g_value_set_float(outValue, self->priv->value);
655 break;
656
657 case PROP_VALUE_RANGE:
658 g_value_set_float(outValue, self->priv->valueRange);
659 break;
660
661 case PROP_RANGE:
662 g_value_set_float(outValue, self->priv->range);
663 break;
664
665 case PROP_PAGE_SIZE_FACTOR:
666 g_value_set_float(outValue, self->priv->pageSizeFactor);
667 break;
668
669 case PROP_SPACING:
670 g_value_set_float(outValue, self->priv->spacing);
671 break;
672
673 case PROP_SLIDER_WIDTH:
674 g_value_set_float(outValue, self->priv->sliderWidth);
675 break;
676
677 case PROP_SLIDER_RADIUS:
678 g_value_set_float(outValue, self->priv->sliderRadius);
679 break;
680
681 case PROP_SLIDER_COLOR:
682 clutter_value_set_color(outValue, self->priv->sliderColor);
683 break;
684
685 default:
686 G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
687 break;
688 }
689 }
690
691 /* Class initialization
692 * Override functions in parent classes and define properties
693 * and signals
694 */
xfdashboard_scrollbar_class_init(XfdashboardScrollbarClass * klass)695 static void xfdashboard_scrollbar_class_init(XfdashboardScrollbarClass *klass)
696 {
697 XfdashboardActorClass *actorClass=XFDASHBOARD_ACTOR_CLASS(klass);
698 ClutterActorClass *clutterActorClass=CLUTTER_ACTOR_CLASS(klass);
699 GObjectClass *gobjectClass=G_OBJECT_CLASS(klass);
700
701 /* Override functions */
702 gobjectClass->set_property=_xfdashboard_scrollbar_set_property;
703 gobjectClass->get_property=_xfdashboard_scrollbar_get_property;
704 gobjectClass->dispose=_xfdashboard_scrollbar_dispose;
705
706 clutterActorClass->get_preferred_width=_xfdashboard_scrollbar_get_preferred_width;
707 clutterActorClass->get_preferred_height=_xfdashboard_scrollbar_get_preferred_height;
708 clutterActorClass->allocate=_xfdashboard_scrollbar_allocate;
709
710 /* Define properties */
711 XfdashboardScrollbarProperties[PROP_ORIENTATION]=
712 g_param_spec_enum("orientation",
713 "Orientation",
714 "Defines if scrollbar is horizontal or vertical",
715 CLUTTER_TYPE_ORIENTATION,
716 CLUTTER_ORIENTATION_HORIZONTAL,
717 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
718
719 XfdashboardScrollbarProperties[PROP_VALUE]=
720 g_param_spec_float("value",
721 "Value",
722 "Current value of scroll bar within range",
723 0.0f, G_MAXFLOAT,
724 0.0f,
725 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
726
727 XfdashboardScrollbarProperties[PROP_VALUE_RANGE]=
728 g_param_spec_float("value-range",
729 "Value range",
730 "The range the slider of scroll bar covers",
731 0.0f, G_MAXFLOAT,
732 0.0f,
733 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
734
735 XfdashboardScrollbarProperties[PROP_RANGE]=
736 g_param_spec_float("range",
737 "Range",
738 "Range to scroll within",
739 0.0f, G_MAXFLOAT,
740 1.0f,
741 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
742
743 XfdashboardScrollbarProperties[PROP_PAGE_SIZE_FACTOR]=
744 g_param_spec_float("page-size-factor",
745 "Page size factor",
746 "The factor of value range to increase or decrease value by on pointer scroll events.",
747 0.1f, 1.0f,
748 0.5f,
749 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
750
751 XfdashboardScrollbarProperties[PROP_SPACING]=
752 g_param_spec_float("spacing",
753 "Spacing",
754 "The spacing between scrollbar and background",
755 0.0f, G_MAXFLOAT,
756 0.0f,
757 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
758
759 XfdashboardScrollbarProperties[PROP_SLIDER_WIDTH]=
760 g_param_spec_float("slider-width",
761 "Slider width",
762 "The width of slider",
763 1.0f, G_MAXFLOAT,
764 1.0f,
765 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
766
767 XfdashboardScrollbarProperties[PROP_SLIDER_RADIUS]=
768 g_param_spec_float("slider-radius",
769 "Slider radius",
770 "The radius of slider's rounded corners",
771 0.0f, G_MAXFLOAT,
772 0.0f,
773 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
774
775 XfdashboardScrollbarProperties[PROP_SLIDER_COLOR]=
776 clutter_param_spec_color("slider-color",
777 "Slider color",
778 "Color of slider",
779 CLUTTER_COLOR_White,
780 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
781
782 g_object_class_install_properties(gobjectClass, PROP_LAST, XfdashboardScrollbarProperties);
783
784 /* Define stylable properties */
785 xfdashboard_actor_install_stylable_property(actorClass, XfdashboardScrollbarProperties[PROP_ORIENTATION]);
786 xfdashboard_actor_install_stylable_property(actorClass, XfdashboardScrollbarProperties[PROP_PAGE_SIZE_FACTOR]);
787 xfdashboard_actor_install_stylable_property(actorClass, XfdashboardScrollbarProperties[PROP_SPACING]);
788 xfdashboard_actor_install_stylable_property(actorClass, XfdashboardScrollbarProperties[PROP_SLIDER_WIDTH]);
789 xfdashboard_actor_install_stylable_property(actorClass, XfdashboardScrollbarProperties[PROP_SLIDER_RADIUS]);
790 xfdashboard_actor_install_stylable_property(actorClass, XfdashboardScrollbarProperties[PROP_SLIDER_COLOR]);
791
792 /* Define signals */
793 XfdashboardScrollbarSignals[SIGNAL_VALUE_CHANGED]=
794 g_signal_new("value-changed",
795 G_TYPE_FROM_CLASS(klass),
796 G_SIGNAL_RUN_LAST,
797 G_STRUCT_OFFSET(XfdashboardScrollbarClass, value_changed),
798 NULL,
799 NULL,
800 g_cclosure_marshal_VOID__FLOAT,
801 G_TYPE_NONE,
802 1,
803 G_TYPE_FLOAT);
804 }
805
806 /* Object initialization
807 * Create private structure and set up default values
808 */
xfdashboard_scrollbar_init(XfdashboardScrollbar * self)809 static void xfdashboard_scrollbar_init(XfdashboardScrollbar *self)
810 {
811 XfdashboardScrollbarPrivate *priv;
812
813 priv=self->priv=xfdashboard_scrollbar_get_instance_private(self);
814
815 /* Set up default values */
816 priv->orientation=CLUTTER_ORIENTATION_HORIZONTAL;
817 priv->value=0.0f;
818 priv->valueRange=0.0f;
819 priv->range=1.0f;
820 priv->pageSizeFactor=0.5f;
821 priv->spacing=0.0f;
822 priv->sliderWidth=1.0f;
823 priv->sliderRadius=0.0f;
824 priv->sliderColor=NULL;
825 priv->slider=clutter_canvas_new();
826 priv->signalButtonReleasedID=0;
827 priv->signalMotionEventID=0;
828 priv->dragDevice=NULL;
829
830 /* Set up actor */
831 clutter_actor_set_reactive(CLUTTER_ACTOR(self), TRUE);
832 clutter_actor_set_content(CLUTTER_ACTOR(self), priv->slider);
833 if(priv->orientation==CLUTTER_ORIENTATION_HORIZONTAL) clutter_actor_set_request_mode(CLUTTER_ACTOR(self), CLUTTER_REQUEST_HEIGHT_FOR_WIDTH);
834 else clutter_actor_set_request_mode(CLUTTER_ACTOR(self), CLUTTER_REQUEST_WIDTH_FOR_HEIGHT);
835
836 /* Connect signals */
837 g_signal_connect_swapped(priv->slider, "draw", G_CALLBACK(_xfdashboard_scrollbar_on_draw_slider), self);
838 g_signal_connect(self, "button-press-event", G_CALLBACK(_xfdashboard_scrollbar_on_button_pressed), NULL);
839 g_signal_connect(self, "scroll-event", G_CALLBACK(_xfdashboard_scrollbar_on_scroll_event), NULL);
840 }
841
842 /* IMPLEMENTATION: Public API */
843
844 /* Create new actor */
xfdashboard_scrollbar_new(ClutterOrientation inOrientation)845 ClutterActor* xfdashboard_scrollbar_new(ClutterOrientation inOrientation)
846 {
847 return(g_object_new(XFDASHBOARD_TYPE_SCROLLBAR,
848 "orientation", inOrientation,
849 NULL));
850 }
851
852 /* Get/set orientation */
xfdashboard_scrollbar_get_orientation(XfdashboardScrollbar * self)853 gfloat xfdashboard_scrollbar_get_orientation(XfdashboardScrollbar *self)
854 {
855 g_return_val_if_fail(XFDASHBOARD_IS_SCROLLBAR(self), CLUTTER_ORIENTATION_HORIZONTAL);
856
857 return(self->priv->spacing);
858 }
859
xfdashboard_scrollbar_set_orientation(XfdashboardScrollbar * self,ClutterOrientation inOrientation)860 void xfdashboard_scrollbar_set_orientation(XfdashboardScrollbar *self, ClutterOrientation inOrientation)
861 {
862 XfdashboardScrollbarPrivate *priv;
863
864 g_return_if_fail(XFDASHBOARD_IS_SCROLLBAR(self));
865 g_return_if_fail(inOrientation==CLUTTER_ORIENTATION_HORIZONTAL || inOrientation==CLUTTER_ORIENTATION_VERTICAL);
866
867 priv=self->priv;
868
869 /* Only set value if it changes */
870 if(inOrientation==priv->orientation) return;
871
872 /* Set new value */
873 priv->orientation=inOrientation;
874
875 if(priv->orientation==CLUTTER_ORIENTATION_HORIZONTAL) clutter_actor_set_request_mode(CLUTTER_ACTOR(self), CLUTTER_REQUEST_HEIGHT_FOR_WIDTH);
876 else clutter_actor_set_request_mode(CLUTTER_ACTOR(self), CLUTTER_REQUEST_WIDTH_FOR_HEIGHT);
877
878 if(priv->slider) clutter_content_invalidate(priv->slider);
879 clutter_actor_queue_relayout(CLUTTER_ACTOR(self));
880
881 /* Notify about property change */
882 g_object_notify_by_pspec(G_OBJECT(self), XfdashboardScrollbarProperties[PROP_ORIENTATION]);
883 }
884
885 /* Get/set value */
xfdashboard_scrollbar_get_value(XfdashboardScrollbar * self)886 gfloat xfdashboard_scrollbar_get_value(XfdashboardScrollbar *self)
887 {
888 g_return_val_if_fail(XFDASHBOARD_IS_SCROLLBAR(self), 0.0f);
889
890 return(self->priv->value);
891 }
892
xfdashboard_scrollbar_set_value(XfdashboardScrollbar * self,gfloat inValue)893 void xfdashboard_scrollbar_set_value(XfdashboardScrollbar *self, gfloat inValue)
894 {
895 XfdashboardScrollbarPrivate *priv;
896 gboolean enforceNewValue;
897
898 g_return_if_fail(XFDASHBOARD_IS_SCROLLBAR(self));
899 g_return_if_fail(inValue>=0.0f);
900
901 priv=self->priv;
902
903 /* Check if value is within range */
904 enforceNewValue=FALSE;
905 if(inValue+priv->valueRange>priv->range)
906 {
907 gfloat oldValue;
908
909 oldValue=inValue;
910 inValue=MAX(0.0f, priv->range-priv->valueRange);
911 XFDASHBOARD_DEBUG(self, ACTOR,
912 "Adjusting value %.2f to %.2f in scrollbar to fit into range [0-%.2f]",
913 oldValue,
914 inValue,
915 priv->range);
916 enforceNewValue=TRUE;
917 }
918
919 /* Only set value if it changes */
920 if(inValue==priv->value && enforceNewValue==FALSE) return;
921
922 /* Set new value */
923 priv->value=inValue;
924 if(priv->slider) clutter_content_invalidate(priv->slider);
925 clutter_actor_queue_redraw(CLUTTER_ACTOR(self));
926
927 /* Notify about property change */
928 g_object_notify_by_pspec(G_OBJECT(self), XfdashboardScrollbarProperties[PROP_VALUE]);
929 g_signal_emit(self, XfdashboardScrollbarSignals[SIGNAL_VALUE_CHANGED], 0, priv->value);
930 }
931
932 /* Get value range */
xfdashboard_scrollbar_get_value_range(XfdashboardScrollbar * self)933 gfloat xfdashboard_scrollbar_get_value_range(XfdashboardScrollbar *self)
934 {
935 g_return_val_if_fail(XFDASHBOARD_IS_SCROLLBAR(self), 0.0f);
936
937 return(self->priv->valueRange);
938 }
939
940 /* Get/set range */
xfdashboard_scrollbar_get_range(XfdashboardScrollbar * self)941 gfloat xfdashboard_scrollbar_get_range(XfdashboardScrollbar *self)
942 {
943 g_return_val_if_fail(XFDASHBOARD_IS_SCROLLBAR(self), 0.0f);
944
945 return(self->priv->range);
946 }
947
xfdashboard_scrollbar_set_range(XfdashboardScrollbar * self,gfloat inRange)948 void xfdashboard_scrollbar_set_range(XfdashboardScrollbar *self, gfloat inRange)
949 {
950 XfdashboardScrollbarPrivate *priv;
951
952 g_return_if_fail(XFDASHBOARD_IS_SCROLLBAR(self));
953 g_return_if_fail(inRange>=0.0f);
954
955 priv=self->priv;
956
957 /* Only set value if it changes */
958 if(inRange==priv->range) return;
959
960 /* Freeze notification */
961 g_object_freeze_notify(G_OBJECT(self));
962
963 /* Set new value */
964 priv->range=inRange;
965 if(priv->slider) clutter_content_invalidate(priv->slider);
966 clutter_actor_queue_redraw(CLUTTER_ACTOR(self));
967
968 /* Notify about property change */
969 g_object_notify_by_pspec(G_OBJECT(self), XfdashboardScrollbarProperties[PROP_RANGE]);
970
971 /* Check if value is still within new range otherwise adjust value */
972 if(priv->value>priv->range)
973 {
974 gfloat oldValue;
975
976 XFDASHBOARD_DEBUG(self, ACTOR,
977 "Adjusting value %.2f in scrollbar to fit into new range %.2f",
978 priv->value,
979 priv->range);
980 oldValue=priv->value;
981 xfdashboard_scrollbar_set_value(self, priv->range);
982 XFDASHBOARD_DEBUG(self, ACTOR,
983 "Adjusted value %.2f to %.2f in scrollbar to fit into new range %.2f",
984 oldValue,
985 priv->value,
986 priv->range);
987 }
988
989 /* Thaw notification */
990 g_object_thaw_notify(G_OBJECT(self));
991 }
992
993 /* Get/set page-size factor */
xfdashboard_scrollbar_get_page_size_factor(XfdashboardScrollbar * self)994 gfloat xfdashboard_scrollbar_get_page_size_factor(XfdashboardScrollbar *self)
995 {
996 g_return_val_if_fail(XFDASHBOARD_IS_SCROLLBAR(self), 0.0f);
997
998 return(self->priv->pageSizeFactor);
999 }
1000
xfdashboard_scrollbar_set_page_size_factor(XfdashboardScrollbar * self,gfloat inFactor)1001 void xfdashboard_scrollbar_set_page_size_factor(XfdashboardScrollbar *self, gfloat inFactor)
1002 {
1003 XfdashboardScrollbarPrivate *priv;
1004
1005 g_return_if_fail(XFDASHBOARD_IS_SCROLLBAR(self));
1006 g_return_if_fail(inFactor>=0.1f && inFactor<=1.0f);
1007
1008 priv=self->priv;
1009
1010 /* Only set value if it changes */
1011 if(inFactor==priv->pageSizeFactor) return;
1012
1013 /* Set new value */
1014 priv->pageSizeFactor=inFactor;
1015
1016 /* Notify about property change */
1017 g_object_notify_by_pspec(G_OBJECT(self), XfdashboardScrollbarProperties[PROP_PAGE_SIZE_FACTOR]);
1018 }
1019
1020 /* Get/set spacing */
xfdashboard_scrollbar_get_spacing(XfdashboardScrollbar * self)1021 gfloat xfdashboard_scrollbar_get_spacing(XfdashboardScrollbar *self)
1022 {
1023 g_return_val_if_fail(XFDASHBOARD_IS_SCROLLBAR(self), 0.0f);
1024
1025 return(self->priv->spacing);
1026 }
1027
xfdashboard_scrollbar_set_spacing(XfdashboardScrollbar * self,gfloat inSpacing)1028 void xfdashboard_scrollbar_set_spacing(XfdashboardScrollbar *self, gfloat inSpacing)
1029 {
1030 XfdashboardScrollbarPrivate *priv;
1031
1032 g_return_if_fail(XFDASHBOARD_IS_SCROLLBAR(self));
1033 g_return_if_fail(inSpacing>=0.0f);
1034
1035 priv=self->priv;
1036
1037 /* Only set value if it changes */
1038 if(inSpacing==priv->spacing) return;
1039
1040 /* Set new value */
1041 priv->spacing=inSpacing;
1042 clutter_actor_queue_relayout(CLUTTER_ACTOR(self));
1043
1044 /* Notify about property change */
1045 g_object_notify_by_pspec(G_OBJECT(self), XfdashboardScrollbarProperties[PROP_SPACING]);
1046 }
1047
1048 /* Get/set slider width (thickness) */
xfdashboard_scrollbar_get_slider_width(XfdashboardScrollbar * self)1049 gfloat xfdashboard_scrollbar_get_slider_width(XfdashboardScrollbar *self)
1050 {
1051 g_return_val_if_fail(XFDASHBOARD_IS_SCROLLBAR(self), 0.0f);
1052
1053 return(self->priv->sliderWidth);
1054 }
1055
xfdashboard_scrollbar_set_slider_width(XfdashboardScrollbar * self,gfloat inWidth)1056 void xfdashboard_scrollbar_set_slider_width(XfdashboardScrollbar *self, gfloat inWidth)
1057 {
1058 XfdashboardScrollbarPrivate *priv;
1059
1060 g_return_if_fail(XFDASHBOARD_IS_SCROLLBAR(self));
1061 g_return_if_fail(inWidth>=1.0f);
1062
1063 priv=self->priv;
1064
1065 /* Only set value if it changes */
1066 if(inWidth==priv->sliderWidth) return;
1067
1068 /* Set new value */
1069 priv->sliderWidth=inWidth;
1070 clutter_actor_queue_relayout(CLUTTER_ACTOR(self));
1071
1072 /* Notify about property change */
1073 g_object_notify_by_pspec(G_OBJECT(self), XfdashboardScrollbarProperties[PROP_SLIDER_WIDTH]);
1074 }
1075
1076 /* Get/set radius of rounded corners of slider */
xfdashboard_scrollbar_get_slider_radius(XfdashboardScrollbar * self)1077 gfloat xfdashboard_scrollbar_get_slider_radius(XfdashboardScrollbar *self)
1078 {
1079 g_return_val_if_fail(XFDASHBOARD_IS_SCROLLBAR(self), 0.0f);
1080
1081 return(self->priv->sliderWidth);
1082 }
1083
xfdashboard_scrollbar_set_slider_radius(XfdashboardScrollbar * self,gfloat inRadius)1084 void xfdashboard_scrollbar_set_slider_radius(XfdashboardScrollbar *self, gfloat inRadius)
1085 {
1086 XfdashboardScrollbarPrivate *priv;
1087
1088 g_return_if_fail(XFDASHBOARD_IS_SCROLLBAR(self));
1089 g_return_if_fail(inRadius>=0.0f);
1090
1091 priv=self->priv;
1092
1093 /* Only set value if it changes */
1094 if(inRadius==priv->sliderRadius) return;
1095
1096 /* Set new value */
1097 priv->sliderRadius=inRadius;
1098 if(priv->slider) clutter_content_invalidate(priv->slider);
1099
1100 /* Notify about property change */
1101 g_object_notify_by_pspec(G_OBJECT(self), XfdashboardScrollbarProperties[PROP_SLIDER_RADIUS]);
1102 }
1103
1104 /* Get/set color of slider */
xfdashboard_scrollbar_get_slider_color(XfdashboardScrollbar * self)1105 const ClutterColor* xfdashboard_scrollbar_get_slider_color(XfdashboardScrollbar *self)
1106 {
1107 g_return_val_if_fail(XFDASHBOARD_IS_SCROLLBAR(self), NULL);
1108
1109 return(self->priv->sliderColor);
1110 }
1111
xfdashboard_scrollbar_set_slider_color(XfdashboardScrollbar * self,const ClutterColor * inColor)1112 void xfdashboard_scrollbar_set_slider_color(XfdashboardScrollbar *self, const ClutterColor *inColor)
1113 {
1114 XfdashboardScrollbarPrivate *priv;
1115
1116 g_return_if_fail(XFDASHBOARD_IS_SCROLLBAR(self));
1117 g_return_if_fail(inColor);
1118
1119 priv=self->priv;
1120
1121 /* Set value if changed */
1122 if(priv->sliderColor==NULL || clutter_color_equal(inColor, priv->sliderColor)==FALSE)
1123 {
1124 /* Set value */
1125 if(priv->sliderColor) clutter_color_free(priv->sliderColor);
1126 priv->sliderColor=clutter_color_copy(inColor);
1127
1128 /* Invalidate canvas to get it redrawn */
1129 if(priv->slider) clutter_content_invalidate(priv->slider);
1130
1131 /* Notify about property change */
1132 g_object_notify_by_pspec(G_OBJECT(self), XfdashboardScrollbarProperties[PROP_SLIDER_COLOR]);
1133 }
1134 }
1135