src/gui/widgets/qmenu.cpp
changeset 3 41300fa6a67c
parent 0 1918ee327afb
child 4 3b1da2848fc7
child 7 f7bc934e204c
child 18 2f34d5167611
--- a/src/gui/widgets/qmenu.cpp	Tue Jan 26 12:42:25 2010 +0200
+++ b/src/gui/widgets/qmenu.cpp	Tue Feb 02 00:43:10 2010 +0200
@@ -65,6 +65,8 @@
 #include "qmenubar_p.h"
 #include "qwidgetaction.h"
 #include "qtoolbutton.h"
+#include "qpushbutton.h"
+#include <private/qpushbutton_p.h>
 #include <private/qaction_p.h>
 #include <private/qsoftkeymanager_p.h>
 #ifdef QT3_SUPPORT
@@ -229,7 +231,7 @@
     Q_Q(const QMenu);
     if (!itemsDirty)
         return;
-		
+
     q->ensurePolished();
 
     //let's reinitialize the buffer
@@ -273,7 +275,7 @@
 
     for (int i = 0; i < actions.count(); ++i) {
         QAction *action = actions.at(i);
-        if (action->isSeparator() || !action->isVisible() || widgetItems.at(i))
+        if (action->isSeparator() || !action->isVisible() || widgetItems.contains(action))
             continue;
         //..and some members
         hasCheckableItems |= action->isCheckable();
@@ -292,7 +294,7 @@
         if (!action->isVisible() ||
             (collapsibleSeparators && previousWasSeparator && action->isSeparator()))
             continue; // we continue, this action will get an empty QRect
-        
+
         previousWasSeparator = action->isSeparator();
 
         //let the style modify the above size..
@@ -301,7 +303,7 @@
         const QFontMetrics &fm = opt.fontMetrics;
 
         QSize sz;
-        if (QWidget *w = widgetItems.at(i)) {
+        if (QWidget *w = widgetItems.value(action)) {
           sz = w->sizeHint().expandedTo(w->minimumSize()).expandedTo(w->minimumSizeHint()).boundedTo(w->maximumSize());
         } else {
             //calc what I think the size is..
@@ -370,7 +372,7 @@
         rect.setWidth(max_column_width); //uniform width
 
         //we need to update the widgets geometry
-        if (QWidget *widget = widgetItems.at(i)) {
+        if (QWidget *widget = widgetItems.value(actions.at(i))) {
             widget->setGeometry(rect);
             widget->setVisible(actions.at(i)->isVisible());
         }
@@ -417,12 +419,7 @@
                     hideMenu(m, fadeMenus);
                 if (!fadeMenus) // Mac doesn't clear the action until after hidden.
                     m->d_func()->setCurrentAction(0);
-            } else {
-#ifndef QT_NO_TOOLBUTTON
-                if (qobject_cast<QToolButton*>(caused) == 0)
-#endif
-                    qWarning("QMenu: Internal error");
-                caused = 0;
+            } else {                caused = 0;
             }
         }
 #if defined(Q_WS_MAC)
@@ -583,8 +580,7 @@
             q->update(actionRect(action));
 
             if (reason == SelectedFromKeyboard) {
-                const int actionIndex = actions.indexOf(action);
-                QWidget *widget = widgetItems.at(actionIndex);
+                QWidget *widget = widgetItems.value(action);
                 if (widget) {
                     if (widget->focusPolicy() != Qt::NoFocus)
                         widget->setFocus(Qt::TabFocusReason);
@@ -603,14 +599,7 @@
         }
 #ifndef QT_NO_STATUSTIP
     }  else if (previousAction) {
-        QWidget *w = causedPopup.widget;
-        while (QMenu *m = qobject_cast<QMenu*>(w))
-            w = m->d_func()->causedPopup.widget;
-        if (w) {
-            QString empty;
-            QStatusTipEvent tip(empty);
-            QApplication::sendEvent(w, &tip);
-        }
+        previousAction->d_func()->showStatusText(topCausedWidget(), QString());
 #endif
     }
     if (hideActiveMenu) {
@@ -624,6 +613,15 @@
     }
 }
 
+//return the top causedPopup.widget that is not a QMenu
+QWidget *QMenuPrivate::topCausedWidget() const
+{
+    QWidget* top = causedPopup.widget;
+    while (QMenu* m = qobject_cast<QMenu *>(top))
+        top = m->d_func()->causedPopup.widget;
+    return top;
+}
+
 QAction *QMenuPrivate::actionAt(QPoint p) const
 {
     if (!q_func()->rect().contains(p))     //sanity check
@@ -653,6 +651,24 @@
     menuAction=defaultMenuAction;
 }
 
+
+void QMenuPrivate::updateLayoutDirection()
+{
+    Q_Q(QMenu);
+    //we need to mimic the cause of the popup's layout direction
+    //to allow setting it on a mainwindow for example
+    //we call setLayoutDirection_helper to not overwrite a user-defined value
+    if (!q->testAttribute(Qt::WA_SetLayoutDirection)) {
+        if (QWidget *w = causedPopup.widget)
+            setLayoutDirection_helper(w->layoutDirection());
+        else if (QWidget *w = q->parentWidget())
+            setLayoutDirection_helper(w->layoutDirection());
+        else
+            setLayoutDirection_helper(QApplication::layoutDirection());
+    }
+}
+
+
 /*!
     Returns the action associated with this menu.
 */
@@ -800,7 +816,7 @@
             current.moveTop(current.top() + delta);
 
             //we need to update the widgets geometry
-            if (QWidget *w = widgetItems.at(i))
+            if (QWidget *w = widgetItems.value(actions.at(i)))
                 w->setGeometry(current);
         }
     }
