From f430da1d45e8ca5538ea9871d1a3ed94a5b762ff Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sat, 30 Nov 2019 15:36:17 +0100 Subject: [PATCH] LibGUI: Add GUndoStack and GCommand classes This patch converts the undo stack from GTextDocument into GUndoStack, and GTextDocumentUndoCommand now inherits from GCommand. Let's turn this into a generic mechanism that can be used to implement undo/redo in any application. :^) --- Libraries/LibGUI/GCommand.cpp | 5 ++ Libraries/LibGUI/GCommand.h | 20 +++++++ Libraries/LibGUI/GTextDocument.cpp | 63 ++------------------- Libraries/LibGUI/GTextDocument.h | 19 ++----- Libraries/LibGUI/GUndoStack.cpp | 89 ++++++++++++++++++++++++++++++ Libraries/LibGUI/GUndoStack.h | 31 +++++++++++ Libraries/LibGUI/Makefile | 2 + 7 files changed, 156 insertions(+), 73 deletions(-) create mode 100644 Libraries/LibGUI/GCommand.cpp create mode 100644 Libraries/LibGUI/GCommand.h create mode 100644 Libraries/LibGUI/GUndoStack.cpp create mode 100644 Libraries/LibGUI/GUndoStack.h diff --git a/Libraries/LibGUI/GCommand.cpp b/Libraries/LibGUI/GCommand.cpp new file mode 100644 index 0000000000..b57cfe13a7 --- /dev/null +++ b/Libraries/LibGUI/GCommand.cpp @@ -0,0 +1,5 @@ +#include + +GCommand::~GCommand() +{ +} diff --git a/Libraries/LibGUI/GCommand.h b/Libraries/LibGUI/GCommand.h new file mode 100644 index 0000000000..7c61deb95b --- /dev/null +++ b/Libraries/LibGUI/GCommand.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +class GCommand { +public: + virtual ~GCommand(); + + virtual void undo() {} + virtual void redo() {} + + String action_text() const { return m_action_text; } + +protected: + GCommand() {} + void set_action_text(const String& text) { m_action_text = text; } + +private: + String m_action_text; +}; diff --git a/Libraries/LibGUI/GTextDocument.cpp b/Libraries/LibGUI/GTextDocument.cpp index a3217c06ea..95123f22b0 100644 --- a/Libraries/LibGUI/GTextDocument.cpp +++ b/Libraries/LibGUI/GTextDocument.cpp @@ -366,23 +366,7 @@ void GTextDocument::undo() { if (!can_undo()) return; - - auto& undo_container = m_undo_stack[m_undo_stack_index]; - auto& undo_vector = undo_container.m_undo_vector; - - //If we try to undo a empty vector, delete it and skip over. - if (undo_vector.is_empty()) { - m_undo_stack.remove(m_undo_stack_index); - undo(); - return; - } - - for (int i = 0; i < undo_vector.size(); i++) { - auto& undo_command = undo_vector[i]; - undo_command.undo(); - } - - m_undo_stack_index++; + m_undo_stack.undo(); notify_did_change(); } @@ -390,36 +374,13 @@ void GTextDocument::redo() { if (!can_redo()) return; - - auto& undo_container = m_undo_stack[m_undo_stack_index - 1]; - auto& redo_vector = undo_container.m_undo_vector; - - for (int i = redo_vector.size() - 1; i >= 0; i--) { - auto& undo_command = redo_vector[i]; - undo_command.redo(); - } - - m_undo_stack_index--; + m_undo_stack.redo(); notify_did_change(); } void GTextDocument::add_to_undo_stack(NonnullOwnPtr undo_command) { - if (m_undo_stack.is_empty()) { - auto undo_commands_container = make(); - m_undo_stack.prepend(move(undo_commands_container)); - } - - // Clear the elements of the stack before the m_undo_stack_index (Excluding our new element) - for (int i = 1; i < m_undo_stack_index; i++) - m_undo_stack.remove(1); - - if (m_undo_stack_index > 0 && !m_undo_stack.is_empty()) - m_undo_stack[0].m_undo_vector.clear(); - - m_undo_stack_index = 0; - - m_undo_stack[0].m_undo_vector.prepend(move(undo_command)); + m_undo_stack.push(move(undo_command)); } GTextDocumentUndoCommand::GTextDocumentUndoCommand(GTextDocument& document) @@ -536,21 +497,5 @@ void CreateLineCommand::redo() void GTextDocument::update_undo_timer() { - if (m_undo_stack.is_empty()) - return; - - auto& undo_vector = m_undo_stack[0].m_undo_vector; - - if (undo_vector.size() == m_last_updated_undo_vector_size && !undo_vector.is_empty()) { - auto undo_commands_container = make(); - m_undo_stack.prepend(move(undo_commands_container)); - // Note: Remove dbg() if we're 100% sure there are no bugs left. - dbg() << "Undo stack increased to " << m_undo_stack.size(); - - // Shift the index to the left since we're adding an empty container. - if (m_undo_stack_index > 0) - m_undo_stack_index++; - } - - m_last_updated_undo_vector_size = undo_vector.size(); + m_undo_stack.finalize_current_combo(); } diff --git a/Libraries/LibGUI/GTextDocument.h b/Libraries/LibGUI/GTextDocument.h index 3dd2f08bf6..7b527559dd 100644 --- a/Libraries/LibGUI/GTextDocument.h +++ b/Libraries/LibGUI/GTextDocument.h @@ -9,6 +9,7 @@ #include #include #include +#include class GTextEditor; class GTextDocument; @@ -23,12 +24,10 @@ struct GTextDocumentSpan { void* data { nullptr }; }; -class GTextDocumentUndoCommand { +class GTextDocumentUndoCommand : public GCommand { public: GTextDocumentUndoCommand(GTextDocument&); virtual ~GTextDocumentUndoCommand(); - virtual void undo() {} - virtual void redo() {} protected: GTextDocument& m_document; @@ -143,15 +142,10 @@ public: Optional first_non_skippable_span_before(const GTextPosition&) const; Optional first_non_skippable_span_after(const GTextPosition&) const; - struct UndoCommandsContainer { - NonnullOwnPtrVector m_undo_vector; - }; - void add_to_undo_stack(NonnullOwnPtr); - bool can_undo() const { return m_undo_stack_index < m_undo_stack.size() && !m_undo_stack.is_empty(); } - bool can_redo() const { return m_undo_stack_index > 0 && m_undo_stack[m_undo_stack_index - 1].m_undo_vector.size() > 0 && !m_undo_stack.is_empty(); } - + bool can_undo() const { return m_undo_stack.can_undo(); } + bool can_redo() const { return m_undo_stack.can_redo(); } void undo(); void redo(); @@ -169,10 +163,7 @@ private: HashTable m_clients; bool m_client_notifications_enabled { true }; - NonnullOwnPtrVector m_undo_stack; - int m_undo_stack_index { 0 }; - int m_last_updated_undo_vector_size = 0; - + GUndoStack m_undo_stack; RefPtr m_undo_timer; }; diff --git a/Libraries/LibGUI/GUndoStack.cpp b/Libraries/LibGUI/GUndoStack.cpp new file mode 100644 index 0000000000..670ee16111 --- /dev/null +++ b/Libraries/LibGUI/GUndoStack.cpp @@ -0,0 +1,89 @@ +#include + +GUndoStack::GUndoStack() +{ +} + +GUndoStack::~GUndoStack() +{ +} + +void GUndoStack::undo() +{ + if (!can_undo()) + return; + + auto& undo_container = m_stack[m_stack_index]; + auto& undo_vector = undo_container.m_undo_vector; + + //If we try to undo a empty vector, delete it and skip over. + if (undo_vector.is_empty()) { + m_stack.remove(m_stack_index); + undo(); + return; + } + + for (int i = 0; i < undo_vector.size(); i++) { + auto& undo_command = undo_vector[i]; + undo_command.undo(); + } + + m_stack_index++; +} + +void GUndoStack::redo() +{ + if (!can_redo()) + return; + + auto& undo_container = m_stack[m_stack_index - 1]; + auto& redo_vector = undo_container.m_undo_vector; + + for (int i = redo_vector.size() - 1; i >= 0; i--) { + auto& undo_command = redo_vector[i]; + undo_command.redo(); + } + + m_stack_index--; +} + +void GUndoStack::push(NonnullOwnPtr&& command) +{ + if (m_stack.is_empty()) { + auto undo_commands_container = make(); + m_stack.prepend(move(undo_commands_container)); + } + + // Clear the elements of the stack before the m_undo_stack_index (Excluding our new element) + for (int i = 1; i < m_stack_index; i++) + m_stack.remove(1); + + if (m_stack_index > 0 && !m_stack.is_empty()) + m_stack[0].m_undo_vector.clear(); + + m_stack_index = 0; + + m_stack[0].m_undo_vector.prepend(move(command)); +} + + +void GUndoStack::finalize_current_combo() +{ + if (m_stack.is_empty()) + return; + + auto& undo_vector = m_stack[0].m_undo_vector; + + if (undo_vector.size() == m_last_updated_undo_vector_size && !undo_vector.is_empty()) { + auto undo_commands_container = make(); + m_stack.prepend(move(undo_commands_container)); + // Note: Remove dbg() if we're 100% sure there are no bugs left. + dbg() << "Undo stack increased to " << m_stack.size(); + + // Shift the index to the left since we're adding an empty container. + if (m_stack_index > 0) + m_stack_index++; + } + + m_last_updated_undo_vector_size = undo_vector.size(); +} diff --git a/Libraries/LibGUI/GUndoStack.h b/Libraries/LibGUI/GUndoStack.h new file mode 100644 index 0000000000..4bb83ea7e9 --- /dev/null +++ b/Libraries/LibGUI/GUndoStack.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +class GCommand; + +class GUndoStack { +public: + GUndoStack(); + ~GUndoStack(); + + void push(NonnullOwnPtr&&); + + bool can_undo() const { return m_stack_index < m_stack.size() && !m_stack.is_empty(); } + bool can_redo() const { return m_stack_index > 0 && m_stack[m_stack_index - 1].m_undo_vector.size() > 0 && !m_stack.is_empty(); } + + void undo(); + void redo(); + + void finalize_current_combo(); + +private: + struct UndoCommandsContainer { + NonnullOwnPtrVector m_undo_vector; + }; + + NonnullOwnPtrVector m_stack; + int m_stack_index { 0 }; + int m_last_updated_undo_vector_size { 0 }; +}; diff --git a/Libraries/LibGUI/Makefile b/Libraries/LibGUI/Makefile index ac082da414..1993aabf73 100644 --- a/Libraries/LibGUI/Makefile +++ b/Libraries/LibGUI/Makefile @@ -57,6 +57,8 @@ OBJS = \ GAboutDialog.o \ GModelSelection.o \ GLazyWidget.o \ + GCommand.o \ + GUndoStack.o \ GWindow.o LIBRARY = libgui.a