Snapshots in Action manager

This commit is contained in:
brunoherbelin
2021-04-15 22:50:28 +02:00
parent 11df7c28b4
commit 2d4a6d1fe6
5 changed files with 242 additions and 134 deletions

View File

@@ -10,6 +10,7 @@
#include "SessionVisitor.h" #include "SessionVisitor.h"
#include "SessionCreator.h" #include "SessionCreator.h"
#include "Settings.h" #include "Settings.h"
#include "GlmToolkit.h"
#include "ActionManager.h" #include "ActionManager.h"
@@ -19,16 +20,20 @@
using namespace tinyxml2; using namespace tinyxml2;
Action::Action(): step_(0), max_step_(0) Action::Action(): history_step_(0), history_max_step_(0)
{ {
} }
void Action::clear() void Action::clear()
{ {
// clean the history // clean the history
xmlDoc_.Clear(); history_doc_.Clear();
step_ = 0; history_step_ = 0;
max_step_ = 0; history_max_step_ = 0;
// clean the snapshots
snapshots_doc_.Clear();
snapshots_.clear();
// start fresh // start fresh
store("Session start"); store("Session start");
@@ -41,21 +46,21 @@ void Action::store(const std::string &label)
return; return;
// incremental naming of history nodes // incremental naming of history nodes
step_++; history_step_++;
std::string nodename = "H" + std::to_string(step_); std::string nodename = "H" + std::to_string(history_step_);
// erase future // erase future
for (uint e = step_; e <= max_step_; e++) { for (uint e = history_step_; e <= history_max_step_; e++) {
std::string name = "H" + std::to_string(e); std::string name = "H" + std::to_string(e);
XMLElement *node = xmlDoc_.FirstChildElement( name.c_str() ); XMLElement *node = history_doc_.FirstChildElement( name.c_str() );
if ( node ) if ( node )
xmlDoc_.DeleteChild(node); history_doc_.DeleteChild(node);
} }
max_step_ = step_; history_max_step_ = history_step_;
// create history node // create history node
XMLElement *sessionNode = xmlDoc_.NewElement( nodename.c_str() ); XMLElement *sessionNode = history_doc_.NewElement( nodename.c_str() );
xmlDoc_.InsertEndChild(sessionNode); history_doc_.InsertEndChild(sessionNode);
// label describes the action // label describes the action
sessionNode->SetAttribute("label", label.c_str()); sessionNode->SetAttribute("label", label.c_str());
// view indicates the view when this action occured // view indicates the view when this action occured
@@ -65,7 +70,7 @@ void Action::store(const std::string &label)
Session *se = Mixer::manager().session(); Session *se = Mixer::manager().session();
// save all sources using source visitor // save all sources using source visitor
SessionVisitor sv(&xmlDoc_, sessionNode); SessionVisitor sv(&history_doc_, sessionNode);
for (auto iter = se->begin(); iter != se->end(); iter++, sv.setRoot(sessionNode) ) for (auto iter = se->begin(); iter != se->end(); iter++, sv.setRoot(sessionNode) )
(*iter)->accept(sv); (*iter)->accept(sv);
@@ -79,31 +84,31 @@ void Action::store(const std::string &label)
void Action::undo() void Action::undo()
{ {
// not possible to go to 1 -1 = 0 // not possible to go to 1 -1 = 0
if (step_ <= 1) if (history_step_ <= 1)
return; return;
// restore always changes step_ to step_ - 1 // restore always changes step_ to step_ - 1
restore( step_ - 1); restore( history_step_ - 1);
} }
void Action::redo() void Action::redo()
{ {
// not possible to go to max_step_ + 1 // not possible to go to max_step_ + 1
if (step_ >= max_step_) if (history_step_ >= history_max_step_)
return; return;
// restore always changes step_ to step_ + 1 // restore always changes step_ to step_ + 1
restore( step_ + 1); restore( history_step_ + 1);
} }
void Action::stepTo(uint target) void Action::stepTo(uint target)
{ {
// get reasonable target // get reasonable target
uint t = CLAMP(target, 1, max_step_); uint t = CLAMP(target, 1, history_max_step_);
// ignore t == step_ // ignore t == step_
if (t != step_) if (t != history_step_)
restore(t); restore(t);
} }
@@ -111,9 +116,9 @@ std::string Action::label(uint s) const
{ {
std::string l = ""; std::string l = "";
if (s > 0 && s <= max_step_) { if (s > 0 && s <= history_max_step_) {
std::string nodename = "H" + std::to_string(s); std::string nodename = "H" + std::to_string(s);
const XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() ); const XMLElement *sessionNode = history_doc_.FirstChildElement( nodename.c_str() );
l = sessionNode->Attribute("label"); l = sessionNode->Attribute("label");
} }
return l; return l;
@@ -125,100 +130,102 @@ void Action::restore(uint target)
locked_ = true; locked_ = true;
// get history node of target step // get history node of target step
step_ = CLAMP(target, 1, max_step_); history_step_ = CLAMP(target, 1, history_max_step_);
std::string nodename = "H" + std::to_string(step_); std::string nodename = "H" + std::to_string(history_step_);
XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() ); XMLElement *sessionNode = history_doc_.FirstChildElement( nodename.c_str() );
// ask view to refresh, and switch to action view if user prefers if (sessionNode) {
int view = Settings::application.current_view ;
if (Settings::application.action_history_follow_view)
sessionNode->QueryIntAttribute("view", &view);
Mixer::manager().setView( (View::Mode) view);
#ifdef ACTION_DEBUG // ask view to refresh, and switch to action view if user prefers
Log::Info("Restore %s '%s' ", nodename.c_str(), sessionNode->Attribute("label")); int view = Settings::application.current_view ;
#endif if (Settings::application.action_history_follow_view)
sessionNode->QueryIntAttribute("view", &view);
Mixer::manager().setView( (View::Mode) view);
// // actually restore
// compare source lists Mixer::manager().restore(sessionNode);
// }
// we operate on the current session // free
Session *se = Mixer::manager().session(); locked_ = false;
if (se == nullptr) }
return;
void Action::snapshot(const std::string &label)
// sessionsources contains list of ids of all sources currently in the session (before loading) {
SourceIdList session_sources = se->getIdList(); // ignore if locked or if no label is given
// for( auto it = sessionsources.begin(); it != sessionsources.end(); it++) if (locked_ || label.empty())
// Log::Info("sessionsources id %s", std::to_string(*it).c_str()); return;
// load history status: u_int64_t id = GlmToolkit::uniqueId();
// - if a source exists, its attributes are updated, and that's all snapshots_.push_back(id);
// - if a source does not exists (in current session), it is created inside the session
SessionLoader loader( se ); // create history node
loader.load( sessionNode ); XMLElement *sessionNode = snapshots_doc_.NewElement( std::to_string(id).c_str() );
snapshots_doc_.InsertEndChild(sessionNode);
// loaded_sources contains map of xml ids of all sources treated by loader // label describes the action
std::map< uint64_t, Source* > loaded_sources = loader.getSources(); sessionNode->SetAttribute("label", label.c_str());
// remove intersect of both lists (sources were updated by SessionLoader) // get session to operate on
for( auto lsit = loaded_sources.begin(); lsit != loaded_sources.end(); ){ Session *se = Mixer::manager().session();
auto ssit = std::find(session_sources.begin(), session_sources.end(), (*lsit).first);
if ( ssit != session_sources.end() ) { // save all sources using source visitor
lsit = loaded_sources.erase(lsit); SessionVisitor sv(&snapshots_doc_, sessionNode);
session_sources.erase(ssit); for (auto iter = se->begin(); iter != se->end(); iter++, sv.setRoot(sessionNode) )
} (*iter)->accept(sv);
else
lsit++; // debug
} #ifdef ACTION_DEBUG
Log::Info("Snapshot stored %s '%s'", std::to_string(id).c_str(), label.c_str());
// remaining ids in list sessionsources : to remove // XMLSaveDoc(&xmlDoc_, "/home/bhbn/history.xml");
while ( !session_sources.empty() ){ #endif
Source *s = Mixer::manager().findSource( session_sources.front() ); }
if (s!=nullptr) {
#ifdef ACTION_DEBUG std::string Action::label(uint64_t s) const
Log::Info("Delete id %s\n", std::to_string(session_sources.front() ).c_str()); {
#endif std::string l = "";
// remove the source from the mixer const XMLElement *sessionNode = snapshots_doc_.FirstChildElement( std::to_string(s).c_str() );
Mixer::manager().detach( s );
// delete source from session if (sessionNode) {
se->deleteSource( s ); l = sessionNode->Attribute("label");
} }
session_sources.pop_front(); return l;
} }
// remaining sources in list loaded_sources : to add void Action::setLabel (uint64_t snapshotid, const std::string &label)
for ( auto lsit = loaded_sources.begin(); lsit != loaded_sources.end(); lsit++) {
{ // get history node of target
#ifdef ACTION_DEBUG XMLElement *sessionNode = snapshots_doc_.FirstChildElement( std::to_string(snapshotid).c_str() );
Log::Info("Recreate id %s to %s\n", std::to_string((*lsit).first).c_str(), std::to_string((*lsit).second->id()).c_str());
#endif if (sessionNode) {
// attach created source sessionNode->SetAttribute("label", label.c_str());
Mixer::manager().attach( (*lsit).second ); }
}
}
void Action::remove(uint64_t snapshotid)
// {
// compare mixing groups // get history node of target
// XMLElement *sessionNode = snapshots_doc_.FirstChildElement( std::to_string(snapshotid).c_str() );
// Get the list of mixing groups in the xml loader if (sessionNode) {
std::list< SourceList > loadergroups = loader.getMixingGroups(); snapshots_doc_.DeleteChild( sessionNode );
snapshots_.remove(snapshotid);
// clear all session groups }
auto group_iter = se->beginMixingGroup(); }
while ( group_iter != se->endMixingGroup() )
group_iter = se->deleteMixingGroup(group_iter); void Action::restore(uint64_t snapshotid)
{
// apply all changes creating or modifying groups in the session // lock
// (after this, new groups are created and existing groups are adjusted) locked_ = true;
for (auto group_loader_it = loadergroups.begin(); group_loader_it != loadergroups.end(); group_loader_it++) {
se->link( *group_loader_it, Mixer::manager().view(View::MIXING)->scene.fg() ); // get history node of target
XMLElement *sessionNode = snapshots_doc_.FirstChildElement( std::to_string(snapshotid).c_str() );
if (sessionNode) {
// actually restore
Mixer::manager().restore(sessionNode);
} }
// free // free
locked_ = false; locked_ = false;
} }

