kde-workspace/kate/part/undo/kateundomanager.cpp
Ivailo Monev f68295ea28 generic: move sub-projects from kde-baseapps [ci reset]
Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2022-05-14 21:56:54 +03:00

439 lines
12 KiB
C++

/* This file is part of the KDE libraries
Copyright (C) 2009-2010 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kateundomanager.h"
#include <ktexteditor/view.h>
#include "katedocument.h"
#include "katemodifiedundo.h"
#include <QBitArray>
KateUndoManager::KateUndoManager (KateDocument *doc)
: QObject (doc)
, m_document (doc)
, m_undoComplexMerge (false)
, m_isActive (true)
, m_editCurrentUndo (0)
, lastUndoGroupWhenSaved(0)
, lastRedoGroupWhenSaved(0)
, docWasSavedWhenUndoWasEmpty(true)
, docWasSavedWhenRedoWasEmpty(true)
{
connect(this, SIGNAL(undoEnd(KTextEditor::Document*)), this, SIGNAL(undoChanged()));
connect(this, SIGNAL(redoEnd(KTextEditor::Document*)), this, SIGNAL(undoChanged()));
connect(doc, SIGNAL(viewCreated(KTextEditor::Document*,KTextEditor::View*)), SLOT(viewCreated(KTextEditor::Document*,KTextEditor::View*)));
}
KateUndoManager::~KateUndoManager()
{
delete m_editCurrentUndo;
// cleanup the undo/redo items, very important, truee :/
qDeleteAll(undoItems);
undoItems.clear();
qDeleteAll(redoItems);
redoItems.clear();
}
KTextEditor::Document *KateUndoManager::document()
{
return m_document;
}
void KateUndoManager::viewCreated (KTextEditor::Document *, KTextEditor::View *newView)
{
connect(newView, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), SLOT(undoCancel()));
}
void KateUndoManager::editStart()
{
if (!m_isActive)
return;
// editStart() and editEnd() must be called in alternating fashion
Q_ASSERT(m_editCurrentUndo == 0); // make sure to enter a clean state
const KTextEditor::Cursor cursorPosition = activeView() ? activeView()->cursorPosition() : KTextEditor::Cursor::invalid();
const KTextEditor::Range selectionRange = activeView() ? activeView()->selectionRange() : KTextEditor::Range::invalid();
// new current undo item
m_editCurrentUndo = new KateUndoGroup(this, cursorPosition, selectionRange);
Q_ASSERT(m_editCurrentUndo != 0); // a new undo group must be created by this method
}
void KateUndoManager::editEnd()
{
if (!m_isActive)
return;
// editStart() and editEnd() must be called in alternating fashion
Q_ASSERT(m_editCurrentUndo != 0); // an undo group must have been created by editStart()
const KTextEditor::Cursor cursorPosition = activeView() ? activeView()->cursorPosition() : KTextEditor::Cursor::invalid();
const KTextEditor::Range selectionRange = activeView() ? activeView()->selectionRange() : KTextEditor::Range::invalid();
m_editCurrentUndo->editEnd(cursorPosition, selectionRange);
bool changedUndo = false;
if (m_editCurrentUndo->isEmpty()) {
delete m_editCurrentUndo;
} else if (!undoItems.isEmpty()
&& undoItems.last()->merge(m_editCurrentUndo, m_undoComplexMerge)) {
delete m_editCurrentUndo;
} else {
undoItems.append(m_editCurrentUndo);
changedUndo = true;
}
m_editCurrentUndo = 0L;
if (changedUndo)
emit undoChanged();
Q_ASSERT(m_editCurrentUndo == 0); // must be 0 after calling this method
}
void KateUndoManager::inputMethodStart()
{
setActive(false);
m_document->editStart();
}
void KateUndoManager::inputMethodEnd()
{
m_document->editEnd();
setActive(true);
}
void KateUndoManager::startUndo()
{
setActive(false);
m_document->editStart();
}
void KateUndoManager::endUndo()
{
m_document->editEnd();
setActive(true);
}
void KateUndoManager::slotTextInserted(int line, int col, const QString &s)
{
if (m_editCurrentUndo != 0) // do we care about notifications?
addUndoItem(new KateModifiedInsertText(m_document, line, col, s));
}
void KateUndoManager::slotTextRemoved(int line, int col, const QString &s)
{
if (m_editCurrentUndo != 0) // do we care about notifications?
addUndoItem(new KateModifiedRemoveText(m_document, line, col, s));
}
void KateUndoManager::slotMarkLineAutoWrapped(int line, bool autowrapped)
{
if (m_editCurrentUndo != 0) // do we care about notifications?
addUndoItem(new KateEditMarkLineAutoWrappedUndo(m_document, line, autowrapped));
}
void KateUndoManager::slotLineWrapped(int line, int col, int length, bool newLine)
{
if (m_editCurrentUndo != 0) // do we care about notifications?
addUndoItem(new KateModifiedWrapLine(m_document, line, col, length, newLine));
}
void KateUndoManager::slotLineUnWrapped(int line, int col, int length, bool lineRemoved)
{
if (m_editCurrentUndo != 0) // do we care about notifications?
addUndoItem(new KateModifiedUnWrapLine(m_document, line, col, length, lineRemoved));
}
void KateUndoManager::slotLineInserted(int line, const QString &s)
{
if (m_editCurrentUndo != 0) // do we care about notifications?
addUndoItem(new KateModifiedInsertLine(m_document, line, s));
}
void KateUndoManager::slotLineRemoved(int line, const QString &s)
{
if (m_editCurrentUndo != 0) // do we care about notifications?
addUndoItem(new KateModifiedRemoveLine(m_document, line, s));
}
void KateUndoManager::undoCancel()
{
// Don't worry about this when an edit is in progress
if (m_document->isEditRunning())
return;
undoSafePoint();
}
void KateUndoManager::undoSafePoint() {
KateUndoGroup *undoGroup = m_editCurrentUndo;
if (undoGroup == 0 && !undoItems.isEmpty())
undoGroup = undoItems.last();
if (undoGroup == 0)
return;
undoGroup->safePoint();
}
void KateUndoManager::addUndoItem(KateUndo *undo)
{
Q_ASSERT(undo != 0); // don't add null pointers to our history
Q_ASSERT(m_editCurrentUndo != 0); // make sure there is an undo group for our item
m_editCurrentUndo->addItem(undo);
// Clear redo buffer
qDeleteAll(redoItems);
redoItems.clear();
}
void KateUndoManager::setActive(bool enabled)
{
Q_ASSERT(m_editCurrentUndo == 0); // must not already be in edit mode
Q_ASSERT(m_isActive != enabled);
m_isActive = enabled;
emit isActiveChanged(enabled);
}
uint KateUndoManager::undoCount () const
{
return undoItems.count ();
}
uint KateUndoManager::redoCount () const
{
return redoItems.count ();
}
void KateUndoManager::undo()
{
Q_ASSERT(m_editCurrentUndo == 0); // undo is not supported while we care about notifications (call editEnd() first)
if (undoItems.count() > 0)
{
emit undoStart(document());
undoItems.last()->undo(activeView());
redoItems.append (undoItems.last());
undoItems.removeLast ();
updateModified();
emit undoEnd(document());
}
}
void KateUndoManager::redo()
{
Q_ASSERT(m_editCurrentUndo == 0); // redo is not supported while we care about notifications (call editEnd() first)
if (redoItems.count() > 0)
{
emit redoStart(document());
redoItems.last()->redo(activeView());
undoItems.append (redoItems.last());
redoItems.removeLast ();
updateModified();
emit redoEnd(document());
}
}
void KateUndoManager::updateModified()
{
/*
How this works:
After noticing that there where to many scenarios to take into
consideration when using 'if's to toggle the "Modified" flag
I came up with this baby, flexible and repetitive calls are
minimal.
A numeric unique pattern is generated by toggling a set of bits,
each bit symbolizes a different state in the Undo Redo structure.
undoItems.isEmpty() != null BIT 1
redoItems.isEmpty() != null BIT 2
docWasSavedWhenUndoWasEmpty == true BIT 3
docWasSavedWhenRedoWasEmpty == true BIT 4
lastUndoGroupWhenSavedIsLastUndo BIT 5
lastUndoGroupWhenSavedIsLastRedo BIT 6
lastRedoGroupWhenSavedIsLastUndo BIT 7
lastRedoGroupWhenSavedIsLastRedo BIT 8
If you find a new pattern, please add it to the patterns array
*/
unsigned char currentPattern = 0;
const unsigned char patterns[] = {5,16,21,24,26,88,90,93,133,144,149,154,165};
const unsigned char patternCount = sizeof(patterns);
KateUndoGroup* undoLast = 0;
KateUndoGroup* redoLast = 0;
if (undoItems.isEmpty())
{
currentPattern |= 1;
}
else
{
undoLast = undoItems.last();
}
if (redoItems.isEmpty())
{
currentPattern |= 2;
}
else
{
redoLast = redoItems.last();
}
if (docWasSavedWhenUndoWasEmpty) currentPattern |= 4;
if (docWasSavedWhenRedoWasEmpty) currentPattern |= 8;
if (lastUndoGroupWhenSaved == undoLast) currentPattern |= 16;
if (lastUndoGroupWhenSaved == redoLast) currentPattern |= 32;
if (lastRedoGroupWhenSaved == undoLast) currentPattern |= 64;
if (lastRedoGroupWhenSaved == redoLast) currentPattern |= 128;
// This will print out the pattern information
kDebug() << "Pattern:" << static_cast<unsigned int>(currentPattern);
for (uint patternIndex = 0; patternIndex < patternCount; ++patternIndex)
{
if ( currentPattern == patterns[patternIndex] )
{
// Note: m_document->setModified() calls KateUndoManager::setModified!
m_document->setModified( false );
// (dominik) whenever the doc is not modified, succeeding edits
// should not be merged
undoSafePoint();
kDebug() << "setting modified to false!";
break;
}
}
}
void KateUndoManager::clearUndo()
{
qDeleteAll(undoItems);
undoItems.clear ();
lastUndoGroupWhenSaved = 0;
docWasSavedWhenUndoWasEmpty = false;
emit undoChanged ();
}
void KateUndoManager::clearRedo()
{
qDeleteAll(redoItems);
redoItems.clear ();
lastRedoGroupWhenSaved = 0;
docWasSavedWhenRedoWasEmpty = false;
emit undoChanged ();
}
void KateUndoManager::setModified(bool modified)
{
if ( !modified )
{
if ( ! undoItems.isEmpty() ) {
lastUndoGroupWhenSaved = undoItems.last();
}
if ( ! redoItems.isEmpty() ) {
lastRedoGroupWhenSaved = redoItems.last();
}
docWasSavedWhenUndoWasEmpty = undoItems.isEmpty();
docWasSavedWhenRedoWasEmpty = redoItems.isEmpty();
}
}
void KateUndoManager::updateLineModifications()
{
// change LineSaved flag of all undo & redo items to LineModified
foreach (KateUndoGroup* undoGroup, undoItems)
undoGroup->flagSavedAsModified();
foreach (KateUndoGroup* undoGroup, redoItems)
undoGroup->flagSavedAsModified();
// iterate all undo/redo items to find out, which item sets the flag LineSaved
QBitArray lines(document()->lines(), false);
for (int i = undoItems.size() - 1; i >= 0; --i) {
undoItems[i]->markRedoAsSaved(lines);
}
lines.fill(false);
for (int i = redoItems.size() - 1; i >= 0; --i) {
redoItems[i]->markUndoAsSaved(lines);
}
}
void KateUndoManager::setUndoRedoCursorsOfLastGroup(const KTextEditor::Cursor undoCursor,
const KTextEditor::Cursor redoCursor)
{
Q_ASSERT(m_editCurrentUndo == 0);
if (undoItems.size()) {
KateUndoGroup * last = undoItems.last();
last->setUndoCursor(undoCursor);
last->setRedoCursor(redoCursor);
}
}
KTextEditor::Cursor KateUndoManager::lastRedoCursor() const
{
Q_ASSERT(m_editCurrentUndo == 0);
if (undoItems.size()) {
KateUndoGroup * last = undoItems.last();
return last->redoCursor();
}
return KTextEditor::Cursor::invalid();
}
void KateUndoManager::updateConfig ()
{
emit undoChanged ();
}
void KateUndoManager::setAllowComplexMerge(bool allow)
{
m_undoComplexMerge = allow;
}
KTextEditor::View* KateUndoManager::activeView()
{
return m_document->activeView();
}
// kate: space-indent on; indent-width 2; replace-tabs on;