[gnome-software] Only show one modal dialog at a time



commit 12a781c09bd72dd60ad5187b0d1472ff042480a6
Author: Richard Hughes <richard hughsie com>
Date:   Fri Mar 4 11:53:24 2016 +0000

    Only show one modal dialog at a time
    
    Keep track of the currently shown modal dialog so that we can destroy it when
    another dialog is shown. Bad things happen when we try to show more than one
    modal window, e.g. windows become unclickable and the main UI is unresponsive.
    
    One way this can be seen when going to the details panel, clicking on an update
    description and then clicking 'About' in the application menu.
    
    This also involved porting away from using gtk_dialog_run() which does not cope
    well when doing this kind of stacking.
    
    You'll need https://bugzilla.gnome.org/show_bug.cgi?id=762475 for gnome-shell
    if you see the application window in shadow with no modal dialogs open.

 src/gs-application.c     |   49 ++++++-------
 src/gs-page.c            |  174 ++++++++++++++++++++++++++++------------------
 src/gs-shell-details.c   |   76 +++++++++++---------
 src/gs-shell-installed.c |    4 +-
 src/gs-shell-updates.c   |   10 +--
 src/gs-shell.c           |   56 ++++++++++++++-
 src/gs-shell.h           |    2 +
 7 files changed, 232 insertions(+), 139 deletions(-)
---
diff --git a/src/gs-application.c b/src/gs-application.c
index 9768c57..bffb138 100644
--- a/src/gs-application.c
+++ b/src/gs-application.c
@@ -249,9 +249,7 @@ gs_application_show_first_run_dialog (GsApplication *app)
 
        if (g_settings_get_boolean (app->settings, "first-run") == TRUE) {
                dialog = gs_first_run_dialog_new ();
-               gtk_window_set_transient_for (GTK_WINDOW (dialog), gs_shell_get_window (app->shell));
-               gtk_window_present (GTK_WINDOW (dialog));
-
+               gs_shell_modal_dialog_present (app->shell, GTK_DIALOG (dialog));
                g_settings_set_boolean (app->settings, "first-run", FALSE);
        }
 }
@@ -345,8 +343,9 @@ sources_activated (GSimpleAction *action,
 static void
 about_activated (GSimpleAction *action,
                 GVariant      *parameter,
-                gpointer       app)
+                gpointer       user_data)
 {
+       GsApplication *app = GS_APPLICATION (user_data);
        const gchar *authors[] = {
                "Richard Hughes",
                "Matthias Clasen",
@@ -355,30 +354,30 @@ about_activated (GSimpleAction *action,
                "William Jon McCann",
                NULL
        };
-       const gchar *copyright = "Copyright \xc2\xa9 2013 Richard Hughes, Matthias Clasen";
-       GList *windows;
-       GtkWindow *parent = NULL;
+       const gchar *copyright = "Copyright \xc2\xa9 2016 Richard Hughes, Matthias Clasen";
+       GtkAboutDialog *dialog;
 
        gs_application_initialize_ui (app);
 
-       windows = gtk_application_get_windows (GTK_APPLICATION (app));
-       if (windows)
-               parent = windows->data;
-
-       gtk_show_about_dialog (parent,
-                              /* TRANSLATORS: this is the title of the about window */
-                              "title", _("About Software"),
-                              /* TRANSLATORS: this is the application name */
-                              "program-name", _("Software"),
-                              "authors", authors,
-                              /* TRANSLATORS: well, we seem to think so, anyway */
-                              "comments", _("A nice way to manage the software on your system."),
-                              "copyright", copyright,
-                              "license-type", GTK_LICENSE_GPL_2_0,
-                              "logo-icon-name", "org.gnome.Software",
-                              "translator-credits", _("translator-credits"),
-                              "version", VERSION,
-                              NULL);
+       dialog = GTK_ABOUT_DIALOG (gtk_about_dialog_new ());
+       gtk_about_dialog_set_authors (dialog, authors);
+       gtk_about_dialog_set_copyright (dialog, copyright);
+       gtk_about_dialog_set_license_type (dialog, GTK_LICENSE_GPL_2_0);
+       gtk_about_dialog_set_logo_icon_name (dialog, "org.gnome.Software");
+       gtk_about_dialog_set_translator_credits (dialog, _("translator-credits"));
+       gtk_about_dialog_set_version (dialog, VERSION);
+
+       /* TRANSLATORS: this is the title of the about window */
+       gtk_window_set_title (GTK_WINDOW (dialog), _("About Software"));
+
+       /* TRANSLATORS: this is the application name */
+       gtk_about_dialog_set_program_name (dialog, _("Software"));
+
+       /* TRANSLATORS: well, we seem to think so, anyway */
+       gtk_about_dialog_set_comments (dialog, _("A nice way to manage the "
+                                                "software on your system."));
+
+       gs_shell_modal_dialog_present (app->shell, GTK_DIALOG (dialog));
 }
 
 static void
diff --git a/src/gs-page.c b/src/gs-page.c
index fdd1621..db91dea 100644
--- a/src/gs-page.c
+++ b/src/gs-page.c
@@ -43,16 +43,16 @@ G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GsPage, gs_page, GTK_TYPE_BIN)
 typedef struct {
        GsApp           *app;
        GsPage          *page;
-} InstallRemoveData;
+} GsPageHelper;
 
 static void
