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