[PATCH 06/10] Add a window to manage Anaconda screen transitions.

David Shea dshea at redhat.com
Mon Jun 30 19:41:12 UTC 2014


Instead of each hub and spoke being a separate Gtk window, create a
single window to contain every non-dialog screen. This changes the
parent type of AnacondaBaseWindow to GtkBin instead of GtkWindow.

This change is mostly helpful in live environments where we have less
control over the window manager. Each spoke and hub appeared as a
separate window the in the window manager and it made things look kind
of weird, as well as opening up the possibility for some troublesome
window interactions.

Remove the separate GtkWindowGroup for exception windows, since there's
no longer a separate Gtk.main running for spokes.
---
 pyanaconda/ui/common.py              |  2 +-
 pyanaconda/ui/gui/__init__.py        | 98 ++++++++++++++++++++++++++++++++----
 pyanaconda/ui/gui/hubs/__init__.py   | 64 +++++++++--------------
 pyanaconda/ui/gui/spokes/__init__.py |  7 ++-
 pyanaconda/ui/gui/utils.py           |  2 +-
 widgets/src/BaseWindow.c             | 14 +++---
 widgets/src/BaseWindow.h             |  6 +--
 widgets/src/HubWindow.c              |  9 ++--
 widgets/src/SpokeWindow.c            | 29 ++---------
 widgets/src/StandaloneWindow.c       | 23 +--------
 10 files changed, 136 insertions(+), 118 deletions(-)

diff --git a/pyanaconda/ui/common.py b/pyanaconda/ui/common.py
index d083ba4..f0ac602 100644
--- a/pyanaconda/ui/common.py
+++ b/pyanaconda/ui/common.py
@@ -226,7 +226,7 @@ class Spoke(UIObject):
         self.instclass = instclass
         self.applyOnSkip = False
 
-        self._visitedSinceApplied = True
+        self.visitedSinceApplied = True
 
     @property
     def storage(self):
diff --git a/pyanaconda/ui/gui/__init__.py b/pyanaconda/ui/gui/__init__.py
index 604a3b5..db3eb82 100644
--- a/pyanaconda/ui/gui/__init__.py
+++ b/pyanaconda/ui/gui/__init__.py
@@ -123,7 +123,6 @@ class GUIObject(common.UIObject):
         else:
             self.builder.add_from_file(self._findUIFile())
 
-        ANACONDA_WINDOW_GROUP.add_window(self.window)
         self.builder.connect_signals(self)
 
         # Keybinder from GI needs to be initialized before use
@@ -169,7 +168,7 @@ class GUIObject(common.UIObject):
 
     @property
     def window(self):
