Salome HOME
b7f9444af832e9c6e77a3b17b0d0660de487a349
[modules/gui.git] / src / Qtx / QtxSearchTool.cxx
1 //  Copyright (C) 2007-2008  CEA/DEN, EDF R&D, OPEN CASCADE
2 //
3 //  Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 //  CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
5 //
6 //  This library is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU Lesser General Public
8 //  License as published by the Free Software Foundation; either
9 //  version 2.1 of the License.
10 //
11 //  This library is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 //  Lesser General Public License for more details.
15 //
16 //  You should have received a copy of the GNU Lesser General Public
17 //  License along with this library; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 //
20 //  See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 //
22 // File   : QtxSearchTool.cxx
23 // Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com)
24 //
25 #include "QtxSearchTool.h"
26
27 #include <QApplication>
28 #include <QCheckBox>
29 #include <QEvent>
30 #include <QHBoxLayout>
31 #include <QKeyEvent>
32 #include <QLineEdit>
33 #include <QPersistentModelIndex>
34 #include <QShortcut>
35 #include <QTimer>
36 #include <QToolButton>
37 #include <QTreeView>
38
39 const char* const first_xpm[] = {
40 "16 16 14 1",
41 "       c None",
42 ".      c #111111",
43 "+      c #0A0A0A",
44 "@      c #161616",
45 "#      c #ACACAC",
46 "$      c #FC6D6E",
47 "%      c #FB6364",
48 "&      c #F25B5C",
49 "*      c #EA5859",
50 "=      c #C1494A",
51 "-      c #B64545",
52 ";      c #AB4040",
53 ">      c #A03C3C",
54 ",      c #99393A",
55 "           .    ",
56 " +@+      ..#   ",
57 " +$+#    .$.... ",
58 " +$+#   .$$$$$.#",
59 " +%+#  .%%%%%%.#",
60 " +&+# .&&&&&&&.#",
61 " +*+#.********.#",
62 " +=+.=========.#",
63 " +-+#.--------.#",
64 " +;+##.;;;;;;;.#",
65 " +>+# #.>>>>>>.#",
66 " +,+#  #.,,,,,.#",
67 " +,+#   #.,....#",
68 " +,+#    #..####",
69 " +@+#     #.#   ",
70 "  ###      ##   "};
71
72 const char* const last_xpm[] = {
73 "16 16 14 1",
74 "       c None",
75 ".      c #111111",
76 "+      c #0A0A0A",
77 "@      c #161616",
78 "#      c #FC6D6E",
79 "$      c #ACACAC",
80 "%      c #FB6364",
81 "&      c #F25B5C",
82 "*      c #EA5859",
83 "=      c #C1494A",
84 "-      c #B64545",
85 ";      c #AB4040",
86 ">      c #A03C3C",
87 ",      c #99393A",
88 "    .           ",
89 "    ..      +@+ ",
90 " ....#.     +#+$",
91 " .#####.    +#+$",
92 " .%%%%%%.   +%+$",
93 " .&&&&&&&.  +&+$",
94 " .********. +*+$",
95 " .=========.+=+$",
96 " .--------.$+-+$",
97 " .;;;;;;;.$$+;+$",
98 " .>>>>>>.$$ +>+$",
99 " .,,,,,.$$  +,+$",
100 " ....,.$$   +,+$",
101 " $$$..$$    +,+$",
102 "    .$$     +@+$",
103 "    $$       $$$"};
104
105 const char* const prev_xpm[] = {
106 "16 16 12 1",
107 "       c None",
108 ".      c #111111",
109 "+      c #ACACAC",
110 "@      c #FC6D6E",
111 "#      c #FB6364",
112 "$      c #F25B5C",
113 "%      c #EA5859",
114 "&      c #C1494A",
115 "*      c #B64545",
116 "=      c #AB4040",
117 "-      c #A03C3C",
118 ";      c #99393A",
119 "        .       ",
120 "       ..+      ",
121 "      .@......  ",
122 "     .@@@@@@@.+ ",
123 "    .########.+ ",
124 "   .$$$$$$$$$.+ ",
125 "  .%%%%%%%%%%.+ ",
126 " .&&&&&&&&&&&.+ ",
127 "  .**********.+ ",
128 "  +.=========.+ ",
129 "   +.--------.+ ",
130 "    +.;;;;;;;.+ ",
131 "     +.;......+ ",
132 "      +..++++++ ",
133 "       +.+      ",
134 "        ++      "};
135
136 const char* const next_xpm[] = {
137 "16 16 12 1",
138 "       c None",
139 ".      c #111111",
140 "+      c #FC6D6E",
141 "@      c #FB6364",
142 "#      c #F25B5C",
143 "$      c #EA5859",
144 "%      c #C1494A",
145 "&      c #B64545",
146 "*      c #ACACAC",
147 "=      c #AB4040",
148 "-      c #A03C3C",
149 ";      c #99393A",
150 "       .        ",
151 "       ..       ",
152 "  ......+.      ",
153 "  .+++++++.     ",
154 "  .@@@@@@@@.    ",
155 "  .#########.   ",
156 "  .$$$$$$$$$$.  ",
157 "  .%%%%%%%%%%%. ",
158 "  .&&&&&&&&&&.**",
159 "  .=========.** ",
160 "  .--------.**  ",
161 "  .;;;;;;;.**   ",
162 "  ......;.**    ",
163 "   ****..**     ",
164 "       .**      ",
165 "       **       "};
166
167 const char* const close_xpm[] = {
168 "16 16 8 1",
169 "       c None",
170 ".      c #D73727",
171 "+      c #E17765",
172 "@      c #E7957F",
173 "#      c #DE6F48",
174 "$      c #DF7B4F",
175 "%      c #FAE9E4",
176 "&      c #FFFFFF",
177 "                ",
178 "  ............  ",
179 " .+@@@@@@@@@@+. ",
180 " .@#$$$$$$$$#@. ",
181 " .@$$%$$$$%$$@. ",
182 " .@$%&%$$%&%$@. ",
183 " .@$$%&%%&%$$@. ",
184 " .@$$$%&&%$$$@. ",
185 " .@$$$%&&%$$$@. ",
186 " .@$$%&%%&%$$@. ",
187 " .@$%&%$$%&%$@. ",
188 " .@$$%$$$$%$$@. ",
189 " .@#$$$$$$$$#@. ",
190 " .+@@@@@@@@@@+. ",
191 "  ............  ",
192 "                "};
193
194 const char* highlightColor = "#FF6666";
195 const int DefaultAutoHideDelay = 10000;
196
197 /*!
198   \brief Wrap specified widget by another dumb widget.
199   \internal
200   \param parent widget to be used as parent for the dumb widget
201   \param w widget to be wrapped
202   \return wrapper widget
203 */
204 static QWidget* wrapWidget( QWidget* parent, QWidget* w )
205 {
206   QWidget* wrapper = new QWidget( parent );
207   w->setParent( wrapper );
208   QHBoxLayout* l = new QHBoxLayout( wrapper );
209   l->setMargin( 1 );
210   l->setSpacing( 0 );
211   l->addWidget( w );
212   return wrapper;
213 }
214
215 /*!
216   \class QtxSearchTool
217   \brief Context search tool.
218
219   The QtxSearchTool class implements a specific context search tool widget
220   which can be embedded into any GUI element.
221   It represents the usual dialog panel with the line edit box used to enter
222   text to be searched and set of buttons, like "Find Next", "Find Previous", etc.
223   In addition, the search modifiers like "Case sensitive search", "Wrap search"
224   are provided.
225
226   Actually the class QtxSearchTool does not perform a serach itself - it is only
227   the generic widget. To use this widget, you have to install a searcher depending
228   on your needs. This should be a successor of the class QtxSearchTool::Searcher -
229   it is the class which will perform actual search of the data in your widget
230   according to the widget type.
231
232   For the current moment, only one standard searcher is implemented: it is the
233   class QtxTreeViewSearcher, which can be used to search the text data in the
234   tree view widget (QTreeView). See this class for more details.
235
236   The usual usage of the searcher widget is the following:
237   \code
238   QTreeView* tree = new QTreeView( this );
239   QtxSearchTool* st = new QtxSearchTool( this, tree, QtxSearchTool::Standard );
240   st->setActivators( QtxSearchTool::SlashKey | QtxSearchTool::StandardKey );
241   st->setSearcher( new QtxTreeViewSearcher( tree ) );
242   \endcode
243
244   Note, that controls to be displayed by the search tool widget are passed as
245   ORed flags to the widget's constructor. At any time, the available controls
246   can be set/get with setControls() and controls() methods.
247   By default, all widgets are displayed (see also QtxSearchTool::Controls
248   enumeration).
249
250   The class QtxSearchTool also provides a way to add custom widgets -
251   these widgets are displayed at the bottom area of the tool box. Use
252   method addCustomWidget() to add custom widget to the search tool box.
253   Your searcher class can use custom widgets to perform advanced search.
254
255   The class supports different ways of the activation, all of them can be
256   switched on/off with setActivators() method. See QtxSearchTool::Activator
257   enumeration for more details.
258   By default, all methods are switched on: default hot key is <Ctrl><S> and
259   standard key bindings are the platform dependent keyboard shortcuts.
260   Shortcuts can be assigned with the setShortcuts() methods.
261 */
262
263 /*!
264   \brief Constructor.
265
266   Creates a search tool widget with parent widget \a parent
267   and watched widget \a watched. The controls to be displayed can be passed
268   via \a controls parameter. By default, all controls are displayed.
269
270   \param parent parent widget
271   \param watched watched widget
272   \param controls ORed controls flags (QtxSearchTool::Controls)
273   \sa setWatchedWidget(), setControls()
274 */
275 QtxSearchTool::QtxSearchTool( QWidget* parent, QWidget* watched, int controls )
276 : QFrame( parent ),
277   myWatched( watched ? watched : parent ),
278   mySearcher( 0 ),
279   myControls( controls ),
280   myActivators( None ),
281   myAutoHideTimer( 0 ),
282   myAutoHideEnabled( true )
283 {
284   init();
285 }
286
287 /*!
288   \brief Constructor.
289
290   Creates a search tool widget with parent widget \a parent.
291   Parameter \a parent is also used to set watched widget.
292   The controls to be displayed can be passed via \a controls parameter.
293   By default, all controls are displayed.
294
295   \param parent parent widget
296   \param controls ORed controls flags (QtxSearchTool::Controls)
297   \sa setWatchedWidget(), setControls()
298 */
299 QtxSearchTool::QtxSearchTool( QWidget* parent, int controls )
300 : QFrame( parent ),
301   myWatched( parent ),
302   mySearcher( 0 ),
303   myControls( controls ),
304   myActivators( None ),
305   myAutoHideTimer( 0 ),
306   myAutoHideEnabled( true )
307 {
308   init();
309 }
310
311 /*!
312   \brief Destructor.
313 */
314 QtxSearchTool::~QtxSearchTool()
315 {
316   clearShortcuts();
317   if ( mySearcher )
318     delete mySearcher;
319 }
320
321 /*!
322   \brief Get watched widget.
323   \return currently used watched widget
324   \sa setWatchedWidget(), activators(), setActivators()
325 */
326 QWidget* QtxSearchTool::watchedWidget() const
327 {
328   return myWatched;
329 }
330
331 /*!
332   \brief Set watched widget.
333
334   Watched widget is that one for which shortcut bindings are set.
335   When this widget has focus and any hot key binbding is pressed by the user,
336   the search tool box is activated. The same occurs if slash key is pressed and
337   QtxSearchTool::SlashKey activator is enabled. If the QtxSearchTool::PrintKey
338   activator is enabled, the tool box is activated if any printed key is pressed
339   by the user.
340
341   \param watched a widget to be watched by the search tool
342   \sa watchedWidget(), activators(), setActivators()
343 */
344 void QtxSearchTool::setWatchedWidget( QWidget* watched )
345 {
346   if ( myWatched )
347   {
348     myWatched->removeEventFilter( this );
349   }
350
351   myWatched = watched;
352
353   initShortcuts( shortcuts() );
354
355   if ( myWatched )
356   {
357     myWatched->installEventFilter( this );
358   }
359 }
360
361 /*!
362   \brief Get current searcher.
363   \return currently set searcher (QtxSearchTool::Searcher)
364   \sa setSearcher()
365 */
366 QtxSearchTool::Searcher* QtxSearchTool::searcher() const
367 {
368   return mySearcher;
369 }
370
371 /*!
372   \brief Assign searcher.
373
374   Note: the search tool takes ownership to the searcher
375   and destroys it when deleted.
376
377   \param s searcher to be used (QtxSearchTool::Searcher)
378   \sa searcher()
379 */
380 void QtxSearchTool::setSearcher( QtxSearchTool::Searcher* s )
381 {
382   if ( mySearcher )
383     delete mySearcher;
384   mySearcher = s;
385 }
386
387 /*!
388   \brief Get activators.
389   \return activators currently enabled (ORed QtxSearchTool::Activator flags)
390   \sa setActivators()
391 */
392 int QtxSearchTool::activators() const
393 {
394   return myActivators;
395 }
396
397 /*!
398   \brief Set activators.
399   \param flags set activators to be used (ORed QtxSearchTool::Activator flags)
400   \sa activators()
401 */
402 void QtxSearchTool::setActivators( const int flags )
403 {
404   myActivators = flags;
405   updateShortcuts();
406 }
407
408 /*!
409   \brief Get controls.
410   \return controls currently enabled (ORed QtxSearchTool::Controls flags)
411   \sa setControls()
412 */
413 int QtxSearchTool::controls() const
414 {
415   return myControls;
416 }
417
418 /*!
419   \brief Set controls.
420   \param ctrls controls to be displayed (ORed QtxSearchTool::Controls flags)
421   \sa controls()
422 */
423 void QtxSearchTool::setControls( const int ctrls )
424 {
425   if ( myControls == ctrls )
426     return;
427   myControls = ctrls;
428   updateControls();
429 }
430
431 /*!
432   \brief Get shortcuts.
433
434   Note: the standard bindings are not include to the resulting list.
435
436   \return list of shortcuts bindings currently set
437   \sa setShortcuts()
438 */
439 QList<QKeySequence> QtxSearchTool::shortcuts() const
440 {
441   QList<QKeySequence> ks;
442
443   ShortcutList::ConstIterator it;
444   int i;
445   for ( it = myShortcuts.begin(), i = 0; it != myShortcuts.end(); ++it, i++ )
446   {
447     if ( i > 2 ) ks.append( (*it)->key() );
448   }
449
450   return ks;
451 }
452
453 /*!
454   \brief Set shortcuts.
455   \param accel shortcut binding(s) to be used
456   \sa shortcuts()
457 */
458 void QtxSearchTool::setShortcuts( const QKeySequence& accel )
459 {
460   QList<QKeySequence> ks;
461   ks << accel;
462   setShortcuts( ks );
463 }
464
465 /*!
466   \brief Set shortcuts.
467   \param accel shortcut bindings to be used
468   \sa shortcuts()
469 */
470 void QtxSearchTool::setShortcuts( const QList<QKeySequence>& accels )
471 {
472   initShortcuts( accels );
473 }
474
475 /*!
476   \brief Add custom widget.
477   \param w custom widget to be added
478   \param id widget unique ID to be used (if < 0, automatically assigned)
479   \return widget unique ID
480   \sa customWidget(), customWidgetId()
481 */
482 int QtxSearchTool::addCustomWidget( QWidget* w, int id )
483 {
484   if ( !w ) return -1;
485
486   static int _wid = -1;
487
488   int wid = -1;
489   QMap<int, QWidget*>::ConstIterator it;
490   for ( it = myWidgets.begin(); it != myWidgets.end() && wid == -1; ++it )
491   {
492     if ( it.value() == w )
493       wid = it.key();
494   }
495
496   if ( wid != -1 )
497     return wid;
498
499   wid = id < 0 ? --_wid : id;
500
501   QVBoxLayout* vbox = qobject_cast<QVBoxLayout*>( layout() );
502   w->setParent( this );
503   vbox->addWidget( w );
504   myWidgets.insert( wid, w );
505
506   return wid;
507 }
508
509 /*!
510   \brief Get custom widget by ID.
511   \param id widget ID
512   \return custom widget or 0 if not found
513   \sa addCustomWidget(), customWidgetId()
514 */
515 QWidget* QtxSearchTool::customWidget( int id ) const
516 {
517   QWidget* w = 0;
518   if ( myWidgets.contains( id ) )
519     w = myWidgets[ id ];
520   return w;
521 }
522
523 /*!
524   \brief Get custom widget ID.
525   \param w custom widget
526   \return custom widget ID or -1 if widget does not belong to the search tool
527   \sa addCustomWidget(), customWidget()
528 */
529 int QtxSearchTool::customWidgetId( QWidget* w ) const
530 {
531   int wid = -1;
532   QMap<int, QWidget*>::ConstIterator it;
533   for ( it = myWidgets.begin(); it != myWidgets.end() && wid == -1; ++it )
534   {
535     if ( it.value() == w )
536       wid = it.key();
537   }
538   return wid;
539 }
540
541 /*!
542   \brief Check if auto-hide of the tool widget is enabled.
543
544   By default, the search tool widget is automatically hidden
545   after 10 seconds of idle (only if watched widget has input focus).
546
547   \return \c true if auto-hide option is set
548   \sa enableAutoHide()
549 */
550 bool QtxSearchTool::isAutoHideEnabled() const
551 {
552   return myAutoHideEnabled;
553 }
554
555 /*!
556   \brief Set/clear auto-hide option.
557
558   By default, the search tool widget is automatically hidden
559   after 10 seconds of idle (only if watched widget has input focus).
560
561   \param enable new option state
562   \sa isAutoHideEnabled()
563 */
564 void QtxSearchTool::enableAutoHide( bool enable )
565 {
566   if ( myAutoHideEnabled == enable ) return;
567
568   myAutoHideEnabled = enable;
569
570   if ( myAutoHideEnabled )
571   {
572     if ( isVisible() && !focused() )
573       myAutoHideTimer->start();
574   }
575   else
576   {
577     myAutoHideTimer->stop();
578   }
579 }
580
581 /*!
582   \brief Get 'case sensitive search' option value.
583
584   This method returns \c true if 'case sensitive search' control
585   is enabled and switched on.
586
587   \return \c true if case sensitive search is performed
588   \sa isRegExpSearch(), isSearchWrapped(), setControls()
589   \sa setCaseSensitive(), setRegExpSearch(), setSearchWrapped()
590 */
591 bool QtxSearchTool::isCaseSensitive() const
592 {
593   return myControls & Case && myIsCaseSens->isChecked();
594 }
595
596 /*!
597   \brief Get 'regular expression search' option value.
598
599   This method returns \c true if 'regular expression search' control
600   is enabled and switched on.
601
602   \return \c true if regular expression search is performed
603   \sa isCaseSensitive(), isSearchWrapped(), setControls()
604   \sa setCaseSensitive(), setRegExpSearch(), setSearchWrapped()
605 */
606 bool QtxSearchTool::isRegExpSearch() const
607 {
608   return myControls & RegExp && myIsRegExp->isChecked();
609 }
610
611 /*!
612   \brief Get 'search wrapping' option value.
613
614   This method returns \c true if 'wrap search' control
615   is enabled and switched on.
616
617   \return \c true if search wrapping is enabled
618   \sa isCaseSensitive(), isRegExpSearch(), setControls()
619   \sa setCaseSensitive(), setRegExpSearch(), setSearchWrapped()
620 */
621 bool QtxSearchTool::isSearchWrapped() const
622 {
623   return myControls & Wrap && myWrap->isChecked();
624 }
625
626 /*!
627   \brief Set 'case sensitive search' option value.
628   \param on new option state
629   \sa setRegExpSearch(), setSearchWrapped(), setControls()
630   \sa isCaseSensitive(), isRegExpSearch(), isSearchWrapped()
631 */
632 void QtxSearchTool::setCaseSensitive( bool on )
633 {
634   if ( myControls & Case )
635     myIsCaseSens->setChecked( on );
636 }
637
638 /*!
639   \brief Set 'regular expression search' option value.
640   \param on new option state
641   \sa setCaseSensitive(), setSearchWrapped(), setControls()
642   \sa isCaseSensitive(), isRegExpSearch(), isSearchWrapped()
643 */
644 void QtxSearchTool::setRegExpSearch( bool on )
645 {
646   if ( myControls & RegExp )
647     myIsRegExp->setChecked( on );
648 }
649
650 /*!
651   \brief Set 'search wrapping' option value.
652   \param on new option state
653   \sa setCaseSensitive(), setRegExpSearch(), setControls()
654   \sa isCaseSensitive(), isRegExpSearch(), isSearchWrapped()
655 */
656 void QtxSearchTool::setSearchWrapped( bool on )
657 {
658   if ( myControls & Wrap )
659     myWrap->setChecked( on );
660 }
661
662 /*!
663   \brief Customize event handling.
664   \param e event
665   \return \c true if event has been handled
666 */
667 bool QtxSearchTool::event( QEvent* e )
668 {
669   if ( e->type() == QEvent::EnabledChange )
670   {
671     updateShortcuts();
672   }
673   else if ( e->type() == QEvent::KeyPress )
674   {
675     QKeyEvent* ke = (QKeyEvent*)e;
676     if ( ke->key() == Qt::Key_Escape )
677       hide();
678   }
679   else if ( e->type() == QEvent::Hide && myWatched )
680   {
681     myWatched->setFocus();
682   }
683   return QFrame::event( e );
684 }
685
686 /*!
687   \brief Filter events from the watched widget.
688   \param o object
689   \param e event
690   \return \c true if further event processing should be stopped
691 */
692 bool QtxSearchTool::eventFilter( QObject* o, QEvent* e )
693 {
694   switch ( e->type() ) 
695   {
696   case QEvent::KeyPress:
697     if ( myWatched && o == myWatched )
698     {
699       QKeyEvent* ke = (QKeyEvent*)e;
700       int key = ke->key();
701       QString ttf = myData->text();
702       QString text = ke->text();
703       
704       if ( isVisible() )
705       {
706         switch ( key )
707         {
708         case Qt::Key_Escape:
709           hide();
710           return true;
711         case Qt::Key_Backspace:
712           ttf.chop( 1 );
713           break;
714         case Qt::Key_Return:
715         case Qt::Key_Enter:
716           findNext();
717           return true;
718         default:
719           if ( text.isEmpty() || !text[0].isPrint() )
720             return QFrame::eventFilter( o, e );
721           ttf += text;
722         }
723       }
724       else
725       {
726         if ( text.isEmpty() || ! isEnabled() || !text[0].isPrint() )
727           return QFrame::eventFilter( o, e );
728
729         if ( text.startsWith( '/' ) && myActivators & SlashKey )
730         {
731           myData->clear();
732           find();
733           return true;
734         }
735         else if ( !( myActivators & PrintKey ) )
736         {
737           return QFrame::eventFilter( o, e );
738         }
739         
740         ttf = text;
741         show();
742       }
743       myData->setText( ttf );
744       find( ttf );
745     }
746     break; // case QEvent::KeyPress
747   case QEvent::FocusIn:
748   case QEvent::FocusOut:
749     if ( focused() )
750     {
751       myAutoHideTimer->stop();
752     }
753     else if ( isVisible() && isAutoHideEnabled() )
754     {
755       myAutoHideTimer->start();
756     }
757     break;
758   default:
759     break;
760   }
761   return QFrame::eventFilter( o, e );
762 }
763
764 /*!
765   \brief Activate search tool.
766
767   Call this method to start new search.
768 */
769 void QtxSearchTool::find()
770 {
771   show();
772
773   myData->setFocus( Qt::ShortcutFocusReason );
774   myData->selectAll();
775   myAutoHideTimer->stop();
776 }
777
778 /*!
779   \brief Find next appropriate data.
780
781   Call this method to repeat the search in the forward direction.
782 */
783 void QtxSearchTool::findNext()
784 {
785   find( myData->text(), fNext );
786 }
787
788 /*!
789   \brief Find previous appropriate data.
790
791   Call this method to repeat the search in the backward direction.
792 */
793 void QtxSearchTool::findPrevious()
794 {
795   find( myData->text(), fPrevious );
796 }
797
798 /*!
799   \brief Find first appropriate data.
800
801   Call this method to find the very first appropriate data.
802 */
803 void QtxSearchTool::findFirst()
804 {
805   find( myData->text(), fFirst );
806 }
807
808 /*!
809   \brief Find last appropriate data.
810
811   Call this method to find the very last appropriate data.
812 */
813 void QtxSearchTool::findLast()
814 {
815   find( myData->text(), fLast );
816 }
817
818 /*!
819   \brief Perform search.
820   \internal
821   \param what text to be searched
822   \param where search flags
823 */
824 void QtxSearchTool::find( const QString& what, int where )
825 {
826   if ( !isVisible() )
827     show();
828
829   QPalette p = myData->palette();
830   p.setColor( QPalette::Active,
831               QPalette::Base,
832               QApplication::palette( myData ).color( QPalette::Active,
833                                                      QPalette::Base ) );
834
835   bool found = true;
836   if ( mySearcher && !what.isEmpty() )
837   {
838     switch( where )
839     {
840     case fNext:
841       found = mySearcher->findNext( what, this ); break;
842     case fPrevious:
843       found = mySearcher->findPrevious( what, this ); break;
844     case fFirst:
845       found = mySearcher->findFirst( what, this ); break;
846     case fLast:
847       found = mySearcher->findLast( what, this ); break;
848     case fAny:
849     default:
850       found = mySearcher->find( what, this ); break;
851     }
852   }
853
854   if ( !found )
855     p.setColor( QPalette::Active, QPalette::Base, QColor( highlightColor ) );
856
857   if ( !focused() && myAutoHideEnabled )
858     myAutoHideTimer->start();
859
860   myData->setPalette( p );
861 }
862
863 /*!
864   \brief Called when any search modifier is switched.
865   \internal
866 */
867 void QtxSearchTool::modifierSwitched()
868 {
869   find( myData->text() );
870 }
871
872 /*!
873   \brief Initialize the search tool widget.
874   \internal
875 */
876 void QtxSearchTool::init()
877 {
878   setFrameStyle( QFrame::StyledPanel | QFrame::Plain );
879   QVBoxLayout* vbox = new QVBoxLayout();
880   vbox->setSpacing( 0 );
881   vbox->setMargin( 5 );
882   setLayout( vbox );
883
884   myBtnWidget = new QWidget( this );
885   QHBoxLayout* myBtnWidget_layout = new QHBoxLayout( myBtnWidget );
886   myBtnWidget_layout->setSpacing( 0 );
887   myBtnWidget_layout->setMargin( 0 );
888   vbox->addWidget( myBtnWidget );
889
890   myModWidget = new QWidget( this );
891   QHBoxLayout* myModWidget_layout = new QHBoxLayout( myModWidget );
892   myModWidget_layout->setSpacing( 0 );
893   myModWidget_layout->setMargin( 0 );
894   vbox->addWidget( myModWidget );
895
896   myClose = new QToolButton( myBtnWidget );
897   myClose->setIcon( QIcon( close_xpm ) );
898   myClose->setAutoRaise( true );
899   myBtnWidget_layout->addWidget( wrapWidget( myBtnWidget, myClose ) );
900   connect( myClose, SIGNAL( clicked() ), this, SLOT( hide() ) );
901
902   myData = new QLineEdit( myBtnWidget );
903   myData->setMinimumWidth( 50 );
904   myBtnWidget_layout->addWidget( wrapWidget( myBtnWidget, myData ), 1 );
905   connect( myData, SIGNAL( textChanged( const QString& ) ), this, SLOT( find( const QString& ) ) );
906   connect( myData, SIGNAL( returnPressed() ), this, SLOT( findNext() ) );
907   myData->installEventFilter( this );
908
909   myToFirst = new QToolButton( myBtnWidget );
910   myToFirst->setIcon( QIcon( first_xpm ) );
911   myToFirst->setAutoRaise( true );
912   myBtnWidget_layout->addWidget( wrapWidget( myBtnWidget, myToFirst ), 0 );
913   connect( myToFirst, SIGNAL( clicked() ), this, SLOT( findFirst() ) );
914   myToFirst->installEventFilter( this );
915
916   myPrev = new QToolButton( myBtnWidget );
917   myPrev->setIcon( QIcon( prev_xpm ) );
918   myPrev->setAutoRaise( true );
919   myBtnWidget_layout->addWidget( wrapWidget( myBtnWidget, myPrev ), 0 );
920   connect( myPrev, SIGNAL( clicked() ), this, SLOT( findPrevious() ) );
921   myPrev->installEventFilter( this );
922
923   myNext = new QToolButton( myBtnWidget );
924   myNext->setIcon( QIcon( next_xpm ) );
925   myNext->setAutoRaise( true );
926   myBtnWidget_layout->addWidget( wrapWidget( myBtnWidget, myNext ), 0 );
927   connect( myNext, SIGNAL( clicked() ), this, SLOT( findNext() ) );
928   myNext->installEventFilter( this );
929
930   myToLast = new QToolButton( myBtnWidget );
931   myToLast->setIcon( QIcon( last_xpm ) );
932   myToLast->setAutoRaise( true );
933   myBtnWidget_layout->addWidget( wrapWidget( myBtnWidget, myToLast ), 0 );
934   connect( myToLast, SIGNAL( clicked() ), this, SLOT( findLast() ) );
935   myToLast->installEventFilter( this );
936
937   myIsCaseSens = new QCheckBox( tr( "Case sensitive" ), myModWidget );
938   myModWidget_layout->addWidget( wrapWidget( myBtnWidget, myIsCaseSens ) );
939   connect( myIsCaseSens, SIGNAL( stateChanged( int ) ), this, SLOT( modifierSwitched() ) );
940   myIsCaseSens->installEventFilter( this );
941
942   myIsRegExp = new QCheckBox( tr( "Regular expression" ), myModWidget );
943   myModWidget_layout->addWidget( wrapWidget( myBtnWidget, myIsRegExp ) );
944   connect( myIsRegExp, SIGNAL( stateChanged( int ) ), this, SLOT( modifierSwitched() ) );
945   myIsRegExp->installEventFilter( this );
946
947   myWrap = new QCheckBox( tr( "Wrap search" ), myModWidget );
948   myModWidget_layout->addWidget( wrapWidget( myBtnWidget, myWrap ) );
949   connect( myWrap, SIGNAL( stateChanged( int ) ), this, SLOT( modifierSwitched() ) );
950   myWrap->installEventFilter( this );
951
952   setWatchedWidget( myWatched );
953
954   setShortcuts( QKeySequence( "Ctrl+S" ) );
955   setActivators( Any );
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 }