? gossip-spell.patch
? spell.patch
? spell.patch2
Index: ChangeLog
===================================================================
RCS file: /cvs/gnome/gossip/ChangeLog,v
retrieving revision 1.417
diff -u -b -B -p -r1.417 ChangeLog
--- ChangeLog	19 Jan 2005 23:34:30 -0000	1.417
+++ ChangeLog	20 Jan 2005 20:08:31 -0000
@@ -1,3 +1,18 @@
+2005-01-20  Martyn Russell  <mr@gnome.org>
+
+	* src/gossip-spell-dialog.c:
+	- Added to present a list of suggestions.
+	* configure.in:
+	- Added checks for aspell, Gossip does not depend on it.
+	* src/gossip-spell.c:
+	- Added code to call use aspell library and provide generic API
+	  for the GossipChat object to use.
+	* src/gossip-chat.c:
+	- Added menu items to the context menu of the input textview to
+	  list suggestions for misspelled words.
+	* src/gossip-preferences.c:
+	- Added ability to disable spell checking.
+
 2005-01-20  Mikael Hallendal  <micke@imendio.com>
 
 	* src/gossip-app.c: (app_complete_name_response_cb):
Index: configure.in
===================================================================
RCS file: /cvs/gnome/gossip/configure.in,v
retrieving revision 1.79
diff -u -b -B -p -r1.79 configure.in
--- configure.in	16 Jan 2005 22:57:23 -0000	1.79
+++ configure.in	20 Jan 2005 20:08:32 -0000
@@ -141,6 +141,31 @@ ALL_LINGUAS="ar az bg ca cs cy da de en_
 AM_GLIB_GNU_GETTEXT
 dnl -----------------------------------------------------------
 
+
+dnl -----------------------------------------------------------
+dnl Pango Version (used for highlighting spelling errors) 
+dnl -----------------------------------------------------------
+PKG_CHECK_MODULES(PANGO, [pango >= 1.3.5],
+   [AC_DEFINE(HAVE_PANGO_UNDERLINE_ERROR, 1, [Define to 1 if PANGO_UNDERLINE_ERROR is available.])],
+   [AC_MSG_WARN([Disabling new error underlining since pango < 1.3.5])])           
+dnl -----------------------------------------------------------
+
+
+dnl -----------------------------------------------------------
+dnl ASpell
+dnl -----------------------------------------------------------
+
+AC_CHECK_LIB(aspell, aspell_speller_check,have_aspell=yes,have_aspell=no,)
+
+if test "x$have_aspell" = "xyes"; then
+  AC_DEFINE(HAVE_ASPELL, 1, [Define if we have aspell])
+
+  GOSSIP_LIBS="$GOSSIP_LIBS -laspell"
+fi
+
+dnl -----------------------------------------------------------
+
+
 CFLAGS="$CFLAGS $WARN_CFLAGS"
 
 AC_OUTPUT([
@@ -157,4 +182,5 @@ gossip.desktop.in
 echo
 echo "Gossip prefix: $prefix"
 echo "D-BUS        : $have_dbus"
+echo "ASpell       : $have_aspell"
 echo
Index: src/Makefile.am
===================================================================
RCS file: /cvs/gnome/gossip/src/Makefile.am,v
retrieving revision 1.33
diff -u -b -B -p -r1.33 Makefile.am
--- src/Makefile.am	8 Jan 2005 11:07:03 -0000	1.33
+++ src/Makefile.am	20 Jan 2005 20:08:32 -0000
@@ -14,7 +14,9 @@ AM_CPPFLAGS =	\
 	-DDBUS_API_SUBJECT_TO_CHANGE
 
 if HAVE_DBUS
-dbus_sources = gossip-dbus.c gossip-dbus.h
+dbus_sources =                                                         \
+       gossip-dbus.c                   gossip-dbus.h                   \
+       gossip-jabber-gnomemeeting.c    gossip-jabber-gnomemeeting.h
 endif
 
 bin_PROGRAMS = gossip
@@ -63,6 +65,8 @@ gossip_SOURCES = 				                   
 	gossip-sound.c		           gossip-sound.h			\
 	gossip-startup-druid.c	           gossip-startup-druid.h		\
 	gossip-stock.c		           gossip-stock.h			\
+	gossip-spell.c                     gossip-spell.h                       \
+	gossip-spell-dialog.c 		   gossip-spell-dialog.h                \
 	gossip-time.c                      gossip-time.h                        \
 	gossip-utils.c		           gossip-utils.h			\
 	gossip-edit-groups-dialog.c        gossip-edit-groups-dialog.h		\
@@ -81,15 +85,17 @@ gossip_SOURCES = 				                   
 gossip_LDADD = @GOSSIP_LIBS@
 gossip_LDFLAGS = -export-dynamic
 
+GLIB_GENMARSHAL = $(shell pkg-config glib-2.0 --variable=glib_genmarshal)
+
 gossip-marshal.h: gossip-marshal.list
 	(cd $(srcdir) && \
-	@GLIB_GENMARSHAL@ --prefix=gossip_marshal gossip-marshal.list --header) > xgen-gmh \
+	$(GLIB_GENMARSHAL) --prefix=gossip_marshal gossip-marshal.list --header) > xgen-gmh \
 	&& (cmp -s xgen-gmh gossip-marshal.h || cp xgen-gmh gossip-marshal.h) \
 	&& rm -f xgen-gmh xgen-gmh~
 
 gossip-marshal.c: gossip-marshal.list
 	(cd $(srcdir) && \
-	@GLIB_GENMARSHAL@ --prefix=gossip_marshal gossip-marshal.list --body) > xgen-gmc \
+	$(GLIB_GENMARSHAL) --prefix=gossip_marshal gossip-marshal.list --body) > xgen-gmc \
 	&& cp xgen-gmc gossip-marshal.c \
 	&& rm -f xgen-gmc xgen-gmc~
 
