ENH: Added a new item delegate class to handle highlighting items
[kitware:paraview.git] / Qt / Components / pqItemViewSearchWidget.cxx
1 /*=========================================================================
2
3 Program: ParaView
4 Module:    pqItemViewSearchWidget.cxx
5
6 Copyright (c) 2005-2008 Sandia Corporation, Kitware Inc.
7 All rights reserved.
8
9 ParaView is a free software; you can redistribute it and/or modify it
10 under the terms of the ParaView license version 1.2. 
11
12 See License_v1.2.txt for the full ParaView license.
13 A copy of this license can be obtained by contacting
14 Kitware Inc.
15 28 Corporate Drive
16 Clifton Park, NY 12065
17 USA
18
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
23 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31 =========================================================================*/
32
33 #include "pqItemViewSearchWidget.h"
34
35 #include "pqHighlightItemDelegate.h"
36 #include "pqWaitCursor.h"
37
38 #include <QAbstractItemModel>
39 #include <QAbstractItemView>
40 #include <QApplication>
41 #include <QEvent>
42 #include <QItemSelectionModel>
43 #include <QKeyEvent>
44 #include <QLabel>
45 #include <QLayout>
46 #include <QModelIndex>
47 #include <QPointer>
48 #include <QShowEvent>
49 #include <QStyle>
50
51 #include "ui_pqItemViewSearchWidget.h"
52
53 class pqItemViewSearchWidget::PIMPL : public Ui::pqItemViewSearchWidget
54 {
55 public:
56   PIMPL(QWidget* parentW)
57   {
58   this->BaseWidget = parentW ?
59     qobject_cast<QAbstractItemView*>(parentW) : NULL;;
60     this->RedPal.setColor(QPalette::Base, QColor(240,128,128));
61   this->WhitePal.setColor(QPalette::Base, QColor(Qt::white));
62   this->Highlighter = new pqHighlightItemDelegate(QColor(210,210,231));
63   this->UnHighlighter = new pqHighlightItemDelegate(QColor(Qt::white));
64   }
65   ~PIMPL() {}
66
67   QString SearchString;
68   QModelIndex CurrentFound;
69   QPointer<QAbstractItemView> BaseWidget;
70   QPalette RedPal;
71   QPalette WhitePal;
72   QPointer<pqHighlightItemDelegate> Highlighter;
73   QPointer<QStyledItemDelegate> UnHighlighter;
74 };
75
76 pqItemViewSearchWidget::pqItemViewSearchWidget(QWidget* parentW):
77   Superclass(parentW->parentWidget(), Qt::Dialog|Qt::FramelessWindowHint)
78 {
79   this->Private = new pqItemViewSearchWidget::PIMPL(parentW);
80   this->Private->setupUi(this);
81   QObject::connect(this->Private->lineEditSearch, SIGNAL(textEdited(QString)),
82     this, SLOT(updateSearch(QString)));
83   QObject::connect(this->Private->checkBoxMattchCase, SIGNAL(toggled(bool)),
84     this, SLOT(updateSearch()));
85   QObject::connect(this->Private->nextButton, SIGNAL(clicked()),
86     this, SLOT(findNext()));
87   QObject::connect(this->Private->previousButton, SIGNAL(clicked()),
88     this, SLOT(findPrevious()));
89   this->installEventFilter(this);
90   this->Private->lineEditSearch->installEventFilter(this);
91   this->setAttribute(Qt::WA_DeleteOnClose, true);
92   this->setFocusPolicy(Qt::StrongFocus);
93 }
94
95 // -------------------------------------------------------------------------
96 pqItemViewSearchWidget::~pqItemViewSearchWidget()
97 {
98   this->Private->lineEditSearch->removeEventFilter(this);
99   if(this->Private->CurrentFound.isValid() &&
100     this->Private->BaseWidget)
101     {
102     this->Private->BaseWidget->setItemDelegateForRow(
103       this->Private->CurrentFound.row(), this->Private->UnHighlighter);
104     }
105   delete this->Private;
106 }
107
108 // -------------------------------------------------------------------------
109 void pqItemViewSearchWidget::setBaseWidget(QWidget* widget)
110 {
111   this->Private->BaseWidget = widget ?
112     qobject_cast<QAbstractItemView*>(widget) : NULL;;
113 }
114
115 // -------------------------------------------------------------------------
116 void pqItemViewSearchWidget::showSearchWidget()
117 {
118   if(!this->Private->BaseWidget)
119     {
120     return;
121     }
122   QPoint mappedPoint = this->Private->BaseWidget->parentWidget()->childrenRect().topLeft();
123   mappedPoint.setX(0);
124   mappedPoint = this->Private->BaseWidget->mapToGlobal(mappedPoint);
125   mappedPoint = this->mapFromGlobal(mappedPoint);
126   this->setGeometry( mappedPoint.x(), mappedPoint.y()-2*this->height(),
127                      this->Private->BaseWidget->width(), this->height());
128   this->setModal(false);
129   this->show();
130   this->raise();
131   this->activateWindow();
132 }
133
134 //-----------------------------------------------------------------------------
135 bool pqItemViewSearchWidget::eventFilter(QObject* obj, QEvent* anyevent)
136 {
137  if(anyevent->type()== QEvent::KeyPress)
138    {
139    QKeyEvent *e = dynamic_cast<QKeyEvent*>(anyevent);
140    if(e && e->modifiers()==Qt::AltModifier)
141      {
142      this->keyPressEvent(e);
143      return true;
144      }
145    }
146  else if(anyevent->type()== QEvent::WindowDeactivate)
147    {
148    if(obj == this && !this->isActiveWindow())
149      {
150      anyevent->accept();
151      this->close();
152      return true;
153      }
154    }
155   return this->Superclass::eventFilter(obj, anyevent);
156 }
157
158 //-----------------------------------------------------------------------------
159 void pqItemViewSearchWidget::keyPressEvent(QKeyEvent *e)
160 {
161   if ((e->key()==Qt::Key_Escape))
162     {
163     e->accept();
164     this->accept();
165     }
166   else if((e->modifiers()==Qt::AltModifier))
167     {
168     e->accept();
169     if(e->key()==Qt::Key_N)
170       {
171       this->findNext();
172       }
173     else if(e->key()==Qt::Key_P)
174       {
175       this->findPrevious();
176       }
177     }
178 }
179 //-----------------------------------------------------------------------------
180 void pqItemViewSearchWidget::showEvent(QShowEvent* e)
181 {
182   this->activateWindow();
183   this->Private->lineEditSearch->setFocus();
184   this->Superclass::showEvent(e);
185 }
186
187 //-----------------------------------------------------------------------------
188 void pqItemViewSearchWidget::updateSearch(QString searchText)
189 {
190   this->Private->SearchString = searchText;
191   QModelIndex current;
192   if(this->Private->CurrentFound.isValid())
193     {
194     this->Private->BaseWidget->setItemDelegateForRow(
195       this->Private->CurrentFound.row(), this->Private->UnHighlighter);
196     }
197   this->Private->CurrentFound = current;
198   QAbstractItemView* theView = this->Private->BaseWidget;
199   if (!theView || this->Private->SearchString.size() == 0)
200     {
201     this->Private->lineEditSearch->setPalette(this->Private->WhitePal);
202     return;
203     }
204   const QString searchString =this->Private->SearchString;
205   // Loop through all the model indices in the model
206   QAbstractItemModel* viewModel = theView->model();
207
208   for( int r = 0; r < viewModel->rowCount(); r++ )
209     {
210     for( int c = 0; c < viewModel->columnCount( ); c++ )
211       {
212       current = viewModel->index( r, c );
213       if (this->searchModel( viewModel, current, searchString ))
214         {
215         return;
216         }
217       }
218     }
219
220   this->Private->lineEditSearch->setPalette(this->Private->RedPal);
221 }
222 //-----------------------------------------------------------------------------
223 bool pqItemViewSearchWidget::searchModel( const QAbstractItemModel * M,
224   const QModelIndex & curIdx, const QString & searchString,
225   ItemSearchType searchType ) const
226 {
227   bool found=false;
228   if( !curIdx.isValid() )
229     {
230     return found;
231     }
232   pqWaitCursor wCursor;
233
234   if(searchType == Previous && M->hasChildren(curIdx))
235     {
236     QModelIndex current;
237     // Search curIdx index's children
238     for( int r = M->rowCount( curIdx )-1; r >=0&& !found;r-- )
239       {
240       for( int c = M->columnCount( curIdx )-1; c>=0 && !found;c-- )
241         {
242         current = M->index( r, c, curIdx );
243         found = this->searchModel( M, current, searchString, searchType );
244         }
245       }
246     }
247
248   if(found)
249     {
250     return found;
251     }
252   // Try to match the curIdx index itself
253   if(this->matchString(M, curIdx, searchString))
254     {
255     return true;
256     }
257
258   if(searchType != Previous && M->hasChildren(curIdx))
259     {
260     QModelIndex current;
261     // Search curIdx index's children
262     for( int r = 0; r < M->rowCount( curIdx )&& !found;r ++ )
263       {
264       for( int c = 0; c < M->columnCount( curIdx )&& !found;c ++ )
265         {
266         current = M->index( r, c, curIdx );
267         found = this->searchModel( M, current, searchString, searchType );
268         }
269       }
270     }
271
272   return found;
273 }
274 //-----------------------------------------------------------------------------
275 void pqItemViewSearchWidget::findNext()
276 {
277   QAbstractItemView* theView = this->Private->BaseWidget;
278   if (!theView || this->Private->SearchString.size() == 0)
279     {
280     return;
281     }
282   const QString searchString =this->Private->SearchString;
283
284   // Loop through all the model indices in the model
285   QAbstractItemModel* viewModel = theView->model();
286   QModelIndex current, firstmactch, start=this->Private->CurrentFound;
287   if(start.isValid())
288     {
289     this->Private->BaseWidget->setItemDelegateForRow(
290       start.row(), this->Private->UnHighlighter);
291     // search the rest of this index -- horizontally
292     int r = start.row();
293     int numCols = viewModel->columnCount(start.parent());
294     for( int c = start.column()+1; c < numCols; c++ )
295       {
296       current = start.sibling(r, c);
297       if (this->searchModel( viewModel, current, searchString ))
298         {
299         return;
300         }
301       }
302
303    // Search all the children
304    if(viewModel->hasChildren(start))
305      {
306      for( r = 0; r < viewModel->rowCount(start); r++ )
307        {
308        for( int c = 0; c < viewModel->columnCount(start); c++ )
309          {
310          current = viewModel->index( r, c, start );
311          if (this->searchModel( viewModel, current, searchString ))
312            {
313            return;
314            }
315          }
316        }
317      }
318
319     // search the siblings of this index and
320     // need to recursive up parents until root
321     QModelIndex pidx = start.parent();
322     QModelIndex tmpIdx = start;
323     while(pidx.isValid())
324       {
325       for( r = tmpIdx.row()+1; r < viewModel->rowCount(pidx); r++ )
326         {
327         for( int c = 0; c < viewModel->columnCount(pidx); c++ )
328           {
329           current = viewModel->index( r, c, pidx );
330           if (this->searchModel( viewModel, current, searchString ))
331             {
332             return;
333             }
334           }
335         }
336       tmpIdx = pidx;
337       pidx = pidx.parent();
338       }
339
340     // If not found, start from next row
341     int numRows = viewModel->rowCount();
342     for( r = start.row()+1; r < numRows; r++ )
343       {
344       for( int c = 0; c < viewModel->columnCount( ); c++ )
345         {
346         current = viewModel->index( r, c );
347         if (this->searchModel( viewModel, current, searchString ))
348           {
349           return;
350           }
351         }
352       }
353
354     // If still not found, start from (0,0)
355     for( r = 0; r <= start.row(); r++ )
356       {
357       for( int c = 0; c <= start.column(); c++ )
358         {
359         current = viewModel->index( r, c );
360         if (this->searchModel( viewModel, current, searchString ))
361           {
362           return;
363           }
364         }
365       }
366
367     this->Private->lineEditSearch->setPalette(this->Private->RedPal);
368     }
369   else
370     {
371     this->updateSearch();
372     }
373 }
374 //-----------------------------------------------------------------------------
375 void pqItemViewSearchWidget::updateSearch()
376 {
377   this->updateSearch(this->Private->SearchString);
378 }
379 //-----------------------------------------------------------------------------
380 void pqItemViewSearchWidget::findPrevious()
381 {
382   QAbstractItemView* theView = this->Private->BaseWidget;
383   if (!theView || this->Private->SearchString.size() == 0)
384     {
385     return;
386     }
387   const QString searchString =this->Private->SearchString;
388
389   // Loop through all the model indices in the model
390   QAbstractItemModel* viewModel = theView->model();
391   QModelIndex current, firstmactch, start=this->Private->CurrentFound;
392   if(start.isValid())
393     {
394     this->Private->BaseWidget->setItemDelegateForRow(
395       start.row(), this->Private->UnHighlighter);
396     // search the rest of this index
397     int r = start.row();
398     for( int c = start.column()-1; c >=0; c-- )
399       {
400       current = start.sibling(r, c);
401       if (this->searchModel( viewModel, current, searchString, Previous ))
402         {
403         return;
404         }
405       }
406
407     // search the siblings of this index
408     // need to recursive up parents until root
409     QModelIndex pidx = start.parent();
410     QModelIndex tmpIdx = start;
411     while(pidx.isValid())
412       {
413       for( r = tmpIdx.row()-1; r >=0; r--)
414         {
415         for( int c = viewModel->columnCount(pidx)-1; c >=0; c-- )
416           {
417           current = viewModel->index( r, c, pidx );
418           if (this->searchModel( viewModel, current, searchString, Previous ))
419             {
420             return;
421             }
422           }
423         }
424       // Try to match the parent index itself
425       if(this->matchString(viewModel, pidx, searchString))
426         {
427         return;
428         }
429       tmpIdx = pidx;
430       pidx = pidx.parent();
431       }
432
433     // If not found, start from previous row
434     for( r = start.row()-1; r >=0; r-- )
435       {
436       for( int c = viewModel->columnCount()-1; c >=0; c-- )
437         {
438         current = viewModel->index( r, c );
439         if (this->searchModel( viewModel, current, searchString, Previous ))
440           {
441           return;
442           }
443         }
444       }
445     // If still not found, start from the end(rowCount,columnCount)
446     for( r = viewModel->rowCount()-1; r >= start.row(); r-- )
447       {
448       for( int c =viewModel->columnCount( )-1; c>=0 ; c-- )
449         {
450         current = viewModel->index( r, c );
451         if (this->searchModel( viewModel, current, searchString, Previous ))
452           {
453           return;
454           }
455         }
456       }
457
458     this->Private->lineEditSearch->setPalette(this->Private->RedPal);
459     }
460   else
461     {
462     this->updateSearch();
463     }
464 }
465
466 //-----------------------------------------------------------------------------
467 bool pqItemViewSearchWidget::matchString( const QAbstractItemModel * M,
468                                          const QModelIndex & curIdx,
469                                          const QString & searchString) const
470 {// Try to match the curIdx index itself
471   QString strText = M->data(curIdx, Qt::DisplayRole).toString();
472   Qt::CaseSensitivity cs = this->Private->checkBoxMattchCase->isChecked() ?
473   Qt::CaseSensitive : Qt::CaseInsensitive;
474   if(strText.contains(searchString, cs))
475     {
476     this->Private->CurrentFound = curIdx;
477     this->Private->BaseWidget->setItemDelegateForRow(
478       this->Private->CurrentFound.row(), this->Private->Highlighter);
479     this->Private->BaseWidget->scrollTo(this->Private->CurrentFound);
480     this->Private->lineEditSearch->setPalette(this->Private->WhitePal);
481     return true;
482     }
483   return false;
484 }