diff options
author | Taybin Rutkin <taybin@taybin.com> | 2005-09-25 18:42:24 +0000 |
---|---|---|
committer | Taybin Rutkin <taybin@taybin.com> | 2005-09-25 18:42:24 +0000 |
commit | 209d967b1bb80a9735d690d8f4f0455ecb9970ca (patch) | |
tree | 9d76ddcd7c1ac9d91bb2b1a33d31b66ce4ded5de /gtk2_ardour/io_selector.cc | |
parent | e4b9aed743fc765219ac775905a221c017c88fba (diff) |
Initial import of gtk2_ardour.
git-svn-id: svn://localhost/trunk/ardour2@24 d708f5d6-7413-0410-9779-e7cbd77b26cf
Diffstat (limited to 'gtk2_ardour/io_selector.cc')
-rw-r--r-- | gtk2_ardour/io_selector.cc | 857 |
1 files changed, 857 insertions, 0 deletions
diff --git a/gtk2_ardour/io_selector.cc b/gtk2_ardour/io_selector.cc new file mode 100644 index 0000000000..0d013c72f4 --- /dev/null +++ b/gtk2_ardour/io_selector.cc @@ -0,0 +1,857 @@ +/* + 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 <map> +#include <vector> + +#include <pbd/lockmonitor.h> + +#include <ardour/io.h> +#include <ardour/route.h> +#include <ardour/audioengine.h> +#include <ardour/port.h> +#include <ardour/insert.h> +#include <ardour/session.h> +#include <ardour/diskstream.h> + +#include <gtkmmext/doi.h> +#include <gtkmmext/gtk_ui.h> + +#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<Window *> (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<string,vector<pair<string,string> > > PortMap; + PortMap portmap; + const char **ports; + PageList& pages = notebook.pages(); + gint current_page; + vector<string> 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<string,vector<pair<string,string> > > newpair; + pair<string,string> strpair; + pair<PortMap::iterator,bool> 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<pair<string,string> >::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<CList *>::iterator i = port_displays.begin(); i != port_displays.end(); ) { + + slist<CList *>::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<CList *>::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<Port *> (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<CList*>::iterator next; + + for (slist<CList *>::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<Port *> (clist->get_data (_("port"))); + + if (port != selected_port) { + selected_port = port; + + clist->set_name ("IOSelectorPortListSelected"); + + for (slist<CList *>::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<Port *> (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<Window *> (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 (); +} |