]> gitweb.hamatoma.de Git - reqt/commitdiff
Undo works
authorhama <hama@siduction.net>
Wed, 15 Jul 2015 21:29:03 +0000 (23:29 +0200)
committerhama <hama@siduction.net>
Wed, 15 Jul 2015 21:29:03 +0000 (23:29 +0200)
appl/reditor/reditor.pro
base/ReFile.cpp
base/ReFile.hpp
base/ReQStringUtil.cpp
base/ReQStringUtil.hpp
cunit/cuReFile.cpp
cunit/cuReQStringUtil.cpp
gui/ReEdit.cpp
gui/ReEdit.hpp

index 82217985e3c2ce6b332009e4f552194c8d608960..22c86ca5962108b07c2e5efead581442e24ab8c2 100644 (file)
@@ -17,13 +17,18 @@ SOURCES += main.cpp\
         ../../gui/ReEdit.cpp \
         ../../base/ReFile.cpp \
        mainwindow.cpp \
-    ../../base/ReLogger.cpp
+    ../../base/ReLogger.cpp \
+    ../../base/ReQStringUtil.cpp \
+    ../../base/ReException.cpp
 
 
 HEADERS  += mainwindow.hpp \
         ../../base/rebase.hpp \
         ../../gui/regui.hpp \
-        ../../gui/ReEdit.hpp
+        ../../gui/ReEdit.hpp \
+    ../../base/ReStringUtil.hpp \
+    ../../base/ReQStringUtil.hpp \
+    ../../base/ReException.hpp
 
 FORMS    += mainwindow.ui
 
