QtSpell  0.8.2
Spell checking for Qt text widgets
/usr/src/RPM/BUILD/qtspell-0.8.2/src/UndoRedoStack.cpp
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