X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fqtui%2Fchatitem.cpp;h=913c81e72784bcf06688ea5b8bf7b7fb979286ab;hp=5e2942cc712c7c4f98b8dbf09c148301d4f5ff41;hb=3dd76d9373fb46b7be3f7f963b3d3a38ded63ae5;hpb=b1d9c3ea56467245c910ac1de1252c2a91c234f0 diff --git a/src/qtui/chatitem.cpp b/src/qtui/chatitem.cpp index 5e2942cc..913c81e7 100644 --- a/src/qtui/chatitem.cpp +++ b/src/qtui/chatitem.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005-2010 by the Quassel Project * + * Copyright (C) 2005-2019 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -15,457 +15,576 @@ * 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 "action.h" #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 "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(const QRectF& boundingRect, ChatLine* parent) + : _parent(parent) + , _boundingRect(boundingRect) + , _selectionMode(NoSelection) + , _selectionStart(-1) + , _cachedLayout(nullptr) +{} -ChatItem::~ChatItem() { - delete _cachedLayout; +ChatItem::~ChatItem() +{ + delete _cachedLayout; } -ChatLine *ChatItem::chatLine() const { - return _parent; +ChatLine* ChatItem::chatLine() const +{ + return _parent; } -ChatScene *ChatItem::chatScene() const { - return chatLine()->chatScene(); +ChatScene* ChatItem::chatScene() const +{ + return chatLine()->chatScene(); } -ChatView *ChatItem::chatView() const { - return chatScene()->chatView(); +ChatView* ChatItem::chatView() const +{ + return chatScene()->chatView(); } -const QAbstractItemModel *ChatItem::model() const { - return chatLine()->model(); +const QAbstractItemModel* ChatItem::model() const +{ + return chatLine()->model(); } -int ChatItem::row() const { - return chatLine()->row(); +int ChatItem::row() const +{ + return chatLine()->row(); } -QPointF ChatItem::mapToLine(const QPointF &p) const { - return p + pos(); +QPointF ChatItem::mapToLine(const QPointF& p) const +{ + return p + pos(); } -QPointF ChatItem::mapFromLine(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::mapToScene(const QPointF& p) const +{ + return chatLine()->mapToScene(p /* + pos() */); } -QPointF ChatItem::mapFromScene(const QPointF &p) const { - return chatLine()->mapFromScene(p) /* - pos() */; +QPointF ChatItem::mapFromScene(const QPointF& p) const +{ + return chatLine()->mapFromScene(p) /* - pos() */; } -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(); - } - return model()->data(index, role); +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(); + } + return model()->data(index, role); } -QTextLayout *ChatItem::layout() const { - if(_cachedLayout) - return _cachedLayout; +QTextLayout* ChatItem::layout() const +{ + if (_cachedLayout) + return _cachedLayout; - _cachedLayout = new QTextLayout; - initLayout(_cachedLayout); - chatView()->setHasCache(chatLine()); - return _cachedLayout; + _cachedLayout = new QTextLayout; + initLayout(_cachedLayout); + chatView()->setHasCache(chatLine()); + return _cachedLayout; } -void ChatItem::clearCache() { - delete _cachedLayout; - _cachedLayout = 0; +void ChatItem::clearCache() +{ + delete _cachedLayout; + _cachedLayout = nullptr; } -void ChatItem::initLayoutHelper(QTextLayout *layout, QTextOption::WrapMode wrapMode, Qt::Alignment alignment) const { - Q_ASSERT(layout); +void ChatItem::initLayoutHelper(QTextLayout* layout, QTextOption::WrapMode wrapMode, Qt::Alignment alignment) const +{ + Q_ASSERT(layout); - layout->setText(data(MessageModel::DisplayRole).toString()); + layout->setText(data(MessageModel::DisplayRole).toString()); - QTextOption option; - option.setWrapMode(wrapMode); - option.setAlignment(alignment); - layout->setTextOption(option); + QTextOption option; + option.setWrapMode(wrapMode); + option.setAlignment(alignment); + layout->setTextOption(option); - QList formatRanges - = QtUi::style()->toTextLayoutList(formatList(), layout->text().length(), data(ChatLineModel::MsgLabelRole).toUInt()); - layout->setAdditionalFormats(formatRanges); + UiStyle::FormatContainer formatRanges = QtUi::style()->toTextLayoutList( + formatList(), + layout->text().length(), + data(ChatLineModel::MsgLabelRole).value() + ); + UiStyle::setTextLayoutFormats(*layout, formatRanges); } -void ChatItem::initLayout(QTextLayout *layout) const { - initLayoutHelper(layout, QTextOption::NoWrap); - doLayout(layout); +void ChatItem::initLayout(QTextLayout* layout) const +{ + initLayoutHelper(layout, QTextOption::NoWrap); + doLayout(layout); } -void ChatItem::doLayout(QTextLayout *layout) const { - layout->beginLayout(); - QTextLine line = layout->createLine(); - if(line.isValid()) { - line.setLineWidth(width()); - line.setPosition(QPointF(0,0)); - } - layout->endLayout(); +void ChatItem::doLayout(QTextLayout* layout) const +{ + layout->beginLayout(); + QTextLine line = layout->createLine(); + if (line.isValid()) { + line.setLineWidth(width()); + line.setPosition(QPointF(0, 0)); + } + layout->endLayout(); } -UiStyle::FormatList ChatItem::formatList() const { - return data(MessageModel::FormatRole).value(); +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); +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; + 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()); +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().toInt()); -// 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::overlayFormat(UiStyle::FormatList &fmtList, int start, int end, quint32 overlayFmt) const { - for(int i = 0; i < fmtList.count(); i++) { - int fmtStart = fmtList.at(i).first; - int fmtEnd = (i < fmtList.count()-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(i, fmtList.at(i)); - fmtList[++i].first = start; - } - if(end < fmtEnd) { - fmtList.insert(i, fmtList.at(i)); - fmtList[i+1].first = end; - } - - fmtList[i].second |= overlayFmt; - } -} - -QVector ChatItem::additionalFormats() const { - return selectionFormats(); -} - -QVector ChatItem::selectionFormats() const { - if(!hasSelection()) - return QVector(); - - int start, end; - if(_selectionMode == FullSelection) { - start = 0; - end = data(MessageModel::DisplayRole).toString().length(); - } else { - start = qMin(_selectionStart, _selectionEnd); - end = qMax(_selectionStart, _selectionEnd); - } - - UiStyle::FormatList fmtList = formatList(); - - while(fmtList.count() > 1 && fmtList.at(1).first <= start) - fmtList.removeFirst(); - - fmtList.first().first = start; - - while(fmtList.count() > 1 && fmtList.last().first >= end) - fmtList.removeLast(); - - return QtUi::style()->toTextLayoutList(fmtList, end, UiStyle::Selected|data(ChatLineModel::MsgLabelRole).toUInt()).toVector(); -} - -bool ChatItem::hasSelection() const { - if(_selectionMode == NoSelection) - return false; - if(_selectionMode == FullSelection) - return true; - // partial - return _selectionStart != _selectionEnd; +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::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; + } } -QString ChatItem::selection() const { - if(_selectionMode == FullSelection) - return data(MessageModel::DisplayRole).toString(); - if(_selectionMode == PartialSelection) - return data(MessageModel::DisplayRole).toString().mid(qMin(_selectionStart, _selectionEnd), qAbs(_selectionStart - _selectionEnd)); - return QString(); +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; + + auto itemLabel = data(ChatLineModel::MsgLabelRole).value