First working implementation of ActionManager, but incomplete.

This commit is contained in:
brunoherbelin
2020-10-04 22:52:23 +02:00
parent 5421b5e926
commit 616c6c8bdf
12 changed files with 353 additions and 79 deletions

View File

@@ -1,28 +1,163 @@
#include <string>
#include <algorithm>
#include "Log.h"
#include "View.h"
#include "Mixer.h"
#include "tinyxml2Toolkit.h"
#include "SessionVisitor.h"
#include "SessionCreator.h"
#include "ActionManager.h"
Action::Action()
#ifndef NDEBUG
#define ACTION_DEBUG
#endif
using namespace tinyxml2;
Action::Action(): step_(0), max_step_(0)
{
}
void Action::clear()
{
// clean the history
xmlDoc_.Clear();
step_ = 0;
max_step_ = 0;
// start fresh
store("Session start");
}
void Action::capture()
void Action::store(const std::string &label, int id)
{
// ignore if locked or if no label is given
if (locked_ || label.empty())
return;
// incremental naming of history nodes
step_++;
std::string nodename = "H" + std::to_string(step_);
// erase future
for (uint e = step_; e <= max_step_; e++) {
std::string name = "H" + std::to_string(e);
XMLElement *node = xmlDoc_.FirstChildElement( name.c_str() );
if ( node )
xmlDoc_.DeleteChild(node);
}
max_step_ = step_;
// create history node
XMLElement *sessionNode = xmlDoc_.NewElement( nodename.c_str() );
xmlDoc_.InsertEndChild(sessionNode);
// label describes the action
sessionNode->SetAttribute("label", label.c_str());
// id indicates which object was modified
sessionNode->SetAttribute("id", id);
// get session to operate on
Session *se = Mixer::manager().session();
// save all sources using source visitor
SessionVisitor sv(&xmlDoc_, sessionNode);
for (auto iter = se->begin(); iter != se->end(); iter++, sv.setRoot(sessionNode) )
(*iter)->accept(sv);
// debug
#ifdef ACTION_DEBUG
Log::Info("Action stored %s '%s'", nodename.c_str(), label.c_str());
// XMLSaveDoc(&xmlDoc_, "/home/bhbn/history.xml");
#endif
}
void Action::undo()
{
if (step_ <= 1)
return;
// what id was modified to get to this step ?
// get history node of current step
std::string nodename = "H" + std::to_string(step_);
XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
int id = -1;
sessionNode->QueryIntAttribute("id", &id);
restore( step_ - 1, id);
}
void Action::redo()
{
if (step_ >= max_step_)
return;
// what id to modify to go to next step ?
std::string nodename = "H" + std::to_string(step_ + 1);
XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
int id = -1;
sessionNode->QueryIntAttribute("id", &id);
restore( step_ + 1, id);
}
void Action::stepTo(uint target)
{
// going to target step
}
void Action::restore(uint target, int id)
{
// lock
locked_ = true;
// get history node of target step
step_ = target;
std::string nodename = "H" + std::to_string(step_);
XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
#ifdef ACTION_DEBUG
Log::Info("Restore %s '%s'", nodename.c_str(), sessionNode->Attribute("label"));
#endif
// sessionsources contains ids of all sources currently in the session
std::list<int> sessionsources = Mixer::manager().session()->getIdList();
// load history status:
// - if a source exists, its attributes are updated
// - if a source does not exists (in session), it is created in session
SessionLoader loader( Mixer::manager().session() );
loader.load( sessionNode );
// loadersources contains ids of all sources generated by loader
std::list<int> loadersources = loader.getIdList();
// remove intersect of both lists (sources were updated)
for( auto lsit = loadersources.begin(); lsit != loadersources.end(); ){
auto ssit = std::find(sessionsources.begin(), sessionsources.end(), (*lsit));
if ( ssit != sessionsources.end() ) {
lsit = loadersources.erase(lsit);
sessionsources.erase(ssit);
}
else
lsit++;
}
// remaining ids in list sessionsources : to remove
while ( !sessionsources.empty() ){
Source *s = Mixer::manager().findSource( sessionsources.front() );
Mixer::manager().detach( s );
Mixer::manager().session()->deleteSource( s );
sessionsources.pop_front();
}
// remaining ids in list loadersources : to add
while ( !loadersources.empty() ){
Mixer::manager().attach( Mixer::manager().findSource( loadersources.front() ) );
loadersources.pop_front();
}
// free
locked_ = false;
}

View File

