JournallingObject, ProjectJournal: global checkpoint management

There's no need for having each JournallingObject maintain it's own
checkpoints and build a complex (and buggy) logic in ProjectJournal
in order to manage all the JournallingObject with their checkpoints.

Instead do it the simple way: in ProjectJournal maintain a stack for
undo checkpoints and a stack for redo checkpoints. On each undo or redo
operation simply push and pop to/from the according stacks and save
and load states of the concerned JournallingObject.

This basically strips most functionality from JournallingObject. All
what's left is the management of its ID which unluckily is still
required in order to properly implement undo/redo of additions and
removals of JournallingObject.
This commit is contained in:
Tobias Doerffel
2014-01-07 23:50:27 +01:00
parent 1f203a10f9
commit 4641a8001b
4 changed files with 85 additions and 248 deletions

View File

@@ -27,7 +27,6 @@
#include "lmms_basics.h"
#include "export.h"
#include "mmp.h"
#include "SerializingObject.h"
#include <QtCore/QVariant>
@@ -35,39 +34,6 @@
#include <QtCore/QStack>
class JournalCheckPoint
{
public:
JournalCheckPoint( const multimediaProject &data =
multimediaProject( multimediaProject::JournalData ) ) :
m_data( data )
{
}
~JournalCheckPoint()
{
}
const multimediaProject &data() const
{
return m_data;
}
multimediaProject &data()
{
return m_data;
}
private:
multimediaProject m_data;
} ;
typedef QVector<JournalCheckPoint> JournalCheckPointVector;
class EXPORT JournallingObject : public SerializingObject
{
public:
@@ -79,27 +45,10 @@ public:
return m_id;
}
void undo();
void redo();
void clear()
{
m_journalCheckPoints.clear();
m_currentJournalCheckPoint = m_journalCheckPoints.end();
}
void clearRedoSteps()
{
m_journalCheckPoints.erase( m_currentJournalCheckPoint,
m_journalCheckPoints.end() );
m_currentJournalCheckPoint = m_journalCheckPoints.end();
}
void saveJournallingState( const bool _new_state )
void saveJournallingState( const bool newState )
{
m_journallingStateStack.push( m_journalling );
m_journalling = _new_state;
m_journalling = newState;
}
void restoreJournallingState()
@@ -110,11 +59,10 @@ public:
void addJournalCheckPoint();
virtual QDomElement saveState( QDomDocument & _doc,
QDomElement & _parent );
QDomElement & _parent );
virtual void restoreState( const QDomElement & _this );
inline bool isJournalling() const
{
return m_journalling;
@@ -125,10 +73,10 @@ public:
m_journalling = _sr;
}
inline bool testAndSetJournalling( const bool _sr )
inline bool testAndSetJournalling( const bool newState )
{
const bool oldJournalling = m_journalling;
m_journalling = _sr;
m_journalling = newState;
return oldJournalling;
}
@@ -138,15 +86,8 @@ protected:
private:
void saveJournal( QDomDocument & _doc, QDomElement & _parent );
void loadJournal( const QDomElement & _this );
jo_id_t m_id;
JournalCheckPointVector m_journalCheckPoints;
JournalCheckPointVector::Iterator m_currentJournalCheckPoint;
bool m_journalling;
QStack<bool> m_journallingStateStack;

View File

