1 /*  Copyright (c) 2003-2014 Xfce Development Team
2  *
3  * This program is free software; you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation; either version 2 of the License, or
6  * (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
16  * Boston, MA 02110-1301, USA.
17  */
18 
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22 
23 #include <glib.h>
24 #include <gtk/gtk.h>
25 #include <string.h>
26 
27 #include <libxfce4util/libxfce4util.h>
28 
29 #include "weather-icon.h"
30 #include "weather-debug.h"
31 
32 #define DEFAULT_W_THEME "liquid"
33 #define THEME_INFO_FILE "theme.info"
34 #define ICON_DIR_SMALL "22"
35 #define ICON_DIR_MEDIUM "48"
36 #define ICON_DIR_BIG "128"
37 
38 
39 static const gchar *symbol_names[] = {
40     "NODATA",
41     "SUN",
42     "LIGHTCLOUD",
43     "PARTLYCLOUD",
44     "CLOUD",
45     "LIGHTRAINSUN",
46     "LIGHTRAINTHUNDERSUN",
47     "SLEETSUN",
48     "SNOWSUN",
49     "LIGHTRAIN",
50     "RAIN",
51     "RAINTHUNDER",
52     "SLEET",
53     "SNOW",
54     "SNOWTHUNDER",
55     "FOG",
56     "SUN",
57     "LIGHTCLOUD",
58     "LIGHTRAINSUN",
59     "SNOWSUN",
60     "SLEETSUNTHUNDER",
61     "SNOWSUNTHUNDER",
62     "LIGHTRAINTHUNDER",
63     "SLEETTHUNDER"
64 };
65 
66 
get_symbol_name(gint idx)67 const gchar *get_symbol_name(gint idx)
68 {
69     return symbol_names[idx];
70 }
71 
72 
73 static gboolean
icon_missing(const icon_theme * theme,const gchar * sizedir,const gchar * symbol_name,const gchar * suffix)74 icon_missing(const icon_theme *theme,
75              const gchar *sizedir,
76              const gchar *symbol_name,
77              const gchar *suffix)
78 {
79     gchar *missing, *icon;
80     guint i;
81 
82     icon = g_strconcat(sizedir, G_DIR_SEPARATOR_S, symbol_name, suffix, NULL);
83     for (i = 0; i < theme->missing_icons->len; i++) {
84         missing = g_array_index(theme->missing_icons, gchar *, i);
85         if (G_UNLIKELY(missing == NULL))
86             continue;
87         if (!strcmp(missing, icon)) {
88             g_free(icon);
89             return TRUE;
90         }
91     }
92     g_free(icon);
93     return FALSE;
94 }
95 
96 
97 static void
remember_missing_icon(const icon_theme * theme,const gchar * sizedir,const gchar * symbol_name,const gchar * suffix)98 remember_missing_icon(const icon_theme *theme,
99                       const gchar *sizedir,
100                       const gchar *symbol_name,
101                       const gchar *suffix)
102 {
103     gchar *icon;
104 
105     icon = g_strconcat(sizedir, G_DIR_SEPARATOR_S, symbol_name, suffix, NULL);
106     g_array_append_val(theme->missing_icons, icon);
107     weather_debug("Remembered missing icon %s.", icon);
108 }
109 
110 
111 static const gchar *
get_icon_sizedir(const gint size)112 get_icon_sizedir(const gint size)
113 {
114     const gchar *sizedir;
115 
116     if (size < 24)
117         sizedir = ICON_DIR_SMALL;
118     else if (size < 49)
119         sizedir = ICON_DIR_MEDIUM;
120     else
121         sizedir = ICON_DIR_BIG;
122     return sizedir;
123 }
124 
125 
126 static gchar *
make_icon_filename(const icon_theme * theme,const gchar * sizedir,const gchar * symbol_name,const gchar * suffix)127 make_icon_filename(const icon_theme *theme,
128                    const gchar *sizedir,
129                    const gchar *symbol_name,
130                    const gchar *suffix)
131 {
132     gchar *filename, *symlow;
133 
134     symlow = g_ascii_strdown(symbol_name, -1);
135     filename = g_strconcat(theme->dir, G_DIR_SEPARATOR_S, sizedir,
136                            G_DIR_SEPARATOR_S, symlow, suffix, ".png", NULL);
137     g_free(symlow);
138     return filename;
139 }
140 
141 
142 static gchar *
make_fallback_icon_filename(const gchar * sizedir)143 make_fallback_icon_filename(const gchar *sizedir)
144 {
145     gchar *filename, *symlow;
146 
147     symlow = g_ascii_strdown(symbol_names[SYMBOL_NODATA], -1);
148     filename = g_strconcat(THEMESDIR, G_DIR_SEPARATOR_S, DEFAULT_W_THEME,
149                            G_DIR_SEPARATOR_S, sizedir, G_DIR_SEPARATOR_S,
150                            symlow, ".png", NULL);
151     g_free(symlow);
152     return filename;
153 }
154 
155 
156 static GdkPixbuf *
quiet_gdk_pixbuf_new_from_file_at_scale(const char * filename,int width,int height,gboolean preserve_aspect_ratio,GError ** error)157 quiet_gdk_pixbuf_new_from_file_at_scale(const char *filename,
158                                         int width,
159                                         int height,
160                                         gboolean preserve_aspect_ratio,
161                                         GError **error)
162 {
163     if (height == 0)
164         height = 1;
165 
166     if (width == 0)
167         width = 1;
168 
169     return gdk_pixbuf_new_from_file_at_scale (filename, width, height, preserve_aspect_ratio, error);
170 }
171 
172 
173 GdkPixbuf *
get_icon(const icon_theme * theme,const gchar * symbol_name,const gint size,const gboolean night)174 get_icon(const icon_theme *theme,
175          const gchar *symbol_name,
176          const gint size,
177          const gboolean night)
178 {
179     GdkPixbuf *image = NULL;
180     const gchar *sizedir;
181     gchar *filename = NULL, *suffix = "";
182     GError *error = NULL;
183 
184     g_assert(theme != NULL);
185     if (G_UNLIKELY(!theme)) {
186         g_warning(_("No icon theme!"));
187         return NULL;
188     }
189 
190     /* choose icons from directory best matching the requested size */
191     sizedir = get_icon_sizedir(size);
192 
193     if (symbol_name == NULL || strlen(symbol_name) == 0)
194         symbol_name = symbol_names[SYMBOL_NODATA];
195     else if (night)
196         suffix = "-night";
197 
198     /* check whether icon has been verified to be missing before */
199     if (!icon_missing(theme, sizedir, symbol_name, suffix)) {
200         filename = make_icon_filename(theme, sizedir, symbol_name, suffix);
201         image = quiet_gdk_pixbuf_new_from_file_at_scale(filename, size, size, TRUE, &error);
202     }
203 
204     if (image == NULL) {
205         /* remember failure for future lookups */
206         if (error) {
207             g_warning ("Failed to load pixbuf: %s", error->message);
208             g_error_free (error);
209         }
210         if (filename) {
211             weather_debug("Unable to open image: %s", filename);
212             remember_missing_icon(theme, sizedir, symbol_name, suffix);
213             g_free(filename);
214             filename = NULL;
215         }
216 
217         if (strcmp(symbol_name, symbol_names[SYMBOL_NODATA]))
218             if (night)
219                 /* maybe there is no night icon, so fallback to using day icon... */
220                 return get_icon(theme, symbol_name, size, FALSE);
221             else
222                 /* ... or use NODATA if we tried that already */
223                 return get_icon(theme, NULL, size, FALSE);
224         else {
225             /* last chance: get NODATA icon from standard theme */
226             filename = make_fallback_icon_filename(sizedir);
227             image = quiet_gdk_pixbuf_new_from_file_at_scale(filename, size, size,
228                                                             TRUE, NULL);
229             if (G_UNLIKELY(image == NULL))
230                 g_warning("Failed to open fallback icon from standard theme: %s",
231                           filename);
232         }
233     }
234     g_free(filename);
235 
236     return image;
237 }
238 
239 
240 /*
241  * Create a new icon theme struct, initializing caches to undefined.
242  */
243 static icon_theme *
make_icon_theme(void)244 make_icon_theme(void)
245 {
246     icon_theme *theme = g_slice_new0(icon_theme);
247 
248     g_assert(theme != NULL);
249     if (theme == NULL)
250         return NULL;
251     theme->missing_icons = g_array_new(FALSE, TRUE, sizeof(gchar *));
252     return theme;
253 }
254 
255 
256 /*
257  * Load icon theme info from theme info file given a directory.
258  */
259 icon_theme *
icon_theme_load_info(const gchar * dir)260 icon_theme_load_info(const gchar *dir)
261 {
262     XfceRc *rc;
263     icon_theme *theme = NULL;
264     gchar *filename;
265     const gchar *value;
266 
267     g_assert(dir != NULL);
268     if (G_UNLIKELY(dir == NULL))
269         return NULL;
270 
271     filename = g_build_filename(dir, G_DIR_SEPARATOR_S, THEME_INFO_FILE, NULL);
272 
273     if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
274         rc = xfce_rc_simple_open(filename, TRUE);
275         g_free(filename);
276         filename = NULL;
277 
278         if (!rc)
279             return NULL;
280 
281         if ((theme = make_icon_theme()) == NULL) {
282             xfce_rc_close(rc);
283             return NULL;
284         }
285 
286         theme->dir = g_strdup(dir);
287 
288         value = xfce_rc_read_entry(rc, "Name", NULL);
289         if (value)
290             theme->name = g_strdup(value);
291         else {
292             /* Use directory name as fallback */
293             filename = g_path_get_dirname(dir);
294             if (G_LIKELY(strcmp(filename, "."))) {
295                 theme->dir = g_strdup(dir);
296                 theme->name = g_strdup(filename);
297                 weather_debug("No Name found in theme info file, "
298                               "using directory name %s as fallback.", dir);
299                 g_free(filename);
300                 filename = NULL;
301             } else { /* some weird error, not safe to proceed */
302                 weather_debug("Some weird error, not safe to proceed. "
303                               "Abort loading icon theme from %s.", dir);
304                 icon_theme_free(theme);
305                 g_free(filename);
306                 xfce_rc_close(rc);
307                 return NULL;
308             }
309         }
310 
311         value = xfce_rc_read_entry(rc, "Author", NULL);
312         if (value)
313             theme->author = g_strdup(value);
314 
315         value = xfce_rc_read_entry(rc, "Description", NULL);
316         if (value)
317             theme->description = g_strdup(value);
318 
319         value = xfce_rc_read_entry(rc, "License", NULL);
320         if (value)
321             theme->license = g_strdup(value);
322         xfce_rc_close(rc);
323     }
324 
325     weather_dump(weather_dump_icon_theme, theme);
326     return theme;
327 }
328 
329 
330 /*
331  * Load theme from a directory, fallback to standard theme on failure
332  * or when dir is NULL.
333  */
334 icon_theme *
icon_theme_load(const gchar * dir)335 icon_theme_load(const gchar *dir)
336 {
337     icon_theme *theme = NULL;
338     gchar *default_dir;
339 
340     if (dir != NULL) {
341         weather_debug("Loading icon theme from %s.", dir);
342         if ((theme = icon_theme_load_info(dir)) != NULL) {
343             weather_debug("Successfully loaded theme from %s.", dir);
344             return theme;
345         } else
346             weather_debug("Error loading theme from %s.", dir);
347     }
348 
349     /* on failure try the standard theme */
350     if (theme == NULL) {
351         default_dir = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s",
352                                       THEMESDIR, DEFAULT_W_THEME);
353         weather_debug("Loading standard icon theme from %s.", default_dir);
354         if ((theme = icon_theme_load_info(default_dir)) != NULL)
355             weather_debug("Successfully loaded theme from %s.", default_dir);
356         else
357             weather_debug("Error loading standard theme from %s.", default_dir);
358         g_free(default_dir);
359     }
360     return theme;
361 }
362 
363 
364 /*
365  * Compare two icon_theme structs using their path names, returning
366  * the result as a qsort()-style comparison function (less than zero
367  * for first arg is less than second arg, zero for equal, greater zero
368  * if first arg is greater than second arg).
369  */
370 static gint
icon_theme_compare(gconstpointer a,gconstpointer b)371 icon_theme_compare(gconstpointer a,
372                    gconstpointer b)
373 {
374     icon_theme *it1 = *(icon_theme **) a;
375     icon_theme *it2 = *(icon_theme **) b;
376 
377     if (G_UNLIKELY(it1 == NULL && it2 == NULL))
378         return 0;
379     if (G_UNLIKELY(it1 == NULL))
380         return -1;
381     if (G_UNLIKELY(it2 == NULL))
382         return 1;
383 
384     return g_strcmp0(it1->dir, it2->dir);
385 }
386 
387 
388 static GArray *
find_themes_in_dir(const gchar * path)389 find_themes_in_dir(const gchar *path)
390 {
391     GDir *dir;
392 
393     g_assert(path != NULL);
394     if (G_UNLIKELY(path == NULL))
395         return NULL;
396 
397     weather_debug("Looking for icon themes in %s.", path);
398     dir = g_dir_open(path, 0, NULL);
399     if (dir) {
400         GArray *themes;
401         icon_theme *theme;
402         gchar *themedir;
403         const gchar *dirname;
404 
405         themes = g_array_new(FALSE, TRUE, sizeof(icon_theme *));
406 
407         while ((dirname = g_dir_read_name(dir))) {
408             themedir = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s",
409                                        path, dirname);
410             theme = icon_theme_load_info(themedir);
411             g_free(themedir);
412 
413             if (theme) {
414                 themes = g_array_append_val(themes, theme);
415                 weather_debug("Found icon theme %s", theme->dir);
416                 weather_dump(weather_dump_icon_theme, theme);
417             }
418         }
419         g_dir_close(dir);
420         weather_debug("Found %d icon theme(s) in %s.", themes->len, path);
421         g_array_sort(themes, (GCompareFunc) icon_theme_compare);
422         return themes;
423     } else {
424         weather_debug("Could not list directory %s.", path);
425         return NULL;
426     }
427 }
428 
429 
430 /*
431  * Returns the user icon theme directory as a string which needs to be
432  * freed by the calling function.
433  */
434 gchar *
get_user_icons_dir(void)435 get_user_icons_dir(void)
436 {
437     return g_strconcat(g_get_user_config_dir(), G_DIR_SEPARATOR_S,
438                        "xfce4", G_DIR_SEPARATOR_S, "weather",
439                        G_DIR_SEPARATOR_S, "icons", NULL);
440 }
441 
442 
443 /*
444  * Find all available themes in user's config dir and at the install
445  * location.
446  */
447 GArray *
find_icon_themes(void)448 find_icon_themes(void)
449 {
450     GArray *themes, *found;
451     gchar *dir;
452 
453     themes = g_array_new(FALSE, TRUE, sizeof(icon_theme *));
454 
455     /* look in user directory first */
456     dir = get_user_icons_dir();
457     found = find_themes_in_dir(dir);
458 
459     if (found) {
460         if (found->len > 0)
461             themes = g_array_append_vals(themes, found->data, found->len);
462         g_array_free(found, FALSE);
463     }
464 
465     /* next find themes in system directory */
466     found = find_themes_in_dir(THEMESDIR);
467     if (found) {
468         if (found->len > 0)
469             themes = g_array_append_vals(themes, found->data, found->len);
470         g_array_free(found, FALSE);
471     }
472 
473     weather_debug("Found %d icon themes in total.", themes->len, dir);
474     g_free(dir);
475     return themes;
476 }
477 
478 
479 icon_theme *
icon_theme_copy(icon_theme * src)480 icon_theme_copy(icon_theme *src)
481 {
482     icon_theme *dst;
483 
484     if (G_UNLIKELY(src == NULL))
485         return NULL;
486 
487     dst = make_icon_theme();
488     if (G_UNLIKELY(dst == NULL))
489         return NULL;
490 
491     if (src->dir)
492         dst->dir = g_strdup(src->dir);
493     if (src->name)
494         dst->name = g_strdup(src->name);
495     if (src->author)
496         dst->author = g_strdup(src->author);
497     if (src->description)
498         dst->description = g_strdup(src->description);
499     if (src->license)
500         dst->license = g_strdup(src->license);
501     return dst;
502 }
503 
504 
505 void
icon_theme_free(icon_theme * theme)506 icon_theme_free(icon_theme *theme)
507 {
508     gchar *missing;
509     guint i;
510 
511     g_assert(theme != NULL);
512     if (G_UNLIKELY(theme == NULL))
513         return;
514     g_free(theme->dir);
515     g_free(theme->name);
516     g_free(theme->author);
517     g_free(theme->description);
518     g_free(theme->license);
519     for (i = 0; i < theme->missing_icons->len; i++) {
520         missing = g_array_index(theme->missing_icons, gchar *, i);
521         g_free(missing);
522     }
523     g_array_free(theme->missing_icons, FALSE);
524     g_slice_free(icon_theme, theme);
525 }
526