QtSpell 1.0.1
Spell checking for Qt text widgets
UndoRedoStack.cpp
1/* QtSpell - Spell checking for Qt text widgets.
2 * Copyright (c) 2014-2022 Sandro Mani
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19#include "UndoRedoStack.hpp"
20#include "TextEditChecker_p.hpp"
21#include <QTextDocument>
22
23namespace QtSpell {
24
25struct UndoRedoStack::Action {
26 virtual ~Action(){}
27};
28
29struct UndoRedoStack::UndoableInsert : public UndoRedoStack::Action {
30 QString text;
31 int pos;
32 bool isWhitespace;
33 bool isMergeable;
34
35 UndoableInsert(int _pos, const QString& _text){
36 pos = _pos;
37 text = _text;
38 isWhitespace = text.length() == 1 && text[0].isSpace();
39 isMergeable = (text.length() == 1);
40 }
41};
42
43struct UndoRedoStack::UndoableDelete : public UndoRedoStack::Action {
44 QString text;
45 int start, end;
46 bool deleteKeyUsed;
47 bool isWhitespace;
48 bool isMergeable;
49
50 UndoableDelete(int _start, int _end, const QString& _text, bool _deleteKeyUsed){
51 start = _start;
52 end = _end;
53 text = _text;
54 deleteKeyUsed = _deleteKeyUsed;
55 isWhitespace = text.length() == 1 && text[0].isSpace();
56 isMergeable = (text.length() == 1);
57 }
58};
59
60UndoRedoStack::UndoRedoStack(TextEditProxy* textEdit)
61 : m_textEdit(textEdit)
62{
63 // We need to keep undo/redo enabled to retreive the deleted text in onContentsChange...
64 if(m_textEdit){
65 m_textEdit->document()->setUndoRedoEnabled(true);
66 }
67}
68
69void UndoRedoStack::clear()
70{
71 qDeleteAll(m_undoStack);
72 qDeleteAll(m_redoStack);
73 m_undoStack.clear();
74 m_redoStack.clear();
75 emit undoAvailable(false);
76 emit redoAvailable(false);
77}
78
79void UndoRedoStack::handleContentsChange(int pos, int removed, int added)
80{
81 if(m_actionInProgress || (added == 0 && removed == 0)){
82 return;
83 }
84 // Qt Bug? Apparently, when contents is pasted at pos = 0, added and removed are too large by 1
85 QTextCursor c(m_textEdit->textCursor());
86 c.movePosition(QTextCursor::End);
87 int len = c.position();
88 if(pos == 0 && added > len){
89 --added;
90 --removed;
91 }
92 qDeleteAll(m_redoStack);
93 m_redoStack.clear();
94 if(removed > 0){
95 m_textEdit->document()->undo();
96 bool deleteWasUsed = (c.anchor() == c.position() && c.position() == pos);
97 c.setPosition(pos);
98 c.setPosition(pos + removed, QTextCursor::KeepAnchor);
99 UndoableDelete* undoAction = new UndoableDelete(pos, pos + removed, c.selectedText(), deleteWasUsed);
100 m_textEdit->document()->redo();
101 if(m_undoStack.empty() || !dynamic_cast<UndoableDelete*>(m_undoStack.top())){
102 m_undoStack.push(undoAction);
103 }else{
104 UndoableDelete* prevDelete = static_cast<UndoableDelete*>(m_undoStack.top());
105 if(deleteMergeable(prevDelete, undoAction)){
106 if(prevDelete->start == undoAction->start){ // Delete key used
107 prevDelete->text += undoAction->text;
108 prevDelete->end += (undoAction->end - undoAction->start);
109 }else{ // Backspace used
110 prevDelete->text = undoAction->text + prevDelete->text;
111 prevDelete->start = undoAction->start;
112 }
113 }else{
114 m_undoStack.push(undoAction);
115 }
116 }
117 }
118 if(added > 0){
119 QTextCursor c(m_textEdit->textCursor());
120 c.setPosition(pos);
121 c.setPosition(pos + added, QTextCursor::KeepAnchor);
122 UndoableInsert* undoAction = new UndoableInsert(pos, c.selectedText());
123 if(m_undoStack.empty() || !dynamic_cast<UndoableInsert*>(m_undoStack.top())){
124 m_undoStack.push(undoAction);
125 }else{
126 UndoableInsert* prevInsert = static_cast<UndoableInsert*>(m_undoStack.top());
127 if(insertMergeable(prevInsert, undoAction)){
128 prevInsert->text += undoAction->text;
129 }else{
130 m_undoStack.push(undoAction);
131 }
132 }
133 }
134 // We are only interested in the previous step for delete, no point in storing the rest
135 if(added > 0 || removed > 0){
136 m_textEdit->document()->clearUndoRedoStacks();
137 }
138 emit redoAvailable(false);
139 emit undoAvailable(true);
140}
141
142void UndoRedoStack::undo()
143{
144 if(m_undoStack.empty()){
145 return;
146 }
147 m_actionInProgress = true;
148 Action* undoAction = m_undoStack.pop();
149 m_redoStack.push(undoAction);
150 QTextCursor c(m_textEdit->textCursor());
151 if(dynamic_cast<UndoableInsert*>(undoAction)){
152 UndoableInsert* insertAction = static_cast<UndoableInsert*>(undoAction);
153 c.setPosition(insertAction->pos);
154 c.setPosition(insertAction->pos + insertAction->text.length(), QTextCursor::KeepAnchor);
155 c.removeSelectedText();
156 }else{
157 UndoableDelete* deleteAction = static_cast<UndoableDelete*>(undoAction);
158 c.setPosition(deleteAction->start);
159 c.insertText(deleteAction->text);
160 if(deleteAction->deleteKeyUsed){
161 c.setPosition(deleteAction->start);
162 }
163 }
164 m_textEdit->setTextCursor(c);
165 emit undoAvailable(!m_undoStack.empty());
166 emit redoAvailable(!m_redoStack.empty());
167 m_actionInProgress = false;
168}
169
170void UndoRedoStack::redo()
171{
172 if(m_redoStack.empty()){
173 return;
174 }
175 m_actionInProgress = true;
176 Action* redoAction = m_redoStack.top();
177 m_redoStack.pop();
178 m_undoStack.push(redoAction);
179 QTextCursor c(m_textEdit->textCursor());
180 if(dynamic_cast<UndoableInsert*>(redoAction)){
181 UndoableInsert* insertAction = static_cast<UndoableInsert*>(redoAction);
182 c.setPosition(insertAction->pos);
183 c.insertText(insertAction->text);
184 }else{
185 UndoableDelete* deleteAction = static_cast<UndoableDelete*>(redoAction);
186 c.setPosition(deleteAction->start);
187 c.setPosition(deleteAction->end, QTextCursor::KeepAnchor);
188 c.removeSelectedText();
189 }
190 m_textEdit->setTextCursor(c);
191 emit undoAvailable(!m_undoStack.empty());
192 emit redoAvailable(!m_redoStack.empty());
193 m_actionInProgress = false;
194}
195
196bool UndoRedoStack::insertMergeable(const UndoableInsert* prev, const UndoableInsert* cur) const
197{
198 return (cur->pos == prev->pos + prev->text.length()) &&
199 (cur->isWhitespace == prev->isWhitespace) &&
200 (cur->isMergeable && prev->isMergeable);
201}
202
203bool UndoRedoStack::deleteMergeable(const UndoableDelete* prev, const UndoableDelete* cur) const
204{
205 return (prev->deleteKeyUsed == cur->deleteKeyUsed) &&
206 (cur->isWhitespace == prev->isWhitespace) &&
207 (cur->isMergeable && prev->isMergeable) &&
208 (prev->start == cur->start || prev->start == cur->end);
209}
210
211} // QtSpell
QtSpell namespace.
Definition Checker.cpp:77