@@ -2,6 +2,10 @@
#define ACTIONMANAGER_H
#include <atomic>
#include <tinyxml2.h>
class Action
{
// Private Constructor
@@ -18,14 +22,21 @@ public:
return _instance;
}
void clear();
void capture();
void store(const std::string &label, int id = -1);
void clear();
void undo();
void redo();
void stepTo(uint target);
private:
void restore(uint target, int id);
tinyxml2::XMLDocument xmlDoc_;
uint step_;
uint max_step_;
std::atomic<bool> locked_;
};
#endif // ACTIONMANAGER_H

View File

@@ -25,6 +25,7 @@ using namespace tinyxml2;
#include "PatternSource.h"
#include "DeviceSource.h"
#include "StreamSource.h"
#include "ActionManager.h"
#include "Mixer.h"
@@ -53,13 +54,10 @@ static void saveSession(const std::string& filename, Session *session)
// 1. list of sources
XMLElement *sessionNode = xmlDoc.NewElement("Session");
xmlDoc.InsertEndChild(sessionNode);
SourceList::iterator iter;
for (iter = session->begin(); iter != session->end(); iter++)
{
SessionVisitor sv(&xmlDoc, sessionNode);
for (auto iter = session->begin(); iter != session->end(); iter++, sv.setRoot(sessionNode) )
// source visitor
(*iter)->accept(sv);
}
// 2. config of views
XMLElement *views = xmlDoc.NewElement("Views");
@@ -338,7 +336,7 @@ void Mixer::insertSource(Source *s, View::Mode m)
{
if ( s != nullptr )
{
// Add source to Session
// Add source to Session (ignored if source already in)
SourceList::iterator sit = session_->addSource(s);
// set a default depth to the new source
@@ -348,9 +346,10 @@ void Mixer::insertSource(Source *s, View::Mode m)
mixing_.setAlpha(s);
// add sources Nodes to all views
mixing_.scene.ws()->attach(s->group(View::MIXING));
geometry_.scene.ws()->attach(s->group(View::GEOMETRY));
layer_.scene.ws()->attach(s->group(View::LAYER));
attach(s);
// new state in history manager
Action::manager().store(std::string("Insert ")+s->name());
// if requested to show the source in a given view
// (known to work for View::MIXING et TRANSITION: other views untested)
@@ -371,12 +370,6 @@ void Mixer::deleteSource(Source *s)
{
if ( s != nullptr )
{
// in case it was the current source...
unsetCurrentSource();
// in case it was selected..
selection().remove(s);
// keep name for log
std::string name = s->name();
@@ -386,6 +379,9 @@ void Mixer::deleteSource(Source *s)
// delete source
session_->deleteSource(s);
// new state in history manager
Action::manager().store(std::string("Delete ")+name);
// log
Log::Notify("Source %s deleted.", name.c_str());
}
@@ -402,17 +398,31 @@ void Mixer::deleteSource(Source *s)
void Mixer::attach(Source *s)
{
if ( s != nullptr )
{
// force update
s->touch();
// attach to views
mixing_.scene.ws()->attach( s->group(View::MIXING) );
geometry_.scene.ws()->attach( s->group(View::GEOMETRY) );
layer_.scene.ws()->attach( s->group(View::LAYER) );
}
}
void Mixer::detach(Source *s)
{
if ( s != nullptr )
{
// in case it was the current source...
unsetCurrentSource();
// in case it was selected..
selection().remove(s);
// detach from views
mixing_.scene.ws()->detatch( s->group(View::MIXING) );
geometry_.scene.ws()->detatch( s->group(View::GEOMETRY) );
layer_.scene.ws()->detatch( s->group(View::LAYER) );
transition_.scene.ws()->detatch( s->group(View::TRANSITION) );
}
}
@@ -522,6 +532,7 @@ Source * Mixer::findSource (Node *node)
return nullptr;
}
Source * Mixer::findSource (std::string namesource)
{
SourceList::iterator it = session_->find(namesource);
@@ -530,6 +541,15 @@ Source * Mixer::findSource (std::string namesource)
return nullptr;
}
Source * Mixer::findSource (int id)
{
SourceList::iterator it = session_->find(id);
if (it != session_->end())
return *it;
return nullptr;
}
void Mixer::setCurrentSource(int id)
{
setCurrentSource( session_->find(id) );
@@ -557,8 +577,9 @@ void Mixer::setCurrentIndex(int index)
void Mixer::setCurrentNext()
{
SourceList::iterator it = current_source_;
if (session_->numSource() > 0) {
SourceList::iterator it = current_source_;
it++;
if (it == session_->end()) {
@@ -566,6 +587,7 @@ void Mixer::setCurrentNext()
}
setCurrentSource( it );
}
}
void Mixer::unsetCurrentSource()
@@ -786,6 +808,9 @@ void Mixer::swap()
garbage_.push_back(back_session_);
back_session_ = nullptr;
// reset History manager
Action::manager().clear();
// notification
Log::Notify("Session %s loaded. %d source(s) created.", session_->filename().c_str(), session_->numSource());
}

View File

@@ -47,6 +47,8 @@ public:
void addSource (Source *s);
void deleteSource (Source *s);
void renameSource (Source *s, const std::string &newname);
void attach (Source *s);
void detach (Source *s);
void deleteSelection();
// current source
@@ -64,6 +66,7 @@ public:
// browsing into sources
Source * findSource (Node *node);
Source * findSource (std::string name);
Source * findSource (int id);
// management of view
View *view (View::Mode m = View::INVALID);
@@ -98,8 +101,6 @@ protected:
SourceList candidate_sources_;
SourceList stash_;
void insertSource(Source *s, View::Mode m = View::INVALID);
void attach(Source *s);
void detach(Source *s);
void setCurrentSource(SourceList::iterator it);
SourceList::iterator current_source_;

View File

@@ -250,6 +250,16 @@ uint Session::numSource() const
return sources_.size();
}
std::list<int> Session::getIdList() const
{
std::list<int> idlist;
for( auto sit = sources_.begin(); sit != sources_.end(); sit++)
idlist.push_back( (*sit)->id() );
return idlist;
}
bool Session::empty() const
{
return sources_.empty();

View File

@@ -38,7 +38,9 @@ public:
SourceList::iterator find (Source *s);
SourceList::iterator find (std::string name);
SourceList::iterator find (Node *node);
SourceList::iterator find (int id);
std::list<int> getIdList() const;
SourceList::iterator at (int index);
int index (SourceList::iterator it) const;

View File

@@ -67,14 +67,16 @@ void SessionCreator::load(const std::string& filename)
return;
}
// session file seems legit, create a session
session_ = new Session;
// ok, ready to read sources
// ready to read sources
SessionLoader::load( xmlDoc_.FirstChildElement("Session") );
// load optionnal config
loadConfig( xmlDoc_.FirstChildElement("Views") );
// all good
session_->setFilename(filename);
}
@@ -100,17 +102,15 @@ void SessionLoader::load(XMLElement *sessionNode)
{
if (sessionNode != nullptr && session_ != nullptr) {
int counter = 0;
XMLElement* sourceNode = sessionNode->FirstChildElement("Source");
for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement())
{
xmlCurrent_ = sourceNode;
counter++;
// source to load
Source *load_source = nullptr;
// check if a source with same id exists
// check if a source with the given id exists in the session
int id__ = -1;
xmlCurrent_->QueryIntAttribute("id", &id__);
SourceList::iterator sit = session_->find(id__);
@@ -137,48 +137,75 @@ void SessionLoader::load(XMLElement *sessionNode)
load_source = new DeviceSource;
}
// avoid non recognized types
// skip failed (including clones)
if (!load_source)
continue;
// add source to session
session_->addSource(load_source);
id__ = load_source->id();
}
// get reference to the existing source
else
load_source = *sit;
// apply config to source
load_source->accept(*this);
// remember
sources_id_.push_back( id__ );
}
// create clones after all sources to potentially clone have been created
// create clones after all sources, to be able to clone a source created above
sourceNode = sessionNode->FirstChildElement("Source");
for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement())
{
xmlCurrent_ = sourceNode;
counter++;
// verify type of node
const char *pType = xmlCurrent_->Attribute("type");
if (!pType)
continue;
if ( std::string(pType) == "CloneSource") {
// clone to load
Source *clone_source = nullptr;
// check if a source with same id exists
int id__ = -1;
xmlCurrent_->QueryIntAttribute("id", &id__);
SourceList::iterator sit = session_->find(id__);
// no source clone with this id exists
if ( sit == session_->end() ) {
XMLElement* originNode = xmlCurrent_->FirstChildElement("origin");
if (originNode) {
std::string sourcename = std::string ( originNode->GetText() );
SourceList::iterator origin = session_->find(sourcename);
if (origin != session_->end()) {
CloneSource *new_clone_source = (*origin)->clone();
new_clone_source->accept(*this);
session_->addSource(new_clone_source);
// create a new source of type Clone
clone_source = (*origin)->clone();
}
}
// skip failed
if (!clone_source)
continue;
// add source to session
session_->addSource(clone_source);
id__ = clone_source->id();
}
else
clone_source = *sit;
// apply config to source
clone_source->accept(*this);
// remember
sources_id_.push_back( id__ );
}
}
}
}
else
Log::Warning("Session seems empty.");
}
@@ -209,11 +236,14 @@ void SessionLoader::visit(Node &n)
void SessionLoader::visit(MediaPlayer &n)
{
XMLElement* mediaplayerNode = xmlCurrent_->FirstChildElement("MediaPlayer");
int id__ = -1;
mediaplayerNode->QueryIntAttribute("id", &id__);
if (mediaplayerNode) {
// timeline
XMLElement *timelineelement = mediaplayerNode->FirstChildElement("Timeline");
if (timelineelement) {
Timeline tl;
Timeline tl = *(n.timeline());
XMLElement *gapselement = timelineelement->FirstChildElement("Gaps");
if (gapselement) {
XMLElement* gap = gapselement->FirstChildElement("Interval");
@@ -233,17 +263,23 @@ void SessionLoader::visit(MediaPlayer &n)
}
n.setTimeline(tl);
}
// playing properties
// change play status only if different id (e.g. new media player)
if ( n.id() != id__ ) {
double speed = 1.0;
mediaplayerNode->QueryDoubleAttribute("speed", &speed);
n.setPlaySpeed(speed);
int loop = 1;
mediaplayerNode->QueryIntAttribute("loop", &loop);
n.setLoop( (MediaPlayer::LoopMode) loop);
bool play = true;
mediaplayerNode->QueryBoolAttribute("play", &play);
n.play(play);
}
}
}
void SessionLoader::visit(Shader &n)
@@ -337,6 +373,8 @@ void SessionLoader::visit (MediaSource& s)
XMLElement* uriNode = xmlCurrent_->FirstChildElement("uri");
if (uriNode) {
std::string uri = std::string ( uriNode->GetText() );
// load only new files
if ( uri != s.path() )
s.setPath(uri);
}
@@ -350,6 +388,8 @@ void SessionLoader::visit (SessionSource& s)
XMLElement* pathNode = xmlCurrent_->FirstChildElement("path");
if (pathNode) {
std::string path = std::string ( pathNode->GetText() );
// load only new files
if ( path != s.path() )
s.load(path);
}
@@ -357,19 +397,24 @@ void SessionLoader::visit (SessionSource& s)
void SessionLoader::visit (PatternSource& s)
{
uint p = xmlCurrent_->UnsignedAttribute("pattern");
uint t = xmlCurrent_->UnsignedAttribute("pattern");
glm::ivec2 resolution(800, 600);
XMLElement* res = xmlCurrent_->FirstChildElement("resolution");
if (res)
tinyxml2::XMLElementToGLM( res->FirstChildElement("ivec2"), resolution);
s.setPattern(p, resolution);
// change only if different pattern
if ( t != s.pattern()->type() )
s.setPattern(t, resolution);
}
void SessionLoader::visit (DeviceSource& s)
{
const char *devname = xmlCurrent_->Attribute("device");
std::string devname = std::string ( xmlCurrent_->Attribute("device") );
// change only if different device
if ( devname != s.device() )
s.setDevice(devname);
}

