]> SALOME platform Git repositories - modules/gui.git/commitdiff
Salome HOME
New functionality: Collapsed/Expanded menus.
authorsan <san@opencascade.com>
Mon, 18 Jul 2011 13:17:52 +0000 (13:17 +0000)
committersan <san@opencascade.com>
Mon, 18 Jul 2011 13:17:52 +0000 (13:17 +0000)
src/Qtx/QtxActionMenuMgr.cxx
src/Qtx/QtxActionMenuMgr.h
src/Qtx/QtxMenu.cxx
src/Qtx/QtxMenu.h

index 94368536e86a25f684f2e5d2ea2e0625490529ed..0a3945632654249fa5405421c92cf795108eea5b 100644 (file)
@@ -21,9 +21,9 @@
 
 #include "QtxActionMenuMgr.h"
 
+#include "QtxMenu.h"
 #include "QtxAction.h"
 
-#include <QMenu>
 #include <QMenuBar>
 #include <QWidget>
 #include <QMainWindow>
@@ -111,7 +111,8 @@ QtxActionMenuMgr::MenuNode::~MenuNode()
 QtxActionMenuMgr::QtxActionMenuMgr( QMainWindow* p )
 : QtxActionMgr( p ), 
   myRoot( new MenuNode() ),
-  myMenu( p ? p->menuBar() : 0 )
+  myMenu( p ? p->menuBar() : 0 ),
+  myCollapse( false )
 {
   if ( myMenu ) {
     connect( myMenu, SIGNAL( destroyed( QObject* ) ), this, SLOT( onDestroyed( QObject* ) ) );
@@ -126,7 +127,8 @@ QtxActionMenuMgr::QtxActionMenuMgr( QMainWindow* p )
 QtxActionMenuMgr::QtxActionMenuMgr( QWidget* mw, QObject* p )
 : QtxActionMgr( p ), 
   myRoot( new MenuNode() ),
-  myMenu( mw )
+  myMenu( mw ),
+  myCollapse( false )
 {
   if ( myMenu ) {
     connect( myMenu, SIGNAL( destroyed( QObject* ) ), this, SLOT( onDestroyed( QObject* ) ) );
@@ -329,9 +331,10 @@ int QtxActionMenuMgr::insert( const QString& title, const int pId, const int gro
 
   int gid = (id == -1 || eNode ) ? generateId() : id;
 
-  QMenu* menu = new QMenu( 0 );
+  QtxMenu* menu = new QtxMenu( 0 );
   QAction* ma = menu->menuAction();
   ma->setText( title );
+  menu->setMenuCollapsible( myCollapse );
 
   connect( ma->menu(), SIGNAL( aboutToShow() ), this, SLOT( onAboutToShow() ) );
   connect( ma->menu(), SIGNAL( aboutToHide() ), this, SLOT( onAboutToHide() ) );
@@ -1163,6 +1166,34 @@ void QtxActionMenuMgr::setEmptyEnabled( const int id, const bool enable )
   }
 }
 
+/*!
+  \brief Check is top level menus are collapsible
+  \return \c true if menus are collapsible
+*/
+bool QtxActionMenuMgr::menuCollapsible() const
+{
+  return myCollapse;
+}
+
+/*!
+  \brief Enable/disable collapsible menus
+  \param enable if \c true, menus are collapsible, otherwise menus always be full
+*/
+void QtxActionMenuMgr::setMenuCollapsible( bool enable )
+{
+  if ( myCollapse == enable )
+    return;
+
+  myCollapse = enable;
+
+  for ( MenuMap::iterator it = myMenus.begin(); it != myMenus.end(); ++it )
+  {
+    QtxMenu* m = ::qobject_cast<QtxMenu*>( (*it)->menu() );
+    if ( m )
+      m->setMenuCollapsible( myCollapse );
+  }
+}
+
 /*!
   \brief Perform delayed menu update.
   \param id menu item ID
index 6ee27cf6100cb579579537728edaa09517ed3f75..ff7c47c7d42cb53bbc4e4c85013ed5d72055cfd7 100644 (file)
@@ -99,6 +99,9 @@ public:
   bool         isEmptyEnabled( const int ) const;
   void         setEmptyEnabled( const int, const bool );
 
+  bool         menuCollapsible() const;
+  void         setMenuCollapsible( bool );
+
 private slots:
   void         onAboutToShow();
   void         onAboutToHide();
@@ -146,6 +149,7 @@ private:
   MenuNode*       myRoot;        //!< root menu node
   QWidget*        myMenu;        //!< menu widget
   MenuMap         myMenus;       //!< actions map
+  bool            myCollapse;
   QMap<int, bool> myUpdateIds;   //!< list of actions ID being updated
 };
 
index 418e5e61cb04b9ab789773f26e94b77d3f3bf7d6..e87e82ea737a8e9619bf40998ad0894912b0c5b1 100644 (file)
@@ -1,17 +1,17 @@
 // Copyright (C) 2005  OPEN CASCADE, CEA/DEN, EDF R&D, PRINCIPIA R&D
-// 
+//
 // This library is free software; you can redistribute it and/or
 // modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either 
+// License as published by the Free Software Foundation; either
 // version 2.1 of the License.
-// 
-// This library is distributed in the hope that it will be useful 
-// but WITHOUT ANY WARRANTY; without even the implied warranty of 
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
+//
+// This library is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 // Lesser General Public License for more details.
 //
-// You should have received a copy of the GNU Lesser General Public  
-// License along with this library; if not, write to the Free Software 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 //
 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
 
 #include "QtxMenu.h"
 
+#include <QSet>
 #include <QLabel>
+#include <QTimer>
 #include <QLayout>
+#include <QBitmap>
+#include <QPixmap>
+#include <QMenuBar>
 #include <QPainter>
 #include <QPaintEvent>
+#include <QToolButton>
 #include <QTextDocument>
 #include <QWidgetAction>
 #include <QLinearGradient>
 #include <QAbstractTextDocumentLayout>
 
+static const char* expand_button_xpm[] = {
+/* width height num_colors chars_per_pixel */
+"    18    18       16            1",
+/* colors */
+"` c none",
+". c #f4f4f4",
+"# c #f1f1f1",
+"a c #f6f6f6",
+"b c #f0f0f0",
+"c c #ededed",
+"d c #ebebeb",
+"e c #eaeaea",
+"f c #000000",
+"g c #e8e8e8",
+"h c #e7e7e7",
+"i c #e5e5e5",
+"j c #e4e4e4",
+"k c #e2e2e2",
+"l c #e1e1e1",
+"m c #000000",
+/* pixels */
+"``````````````````",
+"``````..####``````",
+"````.aa..#bcc#````",
+"```.aaa..#bcdeb```",
+"``.aaaa..#bcdee#``",
+"``.aaaf.##fcdegg``",
+"``....ff#ffcdegh``",
+"`#.....fffcdegghe`",
+"`b#####bfcddeghhh`",
+"`bbbbbfccdfegghii`",
+"`bccccffdffeghijg`",
+"`#dddddfffghiijjd`",
+"``deeeeefhiijjkk``",
+"``#ggghhiijjkkle``",
+"```ciiijjkkklli```",
+"````djjkkklllh````",
+"``````gjklli``````",
+"``````````````````"
+};
+
+ QtxMenu::PriorityMap QtxMenu::_actionPriority;
+
 /*!
   \class QtxMenu::Title
   \brief Popup menu title item.
 class QtxMenu::Title : public QWidget
 {
 public:
-  Title( QWidget* = 0 );
+  Title( QtxMenu*, QWidget* = 0 );
   virtual ~Title();
 
   QIcon            icon() const;
-  void             setIcon( const QIcon& );
-
   QString          text() const;
-  void             setText( const QString& );
-
+  QtxMenu*         menu() const;
   Qt::Alignment    alignment() const;
-  void             setAlignment( const Qt::Alignment );
+
+  QTextDocument&   textDocument() const;
 
   virtual QSize    sizeHint() const;
   virtual QSize    minimumSizeHint() const;
@@ -59,9 +106,7 @@ protected:
   virtual void     paintEvent( QPaintEvent* );
 
 private:
-  QIcon            myIcon;
-  QString          myText;
-  Qt::Alignment    myAlignment;
+  QtxMenu*         myMenu;
 };
 
 /*!
@@ -69,9 +114,9 @@ private:
   \param parent parent widget
   \internal
 */
-QtxMenu::Title::Title( QWidget* parent )
+QtxMenu::Title::Title( QtxMenu* menu, QWidget* parent )
 : QWidget( parent ),
-  myAlignment( 0 )
+myMenu( menu )
 {
 }
 
@@ -84,23 +129,26 @@ QtxMenu::Title::~Title()
 }
 
 /*!
-  \brief Get title icon.
-  \return title item icon
+  \brief Get the menu which manage this title.
+  \return menu object
   \internal
 */
-QIcon QtxMenu::Title::icon() const
+QtxMenu* QtxMenu::Title::menu() const
 {
-  return myIcon;
+  return myMenu;
 }
 
 /*!
-  \brief Set title icon.
-  \param ico title item icon
+  \brief Get title menu icon.
+  \return menu icon for the title item
   \internal
 */
-void QtxMenu::Title::setIcon( const QIcon& ico )
+QIcon QtxMenu::Title::icon() const
 {
-  myIcon = ico;
+  QIcon ico;
+  if ( menu() )
+    ico = menu()->icon();
+  return ico;
 }
 
 /*!
@@ -110,17 +158,10 @@ void QtxMenu::Title::setIcon( const QIcon& ico )
 */
 QString QtxMenu::Title::text() const
 {
-  return myText;
-}
-
-/*!
-  \brief Set title menu text.
-  \param txt menu text to be used for the title item
-  \internal
-*/
-void QtxMenu::Title::setText( const QString& txt )
-{
-  myText = txt;
+  QString txt;
+  if ( menu() )
+    txt = menu()->title();
+  return txt;
 }
 
 /*!
@@ -130,17 +171,25 @@ void QtxMenu::Title::setText( const QString& txt )
 */
 Qt::Alignment QtxMenu::Title::alignment() const
 {
-  return myAlignment;
+  Qt::Alignment align;
+  if ( menu() )
+    align = menu()->titleAlignment();
+  return align;
 }
 
 /*!
-  \brief Set title alignment flags.
-  \param a title alignment flags
+  \brief Returns the prepared text document engine.
+  \return text document reference
   \internal
 */
-void QtxMenu::Title::setAlignment( const Qt::Alignment a )
+QTextDocument& QtxMenu::Title::textDocument() const
 {
-  myAlignment = a;
+  static QTextDocument _doc;
+  _doc.setHtml( text() );
+  QFont f = font();
+  f.setBold( true );
+  _doc.setDefaultFont( f );
+  return _doc;
 }
 
 /*!
@@ -150,9 +199,8 @@ void QtxMenu::Title::setAlignment( const Qt::Alignment a )
 */
 QSize QtxMenu::Title::sizeHint() const
 {
-  int m = 5;
-  QTextDocument doc;
-  doc.setHtml( text() );
+  int m = 3;
+  QTextDocument& doc = textDocument();
 
   QSize sz = icon().isNull() ? QSize( 0, 0 ) : icon().actualSize( QSize( 16, 16 ) );
   sz.setWidth( 2 * m + sz.width() + (int)doc.size().width() );
@@ -175,7 +223,7 @@ QSize QtxMenu::Title::minimumSizeHint() const
   \param e paint event (not used)
   \internal
 */
-void QtxMenu::Title::paintEvent( QPaintEvent* /*e*/ )
+void QtxMenu::Title::paintEvent( QPaintEvent* )
 {
   int m = 5;
   QIcon ico = icon();
@@ -188,8 +236,7 @@ void QtxMenu::Title::paintEvent( QPaintEvent* /*e*/ )
   base.setRight( base.right() -1 );
   base.setBottom( base.bottom() - 1 );
 
-  QTextDocument doc;
-  doc.setHtml( txt );
+  QTextDocument& doc = textDocument();
 
   QSize isz = ico.isNull() ? QSize( 0, 0 ) : ico.actualSize( QSize( 16, 16 ) );
   QSize sz( (int)doc.size().width(), (int)doc.size().height() );
@@ -231,6 +278,157 @@ void QtxMenu::Title::paintEvent( QPaintEvent* /*e*/ )
   p.restore();
 }
 
+/*!
+  \class QtxMenu::TitleMgr
+  \brief Widget action for placing title widget into popup.
+  \internal
+*/
+
+class QtxMenu::TitleMgr : public QWidgetAction
+{
+public:
+  TitleMgr( QtxMenu* );
+  virtual ~TitleMgr();
+
+protected:
+  virtual QWidget* createWidget( QWidget* );
+  virtual void     deleteWidget( QWidget* );
+
+private:
+  QtxMenu* myMenu;
+};
+
+/*!
+  \brief Constructor.
+  \internal
+*/
+QtxMenu::TitleMgr::TitleMgr( QtxMenu* parent )
+: QWidgetAction( parent ),
+  myMenu( parent )
+{
+}
+
+/*!
+  \brief Destructor.
+  \internal
+*/
+QtxMenu::TitleMgr::~TitleMgr()
+{
+}
+
+/*!
+  \brief Creates the menu title widget when action added into menu.
+  \internal
+*/
+QWidget* QtxMenu::TitleMgr::createWidget( QWidget* parent )
+{
+  return new Title( myMenu, parent );
+}
+
+/*!
+  \brief Deletes the menu title widget when action removed from menu.
+  \internal
+*/
+void QtxMenu::TitleMgr::deleteWidget( QWidget* w )
+{
+  delete w;
+}
+
+/*!
+  \class QtxMenu::Expander
+  \brief Widget action which represent expand button into popup menu.
+  \internal
+*/
+class QtxMenu::Expander : public QWidgetAction
+{
+public:
+  Expander( QWidget* = 0 );
+  virtual ~Expander();
+
+protected:
+  virtual QWidget* createWidget( QWidget* );
+  virtual void     deleteWidget( QWidget* );
+};
+
+/*!
+  \brief Constructor.
+  \internal
+*/
+QtxMenu::Expander::Expander( QWidget* parent )
+: QWidgetAction( parent )
+{
+  setText( tr( "Expand" ) );
+  setToolTip( tr( "Expand" ) );
+
+  setEnabled( false );
+}
+
+/*!
+  \brief Destructor.
+  \internal
+*/
+QtxMenu::Expander::~Expander()
+{
+}
+
+/*!
+  \brief Creates the expanding button when action added into menu.
+  \internal
+*/
+QWidget* QtxMenu::Expander::createWidget( QWidget* parent )
+{
+  class Button : public QToolButton
+  {
+  public:
+    Button( QWidget* p = 0 ) : QToolButton( p ) {};
+    virtual ~Button() {};
+
+  protected:
+    virtual void resizeEvent( QResizeEvent* e )
+    {
+      QToolButton::resizeEvent( e );
+
+      QPixmap pix( size() );
+      QPainter p( &pix );
+      icon().paint( &p, rect() );
+      p.end();
+
+      if ( pix.mask().isNull() )
+      {
+        QBitmap bm;
+        QImage img = pix.toImage();
+             if ( img.hasAlphaChannel() )
+          bm = QPixmap::fromImage( img.createAlphaMask() );
+             else
+          bm = QPixmap::fromImage( img.createHeuristicMask() );
+
+             pix.setMask( bm );
+           }
+
+      if ( !pix.mask().isNull() )
+             setMask( pix.mask() );
+    };
+  };
+
+  QToolButton* tb = new Button( parent );
+  QPixmap pix( expand_button_xpm );
+  tb->setIcon( pix );
+  tb->setAutoRaise( true );
+
+  connect( tb, SIGNAL( clicked( bool ) ), this, SIGNAL( triggered( bool ) ) );
+
+  return tb;
+}
+
+/*!
+  \brief Deletes the expanding button when action removed from menu.
+  \internal
+*/
+void QtxMenu::Expander::deleteWidget( QWidget* w )
+{
+  delete w;
+}
+
 /*!
   \class QtxMenu
   \brief The class QtxMenu represents the popup menu with the title.
@@ -245,7 +443,7 @@ void QtxMenu::Title::paintEvent( QPaintEvent* /*e*/ )
 
   By default, QtxMenu::TitleAuto mode is used. In this mode, the title item
   is shown only if it is not empty. To show title always (even empty), pass
-  QtxMenu::TitleOn to the setTitleMode() method. To hide the title, use 
+  QtxMenu::TitleOn to the setTitleMode() method. To hide the title, use
   setTitleMode() method with QtxMenu::TitleOff parameter.
 */
 
@@ -255,11 +453,29 @@ void QtxMenu::Title::paintEvent( QPaintEvent* /*e*/ )
 */
 QtxMenu::QtxMenu( QWidget* parent )
 : QMenu( parent ),
-  myMode( TitleAuto )
+  myTitleMode( TitleOff ),
+  myTitleAlign( Qt::AlignVCenter | Qt::AlignLeft ),
+  myLimit( 7 ),
+  myLimitMode( LimitTotal ),
+  myExpandAction( 0 )
 {
-  myTitle = new Title( this );
-  myAction = new QWidgetAction( this );
-  myAction->setDefaultWidget( myTitle );
+  myTitleAction = new TitleMgr( this );
+
+  myShortTimer = new QTimer( this );
+  myShortTimer->setSingleShot( true );
+  myShortTimer->setInterval( 1000 );
+
+  myExpandTimer = new QTimer( this );
+  myExpandTimer->setSingleShot( true );
+  myExpandTimer->setInterval( 5000 );
+
+  connect( menuAction(), SIGNAL( changed() ), this, SLOT( onMenuActionChanged() ) );
+
+  connect( myShortTimer, SIGNAL( timeout() ), this, SLOT( onExpandMenu() ) );
+  connect( myExpandTimer, SIGNAL( timeout() ), this, SLOT( onExpandMenu() ) );
+
+  connect( this, SIGNAL( hovered( QAction* ) ), this, SLOT( onActionHovered( QAction* ) ) );
+  connect( this, SIGNAL( triggered( QAction* ) ), this, SLOT( onActionTriggered( QAction* ) ) );
 }
 
 /*!
@@ -270,92 +486,184 @@ QtxMenu::~QtxMenu()
 }
 
 /*!
-  \brief Get title menu text.
-  \return menu text for the title item
+  \brief Get title item display mode.
+  \return popup menu title display mode (QtxMenu::TitleMode)
 */
-QString QtxMenu::titleText() const
+QtxMenu::TitleMode QtxMenu::titleMode() const
 {
-  return myTitle->text();
+  return myTitleMode;
 }
 
 /*!
-  \brief Get title icon.
-  \return title item icon
+  \brief Get title alignment flags.
+  \return title alignment flags
 */
-QIcon QtxMenu::titleIcon() const
+Qt::Alignment QtxMenu::titleAlignment() const
 {
-  return myTitle->icon();
+  return myTitleAlign;
 }
 
 /*!
-  \brief Get title item display mode.
-  \return popup menu title display mode (QtxMenu::TitleMode)
+  \brief Set title item display mode.
+  \param m popup menu title display mode (QtxMenu::TitleMode)
 */
-QtxMenu::TitleMode QtxMenu::titleMode() const
+void QtxMenu::setTitleMode( const QtxMenu::TitleMode m )
+{
+  if ( myTitleMode == m )
+    return;
+
+  myTitleMode = m;
+
+  updateTitle();
+}
+
+/*!
+  \brief Set title alignment flags.
+  \param a title alignment flags
+*/
+void QtxMenu::setTitleAlignment( const Qt::Alignment a )
 {
-  return myMode;
+  if ( titleAlignment() == a )
+    return;
+
+  myTitleAlign = a;
+
+  updateTitle();
 }
 
 /*!
-  \brief Get title alignment flags.
-  \return title alignment flags
+  \brief Returns 'true' if the menu should be collapsed when displayed.
 */
-Qt::Alignment QtxMenu::titleAlignment() const
+bool QtxMenu::menuCollapsible() const
 {
-  return myTitle->alignment();
+  return myExpandAction;
 }
 
 /*!
-  \brief Set title menu text.
-  \param txt menu text to be used for the title item
+  \brief Sets the menu collapsible property.
 */
-void QtxMenu::setTitleText( const QString& txt )
+void QtxMenu::setMenuCollapsible( bool on )
 {
-  if ( titleText() == txt )
+  if ( menuCollapsible() == on )
     return;
 
-  myTitle->setText( txt );
+  if ( on )
+    myExpandAction = new Expander( this );
+  else
+  {
+    if ( isMenuCollapsed() )
+      expandMenu();
 
-  updateTitle();
+    delete myExpandAction;
+    myExpandAction = 0;
+  }
 }
 
 /*!
-  \brief Set title icon.
-  \param ico title item icon
+  \brief Returns the delay in milliseconds for automatic menu expanding.
+         If delay is zero then menu doesn't perform automatic expanding.
 */
-void QtxMenu::setTitleIcon( const QIcon& ico )
+int QtxMenu::expandingDelay() const
 {
-  myTitle->setIcon( ico );
+  return myExpandTimer->interval();
+}
 
-  updateTitle();
+/*!
+  \brief Sets the automatic menu expanding delay in milliseconds.
+         If delay is zero then menu doesn't perform automatic expanding.
+*/
+void QtxMenu::setExpandingDelay( int msec )
+{
+  myExpandTimer->setInterval( msec );
+  if ( msec == 0 )
+  {
+    myShortTimer->stop();
+    myExpandTimer->stop();
+  }
 }
 
 /*!
-  \brief Set title item display mode.
-  \param m popup menu title display mode (QtxMenu::TitleMode)
+  \brief Returns number of visible menu items in collapsed state.
 */
-void QtxMenu::setTitleMode( const QtxMenu::TitleMode m )
+int QtxMenu::collapseLimit() const
 {
-  if ( myMode == m )
-    return;
+  return myLimit;
+}
 
-  myMode = m;
+/*!
+  \brief Sets number of visible menu items in collapsed state.
+  \param num - number of visible items.
+*/
+void QtxMenu::setCollapseLimit( int num )
+{
+  myLimit = num;
+}
 
-  updateTitle();
+/*!
+  \brief Returns collapse limit mode.
+*/
+QtxMenu::CollapseLimitMode QtxMenu::collapseLimitMode() const
+{
+  return myLimitMode;
 }
 
 /*!
-  \brief Set title alignment flags.
-  \param a title alignment flags
+  \brief Sets collapse limit mode. If mode is 'LimitFrequent' then parameter 'collapse limit'
+         will be applied to most frequent items only. If mode is 'LimitTotal' then parameter
+         'collapse limit' will be limit total quantity of menu items (permanent and most frequent).
+  \param mode - setted collapse limit mode.
 */
-void QtxMenu::setTitleAlignment( const Qt::Alignment a )
+void QtxMenu::setCollapseLimitMode( const QtxMenu::CollapseLimitMode mode )
 {
-  if ( titleAlignment() == a )
-    return;
+  myLimitMode = mode;
+}
 
-  myTitle->setAlignment( a );
+/*!
+  \brief Returns 'true' if the menu in expanded (full) state.
+*/
+bool QtxMenu::isMenuExpanded() const
+{
+  return !isMenuCollapsed();
+}
 
-  updateTitle();
+/*!
+  \brief Returns 'true' if the menu in collapsed state.
+*/
+bool QtxMenu::isMenuCollapsed() const
+{
+  return myExpandAction && myExpandAction->isVisible()
+      && actions().contains( myExpandAction );
+}
+
+/*!
+  \brief Returns the priority for specified action. Priority of the action
+         will be automatically increased by 1 during action activation.
+  \param a - action.
+*/
+int QtxMenu::actionPriority( QAction* a )
+{
+  int p = 0;
+  if ( _actionPriority.contains( a ) )
+    p = _actionPriority[a];
+
+  if ( a->menu() )
+  {
+    QList<QAction*> lst = a->menu()->actions();
+    for ( QList<QAction*>::iterator it = lst.begin(); it != lst.end(); ++it )
+      p = qMax( p, _actionPriority.contains( *it ) ? _actionPriority[*it] : 0 );
+  }
+  return p;
+}
+
+/*!
+  \brief Sets the priority for specified action. Action with negative value of priority
+         will be always displayed in menu (permanent actions).
+  \param a - action.
+*/
+void QtxMenu::setActionPriority( QAction* a, int p )
+{
+  if ( a )
+    _actionPriority.insert( a, p );
 }
 
 /*!
@@ -365,12 +673,61 @@ void QtxMenu::setTitleAlignment( const Qt::Alignment a )
 void QtxMenu::setVisible( bool on )
 {
   if ( on )
+  {
+    if ( menuCollapsible() && isTopLevelMenu() )
+    {
+      if ( isMenuExpanded() )
+        collapseMenu();
+      else
+      {
+        updateExpander();
+        if ( expandingDelay() > 0 )
+          myExpandTimer->start();
+      }
+    }
+
     insertTitle();
+  }
 
   QMenu::setVisible( on );
 
   if ( !on )
-    removeTitle();
+  {
+    if ( !isTearOffMenuVisible() )
+    {
+      removeTitle();
+      expandMenu();
+    }
+
+    myShortTimer->stop();
+    myExpandTimer->stop();
+  }
+}
+
+/*!
+  \brief Reimplemented for internal reasons.
+         Activation the expand item by keys perform menu expanding.
+*/
+void QtxMenu::keyPressEvent( QKeyEvent* e )
+{
+  QAction* cur = activeAction();
+
+  QMenu::keyPressEvent( e );
+
+  if ( cur != activeAction() && isMenuCollapsed() &&
+       activeAction() == myExpandAction )
+    expandMenu();
+}
+
+/*!
+  \brief Reimplemented for internal reasons.
+*/
+void QtxMenu::actionEvent( QActionEvent* e )
+{
+  myVisibilityState.insert( e->action(),
+                            e->action()->isVisible() );
+
+  QMenu::actionEvent( e );
 }
 
 /*!
@@ -378,13 +735,19 @@ void QtxMenu::setVisible( bool on )
 */
 void QtxMenu::insertTitle()
 {
-  if ( titleMode() == TitleOff || ( titleMode() == TitleAuto && titleText().trimmed().isEmpty() ) )
+  if ( titleMode() == TitleOff || ( titleMode() == TitleAuto && title().trimmed().isEmpty() ) )
     return;
 
+  myTitleAction->setIcon( icon() );
+  myTitleAction->setText( title() );
+
+  if ( actions().contains( myTitleAction ) )
+    removeAction( myTitleAction );
+
   if ( actions().isEmpty() )
-    addAction( myAction );
+    addAction( myTitleAction );
   else
-    insertAction( actions().first(), myAction );
+    insertAction( actions().first(), myTitleAction );
 }
 
 /*!
@@ -392,8 +755,8 @@ void QtxMenu::insertTitle()
 */
 void QtxMenu::removeTitle()
 {
-  if ( actions().contains( myAction ) )
-    removeAction( myAction );
+  if ( actions().contains( myTitleAction ) )
+    removeAction( myTitleAction );
 }
 
 /*!
@@ -401,9 +764,220 @@ void QtxMenu::removeTitle()
 */
 void QtxMenu::updateTitle()
 {
-  if ( !actions().contains( myAction ) )
+  if ( !actions().contains( myTitleAction ) )
     return;
 
   removeTitle();
   insertTitle();
 }
+
+/*!
+  \brief Returns the top level menu.
+*/
+QWidget* QtxMenu::topLevelMenu() const
+{
+  return topLevelMenu( this );
+}
+
+/*!
+  \brief Returns the top level menu for given menu.
+  \param menu - start menu which will be used for top level menu search.
+  \internal
+*/
+QWidget* QtxMenu::topLevelMenu( const QMenu* menu ) const
+{
+  if ( !menu )
+     return 0;
+
+  QList<QWidget*> lst;
+  if ( menu->menuAction() )
+    lst = menu->menuAction()->associatedWidgets();
+
+  QWidget* w = 0;
+
+  QMenu* pm = 0;
+  for ( QList<QWidget*>::iterator it = lst.begin(); it != lst.end() && !pm; ++it )
+  {
+    QMenu* m = ::qobject_cast<QMenu*>( *it );
+    if ( m && m->isVisible() )
+      pm = m;
+  }
+
+  if ( pm )
+    w = topLevelMenu( pm );
+
+  if ( !w )
+    w = (QWidget*)menu;
+
+  return w;
+}
+
+/*!
+  \brief Returns 'true' if the menu is top level.
+         Works correctly only when menu is shown.
+*/
+bool QtxMenu::isTopLevelMenu() const
+{
+  return topLevelMenu() == this;
+}
+
+/*!
+  \brief Expand the menu. Display menu in full state. Shows all required menu items.
+  \internal
+*/
+void QtxMenu::expandMenu()
+{
+  if ( isMenuExpanded() )
+    return;
+
+  QList<QAction*> lst = actions();
+  for ( QList<QAction*>::iterator it = lst.begin(); it != lst.end(); ++it )
+  {
+    QAction* a = *it;
+    if ( a == myTitleAction || a == myExpandAction )
+      continue;
+
+    a->setVisible( myVisibilityState.contains( a ) ? myVisibilityState[a] : false );
+  }
+
+  myShortTimer->stop();
+  myExpandTimer->stop();
+
+  if ( myExpandAction )
+    removeAction( myExpandAction );
+
+  myVisibilityState.clear();
+}
+
+/*!
+  \brief Collaps the menu. Display menu in compact state. Shows most frequently used menu items only.
+  \internal
+*/
+void QtxMenu::collapseMenu()
+{
+  QList<QAction*> lst = actions();
+
+  QSet<QAction*> visible;
+  QMap<int, QList<QAction*> > freqMap;
+
+  VisibilityMap aVisBackup;
+  QList< QPair<int, QAction*> > freqList;
+
+  for ( QList<QAction*>::iterator it = lst.begin(); it != lst.end(); ++it )
+  {
+    QAction* a = *it;
+    aVisBackup.insert( a, a->isVisible() );
+
+    if ( a->isSeparator() || !a->isVisible() )
+      continue;
+
+    int priority = actionPriority( a );
+    if ( priority < 0 )
+      visible.insert( a );
+    else
+    {
+      if ( !freqMap.contains( priority ) )
+        freqMap.insert( priority, QList<QAction*>() );
+      freqMap[priority].append( a );
+    }
+  }
+
+  int limit = collapseLimit();
+  if ( collapseLimitMode() == LimitTotal )
+    limit -= visible.count();
+
+  if ( limit > 0 )
+  {
+    QList<int> freqList = freqMap.keys();
+    int i = freqList.count() - 1;
+    for ( int c = 0; c < limit && i >= 0; i-- )
+    {
+      QList<QAction*> lst = freqMap[freqList[i]];
+      for ( QList<QAction*>::iterator it = lst.begin(); it != lst.end() && c < limit; ++it, c++ )
+        visible.insert( *it );
+    }
+  }
+
+  int hidden = 0;
+  for ( QList<QAction*>::iterator itr = lst.begin(); itr != lst.end(); ++itr )
+  {
+    QAction* a = *itr;
+    bool vis = a == myExpandAction || a->isSeparator() || visible.contains( a );
+    a->setVisible( vis );
+    if ( !vis && aVisBackup[a] )
+      hidden++;
+  }
+
+  if ( myExpandAction )
+    myExpandAction->setVisible( hidden );
+
+  updateExpander();
+
+  if ( expandingDelay() > 0 )
+    myExpandTimer->start();
+
+  myVisibilityState = aVisBackup;
+}
+
+/*!
+  \brief Updates the expand item in menu.
+  \internal
+*/
+void QtxMenu::updateExpander()
+{
+  if ( !myExpandAction )
+    return;
+
+  if ( actions().contains( myExpandAction ) )
+    removeAction( myExpandAction );
+
+  addAction( myExpandAction );
+}
+
+/*!
+  \brief Updates title widget when menu action changed.
+  \internal
+*/
+void QtxMenu::onMenuActionChanged()
+{
+  updateTitle();
+}
+
+/*!
+  \brief Expand menu by timeout signal.
+  \internal
+*/
+void QtxMenu::onExpandMenu()
+{
+  expandMenu();
+}
+
+/*!
+  \brief Start the short automatic expanding timer when mouse cursor hover expand item.
+  \internal
+*/
+void QtxMenu::onActionHovered( QAction* a )
+{
+  if ( a == myExpandAction && expandingDelay() )
+    myShortTimer->start();
+  else
+    myShortTimer->stop();
+}
+
+/*!
+  \brief Expanding menu when exanding button activated.
+         Increase priority for activated action.
+  \internal
+*/
+void QtxMenu::onActionTriggered( QAction* a )
+{
+  if ( a == myExpandAction )
+    expandMenu();
+  else
+  {
+    int priority = actionPriority( a );
+    if ( priority >= 0 )
+      priority++;
+    setActionPriority( a, priority );
+  }
+}
index 9a50f23c7004cd89a3744e52fc42859d8d71d2da..5c14e5af872839a6b1cafc44b0e14f16345139f3 100644 (file)
@@ -33,7 +33,16 @@ class QTX_EXPORT QtxMenu : public QMenu
 {
   Q_OBJECT
 
+  Q_PROPERTY( bool menuCollapsible READ menuCollapsible WRITE setMenuCollapsible )
+  Q_PROPERTY( int expandingDelay READ expandingDelay WRITE setExpandingDelay )
+  Q_PROPERTY( TitleMode titleMode READ titleMode WRITE setTitleMode )
+  Q_PROPERTY( Qt::Alignment titleAlignment READ titleAlignment WRITE setTitleAlignment )
+  Q_PROPERTY( int collapseLimit READ collapseLimit WRITE setCollapseLimit )
+  Q_PROPERTY( CollapseLimitMode collapseLimitMode READ collapseLimitMode WRITE setCollapseLimitMode )
+
   class Title;
+  class TitleMgr;
+  class Expander;
 
 public:
   //! Popup menu title mode
@@ -43,34 +52,84 @@ public:
     TitleOff          //!< always off (do not display title)
   } TitleMode;
 
+  typedef enum {
+    LimitFrequent,
+    LimitTotal
+  } CollapseLimitMode;
+
 public:
   QtxMenu( QWidget* = 0 );
   virtual ~QtxMenu();
 
-  QIcon                  titleIcon() const;
-  QString                titleText() const;
-
   TitleMode              titleMode() const;
   Qt::Alignment          titleAlignment() const;
 
-  virtual void           setTitleIcon( const QIcon& );
-  virtual void           setTitleText( const QString& );
-
   virtual void           setTitleMode( const TitleMode );
   virtual void           setTitleAlignment( const Qt::Alignment );
 
+  // Methods for collapsing/expanding functionality
+  bool                   menuCollapsible() const;
+  void                   setMenuCollapsible( bool );
+
+  bool                   isMenuExpanded() const;
+  bool                   isMenuCollapsed() const;
+
+  int                    expandingDelay() const;
+  void                   setExpandingDelay( int );
+
+  int                    collapseLimit() const;
+  void                   setCollapseLimit( int );
+
+  CollapseLimitMode      collapseLimitMode() const;
+  void                   setCollapseLimitMode( const CollapseLimitMode );
+
+  static int             actionPriority( QAction* );
+  static void            setActionPriority( QAction*, int );
+
 public slots:
   virtual void           setVisible( bool );
 
+private slots:
+  void                   onExpandMenu();
+  void                   onMenuActionChanged();
+  void                   onActionHovered( QAction* );
+  void                   onActionTriggered( QAction* );
+
+protected:
+  virtual void           keyPressEvent( QKeyEvent* );
+  virtual void           actionEvent( QActionEvent* );
+
 private:
   void                   updateTitle();
   void                   insertTitle();
   void                   removeTitle();
 
+  void                   expandMenu();
+  void                   collapseMenu();
+  void                   updateExpander();
+
+  QWidget*               topLevelMenu() const;
+  bool                   isTopLevelMenu() const;
+  QWidget*               topLevelMenu( const QMenu* ) const;
+
 private:
-  TitleMode              myMode;
-  Title*                 myTitle;
-  QWidgetAction*         myAction;
+  typedef QMap<QAction*, int>  PriorityMap;
+  typedef QMap<QAction*, bool> VisibilityMap;
+
+private:
+  TitleMode              myTitleMode;
+  Qt::Alignment          myTitleAlign;
+  QWidgetAction*         myTitleAction;
+
+  int                    myLimit;
+  CollapseLimitMode      myLimitMode;
+
+  QTimer*                myShortTimer;
+  QTimer*                myExpandTimer;
+  Expander*              myExpandAction;
+  VisibilityMap          myVisibilityState;
+
+  static PriorityMap     _actionPriority;
 };
 
 #endif // QTXMENU_H