2 Copyright (C) 2002-2003 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 #include <sigc++/bind.h>
25 #include <gtkmm/messagedialog.h>
27 #include <glibmm/thread.h>
29 #include <ardour/io.h>
30 #include <ardour/route.h>
31 #include <ardour/audioengine.h>
32 #include <ardour/port.h>
33 #include <ardour/mtdm.h>
34 #include <ardour/insert.h>
35 #include <ardour/session.h>
36 #include <ardour/audio_diskstream.h>
38 #include <gtkmm2ext/doi.h>
39 #include <gtkmm2ext/gtk_ui.h>
40 #include <gtkmm2ext/utils.h>
43 #include "io_selector.h"
45 #include "gui_thread.h"
53 using namespace ARDOUR
;
55 using namespace Gtkmm2ext
;
57 IOSelectorWindow::IOSelectorWindow (Session
& sess
, boost::shared_ptr
<IO
> ior
, bool input
, bool can_cancel
)
58 : ArdourDialog ("i/o selector"),
59 _selector (sess
, ior
, input
),
60 ok_button (can_cancel
? _("OK"): _("Close")),
61 cancel_button (_("Cancel")),
62 rescan_button (_("Rescan"))
65 add_events (Gdk::KEY_PRESS_MASK
|Gdk::KEY_RELEASE_MASK
);
66 set_name ("IOSelectorWindow");
70 title
= string_compose(_("%1 input"), ior
->name());
72 title
= string_compose(_("%1 output"), ior
->name());
75 ok_button
.set_name ("IOSelectorButton");
76 cancel_button
.set_name ("IOSelectorButton");
77 rescan_button
.set_name ("IOSelectorButton");
79 button_box
.set_spacing (5);
80 button_box
.set_border_width (5);
81 button_box
.set_homogeneous (true);
82 button_box
.pack_start (rescan_button
);
85 button_box
.pack_start (cancel_button
);
90 button_box
.pack_start (ok_button
);
92 get_vbox()->pack_start (_selector
);
93 get_vbox()->pack_start (button_box
, false, false);
95 ok_button
.signal_clicked().connect (mem_fun(*this, &IOSelectorWindow::accept
));
96 cancel_button
.signal_clicked().connect (mem_fun(*this, &IOSelectorWindow::cancel
));
97 rescan_button
.signal_clicked().connect (mem_fun(*this, &IOSelectorWindow::rescan
));
100 set_position (WIN_POS_MOUSE
);
102 signal_delete_event().connect (bind (sigc::ptr_fun (just_hide_it
), reinterpret_cast<Window
*> (this)));
105 IOSelectorWindow::~IOSelectorWindow()
110 IOSelectorWindow::rescan ()
112 _selector
.redisplay ();
116 IOSelectorWindow::cancel ()
118 _selector
.Finished(IOSelector::Cancelled
);
123 IOSelectorWindow::accept ()
125 _selector
.Finished(IOSelector::Accepted
);
130 IOSelectorWindow::on_map ()
132 _selector
.redisplay ();
136 /*************************
137 The IO Selector "widget"
138 *************************/
140 IOSelector::IOSelector (Session
& sess
, boost::shared_ptr
<IO
> ior
, bool input
)
144 port_frame (for_input
? _("Inputs") : _("Outputs")),
145 add_port_button (for_input
? _("Add") : _("Add")),
146 remove_port_button (for_input
? _("Remove") : _("Remove")),
147 clear_connections_button (_("Disconnect All"))
151 notebook
.set_name ("IOSelectorNotebook");
152 notebook
.set_size_request (-1, 125);
154 clear_connections_button
.set_name ("IOSelectorButton");
155 add_port_button
.set_name ("IOSelectorButton");
156 remove_port_button
.set_name ("IOSelectorButton");
158 selector_frame
.set_name ("IOSelectorFrame");
159 port_frame
.set_name ("IOSelectorFrame");
161 selector_frame
.set_label (_("Available connections"));
163 selector_button_box
.set_spacing (5);
164 selector_button_box
.set_border_width (5);
166 selector_box
.set_spacing (5);
167 selector_box
.set_border_width (5);
168 selector_box
.pack_start (notebook
);
169 selector_box
.pack_start (selector_button_box
, false, false);
171 selector_frame
.add (selector_box
);
173 port_box
.set_spacing (5);
174 port_box
.set_border_width (5);
176 port_display_scroller
.set_name ("IOSelectorNotebook");
177 port_display_scroller
.set_border_width (0);
178 port_display_scroller
.set_size_request (-1, 170);
179 port_display_scroller
.add (port_box
);
180 port_display_scroller
.set_policy (POLICY_NEVER
,
183 port_button_box
.set_spacing (5);
184 port_button_box
.set_border_width (5);
186 port_button_box
.pack_start (add_port_button
, false, false);
187 port_button_box
.pack_start (remove_port_button
, false, false);
188 port_button_box
.pack_start (clear_connections_button
, false, false);
190 port_and_button_box
.set_border_width (5);
191 port_and_button_box
.pack_start (port_button_box
, false, false);
192 port_and_button_box
.pack_start (port_display_scroller
);
194 port_frame
.add (port_and_button_box
);
196 port_and_selector_box
.set_spacing (5);
197 port_and_selector_box
.pack_start (port_frame
);
198 port_and_selector_box
.pack_start (selector_frame
);
201 set_border_width (5);
202 pack_start (port_and_selector_box
);
207 clear_connections_button
.signal_clicked().connect (mem_fun(*this, &IOSelector::clear_connections
));
209 add_port_button
.signal_clicked().connect (mem_fun(*this, &IOSelector::add_port
));
210 remove_port_button
.signal_clicked().connect (mem_fun(*this, &IOSelector::remove_port
));
213 io
->input_changed
.connect (mem_fun(*this, &IOSelector::ports_changed
));
215 io
->output_changed
.connect (mem_fun(*this, &IOSelector::ports_changed
));
218 set_button_sensitivity ();
220 io
->name_changed
.connect (mem_fun(*this, &IOSelector::name_changed
));
223 IOSelector::~IOSelector ()
228 IOSelector::set_button_sensitivity ()
232 if (io
->input_maximum() < 0 || io
->input_maximum() > (int) io
->n_inputs()) {
233 add_port_button
.set_sensitive (true);
235 add_port_button
.set_sensitive (false);
240 if (io
->output_maximum() < 0 || io
->output_maximum() > (int) io
->n_outputs()) {
241 add_port_button
.set_sensitive (true);
243 add_port_button
.set_sensitive (false);
249 if (io
->n_inputs() && (io
->input_minimum() < 0 || io
->input_minimum() < (int) io
->n_inputs())) {
250 remove_port_button
.set_sensitive (true);
252 remove_port_button
.set_sensitive (false);
256 if (io
->n_outputs() && (io
->output_minimum() < 0 || io
->output_minimum() < (int) io
->n_outputs())) {
257 remove_port_button
.set_sensitive (true);
259 remove_port_button
.set_sensitive (false);
266 IOSelector::name_changed (void* src
)
268 ENSURE_GUI_THREAD(bind (mem_fun(*this, &IOSelector::name_changed
), src
));
274 IOSelector::clear_connections ()
277 io
->disconnect_inputs (this);
279 io
->disconnect_outputs (this);
284 IOSelector::rescan ()
286 using namespace Notebook_Helpers
;
288 typedef std::map
<string
,vector
<pair
<string
,string
> > > PortMap
;
291 PageList
& pages
= notebook
.pages();
293 vector
<string
> rowdata
;
295 page_selection_connection
.disconnect ();
297 current_page
= notebook
.get_current_page ();
301 /* get relevant current JACK ports */
303 ports
= session
.engine().get_ports ("", JACK_DEFAULT_AUDIO_TYPE
, for_input
? JackPortIsOutput
: JackPortIsInput
);
309 /* find all the client names and group their ports into a list-by-client */
311 for (int n
= 0; ports
[n
]; ++n
) {
313 pair
<string
,vector
<pair
<string
,string
> > > newpair
;
314 pair
<string
,string
> strpair
;
315 pair
<PortMap::iterator
,bool> result
;
317 string str
= ports
[n
];
318 string::size_type pos
;
321 pos
= str
.find (':');
323 newpair
.first
= str
.substr (0, pos
);
324 portname
= str
.substr (pos
+1);
326 result
= portmap
.insert (newpair
);
328 strpair
.first
= portname
;
329 strpair
.second
= str
;
331 result
.first
->second
.push_back (strpair
);
336 for (i
= portmap
.begin(); i
!= portmap
.end(); ++i
) {
338 Box
*client_box
= manage (new VBox
);
339 TreeView
*display
= manage (new TreeView
);
340 RefPtr
<ListStore
> model
= ListStore::create (port_display_columns
);
341 ScrolledWindow
*scroller
= manage (new ScrolledWindow
);
343 display
->set_model (model
);
344 display
->append_column (X_("notvisible"), port_display_columns
.displayed_name
);
345 display
->set_headers_visible (false);
346 display
->get_selection()->set_mode (SELECTION_SINGLE
);
347 display
->set_name ("IOSelectorList");
349 for (vector
<pair
<string
,string
> >::iterator s
= i
->second
.begin(); s
!= i
->second
.end(); ++s
) {
351 TreeModel::Row row
= *(model
->append ());
353 row
[port_display_columns
.displayed_name
] = s
->first
;
354 row
[port_display_columns
.full_name
] = s
->second
;
357 display
->signal_button_release_event().connect (bind (mem_fun(*this, &IOSelector::port_selection_changed
), display
));
358 Label
*tab_label
= manage (new Label
);
360 tab_label
->set_name ("IOSelectorNotebookTab");
361 tab_label
->set_text ((*i
).first
);
363 scroller
->add (*display
);
364 scroller
->set_policy (POLICY_AUTOMATIC
, POLICY_AUTOMATIC
);
366 client_box
->pack_start (*scroller
);
368 pages
.push_back (TabElem (*client_box
, *tab_label
));
371 notebook
.set_current_page (current_page
);
372 page_selection_connection
= notebook
.signal_show().connect (bind (mem_fun (notebook
, &Notebook::set_current_page
), current_page
));
373 selector_box
.show_all ();
377 IOSelector::display_ports ()
379 TreeView
*firsttview
= 0;
380 TreeView
*selected_port_tview
= 0;
382 Glib::Mutex::Lock
lm (port_display_lock
);
387 limit
= io
->n_inputs();
389 limit
= io
->n_outputs();
392 for (slist
<TreeView
*>::iterator i
= port_displays
.begin(); i
!= port_displays
.end(); ) {
394 slist
<TreeView
*>::iterator tmp
;
399 port_box
.remove (**i
);
401 port_displays
.erase (i
);
406 for (uint32_t n
= 0; n
< limit
; ++n
) {
409 //ScrolledWindow *scroller;
410 string really_short_name
;
413 port
= io
->input (n
);
415 port
= io
->output (n
);
418 /* we know there is '/' because we put it there */
420 really_short_name
= port
->short_name();
421 really_short_name
= really_short_name
.substr (really_short_name
.find ('/') + 1);
423 tview
= manage (new TreeView());
424 RefPtr
<ListStore
> port_model
= ListStore::create (port_display_columns
);
430 tview
->set_model (port_model
);
431 tview
->append_column (really_short_name
, port_display_columns
.displayed_name
);
432 tview
->get_selection()->set_mode (SELECTION_SINGLE
);
433 tview
->set_data (X_("port"), port
);
434 tview
->set_headers_visible (true);
435 tview
->set_name (X_("IOSelectorPortList"));
437 port_box
.pack_start (*tview
);
438 port_displays
.insert (port_displays
.end(), tview
);
440 /* now fill the clist with the current connections */
442 const char **connections
= port
->get_connections ();
445 for (uint32_t c
= 0; connections
[c
]; ++c
) {
446 TreeModel::Row row
= *(port_model
->append());
447 row
[port_display_columns
.displayed_name
] = connections
[c
];
448 row
[port_display_columns
.full_name
] = connections
[c
];
454 if (io
->input_maximum() == 1) {
455 selected_port
= port
;
456 selected_port_tview
= tview
;
458 if (port
== selected_port
) {
459 selected_port_tview
= tview
;
465 if (io
->output_maximum() == 1) {
466 selected_port
= port
;
467 selected_port_tview
= tview
;
469 if (port
== selected_port
) {
470 selected_port_tview
= tview
;
475 TreeViewColumn
* col
= tview
->get_column (0);
477 col
->set_clickable (true);
479 /* handle button events on the column header ... */
480 col
->signal_clicked().connect (bind (mem_fun(*this, &IOSelector::select_treeview
), tview
));
482 /* ... and within the treeview itself */
483 tview
->signal_button_release_event().connect (bind (mem_fun(*this, &IOSelector::connection_button_release
), tview
));
486 port_box
.show_all ();
489 if (!selected_port_tview
) {
490 selected_port_tview
= firsttview
;
493 if (selected_port_tview
) {
494 select_treeview (selected_port_tview
);
499 IOSelector::port_selection_changed (GdkEventButton
*ev
, TreeView
* treeview
)
501 TreeModel::iterator i
= treeview
->get_selection()->get_selected();
508 if (selected_port
== 0) {
512 ustring other_port_name
= (*i
)[port_display_columns
.full_name
];
515 if ((status
= io
->connect_input (selected_port
, other_port_name
, this)) == 0) {
516 Port
*p
= session
.engine().get_port_by_name (other_port_name
);
517 p
->enable_metering();
520 status
= io
->connect_output (selected_port
, other_port_name
, this);
524 select_next_treeview ();
527 treeview
->get_selection()->unselect_all();
532 IOSelector::ports_changed (IOChange change
, void *src
)
534 ENSURE_GUI_THREAD(bind (mem_fun(*this, &IOSelector::ports_changed
), change
, src
));
540 IOSelector::add_port ()
542 /* add a new port, then hide the button if we're up to the maximum allowed */
547 io
->add_input_port ("", this);
550 catch (AudioEngine::PortRegistrationFailure
& err
) {
551 MessageDialog
msg (_("There are no more JACK ports available."));
558 io
->add_output_port ("", this);
561 catch (AudioEngine::PortRegistrationFailure
& err
) {
562 MessageDialog
msg (_("There are no more JACK ports available."));
567 set_button_sensitivity ();
571 IOSelector::remove_port ()
575 // always remove last port
578 if ((nports
= io
->n_inputs()) > 0) {
579 io
->remove_input_port (io
->input(nports
-1), this);
583 if ((nports
= io
->n_outputs()) > 0) {
584 io
->remove_output_port (io
->output(nports
-1), this);
588 set_button_sensitivity ();
592 IOSelector::connection_button_release (GdkEventButton
*ev
, TreeView
*treeview
)
594 /* this handles button release on a port name row: i.e. a connection
595 between the named port and the port represented by the treeview.
598 Gtk::TreeModel::iterator iter
;
599 TreeModel::Path path
;
600 TreeViewColumn
* column
;
604 /* only handle button1 events here */
606 if (ev
->button
!= 1) {
610 if (!treeview
->get_path_at_pos ((int)ev
->x
, (int)ev
->y
, path
, column
, cellx
, celly
)) {
614 if ((iter
= treeview
->get_model()->get_iter (path
.to_string()))) {
617 ustring connected_port_name
= (*iter
)[port_display_columns
.full_name
];
618 Port
*port
= reinterpret_cast<Port
*> (treeview
->get_data (X_("port")));
621 Port
*p
= session
.engine().get_port_by_name (connected_port_name
);
622 p
->disable_metering();
623 io
->disconnect_input (port
, connected_port_name
, this);
625 io
->disconnect_output (port
, connected_port_name
, this);
633 IOSelector::select_next_treeview ()
635 slist
<TreeView
*>::iterator next
;
637 if (port_displays
.empty() || port_displays
.size() == 1) {
641 for (slist
<TreeView
*>::iterator i
= port_displays
.begin(); i
!= port_displays
.end(); ++i
) {
643 if ((*i
)->get_name() == "IOSelectorPortListSelected") {
647 if (i
== port_displays
.end()) {
648 select_treeview (port_displays
.front());
650 select_treeview (*i
);
659 IOSelector::select_treeview (TreeView
* tview
)
661 /* Gack. TreeView's don't respond visually to a change
662 in their state, so rename them to force a style
666 Glib::Mutex::Lock
lm (port_display_lock
);
667 Port
* port
= reinterpret_cast<Port
*> (tview
->get_data (X_("port")));
669 selected_port
= port
;
671 tview
->set_name ("IOSelectorPortListSelected");
672 tview
->queue_draw ();
674 /* ugly hack to force the column header button to change as well */
676 TreeViewColumn
* col
= tview
->get_column (0);
677 GtkTreeViewColumn
* ccol
= col
->gobj();
680 gtk_widget_set_name (ccol
->button
, "IOSelectorPortListSelected");
681 gtk_widget_queue_draw (ccol
->button
);
684 for (slist
<TreeView
*>::iterator i
= port_displays
.begin(); i
!= port_displays
.end(); ++i
) {
689 col
= (*i
)->get_column (0);
693 gtk_widget_set_name (ccol
->button
, "IOSelectorPortList");
694 gtk_widget_queue_draw (ccol
->button
);
697 (*i
)->set_name ("IOSelectorPortList");
701 selector_box
.show_all ();
705 IOSelector::redisplay ()
710 if (io
->input_maximum() != 0) {
714 if (io
->output_maximum() != 0) {
720 PortInsertUI::PortInsertUI (Session
& sess
, boost::shared_ptr
<PortInsert
> pi
)
722 , latency_button (_("Measure Latency"))
723 , input_selector (sess
, pi
, true)
724 , output_selector (sess
, pi
, false)
726 latency_hbox
.pack_start (latency_button
, false, false);
727 latency_hbox
.pack_start (latency_display
, false, false);
728 latency_frame
.add (latency_hbox
);
730 hbox
.pack_start (output_selector
, true, true);
731 hbox
.pack_start (input_selector
, true, true);
734 set_border_width (12);
736 pack_start (latency_frame
);
739 latency_button
.signal_toggled().connect (mem_fun (*this, &PortInsertUI::latency_button_toggled
));
743 PortInsertUI::check_latency_measurement ()
745 MTDM
* mtdm
= _pi
->mtdm ();
747 if (mtdm
->resolve () < 0) {
748 latency_display
.set_text (_("No signal detected"));
752 if (mtdm
->err () > 0.3) {
758 nframes_t sample_rate
= input_selector
.session
.engine().frame_rate();
760 if (sample_rate
== 0) {
761 latency_display
.set_text (_("Disconnected from audio engine"));
762 _pi
->stop_latency_detection ();
766 snprintf (buf
, sizeof (buf
), "%10.3lf frames %10.3lf ms", mtdm
->del (), mtdm
->del () * 1000.0f
/sample_rate
);
770 if (mtdm
->err () > 0.2) {
776 strcat (buf
, " (Inv)");
781 _pi
->set_measured_latency ((nframes_t
) rint (mtdm
->del()));
782 strcat (buf
, " (set)");
785 latency_display
.set_text (buf
);
790 PortInsertUI::latency_button_toggled ()
792 if (latency_button
.get_active ()) {
794 _pi
->start_latency_detection ();
795 latency_display
.set_text (_("Detecting ..."));
796 latency_timeout
= Glib::signal_timeout().connect (mem_fun (*this, &PortInsertUI::check_latency_measurement
), 250);
799 _pi
->stop_latency_detection ();
800 latency_timeout
.disconnect ();
805 PortInsertUI::redisplay()
807 input_selector
.redisplay();
808 output_selector
.redisplay();
812 PortInsertUI::finished(IOSelector::Result r
)
814 input_selector
.Finished (r
);
815 output_selector
.Finished (r
);
819 PortInsertWindow::PortInsertWindow (Session
& sess
, boost::shared_ptr
<PortInsert
> pi
, bool can_cancel
)
820 : ArdourDialog ("port insert dialog"),
821 _portinsertui (sess
, pi
),
822 ok_button (can_cancel
? _("OK"): _("Close")),
823 cancel_button (_("Cancel")),
824 rescan_button (_("Rescan"))
827 set_name ("IOSelectorWindow");
828 string title
= _("ardour: ");
832 ok_button
.set_name ("IOSelectorButton");
833 cancel_button
.set_name ("IOSelectorButton");
834 rescan_button
.set_name ("IOSelectorButton");
836 button_box
.set_spacing (5);
837 button_box
.set_border_width (5);
838 button_box
.set_homogeneous (true);
839 button_box
.pack_start (rescan_button
);
841 button_box
.pack_start (cancel_button
);
844 cancel_button
.hide();
846 button_box
.pack_start (ok_button
);
848 get_vbox()->pack_start (_portinsertui
);
849 get_vbox()->pack_start (button_box
, false, false);
851 ok_button
.signal_clicked().connect (mem_fun(*this, &PortInsertWindow::accept
));
852 cancel_button
.signal_clicked().connect (mem_fun(*this, &PortInsertWindow::cancel
));
853 rescan_button
.signal_clicked().connect (mem_fun(*this, &PortInsertWindow::rescan
));
855 signal_delete_event().connect (bind (sigc::ptr_fun (just_hide_it
), reinterpret_cast<Window
*> (this)));
857 going_away_connection
= pi
->GoingAway
.connect (mem_fun(*this, &PortInsertWindow::plugin_going_away
));
861 PortInsertWindow::plugin_going_away ()
863 ENSURE_GUI_THREAD(mem_fun(*this, &PortInsertWindow::plugin_going_away
));
864 going_away_connection
.disconnect ();
865 delete_when_idle (this);
869 PortInsertWindow::on_map ()
871 _portinsertui
.redisplay ();
877 PortInsertWindow::rescan ()
879 _portinsertui
.redisplay();
883 PortInsertWindow::cancel ()
885 _portinsertui
.finished(IOSelector::Cancelled
);
890 PortInsertWindow::accept ()
892 _portinsertui
.finished(IOSelector::Accepted
);