Index: src/gossip-chat.c
===================================================================
RCS file: /cvs/gnome/gossip/src/gossip-chat.c,v
retrieving revision 1.91
diff -u -b -B -p -r1.91 gossip-chat.c
--- src/gossip-chat.c	30 Dec 2004 16:26:58 -0000	1.91
+++ src/gossip-chat.c	20 Jan 2005 20:08:33 -0000
@@ -2,6 +2,7 @@
 /*
  * Copyright (C) 2002-2004 Imendio HB
  * Copyright (C) 2003-2004 Geert-Jan Van den Bogaerde <geertjan@gnome.org>
+ * Copyright (C) 2004      Martyn Russell <mr@gnome.org>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -19,11 +20,21 @@
  * Boston, MA 02111-1307, USA.
  */
 
+#include <string.h>
+#include <stdlib.h>
 #include <config.h>
 #include <gtk/gtk.h>
+#include <gconf/gconf-client.h>
+#include <libgnome/gnome-i18n.h>
 #include "gossip-chat.h"
 #include "gossip-marshal.h"
 #include "gossip-chat-window.h"
+#include "gossip-spell.h"
+#include "gossip-spell-dialog.h"
+
+#define d(x) x
+
+extern GConfClient *gconf_client;
 
 struct _GossipChatPriv {
 	GossipChatWindow *window;
@@ -35,6 +46,15 @@ struct _GossipChatPriv {
 	gint              last_input_height;
 };
 
+typedef struct {
+	GossipChat  *chat;
+       	gchar       *word;
+
+       	GtkTextIter  start;
+       	GtkTextIter  end;
+} GossipChatSpell;
+
+
 static void     gossip_chat_class_init            (GossipChatClass *klass);
 static void     gossip_chat_init                  (GossipChat      *chat);
 static void     chat_finalize                     (GObject         *object);
@@ -43,6 +63,19 @@ static void     chat_input_text_buffer_c
 static void     chat_text_view_size_allocate_cb   (GtkWidget       *widget,
 		                                   GtkAllocation   *allocation,
 					           GossipChat      *chat);
+static void      chat_text_populate_popup_cb       (GtkTextView     *view,
+						    GtkMenu         *menu,
+						    GossipChat      *chat);
+static void      chat_text_check_word_spelling_cb  (GtkMenuItem     *menuitem,
+						    GossipChatSpell *chat_spell);
+GossipChatSpell *chat_spell_new                    (GossipChat      *chat,
+						    const gchar     *word,
+						    GtkTextIter      start,
+						    GtkTextIter      end);
+static void      chat_spell_free                   (GossipChatSpell *chat_spell);
+
+
+
 
 enum {
 	COMPOSING,
@@ -143,7 +176,25 @@ gossip_chat_init (GossipChat *chat)
 			  G_CALLBACK (chat_text_view_size_allocate_cb),
 			  chat);
 
+	g_signal_connect (GTK_TEXT_VIEW (chat->input_text_view),
+			  "populate_popup",
+			  G_CALLBACK (chat_text_populate_popup_cb),
+			  chat);
+
 	gossip_chat_view_set_margin (chat->view, 3);
+
+	/* create misspelt words identification tag */
+	gtk_text_buffer_create_tag (buffer,
+				    "misspelled",
+#ifdef HAVE_PANGO_UNDERLINE_ERROR
+				    "underline", PANGO_UNDERLINE_ERROR,
+#else
+				    "foreground", "red", 
+				    "underline", PANGO_UNDERLINE_SINGLE,
+#endif
+				    NULL);
+
+	chat->spell = gossip_spell_new (NULL);
 }
 
 static void
@@ -157,6 +208,7 @@ chat_finalize (GObject *object)
 	chat = GOSSIP_CHAT (object);
 	priv = chat->priv;
 
+	gossip_spell_unref (chat->spell);
 	g_free (priv);
 
 	G_OBJECT_CLASS (parent_class)->finalize (object);
