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