1 /*
2 * Copyright (C) 2019-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 "actions/undoable_action.h"
21 #include "actions/undo_manager.h"
22 #include "audio/port.h"
23 #include "audio/router.h"
24 #include "audio/track.h"
25 #include "audio/tracklist.h"
26 #include "gui/widgets/main_window.h"
27 #include "gui/widgets/port_connections_popover.h"
28 #include "gui/widgets/port_selector_popover.h"
29 #include "plugins/plugin.h"
30 #include "project.h"
31 #include "utils/error.h"
32 #include "utils/resources.h"
33 #include "utils/ui.h"
34 #include "zrythm_app.h"
35
36 #include <gtk/gtk.h>
37 #include <glib/gi18n.h>
38
39 G_DEFINE_TYPE (
40 PortSelectorPopoverWidget,
41 port_selector_popover_widget,
42 GTK_TYPE_POPOVER)
43
44 /** Used as the first row in the Plugin treeview to
45 * indicate if "Track ports is selected or not. */
46 static const Plugin * dummy_plugin =
47 (const Plugin *) 123;
48
49 static void
on_ok_clicked(GtkButton * btn,PortSelectorPopoverWidget * self)50 on_ok_clicked (
51 GtkButton * btn,
52 PortSelectorPopoverWidget * self)
53 {
54 if (!self->selected_port)
55 {
56 ui_show_error_message (
57 MAIN_WINDOW,
58 _("No port selected"));
59 return;
60 }
61
62 Port * src = NULL, * dest = NULL;
63 if (self->port->id.flow == FLOW_INPUT)
64 {
65 src = self->selected_port;
66 dest = self->port;
67 }
68 else if (self->port->id.flow ==
69 FLOW_OUTPUT)
70 {
71 src = self->port;
72 dest = self->selected_port;
73 }
74
75 g_return_if_fail (src && dest);
76
77 if (ports_can_be_connected (src, dest))
78 {
79 gtk_widget_destroy (GTK_WIDGET (self->owner));
80
81 GError * err = NULL;
82 bool ret =
83 port_connection_action_perform_connect (
84 &src->id, &dest->id, &err);
85 if (!ret)
86 {
87 HANDLE_ERROR (
88 err,
89 _("Failed to connect %s to %s"),
90 src->id.label, dest->id.label);
91 }
92
93 /*port_connections_popover_widget_refresh (*/
94 /*self->owner);*/
95 }
96 else
97 {
98 ui_show_error_message (
99 MAIN_WINDOW,
100 _("These ports cannot be connected"));
101 }
102 }
103
104 static void
on_cancel_clicked(GtkButton * btn,PortSelectorPopoverWidget * self)105 on_cancel_clicked (
106 GtkButton * btn,
107 PortSelectorPopoverWidget * self)
108 {
109 gtk_widget_set_visible (GTK_WIDGET (self), 0);
110 }
111
112 /**
113 * @param track Track, if track ports selected.
114 * @param pl Plugin, if plugin selected.
115 */
116 static GtkTreeModel *
create_model_for_ports(PortSelectorPopoverWidget * self,Track * track,Plugin * pl)117 create_model_for_ports (
118 PortSelectorPopoverWidget * self,
119 Track * track,
120 Plugin * pl)
121 {
122 GtkListStore *list_store;
123 GtkTreeIter iter;
124
125 /* icon, name, pointer to port */
126 list_store =
127 gtk_list_store_new (
128 3, G_TYPE_STRING, G_TYPE_STRING,
129 G_TYPE_POINTER);
130
131 PortType type = self->port->id.type;
132 PortFlow flow = self->port->id.flow;
133
134 /* Add a new row to the model if not already
135 * in the destinations */
136 #define ADD_ROW \
137 if ((flow == FLOW_INPUT && \
138 !ports_connected (port, self->port)) || \
139 (flow == FLOW_OUTPUT && \
140 !ports_connected (self->port, port))) \
141 { \
142 gtk_list_store_append ( \
143 list_store, &iter); \
144 gtk_list_store_set ( \
145 list_store, &iter, \
146 0, "node-type-cusp", \
147 1, port->id.label, \
148 2, port, \
149 -1); \
150 }
151
152 Port * port;
153 if (track)
154 {
155 Channel * ch = track->channel;
156 if (flow == FLOW_INPUT)
157 {
158 if ((type == TYPE_AUDIO ||
159 type == TYPE_CV) &&
160 track->out_signal_type ==
161 TYPE_AUDIO)
162 {
163 port = ch->prefader->stereo_out->l;
164 ADD_ROW;
165 port = ch->prefader->stereo_out->r;
166 ADD_ROW;
167 port = ch->fader->stereo_out->l;
168 ADD_ROW;
169 port = ch->fader->stereo_out->r;
170 ADD_ROW;
171 }
172 else if (type == TYPE_EVENT &&
173 track->out_signal_type ==
174 TYPE_EVENT)
175 {
176 port = ch->midi_out;
177 ADD_ROW;
178 }
179 }
180 else if (flow == FLOW_OUTPUT)
181 {
182 if (type == TYPE_AUDIO ||
183 type == TYPE_CV)
184 {
185 if (track->in_signal_type ==
186 TYPE_AUDIO)
187 {
188 port =
189 track->processor->stereo_in->l;
190 ADD_ROW;
191 port =
192 track->processor->stereo_in->r;
193 ADD_ROW;
194 }
195
196 if (track->channel)
197 {
198 port =
199 track->channel->fader->amp;
200 ADD_ROW;
201 port =
202 track->channel->fader->balance;
203 ADD_ROW;
204 }
205
206 if (track->type ==
207 TRACK_TYPE_MODULATOR)
208 {
209 for (int j = 0;
210 j <
211 track->num_modulator_macros;
212 j++)
213 {
214 port =
215 track->modulator_macros[j]->
216 cv_in;
217 ADD_ROW;
218 }
219 }
220 }
221 else if (type == TYPE_EVENT &&
222 track->in_signal_type ==
223 TYPE_EVENT)
224 {
225 port = track->processor->midi_in;
226 ADD_ROW;
227 }
228 }
229 }
230 else if (pl)
231 {
232 if (flow == FLOW_INPUT)
233 {
234 for (int i = 0;
235 i < pl->num_out_ports; i++)
236 {
237 port = pl->out_ports[i];
238
239 if ((port->id.type != type) &&
240 !(port->id.type == TYPE_CV &&
241 type == TYPE_CONTROL))
242 continue;
243
244 ADD_ROW;
245 }
246 }
247 else if (flow == FLOW_OUTPUT)
248 {
249 for (int i = 0;
250 i < pl->num_in_ports; i++)
251 {
252 port = pl->in_ports[i];
253
254 if ((port->id.type != type) &&
255 !(type == TYPE_CV &&
256 port->id.type == TYPE_CONTROL))
257 continue;
258
259 ADD_ROW;
260 }
261 }
262 }
263
264 #undef ADD_ROW
265
266 return GTK_TREE_MODEL (list_store);
267 }
268
269 static GtkTreeModel *
create_model_for_tracks(PortSelectorPopoverWidget * self)270 create_model_for_tracks (
271 PortSelectorPopoverWidget * self)
272 {
273 GtkListStore *list_store;
274 GtkTreeIter iter;
275
276 /* icon, name, pointer to channel */
277 list_store =
278 gtk_list_store_new (
279 3,
280 G_TYPE_STRING,
281 G_TYPE_STRING,
282 G_TYPE_POINTER);
283
284 Track * track;
285 for (int i = 0; i < TRACKLIST->num_tracks; i++)
286 {
287 track = TRACKLIST->tracks[i];
288
289 if (track->type != TRACK_TYPE_MODULATOR &&
290 !track->channel)
291 continue;
292
293 // Add a new row to the model
294 gtk_list_store_append (list_store, &iter);
295 gtk_list_store_set (
296 list_store, &iter,
297 0, "track-inspector",
298 1, track->name,
299 2, track,
300 -1);
301 }
302
303 return GTK_TREE_MODEL (list_store);
304 }
305
306 static void
add_plugin(PortSelectorPopoverWidget * self,Plugin * pl,GtkListStore * list_store,GtkTreeIter * iter)307 add_plugin (
308 PortSelectorPopoverWidget * self,
309 Plugin * pl,
310 GtkListStore * list_store,
311 GtkTreeIter * iter)
312 {
313 Port * port = self->port;
314 PortIdentifier * id = &port->id;
315
316 /* skip if no plugin or the plugin is the
317 * port's plugin */
318 if (!pl ||
319 (id->owner_type == PORT_OWNER_TYPE_PLUGIN &&
320 pl == port_get_plugin (self->port, true)))
321 {
322 return;
323 }
324
325 // Add a new row to the model
326 gtk_list_store_append (list_store, iter);
327 gtk_list_store_set (
328 list_store, iter,
329 0, "plugins",
330 1, pl->setting->descr->name,
331 2, pl,
332 -1);
333 }
334
335 /* FIXME leaking if model already exists */
336 static GtkTreeModel *
create_model_for_plugins(PortSelectorPopoverWidget * self,Track * track)337 create_model_for_plugins (
338 PortSelectorPopoverWidget * self,
339 Track * track)
340 {
341 GtkListStore * list_store;
342 GtkTreeIter iter;
343
344 /* icon, name, pointer to plugin */
345 list_store =
346 gtk_list_store_new (
347 3,
348 G_TYPE_STRING,
349 G_TYPE_STRING,
350 G_TYPE_POINTER);
351
352 Port * port = self->port;
353 PortIdentifier * id = &port->id;
354 if (track)
355 {
356 /* skip track ports if the owner port is
357 * a track port of the same track */
358 Track * port_track =
359 port_get_track (port, 0);
360 if (!((id->owner_type ==
361 PORT_OWNER_TYPE_TRACK &&
362 port_track == track) ||
363 (id->owner_type ==
364 PORT_OWNER_TYPE_FADER &&
365 port_track == track) ||
366 (id->owner_type ==
367 PORT_OWNER_TYPE_TRACK_PROCESSOR &&
368 port_track == track)))
369 {
370 // Add a new row to the model
371 gtk_list_store_append (list_store, &iter);
372 gtk_list_store_set (
373 list_store, &iter,
374 0, "folder",
375 1, _("Track Ports"),
376 2, dummy_plugin,
377 -1);
378 }
379
380 Channel * ch = track->channel;
381 Plugin * pl;
382
383 if (ch)
384 {
385 for (int i = 0; i < STRIP_SIZE; i++)
386 {
387 pl = ch->midi_fx[i];
388 add_plugin (
389 self, pl, list_store, &iter);
390 }
391 for (int i = 0; i < STRIP_SIZE; i++)
392 {
393 pl = ch->inserts[i];
394 add_plugin (
395 self, pl, list_store, &iter);
396 }
397
398 add_plugin (
399 self, ch->instrument, list_store, &iter);
400 }
401 for (int i = 0; i < track->num_modulators; i++)
402 {
403 pl = track->modulators[i];
404 add_plugin (self, pl, list_store, &iter);
405 }
406 }
407
408 return GTK_TREE_MODEL (list_store);
409 }
410
411 static void
412 tree_view_setup (
413 PortSelectorPopoverWidget * self,
414 GtkTreeModel * model,
415 int init);
416
417 static void
on_selection_changed(GtkTreeSelection * ts,PortSelectorPopoverWidget * self)418 on_selection_changed (
419 GtkTreeSelection * ts,
420 PortSelectorPopoverWidget * self)
421 {
422 GtkTreeView * tv =
423 gtk_tree_selection_get_tree_view (ts);
424 GtkTreeModel * model =
425 gtk_tree_view_get_model (tv);
426 GList * selected_rows =
427 gtk_tree_selection_get_selected_rows (ts,
428 NULL);
429 if (selected_rows)
430 {
431 GtkTreePath * tp =
432 (GtkTreePath *)
433 g_list_first (selected_rows)->data;
434 GtkTreeIter iter;
435 gtk_tree_model_get_iter (
436 model, &iter, tp);
437 GValue value = G_VALUE_INIT;
438
439 if (model == self->track_model)
440 {
441 gtk_tree_model_get_value (
442 model, &iter, 2, &value);
443 self->selected_track =
444 g_value_get_pointer (&value);
445 self->plugin_model =
446 create_model_for_plugins (
447 self, self->selected_track);
448 tree_view_setup (
449 self,
450 self->plugin_model, 0);
451 self->port_model =
452 create_model_for_ports (
453 self, NULL, NULL);
454 tree_view_setup (
455 self,
456 self->port_model, 0);
457 }
458 else if (model == self->plugin_model)
459 {
460 gtk_tree_model_get_value (model,
461 &iter,
462 2,
463 &value);
464 Plugin * selected_pl =
465 g_value_get_pointer (&value);
466 if (selected_pl == dummy_plugin)
467 {
468 self->selected_plugin = NULL;
469 self->track_ports_selected = 1;
470 }
471 else
472 {
473 self->selected_plugin = selected_pl;
474 self->track_ports_selected = 0;
475 }
476
477 if (self->track_ports_selected)
478 self->port_model =
479 create_model_for_ports (
480 self, self->selected_track,
481 NULL);
482 else
483 self->port_model =
484 create_model_for_ports (
485 self, NULL,
486 self->selected_plugin);
487 tree_view_setup (
488 self,
489 self->port_model, 0);
490 }
491 else if (model == self->port_model)
492 {
493 gtk_tree_model_get_value (model,
494 &iter,
495 2,
496 &value);
497 self->selected_port =
498 g_value_get_pointer (&value);
499 }
500 }
501 }
502
503 /**
504 * @param init Initialize columns, only the first time.
505 */
506 static void
tree_view_setup(PortSelectorPopoverWidget * self,GtkTreeModel * model,int init)507 tree_view_setup (
508 PortSelectorPopoverWidget * self,
509 GtkTreeModel * model,
510 int init)
511 {
512 GtkTreeView * tree_view = NULL;
513
514 if (model == self->track_model)
515 tree_view = self->track_treeview;
516 else if (model == self->plugin_model)
517 tree_view = self->plugin_treeview;
518 else if (model == self->port_model)
519 tree_view = self->port_treeview;
520
521 /* instantiate tree view using model */
522 gtk_tree_view_set_model (
523 tree_view, model);
524
525 if (init)
526 {
527 /* init tree view */
528 GtkCellRenderer * renderer;
529 GtkTreeViewColumn * column;
530
531 /* column for icon */
532 renderer =
533 gtk_cell_renderer_pixbuf_new ();
534 column =
535 gtk_tree_view_column_new_with_attributes (
536 "icon", renderer,
537 "icon-name", 0,
538 NULL);
539 gtk_tree_view_append_column (
540 GTK_TREE_VIEW (tree_view),
541 column);
542
543 /* column for name */
544 renderer =
545 gtk_cell_renderer_text_new ();
546 column =
547 gtk_tree_view_column_new_with_attributes (
548 "name", renderer,
549 "text", 1,
550 NULL);
551 gtk_tree_view_append_column (
552 GTK_TREE_VIEW (tree_view),
553 column);
554
555 /* set search column */
556 gtk_tree_view_set_search_column (
557 GTK_TREE_VIEW (tree_view),
558 1);
559
560 /* set headers invisible */
561 gtk_tree_view_set_headers_visible (
562 GTK_TREE_VIEW (tree_view), false);
563
564 g_signal_connect (
565 G_OBJECT (gtk_tree_view_get_selection (
566 GTK_TREE_VIEW (tree_view))),
567 "changed",
568 G_CALLBACK (on_selection_changed),
569 self);
570
571 gtk_widget_set_visible (
572 GTK_WIDGET (tree_view), 1);
573 }
574 }
575
576 PortSelectorPopoverWidget *
port_selector_popover_widget_new(PortConnectionsPopoverWidget * owner,Port * port)577 port_selector_popover_widget_new (
578 PortConnectionsPopoverWidget * owner,
579 Port * port)
580 {
581 PortSelectorPopoverWidget * self =
582 g_object_new (
583 PORT_SELECTOR_POPOVER_WIDGET_TYPE, NULL);
584
585 g_warn_if_fail (port);
586 self->port = port;
587 self->owner = owner;
588
589 self->track_model =
590 create_model_for_tracks (self);
591 tree_view_setup (self, self->track_model, 1);
592
593 self->plugin_model =
594 create_model_for_plugins (self, NULL);
595 tree_view_setup (
596 self,
597 self->plugin_model,
598 1);
599
600 self->port_model =
601 create_model_for_ports (
602 self, NULL, NULL);
603 tree_view_setup (
604 self,
605 self->port_model,
606 1);
607
608 return self;
609 }
610
611 static void
port_selector_popover_widget_class_init(PortSelectorPopoverWidgetClass * _klass)612 port_selector_popover_widget_class_init (
613 PortSelectorPopoverWidgetClass * _klass)
614 {
615 GtkWidgetClass * klass = GTK_WIDGET_CLASS (_klass);
616 resources_set_class_template (
617 klass, "port_selector_popover.ui");
618
619 #define BIND_CHILD(x) \
620 gtk_widget_class_bind_template_child ( \
621 klass, PortSelectorPopoverWidget, x)
622
623 BIND_CHILD (track_scroll);
624 BIND_CHILD (track_treeview);
625 BIND_CHILD (plugin_scroll);
626 BIND_CHILD (plugin_treeview);
627 BIND_CHILD (plugin_separator);
628 BIND_CHILD (port_scroll);
629 BIND_CHILD (port_treeview);
630 BIND_CHILD (ok);
631 BIND_CHILD (cancel);
632
633 #undef BIND_CHILD
634 }
635
636 static void
port_selector_popover_widget_init(PortSelectorPopoverWidget * self)637 port_selector_popover_widget_init (
638 PortSelectorPopoverWidget * self)
639 {
640 gtk_widget_init_template (GTK_WIDGET (self));
641
642 /* set max height */
643 int max_height = 380;
644 gtk_scrolled_window_set_max_content_height (
645 self->track_scroll, max_height);
646 gtk_scrolled_window_set_max_content_height (
647 self->plugin_scroll, max_height);
648 gtk_scrolled_window_set_max_content_height (
649 self->port_scroll, max_height);
650
651 g_signal_connect (
652 G_OBJECT (self->ok), "clicked",
653 G_CALLBACK (on_ok_clicked), self);
654 g_signal_connect (
655 G_OBJECT (self->cancel), "clicked",
656 G_CALLBACK (on_cancel_clicked), self);
657 }
658