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