From aef939ff10362285ce1ebd872518627e524917bc Mon Sep 17 00:00:00 2001
From: David Robillard <d@drobilla.net>
Date: Thu, 19 Feb 2015 09:44:41 +0000
Subject: Server side presets.

git-svn-id: http://svn.drobilla.net/lad/trunk/ingen@5587 a436a847-0d15-0410-975c-d299462d15a1
---
 ingen/Status.hpp                   |  2 ++
 ingen/URIs.hpp                     |  1 +
 src/URIs.cpp                       |  2 ++
 src/gui/NodeMenu.cpp               | 60 ++++--------------------------------
 src/gui/NodeMenu.hpp               |  5 ++-
 src/server/BlockImpl.cpp           | 11 +++++++
 src/server/BlockImpl.hpp           | 11 +++++++
 src/server/LV2Block.cpp            | 27 ++++++++++++++++
 src/server/LV2Block.hpp            |  4 +++
 src/server/events/Delta.cpp        | 63 +++++++++++++++++++++++++++++++++++++-
 src/server/events/Delta.hpp        | 11 ++++++-
 src/server/events/SetPortValue.cpp |  4 ++-
 src/server/events/SetPortValue.hpp |  6 +++-
 wscript                            |  2 +-
 14 files changed, 149 insertions(+), 60 deletions(-)

diff --git a/ingen/Status.hpp b/ingen/Status.hpp
index ed388ccf..1fae5358 100644
--- a/ingen/Status.hpp
+++ b/ingen/Status.hpp
@@ -28,6 +28,7 @@ enum class Status {
 	BAD_REQUEST,
 	BAD_URI,
 	BAD_VALUE_TYPE,
+	BAD_VALUE,
 	CLIENT_NOT_FOUND,
 	CREATION_FAILED,
 	DIRECTION_MISMATCH,
@@ -60,6 +61,7 @@ ingen_status_string(Status st)
 	case Status::BAD_REQUEST:         return "Invalid request";
 	case Status::BAD_URI:             return "Invalid URI";
 	case Status::BAD_VALUE_TYPE:      return "Invalid value type";
+	case Status::BAD_VALUE:           return "Invalid value";
 	case Status::CLIENT_NOT_FOUND:    return "Client not found";
 	case Status::CREATION_FAILED:     return "Creation failed";
 	case Status::DIRECTION_MISMATCH:  return "Direction mismatch";
diff --git a/ingen/URIs.hpp b/ingen/URIs.hpp
index 70406f57..2dc3f2f6 100644
--- a/ingen/URIs.hpp
+++ b/ingen/URIs.hpp
@@ -149,6 +149,7 @@ public:
 	const Quark patch_subject;
 	const Quark patch_value;
 	const Quark patch_wildcard;
+	const Quark pset_preset;
 	const Quark pprops_logarithmic;
 	const Quark rdf_type;
 	const Quark rdfs_seeAlso;
diff --git a/src/URIs.cpp b/src/URIs.cpp
index a70e8d77..66d7965b 100644
--- a/src/URIs.cpp
+++ b/src/URIs.cpp
@@ -24,6 +24,7 @@
 #include "lv2/lv2plug.in/ns/ext/morph/morph.h"
 #include "lv2/lv2plug.in/ns/ext/parameters/parameters.h"
 #include "lv2/lv2plug.in/ns/ext/patch/patch.h"
+#include "lv2/lv2plug.in/ns/ext/presets/presets.h"
 #include "lv2/lv2plug.in/ns/ext/port-props/port-props.h"
 #include "lv2/lv2plug.in/ns/ext/resize-port/resize-port.h"
 #include "lv2/lv2plug.in/ns/ext/time/time.h"
@@ -139,6 +140,7 @@ URIs::URIs(Forge& f, URIMap* map)
 	, patch_subject         (forge, map, LV2_PATCH__subject)
 	, patch_value           (forge, map, LV2_PATCH__value)
 	, patch_wildcard        (forge, map, LV2_PATCH__wildcard)
+	, pset_preset           (forge, map, LV2_PRESETS__preset)
 	, pprops_logarithmic    (forge, map, LV2_PORT_PROPS__logarithmic)
 	, rdf_type              (forge, map, NS_RDF "type")
 	, rdfs_seeAlso          (forge, map, NS_RDFS "seeAlso")
diff --git a/src/gui/NodeMenu.cpp b/src/gui/NodeMenu.cpp
index 08524ade..b99ce546 100644
--- a/src/gui/NodeMenu.cpp
+++ b/src/gui/NodeMenu.cpp
@@ -108,18 +108,11 @@ NodeMenu::init(App& app, SPtr<const Client::BlockModel> block)
 								sigc::mem_fun(this, &NodeMenu::on_preset_activated),
 								string(lilv_node_as_string(preset)))));
 
