| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include<iostream> |
| | #include "rs_undo.h" |
| | #include <unordered_set> |
| | #include "rs_debug.h" |
| | #include "rs_undocycle.h" |
| |
|
| | |
| | |
| | |
| | int RS_Undo::countUndoCycles() { |
| | RS_DEBUG->print("RS_Undo::countUndoCycles"); |
| |
|
| | return std::distance(undoList.cbegin(), m_redoPointer); |
| | } |
| |
|
| | |
| | |
| | |
| | int RS_Undo::countRedoCycles() { |
| | RS_DEBUG->print("RS_Undo::countRedoCycles"); |
| |
|
| | return std::distance(m_redoPointer, undoList.cend()); |
| | } |
| |
|
| | |
| | |
| | |
| | bool RS_Undo::hasUndoable() |
| | { |
| | return nullptr != currentCycle && !currentCycle->empty(); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | void RS_Undo::addUndoCycle(std::shared_ptr<RS_UndoCycle> undoCycle) { |
| | RS_DEBUG->print("RS_Undo::addUndoCycle"); |
| |
|
| | |
| | undoList.push_back(std::move(undoCycle)); |
| | m_redoPointer = undoList.cend(); |
| |
|
| | updateUndoState(); |
| |
|
| | RS_DEBUG->print("RS_Undo::addUndoCycle: ok"); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | void RS_Undo::startUndoCycle(){ |
| | if (1 < ++refCount) { |
| | |
| | return; |
| | } |
| |
|
| | |
| | |
| | |
| | if (undoList.cend() != m_redoPointer) { |
| | |
| | std::unordered_set<RS_Undoable*> keep; |
| | for (auto it = undoList.begin(); it != m_redoPointer; ++it) { |
| | for (RS_Undoable* undoable: (*it)->getUndoables()){ |
| | keep.insert(undoable); |
| | } |
| | } |
| |
|
| | |
| | std::unordered_set<RS_Undoable*> obsolete; |
| | for (auto it = m_redoPointer; it != undoList.end(); ++it) { |
| | for (RS_Undoable* undoable: (*it)->getUndoables()){ |
| | obsolete.insert(undoable); |
| | } |
| | } |
| |
|
| | |
| | for (RS_Undoable* undoable: obsolete) { |
| | if (keep.end() == keep.find(undoable)) { |
| | removeUndoable(undoable); |
| | } |
| | } |
| | |
| | undoList.erase(m_redoPointer, undoList.cend()); |
| | m_redoPointer = undoList.cend(); |
| | } |
| |
|
| | |
| | currentCycle = std::make_shared<RS_UndoCycle>(); |
| | } |
| |
|
| | |
| | |
| | |
| | void RS_Undo::addUndoable(RS_Undoable* u) { |
| | RS_DEBUG->print("RS_Undo::%s(): begin", __func__); |
| |
|
| | if( nullptr == currentCycle) { |
| | RS_DEBUG->print( RS_Debug::D_CRITICAL, "RS_Undo::%s(): invalid currentCycle, possibly missing startUndoCycle()", __func__); |
| | return; |
| | } |
| | currentCycle->addUndoable(u); |
| | RS_DEBUG->print("RS_Undo::%s(): end", __func__); |
| | } |
| |
|
| | |
| | |
| | |
| | void RS_Undo::endUndoCycle(){ |
| | if (0 < refCount) { |
| | |
| | if( 0 < --refCount) { |
| | |
| | return; |
| | } |
| | } |
| | else { |
| | RS_DEBUG->print(RS_Debug::D_WARNING, "Warning: RS_Undo::endUndoCycle() called without previous startUndoCycle() %d", refCount); |
| | return; |
| | } |
| |
|
| | if (hasUndoable()) { |
| | |
| | addUndoCycle(currentCycle); |
| | } |
| |
|
| | updateUndoState(); |
| | currentCycle.reset(); |
| | } |
| |
|
| | |
| | |
| | |
| | bool RS_Undo::undo() { |
| | RS_DEBUG->print("RS_Undo::undo"); |
| |
|
| | if (m_redoPointer == undoList.cbegin()) |
| | return false; |
| |
|
| | m_redoPointer = std::prev(m_redoPointer); |
| | std::shared_ptr<RS_UndoCycle> uc = *m_redoPointer; |
| |
|
| | updateUndoState(); |
| | uc->changeUndoState(); |
| | return true; |
| | } |
| |
|
| | |
| | |
| | |
| | bool RS_Undo::redo() { |
| | RS_DEBUG->print("RS_Undo::redo"); |
| |
|
| | if (m_redoPointer != undoList.cend()) { |
| |
|
| | std::shared_ptr<RS_UndoCycle> uc = *m_redoPointer; |
| | m_redoPointer = std::next(m_redoPointer); |
| |
|
| | updateUndoState(); |
| | uc->changeUndoState(); |
| | return true; |
| | } |
| | return false; |
| | } |
| |
|
| |
|
| |
|
| | |
| | |
| | |
| | |
| | void RS_Undo::updateUndoState() const{ |
| | bool redoAvailable = m_redoPointer != undoList.end(); |
| | bool undoAvailable = !undoList.empty() && m_redoPointer != undoList.cbegin(); |
| | fireUndoStateChanged(undoAvailable, redoAvailable); |
| | } |
| |
|
| | void RS_Undo::collectUndoState(bool& undoAvailable, bool& redoAvailable) const { |
| | redoAvailable = m_redoPointer != undoList.end(); |
| | undoAvailable = !undoList.empty() && m_redoPointer != undoList.cbegin(); |
| | } |
| |
|
| | |
| | |
| | |
| | std::ostream& operator << (std::ostream& os, RS_Undo& l) { |
| | os << "Undo List: " << "\n"; |
| | int position = std::distance(l.undoList.cbegin(), l.m_redoPointer); |
| | os << " Redo Pointer is at: " << position << "\n"; |
| |
|
| | for(auto it = l.undoList.cbegin(); it != l.undoList.cend(); ++it) { |
| | os << ((it != l.m_redoPointer) ? " " : " -->"); |
| | os << *it << "\n"; |
| | } |
| | return os; |
| | } |
| |
|
| | |
| | |
| | |
| | #ifdef RS_TEST |
| | bool RS_Undo::test() { |
| |
|
| | int i, k; |
| | RS_UndoStub undo; |
| | |
| | RS_Undoable* u1; |
| |
|
| | std::cout << "Testing RS_Undo\n"; |
| |
|
| | std::cout << " Adding 500 cycles.."; |
| | |
| | for (i=1; i<=500; ++i) { |
| | |
| | undo.startUndoCycle(); |
| | for (k=1; k<=i; ++k) { |
| | u1 = new RS_Undoable(); |
| | |
| | undo.addUndoable(u1); |
| | } |
| | |
| | undo.endUndoCycle(); |
| | } |
| | std::cout << "OK\n"; |
| |
|
| | assert(undo.countUndoCycles()==500); |
| | assert(undo.countRedoCycles()==0); |
| |
|
| | std::cout << " Undo 500 cycles.."; |
| | |
| | for (i=1; i<=500; ++i) { |
| | undo.undo(); |
| | } |
| | std::cout << "OK\n"; |
| |
|
| | assert(undo.countUndoCycles()==0); |
| | assert(undo.countRedoCycles()==500); |
| |
|
| | std::cout << " Redo 500 cycles.."; |
| | |
| | for (i=1; i<=500; ++i) { |
| | undo.redo(); |
| | } |
| | std::cout << "OK\n"; |
| |
|
| | assert(undo.countUndoCycles()==500); |
| | assert(undo.countRedoCycles()==0); |
| |
|
| | std::cout << " Undo 250 cycles.."; |
| | |
| | for (i=1; i<=250; ++i) { |
| | undo.undo(); |
| | } |
| | std::cout << "OK\n"; |
| |
|
| | assert(undo.countUndoCycles()==250); |
| | assert(undo.countRedoCycles()==250); |
| |
|
| | std::cout << " Adding 10 cycles.."; |
| | for (i=1; i<=10; ++i) { |
| | |
| | undo.startUndoCycle(); |
| | for (k=1; k<=10; ++k) { |
| | u1 = new RS_Undoable(); |
| | |
| | undo.addUndoable(u1); |
| | } |
| | |
| | undo.endUndoCycle(); |
| | } |
| | std::cout << "OK\n"; |
| |
|
| | assert(undo.countUndoCycles()==260); |
| | assert(undo.countRedoCycles()==0); |
| |
|
| | std::cout << " Undo 5 cycles.."; |
| | for (i=1; i<=5; ++i) { |
| | undo.undo(); |
| | } |
| | std::cout << "OK\n"; |
| |
|
| | assert(undo.countUndoCycles()==255); |
| | assert(undo.countRedoCycles()==5); |
| |
|
| | std::cout << " Redo 5 cycles.."; |
| | for (i=1; i<=5; ++i) { |
| | undo.redo(); |
| | } |
| | std::cout << "OK\n"; |
| |
|
| | assert(undo.countUndoCycles()==260); |
| | assert(undo.countRedoCycles()==0); |
| |
|
| | std::cout << " Undo 15 cycles.."; |
| | for (i=1; i<=15; ++i) { |
| | undo.undo(); |
| | } |
| | std::cout << "OK\n"; |
| |
|
| | assert(undo.countUndoCycles()==245); |
| | assert(undo.countRedoCycles()==15); |
| |
|
| | std::cout << " Adding 1 cycle.."; |
| | for (i=1; i<=1; ++i) { |
| | |
| | undo.startUndoCycle(); |
| | for (k=1; k<=10; ++k) { |
| | u1 = new RS_Undoable(); |
| | |
| | undo.addUndoable(u1); |
| | } |
| | |
| | undo.endUndoCycle(); |
| | } |
| | std::cout << "OK\n"; |
| |
|
| | assert(undo.countUndoCycles()==246); |
| | assert(undo.countRedoCycles()==0); |
| |
|
| | return true; |
| |
|
| | } |
| | #endif |
| |
|