Add global port matrix dialogs.
[ardour.git] / gtk2_ardour / io_selector.cc
1 /*
2     Copyright (C) 2002-2007 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 */
19
20 #include <gtkmm/messagedialog.h>
21 #include <glibmm/objectbase.h>
22
23 #include <gtkmm2ext/doi.h>
24
25 #include <ardour/port_insert.h>
26 #include "ardour/session.h"
27 #include "ardour/io.h"
28 #include "ardour/audioengine.h"
29 #include "ardour/track.h"
30 #include "ardour/audio_track.h"
31 #include "ardour/midi_track.h"
32 #include "ardour/data_type.h"
33 #include "ardour/port.h"
34 #include "ardour/bundle.h"
35
36 #include "io_selector.h"
37 #include "utils.h"
38 #include "gui_thread.h"
39 #include "i18n.h"
40
41 using namespace ARDOUR;
42 using namespace Gtk;
43
44 IOSelector::IOSelector (ARDOUR::Session& session, boost::shared_ptr<ARDOUR::IO> io, bool offer_inputs)
45         : PortMatrix (session, io->default_type(), offer_inputs,
46                       PortGroupList::Mask (PortGroupList::BUSS | 
47                                            PortGroupList::SYSTEM | 
48                                            PortGroupList::OTHER))
49         , _session (session)
50         , _io (io)
51 {
52         /* Listen for ports changing on the IO */
53         _io->PortCountChanged.connect (sigc::hide (mem_fun (*this, &IOSelector::ports_changed)));
54         
55         setup ();
56 }
57
58 void
59 IOSelector::setup ()
60 {
61         _our_bundles.clear ();
62         _our_bundles.push_back (boost::shared_ptr<ARDOUR::Bundle> (new ARDOUR::Bundle));
63         _our_bundles.front()->set_name (_io->name());
64
65         if (offering_input ()) {
66                 const PortSet& ps (_io->outputs());
67
68                 int j = 0;
69                 for (PortSet::const_iterator i = ps.begin(); i != ps.end(); ++i) {
70                         char buf[32];
71                         snprintf (buf, sizeof(buf), _("out %d"), j + 1);
72                         _our_bundles.front()->add_channel (buf);
73                         _our_bundles.front()->add_port_to_channel (j, _session.engine().make_port_name_non_relative (i->name()));
74                         ++j;
75                 }
76                 
77         } else {
78                 
79                 const PortSet& ps (_io->inputs());
80
81                 int j = 0;
82                 for (PortSet::const_iterator i = ps.begin(); i != ps.end(); ++i) {
83                         char buf[32];
84                         snprintf (buf, sizeof(buf), _("in %d"), j + 1);
85                         _our_bundles.front()->add_channel (buf);
86                         _our_bundles.front()->add_port_to_channel (j, _session.engine().make_port_name_non_relative (i->name()));
87                         ++j;
88                 }
89
90         }
91
92         PortMatrix::setup ();
93 }
94
95 void
96 IOSelector::ports_changed ()
97 {
98         ENSURE_GUI_THREAD (mem_fun (*this, &IOSelector::ports_changed));
99
100         setup ();
101 }
102
103 void
104 IOSelector::set_state (
105         boost::shared_ptr<ARDOUR::Bundle> ab,
106         uint32_t ac,
107         boost::shared_ptr<ARDOUR::Bundle> bb,
108         uint32_t bc,
109         bool s,
110         uint32_t k
111         )
112 {
113         ARDOUR::Bundle::PortList const& our_ports = ab->channel_ports (ac);
114         ARDOUR::Bundle::PortList const& other_ports = bb->channel_ports (bc);
115
116         for (ARDOUR::Bundle::PortList::const_iterator i = our_ports.begin(); i != our_ports.end(); ++i) {
117                 for (ARDOUR::Bundle::PortList::const_iterator j = other_ports.begin(); j != other_ports.end(); ++j) {
118
119                         Port* f = _session.engine().get_port_by_name (*i);
120                         if (!f) {
121                                 return;
122                         }
123
124                         if (s) {
125                                 if (!offering_input()) {
126                                         _io->connect_input (f, *j, 0);
127                                 } else {
128                                         _io->connect_output (f, *j, 0);
129                                 }
130                         } else {
131                                 if (!offering_input()) {
132                                         _io->disconnect_input (f, *j, 0);
133                                 } else {
134                                         _io->disconnect_output (f, *j, 0);
135                                 }
136                         }
137                 }
138         }
139 }
140
141 PortMatrix::State
142 IOSelector::get_state (
143         boost::shared_ptr<ARDOUR::Bundle> ab,
144         uint32_t ac,
145         boost::shared_ptr<ARDOUR::Bundle> bb,
146         uint32_t bc
147         ) const
148 {
149         ARDOUR::Bundle::PortList const& our_ports = ab->channel_ports (ac);
150         ARDOUR::Bundle::PortList const& other_ports = bb->channel_ports (bc);
151
152         for (ARDOUR::Bundle::PortList::const_iterator i = our_ports.begin(); i != our_ports.end(); ++i) {
153                 for (ARDOUR::Bundle::PortList::const_iterator j = other_ports.begin(); j != other_ports.end(); ++j) {
154
155                         Port* f = _session.engine().get_port_by_name (*i);
156
157                         /* since we are talking about an IO, our ports should all have an associated Port *,
158                            so the above call should never fail */
159                         assert (f);
160                         
161                         if (!f->connected_to (*j)) {
162                                 /* if any one thing is not connected, all bets are off */
163                                 return NOT_ASSOCIATED;
164                         }
165                 }
166         }
167
168         return ASSOCIATED;
169 }
170
171 uint32_t
172 IOSelector::n_rows () const
173 {
174         if (!offering_input()) {
175                 return _io->inputs().num_ports (_io->default_type());
176         } else {
177                 return _io->outputs().num_ports (_io->default_type());
178         }
179 }
180
181 uint32_t
182 IOSelector::maximum_rows () const
183 {
184         if (!offering_input()) {
185                 return _io->input_maximum ().get (_io->default_type());
186         } else {
187                 return _io->output_maximum ().get (_io->default_type());
188         }
189 }
190
191
192 uint32_t
193 IOSelector::minimum_rows () const
194 {
195         if (!offering_input()) {
196                 return _io->input_minimum ().get (_io->default_type());
197         } else {
198                 return _io->output_minimum ().get (_io->default_type());
199         }
200 }
201
202 void
203 IOSelector::add_channel (boost::shared_ptr<ARDOUR::Bundle> b)
204 {
205         /* we ignore the bundle parameter, as we know what it is that we're adding to */
206         
207         // The IO selector only works for single typed IOs
208         const ARDOUR::DataType t = _io->default_type ();
209
210         if (!offering_input()) {
211
212                 try {
213                         _io->add_input_port ("", this);
214                 }
215
216                 catch (AudioEngine::PortRegistrationFailure& err) {
217                         MessageDialog msg (_("There are no more JACK ports available."));
218                         msg.run ();
219                 }
220
221         } else {
222
223                 try {
224                         _io->add_output_port ("", this);
225                 }
226
227                 catch (AudioEngine::PortRegistrationFailure& err) {
228                         MessageDialog msg (_("There are no more JACK ports available."));
229                         msg.run ();
230                 }
231         }
232 }
233
234 void
235 IOSelector::remove_channel (boost::shared_ptr<ARDOUR::Bundle> b, uint32_t c)
236 {
237         Port* f = _session.engine().get_port_by_name (b->channel_ports(c)[0]);
238         if (!f) {
239                 return;
240         }
241         
242         if (offering_input()) {
243                 _io->remove_output_port (f, this);
244         } else {
245                 _io->remove_input_port (f, this);
246         }
247 }
248
249 IOSelectorWindow::IOSelectorWindow (ARDOUR::Session& session, boost::shared_ptr<ARDOUR::IO> io, bool for_input, bool can_cancel)
250         : ArdourDialog ("I/O selector")
251         , _selector (session, io, !for_input)
252         , add_button (_("Add Port"))
253         , disconnect_button (_("Disconnect All"))
254         , ok_button (can_cancel ? _("OK"): _("Close"))
255         , cancel_button (_("Cancel"))
256         , rescan_button (_("Rescan"))
257
258 {
259         /* XXX: what's this for? */
260         add_events (Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
261         
262         set_name ("IOSelectorWindow2");
263
264         /* Disconnect All button */
265         disconnect_button.set_name ("IOSelectorButton");
266         disconnect_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::DISCONNECT, Gtk::ICON_SIZE_BUTTON)));
267         disconnect_button.signal_clicked().connect (sigc::mem_fun (_selector, &IOSelector::disassociate_all));
268         get_action_area()->pack_start (disconnect_button, false, false);
269
270         /* Add Port button */
271         if (_selector.maximum_rows() > _selector.n_rows()) {
272                 add_button.set_name ("IOSelectorButton");
273                 add_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::ADD, Gtk::ICON_SIZE_BUTTON)));
274                 get_action_area()->pack_start (add_button, false, false);
275                 add_button.signal_clicked().connect (sigc::bind (sigc::mem_fun (_selector, &IOSelector::add_channel), boost::shared_ptr<Bundle> ()));
276         } 
277
278         /* Rescan button */
279         rescan_button.set_name ("IOSelectorButton");
280         rescan_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::REFRESH, Gtk::ICON_SIZE_BUTTON)));
281         rescan_button.signal_clicked().connect (sigc::mem_fun (_selector, &IOSelector::setup));
282         get_action_area()->pack_start (rescan_button, false, false);
283
284         io->PortCountChanged.connect (sigc::hide (mem_fun (*this, &IOSelectorWindow::ports_changed)));
285
286         /* Cancel button */
287         if (can_cancel) {
288                 cancel_button.set_name ("IOSelectorButton");
289                 cancel_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::CANCEL, Gtk::ICON_SIZE_BUTTON)));
290                 get_action_area()->pack_start (cancel_button, false, false);
291         } else {
292                 cancel_button.hide();
293         }
294         cancel_button.signal_clicked().connect (mem_fun(*this, &IOSelectorWindow::cancel));
295
296         /* OK button */
297         ok_button.set_name ("IOSelectorButton");
298         if (!can_cancel) {
299                 ok_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::CLOSE, Gtk::ICON_SIZE_BUTTON)));
300         }
301         ok_button.signal_clicked().connect (mem_fun(*this, &IOSelectorWindow::accept));
302         get_action_area()->pack_start (ok_button, false, false);
303
304         get_vbox()->set_spacing (8);
305
306         /* XXX: do we still need the ScrolledWindow? */
307         Gtk::ScrolledWindow* sel_scroll = Gtk::manage (new Gtk::ScrolledWindow);
308         sel_scroll->set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_NEVER);
309         sel_scroll->add (_selector);
310         get_vbox()->pack_start (*sel_scroll, true, true);
311
312         set_position (Gtk::WIN_POS_MOUSE);
313
314         io_name_changed (this);
315         ports_changed ();
316
317         show_all ();
318
319         signal_delete_event().connect (bind (sigc::ptr_fun (just_hide_it), this));
320 }
321
322 void
323 IOSelectorWindow::ports_changed ()
324 {
325         if (_selector.maximum_rows() > _selector.n_rows()) {
326                 add_button.set_sensitive (true);
327         } else {
328                 add_button.set_sensitive (false);
329         }
330 }
331
332 void
333 IOSelectorWindow::cancel ()
334 {
335         _selector.Finished (IOSelector::Cancelled);
336         hide ();
337 }
338
339 void
340 IOSelectorWindow::accept ()
341 {
342         _selector.Finished (IOSelector::Accepted);
343         hide ();
344 }
345
346 void
347 IOSelectorWindow::on_map ()
348 {
349         _selector.setup ();
350         Window::on_map ();
351 }
352
353 void
354 IOSelectorWindow::io_name_changed (void* src)
355 {
356         ENSURE_GUI_THREAD(bind (mem_fun(*this, &IOSelectorWindow::io_name_changed), src));
357         
358         string title;
359
360         if (!_selector.offering_input()) {
361                 title = string_compose(_("%1 input"), _selector.io()->name());
362         } else {
363                 title = string_compose(_("%1 output"), _selector.io()->name());
364         }
365
366         set_title (title);
367 }
368
369 PortInsertUI::PortInsertUI (ARDOUR::Session& sess, boost::shared_ptr<ARDOUR::PortInsert> pi)
370         : input_selector (sess, pi->io(), true),
371           output_selector (sess, pi->io(), false)
372 {
373         pack_start (output_selector, true, true);
374         pack_start (input_selector, true, true);
375 }
376
377 void
378 PortInsertUI::redisplay ()
379 {
380         input_selector.setup ();
381         output_selector.setup ();
382 }
383
384 void
385 PortInsertUI::finished (IOSelector::Result r)
386 {
387         input_selector.Finished (r);
388         output_selector.Finished (r);
389 }
390
391
392 PortInsertWindow::PortInsertWindow (ARDOUR::Session& sess, boost::shared_ptr<ARDOUR::PortInsert> pi, bool can_cancel)
393         : ArdourDialog ("port insert dialog"),
394           _portinsertui (sess, pi),
395           ok_button (can_cancel ? _("OK"): _("Close")),
396           cancel_button (_("Cancel")),
397           rescan_button (_("Rescan"))
398 {
399
400         set_name ("IOSelectorWindow");
401         string title = _("ardour: ");
402         title += pi->name();
403         set_title (title);
404         
405         ok_button.set_name ("IOSelectorButton");
406         if (!can_cancel) {
407                 ok_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::CLOSE, Gtk::ICON_SIZE_BUTTON)));
408         }
409         cancel_button.set_name ("IOSelectorButton");
410         rescan_button.set_name ("IOSelectorButton");
411         rescan_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::REFRESH, Gtk::ICON_SIZE_BUTTON)));
412
413         get_action_area()->pack_start (rescan_button, false, false);
414         if (can_cancel) {
415                 cancel_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::CANCEL, Gtk::ICON_SIZE_BUTTON)));
416                 get_action_area()->pack_start (cancel_button, false, false);
417         } else {
418                 cancel_button.hide();
419         }
420         get_action_area()->pack_start (ok_button, false, false);
421
422         get_vbox()->pack_start (_portinsertui);
423
424         ok_button.signal_clicked().connect (mem_fun (*this, &PortInsertWindow::accept));
425         cancel_button.signal_clicked().connect (mem_fun (*this, &PortInsertWindow::cancel));
426
427         signal_delete_event().connect (bind (sigc::ptr_fun (just_hide_it), reinterpret_cast<Window *> (this))); 
428
429         going_away_connection = pi->GoingAway.connect (mem_fun (*this, &PortInsertWindow::plugin_going_away));
430 }
431
432 void
433 PortInsertWindow::plugin_going_away ()
434 {
435         ENSURE_GUI_THREAD (mem_fun (*this, &PortInsertWindow::plugin_going_away));
436         
437         going_away_connection.disconnect ();
438         delete_when_idle (this);
439 }
440
441 void
442 PortInsertWindow::on_map ()
443 {
444         _portinsertui.redisplay ();
445         Window::on_map ();
446 }
447
448
449 void
450 PortInsertWindow::cancel ()
451 {
452         _portinsertui.finished (IOSelector::Cancelled);
453         hide ();
454 }
455
456 void
457 PortInsertWindow::accept ()
458 {
459         _portinsertui.finished (IOSelector::Accepted);
460         hide ();
461 }