Salome HOME
updated copyright message
[modules/gui.git] / src / Qtx / QtxToolButton.cxx
1 // Copyright (C) 2007-2023  CEA/DEN, EDF R&D, 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 #include "QtxToolButton.h"
21 #include "QtxMenu.h"
22
23 #include <QKeyEvent>
24 #include <QWheelEvent>
25
26 namespace
27 {
28   bool isSeparator( QAction* a )
29   {
30     return a->property( "separator" ).toBool();
31   }
32   void setSeparator( QAction* a )
33   {
34     a->setProperty( "separator", true );
35   }
36 }
37
38 /*!
39   \class QtxToolButton
40   \brief Drop-down tool button that behaves like a drop-down combo-box.
41
42   In contrast to the standard combo box, QtxToolButton can show drop-down
43   menu containing groups of items where each group has a separate title.
44 */
45
46 /*!
47   \brief Constructor.
48   \param parent Parent widget.
49 */
50 QtxToolButton::QtxToolButton( QWidget* parent )
51   : QToolButton( parent )
52 {
53   setMenu( new QtxMenu( this ) );
54   setPopupMode( InstantPopup );
55   setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
56   connect( this, SIGNAL( triggered( QAction* ) ), this, SLOT( actionTriggered( QAction* ) ) );
57 }
58
59 /*!
60   \brief Destructor.
61 */
62 QtxToolButton::~QtxToolButton()
63 {
64 }
65
66 /*!
67   \brief Add an item with the given \a text, and containing the specified \a userData.
68   The item is appended to the list of existing items.
69   \param text Item's text.
70   \param userData Item's data.
71   \return Index of just added item.
72 */
73 int QtxToolButton::addItem( const QString& text, const QVariant& userData )
74 {
75   QAction* action = menu()->addAction( text );
76   action->setData( userData );
77   bool currentChanged = false;
78   if ( !menu()->defaultAction() ) {
79     menu()->setDefaultAction( action );
80     currentChanged = true;
81   }
82   internalUpdate();
83   if ( currentChanged ) emitCurrentChanged( false, true );
84   return count()-1;
85 }
86
87 /*!
88   \brief Add an item with the given \a icon and \a text, and containing the specified
89   \a userData. The item is appended to the list of existing items.
90   \param text Item's text.
91   \param icon Item's icon.
92   \param userData Item's data.
93   \return Index of just added item.
94 */
95 int QtxToolButton::addItem( const QIcon& icon, const QString& text, const QVariant& userData )
96 {
97   QAction* action = menu()->addAction( icon, text );
98   action->setData( userData );
99   bool currentChanged = false;
100   if ( !menu()->defaultAction() ) {
101     menu()->setDefaultAction( action );
102     currentChanged = true;
103   }
104   internalUpdate();
105   if ( currentChanged ) emitCurrentChanged( false, true );
106   return count()-1;
107 }
108
109 /*!
110   \brief Add items with given \a texts. Each item is appended to the list of existing
111   items in turn.
112   \param texts Items being added.
113 */
114 void QtxToolButton::addItems( const QStringList& texts )
115 {
116   Q_FOREACH( QString text, texts )
117     menu()->addAction( text );
118   bool currentChanged = false;
119   if ( !menu()->defaultAction() && menu()->actions().count() > 0 ) {
120     menu()->setDefaultAction( menu()->actions()[0] );
121     currentChanged = true;
122   }
123   if ( currentChanged ) emitCurrentChanged( false, true );
124   internalUpdate();
125 }
126
127 /*!
128   \brief Add separator to the end of the items list.
129   \param text Separator's text.
130   \return Index of just added item.
131 */
132 int QtxToolButton::addSeparator( const QString& text )
133 {
134   (qobject_cast<QtxMenu*>(menu()))->addGroup( text );
135   setSeparator( actionAt( count()-1 ) );
136   return count()-1;
137 }
138
139 /*!
140   \brief Add separator to the end of the items list.
141   \param icon Separator's icon.
142   \param text Separator's text.
143   \return Index of just added item.
144 */
145 int QtxToolButton::addSeparator( const QIcon& icon, const QString& text )
146 {
147   (qobject_cast<QtxMenu*>(menu()))->addGroup( icon, text );
148   setSeparator( actionAt( count()-1 ) );
149   return count()-1;
150 }
151
152 /*!
153   \brief Remove item with given \a index.
154   \param index Index of item to be removed.
155 */
156 void QtxToolButton::removeItem( int index )
157 {
158   QAction* action = actionAt( index );
159   if ( !action ) return;
160   QAction* current = menu()->defaultAction();
161   menu()->removeAction( action );
162   bool currentChanged = false;
163   if ( action == current ) {
164     for ( int i = index; i < count(); i++ ) {
165       QAction* a = actionAt( i );
166       if ( a && !isSeparator( a ) ) {
167         menu()->setDefaultAction( a );
168         currentChanged = true;
169         break;
170       }
171     }
172     for ( int i = index-1; i >= 0; i-- ) {
173       QAction* a = actionAt( i );
174       if ( a && !isSeparator( a ) ) {
175         menu()->setDefaultAction( a );
176         currentChanged = true;
177         break;
178       }
179     }
180   }
181   internalUpdate();
182   if ( currentChanged ) emitCurrentChanged( false, true );
183 }
184
185 /*!
186   \brief Get the number of items.
187   \return Number of items.
188 */
189 int QtxToolButton::count() const
190 {
191   return menu()->actions().count();
192 }
193
194 /*!
195   \brief Get data of the current item.
196   \return Current item's data (invalid QVariant if list of items is empty
197   or current item doesn't have data).
198 */
199 QVariant QtxToolButton::currentData() const
200 {
201   QAction* action = menu()->defaultAction();
202   return action == 0 ? QVariant() : action->data();
203 }
204
205 /*!
206   \brief Get index of current item.
207   \return Current item's index; -1 if list of items is empty or if there's no
208   current item.
209 */
210 int QtxToolButton::currentIndex() const
211 {
212   QAction* action = menu()->defaultAction();
213   return action == 0 ? -1 : menu()->actions().indexOf( action );
214 }
215
216 /*!
217   \brief Get text of current item.
218   \return Current item's text; null sting if list of items is empty or
219   if there's no current item.
220 */
221 QString QtxToolButton::currentText() const
222 {
223   QAction* action = menu()->defaultAction();
224   return action == 0 ? QString() : action->text();
225 }
226
227 /*!
228   \brief Get custom data of the item at given \a index.
229   \param index Item's index.
230   \return Item's data (invalid QVariant if index is out of range).
231 */
232 QVariant QtxToolButton::itemData( int index ) const
233 {
234   QAction* action = actionAt( index );
235   return action == 0 ? QVariant() : action->data();
236 }
237
238 /*!
239   \brief Get icon of the item at given \a index.
240   \param index Item's index.
241   \return Item's icon.
242 */
243 QIcon QtxToolButton::itemIcon( int index ) const
244 {
245   QAction* action = actionAt( index );
246   return action == 0 ? QIcon() : action->icon();
247 }
248
249 /*!
250   \brief Get text of the item at given \a index.
251   \param index Item's index.
252   \return Item's text.
253 */
254 QString QtxToolButton::itemText( int index ) const
255 {
256   QAction* action = actionAt( index );
257   return action == 0 ? QString() : action->text();
258 }
259
260 /*!
261   \brief Set custom data of the item at given \a index.
262   \param index Item's index.
263   \param value Item's data.
264 */
265 void QtxToolButton::setItemData( int index, const QVariant& value )
266 {
267   QAction* action = actionAt( index );
268   if ( action ) action->setData( value );
269 }
270
271 /*!
272   \brief Set icon of the item at given \a index.
273   \param index Item's index.
274   \param icon Item's icon.
275 */
276 void QtxToolButton::setItemIcon( int index, const QIcon& icon )
277 {
278   QAction* action = actionAt( index );
279   if ( action ) action->setIcon( icon );
280   internalUpdate();
281 }
282
283 /*!
284   \brief Set text of the item at given \a index.
285   \param index Item's index.
286   \param text Item's text.
287 */
288 void QtxToolButton::setItemText( int index, const QString& text )
289 {
290   QAction* action = actionAt( index );
291   bool currentChanged = false;
292   if ( action ) {
293     currentChanged = menu()->defaultAction() == action && action->text() != text;
294     action->setText( text );
295   }
296   internalUpdate();
297   if ( currentChanged )
298     emit currentTextChanged( text );
299 }
300
301 /*!
302   \brief Search item with given \a text.
303   \param Item's text.
304   \return Item's index; -1 if item is not found.
305 */
306 int QtxToolButton::findText( const QString& text )
307 {
308   int index = -1;
309   for ( int i = 0; i < count() && index == -1; i++ ) {
310     QAction* action = actionAt( i );
311     if ( isSeparator( action ) ) continue;
312     if ( action->text() == text ) index = i;
313   }
314   return index;
315 }
316
317 /*!
318   \brief Clear widget.
319 */
320 void QtxToolButton::clear()
321 {
322   QAction* action = menu()->defaultAction();
323   menu()->clear();
324   internalUpdate();
325   if ( action ) emitCurrentChanged( false, true );
326 }
327
328 /*!
329   \brief Set current item by given \a index.
330   \param index Item's index.
331 */
332 void QtxToolButton::setCurrentIndex( int index )
333 {
334   bool currentChanged = false;
335   if ( index == -1 ) {
336     currentChanged = currentIndex() != -1;
337     menu()->setDefaultAction( 0 );
338   }
339   else if ( index >= count() )
340     return;
341   else {
342     QAction* action = actionAt( index );
343     if ( !isSeparator( action ) ) {
344       currentChanged = currentIndex() != index;
345       menu()->setDefaultAction( action );
346     }
347   }
348   internalUpdate();
349   if ( currentChanged ) emitCurrentChanged( false, true );
350 }
351
352 /*!
353   \brief Set current item by given \a text.
354   \param index Item's index.
355 */
356 void QtxToolButton::setCurrentText( const QString& text )
357 {
358   int index = findText( text );
359   if ( index != -1 )
360     setCurrentIndex( index );
361 }
362
363 /*!
364   \brief Reimplemented from QToolButton::keyPressEvent().
365   Process key press event.
366   \param e Key press event.
367 */
368 void QtxToolButton::keyPressEvent( QKeyEvent* e )
369 {
370   Move move = NoMove;
371   switch ( e->key() ) {
372   case Qt::Key_Up:
373   case Qt::Key_PageUp:
374     move = ( e->modifiers() & Qt::ControlModifier ) ? MoveFirst : MoveUp;
375     break;
376   case Qt::Key_Down:
377     if ( e->modifiers() & Qt::AltModifier ) {
378       showMenu();
379       return;
380     }
381     // fall through!
382   case Qt::Key_PageDown:
383     move = ( e->modifiers() & Qt::ControlModifier ) ? MoveLast : MoveDown;
384     break;
385   case Qt::Key_Home:
386     move = MoveFirst;
387     break;
388   case Qt::Key_End:
389     move = MoveLast;
390     break;
391   case Qt::Key_F4:
392     if ( !e->modifiers() ) {
393       showMenu();
394       return;
395     }
396     break;
397   case Qt::Key_Space:
398     showMenu();
399     return;
400   case Qt::Key_Enter:
401   case Qt::Key_Return:
402   case Qt::Key_Escape:
403     e->ignore();
404     break;
405   case Qt::Key_Select:
406     showMenu();
407     return;
408   case Qt::Key_Left:
409     move = ( e->modifiers() & Qt::ControlModifier ) ? MoveFirst : MoveUp;
410     break;
411   case Qt::Key_Right:
412     move = ( e->modifiers() & Qt::ControlModifier ) ? MoveLast : MoveDown;
413     break;
414   default:
415     e->ignore();
416     break;
417   }
418   moveIndex( move );
419 }
420
421 /*!
422   \brief Reimplemented from QToolButton::wheelEvent().
423   Process mouse wheel event.
424   \param e Mouse wheel event.
425 */
426 void QtxToolButton::wheelEvent( QWheelEvent* e )
427 {
428   Move move = NoMove;
429   if ( e->delta() > 0 )
430     move = ( e->modifiers() & Qt::ControlModifier ) ? MoveFirst : MoveUp;
431   else if ( e->delta() < 0 )
432     move = ( e->modifiers() & Qt::ControlModifier ) ? MoveLast : MoveDown;
433   moveIndex( move );
434 }
435
436 /*!
437   \brief Called when menu action is triggered.
438   \internal
439 */
440 void QtxToolButton::actionTriggered( QAction* action )
441 {
442   if ( action && !isSeparator( action ) ) {
443     int index = currentIndex();
444     menu()->setDefaultAction( action );
445     internalUpdate();
446     int newIndex = currentIndex();
447     emitCurrentChanged( true, index != newIndex );
448   }
449 }
450
451 /*!
452   \brief Update content of the widget.
453   \internal
454 */
455 void QtxToolButton::internalUpdate()
456 {
457   QAction* action = menu()->defaultAction();
458   setText( action == 0 ? "" : action->text() );
459   setIcon( action == 0 ? QIcon() : action->icon() );
460 }
461
462 /*!
463   \brief Get menu action at given index.
464   \internal
465 */
466 QAction* QtxToolButton::actionAt( int index ) const
467 {
468   return ( index >=0 && index < count() ) ? menu()->actions()[index] : 0;
469 }
470
471 /*!
472   \brief Move current index.
473   \internal
474 */
475 void QtxToolButton::moveIndex( Move move )
476 {
477   int index = currentIndex();
478   int newIndex = index;
479   switch ( move ) {
480   case MoveUp:
481   case MoveFirst:
482     for ( int i = index-1; i >= 0; i-- ) {
483       QAction* a = actionAt( i );
484       if ( a && !isSeparator( a ) ) {
485         newIndex = i;
486         if ( move == MoveUp )
487           break;
488       }
489     }
490     break;
491   case MoveDown:
492   case MoveLast:
493     for ( int i = index+1; i < count(); i++ ) {
494       QAction* a = actionAt( i );
495       if ( a && !isSeparator( a ) ) {
496         newIndex = i;
497         if ( move == MoveDown )
498           break;
499       }
500     }
501     break;
502   default:
503     break;
504   }
505   if ( newIndex != index ) {
506     menu()->setDefaultAction( actionAt( newIndex ) );
507     internalUpdate();
508     emitCurrentChanged( true, true );
509   }
510 }
511
512 /*!
513   \brief Emit `currentChanged()` signal.
514   \internal
515 */
516 void QtxToolButton::emitCurrentChanged( bool activate, bool changed )
517 {
518   QAction* action = menu()->defaultAction();
519   int index = action == 0 ? -1 : menu()->actions().indexOf( action );
520   QString text = action == 0 ? QString() : action->text();
521   if ( activate ) {
522     emit activated( index );
523     emit activated( text );
524   }
525   if ( changed ) {
526     emit currentIndexChanged( index );
527     emit currentIndexChanged( text );
528     emit currentTextChanged( text );
529   }
530 }