/* Copyright (C) 2002-2003 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. $Id$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include "ardour_message.h" #include "io_selector.h" #include "extra_bind.h" #include "keyboard.h" #include "gui_thread.h" #include "i18n.h" using namespace std; using namespace Gtk; using namespace SigC; using namespace ARDOUR; IOSelectorWindow::IOSelectorWindow (Session& sess, IO& ior, bool input, bool can_cancel) : ArdourDialog ("i/o selector"), _selector (sess, ior, input), ok_button (can_cancel ? _("OK"): _("Close")), cancel_button (_("Cancel")), rescan_button (_("Rescan")) { add_events (GDK_KEY_PRESS_MASK|GDK_KEY_RELEASE_MASK); set_name ("IOSelectorWindow"); string title; if (input) { title = compose(_("%1 input"), ior.name()); } else { title = compose(_("%1 output"), ior.name()); } ok_button.set_name ("IOSelectorButton"); cancel_button.set_name ("IOSelectorButton"); rescan_button.set_name ("IOSelectorButton"); button_box.set_spacing (5); button_box.set_border_width (5); button_box.set_homogeneous (true); button_box.pack_start (rescan_button); if (can_cancel) { button_box.pack_start (cancel_button); } else { cancel_button.hide(); } button_box.pack_start (ok_button); vbox.pack_start (_selector); vbox.pack_start (button_box, false, false); ok_button.clicked.connect (slot (*this, &IOSelectorWindow::accept)); cancel_button.clicked.connect (slot (*this, &IOSelectorWindow::cancel)); rescan_button.clicked.connect (slot (*this, &IOSelectorWindow::rescan)); set_title (title); set_position (GTK_WIN_POS_MOUSE); add (vbox); delete_event.connect (bind (slot (just_hide_it), reinterpret_cast (this))); } IOSelectorWindow::~IOSelectorWindow() { } void IOSelectorWindow::rescan () { _selector.redisplay (); } void IOSelectorWindow::cancel () { _selector.Finished(IOSelector::Cancelled); hide (); } void IOSelectorWindow::accept () { _selector.Finished(IOSelector::Accepted); hide (); } gint IOSelectorWindow::map_event_impl (GdkEventAny *ev) { _selector.redisplay (); return Window::map_event_impl (ev); } /************************* The IO Selector "widget" *************************/ IOSelector::IOSelector (Session& sess, IO& ior, bool input) : session (sess), io (ior), for_input (input), port_frame (for_input? _("Inputs") : _("Outputs")), add_port_button (for_input? _("Add Input") : _("Add Output")), remove_port_button (for_input? _("Remove Input") : _("Remove Output")), clear_connections_button (_("Disconnect All")) { selected_port = 0; notebook.set_name ("IOSelectorNotebook"); notebook.set_usize (-1, 125); clear_connections_button.set_name ("IOSelectorButton"); add_port_button.set_name ("IOSelectorButton"); remove_port_button.set_name ("IOSelectorButton"); selector_frame.set_name ("IOSelectorFrame"); port_frame.set_name ("IOSelectorFrame"); selector_frame.set_label (_("Available connections")); selector_button_box.set_spacing (5); selector_button_box.set_border_width (5); selector_box.set_spacing (5); selector_box.set_border_width (5); selector_box.pack_start (notebook); selector_box.pack_start (selector_button_box, false, false); selector_frame.add (selector_box); port_box.set_spacing (5); port_box.set_border_width (5); port_display_scroller.set_name ("IOSelectorNotebook"); port_display_scroller.set_border_width (0); port_display_scroller.set_usize (-1, 170); port_display_scroller.add_with_viewport (port_box); port_display_scroller.set_policy (GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); port_button_box.set_spacing (5); port_button_box.set_border_width (5); port_button_box.pack_start (add_port_button, false, false); if (for_input) { if (io.input_maximum() < 0 || io.input_maximum() > (int) io.n_inputs()) { add_port_button.set_sensitive (true); } else { add_port_button.set_sensitive (false); } } else { if (io.output_maximum() < 0 || io.output_maximum() > (int) io.n_outputs()) { add_port_button.set_sensitive (true); } else { add_port_button.set_sensitive (false); } } port_button_box.pack_start (remove_port_button, false, false); if (for_input) { if (io.input_minimum() < 0 || io.input_minimum() < (int) io.n_inputs()) { remove_port_button.set_sensitive (true); } else { remove_port_button.set_sensitive (false); } } else { if (io.output_minimum() < 0 || io.output_minimum() < (int) io.n_outputs()) { remove_port_button.set_sensitive (true); } else { remove_port_button.set_sensitive (false); } } port_button_box.pack_start (clear_connections_button, false, false); port_and_button_box.set_border_width (5); port_and_button_box.pack_start (port_button_box, false, false); port_and_button_box.pack_start (port_display_scroller); port_frame.add (port_and_button_box); port_and_selector_box.set_spacing (5); port_and_selector_box.pack_start (port_frame); port_and_selector_box.pack_start (selector_frame); set_spacing (5); set_border_width (5); pack_start (port_and_selector_box); rescan(); display_ports (); clear_connections_button.clicked.connect (slot (*this, &IOSelector::clear_connections)); add_port_button.clicked.connect (slot (*this, &IOSelector::add_port)); remove_port_button.clicked.connect (slot (*this, &IOSelector::remove_port)); if (for_input) { io.input_changed.connect (slot (*this, &IOSelector::ports_changed)); } else { io.output_changed.connect (slot (*this, &IOSelector::ports_changed)); } io.name_changed.connect (slot (*this, &IOSelector::name_changed)); } IOSelector::~IOSelector () { } void IOSelector::name_changed (void* src) { ENSURE_GUI_THREAD(bind (slot (*this, &IOSelector::name_changed), src)); display_ports (); } void IOSelector::clear_connections () { if (for_input) { io.disconnect_inputs (this); } else { io.disconnect_outputs (this); } } void IOSelector::rescan () { using namespace Notebook_Helpers; using namespace CList_Helpers; typedef map > > PortMap; PortMap portmap; const char **ports; PageList& pages = notebook.pages(); gint current_page; vector rowdata; current_page = notebook.get_current_page_num (); pages.clear (); /* get relevant current JACK ports */ ports = session.engine().get_ports ("", JACK_DEFAULT_AUDIO_TYPE, for_input?JackPortIsOutput:JackPortIsInput); if (ports == 0) { return; } /* find all the client names and group their ports into a list-by-client */ for (int n = 0; ports[n]; ++n) { pair > > newpair; pair strpair; pair result; string str = ports[n]; string::size_type pos; string portname; pos = str.find (':'); newpair.first = str.substr (0, pos); portname = str.substr (pos+1); /* this may or may not succeed at actually inserting. we don't care, however: we just want an iterator that gives us either the inserted element or the existing one with the same name. */ result = portmap.insert (newpair); strpair.first = portname; strpair.second = str; result.first->second.push_back (strpair); } PortMap::iterator i; for (i = portmap.begin(); i != portmap.end(); ++i) { Box *client_box = manage (new VBox); Gtk::CList *client_port_display = manage (new Gtk::CList (1)); ScrolledWindow *scroller = manage (new ScrolledWindow); scroller->add_with_viewport (*client_port_display); scroller->set_policy (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); client_box->pack_start (*scroller); client_port_display->set_selection_mode (GTK_SELECTION_BROWSE); client_port_display->set_name ("IOSelectorList"); for (vector >::iterator s = i->second.begin(); s != i->second.end(); ++s) { rowdata.clear (); rowdata.push_back (s->first); client_port_display->rows().push_back (rowdata); client_port_display->rows().back().set_data (g_strdup (s->second.c_str()), free); } client_port_display->columns_autosize (); client_port_display->select_row.connect (bind (slot (*this, &IOSelector::port_selection_handler), client_port_display)); Label *tab_label = manage (new Label); tab_label->set_name ("IOSelectorNotebookTab"); tab_label->set_text ((*i).first); pages.push_back (TabElem (*client_box, *tab_label)); } notebook.set_page (current_page); notebook.show.connect (bind (slot (notebook, &Notebook::set_page), current_page)); selector_box.show_all (); } void IOSelector::display_ports () { CList *clist = 0; CList *firstclist = 0; CList *selected_port_list = 0; { LockMonitor lm (port_display_lock, __LINE__, __FILE__); Port *port; uint32_t limit; if (for_input) { limit = io.n_inputs(); } else { limit = io.n_outputs(); } for (slist::iterator i = port_displays.begin(); i != port_displays.end(); ) { slist::iterator tmp; tmp = i; ++tmp; port_box.remove (**i); delete *i; port_displays.erase (i); i = tmp; } for (uint32_t n = 0; n < limit; ++n) { const gchar *title[1]; string really_short_name; if (for_input) { port = io.input (n); } else { port = io.output (n); } /* we know there is '/' because we put it there */ really_short_name = port->short_name(); really_short_name = really_short_name.substr (really_short_name.find ('/') + 1); title[0] = really_short_name.c_str(); clist = new CList (1, title); if (!firstclist) { firstclist = clist; } port_displays.insert (port_displays.end(), clist); port_box.pack_start (*clist); clist->set_data (_("port"), port); /* XXX THIS IS A DIGUSTING AND DIRTY HACK, FORCED UPON US BECAUSE GtkCList DOESN'T PROVIDE ANY WAY TO CONNECT TO BUTTON_PRESS_EVENTS FOR THE COLUMN TITLE BUTTON. */ clist->column(0).get_widget(); // force the column title button to be created GtkButton *b = GTK_BUTTON(clist->gtkobj()->column[0].button); // no API to access this Gtk::Button *B = wrap (b); // make C++ signal handling easier. clist->column_titles_show (); clist->column_titles_active (); if (for_input) { if (io.input_maximum() == 1) { selected_port = port; selected_port_list = clist; } else { if (port == selected_port) { selected_port_list = clist; } } B->button_release_event.connect (bind (slot (*this, &IOSelector::port_column_button_release), clist)); } else { if (io.output_maximum() == 1) { selected_port = port; selected_port_list = clist; } else { if (port == selected_port) { selected_port_list = clist; } } B->button_release_event.connect (bind (slot (*this, &IOSelector::port_column_button_release), clist)); } clist->set_name ("IOSelectorPortList"); clist->set_selection_mode (GTK_SELECTION_SINGLE); clist->set_shadow_type (GTK_SHADOW_IN); clist->set_usize (-1, 75); /* now fill the clist with the current connections */ const char **connections = port->get_connections (); if (connections) { for (uint32_t c = 0; connections[c]; ++c) { const gchar *txt[1]; txt[0] = connections[c]; clist->rows().push_back (txt); } free (connections); } clist->columns_autosize (); clist->button_release_event.connect (bind (slot (*this, &IOSelector::connection_click), clist)); } port_box.show_all (); if (selected_port_list) { selected_port_list->click_column(0); selected_port_list->set_name ("IOSelectorPortListSelected"); for (slist::iterator i = port_displays.begin(); i != port_displays.end(); ++i) { if (*i != selected_port_list) { (*i)->set_name ("IOSelectorPortList"); (*i)->queue_draw (); } } } else { selected_port = 0; selector_box.hide_all (); } } if (selected_port_list) { select_clist (selected_port_list); } else if (firstclist) { // select first select_clist (firstclist); } } void IOSelector::port_selection_handler (gint row, gint col, GdkEvent *ev, Gtk::CList *clist) { using namespace CList_Helpers; int status; if (selected_port == 0) { return; } string other_port_name = (char *) clist->rows()[row].get_data(); if (for_input) { if ((status = io.connect_input (selected_port, other_port_name, this)) == 0) { Port *p = session.engine().get_port_by_name (other_port_name); p->enable_metering(); } } else { status = io.connect_output (selected_port, other_port_name, this); } if (status == 0) { select_next_clist (); } } void IOSelector::ports_changed (IOChange change, void *src) { ENSURE_GUI_THREAD(bind (slot (*this, &IOSelector::ports_changed), change, src)); display_ports (); } void IOSelector::add_port () { /* add a new port, then hide the button if we're up to the maximum allowed */ if (for_input) { try { io.add_input_port ("", this); } catch (AudioEngine::PortRegistrationFailure& err) { ArdourMessage msg (0, X_("noport dialog"), _("There are no more JACK ports available.")); } if (io.input_maximum() >= 0 && io.input_maximum() <= (int) io.n_inputs()) { add_port_button.set_sensitive (false); } if (io.input_minimum() < (int) io.n_inputs()) { remove_port_button.set_sensitive (true); } } else { try { io.add_output_port ("", this); } catch (AudioEngine::PortRegistrationFailure& err) { ArdourMessage msg (0, X_("noport dialog"), _("There are no more JACK ports available.")); } if (io.output_maximum() >= 0 && io.output_maximum() <= (int) io.n_outputs()) { add_port_button.set_sensitive (false); } } } void IOSelector::remove_port () { // always remove last port uint32_t nports; if (for_input) { if ((nports = io.n_inputs()) > 0) { io.remove_input_port (io.input(nports-1), this); } if (io.input_minimum() == (int) io.n_inputs()) { remove_port_button.set_sensitive (false); } } else { if ((nports = io.n_outputs()) > 0) { io.remove_output_port (io.output(nports-1), this); } } } gint IOSelector::remove_port_when_idle (Port *port) { if (for_input) { io.remove_input_port (port, this); } else { io.remove_output_port (port, this); } return FALSE; } gint IOSelector::port_column_button_release (GdkEventButton *event, CList *clist) { if (Keyboard::is_delete_event (event)) { Port* port; { LockMonitor lm (port_display_lock, __LINE__, __FILE__); port = reinterpret_cast (clist->get_data (_("port"))); if (port == selected_port) { selected_port = 0; clist->set_name ("IOSelectorPortList"); clist->queue_draw(); } } /* remove the port when idle - if we do it here, we will destroy the widget for whom we are handling an event. not good. */ Gtk::Main::idle.connect (bind (slot (*this, &IOSelector::remove_port_when_idle), port)); } else { select_clist(clist); } return TRUE; } void IOSelector::select_next_clist () { slist::iterator next; for (slist::iterator i = port_displays.begin(); i != port_displays.end(); ++i) { if ((*i)->get_name() == "IOSelectorPortListSelected") { ++i; if (i == port_displays.end()) { select_clist (port_displays.front()); } else { select_clist (*i); } break; } } } void IOSelector::select_clist(Gtk::CList* clist) { /* Gack. CList's don't respond visually to a change in their state, so rename them to force a style switch. */ LockMonitor lm (port_display_lock, __LINE__, __FILE__); Port* port = reinterpret_cast (clist->get_data (_("port"))); if (port != selected_port) { selected_port = port; clist->set_name ("IOSelectorPortListSelected"); for (slist::iterator i = port_displays.begin(); i != port_displays.end(); ++i) { if (*i != clist) { (*i)->set_name ("IOSelectorPortList"); (*i)->queue_draw (); } } selector_box.show_all (); } } gint IOSelector::connection_click (GdkEventButton *ev, CList *clist) { gint row, col; /* only handle button1 events here */ if (ev->button != 1) { return FALSE; } if (clist->get_selection_info ((int)ev->x, (int)ev->y, &row, &col) == 0) { return FALSE; } if (row < 0 || col < 0) { return FALSE; } Port *port = reinterpret_cast (clist->get_data (_("port"))); string connected_port_name = clist->cell(row,col).get_text (); if (for_input) { Port *p = session.engine().get_port_by_name (connected_port_name); p->disable_metering(); io.disconnect_input (port, connected_port_name, this); } else { io.disconnect_output (port, connected_port_name, this); } return TRUE; } void IOSelector::redisplay () { display_ports (); if (for_input) { if (io.input_maximum() != 0) { rescan (); } } else { if (io.output_maximum() != 0) { rescan(); } } } PortInsertUI::PortInsertUI (Session& sess, PortInsert& pi) : input_selector (sess, pi, true), output_selector (sess, pi, false) { hbox.pack_start (output_selector, true, true); hbox.pack_start (input_selector, true, true); pack_start (hbox); } void PortInsertUI::redisplay() { input_selector.redisplay(); output_selector.redisplay(); } void PortInsertUI::finished(IOSelector::Result r) { input_selector.Finished (r); output_selector.Finished (r); } PortInsertWindow::PortInsertWindow (Session& sess, PortInsert& pi, bool can_cancel) : ArdourDialog ("port insert dialog"), _portinsertui(sess, pi), ok_button (can_cancel ? _("OK"): _("Close")), cancel_button (_("Cancel")), rescan_button (_("Rescan")) { set_name ("IOSelectorWindow"); string title = _("ardour: "); title += pi.name(); set_title (title); ok_button.set_name ("IOSelectorButton"); cancel_button.set_name ("IOSelectorButton"); rescan_button.set_name ("IOSelectorButton"); button_box.set_spacing (5); button_box.set_border_width (5); button_box.set_homogeneous (true); button_box.pack_start (rescan_button); if (can_cancel) { button_box.pack_start (cancel_button); } else { cancel_button.hide(); } button_box.pack_start (ok_button); vbox.pack_start (_portinsertui); vbox.pack_start (button_box, false, false); add (vbox); ok_button.clicked.connect (slot (*this, &PortInsertWindow::accept)); cancel_button.clicked.connect (slot (*this, &PortInsertWindow::cancel)); rescan_button.clicked.connect (slot (*this, &PortInsertWindow::rescan)); delete_event.connect (bind (slot (just_hide_it), reinterpret_cast (this))); pi.GoingAway.connect (slot (*this, &PortInsertWindow::plugin_going_away)); } void PortInsertWindow::plugin_going_away (ARDOUR::Redirect* ignored) { ENSURE_GUI_THREAD(bind (slot (*this, &PortInsertWindow::plugin_going_away), ignored)); delete_when_idle (this); } gint PortInsertWindow::map_event_impl (GdkEventAny *ev) { _portinsertui.redisplay (); return Window::map_event_impl (ev); } void PortInsertWindow::rescan () { _portinsertui.redisplay(); } void PortInsertWindow::cancel () { _portinsertui.finished(IOSelector::Cancelled); hide (); } void PortInsertWindow::accept () { _portinsertui.finished(IOSelector::Accepted); hide (); }