-					// I have no idea why this is necessary, signal_activated doesn't work
-					// in this menu (and only this menu)
-					Gtk::MenuItem* item = &(_presets_menu->items().back());
-					item->signal_button_release_event().connect(
-						sigc::bind<0>(sigc::mem_fun(this, &NodeMenu::on_preset_clicked),
-						              string(lilv_node_as_string(preset))));
-
 					lilv_nodes_free(labels);
 					++n_presets;
 				} else {
 					app.log().error(
-						fmt("Preset <%1> has no rdfs:label\n")
+						fmt("Preset <%1%> has no rdfs:label\n")
 						% lilv_node_as_string(lilv_nodes_get(presets, i)));
 				}
 			}
@@ -177,7 +170,7 @@ NodeMenu::on_menu_randomize()
 {
 	_app->interface()->bundle_begin();
 
-	const BlockModel* const bm = (const BlockModel*)_object.get();
+	const SPtr<const BlockModel> bm = block();
 	for (const auto& p : bm->ports()) {
 		if (p->is_input() && _app->can_control(p.get())) {
 			float min = 0.0f, max = 1.0f;
@@ -198,60 +191,19 @@ NodeMenu::on_menu_disconnect()
 	_app->interface()->disconnect_all(_object->parent()->path(), _object->path());
 }
 
-static void
-set_port_value(const char* port_symbol,
-               void*       user_data,
-               const void* value,
-               uint32_t    size,
-               uint32_t    type)
-{
-	NodeMenu*               menu  = (NodeMenu*)user_data;
-	const BlockModel* const block = (const BlockModel*)menu->object().get();
-
-	if (!Raul::Symbol::is_valid(port_symbol)) {
-		menu->app()->log().error(
-			fmt("Preset with invalid port symbol `%1'\n") % port_symbol);
-		return;
-	}
-
-	menu->app()->set_property(
-		Node::path_to_uri(block->path().child(Raul::Symbol(port_symbol))),
-		menu->app()->uris().ingen_value,
-		menu->app()->forge().alloc(size, type, value));
-}
-
 void
 NodeMenu::on_preset_activated(const std::string& uri)
 {
-	const BlockModel* const  block  = (const BlockModel*)_object.get();
-	const PluginModel* const plugin = dynamic_cast<const PluginModel*>(block->plugin());
-
-	LilvNode*  pset  = lilv_new_uri(plugin->lilv_world(), uri.c_str());
-	LilvState* state = lilv_state_new_from_world(
-		plugin->lilv_world(),
-		&_app->world()->uri_map().urid_map_feature()->urid_map,
-		pset);
-
-	if (state) {
-		lilv_state_restore(state, NULL, set_port_value, this, 0, NULL);
-		lilv_state_free(state);
-	}
-
-	lilv_node_free(pset);
-}
+	_app->set_property(block()->uri(),
+	                   _app->uris().pset_preset,
+	                   _app->forge().alloc_uri(uri));
 
-bool
-NodeMenu::on_preset_clicked(const std::string& uri, GdkEventButton* ev)
-{
-	on_preset_activated(uri);
-	return false;
 }
 
 bool
 NodeMenu::has_control_inputs()
 {
-	const BlockModel* const bm = (const BlockModel*)_object.get();
-	for (const auto& p : bm->ports())
+	for (const auto& p : block()->ports())
 		if (p->is_input() && p->is_numeric())
 			return true;
 
diff --git a/src/gui/NodeMenu.hpp b/src/gui/NodeMenu.hpp
index d84bc84f..f28fe334 100644
--- a/src/gui/NodeMenu.hpp
+++ b/src/gui/NodeMenu.hpp
@@ -48,12 +48,15 @@ public:
 	sigc::signal<void, bool> signal_embed_gui;
 
 protected:
+	SPtr<const Client::BlockModel> block() const {
+		return dynamic_ptr_cast<const Client::BlockModel>(_object);
+	}
+
 	void on_menu_disconnect();
 	void on_menu_embed_gui();
 	void on_menu_enabled();
 	void on_menu_randomize();
 	void on_preset_activated(const std::string& uri);
-	bool on_preset_clicked(const std::string& uri, GdkEventButton* ev);
 
 	Gtk::MenuItem*      _popup_gui_menuitem;
 	Gtk::CheckMenuItem* _embed_gui_menuitem;
diff --git a/src/server/BlockImpl.cpp b/src/server/BlockImpl.cpp
index ce6b169c..d21514ef 100644
--- a/src/server/BlockImpl.cpp
+++ b/src/server/BlockImpl.cpp
@@ -160,6 +160,17 @@ BlockImpl::nth_port_by_type(uint32_t n, bool input, PortType type)
 	return NULL;
 }
 
+PortImpl*
+BlockImpl::port_by_symbol(const char* symbol)
+{
+	for (uint32_t p = 0; _ports && p < _ports->size(); ++p) {
+		if (_ports->at(p)->symbol() == symbol) {
+			return _ports->at(p);
+		}
+	}
+	return NULL;
+}
+
 void
 BlockImpl::pre_process(ProcessContext& context)
 {
diff --git a/src/server/BlockImpl.hpp b/src/server/BlockImpl.hpp
index b3064168..965adbcc 100644
--- a/src/server/BlockImpl.hpp
+++ b/src/server/BlockImpl.hpp
@@ -21,6 +21,8 @@
 
 #include <boost/intrusive/slist.hpp>
 
+#include "lilv/lilv.h"
+
 #include "raul/Array.hpp"
 
 #include "BufferRef.hpp"
@@ -98,6 +100,12 @@ public:
 	/** Enable or disable (bypass) this block. */
 	void set_enabled(bool e) { _enabled = e; }
 
+	/** Load a preset from the world for this block. */
+	virtual LilvState* load_preset(const Raul::URI& uri) { return NULL; }
+
+	/** Restore `state`. */
+	virtual void apply_state(LilvState* state) {}
+
 	/** Learn the next incoming MIDI event (for internals) */
 	virtual void learn() {}
 
@@ -122,6 +130,9 @@ public:
 	virtual Node*     port(uint32_t index)      const;
 	virtual PortImpl* port_impl(uint32_t index) const { return (*_ports)[index]; }
 
+	/** Get a port by symbol. */
+	virtual PortImpl* port_by_symbol(const char* symbol);
+
 	/** Blocks that are connected to this Block's inputs. */
 	std::list<BlockImpl*>& providers() { return _providers; }
 
diff --git a/src/server/LV2Block.cpp b/src/server/LV2Block.cpp
index 3f6f4be1..64c80764 100644
--- a/src/server/LV2Block.cpp
+++ b/src/server/LV2Block.cpp
@@ -20,6 +20,7 @@
 #include <cmath>
 
 #include "lv2/lv2plug.in/ns/ext/morph/morph.h"
+#include "lv2/lv2plug.in/ns/ext/presets/presets.h"
 #include "lv2/lv2plug.in/ns/ext/options/options.h"
 #include "lv2/lv2plug.in/ns/ext/resize-port/resize-port.h"
 #include "lv2/lv2plug.in/ns/ext/state/state.h"
@@ -541,6 +542,32 @@ LV2Block::post_process(ProcessContext& context)
 	}
 }
 
+LilvState*
+LV2Block::load_preset(const Raul::URI& uri)
+{
+	World*     world  = &_lv2_plugin->lv2_info()->world();
+	LilvWorld* lworld = _lv2_plugin->lv2_info()->lv2_world();
+	LilvNode*  preset = lilv_new_uri(lworld, uri.c_str());
+
+	// Load preset into world if necessary
+	lilv_world_load_resource(lworld, preset);
+
+	// Load preset from world
+	LV2_URID_Map* map   = &world->uri_map().urid_map_feature()->urid_map;
+	LilvState*    state = lilv_state_new_from_world(lworld, map, preset);
+
+	lilv_node_free(preset);
+	return state;
+}
+
+void
+LV2Block::apply_state(LilvState* state)
+{
+	for (uint32_t v = 0; v < _polyphony; ++v) {
+		lilv_state_restore(state, instance(v), NULL, NULL, 0, NULL);
+	}
+}
+
 void
 LV2Block::set_port_buffer(uint32_t    voice,
                           uint32_t    port_num,
diff --git a/src/server/LV2Block.hpp b/src/server/LV2Block.hpp
index ec2f99f4..6b343df7 100644
--- a/src/server/LV2Block.hpp
+++ b/src/server/LV2Block.hpp
@@ -63,6 +63,10 @@ public:
 	void run(ProcessContext& context);
 	void post_process(ProcessContext& context);
 
+	LilvState* load_preset(const Raul::URI& uri);
+
+	void apply_state(LilvState* state);
+
 	void set_port_buffer(uint32_t    voice,
 	                     uint32_t    port_num,
 	                     BufferRef   buf,
diff --git a/src/server/events/Delta.cpp b/src/server/events/Delta.cpp
index 03a7ccd5..8df79994 100644
--- a/src/server/events/Delta.cpp
+++ b/src/server/events/Delta.cpp
@@ -17,6 +17,7 @@
 #include <vector>
 #include <thread>
 
+#include "ingen/Log.hpp"
 #include "ingen/Store.hpp"
 #include "ingen/URIs.hpp"
 #include "raul/Maid.hpp"
@@ -60,6 +61,7 @@ Delta::Delta(Engine&           engine,
 	, _object(NULL)
 	, _graph(NULL)
 	, _compiled_graph(NULL)
+	, _state(NULL)
 	, _context(context)
 	, _type(type)
 	, _poly_lock(engine.store()->mutex(), std::defer_lock)
@@ -98,6 +100,37 @@ Delta::~Delta()
 	delete _create_event;
 }
 
+void
+Delta::add_set_event(const char* port_symbol,
+                     const void* value,
+                     uint32_t    size,
+                     uint32_t    type)
+{
+	BlockImpl* block = dynamic_cast<BlockImpl*>(_object);
+	PortImpl*  port  = block->port_by_symbol(port_symbol);
+	if (!port) {
+		_engine.log().warn(fmt("Unknown port `%1' in state") % port_symbol);
+		return;
+	}
+
+	SetPortValue* ev = new SetPortValue(
+		_engine, _request_client, _request_id, _time,
+		port, Atom(size, type, value), true);
+
+	ev->pre_process();
+	_set_events.push_back(ev);
+}
+
+static void
+s_add_set_event(const char* port_symbol,
+                void*       user_data,
+                const void* value,
+                uint32_t    size,
+                uint32_t    type)
+{
+	((Delta*)user_data)->add_set_event(port_symbol, value, size, type);
+}
+
 bool
 Delta::pre_process()
 {
@@ -216,6 +249,22 @@ Delta::pre_process()
 					} else {
 						_status = Status::BAD_VALUE_TYPE;
 					}
+				} else if (key == uris.pset_preset) {
+					if (value.type() == uris.forge.URI) {
+						const char* str = value.ptr<char>();
+						if (Raul::URI::is_valid(str)) {
+							op = SpecialType::PRESET;
+							const Raul::URI uri(str);
+							if ((_state = block->load_preset(Raul::URI(str)))) {
+								lilv_state_emit_port_values(
+									_state, s_add_set_event, this);
+							}
+						} else {
+							_status = Status::BAD_VALUE;
+						}
+					} else {
+						_status = Status::BAD_VALUE_TYPE;
+					}
 				}
 			}
 
@@ -362,6 +411,9 @@ Delta::execute(ProcessContext& context)
 				}
 			}
 			break;