-        """Return the top-level object out of the GtkBuilder representation
+        """Return the object out of the GtkBuilder representation
            previously loaded by the load method.
         """
 
@@ -180,6 +179,11 @@ class GUIObject(common.UIObject):
 
         return self._window
 
+    @property
+    def main_window(self):
+        """Return the top-level window containing this GUIObject."""
+        return self.window.get_toplevel()
+
     def clear_info(self):
         """Clear any info bar from the bottom of the screen."""
         self.window.clear_info()
@@ -236,6 +240,80 @@ class ErrorDialog(GUIObject):
         rc = self.window.run()
         return rc
 
+class MainWindow(Gtk.Window):
+    """This is a top-level, full size window containing the Anaconda screens."""
+
+    def __init__(self):
+        Gtk.Window.__init__(self)
+
+        # Create a stack and a list of what's been added to the stack
+        self._stack = Gtk.Stack()
+        self._stack_contents = set()
+
+        # Create an accel group for the F12 accelerators added after window transitions
+        self._accel_group = Gtk.AccelGroup()
+        self.add_accel_group(self._accel_group)
+
+        # Set properties on the window
+        self.set_decorated(False)
+        self.maximize()
+        self.add(self._stack)
+        self.show_all()
+
+        self._current_action = None
+
+    @property
+    def current_action(self):
+        return self._current_action
+
+    def _setVisibleChild(self, child):
+        # Remove the F12 accelerator from the old window
+        old_screen = self._stack.get_visible_child()
+        if old_screen:
+            old_screen.remove_accelerator(self._accel_group, Gdk.KEY_F12, 0)
+
+        # Check if the widget is already on the stack
+        if child not in self._stack_contents:
+            self._stack.add(child.window)
+            self._stack_contents.add(child)
+            child.window.show_all()
+
+        # It would be handy for F12 to continue to work like it did in the old
+        # UI, by skipping you to the next screen or sending you back to the hub
+        if isinstance(child.window, AnacondaWidgets.BaseStandalone):
+            child.window.add_accelerator("continue-clicked", self._accel_group,
+                    Gdk.KEY_F12, 0, 0)
+        elif isinstance(child.window, AnacondaWidgets.SpokeWindow):
+            child.window.add_accelerator("button-clicked", self._accel_group,
+                    Gdk.KEY_F12, 0, 0)
+
+        self._stack.set_visible_child(child.window)
+
+    def setCurrentAction(self, standalone):
+        """Set the current standalone widget.
+
+           This changes the currently displayed screen and, if the standalone
+           is a hub, sets the hub as the screen to which spokes will return.
+
+           :param AnacondaWidgets.BaseStandalone standalone: the new standalone action
+        """
+        self._current_action = standalone
+        self._setVisibleChild(standalone)
+
+    def enterSpoke(self, spoke):
+        """Enter a spoke.
+
+           The spoke will be displayed as the current screen, but the current-action
+           to which the spoke will return will not be changed.
+
+           :param AnacondaWidgets.SpokeWindow spoke: a spoke to enter
+        """
+        self._setVisibleChild(spoke)
+
+    def returnToHub(self):
+        """Exit a spoke and return to a hub."""
+        self._setVisibleChild(self._current_action)
+
 class GraphicalUserInterface(UserInterface):
     """This is the standard GTK+ interface we try to steer everything to using.
        It is suitable for use both directly and via VNC.
@@ -252,12 +330,16 @@ class GraphicalUserInterface(UserInterface):
 
         self.data = None
 
+        self.mainWindow = MainWindow()
+
         self._distributionText = distributionText
         self._isFinal = isFinal
         self._quitDialog = quitDialog
         self._mehInterface = GraphicalExceptionHandlingIface(
                                     self.lightbox_over_current_action)
 
+        ANACONDA_WINDOW_GROUP.add_window(self.mainWindow)
+
     basemask = "pyanaconda.ui"
     basepath = os.path.dirname(__file__)
     updatepath = "/tmp/updates/pyanaconda/ui"
@@ -349,7 +431,7 @@ class GraphicalUserInterface(UserInterface):
 
         # if there are no actions (not populated yet), we can do nothing
         if len(self._actions) > 0 and self._currentAction:
-            lightbox = AnacondaWidgets.Lightbox(parent_window=self._currentAction.window)
+            lightbox = AnacondaWidgets.Lightbox(parent_window=self.mainWindow)
             ANACONDA_WINDOW_GROUP.add_window(lightbox)
             window.main_window.set_transient_for(lightbox)
 
@@ -423,7 +505,7 @@ class GraphicalUserInterface(UserInterface):
         # try to make sure a logo image is present
         self._assureLogoImage()
 
-        self._currentAction.window.show_all()
+        self.mainWindow.setCurrentAction(self._currentAction)
 
         # Do this at the last possible minute.
         unbusyCursor()
@@ -529,8 +611,7 @@ class GraphicalUserInterface(UserInterface):
 
         # Do this last.  Setting up curAction could take a while, and we want
         # to leave something on the screen while we work.
-        nextAction.window.show_all()
-        self._currentAction.window.hide()
+        self.mainWindow.setCurrentAction(nextAction)
         self._currentAction = nextAction
         self._actions.pop(0)
 
@@ -572,10 +653,7 @@ class GraphicalExceptionHandlingIface(meh.ui.gui.GraphicalIntf):
 
         self._lightbox_func(exc_window)
 
