This commit is more then due but is still WIP.
Heavy changes in ChatScene, ChatLine, ChatItems.
Finally making use of the disposable layouts.
#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<UiStyle::FormatList>().at(0).second);
+ const QAbstractItemModel *model_ = model();
+ QModelIndex index = model_->index(row(), col);
+ _fontMetrics = QtUi::style()->fontMetrics(model_->data(index, ChatLineModel::FormatRole).value<UiStyle::FormatList>().at(0).second);
setAcceptHoverEvents(true);
setZValue(20);
+ setPos(pos);
}
ChatItem::~ChatItem() {
- delete _layout;
+ delete _data;
}
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());
}
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();
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()) {
searchIdx = plainText.indexOf(searchWord, searchIdx + 1, caseSensitive);
}
- if(!haveLayout())
+ if(!hasLayout())
updateLayout();
foreach(int idx, indexList) {
}
}
-/*************************************************************************************************/
-
-/*************************************************************************************************/
-
-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<ChatLineModel::WrapList>();
QVector<QTextLayout::FormatRange> ContentsChatItem::additionalFormats() const {
// mark a clickable if hovered upon
QVector<QTextLayout::FormatRange> 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;
}
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) {
// 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);
}
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;
}
if(onClickable) {
setCursor(Qt::PointingHandCursor);
- layoutData()->currentClickable = click;
+ privateData()->currentClickable = click;
update();
break;
}
layout(0),
wrapList(item->data(ChatLineModel::WrapListRole).value<ChatLineModel::WrapList>()),
wordidx(0),
+ lineCount(0),
+ choppedTrailing(0),
lastwrapcol(0),
lastwrappos(0),
- w(0)
+ width(0)
{
}
}
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;
}
+
#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<ChatScene *>(scene()); }
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();
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<QTextLayout::FormatRange> additionalFormats() const { return QVector<QTextLayout::FormatRange>(); }
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);
virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event);
- virtual inline QTextLayout *layout() const;
- virtual void setLayout(QTextLayout *l);
virtual QVector<QTextLayout::FormatRange> additionalFormats() const;
+ virtual void updateLayout();
+
private:
- struct LayoutData;
struct Clickable;
class WrapColumnFinder;
- inline LayoutData *layoutData() const;
+ inline ContentsChatItemPrivate *privateData() const;
- qreal computeHeight();
QList<Clickable> 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 {
inline bool isValid() const { return type != Invalid; }
};
-struct ContentsChatItem::LayoutData {
- QTextLayout *layout;
- QList<Clickable> clickables;
- Clickable currentClickable;
+struct ContentsChatItemPrivate : ChatItemPrivate {
+ QList<ContentsChatItem::Clickable> clickables;
+ ContentsChatItem::Clickable currentClickable;
bool hasDragged;
- LayoutData() { layout = 0; hasDragged = false; }
- ~LayoutData() { delete layout; }
+ ContentsChatItemPrivate(QTextLayout *l, const QList<ContentsChatItem::Clickable> &c) : ChatItemPrivate(l), clickables(c), hasDragged(false) {}
};
class ContentsChatItem::WrapColumnFinder {
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<ChatLine *>(parentItem())->model(); }
int ChatItem::row() const { return static_cast<ChatLine *>(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
#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);
}
}
-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);
#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<ChatScene *>(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
/// Used to store information about words to be used for wrapping
struct Word {
quint16 start;
+ qreal endX;
qreal width;
qreal trailing;
};
}
qreal wordendx = line.cursorToX(oldidx);
qreal trailingendx = line.cursorToX(idx);
+ word.endX = wordendx;
word.width = wordendx - wordstartx;
word.trailing = trailingendx - wordendx;
wordstartx = trailingendx;
_model(model),
_singleBufferScene(false),
_sceneRect(0, 0, width, 0),
+ _viewportHeight(0),
_selectingItem(0),
_selectionStart(-1),
_isSelecting(false),
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();
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
updateSceneRect(_sceneRect.adjusted(0, h, 0, 0));
} else {
updateSceneRect(_sceneRect.adjusted(0, 0, 0, h));
- emit sceneHeightChanged(h);
+ emit lastLineChanged(_lines.last());
}
}
}
}
+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<ChatLine *>::iterator lineIter = _lines.end();
+ QList<ChatLine *>::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) {
}
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);
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
void requestBacklog();
signals:
- void sceneHeightChanged(qreal dh);
+ void lastLineChanged(QGraphicsItem *);
protected:
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent);
// we store the size in a member variable.
QRectF _sceneRect;
void updateSceneRect(const QRectF &rect);
+ qreal _viewportHeight;
ColumnHandleItem *firstColHandle, *secondColHandle;
qreal firstColHandlePos, secondColHandlePos;
};
bool ChatScene::containsBuffer(const BufferId &id) const {
- return qobject_cast<MessageFilter*>(model()) ? qobject_cast<MessageFilter*>(model())->containsBuffer(id) : false;
+ MessageFilter *filter = qobject_cast<MessageFilter*>(model());
+ if(filter)
+ return filter->containsBuffer(id);
+ else
+ return false;
}
#endif
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) {
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);