+        case SpecialType::PRESET:
+	        block->set_enabled(false);
+			break;
 		case SpecialType::NONE:
 			if (port) {
 				if (key == uris.lv2_minimum) {
@@ -382,6 +434,15 @@ Delta::post_process()
 		_poly_lock.unlock();
 	}
 
+	if (_state) {
+		BlockImpl* block = dynamic_cast<BlockImpl*>(_object);
+		if (block) {
+			block->apply_state(_state);
+			block->set_enabled(true);
+		}
+		lilv_state_free(_state);
+	}
+
 	Broadcaster::Transfer t(*_engine.broadcaster());
 
 	if (_create_event) {
@@ -392,7 +453,7 @@ Delta::post_process()
 	}
 
 	for (auto& s : _set_events) {
-		if (s->status() != Status::SUCCESS) {
+		if (s->synthetic() || s->status() != Status::SUCCESS) {
 			s->post_process();  // Set failed, report error
 		}
 	}
diff --git a/src/server/events/Delta.hpp b/src/server/events/Delta.hpp
index 43edacbf..750411ff 100644
--- a/src/server/events/Delta.hpp
+++ b/src/server/events/Delta.hpp
@@ -19,6 +19,8 @@
 
 #include <vector>
 
+#include "lilv/lilv.h"
+
 #include "raul/URI.hpp"
 
 #include "ControlBindings.hpp"
@@ -87,6 +89,11 @@ public:
 
 	~Delta();
 
+	void add_set_event(const char* port_symbol,
+	                   const void* value,
+	                   uint32_t    size,
+	                   uint32_t    type);
+
 	bool pre_process();
 	void execute(ProcessContext& context);
 	void post_process();
@@ -98,7 +105,8 @@ private:
 		ENABLE_BROADCAST,
 		POLYPHONY,
 		POLYPHONIC,
-		CONTROL_BINDING
+		CONTROL_BINDING,
+		PRESET
 	};
 
 	typedef std::vector<SetPortValue*> SetEvents;
@@ -113,6 +121,7 @@ private:
 	Ingen::Resource*         _object;
 	GraphImpl*               _graph;
 	CompiledGraph*           _compiled_graph;
+	LilvState*               _state;
 	Resource::Graph          _context;
 	ControlBindings::Key     _binding;
 	Type                     _type;
diff --git a/src/server/events/SetPortValue.cpp b/src/server/events/SetPortValue.cpp
index df0cb06d..655ff281 100644
--- a/src/server/events/SetPortValue.cpp
+++ b/src/server/events/SetPortValue.cpp
@@ -39,10 +39,12 @@ SetPortValue::SetPortValue(Engine&         engine,
                            int32_t         id,
                            SampleCount     timestamp,
                            PortImpl*       port,
-                           const Atom&     value)
+                           const Atom&     value,
+                           bool            synthetic)
 	: Event(engine, client, id, timestamp)
 	, _port(port)
 	, _value(value)
