modernize: Pass arguments by value and move in constructors
[quassel.git] / src / qtui / chatscene.cpp
index 1d9d94e..78c33c8 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
 /***************************************************************************
- *   Copyright (C) 2005-2015 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  *
  *   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.         *
  ***************************************************************************/
 
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
  ***************************************************************************/
 
+#include "chatscene.h"
+
+#include <utility>
+
 #include <QApplication>
 #include <QClipboard>
 #include <QDesktopServices>
 #include <QDrag>
 #include <QGraphicsSceneMouseEvent>
 #include <QApplication>
 #include <QClipboard>
 #include <QDesktopServices>
 #include <QDrag>
 #include <QGraphicsSceneMouseEvent>
-#include <QIcon>
 #include <QMenu>
 #include <QMenuBar>
 #include <QMimeData>
 #include <QPersistentModelIndex>
 #include <QUrl>
 
 #include <QMenu>
 #include <QMenuBar>
 #include <QMimeData>
 #include <QPersistentModelIndex>
 #include <QUrl>
 
-#ifdef HAVE_KDE
-#  include <KMenuBar>
-#else
-#  include <QMenuBar>
-#endif
-
-#ifdef HAVE_WEBKIT
+#ifdef HAVE_WEBENGINE
+#  include <QWebEngineView>
+#elif defined HAVE_WEBKIT
 #  include <QWebView>
 #endif
 
 #include "chatitem.h"
 #include "chatline.h"
 #include "chatlinemodelitem.h"
 #  include <QWebView>
 #endif
 
 #include "chatitem.h"
 #include "chatline.h"
 #include "chatlinemodelitem.h"
-#include "chatscene.h"
 #include "chatview.h"
 #include "chatview.h"
+#include "chatviewsettings.h"
 #include "client.h"
 #include "clientbacklogmanager.h"
 #include "columnhandleitem.h"
 #include "contextmenuactionprovider.h"
 #include "client.h"
 #include "clientbacklogmanager.h"
 #include "columnhandleitem.h"
 #include "contextmenuactionprovider.h"
+#include "icon.h"
 #include "mainwin.h"
 #include "markerlineitem.h"
 #include "messagefilter.h"
 #include "qtui.h"
 #include "qtuistyle.h"
 #include "mainwin.h"
 #include "markerlineitem.h"
 #include "messagefilter.h"
 #include "qtui.h"
 #include "qtuistyle.h"
-#include "chatviewsettings.h"
 #include "webpreviewitem.h"
 
 const qreal minContentsWidth = 200;
 
 #include "webpreviewitem.h"
 
 const qreal minContentsWidth = 200;
 
-ChatScene::ChatScene(QAbstractItemModel *model, const QString &idString, qreal width, ChatView *parent)
+ChatScene::ChatScene(QAbstractItemModel *model, QString idString, qreal width, ChatView *parent)
     : QGraphicsScene(0, 0, width, 0, (QObject *)parent),
     _chatView(parent),
     : QGraphicsScene(0, 0, width, 0, (QObject *)parent),
     _chatView(parent),
-    _idString(idString),
+    _idString(std::move(idString)),
     _model(model),
     _singleBufferId(BufferId()),
     _sceneRect(0, 0, width, 0),
     _model(model),
     _singleBufferId(BufferId()),
     _sceneRect(0, 0, width, 0),
@@ -73,7 +72,8 @@ ChatScene::ChatScene(QAbstractItemModel *model, const QString &idString, qreal w
     _markerLineValid(false),
     _markerLineJumpPending(false),
     _cutoffMode(CutoffRight),
     _markerLineValid(false),
     _markerLineJumpPending(false),
     _cutoffMode(CutoffRight),
-    _selectingItem(0),
+    _alwaysBracketSender(false),
+    _selectingItem(nullptr),
     _selectionStart(-1),
     _isSelecting(false),
     _clickMode(NoClick),
     _selectionStart(-1),
     _isSelecting(false),
     _clickMode(NoClick),
@@ -122,13 +122,23 @@ ChatScene::ChatScene(QAbstractItemModel *model, const QString &idString, qreal w
         this, SLOT(rowsRemoved()));
     connect(model, SIGNAL(dataChanged(QModelIndex, QModelIndex)), SLOT(dataChanged(QModelIndex, QModelIndex)));
 
         this, SLOT(rowsRemoved()));
     connect(model, SIGNAL(dataChanged(QModelIndex, QModelIndex)), SLOT(dataChanged(QModelIndex, QModelIndex)));
 
