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