|
QtSpell
0.8.2
Spell checking for Qt text widgets
|
00001 /* QtSpell - Spell checking for Qt text widgets. 00002 * Copyright (c) 2014 Sandro Mani 00003 * 00004 * This program is free software; you can redistribute it and/or modify 00005 * it under the terms of the GNU General Public License as published by 00006 * the Free Software Foundation; either version 2 of the License, or 00007 * (at your option) any later version. 00008 * 00009 * This program is distributed in the hope that it will be useful, 00010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 * GNU General Public License for more details. 00013 * 00014 * You should have received a copy of the GNU General Public License along 00015 * with this program; if not, write to the Free Software Foundation, Inc., 00016 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 00017 */ 00018 00019 #include "UndoRedoStack.hpp" 00020 #include "TextEditChecker_p.hpp" 00021 #include <QTextDocument> 00022 00023 namespace QtSpell { 00024 00025 struct UndoRedoStack::Action { 00026 virtual ~Action(){} 00027 }; 00028 00029 struct UndoRedoStack::UndoableInsert : public UndoRedoStack::Action { 00030 QString text; 00031 int pos; 00032 bool isWhitespace; 00033 bool isMergeable; 00034 00035 UndoableInsert(int _pos, const QString& _text){ 00036 pos = _pos; 00037 text = _text; 00038 isWhitespace = text.length() == 1 && text[0].isSpace(); 00039 isMergeable = (text.length() == 1); 00040 } 00041 }; 00042 00043 struct UndoRedoStack::UndoableDelete : public UndoRedoStack::Action { 00044 QString text; 00045 int start, end; 00046 bool deleteKeyUsed; 00047 bool isWhitespace; 00048 bool isMergeable; 00049 00050 UndoableDelete(int _start, int _end, const QString& _text, bool _deleteKeyUsed){ 00051 start = _start; 00052 end = _end; 00053 text = _text; 00054 deleteKeyUsed = _deleteKeyUsed; 00055 isWhitespace = text.length() == 1 && text[0].isSpace(); 00056 isMergeable = (text.length() == 1); 00057 } 00058 }; 00059 00060 UndoRedoStack::UndoRedoStack(TextEditProxy* textEdit) 00061 : m_actionInProgress(false), m_textEdit(textEdit) 00062 { 00063 // We need to keep undo/redo enabled to retreive the deleted text in onContentsChange... 00064 if(m_textEdit){ 00065 m_textEdit->document()->setUndoRedoEnabled(true); 00066 } 00067 } 00068 00069 void UndoRedoStack::clear() 00070 { 00071 qDeleteAll(m_undoStack); 00072 qDeleteAll(m_redoStack); 00073 m_undoStack.clear(); 00074 m_redoStack.clear(); 00075 emit undoAvailable(false); 00076 emit redoAvailable(false); 00077 } 00078 00079 void UndoRedoStack::handleContentsChange(int pos, int removed, int added) 00080 { 00081 if(m_actionInProgress || (added == 0 && removed == 0)){ 00082 return; 00083 } 00084 // Qt Bug? Apparently, when contents is pasted at pos = 0, added and removed are too large by 1 00085 QTextCursor c(m_textEdit->textCursor()); 00086 c.movePosition(QTextCursor::End); 00087 int len = c.position(); 00088 if(pos == 0 && added > len){ 00089 --added; 00090 --removed; 00091 } 00092 qDeleteAll(m_redoStack); 00093 m_redoStack.clear(); 00094 if(removed > 0){ 00095 m_textEdit->document()->undo(); 00096 bool deleteWasUsed = (c.anchor() == c.position() && c.position() == pos); 00097 c.setPosition(pos); 00098 c.setPosition(pos + removed, QTextCursor::KeepAnchor); 00099 UndoableDelete* undoAction = new UndoableDelete(pos, pos + removed, c.selectedText(), deleteWasUsed); 00100 m_textEdit->document()->redo(); 00101 if(m_undoStack.empty() || !dynamic_cast<UndoableDelete*>(m_undoStack.top())){ 00102 m_undoStack.push(undoAction); 00103 }else{ 00104 UndoableDelete* prevDelete = static_cast<UndoableDelete*>(m_undoStack.top()); 00105 if(deleteMergeable(prevDelete, undoAction)){ 00106 if(prevDelete->start == undoAction->start){ // Delete key used 00107 prevDelete->text += undoAction->text; 00108 prevDelete->end += (undoAction->end - undoAction->start); 00109 }else{ // Backspace used 00110 prevDelete->text = undoAction->text + prevDelete->text; 00111 prevDelete->start = undoAction->start; 00112 } 00113 }else{ 00114 m_undoStack.push(undoAction); 00115 } 00116 } 00117 } 00118 if(added > 0){ 00119 QTextCursor c(m_textEdit->textCursor()); 00120 c.setPosition(pos); 00121 c.setPosition(pos + added, QTextCursor::KeepAnchor); 00122 UndoableInsert* undoAction = new UndoableInsert(pos, c.selectedText()); 00123 if(m_undoStack.empty() || !dynamic_cast<UndoableInsert*>(m_undoStack.top())){ 00124 m_undoStack.push(undoAction); 00125 }else{ 00126 UndoableInsert* prevInsert = static_cast<UndoableInsert*>(m_undoStack.top()); 00127 if(insertMergeable(prevInsert, undoAction)){ 00128 prevInsert->text += undoAction->text; 00129 }else{ 00130 m_undoStack.push(undoAction); 00131 } 00132 } 00133 } 00134 // We are only interested in the previous step for delete, no point in storing the rest 00135 if(added > 0 || removed > 0){ 00136 m_textEdit->document()->clearUndoRedoStacks(); 00137 } 00138 emit redoAvailable(false); 00139 emit undoAvailable(true); 00140 } 00141 00142 void UndoRedoStack::undo() 00143 { 00144 if(m_undoStack.empty()){ 00145 return; 00146 } 00147 m_actionInProgress = true; 00148 Action* undoAction = m_undoStack.pop(); 00149 m_redoStack.push(undoAction); 00150 QTextCursor c(m_textEdit->textCursor()); 00151 if(dynamic_cast<UndoableInsert*>(undoAction)){ 00152 UndoableInsert* insertAction = static_cast<UndoableInsert*>(undoAction); 00153 c.setPosition(insertAction->pos); 00154 c.setPosition(insertAction->pos + insertAction->text.length(), QTextCursor::KeepAnchor); 00155 c.removeSelectedText(); 00156 }else{ 00157 UndoableDelete* deleteAction = static_cast<UndoableDelete*>(undoAction); 00158 c.setPosition(deleteAction->start); 00159 c.insertText(deleteAction->text); 00160 if(deleteAction->deleteKeyUsed){ 00161 c.setPosition(deleteAction->start); 00162 } 00163 } 00164 m_textEdit->setTextCursor(c); 00165 emit undoAvailable(!m_undoStack.empty()); 00166 emit redoAvailable(!m_redoStack.empty()); 00167 m_actionInProgress = false; 00168 } 00169 00170 void UndoRedoStack::redo() 00171 { 00172 if(m_redoStack.empty()){ 00173 return; 00174 } 00175 m_actionInProgress = true; 00176 Action* redoAction = m_redoStack.top(); 00177 m_redoStack.pop(); 00178 m_undoStack.push(redoAction); 00179 QTextCursor c(m_textEdit->textCursor()); 00180 if(dynamic_cast<UndoableInsert*>(redoAction)){ 00181 UndoableInsert* insertAction = static_cast<UndoableInsert*>(redoAction); 00182 c.setPosition(insertAction->pos); 00183 c.insertText(insertAction->text); 00184 }else{ 00185 UndoableDelete* deleteAction = static_cast<UndoableDelete*>(redoAction); 00186 c.setPosition(deleteAction->start); 00187 c.setPosition(deleteAction->end, QTextCursor::KeepAnchor); 00188 c.removeSelectedText(); 00189 } 00190 m_textEdit->setTextCursor(c); 00191 emit undoAvailable(!m_undoStack.empty()); 00192 emit redoAvailable(!m_redoStack.empty()); 00193 m_actionInProgress = false; 00194 } 00195 00196 bool UndoRedoStack::insertMergeable(const UndoableInsert* prev, const UndoableInsert* cur) const 00197 { 00198 return (cur->pos == prev->pos + prev->text.length()) && 00199 (cur->isWhitespace == prev->isWhitespace) && 00200 (cur->isMergeable && prev->isMergeable); 00201 } 00202 00203 bool UndoRedoStack::deleteMergeable(const UndoableDelete* prev, const UndoableDelete* cur) const 00204 { 00205 return (prev->deleteKeyUsed == cur->deleteKeyUsed) && 00206 (cur->isWhitespace == prev->isWhitespace) && 00207 (cur->isMergeable && prev->isMergeable) && 00208 (prev->start == cur->start || prev->start == cur->end); 00209 } 00210 00211 } // QtSpell
1.7.6.1