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 "actions/port_connection_action.h"
21 #include "audio/port_connections_manager.h"
22 #include "gui/widgets/port_connections_tree.h"
23 #include "project.h"
24 #include "utils/error.h"
25 #include "utils/gtk.h"
26 #include "utils/objects.h"
27 #include "utils/resources.h"
28 #include "utils/string.h"
29 #include "utils/ui.h"
30 #include "zrythm_app.h"
31 
32 #include <gtk/gtk.h>
33 #include <glib/gi18n.h>
34 
35 G_DEFINE_TYPE (
36   PortConnectionsTreeWidget,
37   port_connections_tree_widget,
38   GTK_TYPE_SCROLLED_WINDOW)
39 
40 enum
41 {
42   COL_ENABLED,
43   COL_SRC_PATH,
44   COL_DEST_PATH,
45   COL_MULTIPLIER,
46 
47   COL_SRC_PORT,
48   COL_DEST_PORT,
49 
50   NUM_COLS
51 };
52 
53 static void
on_enabled_toggled(GtkCellRendererToggle * cell,gchar * path_str,PortConnectionsTreeWidget * self)54 on_enabled_toggled (
55   GtkCellRendererToggle * cell,
56   gchar                 * path_str,
57   PortConnectionsTreeWidget *  self)
58 {
59   GtkTreeModel * model = self->tree_model;
60   GtkTreeIter  iter;
61   GtkTreePath *path =
62     gtk_tree_path_new_from_string (path_str);
63 
64   /* get toggled iter */
65   gtk_tree_model_get_iter (model, &iter, path);
66 
67   /* get ports and toggled */
68   gboolean enabled;
69   Port * src_port = NULL, * dest_port = NULL;
70   gtk_tree_model_get (
71     model, &iter,
72     COL_ENABLED, &enabled,
73     COL_SRC_PORT, &src_port,
74     COL_DEST_PORT, &dest_port,
75     -1);
76 
77   /* get new value */
78   enabled ^= 1;
79 
80   /* set new value on widget */
81   gtk_list_store_set (
82     GTK_LIST_STORE (model), &iter,
83     COL_ENABLED, enabled, -1);
84 
85   /* clean up */
86   gtk_tree_path_free (path);
87 
88   /* perform an undoable action */
89   GError * err = NULL;
90   bool ret =
91     port_connection_action_perform_enable (
92       &src_port->id, &dest_port->id, enabled, &err);
93   if (!ret)
94     {
95       HANDLE_ERROR (
96         err,
97         _("Failed to enable connection from %s to "
98         "%s"),
99         src_port->id.label,
100         dest_port->id.label);
101     }
102 }
103 
104 static GtkTreeModel *
create_model()105 create_model ()
106 {
107   GtkListStore *store;
108   GtkTreeIter iter;
109 
110   /* create list store */
111   store =
112     gtk_list_store_new (
113       NUM_COLS,
114       G_TYPE_BOOLEAN,
115       G_TYPE_STRING,
116       G_TYPE_STRING,
117       G_TYPE_STRING,
118       G_TYPE_POINTER,
119       G_TYPE_POINTER);
120 
121   /* add data to the list store */
122   for (int i = 0;
123        i < PORT_CONNECTIONS_MGR->num_connections;
124        i++)
125     {
126       PortConnection * conn =
127         PORT_CONNECTIONS_MGR->connections[i];
128 
129       /* skip locked connections */
130       if (conn->locked)
131         continue;
132 
133       Port * src_port =
134         port_find_from_identifier (conn->src_id);
135       Port * dest_port =
136         port_find_from_identifier (conn->dest_id);
137       g_return_val_if_fail (
138         IS_PORT_AND_NONNULL (src_port)
139         && IS_PORT_AND_NONNULL (dest_port),
140         NULL);
141 
142       char src_path[600];
143       port_get_full_designation (
144         src_port, src_path);
145       char dest_path[600];
146       port_get_full_designation (
147         dest_port, dest_path);
148 
149       /* get multiplier */
150       char mult_str[40];
151       sprintf (
152         mult_str, "%.4f", (double) conn->multiplier);
153 
154       gtk_list_store_append (
155         store, &iter);
156       gtk_list_store_set (
157         store, &iter,
158         COL_ENABLED, conn->enabled,
159         COL_SRC_PATH, src_path,
160         COL_DEST_PATH, dest_path,
161         COL_MULTIPLIER, mult_str,
162         COL_SRC_PORT, src_port,
163         COL_DEST_PORT, dest_port,
164         -1);
165     }
166 
167   return GTK_TREE_MODEL (store);
168 }
169 
170 static void
on_delete_activate(GtkMenuItem * menuitem,PortConnectionsTreeWidget * self)171 on_delete_activate (
172   GtkMenuItem *               menuitem,
173   PortConnectionsTreeWidget * self)
174 {
175   GError * err = NULL;
176   bool ret =
177     port_connection_action_perform_disconnect (
178       &self->src_port->id, &self->dest_port->id,
179       &err);
180   if (!ret)
181     {
182       HANDLE_ERROR (
183         err,
184         _("Failed to disconnect %s from %s"),
185         self->src_port->id.label,
186         self->dest_port->id.label);
187     }
188 }
189 
190 static void
show_context_menu(PortConnectionsTreeWidget * self)191 show_context_menu (
192   PortConnectionsTreeWidget * self)
193 {
194   GtkWidget * menuitem;
195   GtkWidget * menu = gtk_menu_new ();
196 
197   menuitem =
198     gtk_menu_item_new_with_label (_("Delete"));
199   gtk_widget_set_visible (
200     GTK_WIDGET (menuitem), true);
201   gtk_menu_shell_append (
202     GTK_MENU_SHELL (menu), GTK_WIDGET (menuitem));
203   g_signal_connect_data (
204     G_OBJECT (menuitem), "activate",
205     G_CALLBACK (on_delete_activate), self, NULL, 0);
206 
207   gtk_menu_attach_to_widget (
208     GTK_MENU (menu),
209     GTK_WIDGET (self), NULL);
210   gtk_menu_popup_at_pointer (GTK_MENU (menu), NULL);
211 }
212 
213 static void
on_right_click(GtkGestureMultiPress * gesture,gint n_press,gdouble x_dbl,gdouble y_dbl,PortConnectionsTreeWidget * self)214 on_right_click (
215   GtkGestureMultiPress * gesture,
216   gint                   n_press,
217   gdouble                x_dbl,
218   gdouble                y_dbl,
219   PortConnectionsTreeWidget * self)
220 {
221   if (n_press != 1)
222     return;
223 
224   g_message ("right click");
225 
226   GtkTreePath *path;
227   GtkTreeViewColumn *column;
228 
229   int x, y;
230   gtk_tree_view_convert_widget_to_bin_window_coords (
231     self->tree, (int) x_dbl, (int) y_dbl, &x, &y);
232 
233   GtkTreeSelection * selection =
234     gtk_tree_view_get_selection (self->tree);
235   if (!gtk_tree_view_get_path_at_pos (
236         GTK_TREE_VIEW (self->tree), x, y,
237         &path, &column, NULL, NULL))
238     {
239       g_message ("no path at position %d %d", x, y);
240       return;
241     }
242 
243   gtk_tree_selection_unselect_all (selection);
244   gtk_tree_selection_select_path (selection, path);
245   GtkTreeIter iter;
246   gtk_tree_model_get_iter (
247     GTK_TREE_MODEL (self->tree_model),
248     &iter, path);
249   GValue init_value = G_VALUE_INIT;
250   GValue value = init_value;
251   gtk_tree_model_get_value (
252     GTK_TREE_MODEL (self->tree_model),
253     &iter, COL_SRC_PORT, &value);
254   self->src_port = g_value_get_pointer (&value);
255   value = init_value;
256   gtk_tree_model_get_value (
257     GTK_TREE_MODEL (self->tree_model),
258     &iter, COL_DEST_PORT, &value);
259   self->dest_port = g_value_get_pointer (&value);
260 
261   gtk_tree_path_free (path);
262 
263   show_context_menu (self);
264 }
265 
266 static void
tree_view_setup(PortConnectionsTreeWidget * self,GtkTreeView * tree_view)267 tree_view_setup (
268   PortConnectionsTreeWidget * self,
269   GtkTreeView *         tree_view)
270 {
271   /* init tree view */
272   GtkCellRenderer * renderer;
273   GtkTreeViewColumn * column;
274 
275   /* column for checkbox */
276   renderer = gtk_cell_renderer_toggle_new ();
277   column =
278     gtk_tree_view_column_new_with_attributes (
279       _("On"), renderer,
280       "active", COL_ENABLED,
281       NULL);
282   gtk_tree_view_append_column (
283     GTK_TREE_VIEW (tree_view), column);
284   g_signal_connect (
285     renderer, "toggled",
286     G_CALLBACK (on_enabled_toggled), self);
287 
288   /* column for src path */
289   renderer = gtk_cell_renderer_text_new ();
290   column =
291     gtk_tree_view_column_new_with_attributes (
292       _("Source"), renderer,
293       "text", COL_SRC_PATH,
294       NULL);
295   gtk_tree_view_append_column (
296     GTK_TREE_VIEW (tree_view), column);
297   g_object_set (
298     renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
299   gtk_tree_view_column_set_resizable (
300     GTK_TREE_VIEW_COLUMN (column), true);
301   gtk_tree_view_column_set_min_width (
302     GTK_TREE_VIEW_COLUMN (column), 120);
303   gtk_tree_view_column_set_expand (
304     GTK_TREE_VIEW_COLUMN (column), true);
305 
306   /* column for src path */
307   renderer = gtk_cell_renderer_text_new ();
308   column =
309     gtk_tree_view_column_new_with_attributes (
310       _("Destination"), renderer,
311       "text", COL_DEST_PATH,
312       NULL);
313   gtk_tree_view_append_column (
314     GTK_TREE_VIEW (tree_view), column);
315   g_object_set (
316     renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
317   gtk_tree_view_column_set_resizable (
318     GTK_TREE_VIEW_COLUMN (column), true);
319   gtk_tree_view_column_set_min_width (
320     GTK_TREE_VIEW_COLUMN (column), 120);
321   gtk_tree_view_column_set_expand (
322     GTK_TREE_VIEW_COLUMN (column), true);
323 
324   /* column for multiplier */
325   renderer = gtk_cell_renderer_text_new ();
326   column =
327     gtk_tree_view_column_new_with_attributes (
328       _("Multiplier"), renderer,
329       "text", COL_MULTIPLIER,
330       NULL);
331   gtk_tree_view_append_column (
332     GTK_TREE_VIEW (tree_view), column);
333 
334   /* connect right click handler */
335   GtkGestureMultiPress * mp =
336     GTK_GESTURE_MULTI_PRESS (
337       gtk_gesture_multi_press_new (
338         GTK_WIDGET (tree_view)));
339   gtk_gesture_single_set_button (
340     GTK_GESTURE_SINGLE (mp),
341     GDK_BUTTON_SECONDARY);
342   g_signal_connect (
343     G_OBJECT (mp), "pressed",
344     G_CALLBACK (on_right_click), self);
345 }
346 
347 /**
348  * Refreshes the tree model.
349  */
350 void
port_connections_tree_widget_refresh(PortConnectionsTreeWidget * self)351 port_connections_tree_widget_refresh (
352   PortConnectionsTreeWidget * self)
353 {
354   GtkTreeModel * model = self->tree_model;
355   self->tree_model = create_model ();
356   gtk_tree_view_set_model (
357     self->tree, self->tree_model);
358 
359   if (model)
360     {
361       g_object_unref (model);
362     }
363 }
364 
365 PortConnectionsTreeWidget *
port_connections_tree_widget_new()366 port_connections_tree_widget_new ()
367 {
368   PortConnectionsTreeWidget * self =
369     g_object_new (
370       PORT_CONNECTIONS_TREE_WIDGET_TYPE, NULL);
371 
372   /* setup tree */
373   tree_view_setup (self, self->tree);
374 
375   return self;
376 }
377 
378 static void
port_connections_tree_widget_class_init(PortConnectionsTreeWidgetClass * _klass)379 port_connections_tree_widget_class_init (
380   PortConnectionsTreeWidgetClass * _klass)
381 {
382 }
383 
384 static void
port_connections_tree_widget_init(PortConnectionsTreeWidget * self)385 port_connections_tree_widget_init (
386   PortConnectionsTreeWidget * self)
387 {
388   self->tree =
389     GTK_TREE_VIEW (gtk_tree_view_new ());
390   gtk_widget_set_visible (
391     GTK_WIDGET (self->tree), 1);
392   gtk_container_add (
393     GTK_CONTAINER (self),
394     GTK_WIDGET (self->tree));
395 }
396