summaryrefslogtreecommitdiff
path: root/mate-dictionary/libgdict
diff options
context:
space:
mode:
authorStefano Karapetsas <[email protected]>2011-12-14 10:13:54 +0100
committerStefano Karapetsas <[email protected]>2011-12-14 10:13:54 +0100
commitef0467789bfc8406b57ba553e4d59f4d6c3f9be8 (patch)
tree09d541636a16cb38448fe6183289ebdc3080c1bf /mate-dictionary/libgdict
downloadmate-utils-ef0467789bfc8406b57ba553e4d59f4d6c3f9be8.tar.bz2
mate-utils-ef0467789bfc8406b57ba553e4d59f4d6c3f9be8.tar.xz
Moved from Mate-Extra repository
Diffstat (limited to 'mate-dictionary/libgdict')
-rw-r--r--mate-dictionary/libgdict/Makefile.am134
-rw-r--r--mate-dictionary/libgdict/gdict-client-context.c2162
-rw-r--r--mate-dictionary/libgdict/gdict-client-context.h98
-rw-r--r--mate-dictionary/libgdict/gdict-context-private.h107
-rw-r--r--mate-dictionary/libgdict/gdict-context.c892
-rw-r--r--mate-dictionary/libgdict/gdict-context.h204
-rw-r--r--mate-dictionary/libgdict/gdict-database-chooser.c1142
-rw-r--r--mate-dictionary/libgdict/gdict-database-chooser.h99
-rw-r--r--mate-dictionary/libgdict/gdict-debug.h55
-rw-r--r--mate-dictionary/libgdict/gdict-defbox.c2927
-rw-r--r--mate-dictionary/libgdict/gdict-defbox.h102
-rw-r--r--mate-dictionary/libgdict/gdict-enum-types.c.in33
-rw-r--r--mate-dictionary/libgdict/gdict-enum-types.h.in26
-rw-r--r--mate-dictionary/libgdict/gdict-marshal.list7
-rw-r--r--mate-dictionary/libgdict/gdict-private.h34
-rw-r--r--mate-dictionary/libgdict/gdict-source-chooser.c917
-rw-r--r--mate-dictionary/libgdict/gdict-source-chooser.h93
-rw-r--r--mate-dictionary/libgdict/gdict-source-loader.c600
-rw-r--r--mate-dictionary/libgdict/gdict-source-loader.h81
-rw-r--r--mate-dictionary/libgdict/gdict-source.c1195
-rw-r--r--mate-dictionary/libgdict/gdict-source.h115
-rw-r--r--mate-dictionary/libgdict/gdict-speller.c827
-rw-r--r--mate-dictionary/libgdict/gdict-speller.h87
-rw-r--r--mate-dictionary/libgdict/gdict-strategy-chooser.c1092
-rw-r--r--mate-dictionary/libgdict/gdict-strategy-chooser.h94
-rw-r--r--mate-dictionary/libgdict/gdict-utils.c333
-rw-r--r--mate-dictionary/libgdict/gdict-utils.h85
-rw-r--r--mate-dictionary/libgdict/gdict-version.h.in92
-rw-r--r--mate-dictionary/libgdict/gdict.h36
-rw-r--r--mate-dictionary/libgdict/mate-dict.pc.in12
30 files changed, 13681 insertions, 0 deletions
diff --git a/mate-dictionary/libgdict/Makefile.am b/mate-dictionary/libgdict/Makefile.am
new file mode 100644
index 00000000..006b42f4
--- /dev/null
+++ b/mate-dictionary/libgdict/Makefile.am
@@ -0,0 +1,134 @@
+NULL =
+
+INCLUDES = -DG_LOG_DOMAIN=\"Gdict\" \
+ -DDATADIR=\""$(datadir)"\" \
+ -DLIBDIR=\""$(libdir)"\" \
+ -DSYSCONFDIR=\""$(sysconfdir)"\" \
+ -DPREFIX=\""$(prefix)"\" \
+ -DMATELOCALEDIR=\""$(mateutilslocaledir)"\" \
+ -DGDICTSOURCESDIR=\""$(datadir)/mate-dict/sources"\" \
+ -DGDICT_ENABLE_INTERNALS=1 \
+ -DG_DISABLE_DEPRECATED \
+ -DGDK_DISABLE_DEPRECATED \
+ -DGTK_DISABLE_DEPRECATED \
+ -DPANGO_DISABLE_DEPRECATED \
+ -DG_DISABLE_SINGLE_INCLUDES \
+ -DGTK_DISABLE_SINGLE_INCLUDES \
+ $(NULL)
+
+sources_h = \
+ $(srcdir)/gdict-context.h \
+ $(srcdir)/gdict-client-context.h \
+ $(srcdir)/gdict-database-chooser.h \
+ $(srcdir)/gdict-defbox.h \
+ $(srcdir)/gdict-source-chooser.h \
+ $(srcdir)/gdict-source-loader.h \
+ $(srcdir)/gdict-source.h \
+ $(srcdir)/gdict-speller.h \
+ $(srcdir)/gdict-strategy-chooser.h \
+ $(srcdir)/gdict-utils.h \
+ $(NULL)
+
+sources_h_priv = \
+ $(srcdir)/gdict-context-private.h \
+ $(srcdir)/gdict-debug.h \
+ $(srcdir)/gdict-private.h \
+ $(NULL)
+
+sources_c = \
+ $(srcdir)/gdict-context.c \
+ $(srcdir)/gdict-client-context.c \
+ $(srcdir)/gdict-database-chooser.c \
+ $(srcdir)/gdict-defbox.c \
+ gdict-enum-types.c \
+ gdict-marshal.c \
+ $(srcdir)/gdict-source-chooser.c \
+ $(srcdir)/gdict-source-loader.c \
+ $(srcdir)/gdict-source.c \
+ $(srcdir)/gdict-speller.c \
+ $(srcdir)/gdict-strategy-chooser.c \
+ $(srcdir)/gdict-utils.c \
+ $(NULL)
+
+lib_LTLIBRARIES = libmatedict.la
+
+# MATE Dictionary shared library
+libmatedict_la_SOURCES = $(sources_h) $(sources_h_priv) $(sources_c)
+libmatedict_la_CPPFLAGS = $(LIBGDICT_CFLAGS) $(GDICT_DEBUG_CFLAGS) $(MAINTAINER_CFLAGS)
+libmatedict_la_LIBADD = $(LIBGDICT_LIBS)
+libmatedict_la_LDFLAGS = \
+ -version-info $(LIBGDICT_LT_VERSION) \
+ -export-dynamic \
+ -no-undefined
+
+libgdict_includedir = $(includedir)/mate-dict/gdict
+libgdict_include_HEADERS = \
+ $(sources_h) \
+ gdict-enum-types.h \
+ gdict-version.h \
+ $(srcdir)/gdict.h \
+ $(NULL)
+
+gdict_built_files = \
+ gdict-enum-types.h \
+ gdict-enum-types.c \
+ gdict-marshal.h \
+ gdict-marshal.c
+
+stamp_files = stamp-gdict-enum-types.h stamp-gdict-marshal.h
+
+CLEANFILES = $(stamp_files) $(gdict_built_files)
+DISTCLEANFILES = gdict-version.h
+MAINTAINERCLEANFILES = $(stamp_files) $(gdict_built_files) gdict-version.h
+
+EXTRA_DIST = \
+ gdict-version.h.in \
+ gdict-enum-types.h.in \
+ gdict-enum-types.c.in \
+ gdict-marshal.list \
+ mate-dict.pc.in \
+ $(NULL)
+
+BUILT_SOURCES = $(gdict_built_files)
+
+gdict-marshal.h: stamp-gdict-marshal.h
+ @true
+stamp-gdict-marshal.h: gdict-marshal.list $(GLIB_GENMARSHAL) Makefile
+ $(AM_V_GEN)$(GLIB_GENMARSHAL) $< --header --prefix=gdict_marshal > xgen-gmh \
+ && ( cmp -s xgen-gmh gdict-marshal.h || cp xgen-gmh gdict-marshal.h ) \
+ && rm -f xgen-gmh \
+ && echo timestamp > $(@F)
+
+gdict-marshal.c: gdict-marshal.list $(GLIB_GENMARSHAL) Makefile
+ $(AM_V_GEN)( echo "#include \"gdict-marshal.h\""; echo; \
+ $(GLIB_GENMARSHAL) $< --body --prefix=gdict_marshal ) > xgen-gmc \
+ && cp xgen-gmc gdict-marshal.c \
+ && rm -f xgen-gmc
+
+gdict-enum-types.h: stamp-gdict-enum-types.h
+ @true
+stamp-gdict-enum-types.h: $(sources_h) gdict-enum-types.h.in Makefile
+ $(AM_V_GEN)( cd $(srcdir) && \
+ $(GLIB_MKENUMS) \
+ --template $(srcdir)/gdict-enum-types.h.in \
+ $(sources_h) ) > xgen-ceth \
+ && ( cmp -s xgen-ceth gdict-enum-types.h || cp xgen-ceth gdict-enum-types.h ) \
+ && rm -f xgen-ceth \
+ && echo timestamp > $(@F)
+
+gdict-enum-types.c: gdict-enum-types.h gdict-enum-types.c.in Makefile
+ $(AM_V_GEN)( cd $(srcdir) && \
+ $(GLIB_MKENUMS) \
+ --template $(srcdir)/gdict-enum-types.c.in \
+ $(sources_h) ) > xgen-cetc \
+ && cp xgen-cetc gdict-enum-types.c \
+ && rm -f xgen-cetc
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = mate-dict.pc
+
+distclean-local:
+ if test $(srcdir) != .; then \
+ rm -f $(MAINTAINERCLEANFILES); \
+ fi
+
diff --git a/mate-dictionary/libgdict/gdict-client-context.c b/mate-dictionary/libgdict/gdict-client-context.c
new file mode 100644
index 00000000..5e370f0a
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-client-context.c
@@ -0,0 +1,2162 @@
+/* gdict-client-context.c -
+ *
+ * Copyright (C) 2005 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+/**
+ * SECTION:gdict-client-context
+ * @short_description: DICT client transport
+ *
+ * #GdictClientContext is an implementation of the #GdictContext interface.
+ * It implements the Dictionary Protocol as defined by the RFC 2229 in order
+ * to connect to a dictionary server.
+ *
+ * You should rarely instantiate this object directely: use an appropriate
+ * #GdictSource instead.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <errno.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+
+#include "gdict-context-private.h"
+#include "gdict-context.h"
+#include "gdict-client-context.h"
+#include "gdict-enum-types.h"
+#include "gdict-marshal.h"
+#include "gdict-debug.h"
+#include "gdict-utils.h"
+#include "gdict-private.h"
+
+typedef enum {
+ CMD_CLIENT,
+ CMD_SHOW_DB,
+ CMD_SHOW_STRAT,
+ CMD_SHOW_INFO, /* not implemented */
+ CMD_SHOW_SERVER, /* not implemented */
+ CMD_MATCH,
+ CMD_DEFINE,
+ CMD_STATUS, /* not implemented */
+ CMD_OPTION_MIME, /* not implemented */
+ CMD_AUTH, /* not implemented */
+ CMD_HELP, /* not implemented */
+ CMD_QUIT,
+
+ CMD_INVALID
+} GdictCommandType;
+#define IS_VALID_CMD(cmd) (((cmd) >= CMD_CLIENT) || ((cmd) < CMD_INVALID))
+
+/* command strings: keep synced with the enum above! */
+static const gchar *dict_command_strings[] = {
+ "CLIENT",
+ "SHOW DB",
+ "SHOW STRAT",
+ "SHOW INFO",
+ "SHOW SERVER",
+ "MATCH",
+ "DEFINE",
+ "STATUS",
+ "OPTION MIME",
+ "AUTH",
+ "HELP",
+ "QUIT",
+
+ NULL
+};
+
+/* command stata */
+enum
+{
+ S_START,
+
+ S_STATUS,
+ S_DATA,
+
+ S_FINISH
+};
+
+typedef struct
+{
+ GdictCommandType cmd_type;
+
+ gchar *cmd_string;
+ guint state;
+
+ /* optional parameters passed to the command */
+ gchar *database;
+ gchar *strategy;
+ gchar *word;
+
+ /* buffer used to hold the reply from the server */
+ GString *buffer;
+
+ gpointer data;
+ GDestroyNotify data_destroy;
+} GdictCommand;
+
+/* The default string to be passed to the CLIENT command */
+#define GDICT_DEFAULT_CLIENT "MATE Dictionary (" VERSION ")"
+
+/* Default server:port couple */
+#define GDICT_DEFAULT_HOSTNAME "dict.org"
+#define GDICT_DEFAULT_PORT 2628
+
+/* make the hostname lookup expire every five minutes */
+#define HOSTNAME_LOOKUP_EXPIRE 300
+
+/* wait 30 seconds between connection and receiving data on the line */
+#define CONNECTION_TIMEOUT 30
+
+enum
+{
+ PROP_0,
+
+ PROP_HOSTNAME,
+ PROP_PORT,
+ PROP_STATUS,
+ PROP_CLIENT_NAME
+};
+
+enum
+{
+ CONNECTED,
+ DISCONNECTED,
+
+ LAST_SIGNAL
+};
+
+static guint gdict_client_context_signals[LAST_SIGNAL] = { 0 };
+
+#define GDICT_CLIENT_CONTEXT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDICT_TYPE_CLIENT_CONTEXT, GdictClientContextPrivate))
+struct _GdictClientContextPrivate
+{
+#ifdef ENABLE_IPV6
+ struct sockaddr_storage sockaddr;
+ struct addrinfo *host6info;
+#else
+ struct sockaddr_in sockaddr;
+#endif
+ struct hostent *hostinfo;
+
+ time_t last_lookup;
+
+ gchar *hostname;
+ gint port;
+
+ GIOChannel *channel;
+ guint source_id;
+ guint timeout_id;
+
+ GdictCommand *command;
+ GQueue *commands_queue;
+
+ gchar *client_name;
+
+ GdictStatusCode status_code;
+
+ guint local_only : 1;
+ guint is_connecting : 1;
+};
+
+static void gdict_client_context_iface_init (GdictContextIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GdictClientContext,
+ gdict_client_context,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GDICT_TYPE_CONTEXT,
+ gdict_client_context_iface_init));
+
+/* GObject methods */
+static void gdict_client_context_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gdict_client_context_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gdict_client_context_finalize (GObject *object);
+
+/* GdictContext methods */
+static gboolean gdict_client_context_get_databases (GdictContext *context,
+ GError **error);
+static gboolean gdict_client_context_get_strategies (GdictContext *context,
+ GError **error);
+static gboolean gdict_client_context_define_word (GdictContext *context,
+ const gchar *database,
+ const gchar *word,
+ GError **error);
+static gboolean gdict_client_context_match_word (GdictContext *context,
+ const gchar *database,
+ const gchar *strategy,
+ const gchar *word,
+ GError **error);
+
+static void gdict_client_context_clear_hostinfo (GdictClientContext *context);
+static gboolean gdict_client_context_lookup_server (GdictClientContext *context,
+ GError **error);
+static gboolean gdict_client_context_io_watch_cb (GIOChannel *source,
+ GIOCondition condition,
+ GdictClientContext *context);
+static gboolean gdict_client_context_parse_line (GdictClientContext *context,
+ const gchar *buffer);
+static void gdict_client_context_disconnect (GdictClientContext *context);
+static void gdict_client_context_force_disconnect (GdictClientContext *context);
+static void gdict_client_context_real_connected (GdictClientContext *context);
+static void gdict_client_context_real_disconnected (GdictClientContext *context);
+
+static GdictCommand *gdict_command_new (GdictCommandType cmd_type);
+static void gdict_command_free (GdictCommand *cmd);
+
+
+
+GQuark
+gdict_client_context_error_quark (void)
+{
+ return g_quark_from_static_string ("gdict-client-context-error-quark");
+}
+
+static void
+gdict_client_context_iface_init (GdictContextIface *iface)
+{
+ iface->get_databases = gdict_client_context_get_databases;
+ iface->get_strategies = gdict_client_context_get_strategies;
+ iface->match_word = gdict_client_context_match_word;
+ iface->define_word = gdict_client_context_define_word;
+}
+
+static void
+gdict_client_context_class_init (GdictClientContextClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->set_property = gdict_client_context_set_property;
+ gobject_class->get_property = gdict_client_context_get_property;
+ gobject_class->finalize = gdict_client_context_finalize;
+
+ g_object_class_override_property (gobject_class,
+ GDICT_CONTEXT_PROP_LOCAL_ONLY,
+ "local-only");
+
+ /**
+ * GdictClientContext:client-name
+ *
+ * The name of the client using this context; it will be advertised when
+ * connecting to the dictionary server.
+ *
+ * Since: 1.0
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_CLIENT_NAME,
+ g_param_spec_string ("client-name",
+ _("Client Name"),
+ _("The name of the client of the context object"),
+ NULL,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ /**
+ * GdictClientContext:hostname
+ *
+ * The hostname of the dictionary server to connect to.
+ *
+ * Since: 1.0
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_HOSTNAME,
+ g_param_spec_string ("hostname",
+ _("Hostname"),
+ _("The hostname of the dictionary server to connect to"),
+ NULL,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ /**
+ * GdictClientContext:port
+ *
+ * The port of the dictionary server to connect to.
+ *
+ * Since: 1.0
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_PORT,
+ g_param_spec_uint ("port",
+ _("Port"),
+ _("The port of the dictionary server to connect to"),
+ 0,
+ 65535,
+ GDICT_DEFAULT_PORT,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ /**
+ * GdictClientContext:status
+ *
+ * The status code as returned by the dictionary server.
+ *
+ * Since: 1.0
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_STATUS,
+ g_param_spec_enum ("status",
+ _("Status"),
+ _("The status code as returned by the dictionary server"),
+ GDICT_TYPE_STATUS_CODE,
+ GDICT_STATUS_INVALID,
+ G_PARAM_READABLE));
+
+ /**
+ * GdictClientContext::connected
+ * @client: the object which received the signal
+ *
+ * Emitted when a #GdictClientContext has successfully established a
+ * connection with a dictionary server.
+ *
+ * Since: 1.0
+ */
+ gdict_client_context_signals[CONNECTED] =
+ g_signal_new ("connected",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdictClientContextClass, connected),
+ NULL, NULL,
+ gdict_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ /**
+ * GdictClientContext::disconnected
+ * @client: the object which received the signal
+ *
+ * Emitted when a #GdictClientContext has disconnected from a dictionary
+ * server.
+ *
+ * Since: 1.0
+ */
+ gdict_client_context_signals[DISCONNECTED] =
+ g_signal_new ("disconnected",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdictClientContextClass, disconnected),
+ NULL, NULL,
+ gdict_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ klass->connected = gdict_client_context_real_connected;
+ klass->disconnected = gdict_client_context_real_disconnected;
+
+ g_type_class_add_private (gobject_class, sizeof (GdictClientContextPrivate));
+}
+
+static void
+gdict_client_context_init (GdictClientContext *context)
+{
+ GdictClientContextPrivate *priv;
+
+ priv = GDICT_CLIENT_CONTEXT_GET_PRIVATE (context);
+ context->priv = priv;
+
+ priv->hostname = NULL;
+ priv->port = 0;
+
+ priv->hostinfo = NULL;
+#ifdef ENABLE_IPV6
+ priv->host6info = NULL;
+#endif
+
+ priv->last_lookup = (time_t) -1;
+
+ priv->is_connecting = FALSE;
+ priv->local_only = FALSE;
+
+ priv->status_code = GDICT_STATUS_INVALID;
+
+ priv->client_name = NULL;
+
+ priv->command = NULL;
+ priv->commands_queue = g_queue_new ();
+}
+
+static void
+gdict_client_context_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GdictClientContextPrivate *priv = GDICT_CLIENT_CONTEXT_GET_PRIVATE (object);
+
+ switch (prop_id)
+ {
+ case PROP_HOSTNAME:
+ if (priv->hostname)
+ g_free (priv->hostname);
+ priv->hostname = g_strdup (g_value_get_string (value));
+ gdict_client_context_clear_hostinfo (GDICT_CLIENT_CONTEXT (object));
+ break;
+ case PROP_PORT:
+ priv->port = g_value_get_uint (value);
+ break;
+ case PROP_CLIENT_NAME:
+ if (priv->client_name)
+ g_free (priv->client_name);
+ priv->client_name = g_strdup (g_value_get_string (value));
+ break;
+ case GDICT_CONTEXT_PROP_LOCAL_ONLY:
+ priv->local_only = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gdict_client_context_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GdictClientContextPrivate *priv = GDICT_CLIENT_CONTEXT_GET_PRIVATE (object);
+
+ switch (prop_id)
+ {
+ case PROP_STATUS:
+ g_value_set_enum (value, priv->status_code);
+ break;
+ case PROP_HOSTNAME:
+ g_value_set_string (value, priv->hostname);
+ break;
+ case PROP_PORT:
+ g_value_set_uint (value, priv->port);
+ break;
+ case PROP_CLIENT_NAME:
+ g_value_set_string (value, priv->client_name);
+ break;
+ case GDICT_CONTEXT_PROP_LOCAL_ONLY:
+ g_value_set_boolean (value, priv->local_only);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gdict_client_context_finalize (GObject *object)
+{
+ GdictClientContext *context = GDICT_CLIENT_CONTEXT (object);
+ GdictClientContextPrivate *priv = context->priv;
+
+ /* force disconnection */
+ gdict_client_context_force_disconnect (context);
+
+ if (priv->command)
+ gdict_command_free (priv->command);
+
+ if (priv->commands_queue)
+ {
+ g_queue_foreach (priv->commands_queue,
+ (GFunc) gdict_command_free,
+ NULL);
+ g_queue_free (priv->commands_queue);
+
+ priv->commands_queue = NULL;
+ }
+
+ if (priv->client_name)
+ g_free (priv->client_name);
+
+ if (priv->hostname)
+ g_free (priv->hostname);
+
+#ifdef ENABLE_IPV6
+ if (priv->host6info)
+ freeaddrinfo (priv->host6info);
+#endif
+
+ /* chain up parent's finalize method */
+ G_OBJECT_CLASS (gdict_client_context_parent_class)->finalize (object);
+}
+
+/**
+ * gdict_client_context_new:
+ * @hostname: the hostname of a dictionary server, or %NULL for the
+ * default server
+ * @port: port to be used when connecting to the dictionary server,
+ * or -1 for the default port
+ *
+ * Creates a new #GdictClientContext object for @hostname. Use this
+ * object to connect and query the dictionary server using the Dictionary
+ * Protocol as defined by RFC 2229.
+ *
+ * Return value: the newly created #GdictClientContext object. You should
+ * free it using g_object_unref().
+ */
+GdictContext *
+gdict_client_context_new (const gchar *hostname,
+ gint port)
+{
+ return g_object_new (GDICT_TYPE_CLIENT_CONTEXT,
+ "hostname", (hostname != NULL ? hostname : GDICT_DEFAULT_HOSTNAME),
+ "port", (port != -1 ? port : GDICT_DEFAULT_PORT),
+ "client-name", GDICT_DEFAULT_CLIENT,
+ NULL);
+}
+
+/**
+ * gdict_client_context_set_hostname:
+ * @context: a #GdictClientContext
+ * @hostname: the hostname of a Dictionary server, or %NULL
+ *
+ * Sets @hostname as the hostname of the dictionary server to be used.
+ * If @hostname is %NULL, the default dictionary server will be used.
+ */
+void
+gdict_client_context_set_hostname (GdictClientContext *context,
+ const gchar *hostname)
+{
+ g_return_if_fail (GDICT_IS_CLIENT_CONTEXT (context));
+
+ g_object_set (G_OBJECT (context),
+ "hostname", (hostname != NULL ? hostname : GDICT_DEFAULT_HOSTNAME),
+ NULL);
+}
+
+/**
+ * gdict_client_context_get_hostname:
+ * @context: a #GdictClientContext
+ *
+ * Gets the hostname of the dictionary server used by @context.
+ *
+ * Return value: the hostname of a dictionary server. The returned string is
+ * owned by the #GdictClientContext object and should never be modified or
+ * freed.
+ */
+const gchar *
+gdict_client_context_get_hostname (GdictClientContext *context)
+{
+ gchar *hostname;
+
+ g_return_val_if_fail (GDICT_IS_CLIENT_CONTEXT (context), NULL);
+
+ g_object_get (G_OBJECT (context), "hostname", &hostname, NULL);
+
+ return hostname;
+}
+
+/**
+ * gdict_client_context_set_port:
+ * @context: a #GdictClientContext
+ * @port: port of the dictionary server to be used, or -1
+ *
+ * Sets the port of the dictionary server to be used when connecting.
+ *
+ * If @port is -1, the default port will be used.
+ */
+void
+gdict_client_context_set_port (GdictClientContext *context,
+ gint port)
+{
+ g_return_if_fail (GDICT_IS_CLIENT_CONTEXT (context));
+
+ g_object_set (G_OBJECT (context),
+ "port", (port != -1 ? port : GDICT_DEFAULT_PORT),
+ NULL);
+}
+
+/**
+ * gdict_client_context_get_port:
+ * @context: a #GdictClientContext
+ *
+ * Gets the port of the dictionary server used by @context.
+ *
+ * Return value: the number of the port.
+ */
+guint
+gdict_client_context_get_port (GdictClientContext *context)
+{
+ guint port;
+
+ g_return_val_if_fail (GDICT_IS_CLIENT_CONTEXT (context), -1);
+
+ g_object_get (G_OBJECT (context), "port", &port, NULL);
+
+ return port;
+}
+
+/**
+ * gdict_client_context_set_client:
+ * @context: a #GdictClientContext
+ * @client: the client name to use, or %NULL
+ *
+ * Sets @client as the client name to be used when advertising ourselves when
+ * a connection the the dictionary server has been established.
+ * If @client is %NULL, the default client name will be used.
+ */
+void
+gdict_client_context_set_client (GdictClientContext *context,
+ const gchar *client)
+{
+ g_return_if_fail (GDICT_IS_CLIENT_CONTEXT (context));
+
+ g_object_set (G_OBJECT (context),
+ "client-name", (client != NULL ? client : GDICT_DEFAULT_CLIENT),
+ NULL);
+}
+
+/**
+ * gdict_client_context_get_client:
+ * @context: a #GdictClientContext
+ *
+ * Gets the client name used by @context. See gdict_client_context_set_client().
+ *
+ * Return value: the client name. The returned string is owned by the
+ * #GdictClientContext object and should never be modified or freed.
+ */
+const gchar *
+gdict_client_context_get_client (GdictClientContext *context)
+{
+ gchar *client_name;
+
+ g_return_val_if_fail (GDICT_IS_CLIENT_CONTEXT (context), NULL);
+
+ g_object_get (G_OBJECT (context), "client-name", &client_name, NULL);
+
+ return client_name;
+}
+
+/* creates a new command to be sent to the dictionary server */
+static GdictCommand *
+gdict_command_new (GdictCommandType cmd_type)
+{
+ GdictCommand *retval;
+
+ g_assert (IS_VALID_CMD (cmd_type));
+
+ retval = g_slice_new0 (GdictCommand);
+
+ retval->cmd_type = cmd_type;
+ retval->state = S_START;
+
+ return retval;
+}
+
+static void
+gdict_command_free (GdictCommand *cmd)
+{
+ if (!cmd)
+ return;
+
+ g_free (cmd->cmd_string);
+
+ switch (cmd->cmd_type)
+ {
+ case CMD_CLIENT:
+ case CMD_QUIT:
+ break;
+ case CMD_SHOW_DB:
+ case CMD_SHOW_STRAT:
+ break;
+ case CMD_MATCH:
+ g_free (cmd->database);
+ g_free (cmd->strategy);
+ g_free (cmd->word);
+ break;
+ case CMD_DEFINE:
+ g_free (cmd->database);
+ g_free (cmd->word);
+ break;
+ default:
+ break;
+ }
+
+ if (cmd->buffer)
+ g_string_free (cmd->buffer, TRUE);
+
+ if (cmd->data_destroy)
+ cmd->data_destroy (cmd->data);
+
+ g_slice_free (GdictCommand, cmd);
+}
+
+/* push @command into the head of the commands queue; the command queue is
+ * a FIFO-like pipe: commands go into the head and are retrieved from the
+ * tail.
+ */
+static gboolean
+gdict_client_context_push_command (GdictClientContext *context,
+ GdictCommand *command)
+{
+ GdictClientContextPrivate *priv;
+
+ g_assert (GDICT_IS_CLIENT_CONTEXT (context));
+ g_assert (command != NULL);
+
+ priv = context->priv;
+
+ /* avoid pushing a command twice */
+ if (g_queue_find (priv->commands_queue, command))
+ {
+ g_warning ("gdict_client_context_push_command() called on a command already in queue\n");
+ return FALSE;
+ }
+
+ GDICT_NOTE (DICT, "Pushing command ('%s') into the queue...",
+ dict_command_strings[command->cmd_type]);
+
+ g_queue_push_head (priv->commands_queue, command);
+
+ return TRUE;
+}
+
+static GdictCommand *
+gdict_client_context_pop_command (GdictClientContext *context)
+{
+ GdictClientContextPrivate *priv;
+ GdictCommand *retval;
+
+ g_assert (GDICT_IS_CLIENT_CONTEXT (context));
+
+ priv = context->priv;
+
+ retval = (GdictCommand *) g_queue_pop_tail (priv->commands_queue);
+ if (!retval)
+ return NULL;
+
+ GDICT_NOTE (DICT, "Getting command ('%s') from the queue...",
+ dict_command_strings[retval->cmd_type]);
+
+ return retval;
+}
+
+/* send @command on the wire */
+static gboolean
+gdict_client_context_send_command (GdictClientContext *context,
+ GdictCommand *command,
+ GError **error)
+{
+ GdictClientContextPrivate *priv;
+ GError *write_error;
+ gsize written_bytes;
+ GIOStatus res;
+
+ g_assert (GDICT_IS_CLIENT_CONTEXT (context));
+ g_assert (command != NULL && command->cmd_string != NULL);
+
+ priv = context->priv;
+
+ if (!priv->channel)
+ {
+ GDICT_NOTE (DICT, "No connection established");
+
+ g_set_error (error, GDICT_CLIENT_CONTEXT_ERROR,
+ GDICT_CLIENT_CONTEXT_ERROR_NO_CONNECTION,
+ _("No connection to the dictionary server at '%s:%d'"),
+ priv->hostname,
+ priv->port);
+
+ return FALSE;
+ }
+
+ write_error = NULL;
+ res = g_io_channel_write_chars (priv->channel,
+ command->cmd_string,
+ -1,
+ &written_bytes,
+ &write_error);
+ if (res != G_IO_STATUS_NORMAL)
+ {
+ g_propagate_error (error, write_error);
+
+ return FALSE;
+ }
+
+ /* force flushing of the write buffer */
+ g_io_channel_flush (priv->channel, NULL);
+
+ GDICT_NOTE (DICT, "Wrote %"G_GSIZE_FORMAT" bytes to the channel", written_bytes);
+
+ return TRUE;
+}
+
+/* gdict_client_context_run_command: runs @command inside @context; this
+ * function builds the command string and then passes it to the dictionary
+ * server.
+ */
+static gboolean
+gdict_client_context_run_command (GdictClientContext *context,
+ GdictCommand *command,
+ GError **error)
+{
+ GdictClientContextPrivate *priv;
+ gchar *payload;
+ GError *send_error;
+ gboolean res;
+
+ g_assert (GDICT_IS_CLIENT_CONTEXT (context));
+ g_assert (command != NULL);
+ g_assert (IS_VALID_CMD (command->cmd_type));
+
+ GDICT_NOTE (DICT, "GdictCommand command =\n"
+ "{\n"
+ " .cmd_type = '%02d' ('%s');\n"
+ " .database = '%s';\n"
+ " .strategy = '%s';\n"
+ " .word = '%s';\n"
+ "}\n",
+ command->cmd_type, dict_command_strings[command->cmd_type],
+ command->database ? command->database : "<none>",
+ command->strategy ? command->strategy : "<none>",
+ command->word ? command->word : "<none>");
+
+ priv = context->priv;
+
+ g_assert (priv->command == NULL);
+
+ priv->command = command;
+
+ /* build the command string to be sent to the server */
+ switch (command->cmd_type)
+ {
+ case CMD_CLIENT:
+ payload = g_shell_quote (priv->client_name);
+ command->cmd_string = g_strdup_printf ("%s %s\r\n",
+ dict_command_strings[CMD_CLIENT],
+ payload);
+ g_free (payload);
+ break;
+ case CMD_QUIT:
+ command->cmd_string = g_strdup_printf ("%s\r\n",
+ dict_command_strings[CMD_QUIT]);
+ break;
+ case CMD_SHOW_DB:
+ command->cmd_string = g_strdup_printf ("%s\r\n",
+ dict_command_strings[CMD_SHOW_DB]);
+ break;
+ case CMD_SHOW_STRAT:
+ command->cmd_string = g_strdup_printf ("%s\r\n",
+ dict_command_strings[CMD_SHOW_STRAT]);
+ break;
+ case CMD_MATCH:
+ g_assert (command->word);
+ payload = g_shell_quote (command->word);
+ command->cmd_string = g_strdup_printf ("%s %s %s %s\r\n",
+ dict_command_strings[CMD_MATCH],
+ (command->database != NULL ? command->database : "!"),
+ (command->strategy != NULL ? command->strategy : "*"),
+ payload);
+ g_free (payload);
+ break;
+ case CMD_DEFINE:
+ g_assert (command->word);
+ payload = g_shell_quote (command->word);
+ command->cmd_string = g_strdup_printf ("%s %s %s\r\n",
+ dict_command_strings[CMD_DEFINE],
+ (command->database != NULL ? command->database : "!"),
+ payload);
+ g_free (payload);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ g_assert (command->cmd_string);
+
+ GDICT_NOTE (DICT, "Sending command ('%s') to the server",
+ dict_command_strings[command->cmd_type]);
+
+ send_error = NULL;
+ res = gdict_client_context_send_command (context, command, &send_error);
+ if (!res)
+ {
+ g_propagate_error (error, send_error);
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* we use this signal to advertise ourselves to the dictionary server */
+static void
+gdict_client_context_real_connected (GdictClientContext *context)
+{
+ GdictCommand *cmd;
+
+ cmd = gdict_command_new (CMD_CLIENT);
+ cmd->state = S_FINISH;
+
+ /* the CLIENT command should be the first one in our queue, so we place
+ * it above all other commands the user might have issued between the
+ * first and the emission of the "connected" signal, by calling it
+ * directely.
+ */
+ gdict_client_context_run_command (context, cmd, NULL);
+}
+
+static void
+clear_command_queue (GdictClientContext *context)
+{
+ GdictClientContextPrivate *priv = context->priv;
+
+ if (priv->commands_queue)
+ {
+ g_queue_foreach (priv->commands_queue,
+ (GFunc) gdict_command_free,
+ NULL);
+
+ g_queue_free (priv->commands_queue);
+ }
+
+ /* renew */
+ priv->commands_queue = g_queue_new ();
+}
+
+/* force a disconnection from the server */
+static void
+gdict_client_context_force_disconnect (GdictClientContext *context)
+{
+ GdictClientContextPrivate *priv = context->priv;
+
+ if (priv->timeout_id)
+ {
+ g_source_remove (priv->timeout_id);
+ priv->timeout_id = 0;
+ }
+
+ if (priv->source_id)
+ {
+ g_source_remove (priv->source_id);
+ priv->source_id = 0;
+ }
+
+ if (priv->channel)
+ {
+ g_io_channel_shutdown (priv->channel, TRUE, NULL);
+ g_io_channel_unref (priv->channel);
+
+ priv->channel = NULL;
+ }
+
+ if (priv->command)
+ {
+ gdict_command_free (priv->command);
+ priv->command = NULL;
+ }
+
+ clear_command_queue (context);
+}
+
+static void
+gdict_client_context_real_disconnected (GdictClientContext *context)
+{
+ gdict_client_context_force_disconnect (context);
+}
+
+/* clear the lookup data */
+static void
+gdict_client_context_clear_hostinfo (GdictClientContext *context)
+{
+ GdictClientContextPrivate *priv;
+
+ g_assert (GDICT_IS_CLIENT_CONTEXT (context));
+
+ priv = context->priv;
+
+#ifdef ENABLE_IPV6
+ if (!priv->host6info)
+ return;
+#endif
+
+ if (!priv->hostinfo)
+ return;
+
+#ifdef ENABLE_IPV6
+ freeaddrinfo (priv->host6info);
+#endif
+ priv->hostinfo = NULL;
+}
+
+/* gdict_client_context_lookup_server: perform an hostname lookup in order to
+ * connect to the dictionary server
+ */
+static gboolean
+gdict_client_context_lookup_server (GdictClientContext *context,
+ GError **error)
+{
+ GdictClientContextPrivate *priv;
+ gboolean is_expired = FALSE;
+ time_t now;
+
+ g_assert (GDICT_IS_CLIENT_CONTEXT (context));
+
+ priv = context->priv;
+
+ /* we need the hostname, at this point */
+ g_assert (priv->hostname != NULL);
+
+ time (&now);
+ if (now >= (priv->last_lookup + HOSTNAME_LOOKUP_EXPIRE))
+ is_expired = TRUE;
+
+ /* we already have resolved the hostname */
+#ifdef ENABLE_IPV6
+ if (priv->host6info && !is_expired)
+ return TRUE;
+#endif
+
+ if (priv->hostinfo && !is_expired)
+ return TRUE;
+
+ /* clear any previously acquired lookup data */
+ gdict_client_context_clear_hostinfo (context);
+
+ GDICT_NOTE (DICT, "Looking up hostname '%s'", priv->hostname);
+
+#ifdef ENABLE_IPV6
+ if (_gdict_has_ipv6 ())
+ {
+ struct addrinfo hints, *res;
+
+ GDICT_NOTE (DICT, "Hostname '%s' look-up (using IPv6)", priv->hostname);
+
+ memset (&hints, 0, sizeof (hints));
+ hints.ai_socktype = SOCK_STREAM;
+
+ if (getaddrinfo (priv->hostname, NULL, &hints, &(priv->host6info)) == 0)
+ {
+ for (res = priv->host6info; res; res = res->ai_next)
+ if (res->ai_family == AF_INET6 || res->ai_family == AF_INET)
+ break;
+
+ if (!res)
+ {
+ g_set_error (error, GDICT_CLIENT_CONTEXT_ERROR,
+ GDICT_CLIENT_CONTEXT_ERROR_LOOKUP,
+ _("Lookup failed for hostname '%s': no suitable resources found"),
+ priv->hostname);
+
+ return FALSE;
+ }
+ else
+ {
+ if (res->ai_family == AF_INET6)
+ memcpy (&((struct sockaddr_in6 *) &priv->sockaddr)->sin6_addr,
+ &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr,
+ sizeof (struct in6_addr));
+
+ if (res->ai_family == AF_INET)
+ memcpy (&((struct sockaddr_in *) &priv->sockaddr)->sin_addr,
+ &((struct sockaddr_in *) res->ai_addr)->sin_addr,
+ sizeof (struct in_addr));
+
+ priv->sockaddr.ss_family = res->ai_family;
+
+ GDICT_NOTE (DICT, "Hostname '%s' found (using IPv6)",
+ priv->hostname);
+
+ priv->last_lookup = time (NULL);
+
+ return TRUE;
+ }
+ }
+ else
+ {
+ g_set_error (error, GDICT_CLIENT_CONTEXT_ERROR,
+ GDICT_CLIENT_CONTEXT_ERROR_LOOKUP,
+ _("Lookup failed for host '%s': %s"),
+ priv->hostname,
+ gai_strerror (errno));
+
+ return FALSE;
+ }
+ }
+ else
+ {
+#endif /* ENABLE_IPV6 */
+ /* if we don't support IPv6, fallback to usual IPv4 lookup */
+
+ GDICT_NOTE (DICT, "Hostname '%s' look-up (using IPv4)", priv->hostname);
+
+ ((struct sockaddr_in *) &priv->sockaddr)->sin_family = AF_INET;
+
+ priv->hostinfo = gethostbyname (priv->hostname);
+ if (priv->hostinfo)
+ {
+ memcpy (&((struct sockaddr_in *) &(priv->sockaddr))->sin_addr,
+ priv->hostinfo->h_addr,
+ priv->hostinfo->h_length);
+
+ GDICT_NOTE (DICT, "Hostname '%s' found (using IPv4)",
+ priv->hostname);
+
+ priv->last_lookup = time (NULL);
+
+ return TRUE;
+ }
+ else
+ {
+ g_set_error (error, GDICT_CLIENT_CONTEXT_ERROR,
+ GDICT_CLIENT_CONTEXT_ERROR_LOOKUP,
+ _("Lookup failed for host '%s': host not found"),
+ priv->hostname);
+
+ return FALSE;
+ }
+#ifdef ENABLE_IPV6
+ }
+#endif
+
+ g_assert_not_reached ();
+
+ return FALSE;
+}
+
+/* gdict_client_context_parse_line: parses a line from the dictionary server
+ * this is the core of the RFC2229 protocol implementation, here's where
+ * the magic happens.
+ */
+static gboolean
+gdict_client_context_parse_line (GdictClientContext *context,
+ const gchar *buffer)
+{
+ GdictClientContextPrivate *priv;
+ GError *server_error = NULL;
+
+ g_assert (GDICT_IS_CLIENT_CONTEXT (context));
+ g_assert (buffer != NULL);
+
+ priv = context->priv;
+
+ GDICT_NOTE (DICT, "parse buffer: '%s'", buffer);
+
+ /* connection is a special case: we don't have a command, so we just
+ * make sure that the server replied with the correct code. WARNING:
+ * the server might be shutting down or not available, so we must
+ * take into account those responses too!
+ */
+ if (!priv->command)
+ {
+ if (priv->status_code == GDICT_STATUS_CONNECT)
+ {
+ /* the server accepts our connection */
+ g_signal_emit (context, gdict_client_context_signals[CONNECTED], 0);
+
+ return TRUE;
+ }
+ else if ((priv->status_code == GDICT_STATUS_SERVER_DOWN) ||
+ (priv->status_code == GDICT_STATUS_SHUTDOWN))
+ {
+ /* the server is shutting down or is not available */
+ g_set_error (&server_error, GDICT_CLIENT_CONTEXT_ERROR,
+ GDICT_CLIENT_CONTEXT_ERROR_SERVER_DOWN,
+ _("Unable to connect to the dictionary server "
+ "at '%s:%d'. The server replied with "
+ "code %d (server down)"),
+ priv->hostname,
+ priv->port,
+ priv->status_code);
+
+ g_signal_emit_by_name (context, "error", server_error);
+
+ g_error_free (server_error);
+
+ return TRUE;
+ }
+ else
+ {
+ GError *parse_error = NULL;
+
+ g_set_error (&parse_error, GDICT_CONTEXT_ERROR,
+ GDICT_CONTEXT_ERROR_PARSE,
+ _("Unable to parse the dictionary server reply\n: '%s'"),
+ buffer);
+
+ g_signal_emit_by_name (context, "error", parse_error);
+
+ g_error_free (parse_error);
+
+ return FALSE;
+ }
+ }
+
+ /* disconnection is another special case: the server replies with code
+ * 221, and closes the connection; we emit the "disconnected" signal
+ * and close the connection on our side.
+ */
+ if (priv->status_code == GDICT_STATUS_QUIT)
+ {
+ g_signal_emit (context, gdict_client_context_signals[DISCONNECTED], 0);
+
+ return TRUE;
+ }
+
+ /* here we catch all the errors codes that the server might give us */
+ server_error = NULL;
+ switch (priv->status_code)
+ {
+ case GDICT_STATUS_NO_MATCH:
+ g_set_error (&server_error, GDICT_CONTEXT_ERROR,
+ GDICT_CONTEXT_ERROR_NO_MATCH,
+ _("No definitions found for '%s'"),
+ priv->command->word);
+
+ GDICT_NOTE (DICT, "No match: %s", server_error->message);
+
+ g_signal_emit_by_name (context, "error", server_error);
+
+ g_error_free (server_error);
+ server_error = NULL;
+
+ priv->command->state = S_FINISH;
+ break;
+ case GDICT_STATUS_BAD_DATABASE:
+ g_set_error (&server_error, GDICT_CONTEXT_ERROR,
+ GDICT_CONTEXT_ERROR_INVALID_DATABASE,
+ _("Invalid database '%s'"),
+ priv->command->database);
+
+ GDICT_NOTE (DICT, "Bad DB: %s", server_error->message);
+
+ g_signal_emit_by_name (context, "error", server_error);
+
+ g_error_free (server_error);
+ server_error = NULL;
+
+ priv->command->state = S_FINISH;
+ break;
+ case GDICT_STATUS_BAD_STRATEGY:
+ g_set_error (&server_error, GDICT_CONTEXT_ERROR,
+ GDICT_CONTEXT_ERROR_INVALID_STRATEGY,
+ _("Invalid strategy '%s'"),
+ priv->command->strategy);
+
+ GDICT_NOTE (DICT, "Bad strategy: %s", server_error->message);
+
+ g_signal_emit_by_name (context, "error", server_error);
+
+ g_error_free (server_error);
+ server_error = NULL;
+
+ priv->command->state = S_FINISH;
+ break;
+ case GDICT_STATUS_BAD_COMMAND:
+ g_set_error (&server_error, GDICT_CONTEXT_ERROR,
+ GDICT_CONTEXT_ERROR_INVALID_COMMAND,
+ _("Bad command '%s'"),
+ dict_command_strings[priv->command->cmd_type]);
+
+ GDICT_NOTE (DICT, "Bad command: %s", server_error->message);
+
+ g_signal_emit_by_name (context, "error", server_error);
+
+ g_error_free (server_error);
+ server_error = NULL;
+
+ priv->command->state = S_FINISH;
+ break;
+ case GDICT_STATUS_BAD_PARAMETERS:
+ g_set_error (&server_error, GDICT_CONTEXT_ERROR,
+ GDICT_CONTEXT_ERROR_INVALID_COMMAND,
+ _("Bad parameters for command '%s'"),
+ dict_command_strings[priv->command->cmd_type]);
+
+ GDICT_NOTE (DICT, "Bad params: %s", server_error->message);
+
+ g_signal_emit_by_name (context, "error", server_error);
+
+ g_error_free (server_error);
+ server_error = NULL;
+
+ priv->command->state = S_FINISH;
+ break;
+ case GDICT_STATUS_NO_DATABASES_PRESENT:
+ g_set_error (&server_error, GDICT_CONTEXT_ERROR,
+ GDICT_CONTEXT_ERROR_NO_DATABASES,
+ _("No databases found on dictionary server at '%s'"),
+ priv->hostname);
+
+ GDICT_NOTE (DICT, "No DB: %s", server_error->message);
+
+ g_signal_emit_by_name (context, "error", server_error);
+
+ g_error_free (server_error);
+ server_error = NULL;
+
+ priv->command->state = S_FINISH;
+ break;
+ case GDICT_STATUS_NO_STRATEGIES_PRESENT:
+ g_set_error (&server_error, GDICT_CONTEXT_ERROR,
+ GDICT_CONTEXT_ERROR_NO_STRATEGIES,
+ _("No strategies found on dictionary server at '%s'"),
+ priv->hostname);
+
+ GDICT_NOTE (DICT, "No strategies: %s", server_error->message);
+
+ g_signal_emit_by_name (context, "error", server_error);
+
+ g_error_free (server_error);
+ server_error = NULL;
+
+ priv->command->state = S_FINISH;
+ break;
+ default:
+ GDICT_NOTE (DICT, "non-error code: %d", priv->status_code);
+ break;
+ }
+
+ /* server replied with 'ok' or the command has reached its FINISH state,
+ * so now we are clear for destroying the current command and check if
+ * there are other commands on the queue, and run them.
+ */
+ if ((priv->status_code == GDICT_STATUS_OK) ||
+ (priv->command->state == S_FINISH))
+ {
+ GdictCommand *new_command;
+ GError *run_error;
+ GdictCommandType last_cmd;
+
+ last_cmd = priv->command->cmd_type;
+
+ gdict_command_free (priv->command);
+ priv->command = NULL;
+
+ /* notify the end of a command - ignore CLIENT and QUIT commands, as
+ * we issue them ourselves
+ */
+ if ((last_cmd != CMD_CLIENT) && (last_cmd != CMD_QUIT))
+ g_signal_emit_by_name (context, "lookup-end");
+
+ /* pop the next command from the queue */
+ new_command = gdict_client_context_pop_command (context);
+ if (!new_command)
+ {
+ /* if the queue is empty, quit */
+ gdict_client_context_disconnect (context);
+ new_command = gdict_client_context_pop_command (context);
+ }
+
+ run_error = NULL;
+ gdict_client_context_run_command (context, new_command, &run_error);
+ if (run_error)
+ {
+ g_signal_emit_by_name (context, "error", run_error);
+
+ g_error_free (run_error);
+ }
+
+ return TRUE;
+ }
+
+ GDICT_NOTE (DICT, "check command %d ('%s')[state:%d]",
+ priv->command->cmd_type,
+ dict_command_strings[priv->command->cmd_type],
+ priv->command->state);
+
+ /* check command type */
+ switch (priv->command->cmd_type)
+ {
+ case CMD_CLIENT:
+ case CMD_QUIT:
+ break;
+ case CMD_SHOW_DB:
+ if (priv->status_code == GDICT_STATUS_N_DATABASES_PRESENT)
+ {
+ gchar *p;
+
+ priv->command->state = S_DATA;
+
+ p = g_utf8_strchr (buffer, -1, ' ');
+ if (p)
+ p = g_utf8_next_char (p);
+
+ GDICT_NOTE (DICT, "server replied: %d databases found", atoi (p));
+
+ g_signal_emit_by_name (context, "lookup-start");
+ }
+ else if (0 == strcmp (buffer, "."))
+ priv->command->state = S_FINISH;
+ else
+ {
+ GdictDatabase *db;
+ gchar *name, *full, *p;
+
+ g_assert (priv->command->state == S_DATA);
+
+ /* first token: database name;
+ * second token: database description;
+ */
+ name = (gchar *) buffer;
+ if (!name)
+ break;
+
+ p = g_utf8_strchr (name, -1, ' ');
+ if (p)
+ *p = '\0';
+
+ full = g_utf8_next_char (p);
+
+ if (full[0] == '\"')
+ full = g_utf8_next_char (full);
+
+ p = g_utf8_strchr (full, -1, '\"');
+ if (p)
+ *p = '\0';
+
+ db = _gdict_database_new (name);
+ db->full_name = g_strdup (full);
+
+ g_signal_emit_by_name (context, "database-found", db);
+
+ gdict_database_unref (db);
+ }
+ break;
+ case CMD_SHOW_STRAT:
+ if (priv->status_code == GDICT_STATUS_N_STRATEGIES_PRESENT)
+ {
+ gchar *p;
+
+ priv->command->state = S_DATA;
+
+ p = g_utf8_strchr (buffer, -1, ' ');
+ if (p)
+ p = g_utf8_next_char (p);
+
+ GDICT_NOTE (DICT, "server replied: %d strategies found", atoi (p));
+
+ g_signal_emit_by_name (context, "lookup-start");
+ }
+ else if (0 == strcmp (buffer, "."))
+ priv->command->state = S_FINISH;
+ else
+ {
+ GdictStrategy *strat;
+ gchar *name, *desc, *p;
+
+ g_assert (priv->command->state == S_DATA);
+
+ name = (gchar *) buffer;
+ if (!name)
+ break;
+
+ p = g_utf8_strchr (name, -1, ' ');
+ if (p)
+ *p = '\0';
+
+ desc = g_utf8_next_char (p);
+
+ if (desc[0] == '\"')
+ desc = g_utf8_next_char (desc);
+
+ p = g_utf8_strchr (desc, -1, '\"');
+ if (p)
+ *p = '\0';
+
+ strat = _gdict_strategy_new (name);
+ strat->description = g_strdup (desc);
+
+ g_signal_emit_by_name (context, "strategy-found", strat);
+
+ gdict_strategy_unref (strat);
+ }
+ break;
+ case CMD_DEFINE:
+ if (priv->status_code == GDICT_STATUS_N_DEFINITIONS_RETRIEVED)
+ {
+ GdictDefinition *def;
+ gchar *p;
+
+ priv->command->state = S_STATUS;
+
+ p = g_utf8_strchr (buffer, -1, ' ');
+ if (p)
+ p = g_utf8_next_char (p);
+
+ GDICT_NOTE (DICT, "server replied: %d definitions found", atoi (p));
+
+ def = _gdict_definition_new (atoi (p));
+
+ priv->command->data = def;
+ priv->command->data_destroy = (GDestroyNotify) gdict_definition_unref;
+
+ g_signal_emit_by_name (context, "lookup-start");
+ }
+ else if (priv->status_code == GDICT_STATUS_WORD_DB_NAME)
+ {
+ GdictDefinition *def;
+ gchar *word, *db_name, *db_full, *p;
+
+ word = (gchar *) buffer;
+
+ /* skip the status code */
+ word = g_utf8_strchr (word, -1, ' ');
+ word = g_utf8_next_char (word);
+
+ if (word[0] == '\"')
+ word = g_utf8_next_char (word);
+
+ p = g_utf8_strchr (word, -1, '\"');
+ if (p)
+ *p = '\0';
+
+ p = g_utf8_next_char (p);
+
+ /* the database name is not protected by "" */
+ db_name = g_utf8_next_char (p);
+ if (!db_name)
+ break;
+
+ p = g_utf8_strchr (db_name, -1, ' ');
+ if (p)
+ *p = '\0';
+
+ p = g_utf8_next_char (p);
+
+ db_full = g_utf8_next_char (p);
+ if (!db_full)
+ break;
+
+ if (db_full[0] == '\"')
+ db_full = g_utf8_next_char (db_full);
+
+ p = g_utf8_strchr (db_full, -1, '\"');
+ if (p)
+ *p = '\0';
+
+ def = (GdictDefinition *) priv->command->data;
+
+ GDICT_NOTE (DICT, "{ word = '%s', db_name = '%s', db_full = '%s' }",
+ word,
+ db_name,
+ db_full);
+
+ def->word = g_strdup (word);
+ def->database_name = g_strdup (db_name);
+ def->database_full = g_strdup (db_full);
+ def->definition = NULL;
+
+ priv->command->state = S_DATA;
+ }
+ else if (strcmp (buffer, ".") == 0)
+ {
+ GdictDefinition *def;
+ gint num;
+
+ g_assert (priv->command->state == S_DATA);
+
+ def = (GdictDefinition *) priv->command->data;
+ if (!def)
+ break;
+
+ def->definition = g_string_free (priv->command->buffer, FALSE);
+
+ /* store the numer of definitions */
+ num = def->total;
+
+ g_signal_emit_by_name (context, "definition-found", def);
+
+ gdict_definition_unref (def);
+
+ priv->command->buffer = NULL;
+ priv->command->data = _gdict_definition_new (num);
+
+ priv->command->state = S_STATUS;
+ }
+ else
+ {
+ g_assert (priv->command->state == S_DATA);
+
+ if (!priv->command->buffer)
+ priv->command->buffer = g_string_new (NULL);
+
+ GDICT_NOTE (DICT, "appending to buffer:\n %s", buffer);
+
+ /* TODO - collapse '..' to '.' */
+ g_string_append_printf (priv->command->buffer, "%s\n", buffer);
+ }
+ break;
+ case CMD_MATCH:
+ if (priv->status_code == GDICT_STATUS_N_MATCHES_FOUND)
+ {
+ gchar *p;
+
+ priv->command->state = S_DATA;
+
+ p = g_utf8_strchr (buffer, -1, ' ');
+ if (p)
+ p = g_utf8_next_char (p);
+
+ GDICT_NOTE (DICT, "server replied: %d matches found", atoi (p));
+
+ g_signal_emit_by_name (context, "lookup-start");
+ }
+ else if (0 == strcmp (buffer, "."))
+ priv->command->state = S_FINISH;
+ else
+ {
+ GdictMatch *match;
+ gchar *word, *db_name, *p;
+
+ g_assert (priv->command->state == S_DATA);
+
+ db_name = (gchar *) buffer;
+ if (!db_name)
+ break;
+
+ p = g_utf8_strchr (db_name, -1, ' ');
+ if (p)
+ *p = '\0';
+
+ word = g_utf8_next_char (p);
+
+ if (word[0] == '\"')
+ word = g_utf8_next_char (word);
+
+ p = g_utf8_strchr (word, -1, '\"');
+ if (p)
+ *p = '\0';
+
+ match = _gdict_match_new (word);
+ match->database = g_strdup (db_name);
+
+ g_signal_emit_by_name (context, "match-found", match);
+
+ gdict_match_unref (match);
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ return TRUE;
+}
+
+/* retrieve the status code from the server response line */
+static gint
+get_status_code (const gchar *line,
+ gint old_status)
+{
+ gchar *status;
+ gint possible_status, retval;
+
+ if (strlen (line) < 3)
+ return 0;
+
+ if (!g_unichar_isdigit (line[0]) ||
+ !g_unichar_isdigit (line[1]) ||
+ !g_unichar_isdigit (line[2]))
+ return 0;
+
+ if (!g_unichar_isspace (line[3]))
+ return 0;
+
+ status = g_strndup (line, 3);
+ possible_status = atoi (status);
+ g_free (status);
+
+ /* status whitelisting: sometimes, a database *cough* moby-thes *cough*
+ * might return a number as first word; we do a small check here for
+ * invalid status codes based on the previously set status; we don't check
+ * the whole line, as we need only to be sure that the status code is
+ * consistent with what we expect.
+ */
+ switch (old_status)
+ {
+ case GDICT_STATUS_WORD_DB_NAME:
+ case GDICT_STATUS_N_MATCHES_FOUND:
+ if (possible_status == GDICT_STATUS_OK)
+ retval = possible_status;
+ else
+ retval = 0;
+ break;
+ case GDICT_STATUS_N_DEFINITIONS_RETRIEVED:
+ if (possible_status == GDICT_STATUS_WORD_DB_NAME)
+ retval = possible_status;
+ else
+ retval = 0;
+ break;
+ default:
+ retval = possible_status;
+ break;
+ }
+
+ return retval;
+}
+
+static gboolean
+gdict_client_context_io_watch_cb (GIOChannel *channel,
+ GIOCondition condition,
+ GdictClientContext *context)
+{
+ GdictClientContextPrivate *priv;
+
+ g_assert (GDICT_IS_CLIENT_CONTEXT (context));
+ priv = context->priv;
+
+ /* since this is an asynchronous channel, we might end up here
+ * even though the channel has been shut down.
+ */
+ if (!priv->channel)
+ {
+ g_warning ("No channel available\n");
+
+ return FALSE;
+ }
+
+ if (priv->is_connecting)
+ {
+ priv->is_connecting = FALSE;
+
+ if (priv->timeout_id)
+ {
+ g_source_remove (priv->timeout_id);
+ priv->timeout_id = 0;
+ }
+ }
+
+ if (condition & G_IO_ERR)
+ {
+ GError *err = NULL;
+
+ g_set_error (&err, GDICT_CLIENT_CONTEXT_ERROR,
+ GDICT_CLIENT_CONTEXT_ERROR_SOCKET,
+ _("Connection failed to the dictionary server at %s:%d"),
+ priv->hostname,
+ priv->port);
+
+ g_signal_emit_by_name (context, "error", err);
+
+ g_error_free (err);
+
+ return FALSE;
+ }
+
+ while (1)
+ {
+ GIOStatus res;
+ guint status_code;
+ GError *read_err;
+ gsize len, term;
+ gchar *line;
+ gboolean parse_res;
+
+ /* we might sever the connection while still inside the read loop,
+ * so we must check the state of the channel before actually doing
+ * the line reading, otherwise we'll end up with death, destruction
+ * and chaos on all earth. oh, and an assertion failed inside
+ * g_io_channel_read_line().
+ */
+ if (!priv->channel)
+ break;
+
+ read_err = NULL;
+ res = g_io_channel_read_line (priv->channel, &line, &len, &term, &read_err);
+ if (res == G_IO_STATUS_ERROR)
+ {
+ if (read_err)
+ {
+ GError *err = NULL;
+
+ g_set_error (&err, GDICT_CLIENT_CONTEXT_ERROR,
+ GDICT_CLIENT_CONTEXT_ERROR_SOCKET,
+ _("Error while reading reply from server:\n%s"),
+ read_err->message);
+
+ g_signal_emit_by_name (context, "error", err);
+
+ g_error_free (err);
+ g_error_free (read_err);
+ }
+
+ gdict_client_context_force_disconnect (context);
+
+ return FALSE;
+ }
+
+ if (len == 0)
+ break;
+
+ /* truncate the line terminator before parsing */
+ line[term] = '\0';
+
+ status_code = get_status_code (line, priv->status_code);
+ if ((status_code == 0) || (GDICT_IS_VALID_STATUS_CODE (status_code)))
+ {
+ priv->status_code = status_code;
+
+ GDICT_NOTE (DICT, "new status = '%d'", priv->status_code);
+ }
+ else
+ priv->status_code = GDICT_STATUS_INVALID;
+
+ /* notify changes only for valid status codes */
+ if (priv->status_code != GDICT_STATUS_INVALID)
+ g_object_notify (G_OBJECT (context), "status");
+
+ parse_res = gdict_client_context_parse_line (context, line);
+ if (!parse_res)
+ {
+ g_free (line);
+
+ g_warning ("Parsing failed");
+
+ gdict_client_context_force_disconnect (context);
+
+ return FALSE;
+ }
+
+ g_free (line);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+check_for_connection (gpointer data)
+{
+ GdictClientContext *context = data;
+
+#if 0
+ g_debug (G_STRLOC ": checking for connection (is connecting:%s)",
+ context->priv->is_connecting ? "true" : "false");
+#endif
+
+ if (context == NULL)
+ return FALSE;
+
+ if (context->priv->is_connecting)
+ {
+ GError *err = NULL;
+
+ GDICT_NOTE (DICT, "Forcing a disconnection due to timeout");
+
+ g_set_error (&err, GDICT_CLIENT_CONTEXT_ERROR,
+ GDICT_CLIENT_CONTEXT_ERROR_SOCKET,
+ _("Connection timeout for the dictionary server at '%s:%d'"),
+ context->priv->hostname,
+ context->priv->port);
+
+ g_signal_emit_by_name (context, "error", err);
+
+ g_error_free (err);
+
+ gdict_client_context_force_disconnect (context);
+ }
+
+ /* this is a one-off operation */
+ return FALSE;
+}
+
+static gboolean
+gdict_client_context_connect (GdictClientContext *context,
+ GError **error)
+{
+ GdictClientContextPrivate *priv;
+ GError *lookup_error, *flags_error;
+ gboolean res;
+ gint sock_fd, sock_res;
+ gsize addrlen;
+ GIOFlags flags;
+
+ g_return_val_if_fail (GDICT_IS_CLIENT_CONTEXT (context), FALSE);
+
+ priv = context->priv;
+
+ if (!priv->hostname)
+ {
+ g_set_error (error, GDICT_CLIENT_CONTEXT_ERROR,
+ GDICT_CLIENT_CONTEXT_ERROR_LOOKUP,
+ _("No hostname defined for the dictionary server"));
+
+ return FALSE;
+ }
+
+ /* forgive the absence of a port */
+ if (!priv->port)
+ priv->port = GDICT_DEFAULT_PORT;
+
+ priv->is_connecting = TRUE;
+
+ lookup_error = NULL;
+ res = gdict_client_context_lookup_server (context, &lookup_error);
+ if (!res)
+ {
+ g_propagate_error (error, lookup_error);
+
+ return FALSE;
+ }
+
+#ifdef ENABLE_IPV6
+ if (priv->sockaddr.ss_family == AF_INET6)
+ ((struct sockaddr_in6 *) &priv->sockaddr)->sin6_port = g_htons (priv->port);
+ else
+#endif
+ ((struct sockaddr_in *) &priv->sockaddr)->sin_port = g_htons (priv->port);
+
+
+#ifdef ENABLE_IPV6
+ if (priv->sockaddr.ss_family == AF_INET6)
+ {
+ sock_fd = socket (AF_INET6, SOCK_STREAM, 0);
+ if (sock_fd < 0)
+ {
+ g_set_error (error, GDICT_CLIENT_CONTEXT_ERROR,
+ GDICT_CLIENT_CONTEXT_ERROR_SOCKET,
+ _("Unable to create socket"));
+
+ return FALSE;
+ }
+
+ addrlen = sizeof (struct sockaddr_in6);
+ }
+ else
+ {
+#endif /* ENABLE_IPV6 */
+ sock_fd = socket (AF_INET, SOCK_STREAM, 0);
+ if (sock_fd < 0)
+ {
+ g_set_error (error, GDICT_CLIENT_CONTEXT_ERROR,
+ GDICT_CLIENT_CONTEXT_ERROR_SOCKET,
+ _("Unable to create socket"));
+
+ return FALSE;
+ }
+
+ addrlen = sizeof (struct sockaddr_in);
+#ifdef ENABLE_IPV6
+ }
+#endif
+
+ priv->channel = g_io_channel_unix_new (sock_fd);
+
+ /* RFC2229 mandates the usage of UTF-8, so we force this encoding */
+ g_io_channel_set_encoding (priv->channel, "UTF-8", NULL);
+
+ g_io_channel_set_line_term (priv->channel, "\r\n", 2);
+
+ /* make sure that the channel is non-blocking */
+ flags = g_io_channel_get_flags (priv->channel);
+ flags |= G_IO_FLAG_NONBLOCK;
+ flags_error = NULL;
+ g_io_channel_set_flags (priv->channel, flags, &flags_error);
+ if (flags_error)
+ {
+ g_set_error (error, GDICT_CLIENT_CONTEXT_ERROR,
+ GDICT_CLIENT_CONTEXT_ERROR_SOCKET,
+ _("Unable to set the channel as non-blocking: %s"),
+ flags_error->message);
+
+ g_error_free (flags_error);
+ g_io_channel_unref (priv->channel);
+
+ return FALSE;
+ }
+
+ /* let the magic begin */
+ sock_res = connect (sock_fd, (struct sockaddr *) &priv->sockaddr, addrlen);
+ if ((sock_res != 0) && (errno != EINPROGRESS))
+ {
+ g_set_error (error, GDICT_CLIENT_CONTEXT_ERROR,
+ GDICT_CLIENT_CONTEXT_ERROR_SOCKET,
+ _("Unable to connect to the dictionary server at '%s:%d'"),
+ priv->hostname,
+ priv->port);
+
+ return FALSE;
+ }
+
+ priv->timeout_id = g_timeout_add (CONNECTION_TIMEOUT * 1000,
+ check_for_connection,
+ context);
+
+ /* XXX - remember that g_io_add_watch() increases the reference count
+ * of the GIOChannel we are using.
+ */
+ priv->source_id = g_io_add_watch (priv->channel,
+ (G_IO_IN | G_IO_ERR),
+ (GIOFunc) gdict_client_context_io_watch_cb,
+ context);
+
+ return TRUE;
+}
+
+static void
+gdict_client_context_disconnect (GdictClientContext *context)
+{
+ GdictCommand *cmd;
+
+ g_return_if_fail (GDICT_IS_CLIENT_CONTEXT (context));
+
+ /* instead of just breaking the connection to the server, we push
+ * a QUIT command on the queue, and wait for every other scheduled
+ * command to perform; this allows the creation of a batch of
+ * commands.
+ */
+ cmd = gdict_command_new (CMD_QUIT);
+ cmd->state = S_FINISH;
+
+ gdict_client_context_push_command (context, cmd);
+}
+
+static gboolean
+gdict_client_context_is_connected (GdictClientContext *context)
+{
+ g_assert (GDICT_IS_CLIENT_CONTEXT (context));
+
+ /* we are in the middle of a connection attempt */
+ if (context->priv->is_connecting)
+ return TRUE;
+
+ return (context->priv->channel != NULL && context->priv->source_id != 0);
+}
+
+static gboolean
+gdict_client_context_get_databases (GdictContext *context,
+ GError **error)
+{
+ GdictClientContext *client_ctx;
+ GdictCommand *cmd;
+
+ g_return_val_if_fail (GDICT_IS_CLIENT_CONTEXT (context), FALSE);
+
+ client_ctx = GDICT_CLIENT_CONTEXT (context);
+
+ if (!gdict_client_context_is_connected (client_ctx))
+ {
+ GError *connect_error = NULL;
+
+ gdict_client_context_connect (client_ctx, &connect_error);
+ if (connect_error)
+ {
+ g_propagate_error (error, connect_error);
+
+ return FALSE;
+ }
+ }
+
+ cmd = gdict_command_new (CMD_SHOW_DB);
+
+ return gdict_client_context_push_command (client_ctx, cmd);
+}
+
+static gboolean
+gdict_client_context_get_strategies (GdictContext *context,
+ GError **error)
+{
+ GdictClientContext *client_ctx;
+ GdictCommand *cmd;
+
+ g_return_val_if_fail (GDICT_IS_CLIENT_CONTEXT (context), FALSE);
+
+ client_ctx = GDICT_CLIENT_CONTEXT (context);
+
+ if (!gdict_client_context_is_connected (client_ctx))
+ {
+ GError *connect_error = NULL;
+
+ gdict_client_context_connect (client_ctx, &connect_error);
+ if (connect_error)
+ {
+ g_propagate_error (error, connect_error);
+
+ return FALSE;
+ }
+ }
+
+ cmd = gdict_command_new (CMD_SHOW_STRAT);
+
+ return gdict_client_context_push_command (client_ctx, cmd);
+}
+
+static gboolean
+gdict_client_context_define_word (GdictContext *context,
+ const gchar *database,
+ const gchar *word,
+ GError **error)
+{
+ GdictClientContext *client_ctx;
+ GdictCommand *cmd;
+
+ g_return_val_if_fail (GDICT_IS_CLIENT_CONTEXT (context), FALSE);
+
+ client_ctx = GDICT_CLIENT_CONTEXT (context);
+
+ if (!gdict_client_context_is_connected (client_ctx))
+ {
+ GError *connect_error = NULL;
+
+ gdict_client_context_connect (client_ctx, &connect_error);
+ if (connect_error)
+ {
+ g_propagate_error (error, connect_error);
+
+ return FALSE;
+ }
+ }
+
+ cmd = gdict_command_new (CMD_DEFINE);
+ cmd->database = g_strdup ((database != NULL ? database : GDICT_DEFAULT_DATABASE));
+ cmd->word = g_utf8_normalize (word, -1, G_NORMALIZE_NFC);
+
+ return gdict_client_context_push_command (client_ctx, cmd);
+}
+
+static gboolean
+gdict_client_context_match_word (GdictContext *context,
+ const gchar *database,
+ const gchar *strategy,
+ const gchar *word,
+ GError **error)
+{
+ GdictClientContext *client_ctx;
+ GdictCommand *cmd;
+
+ g_return_val_if_fail (GDICT_IS_CLIENT_CONTEXT (context), FALSE);
+
+ client_ctx = GDICT_CLIENT_CONTEXT (context);
+
+ if (!gdict_client_context_is_connected (client_ctx))
+ {
+ GError *connect_error = NULL;
+
+ gdict_client_context_connect (client_ctx, &connect_error);
+ if (connect_error)
+ {
+ g_propagate_error (error, connect_error);
+
+ return FALSE;
+ }
+ }
+
+ cmd = gdict_command_new (CMD_MATCH);
+ cmd->database = g_strdup ((database != NULL ? database : GDICT_DEFAULT_DATABASE));
+ cmd->strategy = g_strdup ((strategy != NULL ? strategy : GDICT_DEFAULT_STRATEGY));
+ cmd->word = g_utf8_normalize (word, -1, G_NORMALIZE_NFC);
+
+ return gdict_client_context_push_command (client_ctx, cmd);
+}
diff --git a/mate-dictionary/libgdict/gdict-client-context.h b/mate-dictionary/libgdict/gdict-client-context.h
new file mode 100644
index 00000000..5dbe6ed2
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-client-context.h
@@ -0,0 +1,98 @@
+/* gdict-server-context.h - Implementation of a dictionary protocol client context
+ *
+ * Copyright (C) 2005 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+#ifndef __GDICT_CLIENT_CONTEXT_H__
+#define __GDICT_CLIENT_CONTEXT_H__
+
+#include <glib-object.h>
+
+#define GDICT_TYPE_CLIENT_CONTEXT (gdict_client_context_get_type ())
+#define GDICT_CLIENT_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDICT_TYPE_CLIENT_CONTEXT, GdictClientContext))
+#define GDICT_IS_CLIENT_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDICT_TYPE_CLIENT_CONTEXT))
+#define GDICT_CLIENT_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDICT_TYPE_CLIENT_CONTEXT, GdictClientContextClass))
+#define GDICT_CLIENT_CONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GDICT_TYPE_CLIENT_CONTEXT, GdictClientContextClass))
+
+typedef struct _GdictClientContext GdictClientContext;
+typedef struct _GdictClientContextClass GdictClientContextClass;
+typedef struct _GdictClientContextPrivate GdictClientContextPrivate;
+
+#define GDICT_CLIENT_CONTEXT_ERROR (gdict_client_context_error_quark ())
+
+/**
+ * GdictClientContextError:
+ * @GDICT_CLIENT_CONTEXT_ERROR_SOCKET:
+ * @GDICT_CLIENT_CONTEXT_ERROR_LOOKUP:
+ * @GDICT_CLIENT_CONTEXT_ERROR_NO_CONNECTION:
+ * @GDICT_CLIENT_CONTEXT_ERROR_SERVER_DOWN:
+ *
+ * #GdictClientContext error enumeration
+ */
+typedef enum {
+ GDICT_CLIENT_CONTEXT_ERROR_SOCKET,
+ GDICT_CLIENT_CONTEXT_ERROR_LOOKUP,
+ GDICT_CLIENT_CONTEXT_ERROR_NO_CONNECTION,
+ GDICT_CLIENT_CONTEXT_ERROR_SERVER_DOWN
+} GdictClientContextError;
+
+GQuark gdict_client_context_error_quark (void);
+
+struct _GdictClientContext
+{
+ /*< private >*/
+ GObject parent_instance;
+
+ GdictClientContextPrivate *priv;
+};
+
+struct _GdictClientContextClass
+{
+ /*< private >*/
+ GObjectClass parent_class;
+
+ /*< public >*/
+ /* signals monitoring the lifetime of the connection with
+ * the dictionary server
+ */
+ void (*connected) (GdictClientContext *context);
+ void (*disconnected) (GdictClientContext *context);
+
+ /*< private >*/
+ /* padding for future expansion */
+ void (*_gdict_client_1) (void);
+ void (*_gdict_client_2) (void);
+ void (*_gdict_client_3) (void);
+ void (*_gdict_client_4) (void);
+};
+
+GType gdict_client_context_get_type (void) G_GNUC_CONST;
+
+GdictContext * gdict_client_context_new (const gchar *hostname,
+ gint port);
+
+void gdict_client_context_set_hostname (GdictClientContext *context,
+ const gchar *hostname);
+const gchar *gdict_client_context_get_hostname (GdictClientContext *context);
+void gdict_client_context_set_port (GdictClientContext *context,
+ gint port);
+guint gdict_client_context_get_port (GdictClientContext *context);
+void gdict_client_context_set_client (GdictClientContext *context,
+ const gchar *client);
+const gchar *gdict_client_context_get_client (GdictClientContext *context);
+
+#endif /* __GDICT_CLIENT_CONTEXT_H__ */
diff --git a/mate-dictionary/libgdict/gdict-context-private.h b/mate-dictionary/libgdict/gdict-context-private.h
new file mode 100644
index 00000000..24ea463a
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-context-private.h
@@ -0,0 +1,107 @@
+/* gdict-private.h - Private definitions for Gdict
+ *
+ * Copyright (C) 2005 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+#ifndef __GDICT_PRIVATE_H__
+#define __GDICT_PRIVATE_H__
+
+#ifndef GDICT_ENABLE_INTERNALS
+#error "You are trying to access Gdict's internals outside Gdict. The API of these internal functions is not fixed."
+#endif
+
+#include <glib-object.h>
+
+#include "gdict-context.h"
+
+G_BEGIN_DECLS
+
+/* boilerplate code, similar to G_DEFINE_TYPE in spirit, used to define
+ * our boxed types and their ref/unref functions; you still have to
+ * implement your own ref/unref functions!
+ */
+#define GDICT_DEFINE_BOXED_TYPE(TypeName,type_name) \
+\
+static gpointer type_name##_intern_ref (gpointer self) \
+{ \
+ return type_name##_ref ((TypeName *) self); \
+} \
+static void type_name##_intern_unref (gpointer self) \
+{ \
+ type_name##_unref ((TypeName *) self); \
+} \
+\
+GType \
+type_name##_get_type (void) \
+{ \
+ static GType gdict_define_boxed_type = 0; \
+ if (G_UNLIKELY (gdict_define_boxed_type == 0)) \
+ gdict_define_boxed_type = g_boxed_type_register_static (#TypeName, (GBoxedCopyFunc) type_name##_intern_ref, (GBoxedFreeFunc) type_name##_intern_unref); \
+ return gdict_define_boxed_type; \
+}
+
+/* Never, _ever_ access the members of these structures, unless you
+ * know what you are doing.
+ */
+
+struct _GdictDatabase
+{
+ gchar *name;
+ gchar *full_name;
+
+ guint ref_count;
+};
+
+struct _GdictStrategy
+{
+ gchar *name;
+ gchar *description;
+
+ guint ref_count;
+};
+
+struct _GdictMatch
+{
+ gchar *database;
+ gchar *word;
+
+ guint ref_count;
+};
+
+struct _GdictDefinition
+{
+ gint total;
+
+ gchar *word;
+ gchar *database_name;
+ gchar *database_full;
+ gchar *definition;
+
+ guint ref_count;
+};
+
+/* constructors for these objects are private, as the world outside do
+ * not know what they hold, and accessor functions are getters only
+ */
+GdictDatabase * _gdict_database_new (const gchar *name);
+GdictStrategy * _gdict_strategy_new (const gchar *name);
+GdictMatch * _gdict_match_new (const gchar *word);
+GdictDefinition * _gdict_definition_new (gint total);
+
+G_END_DECLS
+
+#endif /* __GDICT_PRIVATE_H__ */
diff --git a/mate-dictionary/libgdict/gdict-context.c b/mate-dictionary/libgdict/gdict-context.c
new file mode 100644
index 00000000..4566bc1a
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-context.c
@@ -0,0 +1,892 @@
+/* gdict-context.c - Abstract class for dictionary contexts
+ *
+ * Copyright (C) 2005 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+/**
+ * SECTION:gdict-context
+ * @short_description: Interface for dictionary transports
+ *
+ * #GdictContext is an interface used to uniformly access dictionary
+ * transport objects. Each implementation of #GdictContext must provide
+ * functions for accessing the list of databases available on a dictionary
+ * source and the available matching strategies; a function for retrieving
+ * all words matching a given string, inside one (or more) of those databases
+ * and using one of those strategies; a function for querying one (or more)
+ * of those databases for a definition of a word.
+ *
+ * Implementations of the #GdictContext interface should query their
+ * dictionary sources asynchronously; methods of the #GdictContext interface
+ * should return immediately, and each time a new database, strategy, match
+ * or definition has been found, a signal should be fired by those
+ * implementations.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib/gi18n-lib.h>
+
+#include "gdict-context.h"
+#include "gdict-enum-types.h"
+#include "gdict-utils.h"
+#include "gdict-marshal.h"
+#include "gdict-context-private.h"
+#include "gdict-private.h"
+
+
+static void gdict_context_class_init (gpointer g_iface);
+
+
+GType
+gdict_context_get_type (void)
+{
+ static GType context_type = 0;
+
+ if (G_UNLIKELY (context_type == 0))
+ {
+ static GTypeInfo context_info =
+ {
+ sizeof (GdictContextIface),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) gdict_context_class_init,
+ };
+
+ context_type = g_type_register_static (G_TYPE_INTERFACE,
+ "GdictContext",
+ &context_info, 0);
+ g_type_interface_add_prerequisite (context_type, G_TYPE_OBJECT);
+ }
+
+ return context_type;
+}
+
+
+static void
+gdict_context_class_init (gpointer g_iface)
+{
+ GType iface_type = G_TYPE_FROM_INTERFACE (g_iface);
+
+ /**
+ * GdictContext::lookup-start
+ * @context: the object which received the signal
+ *
+ * This signal is emitted when a look up operation has been issued using
+ * a #GdictContext. Since every operation using a context is
+ * asynchronous, you can use this signal to know if the request has been
+ * issued or not.
+ *
+ * Since: 1.0
+ */
+ g_signal_new ("lookup-start",
+ iface_type,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdictContextIface, lookup_start),
+ NULL, NULL,
+ gdict_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ /**
+ * GdictContext::lookup-end
+ * @context: the object which received the signal
+ *
+ * This signal is emitted when a look up operation that has been issued
+ * using a #GdictContext has been completed. Since every operation using a
+ * context is asynchronous, you can use this signal to know if the request
+ * has been completed or not.
+ *
+ * Since: 1.0
+ */
+ g_signal_new ("lookup-end",
+ iface_type,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdictContextIface, lookup_end),
+ NULL, NULL,
+ gdict_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ /**
+ * GdictContext::error
+ * @context: the object which received the signal
+ * @error: a #GError
+ *
+ * This signal is emitted when an error happened during an asynchronous
+ * request.
+ *
+ * Since: 1.0
+ */
+ g_signal_new ("error",
+ iface_type,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdictContextIface, error),
+ NULL, NULL,
+ gdict_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+ /**
+ * GdictContext::database-found
+ * @context: the object which received the signal
+ * @database: a #GdictDatabase
+ *
+ * This signal is emitted when a database request has found a database.
+ *
+ * Since: 1.0
+ */
+ g_signal_new ("database-found",
+ iface_type,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdictContextIface, database_found),
+ NULL, NULL,
+ gdict_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1,
+ GDICT_TYPE_DATABASE);
+ /**
+ * GdictContext::strategy-found
+ * @context: the object which received the signal
+ * @strategy: a #GdictStrategy
+ *
+ * This signal is emitted when a strategy request has found a strategy.
+ *
+ * Since: 1.0
+ */
+ g_signal_new ("strategy-found",
+ iface_type,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdictContextIface, strategy_found),
+ NULL, NULL,
+ gdict_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1,
+ GDICT_TYPE_STRATEGY);
+ /**
+ * GdictContext::match-found
+ * @context: the object which received the signal
+ * @match: a #GdictMatch
+ *
+ * This signal is emitted when a match request has found a match.
+ *
+ * Since: 1.0
+ */
+ g_signal_new ("match-found",
+ iface_type,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdictContextIface, match_found),
+ NULL, NULL,
+ gdict_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1,
+ GDICT_TYPE_MATCH);
+ /**
+ * GdictContext::definition-found
+ * @context: the object which received the signal
+ * @definition: a #GdictDefinition
+ *
+ * This signal is emitted when a definition request has found a definition.
+ *
+ * Since: 1.0
+ */
+ g_signal_new ("definition-found",
+ iface_type,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdictContextIface, definition_found),
+ NULL, NULL,
+ gdict_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1,
+ GDICT_TYPE_DEFINITION);
+
+ /**
+ * GdictContext:local-only
+ *
+ * Whether the context uses only local dictionaries or not.
+ *
+ * Since: 1.0
+ */
+ g_object_interface_install_property (g_iface,
+ g_param_spec_boolean ("local-only",
+ _("Local Only"),
+ _("Whether the context uses only local dictionaries or not"),
+ FALSE,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+}
+
+GQuark
+gdict_context_error_quark (void)
+{
+ return g_quark_from_static_string ("gdict-context-error-quark");
+}
+
+/**
+ * gdict_context_set_local_only:
+ * @context: a #GdictContext
+ * @local_only: %TRUE if only local resources will be used
+ *
+ * Sets whether only local resources will be used when querying for databases,
+ * strategies, matches or definitions.
+ *
+ * Since: 1.0
+ */
+void
+gdict_context_set_local_only (GdictContext *context,
+ gboolean local_only)
+{
+ g_return_if_fail (GDICT_IS_CONTEXT (context));
+
+ g_object_set (context, "local-only", &local_only, NULL);
+}
+
+/**
+ * gdict_context_get_local_only:
+ * @context: a #GdictContext
+ *
+ * Gets whether only local resources will be used when querying.
+ *
+ * Return value: %TRUE if only local resources will be used.
+ *
+ * Since: 1.0
+ */
+gboolean
+gdict_context_get_local_only (GdictContext *context)
+{
+ gboolean local_only;
+
+ g_return_val_if_fail (GDICT_IS_CONTEXT (context), FALSE);
+
+ g_object_get (context, "local-only", &local_only, NULL);
+
+ return local_only;
+}
+
+/**
+ * gdict_context_lookup_databases:
+ * @context: a #GdictContext
+ * @error: return location for a #GError, or %NULL
+ *
+ * Query @context for the list of databases available. Each time a
+ * database is found, the "database-found" signal is fired.
+ *
+ * Return value: %TRUE if the query was successfully started.
+ *
+ * Since: 1.0
+ */
+gboolean
+gdict_context_lookup_databases (GdictContext *context,
+ GError **error)
+{
+ g_return_val_if_fail (GDICT_IS_CONTEXT (context), FALSE);
+
+ if (!GDICT_CONTEXT_GET_IFACE (context)->get_databases)
+ {
+ g_warning ("Object `%s' does not implement the get_databases "
+ "virtual function.",
+ g_type_name (G_OBJECT_TYPE (context)));
+
+ return FALSE;
+ }
+
+ return GDICT_CONTEXT_GET_IFACE (context)->get_databases (context, error);
+}
+
+/**
+ * gdict_context_lookup_strategies:
+ * @context: a #GdictContext
+ * @error: return location for a #GError, or %NULL
+ *
+ * Query @context for the list of matching strategies available. Each
+ * time a new strategy is found, the "strategy-found" signal is fired.
+ *
+ * Return value: %TRUE if the query was successfully started.
+ *
+ * Since: 1.0
+ */
+gboolean
+gdict_context_lookup_strategies (GdictContext *context,
+ GError **error)
+{
+ g_return_val_if_fail (GDICT_IS_CONTEXT (context), FALSE);
+
+ if (!GDICT_CONTEXT_GET_IFACE (context)->get_strategies)
+ {
+ g_warning ("Object `%s' does not implement the get_strategies "
+ "virtual function.",
+ g_type_name (G_OBJECT_TYPE (context)));
+
+ return FALSE;
+ }
+
+ return GDICT_CONTEXT_GET_IFACE (context)->get_strategies (context, error);
+}
+
+/**
+ * gdict_context_match_word:
+ * @context: a #GdictContext
+ * @database: a database name to search into, or %NULL for the
+ * default database
+ * @strategy: a strategy name to use for matching, or %NULL for
+ * the default strategy
+ * @word: the word to match
+ * @error: return location for a #GError, or %NULL
+ *
+ * Query @context for a list of word matching @word inside @database,
+ * using @strategy as a matching strategy. Each time a matching word
+ * is found, the "match-found" signal is fired.
+ *
+ * Return value: %TRUE if the query was successfully started.
+ *
+ * Since: 1.0
+ */
+gboolean
+gdict_context_match_word (GdictContext *context,
+ const gchar *database,
+ const gchar *strategy,
+ const gchar *word,
+ GError **error)
+{
+ g_return_val_if_fail (GDICT_IS_CONTEXT (context), FALSE);
+ g_return_val_if_fail (word != NULL, FALSE);
+
+ if (!GDICT_CONTEXT_GET_IFACE (context)->match_word)
+ {
+ g_warning ("Object `%s' does not implement the match_word "
+ "virtual function.",
+ g_type_name (G_OBJECT_TYPE (context)));
+
+ return FALSE;
+ }
+
+ return GDICT_CONTEXT_GET_IFACE (context)->match_word (context,
+ database,
+ strategy,
+ word,
+ error);
+}
+
+/**
+ * gdict_context_define_word:
+ * @context: a #GdictContext
+ * @database: a database name to search into, or %NULL for the
+ * default database
+ * @word: the word to search
+ * @error: return location for a #GError, or %NULL
+ *
+ * Query @context for a list of definitions of @word inside @database. Each
+ * time a new definition is found, the "definition-found" signal is fired.
+ *
+ * Return value: %TRUE if the query was successfully sent.
+ *
+ * Since: 1.0
+ */
+gboolean
+gdict_context_define_word (GdictContext *context,
+ const gchar *database,
+ const gchar *word,
+ GError **error)
+{
+ g_return_val_if_fail (GDICT_IS_CONTEXT (context), FALSE);
+ g_return_val_if_fail (word != NULL, FALSE);
+
+ if (!GDICT_CONTEXT_GET_IFACE (context)->define_word)
+ {
+ g_warning ("Object `%s' does not implement the define_word "
+ "virtual function.",
+ g_type_name (G_OBJECT_TYPE (context)));
+
+ return FALSE;
+ }
+
+ return GDICT_CONTEXT_GET_IFACE (context)->define_word (context,
+ database,
+ word,
+ error);
+}
+
+
+
+/*****************
+ * GdictDatabase *
+ *****************/
+
+GDICT_DEFINE_BOXED_TYPE (GdictDatabase, gdict_database);
+
+GdictDatabase *
+_gdict_database_new (const gchar *name)
+{
+ GdictDatabase *retval;
+
+ g_return_val_if_fail (name != NULL, NULL);
+
+ retval = g_slice_new (GdictDatabase);
+ retval->name = g_strdup (name);
+ retval->full_name = NULL;
+ retval->ref_count = 1;
+
+ return retval;
+}
+
+/**
+ * gdict_database_ref:
+ * @db: a #GdictDatabase
+ *
+ * Increases the reference count of @db by one.
+ *
+ * Return value: @db with its reference count increased
+ *
+ * Since: 1.0
+ */
+GdictDatabase *
+gdict_database_ref (GdictDatabase *db)
+{
+ g_return_val_if_fail (db != NULL, NULL);
+
+ g_assert (db->ref_count != 0);
+
+ db->ref_count += 1;
+
+ return db;
+}
+
+/**
+ * gdict_database_unref:
+ * @db: a #GdictDatabase
+ *
+ * Decreases the reference count of @db by one. If the reference count reaches
+ * zero, @db is destroyed.
+ *
+ * Since: 1.0
+ */
+void
+gdict_database_unref (GdictDatabase *db)
+{
+ g_return_if_fail (db != NULL);
+
+ g_assert (db->ref_count != 0);
+
+ db->ref_count -= 1;
+ if (db->ref_count == 0)
+ {
+ g_free (db->name);
+ g_free (db->full_name);
+
+ g_slice_free (GdictDatabase, db);
+ }
+}
+
+/**
+ * gdict_database_get_name:
+ * @db: a #GdictDatabase
+ *
+ * Gets the short name of the database, to be used with functions like
+ * gdict_context_match_word() or gdict_context_define_word().
+ *
+ * Return value: the short name of the database. The string is owned by
+ * the #GdictDatabase object, and should never be modified or freed.
+ *
+ * Since: 1.0
+ */
+const gchar *
+gdict_database_get_name (GdictDatabase *db)
+{
+ g_return_val_if_fail (db != NULL, NULL);
+
+ return db->name;
+}
+
+/**
+ * gdict_database_get_full_name:
+ * @db: a #GdictDatabase
+ *
+ * Gets the full name of the database, suitable for display.
+ *
+ * Return value: the full name of the database. The string is owned by
+ * the #GdictDatabase object, and should never be modified or freed.
+ *
+ * Since: 1.0
+ */
+const gchar *
+gdict_database_get_full_name (GdictDatabase *db)
+{
+ g_return_val_if_fail (db != NULL, NULL);
+
+ return db->full_name;
+}
+
+
+
+/*****************
+ * GdictStrategy *
+ *****************/
+
+GDICT_DEFINE_BOXED_TYPE (GdictStrategy, gdict_strategy);
+
+GdictStrategy *
+_gdict_strategy_new (const gchar *name)
+{
+ GdictStrategy *strat;
+
+ g_return_val_if_fail (name != NULL, NULL);
+
+ strat = g_slice_new (GdictStrategy);
+ strat->name = g_strdup (name);
+ strat->description = NULL;
+ strat->ref_count = 1;
+
+ return strat;
+}
+
+/**
+ * gdict_strategy_ref:
+ * @strat: a #GdictStrategy
+ *
+ * Increases the reference count of @strat by one.
+ *
+ * Return value: the #GdictStrategy object with its reference count
+ * increased
+ *
+ * Since: 1.0
+ */
+GdictStrategy *
+gdict_strategy_ref (GdictStrategy *strat)
+{
+ g_return_val_if_fail (strat != NULL, NULL);
+
+ g_assert (strat->ref_count != 0);
+
+ strat->ref_count += 1;
+
+ return strat;
+}
+
+/**
+ * gdict_strategy_unref:
+ * @strat: a #GdictStrategy
+ *
+ * Decreases the reference count of @strat by one. If the reference count
+ * reaches zero, the #GdictStrategy object is freed.
+ *
+ * Since: 1.0
+ */
+void
+gdict_strategy_unref (GdictStrategy *strat)
+{
+ g_return_if_fail (strat != NULL);
+
+ g_assert (strat->ref_count != 0);
+
+ strat->ref_count -= 1;
+ if (strat->ref_count == 0)
+ {
+ g_free (strat->name);
+ g_free (strat->description);
+
+ g_slice_free (GdictStrategy, strat);
+ }
+}
+
+/**
+ * gdict_strategy_get_name:
+ * @strat: a #GdictStrategy
+ *
+ * FIXME
+ *
+ * Return value: FIXME
+ *
+ * Since: 1.0
+ */
+const gchar *
+gdict_strategy_get_name (GdictStrategy *strat)
+{
+ g_return_val_if_fail (strat != NULL, NULL);
+
+ return strat->name;
+}
+
+/**
+ * gdict_strategy_get_description:
+ * @strat: a #GdictStrategy
+ *
+ * FIXME
+ *
+ * Return value: FIXME
+ *
+ * Since: 1.0
+ */
+const gchar *
+gdict_strategy_get_description (GdictStrategy *strat)
+{
+ g_return_val_if_fail (strat != NULL, NULL);
+
+ return strat->description;
+}
+
+
+
+/**************
+ * GdictMatch *
+ **************/
+
+GDICT_DEFINE_BOXED_TYPE (GdictMatch, gdict_match);
+
+GdictMatch *
+_gdict_match_new (const gchar *word)
+{
+ GdictMatch *match;
+
+ g_return_val_if_fail (word != NULL, NULL);
+
+ match = g_slice_new (GdictMatch);
+ match->word = g_strdup (word);
+ match->database = NULL;
+ match->ref_count = 1;
+
+ return match;
+}
+
+/**
+ * gdict_match_ref:
+ * @match: a #GdictMatch
+ *
+ * FIXME
+ *
+ * Return value: FIXME
+ *
+ * Since: 1.0
+ */
+GdictMatch *
+gdict_match_ref (GdictMatch *match)
+{
+ g_return_val_if_fail (match != NULL, NULL);
+
+ g_assert (match->ref_count != 0);
+
+ match->ref_count += 1;
+
+ return match;
+}
+
+/**
+ * gdict_match_unref:
+ * @match: a #GdictMatch
+ *
+ * FIXME
+ *
+ * Since: 1.0
+ */
+void
+gdict_match_unref (GdictMatch *match)
+{
+ g_return_if_fail (match != NULL);
+
+ g_assert (match->ref_count != 0);
+
+ match->ref_count -= 1;
+
+ if (match->ref_count == 0)
+ {
+ g_free (match->word);
+ g_free (match->database);
+
+ g_slice_free (GdictMatch, match);
+ }
+}
+
+/**
+ * gdict_match_get_word:
+ * @match: a #GdictMatch
+ *
+ * FIXME
+ *
+ * Return value: FIXME
+ *
+ * Since: 1.0
+ */
+const gchar *
+gdict_match_get_word (GdictMatch *match)
+{
+ g_return_val_if_fail (match != NULL, NULL);
+
+ return match->word;
+}
+
+/**
+ * gdict_match_get_database:
+ * @match: a #GdictMatch
+ *
+ * FIXME
+ *
+ * Return value: FIXME
+ *
+ * Since: 1.0
+ */
+const gchar *
+gdict_match_get_database (GdictMatch *match)
+{
+ g_return_val_if_fail (match != NULL, NULL);
+
+ return match->database;
+}
+
+
+
+/*******************
+ * GdictDefinition *
+ *******************/
+
+GDICT_DEFINE_BOXED_TYPE (GdictDefinition, gdict_definition);
+
+/* GdictDefinition constructor */
+GdictDefinition *
+_gdict_definition_new (gint total)
+{
+ GdictDefinition *def;
+
+ def = g_slice_new (GdictDefinition);
+
+ def->total = total;
+ def->word = NULL;
+ def->database_name = NULL;
+ def->database_full = NULL;
+ def->ref_count = 1;
+
+ return def;
+}
+
+/**
+ * gdict_definition_ref:
+ * @def: a #GdictDefinition
+ *
+ * Increases the reference count of @def by one.
+ *
+ * Return value: the #GdictDefinition object with its reference count
+ * increased.
+ *
+ * Since: 1.0
+ */
+GdictDefinition *
+gdict_definition_ref (GdictDefinition *def)
+{
+ g_return_val_if_fail (def != NULL, NULL);
+
+ g_assert (def->ref_count != 0);
+
+ def->ref_count += 1;
+
+ return def;
+}
+
+/**
+ * gdict_definition_unref:
+ * @def: a #GdictDefinition
+ *
+ * Decreases the reference count of @def by one. If the reference count
+ * reaches zero, the #GdictDefinition object is freed.
+ *
+ * Since: 1.0
+ */
+void
+gdict_definition_unref (GdictDefinition *def)
+{
+ g_return_if_fail (def != NULL);
+
+ g_assert (def->ref_count != 0);
+
+ def->ref_count -= 1;
+ if (def->ref_count == 0)
+ {
+ g_free (def->word);
+ g_free (def->database_name);
+ g_free (def->database_full);
+
+ g_slice_free (GdictDefinition, def);
+ }
+}
+
+/**
+ * gdict_definition_get_total:
+ * @def: a #GdictDefinition
+ *
+ * Retrieves the total number of definitions that were found on a
+ * dictionary.
+ *
+ * Return value: the number of definitions.
+ *
+ * Since: 1.0
+ */
+gint
+gdict_definition_get_total (GdictDefinition *def)
+{
+ g_return_val_if_fail (def != NULL, -1);
+
+ return def->total;
+}
+
+/**
+ * gdict_definition_get_word:
+ * @def: a #GdictDefinition
+ *
+ * Retrieves the word used by the dictionary database to store
+ * the definition.
+ *
+ * Return value: a word. The returned string is owned by the
+ * #GdictDefinition object and should not be modified or freed.
+ *
+ * Since: 1.0
+ */
+const gchar *
+gdict_definition_get_word (GdictDefinition *def)
+{
+ g_return_val_if_fail (def != NULL, NULL);
+
+ return def->word;
+}
+
+/**
+ * gdict_definition_get_database:
+ * @def: a #GdictDefinition
+ *
+ * Retrieves the full name of the dictionary database where the
+ * definition is stored.
+ *
+ * Return value: the full name of a database. The returned string
+ * is owned by the #GdictDefinition object and should not be
+ * modified or freed.
+ *
+ * Since: 1.0
+ */
+const gchar *
+gdict_definition_get_database (GdictDefinition *def)
+{
+ g_return_val_if_fail (def != NULL, NULL);
+
+ return def->database_full;
+}
+
+/**
+ * gdict_definition_get_text:
+ * @def: a #GdictDefinition
+ *
+ * Retrieves the text of the definition.
+ *
+ * Return value: the text of the definition. The returned string
+ * is owned by the #GdictDefinition object, and should not be
+ * modified or freed.
+ *
+ * Since: 1.0
+ */
+const gchar *
+gdict_definition_get_text (GdictDefinition *def)
+{
+ g_return_val_if_fail (def != NULL, NULL);
+
+ return def->definition;
+}
diff --git a/mate-dictionary/libgdict/gdict-context.h b/mate-dictionary/libgdict/gdict-context.h
new file mode 100644
index 00000000..974a820b
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-context.h
@@ -0,0 +1,204 @@
+/* gdict-context.h - Abstract class for dictionary contexts
+ *
+ * Copyright (C) 2005 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+#ifndef __GDICT_CONTEXT_H__
+#define __GDICT_CONTEXT_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GDICT_TYPE_DATABASE (gdict_database_get_type ())
+#define GDICT_TYPE_STRATEGY (gdict_strategy_get_type ())
+#define GDICT_TYPE_MATCH (gdict_match_get_type ())
+#define GDICT_TYPE_DEFINITION (gdict_definition_get_type ())
+
+#define GDICT_TYPE_CONTEXT (gdict_context_get_type ())
+#define GDICT_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDICT_TYPE_CONTEXT, GdictContext))
+#define GDICT_IS_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDICT_TYPE_CONTEXT))
+#define GDICT_CONTEXT_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GDICT_TYPE_CONTEXT, GdictContextIface))
+
+/* These are the boxed containers for action results. */
+typedef struct _GdictDatabase GdictDatabase;
+typedef struct _GdictStrategy GdictStrategy;
+typedef struct _GdictMatch GdictMatch;
+typedef struct _GdictDefinition GdictDefinition;
+
+typedef struct _GdictContext GdictContext; /* dummy typedef */
+typedef struct _GdictContextIface GdictContextIface;
+
+#define GDICT_CONTEXT_ERROR (gdict_context_error_quark ())
+
+/**
+ * GdictContextError:
+ * @GDICT_CONTEXT_ERROR_PARSE:
+ * @GDICT_CONTEXT_ERROR_NOT_IMPLEMENTED:
+ * @GDICT_CONTEXT_ERROR_INVALID_DATABASE:
+ * @GDICT_CONTEXT_ERROR_INVALID_STRATEGY:
+ * @GDICT_CONTEXT_ERROR_INVALID_COMMAND:
+ * @GDICT_CONTEXT_ERROR_NO_MATCH:
+ * @GDICT_CONTEXT_ERROR_NO_DATABASES:
+ * @GDICT_CONTEXT_ERROR_NO_STRATEGIES:
+ *
+ * #GdictContext error enumeration.
+ */
+typedef enum {
+ GDICT_CONTEXT_ERROR_PARSE,
+ GDICT_CONTEXT_ERROR_NOT_IMPLEMENTED,
+ GDICT_CONTEXT_ERROR_INVALID_DATABASE,
+ GDICT_CONTEXT_ERROR_INVALID_STRATEGY,
+ GDICT_CONTEXT_ERROR_INVALID_COMMAND,
+ GDICT_CONTEXT_ERROR_NO_MATCH,
+ GDICT_CONTEXT_ERROR_NO_DATABASES,
+ GDICT_CONTEXT_ERROR_NO_STRATEGIES
+} GdictContextError;
+
+GQuark gdict_context_error_quark (void);
+
+/**
+ * GdictDatabase:
+ *
+ * A #GdictDatabase represents a database inside a dictionary source.
+ *
+ * The #GdictDatabase structure is private and should only be accessed
+ * using the available functions.
+ */
+GType gdict_database_get_type (void) G_GNUC_CONST;
+GdictDatabase * gdict_database_ref (GdictDatabase *db);
+void gdict_database_unref (GdictDatabase *db);
+const gchar *gdict_database_get_name (GdictDatabase *db);
+const gchar *gdict_database_get_full_name (GdictDatabase *db);
+
+/**
+ * GdictStrategy:
+ *
+ * A #GdictStrategy represents a matching strategy implemented by
+ * a dictionary source.
+ *
+ * The #GdictStrategy structure is private and should only be accessed
+ * using the available functions.
+ */
+GType gdict_strategy_get_type (void) G_GNUC_CONST;
+GdictStrategy * gdict_strategy_ref (GdictStrategy *strat);
+void gdict_strategy_unref (GdictStrategy *strat);
+const gchar *gdict_strategy_get_name (GdictStrategy *strat);
+const gchar *gdict_strategy_get_description (GdictStrategy *strat);
+
+/**
+ * GdictMatch:
+ *
+ * A #GdictMatch represents a single match for the searched word.
+ *
+ * The #GdictMatch structure is private and should only be accessed
+ * using the available functions.
+ */
+GType gdict_match_get_type (void) G_GNUC_CONST;
+GdictMatch * gdict_match_ref (GdictMatch *match);
+void gdict_match_unref (GdictMatch *match);
+const gchar *gdict_match_get_word (GdictMatch *match);
+const gchar *gdict_match_get_database (GdictMatch *match);
+
+/**
+ * GdictDefinition:
+ *
+ * A #GdictDefinition represents a single definition for the searched
+ * word.
+ *
+ * The #GdictDefinition structure is private and should only be
+ * accessed using the available functions.
+ */
+GType gdict_definition_get_type (void) G_GNUC_CONST;
+GdictDefinition * gdict_definition_ref (GdictDefinition *def);
+void gdict_definition_unref (GdictDefinition *def);
+gint gdict_definition_get_total (GdictDefinition *def);
+const gchar *gdict_definition_get_word (GdictDefinition *def);
+const gchar *gdict_definition_get_database (GdictDefinition *def);
+const gchar *gdict_definition_get_text (GdictDefinition *def);
+
+/**
+ * GdictContextIface:
+ *
+ * Interface defintion
+ */
+struct _GdictContextIface
+{
+ /*< private >*/
+ GTypeInterface base_iface;
+
+ /*< public >*/
+ /* methods, not signals */
+ gboolean (*get_databases) (GdictContext *context,
+ GError **error);
+ gboolean (*get_strategies) (GdictContext *context,
+ GError **error);
+ gboolean (*match_word) (GdictContext *context,
+ const gchar *database,
+ const gchar *strategy,
+ const gchar *word,
+ GError **error);
+ gboolean (*define_word) (GdictContext *context,
+ const gchar *database,
+ const gchar *word,
+ GError **error);
+
+ /* signals */
+ void (*lookup_start) (GdictContext *context);
+ void (*lookup_end) (GdictContext *context);
+
+ void (*database_found) (GdictContext *context,
+ GdictDatabase *database);
+ void (*strategy_found) (GdictContext *context,
+ GdictStrategy *strategy);
+ void (*match_found) (GdictContext *context,
+ GdictMatch *match);
+ void (*definition_found) (GdictContext *context,
+ GdictDefinition *definition);
+
+ /* fired each time there's an error; the GError is owned
+ * by the context, and should never be modified or freed
+ */
+ void (*error) (GdictContext *context,
+ const GError *error);
+};
+
+GType gdict_context_get_type (void) G_GNUC_CONST;
+
+/* Configuration */
+void gdict_context_set_local_only (GdictContext *context,
+ gboolean local_only);
+gboolean gdict_context_get_local_only (GdictContext *context);
+
+/* Actions */
+gboolean gdict_context_lookup_databases (GdictContext *context,
+ GError **error);
+gboolean gdict_context_lookup_strategies (GdictContext *context,
+ GError **error);
+gboolean gdict_context_match_word (GdictContext *context,
+ const gchar *database,
+ const gchar *strategy,
+ const gchar *word,
+ GError **error);
+gboolean gdict_context_define_word (GdictContext *context,
+ const gchar *database,
+ const gchar *word,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __GDICT_CONTEXT_H__ */
diff --git a/mate-dictionary/libgdict/gdict-database-chooser.c b/mate-dictionary/libgdict/gdict-database-chooser.c
new file mode 100644
index 00000000..475b150b
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-database-chooser.c
@@ -0,0 +1,1142 @@
+/* gdict-database-chooser.c - display widget for database names
+ *
+ * Copyright (C) 2006 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+/**
+ * SECTION:gdict-database-chooser
+ * @short_description: Display the list of available databases
+ *
+ * Each #GdictContext has a list of databases, that is dictionaries that
+ * can be queried. #GdictDatabaseChooser is a widget that queries a given
+ * #GdictContext and displays the list of available databases.
+ *
+ * #GdictDatabaseChooser is available since Gdict 0.10
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n-lib.h>
+
+#include "gdict-database-chooser.h"
+#include "gdict-utils.h"
+#include "gdict-debug.h"
+#include "gdict-private.h"
+#include "gdict-enum-types.h"
+#include "gdict-marshal.h"
+
+#define GDICT_DATABASE_CHOOSER_GET_PRIVATE(obj) \
+(G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDICT_TYPE_DATABASE_CHOOSER, GdictDatabaseChooserPrivate))
+
+struct _GdictDatabaseChooserPrivate
+{
+ GtkListStore *store;
+
+ GtkWidget *treeview;
+ GtkWidget *clear_button;
+ GtkWidget *refresh_button;
+ GtkWidget *buttons_box;
+
+ GdictContext *context;
+ gint results;
+
+ guint start_id;
+ guint match_id;
+ guint end_id;
+ guint error_id;
+
+ GdkCursor *busy_cursor;
+
+ gchar *current_db;
+
+ guint is_searching : 1;
+};
+
+enum
+{
+ DATABASE_NAME,
+ DATABASE_ERROR
+} DBType;
+
+enum
+{
+ DB_COLUMN_TYPE,
+ DB_COLUMN_NAME,
+ DB_COLUMN_DESCRIPTION,
+ DB_COLUMN_CURRENT,
+
+ DB_N_COLUMNS
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_CONTEXT,
+ PROP_COUNT
+};
+
+enum
+{
+ DATABASE_ACTIVATED,
+ SELECTION_CHANGED,
+
+ LAST_SIGNAL
+};
+
+static guint db_chooser_signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GdictDatabaseChooser,
+ gdict_database_chooser,
+ GTK_TYPE_VBOX);
+
+
+static void
+set_gdict_context (GdictDatabaseChooser *chooser,
+ GdictContext *context)
+{
+ GdictDatabaseChooserPrivate *priv;
+
+ g_assert (GDICT_IS_DATABASE_CHOOSER (chooser));
+ priv = chooser->priv;
+
+ if (priv->context)
+ {
+ if (priv->start_id)
+ {
+ GDICT_NOTE (CHOOSER, "Removing old context handlers");
+
+ g_signal_handler_disconnect (priv->context, priv->start_id);
+ g_signal_handler_disconnect (priv->context, priv->match_id);
+ g_signal_handler_disconnect (priv->context, priv->end_id);
+
+ priv->start_id = 0;
+ priv->end_id = 0;
+ priv->match_id = 0;
+ }
+
+ if (priv->error_id)
+ {
+ g_signal_handler_disconnect (priv->context, priv->error_id);
+
+ priv->error_id = 0;
+ }
+
+ GDICT_NOTE (CHOOSER, "Removing old context");
+
+ g_object_unref (G_OBJECT (priv->context));
+
+ priv->context = NULL;
+ priv->results = -1;
+ }
+
+ if (!context)
+ return;
+
+ if (!GDICT_IS_CONTEXT (context))
+ {
+ g_warning ("Object of type '%s' instead of a GdictContext\n",
+ g_type_name (G_OBJECT_TYPE (context)));
+ return;
+ }
+
+ GDICT_NOTE (CHOOSER, "Setting new context");
+
+ priv->context = g_object_ref (context);
+ priv->results = 0;
+}
+
+static void
+gdict_database_chooser_finalize (GObject *gobject)
+{
+ GdictDatabaseChooser *chooser = GDICT_DATABASE_CHOOSER (gobject);
+ GdictDatabaseChooserPrivate *priv = chooser->priv;
+
+ g_free (priv->current_db);
+
+ G_OBJECT_CLASS (gdict_database_chooser_parent_class)->finalize (gobject);
+}
+
+static void
+gdict_database_chooser_dispose (GObject *gobject)
+{
+ GdictDatabaseChooser *chooser = GDICT_DATABASE_CHOOSER (gobject);
+ GdictDatabaseChooserPrivate *priv = chooser->priv;
+
+ set_gdict_context (chooser, NULL);
+
+ if (priv->busy_cursor)
+ {
+ gdk_cursor_unref (priv->busy_cursor);
+ priv->busy_cursor = NULL;
+ }
+
+ if (priv->store)
+ {
+ g_object_unref (priv->store);
+ priv->store = NULL;
+ }
+
+ G_OBJECT_CLASS (gdict_database_chooser_parent_class)->dispose (gobject);
+}
+
+static void
+gdict_database_chooser_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GdictDatabaseChooser *chooser = GDICT_DATABASE_CHOOSER (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_CONTEXT:
+ set_gdict_context (chooser, g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gdict_database_chooser_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GdictDatabaseChooser *chooser = GDICT_DATABASE_CHOOSER (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_CONTEXT:
+ g_value_set_object (value, chooser->priv->context);
+ break;
+ case PROP_COUNT:
+ g_value_set_int (value, chooser->priv->results);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+row_activated_cb (GtkTreeView *treeview,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer user_data)
+{
+ GdictDatabaseChooser *chooser = GDICT_DATABASE_CHOOSER (user_data);
+ GdictDatabaseChooserPrivate *priv = chooser->priv;
+ GtkTreeIter iter;
+ gchar *db_name, *db_desc;
+ gboolean valid;
+
+ valid = gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->store),
+ &iter,
+ path);
+ if (!valid)
+ {
+ g_warning ("Invalid iterator found");
+ return;
+ }
+
+ gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter,
+ DB_COLUMN_NAME, &db_name,
+ DB_COLUMN_DESCRIPTION, &db_desc,
+ -1);
+ if (db_name && db_desc)
+ {
+ g_free (priv->current_db);
+ priv->current_db = g_strdup (db_name);
+
+ g_signal_emit (chooser, db_chooser_signals[DATABASE_ACTIVATED], 0,
+ db_name, db_desc);
+ }
+ else
+ {
+ gchar *row = gtk_tree_path_to_string (path);
+
+ g_warning ("Row %s activated, but no database attached", row);
+ g_free (row);
+ }
+
+ g_free (db_name);
+ g_free (db_desc);
+}
+
+static void
+refresh_button_clicked_cb (GtkWidget *widget,
+ gpointer user_data)
+{
+ GdictDatabaseChooser *chooser = GDICT_DATABASE_CHOOSER (user_data);
+
+ gdict_database_chooser_refresh (chooser);
+}
+
+static void
+clear_button_clicked_cb (GtkWidget *widget,
+ gpointer user_data)
+{
+ GdictDatabaseChooser *chooser = GDICT_DATABASE_CHOOSER (user_data);
+
+ gdict_database_chooser_clear (chooser);
+}
+
+static void
+selection_changed_cb (GtkTreeSelection *selection,
+ gpointer user_data)
+{
+ g_signal_emit (user_data, db_chooser_signals[SELECTION_CHANGED], 0);
+}
+
+static GObject *
+gdict_database_chooser_constructor (GType type,
+ guint n_params,
+ GObjectConstructParam *params)
+{
+ GObjectClass *parent_class;
+ GObject *object;
+ GdictDatabaseChooser *chooser;
+ GdictDatabaseChooserPrivate *priv;
+ GtkWidget *sw;
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+ GtkWidget *hbox;
+
+ parent_class = G_OBJECT_CLASS (gdict_database_chooser_parent_class);
+ object = parent_class->constructor (type, n_params, params);
+
+ chooser = GDICT_DATABASE_CHOOSER (object);
+ priv = chooser->priv;
+
+ gtk_widget_push_composite_child ();
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_widget_set_composite_name (sw, "gdict-database-chooser-scrolled-window");
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+ GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (chooser), sw, TRUE, TRUE, 0);
+ gtk_widget_show (sw);
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("databases",
+ renderer,
+ "text", DB_COLUMN_DESCRIPTION,
+ "weight", DB_COLUMN_CURRENT,
+ NULL);
+ priv->treeview = gtk_tree_view_new ();
+ gtk_widget_set_composite_name (priv->treeview, "gdict-database-chooser-treeview");
+ gtk_tree_view_set_model (GTK_TREE_VIEW (priv->treeview),
+ GTK_TREE_MODEL (priv->store));
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->treeview), FALSE);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (priv->treeview), column);
+ g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview)),
+ "changed", G_CALLBACK (selection_changed_cb),
+ chooser);
+ g_signal_connect (priv->treeview, "row-activated",
+ G_CALLBACK (row_activated_cb), chooser);
+ gtk_container_add (GTK_CONTAINER (sw), priv->treeview);
+ gtk_widget_show (priv->treeview);
+
+ hbox = gtk_hbox_new (FALSE, 0);
+ priv->buttons_box = hbox;
+
+ priv->refresh_button = gtk_button_new ();
+ gtk_button_set_image (GTK_BUTTON (priv->refresh_button),
+ gtk_image_new_from_stock (GTK_STOCK_REFRESH,
+ GTK_ICON_SIZE_SMALL_TOOLBAR));
+ g_signal_connect (priv->refresh_button, "clicked",
+ G_CALLBACK (refresh_button_clicked_cb),
+ chooser);
+ gtk_box_pack_start (GTK_BOX (hbox), priv->refresh_button, FALSE, FALSE, 0);
+ gtk_widget_show (priv->refresh_button);
+ gtk_widget_set_tooltip_text (priv->refresh_button,
+ _("Reload the list of available databases"));
+
+ priv->clear_button = gtk_button_new ();
+ gtk_button_set_image (GTK_BUTTON (priv->clear_button),
+ gtk_image_new_from_stock (GTK_STOCK_CLEAR,
+ GTK_ICON_SIZE_SMALL_TOOLBAR));
+ g_signal_connect (priv->clear_button, "clicked",
+ G_CALLBACK (clear_button_clicked_cb),
+ chooser);
+ gtk_box_pack_start (GTK_BOX (hbox), priv->clear_button, FALSE, FALSE, 0);
+ gtk_widget_show (priv->clear_button);
+ gtk_widget_set_tooltip_text (priv->clear_button,
+ _("Clear the list of available databases"));
+
+ gtk_box_pack_end (GTK_BOX (chooser), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ gtk_widget_pop_composite_child ();
+
+ return object;
+}
+
+static void
+gdict_database_chooser_class_init (GdictDatabaseChooserClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = gdict_database_chooser_finalize;
+ gobject_class->dispose = gdict_database_chooser_dispose;
+ gobject_class->set_property = gdict_database_chooser_set_property;
+ gobject_class->get_property = gdict_database_chooser_get_property;
+ gobject_class->constructor = gdict_database_chooser_constructor;
+
+ /**
+ * GdictDatabaseChooser:context:
+ *
+ * The #GdictContext used to retrieve the list of available databases.
+ *
+ * Since: 0.10
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_CONTEXT,
+ g_param_spec_object ("context",
+ "Context",
+ "The GdictContext object used to get the list of databases",
+ GDICT_TYPE_CONTEXT,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)));
+ /**
+ * GdictDatabaseChooser:count:
+ *
+ * The number of displayed databases or, if no #GdictContext is set, -1.
+ *
+ * Since: 0.12
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_COUNT,
+ g_param_spec_int ("count",
+ "Count",
+ "The number of available databases",
+ -1, G_MAXINT, -1,
+ G_PARAM_READABLE));
+
+ /**
+ * GdictDatabaseChooser::database-activated:
+ * @chooser: the database chooser that received the signal
+ * @name: the name of the activated database
+ * @description: the description of the activated database
+ *
+ * The ::database-activated signal is emitted each time the user
+ * activated a row in the database chooser widget, either by double
+ * clicking on it or by a keyboard event.
+ *
+ * Since: 0.10
+ */
+ db_chooser_signals[DATABASE_ACTIVATED] =
+ g_signal_new ("database-activated",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdictDatabaseChooserClass, database_activated),
+ NULL, NULL,
+ gdict_marshal_VOID__STRING_STRING,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+ /**
+ * GdictDatabaseChooser::selection-changed:
+ * @chooser: the database chooser that received the signal
+ *
+ * The ::selection-changed signal is emitted each time the selection
+ * inside the database chooser has been changed.
+ *
+ * Since: 0.12
+ */
+ db_chooser_signals[SELECTION_CHANGED] =
+ g_signal_new ("selection-changed",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdictDatabaseChooserClass, selection_changed),
+ NULL, NULL,
+ gdict_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_type_class_add_private (gobject_class, sizeof (GdictDatabaseChooserPrivate));
+}
+
+static void
+gdict_database_chooser_init (GdictDatabaseChooser *chooser)
+{
+ GdictDatabaseChooserPrivate *priv;
+
+ chooser->priv = priv = GDICT_DATABASE_CHOOSER_GET_PRIVATE (chooser);
+
+ priv->results = -1;
+ priv->context = NULL;
+
+ priv->store = gtk_list_store_new (DB_N_COLUMNS,
+ G_TYPE_INT, /* db_type */
+ G_TYPE_STRING, /* db_name */
+ G_TYPE_STRING, /* db_desc */
+ G_TYPE_INT /* db_current */);
+
+ priv->start_id = 0;
+ priv->end_id = 0;
+ priv->match_id = 0;
+ priv->error_id = 0;
+}
+
+/**
+ * gdict_database_chooser_new:
+ *
+ * Creates a new #GdictDatabaseChooser widget. A Database chooser widget
+ * can be used to display the list of available databases on a dictionary
+ * source using the #GdictContext representing it. After creation, the
+ * #GdictContext can be set using gdict_database_chooser_set_context().
+ *
+ * Return value: the newly created #GdictDatabaseChooser widget.
+ *
+ * Since: 0.10
+ */
+GtkWidget *
+gdict_database_chooser_new (void)
+{
+ return g_object_new (GDICT_TYPE_DATABASE_CHOOSER, NULL);
+}
+
+/**
+ * gdict_database_chooser_new_with_context:
+ * @context: a #GdictContext
+ *
+ * Creates a new #GdictDatabaseChooser, using @context as the representation
+ * of the dictionary source to query for the list of available databases.
+ *
+ * Return value: the newly created #GdictDatabaseChooser widget.
+ *
+ * Since: 0.10
+ */
+GtkWidget *
+gdict_database_chooser_new_with_context (GdictContext *context)
+{
+ g_return_val_if_fail (GDICT_IS_CONTEXT (context), NULL);
+
+ return g_object_new (GDICT_TYPE_DATABASE_CHOOSER,
+ "context", context,
+ NULL);
+}
+
+/**
+ * gdict_database_chooser_get_context:
+ * @chooser: a #GdictDatabaseChooser
+ *
+ * Retrieves the #GdictContext used by @chooser.
+ *
+ * Return value: a #GdictContext or %NULL
+ *
+ * Since: 0.10
+ */
+GdictContext *
+gdict_database_chooser_get_context (GdictDatabaseChooser *chooser)
+{
+ g_return_val_if_fail (GDICT_IS_DATABASE_CHOOSER (chooser), NULL);
+
+ return chooser->priv->context;
+}
+
+/**
+ * gdict_database_chooser_set_context:
+ * @chooser: a #GdictDatabaseChooser
+ * @context: a #GdictContext
+ *
+ * Sets the #GdictContext to be used to query a dictionary source
+ * for the list of available databases.
+ *
+ * Since: 0.10
+ */
+void
+gdict_database_chooser_set_context (GdictDatabaseChooser *chooser,
+ GdictContext *context)
+{
+ g_return_if_fail (GDICT_IS_DATABASE_CHOOSER (chooser));
+ g_return_if_fail (context == NULL || GDICT_IS_CONTEXT (context));
+
+ set_gdict_context (chooser, context);
+
+ g_object_notify (G_OBJECT (chooser), "context");
+}
+
+/**
+ * gdict_database_chooser_get_databases:
+ * @chooser: a #GdictDatabaseChooser
+ * @length: return location for the length of the returned vector
+ *
+ * Gets the list of available database names.
+ *
+ * Return value: a newly allocated, %NULL terminated string vector
+ * containing database names. Use g_strfreev() to deallocate it.
+ *
+ * Since: 0.10
+ */
+gchar **
+gdict_database_chooser_get_databases (GdictDatabaseChooser *chooser,
+ gsize *length)
+{
+ GdictDatabaseChooserPrivate *priv;
+ GtkTreeIter iter;
+ gchar **retval;
+ gsize i;
+
+ g_return_val_if_fail (GDICT_IS_DATABASE_CHOOSER (chooser), NULL);
+
+ priv = chooser->priv;
+
+ if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->store), &iter))
+ return NULL;
+
+ i = 0;
+ retval = g_new (gchar*, priv->results);
+
+ do
+ {
+ gchar *db_name;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter,
+ DB_COLUMN_NAME, &db_name,
+ -1);
+
+ retval[i++] = db_name;
+ }
+ while (gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->store), &iter));
+
+ retval[i] = NULL;
+
+ if (length)
+ *length = i;
+
+ return retval;
+}
+
+/**
+ * gdict_database_chooser_has_database:
+ * @chooser: a #GdictDatabaseChooser
+ * @database: the name of a database
+ *
+ * Checks whether the @chooser displays @database
+ *
+ * Return value: %TRUE if the search database name is present
+ *
+ * Since: 0.10
+ */
+gboolean
+gdict_database_chooser_has_database (GdictDatabaseChooser *chooser,
+ const gchar *database)
+{
+ GdictDatabaseChooserPrivate *priv;
+ GtkTreeIter iter;
+ gboolean retval;
+
+ g_return_val_if_fail (GDICT_IS_DATABASE_CHOOSER (chooser), FALSE);
+ g_return_val_if_fail (database != NULL, FALSE);
+
+ priv = chooser->priv;
+
+ if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->store), &iter))
+ return FALSE;
+
+ retval = FALSE;
+
+ do
+ {
+ gchar *db_name;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter,
+ DB_COLUMN_NAME, &db_name,
+ -1);
+
+ if (strcmp (db_name, database) == 0)
+ {
+ g_free (db_name);
+ retval = TRUE;
+ break;
+ }
+
+ g_free (db_name);
+ }
+ while (gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->store), &iter));
+
+ return retval;
+}
+
+/**
+ * gdict_database_chooser_count_databases:
+ * @chooser: a #GdictDatabaseChooser
+ *
+ * Returns the number of databases found.
+ *
+ * Return value: the number of databases or -1 if no context is set
+ *
+ * Since: 0.10
+ */
+gint
+gdict_database_chooser_count_databases (GdictDatabaseChooser *chooser)
+{
+ g_return_val_if_fail (GDICT_IS_DATABASE_CHOOSER (chooser), -1);
+
+ return chooser->priv->results;
+}
+
+static void
+lookup_start_cb (GdictContext *context,
+ gpointer user_data)
+{
+ GdictDatabaseChooser *chooser = GDICT_DATABASE_CHOOSER (user_data);
+ GdictDatabaseChooserPrivate *priv = chooser->priv;
+
+ if (!priv->busy_cursor)
+ priv->busy_cursor = gdk_cursor_new (GDK_WATCH);
+
+ if (gtk_widget_get_window (GTK_WIDGET (chooser)))
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (chooser)), priv->busy_cursor);
+
+ priv->is_searching = TRUE;
+}
+
+static void
+lookup_end_cb (GdictContext *context,
+ gpointer user_data)
+{
+ GdictDatabaseChooser *chooser = GDICT_DATABASE_CHOOSER (user_data);
+ GdictDatabaseChooserPrivate *priv = chooser->priv;
+
+ if (gtk_widget_get_window (GTK_WIDGET (chooser)))
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (chooser)), NULL);
+
+ priv->is_searching = FALSE;
+}
+
+static void
+database_found_cb (GdictContext *context,
+ GdictDatabase *database,
+ gpointer user_data)
+{
+ GdictDatabaseChooser *chooser = GDICT_DATABASE_CHOOSER (user_data);
+ GdictDatabaseChooserPrivate *priv = chooser->priv;
+ GtkTreeIter iter;
+ const gchar *name, *full_name;
+ gint weight = PANGO_WEIGHT_NORMAL;
+
+ name = gdict_database_get_name (database);
+ full_name = gdict_database_get_full_name (database);
+
+ if (priv->current_db && !strcmp (priv->current_db, name))
+ weight = PANGO_WEIGHT_BOLD;
+
+ GDICT_NOTE (CHOOSER, "DATABASE: `%s' (`%s')",
+ name,
+ full_name);
+
+ gtk_list_store_append (priv->store, &iter);
+ gtk_list_store_set (priv->store, &iter,
+ DB_COLUMN_TYPE, DATABASE_NAME,
+ DB_COLUMN_NAME, name,
+ DB_COLUMN_DESCRIPTION, full_name,
+ DB_COLUMN_CURRENT, weight,
+ -1);
+
+ priv->results += 1;
+}
+
+static void
+error_cb (GdictContext *context,
+ const GError *error,
+ gpointer user_data)
+{
+ GdictDatabaseChooser *chooser = GDICT_DATABASE_CHOOSER (user_data);
+
+ if (gtk_widget_get_window (GTK_WIDGET (chooser)))
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (chooser)), NULL);
+
+ chooser->priv->is_searching = FALSE;
+ chooser->priv->results = 0;
+}
+
+/**
+ * gdict_database_chooser_refresh:
+ * @chooser: a #GdictDatabaseChooser
+ *
+ * Reloads the list of available databases.
+ *
+ * Since: 0.10
+ */
+void
+gdict_database_chooser_refresh (GdictDatabaseChooser *chooser)
+{
+ GdictDatabaseChooserPrivate *priv;
+ GError *db_error;
+
+ g_return_if_fail (GDICT_IS_DATABASE_CHOOSER (chooser));
+
+ priv = chooser->priv;
+
+ if (!priv->context)
+ {
+ g_warning ("Attempting to retrieve the available databases, but "
+ "no GdictContext has been set. Use gdict_database_chooser_set_context() "
+ "before invoking gdict_database_chooser_refresh().");
+ return;
+ }
+
+ if (priv->is_searching)
+ return;
+
+ gdict_database_chooser_clear (chooser);
+
+ if (!priv->start_id)
+ {
+ priv->start_id = g_signal_connect (priv->context, "lookup-start",
+ G_CALLBACK (lookup_start_cb),
+ chooser);
+ priv->match_id = g_signal_connect (priv->context, "database-found",
+ G_CALLBACK (database_found_cb),
+ chooser);
+ priv->end_id = g_signal_connect (priv->context, "lookup-end",
+ G_CALLBACK (lookup_end_cb),
+ chooser);
+ }
+
+ if (!priv->error_id)
+ priv->error_id = g_signal_connect (priv->context, "error",
+ G_CALLBACK (error_cb),
+ chooser);
+
+ db_error = NULL;
+ gdict_context_lookup_databases (priv->context, &db_error);
+ if (db_error)
+ {
+ GtkTreeIter iter;
+
+ gtk_list_store_append (priv->store, &iter);
+ gtk_list_store_set (priv->store, &iter,
+ DB_COLUMN_TYPE, DATABASE_ERROR,
+ DB_COLUMN_NAME, _("Error while matching"),
+ DB_COLUMN_DESCRIPTION, NULL,
+ -1);
+
+ g_warning ("Error while looking for databases: %s",
+ db_error->message);
+
+ g_error_free (db_error);
+ }
+}
+
+/**
+ * gdict_database_chooser_clear:
+ * @chooser: a #GdictDatabaseChooser
+ *
+ * Clears @chooser.
+ *
+ * Since: 0.10
+ */
+void
+gdict_database_chooser_clear (GdictDatabaseChooser *chooser)
+{
+ GdictDatabaseChooserPrivate *priv;
+
+ g_return_if_fail (GDICT_IS_DATABASE_CHOOSER (chooser));
+
+ priv = chooser->priv;
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (priv->treeview), NULL);
+
+ gtk_list_store_clear (priv->store);
+ priv->results = 0;
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (priv->treeview),
+ GTK_TREE_MODEL (priv->store));
+}
+
+typedef struct
+{
+ gchar *db_name;
+ GdictDatabaseChooser *chooser;
+
+ guint found : 1;
+ guint do_select : 1;
+ guint do_activate : 1;
+} SelectData;
+
+static gboolean
+scan_for_db_name (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ SelectData *select_data = user_data;
+ gchar *db_name = NULL;
+
+ if (!select_data)
+ return TRUE;
+
+ gtk_tree_model_get (model, iter, DB_COLUMN_NAME, &db_name, -1);
+ if (!db_name)
+ return FALSE;
+
+ if (strcmp (db_name, select_data->db_name) == 0)
+ {
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+
+ select_data->found = TRUE;
+
+ tree_view = GTK_TREE_VIEW (select_data->chooser->priv->treeview);
+ if (select_data->do_activate)
+ {
+ GtkTreeViewColumn *column;
+
+ gtk_list_store_set (GTK_LIST_STORE (model), iter,
+ DB_COLUMN_CURRENT, PANGO_WEIGHT_BOLD,
+ -1);
+
+ column = gtk_tree_view_get_column (tree_view, 0);
+ gtk_tree_view_row_activated (tree_view, path, column);
+ }
+
+ selection = gtk_tree_view_get_selection (tree_view);
+ if (select_data->do_select)
+ gtk_tree_selection_select_path (selection, path);
+ else
+ gtk_tree_selection_unselect_path (selection, path);
+ }
+ else
+ {
+ gtk_list_store_set (GTK_LIST_STORE (model), iter,
+ DB_COLUMN_CURRENT, PANGO_WEIGHT_NORMAL,
+ -1);
+ }
+
+ g_free (db_name);
+
+ return FALSE;
+}
+
+/**
+ * gdict_database_chooser_select_database:
+ * @chooser: a #GdictDatabaseChooser
+ * @db_name: name of the database to select
+ *
+ * Selects the database with @db_name inside the @chooser widget.
+ *
+ * Return value: %TRUE if the database was found and selected
+ *
+ * Since: 0.10
+ */
+gboolean
+gdict_database_chooser_select_database (GdictDatabaseChooser *chooser,
+ const gchar *db_name)
+{
+ GdictDatabaseChooserPrivate *priv;
+ SelectData data;
+ gboolean retval;
+
+ g_return_val_if_fail (GDICT_IS_DATABASE_CHOOSER (chooser), FALSE);
+ g_return_val_if_fail (db_name != NULL, FALSE);
+
+ priv = chooser->priv;
+
+ data.db_name = g_strdup (db_name);
+ data.chooser = chooser;
+ data.found = FALSE;
+ data.do_select = TRUE;
+ data.do_activate = FALSE;
+
+ gtk_tree_model_foreach (GTK_TREE_MODEL (priv->store),
+ scan_for_db_name,
+ &data);
+
+ retval = data.found;
+
+ g_free (data.db_name);
+
+ return retval;
+}
+
+/**
+ * gdict_database_chooser_unselect_database:
+ * @chooser: a #GdictDatabaseChooser
+ * @db_name: name of the database to unselect
+ *
+ * Unselects the database @db_name inside the @chooser widget
+ *
+ * Return value: %TRUE if the database was found and unselected
+ *
+ * Since: 0.10
+ */
+gboolean
+gdict_database_chooser_unselect_database (GdictDatabaseChooser *chooser,
+ const gchar *db_name)
+{
+ GdictDatabaseChooserPrivate *priv;
+ SelectData data;
+ gboolean retval;
+
+ g_return_val_if_fail (GDICT_IS_DATABASE_CHOOSER (chooser), FALSE);
+ g_return_val_if_fail (db_name != NULL, FALSE);
+
+ priv = chooser->priv;
+
+ data.db_name = g_strdup (db_name);
+ data.chooser = chooser;
+ data.found = FALSE;
+ data.do_select = FALSE;
+ data.do_activate = FALSE;
+
+ gtk_tree_model_foreach (GTK_TREE_MODEL (priv->store),
+ scan_for_db_name,
+ &data);
+
+ retval = data.found;
+
+ g_free (data.db_name);
+
+ return retval;
+}
+
+/**
+ * gdict_database_chooser_set_current_database:
+ * @chooser: a #GdictDatabaseChooser
+ * @db_name: the name of the database
+ *
+ * Sets @db_name as the current database. This function will select
+ * and activate the corresponding row, if the database is found.
+ *
+ * Return value: %TRUE if the database was found and set
+ *
+ * Since: 0.10
+ */
+gboolean
+gdict_database_chooser_set_current_database (GdictDatabaseChooser *chooser,
+ const gchar *db_name)
+{
+ GdictDatabaseChooserPrivate *priv;
+ SelectData data;
+ gboolean retval;
+
+ g_return_val_if_fail (GDICT_IS_DATABASE_CHOOSER (chooser), FALSE);
+ g_return_val_if_fail (db_name != NULL, FALSE);
+
+ priv = chooser->priv;
+
+ data.db_name = g_strdup (db_name);
+ data.chooser = chooser;
+ data.found = FALSE;
+ data.do_select = TRUE;
+ data.do_activate = TRUE;
+
+ gtk_tree_model_foreach (GTK_TREE_MODEL (priv->store),
+ scan_for_db_name,
+ &data);
+
+ retval = data.found;
+
+ if (data.found)
+ {
+ g_free (priv->current_db);
+ priv->current_db = data.db_name;
+ }
+ else
+ g_free (data.db_name);
+
+ return retval;
+}
+
+/**
+ * gdict_database_chooser_get_current_database:
+ * @chooser: a #GdictDatabaseChooser
+ *
+ * Retrieves the name of the currently selected database inside @chooser
+ *
+ * Return value: the name of the selected database. Use g_free() on the
+ * returned string when done using it
+ *
+ * Since: 0.10
+ */
+gchar *
+gdict_database_chooser_get_current_database (GdictDatabaseChooser *chooser)
+{
+ GdictDatabaseChooserPrivate *priv;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gchar *retval = NULL;
+
+ g_return_val_if_fail (GDICT_IS_DATABASE_CHOOSER (chooser), NULL);
+
+ priv = chooser->priv;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+ return NULL;
+
+ gtk_tree_model_get (model, &iter, DB_COLUMN_NAME, &retval, -1);
+
+ g_free (priv->current_db);
+ priv->current_db = g_strdup (retval);
+
+ return retval;
+}
+
+/**
+ * gdict_database_chooser_add_button:
+ * @chooser: a #GdictDatabase
+ * @button_text: text of the button
+ *
+ * Adds a #GtkButton with @button_text to the button area on
+ * the bottom of @chooser. The @button_text can also be a
+ * stock ID.
+ *
+ * Return value: the newly packed button.
+ *
+ * Since: 0.10
+ */
+GtkWidget *
+gdict_database_chooser_add_button (GdictDatabaseChooser *chooser,
+ const gchar *button_text)
+{
+ GdictDatabaseChooserPrivate *priv;
+ GtkWidget *button;
+
+ g_return_val_if_fail (GDICT_IS_DATABASE_CHOOSER (chooser), NULL);
+ g_return_val_if_fail (button_text != NULL, NULL);
+
+ priv = chooser->priv;
+
+ button = gtk_button_new_from_stock (button_text);
+
+ gtk_widget_set_can_default (button, TRUE);
+
+ gtk_widget_show (button);
+
+ gtk_box_pack_end (GTK_BOX (priv->buttons_box), button, FALSE, TRUE, 0);
+
+ return button;
+}
diff --git a/mate-dictionary/libgdict/gdict-database-chooser.h b/mate-dictionary/libgdict/gdict-database-chooser.h
new file mode 100644
index 00000000..c95a23ab
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-database-chooser.h
@@ -0,0 +1,99 @@
+/* gdict-database-chooser.h - display widget for database names
+ *
+ * Copyright (C) 2006 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+#ifndef __GDICT_DATABASE_CHOOSER_H__
+#define __GDICT_DATABASE_CHOOSER_H__
+
+#include <gtk/gtk.h>
+#include "gdict-context.h"
+
+G_BEGIN_DECLS
+
+#define GDICT_TYPE_DATABASE_CHOOSER (gdict_database_chooser_get_type ())
+#define GDICT_DATABASE_CHOOSER(obj) \
+(G_TYPE_CHECK_INSTANCE_CAST ((obj), GDICT_TYPE_DATABASE_CHOOSER, GdictDatabaseChooser))
+#define GDICT_IS_DATABASE_CHOOSER(obj) \
+(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDICT_TYPE_DATABASE_CHOOSER))
+#define GDICT_DATABASE_CHOOSER_CLASS(klass) \
+(G_TYPE_CHECK_CLASS_CAST ((klass), GDICT_TYPE_DATABASE_CHOOSER, GdictDatabaseChooserClass))
+#define GDICT_IS_DATABASE_CHOOSER_CLASS(klass) \
+(G_TYPE_CHECK_CLASS_TYPE ((klass), GDICT_TYPE_DATABASE_CHOOSER))
+#define GDICT_DATABASE_CHOOSER_GET_CLASS(obj) \
+(G_TYPE_INSTANCE_GET_CLASS ((obj), GDICT_TYPE_DATABASE_CHOOSER, GdictDatabaseChooserClass))
+
+typedef struct _GdictDatabaseChooser GdictDatabaseChooser;
+typedef struct _GdictDatabaseChooserPrivate GdictDatabaseChooserPrivate;
+typedef struct _GdictDatabaseChooserClass GdictDatabaseChooserClass;
+
+struct _GdictDatabaseChooser
+{
+ /*< private >*/
+ GtkVBox parent_instance;
+
+ GdictDatabaseChooserPrivate *priv;
+};
+
+struct _GdictDatabaseChooserClass
+{
+ /*< private >*/
+ GtkVBoxClass parent_class;
+
+ /*< public >*/
+ void (*database_activated) (GdictDatabaseChooser *chooser,
+ const gchar *name,
+ const gchar *description);
+ void (*selection_changed) (GdictDatabaseChooser *chooser);
+
+ /*< private >*/
+ /* padding for future expansion */
+ void (*_gdict_padding2) (void);
+ void (*_gdict_padding3) (void);
+ void (*_gdict_padding4) (void);
+ void (*_gdict_padding5) (void);
+ void (*_gdict_padding6) (void);
+};
+
+GType gdict_database_chooser_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gdict_database_chooser_new (void);
+GtkWidget * gdict_database_chooser_new_with_context (GdictContext *context);
+
+GdictContext *gdict_database_chooser_get_context (GdictDatabaseChooser *chooser);
+void gdict_database_chooser_set_context (GdictDatabaseChooser *chooser,
+ GdictContext *context);
+gboolean gdict_database_chooser_select_database (GdictDatabaseChooser *chooser,
+ const gchar *db_name);
+gboolean gdict_database_chooser_unselect_database (GdictDatabaseChooser *chooser,
+ const gchar *db_name);
+gboolean gdict_database_chooser_set_current_database (GdictDatabaseChooser *chooser,
+ const gchar *db_name);
+gchar * gdict_database_chooser_get_current_database (GdictDatabaseChooser *chooser) G_GNUC_MALLOC;
+gchar ** gdict_database_chooser_get_databases (GdictDatabaseChooser *chooser,
+ gsize *length) G_GNUC_MALLOC;
+gint gdict_database_chooser_count_databases (GdictDatabaseChooser *chooser);
+gboolean gdict_database_chooser_has_database (GdictDatabaseChooser *chooser,
+ const gchar *database);
+void gdict_database_chooser_refresh (GdictDatabaseChooser *chooser);
+void gdict_database_chooser_clear (GdictDatabaseChooser *chooser);
+GtkWidget * gdict_database_chooser_add_button (GdictDatabaseChooser *chooser,
+ const gchar *button_text);
+
+G_END_DECLS
+
+#endif /* __GDICT_DATABASE_CHOOSER_H__ */
diff --git a/mate-dictionary/libgdict/gdict-debug.h b/mate-dictionary/libgdict/gdict-debug.h
new file mode 100644
index 00000000..af234b1f
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-debug.h
@@ -0,0 +1,55 @@
+/* gdict-debug.h - Debug facilities for Gdict
+ *
+ * Copyright (C) 2006 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+#ifndef __GDICT_DEBUG_H__
+#define __GDICT_DEBUG_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ GDICT_DEBUG_MISC = 1 << 0,
+ GDICT_DEBUG_CONTEXT = 1 << 1,
+ GDICT_DEBUG_DICT = 1 << 2,
+ GDICT_DEBUG_SOURCE = 1 << 3,
+ GDICT_DEBUG_LOADER = 1 << 4,
+ GDICT_DEBUG_CHOOSER = 1 << 5,
+ GDICT_DEBUG_DEFBOX = 1 << 6,
+ GDICT_DEBUG_SPELLER = 1 << 7
+} GdictDebugFlags;
+
+#ifdef GDICT_ENABLE_DEBUG
+
+#define GDICT_NOTE(type,x,a...) G_STMT_START { \
+ if (gdict_debug_flags & GDICT_DEBUG_##type) { \
+ g_message ("[" #type "]: " G_STRLOC ": " x, ##a); \
+ } } G_STMT_END
+
+#else
+
+#define GDICT_NOTE(type,x,a...)
+
+#endif /* !GDICT_ENABLE_DEBUG */
+
+extern guint gdict_debug_flags;
+
+G_END_DECLS
+
+#endif /* __GDICT_DEBUG_H__ */
diff --git a/mate-dictionary/libgdict/gdict-defbox.c b/mate-dictionary/libgdict/gdict-defbox.c
new file mode 100644
index 00000000..0b739475
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-defbox.c
@@ -0,0 +1,2927 @@
+/* gdict-defbox.c - display widget for dictionary definitions
+ *
+ * Copyright (C) 2005-2006 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+/**
+ * SECTION:gdict-defbox
+ * @short_description: Display the list of definitions for a word
+ *
+ * The #GdictDefbox widget is a composite widget showing the list of
+ * definitions for a word. It queries the passed #GdictContext and displays
+ * the list of #GdictDefinition<!-- -->s obtained.
+ *
+ * It provides syntax highlighting, clickable links and an embedded find
+ * bar.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n-lib.h>
+
+#include "gdict-defbox.h"
+#include "gdict-utils.h"
+#include "gdict-debug.h"
+#include "gdict-private.h"
+#include "gdict-enum-types.h"
+#include "gdict-marshal.h"
+
+/**
+ * G_UNICODE_COMBINING_MARK was deprecated on glib 2.30
+ * use G_UNICODE_SPACING_MARK
+ */
+
+#if !GLIB_CHECK_VERSION(2, 29, 14)
+ #define G_UNICODE_SPACING_MARK G_UNICODE_COMBINING_MARK
+#endif
+
+#define QUERY_MARGIN 48
+#define ERROR_MARGIN 24
+
+typedef struct
+{
+ GdictDefinition *definition;
+
+ gint begin;
+} Definition;
+
+#define GDICT_DEFBOX_GET_PRIVATE(obj) \
+(G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDICT_TYPE_DEFBOX, GdictDefboxPrivate))
+
+struct _GdictDefboxPrivate
+{
+ GtkWidget *text_view;
+
+ /* the "find" pane */
+ GtkWidget *find_pane;
+ GtkWidget *find_entry;
+ GtkWidget *find_next;
+ GtkWidget *find_prev;
+ GtkWidget *find_label;
+
+ GtkWidget *progress_dialog;
+
+ GtkTextBuffer *buffer;
+
+ GdictContext *context;
+ GSList *definitions;
+
+ gchar *word;
+ gchar *database;
+ gchar *font_name;
+
+ guint show_find : 1;
+ guint is_searching : 1;
+ guint is_hovering : 1;
+
+ GdkCursor *busy_cursor;
+ GdkCursor *hand_cursor;
+ GdkCursor *regular_cursor;
+
+ guint start_id;
+ guint end_id;
+ guint define_id;
+ guint error_id;
+ guint hide_timeout;
+
+ GtkTextTag *link_tag;
+ GtkTextTag *visited_link_tag;
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_CONTEXT,
+ PROP_WORD,
+ PROP_DATABASE,
+ PROP_FONT_NAME,
+ PROP_COUNT
+};
+
+enum
+{
+ SHOW_FIND,
+ HIDE_FIND,
+ FIND_NEXT,
+ FIND_PREVIOUS,
+ LINK_CLICKED,
+
+ LAST_SIGNAL
+};
+
+static guint gdict_defbox_signals[LAST_SIGNAL] = { 0 };
+static GdkColor default_link_color = { 0, 0, 0, 0xeeee };
+static GdkColor default_visited_link_color = { 0, 0x5555, 0x1a1a, 0x8b8b };
+
+
+G_DEFINE_TYPE (GdictDefbox, gdict_defbox, GTK_TYPE_VBOX);
+
+
+static Definition *
+definition_new (void)
+{
+ Definition *def;
+
+ def = g_slice_new (Definition);
+ def->definition = NULL;
+ def->begin = -1;
+
+ return def;
+}
+
+static void
+definition_free (Definition *def)
+{
+ if (!def)
+ return;
+
+ gdict_definition_unref (def->definition);
+ g_slice_free (Definition, def);
+}
+
+static void
+gdict_defbox_dispose (GObject *gobject)
+{
+ GdictDefbox *defbox = GDICT_DEFBOX (gobject);
+ GdictDefboxPrivate *priv = defbox->priv;
+
+ if (priv->start_id)
+ {
+ g_signal_handler_disconnect (priv->context, priv->start_id);
+ g_signal_handler_disconnect (priv->context, priv->end_id);
+ g_signal_handler_disconnect (priv->context, priv->define_id);
+
+ priv->start_id = 0;
+ priv->end_id = 0;
+ priv->define_id = 0;
+ }
+
+ if (priv->error_id)
+ {
+ g_signal_handler_disconnect (priv->context, priv->error_id);
+ priv->error_id = 0;
+ }
+
+ if (priv->context)
+ {
+ g_object_unref (priv->context);
+ priv->context = NULL;
+ }
+
+ if (priv->buffer)
+ {
+ g_object_unref (priv->buffer);
+ priv->buffer = NULL;
+ }
+
+ if (priv->busy_cursor)
+ {
+ gdk_cursor_unref (priv->busy_cursor);
+ priv->busy_cursor = NULL;
+ }
+
+ if (priv->hand_cursor)
+ {
+ gdk_cursor_unref (priv->hand_cursor);
+ priv->hand_cursor = NULL;
+ }
+
+ if (priv->regular_cursor)
+ {
+ gdk_cursor_unref (priv->regular_cursor);
+ priv->regular_cursor = NULL;
+ }
+
+ G_OBJECT_CLASS (gdict_defbox_parent_class)->dispose (gobject);
+}
+
+static void
+gdict_defbox_finalize (GObject *object)
+{
+ GdictDefbox *defbox = GDICT_DEFBOX (object);
+ GdictDefboxPrivate *priv = defbox->priv;
+
+ g_free (priv->database);
+ g_free (priv->word);
+ g_free (priv->font_name);
+
+ if (priv->definitions)
+ {
+ g_slist_foreach (priv->definitions, (GFunc) definition_free, NULL);
+ g_slist_free (priv->definitions);
+
+ priv->definitions = NULL;
+ }
+
+ G_OBJECT_CLASS (gdict_defbox_parent_class)->finalize (object);
+}
+
+static void
+set_gdict_context (GdictDefbox *defbox,
+ GdictContext *context)
+{
+ GdictDefboxPrivate *priv;
+
+ g_assert (GDICT_IS_DEFBOX (defbox));
+
+ priv = defbox->priv;
+ if (priv->context)
+ {
+ if (priv->start_id)
+ {
+ GDICT_NOTE (DEFBOX, "Removing old context handlers");
+
+ g_signal_handler_disconnect (priv->context, priv->start_id);
+ g_signal_handler_disconnect (priv->context, priv->define_id);
+ g_signal_handler_disconnect (priv->context, priv->end_id);
+
+ priv->start_id = 0;
+ priv->end_id = 0;
+ priv->define_id = 0;
+ }
+
+ if (priv->error_id)
+ {
+ g_signal_handler_disconnect (priv->context, priv->error_id);
+
+ priv->error_id = 0;
+ }
+
+ GDICT_NOTE (DEFBOX, "Removing old context");
+
+ g_object_unref (G_OBJECT (priv->context));
+ }
+
+ if (!context)
+ return;
+
+ if (!GDICT_IS_CONTEXT (context))
+ {
+ g_warning ("Object of type '%s' instead of a GdictContext\n",
+ g_type_name (G_OBJECT_TYPE (context)));
+ return;
+ }
+
+ GDICT_NOTE (DEFBOX, "Setting new context");
+
+ priv->context = context;
+ g_object_ref (G_OBJECT (priv->context));
+}
+
+static void
+gdict_defbox_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GdictDefbox *defbox = GDICT_DEFBOX (object);
+ GdictDefboxPrivate *priv = defbox->priv;
+
+ switch (prop_id)
+ {
+ case PROP_WORD:
+ gdict_defbox_lookup (defbox, g_value_get_string (value));
+ break;
+ case PROP_CONTEXT:
+ set_gdict_context (defbox, g_value_get_object (value));
+ break;
+ case PROP_DATABASE:
+ g_free (priv->database);
+ priv->database = g_strdup (g_value_get_string (value));
+ break;
+ case PROP_FONT_NAME:
+ gdict_defbox_set_font_name (defbox, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gdict_defbox_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GdictDefbox *defbox = GDICT_DEFBOX (object);
+ GdictDefboxPrivate *priv = defbox->priv;
+
+ switch (prop_id)
+ {
+ case PROP_WORD:
+ g_value_set_string (value, priv->word);
+ break;
+ case PROP_CONTEXT:
+ g_value_set_object (value, priv->context);
+ break;
+ case PROP_DATABASE:
+ g_value_set_string (value, priv->database);
+ break;
+ case PROP_FONT_NAME:
+ g_value_set_string (value, priv->font_name);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/*
+ * this code has been copied from gtksourceview; it's the implementation
+ * for case-insensitive search in a GtkTextBuffer. this is non-trivial, as
+ * searches on a utf-8 text stream involve a norm(casefold(norm(utf8)))
+ * operation which can be costly on large buffers. luckily for us, it's
+ * not the case on a set of definitions.
+ */
+
+#define GTK_TEXT_UNKNOWN_CHAR 0xFFFC
+
+/* this function acts like g_utf8_offset_to_pointer() except that if it finds a
+ * decomposable character it consumes the decomposition length from the given
+ * offset. So it's useful when the offset was calculated for the normalized
+ * version of str, but we need a pointer to str itself. */
+static const gchar *
+pointer_from_offset_skipping_decomp (const gchar *str, gint offset)
+{
+ gchar *casefold, *normal;
+ const gchar *p, *q;
+
+ p = str;
+ while (offset > 0)
+ {
+ q = g_utf8_next_char (p);
+ casefold = g_utf8_casefold (p, q - p);
+ normal = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ offset -= g_utf8_strlen (normal, -1);
+ g_free (casefold);
+ g_free (normal);
+ p = q;
+ }
+ return p;
+}
+
+static gboolean
+exact_prefix_cmp (const gchar *string,
+ const gchar *prefix,
+ guint prefix_len)
+{
+ GUnicodeType type;
+
+ if (strncmp (string, prefix, prefix_len) != 0)
+ return FALSE;
+ if (string[prefix_len] == '\0')
+ return TRUE;
+
+ type = g_unichar_type (g_utf8_get_char (string + prefix_len));
+
+ /* If string contains prefix, check that prefix is not followed
+ * by a unicode mark symbol, e.g. that trailing 'a' in prefix
+ * is not part of two-char a-with-hat symbol in string. */
+ return type != G_UNICODE_SPACING_MARK &&
+ type != G_UNICODE_ENCLOSING_MARK &&
+ type != G_UNICODE_NON_SPACING_MARK;
+}
+
+static const gchar *
+utf8_strcasestr (const gchar *haystack, const gchar *needle)
+{
+ gsize needle_len;
+ gsize haystack_len;
+ const gchar *ret = NULL;
+ gchar *p;
+ gchar *casefold;
+ gchar *caseless_haystack;
+ gint i;
+
+ g_return_val_if_fail (haystack != NULL, NULL);
+ g_return_val_if_fail (needle != NULL, NULL);
+
+ casefold = g_utf8_casefold (haystack, -1);
+ caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ g_free (casefold);
+
+ needle_len = g_utf8_strlen (needle, -1);
+ haystack_len = g_utf8_strlen (caseless_haystack, -1);
+
+ if (needle_len == 0)
+ {
+ ret = (gchar *)haystack;
+ goto finally_1;
+ }
+
+ if (haystack_len < needle_len)
+ {
+ ret = NULL;
+ goto finally_1;
+ }
+
+ p = (gchar*)caseless_haystack;
+ needle_len = strlen (needle);
+ i = 0;
+
+ while (*p)
+ {
+ if (exact_prefix_cmp (p, needle, needle_len))
+ {
+ ret = pointer_from_offset_skipping_decomp (haystack, i);
+ goto finally_1;
+ }
+
+ p = g_utf8_next_char (p);
+ i++;
+ }
+
+finally_1:
+ g_free (caseless_haystack);
+
+ return ret;
+}
+
+static const gchar *
+utf8_strrcasestr (const gchar *haystack, const gchar *needle)
+{
+ gsize needle_len;
+ gsize haystack_len;
+ const gchar *ret = NULL;
+ gchar *p;
+ gchar *casefold;
+ gchar *caseless_haystack;
+ gint i;
+
+ g_return_val_if_fail (haystack != NULL, NULL);
+ g_return_val_if_fail (needle != NULL, NULL);
+
+ casefold = g_utf8_casefold (haystack, -1);
+ caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ g_free (casefold);
+
+ needle_len = g_utf8_strlen (needle, -1);
+ haystack_len = g_utf8_strlen (caseless_haystack, -1);
+
+ if (needle_len == 0)
+ {
+ ret = (gchar *)haystack;
+ goto finally_1;
+ }
+
+ if (haystack_len < needle_len)
+ {
+ ret = NULL;
+ goto finally_1;
+ }
+
+ i = haystack_len - needle_len;
+ p = g_utf8_offset_to_pointer (caseless_haystack, i);
+ needle_len = strlen (needle);
+
+ while (p >= caseless_haystack)
+ {
+ if (exact_prefix_cmp (p, needle, needle_len))
+ {
+ ret = pointer_from_offset_skipping_decomp (haystack, i);
+ goto finally_1;
+ }
+
+ p = g_utf8_prev_char (p);
+ i--;
+ }
+
+finally_1:
+ g_free (caseless_haystack);
+
+ return ret;
+}
+
+static gboolean
+utf8_caselessnmatch (const char *s1, const char *s2,
+ gssize n1, gssize n2)
+{
+ gchar *casefold;
+ gchar *normalized_s1;
+ gchar *normalized_s2;
+ gint len_s1;
+ gint len_s2;
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (s1 != NULL, FALSE);
+ g_return_val_if_fail (s2 != NULL, FALSE);
+ g_return_val_if_fail (n1 > 0, FALSE);
+ g_return_val_if_fail (n2 > 0, FALSE);
+
+ casefold = g_utf8_casefold (s1, n1);
+ normalized_s1 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ g_free (casefold);
+
+ casefold = g_utf8_casefold (s2, n2);
+ normalized_s2 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ g_free (casefold);
+
+ len_s1 = strlen (normalized_s1);
+ len_s2 = strlen (normalized_s2);
+
+ if (len_s1 < len_s2)
+ goto finally_2;
+
+ ret = (strncmp (normalized_s1, normalized_s2, len_s2) == 0);
+
+finally_2:
+ g_free (normalized_s1);
+ g_free (normalized_s2);
+
+ return ret;
+}
+
+/* FIXME: total horror */
+static gboolean
+char_is_invisible (const GtkTextIter *iter)
+{
+ GSList *tags;
+ gboolean invisible = FALSE;
+ tags = gtk_text_iter_get_tags (iter);
+ while (tags)
+ {
+ gboolean this_invisible, invisible_set;
+ g_object_get (tags->data, "invisible", &this_invisible,
+ "invisible-set", &invisible_set, NULL);
+ if (invisible_set)
+ invisible = this_invisible;
+ tags = g_slist_delete_link (tags, tags);
+ }
+ return invisible;
+}
+
+static void
+forward_chars_with_skipping (GtkTextIter *iter,
+ gint count,
+ gboolean skip_invisible,
+ gboolean skip_nontext,
+ gboolean skip_decomp)
+{
+ gint i;
+
+ g_return_if_fail (count >= 0);
+
+ i = count;
+
+ while (i > 0)
+ {
+ gboolean ignored = FALSE;
+
+ /* minimal workaround to avoid the infinite loop of bug #168247.
+ * It doesn't fix the problemjust the symptom...
+ */
+ if (gtk_text_iter_is_end (iter))
+ return;
+
+ if (skip_nontext && gtk_text_iter_get_char (iter) == GTK_TEXT_UNKNOWN_CHAR)
+ ignored = TRUE;
+
+ /* FIXME: char_is_invisible() gets list of tags for each char there,
+ and checks every tag. It doesn't sound like a good idea. */
+ if (!ignored && skip_invisible && char_is_invisible (iter))
+ ignored = TRUE;
+
+ if (!ignored && skip_decomp)
+ {
+ /* being UTF8 correct sucks; this accounts for extra
+ offsets coming from canonical decompositions of
+ UTF8 characters (e.g. accented characters) which
+ g_utf8_normalize() performs */
+ gchar *normal;
+ gchar buffer[6];
+ gint buffer_len;
+
+ buffer_len = g_unichar_to_utf8 (gtk_text_iter_get_char (iter), buffer);
+ normal = g_utf8_normalize (buffer, buffer_len, G_NORMALIZE_NFD);
+ i -= (g_utf8_strlen (normal, -1) - 1);
+ g_free (normal);
+ }
+
+ gtk_text_iter_forward_char (iter);
+
+ if (!ignored)
+ --i;
+ }
+}
+
+static gboolean
+lines_match (const GtkTextIter *start,
+ const gchar **lines,
+ gboolean visible_only,
+ gboolean slice,
+ GtkTextIter *match_start,
+ GtkTextIter *match_end)
+{
+ GtkTextIter next;
+ gchar *line_text;
+ const gchar *found;
+ gint offset;
+
+ if (*lines == NULL || **lines == '\0')
+ {
+ if (match_start)
+ *match_start = *start;
+ if (match_end)
+ *match_end = *start;
+ return TRUE;
+ }
+
+ next = *start;
+ gtk_text_iter_forward_line (&next);
+
+ /* No more text in buffer, but *lines is nonempty */
+ if (gtk_text_iter_equal (start, &next))
+ return FALSE;
+
+ if (slice)
+ {
+ if (visible_only)
+ line_text = gtk_text_iter_get_visible_slice (start, &next);
+ else
+ line_text = gtk_text_iter_get_slice (start, &next);
+ }
+ else
+ {
+ if (visible_only)
+ line_text = gtk_text_iter_get_visible_text (start, &next);
+ else
+ line_text = gtk_text_iter_get_text (start, &next);
+ }
+
+ if (match_start) /* if this is the first line we're matching */
+ {
+ found = utf8_strcasestr (line_text, *lines);
+ }
+ else
+ {
+ /* If it's not the first line, we have to match from the
+ * start of the line.
+ */
+ if (utf8_caselessnmatch (line_text, *lines, strlen (line_text),
+ strlen (*lines)))
+ found = line_text;
+ else
+ found = NULL;
+ }
+
+ if (found == NULL)
+ {
+ g_free (line_text);
+ return FALSE;
+ }
+
+ /* Get offset to start of search string */
+ offset = g_utf8_strlen (line_text, found - line_text);
+
+ next = *start;
+
+ /* If match start needs to be returned, set it to the
+ * start of the search string.
+ */
+ forward_chars_with_skipping (&next, offset, visible_only, !slice, FALSE);
+ if (match_start)
+ {
+ *match_start = next;
+ }
+
+ /* Go to end of search string */
+ forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), visible_only, !slice, TRUE);
+
+ g_free (line_text);
+
+ ++lines;
+
+ if (match_end)
+ *match_end = next;
+
+ /* pass NULL for match_start, since we don't need to find the
+ * start again.
+ */
+ return lines_match (&next, lines, visible_only, slice, NULL, match_end);
+}
+
+static gboolean
+backward_lines_match (const GtkTextIter *start,
+ const gchar **lines,
+ gboolean visible_only,
+ gboolean slice,
+ GtkTextIter *match_start,
+ GtkTextIter *match_end)
+{
+ GtkTextIter line, next;
+ gchar *line_text;
+ const gchar *found;
+ gint offset;
+
+ if (*lines == NULL || **lines == '\0')
+ {
+ if (match_start)
+ *match_start = *start;
+ if (match_end)
+ *match_end = *start;
+ return TRUE;
+ }
+
+ line = next = *start;
+ if (gtk_text_iter_get_line_offset (&next) == 0)
+ {
+ if (!gtk_text_iter_backward_line (&next))
+ return FALSE;
+ }
+ else
+ gtk_text_iter_set_line_offset (&next, 0);
+
+ if (slice)
+ {
+ if (visible_only)
+ line_text = gtk_text_iter_get_visible_slice (&next, &line);
+ else
+ line_text = gtk_text_iter_get_slice (&next, &line);
+ }
+ else
+ {
+ if (visible_only)
+ line_text = gtk_text_iter_get_visible_text (&next, &line);
+ else
+ line_text = gtk_text_iter_get_text (&next, &line);
+ }
+
+ if (match_start) /* if this is the first line we're matching */
+ {
+ found = utf8_strrcasestr (line_text, *lines);
+ }
+ else
+ {
+ /* If it's not the first line, we have to match from the
+ * start of the line.
+ */
+ if (utf8_caselessnmatch (line_text, *lines, strlen (line_text),
+ strlen (*lines)))
+ found = line_text;
+ else
+ found = NULL;
+ }
+
+ if (found == NULL)
+ {
+ g_free (line_text);
+ return FALSE;
+ }
+
+ /* Get offset to start of search string */
+ offset = g_utf8_strlen (line_text, found - line_text);
+
+ forward_chars_with_skipping (&next, offset, visible_only, !slice, FALSE);
+
+ /* If match start needs to be returned, set it to the
+ * start of the search string.
+ */
+ if (match_start)
+ {
+ *match_start = next;
+ }
+
+ /* Go to end of search string */
+ forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), visible_only, !slice, TRUE);
+
+ g_free (line_text);
+
+ ++lines;
+
+ if (match_end)
+ *match_end = next;
+
+ /* try to match the rest of the lines forward, passing NULL
+ * for match_start so lines_match will try to match the entire
+ * line */
+ return lines_match (&next, lines, visible_only,
+ slice, NULL, match_end);
+}
+
+/* strsplit () that retains the delimiter as part of the string. */
+static gchar **
+breakup_string (const char *string,
+ const char *delimiter,
+ gint max_tokens)
+{
+ GSList *string_list = NULL, *slist;
+ gchar **str_array, *s, *casefold, *new_string;
+ guint i, n = 1;
+
+ g_return_val_if_fail (string != NULL, NULL);
+ g_return_val_if_fail (delimiter != NULL, NULL);
+
+ if (max_tokens < 1)
+ max_tokens = G_MAXINT;
+
+ s = strstr (string, delimiter);
+ if (s)
+ {
+ guint delimiter_len = strlen (delimiter);
+
+ do
+ {
+ guint len;
+
+ len = s - string + delimiter_len;
+ new_string = g_new (gchar, len + 1);
+ strncpy (new_string, string, len);
+ new_string[len] = 0;
+ casefold = g_utf8_casefold (new_string, -1);
+ g_free (new_string);
+ new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ g_free (casefold);
+ string_list = g_slist_prepend (string_list, new_string);
+ n++;
+ string = s + delimiter_len;
+ s = strstr (string, delimiter);
+ } while (--max_tokens && s);
+ }
+
+ if (*string)
+ {
+ n++;
+ casefold = g_utf8_casefold (string, -1);
+ new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ g_free (casefold);
+ string_list = g_slist_prepend (string_list, new_string);
+ }
+
+ str_array = g_new (gchar*, n);
+
+ i = n - 1;
+
+ str_array[i--] = NULL;
+ for (slist = string_list; slist; slist = slist->next)
+ str_array[i--] = slist->data;
+
+ g_slist_free (string_list);
+
+ return str_array;
+}
+
+static gboolean
+gdict_defbox_iter_forward_search (const GtkTextIter *iter,
+ const gchar *str,
+ GtkTextIter *match_start,
+ GtkTextIter *match_end,
+ const GtkTextIter *limit)
+{
+ gchar **lines = NULL;
+ GtkTextIter match;
+ gboolean retval = FALSE;
+ GtkTextIter search;
+
+ g_return_val_if_fail (iter != NULL, FALSE);
+ g_return_val_if_fail (str != NULL, FALSE);
+
+ if (limit && gtk_text_iter_compare (iter, limit) >= 0)
+ return FALSE;
+
+ if (*str == '\0')
+ {
+ /* If we can move one char, return the empty string there */
+ match = *iter;
+
+ if (gtk_text_iter_forward_char (&match))
+ {
+ if (limit && gtk_text_iter_equal (&match, limit))
+ return FALSE;
+
+ if (match_start)
+ *match_start = match;
+
+ if (match_end)
+ *match_end = match;
+
+ return TRUE;
+ }
+ else
+ return FALSE;
+ }
+
+ /* locate all lines */
+ lines = breakup_string (str, "\n", -1);
+
+ search = *iter;
+
+ /* This loop has an inefficient worst-case, where
+ * gtk_text_iter_get_text () is called repeatedly on
+ * a single line.
+ */
+ do
+ {
+ GtkTextIter end;
+ gboolean res;
+
+ if (limit && gtk_text_iter_compare (&search, limit) >= 0)
+ break;
+
+ res = lines_match (&search, (const gchar**)lines,
+ TRUE, FALSE,
+ &match, &end);
+ if (res)
+ {
+ if (limit == NULL ||
+ (limit && gtk_text_iter_compare (&end, limit) <= 0))
+ {
+ retval = TRUE;
+
+ if (match_start)
+ *match_start = match;
+
+ if (match_end)
+ *match_end = end;
+ }
+
+ break;
+ }
+ } while (gtk_text_iter_forward_line (&search));
+
+ g_strfreev ((gchar**) lines);
+
+ return retval;
+}
+
+static gboolean
+gdict_defbox_iter_backward_search (const GtkTextIter *iter,
+ const gchar *str,
+ GtkTextIter *match_start,
+ GtkTextIter *match_end,
+ const GtkTextIter *limit)
+{
+ gchar **lines = NULL;
+ GtkTextIter match;
+ gboolean retval = FALSE;
+ GtkTextIter search;
+
+ g_return_val_if_fail (iter != NULL, FALSE);
+ g_return_val_if_fail (str != NULL, FALSE);
+
+ if (limit && gtk_text_iter_compare (iter, limit) <= 0)
+ return FALSE;
+
+ if (*str == '\0')
+ {
+ /* If we can move one char, return the empty string there */
+ match = *iter;
+
+ if (gtk_text_iter_backward_char (&match))
+ {
+ if (limit && gtk_text_iter_equal (&match, limit))
+ return FALSE;
+
+ if (match_start)
+ *match_start = match;
+
+ if (match_end)
+ *match_end = match;
+
+ return TRUE;
+ }
+ else
+ return FALSE;
+ }
+
+ /* locate all lines */
+ lines = breakup_string (str, "\n", -1);
+
+ search = *iter;
+
+ /* This loop has an inefficient worst-case, where
+ * gtk_text_iter_get_text () is called repeatedly on
+ * a single line.
+ */
+ while (TRUE)
+ {
+ GtkTextIter end;
+ gboolean res;
+
+ if (limit && gtk_text_iter_compare (&search, limit) <= 0)
+ break;
+
+ res = backward_lines_match (&search, (const gchar**)lines,
+ TRUE, FALSE,
+ &match, &end);
+ if (res)
+ {
+ if (limit == NULL ||
+ (limit && gtk_text_iter_compare (&end, limit) > 0))
+ {
+ retval = TRUE;
+
+ if (match_start)
+ *match_start = match;
+
+ if (match_end)
+ *match_end = end;
+
+ }
+
+ break;
+ }
+
+ if (gtk_text_iter_get_line_offset (&search) == 0)
+ {
+ if (!gtk_text_iter_backward_line (&search))
+ break;
+ }
+ else
+ gtk_text_iter_set_line_offset (&search, 0);
+ }
+
+ g_strfreev ((gchar**) lines);
+
+ return retval;
+}
+
+static gboolean
+gdict_defbox_find_backward (GdictDefbox *defbox,
+ const gchar *text)
+{
+ GdictDefboxPrivate *priv = defbox->priv;
+ GtkTextIter start_iter, end_iter;
+ GtkTextIter match_start, match_end;
+ GtkTextIter iter;
+ GtkTextMark *last_search;
+ gboolean res;
+
+ g_assert (GTK_IS_TEXT_BUFFER (priv->buffer));
+
+ gtk_text_buffer_get_bounds (priv->buffer, &start_iter, &end_iter);
+
+ /* if there already has been another result, begin from there */
+ last_search = gtk_text_buffer_get_mark (priv->buffer, "last-search-prev");
+ if (last_search)
+ gtk_text_buffer_get_iter_at_mark (priv->buffer, &iter, last_search);
+ else
+ iter = end_iter;
+
+ res = gdict_defbox_iter_backward_search (&iter, text,
+ &match_start, &match_end,
+ NULL);
+ if (res)
+ {
+ gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (priv->text_view),
+ &match_start,
+ 0.0,
+ TRUE,
+ 0.0, 0.0);
+ gtk_text_buffer_place_cursor (priv->buffer, &match_end);
+ gtk_text_buffer_move_mark (priv->buffer,
+ gtk_text_buffer_get_mark (priv->buffer, "selection_bound"),
+ &match_start);
+ gtk_text_buffer_create_mark (priv->buffer, "last-search-prev", &match_start, FALSE);
+ gtk_text_buffer_create_mark (priv->buffer, "last-search-next", &match_end, FALSE);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+hide_find_pane (gpointer user_data)
+{
+ GdictDefbox *defbox = user_data;
+
+ gtk_widget_hide (defbox->priv->find_pane);
+ defbox->priv->show_find = FALSE;
+
+ gtk_widget_grab_focus (defbox->priv->text_view);
+
+ defbox->priv->hide_timeout = 0;
+
+ return FALSE;
+}
+
+static void
+find_prev_clicked_cb (GtkWidget *widget,
+ gpointer user_data)
+{
+ GdictDefbox *defbox = GDICT_DEFBOX (user_data);
+ GdictDefboxPrivate *priv = defbox->priv;
+ const gchar *text;
+ gboolean found;
+
+ gtk_widget_hide (priv->find_label);
+
+ text = gtk_entry_get_text (GTK_ENTRY (priv->find_entry));
+ if (!text)
+ return;
+
+ found = gdict_defbox_find_backward (defbox, text);
+ if (!found)
+ {
+ gchar *str;
+
+ str = g_strconcat (" <i>", _("Not found"), "</i>", NULL);
+ gtk_label_set_markup (GTK_LABEL (priv->find_label), str);
+ gtk_widget_show (priv->find_label);
+
+ g_free (str);
+ }
+
+ if (priv->hide_timeout)
+ {
+ g_source_remove (priv->hide_timeout);
+ priv->hide_timeout = g_timeout_add (5000, hide_find_pane, defbox);
+ }
+}
+
+static gboolean
+gdict_defbox_find_forward (GdictDefbox *defbox,
+ const gchar *text,
+ gboolean is_typing)
+{
+ GdictDefboxPrivate *priv = defbox->priv;
+ GtkTextIter start_iter, end_iter;
+ GtkTextIter match_start, match_end;
+ GtkTextIter iter;
+ GtkTextMark *last_search;
+ gboolean res;
+
+ g_assert (GTK_IS_TEXT_BUFFER (priv->buffer));
+
+ gtk_text_buffer_get_bounds (priv->buffer, &start_iter, &end_iter);
+
+ if (!is_typing)
+ {
+ /* if there already has been another result, begin from there */
+ last_search = gtk_text_buffer_get_mark (priv->buffer, "last-search-next");
+
+ if (last_search)
+ gtk_text_buffer_get_iter_at_mark (priv->buffer, &iter, last_search);
+ else
+ iter = start_iter;
+ }
+ else
+ {
+ last_search = gtk_text_buffer_get_mark (priv->buffer, "last-search-prev");
+
+ if (last_search)
+ gtk_text_buffer_get_iter_at_mark (priv->buffer, &iter, last_search);
+ else
+ iter = start_iter;
+ }
+
+ res = gdict_defbox_iter_forward_search (&iter, text,
+ &match_start,
+ &match_end,
+ NULL);
+ if (res)
+ {
+ gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (priv->text_view),
+ &match_start,
+ 0.0,
+ TRUE,
+ 0.0, 0.0);
+ gtk_text_buffer_place_cursor (priv->buffer, &match_end);
+ gtk_text_buffer_move_mark (priv->buffer,
+ gtk_text_buffer_get_mark (priv->buffer, "selection_bound"),
+ &match_start);
+ gtk_text_buffer_create_mark (priv->buffer, "last-search-prev", &match_start, FALSE);
+ gtk_text_buffer_create_mark (priv->buffer, "last-search-next", &match_end, FALSE);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+find_next_clicked_cb (GtkWidget *widget,
+ gpointer user_data)
+{
+ GdictDefbox *defbox = GDICT_DEFBOX (user_data);
+ GdictDefboxPrivate *priv = defbox->priv;
+ const gchar *text;
+ gboolean found;
+
+ gtk_widget_hide (priv->find_label);
+
+ text = gtk_entry_get_text (GTK_ENTRY (priv->find_entry));
+ if (!text)
+ return;
+
+ found = gdict_defbox_find_forward (defbox, text, FALSE);
+ if (!found)
+ {
+ gchar *str;
+
+ str = g_strconcat (" <i>", _("Not found"), "</i>", NULL);
+ gtk_label_set_markup (GTK_LABEL (priv->find_label), str);
+ gtk_widget_show (priv->find_label);
+
+ g_free (str);
+ }
+
+ if (priv->hide_timeout)
+ {
+ g_source_remove (priv->hide_timeout);
+ priv->hide_timeout = g_timeout_add (5000, hide_find_pane, defbox);
+ }
+}
+
+static void
+find_entry_changed_cb (GtkWidget *widget,
+ gpointer user_data)
+{
+ GdictDefbox *defbox = GDICT_DEFBOX (user_data);
+ GdictDefboxPrivate *priv = defbox->priv;
+ gchar *text;
+ gboolean found;
+
+ gtk_widget_hide (priv->find_label);
+
+ text = gtk_editable_get_chars (GTK_EDITABLE (widget), 0, -1);
+ if (!text)
+ return;
+
+ found = gdict_defbox_find_forward (defbox, text, TRUE);
+ if (!found)
+ {
+ gchar *str;
+
+ str = g_strconcat (" <i>", _("Not found"), "</i>", NULL);
+ gtk_label_set_markup (GTK_LABEL (priv->find_label), str);
+ gtk_widget_show (priv->find_label);
+
+ g_free (str);
+ }
+
+ g_free (text);
+
+ if (priv->hide_timeout)
+ {
+ g_source_remove (priv->hide_timeout);
+ priv->hide_timeout = g_timeout_add (5000, hide_find_pane, defbox);
+ }
+}
+
+static void
+close_button_clicked (GtkButton *button,
+ gpointer data)
+{
+ GdictDefboxPrivate *priv = GDICT_DEFBOX (data)->priv;
+
+ if (priv->hide_timeout)
+ g_source_remove (priv->hide_timeout);
+
+ (void) hide_find_pane (data);
+}
+
+static GtkWidget *
+create_find_pane (GdictDefbox *defbox)
+{
+ GdictDefboxPrivate *priv;
+ GtkWidget *find_pane;
+ GtkWidget *label;
+ GtkWidget *sep;
+ GtkWidget *hbox1, *hbox2;
+ GtkWidget *button;
+
+ priv = defbox->priv;
+
+ find_pane = gtk_hbox_new (FALSE, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (find_pane), 0);
+
+ hbox1 = gtk_hbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (find_pane), hbox1, TRUE, TRUE, 0);
+ gtk_widget_show (hbox1);
+
+ button = gtk_button_new ();
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ gtk_button_set_image (GTK_BUTTON (button),
+ gtk_image_new_from_stock (GTK_STOCK_CLOSE,
+ GTK_ICON_SIZE_BUTTON));
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (close_button_clicked), defbox);
+ gtk_box_pack_start (GTK_BOX (hbox1), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ hbox2 = gtk_hbox_new (FALSE, 12);
+ gtk_box_pack_start (GTK_BOX (hbox1), hbox2, TRUE, TRUE, 0);
+ gtk_widget_show (hbox2);
+
+ label = gtk_label_new_with_mnemonic (_("F_ind:"));
+ gtk_box_pack_start (GTK_BOX (hbox2), label, FALSE, FALSE, 0);
+
+ priv->find_entry = gtk_entry_new ();
+ g_signal_connect (priv->find_entry, "changed",
+ G_CALLBACK (find_entry_changed_cb), defbox);
+ gtk_box_pack_start (GTK_BOX (hbox2), priv->find_entry, TRUE, TRUE, 0);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), priv->find_entry);
+
+ sep = gtk_vseparator_new ();
+ gtk_box_pack_start (GTK_BOX (hbox1), sep, FALSE, FALSE, 0);
+ gtk_widget_show (sep);
+
+ priv->find_prev = gtk_button_new_with_mnemonic (_("_Previous"));
+ gtk_button_set_image (GTK_BUTTON (priv->find_prev),
+ gtk_image_new_from_stock (GTK_STOCK_GO_BACK,
+ GTK_ICON_SIZE_MENU));
+ g_signal_connect (priv->find_prev, "clicked",
+ G_CALLBACK (find_prev_clicked_cb), defbox);
+ gtk_box_pack_start (GTK_BOX (hbox1), priv->find_prev, FALSE, FALSE, 0);
+
+ priv->find_next = gtk_button_new_with_mnemonic (_("_Next"));
+ gtk_button_set_image (GTK_BUTTON (priv->find_next),
+ gtk_image_new_from_stock (GTK_STOCK_GO_FORWARD,
+ GTK_ICON_SIZE_MENU));
+ g_signal_connect (priv->find_next, "clicked",
+ G_CALLBACK (find_next_clicked_cb), defbox);
+ gtk_box_pack_start (GTK_BOX (hbox1), priv->find_next, FALSE, FALSE, 0);
+
+ priv->find_label = gtk_label_new (NULL);
+ gtk_label_set_use_markup (GTK_LABEL (priv->find_label), TRUE);
+ gtk_box_pack_end (GTK_BOX (find_pane), priv->find_label, FALSE, FALSE, 0);
+ gtk_widget_hide (priv->find_label);
+
+ return find_pane;
+}
+
+static void
+gdict_defbox_init_tags (GdictDefbox *defbox)
+{
+ GdictDefboxPrivate *priv = defbox->priv;
+ GdkColor *link_color, *visited_link_color;
+
+ g_assert (GTK_IS_TEXT_BUFFER (priv->buffer));
+
+ gtk_text_buffer_create_tag (priv->buffer, "italic",
+ "style", PANGO_STYLE_ITALIC,
+ NULL);
+ gtk_text_buffer_create_tag (priv->buffer, "bold",
+ "weight", PANGO_WEIGHT_BOLD,
+ NULL);
+ gtk_text_buffer_create_tag (priv->buffer, "underline",
+ "underline", PANGO_UNDERLINE_SINGLE,
+ NULL);
+
+ gtk_text_buffer_create_tag (priv->buffer, "big",
+ "scale", 1.6,
+ NULL);
+ gtk_text_buffer_create_tag (priv->buffer, "small",
+ "scale", PANGO_SCALE_SMALL,
+ NULL);
+
+ link_color = visited_link_color = NULL;
+ gtk_widget_style_get (GTK_WIDGET (defbox),
+ "link-color", &link_color,
+ "visited-link-color", &visited_link_color,
+ NULL);
+ if (!link_color)
+ link_color = &default_link_color;
+
+ if (!visited_link_color)
+ visited_link_color = &default_visited_link_color;
+
+ priv->link_tag =
+ gtk_text_buffer_create_tag (priv->buffer, "link",
+ "underline", PANGO_UNDERLINE_SINGLE,
+ "foreground-gdk", link_color,
+ NULL);
+ priv->visited_link_tag =
+ gtk_text_buffer_create_tag (priv->buffer, "visited-link",
+ "underline", PANGO_UNDERLINE_SINGLE,
+ "foreground-gdk", visited_link_color,
+ NULL);
+
+ if (link_color != &default_link_color)
+ gdk_color_free (link_color);
+
+ if (visited_link_color != &default_visited_link_color)
+ gdk_color_free (visited_link_color);
+
+ gtk_text_buffer_create_tag (priv->buffer, "phonetic",
+ "foreground", "dark gray",
+ NULL);
+
+ gtk_text_buffer_create_tag (priv->buffer, "query-title",
+ "left-margin", QUERY_MARGIN,
+ "pixels-above-lines", 5,
+ "pixels-below-lines", 20,
+ NULL);
+ gtk_text_buffer_create_tag (priv->buffer, "query-from",
+ "foreground", "dark gray",
+ "scale", PANGO_SCALE_SMALL,
+ "left-margin", QUERY_MARGIN,
+ "pixels-above-lines", 5,
+ "pixels-below-lines", 10,
+ NULL);
+
+ gtk_text_buffer_create_tag (priv->buffer, "error-title",
+ "foreground", "dark red",
+ "left-margin", ERROR_MARGIN,
+ NULL);
+ gtk_text_buffer_create_tag (priv->buffer, "error-message",
+ "left-margin", ERROR_MARGIN,
+ NULL);
+}
+
+static void
+follow_if_is_link (GdictDefbox *defbox,
+ GtkTextView *text_view,
+ GtkTextIter *iter)
+{
+ GSList *tags, *l;
+
+ tags = gtk_text_iter_get_tags (iter);
+
+ for (l = tags; l != NULL; l = l->next)
+ {
+ GtkTextTag *tag = l->data;
+ gchar *name;
+
+ g_object_get (G_OBJECT (tag), "name", &name, NULL);
+ if (name &&
+ (strcmp (name, "link") == 0 ||
+ strcmp (name, "visited-link") == 0))
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (text_view);
+ GtkTextIter start, end;
+ gchar *link_str;
+
+ start = *iter;
+ end = *iter;
+
+ gtk_text_iter_backward_to_tag_toggle (&start, tag);
+ gtk_text_iter_forward_to_tag_toggle (&end, tag);
+
+ link_str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+
+ g_signal_emit (defbox, gdict_defbox_signals[LINK_CLICKED], 0, link_str);
+
+ g_free (link_str);
+ g_free (name);
+
+ break;
+ }
+
+ g_free (name);
+ }
+
+ g_slist_free (tags);
+}
+
+static gboolean
+defbox_event_after_cb (GtkWidget *text_view,
+ GdkEvent *event,
+ GdictDefbox *defbox)
+{
+ GtkTextIter iter;
+ GtkTextBuffer *buffer;
+ GdkEventButton *button_event;
+ gint bx, by;
+
+ if (event->type != GDK_BUTTON_RELEASE)
+ return FALSE;
+
+ button_event = (GdkEventButton *) event;
+
+ if (button_event->button != 1)
+ return FALSE;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
+ if (gtk_text_buffer_get_has_selection (buffer))
+ return FALSE;
+
+ gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
+ GTK_TEXT_WINDOW_WIDGET,
+ button_event->x, button_event->y,
+ &bx, &by);
+
+ gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (text_view),
+ &iter,
+ bx, by);
+
+ follow_if_is_link (defbox, GTK_TEXT_VIEW (text_view), &iter);
+
+ return FALSE;
+}
+
+static void
+set_cursor_if_appropriate (GdictDefbox *defbox,
+ GtkTextView *text_view,
+ gint x,
+ gint y)
+{
+ GdictDefboxPrivate *priv;
+ GSList *tags, *l;
+ GtkTextIter iter;
+ gboolean hovering = FALSE;
+
+ priv = defbox->priv;
+
+ if (!priv->hand_cursor)
+ priv->hand_cursor = gdk_cursor_new (GDK_HAND2);
+
+ if (!priv->regular_cursor)
+ priv->regular_cursor = gdk_cursor_new (GDK_XTERM);
+
+ gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
+
+ tags = gtk_text_iter_get_tags (&iter);
+ for (l = tags; l != NULL; l = l->next)
+ {
+ GtkTextTag *tag = l->data;
+ gchar *name;
+
+ g_object_get (G_OBJECT (tag), "name", &name, NULL);
+ if (name &&
+ (strcmp (name, "link") == 0 ||
+ strcmp (name, "visited-link") == 0))
+ {
+ hovering = TRUE;
+ g_free (name);
+
+ break;
+ }
+
+ g_free (name);
+ }
+
+ if (hovering != defbox->priv->is_hovering)
+ {
+ defbox->priv->is_hovering = hovering;
+
+ if (defbox->priv->is_hovering)
+ gdk_window_set_cursor (gtk_text_view_get_window (text_view,
+ GTK_TEXT_WINDOW_TEXT),
+ defbox->priv->hand_cursor);
+ else
+ gdk_window_set_cursor (gtk_text_view_get_window (text_view,
+ GTK_TEXT_WINDOW_TEXT),
+ defbox->priv->regular_cursor);
+ }
+
+ if (tags)
+ g_slist_free (tags);
+}
+
+static gboolean
+defbox_motion_notify_cb (GtkWidget *text_view,
+ GdkEventMotion *event,
+ GdictDefbox *defbox)
+{
+ gint bx, by;
+
+ gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
+ GTK_TEXT_WINDOW_WIDGET,
+ event->x, event->y,
+ &bx, &by);
+
+ set_cursor_if_appropriate (defbox, GTK_TEXT_VIEW (text_view), bx, by);
+
+ gdk_window_get_pointer (gtk_widget_get_window (text_view), NULL, NULL, NULL);
+
+ return FALSE;
+}
+
+static gboolean
+defbox_visibility_notify_cb (GtkWidget *text_view,
+ GdkEventVisibility *event,
+ GdictDefbox *defbox)
+{
+ gint wx, wy;
+ gint bx, by;
+
+ gdk_window_get_pointer (gtk_widget_get_window (text_view), &wx, &wy, NULL);
+
+ gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
+ GTK_TEXT_WINDOW_WIDGET,
+ wx, wy,
+ &bx, &by);
+
+ set_cursor_if_appropriate (defbox, GTK_TEXT_VIEW (text_view), bx, by);
+
+ return FALSE;
+}
+
+static GObject *
+gdict_defbox_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_params)
+{
+ GdictDefbox *defbox;
+ GdictDefboxPrivate *priv;
+ GObject *object;
+ GtkWidget *sw;
+
+ object = G_OBJECT_CLASS (gdict_defbox_parent_class)->constructor (type,
+ n_construct_properties,
+ construct_params);
+ defbox = GDICT_DEFBOX (object);
+ priv = defbox->priv;
+
+ gtk_widget_push_composite_child ();
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_widget_set_composite_name (sw, "gdict-defbox-scrolled-window");
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+ GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (defbox), sw, TRUE, TRUE, 0);
+ gtk_widget_show (sw);
+
+ priv->buffer = gtk_text_buffer_new (NULL);
+ gdict_defbox_init_tags (defbox);
+
+ priv->text_view = gtk_text_view_new_with_buffer (priv->buffer);
+ gtk_widget_set_composite_name (priv->text_view, "gdict-defbox-text-view");
+ gtk_text_view_set_editable (GTK_TEXT_VIEW (priv->text_view), FALSE);
+ gtk_text_view_set_left_margin (GTK_TEXT_VIEW (priv->text_view), 4);
+ gtk_container_add (GTK_CONTAINER (sw), priv->text_view);
+ gtk_widget_show (priv->text_view);
+
+ priv->find_pane = create_find_pane (defbox);
+ gtk_widget_set_composite_name (priv->find_pane, "gdict-defbox-find-pane");
+ gtk_box_pack_end (GTK_BOX (defbox), priv->find_pane, FALSE, FALSE, 0);
+
+ /* stuff to make the link machinery work */
+ g_signal_connect (priv->text_view, "event-after",
+ G_CALLBACK (defbox_event_after_cb),
+ defbox);
+ g_signal_connect (priv->text_view, "motion-notify-event",
+ G_CALLBACK (defbox_motion_notify_cb),
+ defbox);
+ g_signal_connect (priv->text_view, "visibility-notify-event",
+ G_CALLBACK (defbox_visibility_notify_cb),
+ defbox);
+
+ gtk_widget_pop_composite_child ();
+
+ return object;
+}
+
+static void
+gdict_defbox_style_set (GtkWidget *widget,
+ GtkStyle *old_style)
+{
+ GdictDefboxPrivate *priv = GDICT_DEFBOX (widget)->priv;
+ GdkColor *link_color, *visited_link_color;
+
+ if (GTK_WIDGET_CLASS (gdict_defbox_parent_class)->style_set)
+ GTK_WIDGET_CLASS (gdict_defbox_parent_class)->style_set (widget, old_style);
+
+ link_color = visited_link_color = NULL;
+ gtk_widget_style_get (widget,
+ "link-color", &link_color,
+ "visited-link-color", &visited_link_color,
+ NULL);
+ if (!link_color)
+ link_color = &default_link_color;
+
+ if (!visited_link_color)
+ visited_link_color = &default_visited_link_color;
+
+ g_object_set (G_OBJECT (priv->link_tag),
+ "foreground-gdk", link_color,
+ NULL);
+
+ g_object_set (G_OBJECT (priv->visited_link_tag),
+ "foreground-gdk", visited_link_color,
+ NULL);
+
+ if (link_color != &default_link_color)
+ gdk_color_free (link_color);
+
+ if (visited_link_color != &default_visited_link_color)
+ gdk_color_free (visited_link_color);
+}
+
+/* we override the GtkWidget::show_all method since we have widgets
+ * we don't want to show, such as the find pane
+ */
+static void
+gdict_defbox_show_all (GtkWidget *widget)
+{
+ GdictDefbox *defbox = GDICT_DEFBOX (widget);
+ GdictDefboxPrivate *priv = defbox->priv;
+
+ gtk_widget_show (widget);
+
+ if (priv->show_find)
+ gtk_widget_show_all (priv->find_pane);
+}
+
+static void
+gdict_defbox_real_show_find (GdictDefbox *defbox)
+{
+ gtk_widget_show_all (defbox->priv->find_pane);
+ defbox->priv->show_find = TRUE;
+
+ gtk_widget_grab_focus (defbox->priv->find_entry);
+
+ defbox->priv->hide_timeout = g_timeout_add (5000, hide_find_pane, defbox);
+}
+
+static void
+gdict_defbox_real_find_next (GdictDefbox *defbox)
+{
+ /* synthetize a "clicked" signal to the "next" button */
+ gtk_button_clicked (GTK_BUTTON (defbox->priv->find_next));
+}
+
+static void
+gdict_defbox_real_find_previous (GdictDefbox *defbox)
+{
+ /* synthetize a "clicked" signal to the "prev" button */
+ gtk_button_clicked (GTK_BUTTON (defbox->priv->find_prev));
+}
+
+static void
+gdict_defbox_real_hide_find (GdictDefbox *defbox)
+{
+ gtk_widget_hide (defbox->priv->find_pane);
+ defbox->priv->show_find = FALSE;
+
+ gtk_widget_grab_focus (defbox->priv->text_view);
+
+ if (defbox->priv->hide_timeout)
+ {
+ g_source_remove (defbox->priv->hide_timeout);
+ defbox->priv->hide_timeout = 0;
+ }
+}
+
+static void
+gdict_defbox_class_init (GdictDefboxClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkBindingSet *binding_set;
+
+ gobject_class->constructor = gdict_defbox_constructor;
+ gobject_class->set_property = gdict_defbox_set_property;
+ gobject_class->get_property = gdict_defbox_get_property;
+ gobject_class->dispose = gdict_defbox_dispose;
+ gobject_class->finalize = gdict_defbox_finalize;
+
+ widget_class->show_all = gdict_defbox_show_all;
+ widget_class->style_set = gdict_defbox_style_set;
+
+ /**
+ * GdictDefbox:word:
+ *
+ * The word to look up.
+ *
+ * Since: 0.10
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_WORD,
+ g_param_spec_string ("word",
+ "Word",
+ "The word to look up",
+ NULL,
+ G_PARAM_READWRITE));
+ /**
+ * GdictDefbox:context:
+ *
+ * The #GdictContext object used to get the word definition.
+ *
+ * Since: 0.1
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_CONTEXT,
+ g_param_spec_object ("context",
+ "Context",
+ "The GdictContext object used to get the word definition",
+ GDICT_TYPE_CONTEXT,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)));
+ /**
+ * GdictDefbox:database
+ *
+ * The database used by the #GdictDefbox bound to this object to get the word
+ * definition.
+ *
+ * Since: 0.1
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_DATABASE,
+ g_param_spec_string ("database",
+ "Database",
+ "The database used to query the GdictContext",
+ GDICT_DEFAULT_DATABASE,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ /**
+ * GdictDefbox:font-name
+ *
+ * The name of the font used by the #GdictDefbox to display the definitions.
+ * use the same string you use for pango_font_description_from_string().
+ *
+ * Since: 0.3
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_FONT_NAME,
+ g_param_spec_string ("font-name",
+ "Font Name",
+ "The font to be used by the defbox",
+ GDICT_DEFAULT_FONT_NAME,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+
+ gdict_defbox_signals[SHOW_FIND] =
+ g_signal_new ("show-find",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GdictDefboxClass, show_find),
+ NULL, NULL,
+ gdict_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ gdict_defbox_signals[FIND_PREVIOUS] =
+ g_signal_new ("find-previous",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GdictDefboxClass, find_previous),
+ NULL, NULL,
+ gdict_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ gdict_defbox_signals[FIND_NEXT] =
+ g_signal_new ("find-next",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GdictDefboxClass, find_next),
+ NULL, NULL,
+ gdict_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ gdict_defbox_signals[HIDE_FIND] =
+ g_signal_new ("hide-find",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GdictDefboxClass, hide_find),
+ NULL, NULL,
+ gdict_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ gdict_defbox_signals[LINK_CLICKED] =
+ g_signal_new ("link-clicked",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdictDefboxClass, link_clicked),
+ NULL, NULL,
+ gdict_marshal_VOID__STRING,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+
+ klass->show_find = gdict_defbox_real_show_find;
+ klass->hide_find = gdict_defbox_real_hide_find;
+ klass->find_next = gdict_defbox_real_find_next;
+ klass->find_previous = gdict_defbox_real_find_previous;
+
+ binding_set = gtk_binding_set_by_class (klass);
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_f, GDK_CONTROL_MASK,
+ "show-find",
+ 0);
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_g, GDK_CONTROL_MASK,
+ "find-next",
+ 0);
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_g, GDK_SHIFT_MASK | GDK_CONTROL_MASK,
+ "find-previous",
+ 0);
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_Escape, 0,
+ "hide-find",
+ 0);
+
+ g_type_class_add_private (klass, sizeof (GdictDefboxPrivate));
+}
+
+static void
+gdict_defbox_init (GdictDefbox *defbox)
+{
+ GdictDefboxPrivate *priv;
+
+ gtk_box_set_spacing (GTK_BOX (defbox), 6);
+
+ priv = GDICT_DEFBOX_GET_PRIVATE (defbox);
+ defbox->priv = priv;
+
+ priv->context = NULL;
+ priv->database = g_strdup (GDICT_DEFAULT_DATABASE);
+ priv->font_name = g_strdup (GDICT_DEFAULT_FONT_NAME);
+ priv->word = NULL;
+
+ priv->definitions = NULL;
+
+ priv->busy_cursor = NULL;
+ priv->hand_cursor = NULL;
+ priv->regular_cursor = NULL;
+
+ priv->show_find = FALSE;
+ priv->is_searching = FALSE;
+ priv->is_hovering = FALSE;
+
+ priv->hide_timeout = 0;
+}
+
+/**
+ * gdict_defbox_new:
+ *
+ * Creates a new #GdictDefbox widget. Use this widget to search for
+ * a word using a #GdictContext, and to show the resulting definition(s).
+ * You must set a #GdictContext for this widget using
+ * gdict_defbox_set_context().
+ *
+ * Return value: a new #GdictDefbox widget.
+ *
+ * Since: 0.1
+ */
+GtkWidget *
+gdict_defbox_new (void)
+{
+ return g_object_new (GDICT_TYPE_DEFBOX, NULL);
+}
+
+/**
+ * gdict_defbox_new_with_context:
+ * @context: a #GdictContext
+ *
+ * Creates a new #GdictDefbox widget. Use this widget to search for
+ * a word using @context, and to show the resulting definition.
+ *
+ * Return value: a new #GdictDefbox widget.
+ *
+ * Since: 0.1
+ */
+GtkWidget *
+gdict_defbox_new_with_context (GdictContext *context)
+{
+ g_return_val_if_fail (GDICT_IS_CONTEXT (context), NULL);
+
+ return g_object_new (GDICT_TYPE_DEFBOX, "context", context, NULL);
+}
+
+/**
+ * gdict_defbox_set_context:
+ * @defbox: a #GdictDefbox
+ * @context: a #GdictContext
+ *
+ * Sets @context as the #GdictContext to be used by @defbox in order
+ * to retrieve the definitions of a word.
+ *
+ * Since: 0.1
+ */
+void
+gdict_defbox_set_context (GdictDefbox *defbox,
+ GdictContext *context)
+{
+ g_return_if_fail (GDICT_IS_DEFBOX (defbox));
+ g_return_if_fail (context == NULL || GDICT_IS_CONTEXT (context));
+
+ g_object_set (defbox, "context", context, NULL);
+}
+
+/**
+ * gdict_defbox_get_context:
+ * @defbox: a #GdictDefbox
+ *
+ * Gets the #GdictContext used by @defbox.
+ *
+ * Return value: a #GdictContext.
+ *
+ * Since: 0.1
+ */
+GdictContext *
+gdict_defbox_get_context (GdictDefbox *defbox)
+{
+ g_return_val_if_fail (GDICT_IS_DEFBOX (defbox), NULL);
+
+ return defbox->priv->context;
+}
+
+/**
+ * gdict_defbox_set_database:
+ * @defbox: a #GdictDefbox
+ * @database: a database
+ *
+ * Sets @database as the database used by the #GdictContext bound to @defbox
+ * to query for word definitions.
+ *
+ * Since: 0.1
+ */
+void
+gdict_defbox_set_database (GdictDefbox *defbox,
+ const gchar *database)
+{
+ GdictDefboxPrivate *priv;
+
+ g_return_if_fail (GDICT_IS_DEFBOX (defbox));
+
+ priv = defbox->priv;
+
+ g_free (priv->database);
+ priv->database = g_strdup (database);
+
+ g_object_notify (G_OBJECT (defbox), "database");
+}
+
+/**
+ * gdict_defbox_get_database:
+ * @defbox: a #GdictDefbox
+ *
+ * Gets the database used by @defbox. See gdict_defbox_set_database().
+ *
+ * Return value: the name of a database. The return string is owned by
+ * the #GdictDefbox widget and should not be modified or freed.
+ *
+ * Since: 0.1
+ */
+const gchar *
+gdict_defbox_get_database (GdictDefbox *defbox)
+{
+ g_return_val_if_fail (GDICT_IS_DEFBOX (defbox), NULL);
+
+ return defbox->priv->database;
+}
+
+/**
+ * gdict_defbox_get_word:
+ * @defbox: a #GdictDefbox
+ *
+ * Retrieves the word being looked up.
+ *
+ * Return value: the word looked up, or %NULL. The returned string is
+ * owned by the #GdictDefbox widget and should never be modified or
+ * freed.
+ *
+ * Since: 0.12
+ */
+const gchar *
+gdict_defbox_get_word (GdictDefbox *defbox)
+{
+ g_return_val_if_fail (GDICT_IS_DEFBOX (defbox), NULL);
+
+ return defbox->priv->word;
+}
+
+/**
+ * gdict_defbox_set_show_find:
+ * @defbox: a #GdictDefbox
+ * @show_find: %TRUE to show the find pane
+ *
+ * Whether @defbox should show the find pane.
+ *
+ * Since: 0.1
+ */
+void
+gdict_defbox_set_show_find (GdictDefbox *defbox,
+ gboolean show_find)
+{
+ GdictDefboxPrivate *priv;
+
+ g_return_if_fail (GDICT_IS_DEFBOX (defbox));
+
+ priv = defbox->priv;
+
+ if (priv->show_find == show_find)
+ return;
+
+ priv->show_find = show_find;
+ if (priv->show_find)
+ {
+ gtk_widget_show_all (priv->find_pane);
+ gtk_widget_grab_focus (priv->find_entry);
+
+ if (!priv->hide_timeout)
+ priv->hide_timeout = g_timeout_add (5000, hide_find_pane, defbox);
+ }
+ else
+ {
+ gtk_widget_hide (priv->find_pane);
+
+ if (priv->hide_timeout)
+ {
+ g_source_remove (priv->hide_timeout);
+ priv->hide_timeout = 0;
+ }
+ }
+}
+
+/**
+ * gdict_defbox_get_show_find:
+ * @defbox: a #GdictDefbox
+ *
+ * Gets whether the find pane should be visible or not.
+ *
+ * Return value: %TRUE if the find pane is visible.
+ *
+ * Since: 0.1
+ */
+gboolean
+gdict_defbox_get_show_find (GdictDefbox *defbox)
+{
+ g_return_val_if_fail (GDICT_IS_DEFBOX (defbox), FALSE);
+
+ return (defbox->priv->show_find == TRUE);
+}
+
+static void
+lookup_start_cb (GdictContext *context,
+ gpointer user_data)
+{
+ GdictDefbox *defbox = GDICT_DEFBOX (user_data);
+ GdictDefboxPrivate *priv = defbox->priv;
+ GdkWindow *window;
+
+ priv->is_searching = TRUE;
+
+ if (!priv->busy_cursor)
+ priv->busy_cursor = gdk_cursor_new (GDK_WATCH);
+
+ window = gtk_text_view_get_window (GTK_TEXT_VIEW (priv->text_view),
+ GTK_TEXT_WINDOW_WIDGET);
+
+ gdk_window_set_cursor (window, priv->busy_cursor);
+}
+
+static void
+lookup_end_cb (GdictContext *context,
+ gpointer user_data)
+{
+ GdictDefbox *defbox = GDICT_DEFBOX (user_data);
+ GdictDefboxPrivate *priv = defbox->priv;
+ GtkTextBuffer *buffer;
+ GtkTextIter start;
+ GdkWindow *window;
+
+ /* explicitely move the cursor to the beginning */
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
+ gtk_text_buffer_get_start_iter (buffer, &start);
+ gtk_text_buffer_place_cursor (buffer, &start);
+
+ window = gtk_text_view_get_window (GTK_TEXT_VIEW (priv->text_view),
+ GTK_TEXT_WINDOW_WIDGET);
+
+ gdk_window_set_cursor (window, NULL);
+
+ priv->is_searching = FALSE;
+}
+
+static void
+gdict_defbox_insert_word (GdictDefbox *defbox,
+ GtkTextIter *iter,
+ const gchar *word)
+{
+ GdictDefboxPrivate *priv;
+ gchar *text;
+
+ if (!word)
+ return;
+
+ g_assert (GDICT_IS_DEFBOX (defbox));
+ priv = defbox->priv;
+
+ g_assert (GTK_IS_TEXT_BUFFER (priv->buffer));
+
+ text = g_strdup_printf ("%s\n", word);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ iter,
+ text, strlen (text),
+ "big", "bold", "query-title",
+ NULL);
+ g_free (text);
+}
+
+/* escape a link string; links are expressed as "{...}".
+ * the link with the '{}' removed is stored inside link_str, while
+ * the returned value is a pointer to what follows the trailing '}'.
+ * link_str is allocated and should be freed.
+ */
+static const gchar *
+escape_link (const gchar *str,
+ gchar **link_str)
+{
+ gsize str_len;
+ GString *link_buf;
+ const gchar *p;
+
+ str_len = strlen (str);
+ link_buf = g_string_sized_new (str_len - 2);
+
+ for (p = str + 1; *p != '}'; p++)
+ {
+ link_buf = g_string_append_c (link_buf, *p);
+ }
+
+ if (link_str)
+ *link_str = g_string_free (link_buf, FALSE);
+
+ p++;
+
+ return p;
+}
+
+static const gchar *
+escape_phonethic (const gchar *str,
+ gchar **phon_str)
+{
+ gsize str_len;
+ GString *phon_buf;
+ const gchar *p;
+
+ str_len = strlen (str);
+ phon_buf = g_string_sized_new (str_len - 2);
+
+ for (p = str + 1; *p != '\\'; p++)
+ {
+ phon_buf = g_string_append_c (phon_buf, *p);
+ }
+
+ if (phon_str)
+ *phon_str = g_string_free (phon_buf, FALSE);
+
+ p++;
+
+ return p;
+}
+
+static void
+gdict_defbox_insert_body (GdictDefbox *defbox,
+ GtkTextIter *iter,
+ const gchar *body)
+{
+ GdictDefboxPrivate *priv;
+ gchar **words;
+ gint len, i;
+ GtkTextIter end_iter;
+
+ if (!body)
+ return;
+
+ g_assert (GDICT_IS_DEFBOX (defbox));
+ priv = defbox->priv;
+
+ g_assert (GTK_IS_TEXT_BUFFER (priv->buffer));
+
+ words = g_strsplit (body, " ", -1);
+ len = g_strv_length (words);
+ end_iter = *iter;
+
+ for (i = 0; i < len; i++)
+ {
+ gchar *w = words[i];
+ gint w_len = strlen (w);
+ gchar *begin, *end;
+
+ if (w_len == 0)
+ continue;
+
+ begin = g_utf8_offset_to_pointer (w, 0);
+
+ if (*begin == '{')
+ {
+ end = g_utf8_strrchr (w, -1, '}');
+
+ /* see this is a self contained link */
+ if (end && *end == '}')
+ {
+ const gchar *rest;
+ gchar *link_str;
+
+ rest = escape_link (w, &link_str);
+
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &end_iter,
+ link_str, -1,
+ "link",
+ NULL);
+
+ gtk_text_buffer_insert (priv->buffer, &end_iter, rest, -1);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &end_iter);
+ gtk_text_buffer_insert (priv->buffer, &end_iter, " ", 1);
+
+ g_free (link_str);
+
+ continue;
+ }
+ else
+ {
+ /* uh-oh: the link ends in another word */
+ GString *buf;
+ gchar *next;
+ gint cur = i;
+
+ buf = g_string_new (NULL);
+ next = words[cur++];
+
+ while (next && (end = g_utf8_strrchr (next, -1, '}')) == NULL)
+ {
+ buf = g_string_append (buf, next);
+ buf = g_string_append_c (buf, ' ');
+
+ next = words[cur++];
+ }
+
+ buf = g_string_append (buf, next);
+
+ next = g_string_free (buf, FALSE);
+
+ if (end && *end == '}')
+ {
+ const gchar *rest;
+ gchar *link_str;
+
+ rest = escape_link (next, &link_str);
+
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &end_iter,
+ link_str, -1,
+ "link",
+ NULL);
+
+ gtk_text_buffer_insert (priv->buffer, &end_iter, rest, -1);
+ gtk_text_buffer_insert (priv->buffer, &end_iter, " ", 1);
+
+ g_free (link_str);
+ }
+
+ g_free (next);
+ i = cur;
+
+ continue;
+ }
+ }
+ else if (*begin == '\\')
+ {
+ end = g_utf8_strrchr (w, -1, '\\');
+
+ if (end && *end == '\\')
+ {
+ const gchar *rest;
+ gchar *phon;
+
+ rest = escape_phonethic (w, &phon);
+
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &end_iter,
+ phon, -1,
+ "italic", "phonetic",
+ NULL);
+
+ gtk_text_buffer_insert (priv->buffer, &end_iter, rest, -1);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &end_iter);
+ gtk_text_buffer_insert (priv->buffer, &end_iter, " ", -1);
+
+ g_free (phon);
+
+ continue;
+ }
+ }
+
+ gtk_text_buffer_insert (priv->buffer, &end_iter, w, w_len);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &end_iter);
+ gtk_text_buffer_insert (priv->buffer, &end_iter, " ", 1);
+ }
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &end_iter);
+ gtk_text_buffer_insert (priv->buffer, &end_iter, "\n", 1);
+
+ *iter = end_iter;
+
+ g_strfreev (words);
+}
+
+static void
+gdict_defbox_insert_from (GdictDefbox *defbox,
+ GtkTextIter *iter,
+ const gchar *database)
+{
+ GdictDefboxPrivate *priv;
+ gchar *text;
+
+ if (!database)
+ return;
+
+ g_assert (GDICT_IS_DEFBOX (defbox));
+ priv = defbox->priv;
+
+ g_assert (GTK_IS_TEXT_BUFFER (priv->buffer));
+
+ text = g_strdup_printf ("\t-- From %s\n\n", database);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ iter,
+ text, strlen (text),
+ "small", "query-from",
+ NULL);
+ g_free (text);
+}
+
+static void
+gdict_defbox_insert_error (GdictDefbox *defbox,
+ GtkTextIter *iter,
+ const gchar *title,
+ const gchar *message)
+{
+ GdictDefboxPrivate *priv;
+ GtkTextMark *mark;
+ GtkTextIter cur_iter;
+
+ if (!title)
+ return;
+
+ g_assert (GDICT_IS_DEFBOX (defbox));
+ priv = defbox->priv;
+
+ g_assert (GTK_IS_TEXT_BUFFER (priv->buffer));
+
+ mark = gtk_text_buffer_create_mark (priv->buffer, "block-cursor", iter, FALSE);
+ gtk_text_buffer_get_iter_at_mark (priv->buffer, &cur_iter, mark);
+
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &cur_iter,
+ title, strlen (title),
+ "error-title", "big", "bold",
+ NULL);
+ gtk_text_buffer_get_iter_at_mark (priv->buffer, &cur_iter, mark);
+
+ gtk_text_buffer_insert (priv->buffer, &cur_iter, "\n\n", -1);
+ gtk_text_buffer_get_iter_at_mark (priv->buffer, &cur_iter, mark);
+
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &cur_iter,
+ message, strlen (message),
+ "error-message",
+ NULL);
+}
+
+static void
+definition_found_cb (GdictContext *context,
+ GdictDefinition *definition,
+ gpointer user_data)
+{
+ GdictDefbox *defbox = GDICT_DEFBOX (user_data);
+ GdictDefboxPrivate *priv = defbox->priv;
+ GtkTextIter iter;
+ Definition *def;
+
+ /* insert the word if this is the first definition */
+ if (!priv->definitions)
+ {
+ gtk_text_buffer_get_start_iter (priv->buffer, &iter);
+ gdict_defbox_insert_word (defbox, &iter,
+ gdict_definition_get_word (definition));
+ }
+
+ def = definition_new ();
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ def->begin = gtk_text_iter_get_offset (&iter);
+ gdict_defbox_insert_body (defbox, &iter, gdict_definition_get_text (definition));
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gdict_defbox_insert_from (defbox, &iter, gdict_definition_get_database (definition));
+
+ def->definition = gdict_definition_ref (definition);
+
+ priv->definitions = g_slist_append (priv->definitions, def);
+}
+
+static void
+error_cb (GdictContext *context,
+ const GError *error,
+ gpointer user_data)
+{
+ GdictDefbox *defbox = GDICT_DEFBOX (user_data);
+ GdictDefboxPrivate *priv = defbox->priv;
+ GtkTextIter iter;
+
+ if (!error)
+ return;
+
+ gdict_defbox_clear (defbox);
+
+ gtk_text_buffer_get_start_iter (priv->buffer, &iter);
+ gdict_defbox_insert_error (defbox, &iter,
+ _("Error while looking up definition"),
+ error->message);
+
+ g_free (priv->word);
+ priv->word = NULL;
+
+ defbox->priv->is_searching = FALSE;
+}
+
+/**
+ * gdict_defbox_lookup:
+ * @defbox: a #GdictDefbox
+ * @word: the word to look up
+ *
+ * Searches @word inside the dictionary sources using the #GdictContext
+ * provided when creating @defbox or set using gdict_defbox_set_context().
+ *
+ * Since: 0.1
+ */
+void
+gdict_defbox_lookup (GdictDefbox *defbox,
+ const gchar *word)
+{
+ GdictDefboxPrivate *priv;
+ GError *define_error;
+
+ g_return_if_fail (GDICT_IS_DEFBOX (defbox));
+
+ priv = defbox->priv;
+
+ if (!priv->context)
+ {
+ g_warning ("Attempting to look up `%s', but no GdictContext "
+ "has been set. Use gdict_defbox_set_context() "
+ "before invoking gdict_defbox_lookup().",
+ word);
+ return;
+ }
+
+ if (priv->is_searching)
+ {
+ _gdict_show_error_dialog (GTK_WIDGET (defbox),
+ _("Another search is in progress"),
+ _("Please wait until the current search ends."));
+
+ return;
+ }
+
+ gdict_defbox_clear (defbox);
+
+ if (!priv->start_id)
+ {
+ priv->start_id = g_signal_connect (priv->context, "lookup-start",
+ G_CALLBACK (lookup_start_cb),
+ defbox);
+ priv->define_id = g_signal_connect (priv->context, "definition-found",
+ G_CALLBACK (definition_found_cb),
+ defbox);
+ priv->end_id = g_signal_connect (priv->context, "lookup-end",
+ G_CALLBACK (lookup_end_cb),
+ defbox);
+ }
+
+ if (!priv->error_id)
+ priv->error_id = g_signal_connect (priv->context, "error",
+ G_CALLBACK (error_cb),
+ defbox);
+
+ priv->word = g_strdup (word);
+ g_object_notify (G_OBJECT (defbox), "word");
+
+ define_error = NULL;
+ gdict_context_define_word (priv->context,
+ priv->database,
+ word,
+ &define_error);
+ if (define_error)
+ {
+ GtkTextIter iter;
+
+ gtk_text_buffer_get_start_iter (priv->buffer, &iter);
+ gdict_defbox_insert_error (defbox, &iter,
+ _("Error while retrieving the definition"),
+ define_error->message);
+
+ g_error_free (define_error);
+ }
+}
+
+/**
+ * gdict_defbox_clear:
+ * @defbox: a @GdictDefbox
+ *
+ * Clears the buffer of @defbox
+ *
+ * Since: 0.1
+ */
+void
+gdict_defbox_clear (GdictDefbox *defbox)
+{
+ GdictDefboxPrivate *priv;
+ GtkTextIter start, end;
+
+ g_return_if_fail (GDICT_IS_DEFBOX (defbox));
+
+ priv = defbox->priv;
+
+ /* destroy previously found definitions */
+ if (priv->definitions)
+ {
+ g_slist_foreach (priv->definitions,
+ (GFunc) definition_free,
+ NULL);
+ g_slist_free (priv->definitions);
+
+ priv->definitions = NULL;
+ }
+
+ gtk_text_buffer_get_bounds (priv->buffer, &start, &end);
+ gtk_text_buffer_delete (priv->buffer, &start, &end);
+}
+
+/**
+ * gdict_defbox_find_next:
+ * @defbox: a #GdictDefbox
+ *
+ * Emits the "find-next" signal.
+ *
+ * Since: 0.1
+ */
+void
+gdict_defbox_find_next (GdictDefbox *defbox)
+{
+ g_return_if_fail (GDICT_IS_DEFBOX (defbox));
+
+ g_signal_emit (defbox, gdict_defbox_signals[FIND_NEXT], 0);
+}
+
+/**
+ * gdict_defbox_find_previous:
+ * @defbox: a #GdictDefbox
+ *
+ * Emits the "find-previous" signal.
+ *
+ * Since: 0.1
+ */
+void
+gdict_defbox_find_previous (GdictDefbox *defbox)
+{
+ g_return_if_fail (GDICT_IS_DEFBOX (defbox));
+
+ g_signal_emit (defbox, gdict_defbox_signals[FIND_PREVIOUS], 0);
+}
+
+/**
+ * gdict_defbox_select_all:
+ * @defbox: a #GdictDefbox
+ *
+ * Selects all the text displayed by @defbox
+ *
+ * Since: 0.1
+ */
+void
+gdict_defbox_select_all (GdictDefbox *defbox)
+{
+ GdictDefboxPrivate *priv;
+ GtkTextBuffer *buffer;
+ GtkTextIter start, end;
+
+ g_return_if_fail (GDICT_IS_DEFBOX (defbox));
+
+ priv = defbox->priv;
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
+
+ gtk_text_buffer_get_bounds (buffer, &start, &end);
+ gtk_text_buffer_select_range (buffer, &start, &end);
+}
+
+/**
+ * gdict_defbox_copy_to_clipboard:
+ * @defbox: a #GdictDefbox
+ * @clipboard: a #GtkClipboard
+ *
+ * Copies the selected text inside @defbox into @clipboard.
+ *
+ * Since: 0.1
+ */
+void
+gdict_defbox_copy_to_clipboard (GdictDefbox *defbox,
+ GtkClipboard *clipboard)
+{
+ GdictDefboxPrivate *priv;
+ GtkTextBuffer *buffer;
+
+ g_return_if_fail (GDICT_IS_DEFBOX (defbox));
+ g_return_if_fail (GTK_IS_CLIPBOARD (clipboard));
+
+ priv = defbox->priv;
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
+
+ gtk_text_buffer_copy_clipboard (buffer, clipboard);
+}
+
+/**
+ * gdict_defbox_count_definitions:
+ * @defbox: a #GdictDefbox
+ *
+ * Gets the number of definitions displayed by @defbox
+ *
+ * Return value: the number of definitions.
+ *
+ * Since: 0.1
+ */
+gint
+gdict_defbox_count_definitions (GdictDefbox *defbox)
+{
+ GdictDefboxPrivate *priv;
+
+ g_return_val_if_fail (GDICT_IS_DEFBOX (defbox), -1);
+
+ priv = defbox->priv;
+ if (!priv->definitions)
+ return -1;
+
+ return g_slist_length (priv->definitions);
+}
+
+/**
+ * gdict_defbox_jump_to_definition:
+ * @defbox: a #GdictDefbox
+ * @number: the definition to jump to
+ *
+ * Scrolls to the definition identified by @number. If @number is -1,
+ * jumps to the last definition.
+ *
+ * Since: 0.1
+ */
+void
+gdict_defbox_jump_to_definition (GdictDefbox *defbox,
+ gint number)
+{
+ GdictDefboxPrivate *priv;
+ gint count;
+ Definition *def;
+ GtkTextBuffer *buffer;
+ GtkTextIter def_start;
+
+ g_return_if_fail (GDICT_IS_DEFBOX (defbox));
+
+ count = gdict_defbox_count_definitions (defbox) - 1;
+ if (count == -1)
+ return;
+
+ if ((number == -1) || (number > count))
+ number = count;
+
+ priv = defbox->priv;
+ def = (Definition *) g_slist_nth_data (priv->definitions, number);
+ if (!def)
+ return;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
+ gtk_text_buffer_get_iter_at_offset (buffer, &def_start, def->begin);
+ gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (priv->text_view),
+ &def_start,
+ 0.0,
+ TRUE,
+ 0.0, 0.0);
+}
+
+/**
+ * gdict_defbox_get_text:
+ * @defbox: a #GdictDefbox
+ * @length: return location for the text length or %NULL
+ *
+ * Gets the full contents of @defbox.
+ *
+ * Return value: a newly allocated string containing the text displayed by
+ * @defbox.
+ *
+ * Since: 0.1
+ */
+gchar *
+gdict_defbox_get_text (GdictDefbox *defbox,
+ gsize *length)
+{
+ GdictDefboxPrivate *priv;
+ GtkTextBuffer *buffer;
+ GtkTextIter start, end;
+ gchar *retval;
+
+ g_return_val_if_fail (GDICT_IS_DEFBOX (defbox), NULL);
+
+ priv = defbox->priv;
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
+
+ gtk_text_buffer_get_bounds (buffer, &start, &end);
+
+ retval = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+
+ if (length)
+ *length = strlen (retval);
+
+ return retval;
+}
+
+/**
+ * gdict_defbox_set_font_name:
+ * @defbox: a #GdictDefbox
+ * @font_name: a font description, or %NULL
+ *
+ * Sets @font_name as the font for @defbox. It calls internally
+ * pango_font_description_from_string() and gtk_widget_modify_font().
+ *
+ * Passing %NULL for @font_name will reset any previously set font.
+ *
+ * Since: 0.3.0
+ */
+void
+gdict_defbox_set_font_name (GdictDefbox *defbox,
+ const gchar *font_name)
+{
+ GdictDefboxPrivate *priv;
+ PangoFontDescription *font_desc;
+
+ g_return_if_fail (GDICT_IS_DEFBOX (defbox));
+
+ priv = defbox->priv;
+
+ if (font_name)
+ {
+ font_desc = pango_font_description_from_string (font_name);
+ g_return_if_fail (font_desc != NULL);
+ }
+ else
+ font_desc = NULL;
+
+ gtk_widget_modify_font (priv->text_view, font_desc);
+
+ if (font_desc)
+ pango_font_description_free (font_desc);
+
+ g_free (priv->font_name);
+ priv->font_name = g_strdup (font_name);
+}
+
+/**
+ * gdict_defbox_get_font_name:
+ * @defbox: a #GdictDefbox
+ *
+ * Retrieves the font currently used by @defbox.
+ *
+ * Return value: a font name. The returned string is owned by @defbox and
+ * should not be modified or freed.
+ *
+ * Since: 0.3
+ */
+const gchar *
+gdict_defbox_get_font_name (GdictDefbox *defbox)
+{
+ g_return_val_if_fail (GDICT_IS_DEFBOX (defbox), NULL);
+
+ return defbox->priv->font_name;
+}
+
+/**
+ * gdict_defbox_get_selected_word:
+ * @defbox: a #GdictDefbox
+ *
+ * Retrieves the selected word from the defbox widget
+ *
+ * Return value: a newly allocated string containing the selected
+ * word. Use g_free() when done using it.
+ *
+ * Since: 0.12
+ */
+gchar *
+gdict_defbox_get_selected_word (GdictDefbox *defbox)
+{
+ GdictDefboxPrivate *priv;
+ GtkTextBuffer *buffer;
+
+ g_return_val_if_fail (GDICT_IS_DEFBOX (defbox), NULL);
+
+ priv = defbox->priv;
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
+
+ if (!gtk_text_buffer_get_has_selection (buffer))
+ return NULL;
+ else
+ {
+ GtkTextIter start, end;
+ gchar *retval;
+
+ gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
+ retval = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+
+ return retval;
+ }
+}
diff --git a/mate-dictionary/libgdict/gdict-defbox.h b/mate-dictionary/libgdict/gdict-defbox.h
new file mode 100644
index 00000000..4c1278f6
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-defbox.h
@@ -0,0 +1,102 @@
+/* gdict-defbox.h - display widget for dictionary definitions
+ *
+ * Copyright (C) 2005-2006 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+#ifndef __GDICT_DEFBOX_H__
+#define __GDICT_DEFBOX_H__
+
+#include <gtk/gtk.h>
+#include "gdict-context.h"
+
+G_BEGIN_DECLS
+
+#define GDICT_TYPE_DEFBOX (gdict_defbox_get_type ())
+#define GDICT_DEFBOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDICT_TYPE_DEFBOX, GdictDefbox))
+#define GDICT_IS_DEFBOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDICT_TYPE_DEFBOX))
+#define GDICT_DEFBOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDICT_TYPE_DEFBOX, GdictDefboxClass))
+#define GDICT_IS_DEFBOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDICT_TYPE_DEFBOX))
+#define GDICT_DEFBOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GDICT_TYPE_DEFBOX, GdictDefboxClass))
+
+typedef struct _GdictDefbox GdictDefbox;
+typedef struct _GdictDefboxClass GdictDefboxClass;
+typedef struct _GdictDefboxPrivate GdictDefboxPrivate;
+
+struct _GdictDefbox
+{
+ /*< private >*/
+ GtkVBox parent_instance;
+
+ GdictDefboxPrivate *priv;
+};
+
+struct _GdictDefboxClass
+{
+ GtkVBoxClass parent_class;
+
+ /* these are all RUN_ACTION signals for key bindings */
+ void (*show_find) (GdictDefbox *defbox);
+ void (*hide_find) (GdictDefbox *defbox);
+ void (*find_previous) (GdictDefbox *defbox);
+ void (*find_next) (GdictDefbox *defbox);
+
+ /* signals */
+ void (*link_clicked) (GdictDefbox *defbox,
+ const gchar *link);
+
+ /* padding for future expansion */
+ void (*_gdict_defbox_1) (void);
+ void (*_gdict_defbox_2) (void);
+ void (*_gdict_defbox_3) (void);
+ void (*_gdict_defbox_4) (void);
+};
+
+GType gdict_defbox_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gdict_defbox_new (void);
+GtkWidget * gdict_defbox_new_with_context (GdictContext *context);
+void gdict_defbox_set_context (GdictDefbox *defbox,
+ GdictContext *context);
+GdictContext * gdict_defbox_get_context (GdictDefbox *defbox);
+void gdict_defbox_set_database (GdictDefbox *defbox,
+ const gchar *database);
+const gchar *gdict_defbox_get_database (GdictDefbox *defbox);
+const gchar *gdict_defbox_get_word (GdictDefbox *defbox);
+gchar * gdict_defbox_get_text (GdictDefbox *defbox,
+ gsize *length) G_GNUC_MALLOC;
+void gdict_defbox_select_all (GdictDefbox *defbox);
+void gdict_defbox_copy_to_clipboard (GdictDefbox *defbox,
+ GtkClipboard *clipboard);
+void gdict_defbox_clear (GdictDefbox *defbox);
+void gdict_defbox_lookup (GdictDefbox *defbox,
+ const gchar *word);
+gint gdict_defbox_count_definitions (GdictDefbox *defbox);
+void gdict_defbox_jump_to_definition (GdictDefbox *defbox,
+ gint number);
+void gdict_defbox_set_show_find (GdictDefbox *defbox,
+ gboolean show_find);
+gboolean gdict_defbox_get_show_find (GdictDefbox *defbox);
+void gdict_defbox_find_next (GdictDefbox *defbox);
+void gdict_defbox_find_previous (GdictDefbox *defbox);
+void gdict_defbox_set_font_name (GdictDefbox *defbox,
+ const gchar *font_name);
+const gchar *gdict_defbox_get_font_name (GdictDefbox *defbox);
+gchar * gdict_defbox_get_selected_word (GdictDefbox *defbox) G_GNUC_MALLOC;
+
+G_END_DECLS
+
+#endif /* __GDICT_DEFBOX_H__ */
diff --git a/mate-dictionary/libgdict/gdict-enum-types.c.in b/mate-dictionary/libgdict/gdict-enum-types.c.in
new file mode 100644
index 00000000..0dbbdc66
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-enum-types.c.in
@@ -0,0 +1,33 @@
+/*** BEGIN file-header ***/
+#include "gdict-enum-types.h"
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@filename@" */
+#include "@filename@"
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType
+@enum_name@_get_type (void)
+{
+ static GType g_enum_type_id = 0;
+ if (G_UNLIKELY (g_enum_type_id == 0))
+ {
+ static const G@Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+ { @VALUENAME@, "@VALUENAME@", "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+ { 0, NULL, NULL }
+ };
+ g_enum_type_id = g_@type@_register_static("@EnumName@", values);
+ }
+
+ return g_enum_type_id;
+}
+/*** END value-tail ***/
+
diff --git a/mate-dictionary/libgdict/gdict-enum-types.h.in b/mate-dictionary/libgdict/gdict-enum-types.h.in
new file mode 100644
index 00000000..bfaf3e29
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-enum-types.h.in
@@ -0,0 +1,26 @@
+/*** BEGIN file-header ***/
+#ifndef __GDICT_ENUM_TYPES_H__
+#define __GDICT_ENUM_TYPES_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@filename@" */
+/*** END file-production ***/
+
+/*** BEGIN file-tail ***/
+G_END_DECLS
+
+#endif /* !__GDICT_ENUM_TYPES_H__ */
+/*** END file-tail ***/
+
+/*** BEGIN value-header ***/
+GType @enum_name@_get_type (void) G_GNUC_CONST;
+#define GDICT_TYPE_@ENUMSHORT@ (@enum_name@_get_type())
+
+/*** END value-header ***/
+
diff --git a/mate-dictionary/libgdict/gdict-marshal.list b/mate-dictionary/libgdict/gdict-marshal.list
new file mode 100644
index 00000000..39fa85e3
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-marshal.list
@@ -0,0 +1,7 @@
+VOID:VOID
+VOID:POINTER
+VOID:BOXED
+VOID:OBJECT
+VOID:STRING
+VOID:STRING,STRING
+VOID:STRING,OBJECT
diff --git a/mate-dictionary/libgdict/gdict-private.h b/mate-dictionary/libgdict/gdict-private.h
new file mode 100644
index 00000000..224ba776
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-private.h
@@ -0,0 +1,34 @@
+/* gdict-private.h - Private stuff for Gdict
+ *
+ * Copyright (C) 2006 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+#include <gtk/gtk.h>
+#include "gdict-debug.h"
+
+G_BEGIN_DECLS
+
+gboolean _gdict_has_ipv6 (void);
+
+void _gdict_show_error_dialog (GtkWidget *widget,
+ const gchar *title,
+ const gchar *detail);
+void _gdict_show_gerror_dialog (GtkWidget *widget,
+ const gchar *title,
+ GError *error);
+
+G_END_DECLS
diff --git a/mate-dictionary/libgdict/gdict-source-chooser.c b/mate-dictionary/libgdict/gdict-source-chooser.c
new file mode 100644
index 00000000..ab640f85
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-source-chooser.c
@@ -0,0 +1,917 @@
+/* gdict-source-chooser.h - display widget for dictionary sources
+ *
+ * Copyright (C) 2007 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+/**
+ * SECTION:gdict-source-chooser
+ * @short_description: Display the list of available sources
+ *
+ * #GdictSourceChooser is a widget that shows the list of available
+ * dictionary sources using a #GdictSourceLoader instance as a model.
+ * It can be used to allow choosing the current dictionary source.
+ *
+ * #GdictSourceChooser is available since Gdict 0.12.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include <gdk/gdk.h>
+
+#include <gtk/gtk.h>
+
+#include "gdict-source-chooser.h"
+#include "gdict-utils.h"
+#include "gdict-private.h"
+#include "gdict-enum-types.h"
+#include "gdict-marshal.h"
+
+#define GDICT_SOURCE_CHOOSER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
+ GDICT_TYPE_SOURCE_CHOOSER, \
+ GdictSourceChooserPrivate))
+
+struct _GdictSourceChooserPrivate
+{
+ GtkListStore *store;
+
+ GtkWidget *treeview;
+ GtkWidget *refresh_button;
+ GtkWidget *buttons_box;
+
+ GdictSourceLoader *loader;
+ gint n_sources;
+
+ GdkCursor *busy_cursor;
+
+ gchar *current_source;
+};
+
+enum
+{
+ SOURCE_TRANSPORT,
+ SOURCE_NAME,
+ SOURCE_DESCRIPTION,
+ SOURCE_CURRENT,
+
+ SOURCE_N_COLUMNS
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_LOADER,
+ PROP_COUNT
+};
+
+enum
+{
+ SOURCE_ACTIVATED,
+ SELECTION_CHANGED,
+
+ LAST_SIGNAL
+};
+
+static guint source_chooser_signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE (GdictSourceChooser, gdict_source_chooser, GTK_TYPE_VBOX);
+
+static void
+gdict_source_chooser_finalize (GObject *gobject)
+{
+ GdictSourceChooser *chooser = GDICT_SOURCE_CHOOSER (gobject);
+ GdictSourceChooserPrivate *priv = chooser->priv;
+
+ g_free (priv->current_source);
+
+ G_OBJECT_CLASS (gdict_source_chooser_parent_class)->finalize (gobject);
+}
+
+static void
+gdict_source_chooser_dispose (GObject *gobject)
+{
+ GdictSourceChooser *chooser = GDICT_SOURCE_CHOOSER (gobject);
+ GdictSourceChooserPrivate *priv = chooser->priv;
+
+ if (priv->store)
+ {
+ g_object_unref (priv->store);
+ priv->store = NULL;
+ }
+
+ if (priv->loader)
+ {
+ g_object_unref (priv->loader);
+ priv->loader = NULL;
+ }
+
+ if (priv->busy_cursor)
+ {
+ gdk_cursor_unref (priv->busy_cursor);
+ priv->busy_cursor = NULL;
+ }
+
+ G_OBJECT_CLASS (gdict_source_chooser_parent_class)->dispose (gobject);
+}
+
+static void
+gdict_source_chooser_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (prop_id)
+ {
+ case PROP_LOADER:
+ gdict_source_chooser_set_loader (GDICT_SOURCE_CHOOSER (gobject),
+ g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gdict_source_chooser_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GdictSourceChooserPrivate *priv;
+
+ priv = GDICT_SOURCE_CHOOSER (gobject)->priv;
+
+ switch (prop_id)
+ {
+ case PROP_LOADER:
+ g_value_set_object (value, priv->loader);
+ break;
+ case PROP_COUNT:
+ g_value_set_int (value, priv->n_sources);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+row_activated_cb (GtkTreeView *treeview,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer data)
+{
+ GdictSourceChooser *chooser = GDICT_SOURCE_CHOOSER (data);
+ GdictSourceChooserPrivate *priv = chooser->priv;
+ GtkTreeIter iter;
+ gchar *name;
+ GdictSource *source;
+
+ if (!priv->loader)
+ return;
+
+ if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->store), &iter, path))
+ return;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter,
+ SOURCE_NAME, &name,
+ -1);
+ if (!name)
+ return;
+
+ source = gdict_source_loader_get_source (priv->loader, name);
+ if (!source)
+ {
+ g_free (name);
+ return;
+ }
+
+ g_signal_emit (chooser, source_chooser_signals[SOURCE_ACTIVATED], 0,
+ name, source);
+
+ g_free (name);
+ g_object_unref (source);
+}
+
+static void
+refresh_button_clicked_cb (GtkButton *button,
+ gpointer data)
+{
+ gdict_source_chooser_refresh (GDICT_SOURCE_CHOOSER (data));
+}
+
+static void
+selection_changed_cb (GtkTreeSelection *selection,
+ gpointer data)
+{
+ g_signal_emit (data, source_chooser_signals[SELECTION_CHANGED], 0);
+}
+
+static GObject *
+gdict_source_chooser_constructor (GType gtype,
+ guint n_params,
+ GObjectConstructParam *params)
+{
+ GdictSourceChooser *chooser;
+ GdictSourceChooserPrivate *priv;
+ GObjectClass *parent_class;
+ GObject *retval;
+ GtkWidget *sw;
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+ GtkWidget *hbox;
+
+ parent_class = G_OBJECT_CLASS (gdict_source_chooser_parent_class);
+ retval = parent_class->constructor (gtype, n_params, params);
+
+ chooser = GDICT_SOURCE_CHOOSER (retval);
+ priv = chooser->priv;
+
+ gtk_widget_push_composite_child ();
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_widget_set_composite_name (sw, "gdict-source-chooser-scrolled-window");
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+ GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (chooser), sw, TRUE, TRUE, 0);
+ gtk_widget_show (sw);
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("sources",
+ renderer,
+ "text", SOURCE_DESCRIPTION,
+ "weight", SOURCE_CURRENT,
+ NULL);
+ priv->treeview = gtk_tree_view_new ();
+ gtk_widget_set_composite_name (priv->treeview, "gdict-source-chooser-treeview");
+ gtk_tree_view_set_model (GTK_TREE_VIEW (priv->treeview),
+ GTK_TREE_MODEL (priv->store));
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->treeview), FALSE);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (priv->treeview), column);
+ g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview)),
+ "changed", G_CALLBACK (selection_changed_cb),
+ chooser);
+ g_signal_connect (priv->treeview,
+ "row-activated", G_CALLBACK (row_activated_cb),
+ chooser);
+ gtk_container_add (GTK_CONTAINER (sw), priv->treeview);
+ gtk_widget_show (priv->treeview);
+
+ hbox = gtk_hbox_new (FALSE, 6);
+ priv->buttons_box = hbox;
+
+ priv->refresh_button = gtk_button_new ();
+ gtk_button_set_image (GTK_BUTTON (priv->refresh_button),
+ gtk_image_new_from_stock (GTK_STOCK_REFRESH,
+ GTK_ICON_SIZE_BUTTON));
+ g_signal_connect (priv->refresh_button,
+ "clicked", G_CALLBACK (refresh_button_clicked_cb),
+ chooser);
+ gtk_box_pack_start (GTK_BOX (hbox), priv->refresh_button, FALSE, FALSE, 0);
+ gtk_widget_show (priv->refresh_button);
+ gtk_widget_set_tooltip_text (priv->refresh_button,
+ _("Reload the list of available sources"));
+
+ gtk_box_pack_end (GTK_BOX (chooser), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ gtk_widget_pop_composite_child ();
+
+ return retval;
+}
+
+static void
+gdict_source_chooser_class_init (GdictSourceChooserClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (GdictSourceChooserPrivate));
+
+ gobject_class->finalize = gdict_source_chooser_finalize;
+ gobject_class->dispose = gdict_source_chooser_dispose;
+ gobject_class->set_property = gdict_source_chooser_set_property;
+ gobject_class->get_property = gdict_source_chooser_get_property;
+ gobject_class->constructor = gdict_source_chooser_constructor;
+
+ /**
+ * GdictSourceChooser:loader:
+ *
+ * The #GdictSourceLoader used to retrieve the list of available
+ * dictionary sources.
+ *
+ * Since: 0.12
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_LOADER,
+ g_param_spec_object ("loader",
+ "Loader",
+ "The GdictSourceLoader used to get the list of sources",
+ GDICT_TYPE_SOURCE_LOADER,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+ /**
+ * GdictSourceChooser:count:
+ *
+ * The number of available dictionary sources, or -1 if no
+ * #GdictSourceLoader is set.
+ *
+ * Since: 0.12
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_COUNT,
+ g_param_spec_int ("count",
+ "Count",
+ "The number of available dictionary sources",
+ -1, G_MAXINT, -1,
+ G_PARAM_READABLE));
+
+ /**
+ * GdictSourceChooser::source-activated:
+ * @chooser: the #GdictSourceChooser that received the signal
+ * @source_name: the name of the activated source
+ * @source: the activated #GdictSource
+ *
+ * The ::source-activated signal is emitted each time the user
+ * activates a row in the source chooser widget, either by double
+ * clicking on it or by a keyboard event.
+ *
+ * Since: 0.12
+ */
+ source_chooser_signals[SOURCE_ACTIVATED] =
+ g_signal_new ("source-activated",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdictSourceChooserClass, source_activated),
+ NULL, NULL,
+ gdict_marshal_VOID__STRING_OBJECT,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ GDICT_TYPE_SOURCE);
+ /**
+ * GdictSourceChooser::selection-changed:
+ * @chooser: the #GdictSourceChooser that received the signal
+ *
+ * The ::selection-changed signal is emitted each time the
+ * selection inside the source chooser widget has been changed.
+ *
+ * Since: 0.12
+ */
+ source_chooser_signals[SELECTION_CHANGED] =
+ g_signal_new ("selection-changed",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdictSourceChooserClass, selection_changed),
+ NULL, NULL,
+ gdict_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+gdict_source_chooser_init (GdictSourceChooser *chooser)
+{
+ GdictSourceChooserPrivate *priv;
+
+ chooser->priv = priv = GDICT_SOURCE_CHOOSER_GET_PRIVATE (chooser);
+
+ priv->store = gtk_list_store_new (SOURCE_N_COLUMNS,
+ G_TYPE_INT, /* TRANSPORT */
+ G_TYPE_STRING, /* NAME */
+ G_TYPE_STRING, /* DESCRIPTION */
+ G_TYPE_INT /* CURRENT */);
+
+ priv->loader = NULL;
+ priv->n_sources = -1;
+
+ priv->busy_cursor = gdk_cursor_new (GDK_WATCH);
+}
+
+/**
+ * gdict_source_chooser_new:
+ *
+ * Creates a new #GdictSourceChooser widget. This widget can be used to
+ * display the list of available dictionary sources.
+ *
+ * Return value: the newly created #GdictSourceChooser widget.
+ *
+ * Since: 0.12
+ */
+GtkWidget *
+gdict_source_chooser_new (void)
+{
+ return g_object_new (GDICT_TYPE_SOURCE_CHOOSER, NULL);
+}
+
+/**
+ * gdict_source_chooser_new_with_loader:
+ * @loader: a #GdictSourceLoader
+ *
+ * Creates a new #GdictSourceChooser widget and sets @loader as the
+ * #GdictSourceLoader object to be used to retrieve the list of
+ * available dictionary sources.
+ *
+ * Return value: the newly created #GdictSourceChooser widget.
+ *
+ * Since: 0.12
+ */
+GtkWidget *
+gdict_source_chooser_new_with_loader (GdictSourceLoader *loader)
+{
+ g_return_val_if_fail (GDICT_IS_SOURCE_LOADER (loader), NULL);
+
+ return g_object_new (GDICT_TYPE_SOURCE_CHOOSER, "loader", loader, NULL);
+}
+
+/**
+ * gdict_source_chooser_set_loader:
+ * @chooser: a #GdictSourceChooser
+ * @loader: a #GdictSourceLoader or %NULL to unset it
+ *
+ * Sets the #GdictSourceLoader to be used by the source chooser
+ * widget.
+ *
+ * Since: 0.12
+ */
+void
+gdict_source_chooser_set_loader (GdictSourceChooser *chooser,
+ GdictSourceLoader *loader)
+{
+ GdictSourceChooserPrivate *priv;
+
+ g_return_if_fail (GDICT_IS_SOURCE_CHOOSER (chooser));
+ g_return_if_fail (loader == NULL || GDICT_IS_SOURCE_LOADER (loader));
+
+ priv = chooser->priv;
+
+ if (priv->loader != loader)
+ {
+ if (priv->loader)
+ g_object_unref (priv->loader);
+
+ if (loader)
+ {
+ priv->loader = g_object_ref (loader);
+ gdict_source_chooser_refresh (chooser);
+ }
+
+ g_object_notify (G_OBJECT (chooser), "loader");
+ }
+}
+
+/**
+ * gdict_source_chooser_get_loader:
+ * @chooser: a #GdictSourceChooser
+ *
+ * Retrieves the #GdictSourceLoader used by @chooser.
+ *
+ * Return value: a #GdictSourceLoader or %NULL is none is set
+ *
+ * Since: 0.12
+ */
+GdictSourceLoader *
+gdict_source_chooser_get_loader (GdictSourceChooser *chooser)
+{
+ g_return_val_if_fail (GDICT_IS_SOURCE_CHOOSER (chooser), NULL);
+
+ return chooser->priv->loader;
+}
+
+typedef struct
+{
+ gchar *source_name;
+ GdictSourceChooser *chooser;
+
+ guint found : 1;
+ guint do_select : 1;
+ guint do_activate : 1;
+} SelectData;
+
+static gboolean
+scan_for_source_name (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ SelectData *select_data = user_data;
+ gchar *source_name = NULL;
+
+ if (!select_data)
+ return TRUE;
+
+ gtk_tree_model_get (model, iter, SOURCE_NAME, &source_name, -1);
+ if (!source_name)
+ return FALSE;
+
+ if (strcmp (source_name, select_data->source_name) == 0)
+ {
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+
+ select_data->found = TRUE;
+
+ tree_view = GTK_TREE_VIEW (select_data->chooser->priv->treeview);
+
+ if (select_data->do_activate)
+ {
+ GtkTreeViewColumn *column;
+
+ column = gtk_tree_view_get_column (tree_view, 2);
+
+ gtk_list_store_set (GTK_LIST_STORE (model), iter,
+ SOURCE_CURRENT, PANGO_WEIGHT_BOLD,
+ -1);
+
+ gtk_tree_view_row_activated (tree_view, path, column);
+ }
+
+ selection = gtk_tree_view_get_selection (tree_view);
+ if (select_data->do_select)
+ gtk_tree_selection_select_path (selection, path);
+ else
+ gtk_tree_selection_unselect_path (selection, path);
+ }
+ else
+ {
+ gtk_list_store_set (GTK_LIST_STORE (model), iter,
+ SOURCE_CURRENT, PANGO_WEIGHT_NORMAL,
+ -1);
+ }
+
+ g_free (source_name);
+
+ return FALSE;
+}
+
+/**
+ * gdict_source_chooser_select_source:
+ * @chooser: a #GdictSourceChooser
+ * @source_name: the name of a dictionary source
+ *
+ * Selects the dictionary source named @source_name inside @chooser.
+ * The selection is moved but the row containing the dictionary source
+ * is not activated.
+ *
+ * Return value: %TRUE if the source was found and selected
+ *
+ * Since: 0.12
+ */
+gboolean
+gdict_source_chooser_select_source (GdictSourceChooser *chooser,
+ const gchar *source_name)
+{
+ GdictSourceChooserPrivate *priv;
+ SelectData data;
+ gboolean retval;
+
+ g_return_val_if_fail (GDICT_IS_SOURCE_CHOOSER (chooser), FALSE);
+ g_return_val_if_fail (source_name != NULL, FALSE);
+
+ priv = chooser->priv;
+
+ data.source_name = g_strdup (source_name);
+ data.chooser = chooser;
+ data.found = FALSE;
+ data.do_select = TRUE;
+ data.do_activate = FALSE;
+
+ gtk_tree_model_foreach (GTK_TREE_MODEL (priv->store),
+ scan_for_source_name,
+ &data);
+
+ retval = data.found;
+
+ g_free (data.source_name);
+
+ return retval;
+}
+
+/**
+ * gdict_source_chooser_unselect_source:
+ * @chooser: a #GdictSourceChooser
+ * @source_name: the name of a dictionary source
+ *
+ * Unselects @source_name inside @chooser.
+ *
+ * Return value: %TRUE if the source was found and unselected
+ *
+ * Since: 0.12
+ */
+gboolean
+gdict_source_chooser_unselect_source (GdictSourceChooser *chooser,
+ const gchar *source_name)
+{
+ GdictSourceChooserPrivate *priv;
+ SelectData data;
+ gboolean retval;
+
+ g_return_val_if_fail (GDICT_IS_SOURCE_CHOOSER (chooser), FALSE);
+ g_return_val_if_fail (source_name != NULL, FALSE);
+
+ priv = chooser->priv;
+
+ data.source_name = g_strdup (source_name);
+ data.chooser = chooser;
+ data.found = FALSE;
+ data.do_select = FALSE;
+ data.do_activate = FALSE;
+
+ gtk_tree_model_foreach (GTK_TREE_MODEL (priv->store),
+ scan_for_source_name,
+ &data);
+
+ retval = data.found;
+
+ g_free (data.source_name);
+
+ return retval;
+}
+
+/**
+ * gdict_source_chooser_set_current_source:
+ * @chooser: a #GdictSourceChooser
+ * @source_name: the name of a dictionary source
+ *
+ * Sets the current dictionary source named @source_name. The row
+ * of the source, if found, will be selected and activated.
+ *
+ * Return value: %TRUE if the source was found
+ *
+ * Since: 0.12
+ */
+gboolean
+gdict_source_chooser_set_current_source (GdictSourceChooser *chooser,
+ const gchar *source_name)
+{
+ GdictSourceChooserPrivate *priv;
+ SelectData data;
+ gboolean retval;
+
+ g_return_val_if_fail (GDICT_IS_SOURCE_CHOOSER (chooser), FALSE);
+ g_return_val_if_fail (source_name != NULL, FALSE);
+
+ priv = chooser->priv;
+
+ if (priv->current_source && !strcmp (priv->current_source, source_name))
+ return TRUE;
+
+ data.source_name = g_strdup (source_name);
+ data.chooser = chooser;
+ data.found = FALSE;
+ data.do_select = TRUE;
+ data.do_activate = TRUE;
+
+ gtk_tree_model_foreach (GTK_TREE_MODEL (priv->store),
+ scan_for_source_name,
+ &data);
+
+ retval = data.found;
+
+ GDICT_NOTE (CHOOSER, "%s current source: %s",
+ data.found ? "set" : "not set",
+ data.source_name);
+
+ if (data.found)
+ {
+ g_free (priv->current_source);
+ priv->current_source = data.source_name;
+ }
+ else
+ g_free (data.source_name);
+
+ return retval;
+}
+
+/**
+ * gdict_source_chooser_get_current_source:
+ * @chooser: a #GdictSourceChooser
+ *
+ * Retrieves the currently selected source.
+ *
+ * Return value: a newly allocated string containing the name of
+ * the currently selected source. Use g_free() when done using it
+ *
+ * Since: 0.12
+ */
+gchar *
+gdict_source_chooser_get_current_source (GdictSourceChooser *chooser)
+{
+ GdictSourceChooserPrivate *priv;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gchar *retval = NULL;
+
+ g_return_val_if_fail (GDICT_IS_SOURCE_CHOOSER (chooser), NULL);
+
+ priv = chooser->priv;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+ return NULL;
+
+ gtk_tree_model_get (model, &iter, SOURCE_NAME, &retval, -1);
+
+ g_free (priv->current_source);
+ priv->current_source = g_strdup (retval);
+
+ return retval;
+}
+
+/**
+ * gdict_source_chooser_get_sources:
+ * @chooser: a #GdictSouceChooser
+ * @length: return location for the length of the returned vector
+ *
+ * Retrieves the names of the available dictionary sources.
+ *
+ * Return value: a newly allocated, %NULL terminated string vector
+ * containing the names of the available sources. Use g_strfreev()
+ * when done using it.
+ *
+ * Since: 0.12
+ */
+gchar **
+gdict_source_chooser_get_sources (GdictSourceChooser *chooser,
+ gsize *length)
+{
+ GdictSourceChooserPrivate *priv;
+ gchar **retval;
+ gsize retval_len;
+
+ g_return_val_if_fail (GDICT_IS_SOURCE_CHOOSER (chooser), NULL);
+
+ priv = chooser->priv;
+
+ if (!priv->loader)
+ return NULL;
+
+ retval = gdict_source_loader_get_names (priv->loader, &retval_len);
+ if (length)
+ *length = retval_len;
+
+ return retval;
+}
+
+/**
+ * gdict_source_chooser_count_sources:
+ * @chooser: a #GdictSourceChooser
+ *
+ * Retrieve the number of available dictionary sources.
+ *
+ * Return value: the number of available sources, or -1 if no
+ * #GdictSourceLoader has been set
+ *
+ * Since: 0.12
+ */
+gint
+gdict_source_chooser_count_sources (GdictSourceChooser *chooser)
+{
+ g_return_val_if_fail (GDICT_IS_SOURCE_CHOOSER (chooser), -1);
+
+ return chooser->priv->n_sources;
+}
+
+/**
+ * gdict_source_chooser_has_source:
+ * @chooser: a #GdictSourceChooser
+ * @source_name: the name of a dictionary source
+ *
+ * Checks whether @chooser has a dictionary source named @source_name.
+ *
+ * Return value: %TRUE if the dictionary source was found
+ *
+ * Since: 0.12
+ */
+gboolean
+gdict_source_chooser_has_source (GdictSourceChooser *chooser,
+ const gchar *source_name)
+{
+ GdictSourceChooserPrivate *priv;
+
+ g_return_val_if_fail (GDICT_IS_SOURCE_CHOOSER (chooser), FALSE);
+ g_return_val_if_fail (source_name != NULL, FALSE);
+
+ priv = chooser->priv;
+
+ if (!priv->loader)
+ return FALSE;
+
+ return gdict_source_loader_has_source (priv->loader, source_name);
+}
+
+/**
+ * gdict_source_chooser_refresh:
+ * @chooser: a #GdictSourceChooser
+ *
+ * Forces a refresh on the contents of the source chooser widget
+ *
+ * Since: 0.12
+ */
+void
+gdict_source_chooser_refresh (GdictSourceChooser *chooser)
+{
+ GdictSourceChooserPrivate *priv;
+
+ g_return_if_fail (GDICT_IS_SOURCE_CHOOSER (chooser));
+
+ priv = chooser->priv;
+
+ if (priv->loader)
+ {
+ const GSList *sources, *l;
+
+ if (priv->treeview)
+ gtk_tree_view_set_model (GTK_TREE_VIEW (priv->treeview), NULL);
+
+ gtk_list_store_clear (priv->store);
+
+ sources = gdict_source_loader_get_sources (priv->loader);
+ for (l = sources; l != NULL; l = l->next)
+ {
+ GdictSource *source = l->data;
+ const gchar *name, *description;
+ GdictSourceTransport transport;
+ gint weight;
+
+ transport = gdict_source_get_transport (source);
+ name = gdict_source_get_name (source);
+ description = gdict_source_get_description (source);
+ weight = PANGO_WEIGHT_NORMAL;
+
+ if (priv->current_source && !strcmp (priv->current_source, name))
+ weight = PANGO_WEIGHT_BOLD;
+
+ gtk_list_store_insert_with_values (priv->store, NULL, -1,
+ SOURCE_TRANSPORT, transport,
+ SOURCE_NAME, name,
+ SOURCE_DESCRIPTION, description,
+ SOURCE_CURRENT, weight,
+ -1);
+ }
+
+ if (priv->treeview)
+ gtk_tree_view_set_model (GTK_TREE_VIEW (priv->treeview),
+ GTK_TREE_MODEL (priv->store));
+ }
+}
+
+/**
+ * gdict_source_chooser_add_button:
+ * @chooser: a #GdictSourceChooser
+ * @button_text: text of the button
+ *
+ * Adds a #GtkButton with @button_text to the button area on
+ * the bottom of @chooser. The @button_text can also be a
+ * stock ID.
+ *
+ * Return value: the newly packed button.
+ *
+ * Since: 0.12
+ */
+GtkWidget *
+gdict_source_chooser_add_button (GdictSourceChooser *chooser,
+ const gchar *button_text)
+{
+ GdictSourceChooserPrivate *priv;
+ GtkWidget *button;
+
+ g_return_val_if_fail (GDICT_IS_SOURCE_CHOOSER (chooser), NULL);
+ g_return_val_if_fail (button_text != NULL, NULL);
+
+ priv = chooser->priv;
+
+ button = gtk_button_new_from_stock (button_text);
+
+ gtk_widget_set_can_default (button, TRUE);
+
+ gtk_widget_show (button);
+
+ gtk_box_pack_end (GTK_BOX (priv->buttons_box), button, FALSE, TRUE, 0);
+
+ return button;
+}
+
diff --git a/mate-dictionary/libgdict/gdict-source-chooser.h b/mate-dictionary/libgdict/gdict-source-chooser.h
new file mode 100644
index 00000000..ed9bf3fc
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-source-chooser.h
@@ -0,0 +1,93 @@
+/* gdict-source-chooser.h - display widget for dictionary sources
+ *
+ * Copyright (C) 2007 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+#ifndef __GDICT_SOURCE_CHOOSER_H__
+#define __GDICT_SOURCE_CHOOSER_H__
+
+#include <gtk/gtk.h>
+#include "gdict-source-loader.h"
+
+G_BEGIN_DECLS
+
+#define GDICT_TYPE_SOURCE_CHOOSER (gdict_source_chooser_get_type ())
+#define GDICT_SOURCE_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDICT_TYPE_SOURCE_CHOOSER, GdictSourceChooser))
+#define GDICT_IS_SOURCE_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDICT_TYPE_SOURCE_CHOOSER))
+#define GDICT_SOURCE_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDICT_TYPE_SOURCE_CHOOSER, GdictSourceChooserClass))
+#define GDICT_IS_SOURCE_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDICT_TYPE_SOURCE_CHOOSER))
+#define GDICT_SOURCE_CHOOSER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GDICT_TYPE_SOURCE_CHOOSER, GdictSourceChooserClass))
+
+typedef struct _GdictSourceChooser GdictSourceChooser;
+typedef struct _GdictSourceChooserPrivate GdictSourceChooserPrivate;
+typedef struct _GdictSourceChooserClass GdictSourceChooserClass;
+
+struct _GdictSourceChooser
+{
+ /*< private >*/
+ GtkVBox parent_instance;
+
+ GdictSourceChooserPrivate *priv;
+};
+
+struct _GdictSourceChooserClass
+{
+ /*< private >*/
+ GtkVBoxClass parent_class;
+
+ /*< public >*/
+ void (*source_activated) (GdictSourceChooser *chooser,
+ const gchar *source_name,
+ GdictSource *source);
+ void (*selection_changed) (GdictSourceChooser *chooser);
+
+ /*< private >*/
+ /* padding for future expansion */
+ void (*_gdict_padding1) (void);
+ void (*_gdict_padding2) (void);
+ void (*_gdict_padding3) (void);
+ void (*_gdict_padding4) (void);
+ void (*_gdict_padding5) (void);
+ void (*_gdict_padding6) (void);
+};
+
+GType gdict_source_chooser_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gdict_source_chooser_new (void);
+GtkWidget * gdict_source_chooser_new_with_loader (GdictSourceLoader *loader);
+void gdict_source_chooser_set_loader (GdictSourceChooser *chooser,
+ GdictSourceLoader *loader);
+GdictSourceLoader *gdict_source_chooser_get_loader (GdictSourceChooser *chooser);
+gboolean gdict_source_chooser_select_source (GdictSourceChooser *chooser,
+ const gchar *source_name);
+gboolean gdict_source_chooser_unselect_source (GdictSourceChooser *chooser,
+ const gchar *source_name);
+gboolean gdict_source_chooser_set_current_source (GdictSourceChooser *chooser,
+ const gchar *source_name);
+gchar * gdict_source_chooser_get_current_source (GdictSourceChooser *chooser) G_GNUC_MALLOC;
+gchar ** gdict_source_chooser_get_sources (GdictSourceChooser *chooser,
+ gsize *length) G_GNUC_MALLOC;
+gint gdict_source_chooser_count_sources (GdictSourceChooser *chooser);
+gboolean gdict_source_chooser_has_source (GdictSourceChooser *chooser,
+ const gchar *source_name);
+void gdict_source_chooser_refresh (GdictSourceChooser *chooser);
+GtkWidget * gdict_source_chooser_add_button (GdictSourceChooser *chooser,
+ const gchar *button_text);
+
+G_END_DECLS
+
+#endif /* __GDICT_SOURCE_CHOOSER_H__ */
diff --git a/mate-dictionary/libgdict/gdict-source-loader.c b/mate-dictionary/libgdict/gdict-source-loader.c
new file mode 100644
index 00000000..71d919af
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-source-loader.c
@@ -0,0 +1,600 @@
+/* gdict-source-loader.c - Source loader for Gdict
+ *
+ * Copyright (C) 2005 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+/**
+ * SECTION:gdict-source-loader
+ * @short_description: Loader object for a set of dictionary sources
+ *
+ * #GdictSourceLoader allows searching for dictionary source definition
+ * files inside a set of paths and return a #GdictSource using its name.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+
+#include "gdict-source-loader.h"
+#include "gdict-utils.h"
+#include "gdict-enum-types.h"
+#include "gdict-marshal.h"
+#include "gdict-private.h"
+
+#define GDICT_SOURCE_FILE_SUFFIX ".desktop"
+
+#define GDICT_SOURCE_LOADER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDICT_TYPE_SOURCE_LOADER, GdictSourceLoaderPrivate))
+
+struct _GdictSourceLoaderPrivate
+{
+ GSList *paths;
+
+ GSList *sources;
+ GHashTable *sources_by_name;
+
+ guint paths_dirty : 1;
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_PATHS,
+ PROP_SOURCES
+};
+
+enum
+{
+ SOURCE_LOADED,
+
+ LAST_SIGNAL
+};
+
+static guint loader_signals[LAST_SIGNAL] = { 0 };
+
+
+
+G_DEFINE_TYPE (GdictSourceLoader, gdict_source_loader, G_TYPE_OBJECT);
+
+
+static void
+gdict_source_loader_finalize (GObject *object)
+{
+ GdictSourceLoaderPrivate *priv = GDICT_SOURCE_LOADER_GET_PRIVATE (object);
+
+ if (priv->paths)
+ {
+ g_slist_foreach (priv->paths,
+ (GFunc) g_free,
+ NULL);
+ g_slist_free (priv->paths);
+
+ priv->paths = NULL;
+ }
+
+ if (priv->sources_by_name)
+ g_hash_table_destroy (priv->sources_by_name);
+
+ if (priv->sources)
+ {
+ g_slist_foreach (priv->sources,
+ (GFunc) g_object_unref,
+ NULL);
+ g_slist_free (priv->sources);
+
+ priv->sources = NULL;
+ }
+
+ G_OBJECT_CLASS (gdict_source_loader_parent_class)->finalize (object);
+}
+
+static void
+gdict_source_loader_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (prop_id)
+ {
+ case PROP_PATHS:
+ break;
+ case PROP_SOURCES:
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gdict_source_loader_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (prop_id)
+ {
+ case PROP_PATHS:
+ break;
+ case PROP_SOURCES:
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gdict_source_loader_class_init (GdictSourceLoaderClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->set_property = gdict_source_loader_set_property;
+ gobject_class->get_property = gdict_source_loader_get_property;
+ gobject_class->finalize = gdict_source_loader_finalize;
+
+ /**
+ * GdictSourceLoader:paths
+ *
+ * The search paths used by this object
+ *
+ * Since: 1.0
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_PATHS,
+ g_param_spec_pointer ("paths",
+ _("Paths"),
+ _("Search paths used by this object"),
+ G_PARAM_READABLE));
+ /**
+ * GdictSourceLoader:sources
+ *
+ * The #GdictSource objects found by this object
+ *
+ * Since: 1.0
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_SOURCES,
+ g_param_spec_pointer ("sources",
+ _("Sources"),
+ _("Dictionary sources found"),
+ G_PARAM_READABLE));
+
+ /**
+ * GdictSourceLoader::source-loaded
+ * @loader: the object which received the signal
+ * @source: the new #GdictSource object found
+ *
+ * This signal is emitted when a new dictionary source has been added
+ * to the list.
+ *
+ * Since: 1.0
+ */
+ loader_signals[SOURCE_LOADED] =
+ g_signal_new ("source-loaded",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdictSourceLoaderClass, source_loaded),
+ NULL, NULL,
+ gdict_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GDICT_TYPE_SOURCE);
+
+ g_type_class_add_private (klass, sizeof (GdictSourceLoaderPrivate));
+}
+
+static void
+gdict_source_loader_init (GdictSourceLoader *loader)
+{
+ GdictSourceLoaderPrivate *priv;
+
+ priv = GDICT_SOURCE_LOADER_GET_PRIVATE (loader);
+ loader->priv = priv;
+
+ priv->paths = NULL;
+ /* add the default, system-wide path */
+ priv->paths = g_slist_prepend (priv->paths, g_strdup (GDICTSOURCESDIR));
+
+ priv->sources = NULL;
+ priv->sources_by_name = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ NULL);
+
+ /* ensure that the sources list will be updated */
+ priv->paths_dirty = TRUE;
+}
+
+/**
+ * gdict_source_loader_new:
+ *
+ * Creates a new #GdictSourceLoader object. This object is used to search
+ * into a list of paths for dictionary source files. See #GdictSource for
+ * more informations about the format of dictionary source files.
+ *
+ * Return value: a new #GdictSourceLoader object
+ *
+ * Since: 1.0
+ */
+GdictSourceLoader *
+gdict_source_loader_new (void)
+{
+ return g_object_new (GDICT_TYPE_SOURCE_LOADER, NULL);
+}
+
+/**
+ * gdict_source_loader_update:
+ * @loader: a #GdictSourceLoader
+ *
+ * Queue an update of the sources inside @loader.
+ *
+ * Since: 1.0
+ */
+void
+gdict_source_loader_update (GdictSourceLoader *loader)
+{
+ g_return_if_fail (GDICT_IS_SOURCE_LOADER (loader));
+
+ loader->priv->paths_dirty = TRUE;
+}
+
+/**
+ * gdict_source_loader_add_search_path:
+ * @loader: a #GdictSourceLoader
+ * @path: a path to be added to the search path list
+ *
+ * Adds @path to the search paths list of @loader.
+ *
+ * Since: 1.0
+ */
+void
+gdict_source_loader_add_search_path (GdictSourceLoader *loader,
+ const gchar *path)
+{
+ GSList *l;
+
+ g_return_if_fail (GDICT_IS_SOURCE_LOADER (loader));
+ g_return_if_fail (path != NULL);
+
+ /* avoid duplications */
+ for (l = loader->priv->paths; l != NULL; l = l->next)
+ if (strcmp (path, (gchar *) l->data) == 0)
+ return;
+
+ loader->priv->paths = g_slist_append (loader->priv->paths, g_strdup (path));
+ loader->priv->paths_dirty = TRUE;
+}
+
+/**
+ * gdict_source_loader_get_paths:
+ * @loader: a #GdictSourceLoader
+ *
+ * Gets the list of paths used by @loader to search for dictionary source
+ * files.
+ *
+ * Return value: a list containing the paths. The returned list is owned
+ * by the #GdictSourceLoader object and should never be free or modified.
+ *
+ * Since: 1.0
+ */
+const GSList *
+gdict_source_loader_get_paths (GdictSourceLoader *loader)
+{
+ g_return_val_if_fail (GDICT_IS_SOURCE_LOADER (loader), NULL);
+
+ return loader->priv->paths;
+}
+
+/* create the list of dictionary source files, by scanning the search path
+ * directories for .desktop files; we disavow symlinks and sub-directories
+ * for the time being.
+ */
+static GSList *
+build_source_filenames (GdictSourceLoader *loader)
+{
+ GSList *retval, *d;
+
+ g_assert (GDICT_IS_SOURCE_LOADER (loader));
+
+ if (!loader->priv->paths)
+ return NULL;
+
+ retval = NULL;
+ for (d = loader->priv->paths; d != NULL; d = d->next)
+ {
+ gchar *path = (gchar *) d->data;
+ const gchar *filename;
+ GDir *dir;
+
+ dir = g_dir_open (path, 0, NULL);
+ if (!dir)
+ continue;
+
+ do
+ {
+ filename = g_dir_read_name (dir);
+ if (filename)
+ {
+ gchar *full_path;
+
+ if (!g_str_has_suffix (filename, GDICT_SOURCE_FILE_SUFFIX))
+ break;
+
+ full_path = g_build_filename (path, filename, NULL);
+ if (g_file_test (full_path, G_FILE_TEST_IS_REGULAR))
+ {
+ retval = g_slist_prepend (retval, full_path);
+ }
+ }
+ }
+ while (filename != NULL);
+
+ g_dir_close (dir);
+ }
+
+ return g_slist_reverse (retval);
+}
+
+static void
+gdict_source_loader_update_sources (GdictSourceLoader *loader)
+{
+ GSList *filenames, *f;
+
+ g_assert (GDICT_IS_SOURCE_LOADER (loader));
+
+ g_slist_foreach (loader->priv->sources,
+ (GFunc) g_object_unref,
+ NULL);
+ g_slist_free (loader->priv->sources);
+ loader->priv->sources = NULL;
+
+ filenames = build_source_filenames (loader);
+ for (f = filenames; f != NULL; f = f->next)
+ {
+ GdictSource *source;
+ GError *load_err;
+ gchar *path = (gchar *) f->data;
+
+ g_assert (path != NULL);
+
+ source = gdict_source_new ();
+
+ load_err = NULL;
+ gdict_source_load_from_file (source, path, &load_err);
+ if (load_err)
+ {
+ g_warning ("Unable to load dictionary source at '%s': %s\n",
+ path,
+ load_err->message);
+ g_error_free (load_err);
+
+ continue;
+ }
+
+ loader->priv->sources = g_slist_append (loader->priv->sources,
+ source);
+ g_hash_table_replace (loader->priv->sources_by_name,
+ g_strdup (gdict_source_get_name (source)),
+ source);
+
+ g_signal_emit (loader, loader_signals[SOURCE_LOADED], 0, source);
+ }
+
+ g_slist_foreach (filenames,
+ (GFunc) g_free,
+ NULL);
+ g_slist_free (filenames);
+
+ loader->priv->paths_dirty = FALSE;
+}
+
+/**
+ * gdict_source_loader_get_names:
+ * @loader: a #GdictSourceLoader
+ * @length: return location for the number of source names, or %NULL
+ *
+ * Retrieves the list of dictionary source names available into the
+ * search paths of @loader.
+ *
+ * Return value: a newly allocated, %NULL terminated array of strings. You
+ * should free the returned string array with g_strfreev()
+ *
+ * Since: 1.0
+ */
+gchar **
+gdict_source_loader_get_names (GdictSourceLoader *loader,
+ gsize *length)
+{
+ GSList *l;
+ gchar **names;
+ gsize i;
+
+ g_return_val_if_fail (GDICT_IS_SOURCE_LOADER (loader), NULL);
+
+ if (loader->priv->paths_dirty)
+ gdict_source_loader_update_sources (loader);
+
+ names = g_new0 (gchar *, g_slist_length (loader->priv->sources) + 1);
+
+ i = 0;
+ for (l = loader->priv->sources; l != NULL; l = l->next)
+ {
+ GdictSource *s = GDICT_SOURCE (l->data);
+
+ g_assert (s != NULL);
+
+ names[i++] = g_strdup (gdict_source_get_name (s));
+ }
+ names[i] = NULL;
+
+ if (length)
+ *length = i;
+
+ return names;
+}
+
+/**
+ * gdict_source_loader_get_sources:
+ * @loader: a #GdictSourceLoader
+ *
+ * Retrieves the list of dictionary sources available into the search
+ * paths of @loader, in form of #GdictSource objects.
+ *
+ * Return value: a list of #GdictSource objects. The returned list
+ * is owned by the #GdictSourceLoader object, and should never be
+ * freed or modified.
+ *
+ * Since: 1.0
+ */
+const GSList *
+gdict_source_loader_get_sources (GdictSourceLoader *loader)
+{
+ g_return_val_if_fail (GDICT_IS_SOURCE_LOADER (loader), NULL);
+
+ if (loader->priv->paths_dirty)
+ gdict_source_loader_update_sources (loader);
+
+ return loader->priv->sources;
+}
+
+/**
+ * gdict_source_loader_get_source:
+ * @loader: a #GdictSourceLoader
+ * @name: a name of a dictionary source
+ *
+ * Retrieves a dictionary source using @name. You can use the returned
+ * #GdictSource object to create the right #GdictContext for that
+ * dictionary source.
+ *
+ * Return value: a referenced #GdictSource object. You should de-reference
+ * it using g_object_unref() when you finished using it.
+ *
+ * Since: 1.0
+ */
+GdictSource *
+gdict_source_loader_get_source (GdictSourceLoader *loader,
+ const gchar *name)
+{
+ GdictSource *retval;
+
+ g_return_val_if_fail (GDICT_IS_SOURCE_LOADER (loader), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ if (loader->priv->paths_dirty)
+ gdict_source_loader_update_sources (loader);
+
+ retval = g_hash_table_lookup (loader->priv->sources_by_name, name);
+ if (retval)
+ return g_object_ref (retval);
+
+ return NULL;
+}
+
+/**
+ * gdict_source_loader_remove_source:
+ * @loader: a #GdictSourceLoader
+ * @name: name of a dictionary source
+ *
+ * Removes the dictionary source @name from @loader. This function will
+ * also remove the dictionary source definition file bound to it.
+ *
+ * Return value: %TRUE if the dictionary source was successfully removed
+ *
+ * Since: 1.0
+ */
+gboolean
+gdict_source_loader_remove_source (GdictSourceLoader *loader,
+ const gchar *name)
+{
+ GdictSourceLoaderPrivate *priv;
+ GSList *l;
+
+ g_return_val_if_fail (GDICT_IS_SOURCE_LOADER (loader), FALSE);
+ g_return_val_if_fail (name != NULL, FALSE);
+
+ priv = loader->priv;
+
+ if (priv->paths_dirty)
+ gdict_source_loader_update_sources (loader);
+
+ for (l = priv->sources; l != NULL; l = l->next)
+ {
+ GdictSource *s = GDICT_SOURCE (l->data);
+
+ if (strcmp (gdict_source_get_name (s), name) == 0)
+ {
+ gchar *filename;
+
+ g_object_get (G_OBJECT (s), "filename", &filename, NULL);
+
+ if (g_unlink (filename) == -1)
+ {
+ g_warning ("Unable to remove filename '%s' for the "
+ "dictionary source '%s'\n",
+ filename,
+ name);
+
+ return FALSE;
+ }
+
+ g_hash_table_remove (priv->sources_by_name, name);
+
+ priv->sources = g_slist_remove_link (priv->sources, l);
+
+ g_object_unref (s);
+ g_slist_free (l);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * gdict_source_loader_has_source:
+ * @loader: a #GdictSourceLoader
+ * @source_name: the name of a dictionary source
+ *
+ * Checks whether @loader has a dictionary source with name @source_name.
+ *
+ * Return value: %TRUE if the dictionary source is known
+ *
+ * Since: 0.12
+ */
+gboolean
+gdict_source_loader_has_source (GdictSourceLoader *loader,
+ const gchar *source_name)
+{
+ g_return_val_if_fail (GDICT_IS_SOURCE_LOADER (loader), FALSE);
+ g_return_val_if_fail (source_name != NULL, FALSE);
+
+ if (loader->priv->paths_dirty)
+ gdict_source_loader_update_sources (loader);
+
+ return (g_hash_table_lookup (loader->priv->sources_by_name, source_name) != NULL);
+}
diff --git a/mate-dictionary/libgdict/gdict-source-loader.h b/mate-dictionary/libgdict/gdict-source-loader.h
new file mode 100644
index 00000000..51f7bb78
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-source-loader.h
@@ -0,0 +1,81 @@
+/* gdict-source-loader.h - Source loader for Gdict
+ *
+ * Copyright (C) 2005 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+#ifndef __GDICT_SOURCE_LOADER_H__
+#define __GDICT_SOURCE_LOADER_H__
+
+#include <glib-object.h>
+#include "gdict-source.h"
+
+G_BEGIN_DECLS
+
+#define GDICT_TYPE_SOURCE_LOADER (gdict_source_loader_get_type ())
+#define GDICT_SOURCE_LOADER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDICT_TYPE_SOURCE_LOADER, GdictSourceLoader))
+#define GDICT_IS_SOURCE_LOADER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDICT_TYPE_SOURCE_LOADER))
+#define GDICT_SOURCE_LOADER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDICT_TYPE_SOURCE_LOADER, GdictSourceLoaderClass))
+#define GDICT_IS_SOURCE_LOADER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDICT_TYPE_SOURCE_LOADER))
+#define GDICT_SOURCE_LOADER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GDICT_TYPE_SOURCE_LOADER, GdictSourceLoaderClass))
+
+typedef struct _GdictSourceLoader GdictSourceLoader;
+typedef struct _GdictSourceLoaderClass GdictSourceLoaderClass;
+typedef struct _GdictSourceLoaderPrivate GdictSourceLoaderPrivate;
+
+
+struct _GdictSourceLoader
+{
+ /*< private >*/
+ GObject parent_instance;
+
+ GdictSourceLoaderPrivate *priv;
+};
+
+struct _GdictSourceLoaderClass
+{
+ GObjectClass parent_class;
+
+ void (*source_loaded) (GdictSourceLoader *loader,
+ GdictSource *source);
+
+ /* padding for future expansion */
+ void (*_gdict_source_1) (void);
+ void (*_gdict_source_2) (void);
+ void (*_gdict_source_3) (void);
+ void (*_gdict_source_4) (void);
+};
+
+GType gdict_source_loader_get_type (void) G_GNUC_CONST;
+
+GdictSourceLoader * gdict_source_loader_new (void);
+void gdict_source_loader_update (GdictSourceLoader *loader);
+void gdict_source_loader_add_search_path (GdictSourceLoader *loader,
+ const gchar *path);
+const GSList *gdict_source_loader_get_paths (GdictSourceLoader *loader);
+gchar ** gdict_source_loader_get_names (GdictSourceLoader *loader,
+ gsize *length) G_GNUC_MALLOC;
+const GSList *gdict_source_loader_get_sources (GdictSourceLoader *loader);
+GdictSource * gdict_source_loader_get_source (GdictSourceLoader *loader,
+ const gchar *name);
+gboolean gdict_source_loader_has_source (GdictSourceLoader *loader,
+ const gchar *source_name);
+gboolean gdict_source_loader_remove_source (GdictSourceLoader *loader,
+ const gchar *name);
+
+G_END_DECLS
+
+#endif /* __GDICT_SOURCE_LOADER_H__ */
diff --git a/mate-dictionary/libgdict/gdict-source.c b/mate-dictionary/libgdict/gdict-source.c
new file mode 100644
index 00000000..aea506d2
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-source.c
@@ -0,0 +1,1195 @@
+/* gdict-source.c - Source configuration for Gdict
+ *
+ * Copyright (C) 2005 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+/**
+ * SECTION:gdict-source
+ * @short_description: A dictionary source definition
+ *
+ * #GdictSource is the representation of a #GdictContext. Each dictionary
+ * source provides a list of available dictionaries (databases) and a list
+ * of available matching strategies. Using a #GdictContext you can query
+ * the dictionary source for matching words and for definitions.
+ *
+ * By using a #GdictSource object you can retrieve the appropriate
+ * #GdictContext, already set up with the right parameters.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "gdict-source.h"
+#include "gdict-client-context.h"
+#include "gdict-utils.h"
+#include "gdict-enum-types.h"
+#include "gdict-private.h"
+
+/* Main group */
+#define SOURCE_GROUP "Dictionary Source"
+
+/* Common keys */
+#define SOURCE_KEY_NAME "Name"
+#define SOURCE_KEY_DESCRIPTION "Description"
+#define SOURCE_KEY_TRANSPORT "Transport"
+#define SOURCE_KEY_DATABASE "Database"
+#define SOURCE_KEY_STRATEGY "Strategy"
+
+/* dictd transport keys */
+#define SOURCE_KEY_HOSTNAME "Hostname"
+#define SOURCE_KEY_PORT "Port"
+
+
+#define GDICT_SOURCE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDICT_TYPE_SOURCE, GdictSourcePrivate))
+
+struct _GdictSourcePrivate
+{
+ gchar *filename;
+ GKeyFile *keyfile;
+
+ gchar *name;
+ gchar *description;
+
+ gchar *database;
+ gchar *strategy;
+
+ GdictSourceTransport transport;
+
+ GdictContext *context;
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_FILENAME,
+ PROP_NAME,
+ PROP_DESCRIPTION,
+ PROP_DATABASE,
+ PROP_STRATEGY,
+ PROP_TRANSPORT,
+ PROP_CONTEXT
+};
+
+/* keep in sync with GdictSourceTransport */
+static const gchar *valid_transports[] =
+{
+ "dictd", /* GDICT_SOURCE_TRANSPORT_DICTD */
+
+ NULL /* GDICT_SOURCE_TRANSPORT_INVALID */
+};
+
+#define IS_VALID_TRANSPORT(t) (((t) >= GDICT_SOURCE_TRANSPORT_DICTD) && \
+ ((t) < GDICT_SOURCE_TRANSPORT_INVALID))
+
+GQuark
+gdict_source_error_quark (void)
+{
+ static GQuark quark = 0;
+
+ if (G_UNLIKELY (quark == 0))
+ quark = g_quark_from_static_string ("gdict-source-error-quark");
+
+ return quark;
+}
+
+
+G_DEFINE_TYPE (GdictSource, gdict_source, G_TYPE_OBJECT);
+
+
+
+static void
+gdict_source_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GdictSource *source = GDICT_SOURCE (object);
+
+ switch (prop_id)
+ {
+ case PROP_NAME:
+ gdict_source_set_name (source, g_value_get_string (value));
+ break;
+ case PROP_DESCRIPTION:
+ gdict_source_set_description (source, g_value_get_string (value));
+ break;
+ case PROP_TRANSPORT:
+ gdict_source_set_transport (source, g_value_get_enum (value), NULL);
+ break;
+ case PROP_DATABASE:
+ gdict_source_set_database (source, g_value_get_string (value));
+ break;
+ case PROP_STRATEGY:
+ gdict_source_set_strategy (source, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gdict_source_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GdictSource *source = GDICT_SOURCE (object);
+ GdictSourcePrivate *priv = source->priv;
+
+ switch (prop_id)
+ {
+ case PROP_FILENAME:
+ g_value_set_string (value, priv->filename);
+ break;
+ case PROP_NAME:
+ g_value_set_string (value, priv->name);
+ break;
+ case PROP_DESCRIPTION:
+ g_value_set_string (value, priv->description);
+ break;
+ case PROP_DATABASE:
+ g_value_set_string (value, priv->database);
+ break;
+ case PROP_STRATEGY:
+ g_value_set_string (value, priv->strategy);
+ break;
+ case PROP_TRANSPORT:
+ g_value_set_enum (value, priv->transport);
+ break;
+ case PROP_CONTEXT:
+ g_value_set_object (value, gdict_source_peek_context (source));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gdict_source_finalize (GObject *object)
+{
+ GdictSourcePrivate *priv = GDICT_SOURCE_GET_PRIVATE (object);
+
+ g_free (priv->filename);
+
+ if (priv->keyfile)
+ g_key_file_free (priv->keyfile);
+
+ g_free (priv->name);
+ g_free (priv->description);
+
+ g_free (priv->database);
+ g_free (priv->strategy);
+
+ if (priv->context)
+ g_object_unref (priv->context);
+
+ G_OBJECT_CLASS (gdict_source_parent_class)->finalize (object);
+}
+
+static void
+gdict_source_class_init (GdictSourceClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->set_property = gdict_source_set_property;
+ gobject_class->get_property = gdict_source_get_property;
+ gobject_class->finalize = gdict_source_finalize;
+
+ /**
+ * GdictSource:filename
+ *
+ * The filename used by this dictionary source.
+ *
+ * Since: 1.0
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_FILENAME,
+ g_param_spec_string ("filename",
+ _("Filename"),
+ _("The filename used by this dictionary source"),
+ NULL,
+ G_PARAM_READABLE));
+ /**
+ * GdictSource:name
+ *
+ * The display name of this dictionary source.
+ *
+ * Since: 1.0
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_NAME,
+ g_param_spec_string ("name",
+ _("Name"),
+ _("The display name of this dictonary source"),
+ NULL,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ /**
+ * GdictSource:description
+ *
+ * The description of this dictionary source.
+ *
+ * Since: 1.0
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_DESCRIPTION,
+ g_param_spec_string ("description",
+ _("Description"),
+ _("The description of this dictionary source"),
+ NULL,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ /**
+ * GdictSource:database
+ *
+ * The default database of this dictionary source.
+ *
+ * Since: 1.0
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_DATABASE,
+ g_param_spec_string ("database",
+ _("Database"),
+ _("The default database of this dictonary source"),
+ NULL,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ /**
+ * GdictSource:strategy
+ *
+ * The default strategy of this dictionary source.
+ *
+ * Since: 1.0
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_STRATEGY,
+ g_param_spec_string ("strategy",
+ _("Strategy"),
+ _("The default strategy of this dictonary source"),
+ NULL,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ /**
+ * GdictSource:transport
+ *
+ * The transport mechanism used by this source.
+ *
+ * Since: 1.0
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_TRANSPORT,
+ g_param_spec_enum ("transport",
+ _("Transport"),
+ _("The transport mechanism used by this dictionary source"),
+ GDICT_TYPE_SOURCE_TRANSPORT,
+ GDICT_SOURCE_TRANSPORT_INVALID,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ /**
+ * GdictSource:context
+ *
+ * The #GdictContext bound to this source.
+ *
+ * Since: 1.0
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_CONTEXT,
+ g_param_spec_object ("context",
+ _("Context"),
+ _("The GdictContext bound to this source"),
+ GDICT_TYPE_CONTEXT,
+ G_PARAM_READABLE));
+
+ g_type_class_add_private (klass, sizeof (GdictSourcePrivate));
+}
+
+static void
+gdict_source_init (GdictSource *source)
+{
+ GdictSourcePrivate *priv;
+
+ priv = GDICT_SOURCE_GET_PRIVATE (source);
+ source->priv = priv;
+
+ priv->filename = NULL;
+ priv->keyfile = g_key_file_new ();
+
+ priv->name = NULL;
+ priv->description = NULL;
+ priv->database = NULL;
+ priv->strategy = NULL;
+ priv->transport = GDICT_SOURCE_TRANSPORT_INVALID;
+
+ priv->context = NULL;
+}
+
+/**
+ * gdict_source_new:
+ *
+ * Creates an empty #GdictSource object. Use gdict_load_from_file() to
+ * read an existing dictionary source definition file.
+ *
+ * Return value: an empty #GdictSource
+ */
+GdictSource *
+gdict_source_new (void)
+{
+ return g_object_new (GDICT_TYPE_SOURCE, NULL);
+}
+
+static GdictSourceTransport
+gdict_source_resolve_transport (const gchar *transport)
+{
+ if (!transport)
+ return GDICT_SOURCE_TRANSPORT_INVALID;
+
+ if (strcmp (transport, "dictd") == 0)
+ return GDICT_SOURCE_TRANSPORT_DICTD;
+ else
+ return GDICT_SOURCE_TRANSPORT_INVALID;
+
+ g_assert_not_reached ();
+}
+
+static GdictContext *
+gdict_source_create_context (GdictSource *source,
+ GdictSourceTransport transport,
+ GError **error)
+{
+ GdictSourcePrivate *priv;
+ GdictContext *context;
+
+ g_assert (GDICT_IS_SOURCE (source));
+
+ priv = source->priv;
+
+ switch (transport)
+ {
+ case GDICT_SOURCE_TRANSPORT_DICTD:
+ {
+ gchar *hostname;
+ gint port;
+
+ hostname = g_key_file_get_string (priv->keyfile,
+ SOURCE_GROUP,
+ SOURCE_KEY_HOSTNAME,
+ NULL);
+
+ port = g_key_file_get_integer (priv->keyfile,
+ SOURCE_GROUP,
+ SOURCE_KEY_PORT,
+ NULL);
+ if (!port)
+ port = -1;
+
+ context = gdict_client_context_new (hostname, port);
+
+ if (hostname)
+ g_free (hostname);
+ }
+ break;
+ default:
+ g_set_error (error, GDICT_SOURCE_ERROR,
+ GDICT_SOURCE_ERROR_PARSE,
+ _("Invalid transport type '%d'"),
+ transport);
+ return NULL;
+ }
+
+ g_assert (context != NULL);
+
+ if (priv->transport != transport)
+ priv->transport = transport;
+
+ return context;
+}
+
+static gboolean
+gdict_source_parse (GdictSource *source,
+ GError **error)
+{
+ GdictSourcePrivate *priv;
+ GError *parse_error;
+ gchar *transport;
+ GdictSourceTransport t;
+
+ priv = source->priv;
+
+ if (!g_key_file_has_group (priv->keyfile, SOURCE_GROUP))
+ {
+ g_set_error (error, GDICT_SOURCE_ERROR,
+ GDICT_SOURCE_ERROR_PARSE,
+ _("No '%s' group found inside the dictionary source definition"),
+ SOURCE_GROUP);
+
+ return FALSE;
+ }
+
+ /* fetch the name for the dictionary source definition */
+ parse_error = NULL;
+ priv->name = g_key_file_get_string (priv->keyfile,
+ SOURCE_GROUP,
+ SOURCE_KEY_NAME,
+ &parse_error);
+ if (parse_error)
+ {
+ g_set_error (error, GDICT_SOURCE_ERROR,
+ GDICT_SOURCE_ERROR_PARSE,
+ _("Unable to get the '%s' key inside the dictionary "
+ "source definition: %s"),
+ SOURCE_KEY_NAME,
+ parse_error->message);
+ g_error_free (parse_error);
+
+ g_key_file_free (priv->keyfile);
+ priv->keyfile = NULL;
+
+ return FALSE;
+ }
+
+ /* if present, fetch the localized description */
+ if (g_key_file_has_key (priv->keyfile, SOURCE_GROUP, SOURCE_KEY_DESCRIPTION, NULL))
+ {
+ priv->description = g_key_file_get_locale_string (priv->keyfile,
+ SOURCE_GROUP,
+ SOURCE_KEY_DESCRIPTION,
+ NULL,
+ &parse_error);
+ if (parse_error)
+ {
+ g_set_error (error, GDICT_SOURCE_ERROR,
+ GDICT_SOURCE_ERROR_PARSE,
+ _("Unable to get the '%s' key inside the dictionary "
+ "source definition: %s"),
+ SOURCE_KEY_DESCRIPTION,
+ parse_error->message);
+
+ g_error_free (parse_error);
+ g_key_file_free (priv->keyfile);
+ priv->keyfile = NULL;
+ g_free (priv->name);
+
+ return FALSE;
+ }
+ }
+
+ if (g_key_file_has_key (priv->keyfile, SOURCE_GROUP, SOURCE_KEY_DATABASE, NULL))
+ {
+ priv->database = g_key_file_get_string (priv->keyfile,
+ SOURCE_GROUP,
+ SOURCE_KEY_DATABASE,
+ &parse_error);
+ if (parse_error)
+ {
+ g_set_error (error, GDICT_SOURCE_ERROR,
+ GDICT_SOURCE_ERROR_PARSE,
+ _("Unable to get the '%s' key inside the dictionary "
+ "source definition: %s"),
+ SOURCE_KEY_DATABASE,
+ parse_error->message);
+
+ g_error_free (parse_error);
+ g_key_file_free (priv->keyfile);
+ priv->keyfile = NULL;
+ g_free (priv->name);
+ g_free (priv->description);
+
+ return FALSE;
+ }
+ }
+
+ if (g_key_file_has_key (priv->keyfile, SOURCE_GROUP, SOURCE_KEY_STRATEGY, NULL))
+ {
+ priv->strategy = g_key_file_get_string (priv->keyfile,
+ SOURCE_GROUP,
+ SOURCE_KEY_STRATEGY,
+ &parse_error);
+ if (parse_error)
+ {
+ g_set_error (error, GDICT_SOURCE_ERROR,
+ GDICT_SOURCE_ERROR_PARSE,
+ _("Unable to get the '%s' key inside the dictionary "
+ "source definition: %s"),
+ SOURCE_KEY_STRATEGY,
+ parse_error->message);
+
+ g_error_free (parse_error);
+ g_key_file_free (priv->keyfile);
+ priv->keyfile = NULL;
+
+ g_free (priv->name);
+ g_free (priv->description);
+ g_free (priv->database);
+
+ return FALSE;
+ }
+ }
+
+ transport = g_key_file_get_string (priv->keyfile,
+ SOURCE_GROUP,
+ SOURCE_KEY_TRANSPORT,
+ &parse_error);
+ if (parse_error)
+ {
+ g_set_error (error, GDICT_SOURCE_ERROR,
+ GDICT_SOURCE_ERROR_PARSE,
+ _("Unable to get the '%s' key inside the dictionary "
+ "source definition file: %s"),
+ SOURCE_KEY_TRANSPORT,
+ parse_error->message);
+
+ g_error_free (parse_error);
+ g_key_file_free (priv->keyfile);
+ priv->keyfile = NULL;
+ g_free (priv->name);
+ g_free (priv->description);
+ g_free (priv->database);
+ g_free (priv->strategy);
+
+ return FALSE;
+ }
+
+ t = gdict_source_resolve_transport (transport);
+ g_free (transport);
+
+ priv->context = gdict_source_create_context (source, t, &parse_error);
+ if (parse_error)
+ {
+ g_propagate_error (error, parse_error);
+
+ g_key_file_free (priv->keyfile);
+ priv->keyfile = NULL;
+
+ g_free (priv->name);
+ g_free (priv->description);
+ g_free (priv->database);
+ g_free (priv->strategy);
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * gdict_source_load_from_file:
+ * @source: an empty #GdictSource
+ * @filename: path to a dictionary source file
+ * @error: return location for a #GError or %NULL
+ *
+ * Loads a dictionary source definition file into an empty #GdictSource
+ * object.
+ *
+ * Return value: %TRUE if @filename was loaded successfully.
+ *
+ * Since: 1.0
+ */
+gboolean
+gdict_source_load_from_file (GdictSource *source,
+ const gchar *filename,
+ GError **error)
+{
+ GdictSourcePrivate *priv;
+ GError *read_error;
+ GError *parse_error;
+
+ g_return_val_if_fail (GDICT_IS_SOURCE (source), FALSE);
+ g_return_val_if_fail (filename != NULL, FALSE);
+
+ priv = source->priv;
+
+ if (!priv->keyfile)
+ priv->keyfile = g_key_file_new ();
+
+ read_error = NULL;
+ g_key_file_load_from_file (priv->keyfile,
+ filename,
+ G_KEY_FILE_KEEP_TRANSLATIONS,
+ &read_error);
+ if (read_error)
+ {
+ g_propagate_error (error, read_error);
+
+ return FALSE;
+ }
+
+ parse_error = NULL;
+ gdict_source_parse (source, &parse_error);
+ if (parse_error)
+ {
+ g_propagate_error (error, parse_error);
+
+ return FALSE;
+ }
+
+ g_assert (priv->context != NULL);
+
+ priv->filename = g_strdup (filename);
+
+ return TRUE;
+}
+
+/**
+ * gdict_source_load_from_data:
+ * @source: a #GdictSource
+ * @data: string containing a dictionary source
+ * @length: length of @data
+ * @error: return location for a #GError or %NULL
+ *
+ * Loads a dictionary source definition from @data inside an empty
+ * #GdictSource object.
+ *
+ * Return value: %TRUE if @filename was loaded successfully.
+ *
+ * Since: 1.0
+ */
+gboolean
+gdict_source_load_from_data (GdictSource *source,
+ const gchar *data,
+ gsize length,
+ GError **error)
+{
+ GdictSourcePrivate *priv;
+ GError *read_error;
+ GError *parse_error;
+
+ g_return_val_if_fail (GDICT_IS_SOURCE (source), FALSE);
+ g_return_val_if_fail (data != NULL, FALSE);
+
+ priv = source->priv;
+
+ if (!priv->keyfile)
+ priv->keyfile = g_key_file_new ();
+
+ read_error = NULL;
+ g_key_file_load_from_data (priv->keyfile,
+ data,
+ length,
+ G_KEY_FILE_KEEP_TRANSLATIONS,
+ &read_error);
+ if (read_error)
+ {
+ g_propagate_error (error, read_error);
+
+ return FALSE;
+ }
+
+ parse_error = NULL;
+ gdict_source_parse (source, &parse_error);
+ if (parse_error)
+ {
+ g_propagate_error (error, parse_error);
+
+ return FALSE;
+ }
+
+ g_assert (priv->context != NULL);
+
+ g_free (priv->filename);
+ priv->filename = NULL;
+
+ return TRUE;
+}
+
+/**
+ * gdict_source_to_data:
+ * @source: a #GdictSource
+ * @length: return loaction for the length of the string, or %NULL
+ * @error: return location for a #GError or %NULL
+ *
+ * Outputs a dictionary source as a string.
+ *
+ * Return value: a newly allocated string holding the contents of @source.
+ *
+ * Since: 1.0
+ */
+gchar *
+gdict_source_to_data (GdictSource *source,
+ gsize *length,
+ GError **error)
+{
+ GdictSourcePrivate *priv;
+ gchar *retval = NULL;
+
+ g_return_val_if_fail (GDICT_IS_SOURCE (source), NULL);
+
+ priv = source->priv;
+
+ if (!priv->name)
+ {
+ g_set_error (error, GDICT_SOURCE_ERROR,
+ GDICT_SOURCE_ERROR_INVALID_NAME,
+ _("Dictionary source does not have name"));
+
+ return NULL;
+ }
+
+ if (!IS_VALID_TRANSPORT (priv->transport))
+ {
+ g_set_error (error, GDICT_SOURCE_ERROR,
+ GDICT_SOURCE_ERROR_INVALID_TRANSPORT,
+ _("Dictionary source '%s' has invalid transport '%s'"),
+ priv->name,
+ valid_transports[priv->transport]);
+
+ return NULL;
+ }
+
+ if (priv->keyfile)
+ {
+ GError *write_error = NULL;
+
+ retval = g_key_file_to_data (priv->keyfile,
+ length,
+ &write_error);
+ if (write_error)
+ g_propagate_error (error, write_error);
+ }
+
+ return retval;
+}
+
+/**
+ * gdict_source_set_name:
+ * @source: a #GdictSource
+ * @name: the UTF8-encoded name of the dictionary source
+ *
+ * Sets @name as the displayable name of the dictionary source.
+ *
+ * Since: 1.0
+ */
+void
+gdict_source_set_name (GdictSource *source,
+ const gchar *name)
+{
+ g_return_if_fail (GDICT_IS_SOURCE (source));
+ g_return_if_fail (name != NULL);
+
+ g_free (source->priv->name);
+ source->priv->name = g_strdup (name);
+
+ if (!source->priv->keyfile)
+ source->priv->keyfile = g_key_file_new ();
+
+ g_key_file_set_string (source->priv->keyfile,
+ SOURCE_GROUP,
+ SOURCE_KEY_NAME,
+ name);
+}
+
+/**
+ * gdict_source_get_name:
+ * @source: a #GdictSource
+ *
+ * Retrieves the name of @source.
+ *
+ * Return value: the name of a #GdictSource. The returned string is owned
+ * by the #GdictSource object, and should not be modified or freed.
+ *
+ * Since: 1.0
+ */
+const gchar *
+gdict_source_get_name (GdictSource *source)
+{
+ g_return_val_if_fail (GDICT_IS_SOURCE (source), NULL);
+
+ return source->priv->name;
+}
+
+/**
+ * gdict_source_set_description:
+ * @source: a #GdictSource
+ * @description: a UTF-8 encoded description or %NULL
+ *
+ * Sets the description of @source. If @description is %NULL, unsets the
+ * currently set description.
+ *
+ * Since: 1.0
+ */
+void
+gdict_source_set_description (GdictSource *source,
+ const gchar *description)
+{
+ g_return_if_fail (GDICT_IS_SOURCE (source));
+
+ g_free (source->priv->description);
+
+ if (!source->priv->keyfile)
+ source->priv->keyfile = g_key_file_new ();
+
+ if (description && description[0] != '\0')
+ {
+ source->priv->description = g_strdup (description);
+
+ g_key_file_set_string (source->priv->keyfile,
+ SOURCE_GROUP,
+ SOURCE_KEY_DESCRIPTION,
+ description);
+ }
+ else
+ {
+ if (g_key_file_has_key (source->priv->keyfile,
+ SOURCE_GROUP,
+ SOURCE_KEY_DESCRIPTION,
+ NULL))
+ g_key_file_remove_key (source->priv->keyfile,
+ SOURCE_GROUP,
+ SOURCE_KEY_DESCRIPTION,
+ NULL);
+ }
+}
+
+/**
+ * gdict_source_get_description:
+ * @source: a #GdictSource
+ *
+ * Retrieves the description of @source.
+ *
+ * Return value: the description of a #GdictSource. The returned string is
+ * owned by the #GdictSource object, and should not be modified or freed.
+ *
+ * Since: 1.0
+ */
+const gchar *
+gdict_source_get_description (GdictSource *source)
+{
+ g_return_val_if_fail (GDICT_IS_SOURCE (source), NULL);
+
+ return source->priv->description;
+}
+
+/**
+ * gdict_source_set_database:
+ * @source: a #GdictSource
+ * @database: a UTF-8 encoded database name or %NULL
+ *
+ * Sets the default database of @source. If @database is %NULL, unsets the
+ * currently set database.
+ *
+ * Since: 1.0
+ */
+void
+gdict_source_set_database (GdictSource *source,
+ const gchar *database)
+{
+ g_return_if_fail (GDICT_IS_SOURCE (source));
+
+ g_free (source->priv->database);
+
+ if (!source->priv->keyfile)
+ source->priv->keyfile = g_key_file_new ();
+
+ if (database && database[0] != '\0')
+ {
+ source->priv->database = g_strdup (database);
+
+ g_key_file_set_string (source->priv->keyfile,
+ SOURCE_GROUP,
+ SOURCE_KEY_DATABASE,
+ database);
+ }
+ else
+ {
+ if (g_key_file_has_key (source->priv->keyfile,
+ SOURCE_GROUP,
+ SOURCE_KEY_DATABASE,
+ NULL))
+ g_key_file_remove_key (source->priv->keyfile,
+ SOURCE_GROUP,
+ SOURCE_KEY_DATABASE,
+ NULL);
+ }
+}
+
+/**
+ * gdict_source_get_database:
+ * @source: a #GdictSource
+ *
+ * Retrieves the default database of @source.
+ *
+ * Return value: the default strategy of a #GdictSource. The returned string
+ * is owned by the #GdictSource object, and should not be modified or freed.
+ *
+ * Since: 1.0
+ */
+const gchar *
+gdict_source_get_database (GdictSource *source)
+{
+ g_return_val_if_fail (GDICT_IS_SOURCE (source), NULL);
+
+ return source->priv->database;
+}
+
+/**
+ * gdict_source_set_strategy:
+ * @source: a #GdictSource
+ * @strategy: a UTF-8 encoded strategy or %NULL
+ *
+ * Sets the description of @source. If @strategy is %NULL, unsets the
+ * currently set strategy.
+ *
+ * Since: 1.0
+ */
+void
+gdict_source_set_strategy (GdictSource *source,
+ const gchar *strategy)
+{
+ g_return_if_fail (GDICT_IS_SOURCE (source));
+
+ g_free (source->priv->strategy);
+
+ if (!source->priv->keyfile)
+ source->priv->keyfile = g_key_file_new ();
+
+ if (strategy && strategy[0] != '\0')
+ {
+ source->priv->strategy = g_strdup (strategy);
+
+ g_key_file_set_string (source->priv->keyfile,
+ SOURCE_GROUP,
+ SOURCE_KEY_STRATEGY,
+ strategy);
+ }
+ else
+ {
+ if (g_key_file_has_key (source->priv->keyfile,
+ SOURCE_GROUP,
+ SOURCE_KEY_STRATEGY,
+ NULL))
+ g_key_file_remove_key (source->priv->keyfile,
+ SOURCE_GROUP,
+ SOURCE_KEY_STRATEGY,
+ NULL);
+ }
+}
+
+/**
+ * gdict_source_get_strategy:
+ * @source: a #GdictSource
+ *
+ * Retrieves the default strategy of @source.
+ *
+ * Return value: the default strategy of a #GdictSource. The returned string
+ * is owned by the #GdictSource object, and should not be modified or freed.
+ *
+ * Since: 1.0
+ */
+const gchar *
+gdict_source_get_strategy (GdictSource *source)
+{
+ g_return_val_if_fail (GDICT_IS_SOURCE (source), NULL);
+
+ return source->priv->strategy;
+}
+
+/**
+ * gdict_source_set_transportv
+ * @source: a #GdictSource
+ * @transport: a #GdictSourceTransport
+ * @first_transport_property: FIXME
+ * @var_args: FIXME
+ *
+ * FIXME
+ *
+ * Since: 1.0
+ */
+void
+gdict_source_set_transportv (GdictSource *source,
+ GdictSourceTransport transport,
+ const gchar *first_transport_property,
+ va_list var_args)
+{
+ GdictSourcePrivate *priv;
+
+ g_return_if_fail (GDICT_IS_SOURCE (source));
+ g_return_if_fail (IS_VALID_TRANSPORT (transport));
+
+ priv = source->priv;
+
+ priv->transport = transport;
+
+ if (priv->context)
+ g_object_unref (priv->context);
+
+ switch (priv->transport)
+ {
+ case GDICT_SOURCE_TRANSPORT_DICTD:
+ priv->context = gdict_client_context_new (NULL, -1);
+ g_assert (GDICT_IS_CLIENT_CONTEXT (priv->context));
+
+ g_object_set_valist (G_OBJECT (priv->context),
+ first_transport_property,
+ var_args);
+
+ break;
+ case GDICT_SOURCE_TRANSPORT_INVALID:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ /* update the keyfile */
+ if (!priv->keyfile)
+ priv->keyfile = g_key_file_new ();
+
+ g_key_file_set_string (priv->keyfile,
+ SOURCE_GROUP,
+ SOURCE_KEY_TRANSPORT,
+ valid_transports[transport]);
+
+ switch (priv->transport)
+ {
+ case GDICT_SOURCE_TRANSPORT_DICTD:
+ g_key_file_set_string (priv->keyfile,
+ SOURCE_GROUP,
+ SOURCE_KEY_HOSTNAME,
+ gdict_client_context_get_hostname (GDICT_CLIENT_CONTEXT (priv->context)));
+ g_key_file_set_integer (priv->keyfile,
+ SOURCE_GROUP,
+ SOURCE_KEY_PORT,
+ gdict_client_context_get_port (GDICT_CLIENT_CONTEXT (priv->context)));
+ break;
+ case GDICT_SOURCE_TRANSPORT_INVALID:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+/**
+ * gdict_source_set_transport:
+ * @source: a #GdictSource
+ * @transport: a valid transport
+ * @first_transport_property: property for the context bound to
+ * the transport, or %NULL
+ * @Varargs: property value for first property name, then additionary
+ * properties, ending with %NULL
+ *
+ * Sets @transport as the choosen transport for @source. The @transport
+ * argument is a method of retrieving dictionary data from a source; it is
+ * used to create the right #GdictContext for this #GdictSource. After
+ * @transport, property name/value pairs should be listed, with a %NULL
+ * pointer ending the list. Properties are the same passed to a #GdictContext
+ * implementation instance using g_object_set().
+ *
+ * Here's a simple example:
+ *
+ * <informalexample><programlisting>
+ * #include &lt;gdict/gdict.h&gt;
+ * GdictSource *source = gdict_source_new ();
+ *
+ * gdict_source_set_name (source, "My Source");
+ * gdict_source_set_transport (source, GDICT_SOURCE_TRANSPORT_DICTD,
+ * "hostname", "dictionary-server.org",
+ * "port", 2628,
+ * NULL);
+ * </programlisting></informalexample>
+ *
+ * Since: 1.0
+ */
+void
+gdict_source_set_transport (GdictSource *source,
+ GdictSourceTransport transport,
+ const gchar *first_transport_property,
+ ...)
+{
+ va_list args;
+
+ g_return_if_fail (GDICT_IS_SOURCE (source));
+ g_return_if_fail (IS_VALID_TRANSPORT (transport));
+
+ va_start (args, first_transport_property);
+
+ gdict_source_set_transportv (source, transport,
+ first_transport_property,
+ args);
+
+ va_end (args);
+}
+
+/**
+ * gdict_source_get_transport:
+ * @source: a #GdictSource
+ *
+ * FIXME
+ *
+ * Return value: FIXME
+ *
+ * Since: 1.0
+ */
+GdictSourceTransport
+gdict_source_get_transport (GdictSource *source)
+{
+ g_return_val_if_fail (GDICT_IS_SOURCE (source), GDICT_SOURCE_TRANSPORT_INVALID);
+
+ return source->priv->transport;
+}
+
+/**
+ * gdict_source_get_context:
+ * @source: a #GdictSource
+ *
+ * Gets the #GdictContext bound to @source.
+ *
+ * Return value: a #GdictContext for @source. Use g_object_unref()
+ * when you don't need it anymore.
+ *
+ * Since: 1.0
+ */
+GdictContext *
+gdict_source_get_context (GdictSource *source)
+{
+ GdictContext *retval;
+
+ g_return_val_if_fail (GDICT_IS_SOURCE (source), NULL);
+
+ retval = gdict_source_create_context (source,
+ source->priv->transport,
+ NULL);
+
+ return retval;
+}
+
+/**
+ * gdict_source_peek_context:
+ * @source: a #GdictSource
+ *
+ * Gets the #GdictContext bound to @source. The returned object is a
+ * referenced copy of the context held by @source; if you want a different
+ * instance, use gdict_source_get_context().
+ *
+ * Return value: a referenced #GdictContext. Use g_object_unref() when
+ * finished using it.
+ *
+ * Since: 1.0
+ */
+GdictContext *
+gdict_source_peek_context (GdictSource *source)
+{
+ g_return_val_if_fail (GDICT_IS_SOURCE (source), NULL);
+
+ if (!source->priv->context)
+ source->priv->context = gdict_source_create_context (source,
+ source->priv->transport,
+ NULL);
+ return g_object_ref (source->priv->context);
+}
diff --git a/mate-dictionary/libgdict/gdict-source.h b/mate-dictionary/libgdict/gdict-source.h
new file mode 100644
index 00000000..b51ab97c
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-source.h
@@ -0,0 +1,115 @@
+/* gdict-source.h - Source configuration for Gdict
+ *
+ * Copyright (C) 2005 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+#ifndef __GDICT_SOURCE_H__
+#define __GDICT_SOURCE_H__
+
+#include <stdarg.h>
+#include <glib-object.h>
+#include "gdict-context.h"
+
+G_BEGIN_DECLS
+
+#define GDICT_TYPE_SOURCE (gdict_source_get_type ())
+#define GDICT_SOURCE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDICT_TYPE_SOURCE, GdictSource))
+#define GDICT_IS_SOURCE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDICT_TYPE_SOURCE))
+#define GDICT_SOURCE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDICT_TYPE_SOURCE, GdictSourceClass))
+#define GDICT_IS_SOURCE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDICT_TYPE_SOURCE))
+#define GDICT_SOURCE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GDICT_TYPE_SOURCE, GdictSourceClass))
+
+
+typedef struct _GdictSource GdictSource;
+typedef struct _GdictSourceClass GdictSourceClass;
+typedef struct _GdictSourcePrivate GdictSourcePrivate;
+
+typedef enum
+{
+ GDICT_SOURCE_TRANSPORT_DICTD,
+
+ GDICT_SOURCE_TRANSPORT_INVALID /* only for debug */
+} GdictSourceTransport;
+
+#define GDICT_SOURCE_ERROR (gdict_source_error_quark ())
+
+typedef enum
+{
+ GDICT_SOURCE_ERROR_PARSE,
+ GDICT_SOURCE_ERROR_INVALID_NAME,
+ GDICT_SOURCE_ERROR_INVALID_TRANSPORT,
+ GDICT_SOURCE_ERROR_INVALID_BAD_PARAMETER
+} GdictSourceError;
+
+GQuark gdict_source_error_quark (void);
+
+struct _GdictSource
+{
+ /*< private >*/
+ GObject parent_instance;
+
+ GdictSourcePrivate *priv;
+};
+
+struct _GdictSourceClass
+{
+ /*< private >*/
+ GObjectClass parent_class;
+};
+
+GType gdict_source_get_type (void) G_GNUC_CONST;
+
+GdictSource * gdict_source_new (void);
+gboolean gdict_source_load_from_file (GdictSource *source,
+ const gchar *filename,
+ GError **error);
+gboolean gdict_source_load_from_data (GdictSource *source,
+ const gchar *data,
+ gsize length,
+ GError **error);
+gchar * gdict_source_to_data (GdictSource *source,
+ gsize *length,
+ GError **error) G_GNUC_MALLOC;
+
+void gdict_source_set_name (GdictSource *source,
+ const gchar *name);
+const gchar *gdict_source_get_name (GdictSource *source);
+void gdict_source_set_description (GdictSource *source,
+ const gchar *description);
+const gchar *gdict_source_get_description (GdictSource *source);
+void gdict_source_set_database (GdictSource *source,
+ const gchar *database);
+const gchar *gdict_source_get_database (GdictSource *source);
+void gdict_source_set_strategy (GdictSource *source,
+ const gchar *strategy);
+const gchar *gdict_source_get_strategy (GdictSource *source);
+void gdict_source_set_transport (GdictSource *source,
+ GdictSourceTransport transport,
+ const gchar *first_transport_property,
+ ...);
+void gdict_source_set_transportv (GdictSource *source,
+ GdictSourceTransport transport,
+ const gchar *first_transport_property,
+ va_list var_args);
+GdictSourceTransport gdict_source_get_transport (GdictSource *source);
+
+GdictContext * gdict_source_get_context (GdictSource *source);
+GdictContext * gdict_source_peek_context (GdictSource *source);
+
+G_END_DECLS
+
+#endif /* __GDICT_SOURCE_H__ */
diff --git a/mate-dictionary/libgdict/gdict-speller.c b/mate-dictionary/libgdict/gdict-speller.c
new file mode 100644
index 00000000..455883a1
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-speller.c
@@ -0,0 +1,827 @@
+/* gdict-speller.c - display widget for dictionary matches
+ *
+ * Copyright (C) 2006 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+/**
+ * SECTION:gdict-speller
+ * @short_description: Display matching words
+ *
+ * #GdictSpeller is a widget showing a list of words returned by a
+ * #GdictContext query, using a specific database and a matching strategy.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n-lib.h>
+
+#include "gdict-speller.h"
+#include "gdict-utils.h"
+#include "gdict-enum-types.h"
+#include "gdict-marshal.h"
+#include "gdict-debug.h"
+#include "gdict-private.h"
+
+#define GDICT_SPELLER_GET_PRIVATE(obj) \
+(G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDICT_TYPE_SPELLER, GdictSpellerPrivate))
+
+struct _GdictSpellerPrivate
+{
+ GdictContext *context;
+ gchar *database;
+ gchar *strategy;
+
+ gchar *word;
+
+ GtkWidget *treeview;
+ GtkWidget *clear_button;
+
+ GdkCursor *busy_cursor;
+
+ GtkListStore *store;
+ gint results;
+
+ guint start_id;
+ guint end_id;
+ guint match_id;
+ guint error_id;
+
+ guint is_searching : 1;
+};
+
+typedef enum
+{
+ MATCH_DB,
+ MATCH_WORD,
+ MATCH_ERROR
+} MatchType;
+
+enum
+{
+ MATCH_COLUMN_TYPE,
+ MATCH_COLUMN_DB_NAME,
+ MATCH_COLUMN_WORD,
+
+ MATCH_N_COLUMNS
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_CONTEXT,
+ PROP_WORD,
+ PROP_DATABASE,
+ PROP_STRATEGY,
+ PROP_COUNT
+};
+
+enum
+{
+ WORD_ACTIVATED,
+
+ LAST_SIGNAL
+};
+
+static guint speller_signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GdictSpeller, gdict_speller, GTK_TYPE_VBOX);
+
+
+static void
+set_gdict_context (GdictSpeller *speller,
+ GdictContext *context)
+{
+ GdictSpellerPrivate *priv;
+
+ g_assert (GDICT_IS_SPELLER (speller));
+
+ priv = speller->priv;
+ if (priv->context)
+ {
+ if (priv->start_id)
+ {
+ GDICT_NOTE (SPELLER, "Removing old context handlers");
+
+ g_signal_handler_disconnect (priv->context, priv->start_id);
+ g_signal_handler_disconnect (priv->context, priv->match_id);
+ g_signal_handler_disconnect (priv->context, priv->end_id);
+
+ priv->start_id = 0;
+ priv->end_id = 0;
+ priv->match_id = 0;
+ }
+
+ if (priv->error_id)
+ {
+ g_signal_handler_disconnect (priv->context, priv->error_id);
+
+ priv->error_id = 0;
+ }
+
+ GDICT_NOTE (SPELLER, "Removing old context");
+
+ g_object_unref (G_OBJECT (priv->context));
+ }
+
+ if (!context)
+ return;
+
+ if (!GDICT_IS_CONTEXT (context))
+ {
+ g_warning ("Object of type `%s' instead of a GdictContext\n",
+ g_type_name (G_OBJECT_TYPE (context)));
+ return;
+ }
+
+ GDICT_NOTE (SPELLER, "Setting new context\n");
+
+ priv->context = context;
+ g_object_ref (G_OBJECT (priv->context));
+}
+
+static void
+gdict_speller_finalize (GObject *gobject)
+{
+ GdictSpeller *speller = GDICT_SPELLER (gobject);
+ GdictSpellerPrivate *priv = speller->priv;
+
+ if (priv->context)
+ set_gdict_context (speller, NULL);
+
+ if (priv->busy_cursor)
+ gdk_cursor_unref (priv->busy_cursor);
+
+ g_free (priv->strategy);
+ g_free (priv->database);
+ g_free (priv->word);
+
+ if (priv->store)
+ g_object_unref (priv->store);
+
+ G_OBJECT_CLASS (gdict_speller_parent_class)->finalize (gobject);
+}
+
+
+static void
+gdict_speller_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GdictSpeller *speller = GDICT_SPELLER (gobject);
+ GdictSpellerPrivate *priv = speller->priv;
+
+ switch (prop_id)
+ {
+ case PROP_CONTEXT:
+ set_gdict_context (speller, g_value_get_object (value));
+ break;
+ case PROP_DATABASE:
+ g_free (priv->database);
+ priv->database = g_strdup (g_value_get_string (value));
+ break;
+ case PROP_STRATEGY:
+ g_free (priv->strategy);
+ priv->strategy = g_strdup (g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gdict_speller_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GdictSpeller *speller = GDICT_SPELLER (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_DATABASE:
+ g_value_set_string (value, speller->priv->database);
+ break;
+ case PROP_STRATEGY:
+ g_value_set_string (value, speller->priv->strategy);
+ break;
+ case PROP_CONTEXT:
+ g_value_set_object (value, speller->priv->context);
+ break;
+ case PROP_COUNT:
+ g_value_set_int (value, speller->priv->results);
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+row_activated_cb (GtkTreeView *treeview,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer user_data)
+{
+ GdictSpeller *speller = GDICT_SPELLER (user_data);
+ GdictSpellerPrivate *priv = speller->priv;
+ GtkTreeIter iter;
+ gchar *word, *db_name;
+ gboolean valid;
+
+ valid = gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->store),
+ &iter,
+ path);
+ if (!valid)
+ {
+ g_warning ("Invalid iterator found");
+
+ return;
+ }
+
+ gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter,
+ MATCH_COLUMN_WORD, &word,
+ MATCH_COLUMN_DB_NAME, &db_name,
+ -1);
+ if (word)
+ g_signal_emit (speller, speller_signals[WORD_ACTIVATED], 0,
+ word, db_name);
+ else
+ {
+ gchar *row = gtk_tree_path_to_string (path);
+
+ g_warning ("Row %s activated, but no word attached", row);
+ g_free (row);
+ }
+
+ g_free (word);
+ g_free (db_name);
+}
+
+static void
+clear_button_clicked_cb (GtkWidget *widget,
+ gpointer user_data)
+{
+ GdictSpeller *speller = GDICT_SPELLER (user_data);
+
+ gdict_speller_clear (speller);
+}
+
+static GObject *
+gdict_speller_constructor (GType type,
+ guint n_params,
+ GObjectConstructParam *params)
+{
+ GObject *object;
+ GdictSpeller *speller;
+ GdictSpellerPrivate *priv;
+ GtkWidget *sw;
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+ GtkWidget *hbox;
+
+ object = G_OBJECT_CLASS (gdict_speller_parent_class)->constructor (type,
+ n_params,
+ params);
+ speller = GDICT_SPELLER (object);
+ priv = speller->priv;
+
+ gtk_widget_push_composite_child ();
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_widget_set_composite_name (sw, "gdict-speller-scrolled-window");
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+ GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (speller), sw, TRUE, TRUE, 0);
+ gtk_widget_show (sw);
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("matches",
+ renderer,
+ "text", MATCH_COLUMN_WORD,
+ NULL);
+
+ priv->treeview = gtk_tree_view_new ();
+ gtk_widget_set_composite_name (priv->treeview, "gdict-speller-treeview");
+ gtk_tree_view_set_model (GTK_TREE_VIEW (priv->treeview),
+ GTK_TREE_MODEL (priv->store));
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->treeview), FALSE);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (priv->treeview), column);
+ g_signal_connect (priv->treeview, "row-activated",
+ G_CALLBACK (row_activated_cb), speller);
+ gtk_container_add (GTK_CONTAINER (sw), priv->treeview);
+ gtk_widget_show (priv->treeview);
+
+ hbox = gtk_hbox_new (FALSE, 0);
+
+ priv->clear_button = gtk_button_new ();
+ gtk_button_set_image (GTK_BUTTON (priv->clear_button),
+ gtk_image_new_from_stock (GTK_STOCK_CLEAR,
+ GTK_ICON_SIZE_SMALL_TOOLBAR));
+ g_signal_connect (priv->clear_button, "clicked",
+ G_CALLBACK (clear_button_clicked_cb),
+ speller);
+ gtk_box_pack_start (GTK_BOX (hbox), priv->clear_button, FALSE, FALSE, 0);
+ gtk_widget_show (priv->clear_button);
+ gtk_widget_set_tooltip_text (priv->clear_button,
+ _("Clear the list of similar words"));
+
+ gtk_box_pack_end (GTK_BOX (speller), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ gtk_widget_pop_composite_child ();
+
+ return object;
+}
+
+static void
+gdict_speller_class_init (GdictSpellerClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = gdict_speller_finalize;
+ gobject_class->set_property = gdict_speller_set_property;
+ gobject_class->get_property = gdict_speller_get_property;
+ gobject_class->constructor = gdict_speller_constructor;
+
+ g_object_class_install_property (gobject_class,
+ PROP_CONTEXT,
+ g_param_spec_object ("context",
+ _("Context"),
+ _("The GdictContext object used to get the word definition"),
+ GDICT_TYPE_CONTEXT,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)));
+ g_object_class_install_property (gobject_class,
+ PROP_DATABASE,
+ g_param_spec_string ("database",
+ _("Database"),
+ _("The database used to query the GdictContext"),
+ GDICT_DEFAULT_DATABASE,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ g_object_class_install_property (gobject_class,
+ PROP_DATABASE,
+ g_param_spec_string ("strategy",
+ _("Strategy"),
+ _("The strategy used to query the GdictContext"),
+ GDICT_DEFAULT_STRATEGY,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+
+ speller_signals[WORD_ACTIVATED] =
+ g_signal_new ("word-activated",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdictSpellerClass, word_activated),
+ NULL, NULL,
+ gdict_marshal_VOID__STRING_STRING,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ g_type_class_add_private (gobject_class, sizeof (GdictSpellerPrivate));
+}
+
+static void
+gdict_speller_init (GdictSpeller *speller)
+{
+ GdictSpellerPrivate *priv;
+
+ speller->priv = priv = GDICT_SPELLER_GET_PRIVATE (speller);
+
+ priv->database = NULL;
+ priv->strategy = NULL;
+ priv->word = NULL;
+
+ priv->results = -1;
+ priv->context = NULL;
+
+ priv->store = gtk_list_store_new (MATCH_N_COLUMNS,
+ G_TYPE_INT, /* MatchType */
+ G_TYPE_STRING, /* db_name */
+ G_TYPE_STRING /* word */);
+
+ priv->start_id = 0;
+ priv->end_id = 0;
+ priv->match_id = 0;
+ priv->error_id = 0;
+}
+
+/**
+ * gdict_speller_new:
+ *
+ * FIXME
+ *
+ * Return value: FIXME
+ *
+ * Since:
+ */
+GtkWidget *
+gdict_speller_new (void)
+{
+ return g_object_new (GDICT_TYPE_SPELLER, NULL);
+}
+
+/**
+ * gdict_speller_new_with_context:
+ * @context: a #GdictContext
+ *
+ * FIXME
+ *
+ * Return value: FIXME
+ *
+ * Since:
+ */
+GtkWidget *
+gdict_speller_new_with_context (GdictContext *context)
+{
+ g_return_val_if_fail (GDICT_IS_CONTEXT (context), NULL);
+
+ return g_object_new (GDICT_TYPE_SPELLER,
+ "context", context,
+ NULL);
+}
+
+/**
+ * gdict_speller_set_context:
+ * @speller: a #GdictSpeller
+ * @context: a #GdictContext
+ *
+ * FIXME
+ *
+ * Since:
+ */
+void
+gdict_speller_set_context (GdictSpeller *speller,
+ GdictContext *context)
+{
+ g_return_if_fail (GDICT_IS_SPELLER (speller));
+ g_return_if_fail (context == NULL || GDICT_IS_CONTEXT (context));
+
+ set_gdict_context (speller, context);
+
+ g_object_notify (G_OBJECT (speller), "context");
+}
+
+/**
+ * gdict_speller_get_context:
+ * @speller: a #GdictSpeller
+ *
+ * FIXME
+ *
+ * Return value: a #GdictContext
+ *
+ * Since:
+ */
+GdictContext *
+gdict_speller_get_context (GdictSpeller *speller)
+{
+ g_return_val_if_fail (GDICT_IS_SPELLER (speller), NULL);
+
+ return speller->priv->context;
+}
+
+/**
+ * gdict_speller_set_database:
+ * @speller: a #GdictSpeller
+ * @database: FIXME
+ *
+ * FIXME
+ *
+ * Since:
+ */
+void
+gdict_speller_set_database (GdictSpeller *speller,
+ const gchar *database)
+{
+ GdictSpellerPrivate *priv;
+
+ g_return_if_fail (GDICT_IS_SPELLER (speller));
+
+ priv = speller->priv;
+
+ if (!database || database[0] == '\0')
+ database = GDICT_DEFAULT_DATABASE;
+
+ g_free (priv->database);
+ priv->database = g_strdup (database);
+
+ g_object_notify (G_OBJECT (speller), "database");
+}
+
+/**
+ * gdict_speller_get_database:
+ * @speller: a #GdictSpeller
+ *
+ * FIXME
+ *
+ * Return value: FIXME
+ *
+ * Since: FIXME
+ */
+const gchar *
+gdict_speller_get_database (GdictSpeller *speller)
+{
+ g_return_val_if_fail (GDICT_IS_SPELLER (speller), NULL);
+
+ return speller->priv->database;
+}
+
+/**
+ * gdict_speller_set_strategy:
+ * @speller: a #GdictSpeller
+ * @strategy: FIXME
+ *
+ * FIXME
+ *
+ * Since: FIXME
+ */
+void
+gdict_speller_set_strategy (GdictSpeller *speller,
+ const gchar *strategy)
+{
+ GdictSpellerPrivate *priv;
+
+ g_return_if_fail (GDICT_IS_SPELLER (speller));
+
+ priv = speller->priv;
+
+ if (!strategy || strategy[0] == '\0')
+ strategy = GDICT_DEFAULT_STRATEGY;
+
+ g_free (priv->strategy);
+ priv->strategy = g_strdup (strategy);
+
+ g_object_notify (G_OBJECT (speller), "strategy");
+}
+
+/**
+ * gdict_speller_get_strategy:
+ * @speller: a #GdictSpeller
+ *
+ * FIXME
+ *
+ * Return value: FIXME
+ *
+ * Since: FIXME
+ */
+const gchar *
+gdict_speller_get_strategy (GdictSpeller *speller)
+{
+ g_return_val_if_fail (GDICT_IS_SPELLER (speller), NULL);
+
+ return speller->priv->strategy;
+}
+
+/**
+ * gdict_speller_clear:
+ * @speller: a #GdictSpeller
+ *
+ * FIXME
+ *
+ * Since: FIXME
+ */
+void
+gdict_speller_clear (GdictSpeller *speller)
+{
+ GdictSpellerPrivate *priv;
+
+ g_return_if_fail (GDICT_IS_SPELLER (speller));
+ priv = speller->priv;
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (priv->treeview), NULL);
+
+ gtk_list_store_clear (priv->store);
+ priv->results = -1;
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (priv->treeview),
+ GTK_TREE_MODEL (priv->store));
+}
+
+static void
+lookup_start_cb (GdictContext *context,
+ gpointer user_data)
+{
+ GdictSpeller *speller = GDICT_SPELLER (user_data);
+ GdictSpellerPrivate *priv = speller->priv;
+
+ if (!priv->busy_cursor)
+ priv->busy_cursor = gdk_cursor_new (GDK_WATCH);
+
+ if (gtk_widget_get_window (GTK_WIDGET (speller)))
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (speller)), priv->busy_cursor);
+
+ priv->is_searching = TRUE;
+}
+
+static void
+lookup_end_cb (GdictContext *context,
+ gpointer user_data)
+{
+ GdictSpeller *speller = GDICT_SPELLER (user_data);
+ GdictSpellerPrivate *priv = speller->priv;
+
+ if (gtk_widget_get_window (GTK_WIDGET (speller)))
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (speller)), NULL);
+
+ g_free (priv->word);
+ priv->word = NULL;
+
+ priv->is_searching = FALSE;
+}
+
+static void
+match_found_cb (GdictContext *context,
+ GdictMatch *match,
+ gpointer user_data)
+{
+ GdictSpeller *speller = GDICT_SPELLER (user_data);
+ GdictSpellerPrivate *priv = speller->priv;
+ GtkTreeIter iter;
+
+ GDICT_NOTE (SPELLER, "MATCH: `%s' (from `%s')",
+ gdict_match_get_word (match),
+ gdict_match_get_database (match));
+
+ gtk_list_store_append (priv->store, &iter);
+ gtk_list_store_set (priv->store, &iter,
+ MATCH_COLUMN_TYPE, MATCH_WORD,
+ MATCH_COLUMN_DB_NAME, gdict_match_get_database (match),
+ MATCH_COLUMN_WORD, gdict_match_get_word (match),
+ -1);
+
+ if (priv->results == -1)
+ priv->results = 1;
+ else
+ priv->results += 1;
+}
+
+static void
+error_cb (GdictContext *context,
+ const GError *error,
+ gpointer user_data)
+{
+ GdictSpeller *speller = GDICT_SPELLER (user_data);
+ GdictSpellerPrivate *priv = speller->priv;
+
+ gdict_speller_clear (speller);
+
+ if (gtk_widget_get_window (GTK_WIDGET (speller)))
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (speller)), NULL);
+
+ g_free (priv->word);
+ priv->word = NULL;
+
+ priv->is_searching = FALSE;
+}
+
+/**
+ * gdict_speller_match:
+ * @speller: a #GdictSpeller
+ * @word: FIXME
+ *
+ * FIXME
+ *
+ * Since: FIXME
+ */
+void
+gdict_speller_match (GdictSpeller *speller,
+ const gchar *word)
+{
+ GdictSpellerPrivate *priv;
+ GError *match_error;
+
+ g_return_if_fail (GDICT_IS_SPELLER (speller));
+ g_return_if_fail (word != NULL);
+
+ priv = speller->priv;
+
+ if (!priv->context)
+ {
+ g_warning ("Attempting to match `%s', but no GdictContext "
+ "has been set. Use gdict_speller_set_context() "
+ "before invoking gdict_speller_match().",
+ word);
+
+ return;
+ }
+
+ if (priv->is_searching)
+ {
+ _gdict_show_error_dialog (NULL,
+ _("Another search is in progress"),
+ _("Please wait until the current search ends."));
+
+ return;
+ }
+
+ gdict_speller_clear (speller);
+
+ if (!priv->start_id)
+ {
+ priv->start_id = g_signal_connect (priv->context, "lookup-start",
+ G_CALLBACK (lookup_start_cb),
+ speller);
+ priv->match_id = g_signal_connect (priv->context, "match-found",
+ G_CALLBACK (match_found_cb),
+ speller);
+ priv->end_id = g_signal_connect (priv->context, "lookup-end",
+ G_CALLBACK (lookup_end_cb),
+ speller);
+ }
+
+ if (!priv->error_id)
+ priv->error_id = g_signal_connect (priv->context, "error",
+ G_CALLBACK (error_cb),
+ speller);
+
+ g_free (priv->word);
+ priv->word = g_strdup (word);
+
+ match_error = NULL;
+ gdict_context_match_word (priv->context,
+ priv->database,
+ priv->strategy,
+ priv->word,
+ &match_error);
+ if (match_error)
+ {
+ GtkTreeIter iter;
+
+ gtk_list_store_append (priv->store, &iter);
+ gtk_list_store_set (priv->store, &iter,
+ MATCH_COLUMN_TYPE, MATCH_ERROR,
+ MATCH_COLUMN_DB_NAME, _("Error while matching"),
+ MATCH_COLUMN_WORD, NULL,
+ -1);
+
+ g_warning ("Error while matching `%s': %s",
+ priv->word,
+ match_error->message);
+
+ g_error_free (match_error);
+ }
+}
+
+/**
+ * gdict_speller_count_match:
+ * @speller: a #GdictSpeller
+ *
+ * FIXME
+ *
+ * Return value: FIXME
+ *
+ * Since: FIXME
+ */
+gint
+gdict_speller_count_matches (GdictSpeller *speller)
+{
+ g_return_val_if_fail (GDICT_IS_SPELLER (speller), -1);
+
+ return speller->priv->results;
+}
+
+/**
+ * gdict_speller_get_matches:
+ * @speller: a #GdictSpeller
+ * @length: FIXME
+ *
+ * FIXME
+ *
+ * Return value: FIXME
+ *
+ * Since: FIXME
+ */
+gchar **
+gdict_speller_get_matches (GdictSpeller *speller,
+ gsize length)
+{
+ g_return_val_if_fail (GDICT_IS_SPELLER (speller), NULL);
+
+ return NULL;
+}
diff --git a/mate-dictionary/libgdict/gdict-speller.h b/mate-dictionary/libgdict/gdict-speller.h
new file mode 100644
index 00000000..9d6e81ff
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-speller.h
@@ -0,0 +1,87 @@
+/* gdict-speller.h - display widget for dictionary matches
+ *
+ * Copyright (C) 2006 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+#ifndef __GDICT_SPELLER_H__
+#define __GDICT_SPELLER_H__
+
+#include <gtk/gtk.h>
+#include "gdict-context.h"
+
+G_BEGIN_DECLS
+
+#define GDICT_TYPE_SPELLER (gdict_speller_get_type ())
+#define GDICT_SPELLER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDICT_TYPE_SPELLER, GdictSpeller))
+#define GDICT_SPELLER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDICT_TYPE_SPELLER, GdictSpellerClass))
+#define GDICT_IS_SPELLER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDICT_TYPE_SPELLER))
+#define GDICT_IS_SPELLER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDICT_TYPE_SPELLER))
+#define GDICT_SPELLER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GDICT_TYPE_SPELLER, GdictSpellerClass))
+
+typedef struct _GdictSpeller GdictSpeller;
+typedef struct _GdictSpellerPrivate GdictSpellerPrivate;
+typedef struct _GdictSpellerClass GdictSpellerClass;
+
+struct _GdictSpeller
+{
+ GtkVBox parent_instance;
+
+ /*< private >*/
+ GdictSpellerPrivate *priv;
+};
+
+struct _GdictSpellerClass
+{
+ GtkVBoxClass parent_class;
+
+ void (*word_activated) (GdictSpeller *speller,
+ const gchar *word,
+ const gchar *database);
+
+ /* padding for future expansion */
+ void (*_gdict_speller_1) (void);
+ void (*_gdict_speller_2) (void);
+ void (*_gdict_speller_3) (void);
+ void (*_gdict_speller_4) (void);
+};
+
+GType gdict_speller_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gdict_speller_new (void);
+GtkWidget * gdict_speller_new_with_context (GdictContext *context);
+
+void gdict_speller_set_context (GdictSpeller *speller,
+ GdictContext *context);
+GdictContext * gdict_speller_get_context (GdictSpeller *speller);
+void gdict_speller_set_database (GdictSpeller *speller,
+ const gchar *database);
+const gchar *gdict_speller_get_database (GdictSpeller *speller);
+void gdict_speller_set_strategy (GdictSpeller *speller,
+ const gchar *strategy);
+const gchar *gdict_speller_get_strategy (GdictSpeller *speller);
+
+void gdict_speller_clear (GdictSpeller *speller);
+void gdict_speller_match (GdictSpeller *speller,
+ const gchar *word);
+
+gint gdict_speller_count_matches (GdictSpeller *speller);
+gchar ** gdict_speller_get_matches (GdictSpeller *speller,
+ gsize length);
+
+G_END_DECLS
+
+#endif /* __GDICT_SPELLER_H__ */
diff --git a/mate-dictionary/libgdict/gdict-strategy-chooser.c b/mate-dictionary/libgdict/gdict-strategy-chooser.c
new file mode 100644
index 00000000..93df7073
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-strategy-chooser.c
@@ -0,0 +1,1092 @@
+/* gdict-strategy-chooser.c - display widget for strategy names
+ *
+ * Copyright (C) 2006 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+/**
+ * SECTION:gdict-strategy-chooser
+ * @short_description: Display a list of matching strategies
+ *
+ * Each #GdictContext allows matching a word using a specific "matching
+ * strategy". The #GdictStrategyChooser widget queries a #GdictContext and
+ * displays the list of available matching strategies.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n-lib.h>
+
+#include "gdict-strategy-chooser.h"
+#include "gdict-utils.h"
+#include "gdict-debug.h"
+#include "gdict-private.h"
+#include "gdict-enum-types.h"
+#include "gdict-marshal.h"
+
+#define GDICT_STRATEGY_CHOOSER_GET_PRIVATE(obj) \
+(G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDICT_TYPE_STRATEGY_CHOOSER, GdictStrategyChooserPrivate))
+
+struct _GdictStrategyChooserPrivate
+{
+ GtkListStore *store;
+
+ GtkWidget *treeview;
+ GtkWidget *clear_button;
+ GtkWidget *refresh_button;
+ GtkWidget *buttons_box;
+
+ GdictContext *context;
+ gint results;
+
+ guint start_id;
+ guint match_id;
+ guint end_id;
+ guint error_id;
+
+ GdkCursor *busy_cursor;
+
+ gchar *current_strat;
+
+ guint is_searching : 1;
+};
+
+enum
+{
+ STRATEGY_NAME,
+ STRATEGY_ERROR
+} StratType;
+
+enum
+{
+ STRAT_COLUMN_TYPE,
+ STRAT_COLUMN_NAME,
+ STRAT_COLUMN_DESCRIPTION,
+ STRAT_COLUMN_CURRENT,
+
+ STRAT_N_COLUMNS
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_CONTEXT,
+ PROP_COUNT
+};
+
+enum
+{
+ STRATEGY_ACTIVATED,
+ CLOSED,
+
+ LAST_SIGNAL
+};
+
+static guint db_chooser_signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GdictStrategyChooser,
+ gdict_strategy_chooser,
+ GTK_TYPE_VBOX);
+
+
+static void
+set_gdict_context (GdictStrategyChooser *chooser,
+ GdictContext *context)
+{
+ GdictStrategyChooserPrivate *priv;
+
+ g_assert (GDICT_IS_STRATEGY_CHOOSER (chooser));
+ priv = chooser->priv;
+
+ if (priv->context)
+ {
+ if (priv->start_id)
+ {
+ GDICT_NOTE (CHOOSER, "Removing old context handlers");
+
+ g_signal_handler_disconnect (priv->context, priv->start_id);
+ g_signal_handler_disconnect (priv->context, priv->match_id);
+ g_signal_handler_disconnect (priv->context, priv->end_id);
+
+ priv->start_id = 0;
+ priv->end_id = 0;
+ priv->match_id = 0;
+ }
+
+ if (priv->error_id)
+ {
+ g_signal_handler_disconnect (priv->context, priv->error_id);
+
+ priv->error_id = 0;
+ }
+
+ GDICT_NOTE (CHOOSER, "Removing old context");
+
+ g_object_unref (G_OBJECT (priv->context));
+
+ priv->context = NULL;
+ priv->results = -1;
+ }
+
+ if (!context)
+ return;
+
+ if (!GDICT_IS_CONTEXT (context))
+ {
+ g_warning ("Object of type '%s' instead of a GdictContext\n",
+ g_type_name (G_OBJECT_TYPE (context)));
+ return;
+ }
+
+ GDICT_NOTE (CHOOSER, "Setting new context");
+
+ priv->context = g_object_ref (context);
+ priv->results = 0;
+}
+
+static void
+gdict_strategy_chooser_dispose (GObject *gobject)
+{
+ GdictStrategyChooser *chooser = GDICT_STRATEGY_CHOOSER (gobject);
+ GdictStrategyChooserPrivate *priv = chooser->priv;
+
+ set_gdict_context (chooser, NULL);
+
+ if (priv->busy_cursor)
+ {
+ gdk_cursor_unref (priv->busy_cursor);
+ priv->busy_cursor = NULL;
+ }
+
+ if (priv->store)
+ {
+ g_object_unref (priv->store);
+ priv->store = NULL;
+ }
+
+ G_OBJECT_CLASS (gdict_strategy_chooser_parent_class)->dispose (gobject);
+}
+
+static void
+gdict_strategy_chooser_finalize (GObject *gobject)
+{
+ GdictStrategyChooser *chooser = GDICT_STRATEGY_CHOOSER (gobject);
+ GdictStrategyChooserPrivate *priv = chooser->priv;
+
+ g_free (priv->current_strat);
+
+ G_OBJECT_CLASS (gdict_strategy_chooser_parent_class)->finalize (gobject);
+}
+
+static void
+gdict_strategy_chooser_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GdictStrategyChooser *chooser = GDICT_STRATEGY_CHOOSER (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_CONTEXT:
+ set_gdict_context (chooser, g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gdict_strategy_chooser_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GdictStrategyChooser *chooser = GDICT_STRATEGY_CHOOSER (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_CONTEXT:
+ g_value_set_object (value, chooser->priv->context);
+ break;
+ case PROP_COUNT:
+ g_value_set_int (value, chooser->priv->results);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+row_activated_cb (GtkTreeView *treeview,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer user_data)
+{
+ GdictStrategyChooser *chooser = GDICT_STRATEGY_CHOOSER (user_data);
+ GdictStrategyChooserPrivate *priv = chooser->priv;
+ GtkTreeIter iter;
+ gchar *db_name, *db_desc;
+ gboolean valid;
+
+ valid = gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->store),
+ &iter,
+ path);
+ if (!valid)
+ {
+ g_warning ("Invalid iterator found");
+ return;
+ }
+
+ gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter,
+ STRAT_COLUMN_NAME, &db_name,
+ STRAT_COLUMN_DESCRIPTION, &db_desc,
+ -1);
+ if (db_name && db_desc)
+ {
+ g_signal_emit (chooser, db_chooser_signals[STRATEGY_ACTIVATED], 0,
+ db_name, db_desc);
+ }
+ else
+ {
+ gchar *row = gtk_tree_path_to_string (path);
+
+ g_warning ("Row %s activated, but no strategy attached", row);
+ g_free (row);
+ }
+
+ g_free (db_name);
+ g_free (db_desc);
+}
+
+static void
+refresh_button_clicked_cb (GtkWidget *widget,
+ gpointer user_data)
+{
+ GdictStrategyChooser *chooser = GDICT_STRATEGY_CHOOSER (user_data);
+
+ gdict_strategy_chooser_refresh (chooser);
+}
+
+static void
+clear_button_clicked_cb (GtkWidget *widget,
+ gpointer user_data)
+{
+ GdictStrategyChooser *chooser = GDICT_STRATEGY_CHOOSER (user_data);
+
+ gdict_strategy_chooser_clear (chooser);
+}
+
+static GObject *
+gdict_strategy_chooser_constructor (GType type,
+ guint n_params,
+ GObjectConstructParam *params)
+{
+ GObject *object;
+ GdictStrategyChooser *chooser;
+ GdictStrategyChooserPrivate *priv;
+ GtkWidget *sw;
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+ GtkWidget *hbox;
+
+ object = G_OBJECT_CLASS (gdict_strategy_chooser_parent_class)->constructor (type,
+ n_params,
+ params);
+
+ chooser = GDICT_STRATEGY_CHOOSER (object);
+ priv = chooser->priv;
+
+ gtk_widget_push_composite_child ();
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_widget_set_composite_name (sw, "gdict-strategy-chooser-scrolled-window");
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+ GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (chooser), sw, TRUE, TRUE, 0);
+ gtk_widget_show (sw);
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("strategies",
+ renderer,
+ "text", STRAT_COLUMN_DESCRIPTION,
+ "weight", STRAT_COLUMN_CURRENT,
+ NULL);
+ priv->treeview = gtk_tree_view_new ();
+ gtk_widget_set_composite_name (priv->treeview, "gdict-strategy-chooser-treeview");
+ gtk_tree_view_set_model (GTK_TREE_VIEW (priv->treeview),
+ GTK_TREE_MODEL (priv->store));
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->treeview), FALSE);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (priv->treeview), column);
+ g_signal_connect (priv->treeview, "row-activated",
+ G_CALLBACK (row_activated_cb), chooser);
+ gtk_container_add (GTK_CONTAINER (sw), priv->treeview);
+ gtk_widget_show (priv->treeview);
+
+ hbox = gtk_hbox_new (FALSE, 0);
+
+ priv->refresh_button = gtk_button_new ();
+ gtk_button_set_image (GTK_BUTTON (priv->refresh_button),
+ gtk_image_new_from_stock (GTK_STOCK_REFRESH,
+ GTK_ICON_SIZE_SMALL_TOOLBAR));
+ g_signal_connect (priv->refresh_button, "clicked",
+ G_CALLBACK (refresh_button_clicked_cb),
+ chooser);
+ gtk_box_pack_start (GTK_BOX (hbox), priv->refresh_button, FALSE, FALSE, 0);
+ gtk_widget_show (priv->refresh_button);
+ gtk_widget_set_tooltip_text (priv->refresh_button,
+ _("Reload the list of available strategies"));
+
+ priv->clear_button = gtk_button_new ();
+ gtk_button_set_image (GTK_BUTTON (priv->clear_button),
+ gtk_image_new_from_stock (GTK_STOCK_CLEAR,
+ GTK_ICON_SIZE_SMALL_TOOLBAR));
+ g_signal_connect (priv->clear_button, "clicked",
+ G_CALLBACK (clear_button_clicked_cb),
+ chooser);
+ gtk_box_pack_start (GTK_BOX (hbox), priv->clear_button, FALSE, FALSE, 0);
+ gtk_widget_show (priv->clear_button);
+ gtk_widget_set_tooltip_text (priv->clear_button,
+ _("Clear the list of available strategies"));
+
+ gtk_box_pack_end (GTK_BOX (chooser), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ gtk_widget_pop_composite_child ();
+
+ return object;
+}
+
+static void
+gdict_strategy_chooser_class_init (GdictStrategyChooserClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = gdict_strategy_chooser_finalize;
+ gobject_class->dispose = gdict_strategy_chooser_dispose;
+ gobject_class->set_property = gdict_strategy_chooser_set_property;
+ gobject_class->get_property = gdict_strategy_chooser_get_property;
+ gobject_class->constructor = gdict_strategy_chooser_constructor;
+
+ /**
+ * GdictStrategyChooser:context:
+ *
+ * The #GdictContext object used to retrieve the list of strategies.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_CONTEXT,
+ g_param_spec_object ("context",
+ "Context",
+ "The GdictContext object used to get the list of strategies",
+ GDICT_TYPE_CONTEXT,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)));
+
+ /**
+ * GdictStrategyChooser::strategy-activated:
+ * @chooser: the widget that received the signal
+ * @name: the name of the activated strategy
+ * @description: the description of the activate strategy
+ *
+ * The ::strategy-activated signal is emitted each time the user
+ * activates a strategy in the @chooser, either by double click or
+ * using the keyboard.
+ */
+ db_chooser_signals[STRATEGY_ACTIVATED] =
+ g_signal_new ("strategy-activated",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdictStrategyChooserClass, strategy_activated),
+ NULL, NULL,
+ gdict_marshal_VOID__STRING_STRING,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ g_type_class_add_private (gobject_class, sizeof (GdictStrategyChooserPrivate));
+}
+
+static void
+gdict_strategy_chooser_init (GdictStrategyChooser *chooser)
+{
+ GdictStrategyChooserPrivate *priv;
+
+ chooser->priv = priv = GDICT_STRATEGY_CHOOSER_GET_PRIVATE (chooser);
+
+ priv->results = -1;
+ priv->context = NULL;
+
+ priv->store = gtk_list_store_new (STRAT_N_COLUMNS,
+ G_TYPE_INT, /* strat_type */
+ G_TYPE_STRING, /* strat_name */
+ G_TYPE_STRING, /* strat_desc */
+ G_TYPE_INT /* strat_current */);
+
+ priv->start_id = 0;
+ priv->end_id = 0;
+ priv->match_id = 0;
+ priv->error_id = 0;
+}
+
+/**
+ * gdict_strategy_chooser_new:
+ *
+ * Creates a new #GdictStrategyChooser. Use this widget to show a list
+ * of matching strategies available on a dictionary source represented
+ * by a #GdictContext, set with gdict_strategy_chooser_set_context().
+ *
+ * Return value: the newly created #GdictStrategyChooser widget
+ *
+ * Since: 0.9
+ */
+GtkWidget *
+gdict_strategy_chooser_new (void)
+{
+ return g_object_new (GDICT_TYPE_STRATEGY_CHOOSER, NULL);
+}
+
+/**
+ * gdict_strategy_chooser_new_with_context:
+ * @context: a #GdictContext
+ *
+ * Creates a new #GdictStrategyChooser widget, using @context as the
+ * representation of a dictionary source.
+ *
+ * Return value: the newly created #GdictStrategyChooser widget
+ *
+ * Since: 0.9
+ */
+GtkWidget *
+gdict_strategy_chooser_new_with_context (GdictContext *context)
+{
+ g_return_val_if_fail (GDICT_IS_CONTEXT (context), NULL);
+
+ return g_object_new (GDICT_TYPE_STRATEGY_CHOOSER,
+ "context", context,
+ NULL);
+}
+
+/**
+ * gdict_strategy_chooser_get_context:
+ * @chooser: a #GdictStrategyChooser
+ *
+ * Retrieves the #GdictContext used by @chooser.
+ *
+ * Return value: a #GdictContext
+ *
+ * Since:
+ */
+GdictContext *
+gdict_strategy_chooser_get_context (GdictStrategyChooser *chooser)
+{
+ g_return_val_if_fail (GDICT_IS_STRATEGY_CHOOSER (chooser), NULL);
+
+ return chooser->priv->context;
+}
+
+/**
+ * gdict_strategy_chooser_set_context:
+ * @chooser: a #GdictStrategyChooser
+ * @context: a #GdictContext, or %NULL to unset the context
+ *
+ * Sets the #GdictContext to be used by @chooser to retrieve the
+ * list of matching strategies.
+ *
+ * Since: 0.9
+ */
+void
+gdict_strategy_chooser_set_context (GdictStrategyChooser *chooser,
+ GdictContext *context)
+{
+ g_return_if_fail (GDICT_IS_STRATEGY_CHOOSER (chooser));
+ g_return_if_fail (context == NULL || GDICT_IS_CONTEXT (context));
+
+ set_gdict_context (chooser, context);
+
+ g_object_notify (G_OBJECT (chooser), "context");
+}
+
+/**
+ * gdict_strategy_chooser_get_strategies:
+ * @chooser: a #GdictStrategyChooser
+ * @length: return location for the length of the returned string list
+ *
+ * Retrieves the list of matching strategies available.
+ *
+ * Return value: a string vector containing the names of the matching
+ * strategies. Use g_strfreev() to deallocate the memory when done
+ *
+ * Since:0.9
+ */
+gchar **
+gdict_strategy_chooser_get_strategies (GdictStrategyChooser *chooser,
+ gsize *length)
+{
+ GdictStrategyChooserPrivate *priv;
+ GtkTreeIter iter;
+ gchar **retval;
+ gsize i;
+
+ g_return_val_if_fail (GDICT_IS_STRATEGY_CHOOSER (chooser), NULL);
+
+ priv = chooser->priv;
+
+ if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->store), &iter))
+ return NULL;
+
+ i = 0;
+ retval = g_new (gchar*, priv->results);
+
+ do
+ {
+ gchar *strat_name;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter,
+ STRAT_COLUMN_NAME, &strat_name,
+ -1);
+
+ retval[i++] = strat_name;
+ }
+ while (gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->store), &iter));
+
+ retval[i] = NULL;
+
+ if (length)
+ *length = i;
+
+ return retval;
+}
+
+/**
+ * gdict_strategy_chooser_has_strategy:
+ * @chooser: a #GdictStrategyChooser
+ * @strategy: a strategy name
+ *
+ * Checks whether @strategy is available in the list of matching
+ * strategies displayed by @chooser.
+ *
+ * Return value: %TRUE if the strategy was found, %FALSE otherwise
+ *
+ * Since: 0.9
+ */
+gboolean
+gdict_strategy_chooser_has_strategy (GdictStrategyChooser *chooser,
+ const gchar *strategy)
+{
+ GdictStrategyChooserPrivate *priv;
+ GtkTreeIter iter;
+ gboolean retval;
+
+ g_return_val_if_fail (GDICT_IS_STRATEGY_CHOOSER (chooser), FALSE);
+ g_return_val_if_fail (strategy != NULL, FALSE);
+
+ priv = chooser->priv;
+
+ if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->store), &iter))
+ return FALSE;
+
+ retval = FALSE;
+
+ do
+ {
+ gchar *strat_name;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter,
+ STRAT_COLUMN_NAME, &strat_name,
+ -1);
+
+ if (strcmp (strat_name, strategy) == 0)
+ {
+ retval = TRUE;
+ g_free (strat_name);
+ break;
+ }
+
+ g_free (strat_name);
+ }
+ while (gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->store), &iter));
+
+ return retval;
+}
+
+/**
+ * gdict_strategy_chooser_count_strategies:
+ * @chooser: a #GdictStrategyChooser
+ *
+ * Returns the number of strategies found.
+ *
+ * Return value: the number of strategies or -1 if case of error
+ *
+ * Since:
+ */
+gint
+gdict_strategy_chooser_count_strategies (GdictStrategyChooser *chooser)
+{
+ g_return_val_if_fail (GDICT_IS_STRATEGY_CHOOSER (chooser), -1);
+
+ return chooser->priv->results;
+}
+
+static void
+lookup_start_cb (GdictContext *context,
+ gpointer user_data)
+{
+ GdictStrategyChooser *chooser = GDICT_STRATEGY_CHOOSER (user_data);
+ GdictStrategyChooserPrivate *priv = chooser->priv;
+
+ if (!priv->busy_cursor)
+ priv->busy_cursor = gdk_cursor_new (GDK_WATCH);
+
+ if (gtk_widget_get_window (GTK_WIDGET (chooser)))
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (chooser)), priv->busy_cursor);
+
+ priv->is_searching = TRUE;
+}
+
+static void
+lookup_end_cb (GdictContext *context,
+ gpointer user_data)
+{
+ GdictStrategyChooser *chooser = GDICT_STRATEGY_CHOOSER (user_data);
+ GdictStrategyChooserPrivate *priv = chooser->priv;
+
+ if (gtk_widget_get_window (GTK_WIDGET (chooser)))
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (chooser)), NULL);
+
+ priv->is_searching = FALSE;
+}
+
+static void
+strategy_found_cb (GdictContext *context,
+ GdictStrategy *strategy,
+ gpointer user_data)
+{
+ GdictStrategyChooser *chooser = GDICT_STRATEGY_CHOOSER (user_data);
+ GdictStrategyChooserPrivate *priv = chooser->priv;
+ const gchar *name, *description;
+ GtkTreeIter iter;
+ gint weight = PANGO_WEIGHT_NORMAL;
+
+ name = gdict_strategy_get_name (strategy);
+ description = gdict_strategy_get_description (strategy);
+
+ GDICT_NOTE (CHOOSER, "STRATEGY: `%s' (`%s')",
+ name,
+ description);
+
+ if (priv->current_strat && !strcmp (priv->current_strat, name))
+ weight = PANGO_WEIGHT_BOLD;
+
+ gtk_list_store_append (priv->store, &iter);
+ gtk_list_store_set (priv->store, &iter,
+ STRAT_COLUMN_TYPE, STRATEGY_NAME,
+ STRAT_COLUMN_NAME, name,
+ STRAT_COLUMN_DESCRIPTION, description,
+ STRAT_COLUMN_CURRENT, weight,
+ -1);
+
+ priv->results += 1;
+}
+
+static void
+error_cb (GdictContext *context,
+ const GError *error,
+ gpointer user_data)
+{
+ GdictStrategyChooser *chooser = GDICT_STRATEGY_CHOOSER (user_data);
+
+ if (gtk_widget_get_window (GTK_WIDGET (chooser)))
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (chooser)), NULL);
+
+ chooser->priv->is_searching = FALSE;
+ chooser->priv->results = 0;
+}
+
+/**
+ * gdict_strategy_chooser_refresh:
+ * @chooser: a #GdictStrategyChooser
+ *
+ * Reloads the list of available strategies.
+ *
+ * Since: 0.10
+ */
+void
+gdict_strategy_chooser_refresh (GdictStrategyChooser *chooser)
+{
+ GdictStrategyChooserPrivate *priv;
+ GError *db_error;
+
+ g_return_if_fail (GDICT_IS_STRATEGY_CHOOSER (chooser));
+
+ priv = chooser->priv;
+
+ if (!priv->context)
+ {
+ g_warning ("Attempting to retrieve the available strategies, but "
+ "no GdictContext has been set. Use gdict_strategy_chooser_set_context() "
+ "before invoking gdict_strategy_chooser_refresh().");
+ return;
+ }
+
+ if (priv->is_searching)
+ return;
+
+ gdict_strategy_chooser_clear (chooser);
+
+ if (!priv->start_id)
+ {
+ priv->start_id = g_signal_connect (priv->context, "lookup-start",
+ G_CALLBACK (lookup_start_cb),
+ chooser);
+ priv->match_id = g_signal_connect (priv->context, "strategy-found",
+ G_CALLBACK (strategy_found_cb),
+ chooser);
+ priv->end_id = g_signal_connect (priv->context, "lookup-end",
+ G_CALLBACK (lookup_end_cb),
+ chooser);
+ }
+
+ if (!priv->error_id)
+ priv->error_id = g_signal_connect (priv->context, "error",
+ G_CALLBACK (error_cb),
+ chooser);
+
+ db_error = NULL;
+ gdict_context_lookup_strategies (priv->context, &db_error);
+ if (db_error)
+ {
+ GtkTreeIter iter;
+
+ gtk_list_store_append (priv->store, &iter);
+ gtk_list_store_set (priv->store, &iter,
+ STRAT_COLUMN_TYPE, STRATEGY_ERROR,
+ STRAT_COLUMN_NAME, _("Error while matching"),
+ STRAT_COLUMN_DESCRIPTION, NULL,
+ -1);
+
+ g_warning ("Error while retrieving strategies: %s",
+ db_error->message);
+
+ g_error_free (db_error);
+ }
+}
+
+/**
+ * gdict_strategy_chooser_clear:
+ * @chooser: a #GdictStrategyChooser
+ *
+ * Clears @chooser.
+ *
+ * Since: 0.10
+ */
+void
+gdict_strategy_chooser_clear (GdictStrategyChooser *chooser)
+{
+ GdictStrategyChooserPrivate *priv;
+
+ g_return_if_fail (GDICT_IS_STRATEGY_CHOOSER (chooser));
+
+ priv = chooser->priv;
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (priv->treeview), NULL);
+
+ gtk_list_store_clear (priv->store);
+ priv->results = 0;
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (priv->treeview),
+ GTK_TREE_MODEL (priv->store));
+}
+
+typedef struct
+{
+ gchar *strat_name;
+ GdictStrategyChooser *chooser;
+
+ guint found : 1;
+ guint do_select : 1;
+ guint do_activate : 1;
+} SelectData;
+
+static gboolean
+scan_for_strat_name (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ SelectData *select_data = user_data;
+ gchar *strat_name = NULL;
+
+ if (!select_data)
+ return TRUE;
+
+ gtk_tree_model_get (model, iter, STRAT_COLUMN_NAME, &strat_name, -1);
+ if (!strat_name)
+ return FALSE;
+
+ if (strcmp (strat_name, select_data->strat_name) == 0)
+ {
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+
+ select_data->found = TRUE;
+
+ tree_view = GTK_TREE_VIEW (select_data->chooser->priv->treeview);
+ if (select_data->do_activate)
+ {
+ GtkTreeViewColumn *column;
+
+ gtk_list_store_set (GTK_LIST_STORE (model), iter,
+ STRAT_COLUMN_CURRENT, PANGO_WEIGHT_BOLD,
+ -1);
+
+ column = gtk_tree_view_get_column (tree_view, 2);
+ gtk_tree_view_row_activated (tree_view, path, column);
+ }
+
+ selection = gtk_tree_view_get_selection (tree_view);
+ if (select_data->do_select)
+ gtk_tree_selection_select_path (selection, path);
+ else
+ gtk_tree_selection_unselect_path (selection, path);
+ }
+ else
+ {
+ gtk_list_store_set (GTK_LIST_STORE (model), iter,
+ STRAT_COLUMN_CURRENT, PANGO_WEIGHT_NORMAL,
+ -1);
+ }
+
+ g_free (strat_name);
+
+ return FALSE;
+}
+
+/**
+ * gdict_strategy_chooser_select_strategy:
+ * @chooser: a #GdictStrategyChooser
+ * @strat_name: the name of the strategy to select
+ *
+ * Selects @strat_name, if available.
+ *
+ * Return value: %TRUE if the matching strategy was found and selected
+ *
+ * Since: 0.10
+ */
+gboolean
+gdict_strategy_chooser_select_strategy (GdictStrategyChooser *chooser,
+ const gchar *strat_name)
+{
+ GdictStrategyChooserPrivate *priv;
+ SelectData data;
+ gboolean retval;
+
+ g_return_val_if_fail (GDICT_IS_STRATEGY_CHOOSER (chooser), FALSE);
+ g_return_val_if_fail (strat_name != NULL, FALSE);
+
+ priv = chooser->priv;
+
+ data.strat_name = g_strdup (strat_name);
+ data.chooser = chooser;
+ data.found = FALSE;
+ data.do_select = TRUE;
+ data.do_activate = FALSE;
+
+ gtk_tree_model_foreach (GTK_TREE_MODEL (priv->store),
+ scan_for_strat_name,
+ &data);
+
+ retval = data.found;
+
+ g_free (data.strat_name);
+
+ return retval;
+}
+
+/**
+ * gdict_strategy_chooser_unselect_strategy:
+ * @chooser: a #GdictStrategyChooser
+ * @strat_name: the name of the strategy to unselect
+ *
+ * Unselects @strat_name from the list.
+ *
+ * Return value: %TRUE if the matching strategy was found and successfully
+ * unselected
+ *
+ * Since: 0.10
+ */
+gboolean
+gdict_strategy_chooser_unselect_strategy (GdictStrategyChooser *chooser,
+ const gchar *strat_name)
+{
+ GdictStrategyChooserPrivate *priv;
+ SelectData data;
+ gboolean retval;
+
+ g_return_val_if_fail (GDICT_IS_STRATEGY_CHOOSER (chooser), FALSE);
+ g_return_val_if_fail (strat_name != NULL, FALSE);
+
+ priv = chooser->priv;
+
+ data.strat_name = g_strdup (strat_name);
+ data.chooser = chooser;
+ data.found = FALSE;
+ data.do_select = FALSE;
+ data.do_activate = FALSE;
+
+ gtk_tree_model_foreach (GTK_TREE_MODEL (priv->store),
+ scan_for_strat_name,
+ &data);
+
+ retval = data.found;
+
+ g_free (data.strat_name);
+
+ return retval;
+}
+
+/**
+ * gdict_strategy_chooser_set_current_strategy:
+ * @chooser: a #GdictStrategyChooser
+ * @strat_name: the name of the matching strategy
+ *
+ * Sets @strat_name as the current matching strategy.
+ *
+ * Return value: %TRUE if the matching strategy was found
+ *
+ * Since: 0.10
+ */
+gboolean
+gdict_strategy_chooser_set_current_strategy (GdictStrategyChooser *chooser,
+ const gchar *strat_name)
+{
+ GdictStrategyChooserPrivate *priv;
+ SelectData data;
+ gboolean retval;
+
+ g_return_val_if_fail (GDICT_IS_STRATEGY_CHOOSER (chooser), FALSE);
+ g_return_val_if_fail (strat_name != NULL, FALSE);
+
+ priv = chooser->priv;
+
+ if (priv->current_strat && !strcmp (priv->current_strat, strat_name))
+ return TRUE;
+
+ data.strat_name = g_strdup (strat_name);
+ data.chooser = chooser;
+ data.found = FALSE;
+ data.do_select = TRUE;
+ data.do_activate = TRUE;
+
+ gtk_tree_model_foreach (GTK_TREE_MODEL (priv->store),
+ scan_for_strat_name,
+ &data);
+
+ retval = data.found;
+
+ if (data.found)
+ {
+ g_free (priv->current_strat);
+ priv->current_strat = data.strat_name;
+ }
+ else
+ g_free (data.strat_name);
+
+ return retval;
+}
+
+/**
+ * gdict_strategy_chooser_get_current_strategy:
+ * @chooser: a #GdictStrategyChooser
+ *
+ * Retrieves the current matching strategy.
+ *
+ * Return value: a newly allocated string containing the name of
+ * the current matching strategy
+ *
+ * Since: 0.10
+ */
+gchar *
+gdict_strategy_chooser_get_current_strategy (GdictStrategyChooser *chooser)
+{
+ GdictStrategyChooserPrivate *priv;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gchar *retval = NULL;
+
+ g_return_val_if_fail (GDICT_IS_STRATEGY_CHOOSER (chooser), NULL);
+
+ priv = chooser->priv;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+ return NULL;
+
+ gtk_tree_model_get (model, &iter, STRAT_COLUMN_NAME, &retval, -1);
+
+ g_free (priv->current_strat);
+ priv->current_strat = g_strdup (retval);
+
+ return retval;
+}
+
+/**
+ * gdict_strategy_chooser_add_button:
+ * @chooser: a #GdictStrategyChooser
+ * @button_text: text of the button (can be a stock id)
+ *
+ * Creates a new button and packs it into the #GdictStrategyChooser
+ * "action area".
+ *
+ * Return value: the packed #GtkButton
+ *
+ * Since: 0.10
+ */
+GtkWidget *
+gdict_strategy_chooser_add_button (GdictStrategyChooser *chooser,
+ const gchar *button_text)
+{
+ GdictStrategyChooserPrivate *priv;
+ GtkWidget *button;
+
+ g_return_val_if_fail (GDICT_IS_STRATEGY_CHOOSER (chooser), NULL);
+ g_return_val_if_fail (button_text != NULL, NULL);
+
+ priv = chooser->priv;
+
+ button = gtk_button_new_from_stock (button_text);
+
+ gtk_widget_set_can_default (button, TRUE);
+
+ gtk_widget_show (button);
+
+ gtk_box_pack_end (GTK_BOX (priv->buttons_box), button, FALSE, TRUE, 0);
+
+ return button;
+}
diff --git a/mate-dictionary/libgdict/gdict-strategy-chooser.h b/mate-dictionary/libgdict/gdict-strategy-chooser.h
new file mode 100644
index 00000000..efcf8e89
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-strategy-chooser.h
@@ -0,0 +1,94 @@
+/* gdict-strategy-chooser.h - display widget for strategy names
+ *
+ * Copyright (C) 2006 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+#ifndef __GDICT_STRATEGY_CHOOSER_H__
+#define __GDICT_STRATEGY_CHOOSER_H__
+
+#include <gtk/gtk.h>
+#include "gdict-context.h"
+
+G_BEGIN_DECLS
+
+#define GDICT_TYPE_STRATEGY_CHOOSER (gdict_strategy_chooser_get_type ())
+#define GDICT_STRATEGY_CHOOSER(obj) \
+(G_TYPE_CHECK_INSTANCE_CAST ((obj), GDICT_TYPE_STRATEGY_CHOOSER, GdictStrategyChooser))
+#define GDICT_IS_STRATEGY_CHOOSER(obj) \
+(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDICT_TYPE_STRATEGY_CHOOSER))
+#define GDICT_STRATEGY_CHOOSER_CLASS(klass) \
+(G_TYPE_CHECK_CLASS_CAST ((klass), GDICT_TYPE_STRATEGY_CHOOSER, GdictStrategyChooserClass))
+#define GDICT_IS_STRATEGY_CHOOSER_CLASS(klass) \
+(G_TYPE_CHECK_CLASS_TYPE ((klass), GDICT_TYPE_STRATEGY_CHOOSER))
+#define GDICT_STRATEGY_CHOOSER_GET_CLASS(obj) \
+(G_TYPE_INSTANCE_GET_CLASS ((obj), GDICT_TYPE_STRATEGY_CHOOSER, GdictStrategyChooserClass))
+
+typedef struct _GdictStrategyChooser GdictStrategyChooser;
+typedef struct _GdictStrategyChooserPrivate GdictStrategyChooserPrivate;
+typedef struct _GdictStrategyChooserClass GdictStrategyChooserClass;
+
+struct _GdictStrategyChooser
+{
+ GtkVBox parent_instance;
+
+ GdictStrategyChooserPrivate *priv;
+};
+
+struct _GdictStrategyChooserClass
+{
+ GtkVBoxClass parent_class;
+
+ void (*strategy_activated) (GdictStrategyChooser *chooser,
+ const gchar *name,
+ const gchar *description);
+
+ void (*_gdict_padding1) (void);
+ void (*_gdict_padding2) (void);
+ void (*_gdict_padding3) (void);
+ void (*_gdict_padding4) (void);
+ void (*_gdict_padding5) (void);
+ void (*_gdict_padding6) (void);
+};
+
+GType gdict_strategy_chooser_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gdict_strategy_chooser_new (void);
+GtkWidget * gdict_strategy_chooser_new_with_context (GdictContext *context);
+
+GdictContext *gdict_strategy_chooser_get_context (GdictStrategyChooser *chooser);
+void gdict_strategy_chooser_set_context (GdictStrategyChooser *chooser,
+ GdictContext *context);
+gboolean gdict_strategy_chooser_select_strategy (GdictStrategyChooser *chooser,
+ const gchar *strat_name);
+gboolean gdict_strategy_chooser_unselect_strategy (GdictStrategyChooser *chooser,
+ const gchar *strat_name);
+gboolean gdict_strategy_chooser_set_current_strategy (GdictStrategyChooser *chooser,
+ const gchar *strat_name);
+gchar * gdict_strategy_chooser_get_current_strategy (GdictStrategyChooser *chooser) G_GNUC_MALLOC;
+gchar ** gdict_strategy_chooser_get_strategies (GdictStrategyChooser *chooser,
+ gsize *length) G_GNUC_MALLOC;
+gint gdict_strategy_chooser_count_strategies (GdictStrategyChooser *chooser);
+gboolean gdict_strategy_chooser_has_strategy (GdictStrategyChooser *chooser,
+ const gchar *strategy);
+void gdict_strategy_chooser_refresh (GdictStrategyChooser *chooser);
+void gdict_strategy_chooser_clear (GdictStrategyChooser *chooser);
+GtkWidget * gdict_strategy_chooser_add_button (GdictStrategyChooser *chooser,
+ const gchar *button_text);
+
+G_END_DECLS
+
+#endif /* __GDICT_STRATEGY_CHOOSER_H__ */
diff --git a/mate-dictionary/libgdict/gdict-utils.c b/mate-dictionary/libgdict/gdict-utils.c
new file mode 100644
index 00000000..cdace3d3
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-utils.c
@@ -0,0 +1,333 @@
+/* gdict-utils.c - Utility functions for Gdict
+ *
+ * Copyright (C) 2005 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+
+#include "gdict-context-private.h"
+#include "gdict-debug.h"
+#include "gdict-utils.h"
+#include "gdict-version.h"
+#include "gdict-private.h"
+
+guint gdict_major_version = GDICT_MAJOR_VERSION;
+guint gdict_minor_version = GDICT_MINOR_VERSION;
+guint gdict_micro_version = GDICT_MICRO_VERSION;
+
+guint gdict_debug_flags = 0; /* global gdict debug flag */
+
+#ifdef GDICT_ENABLE_DEBUG
+static const GDebugKey gdict_debug_keys[] = {
+ { "misc", GDICT_DEBUG_MISC },
+ { "context", GDICT_DEBUG_CONTEXT },
+ { "dict", GDICT_DEBUG_DICT },
+ { "source", GDICT_DEBUG_SOURCE },
+ { "loader", GDICT_DEBUG_LOADER },
+ { "chooser", GDICT_DEBUG_CHOOSER },
+ { "defbux", GDICT_DEBUG_DEFBOX },
+ { "speller", GDICT_DEBUG_SPELLER },
+};
+#endif /* GDICT_ENABLE_DEBUG */
+
+static gboolean gdict_is_initialized = FALSE;
+
+#ifdef GDICT_ENABLE_DEBUG
+static gboolean
+gdict_arg_debug_cb (const char *key,
+ const char *value,
+ gpointer user_data)
+{
+ gdict_debug_flags |=
+ g_parse_debug_string (value,
+ gdict_debug_keys,
+ G_N_ELEMENTS (gdict_debug_keys));
+ return TRUE;
+}
+
+static gboolean
+gdict_arg_no_debug_cb (const char *key,
+ const char *value,
+ gpointer user_data)
+{
+ gdict_debug_flags &=
+ ~g_parse_debug_string (value,
+ gdict_debug_keys,
+ G_N_ELEMENTS (gdict_debug_keys));
+ return TRUE;
+}
+#endif /* CLUTTER_ENABLE_DEBUG */
+
+static GOptionEntry gdict_args[] = {
+#ifdef GDICT_ENABLE_DEBUG
+ { "gdict-debug", 0, 0, G_OPTION_ARG_CALLBACK, gdict_arg_debug_cb,
+ N_("GDict debugging flags to set"), N_("FLAGS") },
+ { "gdict-no-debug", 0, 0, G_OPTION_ARG_CALLBACK, gdict_arg_no_debug_cb,
+ N_("GDict debugging flags to unset"), N_("FLAGS") },
+#endif /* GDICT_ENABLE_DEBUG */
+ { NULL, },
+};
+
+static gboolean
+pre_parse_hook (GOptionContext *context,
+ GOptionGroup *group,
+ gpointer data,
+ GError **error)
+{
+ const char *env_string;
+
+ if (gdict_is_initialized)
+ return TRUE;
+
+#ifdef GDICT_ENABLE_DEBUG
+ env_string = g_getenv ("GDICT_DEBUG");
+ if (env_string != NULL)
+ {
+ gdict_debug_flags =
+ g_parse_debug_string (env_string,
+ gdict_debug_keys,
+ G_N_ELEMENTS (gdict_debug_keys));
+ }
+#else
+ env_string = NULL;
+#endif /* GDICT_ENABLE_DEBUG */
+
+ return TRUE;
+}
+
+static gboolean
+post_parse_hook (GOptionContext *context,
+ GOptionGroup *group,
+ gpointer data,
+ GError **error)
+{
+ gdict_is_initialized = TRUE;
+
+ return TRUE;
+}
+
+/**
+ * gdict_get_option_group:
+ *
+ * FIXME
+ *
+ * Return value: FIXME
+ *
+ * Since: 0.12
+ */
+GOptionGroup *
+gdict_get_option_group (void)
+{
+ GOptionGroup *group;
+
+ group = g_option_group_new ("gdict",
+ _("GDict Options"),
+ _("Show GDict Options"),
+ NULL,
+ NULL);
+
+ g_option_group_set_parse_hooks (group, pre_parse_hook, post_parse_hook);
+ g_option_group_add_entries (group, gdict_args);
+ g_option_group_set_translation_domain (group, GETTEXT_PACKAGE);
+
+ return group;
+}
+
+/**
+ * gdict_debug_init:
+ * @argc: FIXME
+ * @argv: FIXME
+ *
+ * FIXME
+ *
+ * Since: 0.12
+ */
+void
+gdict_debug_init (gint *argc,
+ gchar ***argv)
+{
+ GOptionContext *option_context;
+ GOptionGroup *gdict_group;
+ GError *error = NULL;
+
+ if (gdict_is_initialized)
+ return;
+
+ option_context = g_option_context_new (NULL);
+ g_option_context_set_ignore_unknown_options (option_context, TRUE);
+ g_option_context_set_help_enabled (option_context, FALSE);
+
+ gdict_group = gdict_get_option_group ();
+ g_option_context_set_main_group (option_context, gdict_group);
+
+ if (!g_option_context_parse (option_context, argc, argv, &error))
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+
+ g_option_context_free (option_context);
+}
+
+gboolean
+gdict_check_version (guint required_major,
+ guint required_minor,
+ guint required_micro)
+{
+ gint gdict_effective_micro = 100 * GDICT_MINOR_VERSION + GDICT_MICRO_VERSION;
+ gint required_effective_micro = 100 * required_minor + required_micro;
+
+ if (required_major > GDICT_MAJOR_VERSION)
+ return FALSE;
+
+ if (required_major < GDICT_MAJOR_VERSION)
+ return FALSE;
+
+ if (required_effective_micro < gdict_effective_micro)
+ return FALSE;
+
+ if (required_effective_micro > gdict_effective_micro)
+ return FALSE;
+
+ return TRUE;
+}
+/* gdict_has_ipv6: checks for the existence of the IPv6 extensions; if
+ * IPv6 support was not enabled, this function always return false
+ */
+gboolean
+_gdict_has_ipv6 (void)
+{
+#ifdef ENABLE_IPV6
+ int s;
+
+ s = socket (AF_INET6, SOCK_STREAM, 0);
+ if (s != -1)
+ {
+ close(s);
+
+ return TRUE;
+ }
+#endif
+
+ return FALSE;
+}
+
+/* shows an error dialog making it transient for @parent */
+static void
+show_error_dialog (GtkWindow *parent,
+ const gchar *message,
+ const gchar *detail)
+{
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new (parent,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ "%s", message);
+ gtk_window_set_title (GTK_WINDOW (dialog), "");
+
+ if (detail)
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", detail);
+
+ if (parent && gtk_window_get_group (parent))
+ gtk_window_group_add_window (gtk_window_get_group (parent), GTK_WINDOW (dialog));
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+
+ gtk_widget_destroy (dialog);
+}
+
+/* find the toplevel widget for @widget */
+static GtkWindow *
+get_toplevel_window (GtkWidget *widget)
+{
+ GtkWidget *toplevel;
+
+ toplevel = gtk_widget_get_toplevel (widget);
+ if (!gtk_widget_is_toplevel (toplevel))
+ return NULL;
+ else
+ return GTK_WINDOW (toplevel);
+}
+
+/**
+ * gdict_show_error_dialog:
+ * @widget: the widget that emits the error
+ * @title: the primary error message
+ * @message: the secondary error message or %NULL
+ *
+ * Creates and shows an error dialog bound to @widget.
+ *
+ * Since: 1.0
+ */
+void
+_gdict_show_error_dialog (GtkWidget *widget,
+ const gchar *title,
+ const gchar *detail)
+{
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+ g_return_if_fail (title != NULL);
+
+ show_error_dialog (get_toplevel_window (widget), title, detail);
+}
+
+/**
+ * gdict_show_gerror_dialog:
+ * @widget: the widget that emits the error
+ * @title: the primary error message
+ * @error: a #GError
+ *
+ * Creates and shows an error dialog bound to @widget, using @error
+ * to fill the secondary text of the message dialog with the error
+ * message. Also takes care of freeing @error.
+ *
+ * Since: 1.0
+ */
+void
+_gdict_show_gerror_dialog (GtkWidget *widget,
+ const gchar *title,
+ GError *error)
+{
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+ g_return_if_fail (title != NULL);
+ g_return_if_fail (error != NULL);
+
+ show_error_dialog (get_toplevel_window (widget), title, error->message);
+
+ g_error_free (error);
+}
diff --git a/mate-dictionary/libgdict/gdict-utils.h b/mate-dictionary/libgdict/gdict-utils.h
new file mode 100644
index 00000000..5f40eb5f
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-utils.h
@@ -0,0 +1,85 @@
+/* gdict-utils.h - Utility functions for Gdict
+ *
+ * Copyright (C) 2005 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+#ifndef __GDICT_UTILS_H__
+#define __GDICT_UTILS_H__
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GDICT_DEFAULT_DATABASE "*"
+#define GDICT_DEFAULT_STRATEGY "."
+
+#define GDICT_DEFAULT_FONT_NAME "Sans 10"
+
+/* properties shared between GdictContext implementations */
+typedef enum {
+ GDICT_CONTEXT_PROP_FIRST = 0x1000,
+ GDICT_CONTEXT_PROP_LOCAL_ONLY = GDICT_CONTEXT_PROP_FIRST,
+ GDICT_CONTEXT_PROP_LAST
+} GdictContextProp;
+
+/* Status codes as defined by RFC 2229 */
+typedef enum {
+ GDICT_STATUS_INVALID = 0,
+
+ GDICT_STATUS_N_DATABASES_PRESENT = 110,
+ GDICT_STATUS_N_STRATEGIES_PRESENT = 111,
+ GDICT_STATUS_DATABASE_INFO = 112,
+ GDICT_STATUS_HELP_TEXT = 113,
+ GDICT_STATUS_SERVER_INFO = 114,
+ GDICT_STATUS_CHALLENGE = 130,
+ GDICT_STATUS_N_DEFINITIONS_RETRIEVED = 150,
+ GDICT_STATUS_WORD_DB_NAME = 151,
+ GDICT_STATUS_N_MATCHES_FOUND = 152,
+ GDICT_STATUS_CONNECT = 220,
+ GDICT_STATUS_QUIT = 221,
+ GDICT_STATUS_AUTH_OK = 230,
+ GDICT_STATUS_OK = 250,
+ GDICT_STATUS_SEND_RESPONSE = 330,
+ /* Connect response codes */
+ GDICT_STATUS_SERVER_DOWN = 420,
+ GDICT_STATUS_SHUTDOWN = 421,
+ /* Error codes */
+ GDICT_STATUS_BAD_COMMAND = 500,
+ GDICT_STATUS_BAD_PARAMETERS = 501,
+ GDICT_STATUS_COMMAND_NOT_IMPLEMENTED = 502,
+ GDICT_STATUS_PARAMETER_NOT_IMPLEMENTED = 503,
+ GDICT_STATUS_NO_ACCESS = 530,
+ GDICT_STATUS_USE_SHOW_INFO = 531,
+ GDICT_STATUS_UNKNOWN_MECHANISM = 532,
+ GDICT_STATUS_BAD_DATABASE = 550,
+ GDICT_STATUS_BAD_STRATEGY = 551,
+ GDICT_STATUS_NO_MATCH = 552,
+ GDICT_STATUS_NO_DATABASES_PRESENT = 554,
+ GDICT_STATUS_NO_STRATEGIES_PRESENT = 555
+} GdictStatusCode;
+
+#define GDICT_IS_VALID_STATUS_CODE(x) (((x) > GDICT_STATUS_INVALID) && \
+ ((x) <= GDICT_STATUS_NO_STRATEGIES_PRESENT))
+
+GOptionGroup *gdict_get_option_group (void);
+void gdict_debug_init (gint *argc,
+ gchar ***argv);
+
+G_END_DECLS
+
+#endif /* __GDICT_UTILS_H__ */
diff --git a/mate-dictionary/libgdict/gdict-version.h.in b/mate-dictionary/libgdict/gdict-version.h.in
new file mode 100644
index 00000000..b9491cb9
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict-version.h.in
@@ -0,0 +1,92 @@
+/* gdict-version.h - convenience version header for libgdict
+ *
+ * Copyright (C) 2005 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+#ifndef __GDICT_VERSION_H__
+#define __GDICT_VERSION_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GDICT_MAJOR_VERSION:
+ *
+ * FIXME
+ */
+#define GDICT_MAJOR_VERSION (@GDICT_MAJOR_VERSION@)
+
+/**
+ * GDICT_MINOR_VERSION:
+ *
+ * FIXME
+ */
+#define GDICT_MINOR_VERSION (@GDICT_MINOR_VERSION@)
+
+/**
+ * GDICT_MICRO_VERSION:
+ *
+ * FIXME
+ */
+#define GDICT_MICRO_VERSION (@GDICT_MICRO_VERSION@)
+
+/**
+ * GDICT_VERSION:
+ *
+ * FIXME
+ *
+ * Since: 0.11
+ */
+#define GDICT_VERSION (@GDICT_VERSION@)
+
+/**
+ * GDICT_VERSION_S:
+ *
+ * FIXME
+ *
+ * Since: 0.11
+ */
+#define GDICT_VERSION_S "@GDICT_VERSION@"
+
+/**
+ * GDICT_VERSION_HEX:
+ *
+ * FIXME
+ *
+ * Since: 0.11
+ */
+#define GDICT_VERSION_HEX (GDICT_MAJOR_VERSION << 24 | \
+ GDICT_MINOR_VERSION << 16 | \
+ GDICT_MICRO_VERSION << 8)
+
+#define GDICT_CHECK_VERSION(major,minor,micro) \
+ (((major) >= GDICT_MAJOR_VERSION) || \
+ (((major) == GDICT_MAJOR_VERSION) && ((minor) >= GDICT_MINOR_VERSION)) || \
+ (((major) == GDICT_MAJOR_VERSION) && ((minor) == GDICT_MINOR_VERSION) && ((micro) >= GDICT_MICRO_VERSION)))
+
+extern guint gdict_major;
+extern guint gdict_minor;
+extern guint gdict_micro;
+
+gboolean gdict_check_version (guint required_major,
+ guint required_minor,
+ guint required_micro);
+
+G_END_DECLS
+
+#endif /* __GDICT_VERSION_H__ */
diff --git a/mate-dictionary/libgdict/gdict.h b/mate-dictionary/libgdict/gdict.h
new file mode 100644
index 00000000..f5a32af1
--- /dev/null
+++ b/mate-dictionary/libgdict/gdict.h
@@ -0,0 +1,36 @@
+/* gdict.h - convenience header for libgdict
+ *
+ * Copyright (C) 2005 Emmanuele Bassi <[email protected]>
+ *
+ * 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,
+ */
+
+#ifndef __GDICT_H__
+#define __GDICT_H__
+
+#include "gdict-context.h"
+#include "gdict-client-context.h"
+#include "gdict-source.h"
+#include "gdict-source-loader.h"
+#include "gdict-source-chooser.h"
+#include "gdict-database-chooser.h"
+#include "gdict-defbox.h"
+#include "gdict-speller.h"
+#include "gdict-strategy-chooser.h"
+#include "gdict-utils.h"
+#include "gdict-enum-types.h"
+#include "gdict-version.h"
+
+#endif /* __GDICT_H__ */
diff --git a/mate-dictionary/libgdict/mate-dict.pc.in b/mate-dictionary/libgdict/mate-dict.pc.in
new file mode 100644
index 00000000..9eff837e
--- /dev/null
+++ b/mate-dictionary/libgdict/mate-dict.pc.in
@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: gdict-1.0
+Description: MATE Dictionary Protocol client library
+Requires: gtk+-2.0
+Version: @GDICT_VERSION@
+Libs: -L${libdir} -lmate-dict
+Cflags: -I${includedir}/mate-dict
+