Unconditionally save instant.xml on session-close
[ardour.git] / gtk2_ardour / bundle_manager.cc
1 /*
2  * Copyright (C) 2007-2012 Carl Hetherington <carl@carlh.net>
3  * Copyright (C) 2008-2016 Paul Davis <paul@linuxaudiosystems.com>
4  * Copyright (C) 2009-2014 David Robillard <d@drobilla.net>
5  * Copyright (C) 2014-2016 Robin Gareus <robin@gareus.org>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include <gtkmm/stock.h>
23 #include <gtkmm/button.h>
24 #include <gtkmm/label.h>
25 #include <gtkmm/entry.h>
26 #include <gtkmm/table.h>
27 #include <gtkmm/comboboxtext.h>
28 #include <gtkmm/alignment.h>
29
30 #include "ardour/session.h"
31 #include "ardour/user_bundle.h"
32 #include "bundle_manager.h"
33 #include "gui_thread.h"
34 #include "pbd/i18n.h"
35 #include "utils.h"
36
37 using namespace std;
38 using namespace ARDOUR;
39 using namespace ARDOUR_UI_UTILS;
40
41 BundleEditorMatrix::BundleEditorMatrix (Gtk::Window* parent, Session* session, boost::shared_ptr<Bundle> bundle)
42         : PortMatrix (parent, session, DataType::NIL)
43         , _bundle (bundle)
44 {
45         _port_group = boost::shared_ptr<PortGroup> (new PortGroup (""));
46         _port_group->add_bundle (_bundle);
47
48         setup_all_ports ();
49         init ();
50 }
51
52 void
53 BundleEditorMatrix::setup_ports (int dim)
54 {
55         if (dim == OURS) {
56                 _ports[OURS].clear ();
57                 _ports[OURS].add_group (_port_group);
58         } else {
59                 _ports[OTHER].suspend_signals ();
60
61                 /* when we gather, allow the matrix to contain bundles with duplicate port sets,
62                    otherwise in some cases the basic system IO ports may be hidden, making
63                    the bundle editor useless */
64
65                 _ports[OTHER].gather (_session, DataType::NIL, _bundle->ports_are_inputs(), true, show_only_bundles ());
66                 _ports[OTHER].remove_bundle (_bundle);
67                 _ports[OTHER].resume_signals ();
68         }
69 }
70
71 void
72 BundleEditorMatrix::set_state (BundleChannel c[2], bool s)
73 {
74         Bundle::PortList const& pl = c[OTHER].bundle->channel_ports (c[OTHER].channel);
75         for (Bundle::PortList::const_iterator i = pl.begin(); i != pl.end(); ++i) {
76                 if (s) {
77                         c[OURS].bundle->add_port_to_channel (c[OURS].channel, *i);
78                 } else {
79                         c[OURS].bundle->remove_port_from_channel (c[OURS].channel, *i);
80                 }
81         }
82 }
83
84 PortMatrixNode::State
85 BundleEditorMatrix::get_state (BundleChannel c[2]) const
86 {
87         if (c[0].bundle->nchannels() == ChanCount::ZERO || c[1].bundle->nchannels() == ChanCount::ZERO) {
88                 return PortMatrixNode::NOT_ASSOCIATED;
89         }
90
91         Bundle::PortList const& pl = c[OTHER].bundle->channel_ports (c[OTHER].channel);
92         if (pl.empty ()) {
93                 return PortMatrixNode::NOT_ASSOCIATED;
94         }
95
96         for (Bundle::PortList::const_iterator i = pl.begin(); i != pl.end(); ++i) {
97                 if (!c[OURS].bundle->port_attached_to_channel (c[OURS].channel, *i)) {
98                         return PortMatrixNode::NOT_ASSOCIATED;
99                 }
100         }
101
102         return PortMatrixNode::ASSOCIATED;
103 }
104
105 bool
106 BundleEditorMatrix::can_add_channels (boost::shared_ptr<Bundle> b) const
107 {
108         if (b == _bundle) {
109                 return true;
110         }
111
112         return PortMatrix::can_add_channels (b);
113 }
114
115 void
116 BundleEditorMatrix::add_channel (boost::shared_ptr<Bundle> b, DataType t)
117 {
118         if (b == _bundle) {
119
120                 NameChannelDialog d;
121
122                 if (d.run () != Gtk::RESPONSE_ACCEPT) {
123                         return;
124                 }
125
126                 _bundle->add_channel (d.get_name(), t);
127                 setup_ports (OURS);
128
129         } else {
130
131                 PortMatrix::add_channel (b, t);
132
133         }
134 }
135
136 bool
137 BundleEditorMatrix::can_remove_channels (boost::shared_ptr<Bundle> b) const
138 {
139         if (b == _bundle) {
140                 return true;
141         }
142
143         return PortMatrix::can_remove_channels (b);
144 }
145
146 void
147 BundleEditorMatrix::remove_channel (BundleChannel bc)
148 {
149         bc.bundle->remove_channel (bc.channel);
150         setup_ports (OURS);
151 }
152
153 bool
154 BundleEditorMatrix::can_rename_channels (boost::shared_ptr<Bundle> b) const
155 {
156         if (b == _bundle) {
157                 return true;
158         }
159
160         return PortMatrix::can_rename_channels (b);
161 }
162
163 void
164 BundleEditorMatrix::rename_channel (BundleChannel bc)
165 {
166         NameChannelDialog d (bc.bundle, bc.channel);
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 (_("Destination"));
209         _input_or_output.append_text (_("Source"));
210
211         if (bundle->ports_are_inputs()) {
212                 _input_or_output.set_active_text (_("Source"));
213         } else {
214                 _input_or_output.set_active_text (_("Destination"));
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         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() == _("Source")) {
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::StockID(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         set_button_sensitivity ();
315
316         show_all ();
317 }
318
319 void
320 BundleManager::set_button_sensitivity ()
321 {
322         bool const sel = (_tree_view.get_selection()->get_selected() != 0);
323         edit_button.set_sensitive (sel);
324         delete_button.set_sensitive (sel);
325 }
326
327
328 void
329 BundleManager::new_clicked ()
330 {
331         boost::shared_ptr<UserBundle> b (new UserBundle (_("Bundle")));
332
333         /* Start off with a single channel */
334         /* XXX: allow user to specify type */
335         b->add_channel ("1", DataType::AUDIO);
336
337         _session->add_bundle (b);
338         add_bundle (b);
339
340         BundleEditor e (_session, b);
341         e.run ();
342 }
343
344 void
345 BundleManager::edit_clicked ()
346 {
347         Gtk::TreeModel::iterator i = _tree_view.get_selection()->get_selected();
348         if (i) {
349                 boost::shared_ptr<UserBundle> b = (*i)[_list_model_columns.bundle];
350                 BundleEditor e (_session, b);
351                 e.run ();
352         }
353 }
354
355 void
356 BundleManager::delete_clicked ()
357 {
358         Gtk::TreeModel::iterator i = _tree_view.get_selection()->get_selected();
359         if (i) {
360                 boost::shared_ptr<UserBundle> b = (*i)[_list_model_columns.bundle];
361                 _session->remove_bundle (b);
362                 _list_model->erase (i);
363         }
364 }
365
366 void
367 BundleManager::add_bundle (boost::shared_ptr<Bundle> b)
368 {
369         boost::shared_ptr<UserBundle> u = boost::dynamic_pointer_cast<UserBundle> (b);
370         if (u == 0) {
371                 return;
372         }
373
374         Gtk::TreeModel::iterator i = _list_model->append ();
375         (*i)[_list_model_columns.name] = u->name ();
376         (*i)[_list_model_columns.bundle] = u;
377
378         u->Changed.connect (bundle_connections, invalidator (*this), boost::bind (&BundleManager::bundle_changed, this, _1, u), gui_context());
379 }
380
381 void
382 BundleManager::bundle_changed (Bundle::Change c, boost::shared_ptr<UserBundle> b)
383 {
384         if ((c & Bundle::NameChanged) == 0) {
385                 return;
386         }
387
388         Gtk::TreeModel::iterator i = _list_model->children().begin ();
389         while (i != _list_model->children().end()) {
390                 boost::shared_ptr<UserBundle> t = (*i)[_list_model_columns.bundle];
391                 if (t == b) {
392                         break;
393                 }
394                 ++i;
395         }
396
397         if (i != _list_model->children().end()) {
398                 (*i)[_list_model_columns.name] = b->name ();
399         }
400 }
401
402 void
403 BundleManager::row_activated (Gtk::TreeModel::Path const & p, Gtk::TreeViewColumn*)
404 {
405         Gtk::TreeModel::iterator i = _list_model->get_iter (p);
406         if (!i) {
407                 return;
408         }
409
410         boost::shared_ptr<UserBundle> b = (*i)[_list_model_columns.bundle];
411         BundleEditor e (_session, b);
412         e.run ();
413 }
414
415 NameChannelDialog::NameChannelDialog ()
416         : ArdourDialog (_("Add Channel")),
417           _adding (true)
418 {
419         setup ();
420 }
421
422 NameChannelDialog::NameChannelDialog (boost::shared_ptr<Bundle> b, uint32_t c)
423         : ArdourDialog (_("Rename Channel")),
424           _bundle (b),
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