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