@@ -168,8 +220,17 @@ chat_input_text_buffer_changed_cb (GtkTe
 {
 	GossipChatPriv *priv;
 
+	GtkTextIter     start, end;
+	gchar          *str;
+	gboolean        disable_spell_checker;
+
 	priv = chat->priv;
 
+ 	disable_spell_checker = gconf_client_get_bool ( 
+ 		gconf_client, 
+ 		"/apps/gossip/conversation/disable_spell_checker", 
+ 		NULL); 
+
 	if (chat->is_first_char) {
 		GtkRequisition  req;
 		gint            window_height;
@@ -191,6 +252,54 @@ chat_input_text_buffer_changed_cb (GtkTe
 
 		chat->is_first_char = FALSE;
 	}
+
+	gtk_text_buffer_get_start_iter (buffer, &start);
+
+	if (disable_spell_checker) {
+		gtk_text_buffer_get_end_iter (buffer, &end);
+		gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
+		return;
+	}
+	
+	/* NOTE: this is really inefficient, we shouldn't have to
+	   reiterate the whole buffer each time and check each work
+	   every time. */
+	while (gossip_spell_has_backend (chat->spell)) {
+		gboolean correct = FALSE;
+
+		/* if at start */
+		if (gtk_text_iter_is_start (&start)) {
+			end = start;
+
+			if (!gtk_text_iter_forward_word_end (&end)) {
+				/* no whole word yet */
+				break;
+			}
+		} else {
+			if (!gtk_text_iter_forward_word_end (&end)) {
+				/* must be the end of the buffer */
+				break;
+			}
+
+			start = end; 
+			gtk_text_iter_backward_word_start (&start);
+		}
+	
+		str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+
+		/* spell check string */
+		correct = gossip_spell_check (chat->spell, str);
+		if (!correct) {
+			gtk_text_buffer_apply_tag_by_name (buffer, "misspelled", &start, &end);
+		} else {
+			gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
+		}
+
+		g_free (str);
+
+		/* set start iter to the end iters position */
+		start = end; 
+	}
 }
 
 typedef struct {
@@ -265,6 +374,128 @@ chat_text_view_size_allocate_cb (GtkWidg
 	g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
 			 (GSourceFunc) chat_change_size_in_idle_cb,
 			 data, g_free);
+}
+
+static void
+chat_text_populate_popup_cb (GtkTextView *view,
+			     GtkMenu     *menu,
+			     GossipChat  *chat)
+{
+	GtkTextBuffer      *buffer;
+	GtkTextTagTable    *table;
+	GtkTextTag         *tag;
+	gint                x, y;
+	GtkTextIter         iter, start, end;
+	GtkWidget          *item;
+	gchar              *str = NULL;
+	GossipChatSpell    *chat_spell;
+
+	buffer = gtk_text_view_get_buffer (view);
+	table = gtk_text_buffer_get_tag_table (buffer);
+	
+	/* handle misspelled tags */
+	tag = gtk_text_tag_table_lookup (table, "misspelled");
+	
+	gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
+	
+	gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view), 
+					       GTK_TEXT_WINDOW_WIDGET,
+					       x, y,
+					       &x, &y);
+	
+	gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
+
+	start = end = iter;
+	
+	if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
+	    gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
+					
+		str = gtk_text_buffer_get_text (buffer, 
+						&start, &end, FALSE);
+	}
+
+	if (!str || strlen (str) == 0) {
+		return;
+	}
+
+	chat_spell = chat_spell_new (chat, str, start, end);
+
+	g_object_set_data_full (G_OBJECT (menu), 
+				"chat_spell", chat_spell, 
+				(GDestroyNotify) chat_spell_free);
+
+	item = gtk_separator_menu_item_new ();
+	gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+	gtk_widget_show (item);
+	
+	item = gtk_menu_item_new_with_mnemonic (_("_Check Word Spelling..."));
+	g_signal_connect (item,
+			  "activate",
+			  G_CALLBACK (chat_text_check_word_spelling_cb),
+			  chat_spell);
+	gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+	gtk_widget_show (item);
+}
+
+GossipChatSpell *
+chat_spell_new (GossipChat  *chat, 
+		const gchar *word,
+		GtkTextIter  start,
+		GtkTextIter  end)
+{
+	GossipChatSpell *chat_spell;
+
+	g_return_val_if_fail (chat != NULL, NULL);
+	g_return_val_if_fail (word != NULL, NULL);
+
+	chat_spell = g_new0 (GossipChatSpell, 1);
+
+	chat_spell->chat = g_object_ref (chat);
+
+	chat_spell->word = g_strdup (word);
+
+	chat_spell->start = start;
+	chat_spell->end = end;
+	
+	return chat_spell;
+}
+
+static void
+chat_spell_free (GossipChatSpell *chat_spell)
+{
+	g_object_unref (chat_spell->chat);
+	g_free (chat_spell->word);
+	g_free (chat_spell);
+}
+
+static void     
+chat_text_check_word_spelling_cb (GtkMenuItem         *menuitem,
+				  GossipChatSpell     *chat_spell)
+{
+	gossip_spell_dialog_show (chat_spell->chat,
+				  chat_spell->start,
+				  chat_spell->end,
+				  chat_spell->word);
+}
+
+void
+gossip_chat_correct_word (GossipChat  *chat,
+			  GtkTextIter  start,
+			  GtkTextIter  end,
+			  const gchar *new_word)
+{
+	GtkTextBuffer *buffer;
+
+	g_return_if_fail (chat != NULL);
+	g_return_if_fail (new_word != NULL);
+	g_return_if_fail (strlen (new_word) > 0);
+
+	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+
+	gtk_text_buffer_delete (buffer, &start, &end);
+	gtk_text_buffer_insert (buffer, &start, 
+				new_word, 
+				g_utf8_strlen (new_word, -1));
 }
 
 const gchar *
