From d03f523dab99528aa8d6dcfa9519cde65ac876e9 Mon Sep 17 00:00:00 2001 From: san Date: Mon, 18 Jul 2011 13:17:52 +0000 Subject: [PATCH] New functionality: Collapsed/Expanded menus. --- src/Qtx/QtxActionMenuMgr.cxx | 39 +- src/Qtx/QtxActionMenuMgr.h | 4 + src/Qtx/QtxMenu.cxx | 778 ++++++++++++++++++++++++++++++----- src/Qtx/QtxMenu.h | 77 +++- 4 files changed, 783 insertions(+), 115 deletions(-) diff --git a/src/Qtx/QtxActionMenuMgr.cxx b/src/Qtx/QtxActionMenuMgr.cxx index 94368536e..0a3945632 100644 --- a/src/Qtx/QtxActionMenuMgr.cxx +++ b/src/Qtx/QtxActionMenuMgr.cxx @@ -21,9 +21,9 @@ #include "QtxActionMenuMgr.h" +#include "QtxMenu.h" #include "QtxAction.h" -#include #include #include #include @@ -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( (*it)->menu() ); + if ( m ) + m->setMenuCollapsible( myCollapse ); + } +} + /*! \brief Perform delayed menu update. \param id menu item ID diff --git a/src/Qtx/QtxActionMenuMgr.h b/src/Qtx/QtxActionMenuMgr.h index 6ee27cf61..ff7c47c7d 100644 --- a/src/Qtx/QtxActionMenuMgr.h +++ b/src/Qtx/QtxActionMenuMgr.h @@ -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 myUpdateIds; //!< list of actions ID being updated }; diff --git a/src/Qtx/QtxMenu.cxx b/src/Qtx/QtxMenu.cxx index 418e5e61c..e87e82ea7 100644 --- a/src/Qtx/QtxMenu.cxx +++ b/src/Qtx/QtxMenu.cxx @@ -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 @@ -22,15 +22,64 @@ #include "QtxMenu.h" +#include #include +#include #include +#include +#include +#include #include #include +#include #include #include #include #include +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. @@ -40,17 +89,15 @@ 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 lst = a->menu()->actions(); + for ( QList::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 lst; + if ( menu->menuAction() ) + lst = menu->menuAction()->associatedWidgets(); + + QWidget* w = 0; + + QMenu* pm = 0; + for ( QList::iterator it = lst.begin(); it != lst.end() && !pm; ++it ) + { + QMenu* m = ::qobject_cast( *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 lst = actions(); + for ( QList::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 lst = actions(); + + QSet visible; + QMap > freqMap; + + VisibilityMap aVisBackup; + QList< QPair > freqList; + + for ( QList::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() ); + freqMap[priority].append( a ); + } + } + + int limit = collapseLimit(); + if ( collapseLimitMode() == LimitTotal ) + limit -= visible.count(); + + if ( limit > 0 ) + { + QList freqList = freqMap.keys(); + int i = freqList.count() - 1; + for ( int c = 0; c < limit && i >= 0; i-- ) + { + QList lst = freqMap[freqList[i]]; + for ( QList::iterator it = lst.begin(); it != lst.end() && c < limit; ++it, c++ ) + visible.insert( *it ); + } + } + + int hidden = 0; + for ( QList::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 ); + } +} diff --git a/src/Qtx/QtxMenu.h b/src/Qtx/QtxMenu.h index 9a50f23c7..5c14e5af8 100644 --- a/src/Qtx/QtxMenu.h +++ b/src/Qtx/QtxMenu.h @@ -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 PriorityMap; + typedef QMap 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 -- 2.39.2