From c8ae7295e911c62cf9dedf90187656937cc18cbb Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sat, 30 Jul 2016 13:10:13 -0400 Subject: Add undo support --- src/server/Broadcaster.hpp | 4 +- src/server/Engine.cpp | 14 +- src/server/Engine.hpp | 14 +- src/server/Event.hpp | 19 ++- src/server/EventWriter.cpp | 69 ++++++++-- src/server/EventWriter.hpp | 14 +- src/server/PreProcessor.cpp | 40 +++++- src/server/PreProcessor.hpp | 8 +- src/server/UndoStack.cpp | 252 ++++++++++++++++++++++++++++++++++++ src/server/UndoStack.hpp | 107 +++++++++++++++ src/server/events.hpp | 6 +- src/server/events/Connect.cpp | 8 +- src/server/events/Connect.hpp | 3 +- src/server/events/Copy.cpp | 8 +- src/server/events/Copy.hpp | 3 +- src/server/events/CreateBlock.cpp | 8 +- src/server/events/CreateBlock.hpp | 3 +- src/server/events/CreateGraph.cpp | 8 +- src/server/events/CreateGraph.hpp | 1 + src/server/events/CreatePort.cpp | 8 +- src/server/events/CreatePort.hpp | 3 +- src/server/events/Delete.cpp | 14 +- src/server/events/Delete.hpp | 3 +- src/server/events/Delta.cpp | 57 +++++++- src/server/events/Delta.hpp | 6 +- src/server/events/Disconnect.cpp | 8 +- src/server/events/Disconnect.hpp | 6 +- src/server/events/DisconnectAll.cpp | 10 +- src/server/events/DisconnectAll.hpp | 3 +- src/server/events/Mark.cpp | 65 ++++++++++ src/server/events/Mark.hpp | 55 ++++++++ src/server/events/Move.cpp | 8 +- src/server/events/Move.hpp | 3 +- src/server/events/Undo.cpp | 77 +++++++++++ src/server/events/Undo.hpp | 54 ++++++++ src/server/wscript | 3 + 36 files changed, 912 insertions(+), 60 deletions(-) create mode 100644 src/server/UndoStack.cpp create mode 100644 src/server/UndoStack.hpp create mode 100644 src/server/events/Mark.cpp create mode 100644 src/server/events/Mark.hpp create mode 100644 src/server/events/Undo.cpp create mode 100644 src/server/events/Undo.hpp (limited to 'src/server') diff --git a/src/server/Broadcaster.hpp b/src/server/Broadcaster.hpp index f7952a31..9efb9c30 100644 --- a/src/server/Broadcaster.hpp +++ b/src/server/Broadcaster.hpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -148,6 +148,8 @@ public: Raul::URI uri() const { return Raul::URI("ingen:/broadcaster"); } + void undo() {} ///< N/A + void redo() {} ///< N/A void set_response_id(int32_t id) {} ///< N/A void get(const Raul::URI& uri) {} ///< N/A void response(int32_t id, Status status, const std::string& subject) {} ///< N/A diff --git a/src/server/Engine.cpp b/src/server/Engine.cpp index 7ab7e315..beb38a24 100644 --- a/src/server/Engine.cpp +++ b/src/server/Engine.cpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -24,6 +24,7 @@ #include "lv2/lv2plug.in/ns/ext/state/state.h" #include "events/CreateGraph.hpp" +#include "ingen/AtomReader.hpp" #include "ingen/Configuration.hpp" #include "ingen/Log.hpp" #include "ingen/Store.hpp" @@ -47,6 +48,7 @@ #include "PreProcessor.hpp" #include "ProcessContext.hpp" #include "ThreadManager.hpp" +#include "UndoStack.hpp" #include "Worker.hpp" #ifdef HAVE_SOCKET #include "SocketListener.hpp" @@ -67,9 +69,12 @@ Engine::Engine(Ingen::World* world) , _buffer_factory(new BufferFactory(*this, world->uris())) , _control_bindings(NULL) , _event_writer(new EventWriter(*this)) + , _atom_interface(new AtomReader(world->uri_map(), world->uris(), world->log(), *_event_writer)) , _maid(new Raul::Maid()) , _options(new LV2Options(world->uris())) - , _pre_processor(new PreProcessor()) + , _undo_stack(new UndoStack(_world->uris(), _world->uri_map())) + , _redo_stack(new UndoStack(_world->uris(), _world->uri_map())) + , _pre_processor(new PreProcessor(*this)) , _post_processor(new PostProcessor(*this)) , _root_graph(NULL) , _worker(new Worker(world->log(), event_queue_size())) @@ -134,6 +139,7 @@ Engine::~Engine() #endif delete _pre_processor; delete _post_processor; + delete _undo_stack; delete _block_factory; delete _control_bindings; delete _broadcaster; @@ -307,9 +313,9 @@ Engine::pending_events() } void -Engine::enqueue_event(Event* ev) +Engine::enqueue_event(Event* ev, Event::Mode mode) { - _pre_processor->event(ev); + _pre_processor->event(ev, mode); } unsigned diff --git a/src/server/Engine.hpp b/src/server/Engine.hpp index 4a4d6a56..d4ff4420 100644 --- a/src/server/Engine.hpp +++ b/src/server/Engine.hpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -26,12 +26,14 @@ #include "ingen/ingen.h" #include "ingen/types.hpp" +#include "Event.hpp" #include "ProcessContext.hpp" namespace Raul { class Maid; } namespace Ingen { +class AtomReader; class Store; class World; @@ -42,7 +44,6 @@ class Broadcaster; class BufferFactory; class ControlBindings; class Driver; -class Event; class EventWriter; class GraphImpl; class LV2Options; @@ -50,6 +51,7 @@ class PostProcessor; class PreProcessor; class ProcessContext; class SocketListener; +class UndoStack; class Worker; /** @@ -89,7 +91,7 @@ public: SampleCount event_time(); /** Enqueue an event to be processed (non-realtime threads only). */ - void enqueue_event(Event* ev); + void enqueue_event(Event* ev, Event::Mode mode=Event::Mode::NORMAL); /** Process events (process thread only). */ unsigned process_events(); @@ -101,6 +103,7 @@ public: Ingen::World* world() const { return _world; } EventWriter* interface() const { return _event_writer; } + AtomReader* atom_interface() const { return _atom_interface; } BlockFactory* block_factory() const { return _block_factory; } Broadcaster* broadcaster() const { return _broadcaster; } BufferFactory* buffer_factory() const { return _buffer_factory; } @@ -110,6 +113,8 @@ public: GraphImpl* root_graph() const { return _root_graph; } PostProcessor* post_processor() const { return _post_processor; } Raul::Maid* maid() const { return _maid; } + UndoStack* undo_stack() const { return _undo_stack; } + UndoStack* redo_stack() const { return _redo_stack; } Worker* worker() const { return _worker; } ProcessContext& process_context() { return _process_context; } @@ -127,8 +132,11 @@ private: ControlBindings* _control_bindings; SPtr _driver; EventWriter* _event_writer; + AtomReader* _atom_interface; Raul::Maid* _maid; SPtr _options; + UndoStack* _undo_stack; + UndoStack* _redo_stack; PreProcessor* _pre_processor; PostProcessor* _post_processor; GraphImpl* _root_graph; diff --git a/src/server/Event.hpp b/src/server/Event.hpp index 77249262..15ce386b 100644 --- a/src/server/Event.hpp +++ b/src/server/Event.hpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -53,6 +53,9 @@ class Event : public Raul::Deletable, public Raul::Noncopyable public: virtual ~Event() {} + /** Event mode to distinguish normal events from undo events. */ + enum class Mode { NORMAL, UNDO, REDO }; + /** Pre-process event before execution (non-realtime). */ virtual bool pre_process() = 0; @@ -62,6 +65,9 @@ public: /** Post-process event after execution (non-realtime). */ virtual void post_process() = 0; + /** Write the inverse of this event to `sink`. */ + virtual void undo(Interface& target) {} + /** Return true iff this event has been pre-processed. */ inline bool is_prepared() const { return _status != Status::NOT_PREPARED; } @@ -80,6 +86,14 @@ public: /** Return the status (success or error code) of this event. */ Status status() const { return _status; } + /** Return true iff this is a generated undo event. */ + Mode get_mode() const { return _mode; } + + /** Flag this event as a generated undo event. */ + void set_mode(Mode mode) { _mode = mode; } + + inline Engine& engine() { return _engine; } + protected: Event(Engine& engine, SPtr client, int32_t id, FrameTime time) : _engine(engine) @@ -88,6 +102,7 @@ protected: , _request_id(id) , _time(time) , _status(Status::NOT_PREPARED) + , _mode(Mode::NORMAL) {} /** Constructor for internal events only */ @@ -97,6 +112,7 @@ protected: , _request_id(0) , _time(0) , _status(Status::NOT_PREPARED) + , _mode(Mode::NORMAL) {} inline bool pre_process_done(Status st) { @@ -128,6 +144,7 @@ protected: FrameTime _time; Status _status; std::string _err_subject; + Mode _mode; }; } // namespace Server diff --git a/src/server/EventWriter.cpp b/src/server/EventWriter.cpp index 9732f04c..5aecf2db 100644 --- a/src/server/EventWriter.cpp +++ b/src/server/EventWriter.cpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -28,6 +28,7 @@ namespace Server { EventWriter::EventWriter(Engine& engine) : _engine(engine) , _request_id(0) + , _event_mode(Event::Mode::NORMAL) { } @@ -47,6 +48,24 @@ EventWriter::set_response_id(int32_t id) _request_id = id; } +void +EventWriter::bundle_begin() +{ + _engine.enqueue_event( + new Events::Mark(_engine, _respondee, _request_id, now(), + Events::Mark::Type::BUNDLE_START), + _event_mode); +} + +void +EventWriter::bundle_end() +{ + _engine.enqueue_event( + new Events::Mark(_engine, _respondee, _request_id, now(), + Events::Mark::Type::BUNDLE_END), + _event_mode); +} + void EventWriter::put(const Raul::URI& uri, const Resource::Properties& properties, @@ -54,7 +73,8 @@ EventWriter::put(const Raul::URI& uri, { _engine.enqueue_event( new Events::Delta(_engine, _respondee, _request_id, now(), - Events::Delta::Type::PUT, ctx, uri, properties)); + Events::Delta::Type::PUT, ctx, uri, properties), + _event_mode); } void @@ -65,7 +85,8 @@ EventWriter::delta(const Raul::URI& uri, _engine.enqueue_event( new Events::Delta(_engine, _respondee, _request_id, now(), Events::Delta::Type::PATCH, Resource::Graph::DEFAULT, - uri, add, remove)); + uri, add, remove), + _event_mode); } void @@ -74,7 +95,8 @@ EventWriter::copy(const Raul::URI& old_uri, { _engine.enqueue_event( new Events::Copy(_engine, _respondee, _request_id, now(), - old_uri, new_uri)); + old_uri, new_uri), + _event_mode); } void @@ -83,14 +105,16 @@ EventWriter::move(const Raul::Path& old_path, { _engine.enqueue_event( new Events::Move(_engine, _respondee, _request_id, now(), - old_path, new_path)); + old_path, new_path), + _event_mode); } void EventWriter::del(const Raul::URI& uri) { _engine.enqueue_event( - new Events::Delete(_engine, _respondee, _request_id, now(), uri)); + new Events::Delete(_engine, _respondee, _request_id, now(), uri), + _event_mode); } void @@ -99,7 +123,8 @@ EventWriter::connect(const Raul::Path& tail_path, { _engine.enqueue_event( new Events::Connect(_engine, _respondee, _request_id, now(), - tail_path, head_path)); + tail_path, head_path), + _event_mode); } @@ -109,7 +134,8 @@ EventWriter::disconnect(const Raul::Path& src, { _engine.enqueue_event( new Events::Disconnect(_engine, _respondee, _request_id, now(), - src, dst)); + src, dst), + _event_mode); } void @@ -118,7 +144,8 @@ EventWriter::disconnect_all(const Raul::Path& graph, { _engine.enqueue_event( new Events::DisconnectAll(_engine, _respondee, _request_id, now(), - graph, path)); + graph, path), + _event_mode); } void @@ -128,15 +155,33 @@ EventWriter::set_property(const Raul::URI& uri, { _engine.enqueue_event( new Events::Delta(_engine, _respondee, _request_id, now(), - Events::Delta::Type::PUT, Resource::Graph::DEFAULT, - uri, {{predicate, value}}, {})); + Events::Delta::Type::SET, Resource::Graph::DEFAULT, + uri, {{predicate, value}}, {}), + _event_mode); +} + +void +EventWriter::undo() +{ + _engine.enqueue_event( + new Events::Undo(_engine, _respondee, _request_id, now(), false), + _event_mode); +} + +void +EventWriter::redo() +{ + _engine.enqueue_event( + new Events::Undo(_engine, _respondee, _request_id, now(), true), + _event_mode); } void EventWriter::get(const Raul::URI& uri) { _engine.enqueue_event( - new Events::Get(_engine, _respondee, _request_id, now(), uri)); + new Events::Get(_engine, _respondee, _request_id, now(), uri), + _event_mode); } } // namespace Server diff --git a/src/server/EventWriter.hpp b/src/server/EventWriter.hpp index 15a144ef..7b9b920b 100644 --- a/src/server/EventWriter.hpp +++ b/src/server/EventWriter.hpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -25,6 +25,7 @@ #include "ingen/Resource.hpp" #include "ingen/types.hpp" +#include "Event.hpp" #include "types.hpp" namespace Ingen { @@ -52,9 +53,9 @@ public: virtual void set_response_id(int32_t id); - virtual void bundle_begin() {} + virtual void bundle_begin(); - virtual void bundle_end() {} + virtual void bundle_end(); virtual void put(const Raul::URI& path, const Resource::Properties& properties, @@ -85,16 +86,23 @@ public: virtual void disconnect_all(const Raul::Path& graph, const Raul::Path& path); + virtual void undo(); + + virtual void redo(); + virtual void get(const Raul::URI& uri); virtual void response(int32_t id, Status status, const std::string& subject) {} ///< N/A virtual void error(const std::string& msg) {} ///< N/A + void set_event_mode(Event::Mode mode) { _event_mode = mode; } + protected: Engine& _engine; SPtr _respondee; int32_t _request_id; + Event::Mode _event_mode; private: SampleCount now() const; diff --git a/src/server/PreProcessor.cpp b/src/server/PreProcessor.cpp index e26ebcdc..d57bb6b9 100644 --- a/src/server/PreProcessor.cpp +++ b/src/server/PreProcessor.cpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -16,19 +16,25 @@ #include +#include "ingen/AtomSink.hpp" +#include "ingen/AtomWriter.hpp" + +#include "Engine.hpp" #include "Event.hpp" #include "PostProcessor.hpp" #include "PreProcessor.hpp" #include "ProcessContext.hpp" #include "ThreadManager.hpp" +#include "UndoStack.hpp" using namespace std; namespace Ingen { namespace Server { -PreProcessor::PreProcessor() - : _sem(0) +PreProcessor::PreProcessor(Engine& engine) + : _engine(engine) + , _sem(0) , _head(NULL) , _prepared_back(NULL) , _tail(NULL) @@ -46,7 +52,7 @@ PreProcessor::~PreProcessor() } void -PreProcessor::event(Event* const ev) +PreProcessor::event(Event* const ev, Event::Mode mode) { // TODO: Probably possible to make this lock-free with CAS ThreadManager::assert_not_thread(THREAD_IS_REAL_TIME); @@ -54,6 +60,7 @@ PreProcessor::event(Event* const ev) assert(!ev->is_prepared()); assert(!ev->next()); + ev->set_mode(mode); /* Note that tail is only used here, not in process(). The head must be checked first here, since if it is NULL the tail pointer is junk. */ @@ -113,6 +120,13 @@ PreProcessor::process(ProcessContext& context, PostProcessor& dest, size_t limit void PreProcessor::run() { + UndoStack& undo_stack = *_engine.undo_stack(); + UndoStack& redo_stack = *_engine.redo_stack(); + AtomWriter undo_writer( + _engine.world()->uri_map(), _engine.world()->uris(), undo_stack); + AtomWriter redo_writer( + _engine.world()->uri_map(), _engine.world()->uris(), redo_stack); + ThreadManager::set_flag(THREAD_PRE_PROCESS); while (!_exit_flag) { if (!_sem.timed_wait(1000)) { @@ -125,7 +139,23 @@ PreProcessor::run() } assert(!ev->is_prepared()); - ev->pre_process(); + if (ev->pre_process()) { + switch (ev->get_mode()) { + case Event::Mode::NORMAL: + case Event::Mode::REDO: + undo_stack.start_entry(); + ev->undo(undo_writer); + undo_stack.finish_entry(); + // undo_stack.save(stderr); + break; + case Event::Mode::UNDO: + redo_stack.start_entry(); + ev->undo(redo_writer); + redo_stack.finish_entry(); + // redo_stack.save(stderr, "redo"); + break; + } + } assert(ev->is_prepared()); _prepared_back = (Event*)ev->next(); diff --git a/src/server/PreProcessor.hpp b/src/server/PreProcessor.hpp index 586d6dd8..9ef75473 100644 --- a/src/server/PreProcessor.hpp +++ b/src/server/PreProcessor.hpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -26,6 +26,7 @@ namespace Ingen { namespace Server { +class Engine; class Event; class PostProcessor; class ProcessContext; @@ -33,7 +34,7 @@ class ProcessContext; class PreProcessor { public: - explicit PreProcessor(); + explicit PreProcessor(Engine& engine); ~PreProcessor(); @@ -43,7 +44,7 @@ public: /** Enqueue an event. * This is safe to call from any non-realtime thread (it locks). */ - void event(Event* ev); + void event(Event* ev, Event::Mode mode); /** Process events for a cycle. * @return The number of events processed. @@ -56,6 +57,7 @@ protected: void run(); private: + Engine& _engine; std::mutex _mutex; Raul::Semaphore _sem; std::atomic _head; diff --git a/src/server/UndoStack.cpp b/src/server/UndoStack.cpp new file mode 100644 index 00000000..f8a7f37b --- /dev/null +++ b/src/server/UndoStack.cpp @@ -0,0 +1,252 @@ +/* + This file is part of Ingen. + Copyright 2016 David Robillard + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen 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 Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see . +*/ + +#include +#include + +#include "ingen/URIMap.hpp" +#include "ingen/URIs.hpp" +#include "lv2/lv2plug.in/ns/ext/atom/util.h" +#include "lv2/lv2plug.in/ns/ext/patch/patch.h" +#include "serd/serd.h" +#include "sratom/sratom.h" + +#include "UndoStack.hpp" + +#define NS_RDF (const uint8_t*)"http://www.w3.org/1999/02/22-rdf-syntax-ns#" + +#define USTR(s) ((const uint8_t*)(s)) + +namespace Ingen { +namespace Server { + +void +UndoStack::start_entry() +{ + if (_depth == 0) { + time_t now; + time(&now); + _stack.push_back(Entry(now)); + } + ++_depth; +} + +bool +UndoStack::write(const LV2_Atom* msg) +{ + _stack.back().push_event(msg); + return true; +} + +bool +UndoStack::ignore_later_event(const LV2_Atom* first, + const LV2_Atom* second) const +{ + if (first->type != _uris.atom_Object || first->type != second->type) { + return false; + } + + const LV2_Atom_Object* f = (const LV2_Atom_Object*)first; + const LV2_Atom_Object* s = (const LV2_Atom_Object*)second; + if (f->body.otype == _uris.patch_Set && f->body.otype == s->body.otype) { + const LV2_Atom* f_subject = NULL; + const LV2_Atom* f_property = NULL; + const LV2_Atom* s_subject = NULL; + const LV2_Atom* s_property = NULL; + lv2_atom_object_get(f, + (LV2_URID)_uris.patch_subject, &f_subject, + (LV2_URID)_uris.patch_property, &f_property, + 0); + lv2_atom_object_get(s, + (LV2_URID)_uris.patch_subject, &s_subject, + (LV2_URID)_uris.patch_property, &s_property, + 0); + return (lv2_atom_equals(f_subject, s_subject) && + lv2_atom_equals(f_property, s_property)); + } + + return false; +} + +void +UndoStack::finish_entry() +{ + if (--_depth > 0) { + return; + } else if (_stack.back().events.empty()) { + // Disregard empty entry + _stack.pop_back(); + } else if (_stack.size() > 1 && _stack.back().events.size() == 1) { + // This entry and the previous one have one event, attempt to merge + auto i = _stack.rbegin(); + ++i; + if (i->events.size() == 1) { + if (ignore_later_event(i->events[0], _stack.back().events[0])) { + _stack.pop_back(); + } + } + } +} + +UndoStack::Entry +UndoStack::pop() +{ + Entry top; + if (!_stack.empty()) { + top = _stack.back(); + _stack.pop_back(); + } + return top; +} + +struct BlankIDs { + BlankIDs(char c='b') : n(0), c(c) {} + + SerdNode get() { + snprintf(buf, sizeof(buf), "%c%u", c, n++); + return serd_node_from_string(SERD_BLANK, USTR(buf)); + } + + char buf[16]; + unsigned n; + const char c; +}; + +struct ListContext { + explicit ListContext(BlankIDs& ids, unsigned flags, const SerdNode* s, const SerdNode* p) + : ids(ids) + , s(*s) + , p(*p) + , flags(flags | SERD_LIST_O_BEGIN) + {} + + SerdNode start_node(SerdWriter* writer) { + const SerdNode node = ids.get(); + serd_writer_write_statement(writer, flags, NULL, &s, &p, &node, NULL, NULL); + return node; + } + + void append(SerdWriter* writer, unsigned oflags, const SerdNode* value) { + // s p node + const SerdNode node = start_node(writer); + + // node rdf:first value + p = serd_node_from_string(SERD_URI, NS_RDF "first"); + flags = SERD_LIST_CONT; + serd_writer_write_statement(writer, flags|oflags, NULL, &node, &p, value, NULL, NULL); + + end_node(writer, &node); + } + + void end_node(SerdWriter* writer, const SerdNode* node) { + // Prepare for next call: node rdf:rest ... + s = *node; + p = serd_node_from_string(SERD_URI, NS_RDF "rest"); + } + + void end(SerdWriter* writer) { + const SerdNode nil = serd_node_from_string(SERD_URI, NS_RDF "nil"); + serd_writer_write_statement(writer, flags, NULL, &s, &p, &nil, NULL, NULL); + } + + BlankIDs& ids; + SerdNode s; + SerdNode p; + unsigned flags; +}; + +void +UndoStack::write_entry(Sratom* sratom, + SerdWriter* writer, + const SerdNode* const subject, + const UndoStack::Entry& entry) +{ + char time_str[24]; + strftime(time_str, sizeof(time_str), "%FT%T", gmtime(&entry.time)); + + // entry rdf:type ingen:UndoEntry + SerdNode p = serd_node_from_string(SERD_URI, USTR(INGEN_NS "time")); + SerdNode o = serd_node_from_string(SERD_LITERAL, USTR(time_str)); + serd_writer_write_statement(writer, SERD_ANON_CONT, NULL, subject, &p, &o, NULL, NULL); + + p = serd_node_from_string(SERD_URI, USTR(INGEN_NS "events")); + + BlankIDs ids('e'); + ListContext ctx(ids, SERD_ANON_CONT, subject, &p); + + for (const LV2_Atom* atom : entry.events) { + const SerdNode node = ctx.start_node(writer); + + p = serd_node_from_string(SERD_URI, NS_RDF "first"); + ctx.flags = SERD_LIST_CONT; + sratom_write(sratom, &_map.urid_unmap_feature()->urid_unmap, SERD_LIST_CONT, + &node, &p, + atom->type, atom->size, LV2_ATOM_BODY_CONST(atom)); + + ctx.end_node(writer, &node); + } + + ctx.end(writer); +} + +void +UndoStack::save(FILE* stream, const char* name) +{ + SerdEnv* env = serd_env_new(NULL); + serd_env_set_prefix_from_strings(env, USTR("atom"), USTR(LV2_ATOM_PREFIX)); + serd_env_set_prefix_from_strings(env, USTR("ingen"), USTR(INGEN_NS)); + serd_env_set_prefix_from_strings(env, USTR("patch"), USTR(LV2_PATCH_PREFIX)); + + const SerdNode base = serd_node_from_string(SERD_URI, USTR("ingen:/")); + SerdURI base_uri; + serd_uri_parse(base.buf, &base_uri); + + SerdWriter* writer = serd_writer_new( + SERD_TURTLE, + (SerdStyle)(SERD_STYLE_RESOLVED|SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED), + env, + &base_uri, + serd_file_sink, + stream); + + // Configure sratom to write directly to the writer (and thus the socket) + Sratom* sratom = sratom_new(&_map.urid_map_feature()->urid_map); + sratom_set_sink(sratom, + (const char*)base.buf, + (SerdStatementSink)serd_writer_write_statement, + (SerdEndSink)serd_writer_end_anon, + writer); + + SerdNode s = serd_node_from_string(SERD_BLANK, (const uint8_t*)name); + SerdNode p = serd_node_from_string(SERD_URI, USTR(INGEN_NS "entries")); + + BlankIDs ids('u'); + ListContext ctx(ids, 0, &s, &p); + for (const Entry& e : _stack) { + const SerdNode entry = ids.get(); + ctx.append(writer, SERD_ANON_O_BEGIN, &entry); + write_entry(sratom, writer, &entry, e); + serd_writer_end_anon(writer, &entry); + } + ctx.end(writer); + + sratom_free(sratom); + serd_writer_finish(writer); + serd_writer_free(writer); +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/UndoStack.hpp b/src/server/UndoStack.hpp new file mode 100644 index 00000000..aeb18529 --- /dev/null +++ b/src/server/UndoStack.hpp @@ -0,0 +1,107 @@ +/* + This file is part of Ingen. + Copyright 2016 David Robillard + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen 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 Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see . +*/ + +#ifndef INGEN_ENGINE_UNDOSTACK_HPP +#define INGEN_ENGINE_UNDOSTACK_HPP + +#include +#include + +#include "ingen/AtomSink.hpp" +#include "ingen/ingen.h" +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "serd/serd.h" +#include "sratom/sratom.h" + +namespace Ingen { + +class URIMap; +class URIs; + +namespace Server { + +class INGEN_API UndoStack : public AtomSink { +public: + struct Entry { + Entry(time_t time=0) : time(time) {} + + Entry(const Entry& copy) + : time(copy.time) + { + for (const LV2_Atom* ev : copy.events) { + push_event(ev); + } + } + + ~Entry() { clear(); } + + Entry& operator=(const Entry& rhs) { + clear(); + time = rhs.time; + for (const LV2_Atom* ev : rhs.events) { + push_event(ev); + } + return *this; + } + + void clear() { + for (LV2_Atom* ev : events) { + free(ev); + } + events.clear(); + } + + void push_event(const LV2_Atom* ev) { + const uint32_t size = lv2_atom_total_size(ev); + LV2_Atom* copy = (LV2_Atom*)malloc(size); + memcpy(copy, ev, size); + events.push_back(copy); + } + + time_t time; + std::vector events; + }; + + UndoStack(URIs& uris, URIMap& map) : _uris(uris), _map(map), _depth(0) {} + + void start_entry(); + bool write(const LV2_Atom* msg); + void finish_entry(); + + bool empty() const { return _stack.empty(); } + Entry pop(); + + void save(FILE* stream, const char* name="undo"); + +private: + bool ignore_later_event(const LV2_Atom* first, + const LV2_Atom* second) const; + + void write_entry(Sratom* sratom, + SerdWriter* writer, + const SerdNode* subject, + const Entry& entry); + + URIs& _uris; + URIMap& _map; + std::deque _stack; + int _depth; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_UNDOSTACK_HPP diff --git a/src/server/events.hpp b/src/server/events.hpp index bbe0aa64..5f77b431 100644 --- a/src/server/events.hpp +++ b/src/server/events.hpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -18,6 +18,7 @@ #define INGEN_ENGINE_EVENTS_HPP #include "events/Connect.hpp" +#include "events/Copy.hpp" #include "events/CreateBlock.hpp" #include "events/CreateGraph.hpp" #include "events/CreatePort.hpp" @@ -26,8 +27,9 @@ #include "events/Disconnect.hpp" #include "events/DisconnectAll.hpp" #include "events/Get.hpp" +#include "events/Mark.hpp" #include "events/Move.hpp" -#include "events/Copy.hpp" #include "events/SetPortValue.hpp" +#include "events/Undo.hpp" #endif // INGEN_ENGINE_EVENTS_HPP diff --git a/src/server/events/Connect.cpp b/src/server/events/Connect.cpp index 8880322d..f0ba39bb 100644 --- a/src/server/events/Connect.cpp +++ b/src/server/events/Connect.cpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -168,6 +168,12 @@ Connect::post_process() } } +void +Connect::undo(Interface& target) +{ + target.disconnect(_tail_path, _head_path); +} + } // namespace Events } // namespace Server } // namespace Ingen diff --git a/src/server/events/Connect.hpp b/src/server/events/Connect.hpp index f6b6cccc..bd15d6d3 100644 --- a/src/server/events/Connect.hpp +++ b/src/server/events/Connect.hpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -55,6 +55,7 @@ public: bool pre_process(); void execute(ProcessContext& context); void post_process(); + void undo(Interface& target); private: const Raul::Path _tail_path; diff --git a/src/server/events/Copy.cpp b/src/server/events/Copy.cpp index eed68d75..34a63e58 100644 --- a/src/server/events/Copy.cpp +++ b/src/server/events/Copy.cpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -211,6 +211,12 @@ Copy::post_process() } } +void +Copy::undo(Interface& target) +{ + target.del(_new_uri); +} + } // namespace Events } // namespace Server } // namespace Ingen diff --git a/src/server/events/Copy.hpp b/src/server/events/Copy.hpp index 2677ba53..a1726cc6 100644 --- a/src/server/events/Copy.hpp +++ b/src/server/events/Copy.hpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -49,6 +49,7 @@ public: bool pre_process(); void execute(ProcessContext& context); void post_process(); + void undo(Interface& target); private: bool engine_to_engine(); diff --git a/src/server/events/CreateBlock.cpp b/src/server/events/CreateBlock.cpp index 7ba35d1a..cde15622 100644 --- a/src/server/events/CreateBlock.cpp +++ b/src/server/events/CreateBlock.cpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -164,6 +164,12 @@ CreateBlock::post_process() } } +void +CreateBlock::undo(Interface& target) +{ + target.del(_block->uri()); +} + } // namespace Events } // namespace Server } // namespace Ingen diff --git a/src/server/events/CreateBlock.hpp b/src/server/events/CreateBlock.hpp index 40d72f52..1282fe8b 100644 --- a/src/server/events/CreateBlock.hpp +++ b/src/server/events/CreateBlock.hpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -50,6 +50,7 @@ public: bool pre_process(); void execute(ProcessContext& context); void post_process(); + void undo(Interface& target); private: Raul::Path _path; diff --git a/src/server/events/CreateGraph.cpp b/src/server/events/CreateGraph.cpp index e6ad0cb4..93191437 100644 --- a/src/server/events/CreateGraph.cpp +++ b/src/server/events/CreateGraph.cpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -213,6 +213,12 @@ CreateGraph::post_process() _child_events.clear(); } +void +CreateGraph::undo(Interface& target) +{ + target.del(_graph->uri()); +} + } // namespace Events } // namespace Server } // namespace Ingen diff --git a/src/server/events/CreateGraph.hpp b/src/server/events/CreateGraph.hpp index cf40fb41..efeabf48 100644 --- a/src/server/events/CreateGraph.hpp +++ b/src/server/events/CreateGraph.hpp @@ -47,6 +47,7 @@ public: bool pre_process(); void execute(ProcessContext& context); void post_process(); + void undo(Interface& target); GraphImpl* graph() { return _graph; } diff --git a/src/server/events/CreatePort.cpp b/src/server/events/CreatePort.cpp index 0f711f4f..0e512852 100644 --- a/src/server/events/CreatePort.cpp +++ b/src/server/events/CreatePort.cpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -197,6 +197,12 @@ CreatePort::post_process() delete _old_ports_array; } +void +CreatePort::undo(Interface& target) +{ + target.del(_graph_port->uri()); +} + } // namespace Events } // namespace Server } // namespace Ingen diff --git a/src/server/events/CreatePort.hpp b/src/server/events/CreatePort.hpp index a2dd55ce..754a238f 100644 --- a/src/server/events/CreatePort.hpp +++ b/src/server/events/CreatePort.hpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -54,6 +54,7 @@ public: bool pre_process(); void execute(ProcessContext& context); void post_process(); + void undo(Interface& target); private: enum class Flow { diff --git a/src/server/events/Delete.cpp b/src/server/events/Delete.cpp index 06a5cb95..7b27e11f 100644 --- a/src/server/events/Delete.cpp +++ b/src/server/events/Delete.cpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -162,6 +162,18 @@ Delete::post_process() } } +void +Delete::undo(Interface& target) +{ + auto i = _removed_objects.find(_path); + if (i != _removed_objects.end()) { + target.put(_uri, i->second->properties()); + if (_disconnect_event) { + _disconnect_event->undo(target); + } + } +} + } // namespace Events } // namespace Server } // namespace Ingen diff --git a/src/server/events/Delete.hpp b/src/server/events/Delete.hpp index 4403d4da..c6e38839 100644 --- a/src/server/events/Delete.hpp +++ b/src/server/events/Delete.hpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -57,6 +57,7 @@ public: bool pre_process(); void execute(ProcessContext& context); void post_process(); + void undo(Interface& target); private: Raul::URI _uri; diff --git a/src/server/events/Delta.cpp b/src/server/events/Delta.cpp index 83312814..a765932b 100644 --- a/src/server/events/Delta.cpp +++ b/src/server/events/Delta.cpp @@ -277,6 +277,7 @@ Delta::pre_process() _old_bindings = _engine.control_bindings()->remove(port); } if (_object) { + _removed.emplace(key, value); _object->remove_property(key, value); } else if (is_engine && key == uris.ingen_loadedBundle) { LilvWorld* lworld = _engine.world()->lilv_world(); @@ -299,8 +300,23 @@ Delta::pre_process() // Remove all added properties if this is a put or set if (_object && (_type == Type::PUT || _type == Type::SET)) { - for (const auto& p : _properties) { - _object->remove_property(p.first, uris.patch_wildcard); + for (auto p = _properties.begin(); + p != _properties.end(); + p = _properties.upper_bound(p->first)) { + for (auto q = _object->properties().find(p->first); + q != _object->properties().end() && q->first == p->first;) { + auto next = q; + ++next; + + if (!_properties.contains(q->first, q->second)) { + const auto r = std::make_pair(q->first, q->second); + _object->properties().erase(q); + _object->on_property_removed(r.first, r.second); + _removed.insert(r); + } + + q = next; + } } } @@ -311,7 +327,9 @@ Delta::pre_process() if (obj) { Resource& resource = *obj; if (value != uris.patch_wildcard) { - resource.add_property(key, value, value.context()); + if (resource.add_property(key, value, value.context())) { + _added.emplace(key, value); + } } BlockImpl* block = NULL; @@ -589,12 +607,16 @@ Delta::post_process() /* Kludge to avoid feedback for set events only. The GUI depends on put responses to e.g. initially place blocks. Some more sensible way of controlling this is needed. */ - _engine.broadcaster()->set_ignore_client(_request_client); + if (_mode == Mode::NORMAL) { + _engine.broadcaster()->set_ignore_client(_request_client); + } _engine.broadcaster()->set_property( _subject, - (*_properties.begin()).first, - (*_properties.begin()).second); - _engine.broadcaster()->clear_ignore_client(); + _properties.begin()->first, + _properties.begin()->second); + if (_mode == Mode::NORMAL) { + _engine.broadcaster()->clear_ignore_client(); + } break; case Type::PUT: if (_type == Type::PUT && _subject.substr(0, 5) == "file:") { @@ -614,6 +636,27 @@ Delta::post_process() } } +void +Delta::undo(Interface& target) +{ + const Ingen::URIs& uris = _engine.world()->uris(); + + if (_create_event) { + _create_event->undo(target); + } else if (_type == Type::PATCH) { + target.delta(_subject, _added, _removed); + } else if (_type == Type::SET || _type == Type::PUT) { + if (_removed.size() == 1) { + target.set_property( + _subject, _removed.begin()->first, _removed.begin()->second); + } else if (_removed.empty()) { + target.delta(_subject, _added, {}); + } else { + target.put(_subject, _removed); + } + } +} + } // namespace Events } // namespace Server } // namespace Ingen diff --git a/src/server/events/Delta.hpp b/src/server/events/Delta.hpp index e9d1970b..b1f2d66a 100644 --- a/src/server/events/Delta.hpp +++ b/src/server/events/Delta.hpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -76,6 +76,7 @@ public: bool pre_process(); void execute(ProcessContext& context); void post_process(); + void undo(Interface& target); private: enum class SpecialType { @@ -107,6 +108,9 @@ private: ControlBindings::Key _binding; Type _type; + Resource::Properties _added; + Resource::Properties _removed; + SPtr _old_bindings; boost::optional _preset; diff --git a/src/server/events/Disconnect.cpp b/src/server/events/Disconnect.cpp index 6f84dc1a..13f419ce 100644 --- a/src/server/events/Disconnect.cpp +++ b/src/server/events/Disconnect.cpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -219,6 +219,12 @@ Disconnect::post_process() } } +void +Disconnect::undo(Interface& target) +{ + target.connect(_tail_path, _head_path); +} + } // namespace Events } // namespace Server } // namespace Ingen diff --git a/src/server/events/Disconnect.hpp b/src/server/events/Disconnect.hpp index 64e08246..8a69dac4 100644 --- a/src/server/events/Disconnect.hpp +++ b/src/server/events/Disconnect.hpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -57,6 +57,7 @@ public: bool pre_process(); void execute(ProcessContext& context); void post_process(); + void undo(Interface& target); class Impl { public: @@ -67,7 +68,8 @@ public: bool execute(ProcessContext& context, bool set_head_buffers); - inline InputPort* head() { return _head; } + inline OutputPort* tail() { return _tail; } + inline InputPort* head() { return _head; } private: Engine& _engine; diff --git a/src/server/events/DisconnectAll.cpp b/src/server/events/DisconnectAll.cpp index bd4fef7d..ee19797e 100644 --- a/src/server/events/DisconnectAll.cpp +++ b/src/server/events/DisconnectAll.cpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -164,6 +164,14 @@ DisconnectAll::post_process() } } +void +DisconnectAll::undo(Interface& target) +{ + for (auto& i : _impls) { + target.connect(i->tail()->path(), i->head()->path()); + } +} + } // namespace Events } // namespace Server } // namespace Ingen diff --git a/src/server/events/DisconnectAll.hpp b/src/server/events/DisconnectAll.hpp index 039e3f54..f8123a45 100644 --- a/src/server/events/DisconnectAll.hpp +++ b/src/server/events/DisconnectAll.hpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -59,6 +59,7 @@ public: bool pre_process(); void execute(ProcessContext& context); void post_process(); + void undo(Interface& target); private: typedef std::list Impls; diff --git a/src/server/events/Mark.cpp b/src/server/events/Mark.cpp new file mode 100644 index 00000000..0e14f008 --- /dev/null +++ b/src/server/events/Mark.cpp @@ -0,0 +1,65 @@ +/* + This file is part of Ingen. + Copyright 2016 David Robillard + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen 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 Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see . +*/ + +#include "Engine.hpp" +#include "UndoStack.hpp" +#include "events/Mark.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +Mark::Mark(Engine& engine, + SPtr client, + int32_t id, + SampleCount timestamp, + Type type) + : Event(engine, client, id, timestamp) + , _type(type) +{} + +bool +Mark::pre_process() +{ + UndoStack* const stack = ((_mode == Mode::UNDO) + ? _engine.redo_stack() + : _engine.undo_stack()); + + switch (_type) { + case Type::BUNDLE_START: + stack->start_entry(); + break; + case Type::BUNDLE_END: + stack->finish_entry(); + break; + } + + return Event::pre_process_done(Status::SUCCESS); +} + +void +Mark::execute(ProcessContext& context) +{} + +void +Mark::post_process() +{ + respond(); +} + +} // namespace Events +} // namespace Server +} // namespace Ingen diff --git a/src/server/events/Mark.hpp b/src/server/events/Mark.hpp new file mode 100644 index 00000000..995df746 --- /dev/null +++ b/src/server/events/Mark.hpp @@ -0,0 +1,55 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen 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 Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see . +*/ + +#ifndef INGEN_EVENTS_MARK_HPP +#define INGEN_EVENTS_MARK_HPP + +#include "Event.hpp" + +namespace Ingen { +namespace Server { + +class Engine; + +namespace Events { + +/** Set properties of a graph object. + * \ingroup engine + */ +class Mark : public Event +{ +public: + enum class Type { BUNDLE_START, BUNDLE_END }; + + Mark(Engine& engine, + SPtr client, + int32_t id, + SampleCount timestamp, + Type type); + + bool pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + Type _type; +}; + +} // namespace Events +} // namespace Server +} // namespace Ingen + +#endif // INGEN_EVENTS_MARK_HPP diff --git a/src/server/events/Move.cpp b/src/server/events/Move.cpp index 2c689fe5..a51617cb 100644 --- a/src/server/events/Move.cpp +++ b/src/server/events/Move.cpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -87,6 +87,12 @@ Move::post_process() } } +void +Move::undo(Interface& target) +{ + target.move(_new_path, _old_path); +} + } // namespace Events } // namespace Server } // namespace Ingen diff --git a/src/server/events/Move.hpp b/src/server/events/Move.hpp index ae811138..74d32c61 100644 --- a/src/server/events/Move.hpp +++ b/src/server/events/Move.hpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2016 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -48,6 +48,7 @@ public: bool pre_process(); void execute(ProcessContext& context); void post_process(); + void undo(Interface& target); private: const Raul::Path _old_path; diff --git a/src/server/events/Undo.cpp b/src/server/events/Undo.cpp new file mode 100644 index 00000000..28b8e188 --- /dev/null +++ b/src/server/events/Undo.cpp @@ -0,0 +1,77 @@ +/* + This file is part of Ingen. + Copyright 2016 David Robillard + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen 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 Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see . +*/ + +#include "ingen/AtomReader.hpp" + +#include "Engine.hpp" +#include "EventWriter.hpp" +#include "Undo.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +Undo::Undo(Engine& engine, + SPtr client, + int32_t id, + SampleCount timestamp, + bool is_redo) + : Event(engine, client, id, timestamp) + , _is_redo(is_redo) +{} + +bool +Undo::pre_process() +{ + UndoStack* stack = _is_redo ? _engine.redo_stack() : _engine.undo_stack(); + Event::Mode mode = _is_redo ? Event::Mode::REDO : Event::Mode::UNDO; + + if (stack->empty()) { + return Event::pre_process_done(Status::NOT_FOUND); + } + + _entry = stack->pop(); + _engine.interface()->set_event_mode(mode); + if (_entry.events.size() > 1) { + _engine.interface()->bundle_begin(); + } + + for (const LV2_Atom* ev : _entry.events) { + _engine.atom_interface()->write(ev); + } + + if (_entry.events.size() > 1) { + _engine.interface()->bundle_end(); + } + _engine.interface()->set_event_mode(mode); + + return Event::pre_process_done(Status::SUCCESS); +} + +void +Undo::execute(ProcessContext& context) +{ +} + +void +Undo::post_process() +{ + respond(); +} + +} // namespace Events +} // namespace Server +} // namespace Ingen diff --git a/src/server/events/Undo.hpp b/src/server/events/Undo.hpp new file mode 100644 index 00000000..fff06b8d --- /dev/null +++ b/src/server/events/Undo.hpp @@ -0,0 +1,54 @@ +/* + This file is part of Ingen. + Copyright 2016 David Robillard + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen 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 Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see . +*/ + +#ifndef INGEN_EVENTS_UNDO_HPP +#define INGEN_EVENTS_UNDO_HPP + +#include "Event.hpp" +#include "UndoStack.hpp" +#include "types.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +/** A request to undo the last change to the engine. + * + * \ingroup engine + */ +class Undo : public Event +{ +public: + Undo(Engine& engine, + SPtr client, + int32_t id, + SampleCount timestamp, + bool is_redo); + + bool pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + UndoStack::Entry _entry; + bool _is_redo; +}; + +} // namespace Events +} // namespace Server +} // namespace Ingen + +#endif // INGEN_EVENTS_UNDO_HPP diff --git a/src/server/wscript b/src/server/wscript index 4d1beecf..4bb9b1f3 100644 --- a/src/server/wscript +++ b/src/server/wscript @@ -27,6 +27,7 @@ def build(bld): PostProcessor.cpp PreProcessor.cpp SocketListener.cpp + UndoStack.cpp Worker.cpp events/Connect.cpp events/Copy.cpp @@ -38,8 +39,10 @@ def build(bld): events/Disconnect.cpp events/DisconnectAll.cpp events/Get.cpp + events/Mark.cpp events/Move.cpp events/SetPortValue.cpp + events/Undo.cpp ingen_engine.cpp internals/Controller.cpp internals/Delay.cpp -- cgit v1.2.1