Commit cbe876c9 authored by Eli MacKenzie's avatar Eli MacKenzie 🇨🇦
Browse files

Changed method of remember/marker line tracking.

Qt 4.7.4 introduces a change to the handling of
QTextBlock::userState that makes it unusable for the purposes of
tracking which block represents a marker or remember line. Previous
versions of Qt also changed or otherwise corrupted the userState
value causing the loss of synchronization with the data held in the
IRCView itself. Blocks are now tracked via the userData field, which
was unmolested in the change made to QTextDocument.

Warning: this commit introduces a large volume of debugging output.

For more information about the Qt change, see QTBUG-20916.

BUG: 210106
CCBUG: 260598
parent 95723641
// This COMMIT number is added to version string to be used as "patch level" // This COMMIT number is added to version string to be used as "patch level"
#ifndef COMMIT #ifndef COMMIT
#define COMMIT 4092 #define COMMIT 4093
#endif #endif
...@@ -42,6 +42,13 @@ ...@@ -42,6 +42,13 @@
#include <KMessageBox> #include <KMessageBox>
#include <KAboutData> #include <KAboutData>
#include <QTextDocument>
#include <QTextBlock>
#include <kdebug.h>
#include "ircview.h"
QDebug operator<<(QDebug d, QTextDocument* document);
namespace Konversation namespace Konversation
{ {
QSet<QString> OutputFilter::m_commands; QSet<QString> OutputFilter::m_commands;
...@@ -1933,6 +1940,16 @@ namespace Konversation ...@@ -1933,6 +1940,16 @@ namespace Konversation
return OutputFilterResult(); return OutputFilterResult();
} }
OutputFilterResult OutputFilter::command_dumpdoc(const OutputFilterInput& input)
{
if (input.context && input.context->getTextView())
{
KDebug::Block myBlock(qPrintable(QString::number((uint)(input.context->getTextView()), 16)));
kDebug() << input.context->getTextView()->document();
}
return OutputFilterResult();
}
OutputFilterResult OutputFilter::changeMode(const QString &parameter, const QString& destination, OutputFilterResult OutputFilter::changeMode(const QString &parameter, const QString& destination,
char mode, char giveTake) char mode, char giveTake)
{ {
......
...@@ -197,6 +197,7 @@ namespace Konversation ...@@ -197,6 +197,7 @@ namespace Konversation
OutputFilterResult command_sayversion(const OutputFilterInput& input); OutputFilterResult command_sayversion(const OutputFilterInput& input);
OutputFilterResult command_cycle(const OutputFilterInput& input); OutputFilterResult command_cycle(const OutputFilterInput& input);
OutputFilterResult command_clear(const OutputFilterInput& input); OutputFilterResult command_clear(const OutputFilterInput& input);
OutputFilterResult command_dumpdoc(const OutputFilterInput& input);
private: private:
static void fillCommandList(); static void fillCommandList();
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
Copyright (C) 2002 Dario Abatianni <eisfuchs@tigress.com> Copyright (C) 2002 Dario Abatianni <eisfuchs@tigress.com>
Copyright (C) 2005-2007 Peter Simonsson <psn@linux.se> Copyright (C) 2005-2007 Peter Simonsson <psn@linux.se>
Copyright (C) 2006-2010 Eike Hein <hein@kde.org> Copyright (C) 2006-2010 Eike Hein <hein@kde.org>
Copyright (C) 2004-2009 Eli Mackenzie <argonel@gmail.com> Copyright (C) 2004-2011 Eli Mackenzie <argonel@gmail.com>
*/ */
#include "ircview.h" #include "ircview.h"
...@@ -61,7 +61,10 @@ class SelectionPin ...@@ -61,7 +61,10 @@ class SelectionPin
if (d->textCursor().hasSelection()) if (d->textCursor().hasSelection())
{ {
int end = d->document()->rootFrame()->lastPosition(); int end = d->document()->rootFrame()->lastPosition();
QTextBlock b = d->document()->lastBlock();
//WARNING if selection pins don't work in some build environments, we need to keep the result
d->document()->lastBlock();
pos = d->textCursor().position(); pos = d->textCursor().position();
anc = d->textCursor().anchor(); anc = d->textCursor().anchor();
if (pos != end && anc != end) if (pos != end && anc != end)
...@@ -82,7 +85,7 @@ class SelectionPin ...@@ -82,7 +85,7 @@ class SelectionPin
}; };
IRCView::IRCView(QWidget* parent) : KTextBrowser(parent), m_nextCullIsMarker(false), m_rememberLinePosition(-1), m_rememberLineDirtyBit(false), markerFormatObject(this) IRCView::IRCView(QWidget* parent) : KTextBrowser(parent), m_rememberLine(0), m_lastMarkerLine(0), m_rememberLineDirtyBit(false), markerFormatObject(this)
{ {
m_mousePressedOnUrl = false; m_mousePressedOnUrl = false;
m_isOnNick = false; m_isOnNick = false;
...@@ -191,34 +194,6 @@ bool IRCView::searchNext(bool reversed) ...@@ -191,34 +194,6 @@ bool IRCView::searchNext(bool reversed)
return find(m_pattern, m_searchFlags); return find(m_pattern, m_searchFlags);
} }
// Marker lines
#define _S(x) #x << (x)
void dump_doc(QTextDocument* document)
{
QTextBlock b(document->firstBlock());
while (b.isValid())
{
kDebug() << _S(b.position())
<< _S(b.length())
<< _S(b.userState())
;
b=b.next();
};
}
QDebug operator<<(QDebug dbg, QList<QTextBlock> &l)
{
dbg.space() << _S(l.count()) << endl;
for (int i=0; i< l.count(); ++i)
{
QTextBlock b=l[i];
dbg.space() << _S(i) << _S(b.blockNumber()) << _S(b.length()) << _S(b.userState()) << endl;
}
return dbg.space();
}
class IrcViewMimeData : public QMimeData class IrcViewMimeData : public QMimeData
{ {
public: public:
...@@ -283,13 +258,74 @@ void IRCView::dropEvent(QDropEvent* e) ...@@ -283,13 +258,74 @@ void IRCView::dropEvent(QDropEvent* e)
emit urlsDropped(KUrl::List::fromMimeData(e->mimeData(), KUrl::List::PreferLocalUrls)); emit urlsDropped(KUrl::List::fromMimeData(e->mimeData(), KUrl::List::PreferLocalUrls));
} }
// Marker lines
#define _S(x) #x << (x)
#define DebugBanner KDebug::Block myBlock(qPrintable(QString::number((uint)this, 16)))
QDebug operator<<(QDebug dbg, QTextBlockUserData *bd);
QDebug operator<<(QDebug d, QTextFrame* feed);
QDebug operator<<(QDebug d, QTextDocument* document);
QDebug operator<<(QDebug d, QTextBlock b);
// This object gets stuffed into the userData field of a text block.
// Qt does not give us a way to track blocks, so we have to
// rely on the destructor of this object to notify us that a
// block we care about was removed from the document. This does not
// prevent the first block bug from deleting the wrong block's data,
// however that should not result in a crash.
struct Burr: public QTextBlockUserData
{
Burr(IRCView* o, Burr* prev, QTextBlock b, int objFormat)
: m_block(b), m_format(objFormat), m_prev(prev), m_next(0),
m_owner(o)
{
if (m_prev)
m_prev->m_next = this;
}
~Burr()
{
DebugBanner;
kDebug() << "~Burr" << (void*)this << _S(m_format) << _S(m_block.blockNumber()) << "deleted";
m_owner->blockDeleted(this);
unlink();
}
void unlink()
{
kDebug() << "====" << (void*)this << m_block;
if (m_prev)
{
kDebug() << "prev->next" << (void*)m_next;
m_prev->m_next = m_next;
}
else
kDebug() << "prev was null";
if (m_next)
{
kDebug() << "next";
m_next->m_prev = m_prev;
}
else
kDebug() << "next was null";
}
QTextBlock m_block;
int m_format;
Burr* m_prev, *m_next;
IRCView* m_owner;
};
void IrcViewMarkerLine::drawObject(QPainter *painter, const QRectF &r, QTextDocument *doc, int posInDocument, const QTextFormat &format) void IrcViewMarkerLine::drawObject(QPainter *painter, const QRectF &r, QTextDocument *doc, int posInDocument, const QTextFormat &format)
{ {
Q_UNUSED(format); Q_UNUSED(format);
QTextBlock block=doc->findBlock(posInDocument); QTextBlock block=doc->findBlock(posInDocument);
QPen pen; QPen pen;
switch (block.userState()) Burr* b = dynamic_cast<Burr*>(block.userData());
Q_ASSERT(b); // remember kids, only YOU can makes this document support two user data types
switch (b->m_format)
{ {
case IRCView::BlockIsMarker: case IRCView::BlockIsMarker:
pen.setColor(Preferences::self()->color(Preferences::ActionMessage)); pen.setColor(Preferences::self()->color(Preferences::ActionMessage));
...@@ -322,35 +358,88 @@ QSizeF IrcViewMarkerLine::intrinsicSize(QTextDocument *doc, int posInDocument, c ...@@ -322,35 +358,88 @@ QSizeF IrcViewMarkerLine::intrinsicSize(QTextDocument *doc, int posInDocument, c
return QSizeF(width, 6); // FIXME this is a hardcoded value... return QSizeF(width, 6); // FIXME this is a hardcoded value...
} }
QTextCharFormat IRCView::getFormat(ObjectFormats x)
{
QTextCharFormat f;
f.setObjectType(x);
return f;
}
void IRCView::blockDeleted(Burr* b) //slot
{
DebugBanner;
Q_ASSERT(b); // this method only to be called from a ~Burr();
kDebug() << "blockDeleted" << (void*)(b) << b->m_block << _S(m_lastMarkerLine) << _S(m_rememberLine);
//tracking only the tail
if (b == m_lastMarkerLine)
m_lastMarkerLine = b->m_prev;
if (b == m_rememberLine)
m_rememberLine = 0;
}
void IRCView::cullMarkedLine(int where, int rem, int add) //slot void IRCView::cullMarkedLine(int where, int rem, int add) //slot
{ {
if (where == 0 && add == 0 && rem !=0) bool showDebug = false;
QString output;
QDebug d = QDebug(&output);//KDebug(QtDebugMsg, __FILE__, __LINE__, Q_FUNC_INFO)();
bool merged = (add!=0 && rem !=0); // i have never seen this happen, adds and removes are always separate
int blockCount = document()->blockCount();
void *view = this;
QTextBlock prime = document()->firstBlock();
d << "================= cullMarkedLine" << _S(view) << _S(where) << _S(rem) << _S(add) << _S(blockCount) << _S(prime.length()) << _S(merged);
if (prime.length() == 1)
{ {
if (document()->blockCount() == 1 && document()->firstBlock().length() == 1) if (document()->blockCount() == 1) //the entire document was wiped. was a signal such a burden? apparently..
{ {
showDebug = true;
d << "- wipeLineParagraphs()" << (void*)m_rememberLine << (void*)m_lastMarkerLine;
wipeLineParagraphs(); wipeLineParagraphs();
} }
else else if (document()->characterAt(0).unicode() == 0x2029)
{ {
if (m_nextCullIsMarker) showDebug = true;
d << "- only QChar::ParagraphSeparator";
// this should never happen, it should be 0xfffc2029
if (dynamic_cast<Burr*>(prime.userData()))
d << "Burr!" << prime.userData();
}
else
{ {
//move the remember line up.. if the cull removed it, this will forget its position showDebug = true;
if (m_rememberLinePosition >= 0) QString fc = "0x" + QString::number(document()->characterAt(0).unicode(), 16).rightJustified(4, '0');
--m_rememberLinePosition; d << "- block of length 1 but not 2029" << qPrintable(fc);
m_markers.takeFirst(); }
} }
int s = document()->firstBlock().userState(); else if (prime.length() == 2)
m_nextCullIsMarker = (s == BlockIsMarker || s == BlockIsRemember); {
//probably a Burr going to be culled next..
showDebug = true;
QString fc = "0x" + QString::number(document()->characterAt(0).unicode(), 16).rightJustified(4, '0');
QString sc = "0x" + QString::number(document()->characterAt(1).unicode(), 16).rightJustified(4, '0');
d << "- prime(2)" << fc << sc;
} }
if (showDebug)
{
DebugBanner;
kDebug() << output;
} }
} }
void IRCView::insertMarkerLine() //slot void IRCView::insertMarkerLine() //slot
{ {
DebugBanner;
//if the last line is already a marker of any kind, skip out //if the last line is already a marker of any kind, skip out
if (lastBlockIsLine()) if (lastBlockIsLine(BlockIsMarker))
return; return;
kDebug() << "ok to insert..";
//the code used to preserve the dirty bit status, but that was never affected by appendLine... //the code used to preserve the dirty bit status, but that was never affected by appendLine...
//maybe i missed something //maybe i missed something
appendLine(IRCView::MarkerLine); appendLine(IRCView::MarkerLine);
...@@ -358,10 +447,15 @@ void IRCView::insertMarkerLine() //slot ...@@ -358,10 +447,15 @@ void IRCView::insertMarkerLine() //slot
void IRCView::insertRememberLine() //slot void IRCView::insertRememberLine() //slot
{ {
DebugBanner;
m_rememberLineDirtyBit = true; // means we're going to append a remember line if some text gets inserted m_rememberLineDirtyBit = true; // means we're going to append a remember line if some text gets inserted
if (!Preferences::self()->automaticRememberLineOnlyOnTextChange()) if (!Preferences::self()->automaticRememberLineOnlyOnTextChange())
{
kDebug();
appendRememberLine(); appendRememberLine();
}
} }
void IRCView::cancelRememberLine() //slot void IRCView::cancelRememberLine() //slot
...@@ -371,7 +465,14 @@ void IRCView::cancelRememberLine() //slot ...@@ -371,7 +465,14 @@ void IRCView::cancelRememberLine() //slot
bool IRCView::lastBlockIsLine(int select) bool IRCView::lastBlockIsLine(int select)
{ {
int state = document()->lastBlock().userState(); Burr *b = dynamic_cast<Burr*>(document()->lastBlock().userData());
int state = -1;
if (b)
state = b->m_format;
kDebug() << _S(state) << _S(select);
if (select == -1) if (select == -1)
return (state == BlockIsRemember || state == BlockIsMarker); return (state == BlockIsRemember || state == BlockIsMarker);
...@@ -381,6 +482,8 @@ bool IRCView::lastBlockIsLine(int select) ...@@ -381,6 +482,8 @@ bool IRCView::lastBlockIsLine(int select)
void IRCView::appendRememberLine() void IRCView::appendRememberLine()
{ {
DebugBanner;
//clear this now, so that doAppend doesn't double insert //clear this now, so that doAppend doesn't double insert
m_rememberLineDirtyBit = false; m_rememberLineDirtyBit = false;
...@@ -388,95 +491,77 @@ void IRCView::appendRememberLine() ...@@ -388,95 +491,77 @@ void IRCView::appendRememberLine()
if (lastBlockIsLine(BlockIsRemember)) if (lastBlockIsLine(BlockIsRemember))
return; return;
// if we already have a rememberline, remove the previous one if (m_rememberLine)
if (m_rememberLinePosition > -1)
{ {
//get the block that is the remember line QTextBlock rem = m_rememberLine->m_block;
QTextBlock rem = m_markers[m_rememberLinePosition];
m_markers.removeAt(m_rememberLinePosition); //probably will be in there only once
m_rememberLinePosition=-1;
voidLineBlock(rem); voidLineBlock(rem);
Q_ASSERT(m_rememberLine == 0);
} }
//tell the control we did stuff m_rememberLine = appendLine(IRCView::RememberLine);
//FIXME do we still do something like this?
//repaintChanged();
//actually insert a line
appendLine(IRCView::RememberLine);
//store the index of the remember line kDebug() << (void*)m_rememberLine;
m_rememberLinePosition = m_markers.count() - 1;
} }
void IRCView::voidLineBlock(QTextBlock rem) void IRCView::voidLineBlock(QTextBlock rem)
{ {
if (rem.blockNumber() == 0)
{
Q_ASSERT(m_nextCullIsMarker);
m_nextCullIsMarker = false;
}
QTextCursor c(rem); QTextCursor c(rem);
//FIXME make sure this doesn't flicker
c.select(QTextCursor::BlockUnderCursor); c.select(QTextCursor::BlockUnderCursor);
c.removeSelectedText(); c.removeSelectedText();
} }
void IRCView::clearLines() void IRCView::clearLines()
{ {
//if we have a remember line, put it in the list DebugBanner;
//its already in the list kDebug() << document();
kDebug() << _S(m_nextCullIsMarker) << _S(m_rememberLinePosition) << _S(textCursor().position()) << m_markers;
dump_doc(document());
//are there any markers? while (hasLines())
if (hasLines())
{ {
for (int i=0; i < m_markers.count(); ++i) //IRCView::blockDeleted takes care of the pointers
voidLineBlock(m_markers[i]); voidLineBlock(m_lastMarkerLine->m_block);
};
wipeLineParagraphs();
//FIXME do we have this? //repaintChanged();
}
} }
void IRCView::wipeLineParagraphs() void IRCView::wipeLineParagraphs()
{ {
m_nextCullIsMarker = false; m_rememberLine = m_lastMarkerLine = 0;
m_rememberLinePosition = -1;
m_markers.clear();
} }
bool IRCView::hasLines() bool IRCView::hasLines()
{ {
return m_markers.count() > 0; DebugBanner;
}
QTextCharFormat IRCView::getFormat(ObjectFormats x) kDebug() << (void*)m_lastMarkerLine << (void*)m_rememberLine;
{
QTextCharFormat f; return m_lastMarkerLine != 0;
f.setObjectType(x);
return f;
} }
void IRCView::appendLine(IRCView::ObjectFormats type) Burr* IRCView::appendLine(IRCView::ObjectFormats type)
{ {
DebugBanner;
ScrollBarPin barpin(verticalScrollBar()); ScrollBarPin barpin(verticalScrollBar());
SelectionPin selpin(this); SelectionPin selpin(this);
QTextCursor cursor(document()); QTextCursor cursor(document());
cursor.movePosition(QTextCursor::End); cursor.movePosition(QTextCursor::End);
cursor.insertBlock(); cursor.insertBlock();
cursor.insertText(QString(QChar::ObjectReplacementCharacter), getFormat(type)); cursor.insertText(QString(QChar::ObjectReplacementCharacter), getFormat(type));
cursor.block().setUserState(type == MarkerLine? BlockIsMarker : BlockIsRemember);
m_markers.append(cursor.block()); QTextBlock block = cursor.block();
} Burr *b = new Burr(this, m_lastMarkerLine, block, type == MarkerLine? BlockIsMarker : BlockIsRemember);
block.setUserData(b);
m_lastMarkerLine = b;
//TODO figure out what this is for
cursor.setPosition(block.position());
kDebug() << block;
return b;
}
// Other stuff // Other stuff
...@@ -2178,3 +2263,101 @@ QChar::Direction IRCView::basicDirection(const QString& string) ...@@ -2178,3 +2263,101 @@ QChar::Direction IRCView::basicDirection(const QString& string)
else else
return QChar::DirL; return QChar::DirL;
} }
#define dS d.space()
#define dN d.nospace()
QDebug operator<<(QDebug d, QTextBlockUserData *bd)
{
Burr* b = dynamic_cast<Burr*>(bd);
if (b)
{
dN;
d << "(";
d << (void*)(b) << ", format=" << b->m_format << ", blockNumber=" << b->m_block.blockNumber() << " p,n=" << (void*)b->m_prev << ", " << (void*)b->m_next;
d << ")";
}
else if (bd)
dN << "(UNKNOWN! " << (void*)bd << ")";
else
d << "(none)";
return d.space();
}
QDebug operator<<(QDebug d, QTextFrame* feed)
{
if (feed)
{
d << "\nDumping frame...";
dN << hex << (void*)feed << dec;
QTextFrame::iterator it = feed->begin();
if (it.currentFrame() == feed)
dS << "loop!" << endl;
dS << "position" << feed->firstPosition() << feed->lastPosition();
dN << "parentFrame=" << (void*)feed->parentFrame();
dS;
while (!it.atEnd())
{
//d << "spin";
QTextFrame *frame = it.currentFrame();
if (!frame) // this is a block
{
//d<<"dumping blocks:";
QTextBlock b = it.currentBlock();
//d << "block" << b.position() << b.length();
d << endl << b;
}
else if (frame != feed)
{
d << frame;
}
++it;