-        # without a new GtkWindowGroup, python-meh's window is insensitive if it
-        # appears above a spoke (Gtk.Window running its own Gtk.main loop)
-        window_group = Gtk.WindowGroup()
-        window_group.add_window(exc_window.main_window)
+        ANACONDA_WINDOW_GROUP.add_window(exc_window.main_window)
 
         # the busy cursor may be set
         unbusyCursor()
diff --git a/pyanaconda/ui/gui/hubs/__init__.py b/pyanaconda/ui/gui/hubs/__init__.py
index 3645759..b89e596 100644
--- a/pyanaconda/ui/gui/hubs/__init__.py
+++ b/pyanaconda/ui/gui/hubs/__init__.py
@@ -95,40 +95,6 @@ class Hub(GUIObject, common.Hub):
 
         self._checker = None
 
-    def _runSpoke(self, action):
-        from gi.repository import Gtk
-
-        # This duplicates code in widgets/src/BaseWindow.c, but we want to make sure
-        # maximize gets called every time a spoke is displayed to prevent the 25%
-        # UI from showing up.
-        action.window.maximize()
-        action.window.set_property("expand", True)
-
-        action.entry_logger()
-
-        action.refresh()
-
-        action.window.set_transient_for(self.window)
-        action.window.show_all()
-
-        # Start a recursive main loop for this spoke, which will prevent
-        # signals from going to the underlying (but still displayed) Hub and
-        # prevent the user from switching away.  It's up to the spoke's back
-        # button handler to kill its own layer of main loop.
-        Gtk.main()
-        action.window.set_transient_for(None)
-
-        action._visitedSinceApplied = True
-
-        # Don't take _visitedSinceApplied into account here.  It will always be
-        # True from the line above.
-        if action.changed and (not action.skipTo or (action.skipTo and action.applyOnSkip)):
-            action.apply()
-            action.execute()
-            action._visitedSinceApplied = False
-
-        action.exit_logger()
-
     def _createBox(self):
         from gi.repository import Gtk, AnacondaWidgets
         from pyanaconda.ui.gui.utils import setViewportBackground
@@ -198,9 +164,9 @@ class Hub(GUIObject, common.Hub):
 
                 # If this is a kickstart install, attempt to execute any provided ksdata now.
                 if flags.automatedInstall and spoke.ready and spoke.changed and \
-                   spoke._visitedSinceApplied:
+                   spoke.visitedSinceApplied:
                     spoke.execute()
-                    spoke._visitedSinceApplied = False
+                    spoke.visitedSinceApplied = False
 
                 selectors.append(spoke.selector)
 
@@ -327,9 +293,9 @@ class Hub(GUIObject, common.Hub):
                     # _createBox skipped.  Now that it's become ready, do it.  Note
                     # that we also provide a way to skip this processing (see comments
                     # communication.py) to prevent getting caught in a loop.
-                    if not args[1] and spoke.changed and spoke._visitedSinceApplied:
+                    if not args[1] and spoke.changed and spoke.visitedSinceApplied:
                         spoke.execute()
-                        spoke._visitedSinceApplied = False
+                        spoke.visitedSinceApplied = False
 
                     if self.continuePossible:
                         if self._inSpoke:
@@ -383,8 +349,24 @@ class Hub(GUIObject, common.Hub):
         # that he is done configuring by pressing the continue button.
         self._autoContinue = False
 
+        # Enter the spoke
         self._inSpoke = True
-        self._runSpoke(spoke)
+        spoke.entry_logger()
+        spoke.refresh()
+        self.main_window.enterSpoke(spoke)
+
+    def spoke_done(self, spoke):
+        spoke.visitedSinceApplied = True
+
+        # Don't take visitedSinceApplied into account here.  It will always be
+        # True from the line above.
+        if spoke.changed and (not spoke.skipTo or (spoke.skipTo and spoke.applyOnSkip)):
+            spoke.apply()
+            spoke.execute()
+            spoke.visitedSinceApplied = False
+
+        spoke.exit_logger()
+
         self._inSpoke = False
 
         # Now update the selector with the current status and completeness.
@@ -403,5 +385,7 @@ class Hub(GUIObject, common.Hub):
             spoke.skipTo = None
 
             self._on_spoke_clicked(self._spokes[dest].selector, None, self._spokes[dest])