@@ -1,7 +1,7 @@
/*
* ProjectJournal.h - declaration of class ProjectJournal
*
* Copyright (c) 2006-2008 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* Copyright (c) 2006-2010 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net
*
@@ -26,10 +26,10 @@
#define _PROJECT_JOURNAL_H
#include <QtCore/QHash>
#include <QtCore/QVariant>
#include <QtCore/QVector>
#include <QtCore/QStack>
#include "lmms_basics.h"
#include "mmp.h"
class JournallingObject;
@@ -43,8 +43,7 @@ public:
void undo();
void redo();
// tell history that a new journal entry was added to object with ID _id
void journalEntryAdded( const jo_id_t _id );
void addJournalCheckPoint( JournallingObject *jo );
bool isJournalling() const
{
@@ -72,10 +71,6 @@ public:
reallocID( _id, NULL );
}
// completely remove everything linked with ID _id - all global
// journalling information about the ID get's lost
void forgetAboutID( const jo_id_t _id );
void clearJournal();
JournallingObject * journallingObject( const jo_id_t _id )
@@ -90,12 +85,25 @@ public:
private:
typedef QHash<jo_id_t, JournallingObject *> JoIdMap;
typedef QVector<jo_id_t> JournalEntryVector;
struct CheckPoint
{
CheckPoint( jo_id_t initID = 0,
const multimediaProject &initData =
multimediaProject( multimediaProject::JournalData ) ) :
joID( initID ),
data( initData )
{
}
jo_id_t joID;
multimediaProject data;
} ;
typedef QStack<CheckPoint> CheckPointStack;
JoIdMap m_joIDs;
JournalEntryVector m_journalEntries;
JournalEntryVector::Iterator m_currentJournalEntry;
CheckPointStack m_undoCheckPoints;
CheckPointStack m_redoCheckPoints;
bool m_journalling;

View File

@@ -37,8 +37,6 @@
JournallingObject::JournallingObject() :
SerializingObject(),
m_id( engine::projectJournal()->allocID( this ) ),
m_journalCheckPoints(),
m_currentJournalCheckPoint( m_journalCheckPoints.end() ),
m_journalling( true ),
m_journallingStateStack()
{
@@ -58,34 +56,11 @@ JournallingObject::~JournallingObject()
void JournallingObject::undo()
void JournallingObject::addJournalCheckPoint()
{
if( m_journalCheckPoints.empty() == true )
if( isJournalling() )
{
return;
}
if( m_currentJournalCheckPoint - 1 >= m_journalCheckPoints.begin() )
{
--m_currentJournalCheckPoint;
restoreState( m_currentJournalCheckPoint->data().content().firstChildElement() );
}
}
void JournallingObject::redo()
{
if( m_journalCheckPoints.empty() == true )
{
return;
}
if( m_currentJournalCheckPoint < m_journalCheckPoints.end() )
{
restoreState( m_currentJournalCheckPoint->data().content().firstChildElement() );
++m_currentJournalCheckPoint;
engine::projectJournal()->addJournalCheckPoint( this );
}
}
@@ -96,7 +71,12 @@ QDomElement JournallingObject::saveState( QDomDocument & _doc,
QDomElement & _parent )
{
QDomElement _this = SerializingObject::saveState( _doc, _parent );
saveJournal( _doc, _this );
QDomElement journalNode = _doc.createElement( "journallingObject" );
journalNode.setAttribute( "id", id() );
journalNode.setAttribute( "metadata", true );
_this.appendChild( journalNode );
return _this;
}
@@ -115,7 +95,11 @@ void JournallingObject::restoreState( const QDomElement & _this )
{
if( node.isElement() && node.nodeName() == "journal" )
{
loadJournal( node.toElement() );
const jo_id_t new_id = node.toElement().attribute( "id" ).toInt();
if( new_id )
{
changeID( new_id );
}
}
node = node.nextSibling();
}
@@ -126,26 +110,6 @@ void JournallingObject::restoreState( const QDomElement & _this )
void JournallingObject::addJournalCheckPoint()
{
if( engine::projectJournal()->isJournalling() && isJournalling() )
{
m_journalCheckPoints.erase( m_currentJournalCheckPoint,
m_journalCheckPoints.end() );
multimediaProject mmp( multimediaProject::JournalData );
saveState( mmp, mmp.content() );
m_journalCheckPoints.push_back( JournalCheckPoint( mmp ) );
m_currentJournalCheckPoint = m_journalCheckPoints.end();
engine::projectJournal()->journalEntryAdded( id() );
}
}
void JournallingObject::changeID( jo_id_t _id )
{
if( id() != _id )
@@ -166,77 +130,10 @@ void JournallingObject::changeID( jo_id_t _id )
(int) _id, used_by.toUtf8().constData() );
return;
}
engine::projectJournal()->forgetAboutID( id() );
engine::projectJournal()->reallocID( _id, this );
m_id = _id;
}
}
void JournallingObject::saveJournal( QDomDocument & _doc,
QDomElement & _parent )
{
/* // avoid creating empty journal-nodes
if( m_journalCheckPoints.size() == 0 )
{
return;
}*/
QDomElement journal_de = _doc.createElement( "journal" );
journal_de.setAttribute( "id", id() );
journal_de.setAttribute( "entries", m_journalCheckPoints.size() );
journal_de.setAttribute( "curentry", (int)( m_currentJournalCheckPoint -
m_journalCheckPoints.begin() ) );
journal_de.setAttribute( "metadata", true );
for( JournalCheckPointVector::const_iterator it = m_journalCheckPoints.begin();
it != m_journalCheckPoints.end(); ++it )
{
QDomElement je_de = _doc.createElement( "entry" );
je_de.setAttribute( "pos", (int)( it -
m_journalCheckPoints.begin() ) );
//je_de.setAttribute( "data", base64::encode( it->data().toString() ) );
je_de.setAttribute( "data", it->data().toString() );
journal_de.appendChild( je_de );
}
_parent.appendChild( journal_de );
}
void JournallingObject::loadJournal( const QDomElement & _this )
{
clear();
const jo_id_t new_id = _this.attribute( "id" ).toInt();
if( new_id == 0 )
{
return;
}
changeID( new_id );
m_journalCheckPoints.resize( _this.attribute( "entries" ).toInt() );
QDomNode node = _this.firstChild();
while( !node.isNull() )
{
if( node.isElement() )
{
const QDomElement & je = node.toElement();
m_journalCheckPoints[je.attribute( "pos" ).toInt()] =
JournalCheckPoint(
multimediaProject( je.attribute( "data" ).toUtf8() ) );
}
node = node.nextSibling();
}
m_currentJournalCheckPoint = m_journalCheckPoints.begin() +
_this.attribute( "curentry" ).toInt();
}