View File

@@ -1,11 +1,13 @@
#ifndef ACTIONMANAGER_H #ifndef ACTIONMANAGER_H
#define ACTIONMANAGER_H #define ACTIONMANAGER_H
#include <list>
#include <atomic> #include <atomic>
#include <tinyxml2.h> #include <tinyxml2.h>
class Action class Action
{ {
// Private Constructor // Private Constructor
@@ -23,25 +25,36 @@ public:
} }
void store(const std::string &label); void store(const std::string &label);
void clear(); void clear();
void undo(); void undo();
void redo(); void redo();
void stepTo(uint target); void stepTo(uint target);
inline uint current() const { return step_; } inline uint current() const { return history_step_; }
inline uint max() const { return max_step_; } inline uint max() const { return history_max_step_; }
std::string label(uint s) const; std::string label(uint s) const;
void snapshot(const std::string &label);
inline std::list<uint64_t> snapshots() const { return snapshots_; }
std::string label(uint64_t s) const;
void restore(uint64_t snapshotid);
void remove (uint64_t snapshotid);
void setLabel (uint64_t snapshotid, const std::string &label);
private: private:
void restore(uint target); void restore(uint target);
tinyxml2::XMLDocument xmlDoc_; tinyxml2::XMLDocument history_doc_;
uint step_; uint history_step_;
uint max_step_; uint history_max_step_;
std::atomic<bool> locked_; std::atomic<bool> locked_;
tinyxml2::XMLDocument snapshots_doc_;
std::list<uint64_t> snapshots_;
}; };

