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 <gtkmm/table.h>
21 #include <gtkmm/box.h>
22 #include <gtkmm/label.h>
24 #include "gtkmm2ext/utils.h"
25 #include "gtkmm2ext/rgb_macros.h"
27 #include "plugin_pin_dialog.h"
28 #include "gui_thread.h"
29 #include "ui_config.h"
33 using namespace ARDOUR;
37 using namespace Gtkmm2ext;
39 PluginPinDialog::PluginPinDialog (boost::shared_ptr<ARDOUR::PluginInsert> pi)
40 : ArdourWindow (string_compose (_("Pin Configuration: %1"), pi->name ()))
41 , _ind_strict_io (_("Strict I/O"))
42 , _ind_customized (_("Customized"))
43 , _rst_config (_("Configuration"))
44 , _rst_mapping (_("Connections"))
45 , _add_plugin (_("+"))
46 , _del_plugin (_("-"))
47 , _add_output_audio (_("+"))
48 , _del_output_audio (_("-"))
49 , _add_output_midi (_("+"))
50 , _del_output_midi (_("-"))
54 , _position_valid (false)
55 , _ignore_updates (false)
57 assert (pi->owner ()); // Route
59 _pi->PluginIoReConfigure.connect (
60 _plugin_connections, invalidator (*this), boost::bind (&PluginPinDialog::plugin_reconfigured, this), gui_context()
63 _pi->PluginMapChanged.connect (
64 _plugin_connections, invalidator (*this), boost::bind (&PluginPinDialog::plugin_reconfigured, this), gui_context()
67 _pi->PluginConfigChanged.connect (
68 _plugin_connections, invalidator (*this), boost::bind (&PluginPinDialog::plugin_reconfigured, this), gui_context()
71 // needs a better way: insensitive indicators
72 _ind_strict_io.set_sensitive (false); // track status
73 _ind_strict_io.set_name ("rude solo");
74 _ind_customized.set_sensitive (false); // plugin status
75 _ind_customized.set_name ("rude solo");
79 Table* t = manage (new Table (4, 3));
80 t->set_border_width (0);
83 l = manage (new Label (_("Track/Bus:"), ALIGN_END));
84 t->attach (*l, 0, 1, r, r + 1);
85 l = manage (new Label ());
86 l->set_alignment (ALIGN_START);
87 l->set_padding (2, 1);
88 l->set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
89 l->set_width_chars (24);
90 l->set_max_width_chars (24);
91 l->set_text (_route()->name ());
92 t->attach (*l, 1, 3, r, r + 1);
95 l = manage (new Label (_("Plugin:"), ALIGN_END));
96 t->attach (*l, 0, 1, r, r + 1);
97 l = manage (new Label ());
98 l->set_alignment(ALIGN_START);
99 l->set_padding (2, 1);
100 l->set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
101 l->set_width_chars (24);
102 l->set_max_width_chars (24);
103 l->set_text (pi->name ());
104 t->attach (*l, 1, 3, r, r + 1);
107 l = manage (new Label (_("Status:"), ALIGN_END));
108 t->attach (*l, 0, 1, r, r + 1);
109 t->attach (_ind_strict_io, 1, 2, r, r + 1, FILL, SHRINK);
110 t->attach (_ind_customized, 2, 3, r, r + 1, FILL, SHRINK);
113 l = manage (new Label (_("Reset:"), ALIGN_END));
114 t->attach (*l, 0, 1, r, r + 1);
115 t->attach (_rst_mapping, 1, 2, r, r + 1, FILL, SHRINK);
116 t->attach (_rst_config, 2, 3, r, r + 1, FILL, SHRINK);
119 l = manage (new Label (_("Instances:"), ALIGN_END));
120 t->attach (*l, 0, 1, r, r + 1);
121 t->attach (_add_plugin, 1, 2, r, r + 1, SHRINK, SHRINK);
122 t->attach (_del_plugin, 2, 3, r, r + 1, SHRINK, SHRINK);
125 l = manage (new Label (_("Audio Out:"), ALIGN_END));
126 t->attach (*l, 0, 1, r, r + 1);
127 t->attach (_add_output_audio, 1, 2, r, r + 1, SHRINK, SHRINK);
128 t->attach (_del_output_audio, 2, 3, r, r + 1, SHRINK, SHRINK);
131 l = manage (new Label (_("Midi Out:"), ALIGN_END));
132 t->attach (*l, 0, 1, r, r + 1);
133 t->attach (_add_output_midi, 1, 2, r, r + 1, SHRINK, SHRINK);
134 t->attach (_del_output_midi, 2, 3, r, r + 1, SHRINK, SHRINK);
137 HBox* hbox = manage (new HBox);
138 hbox->pack_start (darea, true, true);
139 hbox->pack_start (*t, false, true);
141 VBox* vbox = manage (new VBox);
142 vbox->pack_start (*hbox, true, true);
146 plugin_reconfigured ();
148 darea.add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::POINTER_MOTION_MASK);
149 darea.signal_size_request().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_size_request));
150 darea.signal_size_allocate().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_size_allocate));
151 darea.signal_expose_event().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_expose_event));
152 darea.signal_button_press_event().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_button_press_event));
153 darea.signal_button_release_event().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_button_release_event));
154 darea.signal_motion_notify_event().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_motion_notify_event));
156 _rst_mapping.signal_clicked.connect (sigc::mem_fun(*this, &PluginPinDialog::reset_mapping));
157 _rst_config.signal_clicked.connect (sigc::mem_fun(*this, &PluginPinDialog::reset_configuration));
158 _add_plugin.signal_clicked.connect (sigc::bind (sigc::mem_fun(*this, &PluginPinDialog::add_remove_plugin_clicked), true));
159 _del_plugin.signal_clicked.connect (sigc::bind (sigc::mem_fun(*this, &PluginPinDialog::add_remove_plugin_clicked), false));
161 _add_output_audio.signal_clicked.connect (sigc::bind (sigc::mem_fun(*this, &PluginPinDialog::add_remove_port_clicked), true, DataType::AUDIO));
162 _del_output_audio.signal_clicked.connect (sigc::bind (sigc::mem_fun(*this, &PluginPinDialog::add_remove_port_clicked), false, DataType::AUDIO));
163 _add_output_midi.signal_clicked.connect (sigc::bind (sigc::mem_fun(*this, &PluginPinDialog::add_remove_port_clicked), true, DataType::MIDI));
164 _del_output_midi.signal_clicked.connect (sigc::bind (sigc::mem_fun(*this, &PluginPinDialog::add_remove_port_clicked), false, DataType::MIDI));
167 PluginPinDialog::~PluginPinDialog()
172 PluginPinDialog::plugin_reconfigured ()
174 if (_ignore_updates) {
177 _n_plugins = _pi->get_count ();
178 _pi->configured_io (_in, _out);
179 _sinks = _pi->natural_input_streams ();
180 _sources = _pi->natural_output_streams ();
182 _del_plugin.set_sensitive (_n_plugins > 1);
183 _del_output_audio.set_sensitive (_out.n_audio () > 0 && _out.n_total () > 1);
184 _del_output_midi.set_sensitive (_out.n_midi () > 0 && _out.n_total () > 1);
185 _ind_strict_io.set_active (_pi->strict_io());
186 _ind_customized.set_active (_pi->custom_cfg());
188 // calc minimum width
189 const uint32_t max_ports = std::max (_in.n_total (), _out.n_total ());
190 const uint32_t max_pins = std::max ((_sinks * _n_plugins).n_total (), (_sources * _n_plugins).n_total ());
191 uint32_t min_width = std::max (25 * max_ports, (uint32_t)(20 + _pin_box_size) * max_pins);
192 min_width = std::max (min_width, 40 * _n_plugins);
193 min_width = std::max ((uint32_t)300, min_width);
194 min_width = 10 * ceilf (min_width / 10.f);
195 if (min_width != _min_width) {
196 _min_width = min_width;
197 darea.queue_resize ();
204 PluginPinDialog::update_elements ()
211 for (uint32_t i = 0; i < _in.n_total (); ++i) {
212 int id = (i < _in.n_midi ()) ? i : i - _in.n_midi ();
213 _elements.push_back (CtrlWidget (Input, (i < _in.n_midi () ? DataType::MIDI : DataType::AUDIO), id));
216 for (uint32_t i = 0; i < _out.n_total (); ++i) {
217 int id = (i < _out.n_midi ()) ? i : i - _out.n_midi ();
218 _elements.push_back (CtrlWidget (Output, (i < _out.n_midi () ? DataType::MIDI : DataType::AUDIO), id));
221 for (uint32_t n = 0; n < _n_plugins; ++n) {
222 for (uint32_t i = 0; i < _sinks.n_total(); ++i) {
223 _elements.push_back (CtrlWidget (Sink, (i < _sinks.n_midi () ? DataType::MIDI : DataType::AUDIO), i, n));
225 for (uint32_t i = 0; i < _sources.n_total(); ++i) {
226 _elements.push_back (CtrlWidget (Source, (i < _sources.n_midi () ? DataType::MIDI : DataType::AUDIO), i, n));
229 _position_valid = false;
234 PluginPinDialog::update_element_pos ()
237 const double yc = rint (_height * .5);
238 const double bxh2 = 18;
239 const double bxw = rint ((_width * .9) / ((_n_plugins) + .2 * (_n_plugins - 1)));
240 const double bxw2 = rint (bxw * .5);
241 const double y_in = 40;
242 const double y_out = _height - 40;
244 _pin_box_size = rint (max (6., 8. * UIConfiguration::instance().get_ui_scale()));
246 for (CtrlElemList::iterator i = _elements.begin(); i != _elements.end(); ++i) {
250 uint32_t idx = i->e->id;
251 if (i->e->dt == DataType::AUDIO) { idx += _in.n_midi (); }
252 i->x = rint ((idx + 1) * _width / (1. + _in.n_total ())) - 5.5;
260 uint32_t idx = i->e->id;
261 if (i->e->dt == DataType::AUDIO) { idx += _out.n_midi (); }
262 i->x = rint ((idx + 1) * _width / (1. + _out.n_total ())) - 5.5;
270 const double x0 = rint ((i->e->ip + .5) * _width / (double)(_n_plugins)) - .5 - bxw2;
271 i->x = rint (x0 + (i->e->id + 1) * bxw / (1. + _sinks.n_total ())) - .5 - _pin_box_size * .5;
272 i->y = yc - bxh2 - _pin_box_size;
273 i->w = _pin_box_size + 1;
274 i->h = _pin_box_size;
279 const double x0 = rint ((i->e->ip + .5) * _width / (double)(_n_plugins)) - .5 - bxw2;
280 i->x = rint (x0 + (i->e->id + 1) * bxw / (1. + _sources.n_total ())) - .5 - _pin_box_size * .5;
282 i->w = _pin_box_size + 1;
283 i->h = _pin_box_size;
292 PluginPinDialog::set_color (cairo_t* cr, bool midi)
294 // see also gtk2_ardour/processor_box.cc
295 static const uint32_t audio_port_color = 0x4A8A0EFF; // Green
296 static const uint32_t midi_port_color = 0x960909FF; //Red
299 cairo_set_source_rgb (cr,
300 UINT_RGBA_R_FLT(midi_port_color),
301 UINT_RGBA_G_FLT(midi_port_color),
302 UINT_RGBA_B_FLT(midi_port_color));
304 cairo_set_source_rgb (cr,
305 UINT_RGBA_R_FLT(audio_port_color),
306 UINT_RGBA_G_FLT(audio_port_color),
307 UINT_RGBA_B_FLT(audio_port_color));
312 PluginPinDialog::draw_io_pin (cairo_t* cr, const CtrlWidget& w)
314 const double dir = (w.e->ct == Input) ? 1 : -1;
316 cairo_move_to (cr, w.x + 5.0, w.y + ((w.e->ct == Input) ? 25 : 0));
317 cairo_rel_line_to (cr, -5., -5. * dir);
318 cairo_rel_line_to (cr, 0., -25. * dir);
319 cairo_rel_line_to (cr, 10., 0.);
320 cairo_rel_line_to (cr, 0., 25. * dir);
321 cairo_close_path (cr);
323 cairo_set_line_width (cr, 1.0);
324 cairo_set_source_rgb (cr, 0, 0, 0);
325 cairo_stroke_preserve (cr);
327 set_color (cr, w.e->dt == DataType::MIDI);
328 if (w.e == _selection || w.e == _actor) {
329 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0);
330 } else if (w.prelight) {
331 cairo_fill_preserve (cr);
332 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.5);
338 PluginPinDialog::draw_plugin_pin (cairo_t* cr, const CtrlWidget& w)
340 cairo_rectangle (cr, w.x, w.y, w.w, w.h);
341 set_color (cr, w.e->dt == DataType::MIDI);
342 if (w.e == _selection || w.e == _actor) {
343 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0);
344 } else if (w.prelight) {
345 cairo_fill_preserve (cr);
346 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.5);
352 PluginPinDialog::pin_x_pos (uint32_t i, double x0, double width, uint32_t n_total, uint32_t n_midi, bool midi)
354 if (!midi) { i += n_midi; }
355 return rint (x0 + (i + 1) * width / (1. + n_total)) - .5;
359 PluginPinDialog::draw_connection (cairo_t* cr, double x0, double x1, double y0, double y1, bool midi, bool dashed)
361 const double bz = 2 * _pin_box_size;
362 const double bc = dashed ? 1.25 * _pin_box_size : 0;
364 cairo_move_to (cr, x0, y0);
365 cairo_curve_to (cr, x0 - bc, y0 + bz, x1 - bc, y1 - bz, x1, y1);
366 cairo_set_line_width (cr, 3.0);
367 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
368 cairo_set_source_rgb (cr, 1, 0, 0);
370 const double dashes[] = { 5, 7 };
371 cairo_set_dash (cr, dashes, 2, 0);
373 set_color (cr, midi);
376 cairo_set_dash (cr, 0, 0, 0);
381 PluginPinDialog::darea_expose_event (GdkEventExpose* ev)
383 Gtk::Allocation a = darea.get_allocation();
384 double const width = a.get_width();
385 double const height = a.get_height();
387 if (!_position_valid) {
390 update_element_pos ();
391 _position_valid = true;
394 cairo_t* cr = gdk_cairo_create (darea.get_window()->gobj());
395 cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
398 Gdk::Color const bg = get_style()->get_bg (STATE_NORMAL);
399 cairo_set_source_rgb (cr, bg.get_red_p (), bg.get_green_p (), bg.get_blue_p ());
400 cairo_rectangle (cr, 0, 0, width, height);
403 /* layout sizes -- TODO consolidate w/ update_element_pos() */
405 const double y_in = 40;
406 const double y_out = height - 40;
409 const double yc = rint (height * .5);
410 const double bxh2 = 18;
411 const double bxw = rint ((width * .9) / ((_n_plugins) + .2 * (_n_plugins - 1)));
412 const double bxw2 = rint (bxw * .5);
414 const uint32_t pc_in = _in.n_total();
415 const uint32_t pc_in_midi = _in.n_midi();
416 const uint32_t pc_out = _out.n_total();
417 const uint32_t pc_out_midi = _out.n_midi();
419 /* draw midi-bypass (behind) */
420 if (_pi->has_midi_bypass ()) {
421 double x0 = rint (width / (1. + pc_in)) - .5;
422 double x1 = rint (width / (1. + pc_out)) - .5;
423 draw_connection (cr, x0, x1, y_in, y_out, true, true);
426 /* plugins & connection wires */
427 for (uint32_t i = 0; i < _n_plugins; ++i) {
428 double x0 = rint ((i + .5) * width / (double)(_n_plugins)) - .5;
431 cairo_set_source_rgb (cr, .3, .3, .3);
432 rounded_rectangle (cr, x0 - bxw2, yc - bxh2, bxw, 2 * bxh2, 7);
435 const ChanMapping::Mappings in_map = _pi->input_map (i).mappings();
436 const ChanMapping::Mappings out_map = _pi->output_map (i).mappings();
438 for (ChanMapping::Mappings::const_iterator t = in_map.begin (); t != in_map.end (); ++t) {
439 bool is_midi = t->first == DataType::MIDI;
440 for (ChanMapping::TypeMapping::const_iterator c = (*t).second.begin (); c != (*t).second.end () ; ++c) {
441 uint32_t pn = (*c).first; // pin
442 uint32_t pb = (*c).second;
443 double c_x0 = pin_x_pos (pb, 0, width, pc_in, pc_in_midi, is_midi);
444 double c_x1 = pin_x_pos (pn, x0 - bxw2, bxw, _sinks.n_total (), _sinks.n_midi (), is_midi);
445 draw_connection (cr, c_x0, c_x1, y_in, yc - bxh2 - _pin_box_size, is_midi);
449 for (ChanMapping::Mappings::const_iterator t = out_map.begin (); t != out_map.end (); ++t) {
450 bool is_midi = t->first == DataType::MIDI;
451 for (ChanMapping::TypeMapping::const_iterator c = (*t).second.begin (); c != (*t).second.end () ; ++c) {
452 uint32_t pn = (*c).first; // pin
453 uint32_t pb = (*c).second;
454 double c_x0 = pin_x_pos (pn, x0 - bxw2, bxw, _sources.n_total (), _sources.n_midi (), is_midi);
455 double c_x1 = pin_x_pos (pb, 0, width, pc_out, pc_out_midi, is_midi);
456 draw_connection (cr, c_x0, c_x1, yc + bxh2 + _pin_box_size, y_out, is_midi);
462 for (CtrlElemList::const_iterator i = _elements.begin(); i != _elements.end(); ++i) {
466 draw_io_pin (cr, *i);
470 draw_plugin_pin (cr, *i);
480 PluginPinDialog::darea_size_request (Gtk::Requisition* req)
482 req->width = _min_width;
487 PluginPinDialog::darea_size_allocate (Gtk::Allocation&)
489 _position_valid = false;
493 PluginPinDialog::darea_motion_notify_event (GdkEventMotion* ev)
495 bool changed = false;
497 for (CtrlElemList::iterator i = _elements.begin(); i != _elements.end(); ++i) {
498 if (ev->x >= i->x && ev->x <= i->x + i->w
499 && ev->y >= i->y && ev->y <= i->y + i->h)
501 if (!i->prelight) changed = true;
505 if (i->prelight) changed = true;
516 PluginPinDialog::darea_button_press_event (GdkEventButton* ev)
518 if (ev->type != GDK_BUTTON_PRESS) {
522 switch (ev->button) {
524 if (!_selection || (_selection && !_hover)) {
528 } else if (_selection && _hover && _selection != _hover) {
529 if (_selection->dt != _hover->dt) { _actor.reset (); }
530 else if (_selection->ct == Input && _hover->ct == Sink) { _actor = _hover; }
531 else if (_selection->ct == Sink && _hover->ct == Input) { _actor = _hover; }
532 else if (_selection->ct == Output && _hover->ct == Source) { _actor = _hover; }
533 else if (_selection->ct == Source && _hover->ct == Output) { _actor = _hover; }
551 PluginPinDialog::darea_button_release_event (GdkEventButton* ev)
553 if (_hover == _actor && _actor) {
555 assert (_selection->dt == _actor->dt);
556 if (_selection->ct == Input && _actor->ct == Sink) {
557 handle_input_action (_actor, _selection);
559 else if (_selection->ct == Sink && _actor->ct == Input) {
560 handle_input_action (_selection, _actor);
562 else if (_selection->ct == Output && _actor->ct == Source) {
563 handle_output_action (_actor, _selection);
565 else if (_selection->ct == Source && _actor->ct == Output) {
566 handle_output_action (_selection, _actor);
576 PluginPinDialog::handle_input_action (const CtrlElem &s, const CtrlElem &i)
578 const int pc = s->ip;
580 ChanMapping in_map (_pi->input_map (pc));
581 uint32_t idx = in_map.get (s->dt, s->id, &valid);
583 if (valid && idx == i->id) {
585 in_map.unset (s->dt, s->id);
586 _pi->set_input_map (pc, in_map);
590 in_map.set (s->dt, s->id, i->id);
591 _pi->set_input_map (pc, in_map);
595 in_map.unset (s->dt, s->id);
596 in_map.set (s->dt, s->id, i->id);
597 _pi->set_input_map (pc, in_map);
602 PluginPinDialog::handle_output_action (const CtrlElem &s, const CtrlElem &o)
604 const uint32_t pc = s->ip;
606 ChanMapping out_map (_pi->output_map (pc));
607 uint32_t idx = out_map.get (s->dt, s->id, &valid);
609 if (valid && idx == o->id) {
611 out_map.unset (s->dt, s->id);
612 _pi->set_output_map (pc, out_map);
617 out_map.unset (s->dt, s->id);
619 // disconnect other outputs
620 _ignore_updates = true;
621 for (uint32_t n = 0; n < _n_plugins; ++n) {
625 ChanMapping n_out_map (_pi->output_map (n));
626 idx = n_out_map.get_src (s->dt, o->id, &valid);
628 n_out_map.unset (s->dt, idx);
629 _pi->set_output_map (n, n_out_map);
632 _ignore_updates = false;
633 idx = out_map.get_src (s->dt, o->id, &valid);
635 out_map.unset (s->dt, idx);
638 out_map.set (s->dt, s->id, o->id);
639 _pi->set_output_map (pc, out_map);
644 PluginPinDialog::reset_configuration ()
646 _route()->reset_plugin_insert (_pi);
650 PluginPinDialog::reset_mapping ()
656 PluginPinDialog::add_remove_plugin_clicked (bool add)
658 ChanCount out = _out;
659 assert (add || _n_plugins > 0);
660 _route()->customize_plugin_insert (_pi, _n_plugins + (add ? 1 : -1), out);
664 PluginPinDialog::add_remove_port_clicked (bool add, ARDOUR::DataType dt)
666 ChanCount out = _out;
667 assert (add || out.get (dt) > 0);
668 out.set (dt, out.get (dt) + (add ? 1 : -1));
669 _route()->customize_plugin_insert (_pi, _n_plugins, out);