-install_remove_data_free (InstallRemoveData *data)
+gs_page_helper_free (GsPageHelper *helper)
 {
-       if (data->app != NULL)
-               g_object_unref (data->app);
-       if (data->page != NULL)
-               g_object_unref (data->page);
-       g_slice_free (InstallRemoveData, data);
+       if (helper->app != NULL)
+               g_object_unref (helper->app);
+       if (helper->page != NULL)
+               g_object_unref (helper->page);
+       g_slice_free (GsPageHelper, helper);
 }
 
 static void
@@ -61,8 +61,8 @@ gs_page_app_installed_cb (GObject *source,
                           gpointer user_data)
 {
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
-       InstallRemoveData *data = (InstallRemoveData *) user_data;
-       GsPage *page = data->page;
+       GsPageHelper *helper = (GsPageHelper *) user_data;
+       GsPage *page = helper->page;
        GsPagePrivate *priv = gs_page_get_instance_private (page);
        gboolean ret;
        g_autoptr(GError) error = NULL;
@@ -72,9 +72,9 @@ gs_page_app_installed_cb (GObject *source,
                                                  &error);
        if (!ret) {
                g_warning ("failed to install %s: %s",
-                          gs_app_get_id (data->app),
+                          gs_app_get_id (helper->app),
                           error->message);
-               gs_app_notify_failed_modal (data->app,
+               gs_app_notify_failed_modal (helper->app,
                                            gs_shell_get_window (priv->shell),
                                            GS_PLUGIN_LOADER_ACTION_INSTALL,
                                            error);
@@ -82,15 +82,15 @@ gs_page_app_installed_cb (GObject *source,
        }
 
        /* only show this if the window is not active */
-       if (gs_app_get_state (data->app) != AS_APP_STATE_QUEUED_FOR_INSTALL &&
+       if (gs_app_get_state (helper->app) != AS_APP_STATE_QUEUED_FOR_INSTALL &&
            !gs_shell_is_active (priv->shell))
-               gs_app_notify_installed (data->app);
+               gs_app_notify_installed (helper->app);
 
        if (GS_PAGE_GET_CLASS (page)->app_installed != NULL)
-               GS_PAGE_GET_CLASS (page)->app_installed (page, data->app);
+               GS_PAGE_GET_CLASS (page)->app_installed (page, helper->app);
 
 out:
-       install_remove_data_free (data);
+       gs_page_helper_free (helper);
 }
 
 static void
@@ -99,8 +99,8 @@ gs_page_app_removed_cb (GObject *source,
                         gpointer user_data)
 {
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
-       InstallRemoveData *data = (InstallRemoveData *) user_data;
-       GsPage *page = data->page;
+       GsPageHelper *helper = (GsPageHelper *) user_data;
+       GsPage *page = helper->page;
        GsPagePrivate *priv = gs_page_get_instance_private (page);
        gboolean ret;
        g_autoptr(GError) error = NULL;
@@ -110,7 +110,7 @@ gs_page_app_removed_cb (GObject *source,
                                                  &error);
        if (!ret) {
                g_warning ("failed to remove: %s", error->message);
-               gs_app_notify_failed_modal (data->app,
+               gs_app_notify_failed_modal (helper->app,
                                            gs_shell_get_window (priv->shell),
                                            GS_PLUGIN_LOADER_ACTION_REMOVE,
                                            error);
@@ -118,10 +118,10 @@ gs_page_app_removed_cb (GObject *source,
        }
 
        if (GS_PAGE_GET_CLASS (page)->app_removed != NULL)
-               GS_PAGE_GET_CLASS (page)->app_removed (page, data->app);
+               GS_PAGE_GET_CLASS (page)->app_removed (page, helper->app);
 
 out:
-       install_remove_data_free (data);
+       gs_page_helper_free (helper);
 }
 
 GtkWidget *
@@ -160,7 +160,7 @@ void
 gs_page_install_app (GsPage *page, GsApp *app)
 {
        GsPagePrivate *priv = gs_page_get_instance_private (page);
-       InstallRemoveData *data;
+       GsPageHelper *helper;
        GtkResponseType response;
 
        /* probably non-free */
@@ -170,61 +170,75 @@ gs_page_install_app (GsPage *page, GsApp *app)
                        return;
        }
 
-       data = g_slice_new0 (InstallRemoveData);
-       data->app = g_object_ref (app);
-       data->page = g_object_ref (page);
+       helper = g_slice_new0 (GsPageHelper);
+       helper->app = g_object_ref (app);
+       helper->page = g_object_ref (page);
        gs_plugin_loader_app_action_async (priv->plugin_loader,
                                           app,
                                           GS_PLUGIN_LOADER_ACTION_INSTALL,
                                           priv->cancellable,
                                           gs_page_app_installed_cb,
-                                          data);
+                                          helper);
 }
 
 static void
-gs_page_update_app_real (GsPage *page, GsApp *app)
+gs_page_update_app_response_cb (GtkDialog *dialog,
+                               gint response,
+                               GsPageHelper *helper)
 {
-       GsPagePrivate *priv = gs_page_get_instance_private (page);
-       InstallRemoveData *data;
-       data = g_slice_new0 (InstallRemoveData);
-       data->app = g_object_ref (app);
-       data->page = g_object_ref (page);
-       g_debug ("update %s", gs_app_get_id (app));
+       GsPagePrivate *priv = gs_page_get_instance_private (helper->page);
+
+       /* not agreed */
+       if (response != GTK_RESPONSE_OK) {
+               gs_page_helper_free (helper);
+               return;
+       }
+       g_debug ("update %s", gs_app_get_id (helper->app));
        gs_plugin_loader_app_action_async (priv->plugin_loader,
-                                          app,
+                                          helper->app,
                                           GS_PLUGIN_LOADER_ACTION_UPDATE,
                                           priv->cancellable,
                                           gs_page_app_installed_cb,
-                                          data);
+                                          helper);
 }
 
 void
 gs_page_update_app (GsPage *page, GsApp *app)
 {
        GsPagePrivate *priv = gs_page_get_instance_private (page);
-       GtkResponseType response;
+       GsPageHelper *helper;
        GtkWidget *dialog;
        AsScreenshot *ss;
        g_autofree gchar *escaped = NULL;
 
        /* non-firmware applications do not have to be prepared */
-       if (gs_app_get_kind (app) != AS_APP_KIND_FIRMWARE) {
-               gs_page_update_app_real (page, app);
-               return;
-       }
-
-       /* there are no steps required to put the device into DFU mode */
-       if (gs_app_get_screenshots (app)->len == 0) {
-               gs_page_update_app_real (page, app);
+       helper = g_slice_new0 (GsPageHelper);
+       helper->app = g_object_ref (app);
+       helper->page = g_object_ref (page);
+       if (gs_app_get_kind (app) != AS_APP_KIND_FIRMWARE ||
+           gs_app_get_screenshots (app)->len == 0) {
+               gs_plugin_loader_app_action_async (priv->plugin_loader,
+                                                  helper->app,
+                                                  GS_PLUGIN_LOADER_ACTION_UPDATE,
+                                                  priv->cancellable,
+                                                  gs_page_app_installed_cb,
+                                                  helper);
                return;
        }
 
        /* tell the user what they have to do */
        ss = g_ptr_array_index (gs_app_get_screenshots (app), 0);
        if (as_screenshot_get_caption (ss, NULL) == NULL) {
-               gs_page_update_app_real (page, app);
+               gs_plugin_loader_app_action_async (priv->plugin_loader,
+                                                  helper->app,
+                                                  GS_PLUGIN_LOADER_ACTION_UPDATE,
+                                                  priv->cancellable,
+                                                  gs_page_app_installed_cb,
+                                                  helper);
                return;
        }
+
+       /* show user caption */
        dialog = gtk_message_dialog_new (gs_shell_get_window (priv->shell),
                                         GTK_DIALOG_MODAL,
                                         GTK_MESSAGE_INFO,
@@ -238,20 +252,58 @@ gs_page_update_app (GsPage *page, GsApp *app)
                                                    "%s", escaped);
        /* TRANSLATORS: this is button text to update the firware */
        gtk_dialog_add_button (GTK_DIALOG (dialog), _("Install"), GTK_RESPONSE_OK);
-       response = gtk_dialog_run (GTK_DIALOG (dialog));
-       if (response == GTK_RESPONSE_OK)
-               gs_page_update_app_real (page, app);
-       gtk_widget_destroy (dialog);
+
+       /* handle this async */
+       g_signal_connect (dialog, "response",
+                         G_CALLBACK (gs_page_update_app_response_cb), helper);
+       gs_shell_modal_dialog_present (priv->shell, GTK_DIALOG (dialog));
+}
+
+static void
+gs_page_remove_app_response_cb (GtkDialog *dialog,
+                               gint response,
+                               GsPageHelper *helper)
+{
+       GsPagePrivate *priv = gs_page_get_instance_private (helper->page);
+
+       /* not agreed */
+       if (response != GTK_RESPONSE_OK) {
+               gs_page_helper_free (helper);
+               return;
+       }
+       g_debug ("remove %s", gs_app_get_id (helper->app));
+       gs_plugin_loader_app_action_async (priv->plugin_loader,
+                                          helper->app,
+                                          GS_PLUGIN_LOADER_ACTION_REMOVE,
+                                          priv->cancellable,
+                                          gs_page_app_removed_cb,
+                                          helper);
 }
 
 void
 gs_page_remove_app (GsPage *page, GsApp *app)
 {
        GsPagePrivate *priv = gs_page_get_instance_private (page);
-       GtkResponseType response;
+       GsPageHelper *helper;
        GtkWidget *dialog;
        g_autofree gchar *escaped = NULL;
 
+       /* pending install */
+       helper = g_slice_new0 (GsPageHelper);
+       helper->app = g_object_ref (app);
+       helper->page = g_object_ref (page);
+       if (gs_app_get_state (app) == AS_APP_STATE_QUEUED_FOR_INSTALL) {
+               g_debug ("remove %s", gs_app_get_id (app));
+               gs_plugin_loader_app_action_async (priv->plugin_loader,
+                                                  app,
+                                                  GS_PLUGIN_LOADER_ACTION_REMOVE,
+                                                  priv->cancellable,
+                                                  gs_page_app_removed_cb,
+                                                  helper);
+               return;
+       }
+
+       /* ask for confirmation */
        dialog = gtk_message_dialog_new (gs_shell_get_window (priv->shell),
                                         GTK_DIALOG_MODAL,
                                         GTK_MESSAGE_QUESTION,
@@ -265,26 +317,14 @@ gs_page_remove_app (GsPage *page, GsApp *app)
                                                    /* TRANSLATORS: longer dialog text */
                                                     _("%s will be removed, and you will have to install it 
to use it again."),
                                                     escaped);
+
        /* TRANSLATORS: this is button text to remove the application */
        gtk_dialog_add_button (GTK_DIALOG (dialog), _("Remove"), GTK_RESPONSE_OK);
-       if (gs_app_get_state (app) == AS_APP_STATE_QUEUED_FOR_INSTALL)
-               response = GTK_RESPONSE_OK; /* pending install */
-       else
-               response = gtk_dialog_run (GTK_DIALOG (dialog));
-       if (response == GTK_RESPONSE_OK) {
-               InstallRemoveData *data;
-               g_debug ("remove %s", gs_app_get_id (app));
-               data = g_slice_new0 (InstallRemoveData);
-               data->app = g_object_ref (app);
-               data->page = g_object_ref (page);
-               gs_plugin_loader_app_action_async (priv->plugin_loader,
-                                                  app,
-                                                  GS_PLUGIN_LOADER_ACTION_REMOVE,
-                                                  priv->cancellable,
-                                                  gs_page_app_removed_cb,
-                                                  data);
-       }
-       gtk_widget_destroy (dialog);
+
+       /* handle this async */
+       g_signal_connect (dialog, "response",
+                         G_CALLBACK (gs_page_remove_app_response_cb), helper);
+       gs_shell_modal_dialog_present (priv->shell, GTK_DIALOG (dialog));
 }
 
 static void
diff --git a/src/gs-shell-details.c b/src/gs-shell-details.c
index a96cb64..895abe1 100644
--- a/src/gs-shell-details.c
+++ b/src/gs-shell-details.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
- * Copyright (C) 2013-2014 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2013-2016 Richard Hughes <richard hughsie com>
  * Copyright (C) 2013 Matthias Clasen <mclasen redhat com>
  *
  * Licensed under the GNU General Public License Version 2
@@ -1161,9 +1161,7 @@ gs_shell_details_filename_to_app_cb (GObject *source,
                                                 _("Sorry, this did not work"));
                gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
                                                          "%s", error->message);
-               g_signal_connect (dialog, "response",
-                                 G_CALLBACK (gtk_widget_destroy), NULL);
-               gtk_window_present (GTK_WINDOW (dialog));
+               gs_shell_modal_dialog_present (self->shell, GTK_DIALOG (dialog));
 
                g_warning ("failed to convert to GsApp: %s", error->message);
 
@@ -1381,9 +1379,43 @@ gs_shell_details_app_history_button_cb (GtkWidget *widget, GsShellDetails *self)
 
        dialog = gs_history_dialog_new ();
        gs_history_dialog_set_app (GS_HISTORY_DIALOG (dialog), self->app);
+       gs_shell_modal_dialog_present (self->shell, GTK_DIALOG (dialog));
+}
+
+/**
+ * gs_shell_details_review_response_cb:
+ **/
+static void
+gs_shell_details_review_response_cb (GtkDialog *dialog,
+                                    gint response,
+                                    GsShellDetails *self)
+{
+       g_autofree gchar *text = NULL;
+       g_autoptr(GDateTime) now = NULL;
+       g_autoptr(GsReview) review = NULL;
+       GsReviewDialog *rdialog = GS_REVIEW_DIALOG (dialog);
+
+       /* not agreed */
+       if (response != GTK_RESPONSE_OK)
+               return;
 
-       gtk_window_set_transient_for (GTK_WINDOW (dialog), gs_shell_get_window (self->shell));
-       gtk_window_present (GTK_WINDOW (dialog));
+       review = gs_review_new ();
+       gs_review_set_summary (review, gs_review_dialog_get_summary (rdialog));
+       text = gs_review_dialog_get_text (rdialog);
+       gs_review_set_text (review, text);
+       gs_review_set_rating (review, gs_review_dialog_get_rating (rdialog));
+       gs_review_set_version (review, gs_app_get_version (self->app));
+       now = g_date_time_new_now_local ();
+       gs_review_set_date (review, now);
+
+       /* call into the plugins to set the new value */
+       gs_plugin_loader_review_action_async (self->plugin_loader,
+                                             self->app,
+                                             review,
+                                             GS_REVIEW_ACTION_SUBMIT,
+                                             self->cancellable,
+                                             gs_shell_details_app_set_review_cb,
+                                             self);
 }
 
 /**
@@ -1394,36 +1426,10 @@ gs_shell_details_write_review_cb (GtkButton *button,
                                  GsShellDetails *self)
 {
        GtkWidget *dialog;
-       GtkResponseType response;
-
        dialog = gs_review_dialog_new ();
-
-       gtk_window_set_transient_for (GTK_WINDOW (dialog), gs_shell_get_window (self->shell));
-       response = gtk_dialog_run (GTK_DIALOG (dialog));
-       if (response == GTK_RESPONSE_OK) {
-               g_autoptr(GsReview) review = NULL;
-               g_autoptr(GDateTime) now = NULL;
-               g_autofree gchar *text = NULL;
-
-               review = gs_review_new ();
-               gs_review_set_summary (review, gs_review_dialog_get_summary (GS_REVIEW_DIALOG (dialog)));
-               text = gs_review_dialog_get_text (GS_REVIEW_DIALOG (dialog));
-               gs_review_set_text (review, text);
-               gs_review_set_rating (review, gs_review_dialog_get_rating (GS_REVIEW_DIALOG (dialog)));
-               gs_review_set_version (review, gs_app_get_version (self->app));
-               now = g_date_time_new_now_local ();
-               gs_review_set_date (review, now);
-
-               /* call into the plugins to set the new value */
-               gs_plugin_loader_review_action_async (self->plugin_loader,
-                                                     self->app,
-                                                     review,
-                                                     GS_REVIEW_ACTION_SUBMIT,
-                                                     self->cancellable,
-                                                     gs_shell_details_app_set_review_cb,
-                                                     self);
-       }
-       gtk_widget_destroy (dialog);
+       g_signal_connect (dialog, "response",
+                         G_CALLBACK (gs_shell_details_review_response_cb), self);
+       gs_shell_modal_dialog_present (self->shell, GTK_DIALOG (dialog));
 }
 
 static void
