summaryrefslogtreecommitdiff
path: root/src/lib/elementary/efl_ui_format.c
blob: beb3945f431b4d086c8eb44abceb3420c579915e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
#define EFL_UI_FORMAT_PROTECTED 1

#include "config.h"
#include "Efl_Ui.h"
#include "elm_priv.h" /* To be able to use elm_widget_is_legacy() */

typedef enum _Format_Type
{
   /* When a format string is used, it is parsed to find out the expected data type */
   FORMAT_TYPE_INVALID, /* Format description not understood */
   FORMAT_TYPE_DOUBLE,  /* double */
   FORMAT_TYPE_INT,     /* int */
   FORMAT_TYPE_TM,      /* struct tm, for time and date values */
   FORMAT_TYPE_STRING,  /* const char* */
   FORMAT_TYPE_STATIC   /* No value is passed, the format string IS the formatted output */
} Format_Type;

typedef struct
{
   Efl_Ui_Format_Func  format_func;         /* User-supplied formatting function */
   void                *format_func_data;   /* User data for the above function */
   Eina_Free_Cb        format_func_free;    /* How to free the above data */

   Eina_Inarray        *format_values;      /* Array of formatting values, owned by us */

   const char          *format_string;      /* User-supplied formatting string, stringshare */
   Format_Type         format_string_type;  /* Type of data expected in the above string */
} Efl_Ui_Format_Data;

static Eina_Bool
_is_valid_digit(char x)
{
   return ((x >= '0' && x <= '9') || (x == '.')) ? EINA_TRUE : EINA_FALSE;
}

static Format_Type
_format_string_check(const char *fmt, Efl_Ui_Format_String_Type type)
{
   const char *itr;
   Eina_Bool found = EINA_FALSE;
   Format_Type ret_type = FORMAT_TYPE_STATIC;

   if (type == EFL_UI_FORMAT_STRING_TYPE_TIME) return FORMAT_TYPE_TM;

   for (itr = fmt; *itr; itr++)
     {
        if (itr[0] != '%') continue;
        if (itr[1] == '%')
          {
             itr++;
             if (ret_type == FORMAT_TYPE_STATIC)
               ret_type = FORMAT_TYPE_STRING;
             continue;
          }

        if (!found)
          {
             found = EINA_TRUE;
             for (itr++; *itr; itr++)
               {
                  // FIXME: This does not properly support int64 or unsigned.
                  if ((*itr == 'd') || (*itr == 'u') || (*itr == 'i') ||
                      (*itr == 'o') || (*itr == 'x') || (*itr == 'X'))
                    {
                       ret_type = FORMAT_TYPE_INT;
                       break;
                    }
                  else if ((*itr == 'f') || (*itr == 'F'))
                    {
                       ret_type = FORMAT_TYPE_DOUBLE;
                       break;
                    }
                  else if (*itr == 's')
                    {
                       ret_type = FORMAT_TYPE_STRING;
                       break;
                    }
                  else if (_is_valid_digit(*itr))
                    {
                       continue;
                    }
                  else
                    {
                       ERR("Format string '%s' has unknown format element '%c' in format. It must have one format element of type 's', 'f', 'F', 'd', 'u', 'i', 'o', 'x' or 'X'", fmt, *itr);
                       found = EINA_FALSE;
                       break;
                    }
               }
             if (!(*itr)) break;
          }
        else
          {
             ret_type = FORMAT_TYPE_INVALID;
             break;
          }
     }

   if (ret_type == FORMAT_TYPE_INVALID)
     {
        ERR("Format string '%s' is invalid. It must have one and only one format element of type 's', 'f', 'F', 'd', 'u', 'i', 'o', 'x' or 'X'", fmt);
     }
   return ret_type;
}

