/***************************************************************************
- * Copyright (C) 2005-09 by the Quassel Project *
+ * Copyright (C) 2005-2010 by the Quassel Project *
* devel@quassel-irc.org *
* *
* This program is free software; you can redistribute it and/or modify *
#include "buffermodel.h"
#include "bufferview.h"
#include "chatitem.h"
+#include "chatline.h"
#include "chatlinemodel.h"
+#include "chatview.h"
#include "contextmenuactionprovider.h"
#include "iconloader.h"
#include "mainwin.h"
#include "qtui.h"
#include "qtuistyle.h"
-ChatItem::ChatItem(const qreal &width, const qreal &height, const QPointF &pos, QGraphicsItem *parent)
- : QGraphicsItem(parent),
- _boundingRect(0, 0, width, height),
- _selectionMode(NoSelection),
- _selectionStart(-1)
+ChatItem::ChatItem(const QRectF &boundingRect, ChatLine *parent)
+: _parent(parent),
+ _boundingRect(boundingRect),
+ _selectionMode(NoSelection),
+ _selectionStart(-1),
+ _cachedLayout(0)
{
- setAcceptHoverEvents(true);
- setZValue(20);
- setPos(pos);
+
+}
+
+ChatItem::~ChatItem() {
+ delete _cachedLayout;
+}
+
+ChatLine *ChatItem::chatLine() const {
+ return _parent;
+}
+
+ChatScene *ChatItem::chatScene() const {
+ return chatLine()->chatScene();
+}
+
+ChatView *ChatItem::chatView() const {
+ return chatScene()->chatView();
+}
+
+const QAbstractItemModel *ChatItem::model() const {
+ return chatLine()->model();
+}
+
+int ChatItem::row() const {
+ return chatLine()->row();
+}
+
+QPointF ChatItem::mapToLine(const QPointF &p) const {
+ return p + pos();
+}
+
+QPointF ChatItem::mapFromLine(const QPointF &p) const {
+ return p - pos();
+}
+
+// relative to the ChatLine
+QPointF ChatItem::mapToScene(const QPointF &p) const {
+ return chatLine()->mapToScene(p /* + pos() */);
+}
+
+QPointF ChatItem::mapFromScene(const QPointF &p) const {
+ return chatLine()->mapFromScene(p) /* - pos() */;
}
QVariant ChatItem::data(int role) const {
return model()->data(index, role);
}
-qint16 ChatItem::posToCursor(const QPointF &pos) const {
- 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::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::initLayout(QTextLayout *layout) const {
+ initLayoutHelper(layout, QTextOption::NoWrap);
+ doLayout(layout);
}
void ChatItem::doLayout(QTextLayout *layout) const {
layout->endLayout();
}
-void ChatItem::paintBackground(QPainter *painter) {
- painter->setClipRect(boundingRect()); // no idea why QGraphicsItem clipping won't work
+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)
bgBrush = data(ChatLineModel::SelectedBackgroundRole);
// 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);
+ painter->save();
+ painter->setClipRect(boundingRect());
paintBackground(painter);
- QTextLayout layout;
- initLayout(&layout);
- layout.draw(painter, QPointF(0,0), additionalFormats(), boundingRect());
+ layout()->draw(painter, pos(), additionalFormats(), boundingRect());
// layout()->draw(painter, QPointF(0,0), formats, boundingRect());
// }
// 3) draw bounding rect
// painter->drawRect(_boundingRect.adjusted(0, 0, -1, -1));
+
+ painter->restore();
}
void ChatItem::overlayFormat(UiStyle::FormatList &fmtList, int start, int end, quint32 overlayFmt) const {
_selectionMode = mode;
_selectionStart = start;
_selectionEnd = end;
- update();
+ chatLine()->update();
}
void ChatItem::setFullSelection() {
if(_selectionMode != FullSelection) {
_selectionMode = FullSelection;
- update();
+ chatLine()->update();
}
}
void ChatItem::clearSelection() {
if(_selectionMode != NoSelection) {
_selectionMode = NoSelection;
- update();
+ chatLine()->update();
}
}
void ChatItem::continueSelecting(const QPointF &pos) {
_selectionMode = PartialSelection;
_selectionEnd = posToCursor(pos);
- update();
+ chatLine()->update();
}
bool ChatItem::isPosOverSelection(const QPointF &pos) const {
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();
chatScene()->setSelectingItem(this);
_selectionStart = _selectionEnd = posToCursor(pos);
_selectionMode = NoSelection; // will be set to PartialSelection by mouseMoveEvent
- update();
+ chatLine()->update();
}
}
void ChatItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
if(event->buttons() == Qt::LeftButton) {
- if(contains(event->pos())) {
+ if(boundingRect().contains(event->pos())) {
qint16 end = posToCursor(event->pos());
if(end != _selectionEnd) {
_selectionEnd = end;
_selectionMode = (_selectionStart != _selectionEnd ? PartialSelection : NoSelection);
- update();
+ chatLine()->update();
}
} else {
setFullSelection();
// SenderChatItem
// ************************************************************
+void SenderChatItem::initLayout(QTextLayout *layout) const {
+ initLayoutHelper(layout, QTextOption::ManualWrap, Qt::AlignRight);
+ doLayout(layout);
+}
+
void SenderChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
Q_UNUSED(option); Q_UNUSED(widget);
+ painter->save();
+ 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
gradient.setColorAt(0, Qt::white);
gradient.setColorAt(1, Qt::black);
}
- maskPainter.fillRect(boundingRect(), gradient);
+ maskPainter.fillRect(0, 0, pixmap.width(), pixmap.height(), gradient);
pixmap.setAlphaChannel(mask);
- painter->drawPixmap(0, 0, pixmap);
+ painter->drawPixmap(pos(), pixmap);
} else {
- layout.draw(painter, QPointF(0,0), additionalFormats(), boundingRect());
+ layout()->draw(painter, pos(), additionalFormats(), boundingRect());
+ }
+ painter->restore();
+}
+
+void SenderChatItem::handleClick(const QPointF &pos, ChatScene::ClickMode clickMode) {
+ if(clickMode == ChatScene::DoubleClick) {
+ BufferInfo curBufInfo = Client::networkModel()->bufferInfo(data(MessageModel::BufferIdRole).value<BufferId>());
+ QString nick = data(MessageModel::EditRole).toString();
+ // check if the nick is a valid ircUser
+ if(!nick.isEmpty() && Client::network(curBufInfo.networkId())->ircUser(nick))
+ Client::bufferModel()->switchToOrStartQuery(curBufInfo.networkId(), nick);
}
+ else
+ ChatItem::handleClick(pos, clickMode);
}
// ************************************************************
ContentsChatItem::ActionProxy ContentsChatItem::_actionProxy;
-ContentsChatItem::ContentsChatItem(const qreal &width, const QPointF &pos, QGraphicsItem *parent)
- : ChatItem(0, 0, pos, parent),
+ContentsChatItem::ContentsChatItem(const QPointF &pos, const qreal &width, ChatLine *parent)
+ : ChatItem(QRectF(pos, QSizeF(width, 0)), parent),
_data(0)
{
+ setPos(pos);
setGeometryByWidth(width);
}
delete _data;
}
+void ContentsChatItem::clearCache() {
+ delete _data;
+ _data = 0;
+ ChatItem::clearCache();
+}
+
ContentsChatItemPrivate *ContentsChatItem::privateData() const {
if(!_data) {
ContentsChatItem *that = const_cast<ContentsChatItem *>(this);
WrapColumnFinder finder(this);
while(finder.nextWrapColumn(w) > 0)
lines++;
- qreal h = lines * fontMetrics()->lineSpacing();
+ qreal spacing = qMax(fontMetrics()->lineSpacing(), fontMetrics()->height()); // cope with negative leading()
+ qreal h = lines * spacing;
delete _data;
_data = 0;
- if(w != width() || h != height()) {
- prepareGeometryChange();
+ if(w != width() || h != height())
setGeometry(w, h);
- }
+
return h;
}
+void ContentsChatItem::initLayout(QTextLayout *layout) const {
+ initLayoutHelper(layout, QTextOption::WrapAtWordBoundaryOrAnywhere);
+ doLayout(layout);
+}
+
void ContentsChatItem::doLayout(QTextLayout *layout) const {
ChatLineModel::WrapList wrapList = data(ChatLineModel::WrapListRole).value<ChatLineModel::WrapList>();
if(!wrapList.count()) return; // empty chatitem
qreal h = 0;
+ qreal spacing = qMax(fontMetrics()->lineSpacing(), fontMetrics()->height()); // cope with negative leading()
WrapColumnFinder finder(this);
layout->beginLayout();
forever {
break;
int col = finder.nextWrapColumn(width());
- line.setNumColumns(col >= 0 ? col - line.textStart() : layout->text().length());
+ if(col < 0)
+ col = layout->text().length();
+ int num = col - line.textStart();
+
+ line.setNumColumns(num);
+
+ // Sometimes, setNumColumns will create a line that's too long (cf. Qt bug 238249)
+ // We verify this and try setting the width again, making it shorter each time until the lengths match.
+ // Dead fugly, but seems to work…
+ for(int i = line.textLength()-1; i >= 0 && line.textLength() > num; i--) {
+ line.setNumColumns(i);
+ }
+ if(num != line.textLength()) {
+ qWarning() << "WARNING: Layout engine couldn't workaround Qt bug 238249, please report!";
+ // qDebug() << num << line.textLength() << t.mid(line.textStart(), line.textLength()) << t.mid(line.textStart() + line.textLength());
+ }
+
line.setPosition(QPointF(0, h));
- h += fontMetrics()->lineSpacing();
+ h += spacing;
}
layout->endLayout();
}
void ContentsChatItem::endHoverMode() {
if(privateData()) {
if(privateData()->currentClickable.isValid()) {
- setCursor(Qt::ArrowCursor);
+ chatLine()->unsetCursor();
privateData()->currentClickable = Clickable();
}
clearWebPreview();
- update();
+ chatLine()->update();
}
}
setSelectionStart(start);
setSelectionEnd(end);
}
- update();
+ chatLine()->update();
} else if(clickMode == ChatScene::TripleClick) {
setSelection(PartialSelection, 0, data(ChatLineModel::DisplayRole).toString().length());
}
onClickable = true;
}
if(onClickable) {
- setCursor(Qt::PointingHandCursor);
+ chatLine()->setCursor(Qt::PointingHandCursor);
privateData()->currentClickable = click;
- update();
+ chatLine()->update();
return;
}
}
#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();
qreal y = height * line.lineNumber();
- QPointF topLeft = scenePos() + QPointF(x, y);
+ QPointF topLeft = mapToScene(pos()) + QPointF(x, y);
QRectF urlRect = QRectF(topLeft.x(), topLeft.y(), width, height);
- QString url = data(ChatLineModel::DisplayRole).toString().mid(click.start(), click.length());
- if(!url.contains("://"))
- url = "http://" + url;
+ QString urlstr = data(ChatLineModel::DisplayRole).toString().mid(click.start(), click.length());
+ if(!urlstr.contains("://"))
+ urlstr= "http://" + urlstr;
+ QUrl url = QUrl::fromEncoded(urlstr.toUtf8(), QUrl::TolerantMode);
chatScene()->loadWebPreview(this, url, urlRect);
#endif
}