Remove the word boundary cache
authorManuel Nickschas <sputnick@quassel-irc.org>
Wed, 29 Jul 2009 15:31:05 +0000 (17:31 +0200)
committerManuel Nickschas <sputnick@quassel-irc.org>
Thu, 6 Aug 2009 18:25:06 +0000 (20:25 +0200)
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.

src/qtui/chatitem.cpp
src/qtui/chatitem.h
src/qtui/chatlinemodel.cpp
src/qtui/chatlinemodel.h
src/qtui/chatlinemodelitem.cpp
src/qtui/chatlinemodelitem.h

index 2297983..2159f3f 100644 (file)
@@ -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<ChatLineModel::WrapList>();
-  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::Clickable> ContentsChatItem::findClickables() const {
@@ -684,66 +665,3 @@ void ContentsChatItem::clearWebPreview() {
 
 /*************************************************************************************************/
 
-ContentsChatItem::WrapColumnFinder::WrapColumnFinder(const ChatItem *_item)
-  : item(_item),
-    wrapList(item->data(ChatLineModel::WrapListRole).value<ChatLineModel::WrapList>()),
-    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;
-}
-
-/*************************************************************************************************/
-
index efd0705..cc37dac 100644 (file)
@@ -186,15 +186,13 @@ protected:
   virtual QVector<QTextLayout::FormatRange> 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<ContentsChatItem::Clickable> &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)
index 72af95c..a1cbbc3 100644 (file)
@@ -23,8 +23,7 @@
 ChatLineModel::ChatLineModel(QObject *parent)
   : MessageModel(parent)
 {
-  qRegisterMetaType<WrapList>("ChatLineModel::WrapList");
-  qRegisterMetaTypeStreamOperators<WrapList>("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;
-}
index 74f0ace..c21d1c3 100644 (file)
@@ -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<ChatLineModelItem> _messageList;
 };
 
-QDataStream &operator<<(QDataStream &out, const ChatLineModel::WrapList);
-QDataStream &operator>>(QDataStream &in, ChatLineModel::WrapList &);
-
-Q_DECLARE_METATYPE(ChatLineModel::WrapList)
-
 #endif
 
index 343fb99..187ba33 100644 (file)
  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
  ***************************************************************************/
 
-#include <QFontMetrics>
-#include <QTextBoundaryFinder>
-
 #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<UiStyle::FormatList>(_styledMsg.contentsFormatList());
-  case ChatLineModel::WrapListRole:
-    if(_wrapList.isEmpty())
-      computeWrapList();
-    return QVariant::fromValue<ChatLineModel::WrapList>(_wrapList);
   }
   return QVariant();
 }
@@ -144,78 +122,3 @@ QVariant ChatLineModelItem::backgroundBrush(UiStyle::FormatType subelement, bool
     return QVariant::fromValue<QBrush>(fmt.background());
   return QVariant();
 }
-
-void ChatLineModelItem::computeWrapList() const {
-  int length = _styledMsg.plainContents().length();
-  if(!length)
-    return;
-
-  enum Mode { SearchStart, SearchEnd };
-
-  QList<ChatLineModel::Word> 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);
-  }
-}
-
index b7a3a85..9fe4b8b 100644 (file)
@@ -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<Word> 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