static Eina_Bool
_do_format_string(Efl_Ui_Format_Data *pd, Eina_Strbuf *str, const Eina_Value value)
{
   switch (pd->format_string_type)
     {
      case FORMAT_TYPE_DOUBLE:
      {
        double v = 0.0;
        eina_value_double_convert(&value, &v);
        eina_strbuf_append_printf(str, pd->format_string, v);
        break;
      }
      case FORMAT_TYPE_INT:
      {
        int v = 0;
        eina_value_int_convert(&value, &v);
        eina_strbuf_append_printf(str, pd->format_string, v);
        break;
      }
      case FORMAT_TYPE_STRING:
      {
        char *v = eina_value_to_string(&value);
        eina_strbuf_append_printf(str, pd->format_string, v);
        free(v);
        break;
      }
      case FORMAT_TYPE_STATIC:
      {
        eina_strbuf_append(str, pd->format_string);
        break;
      }
      case FORMAT_TYPE_TM:
      {
        struct tm v;
        char *buf;
        eina_value_get(&value, &v);
        buf = eina_strftime(pd->format_string, &v);
        eina_strbuf_append(str, buf);
        free(buf);
        break;
      }
      default:
        return EINA_FALSE;
     }
   return EINA_TRUE;
}

static Eina_Bool
_legacy_default_format_func(void *data, Eina_Strbuf *str, const Eina_Value value)
{
   if (!_do_format_string(data, str, value))
      {
        /* Fallback to just printing the value if format string fails (legacy behavior) */
        char *v = eina_value_to_string(&value);
        eina_strbuf_append(str, v);
        free(v);
      }
   return EINA_TRUE;
}

EOLIAN static void
_efl_ui_format_format_func_set(Eo *obj, Efl_Ui_Format_Data *pd, void *func_data, Efl_Ui_Format_Func func, Eina_Free_Cb func_free_cb)
{
   if (pd->format_func_free)
     pd->format_func_free(pd->format_func_data);
   pd->format_func = func;
   pd->format_func_data = func_data;
   pd->format_func_free = func_free_cb;

   if (efl_alive_get(obj))
     efl_ui_format_apply_formatted_value(obj);
}

EOLIAN static Efl_Ui_Format_Func
_efl_ui_format_format_func_get(const Eo *obj EINA_UNUSED, Efl_Ui_Format_Data *pd)
{
   return pd->format_func;
}

static int
_value_compare(const Efl_Ui_Format_Value *val1, const Efl_Ui_Format_Value *val2)
{
   return val1->value - val2->value;
}

EOLIAN static void
_efl_ui_format_format_values_set(Eo *obj, Efl_Ui_Format_Data *pd, Eina_Accessor *values)
{
   Efl_Ui_Format_Value v;
   int i;
   if (pd->format_values)
     {
        Efl_Ui_Format_Value *vptr;
        /* Delete previous values array */
        EINA_INARRAY_FOREACH(pd->format_values, vptr)
          {
             eina_stringshare_del(vptr->text);
          }
        eina_inarray_free(pd->format_values);
        pd->format_values = NULL;
     }
   if (values == NULL)
     {
        if (efl_alive_get(obj))
          efl_ui_format_apply_formatted_value(obj);
        return;
     }

   /* Copy the values to our internal array */
   pd->format_values = eina_inarray_new(sizeof(Efl_Ui_Format_Value), 4);
   EINA_ACCESSOR_FOREACH(values, i, v)
     {
        Efl_Ui_Format_Value vcopy = { v.value, eina_stringshare_add(v.text) };
        eina_inarray_insert_sorted(pd->format_values, &vcopy, (Eina_Compare_Cb)_value_compare);
     }
   eina_accessor_free(values);

   if (efl_alive_get(obj))
     efl_ui_format_apply_formatted_value(obj);
}

EOLIAN static Eina_Accessor *
_efl_ui_format_format_values_get(const Eo *obj EINA_UNUSED, Efl_Ui_Format_Data *pd)
{
   if (!pd->format_values) return NULL;
   return eina_inarray_accessor_new(pd->format_values);
}

