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/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 ++++++++++++++++++++++++++ 24 files changed, 396 insertions(+), 26 deletions(-) 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/events') 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 -- cgit v1.2.1