src/gui/widgets/qmenu_mac.mm
changeset 30 5dc02b23752f
parent 18 2f34d5167611
--- a/src/gui/widgets/qmenu_mac.mm	Wed Jun 23 19:07:03 2010 +0300
+++ b/src/gui/widgets/qmenu_mac.mm	Tue Jul 06 15:10:48 2010 +0300
@@ -247,7 +247,7 @@
 
     //now walk up firing for each "caused" widget (like in the platform independent menu)
     QWidget *caused = 0;
-    if (GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyCausedQWidget, sizeof(caused), 0, &caused) == noErr) {
+    if (action_e == QAction::Hover && GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyCausedQWidget, sizeof(caused), 0, &caused) == noErr) {
         MenuRef caused_menu = 0;
         if (QMenu *qmenu2 = qobject_cast<QMenu*>(caused))
             caused_menu = qmenu2->macMenu();
@@ -260,25 +260,17 @@
             QWidget *widget = 0;
             GetMenuItemProperty(caused_menu, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(widget), 0, &widget);
             if (QMenu *qmenu = qobject_cast<QMenu*>(widget)) {
-                if (action_e == QAction::Trigger) {
-                    emit qmenu->triggered(action->action);
-                } else if (action_e == QAction::Hover) {
-                    action->action->showStatusText(widget);
-                    emit qmenu->hovered(action->action);
-                }
+                action->action->showStatusText(widget);
+                emit qmenu->hovered(action->action);
             } else if (QMenuBar *qmenubar = qobject_cast<QMenuBar*>(widget)) {
-                if (action_e == QAction::Trigger) {
-                    emit qmenubar->triggered(action->action);
-                } else if (action_e == QAction::Hover) {
-                    action->action->showStatusText(widget);
-                    emit qmenubar->hovered(action->action);
-                }
+                action->action->showStatusText(widget);
+                emit qmenubar->hovered(action->action);
                 break; //nothing more..
             }
 
             //walk up
             if (GetMenuItemProperty(caused_menu, 0, kMenuCreatorQt, kMenuPropertyCausedQWidget,
-                                   sizeof(caused), 0, &caused) != noErr)
+                                    sizeof(caused), 0, &caused) != noErr)
                 break;
             if (QMenu *qmenu2 = qobject_cast<QMenu*>(caused))
                 caused_menu = qmenu2->macMenu();
@@ -649,7 +641,7 @@
     NSMenuItem *item = [[NSMenuItem alloc] 
                          initWithTitle:qt_mac_QStringToNSString(title)
                          action:@selector(qtDispatcherToQAction:) keyEquivalent:@""];
-    [item setTarget:getMenuLoader()];
+    [item setTarget:nil];
     return item;
 }
 #endif
@@ -673,6 +665,7 @@
         }
     }
 #else
+    bool modalWindowOnScreen = qApp->activeModalWidget() != 0;
     for (NSMenuItem *item in [menu itemArray]) {
         OSMenuRef submenu = [item submenu];
         if (submenu != merge) {
@@ -682,10 +675,20 @@
                 // The item should follow what the QAction has.
                 if ([item tag]) {
                     QAction *action = reinterpret_cast<QAction *>([item tag]);
-                     syncNSMenuItemEnabled(item, action->isEnabled());
-                 } else {
-                     syncNSMenuItemEnabled(item, YES);
-                 }
+                    syncNSMenuItemEnabled(item, action->isEnabled());
+                } else {
+                    syncNSMenuItemEnabled(item, YES);
+                }
+                // We sneak in some extra code here to handle a menu problem:
+                // If there is no window on screen, we cannot set 'nil' as
+                // menu item target, because then cocoa will disable the item
+                // (guess it assumes that there will be no first responder to
+                // catch the trigger anyway?) OTOH, If we have a modal window,
+                // then setting the menu loader as target will make cocoa not
+                // deliver the trigger because the loader is then seen as modally
+                // shaddowed). So either way there are shortcomings. Instead, we
+                // decide the target as late as possible:
+                [item setTarget:modalWindowOnScreen ? nil : getMenuLoader()];
             } else {
                 syncNSMenuItemEnabled(item, NO);
             }
@@ -749,32 +752,6 @@
     return qt_mac_menus_open_count > 0;
 }
 
-void qt_mac_clear_menubar()
-{
-    if (QApplication::testAttribute(Qt::AA_MacPluginApplication))
-        return;
-
-#ifndef QT_MAC_USE_COCOA
-    MenuRef clear_menu = 0;
-    if (CreateNewMenu(0, 0, &clear_menu) == noErr) {
-        SetRootMenu(clear_menu);
-        ReleaseMenu(clear_menu);
-    } else {
-        qWarning("QMenu: Internal error at %s:%d", __FILE__, __LINE__);
-    }
-    ClearMenuBar();
-    qt_mac_command_set_enabled(0, kHICommandPreferences, false);
-    InvalMenuBar();
-#else
-    QMacCocoaAutoReleasePool pool;
-    QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
-    NSMenu *menu = [loader menu];
-    [loader ensureAppMenuInMenu:menu];
-    [NSApp setMainMenu:menu];
-#endif
-}
-
-
 QMacMenuAction::~QMacMenuAction()
 {
 #ifdef QT_MAC_USE_COCOA
@@ -943,6 +920,7 @@
 static QString qt_mac_menu_merge_text(QMacMenuAction *action)
 {
     QString ret;
+    extern QString qt_mac_applicationmenu_string(int type);
 #ifdef QT_MAC_USE_COCOA
     QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
 #endif
@@ -950,22 +928,26 @@
         ret = action->action->text();
 #ifndef QT_MAC_USE_COCOA
     else if (action->command == kHICommandAbout)
-        ret = QMenuBar::tr("About %1").arg(qAppName());
+        ret = qt_mac_applicationmenu_string(6).arg(qAppName());
     else if (action->command == kHICommandAboutQt)
         ret = QMenuBar::tr("About Qt");
     else if (action->command == kHICommandPreferences)
-        ret = QMenuBar::tr("Preferences");
+        ret = qt_mac_applicationmenu_string(4);
     else if (action->command == kHICommandQuit)
-        ret = QMenuBar::tr("Quit %1").arg(qAppName());
+        ret = qt_mac_applicationmenu_string(5).arg(qAppName());
 #else
-    else if (action->menuItem == [loader aboutMenuItem])
-        ret = QMenuBar::tr("About %1").arg(qAppName());
-    else if (action->menuItem == [loader aboutQtMenuItem])
-        ret = QMenuBar::tr("About Qt");
-    else if (action->menuItem == [loader preferencesMenuItem])
-        ret = QMenuBar::tr("Preferences");
-    else if (action->menuItem == [loader quitMenuItem])
-        ret = QMenuBar::tr("Quit %1").arg(qAppName());
+    else if (action->menuItem == [loader aboutMenuItem]) {
+        ret = qt_mac_applicationmenu_string(6).arg(qAppName());
+    } else if (action->menuItem == [loader aboutQtMenuItem]) {
+        if (action->action->text() == QString("About Qt"))
+            ret = QMenuBar::tr("About Qt");
+        else
+            ret = action->action->text();
+    } else if (action->menuItem == [loader preferencesMenuItem]) {
+        ret = qt_mac_applicationmenu_string(4);
+    } else if (action->menuItem == [loader quitMenuItem]) {
+        ret = qt_mac_applicationmenu_string(5).arg(qAppName());
+    }
 #endif
     return ret;
 }
@@ -1130,7 +1112,7 @@
                 action->menu = merge;
                 [cmd retain];
                 [cmd setAction:@selector(qtDispatcherToQAction:)];
-                [cmd setTarget:getMenuLoader()];
+                [cmd setTarget:nil];
                 [action->menuItem release];
                 action->menuItem = cmd;
                 QMenuMergeList *list = QMenuPrivate::mergeMenuItemsHash.value(merge);
@@ -1420,7 +1402,11 @@
     } else {
         [item setTitle: qt_mac_QStringToNSString(finalString)];
     }
