[recipes] Add a shell search provider
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [recipes] Add a shell search provider
- Date: Thu, 8 Dec 2016 09:53:05 +0000 (UTC)
commit 2188dc0043906b4c2ec5c6fb8a1f514feb492be6
Author: Matthias Clasen <mclasen redhat com>
Date: Wed Dec 7 21:55:56 2016 -0500
Add a shell search provider
This makes recipes show up as search results in the shell
overview.
src/Makefile.am | 20 ++-
src/gr-app.c | 90 ++++++-
src/gr-shell-search-provider.c | 352 +++++++++++++++++++++++++
src/gr-shell-search-provider.h | 41 +++
src/gr-window.c | 9 +
src/gr-window.h | 2 +
src/shell-search-provider-dbus-interfaces.xml | 44 +++
7 files changed, 545 insertions(+), 13 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 9dbbb0c..a6b39c4 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -90,12 +90,18 @@ recipes_SOURCES = \
gr-window.c \
resources.c \
types.h \
- types.c
+ types.c \
+ gr-shell-search-provider.h \
+ gr-shell-search-provider.c \
+ gr-shell-search-provider-dbus.h \
+ gr-shell-search-provider-dbus.c
BUILT_SOURCES = \
resources.c \
types.h \
- types.c
+ types.c \
+ gr-shell-search-provider-dbus.h \
+ gr-shell-search-provider-dbus.c
resource_files = $(shell $(GLIB_COMPILE_RESOURCES) --sourcedir=$(srcdir) --generate-dependencies
$(srcdir)/recipes.gresource.xml)
@@ -113,6 +119,16 @@ types.c: types.c.template gr-recipe.h
cp xgen-ec $@ && \
rm -f xgen-ec
+gr-shell-search-provider-dbus.h gr-shell-search-provider-dbus.c: Makefile.am
$(srcdir)/shell-search-provider-dbus-interfaces.xml
+ $(AM_V_GEN) gdbus-codegen \
+ --interface-prefix org.gnome. \
+ --c-namespace Gr \
+ --generate-c-code gr-shell-search-provider-dbus \
+ $(srcdir)/shell-search-provider-dbus-interfaces.xml
+
+searchproviderdir = $(datadir)/gnome-shell/search-providers
+searchprovider_DATA = org.gnome.Recipes-search-provider.ini
+
EXTRA_DIST = \
$(resource_files) \
types.h.template \
diff --git a/src/gr-app.c b/src/gr-app.c
index 6b35567..58e9888 100644
--- a/src/gr-app.c
+++ b/src/gr-app.c
@@ -28,6 +28,7 @@
#include "gr-preferences.h"
#include "gr-recipe-store.h"
#include "gr-cuisine.h"
+#include "gr-shell-search-provider.h"
struct _GrApp
@@ -35,6 +36,7 @@ struct _GrApp
GtkApplication parent_instance;
GrRecipeStore *store;
+ GrShellSearchProvider *search_provider;
};
G_DEFINE_TYPE (GrApp, gr_app, GTK_TYPE_APPLICATION)
@@ -51,6 +53,17 @@ gr_app_finalize (GObject *object)
}
static void
+gr_app_activate (GApplication *app)
+{
+ GtkWindow *win;
+
+ win = gtk_application_get_active_window (GTK_APPLICATION (app));
+ if (!win)
+ win = GTK_WINDOW (gr_window_new (GR_APP (app)));
+ gtk_window_present (win);
+}
+
+static void
preferences_activated (GSimpleAction *action,
GVariant *parameter,
gpointer app)
@@ -136,11 +149,47 @@ import_activated (GSimpleAction *action,
gr_window_load_recipe (GR_WINDOW (win), NULL);
}
+static void
+details_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer application)
+{
+ GrApp *app = GR_APP (application);
+ GtkWindow *win;
+ const char *id, *search;
+ g_autoptr(GrRecipe) recipe = NULL;
+
+ g_variant_get (parameter, "(&s&s)", &id, &search);
+
+ gr_app_activate (G_APPLICATION (app));
+ win = gtk_application_get_active_window (GTK_APPLICATION (app));
+ recipe = gr_recipe_store_get (app->store, id);
+ gr_window_show_recipe (GR_WINDOW (win), recipe);
+}
+
+static void
+search_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer application)
+{
+ GrApp *app = GR_APP (application);
+ GtkWindow *win;
+ const char *search;
+
+ g_variant_get (parameter, "&s", &search);
+
+ win = gtk_application_get_active_window (GTK_APPLICATION (app));
+ gr_app_activate (G_APPLICATION (app));
+ gr_window_show_search (GR_WINDOW (win), search);
+}
+
static GActionEntry app_entries[] =
{
{ "preferences", preferences_activated, NULL, NULL, NULL },
{ "about", about_activated, NULL, NULL, NULL },
{ "import", import_activated, NULL, NULL, NULL },
+ { "details", details_activated, "(ss)", NULL, NULL },
+ { "search", search_activated, "s", NULL, NULL },
{ "timer-expired", timer_expired, "s", NULL, NULL },
{ "quit", quit_activated, NULL, NULL, NULL }
};
@@ -192,17 +241,6 @@ gr_app_startup (GApplication *app)
}
static void
-gr_app_activate (GApplication *app)
-{
- GtkWindow *win;
-
- win = gtk_application_get_active_window (GTK_APPLICATION (app));
- if (!win)
- win = GTK_WINDOW (gr_window_new (GR_APP (app)));
- gtk_window_present (win);
-}
-
-static void
gr_app_open (GApplication *app,
GFile **files,
gint n_files,
@@ -222,6 +260,34 @@ gr_app_open (GApplication *app,
gtk_window_present (win);
}
+static gboolean
+gr_app_dbus_register (GApplication *application,
+ GDBusConnection *connection,
+ const gchar *object_path,
+ GError **error)
+{
+ GrApp *app = GR_APP (application);
+
+ app->search_provider = gr_shell_search_provider_new ();
+ gr_shell_search_provider_setup (app->search_provider, app->store);
+
+ return gr_shell_search_provider_register (app->search_provider, connection, error);
+}
+
+static void
+gr_app_dbus_unregister (GApplication *application,
+ GDBusConnection *connection,
+ const gchar *object_path)
+{
+ GrApp *app = GR_APP (application);
+
+ if (app->search_provider != NULL) {
+ gr_shell_search_provider_unregister (app->search_provider);
+ g_clear_object (&app->search_provider);
+ }
+}
+
+
static void
gr_app_init (GrApp *self)
{
@@ -239,6 +305,8 @@ gr_app_class_init (GrAppClass *klass)
application_class->startup = gr_app_startup;
application_class->activate = gr_app_activate;
application_class->open = gr_app_open;
+ application_class->dbus_register = gr_app_dbus_register;
+ application_class->dbus_unregister = gr_app_dbus_unregister;
}
GrApp *
diff --git a/src/gr-shell-search-provider.c b/src/gr-shell-search-provider.c
new file mode 100644
index 0000000..14139c0
--- /dev/null
+++ b/src/gr-shell-search-provider.c
@@ -0,0 +1,352 @@
+/* gr-shell-search-provider.c:
+ *
+ * Copyright (C) 2016 Matthias Clasen
+ *
+ * Licensed under the GNU General Public License Version 3
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 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.
+ */
+
+#include <config.h>
+
+#include <gio/gio.h>
+#include <string.h>
+#include <glib/gi18n.h>
+
+#include "gr-shell-search-provider-dbus.h"
+#include "gr-shell-search-provider.h"
+#include "gr-images.h"
+#include "gr-utils.h"
+
+
+typedef struct {
+ GrShellSearchProvider *provider;
+ GDBusMethodInvocation *invocation;
+ GrRecipeSearch *search;
+ GList *results;
+} PendingSearch;
+
+struct _GrShellSearchProvider {
+ GObject parent;
+
+ GrShellSearchProvider2 *skeleton;
+ GrRecipeStore *store;
+ GCancellable *cancellable;
+
+ GHashTable *metas_cache;
+};
+
+G_DEFINE_TYPE (GrShellSearchProvider, gr_shell_search_provider, G_TYPE_OBJECT)
+
+static void
+pending_search_free (PendingSearch *search)
+{
+ g_object_unref (search->invocation);
+ g_object_unref (search->search);
+ g_slice_free (PendingSearch, search);
+}
+
+static void
+finished_cb (GrRecipeSearch *search,
+ gpointer user_data)
+{
+ PendingSearch *pending_search = user_data;
+ GVariantBuilder builder;
+ GList *l;
+
+ if (pending_search->results == NULL) {
+ g_dbus_method_invocation_return_value (pending_search->invocation, g_variant_new ("(as)",
NULL));
+ pending_search_free (pending_search);
+ g_application_release (g_application_get_default ());
+ return;
+ }
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
+ for (l = pending_search->results; l; l = l->next) {
+ g_variant_builder_add (&builder, "s", l->data);
+ }
+ g_dbus_method_invocation_return_value (pending_search->invocation, g_variant_new ("(as)", &builder));
+
+ pending_search_free (pending_search);
+ g_application_release (g_application_get_default ());
+}
+
+static void
+hits_added_cb (GrRecipeSearch *search,
+ GList *hits,
+ PendingSearch *pending_search)
+{
+ GList *l;
+
+ for (l = hits; l; l = l->next) {
+ GrRecipe *recipe = l->data;
+
+ pending_search->results = g_list_prepend (pending_search->results,
(gpointer)gr_recipe_get_name (recipe));
+ }
+}
+
+static void
+execute_search (GrShellSearchProvider *self,
+ GDBusMethodInvocation *invocation,
+ gchar **terms)
+{
+ PendingSearch *pending_search;
+ g_autofree gchar *string = NULL;
+
+ string = g_strjoinv (" ", terms);
+
+ if (self->cancellable != NULL) {
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+ }
+
+ /* don't attempt searches for a single character */
+ if (g_strv_length (terms) == 1 &&
+ g_utf8_strlen (terms[0], -1) == 1) {
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(as)", NULL));
+ return;
+ }
+
+ pending_search = g_slice_new (PendingSearch);
+ pending_search->provider = self;
+ pending_search->invocation = g_object_ref (invocation);
+ pending_search->results = NULL;
+
+ g_application_hold (g_application_get_default ());
+ self->cancellable = g_cancellable_new ();
+
+ pending_search->search = gr_recipe_search_new ();
+ g_signal_connect (pending_search->search, "hits-added",
+ G_CALLBACK (hits_added_cb), pending_search);
+ g_signal_connect (pending_search->search, "finished",
+ G_CALLBACK (finished_cb), pending_search);
+
+ gr_recipe_search_set_query (pending_search->search, string);
+}
+
+static gboolean
+handle_get_initial_result_set (GrShellSearchProvider2 *skeleton,
+ GDBusMethodInvocation *invocation,
+ gchar **terms,
+ gpointer user_data)
+{
+ GrShellSearchProvider *self = user_data;
+
+ g_debug ("****** GetInitialResultSet");
+ execute_search (self, invocation, terms);
+ return TRUE;
+}
+
+static gboolean
+handle_get_subsearch_result_set (GrShellSearchProvider2 *skeleton,
+ GDBusMethodInvocation *invocation,
+ gchar **previous_results,
+ gchar **terms,
+ gpointer user_data)
+{
+ GrShellSearchProvider *self = user_data;
+
+ g_debug ("****** GetSubSearchResultSet");
+ execute_search (self, invocation, terms);
+ return TRUE;
+}
+
+static GdkPixbuf *
+gr_recipe_get_pixbuf (GrRecipe *recipe)
+{
+ g_autoptr(GArray) images = NULL;
+
+ g_object_get (recipe, "images", &images, NULL);
+
+ if (images->len > 0) {
+ GrRotatedImage *ri = &g_array_index (images, GrRotatedImage, 0);
+ GdkPixbuf *pixbuf;
+
+ pixbuf = load_pixbuf_fill_size (ri->path, ri->angle, 64, 64);
+ return pixbuf;
+ }
+
+ return NULL;
+}
+
+static gboolean
+handle_get_result_metas (GrShellSearchProvider2 *skeleton,
+ GDBusMethodInvocation *invocation,
+ gchar **results,
+ gpointer user_data)
+{
+ GrShellSearchProvider *self = user_data;
+ GVariantBuilder meta;
+ GVariant *meta_variant;
+ GdkPixbuf *pixbuf;
+ gint i;
+ GVariantBuilder builder;
+
+ g_debug ("****** GetResultMetas");
+
+ for (i = 0; results[i]; i++) {
+ g_autoptr(GrRecipe) recipe = NULL;
+
+ if (g_hash_table_lookup (self->metas_cache, results[i]))
+ continue;
+
+ recipe = gr_recipe_store_get (self->store, results[i]);
+ if (recipe == NULL) {
+ g_warning ("failed to find %s", results[i]);
+ continue;
+ }
+
+ g_variant_builder_init (&meta, G_VARIANT_TYPE ("a{sv}"));
+ g_variant_builder_add (&meta, "{sv}", "id", g_variant_new_string (gr_recipe_get_name
(recipe)));
+ g_variant_builder_add (&meta, "{sv}", "name", g_variant_new_string (gr_recipe_get_name
(recipe)));
+ pixbuf = gr_recipe_get_pixbuf (recipe);
+ if (pixbuf != NULL)
+ g_variant_builder_add (&meta, "{sv}", "icon", g_icon_serialize (G_ICON (pixbuf)));
+ g_variant_builder_add (&meta, "{sv}", "description", g_variant_new_string
(gr_recipe_get_description (recipe)));
+ meta_variant = g_variant_builder_end (&meta);
+ g_hash_table_insert (self->metas_cache, g_strdup (gr_recipe_get_name (recipe)),
g_variant_ref_sink (meta_variant));
+
+ }
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
+ for (i = 0; results[i]; i++) {
+ meta_variant = (GVariant*)g_hash_table_lookup (self->metas_cache, results[i]);
+ if (meta_variant == NULL)
+ continue;
+ g_variant_builder_add_value (&builder, meta_variant);
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(aa{sv})", &builder));
+
+ return TRUE;
+}
+
+static gboolean
+handle_activate_result (GrShellSearchProvider2 *skeleton,
+ GDBusMethodInvocation *invocation,
+ gchar *result,
+ gchar **terms,
+ guint32 timestamp,
+ gpointer user_data)
+{
+ GApplication *app = g_application_get_default ();
+ g_autofree gchar *string = NULL;
+
+ string = g_strjoinv (" ", terms);
+
+ g_action_group_activate_action (G_ACTION_GROUP (app), "details",
+ g_variant_new ("(ss)", result, string));
+
+ gr_shell_search_provider2_complete_activate_result (skeleton, invocation);
+
+ return TRUE;
+}
+
+static gboolean
+handle_launch_search (GrShellSearchProvider2 *skeleton,
+ GDBusMethodInvocation *invocation,
+ gchar **terms,
+ guint32 timestamp,
+ gpointer user_data)
+{
+ GApplication *app = g_application_get_default ();
+ g_autofree gchar *string = g_strjoinv (" ", terms);
+
+ g_action_group_activate_action (G_ACTION_GROUP (app), "search",
+ g_variant_new ("s", string));
+
+ gr_shell_search_provider2_complete_launch_search (skeleton, invocation);
+
+ return TRUE;
+}
+
+gboolean
+gr_shell_search_provider_register (GrShellSearchProvider *self,
+ GDBusConnection *connection,
+ GError **error)
+{
+ return g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->skeleton),
+ connection,
+ "/org/gnome/Recipes/SearchProvider",
+ error);
+}
+
+void
+gr_shell_search_provider_unregister (GrShellSearchProvider *self)
+{
+ g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self->skeleton));
+}
+
+static void
+search_provider_dispose (GObject *obj)
+{
+ GrShellSearchProvider *self = GR_SHELL_SEARCH_PROVIDER (obj);
+
+ if (self->cancellable != NULL) {
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+ }
+
+ if (self->metas_cache != NULL) {
+ g_hash_table_destroy (self->metas_cache);
+ self->metas_cache = NULL;
+ }
+
+ g_clear_object (&self->store);
+ g_clear_object (&self->skeleton);
+
+ G_OBJECT_CLASS (gr_shell_search_provider_parent_class)->dispose (obj);
+}
+
+static void
+gr_shell_search_provider_init (GrShellSearchProvider *self)
+{
+ self->metas_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) g_variant_unref);
+
+ self->skeleton = gr_shell_search_provider2_skeleton_new ();
+
+ g_signal_connect (self->skeleton, "handle-get-initial-result-set",
+ G_CALLBACK (handle_get_initial_result_set), self);
+ g_signal_connect (self->skeleton, "handle-get-subsearch-result-set",
+ G_CALLBACK (handle_get_subsearch_result_set), self);
+ g_signal_connect (self->skeleton, "handle-get-result-metas",
+ G_CALLBACK (handle_get_result_metas), self);
+ g_signal_connect (self->skeleton, "handle-activate-result",
+ G_CALLBACK (handle_activate_result), self);
+ g_signal_connect (self->skeleton, "handle-launch-search",
+ G_CALLBACK (handle_launch_search), self);
+}
+
+static void
+gr_shell_search_provider_class_init (GrShellSearchProviderClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->dispose = search_provider_dispose;
+}
+
+GrShellSearchProvider *
+gr_shell_search_provider_new (void)
+{
+ return g_object_new (gr_shell_search_provider_get_type (), NULL);
+}
+
+void
+gr_shell_search_provider_setup (GrShellSearchProvider *provider,
+ GrRecipeStore *store)
+{
+ provider->store = g_object_ref (store);
+}
diff --git a/src/gr-shell-search-provider.h b/src/gr-shell-search-provider.h
new file mode 100644
index 0000000..26a6688
--- /dev/null
+++ b/src/gr-shell-search-provider.h
@@ -0,0 +1,41 @@
+/* gr-shell-search-provider.h:
+ *
+ * Copyright (C) 2016 Matthias Clasen <mclasen redhat com>
+ *
+ * Licensed under the GNU General Public License Version 3
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 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.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+#include "gr-recipe-store.h"
+
+G_BEGIN_DECLS
+
+#define GR_TYPE_SHELL_SEARCH_PROVIDER gr_shell_search_provider_get_type()
+
+G_DECLARE_FINAL_TYPE (GrShellSearchProvider, gr_shell_search_provider, GR, SHELL_SEARCH_PROVIDER, GObject)
+
+gboolean gr_shell_search_provider_register (GrShellSearchProvider *self,
+ GDBusConnection *connection,
+ GError **error);
+void gr_shell_search_provider_unregister (GrShellSearchProvider *self);
+GrShellSearchProvider *gr_shell_search_provider_new (void);
+void gr_shell_search_provider_setup (GrShellSearchProvider *self,
+ GrRecipeStore *store);
+
+G_END_DECLS
diff --git a/src/gr-window.c b/src/gr-window.c
index 16dd27b..eed30a6 100644
--- a/src/gr-window.c
+++ b/src/gr-window.c
@@ -189,6 +189,15 @@ switch_to_search (GrWindow *window)
gtk_stack_set_visible_child_name (GTK_STACK (window->main_stack), "search");
}
+void
+gr_window_show_search (GrWindow *window,
+ const char *search)
+{
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (window->search_button), TRUE);
+ switch_to_search (window);
+ gr_query_editor_set_query (GR_QUERY_EDITOR (window->search_bar), search);
+}
+
static void
visible_page_changed (GrWindow *window)
{
diff --git a/src/gr-window.h b/src/gr-window.h
index ff39668..7bf49db 100644
--- a/src/gr-window.h
+++ b/src/gr-window.h
@@ -38,6 +38,8 @@ void gr_window_edit_recipe (GrWindow *window,
GrRecipe *recipe);
void gr_window_load_recipe (GrWindow *window,
GFile *file);
+void gr_window_show_search (GrWindow *window,
+ const char *terms);
void gr_window_go_back (GrWindow *window);
void gr_window_show_diet (GrWindow *window,
const char *title,
diff --git a/src/shell-search-provider-dbus-interfaces.xml b/src/shell-search-provider-dbus-interfaces.xml
new file mode 100644
index 0000000..f6840e2
--- /dev/null
+++ b/src/shell-search-provider-dbus-interfaces.xml
@@ -0,0 +1,44 @@
+<!DOCTYPE node PUBLIC
+"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<!--
+ 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, see <http://www.gnu.org/licenses/>.
+-->
+<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
+ <interface name='org.gnome.Shell.SearchProvider2'>
+ <method name='GetInitialResultSet'>
+ <arg type='as' name='Terms' direction='in' />
+ <arg type='as' name='Results' direction='out' />
+ </method>
+ <method name = 'GetSubsearchResultSet'>
+ <arg type='as' name='PreviousResults' direction='in' />
+ <arg type='as' name='Terms' direction='in' />
+ <arg type='as' name='Results' direction='out' />
+ </method>
+ <method name = 'GetResultMetas'>
+ <arg type='as' name='Results' direction='in' />
+ <arg type='aa{sv}' name='Metas' direction='out' />
+ </method>
+ <method name = 'ActivateResult'>
+ <arg type='s' name='Result' direction='in' />
+ <arg type='as' name='Terms' direction='in' />
+ <arg type='u' name='Timestamp' direction='in' />
+ </method>
+ <method name = 'LaunchSearch'>
+ <arg type='as' name='Terms' direction='in' />
+ <arg type='u' name='Timestamp' direction='in' />
+ </method>
+ </interface>
+</node>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]