| |
| |
| #include "tundo.h" |
| #include <deque> |
| |
| |
| |
| using std::for_each; |
| |
| namespace { |
| |
| void deleteUndo(const TUndo *undo) { delete undo; } |
| void callUndo(const TUndo *undo) { undo->undo(); } |
| void callRedo(const TUndo *undo) { undo->redo(); } |
| |
| |
| class TUndoBlock final : public TUndo { |
| std::vector<TUndo *> m_undos; |
| typedef std::vector<TUndo *>::const_iterator Iterator; |
| typedef std::vector<TUndo *>::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; |
| 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 = 1; i < m_undos.size(); i++) |
| m_undos[i]->m_isLastInBlock = false; |
| m_undos[0]->m_isLastInBlock = true; |
| } |
| |
| void undo() const override { |
| assert(!m_deleted); |
| assert(!m_undoing); |
| m_undoing = true; |
| |
| for_each(m_undos.rbegin(), m_undos.rend(), callUndo); |
| |
| |
| m_undoing = false; |
| } |
| void redo() const override { |
| assert(!m_deleted); |
| |
| for_each(m_undos.begin(), m_undos.end(), callRedo); |
| |
| |
| } |
| |
| |
| |
| |
| void onAdd() override {} |
| void add(TUndo *undo) { |
| undo->m_isLastInBlock = 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(); |
| } |
| }; |
| } |
| |
| typedef std::deque<TUndo *> 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; |
| |
| std::vector<TUndoBlock *> 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() { |
| |
| 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()) { |
| 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)) |
| { |
| --count; |
| TUndo *undo = m_undoList.front(); |
| m_undoList.pop_front(); |
| memorySize -= undo->getSize(); |
| delete undo; |
| } |
| |
| undo->m_isLastInBlock = true; |
| m_undoList.push_back(undo); |
| m_current = m_undoList.end(); |
| } |
| |
| |
| |
| void TUndoManager::beginBlock() { |
| if (m_imp->m_current != m_imp->m_undoList.end()) { |
| 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() { |
| |
| 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; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| 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++; |
| } |
| } |
| 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); |
| } |
| |