use new syntax for connecting to backend signals that enforces explicit connection...
[ardour.git] / gtk2_ardour / bundle_manager.cc
1 /*
2     Copyright (C) 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/stock.h>
21 #include <gtkmm/button.h>
22 #include <gtkmm/label.h>
23 #include <gtkmm/entry.h>
24 #include <gtkmm/table.h>
25 #include <gtkmm/comboboxtext.h>
26 #include <gtkmm/alignment.h>
27 #include "ardour/session.h"
28 #include "ardour/user_bundle.h"
29 #include "ardour/audioengine.h"
30 #include "bundle_manager.h"
31 #include "i18n.h"
32 #include "utils.h"
33
34 using namespace std;
35 using namespace ARDOUR;
36
37 BundleEditorMatrix::BundleEditorMatrix (Gtk::Window* parent, Session* session, boost::shared_ptr<Bundle> bundle)
38         : PortMatrix (parent, session, bundle->type())
39         , _bundle (bundle)
40 {
41         _port_group = boost::shared_ptr<PortGroup> (new PortGroup (""));
42         _port_group->add_bundle (_bundle);
43
44         setup_all_ports ();
45         init ();
46 }
47
48 void
49 BundleEditorMatrix::setup_ports (int dim)
50 {
51         if (dim == OURS) {
52                 _ports[OURS].clear ();
53                 _ports[OURS].add_group (_port_group);
54         } else {
55                 _ports[OTHER].suspend_signals ();
56
57                 /* when we gather, allow the matrix to contain bundles with duplicate port sets,
58                    otherwise in some cases the basic system IO ports may be hidden, making
59                    the bundle editor useless */
60                 
61                 _ports[OTHER].gather (_session, _bundle->ports_are_inputs(), true);
62                 _ports[OTHER].remove_bundle (_bundle);
63                 _ports[OTHER].resume_signals ();
64         }
65 }
66
67 void
68 BundleEditorMatrix::set_state (BundleChannel c[2], bool s)
69 {
70         Bundle::PortList const& pl = c[OTHER].bundle->channel_ports (c[OTHER].channel);
71         for (Bundle::PortList::const_iterator i = pl.begin(); i != pl.end(); ++i) {
72                 if (s) {
73                         c[OURS].bundle->add_port_to_channel (c[OURS].channel, *i);
74                 } else {
75                         c[OURS].bundle->remove_port_from_channel (c[OURS].channel, *i);
76                 }
77         }
78 }
79
80 PortMatrixNode::State
81 BundleEditorMatrix::get_state (BundleChannel c[2]) const
82 {
83         Bundle::PortList const& pl = c[OTHER].bundle->channel_ports (c[OTHER].channel);
84         if (pl.empty ()) {
85                 return PortMatrixNode::NOT_ASSOCIATED;
86         }
87         
88         for (Bundle::PortList::const_iterator i = pl.begin(); i != pl.end(); ++i) {
89                 if (!c[OURS].bundle->port_attached_to_channel (c[OURS].channel, *i)) {
90                         return PortMatrixNode::NOT_ASSOCIATED;
91                 }
92         }
93
94         return PortMatrixNode::ASSOCIATED;
95 }
96
97 bool
98 BundleEditorMatrix::can_add_channel (boost::shared_ptr<Bundle> b) const
99 {
100         if (b == _bundle) {
101                 return true;
102         }
103
104         return PortMatrix::can_add_channel (b);
105 }
106
107 void
108 BundleEditorMatrix::add_channel (boost::shared_ptr<Bundle> b)
109 {
110         if (b == _bundle) {
111
112                 NameChannelDialog d;
113                 d.set_position (Gtk::WIN_POS_MOUSE);
114
115                 if (d.run () != Gtk::RESPONSE_ACCEPT) {
116                         return;
117                 }
118
119                 _bundle->add_channel (d.get_name());
120                 setup_ports (OURS);
121
122         } else {
123
124                 PortMatrix::add_channel (b);
125
126         }
127 }
128
129 bool
130 BundleEditorMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
131 {
132         if (b == _bundle) {
133                 return true;
134         }
135
136         return PortMatrix::can_remove_channels (b);
137 }
138
139 void
140 BundleEditorMatrix::remove_channel (BundleChannel bc)
141 {
142         bc.bundle->remove_channel (bc.channel);
143         setup_ports (OURS);
144 }
145
146 bool
147 BundleEditorMatrix::can_rename_channels (boost::shared_ptr<Bundle> b) const
148 {
149         if (b == _bundle) {
150                 return true;
151         }
152
153         return PortMatrix::can_rename_channels (b);
154 }
155
156 void
157 BundleEditorMatrix::rename_channel (BundleChannel bc)
158 {
159         NameChannelDialog d (bc.bundle, bc.channel);
160         d.set_position (Gtk::WIN_POS_MOUSE);
161
162         if (d.run () != Gtk::RESPONSE_ACCEPT) {
163                 return;
164         }
165
166         bc.bundle->set_channel_name (bc.channel, d.get_name ());
167 }
168
169 bool
170 BundleEditorMatrix::list_is_global (int dim) const
171 {
172         return (dim == OTHER);
173 }
174
175 BundleEditor::BundleEditor (Session* session, boost::shared_ptr<UserBundle> bundle)
176         : ArdourDialog (_("Edit Bundle")), _matrix (this, session, bundle), _bundle (bundle)
177 {
178         Gtk::Table* t = new Gtk::Table (3, 2);
179         t->set_spacings (4);
180
181         /* Bundle name */
182         Gtk::Alignment* a = new Gtk::Alignment (1, 0.5, 0, 1);
183         a->add (*Gtk::manage (new Gtk::Label (_("Name:"))));
184         t->attach (*Gtk::manage (a), 0, 1, 0, 1, Gtk::FILL, Gtk::FILL);
185         t->attach (_name, 1, 2, 0, 1);
186         _name.set_text (_bundle->name ());
187         _name.signal_changed().connect (sigc::mem_fun (*this, &BundleEditor::name_changed));
188
189         /* Direction (input or output) */
190         a = new Gtk::Alignment (1, 0.5, 0, 1);
191         a->add (*Gtk::manage (new Gtk::Label (_("Direction:"))));
192         t->attach (*Gtk::manage (a), 0, 1, 1, 2, Gtk::FILL, Gtk::FILL);
193         a = new Gtk::Alignment (0, 0.5, 0, 1);
194         a->add (_input_or_output);
195         t->attach (*Gtk::manage (a), 1, 2, 1, 2);
196         _input_or_output.append_text (_("Input"));
197         _input_or_output.append_text (_("Output"));
198
199         if (bundle->ports_are_inputs()) {
200                 _input_or_output.set_active_text (_("Input"));
201         } else {
202                 _input_or_output.set_active_text (_("Output"));
203         }
204
205         _input_or_output.signal_changed().connect (sigc::mem_fun (*this, &BundleEditor::input_or_output_changed));
206
207         /* Type (audio or MIDI) */
208         a = new Gtk::Alignment (1, 0.5, 0, 1);
209         a->add (*Gtk::manage (new Gtk::Label (_("Type:"))));
210         t->attach (*Gtk::manage (a), 0, 1, 2, 3, Gtk::FILL, Gtk::FILL);
211         a = new Gtk::Alignment (0, 0.5, 0, 1);
212         a->add (_type);
213         t->attach (*Gtk::manage (a), 1, 2, 2, 3);
214
215         _type.append_text (_("Audio"));
216         _type.append_text (_("MIDI"));
217
218         switch (bundle->type ()) {
219         case DataType::AUDIO:
220                 _type.set_active_text (_("Audio"));
221                 break;
222         case DataType::MIDI:
223                 _type.set_active_text (_("MIDI"));
224                 break;
225         }
226
227         _type.signal_changed().connect (sigc::mem_fun (*this, &BundleEditor::type_changed));
228
229         get_vbox()->pack_start (*Gtk::manage (t), false, false);
230         get_vbox()->pack_start (_matrix);
231         get_vbox()->set_spacing (4);
232
233         add_button (Gtk::Stock::CLOSE, Gtk::RESPONSE_ACCEPT);
234         show_all ();
235 }
236
237 void
238 BundleEditor::on_show ()
239 {
240         Gtk::Window::on_show ();
241         pair<uint32_t, uint32_t> const pm_max = _matrix.max_size ();
242         resize_window_to_proportion_of_monitor (this, pm_max.first, pm_max.second);
243 }
244
245 void
246 BundleEditor::name_changed ()
247 {
248         _bundle->set_name (_name.get_text ());
249 }
250
251 void
252 BundleEditor::input_or_output_changed ()
253 {
254         _bundle->remove_ports_from_channels ();
255
256         if (_input_or_output.get_active_text() == _("Output")) {
257                 _bundle->set_ports_are_outputs ();
258         } else {
259                 _bundle->set_ports_are_inputs ();
260         }
261
262         _matrix.setup_all_ports ();
263 }
264
265 void
266 BundleEditor::type_changed ()
267 {
268         _bundle->remove_ports_from_channels ();
269
270         DataType const t = _type.get_active_text() == _("Audio") ?
271                 DataType::AUDIO : DataType::MIDI;
272
273         _bundle->set_type (t);
274         _matrix.set_type (t);
275 }
276
277 void
278 BundleEditor::on_map ()
279 {
280         _matrix.setup_all_ports ();
281         Window::on_map ();
282 }
283
284
285 BundleManager::BundleManager (Session* session)
286         : ArdourDialog (_("Bundle Manager"))
287         , edit_button (_("Edit"))
288         , delete_button (_("Delete"))
289 {
290         set_session (session);
291
292         _list_model = Gtk::ListStore::create (_list_model_columns);
293         _tree_view.set_model (_list_model);
294         _tree_view.append_column (_("Name"), _list_model_columns.name);
295         _tree_view.set_headers_visible (false);
296
297         boost::shared_ptr<BundleList> bundles = _session->bundles ();
298         for (BundleList::iterator i = bundles->begin(); i != bundles->end(); ++i) {
299                 add_bundle (*i);
300         }
301
302         /* New / Edit / Delete buttons */
303         Gtk::VBox* buttons = new Gtk::VBox;
304         buttons->set_spacing (8);
305         Gtk::Button* b = new Gtk::Button (_("New"));
306         b->set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::NEW, Gtk::ICON_SIZE_BUTTON)));
307         b->signal_clicked().connect (sigc::mem_fun (*this, &BundleManager::new_clicked));
308         buttons->pack_start (*Gtk::manage (b), false, false);
309         edit_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::EDIT, Gtk::ICON_SIZE_BUTTON)));
310         edit_button.signal_clicked().connect (sigc::mem_fun (*this, &BundleManager::edit_clicked));
311         buttons->pack_start (edit_button, false, false);
312         delete_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::DELETE, Gtk::ICON_SIZE_BUTTON)));
313         delete_button.signal_clicked().connect (sigc::mem_fun (*this, &BundleManager::delete_clicked));
314         buttons->pack_start (delete_button, false, false);
315
316         Gtk::HBox* h = new Gtk::HBox;
317         h->set_spacing (8);
318         h->set_border_width (8);
319         h->pack_start (_tree_view);
320         h->pack_start (*Gtk::manage (buttons), false, false);
321
322         get_vbox()->set_spacing (8);
323         get_vbox()->pack_start (*Gtk::manage (h));
324
325         set_default_size (480, 240);
326
327         _tree_view.get_selection()->signal_changed().connect (
328                 sigc::mem_fun (*this, &BundleManager::set_button_sensitivity)
329                 );
330
331         _tree_view.signal_row_activated().connect (
332                 sigc::mem_fun (*this, &BundleManager::row_activated)
333                 );
334
335         set_button_sensitivity ();
336
337         show_all ();
338 }
339
340 void
341 BundleManager::set_button_sensitivity ()
342 {
343         bool const sel = (_tree_view.get_selection()->get_selected() != 0);
344         edit_button.set_sensitive (sel);
345         delete_button.set_sensitive (sel);
346 }
347
348
349 void
350 BundleManager::new_clicked ()
351 {
352         boost::shared_ptr<UserBundle> b (new UserBundle (_("Bundle")));
353
354         /* Start off with a single channel */
355         b->add_channel ("1");
356
357         _session->add_bundle (b);
358         add_bundle (b);
359
360         BundleEditor e (_session, b);
361         e.run ();
362 }
363
364 void
365 BundleManager::edit_clicked ()
366 {
367         Gtk::TreeModel::iterator i = _tree_view.get_selection()->get_selected();
368         if (i) {
369                 boost::shared_ptr<UserBundle> b = (*i)[_list_model_columns.bundle];
370                 BundleEditor e (_session, b);
371                 e.run ();
372         }
373 }
374
375 void
376 BundleManager::delete_clicked ()
377 {
378         Gtk::TreeModel::iterator i = _tree_view.get_selection()->get_selected();
379         if (i) {
380                 boost::shared_ptr<UserBundle> b = (*i)[_list_model_columns.bundle];
381                 _session->remove_bundle (b);
382                 _list_model->erase (i);
383         }
384 }
385
386 void
387 BundleManager::add_bundle (boost::shared_ptr<Bundle> b)
388 {
389         boost::shared_ptr<UserBundle> u = boost::dynamic_pointer_cast<UserBundle> (b);
390         if (u == 0) {
391                 return;
392         }
393
394         Gtk::TreeModel::iterator i = _list_model->append ();
395         (*i)[_list_model_columns.name] = u->name ();
396         (*i)[_list_model_columns.bundle] = u;
397
398         u->Changed.connect (bundle_connections, boost::bind (&BundleManager::bundle_changed, this, _1, u));
399 }
400
401 void
402 BundleManager::bundle_changed (Bundle::Change c, boost::shared_ptr<UserBundle> b)
403 {
404         if ((c & Bundle::NameChanged) == 0) {
405                 return;
406         }
407
408         Gtk::TreeModel::iterator i = _list_model->children().begin ();
409         while (i != _list_model->children().end()) {
410                 boost::shared_ptr<UserBundle> t = (*i)[_list_model_columns.bundle];
411                 if (t == b) {
412                         break;
413                 }
414                 ++i;
415         }
416
417         if (i != _list_model->children().end()) {
418                 (*i)[_list_model_columns.name] = b->name ();
419         }
420 }
421
422 void
423 BundleManager::row_activated (Gtk::TreeModel::Path const & p, Gtk::TreeViewColumn* c)
424 {
425         Gtk::TreeModel::iterator i = _list_model->get_iter (p);
426         if (!i) {
427                 return;
428         }
429         
430         boost::shared_ptr<UserBundle> b = (*i)[_list_model_columns.bundle];
431         BundleEditor e (_session, b);
432         e.run ();
433 }
434
435 NameChannelDialog::NameChannelDialog ()
436         : ArdourDialog (_("Add channel")),
437           _adding (true)
438 {
439         setup ();
440 }
441
442 NameChannelDialog::NameChannelDialog (boost::shared_ptr<Bundle> b, uint32_t c)
443         : ArdourDialog (_("Rename channel")),
444           _bundle (b),
445           _channel (c),
446           _adding (false)
447 {
448         _name.set_text (b->channel_name (c));
449
450         setup ();
451 }
452
453 void
454 NameChannelDialog::setup ()
455 {
456         Gtk::HBox* box = Gtk::manage (new Gtk::HBox ());
457
458         box->pack_start (*Gtk::manage (new Gtk::Label (_("Name"))));
459         box->pack_start (_name);
460         _name.set_activates_default (true);
461
462         get_vbox ()->pack_end (*box);
463         box->show_all ();
464
465         add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
466         if (_adding) {
467                 add_button (Gtk::Stock::ADD, Gtk::RESPONSE_ACCEPT);
468         } else {
469                 add_button (Gtk::Stock::APPLY, Gtk::RESPONSE_ACCEPT);
470         }
471         set_default_response (Gtk::RESPONSE_ACCEPT);
472 }
473
474 string
475 NameChannelDialog::get_name () const
476 {
477         return _name.get_text ();
478 }
479