UndoStack.cpp 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibGUI/Command.h>
  7. #include <LibGUI/UndoStack.h>
  8. namespace GUI {
  9. UndoStack::UndoStack()
  10. {
  11. }
  12. UndoStack::~UndoStack()
  13. {
  14. }
  15. bool UndoStack::can_undo() const
  16. {
  17. return m_stack_index > 0 || (m_stack.size() == 1 && m_stack[0].commands.size() > 0);
  18. }
  19. bool UndoStack::can_redo() const
  20. {
  21. if (m_stack.is_empty())
  22. return false;
  23. return m_stack_index != m_stack.size() - 1;
  24. }
  25. void UndoStack::undo()
  26. {
  27. if (!can_undo())
  28. return;
  29. finalize_current_combo();
  30. auto& combo = m_stack[--m_stack_index];
  31. for (auto i = static_cast<ssize_t>(combo.commands.size()) - 1; i >= 0; i--)
  32. combo.commands[i].undo();
  33. if (on_state_change)
  34. on_state_change();
  35. }
  36. void UndoStack::redo()
  37. {
  38. if (!can_redo())
  39. return;
  40. auto& commands = m_stack[m_stack_index++].commands;
  41. for (auto& command : commands)
  42. command.redo();
  43. if (on_state_change)
  44. on_state_change();
  45. }
  46. void UndoStack::pop()
  47. {
  48. VERIFY(!m_stack.is_empty());
  49. m_stack.take_last();
  50. if (m_clean_index.has_value() && m_clean_index.value() > m_stack.size())
  51. m_clean_index = {};
  52. }
  53. void UndoStack::push(NonnullOwnPtr<Command>&& command)
  54. {
  55. if (m_stack.is_empty())
  56. finalize_current_combo();
  57. // If the stack cursor is behind the top of the stack, nuke everything from here to the top.
  58. if (m_stack_index != m_stack.size() - 1) {
  59. while (m_stack.size() != m_stack_index) {
  60. pop();
  61. }
  62. finalize_current_combo();
  63. }
  64. if (!m_stack.last().commands.is_empty()) {
  65. bool merged = m_stack.last().commands.last().merge_with(*command);
  66. if (merged)
  67. return;
  68. }
  69. m_stack.last().commands.append(move(command));
  70. if (on_state_change)
  71. on_state_change();
  72. }
  73. void UndoStack::finalize_current_combo()
  74. {
  75. if (m_stack.is_empty()) {
  76. m_stack.append(make<Combo>());
  77. return;
  78. }
  79. if (!m_stack.last().commands.is_empty()) {
  80. m_stack.append(make<Combo>());
  81. m_stack_index = m_stack.size() - 1;
  82. if (on_state_change)
  83. on_state_change();
  84. }
  85. }
  86. void UndoStack::set_current_unmodified()
  87. {
  88. finalize_current_combo();
  89. if (m_clean_index.has_value() && m_clean_index.value() == m_stack_index)
  90. return;
  91. m_clean_index = m_stack_index;
  92. if (on_state_change)
  93. on_state_change();
  94. }
  95. bool UndoStack::is_current_modified() const
  96. {
  97. if (!m_clean_index.has_value())
  98. return true;
  99. if (m_stack_index != m_clean_index.value())
  100. return true;
  101. if (m_stack.is_empty())
  102. return false;
  103. if (m_stack_index == m_stack.size() - 1 && !m_stack[m_stack_index].commands.is_empty())
  104. return true;
  105. return false;
  106. }
  107. void UndoStack::clear()
  108. {
  109. if (m_stack.is_empty() && m_stack_index == 0 && !m_clean_index.has_value())
  110. return;
  111. m_stack.clear();
  112. m_stack_index = 0;
  113. m_clean_index.clear();
  114. if (on_state_change)
  115. on_state_change();
  116. }
  117. }