#include "tundo.h" #include //----------------------------------------------------------------------------- namespace { void deleteUndo(const TUndo *undo) { delete undo; } void callUndo(const TUndo *undo) { undo->undo(); } void callRedo(const TUndo *undo) { undo->redo(); } // void callRepeat(const TUndo* undo) {undo->repeat(); } class TUndoBlock final : public TUndo { std::vector m_undos; typedef std::vector::const_iterator Iterator; typedef std::vector::const_reverse_iterator ReverseIterator; mutable bool m_deleted, m_undoing; public: TUndoBlock() : m_deleted(false), m_undoing(false) {} ~TUndoBlock() { assert(m_undoing == false); assert(m_deleted == false); m_deleted = true; std::for_each(m_undos.begin(), m_undos.end(), deleteUndo); m_undos.clear(); } int getSize() const override { int size = sizeof(*this); for (Iterator cit = m_undos.begin(); cit != m_undos.end(); ++cit) size += (*cit)->getSize(); size += (m_undos.capacity() - m_undos.size()) * sizeof(TUndo *); return size; } int getUndoCount() const { return (int)m_undos.size(); } void setLast() { for (UINT i = 0; i < m_undos.size(); i++) { m_undos[i]->m_isLastInBlock = (i == 0); m_undos[i]->m_isLastInRedoBlock = (i == m_undos.size() - 1); } } void undo() const override { assert(!m_deleted); assert(!m_undoing); m_undoing = true; // VERSIONE CORRETTA std::for_each(m_undos.rbegin(), m_undos.rend(), callUndo); // VERSIONE SBAGLIATA // for_each(m_undos.begin(), m_undos.end(), callUndo); m_undoing = false; } void redo() const override { assert(!m_deleted); // VERSIONE CORRETTA std::for_each(m_undos.begin(), m_undos.end(), callRedo); // VERSIONE SBAGLIATA // for_each(m_undos.rbegin(), m_undos.rend(), callRedo); } // void repeat() const { // for_each(m_undos.begin(), m_undos.end(), callRepeat); //} void onAdd() override {} void add(TUndo *undo) { undo->m_isLastInBlock = true; undo->m_isLastInRedoBlock = true; m_undos.push_back(undo); } void popUndo(int n) { if (n == -1) n = m_undos.size(); if (m_undos.empty() || n <= 0) return; while (n > 0 && !m_undos.empty()) { TUndo *undo = m_undos.back(); m_undos.pop_back(); delete undo; n--; } } QString getHistoryString() override { if (m_undos.empty()) return TUndo::getHistoryString(); else if ((int)m_undos.size() == 1) return m_undos.back()->getHistoryString(); else { return QString("%1 etc..").arg(m_undos.back()->getHistoryString()); } } int getHistoryType() override { if (m_undos.empty()) return TUndo::getHistoryType(); else return m_undos.back()->getHistoryType(); } }; } // namespace typedef std::deque UndoList; typedef UndoList::iterator UndoListIterator; typedef UndoList::const_iterator UndoListConstIterator; //----------------------------------------------------------------------------- struct TUndoManager::TUndoManagerImp { UndoList m_undoList; UndoListIterator m_current; bool m_skipped; int m_undoMemorySize; // in bytes std::vector m_blockStack; public: TUndoManagerImp() : m_skipped(false), m_undoMemorySize(0) { m_current = m_undoList.end(); } ~TUndoManagerImp() {} void add(TUndo *undo); public: static struct ManagerPtr { TUndoManager *m_ptr; public: ManagerPtr() : m_ptr(0) {} ~ManagerPtr() { if (m_ptr) delete m_ptr; m_ptr = 0; } } theManager; private: void doAdd(TUndo *undo); }; //============================================================================= TUndoManager::TUndoManagerImp::ManagerPtr TUndoManager::TUndoManagerImp::theManager; //----------------------------------------------------------------------------- TUndoManager *TUndoManager::manager() { if (!TUndoManagerImp::theManager.m_ptr) TUndoManagerImp::theManager.m_ptr = new TUndoManager; return TUndoManagerImp::theManager.m_ptr; } //============================================================================= TUndoManager::TUndoManager() : m_imp(new TUndoManagerImp) {} //----------------------------------------------------------------------------- TUndoManager::~TUndoManager() { // cout << "Distrutto undo manager" << endl; assert(m_imp->m_blockStack.empty()); reset(); } //----------------------------------------------------------------------------- void TUndoManager::TUndoManagerImp::add(TUndo *undo) { assert(undo); if (!m_blockStack.empty()) { assert(m_current == m_undoList.end()); m_blockStack.back()->add(undo); } else doAdd(undo); } //----------------------------------------------------------------------------- void TUndoManager::TUndoManagerImp::doAdd(TUndo *undo) { if (m_current != m_undoList.end()) { std::for_each(m_current, m_undoList.end(), deleteUndo); m_undoList.erase(m_current, m_undoList.end()); } int i, memorySize = 0, count = m_undoList.size(); for (i = 0; i < count; i++) memorySize += m_undoList[i]->getSize(); while (count > 100 || (count != 0 && memorySize + undo->getSize() > m_undoMemorySize)) // 20MB { --count; TUndo *undo = m_undoList.front(); m_undoList.pop_front(); memorySize -= undo->getSize(); delete undo; } undo->m_isLastInBlock = true; undo->m_isLastInRedoBlock = true; m_undoList.push_back(undo); m_current = m_undoList.end(); } //----------------------------------------------------------------------------- void TUndoManager::beginBlock() { if (m_imp->m_current != m_imp->m_undoList.end()) { std::for_each(m_imp->m_current, m_imp->m_undoList.end(), deleteUndo); m_imp->m_undoList.erase(m_imp->m_current, m_imp->m_undoList.end()); } TUndoBlock *undoBlock = new TUndoBlock; m_imp->m_blockStack.push_back(undoBlock); m_imp->m_current = m_imp->m_undoList.end(); } //----------------------------------------------------------------------------- void TUndoManager::endBlock() { // vogliamo fare anche resize del vector ??? assert(m_imp->m_blockStack.empty() == false); TUndoBlock *undoBlock = m_imp->m_blockStack.back(); m_imp->m_blockStack.pop_back(); if (undoBlock->getUndoCount() > 0) { undoBlock->setLast(); m_imp->add(undoBlock); emit historyChanged(); } else { delete undoBlock; m_imp->m_current = m_imp->m_undoList.end(); } } //----------------------------------------------------------------------------- bool TUndoManager::undo() { assert(m_imp->m_blockStack.empty()); UndoListIterator &it = m_imp->m_current; if (it != m_imp->m_undoList.begin()) { m_imp->m_skipped = false; --it; (*it)->undo(); emit historyChanged(); if (m_imp->m_skipped) { m_imp->m_skipped = false; return undo(); } return true; } else return false; } //----------------------------------------------------------------------------- bool TUndoManager::redo() { assert(m_imp->m_blockStack.empty()); UndoListIterator &it = m_imp->m_current; if (it != m_imp->m_undoList.end()) { m_imp->m_skipped = false; (*it)->redo(); ++it; emit historyChanged(); if (m_imp->m_skipped) { m_imp->m_skipped = false; return redo(); } return true; } else return false; } //----------------------------------------------------------------------------- // repeat e' come redo ma non sposta il puntatore al corrente /* void TUndoManager::repeat() { assert(m_imp->m_blockStack.empty()); UndoListIterator &it = m_imp->m_current; if (it != m_imp->m_undoList.end()) { (*it)->repeat(); } } */ //----------------------------------------------------------------------------- void TUndoManager::skip() { m_imp->m_skipped = true; } //----------------------------------------------------------------------------- void TUndoManager::setUndoMemorySize(int memorySyze) { m_imp->m_undoMemorySize = 1048576 * memorySyze; } //----------------------------------------------------------------------------- void TUndoManager::add(TUndo *undo) { assert(undo); if (!undo) return; m_imp->add(undo); Q_EMIT historyChanged(); undo->onAdd(); Q_EMIT somethingChanged(); } //----------------------------------------------------------------------------- void TUndoManager::reset() { assert(m_imp->m_blockStack.empty()); m_imp->m_blockStack.clear(); UndoList &lst = m_imp->m_undoList; std::for_each(lst.begin(), lst.end(), deleteUndo); lst.clear(); m_imp->m_current = m_imp->m_undoList.end(); Q_EMIT historyChanged(); } //----------------------------------------------------------------------------- void TUndoManager::popUndo(int n, bool forward) { if (!forward) { if (m_imp->m_blockStack.empty()) { if (n == -1) { UndoListIterator start = m_imp->m_undoList.begin(); n = 0; while (start != m_imp->m_current) ++n; } if (m_imp->m_undoList.empty() || n <= 0) return; if (m_imp->m_current == m_imp->m_undoList.end()) { int i; for (i = 0; i < n && m_imp->m_current != m_imp->m_undoList.begin(); i++) { m_imp->m_current--; delete (*m_imp->m_current); m_imp->m_undoList.erase(m_imp->m_current); m_imp->m_current = m_imp->m_undoList.end(); } } else { TUndo *undo = *m_imp->m_current; UndoListIterator start, end = m_imp->m_current; if (end == m_imp->m_undoList.begin()) return; int i; for (i = 0; i < n && m_imp->m_current != m_imp->m_undoList.begin(); i++) m_imp->m_current--; start = m_imp->m_current; UndoListIterator _end = end; while (_end != start) { _end--; delete (*_end); } m_imp->m_undoList.erase(start, end); m_imp->m_current = m_imp->m_undoList.begin(); while (*m_imp->m_current != undo) m_imp->m_current++; } } else m_imp->m_blockStack.back()->popUndo(n); return; } if (m_imp->m_current == m_imp->m_undoList.end()) return; if (m_imp->m_blockStack.empty()) { UndoListIterator end, start = m_imp->m_current++; if (n == -1) end = m_imp->m_undoList.end(); else { UndoListIterator it = start; end = it; int i = 0; while (i != n && end != m_imp->m_undoList.end()) { ++end; i++; } } std::for_each(start, end, deleteUndo); m_imp->m_undoList.erase(start, end); m_imp->m_current = m_imp->m_undoList.end(); } else m_imp->m_blockStack.back()->popUndo(n); } //----------------------------------------------------------------------------- int TUndoManager::getHistoryCount() { return (int)m_imp->m_undoList.size(); } //----------------------------------------------------------------------------- int TUndoManager::getCurrentHistoryIndex() { int index = 0; UndoListIterator it = m_imp->m_undoList.begin(); while (1) { if (it == m_imp->m_current) return index; if (it == m_imp->m_undoList.end()) break; index++; it++; } return 0; } //----------------------------------------------------------------------------- TUndo *TUndoManager::getUndoItem(int index) { if (index > (int)m_imp->m_undoList.size() || index <= 0) return 0; return m_imp->m_undoList.at(index - 1); }