Initial work on a help API
- From: Shaun McCance <shaunm gnome org>
- To: gtk-devel-list gnome org
- Subject: Initial work on a help API
- Date: Thu, 21 Apr 2011 16:16:01 -0400
Hi all,
I had a project called Squawk I was working on last year.
I chatted a bit with Ryan Lortie about it at the recent
help hackfest, and decided to work on it again, but this
time directly in GLib and GTK+.
The basic idea is that GLib has a help provider API that
knows about your application's help. It knows more than
just that the help exists. It actually knows about all
the topics in your help, their URIs, titles, tags, etc.
Then there's a set of GTK+ widgets that consume this.
If you want to make a help button in a dialog, instead
of just making a button and attaching a static URI to
it, you'd make a help button and give it a tag. Then
it's up to help authors to tag topics for that button.
Then do menus the same way. We can kill off the crappy
Help->Contents item, and have the Help menu display
whatever topics help authors feel are worth promoting
to the menu. You can change widget tags at run-time to
make them reflect UI state or what the user is doing.
On top of that, what I'd like to do is add a search entry
directly into the help menu, so as you start typing, the
menu is repopulated with matching help topics.
So I've got a basic implementation, which I've attached.
There's a patch for GLib and a patch for GTK+. The GLib
API uses extension points, and the default help provider
does almost nothing. So I've got a bad proof-of-concept
module to read Mallard documents from ghelp: URIs. I've
attached that too. Build with:
gcc -shared -o libgnomehelp.so \
`pkg-config --cflags --libs gio-2.0 libxml-2.0` \
gnomehelp.c
Then copy to $libdir/gio/modules and run gio-querymodules.
In GTK+, under tests, there's testhelpbutton. Build it
and run it, passing a ghelp URI. ghelp:gnome-help is a
good example. The text entry is the tag. Type something
there to set the tag for the help button. Try "mouse"
or "button".
If there are no matching topics, the button is insensitive.
If there's only one matching topic, the button just links
to that topic. If there are multiple, the button becomes a
menu button. Click it to get a menu of topics.
Caveats: The gnomehelp implementation is sucky in all
sorts of ways. First, it's not even doing tags right.
The intention is that we'd have proper attached to each
page. We have markup for that. But none of the pages you
have installed have those. So in the interest of testing,
the module just treats page titles as if they were also
a list of tags.
Also, it reads all the Mallard pages files for a document.
I've talked to Ryan about ways page information could be
cached. That's the way forward. This module is only for
showing off the idea. Don't get hung up on implementation.
What I'm really interested in is comments on the GLib and
GTK+ APIs, as well as the general direction. Are people
interested in this?
Thanks,
Shaun
>From 6f51564831c7710f4216424784ea34044f0a1ad2 Mon Sep 17 00:00:00 2001
From: Shaun McCance <shaunm gnome org>
Date: Wed, 20 Apr 2011 09:09:46 -0400
Subject: [PATCH] ghelp: Initial checking of GHelp API
---
gio/Makefile.am | 12 ++-
gio/ghelp.c | 173 +++++++++++++++++++++++++++++++++
gio/ghelp.h | 107 ++++++++++++++++++++
gio/gio.h | 1 +
gio/giomodule.c | 5 +
gio/giotypes.h | 2 +
gio/gnullhelp.c | 251 ++++++++++++++++++++++++++++++++++++++++++++++++
gio/tests/.gitignore | 1 +
gio/tests/Makefile.am | 4 +
gio/tests/ghelp-list.c | 65 +++++++++++++
10 files changed, 620 insertions(+), 1 deletions(-)
create mode 100644 gio/ghelp.c
create mode 100644 gio/ghelp.h
create mode 100644 gio/gnullhelp.c
create mode 100644 gio/tests/ghelp-list.c
diff --git a/gio/Makefile.am b/gio/Makefile.am
index e2fcd7e..5a30e28 100644
--- a/gio/Makefile.am
+++ b/gio/Makefile.am
@@ -150,6 +150,14 @@ application_sources = \
gapplicationimpl-dbus.c \
gapplication.c
+ghelp_headers = \
+ ghelp.h
+
+ghelp_sources = \
+ ghelp.h \
+ ghelp.c \
+ gnullhelp.c
+
local_sources = \
glocaldirectorymonitor.c \
glocaldirectorymonitor.h \
@@ -400,6 +408,7 @@ libgio_2_0_la_SOURCES = \
$(win32_sources) \
$(application_sources) \
$(settings_sources) \
+ $(ghelp_sources) \
$(gdbus_sources) \
$(local_sources) \
$(marshal_sources) \
@@ -546,6 +555,7 @@ gio_headers = \
gzlibdecompressor.h \
$(application_headers) \
$(settings_headers) \
+ $(ghelp_headers) \
$(gdbus_headers) \
$(NULL)
@@ -652,7 +662,7 @@ dist-hook: $(BUILT_EXTRA_DIST) ../build/win32/vs9/gio.vcproj ../build/win32/vs10
done | sort -u >libgio.sourcefiles
$(CPP) -P - <$(top_srcdir)/build/win32/vs9/gio.vcprojin >$@
rm libgio.sourcefiles
-
+
../build/win32/vs10/gio.vcxproj: $(top_srcdir)/build/win32/vs10/gio.vcxprojin
for F in `echo $(libgio_2_0_la_SOURCES) $(win32_actual_sources) $(win32_actual_more_sources_for_vcproj) | tr '/' '\\'`; do \
case $$F in \
diff --git a/gio/ghelp.c b/gio/ghelp.c
new file mode 100644
index 0000000..6f90077
--- /dev/null
+++ b/gio/ghelp.c
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2011 Shaun McCance
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Shaun McCance <shaunm gnome org>
+ */
+
+#include "config.h"
+#include "ghelp.h"
+#include "giomodule-priv.h"
+#include "glibintl.h"
+
+
+/**
+ * SECTION:ghelp
+ * @short_description: FIXME
+ * @include: gio/gio.h
+ *
+ * FIXME
+ **/
+
+typedef GHelpIface GHelpInterface;
+G_DEFINE_INTERFACE (GHelp, g_help, G_TYPE_OBJECT)
+
+typedef GHelpPagesIface GHelpPagesInterface;
+G_DEFINE_INTERFACE (GHelpPages, g_help_pages, G_TYPE_OBJECT)
+
+static void
+g_help_default_init (GHelpIface *iface)
+{
+ g_object_interface_install_property (iface,
+ g_param_spec_string ("uri",
+ P_("URI"),
+ P_("The base URI of the help source"),
+ NULL,
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB |
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ g_object_interface_install_property (iface,
+ g_param_spec_boolean ("ready",
+ P_("Ready"),
+ P_("Whether the help source is ready to serve pages"),
+ FALSE,
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB |
+ G_PARAM_READWRITE));
+ g_object_interface_install_property (iface,
+ g_param_spec_boolean ("searchable",
+ P_("Searchable"),
+ P_("If TRUE, text search is supported"),
+ FALSE,
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB |
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+g_help_pages_default_init (GHelpPagesIface *iface)
+{
+}
+
+
+/**
+ * g_help_new:
+ * @uri: FIXME
+ *
+ * FIXME
+ *
+ * Returns: (transfer full): a new #GHelp object.
+ **/
+GHelp *
+g_help_new (const char *uri)
+{
+ GType extension_type;
+ static gsize backend;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ if (g_once_init_enter (&backend))
+ {
+ GIOExtensionPoint *point;
+ GIOExtension *extension;
+ GList *extensions;
+
+ _g_io_modules_ensure_loaded ();
+
+ point = g_io_extension_point_lookup (G_HELP_EXTENSION_POINT_NAME);
+ extension = NULL;
+
+ extensions = g_io_extension_point_get_extensions (point);
+
+ if (extensions == NULL)
+ g_error ("No GHelp implementations exist.");
+
+ extension = extensions->data;
+
+ g_once_init_leave (&backend, (gsize) extension);
+ }
+
+ extension_type = g_io_extension_get_type ((GIOExtension *) backend);
+ return g_object_new (extension_type,
+ "uri", uri,
+ NULL);
+}
+
+void
+g_help_restrict_to_tags (GHelp *help,
+ const char *tags)
+{
+ GHelpInterface *iface = G_HELP_GET_IFACE (help);
+
+ if (iface->restrict_to_tags)
+ iface->restrict_to_tags (help, tags);
+}
+
+GHelpPages *
+g_help_list_for_tags (GHelp *help,
+ const char *tags)
+{
+ GHelpInterface *iface = G_HELP_GET_IFACE (help);
+
+ if (iface->list_for_tags)
+ return iface->list_for_tags (help, tags);
+
+ /* FIXME */
+ return NULL;
+}
+
+gint
+g_help_pages_get_count (GHelpPages *pages)
+{
+ GHelpPagesInterface *iface = G_HELP_PAGES_GET_IFACE (pages);
+
+ if (iface->get_count)
+ return iface->get_count (pages);
+
+ return 0;
+}
+
+const char *
+g_help_pages_get_uri (GHelpPages *pages,
+ gint num)
+{
+ GHelpPagesInterface *iface = G_HELP_PAGES_GET_IFACE (pages);
+
+ if (iface->get_uri)
+ return iface->get_uri (pages, num);
+
+ return NULL;
+}
+
+const char *
+g_help_pages_get_title (GHelpPages *pages,
+ gint num)
+{
+ GHelpPagesInterface *iface = G_HELP_PAGES_GET_IFACE (pages);
+
+ if (iface->get_title)
+ return iface->get_title (pages, num);
+
+ return NULL;
+}
diff --git a/gio/ghelp.h b/gio/ghelp.h
new file mode 100644
index 0000000..8c5dd74
--- /dev/null
+++ b/gio/ghelp.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2011 Shaun McCance
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Shaun McCance <shaunm gnome org>
+ */
+
+#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION)
+#error "Only <gio/gio.h> can be included directly."
+#endif
+
+#ifndef __G_HELP_H__
+#define __G_HELP_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_HELP (g_help_get_type ())
+#define G_HELP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_HELP, GHelp))
+#define G_IS_HELP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_HELP))
+#define G_HELP_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), G_TYPE_HELP, GHelpIface))
+
+#define G_TYPE_HELP_PAGES (g_help_pages_get_type ())
+#define G_HELP_PAGES(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_HELP_PAGES, GHelpPages))
+#define G_IS_HELP_PAGES(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_HELP_PAGES))
+#define G_HELP_PAGES_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), G_TYPE_HELP_PAGES, GHelpPagesIface))
+
+
+/**
+ * G_HELP_EXTENSION_POINT_NAME:
+ *
+ * Extension point for #GHelp functionality.
+ **/
+#define G_HELP_EXTENSION_POINT_NAME "ghelp"
+
+
+/**
+ * GHelpIface:
+ * @g_iface: The parent interface.
+ *
+ * An interface for help providers.
+ **/
+typedef struct _GHelpIface GHelpIface;
+struct _GHelpIface
+{
+ GTypeInterface g_iface;
+
+ /* Virtual Table */
+
+ void (* restrict_to_tags) (GHelp *help,
+ const char *tags);
+ GHelpPages * (* list_for_tags) (GHelp *help,
+ const char *tags);
+};
+
+/**
+ * GHelpPagesIface:
+ * @g_iface: The parent interface.
+ *
+ * FIXME
+ **/
+typedef struct _GHelpPagesIface GHelpPagesIface;
+struct _GHelpPagesIface
+{
+ GTypeInterface g_iface;
+
+ /* Virtual Table */
+ gint (* get_count) (GHelpPages *pages);
+ const char * (* get_uri) (GHelpPages *pages,
+ gint num);
+ const char * (* get_title) (GHelpPages *pages,
+ gint num);
+};
+
+GType g_help_get_type (void) G_GNUC_CONST;
+GType g_help_pages_get_type (void) G_GNUC_CONST;
+
+GHelp * g_help_new (const char *uri);
+void g_help_restrict_to_tags (GHelp *help,
+ const char *tags);
+GHelpPages * g_help_list_for_tags (GHelp *help,
+ const char *tags);
+
+gint g_help_pages_get_count (GHelpPages *pages);
+const char * g_help_pages_get_uri (GHelpPages *pages,
+ gint num);
+const char * g_help_pages_get_title (GHelpPages *pages,
+ gint num);
+
+G_END_DECLS
+
+#endif /* __G_HELP_H__ */
diff --git a/gio/gio.h b/gio/gio.h
index 288da44..e5d5255 100644
--- a/gio/gio.h
+++ b/gio/gio.h
@@ -73,6 +73,7 @@
#include <gio/gfileoutputstream.h>
#include <gio/gfilterinputstream.h>
#include <gio/gfilteroutputstream.h>
+#include <gio/ghelp.h>
#include <gio/gicon.h>
#include <gio/ginetaddress.h>
#include <gio/ginetsocketaddress.h>
diff --git a/gio/giomodule.c b/gio/giomodule.c
index 00e8c48..ff7d4ff 100644
--- a/gio/giomodule.c
+++ b/gio/giomodule.c
@@ -24,6 +24,7 @@
#include <string.h>
+#include "ghelp.h"
#include "giomodule.h"
#include "giomodule-priv.h"
#include "glocalfilemonitor.h"
@@ -553,6 +554,9 @@ _g_io_modules_ensure_extension_points_registered (void)
ep = g_io_extension_point_register ("gsettings-backend");
g_io_extension_point_set_required_type (ep, G_TYPE_OBJECT);
+ ep = g_io_extension_point_register (G_HELP_EXTENSION_POINT_NAME);
+ g_io_extension_point_set_required_type (ep, G_TYPE_OBJECT);
+
ep = g_io_extension_point_register (G_PROXY_RESOLVER_EXTENSION_POINT_NAME);
g_io_extension_point_set_required_type (ep, G_TYPE_PROXY_RESOLVER);
@@ -600,6 +604,7 @@ _g_io_modules_ensure_loaded (void)
/* Initialize types from built-in "modules" */
g_null_settings_backend_get_type ();
g_memory_settings_backend_get_type ();
+ g_null_help_get_type ();
#if defined(HAVE_SYS_INOTIFY_H) || defined(HAVE_LINUX_INOTIFY_H)
_g_inotify_directory_monitor_get_type ();
_g_inotify_file_monitor_get_type ();
diff --git a/gio/giotypes.h b/gio/giotypes.h
index 1c35083..0a3f372 100644
--- a/gio/giotypes.h
+++ b/gio/giotypes.h
@@ -56,6 +56,8 @@ typedef struct _GApplicationCommandLine GApplicationCommandLine;
typedef struct _GSettingsBackend GSettingsBackend;
typedef struct _GSettings GSettings;
typedef struct _GPermission GPermission;
+typedef struct _GHelp GHelp;
+typedef struct _GHelpPages GHelpPages;
/**
* GDrive:
diff --git a/gio/gnullhelp.c b/gio/gnullhelp.c
new file mode 100644
index 0000000..e04d767
--- /dev/null
+++ b/gio/gnullhelp.c
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2011 Shaun McCance
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Shaun McCance <shaunm gnome org>
+ */
+
+#include "config.h"
+
+#include "ghelp.h"
+#include "giomodule-priv.h"
+#include "glibintl.h"
+
+#define G_TYPE_NULL_HELP (g_null_help_get_type ())
+#define G_NULL_HELP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), G_TYPE_NULL_HELP, GNullHelp))
+
+#define G_TYPE_NULL_HELP_PAGES (g_null_help_pages_get_type ())
+#define G_NULL_HELP_PAGES(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), G_TYPE_NULL_HELP_PAGES, GNullHelpPages))
+
+typedef struct _GNullHelp GNullHelp;
+typedef struct _GNullHelpClass GNullHelpClass;
+typedef struct _GNullHelpPages GNullHelpPages;
+typedef struct _GNullHelpPagesClass GNullHelpPagesClass;
+
+static void g_null_help_iface_init (GHelpIface *iface);
+static void g_null_help_finalize (GObject *object);
+static void g_null_help_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void g_null_help_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static GHelpPages * g_null_help_list_for_tags (GHelp *help,
+ const char *tags);
+
+static void g_null_help_pages_iface_init (GHelpPagesIface *iface);
+static void g_null_help_pages_finalize (GObject *object);
+static gint g_null_help_pages_get_count (GHelpPages *pages);
+static const char * g_null_help_pages_get_uri (GHelpPages *pages,
+ gint num);
+static const char * g_null_help_pages_get_title (GHelpPages *pages,
+ gint num);
+
+
+struct _GNullHelp
+{
+ GObject parent_instance;
+ char *uri;
+ gboolean ready;
+};
+
+struct _GNullHelpClass
+{
+ GObjectClass parent_class;
+};
+
+struct _GNullHelpPages
+{
+ GObject parent_instance;
+ char *uri;
+};
+
+struct _GNullHelpPagesClass
+{
+ GObjectClass parent_class;
+};
+
+G_DEFINE_TYPE_WITH_CODE (GNullHelp, g_null_help, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_HELP, g_null_help_iface_init)
+ _g_io_modules_ensure_extension_points_registered ();
+ g_io_extension_point_implement (G_HELP_EXTENSION_POINT_NAME,
+ g_define_type_id, "null", 0))
+G_DEFINE_TYPE_WITH_CODE (GNullHelpPages, g_null_help_pages, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_HELP_PAGES, g_null_help_pages_iface_init))
+
+enum {
+ PROP_0,
+ PROP_HELP_URI,
+ PROP_HELP_READY,
+ PROP_HELP_SEARCHABLE
+};
+
+static void
+g_null_help_init (GNullHelp *help)
+{
+ g_object_set (help, "ready", TRUE, NULL);
+}
+
+static void
+g_null_help_class_init (GNullHelpClass *class)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+ gobject_class->set_property = g_null_help_set_property;
+ gobject_class->get_property = g_null_help_get_property;
+ gobject_class->finalize = g_null_help_finalize;
+
+ g_object_class_override_property (gobject_class, PROP_HELP_URI, "uri");
+ g_object_class_override_property (gobject_class, PROP_HELP_READY, "ready");
+ g_object_class_override_property (gobject_class, PROP_HELP_SEARCHABLE, "searchable");
+}
+
+static void
+g_null_help_iface_init (GHelpIface *iface)
+{
+ iface->list_for_tags = g_null_help_list_for_tags;
+}
+
+static void
+g_null_help_finalize (GObject *object)
+{
+ GNullHelp *help = G_NULL_HELP (object);
+
+ g_free (help->uri);
+
+ G_OBJECT_CLASS (g_null_help_parent_class)->finalize (object);
+}
+
+static void
+g_null_help_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GNullHelp *help = G_NULL_HELP (object);
+
+ switch (prop_id)
+ {
+ case PROP_HELP_URI:
+ g_value_set_string (value, help->uri);
+ break;
+ case PROP_HELP_READY:
+ g_value_set_boolean (value, help->ready);
+ break;
+ case PROP_HELP_SEARCHABLE:
+ g_value_set_boolean (value, FALSE);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+g_null_help_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GNullHelp *help = G_NULL_HELP (object);
+
+ switch (prop_id)
+ {
+ case PROP_HELP_URI:
+ help->uri = g_value_dup_string (value);
+ break;
+ case PROP_HELP_READY:
+ help->ready = g_value_get_boolean (value);
+ break;
+ case PROP_HELP_SEARCHABLE:
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+g_null_help_pages_init (GNullHelpPages *pages)
+{
+}
+
+static void
+g_null_help_pages_class_init (GNullHelpPagesClass *class)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+ gobject_class->finalize = g_null_help_pages_finalize;
+}
+
+static void
+g_null_help_pages_iface_init (GHelpPagesIface *iface)
+{
+ iface->get_count = g_null_help_pages_get_count;
+ iface->get_uri = g_null_help_pages_get_uri;
+ iface->get_title = g_null_help_pages_get_title;
+}
+
+static void
+g_null_help_pages_finalize (GObject *object)
+{
+ GNullHelpPages *pages = G_NULL_HELP_PAGES (object);
+
+ g_free (pages->uri);
+
+ G_OBJECT_CLASS (g_null_help_pages_parent_class)->finalize (object);
+}
+
+
+static GHelpPages *
+g_null_help_list_for_tags (GHelp *help,
+ const char *tags)
+{
+ GNullHelp *nhelp;
+ GNullHelpPages *results;
+
+ nhelp = G_NULL_HELP (help);
+
+ results = g_object_new (G_TYPE_NULL_HELP_PAGES, NULL);
+ results->uri = g_strdup (nhelp->uri);
+
+ return (GHelpPages *) results;
+}
+
+static gint
+g_null_help_pages_get_count (GHelpPages *pages)
+{
+ return 1;
+}
+
+static const char *
+g_null_help_pages_get_uri (GHelpPages *pages,
+ gint num)
+{
+ GNullHelpPages *npages = G_NULL_HELP_PAGES (pages);
+
+ return npages->uri;
+}
+
+static const char *
+g_null_help_pages_get_title (GHelpPages *pages,
+ gint num)
+{
+ return _("All help topics");
+}
diff --git a/gio/tests/.gitignore b/gio/tests/.gitignore
index c7aec70..417c906 100644
--- a/gio/tests/.gitignore
+++ b/gio/tests/.gitignore
@@ -51,6 +51,7 @@ gdbus-threading
g-file
g-file-info
g-icon
+ghelp-list
gschemas.compiled
gsettings
gsettings.store
diff --git a/gio/tests/Makefile.am b/gio/tests/Makefile.am
index 46b0efa..0ab9c83 100644
--- a/gio/tests/Makefile.am
+++ b/gio/tests/Makefile.am
@@ -92,6 +92,7 @@ SAMPLE_PROGS = \
gapplication-example-cmdline2 \
gapplication-example-cmdline3 \
gapplication-example-actions \
+ ghelp-list \
$(NULL)
@@ -335,6 +336,9 @@ gapplication_example_cmdline3_LDADD = $(progs_ldadd)
gapplication_example_actions_SOURCES = gapplication-example-actions.c
gapplication_example_actions_LDADD = $(progs_ldadd)
+ghelp_list_SOURCES = ghelp-list.c
+ghelp_list_LDADD = $(progs_ldadd)
+
schema_tests = \
schema-tests/array-default-not-in-choices.gschema.xml \
schema-tests/bad-choice.gschema.xml \
diff --git a/gio/tests/ghelp-list.c b/gio/tests/ghelp-list.c
new file mode 100644
index 0000000..389b944
--- /dev/null
+++ b/gio/tests/ghelp-list.c
@@ -0,0 +1,65 @@
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+
+static gboolean running;
+static GMainLoop *main_loop;
+static char *tags;
+
+static void
+help_ready (GHelp *help)
+{
+ GHelpPages *pages;
+ gint i, cnt;
+
+ pages = g_help_list_for_tags (help, tags);
+ cnt = g_help_pages_get_count (pages);
+
+ for (i = 0; i < cnt; i++)
+ g_print ("%s - %s\n",
+ g_help_pages_get_uri (pages, i),
+ g_help_pages_get_title (pages, i));
+
+ g_object_unref (pages);
+ g_object_unref (help);
+
+ if (running)
+ g_main_loop_quit (main_loop);
+}
+
+int
+main (int argc, char *argv[])
+{
+ GHelp *help;
+ gboolean ready;
+
+ g_type_init ();
+ running = FALSE;
+
+ if (argc < 2)
+ {
+ g_print ("Usage: %s URI [TAGS]\n", argv[0]);
+ return 0;
+ }
+
+ help = g_help_new (argv[1]);
+ if (argc >= 3)
+ tags = argv[2];
+ else
+ tags = "";
+
+ g_object_get (help, "ready", &ready, NULL);
+ if (ready)
+ {
+ help_ready (help);
+ }
+ else
+ {
+ g_signal_connect (help, "notify::ready",
+ G_CALLBACK (help_ready), NULL);
+ running = TRUE;
+ g_main_loop_run (main_loop);
+ }
+
+ return 0;
+}
--
1.7.3.2
>From af256e62d4c17ff1e6618d57e24648207cdf7540 Mon Sep 17 00:00:00 2001
From: Shaun McCance <shaunm gnome org>
Date: Thu, 21 Apr 2011 15:53:59 -0400
Subject: [PATCH] gtkhelpbutton: Initial work on GtkHelpButton
---
gtk/Makefile.am | 2 +
gtk/gtk.h | 1 +
gtk/gtkhelpbutton.c | 401 ++++++++++++++++++++++++++++++++++++++++++++++++
gtk/gtkhelpbutton.h | 69 ++++++++
tests/Makefile.am | 5 +
tests/testhelpbutton.c | 74 +++++++++
6 files changed, 552 insertions(+), 0 deletions(-)
create mode 100644 gtk/gtkhelpbutton.c
create mode 100644 gtk/gtkhelpbutton.h
create mode 100644 tests/testhelpbutton.c
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index efe9315..a24902d 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -221,6 +221,7 @@ gtk_public_h_sources = \
gtkhandlebox.h \
gtkhbbox.h \
gtkhbox.h \
+ gtkhelpbutton.h \
gtkhpaned.h \
gtkhscale.h \
gtkhscrollbar.h \
@@ -539,6 +540,7 @@ gtk_base_c_sources = \
gtkhandlebox.c \
gtkhbbox.c \
gtkhbox.c \
+ gtkhelpbutton.c \
gtkhpaned.c \
gtkhscale.c \
gtkhscrollbar.c \
diff --git a/gtk/gtk.h b/gtk/gtk.h
index 0a72d24..605d1f0 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -107,6 +107,7 @@
#include <gtk/gtkhandlebox.h>
#include <gtk/gtkhbbox.h>
#include <gtk/gtkhbox.h>
+#include <gtk/gtkhelpbutton.h>
#include <gtk/gtkhpaned.h>
#include <gtk/gtkhscale.h>
#include <gtk/gtkhscrollbar.h>
diff --git a/gtk/gtkhelpbutton.c b/gtk/gtkhelpbutton.c
new file mode 100644
index 0000000..4cd4d94
--- /dev/null
+++ b/gtk/gtkhelpbutton.c
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2011 Shaun McCance
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Shaun McCance <shaunm gnome org>
+ */
+
+#include "config.h"
+
+#ifndef _WIN32
+#define _GNU_SOURCE
+#endif
+
+#include "gtkarrow.h"
+#include "gtkbox.h"
+#include "gtkhelpbutton.h"
+#include "gtkimage.h"
+#include "gtkintl.h"
+#include "gtkmenu.h"
+#include "gtkmenuitem.h"
+#include "gtkmenushell.h"
+#include "gtkprivate.h"
+#include "gtkseparator.h"
+#include "gtkshow.h"
+
+/**
+ * SECTION:gtkhelpbutton
+ * @Short_description: FIXME
+ * @Title: GtkHelpButton
+ *
+ * FIXME
+ */
+
+enum
+{
+ PROP_0,
+ PROP_HELP,
+ PROP_TAG
+};
+
+struct _GtkHelpButtonPrivate
+{
+ GHelp *help;
+ gchar *tag;
+ GHelpPages *pages;
+
+ /* do not free */
+ GtkWidget *icon;
+ GtkWidget *sep;
+ GtkWidget *arrow;
+};
+
+static void gtk_help_button_constructed (GObject *object);
+static void gtk_help_button_dispose (GObject *object);
+static void gtk_help_button_finalize (GObject *object);
+static void gtk_help_button_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gtk_help_button_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gtk_help_button_update_content (GtkHelpButton *button);
+static gboolean gtk_help_button_press (GtkWidget *widget,
+ GdkEventButton *event);
+static gboolean gtk_help_button_release (GtkWidget *widget,
+ GdkEventButton *event);
+static void gtk_help_button_set_tag_private (GtkHelpButton *button,
+ const gchar *tag);
+
+G_DEFINE_TYPE (GtkHelpButton, gtk_help_button, GTK_TYPE_BUTTON)
+
+static void
+gtk_help_button_class_init (GtkHelpButtonClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (GtkHelpButtonPrivate));
+
+ gobject_class->constructed = gtk_help_button_constructed;
+ gobject_class->dispose = gtk_help_button_dispose;
+ gobject_class->finalize = gtk_help_button_finalize;
+ gobject_class->set_property = gtk_help_button_set_property;
+ gobject_class->get_property = gtk_help_button_get_property;
+
+ widget_class->button_press_event = gtk_help_button_press;
+ widget_class->button_release_event = gtk_help_button_release;
+
+ /**
+ * GtkHelpButton:help:
+ *
+ * A #GHelp object to get pages from.
+ **/
+ g_object_class_install_property (gobject_class,
+ PROP_HELP,
+ g_param_spec_object ("help",
+ P_("Help"),
+ P_("A GHelp object to get pages from"),
+ G_TYPE_HELP,
+ GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ /**
+ * GtkHelpButton:tag:
+ *
+ * Tag to match against help pages. Every page in a #GHelp object has a set
+ * of associated tags. The #GtkHelpButton will show all matching pages from
+ * its #GHelp object, where a page matches if any of its tags is the same
+ * as the button's tag.
+ **/
+ g_object_class_install_property (gobject_class,
+ PROP_TAG,
+ g_param_spec_string ("tag",
+ P_("Tag"),
+ P_("The tag to match help pages"),
+ "",
+ GTK_PARAM_READWRITE));
+}
+
+static void
+gtk_help_button_init (GtkHelpButton *button)
+{
+ button->priv = G_TYPE_INSTANCE_GET_PRIVATE (button,
+ GTK_TYPE_HELP_BUTTON,
+ GtkHelpButtonPrivate);
+}
+
+static void
+gtk_help_button_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkHelpButton *button = GTK_HELP_BUTTON (object);
+
+ switch (prop_id)
+ {
+ case PROP_HELP:
+ button->priv->help = g_value_dup_object (value);
+ break;
+ case PROP_TAG:
+ gtk_help_button_set_tag_private (button, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_help_button_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkHelpButton *button = GTK_HELP_BUTTON (object);
+
+ switch (prop_id)
+ {
+ case PROP_HELP:
+ g_value_set_object (value, button->priv->help);
+ break;
+ case PROP_TAG:
+ g_value_set_string (value, button->priv->tag);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_help_button_constructed (GObject *object)
+{
+ GtkHelpButton *button = GTK_HELP_BUTTON (object);
+ GtkWidget *box;
+
+ gtk_widget_push_composite_child ();
+
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_container_add (GTK_CONTAINER (button), box);
+
+ button->priv->icon = gtk_image_new_from_icon_name ("help-browser", GTK_ICON_SIZE_BUTTON);
+ gtk_box_pack_start (GTK_BOX (box), button->priv->icon, FALSE, FALSE, 0);
+
+ button->priv->sep = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
+ gtk_widget_set_no_show_all (button->priv->sep, TRUE);
+ gtk_box_pack_start (GTK_BOX (box), button->priv->sep, FALSE, FALSE, 0);
+
+ button->priv->arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
+ gtk_widget_set_no_show_all (button->priv->arrow, TRUE);
+ gtk_box_pack_start (GTK_BOX (box), button->priv->arrow, FALSE, FALSE, 0);
+
+ gtk_widget_show_all (box);
+
+ gtk_widget_pop_composite_child ();
+
+ gtk_help_button_update_content (button);
+}
+
+static void
+gtk_help_button_dispose (GObject *object)
+{
+ GtkHelpButton *button = GTK_HELP_BUTTON (object);
+
+ if (button->priv->help)
+ {
+ g_object_unref (button->priv->help);
+ button->priv->help = NULL;
+ }
+
+ if (button->priv->pages)
+ {
+ g_object_unref (button->priv->pages);
+ button->priv->pages = NULL;
+ }
+
+ G_OBJECT_CLASS (gtk_help_button_parent_class)->dispose (object);
+}
+
+static void
+gtk_help_button_finalize (GObject *object)
+{
+ GtkHelpButton *button = GTK_HELP_BUTTON (object);
+
+ if (button->priv->tag)
+ {
+ g_free (button->priv->tag);
+ button->priv->tag = NULL;
+ }
+
+ G_OBJECT_CLASS (gtk_help_button_parent_class)->finalize (object);
+}
+
+/**
+ * gtk_help_button_new:
+ * @help: a #GHelp object
+ * @tag: the tag to match help pages
+ *
+ * Creates a #GtkHelpButton, matching pages from @help with the tag @tag.
+ *
+ * Return value: a new #GtkHelpButton
+ */
+GtkWidget *
+gtk_help_button_new (GHelp *help,
+ const gchar *tag)
+{
+ GtkWidget *button;
+
+ button = g_object_new (GTK_TYPE_HELP_BUTTON,
+ "help", help,
+ "tag", tag,
+ NULL);
+
+ return button;
+}
+
+static void
+help_button_set_insensitive (GtkHelpButton *button)
+{
+ gtk_widget_hide (button->priv->sep);
+ gtk_widget_hide (button->priv->arrow);
+ gtk_widget_set_sensitive (GTK_WIDGET (button), FALSE);
+}
+
+static void
+gtk_help_button_update_content (GtkHelpButton *button)
+{
+ gboolean ready;
+ gint count;
+
+ g_object_get (button->priv->help, "ready", &ready, NULL);
+
+ if (!ready || button->priv->tag == NULL || button->priv->tag[0] == '\0')
+ {
+ help_button_set_insensitive (button);
+ return;
+ }
+
+ if (button->priv->pages)
+ g_object_unref (button->priv->pages);
+ button->priv->pages = g_help_list_for_tags (button->priv->help, button->priv->tag);
+ count = g_help_pages_get_count (button->priv->pages);
+
+ if (count == 0)
+ {
+ help_button_set_insensitive (button);
+ return;
+ }
+
+ gtk_widget_set_sensitive (GTK_WIDGET (button), TRUE);
+ if (count == 1)
+ {
+ gtk_widget_hide (button->priv->sep);
+ gtk_widget_hide (button->priv->arrow);
+ gtk_widget_set_tooltip_text (GTK_WIDGET (button),
+ g_help_pages_get_title (button->priv->pages, 0));
+ }
+ else
+ {
+ gchar *tooltip;
+ gtk_widget_show (button->priv->sep);
+ gtk_widget_show (button->priv->arrow);
+ tooltip = g_strdup_printf (g_dngettext(GETTEXT_PACKAGE, "%i help topic", "%i help topics", count), count);
+ gtk_widget_set_tooltip_text (GTK_WIDGET (button), tooltip);
+ g_free (tooltip);
+ }
+}
+
+static gboolean
+gtk_help_button_press (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GtkHelpButton *button = GTK_HELP_BUTTON (widget);
+ gint count;
+
+ count = g_help_pages_get_count (button->priv->pages);
+
+ if (count > 1)
+ {
+ gint i;
+ GtkWidget *menu;
+
+ menu = gtk_menu_new ();
+
+ for (i = 0; i < count; i++)
+ {
+ GtkWidget *item;
+ item = gtk_menu_item_new_with_label (g_help_pages_get_title (button->priv->pages, i));
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show_all (item);
+ }
+
+ gtk_menu_attach_to_widget (GTK_MENU (menu), widget, NULL);
+ gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, event->button, event->time);
+ /* FIXME: show menu */
+ /*
+ GError *error = NULL;
+ if (!gtk_show_uri (gtk_widget_get_screen (widget),
+ g_help_pages_get_uri (button->priv->pages, 0),
+ event->time, &error))
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+ */
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gtk_help_button_release (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GtkHelpButton *button = GTK_HELP_BUTTON (widget);
+ gint count;
+
+ count = g_help_pages_get_count (button->priv->pages);
+
+ if (count == 1)
+ {
+ GError *error = NULL;
+ if (!gtk_show_uri (gtk_widget_get_screen (widget),
+ g_help_pages_get_uri (button->priv->pages, 0),
+ event->time, &error))
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+gtk_help_button_set_tag_private (GtkHelpButton *button,
+ const gchar *tag)
+{
+ if (button->priv->tag)
+ g_free (button->priv->tag);
+
+ button->priv->tag = g_strdup (tag);
+
+ gtk_help_button_update_content (button);
+}
diff --git a/gtk/gtkhelpbutton.h b/gtk/gtkhelpbutton.h
new file mode 100644
index 0000000..7b7968c
--- /dev/null
+++ b/gtk/gtkhelpbutton.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2011 Shaun McCance
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Shaun McCance <shaunm gnome org>
+ */
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#ifndef __GTK_HELP_BUTTON_H__
+#define __GTK_HELP_BUTTON_H__
+
+#include <gtk/gtkbutton.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_HELP_BUTTON (gtk_help_button_get_type ())
+#define GTK_HELP_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_HELP_BUTTON, GtkHelpButton))
+#define GTK_HELP_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_HELP_BUTTON, GtkHelpButtonClass))
+#define GTK_IS_HELP_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_HELP_BUTTON))
+#define GTK_IS_HELP_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_HELP_BUTTON))
+#define GTK_HELP_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_HELP_BUTTON, GtkHelpButtonClass))
+
+typedef struct _GtkHelpButton GtkHelpButton;
+typedef struct _GtkHelpButtonClass GtkHelpButtonClass;
+typedef struct _GtkHelpButtonPrivate GtkHelpButtonPrivate;
+
+struct _GtkHelpButton
+{
+ GtkButton parent;
+
+ /*< private >*/
+ GtkHelpButtonPrivate *priv;
+};
+
+struct _GtkHelpButtonClass
+{
+ GtkButtonClass parent_class;
+
+ /* Padding for future expansion */
+ void (*_gtk_reserved1) (void);
+ void (*_gtk_reserved2) (void);
+ void (*_gtk_reserved3) (void);
+ void (*_gtk_reserved4) (void);
+};
+
+GType gtk_help_button_get_type (void) G_GNUC_CONST;
+GtkWidget * gtk_help_button_new (GHelp *help,
+ const gchar *tag);
+
+G_END_DECLS
+
+#endif /* __GTK_HELP_BUTTON_H__ */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 318bbfe..6dc8833 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -52,6 +52,7 @@ noinst_PROGRAMS = $(TEST_PROGS) \
testgrid \
testgtk \
testheightforwidth \
+ testhelpbutton \
testiconview \
testiconview-keynav \
testicontheme \
@@ -197,6 +198,7 @@ testscrolledwindow_DEPENDENCIES = $(TEST_DEPS)
testcellarea_DEPENDENCIES = $(TEST_DEPS)
testtreemenu_DEPENDENCIES = $(TEST_DEPS)
testwindows_DEPENDENCIES = $(TEST_DEPS)
+testhelpbutton_DEPENDENCIES = $(TEST_DEPS)
testexpand_DEPENDENCIES = $(TEST_DEPS)
testexpander_DEPENDENCIES = $(TEST_DEPS)
testswitch_DEPENDENCIES = $(TEST_DEPS)
@@ -281,6 +283,7 @@ testscrolledwindow_LDADD = $(LDADDS)
testcellarea_LDADD = $(LDADDS)
testtreemenu_LDADD = $(LDADDS)
testwindows_LDADD = $(LDADDS)
+testhelpbutton_LDADD = $(LDADDS)
testexpand_LDADD = $(LDADDS)
testexpander_LDADD = $(LDADDS)
testswitch_LDADD = $(LDADDS)
@@ -424,6 +427,8 @@ testappchooserbutton_SOURCES = \
testwindows_SOURCES = \
testwindows.c
+testhelpbutton_SOURCES = testhelpbutton.c
+
testexpand_SOURCES = testexpand.c
testexpander_SOURCES = testexpander.c
diff --git a/tests/testhelpbutton.c b/tests/testhelpbutton.c
new file mode 100644
index 0000000..663023e
--- /dev/null
+++ b/tests/testhelpbutton.c
@@ -0,0 +1,74 @@
+/* testhelpbutton.c
+ * Copyright (C) 2011 Shaun McCance
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+static void
+entry_changed (GtkEntry *entry, GtkHelpButton *button)
+{
+ g_object_set (button,
+ "tag", gtk_entry_get_text (entry),
+ NULL);
+}
+
+int
+main (int argc, char *argv[])
+{
+ GHelp *help;
+ GtkWidget *window, *box, *entry, *button;
+
+ if (argc != 2)
+ {
+ g_print ("Usage: %s URI\n", argv[0]);
+ return 0;
+ }
+
+ gtk_init (&argc, &argv);
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title (GTK_WINDOW (window), "Help Button");
+ g_signal_connect (window, "delete-event",
+ G_CALLBACK (gtk_main_quit), window);
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ g_object_set (box, "margin", 6, NULL);
+ gtk_container_add (GTK_CONTAINER (window), box);
+
+ entry = gtk_entry_new ();
+ gtk_box_pack_start (GTK_BOX (box), entry, FALSE, FALSE, 0);
+
+ help = g_help_new (argv[1]);
+ button = gtk_help_button_new (help, "");
+ g_object_set (button,
+ "expand", FALSE,
+ "halign", GTK_ALIGN_CENTER,
+ "valign", GTK_ALIGN_START,
+ NULL);
+ gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
+
+ g_signal_connect (entry, "changed",
+ G_CALLBACK (entry_changed), button);
+
+ gtk_widget_show_all (window);
+
+ gtk_main ();
+
+ return 0;
+}
--
1.7.3.2
#include <glib-object.h>
#include <gio/gio.h>
#include <gmodule.h>
#include <libxml/parser.h>
#include <libxml/xinclude.h>
#include <libxml/xpath.h>
#define GNOME_TYPE_HELP (gnome_help_get_type ())
#define GNOME_HELP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GNOME_TYPE_HELP, GnomeHelp))
#define GNOME_TYPE_HELP_PAGES (gnome_help_pages_get_type ())
#define GNOME_HELP_PAGES(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GNOME_TYPE_HELP_PAGES, GnomeHelpPages))
typedef struct _GnomeHelp GnomeHelp;
typedef struct _GnomeHelpClass GnomeHelpClass;
typedef struct _GnomeHelpPages GnomeHelpPages;
typedef struct _GnomeHelpPagesClass GnomeHelpPagesClass;
struct _GnomeHelp
{
GObject parent;
char *uri;
gboolean ready;
GRegex *split;
xmlXPathCompExprPtr get_id;
xmlXPathCompExprPtr get_title;
GHashTable *ids;
GHashTable *titles;
GHashTable *tags;
};
struct _GnomeHelpClass
{
GObjectClass parent_class;
};
struct _GnomeHelpPages
{
GObject parent;
GnomeHelp *help;
gchar *tag;
};
struct _GnomeHelpPagesClass
{
GObjectClass parent_class;
};
static void gnome_help_constructed (GObject *object);
static void gnome_help_finalize (GObject *object);
static void gnome_help_iface_init (GHelpIface *iface);
static void gnome_help_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
static void gnome_help_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static GHelpPages * gnome_help_list_for_tags (GHelp *help,
const char *tags);
static void gnome_help_pages_dispose (GObject *object);
static void gnome_help_pages_finalize (GObject *object);
static void gnome_help_pages_iface_init (GHelpPagesIface *iface);
static gint gnome_help_pages_get_count (GHelpPages *pages);
static const char * gnome_help_pages_get_uri (GHelpPages *pages,
gint num);
static const char * gnome_help_pages_get_title (GHelpPages *pages,
gint num);
static void gnome_help_thread (GnomeHelp *help);
static void gnome_help_read_page (GnomeHelp *help,
const gchar *page);
static gboolean gnome_help_thread_done (GnomeHelp *help);
G_DEFINE_DYNAMIC_TYPE_EXTENDED (GnomeHelp, gnome_help, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE_DYNAMIC (G_TYPE_HELP,
gnome_help_iface_init))
G_DEFINE_DYNAMIC_TYPE_EXTENDED (GnomeHelpPages, gnome_help_pages, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE_DYNAMIC (G_TYPE_HELP_PAGES,
gnome_help_pages_iface_init))
enum {
PROP_0,
PROP_HELP_URI,
PROP_HELP_READY,
PROP_HELP_SEARCHABLE
};
static void
ptr_array_free_all (GPtrArray *array)
{
/* actual strings belong to help->titles */
g_ptr_array_free (array, TRUE);
}
static void
gnome_help_init (GnomeHelp *help)
{
help->split = g_regex_new ("\\W", 0, 0, NULL);
help->ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
help->titles = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_free);
help->tags = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify) ptr_array_free_all);
}
static void
gnome_help_class_init (GnomeHelpClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
gobject_class->set_property = gnome_help_set_property;
gobject_class->get_property = gnome_help_get_property;
gobject_class->constructed = gnome_help_constructed;
gobject_class->finalize = gnome_help_finalize;
g_object_class_override_property (gobject_class, PROP_HELP_URI, "uri");
g_object_class_override_property (gobject_class, PROP_HELP_READY, "ready");
g_object_class_override_property (gobject_class, PROP_HELP_SEARCHABLE, "searchable");
}
static void
gnome_help_iface_init (GHelpIface *iface)
{
iface->list_for_tags = gnome_help_list_for_tags;
}
static void
gnome_help_constructed (GObject *object)
{
GnomeHelp *help = GNOME_HELP (object);
help->get_id = xmlXPathCompile ("string(/mal:page/@id)");
help->get_title = xmlXPathCompile ("normalize-space((/mal:page/mal:info/mal:title[@type='text'] |"
" /mal:page/mal:title)[1])");
g_object_ref (object);
g_thread_create ((GThreadFunc) gnome_help_thread, object, FALSE, NULL);
}
static void
gnome_help_finalize (GObject *object)
{
GnomeHelp *help = GNOME_HELP (object);
g_free (help->uri);
g_regex_unref (help->split);
if (help->get_id)
{
xmlXPathFreeCompExpr (help->get_id);
help->get_id = NULL;
}
if (help->get_title)
{
xmlXPathFreeCompExpr (help->get_title);
help->get_title = NULL;
}
g_hash_table_destroy (help->ids);
g_hash_table_destroy (help->titles);
g_hash_table_destroy (help->tags);
G_OBJECT_CLASS (gnome_help_parent_class)->finalize (object);
}
static void
gnome_help_class_finalize (GnomeHelpClass *class)
{
}
static void
gnome_help_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GnomeHelp *help = GNOME_HELP (object);
switch (prop_id)
{
case PROP_HELP_URI:
g_value_set_string (value, help->uri);
break;
case PROP_HELP_READY:
g_value_set_boolean (value, help->ready);
break;
case PROP_HELP_SEARCHABLE:
g_value_set_boolean (value, FALSE);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gnome_help_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GnomeHelp *help = GNOME_HELP (object);
switch (prop_id)
{
case PROP_HELP_URI:
help->uri = g_value_dup_string (value);
break;
case PROP_HELP_READY:
help->ready = g_value_get_boolean (value);
break;
case PROP_HELP_SEARCHABLE:
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gnome_help_thread (GnomeHelp *help)
{
const gchar * const *datadirs = g_get_system_data_dirs ();
const gchar * const *langs = g_get_language_names ();
gchar *docid; /* do not free */
gint datadir_i, lang_i;
if (!g_str_has_prefix (help->uri, "ghelp:"))
goto done;
docid = help->uri + 6;
for (datadir_i = 0; datadirs[datadir_i]; datadir_i++)
{
for (lang_i = 0; langs[lang_i]; lang_i++)
{
GFile *gfile;
GFileEnumerator *children;
GFileInfo *pageinfo;
gchar *helpdir = g_build_filename (datadirs[datadir_i],
"gnome", "help",
docid,
langs[lang_i],
NULL);
if (!g_file_test (helpdir, G_FILE_TEST_IS_DIR))
{
g_free (helpdir);
continue;
}
gfile = g_file_new_for_path (helpdir);
children = g_file_enumerate_children (gfile,
G_FILE_ATTRIBUTE_STANDARD_NAME,
G_FILE_QUERY_INFO_NONE,
NULL, NULL);
while ((pageinfo = g_file_enumerator_next_file (children, NULL, NULL)))
{
gchar *filename;
filename = g_file_info_get_attribute_as_string (pageinfo,
G_FILE_ATTRIBUTE_STANDARD_NAME);
if (g_str_has_suffix (filename, ".page"))
{
gchar *fullpath;
GFile *pagefile = g_file_resolve_relative_path (gfile, filename);
fullpath = g_file_get_path (pagefile);
gnome_help_read_page (help, fullpath);
g_free (fullpath);
g_object_unref (pagefile);
}
g_free (filename);
g_object_unref (pageinfo);
}
g_free (helpdir);
g_object_unref (gfile);
g_object_unref (children);
}
}
done:
g_idle_add ((GSourceFunc) gnome_help_thread_done, help);
}
static void
gnome_help_read_page (GnomeHelp *help,
const gchar *page)
{
xmlParserCtxtPtr parser;
xmlDocPtr doc;
xmlXPathContextPtr xpath;
xmlXPathObjectPtr obj;
gchar *id = NULL, *title = NULL;
parser = xmlNewParserCtxt ();
doc = xmlCtxtReadFile (parser, (const char *) page, NULL,
XML_PARSE_DTDLOAD | XML_PARSE_NOCDATA |
XML_PARSE_NOENT | XML_PARSE_NONET );
if (doc == NULL)
{
xmlFreeParserCtxt (parser);
return;
}
if (xmlXIncludeProcessFlags (doc,
XML_PARSE_DTDLOAD | XML_PARSE_NOCDATA |
XML_PARSE_NOENT | XML_PARSE_NONET )
< 0)
{
xmlFreeParserCtxt (parser);
return;
}
xpath = xmlXPathNewContext (doc);
xmlXPathRegisterNs (xpath, BAD_CAST "mal",
BAD_CAST "http://projectmallard.org/1.0/");
obj = xmlXPathCompiledEval (help->get_id, xpath);
if (obj)
{
if (obj->stringval)
id = g_strdup (obj->stringval);
xmlXPathFreeObject (obj);
}
if (id == NULL || g_hash_table_lookup (help->ids, id))
{
g_free (id);
xmlXPathFreeContext (xpath);
return;
}
obj = xmlXPathCompiledEval (help->get_title, xpath);
if (obj)
{
if (obj->stringval)
title = g_strdup (obj->stringval);
xmlXPathFreeObject (obj);
}
if (title != NULL)
{
gchar **tags;
gchar *uri;
gint i;
uri = g_strconcat (help->uri, "?", id, NULL);
g_hash_table_insert (help->ids, id, id);
g_hash_table_insert (help->titles, uri, title);
/* This is absolutely not how this is supposed to work. Tags aren't
* text search. There will be a mechanism of putting defined tags
* into pages. But for testing right now, we don't have those tags,
* so we'll just treat titles as if they're a list of tags.
*/
tags = g_regex_split (help->split, title, 0);
for (i = 0; tags[i]; i++)
{
GPtrArray *array;
gint j;
for (j = 0; tags[i][j] != '\0'; j++)
tags[i][j] = g_ascii_tolower (tags[i][j]);
array = g_hash_table_lookup (help->tags, tags[i]);
if (array == NULL)
{
array = g_ptr_array_new ();
g_hash_table_insert (help->tags, g_strdup (tags[i]), array);
}
g_ptr_array_add (array, uri);
}
g_strfreev (tags);
}
xmlXPathFreeContext (xpath);
}
static gboolean
gnome_help_thread_done (GnomeHelp *help)
{
if (help->get_id)
{
xmlXPathFreeCompExpr (help->get_id);
help->get_id = NULL;
}
if (help->get_title)
{
xmlXPathFreeCompExpr (help->get_title);
help->get_title = NULL;
}
g_object_set (help, "ready", TRUE, NULL);
g_object_unref (help);
return FALSE;
}
static void
gnome_help_pages_init (GnomeHelpPages *ghelp)
{
}
static void
gnome_help_pages_class_init (GnomeHelpPagesClass *class)
{
GObjectClass *object_class;
object_class = (GObjectClass *) class;
object_class->dispose = gnome_help_pages_dispose;
object_class->finalize = gnome_help_pages_finalize;
}
static void
gnome_help_pages_iface_init (GHelpPagesIface *iface)
{
iface->get_count = gnome_help_pages_get_count;
iface->get_uri = gnome_help_pages_get_uri;
iface->get_title = gnome_help_pages_get_title;
}
static void
gnome_help_pages_dispose (GObject *object)
{
GnomeHelpPages *pages = GNOME_HELP_PAGES (object);
if (pages->help)
{
g_object_unref (pages->help);
pages->help = NULL;
}
G_OBJECT_CLASS (gnome_help_pages_parent_class)->dispose (object);
}
static void
gnome_help_pages_finalize (GObject *object)
{
GnomeHelpPages *pages = GNOME_HELP_PAGES (object);
g_free (pages->tag);
G_OBJECT_CLASS (gnome_help_pages_parent_class)->finalize (object);
}
static void
gnome_help_pages_class_finalize (GnomeHelpPagesClass *class)
{
}
static GHelpPages *
gnome_help_list_for_tags (GHelp *help,
const char *tags)
{
GnomeHelp *ghelp;
GnomeHelpPages *results;
ghelp = GNOME_HELP (help);
results = g_object_new (GNOME_TYPE_HELP_PAGES, NULL);
results->help = g_object_ref (help);
results->tag = g_strdup (tags);
return (GHelpPages *) results;
}
static gint
gnome_help_pages_get_count (GHelpPages *pages)
{
GnomeHelpPages *gpages = GNOME_HELP_PAGES (pages);
GPtrArray *array;
array = g_hash_table_lookup (gpages->help->tags, gpages->tag);
if (array == 0)
return 0;
return array->len;
}
static const char *
gnome_help_pages_get_uri (GHelpPages *pages,
gint num)
{
GnomeHelpPages *gpages = GNOME_HELP_PAGES (pages);
GPtrArray *array;
array = g_hash_table_lookup (gpages->help->tags, gpages->tag);
if (array == 0)
return NULL;
return g_ptr_array_index (array, num);
}
static const char *
gnome_help_pages_get_title (GHelpPages *pages,
gint num)
{
GnomeHelpPages *gpages = GNOME_HELP_PAGES (pages);
GPtrArray *array;
array = g_hash_table_lookup (gpages->help->tags, gpages->tag);
if (array == 0)
return NULL;
return g_hash_table_lookup (gpages->help->titles,
g_ptr_array_index (array, num));
}
void
g_io_module_load (GIOModule *module)
{
g_type_module_use (G_TYPE_MODULE (module));
gnome_help_register_type (G_TYPE_MODULE (module));
gnome_help_pages_register_type (G_TYPE_MODULE (module));
g_io_extension_point_implement (G_HELP_EXTENSION_POINT_NAME,
GNOME_TYPE_HELP,
"gnomehelp",
10);
}
void
g_io_module_unload (GIOModule *module)
{
}
char **
g_io_module_query (void)
{
char *eps[] = {
G_HELP_EXTENSION_POINT_NAME,
NULL
};
return g_strdupv (eps);
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]