From e41dfa6599c9768e81147c04cd4f5d1c697a4d54 Mon Sep 17 00:00:00 2001 From: Zack Penn Date: Sat, 19 Feb 2022 14:03:50 -0600 Subject: [PATCH] Spreadsheet: Add undo/redo implementation The Spreadsheet application currently does not support undo/redo, and with this update, we are starting the process of adding this feature :-) Additionally, the save dialog has been updated to use GUI::MessageBox::ask_about_unsaved_changes() for system cohesity. Spreadsheet: Add basic undo functinoality The spreadsheet application now has basic support for undo. Testing of this feature is limited, and may not work as intended yet. Spreadsheet: Add callback when a cell's value is changed In addition to the callback being added, this commit also exposes the SheetModel class via a getter in SpreadSheetView. Spreadsheet: Remove debug statements and use cell change callback This commit uses the on_cell_data_change callback from within the SheetModel class. This allows for us to push/pop changes to the undo stack. With this, we have basic Undo/Redo functionality :-) Spreadsheet: Actually add window::set_modified Spreadsheet: Const-correctness :-) Spreadsheet: Reorder the edit menu actions --- Userland/Applications/Spreadsheet/Cell.cpp | 17 ++++++++ Userland/Applications/Spreadsheet/Cell.h | 14 ++++++ .../Spreadsheet/SpreadsheetModel.cpp | 3 ++ .../Spreadsheet/SpreadsheetModel.h | 2 + .../Spreadsheet/SpreadsheetView.h | 2 + .../Spreadsheet/SpreadsheetWidget.cpp | 43 ++++++++++++++++++- .../Spreadsheet/SpreadsheetWidget.h | 10 +++++ 7 files changed, 89 insertions(+), 2 deletions(-) diff --git a/Userland/Applications/Spreadsheet/Cell.cpp b/Userland/Applications/Spreadsheet/Cell.cpp index 32e98270e2..dd111615c1 100644 --- a/Userland/Applications/Spreadsheet/Cell.cpp +++ b/Userland/Applications/Spreadsheet/Cell.cpp @@ -191,4 +191,21 @@ void Cell::copy_from(const Cell& other) m_thrown_value = other.m_thrown_value; } +CellUndoCommand::CellUndoCommand(Cell& cell, String const& previous_data) + : m_cell(cell) + , m_current_data(cell.data()) + , m_previous_data(previous_data) +{ +} + +void CellUndoCommand::undo() +{ + m_cell.set_data(m_previous_data); +} + +void CellUndoCommand::redo() +{ + m_cell.set_data(m_current_data); +} + } diff --git a/Userland/Applications/Spreadsheet/Cell.h b/Userland/Applications/Spreadsheet/Cell.h index 231bb92738..f38b0a7092 100644 --- a/Userland/Applications/Spreadsheet/Cell.h +++ b/Userland/Applications/Spreadsheet/Cell.h @@ -14,6 +14,7 @@ #include #include #include +#include namespace Spreadsheet { @@ -120,4 +121,17 @@ private: Format m_evaluated_formats; }; +class CellUndoCommand : public GUI::Command { +public: + CellUndoCommand(Cell&, String const&); + + virtual void undo() override; + virtual void redo() override; + +private: + Cell& m_cell; + String m_current_data; + String m_previous_data; +}; + } diff --git a/Userland/Applications/Spreadsheet/SpreadsheetModel.cpp b/Userland/Applications/Spreadsheet/SpreadsheetModel.cpp index 51b1c5cca4..e7980430f0 100644 --- a/Userland/Applications/Spreadsheet/SpreadsheetModel.cpp +++ b/Userland/Applications/Spreadsheet/SpreadsheetModel.cpp @@ -153,7 +153,10 @@ void SheetModel::set_data(const GUI::ModelIndex& index, const GUI::Variant& valu return; auto& cell = m_sheet->ensure({ (size_t)index.column(), (size_t)index.row() }); + auto previous_data = cell.data(); cell.set_data(value.to_string()); + if (on_cell_data_change) + on_cell_data_change(cell, previous_data); did_update(UpdateFlag::DontInvalidateIndices); } diff --git a/Userland/Applications/Spreadsheet/SpreadsheetModel.h b/Userland/Applications/Spreadsheet/SpreadsheetModel.h index f28411db23..5c6dcb36a5 100644 --- a/Userland/Applications/Spreadsheet/SpreadsheetModel.h +++ b/Userland/Applications/Spreadsheet/SpreadsheetModel.h @@ -29,6 +29,8 @@ public: void update(); + Function on_cell_data_change; + private: explicit SheetModel(Sheet& sheet) : m_sheet(sheet) diff --git a/Userland/Applications/Spreadsheet/SpreadsheetView.h b/Userland/Applications/Spreadsheet/SpreadsheetView.h index e372d68385..67f2dc4f3c 100644 --- a/Userland/Applications/Spreadsheet/SpreadsheetView.h +++ b/Userland/Applications/Spreadsheet/SpreadsheetView.h @@ -103,6 +103,8 @@ public: void move_cursor(GUI::AbstractView::CursorMovement); + NonnullRefPtr model() { return m_sheet_model; }; + private: virtual void hide_event(GUI::HideEvent&) override; virtual void show_event(GUI::ShowEvent&) override; diff --git a/Userland/Applications/Spreadsheet/SpreadsheetWidget.cpp b/Userland/Applications/Spreadsheet/SpreadsheetWidget.cpp index cc320b7974..3396b695aa 100644 --- a/Userland/Applications/Spreadsheet/SpreadsheetWidget.cpp +++ b/Userland/Applications/Spreadsheet/SpreadsheetWidget.cpp @@ -205,6 +205,14 @@ SpreadsheetWidget::SpreadsheetWidget(GUI::Window& parent_window, NonnullRefPtrVe }, window()); + m_undo_action = GUI::CommonActions::make_undo_action([&](auto&) { + undo(); + }); + + m_redo_action = GUI::CommonActions::make_redo_action([&](auto&) { + redo(); + }); + m_functions_help_action = GUI::Action::create( "&Functions Help", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/app-help.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) { if (auto* worksheet_ptr = current_worksheet_if_available()) { @@ -227,6 +235,8 @@ SpreadsheetWidget::SpreadsheetWidget(GUI::Window& parent_window, NonnullRefPtrVe toolbar.add_action(*m_cut_action); toolbar.add_action(*m_copy_action); toolbar.add_action(*m_paste_action); + toolbar.add_action(*m_undo_action); + toolbar.add_action(*m_redo_action); } void SpreadsheetWidget::resize_event(GUI::ResizeEvent& event) @@ -251,6 +261,10 @@ void SpreadsheetWidget::setup_tabs(NonnullRefPtrVector new_sheets) m_selected_view->on_selection_dropped = nullptr; } m_selected_view = &static_cast(selected_widget); + m_selected_view->model()->on_cell_data_change = [&](auto& cell, auto& previous_data) { + undo_stack().push(make(cell, previous_data)); + window()->set_modified(true); + }; m_selected_view->on_selection_changed = [&](Vector&& selection) { auto* sheet_ptr = m_selected_view->sheet_if_available(); // How did this even happen? @@ -390,11 +404,33 @@ void SpreadsheetWidget::try_generate_tip_for_input_expression(StringView source, } } +void SpreadsheetWidget::undo() +{ + if (!m_undo_stack.can_undo()) + return; + + m_undo_stack.undo(); + update(); +} + +void SpreadsheetWidget::redo() +{ + if (!m_undo_stack.can_redo()) + return; + + m_undo_stack.redo(); + update(); +} + void SpreadsheetWidget::save(StringView filename) { auto result = m_workbook->save(filename); - if (result.is_error()) + if (result.is_error()) { GUI::MessageBox::show_error(window(), result.error()); + return; + } + undo_stack().set_current_unmodified(); + window()->set_modified(false); } void SpreadsheetWidget::load_file(Core::File& file) @@ -418,7 +454,7 @@ void SpreadsheetWidget::load_file(Core::File& file) bool SpreadsheetWidget::request_close() { - if (!m_workbook->dirty()) + if (!undo_stack().is_current_modified()) return true; auto result = GUI::MessageBox::ask_about_unsaved_changes(window(), current_filename()); @@ -533,6 +569,9 @@ void SpreadsheetWidget::initialize_menubar(GUI::Window& window) file_menu.add_action(*m_quit_action); auto& edit_menu = window.add_menu("&Edit"); + edit_menu.add_action(*m_undo_action); + edit_menu.add_action(*m_redo_action); + edit_menu.add_separator(); edit_menu.add_action(*m_cut_action); edit_menu.add_action(*m_copy_action); edit_menu.add_action(*m_paste_action); diff --git a/Userland/Applications/Spreadsheet/SpreadsheetWidget.h b/Userland/Applications/Spreadsheet/SpreadsheetWidget.h index aa7fb14b4d..4a2ead0fe2 100644 --- a/Userland/Applications/Spreadsheet/SpreadsheetWidget.h +++ b/Userland/Applications/Spreadsheet/SpreadsheetWidget.h @@ -42,6 +42,10 @@ public: void initialize_menubar(GUI::Window&); + void undo(); + void redo(); + auto& undo_stack() { return m_undo_stack; } + private: virtual void resize_event(GUI::ResizeEvent&) override; @@ -60,6 +64,7 @@ private: RefPtr m_tab_context_menu; RefPtr m_tab_context_menu_sheet_view; bool m_should_change_selected_cells { false }; + GUI::UndoStack m_undo_stack; OwnPtr m_workbook; @@ -69,11 +74,16 @@ private: RefPtr m_save_action; RefPtr m_save_as_action; RefPtr m_quit_action; + RefPtr m_cut_action; RefPtr m_copy_action; RefPtr m_paste_action; + RefPtr m_undo_action; + RefPtr m_redo_action; + RefPtr m_functions_help_action; RefPtr m_about_action; + RefPtr m_rename_action; };