diff --git a/Modules/Core/include/mitkLimitedLinearUndo.h b/Modules/Core/include/mitkLimitedLinearUndo.h index 3cf6f15c40..7629fffef1 100644 --- a/Modules/Core/include/mitkLimitedLinearUndo.h +++ b/Modules/Core/include/mitkLimitedLinearUndo.h @@ -1,142 +1,162 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef LIMITEDLINEARUNDO_H_HEADER_INCLUDED_C16E9C96 #define LIMITEDLINEARUNDO_H_HEADER_INCLUDED_C16E9C96 // MITK header #include "mitkOperationEvent.h" #include "mitkUndoModel.h" #include // STL header #include // ITK header #pragma GCC visibility push(default) #include #pragma GCC visibility pop +#include + namespace mitk { //##Documentation //## @brief A linear undo model with one undo and one redo stack. //## //## Derived from UndoModel AND itk::Object. Invokes ITK-events to signal listening //## GUI elements, whether each of the stacks is empty or not (to enable/disable button, ...) class MITKCORE_EXPORT LimitedLinearUndo : public UndoModel { public: - typedef std::vector UndoContainer; - typedef std::vector::reverse_iterator UndoContainerRevIter; + typedef std::deque UndoContainer; + typedef std::deque::reverse_iterator UndoContainerRevIter; mitkClassMacro(LimitedLinearUndo, UndoModel); itkFactorylessNewMacro(Self) itkCloneMacro(Self) bool SetOperationEvent(UndoStackItem *stackItem) override; //##Documentation //## @brief Undoes the last changes //## //## Reads the top element of the Undo-Stack, //## executes the operation, //## swaps the OperationEvent-Undo with the Operation //## and sets it to Redo-Stack bool Undo() override; bool Undo(bool) override; //##Documentation //## @brief Undoes all changes until ObjectEventID oeid virtual bool Undo(int oeid); //##Documentation //## @brief Undoes the last changes //## //## Reads the top element of the Redo-Stack, //## executes the operation, //## swaps the OperationEvent-Operation with the Undo-Operation //## and sets it to Undo-Stack bool Redo() override; bool Redo(bool) override; //##Documentation //## @brief Redoes all changes until ObjectEventID oeid virtual bool Redo(int oeid); //##Documentation //## @brief Clears UndoList and RedoList void Clear() override; //##Documentation //## @brief Clears the RedoList void ClearRedoList() override; //##Documentation //## @brief True, if RedoList is empty bool RedoListEmpty() override; + //##Documentation + //## @brief Gets the limit on the size of the undo history. + //## The undo limit determines how many items can be stored + //## in the undo stack. If the value is 0 that means that + //## there is no limit. + std::size_t GetUndoLimit() const override; + + //##Documentation + //## @brief Sets a limit on the size of the undo history. + //## If the limit is reached, the oldest undo items will + //## be dropped from the bottom of the undo stack. + //## The 0 value means that there is no limit. + //## @param limit the maximum number of items on the stack + void SetUndoLimit(std::size_t limit) override; + //##Documentation //## @brief Returns the ObjectEventId of the //## top element in the OperationHistory int GetLastObjectEventIdInList() override; //##Documentation //## @brief Returns the GroupEventId of the //## top element in the OperationHistory int GetLastGroupEventIdInList() override; //##Documentation //## @brief Returns the last specified OperationEvent in Undo-list //## corresponding to the given values; if nothing found, then returns nullptr OperationEvent *GetLastOfType(OperationActor *destination, OperationType opType) override; protected: //##Documentation //## Constructor LimitedLinearUndo(); //##Documentation //## Destructor ~LimitedLinearUndo() override; //## @brief Convenience method to free the memory of //## elements in the list and to clear the list void ClearList(UndoContainer *list); UndoContainer m_UndoList; UndoContainer m_RedoList; private: int FirstObjectEventIdOfCurrentGroup(UndoContainer &stack); + + std::size_t m_UndoLimit; + }; #pragma GCC visibility push(default) /// Some itk events to notify listening GUI elements, when the undo or redo stack is empty (diable undo button) /// or when there are items in the stack (enable button) itkEventMacro(UndoStackEvent, itk::ModifiedEvent); itkEventMacro(UndoEmptyEvent, UndoStackEvent); itkEventMacro(RedoEmptyEvent, UndoStackEvent); itkEventMacro(UndoNotEmptyEvent, UndoStackEvent); itkEventMacro(RedoNotEmptyEvent, UndoStackEvent); /// Additional unused events, if anybody wants to put an artificial limit to the possible number of items in the stack itkEventMacro(UndoFullEvent, UndoStackEvent); itkEventMacro(RedoFullEvent, UndoStackEvent); #pragma GCC visibility pop } // namespace mitk #endif /* LIMITEDLINEARUNDO_H_HEADER_INCLUDED_C16E9C96 */ diff --git a/Modules/Core/include/mitkUndoModel.h b/Modules/Core/include/mitkUndoModel.h index 64256c1301..e90052ea28 100644 --- a/Modules/Core/include/mitkUndoModel.h +++ b/Modules/Core/include/mitkUndoModel.h @@ -1,88 +1,103 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef UNDOMODEL_H_HEADER_INCLUDED_C16ED098 #define UNDOMODEL_H_HEADER_INCLUDED_C16ED098 #include "mitkCommon.h" #include "mitkOperation.h" #include #include namespace mitk { class UndoStackItem; class OperationEvent; class OperationActor; //##Documentation //## @brief superclass for all UndoModels //## //## all necessary operations, that all UndoModels share. //## @ingroup Undo class MITKCORE_EXPORT UndoModel : public itk::Object { public: mitkClassMacroItkParent(UndoModel, itk::Object); // no New Macro because this is an abstract class! virtual bool SetOperationEvent(UndoStackItem *stackItem) = 0; virtual bool Undo() = 0; virtual bool Undo(bool fine) = 0; virtual bool Redo() = 0; virtual bool Redo(bool fine) = 0; //##Documentation //## @brief clears undo and Redolist virtual void Clear() = 0; //##Documentation //## @brief clears the RedoList virtual void ClearRedoList() = 0; //##Documentation //## @brief true if RedoList is empty virtual bool RedoListEmpty() = 0; + //##Documentation + //## @brief Gets the limit on the size of the undo history. + //## The undo limit determines how many items can be stored + //## in the undo stack. If the value is 0 that means that + //## there is no limit. + virtual std::size_t GetUndoLimit() const = 0; + + //##Documentation + //## @brief Sets a limit on the size of the undo history. + //## If the limit is reached, the oldest undo items will + //## be dropped from the bottom of the undo stack. + //## The 0 value means that there is no limit. + //## @param limit the maximum number of items on the stack + virtual void SetUndoLimit(std::size_t limit) = 0; + //##Documentation //## @brief returns the ObjectEventId of the //## top Element in the OperationHistory of the selected //## UndoModel virtual int GetLastObjectEventIdInList() = 0; //##Documentation //## @brief returns the GroupEventId of the //## top Element in the OperationHistory of the selected //## UndoModel virtual int GetLastGroupEventIdInList() = 0; //##Documentation //## @brief returns the last specified OperationEvent in Undo-list //## corresponding to the given values; if nothing found, then returns nullptr //## //## needed to get the old Position of an Element for declaring an UndoOperation virtual OperationEvent *GetLastOfType(OperationActor *destination, OperationType opType) = 0; protected: UndoModel(){}; ~UndoModel() override{}; }; } // namespace mitk #endif /* UNDOMODEL_H_HEADER_INCLUDED_C16ED098 */ diff --git a/Modules/Core/src/Controllers/mitkLimitedLinearUndo.cpp b/Modules/Core/src/Controllers/mitkLimitedLinearUndo.cpp index e8909d7d67..0c97653d03 100644 --- a/Modules/Core/src/Controllers/mitkLimitedLinearUndo.cpp +++ b/Modules/Core/src/Controllers/mitkLimitedLinearUndo.cpp @@ -1,216 +1,238 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkLimitedLinearUndo.h" #include mitk::LimitedLinearUndo::LimitedLinearUndo() +: m_UndoLimit(0) { // nothing to do } mitk::LimitedLinearUndo::~LimitedLinearUndo() { // delete undo and redo list this->ClearList(&m_UndoList); this->ClearList(&m_RedoList); } void mitk::LimitedLinearUndo::ClearList(UndoContainer *list) { while (!list->empty()) { UndoStackItem *item = list->back(); list->pop_back(); delete item; } } bool mitk::LimitedLinearUndo::SetOperationEvent(UndoStackItem *stackItem) { auto *operationEvent = dynamic_cast(stackItem); if (!operationEvent) return false; // clear the redolist, if a new operation is saved if (!m_RedoList.empty()) { this->ClearList(&m_RedoList); InvokeEvent(RedoEmptyEvent()); } + if (m_UndoLimit > 0 && m_UndoList.size() == m_UndoLimit) + { + m_UndoList.pop_front(); + } m_UndoList.push_back(operationEvent); InvokeEvent(UndoNotEmptyEvent()); return true; } bool mitk::LimitedLinearUndo::Undo(bool fine) { if (fine) { // undo one object event ID return Undo(); } else { // undo one group event ID int oeid = FirstObjectEventIdOfCurrentGroup( m_UndoList); // get the Object Event ID of the first item with a differnt Group ID (as seen from the end of stack) return Undo(oeid); } } bool mitk::LimitedLinearUndo::Undo() { if (m_UndoList.empty()) return false; int undoObjectEventId = m_UndoList.back()->GetObjectEventId(); return Undo(undoObjectEventId); } bool mitk::LimitedLinearUndo::Undo(int oeid) { if (m_UndoList.empty()) return false; bool rc = true; do { m_UndoList.back()->ReverseAndExecute(); m_RedoList.push_back(m_UndoList.back()); // move to redo stack m_UndoList.pop_back(); InvokeEvent(RedoNotEmptyEvent()); if (m_UndoList.empty()) { InvokeEvent(UndoEmptyEvent()); rc = false; break; } } while (m_UndoList.back()->GetObjectEventId() >= oeid); // Update. Check Rendering Mechanism where to request updates mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return rc; } bool mitk::LimitedLinearUndo::Redo(bool) { return Redo(); } bool mitk::LimitedLinearUndo::Redo() { if (m_RedoList.empty()) return false; int redoObjectEventId = m_RedoList.back()->GetObjectEventId(); return Redo(redoObjectEventId); } bool mitk::LimitedLinearUndo::Redo(int oeid) { if (m_RedoList.empty()) return false; do { m_RedoList.back()->ReverseAndExecute(); m_UndoList.push_back(m_RedoList.back()); m_RedoList.pop_back(); InvokeEvent(UndoNotEmptyEvent()); if (m_RedoList.empty()) { InvokeEvent(RedoEmptyEvent()); break; } } while (m_RedoList.back()->GetObjectEventId() <= oeid); // Update. This should belong into the ExecuteOperation() of OperationActors, but it seems not to be used everywhere mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return true; } void mitk::LimitedLinearUndo::Clear() { this->ClearList(&m_UndoList); InvokeEvent(UndoEmptyEvent()); this->ClearList(&m_RedoList); InvokeEvent(RedoEmptyEvent()); } void mitk::LimitedLinearUndo::ClearRedoList() { this->ClearList(&m_RedoList); InvokeEvent(RedoEmptyEvent()); } bool mitk::LimitedLinearUndo::RedoListEmpty() { return m_RedoList.empty(); } +std::size_t mitk::LimitedLinearUndo::GetUndoLimit() const +{ + return m_UndoLimit; +} + +void mitk::LimitedLinearUndo::SetUndoLimit(std::size_t undoLimit) +{ + if (undoLimit != m_UndoLimit) + { + if (m_UndoList.size() > undoLimit) + { + m_UndoList.erase(m_UndoList.begin(), m_UndoList.end() - undoLimit); + } + m_UndoLimit = undoLimit; + } +} + int mitk::LimitedLinearUndo::GetLastObjectEventIdInList() { return m_UndoList.back()->GetObjectEventId(); } int mitk::LimitedLinearUndo::GetLastGroupEventIdInList() { return m_UndoList.back()->GetGroupEventId(); } mitk::OperationEvent *mitk::LimitedLinearUndo::GetLastOfType(OperationActor *destination, OperationType opType) { // When/where is this function needed? In CoordinateSupplier... for (auto iter = m_UndoList.rbegin(); iter != m_UndoList.rend(); ++iter) { auto *opEvent = dynamic_cast(*iter); if (!opEvent) continue; if (opEvent->GetOperation() != nullptr && opEvent->GetOperation()->GetOperationType() == opType && opEvent->IsValid() && opEvent->GetDestination() == destination) return opEvent; } return nullptr; } int mitk::LimitedLinearUndo::FirstObjectEventIdOfCurrentGroup(mitk::LimitedLinearUndo::UndoContainer &stack) { int currentGroupEventId = stack.back()->GetGroupEventId(); int firstObjectEventId = -1; for (auto iter = stack.rbegin(); iter != stack.rend(); ++iter) { if ((*iter)->GetGroupEventId() == currentGroupEventId) { firstObjectEventId = (*iter)->GetObjectEventId(); } else break; } return firstObjectEventId; } diff --git a/Modules/Core/src/Controllers/mitkVerboseLimitedLinearUndo.cpp b/Modules/Core/src/Controllers/mitkVerboseLimitedLinearUndo.cpp index ac8817ec37..aaf4663a13 100644 --- a/Modules/Core/src/Controllers/mitkVerboseLimitedLinearUndo.cpp +++ b/Modules/Core/src/Controllers/mitkVerboseLimitedLinearUndo.cpp @@ -1,181 +1,186 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkVerboseLimitedLinearUndo.h" #include "mitkOperationEvent.h" mitk::VerboseLimitedLinearUndo::VerboseLimitedLinearUndo() { } mitk::VerboseLimitedLinearUndo::~VerboseLimitedLinearUndo() { } bool mitk::VerboseLimitedLinearUndo::SetOperationEvent(UndoStackItem *undoStackItem) { if (!undoStackItem) return false; // clear the redolist, if a new operation is saved if (!m_RedoList.empty()) { this->ClearList(&m_RedoList); InvokeEvent(RedoEmptyEvent()); } + std::size_t undoLimit = this->GetUndoLimit(); + if (undoLimit > 0 && m_UndoList.size() == undoLimit) + { + m_UndoList.pop_front(); + } m_UndoList.push_back(undoStackItem); InvokeEvent(UndoNotEmptyEvent()); return true; } mitk::VerboseLimitedLinearUndo::StackDescription mitk::VerboseLimitedLinearUndo::GetUndoDescriptions() { mitk::VerboseLimitedLinearUndo::StackDescription descriptions; if (m_UndoList.empty()) return descriptions; int oeid = m_UndoList.back()->GetObjectEventId(); // ObjectEventID of current group std::string currentDescription; // description of current group int currentDescriptionCount(0); // counter, how many items of the current group gave descriptions bool niceDescriptionFound(false); // have we yet seen a plain descriptive entry (not OperationEvent)? std::string lastDescription; // stores the last description to inhibit entries like "name AND name AND name..." if // name is always the same for (auto iter = m_UndoList.rbegin(); iter != m_UndoList.rend(); ++iter) { if (oeid != (*iter)->GetObjectEventId()) { // current description complete, append to list if (currentDescription.empty()) currentDescription = "Some unnamed action"; // set a default description descriptions.push_back(StackDescriptionItem(oeid, currentDescription)); currentDescription = ""; // prepare for next group currentDescriptionCount = 0; niceDescriptionFound = false; oeid = (*iter)->GetObjectEventId(); } if (!(*iter)->GetDescription().empty()) // if there is a description { if (!dynamic_cast(*iter)) { // anything but an OperationEvent overrides the collected descriptions currentDescription = (*iter)->GetDescription(); niceDescriptionFound = true; } else if (!niceDescriptionFound) // mere descriptive items override OperationEvents' descriptions { if (currentDescriptionCount) // if we have already seen another description { if (lastDescription != (*iter)->GetDescription()) { // currentDescription += '\n'; // concatenate descriptions with newline currentDescription += " AND "; // this has to wait until the popup can process multiline items currentDescription += (*iter)->GetDescription(); } } else { currentDescription += (*iter)->GetDescription(); } } lastDescription = (*iter)->GetDescription(); ++currentDescriptionCount; } } // for // add last description to list if (currentDescription.empty()) currentDescription = "Some unnamed action"; descriptions.push_back(StackDescriptionItem(oeid, currentDescription)); return descriptions; // list ready } mitk::VerboseLimitedLinearUndo::StackDescription mitk::VerboseLimitedLinearUndo::GetRedoDescriptions() { mitk::VerboseLimitedLinearUndo::StackDescription descriptions; if (m_RedoList.empty()) return descriptions; int oeid = m_RedoList.back()->GetObjectEventId(); // ObjectEventID of current group std::string currentDescription; // description of current group int currentDescriptionCount(0); // counter, how many items of the current group gave descriptions bool niceDescriptionFound(false); // have we yet seen a plain descriptive entry (not OperationEvent)? std::string lastDescription; // stores the last description to inhibit entries like "name AND name AND name..." if // name is always the same for (auto iter = m_RedoList.rbegin(); iter != m_RedoList.rend(); ++iter) { if (oeid != (*iter)->GetObjectEventId()) { // current description complete, append to list if (currentDescription.empty()) currentDescription = "Some unnamed action"; // set a default description descriptions.push_back(StackDescriptionItem(oeid, currentDescription)); currentDescription = ""; // prepare for next group currentDescriptionCount = 0; niceDescriptionFound = false; oeid = (*iter)->GetObjectEventId(); } if (!(*iter)->GetDescription().empty()) // if there is a description { if (!dynamic_cast(*iter)) { // anything but an OperationEvent overrides the collected descriptions currentDescription = (*iter)->GetDescription(); niceDescriptionFound = true; } else if (!niceDescriptionFound) // mere descriptive items override OperationEvents' descriptions { if (currentDescriptionCount) // if we have already seen another description { if (lastDescription != (*iter)->GetDescription()) { // currentDescription += '\n'; // concatenate descriptions with newline currentDescription += " AND "; // this has to wait until the popup can process multiline items currentDescription += (*iter)->GetDescription(); } } else { currentDescription += (*iter)->GetDescription(); } } lastDescription = (*iter)->GetDescription(); ++currentDescriptionCount; } } // for // add last description to list if (currentDescription.empty()) currentDescription = "Some unnamed action"; descriptions.push_back(StackDescriptionItem(oeid, currentDescription)); return descriptions; // list ready }