1 /***************************************************************************
2 * Copyright (C) 2005-2010 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
21 #include "chatviewsearchcontroller.h"
23 #include <QAbstractItemModel>
28 #include "chatlinemodel.h"
29 #include "chatscene.h"
30 #include "messagemodel.h"
32 ChatViewSearchController::ChatViewSearchController(QObject *parent)
36 _caseSensitive(false),
37 _searchSenders(false),
39 _searchOnlyRegularMsgs(true)
43 void ChatViewSearchController::setSearchString(const QString &searchString) {
44 QString oldSearchString = _searchString;
45 _searchString = searchString;
47 if(!searchString.startsWith(oldSearchString) || oldSearchString.isEmpty()) {
48 // we can't reuse our all findings... cler the scene and do it all over
52 updateHighlights(true);
57 void ChatViewSearchController::setScene(ChatScene *scene) {
63 disconnect(_scene, 0, this, 0);
64 disconnect(Client::messageModel(), 0, this, 0);
65 qDeleteAll(_highlightItems);
66 _highlightItems.clear();
73 connect(_scene, SIGNAL(destroyed()), this, SLOT(sceneDestroyed()));
74 connect(_scene, SIGNAL(layoutChanged()), this, SLOT(repositionHighlights()));
75 connect(Client::messageModel(), SIGNAL(finishedBacklogFetch(BufferId)), this, SLOT(updateHighlights()));
79 void ChatViewSearchController::highlightNext() {
80 if(_highlightItems.isEmpty())
83 if(_currentHighlight < _highlightItems.count()) {
84 _highlightItems.at(_currentHighlight)->setHighlighted(false);
88 if(_currentHighlight >= _highlightItems.count())
89 _currentHighlight = 0;
90 _highlightItems.at(_currentHighlight)->setHighlighted(true);
91 emit newCurrentHighlight(_highlightItems.at(_currentHighlight));
94 void ChatViewSearchController::highlightPrev() {
95 if(_highlightItems.isEmpty())
98 if(_currentHighlight < _highlightItems.count()) {
99 _highlightItems.at(_currentHighlight)->setHighlighted(false);
103 if(_currentHighlight < 0)
104 _currentHighlight = _highlightItems.count() - 1;
105 _highlightItems.at(_currentHighlight)->setHighlighted(true);
106 emit newCurrentHighlight(_highlightItems.at(_currentHighlight));
109 void ChatViewSearchController::updateHighlights(bool reuse) {
114 QSet<ChatLine *> chatLines;
115 foreach(SearchHighlightItem *highlightItem, _highlightItems) {
116 ChatLine *line = qgraphicsitem_cast<ChatLine *>(highlightItem->parentItem());
120 foreach(ChatLine *line, QList<ChatLine *>(chatLines.toList())) {
121 updateHighlights(line);
124 QPointF oldHighlightPos;
125 if(!_highlightItems.isEmpty() && _currentHighlight < _highlightItems.count()) {
126 oldHighlightPos = _highlightItems[_currentHighlight]->scenePos();
128 qDeleteAll(_highlightItems);
129 _highlightItems.clear();
130 Q_ASSERT(_highlightItems.isEmpty());
132 if(searchString().isEmpty() || !(_searchSenders || _searchMsgs))
135 checkMessagesForHighlight();
137 if(!_highlightItems.isEmpty()) {
138 if(!oldHighlightPos.isNull()) {
139 int start = 0; int end = _highlightItems.count() - 1;
143 startPos = _highlightItems[start]->scenePos();
144 endPos = _highlightItems[end]->scenePos();
145 if(startPos == oldHighlightPos) {
146 _currentHighlight = start;
149 if(endPos == oldHighlightPos) {
150 _currentHighlight = end;
153 if(end - start == 1) {
154 _currentHighlight = start;
157 int pivot = (end + start) / 2;
158 QPointF pivotPos = _highlightItems[pivot]->scenePos();
159 if(startPos.y() == endPos.y()) {
160 if(oldHighlightPos.x() <= pivotPos.x())
165 if(oldHighlightPos.y() <= pivotPos.y())
172 _currentHighlight = _highlightItems.count() - 1;
174 _highlightItems[_currentHighlight]->setHighlighted(true);
175 emit newCurrentHighlight(_highlightItems[_currentHighlight]);
180 void ChatViewSearchController::checkMessagesForHighlight(int start, int end) {
181 QAbstractItemModel *model = _scene->model();
185 end = model->rowCount() - 1;
191 for(int row = start; row <= end; row++) {
192 if(_searchOnlyRegularMsgs) {
193 index = model->index(row, 0);
194 if(!checkType((Message::Type)index.data(MessageModel::TypeRole).toInt()))
197 highlightLine(_scene->chatLine(row));
201 void ChatViewSearchController::updateHighlights(ChatLine *line) {
202 QList<ChatItem *> checkItems;
204 checkItems << line->item(MessageModel::SenderColumn);
207 checkItems << line->item(MessageModel::ContentsColumn);
209 QHash<quint64, QHash<quint64, QRectF> > wordRects;
210 foreach(ChatItem *item, checkItems) {
211 foreach(QRectF wordRect, item->findWords(searchString(), caseSensitive())) {
212 wordRects[(quint64)(wordRect.x() + item->x())][(quint64)(wordRect.y())] = wordRect;
216 bool deleteAll = false;
217 QAbstractItemModel *model = _scene->model();
219 if(_searchOnlyRegularMsgs) {
220 QModelIndex index = model->index(line->row(), 0);
221 if(!checkType((Message::Type)index.data(MessageModel::TypeRole).toInt()))
226 foreach(QGraphicsItem *child, line->childItems()) {
227 SearchHighlightItem *highlightItem = qgraphicsitem_cast<SearchHighlightItem *>(child);
231 if(!deleteAll && wordRects.contains((quint64)(highlightItem->pos().x())) && wordRects[(quint64)(highlightItem->pos().x())].contains((quint64)(highlightItem->pos().y()))) {
232 QRectF &wordRect = wordRects[(quint64)(highlightItem->pos().x())][(quint64)(highlightItem->pos().y())];
233 highlightItem->updateGeometry(wordRect.width(), wordRect.height());
235 int pos = _highlightItems.indexOf(highlightItem);
236 if(pos == _currentHighlight) {
238 } else if (pos < _currentHighlight) {
242 _highlightItems.removeAt(pos);
243 delete highlightItem;
248 void ChatViewSearchController::highlightLine(ChatLine *line) {
249 QList<ChatItem *> checkItems;
251 checkItems << line->item(MessageModel::SenderColumn);
254 checkItems << line->item(MessageModel::ContentsColumn);
256 foreach(ChatItem *item, checkItems) {
257 foreach(QRectF wordRect, item->findWords(searchString(), caseSensitive())) {
258 _highlightItems << new SearchHighlightItem(wordRect.adjusted(item->x(), 0, item->x(), 0), line);
263 void ChatViewSearchController::repositionHighlights() {
264 QSet<ChatLine *> chatLines;
265 foreach(SearchHighlightItem *item, _highlightItems) {
266 ChatLine *line = qgraphicsitem_cast<ChatLine *>(item->parentItem());
270 QList<ChatLine *> chatLineList(chatLines.toList());
271 foreach(ChatLine *line, chatLineList) {
272 repositionHighlights(line);
276 void ChatViewSearchController::repositionHighlights(ChatLine *line) {
277 QList<SearchHighlightItem *> searchHighlights;
278 foreach(QGraphicsItem *child, line->childItems()) {
279 SearchHighlightItem *highlightItem = qgraphicsitem_cast<SearchHighlightItem *>(child);
281 searchHighlights << highlightItem;
284 if(searchHighlights.isEmpty())
287 QList<QPointF> wordPos;
289 foreach(QRectF wordRect, line->senderItem()->findWords(searchString(), caseSensitive())) {
290 wordPos << QPointF(wordRect.x() + line->senderItem()->x(), wordRect.y());
294 foreach(QRectF wordRect, line->contentsItem()->findWords(searchString(), caseSensitive())) {
295 wordPos << QPointF(wordRect.x() + line->contentsItem()->x(), wordRect.y());
299 qSort(searchHighlights.begin(), searchHighlights.end(), SearchHighlightItem::firstInLine);
301 Q_ASSERT(wordPos.count() == searchHighlights.count());
302 for(int i = 0; i < searchHighlights.count(); i++) {
303 searchHighlights.at(i)->setPos(wordPos.at(i));
307 void ChatViewSearchController::sceneDestroyed() {
308 // WARNING: don't call any methods on scene!
310 // the items will be automatically deleted when the scene is destroyed
311 // so we just have to clear the list;
312 _highlightItems.clear();
315 void ChatViewSearchController::setCaseSensitive(bool caseSensitive) {
316 if(_caseSensitive == caseSensitive)
319 _caseSensitive = caseSensitive;
321 // we can reuse the original search results if the new search
322 // parameters are a restriction of the original one
323 updateHighlights(caseSensitive);
326 void ChatViewSearchController::setSearchSenders(bool searchSenders) {
327 if(_searchSenders == searchSenders)
330 _searchSenders = searchSenders;
331 // we can reuse the original search results if the new search
332 // parameters are a restriction of the original one
333 updateHighlights(!searchSenders);
336 void ChatViewSearchController::setSearchMsgs(bool searchMsgs) {
337 if(_searchMsgs == searchMsgs)
340 _searchMsgs = searchMsgs;
342 // we can reuse the original search results if the new search
343 // parameters are a restriction of the original one
344 updateHighlights(!searchMsgs);
347 void ChatViewSearchController::setSearchOnlyRegularMsgs(bool searchOnlyRegularMsgs) {
348 if(_searchOnlyRegularMsgs == searchOnlyRegularMsgs)
351 _searchOnlyRegularMsgs = searchOnlyRegularMsgs;
353 // we can reuse the original search results if the new search
354 // parameters are a restriction of the original one
355 updateHighlights(searchOnlyRegularMsgs);
359 // ==================================================
360 // SearchHighlightItem
361 // ==================================================
362 SearchHighlightItem::SearchHighlightItem(QRectF wordRect, QGraphicsItem *parent)
364 QGraphicsItem(parent),
369 setPos(wordRect.x(), wordRect.y());
370 updateGeometry(wordRect.width(), wordRect.height());
372 connect(&_timeLine, SIGNAL(valueChanged(qreal)), this, SLOT(updateHighlight(qreal)));
375 void SearchHighlightItem::setHighlighted(bool highlighted) {
376 _highlighted = highlighted;
379 _timeLine.setDirection(QTimeLine::Forward);
381 _timeLine.setDirection(QTimeLine::Backward);
383 if(_timeLine.state() != QTimeLine::Running)
389 void SearchHighlightItem::updateHighlight(qreal value) {
390 _alpha = 70 + (int)(80 * value);
394 void SearchHighlightItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
398 painter->setPen(QPen(QColor(0, 0, 0), 1.5));
399 painter->setBrush(QColor(254, 237, 45, _alpha));
400 painter->setRenderHints(QPainter::Antialiasing);
401 qreal radius = boundingRect().height() * 0.30;
402 painter->drawRoundedRect(boundingRect(), radius, radius);
405 void SearchHighlightItem::updateGeometry(qreal width, qreal height) {
406 prepareGeometryChange();
407 qreal sizedelta = height * 0.1;
408 _boundingRect = QRectF(-sizedelta, -sizedelta, width + 2 * sizedelta, height + 2 * sizedelta);
412 bool SearchHighlightItem::firstInLine(QGraphicsItem *item1, QGraphicsItem *item2) {
413 if(item1->pos().y() != item2->pos().y())
414 return item1->pos().y() < item2->pos().y();
416 return item1->pos().x() < item2->pos().x();