/***************************************************************************
- * Copyright (C) 2005-2012 by the Quassel Project *
+ * Copyright (C) 2005-2020 by the Quassel Project *
* devel@quassel-irc.org *
* *
* This program is free software; you can redistribute it and/or modify *
#include "chatviewsearchcontroller.h"
+#include <algorithm>
+
#include <QAbstractItemModel>
#include <QPainter>
#include "chatscene.h"
#include "messagemodel.h"
-ChatViewSearchController::ChatViewSearchController(QObject *parent)
- : QObject(parent),
- _scene(0),
- _currentHighlight(0),
- _caseSensitive(false),
- _searchSenders(false),
- _searchMsgs(true),
- _searchOnlyRegularMsgs(true)
-{
-}
-
+ChatViewSearchController::ChatViewSearchController(QObject* parent)
+ : QObject(parent)
+{}
-void ChatViewSearchController::setSearchString(const QString &searchString)
+void ChatViewSearchController::setSearchString(const QString& searchString)
{
QString oldSearchString = _searchString;
_searchString = searchString;
}
}
-
-void ChatViewSearchController::setScene(ChatScene *scene)
+void ChatViewSearchController::setScene(ChatScene* scene)
{
Q_ASSERT(scene);
if (scene == _scene)
return;
if (_scene) {
- disconnect(_scene, 0, this, 0);
- disconnect(Client::messageModel(), 0, this, 0);
+ disconnect(_scene, nullptr, this, nullptr);
+ disconnect(Client::messageModel(), nullptr, this, nullptr);
qDeleteAll(_highlightItems);
_highlightItems.clear();
}
if (!scene)
return;
- connect(_scene, SIGNAL(destroyed()), this, SLOT(sceneDestroyed()));
- connect(_scene, SIGNAL(layoutChanged()), this, SLOT(repositionHighlights()));
- connect(Client::messageModel(), SIGNAL(finishedBacklogFetch(BufferId)), this, SLOT(updateHighlights()));
+ connect(_scene, &QObject::destroyed, this, &ChatViewSearchController::sceneDestroyed);
+ connect(_scene, &ChatScene::layoutChanged, this, [this]() { repositionHighlights(); });
+ connect(Client::messageModel(), &MessageModel::finishedBacklogFetch, this, [this]() { updateHighlights(); });
updateHighlights();
}
-
void ChatViewSearchController::highlightNext()
{
if (_highlightItems.isEmpty())
emit newCurrentHighlight(_highlightItems.at(_currentHighlight));
}
-
void ChatViewSearchController::highlightPrev()
{
if (_highlightItems.isEmpty())
emit newCurrentHighlight(_highlightItems.at(_currentHighlight));
}
-
void ChatViewSearchController::updateHighlights(bool reuse)
{
if (!_scene)
return;
if (reuse) {
- QSet<ChatLine *> chatLines;
- foreach(SearchHighlightItem *highlightItem, _highlightItems) {
- ChatLine *line = qgraphicsitem_cast<ChatLine *>(highlightItem->parentItem());
+ QSet<ChatLine*> chatLines;
+ foreach (SearchHighlightItem* highlightItem, _highlightItems) {
+ auto* line = qgraphicsitem_cast<ChatLine*>(highlightItem->parentItem());
if (line)
chatLines << line;
}
- foreach(ChatLine *line, QList<ChatLine *>(chatLines.toList())) {
+ foreach (ChatLine* line, chatLines) {
updateHighlights(line);
}
}
if (!_highlightItems.isEmpty()) {
if (!oldHighlightPos.isNull()) {
- int start = 0; int end = _highlightItems.count() - 1;
+ int start = 0;
+ int end = _highlightItems.count() - 1;
QPointF startPos;
QPointF endPos;
- while (1) {
+ while (true) {
startPos = _highlightItems[start]->scenePos();
endPos = _highlightItems[end]->scenePos();
if (startPos == oldHighlightPos) {
_currentHighlight = start;
break;
}
+ if (end == 0 && start == 0) {
+ // Sometimes we can run into an issue where the start and end are both set
+ // to zero. Rather than endlessly spin this loop, bail out. Search seems
+ // to work fine.
+ // [Test case]
+ // Unfortunately, this seems specific to the contents of a buffer. First,
+ // find a buffer that you've encountered freezing, and keep track of what
+ // was loaded, where it was, and the two most recent search terms.
+ // For example...
+ // 1. Load some backlog to a buffer
+ // 2. Search for term with any number of matches
+ // 3. Making sure to -type over existing words without first backspacing-,
+ // search for another term with only one match
+ // Expected: Search results found, no freezing
+ // Actual: Quassel hangs. startPos and endPos = same place, start = 0,
+ // end = 0, _currentHighlight appears to retain values from the
+ // previous search.
+
+ // Reset _currentHighlight to start, otherwise it'll retain the value from
+ // previous search, resulting in an index-out-of-bounds error.
+ _currentHighlight = start;
+ // Escape from the loop!
+ break;
+ }
int pivot = (end + start) / 2;
QPointF pivotPos = _highlightItems[pivot]->scenePos();
if (startPos.y() == endPos.y()) {
}
}
-
void ChatViewSearchController::checkMessagesForHighlight(int start, int end)
{
- QAbstractItemModel *model = _scene->model();
+ QAbstractItemModel* model = _scene->model();
Q_ASSERT(model);
if (end == -1) {
}
}
-
-void ChatViewSearchController::updateHighlights(ChatLine *line)
+void ChatViewSearchController::updateHighlights(ChatLine* line)
{
- QList<ChatItem *> checkItems;
+ QList<ChatItem*> checkItems;
if (_searchSenders)
checkItems << line->item(MessageModel::SenderColumn);
if (_searchMsgs)
checkItems << line->item(MessageModel::ContentsColumn);
- QHash<quint64, QHash<quint64, QRectF> > wordRects;
- foreach(ChatItem *item, checkItems) {
- foreach(QRectF wordRect, item->findWords(searchString(), caseSensitive())) {
+ QHash<quint64, QHash<quint64, QRectF>> wordRects;
+ foreach (ChatItem* item, checkItems) {
+ foreach (QRectF wordRect, item->findWords(searchString(), caseSensitive())) {
wordRects[(quint64)(wordRect.x() + item->x())][(quint64)(wordRect.y())] = wordRect;
}
}
bool deleteAll = false;
- QAbstractItemModel *model = _scene->model();
+ QAbstractItemModel* model = _scene->model();
Q_ASSERT(model);
if (_searchOnlyRegularMsgs) {
QModelIndex index = model->index(line->row(), 0);
deleteAll = true;
}
- foreach(QGraphicsItem *child, line->childItems()) {
- SearchHighlightItem *highlightItem = qgraphicsitem_cast<SearchHighlightItem *>(child);
+ foreach (QGraphicsItem* child, line->childItems()) {
+ auto* highlightItem = qgraphicsitem_cast<SearchHighlightItem*>(child);
if (!highlightItem)
continue;
- if (!deleteAll && wordRects.contains((quint64)(highlightItem->pos().x())) && wordRects[(quint64)(highlightItem->pos().x())].contains((quint64)(highlightItem->pos().y()))) {
- QRectF &wordRect = wordRects[(quint64)(highlightItem->pos().x())][(quint64)(highlightItem->pos().y())];
+ if (!deleteAll && wordRects.contains((quint64)(highlightItem->pos().x()))
+ && wordRects[(quint64)(highlightItem->pos().x())].contains((quint64)(highlightItem->pos().y()))) {
+ QRectF& wordRect = wordRects[(quint64)(highlightItem->pos().x())][(quint64)(highlightItem->pos().y())];
highlightItem->updateGeometry(wordRect.width(), wordRect.height());
}
else {
}
}
-
-void ChatViewSearchController::highlightLine(ChatLine *line)
+void ChatViewSearchController::highlightLine(ChatLine* line)
{
- QList<ChatItem *> checkItems;
+ QList<ChatItem*> checkItems;
if (_searchSenders)
checkItems << line->item(MessageModel::SenderColumn);
if (_searchMsgs)
checkItems << line->item(MessageModel::ContentsColumn);
- foreach(ChatItem *item, checkItems) {
- foreach(QRectF wordRect, item->findWords(searchString(), caseSensitive())) {
+ foreach (ChatItem* item, checkItems) {
+ foreach (QRectF wordRect, item->findWords(searchString(), caseSensitive())) {
_highlightItems << new SearchHighlightItem(wordRect.adjusted(item->x(), 0, item->x(), 0), line);
}
}
}
-
void ChatViewSearchController::repositionHighlights()
{
- QSet<ChatLine *> chatLines;
- foreach(SearchHighlightItem *item, _highlightItems) {
- ChatLine *line = qgraphicsitem_cast<ChatLine *>(item->parentItem());
+ QSet<ChatLine*> chatLines;
+ foreach (SearchHighlightItem* item, _highlightItems) {
+ auto* line = qgraphicsitem_cast<ChatLine*>(item->parentItem());
if (line)
chatLines << line;
}
- QList<ChatLine *> chatLineList(chatLines.toList());
- foreach(ChatLine *line, chatLineList) {
+ foreach (ChatLine* line, chatLines) {
repositionHighlights(line);
}
}
-
-void ChatViewSearchController::repositionHighlights(ChatLine *line)
+void ChatViewSearchController::repositionHighlights(ChatLine* line)
{
- QList<SearchHighlightItem *> searchHighlights;
- foreach(QGraphicsItem *child, line->childItems()) {
- SearchHighlightItem *highlightItem = qgraphicsitem_cast<SearchHighlightItem *>(child);
+ QList<SearchHighlightItem*> searchHighlights;
+ foreach (QGraphicsItem* child, line->childItems()) {
+ auto* highlightItem = qgraphicsitem_cast<SearchHighlightItem*>(child);
if (highlightItem)
searchHighlights << highlightItem;
}
QList<QPointF> wordPos;
if (_searchSenders) {
- foreach(QRectF wordRect, line->senderItem()->findWords(searchString(), caseSensitive())) {
+ foreach (QRectF wordRect, line->senderItem()->findWords(searchString(), caseSensitive())) {
wordPos << QPointF(wordRect.x() + line->senderItem()->x(), wordRect.y());
}
}
if (_searchMsgs) {
- foreach(QRectF wordRect, line->contentsItem()->findWords(searchString(), caseSensitive())) {
+ foreach (QRectF wordRect, line->contentsItem()->findWords(searchString(), caseSensitive())) {
wordPos << QPointF(wordRect.x() + line->contentsItem()->x(), wordRect.y());
}
}
- qSort(searchHighlights.begin(), searchHighlights.end(), SearchHighlightItem::firstInLine);
+ std::sort(searchHighlights.begin(), searchHighlights.end(), SearchHighlightItem::firstInLine);
Q_ASSERT(wordPos.count() == searchHighlights.count());
for (int i = 0; i < searchHighlights.count(); i++) {
}
}
-
void ChatViewSearchController::sceneDestroyed()
{
// WARNING: don't call any methods on scene!
- _scene = 0;
+ _scene = nullptr;
// the items will be automatically deleted when the scene is destroyed
// so we just have to clear the list;
_highlightItems.clear();
}
-
void ChatViewSearchController::setCaseSensitive(bool caseSensitive)
{
if (_caseSensitive == caseSensitive)
updateHighlights(caseSensitive);
}
-
void ChatViewSearchController::setSearchSenders(bool searchSenders)
{
if (_searchSenders == searchSenders)
updateHighlights(!searchSenders);
}
-
void ChatViewSearchController::setSearchMsgs(bool searchMsgs)
{
if (_searchMsgs == searchMsgs)
updateHighlights(!searchMsgs);
}
-
void ChatViewSearchController::setSearchOnlyRegularMsgs(bool searchOnlyRegularMsgs)
{
if (_searchOnlyRegularMsgs == searchOnlyRegularMsgs)
updateHighlights(searchOnlyRegularMsgs);
}
-
// ==================================================
// SearchHighlightItem
// ==================================================
-SearchHighlightItem::SearchHighlightItem(QRectF wordRect, QGraphicsItem *parent)
- : QObject(),
- QGraphicsItem(parent),
- _highlighted(false),
- _alpha(70),
- _timeLine(150)
+SearchHighlightItem::SearchHighlightItem(QRectF wordRect, QGraphicsItem* parent)
+ : QObject()
+ , QGraphicsItem(parent)
+ , _highlighted(false)
+ , _alpha(70)
+ , _timeLine(150)
{
setPos(wordRect.x(), wordRect.y());
updateGeometry(wordRect.width(), wordRect.height());
- connect(&_timeLine, SIGNAL(valueChanged(qreal)), this, SLOT(updateHighlight(qreal)));
+ connect(&_timeLine, &QTimeLine::valueChanged, this, &SearchHighlightItem::updateHighlight);
}
-
void SearchHighlightItem::setHighlighted(bool highlighted)
{
_highlighted = highlighted;
update();
}
-
void SearchHighlightItem::updateHighlight(qreal value)
{
_alpha = 70 + (int)(80 * value);
update();
}
-
-void SearchHighlightItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+void SearchHighlightItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
painter->drawRoundedRect(boundingRect(), radius, radius);
}
-
void SearchHighlightItem::updateGeometry(qreal width, qreal height)
{
prepareGeometryChange();
update();
}
-
-bool SearchHighlightItem::firstInLine(QGraphicsItem *item1, QGraphicsItem *item2)
+bool SearchHighlightItem::firstInLine(QGraphicsItem* item1, QGraphicsItem* item2)
{
if (item1->pos().y() != item2->pos().y())
return item1->pos().y() < item2->pos().y();