1 /*
2  * Copyright (C) 2005-2017 Paul Davis <paul@linuxaudiosystems.com>
3  * Copyright (C) 2012-2021 Robin Gareus <robin@gareus.org>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 #include "ardour/async_midi_port.h"
21 #include "ardour/audioengine.h"
22 #include "ardour/profile.h"
23 #include "ardour/route.h"
24 #include "ardour/session.h"
25 #include "ardour/track.h"
26 #include "ardour/user_bundle.h"
27 
28 #include "gtkmm2ext/menu_elems.h"
29 #include "gtkmm2ext/utils.h"
30 #include "widgets/tooltips.h"
31 
32 #include "ardour_message.h"
33 #include "gui_thread.h"
34 #include "io_button.h"
35 #include "io_selector.h"
36 #include "route_ui.h"
37 #include "ui_config.h"
38 
39 #include "pbd/i18n.h"
40 
41 using namespace ARDOUR;
42 using namespace ArdourWidgets;
43 using namespace PBD;
44 using namespace Gtkmm2ext;
45 using namespace std;
46 
IOButton(bool input)47 IOButton::IOButton (bool input)
48 	: _input (input)
49 	, _route_ui (0)
50 {
51 	set_text (input ? _("Input") : _("Output"));
52 	set_name ("mixer strip button");
53 	set_text_ellipsize (Pango::ELLIPSIZE_MIDDLE);
54 
55 	signal_button_press_event ().connect (sigc::mem_fun (*this, &IOButton::button_press), false);
56 	signal_button_release_event ().connect (sigc::mem_fun (*this, &IOButton::button_release), false);
57 	signal_size_allocate ().connect (sigc::mem_fun (*this, &IOButton::button_resized));
58 }
59 
60 void
set_route(boost::shared_ptr<ARDOUR::Route> rt,RouteUI * routeui)61 IOButton::set_route (boost::shared_ptr<ARDOUR::Route> rt, RouteUI* routeui)
62 {
63 	_connections.drop_connections ();
64 	_bundle_connections.drop_connections ();
65 
66 	_route    = rt;
67 	_route_ui = routeui;
68 
69 	if (!_route) {
70 		_route_ui = NULL;
71 		return;
72 	}
73 
74 	AudioEngine::instance ()->PortConnectedOrDisconnected.connect (_connections, invalidator (*this), boost::bind (&IOButton::port_connected_or_disconnected, this, _1, _3), gui_context ());
75 	AudioEngine::instance ()->PortPrettyNameChanged.connect (_connections, invalidator (*this), boost::bind (&IOButton::port_pretty_name_changed, this, _1), gui_context ());
76 
77 	io ()->changed.connect (_connections, invalidator (*this), boost::bind (&IOButton::update, this), gui_context ());
78 	/* We're really only interested in BundleRemoved when connected to that bundle */
79 	_route->session ().BundleAddedOrRemoved.connect (_connections, invalidator (*this), boost::bind (&IOButton::update, this), gui_context ());
80 
81 	update ();
82 }
83 
~IOButton()84 IOButton::~IOButton ()
85 {
86 }
87 
88 boost::shared_ptr<IO>
io() const89 IOButton::io () const
90 {
91 	return _input ? _route->input () : _route->output ();
92 }
93 
94 boost::shared_ptr<Track>
track() const95 IOButton::track () const
96 {
97 	return boost::dynamic_pointer_cast<Track> (_route);
98 }
99 
100 void
port_pretty_name_changed(std::string pn)101 IOButton::port_pretty_name_changed (std::string pn)
102 {
103 	if (io ()->connected_to (pn)) {
104 		update ();
105 	}
106 }
107 
108 void
port_connected_or_disconnected(boost::weak_ptr<Port> wa,boost::weak_ptr<Port> wb)109 IOButton::port_connected_or_disconnected (boost::weak_ptr<Port> wa, boost::weak_ptr<Port> wb)
110 {
111 	boost::shared_ptr<Port> a = wa.lock ();
112 	boost::shared_ptr<Port> b = wb.lock ();
113 
114 	if ((a && io ()->has_port (a)) || (b && io ()->has_port (b))) {
115 		update ();
116 	}
117 }
118 
119 void
bundle_chosen(boost::shared_ptr<ARDOUR::Bundle> c)120 IOButton::bundle_chosen (boost::shared_ptr<ARDOUR::Bundle> c)
121 {
122 	if (_input) {
123 		_route->input ()->connect_ports_to_bundle (c, true, this);
124 	} else {
125 		_route->output ()->connect_ports_to_bundle (c, true, true, this);
126 	}
127 }
128 
129 void
disconnect()130 IOButton::disconnect ()
131 {
132 	io ()->disconnect (this);
133 }
134 
135 void
add_port(DataType t)136 IOButton::add_port (DataType t)
137 {
138 	if (io ()->add_port ("", this, t) != 0) {
139 		ArdourMessageDialog msg (_("It is not possible to add a port here."));
140 		msg.set_title (_("Cannot add port"));
141 		msg.run ();
142 	}
143 }
144 
145 void
button_resized(Gtk::Allocation & alloc)146 IOButton::button_resized (Gtk::Allocation& alloc)
147 {
148 	set_layout_ellipsize_width (alloc.get_width () * PANGO_SCALE);
149 }
150 
151 struct RouteCompareByName {
operator ()RouteCompareByName152 	bool operator() (boost::shared_ptr<Route> a, boost::shared_ptr<Route> b)
153 	{
154 		return a->name ().compare (b->name ()) < 0;
155 	}
156 };
157 
158 bool
button_release(GdkEventButton * ev)159 IOButton::button_release (GdkEventButton* ev)
160 {
161 	if (!_route || !_route_ui) {
162 		return false;
163 	}
164 	if (ev->button == 3) {
165 		if (_input) {
166 			_route_ui->edit_input_configuration ();
167 		} else {
168 			_route_ui->edit_output_configuration ();
169 		}
170 	}
171 	return false;
172 }
173 
174 bool
button_press(GdkEventButton * ev)175 IOButton::button_press (GdkEventButton* ev)
176 {
177 	using namespace Gtk::Menu_Helpers;
178 
179 	if (!ARDOUR_UI_UTILS::engine_is_running () || !_route || !_route_ui) {
180 		return true;
181 	}
182 
183 	MenuList& citems = _menu.items ();
184 	_menu.set_name ("ArdourContextMenu");
185 	citems.clear ();
186 
187 	if (_route->session ().actively_recording () && track () && track ()->rec_enable_control ()->get_value ()) {
188 		return true;
189 	}
190 
191 	switch (ev->button) {
192 		case 3:
193 			/* don't handle the mouse-down here, parent handles mouse-up if needed. */
194 			return false;
195 		case 1:
196 			break;
197 		default:
198 			/* do nothing */
199 			return true;
200 	}
201 
202 	citems.push_back (MenuElem (_("Disconnect"), sigc::mem_fun (*this, &IOButton::disconnect)));
203 	citems.push_back (SeparatorElem ());
204 	uint32_t const n_with_separator = citems.size ();
205 
206 	_menu_bundles.clear ();
207 	ARDOUR::BundleList                    current = io ()->bundles_connected ();
208 	boost::shared_ptr<ARDOUR::BundleList> b       = _route->session ().bundles ();
209 
210 	if (_input) {
211 		/* give user bundles first chance at being in the menu */
212 		for (ARDOUR::BundleList::iterator i = b->begin (); i != b->end (); ++i) {
213 			if (boost::dynamic_pointer_cast<UserBundle> (*i)) {
214 				maybe_add_bundle_to_menu (*i, current);
215 			}
216 		}
217 
218 		for (ARDOUR::BundleList::iterator i = b->begin (); i != b->end (); ++i) {
219 			if (boost::dynamic_pointer_cast<UserBundle> (*i) == 0) {
220 				maybe_add_bundle_to_menu (*i, current);
221 			}
222 		}
223 	} else {
224 		/* guess the user-intended main type of the route output */
225 		DataType intended_type = guess_main_type ();
226 
227 		/* try adding the master bus first */
228 		boost::shared_ptr<Route> master = _route->session ().master_out ();
229 		if (master) {
230 			maybe_add_bundle_to_menu (master->input ()->bundle (), current, intended_type);
231 		}
232 	}
233 
234 	boost::shared_ptr<ARDOUR::RouteList> routes = _route->session ().get_routes ();
235 	RouteList                            copy   = *routes;
236 	copy.sort (RouteCompareByName ());
237 
238 	if (_input) {
239 		/* other routes outputs */
240 		for (ARDOUR::RouteList::const_iterator i = copy.begin (); i != copy.end (); ++i) {
241 			if ((*i)->is_foldbackbus ()) {
242 				continue;
243 			}
244 			if (_route->feeds_according_to_graph (*i)) {
245 				/* do not offer connections that would cause feedback */
246 				continue;
247 			}
248 			maybe_add_bundle_to_menu ((*i)->output ()->bundle (), current);
249 		}
250 	} else {
251 		DataType intended_type = guess_main_type ();
252 
253 		/* other routes inputs */
254 		for (ARDOUR::RouteList::const_iterator i = copy.begin(); i != copy.end(); ++i) {
255 			if ((*i)->is_foldbackbus () || _route->is_foldbackbus ()) {
256 				continue;
257 			}
258 			if ((*i)->feeds_according_to_graph (_route)) {
259 				/* do not offer connections that would cause feedback */
260 				continue;
261 			}
262 			maybe_add_bundle_to_menu ((*i)->input()->bundle(), current, intended_type);
263 		}
264 
265 		/* then try adding user output bundles, often labeled/grouped physical inputs */
266 		for (ARDOUR::BundleList::iterator i = b->begin (); i != b->end (); ++i) {
267 			if (boost::dynamic_pointer_cast<UserBundle> (*i)) {
268 				maybe_add_bundle_to_menu (*i, current, intended_type);
269 			}
270 		}
271 
272 		/* then all other bundles, including physical outs or other sofware */
273 		for (ARDOUR::BundleList::iterator i = b->begin (); i != b->end (); ++i) {
274 			if (boost::dynamic_pointer_cast<UserBundle> (*i) == 0) {
275 				maybe_add_bundle_to_menu (*i, current, intended_type);
276 			}
277 		}
278 	}
279 
280 	if (citems.size () > n_with_separator) {
281 		citems.push_back (SeparatorElem ());
282 	}
283 
284 	if (_input || !ARDOUR::Profile->get_mixbus ()) {
285 		bool need_separator = false;
286 		for (DataType::iterator i = DataType::begin (); i != DataType::end (); ++i) {
287 			if (!io ()->can_add_port (*i)) {
288 				continue;
289 			}
290 			need_separator = true;
291 			citems.push_back (
292 			    MenuElem (
293 			        string_compose (_("Add %1 port"), (*i).to_i18n_string ()),
294 			        sigc::bind (sigc::mem_fun (*this, &IOButton::add_port), *i)));
295 		}
296 		if (need_separator) {
297 			citems.push_back (SeparatorElem ());
298 		}
299 	}
300 
301 	if (_input) {
302 		citems.push_back (MenuElem (_("Routing Grid"), sigc::mem_fun (*_route_ui, &RouteUI::edit_input_configuration)));
303 	} else {
304 		citems.push_back (MenuElem (_("Routing Grid"), sigc::mem_fun (*_route_ui, &RouteUI::edit_output_configuration)));
305 	}
306 
307 	Gtkmm2ext::anchored_menu_popup (&_menu, this, "", 1, ev->time);
308 	return true;
309 }
310 
311 DataType
guess_main_type(bool favor_connected) const312 IOButton::guess_main_type (bool favor_connected) const
313 {
314 	/* The heuristic follows these principles:
315 	 *  A) If all ports that the user connected are of the same type, then he
316 	 *     very probably intends to use the IO with that type. A common subcase
317 	 *     is when the IO has only ports of the same type (connected or not).
318 	 *  B) If several types of ports are connected, then we should guess based
319 	 *     on the likeliness of the user wanting to use a given type.
320 	 *     We assume that the DataTypes are ordered from the most likely to the
321 	 *     least likely when iterating or comparing them with "<".
322 	 *  C) If no port is connected, the same logic can be applied with all ports
323 	 *     instead of connected ones. TODO: Try other ideas, for instance look at
324 	 *     the last plugin output when |for_input| is false (note: when StrictIO
325 	 *     the outs of the last plugin should be the same as the outs of the route
326 	 *     modulo the panner which forwards non-audio anyway).
327 	 * All of these constraints are respected by the following algorithm that
328 	 * just returns the most likely datatype found in connected ports if any, or
329 	 * available ports if any (since if all ports are of the same type, the most
330 	 * likely found will be that one obviously). */
331 
332 	boost::shared_ptr<IO> io = _input ? _route->input () : _route->output ();
333 
334 	/* Find most likely type among connected ports */
335 	if (favor_connected) {
336 		DataType type = DataType::NIL; /* NIL is always last so least likely */
337 		for (PortSet::iterator p = io->ports ().begin (); p != io->ports ().end (); ++p) {
338 			if (p->connected () && p->type () < type)
339 				type = p->type ();
340 		}
341 		if (type != DataType::NIL) {
342 			/* There has been a connected port (necessarily non-NIL) */
343 			return type;
344 		}
345 	}
346 
347 	/* Find most likely type among available ports.
348 	 * The iterator stops before NIL. */
349 	for (DataType::iterator t = DataType::begin (); t != DataType::end (); ++t) {
350 		if (io->n_ports ().n (*t) > 0)
351 			return *t;
352 	}
353 
354 	/* No port at all, return the most likely datatype by default */
355 	return DataType::front ();
356 }
357 
358 /*
359  * Output port labelling
360  *
361  * Case 1: Each output has one connection, all connections are to system:playback_%i
362  *   out 1 -> system:playback_1
363  *   out 2 -> system:playback_2
364  *   out 3 -> system:playback_3
365  *   Display as: 1/2/3
366  *
367  * Case 2: Each output has one connection, all connections are to ardour:track_x/in 1
368  *   out 1 -> ardour:track_x/in 1
369  *   out 2 -> ardour:track_x/in 2
370  *   Display as: track_x
371  *
372  * Case 3: Each output has one connection, all connections are to Jack client "program x"
373  *   out 1 -> program x:foo
374  *   out 2 -> program x:foo
375  *   Display as: program x
376  *
377  * Case 4: No connections (Disconnected)
378  *   Display as: -
379  *
380  * Default case (unusual routing):
381  *   Display as: *number of connections*
382  *
383  *
384  * Tooltips
385  *
386  * .-----------------------------------------------.
387  * | Mixdown                                       |
388  * | out 1 -> ardour:master/in 1, jamin:input/in 1 |
389  * | out 2 -> ardour:master/in 2, jamin:input/in 2 |
390  * '-----------------------------------------------'
391  * .-----------------------------------------------.
392  * | Guitar SM58                                   |
393  * | Disconnected                                  |
394  * '-----------------------------------------------'
395  */
396 
397 void
update()398 IOButton::update ()
399 {
400 	ostringstream tooltip;
401 	ostringstream label;
402 	bool          have_label = false;
403 
404 	uint32_t total_connection_count             = 0;
405 	uint32_t typed_connection_count             = 0;
406 	bool     each_typed_port_has_one_connection = true;
407 
408 	DataType              dt = guess_main_type ();
409 	boost::shared_ptr<IO> io = _input ? _route->input () : _route->output ();
410 
411 	_bundle_connections.drop_connections ();
412 
413 	/* Fill in the tooltip. Also count:
414 	 *  - The total number of connections.
415 	 *  - The number of main-typed connections.
416 	 *  - Whether each main-typed port has exactly one connection. */
417 	if (_input) {
418 		tooltip << string_compose (_("<b>INPUT</b> to %1"),
419 		                           Gtkmm2ext::markup_escape_text (_route->name ()));
420 	} else {
421 		tooltip << string_compose (_("<b>OUTPUT</b> from %1"),
422 		                           Gtkmm2ext::markup_escape_text (_route->name ()));
423 	}
424 
425 	string         arrow = Gtkmm2ext::markup_escape_text (_input ? " <- " : " -> ");
426 	vector<string> port_connections;
427 	for (PortSet::iterator port = io->ports ().begin ();
428 	     port != io->ports ().end ();
429 	     ++port) {
430 		port_connections.clear ();
431 		port->get_connections (port_connections);
432 
433 		uint32_t port_connection_count = 0;
434 
435 		for (vector<string>::iterator i = port_connections.begin ();
436 		     i != port_connections.end ();
437 		     ++i) {
438 			++port_connection_count;
439 
440 			if (port_connection_count == 1) {
441 				tooltip << endl
442 				        << Gtkmm2ext::markup_escape_text (
443 				               port->name ().substr (port->name ().find ("/") + 1));
444 				tooltip << arrow;
445 			} else {
446 				tooltip << ", ";
447 			}
448 
449 			tooltip << Gtkmm2ext::markup_escape_text (*i);
450 		}
451 
452 		total_connection_count += port_connection_count;
453 		if (port->type () == dt) {
454 			typed_connection_count += port_connection_count;
455 			each_typed_port_has_one_connection &= (port_connection_count == 1);
456 		}
457 	}
458 
459 	if (total_connection_count == 0) {
460 		tooltip << endl
461 		        << _("Disconnected");
462 	}
463 
464 	if (typed_connection_count == 0) {
465 		label << "-";
466 		have_label = true;
467 	}
468 
469 	/* Are all main-typed channels connected to the same route ? */
470 	if (!have_label) {
471 		boost::shared_ptr<ARDOUR::RouteList> routes = _route->session ().get_routes ();
472 		for (ARDOUR::RouteList::const_iterator route = routes->begin ();
473 		     route != routes->end ();
474 		     ++route) {
475 			boost::shared_ptr<IO> dest_io = _input ? (*route)->output () : (*route)->input ();
476 			if (io->bundle ()->connected_to (dest_io->bundle (), _route->session ().engine (), dt, true)) {
477 				label << Gtkmm2ext::markup_escape_text ((*route)->name ());
478 				have_label = true;
479 				break;
480 			}
481 		}
482 	}
483 
484 	/* Are all main-typed channels connected to the same (user) bundle ? */
485 	if (!have_label) {
486 		boost::shared_ptr<ARDOUR::BundleList> bundles       = _route->session ().bundles ();
487 		boost::shared_ptr<ARDOUR::Port>       ap            = boost::dynamic_pointer_cast<ARDOUR::Port> (_route->session ().vkbd_output_port ());
488 		std::string                           vkbd_portname = AudioEngine::instance ()->make_port_name_non_relative (ap->name ());
489 		for (ARDOUR::BundleList::iterator bundle = bundles->begin ();
490 		     bundle != bundles->end ();
491 		     ++bundle) {
492 			if (boost::dynamic_pointer_cast<UserBundle> (*bundle) == 0) {
493 				if (!(*bundle)->offers_port (vkbd_portname)) {
494 					continue;
495 				}
496 			}
497 			if (io->bundle ()->connected_to (*bundle, _route->session ().engine (), dt, true)) {
498 				label << Gtkmm2ext::markup_escape_text ((*bundle)->name ());
499 				have_label = true;
500 				(*bundle)->Changed.connect (_bundle_connections, invalidator (*this), boost::bind (&IOButton::update, this), gui_context ());
501 				break;
502 			}
503 		}
504 	}
505 
506 	/* Is each main-typed channel only connected to a physical output ? */
507 	if (!have_label && each_typed_port_has_one_connection) {
508 		ostringstream  temp_label;
509 		vector<string> phys;
510 		string         playorcapture;
511 		if (_input) {
512 			_route->session ().engine ().get_physical_inputs (dt, phys);
513 			playorcapture = "capture_";
514 		} else {
515 			_route->session ().engine ().get_physical_outputs (dt, phys);
516 			playorcapture = "playback_";
517 		}
518 		for (PortSet::iterator port = io->ports ().begin (dt);
519 		     port != io->ports ().end (dt);
520 		     ++port) {
521 			string pn = "";
522 			for (vector<string>::iterator s = phys.begin ();
523 			     s != phys.end ();
524 			     ++s) {
525 				if (!port->connected_to (*s)) {
526 					continue;
527 				}
528 				pn = AudioEngine::instance ()->get_pretty_name_by_name (*s);
529 				if (pn.empty ()) {
530 					string::size_type start = (*s).find (playorcapture);
531 					if (start != string::npos) {
532 						pn = (*s).substr (start + playorcapture.size ());
533 					}
534 				}
535 				break;
536 			}
537 
538 			if (pn.empty ()) {
539 				temp_label.str (""); /* erase the failed attempt */
540 				break;
541 			}
542 			if (port != io->ports ().begin (dt))
543 				temp_label << "/";
544 			temp_label << pn;
545 		}
546 
547 		if (!temp_label.str ().empty ()) {
548 			label << temp_label.str ();
549 			have_label = true;
550 		}
551 	}
552 
553 	/* Is each main-typed channel connected to a single and different port with
554 	 * the same client name (e.g. another JACK client) ? */
555 	if (!have_label && each_typed_port_has_one_connection) {
556 		string         maybe_client = "";
557 		vector<string> connections;
558 		for (PortSet::iterator port = io->ports ().begin (dt);
559 		     port != io->ports ().end (dt);
560 		     ++port) {
561 			port_connections.clear ();
562 			port->get_connections (port_connections);
563 			string connection = port_connections.front ();
564 
565 			vector<string>::iterator i = connections.begin ();
566 			while (i != connections.end () && *i != connection) {
567 				++i;
568 			}
569 			if (i != connections.end ()) {
570 				break; /* duplicate connection */
571 			}
572 			connections.push_back (connection);
573 
574 			connection = connection.substr (0, connection.find (":"));
575 
576 			if (maybe_client.empty ()) {
577 				maybe_client = connection;
578 			}
579 			if (maybe_client != connection) {
580 				break;
581 			}
582 		}
583 		if (connections.size () == io->n_ports ().n (dt)) {
584 			label << maybe_client;
585 			have_label = true;
586 		}
587 	}
588 
589 	/* Odd configuration */
590 	if (!have_label) {
591 		label << "*" << total_connection_count << "*";
592 	}
593 
594 	if (total_connection_count > typed_connection_count) {
595 		label << "\u2295"; /* circled plus */
596 	}
597 
598 	set_text (label.str ());
599 	set_tooltip (this, tooltip.str ());
600 }
601 
602 void
maybe_add_bundle_to_menu(boost::shared_ptr<Bundle> b,ARDOUR::BundleList const &,ARDOUR::DataType type)603 IOButton::maybe_add_bundle_to_menu (boost::shared_ptr<Bundle> b, ARDOUR::BundleList const& /*current*/, ARDOUR::DataType type)
604 {
605 	using namespace Gtk::Menu_Helpers;
606 
607 	if (_input) {
608 		/* The bundle should be a source with matching inputs, but not ours */
609 		if (b->ports_are_outputs () == false || b->nchannels () != _route->n_inputs () || *b == *_route->output ()->bundle ()) {
610 			return;
611 		}
612 	} else {
613 		/* The bundle should be sink, but not ours */
614 		if (b->ports_are_inputs () == false || *b == *_route->input ()->bundle ()) {
615 			return;
616 		}
617 
618 		/* Don't add the monitor input unless we are Master */
619 		boost::shared_ptr<Route> monitor = _route->session ().monitor_out ();
620 		if ((!_route->is_master ()) && monitor && b->has_same_ports (monitor->input ()->bundle ())) {
621 			return;
622 		}
623 
624 		/* It should either match exactly our outputs (if |type| is DataType::NIL)
625 		 * or have the same number of |type| channels than our outputs. */
626 		if (type == DataType::NIL) {
627 			if (b->nchannels () != _route->n_outputs ()) {
628 				return;
629 			}
630 		} else {
631 			if (b->nchannels ().n (type) != _route->n_outputs ().n (type))
632 				return;
633 		}
634 	}
635 
636 	/* Avoid adding duplicates */
637 	list<boost::shared_ptr<Bundle> >::iterator i = _menu_bundles.begin ();
638 	while (i != _menu_bundles.end () && b->has_same_ports (*i) == false) {
639 		++i;
640 	}
641 	if (i != _menu_bundles.end ()) {
642 		return;
643 	}
644 
645 	/* Finally add the bundle to the menu */
646 	_menu_bundles.push_back (b);
647 
648 	MenuList& citems = _menu.items ();
649 	citems.push_back (MenuElemNoMnemonic (b->name (), sigc::bind (sigc::mem_fun (*this, &IOButton::bundle_chosen), b)));
650 }
651