diff options
Diffstat (limited to 'src/Patchage.cpp')
-rw-r--r-- | src/Patchage.cpp | 1579 |
1 files changed, 708 insertions, 871 deletions
diff --git a/src/Patchage.cpp b/src/Patchage.cpp index eae2ef9..785cd2d 100644 --- a/src/Patchage.cpp +++ b/src/Patchage.cpp @@ -1,1078 +1,915 @@ -/* 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> +// Copyright 2007-2021 David Robillard <d@drobilla.net> +// SPDX-License-Identifier: GPL-3.0-or-later -#include <cmath> -#include <fstream> +#include "Patchage.hpp" -#include <glib.h> -#include <glib/gstdio.h> -#include <gtk/gtkwindow.h> +#include "Action.hpp" +#include "AudioDriver.hpp" +#include "Canvas.hpp" +#include "CanvasModule.hpp" +#include "CanvasPort.hpp" +#include "Configuration.hpp" +#include "Coord.hpp" +#include "Driver.hpp" +#include "Drivers.hpp" +#include "Event.hpp" +#include "Legend.hpp" +#include "Options.hpp" +#include "PortType.hpp" +#include "Reactor.hpp" +#include "Setting.hpp" +#include "TextViewLog.hpp" +#include "UIFile.hpp" +#include "Widget.hpp" +#include "event_to_string.hpp" +#include "handle_event.hpp" +#include "i18n.hpp" +#include "patchage_config.h" // IWYU pragma: keep +#include "warnings.hpp" + +PATCHAGE_DISABLE_GANV_WARNINGS +#include "ganv/Edge.hpp" +#include "ganv/Module.hpp" +#include "ganv/Node.hpp" +#include "ganv/Port.hpp" +#include "ganv/module.h" +#include "ganv/types.h" +PATCHAGE_RESTORE_WARNINGS -#include <boost/format.hpp> +PATCHAGE_DISABLE_FMT_WARNINGS +#include <fmt/core.h> +PATCHAGE_RESTORE_WARNINGS -#include <gtkmm/button.h> +#include <glib-object.h> +#include <glib.h> +#include <glibmm/fileutils.h> +#include <glibmm/main.h> +#include <glibmm/miscutils.h> +#include <glibmm/propertyproxy.h> +#include <glibmm/signalproxy.h> +#include <glibmm/ustring.h> +#include <gobject/gclosure.h> +#include <gtk/gtk.h> +#include <gtkmm/aboutdialog.h> +#include <gtkmm/adjustment.h> +#include <gtkmm/alignment.h> +#include <gtkmm/box.h> +#include <gtkmm/builder.h> +#include <gtkmm/checkbutton.h> +#include <gtkmm/checkmenuitem.h> +#include <gtkmm/combobox.h> +#include <gtkmm/dialog.h> +#include <gtkmm/enums.h> +#include <gtkmm/filechooser.h> #include <gtkmm/filechooserdialog.h> +#include <gtkmm/filefilter.h> +#include <gtkmm/imagemenuitem.h> +#include <gtkmm/label.h> +#include <gtkmm/layout.h> #include <gtkmm/liststore.h> +#include <gtkmm/menubar.h> #include <gtkmm/menuitem.h> #include <gtkmm/messagedialog.h> +#include <gtkmm/object.h> +#include <gtkmm/paned.h> +#include <gtkmm/scrolledwindow.h> #include <gtkmm/stock.h> +#include <gtkmm/textbuffer.h> +#include <gtkmm/texttag.h> +#include <gtkmm/textview.h> +#include <gtkmm/toolbar.h> +#include <gtkmm/toolbutton.h> +#include <gtkmm/treeiter.h> #include <gtkmm/treemodel.h> +#include <gtkmm/window.h> +#include <sigc++/adaptors/bind.h> +#include <sigc++/functors/mem_fun.h> +#include <sigc++/functors/ptr_fun.h> +#include <sigc++/signal.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 +#include <algorithm> +#include <cmath> +#include <cstdint> +#include <cstdlib> +#include <functional> +#include <map> +#include <optional> +#include <utility> +#include <variant> -#ifdef PATCHAGE_JACK_SESSION - #include <jack/session.h> -#endif +#ifdef PATCHAGE_GTK_OSX -#ifdef HAVE_ALSA - #include "AlsaDriver.hpp" -#endif +# include <gtkmm/main.h> +# include <gtkosxapplication.h> -#ifdef PATCHAGE_GTK_OSX - #include <gtkosxapplication.h> +namespace { -static gboolean -can_activate_cb(GtkWidget* widget, guint signal_id, gpointer data) +gboolean +can_activate_cb(GtkWidget* widget, guint, gpointer) { return gtk_widget_is_sensitive(widget); } -static void -terminate_cb(GtkosxApplication* app, gpointer data) +void +terminate_cb(GtkosxApplication*, gpointer data) { - Patchage* patchage = (Patchage*)data; - patchage->save(); - Gtk::Main::quit(); + auto* patchage = static_cast<patchage::Patchage*>(data); + patchage->save(); + Gtk::Main::quit(); } +} // namespace + #endif -static bool -configure_cb(GtkWindow* parentWindow, GdkEvent* event, gpointer data) +namespace patchage { + +namespace { + +bool +configure_cb(GtkWindow*, GdkEvent*, gpointer data) { - ((Patchage*)data)->store_window_location(); - return FALSE; + static_cast<Patchage*>(data)->store_window_location(); + return FALSE; } -static int -port_order(const GanvPort* a, const GanvPort* b, void* data) +int +port_order(const GanvPort* a, const GanvPort* b, void*) { - 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; -} + const auto* pa = dynamic_cast<const CanvasPort*>(Glib::wrap(a)); + const auto* pb = dynamic_cast<const CanvasPort*>(Glib::wrap(b)); + if (pa && pb) { + const auto oa = pa->order(); + const auto ob = pb->order(); + if (oa && ob) { + return *oa - *ob; + } -struct ProjectList_column_record : public Gtk::TreeModel::ColumnRecord { - Gtk::TreeModelColumn<Glib::ustring> label; -}; + if (pa->order()) { + return -1; + } -using std::cout; -using std::endl; -using std::string; + if (pb->order()) { + return 1; + } -#define INIT_WIDGET(x) x(_xml, ((const char*)#x) + 1) + return pa->name().compare(pb->name()); + } + return 0; +} -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 +template<class S> +void +on_setting_toggled(Reactor* const reactor, const Gtk::CheckMenuItem* const item) { - _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 + (*reactor)(action::ChangeSetting{{S{item->get_active()}}}); +} -#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)); +void +update_labels(GanvNode* node, void* data) +{ + const bool human_names = *static_cast<const bool*>(data); + if (GANV_IS_MODULE(node)) { + Ganv::Module* gmod = Glib::wrap(GANV_MODULE(node)); + auto* pmod = dynamic_cast<CanvasModule*>(gmod); + if (pmod) { + for (Ganv::Port* gport : *gmod) { + auto* pport = dynamic_cast<CanvasPort*>(gport); + if (pport) { + pport->show_human_name(human_names); + } + } + } + } +} + +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; - _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 + return ((r << 24u) | (g << 16u) | (b << 8u) | a); +} -#ifdef HAVE_ALSA - _alsa_driver = new AlsaDriver(this); -#endif +void +update_port_colors(GanvNode* node, void* data) +{ + auto* patchage = static_cast<Patchage*>(data); + if (!GANV_IS_MODULE(node)) { + return; + } - 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); + Ganv::Module* gmod = Glib::wrap(GANV_MODULE(node)); + auto* pmod = dynamic_cast<CanvasModule*>(gmod); + if (!pmod) { + return; + } - g_signal_connect(_main_win->gobj(), "configure-event", - G_CALLBACK(configure_cb), this); + for (Ganv::Port* p : *pmod) { + auto* port = dynamic_cast<CanvasPort*>(p); + 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)); + } + } +} - _canvas->widget().grab_focus(); +void +update_edge_color(GanvEdge* edge, void* data) +{ + auto* patchage = static_cast<Patchage*>(data); + Ganv::Edge* edgemm = Glib::wrap(edge); + + if (edgemm) { + auto* tail = dynamic_cast<CanvasPort*>((edgemm)->get_tail()); + if (tail) { + edgemm->set_color(patchage->conf().get_port_color(tail->type())); + } + } +} + +} // namespace + +#define INIT_WIDGET(x) x(_xml, (#x) + 1) + +Patchage::Patchage(Options options) + : _xml(UIFile::open("patchage")) + , 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_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(_dropouts_label) + , 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) + , _conf([this](const Setting& setting) { on_conf_change(setting); }) + , _log(_status_text) + , _canvas(new Canvas{_log, _action_sink, 1600 * 2, 1200 * 2}) + , _drivers(_log, [this](const Event& event) { on_driver_event(event); }) + , _reactor(_conf, _drivers, *_canvas, _log) + , _action_sink([this](const Action& action) { _reactor(action); }) + , _options{options} +{ + 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 + const Glib::RefPtr<Gtk::ListStore> buf_size_store = + Gtk::ListStore::create(_buf_size_columns); + for (size_t i = 32; i <= 4096; i *= 2) { + const 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)); + + _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::bind( + sigc::mem_fun(this, &Patchage::on_menu_action), Action{action::Refresh{}})); + + _menu_view_human_names->signal_activate().connect( + sigc::bind(sigc::ptr_fun(&on_setting_toggled<setting::HumanNames>), + &_reactor, + _menu_view_human_names.get())); + + _menu_view_sort_ports->signal_activate().connect( + sigc::bind(sigc::ptr_fun(&on_setting_toggled<setting::SortedPorts>), + &_reactor, + _menu_view_sort_ports.get())); + + _menu_view_arrange->signal_activate().connect( + sigc::mem_fun(this, &Patchage::on_arrange)); + + _menu_view_sprung_layout->signal_activate().connect( + sigc::bind(sigc::ptr_fun(&on_setting_toggled<setting::SprungLayout>), + &_reactor, + _menu_view_sprung_layout.get())); + + _menu_view_messages->signal_activate().connect( + sigc::bind(sigc::ptr_fun(&on_setting_toggled<setting::MessagesVisible>), + &_reactor, + _menu_view_messages.get())); + + _menu_view_toolbar->signal_activate().connect( + sigc::bind(sigc::ptr_fun(&on_setting_toggled<setting::ToolbarVisible>), + &_reactor, + _menu_view_toolbar.get())); + + _menu_help_about->signal_activate().connect( + sigc::mem_fun(this, &Patchage::on_help_about)); + + _menu_zoom_in->signal_activate().connect(sigc::bind( + sigc::mem_fun(this, &Patchage::on_menu_action), Action{action::ZoomIn{}})); + _menu_zoom_out->signal_activate().connect(sigc::bind( + sigc::mem_fun(this, &Patchage::on_menu_action), Action{action::ZoomOut{}})); + _menu_zoom_normal->signal_activate().connect( + sigc::bind(sigc::mem_fun(this, &Patchage::on_menu_action), + Action{action::ZoomNormal{}})); + _menu_zoom_full->signal_activate().connect( + sigc::bind(sigc::mem_fun(this, &Patchage::on_menu_action), + Action{action::ZoomFull{}})); + _menu_increase_font_size->signal_activate().connect( + sigc::bind(sigc::mem_fun(this, &Patchage::on_menu_action), + Action{action::IncreaseFontSize{}})); + _menu_decrease_font_size->signal_activate().connect( + sigc::bind(sigc::mem_fun(this, &Patchage::on_menu_action), + Action{action::DecreaseFontSize{}})); + _menu_normal_font_size->signal_activate().connect( + sigc::bind(sigc::mem_fun(this, &Patchage::on_menu_action), + Action{action::ResetFontSize{}})); + + 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); + } + + // Present window so that display attributes like font size are available + _canvas->widget().show(); + _main_win->present(); + + // Set the default font size based on the current GUI environment + _conf.set<setting::FontSize>(_canvas->get_default_font_size()); + + // Load configuration file (but do not apply it yet, see below) + _conf.load(); + + _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); - // Idle callback, check if we need to refresh - Glib::signal_timeout().connect( - sigc::mem_fun(this, &Patchage::idle_callback), 100); +#ifdef __APPLE__ + try { + _about_win->set_logo(Gdk::Pixbuf::create_from_file( + bundle_location() + "/Resources/Patchage.icns")); + } catch (const Glib::Exception& e) { + _log.error(fmt::format("Failed to set logo ({})", std::string(e.what()))); + } +#endif + + // Enable JACK menu items if driver is present + if (_drivers.jack()) { + _menu_jack_connect->signal_activate().connect(sigc::bind( + sigc::mem_fun(_drivers.jack().get(), &AudioDriver::attach), true)); + _menu_jack_disconnect->signal_activate().connect( + sigc::mem_fun(_drivers.jack().get(), &AudioDriver::detach)); + } else { + _menu_jack_connect->set_sensitive(false); + _menu_jack_disconnect->set_sensitive(false); + } + + // Enable ALSA menu items if driver is present + if (_drivers.alsa()) { + _menu_alsa_connect->signal_activate().connect( + sigc::bind(sigc::mem_fun(_drivers.alsa().get(), &Driver::attach), false)); + _menu_alsa_disconnect->signal_activate().connect( + sigc::mem_fun(_drivers.alsa().get(), &Driver::detach)); + } else { + _menu_alsa_connect->set_sensitive(false); + _menu_alsa_disconnect->set_sensitive(false); + } + + g_signal_connect( + _main_win->gobj(), "configure-event", G_CALLBACK(configure_cb), this); + + _canvas->widget().grab_focus(); #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); + // Set up Mac menu bar + GtkosxApplication* osxapp = static_cast<GtkosxApplication*>( + g_object_new(GTKOSX_TYPE_APPLICATION, nullptr)); + + _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), + nullptr); + g_signal_connect( + osxapp, "NSApplicationWillTerminate", G_CALLBACK(terminate_cb), this); + gtkosx_application_ready(osxapp); #endif + + // Apply all configuration settings to ensure the GUI is synced + _conf.each([this](const Setting& setting) { on_conf_change(setting); }); + + // Set up an idle callback to process events and update the GUI if necessary + Glib::signal_timeout().connect(sigc::mem_fun(this, &Patchage::idle_callback), + 100); } 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(); + _about_win.destroy(); + _xml.reset(); } void Patchage::attach() { - _enable_refresh = false; + if (_drivers.jack() && _options.jack_driver_autoattach) { + _drivers.jack()->attach(true); + } -#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 + if (_drivers.alsa() && _options.alsa_driver_autoattach) { + _drivers.alsa()->attach(false); + } - _enable_refresh = true; - - refresh(); - update_toolbar(); + process_events(); + 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 + // Initial run, attach + if (_attach) { + attach(); + _menu_view_messages->set_active(_conf.get<setting::MessagesVisible>()); + _attach = false; + } - // 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; + // Process any events from drivers + process_events(); - // Update load every 5 idle callbacks - static int count = 0; - if (++count == 5) { - update_load(); - count = 0; - } + // Update load every 5 idle callbacks + static int count = 0; + if (++count == 5) { + update_load(); + count = 0; + } - return true; + 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; + static bool updating = false; + if (updating) { + return; + } + + updating = true; + + if (_drivers.jack() && _drivers.jack()->is_attached()) { + const auto buffer_size = _drivers.jack()->buffer_size(); + const auto sample_rate = _drivers.jack()->sample_rate(); + if (sample_rate != 0) { + const auto sample_rate_khz = sample_rate / 1000.0; + const auto latency_ms = buffer_size / sample_rate_khz; + + _latency_label->set_label(" " + + fmt::format(T("frames at {} kHz ({:0.2f} ms)"), + sample_rate_khz, + latency_ms)); + + _latency_label->set_visible(true); + _buf_size_combo->set_active( + static_cast<int>(log2f(_drivers.jack()->buffer_size()) - 5)); + updating = false; + return; + } + } + + _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 + if (_drivers.jack() && _drivers.jack()->is_attached()) { + const auto xruns = _drivers.jack()->xruns(); - return true; -} + _dropouts_label->set_text(" " + fmt::format(T("Dropouts: {}"), xruns)); -void -Patchage::zoom(double z) -{ - _conf->set_zoom(z); - _canvas->set_zoom(z); + if (xruns > 0u) { + _dropouts_label->show(); + _clear_load_but->show(); + } else { + _dropouts_label->hide(); + _clear_load_but->hide(); + } + } + + return true; } void -Patchage::refresh() +Patchage::store_window_location() { - if (_canvas && _enable_refresh) { - _canvas->clear(); + int loc_x = 0; + int loc_y = 0; + _main_win->get_position(loc_x, loc_y); -#if defined(PATCHAGE_LIBJACK) || defined(HAVE_JACK_DBUS) - if (_jack_driver) - _jack_driver->refresh(); -#endif + int size_x = 0; + int size_y = 0; + _main_win->get_size(size_x, size_y); -#ifdef HAVE_ALSA - if (_alsa_driver) - _alsa_driver->refresh(); -#endif - } -} + _conf.set<setting::WindowLocation>( + {static_cast<double>(loc_x), static_cast<double>(loc_y)}); -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); + _conf.set<setting::WindowSize>( + {static_cast<double>(size_x), static_cast<double>(size_y)}); } 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 + _dropouts_label->set_text(" " + fmt::format(T("Dropouts: {}"), 0U)); + _dropouts_label->hide(); + _clear_load_but->hide(); + if (_drivers.jack()) { + _drivers.jack()->reset_xruns(); + } } void -Patchage::error_msg(const std::string& msg) +Patchage::operator()(const setting::AlsaAttached& setting) { - 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); -} + if (setting.value) { + _menu_alsa_connect->set_sensitive(false); + _menu_alsa_disconnect->set_sensitive(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); + if (_drivers.alsa()) { + _drivers.alsa()->refresh([this](const Event& event) { + handle_event(_conf, _metadata, *_canvas, _log, event); + }); + } + } else { + _menu_alsa_connect->set_sensitive(true); + _menu_alsa_disconnect->set_sensitive(false); + + _canvas->remove_ports([](const CanvasPort* port) { + return port->type() == PortType::alsa_midi; + }); + } } void -Patchage::warning_msg(const std::string& msg) +Patchage::operator()(const setting::JackAttached& setting) { - 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); -} + if (setting.value) { + _menu_jack_connect->set_sensitive(false); + _menu_jack_disconnect->set_sensitive(true); -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(); - } - } + if (_drivers.jack()) { + _drivers.jack()->refresh([this](const Event& event) { + handle_event(_conf, _metadata, *_canvas, _log, event); + }); + } + } else { + _menu_jack_connect->set_sensitive(true); + _menu_jack_disconnect->set_sensitive(false); + + _canvas->remove_ports([](const CanvasPort* port) { + return (port->type() == PortType::jack_audio || + port->type() == PortType::jack_midi || + port->type() == PortType::jack_osc || + port->type() == PortType::jack_cv); + }); + } } void -Patchage::update_state() +Patchage::operator()(const setting::FontSize& setting) { - _canvas->for_each_node(load_module_location, NULL); + if (static_cast<float>(_canvas->get_font_size()) != setting.value) { + _canvas->set_font_size(setting.value); + } } -/** 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() +Patchage::operator()(const setting::HumanNames& setting) { -#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)); + bool human_names = setting.value; - _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 + _menu_view_human_names->set_active(human_names); + _canvas->for_each_node(update_labels, &human_names); } -#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) +Patchage::operator()(const setting::MessagesHeight& setting) { - 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 (_log_scrolledwindow->is_visible()) { + const int min_height = _log.min_height(); + const int max_pos = _main_paned->get_allocation().get_height(); + const int conf_height = setting.value; - if (!src || !dst || src->type() == ALSA_MIDI || dst->type() == ALSA_MIDI) { - return; - } - - (*script) << "jack_connect '" << src->full_name() - << "' '" << dst->full_name() << "' &" << endl; + _main_paned->set_position(max_pos - std::max(conf_height, min_height)); + } } void -Patchage::save_session(bool close) +Patchage::operator()(const setting::MessagesVisible& setting) { - 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); -} + if (setting.value) { + _log_scrolledwindow->show(); + _status_text->scroll_to_mark(_status_text->get_buffer()->get_insert(), 0); + } else { + _log_scrolledwindow->hide(); + } -void -Patchage::show_save_session_dialog() -{ - save_session(false); + _menu_view_messages->set_active(setting.value); } void -Patchage::show_save_close_session_dialog() +Patchage::operator()(const setting::PortColor&) { - save_session(true); + _canvas->for_each_node(update_port_colors, this); + _canvas->for_each_edge(update_edge_color, this); } -#endif - -#ifdef HAVE_ALSA void -Patchage::menu_alsa_connect() +Patchage::operator()(const setting::SortedPorts& setting) { - _alsa_driver->attach(false); - _alsa_driver->refresh(); + _menu_view_sort_ports->set_active(setting.value); + if (setting.value) { + _canvas->set_port_order(port_order, nullptr); + } else { + _canvas->set_port_order(nullptr, nullptr); + } } void -Patchage::menu_alsa_disconnect() +Patchage::operator()(const setting::SprungLayout& setting) { - _alsa_driver->detach(); - refresh(); + _canvas->set_sprung_layout(setting.value); + _menu_view_sprung_layout->set_active(setting.value); } -#endif void -Patchage::on_arrange() +Patchage::operator()(const setting::ToolbarVisible& setting) { - if (_canvas) { - _canvas->arrange(); - } + if (setting.value) { + _toolbar->show(); + _menu_view_toolbar->set_active(true); + } else { + _toolbar->hide(); + _menu_view_toolbar->set_active(false); + } } void -Patchage::on_sprung_layout_toggled() +Patchage::operator()(const setting::WindowLocation& setting) { - const bool sprung = _menu_view_sprung_layout->get_active(); + const int new_x = static_cast<int>(setting.value.x); + const int new_y = static_cast<int>(setting.value.y); + + int current_x = 0; + int current_y = 0; + _main_win->get_position(current_x, current_y); - _canvas->set_sprung_layout(sprung); - _conf->set_sprung_layout(sprung); + if (new_x != current_x || new_y != current_y) { + _main_win->move(new_x, new_y); + } } void -Patchage::on_help_about() +Patchage::operator()(const setting::WindowSize& setting) { - _about_win->run(); - _about_win->hide(); -} + const int new_w = static_cast<int>(setting.value.x); + const int new_h = static_cast<int>(setting.value.y); -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); - } - } - } - } -} + int current_w = 0; + int current_h = 0; + _main_win->get_size(current_w, current_h); -void -Patchage::on_view_human_names() -{ - bool human_names = show_human_names(); - _canvas->for_each_node(update_labels, &human_names); + if (new_w != current_w || new_h != current_h) { + _main_win->resize(new_w, new_h); + } } void -Patchage::on_view_sort_ports() +Patchage::operator()(const setting::Zoom& setting) { - const bool sort_ports = this->sort_ports(); - _canvas->set_port_order(sort_ports ? port_order : NULL, NULL); - _conf->set_sort_ports(sort_ports); - refresh(); + if (static_cast<float>(_canvas->get_zoom()) != setting.value) { + _canvas->set_zoom(setting.value); + } } void -Patchage::on_zoom_in() +Patchage::on_driver_event(const Event& event) { - const float zoom = _canvas->get_zoom() * 1.25; - _canvas->set_zoom(zoom); - _conf->set_zoom(zoom); -} + const std::lock_guard<std::mutex> lock{_events_mutex}; -void -Patchage::on_zoom_out() -{ - const float zoom = _canvas->get_zoom() * 0.75; - _canvas->set_zoom(zoom); - _conf->set_zoom(zoom); + _driver_events.emplace(event); } void -Patchage::on_zoom_normal() +Patchage::process_events() { - _canvas->set_zoom(1.0); - _conf->set_zoom(1.0); -} + const std::lock_guard<std::mutex> lock{_events_mutex}; -void -Patchage::on_zoom_full() -{ - _canvas->zoom_full(); - _conf->set_zoom(_canvas->get_zoom()); -} + while (!_driver_events.empty()) { + const Event& event = _driver_events.front(); -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); -} + _log.info(event_to_string(event)); + handle_event(_conf, _metadata, *_canvas, _log, event); -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); + _driver_events.pop(); + } } void -Patchage::on_normal_font_size() +Patchage::on_conf_change(const Setting& setting) { - _canvas->set_font_size(_canvas->get_default_font_size()); - _conf->set_font_size(_canvas->get_default_font_size()); + std::visit(*this, setting); } -static inline guint -highlight_color(guint c, guint delta) +void +Patchage::on_arrange() { - 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)))); + if (_canvas) { + _canvas->arrange(); + } } -static void -update_port_colors(GanvNode* node, void* data) +void +Patchage::on_help_about() { - 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)); - } - } + _about_win->run(); + _about_win->hide(); } -static void -update_edge_color(GanvEdge* edge, void* data) +void +Patchage::on_legend_color_change(PortType id, const std::string&, uint32_t rgba) { - 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())); - } + _reactor(action::ChangeSetting{{setting::PortColor{id, rgba}}}); } void -Patchage::on_legend_color_change(int id, const std::string& label, uint32_t rgba) +Patchage::on_messages_resized(Gtk::Allocation&) { - _conf->set_port_color((PortType)id, rgba); - _canvas->for_each_node(update_port_colors, this); - _canvas->for_each_edge(update_edge_color, this); + const int max_pos = _main_paned->get_allocation().get_height(); + + _conf.set<setting::MessagesHeight>(max_pos - _main_paned->get_position()); } void -Patchage::on_messages_resized(Gtk::Allocation& alloc) +Patchage::save() { - const int max_pos = _main_paned->get_allocation().get_height(); - _conf->set_messages_height(max_pos - _main_paned->get_position()); + _conf.set<setting::Zoom>(_canvas->get_zoom()); // Can be changed by ganv + _conf.save(); } void -Patchage::save() +Patchage::quit() { - _conf->set_zoom(_canvas->get_zoom()); // Can be changed by ganv - _conf->save(); + _main_win->hide(); } 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(); + if (_drivers.alsa()) { + _drivers.alsa()->detach(); + } + + if (_drivers.jack()) { + _drivers.jack()->detach(); + } + + _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()); - } + Gtk::FileChooserDialog dialog(T("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); + + using Types = std::map<std::string, std::string>; + + Types types; + types["*.dot"] = "Graphviz DOT"; + types["*.pdf"] = "Portable Document Format"; + types["*.ps"] = "PostScript"; + types["*.svg"] = "Scalable Vector Graphics"; + for (const auto& t : types) { + Gtk::FileFilter filt; + filt.add_pattern(t.first); + filt.set_name(t.second); + dialog.add_filter(filt); + } + + auto* bg_but = new Gtk::CheckButton(T("Draw _Background"), true); + auto* 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( + fmt::format(T("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() +bool +Patchage::on_scroll(GdkEventScroll*) { - 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); - } + return 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) +Patchage::on_menu_action(const Action& action) { - return false; + _reactor(action); } 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 + if (_drivers.jack()) { + const int selected = _buf_size_combo->get_active_row_number(); + + if (selected == -1) { + update_toolbar(); + } else { + const uint32_t buffer_size = 1u << (selected + 5); + _drivers.jack()->set_buffer_size(buffer_size); + update_toolbar(); + } + } } +} // namespace patchage |