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