Salome HOME
Updated copyright comment
[modules/gui.git] / src / Qtx / QtxSearchTool.cxx
1 // Copyright (C) 2007-2024  CEA, EDF, OPEN CASCADE
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Lesser General Public License for more details.
12 //
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 //
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 //
19
20 // File   : QtxSearchTool.cxx
21 // Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com)
22 //
23 #include "QtxSearchTool.h"
24
25 #include <QApplication>
26 #include <QCheckBox>
27 #include <QEvent>
28 #include <QHBoxLayout>
29 #include <QKeyEvent>
30 #include <QLineEdit>
31 #include <QPersistentModelIndex>
32 #include <QShortcut>
33 #include <QTimer>
34 #include <QToolButton>
35 #include <QTreeView>
36
37 const char* const first_xpm[] = {
38 "16 16 14 1",
39 "       c None",
40 ".      c #111111",
41 "+      c #0A0A0A",
42 "@      c #161616",
43 "#      c #ACACAC",
44 "$      c #FC6D6E",
45 "%      c #FB6364",
46 "&      c #F25B5C",
47 "*      c #EA5859",
48 "=      c #C1494A",
49 "-      c #B64545",
50 ";      c #AB4040",
51 ">      c #A03C3C",
52 ",      c #99393A",
53 "           .    ",
54 " +@+      ..#   ",
55 " +$+#    .$.... ",
56 " +$+#   .$$$$$.#",
57 " +%+#  .%%%%%%.#",
58 " +&+# .&&&&&&&.#",
59 " +*+#.********.#",
60 " +=+.=========.#",
61 " +-+#.--------.#",
62 " +;+##.;;;;;;;.#",
63 " +>+# #.>>>>>>.#",
64 " +,+#  #.,,,,,.#",
65 " +,+#   #.,....#",
66 " +,+#    #..####",
67 " +@+#     #.#   ",
68 "  ###      ##   "};
69
70 const char* const last_xpm[] = {
71 "16 16 14 1",
72 "       c None",
73 ".      c #111111",
74 "+      c #0A0A0A",
75 "@      c #161616",
76 "#      c #FC6D6E",
77 "$      c #ACACAC",
78 "%      c #FB6364",
79 "&      c #F25B5C",
80 "*      c #EA5859",
81 "=      c #C1494A",
82 "-      c #B64545",
83 ";      c #AB4040",
84 ">      c #A03C3C",
85 ",      c #99393A",
86 "    .           ",
87 "    ..      +@+ ",
88 " ....#.     +#+$",
89 " .#####.    +#+$",
90 " .%%%%%%.   +%+$",
91 " .&&&&&&&.  +&+$",
92 " .********. +*+$",
93 " .=========.+=+$",
94 " .--------.$+-+$",
95 " .;;;;;;;.$$+;+$",
96 " .>>>>>>.$$ +>+$",
97 " .,,,,,.$$  +,+$",
98 " ....,.$$   +,+$",
99 " $$$..$$    +,+$",
100 "    .$$     +@+$",
101 "    $$       $$$"};
102
103 const char* const prev_xpm[] = {
104 "16 16 12 1",
105 "       c None",
106 ".      c #111111",
107 "+      c #ACACAC",
108 "@      c #FC6D6E",
109 "#      c #FB6364",
110 "$      c #F25B5C",
111 "%      c #EA5859",
112 "&      c #C1494A",
113 "*      c #B64545",
114 "=      c #AB4040",
115 "-      c #A03C3C",
116 ";      c #99393A",
117 "        .       ",
118 "       ..+      ",
119 "      .@......  ",
120 "     .@@@@@@@.+ ",
121 "    .########.+ ",
122 "   .$$$$$$$$$.+ ",
123 "  .%%%%%%%%%%.+ ",
124 " .&&&&&&&&&&&.+ ",
125 "  .**********.+ ",
126 "  +.=========.+ ",
127 "   +.--------.+ ",
128 "    +.;;;;;;;.+ ",
129 "     +.;......+ ",
130 "      +..++++++ ",
131 "       +.+      ",
132 "        ++      "};
133
134 const char* const next_xpm[] = {
135 "16 16 12 1",
136 "       c None",
137 ".      c #111111",
138 "+      c #FC6D6E",
139 "@      c #FB6364",
140 "#      c #F25B5C",
141 "$      c #EA5859",
142 "%      c #C1494A",
143 "&      c #B64545",
144 "*      c #ACACAC",
145 "=      c #AB4040",
146 "-      c #A03C3C",
147 ";      c #99393A",
148 "       .        ",
149 "       ..       ",
150 "  ......+.      ",
151 "  .+++++++.     ",
152 "  .@@@@@@@@.    ",
153 "  .#########.   ",
154 "  .$$$$$$$$$$.  ",
155 "  .%%%%%%%%%%%. ",
156 "  .&&&&&&&&&&.**",
157 "  .=========.** ",
158 "  .--------.**  ",
159 "  .;;;;;;;.**   ",
160 "  ......;.**    ",
161 "   ****..**     ",
162 "       .**      ",
163 "       **       "};
164
165 const char* const close_xpm[] = {
166 "16 16 8 1",
167 "       c None",
168 ".      c #D73727",
169 "+      c #E17765",
170 "@      c #E7957F",
171 "#      c #DE6F48",
172 "$      c #DF7B4F",
173 "%      c #FAE9E4",
174 "&      c #FFFFFF",
175 "                ",
176 "  ............  ",
177 " .+@@@@@@@@@@+. ",
178 " .@#$$$$$$$$#@. ",
179 " .@$$%$$$$%$$@. ",
180 " .@$%&%$$%&%$@. ",
181 " .@$$%&%%&%$$@. ",
182 " .@$$$%&&%$$$@. ",
183 " .@$$$%&&%$$$@. ",
184 " .@$$%&%%&%$$@. ",
185 " .@$%&%$$%&%$@. ",
186 " .@$$%$$$$%$$@. ",
187 " .@#$$$$$$$$#@. ",
188 " .+@@@@@@@@@@+. ",
189 "  ............  ",
190 "                "};
191
192 const char* highlightColor = "#FF6666";
193 const int DefaultAutoHideDelay = 10000;
194
195 /*!
196   \brief Wrap specified widget by another dumb widget.
197   \internal
198   \param parent widget to be used as parent for the dumb widget
199   \param w widget to be wrapped
200   \return wrapper widget
201 */
202 static QWidget* wrapWidget( QWidget* parent, QWidget* w )
203 {
204   QWidget* wrapper = new QWidget( parent );
205   w->setParent( wrapper );
206   QHBoxLayout* l = new QHBoxLayout( wrapper );
207   l->setMargin( 1 );
208   l->setSpacing( 0 );
209   l->addWidget( w );
210   return wrapper;
211 }
212
213 /*!
214   \class QtxSearchTool
215   \brief Context search tool.
216
217   The QtxSearchTool class implements a specific context search tool widget
218   which can be embedded into any GUI element.
219   It represents the usual dialog panel with the line edit box used to enter
220   text to be searched and set of buttons, like "Find Next", "Find Previous", etc.
221   In addition, the search modifiers like "Case sensitive search", "Wrap search"
222   are provided.
223
224   Actually the class QtxSearchTool does not perform a serach itself - it is only
225   the generic widget. To use this widget, you have to install a searcher depending
226   on your needs. This should be a successor of the class QtxSearchTool::Searcher -
227   it is the class which will perform actual search of the data in your widget
228   according to the widget type.
229
230   For the current moment, only one standard searcher is implemented: it is the
231   class QtxTreeViewSearcher, which can be used to search the text data in the
232   tree view widget (QTreeView). See this class for more details.
233
234   The usual usage of the searcher widget is the following:
235   \code
236   QTreeView* tree = new QTreeView( this );
237   QtxSearchTool* st = new QtxSearchTool( this, tree, QtxSearchTool::Standard );
238   st->setActivators( QtxSearchTool::SlashKey | QtxSearchTool::StandardKey );
239   st->setSearcher( new QtxTreeViewSearcher( tree ) );
240   \endcode
241
242   Note, that controls to be displayed by the search tool widget are passed as
243   ORed flags to the widget's constructor. At any time, the available controls
244   can be set/get with setControls() and controls() methods.
245   By default, all widgets are displayed (see also QtxSearchTool::Controls
246   enumeration).
247
248   The class QtxSearchTool also provides a way to add custom widgets -
249   these widgets are displayed at the bottom area of the tool box. Use
250   method addCustomWidget() to add custom widget to the search tool box.
251   Your searcher class can use custom widgets to perform advanced search.
252
253   The class supports different ways of the activation, all of them can be
254   switched on/off with setActivators() method. See QtxSearchTool::Activator
255   enumeration for more details.
256   By default, all methods are switched on: default hot key is <Ctrl><S> and
257   standard key bindings are the platform dependent keyboard shortcuts.
258   Shortcuts can be assigned with the setShortcuts() methods.
259 */
260
261 /*!
262   \brief Constructor.
263
264   Creates a search tool widget with parent widget \a parent
265   and watched widget \a watched. The controls to be displayed can be passed
266   via \a controls parameter. By default, all controls are displayed.
267
268   \param parent parent widget
269   \param watched watched widget
270   \param controls ORed controls flags (QtxSearchTool::Controls)
271   \sa setWatchedWidget(), setControls()
272 */
273 QtxSearchTool::QtxSearchTool( QWidget* parent, QWidget* watched, int controls, Qt::Orientation orientation )
274 : QFrame( parent ),
275   myWatched( watched ? watched : parent ),
276   mySearcher( 0 ),
277   myControls( controls ),
278   myActivators( None ),
279   myAutoHideTimer( 0 ),
280   myAutoHideEnabled( true )
281 {
282   init( orientation );
283 }
284
285 /*!
286   \brief Constructor.
287
288   Creates a search tool widget with parent widget \a parent.
289   Parameter \a parent is also used to set watched widget.
290   The controls to be displayed can be passed via \a controls parameter.
291   By default, all controls are displayed.
292
293   \param parent parent widget
294   \param controls ORed controls flags (QtxSearchTool::Controls)
295   \sa setWatchedWidget(), setControls()
296 */
297 QtxSearchTool::QtxSearchTool( QWidget* parent, int controls, Qt::Orientation orientation )
298 : QFrame( parent ),
299   myWatched( parent ),
300   mySearcher( 0 ),
301   myControls( controls ),
302   myActivators( None ),
303   myAutoHideTimer( 0 ),
304   myAutoHideEnabled( true )
305 {
306   init( orientation );
307 }
308
309 /*!
310   \brief Destructor.
311 */
312 QtxSearchTool::~QtxSearchTool()
313 {
314   clearShortcuts();
315   if ( mySearcher )
316     delete mySearcher;
317 }
318
319 /*!
320   \brief Get watched widget.
321   \return currently used watched widget
322   \sa setWatchedWidget(), activators(), setActivators()
323 */
324 QWidget* QtxSearchTool::watchedWidget() const
325 {
326   return myWatched;
327 }
328
329 /*!
330   \brief Set watched widget.
331
332   Watched widget is that one for which shortcut bindings are set.
333   When this widget has focus and any hot key binbding is pressed by the user,
334   the search tool box is activated. The same occurs if slash key is pressed and
335   QtxSearchTool::SlashKey activator is enabled. If the QtxSearchTool::PrintKey
336   activator is enabled, the tool box is activated if any printed key is pressed
337   by the user.
338
339   \param watched a widget to be watched by the search tool
340   \sa watchedWidget(), activators(), setActivators()
341 */
342 void QtxSearchTool::setWatchedWidget( QWidget* watched )
343 {
344   if ( myWatched )
345   {
346     myWatched->removeEventFilter( this );
347   }
348
349   myWatched = watched;
350
351   initShortcuts( shortcuts() );
352
353   if ( myWatched )
354   {
355     myWatched->installEventFilter( this );
356   }
357 }
358
359 /*!
360   \brief Get current searcher.
361   \return currently set searcher (QtxSearchTool::Searcher)
362   \sa setSearcher()
363 */
364 QtxSearchTool::Searcher* QtxSearchTool::searcher() const
365 {
366   return mySearcher;
367 }
368
369 /*!
370   \brief Assign searcher.
371
372   Note: the search tool takes ownership to the searcher
373   and destroys it when deleted.
374
375   \param s searcher to be used (QtxSearchTool::Searcher)
376   \sa searcher()
377 */
378 void QtxSearchTool::setSearcher( QtxSearchTool::Searcher* s )
379 {
380   if ( mySearcher )
381     delete mySearcher;
382   mySearcher = s;
383 }
384
385 /*!
386   \brief Get activators.
387   \return activators currently enabled (ORed QtxSearchTool::Activator flags)
388   \sa setActivators()
389 */
390 int QtxSearchTool::activators() const
391 {
392   return myActivators;
393 }
394
395 /*!
396   \brief Set activators.
397   \param flags set activators to be used (ORed QtxSearchTool::Activator flags)
398   \sa activators()
399 */
400 void QtxSearchTool::setActivators( const int flags )
401 {
402   myActivators = flags;
403   updateShortcuts();
404 }
405
406 /*!
407   \brief Get controls.
408   \return controls currently enabled (ORed QtxSearchTool::Controls flags)
409   \sa setControls()
410 */
411 int QtxSearchTool::controls() const
412 {
413   return myControls;
414 }
415
416 /*!
417   \brief Set controls.
418   \param ctrls controls to be displayed (ORed QtxSearchTool::Controls flags)
419   \sa controls()
420 */
421 void QtxSearchTool::setControls( const int ctrls )
422 {
423   if ( myControls == ctrls )
424     return;
425   myControls = ctrls;
426   updateControls();
427 }
428
429 /*!
430   \brief Get shortcuts.
431
432   Note: the standard bindings are not include to the resulting list.
433
434   \return list of shortcuts bindings currently set
435   \sa setShortcuts()
436 */
437 QList<QKeySequence> QtxSearchTool::shortcuts() const
438 {
439   QList<QKeySequence> ks;
440
441   ShortcutList::ConstIterator it;
442   int i;
443   for ( it = myShortcuts.begin(), i = 0; it != myShortcuts.end(); ++it, i++ )
444   {
445     if ( i > 2 ) ks.append( (*it)->key() );
446   }
447
448   return ks;
449 }
450
451 /*!
452   \brief Set shortcuts.
453   \param accel shortcut binding(s) to be used
454   \sa shortcuts()
455 */
456 void QtxSearchTool::setShortcuts( const QKeySequence& accel )
457 {
458   QList<QKeySequence> ks;
459   ks << accel;
460   setShortcuts( ks );
461 }
462
463 /*!
464   \brief Set shortcuts.
465   \param accel shortcut bindings to be used
466   \sa shortcuts()
467 */
468 void QtxSearchTool::setShortcuts( const QList<QKeySequence>& accels )
469 {
470   initShortcuts( accels );
471 }
472
473 /*!
474   \brief Add custom widget.
475   \param w custom widget to be added
476   \param id widget unique ID to be used (if < 0, automatically assigned)
477   \return widget unique ID
478   \sa customWidget(), customWidgetId()
479 */
480 int QtxSearchTool::addCustomWidget( QWidget* w, int id )
481 {
482   if ( !w ) return -1;
483
484   static int _wid = -1;
485
486   int wid = -1;
487   QMap<int, QWidget*>::ConstIterator it;
488   for ( it = myWidgets.begin(); it != myWidgets.end() && wid == -1; ++it )
489   {
490     if ( it.value() == w )
491       wid = it.key();
492   }
493
494   if ( wid != -1 )
495     return wid;
496
497   wid = id < 0 ? --_wid : id;
498
499   QBoxLayout* vbox = qobject_cast<QBoxLayout*>( layout() );
500   w->setParent( this );
501   vbox->addWidget( w );
502   myWidgets.insert( wid, w );
503
504   return wid;
505 }
506
507 /*!
508   \brief Get custom widget by ID.
509   \param id widget ID
510   \return custom widget or 0 if not found
511   \sa addCustomWidget(), customWidgetId()
512 */
513 QWidget* QtxSearchTool::customWidget( int id ) const
514 {
515   QWidget* w = 0;
516   if ( myWidgets.contains( id ) )
517     w = myWidgets[ id ];
518   return w;
519 }
520
521 /*!
522   \brief Get custom widget ID.
523   \param w custom widget
524   \return custom widget ID or -1 if widget does not belong to the search tool
525   \sa addCustomWidget(), customWidget()
526 */
527 int QtxSearchTool::customWidgetId( QWidget* w ) const
528 {
529   int wid = -1;
530   QMap<int, QWidget*>::ConstIterator it;
531   for ( it = myWidgets.begin(); it != myWidgets.end() && wid == -1; ++it )
532   {
533     if ( it.value() == w )
534       wid = it.key();
535   }
536   return wid;
537 }
538
539 /*!
540   \brief Check if auto-hide of the tool widget is enabled.
541
542   By default, the search tool widget is automatically hidden
543   after 10 seconds of idle (only if watched widget has input focus).
544
545   \return \c true if auto-hide option is set
546   \sa enableAutoHide()
547 */
548 bool QtxSearchTool::isAutoHideEnabled() const
549 {
550   return myAutoHideEnabled;
551 }
552
553 /*!
554   \brief Set/clear auto-hide option.
555
556   By default, the search tool widget is automatically hidden
557   after 10 seconds of idle (only if watched widget has input focus).
558
559   \param enable new option state
560   \sa isAutoHideEnabled()
561 */
562 void QtxSearchTool::enableAutoHide( bool enable )
563 {
564   if ( myAutoHideEnabled == enable ) return;
565
566   myAutoHideEnabled = enable;
567
568   if ( myAutoHideEnabled )
569   {
570     if ( isVisible() && !focused() )
571       myAutoHideTimer->start();
572   }
573   else
574   {
575     myAutoHideTimer->stop();
576   }
577 }
578
579 /*!
580   \brief Get 'case sensitive search' option value.
581
582   This method returns \c true if 'case sensitive search' control
583   is enabled and switched on.
584
585   \return \c true if case sensitive search is performed
586   \sa isRegExpSearch(), isSearchWrapped(), setControls()
587   \sa setCaseSensitive(), setRegExpSearch(), setSearchWrapped()
588 */
589 bool QtxSearchTool::isCaseSensitive() const
590 {
591   return myControls & Case && myIsCaseSens->isChecked();
592 }
593
594 /*!
595   \brief Get 'regular expression search' option value.
596
597   This method returns \c true if 'regular expression search' control
598   is enabled and switched on.
599
600   \return \c true if regular expression search is performed
601   \sa isCaseSensitive(), isSearchWrapped(), setControls()
602   \sa setCaseSensitive(), setRegExpSearch(), setSearchWrapped()
603 */
604 bool QtxSearchTool::isRegExpSearch() const
605 {
606   return myControls & RegExp && myIsRegExp->isChecked();
607 }
608
609 /*!
610   \brief Get 'search wrapping' option value.
611
612   This method returns \c true if 'wrap search' control
613   is enabled and switched on.
614
615   \return \c true if search wrapping is enabled
616   \sa isCaseSensitive(), isRegExpSearch(), setControls()
617   \sa setCaseSensitive(), setRegExpSearch(), setSearchWrapped()
618 */
619 bool QtxSearchTool::isSearchWrapped() const
620 {
621   return myControls & Wrap && myWrap->isChecked();
622 }
623
624 /*!
625   \brief Set 'case sensitive search' option value.
626   \param on new option state
627   \sa setRegExpSearch(), setSearchWrapped(), setControls()
628   \sa isCaseSensitive(), isRegExpSearch(), isSearchWrapped()
629 */
630 void QtxSearchTool::setCaseSensitive( bool on )
631 {
632   if ( myControls & Case )
633     myIsCaseSens->setChecked( on );
634 }
635
636 /*!
637   \brief Set 'regular expression search' option value.
638   \param on new option state
639   \sa setCaseSensitive(), setSearchWrapped(), setControls()
640   \sa isCaseSensitive(), isRegExpSearch(), isSearchWrapped()
641 */
642 void QtxSearchTool::setRegExpSearch( bool on )
643 {
644   if ( myControls & RegExp )
645     myIsRegExp->setChecked( on );
646 }
647
648 /*!
649   \brief Set 'search wrapping' option value.
650   \param on new option state
651   \sa setCaseSensitive(), setRegExpSearch(), setControls()
652   \sa isCaseSensitive(), isRegExpSearch(), isSearchWrapped()
653 */
654 void QtxSearchTool::setSearchWrapped( bool on )
655 {
656   if ( myControls & Wrap )
657     myWrap->setChecked( on );
658 }
659
660 /*!
661   \brief Customize event handling.
662   \param e event
663   \return \c true if event has been handled
664 */
665 bool QtxSearchTool::event( QEvent* e )
666 {
667   if ( e->type() == QEvent::EnabledChange )
668   {
669     updateShortcuts();
670   }
671   else if ( e->type() == QEvent::KeyPress )
672   {
673     QKeyEvent* ke = (QKeyEvent*)e;
674     if ( ke->key() == Qt::Key_Escape )
675       hide();
676   }
677   else if ( e->type() == QEvent::Hide && myWatched )
678   {
679     myWatched->setFocus();
680   }
681   return QFrame::event( e );
682 }
683
684 /*!
685   \brief Filter events from the watched widget.
686   \param o object
687   \param e event
688   \return \c true if further event processing should be stopped
689 */
690 bool QtxSearchTool::eventFilter( QObject* o, QEvent* e )
691 {
692   switch ( e->type() ) 
693   {
694   case QEvent::KeyPress:
695     if ( myWatched && o == myWatched )
696     {
697       QKeyEvent* ke = (QKeyEvent*)e;
698       int key = ke->key();
699       QString ttf = myData->text();
700       QString text = ke->text();
701       
702       if ( isVisible() )
703       {
704         switch ( key )
705         {
706         case Qt::Key_Escape:
707           hide();
708           return true;
709         case Qt::Key_Backspace:
710           ttf.chop( 1 );
711           break;
712         case Qt::Key_Return:
713         case Qt::Key_Enter:
714           findNext();
715           return true;
716         default:
717           if ( text.isEmpty() || !text[0].isPrint() )
718             return QFrame::eventFilter( o, e );
719           ttf += text;
720         }
721       }
722       else
723       {
724         if ( text.isEmpty() || ! isEnabled() || !text[0].isPrint() )
725           return QFrame::eventFilter( o, e );
726
727         if ( text.startsWith( '/' ) && myActivators & SlashKey )
728         {
729           myData->clear();
730           find();
731           return true;
732         }
733         else if ( !( myActivators & PrintKey ) )
734         {
735           return QFrame::eventFilter( o, e );
736         }
737         
738         ttf = text;
739         show();
740       }
741       myData->setText( ttf );
742       find( ttf );
743     }
744     break; // case QEvent::KeyPress
745   case QEvent::FocusIn:
746   case QEvent::FocusOut:
747     if ( focused() )
748     {
749       myAutoHideTimer->stop();
750     }
751     else if ( isVisible() && isAutoHideEnabled() )
752     {
753       myAutoHideTimer->start();
754     }
755     break;
756   default:
757     break;
758   }
759   return QFrame::eventFilter( o, e );
760 }
761
762 /*!
763   \brief Activate search tool.
764
765   Call this method to start new search.
766 */
767 void QtxSearchTool::find()
768 {
769   show();
770
771   myData->setFocus( Qt::ShortcutFocusReason );
772   myData->selectAll();
773   myAutoHideTimer->stop();
774 }
775
776 /*!
777   \brief Find next appropriate data.
778
779   Call this method to repeat the search in the forward direction.
780 */
781 void QtxSearchTool::findNext()
782 {
783   find( myData->text(), fNext );
784 }
785
786 /*!
787   \brief Find previous appropriate data.
788
789   Call this method to repeat the search in the backward direction.
790 */
791 void QtxSearchTool::findPrevious()
792 {
793   find( myData->text(), fPrevious );
794 }
795
796 /*!
797   \brief Find first appropriate data.
798
799   Call this method to find the very first appropriate data.
800 */
801 void QtxSearchTool::findFirst()
802 {
803   find( myData->text(), fFirst );
804 }
805
806 /*!
807   \brief Find last appropriate data.
808
809   Call this method to find the very last appropriate data.
810 */
811 void QtxSearchTool::findLast()
812 {
813   find( myData->text(), fLast );
814 }
815
816 /*!
817   \brief Perform search.
818   \internal
819   \param what text to be searched
820   \param where search flags
821 */
822 void QtxSearchTool::find( const QString& what, int where )
823 {
824   if ( !isVisible() )
825     show();
826
827   QPalette p = myData->palette();
828   p.setColor( QPalette::Active,
829               QPalette::Base,
830               QApplication::palette( myData ).color( QPalette::Active,
831                                                      QPalette::Base ) );
832
833   bool found = true;
834   if ( mySearcher && !what.isEmpty() )
835   {
836     switch( where )
837     {
838     case fNext:
839       found = mySearcher->findNext( what, this ); break;
840     case fPrevious:
841       found = mySearcher->findPrevious( what, this ); break;
842     case fFirst:
843       found = mySearcher->findFirst( what, this ); break;
844     case fLast:
845       found = mySearcher->findLast( what, this ); break;
846     case fAny:
847     default:
848       found = mySearcher->find( what, this ); break;
849     }
850   }
851
852   if ( !found )
853     p.setColor( QPalette::Active, QPalette::Base, QColor( highlightColor ) );
854
855   if ( !focused() && myAutoHideEnabled )
856     myAutoHideTimer->start();
857
858   myData->setPalette( p );
859 }
860
861 /*!
862   \brief Called when any search modifier is switched.
863   \internal
864 */
865 void QtxSearchTool::modifierSwitched()
866 {
867   find( myData->text() );
868 }
869
870 /*!
871   \brief Initialize the search tool widget.
872   \internal
873 */
874 void QtxSearchTool::init( Qt::Orientation orientation )
875 {
876   setFrameStyle( QFrame::StyledPanel | QFrame::Plain );
877
878   myBtnWidget = new QWidget( this );
879   QHBoxLayout* myBtnWidget_layout = new QHBoxLayout( myBtnWidget );
880   myBtnWidget_layout->setSpacing( 0 );
881   myBtnWidget_layout->setMargin( 0 );
882
883   myModWidget = new QWidget( this );
884   QHBoxLayout* myModWidget_layout = new QHBoxLayout( myModWidget );
885   myModWidget_layout->setSpacing( 0 );
886   myModWidget_layout->setMargin( 0 );
887
888   myClose = new QToolButton( myBtnWidget );
889   myClose->setIcon( QPixmap( close_xpm ) );
890   myClose->setAutoRaise( true );
891   myBtnWidget_layout->addWidget( wrapWidget( myBtnWidget, myClose ) );
892   connect( myClose, SIGNAL( clicked() ), this, SLOT( hide() ) );
893
894   myData = new QLineEdit( myBtnWidget );
895   myData->setMinimumWidth( 50 );
896   myBtnWidget_layout->addWidget( wrapWidget( myBtnWidget, myData ), 1 );
897   connect( myData, SIGNAL( textChanged( const QString& ) ), this, SLOT( find( const QString& ) ) );
898   connect( myData, SIGNAL( returnPressed() ), this, SLOT( findNext() ) );
899   myData->installEventFilter( this );
900
901   myToFirst = new QToolButton( myBtnWidget );
902   myToFirst->setIcon( QPixmap( first_xpm ) );
903   myToFirst->setAutoRaise( true );
904   myBtnWidget_layout->addWidget( wrapWidget( myBtnWidget, myToFirst ), 0 );
905   connect( myToFirst, SIGNAL( clicked() ), this, SLOT( findFirst() ) );
906   myToFirst->installEventFilter( this );
907
908   myPrev = new QToolButton( myBtnWidget );
909   myPrev->setIcon( QPixmap( prev_xpm ) );
910   myPrev->setAutoRaise( true );
911   myBtnWidget_layout->addWidget( wrapWidget( myBtnWidget, myPrev ), 0 );
912   connect( myPrev, SIGNAL( clicked() ), this, SLOT( findPrevious() ) );
913   myPrev->installEventFilter( this );
914
915   myNext = new QToolButton( myBtnWidget );
916   myNext->setIcon( QPixmap( next_xpm ) );
917   myNext->setAutoRaise( true );
918   myBtnWidget_layout->addWidget( wrapWidget( myBtnWidget, myNext ), 0 );
919   connect( myNext, SIGNAL( clicked() ), this, SLOT( findNext() ) );
920   myNext->installEventFilter( this );
921
922   myToLast = new QToolButton( myBtnWidget );
923   myToLast->setIcon( QPixmap( last_xpm ) );
924   myToLast->setAutoRaise( true );
925   myBtnWidget_layout->addWidget( wrapWidget( myBtnWidget, myToLast ), 0 );
926   connect( myToLast, SIGNAL( clicked() ), this, SLOT( findLast() ) );
927   myToLast->installEventFilter( this );
928
929   myIsCaseSens = new QCheckBox( tr( "Case sensitive" ), myModWidget );
930   myModWidget_layout->addWidget( wrapWidget( myBtnWidget, myIsCaseSens ) );
931   connect( myIsCaseSens, SIGNAL( stateChanged( int ) ), this, SLOT( modifierSwitched() ) );
932   myIsCaseSens->installEventFilter( this );
933
934   myIsRegExp = new QCheckBox( tr( "Regular expression" ), myModWidget );
935   myModWidget_layout->addWidget( wrapWidget( myBtnWidget, myIsRegExp ) );
936   connect( myIsRegExp, SIGNAL( stateChanged( int ) ), this, SLOT( modifierSwitched() ) );
937   myIsRegExp->installEventFilter( this );
938
939   myWrap = new QCheckBox( tr( "Wrap search" ), myModWidget );
940   myModWidget_layout->addWidget( wrapWidget( myBtnWidget, myWrap ) );
941   connect( myWrap, SIGNAL( stateChanged( int ) ), this, SLOT( modifierSwitched() ) );
942   myWrap->installEventFilter( this );
943
944   setWatchedWidget( myWatched );
945
946   setShortcuts( QKeySequence( "Ctrl+S" ) );
947   setActivators( Any );
948   
949   QBoxLayout* box = orientation == Qt::Vertical ? (QBoxLayout*)( new QVBoxLayout ) : (QBoxLayout*)( new QHBoxLayout );
950   box->setSpacing( 0 );
951   box->setMargin( 5 );
952   box->addWidget( myBtnWidget );
953   box->addWidget( myModWidget );
954   setLayout( box );
955
956   updateControls();
957 }
958
959 /*!
960   \brief Check if any child widget has input focus.
961   \internal
962   \return \c true if any child widget has input focus
963 */
964 bool QtxSearchTool::focused() const
965 {
966   return isVisible() && isAncestorOf( QApplication::focusWidget() );
967 }
968
969 /*!
970   \brief Clear shortcuts.
971   \internal
972 */
973 void QtxSearchTool::clearShortcuts()
974 {
975   ShortcutList::Iterator it;
976   for ( it = myShortcuts.begin(); it != myShortcuts.end(); ++it )
977   {
978     if ( !(*it).isNull() )
979     {
980       QShortcut* sc = (*it);
981       delete sc;
982     }
983   }
984   myShortcuts.clear();
985 }
986
987 /*!
988   \brief Install shortcuts.
989   \internal
990   \param accels shortcuts list
991 */
992 void QtxSearchTool::initShortcuts( const QList<QKeySequence>& accels )
993 {
994   clearShortcuts();
995
996   QWidget* p = myWatched ? myWatched : ( parentWidget() ? parentWidget() : this );
997   QShortcut* sc;
998
999   sc = new QShortcut( QKeySequence::Find, p );
1000   connect( sc, SIGNAL( activated() ), this, SLOT( find() ) );
1001   sc->setContext( Qt::WidgetShortcut );
1002   myShortcuts.append( sc );
1003
1004   sc = new QShortcut( QKeySequence::FindNext, p );
1005   sc->setContext( Qt::WidgetShortcut );
1006   connect( sc, SIGNAL( activated() ), this, SLOT( findNext() ) );
1007   myShortcuts.append( sc );
1008
1009   sc = new QShortcut( QKeySequence::FindPrevious, p );
1010   sc->setContext( Qt::WidgetShortcut );
1011   connect( sc, SIGNAL( activated() ), this, SLOT( findPrevious() ) );
1012   myShortcuts.append( sc );
1013
1014   QList<QKeySequence>::ConstIterator it;
1015   for ( it = accels.begin(); it != accels.end(); ++it )
1016   {
1017     sc = new QShortcut( *it, p );
1018     sc->setContext( Qt::WidgetShortcut );
1019     connect( sc, SIGNAL( activated() ), this, SLOT( find() ) );
1020     myShortcuts.append( sc );
1021   }
1022
1023   myAutoHideTimer = new QTimer( this );
1024   myAutoHideTimer->setInterval( DefaultAutoHideDelay );
1025   myAutoHideTimer->setSingleShot( true );
1026   connect( myAutoHideTimer, SIGNAL( timeout() ), this, SLOT( hide() ) );
1027
1028   updateShortcuts();
1029
1030   hide();
1031 }
1032
1033 /*!
1034   \brief Update shortcuts state.
1035   \internal
1036 */
1037 void QtxSearchTool::updateShortcuts()
1038 {
1039   int i;
1040   ShortcutList::Iterator it;
1041   for ( it = myShortcuts.begin(), i = 0; it != myShortcuts.end(); ++it, i++ )
1042   {
1043     (*it)->setEnabled( isEnabled() && ( ( i < 3 && myActivators & StandardKey ) ||
1044                                         ( i > 2 && myActivators & HotKey ) ) );
1045   }
1046 }
1047
1048 /*!
1049   \brief Update controls state.
1050   \internal
1051 */
1052 void QtxSearchTool::updateControls()
1053 {
1054   myData->parentWidget()->setVisible( myControls & Search );
1055   myNext->parentWidget()->setVisible( myControls & Next );
1056   myPrev->parentWidget()->setVisible( myControls & Prev );
1057   myToFirst->parentWidget()->setVisible( myControls & First );
1058   myToLast->parentWidget()->setVisible( myControls & Last );
1059   myClose->parentWidget()->setVisible( myControls & Close );
1060   myIsCaseSens->parentWidget()->setVisible( myControls & Case );
1061   myIsRegExp->parentWidget()->setVisible( myControls & RegExp );
1062   myWrap->parentWidget()->setVisible( myControls & Wrap );
1063
1064   myBtnWidget->setVisible( myControls & Standard );
1065   myModWidget->setVisible( myControls & Modifiers );
1066 }
1067
1068 /*!
1069   \class QtxSearchTool::Searcher
1070   \brief Generic searcher class.
1071
1072   Searcher is generic class which is used by the search tool to perform
1073   widget-dependant search.
1074
1075   To implement a searcher for some widget, just inherit from QtxSearchTool::Searcher
1076   and override pure virtual methods find(), findNext(), findPrevious(),
1077   findFirst() and findLast()
1078 */
1079
1080 /*!
1081   \brief Constructor.
1082 */
1083 QtxSearchTool::Searcher::Searcher()
1084 {
1085 }
1086
1087 /*!
1088   \brief Destructor.
1089 */
1090 QtxSearchTool::Searcher::~Searcher()
1091 {
1092 }
1093
1094 /*!
1095   \fn QtxSearchTool::Searcher::find(const QString& text, QtxSearchTool* st)
1096   \brief Start new search.
1097   \param text text to be found
1098   \param st search tool widget
1099   \sa findNext(), findPrevious(), findFirst(), findLast()
1100 */
1101
1102 /*!
1103   \fn QtxSearchTool::Searcher::findNext(const QString& text, QtxSearchTool* st)
1104   \brief Search next appropriate item.
1105   \param text text to be found
1106   \param st search tool widget
1107   \sa find(), findPrevious(), findFirst(), findLast()
1108 */
1109
1110 /*!
1111   \fn QtxSearchTool::Searcher::findPrevious(const QString& text, QtxSearchTool* st)
1112   \brief Search previous appropriate item.
1113   \param text text to be found
1114   \param st search tool widget
1115   \sa find(), findNext(), findFirst(), findLast()
1116 */
1117
1118 /*!
1119   \fn QtxSearchTool::Searcher::findFirst(const QString& text, QtxSearchTool* st)
1120   \brief Search first appropriate item.
1121   \param text text to be found
1122   \param st search tool widget
1123   \sa find(), findNext(), findPrevious(), findLast()
1124 */
1125
1126 /*!
1127   \fn QtxSearchTool::Searcher::findLast(const QString& text, QtxSearchTool* st)
1128   \brief Search last appropriate item.
1129   \param text text to be found
1130   \param st search tool widget
1131   \sa find(), findNext(), findPrevious(), findFirst()
1132 */
1133
1134 /*!
1135   \class QtxTreeViewSearcher
1136   \brief A QTreeView class based searcher.
1137
1138   The class QtxTreeViewSearcher can be used to find the items in the
1139   QTreeView widget.
1140
1141   The column for which data should be searched can be get/set with the
1142   searchColumn(), setSearchColumn() methods.
1143   By default, column 0 is used.
1144 */
1145
1146 /*!
1147   \brief Constructor.
1148   \param view tree view widget
1149   \param col column for which search to be performed (0 by default)
1150   \sa setSearchColumn()
1151 */
1152 QtxTreeViewSearcher::QtxTreeViewSearcher( QTreeView* view, int col )
1153   : myView( view ), myColumn( col )
1154 {
1155 }
1156
1157 /*!
1158   \brief Destructor.
1159 */
1160 QtxTreeViewSearcher::~QtxTreeViewSearcher()
1161 {
1162 }
1163
1164 /*!
1165   \brief Get column for which search is performed.
1166   \return column number
1167   \sa setSearchColumn()
1168 */
1169 int QtxTreeViewSearcher::searchColumn() const
1170 {
1171   return myColumn;
1172 }
1173
1174 /*!
1175   \brief Set column for which search should be performed.
1176   \param column column number
1177   \sa searchColumn()
1178 */
1179 void QtxTreeViewSearcher::setSearchColumn( int column )
1180 {
1181   myColumn = column;
1182 }
1183
1184 /*!
1185   \brief Start new search.
1186   \param text text to be found
1187   \param st search tool widget
1188   \sa findNext(), findPrevious(), findFirst(), findLast()
1189 */
1190 bool QtxTreeViewSearcher::find( const QString& text, QtxSearchTool* st )
1191 {
1192   if ( !myView )
1193     return false;
1194
1195   const QModelIndexList& l = myView->selectionModel() ?
1196     myView->selectionModel()->selectedIndexes() : QModelIndexList();
1197
1198   QModelIndex current;
1199   if ( l.count() > 0 )
1200     current = l.first();
1201
1202   bool wrapSearch = st->isSearchWrapped();
1203
1204   QModelIndexList found = findItems( text, st );
1205
1206   if ( found.count() > 0 )
1207   {
1208     if ( !current.isValid() )
1209     {
1210       showItem( found.first() );
1211       return true;
1212     }
1213
1214     if ( found.contains( current ) )
1215     {
1216       showItem( current );
1217       return true;
1218     }
1219
1220     QModelIndex next = findNearest( current, found, true );
1221     if ( next.isValid() )
1222     {
1223       showItem( next );
1224       return true;
1225     }
1226
1227     if ( wrapSearch )
1228     {
1229       showItem( found.first() );
1230       return true;
1231     }
1232   }
1233
1234   return false;
1235 }
1236
1237 /*!
1238   \brief Search next appropriate item.
1239   \param text text to be found
1240   \param st search tool widget
1241   \sa find(), findPrevious(), findFirst(), findLast()
1242 */
1243 bool QtxTreeViewSearcher::findNext( const QString& text, QtxSearchTool* st )
1244 {
1245   if ( !myView )
1246     return false;
1247
1248   const QModelIndexList& l = myView->selectionModel() ?
1249     myView->selectionModel()->selectedIndexes() : QModelIndexList();
1250
1251   QModelIndex current;
1252   if ( l.count() > 0 )
1253     current = l.first();
1254   else if ( myIndex.isValid() )
1255     current = myIndex;
1256
1257   bool wrapSearch = st->isSearchWrapped();
1258
1259   QModelIndexList found = findItems( text, st );
1260
1261   if ( found.count() > 0 )
1262   {
1263     if ( !current.isValid() )
1264     {
1265       showItem( found.first() );
1266       return true;
1267     }
1268
1269     QModelIndex next = findNearest( current, found, true );
1270     if ( next.isValid() )
1271     {
1272       showItem( next );
1273       return true;
1274     }
1275
1276     if ( wrapSearch )
1277     {
1278       showItem( found.first() );
1279       return true;
1280     }
1281   }
1282
1283   return false;
1284 }
1285
1286 /*!
1287   \brief Search previous appropriate item.
1288   \param text text to be found
1289   \param st search tool widget
1290   \sa find(), findNext(), findFirst(), findLast()
1291 */
1292 bool QtxTreeViewSearcher::findPrevious( const QString& text, QtxSearchTool* st )
1293 {
1294   if ( !myView )
1295     return false;
1296
1297   const QModelIndexList& l = myView->selectionModel() ?
1298     myView->selectionModel()->selectedIndexes() : QModelIndexList();
1299
1300   QModelIndex current;
1301   if ( l.count() > 0 )
1302     current = l.first();
1303   else if ( myIndex.isValid() )
1304     current = myIndex;
1305
1306   bool wrapSearch = st->isSearchWrapped();
1307
1308   QModelIndexList found = findItems( text, st );
1309
1310   if ( found.count() > 0 )
1311   {
1312     if ( !current.isValid() )
1313     {
1314       showItem( found.first() );
1315       return true;
1316     }
1317
1318     QModelIndex next = findNearest( current, found, false );
1319     if ( next.isValid() )
1320     {
1321       showItem( next );
1322       return true;
1323     }
1324
1325     if ( wrapSearch )
1326     {
1327       showItem( found.last() );
1328       return true;
1329     }
1330   }
1331
1332   return false;
1333 }
1334
1335 /*!
1336   \brief Search first appropriate item.
1337   \param text text to be found
1338   \param st search tool widget
1339   \sa find(), findNext(), findPrevious(), findLast()
1340 */
1341 bool QtxTreeViewSearcher::findFirst( const QString& text, QtxSearchTool* st )
1342 {
1343   QModelIndexList found = findItems( text, st );
1344
1345   if ( found.count() > 0 )
1346   {
1347     showItem( found.first() );
1348     return true;
1349   }
1350
1351   return false;
1352 }
1353
1354 /*!
1355   \brief Search last appropriate item.
1356   \param text text to be found
1357   \param st search tool widget
1358   \sa find(), findNext(), findPrevious(), findFirst()
1359 */
1360 bool QtxTreeViewSearcher::findLast( const QString& text, QtxSearchTool* st )
1361 {
1362   QModelIndexList found = findItems( text, st );
1363
1364   if ( found.count() > 0 )
1365   {
1366     showItem( found.last() );
1367     return true;
1368   }
1369
1370   return false;
1371 }
1372
1373 /*!
1374   \brief Get match flags to be used by the searcher.
1375   \param st search tool widget
1376 */
1377 Qt::MatchFlags QtxTreeViewSearcher::matchFlags( QtxSearchTool* st ) const
1378 {
1379   Qt::MatchFlags fl = Qt::MatchRecursive;
1380
1381   if ( st->isCaseSensitive() )
1382     fl = fl | Qt::MatchCaseSensitive;
1383   if ( st->isRegExpSearch() )
1384     fl = fl | Qt::MatchRegExp;
1385   else
1386     fl = fl | Qt::MatchContains;
1387
1388   return fl;
1389 }
1390
1391 /*!
1392   \brief Find all appropriate items.
1393   \internal
1394   \param text text to be found
1395   \param st search tool widget
1396 */
1397 QModelIndexList QtxTreeViewSearcher::findItems( const QString& text, QtxSearchTool* st )
1398 {
1399   QString s = text;
1400
1401   Qt::MatchFlags fl = matchFlags( st );
1402   if ( fl & Qt::MatchRegExp ) {
1403     if ( !s.startsWith( "^" ) && !s.startsWith( ".*" ) )
1404       s.prepend( ".*" );
1405     if ( !s.endsWith( "$" ) && !s.endsWith( ".*" ) )
1406       s.append( ".*" );
1407   }
1408
1409   if ( myView->model() )
1410     return myView->model()->match( myView->model()->index( 0, myColumn ),
1411                                    Qt::DisplayRole,
1412                                    s, -1, fl );
1413   return QModelIndexList();
1414 }
1415
1416 /*!
1417   \brief Find model index from the list nearest to the specified index.
1418   \internal
1419   \param index model index for which a nearest item is searched
1420   \param lst list of model indices
1421   \param direction if \c true find next appropriate item, otherwise find privious
1422   appropriate item
1423 */
1424 QModelIndex QtxTreeViewSearcher::findNearest( const QModelIndex& index,
1425                                               const QModelIndexList& lst,
1426                                               bool direction )
1427 {
1428   if ( direction )
1429   {
1430     QListIterator<QModelIndex> it( lst );
1431     while ( it.hasNext() )
1432     {
1433       QModelIndex found = it.next();
1434       if ( compareIndices( found, index ) > 0 )
1435         return found;
1436     }
1437   }
1438   else
1439   {
1440     QListIterator<QModelIndex> it( lst );
1441     it.toBack();
1442     while ( it.hasPrevious() )
1443     {
1444       QModelIndex found = it.previous();
1445       if ( compareIndices( found, index ) < 0 )
1446         return found;
1447     }
1448   }
1449   return QModelIndex();
1450 }
1451
1452 /*!
1453   \brief Ensure the found item to become visible and selected.
1454   \internal
1455   \param index item to be shown
1456 */
1457 void QtxTreeViewSearcher::showItem( const QModelIndex& index )
1458 {
1459   if ( myView && index.isValid() && myView->selectionModel() )
1460   {
1461     QItemSelectionModel::SelectionFlags f =
1462       QItemSelectionModel::Select | QItemSelectionModel::Rows | QItemSelectionModel::Clear;
1463     myView->selectionModel()->select( index, f );
1464     myView->scrollTo( index );
1465     myIndex = index;
1466   }
1467 }
1468
1469 /*!
1470   \brief Get unique item ID.
1471   \internal
1472   \param index model index
1473   \return item ID
1474 */
1475 QString QtxTreeViewSearcher::getId( const QModelIndex& index )
1476 {
1477   QStringList ids;
1478   QModelIndex p = index;
1479   while ( p.isValid() )
1480   {
1481     ids.prepend( QString::number( p.row() ) );
1482     p = p.parent();
1483   }
1484   ids.prepend( "0" );
1485   return ids.join( ":" );
1486 }
1487
1488 /*!
1489   \brief Compare items.
1490   \internal
1491   \param left first model index to be compared
1492   \param right last model index to be compared
1493   \return 0 if items are equal, negative value if left item is less than right one
1494   and positive value otherwise
1495 */
1496 int QtxTreeViewSearcher::compareIndices( const QModelIndex& left,
1497                                          const QModelIndex& right )
1498 {
1499   QString leftId = getId( left );
1500   QString rightId = getId( right );
1501
1502   QStringList idsLeft  = leftId.split( ":", QString::SkipEmptyParts );
1503   QStringList idsRight = rightId.split( ":", QString::SkipEmptyParts );
1504
1505   for ( int i = 0; i < idsLeft.count() && i < idsRight.count(); i++ )
1506   {
1507     int lid = idsLeft[i].toInt();
1508     int rid = idsRight[i].toInt();
1509     if ( lid != rid )
1510       return lid - rid;
1511   }
1512   return idsLeft.count() < idsRight.count() ? -1 :
1513     ( idsLeft.count() == idsRight.count() ? 0 : 1 );
1514 }