]> git.saurik.com Git - wxWidgets.git/blobdiff - src/gtk/menu.cpp
Add doc for wxGraphicsContext.Create(), which is a lightweight context with no target...
[wxWidgets.git] / src / gtk / menu.cpp
index f0dbd471f767bdb18be9e2e8107fbf926f22f6aa..686761a6bee70638d6d9f10b78095ab83770f1cd 100644 (file)
@@ -17,6 +17,7 @@
 #ifndef WX_PRECOMP
     #include "wx/intl.h"
     #include "wx/log.h"
+    #include "wx/dialog.h"
     #include "wx/frame.h"
     #include "wx/bitmap.h"
     #include "wx/app.h"
 
 #include "wx/accel.h"
 #include "wx/stockitem.h"
+
+#include <gtk/gtk.h>
 #include "wx/gtk/private.h"
+#include "wx/gtk/private/gtk2-compat.h"
 #include "wx/gtk/private/mnemonics.h"
 
+// Number of currently open modal dialogs, defined in src/gtk/toplevel.cpp.
+extern int wxOpenModalDialogsCount;
+
 // we use normal item but with a special id for the menu title
 static const int wxGTK_TITLE_ID = -3;
 
@@ -40,8 +47,34 @@ extern "C"
 static void wxGetGtkAccel(const wxMenuItem*, guint*, GdkModifierType*);
 #endif
 
+// Unity hack: under Ubuntu Unity the global menu bar is not affected by a
+// modal dialog being shown, so the user can select a menu item before hiding
+// the dialog and, in particular, a new instance of the same dialog can be
+// shown again, breaking a lot of programs not expecting this.
+//
+// So explicitly ignore any menu events generated while any modal dialogs
+// are opened except for the events generated by a context menu within the
+// modal dialog itself that should have a dialog as their invoking window.
+static bool IsMenuEventAllowed(wxMenu* menu)
+{
+    if ( wxOpenModalDialogsCount )
+    {
+        wxWindow* tlw = wxGetTopLevelParent(menu->GetWindow());
+        if ( !tlw || !wxDynamicCast(tlw, wxDialog) )
+        {
+            // This must be an event from a menu bar of one of the frames.
+            return false;
+        }
+    }
+
+    return true;
+}
+
 static void DoCommonMenuCallbackCode(wxMenu *menu, wxMenuEvent& event)
 {
+    if ( !IsMenuEventAllowed(menu) )
+        return;
+
     event.SetEventObject( menu );
 
     wxEvtHandler* handler = menu->GetEventHandler();
@@ -60,14 +93,6 @@ static void DoCommonMenuCallbackCode(wxMenu *menu, wxMenuEvent& event)
 
 wxMenuBar::~wxMenuBar()
 {
-    if (m_widget)
-    {
-        // Work around a probable bug in Ubuntu 12.04 which causes a warning if
-        // gtk_widget_destroy() is called on a wxMenuBar attached to a frame
-        GtkWidget* widget = m_widget;
-        m_widget = NULL;
-        g_object_unref(widget);
-    }
 }
 
 void wxMenuBar::Init(size_t n, wxMenu *menus[], const wxString titles[], long style)
@@ -99,8 +124,6 @@ void wxMenuBar::Init(size_t n, wxMenu *menus[], const wxString titles[], long st
     }
 
     PostCreation();
-
-    GTKApplyWidgetStyle();
 #endif // wxUSE_LIBHILDON || wxUSE_LIBHILDON2/!wxUSE_LIBHILDON && !wxUSE_LIBHILDON2
 
     g_object_ref_sink(m_widget);
@@ -129,6 +152,18 @@ wxMenuBar::wxMenuBar()
 namespace
 {
 
+// This should be called when detaching menus to ensure that they don't keep
+// focus grab, because if they do, they continue getting all GTK+ messages
+// which they can't process any more in their (soon to be) unrealized state.
+void
+EnsureNoGrab(GtkWidget* widget)
+{
+#if !wxUSE_LIBHILDON && !wxUSE_LIBHILDON2
+    gtk_widget_hide(widget);
+    gtk_grab_remove(widget);
+#endif // !wxUSE_LIBHILDON && !wxUSE_LIBHILDON2
+}
+
 void
 DetachFromFrame(wxMenu* menu, wxFrame* frame)
 {
@@ -138,7 +173,7 @@ DetachFromFrame(wxMenu* menu, wxFrame* frame)
         // Note that wxGetTopLevelParent() is really needed because this frame
         // can be an MDI child frame which is a fake frame and not a TLW at all
         GtkWindow * const tlw = GTK_WINDOW(wxGetTopLevelParent(frame)->m_widget);
-        if (g_slist_find(menu->m_accel->acceleratables, tlw))
+        if (g_slist_find(gtk_accel_groups_from_object(G_OBJECT(tlw)), menu->m_accel))
             gtk_window_remove_accel_group(tlw, menu->m_accel);
     }
 
@@ -150,6 +185,8 @@ DetachFromFrame(wxMenu* menu, wxFrame* frame)
             DetachFromFrame(menuitem->GetSubMenu(), frame);
         node = node->GetNext();
     }
+
+    EnsureNoGrab(menu->m_menu);
 }
 
 void
@@ -159,7 +196,7 @@ AttachToFrame(wxMenu* menu, wxFrame* frame)
     if (menu->m_accel)
     {
         GtkWindow * const tlw = GTK_WINDOW(wxGetTopLevelParent(frame)->m_widget);
-        if (!g_slist_find(menu->m_accel->acceleratables, tlw))
+        if (!g_slist_find(gtk_accel_groups_from_object(G_OBJECT(tlw)), menu->m_accel))
             gtk_window_add_accel_group(tlw, menu->m_accel);
     }
 
@@ -237,6 +274,8 @@ void wxMenuBar::Detach()
         node = node->GetNext();
     }
 
+    EnsureNoGrab(m_widget);
+
     wxMenuBarBase::Detach();
 }
 
@@ -289,6 +328,7 @@ void wxMenuBar::GtkAppend(wxMenu* menu, const wxString& title, int pos)
 
         gtk_menu_item_set_submenu( GTK_MENU_ITEM(menu->m_owner), menu->m_menu );
     }
+    g_object_ref(menu->m_owner);
 
     gtk_widget_show( menu->m_owner );
 
@@ -330,13 +370,22 @@ wxMenu *wxMenuBar::Remove(size_t pos)
     if ( !menu )
         return NULL;
 
-    gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu->m_owner), NULL);
+    // remove item from menubar before destroying item to avoid spurious
+    // warnings from Ubuntu libdbusmenu
     gtk_container_remove(GTK_CONTAINER(m_menubar), menu->m_owner);
+    // remove submenu to avoid destroying it when item is destroyed
+#ifdef __WXGTK3__
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu->m_owner), NULL);
+#else
+    gtk_menu_item_remove_submenu(GTK_MENU_ITEM(menu->m_owner));
+#endif
 
     gtk_widget_destroy( menu->m_owner );
+    g_object_unref(menu->m_owner);
     menu->m_owner = NULL;
 
-    DetachFromFrame( menu, m_menuBarFrame );
+    if ( m_menuBarFrame )
+        DetachFromFrame( menu, m_menuBarFrame );
 
     return menu;
 }
@@ -434,7 +483,7 @@ bool wxMenuBar::IsEnabledTop(size_t pos) const
     wxCHECK_MSG( node, false, wxS("invalid index in IsEnabledTop") );
     wxMenu* const menu = node->GetData();
     wxCHECK_MSG( menu->m_owner, true, wxS("no menu owner?") );
-    return gtk_widget_get_sensitive( menu->m_owner );
+    return gtk_widget_get_sensitive( menu->m_owner ) != 0;
 }
 
 wxString wxMenuBar::GetMenuLabel( size_t pos ) const
@@ -474,6 +523,9 @@ static void menuitem_activate(GtkWidget*, wxMenuItem* item)
     if (!item->IsEnabled())
         return;
 
+    if ( !IsMenuEventAllowed(item->GetMenu()) )
+        return;
+
     int id = item->GetId();
     if (id == wxGTK_TITLE_ID)
     {
@@ -501,6 +553,14 @@ static void menuitem_activate(GtkWidget*, wxMenuItem* item)
 
     wxMenu* menu = item->GetMenu();
     menu->SendEvent(id, item->IsCheckable() ? item->IsChecked() : -1);
+
+    // A lot of existing code, including any program that closes its main
+    // window from a menu handler and expects the program to exit -- as our own
+    // minimal sample -- relies on getting an idle event after a menu event.
+    // But when using Ubuntu Unity detached menus, we get called from a DBUS
+    // handler called from g_timeout_dispatch() and Glib doesn't send us any
+    // idle events after it. So ask explicitly for an idle event to get one.
+    wxWakeUpIdle();
 }
 }
 
