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/insert.h>
34 #include <ardour/session.h>
35 #include <ardour/audio_diskstream.h>
37 #include <gtkmm2ext/doi.h>
38 #include <gtkmm2ext/gtk_ui.h>
39 #include <gtkmm2ext/utils.h>
42 #include "io_selector.h"
44 #include "gui_thread.h"
52 using namespace ARDOUR
;
54 using namespace Gtkmm2ext
;
56 IOSelectorWindow::IOSelectorWindow (Session
& sess
, boost::shared_ptr
<IO
> ior
, bool input
, bool can_cancel
)
57 : ArdourDialog ("i/o selector"),
58 _selector (sess
, ior
, input
),
59 ok_button (can_cancel
? _("OK"): _("Close")),
60 cancel_button (_("Cancel")),
61 rescan_button (_("Rescan"))
64 add_events (Gdk::KEY_PRESS_MASK
|Gdk::KEY_RELEASE_MASK
);
65 set_name ("IOSelectorWindow");
69 title
= string_compose(_("%1 input"), ior
->name());
71 title
= string_compose(_("%1 output"), ior
->name());
74 ok_button
.set_name ("IOSelectorButton");
75 cancel_button
.set_name ("IOSelectorButton");
76 rescan_button
.set_name ("IOSelectorButton");
78 button_box
.set_spacing (5);
79 button_box
.set_border_width (5);
80 button_box
.set_homogeneous (true);
81 button_box
.pack_start (rescan_button
);
84 button_box
.pack_start (cancel_button
);
89 button_box
.pack_start (ok_button
);
91 get_vbox()->pack_start (_selector
);
92 get_vbox()->pack_start (button_box
, false, false);
94 ok_button
.signal_clicked().connect (mem_fun(*this, &IOSelectorWindow::accept
));
95 cancel_button
.signal_clicked().connect (mem_fun(*this, &IOSelectorWindow::cancel
));
96 rescan_button
.signal_clicked().connect (mem_fun(*this, &IOSelectorWindow::rescan
));
99 set_position (WIN_POS_MOUSE
);
101 signal_delete_event().connect (bind (sigc::ptr_fun (just_hide_it
), reinterpret_cast<Window
*> (this)));
104 IOSelectorWindow::~IOSelectorWindow()
109 IOSelectorWindow::rescan ()
111 _selector
.redisplay ();
115 IOSelectorWindow::cancel ()
117 _selector
.Finished(IOSelector::Cancelled
);
122 IOSelectorWindow::accept ()
124 _selector
.Finished(IOSelector::Accepted
);
129 IOSelectorWindow::on_map ()
131 _selector
.redisplay ();
135 /*************************
136 The IO Selector "widget"
137 *************************/
139 IOSelector::IOSelector (Session
& sess
, boost::shared_ptr
<IO
> ior
, bool input
)
143 port_frame (for_input
? _("Inputs") : _("Outputs")),
144 add_port_button (for_input
? _("Add Input") : _("Add Output")),
145 remove_port_button (for_input
? _("Remove Input") : _("Remove Output")),
146 clear_connections_button (_("Disconnect All"))
150 notebook
.set_name ("IOSelectorNotebook");
151 notebook
.set_size_request (-1, 125);
153 clear_connections_button
.set_name ("IOSelectorButton");
154 add_port_button
.set_name ("IOSelectorButton");
155 remove_port_button
.set_name ("IOSelectorButton");
157 selector_frame
.set_name ("IOSelectorFrame");
158 port_frame
.set_name ("IOSelectorFrame");
160 selector_frame
.set_label (_("Available connections"));
162 selector_button_box
.set_spacing (5);
163 selector_button_box
.set_border_width (5);
165 selector_box
.set_spacing (5);
166 selector_box
.set_border_width (5);
167 selector_box
.pack_start (notebook
);
168 selector_box
.pack_start (selector_button_box
, false, false);
170 selector_frame
.add (selector_box
);
172 port_box
.set_spacing (5);
173 port_box
.set_border_width (5);
175 port_display_scroller
.set_name ("IOSelectorNotebook");
176 port_display_scroller
.set_border_width (0);
177 port_display_scroller
.set_size_request (-1, 170);
178 port_display_scroller
.add (port_box
);
179 port_display_scroller
.set_policy (POLICY_NEVER
,
182 port_button_box
.set_spacing (5);
183 port_button_box
.set_border_width (5);
185 port_button_box
.pack_start (add_port_button
, false, false);
186 port_button_box
.pack_start (remove_port_button
, false, false);
187 port_button_box
.pack_start (clear_connections_button
, false, false);
189 port_and_button_box
.set_border_width (5);
190 port_and_button_box
.pack_start (port_button_box
, false, false);
191 port_and_button_box
.pack_start (port_display_scroller
);
193 port_frame
.add (port_and_button_box
);
195 port_and_selector_box
.set_spacing (5);
196 port_and_selector_box
.pack_start (port_frame
);
197 port_and_selector_box
.pack_start (selector_frame
);
200 set_border_width (5);
201 pack_start (port_and_selector_box
);
206 clear_connections_button
.signal_clicked().connect (mem_fun(*this, &IOSelector::clear_connections
));
208 add_port_button
.signal_clicked().connect (mem_fun(*this, &IOSelector::add_port
));
209 remove_port_button
.signal_clicked().connect (mem_fun(*this, &IOSelector::remove_port
));
212 io
->input_changed
.connect (mem_fun(*this, &IOSelector::ports_changed
));
214 io
->output_changed
.connect (mem_fun(*this, &IOSelector::ports_changed
));
217 set_button_sensitivity ();
219 io
->name_changed
.connect (mem_fun(*this, &IOSelector::name_changed
));
222 IOSelector::~IOSelector ()
227 IOSelector::set_button_sensitivity ()
231 if (io
->input_maximum() < 0 || io
->input_maximum() > (int) io
->n_inputs()) {
232 add_port_button
.set_sensitive (true);
234 add_port_button
.set_sensitive (false);
239 if (io
->output_maximum() < 0 || io
->output_maximum() > (int) io
->n_outputs()) {
240 add_port_button
.set_sensitive (true);
242 add_port_button
.set_sensitive (false);
248 if (io
->n_inputs() && (io
->input_minimum() < 0 || io
->input_minimum() < (int) io
->n_inputs())) {
249 remove_port_button
.set_sensitive (true);
251 remove_port_button
.set_sensitive (false);
255 if (io
->n_outputs() && (io
->output_minimum() < 0 || io
->output_minimum() < (int) io
->n_outputs())) {
256 remove_port_button
.set_sensitive (true);
258 remove_port_button
.set_sensitive (false);
265 IOSelector::name_changed (void* src
)
267 ENSURE_GUI_THREAD(bind (mem_fun(*this, &IOSelector::name_changed
), src
));
273 IOSelector::clear_connections ()
276 io
->disconnect_inputs (this);
278 io
->disconnect_outputs (this);
283 IOSelector::rescan ()
285 using namespace Notebook_Helpers
;
287 typedef std::map
<string
,vector
<pair
<string
,string
> > > PortMap
;
290 PageList
& pages
= notebook
.pages();
292 vector
<string
> rowdata
;
294 page_selection_connection
.disconnect ();
296 current_page
= notebook
.get_current_page ();
300 /* get relevant current JACK ports */
302 ports
= session
.engine().get_ports ("", JACK_DEFAULT_AUDIO_TYPE
, for_input
? JackPortIsOutput
: JackPortIsInput
);
308 /* find all the client names and group their ports into a list-by-client */
310 for (int n
= 0; ports
[n
]; ++n
) {
312 pair
<string
,vector
<pair
<string
,string
> > > newpair
;
313 pair
<string
,string
> strpair
;
314 pair
<PortMap::iterator
,bool> result
;
316 string str
= ports
[n
];
317 string::size_type pos
;
320 pos
= str
.find (':');
322 newpair
.first
= str
.substr (0, pos
);
323 portname
= str
.substr (pos
+1);
325 result
= portmap
.insert (newpair
);
327 strpair
.first
= portname
;
328 strpair
.second
= str
;
330 result
.first
->second
.push_back (strpair
);
335 for (i
= portmap
.begin(); i
!= portmap
.end(); ++i
) {
337 Box
*client_box
= manage (new VBox
);
338 TreeView
*display
= manage (new TreeView
);
339 RefPtr
<ListStore
> model
= ListStore::create (port_display_columns
);
340 ScrolledWindow
*scroller
= manage (new ScrolledWindow
);
342 display
->set_model (model
);
343 display
->append_column (X_("notvisible"), port_display_columns
.displayed_name
);
344 display
->set_headers_visible (false);
345 display
->get_selection()->set_mode (SELECTION_SINGLE
);
346 display
->set_name ("IOSelectorList");
348 for (vector
<pair
<string
,string
> >::iterator s
= i
->second
.begin(); s
!= i
->second
.end(); ++s
) {
350 TreeModel::Row row
= *(model
->append ());
352 row
[port_display_columns
.displayed_name
] = s
->first
;
353 row
[port_display_columns
.full_name
] = s
->second
;
356 display
->signal_button_release_event().connect (bind (mem_fun(*this, &IOSelector::port_selection_changed
), display
));
357 Label
*tab_label
= manage (new Label
);
359 tab_label
->set_name ("IOSelectorNotebookTab");
360 tab_label
->set_text ((*i
).first
);
362 scroller
->add (*display
);
363 scroller
->set_policy (POLICY_AUTOMATIC
, POLICY_AUTOMATIC
);
365 client_box
->pack_start (*scroller
);
367 pages
.push_back (TabElem (*client_box
, *tab_label
));
370 notebook
.set_current_page (current_page
);
371 page_selection_connection
= notebook
.signal_show().connect (bind (mem_fun (notebook
, &Notebook::set_current_page
), current_page
));
372 selector_box
.show_all ();
376 IOSelector::display_ports ()
378 TreeView
*firsttview
= 0;
379 TreeView
*selected_port_tview
= 0;
381 Glib::Mutex::Lock
lm (port_display_lock
);
386 limit
= io
->n_inputs();
388 limit
= io
->n_outputs();
391 for (slist
<TreeView
*>::iterator i
= port_displays
.begin(); i
!= port_displays
.end(); ) {
393 slist
<TreeView
*>::iterator tmp
;
398 port_box
.remove (**i
);
400 port_displays
.erase (i
);
405 for (uint32_t n
= 0; n
< limit
; ++n
) {
408 //ScrolledWindow *scroller;
409 string really_short_name
;
412 port
= io
->input (n
);
414 port
= io
->output (n
);
417 /* we know there is '/' because we put it there */
419 really_short_name
= port
->short_name();
420 really_short_name
= really_short_name
.substr (really_short_name
.find ('/') + 1);
422 tview
= manage (new TreeView());
423 RefPtr
<ListStore
> port_model
= ListStore::create (port_display_columns
);
429 tview
->set_model (port_model
);
430 tview
->append_column (really_short_name
, port_display_columns
.displayed_name
);
431 tview
->get_selection()->set_mode (SELECTION_SINGLE
);
432 tview
->set_data (X_("port"), port
);
433 tview
->set_headers_visible (true);
434 tview
->set_name (X_("IOSelectorPortList"));
436 port_box
.pack_start (*tview
);
437 port_displays
.insert (port_displays
.end(), tview
);
439 /* now fill the clist with the current connections */
441 const char **connections
= port
->get_connections ();
444 for (uint32_t c
= 0; connections
[c
]; ++c
) {
445 TreeModel::Row row
= *(port_model
->append());
446 row
[port_display_columns
.displayed_name
] = connections
[c
];
447 row
[port_display_columns
.full_name
] = connections
[c
];
453 if (io
->input_maximum() == 1) {
454 selected_port
= port
;
455 selected_port_tview
= tview
;
457 if (port
== selected_port
) {
458 selected_port_tview
= tview
;
464 if (io
->output_maximum() == 1) {
465 selected_port
= port
;
466 selected_port_tview
= tview
;
468 if (port
== selected_port
) {
469 selected_port_tview
= tview
;
474 TreeViewColumn
* col
= tview
->get_column (0);
476 col
->set_clickable (true);
478 /* handle button events on the column header ... */
479 col
->signal_clicked().connect (bind (mem_fun(*this, &IOSelector::select_treeview
), tview
));
481 /* ... and within the treeview itself */
482 tview
->signal_button_release_event().connect (bind (mem_fun(*this, &IOSelector::connection_button_release
), tview
));
485 port_box
.show_all ();
488 if (!selected_port_tview
) {
489 selected_port_tview
= firsttview
;
492 if (selected_port_tview
) {
493 select_treeview (selected_port_tview
);
498 IOSelector::port_selection_changed (GdkEventButton
*ev
, TreeView
* treeview
)
500 TreeModel::iterator i
= treeview
->get_selection()->get_selected();
507 if (selected_port
== 0) {
511 ustring other_port_name
= (*i
)[port_display_columns
.full_name
];
514 if ((status
= io
->connect_input (selected_port
, other_port_name
, this)) == 0) {
515 Port
*p
= session
.engine().get_port_by_name (other_port_name
);
516 p
->enable_metering();
519 status
= io
->connect_output (selected_port
, other_port_name
, this);
523 select_next_treeview ();
526 treeview
->get_selection()->unselect_all();
531 IOSelector::ports_changed (IOChange change
, void *src
)
533 ENSURE_GUI_THREAD(bind (mem_fun(*this, &IOSelector::ports_changed
), change
, src
));
539 IOSelector::add_port ()
541 /* add a new port, then hide the button if we're up to the maximum allowed */
546 io
->add_input_port ("", this);
549 catch (AudioEngine::PortRegistrationFailure
& err
) {
550 MessageDialog
msg (_("There are no more JACK ports available."));
557 io
->add_output_port ("", this);
560 catch (AudioEngine::PortRegistrationFailure
& err
) {
561 MessageDialog
msg (_("There are no more JACK ports available."));
566 set_button_sensitivity ();
570 IOSelector::remove_port ()
574 // always remove last port
577 if ((nports
= io
->n_inputs()) > 0) {
578 io
->remove_input_port (io
->input(nports
-1), this);
582 if ((nports
= io
->n_outputs()) > 0) {
583 io
->remove_output_port (io
->output(nports
-1), this);
587 set_button_sensitivity ();
591 IOSelector::connection_button_release (GdkEventButton
*ev
, TreeView
*treeview
)
593 /* this handles button release on a port name row: i.e. a connection
594 between the named port and the port represented by the treeview.
597 Gtk::TreeModel::iterator iter
;
598 TreeModel::Path path
;
599 TreeViewColumn
* column
;
603 /* only handle button1 events here */
605 if (ev
->button
!= 1) {
609 if (!treeview
->get_path_at_pos ((int)ev
->x
, (int)ev
->y
, path
, column
, cellx
, celly
)) {
613 if ((iter
= treeview
->get_model()->get_iter (path
.to_string()))) {
616 ustring connected_port_name
= (*iter
)[port_display_columns
.full_name
];
617 Port
*port
= reinterpret_cast<Port
*> (treeview
->get_data (X_("port")));
620 Port
*p
= session
.engine().get_port_by_name (connected_port_name
);
621 p
->disable_metering();
622 io
->disconnect_input (port
, connected_port_name
, this);
624 io
->disconnect_output (port
, connected_port_name
, this);
632 IOSelector::select_next_treeview ()
634 slist
<TreeView
*>::iterator next
;
636 if (port_displays
.empty() || port_displays
.size() == 1) {
640 for (slist
<TreeView
*>::iterator i
= port_displays
.begin(); i
!= port_displays
.end(); ++i
) {
642 if ((*i
)->get_name() == "IOSelectorPortListSelected") {
646 if (i
== port_displays
.end()) {
647 select_treeview (port_displays
.front());
649 select_treeview (*i
);
658 IOSelector::select_treeview (TreeView
* tview
)
660 /* Gack. TreeView's don't respond visually to a change
661 in their state, so rename them to force a style
665 Glib::Mutex::Lock
lm (port_display_lock
);
666 Port
* port
= reinterpret_cast<Port
*> (tview
->get_data (X_("port")));
668 selected_port
= port
;
670 tview
->set_name ("IOSelectorPortListSelected");
671 tview
->queue_draw ();
673 /* ugly hack to force the column header button to change as well */
675 TreeViewColumn
* col
= tview
->get_column (0);
676 GtkTreeViewColumn
* ccol
= col
->gobj();
679 gtk_widget_set_name (ccol
->button
, "IOSelectorPortListSelected");
680 gtk_widget_queue_draw (ccol
->button
);
683 for (slist
<TreeView
*>::iterator i
= port_displays
.begin(); i
!= port_displays
.end(); ++i
) {
688 col
= (*i
)->get_column (0);
692 gtk_widget_set_name (ccol
->button
, "IOSelectorPortList");
693 gtk_widget_queue_draw (ccol
->button
);
696 (*i
)->set_name ("IOSelectorPortList");
700 selector_box
.show_all ();
704 IOSelector::redisplay ()
709 if (io
->input_maximum() != 0) {
713 if (io
->output_maximum() != 0) {
719 PortInsertUI::PortInsertUI (Session
& sess
, boost::shared_ptr
<PortInsert
> pi
)
720 : input_selector (sess
, pi
, true),
721 output_selector (sess
, pi
, false)
723 hbox
.pack_start (output_selector
, true, true);
724 hbox
.pack_start (input_selector
, true, true);
731 PortInsertUI::redisplay()
734 input_selector
.redisplay();
735 output_selector
.redisplay();
739 PortInsertUI::finished(IOSelector::Result r
)
741 input_selector
.Finished (r
);
742 output_selector
.Finished (r
);
746 PortInsertWindow::PortInsertWindow (Session
& sess
, boost::shared_ptr
<PortInsert
> pi
, bool can_cancel
)
747 : ArdourDialog ("port insert dialog"),
748 _portinsertui (sess
, pi
),
749 ok_button (can_cancel
? _("OK"): _("Close")),
750 cancel_button (_("Cancel")),
751 rescan_button (_("Rescan"))
754 set_name ("IOSelectorWindow");
755 string title
= _("ardour: ");
759 ok_button
.set_name ("IOSelectorButton");
760 cancel_button
.set_name ("IOSelectorButton");
761 rescan_button
.set_name ("IOSelectorButton");
763 button_box
.set_spacing (5);
764 button_box
.set_border_width (5);
765 button_box
.set_homogeneous (true);
766 button_box
.pack_start (rescan_button
);
768 button_box
.pack_start (cancel_button
);
771 cancel_button
.hide();
773 button_box
.pack_start (ok_button
);
775 get_vbox()->pack_start (_portinsertui
);
776 get_vbox()->pack_start (button_box
, false, false);
778 ok_button
.signal_clicked().connect (mem_fun(*this, &PortInsertWindow::accept
));
779 cancel_button
.signal_clicked().connect (mem_fun(*this, &PortInsertWindow::cancel
));
780 rescan_button
.signal_clicked().connect (mem_fun(*this, &PortInsertWindow::rescan
));
782 signal_delete_event().connect (bind (sigc::ptr_fun (just_hide_it
), reinterpret_cast<Window
*> (this)));
784 going_away_connection
= pi
->GoingAway
.connect (mem_fun(*this, &PortInsertWindow::plugin_going_away
));
788 PortInsertWindow::plugin_going_away ()
790 ENSURE_GUI_THREAD(mem_fun(*this, &PortInsertWindow::plugin_going_away
));
791 going_away_connection
.disconnect ();
792 delete_when_idle (this);
796 PortInsertWindow::on_map ()
798 _portinsertui
.redisplay ();
804 PortInsertWindow::rescan ()
806 _portinsertui
.redisplay();
810 PortInsertWindow::cancel ()
812 _portinsertui
.finished(IOSelector::Cancelled
);
817 PortInsertWindow::accept ()
819 _portinsertui
.finished(IOSelector::Accepted
);