3a097001b54a14c84a6f8a623614abdb4c43ecd6
[claws.git] / src / plugins / libravatar / libravatar_prefs.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2014-2016 Ricardo Mones and the Claws Mail Team
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
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
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 #ifdef HAVE_CONFIG_H
20 #  include "config.h"
21 #include "claws-features.h"
22 #endif
23
24 #include <glib.h>
25 #include <glib/gi18n.h>
26
27 #include "libravatar.h"
28
29 #include <gtk/gtk.h>
30 #include <gtk/filesel.h>
31
32 #include "defs.h"
33 #include "libravatar_prefs.h"
34 #include "libravatar_cache.h"
35 #include "prefs_common.h"
36 #include "prefs_gtk.h"
37 #include "alertpanel.h"
38
39 #define PREFS_BLOCK_NAME "Libravatar"
40 #define NUM_DEF_BUTTONS 7
41 #define CUSTOM_URL_BUTTON_INDEX 6
42 /* cache interval goes from 1 hour to 30 days */
43 #define INTERVAL_MIN_H 1.0
44 #define INTERVAL_MAX_H 720.0
45 /* timeout interval goes from 0 seconds (= use general timeout value)
46    to (general timeout value - 1) seconds */
47 #define TIMEOUT_MIN_S 0.0
48
49 LibravatarPrefs libravatarprefs;
50 GHashTable *libravatarmisses;
51
52 struct LibravatarPrefsPage
53 {
54         PrefsPage page;
55
56         GtkWidget *cache_interval_spin;
57         GtkWidget *cache_icons_check;
58         GtkWidget *defm_radio[NUM_DEF_BUTTONS];
59         GtkWidget *defm_url_text;
60         GtkWidget *allow_redirects_check;
61 #if defined USE_GNUTLS
62         GtkWidget *allow_federated_check;
63 #endif
64         GtkWidget *timeout;
65 };
66
67 struct LibravatarPrefsPage libravatarprefs_page;
68
69 static PrefParam param[] = {
70         { "base_url", "http://cdn.libravatar.org/avatar",
71           &libravatarprefs.base_url,
72           P_STRING, NULL, NULL, NULL },
73         { "cache_interval", "24",
74           &libravatarprefs.cache_interval,
75           P_INT, NULL, NULL, NULL },
76         { "cache_icons", "TRUE",
77           &libravatarprefs.cache_icons,
78           P_BOOL, NULL, NULL, NULL },
79         { "default_mode", "0",
80           &libravatarprefs.default_mode,
81           P_INT, NULL, NULL, NULL },
82         { "default_mode_url", "",
83           &libravatarprefs.default_mode_url,
84           P_STRING, NULL, NULL, NULL },
85         { "allow_redirects", "TRUE",
86           &libravatarprefs.allow_redirects,
87           P_BOOL, NULL, NULL, NULL },
88 #if defined USE_GNUTLS
89         { "allow_federated", "TRUE",
90           &libravatarprefs.allow_federated,
91           P_BOOL, NULL, NULL, NULL },
92 #endif
93         { "timeout", "0",
94           &libravatarprefs.timeout,
95           P_INT, NULL, NULL, NULL },
96         { "max_redirects_url", "7",
97           &libravatarprefs.max_redirects_url,
98           P_INT, NULL, NULL, NULL },
99         { "max_redirects_mm", "5",
100           &libravatarprefs.max_redirects_mm,
101           P_INT, NULL, NULL, NULL },
102         { "max_redirects", "3",
103           &libravatarprefs.max_redirects,
104           P_INT, NULL, NULL, NULL },
105         {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL}
106 };
107
108 static GtkWidget *create_checkbox(gchar *label, gchar *hint)
109 {
110         GtkWidget *cb = gtk_check_button_new_with_mnemonic(label);
111         CLAWS_SET_TIP(cb, hint);
112         gtk_widget_show(cb);
113
114         return cb;
115 }
116
117 static void cache_icons_check_toggled_cb(GtkToggleButton *button, gpointer data)
118 {
119         gtk_widget_set_sensitive(libravatarprefs_page.cache_interval_spin,
120                                  gtk_toggle_button_get_active(button));
121 }
122
123 static GtkWidget *labeled_spinner_box(gchar *label, GtkWidget *spinner, gchar *units, gchar *hint)
124 {
125         GtkWidget *lbl, *lbla, *hbox;
126
127         lbl = gtk_label_new(label);
128         gtk_widget_show(lbl);
129         lbla = gtk_label_new(units);
130         gtk_widget_show(lbla);
131         hbox = gtk_hbox_new(FALSE, 6);
132         if (hint != NULL) {
133                 CLAWS_SET_TIP(spinner, hint);
134         }
135         gtk_box_pack_start(GTK_BOX(hbox), lbl, FALSE, FALSE, 0);
136         gtk_box_pack_start(GTK_BOX(hbox), spinner, FALSE, FALSE, 0);
137         gtk_box_pack_start(GTK_BOX(hbox), lbla, FALSE, FALSE, 0);
138
139         return hbox;
140 }
141
142 static gchar *avatar_stats_label_markup(AvatarCacheStats *stats)
143 {
144         if (stats == NULL)
145                 return g_strdup(g_strconcat("<span color=\"red\">",
146                         _("Error reading cache stats"), "</span>", NULL));
147
148         if (stats->errors > 0)
149                 return g_markup_printf_escaped(g_strconcat("<span color=\"red\">",
150                         _("Using %s in %d files, %d "
151                         "directories, %d others and %d errors"), "</span>", NULL),
152                         to_human_readable((goffset) stats->bytes),
153                         stats->files,
154                         stats->dirs,
155                         stats->others,
156                         stats->errors);
157
158         return g_strdup_printf(
159                 _("Using %s in %d files, %d directories and %d others"),
160                 to_human_readable((goffset) stats->bytes),
161                 stats->files,
162                 stats->dirs,
163                 stats->others);
164 }
165
166 static void cache_clean_button_clicked_cb(GtkButton *button, gpointer data)
167 {
168         GtkLabel *label = (GtkLabel *) data;
169         gint val = 0;
170         AvatarCleanupResult *acr;
171         guint misses;
172
173         val = alertpanel_full(_("Clear icon cache"),
174                         _("Are you sure you want to remove all cached avatar icons?"),
175                         GTK_STOCK_NO, GTK_STOCK_YES, NULL, ALERTFOCUS_FIRST, FALSE,
176                         NULL, ALERT_WARNING);
177         if (val != G_ALERTALTERNATE)
178                 return;
179
180         debug_print("cleaning missing cache\n");
181         misses = g_hash_table_size(libravatarmisses);
182         g_hash_table_remove_all(libravatarmisses);
183
184         debug_print("cleaning disk cache\n");
185         acr = libravatar_cache_clean();
186         if (acr == NULL) {
187                 alertpanel_error(_("Not enough memory for operation"));
188                 return;
189         }
190
191         if (acr->e_stat == 0 && acr->e_unlink == 0) {
192                 alertpanel_notice(_("Icon cache successfully cleared:\n"
193                         "• %u missing entries removed.\n"
194                         "• %u files removed."),
195                         misses, acr->removed);
196                 gtk_label_set_markup(label, g_strconcat("<span color=\"#006400\">",
197                         _("Icon cache successfully cleared!"), "</span>", NULL));
198         }
199         else {
200                 alertpanel_warning(_("Errors clearing icon cache:\n"
201                         "• %u missing entries removed.\n"
202                         "• %u files removed.\n"
203                         "• %u files failed to be read.\n"
204                         "• %u files couldn't be removed."),
205                         misses, acr->removed, acr->e_stat, acr->e_unlink);
206                 gtk_label_set_markup(label, g_strconcat("<span color=\"red\">",
207                         _("Error clearing icon cache."), "</span>", NULL));
208         }
209         gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE);
210         g_free(acr);
211 }
212
213 static GtkWidget *p_create_frame_cache(struct LibravatarPrefsPage *page)
214 {
215         GtkWidget *vbox, *checkbox, *spinner, *hbox, *label, *button;
216         GtkAdjustment *adj;
217         AvatarCacheStats *stats;
218         gchar *markup;
219
220         vbox =  gtk_vbox_new(FALSE, 6);
221
222         checkbox = create_checkbox(_("_Use cached icons"),
223                                    _("Keep icons on disk for reusing instead "
224                                      "of making another network request"));
225         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
226                                      libravatarprefs.cache_icons);
227         g_signal_connect(checkbox, "toggled",
228                          G_CALLBACK(cache_icons_check_toggled_cb), NULL);
229         page->cache_icons_check = checkbox;
230
231         adj = (GtkAdjustment *) gtk_adjustment_new(
232                                         libravatarprefs.cache_interval,
233                                         INTERVAL_MIN_H, INTERVAL_MAX_H, 1.0,
234                                         0.0, 0.0);
235         spinner = gtk_spin_button_new(adj, 1.0, 0);
236         gtk_widget_show(spinner);
237         gtk_widget_set_sensitive(spinner, libravatarprefs.cache_icons);
238         hbox = labeled_spinner_box(_("Cache refresh interval"), spinner, _("hours"), NULL);
239         page->cache_interval_spin = spinner;
240
241         gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 0);
242         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
243
244         label = gtk_label_new(NULL);
245         gtk_widget_show(label);
246         stats = libravatar_cache_stats();
247         markup = avatar_stats_label_markup(stats);
248         gtk_label_set_markup(GTK_LABEL(label), markup);
249         g_free(markup);
250         gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
251
252         button = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
253         gtk_widget_show(button);
254         g_signal_connect(button, "clicked",
255                 G_CALLBACK(cache_clean_button_clicked_cb), label);
256         gtk_widget_set_sensitive(button, (stats != NULL && stats->bytes > 0));
257
258         hbox = gtk_hbox_new(FALSE, 6);
259         gtk_widget_show(hbox);
260         gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
261         gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
262         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
263
264         if (stats != NULL)
265                 g_free(stats);
266
267         return vbox;
268 }
269
270 static void default_mode_radio_button_cb(GtkToggleButton *button, gpointer data)
271 {
272         guint mode;
273         gboolean is_url;
274
275         if (gtk_toggle_button_get_active(button) != TRUE)
276                 return;
277
278         mode = *((guint *)data);
279         is_url = (mode == DEF_MODE_URL)? TRUE: FALSE;
280
281         gtk_widget_set_sensitive(libravatarprefs_page.defm_url_text, is_url);
282         if (is_url) /* custom URL requires following redirects */
283                 gtk_toggle_button_set_active(
284                         GTK_TOGGLE_BUTTON(libravatarprefs_page.allow_redirects_check),
285                         TRUE);
286
287         if (mode == DEF_MODE_NONE) {
288                 prefs_common_get_prefs()->enable_avatars = AVATARS_ENABLE_BOTH;
289         } else {
290                 /* don't waste time with headers that won't be displayed */
291                 prefs_common_get_prefs()->enable_avatars = AVATARS_DISABLE;
292                 /* empty missing cache when switching to generated */
293                 g_hash_table_remove_all(libravatarmisses);
294         }
295 }
296
297 static const guint radio_value[] = {
298         DEF_MODE_NONE,
299         DEF_MODE_MM,
300         DEF_MODE_IDENTICON,
301         DEF_MODE_MONSTERID,
302         DEF_MODE_WAVATAR,
303         DEF_MODE_RETRO,
304         DEF_MODE_URL
305 };
306
307 static GtkWidget *p_create_frame_missing(struct LibravatarPrefsPage *page)
308 {
309         GtkWidget *vbox, *radio[NUM_DEF_BUTTONS], *hbox, *entry;
310         gboolean enable = FALSE;
311         int i, e = 0;
312         gchar *radio_label[] = {
313                 _("None"),
314                 _("Mystery man"),
315                 _("Identicon"),
316                 _("MonsterID"),
317                 _("Wavatar"),
318                 _("Retro"),
319                 _("Custom URL")
320         };
321         gchar *radio_hint[] = {
322                 _("A blank image"),
323                 _("The unobtrusive low-contrast greyish silhouette"),
324                 _("A generated geometric pattern"),
325                 _("A generated full-body monster"),
326                 _("A generated almost unique face"),
327                 _("A generated 8-bit arcade-style pixelated image"),
328                 _("Redirect to a user provided URL")
329         };
330
331         vbox =  gtk_vbox_new(FALSE, 6);
332
333         for (i = 0; i < NUM_DEF_BUTTONS; ++i) {
334                 enable = (libravatarprefs.default_mode == radio_value[i])? TRUE: FALSE;
335                 e += enable? 1: 0;
336                 radio[i] = gtk_radio_button_new_with_label_from_widget(
337                                 (i > 0)? GTK_RADIO_BUTTON(radio[i - 1]): NULL, radio_label[i]);
338                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio[i]), enable);
339                 if (i == CUSTOM_URL_BUTTON_INDEX) {
340                         /* set related entry next to radio button */
341                         entry = gtk_entry_new_with_max_length(MAX_URL_LENGTH);
342                         CLAWS_SET_TIP(entry, _("Enter the URL you want to be "
343                                 "redirected when no user icon is available. "
344                                 "Leave an empty URL to use the default "
345                                 "libravatar orange icon."));
346                         gtk_widget_show(entry);
347                         gtk_entry_set_text(GTK_ENTRY(entry),
348                                 libravatarprefs.default_mode_url);
349                         hbox = gtk_hbox_new(FALSE, 6);
350                         gtk_box_pack_start(GTK_BOX(hbox), radio[i], FALSE, FALSE, 0);
351                         gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
352                         gtk_widget_set_sensitive(entry,
353                                 (libravatarprefs.default_mode == DEF_MODE_URL)
354                                 ? TRUE: FALSE);
355                         page->defm_url_text = entry;
356                         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
357                 } else {
358                         gtk_box_pack_start(GTK_BOX(vbox), radio[i], FALSE, FALSE, 0);
359                 }
360                 g_signal_connect(radio[i], "toggled",
361                                  G_CALLBACK(default_mode_radio_button_cb),
362                                  (gpointer) &(radio_value[i]));
363                 CLAWS_SET_TIP(radio[i], radio_hint[i]);
364                 gtk_widget_show(radio[i]);
365                 page->defm_radio[i] = radio[i];
366         }
367         if (e == 0) { /* unknown value, go default */
368                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio[0]), TRUE);
369                 libravatarprefs.default_mode = DEF_MODE_NONE;
370         }
371         /* don't waste time with headers that won't be displayed */
372         prefs_common_get_prefs()->enable_avatars =
373                 (libravatarprefs.default_mode == DEF_MODE_NONE)
374                 ? AVATARS_ENABLE_BOTH: AVATARS_DISABLE;
375
376
377
378         return vbox;
379 }
380
381 static GtkWidget *p_create_frame_network(struct LibravatarPrefsPage *page)
382 {
383         GtkWidget *vbox, *chk_redirects, *spinner, *hbox;
384         GtkAdjustment *adj;
385 #if defined USE_GNUTLS
386         GtkWidget *chk_federated;
387 #endif
388
389         vbox =  gtk_vbox_new(FALSE, 6);
390
391         chk_redirects = create_checkbox(_("_Allow redirects to other sites"),
392                                    _("Follow redirect responses received from "
393                                      "libravatar server to other avatar "
394                                      "services like gravatar.com"));
395         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(chk_redirects),
396                                      libravatarprefs.allow_redirects);
397         page->allow_redirects_check = chk_redirects;
398         gtk_box_pack_start(GTK_BOX(vbox), chk_redirects, FALSE, FALSE, 0);
399
400 #if defined USE_GNUTLS
401         chk_federated = create_checkbox(_("_Enable federated servers"),
402                                 _("Try to get avatar from sender's domain "
403                                   "libravatar server"));
404         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(chk_federated),
405                                      libravatarprefs.allow_federated);
406         page->allow_federated_check = chk_federated;
407         gtk_box_pack_start(GTK_BOX(vbox), chk_federated, FALSE, FALSE, 0);
408 #endif
409
410         adj = (GtkAdjustment *) gtk_adjustment_new(
411                                         libravatarprefs.timeout,
412                                         TIMEOUT_MIN_S,
413                                         (prefs_common_get_prefs()->io_timeout_secs > 0)
414                                         ? (prefs_common_get_prefs()->io_timeout_secs - 1)
415                                         : 0,
416                                         1.0, 0.0, 0.0);
417         spinner = gtk_spin_button_new(adj, 1.0, 0);
418         gtk_widget_show(spinner);
419         hbox = labeled_spinner_box(_("Request timeout"), spinner, _("second(s)"),
420                 _("Set to 0 to use global socket I/O timeout. "
421                   "Maximum value must be also less than global socket "
422                   "I/O timeout."));
423         page->timeout = spinner;
424         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
425
426         return vbox;
427 }
428
429 /*
430   ┌─Icon cache───────────────────────────────────────────┐
431   │ [✔] Use cached icons                                 │
432   │ Cache refresh interval [ 24 |⬘] hours                │
433   │ Using X KB in Y files and Z directories [Clear]      │
434   └──────────────────────────────────────────────────────┘
435   ┌─Default missing icon mode────────────────────────────┐
436   │ (•) None                                             │
437   │ ( ) Mystery man                                      │
438   │ ( ) Identicon                                        │
439   │ ( ) MonsterID                                        │
440   │ ( ) Wavatar                                          │
441   │ ( ) Retro                                            │
442   │ ( ) Custom URL [___________________________________] │
443   └──────────────────────────────────────────────────────┘
444   ┌─Network──────────────────────────────────────────────┐
445   │ [✔] Allow redirects                                  │
446   │ [✔] Federated servers                                │
447   │ Timeout [ 10 |⬘] seconds                             │
448   └──────────────────────────────────────────────────────┘
449  */
450 static void libravatar_prefs_create_widget_func(PrefsPage * _page,
451                                                 GtkWindow * window,
452                                                 gpointer data)
453 {
454         struct LibravatarPrefsPage *page = (struct LibravatarPrefsPage *) _page;
455         GtkWidget *vbox, *vbox1, *vbox2, *vbox3, *frame;
456
457         vbox1 = p_create_frame_cache(page);
458         vbox2 = p_create_frame_missing(page);
459         vbox3 = p_create_frame_network(page);
460
461         vbox = gtk_vbox_new(FALSE, 6);
462         gtk_container_set_border_width(GTK_CONTAINER(vbox), VBOX_BORDER);
463
464         PACK_FRAME (vbox, frame, _("Icon cache"));
465         gtk_container_set_border_width(GTK_CONTAINER(vbox1), 6);
466         gtk_container_add(GTK_CONTAINER(frame), vbox1);
467
468         PACK_FRAME (vbox, frame, _("Default missing icon mode"));
469         gtk_container_set_border_width(GTK_CONTAINER(vbox2), 6);
470         gtk_container_add(GTK_CONTAINER(frame), vbox2);
471
472         PACK_FRAME (vbox, frame, _("Network"));
473         gtk_container_set_border_width(GTK_CONTAINER(vbox3), 6);
474         gtk_container_add(GTK_CONTAINER(frame), vbox3);
475
476         gtk_widget_show_all(vbox);
477         page->page.widget = vbox;
478 }
479
480 static void libravatar_prefs_destroy_widget_func(PrefsPage *_page)
481 {
482         /* nothing */
483 }
484
485 static void libravatar_save_config(void)
486 {
487         PrefFile *pfile;
488         gchar *rcpath;
489
490         debug_print("Saving Libravatar Page\n");
491
492         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
493         pfile = prefs_write_open(rcpath);
494         g_free(rcpath);
495         if (!pfile || (prefs_set_block_label(pfile, PREFS_BLOCK_NAME) < 0))
496                 return;
497
498         if (prefs_write_param(param, pfile->fp) < 0) {
499                 g_warning("failed to write Libravatar configuration to file");
500                 prefs_file_close_revert(pfile);
501                 return;
502         }
503         if (fprintf(pfile->fp, "\n") < 0) {
504                 FILE_OP_ERROR(rcpath, "fprintf");
505                 prefs_file_close_revert(pfile);
506         } else
507                 prefs_file_close(pfile);
508 }
509
510 static void libravatar_prefs_save_func(PrefsPage * _page)
511 {
512         struct LibravatarPrefsPage *page = (struct LibravatarPrefsPage *) _page;
513         int i;
514
515         /* cache */
516         libravatarprefs.cache_icons = gtk_toggle_button_get_active(
517                 GTK_TOGGLE_BUTTON(page->cache_icons_check));
518         /* cache interval */
519         libravatarprefs.cache_interval = gtk_spin_button_get_value_as_int(
520                 GTK_SPIN_BUTTON(page->cache_interval_spin));
521         /* default mode */
522         for (i = 0; i < NUM_DEF_BUTTONS; ++i) {
523                 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(page->defm_radio[i]))) {
524                         libravatarprefs.default_mode = radio_value[i];
525                         break;
526                 }
527         }
528         /* custom url */
529         if (libravatarprefs.default_mode_url != NULL) {
530                 g_free(libravatarprefs.default_mode_url);
531         }
532         libravatarprefs.default_mode_url = gtk_editable_get_chars(
533                 GTK_EDITABLE(page->defm_url_text), 0, -1);
534         /* redirects */
535         libravatarprefs.allow_redirects = gtk_toggle_button_get_active(
536                 GTK_TOGGLE_BUTTON(page->allow_redirects_check));
537         /* federation */
538 #if defined USE_GNUTLS
539         libravatarprefs.allow_federated = gtk_toggle_button_get_active(
540                 GTK_TOGGLE_BUTTON(page->allow_federated_check));
541 #endif
542         /* timeout */
543         libravatarprefs.timeout = gtk_spin_button_get_value_as_int(
544                 GTK_SPIN_BUTTON(page->timeout));
545
546         libravatar_save_config();
547 }
548
549 void libravatar_prefs_init(void)
550 {
551         static gchar *path[3];
552         gchar *rcpath;
553
554         path[0] = _("Plugins");
555         path[1] = _("Libravatar");
556         path[2] = NULL;
557
558         prefs_set_default(param);
559         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
560         prefs_read_config(param, PREFS_BLOCK_NAME, rcpath, NULL);
561         g_free(rcpath);
562
563         libravatarprefs_page.page.path = path;
564         libravatarprefs_page.page.create_widget = libravatar_prefs_create_widget_func;
565         libravatarprefs_page.page.destroy_widget = libravatar_prefs_destroy_widget_func;
566         libravatarprefs_page.page.save_page = libravatar_prefs_save_func;
567         libravatarprefs_page.page.weight = 40.0;
568
569         prefs_gtk_register_page((PrefsPage *) &libravatarprefs_page);
570 }
571
572 void libravatar_prefs_done(void)
573 {
574         prefs_gtk_unregister_page((PrefsPage *) &libravatarprefs_page);
575 }
576