qa: Remove lots of superfluous semicolons
[quassel.git] / src / qtui / chatitem.cpp
index e9922d2..9fadc12 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-2016 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  *
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
  ***************************************************************************/
 
+#include "chatitem.h"
+
+#include <algorithm>
+#include <iterator>
+
 #include <QApplication>
 #include <QClipboard>
 #include <QDesktopServices>
 #include <QFontMetrics>
 #include <QGraphicsSceneMouseEvent>
-#include <QIcon>
 #include <QPainter>
 #include <QPalette>
 #include <QTextLayout>
 
 #include "buffermodel.h"
 #include "bufferview.h"
-#include "chatitem.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"
@@ -245,7 +249,7 @@ void ChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
 //   }
 // 2) draw MsgId over the time column
 //   if(column() == 0) {
-//     QString msgIdString = QString::number(data(MessageModel::MsgIdRole).value<MsgId>().toInt());
+//     QString msgIdString = QString::number(data(MessageModel::MsgIdRole).value<MsgId>().toLongLong());
 //     QPointF bottomPoint = boundingRect().bottomLeft();
 //     bottomPoint.ry() -= 2;
 //     painter->drawText(bottomPoint, msgIdString);
@@ -285,36 +289,92 @@ void ChatItem::overlayFormat(UiStyle::FormatList &fmtList, quint16 start, quint1
 
 QVector<QTextLayout::FormatRange> ChatItem::additionalFormats() const
 {
-    return selectionFormats();
-}
+    // 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;
 
-QVector<QTextLayout::FormatRange> ChatItem::selectionFormats() const
-{
-    if (!hasSelection())
-        return QVector<QTextLayout::FormatRange>();
+    Label itemLabel = data(ChatLineModel::MsgLabelRole).value<Label>();
+    const auto &fmtList = formatList();
 
-    int start, end;
-    if (_selectionMode == FullSelection) {
-        start = 0;
-        end = data(MessageModel::DisplayRole).toString().length();
-    }
-    else {
-        start = qMin(_selectionStart, _selectionEnd);
-        end = qMax(_selectionStart, _selectionEnd);
-    }
+    struct LabelFormat {
+        quint16 offset;
+        Format format;
+        Label label;
+    };
 
-    UiStyle::FormatList fmtList = formatList();
+    // Transform formatList() into an extended list of LabelFormats
+    std::vector<LabelFormat> labelFmtList;
+    std::transform(fmtList.cbegin(), fmtList.cend(), std::back_inserter(labelFmtList), [itemLabel](const std::pair<quint16, Format> &f) {
+        return LabelFormat{f.first, f.second, itemLabel};
+    });
+    // Append dummy element to avoid special-casing handling the last real format
+    labelFmtList.push_back(LabelFormat{quint16(data(MessageModel::DisplayRole).toString().length()), Format(), itemLabel});
 
-    while (fmtList.size() > 1 && fmtList.at(1).first <= start)
-        fmtList.erase(fmtList.begin());
+    // Apply the given label to the given range in the format list, splitting formats as necessary
+    auto applyLabel = [&labelFmtList](quint16 start, quint16 end, Label label) {
+        size_t i = 0;
 
-    fmtList.front().first = start;
+        // Skip unaffected formats
+        for (; i < labelFmtList.size() - 1; ++i) {
+            if (labelFmtList[i+1].offset > start)
+                break;
+        }
+        // Range start doesn't align; split affected format and let the index point to the newly inserted copy
+        if (labelFmtList[i].offset < start) {
+            labelFmtList.insert(labelFmtList.begin() + i, labelFmtList[i]);
+            labelFmtList[++i].offset = start;
+        }
 
-    while (fmtList.size() > 1 && fmtList.back().first >= end)
-        fmtList.pop_back();
+        // Apply label to formats fully affected
+        for (; i < labelFmtList.size() - 1; ++i) {
+            if (labelFmtList[i+1].offset <= end) {
+                labelFmtList[i].label |= label;
+                continue;
+            }
+            // Last affected format, split if end of range doesn't align
+            if (labelFmtList[i+1].offset > end) {
+                labelFmtList.insert(labelFmtList.begin() + i, labelFmtList[i]);
+                labelFmtList[i].label |= label;
+                labelFmtList[i+1].offset = end;
+            }
+            break;
+        }
+    };
+
+    // Apply selection label
+    if (hasSelection()) {
+        quint16 start, end;
+        if (_selectionMode == FullSelection) {
+            start = 0;
+            end = data(MessageModel::DisplayRole).toString().length();
+        }
+        else {
+            start = qMin(_selectionStart, _selectionEnd);
+            end = qMax(_selectionStart, _selectionEnd);
+        }
+        applyLabel(start, end, Label::Selected);
+    }
+
+    // Apply hovered label
+    if (hasActiveClickable()) {
+        applyLabel(activeClickableRange().first, activeClickableRange().second, Label::Hovered);
+    }
+
+    // Add all formats that have an extra label to the additionalFormats list
+    QList<QTextLayout::FormatRange> additionalFormats;
+    for (size_t i = 0; i < labelFmtList.size() - 1; ++i) {
+        if (labelFmtList[i].label != itemLabel) {
+            additionalFormats << QtUi::style()->toTextLayoutList({std::make_pair(labelFmtList[i].offset, labelFmtList[i].format)},
+                                                                 labelFmtList[i+1].offset,
+                                                                 labelFmtList[i].label);
+        }
+    }
 
-    return QtUi::style()->toTextLayoutList(fmtList, end, data(ChatLineModel::MsgLabelRole).value<UiStyle::MessageLabel>()|UiStyle::MessageLabel::Selected).toVector();
+    return additionalFormats.toVector();
 }
 
 
@@ -386,6 +446,18 @@ bool ChatItem::isPosOverSelection(const QPointF &pos) const
 }
 
 
