889 lines
26 KiB
C++
889 lines
26 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2006-2009 fullmetalcoder <fullmetalcoder@hotmail.fr>
|
|
**
|
|
** This file is part of the Edyuk project <http://edyuk.org>
|
|
**
|
|
** This file may be used under the terms of the GNU General Public License
|
|
** version 3 as published by the Free Software Foundation and appearing in the
|
|
** file GPL.txt included in the packaging of this file.
|
|
**
|
|
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
|
|
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "qdocumentcommand.h"
|
|
|
|
/*!
|
|
\file qdocumentcommand.cpp
|
|
\brief Implementation of the QDocumentCommand class and its basic heirs.
|
|
*/
|
|
|
|
#include "qdocument_p.h"
|
|
#include "qformatscheme.h"
|
|
|
|
/*!
|
|
\ingroup document
|
|
@{
|
|
*/
|
|
|
|
/*!
|
|
\class QDocumentCommand
|
|
\brief The base class for document editing command
|
|
*/
|
|
|
|
QList<QDocumentCursorHandle *> QDocumentCommand::m_autoUpdated;
|
|
|
|
/*!
|
|
\brief ctor
|
|
*/
|
|
QDocumentCommand::QDocumentCommand(Command c, QDocument *d, QDocumentCommand *p)
|
|
: QUndoCommand(p), m_state(false), m_first(true), m_doc(d), m_redoOffset(0),
|
|
m_undoOffset(0), m_silent(false), m_keepAnchor(false), m_command(c),
|
|
m_cursor(0) {}
|
|
|
|
/*!
|
|
\brief dtor
|
|
*/
|
|
QDocumentCommand::~QDocumentCommand() {
|
|
if (m_cursor) {
|
|
// release the handle
|
|
m_cursor->deref();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
\return command identifier
|
|
*/
|
|
int QDocumentCommand::id() const { return m_command; }
|
|
|
|
/*!
|
|
\brief Attempts to merge with another command
|
|
|
|
Command merging is not implemented.
|
|
*/
|
|
bool QDocumentCommand::mergeWith(const QUndoCommand *) { return false; }
|
|
|
|
/*!
|
|
\brief Redo the command
|
|
*/
|
|
void QDocumentCommand::redo() { QUndoCommand::redo(); }
|
|
|
|
/*!
|
|
\brief Undo the command
|
|
*/
|
|
void QDocumentCommand::undo() { QUndoCommand::undo(); }
|
|
|
|
/*!
|
|
\return whether the command is silent
|
|
|
|
Silent command do not update the editing cursor of the host document.
|
|
*/
|
|
bool QDocumentCommand::isSilent() const { return m_silent; }
|
|
|
|
/*!
|
|
\brief Set whether the command is silent
|
|
*/
|
|
void QDocumentCommand::setSilent(bool y) { m_silent = y; }
|
|
|
|
/*!
|
|
\return whether the command preserve selection of the target cursor
|
|
|
|
When this property is true, cursor adjustement upon command execution
|
|
will preserve the anchor of target cursor and only alter its position
|
|
thus keeping a selection.
|
|
|
|
\note This is disabled by default
|
|
*/
|
|
bool QDocumentCommand::keepAnchor() const { return m_keepAnchor; }
|
|
|
|
/*!
|
|
\brief Set whether the command preserve selection of the target cursor
|
|
|
|
\note This is disabled by default
|
|
*/
|
|
void QDocumentCommand::setKeepAnchor(bool y) { m_keepAnchor = y; }
|
|
|
|
/*!
|
|
\brief Set the target cursor
|
|
|
|
The position of the target cursor is update upon undo() and redo()
|
|
*/
|
|
void QDocumentCommand::setTargetCursor(QDocumentCursorHandle *h) {
|
|
if (m_cursor) {
|
|
// release the handle
|
|
m_cursor->deref();
|
|
}
|
|
|
|
m_cursor = h;
|
|
|
|
if (m_cursor) {
|
|
// make sure the handle does not get deleted while the command knows it
|
|
m_cursor->ref();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
\brief ?
|
|
*/
|
|
void QDocumentCommand::setRedoOffset(int off) { m_redoOffset = off; }
|
|
|
|
/*!
|
|
\brief ?
|
|
*/
|
|
void QDocumentCommand::setUndoOffset(int off) { m_undoOffset = off; }
|
|
|
|
/*!
|
|
\brief Insert some text
|
|
\param line target line
|
|
\param pos target text position within line
|
|
\param s text to insert
|
|
|
|
This helper method is provided so that subclasses may actually
|
|
modify the document contents without using private API.
|
|
*/
|
|
void QDocumentCommand::insertText(int line, int pos, const QString &s,
|
|
const QString &sfmtID) {
|
|
if (!m_doc)
|
|
return;
|
|
|
|
QDocumentPrivate *pd = m_doc->impl();
|
|
QDocumentLineHandle *h = pd->m_lines.at(line);
|
|
|
|
if (!h)
|
|
return;
|
|
|
|
h->textBuffer().insert(pos, s);
|
|
h->shiftOverlays(pos, s.length());
|
|
|
|
if (!sfmtID.isEmpty()) {
|
|
auto fmt = m_doc->formatScheme();
|
|
if (fmt) {
|
|
auto id = fmt->id(sfmtID);
|
|
if (id) {
|
|
QFormatRange over;
|
|
over.format = id;
|
|
over.offset = pos;
|
|
over.length = s.length();
|
|
|
|
h->addOverlay(over);
|
|
}
|
|
}
|
|
}
|
|
|
|
pd->adjustWidth(line);
|
|
}
|
|
|
|
/*!
|
|
\brief Remove some text
|
|
\param line target line
|
|
\param pos target text position within line
|
|
\param length length of the text to remove
|
|
|
|
This helper method is provided so that subclasses may actually
|
|
modify the document contents without using private API.
|
|
*/
|
|
void QDocumentCommand::removeText(int line, int pos, int length) {
|
|
if (!m_doc)
|
|
return;
|
|
|
|
QDocumentPrivate *pd = m_doc->impl();
|
|
QDocumentLineHandle *h = pd->m_lines.at(line);
|
|
|
|
if (!h || !length)
|
|
return;
|
|
|
|
h->textBuffer().remove(pos, length);
|
|
h->shiftOverlays(pos, -length);
|
|
|
|
pd->adjustWidth(line);
|
|
}
|
|
|
|
/*!
|
|
\brief Insert some lines in the host document
|
|
\param after where to insert lines (line number)
|
|
\param l list of lines to insert
|
|
|
|
This helper method is provided so that subclasses may actually
|
|
modify the document contents without using too much private API
|
|
(QDocumentLineHandle is part of the private API...)
|
|
*/
|
|
void QDocumentCommand::insertLines(int after,
|
|
const QList<QDocumentLineHandle *> &l) {
|
|
if (l.isEmpty() || !m_doc->impl()->at(after))
|
|
return;
|
|
|
|
m_doc->impl()->insertLines(after, l);
|
|
}
|
|
|
|
void QDocumentCommand::updateCursorsOnInsertion(int line, int column,
|
|
int prefixLength, int numLines,
|
|
int suffixLength) {
|
|
// qDebug("inserting %i lines at (%i, %i) with (%i : %i) bounds", numLines,
|
|
// line, column, prefixLength, suffixLength);
|
|
|
|
foreach (QDocumentCursorHandle *ch, m_autoUpdated) {
|
|
if (ch == m_cursor || ch->document() != m_doc)
|
|
continue;
|
|
|
|
// printf("[[watch:0x%x(%i, %i)]]", ch, ch->m_begLine, ch->m_begOffset);
|
|
|
|
// TODO : better selection handling
|
|
if (ch->hasSelection()) {
|
|
int lbeg = line, cbeg = column, lend = line, cend = column;
|
|
|
|
ch->intersectBoundaries(lbeg, cbeg, lend, cend);
|
|
|
|
if (lbeg == line && cbeg == column) {
|
|
// qDebug("expand (%i, %i : %i, %i)", ch->m_begLine,
|
|
// ch->m_begOffset, ch->m_endLine, ch->m_endOffset);
|
|
if ((ch->m_begLine > ch->m_endLine) ||
|
|
(ch->m_begLine == ch->m_endLine &&
|
|
ch->m_begOffset > ch->m_endOffset)) {
|
|
if (numLines) {
|
|
if (ch->m_begLine == line)
|
|
ch->m_begOffset -= column;
|
|
ch->m_begLine += numLines;
|
|
ch->m_begOffset += suffixLength;
|
|
} else if (ch->m_begLine == line) {
|
|
ch->m_begOffset += prefixLength;
|
|
}
|
|
} else {
|
|
if (numLines) {
|
|
if (ch->m_endLine == line)
|
|
ch->m_endOffset -= column;
|
|
ch->m_endLine += numLines;
|
|
ch->m_endOffset += suffixLength;
|
|
} else if (ch->m_endLine == line) {
|
|
ch->m_endOffset += prefixLength;
|
|
}
|
|
}
|
|
// qDebug("into (%i, %i : %i, %i)", ch->m_begLine,
|
|
// ch->m_begOffset, ch->m_endLine, ch->m_endOffset);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// move
|
|
if (ch->m_begLine > line) {
|
|
ch->m_begLine += numLines;
|
|
} else if (ch->m_begLine == line && ch->m_begOffset >= column) {
|
|
if (numLines) {
|
|
ch->m_begLine += numLines;
|
|
ch->m_begOffset -= column;
|
|
ch->m_begOffset += suffixLength;
|
|
} else {
|
|
ch->m_begOffset += prefixLength;
|
|
}
|
|
}
|
|
|
|
if (ch->m_endLine > line) {
|
|
ch->m_endLine += numLines;
|
|
} else if (ch->m_endLine == line && ch->m_endOffset >= column) {
|
|
if (numLines) {
|
|
ch->m_endLine += numLines;
|
|
ch->m_endOffset -= column;
|
|
ch->m_endOffset += suffixLength;
|
|
} else {
|
|
ch->m_endOffset += prefixLength;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void QDocumentCommand::updateCursorsOnDeletion(int line, int column,
|
|
int prefixLength, int numLines,
|
|
int suffixLength) {
|
|
// qDebug("removing %i lines at (%i, %i) with (%i : %i) bounds", numLines,
|
|
// line, column, prefixLength, suffixLength);
|
|
|
|
foreach (QDocumentCursorHandle *ch, m_autoUpdated) {
|
|
if (ch == m_cursor || ch->document() != m_doc)
|
|
continue;
|
|
|
|
// printf("[[watch:0x%x(%i, %i)]]", ch, ch->m_begLine, ch->m_begOffset);
|
|
|
|
// TODO : better selection handling
|
|
if (ch->hasSelection()) {
|
|
int lbeg = line, cbeg = column, lend = line + numLines,
|
|
cend = numLines ? suffixLength : column + prefixLength;
|
|
|
|
ch->intersectBoundaries(lbeg, cbeg, lend, cend);
|
|
// qDebug("intersection (%i, %i : %i, %i)", lbeg, cbeg, lend, cend);
|
|
|
|
if (lbeg != -1 && cbeg != -1 && lend != -1 && cend != -1) {
|
|
// qDebug("shrink (%i, %i : %i, %i)", ch->m_begLine,
|
|
// ch->m_begOffset, ch->m_endLine, ch->m_endOffset); qDebug("of
|
|
// intersection (%i, %i : %i, %i)", lbeg, cbeg, lend, cend);
|
|
ch->substractBoundaries(lbeg, cbeg, lend, cend);
|
|
// qDebug("into (%i, %i : %i, %i)", ch->m_begLine,
|
|
// ch->m_begOffset, ch->m_endLine, ch->m_endOffset);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// move
|
|
if (ch->m_begLine > line + numLines) {
|
|
ch->m_begLine -= numLines;
|
|
} else if (ch->m_begLine == line + numLines &&
|
|
ch->m_begOffset >= suffixLength) {
|
|
if (numLines) {
|
|
ch->m_begLine -= numLines;
|
|
ch->m_begOffset -= suffixLength;
|
|
ch->m_begOffset += column;
|
|
} else {
|
|
ch->m_begOffset -= prefixLength;
|
|
}
|
|
} else if (ch->m_begLine > line ||
|
|
(ch->m_begLine == line && ch->m_begOffset > column)) {
|
|
// cursor will become invalid in an unrecoverable way...
|
|
}
|
|
|
|
if (ch->m_endLine > line + numLines) {
|
|
ch->m_endLine -= numLines;
|
|
} else if (ch->m_endLine == line + numLines &&
|
|
ch->m_endOffset >= suffixLength) {
|
|
if (numLines) {
|
|
ch->m_endLine -= numLines;
|
|
ch->m_endOffset -= suffixLength;
|
|
ch->m_endOffset += column;
|
|
} else {
|
|
ch->m_endOffset -= prefixLength;
|
|
}
|
|
} else if (ch->m_endLine > line ||
|
|
(ch->m_endLine == line && ch->m_endOffset > column)) {
|
|
// cursor will become invalid in an unrecoverable way...
|
|
// except that it should have intersected in the first place...
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
\brief Remove some lines from the host document
|
|
\param after where to remove lines (line number)
|
|
\param n number of lines to remove
|
|
|
|
This helper method is provided so that subclasses may actually
|
|
modify the document contents without using the private API.
|
|
*/
|
|
void QDocumentCommand::removeLines(int after, int n) {
|
|
if (n <= 0 || !m_doc->impl()->at(after) || !m_doc->impl()->at(after + n))
|
|
return;
|
|
|
|
m_doc->impl()->removeLines(after, n);
|
|
}
|
|
|
|
/*!
|
|
\brief Update the target cursor
|
|
\param l target line
|
|
\param offset target text position within target line
|
|
*/
|
|
void QDocumentCommand::updateTarget(int l, int offset) {
|
|
// QDocumentLineHandle *h = m_doc->impl()->at(l);
|
|
|
|
// update command sender if any
|
|
if (m_cursor) {
|
|
// qDebug("moving cursor [0x%x:beg] from (%i, %i) to line
|
|
//(%i, %i) as updating", m_cursor,
|
|
// m_cursor->m_begLine,
|
|
// m_cursor->m_begOffset, l, offset
|
|
// );
|
|
//
|
|
while (l && (offset < 0)) {
|
|
--l;
|
|
offset += m_doc->line(l).length() + 1;
|
|
}
|
|
|
|
while ((l + 1) < m_doc->lines() && m_doc->line(l).length() < offset) {
|
|
offset -= m_doc->line(l).length() + 1;
|
|
++l;
|
|
}
|
|
|
|
if (!m_keepAnchor) {
|
|
m_cursor->m_endLine = -1;
|
|
m_cursor->m_endOffset = -1;
|
|
} else if (m_cursor->m_endLine == -1) {
|
|
m_cursor->m_endLine = m_cursor->m_begLine;
|
|
m_cursor->m_endOffset = m_cursor->m_begOffset;
|
|
}
|
|
|
|
m_cursor->m_begLine = qMax(0, l);
|
|
m_cursor->m_begOffset = qMax(0, offset);
|
|
|
|
m_cursor->refreshColumnMemory();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
\return whether a given cursor is auto updated
|
|
*/
|
|
bool QDocumentCommand::isAutoUpdated(const QDocumentCursorHandle *h) {
|
|
return m_autoUpdated.contains(const_cast<QDocumentCursorHandle *>(h));
|
|
}
|
|
|
|
/*!
|
|
\brief Enable auto update for a given cursor
|
|
*/
|
|
void QDocumentCommand::enableAutoUpdate(QDocumentCursorHandle *h) {
|
|
// qDebug("up(0x%x)", h);
|
|
|
|
if (!m_autoUpdated.contains(h))
|
|
m_autoUpdated << h;
|
|
}
|
|
|
|
/*!
|
|
\brief Disable auto update for a given cursor
|
|
*/
|
|
void QDocumentCommand::disableAutoUpdate(QDocumentCursorHandle *h) {
|
|
// qDebug("no-up(0x%x)", h);
|
|
m_autoUpdated.removeAll(h);
|
|
}
|
|
|
|
void QDocumentCommand::discardHandlesFromDocument(QDocument *d) {
|
|
int idx = 0;
|
|
|
|
while (idx < m_autoUpdated.count()) {
|
|
if (m_autoUpdated.at(idx)->document() == d)
|
|
m_autoUpdated.removeAt(idx);
|
|
else
|
|
++idx;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
\brief Change the modification status of a line
|
|
*/
|
|
void QDocumentCommand::markRedone(QDocumentLineHandle *h, bool firstTime) {
|
|
QHash<QDocumentLineHandle *, QPair<int, int>>::iterator it =
|
|
m_doc->impl()->m_status.find(h);
|
|
|
|
if (it != m_doc->impl()->m_status.end()) {
|
|
if (firstTime && it->first < it->second)
|
|
it->second = -1;
|
|
|
|
++it->first;
|
|
} else {
|
|
m_doc->impl()->m_status[h] = qMakePair(1, 0);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
\brief Change the modifiaction status of a line
|
|
*/
|
|
void QDocumentCommand::markUndone(QDocumentLineHandle *h) {
|
|
QHash<QDocumentLineHandle *, QPair<int, int>>::iterator it =
|
|
m_doc->impl()->m_status.find(h);
|
|
|
|
if (it != m_doc->impl()->m_status.end()) {
|
|
--it->first;
|
|
} else {
|
|
qDebug("warning: status data and/or undo stack corrupted...");
|
|
m_doc->impl()->m_status[h] = qMakePair(-1, 0);
|
|
}
|
|
}
|
|
|
|
////////////////////////////
|
|
|
|
/*!
|
|
\class QDocumentInsertCommand
|
|
\brief A specialized command to insert text
|
|
*/
|
|
|
|
/*!
|
|
\brief ctor
|
|
\param l target line
|
|
\param offset target text position within target line
|
|
\param text text to insert (can contain line feeds, "\n", which will
|
|
result in the creation of new lines) \param doc host document \param p parent
|
|
command
|
|
*/
|
|
QDocumentInsertCommand::QDocumentInsertCommand(int l, int offset,
|
|
const QString &text,
|
|
QDocument *doc,
|
|
const QString &sfmtID,
|
|
QDocumentCommand *p)
|
|
: QDocumentCommand(Insert, doc, p), m_sfmtID(sfmtID) {
|
|
QStringList lines = text.split(QLatin1Char('\n'), Qt::KeepEmptyParts);
|
|
|
|
if (!m_doc || text.isEmpty())
|
|
qFatal("Invalid insert command");
|
|
|
|
m_data.lineNumber = l;
|
|
m_data.startOffset = offset;
|
|
|
|
m_data.begin = lines.takeAt(0);
|
|
m_data.endOffset = lines.count() ? lines.last().length() : -1;
|
|
|
|
for (auto &s : lines) {
|
|
auto lh = new QDocumentLineHandle(s, m_doc);
|
|
QFormatRange over;
|
|
auto fmt = m_doc->formatScheme();
|
|
auto id = fmt->id(sfmtID);
|
|
if (id) {
|
|
over.format = id;
|
|
over.offset = 0;
|
|
over.length = s.length();
|
|
lh->addOverlay(over);
|
|
}
|
|
m_data.handles << lh;
|
|
}
|
|
|
|
QDocumentLine bl = m_doc->line(l);
|
|
|
|
if (m_data.handles.count() && (bl.length() > offset)) {
|
|
m_data.end = bl.text().mid(offset);
|
|
m_data.handles.last()->textBuffer().append(m_data.end);
|
|
}
|
|
|
|
/*
|
|
if ( (text == "\n") && m_data.handles.isEmpty() )
|
|
qWarning("Go fix it by hand...");
|
|
*/
|
|
}
|
|
|
|
/*!
|
|
\brief dtor
|
|
*/
|
|
QDocumentInsertCommand::~QDocumentInsertCommand() {
|
|
if (m_state)
|
|
return;
|
|
|
|
// foreach ( QDocumentLineHandle *h, m_data.handles )
|
|
// h->deref();
|
|
}
|
|
|
|
bool QDocumentInsertCommand::mergeWith(const QUndoCommand *) { return false; }
|
|
|
|
void QDocumentInsertCommand::redo() {
|
|
// state : handles used by doc
|
|
m_state = true;
|
|
|
|
// QDocumentIterator it = m_doc->impl()->index(m_data.lineNumber);
|
|
|
|
// qDebug("inserting %i lines after %i", m_data.handles.count(),
|
|
// m_data.lineNumber);
|
|
|
|
QDocumentLineHandle *hl = m_doc->impl()->at(m_data.lineNumber);
|
|
|
|
if (m_data.handles.count()) {
|
|
removeText(m_data.lineNumber, m_data.startOffset, m_data.end.length());
|
|
}
|
|
|
|
insertText(m_data.lineNumber, m_data.startOffset, m_data.begin, m_sfmtID);
|
|
|
|
insertLines(m_data.lineNumber, m_data.handles);
|
|
|
|
if (m_data.handles.count()) {
|
|
QDocumentLineHandle *h = m_data.handles.last();
|
|
|
|
// updateTarget(h, h->text().length() - m_data.end.length());
|
|
updateTarget(m_data.lineNumber + m_data.handles.count(),
|
|
h->text().length() - m_data.end.length() + m_redoOffset);
|
|
} else {
|
|
updateTarget(m_data.lineNumber,
|
|
m_data.startOffset + m_data.begin.length() + m_redoOffset);
|
|
}
|
|
|
|
updateCursorsOnInsertion(m_data.lineNumber, m_data.startOffset,
|
|
m_data.begin.length(), m_data.handles.count(),
|
|
m_data.endOffset);
|
|
|
|
m_doc->impl()->emitContentsChange(m_data.lineNumber,
|
|
m_data.handles.count() + 1);
|
|
|
|
markRedone(hl, m_first);
|
|
|
|
foreach (QDocumentLineHandle *h, m_data.handles)
|
|
markRedone(h, m_first);
|
|
|
|
// m_doc->impl()->emitContentsChanged();
|
|
m_first = false;
|
|
}
|
|
|
|
void QDocumentInsertCommand::undo() {
|
|
// state : handles !used by doc
|
|
m_state = false;
|
|
|
|
// QDocumentIterator it = m_doc->impl()->index(m_data.line);
|
|
|
|
QDocumentLineHandle *hl = m_doc->impl()->at(m_data.lineNumber);
|
|
|
|
removeLines(m_data.lineNumber, m_data.handles.count());
|
|
removeText(m_data.lineNumber, m_data.startOffset, m_data.begin.size());
|
|
|
|
if (m_data.handles.count()) {
|
|
insertText(m_data.lineNumber, m_data.startOffset, m_data.end);
|
|
}
|
|
|
|
updateTarget(m_data.lineNumber, m_data.startOffset + m_undoOffset);
|
|
|
|
updateCursorsOnDeletion(m_data.lineNumber, m_data.startOffset,
|
|
m_data.begin.length(), m_data.handles.count(),
|
|
m_data.endOffset);
|
|
|
|
m_doc->impl()->emitContentsChange(m_data.lineNumber,
|
|
m_data.handles.count() + 1);
|
|
|
|
markUndone(hl);
|
|
|
|
foreach (QDocumentLineHandle *h, m_data.handles)
|
|
markUndone(h);
|
|
|
|
// m_doc->impl()->emitContentsChanged();
|
|
}
|
|
|
|
///////////////////////////
|
|
|
|
/*!
|
|
\class QDocumentEraseCommand
|
|
\brief A specialized command to erase text
|
|
*/
|
|
|
|
/*!
|
|
\brief ctor
|
|
\param bl begin line of the target area
|
|
\param bo begin text position of the target area
|
|
\param el end line of the target area
|
|
\param eo end text position of the target area
|
|
\param doc host document
|
|
\param p parent command
|
|
*/
|
|
QDocumentEraseCommand::QDocumentEraseCommand(int bl, int bo, int el, int eo,
|
|
QDocument *doc,
|
|
QDocumentCommand *p)
|
|
: QDocumentCommand(Erase, doc, p) {
|
|
QDocumentLineHandle *start = m_doc->impl()->at(bl),
|
|
*end = m_doc->impl()->at(el);
|
|
|
|
QDocumentConstIterator it = m_doc->impl()->begin() + bl; // index(start);
|
|
|
|
m_data.lineNumber = bl;
|
|
m_data.startOffset = bo;
|
|
|
|
if (start == end) {
|
|
m_data.begin = start->text().mid(bo, eo - bo);
|
|
|
|
m_data.end = QString();
|
|
m_data.endOffset = -1;
|
|
|
|
} else {
|
|
m_data.begin = start->text().mid(bo);
|
|
|
|
m_data.endOffset = eo;
|
|
m_data.end = end->text().mid(eo);
|
|
|
|
do {
|
|
m_data.handles << *(++it);
|
|
} while (*it != end);
|
|
}
|
|
|
|
m_state = true;
|
|
}
|
|
|
|
/*!
|
|
\brief dtor
|
|
*/
|
|
QDocumentEraseCommand::~QDocumentEraseCommand() {
|
|
if (m_state)
|
|
return;
|
|
|
|
// qDeleteAll(m_data.handles);
|
|
}
|
|
|
|
bool QDocumentEraseCommand::mergeWith(const QUndoCommand *) { return false; }
|
|
|
|
void QDocumentEraseCommand::redo() {
|
|
// state : handles !used by doc
|
|
m_state = false;
|
|
|
|
// QDocumentIterator it = m_doc->impl()->index(m_data.line);
|
|
|
|
QDocumentLineHandle *hl = m_doc->impl()->at(m_data.lineNumber);
|
|
|
|
if (m_data.handles.isEmpty()) {
|
|
removeText(m_data.lineNumber, m_data.startOffset,
|
|
m_data.begin.length());
|
|
|
|
m_doc->impl()->emitContentsChange(m_data.lineNumber, 1);
|
|
|
|
} else {
|
|
removeText(m_data.lineNumber, m_data.startOffset,
|
|
m_data.begin.length());
|
|
|
|
if (m_data.endOffset != -1)
|
|
insertText(m_data.lineNumber, m_data.startOffset, m_data.end);
|
|
|
|
removeLines(m_data.lineNumber, m_data.handles.count());
|
|
|
|
m_doc->impl()->emitContentsChange(m_data.lineNumber,
|
|
m_data.handles.count() + 1);
|
|
}
|
|
|
|
updateTarget(m_data.lineNumber, m_data.startOffset + m_redoOffset);
|
|
|
|
updateCursorsOnDeletion(m_data.lineNumber, m_data.startOffset,
|
|
m_data.begin.length(), m_data.handles.count(),
|
|
m_data.endOffset);
|
|
|
|
markRedone(hl, m_first);
|
|
|
|
foreach (QDocumentLineHandle *h, m_data.handles)
|
|
markRedone(h, m_first);
|
|
|
|
// m_doc->impl()->emitContentsChanged();
|
|
m_first = false;
|
|
}
|
|
|
|
void QDocumentEraseCommand::undo() {
|
|
// state : handles used by doc
|
|
m_state = true;
|
|
|
|
// QDocumentIterator it = m_doc->impl()->index(m_data.line);
|
|
|
|
QDocumentLineHandle *hl = m_doc->impl()->at(m_data.lineNumber);
|
|
|
|
if (m_data.handles.count()) {
|
|
insertLines(m_data.lineNumber, m_data.handles);
|
|
|
|
if (m_data.endOffset != -1)
|
|
removeText(m_data.lineNumber, m_data.startOffset,
|
|
m_data.end.size());
|
|
|
|
insertText(m_data.lineNumber, m_data.startOffset, m_data.begin);
|
|
|
|
m_doc->impl()->emitContentsChange(m_data.lineNumber,
|
|
m_data.handles.count() + 1);
|
|
} else {
|
|
|
|
insertText(m_data.lineNumber, m_data.startOffset, m_data.begin);
|
|
|
|
m_doc->impl()->emitContentsChange(m_data.lineNumber, 1);
|
|
}
|
|
|
|
if (m_data.handles.count()) {
|
|
QDocumentLineHandle *h = m_data.handles.last();
|
|
|
|
// updateTarget(h, h->text().length() - m_data.end.length());
|
|
updateTarget(m_data.lineNumber + m_data.handles.count(),
|
|
h->text().length() - m_data.end.length() + m_undoOffset);
|
|
} else {
|
|
updateTarget(m_data.lineNumber,
|
|
m_data.startOffset + m_data.begin.length() + m_undoOffset);
|
|
}
|
|
|
|
updateCursorsOnInsertion(m_data.lineNumber, m_data.startOffset,
|
|
m_data.begin.length(), m_data.handles.count(),
|
|
m_data.endOffset);
|
|
|
|
markUndone(hl);
|
|
|
|
foreach (QDocumentLineHandle *h, m_data.handles)
|
|
markUndone(h);
|
|
|
|
// m_doc->impl()->emitContentsChanged();
|
|
}
|
|
|
|
///////////////////////////
|
|
/*
|
|
QDocumentReplaceCommand::QDocumentReplaceCommand(const QDocumentLine& l, int,
|
|
int, const QString&) : QDocumentCommand(Replace, l.document())
|
|
{
|
|
|
|
}
|
|
|
|
QDocumentReplaceCommand::~QDocumentReplaceCommand()
|
|
{
|
|
|
|
}
|
|
|
|
bool QDocumentReplaceCommand::mergeWith(const QUndoCommand *)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void QDocumentReplaceCommand::redo()
|
|
{
|
|
|
|
}
|
|
|
|
void QDocumentReplaceCommand::undo()
|
|
{
|
|
|
|
}
|
|
*/
|
|
|
|
//////////////////////
|
|
|
|
/*!
|
|
\class QDocumentCommandBlock
|
|
\brief A meta command used for command grouping
|
|
*/
|
|
|
|
/*!
|
|
\brief ctor
|
|
\param d host document
|
|
*/
|
|
QDocumentCommandBlock::QDocumentCommandBlock(QDocument *d)
|
|
: QDocumentCommand(Custom, d), m_weakLocked(false) {}
|
|
|
|
/*!
|
|
\brief dtor
|
|
*/
|
|
QDocumentCommandBlock::~QDocumentCommandBlock() {}
|
|
|
|
void QDocumentCommandBlock::redo() {
|
|
if (isWeakLocked()) {
|
|
setWeakLock(false);
|
|
return;
|
|
}
|
|
|
|
// foreach ( QDocumentCommand *c, m_commands )
|
|
// c->redo();
|
|
|
|
for (int i = 0; i < m_commands.count(); ++i)
|
|
m_commands.at(i)->redo();
|
|
}
|
|
|
|
void QDocumentCommandBlock::undo() {
|
|
// foreach ( QDocumentCommand *c, m_commands )
|
|
// c->undo();
|
|
|
|
for (int i = m_commands.count() - 1; i >= 0; --i)
|
|
m_commands.at(i)->undo();
|
|
}
|
|
|
|
/*!
|
|
\brief Set whether the block is weakly locked
|
|
*/
|
|
void QDocumentCommandBlock::setWeakLock(bool l) { m_weakLocked = l; }
|
|
|
|
/*!
|
|
\return whether the block is weakly locked
|
|
|
|
Weak locking of command block is an obscure internal feature
|
|
which prevents the first redo() call from actually redo'ing
|
|
the grouped commands
|
|
*/
|
|
bool QDocumentCommandBlock::isWeakLocked() const { return m_weakLocked; }
|
|
|
|
/*!
|
|
\brief Add a command to the group
|
|
|
|
\warning Doing that after having pushed the command on the undo/redo
|
|
stack is likely to result in corruption of the undo/redo stack
|
|
*/
|
|
void QDocumentCommandBlock::addCommand(QDocumentCommand *c) { m_commands << c; }
|
|
|
|
/*!
|
|
\brief Remove a command from the block
|
|
|
|
\warning Doing that after having pushed the command on the undo/redo
|
|
stack is likely to result in corruption of the undo/redo stack
|
|
*/
|
|
void QDocumentCommandBlock::removeCommand(QDocumentCommand *c) {
|
|
m_commands.removeAll(c);
|
|
}
|
|
|
|
/*! @} */
|