From ef0467789bfc8406b57ba553e4d59f4d6c3f9be8 Mon Sep 17 00:00:00 2001 From: Stefano Karapetsas Date: Wed, 14 Dec 2011 10:13:54 +0100 Subject: Moved from Mate-Extra repository --- mate-dictionary/libgdict/Makefile.am | 134 + mate-dictionary/libgdict/gdict-client-context.c | 2162 +++++++++++++++ mate-dictionary/libgdict/gdict-client-context.h | 98 + mate-dictionary/libgdict/gdict-context-private.h | 107 + mate-dictionary/libgdict/gdict-context.c | 892 +++++++ mate-dictionary/libgdict/gdict-context.h | 204 ++ mate-dictionary/libgdict/gdict-database-chooser.c | 1142 ++++++++ mate-dictionary/libgdict/gdict-database-chooser.h | 99 + mate-dictionary/libgdict/gdict-debug.h | 55 + mate-dictionary/libgdict/gdict-defbox.c | 2927 +++++++++++++++++++++ mate-dictionary/libgdict/gdict-defbox.h | 102 + mate-dictionary/libgdict/gdict-enum-types.c.in | 33 + mate-dictionary/libgdict/gdict-enum-types.h.in | 26 + mate-dictionary/libgdict/gdict-marshal.list | 7 + mate-dictionary/libgdict/gdict-private.h | 34 + mate-dictionary/libgdict/gdict-source-chooser.c | 917 +++++++ mate-dictionary/libgdict/gdict-source-chooser.h | 93 + mate-dictionary/libgdict/gdict-source-loader.c | 600 +++++ mate-dictionary/libgdict/gdict-source-loader.h | 81 + mate-dictionary/libgdict/gdict-source.c | 1195 +++++++++ mate-dictionary/libgdict/gdict-source.h | 115 + mate-dictionary/libgdict/gdict-speller.c | 827 ++++++ mate-dictionary/libgdict/gdict-speller.h | 87 + mate-dictionary/libgdict/gdict-strategy-chooser.c | 1092 ++++++++ mate-dictionary/libgdict/gdict-strategy-chooser.h | 94 + mate-dictionary/libgdict/gdict-utils.c | 333 +++ mate-dictionary/libgdict/gdict-utils.h | 85 + mate-dictionary/libgdict/gdict-version.h.in | 92 + mate-dictionary/libgdict/gdict.h | 36 + mate-dictionary/libgdict/mate-dict.pc.in | 12 + 30 files changed, 13681 insertions(+) create mode 100644 mate-dictionary/libgdict/Makefile.am create mode 100644 mate-dictionary/libgdict/gdict-client-context.c create mode 100644 mate-dictionary/libgdict/gdict-client-context.h create mode 100644 mate-dictionary/libgdict/gdict-context-private.h create mode 100644 mate-dictionary/libgdict/gdict-context.c create mode 100644 mate-dictionary/libgdict/gdict-context.h create mode 100644 mate-dictionary/libgdict/gdict-database-chooser.c create mode 100644 mate-dictionary/libgdict/gdict-database-chooser.h create mode 100644 mate-dictionary/libgdict/gdict-debug.h create mode 100644 mate-dictionary/libgdict/gdict-defbox.c create mode 100644 mate-dictionary/libgdict/gdict-defbox.h create mode 100644 mate-dictionary/libgdict/gdict-enum-types.c.in create mode 100644 mate-dictionary/libgdict/gdict-enum-types.h.in create mode 100644 mate-dictionary/libgdict/gdict-marshal.list create mode 100644 mate-dictionary/libgdict/gdict-private.h create mode 100644 mate-dictionary/libgdict/gdict-source-chooser.c create mode 100644 mate-dictionary/libgdict/gdict-source-chooser.h create mode 100644 mate-dictionary/libgdict/gdict-source-loader.c create mode 100644 mate-dictionary/libgdict/gdict-source-loader.h create mode 100644 mate-dictionary/libgdict/gdict-source.c create mode 100644 mate-dictionary/libgdict/gdict-source.h create mode 100644 mate-dictionary/libgdict/gdict-speller.c create mode 100644 mate-dictionary/libgdict/gdict-speller.h create mode 100644 mate-dictionary/libgdict/gdict-strategy-chooser.c create mode 100644 mate-dictionary/libgdict/gdict-strategy-chooser.h create mode 100644 mate-dictionary/libgdict/gdict-utils.c create mode 100644 mate-dictionary/libgdict/gdict-utils.h create mode 100644 mate-dictionary/libgdict/gdict-version.h.in create mode 100644 mate-dictionary/libgdict/gdict.h create mode 100644 mate-dictionary/libgdict/mate-dict.pc.in (limited to 'mate-dictionary/libgdict') 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#include + +#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 : "", + command->strategy ? command->strategy : "", + command->word ? command->word : ""); + + 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 + * + * 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 + +#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 + * + * 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 + +#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 + * + * 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 + +#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 + * + * 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 + +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 + * + * 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 +#include +#include +#include + +#include +#include +#include + +#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 + * + * 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 +#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 + * + * 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 + +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 + * + * 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 #GdictDefinitions obtained. + * + * It provides syntax highlighting, clickable links and an embedded find + * bar. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include +#include +#include + +#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 (" ", _("Not found"), "", 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 (" ", _("Not found"), "", 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 (" ", _("Not found"), "", 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 + * + * 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 +#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 + +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 + * + * 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 +#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 + * + * 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 +#include + +#include + +#include + +#include + +#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 + * + * 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 +#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 + * + * 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 +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#include +#include + +#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 + * + * 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 +#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 + * + * 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 +#include +#include + +#include + +#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: + * + * + * #include <gdict/gdict.h> + * 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); + * + * + * 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 + * + * 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 +#include +#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 + * + * 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 +#include +#include +#include + +#include +#include +#include + +#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 + * + * 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 +#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 + * + * 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 +#include +#include +#include + +#include +#include + +#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 + * + * 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 +#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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#include +#include + +#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 + * + * 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 +#include + +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 + * + * 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 + +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 + * + * 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 + -- cgit v1.2.1