X-Git-Url: https://git.quassel-irc.org/?a=blobdiff_plain;f=src%2Fqtui%2Fchatitem.cpp;h=10b7d78068dba35629e1cca8ad97b9e18a0ca268;hb=03f61d2ab68356bd74f6f014651c823e79678cbd;hp=568cdccf233c14bd0b86d125dfc67b790498bd6f;hpb=3a9668461efb05bd5b24ba5eb741a7f7f75086c9;p=quassel.git diff --git a/src/qtui/chatitem.cpp b/src/qtui/chatitem.cpp index 568cdccf..10b7d780 100644 --- a/src/qtui/chatitem.cpp +++ b/src/qtui/chatitem.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005-08 by the Quassel Project * + * Copyright (C) 2005-2018 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -15,156 +15,995 @@ * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ +#include "chatitem.h" + +#include +#include + +#include +#include +#include #include #include -#include -#include - -#include +#include +#include +#include +#include -#include "chatitem.h" +#include "buffermodel.h" +#include "bufferview.h" +#include "chatline.h" #include "chatlinemodel.h" +#include "chatview.h" +#include "contextmenuactionprovider.h" +#include "icon.h" +#include "mainwin.h" #include "qtui.h" +#include "qtuistyle.h" + +ChatItem::ChatItem(const QRectF &boundingRect, ChatLine *parent) + : _parent(parent), + _boundingRect(boundingRect), + _selectionMode(NoSelection), + _selectionStart(-1), + _cachedLayout(0) +{ +} + + +ChatItem::~ChatItem() +{ + delete _cachedLayout; +} + + +ChatLine *ChatItem::chatLine() const +{ + return _parent; +} + + +ChatScene *ChatItem::chatScene() const +{ + return chatLine()->chatScene(); +} -ChatItem::ChatItem(const QPersistentModelIndex &index_, QGraphicsItem *parent) : QGraphicsItem(parent), _index(index_) { - QFontMetricsF *metrics = QtUi::style()->fontMetrics(data(ChatLineModel::FormatRole).value().at(0).second); - _lineHeight = metrics->lineSpacing(); + +ChatView *ChatItem::chatView() const +{ + return chatScene()->chatView(); +} + + +const QAbstractItemModel *ChatItem::model() const +{ + return chatLine()->model(); } -ChatItem::~ChatItem() { +int ChatItem::row() const +{ + return chatLine()->row(); } -QVariant ChatItem::data(int role) const { - if(!_index.isValid()) { - qWarning() << "ChatItem::data(): Model index is invalid!" << _index; - return QVariant(); - } - return _index.data(role); + +QPointF ChatItem::mapToLine(const QPointF &p) const +{ + return p + pos(); } -/* -QRectF ChatItem::boundingRect() const { - return QRectF(0, 0, _width, _height); + +QPointF ChatItem::mapFromLine(const QPointF &p) const +{ + return p - pos(); } -*/ -void ChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { - Q_UNUSED(option); Q_UNUSED(widget); - painter->drawText(boundingRect(), data(MessageModel::DisplayRole).toString()); - painter->setPen(Qt::DotLine); - painter->drawRect(boundingRect()); +// relative to the ChatLine +QPointF ChatItem::mapToScene(const QPointF &p) const +{ + return chatLine()->mapToScene(p /* + pos() */); } -int ChatItem::setWidth(int w) { - if(w == _boundingRect.width()) return _boundingRect.height(); - int h = heightForWidth(w); - _boundingRect.setWidth(w); - _boundingRect.setHeight(h); - return h; + +QPointF ChatItem::mapFromScene(const QPointF &p) const +{ + return chatLine()->mapFromScene(p) /* - pos() */; } -int ChatItem::heightForWidth(int width) { - if(data(ChatLineModel::ColumnTypeRole).toUInt() != ChatLineModel::ContentsColumn) - return _lineHeight; // only contents can be multi-line - QVariantList wrapList = data(ChatLineModel::WrapListRole).toList(); - int lines = 1; - int offset = 0; - for(int i = 0; i < wrapList.count(); i+=2) { - if(wrapList.at(i+1).toUInt() - offset < width) continue; - lines++; - if(i > 0) { - if(offset != wrapList.at(i-1).toUInt()) offset = wrapList.at(i-1).toUInt(); - else offset += width; - } else { - offset += width; +QVariant ChatItem::data(int role) const +{ + QModelIndex index = model()->index(row(), column()); + if (!index.isValid()) { + qWarning() << "ChatItem::data(): model index is invalid!" << index; + return QVariant(); } - i-=2; - } - return lines * _lineHeight; + return model()->data(index, role); } -/* -void ChatItem::setTextOption(const QTextOption &option) { - _textOption = option; - layout(); +QTextLayout *ChatItem::layout() const +{ + if (_cachedLayout) + return _cachedLayout; + + _cachedLayout = new QTextLayout; + initLayout(_cachedLayout); + chatView()->setHasCache(chatLine()); + return _cachedLayout; } -QTextOption ChatItem::textOption() const { - return _textOption; + +void ChatItem::clearCache() +{ + delete _cachedLayout; + _cachedLayout = 0; } -QString ChatItem::text() const { - return _layout.text(); + +void ChatItem::initLayoutHelper(QTextLayout *layout, QTextOption::WrapMode wrapMode, Qt::Alignment alignment) const +{ + Q_ASSERT(layout); + + layout->setText(data(MessageModel::DisplayRole).toString()); + + QTextOption option; + option.setWrapMode(wrapMode); + option.setAlignment(alignment); + layout->setTextOption(option); + + QList formatRanges + = QtUi::style()->toTextLayoutList(formatList(), layout->text().length(), data(ChatLineModel::MsgLabelRole).value()); + layout->setAdditionalFormats(formatRanges); } -void ChatItem::setText(const UiStyle::StyledText &text) { - _layout.setText(text.text); - _layout.setAdditionalFormats(text.formatList); - layout(); + +void ChatItem::initLayout(QTextLayout *layout) const +{ + initLayoutHelper(layout, QTextOption::NoWrap); + doLayout(layout); } -void ChatItem::layout() { - if(!_layout.additionalFormats().count()) return; // no text set - if(_width <= 0) return; - prepareGeometryChange(); - QFontMetrics metrics(_layout.additionalFormats()[0].format.font()); - int leading = metrics.leading(); - int height = 0; - _layout.setTextOption(textOption()); - _layout.beginLayout(); - while(1) { - QTextLine line = _layout.createLine(); - if(!line.isValid()) break; - line.setLineWidth(_width); - if(textOption().wrapMode() != QTextOption::NoWrap && line.naturalTextWidth() > _width) { - // word did not fit, we need to wrap it in the middle - // this is a workaround for Qt failing to handle WrapAtWordBoundaryOrAnywhere correctly - QTextOption::WrapMode mode = textOption().wrapMode(); - textOption().setWrapMode(QTextOption::WrapAnywhere); - _layout.setTextOption(textOption()); - line.setLineWidth(_width); - textOption().setWrapMode(mode); - _layout.setTextOption(textOption()); + +void ChatItem::doLayout(QTextLayout *layout) const +{ + layout->beginLayout(); + QTextLine line = layout->createLine(); + if (line.isValid()) { + line.setLineWidth(width()); + line.setPosition(QPointF(0, 0)); } - height += leading; - line.setPosition(QPoint(0, height)); - height += line.height(); - } - _layout.endLayout(); - update(); -} QDateTime _timestamp; - MsgId _msgId; + layout->endLayout(); +} -QRectF ChatItem::boundingRect() const { - return _layout.boundingRect(); +UiStyle::FormatList ChatItem::formatList() const +{ + return data(MessageModel::FormatRole).value(); } -void ChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { - Q_UNUSED(option); Q_UNUSED(widget); - _layout.draw(painter, QPointF(0, 0)); +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); + else + bgBrush = data(ChatLineModel::BackgroundRole); + if (bgBrush.isValid()) + painter->fillRect(boundingRect(), bgBrush.value()); +} + + +// 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); + painter->save(); + painter->setClipRect(boundingRect()); + paintBackground(painter); + + layout()->draw(painter, pos(), additionalFormats(), boundingRect()); + + // layout()->draw(painter, QPointF(0,0), formats, boundingRect()); + + // Debuging Stuff + // uncomment partially or all of the following stuff: + // + // 0) alternativ painter color for debug stuff +// if(row() % 2) +// painter->setPen(Qt::red); +// else +// painter->setPen(Qt::blue); +// 1) draw wordwrap points in the first line +// if(column() == 2) { +// ChatLineModel::WrapList wrapList = data(ChatLineModel::WrapListRole).value(); +// foreach(ChatLineModel::Word word, wrapList) { +// if(word.endX > width()) +// break; +// painter->drawLine(word.endX, 0, word.endX, height()); +// } +// } +// 2) draw MsgId over the time column +// if(column() == 0) { +// QString msgIdString = QString::number(data(MessageModel::MsgIdRole).value().toLongLong()); +// QPointF bottomPoint = boundingRect().bottomLeft(); +// bottomPoint.ry() -= 2; +// painter->drawText(bottomPoint, msgIdString); +// } +// 3) draw bounding rect +// painter->drawRect(_boundingRect.adjusted(0, 0, -1, -1)); + + painter->restore(); } -*/ -/* -void ChatItem::mouseMoveEvent ( QGraphicsSceneMouseEvent * event ) { - qDebug() << (void*)this << "moving" << event->pos(); - if(event->pos().y() < 0) { - QTextCursor cursor(document()); - //cursor.insertText("foo"); - //cursor.select(QTextCursor::Document); - event->ignore(); - } else QGraphicsTextItem::mouseMoveEvent(event); + +void ChatItem::overlayFormat(UiStyle::FormatList &fmtList, quint16 start, quint16 end, UiStyle::FormatType overlayFmt) const +{ + for (size_t i = 0; i < fmtList.size(); i++) { + int fmtStart = fmtList.at(i).first; + int fmtEnd = (i < fmtList.size()-1 ? fmtList.at(i+1).first : data(MessageModel::DisplayRole).toString().length()); + + if (fmtEnd <= start) + continue; + if (fmtStart >= end) + break; + + // split the format if necessary + if (fmtStart < start) { + fmtList.insert(fmtList.begin() + i, fmtList.at(i)); + fmtList[++i].first = start; + } + if (end < fmtEnd) { + fmtList.insert(fmtList.begin() + i, fmtList.at(i)); + fmtList[i+1].first = end; + } + + fmtList[i].second.type |= overlayFmt; + } +} + + +QVector ChatItem::additionalFormats() const +{ + // Calculate formats to overlay (only) if there's a selection, and/or a hovered clickable + if (!hasSelection() && !hasActiveClickable()) { + return {}; + } + + using Label = UiStyle::MessageLabel; + using Format = UiStyle::Format; + + Label itemLabel = data(ChatLineModel::MsgLabelRole).value