View File

@@ -1241,3 +1241,81 @@ void Mixer::paste(const std::string& clipboard)
} }
} }
} }
void Mixer::restore(tinyxml2::XMLElement *sessionNode)
{
//
// source lists
//
// sessionsources contains list of ids of all sources currently in the session (before loading)
SourceIdList session_sources = session_->getIdList();
// for( auto it = sessionsources.begin(); it != sessionsources.end(); it++)
// Log::Info("sessionsources id %s", std::to_string(*it).c_str());
// load history status:
// - if a source exists, its attributes are updated, and that's all
// - if a source does not exists (in current session), it is created inside the session
SessionLoader loader( session_ );
loader.load( sessionNode );
// loaded_sources contains map of xml ids of all sources treated by loader
std::map< uint64_t, Source* > loaded_sources = loader.getSources();
// remove intersect of both lists (sources were updated by SessionLoader)
for( auto lsit = loaded_sources.begin(); lsit != loaded_sources.end(); ){
auto ssit = std::find(session_sources.begin(), session_sources.end(), (*lsit).first);
if ( ssit != session_sources.end() ) {
lsit = loaded_sources.erase(lsit);
session_sources.erase(ssit);
}
else
lsit++;
}
// remaining ids in list sessionsources : to remove
while ( !session_sources.empty() ){
Source *s = Mixer::manager().findSource( session_sources.front() );
if (s!=nullptr) {
#ifdef ACTION_DEBUG
Log::Info("Delete id %s\n", std::to_string(session_sources.front() ).c_str());
#endif
// remove the source from the mixer
detach( s );
// delete source from session
session_->deleteSource( s );
}
session_sources.pop_front();
}
// remaining sources in list loaded_sources : to add
for ( auto lsit = loaded_sources.begin(); lsit != loaded_sources.end(); lsit++)
{
#ifdef ACTION_DEBUG
Log::Info("Recreate id %s to %s\n", std::to_string((*lsit).first).c_str(), std::to_string((*lsit).second->id()).c_str());
#endif
// attach created source
attach( (*lsit).second );
}
//
// mixing groups
//
// Get the list of mixing groups in the xml loader
std::list< SourceList > loadergroups = loader.getMixingGroups();
// clear all session groups
auto group_iter = session_->beginMixingGroup();
while ( group_iter != session_->endMixingGroup() )
group_iter = session_->deleteMixingGroup(group_iter);
// apply all changes creating or modifying groups in the session
// (after this, new groups are created and existing groups are adjusted)
for (auto group_loader_it = loadergroups.begin(); group_loader_it != loadergroups.end(); group_loader_it++)
session_->link( *group_loader_it, view(View::MIXING)->scene.fg() );
}

