1 /* gdict-pref-dialog.c - preferences dialog
2  *
3  * This file is part of GNOME Dictionary
4  *
5  * Copyright (C) 2005 Emmanuele Bassi
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation; either version 2 of
10  * the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <time.h>
27 #include <sys/types.h>
28 #ifdef HAVE_UNISTD_H
29 #include <unistd.h>
30 #endif
31 
32 #include <glib/gi18n.h>
33 
34 #include <gio/gio.h>
35 
36 #include "gdict-source-dialog.h"
37 #include "gdict-pref-dialog.h"
38 #include "gdict-common.h"
39 
40 #define GDICT_PREFERENCES_UI 	"/org/gnome/Dictionary/gdict-pref-dialog.ui"
41 
42 /*******************
43  * GdictPrefDialog *
44  *******************/
45 
46 static GtkWidget *global_dialog = NULL;
47 
48 enum
49 {
50   SOURCES_ACTIVE_COLUMN = 0,
51   SOURCES_NAME_COLUMN,
52   SOURCES_DESCRIPTION_COLUMN,
53 
54   SOURCES_N_COLUMNS
55 };
56 
57 struct _GdictPrefDialog
58 {
59   GtkDialog parent_instance;
60 
61   GtkBuilder *builder;
62 
63   GSettings *settings;
64 
65   gchar *print_font;
66   gchar *active_source;
67   GdictSourceLoader *loader;
68   GtkListStore *sources_list;
69 
70   /* direct pointers to widgets */
71   GtkWidget *preferences_root;
72   GtkWidget *preferences_notebook;
73   GtkWidget *sources_treeview;
74   GtkWidget *add_button;
75   GtkWidget *remove_button;
76   GtkWidget *edit_button;
77   GtkWidget *font_button;
78 };
79 
80 struct _GdictPrefDialogClass
81 {
82   GtkDialogClass parent_class;
83 };
84 
85 enum
86 {
87   PROP_0,
88 
89   PROP_SOURCE_LOADER
90 };
91 
G_DEFINE_TYPE(GdictPrefDialog,gdict_pref_dialog,GTK_TYPE_DIALOG)92 G_DEFINE_TYPE (GdictPrefDialog, gdict_pref_dialog, GTK_TYPE_DIALOG)
93 
94 static gboolean
95 select_active_source_name (GtkTreeModel *model,
96 			   GtkTreePath  *path,
97 			   GtkTreeIter  *iter,
98 			   gpointer      data)
99 {
100   GdictPrefDialog *dialog = GDICT_PREF_DIALOG (data);
101   gboolean is_active;
102 
103   gtk_tree_model_get (model, iter,
104       		      SOURCES_ACTIVE_COLUMN, &is_active,
105       		      -1);
106   if (is_active)
107     {
108       GtkTreeSelection *selection;
109 
110       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->sources_treeview));
111 
112       gtk_tree_selection_select_iter (selection, iter);
113 
114       return TRUE;
115     }
116 
117   return FALSE;
118 }
119 
120 static void
sources_view_cursor_changed_cb(GtkTreeView * tree_view,GdictPrefDialog * dialog)121 sources_view_cursor_changed_cb (GtkTreeView       *tree_view,
122 				GdictPrefDialog   *dialog)
123 {
124   GtkTreeSelection *selection;
125   GtkTreeModel *model;
126   GtkTreeIter iter;
127   GdictSource *source;
128   gboolean is_selected;
129   gchar *name;
130 
131   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->sources_treeview));
132   if (!selection)
133     return;
134 
135   is_selected = gtk_tree_selection_get_selected (selection, &model, &iter);
136   if (!is_selected)
137     return;
138 
139   gtk_tree_model_get (model, &iter, SOURCES_NAME_COLUMN, &name, -1);
140   if (!name)
141     return;
142   else
143     {
144       source = gdict_source_loader_get_source (dialog->loader, name);
145       gtk_widget_set_sensitive (dialog->edit_button, gdict_source_is_editable (source));
146       gtk_widget_set_sensitive (dialog->remove_button, gdict_source_is_editable (source));
147       g_object_unref (source);
148     }
149 }
150 
151 static void
update_sources_view(GdictPrefDialog * dialog)152 update_sources_view (GdictPrefDialog *dialog)
153 {
154   const GSList *sources, *l;
155 
156   gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->sources_treeview), NULL);
157 
158   gtk_list_store_clear (dialog->sources_list);
159 
160   /* force update of the sources list */
161   gdict_source_loader_update (dialog->loader);
162 
163   sources = gdict_source_loader_get_sources (dialog->loader);
164   for (l = sources; l != NULL; l = l->next)
165     {
166       GdictSource *source = GDICT_SOURCE (l->data);
167       GtkTreeIter iter;
168       const gchar *name, *description;
169       gboolean is_active = FALSE;
170 
171       name = gdict_source_get_name (source);
172       description = gdict_source_get_description (source);
173       if (!description)
174 	description = name;
175 
176       if (strcmp (name, dialog->active_source) == 0)
177         is_active = TRUE;
178 
179       gtk_list_store_append (dialog->sources_list, &iter);
180       gtk_list_store_set (dialog->sources_list, &iter,
181       			  SOURCES_ACTIVE_COLUMN, is_active,
182       			  SOURCES_NAME_COLUMN, name,
183       			  SOURCES_DESCRIPTION_COLUMN, description,
184       			  -1);
185     }
186 
187   gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->sources_treeview),
188   			   GTK_TREE_MODEL (dialog->sources_list));
189 
190   /* select the currently active source name */
191   gtk_tree_model_foreach (GTK_TREE_MODEL (dialog->sources_list),
192   			  select_active_source_name,
193   			  dialog);
194   sources_view_cursor_changed_cb (GTK_TREE_VIEW(dialog->sources_treeview), dialog);
195 }
196 
197 static void
source_renderer_toggled_cb(GtkCellRendererToggle * renderer,const gchar * path,GdictPrefDialog * dialog)198 source_renderer_toggled_cb (GtkCellRendererToggle *renderer,
199 			    const gchar           *path,
200 			    GdictPrefDialog       *dialog)
201 {
202   GtkTreePath *treepath;
203   GtkTreeIter iter;
204   gboolean res;
205   gboolean is_active;
206   gchar *name;
207 
208   treepath = gtk_tree_path_new_from_string (path);
209   res = gtk_tree_model_get_iter (GTK_TREE_MODEL (dialog->sources_list),
210                                  &iter,
211                                  treepath);
212   if (!res)
213     {
214       gtk_tree_path_free (treepath);
215 
216       return;
217     }
218 
219   gtk_tree_model_get (GTK_TREE_MODEL (dialog->sources_list), &iter,
220       		      SOURCES_NAME_COLUMN, &name,
221       		      SOURCES_ACTIVE_COLUMN, &is_active,
222       		      -1);
223   if (!is_active && name != NULL)
224     {
225       g_free (dialog->active_source);
226       dialog->active_source = g_strdup (name);
227 
228       g_settings_set_string (dialog->settings, GDICT_SETTINGS_SOURCE_KEY, dialog->active_source);
229       update_sources_view (dialog);
230 
231       g_free (name);
232     }
233 
234   gtk_tree_path_free (treepath);
235 }
236 
237 static void
sources_view_row_activated_cb(GtkTreeView * tree_view,GtkTreePath * tree_path,GtkTreeViewColumn * tree_iter,GdictPrefDialog * dialog)238 sources_view_row_activated_cb (GtkTreeView       *tree_view,
239 			       GtkTreePath       *tree_path,
240 			       GtkTreeViewColumn *tree_iter,
241 			       GdictPrefDialog   *dialog)
242 {
243   GtkWidget *edit_dialog;
244   gchar *source_name;
245   GtkTreeModel *model;
246   GtkTreeIter iter;
247 
248   model = gtk_tree_view_get_model (tree_view);
249   if (!model)
250     return;
251 
252   if (!gtk_tree_model_get_iter (model, &iter, tree_path))
253     return;
254 
255   gtk_tree_model_get (model, &iter, SOURCES_NAME_COLUMN, &source_name, -1);
256   if (!source_name)
257     return;
258 
259   edit_dialog = gdict_source_dialog_new (GTK_WINDOW (dialog),
260 					 _("View Dictionary Source"),
261 					 GDICT_SOURCE_DIALOG_VIEW,
262 					 dialog->loader,
263 					 source_name);
264   gtk_dialog_run (GTK_DIALOG (edit_dialog));
265 
266   gtk_widget_destroy (edit_dialog);
267   g_free (source_name);
268 
269   update_sources_view (dialog);
270 }
271 
272 static void
build_sources_view(GdictPrefDialog * dialog)273 build_sources_view (GdictPrefDialog *dialog)
274 {
275   GtkTreeViewColumn *column;
276   GtkCellRenderer *renderer;
277 
278   if (dialog->sources_list)
279     return;
280 
281   dialog->sources_list = gtk_list_store_new (SOURCES_N_COLUMNS,
282   					     G_TYPE_BOOLEAN,  /* active */
283   					     G_TYPE_STRING,   /* name */
284   					     G_TYPE_STRING    /* description */);
285   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (dialog->sources_list),
286   					SOURCES_DESCRIPTION_COLUMN,
287   					GTK_SORT_ASCENDING);
288 
289   renderer = gtk_cell_renderer_toggle_new ();
290   gtk_cell_renderer_toggle_set_radio (GTK_CELL_RENDERER_TOGGLE (renderer), TRUE);
291   g_signal_connect (renderer, "toggled",
292   		    G_CALLBACK (source_renderer_toggled_cb),
293   		    dialog);
294 
295   column = gtk_tree_view_column_new_with_attributes ("active",
296   						     renderer,
297   						     "active", SOURCES_ACTIVE_COLUMN,
298   						     NULL);
299   gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->sources_treeview), column);
300 
301   renderer = gtk_cell_renderer_text_new ();
302   column = gtk_tree_view_column_new_with_attributes ("description",
303   						     renderer,
304   						     "text", SOURCES_DESCRIPTION_COLUMN,
305   						     NULL);
306   gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->sources_treeview), column);
307 
308   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (dialog->sources_treeview), FALSE);
309   gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->sources_treeview),
310   			   GTK_TREE_MODEL (dialog->sources_list));
311 
312   g_signal_connect (dialog->sources_treeview, "row-activated",
313 		    G_CALLBACK (sources_view_row_activated_cb),
314 		    dialog);
315   g_signal_connect (dialog->sources_treeview, "cursor-changed",
316 		    G_CALLBACK (sources_view_cursor_changed_cb),
317 		    dialog);
318 }
319 
320 static void
source_add_clicked_cb(GdictPrefDialog * dialog)321 source_add_clicked_cb (GdictPrefDialog *dialog)
322 {
323   GtkWidget *add_dialog;
324 
325   add_dialog = gdict_source_dialog_new (GTK_WINDOW (dialog),
326   					_("Add Dictionary Source"),
327   					GDICT_SOURCE_DIALOG_CREATE,
328   					dialog->loader,
329   					NULL);
330 
331   gtk_dialog_run (GTK_DIALOG (add_dialog));
332 
333   gtk_widget_destroy (add_dialog);
334 
335   update_sources_view (dialog);
336 }
337 
338 static void
source_remove_clicked_cb(GdictPrefDialog * dialog)339 source_remove_clicked_cb (GdictPrefDialog *dialog)
340 {
341   GtkTreeSelection *selection;
342   GtkTreeModel *model;
343   GtkTreeIter iter;
344   gboolean is_selected;
345   gchar *name, *description;
346 
347   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->sources_treeview));
348   if (!selection)
349     return;
350 
351   is_selected = gtk_tree_selection_get_selected (selection, &model, &iter);
352   if (!is_selected)
353     return;
354 
355   gtk_tree_model_get (model, &iter,
356   		      SOURCES_NAME_COLUMN, &name,
357   		      SOURCES_DESCRIPTION_COLUMN, &description,
358   		      -1);
359   if (!name)
360     return;
361   else
362     {
363       GtkWidget *confirm_dialog;
364       gint response;
365 
366       confirm_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog),
367       					       GTK_DIALOG_DESTROY_WITH_PARENT,
368       					       GTK_MESSAGE_WARNING,
369       					       GTK_BUTTONS_NONE,
370       					       _("Remove “%s”?"), description);
371       gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (confirm_dialog),
372       						_("This will permanently remove the "
373       						  "dictionary source from the list."));
374 
375       gtk_dialog_add_button (GTK_DIALOG (confirm_dialog), _("_Cancel"), GTK_RESPONSE_CANCEL);
376       gtk_dialog_add_button (GTK_DIALOG (confirm_dialog), _("_Remove"), GTK_RESPONSE_OK);
377 
378       gtk_window_set_title (GTK_WINDOW (confirm_dialog), "");
379 
380       response = gtk_dialog_run (GTK_DIALOG (confirm_dialog));
381       gtk_widget_destroy (confirm_dialog);
382 
383       if (response == GTK_RESPONSE_CANCEL)
384         goto out;
385     }
386 
387   if (gdict_source_loader_remove_source (dialog->loader, name))
388     gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
389   else
390     {
391       GtkWidget *error_dialog;
392       gchar *message;
393 
394       message = g_strdup_printf (_("Unable to remove source “%s”"),
395       				 description);
396 
397       error_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog),
398       					     GTK_DIALOG_DESTROY_WITH_PARENT,
399       					     GTK_MESSAGE_ERROR,
400       					     GTK_BUTTONS_OK,
401       					     "%s", message);
402       gtk_window_set_title (GTK_WINDOW (error_dialog), "");
403 
404       gtk_dialog_run (GTK_DIALOG (error_dialog));
405 
406       gtk_widget_destroy (error_dialog);
407     }
408 
409 out:
410   g_free (name);
411   g_free (description);
412 
413   update_sources_view (dialog);
414 }
415 
416 static void
source_edit_clicked_cb(GdictPrefDialog * dialog)417 source_edit_clicked_cb (GdictPrefDialog *dialog)
418 {
419   GtkTreeSelection *selection;
420   GtkTreeModel *model;
421   GtkTreeIter iter;
422   gboolean is_selected;
423   gchar *name;
424 
425   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->sources_treeview));
426   if (!selection)
427     return;
428 
429   is_selected = gtk_tree_selection_get_selected (selection, &model, &iter);
430   if (!is_selected)
431     return;
432 
433   gtk_tree_model_get (model, &iter, SOURCES_NAME_COLUMN, &name, -1);
434   if (!name)
435     return;
436   else
437     {
438       GtkWidget *edit_dialog;
439 
440       edit_dialog = gdict_source_dialog_new (GTK_WINDOW (dialog),
441                                              _("Edit Dictionary Source"),
442                                              GDICT_SOURCE_DIALOG_EDIT,
443                                              dialog->loader,
444                                              name);
445       gtk_dialog_run (GTK_DIALOG (edit_dialog));
446 
447       gtk_widget_destroy (edit_dialog);
448     }
449 
450   g_free (name);
451 
452   update_sources_view (dialog);
453 }
454 
455 static void
set_source_loader(GdictPrefDialog * dialog,GdictSourceLoader * loader)456 set_source_loader (GdictPrefDialog   *dialog,
457 		   GdictSourceLoader *loader)
458 {
459   if (!dialog->sources_list)
460     return;
461 
462   if (dialog->loader)
463     g_object_unref (dialog->loader);
464 
465   dialog->loader = g_object_ref (loader);
466 
467   update_sources_view (dialog);
468 }
469 
470 static void
font_button_font_set_cb(GdictPrefDialog * dialog,GtkFontButton * font_button)471 font_button_font_set_cb (GdictPrefDialog *dialog,
472                          GtkFontButton   *font_button)
473 {
474   const char *font;
475 
476   font = gtk_font_chooser_get_font (GTK_FONT_CHOOSER (font_button));
477   if (!font || font[0] == '\0')
478     return;
479 
480   if (g_strcmp0 (dialog->print_font, font) == 0)
481     return;
482 
483   g_free (dialog->print_font);
484   dialog->print_font = g_strdup (font);
485 
486   g_settings_set_string (dialog->settings, GDICT_SETTINGS_PRINT_FONT_KEY, dialog->print_font);
487 }
488 
489 static void
gdict_pref_dialog_finalize(GObject * object)490 gdict_pref_dialog_finalize (GObject *object)
491 {
492   GdictPrefDialog *dialog = GDICT_PREF_DIALOG (object);
493 
494   g_clear_object (&dialog->settings);
495   g_clear_object (&dialog->loader);
496 
497   g_free (dialog->active_source);
498 
499   G_OBJECT_CLASS (gdict_pref_dialog_parent_class)->finalize (object);
500 }
501 
502 static void
gdict_pref_dialog_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)503 gdict_pref_dialog_set_property (GObject      *object,
504 				guint         prop_id,
505 				const GValue *value,
506 				GParamSpec   *pspec)
507 {
508   GdictPrefDialog *dialog = GDICT_PREF_DIALOG (object);
509 
510   switch (prop_id)
511     {
512     case PROP_SOURCE_LOADER:
513       set_source_loader (dialog, g_value_get_object (value));
514       break;
515 
516     default:
517       break;
518     }
519 }
520 
521 static void
gdict_pref_dialog_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)522 gdict_pref_dialog_get_property (GObject    *object,
523 				guint       prop_id,
524 				GValue     *value,
525 				GParamSpec *pspec)
526 {
527   GdictPrefDialog *dialog = GDICT_PREF_DIALOG (object);
528 
529   switch (prop_id)
530     {
531     case PROP_SOURCE_LOADER:
532       g_value_set_object (value, dialog->loader);
533       break;
534 
535     default:
536       break;
537     }
538 }
539 
540 static void
gdict_pref_dialog_class_init(GdictPrefDialogClass * klass)541 gdict_pref_dialog_class_init (GdictPrefDialogClass *klass)
542 {
543   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
544   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
545 
546   gobject_class->set_property = gdict_pref_dialog_set_property;
547   gobject_class->get_property = gdict_pref_dialog_get_property;
548   gobject_class->finalize = gdict_pref_dialog_finalize;
549 
550   g_object_class_install_property (gobject_class,
551   				   PROP_SOURCE_LOADER,
552   				   g_param_spec_object ("source-loader",
553   				   			"Source Loader",
554   				   			"The GdictSourceLoader used by the application",
555   				   			GDICT_TYPE_SOURCE_LOADER,
556                                                         G_PARAM_READWRITE |
557                                                         G_PARAM_CONSTRUCT_ONLY |
558                                                         G_PARAM_STATIC_STRINGS));
559 
560   gtk_widget_class_set_template_from_resource (widget_class, GDICT_PREFERENCES_UI);
561 
562   gtk_widget_class_bind_template_child (widget_class, GdictPrefDialog, preferences_notebook);
563   gtk_widget_class_bind_template_child (widget_class, GdictPrefDialog, sources_treeview);
564   gtk_widget_class_bind_template_child (widget_class, GdictPrefDialog, add_button);
565   gtk_widget_class_bind_template_child (widget_class, GdictPrefDialog, remove_button);
566   gtk_widget_class_bind_template_child (widget_class, GdictPrefDialog, edit_button);
567   gtk_widget_class_bind_template_child (widget_class, GdictPrefDialog, font_button);
568 
569   gtk_widget_class_bind_template_callback (widget_class, source_add_clicked_cb);
570   gtk_widget_class_bind_template_callback (widget_class, source_remove_clicked_cb);
571   gtk_widget_class_bind_template_callback (widget_class, source_edit_clicked_cb);
572   gtk_widget_class_bind_template_callback (widget_class, font_button_font_set_cb);
573 }
574 
575 static void
gdict_pref_dialog_init(GdictPrefDialog * dialog)576 gdict_pref_dialog_init (GdictPrefDialog *dialog)
577 {
578   gchar *font;
579 
580   gtk_widget_init_template (GTK_WIDGET (dialog));
581 
582   dialog->settings = g_settings_new (GDICT_SETTINGS_SCHEMA);
583   dialog->active_source = g_settings_get_string (dialog->settings, GDICT_SETTINGS_SOURCE_KEY);
584 
585   build_sources_view (dialog);
586 
587   font = g_settings_get_string (dialog->settings, GDICT_SETTINGS_PRINT_FONT_KEY);
588   gtk_font_chooser_set_font (GTK_FONT_CHOOSER (dialog->font_button), font);
589   g_free (font);
590 
591   gtk_widget_show_all (dialog->preferences_notebook);
592 }
593 
594 void
gdict_show_pref_dialog(GtkWidget * parent,const gchar * title,GdictSourceLoader * loader)595 gdict_show_pref_dialog (GtkWidget         *parent,
596 			const gchar       *title,
597 			GdictSourceLoader *loader)
598 {
599   GtkWidget *dialog;
600 
601   g_return_if_fail (GTK_IS_WIDGET (parent));
602   g_return_if_fail (GDICT_IS_SOURCE_LOADER (loader));
603 
604   if (parent != NULL)
605     dialog = g_object_get_data (G_OBJECT (parent), "gdict-pref-dialog");
606   else
607     dialog = global_dialog;
608 
609   if (dialog == NULL)
610     {
611       dialog = g_object_new (GDICT_TYPE_PREF_DIALOG,
612                              "source-loader", loader,
613                              "title", title,
614                              "use-header-bar", 1,
615                              NULL);
616 
617       g_object_ref_sink (dialog);
618 
619       g_signal_connect (dialog, "delete-event",
620                         G_CALLBACK (gtk_widget_hide_on_delete),
621                         NULL);
622 
623       if (parent != NULL && GTK_IS_WINDOW (parent))
624         {
625           gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent));
626           gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
627           gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
628           g_object_set_data_full (G_OBJECT (parent), "gdict-pref-dialog",
629                                   dialog,
630                                   g_object_unref);
631         }
632       else
633         global_dialog = dialog;
634     }
635 
636   gtk_window_set_screen (GTK_WINDOW (dialog), gtk_widget_get_screen (parent));
637   gtk_window_present (GTK_WINDOW (dialog));
638 }
639