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