View File

@@ -1,6 +1,8 @@
#ifndef SESSIONCREATOR_H
#define SESSIONCREATOR_H
#include <list>
#include "Visitor.h"
#include <tinyxml2.h>
@@ -14,6 +16,7 @@ public:
inline Session *session() const { return session_; }
void load(tinyxml2::XMLElement *sessionNode);
inline std::list<int> getIdList() const { return sources_id_; }
// Elements of Scene
void visit(Node& n) override;
@@ -37,18 +40,19 @@ public:
void visit(ImageShader& n) override;
void visit(ImageProcessingShader& n) override;
// Sources
void visit (Source& s) override;
void visit (MediaSource& s) override;
void visit (SessionSource& s) override;
void visit (PatternSource& s) override;
void visit (DeviceSource& s) override;
static void XMLToNode(tinyxml2::XMLElement *xml, Node &n);
protected:
tinyxml2::XMLElement *xmlCurrent_;
Session *session_;
std::list<int> sources_id_;
static void XMLToNode(tinyxml2::XMLElement *xml, Node &n);
};
class SessionCreator : public SessionLoader {

View File

@@ -16,6 +16,7 @@ public:
bool recursive = false);
inline tinyxml2::XMLDocument *doc() const { return xmlDoc_; }
inline void setRoot(tinyxml2::XMLElement *root) { xmlCurrent_ = root; }
// Elements of Scene
void visit(Scene& n) override;
@@ -39,6 +40,7 @@ public:
void visit(ImageShader& n) override;
void visit(ImageProcessingShader& n) override;
// Sources
void visit (Source& s) override;
void visit (MediaSource& s) override;
void visit (SessionSource& s) override;

