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