EOLIAN static void
_efl_ui_format_format_string_set(Eo *obj EINA_UNUSED, Efl_Ui_Format_Data *sd, const char *string, Efl_Ui_Format_String_Type type)
{
   eina_stringshare_replace(&sd->format_string, string);
   if (string)
     sd->format_string_type = _format_string_check(sd->format_string, type);
   else
     sd->format_string_type = FORMAT_TYPE_INVALID;

   /* In legacy, setting the format string installs a default format func.
      Some widgets then override the format_func_set method so we keep that behavior. */
   if (elm_widget_is_legacy(obj))
     efl_ui_format_func_set(obj, sd, _legacy_default_format_func, NULL);

   if (efl_alive_get(obj))
     efl_ui_format_apply_formatted_value(obj);
}

EOLIAN static void
_efl_ui_format_format_string_get(const Eo *obj EINA_UNUSED, Efl_Ui_Format_Data *sd, const char **string, Efl_Ui_Format_String_Type *type)
{
   if (string) *string = sd->format_string;
   if (type) *type = sd->format_string_type == FORMAT_TYPE_TM ?
     EFL_UI_FORMAT_STRING_TYPE_TIME : EFL_UI_FORMAT_STRING_TYPE_SIMPLE;
}

EOLIAN static void
_efl_ui_format_formatted_value_get(Eo *obj EINA_UNUSED, Efl_Ui_Format_Data *pd, Eina_Strbuf *str, const Eina_Value value)
{
   char *v;
   eina_strbuf_reset(str);
   if (pd->format_values)
     {
        /* Search in the format_values array if we have one */
        Efl_Ui_Format_Value v = { 0 };
        int ndx;
        eina_value_int_convert(&value, &v.value);
        ndx = eina_inarray_search_sorted(pd->format_values, &v, (Eina_Compare_Cb)_value_compare);
        if (ndx > -1) {
          Efl_Ui_Format_Value *entry = eina_inarray_nth(pd->format_values, ndx);
          eina_strbuf_append(str, entry->text);
          return;
        }
     }
   if (pd->format_func)
     {
        /* If we have a formatting function, try to use it */
        if (pd->format_func(pd->format_func_data, str, value))
          return;
     }
   if (pd->format_string)
     {
        /* If we have a formatting string, use it */
        if (_do_format_string(pd, str, value))
          return;
     }

   /* Fallback to just printing the value if everything else fails */
   v = eina_value_to_string(&value);
   eina_strbuf_append(str, v);
   free(v);
}

EOLIAN static int
_efl_ui_format_decimal_places_get(Eo *obj EINA_UNUSED, Efl_Ui_Format_Data *pd)
{
   char result[16] = "0";
   const char *start;

   /* This method can only be called if a format_string has been supplied */
   if (!pd->format_string) return 0;

   start = strchr(pd->format_string, '%');
   while (start)
     {
        if (start[1] != '%')
          {
             start = strchr(start, '.');
             if (start)
                start++;
             break;
          }
        else
          start = strchr(start + 2, '%');
     }

   if (start)
     {
        const char *p = strchr(start, 'f');

        if ((p) && ((p - start) < 15))
          sscanf(start, "%[^f]", result);
     }

   return atoi(result);
}

EOLIAN static void
_efl_ui_format_efl_object_destructor(Eo *obj, Efl_Ui_Format_Data *pd EINA_UNUSED)
{
  /* Legacy widgets keep their own formatting data and have their own destructors */
  if (!elm_widget_is_legacy(obj))
    {
       /* Otherwise, free formatting data */
       efl_ui_format_func_set(obj, NULL, NULL, NULL);
       efl_ui_format_values_set(obj, NULL);
       efl_ui_format_string_set(obj, NULL, 0);
    }
  efl_destructor(efl_super(obj, EFL_UI_FORMAT_MIXIN));
}

#include "efl_ui_format.eo.c"