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