diff --git a/src/gs-shell-installed.c b/src/gs-shell-installed.c
index 078176d..813843d 100644
--- a/src/gs-shell-installed.c
+++ b/src/gs-shell-installed.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
- * Copyright (C) 2013 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2013-2016 Richard Hughes <richard hughsie com>
  * Copyright (C) 2013 Matthias Clasen <mclasen redhat com>
  *
  * Licensed under the GNU General Public License Version 2
@@ -610,9 +610,9 @@ show_folder_dialog (GtkButton *button, GsShellInstalled *self)
        toplevel = gtk_widget_get_toplevel (GTK_WIDGET (button));
        apps = get_selected_apps (self);
        dialog = gs_app_folder_dialog_new (GTK_WINDOW (toplevel), apps);
-       gtk_window_present (GTK_WINDOW (dialog));
        g_signal_connect_swapped (dialog, "delete-event",
                                  G_CALLBACK (folder_dialog_done), self);
+       gs_shell_modal_dialog_present (self->shell, GTK_DIALOG (dialog));
 }
 
 static void
diff --git a/src/gs-shell-updates.c b/src/gs-shell-updates.c
index ba186c7..0e908d6 100644
--- a/src/gs-shell-updates.c
+++ b/src/gs-shell-updates.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
- * Copyright (C) 2013-2014 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2013-2016 Richard Hughes <richard hughsie com>
  *
  * Licensed under the GNU General Public License Version 2
  *
