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 , _strict_io (_("Strict I/O"))
42 , _automatic (_("Automatic"))
43 , _add_plugin (_("+"))
44 , _del_plugin (_("-"))
45 , _add_output_audio (_("+"))
46 , _del_output_audio (_("-"))
47 , _add_output_midi (_("+"))
48 , _del_output_midi (_("-"))
51 , _position_valid (false)
52 , _ignore_updates (false)
54 assert (pi->owner ()); // Route
56 _pi->PluginIoReConfigure.connect (
57 _plugin_connections, invalidator (*this), boost::bind (&PluginPinDialog::plugin_reconfigured, this), gui_context()
60 _pi->PluginMapChanged.connect (
61 _plugin_connections, invalidator (*this), boost::bind (&PluginPinDialog::plugin_reconfigured, this), gui_context()
64 _pi->PluginConfigChanged.connect (
65 _plugin_connections, invalidator (*this), boost::bind (&PluginPinDialog::plugin_reconfigured, this), gui_context()
68 // TODO min. width depending on # of pins.
69 darea.set_size_request(600, 200);
70 _strict_io.set_sensitive (false);
74 Table* t = manage (new Table (4, 3));
75 t->set_border_width (0);
78 l = manage (new Label (_("Track/Bus:"), ALIGN_END));
79 t->attach (*l, 0, 1, r, r + 1);
80 l = manage (new Label ());
81 l->set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
82 l->set_width_chars (24);
83 l->set_max_width_chars (24);
84 l->set_text (_route()->name ());
85 t->attach (*l, 1, 3, r, r + 1);
88 l = manage (new Label (_("Plugin:"), ALIGN_END));
89 t->attach (*l, 0, 1, r, r + 1);
90 l = manage (new Label ());
91 l->set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
92 l->set_width_chars (24);
93 l->set_max_width_chars (24);
94 l->set_text (pi->name ());
95 t->attach (*l, 1, 3, r, r + 1);
98 l = manage (new Label (_("Settings:"), ALIGN_END));
99 t->attach (*l, 0, 1, r, r + 1);
100 t->attach (_strict_io, 1, 2, r, r + 1, FILL, SHRINK);
101 t->attach (_automatic, 2, 3, r, r + 1, FILL, SHRINK);
104 l = manage (new Label (_("Instances:"), ALIGN_END));
105 t->attach (*l, 0, 1, r, r + 1);
106 t->attach (_add_plugin, 1, 2, r, r + 1, SHRINK, SHRINK);
107 t->attach (_del_plugin, 2, 3, r, r + 1, SHRINK, SHRINK);
110 l = manage (new Label (_("Audio Out:"), ALIGN_END));
111 t->attach (*l, 0, 1, r, r + 1);
112 t->attach (_add_output_audio, 1, 2, r, r + 1, SHRINK, SHRINK);
113 t->attach (_del_output_audio, 2, 3, r, r + 1, SHRINK, SHRINK);
116 l = manage (new Label (_("Midi Out:"), ALIGN_END));
117 t->attach (*l, 0, 1, r, r + 1);
118 t->attach (_add_output_midi, 1, 2, r, r + 1, SHRINK, SHRINK);
119 t->attach (_del_output_midi, 2, 3, r, r + 1, SHRINK, SHRINK);
122 HBox* hbox = manage (new HBox);
123 hbox->pack_start (darea, true, true);
124 hbox->pack_start (*t, false, true);
126 VBox* vbox = manage (new VBox);
127 vbox->pack_start (*hbox, true, true);
131 plugin_reconfigured ();
133 darea.add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::POINTER_MOTION_MASK);
134 darea.signal_size_allocate().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_size_allocate));
135 darea.signal_expose_event().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_expose_event));
136 darea.signal_button_press_event().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_button_press_event));
137 darea.signal_button_release_event().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_button_release_event));
138 darea.signal_motion_notify_event().connect (sigc::mem_fun (*this, &PluginPinDialog::darea_motion_notify_event));
140 _automatic.signal_clicked.connect (sigc::mem_fun(*this, &PluginPinDialog::automatic_clicked));
141 _add_plugin.signal_clicked.connect (sigc::bind (sigc::mem_fun(*this, &PluginPinDialog::add_remove_plugin_clicked), true));
142 _del_plugin.signal_clicked.connect (sigc::bind (sigc::mem_fun(*this, &PluginPinDialog::add_remove_plugin_clicked), false));
144 _add_output_audio.signal_clicked.connect (sigc::bind (sigc::mem_fun(*this, &PluginPinDialog::add_remove_port_clicked), true, DataType::AUDIO));
145 _del_output_audio.signal_clicked.connect (sigc::bind (sigc::mem_fun(*this, &PluginPinDialog::add_remove_port_clicked), false, DataType::AUDIO));
146 _add_output_midi.signal_clicked.connect (sigc::bind (sigc::mem_fun(*this, &PluginPinDialog::add_remove_port_clicked), true, DataType::MIDI));
147 _del_output_midi.signal_clicked.connect (sigc::bind (sigc::mem_fun(*this, &PluginPinDialog::add_remove_port_clicked), false, DataType::MIDI));
150 PluginPinDialog::~PluginPinDialog()
155 PluginPinDialog::plugin_reconfigured ()
157 if (_ignore_updates) {
160 _n_plugins = _pi->get_count ();
161 _pi->configured_io (_in, _out);
162 _sinks = _pi->natural_input_streams ();
163 _sources = _pi->natural_output_streams ();
165 _del_plugin.set_sensitive (_n_plugins > 1);
166 _del_output_audio.set_sensitive (_out.n_audio () > 0 && _out.n_total () > 1);
167 _del_output_midi.set_sensitive (_out.n_midi () > 0 && _out.n_total () > 1);
168 _strict_io.set_active (_pi->strict_io());
174 PluginPinDialog::update_elements ()
181 for (uint32_t i = 0; i < _in.n_total (); ++i) {
182 int id = (i < _in.n_midi ()) ? i : i - _in.n_midi ();
183 _elements.push_back (CtrlWidget (Input, (i < _in.n_midi () ? DataType::MIDI : DataType::AUDIO), id));
186 for (uint32_t i = 0; i < _out.n_total (); ++i) {
187 int id = (i < _out.n_midi ()) ? i : i - _out.n_midi ();
188 _elements.push_back (CtrlWidget (Output, (i < _out.n_midi () ? DataType::MIDI : DataType::AUDIO), id));
191 for (uint32_t n = 0; n < _n_plugins; ++n) {
192 for (uint32_t i = 0; i < _sinks.n_total(); ++i) {
193 _elements.push_back (CtrlWidget (Sink, (i < _sinks.n_midi () ? DataType::MIDI : DataType::AUDIO), i, n));
195 for (uint32_t i = 0; i < _sources.n_total(); ++i) {
196 _elements.push_back (CtrlWidget (Source, (i < _sources.n_midi () ? DataType::MIDI : DataType::AUDIO), i, n));
199 _position_valid = false;
204 PluginPinDialog::update_element_pos ()
207 const double yc = rint (_height * .5);
208 const double bxh2 = 18;
209 const double bxw = rint ((_width * .9) / ((_n_plugins) + .2 * (_n_plugins - 1)));
210 const double bxw2 = rint (bxw * .5);
211 const double y_in = 40;
212 const double y_out = _height - 40;
214 _pin_box_size = rint (max (6., 8. * UIConfiguration::instance().get_ui_scale()));
216 for (CtrlElemList::iterator i = _elements.begin(); i != _elements.end(); ++i) {
220 uint32_t idx = i->e->id;
221 if (i->e->dt == DataType::AUDIO) { idx += _in.n_midi (); }
222 i->x = rint ((idx + 1) * _width / (1. + _in.n_total ())) - 5.5;
230 uint32_t idx = i->e->id;
231 if (i->e->dt == DataType::AUDIO) { idx += _out.n_midi (); }
232 i->x = rint ((idx + 1) * _width / (1. + _out.n_total ())) - 5.5;
240 const double x0 = rint ((i->e->ip + .5) * _width / (double)(_n_plugins)) - .5 - bxw2;
241 i->x = rint (x0 + (i->e->id + 1) * bxw / (1. + _sinks.n_total ())) - .5 - _pin_box_size * .5;
242 i->y = yc - bxh2 - _pin_box_size;
243 i->w = _pin_box_size + 1;
244 i->h = _pin_box_size;
249 const double x0 = rint ((i->e->ip + .5) * _width / (double)(_n_plugins)) - .5 - bxw2;
250 i->x = rint (x0 + (i->e->id + 1) * bxw / (1. + _sources.n_total ())) - .5 - _pin_box_size * .5;
252 i->w = _pin_box_size + 1;
253 i->h = _pin_box_size;
262 PluginPinDialog::set_color (cairo_t* cr, bool midi)
264 // see also gtk2_ardour/processor_box.cc
265 static const uint32_t audio_port_color = 0x4A8A0EFF; // Green
266 static const uint32_t midi_port_color = 0x960909FF; //Red
269 cairo_set_source_rgb (cr,
270 UINT_RGBA_R_FLT(midi_port_color),
271 UINT_RGBA_G_FLT(midi_port_color),
272 UINT_RGBA_B_FLT(midi_port_color));
274 cairo_set_source_rgb (cr,
275 UINT_RGBA_R_FLT(audio_port_color),
276 UINT_RGBA_G_FLT(audio_port_color),
277 UINT_RGBA_B_FLT(audio_port_color));
282 PluginPinDialog::draw_io_pin (cairo_t* cr, const CtrlWidget& w)
284 const double dir = (w.e->ct == Input) ? 1 : -1;
286 cairo_move_to (cr, w.x + 5.0, w.y + ((w.e->ct == Input) ? 25 : 0));
287 cairo_rel_line_to (cr, -5., -5. * dir);
288 cairo_rel_line_to (cr, 0., -25. * dir);
289 cairo_rel_line_to (cr, 10., 0.);
290 cairo_rel_line_to (cr, 0., 25. * dir);
291 cairo_close_path (cr);
293 cairo_set_line_width (cr, 1.0);
294 cairo_set_source_rgb (cr, 0, 0, 0);
295 cairo_stroke_preserve (cr);
297 set_color (cr, w.e->dt == DataType::MIDI);
298 if (w.e == _selection || w.e == _actor) {
299 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0);
300 } else if (w.prelight) {
301 cairo_fill_preserve (cr);
302 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.5);
308 PluginPinDialog::draw_plugin_pin (cairo_t* cr, const CtrlWidget& w)
310 cairo_rectangle (cr, w.x, w.y, w.w, w.h);
311 set_color (cr, w.e->dt == DataType::MIDI);
312 if (w.e == _selection || w.e == _actor) {
313 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0);
314 } else if (w.prelight) {
315 cairo_fill_preserve (cr);
316 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.5);
322 PluginPinDialog::is_valid_port (uint32_t i, uint32_t n_total, uint32_t n_midi, bool midi)
324 if (!midi) { i += n_midi; }
332 PluginPinDialog::pin_x_pos (uint32_t i, double x0, double width, uint32_t n_total, uint32_t n_midi, bool midi)
334 if (!midi) { i += n_midi; }
335 return rint (x0 + (i + 1) * width / (1. + n_total)) - .5;
339 PluginPinDialog::draw_connection (cairo_t* cr, double x0, double x1, double y0, double y1, bool midi, bool dashed)
341 const double bz = 2 * _pin_box_size;
343 cairo_move_to (cr, x0, y0);
344 cairo_curve_to (cr, x0, y0 + bz, x1, y1 - bz, x1, y1);
345 cairo_set_line_width (cr, 3.0);
346 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
347 cairo_set_source_rgb (cr, 1, 0, 0);
349 const double dashes[] = { 5, 7 };
350 cairo_set_dash (cr, dashes, 2, 0);
352 set_color (cr, midi);
355 cairo_set_dash (cr, 0, 0, 0);
360 PluginPinDialog::darea_expose_event (GdkEventExpose* ev)
362 Gtk::Allocation a = darea.get_allocation();
363 double const width = a.get_width();
364 double const height = a.get_height();
366 if (!_position_valid) {
369 update_element_pos ();
370 _position_valid = true;
373 cairo_t* cr = gdk_cairo_create (darea.get_window()->gobj());
374 cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
377 Gdk::Color const bg = get_style()->get_bg (STATE_NORMAL);
378 cairo_set_source_rgb (cr, bg.get_red_p (), bg.get_green_p (), bg.get_blue_p ());
379 cairo_rectangle (cr, 0, 0, width, height);
382 /* layout sizes -- TODO consolidate w/ update_element_pos() */
384 const double y_in = 40;
385 const double y_out = height - 40;
388 const double yc = rint (height * .5);
389 const double bxh2 = 18;
390 const double bxw = rint ((width * .9) / ((_n_plugins) + .2 * (_n_plugins - 1)));
391 const double bxw2 = rint (bxw * .5);
393 const uint32_t pc_in = _in.n_total();
394 const uint32_t pc_in_midi = _in.n_midi();
395 const uint32_t pc_out = _out.n_total();
396 const uint32_t pc_out_midi = _out.n_midi();
398 /* draw midi-bypass (behind) */
399 if (_pi->has_midi_bypass ()) {
400 double x0 = rint (width / (1. + pc_in)) - .5;
401 double x1 = rint (width / (1. + pc_out)) - .5;
402 draw_connection (cr, x0, x1, y_in, y_out, true, true);
405 /* plugins & connection wires */
406 for (uint32_t i = 0; i < _n_plugins; ++i) {
407 double x0 = rint ((i + .5) * width / (double)(_n_plugins)) - .5;
410 cairo_set_source_rgb (cr, .3, .3, .3);
411 rounded_rectangle (cr, x0 - bxw2, yc - bxh2, bxw, 2 * bxh2, 7);
414 const ChanMapping::Mappings in_map = _pi->input_map (i).mappings();
415 const ChanMapping::Mappings out_map = _pi->output_map (i).mappings();
417 for (ChanMapping::Mappings::const_iterator t = in_map.begin (); t != in_map.end (); ++t) {
418 bool is_midi = t->first == DataType::MIDI;
419 for (ChanMapping::TypeMapping::const_iterator c = (*t).second.begin (); c != (*t).second.end () ; ++c) {
420 uint32_t pn = (*c).first; // pin
421 uint32_t pb = (*c).second;
422 if (!is_valid_port (pb, pc_in, pc_in_midi, is_midi)) {
425 double c_x0 = pin_x_pos (pb, 0, width, pc_in, pc_in_midi, is_midi);
426 double c_x1 = pin_x_pos (pn, x0 - bxw2, bxw, _sinks.n_total (), _sinks.n_midi (), is_midi);
427 draw_connection (cr, c_x0, c_x1, y_in, yc - bxh2 - _pin_box_size, is_midi);
431 for (ChanMapping::Mappings::const_iterator t = out_map.begin (); t != out_map.end (); ++t) {
432 bool is_midi = t->first == DataType::MIDI;
433 for (ChanMapping::TypeMapping::const_iterator c = (*t).second.begin (); c != (*t).second.end () ; ++c) {
434 uint32_t pn = (*c).first; // pin
435 uint32_t pb = (*c).second;
436 if (!is_valid_port (pb, pc_out, pc_out_midi, is_midi)) {
439 double c_x0 = pin_x_pos (pn, x0 - bxw2, bxw, _sources.n_total (), _sources.n_midi (), is_midi);
440 double c_x1 = pin_x_pos (pb, 0, width, pc_out, pc_out_midi, is_midi);
441 draw_connection (cr, c_x0, c_x1, yc + bxh2 + _pin_box_size, y_out, is_midi);
447 for (CtrlElemList::const_iterator i = _elements.begin(); i != _elements.end(); ++i) {
451 draw_io_pin (cr, *i);
455 draw_plugin_pin (cr, *i);
465 PluginPinDialog::darea_size_allocate (Gtk::Allocation&)
467 _position_valid = false;
471 PluginPinDialog::darea_motion_notify_event (GdkEventMotion* ev)
473 bool changed = false;
475 for (CtrlElemList::iterator i = _elements.begin(); i != _elements.end(); ++i) {
476 if (ev->x >= i->x && ev->x <= i->x + i->w
477 && ev->y >= i->y && ev->y <= i->y + i->h)
479 if (!i->prelight) changed = true;
483 if (i->prelight) changed = true;
494 PluginPinDialog::darea_button_press_event (GdkEventButton* ev)
496 if (ev->type != GDK_BUTTON_PRESS) {
500 switch (ev->button) {
502 if (!_selection || (_selection && !_hover)) {
506 } else if (_selection && _hover && _selection != _hover) {
507 if (_selection->dt != _hover->dt) { _actor.reset (); }
508 else if (_selection->ct == Input && _hover->ct == Sink) { _actor = _hover; }
509 else if (_selection->ct == Sink && _hover->ct == Input) { _actor = _hover; }
510 else if (_selection->ct == Output && _hover->ct == Source) { _actor = _hover; }
511 else if (_selection->ct == Source && _hover->ct == Output) { _actor = _hover; }
529 PluginPinDialog::darea_button_release_event (GdkEventButton* ev)
531 if (_hover == _actor && _actor) {
533 assert (_selection->dt == _actor->dt);
534 if (_selection->ct == Input && _actor->ct == Sink) {
535 handle_input_action (_actor, _selection);
537 else if (_selection->ct == Sink && _actor->ct == Input) {
538 handle_input_action (_selection, _actor);
540 else if (_selection->ct == Output && _actor->ct == Source) {
541 handle_output_action (_actor, _selection);
543 else if (_selection->ct == Source && _actor->ct == Output) {
544 handle_output_action (_selection, _actor);
554 PluginPinDialog::handle_input_action (const CtrlElem &s, const CtrlElem &i)
556 const int pc = s->ip;
558 ChanMapping in_map (_pi->input_map (pc));
559 uint32_t idx = in_map.get (s->dt, s->id, &valid);
561 if (valid && idx == i->id) {
563 in_map.unset (s->dt, s->id);
564 _pi->set_input_map (pc, in_map);
568 in_map.set (s->dt, s->id, i->id);
569 _pi->set_input_map (pc, in_map);
573 in_map.unset (s->dt, s->id);
574 in_map.set (s->dt, s->id, i->id);
575 _pi->set_input_map (pc, in_map);
580 PluginPinDialog::handle_output_action (const CtrlElem &s, const CtrlElem &o)
582 const int pc = s->ip;
584 ChanMapping out_map (_pi->output_map (pc));
585 uint32_t idx = out_map.get (s->dt, s->id, &valid);
587 if (valid && idx == o->id) {
589 out_map.unset (s->dt, s->id);
590 _pi->set_output_map (pc, out_map);
595 out_map.unset (s->dt, s->id);
597 // disconnect other outputs
598 _ignore_updates = true;
599 for (uint32_t n = 0; n < _n_plugins; ++n) {
603 ChanMapping n_out_map (_pi->output_map (n));
604 idx = n_out_map.get_src (s->dt, o->id, &valid);
606 n_out_map.unset (s->dt, idx);
607 _pi->set_output_map (n, n_out_map);
610 _ignore_updates = false;
611 idx = out_map.get_src (s->dt, o->id, &valid);
613 out_map.unset (s->dt, idx);
616 out_map.set (s->dt, s->id, o->id);
617 _pi->set_output_map (pc, out_map);
622 PluginPinDialog::automatic_clicked ()
624 _route()->reset_plugin_insert (_pi);
628 PluginPinDialog::add_remove_plugin_clicked (bool add)
630 ChanCount out = _out;
631 assert (add || _n_plugins > 0);
632 _route()->customize_plugin_insert (_pi, _n_plugins + (add ? 1 : -1), out);
636 PluginPinDialog::add_remove_port_clicked (bool add, ARDOUR::DataType dt)
638 ChanCount out = _out;
639 assert (add || out.get (dt) > 0);
640 out.set (dt, out.get (dt) + (add ? 1 : -1));
641 _route()->customize_plugin_insert (_pi, _n_plugins, out);