View File

@@ -9,6 +9,10 @@
#include "Session.h" #include "Session.h"
#include "Selection.h" #include "Selection.h"
namespace tinyxml2 {
class XMLElement;
}
class SessionSource; class SessionSource;
class Mixer class Mixer
@@ -108,6 +112,7 @@ public:
// create sources if clipboard contains well-formed xml text // create sources if clipboard contains well-formed xml text
void paste (const std::string& clipboard); void paste (const std::string& clipboard);
void restore(tinyxml2::XMLElement *sessionNode);
protected: protected:

View File

@@ -3099,10 +3099,7 @@ void Navigator::RenderMainPannelVimix()
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::ListBoxHeader("##UndoHistory", CLAMP(Action::manager().max(), 4, 8)); ImGui::ListBoxHeader("##UndoHistory", CLAMP(Action::manager().max(), 4, 8));
for (uint i = Action::manager().max(); i > 0; --i) { for (uint i = Action::manager().max(); i > 0; --i) {
if (ImGui::Selectable( Action::manager().label(i).c_str(), i == Action::manager().current(), ImGuiSelectableFlags_AllowDoubleClick )) {
std::string step_label_ = Action::manager().label(i);
bool enable = i == Action::manager().current();
if (ImGui::Selectable( step_label_.c_str(), &enable, ImGuiSelectableFlags_AllowDoubleClick )) {
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
Action::manager().stepTo(i); Action::manager().stepTo(i);
} }
@@ -3132,31 +3129,32 @@ void Navigator::RenderMainPannelVimix()
} }
else { else {
static std::list<std::string> snapshots; std::list<uint64_t> snapshots = Action::manager().snapshots();
static std::list<std::string>::iterator current_snapshot = snapshots.end(); static uint64_t current_snapshot = 0;
pos_top = ImGui::GetCursorPos(); pos_top = ImGui::GetCursorPos();
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::ListBoxHeader("##Snapshots", CLAMP(snapshots.size(), 4, 8)); ImGui::ListBoxHeader("##Snapshots", CLAMP(snapshots.size(), 4, 8));
ImVec2 size = ImVec2( ImGui::GetContentRegionAvailWidth(), ImGui::GetTextLineHeight() ); ImVec2 size = ImVec2( ImGui::GetContentRegionAvailWidth(), ImGui::GetTextLineHeight() );
for (auto ait = snapshots.begin(); ait != snapshots.end(); ++ait) for (auto snapit = snapshots.begin(); snapit != snapshots.end(); ++snapit)
{ {
// size of items // size of items
ImVec2 s = size; ImVec2 s = size;
if ( current_snapshot == ait ) if ( current_snapshot == *snapit )
s.x -= ImGui::GetTextLineHeightWithSpacing(); s.x -= ImGui::GetTextLineHeightWithSpacing();
// entry // entry
if (ImGui::Selectable( ait->c_str(), current_snapshot == ait, ImGuiSelectableFlags_AllowDoubleClick, s )) { if (ImGui::Selectable( Action::manager().label(*snapit).c_str(), current_snapshot == *snapit, ImGuiSelectableFlags_AllowDoubleClick, s )) {
// current list item // current list item
current_snapshot = ait; current_snapshot = *snapit;
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
// trigger snapshot // trigger snapshot
// current_snapshot = snapshots.end(); // current_snapshot = snapshots.end();
Action::manager().restore(current_snapshot);
} }
} }
// context menu // context menu
if ( current_snapshot == ait ) { if ( current_snapshot == *snapit ) {
ImGui::SameLine(); ImGui::SameLine();
if ( ImGuiToolkit::IconButton( ICON_FA_CHEVRON_DOWN ) ) if ( ImGuiToolkit::IconButton( ICON_FA_CHEVRON_DOWN ) )
ImGui::OpenPopup( "MenuSnapshot" ); ImGui::OpenPopup( "MenuSnapshot" );
@@ -3167,16 +3165,22 @@ void Navigator::RenderMainPannelVimix()
if (ImGui::BeginPopup( "MenuSnapshot" )) if (ImGui::BeginPopup( "MenuSnapshot" ))
{ {
if (ImGui::Selectable( " " ICON_FA_ANGLE_DOUBLE_RIGHT " Apply", false, 0, size )) { if (ImGui::Selectable( " " ICON_FA_ANGLE_DOUBLE_RIGHT " Apply", false, 0, size )) {
// current_snapshot = snapshots.end(); Action::manager().restore(current_snapshot);
current_snapshot = 0;
} }
if (ImGui::Selectable( ICON_FA_STAR "_ Remove", false, 0, size )) { if (ImGui::Selectable( ICON_FA_STAR "_ Remove", false, 0, size )) {
snapshots.erase(current_snapshot);
current_snapshot = snapshots.end();
Action::manager().remove(current_snapshot);
current_snapshot = 0;
} }
ImGui::TextDisabled("Rename"); ImGui::TextDisabled("Rename");
ImGui::SetNextItemWidth(size.x); ImGui::SetNextItemWidth(size.x);
if ( current_snapshot != snapshots.end() ) { if ( current_snapshot > 0 ) {
ImGuiToolkit::InputText("##Rename", &(*current_snapshot) ); static std::string label;
label = Action::manager().label(current_snapshot);
if ( ImGuiToolkit::InputText("##Rename", &label ) )
Action::manager().setLabel(current_snapshot, label);
} }
ImGui::EndPopup(); ImGui::EndPopup();
} }
@@ -3187,7 +3191,8 @@ void Navigator::RenderMainPannelVimix()
// right buttons // right buttons
ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y )); ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y ));
if ( ImGuiToolkit::IconButton( ICON_FA_STAR "+")) { if ( ImGuiToolkit::IconButton( ICON_FA_STAR "+")) {
snapshots.push_back( SystemToolkit::date_time_string() ); // snapshots.push_back( SystemToolkit::date_time_string() );
Action::manager().snapshot( SystemToolkit::date_time_string() );
} }
// // active list element : delete snapshot button // // active list element : delete snapshot button
// ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - ImGui::GetFrameHeightWithSpacing())); // ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - ImGui::GetFrameHeightWithSpacing()));