View File

@@ -32,8 +32,8 @@
ProjectJournal::ProjectJournal() :
m_joIDs(),
m_journalEntries(),
m_currentJournalEntry( m_journalEntries.end() ),
m_undoCheckPoints(),
m_redoCheckPoints(),
m_journalling( false )
{
}
@@ -50,57 +50,66 @@ ProjectJournal::~ProjectJournal()
void ProjectJournal::undo()
{
if( m_journalEntries.empty() == true )
while( !m_undoCheckPoints.isEmpty() )
{
return;
}
CheckPoint c = m_undoCheckPoints.pop();
JournallingObject *jo = m_joIDs[c.joID];
JournallingObject * jo;
if( jo )
{
multimediaProject curState( multimediaProject::JournalData );
jo->saveState( curState, curState.content() );
m_redoCheckPoints.push( CheckPoint( c.joID, curState ) );
if( m_currentJournalEntry - 1 >= m_journalEntries.begin() &&
( jo = m_joIDs[*--m_currentJournalEntry] ) != NULL )
{
bool prev = isJournalling();
setJournalling( false );
jo->undo();
setJournalling( prev );
engine::getSong()->setModified();
bool prev = isJournalling();
setJournalling( false );
jo->restoreState( c.data.content().firstChildElement() );
setJournalling( prev );
engine::getSong()->setModified();
break;
}
}
}
void ProjectJournal::redo()
{
if( m_journalEntries.empty() == true )
while( !m_redoCheckPoints.isEmpty() )
{
return;
}
CheckPoint c = m_redoCheckPoints.pop();
JournallingObject *jo = m_joIDs[c.joID];
JournallingObject * jo;
if( jo )
{
multimediaProject curState( multimediaProject::JournalData );
jo->saveState( curState, curState.content() );
m_undoCheckPoints.push( CheckPoint( c.joID, curState ) );
//printf("%d\n", m_joIDs[*(m_currentJournalEntry+1)] );
if( m_currentJournalEntry < m_journalEntries.end() &&
( jo = m_joIDs[*m_currentJournalEntry++] ) != NULL )
{
bool prev = isJournalling();
setJournalling( false );
jo->redo();
setJournalling( prev );
engine::getSong()->setModified();
bool prev = isJournalling();
setJournalling( false );
jo->restoreState( c.data.content().firstChildElement() );
setJournalling( prev );
engine::getSong()->setModified();
break;
}
}
}
void ProjectJournal::journalEntryAdded( const jo_id_t _id )
void ProjectJournal::addJournalCheckPoint( JournallingObject *jo )
{
m_journalEntries.erase( m_currentJournalEntry, m_journalEntries.end() );
m_journalEntries.push_back( _id );
m_currentJournalEntry = m_journalEntries.end();
engine::getSong()->setModified();
if( isJournalling() )
{
m_redoCheckPoints.clear();
multimediaProject mmp( multimediaProject::JournalData );
jo->saveState( mmp, mmp.content() );
m_undoCheckPoints.push( CheckPoint( jo->id(), mmp ) );
}
}
@@ -136,29 +145,11 @@ void ProjectJournal::reallocID( const jo_id_t _id, JournallingObject * _obj )
void ProjectJournal::forgetAboutID( const jo_id_t _id )
{
//printf("forget about %d\n", _id );
JournalEntryVector::Iterator it;
while( ( it = qFind( m_journalEntries.begin(), m_journalEntries.end(),
_id ) ) != m_journalEntries.end() )
{
if( m_currentJournalEntry >= it )
{
--m_currentJournalEntry;
}
m_journalEntries.erase( it );
}
m_joIDs.remove( _id );
}
void ProjectJournal::clearJournal()
{
m_journalEntries.clear();
m_currentJournalEntry = m_journalEntries.end();
m_undoCheckPoints.clear();
m_redoCheckPoints.clear();
for( JoIdMap::Iterator it = m_joIDs.begin(); it != m_joIDs.end(); )
{
if( it.value() == NULL )