1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* gtkhtml-editor-spell-dialog.c
3  *
4  * Copyright (C) 2008 Novell, Inc.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of version 2 of the GNU Lesser General Public
8  * License as published by the Free Software Foundation.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20 
21 #include "gtkhtml-spell-dialog.h"
22 
23 #include <glib/gi18n-lib.h>
24 #include "gtkhtml-spell-checker.h"
25 
26 enum {
27 	COMBO_COLUMN_CHECKER,		/* GTKHTML_TYPE_SPELL_CHECKER */
28 	COMBO_COLUMN_TEXT		/* G_TYPE_STRING */
29 };
30 
31 enum {
32 	PROP_0,
33 	PROP_WORD
34 };
35 
36 enum {
37 	ADDED,
38 	IGNORED,
39 	NEXT_WORD,
40 	PREV_WORD,
41 	REPLACE,
42 	REPLACE_ALL,
43 	LAST_SIGNAL
44 };
45 
46 struct _GtkhtmlSpellDialogPrivate {
47 
48 	/* widgets */
49 	GtkWidget *add_word_button;
50 	GtkWidget *back_button;
51 	GtkWidget *dictionary_combo;
52 	GtkWidget *ignore_button;
53 	GtkWidget *replace_button;
54 	GtkWidget *replace_all_button;
55 	GtkWidget *skip_button;
56 	GtkWidget *suggestion_label;
57 	GtkWidget *tree_view;
58 
59 	GList *spell_checkers;
60 	gchar *word;
61 };
62 
63 static gpointer parent_class;
64 static guint signals[LAST_SIGNAL];
65 
66 static void
spell_dialog_render_checker(GtkComboBox * combo_box,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter)67 spell_dialog_render_checker (GtkComboBox *combo_box,
68                              GtkCellRenderer *renderer,
69                              GtkTreeModel *model,
70                              GtkTreeIter *iter)
71 {
72 	const GtkhtmlSpellLanguage *language;
73 	GtkhtmlSpellChecker *checker;
74 	const gchar *name;
75 
76 	gtk_tree_model_get (model, iter, 0, &checker, -1);
77 	language = gtkhtml_spell_checker_get_language (checker);
78 	name = gtkhtml_spell_language_get_name (language);
79 
80 	g_object_set (renderer, "text", name, NULL);
81 
82 	g_object_unref (checker);
83 }
84 
85 static void
spell_dialog_update_buttons(GtkhtmlSpellDialog * dialog)86 spell_dialog_update_buttons (GtkhtmlSpellDialog *dialog)
87 {
88 	gboolean sensitive;
89 
90 	/* Update "Add Word" and "Ignore" button sensitivity. */
91 	sensitive = (gtkhtml_spell_dialog_get_word (dialog) != NULL);
92 	gtk_widget_set_sensitive (dialog->priv->add_word_button, sensitive);
93 	gtk_widget_set_sensitive (dialog->priv->ignore_button, sensitive);
94 }
95 
96 static void
spell_dialog_update_suggestion_label(GtkhtmlSpellDialog * dialog)97 spell_dialog_update_suggestion_label (GtkhtmlSpellDialog *dialog)
98 {
99 	GtkLabel *label;
100 	const gchar *word;
101 	gchar *markup;
102 	gchar *text;
103 
104 	label = GTK_LABEL (dialog->priv->suggestion_label);
105 	word = gtkhtml_spell_dialog_get_word (dialog);
106 
107 	/* Handle the simple case and get out. */
108 	if (word == NULL) {
109 		gtk_label_set_markup (label, NULL);
110 		return;
111 	}
112 
113 	text = g_strdup_printf (_("Suggestions for \"%s\""), word);
114 	markup = g_strdup_printf ("<b>%s</b>", text);
115 
116 	gtk_label_set_markup (label, markup);
117 
118 	g_free (markup);
119 	g_free (text);
120 }
121 
122 static void
spell_dialog_update_tree_view(GtkhtmlSpellDialog * dialog)123 spell_dialog_update_tree_view (GtkhtmlSpellDialog *dialog)
124 {
125 	GtkhtmlSpellChecker *checker;
126 	GtkTreeSelection *selection;
127 	GtkTreeView *tree_view;
128 	GtkListStore *store;
129 	GtkTreePath *path;
130 	const gchar *word;
131 	GList *list = NULL;
132 
133 	tree_view = GTK_TREE_VIEW (dialog->priv->tree_view);
134 	selection = gtk_tree_view_get_selection (tree_view);
135 	checker = gtkhtml_spell_dialog_get_active_checker (dialog);
136 	word = gtkhtml_spell_dialog_get_word (dialog);
137 
138 	store = gtk_list_store_new (1, G_TYPE_STRING);
139 
140 	if (checker != NULL && word != NULL)
141 		list = gtkhtml_spell_checker_get_suggestions (
142 			checker, word, -1);
143 
144 	while (list != NULL) {
145 		const gchar *suggestion = list->data;
146 		GtkTreeIter iter;
147 
148 		gtk_list_store_append (store, &iter);
149 		gtk_list_store_set (store, &iter, 0, suggestion, -1);
150 
151 		g_free (list->data);
152 		list = g_list_delete_link (list, list);
153 	}
154 
155 	gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (store));
156 
157 	/* Select the first item. */
158 	path = gtk_tree_path_new_first ();
159 	gtk_tree_selection_select_path (selection, path);
160 	gtk_tree_path_free (path);
161 
162 	g_object_unref (checker);
163 }
164 
165 static void
spell_dialog_add_word_cb(GtkhtmlSpellDialog * dialog)166 spell_dialog_add_word_cb (GtkhtmlSpellDialog *dialog)
167 {
168 	GtkhtmlSpellChecker *checker;
169 	const gchar *word;
170 
171 	checker = gtkhtml_spell_dialog_get_active_checker (dialog);
172 	word = gtkhtml_spell_dialog_get_word (dialog);
173 
174 	gtkhtml_spell_checker_add_word (checker, word, -1);
175 	g_signal_emit (dialog, signals[ADDED], 0);
176 
177 	gtkhtml_spell_dialog_next_word (dialog);
178 }
179 
180 static void
spell_dialog_ignore_cb(GtkhtmlSpellDialog * dialog)181 spell_dialog_ignore_cb (GtkhtmlSpellDialog *dialog)
182 {
183 	GtkhtmlSpellChecker *checker;
184 	const gchar *word;
185 
186 	checker = gtkhtml_spell_dialog_get_active_checker (dialog);
187 	word = gtkhtml_spell_dialog_get_word (dialog);
188 
189 	gtkhtml_spell_checker_add_word_to_session (checker, word, -1);
190 	g_signal_emit (dialog, signals[IGNORED], 0);
191 
192 	gtkhtml_spell_dialog_next_word (dialog);
193 }
194 
195 static void
spell_dialog_selection_changed_cb(GtkhtmlSpellDialog * dialog)196 spell_dialog_selection_changed_cb (GtkhtmlSpellDialog *dialog)
197 {
198 	GtkTreeSelection *selection;
199 	GtkTreeView *tree_view;
200 	gboolean selected;
201 
202 	tree_view = GTK_TREE_VIEW (dialog->priv->tree_view);
203 	selection = gtk_tree_view_get_selection (tree_view);
204 
205 	/* Update "Replace" and "Replace All" button sensitivity. */
206 	selected = gtk_tree_selection_get_selected (selection, NULL, NULL);
207 	gtk_widget_set_sensitive (dialog->priv->replace_button, selected);
208 	gtk_widget_set_sensitive (dialog->priv->replace_all_button, selected);
209 }
210 
211 static void
spell_dialog_replace_cb(GtkhtmlSpellDialog * dialog)212 spell_dialog_replace_cb (GtkhtmlSpellDialog *dialog)
213 {
214 	gchar *word;
215 
216 	word = gtkhtml_spell_dialog_get_active_suggestion (dialog);
217 	g_return_if_fail (word != NULL);
218 
219 	g_signal_emit (dialog, signals[REPLACE], 0, word);
220 
221 	g_free (word);
222 }
223 
224 static void
spell_dialog_replace_all_cb(GtkhtmlSpellDialog * dialog)225 spell_dialog_replace_all_cb (GtkhtmlSpellDialog *dialog)
226 {
227 	gchar *word;
228 
229 	word = gtkhtml_spell_dialog_get_active_suggestion (dialog);
230 	g_return_if_fail (word != NULL);
231 
232 	g_signal_emit (dialog, signals[REPLACE_ALL], 0, word);
233 
234 	g_free (word);
235 }
236 
237 static void
spell_dialog_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)238 spell_dialog_set_property (GObject *object,
239                            guint property_id,
240                            const GValue *value,
241                            GParamSpec *pspec)
242 {
243 	switch (property_id) {
244 		case PROP_WORD:
245 			gtkhtml_spell_dialog_set_word (
246 				GTKHTML_SPELL_DIALOG (object),
247 				g_value_get_string (value));
248 			return;
249 	}
250 
251 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
252 }
253 
254 static void
spell_dialog_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)255 spell_dialog_get_property (GObject *object,
256                            guint property_id,
257                            GValue *value,
258                            GParamSpec *pspec)
259 {
260 	switch (property_id) {
261 		case PROP_WORD:
262 			g_value_set_string (
263 				value, gtkhtml_spell_dialog_get_word (
264 				GTKHTML_SPELL_DIALOG (object)));
265 			return;
266 	}
267 
268 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
269 }
270 
271 static void
spell_dialog_dispose(GObject * object)272 spell_dialog_dispose (GObject *object)
273 {
274 	GtkhtmlSpellDialogPrivate *priv;
275 
276 	priv = GTKHTML_SPELL_DIALOG (object)->priv;
277 
278 	if (priv->add_word_button != NULL) {
279 		g_object_unref (priv->add_word_button);
280 		priv->add_word_button = NULL;
281 	}
282 
283 	if (priv->back_button != NULL) {
284 		g_object_unref (priv->back_button);
285 		priv->back_button = NULL;
286 	}
287 
288 	if (priv->dictionary_combo != NULL) {
289 		g_object_unref (priv->dictionary_combo);
290 		priv->dictionary_combo = NULL;
291 	}
292 
293 	if (priv->ignore_button != NULL) {
294 		g_object_unref (priv->ignore_button);
295 		priv->ignore_button = NULL;
296 	}
297 
298 	if (priv->replace_button != NULL) {
299 		g_object_unref (priv->replace_button);
300 		priv->replace_button = NULL;
301 	}
302 
303 	if (priv->replace_all_button != NULL) {
304 		g_object_unref (priv->replace_all_button);
305 		priv->replace_all_button = NULL;
306 	}
307 
308 	if (priv->skip_button != NULL) {
309 		g_object_unref (priv->skip_button);
310 		priv->skip_button = NULL;
311 	}
312 
313 	if (priv->tree_view != NULL) {
314 		g_object_unref (priv->tree_view);
315 		priv->tree_view = NULL;
316 	}
317 
318 	/* Chain up to parent's dispose() method. */
319 	G_OBJECT_CLASS (parent_class)->dispose (object);
320 }
321 
322 static void
spell_dialog_finalize(GObject * object)323 spell_dialog_finalize (GObject *object)
324 {
325 	GtkhtmlSpellDialogPrivate *priv;
326 
327 	priv = GTKHTML_SPELL_DIALOG (object)->priv;
328 
329 	g_free (priv->word);
330 
331 	/* Chain up to parent's finalize() method. */
332 	G_OBJECT_CLASS (parent_class)->finalize (object);
333 }
334 
335 static void
spell_dialog_class_init(GtkhtmlSpellDialogClass * class)336 spell_dialog_class_init (GtkhtmlSpellDialogClass *class)
337 {
338 	GObjectClass *object_class;
339 
340 	parent_class = g_type_class_peek_parent (class);
341 	g_type_class_add_private (class, sizeof (GtkhtmlSpellDialogPrivate));
342 
343 	object_class = G_OBJECT_CLASS (class);
344 	object_class->set_property = spell_dialog_set_property;
345 	object_class->get_property = spell_dialog_get_property;
346 	object_class->dispose = spell_dialog_dispose;
347 	object_class->finalize = spell_dialog_finalize;
348 
349 	g_object_class_install_property (
350 		object_class,
351 		PROP_WORD,
352 		g_param_spec_string (
353 			"word",
354 			"Misspelled Word",
355 			"The current misspelled word",
356 			NULL,
357 			G_PARAM_READWRITE));
358 
359 	signals[ADDED] = g_signal_new (
360 		"added",
361 		G_OBJECT_CLASS_TYPE (object_class),
362 		G_SIGNAL_RUN_LAST,
363 		0, NULL, NULL,
364 		g_cclosure_marshal_VOID__VOID,
365 		G_TYPE_NONE, 0);
366 
367 	signals[IGNORED] = g_signal_new (
368 		"ignored",
369 		G_OBJECT_CLASS_TYPE (object_class),
370 		G_SIGNAL_RUN_LAST,
371 		0, NULL, NULL,
372 		g_cclosure_marshal_VOID__VOID,
373 		G_TYPE_NONE, 0);
374 
375 	signals[NEXT_WORD] = g_signal_new (
376 		"next-word",
377 		G_OBJECT_CLASS_TYPE (object_class),
378 		G_SIGNAL_RUN_LAST,
379 		0, NULL, NULL,
380 		g_cclosure_marshal_VOID__VOID,
381 		G_TYPE_NONE, 0);
382 
383 	signals[PREV_WORD] = g_signal_new (
384 		"prev-word",
385 		G_OBJECT_CLASS_TYPE (object_class),
386 		G_SIGNAL_RUN_LAST,
387 		0, NULL, NULL,
388 		g_cclosure_marshal_VOID__VOID,
389 		G_TYPE_NONE, 0);
390 
391 	signals[REPLACE] = g_signal_new (
392 		"replace",
393 		G_OBJECT_CLASS_TYPE (object_class),
394 		G_SIGNAL_RUN_LAST,
395 		0, NULL, NULL,
396 		g_cclosure_marshal_VOID__STRING,
397 		G_TYPE_NONE, 1,
398 		G_TYPE_STRING);
399 
400 	signals[REPLACE_ALL] = g_signal_new (
401 		"replace-all",
402 		G_OBJECT_CLASS_TYPE (object_class),
403 		G_SIGNAL_RUN_LAST,
404 		0, NULL, NULL,
405 		g_cclosure_marshal_VOID__STRING,
406 		G_TYPE_NONE, 1,
407 		G_TYPE_STRING);
408 }
409 
410 static void
spell_dialog_init(GtkhtmlSpellDialog * dialog)411 spell_dialog_init (GtkhtmlSpellDialog *dialog)
412 {
413 	GtkTreeSelection *selection;
414 	GtkTreeViewColumn *column;
415 	GtkCellRenderer *renderer;
416 	GtkWidget *container;
417 	GtkWidget *content_area;
418 	GtkWidget *table;
419 	GtkWidget *widget;
420 	gchar *markup;
421 
422 	dialog->priv = G_TYPE_INSTANCE_GET_PRIVATE (
423 		dialog, GTKHTML_TYPE_SPELL_DIALOG, GtkhtmlSpellDialogPrivate);
424 
425 	g_signal_connect (
426 		dialog, "notify::word", G_CALLBACK (
427 		spell_dialog_update_buttons), NULL);
428 
429 	g_signal_connect (
430 		dialog, "notify::word", G_CALLBACK (
431 		spell_dialog_update_suggestion_label), NULL);
432 
433 	g_signal_connect (
434 		dialog, "notify::word", G_CALLBACK (
435 		spell_dialog_update_tree_view), NULL);
436 
437 	/* Build the widgets. */
438 
439 	content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
440 
441 	gtk_dialog_add_button (
442 		GTK_DIALOG (dialog), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
443 	gtk_window_set_title (GTK_WINDOW (dialog), _("Spell Checker"));
444 	gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
445 
446 	gtk_box_set_spacing (GTK_BOX (content_area), 2);
447 
448 	/* Table */
449 	widget = gtk_table_new (4, 2, FALSE);
450 	gtk_container_set_border_width (GTK_CONTAINER (widget), 5);
451 	gtk_table_set_row_spacings (GTK_TABLE (widget), 6);
452 	gtk_table_set_col_spacings (GTK_TABLE (widget), 6);
453 	gtk_table_set_row_spacing (GTK_TABLE (widget), 1, 12);
454 	gtk_box_pack_start (GTK_BOX (content_area), widget, TRUE, TRUE, 0);
455 	gtk_widget_show (widget);
456 	table = widget;
457 
458 	/* Suggestion Label */
459 	widget = gtk_label_new (NULL);
460 	gtk_label_set_ellipsize (GTK_LABEL (widget), PANGO_ELLIPSIZE_END);
461 	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
462 	gtk_table_attach (
463 		GTK_TABLE (table), widget, 0, 2, 0, 1,
464 		GTK_EXPAND | GTK_FILL, 0, 0, 0);
465 	dialog->priv->suggestion_label = g_object_ref (widget);
466 	gtk_widget_show (widget);
467 
468 	/* Scrolled Window */
469 	widget = gtk_scrolled_window_new (NULL, NULL);
470 	gtk_scrolled_window_set_policy (
471 		GTK_SCROLLED_WINDOW (widget),
472 		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
473 	gtk_scrolled_window_set_shadow_type (
474 		GTK_SCROLLED_WINDOW (widget),
475 		GTK_SHADOW_ETCHED_IN);
476 	gtk_table_attach (
477 		GTK_TABLE (table), widget, 0, 1, 1, 2,
478 		GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
479 	gtk_widget_show (widget);
480 	container = widget;
481 
482 	/* Tree View */
483 	widget = gtk_tree_view_new ();
484 	column = gtk_tree_view_column_new ();
485 	renderer = gtk_cell_renderer_text_new ();
486 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
487 	g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
488 	gtk_tree_view_column_pack_start (column, renderer, TRUE);
489 	gtk_tree_view_column_add_attribute (column, renderer, "text", 0);
490 	gtk_tree_view_append_column (GTK_TREE_VIEW (widget), column);
491 	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (widget), FALSE);
492 	gtk_label_set_mnemonic_widget (
493 		GTK_LABEL (dialog->priv->suggestion_label), widget);
494 	g_signal_connect_swapped (
495 		widget, "row-activated",
496 		G_CALLBACK (spell_dialog_replace_cb), dialog);
497 	g_signal_connect_swapped (
498 		selection, "changed",
499 		G_CALLBACK (spell_dialog_selection_changed_cb), dialog);
500 	gtk_container_add (GTK_CONTAINER (container), widget);
501 	dialog->priv->tree_view = g_object_ref (widget);
502 	gtk_widget_show (widget);
503 
504 	/* Vertical Button Box */
505 	widget = gtk_vbutton_box_new ();
506 	gtk_button_box_set_layout (
507 		GTK_BUTTON_BOX (widget), GTK_BUTTONBOX_START);
508 	gtk_box_set_spacing (GTK_BOX (widget), 6);
509 	gtk_table_attach (
510 		GTK_TABLE (table), widget, 1, 2, 1, 2,
511 		0, GTK_EXPAND | GTK_FILL, 0, 0);
512 	gtk_widget_show (widget);
513 	container = widget;
514 
515 	/* Replace Button */
516 	widget = gtk_button_new_with_mnemonic (_("_Replace"));
517 	gtk_button_set_image (
518 		GTK_BUTTON (widget),
519 		gtk_image_new_from_stock (
520 		GTK_STOCK_CONVERT, GTK_ICON_SIZE_BUTTON));
521 	g_signal_connect_swapped (
522 		widget, "clicked",
523 		G_CALLBACK (spell_dialog_replace_cb), dialog);
524 	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
525 	dialog->priv->replace_button = g_object_ref (widget);
526 	gtk_widget_set_sensitive (widget, FALSE);
527 	gtk_widget_show (widget);
528 
529 	/* Replace All Button */
530 	widget = gtk_button_new_with_mnemonic (_("R_eplace All"));
531 	gtk_button_set_image (
532 		GTK_BUTTON (widget),
533 		gtk_image_new_from_stock (
534 		GTK_STOCK_APPLY, GTK_ICON_SIZE_BUTTON));
535 	g_signal_connect_swapped (
536 		widget, "clicked",
537 		G_CALLBACK (spell_dialog_replace_all_cb), dialog);
538 	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
539 	dialog->priv->replace_all_button = g_object_ref (widget);
540 	gtk_widget_set_sensitive (widget, FALSE);
541 	gtk_widget_show (widget);
542 
543 	/* Ignore Button */
544 	widget = gtk_button_new_with_mnemonic (_("_Ignore"));
545 	gtk_button_set_image (
546 		GTK_BUTTON (widget),
547 		gtk_image_new_from_stock (
548 		GTK_STOCK_CLEAR, GTK_ICON_SIZE_BUTTON));
549 	g_signal_connect_swapped (
550 		widget, "clicked",
551 		G_CALLBACK (spell_dialog_ignore_cb), dialog);
552 	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
553 	dialog->priv->ignore_button = g_object_ref (widget);
554 	gtk_widget_set_sensitive (widget, FALSE);
555 	gtk_widget_show (widget);
556 
557 	/* Skip Button */
558 	widget = gtk_button_new_with_mnemonic (_("_Skip"));
559 	gtk_button_set_image (
560 		GTK_BUTTON (widget),
561 		gtk_image_new_from_stock (
562 		GTK_STOCK_GO_FORWARD, GTK_ICON_SIZE_BUTTON));
563 	g_signal_connect_swapped (
564 		widget, "clicked",
565 		G_CALLBACK (gtkhtml_spell_dialog_next_word), dialog);
566 	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
567 	dialog->priv->skip_button = g_object_ref (widget);
568 	gtk_widget_show (widget);
569 
570 	/* Back Button */
571 	widget = gtk_button_new_with_mnemonic (_("_Back"));
572 	gtk_button_set_image (
573 		GTK_BUTTON (widget),
574 		gtk_image_new_from_stock (
575 		GTK_STOCK_GO_BACK, GTK_ICON_SIZE_BUTTON));
576 	g_signal_connect_swapped (
577 		widget, "clicked",
578 		G_CALLBACK (gtkhtml_spell_dialog_prev_word), dialog);
579 	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
580 	dialog->priv->back_button = g_object_ref (widget);
581 	gtk_widget_show (widget);
582 
583 	/* Dictionary Label */
584 	markup = g_markup_printf_escaped ("<b>%s</b>", _("Dictionary"));
585 	widget = gtk_label_new (markup);
586 	gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
587 	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
588 	gtk_table_attach (
589 		GTK_TABLE (table), widget, 0, 2, 2, 3,
590 		GTK_EXPAND | GTK_FILL, 0, 0, 0);
591 	gtk_widget_show (widget);
592 	g_free (markup);
593 
594 	/* Dictionary Combo Box */
595 	widget = gtk_combo_box_new ();
596 	renderer = gtk_cell_renderer_text_new ();
597 	gtk_cell_layout_pack_start (
598 		GTK_CELL_LAYOUT (widget), renderer, TRUE);
599 	gtk_cell_layout_set_cell_data_func (
600 		GTK_CELL_LAYOUT (widget), renderer,
601 		(GtkCellLayoutDataFunc) spell_dialog_render_checker,
602 		NULL, NULL);
603 	g_signal_connect_swapped (
604 		widget, "changed",
605 		G_CALLBACK (spell_dialog_update_tree_view), dialog);
606 	gtk_table_attach (
607 		GTK_TABLE (table), widget, 0, 1, 3, 4,
608 		GTK_EXPAND | GTK_FILL, 0, 0, 0);
609 	dialog->priv->dictionary_combo = g_object_ref (widget);
610 	gtk_widget_show (widget);
611 
612 	/* Add Word Button */
613 	widget = gtk_button_new_with_mnemonic (_("_Add Word"));
614 	g_signal_connect_swapped (
615 		widget, "clicked",
616 		G_CALLBACK (spell_dialog_add_word_cb), dialog);
617 	gtk_button_set_image (
618 		GTK_BUTTON (widget),
619 		gtk_image_new_from_stock (
620 		GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON));
621 	gtk_table_attach (
622 		GTK_TABLE (table), widget, 1, 2, 3, 4,
623 		GTK_FILL, 0, 0, 0);
624 	dialog->priv->add_word_button = g_object_ref (widget);
625 	gtk_widget_show (widget);
626 }
627 
628 GType
gtkhtml_spell_dialog_get_type(void)629 gtkhtml_spell_dialog_get_type (void)
630 {
631 	static GType type = 0;
632 
633 	if (G_UNLIKELY (type == 0)) {
634 		static const GTypeInfo type_info = {
635 			sizeof (GtkhtmlSpellDialogClass),
636 			(GBaseInitFunc) NULL,
637 			(GBaseFinalizeFunc) NULL,
638 			(GClassInitFunc) spell_dialog_class_init,
639 			(GClassFinalizeFunc) NULL,
640 			NULL,  /* class_data */
641 			sizeof (GtkhtmlSpellDialog),
642 			0,     /* n_preallocs */
643 			(GInstanceInitFunc) spell_dialog_init,
644 			NULL   /* value_table */
645 		};
646 
647 		type = g_type_register_static (
648 			GTK_TYPE_DIALOG, "GtkhtmlSpellDialog", &type_info, 0);
649 	}
650 
651 	return type;
652 }
653 
654 GtkWidget *
gtkhtml_spell_dialog_new(GtkWindow * parent)655 gtkhtml_spell_dialog_new (GtkWindow *parent)
656 {
657 	return g_object_new (
658 		GTKHTML_TYPE_SPELL_DIALOG, "transient-for", parent, NULL);
659 }
660 
661 void
gtkhtml_spell_dialog_close(GtkhtmlSpellDialog * dialog)662 gtkhtml_spell_dialog_close (GtkhtmlSpellDialog *dialog)
663 {
664 	g_return_if_fail (GTKHTML_IS_SPELL_DIALOG (dialog));
665 
666 	gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_CLOSE);
667 }
668 
669 const gchar *
gtkhtml_spell_dialog_get_word(GtkhtmlSpellDialog * dialog)670 gtkhtml_spell_dialog_get_word (GtkhtmlSpellDialog *dialog)
671 {
672 	g_return_val_if_fail (GTKHTML_IS_SPELL_DIALOG (dialog), NULL);
673 
674 	return dialog->priv->word;
675 }
676 
677 void
gtkhtml_spell_dialog_set_word(GtkhtmlSpellDialog * dialog,const gchar * word)678 gtkhtml_spell_dialog_set_word (GtkhtmlSpellDialog *dialog,
679                                const gchar *word)
680 {
681 	g_return_if_fail (GTKHTML_IS_SPELL_DIALOG (dialog));
682 
683 	/* Do not emit signals if the word is unchanged. */
684 	if (word != NULL && dialog->priv->word != NULL)
685 		if (g_str_equal (word, dialog->priv->word))
686 			return;
687 
688 	g_free (dialog->priv->word);
689 	dialog->priv->word = g_strdup (word);
690 
691 	g_object_notify (G_OBJECT (dialog), "word");
692 }
693 
694 void
gtkhtml_spell_dialog_next_word(GtkhtmlSpellDialog * dialog)695 gtkhtml_spell_dialog_next_word (GtkhtmlSpellDialog *dialog)
696 {
697 	g_signal_emit (dialog, signals[NEXT_WORD], 0);
698 }
699 
700 void
gtkhtml_spell_dialog_prev_word(GtkhtmlSpellDialog * dialog)701 gtkhtml_spell_dialog_prev_word (GtkhtmlSpellDialog *dialog)
702 {
703 	g_signal_emit (dialog, signals[PREV_WORD], 0);
704 }
705 
706 GList *
gtkhtml_spell_dialog_get_spell_checkers(GtkhtmlSpellDialog * dialog)707 gtkhtml_spell_dialog_get_spell_checkers (GtkhtmlSpellDialog *dialog)
708 {
709 	g_return_val_if_fail (GTKHTML_IS_SPELL_DIALOG (dialog), NULL);
710 
711 	return g_list_copy (dialog->priv->spell_checkers);
712 }
713 
714 void
gtkhtml_spell_dialog_set_spell_checkers(GtkhtmlSpellDialog * dialog,GList * spell_checkers)715 gtkhtml_spell_dialog_set_spell_checkers (GtkhtmlSpellDialog *dialog,
716                                          GList *spell_checkers)
717 {
718 	GtkComboBox *combo_box;
719 	GtkListStore *store;
720 	GList *list;
721 
722 	g_return_if_fail (GTKHTML_IS_SPELL_DIALOG (dialog));
723 
724 	combo_box = GTK_COMBO_BOX (dialog->priv->dictionary_combo);
725 
726 	/* Free the old list of spell checkers. */
727 	list = dialog->priv->spell_checkers;
728 	g_list_foreach (list, (GFunc) g_object_unref, NULL);
729 	g_list_free (list);
730 
731 	/* Copy and sort the new list of spell checkers. */
732 	list = g_list_sort (
733 		g_list_copy (spell_checkers),
734 		(GCompareFunc) gtkhtml_spell_checker_compare);
735 	g_list_foreach (list, (GFunc) g_object_ref, NULL);
736 	dialog->priv->spell_checkers = list;
737 
738 	/* Populate a list store for the combo box. */
739 
740 	store = gtk_list_store_new (1, GTKHTML_TYPE_SPELL_CHECKER);
741 
742 	while (list != NULL) {
743 		GtkhtmlSpellChecker *checker = list->data;
744 		GtkTreeIter iter;
745 
746 		gtk_list_store_append (store, &iter);
747 		gtk_list_store_set (store, &iter, 0, checker, -1);
748 
749 		list = g_list_next (list);
750 	}
751 
752 	/* FIXME Try to preserve the previously selected language. */
753 	gtk_combo_box_set_model (combo_box, GTK_TREE_MODEL (store));
754 	gtk_combo_box_set_active (combo_box, 0);
755 
756 	g_object_unref (store);
757 
758 	/* XXX notify property? */
759 }
760 
761 GtkhtmlSpellChecker *
gtkhtml_spell_dialog_get_active_checker(GtkhtmlSpellDialog * dialog)762 gtkhtml_spell_dialog_get_active_checker (GtkhtmlSpellDialog *dialog)
763 {
764 	GtkhtmlSpellChecker *checker;
765 	GtkComboBox *combo_box;
766 	GtkTreeModel *model;
767 	GtkTreeIter iter;
768 
769 	g_return_val_if_fail (GTKHTML_IS_SPELL_DIALOG (dialog), NULL);
770 
771 	combo_box = GTK_COMBO_BOX (dialog->priv->dictionary_combo);
772 	model = gtk_combo_box_get_model (combo_box);
773 
774 	if (!gtk_combo_box_get_active_iter (combo_box, &iter))
775 		return NULL;
776 
777 	gtk_tree_model_get (model, &iter, COMBO_COLUMN_CHECKER, &checker, -1);
778 
779 	return checker;
780 }
781 
782 gchar *
gtkhtml_spell_dialog_get_active_suggestion(GtkhtmlSpellDialog * dialog)783 gtkhtml_spell_dialog_get_active_suggestion (GtkhtmlSpellDialog *dialog)
784 {
785 	GtkTreeSelection *selection;
786 	GtkTreeView *tree_view;
787 	GtkTreeModel *model;
788 	GtkTreeIter iter;
789 	gchar *word;
790 
791 	g_return_val_if_fail (GTKHTML_IS_SPELL_DIALOG (dialog), NULL);
792 
793 	tree_view = GTK_TREE_VIEW (dialog->priv->tree_view);
794 	selection = gtk_tree_view_get_selection (tree_view);
795 
796 	/* If nothing is selected, return NULL. */
797 	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
798 		return NULL;
799 
800 	gtk_tree_model_get (model, &iter, 0, &word, -1);
801 	g_return_val_if_fail (word != NULL, NULL);
802 
803 	return word;
804 }
805