2c44a62a342bc4606e1eb11c21137b1c5b4a3280
[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 "pbd/i18n.h"
33 #include "utils.h"
34
35 using namespace std;
36 using namespace ARDOUR;
37 using namespace ARDOUR_UI_UTILS;
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, show_only_bundles ());
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_channels (boost::shared_ptr<Bundle> b) const
105 {
106         if (b == _bundle) {
107                 return true;
108         }
109
110         return PortMatrix::can_add_channels (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
120                 if (d.run () != Gtk::RESPONSE_ACCEPT) {
121                         return;
122                 }
123
124                 _bundle->add_channel (d.get_name(), t);
125                 setup_ports (OURS);
126
127         } else {
128
129                 PortMatrix::add_channel (b, t);
130
131         }
132 }
133
134 bool
135 BundleEditorMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
136 {
137         if (b == _bundle) {
138                 return true;
139         }
140
141         return PortMatrix::can_remove_channels (b);
142 }
143
144 void
145 BundleEditorMatrix::remove_channel (BundleChannel bc)
146 {
147         bc.bundle->remove_channel (bc.channel);
148         setup_ports (OURS);
149 }
150
151 bool
152 BundleEditorMatrix::can_rename_channels (boost::shared_ptr<Bundle> b) const
153 {
154         if (b == _bundle) {
155                 return true;
156         }
157
158         return PortMatrix::can_rename_channels (b);
159 }
160
161 void
162 BundleEditorMatrix::rename_channel (BundleChannel bc)
163 {
164         NameChannelDialog d (bc.bundle, bc.channel);
165
166         if (d.run () != Gtk::RESPONSE_ACCEPT) {
167                 return;
168         }
169
170         bc.bundle->set_channel_name (bc.channel, d.get_name ());
171 }
172
173 bool
174 BundleEditorMatrix::list_is_global (int dim) const
175 {
176         return (dim == OTHER);
177 }
178
179 string
180 BundleEditorMatrix::disassociation_verb () const
181 {
182         return _("Disassociate");
183 }
184
185 BundleEditor::BundleEditor (Session* session, boost::shared_ptr<UserBundle> bundle)
186         : ArdourDialog (_("Edit Bundle")), _matrix (this, session, bundle), _bundle (bundle)
187 {
188         Gtk::Table* t = new Gtk::Table (3, 2);
189         t->set_spacings (4);
190
191         /* Bundle name */
192         Gtk::Alignment* a = new Gtk::Alignment (1, 0.5, 0, 1);
193         a->add (*Gtk::manage (new Gtk::Label (_("Name:"))));
194         t->attach (*Gtk::manage (a), 0, 1, 0, 1, Gtk::FILL, Gtk::FILL);
195         t->attach (_name, 1, 2, 0, 1);
196         _name.set_text (_bundle->name ());
197         _name.signal_changed().connect (sigc::mem_fun (*this, &BundleEditor::name_changed));
198
199         /* Direction (input or output) */
200         a = new Gtk::Alignment (1, 0.5, 0, 1);
201         a->add (*Gtk::manage (new Gtk::Label (_("Direction:"))));
202         t->attach (*Gtk::manage (a), 0, 1, 1, 2, Gtk::FILL, Gtk::FILL);
203         a = new Gtk::Alignment (0, 0.5, 0, 1);
204         a->add (_input_or_output);
205         t->attach (*Gtk::manage (a), 1, 2, 1, 2);
206         _input_or_output.append_text (_("Destination"));
207         _input_or_output.append_text (_("Source"));
208
209         if (bundle->ports_are_inputs()) {
210                 _input_or_output.set_active_text (_("Source"));
211         } else {
212                 _input_or_output.set_active_text (_("Destination"));
213         }
214
215         _input_or_output.signal_changed().connect (sigc::mem_fun (*this, &BundleEditor::input_or_output_changed));
216
217         get_vbox()->pack_start (*Gtk::manage (t), false, false);
218         get_vbox()->pack_start (_matrix);
219         get_vbox()->set_spacing (4);
220
221         add_button (Gtk::Stock::CLOSE, Gtk::RESPONSE_ACCEPT);
222         show_all ();
223
224         signal_key_press_event().connect (sigc::mem_fun (_matrix, &BundleEditorMatrix::key_press));
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() == _("Source")) {
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::StockID(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         Gtk::Button* close_but = add_button (Gtk::Stock::CLOSE, Gtk::RESPONSE_ACCEPT);
314         close_but->signal_clicked ().connect (sigc::mem_fun (*this, &Gtk::Window::hide));
315
316         set_button_sensitivity ();
317
318         show_all ();
319 }
320
321 void
322 BundleManager::set_button_sensitivity ()
323 {
324         bool const sel = (_tree_view.get_selection()->get_selected() != 0);
325         edit_button.set_sensitive (sel);
326         delete_button.set_sensitive (sel);
327 }
328
329
330 void
331 BundleManager::new_clicked ()
332 {
333         boost::shared_ptr<UserBundle> b (new UserBundle (_("Bundle")));
334
335         /* Start off with a single channel */
336         /* XXX: allow user to specify type */
337         b->add_channel ("1", DataType::AUDIO);
338
339         _session->add_bundle (b);
340         add_bundle (b);
341
342         BundleEditor e (_session, b);
343         e.run ();
344 }
345
346 void
347 BundleManager::edit_clicked ()
348 {
349         Gtk::TreeModel::iterator i = _tree_view.get_selection()->get_selected();
350         if (i) {
351                 boost::shared_ptr<UserBundle> b = (*i)[_list_model_columns.bundle];
352                 BundleEditor e (_session, b);
353                 e.run ();
354         }
355 }
356
357 void
358 BundleManager::delete_clicked ()
359 {
360         Gtk::TreeModel::iterator i = _tree_view.get_selection()->get_selected();
361         if (i) {
362                 boost::shared_ptr<UserBundle> b = (*i)[_list_model_columns.bundle];
363                 _session->remove_bundle (b);
364                 _list_model->erase (i);
365         }
366 }
367
368 void
369 BundleManager::add_bundle (boost::shared_ptr<Bundle> b)
370 {
371         boost::shared_ptr<UserBundle> u = boost::dynamic_pointer_cast<UserBundle> (b);
372         if (u == 0) {
373                 return;
374         }
375
376         Gtk::TreeModel::iterator i = _list_model->append ();
377         (*i)[_list_model_columns.name] = u->name ();
378         (*i)[_list_model_columns.bundle] = u;
379
380         u->Changed.connect (bundle_connections, invalidator (*this), boost::bind (&BundleManager::bundle_changed, this, _1, u), gui_context());
381 }
382
383 void
384 BundleManager::bundle_changed (Bundle::Change c, boost::shared_ptr<UserBundle> b)
385 {
386         if ((c & Bundle::NameChanged) == 0) {
387                 return;
388         }
389
390         Gtk::TreeModel::iterator i = _list_model->children().begin ();
391         while (i != _list_model->children().end()) {
392                 boost::shared_ptr<UserBundle> t = (*i)[_list_model_columns.bundle];
393                 if (t == b) {
394                         break;
395                 }
396                 ++i;
397         }
398
399         if (i != _list_model->children().end()) {
400                 (*i)[_list_model_columns.name] = b->name ();
401         }
402 }
403
404 void
405 BundleManager::row_activated (Gtk::TreeModel::Path const & p, Gtk::TreeViewColumn*)
406 {
407         Gtk::TreeModel::iterator i = _list_model->get_iter (p);
408         if (!i) {
409                 return;
410         }
411
412         boost::shared_ptr<UserBundle> b = (*i)[_list_model_columns.bundle];
413         BundleEditor e (_session, b);
414         e.run ();
415 }
416
417 NameChannelDialog::NameChannelDialog ()
418         : ArdourDialog (_("Add Channel")),
419           _adding (true)
420 {
421         setup ();
422 }
423
424 NameChannelDialog::NameChannelDialog (boost::shared_ptr<Bundle> b, uint32_t c)
425         : ArdourDialog (_("Rename Channel")),
426           _bundle (b),
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