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