Fix buffer overrun, always writing at buffer size + 1.
[claws.git] / src / prefs_themes.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2003-2015 Hiroyuki Yamamoto & 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 #endif
22
23 #include "defs.h"
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/stat.h>
29 #ifdef HAVE_SVG
30 #include <math.h>
31 #endif
32
33 #include <glib.h>
34 #include <glib/gi18n.h>
35 #include <gtk/gtk.h>
36
37 #include "utils.h"
38 #include "codeconv.h"
39 #include "prefs_common.h"
40 #include "prefs_gtk.h"
41
42 #include "gtk/gtkutils.h"
43 #include "gtk/prefswindow.h"
44 #include "gtk/filesel.h"
45
46 #include "stock_pixmap.h"
47 #include "mainwindow.h"
48 #include "compose.h"
49 #include "alertpanel.h"
50 #include "addr_compl.h"
51
52 #define IS_CURRENT_THEME(path)  (strcmp(prefs_common.pixmap_theme_path, path) == 0)
53 #define IS_INTERNAL_THEME(path) (strcmp(DEFAULT_PIXMAP_THEME, path) == 0)
54 #define IS_SYSTEM_THEME(path)   (prefs_themes_is_system_theme(path))
55
56 #define PREVIEW_ICONS 7
57
58 typedef struct _ThemesPage
59 {
60         PrefsPage page;
61
62         GtkWidget *window;              /* do not modify */
63
64         GtkWidget *op_menu;
65         GtkWidget *btn_install;
66         GtkWidget *btn_more;
67         GtkWidget *global;
68
69         GtkWidget *name;
70         GtkWidget *author;
71         GtkWidget *url;
72         GtkWidget *status;
73
74         GtkWidget *icons[PREVIEW_ICONS];
75
76         GtkWidget *btn_remove;
77
78         GdkPixbuf *pixbufs[PREVIEW_ICONS];
79
80 #ifdef HAVE_SVG
81         GtkWidget *checkbtn_enable_alpha;
82         GtkWidget *checkbtn_enable_scaling;
83         GtkWidget *checkbtn_scaling_auto;
84         GtkWidget *label_scaling_ppi;
85         GtkWidget *spinbtn_scaling_ppi;
86 #endif
87 } ThemesPage;
88
89 typedef struct _ThemeInfo
90 {
91         gchar *name;
92         gchar *author;
93         gchar *url;
94         gchar *status;
95 } ThemeInfo;
96
97 typedef struct _ThemeName
98 {
99         gchar *name;
100         GList *item;
101 } ThemeName;
102
103 typedef struct _ThemesData
104 {
105         GList      *themes;
106         GList      *names;
107         gchar      *displayed;
108         ThemesPage *page;
109 } ThemesData;
110
111 typedef void (*FileFunc) (const gchar *filename, gpointer data);
112
113 typedef struct _DirInfo {
114         gint bytes;
115         gint files;
116         gint pixms;
117         /* extensions info */
118         const char **supported;
119         gint *length;
120 } DirInfo;
121
122 typedef struct _CopyInfo {
123         gchar *dest;
124         gchar *status;
125 } CopyInfo;
126
127 static ThemesData *prefs_themes_data;
128
129 StockPixmap prefs_themes_icons[PREVIEW_ICONS] = { 
130         STOCK_PIXMAP_DIR_CLOSE,
131         STOCK_PIXMAP_MAIL_SEND,
132         STOCK_PIXMAP_MAIL_RECEIVE, 
133         STOCK_PIXMAP_MAIL_ATTACH,
134         STOCK_PIXMAP_BOOK, 
135         STOCK_PIXMAP_MIME_TEXT_PLAIN, 
136         STOCK_PIXMAP_REPLIED
137 };
138
139
140
141 static void prefs_themes_btn_remove_clicked_cb  (GtkWidget *widget, gpointer data);
142 static void prefs_themes_btn_install_clicked_cb (GtkWidget *widget, gpointer data);
143 static void prefs_themes_menu_item_activated_cb (GtkWidget *widget, gpointer data);
144 #ifdef HAVE_SVG
145 static gdouble prefs_themes_compute_ppi(GdkScreen *screen);
146 static gdouble prefs_themes_get_adjusted_ppi(void);
147 static void prefs_themes_checkbtn_enable_scaling_toggled_cb (GtkWidget *widget, gpointer data);
148 static void prefs_themes_checkbtn_scaling_auto_toggled_cb (GtkWidget *widget, gpointer data);
149 #endif
150
151 static void prefs_themes_update_buttons         (const ThemesData *tdata);
152 static void prefs_themes_display_global_stats   (const ThemesData *tdata);
153 static void prefs_themes_get_theme_info         (ThemesData *tdata);
154 static void prefs_themes_display_theme_info     (ThemesData *tdata, const ThemeInfo *info);
155 static void prefs_themes_get_themes_and_names   (ThemesData *tdata);
156 static int prefs_themes_cmp_name(gconstpointer a, gconstpointer b);
157 static void prefs_themes_free_names             (ThemesData *tdata);
158
159 static void prefs_themes_set_themes_menu        (GtkComboBox *combo, const ThemesData *tdata);
160
161 static gchar *prefs_themes_get_theme_stats      (const gchar *dirname);
162 static gboolean prefs_themes_is_system_theme    (const gchar *dirname);
163
164 static void prefs_themes_create_widget          (PrefsPage *page, GtkWindow *window, gpointer data);
165 static void prefs_themes_destroy_widget         (PrefsPage *page);
166 static void prefs_themes_save                   (PrefsPage *page);
167
168 static void prefs_themes_foreach_file           (const gchar *dirname, const FileFunc func, gpointer data);
169 static void prefs_themes_file_stats             (const gchar *filename, gpointer data);
170 static void prefs_themes_file_remove            (const gchar *filename, gpointer data);
171 static void prefs_themes_file_install           (const gchar *filename, gpointer data);
172
173
174
175 static void prefs_themes_file_stats(const gchar *filename, gpointer data)
176 {
177 #ifdef G_OS_WIN32
178         GFile *f;
179         GFileInfo *fi;
180         GError *error = NULL;
181 #else
182         GStatBuf s;
183 #endif
184         goffset size;
185         DirInfo *di = (DirInfo *)data;
186         gint len;
187         gint i;
188
189 #ifdef G_OS_WIN32
190         f = g_file_new_for_path(filename);
191         fi = g_file_query_info(f, "standard::size,standard::type",
192                         G_FILE_QUERY_INFO_NONE, NULL, &error);
193         if (error != NULL) {
194                 g_warning(error->message);
195                 g_error_free(error);
196                 g_object_unref(f);
197                 return;
198         }
199         if (g_file_info_get_file_type(fi) != G_FILE_TYPE_REGULAR) {
200                 g_object_unref(fi);
201                 g_object_unref(f);
202                 return;
203         }
204         size = g_file_info_get_size(fi);
205         g_object_unref(fi);
206         g_object_unref(f);
207 #else
208         if ((i = g_stat(filename, &s)) != 0) {
209                 debug_print("g_stat on '%s' failed: %d\n", filename, i);
210                 return;
211         }
212         if (!S_ISREG(s.st_mode)) {
213                 return;
214         }
215         size = s.st_size;
216 #endif
217
218         di->bytes += size;
219         di->files++;
220         len = strlen(filename);
221         for (i = 0; (di->supported)[i] != NULL; ++i) {
222                 gint curlen = (di->length)[i];
223                 if (len <= curlen)
224                         continue;
225                 const gchar *extension = filename + (len - curlen);
226                 if (!strcmp(extension, (di->supported)[i])) {
227                         di->pixms++;
228                         break;
229                 }
230         }
231 }
232         
233 static void prefs_themes_file_remove(const gchar *filename, gpointer data)
234 {
235         gchar **status = (gchar **)data;
236         gchar *base;
237
238         if ((*status) != NULL)
239                 return;
240
241         base = g_path_get_basename(filename);
242         if (TRUE == is_dir_exist(filename)) {
243                 if (strcmp(base, ".") != 0 && strcmp(base, "..") != 0)
244                         g_warning("prefs_themes_file_remove(): subdir in theme dir skipped: '%s'.",
245                                                 base);
246         }
247         else if (0 != claws_unlink(filename)) {
248                 (*status) = g_strdup(filename);
249         }
250         g_free(base);
251 }
252
253 static void prefs_themes_file_install(const gchar *filename, gpointer data)
254 {
255         CopyInfo *ci = (CopyInfo *)data;
256         gchar *base;
257
258         if (ci->status != NULL)
259                 return;
260
261         base = g_path_get_basename(filename);
262         if (TRUE == is_dir_exist(filename)) {
263                 if (strcmp(base, ".") != 0 && strcmp(base, "..") !=0 )
264                         g_warning("prefs_themes_file_install(): subdir in theme dir skipped: '%s'.",
265                                                 base);
266         }
267         else {
268                 gchar *fulldest;
269
270                 fulldest = g_strconcat(ci->dest, G_DIR_SEPARATOR_S, base, NULL);
271
272                 if (0 != copy_file(filename, fulldest, FALSE)) {
273                         ci->status = g_strdup(filename);
274                 }
275                 g_free(fulldest);
276         }
277         g_free(base);
278 }
279
280 static void prefs_themes_foreach_file(const gchar *dirname, const FileFunc func, gpointer data)
281 {
282         const gchar *entry;
283         gchar *fullentry;
284         GDir *dp;
285         GError *error = NULL;
286
287         cm_return_if_fail(dirname != NULL);
288         cm_return_if_fail(func != NULL);
289
290         if ((dp = g_dir_open(dirname, 0, &error)) == NULL) {
291                 debug_print("couldn't open dir '%s': %s (%d)\n", dirname,
292                                 error->message, error->code);
293                 g_error_free(error);
294                 return;
295         }
296
297         while ((entry = g_dir_read_name(dp)) != NULL) {
298
299                 fullentry = g_strconcat(dirname, G_DIR_SEPARATOR_S, entry, NULL);
300
301                 (*func)(fullentry, data);
302
303                 g_free(fullentry);
304         }
305         g_dir_close(dp);
306 }
307
308 static gboolean prefs_themes_is_system_theme(const gchar *dirname)
309 {
310         gint len;
311         gchar *system_theme_dir;
312         gboolean is_sys = FALSE;
313
314         cm_return_val_if_fail(dirname != NULL, FALSE);
315
316         system_theme_dir = stock_pixmap_get_system_theme_dir_for_theme(NULL);
317         len = strlen(system_theme_dir);
318         if (strlen(dirname) > len && 0 == strncmp(dirname, system_theme_dir, len))
319                 is_sys = TRUE;
320
321         g_free(system_theme_dir);
322
323         return is_sys;
324 }
325
326 static void prefs_themes_set_themes_menu(GtkComboBox *combo, const ThemesData *tdata)
327 {
328         GtkListStore *store;
329         GtkTreeIter iter;
330         GList     *themes = tdata->names;
331         gint       i = 0, active = 0;
332         GList     *sorted_list = NULL;
333
334         cm_return_if_fail(combo != NULL);
335
336         /* sort theme data list by data name */
337         while (themes != NULL) {
338                 ThemeName *tname = (ThemeName *)(themes->data);
339
340                 sorted_list = g_list_insert_sorted(sorted_list, (gpointer)(tname),
341                                                    (GCompareFunc)prefs_themes_cmp_name);
342
343                 themes = g_list_next(themes);
344         }
345
346         store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
347
348         /* feed gtk_menu w/ sorted themes names */
349         themes = sorted_list;
350         while (themes != NULL) {
351                 ThemeName *tname = (ThemeName *)(themes->data);
352                 gchar     *tpath = (gchar *)(tname->item->data);
353
354                 gtk_list_store_append(store, &iter);
355                 gtk_list_store_set(store, &iter,
356                                    0, tname->name,
357                                    1, tname->item->data, -1);
358
359                 if (tdata->displayed != NULL && !strcmp2(tdata->displayed,tpath))
360                         active = i;
361                 ++i;
362
363                 themes = g_list_next(themes);
364         }
365
366         g_list_free(sorted_list);
367
368         g_signal_connect(G_OBJECT(combo), "changed",
369                          G_CALLBACK(prefs_themes_menu_item_activated_cb),
370                          NULL);
371
372         gtk_combo_box_set_model(combo, GTK_TREE_MODEL(store));
373         gtk_combo_box_set_active(combo, active);
374 }
375
376 static int prefs_themes_cmp_name(gconstpointer a_p, gconstpointer b_p)
377 {
378         /* compare two ThemeData structures by their name attribute */
379         return strcmp2((gchar *)(((ThemeName*)a_p)->name),
380                                         (gchar *)(((ThemeName*)b_p)->name));
381 }
382
383 static void prefs_themes_get_themes_and_names(ThemesData *tdata)
384 {
385         GList *tpaths;
386
387         cm_return_if_fail(tdata != NULL);
388
389         stock_pixmap_themes_list_free(tdata->themes);
390         prefs_themes_free_names(tdata);
391
392         tdata->themes = stock_pixmap_themes_list_new();
393
394         tpaths = tdata->themes;
395         while (tpaths != NULL) {
396                 ThemeName *name = g_new0(ThemeName, 1);
397                 gchar *sname = g_path_get_basename((const gchar *)(tpaths->data));
398
399                 if (IS_INTERNAL_THEME(sname))
400                         name->name = g_strdup(_("Default internal theme"));
401                 else
402                         name->name = g_strdup(sname);
403                 name->item = tpaths;
404
405                 tdata->names = g_list_append(tdata->names, name);
406                 if (!strcmp2(tpaths->data, prefs_common.pixmap_theme_path)) {
407                         tdata->displayed = (gchar *)tpaths->data;
408                 }
409                 tpaths = g_list_next(tpaths);
410                 g_free(sname);  
411         }
412 }
413
414 void prefs_themes_init(void)
415 {
416         ThemesData   *tdata;
417         ThemesPage   *page;
418         GList        *tpaths;
419         static gchar *path[3];
420
421         path[0] = _("Display");
422         path[1] = _("Themes");
423         path[2] = NULL;
424
425         debug_print("Creating preferences for themes...\n");
426
427         tdata = g_new0(ThemesData, 1);
428         prefs_themes_data = tdata;
429
430         prefs_themes_get_themes_and_names(tdata);
431
432         page = g_new0(ThemesPage, 1);
433
434         page->page.path = path;
435         page->page.create_widget = prefs_themes_create_widget;
436         page->page.destroy_widget = prefs_themes_destroy_widget;
437         page->page.save_page = prefs_themes_save;
438         page->page.weight = 130.0;
439         prefs_gtk_register_page((PrefsPage *) page);
440
441         tdata->page = page;
442
443         tpaths = g_list_first(tdata->themes);
444         if (tdata->displayed == NULL)
445                 tdata->displayed = (gchar *)(tpaths->data);
446 #ifdef HAVE_SVG
447         if (prefs_common.pixmap_scaling_auto)
448                 prefs_common.pixmap_scaling_ppi = prefs_themes_get_adjusted_ppi();
449 #endif
450 }
451
452 static void prefs_themes_free_names(ThemesData *tdata)
453 {
454         GList *names;
455
456         cm_return_if_fail(tdata != NULL);
457
458         if (tdata->names == NULL)
459                 return;
460
461         names = tdata->names;
462         while (names != NULL) {
463                 ThemeName *tn = (ThemeName *)(names->data);
464
465                 tn->item = NULL;
466                 g_free(tn->name);
467                 g_free(tn);
468
469                 names = g_list_next(names);
470         }
471         g_list_free(tdata->names);
472         tdata->names = NULL;
473 }
474
475 void prefs_themes_done(void)
476 {
477         ThemesData *tdata = prefs_themes_data;
478
479         debug_print("Finished preferences for themes.\n");
480
481         stock_pixmap_themes_list_free(tdata->themes);
482         prefs_themes_free_names(tdata); 
483         g_free(tdata->page);
484         g_free(tdata);
485 }
486
487 static void prefs_themes_btn_remove_clicked_cb(GtkWidget *widget, gpointer data)
488 {
489         ThemesData *tdata = prefs_themes_data;
490         gchar      *theme_str;
491         gchar      *alert_title = NULL;
492         AlertValue  val = 0;
493         gchar      *tmp = NULL;
494
495         theme_str = tdata->displayed;
496
497         tmp = g_path_get_basename(theme_str);
498
499         if (IS_SYSTEM_THEME(theme_str)) {
500                 if (!superuser_p()) {
501                         alertpanel_error(_("Only root can remove system themes"));
502                         return;
503                 }
504                 alert_title = g_strdup_printf(_("Remove system theme '%s'"), tmp);
505         }
506         if (NULL == alert_title) {
507                 alert_title = g_strdup_printf(_("Remove theme '%s'"), tmp);
508         }
509
510         g_free(tmp);
511
512         val = alertpanel(alert_title,
513                          _("Are you sure you want to remove this theme?"),
514                          GTK_STOCK_NO, GTK_STOCK_YES, NULL, ALERTFOCUS_FIRST);
515         g_free(alert_title);
516
517         if (G_ALERTALTERNATE == val) {
518                 gchar *status = NULL;
519
520                 prefs_themes_foreach_file(theme_str, prefs_themes_file_remove, &status); 
521                 if (0 != rmdir(theme_str)) {
522                         if (status != NULL) {
523                                 alertpanel_error(_("File %s failed\nwhile removing theme."), status);
524                                 g_free(status);
525                         }
526                         else
527                                 alertpanel_error(_("Removing theme directory failed."));
528                 }
529                 else {  
530                         alertpanel_notice(_("Theme removed successfully"));
531                         /* update interface back to first theme */
532                         prefs_themes_get_themes_and_names(tdata);
533                         prefs_themes_set_themes_menu(GTK_COMBO_BOX(tdata->page->op_menu), tdata);
534                         prefs_themes_display_global_stats(tdata);
535                         tdata->displayed = (gchar *)((g_list_first(tdata->themes))->data);
536                         prefs_themes_get_theme_info(tdata);
537                 }
538         }
539 }
540
541 static void prefs_themes_btn_install_clicked_cb(GtkWidget *widget, gpointer data)
542 {
543         gchar      *filename, *source;
544         gchar      *themeinfo, *themename;
545         gchar      *alert_title = NULL;
546         CopyInfo   *cinfo;
547         AlertValue  val = 0;
548         ThemesData *tdata = prefs_themes_data;
549
550         filename = filesel_select_file_open_folder(_("Select theme folder"), NULL);
551         if (filename == NULL) 
552                 return;
553
554         if (filename[strlen(filename) - 1] != G_DIR_SEPARATOR)
555                 filename = g_strconcat(filename, G_DIR_SEPARATOR_S, NULL);
556         else
557                 filename = g_strdup(filename);
558
559         cinfo = g_new0(CopyInfo, 1);
560         source = g_path_get_dirname(filename);
561         themename = g_path_get_basename(source);
562         debug_print("Installing '%s' theme from %s\n", themename, filename);
563
564         themeinfo = g_strconcat(source, G_DIR_SEPARATOR_S, THEMEINFO_FILE, NULL);
565         alert_title = g_strdup_printf(_("Install theme '%s'"), themename);
566         if (file_exist(themeinfo, FALSE) == FALSE) {
567                 val = alertpanel(alert_title,
568                                  _("This folder doesn't seem to be a theme folder.\nInstall anyway?"),
569                                  GTK_STOCK_NO, GTK_STOCK_YES, NULL, ALERTFOCUS_FIRST);
570                 if (G_ALERTALTERNATE != val)
571                         goto end_inst;
572         }
573         if (superuser_p ()) {
574                 val = alertpanel(alert_title,
575                                  _("Do you want to install theme for all users?"),
576                                  GTK_STOCK_NO, GTK_STOCK_YES, NULL, ALERTFOCUS_FIRST);
577                 switch (val) {
578                 case G_ALERTALTERNATE:
579                         cinfo->dest = stock_pixmap_get_system_theme_dir_for_theme(
580                                                 themename);
581                         break;
582                 case G_ALERTDEFAULT:
583                         break;
584                 default:
585                         goto end_inst;
586                 }
587         }
588         g_free(alert_title);
589         if (cinfo->dest == NULL) {
590                 cinfo->dest = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
591                                           PIXMAP_THEME_DIR, G_DIR_SEPARATOR_S,
592                                           themename, NULL);
593         }
594         if (TRUE == is_dir_exist(cinfo->dest)) {
595                 AlertValue val = alertpanel_full(_("Theme exists"),
596                                 _("A theme with the same name is\nalready installed in this location.\n\n"
597                                   "Do you want to replace it?"),
598                                 GTK_STOCK_CANCEL, _("Overwrite"), NULL, ALERTFOCUS_FIRST,
599                                 FALSE, NULL, ALERT_WARNING);
600                 if (val == G_ALERTALTERNATE) {
601                         if (remove_dir_recursive(cinfo->dest) < 0) {
602                                 alertpanel_error(_("Couldn't delete the old theme in %s."), cinfo->dest);
603                                 goto end_inst;
604                         }
605                 } else {
606                         goto end_inst;
607                 }
608         }
609         if (0 != make_dir_hier(cinfo->dest)) {
610                 alertpanel_error(_("Couldn't create destination directory %s."), cinfo->dest);
611                 goto end_inst;
612         }
613         prefs_themes_foreach_file(source, prefs_themes_file_install, cinfo);
614         if (cinfo->status == NULL) {
615                 GList *insted;
616
617                 /* update interface to show newly installed theme */
618                 prefs_themes_get_themes_and_names(tdata);
619                 insted = g_list_find_custom(tdata->themes, 
620                                             (gpointer)(cinfo->dest), 
621                                             (GCompareFunc)strcmp2);
622                 if (NULL != insted) {
623                         alertpanel_notice(_("Theme installed successfully."));
624                         tdata->displayed = (gchar *)(insted->data);
625                         prefs_themes_set_themes_menu(GTK_COMBO_BOX(tdata->page->op_menu), tdata);
626                         prefs_themes_display_global_stats(tdata);
627                         prefs_themes_get_theme_info(tdata);
628                 }
629                 else
630                         alertpanel_error(_("Failed installing theme"));
631         }
632         else
633                 alertpanel_error(_("File %s failed\nwhile installing theme."), cinfo->status);
634 end_inst:
635         g_free(cinfo->dest);
636         g_free(filename);
637         g_free(source);
638         g_free(themeinfo);
639         g_free(cinfo);
640         g_free(themename);
641 }
642
643 static void prefs_themes_menu_item_activated_cb(GtkWidget *widget, gpointer data)
644 {
645         ThemesData *tdata = prefs_themes_data;
646         gchar      *path;
647         GtkTreeModel *model;
648         GtkTreeIter iter;
649
650         cm_return_if_fail(gtk_combo_box_get_active_iter(GTK_COMBO_BOX(widget), &iter));
651
652         model = gtk_combo_box_get_model(GTK_COMBO_BOX(widget));
653         gtk_tree_model_get(model, &iter, 1, &path, -1);
654
655         tdata->displayed = path;
656         prefs_themes_get_theme_info(tdata);
657 }
658
659 #ifdef HAVE_SVG
660 #define MM_INCH 0.0393700787402
661 static gdouble prefs_themes_compute_ppi(GdkScreen *screen)
662 {
663         gdouble wp = gdk_screen_get_width(screen);
664         gdouble hp = gdk_screen_get_height(screen);
665         gdouble wi = gdk_screen_get_width_mm(screen);
666         gdouble hi = gdk_screen_get_height_mm(screen);
667         gdouble dp, di;
668
669         debug_print("screen is %f x %f pixels, %f x %f mm\n", wp, hp, wi, hi);
670
671         /* https://en.wikipedia.org/wiki/Pixel_density */
672         wi *= MM_INCH;
673         hi *= MM_INCH;
674         dp = sqrt(wp * wp + hp * hp);
675         di = sqrt(wi * wi + hi * hi);
676
677         return (di != 0.0)? dp / di: 0.0;
678 }
679
680 static gdouble prefs_themes_get_adjusted_ppi(void)
681 {
682         gdouble ppi, cppi;
683         GdkScreen * screen = gdk_screen_get_default();
684
685         if (screen == NULL) { /* oops! */
686                 g_warning("unable to get default GDK screen");
687                 return MIN_PPI;
688         }
689
690         ppi = gdk_screen_get_resolution(screen);
691         cppi = prefs_themes_compute_ppi(screen);
692         debug_print("returned PPI: %f / computed PPI: %f\n", ppi, cppi);
693         /*
694          gdk_screen_get_resolution doesn't seem to work well when running
695          on a remote display and returns the value of the local display.
696          height/width functions do this better, so we can compute a PPI
697          from them and take the highest value.
698         */
699         return MAX(ppi, cppi);
700 }
701
702 static void prefs_themes_checkbtn_enable_scaling_toggled_cb (GtkWidget *widget, gpointer data)
703 {
704         ThemesPage *page = (ThemesPage *) data;
705         gboolean enabled = gtk_toggle_button_get_active(
706                                 GTK_TOGGLE_BUTTON (widget));
707         gboolean automatic = gtk_toggle_button_get_active(
708                                 GTK_TOGGLE_BUTTON (page->checkbtn_scaling_auto));
709
710         gtk_widget_set_sensitive(page->checkbtn_scaling_auto, enabled);
711         gtk_widget_set_sensitive(page->spinbtn_scaling_ppi, enabled && !automatic);
712         gtk_widget_set_sensitive(page->label_scaling_ppi, enabled && !automatic);
713 }
714
715 static void prefs_themes_checkbtn_scaling_auto_toggled_cb(GtkWidget *widget, gpointer data)
716 {
717         ThemesPage *page = (ThemesPage *) data;
718         gboolean automatic = gtk_toggle_button_get_active(
719                                 GTK_TOGGLE_BUTTON (widget));
720
721         gtk_widget_set_sensitive(page->spinbtn_scaling_ppi, !automatic);
722         gtk_widget_set_sensitive(page->label_scaling_ppi, !automatic);
723
724         if (automatic) /* update PPI value */
725                 gtk_spin_button_set_value(
726                                 GTK_SPIN_BUTTON (page->spinbtn_scaling_ppi),
727                                 prefs_themes_get_adjusted_ppi());
728 }
729 #endif
730
731 static void prefs_themes_update_buttons(const ThemesData *tdata)
732 {
733         ThemesPage *theme = tdata->page;
734         gboolean    can_rem, can_use;
735
736         can_use = !IS_CURRENT_THEME(tdata->displayed);
737         can_rem = can_use && !IS_INTERNAL_THEME(tdata->displayed);
738
739         if (theme->btn_remove != NULL)
740                 gtk_widget_set_sensitive(theme->btn_remove, can_rem);
741 }
742
743 /* placeholders may already be utf8 (i18n) */
744 #define SET_LABEL_TEXT_UTF8(label, text)                                \
745 {                                                                       \
746         gchar *tmpstr;                                                  \
747                                                                         \
748         if (!g_utf8_validate(text, -1, NULL))                           \
749                 tmpstr = conv_codeset_strdup(text,                      \
750                         conv_get_locale_charset_str(),  CS_UTF_8);      \
751         else                                                            \
752                 tmpstr = g_strdup(text);                                \
753                                                                         \
754         gtk_label_set_text(GTK_LABEL(label), tmpstr);                   \
755         gtk_label_set_selectable(GTK_LABEL(label), TRUE);               \
756         g_free(tmpstr);                                                 \
757 }
758 static void prefs_themes_display_theme_info(ThemesData *tdata, const ThemeInfo *info)
759 {
760         ThemesPage *theme = tdata->page;
761         gchar *save_prefs_path;
762         gint   i;
763
764         SET_LABEL_TEXT_UTF8(theme->name,        info->name);
765         SET_LABEL_TEXT_UTF8(theme->author,      info->author);
766         SET_LABEL_TEXT_UTF8(theme->url,         info->url);
767         SET_LABEL_TEXT_UTF8(theme->status,      info->status);
768
769         save_prefs_path = prefs_common.pixmap_theme_path;
770         prefs_common.pixmap_theme_path = tdata->displayed;
771         for (i = 0; i < PREVIEW_ICONS; ++i) {
772                 stock_pixbuf_gdk(prefs_themes_icons[i], &(theme->pixbufs[i]));
773                 gtk_image_set_from_pixbuf(GTK_IMAGE(theme->icons[i]),
774                                 theme->pixbufs[i]);
775         }
776         prefs_common.pixmap_theme_path = save_prefs_path;
777
778         prefs_themes_update_buttons(tdata);
779 }
780 #undef SET_LABEL_TEXT_UTF8
781
782 static void prefs_themes_display_global_stats(const ThemesData *tdata)
783 {
784         ThemesPage *theme = tdata->page;
785         GList      *tnames = tdata->names;
786         gchar      *gstats;
787         gint        sys = 0;
788         gint        usr = 0;
789         gint        all = 0;
790
791         while (tnames != NULL) {
792                 ThemeName *tname = (ThemeName *)(tnames->data);
793                 gchar     *tpath = (gchar *)(tname->item->data);
794                 
795                 if (IS_SYSTEM_THEME(tpath)) 
796                         ++sys;
797                 else if (!IS_INTERNAL_THEME(tpath)) 
798                         ++usr;
799                 ++all;
800                 tnames = g_list_next(tnames);
801         }
802
803         gstats = g_strdup_printf(_("%d themes available (%d user, %d system, 1 internal)"),
804                                  all, usr, sys);
805         gtk_label_set_text(GTK_LABEL(theme->global), gstats);
806         gtk_label_set_justify (GTK_LABEL (theme->global), GTK_JUSTIFY_LEFT);
807         gtkut_widget_set_small_font_size (theme->global);
808         g_free(gstats);
809 }
810
811 #define INFOFILE_LINE_LEN 80
812
813 #define FGETS_INFOFILE_LINE() \
814         line[0] = '\0'; \
815         if (fgets(line, INFOFILE_LINE_LEN, finfo) != NULL && (len = strlen(line)) > 0) { \
816                 if (line[len - 1] == '\n') line[len - 1] = '\0'; \
817         } \
818         else { \
819                 g_strlcpy(line, _("Unknown"),sizeof(line)); \
820         }
821
822 static void prefs_themes_get_theme_info(ThemesData *tdata)
823 {
824         FILE  *finfo;
825         gchar *sinfo;
826         gchar *path;
827         gchar  line[INFOFILE_LINE_LEN];
828         gint   len;
829         ThemeInfo *info;
830         ThemesPage *theme = tdata->page;
831
832         cm_return_if_fail(theme != NULL);
833         path = tdata->displayed;
834         cm_return_if_fail(path != NULL);
835
836         debug_print("Getting theme info for %s\n", path);
837
838         info = g_new0(ThemeInfo, 1);
839
840         if (IS_INTERNAL_THEME(path)) {
841                 info->name = g_strdup(_("Default internal theme"));
842                 info->author = g_strdup(_("The Claws Mail Team"));
843                 info->url = g_strdup(HOMEPAGE_URI);
844                 info->status = g_strdup_printf(_("Internal theme has %d icons"), N_STOCK_PIXMAPS);
845         }
846         else {
847                 sinfo = g_strconcat(path, G_DIR_SEPARATOR_S, THEMEINFO_FILE, NULL);
848                 finfo = g_fopen(sinfo, "r");
849                 if (finfo == NULL) {
850                         info->name = g_strdup(_("No info file available for this theme"));
851                         info->author = g_strdup(_("Unknown"));
852                         info->url = g_strdup(_("Unknown"));
853                 }
854                 else {
855                         FGETS_INFOFILE_LINE()
856                         info->name = g_strdup(line);
857                         FGETS_INFOFILE_LINE()
858                         info->author = g_strdup(line);
859                         FGETS_INFOFILE_LINE()
860                         info->url = g_strdup(line);
861                 
862                         fclose(finfo);
863                 }
864                 g_free(sinfo);
865
866                 info->status = prefs_themes_get_theme_stats(path);
867                 if (info->status == NULL) {
868                         info->status = g_strdup(_("Error: couldn't get theme status"));
869                 }
870         }
871
872         prefs_themes_display_theme_info(tdata, info);
873
874         g_free(info->name);
875         g_free(info->author);
876         g_free(info->url);
877         g_free(info->status);
878
879         g_free(info);
880 }
881
882 #undef FGETS_INFOFILE_LINE
883
884 static gchar *prefs_themes_get_theme_stats(const gchar *dirname)
885 {
886         gchar   *stats;
887         DirInfo *dinfo;
888         gint     i;
889
890         dinfo = g_new0(DirInfo, 1);
891         dinfo->supported = stock_pixmap_theme_extensions();
892         for (i = 0; (dinfo->supported)[i] != NULL; ++i);
893         dinfo->length = g_malloc(i * sizeof(gint));
894         for (i = 0; (dinfo->supported)[i] != NULL; ++i) {
895                 (dinfo->length)[i] = strlen((dinfo->supported)[i]);
896         }
897         prefs_themes_foreach_file(dirname, prefs_themes_file_stats, dinfo);
898         stats = g_strdup_printf(_("%d files (%d icons), size: %s"),
899                                 dinfo->files, dinfo->pixms,
900                                 to_human_readable((goffset)dinfo->bytes));
901         g_free(dinfo->length);
902         g_free(dinfo);
903         return stats;
904 }
905
906 static void prefs_themes_create_widget(PrefsPage *page, GtkWindow *window, gpointer data)
907 {
908         ThemesPage *prefs_themes = (ThemesPage *)page;
909         ThemesData *tdata = prefs_themes_data;
910
911         GtkWidget *vbox1;
912         GtkWidget *frame1;
913         GtkWidget *vbox2;
914         GtkWidget *hbox3;
915         GtkWidget *menu_themes;
916         GtkWidget *btn_install;
917         GtkWidget *btn_more;
918         GtkWidget *label_global_status;
919         GtkWidget *frame_info;
920         GtkWidget *table1;
921         GtkWidget *label1;
922         GtkWidget *label2;
923         GtkWidget *label3;
924         GtkWidget *label_name;
925         GtkWidget *label_author;
926         GtkWidget *label_url;
927         GtkWidget *label4;
928         GtkWidget *label_status;
929         GtkWidget *frame_preview;
930         GtkWidget *hbox1;
931         GtkWidget *icon_1;
932         GtkWidget *icon_2;
933         GtkWidget *icon_3;
934         GtkWidget *icon_4;
935         GtkWidget *icon_5;
936         GtkWidget *icon_6;
937         GtkWidget *icon_7;
938         GtkWidget *frame_buttons;
939         GtkWidget *hbuttonbox1;
940         GtkWidget *btn_remove;
941         GtkCellRenderer *renderer;
942 #ifdef HAVE_SVG
943         GtkWidget *frame_scaling;
944         GtkWidget *checkbtn_enable_alpha;
945         GtkWidget *checkbtn_enable_scaling;
946         GtkWidget *checkbtn_scaling_auto;
947         GtkWidget *label_scaling_ppi;
948         GtkWidget *spinbtn_scaling_ppi;
949         GtkAdjustment *spinbtn_scaling_ppi_adj;
950 #endif
951
952         vbox1 = gtk_vbox_new (FALSE, VSPACING);
953         gtk_container_set_border_width (GTK_CONTAINER (vbox1), 5);
954         gtk_widget_show (vbox1);
955
956         vbox2 = gtkut_get_options_frame(vbox1, &frame1, _("Selector"));
957
958         hbox3 = gtk_hbox_new (FALSE, 5);
959         gtk_widget_show (hbox3);
960         gtk_box_pack_start (GTK_BOX (vbox2), hbox3, FALSE, FALSE, 0);
961         // gtk_container_set_border_width (GTK_CONTAINER (hbox3), 5);
962
963         menu_themes = gtk_combo_box_new();
964         gtk_widget_show (menu_themes);
965         gtk_box_pack_start (GTK_BOX (hbox3), menu_themes, FALSE, FALSE, 0);
966
967         btn_install = gtk_button_new_with_label (_("Install new..."));
968         gtk_widget_show (btn_install);
969         gtk_box_pack_start (GTK_BOX (hbox3), btn_install, FALSE, FALSE, 0);
970         gtk_widget_set_can_default (btn_install, TRUE);
971
972         btn_more = gtkut_get_link_btn((GtkWidget *)window, THEMES_URI, _("Get more..."));
973         gtk_widget_show (btn_more);
974         gtk_box_pack_start (GTK_BOX (hbox3), btn_more, FALSE, FALSE, 0);
975
976         label_global_status = gtk_label_new ("");
977         gtk_widget_show (label_global_status);
978         gtk_box_pack_start (GTK_BOX (vbox2), label_global_status, FALSE, FALSE, 0);
979         gtk_label_set_justify (GTK_LABEL (label_global_status), GTK_JUSTIFY_LEFT);
980         gtk_misc_set_alignment (GTK_MISC (label_global_status), 0, 0.5);
981         gtk_misc_set_padding (GTK_MISC (label_global_status), 1, 0);
982
983         PACK_FRAME(vbox1, frame_info, _("Information"));
984
985         table1 = gtk_table_new (4, 2, FALSE);
986         gtk_widget_show (table1);
987         gtk_container_add (GTK_CONTAINER (frame_info), table1);
988         gtk_container_set_border_width (GTK_CONTAINER (table1), 5);
989
990         label1 = gtk_label_new (_("Name"));
991         gtk_widget_show (label1);
992         gtk_table_attach (GTK_TABLE (table1), label1, 0, 1, 0, 1,
993                         (GtkAttachOptions) (GTK_FILL),
994                         (GtkAttachOptions) (0), 5, 4);
995         gtk_label_set_justify (GTK_LABEL (label1), GTK_JUSTIFY_LEFT);
996         gtk_misc_set_alignment (GTK_MISC (label1), 1, 0.5);
997
998         label2 = gtk_label_new (_("Author"));
999         gtk_widget_show (label2);
1000         gtk_table_attach (GTK_TABLE (table1), label2, 0, 1, 1, 2,
1001                         (GtkAttachOptions) (GTK_FILL),
1002                         (GtkAttachOptions) (0), 5, 4);
1003         gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_LEFT);
1004         gtk_misc_set_alignment (GTK_MISC (label2), 1, 0.5);
1005
1006         label3 = gtk_label_new (_("URL"));
1007         gtk_widget_show (label3);
1008         gtk_table_attach (GTK_TABLE (table1), label3, 0, 1, 2, 3,
1009                         (GtkAttachOptions) (GTK_FILL),
1010                         (GtkAttachOptions) (0), 5, 4);
1011         gtk_misc_set_alignment (GTK_MISC (label3), 1, 0.5);
1012
1013         label_name = gtk_label_new ("");
1014         gtk_widget_show (label_name);
1015         gtk_table_attach (GTK_TABLE (table1), label_name, 1, 2, 0, 1,
1016                         (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
1017                         (GtkAttachOptions) (0), 5, 0);
1018         gtk_misc_set_alignment (GTK_MISC (label_name), 0, 0.5);
1019
1020         label_author = gtk_label_new ("");
1021         gtk_widget_show (label_author);
1022         gtk_table_attach (GTK_TABLE (table1), label_author, 1, 2, 1, 2,
1023                         (GtkAttachOptions) (GTK_FILL),
1024                         (GtkAttachOptions) (0), 5, 0);
1025         gtk_misc_set_alignment (GTK_MISC (label_author), 0, 0.5);
1026
1027         label_url = gtk_label_new ("");
1028         gtk_widget_show (label_url);
1029         gtk_table_attach (GTK_TABLE (table1), label_url, 1, 2, 2, 3,
1030                         (GtkAttachOptions) (GTK_FILL),
1031                         (GtkAttachOptions) (0), 5, 0);
1032         gtk_misc_set_alignment (GTK_MISC (label_url), 0, 0.5);
1033
1034         label4 = gtk_label_new (_("Status"));
1035         gtk_widget_show (label4);
1036         gtk_table_attach (GTK_TABLE (table1), label4, 0, 1, 3, 4,
1037                         (GtkAttachOptions) (GTK_FILL),
1038                         (GtkAttachOptions) (0), 5, 4);
1039         gtk_misc_set_alignment (GTK_MISC (label4), 1, 0.5);
1040
1041         label_status = gtk_label_new ("");
1042         gtk_widget_show (label_status);
1043         gtk_table_attach (GTK_TABLE (table1), label_status, 1, 2, 3, 4,
1044                         (GtkAttachOptions) (GTK_FILL),
1045                         (GtkAttachOptions) (0), 5, 0);
1046         gtk_misc_set_alignment (GTK_MISC (label_status), 0, 0.5);
1047
1048         PACK_FRAME(vbox1, frame_preview, _("Preview"));
1049
1050         hbox1 = gtk_hbox_new (FALSE, 0);
1051         gtk_widget_show (hbox1);
1052         gtk_container_add (GTK_CONTAINER (frame_preview), hbox1);
1053         gtk_container_set_border_width (GTK_CONTAINER (hbox1), 5);
1054
1055         icon_1 = gtk_image_new();
1056         gtk_widget_show (icon_1);
1057         gtk_box_pack_start (GTK_BOX (hbox1), icon_1, TRUE, TRUE, 2);
1058         gtk_misc_set_padding (GTK_MISC (icon_1), 0, 5);
1059
1060         icon_2 = gtk_image_new();
1061         gtk_widget_show (icon_2);
1062         gtk_box_pack_start (GTK_BOX (hbox1), icon_2, TRUE, TRUE, 2);
1063         gtk_misc_set_padding (GTK_MISC (icon_2), 0, 5);
1064
1065         icon_3 = gtk_image_new();
1066         gtk_widget_show (icon_3);
1067         gtk_box_pack_start (GTK_BOX (hbox1), icon_3, TRUE, TRUE, 2);
1068         gtk_misc_set_padding (GTK_MISC (icon_3), 0, 5);
1069
1070         icon_4 = gtk_image_new();
1071         gtk_widget_show (icon_4);
1072         gtk_box_pack_start (GTK_BOX (hbox1), icon_4, TRUE, TRUE, 2);
1073         gtk_misc_set_padding (GTK_MISC (icon_4), 0, 5);
1074
1075         icon_5 = gtk_image_new();
1076         gtk_widget_show (icon_5);
1077         gtk_box_pack_start (GTK_BOX (hbox1), icon_5, TRUE, TRUE, 2);
1078         gtk_misc_set_padding (GTK_MISC (icon_5), 0, 5);
1079
1080         icon_6 = gtk_image_new();
1081         gtk_widget_show (icon_6);
1082         gtk_box_pack_start (GTK_BOX (hbox1), icon_6, TRUE, TRUE, 2);
1083         gtk_misc_set_padding (GTK_MISC (icon_6), 0, 5);
1084
1085         icon_7 = gtk_image_new();
1086         gtk_widget_show (icon_7);
1087         gtk_box_pack_start (GTK_BOX (hbox1), icon_7, TRUE, TRUE, 2);
1088         gtk_misc_set_padding (GTK_MISC (icon_7), 0, 5);
1089
1090         PACK_FRAME(vbox1, frame_buttons, _("Actions"));
1091
1092         hbuttonbox1 = gtk_hbutton_box_new ();
1093         gtk_widget_show (hbuttonbox1);
1094         gtk_container_add (GTK_CONTAINER (frame_buttons), hbuttonbox1);
1095         gtk_container_set_border_width (GTK_CONTAINER (hbuttonbox1), 8);
1096         gtk_button_box_set_layout (GTK_BUTTON_BOX (hbuttonbox1), GTK_BUTTONBOX_START);
1097         gtk_box_set_spacing (GTK_BOX (hbuttonbox1), 5);
1098
1099         btn_remove = gtk_button_new_with_label (_("Remove"));
1100         gtk_widget_show (btn_remove);
1101         gtk_container_add (GTK_CONTAINER (hbuttonbox1), btn_remove);
1102         gtk_widget_set_can_default (btn_remove, TRUE);
1103
1104 #ifdef HAVE_SVG
1105         PACK_FRAME(vbox1, frame_scaling, _("SVG rendering"));
1106
1107         vbox2 = gtk_vbox_new (FALSE, VSPACING);
1108         gtk_widget_show (vbox2);
1109         gtk_container_set_border_width (GTK_CONTAINER (vbox2), 5);
1110         gtk_container_add (GTK_CONTAINER (frame_scaling), vbox2);
1111
1112         PACK_CHECK_BUTTON(vbox2, checkbtn_enable_alpha, _("Enable alpha channel"));
1113         PACK_CHECK_BUTTON(vbox2, checkbtn_enable_scaling, _("Force scaling"));
1114         PACK_CHECK_BUTTON(vbox2, checkbtn_scaling_auto, _("Automatic"));
1115
1116         hbox3 = gtk_hbox_new (FALSE, 5);
1117         gtk_widget_show (hbox3);
1118
1119         label_scaling_ppi = gtk_label_new (_("Pixels per inch (PPI)"));
1120         gtk_widget_show (label_scaling_ppi);
1121         gtk_box_pack_start (GTK_BOX (hbox3), label_scaling_ppi,
1122                         FALSE, FALSE, 5);
1123
1124         spinbtn_scaling_ppi_adj = GTK_ADJUSTMENT(
1125                 gtk_adjustment_new (MIN_PPI, MIN_PPI, MAX_PPI, 1, 10, 0));
1126         spinbtn_scaling_ppi = gtk_spin_button_new(
1127                         spinbtn_scaling_ppi_adj, 1.0, 0);
1128         gtk_widget_show (spinbtn_scaling_ppi);
1129         gtk_box_pack_start (GTK_BOX (hbox3), spinbtn_scaling_ppi,
1130                         FALSE, FALSE, 5);
1131
1132         gtk_box_pack_start (GTK_BOX (vbox2), hbox3, FALSE, FALSE, 0);
1133
1134         /* initialize widget values */
1135         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (checkbtn_enable_alpha),
1136                         prefs_common.enable_alpha_svg);
1137         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (checkbtn_enable_scaling),
1138                         prefs_common.enable_pixmap_scaling);
1139         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (checkbtn_scaling_auto),
1140                         prefs_common.pixmap_scaling_auto);
1141         gtk_spin_button_set_value(GTK_SPIN_BUTTON (spinbtn_scaling_ppi),
1142                         prefs_common.pixmap_scaling_ppi);
1143
1144         /* sensitivity */
1145         gtk_widget_set_sensitive(checkbtn_scaling_auto,
1146                         prefs_common.enable_pixmap_scaling);
1147         gtk_widget_set_sensitive(spinbtn_scaling_ppi,
1148                         prefs_common.enable_pixmap_scaling
1149                                 && !prefs_common.pixmap_scaling_auto);
1150         gtk_widget_set_sensitive(label_scaling_ppi,
1151                         prefs_common.enable_pixmap_scaling
1152                                 && !prefs_common.pixmap_scaling_auto);
1153
1154         /* signals */
1155         g_signal_connect(G_OBJECT(checkbtn_enable_scaling), "toggled",
1156                          G_CALLBACK(prefs_themes_checkbtn_enable_scaling_toggled_cb),
1157                          prefs_themes);
1158         g_signal_connect(G_OBJECT(checkbtn_scaling_auto), "toggled",
1159                          G_CALLBACK(prefs_themes_checkbtn_scaling_auto_toggled_cb),
1160                          prefs_themes);
1161 #endif
1162
1163         g_signal_connect(G_OBJECT(btn_remove), "clicked",
1164                          G_CALLBACK(prefs_themes_btn_remove_clicked_cb),
1165                          NULL);
1166         g_signal_connect(G_OBJECT(btn_install), "clicked",
1167                          G_CALLBACK(prefs_themes_btn_install_clicked_cb),
1168                          NULL);
1169
1170         prefs_themes->window = GTK_WIDGET(window);
1171
1172         prefs_themes->name   = label_name;
1173         prefs_themes->author = label_author;
1174         prefs_themes->url    = label_url;
1175         prefs_themes->status = label_status;
1176         prefs_themes->global = label_global_status;
1177
1178         prefs_themes->icons[0] = icon_1;
1179         prefs_themes->icons[1] = icon_2;
1180         prefs_themes->icons[2] = icon_3;
1181         prefs_themes->icons[3] = icon_4;
1182         prefs_themes->icons[4] = icon_5;
1183         prefs_themes->icons[5] = icon_6;
1184         prefs_themes->icons[6] = icon_7;
1185
1186         prefs_themes->btn_remove  = btn_remove;
1187         prefs_themes->btn_install = btn_install;
1188         prefs_themes->btn_more    = btn_more;
1189
1190         prefs_themes->op_menu     = menu_themes;
1191
1192 #ifdef HAVE_SVG
1193         prefs_themes->checkbtn_enable_alpha   = checkbtn_enable_alpha;
1194         prefs_themes->checkbtn_enable_scaling = checkbtn_enable_scaling;
1195         prefs_themes->checkbtn_scaling_auto   = checkbtn_scaling_auto;
1196         prefs_themes->label_scaling_ppi       = label_scaling_ppi;
1197         prefs_themes->spinbtn_scaling_ppi     = spinbtn_scaling_ppi;
1198 #endif
1199
1200         prefs_themes->page.widget = vbox1;
1201         
1202         prefs_themes_set_themes_menu(GTK_COMBO_BOX(menu_themes), tdata);
1203         renderer = gtk_cell_renderer_text_new();
1204         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(menu_themes), renderer, TRUE);
1205         gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(menu_themes), renderer,
1206                                         "text", 0, NULL);
1207
1208         prefs_themes_get_theme_info(tdata);
1209         prefs_themes_display_global_stats(tdata);
1210 }
1211
1212 static void prefs_themes_destroy_widget(PrefsPage *page)
1213 {
1214         /* ThemesPage *theme = (ThemesPage *)page; */
1215 }
1216
1217 static void prefs_themes_save(PrefsPage *page)
1218 {
1219         ThemesData *tdata = prefs_themes_data;
1220         gchar      *theme_str = tdata->displayed;
1221 #ifdef HAVE_SVG
1222         ThemesPage *tpage = (ThemesPage *) page;
1223         gboolean alpha = prefs_common.enable_alpha_svg;
1224         gboolean scaling = prefs_common.enable_pixmap_scaling;
1225         gboolean scaling_auto = prefs_common.pixmap_scaling_auto;
1226         gint scaling_ppi = prefs_common.pixmap_scaling_ppi;
1227
1228         prefs_common.enable_alpha_svg = gtk_toggle_button_get_active(
1229                 GTK_TOGGLE_BUTTON (tpage->checkbtn_enable_alpha));
1230         prefs_common.enable_pixmap_scaling = gtk_toggle_button_get_active(
1231                 GTK_TOGGLE_BUTTON (tpage->checkbtn_enable_scaling));
1232         prefs_common.pixmap_scaling_auto = gtk_toggle_button_get_active(
1233                 GTK_TOGGLE_BUTTON (tpage->checkbtn_scaling_auto));
1234         prefs_common.pixmap_scaling_ppi = gtk_spin_button_get_value_as_int (
1235                 GTK_SPIN_BUTTON (tpage->spinbtn_scaling_ppi));
1236 #endif
1237
1238         if (!IS_CURRENT_THEME(theme_str)) {
1239                 debug_print("Changing theme to %s\n", theme_str);
1240                 g_free(prefs_common.pixmap_theme_path);
1241
1242                 prefs_common.pixmap_theme_path = g_strdup(theme_str);
1243
1244                 main_window_reflect_prefs_all_real(TRUE);
1245                 compose_reflect_prefs_pixmap_theme();
1246                 addrcompl_reflect_prefs_pixmap_theme();
1247
1248                 prefs_themes_update_buttons(tdata);
1249         }
1250 #ifdef HAVE_SVG
1251         else if (scaling != prefs_common.enable_pixmap_scaling
1252                         || alpha != prefs_common.enable_alpha_svg
1253                         || (scaling_auto != prefs_common.pixmap_scaling_auto
1254                                 && scaling_ppi != prefs_common.pixmap_scaling_ppi)) {
1255                 /* same theme, different scaling options */
1256                 debug_print("Updating theme scaling\n");
1257                 stock_pixmap_invalidate_all_icons();
1258                 main_window_reflect_prefs_all_real(TRUE);
1259                 compose_reflect_prefs_pixmap_theme();
1260                 addrcompl_reflect_prefs_pixmap_theme();
1261         }
1262 #endif
1263 }
1264