A little copy-editing.
[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
28 #include "ardour/session.h"
29 #include "ardour/user_bundle.h"
30 #include "ardour/audioengine.h"
31 #include "bundle_manager.h"
32 #include "gui_thread.h"
33 #include "i18n.h"
34 #include "utils.h"
35
36 using namespace std;
37 using namespace ARDOUR;
38
39 BundleEditorMatrix::BundleEditorMatrix (Gtk::Window* parent, Session* session, boost::shared_ptr<Bundle> bundle)
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"))
289         , edit_button (_("Edit"))
290         , delete_button (_("Delete"))
291 {
292         set_session (session);
293
294         _list_model = Gtk::ListStore::create (_list_model_columns);
295         _tree_view.set_model (_list_model);
296         _tree_view.append_column (_("Name"), _list_model_columns.name);
297         _tree_view.set_headers_visible (false);
298
299         boost::shared_ptr<BundleList> bundles = _session->bundles ();
300         for (BundleList::iterator i = bundles->begin(); i != bundles->end(); ++i) {
301                 add_bundle (*i);
302         }
303
304         /* New / Edit / Delete buttons */
305         Gtk::VBox* buttons = new Gtk::VBox;
306         buttons->set_spacing (8);
307         Gtk::Button* b = new Gtk::Button (_("New"));
308         b->set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::NEW, Gtk::ICON_SIZE_BUTTON)));
309         b->signal_clicked().connect (sigc::mem_fun (*this, &BundleManager::new_clicked));
310         buttons->pack_start (*Gtk::manage (b), false, false);
311         edit_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::EDIT, Gtk::ICON_SIZE_BUTTON)));
312         edit_button.signal_clicked().connect (sigc::mem_fun (*this, &BundleManager::edit_clicked));
313         buttons->pack_start (edit_button, false, false);
314         delete_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::DELETE, Gtk::ICON_SIZE_BUTTON)));
315         delete_button.signal_clicked().connect (sigc::mem_fun (*this, &BundleManager::delete_clicked));
316         buttons->pack_start (delete_button, false, false);
317
318         Gtk::HBox* h = new Gtk::HBox;
319         h->set_spacing (8);
320         h->set_border_width (8);
321         h->pack_start (_tree_view);
322         h->pack_start (*Gtk::manage (buttons), false, false);
323
324         get_vbox()->set_spacing (8);
325         get_vbox()->pack_start (*Gtk::manage (h));
326
327         set_default_size (480, 240);
328
329         _tree_view.get_selection()->signal_changed().connect (
330                 sigc::mem_fun (*this, &BundleManager::set_button_sensitivity)
331                 );
332
333         _tree_view.signal_row_activated().connect (
334                 sigc::mem_fun (*this, &BundleManager::row_activated)
335                 );
336
337         set_button_sensitivity ();
338
339         show_all ();
340 }
341
342 void
343 BundleManager::set_button_sensitivity ()
344 {
345         bool const sel = (_tree_view.get_selection()->get_selected() != 0);
346         edit_button.set_sensitive (sel);
347         delete_button.set_sensitive (sel);
348 }
349
350
351 void
352 BundleManager::new_clicked ()
353 {
354         boost::shared_ptr<UserBundle> b (new UserBundle (_("Bundle")));
355
356         /* Start off with a single channel */
357         b->add_channel ("1");
358
359         _session->add_bundle (b);
360         add_bundle (b);
361
362         BundleEditor e (_session, b);
363         e.run ();
364 }
365
366 void
367 BundleManager::edit_clicked ()
368 {
369         Gtk::TreeModel::iterator i = _tree_view.get_selection()->get_selected();
370         if (i) {
371                 boost::shared_ptr<UserBundle> b = (*i)[_list_model_columns.bundle];
372                 BundleEditor e (_session, b);
373                 e.run ();
374         }
375 }
376
377 void
378 BundleManager::delete_clicked ()
379 {
380         Gtk::TreeModel::iterator i = _tree_view.get_selection()->get_selected();
381         if (i) {
382                 boost::shared_ptr<UserBundle> b = (*i)[_list_model_columns.bundle];
383                 _session->remove_bundle (b);
384                 _list_model->erase (i);
385         }
386 }
387
388 void
389 BundleManager::add_bundle (boost::shared_ptr<Bundle> b)
390 {
391         boost::shared_ptr<UserBundle> u = boost::dynamic_pointer_cast<UserBundle> (b);
392         if (u == 0) {
393                 return;
394         }
395
396         Gtk::TreeModel::iterator i = _list_model->append ();
397         (*i)[_list_model_columns.name] = u->name ();
398         (*i)[_list_model_columns.bundle] = u;
399
400         u->Changed.connect (bundle_connections, invalidator (*this), ui_bind (&BundleManager::bundle_changed, this, _1, u), gui_context());
401 }
402
403 void
404 BundleManager::bundle_changed (Bundle::Change c, boost::shared_ptr<UserBundle> b)
405 {
406         if ((c & Bundle::NameChanged) == 0) {
407                 return;
408         }
409
410         Gtk::TreeModel::iterator i = _list_model->children().begin ();
411         while (i != _list_model->children().end()) {
412                 boost::shared_ptr<UserBundle> t = (*i)[_list_model_columns.bundle];
413                 if (t == b) {
414                         break;
415                 }
416                 ++i;
417         }
418
419         if (i != _list_model->children().end()) {
420                 (*i)[_list_model_columns.name] = b->name ();
421         }
422 }
423
424 void
425 BundleManager::row_activated (Gtk::TreeModel::Path const & p, Gtk::TreeViewColumn*)
426 {
427         Gtk::TreeModel::iterator i = _list_model->get_iter (p);
428         if (!i) {
429                 return;
430         }
431         
432         boost::shared_ptr<UserBundle> b = (*i)[_list_model_columns.bundle];
433         BundleEditor e (_session, b);
434         e.run ();
435 }
436
437 NameChannelDialog::NameChannelDialog ()
438         : ArdourDialog (_("Add Channel")),
439           _adding (true)
440 {
441         setup ();
442 }
443
444 NameChannelDialog::NameChannelDialog (boost::shared_ptr<Bundle> b, uint32_t c)
445         : ArdourDialog (_("Rename Channel")),
446           _bundle (b),
447           _channel (c),
448           _adding (false)
449 {
450         _name.set_text (b->channel_name (c));
451
452         setup ();
453 }
454
455 void
456 NameChannelDialog::setup ()
457 {
458         Gtk::HBox* box = Gtk::manage (new Gtk::HBox ());
459
460         box->pack_start (*Gtk::manage (new Gtk::Label (_("Name"))));
461         box->pack_start (_name);
462         _name.set_activates_default (true);
463
464         get_vbox ()->pack_end (*box);
465         box->show_all ();
466
467         add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
468         if (_adding) {
469                 add_button (Gtk::Stock::ADD, Gtk::RESPONSE_ACCEPT);
470         } else {
471                 add_button (Gtk::Stock::APPLY, Gtk::RESPONSE_ACCEPT);
472         }
473         set_default_response (Gtk::RESPONSE_ACCEPT);
474 }
475
476 string
477 NameChannelDialog::get_name () const
478 {
479         return _name.get_text ();
480 }
481