@@ -615,9 +615,7 @@ show_update_details (GsApp *app, GsShellUpdates *self)
 
        dialog = gs_update_dialog_new (self->plugin_loader);
        gs_update_dialog_show_update_details (GS_UPDATE_DIALOG (dialog), app);
-
-       gtk_window_set_transient_for (GTK_WINDOW (dialog), gs_shell_get_window (self->shell));
-       gtk_window_present (GTK_WINDOW (dialog));
+       gs_shell_modal_dialog_present (self->shell, GTK_DIALOG (dialog));
 }
 
 /**
@@ -837,7 +835,7 @@ gs_shell_updates_button_refresh_cb (GtkWidget *widget,
                g_signal_connect (dialog, "response",
                                  G_CALLBACK (gs_shell_updates_refresh_confirm_cb),
                                  self);
-               gtk_window_present (GTK_WINDOW (dialog));
+               gs_shell_modal_dialog_present (self->shell, GTK_DIALOG (dialog));
 
        /* no network connection */
        } else {
@@ -861,7 +859,7 @@ gs_shell_updates_button_refresh_cb (GtkWidget *widget,
                g_signal_connect (dialog, "response",
                                  G_CALLBACK (gs_shell_updates_refresh_confirm_cb),
                                  self);
-               gtk_window_present (GTK_WINDOW (dialog));
+               gs_shell_modal_dialog_present (self->shell, GTK_DIALOG (dialog));
        }
 }
 
