2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 2014-2022 Ricardo Mones and the Claws Mail Team
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.
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.
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/>.
21 #include "claws-features.h"
25 #include <glib/gi18n.h>
27 #include "libravatar.h"
30 #include <gtk/filesel.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"
39 #define PREFS_BLOCK_NAME "Libravatar"
40 #define NUM_DEF_BUTTONS 9
41 #define CUSTOM_URL_BUTTON_INDEX 8
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
49 LibravatarPrefs libravatarprefs
;
50 GHashTable
*libravatarmisses
;
52 struct LibravatarPrefsPage
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
;
67 struct LibravatarPrefsPage libravatarprefs_page
;
69 static PrefParam param
[] = {
70 { "base_url", "https://seccdn.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
},
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
}
108 static GtkWidget
*create_checkbox(gchar
*label
, gchar
*hint
)
110 GtkWidget
*cb
= gtk_check_button_new_with_mnemonic(label
);
111 CLAWS_SET_TIP(cb
, hint
);
117 static void cache_icons_check_toggled_cb(GtkToggleButton
*button
, gpointer data
)
119 gtk_widget_set_sensitive(libravatarprefs_page
.cache_interval_spin
,
120 gtk_toggle_button_get_active(button
));
123 static GtkWidget
*labeled_spinner_box(gchar
*label
, GtkWidget
*spinner
, gchar
*units
, gchar
*hint
)
125 GtkWidget
*lbl
, *lbla
, *hbox
;
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_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
133 CLAWS_SET_TIP(spinner
, hint
);
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);
142 static gchar
*avatar_stats_label_markup(AvatarCacheStats
*stats
)
145 return g_markup_printf_escaped("<span color=\"red\">%s</span>",
146 _("Error reading cache stats"));
148 if (stats
->errors
> 0) {
149 gchar
*label_text
= g_strdup_printf(_("Using %s in %d files, %d "
150 "directories, %d others and %d errors"),
151 to_human_readable((goffset
) stats
->bytes
),
156 gchar
*label
= g_markup_printf_escaped("<span color=\"red\">%s</span>",
162 return g_strdup_printf(
163 _("Using %s in %d files, %d directories and %d others"),
164 to_human_readable((goffset
) stats
->bytes
),
170 static void cache_clean_button_clicked_cb(GtkButton
*button
, gpointer data
)
172 GtkLabel
*label
= (GtkLabel
*) data
;
174 AvatarCleanupResult
*acr
;
177 val
= alertpanel_full(_("Clear icon cache"),
178 _("Are you sure you want to remove all cached avatar icons?"),
179 NULL
, _("_No"), NULL
, _("_Yes"), NULL
, NULL
,
180 ALERTFOCUS_FIRST
, FALSE
, NULL
, ALERT_WARNING
);
181 if (val
!= G_ALERTALTERNATE
)
184 debug_print("cleaning missing cache\n");
185 misses
= g_hash_table_size(libravatarmisses
);
186 g_hash_table_remove_all(libravatarmisses
);
188 debug_print("cleaning disk cache\n");
189 acr
= libravatar_cache_clean();
191 alertpanel_error(_("Not enough memory for operation"));
195 if (acr
->e_stat
== 0 && acr
->e_unlink
== 0) {
196 alertpanel_notice(_("Icon cache successfully cleared:\n"
197 "• %u missing entries removed.\n"
198 "• %u files removed."),
199 misses
, acr
->removed
);
200 gtk_label_set_markup(label
, g_strconcat("<span color=\"#006400\">",
201 _("Icon cache successfully cleared!"), "</span>", NULL
));
204 alertpanel_warning(_("Errors clearing icon cache:\n"
205 "• %u missing entries removed.\n"
206 "• %u files removed.\n"
207 "• %u files failed to be read.\n"
208 "• %u files couldn't be removed."),
209 misses
, acr
->removed
, acr
->e_stat
, acr
->e_unlink
);
210 gtk_label_set_markup(label
, g_strconcat("<span color=\"red\">",
211 _("Error clearing icon cache."), "</span>", NULL
));
213 gtk_widget_set_sensitive(GTK_WIDGET(button
), FALSE
);
217 static GtkWidget
*p_create_frame_cache(struct LibravatarPrefsPage
*page
)
219 GtkWidget
*vbox
, *checkbox
, *spinner
, *hbox
, *label
, *button
;
221 AvatarCacheStats
*stats
;
224 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 6);
226 checkbox
= create_checkbox(_("_Use cached icons"),
227 _("Keep icons on disk for reusing instead "
228 "of making another network request"));
229 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox
),
230 libravatarprefs
.cache_icons
);
231 g_signal_connect(checkbox
, "toggled",
232 G_CALLBACK(cache_icons_check_toggled_cb
), NULL
);
233 page
->cache_icons_check
= checkbox
;
235 adj
= (GtkAdjustment
*) gtk_adjustment_new(
236 libravatarprefs
.cache_interval
,
237 INTERVAL_MIN_H
, INTERVAL_MAX_H
, 1.0,
239 spinner
= gtk_spin_button_new(adj
, 1.0, 0);
240 gtk_widget_show(spinner
);
241 gtk_widget_set_sensitive(spinner
, libravatarprefs
.cache_icons
);
242 hbox
= labeled_spinner_box(_("Cache refresh interval"), spinner
, _("hours"), NULL
);
243 page
->cache_interval_spin
= spinner
;
245 gtk_box_pack_start(GTK_BOX(vbox
), checkbox
, FALSE
, FALSE
, 0);
246 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, FALSE
, FALSE
, 0);
248 label
= gtk_label_new(NULL
);
249 gtk_widget_show(label
);
250 stats
= libravatar_cache_stats();
251 markup
= avatar_stats_label_markup(stats
);
252 gtk_label_set_markup(GTK_LABEL(label
), markup
);
254 gtk_label_set_xalign(GTK_LABEL(label
), 0.0);
256 button
= gtkut_stock_button("edit-clear", _("C_lear"));
257 gtk_widget_show(button
);
258 g_signal_connect(button
, "clicked",
259 G_CALLBACK(cache_clean_button_clicked_cb
), label
);
260 gtk_widget_set_sensitive(button
, (stats
!= NULL
&& stats
->bytes
> 0));
262 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
263 gtk_widget_show(hbox
);
264 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
265 gtk_box_pack_start(GTK_BOX(hbox
), button
, FALSE
, FALSE
, 0);
266 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, FALSE
, FALSE
, 0);
274 static void default_mode_radio_button_cb(GtkToggleButton
*button
, gpointer data
)
279 if (gtk_toggle_button_get_active(button
) != TRUE
)
282 mode
= *((guint
*)data
);
283 is_url
= (mode
== DEF_MODE_URL
)? TRUE
: FALSE
;
285 gtk_widget_set_sensitive(libravatarprefs_page
.defm_url_text
, is_url
);
286 if (is_url
) /* custom URL requires following redirects */
287 gtk_toggle_button_set_active(
288 GTK_TOGGLE_BUTTON(libravatarprefs_page
.allow_redirects_check
),
291 if (mode
== DEF_MODE_NONE
) {
292 prefs_common_get_prefs()->enable_avatars
= AVATARS_ENABLE_BOTH
;
294 /* don't waste time with headers that won't be displayed */
295 prefs_common_get_prefs()->enable_avatars
= AVATARS_DISABLE
;
296 /* empty missing cache when switching to generated */
297 g_hash_table_remove_all(libravatarmisses
);
301 static const guint radio_value
[] = {
313 static GtkWidget
*p_create_frame_missing(struct LibravatarPrefsPage
*page
)
315 GtkWidget
*vbox
, *radio
[NUM_DEF_BUTTONS
], *hbox
, *entry
;
316 gboolean enable
= FALSE
;
318 gchar
*radio_label
[] = {
329 gchar
*radio_hint
[] = {
331 _("The unobtrusive low-contrast greyish silhouette"),
332 _("A generated geometric pattern"),
333 _("A generated full-body monster"),
334 _("A generated almost unique face"),
335 _("A generated 8-bit arcade-style pixelated image"),
336 _("A generated robotic character"),
337 _("A generated retro adventure game character"),
338 _("Redirect to a user provided URL")
341 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 6);
343 for (i
= 0; i
< NUM_DEF_BUTTONS
; ++i
) {
344 enable
= (libravatarprefs
.default_mode
== radio_value
[i
])? TRUE
: FALSE
;
346 radio
[i
] = gtk_radio_button_new_with_label_from_widget(
347 (i
> 0)? GTK_RADIO_BUTTON(radio
[i
- 1]): NULL
, radio_label
[i
]);
348 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio
[i
]), enable
);
349 if (i
== CUSTOM_URL_BUTTON_INDEX
) {
350 /* set related entry next to radio button */
351 entry
= gtk_entry_new();
352 CLAWS_SET_TIP(entry
, _("Enter the URL you want to be "
353 "redirected when no user icon is available. "
354 "Leave an empty URL to use the default "
355 "libravatar orange icon."));
356 gtk_entry_set_text(GTK_ENTRY(entry
),
357 libravatarprefs
.default_mode_url
);
358 gtk_entry_set_max_length(GTK_ENTRY(entry
), MAX_URL_LENGTH
);
359 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
360 gtk_box_pack_start(GTK_BOX(hbox
), radio
[i
], FALSE
, FALSE
, 0);
361 gtk_box_pack_start(GTK_BOX(hbox
), entry
, TRUE
, TRUE
, 0);
362 gtk_widget_set_sensitive(entry
,
363 (libravatarprefs
.default_mode
== DEF_MODE_URL
)
365 gtk_widget_show(entry
);
366 page
->defm_url_text
= entry
;
367 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, FALSE
, FALSE
, 0);
369 gtk_box_pack_start(GTK_BOX(vbox
), radio
[i
], FALSE
, FALSE
, 0);
371 g_signal_connect(radio
[i
], "toggled",
372 G_CALLBACK(default_mode_radio_button_cb
),
373 (gpointer
) &(radio_value
[i
]));
374 CLAWS_SET_TIP(radio
[i
], radio_hint
[i
]);
375 gtk_widget_show(radio
[i
]);
376 page
->defm_radio
[i
] = radio
[i
];
378 if (e
== 0) { /* unknown value, go default */
379 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio
[0]), TRUE
);
380 libravatarprefs
.default_mode
= DEF_MODE_NONE
;
382 /* don't waste time with headers that won't be displayed */
383 prefs_common_get_prefs()->enable_avatars
=
384 (libravatarprefs
.default_mode
== DEF_MODE_NONE
)
385 ? AVATARS_ENABLE_BOTH
: AVATARS_DISABLE
;
392 static GtkWidget
*p_create_frame_network(struct LibravatarPrefsPage
*page
)
394 GtkWidget
*vbox
, *chk_redirects
, *spinner
, *hbox
;
396 #if defined USE_GNUTLS
397 GtkWidget
*chk_federated
;
400 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 6);
402 chk_redirects
= create_checkbox(_("_Allow redirects to other sites"),
403 _("Follow redirect responses received from "
404 "libravatar server to other avatar "
405 "services like gravatar.com"));
406 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(chk_redirects
),
407 libravatarprefs
.allow_redirects
);
408 page
->allow_redirects_check
= chk_redirects
;
409 gtk_box_pack_start(GTK_BOX(vbox
), chk_redirects
, FALSE
, FALSE
, 0);
411 #if defined USE_GNUTLS
412 chk_federated
= create_checkbox(_("_Enable federated servers"),
413 _("Try to get avatar from sender's domain "
414 "libravatar server"));
415 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(chk_federated
),
416 libravatarprefs
.allow_federated
);
417 page
->allow_federated_check
= chk_federated
;
418 gtk_box_pack_start(GTK_BOX(vbox
), chk_federated
, FALSE
, FALSE
, 0);
421 adj
= (GtkAdjustment
*) gtk_adjustment_new(
422 libravatarprefs
.timeout
,
424 (prefs_common_get_prefs()->io_timeout_secs
> 0)
425 ? (prefs_common_get_prefs()->io_timeout_secs
- 1)
428 spinner
= gtk_spin_button_new(adj
, 1.0, 0);
429 gtk_widget_show(spinner
);
430 hbox
= labeled_spinner_box(_("Request timeout"), spinner
, _("second(s)"),
431 _("Set to 0 to use global socket I/O timeout. "
432 "Maximum value must be also less than global socket "
434 page
->timeout
= spinner
;
435 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, FALSE
, FALSE
, 0);
441 ┌─Icon cache───────────────────────────────────────────┐
442 │ [✔] Use cached icons │
443 │ Cache refresh interval [ 24 |⬘] hours │
444 │ Using X KB in Y files and Z directories [Clear] │
445 └──────────────────────────────────────────────────────┘
446 ┌─Default missing icon mode────────────────────────────┐
455 │ ( ) Custom URL [___________________________________] │
456 └──────────────────────────────────────────────────────┘
457 ┌─Network──────────────────────────────────────────────┐
458 │ [✔] Allow redirects │
459 │ [✔] Federated servers │
460 │ Timeout [ 10 |⬘] seconds │
461 └──────────────────────────────────────────────────────┘
463 static void libravatar_prefs_create_widget_func(PrefsPage
* _page
,
467 struct LibravatarPrefsPage
*page
= (struct LibravatarPrefsPage
*) _page
;
468 GtkWidget
*vbox
, *vbox1
, *vbox2
, *vbox3
, *frame
;
470 vbox1
= p_create_frame_cache(page
);
471 vbox2
= p_create_frame_missing(page
);
472 vbox3
= p_create_frame_network(page
);
474 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 6);
475 gtk_container_set_border_width(GTK_CONTAINER(vbox
), VBOX_BORDER
);
477 PACK_FRAME (vbox
, frame
, _("Icon cache"));
478 gtk_container_set_border_width(GTK_CONTAINER(vbox1
), 6);
479 gtk_container_add(GTK_CONTAINER(frame
), vbox1
);
481 PACK_FRAME (vbox
, frame
, _("Default missing icon mode"));
482 gtk_container_set_border_width(GTK_CONTAINER(vbox2
), 6);
483 gtk_container_add(GTK_CONTAINER(frame
), vbox2
);
485 PACK_FRAME (vbox
, frame
, _("Network"));
486 gtk_container_set_border_width(GTK_CONTAINER(vbox3
), 6);
487 gtk_container_add(GTK_CONTAINER(frame
), vbox3
);
489 gtk_widget_show_all(vbox
);
490 page
->page
.widget
= vbox
;
493 static void libravatar_prefs_destroy_widget_func(PrefsPage
*_page
)
498 static void libravatar_save_config(void)
503 debug_print("Saving Libravatar Page\n");
505 rcpath
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
, COMMON_RC
, NULL
);
506 pfile
= prefs_write_open(rcpath
);
508 if (!pfile
|| (prefs_set_block_label(pfile
, PREFS_BLOCK_NAME
) < 0))
511 if (prefs_write_param(param
, pfile
->fp
) < 0) {
512 g_warning("failed to write Libravatar configuration to file");
513 prefs_file_close_revert(pfile
);
516 if (fprintf(pfile
->fp
, "\n") < 0) {
517 FILE_OP_ERROR(rcpath
, "fprintf");
518 prefs_file_close_revert(pfile
);
520 prefs_file_close(pfile
);
523 static void libravatar_prefs_save_func(PrefsPage
* _page
)
525 struct LibravatarPrefsPage
*page
= (struct LibravatarPrefsPage
*) _page
;
529 libravatarprefs
.cache_icons
= gtk_toggle_button_get_active(
530 GTK_TOGGLE_BUTTON(page
->cache_icons_check
));
532 libravatarprefs
.cache_interval
= gtk_spin_button_get_value_as_int(
533 GTK_SPIN_BUTTON(page
->cache_interval_spin
));
535 for (i
= 0; i
< NUM_DEF_BUTTONS
; ++i
) {
536 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(page
->defm_radio
[i
]))) {
537 libravatarprefs
.default_mode
= radio_value
[i
];
542 if (libravatarprefs
.default_mode_url
!= NULL
) {
543 g_free(libravatarprefs
.default_mode_url
);
545 libravatarprefs
.default_mode_url
= gtk_editable_get_chars(
546 GTK_EDITABLE(page
->defm_url_text
), 0, -1);
548 libravatarprefs
.allow_redirects
= gtk_toggle_button_get_active(
549 GTK_TOGGLE_BUTTON(page
->allow_redirects_check
));
551 #if defined USE_GNUTLS
552 libravatarprefs
.allow_federated
= gtk_toggle_button_get_active(
553 GTK_TOGGLE_BUTTON(page
->allow_federated_check
));
556 libravatarprefs
.timeout
= gtk_spin_button_get_value_as_int(
557 GTK_SPIN_BUTTON(page
->timeout
));
559 libravatar_save_config();
562 void libravatar_prefs_init(void)
564 static gchar
*path
[3];
567 path
[0] = _("Plugins");
568 path
[1] = _("Libravatar");
571 prefs_set_default(param
);
572 rcpath
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
, COMMON_RC
, NULL
);
573 prefs_read_config(param
, PREFS_BLOCK_NAME
, rcpath
, NULL
);
576 libravatarprefs_page
.page
.path
= path
;
577 libravatarprefs_page
.page
.create_widget
= libravatar_prefs_create_widget_func
;
578 libravatarprefs_page
.page
.destroy_widget
= libravatar_prefs_destroy_widget_func
;
579 libravatarprefs_page
.page
.save_page
= libravatar_prefs_save_func
;
580 libravatarprefs_page
.page
.weight
= 40.0;
582 prefs_gtk_register_page((PrefsPage
*) &libravatarprefs_page
);
585 void libravatar_prefs_done(void)
587 prefs_gtk_unregister_page((PrefsPage
*) &libravatarprefs_page
);