Index: src/gossip-chat.h
===================================================================
RCS file: /cvs/gnome/gossip/src/gossip-chat.h,v
retrieving revision 1.23
diff -u -b -B -p -r1.23 gossip-chat.h
--- src/gossip-chat.h	30 Dec 2004 16:26:58 -0000	1.23
+++ src/gossip-chat.h	20 Jan 2005 20:08:34 -0000
@@ -41,10 +41,13 @@ typedef struct _GossipChatClass GossipCh
 typedef struct _GossipChatPriv GossipChatPriv;
 
 #include "gossip-chat-window.h"
+#include "gossip-spell.h"
 
 struct _GossipChat {
         GObject         parent;
 
+	GossipSpell    *spell;
+
 	/* protected */
 	GossipChatView *view;
 	GtkWidget      *input_text_view;
@@ -100,6 +103,13 @@ void              gossip_chat_get_geomet
 		                                  int              *width,
 						  int              *height);
 gboolean          gossip_chat_get_group_chat     (GossipChat       *chat);
+
+/* for spell checker dialog to correct the misspelled word */
+void              gossip_chat_correct_word       (GossipChat       *chat,
+						  GtkTextIter       start,
+						  GtkTextIter       end,
+						  const gchar      *new_word);
+
 
 G_END_DECLS
 
Index: src/gossip-preferences.c
===================================================================
RCS file: /cvs/gnome/gossip/src/gossip-preferences.c,v
retrieving revision 1.19
diff -u -b -B -p -r1.19 gossip-preferences.c
--- src/gossip-preferences.c	21 Apr 2004 00:04:26 -0000	1.19
+++ src/gossip-preferences.c	20 Jan 2005 20:08:34 -0000
@@ -49,6 +49,7 @@ typedef struct {
 	GtkWidget    *compact_checkbutton;
 	GtkWidget    *leaving_entry;
 	GtkWidget    *away_entry;
+	GtkWidget    *spell_checker_checkbutton;
 	
 	GList        *ids;
 } GossipPreferences;
@@ -362,6 +363,10 @@ preferences_setup_widgets (GossipPrefere
 	hookup_toggle_button (preferences,
 			      GCONF_PATH "/conversation/graphical_smileys",
 			      preferences->smileys_checkbutton);
+
+	hookup_toggle_button (preferences,
+			      GCONF_PATH "/conversation/disable_spell_checker",
+			      preferences->spell_checker_checkbutton);
 }
 
 void
@@ -386,6 +391,7 @@ gossip_preferences_show (void)
 				     "silent_busy_checkbutton", &preferences->silent_busy_checkbutton,
 				     "silent_away_checkbutton", &preferences->silent_away_checkbutton,
 				     "smileys_checkbutton", &preferences->smileys_checkbutton,
+				     "spell_checker_checkbutton", &preferences->spell_checker_checkbutton,
 				     NULL);
 
 	g_object_add_weak_pointer (G_OBJECT (preferences->dialog),
Index: src/gossip-session.c
===================================================================
RCS file: /cvs/gnome/gossip/src/gossip-session.c,v
retrieving revision 1.7
diff -u -b -B -p -r1.7 gossip-session.c
--- src/gossip-session.c	19 Jan 2005 22:25:59 -0000	1.7
+++ src/gossip-session.c	20 Jan 2005 20:08:34 -0000
@@ -22,6 +22,9 @@
 
 #include "gossip-account.h"
 #include "gossip-jabber.h"
+#ifdef HAVE_DBUS
+#include "gossip-jabber-gnomemeeting.h"
+#endif
 #include "gossip-marshal.h"
 #include "gossip-session.h"
 
@@ -30,6 +33,9 @@
 typedef struct _GossipSessionPriv  GossipSessionPriv;
 struct _GossipSessionPriv {
 	GossipJabber   *default_jabber;
+
+	GObject        *gnomemeeting;
+
 	GList          *protocols;
 
 	gchar          *account_name;
@@ -231,6 +237,12 @@ gossip_session_init (GossipSession *sess
 	session_connect_protocol (session, protocol);
 	
 	priv->protocols = g_list_prepend (priv->protocols, protocol);
+	
+#ifdef HAVE_DBUS
+	priv->gnomemeeting = G_OBJECT (gossip_jabber_gnomemeeting_new (GOSSIP_JABBER (protocol)));
+#else
+	priv->gnomemeeting = NULL;
+#endif
 }
 
 static void
@@ -256,6 +268,8 @@ session_finalize (GObject *object)
 
 		g_list_free (priv->contacts);
 	}
+
+	g_object_unref (priv->gnomemeeting);
 
 	(* G_OBJECT_CLASS (parent_class)->finalize) (object);
 }