+bool ChatItem::hasActiveClickable() const
+{
+    return false;
+}
+
+
+std::pair<quint16, quint16> ChatItem::activeClickableRange() const
+{
+    return {};
+}
+
+
 QList<QRectF> ChatItem::findWords(const QString &searchWord, Qt::CaseSensitivity caseSensitive)
 {
     QList<QRectF> resultList;
@@ -662,6 +734,22 @@ void ContentsChatItem::doLayout(QTextLayout *layout) const
 }
 
 
+bool ContentsChatItem::hasActiveClickable() const
+{
+    return privateData()->currentClickable.isValid();
+}
+
+
+std::pair<quint16, quint16> ContentsChatItem::activeClickableRange() const
+{
+    const auto &clickable = privateData()->currentClickable;
+    if (clickable.isValid()) {
+        return {clickable.start(), clickable.start() + clickable.length()};
+    }
+    return {};
+}
+
+
 Clickable ContentsChatItem::clickableAt(const QPointF &pos) const
 {
     return privateData()->clickables.atCursorPos(posToCursor(pos));
@@ -681,22 +769,6 @@ UiStyle::FormatList ContentsChatItem::formatList() const
 }
 
 
-QVector<QTextLayout::FormatRange> ContentsChatItem::additionalFormats() const
-{
-    QVector<QTextLayout::FormatRange> fmt = ChatItem::additionalFormats();
-    // mark a clickable if hovered upon
-    if (privateData()->currentClickable.isValid()) {
-        Clickable click = privateData()->currentClickable;
-        QTextLayout::FormatRange f;
-        f.start = click.start();
-        f.length = click.length();
-        f.format.setFontUnderline(true);
-        fmt.append(f);
-    }
-    return fmt;
-}
-
-
 void ContentsChatItem::endHoverMode()
 {
     if (privateData()) {
@@ -798,7 +870,7 @@ void ContentsChatItem::addActionsToMenu(QMenu *menu, const QPointF &pos)
         switch (click.type()) {
         case Clickable::Url:
             privateData()->activeClickable = click;
-            menu->addAction(QIcon::fromTheme("edit-copy"), tr("Copy Link Address"),
+            menu->addAction(icon::get("edit-copy"), tr("Copy Link Address"),
                 &_actionProxy, SLOT(copyLinkToClipboard()))->setData(QVariant::fromValue<void *>(this));
             break;
         case Clickable::Channel: