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