-
+        # Otherwise, switch back to the hub (that's us!)
+        else:
+            self.main_window.returnToHub()
 
diff --git a/pyanaconda/ui/gui/spokes/__init__.py b/pyanaconda/ui/gui/spokes/__init__.py
index 9a620b6..86706a4 100644
--- a/pyanaconda/ui/gui/spokes/__init__.py
+++ b/pyanaconda/ui/gui/spokes/__init__.py
@@ -84,7 +84,6 @@ class NormalSpoke(Spoke, common.NormalSpoke):
         common.NormalSpoke.__init__(self, data, storage, payload, instclass)
 
     def on_back_clicked(self, window):
-        from gi.repository import Gtk
-
-        self.window.hide()
-        Gtk.main_quit()
+        # Notify the hub that we're finished.
+        # The hub will be the current-action of the main window.
+        self.main_window.current_action.spoke_done(self)
diff --git a/pyanaconda/ui/gui/utils.py b/pyanaconda/ui/gui/utils.py
index 4a3bc21..51bcc0d 100644
--- a/pyanaconda/ui/gui/utils.py
+++ b/pyanaconda/ui/gui/utils.py
@@ -302,7 +302,7 @@ def enlightbox(mainWindow, dialog):
     # importing globally would cause a circular dependency
     from pyanaconda.ui.gui import ANACONDA_WINDOW_GROUP
 
-    lightbox = AnacondaWidgets.Lightbox(parent_window=mainWindow)
+    lightbox = AnacondaWidgets.Lightbox(parent_window=mainWindow.get_toplevel())
     ANACONDA_WINDOW_GROUP.add_window(lightbox)
     dialog.set_position(Gtk.WindowPosition.CENTER_ALWAYS)
     dialog.set_transient_for(lightbox)
diff --git a/widgets/src/BaseWindow.c b/widgets/src/BaseWindow.c
index cabbcfc..9f5282a 100644
--- a/widgets/src/BaseWindow.c
+++ b/widgets/src/BaseWindow.c
@@ -31,11 +31,11 @@
  * @title: AnacondaBaseWindow
  * @short_description: Top-level, non-resizeable window
  *
- * A #AnacondaBaseWindow is a top-level, non-resizeable window that contains
- * other widgets and serves as the base class from which all other specialized
- * Anaconda windows are derived.  It is undecorated.
+ * A #AnacondaBaseWindow is a widget that contains other widgets and serves as
+ * the base class from which all other specialized Anaconda windows are
+ * derived.
  *
- * The window consists of two areas:
+ * The AnacondaBaseWindow consists of two areas:
  *
  * - A navigation area in the top of the screen, consisting of some basic
  *   information about what is being displayed and what is being installed.
@@ -127,7 +127,7 @@ static void format_beta_label(AnacondaBaseWindow *window, const char *markup);
 
 static gboolean anaconda_base_window_info_bar_clicked(GtkWidget *widget, GdkEvent *event, AnacondaBaseWindow *win);
 