Index: src/gossip-spell-dialog.c
===================================================================
RCS file: src/gossip-spell-dialog.c
diff -N src/gossip-spell-dialog.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/gossip-spell-dialog.c	20 Jan 2005 20:08:34 -0000
@@ -0,0 +1,253 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004 Martyn Russell <mr@gnome.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <libgnome/gnome-i18n.h>
+#include "gossip-spell-dialog.h"
+#include "gossip-utils.h"
+
+#define d(x)
+
+
+struct _GossipSpellDialog {
+	GtkWidget   *window;
+	GtkWidget   *button_ok;
+	GtkWidget   *label_word;
+	GtkWidget   *treeview_words;
+
+	GossipChat  *chat;
+	gchar       *word;
+	GtkTextIter start;
+	GtkTextIter end;
+};
+
+
+typedef struct _GossipSpellDialog GossipSpellDialog;
+
+
+enum {
+	COL_SPELL_WORD,
+	COL_SPELL_COUNT
+};
+
+
+static void spell_setup                (GossipSpellDialog *dialog);
+static void spell_populate_columns     (GossipSpellDialog *dialog);
+static void spell_populate_suggestions (GossipSpellDialog *dialog);
+static void spell_selection_changed_cb (GtkTreeSelection  *treeselection,
+					GossipSpellDialog *dialog);
+static void spell_dialog_response_cb   (GtkWidget         *widget,
+					gint               response,
+					GossipSpellDialog *dialog);
+static void spell_dialog_destroy_cb    (GtkWidget         *widget,
+					GossipSpellDialog *dialog);
+
+
+void
+gossip_spell_dialog_show (GossipChat  *chat,
+			  GtkTextIter  start,
+			  GtkTextIter  end,
+			  const gchar *word)
+{
+	GossipSpellDialog *dialog;
+	GladeXML          *gui;
+	gchar             *str;
+
+	g_return_if_fail (chat != NULL);
+	g_return_if_fail (word != NULL);
+	g_return_if_fail (strlen (word) > 0);
+
+	dialog = g_new0 (GossipSpellDialog, 1);
+
+	dialog->chat = g_object_ref (chat);
+
+	dialog->word = g_strdup (word);
+
+	dialog->start = start;
+	dialog->end = end;
+
+	gui = gossip_glade_get_file (GLADEDIR "/main.glade",
+				     "spell_dialog",
+				     NULL,
+				     "spell_dialog", &dialog->window,
+				     "button_ok", &dialog->button_ok,
+				     "label_word", &dialog->label_word,
+				     "treeview_words", &dialog->treeview_words,
+				     NULL);
+
+	gossip_glade_connect (gui,
+			      dialog,
+			      "spell_dialog", "response", spell_dialog_response_cb,
+			      "spell_dialog", "destroy", spell_dialog_destroy_cb,
+			      NULL);
+
+	g_object_unref (gui);
+
+	str = g_strdup_printf ("%s:\n<b>%s</b>", 
+			       _("Suggestions for the word"),
+			       word);
+
+	gtk_label_set_markup (GTK_LABEL (dialog->label_word), str);
+	g_free (str);
+	
+	spell_setup (dialog);
+}
+
+static void 
+spell_setup (GossipSpellDialog *dialog)
+{
+	GtkListStore     *store;
+	GtkTreeSelection *selection;
+
+	store = gtk_list_store_new (COL_SPELL_COUNT,
+				    G_TYPE_STRING);   /* word */
+	
+	gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->treeview_words), 
+				 GTK_TREE_MODEL (store));
+
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->treeview_words));
+	gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+	
+	g_signal_connect (selection, "changed",
+			  G_CALLBACK (spell_selection_changed_cb), dialog);
+
+	spell_populate_columns (dialog);
+	spell_populate_suggestions (dialog);
+
+	g_object_unref (store);
+}
+
+static void 
+spell_populate_columns (GossipSpellDialog *dialog)
+{
+	GtkTreeModel      *model;
+	GtkTreeViewColumn *column; 
+	GtkCellRenderer   *renderer;
+	guint              col_offset;
+	
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->treeview_words));
+	
+	renderer = gtk_cell_renderer_text_new ();
+	col_offset = gtk_tree_view_insert_column_with_attributes (
+		GTK_TREE_VIEW (dialog->treeview_words),
+		-1, _("Word"),
+		renderer, 
+		"text", COL_SPELL_WORD,
+		NULL);
+	
+	g_object_set_data (G_OBJECT (renderer),
+			   "column", GINT_TO_POINTER (COL_SPELL_WORD));
+	
+	column = gtk_tree_view_get_column (GTK_TREE_VIEW (dialog->treeview_words), col_offset - 1);
+	gtk_tree_view_column_set_sort_column_id (column, COL_SPELL_WORD);
+	gtk_tree_view_column_set_resizable (column, FALSE);
+	gtk_tree_view_column_set_clickable (GTK_TREE_VIEW_COLUMN (column), TRUE);
+}
+
+static void
+spell_populate_suggestions (GossipSpellDialog *dialog)
+{
+	GossipChat   *chat;
+
+	GList        *l;
+	GList        *suggestions;
+
+	GtkTreeModel *model;
+	GtkListStore *store;
+
+	chat = dialog->chat;
+
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->treeview_words));
+	store = GTK_LIST_STORE (model);
+
+	suggestions = gossip_spell_suggestions (chat->spell,
+						dialog->word);
+
+	if (!suggestions) {
+		return;
+	}
+
+	for (l=suggestions; l; l=l->next) {
+		GtkTreeIter  iter;
+		gchar       *word;
+
+		word = l->data;
+		gtk_list_store_append (store, &iter);
+		gtk_list_store_set (store, &iter, COL_SPELL_WORD, word, -1);
+	}
+
+	g_list_foreach (suggestions, (GFunc)g_free, NULL);
+	g_list_free (suggestions);
+}
+
+static void
+spell_selection_changed_cb (GtkTreeSelection  *treeselection,
+			    GossipSpellDialog *dialog)
+{
+	gint count;
+
+	count = gtk_tree_selection_count_selected_rows (treeselection);
+	gtk_widget_set_sensitive (dialog->button_ok, (count == 1));
+}
+
+static void
+spell_dialog_destroy_cb (GtkWidget         *widget, 
+			 GossipSpellDialog *dialog)
+{
+	g_object_unref (dialog->chat);
+	g_free (dialog->word);
+	
+ 	g_free (dialog); 
+}
+
+static void
+spell_dialog_response_cb (GtkWidget         *widget,
+			  gint               response, 
+			  GossipSpellDialog *dialog)
+{
+	if (response == GTK_RESPONSE_OK) {
+		GtkTreeView      *view;
+		GtkTreeModel     *model;
+		GtkTreeSelection *selection;
+		GtkTreeIter       iter;
+
+		gchar            *new_word;
+
+		view = GTK_TREE_VIEW (dialog->treeview_words);
+		selection = gtk_tree_view_get_selection (view);
+
+		if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
+			return;
+		}
+		
+		gtk_tree_model_get (model, &iter, COL_SPELL_WORD, &new_word, -1);
+
+		gossip_chat_correct_word (dialog->chat, 
+					  dialog->start,
+					  dialog->end,
+					  new_word);
+		
+		g_free (new_word);
+	}
+
+	gtk_widget_destroy (dialog->window);
+}
Index: src/gossip-spell-dialog.h
===================================================================
RCS file: src/gossip-spell-dialog.h
diff -N src/gossip-spell-dialog.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/gossip-spell-dialog.h	20 Jan 2005 20:08:34 -0000
@@ -0,0 +1,37 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004 Martyn Russell <mr@gnome.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GOSSIP_SPELL_DIALOG_H__
+#define __GOSSIP_SPELL_DIALOG_H__
+
+#include <glib.h>
+
+#include "gossip-chat.h"
+
+G_BEGIN_DECLS
+
+void gossip_spell_dialog_show (GossipChat  *chat, 
+			       GtkTextIter  start,
+			       GtkTextIter  end,
+			       const gchar *word);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_SPELL_DIALOG_H__ */
Index: src/gossip-spell.c
===================================================================
RCS file: src/gossip-spell.c
diff -N src/gossip-spell.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/gossip-spell.c	20 Jan 2005 20:08:34 -0000
@@ -0,0 +1,179 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004 Martyn Russell (mr@gnome.org)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef HAVE_ASPELL
+#include <aspell.h>
+#endif
+
+#include "gossip-spell.h"
+
+#define d(x) x
+
+
+struct _GossipSpell {
+	gint                ref_count;
+
+	gchar              *language;
+	
+	gboolean            has_backend;
+
+#ifdef HAVE_ASPELL
+	/* aspell */
+	AspellConfig       *spell_config;
+	AspellCanHaveError *spell_possible_err;
+        AspellSpeller      *spell_checker;
+#endif
+};
+
+
+static void spell_free (GossipSpell *spell);
+
+
+GossipSpell * 
+gossip_spell_new (const gchar *language)
+{
+	GossipSpell *spell;
+
+#ifndef HAVE_ASPELL
+	return NULL;
+#endif
+
+	spell = g_new0 (GossipSpell, 1);
+
+	spell->ref_count = 1;
+
+	if (!language) {
+		const gchar *lang = NULL;
+
+		lang = getenv ("LANG");
+		if (!lang) {
+			lang = "en_US";
+		}
+
+		spell->language = g_strdup (lang);
+	} else {
+		spell->language = g_strdup (language);
+	}
+
+#ifdef HAVE_ASPELL
+	spell->spell_config = new_aspell_config();
+
+	aspell_config_replace (spell->spell_config, "lang", spell->language);
+	
+	spell->spell_possible_err = new_aspell_speller (spell->spell_config);
+
+	if (aspell_error_number (spell->spell_possible_err) == 0) {
+		spell->spell_checker = to_aspell_speller (spell->spell_possible_err);
+		spell->has_backend = TRUE;
+	}
+#endif
+
+	return spell;
+}
+
+static void
+spell_free (GossipSpell *spell)
+{
+	g_return_if_fail (spell != NULL);
+	
+	g_free (spell->language);
+	g_free (spell);
+}	
+
+GossipSpell *
+gossip_spell_ref (GossipSpell *spell)
+{
+	g_return_val_if_fail (spell != NULL, NULL);
+
+	spell->ref_count++;
+	return spell;
+}
+
+void
+gossip_spell_unref (GossipSpell *spell)
+{
+	g_return_if_fail (spell != NULL);
+
+	spell->ref_count--;
+
+	if (spell->ref_count < 1) {
+		spell_free (spell);
+	}
+}
+
+gboolean
+gossip_spell_has_backend (GossipSpell *spell)
+{
+	g_return_val_if_fail (spell != NULL, FALSE);
+	return spell->has_backend;
+}
+
+gboolean
+gossip_spell_check (GossipSpell *spell, const gchar *word)
+{
+	gboolean correct = FALSE;
+
+	g_return_val_if_fail (spell != NULL, FALSE);
+	g_return_val_if_fail (word != NULL, FALSE);
+	g_return_val_if_fail (strlen (word) > 0, FALSE);
+
+#ifdef HAVE_ASPELL
+	correct = aspell_speller_check (spell->spell_checker, 
+					word, strlen (word));
+#endif
+
+	return correct;
+}
+
+GList *
+gossip_spell_suggestions (GossipSpell *spell, const gchar *word)
+{
+	GList *l = NULL;
+
+#ifdef HAVE_ASPELL
+	AspellWordList *suggestions = NULL;
+	AspellStringEnumeration *elements = NULL;
+
+	const char *next = NULL;
+#endif
+
+	g_return_val_if_fail (spell != NULL, NULL);
+	g_return_val_if_fail (word != NULL, NULL);
+	g_return_val_if_fail (strlen (word) > 0, NULL);
+
+#ifdef HAVE_ASPELL
+	suggestions = (AspellWordList*) aspell_speller_suggest (spell->spell_checker,
+								word, strlen (word));
+
+	elements = aspell_word_list_elements (suggestions);
+	
+	while ((next = aspell_string_enumeration_next (elements)) != NULL) {
+		l = g_list_append (l, g_strdup (next));
+	}
+	
+	delete_aspell_string_enumeration (elements);
+#endif
+
+	return l;
+}
Index: src/gossip-spell.h
===================================================================
RCS file: src/gossip-spell.h
diff -N src/gossip-spell.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/gossip-spell.h	20 Jan 2005 20:08:35 -0000
@@ -0,0 +1,45 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004 Martyn Russell (mr@gnome.org)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GOSSIP_SPELL_H__
+#define __GOSSIP_SPELL_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GossipSpell GossipSpell;
+
+GossipSpell *gossip_spell_new         (const gchar *language);
+GossipSpell *gossip_spell_ref         (GossipSpell *spell);
+void         gossip_spell_unref       (GossipSpell *spell);
+gboolean     gossip_spell_has_backend (GossipSpell *spell);
+gboolean     gossip_spell_check       (GossipSpell *spell,
+				       const gchar *word);
+GList *      gossip_spell_suggestions (GossipSpell *spell,
+				       const gchar *word);
+
+
+
+
+
+G_END_DECLS
+
+#endif /* __GOSSIP_SPELL_H__ */
Index: ui/main.glade
===================================================================
RCS file: /cvs/gnome/gossip/ui/main.glade,v
retrieving revision 1.73
diff -u -b -B -p -r1.73 main.glade
--- ui/main.glade	19 Jan 2005 23:34:31 -0000	1.73
+++ ui/main.glade	20 Jan 2005 20:08:42 -0000
@@ -3102,9 +3102,9 @@ You can change your account settings lat
 	      <child>
 		<widget class="GtkLabel" id="boldlabel_appearance">
 		  <property name="visible">True</property>
