2 * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
3 * Copyright (C) 2011 Paul Davis
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
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.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 #include <boost/algorithm/string.hpp>
22 #include <gtkmm/table.h>
23 #include <gtkmm/frame.h>
24 #include <gtkmm/box.h>
25 #include <gtkmm/label.h>
27 #include "pbd/replace_all.h"
29 #include "gtkmm2ext/utils.h"
30 #include "gtkmm2ext/rgb_macros.h"
32 #include "ardour/audioengine.h"
33 #include "ardour/plugin.h"
34 #include "ardour/port.h"
35 #include "ardour/session.h"
37 #include "plugin_pin_dialog.h"
38 #include "gui_thread.h"
40 #include "ui_config.h"
44 using namespace ARDOUR;
48 using namespace Gtkmm2ext;
50 PluginPinDialog::PluginPinDialog (boost::shared_ptr<ARDOUR::PluginInsert> pi)
51 : ArdourWindow (string_compose (_("Pin Configuration: %1"), pi->name ()))
52 , _set_config (_("Configure"), ArdourButton::led_default_elements)
53 , _tgl_sidechain (_("Side Chain"), ArdourButton::led_default_elements)
54 , _add_plugin (_("+"))
55 , _del_plugin (_("-"))
56 , _add_output_audio (_("+"))
57 , _del_output_audio (_("-"))
58 , _add_output_midi (_("+"))
59 , _del_output_midi (_("-"))
60 , _add_sc_audio (_("Audio"))
61 , _add_sc_midi (_("MIDI"))
73 , _position_valid (false)
74 , _ignore_updates (false)
75 , _sidechain_selector (0)
78 assert (pi->owner ()); // Route
80 _pi->PluginIoReConfigure.connect (
81 _plugin_connections, invalidator (*this), boost::bind (&PluginPinDialog::plugin_reconfigured, this), gui_context ()
84 _pi->PluginMapChanged.connect (
85 _plugin_connections, invalidator (*this), boost::bind (&PluginPinDialog::plugin_reconfigured, this), gui_context ()
88 _pi->PluginConfigChanged.connect (
89 _plugin_connections, invalidator (*this), boost::bind (&PluginPinDialog::plugin_reconfigured, this), gui_context ()
92 _pin_box_size = 2 * ceil (max (8., 10. * UIConfiguration::instance ().get_ui_scale ()) * .5);
93 _margin_x = 2 * ceil (max (24., 28. * UIConfiguration::instance ().get_ui_scale ()) * .5);
94 _margin_y = 2 * ceil (max (36., 40. * UIConfiguration::instance ().get_ui_scale ()) * .5);
96 _tgl_sidechain.set_name ("pinrouting sidechain");
97 _set_config.set_name ("pinrouting custom");
99 Menu_Helpers::MenuList& citems = reset_menu.items();
100 reset_menu.set_name ("ArdourContextMenu");
102 citems.push_back (Menu_Helpers::MenuElem (_("Reset"), sigc::mem_fun (*this, &PluginPinDialog::reset_mapping)));
104 _pm_size_group = SizeGroup::create (SIZE_GROUP_BOTH);
105 _add_plugin.set_tweaks (ArdourButton::Square);
106 _del_plugin.set_tweaks (ArdourButton::Square);
107 _pm_size_group->add_widget (_add_plugin);
108 _pm_size_group->add_widget (_del_plugin);
109 _pm_size_group->add_widget (_add_output_audio);
110 _pm_size_group->add_widget (_del_output_audio);
111 _pm_size_group->add_widget (_add_output_midi);
112 _pm_size_group->add_widget (_del_output_midi);
117 VBox* tl = manage (new VBox ());
118 tl->set_border_width (2);
121 VBox* tr = manage (new VBox ());
122 tr->set_border_width (2);
126 tl->pack_start (_set_config, false, false);
127 tl->pack_start (*manage (new Label ("")), true, true); // invisible separator
129 box = manage (new HBox ());
130 box->set_border_width (2);
131 box->pack_start (_add_plugin, true, false);
132 box->pack_start (_del_plugin, true, false);
133 f = manage (new Frame());
134 f->set_label (_("Instances"));
136 tl->pack_start (*f, false, false);
138 box = manage (new HBox ());
139 box->set_border_width (2);
140 box->pack_start (_add_output_audio, true, false);
141 box->pack_start (_del_output_audio, true, false);
142 f = manage (new Frame());
143 f->set_label (_("Audio Out"));
145 tl->pack_start (*f, false, false);
147 box = manage (new HBox ());
148 box->set_border_width (2);
149 box->pack_start (_add_output_midi, true, false);
150 box->pack_start (_del_output_midi, true, false);
151 f = manage (new Frame());
152 f->set_label (_("MIDI Out"));
154 tl->pack_start (*f, false, false);
158 _sidechain_tbl = manage (new Gtk::Table ());
159 _sidechain_tbl->set_spacings (2);
161 tr->pack_start (_tgl_sidechain, false, false);
162 tr->pack_start (*_sidechain_tbl, true, true);
164 box = manage (new VBox ());
165 box->set_border_width (2);
166 box->set_spacing (2);
167 box->pack_start (_add_sc_audio, false, false);
168 box->pack_start (_add_sc_midi , false, false);
169 f = manage (new Frame());
170 f->set_label (_("Add Sidechain Input"));
173 tr->pack_start (*f, false, false);
176 HBox* hbox = manage (new HBox ());
177 hbox->set_spacing (4);
178 hbox->pack_start (*tl, false, false);
179 hbox->pack_start (darea, true, true);
180 hbox->pack_start (*tr, false, false);
182 VBox* vbox = manage (new VBox ());
183 vbox->pack_start (*hbox, true, true);
184 set_border_width (4);
188 plugin_reconfigured ();
190 darea.add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::POINTER_MOTION_MASK);
191 darea.signal_size_request ().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_size_request));
192 darea.signal_size_allocate ().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_size_allocate));
193 darea.signal_expose_event ().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_expose_event));
194 darea.signal_button_press_event ().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_button_press_event));
195 darea.signal_button_release_event ().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_button_release_event));
196 darea.signal_motion_notify_event ().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_motion_notify_event));
198 _tgl_sidechain.signal_clicked.connect (sigc::mem_fun (*this, &PluginPinDialog::toggle_sidechain));
200 _set_config.signal_clicked.connect (sigc::mem_fun (*this, &PluginPinDialog::reset_configuration));
201 _add_plugin.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_remove_plugin_clicked), true));
202 _del_plugin.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_remove_plugin_clicked), false));
204 _add_output_audio.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_remove_port_clicked), true, DataType::AUDIO));
205 _del_output_audio.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_remove_port_clicked), false, DataType::AUDIO));
206 _add_output_midi.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_remove_port_clicked), true, DataType::MIDI));
207 _del_output_midi.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_remove_port_clicked), false, DataType::MIDI));
208 _add_sc_audio.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_sidechain_port), DataType::AUDIO));
209 _add_sc_midi.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::add_sidechain_port), DataType::MIDI));
211 AudioEngine::instance()->PortConnectedOrDisconnected.connect (
212 _io_connection, invalidator (*this), boost::bind (&PluginPinDialog::port_connected_or_disconnected, this, _1, _3), gui_context ()
216 PluginPinDialog::~PluginPinDialog ()
218 delete _sidechain_selector;
222 PluginPinDialog::plugin_reconfigured ()
224 ENSURE_GUI_THREAD (*this, &PluginPinDialog::plugin_reconfigured);
225 if (_ignore_updates) {
228 _n_plugins = _pi->get_count ();
229 _pi->configured_io (_in, _out);
230 _ins = _pi->internal_streams (); // with sidechain
231 _sinks = _pi->natural_input_streams ();
232 _sources = _pi->natural_output_streams ();
234 _tgl_sidechain.set_active (_pi->has_sidechain ());
235 _add_sc_audio.set_sensitive (_pi->has_sidechain ());
236 _add_sc_midi.set_sensitive (_pi->has_sidechain ());
238 if (_pi->custom_cfg ()) {
239 _set_config.set_active (true);
240 _add_plugin.set_sensitive (true);
241 _add_output_audio.set_sensitive (true);
242 _add_output_midi.set_sensitive (true);
243 _del_plugin.set_sensitive (_n_plugins > 1);
244 _del_output_audio.set_sensitive (_out.n_audio () > 0 && _out.n_total () > 1);
245 _del_output_midi.set_sensitive (_out.n_midi () > 0 && _out.n_total () > 1);
247 _set_config.set_active (false);
248 _add_plugin.set_sensitive (false);
249 _add_output_audio.set_sensitive (false);
250 _add_output_midi.set_sensitive (false);
251 _del_plugin.set_sensitive (false);
252 _del_output_audio.set_sensitive (false);
253 _del_output_midi.set_sensitive (false);
256 if (!_pi->has_sidechain () && _sidechain_selector) {
257 delete _sidechain_selector;
258 _sidechain_selector = 0;
261 refill_sidechain_table ();
263 /* update elements */
271 _n_inputs = _n_sidechains = 0;
273 for (uint32_t i = 0; i < _ins.n_total (); ++i) {
274 DataType dt = i < _ins.n_midi () ? DataType::MIDI : DataType::AUDIO;
275 uint32_t id = dt == DataType::MIDI ? i : i - _ins.n_midi ();
276 bool sidechain = id >= _in.get (dt) ? true : false;
283 CtrlWidget cw (CtrlWidget (Input, dt, id, 0, sidechain));
284 _elements.push_back (cw);
287 for (uint32_t i = 0; i < _out.n_total (); ++i) {
288 int id = (i < _out.n_midi ()) ? i : i - _out.n_midi ();
289 _elements.push_back (CtrlWidget (Output, (i < _out.n_midi () ? DataType::MIDI : DataType::AUDIO), id));
292 for (uint32_t n = 0; n < _n_plugins; ++n) {
293 boost::shared_ptr<Plugin> plugin = _pi->plugin (n);
294 for (uint32_t i = 0; i < _sinks.n_total (); ++i) {
295 DataType dt (i < _sinks.n_midi () ? DataType::MIDI : DataType::AUDIO);
296 int idx = (dt == DataType::MIDI) ? i : i - _sinks.n_midi ();
297 const Plugin::IOPortDescription& iod (plugin->describe_io_port (dt, true, idx));
298 CtrlWidget cw (CtrlWidget (Sink, dt, idx, n, iod.is_sidechain));
299 _elements.push_back (cw);
301 for (uint32_t i = 0; i < _sources.n_total (); ++i) {
302 DataType dt (i < _sources.n_midi () ? DataType::MIDI : DataType::AUDIO);
303 int idx = (dt == DataType::MIDI) ? i : i - _sources.n_midi ();
304 _elements.push_back (CtrlWidget (Source, dt, idx, n));
308 /* calc minimum size */
309 const uint32_t max_ports = std::max (_ins.n_total (), _out.n_total ());
310 const uint32_t max_pins = std::max ((_sinks * _n_plugins).n_total (), (_sources * _n_plugins).n_total ());
311 uint32_t min_width = std::max (25 * max_ports, (uint32_t)(20 + _pin_box_size) * max_pins);
312 min_width = std::max (min_width, (uint32_t)ceilf (_margin_y * .45 * _n_plugins * 16. / 9.)); // 16 : 9 aspect
313 min_width = std::max ((uint32_t)300, min_width);
315 min_width = 50 + 10 * ceilf (min_width / 10.f);
317 uint32_t min_height = 3.5 * _margin_y + 2 * (_n_sidechains + 1) * _pin_box_size;
318 min_height = std::max ((uint32_t)200, min_height);
319 min_height = 4 * ceilf (min_height / 4.f);
321 if (min_width != _min_width || min_height != _min_height) {
322 _min_width = min_width;
323 _min_height = min_height;
324 darea.queue_resize ();
327 _position_valid = false;
332 PluginPinDialog::refill_sidechain_table ()
334 Table_Helpers::TableList& kids = _sidechain_tbl->children ();
335 for (Table_Helpers::TableList::iterator i = kids.begin (); i != kids.end ();) {
338 _sidechain_tbl->resize (1, 1);
339 if (!_pi->has_sidechain () && _sidechain_selector) {
342 boost::shared_ptr<IO> io = _pi->sidechain_input ();
348 PortSet& p (io->ports ());
349 bool can_remove = p.num_ports () > 1;
350 for (PortSet::iterator i = p.begin (DataType::MIDI); i != p.end (DataType::MIDI); ++i, ++r) {
351 add_port_to_table (*i, r, can_remove);
353 for (PortSet::iterator i = p.begin (DataType::AUDIO); i != p.end (DataType::AUDIO); ++i, ++r) {
354 add_port_to_table (*i, r, can_remove);
356 _sidechain_tbl->show_all ();
360 PluginPinDialog::add_port_to_table (boost::shared_ptr<Port> p, uint32_t r, bool can_remove)
363 std::string tip = p->name ();
364 std::vector<std::string> cns;
365 p->get_connections (cns);
367 // TODO proper labels, see MixerStrip::update_io_button()
368 if (cns.size () == 0) {
370 } else if (cns.size () > 1) {
374 string lpn (PROGRAM_NAME);
375 boost::to_lower (lpn);
376 std::string program_port_prefix = lpn + ":"; // e.g. "ardour:"
380 if (lbl.find ("system:capture_") == 0) {
381 lbl = AudioEngine::instance ()->get_pretty_name_by_name (lbl);
383 lbl = cns[0].substr (15);
385 } else if (lbl.find("system:midi_capture_") == 0) {
386 lbl = AudioEngine::instance ()->get_pretty_name_by_name (lbl);
388 // "system:midi_capture_123" -> "123"
389 lbl = "M " + cns[0].substr (20);
391 } else if (lbl.find (program_port_prefix) == 0) {
392 lbl = lbl.substr (program_port_prefix.size());
395 for (std::vector<std::string>::const_iterator i = cns.begin(); i != cns.end(); ++i) {
399 replace_all (lbl, "_", " ");
401 ArdourButton *pb = manage (new ArdourButton (lbl));
402 pb->set_text_ellipsize (Pango::ELLIPSIZE_MIDDLE);
403 pb->set_layout_ellipsize_width (108 * PANGO_SCALE);
404 ARDOUR_UI_UTILS::set_tooltip (*pb, tip);
405 _sidechain_tbl->attach (*pb, 0, 1, r, r +1 , EXPAND|FILL, SHRINK);
407 pb->signal_button_press_event ().connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::sc_input_press), boost::weak_ptr<Port> (p)), false);
408 pb->signal_button_release_event ().connect (sigc::mem_fun (*this, &PluginPinDialog::sc_input_release), false);
410 pb = manage (new ArdourButton ("-"));
411 _sidechain_tbl->attach (*pb, 1, 2, r, r +1 , FILL, SHRINK);
413 pb->signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::remove_port), boost::weak_ptr<Port> (p)));
415 pb->set_sensitive (false);
420 PluginPinDialog::update_element_pos ()
423 _innerwidth = _width - 2. * _margin_x;
425 const double yc = rint (_height * .5);
426 const double bxh2 = rint (_margin_y * .45); // TODO grow?
427 const double bxw = rint ((_innerwidth * .95) / ((_n_plugins) + .2 * (_n_plugins - 1)));
428 const double bxw2 = rint (bxw * .5);
429 const double y_in = _margin_y;
430 const double y_out = _height - _margin_y;
435 const double dx = _pin_box_size * .5;
438 for (CtrlElemList::iterator i = _elements.begin (); i != _elements.end (); ++i) {
442 i->x = _innerwidth + _margin_x - dx;
443 i->y = y_in + (sc_cnt + .5) * _pin_box_size;
444 i->h = _pin_box_size;
445 i->w = 1.5 * _pin_box_size;
448 uint32_t idx = i->e->id;
449 if (i->e->dt == DataType::AUDIO) { idx += _in.n_midi (); }
450 i->x = rint ((idx + 1) * _width / (1. + _n_inputs)) - 0.5 - dx;
451 i->w = _pin_box_size;
452 i->h = 1.5 * _pin_box_size;
458 uint32_t idx = i->e->id;
459 if (i->e->dt == DataType::AUDIO) { idx += _out.n_midi (); }
460 i->x = rint ((idx + 1) * _width / (1. + _out.n_total ())) - 0.5 - dx;
462 i->w = _pin_box_size;
463 i->h = 1.5 * _pin_box_size;
468 uint32_t idx = i->e->id;
469 if (i->e->dt == DataType::AUDIO) { idx += _sinks.n_midi (); }
470 const double x0 = rint ((i->e->ip + .5) * _innerwidth / (double)(_n_plugins)) - .5 - bxw2;
471 i->x = _margin_x + rint (x0 + (idx + 1) * bxw / (1. + _sinks.n_total ())) - .5 - dx;
472 i->y = yc - bxh2 - dx;
473 i->w = _pin_box_size;
474 i->h = _pin_box_size;
479 uint32_t idx = i->e->id;
480 if (i->e->dt == DataType::AUDIO) { idx += _sources.n_midi (); }
481 const double x0 = rint ((i->e->ip + .5) * _innerwidth / (double)(_n_plugins)) - .5 - bxw2;
482 i->x = _margin_x + rint (x0 + (idx + 1) * bxw / (1. + _sources.n_total ())) - .5 - dx;
483 i->y = yc + bxh2 - dx;
484 i->w = _pin_box_size;
485 i->h = _pin_box_size;
493 PluginPinDialog::set_color (cairo_t* cr, bool midi)
495 // see also gtk2_ardour/processor_box.cc
496 static const uint32_t audio_port_color = 0x4A8A0EFF; // Green
497 static const uint32_t midi_port_color = 0x960909FF; //Red
500 cairo_set_source_rgb (cr,
501 UINT_RGBA_R_FLT (midi_port_color),
502 UINT_RGBA_G_FLT (midi_port_color),
503 UINT_RGBA_B_FLT (midi_port_color));
505 cairo_set_source_rgb (cr,
506 UINT_RGBA_R_FLT (audio_port_color),
507 UINT_RGBA_G_FLT (audio_port_color),
508 UINT_RGBA_B_FLT (audio_port_color));
513 PluginPinDialog::draw_io_pin (cairo_t* cr, const CtrlWidget& w)
517 const double dy = w.h * .5;
518 const double dx = w.w - dy;
519 cairo_move_to (cr, w.x, w.y + dy);
520 cairo_rel_line_to (cr, dy, -dy);
521 cairo_rel_line_to (cr, dx, 0);
522 cairo_rel_line_to (cr, 0, w.h);
523 cairo_rel_line_to (cr, -dx, 0);
525 const double dir = (w.e->ct == Input) ? 1 : -1;
526 const double dx = w.w * .5;
527 const double dy = w.h - dx;
529 cairo_move_to (cr, w.x + dx, w.y + ((w.e->ct == Input) ? w.h : 0));
530 cairo_rel_line_to (cr, -dx, -dx * dir);
531 cairo_rel_line_to (cr, 0., -dy * dir);
532 cairo_rel_line_to (cr, 2. * dx, 0.);
533 cairo_rel_line_to (cr, 0., dy * dir);
535 cairo_close_path (cr);
537 cairo_set_line_width (cr, 1.0);
538 cairo_set_source_rgb (cr, 0, 0, 0);
539 cairo_stroke_preserve (cr);
541 set_color (cr, w.e->dt == DataType::MIDI);
544 assert (w.e->ct == Input);
545 cairo_fill_preserve (cr);
546 cairo_set_source_rgba (cr, 0.0, 0.0, 1.0, 0.4);
549 if (w.e == _selection || w.e == _actor) {
550 cairo_fill_preserve (cr);
551 cairo_set_source_rgba (cr, 0.9, 0.9, 1.0, 0.6);
552 } else if (w.prelight) {
553 cairo_fill_preserve (cr);
554 cairo_set_source_rgba (cr, 0.9, 0.9, 0.9, 0.3);
560 PluginPinDialog::draw_plugin_pin (cairo_t* cr, const CtrlWidget& w)
562 const double dx = w.w * .5;
563 const double dy = w.h * .5;
565 cairo_move_to (cr, w.x + dx, w.y);
566 cairo_rel_line_to (cr, -dx, dy);
567 cairo_rel_line_to (cr, dx, dy);
568 cairo_rel_line_to (cr, dx, -dy);
569 cairo_close_path (cr);
571 cairo_set_line_width (cr, 1.0);
572 cairo_set_source_rgb (cr, 0, 0, 0);
573 cairo_stroke_preserve (cr);
575 set_color (cr, w.e->dt == DataType::MIDI);
578 assert (w.e->ct == Sink);
579 cairo_fill_preserve (cr);
580 cairo_set_source_rgba (cr, 0.0, 0.0, 1.0, 0.4);
583 if (w.e == _selection || w.e == _actor) {
584 cairo_fill_preserve (cr);
585 cairo_set_source_rgba (cr, 0.9, 0.9, 1.0, 0.6);
586 } else if (w.prelight) {
587 cairo_fill_preserve (cr);
588 cairo_set_source_rgba (cr, 0.9, 0.9, 0.9, 0.3);
594 PluginPinDialog::pin_x_pos (uint32_t i, double x0, double width, uint32_t n_total, uint32_t n_midi, bool midi)
596 if (!midi) { i += n_midi; }
597 return rint (x0 + (i + 1) * width / (1. + n_total)) - .5;
600 const PluginPinDialog::CtrlWidget&
601 PluginPinDialog::get_io_ctrl (CtrlType ct, DataType dt, uint32_t id, uint32_t ip) const
603 for (CtrlElemList::const_iterator i = _elements.begin (); i != _elements.end (); ++i) {
604 if (i->e->ct == ct && i->e->dt == dt && i->e->id == id && i->e->ip == ip) {
609 fatal << string_compose (_("programming error: %1"),
610 X_("Invalid Plugin I/O Port."))
612 abort (); /*NOTREACHED*/
613 static CtrlWidget screw_old_compilers (Input, DataType::NIL, 0);
614 return screw_old_compilers;
618 PluginPinDialog::edge_coordinates (const CtrlWidget& w, double &x, double &y)
646 PluginPinDialog::draw_connection (cairo_t* cr, double x0, double x1, double y0, double y1, bool midi, bool horiz, bool dashed)
648 const double bz = 2 * _pin_box_size;
649 const double bc = (dashed && x0 == x1) ? 1.25 * _pin_box_size : 0;
651 cairo_move_to (cr, x0, y0);
653 cairo_curve_to (cr, x0 - bz, y0 + bc, x1 - bc, y1 - bz, x1, y1);
655 cairo_curve_to (cr, x0 - bc, y0 + bz, x1 - bc, y1 - bz, x1, y1);
657 cairo_set_line_width (cr, 3.0);
658 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
659 cairo_set_source_rgb (cr, 1, 0, 0);
661 const double dashes[] = { 5, 7 };
662 cairo_set_dash (cr, dashes, 2, 0);
664 set_color (cr, midi);
667 cairo_set_dash (cr, 0, 0, 0);
672 PluginPinDialog::draw_connection (cairo_t* cr, const CtrlWidget& w0, const CtrlWidget& w1, bool dashed)
674 double x0, x1, y0, y1;
675 edge_coordinates (w0, x0, y0);
676 edge_coordinates (w1, x1, y1);
677 assert (w0.e->dt == w1.e->dt);
678 draw_connection (cr, x0, x1, y0, y1, w0.e->dt == DataType::MIDI, w0.e->sc, dashed);
683 PluginPinDialog::darea_expose_event (GdkEventExpose* ev)
685 Gtk::Allocation a = darea.get_allocation ();
686 double const width = a.get_width ();
687 double const height = a.get_height ();
689 if (!_position_valid) {
692 update_element_pos ();
693 _position_valid = true;
696 cairo_t* cr = gdk_cairo_create (darea.get_window ()->gobj ());
697 cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
700 Gdk::Color const bg = get_style ()->get_bg (STATE_NORMAL);
701 cairo_set_source_rgb (cr, bg.get_red_p (), bg.get_green_p (), bg.get_blue_p ());
702 cairo_rectangle (cr, 0, 0, width, height);
705 const double yc = rint (_height * .5);
708 rounded_rectangle (cr, _margin_x, _margin_y - _pin_box_size * .5, _innerwidth, _height - 2 * _margin_y + _pin_box_size, 7);
709 cairo_set_line_width (cr, 1.0);
710 cairo_set_source_rgb (cr, .1, .1, .3);
711 cairo_stroke_preserve (cr);
712 cairo_set_source_rgb (cr, .3, .3, .3);
715 /* draw midi-bypass (behind) */
716 if (_pi->has_midi_bypass ()) {
717 const CtrlWidget& cw0 = get_io_ctrl (Input, DataType::MIDI, 0);
718 const CtrlWidget& cw1 = get_io_ctrl (Output, DataType::MIDI, 0);
719 draw_connection (cr, cw0, cw1, true);
722 /* thru connections */
723 const ChanMapping::Mappings thru_map = _pi->thru_map ().mappings ();
724 for (ChanMapping::Mappings::const_iterator t = thru_map.begin (); t != thru_map.end (); ++t) {
725 for (ChanMapping::TypeMapping::const_iterator c = (*t).second.begin (); c != (*t).second.end () ; ++c) {
726 const CtrlWidget& cw0 = get_io_ctrl (Output, t->first, c->first);
727 const CtrlWidget& cw1 = get_io_ctrl (Input, t->first, c->second);
728 draw_connection (cr, cw1, cw0, true);
733 Glib::RefPtr<Pango::Layout> layout;
734 layout = Pango::Layout::create (get_pango_context ());
736 layout->set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
737 layout->set_width (_height * PANGO_SCALE);
742 layout->set_text (_route ()->name ());
743 layout->get_pixel_size (text_width, text_height);
745 cairo_move_to (cr, .5 * (_margin_x - text_height), .5 * (_height + text_width));
746 cairo_rotate (cr, M_PI * -.5);
747 cairo_set_source_rgba (cr, 1., 1., 1., 1.);
748 pango_cairo_show_layout (cr, layout->gobj ());
752 layout->set_width ((_innerwidth - 2 * _pin_box_size) * PANGO_SCALE);
753 layout->set_text (_pi->name ());
754 layout->get_pixel_size (text_width, text_height);
755 cairo_move_to (cr, _margin_x + _innerwidth - text_width - _pin_box_size * .5, _height - _margin_y - text_height);
756 cairo_set_source_rgba (cr, 1., 1., 1., 1.);
757 pango_cairo_show_layout (cr, layout->gobj ());
759 if (_pi->strict_io ()) {
760 layout->set_text (_("Strict I/O"));
761 layout->get_pixel_size (text_width, text_height);
762 const double sx0 = _margin_x + .5 * (_innerwidth - text_width);
763 const double sy0 = _height - 3 - text_height;
765 rounded_rectangle (cr, sx0 - 2, sy0 - 1, text_width + 4, text_height + 2, 7);
766 cairo_set_source_rgba (cr, .4, .3, .1, 1.);
769 cairo_set_source_rgba (cr, 1., 1., 1., 1.);
770 cairo_move_to (cr, sx0, sy0);
771 cairo_set_source_rgba (cr, 1., 1., 1., 1.);
772 pango_cairo_show_layout (cr, layout->gobj ());
776 /* plugins & connection wires */
777 for (uint32_t i = 0; i < _n_plugins; ++i) {
778 double x0 = _margin_x + rint ((i + .5) * _innerwidth / (double)(_n_plugins)) - .5;
781 cairo_set_source_rgb (cr, .5, .5, .5);
782 rounded_rectangle (cr, x0 - _bxw2, yc - _bxh2, 2 * _bxw2, 2 * _bxh2, 7);
785 const ChanMapping::Mappings in_map = _pi->input_map (i).mappings ();
786 const ChanMapping::Mappings out_map = _pi->output_map (i).mappings ();
788 for (ChanMapping::Mappings::const_iterator t = in_map.begin (); t != in_map.end (); ++t) {
789 for (ChanMapping::TypeMapping::const_iterator c = (*t).second.begin (); c != (*t).second.end () ; ++c) {
790 const CtrlWidget& cw0 = get_io_ctrl (Input, t->first, c->second);
791 const CtrlWidget& cw1 = get_io_ctrl (Sink, t->first, c->first, i);
792 draw_connection (cr, cw0, cw1);
796 for (ChanMapping::Mappings::const_iterator t = out_map.begin (); t != out_map.end (); ++t) {
797 for (ChanMapping::TypeMapping::const_iterator c = (*t).second.begin (); c != (*t).second.end () ; ++c) {
798 const CtrlWidget& cw0 = get_io_ctrl (Source, t->first, c->first, i);
799 const CtrlWidget& cw1 = get_io_ctrl (Output, t->first, c->second);
800 draw_connection (cr, cw0, cw1);
806 for (CtrlElemList::const_iterator i = _elements.begin (); i != _elements.end (); ++i) {
810 draw_io_pin (cr, *i);
814 draw_plugin_pin (cr, *i);
819 CtrlWidget *drag_src = NULL;
821 for (CtrlElemList::iterator i = _elements.begin (); i != _elements.end (); ++i) {
822 if (i->e == _selection ) {
829 if (_selection->ct == Input || _selection->ct == Source) {
830 edge_coordinates (*drag_src, x0, y0);
831 draw_connection (cr, x0, _drag_x, y0, _drag_y,
832 _selection->dt == DataType::MIDI, _selection->sc);
834 edge_coordinates (*drag_src, x0, y0);
835 draw_connection (cr, _drag_x, x0, _drag_y, y0,
836 _selection->dt == DataType::MIDI, _selection->sc);
845 PluginPinDialog::darea_size_request (Gtk::Requisition* req)
847 req->width = _min_width;
848 req->height = _min_height;
852 PluginPinDialog::darea_size_allocate (Gtk::Allocation&)
854 _position_valid = false;
858 PluginPinDialog::drag_type_matches (const CtrlElem& e) {
859 if (!_dragging || !_selection) {
862 if (_selection->dt != e->dt) {
865 if (_selection->ct == Input && e->ct == Sink) { return true; }
866 if (_selection->ct == Sink && e->ct == Input) { return true; }
867 if (_selection->ct == Output && e->ct == Source) { return true; }
868 if (_selection->ct == Source && e->ct == Output) { return true; }
869 if (_selection->ct == Input && e->ct == Output) { return true; }
870 if (_selection->ct == Output && e->ct == Input) { return true; }
875 PluginPinDialog::darea_motion_notify_event (GdkEventMotion* ev)
877 bool changed = false;
879 for (CtrlElemList::iterator i = _elements.begin (); i != _elements.end (); ++i) {
880 if (ev->x >= i->x && ev->x <= i->x + i->w
881 && ev->y >= i->y && ev->y <= i->y + i->h
882 && drag_type_matches (i->e))
884 if (!i->prelight) changed = true;
888 if (i->prelight) changed = true;
896 if (changed || _dragging) {
903 PluginPinDialog::darea_button_press_event (GdkEventButton* ev)
905 if (ev->type != GDK_BUTTON_PRESS) {
909 switch (ev->button) {
911 if (!_selection || (_selection && !_hover)) {
920 } else if (_selection && _hover && _selection != _hover) {
921 if (_selection->dt != _hover->dt) { _actor.reset (); }
922 else if (_selection->ct == Input && _hover->ct == Sink) { _actor = _hover; }
923 else if (_selection->ct == Sink && _hover->ct == Input) { _actor = _hover; }
924 else if (_selection->ct == Output && _hover->ct == Source) { _actor = _hover; }
925 else if (_selection->ct == Source && _hover->ct == Output) { _actor = _hover; }
926 else if (_selection->ct == Input && _hover->ct == Output) { _actor = _hover; }
927 else if (_selection->ct == Output && _hover->ct == Input) { _actor = _hover; }
944 if (_selection != _hover) {
958 PluginPinDialog::darea_button_release_event (GdkEventButton* ev)
960 if (_dragging && _hover && _hover != _selection) {
963 if (_hover == _actor && _actor && ev->button == 1) {
965 assert (_selection->dt == _actor->dt);
966 if (_selection->ct == Input && _actor->ct == Sink) {
967 handle_input_action (_actor, _selection);
969 else if (_selection->ct == Sink && _actor->ct == Input) {
970 handle_input_action (_selection, _actor);
972 else if (_selection->ct == Output && _actor->ct == Source) {
973 handle_output_action (_actor, _selection);
975 else if (_selection->ct == Source && _actor->ct == Output) {
976 handle_output_action (_selection, _actor);
978 else if (_selection->ct == Input && _actor->ct == Output) {
979 handle_thru_action (_actor, _selection);
981 else if (_selection->ct == Output && _actor->ct == Input) {
982 handle_thru_action (_selection, _actor);
985 } else if (_hover == _selection && _selection && ev->button == 3) {
986 handle_disconnect (_selection);
987 } else if (!_hover && ev->button == 3) {
988 reset_menu.popup (1, ev->time);
997 PluginPinDialog::handle_input_action (const CtrlElem &s, const CtrlElem &i)
999 const int pc = s->ip;
1001 ChanMapping in_map (_pi->input_map (pc));
1002 uint32_t idx = in_map.get (s->dt, s->id, &valid);
1004 if (valid && idx == i->id) {
1006 in_map.unset (s->dt, s->id);
1007 _pi->set_input_map (pc, in_map);
1011 in_map.set (s->dt, s->id, i->id);
1012 _pi->set_input_map (pc, in_map);
1016 in_map.unset (s->dt, s->id);
1017 in_map.set (s->dt, s->id, i->id);
1018 _pi->set_input_map (pc, in_map);
1023 PluginPinDialog::disconnect_other_outputs (uint32_t skip_pc, DataType dt, uint32_t id)
1025 _ignore_updates = true;
1026 for (uint32_t n = 0; n < _n_plugins; ++n) {
1031 ChanMapping n_out_map (_pi->output_map (n));
1032 uint32_t idx = n_out_map.get_src (dt, id, &valid);
1034 n_out_map.unset (dt, idx);
1035 _pi->set_output_map (n, n_out_map);
1038 _ignore_updates = false;
1042 PluginPinDialog::disconnect_other_thru (DataType dt, uint32_t id)
1044 _ignore_updates = true;
1046 ChanMapping n_thru_map (_pi->thru_map ());
1047 n_thru_map.get (dt, id, &valid);
1049 n_thru_map.unset (dt, id);
1050 _pi->set_thru_map (n_thru_map);
1052 _ignore_updates = false;
1056 PluginPinDialog::handle_output_action (const CtrlElem &s, const CtrlElem &o)
1058 const uint32_t pc = s->ip;
1060 ChanMapping out_map (_pi->output_map (pc));
1061 uint32_t idx = out_map.get (s->dt, s->id, &valid);
1063 if (valid && idx == o->id) {
1065 out_map.unset (s->dt, s->id);
1066 _pi->set_output_map (pc, out_map);
1069 // disconnect source
1071 out_map.unset (s->dt, s->id);
1073 disconnect_other_outputs (pc, s->dt, o->id);
1074 disconnect_other_thru (s->dt, o->id);
1076 idx = out_map.get_src (s->dt, o->id, &valid);
1078 out_map.unset (s->dt, idx);
1081 out_map.set (s->dt, s->id, o->id);
1082 _pi->set_output_map (pc, out_map);
1087 PluginPinDialog::handle_thru_action (const CtrlElem &o, const CtrlElem &i)
1090 ChanMapping thru_map (_pi->thru_map ());
1091 uint32_t idx = thru_map.get (o->dt, o->id, &valid);
1093 if (valid && idx == i->id) {
1095 thru_map.unset (o->dt, o->id);
1097 // disconnect other outputs first
1098 disconnect_other_outputs (UINT32_MAX, o->dt, o->id);
1099 disconnect_other_thru (o->dt, o->id);
1101 thru_map.set (o->dt, o->id, i->id);
1103 _pi->set_thru_map (thru_map);
1107 PluginPinDialog::handle_disconnect (const CtrlElem &e)
1109 _ignore_updates = true;
1110 bool changed = false;
1116 ChanMapping n_thru_map (_pi->thru_map ());
1117 for (uint32_t i = 0; i < _sources.n_total (); ++i) {
1118 uint32_t idx = n_thru_map.get (e->dt, i, &valid);
1119 if (valid && idx == e->id) {
1120 n_thru_map.unset (e->dt, i);
1125 _pi->set_thru_map (n_thru_map);
1128 for (uint32_t n = 0; n < _n_plugins; ++n) {
1129 ChanMapping map (_pi->input_map (n));
1130 for (uint32_t i = 0; i < _sinks.n_total (); ++i) {
1131 uint32_t idx = map.get (e->dt, i, &valid);
1132 if (valid && idx == e->id) {
1133 map.unset (e->dt, i);
1137 _pi->set_input_map (n, map);
1142 ChanMapping map (_pi->input_map (e->ip));
1143 map.get (e->dt, e->id, &valid);
1145 map.unset (e->dt, e->id);
1146 _pi->set_input_map (e->ip, map);
1153 ChanMapping map (_pi->output_map (e->ip));
1154 map.get (e->dt, e->id, &valid);
1156 map.unset (e->dt, e->id);
1157 _pi->set_output_map (e->ip, map);
1163 for (uint32_t n = 0; n < _n_plugins; ++n) {
1164 ChanMapping map (_pi->output_map (n));
1165 for (uint32_t i = 0; i < _sources.n_total (); ++i) {
1166 uint32_t idx = map.get (e->dt, i, &valid);
1167 if (valid && idx == e->id) {
1168 map.unset (e->dt, i);
1173 _pi->set_output_map (n, map);
1177 ChanMapping n_thru_map (_pi->thru_map ());
1178 n_thru_map.get (e->dt, e->id, &valid);
1180 n_thru_map.unset (e->dt, e->id);
1182 _pi->set_thru_map (n_thru_map);
1187 _ignore_updates = false;
1189 plugin_reconfigured ();
1194 PluginPinDialog::toggle_sidechain ()
1196 if (_session && _session->actively_recording()) { return; }
1197 _route ()->add_remove_sidechain (_pi, !_pi->has_sidechain ());
1201 PluginPinDialog::connect_sidechain ()
1203 if (!_session) { return; }
1205 if (_sidechain_selector == 0) {
1206 _sidechain_selector = new IOSelectorWindow (_session, _pi->sidechain_input ());
1209 if (_sidechain_selector->is_visible ()) {
1210 _sidechain_selector->get_toplevel ()->get_window ()->raise ();
1212 _sidechain_selector->present ();
1217 PluginPinDialog::reset_configuration ()
1219 if (_set_config.get_active ()) {
1220 _route ()->reset_plugin_insert (_pi);
1222 _route ()->customize_plugin_insert (_pi, _n_plugins, _out);
1227 PluginPinDialog::reset_mapping ()
1233 PluginPinDialog::add_remove_plugin_clicked (bool add)
1235 if (_session && _session->actively_recording()) { return; }
1236 ChanCount out = _out;
1237 assert (add || _n_plugins > 0);
1238 _route ()->customize_plugin_insert (_pi, _n_plugins + (add ? 1 : -1), out);
1242 PluginPinDialog::add_remove_port_clicked (bool add, ARDOUR::DataType dt)
1244 if (_session && _session->actively_recording()) { return; }
1245 ChanCount out = _out;
1246 assert (add || out.get (dt) > 0);
1247 out.set (dt, out.get (dt) + (add ? 1 : -1));
1248 _route ()->customize_plugin_insert (_pi, _n_plugins, out);
1252 PluginPinDialog::add_sidechain_port (DataType dt)
1254 if (_session && _session->actively_recording()) { return; }
1255 boost::shared_ptr<IO> io = _pi->sidechain_input ();
1259 io->add_port ("", this, dt);
1263 PluginPinDialog::remove_port (boost::weak_ptr<ARDOUR::Port> wp)
1265 if (_session && _session->actively_recording()) { return; }
1266 boost::shared_ptr<ARDOUR::Port> p = wp.lock ();
1267 boost::shared_ptr<IO> io = _pi->sidechain_input ();
1271 io->remove_port (p, this);
1275 PluginPinDialog::disconnect_port (boost::weak_ptr<ARDOUR::Port> wp)
1277 if (_session && _session->actively_recording()) { return; }
1278 boost::shared_ptr<ARDOUR::Port> p = wp.lock ();
1279 boost::shared_ptr<IO> io = _pi->sidechain_input ();
1283 p->disconnect_all ();
1287 PluginPinDialog::connect_port (boost::weak_ptr<ARDOUR::Port> wp0, boost::weak_ptr<ARDOUR::Port> wp1)
1289 if (_session && _session->actively_recording()) { return; }
1290 boost::shared_ptr<ARDOUR::Port> p0 = wp0.lock ();
1291 boost::shared_ptr<ARDOUR::Port> p1 = wp1.lock ();
1292 boost::shared_ptr<IO> io = _pi->sidechain_input ();
1293 if (!io || !p0 || !p1) {
1296 _ignore_updates = true;
1297 p0->disconnect_all ();
1298 _ignore_updates = false;
1299 p0->connect (p1->name ());
1303 PluginPinDialog::sc_input_release (GdkEventButton *ev)
1305 if (_session && _session->actively_recording()) { return false; }
1306 if (ev->button == 3) {
1307 connect_sidechain ();
1312 struct RouteCompareByName {
1313 bool operator() (boost::shared_ptr<Route> a, boost::shared_ptr<Route> b) {
1314 return a->name().compare (b->name()) < 0;
1319 PluginPinDialog::sc_input_press (GdkEventButton *ev, boost::weak_ptr<ARDOUR::Port> wp)
1321 using namespace Menu_Helpers;
1322 if (!_session || _session->actively_recording()) { return false; }
1323 if (!_session->engine().connected()) { return false; }
1325 if (ev->button == 1) {
1326 MenuList& citems = input_menu.items();
1327 input_menu.set_name ("ArdourContextMenu");
1330 boost::shared_ptr<Port> p = wp.lock ();
1331 if (p && p->connected ()) {
1332 citems.push_back (MenuElem (_("Disconnect"), sigc::bind (sigc::mem_fun (*this, &PluginPinDialog::disconnect_port), wp)));
1333 citems.push_back (SeparatorElem());
1337 // TODO add system inputs, too ?!
1338 boost::shared_ptr<ARDOUR::BundleList> b = _session->bundles ();
1339 for (ARDOUR::BundleList::iterator i = b->begin(); i != b->end(); ++i) {
1340 for (uint32_t j = 0; j < i->nchannels ().n_total (); ++j) {
1342 //maybe_add_bundle_to_input_menu (*i, current);
1346 boost::shared_ptr<ARDOUR::RouteList> routes = _session->get_routes ();
1347 RouteList copy = *routes;
1348 copy.sort (RouteCompareByName ());
1350 for (ARDOUR::RouteList::const_iterator i = copy.begin(); i != copy.end(); ++i) {
1351 added += maybe_add_route_to_input_menu (*i, p->type (), wp);
1355 citems.push_back (SeparatorElem());
1357 citems.push_back (MenuElem (_("Routing Grid"), sigc::mem_fun (*this, &PluginPinDialog::connect_sidechain)));
1358 input_menu.popup (1, ev->time);
1364 PluginPinDialog::maybe_add_route_to_input_menu (boost::shared_ptr<Route> r, DataType dt, boost::weak_ptr<Port> wp)
1367 using namespace Menu_Helpers;
1368 if (r->output () == _route()->output()) {
1372 if (_route ()->feeds_according_to_graph (r)) {
1376 MenuList& citems = input_menu.items();
1377 const IOVector& iov (r->all_outputs());
1379 for (IOVector::const_iterator o = iov.begin(); o != iov.end(); ++o) {
1380 boost::shared_ptr<IO> op = o->lock();
1384 PortSet& p (op->ports ());
1385 for (PortSet::iterator i = p.begin (dt); i != p.end (dt); ++i) {
1386 std::string n = i->name ();
1387 replace_all (n, "_", " ");
1388 citems.push_back (MenuElem (n, sigc::bind (sigc::mem_fun(*this, &PluginPinDialog::connect_port), wp, boost::weak_ptr<Port> (*i))));
1396 PluginPinDialog::port_connected_or_disconnected (boost::weak_ptr<ARDOUR::Port> w0, boost::weak_ptr<ARDOUR::Port> w1)
1398 boost::shared_ptr<Port> p0 = w0.lock ();
1399 boost::shared_ptr<Port> p1 = w1.lock ();
1401 boost::shared_ptr<IO> io = _pi->sidechain_input ();
1402 if (!io) { return; }
1404 if (p0 && io->has_port (p0)) {
1405 plugin_reconfigured ();
1407 else if (p1 && io->has_port (p1)) {
1408 plugin_reconfigured ();