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