Salome HOME
box #29458 Merge branch 'CR29458'
[modules/gui.git] / src / LightApp / LightApp_ModuleAction.cxx
1 // Copyright (C) 2007-2022  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 // File   : LightApp_ModuleAction.cxx
21 // Author : Sergey TELKOV, Vadim SANDLER
22 //
23 #include "LightApp_ModuleAction.h"
24
25 #include <QtxActionSet.h>
26 #include <QtxComboBox.h>
27 #include <QtxResourceMgr.h>
28 #include <QVBoxLayout>
29 #include <QApplication>
30 #include <QEvent>
31 #include <QMenu>
32 #include <QSignalMapper>
33
34 /*!
35   \class LightApp_ModuleAction::ActionSet
36   \brief Internal class to represent list of modules buttons.
37   \internal
38 */
39
40 class LightApp_ModuleAction::ActionSet : public QtxActionSet
41 {
42 public:
43   ActionSet( QObject* );
44   QAction* moduleAction( const QString& ) const;
45   int      moduleId( const QString& ) const;
46   int      moduleId( QAction* ) const;
47   void     setVisible( bool );
48 };
49
50 /*!
51   \brief Constructor.
52   \internal
53   \param parent parent object
54 */
55 LightApp_ModuleAction::ActionSet::ActionSet( QObject* parent )
56 : QtxActionSet( parent ) 
57 {
58 }
59
60 /*!
61   \brief Get action corresponding to the specified module.
62   \internal
63   \param name module name
64   \return module action or 0 if \a name is invalid
65 */
66 QAction* LightApp_ModuleAction::ActionSet::moduleAction( const QString& name ) const
67 {
68   QAction* a = 0;
69
70   QList<QAction*> alist = actions();
71   for ( QList<QAction*>::const_iterator it = alist.begin(); it != alist.end() && !a; ++it )
72   {
73     if ( (*it)->text() == name )
74       a = *it;
75   }
76
77   return a;
78 }
79
80 /*!
81   \brief Get module action identifier.
82   \internal
83   \param name module name
84   \return module action ID or -1 if \a name is invalid
85 */
86 int LightApp_ModuleAction::ActionSet::moduleId( const QString& name ) const
87 {
88   int id = -1;
89
90   QList<QAction*> alist = actions();
91   for ( QList<QAction*>::const_iterator it = alist.begin(); it != alist.end() && id == -1; ++it )
92   {
93     if ( (*it)->text() == name )
94       id = actionId( *it );
95   }
96
97   return id;
98 }
99
100 /*!
101   \brief Get module action identifier.
102   \internal
103   \param a module action
104   \return module action ID or -1 if \a a is null or invalid
105 */
106 int LightApp_ModuleAction::ActionSet::moduleId( QAction* a ) const
107 {
108   return actionId( a );
109 }
110
111 /*!
112   \brief Show/hide modules actions.
113   \internal
114   \param on new visibility state
115 */
116 void LightApp_ModuleAction::ActionSet::setVisible( bool on )
117 {
118   QList<QAction*> alist = actions();
119   for ( QList<QAction*>::const_iterator it = alist.begin(); it != alist.end(); ++it )
120     (*it)->setVisible( on );
121
122   QtxActionSet::setVisible( on );
123 }
124
125 /*!
126   \class LightApp_ModuleAction::ComboAction
127   \brief Internal class to represent combo box with the list of modules in the toolbar.
128   \internal
129 */
130
131 /*!
132   \brief Constructor.
133   \internal
134   \param parent parent object
135 */
136 LightApp_ModuleAction::ComboAction::ComboAction( QObject* parent )
137 : QtxAction( parent )
138 {
139 }
140
141 /*!
142   \brief Get list of associated widgets.
143   \internal
144   \return list of created widgets (QtxComboBox)
145 */
146 QList<QtxComboBox*> LightApp_ModuleAction::ComboAction::widgets() const
147 {
148   QList<QtxComboBox*> lst;
149
150   QList<QWidget*> wlist = createdWidgets();
151   for ( QList<QWidget*>::const_iterator wit = wlist.begin(); wit != wlist.end(); ++wit )
152     lst += qobject_cast<QtxComboBox*>(*wit);
153
154   return lst;
155 }
156
157 /*!
158   \brief Create combo box widget by request from the toolbar.
159   \internal
160   \param parent parent widget (should be QToolBar or its successor)
161   \return new custom widget, containing combo box
162 */
163 QWidget* LightApp_ModuleAction::ComboAction::createWidget( QWidget* parent )
164 {
165   if ( !parent->inherits( "QToolBar" ) )
166     return 0;
167
168   QtxComboBox* cb = new QtxComboBox( parent );
169   cb->setSizeAdjustPolicy( QComboBox::AdjustToContents );
170   cb->setFocusPolicy( Qt::NoFocus );
171   connect( cb, SIGNAL( activatedId( int ) ), this, SIGNAL( activatedId( int ) ) );
172   return cb;
173 }
174
175 /*!
176   \fn void LightApp_ModuleAction::ComboAction::activatedId( int id );
177   \internal
178   \brief Emitted when the combo box item is activated
179   \param item identifier
180 */
181
182 /*!
183   \class LightApp_ModuleAction::ActivateEvent
184   \brief Internal class to represent custom event for transfer the activation item id.
185   \internal
186 */
187 class LightApp_ModuleAction::ActivateEvent : public QEvent
188 {
189 public:
190   ActivateEvent( QEvent::Type type, int id ) : QEvent( type ), myId( id ) {};
191   ~ActivateEvent() {};
192
193   int     id() const { return myId; }
194
195 private:
196   int     myId;
197 };
198
199 /*!
200   \class LightApp_ModuleAction
201   \brief An action, representing the list of modules to be inserted to the
202   toolbar.
203
204   In the toolbar this action is represented as the combo box with the list of
205   available modules, and a set of buttons for each module. Additionally, combo box
206   contains an item representing "neutral point" of the application (i.e. no active module).
207
208   In menu, the action is represented as a plain list of items, one per module.
209
210   Only one module can be active at the moment. It can be set programmatically 
211   with setActiveModule() function. Use this method with empty string to turn
212   to the "neutral point". To get active module, use activeModule() function.
213
214   When user activates/deactivates a module, the moduleActivated() signal
215   is emitted.
216
217   The action also provides an additional separate item "Add modules"; when
218   this button is pressed, a adding() signal is emitted. This signal
219   can be connected to a dedicated slot aimed to dynamically add a new module
220   into the application. In addition, a button "Remove module" shows a dropdown menu
221   with the list of user modules; when any item is selected, the removing() signal
222   is emitted. This signal may be connected to a slot aimed to dynamically remove
223   selected user module from application.
224
225   It is possible to customize which elements to show via the setMode() of setModeEnabled()
226   functions. By default, all elements are shown. The following choices are possible:
227
228   - LightApp_ModuleAction::Buttons: show separate items for all modules
229   - LightApp_ModuleAction::List: show combo box with list of modules (in toolbar only)
230   - LightApp_ModuleAction::AddRemove: show "Add modules" and "Remove modules" items
231   - LightApp_ModuleAction::All: show all items
232 */
233
234 /*!
235   \brief Constructor
236   \param resMgr resource manager
237   \param parent parent object
238 */
239 LightApp_ModuleAction::LightApp_ModuleAction( QtxResourceMgr* resMgr, QObject* parent )
240 : QtxAction( parent )
241 {
242   setText( tr( "APP_NAME" ) );
243   setIcon( resMgr->loadPixmap( "LightApp", tr( "APP_DEFAULT_ICO" ), false ) );
244   setVisible( false );
245
246   myMode = All;
247   myCombo = new ComboAction( this );
248   myAdd = new QtxAction( tr( "ADD_MODULE"),
249                          resMgr->loadPixmap( "LightApp", tr( "ICON_ADD_MODULE" ), false ),
250                          tr( "ADD_MODULE"),
251                          0, this );
252   myRemove = new QtxAction( tr( "REMOVE_MODULE"),
253                             resMgr->loadPixmap( "LightApp", tr( "ICON_REMOVE_MODULE" ), false ),
254                             tr( "REMOVE_MODULE"),
255                             0, this );
256   myRemove->setEnabled( false );
257   myRemove->setMenu( new QMenu() );
258   mySeparator = new QAction( this );
259   mySeparator->setSeparator( true );
260   mySet = new ActionSet( this );
261
262   myMapper = new QSignalMapper( this );
263
264   connect( this,     SIGNAL( changed() ),          this, SLOT( onChanged() ) );
265   connect( myAdd,    SIGNAL( triggered( bool ) ),  this, SIGNAL( adding() ) );
266   connect( mySet,    SIGNAL( triggered( int ) ),   this, SLOT( onTriggered( int ) ) );
267   connect( myCombo,  SIGNAL( activatedId( int ) ), this, SLOT( onComboActivated( int ) ) );
268   connect( myMapper, SIGNAL( mapped( QString ) ),  this, SIGNAL( removing( QString ) ) );
269 }
270
271 /*!
272   \brief Destructor
273 */
274 LightApp_ModuleAction::~LightApp_ModuleAction()
275 {
276 }
277
278 /*!
279   \brief Get number of registered modules.
280   \return modules count
281 */
282 int LightApp_ModuleAction::count() const
283 {
284   return modules().count();
285 }
286
287 /*!
288   \brief Get list of modules.
289   \return modules names list
290 */
291 QStringList LightApp_ModuleAction::modules() const
292 {
293   QStringList lst;
294
295   QList<QAction*> alist = mySet->actions();
296   for ( QList<QAction*>::const_iterator it = alist.begin(); it != alist.end(); ++it )
297     lst.append( (*it)->text() );
298
299   return lst;
300 }
301
302 /*!
303   \brief Get module icon.
304   \param name module name
305   \return module icon
306   \sa setModuleIcon()
307 */
308 QIcon LightApp_ModuleAction::moduleIcon( const QString& name ) const
309 {
310   QAction* a = mySet->moduleAction( name );
311   return a ? a->icon() : QIcon();
312 }
313
314 /*!
315   \brief Set module icon.
316   \param name module name
317   \param ico new module icon
318   \sa moduleIcon()
319 */
320 void LightApp_ModuleAction::setModuleIcon( const QString& name, const QIcon& ico )
321 {
322   QAction* a = mySet->moduleAction( name );
323   if ( !a )
324     return;
325
326   a->setIcon( ico );
327   update();
328 }
329
330 /*!
331   \brief Get module action.
332   \param name module name
333 */
334 QAction* LightApp_ModuleAction::moduleAction( const QString& name ) const
335 {
336   return mySet->moduleAction( name );
337 }
338
339 /*!
340   \brief Add module into the list.
341   \param name module name
342   \param ico module icon
343   \param idx position in the module list (if -1, the module is added to the end of list)
344   \sa removeModule()
345 */
346 void LightApp_ModuleAction::insertModule( const QString& name, const QIcon& ico,
347                                           const int idx )
348 {
349   insertModule( name, ico, false, idx );
350 }
351
352 /*!
353   \brief Add module into the list.
354   \param name module name
355   \param ico module icon
356   \param isCustom \c false to insert regular module, \c true to insert user module
357   \param idx position in the module list (if -1, the module is added to the end of list)
358   \sa removeModule()
359 */
360 void LightApp_ModuleAction::insertModule( const QString& name, const QIcon& ico,
361                                           bool isCustom, const int idx)
362
363 {
364   QtxAction* a = new QtxAction( name, ico, name, 0, this, true );
365   a->setStatusTip( tr( "ACTIVATE_MODULE_TOP" ).arg( name ) );
366   a->setData( isCustom );
367   if ( isCustom )
368   {
369     myRemove->setEnabled( true );
370     QAction* inserted = myRemove->menu()->addAction( name );
371     connect( inserted, SIGNAL( triggered() ), myMapper, SLOT( map() ) );
372     myMapper->setMapping( inserted, name );
373   }
374
375   mySet->insertAction( a, -1, idx );
376   update();
377 }
378
379 /*!
380   \brief Remove module from the list.
381   \param name module name
382   \sa insertModule()
383 */
384 void LightApp_ModuleAction::removeModule( const QString& name )
385 {
386   int id = mySet->moduleId( name );
387   if ( id == -1 )
388     return;
389
390   QAction* a = moduleAction( name );
391   bool isCustom = a->data().toBool();
392
393   mySet->removeAction( id );
394   if ( isCustom )
395   {
396     foreach ( QAction* ma, myRemove->menu()->actions() )
397     {
398       if ( ma->text() == name )
399       {
400         myRemove->menu()->removeAction( ma );
401         break;
402       }
403     }
404     myRemove->setEnabled( !myRemove->menu()->actions().isEmpty() );
405   }
406
407   update();
408 }
409
410 /*!
411   \brief Get active module.
412
413   If there is no active module ("neutral point"), then the null string 
414   is returned.
415
416   \return active module name
417   \sa setActiveModule()
418 */
419 QString LightApp_ModuleAction::activeModule() const
420 {
421   QAction* a = active();
422   return a ? a->text() : QString();
423 }
424
425 /*!
426   \brief Set active module.
427
428   To turn to the "neutral point" (no active module), pass empty string.
429
430   \param name new active module name
431   \sa activeModule()
432 */
433 void LightApp_ModuleAction::setActiveModule( const QString& name )
434 {
435   if ( name == activeModule() )
436     return;
437
438   int id = mySet->moduleId( name );
439   if ( name.isEmpty() || id != -1 )
440     activate( id, false );
441 }
442
443 /*!
444   \brief Set action display mode.
445   \param mode action display options (combination of flags)
446   \sa mode()
447 */
448 void LightApp_ModuleAction::setMode( const LightApp_ModuleAction::Mode& mode )
449 {
450   myMode = mode;
451   update();
452 }
453
454 /*!
455   \brief Enable / disable action display mode.
456   \param mode action display options (combination of flags)
457   \param enabled \c true to enable mode, \c false to disable mode
458   \sa mode()
459 */
460 void LightApp_ModuleAction::setModeEnabled( const LightApp_ModuleAction::Mode& mode, bool enabled )
461 {
462   if ( enabled )
463     myMode |= mode;
464   else
465     myMode &= ~mode;
466   update();
467 }
468
469 /*!
470   \brief Get action display mode.
471   \param mode action display mode
472   \sa setMode()
473 */
474 bool LightApp_ModuleAction::isModeEnabled( const LightApp_ModuleAction::Mode& mode ) const
475 {
476   return (bool)( myMode & mode );
477 }
478
479 /*!
480   \brief Called when the action is added to the widget.
481   \param w widget (not used)
482 */
483 void LightApp_ModuleAction::addedTo( QWidget* w )
484 {
485   if ( w->inherits( "QToolBar" ) )
486     w->insertAction( this, myCombo );
487   w->insertAction( this, myAdd );
488   w->insertAction( this, myRemove );
489   w->insertAction( this, mySeparator );
490   w->insertAction( this, mySet );
491   update();
492 }
493
494 /*!
495   \brief Remove action from widget.
496   \param w widget (menu or toolbar)
497   \return \c true if the action is removed successfully and \c false otherwise.
498   \sa addTo()
499 */
500 void LightApp_ModuleAction::removedFrom( QWidget* w )
501 {
502   if ( w->inherits( "QToolBar" ) )
503     w->removeAction( myCombo );
504   w->removeAction( myAdd );
505   w->removeAction( myRemove );
506   w->removeAction( mySeparator );
507   w->removeAction( mySet );
508 }
509
510 /*!
511   \brief Perform delayed activation with specified id.
512   \param e custom event
513   \return \c true if the event was processed successfully and \c false otherwise.
514 */
515 bool LightApp_ModuleAction::event( QEvent* e )
516 {
517   if ( e->type() == QEvent::MaxUser ) {
518     activate( ((ActivateEvent*)e)->id(), false );
519     return true;
520   }
521   return QtxAction::event( e );
522 }
523
524 /*!
525   \fn void LightApp_ModuleAction::moduleActivated( const QString& name );
526   \brief Emitted when the module is activated
527   \param name module name (empty string for neutral point)
528 */
529
530 /*!
531   \brief Update an action.
532   \internal
533 */
534 void LightApp_ModuleAction::update()
535 {
536   QList<QtxComboBox*> lst = myCombo->widgets();
537   for ( QList<QtxComboBox*>::const_iterator it = lst.begin(); it != lst.end(); ++it )
538     update( *it );
539
540   myCombo->setVisible( myMode & List );
541   myAdd->setVisible( myMode & AddRemove );
542   myRemove->setVisible( myMode & AddRemove );
543   mySet->setVisible( myMode & Buttons );
544 }
545
546 /*!
547   \brief Update combo box.
548   \internal
549   \param cb combo box
550 */
551 void LightApp_ModuleAction::update( QtxComboBox* cb )
552 {
553   if ( !cb )
554     return;
555
556   bool blocked = cb->blockSignals( true );
557   int curId = mySet->moduleId( active() );
558   QList<QAction*> alist = mySet->actions();
559   cb->clear();
560   
561   cb->addItem( icon(), text() );
562   cb->setId( 0, -1 );
563
564   for ( QList<QAction*>::const_iterator it = alist.begin(); it != alist.end(); ++it )
565   {
566     QAction* a = *it;
567     int id = mySet->moduleId( a );
568     cb->addItem( a->icon(), a->text() );
569     cb->setId( cb->count() - 1, id );
570   }
571
572   cb->setCurrentId( curId );
573   cb->blockSignals( blocked );
574 }
575
576 /*!
577   \brief Get an action corresponding to the active module.
578   \internal
579   \return active module action or 0 if there is no active module
580 */
581 QAction* LightApp_ModuleAction::active() const
582 {
583   QAction* a = 0;
584
585   QList<QAction*> alist = mySet->actions();
586   for ( QList<QAction*>::const_iterator it = alist.begin(); it != alist.end() && !a; ++it )
587   {
588     if ( (*it)->isChecked() )
589       a = *it;
590   }
591
592   return a;
593 }
594
595 /*!
596   \brief Activate a module item.
597   \internal
598   \param id module identifier
599   \param fromAction \c true if function is called from the module button
600 */
601 void LightApp_ModuleAction::activate( int id, bool fromAction )
602 {
603   bool checked = false;
604
605   QList<QAction*> alist = mySet->actions();
606   for ( QList<QAction*>::const_iterator it = alist.begin(); it != alist.end(); ++it )
607   {
608     if ( mySet->moduleId( *it ) != id ) {
609       (*it)->setChecked( false );
610     }
611     else {
612       if ( !fromAction )
613         (*it)->setChecked( true );
614       checked = (*it)->isChecked();
615     }
616   }
617
618   QList<QtxComboBox*> widgets = myCombo->widgets();
619   for ( QList<QtxComboBox*>::const_iterator wit = widgets.begin(); wit != widgets.end(); ++wit )
620   {
621     QtxComboBox* cb = *wit;
622     bool blocked = cb->signalsBlocked();
623     cb->blockSignals( true );
624     cb->setCurrentId( checked ? id : -1 );
625     cb->blockSignals( blocked );
626   }
627
628   emit moduleActivated( activeModule() );
629 }
630
631 /*!
632   \brief Called when module button is triggered.
633   \internal
634   \param id module identifier
635 */
636 void LightApp_ModuleAction::onTriggered( int id )
637 {
638   activate( id );
639 }
640
641 /*!
642   \brief Called when action state is changed.
643   \internal
644   
645   This slot is used to prevent making the parent action visible.
646 */
647 void LightApp_ModuleAction::onChanged()
648 {
649   if ( !isVisible() )
650     return;
651
652   bool block = signalsBlocked();
653   blockSignals( true );
654   setVisible( false );
655   blockSignals( block );
656 }
657
658 /*!
659   \brief Called when combo box item is activated.
660   \param id module identifier
661 */
662 void LightApp_ModuleAction::onComboActivated( int id )
663 {
664   QApplication::postEvent( this, new ActivateEvent( QEvent::MaxUser, id ) );
665