Salome HOME
updated copyright message
[modules/gui.git] / src / Qtx / QtxActionMenuMgr.cxx
1 // Copyright (C) 2007-2023  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:      QtxActionMenuMgr.cxx
24 // Author:    Alexander SOLOVYOV, Sergey TELKOV
25 //
26 #include "QtxActionMenuMgr.h"
27
28 #include "QtxAction.h"
29
30 #include <QMenu>
31 #include <QMenuBar>
32 #include <QWidget>
33 #include <QMainWindow>
34
35 /*!
36   \class QtxActionMenuMgr::MenuNode
37   \brief Represents a menu item inside main menu structure.
38   \internal
39 */
40
41 class QtxActionMenuMgr::MenuNode
42 {
43 public:
44   MenuNode();
45   MenuNode( MenuNode*, const int, const int, const int );
46   ~MenuNode();
47   
48   MenuNode* parent;       //!< parent menu node
49   int       id;           //!< menu nodeID
50   int       idx;          //!< menu node index 
51   int       group;        //!< menu group ID
52   bool      visible;      //!< visibility status
53   int       emptyEnabled; //!< enable empty menu flag
54   NodeList  children;     //!< children menu nodes list
55 };
56
57 /*!
58   \brief Default constructor.
59   \internal
60 */
61 QtxActionMenuMgr::MenuNode::MenuNode()
62   : parent( 0 ), id( -1 ), idx( -1 ), group( -1 ), visible( true ), emptyEnabled( 0 )
63 {
64 }
65
66 /*!
67   \brief Constructor.
68   \internal
69   \param p parent menu node
70   \param _id menu node ID
71   \param _idx menu node index
72   \param _group menu node group ID
73 */
74 QtxActionMenuMgr::MenuNode::MenuNode( MenuNode* p,
75                                       const int _id,
76                                       const int _idx,
77                                       const int _group )
78 : parent( p ), id( _id ), idx( _idx ), group( _group ), visible( true ), emptyEnabled( 0 )
79 {
80   if ( p )
81     p->children.append( this );
82 }
83
84 /*!
85   \brief Destructor.
86   \internal
87 */
88 QtxActionMenuMgr::MenuNode::~MenuNode()
89 {
90   for ( NodeList::iterator it = children.begin(); it != children.end(); ++it )
91     delete *it;
92 }
93
94 /*!
95   \class QtxActionMenuMgr
96   \brief Main menu actions manager.
97
98   Menu manager allows using of set of action for automatic generating of
99   application main menu and dynamic update of its contents.
100
101   Use insert(), append() and remove() methods to create main menu.
102   Methods show(), hide() allow displaying/erasing of specified menu items.
103
104   Actions can be grouped with help of group identificator. Inside the popup
105   or main menu bar menu items are ordered by the group identifier (ascending).
106
107   Menu manager automatically optimizes the menu by removing extra separators,
108   hiding empty popup menus etc.
109 */
110
111 /*!
112   \brief Constructor.
113   \param p parent main window
114 */
115 QtxActionMenuMgr::QtxActionMenuMgr( QMainWindow* p )
116 : QtxActionMgr( p ), 
117   myRoot( new MenuNode() ),
118   myMenu( p ? p->menuBar() : 0 )
119 {
120   if ( myMenu ) {
121     connect( myMenu, SIGNAL( destroyed( QObject* ) ), this, SLOT( onDestroyed( QObject* ) ) );
122   }
123 }
124
125 /*!
126   \brief Constructor.
127   \param mw menu widget
128   \param p parent object
129 */
130 QtxActionMenuMgr::QtxActionMenuMgr( QWidget* mw, QObject* p )
131 : QtxActionMgr( p ), 
132   myRoot( new MenuNode() ),
133   myMenu( mw )
134 {
135   if ( myMenu ) {
136     connect( myMenu, SIGNAL( destroyed( QObject* ) ), this, SLOT( onDestroyed( QObject* ) ) );
137   }
138 }
139
140 /*!
141   \brief Destructor.
142 */
143 QtxActionMenuMgr::~QtxActionMenuMgr()
144 {
145   for ( MenuMap::Iterator itr = myMenus.begin(); itr != myMenus.end(); ++itr )
146   {
147     QPointer<QAction> a = itr.value();
148     delete a->menu();
149     delete a;
150   }
151
152   delete myRoot;
153 }
154
155 /*!
156   \brief Check if an action with \a actId identifier is visible to
157   the parent action with \a place identifier.
158   \param actId action ID
159   \param place some parent action ID
160   \return \c true if an action is visible to the parent
161   \sa setVisible()
162 */
163 bool QtxActionMenuMgr::isVisible( const int actId, const int place ) const
164 {
165   MenuNode* node = find( actId, place );
166   return node && node->visible;
167 }
168
169 /*!
170   \brief Set action's visibility flag.
171   \param actId action ID
172   \param place some parent action ID
173   \param v new visibility state
174   \sa isVisible()
175 */
176 void QtxActionMenuMgr::setVisible( const int actId, const int place, const bool v )
177 {
178   MenuNode* node = find( actId, place );
179   if ( node )
180     node->visible = v;
181 }
182
183 /*!
184   \brief Insert action to the menu.
185
186   Insert an action to the named menu. The \a menus parameter represents 
187   the menu name: it can be a sequence of strings, separated by '|' symbol.
188   For example, "File|Edit" means \c File->Edit submenu.
189   If submenu doesn't exist, it will be created.
190
191   \param id action ID
192   \param menus menu name
193   \param group group ID
194   \param idx menu index inside the menu group
195   \return action ID
196 */
197 int QtxActionMenuMgr::insert( const int id, const QString& menus, const int group, const int idx )
198 {
199   return insert( id, menus.split( "|", QString::SkipEmptyParts ), group, idx );
200 }
201
202 /*!
203   \brief Insert action to the menu.
204
205   Insert an action to the named menu. The \a menus parameter represents 
206   the menu name: it can be a sequence of strings, separated by '|' symbol.
207   For example, "File|Edit" means \c File->Edit submenu.
208   If submenu doesn't exist, it will be created.
209
210   \param a action
211   \param menus menu name
212   \param group group ID
213   \param idx menu index inside the menu group
214   \return action ID
215 */
216 int QtxActionMenuMgr::insert( QAction* a, const QString& menus, const int group, const int idx )
217 {
218   return insert( a, menus.split( "|", QString::SkipEmptyParts ), group, idx );
219 }
220
221 /*!
222   \brief Insert action to the menu.
223
224   Insert an action to the named menu. The \a menus parameter represents 
225   the menu names list.
226   For example, string list consisting from two items "File" and "Edit"
227   means \c File->Edit submenu.
228   If submenu doesn't exist, it will be created.
229
230   \param id action ID
231   \param menus menu names list
232   \param group group ID
233   \param idx menu index inside the menu group
234   \return action ID
235 */
236 int QtxActionMenuMgr::insert( const int id, const QStringList& menus, const int group, const int idx )
237 {
238   int pId = createMenu( menus, -1 );
239   if ( pId == -1 )
240     return -1;
241
242   return insert( id, pId, group, idx );
243 }
244
245 /*!
246   \brief Insert action to the menu.
247
248   Insert an action to the named menu. The \a menus parameter represents 
249   the menu names list.
250   For example, string list consisting from two items "File" and "Edit"
251   means \c File->Edit submenu.
252   If submenu doesn't exist, it will be created.
253
254   \param a action
255   \param menus menu names list
256   \param group group ID
257   \param idx menu index inside the menu group
258   \return action ID
259 */
260 int QtxActionMenuMgr::insert( QAction* a, const QStringList& menus, const int group, const int idx )
261 {
262   int pId = createMenu( menus, -1 );
263   if ( pId == -1 )
264     return -1;
265
266   return insert( a, pId, group, idx );
267 }
268
269 /*!
270   \brief Insert action to the menu.
271   \param id action ID
272   \param pId parent menu action ID
273   \param group group ID
274   \param idx menu index inside the menu group
275   \return action ID
276 */
277 int QtxActionMenuMgr::insert( const int id, const int pId, const int group, const int idx )
278 {
279   if ( id == -1 )
280     return -1;
281
282   MenuNode* pNode = pId == -1 ? myRoot : find( pId );
283   if ( !pNode )
284     return -1;
285
286   MenuNode* node = new MenuNode( pNode, id, idx, group );
287
288   triggerUpdate( pNode->id, false );
289
290   return node->id;
291 }
292
293 /*!
294   \brief Insert action to the menu.
295   \param a action
296   \param pId parent menu action ID
297   \param group group ID
298   \param idx menu index inside the menu group
299   \return action ID
300 */
301 int QtxActionMenuMgr::insert( QAction* a, const int pId, const int group, const int idx )
302 {
303   return insert( registerAction( a ), pId, group, idx );
304 }
305
306 /*!
307   \brief Create and insert menu item action to the menu.
308   \param title menu text
309   \param pId parent menu action ID
310   \param group group ID
311   \param id action ID
312   \param idx menu index inside the menu group
313   \return action ID
314 */
315 int QtxActionMenuMgr::insert( const QString& title, const int pId, const int group,
316                               const int id, const int idx,  QMenu* _menu)
317 {
318   MenuNode* pNode = pId == -1 ? myRoot : find( pId );
319   if ( !pNode )
320     return -1;
321
322   MenuNode* eNode = id == -1 ? 0 : find( id );
323
324   int fid = -1;
325   for ( NodeList::iterator it = pNode->children.begin(); it != pNode->children.end() && fid == -1; ++it )
326   {
327     if ( myMenus.contains( (*it)->id ) &&
328          clearTitle( myMenus[(*it)->id]->text() ) == clearTitle( title ) )
329       fid = (*it)->id;
330   }
331
332   if ( fid != -1 )
333     return fid;
334
335   int gid = (id == -1 || eNode ) ? generateId() : id;
336
337   QMenu* menu;
338   if (_menu)
339     menu = _menu;
340   else
341     menu = new QMenu( 0 );
342   QAction* ma = menu->menuAction();
343   ma->setText( title );
344
345   connect( ma->menu(), SIGNAL( aboutToShow() ), this, SLOT( onAboutToShow() ) );
346   connect( ma->menu(), SIGNAL( aboutToHide() ), this, SLOT( onAboutToHide() ) );
347
348   MenuNode* node = new MenuNode( pNode, myMenus.insert( gid, ma ).key(), idx, group );
349
350   triggerUpdate( pNode->id, false );
351
352   return node->id;
353 }
354
355 /*!
356   \brief Create and insert menu item action to the menu.
357
358   Insert an action to the named menu. The \a menus parameter represents 
359   the menu name: it can be a sequence of strings, separated by '|' symbol.
360   For example, "File|Edit" means \c File->Edit submenu.
361   If submenu doesn't exist, it will be created.
362
363   \param title menu text
364   \param menus menu name
365   \param group group ID
366   \param id action ID
367   \param idx menu index inside the menu group
368   \return action ID
369 */
370 int QtxActionMenuMgr::insert( const QString& title, const QString& menus, const int group, const int id, const int idx )
371 {
372   return insert( title, menus.split( "|", QString::SkipEmptyParts ), group, id, idx );
373 }
374
375 /*!
376   \brief Create and insert menu item action to the menu.
377
378   Insert an action to the named menu. The \a menus parameter represents 
379   the menu names list.
380   For example, string list consisting from two items "File" and "Edit"
381   means \c File->Edit submenu.
382   If submenu doesn't exist, it will be created.
383
384   \param title menu text
385   \param menus menu names list
386   \param group group ID
387   \param id action ID
388   \param idx menu index inside the menu group
389   \return action ID
390 */
391 int QtxActionMenuMgr::insert( const QString& title, const QStringList& menus, const int group, const int id, const int idx )
392 {
393   int pId = createMenu( menus, -1 );
394   return insert( title, pId, group, id, idx );
395 }
396
397 /*!
398   \brief Create and add menu item action to the end of menu.
399   \param title menu text
400   \param pId parent menu action ID
401   \param group group ID
402   \param id action ID
403   \return action ID
404 */
405 int QtxActionMenuMgr::append( const QString& title, const int pId, const int group, const int id )
406 {
407   return insert( title, pId, group, id );
408 }
409
410 /*!
411   \brief Create and add menu item action to the end of menu.
412   \param id action ID
413   \param pId parent menu action ID
414   \param group group ID
415   \return action ID
416 */
417 int QtxActionMenuMgr::append( const int id, const int pId, const int group )
418 {
419   return insert( id, pId, group );
420 }
421
422 /*!
423   \brief Create and add menu item action to the end of menu.
424   \param a action
425   \param pId parent menu action ID
426   \param group group ID
427   \return action ID
428 */
429 int QtxActionMenuMgr::append( QAction* a, const int pId, const int group )
430 {
431   return insert( a, pId, group );
432 }
433
434 /*!
435   \brief Create and add menu item action to the beginning of menu.
436   \param title menu text
437   \param pId parent menu action ID
438   \param group group ID
439   \param id action ID
440   \return action ID
441 */
442 int QtxActionMenuMgr::prepend( const QString& title, const int pId, const int group, const int id )
443 {
444   return insert( title, pId, group, id, 0 );
445 }
446
447 /*!
448   \brief Create and add menu item action to the beginning of menu.
449   \param id action ID
450   \param pId parent menu action ID
451   \param group group ID
452   \return action ID
453 */
454 int QtxActionMenuMgr::prepend( const int id, const int pId, const int group )
455 {
456   return insert( id, pId, group, 0 );
457 }
458
459 /*!
460   \brief Create and add menu item action to the beginning of menu.
461   \param a action
462   \param pId parent menu action ID
463   \param group group ID
464   \return action ID
465 */
466 int QtxActionMenuMgr::prepend( QAction* a, const int pId, const int group )
467 {
468   return insert( a, pId, group, 0 );
469 }
470
471 /*!
472   \brief Remove menu item with given \a id.
473   \param id menu action ID
474 */
475 void QtxActionMenuMgr::remove( const int id )
476 {
477   removeMenu( id, 0 );
478   update();
479 }
480
481 /*!
482   \brief Remove menu item with given \a id.
483   \param id menu action ID
484   \param pId parent menu action ID
485   \param group group ID
486 */
487 void QtxActionMenuMgr::remove( const int id, const int pId, const int group )
488 {
489   MenuNode* pNode = pId == -1 ? myRoot : find( pId );
490   if ( !pNode )
491     return;
492
493   NodeList delNodes;
494   for ( NodeList::iterator it = pNode->children.begin(); it != pNode->children.end(); ++it )
495   {
496     if ( (*it)->id == id && ( (*it)->group == group || group == -1 ) )
497       delNodes.append( *it );
498   }
499
500   QWidget* mW = menuWidget( pNode );
501   for ( NodeList::iterator itr = delNodes.begin(); itr != delNodes.end(); ++itr )
502   {
503     int id = (*itr)->id;
504     if( mW && menuAction( id ) )
505     {
506       mW->removeAction( menuAction( id ) );
507       myMenus.remove( id );
508     }
509     else if( mW && itemAction( id ) )
510       mW->removeAction( itemAction( id ) );
511     pNode->children.removeAll( *itr );
512   }
513
514   triggerUpdate( pNode->id, false );
515 }
516
517 /*!
518   \brief Show menu item with given \a id.
519   \param id menu action ID
520   \sa hide()
521 */
522 void QtxActionMenuMgr::show( const int id )
523 {
524   setShown( id, true );
525 }
526
527 /*!
528   \brief Hide menu item with given \a id.
529   \param id menu action ID
530   \sa show()
531 */
532 void QtxActionMenuMgr::hide( const int id )
533 {
534   setShown( id, false );
535 }
536
537 /*!
538   \brief Get visibility status for menu item with given \a id.
539   \param id menu action ID
540   \return \c true if an item is shown
541   \sa setShown()
542 */
543 bool QtxActionMenuMgr::isShown( const int id ) const
544 {
545   bool res = false;
546   MenuNode* node = find( id );
547   if ( node )
548     res = node->visible;
549   return res;
550 }
551
552 /*!
553   \brief Set visibility status for menu item with given \a id.
554   \param id menu action ID
555   \param on new visibility status
556   \sa isShown()
557 */
558 void QtxActionMenuMgr::setShown( const int id, const bool on )
559 {
560   NodeList aNodes;
561   find( id, aNodes );
562
563   for ( NodeList::iterator it = aNodes.begin(); it != aNodes.end(); ++it )
564   {
565     if ( (*it)->visible != on )
566     {
567       (*it)->visible = on;
568       triggerUpdate( (*it)->parent ? (*it)->parent->id : myRoot->id, false );
569     }
570   }
571 }
572
573 /*!
574   \brief Change menu title for the action with given \a id.
575   \param id menu action ID
576   \param title new menu title
577 */
578 void QtxActionMenuMgr::change( const int id, const QString& title )
579 {
580   QAction* a = menuAction( id );
581   if ( a )
582     a->setText( title );
583 }
584
585 /*!
586   \brief Called when the submenu is about to show.
587   
588   Emits the signal menuAboutToShow(QMenu*).
589 */
590 void QtxActionMenuMgr::onAboutToShow()
591 {
592   QMenu* m = ::qobject_cast<QMenu*>( sender() );
593   if ( m )
594     emit menuAboutToShow( m );
595 }
596
597 /*!
598   \brief Called when the submenu is about to hide.
599   
600   Emits the signal menuAboutToHide(QMenu*).
601 */
602 void QtxActionMenuMgr::onAboutToHide()
603 {
604   QMenu* m = ::qobject_cast<QMenu*>( sender() );
605   if ( m )
606     emit menuAboutToHide( m );
607 }
608
609 /*!
610   \brief Called when the corresponding menu object is destroyed.
611
612   Clears internal pointer to menu to disable crashes.
613   
614   \param obj (menu) object being destroyed
615 */
616 void QtxActionMenuMgr::onDestroyed( QObject* obj )
617 {
618   if ( myMenu == obj )
619     myMenu = 0;
620 }
621
622
623 /*!
624   \fn void QtxActionMenuMgr::menuAboutToShow( QMenu* m )
625   \brief Emitted when the menu is about to be shown.
626   \param m menu being shown
627 */
628
629 /*!
630   \fn void QtxActionMenuMgr::menuAboutToHide( QMenu* m )
631   \brief Emitted when the menu is about to be hidden.
632   \param m menu being hidden
633 */
634
635 /*!
636   \brief Get the menu widget.
637   \return menu widget (QMenuBar)
638 */
639 QWidget* QtxActionMenuMgr::menuWidget() const
640 {
641   return myMenu;
642 }
643
644 /*!
645   \brief Assign new menu widget to the menu manager.
646   \param mw new menu widget
647 */
648 void QtxActionMenuMgr::setMenuWidget( QWidget* mw )
649 {
650   if ( myMenu == mw )
651     return;
652
653   if ( myMenu )
654     disconnect( myMenu, SIGNAL( destroyed( QObject* ) ), this, SLOT( onDestroyed( QObject* ) ) );
655
656   myMenu = mw;
657
658   if ( myMenu )
659     connect( myMenu, SIGNAL( destroyed( QObject* ) ), this, SLOT( onDestroyed( QObject* ) ) );
660
661   triggerUpdate( -1, true );
662 }
663
664 /*!
665   \brief Search menu node.
666   \param id menu action ID
667   \param pId parent menu item ID
668   \param rec if \c true perform recursive search
669   \return menu node or 0 if it is not found
670 */
671 QtxActionMenuMgr::MenuNode* QtxActionMenuMgr::find( const int id, const int pId, const bool rec ) const
672 {
673   return find( id, find( pId ), rec );
674 }
675
676 /*!
677   \brief Search menu node.
678   \param id menu action ID
679   \param startNode start menu node (if 0, search from root node)
680   \param rec if \c true perform recursive search
681   \return menu node or 0 if it is not found
682 */
683 QtxActionMenuMgr::MenuNode* QtxActionMenuMgr::find( const int id, MenuNode* startNode, const bool rec ) const
684 {
685   MenuNode* node = 0;
686   MenuNode* start = startNode ? startNode : myRoot;
687   for ( NodeList::iterator it = start->children.begin(); it != start->children.end() && !node; ++it )
688   {
689     if ( (*it)->id == id )
690       node = *it;
691     else if ( rec )
692       node = find( id, *it, rec );
693   }
694   return node;
695 }
696
697 /*!
698   \brief Search recursively all menu nodes with given \a id.
699   \param id menu action ID
700   \param NodeList resulting list of menu nodes
701   \param startNode start menu node (if 0, search from root node)
702   \return \c true if at least one node is found
703 */
704 bool QtxActionMenuMgr::find( const int id, NodeList& lst, MenuNode* startNode ) const
705 {
706   MenuNode* start = startNode ? startNode : myRoot;
707   for ( NodeList::iterator it = start->children.begin(); it != start->children.end(); ++it )
708   {
709     MenuNode* node = *it;
710     if ( node->id == id )
711       lst.prepend( node );
712
713     find( id, lst, node );
714   }
715   return !lst.isEmpty();
716 }
717
718 /*!
719   \brief Search menu node.
720   \param title menu item title
721   \param pId parent menu item ID
722   \param rec if \c true perform recursive search
723   \return menu node or 0 if it is not found
724 */
725 QtxActionMenuMgr::MenuNode* QtxActionMenuMgr::find( const QString& title, const int pId, const bool rec ) const
726 {
727   return find( title, find( pId ), rec );
728 }
729
730 /*!
731   \brief Search recursively all menu nodes with given \a title.
732   \param title menu item title
733   \param NodeList resulting list of menu nodes
734   \param startNode start menu node (if 0, search from root node)
735   \return \c true if at least one node is found
736 */
737 bool QtxActionMenuMgr::find( const QString& title, NodeList& lst, MenuNode* startNode ) const
738 {
739   MenuNode* start = startNode ? startNode : myRoot;
740   for ( NodeList::iterator it = start->children.begin(); it != start->children.end(); ++it )
741   {
742     QAction* a = itemAction( (*it)->id );
743     if ( !a )
744       a = menuAction( (*it)->id );
745     if ( a && clearTitle( a->text() ) == clearTitle( title ) )
746       lst.prepend( *it );
747
748     find( title, lst, *it );
749   }
750   return !lst.isEmpty();
751 }
752
753 /*!
754   \brief Search menu node.
755   \param title menu item title
756   \param startNode start menu node (if 0, search from root node)
757   \param rec if \c true perform recursive search
758   \return menu node or 0 if it is not found
759 */
760 QtxActionMenuMgr::MenuNode* QtxActionMenuMgr::find( const QString& title, MenuNode* startNode, const bool rec ) const
761 {
762   MenuNode* node = 0;
763   MenuNode* start = startNode ? startNode : myRoot;
764   for ( NodeList::iterator it = start->children.begin(); it != start->children.end() && !node; ++it )
765   {
766     QAction* a = itemAction( (*it)->id );
767     if ( !a )
768       a = menuAction( (*it)->id );
769     if ( a && clearTitle( a->text() ) == clearTitle( title ) )
770       node = *it;
771     if ( !node && rec )
772       node = find( title, *it, rec );
773   }
774   return node;
775 }
776
777 /*!
778   \brief Find menu item by given ID (one-level only).
779   \param id menu action ID
780   \param pid parent meun item ID
781   \return id (>0) on success or -1 if menu item is not found
782 */
783 int QtxActionMenuMgr::findId( const int id, const int pid ) const
784 {
785   MenuNode* start = pid != -1 ? find( pid ) : myRoot;
786   if ( start )
787   {
788     for ( NodeList::iterator it = start->children.begin(); it != start->children.end(); ++it )
789     {
790       if ( (*it)->id == id )
791         return id;
792     }
793   }
794   return -1;
795 }
796
797 /*!
798   \brief Removes menu node (with all its children).
799   \param id menu action ID
800   \param startNode parent menu node which search starts from (if 0, search starts from root)
801 */
802 void QtxActionMenuMgr::removeMenu( const int id, MenuNode* startNode )
803 {
804   MenuNode* start = startNode ? startNode : myRoot;
805   for ( NodeList::iterator it = start->children.begin(); it != start->children.end(); ++it )
806   {
807     if ( (*it)->id == id )
808       start->children.removeAll( *it );
809     else
810       removeMenu( id, *it );
811   }
812 }
813
814 /*!
815   \brief Get action by \a id.
816   \param id action ID
817   \return action or 0 if \a id is invalid
818 */
819 QAction* QtxActionMenuMgr::itemAction( const int id ) const
820 {
821   return action( id );
822 }
823
824 /*!
825   \brief Get submenu action by \a id.
826   \param id submenu ID
827   \return submenu action or 0 if action is not found
828 */
829 QAction* QtxActionMenuMgr::menuAction( const int id ) const
830 {
831   QAction* a = 0;
832
833   if ( myMenus.contains( id ) )
834     a = myMenus[id];
835
836   return a;
837 }
838
839 /*!
840   \brief Get submenu action by \a id.
841   \param id submenu ID
842   \return submenu action or 0 if it is not found
843 */
844 int QtxActionMenuMgr::menuActionId( QAction* a ) const
845 {
846   int id = -1;
847   for ( MenuMap::ConstIterator itr = myMenus.begin(); itr != myMenus.end() && id == -1; ++itr )
848   {
849     if ( itr.value() == a )
850       id = itr.key();
851   }
852   return id;
853 }
854
855 /*!
856   \brief Update menu.
857
858   Does nothing if update is disabled.
859
860   \param startNode start menu item to be updated
861   \param rec if \c true, perform recursive update
862   \param updParent if \c true update also parent item (without recursion)
863
864   \sa isUpdatesEnabled() and setUpdatesEnabled()
865 */
866 void QtxActionMenuMgr::updateMenu( MenuNode* startNode, const bool rec, const bool updParent )
867 {
868   if ( !isUpdatesEnabled() )
869     return;
870
871   MenuNode* node = startNode ? startNode : myRoot;
872
873   QWidget* mw = menuWidget( node );
874   if ( !mw )
875     return;
876
877   // first remove all own actions and collect foreign ones
878   QMap< QAction*, QList<QAction*> > foreign;
879   QAction* a;
880   QAction* preva = 0;
881   QListIterator<QAction*> ait( mw->actions() ); ait.toBack();
882   while ( ait.hasPrevious() )
883   {
884     a = ait.previous();
885     if ( ownAction( a, node ) )
886     {
887       preva = a;
888       mw->removeAction( a );     // remove own actions
889     }
890     else
891     {
892       foreign[preva].prepend(a); // do not yet remove foreign actions
893     }
894   }
895   // now only foreign actions should stay in the menu, thus remove them also
896   QMap< QAction*, QList<QAction*> >::Iterator formapit;
897   for( formapit = foreign.begin(); formapit != foreign.end(); ++formapit )
898   {
899     QMutableListIterator<QAction*> foralit( formapit.value() );
900     while ( foralit.hasNext() )
901     {
902       a = foralit.next();
903       if ( !mw->actions().contains( a ) )
904         foralit.remove();
905     }
906   }
907   QList<QAction*> alist = mw->actions();
908   foreach( a, alist ) mw->removeAction( a );
909
910   // collect all registered menus by group id
911   QMap<int, NodeList> idMap;
912   for ( NodeList::iterator it2 = node->children.begin(); it2 != node->children.end(); ++it2 )
913   {
914     NodeList& lst = idMap[(*it2)->group];
915     int idx = (*it2)->idx;
916     if ( idx < 0 || idx >= (int)lst.count() )
917       lst.append( *it2 );
918     else
919       lst.insert( idx, *it2 );
920   }
921
922   QIntList groups = idMap.keys();
923   qSort( groups );
924
925   groups.removeAll( -1 );
926   groups.append( -1 );
927
928   // rebuild menu: 1. add all registered actions
929   for ( QIntList::const_iterator gIt = groups.begin(); gIt != groups.end(); ++gIt )
930   {
931     if ( !idMap.contains( *gIt ) )
932       continue;
933
934     const NodeList& lst = idMap[*gIt];
935     for ( NodeList::const_iterator iter = lst.begin(); iter != lst.end(); ++iter )
936     {
937       MenuNode* node = *iter;
938       if ( !node ) continue;
939
940       if ( rec )
941         updateMenu( node, rec, false );
942
943       MenuNode* par = node->parent;
944       if ( !isVisible( node->id, par ? par->id : -1 ) )
945         continue;
946
947       bool isMenu = false;
948       QAction* a = itemAction( node->id );
949       if ( !a )
950       {
951         isMenu = true;
952         a = menuAction( node->id );
953       }
954       if ( !a ) continue;
955
956       if ( !isMenu || !a->menu()->isEmpty() || node->emptyEnabled > 0 )
957         mw->addAction( a );
958     }
959   }
960
961   // rebuild menu: 2. insert back all foreign actions
962   for( formapit = foreign.begin(); formapit != foreign.end(); ++formapit ) {
963     preva = formapit.key();
964     foreach( a, formapit.value() )
965       mw->insertAction( preva, a );
966   }
967
968   // remove extra separators
969   simplifySeparators( mw );
970
971   // update parent menu if necessary
972   if ( updParent && node->parent ) {
973     updateMenu( node->parent, false );
974   }
975 }
976
977 /*!
978   \brief Internal update.
979   
980   Customizes the menu update processing.
981 */
982 void QtxActionMenuMgr::internalUpdate()
983 {
984   if ( !isUpdatesEnabled() )
985     return;
986
987   updateMenu();
988   myUpdateIds.clear();
989 }
990
991 /*!
992   \brief Check if action belongs to the menu manager
993   \internal
994   \param a action being checked
995   \param node parent menu node
996   \return \c true if action belongs to the menu \a node
997 */
998 bool QtxActionMenuMgr::ownAction( QAction* a, MenuNode* node ) const
999 {
1000   for ( NodeList::const_iterator iter = node->children.begin(); iter != node->children.end(); ++iter )
1001   {
1002     QAction* mya = itemAction( (*iter)->id );
1003     if ( !mya )
1004       mya = menuAction( (*iter)->id );
1005     if ( mya && mya == a )
1006       return true;
1007   }
1008   return false;
1009 }
1010
1011 /*!
1012   \brief Check if menu widget has any actions.
1013   \param wid widget to be checked
1014   \return \c true if widget contains action(s)
1015 */
1016 bool QtxActionMenuMgr::checkWidget( QWidget* wid ) const
1017 {
1018   if ( !wid )
1019     return false;
1020
1021   bool res = false;
1022   QList<QAction*> lst = wid->actions();
1023   for ( QList<QAction*>::const_iterator it = lst.begin(); it != lst.end() && !res; ++it ) {
1024     res = !(*it)->isSeparator() && (*it)->isVisible();
1025   }
1026   return res;
1027 }
1028
1029 /*!
1030   \brief Get menu widget for the given \a node.
1031   \param node menu node
1032   \return popup menu or main menu corresponding to the menu node
1033   (or 0 if it is not found)
1034 */
1035 QWidget* QtxActionMenuMgr::menuWidget( MenuNode* node ) const
1036 {
1037   if ( !node || node == myRoot )
1038      return myMenu;
1039
1040   if ( !myMenus.contains( node->id ) || !myMenus[node->id] )
1041     return 0;
1042
1043   return myMenus[node->id]->menu();
1044 }
1045
1046 /*!
1047   \brief Remove extra separators from menu widget.
1048   \param wid menu widget to be processed
1049 */
1050 void QtxActionMenuMgr::simplifySeparators( QWidget* wid )
1051 {
1052   Qtx::simplifySeparators( wid, false );
1053 }
1054
1055 /*!
1056   \brief Remove special symbols (&) from string to get clear menu title.
1057   \param txt string to be processed
1058   \return clear title
1059 */
1060 QString QtxActionMenuMgr::clearTitle( const QString& txt ) const
1061 {
1062   QString res = txt;
1063
1064   for ( int i = 0; i < (int)res.length(); i++ )
1065   {
1066     if ( res.at( i ) == '&' )
1067       res.remove( i--, 1 );
1068   }
1069
1070   return res;
1071 }
1072
1073 /*!
1074   \brief Create and inserts menu item recursively.
1075   \param lst list of menu names
1076   \param pId parent menu item ID
1077   \return created menu item ID (last in the chain)
1078 */
1079 int QtxActionMenuMgr::createMenu( const QStringList& lst, const int pId )
1080 {
1081   if ( lst.isEmpty() )
1082     return -1;
1083
1084   QStringList sl( lst );
1085
1086   QString title = sl.last().trimmed();
1087   sl.removeLast();
1088
1089   int parentId = sl.isEmpty() ? pId : createMenu( sl, pId );
1090
1091   return insert( title, parentId, -1 );
1092 }
1093
1094 /*!
1095   \brief Load actions description from the file.
1096   \param fname file name
1097   \param r action reader
1098   \return \c true on success and \c false on error
1099 */
1100 bool QtxActionMenuMgr::load( const QString& fname, QtxActionMgr::Reader& r )
1101 {
1102   MenuCreator cr( &r, this );
1103   return r.read( fname, cr );
1104 }
1105
1106 /*!
1107   \brief Check if the parent menu contains menu item with given \a title.
1108   \param title menu title
1109   \param pid parent menu item ID
1110   \return \c true if parent menu item contains such child item
1111 */
1112 bool QtxActionMenuMgr::containsMenu( const QString& title, const int pid, const bool rec ) const
1113 {
1114   return (bool)find( title, pid, rec );
1115 }
1116
1117 /*!
1118   \brief Check if the parent menu contains menu item with given \a id.
1119   \param id menu item ID
1120   \param pid parent menu item ID
1121   \return \c true if parent menu item contains such child item
1122 */
1123 bool QtxActionMenuMgr::containsMenu( const int id, const int pid, const bool rec ) const
1124 {
1125   return (bool)find( id, pid, rec );
1126 }
1127
1128 /*!
1129   \brief Get menu by the specified identifier.
1130   \param id menu item ID
1131   \return menu pointer or 0 if menu is not found
1132 */
1133 QMenu* QtxActionMenuMgr::findMenu( const int id ) const
1134 {
1135   QMenu* m = 0;
1136   QAction* a = menuAction( id );
1137   if ( a )
1138     m = a->menu();
1139   return m;
1140 }
1141
1142 /*!
1143   \brief Get menu by the title.
1144   \param title menu text
1145   \param pid parent menu item ID (to start search)
1146   \param rec if \c true, perform recursive update
1147   \return menu pointer or 0 if menu is not found
1148 */
1149 QMenu* QtxActionMenuMgr::findMenu( const QString& title, const int pid, const bool rec ) const
1150 {
1151   QMenu* m = 0;
1152   MenuNode* node = find( title, pid, rec );
1153   if ( node )
1154   {
1155     QAction* a = menuAction( node->id );
1156     if ( a )
1157       m = a->menu();
1158   }
1159   return m;
1160 }
1161
1162 /*!
1163   \brief Check if empty menu is enabled
1164   \param id menu item ID
1165   \return \c true if empty menu is enabled
1166 */
1167 bool QtxActionMenuMgr::isEmptyEnabled( const int id ) const
1168 {
1169   MenuNode* node = find( id );
1170   if ( node && menuAction( id ) )
1171     return node->emptyEnabled > 0;
1172   
1173   return false;
1174 }
1175
1176 /*!
1177   \brief Enable/disable empty menu
1178   \param id menu item ID
1179   \param enable if \c true, empty menu will be enabled, otherwise empty menu will be disabled
1180 */
1181 void QtxActionMenuMgr::setEmptyEnabled( const int id, const bool enable )
1182 {
1183   MenuNode* node = find( id );
1184   if ( node && menuAction( id ) ) {
1185     int old = node->emptyEnabled;
1186     node->emptyEnabled += enable ? 1 : -1;
1187     if ( ( old <= 0 && enable ) || ( old > 0 && !enable ) ) // update menu only if enabled state has been changed
1188       updateMenu( node, true, true );
1189   }
1190 }
1191
1192 /*!
1193   \brief Perform delayed menu update.
1194   \param id menu item ID
1195   \param rec if \c true, perform recursive update
1196 */
1197 void QtxActionMenuMgr::triggerUpdate( const int id, const bool rec )
1198 {
1199   bool isRec = rec;
1200   if ( myUpdateIds.contains( id ) )
1201     isRec = isRec || myUpdateIds[ id ];
1202   myUpdateIds.insert( id, isRec );
1203
1204   QtxActionMgr::triggerUpdate();
1205 }
1206
1207 /*!
1208   \brief Called when action is changed.
1209
1210   Schedule delayed update for parent menu of changed action.
1211 */
1212 void QtxActionMenuMgr::actionChanged( int id )
1213 {
1214   NodeList aNodes;
1215   find( id, aNodes );
1216
1217   for ( NodeList::iterator it = aNodes.begin(); it != aNodes.end(); ++it )
1218   {
1219     MenuNode* node = *it;
1220     if ( node->visible ) {
1221       triggerUpdate( node->parent ? node->parent->id : myRoot->id, false );
1222     }
1223   }
1224 }
1225
1226 /*!
1227   \brief Called when delayed content update is performed.
1228
1229   Customizes the content update operation.
1230 */
1231 void QtxActionMenuMgr::updateContent()
1232 {
1233   // Warning: For correct updating it is necessary to update the most enclosed submenu in first turn
1234   //          because not updated empty submenu will be skipped. Now the submenus are iterated in
1235   //          ascending order according to their identifiers. For a submenus with automatically generated 
1236   //          identifiers this will work correctly since the uppermost submenus have the biggest number
1237   //          (identifiers are generated by decrementing 1 starting from -1). In general, if any submenu
1238   //          have positive identifiers this method might not work correctly. In this case it would be
1239   //          necessary to improve this method and to add preliminary sorting a submenus by depth of an 
1240   //          enclosure.
1241   for ( QMap<int, bool>::const_iterator it = myUpdateIds.constBegin(); it != myUpdateIds.constEnd(); ++it )
1242   {
1243     MenuNode* node = it.key() == -1 ? myRoot : find( it.key() );
1244     if ( node )
1245       updateMenu( node, it.value() );
1246   }
1247   myUpdateIds.clear();
1248 }
1249
1250 /*!
1251   \class QtxActionMenuMgr::MenuCreator
1252   \brief Menu actions creator.
1253
1254   Used by Reader to create actions by reading descriptions from the file
1255   and fill in the action manager with the actions.
1256 */
1257
1258 /*!
1259   \brief Constructor.
1260   \param r menu actions reader
1261   \param mgr menu manager
1262 */
1263 QtxActionMenuMgr::MenuCreator::MenuCreator( QtxActionMgr::Reader* r, QtxActionMenuMgr* mgr )
1264 : QtxActionMgr::Creator( r ),
1265   myMgr( mgr )
1266 {
1267 }
1268
1269 /*!
1270   \brief Destructor.
1271 */
1272 QtxActionMenuMgr::MenuCreator::~MenuCreator()
1273 {
1274 }
1275
1276 /*!
1277   \brief Create and append to the action manager a new action.
1278   \param tag item tag name
1279   \param subMenu \c true if this item is submenu
1280   \param attr attributes map
1281   \param pId parent action ID
1282   \return menu action ID
1283 */
1284 int QtxActionMenuMgr::MenuCreator::append( const QString& tag, const bool subMenu,
1285                                            const ItemAttributes& attr, const int pId )
1286 {
1287   if( !myMgr || !reader() )
1288     return -1;
1289
1290   QString label   = reader()->option( "label",     "label"     ),
1291           id      = reader()->option( "id",        "id"        ),
1292           pos     = reader()->option( "pos",       "pos"       ),
1293           group   = reader()->option( "group",     "group"     ),
1294           tooltip = reader()->option( "tooltip",   "tooltip"   ),
1295           sep     = reader()->option( "separator", "separator" ),
1296           accel   = reader()->option( "accel",     "accel"     ),
1297           icon    = reader()->option( "icon",      "icon"      ),
1298           toggle  = reader()->option( "toggle",    "toggle"    );
1299
1300   int res = -1, actId = intValue( attr, id, -1 );
1301
1302   if( subMenu )
1303     res = myMgr->insert( strValue( attr, label ), pId, intValue( attr, group, 0 ), intValue( attr, pos, -1 ) );
1304   else if( tag==sep )
1305     res = myMgr->insert( separator(), pId, intValue( attr, group, 0 ), intValue( attr, pos, -1 ) );
1306   else
1307   {
1308     QIcon set;
1309     QPixmap pix;
1310     QString name = strValue( attr, icon );
1311     if( !name.isEmpty() && loadPixmap( name, pix ) )
1312       set = QIcon( pix );
1313
1314     QtxAction* newAct = new QtxAction( strValue( attr, tooltip ), set,
1315                                        strValue( attr, label ),
1316                                        QKeySequence( strValue( attr, accel ) ),
1317                                        myMgr );
1318     newAct->setToolTip( strValue( attr, tooltip ) );
1319     QString toggleact = strValue( attr, toggle );
1320     newAct->setCheckable( !toggleact.isEmpty() );
1321     newAct->setChecked( toggleact.toLower() == "true" );
1322
1323     connect( newAct );
1324     int aid = myMgr->registerAction( newAct, actId );
1325     res = myMgr->insert( aid, pId, intValue( attr, group, 0 ), intValue( attr, pos, -1 ) );
1326   }
1327
1328   return res;
1329 }