From: Marcus Eggenberger Date: Sun, 21 Sep 2008 11:13:45 +0000 (+0200) Subject: Another Speed boost for the new ChatView. X-Git-Tag: 0.3.1~267 X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=commitdiff_plain;h=176d22d49934223b9279719ac5d9e7c03e530d40 Another Speed boost for the new ChatView. This commit is more then due but is still WIP. Heavy changes in ChatScene, ChatLine, ChatItems. Finally making use of the disposable layouts. --- diff --git a/src/qtui/chatitem.cpp b/src/qtui/chatitem.cpp index 28b496ea..495a97ef 100644 --- a/src/qtui/chatitem.cpp +++ b/src/qtui/chatitem.cpp @@ -32,22 +32,24 @@ #include "qtui.h" #include "qtuistyle.h" -ChatItem::ChatItem(ChatLineModel::ColumnType col, QAbstractItemModel *model, QGraphicsItem *parent) +ChatItem::ChatItem(const qreal &width, const qreal &height, const QPointF &pos, ChatLineModel::ColumnType col, QGraphicsItem *parent) : QGraphicsItem(parent), + _data(0), + _boundingRect(0, 0, width, height), _fontMetrics(0), _selectionMode(NoSelection), - _selectionStart(-1), - _layout(0) + _selectionStart(-1) { - Q_ASSERT(model); - QModelIndex index = model->index(row(), col); - _fontMetrics = QtUi::style()->fontMetrics(model->data(index, ChatLineModel::FormatRole).value().at(0).second); + const QAbstractItemModel *model_ = model(); + QModelIndex index = model_->index(row(), col); + _fontMetrics = QtUi::style()->fontMetrics(model_->data(index, ChatLineModel::FormatRole).value().at(0).second); setAcceptHoverEvents(true); setZValue(20); + setPos(pos); } ChatItem::~ChatItem() { - delete _layout; + delete _data; } QVariant ChatItem::data(int role) const { @@ -59,20 +61,6 @@ QVariant ChatItem::data(int role) const { return model()->data(index, role); } -qreal ChatItem::setGeometry(qreal w, qreal h) { - if(w == _boundingRect.width()) return _boundingRect.height(); - prepareGeometryChange(); - _boundingRect.setWidth(w); - if(h < 0) h = computeHeight(); - _boundingRect.setHeight(h); - if(haveLayout()) updateLayout(); - return h; -} - -qreal ChatItem::computeHeight() { - return fontMetrics()->lineSpacing(); // only contents can be multi-line -} - QTextLayout *ChatItem::createLayout(QTextOption::WrapMode wrapMode, Qt::Alignment alignment) { QTextLayout *layout = new QTextLayout(data(MessageModel::DisplayRole).toString()); @@ -88,28 +76,30 @@ QTextLayout *ChatItem::createLayout(QTextOption::WrapMode wrapMode, Qt::Alignmen } void ChatItem::updateLayout() { - if(!haveLayout()) - setLayout(createLayout(QTextOption::WrapAnywhere, Qt::AlignLeft)); - - layout()->beginLayout(); - QTextLine line = layout()->createLine(); + if(!privateData()) { + setPrivateData(new ChatItemPrivate(createLayout())); + } + QTextLayout *layout_ = layout(); + layout_->beginLayout(); + QTextLine line = layout_->createLine(); if(line.isValid()) { line.setLineWidth(width()); line.setPosition(QPointF(0,0)); } - layout()->endLayout(); + layout_->endLayout(); } void ChatItem::clearLayout() { - delete _layout; - _layout = 0; + delete _data; + _data = 0; } // NOTE: This is not the most time-efficient implementation, but it saves space by not caching unnecessary data // This is a deliberate trade-off. (-> selectFmt creation, data() call) void ChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); - if(!haveLayout()) updateLayout(); + if(!hasLayout()) + updateLayout(); painter->setClipRect(boundingRect()); // no idea why QGraphicsItem clipping won't work //if(_selectionMode == FullSelection) { //painter->save(); @@ -131,12 +121,29 @@ void ChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, formats.append(selectFmt); } layout()->draw(painter, QPointF(0,0), formats, boundingRect()); + + // Debuging Stuff + // uncomment the following lines to draw the bounding rect and the row number in alternating colors +// if(row() % 2) +// painter->setPen(Qt::red); +// else +// painter->setPen(Qt::blue); +// QString rowString = QString::number(row()); +// QRect rowRect = painter->fontMetrics().boundingRect(rowString); +// QPointF topPoint = _boundingRect.topLeft(); +// topPoint.ry() += rowRect.height(); +// painter->drawText(topPoint, rowString); +// QPointF bottomPoint = _boundingRect.bottomRight(); +// bottomPoint.rx() -= rowRect.width(); +// painter->drawText(bottomPoint, rowString); +// painter->drawRect(_boundingRect.adjusted(0, 0, -1, -1)); } qint16 ChatItem::posToCursor(const QPointF &pos) { if(pos.y() > height()) return data(MessageModel::DisplayRole).toString().length(); if(pos.y() < 0) return 0; - if(!haveLayout()) updateLayout(); + if(!hasLayout()) + updateLayout(); for(int l = layout()->lineCount() - 1; l >= 0; l--) { QTextLine line = layout()->lineAt(l); if(pos.y() >= line.y()) { @@ -178,7 +185,7 @@ QList ChatItem::findWords(const QString &searchWord, Qt::CaseSensitivity searchIdx = plainText.indexOf(searchWord, searchIdx + 1, caseSensitive); } - if(!haveLayout()) + if(!hasLayout()) updateLayout(); foreach(int idx, indexList) { @@ -235,51 +242,39 @@ void ChatItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { } } -/*************************************************************************************************/ - -/*************************************************************************************************/ - -void SenderChatItem::updateLayout() { - if(!haveLayout()) setLayout(createLayout(QTextOption::WrapAnywhere, Qt::AlignRight)); - ChatItem::updateLayout(); -} - -/*************************************************************************************************/ +// ************************************************************ +// SenderChatItem +// ************************************************************ -ContentsChatItem::ContentsChatItem(QAbstractItemModel *model, QGraphicsItem *parent) : ChatItem(column(), model, parent), - _layoutData(0) +// ************************************************************ +// ContentsChatItem +// ************************************************************ +ContentsChatItem::ContentsChatItem(const qreal &width, const QPointF &pos, QGraphicsItem *parent) + : ChatItem(0, 0, pos, column(), parent) { - -} - -ContentsChatItem::~ContentsChatItem() { - delete _layoutData; -} - -qreal ContentsChatItem::computeHeight() { - int lines = 1; - WrapColumnFinder finder(this); - while(finder.nextWrapColumn() > 0) lines++; - return lines * fontMetrics()->lineSpacing(); -} - -void ContentsChatItem::setLayout(QTextLayout *layout) { - if(!_layoutData) { - _layoutData = new LayoutData; - _layoutData->clickables = findClickables(); - } else { - delete _layoutData->layout; + setGeometryByWidth(width); +} + +qreal ContentsChatItem::setGeometryByWidth(qreal w) { + if(w != width()) { + setWidth(w); + // compute height + int lines = 1; + WrapColumnFinder finder(this); + while(finder.nextWrapColumn() > 0) + lines++; + setHeight(lines * fontMetrics()->lineSpacing()); } - _layoutData->layout = layout; -} - -void ContentsChatItem::clearLayout() { - delete _layoutData; - _layoutData = 0; + return height(); } void ContentsChatItem::updateLayout() { - if(!haveLayout()) setLayout(createLayout(QTextOption::WrapAnywhere)); + if(!privateData()) { + ContentsChatItemPrivate *data = new ContentsChatItemPrivate(createLayout(QTextOption::WrapAnywhere), + findClickables()); + // data->clickables = findClickables(); + setPrivateData(data); + } // Now layout ChatLineModel::WrapList wrapList = data(ChatLineModel::WrapListRole).value(); @@ -367,8 +362,8 @@ QList ContentsChatItem::findClickables() { QVector ContentsChatItem::additionalFormats() const { // mark a clickable if hovered upon QVector fmt; - if(layoutData()->currentClickable.isValid()) { - Clickable click = layoutData()->currentClickable; + if(privateData()->currentClickable.isValid()) { + Clickable click = privateData()->currentClickable; QTextLayout::FormatRange f; f.start = click.start; f.length = click.length; @@ -379,22 +374,22 @@ QVector ContentsChatItem::additionalFormats() const { } void ContentsChatItem::endHoverMode() { - if(layoutData()->currentClickable.isValid()) { + if(privateData()->currentClickable.isValid()) { setCursor(Qt::ArrowCursor); - layoutData()->currentClickable = Clickable(); + privateData()->currentClickable = Clickable(); update(); } } void ContentsChatItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { - layoutData()->hasDragged = false; + privateData()->hasDragged = false; ChatItem::mousePressEvent(event); } void ContentsChatItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { - if(!event->buttons() && !layoutData()->hasDragged) { + if(!event->buttons() && !privateData()->hasDragged) { // got a click - Clickable click = layoutData()->currentClickable; + Clickable click = privateData()->currentClickable; if(click.isValid()) { QString str = data(ChatLineModel::DisplayRole).toString().mid(click.start, click.length); switch(click.type) { @@ -416,9 +411,9 @@ void ContentsChatItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { // mouse move events always mean we're not hovering anymore... endHoverMode(); // also, check if we have dragged the mouse - if(!layoutData()->hasDragged && event->buttons() & Qt::LeftButton + if(!privateData()->hasDragged && event->buttons() & Qt::LeftButton && (event->buttonDownScreenPos(Qt::LeftButton) - event->screenPos()).manhattanLength() >= QApplication::startDragDistance()) - layoutData()->hasDragged = true; + privateData()->hasDragged = true; ChatItem::mouseMoveEvent(event); } @@ -430,8 +425,8 @@ void ContentsChatItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) { void ContentsChatItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { bool onClickable = false; qint16 idx = posToCursor(event->pos()); - for(int i = 0; i < layoutData()->clickables.count(); i++) { - Clickable click = layoutData()->clickables.at(i); + for(int i = 0; i < privateData()->clickables.count(); i++) { + Clickable click = privateData()->clickables.at(i); if(idx >= click.start && idx < click.start + click.length) { if(click.type == Clickable::Url) onClickable = true; @@ -441,7 +436,7 @@ void ContentsChatItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { } if(onClickable) { setCursor(Qt::PointingHandCursor); - layoutData()->currentClickable = click; + privateData()->currentClickable = click; update(); break; } @@ -458,9 +453,11 @@ ContentsChatItem::WrapColumnFinder::WrapColumnFinder(ChatItem *_item) layout(0), wrapList(item->data(ChatLineModel::WrapListRole).value()), wordidx(0), + lineCount(0), + choppedTrailing(0), lastwrapcol(0), lastwrappos(0), - w(0) + width(0) { } @@ -469,33 +466,35 @@ ContentsChatItem::WrapColumnFinder::~WrapColumnFinder() { } qint16 ContentsChatItem::WrapColumnFinder::nextWrapColumn() { - while(wordidx < wrapList.count()) { - w += wrapList.at(wordidx).width; - if(w >= item->width()) { - if(lastwrapcol >= wrapList.at(wordidx).start) { - // first word, and it doesn't fit - if(!line.isValid()) { - layout = item->createLayout(QTextOption::NoWrap); - layout->beginLayout(); - line = layout->createLine(); - line.setLineWidth(item->width()); - layout->endLayout(); - } - int idx = line.xToCursor(lastwrappos + item->width(), QTextLine::CursorOnCharacter); - qreal x = line.cursorToX(idx, QTextLine::Trailing); - w = w - wrapList.at(wordidx).width - (x - lastwrappos); - lastwrappos = x; - lastwrapcol = idx; - return idx; + 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; + + while(true) { + if(start == end) { + wordidx = start; + if(wordidx > 0) { + const ChatLineModel::Word &prevWord = wrapList.at(wordidx - 1); + choppedTrailing += prevWord.trailing - (targetWidth - prevWord.endX); } - // not the first word, so just wrap before this - lastwrapcol = wrapList.at(wordidx).start; - lastwrappos = lastwrappos + w - wrapList.at(wordidx).width; - w = 0; - return lastwrapcol; + return wrapList.at(wordidx).start; + } + qint16 pivot = (end + start) / 2; + if(wrapList.at(pivot).endX > targetWidth && wordidx != pivot) { + end = pivot; + } else { + start = pivot + 1; } - w += wrapList.at(wordidx).trailing; - wordidx++; } return -1; } + diff --git a/src/qtui/chatitem.h b/src/qtui/chatitem.h index a6eb531b..587cc609 100644 --- a/src/qtui/chatitem.h +++ b/src/qtui/chatitem.h @@ -30,15 +30,15 @@ #include "qtui.h" class QTextLayout; +struct ChatItemPrivate; class ChatItem : public QGraphicsItem { - protected: - ChatItem(ChatLineModel::ColumnType column, QAbstractItemModel *, QGraphicsItem *parent); + ChatItem(const qreal &width, const qreal &height, const QPointF &pos, ChatLineModel::ColumnType column, QGraphicsItem *parent); virtual ~ChatItem(); public: - inline const QAbstractItemModel *model() const { return chatScene() ? chatScene()->model() : 0; } + inline const QAbstractItemModel *model() const; inline int row() const; virtual ChatLineModel::ColumnType column() const = 0; inline ChatScene *chatScene() const { return qobject_cast(scene()); } @@ -48,16 +48,15 @@ public: inline qreal width() const { return _boundingRect.width(); } inline qreal height() const { return _boundingRect.height(); } - virtual inline bool haveLayout() const { return layout() != 0; } - virtual void clearLayout(); - virtual QTextLayout *createLayout(QTextOption::WrapMode, Qt::Alignment = Qt::AlignLeft); + inline bool hasLayout() const { return (bool)_data; } + QTextLayout *createLayout(QTextOption::WrapMode, Qt::Alignment = Qt::AlignLeft); + virtual inline QTextLayout *createLayout() { return createLayout(QTextOption::WrapAnywhere); } + void clearLayout(); + virtual void updateLayout(); virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); - virtual QVariant data(int role) const; - - // returns height - qreal setGeometry(qreal width, qreal height = -1); + QVariant data(int role) const; // selection stuff, to be called by the scene void clearSelection(); @@ -71,66 +70,85 @@ protected: virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); - virtual inline QTextLayout *layout() const { return _layout; } - virtual inline void setLayout(QTextLayout *l) { _layout = l; } + inline QTextLayout *layout() const; + virtual inline QVector additionalFormats() const { return QVector(); } qint16 posToCursor(const QPointF &pos); - virtual qreal computeHeight(); + inline void ChatItem::setPrivateData(ChatItemPrivate *data) { Q_ASSERT(!_data); _data = data; } + inline ChatItemPrivate *ChatItem::privateData() const; - QRectF _boundingRect; + // WARNING: setGeometry and setHeight should not be used without either: + // a) calling prepareGeometryChange() immediately before setColumns() + // b) calling Chatline::setPos() immediately afterwards + inline void setGeometry(qreal width, qreal height) { + _boundingRect.setWidth(width); + _boundingRect.setHeight(height); + } + inline void setHeight(const qreal &height) { _boundingRect.setHeight(height); } + inline void setWidth(const qreal &width) { _boundingRect.setWidth(width); } private: // internal selection stuff void setSelection(int start, int length); + ChatItemPrivate *_data; + QRectF _boundingRect; QFontMetricsF *_fontMetrics; enum SelectionMode { NoSelection, PartialSelection, FullSelection }; SelectionMode _selectionMode; qint16 _selectionStart, _selectionEnd; - QTextLayout *_layout; + friend class ChatLine; }; -/*************************************************************************************************/ +struct ChatItemPrivate { + QTextLayout *layout; + ChatItemPrivate(QTextLayout *l) : layout(l) {} + ~ChatItemPrivate() { + delete layout; + } +}; + +// inlines of ChatItem +QTextLayout *ChatItem::layout() const { return privateData()->layout; } +ChatItemPrivate *ChatItem::privateData() const { return _data; } + +// ************************************************************ +// TimestampChatItem +// ************************************************************ //! A ChatItem for the timestamp column class TimestampChatItem : public ChatItem { - public: - TimestampChatItem(QAbstractItemModel *model, QGraphicsItem *parent) : ChatItem(column(), model, parent) {} - inline ChatLineModel::ColumnType column() const { return ChatLineModel::TimestampColumn; } - + TimestampChatItem(const qreal &width, const qreal &height, QGraphicsItem *parent) : ChatItem(width, height, QPointF(0, 0), column(), parent) {} + virtual inline ChatLineModel::ColumnType column() const { return ChatLineModel::TimestampColumn; } }; -/*************************************************************************************************/ - +// ************************************************************ +// SenderChatItem +// ************************************************************ //! A ChatItem for the sender column class SenderChatItem : public ChatItem { - public: - SenderChatItem(QAbstractItemModel *model, QGraphicsItem *parent) : ChatItem(column(), model, parent) {} - inline ChatLineModel::ColumnType column() const { return ChatLineModel::SenderColumn; } - - virtual void updateLayout(); + SenderChatItem(const qreal &width, const qreal &height, const QPointF &pos, QGraphicsItem *parent) : ChatItem(width, height, pos, column(), parent) {} + virtual inline ChatLineModel::ColumnType column() const { return ChatLineModel::SenderColumn; } + virtual inline QTextLayout *createLayout() { return ChatItem::createLayout(QTextOption::WrapAnywhere, Qt::AlignRight); } }; -/*************************************************************************************************/ +// ************************************************************ +// ContentsChatItem +// ************************************************************ +struct ContentsChatItemPrivate; //! A ChatItem for the contents column class ContentsChatItem : public ChatItem { - public: - ContentsChatItem(QAbstractItemModel *model, QGraphicsItem *parent); - virtual ~ContentsChatItem(); + ContentsChatItem(const qreal &width, const QPointF &pos, QGraphicsItem *parent); inline ChatLineModel::ColumnType column() const { return ChatLineModel::ContentsColumn; } - virtual void clearLayout(); - virtual void updateLayout(); - virtual inline bool haveLayout() const { return _layoutData != 0 && layout() != 0; } - protected: virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event); virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); @@ -138,22 +156,25 @@ protected: virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event); - virtual inline QTextLayout *layout() const; - virtual void setLayout(QTextLayout *l); virtual QVector additionalFormats() const; + virtual void updateLayout(); + private: - struct LayoutData; struct Clickable; class WrapColumnFinder; - inline LayoutData *layoutData() const; + inline ContentsChatItemPrivate *privateData() const; - qreal computeHeight(); QList findClickables(); void endHoverMode(); - LayoutData *_layoutData; + // WARNING: setGeometry and setHeight should not be used without either: + // a) calling prepareGeometryChange() immediately before setColumns() + // b) calling Chatline::setPos() immediately afterwards + qreal setGeometryByWidth(qreal w); + friend class ChatLine; + friend struct ContentsChatItemPrivate; }; struct ContentsChatItem::Clickable { @@ -174,14 +195,12 @@ struct ContentsChatItem::Clickable { inline bool isValid() const { return type != Invalid; } }; -struct ContentsChatItem::LayoutData { - QTextLayout *layout; - QList clickables; - Clickable currentClickable; +struct ContentsChatItemPrivate : ChatItemPrivate { + QList clickables; + ContentsChatItem::Clickable currentClickable; bool hasDragged; - LayoutData() { layout = 0; hasDragged = false; } - ~LayoutData() { delete layout; } + ContentsChatItemPrivate(QTextLayout *l, const QList &c) : ChatItemPrivate(l), clickables(c), hasDragged(false) {} }; class ContentsChatItem::WrapColumnFinder { @@ -197,18 +216,20 @@ private: QTextLine line; ChatLineModel::WrapList wrapList; qint16 wordidx; + qint16 lineCount; + qreal choppedTrailing; qint16 lastwrapcol; qreal lastwrappos; - qreal w; + qreal width; }; /*************************************************************************************************/ // Avoid circular include deps #include "chatline.h" +const QAbstractItemModel *ChatItem::model() const { return static_cast(parentItem())->model(); } int ChatItem::row() const { return static_cast(parentItem())->row(); } -QTextLayout *ContentsChatItem::layout() const { return _layoutData->layout; } -ContentsChatItem::LayoutData *ContentsChatItem::layoutData() const { Q_ASSERT(_layoutData); return _layoutData; } +ContentsChatItemPrivate *ContentsChatItem::privateData() const { return (ContentsChatItemPrivate *)privateData(); } #endif diff --git a/src/qtui/chatline.cpp b/src/qtui/chatline.cpp index 1a8501c7..df218a51 100644 --- a/src/qtui/chatline.cpp +++ b/src/qtui/chatline.cpp @@ -34,14 +34,35 @@ #include "qtuisettings.h" #include "qtuistyle.h" -ChatLine::ChatLine(int row, QAbstractItemModel *model, QGraphicsItem *parent) +// ChatLine::ChatLine(int row, QAbstractItemModel *model, QGraphicsItem *parent) +// : QGraphicsItem(parent), +// _row(row), // needs to be set before the items +// _model(model), +// _contentsItem(this), +// _senderItem(this), +// _timestampItem(this), +// _width(0), +// _height(0), +// _selection(0) +// { +// Q_ASSERT(model); +// QModelIndex index = model->index(row, ChatLineModel::ContentsColumn); +// setHighlighted(model->data(index, MessageModel::FlagsRole).toInt() & Message::Highlight); +// } + +ChatLine::ChatLine(int row, QAbstractItemModel *model, + const qreal &width, + const qreal ×tampWidth, const qreal &senderWidth, const qreal &contentsWidth, + const QPointF &senderPos, const QPointF &contentsPos, + QGraphicsItem *parent) : QGraphicsItem(parent), _row(row), // needs to be set before the items - _timestampItem(model, this), - _senderItem(model, this), - _contentsItem(model, this), - _width(0), - _height(0), + _model(model), + _contentsItem(contentsWidth, contentsPos, this), + _senderItem(senderWidth, _contentsItem.height(), senderPos, this), + _timestampItem(timestampWidth, _contentsItem.height(), this), + _width(width), + _height(_contentsItem.height()), _selection(0) { Q_ASSERT(model); @@ -67,13 +88,47 @@ ChatItem &ChatLine::item(ChatLineModel::ColumnType column) { } } -qreal ChatLine::setGeometry(qreal width) { +// WARNING: setColumns should not be used without either: +// a) calling prepareGeometryChange() immediately before setColumns() +// b) calling Chatline::setPos() immediately afterwards +// +// NOTE: senderPos and contentsPos are in ChatLines coordinate system! +qreal ChatLine::setColumns(const qreal ×tampWidth, const qreal &senderWidth, const qreal &contentsWidth, + const QPointF &senderPos, const QPointF &contentsPos) { + _height = _contentsItem.setGeometryByWidth(contentsWidth); + _senderItem.setGeometry(senderWidth, _height); + _timestampItem.setGeometry(timestampWidth, _height); + + _senderItem.setPos(senderPos); + _contentsItem.setPos(contentsPos); + + _contentsItem.clearLayout(); + _senderItem.clearLayout(); + _timestampItem.clearLayout(); + + return _height; +} + +// WARNING: setGeometryByWidth should not be used without either: +// a) calling prepareGeometryChange() immediately before setColumns() +// b) calling Chatline::setPos() immediately afterwards +qreal ChatLine::setGeometryByWidth(const qreal &width, const qreal &contentsWidth) { + _width = width; + _height = _contentsItem.setGeometryByWidth(contentsWidth); + _timestampItem.setHeight(_height); + _senderItem.setHeight(_height); + _contentsItem.clearLayout(); + return _height; +} + +qreal ChatLine::setGeometryByWidth(qreal width) { if(width != _width) prepareGeometryChange(); ColumnHandleItem *firstColumnHandle = chatScene()->firstColumnHandle(); ColumnHandleItem *secondColumnHandle = chatScene()->secondColumnHandle(); - _height = _contentsItem.setGeometry(width - secondColumnHandle->sceneRight()); + + _height = _contentsItem.setGeometryByWidth(width - secondColumnHandle->sceneRight()); _timestampItem.setGeometry(firstColumnHandle->sceneLeft(), _height); _senderItem.setGeometry(secondColumnHandle->sceneLeft() - firstColumnHandle->sceneRight(), _height); diff --git a/src/qtui/chatline.h b/src/qtui/chatline.h index d69caa5b..13f45cec 100644 --- a/src/qtui/chatline.h +++ b/src/qtui/chatline.h @@ -27,38 +27,50 @@ #include "chatitem.h" class ChatLine : public QGraphicsItem { - public: - ChatLine(int row, QAbstractItemModel *model, QGraphicsItem *parent = 0); +// ChatLine(int row, QAbstractItemModel *model, QGraphicsItem *parent = 0); + ChatLine(int row, QAbstractItemModel *model, + const qreal &width, + const qreal ×tampWidth, const qreal &senderWidth, const qreal &contentsWidth, + const QPointF &senderPos, const QPointF &contentsPos, + QGraphicsItem *parent = 0); virtual QRectF boundingRect () const; inline int row() { return _row; } inline void setRow(int row) { _row = row; } - inline const QAbstractItemModel *model() const { return chatScene() ? chatScene()->model() : 0; } + inline const QAbstractItemModel *model() const { return _model; } inline ChatScene *chatScene() const { return qobject_cast(scene()); } inline qreal width() const { return _width; } inline qreal height() const { return _height; } ChatItem &item(ChatLineModel::ColumnType); + inline ChatItem ×tampItem() { return _timestampItem; } + inline ChatItem &senderItem() { return _senderItem; } + inline ContentsChatItem &contentsItem() { return _contentsItem; } virtual void paint (QPainter * painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); // returns height - qreal setGeometry(qreal width); + qreal setGeometryByWidth(qreal width); void setSelected(bool selected, ChatLineModel::ColumnType minColumn = ChatLineModel::ContentsColumn); void setHighlighted(bool highlighted); -protected: - private: int _row; - TimestampChatItem _timestampItem; - SenderChatItem _senderItem; + QAbstractItemModel *_model; ContentsChatItem _contentsItem; + SenderChatItem _senderItem; + TimestampChatItem _timestampItem; qreal _width, _height; enum { Selected = 0x40, Highlighted = 0x80 }; quint8 _selection; // save space, so we put both the col and the flags into one byte + + // setColumns and setGeometryByWidth both return height + qreal setColumns(const qreal ×tampWidth, const qreal &senderWidth, const qreal &contentsWidth, + const QPointF &senderPos, const QPointF &contentsPos); + qreal setGeometryByWidth(const qreal &width, const qreal &contentsWidth); + friend class ChatScene; }; #endif diff --git a/src/qtui/chatlinemodel.h b/src/qtui/chatlinemodel.h index 4604a9b7..4f64efe1 100644 --- a/src/qtui/chatlinemodel.h +++ b/src/qtui/chatlinemodel.h @@ -36,6 +36,7 @@ public: /// Used to store information about words to be used for wrapping struct Word { quint16 start; + qreal endX; qreal width; qreal trailing; }; diff --git a/src/qtui/chatlinemodelitem.cpp b/src/qtui/chatlinemodelitem.cpp index 24e2273d..1e12cd1a 100644 --- a/src/qtui/chatlinemodelitem.cpp +++ b/src/qtui/chatlinemodelitem.cpp @@ -156,6 +156,7 @@ private: } qreal wordendx = line.cursorToX(oldidx); qreal trailingendx = line.cursorToX(idx); + word.endX = wordendx; word.width = wordendx - wordstartx; word.trailing = trailingendx - wordendx; wordstartx = trailingendx; diff --git a/src/qtui/chatscene.cpp b/src/qtui/chatscene.cpp index c3a07b15..ed8e68d2 100644 --- a/src/qtui/chatscene.cpp +++ b/src/qtui/chatscene.cpp @@ -43,6 +43,7 @@ ChatScene::ChatScene(QAbstractItemModel *model, const QString &idString, qreal w _model(model), _singleBufferScene(false), _sceneRect(0, 0, width, 0), + _viewportHeight(0), _selectingItem(0), _selectionStart(-1), _isSelecting(false), @@ -95,7 +96,6 @@ void ChatScene::rowsInserted(const QModelIndex &index, int start, int end) { bool atTop = true; bool atBottom = false; bool moveTop = false; - bool hasWidth = (width != 0); if(start > 0) { y = _lines.value(start - 1)->y() + _lines.value(start - 1)->height(); @@ -104,19 +104,27 @@ void ChatScene::rowsInserted(const QModelIndex &index, int start, int end) { if(start == _lines.count()) atBottom = true; + qreal contentsWidth = width - secondColumnHandle()->sceneRight(); + qreal senderWidth = secondColumnHandle()->sceneLeft() - firstColumnHandle()->sceneRight(); + qreal timestampWidth = firstColumnHandle()->sceneLeft(); + QPointF contentsPos(secondColumnHandle()->sceneRight(), 0); + QPointF senderPos(firstColumnHandle()->sceneRight(), 0); + + for(int i = end; i >= start; i--) { - ChatLine *line = new ChatLine(i, model()); + ChatLine *line = new ChatLine(i, model(), + width, + timestampWidth, senderWidth, contentsWidth, + senderPos, contentsPos); + if(atTop) { + h -= line->height(); + line->setPos(0, y+h); + } else { + line->setPos(0, y+h); + h += line->height(); + } _lines.insert(start, line); addItem(line); - if(hasWidth) { - if(atTop) { - h -= line->setGeometry(width); - line->setPos(0, y+h); - } else { - line->setPos(0, y+h); - h += line->setGeometry(width); - } - } } // update existing items @@ -159,7 +167,7 @@ void ChatScene::rowsInserted(const QModelIndex &index, int start, int end) { updateSceneRect(_sceneRect.adjusted(0, h, 0, 0)); } else { updateSceneRect(_sceneRect.adjusted(0, 0, 0, h)); - emit sceneHeightChanged(h); + emit lastLineChanged(_lines.last()); } } @@ -230,31 +238,57 @@ void ChatScene::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int e } } +void ChatScene::updateForViewport(qreal width, qreal height) { + _viewportHeight = height; + setWidth(width); +} + +// setWidth is used for 2 things: +// a) updating the scene to fit the width of the corresponding view +// b) to update the positions of the items if a columhandle has changed it's position +// forceReposition is true in the second case +// this method features some codeduplication for the sake of performance void ChatScene::setWidth(qreal width, bool forceReposition) { if(width == _sceneRect.width() && !forceReposition) return; - // clock_t startT = clock(); - qreal oldHeight = _sceneRect.height(); - qreal y = _sceneRect.y(); - qreal linePos = y; - - foreach(ChatLine *line, _lines) { - line->setPos(0, linePos); - linePos += line->setGeometry(width); +// clock_t startT = clock(); + + qreal linePos = _sceneRect.y() + _sceneRect.height(); + qreal yBottom = linePos; + QList::iterator lineIter = _lines.end(); + QList::iterator lineIterBegin = _lines.begin(); + ChatLine *line = 0; + qreal lineHeight = 0; + qreal contentsWidth = width - secondColumnHandle()->sceneRight(); + + if(forceReposition) { + qreal timestampWidth = firstColumnHandle()->sceneLeft(); + qreal senderWidth = secondColumnHandle()->sceneLeft() - firstColumnHandle()->sceneRight(); + QPointF senderPos(firstColumnHandle()->sceneRight(), 0); + QPointF contentsPos(secondColumnHandle()->sceneRight(), 0); + while(lineIter != lineIterBegin) { + lineIter--; + line = *lineIter; + lineHeight = line->setColumns(timestampWidth, senderWidth, contentsWidth, senderPos, contentsPos); + linePos -= lineHeight; + line->setPos(0, linePos); + } + } else { + while(lineIter != lineIterBegin) { + lineIter--; + line = *lineIter; + lineHeight = line->setGeometryByWidth(width, contentsWidth); + linePos -= lineHeight; + line->setPos(0, linePos); + } } - qreal height = linePos - y; - - updateSceneRect(QRectF(0, y, width, height)); + updateSceneRect(QRectF(0, linePos, width, yBottom - linePos)); setHandleXLimits(); - qreal dh = height - oldHeight; - if(dh > 0) - emit sceneHeightChanged(dh); - - // clock_t endT = clock(); - // qDebug() << "resized" << _lines.count() << "in" << (float)(endT - startT) / CLOCKS_PER_SEC << "sec"; +// clock_t endT = clock(); +// qDebug() << "resized" << _lines.count() << "in" << (float)(endT - startT) / CLOCKS_PER_SEC << "sec"; } void ChatScene::handlePositionChanged(qreal xpos) { @@ -416,7 +450,7 @@ QString ChatScene::selectionToString() const { } void ChatScene::requestBacklog() { - static const int REQUEST_COUNT = 100; + static const int REQUEST_COUNT = 500; int backlogSize = model()->rowCount(); if(isSingleBufferScene() && backlogSize != 0 && _lastBacklogSize + REQUEST_COUNT <= backlogSize) { QModelIndex msgIdx = model()->index(0, 0); diff --git a/src/qtui/chatscene.h b/src/qtui/chatscene.h index fa441023..e0aaa500 100644 --- a/src/qtui/chatscene.h +++ b/src/qtui/chatscene.h @@ -54,6 +54,7 @@ public: inline ColumnHandleItem *secondColumnHandle() const { return secondColHandle; } public slots: + void updateForViewport(qreal width, qreal height); void setWidth(qreal, bool forceReposition = false); // these are used by the chatitems to notify the scene and manage selections @@ -65,7 +66,7 @@ public slots: void requestBacklog(); signals: - void sceneHeightChanged(qreal dh); + void lastLineChanged(QGraphicsItem *); protected: virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent); @@ -93,6 +94,7 @@ private: // we store the size in a member variable. QRectF _sceneRect; void updateSceneRect(const QRectF &rect); + qreal _viewportHeight; ColumnHandleItem *firstColHandle, *secondColHandle; qreal firstColHandlePos, secondColHandlePos; @@ -108,7 +110,11 @@ private: }; bool ChatScene::containsBuffer(const BufferId &id) const { - return qobject_cast(model()) ? qobject_cast(model())->containsBuffer(id) : false; + MessageFilter *filter = qobject_cast(model()); + if(filter) + return filter->containsBuffer(id); + else + return false; } #endif diff --git a/src/qtui/chatview.cpp b/src/qtui/chatview.cpp index 6344c9c2..6b2d1f59 100644 --- a/src/qtui/chatview.cpp +++ b/src/qtui/chatview.cpp @@ -47,29 +47,33 @@ ChatView::ChatView(MessageFilter *filter, QWidget *parent) void ChatView::init(MessageFilter *filter) { setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setAlignment(Qt::AlignBottom); + // setAlignment(Qt::AlignBottom); setInteractive(true); + setOptimizationFlags(QGraphicsView::DontClipPainter | QGraphicsView::DontAdjustForAntialiasing); + setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); + setTransformationAnchor(QGraphicsView::NoAnchor); _scene = new ChatScene(filter, filter->idString(), viewport()->width() - 2, this); // see below: resizeEvent() - connect(_scene, SIGNAL(sceneHeightChanged(qreal)), this, SLOT(sceneHeightChanged(qreal))); connect(_scene, SIGNAL(sceneRectChanged(const QRectF &)), this, SLOT(sceneRectChanged(const QRectF &))); + //connect(_scene, SIGNAL(lastLineChanged(QGraphicsItem *)), this, SLOT(lastLineChanged(QGraphicsItem *))); setScene(_scene); - _lastScrollbarPos = verticalScrollBar()->maximum(); connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(verticalScrollbarChanged(int))); } void ChatView::resizeEvent(QResizeEvent *event) { QGraphicsView::resizeEvent(event); - scene()->setWidth(viewport()->width() - 2); // FIXME figure out why we have to hardcode the -2 here -> Qt-Bug most probably + + // FIXME: without the hardcoded -2 Qt reserves space for a horizontal scrollbar even though it's disabled permanently. + // this does only occur on QtX11 (at least not on Qt for Mac OS). Seems like a Qt Bug. + scene()->updateForViewport(viewport()->width() - 2, viewport()->height()); + _lastScrollbarPos = verticalScrollBar()->maximum(); verticalScrollBar()->setValue(verticalScrollBar()->maximum()); } -void ChatView::sceneHeightChanged(qreal dh) { - QAbstractSlider *vbar = verticalScrollBar(); - Q_ASSERT(vbar); - if(vbar->maximum() - vbar->value() <= dh + 5) // in case we had scrolled only about half a line to the bottom we allow a grace of 5 - vbar->setValue(vbar->maximum()); +void ChatView::lastLineChanged(QGraphicsItem *chatLine) { + // FIXME: if vbar offset < chatline height -> centerOn(chatLine); + centerOn(chatLine); } void ChatView::verticalScrollbarChanged(int newPos) { diff --git a/src/qtui/chatview.h b/src/qtui/chatview.h index 5341109f..eab096b9 100644 --- a/src/qtui/chatview.h +++ b/src/qtui/chatview.h @@ -48,7 +48,7 @@ protected: virtual void resizeEvent(QResizeEvent *event); protected slots: - virtual void sceneHeightChanged(qreal dh); + void lastLineChanged(QGraphicsItem *chatLine); virtual inline void sceneRectChanged(const QRectF &rect) { setSceneRect(rect); } virtual void verticalScrollbarChanged(int);