diff --git a/src/gs-shell.c b/src/gs-shell.c
index 2563c1a..78afd28 100644
--- a/src/gs-shell.c
+++ b/src/gs-shell.c
@@ -77,6 +77,7 @@ typedef struct
        GtkWindow               *main_window;
        GQueue                  *back_entry_stack;
        gboolean                 ignore_next_search_changed_signal;
+       GPtrArray               *modal_dialogs;
 } GsShellPrivate;
 
 G_DEFINE_TYPE_WITH_PRIVATE (GsShell, gs_shell, G_TYPE_OBJECT)
@@ -89,6 +90,50 @@ enum {
 static guint signals [SIGNAL_LAST] = { 0 };
 
 /**
+ * gs_shell_modal_dialog_present:
+ **/
+static void
+gs_shell_modal_dialog_response_cb (GtkDialog *dialog,
+                                  gint response_id,
+                                  GsShell *shell)
+{
+       GsShellPrivate *priv = gs_shell_get_instance_private (shell);
+       g_debug ("handling modal dialog response %i for %p",
+                response_id, dialog);
+       g_ptr_array_remove (priv->modal_dialogs, dialog);
+}
+
+/**
+ * gs_shell_modal_dialog_present:
+ **/
+void
+gs_shell_modal_dialog_present (GsShell *shell, GtkDialog *dialog)
+{
+       GsShellPrivate *priv = gs_shell_get_instance_private (shell);
+       GtkWindow *parent;
+
+       /* show new modal on top of old modal */
+       if (priv->modal_dialogs->len > 0) {
+               parent = g_ptr_array_index (priv->modal_dialogs,
+                                           priv->modal_dialogs->len - 1);
+               g_debug ("using old modal %p as parent", parent);
+       } else {
+               parent = priv->main_window;
+               g_debug ("using main window");
+       }
+       gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
+
+       /* add to stack, transfer ownership to here */
+       g_ptr_array_add (priv->modal_dialogs, dialog);
+
+       /* present the new one */
+       g_signal_connect (dialog, "response",
+                         G_CALLBACK (gs_shell_modal_dialog_response_cb), shell);
+       gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+       gtk_window_present (GTK_WINDOW (dialog));
+}
+
+/**
  * gs_shell_is_active:
  **/
 gboolean
@@ -277,6 +322,10 @@ gs_shell_change_mode (GsShell *shell,
 
        widget = gs_page_get_header_end_widget (new_page);
        gs_shell_set_header_end_widget (shell, widget);
+
+       /* destroy any existing modals */
+       if (priv->modal_dialogs != NULL)
+               g_ptr_array_set_size (priv->modal_dialogs, 0);
 }
 
 /**
@@ -815,10 +864,7 @@ gs_shell_show_sources (GsShell *shell)
        GtkWidget *dialog;
 
        dialog = gs_sources_dialog_new (priv->main_window, priv->plugin_loader);
-       g_signal_connect_swapped (dialog, "response",
-                                 G_CALLBACK (gtk_widget_destroy),
-                                 dialog);
-       gtk_window_present (GTK_WINDOW (dialog));
+       gs_shell_modal_dialog_present (shell, GTK_DIALOG (dialog));
 }
 
 void
@@ -908,6 +954,7 @@ gs_shell_dispose (GObject *object)
        g_clear_object (&priv->plugin_loader);
        g_clear_object (&priv->header_start_widget);
        g_clear_object (&priv->header_end_widget);
+       g_clear_pointer (&priv->modal_dialogs, (GDestroyNotify) g_ptr_array_unref);
 
        G_OBJECT_CLASS (gs_shell_parent_class)->dispose (object);
 }
@@ -939,6 +986,7 @@ gs_shell_init (GsShell *shell)
 
        priv->back_entry_stack = g_queue_new ();
        priv->ignore_primary_buttons = FALSE;
+       priv->modal_dialogs = g_ptr_array_new_with_free_func ((GDestroyNotify) gtk_widget_destroy);
 }
 
 /**
diff --git a/src/gs-shell.h b/src/gs-shell.h
index ab41856..4a68228 100644
--- a/src/gs-shell.h
+++ b/src/gs-shell.h
@@ -65,6 +65,8 @@ void           gs_shell_change_mode           (GsShell        *shell,
                                                 gboolean        scroll_up);
 void            gs_shell_set_mode              (GsShell        *shell,
                                                 GsShellMode     mode);
+void            gs_shell_modal_dialog_present  (GsShell        *shell,
+                                                GtkDialog      *dialog);
 GsShellMode     gs_shell_get_mode              (GsShell        *shell);
 const gchar    *gs_shell_get_mode_string       (GsShell        *shell);
 void            gs_shell_show_installed_updates(GsShell        *shell);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]