[PATCH 1/3] Add a help button to every Anaconda screen
Anne Mulhern
amulhern at redhat.com
Mon Aug 18 13:52:16 UTC 2014
----- Original Message -----
> From: "Martin Kolman" <mkolman at redhat.com>
> To: anaconda-patches at lists.fedorahosted.org
> Sent: Thursday, August 14, 2014 7:01:21 AM
> Subject: [PATCH 1/3] Add a help button to every Anaconda screen
>
> This makes context aware help possible for every Anaconda screen by
> providing a help button in the upper right corner of the screen.
> Clicking the button opens the yelp help viewer with either a help page
> tailored for the given screen, the main page of the installation guide
> (if no help is available for the screen) or a "no help found" placeholder
> page.
> The new helpFile GUIObject attribute is used to specify which help
> file to show for the given screen. If no help file is specified
> either the main page of the Installation Guide will be shown (if
> available) or the placeholder page will be shown.
>
> Signed-off-by: Martin Kolman <mkolman at redhat.com>
> ---
> anaconda.spec.in | 1 +
> data/HelpPlaceholder.html | 5 ++
> data/HelpPlaceholderWithLinks.html | 13 ++++++
> data/Makefile.am | 2 +
> pyanaconda/constants.py | 6 +++
> pyanaconda/ihelp.py | 90
> ++++++++++++++++++++++++++++++++++++
> pyanaconda/ui/gui/__init__.py | 12 +++++
> pyanaconda/ui/gui/spokes/__init__.py | 9 ++++
> widgets/glade/AnacondaWidgets.xml | 5 ++
> widgets/src/BaseWindow.c | 48 +++++++++++++++++++
> widgets/src/BaseWindow.h | 3 ++
> 11 files changed, 194 insertions(+)
> create mode 100644 data/HelpPlaceholder.html
> create mode 100644 data/HelpPlaceholderWithLinks.html
> create mode 100644 pyanaconda/ihelp.py
>
> diff --git a/anaconda.spec.in b/anaconda.spec.in
> index e23d49c..1c6828f 100644
> --- a/anaconda.spec.in
> +++ b/anaconda.spec.in
> @@ -174,6 +174,7 @@ Requires: zenity
> %endif
> Requires: keybinder3
> Requires: NetworkManager-wifi
> +Requires: yelp
>
> %description gui
> This package contains graphical user interface for the Anaconda installer.
> diff --git a/data/HelpPlaceholder.html b/data/HelpPlaceholder.html
> new file mode 100644
> index 0000000..4d1781a
> --- /dev/null
> +++ b/data/HelpPlaceholder.html
> @@ -0,0 +1,5 @@
> +<body>
> +<h1>The Anaconda built-in help</h1>
> +<p>...is not yet available for this screen.</p>
> +<p>You can check the Anaconda wiki page, the Fedora Installation Guide or
> other online help resources instead.</p>
> +</body>
> diff --git a/data/HelpPlaceholderWithLinks.html
> b/data/HelpPlaceholderWithLinks.html
> new file mode 100644
> index 0000000..5fe0485
> --- /dev/null
> +++ b/data/HelpPlaceholderWithLinks.html
> @@ -0,0 +1,13 @@
> +<body>
> +<h1>The Anaconda built-in help</h1>
> +<p>...is not yet available for this screen.</p>
> +<p>You can check the Anaconda wiki page, the Fedora Installation Guide or
> other online help resources instead:</p>
> +<p>
> +<ul>
> + <li><a
> href="http://docs.fedoraproject.org/en-US/Fedora/20/html/Installation_Guide/">Fedora
> Installation Guide</a></li>
> + <li><a href="https://fedoraproject.org/wiki/Anaconda">Anaconda
> Wiki</a></li>
> + <li><a
> href="http://fedoraproject.org/wiki/Anaconda_Boot_Options">Anaconda Boot
> Options</a></li>
> + <li><a href="http://fedoraproject.org/wiki/Anaconda/Kickstart">Anaconda
> Kickstart</a></li>
> +</ul>
> +</p>
> +</body>
> diff --git a/data/Makefile.am b/data/Makefile.am
> index bcc6bf5..17926b3 100644
> --- a/data/Makefile.am
> +++ b/data/Makefile.am
> @@ -26,6 +26,8 @@ dist_pkgdata_DATA = interactive-defaults.ks \
> anaconda-gtk.css
>
> helpdir = $(datadir)/$(PACKAGE_NAME)
> +docsdir = $(datadir)/docs/$(PACKAGE_NAME)
> dist_help_DATA = anaconda_options.txt
> +dist_docs_DATA = HelpPlaceholder.html HelpPlaceholderWithLinks.html
>
> MAINTAINERCLEANFILES = Makefile.in
> diff --git a/pyanaconda/constants.py b/pyanaconda/constants.py
> index 18c2a1f..5583162 100644
> --- a/pyanaconda/constants.py
> +++ b/pyanaconda/constants.py
> @@ -161,3 +161,9 @@ DEFAULT_AUTOPART_TYPE = AUTOPART_TYPE_LVM
>
> import logging
> LOGLVL_LOCK = logging.DEBUG-1
> +
> +# help
> +HELP_FOLDER = "/usr/share/doc/anaconda"
> +HELP_MAIN_PAGE = "Installation_Guide.xml"
> +HELP_PLACEHOLDER = "HelpPlaceholder.html"
> +HELP_PLACEHOLDER_WITH_LINKS = "HelpPlaceholderWithLinks.html"
> diff --git a/pyanaconda/ihelp.py b/pyanaconda/ihelp.py
> new file mode 100644
> index 0000000..dffd7d8
> --- /dev/null
> +++ b/pyanaconda/ihelp.py
> @@ -0,0 +1,90 @@
> +#
> +# Copyright (C) 2014 Red Hat, Inc.
> +#
> +# This copyrighted material is made available to anyone wishing to use,
> +# modify, copy, or redistribute it subject to the terms and conditions of
> +# the GNU General Public License v.2, or (at your option) any later version.
> +# This program is distributed in the hope that it will be useful, but
> WITHOUT
> +# ANY WARRANTY expressed or implied, including the implied warranties of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
> +# Public License for more details. You should have received a copy of the
> +# GNU General Public License along with this program; if not, write to the
> +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
> MA
> +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
> +# source code or documentation are not subject to the GNU General Public
> +# License and may only be used or replicated with the express permission of
> +# Red Hat, Inc.
> +#
> +# Red Hat Author(s): Martin Kolman <mkolman at redhat.com>
> +#
> +"""
> +Anaconda built-in help module
> +"""
> +import os
> +import subprocess
> +
> +from pyanaconda import constants
> +from pyanaconda.flags import flags
> +
> +import logging
> +log = logging.getLogger("anaconda")
> +
> +yelp_process = None
> +
> +def get_help_path(help_file):
> + """Return the full path for the given help file name,
> + if the help file path does not exist a fallback path is returned.
> + There are actually two possible fallback paths that might be returned:
> + * first we try to return path to the main page of the installation guide
> + (if it exists)
> + * if we can't find the main page of the installation page, path to a
> + "no help found" placeholder bundled with Anaconda is returned
> +
> + :param str help_file: help file name
> + :return str: full path to the help file requested or to a placeholder
> + """
> + # check if the screen has any helpFile defined
> + if help_file:
> + # check if the help file exists
> + help_path = os.path.join(constants.HELP_FOLDER, help_file)
> + if os.path.isfile(help_path):
> + return help_path
> + else:
> + log.debug("the %s help file has been requested, but is not
> available", help_file)
> +
> + # the screen did not have a helpFile defined or the defined help file
> + # does not exist, so next try to check if we can find the main page
> + # of the installation guide and use it instead
> + help_path = os.path.join(constants.HELP_FOLDER,
> constants.HELP_MAIN_PAGE)
> + if os.path.exists(help_path):
> + return help_path
> +
> + # looks like the installation guide is not available, so just return
> + # a placeholder page that should always be available
> + if flags.livecdInstall:
> + return os.path.join(constants.HELP_FOLDER,
> constants.HELP_PLACEHOLDER_WITH_LINKS)
> + else:
> + return os.path.join(constants.HELP_FOLDER,
> constants.HELP_PLACEHOLDER)
> +
> +def start_yelp(help_path):
> + """Start a new yelp process and make sure to kill any existing ones
> +
> + :param str help_path: path to the help file yelp should load
> + """
> +
> + kill_yelp()
> + log.debug("starting yelp")
> + global yelp_process
> + yelp_process = subprocess.Popen(["yelp", help_path])
> +
> +def kill_yelp():
> + """Try to kill any existing yelp processes"""
> +
> + global yelp_process
> + if not yelp_process:
> + return False
> +
> + log.debug("killing yelp")
> + yelp_process.kill()
> + yelp_process = None
> + return True
> diff --git a/pyanaconda/ui/gui/__init__.py b/pyanaconda/ui/gui/__init__.py
> index 3245d3e..7188aa6 100644
> --- a/pyanaconda/ui/gui/__init__.py
> +++ b/pyanaconda/ui/gui/__init__.py
> @@ -30,6 +30,7 @@ from pyanaconda import product
>
> from pyanaconda.ui import UserInterface, common
> from pyanaconda.ui.gui.utils import gtk_action_wait, busyCursor,
> unbusyCursor
> +from pyanaconda import ihelp
> import os.path
>
> import logging
> @@ -80,6 +81,10 @@ class GUIObject(common.UIObject):
> that use GUI elements (Hubs & Spokes) from
> Anaconda
> can override the translation domain with their
> own,
> so that their subclasses are properly translated.
> + helpFile -- The location of the yelp-compatible help file for
> the
> + given GUI object. The default value of ""
> indicates
> + that the object has not specific help file
> assigned
> + and the default help file should be used.
> """
> builderObjects = []
> mainWidgetName = None
> @@ -90,6 +95,7 @@ class GUIObject(common.UIObject):
> focusWidgetName = None
>
> uiFile = ""
> + helpFile = ""
> translationDomain = "anaconda"
>
> screenshots_directory = "/tmp/anaconda-screenshots"
> @@ -535,6 +541,7 @@ class GraphicalUserInterface(UserInterface):
>
> # Use connect_after so classes can add actions before we change
> screens
> obj.window.connect_after("continue-clicked",
> self._on_continue_clicked)
> + obj.window.connect_after("help-button-clicked",
> self._on_help_clicked, obj)
> obj.window.connect_after("quit-clicked", self._on_quit_clicked)
>
> return obj
> @@ -696,6 +703,11 @@ class GraphicalUserInterface(UserInterface):
> self._currentAction = nextAction
> self._actions.pop(0)
>
> + def _on_help_clicked(self, window, obj):
> + # the help button has been clicked, start the yelp viewer with
> + # content for the current screen
> + ihelp.start_yelp(ihelp.get_help_path(obj.helpFile))
> +
> def _on_quit_clicked(self, win, userData=None):
> if not win.get_quit_button():
> return
> diff --git a/pyanaconda/ui/gui/spokes/__init__.py
> b/pyanaconda/ui/gui/spokes/__init__.py
> index 183dc6f..5fb2714 100644
> --- a/pyanaconda/ui/gui/spokes/__init__.py
> +++ b/pyanaconda/ui/gui/spokes/__init__.py
> @@ -21,6 +21,7 @@
>
> from pyanaconda.ui import common
> from pyanaconda.ui.gui import GUIObject
> +from pyanaconda import ihelp
>
> __all__ = ["StandaloneSpoke", "NormalSpoke"]
>
> @@ -44,6 +45,14 @@ class NormalSpoke(GUIObject, common.NormalSpoke):
> GUIObject.__init__(self, data)
> common.NormalSpoke.__init__(self, storage, payload, instclass)
>
> + # Add a help handler
> + self.window.connect_after("help-button-clicked",
> self._on_help_clicked)
> +
> + def _on_help_clicked(self, window):
> + # the help button has been clicked, start the yelp viewer with
> + # content for the current spoke
> + ihelp.start_yelp(ihelp.get_help_path(self.helpFile))
> +
> def on_back_clicked(self, window):
> # Notify the hub that we're finished.
> # The hub will be the current-action of the main window.
> diff --git a/widgets/glade/AnacondaWidgets.xml
> b/widgets/glade/AnacondaWidgets.xml
> index 42c4194..782d360 100644
> --- a/widgets/glade/AnacondaWidgets.xml
> +++ b/widgets/glade/AnacondaWidgets.xml
> @@ -25,6 +25,11 @@
> <property id="distribution" translatable="True" />
> <property id="window-name" translatable="True" />
> </properties>
> +
> + <signals>
> + <signal since="4.0" id="help-button-clicked" />
> + </signals>
> +
> </glade-widget-class>
>
> <glade-widget-class title="Standalone Spoke Window"
> diff --git a/widgets/src/BaseWindow.c b/widgets/src/BaseWindow.c
> index 5b7f337..94dee1f 100644
> --- a/widgets/src/BaseWindow.c
> +++ b/widgets/src/BaseWindow.c
> @@ -28,6 +28,8 @@
> #include "BaseWindow.h"
> #include "intl.h"
>
> + #include <atk/atk.h>
> +
> /**
> * SECTION: BaseWindow
> * @title: AnacondaBaseWindow
> @@ -95,6 +97,7 @@
>
> enum {
> SIGNAL_INFO_BAR_CLICKED,
> + SIGNAL_HELP_BUTTON_CLICKED,
> LAST_SIGNAL
> };
>
> @@ -109,6 +112,7 @@ enum {
> #define DEFAULT_WINDOW_NAME N_("SPOKE NAME")
> #define DEFAULT_BETA N_("PRE-RELEASE / TESTING")
> #define LAYOUT_INDICATOR_LABEL_WIDTH 10
> +#define HELP_BUTTON_LABEL _("_Help!")
>
> struct _AnacondaBaseWindowPrivate {
> gboolean is_beta, info_shown;
> @@ -117,6 +121,7 @@ struct _AnacondaBaseWindowPrivate {
> GtkWidget *nav_box, *nav_area, *action_area;
> GtkWidget *name_label, *distro_label, *beta_label;
> GtkWidget *layout_indicator;
> + GtkWidget *help_button;
>
> /* Untranslated versions of various things. */
> gchar *orig_name, *orig_distro, *orig_beta;
> @@ -128,6 +133,7 @@ static void
> anaconda_base_window_buildable_init(GtkBuildableIface *iface);
> static void format_beta_label(AnacondaBaseWindow *window, const char
> *markup);
>
> static gboolean anaconda_base_window_info_bar_clicked(GtkWidget *widget,
> GdkEvent *event, AnacondaBaseWindow *win);
> +static void anaconda_base_window_help_button_clicked(GtkButton *button,
> AnacondaBaseWindow *win);
>
> G_DEFINE_TYPE_WITH_CODE(AnacondaBaseWindow, anaconda_base_window,
> GTK_TYPE_BIN,
> G_IMPLEMENT_INTERFACE(GTK_TYPE_BUILDABLE,
> anaconda_base_window_buildable_init))
> @@ -173,6 +179,7 @@ static void
> anaconda_base_window_class_init(AnacondaBaseWindowClass *klass) {
> G_PARAM_READWRITE));
>
> klass->info_bar_clicked = NULL;
> + klass->help_button_clicked = NULL;
>
> /**
> * AnacondaBaseWindow::info-bar-clicked:
> @@ -192,6 +199,24 @@ static void
> anaconda_base_window_class_init(AnacondaBaseWindowClass *klass) {
> g_cclosure_marshal_VOID__VOID,
> G_TYPE_NONE, 0);
>
> + /**
> + * AnacondaBaseWindow::help-button-clicked:
> + * @window: the window that received the signal
> + *
> + * Emitted when the help button in the right corner has been activated
> + * (pressed and released). This is commonly used to open the help view
> with
> + * help content for the given spoke or hub
> + *
> + * Since: 4.0
> + */
> + window_signals[SIGNAL_HELP_BUTTON_CLICKED] =
> g_signal_new("help-button-clicked",
> +
> G_TYPE_FROM_CLASS(object_class),
> +
> G_SIGNAL_RUN_FIRST
> | G_SIGNAL_ACTION,
> +
> G_STRUCT_OFFSET(AnacondaBaseWindowClass,
> help_button_clicked),
> + NULL, NULL,
> +
> g_cclosure_marshal_VOID__VOID,
> + G_TYPE_NONE,
> 0);
> +
> g_type_class_add_private(object_class,
> sizeof(AnacondaBaseWindowPrivate));
> }
>
> @@ -210,6 +235,7 @@ GtkWidget *anaconda_base_window_new() {
>
> static void anaconda_base_window_init(AnacondaBaseWindow *win) {
> char *markup;
> + AtkObject *atk;
>
> win->priv = G_TYPE_INSTANCE_GET_PRIVATE(win,
> ANACONDA_TYPE_BASE_WINDOW,
> @@ -325,11 +351,28 @@ G_GNUC_END_IGNORE_DEPRECATIONS
> gtk_widget_set_margin_top(win->priv->layout_indicator, 6);
> gtk_widget_set_margin_bottom(win->priv->layout_indicator, 6);
>
> + /* Create the help button. */
> + win->priv->help_button =
> gtk_button_new_with_mnemonic(HELP_BUTTON_LABEL);
> + gtk_widget_set_halign(win->priv->help_button, GTK_ALIGN_START);
> + gtk_widget_set_vexpand(win->priv->help_button, FALSE);
> + gtk_widget_set_valign(win->priv->help_button, GTK_ALIGN_END);
> + gtk_widget_set_margin_bottom(win->priv->help_button, 6);
> +
> + atk = gtk_widget_get_accessible(win->priv->help_button);
> + atk_object_set_name(atk, HELP_BUTTON_LABEL);
> +
> + /* Hook up some signals for that button. The signal handlers here will
> + * just raise our own custom signals for the whole window.
> + */
> + g_signal_connect(win->priv->help_button, "clicked",
> + G_CALLBACK(anaconda_base_window_help_button_clicked),
> win);
> +
> /* Add everything to the nav area. */
> gtk_grid_attach(GTK_GRID(win->priv->nav_area), win->priv->name_label, 0,
> 0, 1, 1);
> gtk_grid_attach(GTK_GRID(win->priv->nav_area), win->priv->distro_label,
> 1, 0, 1, 1);
> gtk_grid_attach(GTK_GRID(win->priv->nav_area), win->priv->beta_label, 1,
> 1, 1, 1);
> gtk_grid_attach(GTK_GRID(win->priv->nav_area),
> win->priv->layout_indicator, 1, 2, 1, 1);
> + gtk_grid_attach(GTK_GRID(win->priv->nav_area), win->priv->help_button,
> 2, 1, 1, 2);
> }
>
> static void anaconda_base_window_get_property(GObject *object, guint
> prop_id, GValue *value, GParamSpec *pspec) {
> @@ -587,6 +630,11 @@ static gboolean
> anaconda_base_window_info_bar_clicked(GtkWidget *wiget, GdkEvent
> return FALSE;
> }
>
> +static void anaconda_base_window_help_button_clicked(GtkButton *button,
> + AnacondaBaseWindow
> *win) {
> + g_signal_emit(win, window_signals[SIGNAL_HELP_BUTTON_CLICKED], 0);
> +}
> +
> /**
> * anaconda_base_window_clear_info:
> * @win: a #AnacondaBaseWindow
> diff --git a/widgets/src/BaseWindow.h b/widgets/src/BaseWindow.h
> index eb6191d..19891c1 100644
> --- a/widgets/src/BaseWindow.h
> +++ b/widgets/src/BaseWindow.h
> @@ -56,11 +56,14 @@ struct _AnacondaBaseWindow {
> * pointer to be cast to a #GtkBin pointer.
> * @info_bar_clicked : Function pointer called when the
> #AnacondaBaseWindow::info-bar-clicked
> * signal is emitted.
> + * @help_button_clicked: Function pointer called when the
> #AnacondaSpokeWindow::help-button-clicked
> + * signal is emitted.
> */
> struct _AnacondaBaseWindowClass {
> GtkBinClass parent_class;
>
> void (* info_bar_clicked) (AnacondaBaseWindow *window);
> + void (* help_button_clicked) (AnacondaBaseWindow *window);
> };
>
> GType anaconda_base_window_get_type (void);
> --
> 1.9.3
>
> _______________________________________________
> anaconda-patches mailing list
> anaconda-patches at lists.fedorahosted.org
> https://lists.fedorahosted.org/mailman/listinfo/anaconda-patches
>
The fallback help is Fedora specific. Should that part be generalized
for the likelihood that all this will probably end up in RHEL eventually?
- mulhern
More information about the anaconda-patches
mailing list