1 /*
2  * Copyright (C) 2018-2021 Alexandros Theodotou <alex at zrythm dot org>
3  *
4  * This file is part of Zrythm
5  *
6  * Zrythm is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Zrythm is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with Zrythm.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 
20 #include "gui/accel.h"
21 #include "gui/widgets/bot_dock_edge.h"
22 #include "gui/widgets/center_dock.h"
23 #include "gui/widgets/left_dock_edge.h"
24 #include "gui/widgets/main_notebook.h"
25 #include "gui/widgets/main_window.h"
26 #include "gui/widgets/mixer.h"
27 #include "gui/widgets/right_dock_edge.h"
28 #include "gui/widgets/visibility.h"
29 #include "settings/settings.h"
30 #include "utils/gtk.h"
31 #include "utils/io.h"
32 #include "utils/objects.h"
33 #include "utils/resources.h"
34 #include "utils/string.h"
35 #include "utils/strv_builder.h"
36 #include "utils/ui.h"
37 #include "zrythm.h"
38 #include "zrythm_app.h"
39 
40 #ifdef GDK_WINDOWING_WAYLAND
41 #include <gdk/gdkwayland.h>
42 #endif
43 
44 #include <glib/gi18n.h>
45 
46 int
z_gtk_widget_destroy_idle(GtkWidget * widget)47 z_gtk_widget_destroy_idle (
48   GtkWidget * widget)
49 {
50   gtk_widget_destroy (widget);
51 
52   return G_SOURCE_REMOVE;
53 }
54 
55 int
z_gtk_get_primary_monitor_scale_factor(void)56 z_gtk_get_primary_monitor_scale_factor (void)
57 {
58   GdkDisplay * display;
59   GdkMonitor * monitor;
60   int scale_factor;
61 
62   if (ZRYTHM_TESTING || !ZRYTHM_HAVE_UI)
63     {
64       return 1;
65     }
66 
67   display =
68     gdk_display_get_default ();
69   if (!display)
70     {
71       g_message ("no display");
72       goto return_default_scale_factor;
73     }
74   monitor =
75     gdk_display_get_primary_monitor (display);
76   if (!monitor)
77     {
78       g_message ("no primary monitor");
79       goto return_default_scale_factor;
80     }
81   scale_factor =
82     gdk_monitor_get_scale_factor (monitor);
83   if (scale_factor < 1)
84     {
85       g_message (
86         "invalid scale factor: %d", scale_factor);
87       goto return_default_scale_factor;
88     }
89 
90   return scale_factor;
91 
92 return_default_scale_factor:
93   g_message (
94     "failed to get refresh rate from device, "
95     "returning default");
96   return 1;
97 }
98 
99 /**
100  * Returns the refresh rate in Hz.
101  */
102 int
z_gtk_get_primary_monitor_refresh_rate(void)103 z_gtk_get_primary_monitor_refresh_rate (void)
104 {
105   GdkDisplay * display;
106   GdkMonitor * monitor;
107   int refresh_rate;
108 
109   if (ZRYTHM_TESTING || !ZRYTHM_HAVE_UI)
110     {
111       return 30;
112     }
113 
114   display =
115     gdk_display_get_default ();
116   if (!display)
117     {
118       g_warning ("no display");
119       goto return_default_refresh_rate;
120     }
121   monitor =
122     gdk_display_get_primary_monitor (display);
123   if (!monitor)
124     {
125       g_warning ("no primary monitor");
126       goto return_default_refresh_rate;
127     }
128   refresh_rate =
129     /* divide by 1000 because gdk returns the
130      * value in milli-Hz */
131     gdk_monitor_get_refresh_rate (monitor) / 1000;
132   if (refresh_rate == 0)
133     {
134       g_warning (
135         "invalid refresh rate: %d", refresh_rate);
136       goto return_default_refresh_rate;
137     }
138 
139   return refresh_rate;
140 
141 return_default_refresh_rate:
142   g_warning (
143     "failed to get refresh rate from device, "
144     "returning default");
145   return 30;
146 }
147 
148 bool
z_gtk_is_wayland(void)149 z_gtk_is_wayland (void)
150 {
151   if (ZRYTHM_TESTING || !ZRYTHM_HAVE_UI)
152     {
153       return false;
154     }
155 
156   GdkDisplay * display =
157     gdk_display_get_default ();
158   g_return_val_if_fail (display, false);
159 
160 #ifdef GDK_WINDOWING_WAYLAND
161   if (GDK_IS_WAYLAND_DISPLAY (display))
162     {
163       /*g_debug ("wayland");*/
164       return true;
165     }
166   else
167     {
168       /*g_debug ("not wayland");*/
169     }
170 #endif
171 
172   return false;
173 }
174 
175 /**
176  * @note Bumps reference, must be decremented after
177  * calling.
178  */
179 void
z_gtk_container_remove_all_children(GtkContainer * container)180 z_gtk_container_remove_all_children (
181   GtkContainer * container)
182 {
183   GList *children, *iter;
184 
185   children =
186     gtk_container_get_children (
187       GTK_CONTAINER (container));
188   for (iter = children;
189        iter != NULL;
190        iter = g_list_next (iter))
191     {
192       /*g_object_ref (GTK_WIDGET (iter->data));*/
193       gtk_container_remove (
194         container,
195         GTK_WIDGET (iter->data));
196     }
197   g_list_free (children);
198 }
199 
200 /**
201  * Returns the primary or secondary label of the
202  * given GtkMessageDialog.
203  *
204  * @param 0 for primary, 1 for secondary.
205  */
206 GtkLabel *
z_gtk_message_dialog_get_label(GtkMessageDialog * self,const int secondary)207 z_gtk_message_dialog_get_label (
208   GtkMessageDialog * self,
209   const int          secondary)
210 {
211   GList *children, *iter;
212 
213   GtkWidget * box =
214     gtk_message_dialog_get_message_area (self);
215 
216   children =
217     gtk_container_get_children (
218       GTK_CONTAINER (box));
219   GtkLabel * label = NULL;
220   for (iter = children;
221        iter != NULL;
222        iter = g_list_next (iter))
223     {
224       label =
225         GTK_LABEL (iter->data);
226       const char * name =
227       gtk_widget_class_get_css_name (
228         GTK_WIDGET_GET_CLASS (label));
229       if (string_is_equal (name, "label") &&
230           !secondary)
231         {
232           break;
233         }
234       else if (
235         string_is_equal (name, "secondary_label") &&
236         secondary)
237         {
238           break;
239         }
240     }
241   g_list_free (children);
242 
243   return label;
244 }
245 
246 void
z_gtk_overlay_add_if_not_exists(GtkOverlay * overlay,GtkWidget * widget)247 z_gtk_overlay_add_if_not_exists (
248   GtkOverlay * overlay,
249   GtkWidget *  widget)
250 {
251   GList *children, *iter;
252 
253   children =
254     gtk_container_get_children (
255       GTK_CONTAINER (overlay));
256   for (iter = children;
257        iter != NULL;
258        iter = g_list_next (iter))
259     {
260       if (iter->data == widget)
261         {
262           g_message ("exists");
263           g_list_free (children);
264           return;
265         }
266     }
267   g_list_free (children);
268 
269   g_message ("not exists, adding");
270   gtk_overlay_add_overlay (overlay, widget);
271 }
272 
273 void
z_gtk_container_destroy_all_children(GtkContainer * container)274 z_gtk_container_destroy_all_children (
275   GtkContainer * container)
276 {
277   GList *children, *iter;
278 
279   children =
280     gtk_container_get_children (GTK_CONTAINER (container));
281   for (iter = children;
282        iter != NULL;
283        iter = g_list_next (iter))
284     gtk_widget_destroy (GTK_WIDGET (iter->data));
285   g_list_free (children);
286 }
287 
288 void
z_gtk_container_remove_children_of_type(GtkContainer * container,GType type)289 z_gtk_container_remove_children_of_type (
290   GtkContainer * container,
291   GType          type)
292 {
293   GList *children, *iter;
294 
295   children =
296     gtk_container_get_children (GTK_CONTAINER (container));
297   for (iter = children;
298        iter != NULL;
299        iter = g_list_next (iter))
300     {
301       GtkWidget * widget = iter->data;
302       if (G_TYPE_CHECK_INSTANCE_TYPE (
303             widget, type))
304         {
305           /*g_object_ref (widget);*/
306           gtk_container_remove (
307             container,
308             widget);
309         }
310     }
311   g_list_free (children);
312 }
313 
314 void
z_gtk_tree_view_remove_all_columns(GtkTreeView * treeview)315 z_gtk_tree_view_remove_all_columns (
316   GtkTreeView * treeview)
317 {
318   g_return_if_fail (
319     treeview && GTK_IS_TREE_VIEW (treeview));
320 
321   GtkTreeViewColumn *column;
322   GList * list, * iter;
323   list =
324     gtk_tree_view_get_columns (treeview);
325   for (iter = list; iter != NULL;
326        iter = g_list_next (iter))
327     {
328       column =
329         GTK_TREE_VIEW_COLUMN (iter->data);
330       gtk_tree_view_remove_column (
331         treeview, column);
332     }
333   g_list_free (list);
334 }
335 
336 /**
337  * Configures a simple value-text combo box using
338  * the given model.
339  */
340 void
z_gtk_configure_simple_combo_box(GtkComboBox * cb,GtkTreeModel * model)341 z_gtk_configure_simple_combo_box (
342   GtkComboBox * cb,
343   GtkTreeModel * model)
344 {
345   enum
346   {
347     VALUE_COL,
348     TEXT_COL,
349     ID_COL,
350   };
351 
352   GtkCellRenderer *renderer;
353   gtk_combo_box_set_model (
354     cb, model);
355   gtk_combo_box_set_id_column (
356     cb, ID_COL);
357   gtk_cell_layout_clear (
358     GTK_CELL_LAYOUT (cb));
359   renderer = gtk_cell_renderer_text_new ();
360   gtk_cell_layout_pack_start (
361     GTK_CELL_LAYOUT (cb),
362     renderer, TRUE);
363   gtk_cell_layout_set_attributes (
364     GTK_CELL_LAYOUT (cb), renderer,
365     "text", TEXT_COL,
366     NULL);
367 }
368 
369 /**
370  * Sets the icon name and optionally text.
371  */
372 void
z_gtk_button_set_icon_name_and_text(GtkButton * btn,const char * name,const char * text,bool icon_first,GtkOrientation orientation,int spacing)373 z_gtk_button_set_icon_name_and_text (
374   GtkButton *  btn,
375   const char * name,
376   const char * text,
377   bool         icon_first,
378   GtkOrientation orientation,
379   int          spacing)
380 {
381   GtkWidget * img =
382     gtk_image_new_from_icon_name (
383       name, GTK_ICON_SIZE_BUTTON);
384   gtk_widget_set_visible (img, 1);
385   z_gtk_container_remove_all_children (
386     GTK_CONTAINER (btn));
387   GtkWidget * box =
388     gtk_box_new (orientation, spacing);
389   gtk_widget_set_visible (box, true);
390   GtkWidget * label =
391     gtk_label_new (text);
392   gtk_widget_set_visible (label, true);
393   if (orientation == GTK_ORIENTATION_HORIZONTAL)
394     {
395       gtk_widget_set_hexpand (label, true);
396     }
397   else
398     {
399       gtk_widget_set_vexpand (label, true);
400     }
401 
402   if (icon_first)
403     {
404       gtk_container_add (GTK_CONTAINER (box), img);
405       gtk_container_add (
406         GTK_CONTAINER (box), label);
407     }
408   else
409     {
410       gtk_container_add (
411         GTK_CONTAINER (box), label);
412       gtk_container_add (GTK_CONTAINER (box), img);
413     }
414   gtk_container_add (
415     GTK_CONTAINER (btn), box);
416 }
417 
418 /**
419  * Sets the icon name and optionally text.
420  */
421 void
z_gtk_button_set_icon_name(GtkButton * btn,const char * name)422 z_gtk_button_set_icon_name (
423   GtkButton *  btn,
424   const char * name)
425 {
426   GtkWidget * img =
427     gtk_image_new_from_icon_name (
428       name, GTK_ICON_SIZE_BUTTON);
429   gtk_widget_set_visible (img, 1);
430   z_gtk_container_remove_all_children (
431     GTK_CONTAINER (btn));
432   gtk_container_add (GTK_CONTAINER (btn), img);
433 }
434 
435 /**
436  * Sets the given emblem to the button, or unsets
437  * the emblem if \ref emblem_icon is NULL.
438  */
439 void
z_gtk_button_set_emblem(GtkButton * btn,const char * emblem_icon_name)440 z_gtk_button_set_emblem (
441   GtkButton *  btn,
442   const char * emblem_icon_name)
443 {
444   GtkWidget * btn_child =
445     gtk_bin_get_child (GTK_BIN (btn));
446   GtkImage * prev_img = NULL;
447   if (GTK_IS_BIN (btn_child))
448     {
449       GtkWidget * inner_child =
450         gtk_bin_get_child (
451           GTK_BIN (btn_child));
452       if (GTK_IS_CONTAINER (inner_child))
453         {
454           prev_img =
455             GTK_IMAGE (
456               z_gtk_container_get_single_child (
457                 GTK_CONTAINER (inner_child)));
458         }
459       else if (GTK_IS_IMAGE (inner_child))
460         {
461           prev_img = GTK_IMAGE (inner_child);
462         }
463       else
464         {
465           g_critical (
466             "unknown type %s",
467             G_OBJECT_TYPE_NAME (inner_child));
468         }
469     }
470   else if (GTK_IS_IMAGE (btn_child))
471     {
472       prev_img = GTK_IMAGE (btn_child);
473     }
474   else if (GTK_IS_CONTAINER (btn_child))
475     {
476       prev_img =
477         GTK_IMAGE (
478           z_gtk_container_get_single_child (
479             GTK_CONTAINER (btn_child)));
480     }
481   else
482     {
483       g_return_if_reached ();
484     }
485 
486   GtkIconSize icon_size;
487   const char * icon_name = NULL;
488   GtkImageType image_type =
489     gtk_image_get_storage_type (prev_img);
490   if (image_type == GTK_IMAGE_ICON_NAME)
491     {
492       gtk_image_get_icon_name (
493         prev_img, &icon_name, &icon_size);
494     }
495   else if (image_type == GTK_IMAGE_GICON)
496     {
497       GIcon * emblemed_icon = NULL;
498       gtk_image_get_gicon (
499         prev_img, &emblemed_icon, &icon_size);
500       g_return_if_fail (emblemed_icon);
501       GIcon * prev_icon = NULL;
502       if (G_IS_EMBLEMED_ICON (emblemed_icon))
503         {
504           prev_icon =
505             g_emblemed_icon_get_icon (
506               G_EMBLEMED_ICON (emblemed_icon));
507         }
508       else if (G_IS_THEMED_ICON (emblemed_icon))
509         {
510           prev_icon = emblemed_icon;
511         }
512       else
513         {
514           g_return_if_reached ();
515         }
516 
517       const char * const * icon_names =
518         g_themed_icon_get_names (
519           G_THEMED_ICON (prev_icon));
520       icon_name =  icon_names[0];
521     }
522   else
523     {
524       g_return_if_reached ();
525     }
526 
527   GIcon * icon = g_themed_icon_new (icon_name);
528 
529   if (emblem_icon_name)
530     {
531       GIcon * dot_icon =
532         g_themed_icon_new ("media-record");
533       GEmblem * emblem =
534         g_emblem_new (dot_icon);
535       icon =
536         g_emblemed_icon_new (icon, emblem);
537     }
538 
539   /* set new icon */
540   GtkWidget * img =
541     gtk_image_new_from_gicon (
542       icon, icon_size);
543   gtk_widget_set_visible (img, true);
544   gtk_button_set_image (btn, img);
545 }
546 
547 /**
548  * Creates a button with the given icon name.
549  */
550 GtkButton *
z_gtk_button_new_with_icon(const char * name)551 z_gtk_button_new_with_icon (
552   const char * name)
553 {
554   GtkButton * btn = GTK_BUTTON (gtk_button_new ());
555   z_gtk_button_set_icon_name (btn, name);
556   gtk_widget_set_visible (GTK_WIDGET (btn), true);
557   return btn;
558 }
559 
560 /**
561  * Creates a toggle button with the given icon name.
562  */
563 GtkToggleButton *
z_gtk_toggle_button_new_with_icon(const char * name)564 z_gtk_toggle_button_new_with_icon (
565   const char * name)
566 {
567   GtkToggleButton * btn =
568     GTK_TOGGLE_BUTTON (gtk_toggle_button_new ());
569   z_gtk_button_set_icon_name (
570     GTK_BUTTON (btn), name);
571   gtk_widget_set_visible (GTK_WIDGET (btn), true);
572 
573   return btn;
574 }
575 
576 /**
577  * Creates a toggle button with the given icon name.
578  */
579 GtkToggleButton *
z_gtk_toggle_button_new_with_icon_and_text(const char * name,const char * text,bool icon_first,GtkOrientation orientation,int spacing)580 z_gtk_toggle_button_new_with_icon_and_text (
581   const char * name,
582   const char * text,
583   bool         icon_first,
584   GtkOrientation orientation,
585   int          spacing)
586 {
587   GtkToggleButton * btn =
588     GTK_TOGGLE_BUTTON (gtk_toggle_button_new ());
589   z_gtk_button_set_icon_name_and_text (
590     GTK_BUTTON (btn), name, text, icon_first,
591     orientation, spacing);
592   gtk_widget_set_visible (GTK_WIDGET (btn), true);
593 
594   return btn;
595 }
596 
597 /**
598  * Creates a button with the given icon name and
599  * text.
600  */
601 GtkButton *
z_gtk_button_new_with_icon_and_text(const char * name,const char * text,bool icon_first,GtkOrientation orientation,int spacing)602 z_gtk_button_new_with_icon_and_text (
603   const char * name,
604   const char * text,
605   bool         icon_first,
606   GtkOrientation orientation,
607   int          spacing)
608 {
609   GtkButton * btn =
610     GTK_BUTTON (gtk_button_new ());
611   z_gtk_button_set_icon_name_and_text (
612     GTK_BUTTON (btn), name, text, icon_first,
613     orientation, spacing);
614   gtk_widget_set_visible (GTK_WIDGET (btn), true);
615 
616   return btn;
617 }
618 
619 /**
620  * Creates a button with the given resource name as icon.
621  */
622 GtkButton *
z_gtk_button_new_with_resource(IconType icon_type,const char * name)623 z_gtk_button_new_with_resource (IconType  icon_type,
624                                 const char * name)
625 {
626   GtkButton * btn = GTK_BUTTON (gtk_button_new ());
627   resources_add_icon_to_button (btn,
628                                 icon_type,
629                                 name);
630   gtk_widget_set_visible (GTK_WIDGET (btn),
631                           1);
632   return btn;
633 }
634 
635 /**
636  * Creates a toggle button with the given resource name as
637  * icon.
638  */
639 GtkToggleButton *
z_gtk_toggle_button_new_with_resource(IconType icon_type,const char * name)640 z_gtk_toggle_button_new_with_resource (
641   IconType  icon_type,
642   const char * name)
643 {
644   GtkToggleButton * btn =
645     GTK_TOGGLE_BUTTON (gtk_toggle_button_new ());
646   resources_add_icon_to_button (GTK_BUTTON (btn),
647                                 icon_type,
648                                 name);
649   gtk_widget_set_visible (GTK_WIDGET (btn),
650                           1);
651   return btn;
652 }
653 
654 /**
655  * Creates a menu item.
656  */
657 GtkMenuItem *
z_gtk_create_menu_item_full(const gchar * label_name,const gchar * icon_name,IconType resource_icon_type,const gchar * resource,bool is_toggle,const char * action_name)658 z_gtk_create_menu_item_full (
659   const gchar *   label_name,
660   const gchar *   icon_name,
661   IconType        resource_icon_type,
662   const gchar *   resource,
663   bool            is_toggle,
664   const char *    action_name)
665 {
666   GtkWidget *box =
667     gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
668   GtkWidget *icon = NULL;
669   if (icon_name)
670     {
671       icon =
672         gtk_image_new_from_icon_name (
673           icon_name, GTK_ICON_SIZE_MENU);
674     }
675   else if (resource)
676     {
677       icon =
678         resources_get_icon (
679           resource_icon_type, resource);
680     }
681   GtkWidget * label =
682     gtk_accel_label_new (label_name);
683   GtkWidget * menu_item;
684   if (is_toggle)
685     menu_item = gtk_check_menu_item_new ();
686   else
687     menu_item = gtk_menu_item_new ();
688 
689   if (icon)
690     gtk_container_add (GTK_CONTAINER (box), icon);
691 
692   gtk_label_set_use_underline (
693     GTK_LABEL (label), TRUE);
694   gtk_label_set_xalign (GTK_LABEL (label), 0.0);
695 
696   if (action_name)
697     {
698       gtk_actionable_set_action_name (
699         GTK_ACTIONABLE (menu_item),
700         action_name);
701       accel_set_accel_label_from_action (
702         GTK_ACCEL_LABEL (label),
703         action_name);
704     }
705 
706   gtk_box_pack_end (
707     GTK_BOX (box), label, TRUE, TRUE, 0);
708 
709   gtk_container_add (GTK_CONTAINER (menu_item), box);
710 
711   gtk_widget_show_all (menu_item);
712 
713   return GTK_MENU_ITEM (menu_item);
714 }
715 
716 /**
717  * Returns a pointer stored at the given selection.
718  */
719 void *
z_gtk_get_single_selection_pointer(GtkTreeView * tv,int column)720 z_gtk_get_single_selection_pointer (
721   GtkTreeView * tv,
722   int           column)
723 {
724   g_return_val_if_fail (
725     GTK_IS_TREE_VIEW (tv), NULL);
726   GtkTreeSelection * ts =
727     gtk_tree_view_get_selection (tv);
728   g_return_val_if_fail (
729     GTK_IS_TREE_SELECTION (ts), NULL);
730   GtkTreeModel * model =
731     gtk_tree_view_get_model (tv);
732   g_return_val_if_fail (
733     GTK_IS_TREE_MODEL (model), NULL);
734   GList * selected_rows =
735     gtk_tree_selection_get_selected_rows (
736       ts, NULL);
737   GtkTreePath * tp =
738     (GtkTreePath *)
739     g_list_first (selected_rows)->data;
740   g_return_val_if_fail (tp, NULL);
741   GtkTreeIter iter;
742   gtk_tree_model_get_iter (
743     model, &iter, tp);
744   GValue value = G_VALUE_INIT;
745   gtk_tree_model_get_value (
746     model, &iter, column, &value);
747   return g_value_get_pointer (&value);
748 }
749 
750 /**
751  * Returns the label from a given GtkMenuItem.
752  *
753  * The menu item must have a box with an optional
754  * icon and a label inside.
755  */
756 GtkLabel *
z_gtk_get_label_from_menu_item(GtkMenuItem * mi)757 z_gtk_get_label_from_menu_item (
758   GtkMenuItem * mi)
759 {
760   GList *children, *iter;
761 
762   /* get box */
763   GtkWidget * box = NULL;
764   children =
765     gtk_container_get_children (
766       GTK_CONTAINER (mi));
767   for (iter = children;
768        iter != NULL;
769        iter = g_list_next (iter))
770     {
771       if (GTK_IS_BOX (iter->data))
772         {
773           box = iter->data;
774           g_list_free (children);
775           break;
776         }
777     }
778   g_warn_if_fail (box);
779 
780   children =
781     gtk_container_get_children (
782       GTK_CONTAINER (box));
783   for (iter = children;
784        iter != NULL;
785        iter = g_list_next (iter))
786     {
787       if (GTK_IS_LABEL (iter->data))
788         {
789           GtkLabel * label = GTK_LABEL (iter->data);
790           g_list_free (children);
791           return label;
792         }
793     }
794 
795   g_warn_if_reached ();
796   g_list_free (children);
797 
798   return NULL;
799 }
800 
801 /**
802  * Gets the tooltip for the given action on the
803  * given widget.
804  *
805  * If the action is valid, an orange text showing
806  * the accelerator will be added to the tooltip.
807  *
808  * @return A new string that must be free'd with
809  * g_free().
810  */
811 char *
z_gtk_get_tooltip_for_action(const char * detailed_action,const char * tooltip)812 z_gtk_get_tooltip_for_action (
813   const char * detailed_action,
814   const char * tooltip)
815 {
816   char * tmp =
817     accel_get_primary_accel_for_action (
818       detailed_action);
819   if (tmp)
820     {
821       char * accel =
822         g_markup_escape_text (tmp, -1);
823       g_free (tmp);
824       char edited_tooltip[800];
825       sprintf (
826         edited_tooltip,
827         "%s <span size=\"x-small\" "
828         "foreground=\"#F79616\">%s</span>",
829         tooltip, accel);
830       g_free (accel);
831       return g_strdup (edited_tooltip);
832     }
833   else
834     {
835       return g_strdup (tooltip);
836     }
837 }
838 
839 /**
840  * Sets the tooltip for the given action on the
841  * given widget.
842  *
843  * If the action is valid, an orange text showing
844  * the accelerator will be added to the tooltip.
845  */
846 void
z_gtk_widget_set_tooltip_for_action(GtkWidget * widget,const char * detailed_action,const char * tooltip)847 z_gtk_widget_set_tooltip_for_action (
848   GtkWidget *  widget,
849   const char * detailed_action,
850   const char * tooltip)
851 {
852   char * edited_tooltip =
853     z_gtk_get_tooltip_for_action (
854       detailed_action, tooltip);
855   gtk_widget_set_tooltip_markup (
856     widget, edited_tooltip);
857   g_free (edited_tooltip);
858 }
859 
860 /**
861  * Sets the tooltip and finds the accel keys and
862  * appends them to the tooltip in small text.
863  */
864 void
z_gtk_set_tooltip_for_actionable(GtkActionable * actionable,const char * tooltip)865 z_gtk_set_tooltip_for_actionable (
866   GtkActionable * actionable,
867   const char *    tooltip)
868 {
869   const char * action_name =
870     gtk_actionable_get_action_name (
871       actionable);
872   char * detailed_action = g_strdup (action_name);
873   GVariant * target_value =
874     gtk_actionable_get_action_target_value (
875       actionable);
876   if (target_value)
877     {
878       g_free (detailed_action);
879       detailed_action =
880         g_action_print_detailed_name (
881           action_name, target_value);
882     }
883   z_gtk_widget_set_tooltip_for_action (
884     GTK_WIDGET (actionable), detailed_action,
885     tooltip);
886   g_free (detailed_action);
887 }
888 
889 /**
890  * Changes the size of the icon inside tool buttons.
891  */
892 void
z_gtk_tool_button_set_icon_size(GtkToolButton * toolbutton,GtkIconSize icon_size)893 z_gtk_tool_button_set_icon_size (
894   GtkToolButton * toolbutton,
895   GtkIconSize     icon_size)
896 {
897   GtkImage * img =
898     GTK_IMAGE (
899       z_gtk_container_get_single_child (
900         GTK_CONTAINER (
901           z_gtk_container_get_single_child (
902             GTK_CONTAINER (
903               z_gtk_container_get_single_child (
904                 GTK_CONTAINER (toolbutton)))))));
905   GtkImageType type =
906     gtk_image_get_storage_type (GTK_IMAGE (img));
907   g_return_if_fail (type == GTK_IMAGE_ICON_NAME);
908   const char * _icon_name;
909   gtk_image_get_icon_name (
910     GTK_IMAGE (img), &_icon_name, NULL);
911   char * icon_name = g_strdup (_icon_name);
912   gtk_image_set_from_icon_name (
913     GTK_IMAGE (img), icon_name,
914     GTK_ICON_SIZE_SMALL_TOOLBAR);
915   g_free (icon_name);
916 }
917 
918 /**
919  * Sets the ellipsize mode of each text cell
920  * renderer in the combo box.
921  */
922 void
z_gtk_combo_box_set_ellipsize_mode(GtkComboBox * self,PangoEllipsizeMode ellipsize)923 z_gtk_combo_box_set_ellipsize_mode (
924   GtkComboBox * self,
925   PangoEllipsizeMode ellipsize)
926 {
927   GList *children, *iter;
928   children =
929     gtk_cell_layout_get_cells  (
930       GTK_CELL_LAYOUT (self));
931   for (iter = children;
932        iter != NULL;
933        iter = g_list_next (iter))
934     {
935       if (!GTK_IS_CELL_RENDERER_TEXT (iter->data))
936         continue;
937 
938       g_object_set (
939         iter->data, "ellipsize", ellipsize,
940         NULL);
941     }
942   g_list_free (children);
943 }
944 
945 /**
946  * Adds the given style class to the widget.
947  */
948 void
z_gtk_widget_add_style_class(GtkWidget * widget,const gchar * class_name)949 z_gtk_widget_add_style_class (
950   GtkWidget   *widget,
951   const gchar *class_name)
952 {
953   g_return_if_fail (GTK_IS_WIDGET (widget));
954   g_return_if_fail (class_name != NULL);
955 
956   gtk_style_context_add_class (
957     gtk_widget_get_style_context (widget),
958     class_name);
959 }
960 
961 /**
962  * Removes the given style class from the widget.
963  */
964 void
z_gtk_widget_remove_style_class(GtkWidget * widget,const gchar * class_name)965 z_gtk_widget_remove_style_class (
966   GtkWidget   *widget,
967   const gchar *class_name)
968 {
969   g_return_if_fail (GTK_IS_WIDGET (widget));
970   g_return_if_fail (class_name != NULL);
971 
972   gtk_style_context_remove_class (
973     gtk_widget_get_style_context (widget),
974     class_name);
975 }
976 
977 /**
978  * Returns the nth child of a container.
979  */
980 GtkWidget *
z_gtk_container_get_nth_child(GtkContainer * container,int index)981 z_gtk_container_get_nth_child (
982   GtkContainer * container,
983   int            index)
984 {
985   GList *children, *iter;
986   children =
987     gtk_container_get_children (container);
988   int i = 0;
989   for (iter = children;
990        iter != NULL;
991        iter = g_list_next (iter))
992     {
993       GtkWidget * widget =
994         GTK_WIDGET (iter->data);
995       if (i++ == index)
996         {
997           g_list_free (children);
998           return widget;
999         }
1000     }
1001   g_list_free (children);
1002   g_return_val_if_reached (NULL);
1003 }
1004 
1005 /**
1006  * Sets the margin on all 4 sides on the widget.
1007  */
1008 void
z_gtk_widget_set_margin(GtkWidget * widget,int margin)1009 z_gtk_widget_set_margin (
1010   GtkWidget * widget,
1011   int         margin)
1012 {
1013   gtk_widget_set_margin_start (widget, margin);
1014   gtk_widget_set_margin_end (widget, margin);
1015   gtk_widget_set_margin_top (widget, margin);
1016   gtk_widget_set_margin_bottom (widget, margin);
1017 }
1018 
1019 GtkFlowBoxChild *
z_gtk_flow_box_get_selected_child(GtkFlowBox * self)1020 z_gtk_flow_box_get_selected_child (
1021   GtkFlowBox * self)
1022 {
1023   GList * list =
1024     gtk_flow_box_get_selected_children (self);
1025   GtkFlowBoxChild * sel_child = NULL;
1026   for (GList * l = list; l != NULL; l = l->next)
1027     {
1028       sel_child = (GtkFlowBoxChild *) l->data;
1029       break;
1030     }
1031   g_list_free (list);
1032 
1033   return sel_child;
1034 }
1035 
1036 /**
1037  * Callback to use for simple directory links.
1038  */
1039 bool
z_gtk_activate_dir_link_func(GtkLabel * label,char * uri,void * data)1040 z_gtk_activate_dir_link_func (
1041   GtkLabel * label,
1042   char *     uri,
1043   void *     data)
1044 {
1045   io_open_directory (uri);
1046 
1047   return TRUE;
1048 }
1049 
1050 GtkSourceLanguageManager *
z_gtk_source_language_manager_get(void)1051 z_gtk_source_language_manager_get (void)
1052 {
1053   GtkSourceLanguageManager * manager =
1054     gtk_source_language_manager_get_default ();
1055 
1056   static bool already_set = false;
1057 
1058   if (already_set)
1059     {
1060       return manager;
1061     }
1062 
1063   /* get the default search paths */
1064   const char * const * before_paths =
1065     gtk_source_language_manager_get_search_path (
1066       manager);
1067 
1068   /* build the new paths */
1069   StrvBuilder * after_paths_builder =
1070     strv_builder_new ();
1071   StrvBuilder * after_paths_builder_tmp =
1072     strv_builder_new ();
1073   int i = 0;
1074   while (before_paths[i])
1075     {
1076       g_debug (
1077         "language specs dir %d: %s",
1078         i, before_paths[i]);
1079       strv_builder_add (
1080         after_paths_builder, before_paths[i]);
1081       strv_builder_add (
1082         after_paths_builder_tmp, before_paths[i]);
1083       i++;
1084     }
1085 
1086   /* add the new path if not already in the list */
1087   char * language_specs_dir =
1088     zrythm_get_dir (
1089       ZRYTHM_DIR_SYSTEM_SOURCEVIEW_LANGUAGE_SPECS_DIR);
1090   g_return_val_if_fail (language_specs_dir, NULL);
1091   char ** tmp_dirs =
1092     strv_builder_end (after_paths_builder_tmp);
1093   if (!g_strv_contains (
1094          (const char * const *) tmp_dirs,
1095          language_specs_dir))
1096     {
1097       strv_builder_add (
1098         after_paths_builder, language_specs_dir);
1099     }
1100   g_strfreev (tmp_dirs);
1101 
1102   char ** dirs =
1103     strv_builder_end (after_paths_builder);
1104 
1105   i = 0;
1106   while (dirs[i])
1107     {
1108       const char * dir = dirs[i];
1109       g_message ("%d: %s", i, dir);
1110       i++;
1111     }
1112 
1113   gtk_source_language_manager_set_search_path (
1114     manager, dirs);
1115 
1116   g_free (language_specs_dir);
1117   g_strfreev (dirs);
1118 
1119   already_set = true;
1120 
1121   return manager;
1122 }
1123 
1124 typedef struct DetachableNotebookData
1125 {
1126   /** Parent window of original notebook. */
1127   GtkWindow *     parent_window;
1128 
1129   /** Original notebook. */
1130   GtkNotebook *   notebook;
1131 
1132   /** Windows for dropping detached tabs. */
1133   GPtrArray *     new_windows;
1134 
1135   /** New notebooks inside new windows. */
1136   GPtrArray *     new_notebooks;
1137 
1138   /** Hashtable of settings schema keys => widget
1139    * pointers. */
1140   GHashTable *    ht;
1141 
1142   /** Window title. */
1143   const char *    title;
1144 
1145   /** Window role. */
1146   const char *    role;
1147 } DetachableNotebookData;
1148 
1149 static void
on_new_notebook_page_removed(GtkNotebook * notebook,GtkWidget * child,guint page_num,GtkWindow * new_window)1150 on_new_notebook_page_removed (
1151   GtkNotebook *            notebook,
1152   GtkWidget *              child,
1153   guint                    page_num,
1154   GtkWindow *              new_window)
1155 {
1156   /* destroy the sub window after the notebook is
1157    * empty */
1158   if (gtk_notebook_get_n_pages (notebook) == 0)
1159     {
1160       gtk_widget_destroy (GTK_WIDGET (new_window));
1161     }
1162 }
1163 
1164 static void
on_new_window_destroyed(GtkWidget * widget,DetachableNotebookData * data)1165 on_new_window_destroyed (
1166   GtkWidget *              widget,
1167   DetachableNotebookData * data)
1168 {
1169   guint idx;
1170   bool found =
1171     g_ptr_array_find (
1172       data->new_windows, widget, &idx);
1173   g_return_if_fail (found);
1174 
1175   GtkNotebook * new_notebook =
1176     g_ptr_array_index (data->new_notebooks, idx);
1177   g_ptr_array_remove_index (data->new_windows, idx);
1178   g_ptr_array_remove_index (
1179     data->new_notebooks, idx);
1180 
1181   /* if the sub window gets destroyed, push pages
1182    * back to the main window */
1183   /* detach the notebook pages in reverse sequence
1184    * to avoid index errors */
1185   int n_pages =
1186     gtk_notebook_get_n_pages (new_notebook);
1187   for (int i = n_pages - 1; i >= 0; i--)
1188     {
1189       GtkWidget * page =
1190         gtk_notebook_get_nth_page (new_notebook, i);
1191       GtkWidget * tab_label =
1192         gtk_notebook_get_tab_label (
1193           new_notebook, page);
1194       g_object_ref (page);
1195       g_object_ref (tab_label);
1196       gtk_notebook_detach_tab (
1197         new_notebook, page);
1198       gtk_notebook_append_page (
1199         data->notebook, page, tab_label);
1200       g_object_unref (page);
1201       g_object_unref (tab_label);
1202       gtk_notebook_set_tab_detachable (
1203         data->notebook, page, true);
1204       gtk_notebook_set_tab_reorderable (
1205         data->notebook, page, true);
1206     }
1207 }
1208 
1209 typedef struct DetachableNotebookDeleteEventData
1210 {
1211   DetachableNotebookData * data;
1212   GtkWidget *              page;
1213 } DetachableNotebookDeleteEventData;
1214 
1215 static bool
on_new_window_delete_event(GtkWidget * widget,GdkEvent * event,DetachableNotebookDeleteEventData * delete_data)1216 on_new_window_delete_event (
1217   GtkWidget *                         widget,
1218   GdkEvent *                          event,
1219   DetachableNotebookDeleteEventData * delete_data)
1220 {
1221   GtkWidget * page = delete_data->page;
1222 
1223   char * val =
1224     g_hash_table_lookup (
1225       delete_data->data->ht, page);
1226   g_return_val_if_fail (val, false);
1227   char key_detached[600];
1228   sprintf (key_detached, "%s-detached", val);
1229   char key_size[600];
1230   sprintf (key_size, "%s-size", val);
1231 
1232   int w, h;
1233   gtk_window_get_size (
1234     GTK_WINDOW (widget), &w, &h);
1235   g_settings_set_boolean (
1236     S_UI_PANELS, key_detached, false);
1237   g_settings_set (
1238     S_UI_PANELS, key_size, "(ii)", w, h);
1239   g_debug ("saving %s size %d %d", val, w, w);
1240 
1241   return false;
1242 }
1243 
1244 static void
free_delete_data(DetachableNotebookDeleteEventData * data,GClosure * closure)1245 free_delete_data (
1246   DetachableNotebookDeleteEventData * data,
1247   GClosure *                          closure)
1248 {
1249   free (data);
1250 }
1251 
1252 static GtkNotebook *
on_create_window(GtkNotebook * notebook,GtkWidget * page,int x,int y,DetachableNotebookData * data)1253 on_create_window (
1254   GtkNotebook *            notebook,
1255   GtkWidget *              page,
1256   int                      x,
1257   int                      y,
1258   DetachableNotebookData * data)
1259 {
1260   GtkWindow * new_window =
1261     GTK_WINDOW (
1262       gtk_window_new (GTK_WINDOW_TOPLEVEL));
1263   GtkNotebook * new_notebook =
1264     GTK_NOTEBOOK (gtk_notebook_new ());
1265   gtk_container_add (
1266     GTK_CONTAINER (new_window),
1267     GTK_WIDGET (new_notebook));
1268 
1269   const char * title = "Zrythm";
1270   const char * role = "zrythm-panel";
1271 
1272 #define SET_TITLE_AND_ROLE(w,t,r) \
1273   if (page == GTK_WIDGET (w)) \
1274     { \
1275       title = t; \
1276       role = r; \
1277     }
1278 
1279   /* FIXME the names should be fetched from the
1280    * tab labels instead of repeating the names
1281    * here */
1282   SET_TITLE_AND_ROLE (
1283     MW_BOT_DOCK_EDGE->mixer_box,
1284     _("Mixer"), "mixer");
1285   SET_TITLE_AND_ROLE (
1286     MW_BOT_DOCK_EDGE->modulator_view_box,
1287     _("Modulators"), "modulators");
1288   SET_TITLE_AND_ROLE (
1289     MW_BOT_DOCK_EDGE->chord_pad_box,
1290     _("Chord Pad"), "chord-pad");
1291   SET_TITLE_AND_ROLE (
1292     MW_BOT_DOCK_EDGE->clip_editor_box,
1293     _("Editor"), "editor");
1294   SET_TITLE_AND_ROLE (
1295     MW_LEFT_DOCK_EDGE->visibility_box,
1296     _("Visibility"), "track-visibility");
1297   SET_TITLE_AND_ROLE (
1298     MW_LEFT_DOCK_EDGE->track_inspector_scroll,
1299     _("Track Inspector"), "track-inspector");
1300   SET_TITLE_AND_ROLE (
1301     MW_LEFT_DOCK_EDGE->plugin_inspector_scroll,
1302     _("Plugin Inspector"), "plugin-inspector");
1303   SET_TITLE_AND_ROLE (
1304     MW_RIGHT_DOCK_EDGE->plugin_browser_box,
1305     _("Plugin Browser"), "plugin-browser");
1306   SET_TITLE_AND_ROLE (
1307     MW_RIGHT_DOCK_EDGE->file_browser_box,
1308     _("File Browser"), "file-browser");
1309   SET_TITLE_AND_ROLE (
1310     MW_RIGHT_DOCK_EDGE->monitor_section_box,
1311     _("Monitor"), "monitor");
1312   SET_TITLE_AND_ROLE (
1313     MW_MAIN_NOTEBOOK->
1314       timeline_plus_event_viewer_paned,
1315     _("Timeline"), "timeline");
1316   SET_TITLE_AND_ROLE (
1317     MW_MAIN_NOTEBOOK->cc_bindings_box,
1318     _("MIDI CC Bindings"), "midi-cc-bindings");
1319   SET_TITLE_AND_ROLE (
1320     MW_MAIN_NOTEBOOK->port_connections_box,
1321     _("Port Connections"), "port-connections");
1322   SET_TITLE_AND_ROLE (
1323     MW_MAIN_NOTEBOOK->scenes_box,
1324     _("Scenes"), "scenes");
1325 
1326 #undef SET_TITLE_AND_ROLE
1327 
1328   gtk_window_set_title (
1329     new_window, title);
1330   gtk_window_set_role (
1331     new_window, role);
1332 
1333   /* very important for DND */
1334   gtk_notebook_set_group_name (
1335     new_notebook, "foldable-notebook-group");
1336 
1337   g_signal_connect (
1338     G_OBJECT (new_notebook), "page-removed",
1339     G_CALLBACK (on_new_notebook_page_removed),
1340     new_window);
1341   g_signal_connect (
1342     G_OBJECT (new_window), "destroy",
1343     G_CALLBACK (on_new_window_destroyed),
1344     data);
1345 
1346   DetachableNotebookDeleteEventData * delete_data =
1347     object_new (
1348       DetachableNotebookDeleteEventData);
1349   delete_data->data = data;
1350   delete_data->page = page;
1351   g_signal_connect_data (
1352     G_OBJECT (new_window), "delete-event",
1353     G_CALLBACK (on_new_window_delete_event),
1354     delete_data,
1355     (GClosureNotify) free_delete_data, 0);
1356   gtk_window_set_icon_name (
1357     GTK_WINDOW (new_window), "zrythm");
1358   gtk_window_set_transient_for (
1359     GTK_WINDOW (new_window),
1360     GTK_WINDOW (data->parent_window));
1361   gtk_window_set_destroy_with_parent (
1362     new_window, true);
1363 
1364   /* set application so that actions are connected
1365    * properly */
1366   gtk_window_set_application (
1367     GTK_WINDOW (new_window),
1368     GTK_APPLICATION (zrythm_app));
1369 
1370   gtk_widget_show_all (
1371     GTK_WIDGET (new_window));
1372   gtk_widget_set_visible (page, true);
1373 
1374   g_ptr_array_add (
1375     data->new_windows, new_window);
1376   g_ptr_array_add (
1377     data->new_notebooks, new_notebook);
1378 
1379   char * val =
1380     g_hash_table_lookup (data->ht, page);
1381   g_return_val_if_fail (val, NULL);
1382   char key_detached[600];
1383   sprintf (key_detached, "%s-detached", val);
1384   char key_size[600];
1385   sprintf (key_size, "%s-size", val);
1386 
1387   /* save/load settings */
1388   g_settings_set_boolean (
1389     S_UI_PANELS, key_detached, true);
1390   GVariant * size_val =
1391     g_settings_get_value (
1392       S_UI_PANELS, key_size);
1393   int width =
1394     (int)
1395     g_variant_get_int32 (
1396       g_variant_get_child_value (size_val, 0));
1397   int height =
1398     (int)
1399     g_variant_get_int32 (
1400       g_variant_get_child_value (size_val, 1));
1401   g_debug (
1402     "loading %s size %d %d", val, width, height);
1403   gtk_window_resize (
1404     GTK_WINDOW (new_window), width, height);
1405   g_variant_unref (size_val);
1406 
1407   return new_notebook;
1408 }
1409 
1410 static void
on_detachable_notebook_destroyed(GtkWidget * widget,DetachableNotebookData * data)1411 on_detachable_notebook_destroyed (
1412   GtkWidget * widget,
1413   DetachableNotebookData * data)
1414 {
1415   g_ptr_array_free (data->new_windows, false);
1416   g_ptr_array_free (data->new_notebooks, false);
1417   g_hash_table_destroy (data->ht);
1418   free (data);
1419 }
1420 
1421 /**
1422  * Detach eligible pages programmatically into a
1423  * new window.
1424  *
1425  * Used during startup.
1426  *
1427  * @param data Data received from
1428  *   z_gtk_notebook_make_detachable.
1429  */
1430 static void
detach_pages_programmatically(GtkNotebook * old_notebook,DetachableNotebookData * data)1431 detach_pages_programmatically (
1432   GtkNotebook *            old_notebook,
1433   DetachableNotebookData * data)
1434 {
1435   int n_pages =
1436     gtk_notebook_get_n_pages (old_notebook);
1437   for (int i = n_pages - 1; i >= 0; i--)
1438     {
1439       GtkWidget * page =
1440         gtk_notebook_get_nth_page (
1441           old_notebook, i);
1442       GtkWidget * tab_label =
1443         gtk_notebook_get_tab_label (
1444           old_notebook, page);
1445 
1446       char * val =
1447         g_hash_table_lookup (data->ht, page);
1448       g_return_if_fail (val);
1449       char key_detached[600];
1450       sprintf (key_detached, "%s-detached", val);
1451       bool needs_detach =
1452         g_settings_get_boolean (
1453           S_UI_PANELS, key_detached);
1454 
1455       if (needs_detach)
1456         {
1457           g_object_ref (page);
1458           g_object_ref (tab_label);
1459           GtkNotebook * new_notebook =
1460             on_create_window (
1461               old_notebook, page, 12, 12, data);
1462           gtk_notebook_detach_tab (
1463             old_notebook, page);
1464           gtk_notebook_append_page (
1465             new_notebook, page, tab_label);
1466           gtk_notebook_set_tab_detachable (
1467             new_notebook, page, true);
1468           gtk_notebook_set_tab_reorderable (
1469             new_notebook, page, true);
1470           g_object_unref (page);
1471           g_object_unref (tab_label);
1472         }
1473     }
1474 }
1475 
1476 /**
1477  * Makes the given GtkNotebook detachable to
1478  * a new window.
1479  */
1480 void
z_gtk_notebook_make_detachable(GtkNotebook * notebook,GtkWindow * parent_window)1481 z_gtk_notebook_make_detachable (
1482   GtkNotebook * notebook,
1483   GtkWindow *   parent_window)
1484 {
1485   DetachableNotebookData * data =
1486     object_new (DetachableNotebookData);
1487   data->notebook = notebook;
1488   data->new_windows = g_ptr_array_new ();
1489   data->new_notebooks = g_ptr_array_new ();
1490   data->parent_window = parent_window;
1491 
1492   /* prepare hashtable */
1493 #define ADD_PAIR(key,w) \
1494   g_return_if_fail (key); \
1495   g_return_if_fail (w); \
1496   g_hash_table_insert ( \
1497     data->ht, w, g_strdup (key))
1498 
1499   data->ht =
1500     g_hash_table_new_full (
1501       NULL, NULL, NULL, g_free);
1502   ADD_PAIR ("track-visibility",
1503     MW_LEFT_DOCK_EDGE->visibility_box);
1504   ADD_PAIR (
1505     "track-inspector",
1506     MW_LEFT_DOCK_EDGE->track_inspector_scroll);
1507   ADD_PAIR (
1508     "plugin-inspector",
1509     MW_LEFT_DOCK_EDGE->plugin_inspector_scroll);
1510   ADD_PAIR (
1511     "plugin-browser",
1512     MW_RIGHT_DOCK_EDGE->plugin_browser_box);
1513   ADD_PAIR (
1514     "file-browser",
1515     MW_RIGHT_DOCK_EDGE->file_browser_box);
1516   ADD_PAIR (
1517     "monitor-section",
1518     MW_RIGHT_DOCK_EDGE->monitor_section_box);
1519   ADD_PAIR (
1520     "modulator-view",
1521     MW_BOT_DOCK_EDGE->modulator_view_box);
1522   ADD_PAIR (
1523     "mixer",
1524     MW_BOT_DOCK_EDGE->mixer_box);
1525   ADD_PAIR (
1526     "clip-editor",
1527     MW_BOT_DOCK_EDGE->clip_editor_box);
1528   ADD_PAIR (
1529     "chord-pad",
1530     MW_BOT_DOCK_EDGE->chord_pad_box);
1531   ADD_PAIR (
1532     "timeline",
1533     MW_MAIN_NOTEBOOK->
1534       timeline_plus_event_viewer_paned);
1535   ADD_PAIR (
1536     "cc-bindings",
1537     MW_MAIN_NOTEBOOK->cc_bindings_box);
1538   ADD_PAIR (
1539     "port-connections",
1540     MW_MAIN_NOTEBOOK->port_connections_box);
1541   ADD_PAIR (
1542     "scenes",
1543     MW_MAIN_NOTEBOOK->scenes_box);
1544 
1545 #undef ADD_PAIR
1546 
1547   g_signal_connect (
1548     G_OBJECT (notebook), "create-window",
1549     G_CALLBACK (on_create_window), data);
1550   g_signal_connect_after (
1551     G_OBJECT (notebook), "destroy",
1552     G_CALLBACK (on_detachable_notebook_destroyed),
1553     data);
1554 
1555   detach_pages_programmatically (notebook, data);
1556 }
1557 
1558 /**
1559  * Wraps the message area in a scrolled window.
1560  */
1561 void
z_gtk_message_dialog_wrap_message_area_in_scroll(GtkMessageDialog * dialog,int min_width,int min_height)1562 z_gtk_message_dialog_wrap_message_area_in_scroll (
1563   GtkMessageDialog * dialog,
1564   int                min_width,
1565   int                min_height)
1566 {
1567   GtkBox * box =
1568     GTK_BOX (
1569       gtk_message_dialog_get_message_area (
1570         GTK_MESSAGE_DIALOG (dialog)));
1571   GtkWidget * secondary_area =
1572     z_gtk_container_get_nth_child (
1573       GTK_CONTAINER (box), 1);
1574   gtk_container_remove (
1575     GTK_CONTAINER (box), secondary_area);
1576   GtkWidget * scrolled_window =
1577     gtk_scrolled_window_new (NULL, NULL);
1578   gtk_scrolled_window_set_min_content_width (
1579     GTK_SCROLLED_WINDOW (scrolled_window),
1580     min_width);
1581   gtk_scrolled_window_set_min_content_height (
1582     GTK_SCROLLED_WINDOW (scrolled_window),
1583     min_height);
1584   gtk_container_add (
1585     GTK_CONTAINER (scrolled_window),
1586     secondary_area);
1587   gtk_container_add (
1588     GTK_CONTAINER (box), scrolled_window);
1589   gtk_widget_show_all (GTK_WIDGET (box));
1590 }
1591 
1592 /**
1593  * Returns the full text contained in the text
1594  * buffer.
1595  *
1596  * Must be free'd using g_free().
1597  */
1598 char *
z_gtk_text_buffer_get_full_text(GtkTextBuffer * buffer)1599 z_gtk_text_buffer_get_full_text (
1600   GtkTextBuffer * buffer)
1601 {
1602   GtkTextIter start_iter, end_iter;
1603   gtk_text_buffer_get_start_iter (
1604     buffer, &start_iter);
1605   gtk_text_buffer_get_end_iter (
1606     buffer, &end_iter);
1607   return
1608     gtk_text_buffer_get_text (
1609       GTK_TEXT_BUFFER (buffer),
1610       &start_iter, &end_iter, false);
1611 }
1612 
1613 /**
1614  * Generates a screenshot image for the given
1615  * widget.
1616  *
1617  * See gdk_pixbuf_savev() for the parameters.
1618  *
1619  * @param accept_fallback Whether to accept a
1620  *   fallback "no image" pixbuf.
1621  * @param[out] ret_dir Placeholder for directory to
1622  *   be deleted after using the screenshot.
1623  * @param[out] ret_path Placeholder for absolute
1624  *   path to the screenshot.
1625  */
1626 void
z_gtk_generate_screenshot_image(GtkWidget * widget,const char * type,char ** option_keys,char ** option_values,char ** ret_dir,char ** ret_path,bool accept_fallback)1627 z_gtk_generate_screenshot_image (
1628   GtkWidget *  widget,
1629   const char * type,
1630   char **      option_keys,
1631   char **      option_values,
1632   char **      ret_dir,
1633   char **      ret_path,
1634   bool         accept_fallback)
1635 {
1636   g_return_if_fail (
1637     *ret_dir == NULL && *ret_path == NULL);
1638 
1639   GdkWindow * w = gtk_widget_get_window (widget);
1640   int width =
1641     gtk_widget_get_allocated_width (widget);
1642   int height =
1643     gtk_widget_get_allocated_height (widget);
1644 
1645   GdkPixbuf * pixbuf =
1646     gdk_pixbuf_get_from_window (
1647       w, 0, 0, width, height);
1648 
1649   if (!pixbuf)
1650     {
1651       g_warning ("failed to generate pixbuf");
1652 
1653       if (accept_fallback)
1654         {
1655           char * themes_dir =
1656             zrythm_get_dir (
1657               ZRYTHM_DIR_SYSTEM_THEMESDIR);
1658           char * path =
1659             g_build_filename (
1660               themes_dir, "icons", "zrythm-dark",
1661               "scalable", "apps", "zrythm.svg",
1662               NULL);
1663           GError * err = NULL;
1664           pixbuf =
1665             gdk_pixbuf_new_from_file (
1666               path, &err);
1667           g_free (path);
1668           g_free (themes_dir);
1669           if (!pixbuf)
1670             {
1671               g_warning (
1672                 "failed to get fallback pixbuf "
1673                 "from %s: %s",
1674                 path, err->message);
1675             }
1676         } /* end if accept fallback */
1677 
1678       /* if we still don't have a pixbuf, return */
1679       if (!pixbuf)
1680         return;
1681 
1682     } /* end if failed to create original pixbuf */
1683 
1684   GError * err = NULL;
1685   *ret_dir =
1686     g_dir_make_tmp ("zrythm-widget-XXXXXX", &err);
1687   if (*ret_dir == NULL)
1688     {
1689       g_warning (
1690         "failed creating temporary dir: %s",
1691         err->message);
1692       return;
1693     }
1694   char * abs_path =
1695     g_build_filename (
1696       *ret_dir, "screenshot.jpeg", NULL);
1697 
1698   err = NULL;
1699   bool ret =
1700     gdk_pixbuf_savev (
1701       pixbuf, abs_path, type,
1702       option_keys, option_values, &err);
1703   if (ret)
1704     {
1705       *ret_path = abs_path;
1706       return;
1707     }
1708   else
1709     {
1710       g_warning (
1711         "pixbuf save failed: %s",
1712         err->message);
1713       return;
1714     }
1715 
1716   g_message (
1717     "saved widget screenshot to %s", *ret_path);
1718 }
1719 
1720 /**
1721  * Sets the action target of the given GtkActionable
1722  * to be binded to the given setting.
1723  *
1724  * Mainly used for binding GSettings keys to toggle
1725  * buttons.
1726  */
1727 void
z_gtk_actionable_set_action_from_setting(GtkActionable * actionable,GSettings * settings,const char * key)1728 z_gtk_actionable_set_action_from_setting (
1729   GtkActionable * actionable,
1730   GSettings *     settings,
1731   const char *    key)
1732 {
1733   GSimpleActionGroup * action_group =
1734     g_simple_action_group_new ();
1735   GAction * action =
1736     g_settings_create_action (settings, key);
1737   g_action_map_add_action (
1738     G_ACTION_MAP (action_group), action);
1739   char * group_prefix =
1740     g_strdup_printf ("%s-action-group", key);
1741   gtk_widget_insert_action_group (
1742     GTK_WIDGET (actionable), group_prefix,
1743     G_ACTION_GROUP (action_group));
1744   char * action_name =
1745     g_strdup_printf ("%s.%s", group_prefix, key);
1746   gtk_actionable_set_action_name (
1747     actionable, action_name);
1748   g_free (group_prefix);
1749   g_free (action_name);
1750 }
1751 
1752 /**
1753  * Returns column number or -1 if not found or on
1754  * error.
1755  */
1756 int
z_gtk_tree_view_column_get_column_id(GtkTreeViewColumn * col)1757 z_gtk_tree_view_column_get_column_id (
1758   GtkTreeViewColumn * col)
1759 {
1760   GtkTreeView * tree_view =
1761     GTK_TREE_VIEW (
1762       gtk_tree_view_column_get_tree_view (col));
1763   g_return_val_if_fail (tree_view != NULL, -1);
1764 
1765   GList * cols =
1766     gtk_tree_view_get_columns (tree_view);
1767 
1768   int num = g_list_index (cols, (gpointer) col);
1769 
1770   g_list_free (cols);
1771 
1772   return num;
1773 }
1774