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.
}
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();
}
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 {
/*************************************************************************************************/
-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;
-}
-
-/*************************************************************************************************/
-
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;
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)
ChatLineModel::ChatLineModel(QObject *parent)
: MessageModel(parent)
{
- qRegisterMetaType<WrapList>("ChatLineModel::WrapList");
- qRegisterMetaTypeStreamOperators<WrapList>("ChatLineModel::WrapList");
+
}
// MessageModelItem *ChatLineModel::createMessageModelItem(const Message &msg) {
_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;
-}
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 &);
QList<ChatLineModelItem> _messageList;
};
-QDataStream &operator<<(QDataStream &out, const ChatLineModel::WrapList);
-QDataStream &operator>>(QDataStream &in, ChatLineModel::WrapList &);
-
-Q_DECLARE_METATYPE(ChatLineModel::WrapList)
-
#endif
* 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
// ****************************************
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();
}
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);
- }
-}
-
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;
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