X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fqtui%2Fchatitem.cpp;h=5e2942cc712c7c4f98b8dbf09c148301d4f5ff41;hp=b1a071b8f864dedebe6fb04a831bd3434c5f560a;hb=d45d1044c030312878cb648fd1325ce70b079c44;hpb=b2de861297e7bb461b37ff041827c89360ecfec6 diff --git a/src/qtui/chatitem.cpp b/src/qtui/chatitem.cpp index b1a071b8..5e2942cc 100644 --- a/src/qtui/chatitem.cpp +++ b/src/qtui/chatitem.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * 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 * @@ -31,22 +31,64 @@ #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 { @@ -58,19 +100,19 @@ 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 { @@ -88,8 +130,9 @@ void ChatItem::initLayoutHelper(QTextLayout *layout, QTextOption::WrapMode wrapM layout->setAdditionalFormats(formatRanges); } -UiStyle::FormatList ChatItem::formatList() const { - return data(MessageModel::FormatRole).value(); +void ChatItem::initLayout(QTextLayout *layout) const { + initLayoutHelper(layout, QTextOption::NoWrap); + doLayout(layout); } void ChatItem::doLayout(QTextLayout *layout) const { @@ -102,9 +145,27 @@ 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(); +} + +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); @@ -118,11 +179,11 @@ void ChatItem::paintBackground(QPainter *painter) { // 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()); @@ -152,6 +213,8 @@ void ChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, // } // 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 { @@ -229,27 +292,27 @@ void ChatItem::setSelection(SelectionMode mode, qint16 start, qint16 end) { _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 { @@ -276,10 +339,8 @@ QList ChatItem::findWords(const QString &searchWord, Qt::CaseSensitivity 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(); @@ -296,18 +357,18 @@ void ChatItem::handleClick(const QPointF &pos, ChatScene::ClickMode clickMode) { 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(); @@ -344,13 +405,18 @@ void ChatItem::addActionsToMenu(QMenu *menu, const QPointF &pos) { // 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); @@ -360,10 +426,10 @@ void SenderChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *op 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 @@ -381,12 +447,25 @@ void SenderChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *op 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()); + 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); } // ************************************************************ @@ -395,10 +474,11 @@ void SenderChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *op 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); } @@ -410,6 +490,12 @@ ContentsChatItem::~ContentsChatItem() { delete _data; } +void ContentsChatItem::clearCache() { + delete _data; + _data = 0; + ChatItem::clearCache(); +} + ContentsChatItemPrivate *ContentsChatItem::privateData() const { if(!_data) { ContentsChatItem *that = const_cast(this); @@ -426,23 +512,28 @@ qreal ContentsChatItem::setGeometryByWidth(qreal w) { 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 { - // QString t = data(Qt::DisplayRole).toString(); bool d = t.contains("protien"); ChatLineModel::WrapList wrapList = data(ChatLineModel::WrapListRole).value(); 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 { @@ -469,7 +560,7 @@ void ContentsChatItem::doLayout(QTextLayout *layout) const { } line.setPosition(QPointF(0, h)); - h += fontMetrics()->lineSpacing(); + h += spacing; } layout->endLayout(); } @@ -506,11 +597,11 @@ QVector ContentsChatItem::additionalFormats() const { void ContentsChatItem::endHoverMode() { if(privateData()) { if(privateData()->currentClickable.isValid()) { - setCursor(Qt::ArrowCursor); + chatLine()->unsetCursor(); privateData()->currentClickable = Clickable(); } clearWebPreview(); - update(); + chatLine()->update(); } } @@ -540,7 +631,7 @@ void ContentsChatItem::handleClick(const QPointF &pos, ChatScene::ClickMode clic setSelectionStart(start); setSelectionEnd(end); } - update(); + chatLine()->update(); } else if(clickMode == ChatScene::TripleClick) { setSelection(PartialSelection, 0, data(ChatLineModel::DisplayRole).toString().length()); } @@ -573,9 +664,9 @@ void ContentsChatItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { onClickable = true; } if(onClickable) { - setCursor(Qt::PointingHandCursor); + chatLine()->setCursor(Qt::PointingHandCursor); privateData()->currentClickable = click; - update(); + chatLine()->update(); return; } } @@ -625,15 +716,13 @@ void ContentsChatItem::showWebPreview(const Clickable &click) { #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 urlstr = data(ChatLineModel::DisplayRole).toString().mid(click.start(), click.length());