a7c6e265ed5adb6aa14c4bcba5c85eda031d35e9
[ardour.git] / gtk2_ardour / connection_editor.cc
1 /*
2     Copyright (C) 2002 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18     $Id$
19 */
20
21 #include <map>
22 #include <vector>
23 #include <stdint.h>
24
25 #include <gtkmm2ext/gtk_ui.h>
26 #include <gtkmm2ext/utils.h>
27 #include <sigc++/bind.h>
28
29 #include "connection_editor.h"
30
31 #include <ardour/session.h>
32 #include <ardour/session_connection.h>
33 #include <ardour/audioengine.h>
34 #include <ardour/connection.h>
35
36 #include "utils.h"
37 #include "keyboard.h"
38 #include "prompter.h"
39
40 #include "i18n.h"
41
42 #include <inttypes.h>
43
44 using namespace std;
45 using namespace ARDOUR;
46 using namespace PBD;
47 using namespace Gtk;
48 using namespace sigc;
49
50 ConnectionEditor::ConnectionEditor ()
51         : ArdourDialog (_("ardour: connections")),
52           input_frame (_("Input Connections")),
53           output_frame (_("Output Connections")),
54           new_input_connection_button (_("New Input")),
55           new_output_connection_button (_("New Output")),
56           delete_connection_button (_("Delete")),
57           clear_button (_("Clear")),
58           add_port_button (_("Add Port")),
59           ok_button (_("Close")),
60           cancel_button (_("Cancel")),
61           rescan_button (_("Rescan"))
62           
63 {
64         add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK);
65
66         session = 0;
67         selected_port = -1;
68         current_connection = 0;
69         push_at_front = false;
70
71         set_name ("ConnectionEditorWindow");
72
73         ok_button.set_name ("ConnectionEditorButton");
74         cancel_button.set_name ("ConnectionEditorButton");
75         rescan_button.set_name ("ConnectionEditorButton");
76         new_input_connection_button.set_name ("ConnectionEditorButton");
77         new_output_connection_button.set_name ("ConnectionEditorButton");
78         clear_button.set_name ("ConnectionEditorButton");
79         
80         button_frame.set_name ("ConnectionEditorFrame");
81         input_frame.set_name ("ConnectionEditorFrame");
82         output_frame.set_name ("ConnectionEditorFrame");
83
84         button_box.set_spacing (15);
85         button_box.set_border_width (5);
86         Gtkmm2ext::set_size_request_to_display_given_text (ok_button, _("OK"), 40, 15);
87         button_box.pack_end (ok_button, false, false);
88         // button_box.pack_end (cancel_button, false, false);
89         cancel_button.hide();
90         button_frame.add (button_box);
91
92         ok_button.signal_clicked().connect (mem_fun(*this, &ConnectionEditor::accept));
93         cancel_button.signal_clicked().connect (mem_fun(*this, &ConnectionEditor::cancel));
94         cancel_button.signal_clicked().connect (mem_fun(*this, &ConnectionEditor::rescan));
95
96         notebook.set_name ("ConnectionEditorNotebook");
97         notebook.set_size_request (-1, 125);
98
99         clear_button.set_name ("ConnectionEditorButton");
100         add_port_button.set_name ("ConnectionEditorButton");
101         Gtkmm2ext::set_size_request_to_display_given_text (add_port_button, _("Add Port"), 35, 15);
102
103         selector_frame.set_name ("ConnectionEditorFrame");
104         port_frame.set_name ("ConnectionEditorFrame");
105
106         selector_frame.set_label (_("Available Ports"));
107         
108         selector_button_box.set_spacing (5);
109         selector_button_box.set_border_width (5);
110         Gtkmm2ext::set_size_request_to_display_given_text (rescan_button, _("Rescan"), 35, 15);
111         selector_button_box.pack_start (rescan_button, false, false);
112
113         selector_box.set_spacing (5);
114         selector_box.set_border_width (5);
115         selector_box.pack_start (notebook);
116         selector_box.pack_start (selector_button_box);
117
118         selector_frame.add (selector_box);
119
120         port_box.set_spacing (5);
121         port_box.set_border_width (3);
122
123         port_button_box.set_spacing (5);
124         port_button_box.set_border_width (2);
125
126         port_button_box.pack_start (add_port_button, false, false);
127         port_and_button_box.set_border_width (5);
128         port_and_button_box.pack_start (port_button_box, false, false);
129         port_and_button_box.pack_start (port_box);
130
131         port_frame.add (port_and_button_box);
132
133         port_and_selector_box.set_spacing (5);
134         port_and_selector_box.pack_start (port_frame);
135         port_and_selector_box.pack_start (selector_frame);
136
137         right_vbox.set_spacing (5);
138         right_vbox.set_border_width (5);
139         right_vbox.pack_start (port_and_selector_box);
140
141         input_connection_model = ListStore::create (connection_columns);
142         output_connection_model = ListStore::create (connection_columns);
143         
144         input_connection_display.set_model (input_connection_model);
145         output_connection_display.set_model (output_connection_model);
146
147         input_connection_display.append_column (_("Connections"), connection_columns.name);
148         output_connection_display.append_column (_("Connections"), connection_columns.name);
149
150         input_connection_display.get_selection()->set_mode(Gtk::SELECTION_SINGLE);
151         input_connection_display.set_size_request (80, -1);
152         input_connection_display.set_name ("ConnectionEditorConnectionList");
153
154         output_connection_display.get_selection()->set_mode(Gtk::SELECTION_SINGLE);
155         output_connection_display.set_size_request (80, -1);
156         output_connection_display.set_name ("ConnectionEditorConnectionList");
157
158         input_connection_display.get_selection()->signal_changed().connect (bind (mem_fun(*this, &ConnectionEditor::selection_changed), &input_connection_display));
159         output_connection_display.get_selection()->signal_changed().connect (bind (mem_fun(*this, &ConnectionEditor::selection_changed), &output_connection_display));
160
161
162         input_scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
163         output_scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
164
165         input_scroller.add (input_connection_display);
166         output_scroller.add (output_connection_display);
167
168         input_box.set_border_width (5);
169         input_box.set_spacing (5);
170         input_box.pack_start (input_scroller);
171         input_box.pack_start (new_input_connection_button, false, false);
172         input_frame.add (input_box);
173
174         output_box.set_border_width (5);
175         output_box.set_spacing (5);
176         output_box.pack_start (output_scroller);
177         output_box.pack_start (new_output_connection_button, false, false);
178         output_frame.add (output_box);
179
180         connection_box.set_spacing (5);
181         connection_box.pack_start (input_frame);
182         connection_box.pack_start (output_frame);
183
184         left_vbox.set_spacing (5);
185         left_vbox.pack_start (connection_box);
186
187         main_hbox.set_border_width (10);
188         main_hbox.set_spacing (5);
189         main_hbox.pack_start (left_vbox);
190         main_hbox.pack_start (right_vbox);
191
192         main_vbox.set_border_width (10);
193         main_vbox.set_spacing (5);
194         main_vbox.pack_start (main_hbox);
195         main_vbox.pack_start (button_frame, false, false);
196
197         get_vbox()->pack_start (main_vbox);
198         
199         clear_button.signal_clicked().connect (mem_fun(*this, &ConnectionEditor::clear));
200         add_port_button.signal_clicked().connect (mem_fun(*this, &ConnectionEditor::add_port));
201         new_input_connection_button.signal_clicked().connect (bind (mem_fun(*this, &ConnectionEditor::new_connection), true));
202         new_output_connection_button.signal_clicked().connect (bind (mem_fun(*this, &ConnectionEditor::new_connection), false));
203         delete_connection_button.signal_clicked().connect (mem_fun(*this, &ConnectionEditor::delete_connection));
204 }
205
206 ConnectionEditor::~ConnectionEditor()
207 {
208 }
209
210 void
211 ConnectionEditor::set_session (Session *s)
212 {
213         if (s != session) {
214
215                 ArdourDialog::set_session (s);
216                 
217                 if (session) {
218                         session->ConnectionAdded.connect (mem_fun(*this, &ConnectionEditor::proxy_add_connection_and_select));
219                         session->ConnectionRemoved.connect (mem_fun(*this, &ConnectionEditor::proxy_remove_connection));
220                 } else {
221                         hide ();
222                 }
223         }
224 }
225
226 void
227 ConnectionEditor::rescan ()
228 {
229         refill_connection_display ();
230         display_ports ();
231 }
232
233 void
234 ConnectionEditor::cancel ()
235 {
236         hide ();
237 }
238
239 void
240 ConnectionEditor::accept ()
241 {
242         hide ();
243 }
244
245 void
246 ConnectionEditor::clear ()
247 {
248         if (current_connection) {
249                 current_connection->clear ();
250         }
251 }
252
253 void
254 ConnectionEditor::on_map ()
255 {
256         refill_connection_display ();
257         Window::on_map ();
258 }
259
260 void
261 ConnectionEditor::add_connection (ARDOUR::Connection *connection)
262 {
263         TreeModel::Row row;
264
265         if (dynamic_cast<InputConnection *> (connection)) {
266
267                 if (push_at_front) {
268                         row = *(input_connection_model->prepend());
269                 } else {
270                         row = *(input_connection_model->append());
271                 }
272
273         } else {
274
275                 if (push_at_front) {
276                         row = *(output_connection_model->prepend());
277                 } else {
278                         row = *(output_connection_model->append());
279                 }
280         }
281
282         row[connection_columns.connection] = connection;
283         row[connection_columns.name] = connection->name();
284 }
285
286 void
287 ConnectionEditor::remove_connection (ARDOUR::Connection *connection)
288 {
289         TreeModel::iterator i;
290         Glib::RefPtr<TreeModel> model = input_connection_model;
291
292         if (dynamic_cast<InputConnection *> (connection) == 0) {
293                 model = output_connection_model;
294         }
295
296         TreeModel::Children rows = model->children();
297
298         for (i = rows.begin(); i != rows.end(); ++i) {
299                 if ((*i)[connection_columns.connection] == connection) {
300                         // model->erase (i);
301                         break;
302                 }
303         }
304 }
305
306 void
307 ConnectionEditor::proxy_add_connection_and_select (ARDOUR::Connection *connection)
308 {
309         Gtkmm2ext::UI::instance()->call_slot (bind (mem_fun(*this, &ConnectionEditor::add_connection_and_select), connection));
310 }
311
312 void
313 ConnectionEditor::proxy_remove_connection (ARDOUR::Connection *connection)
314 {
315         Gtkmm2ext::UI::instance()->call_slot (bind (mem_fun(*this, &ConnectionEditor::remove_connection), connection));
316 }
317
318 void
319 ConnectionEditor::add_connection_and_select (ARDOUR::Connection *connection)
320 {
321         add_connection (connection);
322
323         // GTK2FIX
324         // if (dynamic_cast<InputConnection *> (connection)) {
325         // input_connection_display.rows().front().select ();
326         // } else {
327         //      output_connection_display.rows().front().select ();
328         //}
329 }
330
331 void
332 ConnectionEditor::refill_connection_display ()
333 {
334         input_connection_display.set_model (Glib::RefPtr<TreeModel>(0));
335         output_connection_display.set_model (Glib::RefPtr<TreeModel>(0));
336
337         input_connection_model->clear();
338         output_connection_model->clear();
339
340         current_connection = 0;
341         
342         if (session) {
343                 session->foreach_connection (this, &ConnectionEditor::add_connection);
344         }
345
346         input_connection_display.set_model (input_connection_model);
347         output_connection_display.set_model (output_connection_model);
348
349 }
350         
351 void
352 ConnectionEditor::selection_changed (TreeView* view)
353 {
354         ARDOUR::Connection *old_current = current_connection;
355
356         TreeIter iter;
357         TreeModel::Path path;
358         Glib::RefPtr<TreeView::Selection> selection = view->get_selection();
359         Glib::RefPtr<TreeModel> model = view->get_model();
360         bool input = (view == &input_connection_display);
361
362         iter = model->get_iter (path);
363         
364         current_connection = (*iter)[connection_columns.connection];
365
366         if (old_current != current_connection) {
367                 config_connection.disconnect ();
368                 connect_connection.disconnect ();
369         }
370
371         if (current_connection) {
372                 config_connection = current_connection->ConfigurationChanged.connect 
373                         (bind (mem_fun(*this, &ConnectionEditor::configuration_changed), input));
374                 connect_connection = current_connection->ConnectionsChanged.connect 
375                         (bind (mem_fun(*this, &ConnectionEditor::connections_changed), input));
376         }
377         
378         display_connection_state (input);
379         display_ports ();
380 }
381
382 void
383 ConnectionEditor::configuration_changed (bool for_input)
384 {
385         display_connection_state (for_input);
386 }
387
388 void
389 ConnectionEditor::connections_changed (int which_port, bool for_input)
390 {
391         display_connection_state (for_input);
392 }
393
394 void
395 ConnectionEditor::display_ports ()
396 {
397         if (session == 0 || current_connection == 0) {
398                 return;
399         }
400         
401         using namespace Notebook_Helpers;
402
403         typedef std::map<std::string,std::vector<std::pair<std::string,std::string> > > PortMap;
404         PortMap portmap;
405         const char **ports;
406         PageList& pages = notebook.pages();
407         gint current_page;
408         vector<string> rowdata;
409         bool for_input;
410
411         current_page = notebook.get_current_page ();
412         pages.clear ();
413
414         /* get relevant current JACK ports */
415
416         for_input = (dynamic_cast<InputConnection *> (current_connection) != 0);
417
418         ports = session->engine().get_ports ("", JACK_DEFAULT_AUDIO_TYPE, for_input?JackPortIsOutput:JackPortIsInput);
419
420         if (ports == 0) {
421                 return;
422         }
423
424         /* find all the client names and group their ports into a list-by-client */
425         
426         for (int n = 0; ports[n]; ++n) {
427
428                 pair<string,vector<pair<string,string> > > newpair;
429                 pair<string,string> strpair;
430                 pair<PortMap::iterator,bool> result;
431
432                 string str = ports[n];
433                 string::size_type pos;
434                 string portname;
435
436                 pos = str.find (':');
437
438                 newpair.first = str.substr (0, pos); 
439                 portname = str.substr (pos+1);
440
441                 result = portmap.insert (newpair);
442
443                 strpair.first = portname;
444                 strpair.second = str;
445
446                 result.first->second.push_back (strpair);
447         }
448
449         PortMap::iterator i;
450
451         for (i = portmap.begin(); i != portmap.end(); ++i) {
452                 
453                 Box *client_box = manage (new VBox);
454                 Gtk::CTreeView *display = manage (new Gtk::TreeView);
455                 RefPtr<TreeModel> model = TreeModel::create (columns);
456                 ScrolledWindow *scroller = manage (new ScrolledWindow);
457
458                 display->set_selection_mode (GTK_SELECTION_SINGLE);
459                 display->set_name ("ConnectionEditorList");
460
461                 for (vector<pair<string,string> >::iterator s = i->second.begin(); s != i->second.end(); ++s) {
462                         
463                         Row row = model->append ();
464
465                         row[displayed_name] = s->first;
466                         row[full_name] = s->second;
467                 }
468
469                 display->get_selection()->signal_changed().connect (bind (mem_fun(*this, &ConnectionEditor::port_selection_handler), display));
470                 
471                 Label *tab_label = manage (new Label);
472
473                 tab_label->set_name ("ConnectionEditorNotebookTab");
474                 tab_label->set_text ((*i).first);
475
476                 display->set_model (model);
477
478                 scroller->add (*client_port_display);
479                 scroller->set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
480
481                 client_box->pack_start (*scroller);
482
483                 pages.push_back (TabElem (*client_box, *tab_label));
484         }
485
486         notebook.set_page (current_page);
487         notebook.show.connect (bind (mem_fun (notebook, &Notebook::set_page), current_page));
488         selector_box.show_all ();
489 }       
490
491 void
492 ConnectionEditor::display_connection_state (bool for_input)
493 {
494         Glib::Mutex::Lock lm  (port_display_lock);
495         uint32_t limit;
496
497         if (session == 0 || current_connection == 0) {
498                 return;
499         }
500
501         string frame_label = _("Connection \"");
502         frame_label += current_connection->name();
503         frame_label += _("\"");
504         port_frame.set_label (frame_label);
505
506         for (slist<ScrolledWindow *>::iterator i = port_displays.begin(); i != port_displays.end(); ) {
507                 
508                 slist<ScrolledWindow *>::iterator tmp;
509
510                 tmp = i;
511                 tmp++;
512
513                 port_box.remove (**i);
514                 delete *i;
515                 port_displays.erase (i);
516
517                 i = tmp;
518         } 
519         
520         limit = current_connection->nports();
521
522         for (uint32_t n = 0; n < limit; ++n) {
523
524                 CList *clist;
525                 ScrolledWindow *scroller;
526
527                 const gchar *title[1];
528                 char buf[32];
529                 string really_short_name;
530
531                 if (for_input) {
532                         snprintf(buf, sizeof(buf)-1, _("in %d"), n+1);
533                 } else {
534                         snprintf(buf, sizeof(buf)-1, _("out %d"), n+1);
535                 }
536                         
537                 tview = manage (new TreeView());
538                 Glib::RefPtr<ListStore> port_model = ListStore::create (*port_display_columns);
539                 
540                 tview->set_model (port_model);
541                 tview->append_column (_(buf), port_display_columns->name);
542                 tview->set_selection()->set_mode (Gtk::SELECTION_SINGLE);
543                 tview->set_data ("port", (gpointer) ((intptr_t) n));
544                 tview->set_headers_visible (true);
545                 tview->set_name ("ConnectionEditorPortList");
546                 tview->signal_button_press_event().connect (bind (mem_fun(*this, &ConnectionEditor::port_column_click), clist));
547
548                 scroller = manage (new ScrolledWindow);
549                 
550                 scroller->add (*tview);
551                 scroller->set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
552
553                 port_displays.insert (port_displays.end(), scroller);
554                 port_box.pack_start (*scroller);
555
556                 scroller->set_size_request (-1, 75);
557
558                 /* now fill the clist with the current connections */
559
560                 const ARDOUR::Connection::PortList& connections = current_connection->port_connections (n);
561         
562                 for (ARDOUR::Connection::PortList::const_iterator i = connections.begin(); i != connections.end(); ++i) {
563
564                         TreeModel::Row row = *(model->append());
565
566                         row[port_connection_columns.name] = (*i)->name();
567                 }
568         }
569
570         port_box.show_all ();
571 }
572
573 void
574 ConnectionEditor::port_selection_changed (TreeView* tview)
575 {
576         Glib::RefPtr<TreeView::Selection> sel = tview->get_selection();
577         TreeModel::iterator iter = sel->get_selected();
578
579         if (!current_connection) {
580                 return;
581         }
582
583         if (iter) {
584                 TreeModel::Row row = *iter;
585                 string other_port_name = row[port_display_columns.full_name];
586
587         
588         if (current_connection && selected_port >= 0) {
589                 current_connection->add_connection (selected_port, other_port_name);
590         }
591
592 }
593
594 void
595 ConnectionEditor::add_port ()
596 {
597         if (current_connection) {
598                 current_connection->add_port ();
599         }
600 }
601
602 void
603 ConnectionEditor::connection_port_button_press_event (GdkEventButton* ev, TreeView* tview)
604 {
605         Glib::Mutex::Lock lm  (port_display_lock);
606
607         int which_port = reinterpret_cast<intptr_t> (treeview->get_data ("port"));
608
609         if (which_port != selected_port) {
610                 
611                 selected_port = which_port;
612                 display_ports ();
613
614                 tview->set_name ("ConnectionEditorPortListSelected");
615
616                 for (slist<ScrolledWindow *>::iterator i = port_displays.begin(); i != port_displays.end(); ++i) {
617
618                         Widget *child = (*i)->get_child();
619
620                         if (static_cast<TreeView *> (child) != tview) {
621                                 child->set_name ("ConnectionEditorPortList");
622                                 child->queue_draw ();
623                         }
624                 }
625                 
626         } else {
627                 
628                 selected_port = -1;
629                 clist->set_name ("ConnectionEditorPortList");
630                 clist->queue_draw();
631         }
632 }
633
634 void
635 ConnectionEditor::connection_selection_changed (TreeView* tview);
636 {
637         Glib::RefPtr<TreeView::Selection> sel = tview->get_selection();
638         TreeModel::iterator iter = sel->get_selected();
639
640         if (iter) {
641                 TreeModel::Row row = *iter;
642                 current_connection = row[XXXX_display_columns.connection];
643         } else {
644                 current_connection = 0;
645         }
646 }
647
648 void
649 ConnectionEditor::new_connection (bool for_input)
650 {
651         string name;
652
653         if (session == 0) {
654                 return;
655         }
656
657         ArdourPrompter prompter (true);
658         prompter.set_prompt (_("Name for new connection:"));
659         prompter.done.connect (Gtk::Main::quit.slot());
660
661         switch (prompter.run()) {
662         case Gtk::RESPONSE_ACCEPT:
663                 prompter.get_result (name);
664                 push_at_front = true;
665                 if (name.length()) {
666                         if (for_input) {
667                                 session->add_connection (new ARDOUR::InputConnection (name));
668                         } else {
669                                 session->add_connection (new ARDOUR::OutputConnection (name));
670                         }
671                 }
672                 push_at_front = false;
673                 break;
674
675         default:
676                 break;
677         }
678 }
679
680 void
681 ConnectionEditor::delete_connection ()
682 {
683         if (session && current_connection) {
684                 session->remove_connection (current_connection);
685                 current_connection = 0;
686         }
687 }
688
689 gint
690 ConnectionEditor::port_button_event (GdkEventButton *ev, Treeview* treeview)
691 {
692         int row, col;
693         TreeIter iter;
694         TreeModel::Path path;
695         TreeViewColumn* column;
696         int cellx;
697         int celly;
698
699         if (current_connection == 0) {
700                 return false;
701         }
702
703         if (!(Keyboard::is_delete_event (ev))) {
704                 return false;
705         }
706
707         if (!treeview->get_path_at_pos ((int)ev->x, (int)ev->y, path, column, cellx, celly)) {
708                 return false;
709         }
710
711         if ((iter = treeview->get_model()->get_iter (path))) {
712                 /* path is valid */
713                 
714                 string port_name = (*iter)[columns.full_name];
715                 int which_port = (intptr_t) treeview->get_data ("port");        
716
717                 current_connection->remove_connection (which_port, port_name);
718         }
719
720         return true;
721 }