fixing wrong positioning of items (fixes overlapping daychange bug)
[quassel.git] / src / qtui / chatviewsearchcontroller.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-08 by the Quassel Project                          *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
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.                                           *
9  *                                                                         *
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.                          *
14  *                                                                         *
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  ***************************************************************************/
20
21 #include "chatviewsearchcontroller.h"
22
23 #include <QAbstractItemModel>
24 #include <QPainter>
25
26 #include "chatitem.h"
27 #include "chatlinemodel.h"
28 #include "chatscene.h"
29 #include "messagemodel.h"
30
31 ChatViewSearchController::ChatViewSearchController(QObject *parent)
32   : QObject(parent),
33     _scene(0),
34     _currentHighlight(0),
35     _caseSensitive(false),
36     _searchSenders(false),
37     _searchMsgs(true),
38     _searchOnlyRegularMsgs(true)
39 {
40 }
41
42 void ChatViewSearchController::setSearchString(const QString &searchString) {
43   QString oldSearchString = _searchString;
44   _searchString = searchString;
45   if(_scene) {
46     if(!searchString.startsWith(oldSearchString) || oldSearchString.isEmpty()) {
47       // we can't reuse our all findings... cler the scene and do it all over
48       updateHighlights();
49     } else {
50       // reuse all findings
51       updateHighlights(true);
52     }
53   }
54 }
55
56  void ChatViewSearchController::setScene(ChatScene *scene) {
57   Q_ASSERT(scene);
58   if(scene == _scene)
59     return;
60
61   if(_scene) {
62     disconnect(_scene, 0, this, 0);
63     qDeleteAll(_highlightItems);
64     _highlightItems.clear();
65   }
66
67   _scene = scene;
68   if(!scene)
69     return;
70
71   connect(_scene, SIGNAL(destroyed()), this, SLOT(sceneDestroyed()));
72   updateHighlights();
73  }
74
75 void ChatViewSearchController::highlightNext() {
76   if(_highlightItems.isEmpty())
77     return;
78
79   if(_currentHighlight < _highlightItems.count()) {
80     _highlightItems.at(_currentHighlight)->setHighlighted(false);
81   }
82
83   _currentHighlight++;
84   if(_currentHighlight >= _highlightItems.count())
85     _currentHighlight = 0;
86   _highlightItems.at(_currentHighlight)->setHighlighted(true);
87   emit newCurrentHighlight(_highlightItems.at(_currentHighlight));
88 }
89
90 void ChatViewSearchController::highlightPrev() {
91   if(_highlightItems.isEmpty())
92     return;
93
94   if(_currentHighlight < _highlightItems.count()) {
95     _highlightItems.at(_currentHighlight)->setHighlighted(false);
96   }
97
98   _currentHighlight--;
99   if(_currentHighlight < 0)
100     _currentHighlight = _highlightItems.count() - 1;
101   _highlightItems.at(_currentHighlight)->setHighlighted(true);
102   emit newCurrentHighlight(_highlightItems.at(_currentHighlight));
103 }
104
105 void ChatViewSearchController::updateHighlights(bool reuse) {
106   if(!_scene)
107     return;
108
109   QAbstractItemModel *model = _scene->model();
110   Q_ASSERT(model);
111
112
113   QList<ChatLine *> chatLines;
114   if(reuse) {
115     foreach(SearchHighlightItem *highlightItem, _highlightItems) {
116       ChatLine *line = dynamic_cast<ChatLine *>(highlightItem->parentItem());
117       if(!line || chatLines.contains(line))
118         continue;
119       chatLines << line;
120     }
121   }
122
123   qDeleteAll(_highlightItems);
124   _highlightItems.clear();
125   Q_ASSERT(_highlightItems.isEmpty());
126
127   if(searchString().isEmpty() || !(_searchSenders || _searchMsgs))
128     return;
129
130   if(reuse) {
131     QModelIndex index;
132     foreach(ChatLine *line, chatLines) {
133       if(_searchOnlyRegularMsgs) {
134         index = model->index(line->row(), 0);
135         if(!checkType((Message::Type)index.data(MessageModel::TypeRole).toInt()))
136           continue;
137       }
138       highlightLine(line);
139     }
140   } else {
141     // we have to crawl through the data
142     QModelIndex index;
143     QString plainText;
144     int rowCount = model->rowCount();
145     for(int row = 0; row < rowCount; row++) {
146       ChatLine *line = _scene->chatLine(row);
147
148       if(_searchOnlyRegularMsgs) {
149         index = model->index(row, 0);
150         if(!checkType((Message::Type)index.data(MessageModel::TypeRole).toInt()))
151           continue;
152       }
153       highlightLine(line);
154     }
155   }
156
157   if(!_highlightItems.isEmpty()) {
158     _highlightItems.last()->setHighlighted(true);
159     _currentHighlight = _highlightItems.count() - 1;
160     emit newCurrentHighlight(_highlightItems.last());
161   }
162 }
163
164 void ChatViewSearchController::highlightLine(ChatLine *line) {
165   QList<ChatItem *> checkItems;
166   if(_searchSenders)
167     checkItems << &(line->item(MessageModel::SenderColumn));
168
169   if(_searchMsgs)
170     checkItems << &(line->item(MessageModel::ContentsColumn));
171
172   foreach(ChatItem *item, checkItems) {
173     foreach(QRectF wordRect, item->findWords(searchString(), caseSensitive())) {
174       _highlightItems << new SearchHighlightItem(wordRect.adjusted(item->x(), 0, item->x(), 0), line);
175     }
176   }
177 }
178
179 void ChatViewSearchController::sceneDestroyed() {
180   // WARNING: don't call any methods on scene!
181   _scene = 0;
182   // the items will be automatically deleted when the scene is destroyed
183   // so we just have to clear the list;
184   _highlightItems.clear();
185 }
186
187 void ChatViewSearchController::setCaseSensitive(bool caseSensitive) {
188   if(_caseSensitive == caseSensitive)
189     return;
190
191   _caseSensitive = caseSensitive;
192
193   // we can reuse the original search results if the new search
194   // parameters are a restriction of the original one
195   updateHighlights(caseSensitive);
196 }
197
198 void ChatViewSearchController::setSearchSenders(bool searchSenders) {
199   if(_searchSenders == searchSenders)
200     return;
201
202   _searchSenders = searchSenders;
203   // we can reuse the original search results if the new search
204   // parameters are a restriction of the original one
205   updateHighlights(!searchSenders);
206 }
207
208 void ChatViewSearchController::setSearchMsgs(bool searchMsgs) {
209   if(_searchMsgs == searchMsgs)
210     return;
211
212   _searchMsgs = searchMsgs;
213
214   // we can reuse the original search results if the new search
215   // parameters are a restriction of the original one
216   updateHighlights(!searchMsgs);
217 }
218
219 void ChatViewSearchController::setSearchOnlyRegularMsgs(bool searchOnlyRegularMsgs) {
220   if(_searchOnlyRegularMsgs == searchOnlyRegularMsgs)
221     return;
222
223   _searchOnlyRegularMsgs = searchOnlyRegularMsgs;
224
225   // we can reuse the original search results if the new search
226   // parameters are a restriction of the original one
227   updateHighlights(searchOnlyRegularMsgs);
228 }
229
230
231 // ==================================================
232 //  SearchHighlightItem
233 // ==================================================
234 SearchHighlightItem::SearchHighlightItem(QRectF wordRect, QGraphicsItem *parent)
235   : QObject(),
236     QGraphicsItem(parent),
237     _highlighted(false),
238     _alpha(100),
239     _timeLine(150)
240 {
241   setPos(wordRect.x(), wordRect.y());
242   qreal sizedelta = wordRect.height() * 0.1;
243   _boundingRect = QRectF(-sizedelta, -sizedelta, wordRect.width() + 2 * sizedelta, wordRect.height() + 2 * sizedelta);
244
245   connect(&_timeLine, SIGNAL(valueChanged(qreal)), this, SLOT(updateHighlight(qreal)));
246 }
247
248 void SearchHighlightItem::setHighlighted(bool highlighted) {
249   _highlighted = highlighted;
250
251   if(highlighted)
252     _timeLine.setDirection(QTimeLine::Forward);
253   else
254     _timeLine.setDirection(QTimeLine::Backward);
255
256   if(_timeLine.state() != QTimeLine::Running)
257     _timeLine.start();
258
259   update();
260 }
261
262 void SearchHighlightItem::updateHighlight(qreal value) {
263   _alpha = 100 + 155 * value;
264   update();
265 }
266
267 void SearchHighlightItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
268   Q_UNUSED(option);
269   Q_UNUSED(widget);
270
271   painter->setPen(QPen(QColor(0, 0, 0, _alpha), 1.5));
272   painter->setBrush(QColor(254, 237, 45, _alpha));
273   painter->setRenderHints(QPainter::Antialiasing);
274   qreal radius = boundingRect().height() * 0.30;
275   painter->drawRoundedRect(boundingRect(), radius, radius);
276 }