summaryrefslogtreecommitdiffstats
path: root/src/Patchage.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/Patchage.cpp')
-rw-r--r--src/Patchage.cpp1078
1 files changed, 1078 insertions, 0 deletions
diff --git a/src/Patchage.cpp b/src/Patchage.cpp
new file mode 100644
index 0000000..eae2ef9
--- /dev/null
+++ b/src/Patchage.cpp
@@ -0,0 +1,1078 @@
+/* This file is part of Patchage.
+ * Copyright 2007-2014 David Robillard <http://drobilla.net>
+ *
+ * Patchage 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 3 of the License, or (at your option)
+ * any later version.
+ *
+ * Patchage 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 details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Patchage. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <pthread.h>
+
+#include <cmath>
+#include <fstream>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gtk/gtkwindow.h>
+
+#include <boost/format.hpp>
+
+#include <gtkmm/button.h>
+#include <gtkmm/filechooserdialog.h>
+#include <gtkmm/liststore.h>
+#include <gtkmm/menuitem.h>
+#include <gtkmm/messagedialog.h>
+#include <gtkmm/stock.h>
+#include <gtkmm/treemodel.h>
+
+#include "ganv/Module.hpp"
+#include "ganv/Edge.hpp"
+
+#include "Configuration.hpp"
+#include "Legend.hpp"
+#include "Patchage.hpp"
+#include "PatchageCanvas.hpp"
+#include "PatchageEvent.hpp"
+#include "UIFile.hpp"
+#include "patchage_config.h"
+
+#if defined(HAVE_JACK_DBUS)
+ #include "JackDbusDriver.hpp"
+#elif defined(PATCHAGE_LIBJACK)
+ #include "JackDriver.hpp"
+ #include <jack/statistics.h>
+#endif
+
+#ifdef PATCHAGE_JACK_SESSION
+ #include <jack/session.h>
+#endif
+
+#ifdef HAVE_ALSA
+ #include "AlsaDriver.hpp"
+#endif
+
+#ifdef PATCHAGE_GTK_OSX
+ #include <gtkosxapplication.h>
+
+static gboolean
+can_activate_cb(GtkWidget* widget, guint signal_id, gpointer data)
+{
+ return gtk_widget_is_sensitive(widget);
+}
+
+static void
+terminate_cb(GtkosxApplication* app, gpointer data)
+{
+ Patchage* patchage = (Patchage*)data;
+ patchage->save();
+ Gtk::Main::quit();
+}
+
+#endif
+
+static bool
+configure_cb(GtkWindow* parentWindow, GdkEvent* event, gpointer data)
+{
+ ((Patchage*)data)->store_window_location();
+ return FALSE;
+}
+
+static int
+port_order(const GanvPort* a, const GanvPort* b, void* data)
+{
+ const PatchagePort* pa = dynamic_cast<const PatchagePort*>(Glib::wrap(a));
+ const PatchagePort* pb = dynamic_cast<const PatchagePort*>(Glib::wrap(b));
+ if (pa && pb) {
+ if (pa->order() && pb->order()) {
+ return *pa->order() - *pb->order();
+ } else if (pa->order()) {
+ return -1;
+ } else if (pb->order()) {
+ return 1;
+ }
+ return pa->name().compare(pb->name());
+ }
+ return 0;
+}
+
+struct ProjectList_column_record : public Gtk::TreeModel::ColumnRecord {
+ Gtk::TreeModelColumn<Glib::ustring> label;
+};
+
+using std::cout;
+using std::endl;
+using std::string;
+
+#define INIT_WIDGET(x) x(_xml, ((const char*)#x) + 1)
+
+Patchage::Patchage(int argc, char** argv)
+ : _xml(UIFile::open("patchage"))
+#ifdef HAVE_ALSA
+ , _alsa_driver(NULL)
+#endif
+ , _jack_driver(NULL)
+ , _conf(NULL)
+ , INIT_WIDGET(_about_win)
+ , INIT_WIDGET(_main_scrolledwin)
+ , INIT_WIDGET(_main_win)
+ , INIT_WIDGET(_main_vbox)
+ , INIT_WIDGET(_menubar)
+ , INIT_WIDGET(_menu_alsa_connect)
+ , INIT_WIDGET(_menu_alsa_disconnect)
+ , INIT_WIDGET(_menu_file_quit)
+ , INIT_WIDGET(_menu_export_image)
+ , INIT_WIDGET(_menu_help_about)
+ , INIT_WIDGET(_menu_jack_connect)
+ , INIT_WIDGET(_menu_jack_disconnect)
+ , INIT_WIDGET(_menu_open_session)
+ , INIT_WIDGET(_menu_save_session)
+ , INIT_WIDGET(_menu_save_close_session)
+ , INIT_WIDGET(_menu_view_arrange)
+ , INIT_WIDGET(_menu_view_sprung_layout)
+ , INIT_WIDGET(_menu_view_messages)
+ , INIT_WIDGET(_menu_view_toolbar)
+ , INIT_WIDGET(_menu_view_refresh)
+ , INIT_WIDGET(_menu_view_human_names)
+ , INIT_WIDGET(_menu_view_sort_ports)
+ , INIT_WIDGET(_menu_zoom_in)
+ , INIT_WIDGET(_menu_zoom_out)
+ , INIT_WIDGET(_menu_zoom_normal)
+ , INIT_WIDGET(_menu_zoom_full)
+ , INIT_WIDGET(_menu_increase_font_size)
+ , INIT_WIDGET(_menu_decrease_font_size)
+ , INIT_WIDGET(_menu_normal_font_size)
+ , INIT_WIDGET(_toolbar)
+ , INIT_WIDGET(_clear_load_but)
+ , INIT_WIDGET(_xrun_progress)
+ , INIT_WIDGET(_buf_size_combo)
+ , INIT_WIDGET(_latency_label)
+ , INIT_WIDGET(_legend_alignment)
+ , INIT_WIDGET(_main_paned)
+ , INIT_WIDGET(_log_scrolledwindow)
+ , INIT_WIDGET(_status_text)
+ , _legend(NULL)
+ , _pane_initialized(false)
+ , _attach(true)
+ , _driver_detached(false)
+ , _refresh(false)
+ , _enable_refresh(true)
+ , _jack_driver_autoattach(true)
+#ifdef HAVE_ALSA
+ , _alsa_driver_autoattach(true)
+#endif
+{
+ _conf = new Configuration();
+ _canvas = boost::shared_ptr<PatchageCanvas>(new PatchageCanvas(this, 1600*2, 1200*2));
+
+ while (argc > 0) {
+ if (!strcmp(*argv, "-h") || !strcmp(*argv, "--help")) {
+ cout << "Usage: patchage [OPTION]..." << endl;
+ cout << "Visually connect JACK and ALSA Audio/MIDI ports." << endl << endl;
+ cout << "Options:" << endl;
+ cout << "\t-h --help Show this help" << endl;
+ cout << "\t-A --no-alsa Do not automatically attach to ALSA" << endl;
+ cout << "\t-J --no-jack Do not automatically attack to JACK" << endl;
+ exit(0);
+#ifdef HAVE_ALSA
+ } else if (!strcmp(*argv, "-A") || !strcmp(*argv, "--no-alsa")) {
+ _alsa_driver_autoattach = false;
+#endif
+#if defined(PATCHAGE_LIBJACK) || defined(HAVE_JACK_DBUS)
+ } else if (!strcmp(*argv, "-J") || !strcmp(*argv, "--no-jack")) {
+ _jack_driver_autoattach = false;
+#endif
+ }
+
+ argv++;
+ argc--;
+ }
+
+ Glib::set_application_name("Patchage");
+ _about_win->property_program_name() = "Patchage";
+ _about_win->property_logo_icon_name() = "patchage";
+ gtk_window_set_default_icon_name("patchage");
+
+ // Create list model for buffer size selector
+ Glib::RefPtr<Gtk::ListStore> buf_size_store = Gtk::ListStore::create(_buf_size_columns);
+ for (size_t i = 32; i <= 4096; i *= 2) {
+ Gtk::TreeModel::Row row = *(buf_size_store->append());
+ row[_buf_size_columns.label] = std::to_string(i);
+ }
+
+ _buf_size_combo->set_model(buf_size_store);
+ _buf_size_combo->pack_start(_buf_size_columns.label);
+
+ _main_scrolledwin->add(_canvas->widget());
+
+ _main_scrolledwin->property_hadjustment().get_value()->set_step_increment(10);
+ _main_scrolledwin->property_vadjustment().get_value()->set_step_increment(10);
+
+ _main_scrolledwin->signal_scroll_event().connect(
+ sigc::mem_fun(this, &Patchage::on_scroll));
+ _clear_load_but->signal_clicked().connect(
+ sigc::mem_fun(this, &Patchage::clear_load));
+ _buf_size_combo->signal_changed().connect(
+ sigc::mem_fun(this, &Patchage::buffer_size_changed));
+ _status_text->signal_size_allocate().connect(
+ sigc::mem_fun(this, &Patchage::on_messages_resized));
+
+#ifdef PATCHAGE_JACK_SESSION
+ _menu_open_session->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::show_open_session_dialog));
+ _menu_save_session->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::show_save_session_dialog));
+ _menu_save_close_session->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::show_save_close_session_dialog));
+#else
+ _menu_open_session->hide();
+ _menu_save_session->hide();
+ _menu_save_close_session->hide();
+#endif
+
+#ifdef HAVE_ALSA
+ _menu_alsa_connect->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::menu_alsa_connect));
+ _menu_alsa_disconnect->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::menu_alsa_disconnect));
+#else
+ _menu_alsa_connect->set_sensitive(false);
+ _menu_alsa_disconnect->set_sensitive(false);
+#endif
+
+ _menu_file_quit->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::on_quit));
+ _menu_export_image->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::on_export_image));
+ _menu_view_refresh->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::refresh));
+ _menu_view_human_names->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::on_view_human_names));
+ _menu_view_sort_ports->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::on_view_sort_ports));
+ _menu_view_arrange->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::on_arrange));
+ _menu_view_sprung_layout->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::on_sprung_layout_toggled));
+ _menu_view_messages->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::on_view_messages));
+ _menu_view_toolbar->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::on_view_toolbar));
+ _menu_help_about->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::on_help_about));
+ _menu_zoom_in->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::on_zoom_in));
+ _menu_zoom_out->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::on_zoom_out));
+ _menu_zoom_normal->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::on_zoom_normal));
+ _menu_zoom_full->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::on_zoom_full));
+ _menu_increase_font_size->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::on_increase_font_size));
+ _menu_decrease_font_size->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::on_decrease_font_size));
+ _menu_normal_font_size->signal_activate().connect(
+ sigc::mem_fun(this, &Patchage::on_normal_font_size));
+
+ if (_canvas->supports_sprung_layout()) {
+ _menu_view_sprung_layout->set_active(true);
+ } else {
+ _menu_view_sprung_layout->set_active(false);
+ _menu_view_sprung_layout->set_sensitive(false);
+ }
+
+ for (int s = Gtk::STATE_NORMAL; s <= Gtk::STATE_INSENSITIVE; ++s) {
+ _status_text->modify_base((Gtk::StateType)s, Gdk::Color("#000000"));
+ _status_text->modify_text((Gtk::StateType)s, Gdk::Color("#FFFFFF"));
+ }
+
+ _error_tag = Gtk::TextTag::create();
+ _error_tag->property_foreground() = "#CC0000";
+ _status_text->get_buffer()->get_tag_table()->add(_error_tag);
+
+ _warning_tag = Gtk::TextTag::create();
+ _warning_tag->property_foreground() = "#C4A000";
+ _status_text->get_buffer()->get_tag_table()->add(_warning_tag);
+
+ _canvas->widget().show();
+ _main_win->present();
+
+ _conf->set_font_size(_canvas->get_default_font_size());
+ _conf->load();
+ _canvas->set_zoom(_conf->get_zoom());
+ _canvas->set_font_size(_conf->get_font_size());
+ if (_conf->get_sort_ports()) {
+ _canvas->set_port_order(port_order, NULL);
+ }
+
+ _main_win->resize(
+ static_cast<int>(_conf->get_window_size().x),
+ static_cast<int>(_conf->get_window_size().y));
+
+ _main_win->move(
+ static_cast<int>(_conf->get_window_location().x),
+ static_cast<int>(_conf->get_window_location().y));
+
+ _legend = new Legend(*_conf);
+ _legend->signal_color_changed.connect(
+ sigc::mem_fun(this, &Patchage::on_legend_color_change));
+ _legend_alignment->add(*Gtk::manage(_legend));
+ _legend->show_all();
+
+ _about_win->set_transient_for(*_main_win);
+#ifdef __APPLE__
+ try {
+ _about_win->set_logo(
+ Gdk::Pixbuf::create_from_file(
+ bundle_location() + "/Resources/Patchage.icns"));
+ } catch (const Glib::Exception& e) {
+ error_msg((boost::format("failed to set logo (%s)") % e.what()).str());
+ }
+#endif
+
+#if defined(PATCHAGE_LIBJACK) || defined(HAVE_JACK_DBUS)
+ _jack_driver = new JackDriver(this);
+ _jack_driver->signal_detached.connect(sigc::mem_fun(this, &Patchage::driver_detached));
+
+ _menu_jack_connect->signal_activate().connect(sigc::bind(
+ sigc::mem_fun(_jack_driver, &JackDriver::attach), true));
+ _menu_jack_disconnect->signal_activate().connect(
+ sigc::mem_fun(_jack_driver, &JackDriver::detach));
+#endif
+
+#ifdef HAVE_ALSA
+ _alsa_driver = new AlsaDriver(this);
+#endif
+
+ connect_widgets();
+ update_state();
+ _menu_view_toolbar->set_active(_conf->get_show_toolbar());
+ _menu_view_sprung_layout->set_active(_conf->get_sprung_layout());
+ _menu_view_sort_ports->set_active(_conf->get_sort_ports());
+ _status_text->set_pixels_inside_wrap(2);
+ _status_text->set_left_margin(4);
+ _status_text->set_right_margin(4);
+ _status_text->set_pixels_below_lines(2);
+
+ g_signal_connect(_main_win->gobj(), "configure-event",
+ G_CALLBACK(configure_cb), this);
+
+ _canvas->widget().grab_focus();
+
+ // Idle callback, check if we need to refresh
+ Glib::signal_timeout().connect(
+ sigc::mem_fun(this, &Patchage::idle_callback), 100);
+
+#ifdef PATCHAGE_GTK_OSX
+ // Set up Mac menu bar
+ GtkosxApplication* osxapp = (GtkosxApplication*)g_object_new(
+ GTKOSX_TYPE_APPLICATION, NULL);
+ _menubar->hide();
+ _menu_file_quit->hide();
+ gtkosx_application_set_menu_bar(osxapp, GTK_MENU_SHELL(_menubar->gobj()));
+ gtkosx_application_insert_app_menu_item(
+ osxapp, GTK_WIDGET(_menu_help_about->gobj()), 0);
+ g_signal_connect(_menubar->gobj(), "can-activate-accel",
+ G_CALLBACK(can_activate_cb), NULL);
+ g_signal_connect(osxapp, "NSApplicationWillTerminate",
+ G_CALLBACK(terminate_cb), this);
+ gtkosx_application_ready(osxapp);
+#endif
+}
+
+Patchage::~Patchage()
+{
+#if defined(PATCHAGE_LIBJACK) || defined(HAVE_JACK_DBUS)
+ delete _jack_driver;
+#endif
+#ifdef HAVE_ALSA
+ delete _alsa_driver;
+#endif
+
+ delete _conf;
+
+ _about_win.destroy();
+ _xml.reset();
+}
+
+void
+Patchage::attach()
+{
+ _enable_refresh = false;
+
+#if defined(PATCHAGE_LIBJACK) || defined(HAVE_JACK_DBUS)
+ if (_jack_driver_autoattach)
+ _jack_driver->attach(true);
+#endif
+
+#ifdef HAVE_ALSA
+ if (_alsa_driver_autoattach)
+ _alsa_driver->attach();
+#endif
+
+ _enable_refresh = true;
+
+ refresh();
+ update_toolbar();
+}
+
+bool
+Patchage::idle_callback()
+{
+ // Initial run, attach
+ if (_attach) {
+ attach();
+ _menu_view_messages->set_active(_conf->get_show_messages());
+ _attach = false;
+ }
+
+ // Process any JACK events
+#if defined(PATCHAGE_LIBJACK) || defined(HAVE_JACK_DBUS)
+ if (_jack_driver) {
+ _jack_driver->process_events(this);
+ }
+#endif
+
+ // Process any ALSA events
+#ifdef HAVE_ALSA
+ if (_alsa_driver) {
+ _alsa_driver->process_events(this);
+ }
+#endif
+
+ // Do a full refresh
+ if (_refresh) {
+ refresh();
+ } else if (_driver_detached) {
+#if defined(PATCHAGE_LIBJACK) || defined(HAVE_JACK_DBUS)
+ if (_jack_driver && !_jack_driver->is_attached())
+ _jack_driver->destroy_all();
+#endif
+#ifdef HAVE_ALSA
+ if (_alsa_driver && !_alsa_driver->is_attached())
+ _alsa_driver->destroy_all();
+#endif
+ }
+
+ _refresh = false;
+ _driver_detached = false;
+
+ // Update load every 5 idle callbacks
+ static int count = 0;
+ if (++count == 5) {
+ update_load();
+ count = 0;
+ }
+
+ return true;
+}
+
+void
+Patchage::update_toolbar()
+{
+ static bool updating = false;
+ if (updating) {
+ return;
+ } else {
+ updating = true;
+ }
+
+#if defined(PATCHAGE_LIBJACK) || defined(HAVE_JACK_DBUS)
+ if (_jack_driver->is_attached()) {
+ const jack_nframes_t buffer_size = _jack_driver->buffer_size();
+ const jack_nframes_t sample_rate = _jack_driver->sample_rate();
+ if (sample_rate != 0) {
+ const int latency_ms = lrintf(buffer_size * 1000 / (float)sample_rate);
+ std::stringstream ss;
+ ss << " frames @ " << (sample_rate / 1000)
+ << "kHz (" << latency_ms << "ms)";
+ _latency_label->set_label(ss.str());
+ _latency_label->set_visible(true);
+ _buf_size_combo->set_active((int)log2f(_jack_driver->buffer_size()) - 5);
+ updating = false;
+ return;
+ }
+ }
+#endif
+ _latency_label->set_visible(false);
+ updating = false;
+}
+
+bool
+Patchage::update_load()
+{
+#if defined(PATCHAGE_LIBJACK) || defined(HAVE_JACK_DBUS)
+ if (_jack_driver->is_attached()) {
+ char buf[8];
+ snprintf(buf, sizeof(buf), "%u", _jack_driver->get_xruns());
+ _xrun_progress->set_text(std::string(buf) + " Dropouts");
+ _xrun_progress->set_fraction(_jack_driver->get_max_dsp_load());
+ }
+#endif
+
+ return true;
+}
+
+void
+Patchage::zoom(double z)
+{
+ _conf->set_zoom(z);
+ _canvas->set_zoom(z);
+}
+
+void
+Patchage::refresh()
+{
+ if (_canvas && _enable_refresh) {
+ _canvas->clear();
+
+#if defined(PATCHAGE_LIBJACK) || defined(HAVE_JACK_DBUS)
+ if (_jack_driver)
+ _jack_driver->refresh();
+#endif
+
+#ifdef HAVE_ALSA
+ if (_alsa_driver)
+ _alsa_driver->refresh();
+#endif
+ }
+}
+
+void
+Patchage::store_window_location()
+{
+ int loc_x, loc_y, size_x, size_y;
+ _main_win->get_position(loc_x, loc_y);
+ _main_win->get_size(size_x, size_y);
+ Coord window_location;
+ window_location.x = loc_x;
+ window_location.y = loc_y;
+ Coord window_size;
+ window_size.x = size_x;
+ window_size.y = size_y;
+ _conf->set_window_location(window_location);
+ _conf->set_window_size(window_size);
+}
+
+void
+Patchage::clear_load()
+{
+#if defined(PATCHAGE_LIBJACK) || defined(HAVE_JACK_DBUS)
+ _xrun_progress->set_fraction(0.0);
+ _jack_driver->reset_xruns();
+ _jack_driver->reset_max_dsp_load();
+#endif
+}
+
+void
+Patchage::error_msg(const std::string& msg)
+{
+ Glib::RefPtr<Gtk::TextBuffer> buffer = _status_text->get_buffer();
+ buffer->insert_with_tag(buffer->end(), std::string("\n") + msg, _error_tag);
+ _status_text->scroll_to_mark(buffer->get_insert(), 0);
+ _menu_view_messages->set_active(true);
+}
+
+void
+Patchage::info_msg(const std::string& msg)
+{
+ Glib::RefPtr<Gtk::TextBuffer> buffer = _status_text->get_buffer();
+ buffer->insert(buffer->end(), std::string("\n") + msg);
+ _status_text->scroll_to_mark(buffer->get_insert(), 0);
+}
+
+void
+Patchage::warning_msg(const std::string& msg)
+{
+ Glib::RefPtr<Gtk::TextBuffer> buffer = _status_text->get_buffer();
+ buffer->insert_with_tag(buffer->end(), std::string("\n") + msg, _warning_tag);
+ _status_text->scroll_to_mark(buffer->get_insert(), 0);
+}
+
+static void
+load_module_location(GanvNode* node, void* data)
+{
+ if (GANV_IS_MODULE(node)) {
+ Ganv::Module* gmod = Glib::wrap(GANV_MODULE(node));
+ PatchageModule* pmod = dynamic_cast<PatchageModule*>(gmod);
+ if (pmod) {
+ pmod->load_location();
+ }
+ }
+}
+
+void
+Patchage::update_state()
+{
+ _canvas->for_each_node(load_module_location, NULL);
+}
+
+/** Update the sensitivity status of menus to reflect the present.
+ *
+ * (eg. disable "Connect to Jack" when Patchage is already connected to Jack)
+ */
+void
+Patchage::connect_widgets()
+{
+#if defined(PATCHAGE_LIBJACK) || defined(HAVE_JACK_DBUS)
+ _jack_driver->signal_attached.connect(sigc::bind(
+ sigc::mem_fun(*_menu_jack_connect, &Gtk::MenuItem::set_sensitive), false));
+ _jack_driver->signal_attached.connect(
+ sigc::mem_fun(this, &Patchage::refresh));
+ _jack_driver->signal_attached.connect(sigc::bind(
+ sigc::mem_fun(*_menu_jack_disconnect, &Gtk::MenuItem::set_sensitive), true));
+
+ _jack_driver->signal_detached.connect(sigc::bind(
+ sigc::mem_fun(*_menu_jack_connect, &Gtk::MenuItem::set_sensitive), true));
+ _jack_driver->signal_detached.connect(sigc::bind(
+ sigc::mem_fun(*_menu_jack_disconnect, &Gtk::MenuItem::set_sensitive), false));
+#endif
+
+#ifdef HAVE_ALSA
+ _alsa_driver->signal_attached.connect(sigc::bind(
+ sigc::mem_fun(*_menu_alsa_connect, &Gtk::MenuItem::set_sensitive), false));
+ _alsa_driver->signal_attached.connect(sigc::bind(
+ sigc::mem_fun(*_menu_alsa_disconnect, &Gtk::MenuItem::set_sensitive), true));
+
+ _alsa_driver->signal_detached.connect(sigc::bind(
+ sigc::mem_fun(*_menu_alsa_connect, &Gtk::MenuItem::set_sensitive), true));
+ _alsa_driver->signal_detached.connect(sigc::bind(
+ sigc::mem_fun(*_menu_alsa_disconnect, &Gtk::MenuItem::set_sensitive), false));
+#endif
+}
+
+#ifdef PATCHAGE_JACK_SESSION
+void
+Patchage::show_open_session_dialog()
+{
+ Gtk::FileChooserDialog dialog(*_main_win, "Open Session",
+ Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER);
+
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ Gtk::Button* open_but = dialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK);
+ open_but->property_has_default() = true;
+
+ if (dialog.run() != Gtk::RESPONSE_OK) {
+ return;
+ }
+
+ const std::string dir = dialog.get_filename();
+ if (g_chdir(dir.c_str())) {
+ error_msg("Failed to switch to session directory " + dir);
+ return;
+ }
+
+ if (system("./jack-session") < 0) {
+ error_msg("Error executing `./jack-session' in " + dir);
+ } else {
+ info_msg("Loaded session " + dir);
+ }
+}
+
+static void
+print_edge(GanvEdge* edge, void* data)
+{
+ std::ofstream* script = (std::ofstream*)data;
+ Ganv::Edge* edgemm = Glib::wrap(edge);
+
+ PatchagePort* src = dynamic_cast<PatchagePort*>((edgemm)->get_tail());
+ PatchagePort* dst = dynamic_cast<PatchagePort*>((edgemm)->get_head());
+
+ if (!src || !dst || src->type() == ALSA_MIDI || dst->type() == ALSA_MIDI) {
+ return;
+ }
+
+ (*script) << "jack_connect '" << src->full_name()
+ << "' '" << dst->full_name() << "' &" << endl;
+}
+
+void
+Patchage::save_session(bool close)
+{
+ Gtk::FileChooserDialog dialog(*_main_win, "Save Session",
+ Gtk::FILE_CHOOSER_ACTION_SAVE);
+
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ Gtk::Button* save_but = dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK);
+ save_but->property_has_default() = true;
+
+ if (dialog.run() != Gtk::RESPONSE_OK) {
+ return;
+ }
+
+ std::string path = dialog.get_filename();
+ if (g_mkdir_with_parents(path.c_str(), 0740)) {
+ error_msg("Failed to create session directory " + path);
+ return;
+ }
+
+ path += '/';
+ jack_session_command_t* cmd = jack_session_notify(
+ _jack_driver->client(),
+ NULL,
+ close ? JackSessionSaveAndQuit : JackSessionSave,
+ path.c_str());
+
+ const std::string script_path = path + "jack-session";
+ std::ofstream script(script_path.c_str());
+ script << "#!/bin/sh" << endl << endl;
+
+ const std::string var("${SESSION_DIR}");
+ for (int c = 0; cmd[c].uuid; ++c) {
+ std::string command = cmd[c].command;
+ const size_t index = command.find(var);
+ if (index != string::npos) {
+ command.replace(index, var.length(), cmd[c].client_name);
+ }
+
+ script << command << " &" << endl;
+ }
+
+ script << endl;
+ script << "sleep 3" << endl;
+ script << endl;
+
+ _canvas->for_each_edge(print_edge, &script);
+
+ script.close();
+ g_chmod(script_path.c_str(), 0740);
+}
+
+void
+Patchage::show_save_session_dialog()
+{
+ save_session(false);
+}
+
+void
+Patchage::show_save_close_session_dialog()
+{
+ save_session(true);
+}
+
+#endif
+
+#ifdef HAVE_ALSA
+void
+Patchage::menu_alsa_connect()
+{
+ _alsa_driver->attach(false);
+ _alsa_driver->refresh();
+}
+
+void
+Patchage::menu_alsa_disconnect()
+{
+ _alsa_driver->detach();
+ refresh();
+}
+#endif
+
+void
+Patchage::on_arrange()
+{
+ if (_canvas) {
+ _canvas->arrange();
+ }
+}
+
+void
+Patchage::on_sprung_layout_toggled()
+{
+ const bool sprung = _menu_view_sprung_layout->get_active();
+
+ _canvas->set_sprung_layout(sprung);
+ _conf->set_sprung_layout(sprung);
+}
+
+void
+Patchage::on_help_about()
+{
+ _about_win->run();
+ _about_win->hide();
+}
+
+static void
+update_labels(GanvNode* node, void* data)
+{
+ const bool human_names = *(const bool*)data;
+ if (GANV_IS_MODULE(node)) {
+ Ganv::Module* gmod = Glib::wrap(GANV_MODULE(node));
+ PatchageModule* pmod = dynamic_cast<PatchageModule*>(gmod);
+ if (pmod) {
+ for (Ganv::Port* gport : *gmod) {
+ PatchagePort* pport = dynamic_cast<PatchagePort*>(gport);
+ if (pport) {
+ pport->show_human_name(human_names);
+ }
+ }
+ }
+ }
+}
+
+void
+Patchage::on_view_human_names()
+{
+ bool human_names = show_human_names();
+ _canvas->for_each_node(update_labels, &human_names);
+}
+
+void
+Patchage::on_view_sort_ports()
+{
+ const bool sort_ports = this->sort_ports();
+ _canvas->set_port_order(sort_ports ? port_order : NULL, NULL);
+ _conf->set_sort_ports(sort_ports);
+ refresh();
+}
+
+void
+Patchage::on_zoom_in()
+{
+ const float zoom = _canvas->get_zoom() * 1.25;
+ _canvas->set_zoom(zoom);
+ _conf->set_zoom(zoom);
+}
+
+void
+Patchage::on_zoom_out()
+{
+ const float zoom = _canvas->get_zoom() * 0.75;
+ _canvas->set_zoom(zoom);
+ _conf->set_zoom(zoom);
+}
+
+void
+Patchage::on_zoom_normal()
+{
+ _canvas->set_zoom(1.0);
+ _conf->set_zoom(1.0);
+}
+
+void
+Patchage::on_zoom_full()
+{
+ _canvas->zoom_full();
+ _conf->set_zoom(_canvas->get_zoom());
+}
+
+void
+Patchage::on_increase_font_size()
+{
+ const float points = _canvas->get_font_size() + 1.0;
+ _canvas->set_font_size(points);
+ _conf->set_font_size(points);
+}
+
+void
+Patchage::on_decrease_font_size()
+{
+ const float points = _canvas->get_font_size() - 1.0;
+ _canvas->set_font_size(points);
+ _conf->set_font_size(points);
+}
+
+void
+Patchage::on_normal_font_size()
+{
+ _canvas->set_font_size(_canvas->get_default_font_size());
+ _conf->set_font_size(_canvas->get_default_font_size());
+}
+
+static inline guint
+highlight_color(guint c, guint delta)
+{
+ const guint max_char = 255;
+ const guint r = MIN((c >> 24) + delta, max_char);
+ const guint g = MIN(((c >> 16) & 0xFF) + delta, max_char);
+ const guint b = MIN(((c >> 8) & 0xFF) + delta, max_char);
+ const guint a = c & 0xFF;
+
+ return ((((guint)(r)) << 24) |
+ (((guint)(g)) << 16) |
+ (((guint)(b)) << 8) |
+ (((guint)(a))));
+}
+
+static void
+update_port_colors(GanvNode* node, void* data)
+{
+ Patchage* patchage = (Patchage*)data;
+ if (!GANV_IS_MODULE(node)) {
+ return;
+ }
+
+ Ganv::Module* gmod = Glib::wrap(GANV_MODULE(node));
+ PatchageModule* pmod = dynamic_cast<PatchageModule*>(gmod);
+ if (!pmod) {
+ return;
+ }
+
+ for (PatchageModule::iterator i = pmod->begin(); i != pmod->end(); ++i) {
+ PatchagePort* port = dynamic_cast<PatchagePort*>(*i);
+ if (port) {
+ const uint32_t rgba = patchage->conf()->get_port_color(port->type());
+ port->set_fill_color(rgba);
+ port->set_border_color(highlight_color(rgba, 0x20));
+ }
+ }
+}
+
+static void
+update_edge_color(GanvEdge* edge, void* data)
+{
+ Patchage* patchage = (Patchage*)data;
+ Ganv::Edge* edgemm = Glib::wrap(edge);
+
+ PatchagePort* tail = dynamic_cast<PatchagePort*>((edgemm)->get_tail());
+ if (tail) {
+ edgemm->set_color(patchage->conf()->get_port_color(tail->type()));
+ }
+}
+
+void
+Patchage::on_legend_color_change(int id, const std::string& label, uint32_t rgba)
+{
+ _conf->set_port_color((PortType)id, rgba);
+ _canvas->for_each_node(update_port_colors, this);
+ _canvas->for_each_edge(update_edge_color, this);
+}
+
+void
+Patchage::on_messages_resized(Gtk::Allocation& alloc)
+{
+ const int max_pos = _main_paned->get_allocation().get_height();
+ _conf->set_messages_height(max_pos - _main_paned->get_position());
+}
+
+void
+Patchage::save()
+{
+ _conf->set_zoom(_canvas->get_zoom()); // Can be changed by ganv
+ _conf->save();
+}
+
+void
+Patchage::on_quit()
+{
+#ifdef HAVE_ALSA
+ _alsa_driver->detach();
+#endif
+#if defined(PATCHAGE_LIBJACK) || defined(HAVE_JACK_DBUS)
+ _jack_driver->detach();
+#endif
+ _main_win->hide();
+}
+
+void
+Patchage::on_export_image()
+{
+ Gtk::FileChooserDialog dialog("Export Image", Gtk::FILE_CHOOSER_ACTION_SAVE);
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK);
+ dialog.set_default_response(Gtk::RESPONSE_OK);
+ dialog.set_transient_for(*_main_win);
+
+ typedef std::map<std::string, std::string> Types;
+ Types types;
+ types["*.dot"] = "Graphviz DOT";
+ types["*.pdf"] = "Portable Document Format";
+ types["*.ps"] = "PostScript";
+ types["*.svg"] = "Scalable Vector Graphics";
+ for (Types::const_iterator t = types.begin(); t != types.end(); ++t) {
+ Gtk::FileFilter filt;
+ filt.add_pattern(t->first);
+ filt.set_name(t->second);
+ dialog.add_filter(filt);
+ }
+
+ Gtk::CheckButton* bg_but = new Gtk::CheckButton("Draw _Background", true);
+ Gtk::Alignment* extra = new Gtk::Alignment(1.0, 0.5, 0.0, 0.0);
+ bg_but->set_active(true);
+ extra->add(*Gtk::manage(bg_but));
+ extra->show_all();
+ dialog.set_extra_widget(*Gtk::manage(extra));
+
+ if (dialog.run() == Gtk::RESPONSE_OK) {
+ const std::string filename = dialog.get_filename();
+ if (Glib::file_test(filename, Glib::FILE_TEST_EXISTS)) {
+ Gtk::MessageDialog confirm(
+ std::string("File exists! Overwrite ") + filename + "?",
+ true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_YES_NO, true);
+ confirm.set_transient_for(dialog);
+ if (confirm.run() != Gtk::RESPONSE_YES) {
+ return;
+ }
+ }
+ _canvas->export_image(filename.c_str(), bg_but->get_active());
+ }
+}
+
+void
+Patchage::on_view_messages()
+{
+ if (_menu_view_messages->get_active()) {
+ Glib::RefPtr<Gtk::TextBuffer> buffer = _status_text->get_buffer();
+ if (!_pane_initialized) {
+ int y, line_height;
+ _status_text->get_line_yrange(buffer->begin(), y, line_height);
+ const int pad = _status_text->get_pixels_inside_wrap();
+ const int max_pos = _main_paned->get_allocation().get_height();
+ const int min_height = (line_height + 2 * pad);
+ const int conf_height = _conf->get_messages_height();
+ _main_paned->set_position(max_pos - std::max(conf_height, min_height));
+ _pane_initialized = true;
+ }
+
+ _log_scrolledwindow->show();
+ _status_text->scroll_to_mark(
+ _status_text->get_buffer()->get_insert(), 0);
+ _conf->set_show_messages(true);
+ } else {
+ _log_scrolledwindow->hide();
+ _conf->set_show_messages(false);
+ }
+}
+
+void
+Patchage::on_view_toolbar()
+{
+ if (_menu_view_toolbar->get_active()) {
+ _toolbar->show();
+ } else {
+ _toolbar->hide();
+ }
+ _conf->set_show_toolbar(_menu_view_toolbar->get_active());
+}
+
+bool
+Patchage::on_scroll(GdkEventScroll* ev)
+{
+ return false;
+}
+
+void
+Patchage::buffer_size_changed()
+{
+#if defined(HAVE_JACK) || defined(HAVE_JACK_DBUS)
+ const int selected = _buf_size_combo->get_active_row_number();
+
+ if (selected == -1) {
+ update_toolbar();
+ } else {
+ const jack_nframes_t buffer_size = 1 << (selected + 5);
+ _jack_driver->set_buffer_size(buffer_size);
+ update_toolbar();
+ }
+#endif
+}
+