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