index 3d818cc31acd78401e0b0e6d1a111a974afc78b5..c616cc854b706235ae2df165f39be0e372fda421 100644 (file)
@@ -44,8 +44,8 @@ int memicmp(const void* str1, const void* str2, size_t length) {
  * Constructor.
  */
 ReLines::ReLines() :
-               QStringList(),
-               m_empty() {
+           QStringList(),
+           m_empty() {
 }
 /**
  * Destructor.
@@ -54,68 +54,136 @@ ReLines::~ReLines() {
 }
 
 /**
- * Inserts a text into a given position of the file.
+ * Checks that the summary size of the undo information remains under the maximum.
  *
- * Note: the line separators will not be stored.
+ * @param stringSize   the additional string size
+ * @return                             <code>true</code>: undo info should be stored
+ *                                             <code>false</code>: too large undo info, all info has
+ *                                             been freed
+ */
+bool ReUndoList::checkSummarySize(qint64 stringSize) {
+       static qint64 sizeStruct = (qint64) sizeof(UndoItem);
+       while (m_currentUndoSize + stringSize + sizeStruct > m_maxUndoSize) {
+               if (m_list.length() == 0)
+                       m_currentUndoSize = 0;
+               else {
+                       UndoItem* item = m_list.at(0);
+                       m_currentUndoSize -= sizeof *item - item->m_string.length();
+                       m_list.removeAt(0);
+                       delete item;
+               }
+       }
+       return stringSize + sizeStruct < m_maxUndoSize;
+}
+
+/**
+ * Removes all lines.
+ */
+void ReLines::clear() {
+       QStringList::clear();
+}
+
+/**
+ * Inserts one or more lines into the lines.
  *
- * @param line  the line number (0..N-1) of the insert position
- * @param col   the column number (0..M-1) of the insert position
- * @param text  the text to insert. May contain line separators ('\n' or "\r\n").
- *              In this case the number of lines grows
- */
-void ReLines::insertText(int line, int col, const QString& text) {
-       if (line >= 0) {
-               if (line == length()) {
-                       int beginOfLine = 0;
-                       int endOfLine = text.indexOf('\n');
-                       while (endOfLine >= 0) {
-                               int nextBOL = endOfLine + 1;
-                               if (endOfLine > 0 && text.at(endOfLine - 1) == '\r')
-                                       endOfLine--;
-                               append(text.mid(beginOfLine, endOfLine - beginOfLine));
-                               beginOfLine = nextBOL;
-                               endOfLine = text.indexOf('\n', beginOfLine);
+ * @param lineNo       the line number (0..N-1) of the new line
+ * @param text         the text to insert. May not contain newlines!
+ * @param withUndo     <code>true</code>: prepares undo operation<br>
+ *                                     <code>false</code>: undo is impossible
+ */
+void ReLines::insertLines(int lineNo, const QString& text, bool withUndo) {
+       if (lineNo >= 0) {
+               int count = 0;
+               if (text.isEmpty())
+                       count = 1;
+               else {
+                       count = ReQStringUtil::countOf(text, '\n');
+                       if (text.at(text.length() - 1) != '\n')
+                               count++;
+               }
+               if (withUndo)
+                       storeInsertLines(lineNo, count);
+               int start = 0;
+               int end;
+               if (lineNo >= length()) {
+                       while ((end = text.indexOf('\n', start)) >= 0) {
+                               append(text.mid(start, end - start));
+                               start = end + 1;
                        }
-                       if (beginOfLine < text.length())
-                               append(text.mid(beginOfLine));
-               } else if (line < length()) {
-                       QString oldLine = at(line);
-                       int length = oldLine.length();
-                       if (col < 0)
-                               col = 0;
-                       else if (col >= length)
-                               col = length - 1;
-                       QString tail;
-                       if (col < length - 1)
-                               tail = oldLine.mid(col);
-                       int endOfLine = text.indexOf('\n');
-                       if (endOfLine < 0) {
-                               replace(line, oldLine.mid(0, col) + text + tail);
-                       } else {
-                               int beginOfLine = endOfLine + 1;
-                               if (endOfLine > 0 && text[endOfLine - 1] == '\r')
-                                       endOfLine--;
-                               replace(line++, oldLine.mid(0, col) + text.mid(0, endOfLine));
-                               do {
-                                       if ((endOfLine = text.indexOf('\n', beginOfLine)) < 0)
-                                               break;
-                                       int nextBOL = endOfLine + 1;
-                                       if (endOfLine > 0 && text.at(endOfLine - 1) == '\r')
-                                               endOfLine--;
-                                       insert(line++,
-                                               text.mid(beginOfLine, endOfLine - beginOfLine));
-                                       beginOfLine = nextBOL;
-                               } while (true);
-                               if (tail.length() == 0) {
-                                       if (beginOfLine < text.length())
-                                               insert(line, text.mid(beginOfLine));
-                               } else {
-                                       if (beginOfLine >= text.length())
-                                               insert(line, tail);
-                                       else
-                                               insert(line, text.mid(beginOfLine) + tail);
-                               }
+                       if (start < text.length())
+                               append(text.mid(start));
+               } else {
+                       while ((end = text.indexOf('\n', start)) >= 0) {
+                               insert(lineNo++, text.mid(start, end - start));
+                               start = end + 1;
                        }
+                       if (start < text.length())
+                               insert(lineNo, text.mid(start));
+               }
+       }
+}
+/**
+ * Inserts a text into a given position of the file.
+ *
+ * @param lineNo       the line number (0..N-1) of the insert position
+ * @param col          the column number (0..M-1) of the insert position<br>
+ *                                     col < 0: no insertion<br>
+ *                                     col > length_of_the_line: the text will be appended to the line
+ * @param text         the text to insert. May not contain newlines!
+ * @param withUndo     <code>true</code>: prepares undo operation<br>
+ *                                     <code>false</code>: undo is impossible
+ */
+void ReLines::insertPart(int lineNo, int col, const QString& text,
+    bool withUndo) {
+       if (lineNo >= 0 && lineNo < lineCount() && col >= 0) {
+               if (withUndo)
+                       storeInsertPart(lineNo, col, text.length());
+               QString current = lineAt(lineNo);
+               if (col == 0)
+                       replace(lineNo, text + current);
+               else if (col < current.length())
+                       replace(lineNo, current.mid(0, col) + text + current.mid(col));
+               else
+                       replace(lineNo, current.mid(0, col) + text);
+       }
+}
+
+/**
+ * Inserts a text into a given position of the file, with or without newlines.
+ *
+ * Note: the line separators will not be stored.
+ *
+ * @param lineNo       the line number (0..N-1) of the insert position
+ * @param col          the column number (0..M-1) of the insert position
+ * @param text         the text to insert. May contain line separators ('\n' or "\r\n").
+ *                                     In this case the number of lines grows
+ */
+void ReLines::insertText(int lineNo, int col, const QString& text) {
+       if (length() == 0)
+               insertLines(0, "", true);
+       int endOfLine = text.indexOf(QChar('\n'));
+       if (endOfLine < 0)
+               insertPart(lineNo, col, text, true);
+       else {
+               splitLine(lineNo, col, true);
+               int newLines = 0;
+               if (lineNo < length())
+                       insertPart(lineNo, col, text.mid(0, endOfLine), true);
+               else
+                       insertLines(lineNo, text.mid(0, endOfLine), true);
+               int lastEoLn = text.lastIndexOf('\n');
+               if (lastEoLn != endOfLine) {
+                       int oldCount = lineCount();
+                       insertLines(lineNo + 1,
+                           text.mid(endOfLine + 1, lastEoLn - endOfLine), true);
+                       newLines = lineCount() - oldCount;
+               }
+               if (lastEoLn != text.length() - 1) {
+                       int nextLine = lineNo + newLines + 1;
+                       if (nextLine < length())
+                               insertPart(nextLine, 0, text.mid(lastEoLn + 1), true);
+                       else
+                               insertLines(nextLine, text.mid(lastEoLn + 1), true);
                }
        }
 }
@@ -140,32 +208,43 @@ bool ReLines::joinLines(int first) {
 /**
  * Removes a part of a line.
  *
- * @param line  the line number (0..N-1) of the first position to delete
- * @param col   -1: join the current line with the previous line.
- *              <line_length>: join the current line with the following
- *              the column number (0..M-1) of the first position to delete
- * @param count the number of character to delete
- * @return             <code>true</code>: a join of 2 lines has been done
+ * @param lineNo       the line number (0..N-1) of the first position to delete
+ * @param col          -1: join the current line with the previous line.
+ *                                     <line_length>: join the current line with the following
+ *                                     the column number (0..M-1) of the first position to delete
+ * @param count                the number of character to delete
+ * @param withUndo     <code>true</code>: prepares undo operation<br>
+ *                                     <code>false</code>: undo is impossible
+ * @return                     <code>true</code>: a join of 2 lines has been done
  */
-bool ReLines::remove(int line, int pos, int count) {
+bool ReLines::removePart(int lineNo, int col, int count, bool withUndo) {
        bool rc = false;
-       if (line >= 0 && line < lineCount()){
-               const QString& current = at(line);
-               if (pos == -1) {
-                       rc = joinLines(line - 1);
-               } else if (pos == current.length()) {
-                       rc = joinLines(line);
-               } else if (pos >= 0) {
+       if (lineNo >= 0 && lineNo < lineCount()) {
+               const QString& current = at(lineNo);
+               if (col == -1) {
+                       if (lineNo > 0) {
+                               if (withUndo)
+                                       storeJoin(lineNo - 1, at(lineNo - 1).length());
+                               rc = joinLines(lineNo - 1);
+                       }
+               } else if (col == current.length()) {
+                       if (withUndo)
+                               storeJoin(lineNo, current.length());
+                       rc = joinLines(lineNo);
+               } else if (col >= 0) {
                        int length = current.length();
-                       if (pos < length - 1) {
-                               if (pos + count > length)
-                                       count = length - pos;
-                               if (pos == 0)
-                                       replace(line, current.mid(count));
-                               else if (pos + count >= length)
-                                       replace(line, current.mid(0, pos));
+                       if (col < length - 1) {
+                               if (col + count > length)
+                                       count = length - col;
+                               if (withUndo)
+                                       storeRemovePart(lineNo, col, current.mid(col, count));
+                               if (col == 0)
+                                       replace(lineNo, current.mid(count));
+                               else if (col + count >= length)
+                                       replace(lineNo, current.mid(0, col));
                                else
-                                       replace(line, current.mid(0, pos) + current.mid(pos + count));
+                                       replace(lineNo,
+                                           current.mid(0, col) + current.mid(col + count));
                        }
                }
        }
@@ -175,45 +254,114 @@ bool ReLines::remove(int line, int pos, int count) {
 /**
  * Removes a given number of lines.
  *
- * @param start the line number (0..N-1) of the first line to remove
- * @param count the number of lines to delete
+ * @param start                the line number (0..N-1) of the first line to remove
+ * @param count                the number of lines to delete
+ * @param withUndo     <code>true</code>: prepares undo operation<br>
+ *                                     <code>false</code>: undo is impossible
  */
-
-void ReLines::removeLines(int start, int count) {
+void ReLines::removeLines(int start, int count, bool withUndo) {
        if (start >= 0 && start < length()) {
                if (start + count > length())
                        count = length() - start;
+               if (withUndo)
+                       storeRemoveLines(start, count, *this);
                for (int ix = start + count - 1; ix >= 0; ix--)
                        removeAt(ix);
        }
 }
 
+/**
+ * Splits a line at a given position into two lines.
+ *
+ * @param lineNo       the line number (0..N-1) of line to split
+ * @param col          the column number (0..M-1) of the split point. The character
+ *                                     of this position will be the first in the next line
+ * @param withUndo     <code>true</code>: prepares undo operation<br>
+ *                                     <code>false</code>: undo is impossible
+ */
+void ReLines::splitLine(int lineNo, int col, bool withUndo) {
+       if (lineNo >= 0 && lineNo < length() && col >= 0) {
+               QString current = at(lineNo);
+               if (withUndo)
+                       storeSplit(lineNo, col);
+               int count = length();
+               if (lineNo >= count - 1) {
+                       if (col >= current.length())
+                               append("");
+                       else
+                               append(current.mid(col));
+               } else {
+                       if (col >= current.length())
+                               insert(lineNo, "");
+                       else
+                               insert(lineNo + 1, current.mid(col));
+               }
+               replace(lineNo, current.mid(0, col));
+       }
+}
+/**
+ * Rewinds the last change operation (insertion/deletion).
+ *
+ * @param lineNo       OUT: the line number of the restored operation
+ * @param col          OUT: the column of the restored operation
+ */
+void ReLines::undo(int& lineNo, int& col) {
+       if (m_list.length() > 0) {
+               UndoItem* item = pop();
+               lineNo = item->m_lineNo;
+               col = item->m_position;
+               switch (item->m_type) {
+               case UIT_INSERT_PART:
+                       removePart(item->m_lineNo, item->m_position, item->m_length, false);
+                       break;
+               case UIT_INSERT_LINES:
+                       removeLines(item->m_lineNo, item->m_length, false);
+                       break;
+               case UIT_SPLIT:
+                       joinLines(item->m_lineNo);
+                       break;
+               case UIT_JOIN:
+                       splitLine(item->m_lineNo, item->m_position, false);
+                       break;
+               case UIT_REMOVE_LINES:
+                       insertLines(item->m_lineNo, item->m_string, false);
+                       break;
+               case UIT_REMOVE_PART:
+                       insertPart(item->m_lineNo, item->m_position, item->m_string, false);
+                       break;
+               default:
+                       break;
+               }
+               delete item;
+       }
+}
+
 /**
  * Constructor.
  *
  * @param filename  name of the file
  */
 ReFile::ReFile(const QString& filename, bool readOnly, ReLogger* logger) :
-               ReLineSource(),
-               ReLines(),
-               m_endOfLine(),
-               m_filename(filename),
-               m_file(filename),
-               m_block(NULL),
-               // in 32-bit address space we allocate only 10 MByte, in 64-bit environments 100 GByte
-               m_blocksize(
-                       sizeof(void*) <= 4 ?
-                               10 * 1024 * 1024ll : 0x100ll * 0x10000 * 0x10000),
-               m_blockOffset(0),
-               m_filesize(0),
-               m_startOfLine(NULL),
-               m_lineLength(0),
-               m_lineOffset(0),
-               m_currentLineNo(0),
-               m_maxLineLength(0x10000),
-               m_content(),
-               m_readOnly(readOnly),
-               m_logger(logger) {
+           ReLineSource(),
+           ReLines(),
+           m_endOfLine(),
+           m_filename(filename),
+           m_file(filename),
+           m_block(NULL),
+           // in 32-bit address space we allocate only 10 MByte, in 64-bit environments 100 GByte
+           m_blocksize(
+               sizeof(void*) <= 4 ?
+                   10 * 1024 * 1024ll : 0x100ll * 0x10000 * 0x10000),
+           m_blockOffset(0),
+           m_filesize(0),
+           m_startOfLine(NULL),
+           m_lineLength(0),
+           m_lineOffset(0),
+           m_currentLineNo(0),
+           m_maxLineLength(0x10000),
+           m_content(),
+           m_readOnly(readOnly),
+           m_logger(logger) {
 #if defined __linux__
        setEndOfLine("\n");
 #elif defined __WIN32__
@@ -247,7 +395,7 @@ int64_t ReFile::blocksize() const {
  * Frees the resources.
  */
 void ReFile::close() {
-       clear();
+       ReFile::clearUndo();
        m_file.close();
 }
 
@@ -262,15 +410,15 @@ QString ReFile::filename() const {
 /**
  * Finds the next line with the string.
  *
- * @param toFind            the string to find    "" or the pattern matching the result
- * @param ignoreCase        true: the search is case insensitive
- * @param lineNo            OUT: 0 or the line number
- * @param line              OUT: "" or the found line
- * @return                  true: a line has been found<br>
- *                          false: a line has not been found
+ * @param toFind               the string to find    "" or the pattern matching the result
+ * @param ignoreCase   true: the search is case insensitive
+ * @param lineNo               OUT: 0 or the line number
+ * @param line                 OUT: "" or the found line
+ * @return                             true: a line has been found<br>
+ *                                             false: a line has not been found
  */
 bool ReFile::findLine(const char* toFind, bool ignoreCase, int& lineNo,
-       QString* line) {
+    QString* line) {
        bool rc = false;
        int length;
        int sourceLength = strlen(toFind);
@@ -281,14 +429,14 @@ bool ReFile::findLine(const char* toFind, bool ignoreCase, int& lineNo,
                const char* ptr = start;
                int restLength = length - sourceLength + 1;
                while (restLength > 0
-                       && (ptr = reinterpret_cast<const char*>(
-                               ignoreCase ?
-                                       memchr(start, first, restLength) :
-                                       memichr(start, first, restLength))) != NULL) {
+                   && (ptr = reinterpret_cast<const char*>(
+                       ignoreCase ?
+                           memchr(start, first, restLength) :
+                           memichr(start, first, restLength))) != NULL) {
                        if ((
-                               ignoreCase ?
-                                       _memicmp(ptr, toFind, sourceLength) :
-                                       memcmp(ptr, toFind, sourceLength)) == 0) {
+                           ignoreCase ?
+                               _memicmp(ptr, toFind, sourceLength) :
+                               memcmp(ptr, toFind, sourceLength)) == 0) {
                                rc = true;
                                lineNo = m_currentLineNo;
                                QByteArray buffer(m_startOfLine, m_lineLength);
@@ -319,8 +467,8 @@ bool ReFile::findLine(const char* toFind, bool ignoreCase, int& lineNo,
  *                          false: a line has not been found
  */
 bool ReFile::findLine(const QString& includePattern, bool includeIsRegExpr,
-       bool includeIgnoreCase, const QString& excludePattern,
-       bool excludeIsRegExpr, bool excludeIgnoreCase, int& lineNo, QString* line) {
+    bool includeIgnoreCase, const QString& excludePattern,
+    bool excludeIsRegExpr, bool excludeIgnoreCase, int& lineNo, QString* line) {
        bool rc = false;
        if (line != NULL)
                *line = "";
@@ -364,9 +512,9 @@ char* ReFile::nextLine(int& length) {
                if (m_currentLineNo == 65639)
                        m_currentLineNo += 0;
                rc = m_startOfLine = remap(m_lineOffset += m_lineLength,
-                       m_maxLineLength, lineLength);
+                   m_maxLineLength, lineLength);
                const char* ptr = reinterpret_cast<const char*>(memchr(rc, '\n',
-                       lineLength));
+                   lineLength));
                if (ptr != NULL)
                        lineLength = ptr - rc + 1;
                length = m_lineLength = lineLength;
@@ -488,7 +636,7 @@ char* ReFile::remap(int64_t offset, int size, int& length) {
                        size = m_filesize - offset;
                // Note: size <= m_blocksize
                if (m_block != NULL && offset >= m_blockOffset
-                       && offset + size <= m_blockOffset + m_blocksize) {
+                   && offset + size <= m_blockOffset + m_blocksize) {
                        // new block is inside the internal block:
                        // no remapping needed
                        rc = m_block + (offset - m_blockOffset);
@@ -503,7 +651,7 @@ char* ReFile::remap(int64_t offset, int size, int& length) {
                        if (m_block != NULL)
                                m_file.unmap(reinterpret_cast<uchar*>(m_block));
                        m_block = reinterpret_cast<char*>(m_file.map(m_blockOffset,
-                               m_blocksize));
+                           m_blocksize));
                        rc = m_block + (offset - m_blockOffset);
                        length = m_blocksize - (rc - m_block);
                        if (length > size)
@@ -554,7 +702,7 @@ void ReFile::setFilename(const QString &filename) {
  * @return              the name of an existing directory
  */
 QByteArray ReFile::tempDir(const char* node, const char* parent,
-       bool withSeparator) {
+    bool withSeparator) {
 #if defined __linux__
        QByteArray temp("/tmp");
        static const char* firstVar = "TMP";
@@ -601,7 +749,7 @@ QByteArray ReFile::tempDir(const char* node, const char* parent,
  * @return                  the full name of a temporary file
  */
 QByteArray ReFile::tempFile(const char* node, const char* parent,
-       bool deleteIfExists) {
+    bool deleteIfExists) {
        QByteArray rc(tempDir(parent));
        if (!rc.endsWith('/'))
                rc += '/';
@@ -615,9 +763,9 @@ QByteArray ReFile::tempFile(const char* node, const char* parent,
 /**
  * Reads the content of the file into the line list.
  *
- * @param filename  the full name of the file. If "" the internal name will be used
- * @return          <code>true</code>success<br>
- *                  <code>false</code>file not readable
+ * @param filename     the full name of the file. If "" the internal name will be used
+ * @return                     <code>true</code>success<br>
+ *                                     <code>false</code>file not readable
  */
 bool ReFile::write(const QString& filename) {
        bool rc = false;
@@ -649,7 +797,7 @@ bool ReFile::write(const QString& filename) {
  * @param mode          file write mode: "w" (write) or "a" (append)
  */
 void ReFile::writeToFile(const char* filename, const char* content,
-       size_t contentLength, const char* mode) {
+    size_t contentLength, const char* mode) {
        FILE* fp = fopen(filename, mode);
        if (fp != NULL) {
                if (contentLength == (size_t) - 1)
@@ -659,3 +807,184 @@ void ReFile::writeToFile(const char* filename, const char* content,
        }
 }
 
+/**
+ * Constructor.
+ */
+ReUndoList::ReUndoList() :
+           m_list(),
+           m_current(NULL),
+           m_lastLine(-1),
+           m_lastPosition(-1),
+           m_maxUndoSize(10 * 1024 * 1024),
+           m_currentUndoSize(0) {
+}
+
+/**
+ * Destructor.
+ */
+ReUndoList::~ReUndoList() {
+       clearUndo();
+}
+
+/**
+ * Frees the resources.
+ */
+void ReUndoList::clearUndo() {
+       for (int ii = 0; ii < m_list.length(); ii++) {
+               delete m_list.at(ii);
+       }
+       m_list.clear();
+}
+
+/**
+ * Returns the current maximum size of the undo information.
+ *
+ * @return     the maximum size of the undo information
+ */
+qint64 ReUndoList::maxUndoSize() const {
+       return m_maxUndoSize;
+}
+/**
+ * Prepares the undo operation of an insertion in a given lineNo.
+ *
+ * @param line the line number of the the first line to insert
+ * @param count        the number of lines to insert
+ */
+void ReUndoList::storeInsertLines(int lineNo, int count) {
+       UndoItem* item = new UndoItem();
+       item->m_type = UIT_INSERT_LINES;
+       item->m_lineNo = lineNo;
+       item->m_position = 0;
+       item->m_length = count;
+       item->m_isPart = false;
+       m_list.append(item);
+       checkSummarySize(0);
+       m_currentUndoSize += (qint64) sizeof(*item);
+}
+
+/**
+ * Prepares the undo operation of an insertion in a given line.
+ *
+ * @param lineNo       the line number of the insertion point
+ * @param col  the index of the insertion point in the line
+ * @param count        the number of chars has been inserted
+ */
+void ReUndoList::storeInsertPart(int lineNo, int col, int count) {
+       UndoItem* item = new UndoItem();
+       item->m_type = UIT_INSERT_PART;
+       item->m_lineNo = lineNo;
+       item->m_position = col;
+       item->m_length = count;
+       item->m_isPart = false;
+       m_list.append(item);
+       checkSummarySize(0);
+       m_currentUndoSize += (qint64) sizeof(*item);
+}
+
+/**
+ * Prepares the undo operation of a join of two lines.
+ *
+ * @param lineNo               the number of the first line to join. The second is line+1
+ * @param length       the length of the first line
+ */
+void ReUndoList::storeJoin(int lineNo, int length) {
+       UndoItem* item = new UndoItem();
+       item->m_type = UIT_JOIN;
+       item->m_lineNo = lineNo;
+       item->m_position = length;
+       item->m_length = 0;
+       item->m_isPart = false;
+       m_list.append(item);
+       checkSummarySize(0);
+       m_currentUndoSize += (qint64) sizeof(*item);
+}
+
+/**
+ * Prepares the undo operation of the deletion of a part of a line.
+ *
+ * @param lineNo               the line number of the deletion point
+ * @param col          the index of the deletion point in the line
+ * @param string       the text which is deleted
+ */
+void ReUndoList::storeRemovePart(int lineNo, int col, const QString& string) {
+       UndoItem* item = new UndoItem();
+       item->m_type = UIT_REMOVE_PART;
+       item->m_lineNo = lineNo;
+       item->m_position = col;
+       item->m_length = string.length();
+       item->m_string = string;
+       item->m_isPart = false;
+       m_list.append(item);
+}
+
+/**
+ * Sets the maximum size of the undo information.
+ *
+ * If the undo info exceeds this value the oldest infos will be removed.
+ *
+ * @param maxUndoSize  the new maximum size of undo information
+ */
+void ReUndoList::setMaxUndoSize(qint64 maxUndoSize) {
+       if (maxUndoSize < (qint64) sizeof(UndoItem) + 1)
+               maxUndoSize = (qint64) sizeof(UndoItem) + 1;
+       m_maxUndoSize = maxUndoSize;
+}
+
+/**
+ * Prepares the undo operation of a line split.
+ *
+ * @param lineNo               the line number of line to split
+ * @param col          the index of the split point. The line will be split behind
+ */
+void ReUndoList::storeSplit(int lineNo, int col) {
+       UndoItem* item = new UndoItem();
+       item->m_type = UIT_SPLIT;
+       item->m_lineNo = lineNo;
+       item->m_position = col;
+       item->m_length = 0;
+       item->m_isPart = false;
+       m_list.append(item);
+       checkSummarySize(0);
+       m_currentUndoSize += (qint64) sizeof(*item);
+}
+
+/**
+ * Prepares the undo operation of some lines.
+ * @param lineNo       the number of the first line to remove
+ * @param count        the number of lines to remove
+ * @param list the list containing the lines for extracting the line content
+ */
+void ReUndoList::storeRemoveLines(int lineNo, int count,
+    const QStringList& list) {
+       qint64 size = 0;
+       // Calculate the additional space
+       for (int ii = lineNo + count - 1; ii >= lineNo; ii--)
+               // +1: the newline
+               size += list.at(ii).length() + 1;
+       //@ToDo: handle more than 2**31 summary length
+       if (checkSummarySize(size)) {
+               assert(size <= INT_MAX);
+               UndoItem* item = new UndoItem();
+               item->m_type = UIT_REMOVE_LINES;
+               item->m_lineNo = lineNo;
+               item->m_position = 0;
+               item->m_length = count;
+               item->m_string.reserve(size);
+               for (int ii = lineNo; ii < lineNo + count; ii++)
+                       item->m_string.append(list.at(ii)).append('\n');
+               m_list.append(item);
+       }
+}
+
+/**
+ * Returns the last element of the undo item and remove it from the list.
+ *
+ * Note: Do not forget to free the returned item!
+ *
+ * @return the last element of the list
+ */
+ReUndoList::UndoItem* ReUndoList::pop() {
+       ReUndoList::UndoItem* item = m_list.last();
+       m_list.removeLast();
+       return item;
+}
index 2f4efcfb99dd7eac6d3ea574fbcb1e782ee325de..5123c68142fb5d0819c606c61d13ef14da5191dd 100644 (file)
 #ifndef REFILE_HPP
 #define REFILE_HPP
 
+class ReUndoList {
+public:
+       enum UndoItemType {
+               UIT_UNDEF,
+               UIT_INSERT_PART,
+               UIT_INSERT_LINES,
+               UIT_JOIN,
+               UIT_REMOVE_PART,
+               UIT_REMOVE_LINES,
+               UIT_SPLIT,
+       };
+
+       class UndoItem {
+       public:
+               UndoItemType m_type;
+               int m_lineNo;
+               int m_position;
+               int m_length;
+               QString m_string;
+               ///@ true: the previous item belongs to this item (transaction)
+               bool m_isPart;
+       };
+
+public:
+       ReUndoList();
+       ~ReUndoList();
+public:
+       bool checkSummarySize(qint64 stringSize);
+       void clearUndo();
+       qint64 maxUndoSize() const;
+       UndoItem* pop();
+       void setMaxUndoSize(qint64 maxUndoSize);
+       void storeInsertPart(int lineNo, int pos, int count);
+       void storeInsertLines(int lineNo, int count);
+       void storeJoin(int lineNo, int length);
+       void storeRemovePart(int lineNo, int pos, const QString& string);
+       void storeRemoveLines(int lineNo, int count, const QStringList& list);
+       void storeSplit(int lineNo, int pos);
+protected:
+       QList<UndoItem*> m_list;
+       UndoItem* m_current;
+       int m_lastLine;
+       int m_lastPosition;
+       qint64 m_maxUndoSize;
+       qint64 m_currentUndoSize;
+};
+
 /**
  * Manages a list of lines.
  *
  * The lines will be stored without line terminators, e.g. '\n'.
  */
-class ReLines: protected QStringList {
+class ReLines: public ReUndoList, protected QStringList {
 public:
        ReLines();
        virtual ~ReLines();
 public:
-       void insertText(int line, int col, const QString& text);
+       void clear();
+       void insertLines(int lineNo, const QString& text, bool withUndo);
+       void insertPart(int lineNo, int col, const QString& text, bool withUndo);
+       void insertText(int lineNo, int col, const QString& text);
        bool joinLines(int first);
        /** Returns a line at a given position.
         * @param index  the index of the line (0..N-1)
@@ -39,8 +89,11 @@ public:
        int lineCount() const {
                return length();
        }
-       virtual bool remove(int line, int pos, int count);
-       virtual void removeLines(int start, int count);
+       virtual bool removePart(int lineNo, int pos, int count, bool withUndo);
+       virtual void removeLines(int start, int count, bool withUndo);
+       void splitLine(int lineNo, int col, bool withUndo);
+       virtual void undo(int& lineNo, int& col);
+
 protected:
        QString m_empty;
 };
@@ -53,7 +106,7 @@ public:
 class ReFile: public ReLineSource, public ReLines {
 public:
        ReFile(const QString& filename, bool readOnly = true, ReLogger* logger =
-               NULL);
+           NULL);
        ~ReFile();
 public:
        int64_t blocksize() const;
@@ -73,11 +126,11 @@ public:
        }
        QString filename() const;
        bool findLine(const char* toFind, bool ignoreCase, int& lineNo,
-               QString* line);
+           QString* line);
        bool findLine(const QString& includePattern, bool includeIsRegExpr,
-               bool includeIgnoreCase, const QString& excludePattern,
-               bool excludeIsRegExpr, bool excludeIgnoreCase, int& lineNo,
-               QString* line);
+           bool includeIgnoreCase, const QString& excludePattern,
+           bool excludeIsRegExpr, bool excludeIgnoreCase, int& lineNo,
+           QString* line);
        virtual int hasMoreLines(int index);
        char* nextLine(int& length);
        char* previousLine(int& length);
@@ -96,12 +149,12 @@ public:
 
 public:
        static QByteArray tempDir(const char* node, const char* parent = NULL,
-               bool withSeparator = true);
+           bool withSeparator = true);
        static QByteArray tempFile(const char* node, const char* parent = NULL,
-               bool deleteIfExists = true);
+           bool deleteIfExists = true);
        static QByteArray& readFromFile(const char* filename, QByteArray& buffer);
        static void writeToFile(const char* filename, const char* content,
-               size_t contentLength = (size_t) - 1, const char* mode = "w");
+           size_t contentLength = (size_t) - 1, const char* mode = "w");
 
 private:
        QByteArray m_endOfLine;
index 349478176ac42c8b33b5518459a0b947bc305868..5773e1bd6b452eb5fef0ec1874b01b9f5388e23e 100644 (file)
@@ -34,6 +34,25 @@ ReString ReQStringUtil::chomp(const ReString& text) {
        return last == text.length() - 1 ? text : text.mid(0, last + 1);
 }
 
+/**
+ * Counts the occurencies of a character in a string.
+ *
+ * @param value                string to inspect
+ * @param toFind       character to find
+ * @param start                first index to search
+ *
+ * @return the count of occurrencies of the character
+ */
+int ReQStringUtil::countOf(const QString& value, QChar toFind, int start) {
+       int rc = 0;
+       if (start >= 0) {
+               for (int ix = start; ix < value.length(); ix++)
+                       if (value.at(ix) == toFind)
+                               rc++;
+       }
+       return rc;
+}
+
 /**
  * Tests whether a given character is the last of the string and append it if not.
  *
index c6135ee9ac34bd485f4cc6b613c5a98955a892fb..2a860c68abe8e16b5f00cadd3f199bba1a00d3a5 100644 (file)
@@ -15,6 +15,7 @@
 class ReQStringUtil {
 public:
        static ReString chomp(const ReString& text);
+       static int countOf(const QString& value, QChar toFind, int start = 0);
        static QString& ensureLastChar(QString& value, QChar lastChar);
        static ReString extensionOf(const ReString& filename);
        static int lengthOfDate(const ReString& text, int start = 0, QDate* value =
index d750ec067a6688663a7f3d5bcdad3f7db63a9570..c89fe5facc996d2862ef46351863ca6c66a747e0 100644 (file)
@@ -132,6 +132,7 @@ public:
                QByteArray fn2(ReFile::tempFile("test2.txt", "cuReFile", true));
                file.write(fn2);
                file.close();
+               file.clear();
                checkEqu(0, file.lineCount());
                ReFile file2(fn2, false);
                checkEqu(3, file2.lineCount());
@@ -189,39 +190,192 @@ public:
                checkEqu("ABCDE", lines.lineAt(2));
 
                // at line start:
-               lines.remove(0, 0, 2);
+               lines.removePart(0, 0, 2, true);
                checkEqu(3, lines.lineCount());
                checkEqu("3", lines.lineAt(0));
                checkEqu("abcdefg", lines.lineAt(1));
                checkEqu("ABCDE", lines.lineAt(2));
 
                // at line end (precisely):
-               lines.remove(1, 5, 2);
+               lines.removePart(1, 5, 2, true);
                checkEqu(3, lines.lineCount());
                checkEqu("3", lines.lineAt(0));
                checkEqu("abcde", lines.lineAt(1));
                checkEqu("ABCDE", lines.lineAt(2));
 
                // at line end (too many chars):
-               lines.remove(1, 3, 99);
+               lines.removePart(1, 3, 99, true);
                checkEqu(3, lines.lineCount());
                checkEqu("3", lines.lineAt(0));
                checkEqu("abc", lines.lineAt(1));
                checkEqu("ABCDE", lines.lineAt(2));
 
                // no remove because of wrong arguments:
-               lines.remove(-1, 3, 1);
-               lines.remove(0, 1, 1);
-               lines.remove(3, 1, 1);
+               lines.removePart(-1, 3, 1, true);
+               checkEqu(3, lines.lineCount());
+               lines.removePart(0, 2, 1, true);
+               checkEqu(3, lines.lineCount());
+               lines.removePart(3, 1, 1, true);
                checkEqu(3, lines.lineCount());
                checkEqu("3", lines.lineAt(0));
                checkEqu("abc", lines.lineAt(1));
                checkEqu("ABCDE", lines.lineAt(2));
 
        }
+       void testRelLinesInsertLines() {
+               ReLines lines;
+               // inserts into an empty instance:
+               lines.insertLines(0, QString("123\nline2-abc\n"), true);
+               checkEqu(2, lines.lineCount());
+               checkEqu("123", lines.lineAt(0));
+               checkEqu("line2-abc", lines.lineAt(1));
+               // inserts at first line, one line:
+               lines.insertLines(0, QString("line-0"), true);
+               checkEqu(3, lines.lineCount());
+               checkEqu("line-0", lines.lineAt(0));
+               checkEqu("123", lines.lineAt(1));
+               checkEqu("line2-abc", lines.lineAt(2));
+               // inserts in the middle, 2 lines, no '\n' at the end
+               lines.insertLines(1, QString("BCDE\nCDEF"), true);
+               checkEqu(5, lines.lineCount());
+               checkEqu("line-0", lines.lineAt(0));
+               checkEqu("BCDE", lines.lineAt(1));
+               checkEqu("CDEF", lines.lineAt(2));
+               checkEqu("123", lines.lineAt(3));
+               checkEqu("line2-abc", lines.lineAt(4));
+               // appends at the end, one line, ending with '\n':
+               lines.insertLines(6, QString("xyz\n"), true);
+               checkEqu(6, lines.lineCount());
+               checkEqu("line-0", lines.lineAt(0));
+               checkEqu("BCDE", lines.lineAt(1));
+               checkEqu("CDEF", lines.lineAt(2));
+               checkEqu("123", lines.lineAt(3));
+               checkEqu("line2-abc", lines.lineAt(4));
+               checkEqu("xyz", lines.lineAt(5));
+
+               // lineno outside:
+               lines.insertLines(-1, QString("bad\n"), true);
+               checkEqu(6, lines.lineCount());
+               lines.insertLines(9999, QString("last\n"), true);
+               checkEqu(7, lines.lineCount());
+               checkEqu("last", lines.lineAt(6));
+       }
+       void testRelLinesInsertPart() {
+               ReLines lines;
+               // inserts into an empty instance:
+               lines.insertLines(0, QString("123\nabc\nA"), true);
+               // first position:
+               lines.insertPart(0, 0, QString("x"), true);
+               checkEqu(3, lines.lineCount());
+               checkEqu("x123", lines.lineAt(0));
+               checkEqu("abc", lines.lineAt(1));
+               checkEqu("A", lines.lineAt(2));
+               // in the middle of the lines, in the middle of the line
+               lines.insertPart(1, 2, QString("YY"), true);
+               checkEqu(3, lines.lineCount());
+               checkEqu("x123", lines.lineAt(0));
+               checkEqu("abYYc", lines.lineAt(1));
+               checkEqu("A", lines.lineAt(2));
+               // in the middle of the lines, at the end of the line
+               lines.insertPart(1, 5, QString("!?!"), true);
+               checkEqu(3, lines.lineCount());
+               checkEqu("x123", lines.lineAt(0));
+               checkEqu("abYYc!?!", lines.lineAt(1));
+               checkEqu("A", lines.lineAt(2));
+               // last line:
+               lines.insertPart(2, 0, QString("xyz"), true);
+               checkEqu(3, lines.lineCount());
+               checkEqu("x123", lines.lineAt(0));
+               checkEqu("abYYc!?!", lines.lineAt(1));
+               checkEqu("xyzA", lines.lineAt(2));
+
+               // outside of the line range:
+               lines.insertPart(-1, 0, QString("wrong"), true);
+               checkEqu(3, lines.lineCount());
+               checkEqu("x123", lines.lineAt(0));
+               checkEqu("abYYc!?!", lines.lineAt(1));
+               checkEqu("xyzA", lines.lineAt(2));
+               lines.insertPart(3, 0, QString("wrong"), true);
+               checkEqu(3, lines.lineCount());
+               checkEqu("x123", lines.lineAt(0));
+               checkEqu("abYYc!?!", lines.lineAt(1));
+               checkEqu("xyzA", lines.lineAt(2));
+
+               // outside of the column range:
+               lines.insertPart(0, -1, QString("wrong"), true);
+               checkEqu(3, lines.lineCount());
+               checkEqu("x123", lines.lineAt(0));
+               checkEqu("abYYc!?!", lines.lineAt(1));
+               checkEqu("xyzA", lines.lineAt(2));
+               lines.insertPart(1, 99, QString("appended"), true);
+               checkEqu(3, lines.lineCount());
+               checkEqu("x123", lines.lineAt(0));
+               checkEqu("abYYc!?!appended", lines.lineAt(1));
+               checkEqu("xyzA", lines.lineAt(2));
+       }
+       void testRelLinesjoinLines() {
+               ReLines lines;
+               // inserts into an empty instance:
+               lines.insertLines(0, QString("123\nabc\nA\nB"), true);
+               checkEqu(4, lines.lineCount());
+
+               // inside the lines:
+               lines.joinLines(1);
+               checkEqu(3, lines.lineCount());
+               checkEqu("123", lines.lineAt(0));
+               checkEqu("abcA", lines.lineAt(1));
+               checkEqu("B", lines.lineAt(2));
+               // the last two lines:
+               lines.joinLines(1);
+               checkEqu(2, lines.lineCount());
+               checkEqu("123", lines.lineAt(0));
+               checkEqu("abcAB", lines.lineAt(1));
+               // the first two lines:
+               lines.joinLines(0);
+               checkEqu(1, lines.lineCount());
+               checkEqu("123abcAB", lines.lineAt(0));
+       }
+       void testReLinesSplitLine() {
+               ReLines lines;
+               // inserts into an empty instance:
+               lines.insertLines(0, QString("123\nabcdefg"), true);
+
+               // in the middle of the line:
+               lines.splitLine(0, 1, true);
+               checkEqu(3, lines.lineCount());
+               checkEqu("1", lines.lineAt(0));
+               checkEqu("23", lines.lineAt(1));
+               checkEqu("abcdefg", lines.lineAt(2));
+               // at the end of the line:
+               lines.splitLine(1, 2, true);
+               checkEqu(4, lines.lineCount());
+               checkEqu("1", lines.lineAt(0));
+               checkEqu("", lines.lineAt(1));
+               checkEqu("23", lines.lineAt(2));
+               checkEqu("abcdefg", lines.lineAt(3));
+
+               lines.clear();
+               lines.insertLines(0, QString("12"), true);
+               // in the middle of the last line:
+               lines.splitLine(0, 1, true);
+               checkEqu(2, lines.lineCount());
+               checkEqu("1", lines.lineAt(0));
+               checkEqu("2", lines.lineAt(1));
+
+               // in the end of the last line:
+               lines.splitLine(1, 99, true);
+               checkEqu(3, lines.lineCount());
+               checkEqu("1", lines.lineAt(0));
+               checkEqu("2", lines.lineAt(1));
+               checkEqu("", lines.lineAt(2));
+       }
 
        virtual void run() {
                testReLinesInsert();
+               testReLinesSplitLine();
+               testRelLinesjoinLines();
+               testRelLinesInsertPart();
+               testRelLinesInsertLines();
                testReLinesRemove();
                testWritableFile();
                testPerformance();
index 15d2d29f82e073d8eb9891557002fb1932b2d714..1aae6fab01214e9577e2dc050e8dd5410de46750 100644 (file)
@@ -22,6 +22,16 @@ public:
        }
 
 public:
+       void testCountOf() {
+               checkEqu(2, ReQStringUtil::countOf(QString("axbx"), 'x'));
+               checkEqu(2, ReQStringUtil::countOf(QString("axbx"), 'x', 1));
+               checkEqu(1, ReQStringUtil::countOf(QString("axbx"), 'x', 2));
+               checkEqu(1, ReQStringUtil::countOf(QString("axbx"), 'x', 3));
+               checkEqu(0, ReQStringUtil::countOf(QString("axbx"), 'x', 4));
+               checkEqu(0, ReQStringUtil::countOf(QString("axbx"), 'x', 5));
+               checkEqu(0, ReQStringUtil::countOf(QString("axbx"), 'x', -1));
+       }
+
        void testLengthOfUInt64() {
                quint64 value = -3;
                checkEqu(1,
index 7ddbaeca850ad461f7599aaf96565f3167474bef..f78617421f2bb63a01797389e37c4d7f25c67e43 100644 (file)
@@ -204,7 +204,8 @@ void ReEdit::assignKeysStandard() {
        m_keyControl[Qt::Key_Delete] = EA_DEL_END_OF_LINE;
        m_keyShift[Qt::Key_Delete] = EA_DEL_BEGIN_OF_LINE;
        m_keyAlt[Qt::Key_Delete] = EA_DEL_LINE;
-
+       m_keyControl[Qt::Key_Z] = EA_UNDO;
+       m_keyControlShift[Qt::Key_Z] = EA_REDO;
 }
 
 /**
@@ -359,6 +360,11 @@ void ReEdit::editorAction(ReEdit::EditorAction action) {
                break;
        case EA_DEL_LINE:
                break;
+       case EA_UNDO:
+               m_lines->undo(m_cursorLine, m_cursorCol);
+               break;
+       case EA_REDO:
+               break;
        default:
                break;
        }
@@ -394,8 +400,8 @@ void ReEdit::keyPressEvent(QKeyEvent* event) {
                switch (key) {
                case Qt::Key_Enter:
                case Qt::Key_Return:
-                       m_lines->insertText(m_cursorLine,
-                               columnToIndex(m_cursorCol) + 1, "\n");
+                       m_lines->splitLine(m_cursorLine,
+                               columnToIndex(m_cursorCol) + 1, true);
                        m_cursorCol = -1;
                        m_cursorLine++;
                        break;
@@ -408,12 +414,12 @@ void ReEdit::keyPressEvent(QKeyEvent* event) {
                                m_cursorCol = indexToColumn(m_lines->lineAt(m_cursorLine - 1)
                                                                                        .length() - 1);
                        }
-                       if (m_lines->remove(m_cursorLine, columnToIndex(currentCol), 1))
+                       if (m_lines->removePart(m_cursorLine, columnToIndex(currentCol), 1, true))
                                m_cursorLine = max(0, m_cursorLine - 1);
                        break;
                }
                case Qt::Key_Delete:
-                       m_lines->remove(m_cursorLine, columnToIndex(m_cursorCol) + 1, 1);
+                       m_lines->removePart(m_cursorLine, columnToIndex(m_cursorCol) + 1, 1, true);
                        break;
                default:
                        m_lines->insertText(m_cursorLine,
index 49e4d0da77b16d66dba9143b90e0191a85f3b4c9..81d6a6cdcc224d0dc7efd1b11ce488fa90b0896c 100644 (file)
@@ -289,6 +289,8 @@ public:
                EA_DEL_END_OF_LINE,
                EA_DEL_BEGIN_OF_LINE,
                EA_DEL_LINE,
+               EA_UNDO,
+               EA_REDO,
        };
 public:
        explicit ReEdit(QWidget *parent = 0);