Add a menu option and key press (F) in port matrices to flip the selected row and...
[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                 d.set_position (Gtk::WIN_POS_MOUSE);
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         d.set_position (Gtk::WIN_POS_MOUSE);
166
167         if (d.run () != Gtk::RESPONSE_ACCEPT) {
168                 return;
169         }
170
171         bc.bundle->set_channel_name (bc.channel, d.get_name ());
172 }
173
174 bool
175 BundleEditorMatrix::list_is_global (int dim) const
176 {
177         return (dim == OTHER);
178 }
179
180 string
181 BundleEditorMatrix::disassociation_verb () const
182 {
183         return _("Disassociate");
184 }
185
186 BundleEditor::BundleEditor (Session* session, boost::shared_ptr<UserBundle> bundle)
187         : ArdourDialog (_("Edit Bundle")), _matrix (this, session, bundle), _bundle (bundle)
188 {
189         Gtk::Table* t = new Gtk::Table (3, 2);
190         t->set_spacings (4);
191
192         /* Bundle name */
193         Gtk::Alignment* a = new Gtk::Alignment (1, 0.5, 0, 1);
194         a->add (*Gtk::manage (new Gtk::Label (_("Name:"))));
195         t->attach (*Gtk::manage (a), 0, 1, 0, 1, Gtk::FILL, Gtk::FILL);
196         t->attach (_name, 1, 2, 0, 1);
197         _name.set_text (_bundle->name ());
198         _name.signal_changed().connect (sigc::mem_fun (*this, &BundleEditor::name_changed));
199
200         /* Direction (input or output) */
201         a = new Gtk::Alignment (1, 0.5, 0, 1);
202         a->add (*Gtk::manage (new Gtk::Label (_("Direction:"))));
203         t->attach (*Gtk::manage (a), 0, 1, 1, 2, Gtk::FILL, Gtk::FILL);
204         a = new Gtk::Alignment (0, 0.5, 0, 1);
205         a->add (_input_or_output);
206         t->attach (*Gtk::manage (a), 1, 2, 1, 2);
207         _input_or_output.append_text (_("Input"));
208         _input_or_output.append_text (_("Output"));
209
210         if (bundle->ports_are_inputs()) {
211                 _input_or_output.set_active_text (_("Input"));
212         } else {
213                 _input_or_output.set_active_text (_("Output"));
214         }
215
216         _input_or_output.signal_changed().connect (sigc::mem_fun (*this, &BundleEditor::input_or_output_changed));
217
218         get_vbox()->pack_start (*Gtk::manage (t), false, false);
219         get_vbox()->pack_start (_matrix);
220         get_vbox()->set_spacing (4);
221
222         add_button (Gtk::Stock::CLOSE, Gtk::RESPONSE_ACCEPT);
223         show_all ();
224
225         signal_key_press_event().connect (sigc::mem_fun (_matrix, &BundleEditorMatrix::key_press));
226 }
227
228 void
229 BundleEditor::on_show ()
230 {
231         Gtk::Window::on_show ();
232         pair<uint32_t, uint32_t> const pm_max = _matrix.max_size ();
233         resize_window_to_proportion_of_monitor (this, pm_max.first, pm_max.second);
234 }
235
236 void
237 BundleEditor::name_changed ()
238 {
239         _bundle->set_name (_name.get_text ());
240 }
241
242 void
243 BundleEditor::input_or_output_changed ()
244 {
245         _bundle->remove_ports_from_channels ();
246
247         if (_input_or_output.get_active_text() == _("Output")) {
248                 _bundle->set_ports_are_outputs ();
249         } else {
250                 _bundle->set_ports_are_inputs ();
251         }
252
253         _matrix.setup_all_ports ();
254 }
255
256 void
257 BundleEditor::on_map ()
258 {
259         _matrix.setup_all_ports ();
260         Window::on_map ();
261 }
262
263
264 BundleManager::BundleManager (Session* session)
265         : ArdourDialog (_("Bundle Manager"))
266         , edit_button (_("Edit"))
267         , delete_button (_("Delete"))
268 {
269         set_session (session);
270
271         _list_model = Gtk::ListStore::create (_list_model_columns);
272         _tree_view.set_model (_list_model);
273         _tree_view.append_column (_("Name"), _list_model_columns.name);
274         _tree_view.set_headers_visible (false);
275
276         boost::shared_ptr<BundleList> bundles = _session->bundles ();
277         for (BundleList::iterator i = bundles->begin(); i != bundles->end(); ++i) {
278                 add_bundle (*i);
279         }
280
281         /* New / Edit / Delete buttons */
282         Gtk::VBox* buttons = new Gtk::VBox;
283         buttons->set_spacing (8);
284         Gtk::Button* b = new Gtk::Button (_("New"));
285         b->set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::NEW, Gtk::ICON_SIZE_BUTTON)));
286         b->signal_clicked().connect (sigc::mem_fun (*this, &BundleManager::new_clicked));
287         buttons->pack_start (*Gtk::manage (b), false, false);
288         edit_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::EDIT, Gtk::ICON_SIZE_BUTTON)));
289         edit_button.signal_clicked().connect (sigc::mem_fun (*this, &BundleManager::edit_clicked));
290         buttons->pack_start (edit_button, false, false);
291         delete_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::DELETE, Gtk::ICON_SIZE_BUTTON)));
292         delete_button.signal_clicked().connect (sigc::mem_fun (*this, &BundleManager::delete_clicked));
293         buttons->pack_start (delete_button, false, false);
294
295         Gtk::HBox* h = new Gtk::HBox;
296         h->set_spacing (8);
297         h->set_border_width (8);
298         h->pack_start (_tree_view);
299         h->pack_start (*Gtk::manage (buttons), false, false);
300
301         get_vbox()->set_spacing (8);
302         get_vbox()->pack_start (*Gtk::manage (h));
303
304         set_default_size (480, 240);
305
306         _tree_view.get_selection()->signal_changed().connect (
307                 sigc::mem_fun (*this, &BundleManager::set_button_sensitivity)
308                 );
309
310         _tree_view.signal_row_activated().connect (
311                 sigc::mem_fun (*this, &BundleManager::row_activated)
312                 );
313
314         Gtk::Button* close_but = add_button (Gtk::Stock::CLOSE, Gtk::RESPONSE_ACCEPT);
315         close_but->signal_clicked ().connect (sigc::mem_fun (*this, &Gtk::Window::hide));
316
317         set_button_sensitivity ();
318
319         show_all ();
320 }
321
322 void
323 BundleManager::set_button_sensitivity ()
324 {
325         bool const sel = (_tree_view.get_selection()->get_selected() != 0);
326         edit_button.set_sensitive (sel);
327         delete_button.set_sensitive (sel);
328 }
329
330
331 void
332 BundleManager::new_clicked ()
333 {
334         boost::shared_ptr<UserBundle> b (new UserBundle (_("Bundle")));
335
336         /* Start off with a single channel */
337         /* XXX: allow user to specify type */
338         b->add_channel ("1", DataType::AUDIO);
339
340         _session->add_bundle (b);
341         add_bundle (b);
342
343         BundleEditor e (_session, b);
344         e.run ();
345 }
346
347 void
348 BundleManager::edit_clicked ()
349 {
350         Gtk::TreeModel::iterator i = _tree_view.get_selection()->get_selected();
351         if (i) {
352                 boost::shared_ptr<UserBundle> b = (*i)[_list_model_columns.bundle];
353                 BundleEditor e (_session, b);
354                 e.run ();
355         }
356 }
357
358 void
359 BundleManager::delete_clicked ()
360 {
361         Gtk::TreeModel::iterator i = _tree_view.get_selection()->get_selected();
362         if (i) {
363                 boost::shared_ptr<UserBundle> b = (*i)[_list_model_columns.bundle];
364                 _session->remove_bundle (b);
365                 _list_model->erase (i);
366         }
367 }
368
369 void
370 BundleManager::add_bundle (boost::shared_ptr<Bundle> b)
371 {
372         boost::shared_ptr<UserBundle> u = boost::dynamic_pointer_cast<UserBundle> (b);
373         if (u == 0) {
374                 return;
375         }
376
377         Gtk::TreeModel::iterator i = _list_model->append ();
378         (*i)[_list_model_columns.name] = u->name ();
379         (*i)[_list_model_columns.bundle] = u;
380
381         u->Changed.connect (bundle_connections, invalidator (*this), boost::bind (&BundleManager::bundle_changed, this, _1, u), gui_context());
382 }
383
384 void
385 BundleManager::bundle_changed (Bundle::Change c, boost::shared_ptr<UserBundle> b)
386 {
387         if ((c & Bundle::NameChanged) == 0) {
388                 return;
389         }
390
391         Gtk::TreeModel::iterator i = _list_model->children().begin ();
392         while (i != _list_model->children().end()) {
393                 boost::shared_ptr<UserBundle> t = (*i)[_list_model_columns.bundle];
394                 if (t == b) {
395                         break;
396                 }
397                 ++i;
398         }
399
400         if (i != _list_model->children().end()) {
401                 (*i)[_list_model_columns.name] = b->name ();
402         }
403 }
404
405 void
406 BundleManager::row_activated (Gtk::TreeModel::Path const & p, Gtk::TreeViewColumn*)
407 {
408         Gtk::TreeModel::iterator i = _list_model->get_iter (p);
409         if (!i) {
410                 return;
411         }
412
413         boost::shared_ptr<UserBundle> b = (*i)[_list_model_columns.bundle];
414         BundleEditor e (_session, b);
415         e.run ();
416 }
417
418 NameChannelDialog::NameChannelDialog ()
419         : ArdourDialog (_("Add Channel")),
420           _adding (true)
421 {
422         setup ();
423 }
424
425 NameChannelDialog::NameChannelDialog (boost::shared_ptr<Bundle> b, uint32_t c)
426         : ArdourDialog (_("Rename Channel")),
427           _bundle (b),
428           _channel (c),
429           _adding (false)
430 {
431         _name.set_text (b->channel_name (c));
432
433         setup ();
434 }
435
436 void
437 NameChannelDialog::setup ()
438 {
439         Gtk::HBox* box = Gtk::manage (new Gtk::HBox ());
440
441         box->pack_start (*Gtk::manage (new Gtk::Label (_("Name"))));
442         box->pack_start (_name);
443         _name.set_activates_default (true);
444
445         get_vbox ()->pack_end (*box);
446         box->show_all ();
447
448         add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
449         if (_adding) {
450                 add_button (Gtk::Stock::ADD, Gtk::RESPONSE_ACCEPT);
451         } else {
452                 add_button (Gtk::Stock::APPLY, Gtk::RESPONSE_ACCEPT);
453         }
454         set_default_response (Gtk::RESPONSE_ACCEPT);
455 }
456
457 string
458 NameChannelDialog::get_name () const
459 {
460         return _name.get_text ();
461 }
462