-		  <property name="label" translatable="yes">Appearance</property>
+		  <property name="label" translatable="yes">&lt;b&gt;Appearance&lt;/b&gt;</property>
 		  <property name="use_underline">False</property>
-		  <property name="use_markup">False</property>
+		  <property name="use_markup">True</property>
 		  <property name="justify">GTK_JUSTIFY_LEFT</property>
 		  <property name="wrap">False</property>
 		  <property name="selectable">False</property>
@@ -3202,9 +3202,9 @@ You can change your account settings lat
 	      <child>
 		<widget class="GtkLabel" id="boldlabel_sound">
 		  <property name="visible">True</property>
-		  <property name="label" translatable="yes">Sound</property>
+		  <property name="label" translatable="yes">&lt;b&gt;Sound&lt;/b&gt;</property>
 		  <property name="use_underline">False</property>
-		  <property name="use_markup">False</property>
+		  <property name="use_markup">True</property>
 		  <property name="justify">GTK_JUSTIFY_LEFT</property>
 		  <property name="wrap">False</property>
 		  <property name="selectable">False</property>
@@ -3330,6 +3330,106 @@ You can change your account settings lat
 	      <property name="fill">True</property>
 	    </packing>
 	  </child>
+
+	  <child>
+	    <widget class="GtkVBox" id="vbox126">
+	      <property name="visible">True</property>
+	      <property name="homogeneous">False</property>
+	      <property name="spacing">6</property>
+
+	      <child>
+		<widget class="GtkLabel" id="label496">
+		  <property name="visible">True</property>
+		  <property name="label" translatable="yes">&lt;b&gt;Spell Checking&lt;/b&gt;</property>
+		  <property name="use_underline">False</property>
+		  <property name="use_markup">True</property>
+		  <property name="justify">GTK_JUSTIFY_LEFT</property>
+		  <property name="wrap">False</property>
+		  <property name="selectable">False</property>
+		  <property name="xalign">0</property>
+		  <property name="yalign">0.5</property>
+		  <property name="xpad">0</property>
+		  <property name="ypad">0</property>
+		</widget>
+		<packing>
+		  <property name="padding">0</property>
+		  <property name="expand">False</property>
+		  <property name="fill">False</property>
+		</packing>
+	      </child>
+
+	      <child>
+		<widget class="GtkHBox" id="hbox96">
+		  <property name="visible">True</property>
+		  <property name="homogeneous">False</property>
+		  <property name="spacing">0</property>
+
+		  <child>
+		    <widget class="GtkLabel" id="label497">
+		      <property name="visible">True</property>
+		      <property name="label" translatable="yes">    </property>
+		      <property name="use_underline">False</property>
+		      <property name="use_markup">False</property>
+		      <property name="justify">GTK_JUSTIFY_LEFT</property>
+		      <property name="wrap">False</property>
+		      <property name="selectable">False</property>
+		      <property name="xalign">0.5</property>
+		      <property name="yalign">0.5</property>
+		      <property name="xpad">0</property>
+		      <property name="ypad">0</property>
+		    </widget>
+		    <packing>
+		      <property name="padding">0</property>
+		      <property name="expand">False</property>
+		      <property name="fill">False</property>
+		    </packing>
+		  </child>
+
+		  <child>
+		    <widget class="GtkVBox" id="vbox127">
+		      <property name="visible">True</property>
+		      <property name="homogeneous">False</property>
+		      <property name="spacing">6</property>
+
+		      <child>
+			<widget class="GtkCheckButton" id="spell_checker_checkbutton">
+			  <property name="visible">True</property>
+			  <property name="can_focus">True</property>
+			  <property name="label" translatable="yes">Disable</property>
+			  <property name="use_underline">True</property>
+			  <property name="relief">GTK_RELIEF_NORMAL</property>
+			  <property name="focus_on_click">True</property>
+			  <property name="active">False</property>
+			  <property name="inconsistent">False</property>
+			  <property name="draw_indicator">True</property>
+			</widget>
+			<packing>
+			  <property name="padding">0</property>
+			  <property name="expand">False</property>
+			  <property name="fill">False</property>
+			</packing>
+		      </child>
+		    </widget>
+		    <packing>
+		      <property name="padding">0</property>
+		      <property name="expand">True</property>
+		      <property name="fill">True</property>
+		    </packing>
+		  </child>
+		</widget>
+		<packing>
+		  <property name="padding">0</property>
+		  <property name="expand">True</property>
+		  <property name="fill">True</property>
+		</packing>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">True</property>
+	      <property name="fill">True</property>
+	    </packing>
+	  </child>
 	</widget>
 	<packing>
 	  <property name="padding">0</property>
