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