/**************************************************************************** ** ** This file is part of the LibreCAD project, a 2D CAD program ** ** Copyright (C) 2018 A. Stebich (librecad@mail.lordofbikes.de) ** Copyright (C) 2010 R. van Twisk (librecad@rvt.dds.nl) ** Copyright (C) 2001-2003 RibbonSoft. All rights reserved. ** ** ** This file may be distributed and/or modified under the terms of the ** GNU General Public License version 2 as published by the Free Software ** Foundation and appearing in the file gpl-2.0.txt included in the ** packaging of this file. ** ** This program 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 General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ** ** This copyright notice MUST APPEAR in all copies of the script! ** **********************************************************************/ #include #include "qc_applicationwindow.h" #include "rs_undocycle.h" #include "rs_undo.h" #include "rs_debug.h" /** * @return Number of Cycles that can be undone. */ int RS_Undo::countUndoCycles() { RS_DEBUG->print("RS_Undo::countUndoCycles"); return undoPointer+1; } /** * @return Number of Cycles that can be redone. */ int RS_Undo::countRedoCycles() { RS_DEBUG->print("RS_Undo::countRedoCycles"); return undoList.size()-1-undoPointer; } /** * @return true, when current undo cycle has at least one undoable */ bool RS_Undo::hasUndoable() { if (nullptr != currentCycle && 0 < currentCycle->size()) { return true; } return false; } /** * Adds an Undo Cycle at the current position in the list. * All Cycles after the new one are removed and the Undoabels * on them deleted. */ void RS_Undo::addUndoCycle(std::shared_ptr const& i) { RS_DEBUG->print("RS_Undo::addUndoCycle"); // undoList.insert(++undoPointer, i); undoList.insert(undoList.begin() + (++undoPointer), i); RS_DEBUG->print("RS_Undo::addUndoCycle: ok"); } /** * Starts a new cycle for one undo step. Every undoable that is * added after calling this method goes into this cycle. */ void RS_Undo::startUndoCycle() { if (1 < ++refCount) { // only the first fresh top call starts a new cycle return; } size_t removePointer {static_cast(undoPointer + 1)}; // if there are undo cycles behind undoPointer // remove obsolete entities and undoCycles if (undoList.size() > removePointer) { // collect remaining undoables std::list keep; for (auto it = undoList.begin(); it != undoList.begin() + removePointer; ++it) { for (auto u: (*it)->getUndoables()){ keep.push_back( u); } } keep.unique(); // collect obsolete undoables std::list obsolete; for (auto it = undoList.begin() + removePointer; it != undoList.end(); ++it) { for (auto u: (*it)->getUndoables()){ obsolete.push_back( u); } } // unique() only works correct on sorted list! obsolete.sort(); obsolete.unique(); // delete obsolete undoables which are not in keep list for (auto it = obsolete.begin(); it != obsolete.end(); ++it) { if (keep.end() == std::find( keep.begin(), keep.end(), *it)) { removeUndoable( *it); } } // clean up obsolete undoCycles while (undoList.size() > removePointer) { undoList.pop_back(); } } // alloc new undoCycle currentCycle = std::make_shared(); } /** * Adds an undoable to the current undo cycle. */ 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__); } /** * Ends the current undo cycle. */ void RS_Undo::endUndoCycle() { if (0 < refCount) { // compensate nested calls of start-/endUndoCycle() if( 0 < --refCount) { // not the final nested call, nothing to do yet return; } } else { RS_DEBUG->print( RS_Debug::D_WARNING, "Warning: RS_Undo::endUndoCycle() called without previous startUndoCycle() %d", refCount); return; } if (hasUndoable()) { // only keep the undoCycle, when it contains undoables addUndoCycle(currentCycle); } setGUIButtons(); currentCycle = nullptr; // invalidate currentCycle for next startUndoCycle() } /** * Undoes the last undo cycle. */ bool RS_Undo::undo() { RS_DEBUG->print("RS_Undo::undo"); if (undoPointer < 0) return false; std::shared_ptr uc = undoList[undoPointer--]; setGUIButtons(); uc->changeUndoState(); return true; } /** * Redoes the undo cycle which was at last undone. */ bool RS_Undo::redo() { RS_DEBUG->print("RS_Undo::redo"); if (undoPointer+1 < int(undoList.size())) { std::shared_ptr uc = undoList[++undoPointer]; setGUIButtons(); uc->changeUndoState(); return true; } return false; } /** * @return The undo item that is next if we're about to undo * or nullptr. */ /** std::shared_ptr RS_Undo::getUndoCycle() { std::shared_ptr ret; RS_DEBUG->print("RS_Undo::getUndoCycle"); if ((undoPointer>=0) && (undoPointer < int(undoList.size()))) { ret = undoList.at(undoPointer); } RS_DEBUG->print("RS_Undo::getUndoCycle: OK"); return ret; } */ /** * @return The redo item that is next if we're about to redo * or nullptr. */ /** std::shared_ptr RS_Undo::getRedoCycle() { RS_DEBUG->print("RS_Undo::getRedoCycle"); if ((undoPointer+1>=0) && (undoPointer+1 < int(undoList.size()))) { return undoList.at(undoPointer+1); } return std::shared_ptr(); } */ /** * enable/disable redo/undo buttons in main application window * Author: Dongxu Li **/ void RS_Undo::setGUIButtons() const { auto appWin = QC_ApplicationWindow::getAppWindow(); if (!appWin) return; appWin->setRedoEnable(undoList.size() > 0 && undoPointer+1 < int(undoList.size())); appWin->setUndoEnable(undoList.size() > 0 && undoPointer >= 0); } /** * Dumps the undo list to stdout. */ std::ostream& operator << (std::ostream& os, RS_Undo& l) { os << "Undo List: " << "\n"; os << " Pointer is at: " << l.undoPointer << "\n"; for (int i = 0; i < int(l.undoList.size()); ++i) { if (i==l.undoPointer) os << " -->"; else os << " "; os << *(l.undoList.at(i)) << "\n"; } return os; } /** * Testing Undoables, Undo Cycles and the Undo container. */ #ifdef RS_TEST bool RS_Undo::test() { int i, k; RS_UndoStub undo; //RS_UndoCycle* c1; RS_Undoable* u1; std::cout << "Testing RS_Undo\n"; std::cout << " Adding 500 cycles.."; // Add 500 Undo Cycles with i Undoables in every Cycle for (i=1; i<=500; ++i) { //c1 = new RS_UndoCycle(); undo.startUndoCycle(); for (k=1; k<=i; ++k) { u1 = new RS_Undoable(); //c1-> undo.addUndoable(u1); } //undo.addUndoCycle(c1); undo.endUndoCycle(); } std::cout << "OK\n"; assert(undo.countUndoCycles()==500); assert(undo.countRedoCycles()==0); std::cout << " Undo 500 cycles.."; // Undo all 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.."; // Redo all 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.."; // Undo all 500 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) { //c1 = new RS_UndoCycle(); undo.startUndoCycle(); for (k=1; k<=10; ++k) { u1 = new RS_Undoable(); //c1->addUndoable(u1); undo.addUndoable(u1); } //undo.addUndoCycle(c1); 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) { //c1 = new RS_UndoCycle(); undo.startUndoCycle(); for (k=1; k<=10; ++k) { u1 = new RS_Undoable(); //c1->addUndoable(u1); undo.addUndoable(u1); } //undo.addUndoCycle(c1); undo.endUndoCycle(); } std::cout << "OK\n"; assert(undo.countUndoCycles()==246); assert(undo.countRedoCycles()==0); return true; } #endif