4bb12bb8482e94c667532ff98d5b4f86c65e12c5
[framework/uifw/e17.git] / src / bin / e_theme.c
1 #include "e.h"
2
3 /* local subsystem functions */
4 typedef struct _E_Theme_Result E_Theme_Result;
5
6 struct _E_Theme_Result
7 {
8    const char *file;
9    const char *cache;
10    Eina_Hash  *quickfind;
11 };
12
13 static Eina_Bool _e_theme_mappings_free_cb(const Eina_Hash *hash, const void *key, void *data, void *fdata);
14 static Eina_Bool _e_theme_mappings_quickfind_free_cb(const Eina_Hash *hash, const void *key, void *data, void *fdata);
15 static void _e_theme_category_register(const char *category);
16 static Eina_List *_e_theme_collection_item_register(Eina_List *list, const char *name);
17 static Eina_List *_e_theme_collection_items_find(const char *base, const char *collname);
18
19
20 /* local subsystem globals */
21 static Eina_Hash *mappings = NULL;
22 static Eina_Hash *group_cache = NULL;
23
24 static Eina_List *categories = NULL;
25 static Eina_List *transitions = NULL;
26 static Eina_List *borders = NULL;
27 static Eina_List *shelfs = NULL;
28 static Eina_List *comps = NULL;
29 static E_Fm2_Mime_Handler *theme_hdl = NULL;
30
31 /* externally accessible functions */
32
33 EINTERN int
34 e_theme_init(void)
35 {
36    E_Config_Theme *et;
37    Eina_List *l = NULL;
38
39    /* Register mime handler */
40    theme_hdl = e_fm2_mime_handler_new(_("Set As Theme"), "preferences-desktop-theme",
41                                       e_theme_handler_set, NULL,
42                                       e_theme_handler_test, NULL);
43    if (theme_hdl) e_fm2_mime_handler_glob_add(theme_hdl, "*.edj");
44
45    /* this is a fallback that is ALWAYS there - if all fails things will */
46    /* always fall back to the default theme. the rest after this are config */
47    /* values users can set */
48    e_theme_file_set("base", "default.edj");
49
50    EINA_LIST_FOREACH(e_config->themes, l, et)
51      {
52         char buf[256];
53
54         snprintf(buf, sizeof(buf), "base/%s", et->category);
55         e_theme_file_set(buf, et->file);
56      }
57
58    /* Find transitions */
59    transitions = _e_theme_collection_items_find("base/theme/transitions", "e/transitions");
60    borders = _e_theme_collection_items_find("base/theme/borders", "e/widgets/border");
61    shelfs = _e_theme_collection_items_find("base/theme/shelf", "e/shelf");
62    comps = _e_theme_collection_items_find("base/theme/borders", "e/comp");
63    if (!mappings) mappings = eina_hash_string_superfast_new(NULL);
64    group_cache = eina_hash_string_superfast_new(NULL);
65
66    return 1;
67 }
68
69 EINTERN int
70 e_theme_shutdown(void)
71 {
72    const char *str;
73
74    if (theme_hdl)
75      {
76         e_fm2_mime_handler_glob_del(theme_hdl, "*.edj");
77         e_fm2_mime_handler_free(theme_hdl);
78      }
79    if (mappings)
80      {
81         eina_hash_foreach(mappings, _e_theme_mappings_free_cb, NULL);
82         eina_hash_free(mappings);
83         mappings = NULL;
84      }
85    if (group_cache)
86      {
87         eina_hash_free(group_cache);
88         group_cache = NULL;
89      }
90    EINA_LIST_FREE(categories, str)
91      eina_stringshare_del(str);
92    EINA_LIST_FREE(transitions, str)
93      eina_stringshare_del(str);
94    EINA_LIST_FREE(borders, str)
95      eina_stringshare_del(str);
96    EINA_LIST_FREE(shelfs, str)
97      eina_stringshare_del(str);
98    EINA_LIST_FREE(comps, str)
99      eina_stringshare_del(str);
100    return 1;
101 }
102
103 EAPI int
104 e_theme_edje_object_set(Evas_Object *o, const char *category, const char *group)
105 {
106    E_Theme_Result *res;
107    char buf[256];
108    char *p;
109
110    /* find category -> edje mapping */
111    _e_theme_category_register(category);
112    res = eina_hash_find(mappings, category);
113    if (res)
114      {
115         const char *str;
116
117         /* if found check cached path */
118         str = res->cache;
119         if (!str)
120           {
121              /* no cached path */
122              str = res->file;
123              /* if its not an absolute path find it */
124              if (str[0] != '/')
125                str = e_path_find(path_themes, str);
126              /* save cached value */
127              if (str) res->cache = str;
128           }
129         if (str)
130           {
131              void *tres;
132              int ok;
133
134              snprintf(buf, sizeof(buf), "%s/::/%s", str, group);
135              tres = eina_hash_find(group_cache, buf);
136              if (!tres)
137                {
138                   ok = edje_object_file_set(o, str, group);
139                   /* save in the group cache hash */
140                   if (ok)
141                     eina_hash_add(group_cache, buf, res);
142                   else
143                     eina_hash_add(group_cache, buf, (void *)1);
144                }
145              else if (tres == (void *)1)
146                ok = 0;
147              else
148                ok = 1;
149              if (ok)
150                {
151                   if (tres)
152                     edje_object_file_set(o, str, group);
153                   return 1;
154                }
155           }
156      }
157    /* no mapping or set failed - fall back */
158    eina_strlcpy(buf, category, sizeof(buf));
159    /* shorten string up to and not including last / char */
160    p = strrchr(buf, '/');
161    if (p) *p = 0;
162    /* no / anymore - we are already as far back as we can go */
163    else return 0;
164    /* try this category */
165    return e_theme_edje_object_set(o, buf, group);
166 }
167
168 const char *
169 _e_theme_edje_file_get(const char *category, const char *group, Eina_Bool fallback_icon)
170 {
171    E_Theme_Result *res;
172    char buf[4096];
173    const char *q;
174    char *p;
175
176    /* find category -> edje mapping */
177    _e_theme_category_register(category);
178    res = eina_hash_find(mappings, category);
179
180    if (e_config->icon_theme &&
181        (!fallback_icon) &&
182        (!strcmp(category, "base")) &&
183        (!strncmp(group, "e/icons", 7)))
184      return "";
185
186    if (res)
187      {
188         const char *str;
189
190         /* if found check cached path */
191         str = res->cache;
192         if (!str)
193           {
194              /* no cached path */
195              str = res->file;
196              /* if its not an absolute path find it */
197              if (str[0] != '/')
198                str = e_path_find(path_themes, str);
199              /* save cached value */
200              if (str) res->cache = str;
201           }
202         if (str)
203           {
204              void *tres;
205              Eina_List *coll, *l;
206              int ok;
207
208              snprintf(buf, sizeof(buf), "%s/::/%s", str, group);
209              tres = eina_hash_find(group_cache, buf);
210              if (!tres)
211                {
212                   /* if the group exists - return */
213                   if (!res->quickfind)
214                     {
215                        const char *col;
216
217                        res->quickfind = eina_hash_string_superfast_new(NULL);
218                        /* create a quick find hash of all group entries */
219                        coll = edje_file_collection_list(str);
220
221                        EINA_LIST_FOREACH(coll, l, col)
222                          {
223                             q = eina_stringshare_add(col);
224                             eina_hash_direct_add(res->quickfind, q, q);
225                          }
226                        if (coll) edje_file_collection_list_free(coll);
227                     }
228                   /* save in the group cache hash */
229                   if (eina_hash_find(res->quickfind, group))
230                     {
231                        eina_hash_add(group_cache, buf, res);
232                        ok = 1;
233                     }
234                   else
235                     {
236                        eina_hash_add(group_cache, buf, (void *)1);
237                        ok = 0;
238                     }
239                }
240              else if (tres == (void *)1) /* special pointer "1" == not there */
241                ok = 0;
242              else
243                ok = 1;
244              if (ok) return str;
245           }
246      }
247    /* no mapping or set failed - fall back */
248    eina_strlcpy(buf, category, sizeof(buf));
249    /* shorten string up to and not including last / char */
250    p = strrchr(buf, '/');
251    if (p) *p = 0;
252    /* no / anymore - we are already as far back as we can go */
253    else return "";
254    /* try this category */
255    return e_theme_edje_file_get(buf, group);
256 }
257
258 EAPI const char *
259 e_theme_edje_file_get(const char *category, const char *group)
260 {
261    return _e_theme_edje_file_get(category, group, EINA_FALSE);
262 }
263
264 EAPI const char *
265 e_theme_edje_icon_fallback_file_get(const char *group)
266 {
267    return _e_theme_edje_file_get("base", group, EINA_TRUE);
268 }
269
270 /*
271  * this is used to set the theme for a CATEGORY of E17. "base" is always set
272  * to the default theme - because if a selected theme wants "base/theme", but
273  * does not provide theme elements, it can fall back to the default theme.
274  * 
275  * the idea is you can actually set a different theme for different parts of
276  * the desktop... :)
277  * 
278  * other possible categories...
279  *  e_theme_file_set("base/theme/about", "default.edj");
280  *  e_theme_file_set("base/theme/borders", "default.edj");
281  *  e_theme_file_set("base/theme/background", "default.edj");
282  *  e_theme_file_set("base/theme/configure", "default.edj");
283  *  e_theme_file_set("base/theme/dialog", "default.edj");
284  *  e_theme_file_set("base/theme/menus", "default.edj");
285  *  e_theme_file_set("base/theme/error", "default.edj");
286  *  e_theme_file_set("base/theme/gadman", "default.edj");
287  *  e_theme_file_set("base/theme/dnd", "default.edj");
288  *  e_theme_file_set("base/theme/icons", "default.edj");
289  *  e_theme_file_set("base/theme/pointer", "default.edj");
290  *  e_theme_file_set("base/theme/transitions", "default.edj");
291  *  e_theme_file_set("base/theme/widgets", "default.edj");
292  *  e_theme_file_set("base/theme/winlist", "default.edj");
293  *  e_theme_file_set("base/theme/modules", "default.edj");
294  *  e_theme_file_set("base/theme/modules/pager", "default.edj");
295  *  e_theme_file_set("base/theme/modules/ibar", "default.edj");
296  *  e_theme_file_set("base/theme/modules/ibox", "default.edj");
297  *  e_theme_file_set("base/theme/modules/clock", "default.edj");
298  *  e_theme_file_set("base/theme/modules/battery", "default.edj");
299  *  e_theme_file_set("base/theme/modules/cpufreq", "default.edj");
300  *  e_theme_file_set("base/theme/modules/start", "default.edj");
301  *  e_theme_file_set("base/theme/modules/temperature", "default.edj");
302  */
303
304 EAPI void
305 e_theme_file_set(const char *category, const char *file)
306 {
307    E_Theme_Result *res;
308
309    if (group_cache)
310      {
311         eina_hash_free(group_cache);
312         group_cache = NULL;
313      }
314    _e_theme_category_register(category);
315    res = eina_hash_find(mappings, category);
316    if (res)
317      {
318         eina_hash_del(mappings, category, res);
319         if (res->file)
320           {
321              e_filereg_deregister(res->file);
322              eina_stringshare_del(res->file);
323           }
324         if (res->cache) eina_stringshare_del(res->cache);
325         E_FREE(res);
326      }
327    res = E_NEW(E_Theme_Result, 1);
328    res->file = eina_stringshare_add(file);
329    e_filereg_register(res->file);
330    if (!mappings)
331       mappings = eina_hash_string_superfast_new(NULL);
332    eina_hash_add(mappings, category, res);
333 }
334
335 EAPI int
336 e_theme_config_set(const char *category, const char *file)
337 {
338    E_Config_Theme *ect;
339    Eina_List *next;
340
341    /* Don't accept unused categories */
342 #if 0
343    if (!e_theme_category_find(category)) return 0;
344 #endif
345
346    /* search for the category */
347    EINA_LIST_FOREACH(e_config->themes, next, ect)
348      {
349         if (!strcmp(ect->category, category))
350           {
351              if (ect->file) eina_stringshare_del(ect->file);
352              ect->file = eina_stringshare_add(file);
353              return 1;
354           }
355      }
356
357    /* the text class doesnt exist */
358    ect = E_NEW(E_Config_Theme, 1);
359    ect->category = eina_stringshare_add(category);
360    ect->file = eina_stringshare_add(file);
361    
362    e_config->themes = eina_list_append(e_config->themes, ect);
363    return 1;
364 }
365
366 /*
367  * returns a pointer to the data, return null if nothing if found.
368  */
369 EAPI E_Config_Theme *
370 e_theme_config_get(const char *category)
371 {
372    E_Config_Theme *ect = NULL;
373    Eina_List *next;
374
375    /* search for the category */
376    EINA_LIST_FOREACH(e_config->themes, next, ect)
377      {
378         if (!strcmp(ect->category, category))
379           return ect;
380      }
381    return NULL;
382 }
383
384 EAPI int
385 e_theme_config_remove(const char *category)
386 {
387    E_Config_Theme *ect;
388    Eina_List *next;
389
390    /* search for the category */
391    EINA_LIST_FOREACH(e_config->themes, next, ect)
392      {
393         if (!strcmp(ect->category, category))
394           {
395              e_config->themes = eina_list_remove_list(e_config->themes, next);
396              if (ect->category) eina_stringshare_del(ect->category);
397              if (ect->file) eina_stringshare_del(ect->file);
398              free(ect);
399              return 1;
400           }
401     }
402    return 1;
403 }
404
405 EAPI Eina_List *
406 e_theme_config_list(void)
407 {
408    return e_config->themes;
409 }
410
411 EAPI int
412 e_theme_category_find(const char *category)
413 {
414    if (eina_list_search_sorted(categories, EINA_COMPARE_CB(strcmp), category))
415      return 1;
416    return 0;
417 }
418
419 EAPI Eina_List *
420 e_theme_category_list(void)
421 {
422    return categories;
423 }
424
425 EAPI int
426 e_theme_transition_find(const char *transition)
427 {
428    if (eina_list_search_sorted(transitions, EINA_COMPARE_CB(strcmp), transition))
429      return 1;
430    return 0;
431 }
432
433 EAPI Eina_List *
434 e_theme_transition_list(void)
435 {
436    return transitions;
437 }
438
439 EAPI int
440 e_theme_border_find(const char *border)
441 {
442    if (eina_list_search_sorted(borders, EINA_COMPARE_CB(strcmp), border))
443      return 1;
444    return 0;
445 }
446
447 EAPI Eina_List *
448 e_theme_border_list(void)
449 {
450    return borders;
451 }
452
453 EAPI int
454 e_theme_shelf_find(const char *shelf)
455 {
456    if (eina_list_search_sorted(shelfs, EINA_COMPARE_CB(strcmp), shelf))
457      return 1;
458    return 0;
459 }
460
461 EAPI Eina_List *
462 e_theme_shelf_list(void)
463 {
464    return shelfs;
465 }
466
467 EAPI int
468 e_theme_comp_find(const char *comp)
469 {
470    if (eina_list_search_sorted(comps, EINA_COMPARE_CB(strcmp), comp))
471      return 1;
472    return 0;
473 }
474
475 EAPI Eina_List *
476 e_theme_comp_list(void)
477 {
478    return comps;
479 }
480
481 EAPI void
482 e_theme_handler_set(Evas_Object *obj __UNUSED__, const char *path, void *data __UNUSED__)
483 {
484    E_Action *a;
485    char buf[PATH_MAX];
486    int copy = 1;
487
488    if (!path) return;
489
490    /* if not in system dir or user dir, copy to user dir */
491    e_prefix_data_concat_static(buf, "data/themes");
492    if (!strncmp(buf, path, strlen(buf)))
493       copy = 0;
494    if (copy)
495      {
496         e_user_dir_concat_static(buf, "themes");
497         if (!strncmp(buf, path, strlen(buf)))
498            copy = 0;
499      }
500    if (copy)
501      {
502         const char *file;
503         char *name;
504
505         file = ecore_file_file_get(path);
506         name = ecore_file_strip_ext(file);
507
508         e_user_dir_snprintf(buf, sizeof(buf), "themes/%s-%f.edj", name, ecore_time_unix_get());
509         free(name);
510
511         if (!ecore_file_exists(buf))
512           {
513              ecore_file_cp(path, buf);
514              e_theme_config_set("theme", buf);
515           }
516         else
517            e_theme_config_set("theme", path);
518      }
519    else
520       e_theme_config_set("theme", path);
521
522    e_config_save_queue();
523    a = e_action_find("restart");
524    if ((a) && (a->func.go)) a->func.go(NULL, NULL);
525 }
526
527 EAPI int
528 e_theme_handler_test(Evas_Object *obj __UNUSED__, const char *path, void *data __UNUSED__)
529 {
530    if (!path) return 0;
531    if (!edje_file_group_exists(path, "e/widgets/border/default/border"))
532      return 0;
533    return 1;
534 }
535
536 /* local subsystem functions */
537 static Eina_Bool
538 _e_theme_mappings_free_cb(const Eina_Hash *hash __UNUSED__, const void *key __UNUSED__, void *data, void *fdata __UNUSED__)
539 {
540    E_Theme_Result *res;
541
542    res = data;
543    if (res->file) eina_stringshare_del(res->file);
544    if (res->cache) eina_stringshare_del(res->cache);
545    if (res->quickfind)
546      {
547         eina_hash_foreach(res->quickfind, _e_theme_mappings_quickfind_free_cb, NULL);
548         eina_hash_free(res->quickfind);
549      }
550    free(res);
551    return EINA_TRUE;
552 }
553
554 static Eina_Bool
555 _e_theme_mappings_quickfind_free_cb(const Eina_Hash *hash __UNUSED__, const void *key, void *data __UNUSED__, void *fdata __UNUSED__)
556 {
557    eina_stringshare_del(key);
558    return EINA_TRUE;
559 }
560
561 static void
562 _e_theme_category_register(const char *category)
563 {
564    Eina_List *l;
565    int ret;
566
567    if (!categories)
568      categories = eina_list_append(categories, eina_stringshare_add(category));
569
570    l = eina_list_search_sorted_near_list(categories, EINA_COMPARE_CB(strcmp),
571          category, &ret);
572
573    if (!ret) return;
574
575    if (ret < 0)
576      categories = eina_list_append_relative_list(categories, eina_stringshare_add(category), l);
577    else
578      categories = eina_list_prepend_relative_list(categories, eina_stringshare_add(category), l);
579 }
580
581 static Eina_List *
582 _e_theme_collection_item_register(Eina_List *list, const char *name)
583 {
584    const char *item;
585    Eina_List *l;
586
587    EINA_LIST_FOREACH(list, l, item)
588      {
589         if (!strcmp(name, item)) return list;
590      }
591    list = eina_list_append(list, eina_stringshare_add(name));
592    return list;
593 }
594
595 static Eina_List *
596 _e_theme_collection_items_find(const char *base, const char *collname)
597 {
598    Eina_List *list = NULL;
599    E_Theme_Result *res;
600    char *category, *p, *p2;
601    int collname_len;
602
603    collname_len = strlen(collname);
604    category = alloca(strlen(base) + 1);
605    strcpy(category, base);
606    do
607      {
608         res = eina_hash_find(mappings, category);
609         if (res)
610           {
611              const char *str;
612
613              /* if found check cached path */
614              str = res->cache;
615              if (!str)
616                {
617                   /* no cached path */
618                   str = res->file;
619                   /* if its not an absolute path find it */
620                   if (str[0] != '/') str = e_path_find(path_themes, str);
621                   /* save cached value */
622                   if (str) res->cache = str;
623                }
624              if (str)
625                {
626                   Eina_List *coll, *l;
627                   
628                   coll = edje_file_collection_list(str);
629                   if (coll)
630                     {
631                        const char *c;
632
633                        EINA_LIST_FOREACH(coll, l, c)
634                          {
635                             if (!strncmp(c, collname, collname_len))
636                               {
637                                  char *trans;
638
639                                  trans = strdup(c);
640                                  p = trans + collname_len + 1;
641                                  if (*p)
642                                    {
643                                       p2 = strchr(p, '/');
644                                       if (p2) *p2 = 0;
645                                       list = _e_theme_collection_item_register(list, p);
646                                    }
647                                  free(trans);
648                               }
649                          }
650                        edje_file_collection_list_free(coll);
651                     }
652                }
653           }
654         p = strrchr(category, '/');
655         if (p) *p = 0;
656      }
657    while (p);
658
659    list = eina_list_sort(list, 0, EINA_COMPARE_CB(strcmp));
660    return list;
661 }