1 /* LIBGIMP - The GIMP Library
2    Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
3 
4    This library is free software: you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public
6    License as published by the Free Software Foundation; either
7    version 3 of the License, or (at your option) any later version.
8 
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Lesser General Public License for more details.
13 
14    You should have received a copy of the GNU Lesser General Public
15    License along with this library.  If not, see
16    <http://www.gnu.org/licenses/>.
17 */
18 
19 // giwtimeline.c (c) 2013 - 2019 G. Finch salsaman+lives@gmail.com
20 
21 // TODO - has own window
22 // - on click, update value
23 // - on motion with button held, update val
24 // - should work exaclt like vslider
25 
26 // add button_press, button_release, motion_notify
27 
28 #include <gtk/gtk.h>
29 
30 #if GTK_CHECK_VERSION(3, 0, 0)
31 
32 #ifndef ROUND
33 #define ROUND(a) ((int)(a<0.?a-.5:a+.5))
34 #endif
35 
36 #include <math.h>
37 #include <string.h>
38 
39 #include "main.h"
40 #include "giwtimeline.h"
41 
42 /**
43    SECTION: giwtimeline
44    @title: GiwTimeline
45    @short_description: A timeline widget with configurable unit and orientation.
46 
47    A timeline widget with configurable unit and orientation.
48  **/
49 
50 #define DEFAULT_TIMELINE_FONT_SCALE  PANGO_SCALE_SMALL
51 #define MINIMUM_INCR              5
52 #define DEFAULT_MAX_SIZE 100000000.
53 
54 enum {
55   PROP_0,
56   PROP_ORIENTATION,
57   PROP_UNIT,
58   PROP_MAX_SIZE
59 };
60 
61 /* All distances below are in 1/72nd's of an inch. (According to
62    Adobe that's a point, but points are really 1/72.27 in.)
63 */
64 
65 static const struct {
66   const gdouble  timeline_scale[16];
67   const gint     subdivide[5];
68 } timeline_metric = {
69   { 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100000 },
70   { 1, 5, 10, 50, 100 }
71 };
72 
73 //static void          giw_timeline_dispose(GObject        *object);
74 static void          giw_timeline_set_property(GObject        *object,
75     guint            prop_id,
76     const GValue   *value,
77     GParamSpec     *pspec);
78 static void          giw_timeline_get_property(GObject        *object,
79     guint           prop_id,
80     GValue         *value,
81     GParamSpec     *pspec);
82 
83 static void          giw_timeline_realize(GtkWidget      *widget);
84 static void          giw_timeline_unrealize(GtkWidget      *widget);
85 static void          giw_timeline_map(GtkWidget      *widget);
86 static void          giw_timeline_unmap(GtkWidget      *widget);
87 static void          giw_timeline_size_allocate(GtkWidget      *widget,
88     GtkAllocation  *allocation);
89 static void          giw_timeline_get_preferred_width(GtkWidget      *widget,
90     gint           *minimum_width,
91     gint           *natural_width);
92 static void          giw_timeline_get_preferred_height(GtkWidget      *widget,
93     gint           *minimum_height,
94     gint           *natural_height);
95 static void          giw_timeline_style_updated(GtkWidget      *widget);
96 static gboolean      giw_timeline_draw(GtkWidget      *widget,
97                                        cairo_t        *cr);
98 
99 static void          giw_timeline_draw_ticks(GiwTimeline      *timeline);
100 static void          giw_timeline_draw_pos(GiwTimeline      *timeline);
101 static void          giw_timeline_make_pixmap(GiwTimeline      *timeline);
102 
103 static PangoLayout *giw_timeline_get_layout(GtkWidget      *widget,
104     const gchar    *text);
105 
106 static void giw_timeline_style_set(GtkWidget *widget, GtkStyle *previous_style);
107 
108 static gboolean giw_timeline_button_press(GtkWidget *widget, GdkEventButton *event);
109 static gboolean giw_timeline_button_release(GtkWidget *widget, GdkEventButton *event);
110 static gboolean giw_timeline_motion_notify(GtkWidget *widget, GdkEventMotion *event);
111 
112 static void giw_timeline_adjustment_changed(GtkAdjustment *adjustment, gpointer data);
113 static void giw_timeline_adjustment_value_changed(GtkAdjustment *adjustment, gpointer data);
114 
G_DEFINE_TYPE(GiwTimeline,giw_timeline,GTK_TYPE_SCALE)115 G_DEFINE_TYPE(GiwTimeline, giw_timeline, GTK_TYPE_SCALE)
116 
117 
118 #define parent_class giw_timeline_parent_class
119 
120 static void giw_timeline_class_init(GiwTimelineClass *klass) {
121   GObjectClass   *object_class = G_OBJECT_CLASS(klass);
122   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
123 
124   //object_class->dispose              = giw_timeline_dispose;
125   object_class->set_property         = giw_timeline_set_property;
126   object_class->get_property         = giw_timeline_get_property;
127 
128   widget_class->realize              = giw_timeline_realize;
129   widget_class->unrealize            = giw_timeline_unrealize;
130   widget_class->map                  = giw_timeline_map;
131   widget_class->unmap                = giw_timeline_unmap;
132   widget_class->get_preferred_width  = giw_timeline_get_preferred_width;
133   widget_class->get_preferred_height = giw_timeline_get_preferred_height;
134   widget_class->size_allocate        = giw_timeline_size_allocate;
135   widget_class->style_updated        = giw_timeline_style_updated;
136   widget_class->draw                 = giw_timeline_draw;
137   widget_class->button_press_event = giw_timeline_button_press;
138   widget_class->button_release_event = giw_timeline_button_release;
139   widget_class->motion_notify_event = giw_timeline_motion_notify;
140   widget_class->style_set = giw_timeline_style_set;
141 
142 #ifndef GTK_PARAM_READABLE
143 #define GTK_PARAM_READABLE (GParamFlags)(G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB)
144 #define GTK_PARAM_WRITABLE (GParamFlags)(G_PARAM_WRITABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB)
145 #define GTK_PARAM_READWRITE (GParamFlags)(G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB)
146 #endif
147 
148   g_object_class_install_property(object_class,
149                                   PROP_ORIENTATION,
150                                   g_param_spec_enum("orientation",
151                                       "Orientation",
152                                       "The orientation of the timeline",
153                                       GTK_TYPE_ORIENTATION,
154                                       GTK_ORIENTATION_HORIZONTAL,
155                                       GTK_PARAM_READWRITE));
156 
157   g_object_class_install_property(object_class,
158                                   PROP_MAX_SIZE,
159                                   g_param_spec_double("max-size",
160                                       "Max Size",
161                                       "Maximum size of the timeline",
162                                       -G_MAXDOUBLE,
163                                       G_MAXDOUBLE,
164                                       0.0,
165                                       GTK_PARAM_READWRITE));
166 
167   gtk_widget_class_install_style_property(widget_class,
168                                           g_param_spec_double("font-scale",
169                                               NULL, NULL,
170                                               0.0,
171                                               G_MAXDOUBLE,
172                                               DEFAULT_TIMELINE_FONT_SCALE,
173                                               GTK_PARAM_READABLE));
174 }
175 
176 
giw_timeline_init(GiwTimeline * timeline)177 static void giw_timeline_init(GiwTimeline *timeline) {
178   gtk_widget_set_has_window(GTK_WIDGET(timeline), TRUE);
179 
180   timeline->orientation   = GTK_ORIENTATION_HORIZONTAL;
181   timeline->unit          = GIW_TIME_UNIT_SECONDS;
182   timeline->max_size      = DEFAULT_MAX_SIZE;
183   timeline->backing_store = NULL;
184   timeline->font_scale    = DEFAULT_TIMELINE_FONT_SCALE;
185   timeline->button       = 0;
186   // Default mouse policy : automatic
187   timeline->mouse_policy = GIW_TIMELINE_MOUSE_AUTOMATIC;
188 }
189 
190 
191 /*static void giw_timeline_dispose(GObject *object) {
192   GiwTimeline        *timeline = GIW_TIMELINE(object);
193 
194   while (timeline->track_widgets)
195     giw_timeline_remove_track_widget(timeline, (GtkWidget *)timeline->track_widgets->data);
196 
197   G_OBJECT_CLASS(parent_class)->dispose(object);
198   }*/
199 
200 
giw_timeline_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)201 static void giw_timeline_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) {
202   GiwTimeline        *timeline = GIW_TIMELINE(object);
203 
204   switch (prop_id) {
205   case PROP_ORIENTATION:
206     timeline->orientation = (GtkOrientation)g_value_get_enum(value);
207     gtk_widget_queue_resize(GTK_WIDGET(timeline));
208     break;
209 
210   case PROP_UNIT:
211     giw_timeline_set_unit(timeline, (GiwTimeUnit)g_value_get_int(value));
212     break;
213 
214   case PROP_MAX_SIZE:
215     giw_timeline_set_max_size(timeline,
216                               g_value_get_double(value));
217     break;
218 
219   default:
220     G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
221     break;
222   }
223 }
224 
225 
giw_timeline_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)226 static void giw_timeline_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) {
227   GiwTimeline        *timeline = GIW_TIMELINE(object);
228 
229   switch (prop_id) {
230   case PROP_ORIENTATION:
231     g_value_set_enum(value, timeline->orientation);
232     break;
233 
234   case PROP_UNIT:
235     g_value_set_int(value, timeline->unit);
236     break;
237 
238   case PROP_MAX_SIZE:
239     g_value_set_double(value, timeline->max_size);
240     break;
241 
242   default:
243     G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
244     break;
245   }
246 }
247 
248 
249 /**
250    giw_timeline_new:
251    @orientation: the timeline's orientation.
252 
253    Creates a new timeline.
254 
255    Return value: a new #GiwTimeline widget.
256 
257  **/
giw_timeline_new(GtkOrientation orientation,GtkAdjustment * adjustment)258 GtkWidget *giw_timeline_new(GtkOrientation orientation, GtkAdjustment *adjustment) {
259   GiwTimeline *timeline;
260 
261   timeline = (GiwTimeline *)g_object_new(GIW_TYPE_TIMELINE, "orientation", orientation, NULL);
262 
263   if (adjustment) giw_timeline_set_adjustment(timeline, adjustment);
264 
265   return GTK_WIDGET(timeline);
266 }
267 
268 
giw_timeline_new_with_adjustment(GtkOrientation orientation,gdouble value,gdouble lower,gdouble upper,gdouble max_size)269 GtkWidget *giw_timeline_new_with_adjustment(GtkOrientation orientation, gdouble value, gdouble lower, gdouble upper,
270     gdouble max_size) {
271   GiwTimeline *timeline;
272 
273   timeline = (GiwTimeline *)g_object_new(GIW_TYPE_TIMELINE,
274                                          "orientation", orientation,
275                                          NULL);
276   giw_timeline_set_adjustment(timeline, (GtkAdjustment *) gtk_adjustment_new(value, lower, upper, 1.0, 1.0, 0.0));
277   giw_timeline_set_max_size(timeline, max_size);
278 
279   return GTK_WIDGET(timeline);
280 }
281 
282 
giw_timeline_set_adjustment(GiwTimeline * timeline,GtkAdjustment * adjustment)283 void giw_timeline_set_adjustment(GiwTimeline *timeline, GtkAdjustment *adjustment) {
284   g_return_if_fail(timeline != NULL);
285   g_return_if_fail(GIW_IS_TIMELINE(timeline));
286   g_return_if_fail(adjustment != NULL);
287 
288   // Freeing the last one
289   if (timeline->adjustment) {
290 #if GTK_CHECK_VERSION(3, 0, 0)
291     g_signal_handler_disconnect((gpointer)(timeline->adjustment), timeline->chsig);
292     g_signal_handler_disconnect((gpointer)(timeline->adjustment), timeline->vchsig);
293     g_signal_handler_disconnect((gpointer)(timeline->adjustment), timeline->tchsig);
294 #else
295     gtk_signal_disconnect_by_data(G_OBJECT(timeline->adjustment), (gpointer) timeline);
296 #endif
297     g_object_unref(G_OBJECT(timeline->adjustment));
298     timeline->adjustment = NULL;
299   }
300 
301   timeline->adjustment = adjustment;
302   g_object_ref(G_OBJECT(timeline->adjustment));
303 
304   timeline->chsig = g_signal_connect(G_OBJECT(adjustment), "changed",
305                                      (GCallback) giw_timeline_adjustment_changed,
306                                      (gpointer) timeline);
307 
308   timeline->vchsig = g_signal_connect(G_OBJECT(adjustment), "value_changed",
309                                       (GCallback) giw_timeline_adjustment_value_changed,
310                                       (gpointer) timeline);
311 
312   timeline->tchsig = g_signal_connect_swapped(G_OBJECT(timeline), "value_changed",
313                      (GCallback) giw_timeline_adjustment_value_changed,
314                      (gpointer)adjustment);
315 
316 #if !GTK_CHECK_VERSION(3,18,0)
317   gtk_adjustment_value_changed(timeline->adjustment);
318   gtk_adjustment_changed(timeline->adjustment);
319 #endif
320 }
321 
322 
giw_timeline_set_mouse_policy(GiwTimeline * timeline,GiwTimelineMousePolicy policy)323 void giw_timeline_set_mouse_policy(GiwTimeline *timeline, GiwTimelineMousePolicy policy) {
324   g_return_if_fail(timeline != NULL);
325   g_return_if_fail(GIW_IS_TIMELINE(timeline));
326 
327   timeline->mouse_policy = policy;
328 }
329 
330 
331 /**
332    giw_timeline_add_track_widget:
333    @timeline: a #GiwTimeline
334    @widget: the track widget to add
335 
336    Adds a "track widget" to the timeline. The timeline will connect to
337    GtkWidget:motion-notify-event: on the track widget and update its
338    position marker accordingly. The marker is correctly updated also
339    for the track widget's children, regardless of whether they are
340    ordinary children of off-screen children.
341 
342 */
giw_timeline_add_track_widget(GiwTimeline * timeline,GtkWidget * widget)343 void giw_timeline_add_track_widget(GiwTimeline *timeline, GtkWidget *widget) {
344 
345   g_return_if_fail(GIW_IS_TIMELINE(timeline));
346   g_return_if_fail(GTK_IS_WIDGET(timeline));
347 
348   g_return_if_fail(g_list_find(timeline->track_widgets, widget) == NULL);
349 
350   timeline->track_widgets = g_list_prepend(timeline->track_widgets, widget);
351 
352   g_signal_connect_swapped(widget, "destroy",
353                            G_CALLBACK(giw_timeline_remove_track_widget),
354                            timeline);
355 }
356 
357 /**
358    giw_timeline_remove_track_widget:
359    @timeline: a #GiwTimeline
360    @widget: the track widget to remove
361 
362    Removes a previously added track widget from the timeline. See
363    giw_timeline_add_track_widget().
364 
365 */
giw_timeline_remove_track_widget(GiwTimeline * timeline,GtkWidget * widget)366 void giw_timeline_remove_track_widget(GiwTimeline *timeline, GtkWidget *widget) {
367 
368   g_return_if_fail(GIW_IS_TIMELINE(timeline));
369   g_return_if_fail(GTK_IS_WIDGET(timeline));
370 
371   g_return_if_fail(g_list_find(timeline->track_widgets, widget) != NULL);
372 
373   timeline->track_widgets = g_list_remove(timeline->track_widgets, widget);
374 
375   g_signal_handlers_disconnect_by_func(widget,
376                                        (gpointer)giw_timeline_remove_track_widget,
377                                        timeline);
378 }
379 
380 /**
381    giw_timeline_set_unit:
382    @timeline: a #GiwTimeline
383    @unit:  the #GiwTimeUnit to set the timeline to
384 
385    This sets the unit of the timeline.
386 
387 */
388 
389 
giw_timeline_set_unit(GiwTimeline * timeline,GiwTimeUnit unit)390 void giw_timeline_set_unit(GiwTimeline *timeline, GiwTimeUnit unit) {
391 
392   g_return_if_fail(GIW_IS_TIMELINE(timeline));
393 
394   if (timeline->unit != unit) {
395     timeline->unit = unit;
396     //g_object_notify(G_OBJECT(timeline), "unit");
397 
398     gtk_widget_queue_draw(GTK_WIDGET(timeline));
399   }
400 }
401 
402 
403 /**
404    giw_timeline_get_unit:
405    @timeline: a #GiwTimeline
406 
407    Return value: the unit currently used in the @timeline widget.
408 
409  **/
giw_timeline_get_unit(GiwTimeline * timeline)410 GiwTimeUnit giw_timeline_get_unit(GiwTimeline *timeline) {
411   g_return_val_if_fail(GIW_IS_TIMELINE(timeline), (GiwTimeUnit)0);
412 
413   return timeline->unit;
414 }
415 
416 
417 /**
418    giw_timeline_get_position:
419    @timeline: a #GiwTimeline
420 
421    Return value: the current position of the @timeline widget.
422 
423    Since: GIW 2.8
424  **/
giw_timeline_get_position(GiwTimeline * timeline)425 static gdouble giw_timeline_get_position(GiwTimeline *timeline) {
426   GtkWidget *widget;
427   GtkRange *range;
428   g_return_val_if_fail(GIW_IS_TIMELINE(timeline), 0.0);
429 
430   widget = GTK_WIDGET(timeline);
431   range = GTK_RANGE(widget);
432 
433   return gtk_range_get_value(range);
434 }
435 
436 /**
437    giw_timeline_set_max_size:
438    @timeline: a #GiwTimeline
439    @max_size: the maximum size of the timeline used when calculating the space to
440    leave for the text
441 
442    This sets the max_size of the timeline.
443 
444 */
giw_timeline_set_max_size(GiwTimeline * timeline,gdouble max_size)445 void giw_timeline_set_max_size(GiwTimeline *timeline,  gdouble max_size) {
446 
447   g_return_if_fail(GIW_IS_TIMELINE(timeline));
448 
449   g_object_freeze_notify(G_OBJECT(timeline));
450   if (timeline->max_size != max_size) {
451     timeline->max_size = max_size;
452     g_object_notify(G_OBJECT(timeline), "max-size");
453   }
454   g_object_thaw_notify(G_OBJECT(timeline));
455 
456   gtk_widget_queue_draw(GTK_WIDGET(timeline));
457 }
458 
459 
giw_timeline_get_max_size(GiwTimeline * timeline)460 gdouble giw_timeline_get_max_size(GiwTimeline *timeline) {
461   g_return_val_if_fail(GIW_IS_TIMELINE(timeline), 0.0);
462   return timeline->max_size;
463 }
464 
465 
466 /**
467    giw_timeline_get_range:
468    @timeline: a #GiwTimeline
469    @lower: location to store lower limit of the timeline, or %NULL
470    @upper: location to store upper limit of the timeline, or %NULL
471    @max_size: location to store the maximum size of the timeline used when
472               calculating the space to leave for the text, or %NULL.
473 
474    Retrieves values indicating the range and current position of a #GiwTimeline.
475    See giw_timeline_set_range().
476 
477  **/
giw_timeline_get_range(GiwTimeline * timeline,gdouble * lower,gdouble * upper,gdouble * max_size)478 static void giw_timeline_get_range(GiwTimeline *timeline, gdouble *lower, gdouble *upper, gdouble *max_size) {
479   GtkAdjustment *adj;
480 
481   g_return_if_fail(GIW_IS_TIMELINE(timeline));
482 
483   adj = timeline->adjustment;
484   if (lower)
485     *lower = gtk_adjustment_get_lower(adj);
486   if (upper)
487     *upper = gtk_adjustment_get_upper(adj);
488   if (max_size)
489     *max_size = timeline->max_size;
490 }
491 
492 
giw_timeline_set_range(GiwTimeline * timeline,gdouble lower,gdouble upper,gdouble max_size)493 void giw_timeline_set_range(GiwTimeline *timeline, gdouble lower, gdouble upper, gdouble max_size) {
494   GtkAdjustment *adj;
495 
496   g_return_if_fail(GIW_IS_TIMELINE(timeline));
497 
498   adj = timeline->adjustment;
499   gtk_adjustment_set_lower(adj, lower);
500   gtk_adjustment_set_upper(adj, upper);
501   giw_timeline_set_max_size(timeline, max_size);
502 }
503 
504 
giw_timeline_realize(GtkWidget * widget)505 static void giw_timeline_realize(GtkWidget *widget) {
506   GtkStyleContext *stylecon;
507   GiwTimeline        *timeline = GIW_TIMELINE(widget);
508   GtkAllocation     allocation;
509   GdkWindowAttr     attributes;
510   gint              attributes_mask;
511 
512   GTK_WIDGET_CLASS(giw_timeline_parent_class)->realize(widget);
513 
514   gtk_widget_get_allocation(widget, &allocation);
515 
516   attributes.window_type = GDK_WINDOW_CHILD;
517   attributes.x           = allocation.x;
518   attributes.y           = allocation.y;
519   attributes.width       = allocation.width;
520   attributes.height      = allocation.height;
521   attributes.wclass      = GDK_INPUT_OUTPUT;
522   attributes.event_mask = gtk_widget_get_events(widget) |
523                           GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
524                           GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK;
525 
526   attributes_mask = GDK_WA_X | GDK_WA_Y;
527 
528   gtk_widget_set_window(widget, (gdk_window_new(gtk_widget_get_window(gtk_widget_get_parent(widget)), &attributes,
529                                  attributes_mask)));
530 
531   stylecon = gtk_style_context_new();
532   gtk_style_context_set_path(stylecon, gtk_widget_get_path(widget));
533   gtk_style_context_set_state(stylecon, GTK_STATE_FLAG_ACTIVE);
534 
535   gdk_window_set_user_data(gtk_widget_get_window(GTK_WIDGET(timeline)), timeline);
536   g_object_ref(G_OBJECT(gtk_widget_get_window(GTK_WIDGET(timeline))));
537 
538   giw_timeline_make_pixmap(timeline);
539 }
540 
541 
giw_timeline_unrealize(GtkWidget * widget)542 static void giw_timeline_unrealize(GtkWidget *widget) {
543   GiwTimeline *timeline = GIW_TIMELINE(widget);
544 
545   if (timeline->backing_store) {
546     cairo_surface_destroy(timeline->backing_store);
547     timeline->backing_store = NULL;
548   }
549 
550   if (timeline->layout) {
551     g_object_unref(timeline->layout);
552     timeline->layout = NULL;
553   }
554 
555   /* if (gtk_widget_get_window(GTK_WIDGET(timeline)) != NULL) { */
556   /*   gdk_window_destroy(gtk_widget_get_window(GTK_WIDGET(timeline))); */
557   /*   gtk_widget_set_window(GTK_WIDGET(timeline), NULL); */
558   /* } */
559 
560   GTK_WIDGET_CLASS(giw_timeline_parent_class)->unrealize(widget);
561 }
562 
563 
giw_timeline_map(GtkWidget * widget)564 static void giw_timeline_map(GtkWidget *widget) {
565   GTK_WIDGET_CLASS(parent_class)->map(widget);
566 
567   if (gtk_widget_get_window(widget) != NULL)
568     gdk_window_show(gtk_widget_get_window(widget));
569 }
570 
571 
giw_timeline_unmap(GtkWidget * widget)572 static void giw_timeline_unmap(GtkWidget *widget) {
573 
574   if (gtk_widget_get_window(widget) != NULL)
575     gdk_window_hide(gtk_widget_get_window(widget));
576 
577   GTK_WIDGET_CLASS(parent_class)->unmap(widget);
578 }
579 
580 
giw_timeline_size_allocate(GtkWidget * widget,GtkAllocation * allocation)581 static void giw_timeline_size_allocate(GtkWidget *widget, GtkAllocation *allocation) {
582   GiwTimeline        *timeline = GIW_TIMELINE(widget);
583   GtkAllocation     widget_allocation;
584   gboolean          resized;
585 
586   gtk_widget_get_allocation(widget, &widget_allocation);
587 
588   resized = (widget_allocation.width  != allocation->width ||
589              widget_allocation.height != allocation->height);
590 
591   gtk_widget_set_allocation(widget, allocation);
592 
593   if (gtk_widget_get_realized(widget)) {
594     gdk_window_move_resize(gtk_widget_get_window(widget),
595                            allocation->x, allocation->y,
596                            allocation->width, allocation->height);
597 
598     if (resized)
599       giw_timeline_make_pixmap(timeline);
600   }
601 }
602 
603 
giw_timeline_size_request(GtkWidget * widget,GtkRequisition * requisition)604 static void giw_timeline_size_request(GtkWidget *widget, GtkRequisition *requisition) {
605   GiwTimeline        *timeline = GIW_TIMELINE(widget);
606   GtkStyleContext  *context = gtk_widget_get_style_context(widget);
607   PangoLayout      *layout;
608   PangoRectangle    ink_rect;
609   GtkBorder         border;
610   gint              size;
611 
612   layout = giw_timeline_get_layout(widget, "0123456789");
613   pango_layout_get_pixel_extents(layout, &ink_rect, NULL);
614 
615   size = 2 + ink_rect.height * 1.7;
616 
617   gtk_style_context_get_border(context, gtk_widget_get_state_flags(widget), &border);
618 
619   requisition->width  = border.left + border.right;
620   requisition->height = border.top + border.bottom;
621 
622   if (timeline->orientation == GTK_ORIENTATION_HORIZONTAL) {
623     requisition->width  += 1;
624     requisition->height += size;
625   } else {
626     requisition->width  += size;
627     requisition->height += 1;
628   }
629 }
630 
631 
giw_timeline_get_preferred_width(GtkWidget * widget,gint * minimum_width,gint * natural_width)632 static void giw_timeline_get_preferred_width(GtkWidget *widget, gint *minimum_width, gint *natural_width) {
633   GtkRequisition requisition;
634 
635   giw_timeline_size_request(widget, &requisition);
636 
637   *minimum_width = *natural_width = requisition.width;
638 }
639 
640 
giw_timeline_get_preferred_height(GtkWidget * widget,gint * minimum_height,gint * natural_height)641 static void giw_timeline_get_preferred_height(GtkWidget *widget, gint *minimum_height, gint *natural_height) {
642   GtkRequisition requisition;
643 
644   giw_timeline_size_request(widget, &requisition);
645 
646   *minimum_height = *natural_height = requisition.height;
647 }
648 
649 
giw_timeline_style_updated(GtkWidget * widget)650 static void giw_timeline_style_updated(GtkWidget *widget) {
651   GiwTimeline        *timeline = GIW_TIMELINE(widget);
652   gtk_widget_style_get(widget,
653                        "font-scale", &timeline->font_scale,
654                        NULL);
655 
656   if (timeline->layout) {
657     g_object_unref(timeline->layout);
658     timeline->layout = NULL;
659   }
660 }
661 
662 
giw_timeline_style_set(GtkWidget * widget,GtkStyle * previous_style)663 static void giw_timeline_style_set(GtkWidget *widget, GtkStyle *previous_style) {
664   giw_timeline_style_updated(widget);
665 }
666 
667 
giw_timeline_draw(GtkWidget * widget,cairo_t * cr)668 static gboolean giw_timeline_draw(GtkWidget *widget, cairo_t *cr) {
669   GiwTimeline        *timeline = GIW_TIMELINE(widget);
670 
671   giw_timeline_draw_ticks(timeline);
672 
673   cairo_set_source_surface(cr, timeline->backing_store, 0, 0);
674   cairo_paint(cr);
675 
676   giw_timeline_draw_pos(timeline);
677 
678   return FALSE;
679 }
680 
681 
giw_timeline_draw_ticks(GiwTimeline * timeline)682 static void giw_timeline_draw_ticks(GiwTimeline *timeline) {
683   GtkWidget        *widget  = GTK_WIDGET(timeline);
684   GtkStyleContext  *context = gtk_widget_get_style_context(widget);
685   GtkAllocation     allocation;
686   GtkBorder         border;
687   GdkRGBA           color;
688   cairo_t          *cr;
689   gint              i;
690   gint              width, height;
691   gint              length, ideal_length;
692   gdouble           lower, upper;  /* Upper and lower limits, in timeline units */
693   gdouble           increment;     /* Number of pixels per unit */
694   gint              scale;         /* Number of units per major unit */
695 
696   gint scaleh, scalem, scales;
697 
698   gint curi, curh, curm, curs;
699   gdouble           start, end, cur;
700   gchar             unit_str[32];
701   gint              digit_height;
702   gint              digit_width;
703   gint              digit_offset;
704   gint              text_size;
705   gint              pos;
706   gdouble           max_size;
707   //GiwTimeUnit       unit;
708   PangoLayout      *layout;
709   PangoRectangle    logical_rect, ink_rect;
710 
711   if (! gtk_widget_is_drawable(widget)) return;
712 
713   gtk_widget_get_allocation(widget, &allocation);
714   gtk_style_context_get_border(context, gtk_widget_get_state_flags(widget), &border);
715 
716   layout = giw_timeline_get_layout(widget, "0123456789");
717   pango_layout_get_extents(layout, &ink_rect, &logical_rect);
718 
719   digit_height = PANGO_PIXELS(ink_rect.height) + 2;
720   digit_width = PANGO_PIXELS(ink_rect.width) + 2;
721   digit_offset = ink_rect.y;
722 
723   if (timeline->orientation == GTK_ORIENTATION_HORIZONTAL) {
724     width  = allocation.width;
725     height = allocation.height - (border.top + border.bottom);
726   } else {
727     width  = allocation.height;
728     height = allocation.width - (border.top + border.bottom);
729   }
730 
731   cr = cairo_create(timeline->backing_store);
732 
733   gtk_render_background(context, cr, 0., 0., allocation.width, allocation.height);
734 
735   gtk_render_frame(context, cr, 0, 0, allocation.width, allocation.height);
736 
737   gtk_style_context_get_color(context, gtk_widget_get_state_flags(widget),
738                               &color);
739   gdk_cairo_set_source_rgba(cr, &color);
740 
741   if (timeline->orientation == GTK_ORIENTATION_HORIZONTAL) {
742     cairo_rectangle(cr,
743                     border.left,
744                     height - border.bottom,
745                     allocation.width - (border.left + border.right),
746                     1);
747   } else {
748     cairo_rectangle(cr,
749                     border.left,
750                     border.top,
751                     1,
752                     allocation.height - (border.top + border.bottom));
753   }
754   cairo_stroke(cr);
755 
756   giw_timeline_get_range(timeline, &lower, &upper, &max_size);
757 
758   if ((upper - lower) == 0.) goto out;
759 
760   increment = (gdouble) width / (upper - lower);
761 
762   /* determine the scale
763        use the maximum extents of the timeline to determine the largest
764        possible number to be displayed.  Calculate the width in pixels
765        of this displayed text. Use this width to find a scale which
766        leaves sufficient room for drawing the timeline.
767   */
768   scale = ceil(max_size);
769 
770   if (timeline->unit == GIW_TIME_UNIT_SECONDS) {
771     g_snprintf(unit_str, sizeof(unit_str), "%d", scale);
772   } else {
773     scaleh = (int)((double)scale / 3600.);
774     scalem = (int)((double)(scale - scaleh * 3600) / 60.);
775     scales = scale - scaleh * 3600 - scalem * 60;
776 
777     if (scale < 0) {
778       scalem = -scalem;
779       scales = -scales;
780     }
781 
782     g_snprintf(unit_str, sizeof(unit_str), "%02d:%02d:%02d", scaleh, scalem, scales);
783   }
784 
785   if (timeline->orientation == GTK_ORIENTATION_HORIZONTAL)
786     text_size = strlen(unit_str) * digit_width + 1;
787   else
788     text_size = strlen(unit_str) * digit_height + 1;
789 
790   for (scale = 0; scale < G_N_ELEMENTS(timeline_metric.timeline_scale); scale++)
791     if (timeline_metric.timeline_scale[scale] * fabs(increment) > 2 * text_size)
792       break;
793 
794   if (scale == G_N_ELEMENTS(timeline_metric.timeline_scale))
795     scale = G_N_ELEMENTS(timeline_metric.timeline_scale) - 1;
796 
797   //unit = giw_timeline_get_unit (timeline);
798   //unit=GIW_TIME_UNIT_SECONDS;
799 
800   /* drawing starts here */
801   length = 0;
802   for (i = G_N_ELEMENTS(timeline_metric.subdivide) - 1; i >= 0; i--) {
803     gdouble subd_incr;
804 
805     /* hack to get proper subdivisions at full pixels */
806     if (scale == 1 && i == 1)
807       subd_incr = 1.0;
808     else
809       subd_incr = ((gdouble) timeline_metric.timeline_scale[scale] /
810                    (gdouble) timeline_metric.subdivide[i]);
811 
812     if (subd_incr * fabs(increment) <= MINIMUM_INCR)
813       continue;
814 
815     /* don't subdivide pixels */
816     if (subd_incr < 1.0)
817       continue;
818 
819     /* Calculate the length of the tickmarks. Make sure that
820        this length increases for each set of ticks
821     */
822     ideal_length = height / (i + 1) - 1;
823     if (ideal_length > ++length)
824       length = ideal_length;
825 
826     if (lower < upper) {
827       start = floor(lower / subd_incr) * subd_incr;
828       end   = ceil(upper / subd_incr) * subd_incr;
829     } else {
830       start = floor(upper / subd_incr) * subd_incr;
831       end   = ceil(lower / subd_incr) * subd_incr;
832     }
833 
834     cairo_set_line_width(cr, 1.);
835 
836     for (cur = start; cur <= end; cur += subd_incr) {
837       pos = ROUND((cur - lower) * increment);
838 
839       if (timeline->orientation == GTK_ORIENTATION_HORIZONTAL) {
840         cairo_rectangle(cr,
841                         pos, height + border.top - length,
842                         1,   length);
843       } else {
844         cairo_rectangle(cr,
845                         height + border.left - length, pos,
846                         length,                        1);
847       }
848       cairo_stroke(cr);
849 
850       /* draw label */
851       if (i < 2) {
852         curi = (int)cur;
853         if (timeline->unit == GIW_TIME_UNIT_SECONDS) {
854           g_snprintf(unit_str, sizeof(unit_str), "%d", curi);
855         } else {
856           curh = (int)(cur / 3600.);
857           curm = (int)((double)(curi - curh * 3600) / 60.);
858           curs = curi - curh * 3600 - curm * 60;
859 
860           if (curi < 0) {
861             curm = -curm;
862             curs = -curs;
863           }
864 
865           g_snprintf(unit_str, sizeof(unit_str), "%02d:%02d:%02d", curh, curm, curs);
866         }
867 
868         if (timeline->orientation == GTK_ORIENTATION_HORIZONTAL) {
869           pango_layout_set_text(layout, unit_str, -1);
870           pango_layout_get_extents(layout, &logical_rect, NULL);
871 
872           cairo_move_to(cr,
873                         pos + 2,
874                         border.top + PANGO_PIXELS(logical_rect.y - digit_offset));
875           pango_cairo_show_layout(cr, layout);
876         } else {
877           gint j;
878 
879           for (j = 0; j < (int) strlen(unit_str); j++) {
880             pango_layout_set_text(layout, unit_str + j, 1);
881             pango_layout_get_extents(layout, NULL, &logical_rect);
882 
883             cairo_move_to(cr,
884                           border.left + 1,
885                           pos + digit_height * j + 2 + PANGO_PIXELS(logical_rect.y - digit_offset));
886             pango_cairo_show_layout(cr, layout);
887           }
888         }
889       }
890     }
891   }
892 
893   cairo_fill(cr);
894 out:
895   cairo_destroy(cr);
896 }
897 
898 
899 /** This is supposed to draw the timeline pointer,
900     it used to work at one point but now it has stopped working.
901 */
giw_timeline_draw_pos(GiwTimeline * timeline)902 static void giw_timeline_draw_pos(GiwTimeline *timeline) {
903   GtkWidget        *widget  = GTK_WIDGET(timeline);
904   GtkStyleContext  *context = gtk_widget_get_style_context(widget);
905   GtkAllocation     allocation;
906   GtkBorder         border;
907   GdkRGBA           color;
908   gint              x, y;
909   gint              width, height;
910   gint              bs_width, bs_height;
911 
912   if (!gtk_widget_is_drawable(widget)) return;
913 
914   gtk_widget_get_allocation(widget, &allocation);
915   gtk_style_context_get_border(context, gtk_widget_get_state_flags(widget), &border);
916 
917   if (timeline->orientation == GTK_ORIENTATION_HORIZONTAL) {
918     width  = allocation.width;
919     height = allocation.height - (border.top + border.bottom);
920 
921     bs_width = height / 2 + 2;
922     bs_width |= 1;  /* make sure it's odd */
923     bs_height = bs_width / 2 + 1;
924   } else {
925     width  = allocation.width - (border.left + border.right);
926     height = allocation.height;
927 
928     bs_height = width / 2 + 2;
929     bs_height |= 1;  /* make sure it's odd */
930     bs_width = bs_height / 2 + 1;
931   }
932 
933   if ((bs_width > 0) && (bs_height > 0)) {
934     cairo_t *cr;
935     gdouble  lower;
936     gdouble  upper;
937     gdouble  position;
938     gdouble  increment;
939 
940 #if !GTK_CHECK_VERSION(3, 22, 0)
941     cr = gdk_cairo_create(gtk_widget_get_window(widget));
942 #else
943     cairo_region_t *reg;
944     GdkWindow *window;
945     GdkDrawingContext *xctx;
946     cairo_rectangle_int_t rect;
947     rect.x = allocation.x;
948     rect.y = allocation.y;
949     rect.width = allocation.width;
950     rect.height = allocation.height;
951 
952     reg = cairo_region_create_rectangle(&rect);
953     window = gtk_widget_get_window(widget);
954     xctx = gdk_window_begin_draw_frame(window, reg);
955     cr = gdk_drawing_context_get_cairo_context(xctx);
956 #endif
957     cairo_rectangle(cr,
958                     allocation.x, allocation.y,
959                     allocation.width, allocation.height);
960     cairo_clip(cr);
961 
962     cairo_translate(cr, allocation.x, allocation.y);
963 
964     //If a backing store exists, restore the timeline
965     if (timeline->backing_store) {
966       cairo_set_source_surface(cr, timeline->backing_store, 0, 0);
967       cairo_rectangle(cr, timeline->xsrc, timeline->ysrc, bs_width, bs_height);
968       cairo_fill(cr);
969     }
970 
971     cairo_new_path(cr);
972 
973     position = giw_timeline_get_position(timeline);
974 
975     giw_timeline_get_range(timeline, &lower, &upper, NULL);
976 
977     if (timeline->orientation == GTK_ORIENTATION_HORIZONTAL) {
978       increment = (gdouble) width / (upper - lower);
979 
980       x = ROUND((position - lower) * increment) + (border.left - bs_width) / 2 - 1;
981       y = (height + bs_height) / 2 + border.top;
982     } else {
983       increment = (gdouble) height / (upper - lower);
984 
985       x = (width + bs_width) / 2 + border.left;
986       y = ROUND((position - lower) * increment) + (border.top - bs_height) / 2 - 1;
987     }
988 
989     gtk_style_context_get_color(context, gtk_widget_get_state_flags(widget),
990                                 &color);
991 
992     gdk_cairo_set_source_rgba(cr, &color);
993 
994     cairo_move_to(cr, x, y);
995 
996     if (timeline->orientation == GTK_ORIENTATION_HORIZONTAL) {
997       cairo_line_to(cr, x + bs_width / 2.0, y + bs_height);
998       cairo_line_to(cr, x + bs_width,       y);
999     } else {
1000       cairo_line_to(cr, x + bs_width, y + bs_height / 2.0);
1001       cairo_line_to(cr, x,            y + bs_height);
1002     }
1003 
1004     cairo_fill(cr);
1005 
1006 #if GTK_CHECK_VERSION(3, 22, 0)
1007     gdk_window_end_draw_frame(window, xctx);
1008 #else
1009     cairo_destroy(cr);
1010 #endif
1011 
1012 #if GTK_CHECK_VERSION(3, 22, 0)
1013     cairo_region_destroy(reg);
1014 #endif
1015     timeline->xsrc = x;
1016     timeline->ysrc = y;
1017   }
1018 }
1019 
1020 
giw_timeline_make_pixmap(GiwTimeline * timeline)1021 static void giw_timeline_make_pixmap(GiwTimeline *timeline) {
1022   GtkWidget        *widget = GTK_WIDGET(timeline);
1023   GtkAllocation     allocation;
1024 
1025   gtk_widget_get_allocation(widget, &allocation);
1026 
1027   if (timeline->backing_store)
1028     cairo_surface_destroy(timeline->backing_store);
1029 
1030   timeline->backing_store =
1031     gdk_window_create_similar_surface(gtk_widget_get_window(widget),
1032                                       CAIRO_CONTENT_COLOR,
1033                                       allocation.width,
1034                                       allocation.height);
1035 }
1036 
1037 
giw_timeline_create_layout(GtkWidget * widget,const gchar * text)1038 static PangoLayout *giw_timeline_create_layout(GtkWidget *widget, const gchar *text) {
1039   GiwTimeline        *timeline = GIW_TIMELINE(widget);
1040   PangoLayout      *layout;
1041   PangoAttrList    *attrs;
1042   PangoAttribute   *attr;
1043 
1044   layout = gtk_widget_create_pango_layout(widget, text);
1045 
1046   attrs = pango_attr_list_new();
1047 
1048   attr = pango_attr_scale_new(timeline->font_scale);
1049   attr->start_index = 0;
1050   attr->end_index   = -1;
1051   pango_attr_list_insert(attrs, attr);
1052 
1053   pango_layout_set_attributes(layout, attrs);
1054   pango_attr_list_unref(attrs);
1055 
1056   return layout;
1057 }
1058 
1059 
giw_timeline_get_layout(GtkWidget * widget,const gchar * text)1060 static PangoLayout *giw_timeline_get_layout(GtkWidget *widget, const gchar *text) {
1061   GiwTimeline *timeline = GIW_TIMELINE(widget);
1062 
1063   if (timeline->layout) {
1064     pango_layout_set_text(timeline->layout, text, -1);
1065     return timeline->layout;
1066   }
1067 
1068   timeline->layout = giw_timeline_create_layout(widget, text);
1069 
1070   return timeline->layout;
1071 }
1072 
1073 
giw_timeline_set_value(GiwTimeline * timeline,gdouble value)1074 void giw_timeline_set_value(GiwTimeline *timeline, gdouble value) {
1075   g_return_if_fail(timeline != NULL);
1076   g_return_if_fail(GIW_IS_TIMELINE(timeline));
1077 
1078   if (gtk_adjustment_get_value(timeline->adjustment) != value) {
1079     gtk_adjustment_set_value(timeline->adjustment, value);
1080 
1081 #if !GTK_CHECK_VERSION(3,18,0)
1082     gtk_adjustment_value_changed(timeline->adjustment);
1083 #endif
1084   }
1085 }
1086 
1087 
giw_timeline_get_value(GiwTimeline * timeline)1088 gdouble giw_timeline_get_value(GiwTimeline *timeline) {
1089   g_return_val_if_fail(timeline != NULL, 0.0);
1090   g_return_val_if_fail(GIW_IS_TIMELINE(timeline), 0.0);
1091   g_return_val_if_fail(timeline->adjustment != NULL, 0.0);
1092 
1093   return (gtk_adjustment_get_value(timeline->adjustment));
1094 }
1095 
1096 
giw_timeline_get_adjustment(GiwTimeline * timeline)1097 GtkAdjustment *giw_timeline_get_adjustment(GiwTimeline *timeline) {
1098   g_return_val_if_fail(timeline != NULL, NULL);
1099   g_return_val_if_fail(GIW_IS_TIMELINE(timeline), NULL);
1100 
1101   return (timeline->adjustment);
1102 }
1103 
1104 
giw_timeline_button_press(GtkWidget * widget,GdkEventButton * event)1105 static gboolean giw_timeline_button_press(GtkWidget *widget, GdkEventButton *event) {
1106   GiwTimeline *timeline;
1107   GtkAllocation allocation;
1108 
1109   g_return_val_if_fail(widget != NULL, FALSE);
1110   g_return_val_if_fail(GIW_IS_TIMELINE(widget), FALSE);
1111   g_return_val_if_fail(event != NULL, FALSE);
1112 
1113   timeline = GIW_TIMELINE(widget);
1114   if (timeline->mouse_policy == GIW_TIMELINE_MOUSE_DISABLED) return FALSE;
1115 
1116   gtk_widget_get_allocation(widget, &allocation);
1117 
1118   if (event->x < 0 || event->x >= allocation.width || event->y <
1119       0 || event->y >= allocation.height) return FALSE;
1120 
1121   if (event->button == 1) {
1122     GtkAdjustment *adjustment = giw_timeline_get_adjustment(timeline);
1123     gdouble new_value;
1124     gint width, pos;
1125 
1126     timeline->button = event->button;
1127 
1128     if (timeline->orientation == GTK_ORIENTATION_HORIZONTAL) {
1129       width  = allocation.width;
1130     } else {
1131       width  = allocation.height;
1132     }
1133 
1134     if (timeline->orientation == GTK_ORIENTATION_HORIZONTAL) {
1135       pos = event->x;
1136     } else {
1137       pos = event->y;
1138     }
1139 
1140     new_value = gtk_adjustment_get_lower(adjustment) + (gdouble)pos / (gdouble)width *
1141                 (gtk_adjustment_get_upper(adjustment) - gtk_adjustment_get_lower(adjustment));
1142     if ((new_value <= gtk_adjustment_get_upper(adjustment)) &&
1143         (new_value >= gtk_adjustment_get_lower(adjustment)))
1144       giw_timeline_set_value(timeline, new_value);
1145 
1146     gtk_widget_queue_draw(GTK_WIDGET(timeline));
1147   }
1148 
1149   return TRUE;
1150 }
1151 
1152 
giw_timeline_button_release(GtkWidget * widget,GdkEventButton * event)1153 static gboolean giw_timeline_button_release(GtkWidget *widget, GdkEventButton *event) {
1154   GiwTimeline *timeline;
1155 
1156   g_return_val_if_fail(widget != NULL, FALSE);
1157   g_return_val_if_fail(GIW_IS_TIMELINE(widget), FALSE);
1158   g_return_val_if_fail(event != NULL, FALSE);
1159 
1160   timeline = GIW_TIMELINE(widget);
1161 
1162   if (timeline->mouse_policy == GIW_TIMELINE_MOUSE_DISABLED) return FALSE;
1163 
1164   if (timeline->button == event->button) {
1165     giw_timeline_button_press(widget, event);
1166     timeline->button = 0;
1167   }
1168 
1169   return TRUE;
1170 }
1171 
1172 
giw_timeline_motion_notify(GtkWidget * widget,GdkEventMotion * event)1173 static gboolean giw_timeline_motion_notify(GtkWidget *widget, GdkEventMotion *event) {
1174   GdkEventButton *ebutton = (GdkEventButton *)event;
1175   GdkDevice *device;
1176   GdkModifierType mask;
1177   GiwTimeline *timeline;
1178 
1179   g_return_val_if_fail(widget != NULL, FALSE);
1180   g_return_val_if_fail(GIW_IS_TIMELINE(widget), FALSE);
1181   g_return_val_if_fail(event != NULL, FALSE);
1182 
1183   timeline = GIW_TIMELINE(widget);
1184   if (timeline->mouse_policy == GIW_TIMELINE_MOUSE_DISABLED) return FALSE;
1185   if (timeline->button == 0) return TRUE;
1186 
1187   device = event->device;
1188   gdk_device_get_state(device, gtk_widget_get_window(widget), NULL, &mask);
1189   if (!(mask & GDK_BUTTON1_MASK)) {
1190     // handle the situation where the button release happened outside the window boundary
1191     timeline->button = 0;
1192     return TRUE;
1193   }
1194 
1195   ebutton->button = timeline->button;
1196   ebutton->y = 0.;
1197   giw_timeline_button_press(widget, ebutton);
1198   return TRUE;
1199 }
1200 
1201 
giw_timeline_adjustment_changed(GtkAdjustment * adjustment,gpointer data)1202 static void giw_timeline_adjustment_changed(GtkAdjustment *adjustment, gpointer data) {
1203   GtkWidget *timeline;
1204 
1205   g_return_if_fail(adjustment != NULL);
1206   g_return_if_fail(data != NULL);
1207   g_return_if_fail(GIW_IS_TIMELINE(data));
1208 
1209   timeline = GTK_WIDGET(data);
1210   gtk_widget_queue_draw(timeline);
1211 }
1212 
1213 
giw_timeline_adjustment_value_changed(GtkAdjustment * adjustment,gpointer data)1214 static void giw_timeline_adjustment_value_changed(GtkAdjustment *adjustment, gpointer data) {
1215   GiwTimeline *timeline;
1216 
1217   g_return_if_fail(adjustment != NULL);
1218   g_return_if_fail(data != NULL);
1219   g_return_if_fail(GIW_IS_TIMELINE(data));
1220 
1221   timeline = GIW_TIMELINE(data);
1222   giw_timeline_draw_pos(timeline);
1223 }
1224 
1225 #endif // #if GTK_CHECK_VERSION(3, 0, 0)
1226 
1227