1 /*
2  * Copyright (C) 2018-2021 Alexandros Theodotou <alex at zrythm dot org>
3  * Copyright (C) 2020 Ryan Gonzalez <rymg19 at gmail dot com>
4  *
5  * This file is part of Zrythm
6  *
7  * Zrythm is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * Zrythm is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with Zrythm.  If not, see <https://www.gnu.org/licenses/>.
19  */
20 
21 #include "zrythm-config.h"
22 
23 #include <math.h>
24 
25 #include "audio/engine.h"
26 #include "audio/engine_rtaudio.h"
27 #include "audio/engine_sdl.h"
28 #include "audio/pan.h"
29 #include "audio/port.h"
30 #include "gui/widgets/bot_bar.h"
31 #include "gui/widgets/bot_dock_edge.h"
32 #include "gui/widgets/center_dock.h"
33 #include "gui/widgets/clip_editor.h"
34 #include "gui/widgets/clip_editor_inner.h"
35 #include "gui/widgets/dialogs/bind_cc_dialog.h"
36 #include "gui/widgets/main_notebook.h"
37 #include "gui/widgets/main_window.h"
38 #include "gui/widgets/editor_ruler.h"
39 #include "gui/widgets/ruler.h"
40 #include "gui/widgets/timeline_panel.h"
41 #include "gui/widgets/timeline_ruler.h"
42 #include "project.h"
43 #include "settings/settings.h"
44 #include "utils/color.h"
45 #include "utils/gtk.h"
46 #include "utils/localization.h"
47 #include "utils/objects.h"
48 #include "utils/string.h"
49 #include "utils/ui.h"
50 #include "zrythm_app.h"
51 
52 #include <glib/gi18n.h>
53 
54 /**
55  * Sets cursor from icon name.
56  */
57 void
ui_set_cursor_from_icon_name(GtkWidget * widget,const char * name,int offset_x,int offset_y)58 ui_set_cursor_from_icon_name (
59   GtkWidget *  widget,
60   const char * name,
61   int          offset_x,
62   int          offset_y)
63 {
64   GdkWindow * win =
65     gtk_widget_get_parent_window (widget);
66   if (!GDK_IS_WINDOW (win))
67     return;
68 
69   g_return_if_fail (offset_x >= 0 && offset_y >= 0);
70 
71   /* check the cache first */
72   for (int i = 0; i < UI_CACHES->num_cursors; i++)
73     {
74       g_return_if_fail (i < UI_MAX_CURSORS);
75 
76       UiCursor * cursor =
77         &UI_CACHES->cursors[i];
78       if (string_is_equal (name, cursor->name) &&
79           cursor->offset_x == offset_x &&
80           cursor->offset_y == offset_y)
81         {
82           gdk_window_set_cursor (
83             win, cursor->cursor);
84           return;
85         }
86     }
87 
88   GdkPixbuf * pixbuf =
89     gtk_icon_theme_load_icon (
90       gtk_icon_theme_get_default (), name,
91       18, 0, NULL);
92   if (!GDK_IS_PIXBUF (pixbuf))
93     {
94       g_warning (
95         "no pixbuf for %s", name);
96       return;
97     }
98   int adjusted_offset_x =
99     MIN (
100       offset_x, gdk_pixbuf_get_width (pixbuf) - 1);
101   int adjusted_offset_y =
102     MIN (
103       offset_y, gdk_pixbuf_get_height (pixbuf) - 1);
104   GdkCursor * gdk_cursor =
105     gdk_cursor_new_from_pixbuf (
106       gdk_display_get_default (), pixbuf,
107       adjusted_offset_x, adjusted_offset_y);
108 
109   /* add the cursor to the caches */
110   UiCursor * cursor =
111     &UI_CACHES->cursors[UI_CACHES->num_cursors++];
112   strcpy (cursor->name, name);
113   cursor->cursor = gdk_cursor;
114   cursor->pixbuf = pixbuf;
115   cursor->offset_x = offset_x;
116   cursor->offset_y = offset_y;
117 
118   gdk_window_set_cursor (win, cursor->cursor);
119 }
120 
121 /**
122  * Sets cursor from standard cursor name.
123  */
124 void
ui_set_cursor_from_name(GtkWidget * widget,const char * name)125 ui_set_cursor_from_name (
126   GtkWidget * widget,
127   const char * name)
128 {
129   GdkWindow * win =
130     gtk_widget_get_parent_window (widget);
131   if (!GDK_IS_WINDOW (win))
132     return;
133   GdkCursor * cursor =
134     gdk_cursor_new_from_name (
135       gdk_display_get_default (),
136       name);
137   gdk_window_set_cursor (win, cursor);
138 }
139 
140 void
ui_set_pointer_cursor(GtkWidget * widget)141 ui_set_pointer_cursor (
142   GtkWidget * widget)
143 {
144   ui_set_cursor_from_icon_name (
145     GTK_WIDGET (widget), "edit-select", 3, 1);
146 }
147 
148 /**
149  * Shows a popup message of the given type with the
150  * given message.
151  */
152 void
ui_show_message_full(GtkWindow * parent_window,GtkMessageType type,const char * format,...)153 ui_show_message_full (
154   GtkWindow *    parent_window,
155   GtkMessageType type,
156   const char *   format,
157   ...)
158 {
159   va_list args;
160   va_start (args, format);
161 
162   static char buf[40000];
163   vsprintf (buf, format, args);
164 
165   if (ZRYTHM_HAVE_UI)
166     {
167       GtkDialogFlags flags =
168         parent_window ?
169           GTK_DIALOG_DESTROY_WITH_PARENT : 0;
170       GtkWidget * dialog =
171         gtk_message_dialog_new (
172           parent_window, flags, type,
173           GTK_BUTTONS_CLOSE, "%s", buf);
174       gtk_window_set_title (
175         GTK_WINDOW (dialog), PROGRAM_NAME);
176       gtk_window_set_icon_name (
177         GTK_WINDOW (dialog), "zrythm");
178       if (parent_window)
179         {
180           gtk_window_set_transient_for (
181             GTK_WINDOW (dialog), parent_window);
182         }
183       gtk_dialog_run (GTK_DIALOG (dialog));
184       gtk_widget_destroy (dialog);
185     }
186   else
187     {
188       switch (type)
189         {
190         case GTK_MESSAGE_ERROR:
191           g_warning ("%s", buf);
192           break;
193         case GTK_MESSAGE_INFO:
194           g_message ("%s", buf);
195           break;
196         default:
197           g_critical ("should not be reached");
198           break;
199         }
200     }
201 
202   va_end (args);
203 }
204 
205 /**
206  * Returns the matching hit child, or NULL.
207  */
208 GtkWidget *
ui_get_hit_child(GtkContainer * parent,double x,double y,GType type)209 ui_get_hit_child (
210   GtkContainer * parent,
211   double         x, ///< x in parent space
212   double         y, ///< y in parent space
213   GType          type) ///< type to look for
214 {
215   GList *children, *iter;
216 
217   /* go through each overlay child */
218   children =
219     gtk_container_get_children (parent);
220   for (iter = children;
221        iter != NULL;
222        iter = g_list_next (iter))
223     {
224       GtkWidget * widget = GTK_WIDGET (iter->data);
225 
226       if (!gtk_widget_get_visible (widget))
227         continue;
228 
229       GtkAllocation allocation;
230       gtk_widget_get_allocation (
231         widget,
232         &allocation);
233 
234       gint wx, wy;
235       gtk_widget_translate_coordinates (
236         GTK_WIDGET (parent),
237         GTK_WIDGET (widget),
238         (int) x, (int) y, &wx, &wy);
239 
240       /* if hit */
241       if (wx >= 0 &&
242           wx <= allocation.width &&
243           wy >= 0 &&
244           wy <= allocation.height)
245         {
246           /* if type matches */
247           if (G_TYPE_CHECK_INSTANCE_TYPE (
248                 widget,
249                 type))
250             {
251               g_list_free (children);
252               return widget;
253             }
254         }
255     }
256 
257   g_list_free (children);
258   return NULL;
259 }
260 
261 NONNULL
262 static void
px_to_pos(double px,Position * pos,bool use_padding,RulerWidget * ruler)263 px_to_pos (
264   double        px,
265   Position *    pos,
266   bool          use_padding,
267   RulerWidget * ruler)
268 {
269   if (use_padding)
270     {
271       px -= SPACE_BEFORE_START_D;
272 
273       /* clamp at 0 */
274       if (px < 0.0)
275         px = 0.0;
276     }
277 
278   pos->schema_version = POSITION_SCHEMA_VERSION;
279   pos->ticks = px / ruler->px_per_tick;
280   position_update_frames_from_ticks (pos);
281 }
282 
283 /**
284  * Converts from pixels to position.
285  *
286  * Only works with positive numbers. Negatives will
287  * be clamped at 0. If a negative is needed, pass
288  * the abs to this function and then change the
289  * sign.
290  *
291  * @param has_padding Whether @ref px contains
292  *   padding.
293  */
294 void
ui_px_to_pos_timeline(double px,Position * pos,bool has_padding)295 ui_px_to_pos_timeline (
296   double     px,
297   Position * pos,
298   bool       has_padding)
299 {
300   if (!MAIN_WINDOW || !MW_RULER)
301     return;
302 
303   px_to_pos (
304     px, pos, has_padding,
305     Z_RULER_WIDGET (MW_RULER));
306 }
307 
308 
309 /**
310  * Converts from pixels to position.
311  *
312  * Only works with positive numbers. Negatives will
313  * be clamped at 0. If a negative is needed, pass
314  * the abs to this function and then change the
315  * sign.
316  *
317  * @param has_padding Whether @ref px contains
318  *   padding.
319  */
320 void
ui_px_to_pos_editor(double px,Position * pos,bool has_padding)321 ui_px_to_pos_editor (
322   double     px,
323   Position * pos,
324   bool       has_padding)
325 {
326   if (!MAIN_WINDOW || !EDITOR_RULER)
327     return;
328 
329   px_to_pos (
330     px, pos, has_padding,
331     Z_RULER_WIDGET (EDITOR_RULER));
332 }
333 
334 PURE
335 NONNULL
336 static inline int
pos_to_px(Position * pos,int use_padding,RulerWidget * ruler)337 pos_to_px (
338   Position *       pos,
339   int              use_padding,
340   RulerWidget *    ruler)
341 {
342   int px =
343     (int)
344     (pos->ticks * ruler->px_per_tick);
345 
346   if (use_padding)
347     px += SPACE_BEFORE_START;
348 
349   return px;
350 }
351 
352 /**
353  * Converts position to px, optionally adding the
354  * ruler padding.
355  */
356 int
ui_pos_to_px_timeline(Position * pos,int use_padding)357 ui_pos_to_px_timeline (
358   Position *       pos,
359   int              use_padding)
360 {
361   if (!MAIN_WINDOW || !MW_RULER)
362     return 0;
363 
364   return pos_to_px (
365     pos, use_padding, (RulerWidget *) (MW_RULER));
366 }
367 
368 /**
369  * Gets pixels from the position, based on the
370  * piano_roll ruler.
371  */
372 int
ui_pos_to_px_editor(Position * pos,bool use_padding)373 ui_pos_to_px_editor (
374   Position *       pos,
375   bool             use_padding)
376 {
377   if (!MAIN_WINDOW || !EDITOR_RULER)
378     return 0;
379 
380   return
381     pos_to_px (
382       pos, use_padding,
383       Z_RULER_WIDGET (EDITOR_RULER));
384 }
385 
386 /**
387  * @param has_padding Whether the given px contains
388  *   padding.
389  */
390 static long
px_to_frames(double px,int has_padding,RulerWidget * ruler)391 px_to_frames (
392   double        px,
393   int           has_padding,
394   RulerWidget * ruler)
395 {
396   if (has_padding)
397     {
398       px -= SPACE_BEFORE_START;
399 
400       /* clamp at 0 */
401       if (px < 0.0)
402         px = 0.0;
403     }
404 
405   return
406     (long)
407     (((double) AUDIO_ENGINE->frames_per_tick * px) /
408     ruler->px_per_tick);
409 }
410 
411 /**
412  * Converts from pixels to frames.
413  *
414  * Returns the frames.
415  *
416  * @param has_padding Whether then given px contains
417  *   padding.
418  */
419 long
ui_px_to_frames_timeline(double px,int has_padding)420 ui_px_to_frames_timeline (
421   double px,
422   int    has_padding)
423 {
424   if (!MAIN_WINDOW || !MW_RULER)
425     return 0;
426 
427   return
428     px_to_frames (
429       px, has_padding,
430       Z_RULER_WIDGET (MW_RULER));
431 }
432 
433 /**
434  * Converts from pixels to frames.
435  *
436  * Returns the frames.
437  *
438  * @param has_padding Whether then given px contains
439  *   padding.
440  */
441 long
ui_px_to_frames_editor(double px,int has_padding)442 ui_px_to_frames_editor (
443   double px,
444   int    has_padding)
445 {
446   if (!MAIN_WINDOW || !EDITOR_RULER)
447     return 0;
448 
449   return
450     px_to_frames (
451       px, has_padding,
452       Z_RULER_WIDGET (EDITOR_RULER));
453 }
454 
455 /**
456  * Returns if \ref rect is hit or not by the
457  * given coordinate.
458  *
459  * @param check_x Check x-axis for match.
460  * @param check_y Check y-axis for match.
461  * @param x x in parent space.
462  * @param y y in parent space.
463  * @param x_padding Padding to add to the x
464  *   of the object when checking if hit.
465  *   The bigger the padding the more space the
466  *   child will have to get hit.
467  * @param y_padding Padding to add to the y
468  *   of the object when checking if hit.
469  *   The bigger the padding the more space the
470  *   child will have to get hit.
471  */
472 bool
ui_is_point_in_rect_hit(GdkRectangle * rect,const bool check_x,const bool check_y,double x,double y,double x_padding,double y_padding)473 ui_is_point_in_rect_hit (
474   GdkRectangle * rect,
475   const bool     check_x,
476   const bool     check_y,
477   double         x,
478   double         y,
479   double         x_padding,
480   double         y_padding)
481 {
482   /* make coordinates local to the rect */
483   x -= rect->x;
484   y -= rect->y;
485 
486   /* if hit */
487   if ((!check_x ||
488         (x >= - x_padding &&
489          x <= rect->width + x_padding)) &&
490       (!check_y ||
491         (y >= - y_padding &&
492          y <= rect->height + y_padding)))
493     {
494       return true;
495     }
496   return false;
497 }
498 
499 /**
500  * Returns if the child is hit or not by the
501  * coordinates in parent.
502  *
503  * @param check_x Check x-axis for match.
504  * @param check_y Check y-axis for match.
505  * @param x x in parent space.
506  * @param y y in parent space.
507  * @param x_padding Padding to add to the x
508  *   of the object when checking if hit.
509  *   The bigger the padding the more space the
510  *   child will have to get hit.
511  * @param y_padding Padding to add to the y
512  *   of the object when checking if hit.
513  *   The bigger the padding the more space the
514  *   child will have to get hit.
515  */
516 int
ui_is_child_hit(GtkWidget * parent,GtkWidget * child,const int check_x,const int check_y,const double x,const double y,const double x_padding,const double y_padding)517 ui_is_child_hit (
518   GtkWidget * parent,
519   GtkWidget *    child,
520   const int            check_x,
521   const int            check_y,
522   const double         x,
523   const double         y,
524   const double         x_padding,
525   const double         y_padding)
526 {
527   GtkAllocation allocation;
528   gtk_widget_get_allocation (
529     child,
530     &allocation);
531 
532   gint wx, wy;
533   gtk_widget_translate_coordinates (
534     GTK_WIDGET (parent),
535     child,
536     (int) x, (int) y, &wx, &wy);
537 
538   //g_message ("wx wy %d %d", wx, wy);
539 
540   /* if hit */
541   if ((!check_x ||
542         (wx >= - x_padding &&
543          wx <= allocation.width + x_padding)) &&
544       (!check_y ||
545         (wy >= - y_padding &&
546          wy <= allocation.height + y_padding)))
547     {
548       return 1;
549     }
550   return 0;
551 }
552 
553 /**
554  * Hides the notification.
555  *
556  * Used ui_show_notification to be called after
557  * a timeout.
558  */
559 static int
hide_notification_async()560 hide_notification_async ()
561 {
562   gtk_revealer_set_reveal_child (
563     GTK_REVEALER (MAIN_WINDOW->revealer),
564     0);
565 
566   return FALSE;
567 }
568 
569 /**
570  * Shows a notification in the revealer.
571  */
572 void
ui_show_notification(const char * msg)573 ui_show_notification (const char * msg)
574 {
575   gtk_label_set_text (MAIN_WINDOW->notification_label,
576                       msg);
577   gtk_revealer_set_reveal_child (
578     GTK_REVEALER (MAIN_WINDOW->revealer),
579     1);
580   g_timeout_add_seconds (
581     3, (GSourceFunc) hide_notification_async, NULL);
582 }
583 
584 /**
585  * Show notification from non-GTK threads.
586  *
587  * This should be used internally. Use the
588  * ui_show_notification_idle macro instead.
589  */
590 int
ui_show_notification_idle_func(char * msg)591 ui_show_notification_idle_func (char * msg)
592 {
593   ui_show_notification (msg);
594   g_free (msg);
595 
596   return G_SOURCE_REMOVE;
597 }
598 
599 /**
600  * Converts RGB to hex string.
601  */
602 void
ui_rgb_to_hex(double red,double green,double blue,char * buf)603 ui_rgb_to_hex (
604   double red,
605   double green,
606   double blue,
607   char * buf)
608 {
609   sprintf (
610     buf, "#%hhx%hhx%hhx",
611     (char) (red * 255.0),
612     (char) (green * 255.0),
613     (char) (blue * 255.0));
614 }
615 
616 void
ui_gdk_rgba_to_hex(GdkRGBA * color,char * buf)617 ui_gdk_rgba_to_hex (
618   GdkRGBA * color,
619   char *    buf)
620 {
621   ui_rgb_to_hex (
622     color->red, color->green, color->blue, buf);
623 }
624 
625 /**
626  * Returns the modifier type (state mask) from the
627  * given gesture.
628  */
629 void
ui_get_modifier_type_from_gesture(GtkGestureSingle * gesture,GdkModifierType * state_mask)630 ui_get_modifier_type_from_gesture (
631   GtkGestureSingle * gesture,
632   GdkModifierType *  state_mask) ///< return value
633 {
634   GdkEventSequence *sequence =
635     gtk_gesture_single_get_current_sequence (
636       gesture);
637   const GdkEvent * event =
638     gtk_gesture_get_last_event (
639       GTK_GESTURE (gesture), sequence);
640   gdk_event_get_state (event, state_mask);
641 }
642 
643 #define CREATE_SIMPLE_MODEL_BOILERPLATE \
644   enum \
645   { \
646     VALUE_COL, \
647     TEXT_COL, \
648     ID_COL, \
649   }; \
650   GtkTreeIter iter; \
651   GtkListStore *store; \
652   gint i; \
653  \
654   store = \
655   gtk_list_store_new (3, \
656          G_TYPE_INT, \
657          G_TYPE_STRING, \
658   G_TYPE_STRING); \
659  \
660   int num_elements = G_N_ELEMENTS (values); \
661   for (i = 0; i < num_elements; i++) \
662     { \
663       gtk_list_store_append (store, &iter); \
664       char id[40]; \
665       sprintf (id, "%d", values[i]); \
666       gtk_list_store_set (store, &iter, \
667                           VALUE_COL, values[i], \
668                           TEXT_COL, labels[i], \
669                           ID_COL, id, \
670                           -1); \
671     } \
672  \
673   return GTK_TREE_MODEL (store);
674 
675 /**
676  * Creates and returns a language model for combo
677  * boxes.
678  */
679 static GtkTreeModel *
ui_create_language_model()680 ui_create_language_model ()
681 {
682   int values[NUM_LL_LANGUAGES];
683   const char * labels[NUM_LL_LANGUAGES];
684   for (int i = 0; i < NUM_LL_LANGUAGES; i++)
685     {
686       values[i] = i;
687       labels[i] =
688         localization_get_string_w_code (i);
689     }
690 
691   CREATE_SIMPLE_MODEL_BOILERPLATE;
692 }
693 
694 static GtkTreeModel *
ui_create_audio_backends_model(void)695 ui_create_audio_backends_model (void)
696 {
697   const int values[] = {
698     AUDIO_BACKEND_DUMMY,
699 #ifdef HAVE_LIBSOUNDIO
700     AUDIO_BACKEND_DUMMY_LIBSOUNDIO,
701 #endif
702 #ifdef HAVE_ALSA
703     AUDIO_BACKEND_ALSA,
704   #ifdef HAVE_LIBSOUNDIO
705     AUDIO_BACKEND_ALSA_LIBSOUNDIO,
706   #endif
707   #ifdef HAVE_RTAUDIO
708     AUDIO_BACKEND_ALSA_RTAUDIO,
709   #endif
710 #endif /* HAVE_ALSA */
711 #ifdef HAVE_JACK
712     AUDIO_BACKEND_JACK,
713   #ifdef HAVE_LIBSOUNDIO
714     AUDIO_BACKEND_JACK_LIBSOUNDIO,
715   #endif
716   #ifdef HAVE_RTAUDIO
717     AUDIO_BACKEND_JACK_RTAUDIO,
718   #endif
719 #endif /* HAVE_JACK */
720 #ifdef HAVE_PULSEAUDIO
721     AUDIO_BACKEND_PULSEAUDIO,
722   #ifdef HAVE_LIBSOUNDIO
723     AUDIO_BACKEND_PULSEAUDIO_LIBSOUNDIO,
724   #endif
725   #ifdef HAVE_RTAUDIO
726     AUDIO_BACKEND_PULSEAUDIO_RTAUDIO,
727   #endif
728 #endif /* HAVE_PULSEAUDIO */
729 #ifdef __APPLE__
730   #ifdef HAVE_LIBSOUNDIO
731     AUDIO_BACKEND_COREAUDIO_LIBSOUNDIO,
732   #endif
733   #ifdef HAVE_RTAUDIO
734     AUDIO_BACKEND_COREAUDIO_RTAUDIO,
735   #endif
736 #endif /* __APPLE__ */
737 #ifdef HAVE_SDL
738     AUDIO_BACKEND_SDL,
739 #endif
740 #ifdef _WOE32
741   #ifdef HAVE_LIBSOUNDIO
742     AUDIO_BACKEND_WASAPI_LIBSOUNDIO,
743   #endif
744   #ifdef HAVE_RTAUDIO
745     AUDIO_BACKEND_WASAPI_RTAUDIO,
746     AUDIO_BACKEND_ASIO_RTAUDIO,
747   #endif
748 #endif /* _WOE32 */
749   };
750   const gchar *labels[] = {
751     _(audio_backend_str[AUDIO_BACKEND_DUMMY]),
752 #ifdef HAVE_LIBSOUNDIO
753     _(audio_backend_str[AUDIO_BACKEND_DUMMY_LIBSOUNDIO]),
754 #endif
755 #ifdef HAVE_ALSA
756     _(audio_backend_str[AUDIO_BACKEND_ALSA]),
757   #ifdef HAVE_LIBSOUNDIO
758     _(audio_backend_str[AUDIO_BACKEND_ALSA_LIBSOUNDIO]),
759   #endif
760   #ifdef HAVE_RTAUDIO
761     _(audio_backend_str[AUDIO_BACKEND_ALSA_RTAUDIO]),
762   #endif
763 #endif /* HAVE_ALSA */
764 #ifdef HAVE_JACK
765     _(audio_backend_str[AUDIO_BACKEND_JACK]),
766   #ifdef HAVE_LIBSOUNDIO
767     _(audio_backend_str[AUDIO_BACKEND_JACK_LIBSOUNDIO]),
768   #endif
769   #ifdef HAVE_RTAUDIO
770     _(audio_backend_str[AUDIO_BACKEND_JACK_RTAUDIO]),
771   #endif
772 #endif /* HAVE_JACK */
773 #ifdef HAVE_PULSEAUDIO
774     _(audio_backend_str[AUDIO_BACKEND_PULSEAUDIO]),
775   #ifdef HAVE_LIBSOUNDIO
776     _(audio_backend_str[AUDIO_BACKEND_PULSEAUDIO_LIBSOUNDIO]),
777   #endif
778   #ifdef HAVE_RTAUDIO
779     _(audio_backend_str[AUDIO_BACKEND_PULSEAUDIO_RTAUDIO]),
780   #endif
781 #endif /* HAVE_PULSEAUDIO */
782 #ifdef __APPLE__
783   #ifdef HAVE_LIBSOUNDIO
784     _(audio_backend_str[AUDIO_BACKEND_COREAUDIO_LIBSOUNDIO]),
785   #endif
786   #ifdef HAVE_RTAUDIO
787     _(audio_backend_str[AUDIO_BACKEND_COREAUDIO_RTAUDIO]),
788   #endif
789 #endif /* __APPLE__ */
790 #ifdef HAVE_SDL
791     _(audio_backend_str[AUDIO_BACKEND_SDL]),
792 #endif
793 #ifdef _WOE32
794   #ifdef HAVE_LIBSOUNDIO
795     _(audio_backend_str[AUDIO_BACKEND_WASAPI_LIBSOUNDIO]),
796   #endif
797   #ifdef HAVE_RTAUDIO
798     _(audio_backend_str[AUDIO_BACKEND_WASAPI_RTAUDIO]),
799     _(audio_backend_str[AUDIO_BACKEND_ASIO_RTAUDIO]),
800   #endif
801 #endif /* _WOE32 */
802   };
803 
804   CREATE_SIMPLE_MODEL_BOILERPLATE;
805 }
806 static GtkTreeModel *
ui_create_midi_backends_model(void)807 ui_create_midi_backends_model (void)
808 {
809   const int values[] = {
810     MIDI_BACKEND_DUMMY,
811 #ifdef HAVE_ALSA
812     MIDI_BACKEND_ALSA,
813   #ifdef HAVE_RTMIDI
814     MIDI_BACKEND_ALSA_RTMIDI,
815   #endif
816 #endif
817 #ifdef HAVE_JACK
818     MIDI_BACKEND_JACK,
819   #ifdef HAVE_RTMIDI
820     MIDI_BACKEND_JACK_RTMIDI,
821   #endif
822 #endif
823 #ifdef _WOE32
824     MIDI_BACKEND_WINDOWS_MME,
825   #ifdef HAVE_RTMIDI
826     MIDI_BACKEND_WINDOWS_MME_RTMIDI,
827   #endif
828 #endif
829 #ifdef __APPLE__
830   #ifdef HAVE_RTMIDI
831     MIDI_BACKEND_COREMIDI_RTMIDI,
832   #endif
833 #endif
834   };
835   const gchar * labels[] = {
836     _(midi_backend_str[MIDI_BACKEND_DUMMY]),
837 #ifdef HAVE_ALSA
838     _(midi_backend_str[MIDI_BACKEND_ALSA]),
839   #ifdef HAVE_RTMIDI
840     _(midi_backend_str[MIDI_BACKEND_ALSA_RTMIDI]),
841   #endif
842 #endif
843 #ifdef HAVE_JACK
844     _(midi_backend_str[MIDI_BACKEND_JACK]),
845   #ifdef HAVE_RTMIDI
846     _(midi_backend_str[MIDI_BACKEND_JACK_RTMIDI]),
847   #endif
848 #endif
849 #ifdef _WOE32
850     _(midi_backend_str[MIDI_BACKEND_WINDOWS_MME]),
851   #ifdef HAVE_RTMIDI
852     _(midi_backend_str[MIDI_BACKEND_WINDOWS_MME_RTMIDI]),
853   #endif
854 #endif
855 #ifdef __APPLE__
856   #ifdef HAVE_RTMIDI
857     _(midi_backend_str[MIDI_BACKEND_COREMIDI_RTMIDI]),
858   #endif
859 #endif
860   };
861 
862   CREATE_SIMPLE_MODEL_BOILERPLATE;
863 }
864 
865 static GtkTreeModel *
ui_create_pan_algo_model(void)866 ui_create_pan_algo_model (void)
867 {
868 
869   const int values[3] = {
870     PAN_ALGORITHM_LINEAR,
871     PAN_ALGORITHM_SQUARE_ROOT,
872     PAN_ALGORITHM_SINE_LAW,
873   };
874   const gchar *labels[3] = {
875     /* TRANSLATORS: Pan algorithm */
876     _("Linear"),
877     _("Square Root"),
878     _("Sine (Equal Power)"),
879   };
880 
881   CREATE_SIMPLE_MODEL_BOILERPLATE;
882 }
883 
884 static GtkTreeModel *
ui_create_pan_law_model(void)885 ui_create_pan_law_model (void)
886 {
887 
888   const int values[3] = {
889     PAN_LAW_0DB,
890     PAN_LAW_MINUS_3DB,
891     PAN_LAW_MINUS_6DB,
892   };
893   const gchar *labels[3] = {
894     /* TRANSLATORS: Pan algorithm */
895     "0dB",
896     "-3dB",
897     "-6dB",
898   };
899 
900   CREATE_SIMPLE_MODEL_BOILERPLATE;
901 }
902 
903 static GtkTreeModel *
ui_create_buffer_size_model(void)904 ui_create_buffer_size_model (void)
905 {
906   const int values[NUM_AUDIO_ENGINE_BUFFER_SIZES] = {
907     AUDIO_ENGINE_BUFFER_SIZE_16,
908     AUDIO_ENGINE_BUFFER_SIZE_32,
909     AUDIO_ENGINE_BUFFER_SIZE_64,
910     AUDIO_ENGINE_BUFFER_SIZE_128,
911     AUDIO_ENGINE_BUFFER_SIZE_256,
912     AUDIO_ENGINE_BUFFER_SIZE_512,
913     AUDIO_ENGINE_BUFFER_SIZE_1024,
914     AUDIO_ENGINE_BUFFER_SIZE_2048,
915     AUDIO_ENGINE_BUFFER_SIZE_4096,
916   };
917   const gchar *labels[NUM_AUDIO_ENGINE_BUFFER_SIZES] = {
918     "16", "32", "64", "128", "256", "512",
919     "1024", "2048", "4096",
920   };
921 
922   CREATE_SIMPLE_MODEL_BOILERPLATE;
923 }
924 
925 static GtkTreeModel *
ui_create_samplerate_model(void)926 ui_create_samplerate_model (void)
927 {
928   const int values[NUM_AUDIO_ENGINE_SAMPLERATES] = {
929     AUDIO_ENGINE_SAMPLERATE_22050,
930     AUDIO_ENGINE_SAMPLERATE_32000,
931     AUDIO_ENGINE_SAMPLERATE_44100,
932     AUDIO_ENGINE_SAMPLERATE_48000,
933     AUDIO_ENGINE_SAMPLERATE_88200,
934     AUDIO_ENGINE_SAMPLERATE_96000,
935     AUDIO_ENGINE_SAMPLERATE_192000,
936   };
937   const gchar *labels[NUM_AUDIO_ENGINE_BUFFER_SIZES] = {
938     "22050", "32000", "44100", "48000",
939     "88200", "96000", "192000",
940   };
941 
942   CREATE_SIMPLE_MODEL_BOILERPLATE;
943 }
944 
945 /**
946  * Sets up a combo box to have a selection of
947  * languages.
948  */
949 void
ui_setup_language_combo_box(GtkComboBox * language)950 ui_setup_language_combo_box (
951   GtkComboBox * language)
952 {
953   z_gtk_configure_simple_combo_box (
954     language, ui_create_language_model ());
955 
956   gtk_combo_box_set_active (
957     GTK_COMBO_BOX (language),
958     g_settings_get_enum (
959       S_P_UI_GENERAL,
960       "language"));
961 }
962 
963 /**
964  * Sets up an audio backends combo box.
965  */
966 void
ui_setup_audio_backends_combo_box(GtkComboBox * cb)967 ui_setup_audio_backends_combo_box (
968   GtkComboBox * cb)
969 {
970   z_gtk_configure_simple_combo_box (
971     cb, ui_create_audio_backends_model ());
972 
973   char id[40];
974   sprintf (id, "%d",
975     g_settings_get_enum (
976       S_P_GENERAL_ENGINE,
977       "audio-backend"));
978   gtk_combo_box_set_active_id (
979     GTK_COMBO_BOX (cb), id);
980 }
981 
982 /**
983  * Sets up a MIDI backends combo box.
984  */
985 void
ui_setup_midi_backends_combo_box(GtkComboBox * cb)986 ui_setup_midi_backends_combo_box (
987   GtkComboBox * cb)
988 {
989   z_gtk_configure_simple_combo_box (
990     cb, ui_create_midi_backends_model ());
991 
992   char id[40];
993   sprintf (id, "%d",
994     g_settings_get_enum (
995       S_P_GENERAL_ENGINE,
996       "midi-backend"));
997   gtk_combo_box_set_active_id (
998     GTK_COMBO_BOX (cb), id);
999 }
1000 
1001 /**
1002  * Sets up a pan algorithm combo box.
1003  */
1004 void
ui_setup_pan_algo_combo_box(GtkComboBox * cb)1005 ui_setup_pan_algo_combo_box (
1006   GtkComboBox * cb)
1007 {
1008   z_gtk_configure_simple_combo_box (
1009     cb, ui_create_pan_algo_model ());
1010 
1011   gtk_combo_box_set_active (
1012     GTK_COMBO_BOX (cb),
1013     g_settings_get_enum (
1014       S_P_DSP_PAN,
1015       "pan-algorithm"));
1016 }
1017 
1018 /**
1019  * Sets up a pan law combo box.
1020  */
1021 void
ui_setup_pan_law_combo_box(GtkComboBox * cb)1022 ui_setup_pan_law_combo_box (
1023   GtkComboBox * cb)
1024 {
1025   z_gtk_configure_simple_combo_box (
1026     cb, ui_create_pan_law_model ());
1027 
1028   gtk_combo_box_set_active (
1029     GTK_COMBO_BOX (cb),
1030     g_settings_get_enum (
1031       S_P_DSP_PAN,
1032       "pan-law"));
1033 }
1034 
1035 /**
1036  * Returns the "a locale for the language you have
1037  * selected..." text based on the given language.
1038  *
1039  * Must be free'd by caller.
1040  */
1041 char *
ui_get_locale_not_available_string(LocalizationLanguage lang)1042 ui_get_locale_not_available_string (
1043   LocalizationLanguage lang)
1044 {
1045   /* show warning */
1046 #ifdef _WOE32
1047   char template =
1048     _("A locale for the language you have \
1049 selected (%s) is not available. Please install one first \
1050 and restart %s");
1051 #else
1052   char * template =
1053     _("A locale for the language you have selected is \
1054 not available. Please enable one first using \
1055 the steps below and try again.\n\
1056 1. Uncomment any locale starting with the \
1057 language code <b>%s</b> in <b>/etc/locale.gen</b> (needs \
1058 root privileges)\n\
1059 2. Run <b>locale-gen</b> as root\n\
1060 3. Restart %s");
1061 #endif
1062 
1063   const char * code =
1064     localization_get_string_code (lang);
1065   char * str =
1066     g_strdup_printf (template, code, PROGRAM_NAME);
1067 
1068   return str;
1069 }
1070 
1071 /**
1072  * Sets up a pan law combo box.
1073  */
1074 void
ui_setup_buffer_size_combo_box(GtkComboBox * cb)1075 ui_setup_buffer_size_combo_box (
1076   GtkComboBox * cb)
1077 {
1078   z_gtk_configure_simple_combo_box (
1079     cb, ui_create_buffer_size_model ());
1080 
1081   char id[40];
1082   sprintf (id, "%d",
1083     g_settings_get_enum (
1084       S_P_GENERAL_ENGINE,
1085       "buffer-size"));
1086   gtk_combo_box_set_active_id (
1087     GTK_COMBO_BOX (cb), id);
1088 }
1089 
1090 /**
1091  * Sets up a pan law combo box.
1092  */
1093 void
ui_setup_samplerate_combo_box(GtkComboBox * cb)1094 ui_setup_samplerate_combo_box (
1095   GtkComboBox * cb)
1096 {
1097   z_gtk_configure_simple_combo_box (
1098     cb, ui_create_samplerate_model ());
1099 
1100   char id[40];
1101   sprintf (id, "%d",
1102     g_settings_get_enum (
1103       S_P_GENERAL_ENGINE,
1104       "sample-rate"));
1105   gtk_combo_box_set_active_id (
1106     GTK_COMBO_BOX (cb), id);
1107 }
1108 
1109 /**
1110  * Sets up a pan law combo box.
1111  */
1112 void
ui_setup_device_name_combo_box(GtkComboBoxText * cb)1113 ui_setup_device_name_combo_box (
1114   GtkComboBoxText * cb)
1115 {
1116   AudioBackend backend =
1117     (AudioBackend)
1118     g_settings_get_enum (
1119       S_P_GENERAL_ENGINE, "audio-backend");
1120 
1121   gtk_combo_box_text_remove_all (cb);
1122 
1123 #define SETUP_DEVICES(type) \
1124   { \
1125     char * names[1024]; \
1126     int num_names; \
1127     engine_##type##_get_device_names ( \
1128       AUDIO_ENGINE, 0, names, &num_names); \
1129     for (int i = 0; i < num_names; i++) \
1130       { \
1131         gtk_combo_box_text_append ( \
1132           cb, NULL, names[i]); \
1133       } \
1134     char * current_device = \
1135       g_settings_get_string ( \
1136         S_P_GENERAL_ENGINE, \
1137         #type "-audio-device-name"); \
1138     for (int i = 0; i < num_names; i++) \
1139       { \
1140         if (string_is_equal ( \
1141               names[i], current_device)) \
1142           { \
1143             gtk_combo_box_set_active ( \
1144               GTK_COMBO_BOX (cb), i); \
1145           } \
1146         g_free (names[i]); \
1147       } \
1148     g_free (current_device); \
1149   }
1150 
1151   switch (backend)
1152     {
1153 #ifdef HAVE_SDL
1154     case AUDIO_BACKEND_SDL:
1155       SETUP_DEVICES (sdl);
1156       break;
1157 #endif
1158 #ifdef HAVE_RTAUDIO
1159     case AUDIO_BACKEND_ALSA_RTAUDIO:
1160     case AUDIO_BACKEND_JACK_RTAUDIO:
1161     case AUDIO_BACKEND_PULSEAUDIO_RTAUDIO:
1162     case AUDIO_BACKEND_COREAUDIO_RTAUDIO:
1163     case AUDIO_BACKEND_WASAPI_RTAUDIO:
1164     case AUDIO_BACKEND_ASIO_RTAUDIO:
1165       SETUP_DEVICES (rtaudio);
1166       break;
1167 #endif
1168     default:
1169       break;
1170     }
1171 }
1172 
1173 /**
1174  * Sets up the VST paths entry.
1175  */
1176 void
ui_setup_vst_paths_entry(GtkEntry * entry)1177 ui_setup_vst_paths_entry (
1178   GtkEntry * entry)
1179 {
1180   char ** paths =
1181     g_settings_get_strv (
1182       S_P_PLUGINS_PATHS,
1183       "vst-search-paths-windows");
1184   g_return_if_fail (paths);
1185 
1186   int path_idx = 0;
1187   char * path;
1188   char delimited_paths[6000];
1189   delimited_paths[0] = '\0';
1190   while ((path = paths[path_idx++]) != NULL)
1191     {
1192       strcat (delimited_paths, path);
1193       strcat (delimited_paths, ";");
1194     }
1195   delimited_paths[strlen (delimited_paths) - 1] =
1196     '\0';
1197 
1198   gtk_entry_set_text (
1199     entry, delimited_paths);
1200 }
1201 
1202 /**
1203  * Updates the the VST paths in the gsettings from
1204  * the text in the entry.
1205  */
1206 void
ui_update_vst_paths_from_entry(GtkEntry * entry)1207 ui_update_vst_paths_from_entry (
1208   GtkEntry * entry)
1209 {
1210   const char * txt =
1211     gtk_entry_get_text (entry);
1212   g_return_if_fail (txt);
1213   char ** paths =
1214     g_strsplit (txt, ";", 0);
1215   g_settings_set_strv (
1216     S_P_PLUGINS_PATHS, "vst-search-paths-windows",
1217     (const char * const *) paths);
1218   g_free (paths);
1219 }
1220 
1221 /**
1222  * Returns the contrasting color (variation of
1223  * black or white) based on if the given color is
1224  * dark enough or not.
1225  *
1226  * @param src The source color.
1227  * @param dest The desination color to write to.
1228  */
1229 void
ui_get_contrast_color(GdkRGBA * src,GdkRGBA * dest)1230 ui_get_contrast_color (
1231   GdkRGBA * src,
1232   GdkRGBA * dest)
1233 {
1234   /* if color is too bright use dark text,
1235    * otherwise use bright text */
1236   if (color_is_bright (src))
1237     *dest = UI_COLORS->dark_text;
1238   else
1239     *dest = UI_COLORS->bright_text;
1240 }
1241 
1242 /**
1243  * Returns the color in-between two colors.
1244  *
1245  * @param transition How far to transition (0.5 for
1246  *   half).
1247  */
1248 void
ui_get_mid_color(GdkRGBA * dest,const GdkRGBA * c1,const GdkRGBA * c2,const double transition)1249 ui_get_mid_color (
1250   GdkRGBA * dest,
1251   const GdkRGBA * c1,
1252   const GdkRGBA * c2,
1253   const double    transition)
1254 {
1255   dest->red =
1256     c1->red * transition +
1257     c2->red * (1.0 - transition);
1258   dest->green =
1259     c1->green * transition +
1260     c2->green * (1.0 - transition);
1261   dest->blue =
1262     c1->blue * transition +
1263     c2->blue * (1.0 - transition);
1264   dest->alpha =
1265     c1->alpha * transition +
1266     c2->alpha * (1.0 - transition);
1267 }
1268 
1269 /**
1270  * Used in handlers to get the state mask.
1271  */
1272 GdkModifierType
ui_get_state_mask(GtkGesture * gesture)1273 ui_get_state_mask (
1274   GtkGesture * gesture)
1275 {
1276   GdkEventSequence * _sequence =
1277     gtk_gesture_single_get_current_sequence (
1278       GTK_GESTURE_SINGLE (gesture));
1279   const GdkEvent * _event =
1280     gtk_gesture_get_last_event (
1281       GTK_GESTURE (gesture), _sequence);
1282   GdkModifierType state_mask;
1283   gdk_event_get_state (_event, &state_mask);
1284   return state_mask;
1285 }
1286 
1287 /**
1288  * Gets the color the widget should be.
1289  *
1290  * @param color The original color.
1291  * @param is_selected Whether the widget is supposed
1292  *   to be selected or not.
1293  */
1294 void
ui_get_arranger_object_color(GdkRGBA * color,const bool is_hovered,const bool is_selected,const bool is_transient,const bool is_muted)1295 ui_get_arranger_object_color (
1296   GdkRGBA *    color,
1297   const bool   is_hovered,
1298   const bool   is_selected,
1299   const bool   is_transient,
1300   const bool   is_muted)
1301 {
1302   if (DEBUGGING)
1303     color->alpha = 0.4;
1304   else
1305     color->alpha = is_transient ? 0.7 : 1.0;
1306   if (is_muted)
1307     {
1308       color->red = 0.6;
1309       color->green = 0.6;
1310       color->blue = 0.6;
1311     }
1312   if (is_selected)
1313     {
1314       color->red += is_muted ? 0.2 : 0.4;
1315       color->green += 0.2;
1316       color->blue += 0.2;
1317       color->alpha = DEBUGGING ? 0.5 : 1.0;
1318     }
1319   else if (is_hovered)
1320     {
1321       if (color_is_very_bright (color))
1322         {
1323           color->red -= 0.1;
1324           color->green -= 0.1;
1325           color->blue -= 0.1;
1326         }
1327       else
1328         {
1329           color->red += 0.1;
1330           color->green += 0.1;
1331           color->blue += 0.1;
1332         }
1333     }
1334 }
1335 
1336 /**
1337  * Gets a draggable value as a normalized value
1338  * between 0 and 1.
1339  *
1340  * @param size Widget size (either width or height).
1341  * @param start_px Px at start of drag.
1342  * @param cur_px Current px.
1343  * @param last_px Px during last call.
1344  */
1345 double
ui_get_normalized_draggable_value(double size,double cur_val,double start_px,double cur_px,double last_px,double multiplier,UiDragMode mode)1346 ui_get_normalized_draggable_value (
1347   double       size,
1348   double       cur_val,
1349   double       start_px,
1350   double       cur_px,
1351   double       last_px,
1352   double       multiplier,
1353   UiDragMode   mode)
1354 {
1355   switch (mode)
1356     {
1357     case UI_DRAG_MODE_CURSOR:
1358       return CLAMP (cur_px / size, 0.0, 1.0);
1359     case UI_DRAG_MODE_RELATIVE:
1360       return
1361         CLAMP (
1362           cur_val + (cur_px - last_px) / size,
1363           0.0, 1.0);
1364     case UI_DRAG_MODE_RELATIVE_WITH_MULTIPLIER:
1365       return
1366         CLAMP (
1367           cur_val +
1368           (multiplier * (cur_px - last_px)) / size,
1369           0.0, 1.0);
1370     }
1371 
1372   g_return_val_if_reached (0.0);
1373 }
1374 
1375 UiDetail
ui_get_detail_level(void)1376 ui_get_detail_level (void)
1377 {
1378   return
1379     (UiDetail)
1380     g_settings_get_enum (
1381       S_P_UI_GENERAL, "graphic-detail");
1382 }
1383 
1384 /**
1385  * All purpose menuitem callback for binding MIDI
1386  * CC to a port.
1387  *
1388  * An action will be performed if bound.
1389  */
1390 void
ui_bind_midi_cc_item_activate_cb(GtkMenuItem * menuitem,Port * port)1391 ui_bind_midi_cc_item_activate_cb (
1392   GtkMenuItem * menuitem,
1393   Port *        port)
1394 {
1395   BindCcDialogWidget * dialog =
1396     bind_cc_dialog_widget_new (
1397       port, true);
1398 
1399   gtk_dialog_run (GTK_DIALOG (dialog));
1400   gtk_widget_destroy (GTK_WIDGET (dialog));
1401 }
1402 
1403 UiCaches *
ui_caches_new()1404 ui_caches_new ()
1405 {
1406   UiCaches * self = object_new (UiCaches);
1407 
1408   GtkWidget * widget =
1409     gtk_drawing_area_new ();
1410   GtkStyleContext * context =
1411     gtk_widget_get_style_context (widget);
1412 
1413   UiColors * colors = &self->colors;
1414   bool ret;
1415 
1416 #define GET_COLOR_FROM_THEME(cname) \
1417   ret = \
1418     gtk_style_context_lookup_color ( \
1419       context, #cname, \
1420       &colors->cname); \
1421   g_return_val_if_fail (ret, NULL)
1422 
1423   GET_COLOR_FROM_THEME (bright_green);
1424   GET_COLOR_FROM_THEME (darkish_green);
1425   GET_COLOR_FROM_THEME (dark_orange);
1426   GET_COLOR_FROM_THEME (bright_orange);
1427   GET_COLOR_FROM_THEME (matcha);
1428   GET_COLOR_FROM_THEME (prefader_send);
1429   GET_COLOR_FROM_THEME (postfader_send);
1430   GET_COLOR_FROM_THEME (solo_active);
1431   GET_COLOR_FROM_THEME (solo_checked);
1432   GET_COLOR_FROM_THEME (fader_fill_start);
1433   GET_COLOR_FROM_THEME (fader_fill_end);
1434   GET_COLOR_FROM_THEME (highlight_scale_bg);
1435   GET_COLOR_FROM_THEME (highlight_chord_bg);
1436   GET_COLOR_FROM_THEME (highlight_bass_bg);
1437   GET_COLOR_FROM_THEME (highlight_both_bg);
1438   GET_COLOR_FROM_THEME (highlight_scale_fg);
1439   GET_COLOR_FROM_THEME (highlight_chord_fg);
1440   GET_COLOR_FROM_THEME (highlight_bass_fg);
1441   GET_COLOR_FROM_THEME (highlight_both_fg);
1442 
1443 #undef GET_COLOR_FROM_THEME
1444 
1445   gtk_widget_destroy (widget);
1446 
1447   gdk_rgba_parse (
1448     &colors->dark_text, UI_COLOR_DARK_TEXT);
1449   gdk_rgba_parse (
1450     &colors->bright_text, UI_COLOR_BRIGHT_TEXT);
1451   gdk_rgba_parse (
1452     &colors->record_active, UI_COLOR_RECORD_ACTIVE);
1453   gdk_rgba_parse (
1454     &colors->record_checked,
1455     UI_COLOR_RECORD_CHECKED);
1456 
1457   return self;
1458 }
1459 
1460 void
ui_caches_free(UiCaches * self)1461 ui_caches_free (
1462   UiCaches * self)
1463 {
1464   for (int i = 0; i < self->num_cursors; i++)
1465     {
1466       UiCursor * cursor = &self->cursors[i];
1467 
1468       g_object_unref (cursor->cursor);
1469       g_object_unref (cursor->pixbuf);
1470     }
1471 
1472   object_zero_and_free (self);
1473 }
1474