-#ifdef HAVE_WEBKIT
+#if defined HAVE_WEBKIT || defined HAVE_WEBENGINE
     webPreview.timer.setSingleShot(true);
     connect(&webPreview.timer, SIGNAL(timeout()), this, SLOT(webPreviewNextStep()));
 #endif
     _showWebPreview = defaultSettings.showWebPreview();
     defaultSettings.notify("ShowWebPreview", this, SLOT(showWebPreviewChanged()));
 
     webPreview.timer.setSingleShot(true);
     connect(&webPreview.timer, SIGNAL(timeout()), this, SLOT(webPreviewNextStep()));
 #endif
     _showWebPreview = defaultSettings.showWebPreview();
     defaultSettings.notify("ShowWebPreview", this, SLOT(showWebPreviewChanged()));
 
+    _showSenderBrackets = defaultSettings.showSenderBrackets();
+    defaultSettings.notify("ShowSenderBrackets", this, SLOT(showSenderBracketsChanged()));
+
+    _useCustomTimestampFormat = defaultSettings.useCustomTimestampFormat();
+    defaultSettings.notify("UseCustomTimestampFormat", this, SLOT(useCustomTimestampFormatChanged()));
+
+    _timestampFormatString = defaultSettings.timestampFormatString();
+    defaultSettings.notify("TimestampFormat", this, SLOT(timestampFormatStringChanged()));
+    updateTimestampHasBrackets();
+
     _clickTimer.setInterval(QApplication::doubleClickInterval());
     _clickTimer.setSingleShot(true);
     connect(&_clickTimer, SIGNAL(timeout()), SLOT(clickTimeout()));
     _clickTimer.setInterval(QApplication::doubleClickInterval());
     _clickTimer.setSingleShot(true);
     connect(&_clickTimer, SIGNAL(timeout()), SLOT(clickTimeout()));
@@ -174,7 +184,7 @@ void ChatScene::resetColumnWidths()
 ChatLine *ChatScene::chatLine(MsgId msgId, bool matchExact, bool ignoreDayChange) const
 {
     if (!_lines.count())
 ChatLine *ChatScene::chatLine(MsgId msgId, bool matchExact, bool ignoreDayChange) const
 {
     if (!_lines.count())
-        return 0;
+        return nullptr;
 
     QList<ChatLine *>::ConstIterator start = _lines.begin();
     QList<ChatLine *>::ConstIterator end = _lines.end();
 
     QList<ChatLine *>::ConstIterator start = _lines.begin();
     QList<ChatLine *>::ConstIterator end = _lines.end();
@@ -199,10 +209,10 @@ ChatLine *ChatScene::chatLine(MsgId msgId, bool matchExact, bool ignoreDayChange
         return *start;
 
     if (matchExact)
         return *start;
 
     if (matchExact)
-        return 0;
+        return nullptr;
 
     if (start == _lines.begin()) // not (yet?) in our scene
 
     if (start == _lines.begin()) // not (yet?) in our scene
-        return 0;
+        return nullptr;
 
     // if we didn't find the exact msgId, take the next-lower one (this makes sense for lastSeen)
 
 
     // if we didn't find the exact msgId, take the next-lower one (this makes sense for lastSeen)
 
@@ -214,7 +224,7 @@ ChatLine *ChatScene::chatLine(MsgId msgId, bool matchExact, bool ignoreDayChange
             if (_lines.at(i)->msgType() != Message::DayChange)
                 return _lines.at(i);
         }
             if (_lines.at(i)->msgType() != Message::DayChange)
                 return _lines.at(i);
         }
-        return 0;
+        return nullptr;
     }
 
     // return the next-lower line
     }
 
     // return the next-lower line
