1 #ifdef HAVE_CONFIG_H
2 # include "elementary_config.h"
3 #endif
4 
5 #include <Elementary.h>
6 
7 #include "elm_priv.h"
8 #include "elm_icon_eo.h"
9 
10 #include "efl_ui_theme.eo.h"
11 
12 static Elm_Theme *theme_default = NULL;
13 
14 static Eina_List *themes = NULL;
15 
16 static Eina_File *
_elm_theme_find_try(Elm_Theme * th,Elm_Theme_File * etf,const char * group,Eina_Bool force)17 _elm_theme_find_try(Elm_Theme *th, Elm_Theme_File *etf, const char *group, Eina_Bool force)
18 {
19    if (edje_mmap_group_exists(etf->handle, group))
20      {
21         if (etf->match_theme && (!force)) // overlay or extension
22           {
23              Elm_Theme_File *base_etf;
24              Eina_Bool found = EINA_FALSE;
25 
26              EINA_INLIST_FOREACH(th->themes, base_etf)
27                {
28                   if (base_etf->base_theme != etf->match_theme) continue;
29                   found = EINA_TRUE;
30                   break;
31                }
32              if (!found) return NULL;
33           }
34         eina_hash_add(th->cache, group, eina_file_dup(etf->handle));
35         return etf->handle;
36      }
37    return NULL;
38 }
39 
40 static inline void
_elm_theme_item_finalize(Eina_Inlist ** files,const char * item,Eina_File * f,Eina_Bool prepend,Eina_Bool istheme)41 _elm_theme_item_finalize(Eina_Inlist **files,
42                          const char *item,
43                          Eina_File *f,
44                          Eina_Bool prepend,
45                          Eina_Bool istheme)
46 {
47    Elm_Theme_File *etf;
48    char *name;
49    /* Theme version history:
50     * <110: legacy, had no version tag
51     *  110: first supported version
52     *  119: switched windows to always use border
53     *       win group has no menu, no blocker
54     *       border group has all required swallows (conformant, bg, win)
55     *       data: "elm_bg_version" matches "version" ("119")
56     */
57    if (!f) return;
58    if (istheme)
59      {
60         char *version;
61         int v;
62 
63         if (!(version = edje_mmap_data_get(f, "version")))
64           {
65              eina_file_close(f); // close matching open (finalize expected to consume eina file) OK
66              return;
67           }
68         v = atoi(version);
69         if (v < 110) // bump this version number when we need to
70           {
71              WRN("Selected theme is too old (version = %d, needs >= 110)", v);
72           }
73         free(version);
74      }
75    etf = calloc(1, sizeof(Elm_Theme_File));
76    EINA_SAFETY_ON_NULL_RETURN(etf);
77    etf->item = eina_stringshare_add(item);
78    etf->handle = f; // now own/consume the file handle
79    if (istheme)
80      {
81         name = edje_mmap_data_get(f, "efl_theme_base");
82         etf->base_theme = eina_stringshare_add(name);
83      }
84    else
85      {
86         name = edje_mmap_data_get(f, "efl_theme_match");
87         etf->match_theme = eina_stringshare_add(name);
88      }
89    free(name);
90    if (prepend)
91      {
92         *files = eina_inlist_prepend(*files, EINA_INLIST_GET(etf));
93      }
94    else
95      {
96         *files = eina_inlist_append(*files, EINA_INLIST_GET(etf));
97      }
98 }
99 
100 static void
_elm_theme_file_item_add(Eina_Inlist ** files,const char * item,Eina_Bool prepend,Eina_Bool istheme)101 _elm_theme_file_item_add(Eina_Inlist **files, const char *item, Eina_Bool prepend, Eina_Bool istheme)
102 {
103    Eina_Strbuf *buf = NULL;
104    Eina_File *f = NULL;
105    const char *home;
106 
107    home = eina_environment_home_get();
108    buf = eina_strbuf_new();
109 
110    if ((item[0] == '/') ||
111        ((item[0] == '.') && (item[1] == '/')) ||
112        ((item[0] == '.') && (item[1] == '.') && (item[2] == '/')) ||
113        ((isalpha(item[0])) && (item[1] == ':')))
114      {
115         f = eina_file_open(item, EINA_FALSE);
116         if (!f) goto on_error;
117      }
118    else if (((item[0] == '~') && (item[1] == '/')))
119      {
120         eina_strbuf_append_printf(buf, "%s/%s", home, item + 2);
121 
122         f = eina_file_open(eina_strbuf_string_get(buf), EINA_FALSE);
123         if (!f) goto on_error;
124      }
125    else
126      {
127         eina_strbuf_append_printf(buf,
128                                   "%s/"ELEMENTARY_BASE_DIR"/themes/%s.edj",
129                                   home, item);
130         f = eina_file_open(eina_strbuf_string_get(buf), EINA_FALSE);
131         _elm_theme_item_finalize(files, item, f, prepend, istheme);
132 
133         eina_strbuf_reset(buf);
134 
135         eina_strbuf_append_printf(buf,
136                                   "%s/themes/%s.edj",
137                                   _elm_data_dir, item);
138         f = eina_file_open(eina_strbuf_string_get(buf), EINA_FALSE);
139         /* Finalize will be done by the common one */
140      }
141 
142    _elm_theme_item_finalize(files, item, f, prepend, istheme);
143 
144  on_error:
145    if (buf) eina_strbuf_free(buf);
146 }
147 
148 static void
_elm_theme_file_item_del(Eina_Inlist ** files,const char * str)149 _elm_theme_file_item_del(Eina_Inlist **files, const char *str)
150 {
151    Eina_Inlist *l;
152    Elm_Theme_File *item;
153 
154    str = eina_stringshare_add(str);
155 
156    EINA_INLIST_FOREACH_SAFE(*files, l, item)
157      {
158         if (item->item != str) continue;
159         eina_file_close(item->handle); // close matching open (file consumed in finalize by putting it in etf) OK
160         eina_stringshare_del(item->item);
161         *files = eina_inlist_remove(*files, EINA_INLIST_GET(item));
162         free(item);
163      }
164 
165    eina_stringshare_del(str);
166 }
167 
168 static void
_elm_theme_file_mmap_del(Eina_Inlist ** files,const Eina_File * file)169 _elm_theme_file_mmap_del(Eina_Inlist **files, const Eina_File *file)
170 {
171    Eina_Inlist *l;
172    Elm_Theme_File *item;
173 
174    EINA_INLIST_FOREACH_SAFE(*files, l, item)
175      {
176         if (item->handle != file) continue;
177         eina_file_close(item->handle); // close matching open (file consumed in finalize by putting it in etf) OK
178         eina_stringshare_del(item->item);
179         *files = eina_inlist_remove(*files, EINA_INLIST_GET(item));
180         free(item);
181      }
182 }
183 
184 static void
_elm_theme_file_clean(Eina_Inlist ** files)185 _elm_theme_file_clean(Eina_Inlist **files)
186 {
187    while (*files)
188      {
189         Elm_Theme_File *etf = EINA_INLIST_CONTAINER_GET(*files, Elm_Theme_File);
190 
191         eina_stringshare_del(etf->item);
192         eina_file_close(etf->handle); // close matching open (file consumed in finalize by putting it in etf) OK
193         eina_stringshare_del(etf->match_theme);
194         *files = eina_inlist_remove(*files, *files);
195         free(etf);
196      }
197 }
198 
199 static void
_elm_theme_clear(Elm_Theme * th)200 _elm_theme_clear(Elm_Theme *th)
201 {
202    Efl_Ui_Theme_Data *td;
203 
204    _elm_theme_file_clean(&th->themes);
205    _elm_theme_file_clean(&th->overlay);
206    _elm_theme_file_clean(&th->extension);
207 
208    ELM_SAFE_FREE(th->overlay_items, eina_list_free);
209    ELM_SAFE_FREE(th->theme_items, eina_list_free);
210    ELM_SAFE_FREE(th->extension_items, eina_list_free);
211 
212    ELM_SAFE_FREE(th->cache, eina_hash_free);
213    ELM_SAFE_FREE(th->cache_data, eina_hash_free);
214    ELM_SAFE_FREE(th->cache_style_load_failed, eina_hash_free);
215    ELM_SAFE_FREE(th->theme, eina_stringshare_del);
216    if (th->ref_theme)
217      {
218         th->ref_theme->referrers =
219            eina_list_remove(th->ref_theme->referrers, th);
220         elm_theme_free(th->ref_theme);
221         th->ref_theme = NULL;
222      }
223 
224    td = efl_data_scope_get(th->eo_theme, EFL_UI_THEME_CLASS);
225    td->th = NULL;
226    th->eo_theme = NULL;
227 }
228 
229 static Eina_File *
_elm_theme_group_file_find_internal(Elm_Theme * th,const char * group,Eina_Bool force)230 _elm_theme_group_file_find_internal(Elm_Theme *th, const char *group, Eina_Bool force)
231 {
232    Elm_Theme_File *etf;
233    Eina_File *file = eina_hash_find(th->cache, group);
234 
235    if (file) return file;
236 
237    EINA_INLIST_FOREACH(th->overlay, etf)
238      {
239         file = _elm_theme_find_try(th, etf, group, force);
240         if (file) return file;
241      }
242    EINA_INLIST_FOREACH(th->themes, etf)
243      {
244         file = _elm_theme_find_try(th, etf, group, force);
245         if (file) return file;
246      }
247    EINA_INLIST_FOREACH(th->extension, etf)
248      {
249         file = _elm_theme_find_try(th, etf, group, force);
250         if (file) return file;
251      }
252    if (th->ref_theme) return _elm_theme_group_file_find_internal(th->ref_theme, group, force);
253    return NULL;
254 }
255 
256 Eina_File *
_elm_theme_group_file_find(Elm_Theme * th,const char * group)257 _elm_theme_group_file_find(Elm_Theme *th, const char *group)
258 {
259    Eina_File *file = _elm_theme_group_file_find_internal(th, group, EINA_FALSE);
260    if (file) return file;
261    file = _elm_theme_group_file_find_internal(th, group, EINA_TRUE);
262    return file;
263 }
264 
265 static const char *
_elm_theme_find_data_try(Elm_Theme * th,const Eina_File * f,const char * key)266 _elm_theme_find_data_try(Elm_Theme *th, const Eina_File *f, const char *key)
267 {
268    char *data;
269    const char *t;
270 
271    data = edje_mmap_data_get(f, key);
272    t = eina_stringshare_add(data);
273    free(data);
274    if (t)
275      {
276         eina_hash_add(th->cache_data, key, t);
277         return t;
278      }
279    return NULL;
280 }
281 
282 static const char *
_elm_theme_data_find(Elm_Theme * th,const char * key)283 _elm_theme_data_find(Elm_Theme *th, const char *key)
284 {
285    Elm_Theme_File *etf;
286 
287    const char *data = eina_hash_find(th->cache_data, key);
288    if (data) return data;
289 
290    EINA_INLIST_FOREACH(th->overlay, etf)
291      {
292         data = _elm_theme_find_data_try(th, etf->handle, key);
293         if (data) return data;
294      }
295    EINA_INLIST_FOREACH(th->themes, etf)
296      {
297         data = _elm_theme_find_data_try(th, etf->handle, key);
298         if (data) return data;
299      }
300    EINA_INLIST_FOREACH(th->extension, etf)
301      {
302         data = _elm_theme_find_data_try(th, etf->handle, key);
303         if (data) return data;
304      }
305    if (th->ref_theme) return _elm_theme_data_find(th->ref_theme, key);
306    return NULL;
307 }
308 
309 Eina_Error
_elm_theme_object_set(Evas_Object * parent,Evas_Object * o,const char * clas,const char * group,const char * style)310 _elm_theme_object_set(Evas_Object *parent, Evas_Object *o, const char *clas, const char *group, const char *style)
311 {
312    Elm_Theme *th = NULL;
313 
314    if (parent) th = elm_widget_theme_get(parent);
315 
316    return _elm_theme_set(th, o, clas, group, style, elm_widget_is_legacy(parent));
317 }
318 
319 /* only issued by elm_icon.c */
320 Eina_Bool
_elm_theme_object_icon_set(Evas_Object * o,const char * group,const char * style)321 _elm_theme_object_icon_set(Evas_Object *o,
322                            const char *group,
323                            const char *style)
324 {
325    Elm_Theme *th = elm_widget_theme_get(o);
326 
327    return _elm_theme_icon_set(th, o, group, style);
328 }
329 
330 Eina_Error
_elm_theme_set(Elm_Theme * th,Evas_Object * o,const char * clas,const char * group,const char * style,Eina_Bool is_legacy)331 _elm_theme_set(Elm_Theme *th, Evas_Object *o, const char *clas, const char *group, const char *style, Eina_Bool is_legacy)
332 {
333    Eina_File *file;
334    char buf2[1024];
335    const char *group_sep = "/";
336    const char *style_sep = ":";
337 
338    if ((!clas) || !o) return EFL_UI_THEME_APPLY_ERROR_GENERIC;
339    if (!th) th = theme_default;
340    if (!th) return EFL_UI_THEME_APPLY_ERROR_GENERIC;
341 
342    if (eina_streq(style, "default")) style = NULL;
343 
344    if (is_legacy)
345      snprintf(buf2, sizeof(buf2), "elm/%s/%s/%s", clas, (group) ? group : "base", (style) ? style : "default");
346    else
347      snprintf(buf2, sizeof(buf2), "efl/%s%s%s%s%s", clas,
348             ((group) ? group_sep : "\0"), ((group) ? group : "\0"),
349             ((style) ? style_sep : "\0"), ((style) ? style : "\0"));
350    if (!eina_hash_find(th->cache_style_load_failed, buf2))
351      {
352         file = _elm_theme_group_file_find(th, buf2);
353         if (file)
354           {
355              if (edje_object_mmap_set(o, file, buf2)) return EFL_UI_THEME_APPLY_ERROR_NONE;
356              else
357                {
358                   ERR("could not set theme group '%s' from file '%s': %s",
359                       buf2,
360                       eina_file_filename_get(file),
361                       edje_load_error_str(edje_object_load_error_get(o)));
362                }
363           }
364         //style not found, add to the not found list
365         eina_hash_add(th->cache_style_load_failed, buf2, (void *)1);
366      }
367 
368    if (!style)
369      return EFL_UI_THEME_APPLY_ERROR_GENERIC;
370 
371    // Use the elementary default style.
372    if (_elm_theme_set(th, o, clas, group, NULL, is_legacy) == EFL_UI_THEME_APPLY_ERROR_NONE)
373      return EFL_UI_THEME_APPLY_ERROR_DEFAULT;
374    return EFL_UI_THEME_APPLY_ERROR_GENERIC;
375 }
376 
377 Eina_Bool
_elm_theme_icon_set(Elm_Theme * th,Evas_Object * o,const char * group,const char * style)378 _elm_theme_icon_set(Elm_Theme *th,
379                     Evas_Object *o,
380                     const char *group,
381                     const char *style)
382 {
383    Eina_File *file;
384    char buf2[1024];
385    int w, h;
386 
387    if (efl_isa((o), ELM_ICON_CLASS) && elm_icon_standard_get(o) &&
388        strcmp(elm_config_icon_theme_get(), ELM_CONFIG_ICON_THEME_ELEMENTARY))
389      {
390         elm_icon_standard_set(o, elm_icon_standard_get(o));
391         return EINA_TRUE;
392      }
393 
394    if (!th) th = theme_default;
395    if (!th) return EINA_FALSE;
396 
397    snprintf(buf2, sizeof(buf2), "elm/icon/%s/%s", group, style);
398    file = _elm_theme_group_file_find(th, buf2);
399    if (file)
400      {
401         elm_image_mmap_set(o, file, buf2);
402         elm_image_object_size_get(o, &w, &h);
403         if (w > 0) return EINA_TRUE;
404      }
405    snprintf(buf2, sizeof(buf2), "elm/icon/%s/default", group);
406    file = _elm_theme_group_file_find(th, buf2);
407 
408    if (!file) return EINA_FALSE;
409 
410    elm_image_mmap_set(o, file, buf2);
411    elm_image_object_size_get(o, &w, &h);
412 
413    return w > 0;
414 }
415 
416 void
_elm_theme_parse(Elm_Theme * th,const char * theme)417 _elm_theme_parse(Elm_Theme *th, const char *theme)
418 {
419    Eina_List *names = NULL;
420    const char *p, *pe;
421 
422    if (!th) th = theme_default;
423    if (!th) return;
424 
425    if (theme)
426      {
427         Eina_Strbuf *buf;
428 
429         buf = eina_strbuf_new();
430 
431         p = theme;
432         pe = p;
433         for (;;)
434           {
435              if ((pe[0] == '\\') && (pe[1] == ':'))
436                {
437                   eina_strbuf_append_char(buf, ':');
438                   pe += 2;
439                }
440 #ifdef HAVE_ELEMENTARY_WIN32
441              else if (isalpha(pe[0]) && (pe[1] == ':') &&
442                       ((pe[2] == '/') || (pe[2] == '\\')))
443                {
444                   // Correct processing file path on  Windows OS "<disk>:/" or "<disk>:\"
445                   eina_strbuf_append_char(buf, *pe);
446                   pe++;
447                   eina_strbuf_append_char(buf, *pe);
448                   pe++;
449                }
450 #endif
451              else if ((*pe == ':') || (!*pe))
452                { // p -> pe == 'name:'
453                   if (pe > p)
454                     {
455                        const char *nn;
456 
457                        nn = eina_stringshare_add(eina_strbuf_string_get(buf));
458                        if (nn) names = eina_list_append(names, nn);
459                        eina_strbuf_reset(buf);
460                     }
461                   if (!*pe) break;
462                   p = pe + 1;
463                   pe = p;
464                }
465              else
466                {
467                   eina_strbuf_append_char(buf, *pe);
468                   pe++;
469                }
470           }
471 
472         eina_strbuf_free(buf);
473      }
474    p = eina_list_data_get(eina_list_last(names));
475    if ((!p) || (strcmp(p, "default")))
476      {
477         p = eina_stringshare_add("default");
478         if (p) names = eina_list_append(names, p);
479      }
480    if (th->cache) eina_hash_free(th->cache);
481    th->cache = eina_hash_string_superfast_new(EINA_FREE_CB(eina_file_close));
482    if (th->cache_data) eina_hash_free(th->cache_data);
483    th->cache_data = eina_hash_string_superfast_new(EINA_FREE_CB(eina_stringshare_del));
484    if (th->cache_style_load_failed) eina_hash_free(th->cache_style_load_failed);
485    th->cache_style_load_failed = eina_hash_string_superfast_new(NULL);
486    _elm_theme_file_clean(&th->themes);
487    th->theme_items = eina_list_free(th->theme_items);
488 
489    EINA_LIST_FREE(names, p)
490      _elm_theme_file_item_add(&th->themes, p, EINA_FALSE, EINA_TRUE);
491    elm_theme_get(th);
492 }
493 
494 void
_elm_theme_init(void)495 _elm_theme_init(void)
496 {
497    Eo *theme_default_obj;
498    Efl_Ui_Theme_Data *td;
499 
500    if (theme_default) return;
501 
502    theme_default_obj = efl_add(EFL_UI_THEME_CLASS, efl_main_loop_get());
503    td = efl_data_scope_get(theme_default_obj, EFL_UI_THEME_CLASS);
504    theme_default = td->th;
505 }
506 
507 void
_elm_theme_shutdown(void)508 _elm_theme_shutdown(void)
509 {
510    Elm_Theme *th;
511 
512    while (themes)
513      {
514         th = eina_list_data_get(themes);
515 
516         /* In theme object destructor, theme is deallocated and the theme is
517          * removed from themes list in _elm_theme_free_internal().
518          * Consequently, themes list is updated.
519          */
520         efl_del(th->eo_theme);
521      }
522 }
523 
524 EAPI Elm_Theme *
elm_theme_new(void)525 elm_theme_new(void)
526 {
527    Eo *obj = efl_add(EFL_UI_THEME_CLASS, efl_main_loop_get());
528    Efl_Ui_Theme_Data *td = efl_data_scope_get(obj, EFL_UI_THEME_CLASS);
529    return td->th;
530 }
531 
532 EAPI void
elm_theme_free(Elm_Theme * th)533 elm_theme_free(Elm_Theme *th)
534 {
535    EINA_SAFETY_ON_NULL_RETURN(th);
536 
537    /* Destructs theme object and theme is deallocated in
538     * _elm_theme_free_internal() in theme object desctructor.
539     */
540    if (efl_ref_count(th->eo_theme) > 1)
541      efl_unref(th->eo_theme);
542    else
543      efl_del(th->eo_theme);
544 }
545 
546 static void
elm_theme_files_copy(Eina_Inlist ** dst,Eina_Inlist ** src)547 elm_theme_files_copy(Eina_Inlist **dst, Eina_Inlist **src)
548 {
549    Elm_Theme_File *etf, *cpy;
550 
551    EINA_INLIST_FOREACH(*src, etf)
552      {
553         cpy = malloc(sizeof(Elm_Theme_File));
554         EINA_SAFETY_ON_NULL_RETURN(cpy);
555         cpy->item = eina_stringshare_ref(etf->item);
556         cpy->handle = eina_file_dup(etf->handle);
557         *dst = eina_inlist_append(*dst, EINA_INLIST_GET(cpy));
558      }
559 }
560 
561 EAPI void
elm_theme_copy(Elm_Theme * th,Elm_Theme * thdst)562 elm_theme_copy(Elm_Theme *th, Elm_Theme *thdst)
563 {
564    Eo *thdst_obj;
565    Efl_Ui_Theme_Data *thdst_td;
566 
567    if (!th) th = theme_default;
568    if (!th) return;
569    if (!thdst) thdst = theme_default;
570    if (!thdst) return;
571 
572    //Clear the given theme and restore the relationship with theme object.
573    thdst_obj = thdst->eo_theme;
574    _elm_theme_clear(thdst);
575    thdst->eo_theme = thdst_obj;
576    thdst_td = efl_data_scope_get(thdst_obj, EFL_UI_THEME_CLASS);
577    thdst_td->th = thdst;
578 
579    if (th->ref_theme)
580      {
581         thdst->ref_theme = th->ref_theme;
582         thdst->ref_theme->referrers =
583            eina_list_append(thdst->ref_theme->referrers, thdst);
584         efl_ref(thdst->ref_theme->eo_theme);
585      }
586    elm_theme_files_copy(&thdst->overlay, &th->overlay);
587    elm_theme_files_copy(&thdst->themes, &th->themes);
588    elm_theme_files_copy(&thdst->extension, &th->extension);
589 
590    if (th->theme) thdst->theme = eina_stringshare_add(th->theme);
591    elm_theme_flush(thdst);
592 }
593 
594 EAPI void
elm_theme_ref_set(Elm_Theme * th,Elm_Theme * thref)595 elm_theme_ref_set(Elm_Theme *th, Elm_Theme *thref)
596 {
597    Eo *th_obj;
598    Efl_Ui_Theme_Data *th_td;
599 
600    if (!th) th = theme_default;
601    if (!th) return;
602    if (!thref) thref = theme_default;
603    if (!thref) return;
604    if (th->ref_theme == thref) return;
605 
606    //Clear the given theme and restore the relationship with theme object.
607    th_obj = th->eo_theme;
608    _elm_theme_clear(th);
609    th->eo_theme = th_obj;
610    th_td = efl_data_scope_get(th_obj, EFL_UI_THEME_CLASS);
611    th_td->th = th;
612 
613    if (thref)
614      {
615         thref->referrers = eina_list_append(thref->referrers, th);
616         efl_ref(thref->eo_theme);
617      }
618    th->ref_theme = thref;
619    elm_theme_flush(th);
620 }
621 
622 EAPI Elm_Theme *
elm_theme_ref_get(const Elm_Theme * th)623 elm_theme_ref_get(const Elm_Theme *th)
624 {
625    if (!th) th = theme_default;
626    if (!th) return NULL;
627    return th->ref_theme;
628 }
629 
630 EAPI Elm_Theme *
elm_theme_default_get(void)631 elm_theme_default_get(void)
632 {
633    return theme_default;
634 }
635 
636 EAPI void
elm_theme_overlay_add(Elm_Theme * th,const char * item)637 elm_theme_overlay_add(Elm_Theme *th, const char *item)
638 {
639    if (!th) th = theme_default;
640    if (!th) return;
641    efl_ui_theme_overlay_add(th->eo_theme, item);
642 }
643 
644 EAPI void
elm_theme_overlay_del(Elm_Theme * th,const char * item)645 elm_theme_overlay_del(Elm_Theme *th, const char *item)
646 {
647    if (!th) th = theme_default;
648    if (!th) return;
649    efl_ui_theme_overlay_del(th->eo_theme, item);
650 }
651 
652 EAPI void
elm_theme_overlay_mmap_add(Elm_Theme * th,const Eina_File * f)653 elm_theme_overlay_mmap_add(Elm_Theme *th, const Eina_File *f)
654 {
655    Eina_File *file = eina_file_dup(f);
656 
657    if (!th) th = theme_default;
658    if (!th)
659      {
660         eina_file_close(file); // close matching open (finalize expected to consume eina file) OK
661         return;
662      }
663    th->overlay_items = eina_list_free(th->overlay_items);
664    _elm_theme_item_finalize(&th->overlay, eina_file_filename_get(file), file, EINA_TRUE, EINA_FALSE);
665    elm_theme_flush(th);
666 }
667 
668 EAPI void
elm_theme_overlay_mmap_del(Elm_Theme * th,const Eina_File * f)669 elm_theme_overlay_mmap_del(Elm_Theme *th, const Eina_File *f)
670 {
671    if (!f) return;
672    if (!th) th = theme_default;
673    if (!th) return;
674    th->overlay_items = eina_list_free(th->overlay_items);
675    _elm_theme_file_mmap_del(&th->overlay, f);
676    elm_theme_flush(th);
677 }
678 
679 EAPI const Eina_List *
elm_theme_overlay_list_get(const Elm_Theme * th)680 elm_theme_overlay_list_get(const Elm_Theme *th)
681 {
682    if (!th) th = theme_default;
683    if (!th) return NULL;
684    if (!th->overlay_items)
685      {
686         Elm_Theme_File *etf;
687 
688         EINA_INLIST_FOREACH(th->overlay, etf)
689           ((Elm_Theme*)th)->overlay_items = eina_list_append(th->overlay_items, etf->item);
690      }
691    return th->overlay_items;
692 }
693 
694 EAPI void
elm_theme_extension_add(Elm_Theme * th,const char * item)695 elm_theme_extension_add(Elm_Theme *th, const char *item)
696 {
697    if (!th) th = theme_default;
698    if (!th) return;
699    efl_ui_theme_extension_add(th->eo_theme, item);
700 }
701 
702 EAPI void
elm_theme_extension_del(Elm_Theme * th,const char * item)703 elm_theme_extension_del(Elm_Theme *th, const char *item)
704 {
705    if (!th) th = theme_default;
706    if (!th) return;
707    efl_ui_theme_extension_del(th->eo_theme, item);
708 }
709 
710 EAPI void
elm_theme_extension_mmap_add(Elm_Theme * th,const Eina_File * f)711 elm_theme_extension_mmap_add(Elm_Theme *th, const Eina_File *f)
712 {
713    Eina_File *file;
714 
715    if (!f) return;
716    if (!th) th = theme_default;
717    if (!th) return;
718    file = eina_file_dup(f);
719    th->extension_items = eina_list_free(th->extension_items);
720    _elm_theme_item_finalize(&th->extension, eina_file_filename_get(file), file, EINA_FALSE, EINA_FALSE);
721    elm_theme_flush(th);
722 }
723 
724 EAPI void
elm_theme_extension_mmap_del(Elm_Theme * th,const Eina_File * f)725 elm_theme_extension_mmap_del(Elm_Theme *th, const Eina_File *f)
726 {
727    if (!f) return;
728    if (!th) th = theme_default;
729    if (!th) return;
730    th->extension_items = eina_list_free(th->extension_items);
731    _elm_theme_file_mmap_del(&th->extension, f);
732    elm_theme_flush(th);
733 }
734 
735 EAPI const Eina_List *
elm_theme_extension_list_get(const Elm_Theme * th)736 elm_theme_extension_list_get(const Elm_Theme *th)
737 {
738    if (!th) th = theme_default;
739    if (!th) return NULL;
740    if (!th->extension_items)
741      {
742         Elm_Theme_File *etf;
743 
744         EINA_INLIST_FOREACH(th->extension, etf)
745           ((Elm_Theme*)th)->extension_items = eina_list_append(th->extension_items, etf->item);
746      }
747    return th->extension_items;
748 }
749 
750 EAPI void
elm_theme_set(Elm_Theme * th,const char * theme)751 elm_theme_set(Elm_Theme *th, const char *theme)
752 {
753    if (!th) th = theme_default;
754    if (!th) return;
755    _elm_theme_parse(th, theme);
756    ELM_SAFE_FREE(th->theme, eina_stringshare_del);
757    elm_theme_flush(th);
758    if (th == theme_default)
759      eina_stringshare_replace(&_elm_config->theme, theme);
760 }
761 
762 EAPI const char *
elm_theme_get(Elm_Theme * th)763 elm_theme_get(Elm_Theme *th)
764 {
765    if (!th) th = theme_default;
766    if (!th) return NULL;
767    if (!th->theme)
768      {
769         Eina_Strbuf *buf;
770         Elm_Theme_File *etf;
771         const char *f;
772 
773         buf = eina_strbuf_new();
774         EINA_INLIST_FOREACH(th->themes, etf)
775           {
776              f = etf->item;
777              while (*f)
778                {
779                   if (*f == ':')
780                     eina_strbuf_append_char(buf, '\\');
781                   eina_strbuf_append_char(buf, *f);
782 
783                   f++;
784                }
785              if (EINA_INLIST_GET(etf)->next)
786                eina_strbuf_append_char(buf, ':');
787           }
788         th->theme = eina_stringshare_add(eina_strbuf_string_get(buf));
789         eina_strbuf_free(buf);
790      }
791    return th->theme;
792 }
793 
794 EAPI const Eina_List *
elm_theme_list_get(const Elm_Theme * th)795 elm_theme_list_get(const Elm_Theme *th)
796 {
797    if (!th) th = theme_default;
798    if (!th) return NULL;
799    if (!th->theme_items)
800      {
801         Elm_Theme_File *etf;
802 
803         EINA_INLIST_FOREACH(th->themes, etf)
804           ((Elm_Theme*)th)->theme_items = eina_list_append(th->theme_items, etf->item);
805      }
806    return th->theme_items;
807 }
808 
809 EAPI char *
elm_theme_list_item_path_get(const char * f,Eina_Bool * in_search_path)810 elm_theme_list_item_path_get(const char *f, Eina_Bool *in_search_path)
811 {
812    static const char *home = NULL;
813    char buf[PATH_MAX];
814 
815    if (!f)
816      {
817         if (in_search_path) *in_search_path = EINA_FALSE;
818         return NULL;
819      }
820 
821    if (!home)
822      {
823         home = eina_environment_home_get();
824         if (!home) home = "";
825      }
826 
827    if ((f[0] == '/') || ((f[0] == '.') && (f[1] == '/')) ||
828        ((f[0] == '.') && (f[1] == '.') && (f[2] == '/')) ||
829        ((isalpha(f[0])) && (f[1] == ':')))
830      {
831         if (in_search_path) *in_search_path = EINA_FALSE;
832         return strdup(f);
833      }
834    else if (((f[0] == '~') && (f[1] == '/')))
835      {
836         if (in_search_path) *in_search_path = EINA_FALSE;
837         snprintf(buf, sizeof(buf), "%s/%s", home, f + 2);
838         return strdup(buf);
839      }
840    snprintf(buf, sizeof(buf), "%s/"ELEMENTARY_BASE_DIR"/themes/%s.edj", home, f);
841    if (ecore_file_exists(buf))
842      {
843         if (in_search_path) *in_search_path = EINA_TRUE;
844         return strdup(buf);
845      }
846 
847    snprintf(buf, sizeof(buf), "%s/themes/%s.edj", _elm_data_dir, f);
848    if (ecore_file_exists(buf))
849      {
850         if (in_search_path) *in_search_path = EINA_TRUE;
851         return strdup(buf);
852      }
853 
854    if (in_search_path) *in_search_path = EINA_FALSE;
855    return NULL;
856 }
857 
858 EAPI void
elm_theme_flush(Elm_Theme * th)859 elm_theme_flush(Elm_Theme *th)
860 {
861    if (!th) th = theme_default;
862    if (!th) return;
863    if (th->cache) eina_hash_free(th->cache);
864    th->cache = eina_hash_string_superfast_new(EINA_FREE_CB(eina_file_close));
865    if (th->cache_data) eina_hash_free(th->cache_data);
866    th->cache_data = eina_hash_string_superfast_new(EINA_FREE_CB(eina_stringshare_del));
867    if (th->cache_style_load_failed) eina_hash_free(th->cache_style_load_failed);
868    th->cache_style_load_failed = eina_hash_string_superfast_new(NULL);
869    _elm_win_rescale(th, EINA_TRUE);
870    _elm_ews_wm_rescale(th, EINA_TRUE);
871    if (th->referrers)
872      {
873         Eina_List *l;
874         Elm_Theme *th2;
875 
876         EINA_LIST_FOREACH(th->referrers, l, th2) elm_theme_flush(th2);
877      }
878 }
879 
880 EAPI void
elm_theme_full_flush(void)881 elm_theme_full_flush(void)
882 {
883    Eina_List *l;
884    Elm_Theme *th;
885 
886    EINA_LIST_FOREACH(themes, l, th)
887      {
888         elm_theme_flush(th);
889      }
890    elm_theme_flush(theme_default);
891 }
892 
893 EAPI Eina_List *
elm_theme_name_available_list_new(void)894 elm_theme_name_available_list_new(void)
895 {
896    Eina_List *list = NULL;
897    Eina_List *dir, *l;
898    char buf[PATH_MAX], *file, *s, *th;
899    static const char *home = NULL;
900 
901    if (!home)
902      {
903         home = eina_environment_home_get();
904         if (!home) home = "";
905      }
906 
907    snprintf(buf, sizeof(buf), "%s/"ELEMENTARY_BASE_DIR"/themes", home);
908    dir = ecore_file_ls(buf);
909    EINA_LIST_FREE(dir, file)
910      {
911         snprintf(buf, sizeof(buf), "%s/"ELEMENTARY_BASE_DIR"/themes/%s", home, file);
912         if ((!ecore_file_is_dir(buf)) && (ecore_file_size(buf) > 0))
913           {
914              if (eina_str_has_extension(file, ".edj"))
915                {
916                   th = strdup(file);
917                   s = strrchr(th, '.');
918                   *s = 0;
919                   list = eina_list_append(list, th);
920                }
921           }
922         free(file);
923      }
924 
925    snprintf(buf, sizeof(buf), "%s/themes", _elm_data_dir);
926    dir = ecore_file_ls(buf);
927    EINA_LIST_FREE(dir, file)
928      {
929         snprintf(buf, sizeof(buf), "%s/themes/%s", _elm_data_dir, file);
930         if ((!ecore_file_is_dir(buf)) && (ecore_file_size(buf) > 0))
931           {
932              if (eina_str_has_extension(file, ".edj"))
933                {
934                   int dupp;
935 
936                   th = strdup(file);
937                   s = strrchr(th, '.');
938                   *s = 0;
939                   dupp = 0;
940                   EINA_LIST_FOREACH(list, l, s)
941                     {
942                        if (!strcmp(s, th))
943                          {
944                             dupp = 1;
945                             break;
946                          }
947                     }
948                   if (dupp) free(th);
949                   else list = eina_list_append(list, th);
950                }
951           }
952         free(file);
953      }
954    list = eina_list_sort(list, 0, EINA_COMPARE_CB(strcasecmp));
955    return list;
956 }
957 
958 EAPI void
elm_theme_name_available_list_free(Eina_List * list)959 elm_theme_name_available_list_free(Eina_List *list)
960 {
961    char *s;
962    EINA_LIST_FREE(list, s) free(s);
963 }
964 
965 EAPI void
elm_object_theme_set(Evas_Object * obj,Elm_Theme * th)966 elm_object_theme_set(Evas_Object *obj, Elm_Theme *th)
967 {
968    EINA_SAFETY_ON_NULL_RETURN(obj);
969    elm_widget_theme_set(obj, th);
970 }
971 
972 EAPI Elm_Theme *
elm_object_theme_get(const Evas_Object * obj)973 elm_object_theme_get(const Evas_Object *obj)
974 {
975    EINA_SAFETY_ON_NULL_RETURN_VAL(obj, NULL);
976    return elm_widget_theme_get(obj);
977 }
978 
979 EAPI const char *
elm_theme_data_get(Elm_Theme * th,const char * key)980 elm_theme_data_get(Elm_Theme *th, const char *key)
981 {
982    if (!th) th = theme_default;
983    if (!th) return NULL;
984    return _elm_theme_data_find(th, key);
985 }
986 
987 EAPI const char *
elm_theme_group_path_find(Elm_Theme * th,const char * group)988 elm_theme_group_path_find(Elm_Theme *th, const char *group)
989 {
990    EINA_SAFETY_ON_NULL_RETURN_VAL(group, NULL);
991    Eina_File *th_file = NULL;
992    if (!th) th = theme_default;
993    if (!th) return NULL;
994 
995    th_file = _elm_theme_group_file_find(th, group);
996    if (th_file)
997      return eina_file_filename_get(th_file);
998    return NULL;
999 }
1000 
1001 static Eina_List *
_elm_theme_file_group_base_list(Eina_List * list,Eina_Inlist * handles,const char * base,int len)1002 _elm_theme_file_group_base_list(Eina_List *list,
1003                                 Eina_Inlist *handles,
1004                                 const char *base, int len)
1005 {
1006    Eina_Stringshare *c, *c2;
1007    Eina_List *coll;
1008    Eina_List *in, *ll;
1009    Elm_Theme_File *etf;
1010 
1011    EINA_INLIST_FOREACH(handles, etf)
1012      {
1013         coll = edje_mmap_collection_list(etf->handle);
1014         EINA_LIST_FOREACH(coll, ll, c)
1015           {
1016              // if base == start of collection str
1017              if (!strncmp(c, base, len))
1018                {
1019                   // check if already in list
1020                   EINA_LIST_FOREACH(list, in, c2)
1021                     if (c == c2)
1022                       break;
1023 
1024                   // if not already in list append shared str to list
1025                   if (!in)
1026                     list = eina_list_append(list, eina_stringshare_ref(c));
1027                }
1028           }
1029         edje_mmap_collection_list_free(coll);
1030      }
1031 
1032    return list;
1033 }
1034 
1035 EAPI Eina_List *
elm_theme_group_base_list(Elm_Theme * th,const char * base)1036 elm_theme_group_base_list(Elm_Theme *th, const char *base)
1037 {
1038    Eina_List *list;
1039    int len;
1040 
1041    EINA_SAFETY_ON_NULL_RETURN_VAL(base, NULL);
1042    if (!th) th = theme_default;
1043    if (!th) return NULL;
1044 
1045    // XXX: look results up in a hash for speed
1046    len = strlen(base);
1047    // go through all possible theme files and find collections that match
1048    list = _elm_theme_file_group_base_list(NULL, th->overlay,
1049                                           base, len);
1050    list = _elm_theme_file_group_base_list(list, th->themes,
1051                                           base, len);
1052    list = _elm_theme_file_group_base_list(list, th->extension,
1053                                           base, len);
1054 
1055    // sort the list nicely at the end
1056    list = eina_list_sort(list, 0, EINA_COMPARE_CB(strcmp));
1057    // XXX: store results in hash for fast lookup...
1058    return list;
1059 }
1060 
1061 EAPI const char *
elm_theme_system_dir_get(void)1062 elm_theme_system_dir_get(void)
1063 {
1064    static char *path = NULL;
1065    char buf[PATH_MAX];
1066 
1067    if (path) return path;
1068 
1069    snprintf(buf, sizeof(buf), "%s/themes", _elm_data_dir);
1070    path = strdup(buf);
1071 
1072    return path;
1073 }
1074 
1075 EAPI const char *
elm_theme_user_dir_get(void)1076 elm_theme_user_dir_get(void)
1077 {
1078    static char *path = NULL;
1079    char buf[PATH_MAX];
1080    const char *home;
1081 
1082    if (path) return path;
1083 
1084    home = eina_environment_home_get();
1085    snprintf(buf, sizeof(buf), "%s/"ELEMENTARY_BASE_DIR"/themes", home);
1086    path = strdup(buf);
1087 
1088    return path;
1089 }
1090 
1091 /* Allocates memory for theme and appends the theme to themes list. */
1092 static Elm_Theme *
_elm_theme_new_internal(Eo * obj)1093 _elm_theme_new_internal(Eo *obj)
1094 {
1095    Elm_Theme *th = calloc(1, sizeof(Elm_Theme));
1096    if (!th) return NULL;
1097    _elm_theme_file_item_add(&th->themes, "default", EINA_FALSE, EINA_TRUE);
1098    themes = eina_list_append(themes, th);
1099    th->eo_theme = obj;
1100    return th;
1101 }
1102 
1103 EOLIAN static Eo *
_efl_ui_theme_efl_object_constructor(Eo * obj,Efl_Ui_Theme_Data * pd)1104 _efl_ui_theme_efl_object_constructor(Eo *obj, Efl_Ui_Theme_Data *pd)
1105 {
1106    obj = efl_constructor(efl_super(obj, EFL_UI_THEME_CLASS));
1107 
1108    pd->th = _elm_theme_new_internal(obj);
1109 
1110    return obj;
1111 }
1112 
1113 /* Removes the given theme from themes list and deallocates the given theme. */
1114 static void
_elm_theme_free_internal(Elm_Theme * th)1115 _elm_theme_free_internal(Elm_Theme *th)
1116 {
1117    _elm_theme_clear(th);
1118    themes = eina_list_remove(themes, th);
1119    free(th);
1120 }
1121 
1122 EOLIAN static void
_efl_ui_theme_efl_object_destructor(Eo * obj,Efl_Ui_Theme_Data * pd)1123 _efl_ui_theme_efl_object_destructor(Eo *obj, Efl_Ui_Theme_Data *pd)
1124 {
1125    Eina_Bool is_theme_default = EINA_FALSE;
1126 
1127    if (pd->th == theme_default)
1128      is_theme_default = EINA_TRUE;
1129 
1130    _elm_theme_free_internal(pd->th);
1131 
1132    if (is_theme_default) theme_default = NULL;
1133 
1134    efl_destructor(efl_super(obj, EFL_UI_THEME_CLASS));
1135 }
1136 
1137 EOLIAN static Efl_Ui_Theme *
_efl_ui_theme_default_get(void)1138 _efl_ui_theme_default_get(void)
1139 {
1140    if (theme_default)
1141      return theme_default->eo_theme;
1142 
1143    return NULL;
1144 }
1145 
1146 EOLIAN static void
_efl_ui_theme_extension_add(Eo * obj EINA_UNUSED,Efl_Ui_Theme_Data * pd,const char * item)1147 _efl_ui_theme_extension_add(Eo *obj EINA_UNUSED, Efl_Ui_Theme_Data *pd, const char *item)
1148 {
1149    if (!item) return;
1150    pd->th->extension_items = eina_list_free(pd->th->extension_items);
1151    _elm_theme_file_item_add(&pd->th->extension, item, EINA_FALSE, EINA_FALSE);
1152    elm_theme_flush(pd->th);
1153 }
1154 
1155 EOLIAN static void
_efl_ui_theme_extension_del(Eo * obj EINA_UNUSED,Efl_Ui_Theme_Data * pd,const char * item)1156 _efl_ui_theme_extension_del(Eo *obj EINA_UNUSED, Efl_Ui_Theme_Data *pd, const char *item)
1157 {
1158    if (!item) return;
1159    pd->th->extension_items = eina_list_free(pd->th->extension_items);
1160    _elm_theme_file_item_del(&pd->th->extension, item);
1161    elm_theme_flush(pd->th);
1162 }
1163 
1164 EOLIAN static void
_efl_ui_theme_overlay_add(Eo * obj EINA_UNUSED,Efl_Ui_Theme_Data * pd,const char * item)1165 _efl_ui_theme_overlay_add(Eo *obj EINA_UNUSED, Efl_Ui_Theme_Data *pd, const char *item)
1166 {
1167    if (!item) return;
1168    pd->th->overlay_items = eina_list_free(pd->th->overlay_items);
1169    _elm_theme_file_item_add(&pd->th->overlay, item, EINA_TRUE, EINA_FALSE);
1170    elm_theme_flush(pd->th);
1171 }
1172 
1173 EOLIAN static void
_efl_ui_theme_overlay_del(Eo * obj EINA_UNUSED,Efl_Ui_Theme_Data * pd,const char * item)1174 _efl_ui_theme_overlay_del(Eo *obj EINA_UNUSED, Efl_Ui_Theme_Data *pd, const char *item)
1175 {
1176    if (!item) return;
1177    pd->th->overlay_items = eina_list_free(pd->th->overlay_items);
1178    _elm_theme_file_item_del(&pd->th->overlay, item);
1179    elm_theme_flush(pd->th);
1180 }
1181 
1182 #include "efl_ui_theme.eo.c"
1183