#include "chatitem.h"
#include "chatline.h"
#include "chatlinemodel.h"
+#include "chatview.h"
#include "contextmenuactionprovider.h"
#include "iconloader.h"
#include "mainwin.h"
: _parent(parent),
_boundingRect(boundingRect),
_selectionMode(NoSelection),
- _selectionStart(-1)
+ _selectionStart(-1),
+ _cachedLayout(0)
{
}
+ChatItem::~ChatItem() {
+ delete _cachedLayout;
+}
+
ChatLine *ChatItem::chatLine() const {
return _parent;
}
return chatLine()->chatScene();
}
+ChatView *ChatItem::chatView() const {
+ return chatScene()->chatView();
+}
+
const QAbstractItemModel *ChatItem::model() const {
return chatLine()->model();
}
return model()->data(index, role);
}
-qint16 ChatItem::posToCursor(const QPointF &posInLine) const {
- QPointF pos = mapFromLine(posInLine);
- if(pos.y() > height()) return data(MessageModel::DisplayRole).toString().length();
- if(pos.y() < 0) return 0;
+QTextLayout *ChatItem::layout() const {
+ if(_cachedLayout)
+ return _cachedLayout;
- QTextLayout layout;
- initLayout(&layout);
- for(int l = layout.lineCount() - 1; l >= 0; l--) {
- QTextLine line = layout.lineAt(l);
- if(pos.y() >= line.y()) {
- return line.xToCursor(pos.x(), QTextLine::CursorOnCharacter);
- }
- }
- return 0;
+ _cachedLayout = new QTextLayout;
+ initLayout(_cachedLayout);
+ chatView()->setHasCache(chatLine());
+ return _cachedLayout;
+}
+
+void ChatItem::clearCache() {
+ delete _cachedLayout;
+ _cachedLayout = 0;
+}
+
+void ChatItem::initLayout(QTextLayout *layout, QTextOption::WrapMode mode, Qt::Alignment alignment) const {
+ initLayoutHelper(layout, mode, alignment);
+ doLayout(layout);
}
void ChatItem::initLayoutHelper(QTextLayout *layout, QTextOption::WrapMode wrapMode, Qt::Alignment alignment) const {
layout->setAdditionalFormats(formatRanges);
}
-UiStyle::FormatList ChatItem::formatList() const {
- return data(MessageModel::FormatRole).value<UiStyle::FormatList>();
-}
-
void ChatItem::doLayout(QTextLayout *layout) const {
layout->beginLayout();
QTextLine line = layout->createLine();
layout->endLayout();
}
+UiStyle::FormatList ChatItem::formatList() const {
+ return data(MessageModel::FormatRole).value<UiStyle::FormatList>();
+}
+
+qint16 ChatItem::posToCursor(const QPointF &posInLine) const {
+ QPointF pos = mapFromLine(posInLine);
+ if(pos.y() > height())
+ return data(MessageModel::DisplayRole).toString().length();
+ if(pos.y() < 0)
+ return 0;
+
+ for(int l = layout()->lineCount() - 1; l >= 0; l--) {
+ QTextLine line = layout()->lineAt(l);
+ if(pos.y() >= line.y()) {
+ return line.xToCursor(pos.x(), QTextLine::CursorOnCharacter);
+ }
+ }
+ return 0;
+}
+
void ChatItem::paintBackground(QPainter *painter) {
QVariant bgBrush;
if(_selectionMode == FullSelection)
painter->setClipRect(boundingRect());
paintBackground(painter);
- QTextLayout layout;
- initLayout(&layout);
- layout.draw(painter, pos(), additionalFormats(), boundingRect());
+ layout()->draw(painter, pos(), additionalFormats(), boundingRect());
// layout()->draw(painter, QPointF(0,0), formats, boundingRect());
searchIdx = plainText.indexOf(searchWord, searchIdx + 1, caseSensitive);
}
- QTextLayout layout;
- initLayout(&layout);
foreach(int idx, indexList) {
- QTextLine line = layout.lineForTextPosition(idx);
+ QTextLine line = layout()->lineForTextPosition(idx);
qreal x = line.cursorToX(idx);
qreal width = line.cursorToX(idx + searchWord.count()) - x;
qreal height = line.height();
painter->setClipRect(boundingRect());
paintBackground(painter);
- QTextLayout layout;
- initLayout(&layout);
- qreal layoutWidth = layout.minimumWidth();
+ qreal layoutWidth = layout()->minimumWidth();
qreal offset = 0;
if(chatScene()->senderCutoffMode() == ChatScene::CutoffLeft)
offset = qMin(width() - layoutWidth, (qreal)0);
if(layoutWidth > width()) {
// Draw a nice gradient for longer items
// Qt's text drawing with a gradient brush sucks, so we use an alpha-channeled pixmap instead
- QPixmap pixmap(layout.boundingRect().toRect().size());
+ QPixmap pixmap(layout()->boundingRect().toRect().size());
pixmap.fill(Qt::transparent);
QPainter pixPainter(&pixmap);
- layout.draw(&pixPainter, QPointF(qMax(offset, (qreal)0), 0), additionalFormats());
+ layout()->draw(&pixPainter, QPointF(qMax(offset, (qreal)0), 0), additionalFormats());
pixPainter.end();
// Create alpha channel mask
pixmap.setAlphaChannel(mask);
painter->drawPixmap(pos(), pixmap);
} else {
- layout.draw(painter, pos(), additionalFormats(), boundingRect());
+ layout()->draw(painter, pos(), additionalFormats(), boundingRect());
}
painter->restore();
}
delete _data;
}
+void ContentsChatItem::clearCache() {
+ delete _data;
+ _data = 0;
+ ChatItem::clearCache();
+}
+
ContentsChatItemPrivate *ContentsChatItem::privateData() const {
if(!_data) {
ContentsChatItem *that = const_cast<ContentsChatItem *>(this);
}
void ContentsChatItem::doLayout(QTextLayout *layout) const {
- // QString t = data(Qt::DisplayRole).toString(); bool d = t.contains("protien");
ChatLineModel::WrapList wrapList = data(ChatLineModel::WrapListRole).value<ChatLineModel::WrapList>();
if(!wrapList.count()) return; // empty chatitem
#ifndef HAVE_WEBKIT
Q_UNUSED(click);
#else
- QTextLayout layout;
- initLayout(&layout);
- QTextLine line = layout.lineForTextPosition(click.start());
+ QTextLine line = layout()->lineForTextPosition(click.start());
qreal x = line.cursorToX(click.start());
qreal width = line.cursorToX(click.start() + click.length()) - x;
qreal height = line.height();
#include <QTextLayout>
class ChatLine;
+class ChatView;
/* All external positions are relative to the parent ChatLine */
/* Yes, that's also true for the boundingRect() and related things */
protected:
// boundingRect is relative to the parent ChatLine
ChatItem(const QRectF &boundingRect, ChatLine *parent);
- virtual ~ChatItem() {}
+ virtual ~ChatItem();
public:
const QAbstractItemModel *model() const;
ChatLine *chatLine() const;
ChatScene *chatScene() const;
+ ChatView *chatView() const;
int row() const;
virtual ChatLineModel::ColumnType column() const = 0;
QPointF mapToScene(const QPointF &) const;
QPointF mapFromScene(const QPointF &) const;
- void initLayoutHelper(QTextLayout *layout, QTextOption::WrapMode, Qt::Alignment = Qt::AlignLeft) const;
- virtual inline void initLayout(QTextLayout *layout) const {
- initLayoutHelper(layout, QTextOption::NoWrap);
- doLayout(layout);
- }
- virtual void doLayout(QTextLayout *) const;
- virtual UiStyle::FormatList formatList() const;
-
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
virtual inline int type() const { return ChatScene::ChatItemType; }
virtual void addActionsToMenu(QMenu *menu, const QPointF &itemPos);
virtual void handleClick(const QPointF &pos, ChatScene::ClickMode);
+ void initLayoutHelper(QTextLayout *layout, QTextOption::WrapMode, Qt::Alignment = Qt::AlignLeft) const;
+
+ //! Remove internally cached data
+ /** This removes e.g. the cached QTextLayout to avoid wasting space for nonvisible ChatLines
+ */
+ virtual void clearCache();
+
protected:
enum SelectionMode {
NoSelection,
virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *) {};
virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *) {};
+ QTextLayout *layout() const;
+ virtual inline void initLayout(QTextLayout *layout) const { initLayout(layout, QTextOption::NoWrap); }
+ virtual void initLayout(QTextLayout *layout, QTextOption::WrapMode, Qt::Alignment alignment = Qt::AlignLeft) const;
+ virtual void doLayout(QTextLayout *) const;
+ virtual UiStyle::FormatList formatList() const;
+
void paintBackground(QPainter *);
QVector<QTextLayout::FormatRange> selectionFormats() const;
virtual QVector<QTextLayout::FormatRange> additionalFormats() const;
SelectionMode _selectionMode;
qint16 _selectionStart, _selectionEnd;
+ mutable QTextLayout *_cachedLayout;
+
// internal selection stuff
void setSelection(int start, int length);
protected:
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
virtual inline int type() const { return ChatScene::SenderChatItemType; }
- virtual inline void initLayout(QTextLayout *layout) const {
- initLayoutHelper(layout, QTextOption::ManualWrap, Qt::AlignRight);
- doLayout(layout);
- }
+ virtual inline void initLayout(QTextLayout *layout) const { ChatItem::initLayout(layout, QTextOption::ManualWrap, Qt::AlignRight); }
};
// ************************************************************
inline ChatLineModel::ColumnType column() const { return ChatLineModel::ContentsColumn; }
QFontMetricsF *fontMetrics() const;
+ virtual void clearCache();
+
protected:
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
virtual QVector<QTextLayout::FormatRange> additionalFormats() const;
- virtual inline void initLayout(QTextLayout *layout) const {
- initLayoutHelper(layout, QTextOption::WrapAtWordBoundaryOrAnywhere);
- doLayout(layout);
- }
+ virtual inline void initLayout(QTextLayout *layout) const { ChatItem::initLayout(layout, QTextOption::WrapAtWordBoundaryOrAnywhere); }
virtual void doLayout(QTextLayout *layout) const;
virtual UiStyle::FormatList formatList() const;
class ActionProxy;
class WrapColumnFinder;
- ContentsChatItemPrivate *_data;
+ mutable ContentsChatItemPrivate *_data;
ContentsChatItemPrivate *privateData() const;
Clickable clickableAt(const QPointF &pos) const;
void clearWebPreview();
qreal setGeometryByWidth(qreal w);
- friend class ChatLine;
- friend struct ContentsChatItemPrivate;
QFontMetricsF *_fontMetrics;
// we need a receiver for Action signals
static ActionProxy _actionProxy;
+
+ friend class ChatLine;
+ friend struct ContentsChatItemPrivate;
};
struct ContentsChatItemPrivate {
setHighlighted(index.data(MessageModel::FlagsRole).toInt() & Message::Highlight);
}
+ChatLine::~ChatLine() {
+ if(chatView())
+ chatView()->setHasCache(this, false);
+}
+
ChatItem *ChatLine::item(ChatLineModel::ColumnType column) {
switch(column) {
case ChatLineModel::TimestampColumn:
return 0;
}
+void ChatLine::clearCache() {
+ _timestampItem.clearCache();
+ _senderItem.clearCache();
+ _contentsItem.clearCache();
+}
+
void ChatLine::setMouseGrabberItem(ChatItem *item) {
_mouseGrabberItem = item;
}
const QPointF &senderPos, const QPointF &contentsPos,
QGraphicsItem *parent = 0);
+ virtual ~ChatLine();
+
virtual inline QRectF boundingRect () const { return QRectF(0, 0, _width, _height); }
inline QModelIndex index() const { return model()->index(row(), 0); }
inline const QAbstractItemModel *model() const { return _model; }
inline ChatScene *chatScene() const { return qobject_cast<ChatScene *>(scene()); }
- inline ChatView *chatView() const { return chatScene()->chatView(); }
+ inline ChatView *chatView() const { return chatScene() ? chatScene()->chatView() : 0; }
inline qreal width() const { return _width; }
inline qreal height() const { return _height; }
void setSelected(bool selected, ChatLineModel::ColumnType minColumn = ChatLineModel::ContentsColumn);
void setHighlighted(bool highlighted);
+ void clearCache();
+
protected:
virtual bool sceneEvent(QEvent *event);
void ChatView::resizeEvent(QResizeEvent *event) {
QGraphicsView::resizeEvent(event);
+ // FIXME: do we really need to scroll down on resize?
+
// we can reduce viewport updates if we scroll to the bottom allready at the beginning
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
scene()->updateForViewport(viewport()->width(), viewport()->height());
_lastScrollbarPos = verticalScrollBar()->maximum();
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
+
+ checkChatLineCaches();
}
void ChatView::adjustSceneRect() {
return one->row() < other->row();
}
+// TODO: figure out if it's cheaper to use a cached list (that we'd need to keep updated)
QSet<ChatLine *> ChatView::visibleChatLines(Qt::ItemSelectionMode mode) const {
QSet<ChatLine *> result;
foreach(QGraphicsItem *item, items(viewport()->rect().adjusted(-1, -1, 1, 1), mode)) {
_invalidateFilter = true;
}
}
+
+void ChatView::scrollContentsBy(int dx, int dy) {
+ QGraphicsView::scrollContentsBy(dx, dy);
+ checkChatLineCaches();
+}
+
+void ChatView::setHasCache(ChatLine *line, bool hasCache) {
+ if(hasCache)
+ _linesWithCache.insert(line);
+ else
+ _linesWithCache.remove(line);
+}
+
+void ChatView::checkChatLineCaches() {
+ qreal top = mapToScene(viewport()->rect().topLeft()).y() - 10; // some grace area to avoid premature cleaning
+ qreal bottom = mapToScene(viewport()->rect().bottomRight()).y() + 10;
+ QSet<ChatLine *>::iterator iter = _linesWithCache.begin();
+ while(iter != _linesWithCache.end()) {
+ ChatLine *line = *iter;
+ if(line->pos().y() + line->height() < top || line->pos().y() > bottom) {
+ line->clearCache();
+ iter = _linesWithCache.erase(iter);
+ } else
+ ++iter;
+ }
+}
virtual void addActionsToMenu(QMenu *, const QPointF &pos);
- virtual bool event(QEvent *event);
-
inline bool isMarkerLineVisible() const { return _markerLineVisible; }
inline ChatLine *markedLine() const { return _markedLine; }
+ //! Tell the view that this ChatLine has cached data
+ /** ChatLines cache some layout data that should be cleared as soon as it's no
+ * longer visible. A ChatLine caching data registers itself with this method to
+ * tell the view about it. The view will call ChatLine::clearCache() when
+ * appropriate.
+ * \param line The ChatLine having cached data
+ */
+ void setHasCache(ChatLine *line, bool hasCache = true);
+
public slots:
inline virtual void clear() {}
void zoomIn();
void setMarkedLine(ChatLine *line);
protected:
+ virtual bool event(QEvent *event);
virtual void resizeEvent(QResizeEvent *event);
+ virtual void scrollContentsBy(int dx, int dy);
protected slots:
virtual void verticalScrollbarChanged(int);
private slots:
void lastLineChanged(QGraphicsItem *chatLine, qreal offset);
void adjustSceneRect();
+ void checkChatLineCaches();
void mouseMoveWhileSelecting(const QPointF &scenePos);
void scrollTimerTimeout();
void invalidateFilter();
bool _invalidateFilter;
bool _markerLineVisible;
ChatLine *_markedLine;
+ QSet<ChatLine *> _linesWithCache;
};