/***************************************************************************
- * Copyright (C) 2005-2012 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 *
* 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 <QMenu>
#include <QMenuBar>
#include <QMimeData>
#include <QPersistentModelIndex>
+#include <QUrl>
-#ifdef HAVE_KDE
-# include <KMenuBar>
-#else
-# include <QMenuBar>
-#endif
-
-#ifdef HAVE_WEBKIT
-# include <QWebView>
+#ifdef HAVE_WEBENGINE
+# include <QWebEngineView>
+#elif defined HAVE_WEBKIT
+# include <QWebView>
#endif
#include "chatitem.h"
#include "chatline.h"
#include "chatlinemodelitem.h"
-#include "chatscene.h"
#include "chatview.h"
+#include "chatviewsettings.h"
#include "client.h"
#include "clientbacklogmanager.h"
#include "columnhandleitem.h"
#include "contextmenuactionprovider.h"
-#include "iconloader.h"
+#include "icon.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;
-ChatScene::ChatScene(QAbstractItemModel *model, const QString &idString, qreal width, ChatView *parent)
- : QGraphicsScene(0, 0, width, 0, (QObject *)parent),
- _chatView(parent),
- _idString(idString),
- _model(model),
- _singleBufferId(BufferId()),
- _sceneRect(0, 0, width, 0),
- _firstLineRow(-1),
- _viewportHeight(0),
- _markerLine(new MarkerLineItem(width)),
- _markerLineVisible(false),
- _markerLineValid(false),
- _markerLineJumpPending(false),
- _cutoffMode(CutoffRight),
- _selectingItem(0),
- _selectionStart(-1),
- _isSelecting(false),
- _clickMode(NoClick),
- _clickHandled(true),
- _leftButtonPressed(false)
+ChatScene::ChatScene(QAbstractItemModel* model, QString idString, qreal width, ChatView* parent)
+ : QGraphicsScene(0, 0, width, 0, (QObject*)parent)
+ , _chatView(parent)
+ , _idString(std::move(idString))
+ , _model(model)
+ , _singleBufferId(BufferId())
+ , _sceneRect(0, 0, width, 0)
+ , _firstLineRow(-1)
+ , _viewportHeight(0)
+ , _markerLine(new MarkerLineItem(width))
+ , _markerLineVisible(false)
+ , _markerLineValid(false)
+ , _markerLineJumpPending(false)
+ , _cutoffMode(CutoffRight)
+ , _alwaysBracketSender(false)
+ , _selectingItem(nullptr)
+ , _selectionStart(-1)
+ , _isSelecting(false)
+ , _clickMode(NoClick)
+ , _clickHandled(true)
+ , _leftButtonPressed(false)
{
- MessageFilter *filter = qobject_cast<MessageFilter *>(model);
+ auto* filter = qobject_cast<MessageFilter*>(model);
if (filter && filter->isSingleBufferFilter()) {
_singleBufferId = filter->singleBufferId();
}
addItem(_markerLine);
- connect(this, SIGNAL(sceneRectChanged(const QRectF &)), _markerLine, SLOT(sceneRectChanged(const QRectF &)));
+ connect(this, &QGraphicsScene::sceneRectChanged, _markerLine, &MarkerLineItem::sceneRectChanged);
ChatViewSettings defaultSettings;
- int defaultFirstColHandlePos = defaultSettings.value("FirstColumnHandlePos", 80).toInt();
- int defaultSecondColHandlePos = defaultSettings.value("SecondColumnHandlePos", 200).toInt();
+ _defaultFirstColHandlePos = defaultSettings.value("FirstColumnHandlePos", 80).toInt();
+ _defaultSecondColHandlePos = defaultSettings.value("SecondColumnHandlePos", 200).toInt();
ChatViewSettings viewSettings(this);
- _firstColHandlePos = viewSettings.value("FirstColumnHandlePos", defaultFirstColHandlePos).toInt();
- _secondColHandlePos = viewSettings.value("SecondColumnHandlePos", defaultSecondColHandlePos).toInt();
+ _firstColHandlePos = viewSettings.value("FirstColumnHandlePos", _defaultFirstColHandlePos).toInt();
+ _secondColHandlePos = viewSettings.value("SecondColumnHandlePos", _defaultSecondColHandlePos).toInt();
_firstColHandle = new ColumnHandleItem(QtUi::style()->firstColumnSeparator());
addItem(_firstColHandle);
_firstColHandle->setXPos(_firstColHandlePos);
- connect(_firstColHandle, SIGNAL(positionChanged(qreal)), this, SLOT(firstHandlePositionChanged(qreal)));
- connect(this, SIGNAL(sceneRectChanged(const QRectF &)), _firstColHandle, SLOT(sceneRectChanged(const QRectF &)));
+ connect(_firstColHandle, &ColumnHandleItem::positionChanged, this, &ChatScene::firstHandlePositionChanged);
+ connect(this, &QGraphicsScene::sceneRectChanged, _firstColHandle, &ColumnHandleItem::sceneRectChanged);
_secondColHandle = new ColumnHandleItem(QtUi::style()->secondColumnSeparator());
addItem(_secondColHandle);
_secondColHandle->setXPos(_secondColHandlePos);
- connect(_secondColHandle, SIGNAL(positionChanged(qreal)), this, SLOT(secondHandlePositionChanged(qreal)));
+ connect(_secondColHandle, &ColumnHandleItem::positionChanged, this, &ChatScene::secondHandlePositionChanged);
- connect(this, SIGNAL(sceneRectChanged(const QRectF &)), _secondColHandle, SLOT(sceneRectChanged(const QRectF &)));
+ connect(this, &QGraphicsScene::sceneRectChanged, _secondColHandle, &ColumnHandleItem::sceneRectChanged);
setHandleXLimits();
if (model->rowCount() > 0)
rowsInserted(QModelIndex(), 0, model->rowCount() - 1);
- connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
- this, SLOT(rowsInserted(const QModelIndex &, int, int)));
- connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
- this, SLOT(rowsAboutToBeRemoved(const QModelIndex &, int, int)));
- connect(model, SIGNAL(rowsRemoved(QModelIndex, int, int)),
- this, SLOT(rowsRemoved()));
- connect(model, SIGNAL(dataChanged(QModelIndex, QModelIndex)), SLOT(dataChanged(QModelIndex, QModelIndex)));
+ connect(model, &QAbstractItemModel::rowsInserted, this, &ChatScene::rowsInserted);
+ connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ChatScene::rowsAboutToBeRemoved);
+ connect(model, &QAbstractItemModel::rowsRemoved, this, &ChatScene::rowsRemoved);
+ connect(model, &QAbstractItemModel::dataChanged, this, &ChatScene::dataChanged);
-#ifdef HAVE_WEBKIT
+#if defined HAVE_WEBKIT || defined HAVE_WEBENGINE
webPreview.timer.setSingleShot(true);
- connect(&webPreview.timer, SIGNAL(timeout()), this, SLOT(webPreviewNextStep()));
+ connect(&webPreview.timer, &QTimer::timeout, this, &ChatScene::webPreviewNextStep);
#endif
_showWebPreview = defaultSettings.showWebPreview();
- defaultSettings.notify("ShowWebPreview", this, SLOT(showWebPreviewChanged()));
+ defaultSettings.notify("ShowWebPreview", this, &ChatScene::showWebPreviewChanged);
+
+ _showSenderBrackets = defaultSettings.showSenderBrackets();
+ defaultSettings.notify("ShowSenderBrackets", this, &ChatScene::showSenderBracketsChanged);
+
+ _useCustomTimestampFormat = defaultSettings.useCustomTimestampFormat();
+ defaultSettings.notify("UseCustomTimestampFormat", this, &ChatScene::useCustomTimestampFormatChanged);
+
+ _timestampFormatString = defaultSettings.timestampFormatString();
+ defaultSettings.notify("TimestampFormat", this, &ChatScene::timestampFormatStringChanged);
+ updateTimestampHasBrackets();
_clickTimer.setInterval(QApplication::doubleClickInterval());
_clickTimer.setSingleShot(true);
- connect(&_clickTimer, SIGNAL(timeout()), SLOT(clickTimeout()));
+ connect(&_clickTimer, &QTimer::timeout, this, &ChatScene::clickTimeout);
setItemIndexMethod(QGraphicsScene::NoIndex);
}
-
-ChatScene::~ChatScene()
-{
-}
-
-
-ChatView *ChatScene::chatView() const
+ChatView* ChatScene::chatView() const
{
return _chatView;
}
-
-ColumnHandleItem *ChatScene::firstColumnHandle() const
+ColumnHandleItem* ChatScene::firstColumnHandle() const
{
return _firstColHandle;
}
-
-ColumnHandleItem *ChatScene::secondColumnHandle() const
+ColumnHandleItem* ChatScene::secondColumnHandle() const
{
return _secondColHandle;
}
+void ChatScene::resetColumnWidths()
+{
+ // make sure first column is at least 80 px wide, second 120 px
+ int firstColHandlePos = qMax(_defaultFirstColHandlePos, 80);
+ int secondColHandlePos = qMax(_defaultSecondColHandlePos, firstColHandlePos + 120);
-ChatLine *ChatScene::chatLine(MsgId msgId, bool matchExact, bool ignoreDayChange) const
+ _firstColHandle->setXPos(firstColHandlePos);
+ _secondColHandle->setXPos(secondColHandlePos);
+}
+
+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 middle;
+ QList<ChatLine*>::ConstIterator start = _lines.begin();
+ QList<ChatLine*>::ConstIterator end = _lines.end();
+ QList<ChatLine*>::ConstIterator middle;
- int n = int(end - start);
+ auto n = int(end - start);
int half;
while (n > 0) {
return *start;
if (matchExact)
- return 0;
+ return nullptr;
- if (start == _lines.begin()) // not (yet?) in our scene
- return 0;
+ if (start == _lines.begin()) // not (yet?) in our scene
+ return nullptr;
// if we didn't find the exact msgId, take the next-lower one (this makes sense for lastSeen)
- if (start == end) { // higher than last element
+ if (start == end) { // higher than last element
if (!ignoreDayChange)
return _lines.last();
- for (int i = _lines.count() -1; i >= 0; i--) {
+ for (int i = _lines.count() - 1; i >= 0; i--) {
if (_lines.at(i)->msgType() != Message::DayChange)
return _lines.at(i);
}
- return 0;
+ return nullptr;
}
// return the next-lower line
do {
if ((*(--start))->msgType() != Message::DayChange)
return *start;
- }
- while (start != _lines.begin());
- return 0;
+ } while (start != _lines.begin());
+ return nullptr;
}
-
-ChatItem *ChatScene::chatItemAt(const QPointF &scenePos) const
+ChatItem* ChatScene::chatItemAt(const QPointF& scenePos) const
{
- foreach(QGraphicsItem *item, items(scenePos, Qt::IntersectsItemBoundingRect, Qt::AscendingOrder)) {
- ChatLine *line = qgraphicsitem_cast<ChatLine *>(item);
+ foreach (QGraphicsItem* item, items(scenePos, Qt::IntersectsItemBoundingRect, Qt::AscendingOrder)) {
+ auto* line = qgraphicsitem_cast<ChatLine*>(item);
if (line)
return line->itemAt(line->mapFromScene(scenePos));
}
- return 0;
+ return nullptr;
}
-
-bool ChatScene::containsBuffer(const BufferId &id) const
+bool ChatScene::containsBuffer(const BufferId& id) const
{
- MessageFilter *filter = qobject_cast<MessageFilter *>(model());
+ auto* filter = qobject_cast<MessageFilter*>(model());
if (filter)
return filter->containsBuffer(id);
else
return false;
}
-
void ChatScene::setMarkerLineVisible(bool visible)
{
_markerLineVisible = visible;
markerLine()->setVisible(false);
}
-
void ChatScene::setMarkerLine(MsgId msgId)
{
if (!isSingleBufferScene())
msgId = Client::markerLine(singleBufferId());
if (msgId.isValid()) {
- ChatLine *line = chatLine(msgId, false, true);
+ ChatLine* line = chatLine(msgId, false, true);
if (line) {
markerLine()->setChatLine(line);
// if this was the last line, we won't see it because it's outside the sceneRect
markerLine()->setVisible(false);
}
-
void ChatScene::jumpToMarkerLine(bool requestBacklog)
{
if (!isSingleBufferScene())
}
}
-
-void ChatScene::rowsInserted(const QModelIndex &index, int start, int end)
+void ChatScene::rowsInserted(const QModelIndex& index, int start, int end)
{
Q_UNUSED(index);
-// QModelIndex sidx = model()->index(start, 2);
-// QModelIndex eidx = model()->index(end, 2);
-// qDebug() << "rowsInserted:";
-// if(start > 0) {
-// QModelIndex ssidx = model()->index(start - 1, 2);
-// qDebug() << "Start--:" << start - 1 << ssidx.data(MessageModel::MsgIdRole).value<MsgId>()
-// << ssidx.data(Qt::DisplayRole).toString();
-// }
-// qDebug() << "Start:" << start << sidx.data(MessageModel::MsgIdRole).value<MsgId>()
-// << sidx.data(Qt::DisplayRole).toString();
-// qDebug() << "End:" << end << eidx.data(MessageModel::MsgIdRole).value<MsgId>()
-// << eidx.data(Qt::DisplayRole).toString();
-// if(end + 1 < model()->rowCount()) {
-// QModelIndex eeidx = model()->index(end + 1, 2);
-// qDebug() << "End++:" << end + 1 << eeidx.data(MessageModel::MsgIdRole).value<MsgId>()
-// << eeidx.data(Qt::DisplayRole).toString();
-// }
+ // QModelIndex sidx = model()->index(start, 2);
+ // QModelIndex eidx = model()->index(end, 2);
+ // qDebug() << "rowsInserted:";
+ // if(start > 0) {
+ // QModelIndex ssidx = model()->index(start - 1, 2);
+ // qDebug() << "Start--:" << start - 1 << ssidx.data(MessageModel::MsgIdRole).value<MsgId>()
+ // << ssidx.data(Qt::DisplayRole).toString();
+ // }
+ // qDebug() << "Start:" << start << sidx.data(MessageModel::MsgIdRole).value<MsgId>()
+ // << sidx.data(Qt::DisplayRole).toString();
+ // qDebug() << "End:" << end << eidx.data(MessageModel::MsgIdRole).value<MsgId>()
+ // << eidx.data(Qt::DisplayRole).toString();
+ // if(end + 1 < model()->rowCount()) {
+ // QModelIndex eeidx = model()->index(end + 1, 2);
+ // qDebug() << "End++:" << end + 1 << eeidx.data(MessageModel::MsgIdRole).value<MsgId>()
+ // << eeidx.data(Qt::DisplayRole).toString();
+ // }
qreal h = 0;
qreal y = 0;
if (atTop) {
for (int i = end; i >= start; i--) {
- ChatLine *line = new ChatLine(i, model(),
- width,
- timestampWidth, senderWidth, contentsWidth,
- senderPos, contentsPos);
+ auto* line = new ChatLine(i, model(), width, timestampWidth, senderWidth, contentsWidth, senderPos, contentsPos);
h += line->height();
- line->setPos(0, y-h);
+ line->setPos(0, y - h);
_lines.insert(start, line);
addItem(line);
}
}
else {
for (int i = start; i <= end; i++) {
- ChatLine *line = new ChatLine(i, model(),
- width,
- timestampWidth, senderWidth, contentsWidth,
- senderPos, contentsPos);
- line->setPos(0, y+h);
+ auto* line = new ChatLine(i, model(), width, timestampWidth, senderWidth, contentsWidth, senderPos, contentsPos);
+ line->setPos(0, y + h);
h += line->height();
_lines.insert(i, line);
addItem(line);
}
// update existing items
- for (int i = end+1; i < _lines.count(); i++) {
+ for (int i = end + 1; i < _lines.count(); i++) {
_lines[i]->setRow(i);
}
// 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);
// check if all went right
Q_ASSERT(start == 0 || _lines.at(start - 1)->pos().y() + _lines.at(start - 1)->height() == _lines.at(start)->pos().y());
-// if(start != 0) {
-// if(_lines.at(start - 1)->pos().y() + _lines.at(start - 1)->height() != _lines.at(start)->pos().y()) {
-// qDebug() << "lines:" << _lines.count() << "start:" << start << "end:" << end;
-// qDebug() << "line[start - 1]:" << _lines.at(start - 1)->pos().y() << "+" << _lines.at(start - 1)->height() << "=" << _lines.at(start - 1)->pos().y() + _lines.at(start - 1)->height();
-// qDebug() << "line[start]" << _lines.at(start)->pos().y();
-// qDebug() << "needed moving:" << !(atTop || atBottom) << moveTop << moveStart << moveEnd << offset;
-// Q_ASSERT(false)
-// }
-// }
+ // if(start != 0) {
+ // if(_lines.at(start - 1)->pos().y() + _lines.at(start - 1)->height() != _lines.at(start)->pos().y()) {
+ // qDebug() << "lines:" << _lines.count() << "start:" << start << "end:" << end;
+ // qDebug() << "line[start - 1]:" << _lines.at(start - 1)->pos().y() << "+" << _lines.at(start - 1)->height() << "=" <<
+ // _lines.at(start - 1)->pos().y() + _lines.at(start - 1)->height(); qDebug() << "line[start]" << _lines.at(start)->pos().y(); qDebug()
+ // << "needed moving:" << !(atTop || atBottom) << moveTop << moveStart << moveEnd << offset; Q_ASSERT(false)
+ // }
+ // }
Q_ASSERT(end + 1 == _lines.count() || _lines.at(end)->pos().y() + _lines.at(end)->height() == _lines.at(end + 1)->pos().y());
-// if(end + 1 < _lines.count()) {
-// if(_lines.at(end)->pos().y() + _lines.at(end)->height() != _lines.at(end + 1)->pos().y()) {
-// qDebug() << "lines:" << _lines.count() << "start:" << start << "end:" << end;
-// qDebug() << "line[end]:" << _lines.at(end)->pos().y() << "+" << _lines.at(end)->height() << "=" << _lines.at(end)->pos().y() + _lines.at(end)->height();
-// qDebug() << "line[end+1]" << _lines.at(end + 1)->pos().y();
-// qDebug() << "needed moving:" << !(atTop || atBottom) << moveTop << moveStart << moveEnd << offset;
-// Q_ASSERT(false);
-// }
-// }
+ // if(end + 1 < _lines.count()) {
+ // if(_lines.at(end)->pos().y() + _lines.at(end)->height() != _lines.at(end + 1)->pos().y()) {
+ // qDebug() << "lines:" << _lines.count() << "start:" << start << "end:" << end;
+ // qDebug() << "line[end]:" << _lines.at(end)->pos().y() << "+" << _lines.at(end)->height() << "=" << _lines.at(end)->pos().y() +
+ // _lines.at(end)->height(); qDebug() << "line[end+1]" << _lines.at(end + 1)->pos().y(); qDebug() << "needed moving:" << !(atTop
+ // || atBottom) << moveTop << moveStart << moveEnd << offset; Q_ASSERT(false);
+ // }
+ // }
if (!atBottom) {
if (start < _firstLineRow) {
setMarkerLine();
}
-
-void ChatScene::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
+void ChatScene::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
{
Q_UNUSED(parent);
- qreal h = 0; // total height of removed items;
+ qreal h = 0; // total height of removed items;
bool atTop = (start == 0);
bool atBottom = (end == _lines.count() - 1);
if (_selectingItem) {
int row = _selectingItem->row();
if (row >= start && row <= end)
- setSelectingItem(0);
+ setSelectingItem(nullptr);
}
// remove items from scene
- QList<ChatLine *>::iterator lineIter = _lines.begin() + start;
+ QList<ChatLine*>::iterator lineIter = _lines.begin() + start;
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);
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);
}
}
- Q_ASSERT(start == 0 || start >= _lines.count() || _lines.at(start - 1)->pos().y() + _lines.at(start - 1)->height() == _lines.at(start)->pos().y());
+ Q_ASSERT(start == 0 || start >= _lines.count()
+ || _lines.at(start - 1)->pos().y() + _lines.at(start - 1)->height() == _lines.at(start)->pos().y());
// update sceneRect
// when searching for the first non-date-line we have to take into account that our
needOffset = true;
}
firstLineIdx = model()->index(_firstLineRow, 0);
- }
- while ((Message::Type)(model()->data(firstLineIdx, MessageModel::TypeRole).toInt()) == Message::DayChange && _firstLineRow < numRows);
+ } while ((Message::Type)(model()->data(firstLineIdx, MessageModel::TypeRole).toInt()) == Message::DayChange && _firstLineRow < numRows);
if (needOffset)
_firstLineRow -= end - start + 1;
updateSceneRect();
}
-
void ChatScene::rowsRemoved()
{
// move the marker line if necessary
setMarkerLine();
}
-
-void ChatScene::dataChanged(const QModelIndex &tl, const QModelIndex &br)
+void ChatScene::dataChanged(const QModelIndex& tl, const QModelIndex& br)
{
layout(tl.row(), br.row(), _sceneRect.width());
}
-
void ChatScene::updateForViewport(qreal width, qreal height)
{
_viewportHeight = height;
setWidth(width);
}
-
void ChatScene::setWidth(qreal width)
{
if (width == _sceneRect.width())
return;
- layout(0, _lines.count()-1, width);
+ layout(0, _lines.count() - 1, width);
}
-
void ChatScene::layout(int start, int end, qreal width)
{
// clock_t startT = clock();
// disabling the index while doing this complex updates is about
// 2 to 10 times faster!
- //setItemIndexMethod(QGraphicsScene::NoIndex);
+ // setItemIndexMethod(QGraphicsScene::NoIndex);
if (end >= 0) {
int row = end;
if (row >= 0) {
// remaining items don't need geometry changes, but maybe repositioning?
- ChatLine *line = _lines.at(row);
+ ChatLine* line = _lines.at(row);
qreal offset = linePos - (line->scenePos().y() + line->height());
if (offset != 0) {
while (row >= 0) {
}
}
- //setItemIndexMethod(QGraphicsScene::BspTreeIndex);
+ // setItemIndexMethod(QGraphicsScene::BspTreeIndex);
updateSceneRect(width);
setHandleXLimits();
setMarkerLine();
emit layoutChanged();
-// clock_t endT = clock();
-// qDebug() << "resized" << _lines.count() << "in" << (float)(endT - startT) / CLOCKS_PER_SEC << "sec";
+ // clock_t endT = clock();
+ // qDebug() << "resized" << _lines.count() << "in" << (float)(endT - startT) / CLOCKS_PER_SEC << "sec";
}
-
void ChatScene::firstHandlePositionChanged(qreal xpos)
{
if (_firstColHandlePos == xpos)
// disabling the index while doing this complex updates is about
// 2 to 10 times faster!
- //setItemIndexMethod(QGraphicsScene::NoIndex);
+ // setItemIndexMethod(QGraphicsScene::NoIndex);
- QList<ChatLine *>::iterator lineIter = _lines.end();
- QList<ChatLine *>::iterator lineIterBegin = _lines.begin();
+ QList<ChatLine*>::iterator lineIter = _lines.end();
+ QList<ChatLine*>::iterator lineIterBegin = _lines.begin();
qreal timestampWidth = firstColumnHandle()->sceneLeft();
qreal senderWidth = secondColumnHandle()->sceneLeft() - firstColumnHandle()->sceneRight();
QPointF senderPos(firstColumnHandle()->sceneRight(), 0);
while (lineIter != lineIterBegin) {
- lineIter--;
+ --lineIter;
(*lineIter)->setFirstColumn(timestampWidth, senderWidth, senderPos);
}
- //setItemIndexMethod(QGraphicsScene::BspTreeIndex);
+ // setItemIndexMethod(QGraphicsScene::BspTreeIndex);
setHandleXLimits();
-// clock_t endT = clock();
-// qDebug() << "resized" << _lines.count() << "in" << (float)(endT - startT) / CLOCKS_PER_SEC << "sec";
+ // clock_t endT = clock();
+ // qDebug() << "resized" << _lines.count() << "in" << (float)(endT - startT) / CLOCKS_PER_SEC << "sec";
}
-
void ChatScene::secondHandlePositionChanged(qreal xpos)
{
if (_secondColHandlePos == xpos)
// disabling the index while doing this complex updates is about
// 2 to 10 times faster!
- //setItemIndexMethod(QGraphicsScene::NoIndex);
+ // setItemIndexMethod(QGraphicsScene::NoIndex);
- QList<ChatLine *>::iterator lineIter = _lines.end();
- QList<ChatLine *>::iterator lineIterBegin = _lines.begin();
+ QList<ChatLine*>::iterator lineIter = _lines.end();
+ QList<ChatLine*>::iterator lineIterBegin = _lines.begin();
qreal linePos = _sceneRect.y() + _sceneRect.height();
qreal senderWidth = secondColumnHandle()->sceneLeft() - firstColumnHandle()->sceneRight();
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);
+ // setItemIndexMethod(QGraphicsScene::BspTreeIndex);
updateSceneRect();
setHandleXLimits();
emit layoutChanged();
-// clock_t endT = clock();
-// qDebug() << "resized" << _lines.count() << "in" << (float)(endT - startT) / CLOCKS_PER_SEC << "sec";
+ // clock_t endT = clock();
+ // qDebug() << "resized" << _lines.count() << "in" << (float)(endT - startT) / CLOCKS_PER_SEC << "sec";
}
-
void ChatScene::setHandleXLimits()
{
_firstColHandle->setXLimits(0, _secondColHandle->sceneLeft());
update();
}
-
-void ChatScene::setSelectingItem(ChatItem *item)
+void ChatScene::setSelectingItem(ChatItem* item)
{
- if (_selectingItem) _selectingItem->clearSelection();
+ if (_selectingItem)
+ _selectingItem->clearSelection();
_selectingItem = item;
}
-
-void ChatScene::startGlobalSelection(ChatItem *item, const QPointF &itemPos)
+void ChatScene::startGlobalSelection(ChatItem* item, const QPointF& itemPos)
{
_selectionStart = _selectionEnd = _firstSelectionRow = item->row();
_selectionStartCol = _selectionMinCol = item->column();
updateSelection(item->mapToScene(itemPos));
}
-
-void ChatScene::updateSelection(const QPointF &pos)
+void ChatScene::updateSelection(const QPointF& pos)
{
int curRow = rowByScenePos(pos);
- if (curRow < 0) return;
- int curColumn = (int)columnByScenePos(pos);
- ChatLineModel::ColumnType minColumn = (ChatLineModel::ColumnType)qMin(curColumn, _selectionStartCol);
+ if (curRow < 0)
+ return;
+ auto curColumn = (int)columnByScenePos(pos);
+ auto minColumn = (ChatLineModel::ColumnType)qMin(curColumn, _selectionStartCol);
if (minColumn != _selectionMinCol) {
_selectionMinCol = minColumn;
for (int l = qMin(_selectionStart, _selectionEnd); l <= qMax(_selectionStart, _selectionEnd); l++) {
_lines[l]->setSelected(false);
}
if (newend > _selectionEnd) {
- for (int l = _selectionEnd+1; l <= newend; l++)
+ for (int l = _selectionEnd + 1; l <= newend; l++)
_lines[l]->setSelected(true, minColumn);
}
if (newend < _selectionEnd) {
- for (int l = newend+1; l <= _selectionEnd; l++)
+ for (int l = newend + 1; l <= _selectionEnd; l++)
_lines[l]->setSelected(false);
}
}
}
-
-bool ChatScene::isPosOverSelection(const QPointF &pos) const
+bool ChatScene::isPosOverSelection(const QPointF& pos) const
{
- ChatItem *chatItem = chatItemAt(pos);
+ ChatItem* chatItem = chatItemAt(pos);
if (!chatItem)
return false;
if (hasGlobalSelection()) {
return false;
}
-
bool ChatScene::isScrollingAllowed() const
{
if (_isSelecting)
return true;
}
-
/******** MOUSE HANDLING **************************************************************************/
-void ChatScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
+void ChatScene::contextMenuEvent(QGraphicsSceneContextMenuEvent* event)
{
QPointF pos = event->scenePos();
QMenu menu;
chatView()->addActionsToMenu(&menu, pos);
menu.addSeparator();
- if (isPosOverSelection(pos))
- menu.addAction(SmallIcon("edit-copy"), tr("Copy Selection"),
- this, SLOT(selectionToClipboard()),
- QKeySequence::Copy);
-
// item-specific options (select link etc)
- ChatItem *item = chatItemAt(pos);
+ ChatItem* item = chatItemAt(pos);
if (item)
item->addActionsToMenu(&menu, item->mapFromScene(pos));
else
// no item -> default scene actions
GraphicalUi::contextMenuActionProvider()->addActions(&menu, filter(), BufferId());
+ // 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(icon::get("edit-copy"),
+ tr("Copy Selection"),
+ &menu,
+ this,
+ [this]() { selectionToClipboard(); },
+ QKeySequence::Copy);
+ menu.insertAction(sep, act);
+
+ QString searchSelectionText = selection();
+ if (searchSelectionText.length() > _webSearchSelectionTextMaxVisible)
+ searchSelectionText = searchSelectionText.left(_webSearchSelectionTextMaxVisible).append(QString::fromUtf8("…"));
+ searchSelectionText = tr("Search '%1'").arg(searchSelectionText);
+
+ QAction* webSearchAction = new Action(icon::get("edit-find"), searchSelectionText, &menu, this, &ChatScene::webSearchOnSelection);
+ menu.insertAction(sep, webSearchAction);
+ }
+
if (QtUi::mainWindow()->menuBar()->isHidden())
menu.addAction(QtUi::actionCollection("General")->action("ToggleMenuBar"));
+ // show column reset action if columns have been resized in this session or there is at least one very narrow column
+ if ((_firstColHandlePos != _defaultFirstColHandlePos) || (_secondColHandlePos != _defaultSecondColHandlePos)
+ || (_firstColHandlePos <= 10) || (_secondColHandlePos - _firstColHandlePos <= 10))
+ menu.addAction(new Action(tr("Reset Column Widths"), &menu, this, &ChatScene::resetColumnWidths));
+
menu.exec(event->screenPos());
}
-
-void ChatScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
+void ChatScene::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
{
if (event->buttons() == Qt::LeftButton) {
if (!_clickHandled && (event->scenePos() - _clickPos).toPoint().manhattanLength() >= QApplication::startDragDistance()) {
QGraphicsScene::mouseMoveEvent(event);
}
-
-void ChatScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
+void ChatScene::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
if (event->buttons() == Qt::LeftButton) {
_leftButtonPressed = true;
if (_clickMode != NoClick && _clickTimer.isActive()) {
switch (_clickMode) {
case NoClick:
- _clickMode = SingleClick; break;
+ _clickMode = SingleClick;
+ break;
case SingleClick:
- _clickMode = DoubleClick; break;
+ _clickMode = DoubleClick;
+ break;
case DoubleClick:
- _clickMode = TripleClick; break;
+ _clickMode = TripleClick;
+ break;
case TripleClick:
- _clickMode = DoubleClick; break;
+ _clickMode = DoubleClick;
+ break;
case DragStartClick:
break;
}
QGraphicsScene::mousePressEvent(event);
}
-
-void ChatScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
+void ChatScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event)
{
// we check for doubleclick ourselves, so just call press handler
mousePressEvent(event);
}
-
-void ChatScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
+void ChatScene::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
{
if (event->button() == Qt::LeftButton && _leftButtonPressed) {
_leftButtonPressed = false;
QGraphicsScene::mouseReleaseEvent(event);
}
-
void ChatScene::clickTimeout()
{
if (!_leftButtonPressed && _clickMode == SingleClick)
handleClick(Qt::LeftButton, _clickPos);
}
-
-void ChatScene::handleClick(Qt::MouseButton button, const QPointF &scenePos)
+void ChatScene::handleClick(Qt::MouseButton button, const QPointF& scenePos)
{
if (button == Qt::LeftButton) {
clearSelection();
// Now send click down to items
- ChatItem *chatItem = chatItemAt(scenePos);
+ ChatItem* chatItem = chatItemAt(scenePos);
if (chatItem) {
chatItem->handleClick(chatItem->mapFromScene(scenePos), _clickMode);
}
}
}
-
-void ChatScene::initiateDrag(QWidget *source)
+void ChatScene::initiateDrag(QWidget* source)
{
- QDrag *drag = new QDrag(source);
- QMimeData *mimeData = new QMimeData;
+ auto* drag = new QDrag(source);
+ auto* mimeData = new QMimeData;
mimeData->setText(selection());
drag->setMimeData(mimeData);
drag->exec(Qt::CopyAction);
}
-
/******** SELECTIONS ******************************************************************************/
void ChatScene::selectionToClipboard(QClipboard::Mode mode)
stringToClipboard(selection(), mode);
}
-
-void ChatScene::stringToClipboard(const QString &str_, QClipboard::Mode mode)
+void ChatScene::stringToClipboard(const QString& str_, QClipboard::Mode mode)
{
QString str = str_;
// remove trailing linefeeds
};
}
-
//!\brief Convert current selection to human-readable string.
QString ChatScene::selection() const
{
- //TODO Make selection format configurable!
+ // TODO Make selection format configurable!
if (hasGlobalSelection()) {
int start = qMin(_selectionStart, _selectionEnd);
int end = qMax(_selectionStart, _selectionEnd);
return QString();
}
QString result;
+
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() + " ";
+ 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 QString();
}
-
bool ChatScene::hasSelection() const
{
return hasGlobalSelection() || (selectingItem() && selectingItem()->hasSelection());
}
-
bool ChatScene::hasGlobalSelection() const
{
return _selectionStart >= 0;
}
-
bool ChatScene::isGloballySelecting() const
{
return _isSelecting;
}
-
void ChatScene::clearGlobalSelection()
{
if (hasGlobalSelection()) {
}
}
-
void ChatScene::clearSelection()
{
clearGlobalSelection();
selectingItem()->clearSelection();
}
+/******** *************************************************************************************/
+
+void ChatScene::webSearchOnSelection()
+{
+ if (!hasSelection())
+ return;
+
+ ChatViewSettings settings;
+ QString webSearchBaseUrl = settings.webSearchUrlFormatString();
+ QString webSearchUrl = webSearchBaseUrl.replace(QString("%s"), selection());
+ QUrl url = QUrl::fromUserInput(webSearchUrl);
+ QDesktopServices::openUrl(url);
+}
/******** *************************************************************************************/
void ChatScene::requestBacklog()
{
- MessageFilter *filter = qobject_cast<MessageFilter *>(model());
+ auto* filter = qobject_cast<MessageFilter*>(model());
if (filter)
return filter->requestBacklog();
return;
}
-
ChatLineModel::ColumnType ChatScene::columnByScenePos(qreal x) const
{
if (x < _firstColHandle->x())
return ChatLineModel::ContentsColumn;
}
-
int ChatScene::rowByScenePos(qreal y) const
{
- QList<QGraphicsItem *> itemList = items(QPointF(0, y));
+ QList<QGraphicsItem*> itemList = items(QPointF(0, y));
// ChatLine should be at the bottom of the list
- for (int i = itemList.count()-1; i >= 0; i--) {
- ChatLine *line = qgraphicsitem_cast<ChatLine *>(itemList.at(i));
+ for (int i = itemList.count() - 1; i >= 0; i--) {
+ auto* line = qgraphicsitem_cast<ChatLine*>(itemList.at(i));
if (line)
return line->row();
}
return -1;
}
-
void ChatScene::updateSceneRect(qreal width)
{
if (_lines.isEmpty()) {
// the following call should be safe. If it crashes something went wrong during insert/remove
if (_firstLineRow < _lines.count()) {
- ChatLine *firstLine = _lines.at(_firstLineRow);
- ChatLine *lastLine = _lines.last();
+ ChatLine* firstLine = _lines.at(_firstLineRow);
+ ChatLine* lastLine = _lines.last();
updateSceneRect(QRectF(0, firstLine->pos().y(), width, lastLine->pos().y() + lastLine->height() - firstLine->pos().y()));
}
else {
}
}
-
-void ChatScene::updateSceneRect(const QRectF &rect)
+void ChatScene::updateSceneRect(const QRectF& rect)
{
_sceneRect = rect;
setSceneRect(rect);
update();
}
-
// ========================================
-// Webkit Only stuff
+// Webkit/WebEngine Only stuff
// ========================================
-#ifdef HAVE_WEBKIT
-void ChatScene::loadWebPreview(ChatItem *parentItem, const QUrl &url, const QRectF &urlRect)
+#if defined HAVE_WEBKIT || defined HAVE_WEBENGINE
+void ChatScene::loadWebPreview(ChatItem* parentItem, const QUrl& url, const QRectF& urlRect)
{
if (!_showWebPreview)
return;
if (webPreview.previewItem->scene())
removeItem(webPreview.previewItem);
delete webPreview.previewItem;
- webPreview.previewItem = 0;
+ webPreview.previewItem = nullptr;
}
webPreview.previewState = WebPreview::NoPreview;
}
// qDebug() << " new State:" << webPreview.previewState << webPreview.timer.isActive();
}
-
void ChatScene::webPreviewNextStep()
{
// qDebug() << Q_FUNC_INFO << webPreview.previewState;
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);
- // Fall through to deletion!
+ }
+
+ // Intentional fallthrough
+
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;
// qDebug() << " new State:" << webPreview.previewState << webPreview.timer.isActive();
}
-
-void ChatScene::clearWebPreview(ChatItem *parentItem)
+void ChatScene::clearWebPreview(ChatItem* parentItem)
{
// qDebug() << Q_FUNC_INFO << webPreview.previewState;
switch (webPreview.previewState) {
case WebPreview::NewPreview:
- webPreview.previewState = WebPreview::NoPreview; // we haven't loaded anything yet
+ 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);
}
- // 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;
// qDebug() << " new State:" << webPreview.previewState << webPreview.timer.isActive();
}
-
#endif
// ========================================
// end of webkit only
// ========================================
+// Local configuration caching
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(R"(^\s*[({[<].+[)}\]>]\s*$)");
+ _timestampHasBrackets = regExpMatchBrackets.exactMatch(_timestampFormatString);
+ }
+}