1 /*
2  * Copyright (C) 2015-2019 Ben Loftis <ben@harrisonconsoles.com>
3  * Copyright (C) 2015-2019 Paul Davis <paul@linuxaudiosystems.com>
4  * Copyright (C) 2017-2019 Robin Gareus <robin@gareus.org>
5  * Copyright (C) 2019 Johannes Mueller <github@johannes-mueller.org>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21 
22 #include <gtkmm/alignment.h>
23 #include <gtkmm/label.h>
24 #include <gtkmm/liststore.h>
25 
26 #include "pbd/file_utils.h"
27 #include "pbd/strsplit.h"
28 #include "pbd/unwind.h"
29 
30 #include "gtkmm2ext/actions.h"
31 #include "gtkmm2ext/action_model.h"
32 #include "gtkmm2ext/bindings.h"
33 #include "gtkmm2ext/gtk_ui.h"
34 #include "gtkmm2ext/gui_thread.h"
35 #include "gtkmm2ext/utils.h"
36 
37 #include "ardour/audioengine.h"
38 #include "ardour/filesystem_paths.h"
39 
40 #include "faderport.h"
41 #include "gui.h"
42 
43 #include "pbd/i18n.h"
44 
45 using namespace PBD;
46 using namespace ARDOUR;
47 using namespace ArdourSurface;
48 using namespace std;
49 using namespace Gtk;
50 using namespace Gtkmm2ext;
51 
52 void*
get_gui() const53 FaderPort::get_gui () const
54 {
55 	if (!gui) {
56 		const_cast<FaderPort*>(this)->build_gui ();
57 	}
58 	static_cast<Gtk::VBox*>(gui)->show_all();
59 	return gui;
60 }
61 
62 void
tear_down_gui()63 FaderPort::tear_down_gui ()
64 {
65 	if (gui) {
66 		Gtk::Widget *w = static_cast<Gtk::VBox*>(gui)->get_parent();
67 		if (w) {
68 			w->hide();
69 			delete w;
70 		}
71 	}
72 	delete static_cast<FPGUI*> (gui);
73 	gui = 0;
74 }
75 
76 void
build_gui()77 FaderPort::build_gui ()
78 {
79 	gui = (void*) new FPGUI (*this);
80 }
81 
82 /*--------------------*/
83 
FPGUI(FaderPort & p)84 FPGUI::FPGUI (FaderPort& p)
85 	: fp (p)
86 	, table (2, 5)
87 	, action_table (5, 4)
88 	, ignore_active_change (false)
89 	, action_model (ActionManager::ActionModel::instance ())
90 {
91 	set_border_width (12);
92 
93 	table.set_row_spacings (4);
94 	table.set_col_spacings (6);
95 	table.set_border_width (12);
96 	table.set_homogeneous (false);
97 
98 	std::string data_file_path;
99 	string name = "faderport-small.png";
100 	Searchpath spath(ARDOUR::ardour_data_search_path());
101 	spath.add_subdirectory_to_paths ("icons");
102 	find_file (spath, name, data_file_path);
103 	if (!data_file_path.empty()) {
104 		image.set (data_file_path);
105 		hpacker.pack_start (image, false, false);
106 	}
107 
108 	Gtk::Label* l;
109 	Gtk::Alignment* align;
110 	int row = 0;
111 
112 	input_combo.pack_start (midi_port_columns.short_name);
113 	output_combo.pack_start (midi_port_columns.short_name);
114 
115 	input_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &FPGUI::active_port_changed), &input_combo, true));
116 	output_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &FPGUI::active_port_changed), &output_combo, false));
117 
118 	l = manage (new Gtk::Label);
119 	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Incoming MIDI on:")));
120 	l->set_alignment (1.0, 0.5);
121 	table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
122 	table.attach (input_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
123 	row++;
124 
125 	l = manage (new Gtk::Label);
126 	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Outgoing MIDI on:")));
127 	l->set_alignment (1.0, 0.5);
128 	table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
129 	table.attach (output_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
130 	row++;
131 
132 	build_mix_action_combo (mix_combo[0], FaderPort::ButtonState(0));
133 	build_mix_action_combo (mix_combo[1], FaderPort::ShiftDown);
134 	build_mix_action_combo (mix_combo[2], FaderPort::LongPress);
135 
136 	build_proj_action_combo (proj_combo[0], FaderPort::ButtonState(0));
137 	build_proj_action_combo (proj_combo[1], FaderPort::ShiftDown);
138 	build_proj_action_combo (proj_combo[2], FaderPort::LongPress);
139 
140 	build_trns_action_combo (trns_combo[0], FaderPort::ButtonState(0));
141 	build_trns_action_combo (trns_combo[1], FaderPort::ShiftDown);
142 	build_trns_action_combo (trns_combo[2], FaderPort::LongPress);
143 
144 	build_foot_action_combo (foot_combo[0], FaderPort::ButtonState(0));
145 	build_foot_action_combo (foot_combo[1], FaderPort::ShiftDown);
146 	build_foot_action_combo (foot_combo[2], FaderPort::LongPress);
147 
148 	/* No shift-press combo for User because that is labelled as "next"
149 	 * (marker)
150 	 */
151 
152 	build_user_action_combo (user_combo[0], FaderPort::ButtonState(0));
153 	build_user_action_combo (user_combo[1], FaderPort::LongPress);
154 
155 	action_table.set_row_spacings (4);
156 	action_table.set_col_spacings (6);
157 	action_table.set_border_width (12);
158 	action_table.set_homogeneous (false);
159 
160 	int action_row = 0;
161 
162 	l = manage (new Gtk::Label);
163 	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Press Action")));
164 	l->set_alignment (0.5, 0.5);
165 	action_table.attach (*l, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
166 	l = manage (new Gtk::Label);
167 	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Shift-Press Action")));
168 	l->set_alignment (0.5, 0.5);
169 	action_table.attach (*l, 2, 3, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
170 	l = manage (new Gtk::Label);
171 	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Long Press Action")));
172 	l->set_alignment (0.5, 0.5);
173 	action_table.attach (*l, 3, 4, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
174 	action_row++;
175 
176 	l = manage (new Gtk::Label);
177 	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Mix")));
178 	l->set_alignment (1.0, 0.5);
179 	action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
180 	align = manage (new Alignment);
181 	align->set (0.0, 0.5);
182 	align->add (mix_combo[0]);
183 	action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
184 	align = manage (new Alignment);
185 	align->set (0.0, 0.5);
186 	align->add (mix_combo[1]);
187 	action_table.attach (*align, 2, 3, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
188 	align = manage (new Alignment);
189 	align->set (0.0, 0.5);
190 	align->add (mix_combo[2]);
191 	action_table.attach (*align, 3, 4, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
192 	action_row++;
193 
194 	l = manage (new Gtk::Label);
195 	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Proj")));
196 	l->set_alignment (1.0, 0.5);
197 	action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
198 	align = manage (new Alignment);
199 	align->set (0.0, 0.5);
200 	align->add (proj_combo[0]);
201 	action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
202 	align = manage (new Alignment);
203 	align->set (0.0, 0.5);
204 	align->add (proj_combo[1]);
205 	action_table.attach (*align, 2, 3, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
206 	align = manage (new Alignment);
207 	align->set (0.0, 0.5);
208 	align->add (proj_combo[2]);
209 	action_table.attach (*align, 3, 4, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
210 	action_row++;
211 
212 	l = manage (new Gtk::Label);
213 	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Trns")));
214 	l->set_alignment (1.0, 0.5);
215 	action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
216 	align = manage (new Alignment);
217 	align->set (0.0, 0.5);
218 	align->add (trns_combo[0]);
219 	action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
220 	align = manage (new Alignment);
221 	align->set (0.0, 0.5);
222 	align->add (trns_combo[1]);
223 	action_table.attach (*align, 2, 3, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
224 	align = manage (new Alignment);
225 	align->set (0.0, 0.5);
226 	align->add (trns_combo[2]);
227 	action_table.attach (*align, 3, 4, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
228 	action_row++;
229 
230 	l = manage (new Gtk::Label);
231 	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("User")));
232 	l->set_alignment (1.0, 0.5);
233 	action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
234 	align = manage (new Alignment);
235 	align->set (0.0, 0.5);
236 	align->add (user_combo[0]);
237 	action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
238 	/* skip shift press combo */
239 	align = manage (new Alignment);
240 	align->set (0.0, 0.5);
241 	align->add (user_combo[1]);
242 	action_table.attach (*align, 3, 4, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
243 	action_row++;
244 
245 	l = manage (new Gtk::Label);
246 	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Footswitch")));
247 	l->set_alignment (1.0, 0.5);
248 	action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
249 	align = manage (new Alignment);
250 	align->set (0.0, 0.5);
251 	align->add (foot_combo[0]);
252 	action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
253 	align = manage (new Alignment);
254 	align->set (0.0, 0.5);
255 	align->add (foot_combo[1]);
256 	action_table.attach (*align, 2, 3, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
257 	align = manage (new Alignment);
258 	align->set (0.0, 0.5);
259 	align->add (foot_combo[2]);
260 	action_table.attach (*align, 3, 4, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
261 	action_row++;
262 
263 	table.attach (action_table, 0, 5, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
264 	row++;
265 
266 	hpacker.pack_start (table, true, true);
267 	pack_start (hpacker, false, false);
268 
269 	/* update the port connection combos */
270 
271 	update_port_combos ();
272 
273 	/* catch future changes to connection state */
274 
275 	ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect (_port_connections, invalidator (*this), boost::bind (&FPGUI::connection_handler, this), gui_context());
276 	ARDOUR::AudioEngine::instance()->PortPrettyNameChanged.connect (_port_connections, invalidator (*this), boost::bind (&FPGUI::connection_handler, this), gui_context());
277 	fp.ConnectionChange.connect (_port_connections, invalidator (*this), boost::bind (&FPGUI::connection_handler, this), gui_context());
278 }
279 
~FPGUI()280 FPGUI::~FPGUI ()
281 {
282 }
283 
284 void
connection_handler()285 FPGUI::connection_handler ()
286 {
287 	/* ignore all changes to combobox active strings here, because we're
288 	   updating them to match a new ("external") reality - we were called
289 	   because port connections have changed.
290 	*/
291 
292 	PBD::Unwinder<bool> ici (ignore_active_change, true);
293 
294 	update_port_combos ();
295 }
296 
297 void
update_port_combos()298 FPGUI::update_port_combos ()
299 {
300 	vector<string> midi_inputs;
301 	vector<string> midi_outputs;
302 
303 	ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsOutput|ARDOUR::IsTerminal), midi_inputs);
304 	ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsInput|ARDOUR::IsTerminal), midi_outputs);
305 
306 	Glib::RefPtr<Gtk::ListStore> input = build_midi_port_list (midi_inputs, true);
307 	Glib::RefPtr<Gtk::ListStore> output = build_midi_port_list (midi_outputs, false);
308 	bool input_found = false;
309 	bool output_found = false;
310 	int n;
311 
312 	input_combo.set_model (input);
313 	output_combo.set_model (output);
314 
315 	Gtk::TreeModel::Children children = input->children();
316 	Gtk::TreeModel::Children::iterator i;
317 	i = children.begin();
318 	++i; /* skip "Disconnected" */
319 
320 
321 	for (n = 1;  i != children.end(); ++i, ++n) {
322 		string port_name = (*i)[midi_port_columns.full_name];
323 		if (fp.input_port()->connected_to (port_name)) {
324 			input_combo.set_active (n);
325 			input_found = true;
326 			break;
327 		}
328 	}
329 
330 	if (!input_found) {
331 		input_combo.set_active (0); /* disconnected */
332 	}
333 
334 	children = output->children();
335 	i = children.begin();
336 	++i; /* skip "Disconnected" */
337 
338 	for (n = 1;  i != children.end(); ++i, ++n) {
339 		string port_name = (*i)[midi_port_columns.full_name];
340 		if (fp.output_port()->connected_to (port_name)) {
341 			output_combo.set_active (n);
342 			output_found = true;
343 			break;
344 		}
345 	}
346 
347 	if (!output_found) {
348 		output_combo.set_active (0); /* disconnected */
349 	}
350 }
351 
352 void
action_changed(Gtk::ComboBox * cb,FaderPort::ButtonID id,FaderPort::ButtonState bs)353 FPGUI::action_changed (Gtk::ComboBox* cb, FaderPort::ButtonID id, FaderPort::ButtonState bs)
354 {
355 	TreeModel::const_iterator row = cb->get_active ();
356 	string action_path = (*row)[action_model.path()];
357 
358 	/* release binding */
359 	fp.set_action (id, action_path, false, bs);
360 }
361 
362 void
build_action_combo(Gtk::ComboBox & cb,vector<pair<string,string>> const & actions,FaderPort::ButtonID id,FaderPort::ButtonState bs)363 FPGUI::build_action_combo (Gtk::ComboBox& cb, vector<pair<string,string> > const & actions, FaderPort::ButtonID id, FaderPort::ButtonState bs)
364 {
365 	const string current_action = fp.get_action (id, false, bs); /* lookup release action */
366 	action_model.build_custom_action_combo (cb, actions, current_action);
367 	cb.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &FPGUI::action_changed), &cb, id, bs));
368 }
369 
370 void
build_mix_action_combo(Gtk::ComboBox & cb,FaderPort::ButtonState bs)371 FPGUI::build_mix_action_combo (Gtk::ComboBox& cb, FaderPort::ButtonState bs)
372 {
373 	vector<pair<string,string> > actions;
374 
375 	actions.push_back (make_pair (string (_("Show Mixer Window")), string (X_("Common/show-mixer"))));
376 	actions.push_back (make_pair (string (_("Show/Hide Mixer list")), string (X_("Mixer/ToggleMixerList"))));
377 	actions.push_back (make_pair (string("Toggle Meterbridge"), string(X_("Common/toggle-meterbridge"))));
378 	actions.push_back (make_pair (string (_("Show/Hide Editor mixer strip")), string (X_("Editor/show-editor-mixer"))));
379 
380 	build_action_combo (cb, actions, FaderPort::Mix, bs);
381 }
382 
383 void
build_proj_action_combo(Gtk::ComboBox & cb,FaderPort::ButtonState bs)384 FPGUI::build_proj_action_combo (Gtk::ComboBox& cb, FaderPort::ButtonState bs)
385 {
386 	vector<pair<string,string> > actions;
387 
388 	actions.push_back (make_pair (string (_("Show Editor Window")), string (X_("Common/show-editor"))));
389 	actions.push_back (make_pair (string("Toggle Editor Lists"), string(X_("Editor/show-editor-list"))));
390 	actions.push_back (make_pair (string("Toggle Summary"), string(X_("Editor/ToggleSummary"))));
391 	actions.push_back (make_pair (string("Toggle Meterbridge"), string(X_("Common/toggle-meterbridge"))));
392 	actions.push_back (make_pair (string (_("Zoom to Session")), string (X_("Editor/zoom-to-session"))));
393 
394 #if 0
395 	actions.push_back (make_pair (string (_("Zoom In")), string (X_("Editor/temporal-zoom-in"))));
396 	actions.push_back (make_pair (string (_("Zoom Out")), string (X_("Editor/temporal-zoom-out"))));
397 #endif
398 
399 	build_action_combo (cb, actions, FaderPort::Proj, bs);
400 }
401 
402 void
build_trns_action_combo(Gtk::ComboBox & cb,FaderPort::ButtonState bs)403 FPGUI::build_trns_action_combo (Gtk::ComboBox& cb, FaderPort::ButtonState bs)
404 {
405 	vector<pair<string,string> > actions;
406 
407 	actions.push_back (make_pair (string("Toggle Big Clock"), string(X_("Window/toggle-big-clock"))));  //note:  this would really make sense if the Big Clock had transport buttons on it
408 	actions.push_back (make_pair (string("Toggle Locations window"), string(X_("Window/toggle-locations"))));
409 	actions.push_back (make_pair (string("Toggle Metronome"), string(X_("Transport/ToggleClick"))));
410 	actions.push_back (make_pair (string("Toggle External Sync"), string(X_("Transport/ToggleExternalSync"))));
411 	actions.push_back (make_pair (string("Toggle Follow Playhead"), string(X_("Editor/toggle-follow-playhead"))));
412 
413 //	actions.push_back (make_pair (string("Set Playhead @pointer"), string(X_("Editor/set-playhead"))));
414 
415 
416 	build_action_combo (cb, actions, FaderPort::Trns, bs);
417 }
418 
419 void
build_foot_action_combo(Gtk::ComboBox & cb,FaderPort::ButtonState bs)420 FPGUI::build_foot_action_combo (Gtk::ComboBox& cb, FaderPort::ButtonState bs)
421 {
422 	vector<pair<string,string> > actions;
423 
424 	actions.push_back (make_pair (string("Toggle Roll"), string(X_("Transport/ToggleRoll"))));
425 	actions.push_back (make_pair (string("Toggle Rec-Enable"), string(X_("Transport/Record"))));
426 	actions.push_back (make_pair (string("Toggle Roll+Rec"), string(X_("Transport/record-roll"))));
427 	actions.push_back (make_pair (string("Toggle Loop"), string(X_("Transport/Loop"))));
428 	actions.push_back (make_pair (string("Toggle Click"), string(X_("Transport/ToggleClick"))));
429 	actions.push_back (make_pair (string("Record with Pre-Roll"), string(X_("Transport/RecordPreroll"))));
430 	actions.push_back (make_pair (string("Record with Count-In"), string(X_("Transport/RecordCountIn"))));
431 
432 	build_action_combo (cb, actions, FaderPort::Footswitch, bs);
433 }
434 
435 
436 void
build_user_action_combo(Gtk::ComboBox & cb,FaderPort::ButtonState bs)437 FPGUI::build_user_action_combo (Gtk::ComboBox& cb, FaderPort::ButtonState bs)
438 {
439 #ifndef MIXBUS
440 	bs = FaderPort::ButtonState (bs|FaderPort::UserDown);
441 #endif
442 
443 	/* set the active "row" to the right value for the current button binding */
444 
445 	string current_action = fp.get_action (FaderPort::User, false, bs); /* lookup release action */
446 	action_model.build_action_combo (cb, current_action);
447 	cb.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &FPGUI::action_changed), &cb, FaderPort::User, bs));
448 
449 }
450 
451 Glib::RefPtr<Gtk::ListStore>
build_midi_port_list(vector<string> const & ports,bool for_input)452 FPGUI::build_midi_port_list (vector<string> const & ports, bool for_input)
453 {
454 	Glib::RefPtr<Gtk::ListStore> store = ListStore::create (midi_port_columns);
455 	TreeModel::Row row;
456 
457 	row = *store->append ();
458 	row[midi_port_columns.full_name] = string();
459 	row[midi_port_columns.short_name] = _("Disconnected");
460 
461 	for (vector<string>::const_iterator p = ports.begin(); p != ports.end(); ++p) {
462 		row = *store->append ();
463 		row[midi_port_columns.full_name] = *p;
464 		std::string pn = ARDOUR::AudioEngine::instance()->get_pretty_name_by_name (*p);
465 		if (pn.empty ()) {
466 			pn = (*p).substr ((*p).find (':') + 1);
467 		}
468 		row[midi_port_columns.short_name] = pn;
469 	}
470 
471 	return store;
472 }
473 
474 void
active_port_changed(Gtk::ComboBox * combo,bool for_input)475 FPGUI::active_port_changed (Gtk::ComboBox* combo, bool for_input)
476 {
477 	if (ignore_active_change) {
478 		return;
479 	}
480 
481 	TreeModel::iterator active = combo->get_active ();
482 	string new_port = (*active)[midi_port_columns.full_name];
483 
484 	if (new_port.empty()) {
485 		if (for_input) {
486 			fp.input_port()->disconnect_all ();
487 		} else {
488 			fp.output_port()->disconnect_all ();
489 		}
490 
491 		return;
492 	}
493 
494 	if (for_input) {
495 		if (!fp.input_port()->connected_to (new_port)) {
496 			fp.input_port()->disconnect_all ();
497 			fp.input_port()->connect (new_port);
498 		}
499 	} else {
500 		if (!fp.output_port()->connected_to (new_port)) {
501 			fp.output_port()->disconnect_all ();
502 			fp.output_port()->connect (new_port);
503 		}
504 	}
505 }
506