+	, _synthetic(synthetic)
 {
 }
 
diff --git a/src/server/events/SetPortValue.hpp b/src/server/events/SetPortValue.hpp
index 852c694e..ed81db47 100644
--- a/src/server/events/SetPortValue.hpp
+++ b/src/server/events/SetPortValue.hpp
@@ -42,7 +42,8 @@ public:
 	             int32_t         id,
 	             SampleCount     timestamp,
 	             PortImpl*       port,
-	             const Atom&     value);
+	             const Atom&     value,
+	             bool            synthetic = false);
 
 	~SetPortValue();
 
@@ -50,12 +51,15 @@ public:
 	void execute(ProcessContext& context);
 	void post_process();
 
+	bool synthetic() const { return _synthetic; }
+
 private:
 	void apply(Context& context);
 
 	PortImpl*            _port;
 	const Atom           _value;
 	ControlBindings::Key _binding;
+	bool                 _synthetic;
 };
 
 } // namespace Events
diff --git a/wscript b/wscript
index 31384834..2ae08103 100644
--- a/wscript
+++ b/wscript
@@ -67,7 +67,7 @@ def configure(conf):
     autowaf.check_pkg(conf, 'gthread-2.0', uselib_store='GTHREAD',
                       atleast_version='2.14.0', mandatory=True)
     autowaf.check_pkg(conf, 'lilv-0', uselib_store='LILV',
-                      atleast_version='0.21.0', mandatory=True)
+                      atleast_version='0.21.2', mandatory=True)
     autowaf.check_pkg(conf, 'suil-0', uselib_store='SUIL',
                       atleast_version='0.2.0', mandatory=True)
     autowaf.check_pkg(conf, 'sratom-0', uselib_store='SRATOM',
-- 
cgit v1.2.1