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