Salome HOME
Updated copyright comment
[modules/gui.git] / src / Qtx / QtxMultiAction.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:      QtxMultiAction.cxx
21 // Author:    Sergey TELKOV
22 //
23 #include "QtxMultiAction.h"
24
25 #include <QMenu>
26 #include <QLayout>
27 #include <QToolBar>
28 #include <QPainter>
29 #include <QHelpEvent>
30 #include <QToolButton>
31 #include <QApplication>
32 #include <QStyleOptionButton>
33
34 /*!
35   \class QtxMultiAction::Filter
36   \brief Waches for the buttons in the popup menu 
37   to update the tool buttons state.
38   \internal
39 */
40
41 class QtxMultiAction::Filter : public QObject
42 {
43 public:
44   //! \brief Constructor
45   Filter( QObject* parent ) : QObject( parent ) {}
46   //! \brief Destructor
47   ~Filter() {}
48   //! \brief Process events from the child tool buttons
49   bool eventFilter( QObject* o, QEvent* e )
50   {
51     if ( e->type() == QEvent::Leave ) {
52       QToolButton* tb = qobject_cast<QToolButton*>( o );
53       if ( tb )
54         tb->setDown( false );
55     }
56     return QObject::eventFilter( o, e );
57   }
58 };
59
60 /*!
61   \class QtxMultiAction::Menu
62   \brief Custom menu to be used with the toolbuttons as drop down list.
63   \internal
64 */
65
66 class QtxMultiAction::Menu : public QMenu
67 {
68 public:
69   //! \brief Constructor
70   Menu( QWidget* parent = 0 ) : QMenu( parent ) {}
71   //! \brief Destructor
72   ~Menu() {};
73
74 protected:
75   //! \brief Paint the button
76   virtual bool event( QEvent* e )
77   {
78     bool res = false;
79     switch ( e->type() )
80     {
81     case QEvent::ToolTip:
82     case QEvent::WhatsThis:
83     case QEvent::QueryWhatsThis:
84       {
85         QHelpEvent* help = static_cast<QHelpEvent*>( e );
86         QWidget* w = QApplication::widgetAt( help->globalPos() );
87         if ( w && Qtx::isParent( w, this ) )
88         {
89           QHelpEvent he( help->type(), w->mapFromGlobal( help->globalPos() ), help->globalPos() );
90           QApplication::sendEvent( w, &he );
91           res = true;
92         }
93       }
94       break;
95     case QEvent::StatusTip:
96     case QEvent::WhatsThisClicked:
97       if ( parentWidget() )
98       {
99         QApplication::sendEvent( parentWidget(), e );
100         res = true;
101       }
102       break;
103     default:
104       res = QMenu::event( e );
105       break;
106     }
107     return res;
108   }
109 };
110
111 /*!
112   \class QtxMultiAction::Button
113   \brief Custom button to be used in the toolbar.
114   \internal
115 */
116
117 class QtxMultiAction::Button : public QToolButton
118 {
119 public:
120   //! \brief Constructor
121   Button( QWidget* parent = 0 ) : QToolButton( parent ) {}
122   //! \brief Destructor
123   ~Button() {};
124
125 protected:
126   //! \brief Paint the button
127   virtual void paintEvent( QPaintEvent* e )
128   {
129     QToolButton::paintEvent( e );
130
131     int s = 10;
132     int m = -2;
133     int w = width();
134     int h = height();
135
136     QStyleOptionButton opt;
137     opt.initFrom( this );
138     QRect rect = opt.rect;
139     int x = rect.x(), y = rect.y();
140     if ( isDown() )
141       opt.rect = QRect( x + w - s - m, y + h - s - m, s, s );
142     else
143       opt.rect = QRect( x + w - s - m - 1, y + h - s - m - 1, s, s );
144
145     QPainter p( this );
146     style()->drawPrimitive( QStyle::PE_IndicatorSpinDown, &opt, &p );
147   }
148 };
149
150 /*!
151   \class QtxMultiAction
152   \brief The class QtxMultiAction implements modifiable action.
153
154   The QtxMultiAction class provides a possibility to assign a set of actions 
155   (insertAction() function). The action can be used in the toolbar (and even
156   in the menu) to show drop-down menu with the list of the assigned actions.
157
158   Initially the first action from the list becomes current and it is activated
159   when the tool button is clicked by the user. If user presses and holds the mouse
160   button at the tool button, it shows the popup menu with all the assigned actions.
161   When the user selects any action from the popup menu, it becames current.
162 */
163
164 /*!
165   \brief Constructor.
166   \param parent parent object
167 */
168 QtxMultiAction::QtxMultiAction( QObject* parent )
169 : QtxActionSet( parent ),
170   myCurrent( 0 )
171 {
172   setVisible( true );
173   setMenu( new QMenu( 0 ) );
174
175   connect( this, SIGNAL( triggered( QAction* ) ), this, SLOT( onTriggered( QAction* ) ) );
176 }
177
178 /*!
179   \brief Constructor.
180   \param txt action menu text
181   \param parent parent object
182 */
183 QtxMultiAction::QtxMultiAction( const QString& txt, QObject* parent )
184 : QtxActionSet( parent ),
185   myCurrent( 0 )
186 {
187   setText( txt );
188   setVisible( true );
189   setMenu( new QMenu( 0 ) );
190
191   connect( this, SIGNAL( triggered( QAction* ) ), this, SLOT( onTriggered( QAction* ) ) );
192 }
193
194 /*!
195   \brief Constructor.
196   \param ico action menu icon
197   \param txt action menu text
198   \param parent parent object
199 */
200 QtxMultiAction::QtxMultiAction( const QIcon& ico, const QString& txt, QObject* parent )
201 : QtxActionSet( parent ),
202   myCurrent( 0 )
203 {
204   setIcon( ico );
205   setText( txt );
206   setVisible( true );
207   setMenu( new QMenu( 0 ) );
208
209   connect( this, SIGNAL( triggered( QAction* ) ), this, SLOT( onTriggered( QAction* ) ) );
210 }
211
212 /*!
213   \brief Destructor
214 */
215 QtxMultiAction::~QtxMultiAction()
216 {
217   // to avoid memory leak
218   if( QMenu* aMenu = menu() )
219   {
220     delete aMenu;
221     aMenu = 0;
222   }
223 }
224
225 /*!
226   \brief Set current action.
227   \param a action to be set current
228 */
229 void QtxMultiAction::setActiveAction( QAction* a )
230 {
231   if ( a && actions().contains( a ) && a != myCurrent && a->isEnabled() )
232   {
233     myCurrent = a;
234     updateAction();
235   }
236 }
237
238 /*!
239   \brief Get current action.
240   \return current action (0 if there is no active action)
241 */
242 QAction* QtxMultiAction::activeAction() const
243 {
244   return myCurrent;
245 }
246
247 /*!
248   \brief Called when the user activates the current action 
249   (for example by clicking the tool button).
250   \param on (not used)
251 */
252 void QtxMultiAction::onClicked( bool /*on*/ )
253 {
254   if ( myCurrent )
255     myCurrent->activate( QAction::Trigger );
256 }
257
258 /*!
259   \brief Called when user activates any action from the
260   dropdown menu.
261   \param a action being activated
262 */
263 void QtxMultiAction::onTriggered( QAction* a )
264 {
265   if ( !a )
266     return;
267
268   QList<QWidget*> lst = createdWidgets();
269   for ( QList<QWidget*>::iterator it = lst.begin(); it != lst.end(); ++it )
270   {
271     QToolButton* tb = ::qobject_cast<QToolButton*>( *it );
272     if ( tb && tb->menu() )
273       tb->menu()->hide();
274   }
275
276   if ( myCurrent != a )
277   {
278     myCurrent = a;
279     updateAction();
280   }
281 }
282
283 /*!
284   \brief Update action.
285 */
286 void QtxMultiAction::updateAction()
287 {
288   QtxActionSet::updateAction();
289
290   QList<QWidget*> lst = createdWidgets();
291   for ( QList<QWidget*>::iterator it = lst.begin(); it != lst.end(); ++it )
292     updateButton( ::qobject_cast<QToolButton*>( *it ) );
293 }
294
295 /*!
296   \brief Update child (popup menu) action.
297   \param w widget menu widget
298 */
299 void QtxMultiAction::updateAction( QWidget* w )
300 {
301   if ( !w )
302     return;
303
304   if ( w->inherits( "QMenu" ) )
305   {
306     QtxActionSet::updateAction( menu() );
307
308     QApplication::instance()->removeEventFilter( this );
309
310     menu()->removeAction( this );
311
312     QApplication::instance()->installEventFilter( this );
313   }
314 }
315
316 /*!
317   \brief Check if the action itself should be invisible
318   (only child action are shown)
319   \return \c true if the action itself should be visible
320 */
321 bool QtxMultiAction::isEmptyAction() const
322 {
323   return false;
324 }
325
326 /*!
327   \brief Create widget to be displayed in the toolbar.
328   \param parent parent widget (should be toolbar)
329   \return toolbar button
330 */
331 QWidget* QtxMultiAction::createWidget( QWidget* parent )
332 {
333   QToolBar* tb = ::qobject_cast<QToolBar*>( parent );
334   if ( !tb )
335     return 0;
336
337   QToolButton* w = new QToolButton( tb );
338   w->setMenu( new Menu( w ) );
339   w->setMouseTracking( true );
340   w->setFocusPolicy( Qt::NoFocus );
341   w->setIconSize( tb->iconSize() );
342   w->setToolButtonStyle( tb->toolButtonStyle() );
343
344   connect( w, SIGNAL( clicked( bool ) ), this, SLOT( onClicked( bool ) ) );
345   connect( tb, SIGNAL( iconSizeChanged( const QSize& ) ), w, SLOT( setIconSize( QSize ) ) );
346   connect( tb, SIGNAL( toolButtonStyleChanged( Qt::ToolButtonStyle ) ),
347            w, SLOT( setToolButtonStyle( Qt::ToolButtonStyle ) ) );
348
349   updateButton( w );
350   return w;
351 }
352
353 /*!
354   \brief Called when the child action is added to this action.
355   \param a child action being added
356 */
357 void QtxMultiAction::actionAdded( QAction* a )
358 {
359   connect( a, SIGNAL( changed() ), this, SLOT( onActionChanged() ) );
360   onActionChanged();
361 }
362
363 /*!
364   \brief Called when the child action is removed from this action.
365   \param a child action being removed
366 */
367 void QtxMultiAction::actionRemoved( QAction* a )
368 {
369   disconnect( a, SIGNAL( changed() ), this, SLOT( onActionChanged() ) );
370
371   if ( myCurrent != a )
372     return;
373
374   myCurrent = 0;
375
376   onActionChanged();
377
378   updateAction();
379 }
380
381 /*!
382   \brief Update toolbar button.
383   \param btn toolbar button
384 */
385 void QtxMultiAction::updateButton( QToolButton* btn )
386 {
387   if ( !btn )
388     return;
389
390   btn->setIcon( myCurrent ? myCurrent->icon() : QIcon() );
391   btn->setText( myCurrent ? myCurrent->text() : QString() );
392   btn->setToolTip( myCurrent ? myCurrent->toolTip() : QString() );
393   btn->setStatusTip( myCurrent ? myCurrent->statusTip() : QString() );
394
395   QMenu* pm = btn->menu();
396   if ( !pm )
397     return;
398
399   pm->clear();
400   for ( int i = 0; pm->layout() && i < pm->layout()->count(); i++ )
401     delete pm->layout()->widget();
402
403   delete pm->layout();
404
405   QVBoxLayout* vbox = new QVBoxLayout( pm );
406   vbox->setMargin( 1 );
407   vbox->setSpacing( 0 );
408   Filter* filter = new Filter( vbox );
409   QList<QAction*> actList = actions();
410   for ( QList<QAction*>::iterator itr = actList.begin(); itr != actList.end(); ++itr )
411   {
412     QToolButton* b = new QToolButton( pm );
413     b->setDefaultAction( *itr );
414     b->setToolTip( (*itr)->toolTip() );
415     b->setStatusTip( (*itr)->statusTip() );
416     b->setAutoRaise( true );
417     b->setIconSize( btn->iconSize() );
418     b->setToolButtonStyle( btn->toolButtonStyle() );
419     b->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
420     b->installEventFilter( filter );
421     vbox->addWidget( b );
422   }
423 }
424
425 /*!
426   \brief Called when any child action is enabled/disabled.
427   
428   If the current action is disabled, the multi-action switches
429   to first found enabled. If all child actions are disabled, the
430   action itself is also disabled.
431 */
432 void QtxMultiAction::onActionChanged()
433 {
434   if ( myCurrent && myCurrent->isEnabled() )
435     return;
436
437   QList<QAction*> alist = actions();
438   QAction* a = 0;
439   for ( QList<QAction*>::ConstIterator it = alist.begin(); it != alist.end() && !a; ++it ) {
440     if ( (*it)->isEnabled() )
441       a = *it;
442   }
443
444   if ( a )
445     myCurrent = a;
446   else
447     myCurrent = alist.isEmpty() ? 0 : alist.first();
448
449   setEnabled( myCurrent && myCurrent->isEnabled() );
450   updateAction();
451 }