summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2021-05-10 13:19:34 -0400
committerDavid Robillard <d@drobilla.net>2021-05-10 13:19:34 -0400
commitc1d99e42674d26a2699e5a7244dbaa988820b40d (patch)
tree29a103fdb8536c6c6e42dcc8013f146fedcc8e14
parenta280246c3043fc2ea1f50fa695539ac0bec44be1 (diff)
downloadpatchage-c1d99e42674d26a2699e5a7244dbaa988820b40d.tar.gz
patchage-c1d99e42674d26a2699e5a7244dbaa988820b40d.tar.bz2
patchage-c1d99e42674d26a2699e5a7244dbaa988820b40d.zip
Add Action representation to isolate behaviour from canvas objects
A step towards isolating the canvas and ultimately the entire UI away so it can be replaced.
-rw-r--r--src/Action.hpp76
-rw-r--r--src/ActionSink.hpp (renamed from src/Connector.hpp)30
-rw-r--r--src/Canvas.cpp34
-rw-r--r--src/Canvas.hpp10
-rw-r--r--src/CanvasModule.cpp63
-rw-r--r--src/CanvasModule.hpp29
-rw-r--r--src/Connector.cpp72
-rw-r--r--src/Event.hpp4
-rw-r--r--src/Patchage.cpp27
-rw-r--r--src/Patchage.hpp8
-rw-r--r--src/Reactor.cpp153
-rw-r--r--src/Reactor.hpp74
-rw-r--r--src/handle_event.hpp4
-rw-r--r--wscript2
14 files changed, 391 insertions, 195 deletions
diff --git a/src/Action.hpp b/src/Action.hpp
new file mode 100644
index 0000000..f843a14
--- /dev/null
+++ b/src/Action.hpp
@@ -0,0 +1,76 @@
+/* This file is part of Patchage.
+ * Copyright 2007-2021 David Robillard <d@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/>.
+ */
+
+#ifndef PATCHAGE_ACTION_HPP
+#define PATCHAGE_ACTION_HPP
+
+#include "ClientID.hpp"
+#include "PortID.hpp"
+#include "SignalDirection.hpp"
+
+#include <boost/variant/variant.hpp>
+
+namespace patchage {
+namespace action {
+
+struct ConnectPorts {
+ PortID tail;
+ PortID head;
+};
+
+struct DisconnectClient {
+ ClientID client;
+ SignalDirection direction;
+};
+
+struct DisconnectPort {
+ PortID port;
+};
+
+struct DisconnectPorts {
+ PortID tail;
+ PortID head;
+};
+
+struct MoveModule {
+ ClientID client;
+ SignalDirection direction;
+ double x;
+ double y;
+};
+
+struct SplitModule {
+ ClientID client;
+};
+
+struct UnsplitModule {
+ ClientID client;
+};
+
+} // namespace action
+
+/// A high-level action from the user
+using Action = boost::variant<action::ConnectPorts,
+ action::DisconnectClient,
+ action::DisconnectPort,
+ action::DisconnectPorts,
+ action::MoveModule,
+ action::SplitModule,
+ action::UnsplitModule>;
+
+} // namespace patchage
+
+#endif // PATCHAGE_ACTION_HPP
diff --git a/src/Connector.hpp b/src/ActionSink.hpp
index f3e3816..c0d92ec 100644
--- a/src/Connector.hpp
+++ b/src/ActionSink.hpp
@@ -14,34 +14,18 @@
* along with Patchage. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef PATCHAGE_CONNECTOR_HPP
-#define PATCHAGE_CONNECTOR_HPP
+#ifndef PATCHAGE_ACTION_SINK_HPP
+#define PATCHAGE_ACTION_SINK_HPP
-#include "PortID.hpp"
+#include "Action.hpp"
-#include <unordered_map>
+#include <functional>
namespace patchage {
-class Driver;
-class ILog;
-
-/// Controller that makes and breaks connections on the system
-class Connector
-{
-public:
- explicit Connector(ILog& log);
-
- void add_driver(PortID::Type type, Driver* driver);
-
- void connect(const PortID& tail, const PortID& head);
- void disconnect(const PortID& tail, const PortID& head);
-
-private:
- ILog& _log;
- std::unordered_map<PortID::Type, Driver*> _drivers;
-};
+/// Sink function for user actions
+using ActionSink = std::function<void(Action)>;
} // namespace patchage
-#endif // PATCHAGE_CONNECTOR_HPP
+#endif // PATCHAGE_ACTION_SINK_HPP
diff --git a/src/Canvas.cpp b/src/Canvas.cpp
index b9e5e52..aba9f75 100644
--- a/src/Canvas.cpp
+++ b/src/Canvas.cpp
@@ -1,5 +1,5 @@
/* This file is part of Patchage.
- * Copyright 2007-2020 David Robillard <d@drobilla.net>
+ * Copyright 2007-2021 David Robillard <d@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
@@ -16,11 +16,11 @@
#include "Canvas.hpp"
+#include "Action.hpp"
#include "CanvasModule.hpp"
#include "CanvasPort.hpp"
#include "ClientInfo.hpp"
#include "Configuration.hpp"
-#include "Connector.hpp"
#include "ILog.hpp"
#include "Metadata.hpp"
#include "Patchage.hpp"
@@ -48,6 +48,8 @@ PATCHAGE_RESTORE_WARNINGS
#include <sigc++/signal.h>
#include <cassert>
+#include <cstdlib>
+#include <functional>
#include <iosfwd>
#include <set>
#include <string>
@@ -55,9 +57,9 @@ PATCHAGE_RESTORE_WARNINGS
namespace patchage {
-Canvas::Canvas(Connector& connector, int width, int height)
+Canvas::Canvas(ActionSink& action_sink, int width, int height)
: Ganv::Canvas(width, height)
- , _connector(connector)
+ , _action_sink(action_sink)
{
signal_event.connect(sigc::mem_fun(this, &Canvas::on_event));
signal_connect.connect(sigc::mem_fun(this, &Canvas::on_connect));
@@ -97,9 +99,19 @@ Canvas::create_port(Patchage& patchage, const PortID& id, const PortInfo& info)
// Find or create parent module
CanvasModule* parent = find_module(client_id, module_type);
if (!parent) {
- parent = new CanvasModule(&patchage, client_name, module_type, client_id);
+ // Determine initial position
+ Coord loc;
+ if (!patchage.conf().get_module_location(client_name, module_type, loc)) {
+ // No position saved, come up with a pseudo-random one
+ loc.x = 20 + rand() % 640;
+ loc.y = 20 + rand() % 480;
+
+ patchage.conf().set_module_location(client_name, module_type, loc);
+ }
+
+ parent = new CanvasModule(
+ *this, _action_sink, client_name, module_type, client_id, loc.x, loc.y);
- parent->load_location();
add_module(client_id, parent);
}
@@ -234,7 +246,7 @@ Canvas::remove_ports(bool (*pred)(const CanvasPort*))
i = next;
}
- for (ClientID id : data.empty_clients) {
+ for (const ClientID& id : data.empty_clients) {
remove_module(id);
}
}
@@ -247,9 +259,9 @@ Canvas::on_connect(Ganv::Node* port1, Ganv::Node* port2)
if (p1 && p2) {
if (p1->is_output() && p2->is_input()) {
- _connector.connect(p1->id(), p2->id());
+ _action_sink(action::ConnectPorts{p1->id(), p2->id()});
} else if (p2->is_output() && p1->is_input()) {
- _connector.connect(p2->id(), p1->id());
+ _action_sink(action::ConnectPorts{p2->id(), p1->id()});
}
}
}
@@ -262,9 +274,9 @@ Canvas::on_disconnect(Ganv::Node* port1, Ganv::Node* port2)
if (p1 && p2) {
if (p1->is_output() && p2->is_input()) {
- _connector.disconnect(p1->id(), p2->id());
+ _action_sink(action::DisconnectPorts{p1->id(), p2->id()});
} else if (p2->is_output() && p1->is_input()) {
- _connector.disconnect(p2->id(), p1->id());
+ _action_sink(action::DisconnectPorts{p2->id(), p1->id()});
}
}
}
diff --git a/src/Canvas.hpp b/src/Canvas.hpp
index c571c5e..3a15ca6 100644
--- a/src/Canvas.hpp
+++ b/src/Canvas.hpp
@@ -1,5 +1,5 @@
/* This file is part of Patchage.
- * Copyright 2007-2020 David Robillard <d@drobilla.net>
+ * Copyright 2007-2021 David Robillard <d@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
@@ -17,6 +17,7 @@
#ifndef PATCHAGE_CANVAS_HPP
#define PATCHAGE_CANVAS_HPP
+#include "ActionSink.hpp"
#include "ClientID.hpp"
#include "PortID.hpp"
#include "SignalDirection.hpp"
@@ -39,15 +40,14 @@ namespace patchage {
struct PortInfo;
-class Patchage;
class CanvasModule;
class CanvasPort;
-class Connector;
+class Patchage;
class Canvas : public Ganv::Canvas
{
public:
- Canvas(Connector& connector, int width, int height);
+ Canvas(ActionSink& action_sink, int width, int height);
CanvasPort* create_port(Patchage& patchage,
const PortID& id,
@@ -79,7 +79,7 @@ private:
void on_connect(Ganv::Node* port1, Ganv::Node* port2);
void on_disconnect(Ganv::Node* port1, Ganv::Node* port2);
- Connector& _connector;
+ ActionSink& _action_sink;
PortIndex _port_index;
ModuleIndex _module_index;
};
diff --git a/src/CanvasModule.cpp b/src/CanvasModule.cpp
index f5b750e..8d69218 100644
--- a/src/CanvasModule.cpp
+++ b/src/CanvasModule.cpp
@@ -1,5 +1,5 @@
/* This file is part of Patchage.
- * Copyright 2010-2020 David Robillard <d@drobilla.net>
+ * Copyright 2010-2021 David Robillard <d@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
@@ -16,10 +16,9 @@
#include "CanvasModule.hpp"
+#include "Action.hpp"
#include "Canvas.hpp"
#include "CanvasPort.hpp"
-#include "Configuration.hpp"
-#include "Patchage.hpp"
#include "PortID.hpp"
#include "SignalDirection.hpp"
#include "warnings.hpp"
@@ -37,30 +36,27 @@ PATCHAGE_RESTORE_WARNINGS
#include <sigc++/signal.h>
#include <cassert>
-#include <cstdlib>
+#include <functional>
#include <memory>
#include <utility>
namespace patchage {
-CanvasModule::CanvasModule(Patchage* app,
+CanvasModule::CanvasModule(Canvas& canvas,
+ ActionSink& action_sink,
const std::string& name,
SignalDirection type,
ClientID id,
double x,
double y)
- : Module(*app->canvas(), name, x, y)
- , _app(app)
+ : Module(canvas, name, x, y)
+ , _action_sink(action_sink)
, _name(name)
, _type(type)
, _id(std::move(id))
{
signal_event().connect(sigc::mem_fun(this, &CanvasModule::on_event));
-
- signal_moved().connect(sigc::mem_fun(this, &CanvasModule::store_location));
-
- // Set as source by default, turned off if input ports added
- set_is_source(true);
+ signal_moved().connect(sigc::mem_fun(this, &CanvasModule::on_moved));
}
void
@@ -98,15 +94,15 @@ CanvasModule::show_menu(GdkEventButton* ev)
if (_type == SignalDirection::duplex) {
items.push_back(Gtk::Menu_Helpers::MenuElem(
- "_Split", sigc::mem_fun(this, &CanvasModule::split)));
+ "_Split", sigc::mem_fun(this, &CanvasModule::on_split)));
update_menu();
} else {
items.push_back(Gtk::Menu_Helpers::MenuElem(
- "_Join", sigc::mem_fun(this, &CanvasModule::join)));
+ "_Join", sigc::mem_fun(this, &CanvasModule::on_join)));
}
items.push_back(Gtk::Menu_Helpers::MenuElem(
- "_Disconnect All", sigc::mem_fun(this, &CanvasModule::disconnect_all)));
+ "_Disconnect", sigc::mem_fun(this, &CanvasModule::on_disconnect)));
_menu->popup(ev->button, ev->time);
return true;
@@ -122,50 +118,29 @@ CanvasModule::on_event(GdkEvent* ev)
}
void
-CanvasModule::load_location()
-{
- Coord loc;
-
- if (_app->conf().get_module_location(_name, _type, loc)) {
- move_to(loc.x, loc.y);
- } else {
- const double x = 20 + rand() % 640;
- const double y = 20 + rand() % 480;
-
- // Move, then store generated location so it is stable
- move_to(x, y);
- store_location(x, y);
- }
-}
-
-void
-CanvasModule::store_location(double x, double y)
+CanvasModule::on_moved(double x, double y)
{
- _app->conf().set_module_location(_name, _type, {x, y});
+ _action_sink(action::MoveModule{_id, _type, x, y});
}
void
-CanvasModule::split()
+CanvasModule::on_split()
{
assert(_type == SignalDirection::duplex);
- _app->conf().set_module_split(_name, true);
- _app->refresh();
+ _action_sink(action::SplitModule{_id});
}
void
-CanvasModule::join()
+CanvasModule::on_join()
{
assert(_type != SignalDirection::duplex);
- _app->conf().set_module_split(_name, false);
- _app->refresh();
+ _action_sink(action::UnsplitModule{_id});
}
void
-CanvasModule::disconnect_all()
+CanvasModule::on_disconnect()
{
- for (Ganv::Port* p : *this) {
- p->disconnect();
- }
+ _action_sink(action::DisconnectClient{_id, _type});
}
CanvasPort*
diff --git a/src/CanvasModule.hpp b/src/CanvasModule.hpp
index 4f8412e..2dcdcc1 100644
--- a/src/CanvasModule.hpp
+++ b/src/CanvasModule.hpp
@@ -1,5 +1,5 @@
/* This file is part of Patchage.
- * Copyright 2007-2020 David Robillard <d@drobilla.net>
+ * Copyright 2007-2021 David Robillard <d@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
@@ -17,6 +17,7 @@
#ifndef PATCHAGE_CANVASMODULE_HPP
#define PATCHAGE_CANVASMODULE_HPP
+#include "ActionSink.hpp"
#include "ClientID.hpp"
#include "SignalDirection.hpp"
#include "warnings.hpp"
@@ -38,18 +39,21 @@ namespace patchage {
struct PortID;
+class Configuration;
+class Canvas;
+
class CanvasPort;
-class Patchage;
class CanvasModule : public Ganv::Module
{
public:
- CanvasModule(Patchage* app,
+ CanvasModule(Canvas& canvas,
+ ActionSink& action_sink,
const std::string& name,
SignalDirection type,
ClientID id,
- double x = 0.0,
- double y = 0.0);
+ double x,
+ double y);
CanvasModule(const CanvasModule&) = delete;
CanvasModule& operator=(const CanvasModule&) = delete;
@@ -57,26 +61,25 @@ public:
CanvasModule(CanvasModule&&) = delete;
CanvasModule& operator=(CanvasModule&&) = delete;
+ ~CanvasModule() override = default;
+
bool show_menu(GdkEventButton* ev);
void update_menu();
- void split();
- void join();
- void disconnect_all();
-
CanvasPort* get_port(const PortID& id);
- void load_location();
- void store_location(double x, double y);
-
SignalDirection type() const { return _type; }
ClientID id() const { return _id; }
const std::string& name() const { return _name; }
protected:
bool on_event(GdkEvent* ev) override;
+ void on_moved(double x, double y);
+ void on_split();
+ void on_join();
+ void on_disconnect();
- Patchage* _app;
+ ActionSink& _action_sink;
std::unique_ptr<Gtk::Menu> _menu;
std::string _name;
SignalDirection _type;
diff --git a/src/Connector.cpp b/src/Connector.cpp
deleted file mode 100644
index fe008ce..0000000
--- a/src/Connector.cpp
+++ /dev/null
@@ -1,72 +0,0 @@
-/* This file is part of Patchage.
- * Copyright 2007-2020 David Robillard <d@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 "Connector.hpp"
-
-#include "Driver.hpp"
-#include "ILog.hpp"
-#include "PortID.hpp"
-
-#include <unordered_map>
-#include <utility>
-
-namespace patchage {
-
-Connector::Connector(ILog& log)
- : _log(log)
-{}
-
-void
-Connector::add_driver(PortID::Type type, Driver* driver)
-{
- _drivers.emplace(type, driver);
-}
-
-void
-Connector::connect(const PortID& tail, const PortID& head)
-{
- if (tail.type() != head.type()) {
- _log.warning("Unable to connect incompatible port types");
- return;
- }
-
- auto d = _drivers.find(tail.type());
- if (d == _drivers.end()) {
- _log.error("No driver for port type");
- return;
- }
-
- d->second->connect(tail, head);
-}
-
-void
-Connector::disconnect(const PortID& tail, const PortID& head)
-{
- if (tail.type() != head.type()) {
- _log.error("Unable to disconnect incompatible port types");
- return;
- }
-
- auto d = _drivers.find(tail.type());
- if (d == _drivers.end()) {
- _log.error("No driver for port type");
- return;
- }
-
- d->second->disconnect(tail, head);
-}
-
-} // namespace patchage
diff --git a/src/Event.hpp b/src/Event.hpp
index 46fdb1e..378f35eb 100644
--- a/src/Event.hpp
+++ b/src/Event.hpp
@@ -1,5 +1,5 @@
/* This file is part of Patchage.
- * Copyright 2007-2020 David Robillard <d@drobilla.net>
+ * Copyright 2007-2021 David Robillard <d@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
@@ -66,7 +66,7 @@ struct PortsDisconnected {
} // namespace event
-/// An event from drivers that is processed by the GUI
+/// An event from drivers that represents a change to the system
using Event = boost::variant<event::ClientCreated,
event::ClientDestroyed,
event::DriverAttached,
diff --git a/src/Patchage.cpp b/src/Patchage.cpp
index baf7eaf..1246771 100644
--- a/src/Patchage.cpp
+++ b/src/Patchage.cpp
@@ -1,5 +1,5 @@
/* This file is part of Patchage.
- * Copyright 2007-2020 David Robillard <d@drobilla.net>
+ * Copyright 2007-2021 David Robillard <d@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
@@ -16,6 +16,7 @@
#include "Patchage.hpp"
+#include "Action.hpp"
#include "AudioDriver.hpp"
#include "Canvas.hpp"
#include "CanvasModule.hpp"
@@ -158,18 +159,6 @@ port_order(const GanvPort* a, const GanvPort* b, void*)
return 0;
}
-void
-load_module_location(GanvNode* node, void*)
-{
- if (GANV_IS_MODULE(node)) {
- Ganv::Module* gmod = Glib::wrap(GANV_MODULE(node));
- auto* pmod = dynamic_cast<CanvasModule*>(gmod);
- if (pmod) {
- pmod->load_location();
- }
- }
-}
-
} // namespace
#define INIT_WIDGET(x) x(_xml, (#x) + 1)
@@ -214,12 +203,14 @@ Patchage::Patchage(Options options)
, INIT_WIDGET(_status_text)
, _legend(nullptr)
, _log(_status_text)
- , _connector(_log)
+ , _reactor(*this)
+ , _action_sink([this](const Action& action) { _reactor(action); })
, _options{options}
, _pane_initialized(false)
, _attach(true)
{
- _canvas = std::unique_ptr<Canvas>{new Canvas(_connector, 1600 * 2, 1200 * 2)};
+ _canvas =
+ std::unique_ptr<Canvas>{new Canvas(_action_sink, 1600 * 2, 1200 * 2)};
Glib::set_application_name("Patchage");
_about_win->property_program_name() = "Patchage";
@@ -331,7 +322,7 @@ Patchage::Patchage(Options options)
_log, [this](const Event& event) { on_driver_event(event); });
if (_jack_driver) {
- _connector.add_driver(PortID::Type::jack, _jack_driver.get());
+ _reactor.add_driver(PortID::Type::jack, _jack_driver.get());
_menu_jack_connect->signal_activate().connect(sigc::bind(
sigc::mem_fun(_jack_driver.get(), &AudioDriver::attach), true));
@@ -347,7 +338,7 @@ Patchage::Patchage(Options options)
_log, [this](const Event& event) { on_driver_event(event); });
if (_alsa_driver) {
- _connector.add_driver(PortID::Type::alsa, _alsa_driver.get());
+ _reactor.add_driver(PortID::Type::alsa, _alsa_driver.get());
_menu_alsa_connect->signal_activate().connect(
sigc::bind(sigc::mem_fun(_alsa_driver.get(), &Driver::attach), false));
@@ -359,8 +350,6 @@ Patchage::Patchage(Options options)
_menu_alsa_disconnect->set_sensitive(false);
}
- _canvas->for_each_node(load_module_location, nullptr);
-
_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());
diff --git a/src/Patchage.hpp b/src/Patchage.hpp
index e108748..ecc1256 100644
--- a/src/Patchage.hpp
+++ b/src/Patchage.hpp
@@ -1,5 +1,5 @@
/* This file is part of Patchage.
- * Copyright 2007-2020 David Robillard <d@drobilla.net>
+ * Copyright 2007-2021 David Robillard <d@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
@@ -24,13 +24,14 @@
#include <gtkmm/treemodelcolumn.h>
#include <gtkmm/widget.h>
+#include "ActionSink.hpp"
#include "ClientType.hpp"
#include "Configuration.hpp"
-#include "Connector.hpp"
#include "Event.hpp"
#include "Metadata.hpp"
#include "Options.hpp"
#include "PortType.hpp"
+#include "Reactor.hpp"
#include "TextViewLog.hpp"
#include "Widget.hpp"
@@ -206,8 +207,9 @@ protected:
Widget<Gtk::TextView> _status_text;
Legend* _legend;
TextViewLog _log;
- Connector _connector;
Metadata _metadata;
+ Reactor _reactor;
+ ActionSink _action_sink;
Glib::RefPtr<Gtk::TextTag> _error_tag;
Glib::RefPtr<Gtk::TextTag> _warning_tag;
diff --git a/src/Reactor.cpp b/src/Reactor.cpp
new file mode 100644
index 0000000..c81df22
--- /dev/null
+++ b/src/Reactor.cpp
@@ -0,0 +1,153 @@
+/* This file is part of Patchage.
+ * Copyright 2007-2021 David Robillard <d@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 "Reactor.hpp"
+
+#include "Canvas.hpp"
+#include "CanvasModule.hpp"
+#include "CanvasPort.hpp"
+#include "ClientID.hpp"
+#include "Configuration.hpp"
+#include "Driver.hpp"
+#include "ILog.hpp"
+#include "Patchage.hpp"
+#include "PortID.hpp"
+#include "warnings.hpp"
+
+#include "ganv/Port.hpp"
+
+PATCHAGE_DISABLE_FMT_WARNINGS
+#include <fmt/core.h>
+PATCHAGE_RESTORE_WARNINGS
+
+#include <boost/variant/apply_visitor.hpp>
+
+#include <memory>
+#include <unordered_map>
+#include <utility>
+
+namespace patchage {
+
+Reactor::Reactor(Patchage& patchage)
+ : _patchage{patchage}
+ , _log{patchage.log()}
+{}
+
+void
+Reactor::add_driver(PortID::Type type, Driver* driver)
+{
+ _drivers.emplace(type, driver);
+}
+
+void
+Reactor::operator()(const action::ConnectPorts& action)
+{
+ if (action.tail.type() != action.head.type()) {
+ _log.warning("Unable to connect incompatible port types");
+ return;
+ }
+
+ auto d = _drivers.find(action.tail.type());
+ if (d == _drivers.end()) {
+ _log.error(fmt::format("No driver for port type {}", action.tail.type()));
+ return;
+ }
+
+ d->second->connect(action.tail, action.head);
+}
+
+void
+Reactor::operator()(const action::DisconnectClient& action)
+{
+ if (CanvasModule* mod = find_module(action.client, action.direction)) {
+ for (Ganv::Port* p : *mod) {
+ p->disconnect();
+ }
+ }
+}
+
+void
+Reactor::operator()(const action::DisconnectPort& action)
+{
+ if (CanvasPort* port = find_port(action.port)) {
+ port->disconnect();
+ }
+}
+
+void
+Reactor::operator()(const action::DisconnectPorts& action)
+{
+ if (action.tail.type() != action.head.type()) {
+ _log.error("Unable to disconnect incompatible port types");
+ return;
+ }
+
+ auto d = _drivers.find(action.tail.type());
+ if (d == _drivers.end()) {
+ _log.error("No driver for port type");
+ return;
+ }
+
+ d->second->disconnect(action.tail, action.head);
+}
+
+void
+Reactor::operator()(const action::MoveModule& action)
+{
+ if (CanvasModule* mod = find_module(action.client, action.direction)) {
+ _patchage.conf().set_module_location(
+ mod->name(), action.direction, {action.x, action.y});
+ }
+}
+
+void
+Reactor::operator()(const action::SplitModule& action)
+{
+ if (CanvasModule* mod = find_module(action.client, SignalDirection::duplex)) {
+ _patchage.conf().set_module_split(mod->name(), true);
+ _patchage.refresh();
+ }
+}
+
+void
+Reactor::operator()(const action::UnsplitModule& action)
+{
+ CanvasModule* mod = find_module(action.client, SignalDirection::input);
+ if (mod || (mod = find_module(action.client, SignalDirection::output))) {
+ _patchage.conf().set_module_split(mod->name(), false);
+ _patchage.refresh();
+ }
+}
+
+void
+Reactor::operator()(const Action& action)
+{
+ boost::apply_visitor(*this, action);
+}
+
+CanvasModule*
+Reactor::find_module(const ClientID& client, const SignalDirection type)
+{
+ return _patchage.canvas()->find_module(client, type);
+}
+
+CanvasPort*
+Reactor::find_port(const PortID& port)
+{
+ return _patchage.canvas()->find_port(port);
+}
+
+} // namespace patchage
diff --git a/src/Reactor.hpp b/src/Reactor.hpp
new file mode 100644
index 0000000..bb31cca
--- /dev/null
+++ b/src/Reactor.hpp
@@ -0,0 +1,74 @@
+/* This file is part of Patchage.
+ * Copyright 2007-2021 David Robillard <d@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/>.
+ */
+
+#ifndef PATCHAGE_REACTOR_HPP
+#define PATCHAGE_REACTOR_HPP
+
+#include "Action.hpp"
+#include "ClientID.hpp"
+#include "PortID.hpp"
+#include "SignalDirection.hpp"
+
+#include <unordered_map>
+
+namespace patchage {
+
+class CanvasModule;
+class CanvasPort;
+class Driver;
+class ILog;
+class Patchage;
+
+/// Reacts to actions from the user
+class Reactor
+{
+public:
+ using result_type = void; ///< For boost::apply_visitor
+
+ explicit Reactor(Patchage& patchage);
+
+ Reactor(const Reactor&) = delete;
+ Reactor& operator=(const Reactor&) = delete;
+
+ Reactor(Reactor&&) = delete;
+ Reactor& operator=(Reactor&&) = delete;
+
+ ~Reactor() = default;
+
+ void add_driver(PortID::Type type, Driver* driver);
+
+ void operator()(const action::ConnectPorts& action);
+ void operator()(const action::DisconnectClient& action);
+ void operator()(const action::DisconnectPort& action);
+ void operator()(const action::DisconnectPorts& action);
+ void operator()(const action::MoveModule& action);
+ void operator()(const action::SplitModule& action);
+ void operator()(const action::UnsplitModule& action);
+
+ void operator()(const Action& action);
+
+private:
+ CanvasModule* find_module(const ClientID& client, SignalDirection type);
+ CanvasPort* find_port(const PortID& port);
+
+ Patchage& _patchage;
+ ILog& _log;
+ std::unordered_map<PortID::Type, Driver*> _drivers;
+};
+
+} // namespace patchage
+
+#endif // PATCHAGE_REACTOR_HPP
diff --git a/src/handle_event.hpp b/src/handle_event.hpp
index 975b3bb..6a62d61 100644
--- a/src/handle_event.hpp
+++ b/src/handle_event.hpp
@@ -1,5 +1,5 @@
/* This file is part of Patchage.
- * Copyright 2007-2020 David Robillard <d@drobilla.net>
+ * Copyright 2007-2021 David Robillard <d@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
@@ -23,7 +23,7 @@ namespace patchage {
class Patchage;
-/// Handle an event in the GUI
+/// Handle an event from the system by updating the GUI as necessary
void
handle_event(Patchage& patchage, const Event& event);
diff --git a/wscript b/wscript
index 5e3d549..2213297 100644
--- a/wscript
+++ b/wscript
@@ -240,10 +240,10 @@ def build(bld):
src/Canvas.cpp
src/CanvasModule.cpp
src/Configuration.cpp
- src/Connector.cpp
src/Legend.cpp
src/Metadata.cpp
src/Patchage.cpp
+ src/Reactor.cpp
src/TextViewLog.cpp
src/event_to_string.cpp
src/handle_event.cpp