summaryrefslogtreecommitdiffstats
path: root/src/Patchage.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/Patchage.cpp')
-rw-r--r--src/Patchage.cpp1579
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