View File

@@ -33,11 +33,13 @@ using namespace std;
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
#include "UserInterfaceManager.h"
#include "defines.h"
#include "Log.h"
#include "SystemToolkit.h"
#include "UserInterfaceManager.h"
#include "RenderingManager.h"
#include "ActionManager.h"
#include "Resource.h"
#include "FileDialog.h"
#include "Settings.h"
@@ -289,6 +291,12 @@ void UserInterface::handleKeyboard()
else
Mixer::manager().session()->addRecorder(new VideoRecorder);
}
else if (ImGui::IsKeyPressed( GLFW_KEY_Z )) {
if (shift_modifier_active)
Action::manager().redo();
else
Action::manager().undo();
}
}
// No CTRL modifier

View File

@@ -28,6 +28,7 @@
#include "Mixer.h"
#include "UserInterfaceManager.h"
#include "UpdateCallback.h"
#include "ActionManager.h"
#include "Log.h"
@@ -67,6 +68,7 @@ void View::update(float dt)
}
}
View::Cursor View::drag (glm::vec2 from, glm::vec2 to)
{
static glm::vec3 start_translation = glm::vec3(0.f);
@@ -110,6 +112,8 @@ std::pair<Node *, glm::vec2> View::pick(glm::vec2 P)
void View::initiate()
{
current_action_ = "";
current_id_ = -1;
for (auto sit = Mixer::manager().session()->begin();
sit != Mixer::manager().session()->end(); sit++){
@@ -117,6 +121,13 @@ void View::initiate()
}
}
void View::terminate()
{
Action::manager().store(current_action_, current_id_);
current_action_ = "";
current_id_ = -1;
}
void View::recenter()
{
// restore default view
@@ -462,6 +473,7 @@ View::Cursor MixingView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pai
// s->group(mode_)->translation_.y = s->group(mode_)->translation_.x * s->stored_status_->translation_.y / s->stored_status_->translation_.x;
// }
// // trying to enter stash
// if ( glm::distance( glm::vec2(s->group(mode_)->translation_), glm::vec2(stashCircle_->translation_)) < stashCircle_->scale_.x) {
@@ -484,11 +496,15 @@ View::Cursor MixingView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pai
std::ostringstream info;
if (s->active())
info << "Alpha " << std::fixed << std::setprecision(3) << s->blendingShader()->color.a;
else if ( Mixer::manager().concealed(s) )
info << "Stashed";
// else if ( Mixer::manager().concealed(s) )
// info << "Stashed";
else
info << "Inactive";
// store action in history
current_action_ = s->name() + " " + info.str();
current_id_ = s->id();
return Cursor(Cursor_ResizeAll, info.str() );
}
@@ -959,6 +975,7 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p
ret.type = corner.x * corner.y > 0.f ? Cursor_ResizeNESW : Cursor_ResizeNWSE;
info << "Size " << std::fixed << std::setprecision(3) << sourceNode->scale_.x;
info << " x " << sourceNode->scale_.y;
}
// picking on the BORDER RESIZING handles left or right
else if ( pick.first == s->handle_[Handles::RESIZE_H] ) {
@@ -1156,6 +1173,11 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p
// request update
s->touch();
// store action in history
current_action_ = s->name() + " " + info.str();
current_id_ = s->id();
// update cursor
ret.info = info.str();
return ret;
}
@@ -1163,6 +1185,8 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p
void GeometryView::terminate()
{
View::terminate();
// hide all overlays
overlay_position_->visible_ = false;
overlay_position_cross_->visible_ = false;
@@ -1329,6 +1353,11 @@ View::Cursor LayerView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair
std::ostringstream info;
info << "Depth " << std::fixed << std::setprecision(2) << d;
// store action in history
current_action_ = s->name() + " " + info.str();
current_id_ = s->id();
return Cursor(Cursor_ResizeNESW, info.str() );
}

4
View.h
View File

@@ -63,7 +63,7 @@ public:
// grab a source provided a start and an end point in screen coordinates and the picking point
virtual void initiate();
virtual void terminate() {}
virtual void terminate();
virtual Cursor grab (Source*, glm::vec2, glm::vec2, std::pair<Node *, glm::vec2>) {
return Cursor();
}
@@ -84,6 +84,8 @@ protected:
virtual void restoreSettings();
virtual void saveSettings();
std::string current_action_;
int current_id_;
Mode mode_;
};