Salome HOME
Update copyrights 2014.
[modules/gui.git] / src / Qtx / QtxActionMgr.cxx
1 // Copyright (C) 2007-2014  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, 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   return theId;
160 }
161
162 /*!
163   \brief Unregister action from internal map.
164   \param id action ID
165   \sa registerAction()
166 */
167 void QtxActionMgr::unRegisterAction( const int id )
168 {
169   if( contains( id ) )
170     myActions.remove( id );
171 }
172
173 /*!
174   \brief Get action by specified identifier.
175   \param id action ID
176   \return action (or 0 if \a id is invalid)
177   \sa actionId()
178 */
179 QAction* QtxActionMgr::action( const int id ) const
180 {
181   if ( contains( id ) )
182     return myActions[ id ];
183   else
184     return 0;
185 }
186
187 /*!
188   \brief Get action identifier.
189   \param a action
190   \return action ID (or -1 if action is not found)
191   \sa action()
192 */
193 int QtxActionMgr::actionId( const QAction* a ) const
194 {
195   if ( !a )
196     return -1;
197
198   int theId = -1;
199   for ( ActionMap::ConstIterator it = myActions.begin(); it != myActions.end() && theId == -1; ++it )
200   {
201     if ( it.value() == a )
202       theId = it.key();
203   }
204
205   return theId;
206 }
207
208 /*!
209   \brief Check if an action with given \a id is registered in the action manager.
210   \param id action ID
211   \return \c true if internal map contains action with such identifier
212 */
213 bool QtxActionMgr::contains( const int id ) const
214 {
215   return myActions.contains( id );
216 }
217
218 /*!
219   \brief Get total number of registered actions.
220   \return number of actions in the internal map
221   \sa isEmpty()
222 */
223 int QtxActionMgr::count() const
224 {
225   return myActions.count();
226 }
227
228 /*!
229   \brief Check if there are no actions registered in the action manager.
230   \return \c true if internal map is empty
231   \sa count()
232 */
233 bool QtxActionMgr::isEmpty() const
234 {
235   return myActions.isEmpty();
236 }
237
238 /*!
239   \brief Get all registered actions identifiers.
240   \return list of actions identifiers
241 */
242 QIntList QtxActionMgr::idList() const
243 {
244   return myActions.keys();
245 }
246
247 /*!
248   \brief Check if update is enabled.
249   \return \c true if update is enabled
250   \sa setUpdatesEnabled(), update()
251 */
252 bool QtxActionMgr::isUpdatesEnabled() const
253 {
254   return myUpdate;
255 }
256
257 /*!
258   \brief Enable/disable update operation.
259   \param upd new state
260   \sa isUpdatesEnabled(), update()
261 */
262 void QtxActionMgr::setUpdatesEnabled( const bool upd )
263 {
264   myUpdate = upd;
265 }
266
267 /*!
268   \brief Check if an action with \a actId identifier is visible to
269   the parent action with \a place identifier.
270
271   This method can be redefined in subclasses.
272   Base implementatin always returns \c true.
273
274   \param actId action ID
275   \param place some parent action ID
276   \return \c true if an action is visible to the parent
277   \sa setVisible()
278 */
279 bool QtxActionMgr::isVisible( const int /*actId*/, const int /*place*/ ) const
280 {
281   return true;
282 }
283
284 /*!
285   \brief Set action's visibility flag.
286
287   This method can be redefined in subclasses.
288   Base implementatin does nothing.
289
290   \param actId action ID
291   \param place some parent action ID
292   \param v new visibility state
293   \sa isVisible()
294 */
295 void QtxActionMgr::setVisible( const int /*actId*/, const int /*place*/, const bool /*v*/ )
296 {
297 }
298
299 /*!
300   \brief Update actions.
301
302   Calls virtual function internalUpdate to update the contents.
303   Does nothing if update is disabled.
304
305   \sa setUpdatesEnabled(), isUpdatesEnabled(), internalUpdate()
306 */
307 void QtxActionMgr::update()
308 {
309   if ( !isUpdatesEnabled() )
310     return;
311
312   internalUpdate();
313   if ( myUpdTimer )
314     myUpdTimer->stop();
315 }
316
317 /*!
318   \brief Internal update.
319
320   This method is called by update() function and can be redefined 
321   in subclasses to customize update operation. Base implementation
322   does nothing.
323 */
324 void QtxActionMgr::internalUpdate()
325 {
326 }
327
328 /*!
329   \brief Generate unique action identifier.
330   \return new ID
331 */
332 int QtxActionMgr::generateId() const
333 {
334   static int id = -1;
335   return --id;
336 }
337
338 /*!
339   \brief Check is action with given \a id is enabled.
340   \param id action ID
341   \return \c true if action is enabled
342 */
343 bool QtxActionMgr::isEnabled( const int id ) const
344 {
345   QAction* a = action( id );
346   if ( a )
347     return a->isEnabled();
348   else
349     return false;
350 }
351
352 /*!
353   Enable/disable action with given \a id.
354   \param id action ID
355   \param enable new state
356 */
357 void QtxActionMgr::setEnabled( const int id, const bool enable )
358 {
359   QAction* a = action( id );
360   if ( a )
361     a->setEnabled( enable );
362 }
363
364 /*!
365   \brief Create new separator action.
366
367   If \a own is \c true, then the caller is responsible for the action
368   destroying. If \a own is \c false, new separator action will be owned by the
369   action manager which will destroy it on application exit.
370
371   \param own ownership flag
372   \return new separator action
373 */
374 QAction* QtxActionMgr::separator( const bool own )
375 {
376   if ( own )
377     return new SeparatorAction();
378
379   if ( qtx_separator_actions.isEmpty() )
380     qAddPostRoutine( qtxSeparatorActionCleanup );
381
382   SeparatorAction* a = new SeparatorAction();
383   qtx_separator_actions.append( a );
384
385   return a;
386 }
387
388 /*!
389   \brief Perform delayed update.
390
391   Does nothing if update is disabled.
392   \sa isUpdatesEnabled(), setUpdatesEnabled(), update()
393 */
394 void QtxActionMgr::triggerUpdate()
395 {
396   if ( !isUpdatesEnabled() )
397     return;
398
399   if ( !myUpdTimer )
400   {
401     myUpdTimer = new QTimer( this );
402     myUpdTimer->setSingleShot( true );
403     connect( myUpdTimer, SIGNAL( timeout() ), this, SLOT( onUpdateContent() ) );
404   }
405   myUpdTimer->stop();
406   // add timer event to event list
407   myUpdTimer->start( 0 );
408 }
409
410 /*!
411   \brief Internal content update operation.
412
413   Called automatically by onUpdateContent() when the delayed update
414   is triggered. Base implementation does nothing.
415
416   \sa triggerUpdate(), onUpdateContent()
417 */
418 void QtxActionMgr::updateContent()
419 {
420 }
421
422 /*!
423   \brief Called when delayed update is performed (via timer event).
424
425   Calls virtual method updateContent() which can be redefined in the
426   subclasses to customize the content update operation.
427 */
428 void QtxActionMgr::onUpdateContent()
429 {
430   updateContent();
431 }
432
433 /*!
434   \class QtxActionMgr::Reader
435   \brief Generic actions description files reader class.
436
437   This class is used to read files of some format to create actions 
438   and fill an action manager with the actions automatically.
439 */
440
441 /*!
442   \brief Constructor.
443 */
444 QtxActionMgr::Reader::Reader()
445 {
446 }
447
448 /*!
449   \brief Destructor
450 */
451 QtxActionMgr::Reader::~Reader()
452 {
453 }
454
455 /*!
456   \brief Get the list of options.
457   \return options list
458 */
459 QStringList QtxActionMgr::Reader::options() const
460 {
461   return myOptions.keys();
462 }
463
464 /*!
465   \brief Get option value.
466   
467   If there is no such option the default value (\a def) is returned.
468
469   \param name option name
470   \param def default option value
471   \return option value
472 */
473 QString QtxActionMgr::Reader::option( const QString& name, const QString& def ) const
474 {
475   if( myOptions.contains( name ) )
476     return myOptions[ name ];
477   else
478     return def;
479 }
480
481 /*!
482   \brief Set option value.
483   \param name option name
484   \param value new option value
485 */
486 void QtxActionMgr::Reader::setOption( const QString& name, const QString& value )
487 {
488   myOptions[ name ] = value;
489 }
490
491 /*!
492   \fn bool QtxActionMgr::Reader::read( const QString& fname, Creator& cr ) const
493   \brief Read the file and fill and action manager with actions 
494          by using help actions creator. 
495
496   This method should be redefined in the subclasses.
497   
498   \param fname XML file name
499   \param cr actions creator
500   \return \c true on success and \c false in case of error
501 */
502
503 /*!
504   \class QtxActionMgr::XMLReader
505   \brief XML file reader.
506
507   This class is used to read files of XML format to create 
508   actions and fill an action manager with actions automatically.
509 */
510
511 /*!
512   \brief Constructor.
513   \param root root XML tag name
514   \param item menu item XML tag name
515   \param dir resources directory (containing icons, etc)
516 */
517 QtxActionMgr::XMLReader::XMLReader( const QString& root,
518                                     const QString& item,
519                                     const QString& dir )
520 : Reader()
521 {
522   setOption( QString( "root_tag" ),  root );
523   setOption( QString( "menu_item" ), item );
524   setOption( QString( "icons_dir" ), dir  );
525   setOption( QString( "id" ),        QString( "item-id" ) );
526   setOption( QString( "pos" ),       QString( "pos-id" ) );
527   setOption( QString( "group" ),     QString( "group-id" ) );
528   setOption( QString( "label" ),     QString( "label-id" ) );
529   setOption( QString( "tooltip" ),   QString( "tooltip-id" ) );
530   setOption( QString( "accel" ),     QString( "accel-id" ) );
531   setOption( QString( "separator" ), QString( "separator" ) );
532   setOption( QString( "icon" ),      QString( "icon-id" ) );
533   setOption( QString( "toggle" ),    QString( "toggle-id" ) );
534 }
535
536 /*!
537   \brief Destructor.
538 */
539 QtxActionMgr::XMLReader::~XMLReader()
540 {
541 }
542
543 /*!
544   \brief Read the file and fill and action manager with actions 
545          by using actions creator.
546   \param fname XML file name
547   \param cr actions creator
548   \return \c true on success and \c false in case of error
549 */
550 bool QtxActionMgr::XMLReader::read( const QString& fname, Creator& cr ) const
551 {
552   bool res = false;
553
554 #ifndef QT_NO_DOM
555
556   QFile file( fname );
557   if ( !file.open( QFile::ReadOnly ) )
558     return res;
559
560   QDomDocument doc;
561
562   res = doc.setContent( &file );
563   file.close();
564
565   if ( !res )
566     return res;
567
568   QString root = option( "root_tag" );
569   for( QDomNode cur = doc.documentElement(); !cur.isNull(); )
570   {
571     if( cur.isElement() && isNodeSimilar( cur, root ) )
572       read( cur, -1, cr );
573     else if( cur.hasChildNodes() )
574     {
575       cur = cur.firstChild();
576       continue;
577     }
578
579     while( !cur.isNull() && cur.nextSibling().isNull() )
580       cur = cur.parentNode();
581     if( !cur.isNull() )
582       cur = cur.nextSibling();
583   }
584
585 #endif
586
587   return res;
588 }
589
590 /*!
591   \brief Read XML mode and create an item if requied.
592   \param parent_node parent XML file node
593   \param parent_id parent action ID
594   \param cr actions creator
595 */
596 void QtxActionMgr::XMLReader::read( const QDomNode& parent_node,
597                                     const int parent_id,
598                                     Creator& cr ) const
599 {
600   if( parent_node.isNull() )
601     return;
602
603   QStringList items = option( "menu_item" ).split( "|", QString::SkipEmptyParts );
604
605   const QDomNodeList& children = parent_node.childNodes();
606   for( int i=0, n=children.count(); i<n; i++ )
607   {
608     QDomNode node = children.item( i );
609     //QString n = node.nodeName();
610     if( node.isElement() /*&& node.hasAttributes()*/ &&
611         ( items.contains( node.nodeName() ) || node.nodeName()==option( "separator" ) ) )
612     {
613       QDomNamedNodeMap map = node.attributes();
614       ItemAttributes attrs;
615
616       for( int i=0, n=map.count(); i<n; i++ )
617         if( map.item( i ).isAttr() )
618         {
619           QDomAttr a = map.item( i ).toAttr();
620           attrs.insert( a.name(), a.value() );
621         }
622
623       int newId = cr.append( node.nodeName(), node.hasChildNodes(), attrs, parent_id );
624       if( node.hasChildNodes() )
625         read( node, newId, cr );
626     }
627   }
628 }
629
630 /*!
631   \brief Check node name correspondance to some pattern.
632   \param node XML file node
633   \param pattern node name pattern
634   \return \c true if node satisfies pattern
635 */
636 bool QtxActionMgr::XMLReader::isNodeSimilar( const QDomNode& node,
637                                              const QString& pattern ) const
638 {
639   if( node.nodeName()==pattern )
640     return true;
641
642   QDomDocument temp;
643   QString mes;
644   temp.setContent( pattern, true, &mes );
645
646   const QDomNamedNodeMap &temp_map = temp.documentElement().attributes(),
647                          &cur_map = node.attributes();
648   bool ok = temp_map.count()>0;
649   for( int i=0, n=temp_map.count(); i<n && ok; i++ )
650   {
651     QDomAttr a = temp_map.item( i ).toAttr(),
652              b = cur_map.namedItem( a.name() ).toAttr();
653     ok = !b.isNull() && a.name()==b.name() && a.value()==b.value();
654   }
655
656   return ok;
657 }
658
659 /*!
660   \class QtxActionMgr::Creator
661   \brief Generic actions creator class.
662
663   Used by Reader to create actions and fill in the action 
664   manager with the actions.
665 */
666
667 /*!
668   \brief Get integer attribute value from the attribute map.
669
670   Returns default value (\a def) if the attribute is not found.
671
672   \param attrs attributes map
673   \param name attribute name
674   \param def default attribute value
675   \return attribute value
676 */
677 int QtxActionMgr::Creator::intValue( const ItemAttributes& attrs,
678                                      const QString& name, int def )
679 {
680   if( attrs.contains( name ) )
681   {
682     bool ok;
683     int res = attrs[ name ].toInt( &ok );
684     if( ok )
685       return res;
686   }
687   return def;
688 }
689
690 /*!
691   \brief Get string attribute value from the attribute map.
692
693   Returns default value (\a def) if the attribute is not found.
694
695   \param attrs attributes map
696   \param name attribute name
697   \param def default attribute value
698   \return attribute value
699 */
700 QString QtxActionMgr::Creator::strValue( const ItemAttributes& attrs,
701                                          const QString& name,
702                                          const QString& def  )
703 {
704   if( attrs.contains( name ) )
705     return attrs[ name ];
706   else
707     return def;
708 }
709
710 /*!
711   \brief Constructor.
712   \param r action reader
713 */
714 QtxActionMgr::Creator::Creator( QtxActionMgr::Reader* r )
715 : myReader( r )
716 {
717 }
718
719 /*!
720   \brief Destructor.
721 */
722 QtxActionMgr::Creator::~Creator()
723 {
724 }
725
726 /*!
727   \brief Get actions reader.
728   \return actions reader
729 */
730 QtxActionMgr::Reader* QtxActionMgr::Creator::reader() const
731 {
732   return myReader;
733 }
734
735 /*!
736   \brief Connect action to some specific slot(s).
737
738   This method can be redefined in subclasses. 
739   Base implementation does nothing.
740
741   \param a action
742 */
743 void QtxActionMgr::Creator::connect( QAction* /*a*/ ) const
744 {
745 }
746
747 /*!
748   \brief Load pixmap from the file.
749   \param fname file name
750   \param pix used to return pixmap
751   \return \c true if pixmap is loaded successfully and \c false in case of error
752 */
753 bool QtxActionMgr::Creator::loadPixmap( const QString& fname, QPixmap& pix ) const
754 {
755   if( !reader() )
756     return false;
757
758   QStringList dirlist = reader()->option( "icons_dir", "." ).split( ";", QString::SkipEmptyParts );
759   QStringList::const_iterator anIt = dirlist.begin(),
760                               aLast = dirlist.end();
761   bool res = false;
762   for( ; anIt!=aLast && !res; anIt++ )
763     res = pix.load( Qtx::addSlash( *anIt ) + fname );
764
765   return res;
766 }
767
768 /*!
769   \fn int QtxActionMgr::Creator::append( const QString& tag, 
770                                          const bool subMenu, 
771                                          const ItemAttributes& attr,
772                                          const int pId )
773   \brief Create (and probably append to the action manager) new action.
774
775   This method should be redefined in the subclasses.
776   
777   \param tag item tag name
778   \param subMenu \c true if this item is submenu
779   \param attr attributes map
780   \param pId parent action ID
781   \return item (for example action) ID
782 */