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