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