From: Manuel Nickschas Date: Wed, 29 Jul 2009 15:31:05 +0000 (+0200) Subject: Remove the word boundary cache X-Git-Tag: 0.5-rc1~114 X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=commitdiff_plain;h=5199330f0b249b20c27cd372d995909f97433786 Remove the word boundary cache This removes the pre-calculation of word boundaries, which we used to speed up chatline height computation. This should also fix the wordwrap issues we had with some fonts. --- diff --git a/src/qtui/chatitem.cpp b/src/qtui/chatitem.cpp index 2297983b..2159f3f3 100644 --- a/src/qtui/chatitem.cpp +++ b/src/qtui/chatitem.cpp @@ -89,11 +89,15 @@ void ChatItem::initLayoutHelper(QTextLayout *layout, QTextOption::WrapMode wrapM } void ChatItem::doLayout(QTextLayout *layout) const { + qreal h = 0; layout->beginLayout(); - QTextLine line = layout->createLine(); - if(line.isValid()) { + forever { + QTextLine line = layout->createLine(); + if(!line.isValid()) + break; line.setLineWidth(width()); - line.setPosition(QPointF(0,0)); + line.setPosition(QPointF(0, h)); + h += line.height(); } layout->endLayout(); } @@ -390,38 +394,15 @@ qreal ContentsChatItem::setGeometryByWidth(qreal w) { if(w != width()) { prepareGeometryChange(); setWidth(w); - // compute height - int lines = 1; - WrapColumnFinder finder(this); - while(finder.nextWrapColumn() > 0) - lines++; - setHeight(lines * fontMetrics()->lineSpacing()); + QTextLayout layout; + initLayout(&layout); + setHeight(layout.boundingRect().height()); delete _data; _data = 0; } return height(); } -void ContentsChatItem::doLayout(QTextLayout *layout) const { - ChatLineModel::WrapList wrapList = data(ChatLineModel::WrapListRole).value(); - if(!wrapList.count()) return; // empty chatitem - - qreal h = 0; - WrapColumnFinder finder(this); - layout->beginLayout(); - forever { - QTextLine line = layout->createLine(); - if(!line.isValid()) - break; - - int col = finder.nextWrapColumn(); - line.setNumColumns(col >= 0 ? col - line.textStart() : layout->text().length()); - line.setPosition(QPointF(0, h)); - h += fontMetrics()->lineSpacing(); - } - layout->endLayout(); -} - // NOTE: This method is not threadsafe and not reentrant! // (RegExps are not constant while matching, and they are static here for efficiency) QList ContentsChatItem::findClickables() const { @@ -684,66 +665,3 @@ void ContentsChatItem::clearWebPreview() { /*************************************************************************************************/ -ContentsChatItem::WrapColumnFinder::WrapColumnFinder(const ChatItem *_item) - : item(_item), - wrapList(item->data(ChatLineModel::WrapListRole).value()), - wordidx(0), - lineCount(0), - choppedTrailing(0) -{ -} - -ContentsChatItem::WrapColumnFinder::~WrapColumnFinder() { -} - -qint16 ContentsChatItem::WrapColumnFinder::nextWrapColumn() { - if(wordidx >= wrapList.count()) - return -1; - - lineCount++; - qreal targetWidth = lineCount * item->width() + choppedTrailing; - - qint16 start = wordidx; - qint16 end = wrapList.count() - 1; - - // check if the whole line fits - if(wrapList.at(end).endX <= targetWidth) // || start == end) - return -1; - - // check if we have a very long word that needs inter word wrap - if(wrapList.at(start).endX > targetWidth) { - if(!line.isValid()) { - item->initLayoutHelper(&layout, QTextOption::NoWrap); - layout.beginLayout(); - line = layout.createLine(); - layout.endLayout(); - } - return line.xToCursor(targetWidth, QTextLine::CursorOnCharacter); - } - - while(true) { - if(start + 1 == end) { - wordidx = end; - const ChatLineModel::Word &lastWord = wrapList.at(start); // the last word we were able to squeeze in - - // both cases should be cought preliminary - Q_ASSERT(lastWord.endX <= targetWidth); // ensure that "start" really fits in - Q_ASSERT(end < wrapList.count()); // ensure that start isn't the last word - - choppedTrailing += lastWord.trailing - (targetWidth - lastWord.endX); - return wrapList.at(wordidx).start; - } - - qint16 pivot = (end + start) / 2; - if(wrapList.at(pivot).endX > targetWidth) { - end = pivot; - } else { - start = pivot; - } - } - Q_ASSERT(false); - return -1; -} - -/*************************************************************************************************/ - diff --git a/src/qtui/chatitem.h b/src/qtui/chatitem.h index efd0705c..cc37dac7 100644 --- a/src/qtui/chatitem.h +++ b/src/qtui/chatitem.h @@ -186,15 +186,13 @@ protected: virtual QVector additionalFormats() const; virtual inline void initLayout(QTextLayout *layout) const { - initLayoutHelper(layout, QTextOption::WrapAnywhere); + initLayoutHelper(layout, QTextOption::WrapAtWordBoundaryOrAnywhere); doLayout(layout); } - virtual void doLayout(QTextLayout *layout) const; private: struct Clickable; class ActionProxy; - class WrapColumnFinder; ContentsChatItemPrivate *_data; ContentsChatItemPrivate *privateData() const; @@ -243,23 +241,6 @@ struct ContentsChatItemPrivate { ContentsChatItemPrivate(const QList &c, ContentsChatItem *parent) : contentsItem(parent), clickables(c) {} }; -class ContentsChatItem::WrapColumnFinder { -public: - WrapColumnFinder(const ChatItem *parent); - ~WrapColumnFinder(); - - qint16 nextWrapColumn(); - -private: - const ChatItem *item; - QTextLayout layout; - QTextLine line; - ChatLineModel::WrapList wrapList; - qint16 wordidx; - qint16 lineCount; - qreal choppedTrailing; -}; - //! Acts as a proxy for Action signals targetted at a ContentsChatItem /** Since a ChatItem is not a QObject, hence cannot receive signals, we use a static ActionProxy * as a receiver instead. This avoids having to handle ChatItem actions (e.g. context menu entries) diff --git a/src/qtui/chatlinemodel.cpp b/src/qtui/chatlinemodel.cpp index 72af95c1..a1cbbc34 100644 --- a/src/qtui/chatlinemodel.cpp +++ b/src/qtui/chatlinemodel.cpp @@ -23,8 +23,7 @@ ChatLineModel::ChatLineModel(QObject *parent) : MessageModel(parent) { - qRegisterMetaType("ChatLineModel::WrapList"); - qRegisterMetaTypeStreamOperators("ChatLineModel::WrapList"); + } // MessageModelItem *ChatLineModel::createMessageModelItem(const Message &msg) { @@ -43,23 +42,3 @@ Message ChatLineModel::takeMessageAt(int i) { _messageList.removeAt(i); return msg; } - -QDataStream &operator<<(QDataStream &out, const ChatLineModel::WrapList wplist) { - out << wplist.count(); - ChatLineModel::WrapList::const_iterator it = wplist.begin(); - while(it != wplist.end()) { - out << (*it).start << (*it).width << (*it).trailing; - ++it; - } - return out; -} - -QDataStream &operator>>(QDataStream &in, ChatLineModel::WrapList &wplist) { - quint16 cnt; - in >> cnt; - wplist.resize(cnt); - for(quint16 i = 0; i < cnt; i++) { - in >> wplist[i].start >> wplist[i].width >> wplist[i].trailing; - } - return in; -} diff --git a/src/qtui/chatlinemodel.h b/src/qtui/chatlinemodel.h index 74f0aceb..c21d1c3b 100644 --- a/src/qtui/chatlinemodel.h +++ b/src/qtui/chatlinemodel.h @@ -31,16 +31,12 @@ class ChatLineModel : public MessageModel { public: enum ChatLineRole { - WrapListRole = MessageModel::UserRole, - MsgLabelRole, + MsgLabelRole = MessageModel::UserRole, SelectedBackgroundRole }; ChatLineModel(QObject *parent = 0); - typedef ChatLineModelItem::Word Word; - typedef ChatLineModelItem::WrapList WrapList; - protected: // virtual MessageModelItem *createMessageModelItem(const Message &); @@ -62,10 +58,5 @@ private: QList _messageList; }; -QDataStream &operator<<(QDataStream &out, const ChatLineModel::WrapList); -QDataStream &operator>>(QDataStream &in, ChatLineModel::WrapList &); - -Q_DECLARE_METATYPE(ChatLineModel::WrapList) - #endif diff --git a/src/qtui/chatlinemodelitem.cpp b/src/qtui/chatlinemodelitem.cpp index 343fb992..187ba33a 100644 --- a/src/qtui/chatlinemodelitem.cpp +++ b/src/qtui/chatlinemodelitem.cpp @@ -18,29 +18,11 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ -#include -#include - #include "chatlinemodelitem.h" #include "chatlinemodel.h" #include "qtui.h" #include "qtuistyle.h" -// This Struct is taken from Harfbuzz. We use it only to calc it's size. -// we use a shared memory region so we do not have to malloc a buffer area for every line -typedef struct { - /*HB_LineBreakType*/ unsigned lineBreakType :2; - /*HB_Bool*/ unsigned whiteSpace :1; /* A unicode whitespace character, except NBSP, ZWNBSP */ - /*HB_Bool*/ unsigned charStop :1; /* Valid cursor position (for left/right arrow) */ - /*HB_Bool*/ unsigned wordBoundary :1; - /*HB_Bool*/ unsigned sentenceBoundary :1; - unsigned unused :2; -} HB_CharAttributes_Dummy; - - -unsigned char *ChatLineModelItem::TextBoundaryFinderBuffer = (unsigned char *)malloc(512 * sizeof(HB_CharAttributes_Dummy)); -int ChatLineModelItem::TextBoundaryFinderBufferSize = 512 * (sizeof(HB_CharAttributes_Dummy) / sizeof(unsigned char)); - // **************************************** // the actual ChatLineModelItem // **************************************** @@ -121,10 +103,6 @@ QVariant ChatLineModelItem::contentsData(int role) const { return backgroundBrush(UiStyle::Contents, true); case ChatLineModel::FormatRole: return QVariant::fromValue(_styledMsg.contentsFormatList()); - case ChatLineModel::WrapListRole: - if(_wrapList.isEmpty()) - computeWrapList(); - return QVariant::fromValue(_wrapList); } return QVariant(); } @@ -144,78 +122,3 @@ QVariant ChatLineModelItem::backgroundBrush(UiStyle::FormatType subelement, bool return QVariant::fromValue(fmt.background()); return QVariant(); } - -void ChatLineModelItem::computeWrapList() const { - int length = _styledMsg.plainContents().length(); - if(!length) - return; - - enum Mode { SearchStart, SearchEnd }; - - QList wplist; // use a temp list which we'll later copy into a QVector for efficiency - QTextBoundaryFinder finder(QTextBoundaryFinder::Word, _styledMsg.plainContents().unicode(), length, - TextBoundaryFinderBuffer, TextBoundaryFinderBufferSize); - - int idx; - int oldidx = 0; - bool wordStart = false; - bool wordEnd = false; - Mode mode = SearchEnd; - ChatLineModel::Word word; - word.start = 0; - qreal wordstartx = 0; - - QTextLayout layout(_styledMsg.plainContents()); - QTextOption option; - option.setWrapMode(QTextOption::NoWrap); - layout.setTextOption(option); - - layout.setAdditionalFormats(QtUi::style()->toTextLayoutList(_styledMsg.contentsFormatList(), length, messageLabel())); - layout.beginLayout(); - QTextLine line = layout.createLine(); - line.setNumColumns(length); - layout.endLayout(); - - do { - idx = finder.toNextBoundary(); - if(idx < 0) { - idx = length; - wordStart = false; - wordEnd = false; - mode = SearchStart; - } else { - wordStart = finder.boundaryReasons().testFlag(QTextBoundaryFinder::StartWord); - wordEnd = finder.boundaryReasons().testFlag(QTextBoundaryFinder::EndWord); - } - - //if(flg) qDebug() << idx << mode << wordStart << wordEnd << contents->plainText.left(idx) << contents->plainText.mid(idx); - - if(mode == SearchEnd || (!wordStart && wordEnd)) { - if(wordStart || !wordEnd) continue; - oldidx = idx; - mode = SearchStart; - continue; - } - qreal wordendx = line.cursorToX(oldidx); - qreal trailingendx = line.cursorToX(idx); - word.endX = wordendx; - word.width = wordendx - wordstartx; - word.trailing = trailingendx - wordendx; - wordstartx = trailingendx; - wplist.append(word); - - if(wordStart) { - word.start = idx; - mode = SearchEnd; - } - // the part " || (finder.position() == contents->plainText.length())" shouldn't be necessary - // but in rare and indeterministic cases Qt states that the end of the text is not a boundary o_O - } while(finder.isAtBoundary() || (finder.position() == length)); - - // A QVector needs less space than a QList - _wrapList.resize(wplist.count()); - for(int i = 0; i < wplist.count(); i++) { - _wrapList[i] = wplist.at(i); - } -} - diff --git a/src/qtui/chatlinemodelitem.h b/src/qtui/chatlinemodelitem.h index b7a3a858..9fe4b8bc 100644 --- a/src/qtui/chatlinemodelitem.h +++ b/src/qtui/chatlinemodelitem.h @@ -39,15 +39,6 @@ public: virtual inline Message::Type msgType() const { return _styledMsg.type(); } virtual inline Message::Flags msgFlags() const { return _styledMsg.flags(); } - /// Used to store information about words to be used for wrapping - struct Word { - quint16 start; - qreal endX; - qreal width; - qreal trailing; - }; - typedef QVector WrapList; - private: QVariant timestampData(int role) const; QVariant senderData(int role) const; @@ -56,13 +47,7 @@ private: QVariant backgroundBrush(UiStyle::FormatType subelement, bool selected = false) const; quint32 messageLabel() const; - void computeWrapList() const; - - mutable WrapList _wrapList; UiStyle::StyledMessage _styledMsg; - - static unsigned char *TextBoundaryFinderBuffer; - static int TextBoundaryFinderBufferSize; }; #endif