Salome HOME
updated copyright message
[modules/gui.git] / src / Qtx / QtxActionMgr.cxx
1 // Copyright (C) 2007-2023  CEA, EDF, 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, or (at your option) any later version.
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
23 // File:      QtxActionMgr.cxx
24 // Author:    Alexander SOLOVYOV, Sergey TELKOV
25 //
26 #include "Qtx.h"
27 #include "QtxActionMgr.h"
28 #include "QtxAction.h"
29 #include <QFile>
30 #include <QTimer>
31 #ifndef QT_NO_DOM
32 #include <QDomDocument>
33 #include <QDomNode>
34 #include <QCoreApplication>
35 #endif
36
37 typedef QList< QPointer<QAction> > qtx_actionlist;
38 static qtx_actionlist qtx_separator_actions;
39
40 /*!
41   \brief Clean all cashed separator actions.
42   \internal
43 */
44 void qtxSeparatorActionCleanup()
45 {
46   for ( qtx_actionlist::iterator it = qtx_separator_actions.begin(); it != qtx_separator_actions.end(); ++it )
47   {
48     QAction* a = *it;
49     delete a;
50   }
51 }
52
53 /*!
54   \class QtxActionMgr::SeparatorAction
55   \brief Separator action class.
56   \internal
57 */
58
59 class QtxActionMgr::SeparatorAction : public QAction
60 {
61 public:
62   SeparatorAction( QObject* = 0 );
63   virtual ~SeparatorAction();
64 };
65
66 /*!
67   \brief Constructor.
68   \internal
69   \param parent parent object
70 */
71 QtxActionMgr::SeparatorAction::SeparatorAction( QObject* parent )
72 : QAction( parent )
73 {
74   setSeparator( true );
75 }
76
77 /*!
78   \brief Destructor.
79 */
80 QtxActionMgr::SeparatorAction::~SeparatorAction()
81 {
82 }
83
84 /*!
85   \class QtxActionMgr
86   \brief Manages a set of actions accessible by unique identifier.
87   
88   Base class for menu, toolbar actions containers and popup menu creators.
89
90   Actions are registered in the manager with the registerAction() method
91   and unregistered from it with the unRegisterAction() method.
92
93   Functions action() and actionId() allow getting action by its identifier
94   and vice versa. Method contains() returns \c true if the action with 
95   the specified identifier is already registered.
96
97   To get total number of the registered actions can be retrieved by
98   the method count(). Function isEmpty() returns \c true if manager does not
99   contains any actions. The list of all actions identifiers can be retrieved
100   with the idList() function.
101
102   The method separator() allows creating a separator action which can be
103   used in the menus or toolbars to separate logical groups of actions.
104   
105   To enable/disable any action by its identifier, use setEnabled() method.
106 */
107
108 /*!
109   \brief Constructor.
110   \param parent parent object
111 */
112 QtxActionMgr::QtxActionMgr( QObject* parent )
113 : QObject( parent ),
114   myUpdate( true ),
115   myUpdTimer( 0 )
116 {
117 }
118
119 /*!
120   \brief Destructor.
121 */
122 QtxActionMgr::~QtxActionMgr()
123 {
124 }
125
126 /*!
127   \brief Register an action in the internal map.
128
129   If \a userId is less than 0, the identifier for the action 
130   is generated automatically. If action with given \a userId 
131   is already registered, it will be re-registered.
132
133   \param a action to be registered
134   \param userId action ID
135   \return action ID (the same as userId or generated one)
136   \sa unRegisterAction()
137 */
138 int QtxActionMgr::registerAction( QAction* a, const int userId )
139 {
140   if ( !a )
141     return -1;
142
143   int theId = userId < 0 ? generateId() : userId;
144
145   if ( contains( theId ) )
146     unRegisterAction( theId );
147
148   int cur = actionId( a );
149   if ( cur != -1 )
150   {
151     if ( userId == -1 )
152       return cur;
153     else
154       unRegisterAction( cur );
155   }
156
157   myActions.insert( theId, a );
158
159   connect( a, SIGNAL( changed() ), this, SLOT( onActionChanged() ) );
160
161   return theId;
162 }
163
164 /*!
165   \brief Unregister action from internal map.
166   \param id action ID
167   \sa registerAction()
168 */
169 void QtxActionMgr::unRegisterAction( const int id )
170 {
171   if ( contains( id ) ) {
172     disconnect( myActions[id], SIGNAL( changed() ),
173                 this, SLOT( onActionChanged() ) );
174     myActions.remove( id );
175   }
176 }
177
178 /*!
179   \brief Get action by specified identifier.
180   \param id action ID
181   \return action (or 0 if \a id is invalid)
182   \sa actionId()
183 */
184 QAction* QtxActionMgr::action( const int id ) const
185 {
186   if ( contains( id ) )
187     return myActions[ id ];
188   else
189     return 0;
190 }
191
192 /*!
193   \brief Get action identifier.
194   \param a action
195   \return action ID (or -1 if action is not found)
196   \sa action()
197 */
198 int QtxActionMgr::actionId( const QAction* a ) const
199 {
200   if ( !a )
201     return -1;
202
203   int theId = -1;
204   for ( ActionMap::ConstIterator it = myActions.begin(); it != myActions.end() && theId == -1; ++it )
205   {
206     if ( it.value() == a )
207       theId = it.key();
208   }
209
210   return theId;
211 }
212
213 /*!
214   \brief Check if an action with given \a id is registered in the action manager.
215   \param id action ID
216   \return \c true if internal map contains action with such identifier
217 */
218 bool QtxActionMgr::contains( const int id ) const
219 {
220   return myActions.contains( id );
221 }
222
223 /*!
224   \brief Get total number of registered actions.
225   \return number of actions in the internal map
226   \sa isEmpty()
227 */
228 int QtxActionMgr::count() const
229 {
230   return myActions.count();
231 }
232
233 /*!
234   \brief Check if there are no actions registered in the action manager.
235   \return \c true if internal map is empty
236   \sa count()
237 */
238 bool QtxActionMgr::isEmpty() const
239 {
240   return myActions.isEmpty();
241 }
242
243 /*!
244   \brief Get all registered actions identifiers.
245   \return list of actions identifiers
246 */
247 QIntList QtxActionMgr::idList() const
248 {
249   return myActions.keys();
250 }
251
252 /*!
253   \brief Check if update is enabled.
254   \return \c true if update is enabled
255   \sa setUpdatesEnabled(), update()
256 */
257 bool QtxActionMgr::isUpdatesEnabled() const
258 {
259   return myUpdate;
260 }
261
262 /*!
263   \brief Enable/disable update operation.
264   \param upd new state
265   \sa isUpdatesEnabled(), update()
266 */
267 void QtxActionMgr::setUpdatesEnabled( const bool upd )
268 {
269   myUpdate = upd;
270 }
271
272 /*!
273   \brief Check if an action with \a actId identifier is visible to
274   the parent action with \a place identifier.
275
276   This method can be redefined in subclasses.
277   Base implementatin always returns \c true.
278
279   \param actId action ID
280   \param place some parent action ID
281   \return \c true if an action is visible to the parent
282   \sa setVisible()
283 */
284 bool QtxActionMgr::isVisible( const int /*actId*/, const int /*place*/ ) const
285 {
286   return true;
287 }
288
289 /*!
290   \brief Set action's visibility flag.
291
292   This method can be redefined in subclasses.
293   Base implementatin does nothing.
294
295   \param actId action ID
296   \param place some parent action ID
297   \param v new visibility state
298   \sa isVisible()
299 */
300 void QtxActionMgr::setVisible( const int /*actId*/, const int /*place*/, const bool /*v*/ )
301 {
302 }
303
304 /*!
305   \brief Update actions.
306
307   Calls virtual function internalUpdate to update the contents.
308   Does nothing if update is disabled.
309
310   \sa setUpdatesEnabled(), isUpdatesEnabled(), internalUpdate()
311 */
312 void QtxActionMgr::update()
313 {
314   if ( !isUpdatesEnabled() )
315     return;
316
317   internalUpdate();
318   if ( myUpdTimer )
319     myUpdTimer->stop();
320 }
321
322 /*!
323   \brief Internal update.
324
325   This method is called by update() function and can be redefined 
326   in subclasses to customize update operation. Base implementation
327   does nothing.
328 */
329 void QtxActionMgr::internalUpdate()
330 {
331 }
332
333 /*!
334   \brief Generate unique action identifier.
335   \return new ID
336 */
337 int QtxActionMgr::generateId() const
338 {
339   static int id = -1;
340   return --id;
341 }
342
343 /*!
344   \brief Check is action with given \a id is enabled.
345   \param id action ID
346   \return \c true if action is enabled
347 */
348 bool QtxActionMgr::isEnabled( const int id ) const
349 {
350   QAction* a = action( id );
351   if ( a )
352     return a->isEnabled();
353   else
354     return false;
355 }
356
357 /*!
358   Enable/disable action with given \a id.
359   \param id action ID
360   \param enable new state
361 */
362 void QtxActionMgr::setEnabled( const int id, const bool enable )
363 {
364   QAction* a = action( id );
365   if ( a )
366     a->setEnabled( enable );
367 }
368
369 /*!
370   \brief Create new separator action.
371
372   If \a own is \c true, then the caller is responsible for the action
373   destroying. If \a own is \c false, new separator action will be owned by the
374   action manager which will destroy it on application exit.
375
376   \param own ownership flag
377   \return new separator action
378 */
379 QAction* QtxActionMgr::separator( const bool own )
380 {
381   if ( own )
382     return new SeparatorAction();
383
384   if ( qtx_separator_actions.isEmpty() )
385     qAddPostRoutine( qtxSeparatorActionCleanup );
386
387   SeparatorAction* a = new SeparatorAction();
388   qtx_separator_actions.append( a );
389
390   return a;
391 }
392
393 /*!
394   \brief Perform delayed update.
395
396   Does nothing if update is disabled.
397   \sa isUpdatesEnabled(), setUpdatesEnabled(), update()
398 */
399 void QtxActionMgr::triggerUpdate()
400 {
401   if ( !isUpdatesEnabled() )
402     return;
403
404   if ( !myUpdTimer )
405   {
406     myUpdTimer = new QTimer( this );
407     myUpdTimer->setSingleShot( true );
408     connect( myUpdTimer, SIGNAL( timeout() ), this, SLOT( onUpdateContent() ) );
409   }
410   myUpdTimer->stop();
411   // add timer event to event list
412   myUpdTimer->start( 0 );
413 }
414
415 /*!
416   \brief Internal content update operation.
417
418   Called automatically by onUpdateContent() when the delayed update
419   is triggered. Base implementation does nothing.
420
421   \sa triggerUpdate(), onUpdateContent()
422 */
423 void QtxActionMgr::updateContent()
424 {
425 }
426
427 /*!
428   \brief Internal action changing response operation.
429 */
430 void QtxActionMgr::actionChanged( int )
431 {
432 }
433
434 /*!
435   \brief Called when delayed update is performed (via timer event).
436
437   Calls virtual method updateContent() which can be redefined in the
438   subclasses to customize the content update operation.
439 */
440 void QtxActionMgr::onUpdateContent()
441 {
442   updateContent();
443 }
444
445 /*!
446   \brief Called when one of the registered actions changed.
447
448   Calls virtual method actionChanged() which can be redefined in the
449   subclasses to customize reaction on this.
450 */
451 void QtxActionMgr::onActionChanged()
452 {
453   QAction* a = ::qobject_cast<QAction*>( sender() );
454
455   int id = actionId( a );
456   if ( id != -1 )
457     actionChanged( id );
458 }
459
460 /*!
461   \class QtxActionMgr::Reader
462   \brief Generic actions description files reader class.
463
464   This class is used to read files of some format to create actions 
465   and fill an action manager with the actions automatically.
466 */
467
468 /*!
469   \brief Constructor.
470 */
471 QtxActionMgr::Reader::Reader()
472 {
473 }
474
475 /*!
476   \brief Destructor
477 */
478 QtxActionMgr::Reader::~Reader()
479 {
480 }
481
482 /*!
483   \brief Get the list of options.
484   \return options list
485 */
486 QStringList QtxActionMgr::Reader::options() const
487 {
488   return myOptions.keys();
489 }
490
491 /*!
492   \brief Get option value.
493   
494   If there is no such option the default value (\a def) is returned.
495
496   \param name option name
497   \param def default option value
498   \return option value
499 */
500 QString QtxActionMgr::Reader::option( const QString& name, const QString& def ) const
501 {
502   if( myOptions.contains( name ) )
503     return myOptions[ name ];
504   else
505     return def;
506 }
507
508 /*!
509   \brief Set option value.
510   \param name option name
511   \param value new option value
512 */
513 void QtxActionMgr::Reader::setOption( const QString& name, const QString& value )
514 {
515   myOptions[ name ] = value;
516 }
517
518 /*!
519   \fn bool QtxActionMgr::Reader::read( const QString& fname, Creator& cr ) const
520   \brief Read the file and fill and action manager with actions 
521          by using help actions creator. 
522
523   This method should be redefined in the subclasses.
524   
525   \param fname XML file name
526   \param cr actions creator
527   \return \c true on success and \c false in case of error
528 */
529
530 /*!
531   \class QtxActionMgr::XMLReader
532   \brief XML file reader.
533
534   This class is used to read files of XML format to create 
535   actions and fill an action manager with actions automatically.
536 */
537
538 /*!
539   \brief Constructor.
540   \param root root XML tag name
541   \param item menu item XML tag name
542   \param dir resources directory (containing icons, etc)
543 */
544 QtxActionMgr::XMLReader::XMLReader( const QString& root,
545                                     const QString& item,
546                                     const QString& dir )
547 : Reader()
548 {
549   setOption( QString( "root_tag" ),  root );
550   setOption( QString( "menu_item" ), item );
551   setOption( QString( "icons_dir" ), dir  );
552   setOption( QString( "id" ),        QString( "item-id" ) );
553   setOption( QString( "pos" ),       QString( "pos-id" ) );
554   setOption( QString( "group" ),     QString( "group-id" ) );
555   setOption( QString( "label" ),     QString( "label-id" ) );
556   setOption( QString( "tooltip" ),   QString( "tooltip-id" ) );
557   setOption( QString( "accel" ),     QString( "accel-id" ) );
558   setOption( QString( "separator" ), QString( "separator" ) );
559   setOption( QString( "icon" ),      QString( "icon-id" ) );
560   setOption( QString( "toggle" ),    QString( "toggle-id" ) );
561 }
562
563 /*!
564   \brief Destructor.
565 */
566 QtxActionMgr::XMLReader::~XMLReader()
567 {
568 }
569
570 /*!
571   \brief Read the file and fill and action manager with actions 
572          by using actions creator.
573   \param fname XML file name
574   \param cr actions creator
575   \return \c true on success and \c false in case of error
576 */
577 bool QtxActionMgr::XMLReader::read( const QString& fname, Creator& cr ) const
578 {
579   bool res = false;
580
581 #ifndef QT_NO_DOM
582
583   QFile file( fname );
584   if ( !file.open( QFile::ReadOnly ) )
585     return res;
586
587   QDomDocument doc;
588
589   res = doc.setContent( &file );
590   file.close();
591
592   if ( !res )
593     return res;
594
595   QString root = option( "root_tag" );
596   for( QDomNode cur = doc.documentElement(); !cur.isNull(); )
597   {
598     if( cur.isElement() && isNodeSimilar( cur, root ) )
599       read( cur, -1, cr );
600     else if( cur.hasChildNodes() )
601     {
602       cur = cur.firstChild();
603       continue;
604     }
605
606     while( !cur.isNull() && cur.nextSibling().isNull() )
607       cur = cur.parentNode();
608     if( !cur.isNull() )
609       cur = cur.nextSibling();
610   }
611
612 #endif
613
614   return res;
615 }
616
617 /*!
618   \brief Read XML mode and create an item if requied.
619   \param parent_node parent XML file node
620   \param parent_id parent action ID
621   \param cr actions creator
622 */
623 void QtxActionMgr::XMLReader::read( const QDomNode& parent_node,
624                                     const int parent_id,
625                                     Creator& cr ) const
626 {
627   if( parent_node.isNull() )
628     return;
629
630   QStringList items = option( "menu_item" ).split( "|", QString::SkipEmptyParts );
631
632   const QDomNodeList& children = parent_node.childNodes();
633   for( int i=0, n=children.count(); i<n; i++ )
634   {
635     QDomNode node = children.item( i );
636     //QString n = node.nodeName();
637     if( node.isElement() /*&& node.hasAttributes()*/ &&
638         ( items.contains( node.nodeName() ) || node.nodeName()==option( "separator" ) ) )
639     {
640       QDomNamedNodeMap map = node.attributes();
641       ItemAttributes attrs;
642
643       for( int i=0, n=map.count(); i<n; i++ )
644         if( map.item( i ).isAttr() )
645         {
646           QDomAttr a = map.item( i ).toAttr();
647           attrs.insert( a.name(), a.value() );
648         }
649
650       int newId = cr.append( node.nodeName(), node.hasChildNodes(), attrs, parent_id );
651       if( node.hasChildNodes() )
652         read( node, newId, cr );
653     }
654   }
655 }
656
657 /*!
658   \brief Check node name correspondance to some pattern.
659   \param node XML file node
660   \param pattern node name pattern
661   \return \c true if node satisfies pattern
662 */
663 bool QtxActionMgr::XMLReader::isNodeSimilar( const QDomNode& node,
664                                              const QString& pattern ) const
665 {
666   if( node.nodeName()==pattern )
667     return true;
668
669   QDomDocument temp;
670   QString mes;
671   temp.setContent( pattern, true, &mes );
672
673   const QDomNamedNodeMap &temp_map = temp.documentElement().attributes(),
674                          &cur_map = node.attributes();
675   bool ok = temp_map.count()>0;
676   for( int i=0, n=temp_map.count(); i<n && ok; i++ )
677   {
678     QDomAttr a = temp_map.item( i ).toAttr(),
679              b = cur_map.namedItem( a.name() ).toAttr();
680     ok = !b.isNull() && a.name()==b.name() && a.value()==b.value();
681   }
682
683   return ok;
684 }
685
686 /*!
687   \class QtxActionMgr::Creator
688   \brief Generic actions creator class.
689
690   Used by Reader to create actions and fill in the action 
691   manager with the actions.
692 */
693
694 /*!
695   \brief Get integer attribute value from the attribute map.
696
697   Returns default value (\a def) if the attribute is not found.
698
699   \param attrs attributes map
700   \param name attribute name
701   \param def default attribute value
702   \return attribute value
703 */
704 int QtxActionMgr::Creator::intValue( const ItemAttributes& attrs,
705                                      const QString& name, int def )
706 {
707   if( attrs.contains( name ) )
708   {
709     bool ok;
710     int res = attrs[ name ].toInt( &ok );
711     if( ok )
712       return res;
713   }
714   return def;
715 }
716
717 /*!
718   \brief Get string attribute value from the attribute map.
719
720   Returns default value (\a def) if the attribute is not found.
721
722   \param attrs attributes map
723   \param name attribute name
724   \param def default attribute value
725   \return attribute value
726 */
727 QString QtxActionMgr::Creator::strValue( const ItemAttributes& attrs,
728                                          const QString& name,
729                                          const QString& def  )
730 {
731   if( attrs.contains( name ) )
732     return attrs[ name ];
733   else
734     return def;
735 }
736
737 /*!
738   \brief Constructor.
739   \param r action reader
740 */
741 QtxActionMgr::Creator::Creator( QtxActionMgr::Reader* r )
742 : myReader( r )
743 {
744 }
745
746 /*!
747   \brief Destructor.
748 */
749 QtxActionMgr::Creator::~Creator()
750 {
751 }
752
753 /*!
754   \brief Get actions reader.
755   \return actions reader
756 */
757 QtxActionMgr::Reader* QtxActionMgr::Creator::reader() const
758 {
759   return myReader;
760 }
761
762 /*!
763   \brief Connect action to some specific slot(s).
764
765   This method can be redefined in subclasses. 
766   Base implementation does nothing.
767
768   \param a action
769 */
770 void QtxActionMgr::Creator::connect( QAction* /*a*/ ) const
771 {
772 }
773
774 /*!
775   \brief Load pixmap from the file.
776   \param fname file name
777   \param pix used to return pixmap
778   \return \c true if pixmap is loaded successfully and \c false in case of error
779 */
780 bool QtxActionMgr::Creator::loadPixmap( const QString& fname, QPixmap& pix ) const
781 {
782   if( !reader() )
783     return false;
784
785   QStringList dirlist = reader()->option( "icons_dir", "." ).split( ";", QString::SkipEmptyParts );
786   QStringList::const_iterator anIt = dirlist.begin(),
787                               aLast = dirlist.end();
788   bool res = false;
789   for( ; anIt!=aLast && !res; anIt++ )
790     res = pix.load( Qtx::addSlash( *anIt ) + fname );
791
792   return res;
793 }
794
795 /*!
796   \fn int QtxActionMgr::Creator::append( const QString& tag, 
797                                          const bool subMenu, 
798                                          const ItemAttributes& attr,
799                                          const int pId )
800   \brief Create (and probably append to the action manager) new action.
801
802   This method should be redefined in the subclasses.
803   
804   \param tag item tag name
805   \param subMenu \c true if this item is submenu
806   \param attr attributes map
807   \param pId parent action ID
808   \return item (for example action) ID
809 */