-G_DEFINE_TYPE_WITH_CODE(AnacondaBaseWindow, anaconda_base_window, GTK_TYPE_WINDOW,
+G_DEFINE_TYPE_WITH_CODE(AnacondaBaseWindow, anaconda_base_window, GTK_TYPE_BIN,
                         G_IMPLEMENT_INTERFACE(GTK_TYPE_BUILDABLE, anaconda_base_window_buildable_init))
 
 static void anaconda_base_window_class_init(AnacondaBaseWindowClass *klass) {
@@ -223,9 +223,7 @@ static void anaconda_base_window_init(AnacondaBaseWindow *win) {
     win->priv->orig_distro = NULL;
     win->priv->orig_beta = NULL;
 
-    /* Set properties on the parent (Gtk.Window) class. */
-    gtk_window_set_decorated(GTK_WINDOW(win), FALSE);
-    gtk_window_maximize(GTK_WINDOW(win));
+    /* Set properties on the parent (Gtk.Bin) class. */
     gtk_widget_set_hexpand(GTK_WIDGET(win), TRUE);
     gtk_widget_set_vexpand(GTK_WIDGET(win), TRUE);
     gtk_container_set_border_width(GTK_CONTAINER(win), 0);
diff --git a/widgets/src/BaseWindow.h b/widgets/src/BaseWindow.h
index 6c08ee8..eb6191d 100644
--- a/widgets/src/BaseWindow.h
+++ b/widgets/src/BaseWindow.h
@@ -42,7 +42,7 @@ typedef struct _AnacondaBaseWindowPrivate AnacondaBaseWindowPrivate;
  * be directly accessed.
  */
 struct _AnacondaBaseWindow {
-    GtkWindow                  parent;
+    GtkBin                     parent;
 
     /*< private >*/
     AnacondaBaseWindowPrivate *priv;
@@ -53,12 +53,12 @@ struct _AnacondaBaseWindow {
  * @parent_class: The object class structure needs to be the first element in
  *                the widget class structure in order for the class mechanism
  *                to work correctly.  This allows a AnacondaBaseWindowClass
- *                pointer to be cast to a #GtkWindow pointer.
+ *                pointer to be cast to a #GtkBin pointer.
  * @info_bar_clicked : Function pointer called when the #AnacondaBaseWindow::info-bar-clicked
  *                     signal is emitted.
  */
 struct _AnacondaBaseWindowClass {
-    GtkWindowClass parent_class;
+    GtkBinClass parent_class;
 
     void (* info_bar_clicked) (AnacondaBaseWindow *window);
 };
diff --git a/widgets/src/HubWindow.c b/widgets/src/HubWindow.c
index 927dd3d..3b5cdea 100644
--- a/widgets/src/HubWindow.c
+++ b/widgets/src/HubWindow.c
@@ -26,12 +26,11 @@
  * @title: AnacondaHubWindow
  * @short_description: Window for displaying a Hub
  *
- * A #AnacondaHubWindow is a top-level window that displays a hub on the
- * entire screen.  A Hub allows selection of multiple configuration spokes
- * from a single interface, as well as a place to display current configuration
- * selections.
+ * A #AnacondaHubWindow is a widget that displays a hub on the screen.  A Hub
+ * allows selection of multiple configuration spokes from a single interface,
+ * as well as a place to display current configuration selections.
  *
- * The window consists of three areas:
+ * The AnacondaHubWindow consists of three areas:
  *
  * - A navigation area in the top of the screen, inherited from #AnacondaBaseWindow.
  *
diff --git a/widgets/src/SpokeWindow.c b/widgets/src/SpokeWindow.c
index 461e493..a1a46f1 100644
--- a/widgets/src/SpokeWindow.c
+++ b/widgets/src/SpokeWindow.c
@@ -30,11 +30,11 @@
  * @title: AnacondaSpokeWindow
  * @short_description: Window for displaying single spokes
  *
- * A #AnacondaSpokeWindow is a top-level window that displays a single spoke
- * on the entire screen.  Examples include the keyboard and language
- * configuration screens off the first hub.
+ * A #AnacondaSpokeWindow is a widget that displays a single spoke on the
+ * screen.  Examples include the keyboard and language configuration screens
+ * off the first hub.
  *
- * The window consists of two areas:
+ * The AnacondaSpokeWindow consists of two areas:
  *
  * - A navigation area in the top of the screen, inherited from #AnacondaBaseWindow
  *   and augmented with a button in the upper left corner.
@@ -58,7 +58,6 @@ struct _AnacondaSpokeWindowPrivate {
 
 G_DEFINE_TYPE(AnacondaSpokeWindow, anaconda_spoke_window, ANACONDA_TYPE_BASE_WINDOW)
 
-static void anaconda_spoke_window_realize(GtkWidget *widget, gpointer user_data);
 static void anaconda_spoke_window_button_clicked(GtkButton *button,
                                                  AnacondaSpokeWindow *win);
 
@@ -113,11 +112,6 @@ static void anaconda_spoke_window_init(AnacondaSpokeWindow *win) {
                                             ANACONDA_TYPE_SPOKE_WINDOW,
                                             AnacondaSpokeWindowPrivate);
 
-    g_signal_connect(win, "map", G_CALLBACK(anaconda_spoke_window_realize), NULL);
-
-    /* Set some default properties. */
-    gtk_window_set_modal(GTK_WINDOW(win), TRUE);
-
     /* Create the button. */
     win->priv->button = gtk_button_new_with_mnemonic(DEFAULT_BUTTON_LABEL);
     gtk_widget_set_halign(win->priv->button, GTK_ALIGN_START);
@@ -143,21 +137,6 @@ static void anaconda_spoke_window_init(AnacondaSpokeWindow *win) {
     gtk_grid_attach(GTK_GRID(nav_area), win->priv->button, 0, 1, 1, 2);
 }
 
-static void anaconda_spoke_window_realize(GtkWidget *widget, gpointer user_data) {
-    GtkAccelGroup *accel_group;
-    AnacondaSpokeWindow *window = ANACONDA_SPOKE_WINDOW(widget);
-
-    /* Pressing F12 should send you back to the hub, similar to how the old UI worked. */
-    accel_group = gtk_accel_group_new();
-    gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
-    gtk_widget_add_accelerator(window->priv->button,
-                               "clicked",
-                               accel_group,
-                               GDK_KEY_F12,
-                               0,
-                               0);
-}
-
 static void anaconda_spoke_window_button_clicked(GtkButton *button,
                                                  AnacondaSpokeWindow *win) {
     g_signal_emit(win, window_signals[SIGNAL_BUTTON_CLICKED], 0);
diff --git a/widgets/src/StandaloneWindow.c b/widgets/src/StandaloneWindow.c
index 4c489c2..5d0836a 100644
--- a/widgets/src/StandaloneWindow.c
+++ b/widgets/src/StandaloneWindow.c
@@ -29,12 +29,12 @@
  * @title: AnacondaStandaloneWindow
  * @short_description: Window for displaying standalone spokes
  *
- * A #AnacondaStandaloneWindow is a top-level window that displays a standalone
+ * A #AnacondaStandaloneWindow is a widget that displays a standalone
  * spoke.  A standalone spoke is like a normal spoke, but is not entered via a
  * hub.  Instead, it is displayed by itself.  Examples include the welcome and
  * network configuration screens at the beginning of installation.
  *
- * The window consist of three areas:
+ * The AnacondaStandaloneWindow consist of three areas:
  *
  * - A navigation area in the top of the screen, inherited from #AnacondaBaseWindow.
  *
@@ -60,8 +60,6 @@ enum {
 };
 
 static void anaconda_standalone_window_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
-static void anaconda_standalone_window_realize(GtkWidget *widget,
-                                               AnacondaStandaloneWindow *win);
 
 G_DEFINE_TYPE(AnacondaStandaloneWindow, anaconda_standalone_window, ANACONDA_TYPE_BASE_STANDALONE)
 
@@ -165,23 +163,6 @@ static void anaconda_standalone_window_init(AnacondaStandaloneWindow *win) {
     gtk_container_add(GTK_CONTAINER(win->priv->button_box), win->priv->continue_button);
 
     gtk_box_pack_start(GTK_BOX(main_box), win->priv->button_box, FALSE, TRUE, 0);
-
-    /* It would be handy for F12 to continue to work like it did in the old
-     * UI, by skipping you to the next screen.
-     */
-    g_signal_connect(win, "realize", G_CALLBACK(anaconda_standalone_window_realize), win);
-}
-
-static void anaconda_standalone_window_realize(GtkWidget *widget,
-                                               AnacondaStandaloneWindow *win) {
-    GtkAccelGroup *accel_group = gtk_accel_group_new();
-    gtk_window_add_accel_group(GTK_WINDOW(win), accel_group);
-    gtk_widget_add_accelerator(win->priv->continue_button,
-                               "clicked",
-                               accel_group,
-                               GDK_KEY_F12,
-                               0,
-                               0);
 }
 
 /**
-- 
2.0.0



More information about the anaconda-patches mailing list