@@ -226,7 +236,7 @@ ChatLine *ChatScene::chatLine(MsgId msgId, bool matchExact, bool ignoreDayChange
             return *start;
     }
     while (start != _lines.begin());
             return *start;
     }
     while (start != _lines.begin());
-    return 0;
+    return nullptr;
 }
 
 
 }
 
 
@@ -237,7 +247,7 @@ ChatItem *ChatScene::chatItemAt(const QPointF &scenePos) const
         if (line)
             return line->itemAt(line->mapFromScene(scenePos));
     }
         if (line)
             return line->itemAt(line->mapFromScene(scenePos));
     }
-    return 0;
+    return nullptr;
 }
 
 
 }
 
 
@@ -411,7 +421,7 @@ void ChatScene::rowsInserted(const QModelIndex &index, int start, int end)
 
     // neither pre- or append means we have to do dirty work: move items...
     if (!(atTop || atBottom)) {
 
     // neither pre- or append means we have to do dirty work: move items...
     if (!(atTop || atBottom)) {
-        ChatLine *line = 0;
+        ChatLine *line = nullptr;
         for (int i = 0; i <= end; i++) {
             line = _lines.at(i);
             line->setPos(0, line->pos().y() - h);
         for (int i = 0; i <= end; i++) {
             line = _lines.at(i);
             line->setPos(0, line->pos().y() - h);
@@ -476,7 +486,7 @@ void ChatScene::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int e
     if (_selectingItem) {
         int row = _selectingItem->row();
         if (row >= start && row <= end)
     if (_selectingItem) {
         int row = _selectingItem->row();
         if (row >= start && row <= end)
-            setSelectingItem(0);
+            setSelectingItem(nullptr);
     }
 
     // remove items from scene
     }
 
     // remove items from scene
@@ -484,7 +494,7 @@ void ChatScene::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int e
     int lineCount = start;
     while (lineIter != _lines.end() && lineCount <= end) {
         if ((*lineIter) == markerLine()->chatLine())
     int lineCount = start;
     while (lineIter != _lines.end() && lineCount <= end) {
         if ((*lineIter) == markerLine()->chatLine())
-            markerLine()->setChatLine(0);
+            markerLine()->setChatLine(nullptr);
         h += (*lineIter)->height();
         delete *lineIter;
         lineIter = _lines.erase(lineIter);
         h += (*lineIter)->height();
         delete *lineIter;
         lineIter = _lines.erase(lineIter);
@@ -526,7 +536,7 @@ void ChatScene::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int e
             moveStart = start;
             offset = -offset;
         }
             moveStart = start;
             offset = -offset;
         }
-        ChatLine *line = 0;
+        ChatLine *line = nullptr;
         for (int i = moveStart; i <= moveEnd; i++) {
             line = _lines.at(i);
             line->setPos(0, line->pos().y() + offset);
         for (int i = moveStart; i <= moveEnd; i++) {
             line = _lines.at(i);
             line->setPos(0, line->pos().y() + offset);
@@ -651,7 +661,7 @@ void ChatScene::firstHandlePositionChanged(qreal xpos)
     QPointF senderPos(firstColumnHandle()->sceneRight(), 0);
 
     while (lineIter != lineIterBegin) {
     QPointF senderPos(firstColumnHandle()->sceneRight(), 0);
 
     while (lineIter != lineIterBegin) {
-        lineIter--;
+        --lineIter;
         (*lineIter)->setFirstColumn(timestampWidth, senderWidth, senderPos);
     }
     //setItemIndexMethod(QGraphicsScene::BspTreeIndex);
         (*lineIter)->setFirstColumn(timestampWidth, senderWidth, senderPos);
     }
     //setItemIndexMethod(QGraphicsScene::BspTreeIndex);
@@ -687,7 +697,7 @@ void ChatScene::secondHandlePositionChanged(qreal xpos)
     qreal contentsWidth = _sceneRect.width() - secondColumnHandle()->sceneRight();
     QPointF contentsPos(secondColumnHandle()->sceneRight(), 0);
     while (lineIter != lineIterBegin) {
     qreal contentsWidth = _sceneRect.width() - secondColumnHandle()->sceneRight();
     QPointF contentsPos(secondColumnHandle()->sceneRight(), 0);
     while (lineIter != lineIterBegin) {
-        lineIter--;
+        --lineIter;
         (*lineIter)->setSecondColumn(senderWidth, contentsWidth, contentsPos, linePos);
     }
     //setItemIndexMethod(QGraphicsScene::BspTreeIndex);
         (*lineIter)->setSecondColumn(senderWidth, contentsWidth, contentsPos, linePos);
     }
     //setItemIndexMethod(QGraphicsScene::BspTreeIndex);
@@ -823,7 +833,7 @@ void ChatScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
     // If we have text selected, insert the Copy Selection as first item
     if (isPosOverSelection(pos)) {
         QAction *sep = menu.insertSeparator(menu.actions().first());
     // If we have text selected, insert the Copy Selection as first item
     if (isPosOverSelection(pos)) {
         QAction *sep = menu.insertSeparator(menu.actions().first());
-        QAction *act = new Action(QIcon::fromTheme("edit-copy"), tr("Copy Selection"), &menu, this,
+        QAction *act = new Action(icon::get("edit-copy"), tr("Copy Selection"), &menu, this,
             SLOT(selectionToClipboard()), QKeySequence::Copy);
         menu.insertAction(sep, act);
 
             SLOT(selectionToClipboard()), QKeySequence::Copy);
         menu.insertAction(sep, act);
 
@@ -832,7 +842,8 @@ void ChatScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
             searchSelectionText = searchSelectionText.left(_webSearchSelectionTextMaxVisible).append(QString::fromUtf8("…"));
         searchSelectionText = tr("Search '%1'").arg(searchSelectionText);
 
             searchSelectionText = searchSelectionText.left(_webSearchSelectionTextMaxVisible).append(QString::fromUtf8("…"));
         searchSelectionText = tr("Search '%1'").arg(searchSelectionText);
 
-        menu.addAction(QIcon::fromTheme("edit-find"), searchSelectionText, this, SLOT(webSearchOnSelection()));
+        QAction *webSearchAction = new Action(icon::get("edit-find"), searchSelectionText, &menu, this, SLOT(webSearchOnSelection()));
+        menu.insertAction(sep, webSearchAction);
     }
 
     if (QtUi::mainWindow()->menuBar()->isHidden())
     }
 
     if (QtUi::mainWindow()->menuBar()->isHidden())
@@ -1020,12 +1031,39 @@ QString ChatScene::selection() const
             return QString();
         }
         QString result;
             return QString();
         }
         QString result;
+
         for (int l = start; l <= end; l++) {
         for (int l = start; l <= end; l++) {
-            if (_selectionMinCol == ChatLineModel::TimestampColumn)
-                result += _lines[l]->item(ChatLineModel::TimestampColumn)->data(MessageModel::DisplayRole).toString() + " ";
-            if (_selectionMinCol <= ChatLineModel::SenderColumn)
-                result += _lines[l]->item(ChatLineModel::SenderColumn)->data(MessageModel::DisplayRole).toString() + " ";
-            result += _lines[l]->item(ChatLineModel::ContentsColumn)->data(MessageModel::DisplayRole).toString() + "\n";
+            if (_selectionMinCol == ChatLineModel::TimestampColumn) {
+                ChatItem *item = _lines[l]->item(ChatLineModel::TimestampColumn);
+                if (!_showSenderBrackets && !_timestampHasBrackets) {
+                    // Only re-add brackets if the current timestamp format does not include them
+                    // -and- sender brackets are disabled.  Don't filter on Message::Plain as
+                    // timestamp brackets affect all types of messages.
+                    // Remove any spaces before and after, otherwise it may look weird.
+                    result += QString("[%1] ").arg(item->data(MessageModel::DisplayRole)
+                                                   .toString().trimmed());
+                } else {
+                    result += item->data(MessageModel::DisplayRole).toString() + " ";
+                }
+            }
+            if (_selectionMinCol <= ChatLineModel::SenderColumn) {
+                ChatItem *item = _lines[l]->item(ChatLineModel::SenderColumn);
+                if (!_showSenderBrackets && (_alwaysBracketSender
+                                             || item->chatLine()->msgType() == Message::Plain)) {
+                    // Copying to plain-text.  Re-add the sender brackets if they're normally hidden
+                    // for...
+                    // * Plain messages
+                    // * All messages in the Chat Monitor
+                    //
+                    // The Chat Monitor sets alwaysBracketSender() to true.
+                    result += QString("<%1> ").arg(item->data(MessageModel::DisplayRole)
+                                                   .toString());
+                } else {
+                    result += item->data(MessageModel::DisplayRole).toString() + " ";
+                }
+            }
+            result += _lines[l]->item(ChatLineModel::ContentsColumn)
+                    ->data(MessageModel::DisplayRole).toString() + "\n";
         }
         return result;
     }
         }
         return result;
     }
@@ -1170,9 +1208,9 @@ void ChatScene::updateSceneRect(const QRectF &rect)
 
 
 // ========================================
 
 
 // ========================================
-//  Webkit Only stuff
+//  Webkit/WebEngine Only stuff
 // ========================================
 // ========================================
-#ifdef HAVE_WEBKIT
+#if defined HAVE_WEBKIT || defined HAVE_WEBENGINE
 void ChatScene::loadWebPreview(ChatItem *parentItem, const QUrl &url, const QRectF &urlRect)
 {
     if (!_showWebPreview)
 void ChatScene::loadWebPreview(ChatItem *parentItem, const QUrl &url, const QRectF &urlRect)
 {
     if (!_showWebPreview)
@@ -1191,7 +1229,7 @@ void ChatScene::loadWebPreview(ChatItem *parentItem, const QUrl &url, const QRec
             if (webPreview.previewItem->scene())
                 removeItem(webPreview.previewItem);
             delete webPreview.previewItem;
             if (webPreview.previewItem->scene())
                 removeItem(webPreview.previewItem);
             delete webPreview.previewItem;
-            webPreview.previewItem = 0;
+            webPreview.previewItem = nullptr;
         }
         webPreview.previewState = WebPreview::NoPreview;
     }
         }
         webPreview.previewState = WebPreview::NoPreview;
     }
@@ -1252,15 +1290,18 @@ void ChatScene::webPreviewNextStep()
     case WebPreview::ShowPreview:
         qWarning() << "ChatScene::webPreviewNextStep() called while in ShowPreview Step!";
         qWarning() << "removing preview";
     case WebPreview::ShowPreview:
         qWarning() << "ChatScene::webPreviewNextStep() called while in ShowPreview Step!";
         qWarning() << "removing preview";
-        if (webPreview.previewItem && webPreview.previewItem->scene())
+        if (webPreview.previewItem && webPreview.previewItem->scene()) {
             removeItem(webPreview.previewItem);
             removeItem(webPreview.previewItem);
-    // Fall through to deletion!
+        }
+
+        // Intentional fallthrough
+
     case WebPreview::HidePreview:
         if (webPreview.previewItem) {
             delete webPreview.previewItem;
     case WebPreview::HidePreview:
         if (webPreview.previewItem) {
             delete webPreview.previewItem;
-            webPreview.previewItem = 0;
+            webPreview.previewItem = nullptr;
         }
         }
-        webPreview.parentItem = 0;
+        webPreview.parentItem = nullptr;
         webPreview.url = QUrl();
         webPreview.urlRect = QRectF();
         webPreview.previewState = WebPreview::NoPreview;
         webPreview.url = QUrl();
         webPreview.urlRect = QRectF();
         webPreview.previewState = WebPreview::NoPreview;
@@ -1277,11 +1318,13 @@ void ChatScene::clearWebPreview(ChatItem *parentItem)
         webPreview.previewState = WebPreview::NoPreview; // we haven't loaded anything yet
         break;
     case WebPreview::ShowPreview:
         webPreview.previewState = WebPreview::NoPreview; // we haven't loaded anything yet
         break;
     case WebPreview::ShowPreview:
-        if (parentItem == 0 || webPreview.parentItem == parentItem) {
+        if (parentItem == nullptr || webPreview.parentItem == parentItem) {
             if (webPreview.previewItem && webPreview.previewItem->scene())
                 removeItem(webPreview.previewItem);
         }
             if (webPreview.previewItem && webPreview.previewItem->scene())
                 removeItem(webPreview.previewItem);
         }
-    // fall through into to set hidden state
+
+        // Intentional fallthrough
+
     case WebPreview::DelayPreview:
         // we're just loading, so haven't shown the preview yet.
         webPreview.previewState = WebPreview::HidePreview;
     case WebPreview::DelayPreview:
         // we're just loading, so haven't shown the preview yet.
         webPreview.previewState = WebPreview::HidePreview;
@@ -1301,8 +1344,60 @@ void ChatScene::clearWebPreview(ChatItem *parentItem)
 //  end of webkit only
 // ========================================
 
 //  end of webkit only
 // ========================================
 
+// Local configuration caching
 void ChatScene::showWebPreviewChanged()
 {
     ChatViewSettings settings;
     _showWebPreview = settings.showWebPreview();
 }
 void ChatScene::showWebPreviewChanged()
 {
     ChatViewSettings settings;
     _showWebPreview = settings.showWebPreview();
 }
+
+void ChatScene::showSenderBracketsChanged()
+{
+    ChatViewSettings settings;
+    _showSenderBrackets = settings.showSenderBrackets();
+}
+
+void ChatScene::useCustomTimestampFormatChanged()
+{
+    ChatViewSettings settings;
+    _useCustomTimestampFormat = settings.useCustomTimestampFormat();
+    updateTimestampHasBrackets();
+}
+
+void ChatScene::timestampFormatStringChanged()
+{
+    ChatViewSettings settings;
+    _timestampFormatString = settings.timestampFormatString();
+    updateTimestampHasBrackets();
+}
+
+void ChatScene::updateTimestampHasBrackets()
+{
+    // Calculate these parameters only as needed, rather than on-demand
+
+    if (!_useCustomTimestampFormat) {
+        // The default timestamp format string does not have brackets, no need to check.
+        // If UiStyle::updateSystemTimestampFormat() has brackets added, change this, too.
+        _timestampHasBrackets = false;
+    } else {
+        // Does the timestamp format contain brackets?  For example:
+        // Classic: "[hh:mm:ss]"
+        // Modern:  " hh:mm:ss"
+        //
+        // Match groups of any opening or closing brackets - (), {}, [], <>, (>, {], etc:
+        //   ^\s*[({[<].+[)}\]>]\s*$
+        //   [...]    is a character group containing ...
+        //   ^        matches start of string
+        //   \s*      matches any amount of whitespace
+        //   [({[<]   matches (, {, [, or <
+        //   .+       matches one or more characters
+        //   [)}\]>]  matches ), }, ], or >, escaping the ]
+        //   $        matches end of string
+        // Alternatively, if opening and closing brackets must be in pairs, use this:
+        //   (^\s*\(.+\)\s*$)|(^\s*\{.+\}\s*$)|(^\s*\[.+\]\s*$)|(^\s*<.+>\s*$)
+        // Note that '\' must be escaped as '\\'
+        // Helpful interactive website for debugging and explaining:  https://regex101.com/
+        const QRegExp regExpMatchBrackets("^\\s*[({[<].+[)}\\]>]\\s*$");
+        _timestampHasBrackets = regExpMatchBrackets.exactMatch(_timestampFormatString);
+    }
+}