next region list fix from chris g; more tweaks to port matrix/ioselector; remove...
[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
34 #include "io_selector.h"
35 #include "utils.h"
36 #include "gui_thread.h"
37 #include "i18n.h"
38
39 using namespace ARDOUR;
40 using namespace Gtk;
41
42 IOSelector::IOSelector (ARDOUR::Session& session, boost::shared_ptr<ARDOUR::IO> io, bool offer_inputs)
43         : PortMatrix (
44                 session, io->default_type(), offer_inputs,
45                 PortGroupList::Mask (PortGroupList::BUSS | 
46                                      PortGroupList::SYSTEM | 
47                                      PortGroupList::OTHER)),
48           _io (io)
49 {
50         /* Listen for ports changing on the IO */
51         if (_offer_inputs) {
52                 _io->output_changed.connect (mem_fun(*this, &IOSelector::ports_changed));
53         } else {
54                 _io->input_changed.connect (mem_fun(*this, &IOSelector::ports_changed));
55         }
56 }
57
58 void
59 IOSelector::ports_changed (ARDOUR::IOChange change, void *src)
60 {
61         ENSURE_GUI_THREAD (bind (mem_fun (*this, &IOSelector::ports_changed), change, src));
62
63         redisplay ();
64 }
65
66 void
67 IOSelector::set_state (int r, std::string const & p, bool s)
68 {
69         if (s) {
70                 if (!_offer_inputs) {
71                         _io->connect_input (_io->input(r), p, 0);
72                 } else {
73                         _io->connect_output (_io->output(r), p, 0);
74                 }
75         } else {
76                 if (!_offer_inputs) {
77                         _io->disconnect_input (_io->input(r), p, 0);
78                 } else {
79                         _io->disconnect_output (_io->output(r), p, 0);
80                 }
81         }
82 }
83
84 bool
85 IOSelector::get_state (int r, std::string const & p) const
86 {
87         vector<string> connections;
88
89         if (_offer_inputs) {
90                 _io->output(r)->get_connections (connections);
91         } else {
92                 _io->input(r)->get_connections (connections);
93         }
94
95         int k = 0;
96         for (vector<string>::iterator i = connections.begin(); i != connections.end(); ++i) {
97
98                 if ((*i)== p) {
99                         return true;
100                 }
101                 
102                 ++k;
103         }
104
105         return false;
106 }
107
108 uint32_t
109 IOSelector::n_rows () const
110 {
111         if (!_offer_inputs) {
112                 return _io->inputs().num_ports (_io->default_type());
113         } else {
114                 return _io->outputs().num_ports (_io->default_type());
115         }
116 }
117
118 uint32_t
119 IOSelector::maximum_rows () const
120 {
121         if (!_offer_inputs) {
122                 return _io->input_maximum ().get (_io->default_type());
123         } else {
124                 return _io->output_maximum ().get (_io->default_type());
125         }
126 }
127
128
129 uint32_t
130 IOSelector::minimum_rows () const
131 {
132         if (!_offer_inputs) {
133                 return _io->input_minimum ().get (_io->default_type());
134         } else {
135                 return _io->output_minimum ().get (_io->default_type());
136         }
137 }
138
139 std::string
140 IOSelector::row_name (int r) const
141 {
142         string n;
143         string::size_type pos;
144
145         if (!_offer_inputs) {
146                 n =  _io->input(r)->short_name();
147         } else {
148                 n = _io->output(r)->short_name();
149         }
150         
151         if ((pos = n.find ('/')) != string::npos) {
152                 return n.substr (pos+1);
153         } else {
154                 return n;
155         }
156 }
157
158 void
159 IOSelector::add_row ()
160 {
161         // The IO selector only works for single typed IOs
162         const ARDOUR::DataType t = _io->default_type ();
163
164         if (!_offer_inputs) {
165
166                 try {
167                         _io->add_input_port ("", this);
168                 }
169
170                 catch (AudioEngine::PortRegistrationFailure& err) {
171                         MessageDialog msg (_("There are no more JACK ports available."));
172                         msg.run ();
173                 }
174
175         } else {
176
177                 try {
178                         _io->add_output_port ("", this);
179                 }
180
181                 catch (AudioEngine::PortRegistrationFailure& err) {
182                         MessageDialog msg (_("There are no more JACK ports available."));
183                         msg.run ();
184                 }
185         }
186 }
187
188 void
189 IOSelector::remove_row (int r)
190 {
191         // The IO selector only works for single typed IOs
192         const ARDOUR::DataType t = _io->default_type ();
193         
194         if (!_offer_inputs) {
195                 _io->remove_input_port (_io->input (r), this);
196         } else {
197                 _io->remove_output_port (_io->output (r), this);
198         }
199 }
200
201 std::string
202 IOSelector::row_descriptor () const
203 {
204         return _("port");
205 }
206
207 IOSelectorWindow::IOSelectorWindow (
208         ARDOUR::Session& session, boost::shared_ptr<ARDOUR::IO> io, bool for_input, bool can_cancel
209         )
210         : ArdourDialog ("I/O selector"),
211           _selector (session, io, !for_input),
212           add_button (_("Add Port")),
213           disconnect_button (_("Disconnect All")),
214           ok_button (can_cancel ? _("OK"): _("Close")),
215           cancel_button (_("Cancel")),
216           rescan_button (_("Rescan"))
217
218 {
219         add_events (Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
220         set_name ("IOSelectorWindow2");
221
222         disconnect_button.set_name ("IOSelectorButton");
223         disconnect_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::DISCONNECT, Gtk::ICON_SIZE_BUTTON)));
224         get_action_area()->pack_start (disconnect_button, false, false);
225
226         if (_selector.maximum_rows() > _selector.n_rows()) {
227                 add_button.set_name ("IOSelectorButton");
228                 add_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::ADD, Gtk::ICON_SIZE_BUTTON)));
229                 get_action_area()->pack_start (add_button, false, false);
230                 add_button.signal_clicked().connect (sigc::mem_fun (_selector, &IOSelector::add_row));
231         } 
232
233         if (!for_input) {
234                 io->output_changed.connect (mem_fun(*this, &IOSelectorWindow::ports_changed));
235         } else {
236                 io->input_changed.connect (mem_fun(*this, &IOSelectorWindow::ports_changed));
237         }
238
239         rescan_button.set_name ("IOSelectorButton");
240         rescan_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::REFRESH, Gtk::ICON_SIZE_BUTTON)));
241         get_action_area()->pack_start (rescan_button, false, false);
242
243         if (can_cancel) {
244                 cancel_button.set_name ("IOSelectorButton");
245                 cancel_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::CANCEL, Gtk::ICON_SIZE_BUTTON)));
246                 get_action_area()->pack_start (cancel_button, false, false);
247         } else {
248                 cancel_button.hide();
249         }
250                 
251         ok_button.set_name ("IOSelectorButton");
252         if (!can_cancel) {
253                 ok_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::CLOSE, Gtk::ICON_SIZE_BUTTON)));
254         }
255         get_action_area()->pack_start (ok_button, false, false);
256
257         get_vbox()->set_spacing (8);
258         get_vbox()->pack_start (_selector);
259
260         suggestion.set_alignment (0.5, 0.5);
261         suggestion_box.pack_start (suggestion, true, true);
262         get_vbox()->pack_start (suggestion_box);
263
264         ok_button.signal_clicked().connect (mem_fun(*this, &IOSelectorWindow::accept));
265         cancel_button.signal_clicked().connect (mem_fun(*this, &IOSelectorWindow::cancel));
266         rescan_button.signal_clicked().connect (mem_fun(*this, &IOSelectorWindow::rescan));
267
268         set_position (Gtk::WIN_POS_MOUSE);
269
270         io_name_changed (this);
271         ports_changed (IOChange (0), this);
272         leave_scroller ((GdkEventCrossing*) 0);
273
274         show_all ();
275
276         signal_delete_event().connect (bind (sigc::ptr_fun (just_hide_it), this));
277
278         _selector.scrolled_window().add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
279         _selector.scrolled_window().signal_enter_notify_event().connect (mem_fun (*this, &IOSelectorWindow::enter_scroller));
280         _selector.scrolled_window().signal_leave_notify_event().connect (mem_fun (*this, &IOSelectorWindow::leave_scroller));
281 }
282
283 IOSelectorWindow::~IOSelectorWindow()
284 {
285         
286 }
287
288 bool
289 IOSelectorWindow::enter_scroller (GdkEventCrossing* ignored)
290 {
291         cerr << "IN\n";
292         suggestion.set_text (_("Click to connect. Ctrl-click to disconnect. Shift-click for cross-connect"));
293         return false;
294 }
295
296 bool
297 IOSelectorWindow::leave_scroller (GdkEventCrossing* ignored)
298 {
299         cerr << "OUT\n";
300         suggestion.set_text (_("Right-click on individual port names for per-port operations"));
301         return false;
302 }
303
304 void
305 IOSelectorWindow::ports_changed (ARDOUR::IOChange change, void *src)
306 {
307         if (_selector.maximum_rows() > _selector.n_rows()) {
308                 add_button.set_sensitive (true);
309         } else {
310                 add_button.set_sensitive (false);
311         }
312 }
313
314 void
315 IOSelectorWindow::rescan ()
316 {
317         _selector.redisplay ();
318 }
319
320 void
321 IOSelectorWindow::cancel ()
322 {
323         _selector.Finished (IOSelector::Cancelled);
324         hide ();
325 }
326
327 void
328 IOSelectorWindow::accept ()
329 {
330         _selector.Finished (IOSelector::Accepted);
331         hide ();
332 }
333
334 void
335 IOSelectorWindow::on_map ()
336 {
337         _selector.redisplay ();
338         Window::on_map ();
339 }
340
341 void
342 IOSelectorWindow::io_name_changed (void* src)
343 {
344         ENSURE_GUI_THREAD(bind (mem_fun(*this, &IOSelectorWindow::io_name_changed), src));
345         
346         string title;
347
348         if (!_selector.offering_input()) {
349                 title = string_compose(_("%1 input"), _selector.io()->name());
350         } else {
351                 title = string_compose(_("%1 output"), _selector.io()->name());
352         }
353
354         set_title (title);
355 }
356
357 PortInsertUI::PortInsertUI (ARDOUR::Session& sess, boost::shared_ptr<ARDOUR::PortInsert> pi)
358         : input_selector (sess, pi->io(), true),
359           output_selector (sess, pi->io(), false)
360 {
361         hbox.pack_start (output_selector, true, true);
362         hbox.pack_start (input_selector, true, true);
363
364         pack_start (hbox);
365 }
366
367 void
368 PortInsertUI::redisplay ()
369 {
370         input_selector.redisplay();
371         output_selector.redisplay();
372 }
373
374 void
375 PortInsertUI::finished (IOSelector::Result r)
376 {
377         input_selector.Finished (r);
378         output_selector.Finished (r);
379 }
380
381
382 PortInsertWindow::PortInsertWindow (ARDOUR::Session& sess, boost::shared_ptr<ARDOUR::PortInsert> pi, bool can_cancel)
383         : ArdourDialog ("port insert dialog"),
384           _portinsertui (sess, pi),
385           ok_button (can_cancel ? _("OK"): _("Close")),
386           cancel_button (_("Cancel")),
387           rescan_button (_("Rescan"))
388 {
389
390         set_name ("IOSelectorWindow");
391         string title = _("ardour: ");
392         title += pi->name();
393         set_title (title);
394         
395         ok_button.set_name ("IOSelectorButton");
396         if (!can_cancel) {
397                 ok_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::CLOSE, Gtk::ICON_SIZE_BUTTON)));
398         }
399         cancel_button.set_name ("IOSelectorButton");
400         rescan_button.set_name ("IOSelectorButton");
401         rescan_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::REFRESH, Gtk::ICON_SIZE_BUTTON)));
402
403         get_action_area()->pack_start (rescan_button, false, false);
404         if (can_cancel) {
405                 cancel_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::CANCEL, Gtk::ICON_SIZE_BUTTON)));
406                 get_action_area()->pack_start (cancel_button, false, false);
407         } else {
408                 cancel_button.hide();
409         }
410         get_action_area()->pack_start (ok_button, false, false);
411
412         get_vbox()->pack_start (_portinsertui);
413
414         ok_button.signal_clicked().connect (mem_fun (*this, &PortInsertWindow::accept));
415         cancel_button.signal_clicked().connect (mem_fun (*this, &PortInsertWindow::cancel));
416         rescan_button.signal_clicked().connect (mem_fun (*this, &PortInsertWindow::rescan));
417
418         signal_delete_event().connect (bind (sigc::ptr_fun (just_hide_it), reinterpret_cast<Window *> (this))); 
419
420         going_away_connection = pi->GoingAway.connect (mem_fun (*this, &PortInsertWindow::plugin_going_away));
421 }
422
423 void
424 PortInsertWindow::plugin_going_away ()
425 {
426         ENSURE_GUI_THREAD (mem_fun (*this, &PortInsertWindow::plugin_going_away));
427         
428         going_away_connection.disconnect ();
429         delete_when_idle (this);
430 }
431
432 void
433 PortInsertWindow::on_map ()
434 {
435         _portinsertui.redisplay ();
436         Window::on_map ();
437 }
438
439
440 void
441 PortInsertWindow::rescan ()
442 {
443         _portinsertui.redisplay ();
444 }
445
446 void
447 PortInsertWindow::cancel ()
448 {
449         _portinsertui.finished (IOSelector::Cancelled);
450         hide ();
451 }
452
453 void
454 PortInsertWindow::accept ()
455 {
456         _portinsertui.finished (IOSelector::Accepted);
457         hide ();
458 }