-    [item setTitle:qt_mac_QStringToNSString(qt_mac_removeMnemonics(text))];
+
+    if (action->action->menuRole() == QAction::AboutRole || action->action->menuRole() == QAction::QuitRole)
+        [item setTitle:qt_mac_QStringToNSString(text)];
+    else
+        [item setTitle:qt_mac_QStringToNSString(qt_mac_removeMnemonics(text))];
 
     // Cocoa Enabled
     [item setEnabled: action->action->isEnabled()];
@@ -1845,7 +1831,7 @@
     menubars()->remove(tlw);
     mac_menubar = 0;
 
-    if (qt_mac_current_menubar.qmenubar == q) {
+    if (!qt_mac_current_menubar.qmenubar || qt_mac_current_menubar.qmenubar == q) {
 #ifdef QT_MAC_USE_COCOA
         QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
         [loader removeActionsFromAppMenu];
@@ -1936,23 +1922,120 @@
     Returns true if the entries of menuBar should be disabled,
     based on the modality type of modalWidget.
 */
-static bool qt_mac_should_disable_menu(QMenuBar *menuBar, QWidget *modalWidget)
+static bool qt_mac_should_disable_menu(QMenuBar *menuBar)
 {
-    if (modalWidget == 0 || menuBar == 0)
+    QWidget *modalWidget = qApp->activeModalWidget();
+    if (!modalWidget)
+        return false;
+
+    if (menuBar && menuBar == menubars()->value(modalWidget))
+        // The menu bar is owned by the modal widget.
+        // In that case we should enable it:
         return false;
 
-    // If there is an application modal window on
-    // screen, the entries of the menubar should be disabled:
+    // When there is an application modal window on screen, the entries of
+    // the menubar should be disabled. The exception in Qt is that if the
+    // modal window is the only window on screen, then we enable the menu bar.
     QWidget *w = modalWidget;
+    QWidgetList topLevelWidgets = QApplication::topLevelWidgets();
     while (w) {
-        if (w->isVisible() && w->windowModality() == Qt::ApplicationModal)
-            return true;
+        if (w->isVisible() && w->windowModality() == Qt::ApplicationModal) {
+            for (int i=0; i<topLevelWidgets.size(); ++i) {
+                QWidget *top = topLevelWidgets.at(i);
+                if (w != top && top->isVisible()) {
+                    // INVARIANT: we found another visible window
+                    // on screen other than our modalWidget. We therefore
+                    // disable the menu bar to follow normal modality logic:
+                    return true;
+                }
+            }
+            // INVARIANT: We have only one window on screen that happends
+            // to be application modal. We choose to enable the menu bar
+            // in that case to e.g. enable the quit menu item.
+            return false;
+        }
         w = w->parentWidget();
     }
 
     // INVARIANT: modalWidget is window modal. Disable menu entries
-    // if the menu bar belongs to an ancestor of modalWidget:
-    return qt_mac_is_ancestor(menuBar->parentWidget(), modalWidget);
+    // if the menu bar belongs to an ancestor of modalWidget. If menuBar
+    // is nil, we understand it as the default menu bar set by the nib:
+    return menuBar ? qt_mac_is_ancestor(menuBar->parentWidget(), modalWidget) : false;
+}
+
+static QWidget *findWindowThatShouldDisplayMenubar()
+{
+    QWidget *w = qApp->activeWindow();
+    if (!w) {
+        // We have no active window on screen. Try to
+        // find a window from the list of top levels:
+        QWidgetList tlws = QApplication::topLevelWidgets();
+        for(int i = 0; i < tlws.size(); ++i) {
+            QWidget *tlw = tlws.at(i);
+            if ((tlw->isVisible() && tlw->windowType() != Qt::Tool &&
+                tlw->windowType() != Qt::Popup)) {
+                w = tlw;
+                break;
+            }
+        }
+    }
+    return w;
+}
+
+static QMenuBar *findMenubarForWindow(QWidget *w)
+{
+    QMenuBar *mb = 0;
+    if (w) {
+        mb = menubars()->value(w);
+#ifndef QT_NO_MAINWINDOW
+        QDockWidget *dw = qobject_cast<QDockWidget *>(w);
+        if (!mb && dw) {
+            QMainWindow *mw = qobject_cast<QMainWindow *>(dw->parentWidget());
+            if (mw && (mb = menubars()->value(mw)))
+                w = mw;
+        }
+#endif
+        while(w && !mb)
+            mb = menubars()->value((w = w->parentWidget()));
+    }
+
+    if (!mb) {
+        // We could not find a menu bar for the window. Lets
+        // check if we have a global (parentless) menu bar instead:
+        mb = fallback;
+    }
+
+    return mb;
+}
+
+void qt_mac_clear_menubar()
+{
+    if (QApplication::testAttribute(Qt::AA_MacPluginApplication))
+        return;
+
+#ifndef QT_MAC_USE_COCOA
+    MenuRef clear_menu = 0;
+    if (CreateNewMenu(0, 0, &clear_menu) == noErr) {
+        SetRootMenu(clear_menu);
+        ReleaseMenu(clear_menu);
+    } else {
+        qWarning("QMenu: Internal error at %s:%d", __FILE__, __LINE__);
+    }
+    ClearMenuBar();
+    qt_mac_command_set_enabled(0, kHICommandPreferences, false);
+    InvalMenuBar();
+#else
+    QMacCocoaAutoReleasePool pool;
+    QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
+    NSMenu *menu = [loader menu];
+    [loader ensureAppMenuInMenu:menu];
+    [NSApp setMainMenu:menu];
+    const bool modal = qt_mac_should_disable_menu(0);
+    if (qt_mac_current_menubar.qmenubar || modal != qt_mac_current_menubar.modal)
+        qt_mac_set_modal_state(menu, modal);
+    qt_mac_current_menubar.qmenubar = 0;
+    qt_mac_current_menubar.modal = modal;
+#endif
 }
 
 /*!
@@ -1967,40 +2050,44 @@
 */
 bool QMenuBar::macUpdateMenuBar()
 {
-    cancelAllMenuTracking();
-    QMenuBar *mb = 0;
-    //find a menu bar
-    QWidget *w = qApp->activeWindow();
+#ifdef QT_MAC_USE_COCOA
+    QMacCocoaAutoReleasePool pool;
+    if (!qt_cocoaPostMessage(getMenuLoader(), @selector(qtUpdateMenubar)))
+        return QMenuBarPrivate::macUpdateMenuBarImmediatly();
+    return true;
+#else
+    return QMenuBarPrivate::macUpdateMenuBarImmediatly();
+#endif
+}
 
-    if (!w) {
-        QWidgetList tlws = QApplication::topLevelWidgets();
-        for(int i = 0; i < tlws.size(); ++i) {
-            QWidget *tlw = tlws.at(i);
-            if ((tlw->isVisible() && tlw->windowType() != Qt::Tool &&
-                tlw->windowType() != Qt::Popup)) {
-                w = tlw;
-                break;
-            }
+bool QMenuBarPrivate::macUpdateMenuBarImmediatly()
+{
+    bool ret = false;
+    cancelAllMenuTracking();
+    QWidget *w = findWindowThatShouldDisplayMenubar();
+    QMenuBar *mb = findMenubarForWindow(w);
+    extern bool qt_mac_app_fullscreen; //qapplication_mac.mm
+
+    // We need to see if we are in full screen mode, if so we need to
+    // switch the full screen mode to be able to show or hide the menubar.
+    if(w && mb) {
+        // This case means we are creating a menubar, check if full screen
+        if(w->isFullScreen()) {
+            // Ok, switch to showing the menubar when hovering over it.
+            SetSystemUIMode(kUIModeAllHidden, kUIOptionAutoShowMenuBar);
+            qt_mac_app_fullscreen = true;
+        }
+    } else if(w) {
+        // Removing a menubar
+        if(w->isFullScreen()) {
+            // Ok, switch to not showing the menubar when hovering on it
+            SetSystemUIMode(kUIModeAllHidden, 0);
+            qt_mac_app_fullscreen = true;
         }
     }
-    if (w) {
-        mb = menubars()->value(w);
-#ifndef QT_NO_MAINWINDOW
-        QDockWidget *dw = qobject_cast<QDockWidget *>(w);
-        if (!mb && dw) {
-            QMainWindow *mw = qobject_cast<QMainWindow *>(dw->parentWidget());
-            if (mw && (mb = menubars()->value(mw)))
-                w = mw;
-        }
-#endif
-        while(w && !mb)
-            mb = menubars()->value((w = w->parentWidget()));
-    }
-    if (!mb)
-        mb = fallback;
-    //now set it
-    bool ret = false;
+
     if (mb && mb->isNativeMenuBar()) {
+        bool modal = QApplicationPrivate::modalState();
 #ifdef QT_MAC_USE_COCOA
         QMacCocoaAutoReleasePool pool;
 #endif
@@ -2030,16 +2117,18 @@
                 }
             }
 #endif
-            QWidget *modalWidget = qApp->activeModalWidget();
-            if (mb != menubars()->value(modalWidget)) {
-                qt_mac_set_modal_state(menu, qt_mac_should_disable_menu(mb, modalWidget));
-            }
+            // Check if menu is modally shaddowed and should  be disabled:
+            modal = qt_mac_should_disable_menu(mb);
+            if (mb != qt_mac_current_menubar.qmenubar || modal != qt_mac_current_menubar.modal)
+                qt_mac_set_modal_state(menu, modal);
         }
         qt_mac_current_menubar.qmenubar = mb;
-        qt_mac_current_menubar.modal = QApplicationPrivate::modalState();
+        qt_mac_current_menubar.modal = modal;
         ret = true;
     } else if (qt_mac_current_menubar.qmenubar && qt_mac_current_menubar.qmenubar->isNativeMenuBar()) {
-        const bool modal = QApplicationPrivate::modalState();
+        // INVARIANT: The currently active menu bar (if any) is not native. But we do have a
+        // native menu bar from before. So we need to decide whether or not is should be enabled:
+        const bool modal = qt_mac_should_disable_menu(qt_mac_current_menubar.qmenubar);
         if (modal != qt_mac_current_menubar.modal) {
             ret = true;
             if (OSMenuRef menu = qt_mac_current_menubar.qmenubar->macMenu()) {
@@ -2051,16 +2140,15 @@
                 [NSApp setMainMenu:menu];
                 syncMenuBarItemsVisiblity(qt_mac_current_menubar.qmenubar->d_func()->mac_menubar);
 #endif
-                QWidget *modalWidget = qApp->activeModalWidget();
-                if (qt_mac_current_menubar.qmenubar != menubars()->value(modalWidget)) {
-                    qt_mac_set_modal_state(menu, qt_mac_should_disable_menu(mb, modalWidget));
-                }
+                qt_mac_set_modal_state(menu, modal);
             }
             qt_mac_current_menubar.modal = modal;
         }
     }
-    if(!ret)
+
+    if (!ret) {
         qt_mac_clear_menubar();
+    }
     return ret;
 }
 
@@ -2131,3 +2219,4 @@
 
 
 QT_END_NAMESPACE
+