@@ -575,9 +635,20 @@ wxMenuItem::wxMenuItem(wxMenu *parentMenu,
 
 wxMenuItem::~wxMenuItem()
 {
+    if (m_menuItem)
+        g_object_unref(m_menuItem);
    // don't delete menu items, the menus take care of that
 }
 
+void wxMenuItem::SetMenuItem(GtkWidget* menuItem)
+{
+    if (m_menuItem)
+        g_object_unref(m_menuItem);
+    m_menuItem = menuItem;
+    if (menuItem)
+        g_object_ref(menuItem);
+}
+
 void wxMenuItem::SetItemLabel( const wxString& str )
 {
 #if wxUSE_ACCEL
@@ -681,6 +752,13 @@ static void menu_map(GtkWidget*, wxMenu* menu)
 // "hide" from m_menu
 static void menu_hide(GtkWidget*, wxMenu* menu)
 {
+    // When using Ubuntu Unity desktop environment we get "hide" signal even
+    // when the window is not shown yet because Unity hides all the menus to
+    // show them only in the global menu bar. Just ignore this even instead of
+    // crashing in DoCommonMenuCallbackCode().
+    if ( !menu->GetWindow() )
+        return;
+
     wxMenuEvent event(wxEVT_MENU_CLOSE, menu->m_popupShown ? -1 : 0, menu);
     menu->m_popupShown = false;
     DoCommonMenuCallbackCode(menu, event);
@@ -703,8 +781,6 @@ void wxMenu::Init()
 
     m_accel = gtk_accel_group_new();
     m_menu = gtk_menu_new();
-    // NB: keep reference to the menu so that it is not destroyed behind
-    //     our back by GTK+ e.g. when it is removed from menubar:
     g_object_ref_sink(m_menu);
 
     m_owner = NULL;
@@ -736,16 +812,18 @@ wxMenu::~wxMenu()
     // Destroying a menu generates a "hide" signal even if it's not shown
     // currently, so disconnect it to avoid dummy wxEVT_MENU_CLOSE events
     // generation.
-    g_signal_handlers_disconnect_by_func(m_menu, (gpointer)menu_hide, this);
+    g_signal_handlers_disconnect_matched(m_menu,
+        GSignalMatchType(G_SIGNAL_MATCH_DATA), 0, 0, NULL, NULL, this);
 
-    // see wxMenu::Init
-    g_object_unref(m_menu);
-
-    // if the menu is inserted in another menu at this time, there was
-    // one more reference to it:
     if (m_owner)
-       gtk_widget_destroy(m_menu);
+    {
+        gtk_widget_destroy(m_owner);
+        g_object_unref(m_owner);
+    }
+    else
+        gtk_widget_destroy(m_menu);
 
+    g_object_unref(m_menu);
     g_object_unref(m_accel);
 }
 
@@ -899,20 +977,15 @@ wxMenuItem *wxMenu::DoRemove(wxMenuItem *item)
         return NULL;
 
     GtkWidget * const mitem = item->GetMenuItem();
-    if (!gtk_check_version(2,12,0))
-    {
-        // gtk_menu_item_remove_submenu() is deprecated since 2.12, but
-        // gtk_menu_item_set_submenu() can now be used with NULL submenu now so
-        // just do use it.
-        gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), NULL);
-    }
-    else // GTK+ < 2.12
-    {
-        // In 2.10 calling gtk_menu_item_set_submenu() with NULL submenu
-        // results in critical GTK+ error messages so use the old function
-        // instead.
-        gtk_menu_item_remove_submenu(GTK_MENU_ITEM(mitem));
-    }
+
+    g_signal_handlers_disconnect_matched(mitem,
+        GSignalMatchType(G_SIGNAL_MATCH_DATA), 0, 0, NULL, NULL, item);
+
+#ifdef __WXGTK3__
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), NULL);
+#else
+    gtk_menu_item_remove_submenu(GTK_MENU_ITEM(mitem));
+#endif
 
     gtk_widget_destroy(mitem);
     item->SetMenuItem(NULL);
@@ -1224,12 +1297,6 @@ const char *wxGetStockGtkID(wxWindowID id)
         case wx:                   \
             return gtk;
 
-    #if GTK_CHECK_VERSION(2,6,0)
-        #define STOCKITEM_26(wx,gtk) STOCKITEM(wx,gtk)
-    #else
-        #define STOCKITEM_26(wx,gtk)
-    #endif
-
     #if GTK_CHECK_VERSION(2,8,0)
         #define STOCKITEM_28(wx,gtk) STOCKITEM(wx,gtk)
     #else
@@ -1245,7 +1312,7 @@ const char *wxGetStockGtkID(wxWindowID id)
 
     switch (id)
     {
-        STOCKITEM_26(wxID_ABOUT,         GTK_STOCK_ABOUT)
+        STOCKITEM(wxID_ABOUT,            GTK_STOCK_ABOUT)
         STOCKITEM(wxID_ADD,              GTK_STOCK_ADD)
         STOCKITEM(wxID_APPLY,            GTK_STOCK_APPLY)
         STOCKITEM(wxID_BACKWARD,         GTK_STOCK_GO_BACK)
@@ -1260,10 +1327,10 @@ const char *wxGetStockGtkID(wxWindowID id)
         STOCKITEM(wxID_CUT,              GTK_STOCK_CUT)
         STOCKITEM(wxID_DELETE,           GTK_STOCK_DELETE)
         STOCKITEM(wxID_DOWN,             GTK_STOCK_GO_DOWN)
-        STOCKITEM_26(wxID_EDIT,          GTK_STOCK_EDIT)
+        STOCKITEM(wxID_EDIT,             GTK_STOCK_EDIT)
         STOCKITEM(wxID_EXECUTE,          GTK_STOCK_EXECUTE)
         STOCKITEM(wxID_EXIT,             GTK_STOCK_QUIT)
-        STOCKITEM_26(wxID_FILE,          GTK_STOCK_FILE)
+        STOCKITEM(wxID_FILE,             GTK_STOCK_FILE)
         STOCKITEM(wxID_FIND,             GTK_STOCK_FIND)
         STOCKITEM(wxID_FIRST,            GTK_STOCK_GOTO_FIRST)
         STOCKITEM(wxID_FLOPPY,           GTK_STOCK_FLOPPY)