1 /*
2 Copyright © 2004 Callum McKenzie
3 Copyright © 2007, 2008, 2009 Christian Persch
4
5 This library 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 /* Authors: Callum McKenzie <callum@physics.otago.ac.nz> */
20
21 #include <config.h>
22
23 #include <string.h>
24 #include <glib.h>
25 #include <gdk-pixbuf/gdk-pixbuf.h>
26 #include <gtk/gtk.h>
27
28 #ifdef GDK_WINDOWING_X11
29 #include <gdk/gdkx.h>
30 #endif
31
32 #include "ar-debug.h"
33 #include "ar-profile.h"
34 #include "ar-runtime.h"
35
36 #include "ar-card-themes.h"
37 #include "ar-card-theme-private.h"
38
39 struct _ArCardThemesClass {
40 GObjectClass parent_class;
41 };
42
43 struct _ArCardThemes {
44 GObject parent;
45
46 GHashTable *theme_infos;
47 gboolean theme_infos_loaded;
48 };
49
50 enum {
51 N_THEME_TYPES = 5
52 };
53
54 enum {
55 CHANGED,
56 LAST_SIGNAL
57 };
58
59 static guint signals[LAST_SIGNAL];
60
61 /**
62 * theme_type_from_string:
63 * @type_str:
64 * @type_str_len: the length of @type_str
65 *
66 * Returns: the #GType of the card theme type @type_str
67 */
68 static GType
theme_type_from_string(const char * type_str,gssize type_str_len)69 theme_type_from_string (const char *type_str,
70 gssize type_str_len)
71 {
72 const struct {
73 const char name[8];
74 GType type;
75 } type_strings[] = {
76 #ifdef HAVE_QTSVG
77 #if defined(ENABLE_CARD_THEME_FORMAT_NATIVE) && !defined(ENABLE_CARD_THEME_FORMAT_SVG)
78 { "svg", AR_TYPE_CARD_THEME_NATIVE },
79 #endif
80 #endif /* HAVE_QTSVG */
81
82 #ifdef HAVE_RSVG
83 #ifdef ENABLE_CARD_THEME_FORMAT_SVG
84 { "svg", AR_TYPE_CARD_THEME_SVG },
85 #endif
86 #endif /* HAVE_RSVG */
87
88 #ifdef HAVE_QTSVG
89 #ifdef ENABLE_CARD_THEME_FORMAT_NATIVE
90 { "native", AR_TYPE_CARD_THEME_NATIVE },
91 #endif
92 #ifdef ENABLE_CARD_THEME_FORMAT_KDE
93 { "kde", AR_TYPE_CARD_THEME_KDE },
94 #endif
95 #endif /* HAVE_QTSVG */
96
97 #ifdef ENABLE_CARD_THEME_FORMAT_PYSOL
98 { "pysol", AR_TYPE_CARD_THEME_PYSOL },
99 #endif
100 #ifdef ENABLE_CARD_THEME_FORMAT_FIXED
101 { "fixed", AR_TYPE_CARD_THEME_FIXED }
102 #endif
103 };
104 GType type = G_TYPE_INVALID;
105
106 if (type_str_len == 0) {
107 static const char default_type_string[] = AR_CARD_THEME_DEFAULT_FORMAT_STRING;
108
109 /* Use the default type */
110 type = theme_type_from_string (default_type_string, strlen (default_type_string));
111 } else {
112 guint i;
113
114 for (i = 0; i < G_N_ELEMENTS (type_strings); ++i) {
115 if (strncmp (type_str, type_strings[i].name, type_str_len) == 0) {
116 type = type_strings[i].type;
117 break;
118 }
119 }
120 }
121
122 return type;
123 }
124
125 /**
126 * theme_filename_and_type_from_name:
127 * @theme_name: the theme name, or %NULL to get the default theme
128 * @type: return location for the card theme type
129 *
130 * Returns: the filename of the theme @theme_name, and puts the type
131 * in @type, or %NULL if it was not possible to get the type or
132 * filename from @theme_name, or if the requested theme type is not
133 * supported
134 */
135 static char *
theme_filename_and_type_from_name(const char * theme_name,GType * type)136 theme_filename_and_type_from_name (const char *theme_name,
137 GType *type)
138 {
139 const char *colon, *filename, *dot;
140
141 g_return_val_if_fail (type != NULL, NULL);
142
143 ar_debug_print (AR_DEBUG_CARD_THEME,
144 "theme_filename_and_type_from_name %s\n",
145 theme_name ? theme_name : "(null)");
146
147 if (!theme_name || !theme_name[0])
148 theme_name = AR_CARD_THEME_DEFAULT;
149
150 colon = strchr (theme_name, ':');
151 *type = theme_type_from_string (theme_name, colon ? colon - theme_name : 0);
152 if (*type == G_TYPE_INVALID)
153 return NULL;
154
155 /* Get the filename from the theme name */
156 if (colon) {
157 filename = colon + 1;
158 } else {
159 filename = theme_name;
160 }
161
162 dot = strrchr (filename, '.');
163 if (filename == dot || !filename[0])
164 return NULL;
165
166 if (dot == NULL) {
167 /* No dot? Try appending the default, for compatibility with old settings */
168 #if defined(ENABLE_CARD_THEME_FORMAT_FIXED)
169 if (*type == AR_TYPE_CARD_THEME_FIXED) {
170 return g_strconcat (filename, ".card-theme", NULL);
171 }
172 #elif defined(ENABLE_CARD_THEME_FORMAT_SVG)
173 if (*type == AR_TYPE_CARD_THEME_SVG) {
174 return g_strconcat (filename, ".svg", NULL);
175 }
176 #endif
177 } else {
178 #if defined(HAVE_GNOME) && defined(ENABLE_CARD_THEME_FORMAT_SVG)
179 if (*type == AR_TYPE_CARD_THEME_SVG &&
180 g_str_has_suffix (filename, ".png")) {
181 char *base_name, *retval;
182
183 /* Very old version; replace .png with .svg */
184 base_name = g_strndup (filename, dot - filename);
185 retval = g_strconcat (base_name, ".svg", NULL);
186 g_free (base_name);
187
188 return retval;
189 }
190 #endif /* HAVE_GNOME && ENABLE_CARD_THEME_FORMAT_SVG */
191 }
192
193 return g_strdup (filename);
194 }
195
196 static gboolean
ar_card_themes_foreach_theme_dir(GType type,ArCardThemeForeachFunc callback,gpointer data)197 ar_card_themes_foreach_theme_dir (GType type,
198 ArCardThemeForeachFunc callback,
199 gpointer data)
200 {
201 ArCardThemeClass *klass;
202 gboolean retval;
203
204 klass = g_type_class_ref (type);
205 if (!klass)
206 return TRUE;
207
208 ar_profilestart ("foreach %s card themes", G_OBJECT_CLASS_NAME (klass));
209 retval = _ar_card_theme_class_foreach_theme_dir (klass, callback, data);
210 ar_profileend ("foreach %s card themes", G_OBJECT_CLASS_NAME (klass));
211
212 g_type_class_unref (klass);
213 return retval;
214 }
215
216 static gboolean
ar_card_themes_foreach_theme_type_and_dir(ArCardThemes * theme_manager,ArCardThemeForeachFunc callback,gpointer data)217 ar_card_themes_foreach_theme_type_and_dir (ArCardThemes *theme_manager,
218 ArCardThemeForeachFunc callback,
219 gpointer data)
220 {
221 const GType types[] = {
222 /* List of supported theme types, in order of decreasing precedence */
223 #ifdef HAVE_QTSVG
224 #if defined(ENABLE_CARD_THEME_FORMAT_NATIVE) && !defined(ENABLE_CARD_THEME_FORMAT_SVG)
225 AR_TYPE_CARD_THEME_NATIVE,
226 #endif
227 #endif /* HAVE_QTSVG */
228 #ifdef HAVE_RSVG
229 #ifdef ENABLE_CARD_THEME_FORMAT_SVG
230 AR_TYPE_CARD_THEME_SVG,
231 #endif
232 #endif /* HAVE_RSVG */
233 #ifdef HAVE_QTSVG
234 #ifdef ENABLE_CARD_THEME_FORMAT_KDE
235 AR_TYPE_CARD_THEME_KDE,
236 #endif
237 #ifdef ENABLE_CARD_THEME_NATIVE
238 AR_TYPE_CARD_THEME_NATIVE,
239 #endif
240 #endif /* HAVE_QTSVG */
241 #ifdef ENABLE_CARD_THEME_FORMAT_PYSOL
242 AR_TYPE_CARD_THEME_PYSOL,
243 #endif
244 #ifdef ENABLE_CARD_THEME_FORMAT_FIXED
245 AR_TYPE_CARD_THEME_FIXED
246 #endif
247 };
248 guint i;
249 gboolean retval = TRUE;
250
251 for (i = 0; i < G_N_ELEMENTS (types); ++i) {
252 retval = ar_card_themes_foreach_theme_dir (types[i], callback, data);
253 if (!retval)
254 break;
255 }
256
257 return retval;
258 }
259
260 static gboolean
ar_card_themes_get_theme_infos_in_dir(ArCardThemeClass * klass,const char * path,ArCardThemes * theme_manager)261 ar_card_themes_get_theme_infos_in_dir (ArCardThemeClass *klass,
262 const char *path,
263 ArCardThemes *theme_manager)
264 {
265 GDir *iter;
266 const char *filename;
267
268 ar_debug_print (AR_DEBUG_CARD_THEME,
269 "Looking for %s themes in %s\n",
270 G_OBJECT_CLASS_NAME (klass),
271 path);
272
273 ar_profilestart ("looking for %s card themes in %s", G_OBJECT_CLASS_NAME (klass), path);
274
275 iter = g_dir_open (path, 0, NULL);
276 if (!iter)
277 goto out;
278
279 while ((filename = g_dir_read_name (iter)) != NULL) {
280 ArCardThemeInfo *info;
281
282 ar_profilestart ("checking for %s card theme in file %s", G_OBJECT_CLASS_NAME (klass), filename);
283 info = _ar_card_theme_class_get_theme_info (klass, path, filename);
284 ar_profileend ("checking for %s card theme in file %s", G_OBJECT_CLASS_NAME (klass), filename);
285
286 if (info != NULL) {
287 /* Don't replace an already existing theme info! */
288 if (g_hash_table_lookup (theme_manager->theme_infos, info->pref_name))
289 ar_card_theme_info_unref (info);
290 else
291 g_hash_table_insert (theme_manager->theme_infos, info->pref_name, info);
292 }
293 }
294
295 g_dir_close (iter);
296
297 out:
298 ar_profileend ("looking for %s card themes in %s", G_OBJECT_CLASS_NAME (klass), path);
299
300 return TRUE;
301 }
302
303 typedef struct {
304 const char *filename;
305 ArCardThemeInfo *theme_info;
306 } LookupData;
307
308 static gboolean
ar_card_themes_try_theme_info_by_filename(ArCardThemeClass * klass,const char * path,LookupData * data)309 ar_card_themes_try_theme_info_by_filename (ArCardThemeClass *klass,
310 const char *path,
311 LookupData *data)
312 {
313 ar_debug_print (AR_DEBUG_CARD_THEME,
314 "Looking for theme %s/%s in %s\n",
315 G_OBJECT_CLASS_NAME (klass),
316 data->filename,
317 path);
318
319 /* Try constructing the theme info */
320 data->theme_info = _ar_card_theme_class_get_theme_info (klass, path, data->filename);
321
322 /* Continue until found */
323 return data->theme_info == NULL;
324 }
325
326 static void
ar_card_themes_load_theme_infos(ArCardThemes * theme_manager)327 ar_card_themes_load_theme_infos (ArCardThemes *theme_manager)
328 {
329 ar_debug_print (AR_DEBUG_CARD_THEME,
330 "Scanning theme directories\n");
331
332 /* FIXMEchpe: clear the hash table here? */
333
334 ar_profilestart ("looking for card themes");
335 ar_card_themes_foreach_theme_type_and_dir (theme_manager,
336 (ArCardThemeForeachFunc) ar_card_themes_get_theme_infos_in_dir,
337 theme_manager);
338 ar_profileend ("looking for card themes");
339
340 theme_manager->theme_infos_loaded = TRUE;
341
342 g_signal_emit (theme_manager, signals[CHANGED], 0);
343 }
344
345 typedef struct {
346 GType type;
347 const char *filename;
348 ArCardThemeInfo *theme_info;
349 } ThemesByTypeAndFilenameData;
350
351 static void
themes_foreach_by_type_and_filename(gpointer key,ArCardThemeInfo * theme_info,ThemesByTypeAndFilenameData * data)352 themes_foreach_by_type_and_filename (gpointer key,
353 ArCardThemeInfo *theme_info,
354 ThemesByTypeAndFilenameData *data)
355 {
356 if (data->theme_info)
357 return;
358
359 if (theme_info->type == data->type &&
360 strcmp (theme_info->filename, data->filename) == 0)
361 data->theme_info = theme_info;
362 }
363
364 static void
themes_foreach_add_to_list(gpointer key,ArCardThemeInfo * theme_info,GList ** list)365 themes_foreach_add_to_list (gpointer key,
366 ArCardThemeInfo *theme_info,
367 GList **list)
368 {
369 *list = g_list_prepend (*list, ar_card_theme_info_ref (theme_info));
370 }
371
372 typedef struct {
373 ArCardThemes *theme_manager;
374 ArCardTheme *theme;
375 } ThemesAnyData;
376
377 static void
themes_foreach_any(gpointer key,ArCardThemeInfo * theme_info,ThemesAnyData * data)378 themes_foreach_any (gpointer key,
379 ArCardThemeInfo *theme_info,
380 ThemesAnyData *data)
381 {
382 if (data->theme)
383 return;
384
385 data->theme = ar_card_themes_get_theme (data->theme_manager, theme_info);
386 }
387
388 /* Class implementation */
389
390 G_DEFINE_TYPE (ArCardThemes, ar_card_themes, G_TYPE_OBJECT);
391
392 static void
ar_card_themes_init(ArCardThemes * theme_manager)393 ar_card_themes_init (ArCardThemes *theme_manager)
394 {
395 /* Hash table: pref name => theme info */
396 theme_manager->theme_infos = g_hash_table_new_full (g_str_hash, g_str_equal,
397 NULL /* key is owned by data */,
398 (GDestroyNotify) ar_card_theme_info_unref);
399
400 theme_manager->theme_infos_loaded = FALSE;
401 }
402
403 static void
ar_card_themes_finalize(GObject * object)404 ar_card_themes_finalize (GObject *object)
405 {
406 ArCardThemes *theme_manager = AR_CARD_THEMES (object);
407
408 g_hash_table_destroy (theme_manager->theme_infos);
409
410 G_OBJECT_CLASS (ar_card_themes_parent_class)->finalize (object);
411 }
412
413 static void
ar_card_themes_class_init(ArCardThemesClass * klass)414 ar_card_themes_class_init (ArCardThemesClass * klass)
415 {
416 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
417
418 gobject_class->finalize = ar_card_themes_finalize;
419
420 /**
421 * ArCardThemes:changed:
422 *
423 * The ::changed signal is emitted when the list of card themes has
424 * changed.
425 */
426 signals[CHANGED] =
427 g_signal_newv ("changed",
428 G_TYPE_FROM_CLASS (klass),
429 (GSignalFlags) (G_SIGNAL_RUN_LAST),
430 NULL,
431 NULL, NULL,
432 g_cclosure_marshal_VOID__VOID,
433 G_TYPE_NONE,
434 0, NULL);
435 }
436
437 /* public API */
438
439 /**
440 * ar_card_themes_new:
441 *
442 * Returns: a new #ArCardThemes object
443 */
444 ArCardThemes *
ar_card_themes_new(void)445 ar_card_themes_new (void)
446 {
447 return g_object_new (AR_TYPE_CARD_THEMES, NULL);
448 }
449
450 /**
451 * ar_card_themes_request_themes:
452 * @theme_manager:
453 *
454 * Scans all theme directories for themes, if necessary. If the
455 * themes list has changed, emits the "changed" signal synchronously.
456 */
457 void
ar_card_themes_request_themes(ArCardThemes * theme_manager)458 ar_card_themes_request_themes (ArCardThemes *theme_manager)
459 {
460 g_return_if_fail (AR_IS_CARD_THEMES (theme_manager));
461
462 if (theme_manager->theme_infos_loaded)
463 return;
464
465 ar_card_themes_load_theme_infos (theme_manager);
466 }
467
468 /**
469 * ar_card_themes_get_theme:
470 * @theme_manager:
471 * @info: a #ArCardThemeInfo
472 *
473 * Returns: a new #ArCardTheme for @info, or %NULL if there was an
474 * error while loading the theme.
475 */
476 ArCardTheme *
ar_card_themes_get_theme(ArCardThemes * theme_manager,ArCardThemeInfo * info)477 ar_card_themes_get_theme (ArCardThemes *theme_manager,
478 ArCardThemeInfo *info)
479 {
480 ArCardTheme *theme;
481 GError *error = NULL;
482
483 g_return_val_if_fail (AR_IS_CARD_THEMES (theme_manager), NULL);
484 g_return_val_if_fail (info != NULL, NULL);
485
486 if (info->type == G_TYPE_INVALID)
487 return NULL;
488
489 ar_profilestart ("loading card theme %s/%s", g_type_name (info->type), info->display_name);
490
491 theme = g_object_new (info->type, "theme-info", info, NULL);
492 if (!theme->klass->load (theme, &error)) {
493 ar_debug_print (AR_DEBUG_CARD_THEME,
494 "Failed to load card theme %s/%s: %s\n",
495 g_type_name (info->type),
496 info->display_name,
497 error ? error->message : "(no error information)");
498
499 g_clear_error (&error);
500 g_object_unref (theme);
501 theme = NULL;
502 } else {
503 ar_debug_print (AR_DEBUG_CARD_THEME,
504 "Successfully loaded card theme %s/%s\n",
505 g_type_name (info->type),
506 info->display_name);
507 }
508
509 ar_profileend ("loading card theme %s/%s", g_type_name (info->type), info->display_name);
510
511 return theme;
512 }
513
514 /**
515 * ar_card_themes_get_theme_by_name:
516 * @theme_manager:
517 * @theme_name: a theme name, or %NULL to get the default theme
518 *
519 * Gets a #ArCardTheme by its persistent name. If @theme_name is %NULL,
520 * gets the defaul theme.
521 *
522 * Returns: a new #ArCardTheme for @theme_name, or %NULL if there was an
523 * error while loading the theme
524 */
525 ArCardTheme *
ar_card_themes_get_theme_by_name(ArCardThemes * theme_manager,const char * theme_name)526 ar_card_themes_get_theme_by_name (ArCardThemes *theme_manager,
527 const char *theme_name)
528 {
529 GType type;
530 char *filename;
531 ArCardThemeInfo *theme_info = NULL;
532
533 g_return_val_if_fail (AR_IS_CARD_THEMES (theme_manager), NULL);
534
535 filename = theme_filename_and_type_from_name (theme_name, &type);
536 ar_debug_print (AR_DEBUG_CARD_THEME,
537 "Resolved card type=%s filename=%s\n",
538 g_type_name (type),
539 filename);
540
541 if (filename == NULL || type == G_TYPE_INVALID)
542 return NULL;
543
544 /* First try to find the theme in our hash table */
545 {
546 ThemesByTypeAndFilenameData data = { type, filename, NULL };
547
548 g_hash_table_foreach (theme_manager->theme_infos, (GHFunc) themes_foreach_by_type_and_filename, &data);
549
550 theme_info = data.theme_info;
551 }
552
553 if (theme_info == NULL &&
554 !theme_manager->theme_infos_loaded) {
555 LookupData data = { filename, NULL };
556
557 ar_card_themes_foreach_theme_dir (type, (ArCardThemeForeachFunc) ar_card_themes_try_theme_info_by_filename, &data);
558 theme_info = data.theme_info;
559
560 if (theme_info)
561 g_hash_table_replace (theme_manager->theme_infos, theme_info->pref_name, theme_info);
562 }
563
564 g_free (filename);
565
566 if (theme_info == NULL)
567 return NULL;
568
569 return ar_card_themes_get_theme (theme_manager, theme_info);
570 }
571
572 /**
573 * ar_card_themes_get_theme_any:
574 *
575 * Loads all card themes until loading one succeeds, and returns it; or
576 * %NULL if all card themes fail to load.
577 *
578 * Returns:
579 */
580 ArCardTheme *
ar_card_themes_get_theme_any(ArCardThemes * theme_manager)581 ar_card_themes_get_theme_any (ArCardThemes *theme_manager)
582 {
583 ThemesAnyData data = { theme_manager, NULL };
584
585 g_return_val_if_fail (AR_IS_CARD_THEMES (theme_manager), NULL);
586
587 ar_debug_print (AR_DEBUG_CARD_THEME,
588 "Fallback: trying to load any theme\n");
589
590 ar_card_themes_request_themes (theme_manager);
591
592 g_hash_table_foreach (theme_manager->theme_infos, (GHFunc) themes_foreach_any, &data);
593
594 return data.theme;
595 }
596
597 /**
598 * ar_card_themes_get_themes_loaded:
599 *
600 * Returns: %TRUE iff the themes list has been loaded
601 */
602 gboolean
ar_card_themes_get_themes_loaded(ArCardThemes * theme_manager)603 ar_card_themes_get_themes_loaded (ArCardThemes *theme_manager)
604 {
605 g_return_val_if_fail (AR_IS_CARD_THEMES (theme_manager), FALSE);
606
607 return theme_manager->theme_infos_loaded;
608 }
609
610 /**
611 * ar_card_themes_get_themes:
612 *
613 * Gets the list of known themes. Note that you may need to call
614 * ar_card_themes_request_themes() first ensure the themes
615 * information has been collected.
616 *
617 * Returns: a newly allocated list of referenced #ArCardThemeInfo objects
618 */
619 GList *
ar_card_themes_get_themes(ArCardThemes * theme_manager)620 ar_card_themes_get_themes (ArCardThemes *theme_manager)
621 {
622 GList *list = NULL;
623
624 g_return_val_if_fail (AR_IS_CARD_THEMES (theme_manager), NULL);
625
626 g_hash_table_foreach (theme_manager->theme_infos, (GHFunc) themes_foreach_add_to_list, &list);
627
628 return g_list_sort (list, (GCompareFunc) _ar_card_theme_info_collate);
629 }
630
631 /**
632 * ar_card_themes_install_themes:
633 * @theme_manager:
634 * @parent_window:
635 * @user_time:
636 *
637 * Try to install more card themes.
638 */
639 void
ar_card_themes_install_themes(ArCardThemes * theme_manager,GtkWidget * parent_window,guint user_time)640 ar_card_themes_install_themes (ArCardThemes *theme_manager,
641 GtkWidget *parent_window,
642 guint user_time)
643 {
644 }
645