@@ -1095,10 +1111,7 @@
             QAccessible::updateAccessibility(q, actionIndex, QAccessible::Selection);
         }
 #endif
-        QWidget *w = causedPopup.widget;
-        while (QMenu *m = qobject_cast<QMenu*>(w))
-            w = m->d_func()->causedPopup.widget;
-        action->showStatusText(w);
+        action->showStatusText(topCausedWidget());
     } else {
         actionAboutToTrigger = 0;
     }
@@ -1108,6 +1121,7 @@
 {
     Q_Q(QMenu);
     if (QAction *action = qobject_cast<QAction *>(q->sender())) {
+        QWeakPointer<QAction> actionGuard = action;
 #ifdef QT3_SUPPORT
         //we store it here because the action might be deleted/changed by connected slots
         const int id = q->findIdForAction(action);
@@ -1117,12 +1131,12 @@
         emit q->activated(id);
 #endif
 
-        if (!activationRecursionGuard) {
+        if (!activationRecursionGuard && actionGuard) {
             //in case the action has not been activated by the mouse
             //we check the parent hierarchy
             QList< QPointer<QWidget> > list;
             for(QWidget *widget = q->parentWidget(); widget; ) {
-                if (qobject_cast<QMenu*>(widget) 
+                if (qobject_cast<QMenu*>(widget)
 #ifndef QT_NO_MENUBAR
                     || qobject_cast<QMenuBar*>(widget)
 #endif
@@ -1289,7 +1303,7 @@
     the addAction(), addActions() and insertAction() functions. An action
     is represented vertically and rendered by QStyle. In addition, actions
     can have a text label, an optional icon drawn on the very left side,
-    and shortcut key sequence such as "Ctrl+X". 
+    and shortcut key sequence such as "Ctrl+X".
 
     The existing actions held by a menu can be found with actions().
 
@@ -1392,11 +1406,12 @@
 QMenu::~QMenu()
 {
     Q_D(QMenu);
-    for (int i = 0; i < d->widgetItems.count(); ++i) {
-        if (QWidget *widget = d->widgetItems.at(i)) {
-            QWidgetAction *action = static_cast<QWidgetAction *>(d->actions.at(i));
+    QHash<QAction *, QWidget *>::iterator it = d->widgetItems.begin();
+    for (; it != d->widgetItems.end(); ++it) {
+        if (QWidget *widget = it.value()) {
+            QWidgetAction *action = static_cast<QWidgetAction *>(it.key());
             action->releaseWidget(widget);
-            d->widgetItems[i] = 0;
+            *it = 0;
         }
     }
 
@@ -1797,20 +1812,25 @@
     d->tearoffHighlighted = 0;
     d->motions = 0;
     d->doChildEffects = true;
+    d->updateLayoutDirection();
 
 #ifndef QT_NO_MENUBAR
     // if this menu is part of a chain attached to a QMenuBar, set the
     // _NET_WM_WINDOW_TYPE_DROPDOWN_MENU X11 window type
-    QWidget* top = this;
-    while (QMenu* m = qobject_cast<QMenu *>(top))
-        top = m->d_func()->causedPopup.widget;
-    setAttribute(Qt::WA_X11NetWmWindowTypeDropDownMenu, qobject_cast<QMenuBar *>(top) != 0);
+    setAttribute(Qt::WA_X11NetWmWindowTypeDropDownMenu, qobject_cast<QMenuBar *>(d->topCausedWidget()) != 0);
 #endif
 
     ensurePolished(); // Get the right font
     emit aboutToShow();
+    const bool actionListChanged = d->itemsDirty;
     d->updateActionRects();
-    QPoint pos = p;
+    QPoint pos;
+    QPushButton *causedButton = qobject_cast<QPushButton*>(d->causedPopup.widget);
+    if (actionListChanged && causedButton)
+        pos = QPushButtonPrivate::get(causedButton)->adjustedMenuPosition();
+    else
+        pos = p;
+
     QSize size = sizeHint();
     QRect screen;
 #ifndef QT_NO_GRAPHICSVIEW
@@ -1878,15 +1898,21 @@
             if(snapToMouse) //position flowing left from the mouse
                 pos.setX(mouse.x()-size.width());
 
+#ifndef QT_NO_MENUBAR
+            //if in a menubar, it should be right-aligned
+            if (qobject_cast<QMenuBar*>(d->causedPopup.widget))
+                pos.rx() -= size.width();
+#endif //QT_NO_MENUBAR
+
             if (pos.x() < screen.left()+desktopFrame)
                 pos.setX(qMax(p.x(), screen.left()+desktopFrame));
             if (pos.x()+size.width()-1 > screen.right()-desktopFrame)
                 pos.setX(qMax(p.x()-size.width(), screen.right()-desktopFrame-size.width()+1));
         } else {
             if (pos.x()+size.width()-1 > screen.right()-desktopFrame)
-                pos.setX(qMin(p.x()+size.width(), screen.right()-desktopFrame-size.width()+1));
+                pos.setX(screen.right()-desktopFrame-size.width()+1);
             if (pos.x() < screen.left()+desktopFrame)
-                pos.setX(qMax(p.x(), screen.left() + desktopFrame));
+                pos.setX(screen.left() + desktopFrame);
         }
         if (pos.y() + size.height() - 1 > screen.bottom() - desktopFrame) {
             if(snapToMouse)
@@ -2147,7 +2173,7 @@
         QAction *action = d->actions.at(i);
         QRect adjustedActionRect = d->actionRects.at(i);
         if (!e->rect().intersects(adjustedActionRect)
-            || d->widgetItems.at(i))
+            || d->widgetItems.value(action))
            continue;
         //set the clip region to be extra safe (and adjust for the scrollers)
         QRegion adjustedActionReg(adjustedActionRect);
@@ -2280,22 +2306,9 @@
         if (action->menu())
             action->menu()->d_func()->setFirstActionActive();
         else {
-#if defined(Q_WS_WIN) && !defined(QT_NO_MENUBAR)
+#if defined(Q_WS_WIN)
             //On Windows only context menus can be activated with the right button
-            bool isContextMenu = true;
-            const QWidget *cause = d->causedPopup.widget;
-            while (cause) {
-                //if the popup was caused by either QMenuBar or a QToolButton, it is not a context menu
-                if (qobject_cast<const QMenuBar *>(cause) || qobject_cast<const QToolButton *>(cause)) {
-                    isContextMenu = false;
-                    break;
-                } else if (const QMenu *menu = qobject_cast<const QMenu *>(cause)) {
-                    cause = menu->d_func()->causedPopup.widget;
-                } else {
-                    break;
-                }
-            }
-            if (e->button() == Qt::LeftButton || isContextMenu)
+            if (e->button() == Qt::LeftButton || d->topCausedWidget() == 0)
 #endif
                 d->activateAction(action, QAction::Trigger);
         }
@@ -2344,6 +2357,9 @@
 {
     Q_D(QMenu);
     switch (e->type()) {
+    case QEvent::Polish:
+        d->updateLayoutDirection();
+        break;
     case QEvent::ShortcutOverride: {
             QKeyEvent *kev = static_cast<QKeyEvent*>(e);
             if (kev->key() == Qt::Key_Up || kev->key() == Qt::Key_Down
@@ -2746,18 +2762,14 @@
             }
         }
         if (!key_consumed) {
-            if (QWidget *caused = d->causedPopup.widget) {
-                while(QMenu *m = qobject_cast<QMenu*>(caused))
-                    caused = m->d_func()->causedPopup.widget;
 #ifndef QT_NO_MENUBAR
-                if (QMenuBar *mb = qobject_cast<QMenuBar*>(caused)) {
-                    QAction *oldAct = mb->d_func()->currentAction;
-                    QApplication::sendEvent(mb, e);
-                    if (mb->d_func()->currentAction != oldAct)
-                        key_consumed = true;
-                }
+            if (QMenuBar *mb = qobject_cast<QMenuBar*>(d->topCausedWidget())) {
+                QAction *oldAct = mb->d_func()->currentAction;
+                QApplication::sendEvent(mb, e);
+                if (mb->d_func()->currentAction != oldAct)
+                    key_consumed = true;
+            }
 #endif
-            }
         }
 
 #ifdef Q_OS_WIN32
@@ -2858,25 +2870,20 @@
             connect(e->action(), SIGNAL(triggered()), this, SLOT(_q_actionTriggered()));
             connect(e->action(), SIGNAL(hovered()), this, SLOT(_q_actionHovered()));
         }
-        QWidget *widget = 0;
-        if (QWidgetAction *wa = qobject_cast<QWidgetAction *>(e->action()))
-            widget = wa->requestWidget(this);
-
-        int index = d->actions.indexOf(e->action());
-        Q_ASSERT(index != -1);
-        d->widgetItems.insert(index, widget);
-
+        if (QWidgetAction *wa = qobject_cast<QWidgetAction *>(e->action())) {
+            QWidget *widget = wa->requestWidget(this);
+            if (widget)
+                d->widgetItems.insert(wa, widget);
+        }
     } else if (e->type() == QEvent::ActionRemoved) {
         e->action()->disconnect(this);
         if (e->action() == d->currentAction)
             d->currentAction = 0;
-        int index = d->actions.indexOf(e->before()) + 1;
         if (QWidgetAction *wa = qobject_cast<QWidgetAction *>(e->action())) {
-            if (QWidget *widget = d->widgetItems.at(index))
+            if (QWidget *widget = d->widgetItems.value(wa))
                 wa->releaseWidget(widget);
         }
-        Q_ASSERT(index != -1);
-        d->widgetItems.removeAt(index);
+        d->widgetItems.remove(e->action());
     }
 
 #ifdef Q_WS_MAC