@@ -5391,6 +5491,135 @@ This will take a few moments, please wai
 		  <property name="expand">True</property>
 		  <property name="fill">True</property>
 		</packing>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">True</property>
+	      <property name="fill">True</property>
+	    </packing>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">True</property>
+	  <property name="fill">True</property>
+	</packing>
+      </child>
+    </widget>
+  </child>
+</widget>
+
+<widget class="GtkDialog" id="spell_dialog">
+  <property name="border_width">4</property>
+  <property name="visible">True</property>
+  <property name="title" translatable="yes">Spell Checker</property>
+  <property name="type">GTK_WINDOW_TOPLEVEL</property>
+  <property name="window_position">GTK_WIN_POS_NONE</property>
+  <property name="modal">True</property>
+  <property name="default_width">275</property>
+  <property name="default_height">225</property>
+  <property name="resizable">True</property>
+  <property name="destroy_with_parent">False</property>
+  <property name="decorated">True</property>
+  <property name="skip_taskbar_hint">False</property>
+  <property name="skip_pager_hint">False</property>
+  <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+  <property name="has_separator">False</property>
+
+  <child internal-child="vbox">
+    <widget class="GtkVBox" id="dialog-vbox7">
+      <property name="visible">True</property>
+      <property name="homogeneous">False</property>
+      <property name="spacing">8</property>
+
+      <child internal-child="action_area">
+	<widget class="GtkHButtonBox" id="dialog-action_area7">
+	  <property name="visible">True</property>
+	  <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+	  <child>
+	    <widget class="GtkButton" id="button_cancel">
+	      <property name="visible">True</property>
+	      <property name="can_default">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">gtk-cancel</property>
+	      <property name="use_stock">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	      <property name="response_id">-6</property>
+	    </widget>
+	  </child>
+
+	  <child>
+	    <widget class="GtkButton" id="button_ok">
+	      <property name="visible">True</property>
+	      <property name="sensitive">False</property>
+	      <property name="can_default">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">gtk-ok</property>
+	      <property name="use_stock">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	      <property name="response_id">-5</property>
+	    </widget>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">True</property>
+	  <property name="pack_type">GTK_PACK_END</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkVBox" id="vbox128">
+	  <property name="border_width">5</property>
+	  <property name="visible">True</property>
+	  <property name="homogeneous">False</property>
+	  <property name="spacing">6</property>
+
+	  <child>
+	    <widget class="GtkLabel" id="label_word">
+	      <property name="visible">True</property>
+	      <property name="label" translatable="yes">Suggestions for the word:</property>
+	      <property name="use_underline">False</property>
+	      <property name="use_markup">True</property>
+	      <property name="justify">GTK_JUSTIFY_LEFT</property>
+	      <property name="wrap">True</property>
+	      <property name="selectable">False</property>
+	      <property name="xalign">0</property>
+	      <property name="yalign">0.5</property>
+	      <property name="xpad">0</property>
+	      <property name="ypad">0</property>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">False</property>
+	      <property name="fill">False</property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkScrolledWindow" id="scrolledwindow9">
+	      <property name="visible">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+	      <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+	      <property name="shadow_type">GTK_SHADOW_IN</property>
+	      <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+	      <child>
+		<widget class="GtkTreeView" id="treeview_words">
+		  <property name="visible">True</property>
+		  <property name="can_focus">True</property>
+		  <property name="headers_visible">False</property>
+		  <property name="rules_hint">False</property>
+		  <property name="reorderable">False</property>
+		  <property name="enable_search">True</property>
+		</widget>
 	      </child>
 	    </widget>
 	    <packing>
