diff options
Diffstat (limited to 'libraries/evas/src/lib/canvas/evas_object_textblock.c')
-rw-r--r-- | libraries/evas/src/lib/canvas/evas_object_textblock.c | 10052 |
1 files changed, 0 insertions, 10052 deletions
diff --git a/libraries/evas/src/lib/canvas/evas_object_textblock.c b/libraries/evas/src/lib/canvas/evas_object_textblock.c deleted file mode 100644 index ee07e20..0000000 --- a/libraries/evas/src/lib/canvas/evas_object_textblock.c +++ /dev/null | |||
@@ -1,10052 +0,0 @@ | |||
1 | /** | ||
2 | * @internal | ||
3 | * @section Evas_Object_Textblock_Internal Internal Textblock Object Tutorial | ||
4 | * | ||
5 | * This explains the internal design of the Evas Textblock Object, it's assumed | ||
6 | * that the reader of this section has already read @ref Evas_Object_Textblock_Tutorial "Textblock's usage docs.". | ||
7 | * | ||
8 | * @subsection textblock_internal_intro Introduction | ||
9 | * There are two main parts to the textblock object, the first being the node | ||
10 | * system, and the second being the layout system. The former is just an | ||
11 | * internal representation of the markup text, while the latter is the internal | ||
12 | * visual representation of the text (i.e positioning, sizing, fonts and etc). | ||
13 | * | ||
14 | * @subsection textblock_nodes The Nodes system | ||
15 | * The nodes mechanism consists of two main data types: | ||
16 | * ::Evas_Object_Textblock_Node_Text and ::Evas_Object_Textblock_Node_Format | ||
17 | * the former is for Text nodes and the latter is for format nodes. | ||
18 | * There's always at least one text node, even if there are only formats. | ||
19 | * | ||
20 | * @subsection textblock_nodes_text Text nodes | ||
21 | * Each text node is essentially a paragraph, it includes an @ref Eina_UStrbuf | ||
22 | * that stores the actual paragraph text, a utf8 string to store the paragraph | ||
23 | * text in utf8 (which is not used internally at all), A pointer to it's | ||
24 | * main @ref textblock_nodes_format_internal "Format Node" and the paragraph's | ||
25 | * @ref evas_bidi_props "BiDi properties". The pointer to the format node may be | ||
26 | * NULL if there's no format node anywhere before the end of the text node, | ||
27 | * not even in previous text nodes. If not NULL, it points to the first format | ||
28 | * node pointing to text inside of the text node, or if there is none, it points | ||
29 | * to the previous's text nodes format node. Each paragraph has a format node | ||
30 | * representing a paragraph separator pointing to it's last position except | ||
31 | * for the last paragraph, which has no such constraint. This constraint | ||
32 | * happens because text nodes are paragraphs and paragraphs are delimited by | ||
33 | * paragraph separators. | ||
34 | * | ||
35 | * @subsection textblock_nodes_format_internal Format Nodes - Internal | ||
36 | * Each format node stores a group of format information, for example the | ||
37 | * markup: \<font=Vera,Kochi font_size=10 align=left\> will all be inserted | ||
38 | * inside the same format node, altohugh it consists of different formatting | ||
39 | * commands. | ||
40 | * Each node has a pointer to it's text node, this pointer is NEVER NULL, even | ||
41 | * if there's only one format, and no text, a text node is created. Each format | ||
42 | * node includes an offset from the last format node of the same text node. For | ||
43 | * example, the markup "0<b>12</b>" will create two format nodes, the first | ||
44 | * having an offset of 1 and the second an offset of 2. Each format node also | ||
45 | * includes a @ref Eina_Strbuf that includes the textual representation of the | ||
46 | * format, and a boolean stating if the format is a visible format or not, see | ||
47 | * @ref textblock_nodes_format_visible | ||
48 | * | ||
49 | * @subsection textblock_nodes_format_visible Visible Format Nodes | ||
50 | * There are two types of format nodes, visible and invisible. They are the same | ||
51 | * in every way, except for the representation in the text node. While invisible | ||
52 | * format nodes have no representation in the text node, the visible ones do. | ||
53 | * The Uniceode object replacement character (0xFFFC) is inserted to every place | ||
54 | * a visible format node points to. This makes it very easy to treat visible | ||
55 | * formats as items in the text, both for BiDi purposes and cursor handling | ||
56 | * purposes. | ||
57 | * Here are a few example visible an invisible formats: | ||
58 | * Visible: newline char, tab, paragraph separator and an embedded item. | ||
59 | * Invisible: setting the color, font or alignment of the text. | ||
60 | * | ||
61 | * @subsection textblock_layout The layout system | ||
62 | * @todo write @ref textblock_layout | ||
63 | */ | ||
64 | #include "evas_common.h" | ||
65 | #include "evas_private.h" | ||
66 | #include <stdlib.h> | ||
67 | |||
68 | #ifdef HAVE_LINEBREAK | ||
69 | #include "linebreak.h" | ||
70 | #include "wordbreak.h" | ||
71 | #endif | ||
72 | |||
73 | /* save typing */ | ||
74 | #define ENFN obj->layer->evas->engine.func | ||
75 | #define ENDT obj->layer->evas->engine.data.output | ||
76 | |||
77 | /* private magic number for textblock objects */ | ||
78 | static const char o_type[] = "textblock"; | ||
79 | |||
80 | /* The char to be inserted instead of visible formats */ | ||
81 | #define _REPLACEMENT_CHAR 0xFFFC | ||
82 | #define _PARAGRAPH_SEPARATOR 0x2029 | ||
83 | #define _NEWLINE '\n' | ||
84 | #define _TAB '\t' | ||
85 | |||
86 | #define _REPLACEMENT_CHAR_UTF8 "\xEF\xBF\xBC" | ||
87 | #define _PARAGRAPH_SEPARATOR_UTF8 "\xE2\x80\xA9" | ||
88 | #define _NEWLINE_UTF8 "\n" | ||
89 | #define _TAB_UTF8 "\t" | ||
90 | #define EVAS_TEXTBLOCK_IS_VISIBLE_FORMAT_CHAR(ch) \ | ||
91 | (((ch) == _REPLACEMENT_CHAR) || \ | ||
92 | ((ch) == _NEWLINE) || \ | ||
93 | ((ch) == _TAB) || \ | ||
94 | ((ch) == _PARAGRAPH_SEPARATOR)) | ||
95 | |||
96 | /* private struct for textblock object internal data */ | ||
97 | /** | ||
98 | * @internal | ||
99 | * @typedef Evas_Object_Textblock | ||
100 | * The actual textblock object. | ||
101 | */ | ||
102 | typedef struct _Evas_Object_Textblock Evas_Object_Textblock; | ||
103 | /** | ||
104 | * @internal | ||
105 | * @typedef Evas_Object_Style_Tag | ||
106 | * The structure used for finding style tags. | ||
107 | */ | ||
108 | typedef struct _Evas_Object_Style_Tag Evas_Object_Style_Tag; | ||
109 | /** | ||
110 | * @internal | ||
111 | * @typedef Evas_Object_Style_Tag | ||
112 | * The structure used for finding style tags. | ||
113 | */ | ||
114 | typedef struct _Evas_Object_Style_Tag_Base Evas_Object_Style_Tag_Base; | ||
115 | /** | ||
116 | * @internal | ||
117 | * @typedef Evas_Object_Textblock_Node_Text | ||
118 | * A text node. | ||
119 | */ | ||
120 | typedef struct _Evas_Object_Textblock_Node_Text Evas_Object_Textblock_Node_Text; | ||
121 | /* | ||
122 | * Defined in Evas.h | ||
123 | typedef struct _Evas_Object_Textblock_Node_Format Evas_Object_Textblock_Node_Format; | ||
124 | */ | ||
125 | |||
126 | /** | ||
127 | * @internal | ||
128 | * @typedef Evas_Object_Textblock_Paragraph | ||
129 | * A layouting paragraph. | ||
130 | */ | ||
131 | typedef struct _Evas_Object_Textblock_Paragraph Evas_Object_Textblock_Paragraph; | ||
132 | /** | ||
133 | * @internal | ||
134 | * @typedef Evas_Object_Textblock_Line | ||
135 | * A layouting line. | ||
136 | */ | ||
137 | typedef struct _Evas_Object_Textblock_Line Evas_Object_Textblock_Line; | ||
138 | /** | ||
139 | * @internal | ||
140 | * @typedef Evas_Object_Textblock_Item | ||
141 | * A layouting item. | ||
142 | */ | ||
143 | typedef struct _Evas_Object_Textblock_Item Evas_Object_Textblock_Item; | ||
144 | /** | ||
145 | * @internal | ||
146 | * @typedef Evas_Object_Textblock_Item | ||
147 | * A layouting text item. | ||
148 | */ | ||
149 | typedef struct _Evas_Object_Textblock_Text_Item Evas_Object_Textblock_Text_Item; | ||
150 | /** | ||
151 | * @internal | ||
152 | * @typedef Evas_Object_Textblock_Format_Item | ||
153 | * A layouting format item. | ||
154 | */ | ||
155 | typedef struct _Evas_Object_Textblock_Format_Item Evas_Object_Textblock_Format_Item; | ||
156 | /** | ||
157 | * @internal | ||
158 | * @typedef Evas_Object_Textblock_Format | ||
159 | * A textblock format. | ||
160 | */ | ||
161 | typedef struct _Evas_Object_Textblock_Format Evas_Object_Textblock_Format; | ||
162 | |||
163 | /** | ||
164 | * @internal | ||
165 | * @def IS_AT_END(ti, ind) | ||
166 | * Return true if ind is at the end of the text item, false otherwise. | ||
167 | */ | ||
168 | #define IS_AT_END(ti, ind) (ind == ti->text_props.text_len) | ||
169 | |||
170 | /** | ||
171 | * @internal | ||
172 | * @def MOVE_PREV_UNTIL(limit, ind) | ||
173 | * This decrements ind as long as ind > limit. | ||
174 | */ | ||
175 | #define MOVE_PREV_UNTIL(limit, ind) \ | ||
176 | do \ | ||
177 | { \ | ||
178 | if ((limit) < (ind)) \ | ||
179 | (ind)--; \ | ||
180 | } \ | ||
181 | while (0) | ||
182 | |||
183 | /** | ||
184 | * @internal | ||
185 | * @def MOVE_NEXT_UNTIL(limit, ind) | ||
186 | * This increments ind as long as ind < limit | ||
187 | */ | ||
188 | #define MOVE_NEXT_UNTIL(limit, ind) \ | ||
189 | do \ | ||
190 | { \ | ||
191 | if ((ind) < (limit)) \ | ||
192 | (ind)++; \ | ||
193 | } \ | ||
194 | while (0) | ||
195 | |||
196 | /** | ||
197 | * @internal | ||
198 | * @def GET_ITEM_TEXT(ti) | ||
199 | * Returns a const reference to the text of the ti (not null terminated). | ||
200 | */ | ||
201 | #define GET_ITEM_TEXT(ti) \ | ||
202 | (((ti)->parent.text_node) ? \ | ||
203 | (eina_ustrbuf_string_get((ti)->parent.text_node->unicode) + \ | ||
204 | (ti)->parent.text_pos) : EINA_UNICODE_EMPTY_STRING) | ||
205 | /** | ||
206 | * @internal | ||
207 | * @def _FORMAT_IS_CLOSER_OF(base, closer, closer_len) | ||
208 | * Returns true if closer is the closer of base. | ||
209 | */ | ||
210 | #define _FORMAT_IS_CLOSER_OF(base, closer, closer_len) \ | ||
211 | (!strncmp(base, closer, closer_len) && \ | ||
212 | (!base[closer_len] || \ | ||
213 | (base[closer_len] == '=') || \ | ||
214 | _is_white(base[closer_len]))) | ||
215 | |||
216 | /*FIXME: document the structs and struct items. */ | ||
217 | struct _Evas_Object_Style_Tag_Base | ||
218 | { | ||
219 | char *tag; | ||
220 | char *replace; | ||
221 | size_t tag_len; | ||
222 | size_t replace_len; | ||
223 | }; | ||
224 | |||
225 | struct _Evas_Object_Style_Tag | ||
226 | { | ||
227 | EINA_INLIST; | ||
228 | Evas_Object_Style_Tag_Base tag; | ||
229 | }; | ||
230 | |||
231 | struct _Evas_Object_Textblock_Node_Text | ||
232 | { | ||
233 | EINA_INLIST; | ||
234 | Eina_UStrbuf *unicode; | ||
235 | char *utf8; | ||
236 | Evas_Object_Textblock_Node_Format *format_node; | ||
237 | Evas_Object_Textblock_Paragraph *par; | ||
238 | Eina_Bool dirty : 1; | ||
239 | Eina_Bool is_new : 1; | ||
240 | }; | ||
241 | |||
242 | struct _Evas_Object_Textblock_Node_Format | ||
243 | { | ||
244 | EINA_INLIST; | ||
245 | const char *format; | ||
246 | const char *orig_format; | ||
247 | Evas_Object_Textblock_Node_Text *text_node; | ||
248 | size_t offset; | ||
249 | unsigned char anchor : 2; | ||
250 | Eina_Bool opener : 1; | ||
251 | Eina_Bool own_closer : 1; | ||
252 | Eina_Bool visible : 1; | ||
253 | Eina_Bool format_change : 1; | ||
254 | Eina_Bool is_new : 1; | ||
255 | }; | ||
256 | |||
257 | /* The default tags to use */ | ||
258 | static const Evas_Object_Style_Tag_Base default_tags[] = { | ||
259 | { "b", "+ font_weight=Bold", 1, 18 }, | ||
260 | { "i", "+ font_style=Italic", 1, 19 }}; | ||
261 | |||
262 | #define ANCHOR_NONE 0 | ||
263 | #define ANCHOR_A 1 | ||
264 | #define ANCHOR_ITEM 2 | ||
265 | |||
266 | /** | ||
267 | * @internal | ||
268 | * @def _NODE_TEXT(x) | ||
269 | * A convinience macro for casting to a text node. | ||
270 | */ | ||
271 | #define _NODE_TEXT(x) ((Evas_Object_Textblock_Node_Text *) (x)) | ||
272 | /** | ||
273 | * @internal | ||
274 | * @def _NODE_FORMAT(x) | ||
275 | * A convinience macro for casting to a format node. | ||
276 | */ | ||
277 | #define _NODE_FORMAT(x) ((Evas_Object_Textblock_Node_Format *) (x)) | ||
278 | /** | ||
279 | * @internal | ||
280 | * @def _ITEM(x) | ||
281 | * A convinience macro for casting to a generic item. | ||
282 | */ | ||
283 | #define _ITEM(x) ((Evas_Object_Textblock_Item *) (x)) | ||
284 | /** | ||
285 | * @internal | ||
286 | * @def _ITEM_TEXT(x) | ||
287 | * A convinience macro for casting to a text item. | ||
288 | */ | ||
289 | #define _ITEM_TEXT(x) ((Evas_Object_Textblock_Text_Item *) (x)) | ||
290 | /** | ||
291 | * @internal | ||
292 | * @def _ITEM_FORMAT(x) | ||
293 | * A convinience macro for casting to a format item. | ||
294 | */ | ||
295 | #define _ITEM_FORMAT(x) ((Evas_Object_Textblock_Format_Item *) (x)) | ||
296 | |||
297 | struct _Evas_Object_Textblock_Paragraph | ||
298 | { | ||
299 | EINA_INLIST; | ||
300 | Evas_Object_Textblock_Line *lines; | ||
301 | Evas_Object_Textblock_Node_Text *text_node; | ||
302 | Eina_List *logical_items; | ||
303 | Evas_BiDi_Paragraph_Props *bidi_props; /* Only valid during layout */ | ||
304 | Evas_BiDi_Direction direction; | ||
305 | Evas_Coord y, w, h; | ||
306 | int line_no; | ||
307 | Eina_Bool is_bidi : 1; | ||
308 | Eina_Bool visible : 1; | ||
309 | Eina_Bool rendered : 1; | ||
310 | }; | ||
311 | |||
312 | struct _Evas_Object_Textblock_Line | ||
313 | { | ||
314 | EINA_INLIST; | ||
315 | Evas_Object_Textblock_Item *items; | ||
316 | Evas_Object_Textblock_Paragraph *par; | ||
317 | Evas_Coord x, y, w, h; | ||
318 | int baseline; | ||
319 | int line_no; | ||
320 | }; | ||
321 | |||
322 | typedef enum _Evas_Textblock_Item_Type | ||
323 | { | ||
324 | EVAS_TEXTBLOCK_ITEM_TEXT, | ||
325 | EVAS_TEXTBLOCK_ITEM_FORMAT, | ||
326 | } Evas_Textblock_Item_Type; | ||
327 | |||
328 | struct _Evas_Object_Textblock_Item | ||
329 | { | ||
330 | EINA_INLIST; | ||
331 | Evas_Textblock_Item_Type type; | ||
332 | Evas_Object_Textblock_Node_Text *text_node; | ||
333 | Evas_Object_Textblock_Format *format; | ||
334 | size_t text_pos; | ||
335 | #ifdef BIDI_SUPPORT | ||
336 | size_t visual_pos; | ||
337 | #endif | ||
338 | Evas_Coord adv, x, w, h; | ||
339 | Eina_Bool merge : 1; /* Indicates whether this | ||
340 | item should merge to the | ||
341 | previous item or not */ | ||
342 | Eina_Bool visually_deleted : 1; | ||
343 | /* Indicates whether this | ||
344 | item is used in the visual | ||
345 | layout or not. */ | ||
346 | }; | ||
347 | |||
348 | struct _Evas_Object_Textblock_Text_Item | ||
349 | { | ||
350 | Evas_Object_Textblock_Item parent; | ||
351 | Evas_Text_Props text_props; | ||
352 | Evas_Coord inset; | ||
353 | Evas_Coord x_adjustment; /* Used to indicate by how | ||
354 | much we adjusted sizes */ | ||
355 | }; | ||
356 | |||
357 | struct _Evas_Object_Textblock_Format_Item | ||
358 | { | ||
359 | Evas_Object_Textblock_Item parent; | ||
360 | Evas_BiDi_Direction bidi_dir; | ||
361 | const char *item; | ||
362 | int y; | ||
363 | unsigned char vsize : 2; | ||
364 | unsigned char size : 2; | ||
365 | Eina_Bool formatme : 1; | ||
366 | }; | ||
367 | |||
368 | struct _Evas_Object_Textblock_Format | ||
369 | { | ||
370 | Evas_Object_Textblock_Node_Format *fnode; | ||
371 | double halign; | ||
372 | double valign; | ||
373 | struct { | ||
374 | Evas_Font_Description *fdesc; | ||
375 | const char *source; | ||
376 | Evas_Font_Set *font; | ||
377 | Evas_Font_Size size; | ||
378 | } font; | ||
379 | struct { | ||
380 | struct { | ||
381 | unsigned char r, g, b, a; | ||
382 | } normal, underline, underline2, underline_dash, outline, shadow, glow, glow2, backing, | ||
383 | strikethrough; | ||
384 | } color; | ||
385 | struct { | ||
386 | int l, r; | ||
387 | } margin; | ||
388 | int ref; | ||
389 | int tabstops; | ||
390 | int linesize; | ||
391 | int linegap; | ||
392 | int underline_dash_width; | ||
393 | int underline_dash_gap; | ||
394 | double linerelsize; | ||
395 | double linerelgap; | ||
396 | double linefill; | ||
397 | double ellipsis; | ||
398 | unsigned char style; | ||
399 | Eina_Bool wrap_word : 1; | ||
400 | Eina_Bool wrap_char : 1; | ||
401 | Eina_Bool wrap_mixed : 1; | ||
402 | Eina_Bool underline : 1; | ||
403 | Eina_Bool underline2 : 1; | ||
404 | Eina_Bool underline_dash : 1; | ||
405 | Eina_Bool strikethrough : 1; | ||
406 | Eina_Bool backing : 1; | ||
407 | Eina_Bool password : 1; | ||
408 | Eina_Bool halign_auto : 1; | ||
409 | }; | ||
410 | |||
411 | struct _Evas_Textblock_Style | ||
412 | { | ||
413 | const char *style_text; | ||
414 | char *default_tag; | ||
415 | Evas_Object_Style_Tag *tags; | ||
416 | Eina_List *objects; | ||
417 | Eina_Bool delete_me : 1; | ||
418 | }; | ||
419 | |||
420 | struct _Evas_Textblock_Cursor | ||
421 | { | ||
422 | Evas_Object *obj; | ||
423 | size_t pos; | ||
424 | Evas_Object_Textblock_Node_Text *node; | ||
425 | }; | ||
426 | |||
427 | /* Size of the index array */ | ||
428 | #define TEXTBLOCK_PAR_INDEX_SIZE 10 | ||
429 | struct _Evas_Object_Textblock | ||
430 | { | ||
431 | DATA32 magic; | ||
432 | Evas_Textblock_Style *style; | ||
433 | Evas_Textblock_Style *style_user; | ||
434 | Evas_Textblock_Cursor *cursor; | ||
435 | Eina_List *cursors; | ||
436 | Evas_Object_Textblock_Node_Text *text_nodes; | ||
437 | Evas_Object_Textblock_Node_Format *format_nodes; | ||
438 | |||
439 | int num_paragraphs; | ||
440 | Evas_Object_Textblock_Paragraph *paragraphs; | ||
441 | Evas_Object_Textblock_Paragraph *par_index[TEXTBLOCK_PAR_INDEX_SIZE]; | ||
442 | |||
443 | Evas_Object_Textblock_Text_Item *ellip_ti; | ||
444 | Eina_List *anchors_a; | ||
445 | Eina_List *anchors_item; | ||
446 | int last_w, last_h; | ||
447 | struct { | ||
448 | int l, r, t, b; | ||
449 | } style_pad; | ||
450 | double valign; | ||
451 | char *markup_text; | ||
452 | void *engine_data; | ||
453 | const char *repch; | ||
454 | const char *bidi_delimiters; | ||
455 | struct { | ||
456 | int w, h; | ||
457 | Eina_Bool valid : 1; | ||
458 | } formatted, native; | ||
459 | Eina_Bool redraw : 1; | ||
460 | Eina_Bool changed : 1; | ||
461 | Eina_Bool content_changed : 1; | ||
462 | Eina_Bool format_changed : 1; | ||
463 | Eina_Bool have_ellipsis : 1; | ||
464 | Eina_Bool legacy_newline : 1; | ||
465 | }; | ||
466 | |||
467 | /* private methods for textblock objects */ | ||
468 | static void evas_object_textblock_init(Evas_Object *obj); | ||
469 | static void *evas_object_textblock_new(void); | ||
470 | static void evas_object_textblock_render(Evas_Object *obj, void *output, void *context, void *surface, int x, int y); | ||
471 | static void evas_object_textblock_free(Evas_Object *obj); | ||
472 | static void evas_object_textblock_render_pre(Evas_Object *obj); | ||
473 | static void evas_object_textblock_render_post(Evas_Object *obj); | ||
474 | |||
475 | static unsigned int evas_object_textblock_id_get(Evas_Object *obj); | ||
476 | static unsigned int evas_object_textblock_visual_id_get(Evas_Object *obj); | ||
477 | static void *evas_object_textblock_engine_data_get(Evas_Object *obj); | ||
478 | |||
479 | static int evas_object_textblock_is_opaque(Evas_Object *obj); | ||
480 | static int evas_object_textblock_was_opaque(Evas_Object *obj); | ||
481 | |||
482 | static void evas_object_textblock_coords_recalc(Evas_Object *obj); | ||
483 | |||
484 | static void evas_object_textblock_scale_update(Evas_Object *obj); | ||
485 | |||
486 | static const Evas_Object_Func object_func = | ||
487 | { | ||
488 | /* methods (compulsory) */ | ||
489 | evas_object_textblock_free, | ||
490 | evas_object_textblock_render, | ||
491 | evas_object_textblock_render_pre, | ||
492 | evas_object_textblock_render_post, | ||
493 | evas_object_textblock_id_get, | ||
494 | evas_object_textblock_visual_id_get, | ||
495 | evas_object_textblock_engine_data_get, | ||
496 | /* these are optional. NULL = nothing */ | ||
497 | NULL, | ||
498 | NULL, | ||
499 | NULL, | ||
500 | NULL, | ||
501 | evas_object_textblock_is_opaque, | ||
502 | evas_object_textblock_was_opaque, | ||
503 | NULL, | ||
504 | NULL, | ||
505 | evas_object_textblock_coords_recalc, | ||
506 | evas_object_textblock_scale_update, | ||
507 | NULL, | ||
508 | NULL, | ||
509 | NULL | ||
510 | }; | ||
511 | |||
512 | /* the actual api call to add a textblock */ | ||
513 | |||
514 | #define TB_HEAD() \ | ||
515 | Evas_Object_Textblock *o; \ | ||
516 | MAGIC_CHECK(obj, Evas_Object, MAGIC_OBJ); \ | ||
517 | return; \ | ||
518 | MAGIC_CHECK_END(); \ | ||
519 | o = (Evas_Object_Textblock *)(obj->object_data); \ | ||
520 | MAGIC_CHECK(o, Evas_Object_Textblock, MAGIC_OBJ_TEXTBLOCK); \ | ||
521 | return; \ | ||
522 | MAGIC_CHECK_END(); | ||
523 | |||
524 | #define TB_HEAD_RETURN(x) \ | ||
525 | Evas_Object_Textblock *o; \ | ||
526 | MAGIC_CHECK(obj, Evas_Object, MAGIC_OBJ); \ | ||
527 | return (x); \ | ||
528 | MAGIC_CHECK_END(); \ | ||
529 | o = (Evas_Object_Textblock *)(obj->object_data); \ | ||
530 | MAGIC_CHECK(o, Evas_Object_Textblock, MAGIC_OBJ_TEXTBLOCK); \ | ||
531 | return (x); \ | ||
532 | MAGIC_CHECK_END(); | ||
533 | |||
534 | |||
535 | |||
536 | static Eina_Bool _evas_textblock_cursor_is_at_the_end(const Evas_Textblock_Cursor *cur); | ||
537 | static void _evas_textblock_node_text_remove(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Text *n); | ||
538 | static void _evas_textblock_node_text_remove_formats_between(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Text *n, int start, int end); | ||
539 | static Evas_Object_Textblock_Node_Format *_evas_textblock_cursor_node_format_before_or_at_pos_get(const Evas_Textblock_Cursor *cur); | ||
540 | static size_t _evas_textblock_node_format_pos_get(const Evas_Object_Textblock_Node_Format *fmt); | ||
541 | static void _evas_textblock_node_format_remove(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Format *n, int visual_adjustment); | ||
542 | static void _evas_textblock_node_format_free(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Format *n); | ||
543 | static void _evas_textblock_node_text_free(Evas_Object_Textblock_Node_Text *n); | ||
544 | static void _evas_textblock_changed(Evas_Object_Textblock *o, Evas_Object *obj); | ||
545 | static void _evas_textblock_invalidate_all(Evas_Object_Textblock *o); | ||
546 | static void _evas_textblock_cursors_update_offset(const Evas_Textblock_Cursor *cur, const Evas_Object_Textblock_Node_Text *n, size_t start, int offset); | ||
547 | static void _evas_textblock_cursors_set_node(Evas_Object_Textblock *o, const Evas_Object_Textblock_Node_Text *n, Evas_Object_Textblock_Node_Text *new_node); | ||
548 | |||
549 | /* styles */ | ||
550 | /** | ||
551 | * @internal | ||
552 | * Clears the textblock style passed except for the style_text which is replaced. | ||
553 | * @param ts The ts to be cleared. Must not be NULL. | ||
554 | * @param style_text the style's text. | ||
555 | */ | ||
556 | static void | ||
557 | _style_replace(Evas_Textblock_Style *ts, const char *style_text) | ||
558 | { | ||
559 | eina_stringshare_replace(&ts->style_text, style_text); | ||
560 | if (ts->default_tag) free(ts->default_tag); | ||
561 | while (ts->tags) | ||
562 | { | ||
563 | Evas_Object_Style_Tag *tag; | ||
564 | |||
565 | tag = (Evas_Object_Style_Tag *)ts->tags; | ||
566 | ts->tags = (Evas_Object_Style_Tag *)eina_inlist_remove(EINA_INLIST_GET(ts->tags), EINA_INLIST_GET(tag)); | ||
567 | free(tag->tag.tag); | ||
568 | free(tag->tag.replace); | ||
569 | free(tag); | ||
570 | } | ||
571 | ts->default_tag = NULL; | ||
572 | ts->tags = NULL; | ||
573 | } | ||
574 | |||
575 | /** | ||
576 | * @internal | ||
577 | * Clears the textblock style passed. | ||
578 | * @param ts The ts to be cleared. Must not be NULL. | ||
579 | */ | ||
580 | static void | ||
581 | _style_clear(Evas_Textblock_Style *ts) | ||
582 | { | ||
583 | _style_replace(ts, NULL); | ||
584 | } | ||
585 | |||
586 | /** | ||
587 | * @internal | ||
588 | * Searches inside the tags stored in the style for the tag matching s. | ||
589 | * @param ts The ts to be cleared. Must not be NULL. | ||
590 | * @param s The tag to be matched. | ||
591 | * @param tag_len the length of the tag string. | ||
592 | * @param[out] replace_len The length of the replcaement found. - Must not be NULL. | ||
593 | * @return The replacement string found. | ||
594 | */ | ||
595 | static inline const char * | ||
596 | _style_match_tag(const Evas_Textblock_Style *ts, const char *s, size_t tag_len, size_t *replace_len) | ||
597 | { | ||
598 | Evas_Object_Style_Tag *tag; | ||
599 | |||
600 | /* Try the style tags */ | ||
601 | EINA_INLIST_FOREACH(ts->tags, tag) | ||
602 | { | ||
603 | if (tag->tag.tag_len != tag_len) continue; | ||
604 | if (!strncmp(tag->tag.tag, s, tag_len)) | ||
605 | { | ||
606 | *replace_len = tag->tag.replace_len; | ||
607 | return tag->tag.replace; | ||
608 | } | ||
609 | } | ||
610 | |||
611 | /* Try the default tags */ | ||
612 | { | ||
613 | size_t i; | ||
614 | const Evas_Object_Style_Tag_Base *btag; | ||
615 | for (btag = default_tags, i = 0 ; | ||
616 | i < (sizeof(default_tags) / sizeof(default_tags[0])) ; | ||
617 | btag++, i++) | ||
618 | { | ||
619 | if (btag->tag_len != tag_len) continue; | ||
620 | if (!strncmp(btag->tag, s, tag_len)) | ||
621 | { | ||
622 | *replace_len = btag->replace_len; | ||
623 | return btag->replace; | ||
624 | } | ||
625 | } | ||
626 | } | ||
627 | |||
628 | *replace_len = 0; | ||
629 | return NULL; | ||
630 | } | ||
631 | |||
632 | /** | ||
633 | * @internal | ||
634 | * Clears all the nodes (text and format) of the textblock object. | ||
635 | * @param obj The evas object, must not be NULL. | ||
636 | */ | ||
637 | static void | ||
638 | _nodes_clear(const Evas_Object *obj) | ||
639 | { | ||
640 | Evas_Object_Textblock *o; | ||
641 | |||
642 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
643 | while (o->text_nodes) | ||
644 | { | ||
645 | Evas_Object_Textblock_Node_Text *n; | ||
646 | |||
647 | n = o->text_nodes; | ||
648 | o->text_nodes = _NODE_TEXT(eina_inlist_remove( | ||
649 | EINA_INLIST_GET(o->text_nodes), EINA_INLIST_GET(n))); | ||
650 | _evas_textblock_node_text_free(n); | ||
651 | } | ||
652 | while (o->format_nodes) | ||
653 | { | ||
654 | Evas_Object_Textblock_Node_Format *n; | ||
655 | |||
656 | n = o->format_nodes; | ||
657 | o->format_nodes = _NODE_FORMAT(eina_inlist_remove(EINA_INLIST_GET(o->format_nodes), EINA_INLIST_GET(n))); | ||
658 | _evas_textblock_node_format_free(o, n); | ||
659 | } | ||
660 | } | ||
661 | |||
662 | /** | ||
663 | * @internal | ||
664 | * Unrefs and frees (if needed) a textblock format. | ||
665 | * @param obj The Evas_Object, Must not be NULL. | ||
666 | * @param fmt the format to be cleaned, must not be NULL. | ||
667 | */ | ||
668 | static void | ||
669 | _format_unref_free(const Evas_Object *obj, Evas_Object_Textblock_Format *fmt) | ||
670 | { | ||
671 | fmt->ref--; | ||
672 | if (fmt->ref > 0) return; | ||
673 | if (fmt->font.fdesc) evas_font_desc_unref(fmt->font.fdesc); | ||
674 | if (fmt->font.source) eina_stringshare_del(fmt->font.source); | ||
675 | evas_font_free(obj->layer->evas, fmt->font.font); | ||
676 | free(fmt); | ||
677 | } | ||
678 | |||
679 | /** | ||
680 | * @internal | ||
681 | * Free a layout item | ||
682 | * @param obj The evas object, must not be NULL. | ||
683 | * @param ln the layout line on which the item is in, must not be NULL. | ||
684 | * @param it the layout item to be freed | ||
685 | */ | ||
686 | static void | ||
687 | _item_free(const Evas_Object *obj, Evas_Object_Textblock_Line *ln, Evas_Object_Textblock_Item *it) | ||
688 | { | ||
689 | if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
690 | { | ||
691 | Evas_Object_Textblock_Text_Item *ti = _ITEM_TEXT(it); | ||
692 | |||
693 | evas_common_text_props_content_unref(&ti->text_props); | ||
694 | } | ||
695 | else | ||
696 | { | ||
697 | Evas_Object_Textblock_Format_Item *fi = _ITEM_FORMAT(it); | ||
698 | |||
699 | if (fi->item) eina_stringshare_del(fi->item); | ||
700 | } | ||
701 | _format_unref_free(obj, it->format); | ||
702 | if (ln) | ||
703 | { | ||
704 | ln->items = (Evas_Object_Textblock_Item *) eina_inlist_remove( | ||
705 | EINA_INLIST_GET(ln->items), EINA_INLIST_GET(ln->items)); | ||
706 | } | ||
707 | free(it); | ||
708 | } | ||
709 | |||
710 | /** | ||
711 | * @internal | ||
712 | * Free a layout line. | ||
713 | * @param obj The evas object, must not be NULL. | ||
714 | * @param ln the layout line to be freed, must not be NULL. | ||
715 | */ | ||
716 | static void | ||
717 | _line_free(Evas_Object_Textblock_Line *ln) | ||
718 | { | ||
719 | /* Items are freed from the logical list, except for the ellip item */ | ||
720 | if (ln) free(ln); | ||
721 | } | ||
722 | |||
723 | /* table of html escapes (that i can find) this should be ordered with the | ||
724 | * most common first as it's a linear search to match - no hash for this. | ||
725 | * | ||
726 | * these are stored as one large string and one additional array that | ||
727 | * contains the offsets to the tokens for space efficiency. | ||
728 | */ | ||
729 | /** | ||
730 | * @internal | ||
731 | * @var escape_strings[] | ||
732 | * This string consists of NULL terminated pairs of strings, the first of | ||
733 | * every pair is an escape and the second is the value of the escape. | ||
734 | */ | ||
735 | static const char escape_strings[] = | ||
736 | /* most common escaped stuff */ | ||
737 | ""\0" "\x22\0" | ||
738 | "&\0" "\x26\0" | ||
739 | "<\0" "\x3c\0" | ||
740 | ">\0" "\x3e\0" | ||
741 | /* all the rest */ | ||
742 | " \0" "\xc2\xa0\0" | ||
743 | "¡\0" "\xc2\xa1\0" | ||
744 | "¢\0" "\xc2\xa2\0" | ||
745 | "£\0" "\xc2\xa3\0" | ||
746 | "¤\0" "\xc2\xa4\0" | ||
747 | "¥\0" "\xc2\xa5\0" | ||
748 | "¦\0" "\xc2\xa6\0" | ||
749 | "§\0" "\xc2\xa7\0" | ||
750 | "¨\0" "\xc2\xa8\0" | ||
751 | "©\0" "\xc2\xa9\0" | ||
752 | "ª\0" "\xc2\xaa\0" | ||
753 | "«\0" "\xc2\xab\0" | ||
754 | "¬\0" "\xc2\xac\0" | ||
755 | "®\0" "\xc2\xae\0" | ||
756 | "¯\0" "\xc2\xaf\0" | ||
757 | "°\0" "\xc2\xb0\0" | ||
758 | "±\0" "\xc2\xb1\0" | ||
759 | "²\0" "\xc2\xb2\0" | ||
760 | "³\0" "\xc2\xb3\0" | ||
761 | "´\0" "\xc2\xb4\0" | ||
762 | "µ\0" "\xc2\xb5\0" | ||
763 | "¶\0" "\xc2\xb6\0" | ||
764 | "·\0" "\xc2\xb7\0" | ||
765 | "¸\0" "\xc2\xb8\0" | ||
766 | "¹\0" "\xc2\xb9\0" | ||
767 | "º\0" "\xc2\xba\0" | ||
768 | "»\0" "\xc2\xbb\0" | ||
769 | "¼\0" "\xc2\xbc\0" | ||
770 | "½\0" "\xc2\xbd\0" | ||
771 | "¾\0" "\xc2\xbe\0" | ||
772 | "¿\0" "\xc2\xbf\0" | ||
773 | "À\0" "\xc3\x80\0" | ||
774 | "Á\0" "\xc3\x81\0" | ||
775 | "Â\0" "\xc3\x82\0" | ||
776 | "Ã\0" "\xc3\x83\0" | ||
777 | "Ä\0" "\xc3\x84\0" | ||
778 | "Å\0" "\xc3\x85\0" | ||
779 | "&Aelig;\0" "\xc3\x86\0" | ||
780 | "Ç\0" "\xc3\x87\0" | ||
781 | "È\0" "\xc3\x88\0" | ||
782 | "É\0" "\xc3\x89\0" | ||
783 | "Ê\0" "\xc3\x8a\0" | ||
784 | "Ë\0" "\xc3\x8b\0" | ||
785 | "Ì\0" "\xc3\x8c\0" | ||
786 | "Í\0" "\xc3\x8d\0" | ||
787 | "Î\0" "\xc3\x8e\0" | ||
788 | "Ï\0" "\xc3\x8f\0" | ||
789 | "&Eth;\0" "\xc3\x90\0" | ||
790 | "Ñ\0" "\xc3\x91\0" | ||
791 | "Ò\0" "\xc3\x92\0" | ||
792 | "Ó\0" "\xc3\x93\0" | ||
793 | "Ô\0" "\xc3\x94\0" | ||
794 | "Õ\0" "\xc3\x95\0" | ||
795 | "Ö\0" "\xc3\x96\0" | ||
796 | "×\0" "\xc3\x97\0" | ||
797 | "Ø\0" "\xc3\x98\0" | ||
798 | "Ù\0" "\xc3\x99\0" | ||
799 | "Ú\0" "\xc3\x9a\0" | ||
800 | "Û\0" "\xc3\x9b\0" | ||
801 | "Ý\0" "\xc3\x9d\0" | ||
802 | "&Thorn;\0" "\xc3\x9e\0" | ||
803 | "ß\0" "\xc3\x9f\0" | ||
804 | "à\0" "\xc3\xa0\0" | ||
805 | "á\0" "\xc3\xa1\0" | ||
806 | "â\0" "\xc3\xa2\0" | ||
807 | "ã\0" "\xc3\xa3\0" | ||
808 | "ä\0" "\xc3\xa4\0" | ||
809 | "å\0" "\xc3\xa5\0" | ||
810 | "æ\0" "\xc3\xa6\0" | ||
811 | "ç\0" "\xc3\xa7\0" | ||
812 | "è\0" "\xc3\xa8\0" | ||
813 | "é\0" "\xc3\xa9\0" | ||
814 | "ê\0" "\xc3\xaa\0" | ||
815 | "ë\0" "\xc3\xab\0" | ||
816 | "ì\0" "\xc3\xac\0" | ||
817 | "í\0" "\xc3\xad\0" | ||
818 | "î\0" "\xc3\xae\0" | ||
819 | "ï\0" "\xc3\xaf\0" | ||
820 | "ð\0" "\xc3\xb0\0" | ||
821 | "ñ\0" "\xc3\xb1\0" | ||
822 | "ò\0" "\xc3\xb2\0" | ||
823 | "ó\0" "\xc3\xb3\0" | ||
824 | "ô\0" "\xc3\xb4\0" | ||
825 | "õ\0" "\xc3\xb5\0" | ||
826 | "ö\0" "\xc3\xb6\0" | ||
827 | "÷\0" "\xc3\xb7\0" | ||
828 | "ø\0" "\xc3\xb8\0" | ||
829 | "ù\0" "\xc3\xb9\0" | ||
830 | "ú\0" "\xc3\xba\0" | ||
831 | "û\0" "\xc3\xbb\0" | ||
832 | "ü\0" "\xc3\xbc\0" | ||
833 | "ý\0" "\xc3\xbd\0" | ||
834 | "þ\0" "\xc3\xbe\0" | ||
835 | "ÿ\0" "\xc3\xbf\0" | ||
836 | "α\0" "\xce\x91\0" | ||
837 | "β\0" "\xce\x92\0" | ||
838 | "γ\0" "\xce\x93\0" | ||
839 | "δ\0" "\xce\x94\0" | ||
840 | "ε\0" "\xce\x95\0" | ||
841 | "ζ\0" "\xce\x96\0" | ||
842 | "η\0" "\xce\x97\0" | ||
843 | "θ\0" "\xce\x98\0" | ||
844 | "ι\0" "\xce\x99\0" | ||
845 | "κ\0" "\xce\x9a\0" | ||
846 | "λ\0" "\xce\x9b\0" | ||
847 | "μ\0" "\xce\x9c\0" | ||
848 | "ν\0" "\xce\x9d\0" | ||
849 | "ξ\0" "\xce\x9e\0" | ||
850 | "ο\0" "\xce\x9f\0" | ||
851 | "π\0" "\xce\xa0\0" | ||
852 | "ρ\0" "\xce\xa1\0" | ||
853 | "σ\0" "\xce\xa3\0" | ||
854 | "τ\0" "\xce\xa4\0" | ||
855 | "υ\0" "\xce\xa5\0" | ||
856 | "φ\0" "\xce\xa6\0" | ||
857 | "χ\0" "\xce\xa7\0" | ||
858 | "ψ\0" "\xce\xa8\0" | ||
859 | "ω\0" "\xce\xa9\0" | ||
860 | "…\0" "\xe2\x80\xa6\0" | ||
861 | "€\0" "\xe2\x82\xac\0" | ||
862 | "←\0" "\xe2\x86\x90\0" | ||
863 | "↑\0" "\xe2\x86\x91\0" | ||
864 | "→\0" "\xe2\x86\x92\0" | ||
865 | "↓\0" "\xe2\x86\x93\0" | ||
866 | "↔\0" "\xe2\x86\x94\0" | ||
867 | "←\0" "\xe2\x87\x90\0" | ||
868 | "→\0" "\xe2\x87\x92\0" | ||
869 | "∀\0" "\xe2\x88\x80\0" | ||
870 | "∃\0" "\xe2\x88\x83\0" | ||
871 | "∇\0" "\xe2\x88\x87\0" | ||
872 | "∏\0" "\xe2\x88\x8f\0" | ||
873 | "∑\0" "\xe2\x88\x91\0" | ||
874 | "∧\0" "\xe2\x88\xa7\0" | ||
875 | "∨\0" "\xe2\x88\xa8\0" | ||
876 | "∫\0" "\xe2\x88\xab\0" | ||
877 | "≠\0" "\xe2\x89\xa0\0" | ||
878 | "≡\0" "\xe2\x89\xa1\0" | ||
879 | "⊕\0" "\xe2\x8a\x95\0" | ||
880 | "⊥\0" "\xe2\x8a\xa5\0" | ||
881 | "†\0" "\xe2\x80\xa0\0" | ||
882 | "‡\0" "\xe2\x80\xa1\0" | ||
883 | "•\0" "\xe2\x80\xa2\0" | ||
884 | ; | ||
885 | |||
886 | EVAS_MEMPOOL(_mp_obj); | ||
887 | |||
888 | /** | ||
889 | * @internal | ||
890 | * Checks if a char is a whitespace. | ||
891 | * @param c the unicode codepoint. | ||
892 | * @return EINA_TRUE if the unicode codepoint is a whitespace, EINA_FALSE otherwise. | ||
893 | */ | ||
894 | static Eina_Bool | ||
895 | _is_white(Eina_Unicode c) | ||
896 | { | ||
897 | /* | ||
898 | * unicode list of whitespace chars | ||
899 | * | ||
900 | * 0009..000D <control-0009>..<control-000D> | ||
901 | * 0020 SPACE | ||
902 | * 0085 <control-0085> | ||
903 | * 00A0 NO-BREAK SPACE | ||
904 | * 1680 OGHAM SPACE MARK | ||
905 | * 180E MONGOLIAN VOWEL SEPARATOR | ||
906 | * 2000..200A EN QUAD..HAIR SPACE | ||
907 | * 2028 LINE SEPARATOR | ||
908 | * 2029 PARAGRAPH SEPARATOR | ||
909 | * 202F NARROW NO-BREAK SPACE | ||
910 | * 205F MEDIUM MATHEMATICAL SPACE | ||
911 | * 3000 IDEOGRAPHIC SPACE | ||
912 | */ | ||
913 | if ( | ||
914 | (c == 0x20) || | ||
915 | ((c >= 0x9) && (c <= 0xd)) || | ||
916 | (c == 0x85) || | ||
917 | (c == 0xa0) || | ||
918 | (c == 0x1680) || | ||
919 | (c == 0x180e) || | ||
920 | ((c >= 0x2000) && (c <= 0x200a)) || | ||
921 | (c == 0x2028) || | ||
922 | (c == 0x2029) || | ||
923 | (c == 0x202f) || | ||
924 | (c == 0x205f) || | ||
925 | (c == 0x3000) | ||
926 | ) | ||
927 | return EINA_TRUE; | ||
928 | return EINA_FALSE; | ||
929 | } | ||
930 | |||
931 | /** | ||
932 | * @internal | ||
933 | * Prepends the text between s and p to the main cursor of the object. | ||
934 | * | ||
935 | * @param cur the cursor to prepend to. | ||
936 | * @param[in] s start of the string | ||
937 | * @param[in] p end of the string | ||
938 | */ | ||
939 | static void | ||
940 | _prepend_text_run(Evas_Textblock_Cursor *cur, const char *s, const char *p) | ||
941 | { | ||
942 | if ((s) && (p > s)) | ||
943 | { | ||
944 | char *ts; | ||
945 | |||
946 | ts = alloca(p - s + 1); | ||
947 | strncpy(ts, s, p - s); | ||
948 | ts[p - s] = 0; | ||
949 | evas_textblock_cursor_text_prepend(cur, ts); | ||
950 | } | ||
951 | } | ||
952 | |||
953 | |||
954 | /** | ||
955 | * @internal | ||
956 | * Returns the numeric value of HEX chars for example for ch = 'A' | ||
957 | * the function will return 10. | ||
958 | * | ||
959 | * @param ch The HEX char. | ||
960 | * @return numeric value of HEX. | ||
961 | */ | ||
962 | static int | ||
963 | _hex_string_get(char ch) | ||
964 | { | ||
965 | if ((ch >= '0') && (ch <= '9')) return (ch - '0'); | ||
966 | else if ((ch >= 'A') && (ch <= 'F')) return (ch - 'A' + 10); | ||
967 | else if ((ch >= 'a') && (ch <= 'f')) return (ch - 'a' + 10); | ||
968 | return 0; | ||
969 | } | ||
970 | |||
971 | /** | ||
972 | * @internal | ||
973 | * Parses a string of one of the formas: | ||
974 | * 1. "#RRGGBB" | ||
975 | * 2. "#RRGGBBAA" | ||
976 | * 3. "#RGB" | ||
977 | * 4. "#RGBA" | ||
978 | * To the rgba values. | ||
979 | * | ||
980 | * @param[in] str The string to parse - NOT NULL. | ||
981 | * @param[out] r The Red value - NOT NULL. | ||
982 | * @param[out] g The Green value - NOT NULL. | ||
983 | * @param[out] b The Blue value - NOT NULL. | ||
984 | * @param[out] a The Alpha value - NOT NULL. | ||
985 | */ | ||
986 | static void | ||
987 | _format_color_parse(const char *str, unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) | ||
988 | { | ||
989 | int slen; | ||
990 | |||
991 | slen = strlen(str); | ||
992 | *r = *g = *b = *a = 0; | ||
993 | |||
994 | if (slen == 7) /* #RRGGBB */ | ||
995 | { | ||
996 | *r = (_hex_string_get(str[1]) << 4) | (_hex_string_get(str[2])); | ||
997 | *g = (_hex_string_get(str[3]) << 4) | (_hex_string_get(str[4])); | ||
998 | *b = (_hex_string_get(str[5]) << 4) | (_hex_string_get(str[6])); | ||
999 | *a = 0xff; | ||
1000 | } | ||
1001 | else if (slen == 9) /* #RRGGBBAA */ | ||
1002 | { | ||
1003 | *r = (_hex_string_get(str[1]) << 4) | (_hex_string_get(str[2])); | ||
1004 | *g = (_hex_string_get(str[3]) << 4) | (_hex_string_get(str[4])); | ||
1005 | *b = (_hex_string_get(str[5]) << 4) | (_hex_string_get(str[6])); | ||
1006 | *a = (_hex_string_get(str[7]) << 4) | (_hex_string_get(str[8])); | ||
1007 | } | ||
1008 | else if (slen == 4) /* #RGB */ | ||
1009 | { | ||
1010 | *r = _hex_string_get(str[1]); | ||
1011 | *r = (*r << 4) | *r; | ||
1012 | *g = _hex_string_get(str[2]); | ||
1013 | *g = (*g << 4) | *g; | ||
1014 | *b = _hex_string_get(str[3]); | ||
1015 | *b = (*b << 4) | *b; | ||
1016 | *a = 0xff; | ||
1017 | } | ||
1018 | else if (slen == 5) /* #RGBA */ | ||
1019 | { | ||
1020 | *r = _hex_string_get(str[1]); | ||
1021 | *r = (*r << 4) | *r; | ||
1022 | *g = _hex_string_get(str[2]); | ||
1023 | *g = (*g << 4) | *g; | ||
1024 | *b = _hex_string_get(str[3]); | ||
1025 | *b = (*b << 4) | *b; | ||
1026 | *a = _hex_string_get(str[4]); | ||
1027 | *a = (*a << 4) | *a; | ||
1028 | } | ||
1029 | *r = (*r * *a) / 255; | ||
1030 | *g = (*g * *a) / 255; | ||
1031 | *b = (*b * *a) / 255; | ||
1032 | } | ||
1033 | |||
1034 | /* The refcount for the formats. */ | ||
1035 | static int format_refcount = 0; | ||
1036 | /* Holders for the stringshares */ | ||
1037 | static const char *fontstr = NULL; | ||
1038 | static const char *font_fallbacksstr = NULL; | ||
1039 | static const char *font_sizestr = NULL; | ||
1040 | static const char *font_sourcestr = NULL; | ||
1041 | static const char *font_weightstr = NULL; | ||
1042 | static const char *font_stylestr = NULL; | ||
1043 | static const char *font_widthstr = NULL; | ||
1044 | static const char *langstr = NULL; | ||
1045 | static const char *colorstr = NULL; | ||
1046 | static const char *underline_colorstr = NULL; | ||
1047 | static const char *underline2_colorstr = NULL; | ||
1048 | static const char *underline_dash_colorstr = NULL; | ||
1049 | static const char *outline_colorstr = NULL; | ||
1050 | static const char *shadow_colorstr = NULL; | ||
1051 | static const char *glow_colorstr = NULL; | ||
1052 | static const char *glow2_colorstr = NULL; | ||
1053 | static const char *backing_colorstr = NULL; | ||
1054 | static const char *strikethrough_colorstr = NULL; | ||
1055 | static const char *alignstr = NULL; | ||
1056 | static const char *valignstr = NULL; | ||
1057 | static const char *wrapstr = NULL; | ||
1058 | static const char *left_marginstr = NULL; | ||
1059 | static const char *right_marginstr = NULL; | ||
1060 | static const char *underlinestr = NULL; | ||
1061 | static const char *strikethroughstr = NULL; | ||
1062 | static const char *backingstr = NULL; | ||
1063 | static const char *stylestr = NULL; | ||
1064 | static const char *tabstopsstr = NULL; | ||
1065 | static const char *linesizestr = NULL; | ||
1066 | static const char *linerelsizestr = NULL; | ||
1067 | static const char *linegapstr = NULL; | ||
1068 | static const char *linerelgapstr = NULL; | ||
1069 | static const char *itemstr = NULL; | ||
1070 | static const char *linefillstr = NULL; | ||
1071 | static const char *ellipsisstr = NULL; | ||
1072 | static const char *passwordstr = NULL; | ||
1073 | static const char *underline_dash_widthstr = NULL; | ||
1074 | static const char *underline_dash_gapstr = NULL; | ||
1075 | |||
1076 | /** | ||
1077 | * @internal | ||
1078 | * Init the format strings. | ||
1079 | */ | ||
1080 | static void | ||
1081 | _format_command_init(void) | ||
1082 | { | ||
1083 | if (format_refcount == 0) | ||
1084 | { | ||
1085 | fontstr = eina_stringshare_add("font"); | ||
1086 | font_fallbacksstr = eina_stringshare_add("font_fallbacks"); | ||
1087 | font_sizestr = eina_stringshare_add("font_size"); | ||
1088 | font_sourcestr = eina_stringshare_add("font_source"); | ||
1089 | font_weightstr = eina_stringshare_add("font_weight"); | ||
1090 | font_stylestr = eina_stringshare_add("font_style"); | ||
1091 | font_widthstr = eina_stringshare_add("font_width"); | ||
1092 | langstr = eina_stringshare_add("lang"); | ||
1093 | colorstr = eina_stringshare_add("color"); | ||
1094 | underline_colorstr = eina_stringshare_add("underline_color"); | ||
1095 | underline2_colorstr = eina_stringshare_add("underline2_color"); | ||
1096 | underline_dash_colorstr = eina_stringshare_add("underline_dash_color"); | ||
1097 | outline_colorstr = eina_stringshare_add("outline_color"); | ||
1098 | shadow_colorstr = eina_stringshare_add("shadow_color"); | ||
1099 | glow_colorstr = eina_stringshare_add("glow_color"); | ||
1100 | glow2_colorstr = eina_stringshare_add("glow2_color"); | ||
1101 | backing_colorstr = eina_stringshare_add("backing_color"); | ||
1102 | strikethrough_colorstr = eina_stringshare_add("strikethrough_color"); | ||
1103 | alignstr = eina_stringshare_add("align"); | ||
1104 | valignstr = eina_stringshare_add("valign"); | ||
1105 | wrapstr = eina_stringshare_add("wrap"); | ||
1106 | left_marginstr = eina_stringshare_add("left_margin"); | ||
1107 | right_marginstr = eina_stringshare_add("right_margin"); | ||
1108 | underlinestr = eina_stringshare_add("underline"); | ||
1109 | strikethroughstr = eina_stringshare_add("strikethrough"); | ||
1110 | backingstr = eina_stringshare_add("backing"); | ||
1111 | stylestr = eina_stringshare_add("style"); | ||
1112 | tabstopsstr = eina_stringshare_add("tabstops"); | ||
1113 | linesizestr = eina_stringshare_add("linesize"); | ||
1114 | linerelsizestr = eina_stringshare_add("linerelsize"); | ||
1115 | linegapstr = eina_stringshare_add("linegap"); | ||
1116 | linerelgapstr = eina_stringshare_add("linerelgap"); | ||
1117 | itemstr = eina_stringshare_add("item"); | ||
1118 | linefillstr = eina_stringshare_add("linefill"); | ||
1119 | ellipsisstr = eina_stringshare_add("ellipsis"); | ||
1120 | passwordstr = eina_stringshare_add("password"); | ||
1121 | underline_dash_widthstr = eina_stringshare_add("underline_dash_width"); | ||
1122 | underline_dash_gapstr = eina_stringshare_add("underline_dash_gap"); | ||
1123 | } | ||
1124 | format_refcount++; | ||
1125 | } | ||
1126 | |||
1127 | /** | ||
1128 | * @internal | ||
1129 | * Shutdown the format strings. | ||
1130 | */ | ||
1131 | static void | ||
1132 | _format_command_shutdown(void) | ||
1133 | { | ||
1134 | if (--format_refcount > 0) return; | ||
1135 | |||
1136 | eina_stringshare_del(fontstr); | ||
1137 | eina_stringshare_del(font_fallbacksstr); | ||
1138 | eina_stringshare_del(font_sizestr); | ||
1139 | eina_stringshare_del(font_sourcestr); | ||
1140 | eina_stringshare_del(font_weightstr); | ||
1141 | eina_stringshare_del(font_stylestr); | ||
1142 | eina_stringshare_del(font_widthstr); | ||
1143 | eina_stringshare_del(langstr); | ||
1144 | eina_stringshare_del(colorstr); | ||
1145 | eina_stringshare_del(underline_colorstr); | ||
1146 | eina_stringshare_del(underline2_colorstr); | ||
1147 | eina_stringshare_del(underline_dash_colorstr); | ||
1148 | eina_stringshare_del(outline_colorstr); | ||
1149 | eina_stringshare_del(shadow_colorstr); | ||
1150 | eina_stringshare_del(glow_colorstr); | ||
1151 | eina_stringshare_del(glow2_colorstr); | ||
1152 | eina_stringshare_del(backing_colorstr); | ||
1153 | eina_stringshare_del(strikethrough_colorstr); | ||
1154 | eina_stringshare_del(alignstr); | ||
1155 | eina_stringshare_del(valignstr); | ||
1156 | eina_stringshare_del(wrapstr); | ||
1157 | eina_stringshare_del(left_marginstr); | ||
1158 | eina_stringshare_del(right_marginstr); | ||
1159 | eina_stringshare_del(underlinestr); | ||
1160 | eina_stringshare_del(strikethroughstr); | ||
1161 | eina_stringshare_del(backingstr); | ||
1162 | eina_stringshare_del(stylestr); | ||
1163 | eina_stringshare_del(tabstopsstr); | ||
1164 | eina_stringshare_del(linesizestr); | ||
1165 | eina_stringshare_del(linerelsizestr); | ||
1166 | eina_stringshare_del(linegapstr); | ||
1167 | eina_stringshare_del(linerelgapstr); | ||
1168 | eina_stringshare_del(itemstr); | ||
1169 | eina_stringshare_del(linefillstr); | ||
1170 | eina_stringshare_del(ellipsisstr); | ||
1171 | eina_stringshare_del(passwordstr); | ||
1172 | eina_stringshare_del(underline_dash_widthstr); | ||
1173 | eina_stringshare_del(underline_dash_gapstr); | ||
1174 | } | ||
1175 | |||
1176 | /** | ||
1177 | * @internal | ||
1178 | * Copies str to dst while removing the \\ char, i.e unescape the escape sequences. | ||
1179 | * | ||
1180 | * @param[out] dst the destination string - Should not be NULL. | ||
1181 | * @param[in] src the source string - Should not be NULL. | ||
1182 | */ | ||
1183 | static void | ||
1184 | _format_clean_param(char *dst, const char *src) | ||
1185 | { | ||
1186 | const char *ss; | ||
1187 | char *ds; | ||
1188 | |||
1189 | ds = dst; | ||
1190 | for (ss = src; *ss; ss++, ds++) | ||
1191 | { | ||
1192 | if ((*ss == '\\') && *(ss + 1)) ss++; | ||
1193 | *ds = *ss; | ||
1194 | } | ||
1195 | *ds = 0; | ||
1196 | } | ||
1197 | |||
1198 | /** | ||
1199 | * @internal | ||
1200 | * Parses the cmd and parameter and adds the parsed format to fmt. | ||
1201 | * | ||
1202 | * @param obj the evas object - should not be NULL. | ||
1203 | * @param fmt The format to populate - should not be NULL. | ||
1204 | * @param[in] cmd the command to process, should be stringshared. | ||
1205 | * @param[in] param the parameter of the command. | ||
1206 | */ | ||
1207 | static void | ||
1208 | _format_command(Evas_Object *obj, Evas_Object_Textblock_Format *fmt, const char *cmd, const char *param) | ||
1209 | { | ||
1210 | int len; | ||
1211 | char *tmp_param; | ||
1212 | |||
1213 | len = strlen(param); | ||
1214 | tmp_param = alloca(len + 1); | ||
1215 | |||
1216 | _format_clean_param(tmp_param, param); | ||
1217 | |||
1218 | /* If we are changing the font, create the fdesc. */ | ||
1219 | if ((cmd == font_weightstr) || (cmd == font_widthstr) || | ||
1220 | (cmd == font_stylestr) || (cmd == langstr) || | ||
1221 | (cmd == fontstr) || (cmd == font_fallbacksstr)) | ||
1222 | { | ||
1223 | if (!fmt->font.fdesc) | ||
1224 | { | ||
1225 | fmt->font.fdesc = evas_font_desc_new(); | ||
1226 | } | ||
1227 | else if (!fmt->font.fdesc->is_new) | ||
1228 | { | ||
1229 | Evas_Font_Description *old = fmt->font.fdesc; | ||
1230 | fmt->font.fdesc = evas_font_desc_dup(fmt->font.fdesc); | ||
1231 | if (old) evas_font_desc_unref(old); | ||
1232 | } | ||
1233 | } | ||
1234 | |||
1235 | |||
1236 | if (cmd == fontstr) | ||
1237 | { | ||
1238 | evas_font_name_parse(fmt->font.fdesc, tmp_param); | ||
1239 | } | ||
1240 | else if (cmd == font_fallbacksstr) | ||
1241 | { | ||
1242 | eina_stringshare_replace(&(fmt->font.fdesc->fallbacks), tmp_param); | ||
1243 | } | ||
1244 | else if (cmd == font_sizestr) | ||
1245 | { | ||
1246 | int v; | ||
1247 | |||
1248 | v = atoi(tmp_param); | ||
1249 | if (v != fmt->font.size) | ||
1250 | { | ||
1251 | fmt->font.size = v; | ||
1252 | } | ||
1253 | } | ||
1254 | else if (cmd == font_sourcestr) | ||
1255 | { | ||
1256 | if ((!fmt->font.source) || | ||
1257 | ((fmt->font.source) && (strcmp(fmt->font.source, tmp_param)))) | ||
1258 | { | ||
1259 | if (fmt->font.source) eina_stringshare_del(fmt->font.source); | ||
1260 | fmt->font.source = eina_stringshare_add(tmp_param); | ||
1261 | } | ||
1262 | } | ||
1263 | else if (cmd == font_weightstr) | ||
1264 | { | ||
1265 | fmt->font.fdesc->weight = evas_font_style_find(tmp_param, | ||
1266 | tmp_param + strlen(tmp_param), EVAS_FONT_STYLE_WEIGHT); | ||
1267 | } | ||
1268 | else if (cmd == font_stylestr) | ||
1269 | { | ||
1270 | fmt->font.fdesc->slant = evas_font_style_find(tmp_param, | ||
1271 | tmp_param + strlen(tmp_param), EVAS_FONT_STYLE_SLANT); | ||
1272 | } | ||
1273 | else if (cmd == font_widthstr) | ||
1274 | { | ||
1275 | fmt->font.fdesc->width = evas_font_style_find(tmp_param, | ||
1276 | tmp_param + strlen(tmp_param), EVAS_FONT_STYLE_WIDTH); | ||
1277 | } | ||
1278 | else if (cmd == langstr) | ||
1279 | { | ||
1280 | eina_stringshare_replace(&(fmt->font.fdesc->lang), tmp_param); | ||
1281 | } | ||
1282 | else if (cmd == colorstr) | ||
1283 | _format_color_parse(tmp_param, | ||
1284 | &(fmt->color.normal.r), &(fmt->color.normal.g), | ||
1285 | &(fmt->color.normal.b), &(fmt->color.normal.a)); | ||
1286 | else if (cmd == underline_colorstr) | ||
1287 | _format_color_parse(tmp_param, | ||
1288 | &(fmt->color.underline.r), &(fmt->color.underline.g), | ||
1289 | &(fmt->color.underline.b), &(fmt->color.underline.a)); | ||
1290 | else if (cmd == underline2_colorstr) | ||
1291 | _format_color_parse(tmp_param, | ||
1292 | &(fmt->color.underline2.r), &(fmt->color.underline2.g), | ||
1293 | &(fmt->color.underline2.b), &(fmt->color.underline2.a)); | ||
1294 | else if (cmd == underline_dash_colorstr) | ||
1295 | _format_color_parse(tmp_param, | ||
1296 | &(fmt->color.underline_dash.r), &(fmt->color.underline_dash.g), | ||
1297 | &(fmt->color.underline_dash.b), &(fmt->color.underline_dash.a)); | ||
1298 | else if (cmd == outline_colorstr) | ||
1299 | _format_color_parse(tmp_param, | ||
1300 | &(fmt->color.outline.r), &(fmt->color.outline.g), | ||
1301 | &(fmt->color.outline.b), &(fmt->color.outline.a)); | ||
1302 | else if (cmd == shadow_colorstr) | ||
1303 | _format_color_parse(tmp_param, | ||
1304 | &(fmt->color.shadow.r), &(fmt->color.shadow.g), | ||
1305 | &(fmt->color.shadow.b), &(fmt->color.shadow.a)); | ||
1306 | else if (cmd == glow_colorstr) | ||
1307 | _format_color_parse(tmp_param, | ||
1308 | &(fmt->color.glow.r), &(fmt->color.glow.g), | ||
1309 | &(fmt->color.glow.b), &(fmt->color.glow.a)); | ||
1310 | else if (cmd == glow2_colorstr) | ||
1311 | _format_color_parse(tmp_param, | ||
1312 | &(fmt->color.glow2.r), &(fmt->color.glow2.g), | ||
1313 | &(fmt->color.glow2.b), &(fmt->color.glow2.a)); | ||
1314 | else if (cmd == backing_colorstr) | ||
1315 | _format_color_parse(tmp_param, | ||
1316 | &(fmt->color.backing.r), &(fmt->color.backing.g), | ||
1317 | &(fmt->color.backing.b), &(fmt->color.backing.a)); | ||
1318 | else if (cmd == strikethrough_colorstr) | ||
1319 | _format_color_parse(tmp_param, | ||
1320 | &(fmt->color.strikethrough.r), &(fmt->color.strikethrough.g), | ||
1321 | &(fmt->color.strikethrough.b), &(fmt->color.strikethrough.a)); | ||
1322 | else if (cmd == alignstr) | ||
1323 | { | ||
1324 | if (!strcmp(tmp_param, "auto")) | ||
1325 | { | ||
1326 | fmt->halign_auto = EINA_TRUE; | ||
1327 | } | ||
1328 | else | ||
1329 | { | ||
1330 | if (!strcmp(tmp_param, "middle")) fmt->halign = 0.5; | ||
1331 | else if (!strcmp(tmp_param, "center")) fmt->halign = 0.5; | ||
1332 | else if (!strcmp(tmp_param, "left")) fmt->halign = 0.0; | ||
1333 | else if (!strcmp(tmp_param, "right")) fmt->halign = 1.0; | ||
1334 | else | ||
1335 | { | ||
1336 | char *endptr = NULL; | ||
1337 | double val = strtod(tmp_param, &endptr); | ||
1338 | if (endptr) | ||
1339 | { | ||
1340 | while (*endptr && _is_white(*endptr)) | ||
1341 | endptr++; | ||
1342 | if (*endptr == '%') | ||
1343 | val /= 100.0; | ||
1344 | } | ||
1345 | fmt->halign = val; | ||
1346 | if (fmt->halign < 0.0) fmt->halign = 0.0; | ||
1347 | else if (fmt->halign > 1.0) fmt->halign = 1.0; | ||
1348 | } | ||
1349 | fmt->halign_auto = EINA_FALSE; | ||
1350 | } | ||
1351 | } | ||
1352 | else if (cmd == valignstr) | ||
1353 | { | ||
1354 | if (!strcmp(tmp_param, "top")) fmt->valign = 0.0; | ||
1355 | else if (!strcmp(tmp_param, "middle")) fmt->valign = 0.5; | ||
1356 | else if (!strcmp(tmp_param, "center")) fmt->valign = 0.5; | ||
1357 | else if (!strcmp(tmp_param, "bottom")) fmt->valign = 1.0; | ||
1358 | else if (!strcmp(tmp_param, "baseline")) fmt->valign = -1.0; | ||
1359 | else if (!strcmp(tmp_param, "base")) fmt->valign = -1.0; | ||
1360 | else | ||
1361 | { | ||
1362 | char *endptr = NULL; | ||
1363 | double val = strtod(tmp_param, &endptr); | ||
1364 | if (endptr) | ||
1365 | { | ||
1366 | while (*endptr && _is_white(*endptr)) | ||
1367 | endptr++; | ||
1368 | if (*endptr == '%') | ||
1369 | val /= 100.0; | ||
1370 | } | ||
1371 | fmt->valign = val; | ||
1372 | if (fmt->valign < 0.0) fmt->valign = 0.0; | ||
1373 | else if (fmt->valign > 1.0) fmt->valign = 1.0; | ||
1374 | } | ||
1375 | } | ||
1376 | else if (cmd == wrapstr) | ||
1377 | { | ||
1378 | if (!strcmp(tmp_param, "word")) | ||
1379 | { | ||
1380 | fmt->wrap_word = 1; | ||
1381 | fmt->wrap_char = fmt->wrap_mixed = 0; | ||
1382 | } | ||
1383 | else if (!strcmp(tmp_param, "char")) | ||
1384 | { | ||
1385 | fmt->wrap_word = fmt->wrap_mixed = 0; | ||
1386 | fmt->wrap_char = 1; | ||
1387 | } | ||
1388 | else if (!strcmp(tmp_param, "mixed")) | ||
1389 | { | ||
1390 | fmt->wrap_word = fmt->wrap_char = 0; | ||
1391 | fmt->wrap_mixed = 1; | ||
1392 | } | ||
1393 | else | ||
1394 | { | ||
1395 | fmt->wrap_word = fmt->wrap_mixed = fmt->wrap_char = 0; | ||
1396 | } | ||
1397 | } | ||
1398 | else if (cmd == left_marginstr) | ||
1399 | { | ||
1400 | if (!strcmp(tmp_param, "reset")) | ||
1401 | fmt->margin.l = 0; | ||
1402 | else | ||
1403 | { | ||
1404 | if (tmp_param[0] == '+') | ||
1405 | fmt->margin.l += atoi(&(tmp_param[1])); | ||
1406 | else if (tmp_param[0] == '-') | ||
1407 | fmt->margin.l -= atoi(&(tmp_param[1])); | ||
1408 | else | ||
1409 | fmt->margin.l = atoi(tmp_param); | ||
1410 | if (fmt->margin.l < 0) fmt->margin.l = 0; | ||
1411 | } | ||
1412 | } | ||
1413 | else if (cmd == right_marginstr) | ||
1414 | { | ||
1415 | if (!strcmp(tmp_param, "reset")) | ||
1416 | fmt->margin.r = 0; | ||
1417 | else | ||
1418 | { | ||
1419 | if (tmp_param[0] == '+') | ||
1420 | fmt->margin.r += atoi(&(tmp_param[1])); | ||
1421 | else if (tmp_param[0] == '-') | ||
1422 | fmt->margin.r -= atoi(&(tmp_param[1])); | ||
1423 | else | ||
1424 | fmt->margin.r = atoi(tmp_param); | ||
1425 | if (fmt->margin.r < 0) fmt->margin.r = 0; | ||
1426 | } | ||
1427 | } | ||
1428 | else if (cmd == underlinestr) | ||
1429 | { | ||
1430 | if (!strcmp(tmp_param, "off")) | ||
1431 | { | ||
1432 | fmt->underline = 0; | ||
1433 | fmt->underline2 = 0; | ||
1434 | } | ||
1435 | else if ((!strcmp(tmp_param, "on")) || | ||
1436 | (!strcmp(tmp_param, "single"))) | ||
1437 | { | ||
1438 | fmt->underline = 1; | ||
1439 | fmt->underline2 = 0; | ||
1440 | } | ||
1441 | else if (!strcmp(tmp_param, "double")) | ||
1442 | { | ||
1443 | fmt->underline = 1; | ||
1444 | fmt->underline2 = 1; | ||
1445 | } | ||
1446 | else if (!strcmp(tmp_param, "dashed")) | ||
1447 | fmt->underline_dash = 1; | ||
1448 | } | ||
1449 | else if (cmd == strikethroughstr) | ||
1450 | { | ||
1451 | if (!strcmp(tmp_param, "off")) | ||
1452 | fmt->strikethrough = 0; | ||
1453 | else if (!strcmp(tmp_param, "on")) | ||
1454 | fmt->strikethrough = 1; | ||
1455 | } | ||
1456 | else if (cmd == backingstr) | ||
1457 | { | ||
1458 | if (!strcmp(tmp_param, "off")) | ||
1459 | fmt->backing = 0; | ||
1460 | else if (!strcmp(tmp_param, "on")) | ||
1461 | fmt->backing = 1; | ||
1462 | } | ||
1463 | else if (cmd == stylestr) | ||
1464 | { | ||
1465 | char *p1, *p2, *p, *pp; | ||
1466 | |||
1467 | p1 = alloca(len + 1); | ||
1468 | *p1 = 0; | ||
1469 | p2 = alloca(len + 1); | ||
1470 | *p2 = 0; | ||
1471 | /* no comma */ | ||
1472 | if (!strstr(tmp_param, ",")) p1 = tmp_param; | ||
1473 | else | ||
1474 | { | ||
1475 | /* split string "str1,str2" into p1 and p2 (if we have more than | ||
1476 | * 1 str2 eg "str1,str2,str3,str4" then we don't care. p2 just | ||
1477 | * ends up being the last one as right now it's only valid to have | ||
1478 | * 1 comma and 2 strings */ | ||
1479 | pp = p1; | ||
1480 | for (p = tmp_param; *p; p++) | ||
1481 | { | ||
1482 | if (*p == ',') | ||
1483 | { | ||
1484 | *pp = 0; | ||
1485 | pp = p2; | ||
1486 | continue; | ||
1487 | } | ||
1488 | *pp = *p; | ||
1489 | pp++; | ||
1490 | } | ||
1491 | *pp = 0; | ||
1492 | } | ||
1493 | if (!strcmp(p1, "off")) fmt->style = EVAS_TEXT_STYLE_PLAIN; | ||
1494 | else if (!strcmp(p1, "none")) fmt->style = EVAS_TEXT_STYLE_PLAIN; | ||
1495 | else if (!strcmp(p1, "plain")) fmt->style = EVAS_TEXT_STYLE_PLAIN; | ||
1496 | else if (!strcmp(p1, "shadow")) fmt->style = EVAS_TEXT_STYLE_SHADOW; | ||
1497 | else if (!strcmp(p1, "outline")) fmt->style = EVAS_TEXT_STYLE_OUTLINE; | ||
1498 | else if (!strcmp(p1, "soft_outline")) fmt->style = EVAS_TEXT_STYLE_SOFT_OUTLINE; | ||
1499 | else if (!strcmp(p1, "outline_shadow")) fmt->style = EVAS_TEXT_STYLE_OUTLINE_SHADOW; | ||
1500 | else if (!strcmp(p1, "outline_soft_shadow")) fmt->style = EVAS_TEXT_STYLE_OUTLINE_SOFT_SHADOW; | ||
1501 | else if (!strcmp(p1, "glow")) fmt->style = EVAS_TEXT_STYLE_GLOW; | ||
1502 | else if (!strcmp(p1, "far_shadow")) fmt->style = EVAS_TEXT_STYLE_FAR_SHADOW; | ||
1503 | else if (!strcmp(p1, "soft_shadow")) fmt->style = EVAS_TEXT_STYLE_SOFT_SHADOW; | ||
1504 | else if (!strcmp(p1, "far_soft_shadow")) fmt->style = EVAS_TEXT_STYLE_FAR_SOFT_SHADOW; | ||
1505 | else fmt->style = EVAS_TEXT_STYLE_PLAIN; | ||
1506 | |||
1507 | if (*p2) | ||
1508 | { | ||
1509 | if (!strcmp(p2, "bottom_right")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_RIGHT); | ||
1510 | else if (!strcmp(p2, "bottom")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM); | ||
1511 | else if (!strcmp(p2, "bottom_left")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_LEFT); | ||
1512 | else if (!strcmp(p2, "left")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_LEFT); | ||
1513 | else if (!strcmp(p2, "top_left")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP_LEFT); | ||
1514 | else if (!strcmp(p2, "top")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP); | ||
1515 | else if (!strcmp(p2, "top_right")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP_RIGHT); | ||
1516 | else if (!strcmp(p2, "right")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_RIGHT); | ||
1517 | else EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_RIGHT); | ||
1518 | } | ||
1519 | } | ||
1520 | else if (cmd == tabstopsstr) | ||
1521 | { | ||
1522 | fmt->tabstops = atoi(tmp_param); | ||
1523 | if (fmt->tabstops < 1) fmt->tabstops = 1; | ||
1524 | } | ||
1525 | else if (cmd == linesizestr) | ||
1526 | { | ||
1527 | fmt->linesize = atoi(tmp_param); | ||
1528 | fmt->linerelsize = 0.0; | ||
1529 | } | ||
1530 | else if (cmd == linerelsizestr) | ||
1531 | { | ||
1532 | char *endptr = NULL; | ||
1533 | double val = strtod(tmp_param, &endptr); | ||
1534 | if (endptr) | ||
1535 | { | ||
1536 | while (*endptr && _is_white(*endptr)) | ||
1537 | endptr++; | ||
1538 | if (*endptr == '%') | ||
1539 | { | ||
1540 | fmt->linerelsize = val / 100.0; | ||
1541 | fmt->linesize = 0; | ||
1542 | if (fmt->linerelsize < 0.0) fmt->linerelsize = 0.0; | ||
1543 | } | ||
1544 | } | ||
1545 | } | ||
1546 | else if (cmd == linegapstr) | ||
1547 | { | ||
1548 | fmt->linegap = atoi(tmp_param); | ||
1549 | fmt->linerelgap = 0.0; | ||
1550 | } | ||
1551 | else if (cmd == linerelgapstr) | ||
1552 | { | ||
1553 | char *endptr = NULL; | ||
1554 | double val = strtod(tmp_param, &endptr); | ||
1555 | if (endptr) | ||
1556 | { | ||
1557 | while (*endptr && _is_white(*endptr)) | ||
1558 | endptr++; | ||
1559 | if (*endptr == '%') | ||
1560 | { | ||
1561 | fmt->linerelgap = val / 100.0; | ||
1562 | fmt->linegap = 0; | ||
1563 | if (fmt->linerelgap < 0.0) fmt->linerelgap = 0.0; | ||
1564 | } | ||
1565 | } | ||
1566 | } | ||
1567 | else if (cmd == itemstr) | ||
1568 | { | ||
1569 | // itemstr == replacement object items in textblock - inline imges | ||
1570 | // for example | ||
1571 | } | ||
1572 | else if (cmd == linefillstr) | ||
1573 | { | ||
1574 | char *endptr = NULL; | ||
1575 | double val = strtod(tmp_param, &endptr); | ||
1576 | if (endptr) | ||
1577 | { | ||
1578 | while (*endptr && _is_white(*endptr)) | ||
1579 | endptr++; | ||
1580 | if (*endptr == '%') | ||
1581 | { | ||
1582 | fmt->linefill = val / 100.0; | ||
1583 | if (fmt->linefill < 0.0) fmt->linefill = 0.0; | ||
1584 | } | ||
1585 | } | ||
1586 | } | ||
1587 | else if (cmd == ellipsisstr) | ||
1588 | { | ||
1589 | char *endptr = NULL; | ||
1590 | fmt->ellipsis = strtod(tmp_param, &endptr); | ||
1591 | if ((fmt->ellipsis < 0.0) || (fmt->ellipsis > 1.0)) | ||
1592 | fmt->ellipsis = -1.0; | ||
1593 | else | ||
1594 | { | ||
1595 | Evas_Object_Textblock *o; | ||
1596 | |||
1597 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
1598 | o->have_ellipsis = 1; | ||
1599 | } | ||
1600 | } | ||
1601 | else if (cmd == passwordstr) | ||
1602 | { | ||
1603 | if (!strcmp(tmp_param, "off")) | ||
1604 | fmt->password = 0; | ||
1605 | else if (!strcmp(tmp_param, "on")) | ||
1606 | fmt->password = 1; | ||
1607 | } | ||
1608 | else if (cmd == underline_dash_widthstr) | ||
1609 | { | ||
1610 | fmt->underline_dash_width = atoi(tmp_param); | ||
1611 | if (fmt->underline_dash_width <= 0) fmt->underline_dash_width = 1; | ||
1612 | } | ||
1613 | else if (cmd == underline_dash_gapstr) | ||
1614 | { | ||
1615 | fmt->underline_dash_gap = atoi(tmp_param); | ||
1616 | if (fmt->underline_dash_gap <= 0) fmt->underline_dash_gap = 1; | ||
1617 | } | ||
1618 | } | ||
1619 | |||
1620 | /** | ||
1621 | * @internal | ||
1622 | * Returns #EINA_TRUE if the item is a format parameter, #EINA_FALSE otherwise. | ||
1623 | * | ||
1624 | * @param[in] item the item to check - Not NULL. | ||
1625 | */ | ||
1626 | static Eina_Bool | ||
1627 | _format_is_param(const char *item) | ||
1628 | { | ||
1629 | if (strchr(item, '=')) return EINA_TRUE; | ||
1630 | return EINA_FALSE; | ||
1631 | } | ||
1632 | |||
1633 | /** | ||
1634 | * @internal | ||
1635 | * Parse the format item and populate key and val with the stringshares that | ||
1636 | * corrospond to the formats parsed. | ||
1637 | * It expects item to be of the structure: | ||
1638 | * "key=val" | ||
1639 | * | ||
1640 | * @param[in] item the item to parse - Not NULL. | ||
1641 | * @param[out] key where to store the key at - Not NULL. | ||
1642 | * @param[out] val where to store the value at - Not NULL. | ||
1643 | */ | ||
1644 | static void | ||
1645 | _format_param_parse(const char *item, const char **key, const char **val) | ||
1646 | { | ||
1647 | const char *start, *end, *quote; | ||
1648 | |||
1649 | start = strchr(item, '='); | ||
1650 | *key = eina_stringshare_add_length(item, start - item); | ||
1651 | start++; /* Advance after the '=' */ | ||
1652 | /* If we can find a quote, our new delimiter is a quote, not a space. */ | ||
1653 | if ((quote = strchr(start, '\''))) | ||
1654 | { | ||
1655 | start = quote + 1; | ||
1656 | end = strchr(start, '\''); | ||
1657 | } | ||
1658 | else | ||
1659 | { | ||
1660 | end = strchr(start, ' '); | ||
1661 | } | ||
1662 | |||
1663 | /* Null terminate before the spaces */ | ||
1664 | if (end) | ||
1665 | { | ||
1666 | *val = eina_stringshare_add_length(start, end - start); | ||
1667 | } | ||
1668 | else | ||
1669 | { | ||
1670 | *val = eina_stringshare_add(start); | ||
1671 | } | ||
1672 | } | ||
1673 | |||
1674 | /** | ||
1675 | * @internal | ||
1676 | * This function parses the format passed in *s and advances s to point to the | ||
1677 | * next format item, while returning the current one as the return value. | ||
1678 | * @param s The current and returned position in the format string. | ||
1679 | * @return the current item parsed from the string. | ||
1680 | */ | ||
1681 | static const char * | ||
1682 | _format_parse(const char **s) | ||
1683 | { | ||
1684 | const char *p; | ||
1685 | const char *s1 = NULL, *s2 = NULL; | ||
1686 | Eina_Bool quote = EINA_FALSE;; | ||
1687 | |||
1688 | p = *s; | ||
1689 | if (*p == 0) return NULL; | ||
1690 | for (;;) | ||
1691 | { | ||
1692 | if (!s1) | ||
1693 | { | ||
1694 | if (*p != ' ') s1 = p; | ||
1695 | if (*p == 0) break; | ||
1696 | } | ||
1697 | else if (!s2) | ||
1698 | { | ||
1699 | if (*p == '\'') | ||
1700 | { | ||
1701 | quote = !quote; | ||
1702 | } | ||
1703 | |||
1704 | if ((p > *s) && (p[-1] != '\\') && (!quote)) | ||
1705 | { | ||
1706 | if (*p == ' ') s2 = p; | ||
1707 | } | ||
1708 | if (*p == 0) s2 = p; | ||
1709 | } | ||
1710 | p++; | ||
1711 | if (s1 && s2) | ||
1712 | { | ||
1713 | *s = s2; | ||
1714 | return s1; | ||
1715 | } | ||
1716 | } | ||
1717 | *s = p; | ||
1718 | return NULL; | ||
1719 | } | ||
1720 | |||
1721 | /** | ||
1722 | * @internal | ||
1723 | * Parse the format str and populate fmt with the formats found. | ||
1724 | * | ||
1725 | * @param obj The evas object - Not NULL. | ||
1726 | * @param[out] fmt The format to populate - Not NULL. | ||
1727 | * @param[in] str the string to parse.- Not NULL. | ||
1728 | */ | ||
1729 | static void | ||
1730 | _format_fill(Evas_Object *obj, Evas_Object_Textblock_Format *fmt, const char *str) | ||
1731 | { | ||
1732 | const char *s; | ||
1733 | const char *item; | ||
1734 | |||
1735 | s = str; | ||
1736 | |||
1737 | /* get rid of any spaces at the start of the string */ | ||
1738 | while (*s == ' ') s++; | ||
1739 | |||
1740 | while ((item = _format_parse(&s))) | ||
1741 | { | ||
1742 | if (_format_is_param(item)) | ||
1743 | { | ||
1744 | const char *key = NULL, *val = NULL; | ||
1745 | |||
1746 | _format_param_parse(item, &key, &val); | ||
1747 | _format_command(obj, fmt, key, val); | ||
1748 | eina_stringshare_del(key); | ||
1749 | eina_stringshare_del(val); | ||
1750 | } | ||
1751 | else | ||
1752 | { | ||
1753 | /* immediate - not handled here */ | ||
1754 | } | ||
1755 | } | ||
1756 | } | ||
1757 | |||
1758 | /** | ||
1759 | * @internal | ||
1760 | * Duplicate a format and return the duplicate. | ||
1761 | * | ||
1762 | * @param obj The evas object - Not NULL. | ||
1763 | * @param[in] fmt The format to duplicate - Not NULL. | ||
1764 | * @return the copy of the format. | ||
1765 | */ | ||
1766 | static Evas_Object_Textblock_Format * | ||
1767 | _format_dup(Evas_Object *obj, const Evas_Object_Textblock_Format *fmt) | ||
1768 | { | ||
1769 | Evas_Object_Textblock_Format *fmt2; | ||
1770 | |||
1771 | fmt2 = calloc(1, sizeof(Evas_Object_Textblock_Format)); | ||
1772 | memcpy(fmt2, fmt, sizeof(Evas_Object_Textblock_Format)); | ||
1773 | fmt2->ref = 1; | ||
1774 | fmt2->font.fdesc = evas_font_desc_ref(fmt->font.fdesc); | ||
1775 | |||
1776 | if (fmt->font.source) fmt2->font.source = eina_stringshare_add(fmt->font.source); | ||
1777 | |||
1778 | /* FIXME: just ref the font here... */ | ||
1779 | fmt2->font.font = evas_font_load(obj->layer->evas, fmt2->font.fdesc, | ||
1780 | fmt2->font.source, (int)(((double) fmt2->font.size) * obj->cur.scale)); | ||
1781 | return fmt2; | ||
1782 | } | ||
1783 | |||
1784 | |||
1785 | |||
1786 | |||
1787 | /** | ||
1788 | * @internal | ||
1789 | * @typedef Ctxt | ||
1790 | * | ||
1791 | * A pack of information that needed to be passed around in the layout engine, | ||
1792 | * packed for easier access. | ||
1793 | */ | ||
1794 | typedef struct _Ctxt Ctxt; | ||
1795 | |||
1796 | struct _Ctxt | ||
1797 | { | ||
1798 | Evas_Object *obj; | ||
1799 | Evas_Object_Textblock *o; | ||
1800 | |||
1801 | Evas_Object_Textblock_Paragraph *paragraphs; | ||
1802 | Evas_Object_Textblock_Paragraph *par; | ||
1803 | Evas_Object_Textblock_Line *ln; | ||
1804 | |||
1805 | |||
1806 | Eina_List *format_stack; | ||
1807 | Evas_Object_Textblock_Format *fmt; | ||
1808 | |||
1809 | int x, y; | ||
1810 | int w, h; | ||
1811 | int wmax, hmax; | ||
1812 | int maxascent, maxdescent; | ||
1813 | int marginl, marginr; | ||
1814 | int line_no; | ||
1815 | int underline_extend; | ||
1816 | int have_underline, have_underline2; | ||
1817 | double align, valign; | ||
1818 | Eina_Bool align_auto : 1; | ||
1819 | Eina_Bool width_changed : 1; | ||
1820 | }; | ||
1821 | |||
1822 | static void _layout_text_add_logical_item(Ctxt *c, Evas_Object_Textblock_Text_Item *ti, Eina_List *rel); | ||
1823 | static void _text_item_update_sizes(Ctxt *c, Evas_Object_Textblock_Text_Item *ti); | ||
1824 | static void _layout_do_format(const Evas_Object *obj, Ctxt *c, Evas_Object_Textblock_Format **_fmt, Evas_Object_Textblock_Node_Format *n, int *style_pad_l, int *style_pad_r, int *style_pad_t, int *style_pad_b, Eina_Bool create_item); | ||
1825 | /** | ||
1826 | * @internal | ||
1827 | * Adjust the ascent/descent of the format and context. | ||
1828 | * | ||
1829 | * @param maxascent The ascent to update - Not NUL. | ||
1830 | * @param maxdescent The descent to update - Not NUL. | ||
1831 | * @param fmt The format to adjust - NOT NULL. | ||
1832 | */ | ||
1833 | static void | ||
1834 | _layout_format_ascent_descent_adjust(const Evas_Object *obj, | ||
1835 | Evas_Coord *maxascent, Evas_Coord *maxdescent, | ||
1836 | Evas_Object_Textblock_Format *fmt) | ||
1837 | { | ||
1838 | int ascent, descent; | ||
1839 | |||
1840 | if (fmt->font.font) | ||
1841 | { | ||
1842 | // ascent = c->ENFN->font_max_ascent_get(c->ENDT, fmt->font.font); | ||
1843 | // descent = c->ENFN->font_max_descent_get(c->ENDT, fmt->font.font); | ||
1844 | ascent = ENFN->font_ascent_get(ENDT, fmt->font.font); | ||
1845 | descent = ENFN->font_descent_get(ENDT, fmt->font.font); | ||
1846 | if (fmt->linesize > 0) | ||
1847 | { | ||
1848 | if ((ascent + descent) < fmt->linesize) | ||
1849 | { | ||
1850 | ascent = ((fmt->linesize * ascent) / (ascent + descent)); | ||
1851 | descent = fmt->linesize - ascent; | ||
1852 | } | ||
1853 | } | ||
1854 | else if (fmt->linerelsize > 0.0) | ||
1855 | { | ||
1856 | descent = descent * fmt->linerelsize; | ||
1857 | ascent = ascent * fmt->linerelsize; | ||
1858 | } | ||
1859 | descent += fmt->linegap; | ||
1860 | descent += ((ascent + descent) * fmt->linerelgap); | ||
1861 | if (*maxascent < ascent) *maxascent = ascent; | ||
1862 | if (*maxdescent < descent) *maxdescent = descent; | ||
1863 | if (fmt->linefill > 0.0) | ||
1864 | { | ||
1865 | int dh; | ||
1866 | |||
1867 | dh = obj->cur.geometry.h - (*maxascent + *maxdescent); | ||
1868 | if (dh < 0) dh = 0; | ||
1869 | dh = fmt->linefill * dh; | ||
1870 | *maxdescent += dh / 2; | ||
1871 | *maxascent += dh - (dh / 2); | ||
1872 | // FIXME: set flag that says "if heigh changes - reformat" | ||
1873 | } | ||
1874 | } | ||
1875 | } | ||
1876 | |||
1877 | /** | ||
1878 | * @internal | ||
1879 | * Create a new line using the info from the format and update the format | ||
1880 | * and context. | ||
1881 | * | ||
1882 | * @param c The context to work on - Not NULL. | ||
1883 | * @param fmt The format to use info from - NOT NULL. | ||
1884 | */ | ||
1885 | static void | ||
1886 | _layout_line_new(Ctxt *c, Evas_Object_Textblock_Format *fmt) | ||
1887 | { | ||
1888 | c->ln = calloc(1, sizeof(Evas_Object_Textblock_Line)); | ||
1889 | c->align = fmt->halign; | ||
1890 | c->align_auto = fmt->halign_auto; | ||
1891 | c->marginl = fmt->margin.l; | ||
1892 | c->marginr = fmt->margin.r; | ||
1893 | c->par->lines = (Evas_Object_Textblock_Line *)eina_inlist_append(EINA_INLIST_GET(c->par->lines), EINA_INLIST_GET(c->ln)); | ||
1894 | c->x = 0; | ||
1895 | c->maxascent = c->maxdescent = 0; | ||
1896 | c->ln->line_no = -1; | ||
1897 | c->ln->par = c->par; | ||
1898 | } | ||
1899 | |||
1900 | static inline Evas_Object_Textblock_Paragraph * | ||
1901 | _layout_find_paragraph_by_y(Evas_Object_Textblock *o, Evas_Coord y) | ||
1902 | { | ||
1903 | Evas_Object_Textblock_Paragraph *start, *par; | ||
1904 | int i; | ||
1905 | |||
1906 | start = o->paragraphs; | ||
1907 | |||
1908 | for (i = 0 ; i < TEXTBLOCK_PAR_INDEX_SIZE ; i++) | ||
1909 | { | ||
1910 | if (!o->par_index[i] || (o->par_index[i]->y > y)) | ||
1911 | { | ||
1912 | break; | ||
1913 | } | ||
1914 | start = o->par_index[i]; | ||
1915 | } | ||
1916 | |||
1917 | EINA_INLIST_FOREACH(start, par) | ||
1918 | { | ||
1919 | if ((par->y <= y) && (y < par->y + par->h)) | ||
1920 | return par; | ||
1921 | } | ||
1922 | |||
1923 | return NULL; | ||
1924 | } | ||
1925 | |||
1926 | static inline Evas_Object_Textblock_Paragraph * | ||
1927 | _layout_find_paragraph_by_line_no(Evas_Object_Textblock *o, int line_no) | ||
1928 | { | ||
1929 | Evas_Object_Textblock_Paragraph *start, *par; | ||
1930 | int i; | ||
1931 | |||
1932 | start = o->paragraphs; | ||
1933 | |||
1934 | for (i = 0 ; i < TEXTBLOCK_PAR_INDEX_SIZE ; i++) | ||
1935 | { | ||
1936 | if (!o->par_index[i] || (o->par_index[i]->line_no > line_no)) | ||
1937 | { | ||
1938 | break; | ||
1939 | } | ||
1940 | start = o->par_index[i]; | ||
1941 | } | ||
1942 | |||
1943 | EINA_INLIST_FOREACH(start, par) | ||
1944 | { | ||
1945 | Evas_Object_Textblock_Paragraph *npar = | ||
1946 | (Evas_Object_Textblock_Paragraph *) EINA_INLIST_GET(par)->next; | ||
1947 | if ((par->line_no <= line_no) && | ||
1948 | (!npar || (line_no < npar->line_no))) | ||
1949 | return par; | ||
1950 | } | ||
1951 | |||
1952 | return NULL; | ||
1953 | } | ||
1954 | /* End of rbtree index functios */ | ||
1955 | |||
1956 | /** | ||
1957 | * @internal | ||
1958 | * Create a new layout paragraph. | ||
1959 | * If c->par is not NULL, the paragraph is appended/prepended according | ||
1960 | * to the append parameter. If it is NULL, the paragraph is appended at | ||
1961 | * the end of the list. | ||
1962 | * | ||
1963 | * @param c The context to work on - Not NULL. | ||
1964 | * @param n the associated text node | ||
1965 | * @param append true to append, false to prpend. | ||
1966 | */ | ||
1967 | static void | ||
1968 | _layout_paragraph_new(Ctxt *c, Evas_Object_Textblock_Node_Text *n, | ||
1969 | Eina_Bool append) | ||
1970 | { | ||
1971 | Evas_Object_Textblock_Paragraph *rel_par = c->par; | ||
1972 | c->par = calloc(1, sizeof(Evas_Object_Textblock_Paragraph)); | ||
1973 | if (append || !rel_par) | ||
1974 | c->paragraphs = (Evas_Object_Textblock_Paragraph *) | ||
1975 | eina_inlist_append_relative(EINA_INLIST_GET(c->paragraphs), | ||
1976 | EINA_INLIST_GET(c->par), | ||
1977 | EINA_INLIST_GET(rel_par)); | ||
1978 | else | ||
1979 | c->paragraphs = (Evas_Object_Textblock_Paragraph *) | ||
1980 | eina_inlist_prepend_relative(EINA_INLIST_GET(c->paragraphs), | ||
1981 | EINA_INLIST_GET(c->par), | ||
1982 | EINA_INLIST_GET(rel_par)); | ||
1983 | |||
1984 | c->ln = NULL; | ||
1985 | c->par->text_node = n; | ||
1986 | if (n) | ||
1987 | n->par = c->par; | ||
1988 | c->par->line_no = -1; | ||
1989 | c->par->visible = 1; | ||
1990 | c->o->num_paragraphs++; | ||
1991 | } | ||
1992 | |||
1993 | #ifdef BIDI_SUPPORT | ||
1994 | /** | ||
1995 | * @internal | ||
1996 | * Update bidi paragraph props. | ||
1997 | * | ||
1998 | * @param par The paragraph to update | ||
1999 | */ | ||
2000 | static inline void | ||
2001 | _layout_update_bidi_props(const Evas_Object_Textblock *o, | ||
2002 | Evas_Object_Textblock_Paragraph *par) | ||
2003 | { | ||
2004 | if (par->text_node) | ||
2005 | { | ||
2006 | const Eina_Unicode *text; | ||
2007 | int *segment_idxs = NULL; | ||
2008 | text = eina_ustrbuf_string_get(par->text_node->unicode); | ||
2009 | |||
2010 | if (o->bidi_delimiters) | ||
2011 | segment_idxs = evas_bidi_segment_idxs_get(text, o->bidi_delimiters); | ||
2012 | |||
2013 | evas_bidi_paragraph_props_unref(par->bidi_props); | ||
2014 | par->bidi_props = evas_bidi_paragraph_props_get(text, | ||
2015 | eina_ustrbuf_length_get(par->text_node->unicode), | ||
2016 | segment_idxs); | ||
2017 | par->direction = EVAS_BIDI_PARAGRAPH_DIRECTION_IS_RTL(par->bidi_props) ? | ||
2018 | EVAS_BIDI_DIRECTION_RTL : EVAS_BIDI_DIRECTION_LTR; | ||
2019 | par->is_bidi = !!par->bidi_props; | ||
2020 | if (segment_idxs) free(segment_idxs); | ||
2021 | } | ||
2022 | } | ||
2023 | #endif | ||
2024 | |||
2025 | |||
2026 | /** | ||
2027 | * @internal | ||
2028 | * Free the visual lines in the paragraph (logical items are kept) | ||
2029 | */ | ||
2030 | static void | ||
2031 | _paragraph_clear(const Evas_Object *obj __UNUSED__, | ||
2032 | Evas_Object_Textblock_Paragraph *par) | ||
2033 | { | ||
2034 | while (par->lines) | ||
2035 | { | ||
2036 | Evas_Object_Textblock_Line *ln; | ||
2037 | |||
2038 | ln = (Evas_Object_Textblock_Line *) par->lines; | ||
2039 | par->lines = (Evas_Object_Textblock_Line *)eina_inlist_remove(EINA_INLIST_GET(par->lines), EINA_INLIST_GET(par->lines)); | ||
2040 | _line_free(ln); | ||
2041 | } | ||
2042 | } | ||
2043 | |||
2044 | /** | ||
2045 | * @internal | ||
2046 | * Free the layout paragraph and all of it's lines and logical items. | ||
2047 | */ | ||
2048 | static void | ||
2049 | _paragraph_free(const Evas_Object *obj, Evas_Object_Textblock_Paragraph *par) | ||
2050 | { | ||
2051 | Evas_Object_Textblock *o; | ||
2052 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
2053 | _paragraph_clear(obj, par); | ||
2054 | |||
2055 | { | ||
2056 | Eina_List *i, *i_prev; | ||
2057 | Evas_Object_Textblock_Item *it; | ||
2058 | EINA_LIST_FOREACH_SAFE(par->logical_items, i, i_prev, it) | ||
2059 | { | ||
2060 | _item_free(obj, NULL, it); | ||
2061 | } | ||
2062 | eina_list_free(par->logical_items); | ||
2063 | } | ||
2064 | #ifdef BIDI_SUPPORT | ||
2065 | if (par->bidi_props) | ||
2066 | evas_bidi_paragraph_props_unref(par->bidi_props); | ||
2067 | #endif | ||
2068 | /* If we are the active par of the text node, set to NULL */ | ||
2069 | if (par->text_node && (par->text_node->par == par)) | ||
2070 | par->text_node->par = NULL; | ||
2071 | |||
2072 | o->num_paragraphs--; | ||
2073 | |||
2074 | free(par); | ||
2075 | } | ||
2076 | |||
2077 | /** | ||
2078 | * @internal | ||
2079 | * Clear all the paragraphs from the inlist pars. | ||
2080 | * | ||
2081 | * @param obj the evas object - Not NULL. | ||
2082 | * @param pars the paragraphs to clean - Not NULL. | ||
2083 | */ | ||
2084 | static void | ||
2085 | _paragraphs_clear(const Evas_Object *obj, Evas_Object_Textblock_Paragraph *pars) | ||
2086 | { | ||
2087 | Evas_Object_Textblock_Paragraph *par; | ||
2088 | |||
2089 | EINA_INLIST_FOREACH(EINA_INLIST_GET(pars), par) | ||
2090 | { | ||
2091 | _paragraph_clear(obj, par); | ||
2092 | } | ||
2093 | } | ||
2094 | |||
2095 | /** | ||
2096 | * @internal | ||
2097 | * Free the paragraphs from the inlist pars, the difference between this and | ||
2098 | * _paragraphs_clear is that the latter keeps the logical items and the par | ||
2099 | * items, while the former frees them as well. | ||
2100 | * | ||
2101 | * @param obj the evas object - Not NULL. | ||
2102 | * @param pars the paragraphs to clean - Not NULL. | ||
2103 | */ | ||
2104 | static void | ||
2105 | _paragraphs_free(const Evas_Object *obj, Evas_Object_Textblock_Paragraph *pars) | ||
2106 | { | ||
2107 | Evas_Object_Textblock *o; | ||
2108 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
2109 | |||
2110 | o->num_paragraphs = 0; | ||
2111 | |||
2112 | while (pars) | ||
2113 | { | ||
2114 | Evas_Object_Textblock_Paragraph *par; | ||
2115 | |||
2116 | par = (Evas_Object_Textblock_Paragraph *) pars; | ||
2117 | pars = (Evas_Object_Textblock_Paragraph *)eina_inlist_remove(EINA_INLIST_GET(pars), EINA_INLIST_GET(par)); | ||
2118 | _paragraph_free(obj, par); | ||
2119 | } | ||
2120 | } | ||
2121 | |||
2122 | /** | ||
2123 | * @internal | ||
2124 | * Push fmt to the format stack, if fmt is NULL, will fush a default item. | ||
2125 | * | ||
2126 | * @param c the context to work on - Not NULL. | ||
2127 | * @param fmt the format to push. | ||
2128 | * @see _layout_format_pop() | ||
2129 | */ | ||
2130 | static Evas_Object_Textblock_Format * | ||
2131 | _layout_format_push(Ctxt *c, Evas_Object_Textblock_Format *fmt, | ||
2132 | Evas_Object_Textblock_Node_Format *fnode) | ||
2133 | { | ||
2134 | if (fmt) | ||
2135 | { | ||
2136 | fmt = _format_dup(c->obj, fmt); | ||
2137 | c->format_stack = eina_list_prepend(c->format_stack, fmt); | ||
2138 | fmt->fnode = fnode; | ||
2139 | } | ||
2140 | else | ||
2141 | { | ||
2142 | fmt = calloc(1, sizeof(Evas_Object_Textblock_Format)); | ||
2143 | c->format_stack = eina_list_prepend(c->format_stack, fmt); | ||
2144 | fmt->ref = 1; | ||
2145 | fmt->halign = 0.0; | ||
2146 | fmt->halign_auto = EINA_TRUE; | ||
2147 | fmt->valign = -1.0; | ||
2148 | fmt->style = EVAS_TEXT_STYLE_PLAIN; | ||
2149 | fmt->tabstops = 32; | ||
2150 | fmt->linesize = 0; | ||
2151 | fmt->linerelsize = 0.0; | ||
2152 | fmt->linegap = 0; | ||
2153 | fmt->underline_dash_width = 6; | ||
2154 | fmt->underline_dash_gap = 2; | ||
2155 | fmt->linerelgap = 0.0; | ||
2156 | fmt->password = 1; | ||
2157 | } | ||
2158 | return fmt; | ||
2159 | } | ||
2160 | |||
2161 | /** | ||
2162 | * @internal | ||
2163 | * Pop fmt to the format stack, if there's something in the stack free fmt | ||
2164 | * and set it to point to the next item instead, else return fmt. | ||
2165 | * | ||
2166 | * @param c the context to work on - Not NULL. | ||
2167 | * @param format - the text of the format to free (assured to start with '-'). | ||
2168 | * @return the next format in the stack, or format if there's none. | ||
2169 | * @see _layout_format_push() | ||
2170 | */ | ||
2171 | static Evas_Object_Textblock_Format * | ||
2172 | _layout_format_pop(Ctxt *c, const char *format) | ||
2173 | { | ||
2174 | Evas_Object_Textblock_Format *fmt = eina_list_data_get(c->format_stack); | ||
2175 | |||
2176 | if ((c->format_stack) && (c->format_stack->next)) | ||
2177 | { | ||
2178 | Eina_List *redo_nodes = NULL; | ||
2179 | |||
2180 | /* Generic pop, should just pop. */ | ||
2181 | if (((format[0] == ' ') && !format[1]) || | ||
2182 | !format[0]) | ||
2183 | { | ||
2184 | _format_unref_free(c->obj, fmt); | ||
2185 | c->format_stack = | ||
2186 | eina_list_remove_list(c->format_stack, c->format_stack); | ||
2187 | } | ||
2188 | else | ||
2189 | { | ||
2190 | size_t len = strlen(format); | ||
2191 | Eina_List *i, *i_next; | ||
2192 | /* Remove only the matching format. */ | ||
2193 | EINA_LIST_FOREACH_SAFE(c->format_stack, i, i_next, fmt) | ||
2194 | { | ||
2195 | /* Stop when we reach the base item */ | ||
2196 | if (!i_next) | ||
2197 | break; | ||
2198 | |||
2199 | c->format_stack = | ||
2200 | eina_list_remove_list(c->format_stack, c->format_stack); | ||
2201 | |||
2202 | /* Make sure the ending tag matches the starting tag. | ||
2203 | * I.e whole of the ending tag matches the start of the | ||
2204 | * starting tag, and the starting tag's next char is either | ||
2205 | * NULL or white. Skip the starting '+'. */ | ||
2206 | if (_FORMAT_IS_CLOSER_OF( | ||
2207 | fmt->fnode->orig_format, format, len)) | ||
2208 | { | ||
2209 | _format_unref_free(c->obj, fmt); | ||
2210 | break; | ||
2211 | } | ||
2212 | else | ||
2213 | { | ||
2214 | redo_nodes = eina_list_prepend(redo_nodes, fmt->fnode); | ||
2215 | _format_unref_free(c->obj, fmt); | ||
2216 | } | ||
2217 | } | ||
2218 | } | ||
2219 | |||
2220 | /* Redo all the nodes needed to be redone */ | ||
2221 | { | ||
2222 | Evas_Object_Textblock_Node_Format *fnode; | ||
2223 | Eina_List *i, *i_next; | ||
2224 | |||
2225 | EINA_LIST_FOREACH_SAFE(redo_nodes, i, i_next, fnode) | ||
2226 | { | ||
2227 | /* FIXME: Actually do something with the new acquired padding, | ||
2228 | * the can be different and affect our padding! */ | ||
2229 | Evas_Coord style_pad_l, style_pad_r, style_pad_t, style_pad_b; | ||
2230 | style_pad_l = style_pad_r = style_pad_t = style_pad_b = 0; | ||
2231 | redo_nodes = eina_list_remove_list(redo_nodes, i); | ||
2232 | fmt = eina_list_data_get(c->format_stack); | ||
2233 | _layout_do_format(c->obj, c, &fmt, fnode, | ||
2234 | &style_pad_l, &style_pad_r, | ||
2235 | &style_pad_t, &style_pad_b, EINA_FALSE); | ||
2236 | } | ||
2237 | } | ||
2238 | |||
2239 | fmt = eina_list_data_get(c->format_stack); | ||
2240 | } | ||
2241 | return fmt; | ||
2242 | } | ||
2243 | |||
2244 | /** | ||
2245 | * @internal | ||
2246 | * Parse item and fill fmt with the item. | ||
2247 | * | ||
2248 | * @param c the context to work on - Not NULL. | ||
2249 | * @param fmt the format to fill - not null. | ||
2250 | */ | ||
2251 | static void | ||
2252 | _layout_format_value_handle(Ctxt *c, Evas_Object_Textblock_Format *fmt, const char *item) | ||
2253 | { | ||
2254 | const char *key = NULL, *val = NULL; | ||
2255 | |||
2256 | _format_param_parse(item, &key, &val); | ||
2257 | if ((key) && (val)) _format_command(c->obj, fmt, key, val); | ||
2258 | if (key) eina_stringshare_del(key); | ||
2259 | if (val) eina_stringshare_del(val); | ||
2260 | c->align = fmt->halign; | ||
2261 | c->align_auto = fmt->halign_auto; | ||
2262 | c->marginl = fmt->margin.l; | ||
2263 | c->marginr = fmt->margin.r; | ||
2264 | } | ||
2265 | |||
2266 | #define VSIZE_FULL 0 | ||
2267 | #define VSIZE_ASCENT 1 | ||
2268 | |||
2269 | #define SIZE 0 | ||
2270 | #define SIZE_ABS 1 | ||
2271 | #define SIZE_REL 2 | ||
2272 | |||
2273 | /** | ||
2274 | * @internal | ||
2275 | * Get the current line's alignment from the context. | ||
2276 | * | ||
2277 | * @param c the context to work on - Not NULL. | ||
2278 | */ | ||
2279 | static inline double | ||
2280 | _layout_line_align_get(Ctxt *c) | ||
2281 | { | ||
2282 | #ifdef BIDI_SUPPORT | ||
2283 | if (c->align_auto && c->ln) | ||
2284 | { | ||
2285 | if (c->ln->items && c->ln->items->text_node && | ||
2286 | (c->ln->par->direction == EVAS_BIDI_DIRECTION_RTL)) | ||
2287 | { | ||
2288 | /* Align right*/ | ||
2289 | return 1.0; | ||
2290 | } | ||
2291 | else | ||
2292 | { | ||
2293 | /* Align left */ | ||
2294 | return 0.0; | ||
2295 | } | ||
2296 | } | ||
2297 | #endif | ||
2298 | return c->align; | ||
2299 | } | ||
2300 | |||
2301 | #ifdef BIDI_SUPPORT | ||
2302 | /** | ||
2303 | * @internal | ||
2304 | * Reorder the items in visual order | ||
2305 | * | ||
2306 | * @param line the line to reorder | ||
2307 | */ | ||
2308 | static void | ||
2309 | _layout_line_reorder(Evas_Object_Textblock_Line *line) | ||
2310 | { | ||
2311 | /*FIXME: do it a bit more efficient - not very efficient ATM. */ | ||
2312 | Evas_Object_Textblock_Item *it; | ||
2313 | EvasBiDiStrIndex *v_to_l = NULL; | ||
2314 | Evas_Coord x; | ||
2315 | size_t start, end; | ||
2316 | size_t len; | ||
2317 | |||
2318 | if (line->items && line->items->text_node && | ||
2319 | line->par->bidi_props) | ||
2320 | { | ||
2321 | Evas_BiDi_Paragraph_Props *props; | ||
2322 | props = line->par->bidi_props; | ||
2323 | start = end = line->items->text_pos; | ||
2324 | |||
2325 | /* Find the first and last positions in the line */ | ||
2326 | |||
2327 | EINA_INLIST_FOREACH(line->items, it) | ||
2328 | { | ||
2329 | if (it->text_pos < start) | ||
2330 | { | ||
2331 | start = it->text_pos; | ||
2332 | } | ||
2333 | else | ||
2334 | { | ||
2335 | int tlen; | ||
2336 | tlen = (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? | ||
2337 | _ITEM_TEXT(it)->text_props.text_len : 1; | ||
2338 | if (it->text_pos + tlen > end) | ||
2339 | { | ||
2340 | end = it->text_pos + tlen; | ||
2341 | } | ||
2342 | } | ||
2343 | } | ||
2344 | |||
2345 | len = end - start; | ||
2346 | evas_bidi_props_reorder_line(NULL, start, len, props, &v_to_l); | ||
2347 | |||
2348 | /* Update visual pos */ | ||
2349 | { | ||
2350 | Evas_Object_Textblock_Item *i; | ||
2351 | i = line->items; | ||
2352 | while (i) | ||
2353 | { | ||
2354 | i->visual_pos = evas_bidi_position_logical_to_visual( | ||
2355 | v_to_l, len, i->text_pos - start); | ||
2356 | i = (Evas_Object_Textblock_Item *) EINA_INLIST_GET(i)->next; | ||
2357 | } | ||
2358 | } | ||
2359 | |||
2360 | /*FIXME: not very efficient, sort the items arrays. Anyhow, should only | ||
2361 | * reorder if it's a bidi paragraph */ | ||
2362 | { | ||
2363 | Evas_Object_Textblock_Item *i, *j, *min; | ||
2364 | i = line->items; | ||
2365 | while (i) | ||
2366 | { | ||
2367 | min = i; | ||
2368 | EINA_INLIST_FOREACH(i, j) | ||
2369 | { | ||
2370 | if (j->visual_pos < min->visual_pos) | ||
2371 | { | ||
2372 | min = j; | ||
2373 | } | ||
2374 | } | ||
2375 | if (min != i) | ||
2376 | { | ||
2377 | line->items = (Evas_Object_Textblock_Item *) eina_inlist_remove(EINA_INLIST_GET(line->items), EINA_INLIST_GET(min)); | ||
2378 | line->items = (Evas_Object_Textblock_Item *) eina_inlist_prepend_relative(EINA_INLIST_GET(line->items), EINA_INLIST_GET(min), EINA_INLIST_GET(i)); | ||
2379 | } | ||
2380 | |||
2381 | i = (Evas_Object_Textblock_Item *) EINA_INLIST_GET(min)->next; | ||
2382 | } | ||
2383 | } | ||
2384 | } | ||
2385 | |||
2386 | if (v_to_l) free(v_to_l); | ||
2387 | x = 0; | ||
2388 | EINA_INLIST_FOREACH(line->items, it) | ||
2389 | { | ||
2390 | it->x = x; | ||
2391 | x += it->adv; | ||
2392 | } | ||
2393 | } | ||
2394 | #endif | ||
2395 | |||
2396 | /* FIXME: doc */ | ||
2397 | static void | ||
2398 | _layout_calculate_format_item_size(const Evas_Object *obj, | ||
2399 | const Evas_Object_Textblock_Format_Item *fi, | ||
2400 | Evas_Coord *maxascent, Evas_Coord *maxdescent, | ||
2401 | Evas_Coord *_y, Evas_Coord *_w, Evas_Coord *_h) | ||
2402 | { | ||
2403 | /* Adjust sizes according to current line height/scale */ | ||
2404 | Evas_Coord w, h; | ||
2405 | const char *p, *s; | ||
2406 | |||
2407 | s = fi->item; | ||
2408 | w = fi->parent.w; | ||
2409 | h = fi->parent.h; | ||
2410 | switch (fi->size) | ||
2411 | { | ||
2412 | case SIZE: | ||
2413 | p = strstr(s, " size="); | ||
2414 | if (p) | ||
2415 | { | ||
2416 | p += 6; | ||
2417 | if (sscanf(p, "%ix%i", &w, &h) == 2) | ||
2418 | { | ||
2419 | w = w * obj->cur.scale; | ||
2420 | h = h * obj->cur.scale; | ||
2421 | } | ||
2422 | } | ||
2423 | break; | ||
2424 | case SIZE_REL: | ||
2425 | p = strstr((char *) s, " relsize="); | ||
2426 | p += 9; | ||
2427 | if (sscanf(p, "%ix%i", &w, &h) == 2) | ||
2428 | { | ||
2429 | int sz = 1; | ||
2430 | if (fi->vsize == VSIZE_FULL) | ||
2431 | { | ||
2432 | sz = *maxdescent + *maxascent; | ||
2433 | } | ||
2434 | else if (fi->vsize == VSIZE_ASCENT) | ||
2435 | { | ||
2436 | sz = *maxascent; | ||
2437 | } | ||
2438 | w = (w * sz) / h; | ||
2439 | h = sz; | ||
2440 | } | ||
2441 | break; | ||
2442 | case SIZE_ABS: | ||
2443 | /* Nothing to do */ | ||
2444 | default: | ||
2445 | break; | ||
2446 | } | ||
2447 | |||
2448 | switch (fi->size) | ||
2449 | { | ||
2450 | case SIZE: | ||
2451 | case SIZE_ABS: | ||
2452 | switch (fi->vsize) | ||
2453 | { | ||
2454 | case VSIZE_FULL: | ||
2455 | if (h > (*maxdescent + *maxascent)) | ||
2456 | { | ||
2457 | *maxascent += h - (*maxdescent + *maxascent); | ||
2458 | *_y = -*maxascent; | ||
2459 | } | ||
2460 | else | ||
2461 | *_y = -(h - *maxdescent); | ||
2462 | break; | ||
2463 | case VSIZE_ASCENT: | ||
2464 | if (h > *maxascent) | ||
2465 | { | ||
2466 | *maxascent = h; | ||
2467 | *_y = -h; | ||
2468 | } | ||
2469 | else | ||
2470 | *_y = -h; | ||
2471 | break; | ||
2472 | default: | ||
2473 | break; | ||
2474 | } | ||
2475 | break; | ||
2476 | case SIZE_REL: | ||
2477 | switch (fi->vsize) | ||
2478 | { | ||
2479 | case VSIZE_FULL: | ||
2480 | case VSIZE_ASCENT: | ||
2481 | *_y = -*maxascent; | ||
2482 | break; | ||
2483 | default: | ||
2484 | break; | ||
2485 | } | ||
2486 | break; | ||
2487 | default: | ||
2488 | break; | ||
2489 | } | ||
2490 | |||
2491 | *_w = w; | ||
2492 | *_h = h; | ||
2493 | } | ||
2494 | |||
2495 | /** | ||
2496 | * @internal | ||
2497 | * Order the items in the line, update it's properties and update it's | ||
2498 | * corresponding paragraph. | ||
2499 | * | ||
2500 | * @param c the context to work on - Not NULL. | ||
2501 | * @param fmt the format to use. | ||
2502 | * @param add_line true if we should create a line, false otherwise. | ||
2503 | */ | ||
2504 | static void | ||
2505 | _layout_line_finalize(Ctxt *c, Evas_Object_Textblock_Format *fmt) | ||
2506 | { | ||
2507 | Evas_Object_Textblock_Item *it; | ||
2508 | Evas_Coord x = 0; | ||
2509 | |||
2510 | /* If there are no text items yet, calc ascent/descent | ||
2511 | * according to the current format. */ | ||
2512 | if (c->maxascent + c->maxdescent == 0) | ||
2513 | _layout_format_ascent_descent_adjust(c->obj, &c->maxascent, | ||
2514 | &c->maxdescent, fmt); | ||
2515 | |||
2516 | /* Adjust all the item sizes according to the final line size, | ||
2517 | * and update the x positions of all the items of the line. */ | ||
2518 | EINA_INLIST_FOREACH(c->ln->items, it) | ||
2519 | { | ||
2520 | if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) | ||
2521 | { | ||
2522 | Evas_Object_Textblock_Format_Item *fi = _ITEM_FORMAT(it); | ||
2523 | if (!fi->formatme) goto loop_advance; | ||
2524 | _layout_calculate_format_item_size(c->obj, fi, &c->maxascent, | ||
2525 | &c->maxdescent, &fi->y, &fi->parent.w, &fi->parent.h); | ||
2526 | fi->parent.adv = fi->parent.w; | ||
2527 | } | ||
2528 | |||
2529 | loop_advance: | ||
2530 | it->x = x; | ||
2531 | x += it->adv; | ||
2532 | |||
2533 | if ((it->x + it->adv) > c->ln->w) c->ln->w = it->x + it->adv; | ||
2534 | } | ||
2535 | |||
2536 | c->ln->y = (c->y - c->par->y) + c->o->style_pad.t; | ||
2537 | c->ln->h = c->maxascent + c->maxdescent; | ||
2538 | c->ln->baseline = c->maxascent; | ||
2539 | if (c->have_underline2) | ||
2540 | { | ||
2541 | if (c->maxdescent < 4) c->underline_extend = 4 - c->maxdescent; | ||
2542 | } | ||
2543 | else if (c->have_underline) | ||
2544 | { | ||
2545 | if (c->maxdescent < 2) c->underline_extend = 2 - c->maxdescent; | ||
2546 | } | ||
2547 | c->ln->line_no = c->line_no - c->ln->par->line_no; | ||
2548 | c->line_no++; | ||
2549 | c->y += c->maxascent + c->maxdescent; | ||
2550 | if (c->w >= 0) | ||
2551 | { | ||
2552 | c->ln->x = c->marginl + c->o->style_pad.l + | ||
2553 | ((c->w - c->ln->w - | ||
2554 | c->o->style_pad.l - c->o->style_pad.r - | ||
2555 | c->marginl - c->marginr) * _layout_line_align_get(c)); | ||
2556 | } | ||
2557 | else | ||
2558 | { | ||
2559 | c->ln->x = c->marginl + c->o->style_pad.l; | ||
2560 | } | ||
2561 | |||
2562 | c->par->h = c->ln->y + c->ln->h; | ||
2563 | if (c->ln->w > c->par->w) | ||
2564 | c->par->w = c->ln->w; | ||
2565 | |||
2566 | { | ||
2567 | Evas_Coord new_wmax = c->ln->w + | ||
2568 | c->marginl + c->marginr - (c->o->style_pad.l + c->o->style_pad.r); | ||
2569 | if (new_wmax > c->wmax) | ||
2570 | c->wmax = new_wmax; | ||
2571 | } | ||
2572 | } | ||
2573 | |||
2574 | /** | ||
2575 | * @internal | ||
2576 | * Create a new line and append it to the lines in the context. | ||
2577 | * | ||
2578 | * @param c the context to work on - Not NULL. | ||
2579 | * @param fmt the format to use. | ||
2580 | * @param add_line true if we should create a line, false otherwise. | ||
2581 | */ | ||
2582 | static void | ||
2583 | _layout_line_advance(Ctxt *c, Evas_Object_Textblock_Format *fmt) | ||
2584 | { | ||
2585 | _layout_line_finalize(c, fmt); | ||
2586 | _layout_line_new(c, fmt); | ||
2587 | } | ||
2588 | |||
2589 | /** | ||
2590 | * @internal | ||
2591 | * Create a new text layout item from the string and the format. | ||
2592 | * | ||
2593 | * @param c the context to work on - Not NULL. | ||
2594 | * @param fmt the format to use. | ||
2595 | * @param str the string to use. | ||
2596 | * @param len the length of the string. | ||
2597 | */ | ||
2598 | static Evas_Object_Textblock_Text_Item * | ||
2599 | _layout_text_item_new(Ctxt *c __UNUSED__, Evas_Object_Textblock_Format *fmt) | ||
2600 | { | ||
2601 | Evas_Object_Textblock_Text_Item *ti; | ||
2602 | |||
2603 | ti = calloc(1, sizeof(Evas_Object_Textblock_Text_Item)); | ||
2604 | ti->parent.format = fmt; | ||
2605 | ti->parent.format->ref++; | ||
2606 | ti->parent.type = EVAS_TEXTBLOCK_ITEM_TEXT; | ||
2607 | return ti; | ||
2608 | } | ||
2609 | |||
2610 | /** | ||
2611 | * @internal | ||
2612 | * Return the cutoff of the text in the text item. | ||
2613 | * | ||
2614 | * @param c the context to work on - Not NULL. | ||
2615 | * @param fmt the format to use. - Not NULL. | ||
2616 | * @param it the item to check - Not null. | ||
2617 | * @return -1 if there is no cutoff (either because there is really none, | ||
2618 | * or because of an error), cutoff index on success. | ||
2619 | */ | ||
2620 | static int | ||
2621 | _layout_text_cutoff_get(Ctxt *c, Evas_Object_Textblock_Format *fmt, | ||
2622 | const Evas_Object_Textblock_Text_Item *ti) | ||
2623 | { | ||
2624 | if (fmt->font.font) | ||
2625 | { | ||
2626 | Evas_Coord x; | ||
2627 | x = c->w - c->o->style_pad.l - c->o->style_pad.r - c->marginl - | ||
2628 | c->marginr - c->x - ti->x_adjustment; | ||
2629 | if (x < 0) | ||
2630 | x = 0; | ||
2631 | return c->ENFN->font_last_up_to_pos(c->ENDT, fmt->font.font, | ||
2632 | &ti->text_props, x, 0); | ||
2633 | } | ||
2634 | return -1; | ||
2635 | } | ||
2636 | |||
2637 | /** | ||
2638 | * @internal | ||
2639 | * Split before cut, and strip if str[cut - 1] is a whitespace. | ||
2640 | * | ||
2641 | * @param c the context to work on - Not NULL. | ||
2642 | * @param ti the item to cut - not null. | ||
2643 | * @param lti the logical list item of the item. | ||
2644 | * @param cut the cut index. | ||
2645 | * @return the second (newly created) item. | ||
2646 | */ | ||
2647 | static Evas_Object_Textblock_Text_Item * | ||
2648 | _layout_item_text_split_strip_white(Ctxt *c, | ||
2649 | Evas_Object_Textblock_Text_Item *ti, Eina_List *lti, size_t cut) | ||
2650 | { | ||
2651 | const Eina_Unicode *ts; | ||
2652 | Evas_Object_Textblock_Text_Item *new_ti = NULL, *white_ti = NULL; | ||
2653 | |||
2654 | ts = GET_ITEM_TEXT(ti); | ||
2655 | |||
2656 | if (!IS_AT_END(ti, cut) && (ti->text_props.text_len > 0)) | ||
2657 | { | ||
2658 | new_ti = _layout_text_item_new(c, ti->parent.format); | ||
2659 | new_ti->parent.text_node = ti->parent.text_node; | ||
2660 | new_ti->parent.text_pos = ti->parent.text_pos + cut; | ||
2661 | new_ti->parent.merge = EINA_TRUE; | ||
2662 | |||
2663 | evas_common_text_props_split(&ti->text_props, | ||
2664 | &new_ti->text_props, cut); | ||
2665 | _layout_text_add_logical_item(c, new_ti, lti); | ||
2666 | } | ||
2667 | |||
2668 | /* Strip the previous white if needed */ | ||
2669 | if ((cut >= 1) && _is_white(ts[cut - 1]) && (ti->text_props.text_len > 0)) | ||
2670 | { | ||
2671 | if (cut - 1 > 0) | ||
2672 | { | ||
2673 | size_t white_cut = cut - 1; | ||
2674 | white_ti = _layout_text_item_new(c, ti->parent.format); | ||
2675 | white_ti->parent.text_node = ti->parent.text_node; | ||
2676 | white_ti->parent.text_pos = ti->parent.text_pos + white_cut; | ||
2677 | white_ti->parent.merge = EINA_TRUE; | ||
2678 | white_ti->parent.visually_deleted = EINA_TRUE; | ||
2679 | |||
2680 | evas_common_text_props_split(&ti->text_props, | ||
2681 | &white_ti->text_props, white_cut); | ||
2682 | _layout_text_add_logical_item(c, white_ti, lti); | ||
2683 | } | ||
2684 | else | ||
2685 | { | ||
2686 | /* Mark this one as the visually deleted. */ | ||
2687 | ti->parent.visually_deleted = EINA_TRUE; | ||
2688 | } | ||
2689 | } | ||
2690 | |||
2691 | if (new_ti || white_ti) | ||
2692 | { | ||
2693 | _text_item_update_sizes(c, ti); | ||
2694 | } | ||
2695 | return new_ti; | ||
2696 | } | ||
2697 | |||
2698 | /** | ||
2699 | * @internal | ||
2700 | * Merge item2 into item1 and free item2. | ||
2701 | * | ||
2702 | * @param c the context to work on - Not NULL. | ||
2703 | * @param item1 the item to copy to | ||
2704 | * @param item2 the item to copy from | ||
2705 | */ | ||
2706 | static void | ||
2707 | _layout_item_merge_and_free(Ctxt *c, | ||
2708 | Evas_Object_Textblock_Text_Item *item1, | ||
2709 | Evas_Object_Textblock_Text_Item *item2) | ||
2710 | { | ||
2711 | evas_common_text_props_merge(&item1->text_props, | ||
2712 | &item2->text_props); | ||
2713 | |||
2714 | _text_item_update_sizes(c, item1); | ||
2715 | |||
2716 | item1->parent.merge = EINA_FALSE; | ||
2717 | item1->parent.visually_deleted = EINA_FALSE; | ||
2718 | |||
2719 | _item_free(c->obj, NULL, _ITEM(item2)); | ||
2720 | } | ||
2721 | |||
2722 | /** | ||
2723 | * @internal | ||
2724 | * Calculates an item's size. | ||
2725 | * | ||
2726 | * @param c the context | ||
2727 | * @param it the item itself. | ||
2728 | */ | ||
2729 | static void | ||
2730 | _text_item_update_sizes(Ctxt *c, Evas_Object_Textblock_Text_Item *ti) | ||
2731 | { | ||
2732 | int tw, th, inset, advw; | ||
2733 | const Evas_Object_Textblock_Format *fmt = ti->parent.format; | ||
2734 | int shad_sz = 0, shad_dst = 0, out_sz = 0; | ||
2735 | int dx = 0, minx = 0, maxx = 0, shx1, shx2; | ||
2736 | |||
2737 | tw = th = 0; | ||
2738 | if (fmt->font.font) | ||
2739 | c->ENFN->font_string_size_get(c->ENDT, fmt->font.font, | ||
2740 | &ti->text_props, &tw, &th); | ||
2741 | inset = 0; | ||
2742 | if (fmt->font.font) | ||
2743 | inset = c->ENFN->font_inset_get(c->ENDT, fmt->font.font, | ||
2744 | &ti->text_props); | ||
2745 | advw = 0; | ||
2746 | if (fmt->font.font) | ||
2747 | advw = c->ENFN->font_h_advance_get(c->ENDT, fmt->font.font, | ||
2748 | &ti->text_props); | ||
2749 | |||
2750 | |||
2751 | /* These adjustments are calculated and thus heavily linked to those in | ||
2752 | * textblock_render!!! Don't change one without the other. */ | ||
2753 | |||
2754 | switch (ti->parent.format->style & EVAS_TEXT_STYLE_MASK_BASIC) | ||
2755 | { | ||
2756 | case EVAS_TEXT_STYLE_SHADOW: | ||
2757 | shad_dst = 1; | ||
2758 | break; | ||
2759 | case EVAS_TEXT_STYLE_OUTLINE_SHADOW: | ||
2760 | case EVAS_TEXT_STYLE_FAR_SHADOW: | ||
2761 | shad_dst = 2; | ||
2762 | out_sz = 1; | ||
2763 | break; | ||
2764 | case EVAS_TEXT_STYLE_OUTLINE_SOFT_SHADOW: | ||
2765 | shad_dst = 1; | ||
2766 | shad_sz = 2; | ||
2767 | out_sz = 1; | ||
2768 | break; | ||
2769 | case EVAS_TEXT_STYLE_FAR_SOFT_SHADOW: | ||
2770 | shad_dst = 2; | ||
2771 | shad_sz = 2; | ||
2772 | break; | ||
2773 | case EVAS_TEXT_STYLE_SOFT_SHADOW: | ||
2774 | shad_dst = 1; | ||
2775 | shad_sz = 2; | ||
2776 | break; | ||
2777 | case EVAS_TEXT_STYLE_GLOW: | ||
2778 | case EVAS_TEXT_STYLE_SOFT_OUTLINE: | ||
2779 | out_sz = 2; | ||
2780 | break; | ||
2781 | case EVAS_TEXT_STYLE_OUTLINE: | ||
2782 | out_sz = 1; | ||
2783 | break; | ||
2784 | default: | ||
2785 | break; | ||
2786 | } | ||
2787 | switch (ti->parent.format->style & EVAS_TEXT_STYLE_MASK_SHADOW_DIRECTION) | ||
2788 | { | ||
2789 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_LEFT: | ||
2790 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_LEFT: | ||
2791 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP_LEFT: | ||
2792 | dx = -1; | ||
2793 | break; | ||
2794 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_RIGHT: | ||
2795 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP_RIGHT: | ||
2796 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_RIGHT: | ||
2797 | dx = 1; | ||
2798 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP: | ||
2799 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM: | ||
2800 | default: | ||
2801 | dx = 0; | ||
2802 | break; | ||
2803 | } | ||
2804 | minx = -out_sz; | ||
2805 | maxx = out_sz; | ||
2806 | shx1 = dx * shad_dst; | ||
2807 | shx1 -= shad_sz; | ||
2808 | shx2 = dx * shad_dst; | ||
2809 | shx2 += shad_sz; | ||
2810 | if (shx1 < minx) minx = shx1; | ||
2811 | if (shx2 > maxx) maxx = shx2; | ||
2812 | inset += -minx; | ||
2813 | ti->x_adjustment = maxx - minx; | ||
2814 | |||
2815 | ti->inset = inset; | ||
2816 | ti->parent.w = tw + ti->x_adjustment; | ||
2817 | ti->parent.h = th; | ||
2818 | ti->parent.adv = advw; | ||
2819 | ti->parent.x = 0; | ||
2820 | } | ||
2821 | |||
2822 | /** | ||
2823 | * @internal | ||
2824 | * Adds the item to the list, updates the item's properties (e.g, x,w,h) | ||
2825 | * | ||
2826 | * @param c the context | ||
2827 | * @param it the item itself. | ||
2828 | * @param rel item ti will be appened after, NULL = last. | ||
2829 | */ | ||
2830 | static void | ||
2831 | _layout_text_add_logical_item(Ctxt *c, Evas_Object_Textblock_Text_Item *ti, | ||
2832 | Eina_List *rel) | ||
2833 | { | ||
2834 | _text_item_update_sizes(c, ti); | ||
2835 | |||
2836 | c->par->logical_items = eina_list_append_relative_list( | ||
2837 | c->par->logical_items, ti, rel); | ||
2838 | } | ||
2839 | |||
2840 | /** | ||
2841 | * @internal | ||
2842 | * Appends the text from node n starting at start ending at off to the layout. | ||
2843 | * It uses the fmt for the formatting. | ||
2844 | * | ||
2845 | * @param c the current context- NOT NULL. | ||
2846 | * @param fmt the format to use. | ||
2847 | * @param n the text node. - Not null. | ||
2848 | * @param start the start position. - in range. | ||
2849 | * @param off the offset - start + offset in range. if offset is -1, it'll add everything to the end of the string if offset = 0 it'll return with doing nothing. | ||
2850 | * @param repch a replacement char to print instead of the original string, for example, * when working with passwords. | ||
2851 | */ | ||
2852 | static void | ||
2853 | _layout_text_append(Ctxt *c, Evas_Object_Textblock_Format *fmt, Evas_Object_Textblock_Node_Text *n, int start, int off, const char *repch) | ||
2854 | { | ||
2855 | const Eina_Unicode *str = EINA_UNICODE_EMPTY_STRING; | ||
2856 | const Eina_Unicode *tbase; | ||
2857 | Evas_Object_Textblock_Text_Item *ti; | ||
2858 | size_t cur_len = 0; | ||
2859 | Eina_Unicode urepch = 0; | ||
2860 | |||
2861 | /* prepare a working copy of the string, either filled by the repch or | ||
2862 | * filled with the true values */ | ||
2863 | if (n) | ||
2864 | { | ||
2865 | int len; | ||
2866 | int orig_off = off; | ||
2867 | |||
2868 | /* Figure out if we want to bail, work with an empty string, | ||
2869 | * or continue with a slice of the passed string */ | ||
2870 | len = eina_ustrbuf_length_get(n->unicode); | ||
2871 | if (off == 0) return; | ||
2872 | else if (off < 0) off = len - start; | ||
2873 | |||
2874 | if (start < 0) | ||
2875 | { | ||
2876 | start = 0; | ||
2877 | } | ||
2878 | else if ((start == 0) && (off == 0) && (orig_off == -1)) | ||
2879 | { | ||
2880 | /* Special case that means that we need to add an empty | ||
2881 | * item */ | ||
2882 | str = EINA_UNICODE_EMPTY_STRING; | ||
2883 | goto skip; | ||
2884 | } | ||
2885 | else if ((start >= len) || (start + off > len)) | ||
2886 | { | ||
2887 | return; | ||
2888 | } | ||
2889 | |||
2890 | /* If we work with a replacement char, create a string which is the same | ||
2891 | * but with replacement chars instead of regular chars. */ | ||
2892 | if ((fmt->password) && (repch) && (eina_ustrbuf_length_get(n->unicode))) | ||
2893 | { | ||
2894 | int i, ind; | ||
2895 | Eina_Unicode *ptr; | ||
2896 | |||
2897 | tbase = str = ptr = alloca((off + 1) * sizeof(Eina_Unicode)); | ||
2898 | ind = 0; | ||
2899 | urepch = eina_unicode_utf8_get_next(repch, &ind); | ||
2900 | for (i = 0 ; i < off; ptr++, i++) | ||
2901 | *ptr = urepch; | ||
2902 | *ptr = 0; | ||
2903 | } | ||
2904 | /* Use the string, just cut the relevant parts */ | ||
2905 | else | ||
2906 | { | ||
2907 | str = eina_ustrbuf_string_get(n->unicode) + start; | ||
2908 | } | ||
2909 | |||
2910 | cur_len = off; | ||
2911 | } | ||
2912 | |||
2913 | skip: | ||
2914 | tbase = str; | ||
2915 | |||
2916 | /* If there's no parent text node, only create an empty item */ | ||
2917 | if (!n) | ||
2918 | { | ||
2919 | ti = _layout_text_item_new(c, fmt); | ||
2920 | ti->parent.text_node = NULL; | ||
2921 | ti->parent.text_pos = 0; | ||
2922 | _layout_text_add_logical_item(c, ti, NULL); | ||
2923 | |||
2924 | return; | ||
2925 | } | ||
2926 | |||
2927 | while (cur_len > 0) | ||
2928 | { | ||
2929 | Evas_Font_Instance *script_fi = NULL; | ||
2930 | int script_len, tmp_cut; | ||
2931 | Evas_Script_Type script; | ||
2932 | |||
2933 | script_len = cur_len; | ||
2934 | |||
2935 | tmp_cut = evas_common_language_script_end_of_run_get(str, | ||
2936 | c->par->bidi_props, start + str - tbase, script_len); | ||
2937 | if (tmp_cut > 0) | ||
2938 | { | ||
2939 | script_len = tmp_cut; | ||
2940 | } | ||
2941 | cur_len -= script_len; | ||
2942 | |||
2943 | script = evas_common_language_script_type_get(str, script_len); | ||
2944 | |||
2945 | |||
2946 | while (script_len > 0) | ||
2947 | { | ||
2948 | Evas_Font_Instance *cur_fi = NULL; | ||
2949 | int run_len = script_len; | ||
2950 | ti = _layout_text_item_new(c, fmt); | ||
2951 | ti->parent.text_node = n; | ||
2952 | ti->parent.text_pos = start + str - tbase; | ||
2953 | |||
2954 | if (ti->parent.format->font.font) | ||
2955 | { | ||
2956 | run_len = c->ENFN->font_run_end_get(c->ENDT, | ||
2957 | ti->parent.format->font.font, &script_fi, &cur_fi, | ||
2958 | script, str, script_len); | ||
2959 | } | ||
2960 | |||
2961 | evas_common_text_props_bidi_set(&ti->text_props, | ||
2962 | c->par->bidi_props, ti->parent.text_pos); | ||
2963 | evas_common_text_props_script_set(&ti->text_props, script); | ||
2964 | |||
2965 | if (cur_fi) | ||
2966 | { | ||
2967 | c->ENFN->font_text_props_info_create(c->ENDT, | ||
2968 | cur_fi, str, &ti->text_props, c->par->bidi_props, | ||
2969 | ti->parent.text_pos, run_len); | ||
2970 | } | ||
2971 | str += run_len; | ||
2972 | script_len -= run_len; | ||
2973 | |||
2974 | _layout_text_add_logical_item(c, ti, NULL); | ||
2975 | } | ||
2976 | } | ||
2977 | } | ||
2978 | |||
2979 | /** | ||
2980 | * @internal | ||
2981 | * Add a format item from the format node n and the item item. | ||
2982 | * | ||
2983 | * @param c the current context- NOT NULL. | ||
2984 | * @param n the source format node - not null. | ||
2985 | * @param item the format text. | ||
2986 | * | ||
2987 | * @return the new format item. | ||
2988 | */ | ||
2989 | static Evas_Object_Textblock_Format_Item * | ||
2990 | _layout_format_item_add(Ctxt *c, Evas_Object_Textblock_Node_Format *n, const char *item, Evas_Object_Textblock_Format *fmt) | ||
2991 | { | ||
2992 | Evas_Object_Textblock_Format_Item *fi; | ||
2993 | |||
2994 | fi = calloc(1, sizeof(Evas_Object_Textblock_Format_Item)); | ||
2995 | fi->item = eina_stringshare_add(item); | ||
2996 | fi->parent.type = EVAS_TEXTBLOCK_ITEM_FORMAT; | ||
2997 | fi->parent.format = fmt; | ||
2998 | fi->parent.format->ref++; | ||
2999 | c->par->logical_items = eina_list_append(c->par->logical_items, fi); | ||
3000 | if (n) | ||
3001 | { | ||
3002 | fi->parent.text_node = n->text_node; | ||
3003 | /* FIXME: make it more efficient */ | ||
3004 | fi->parent.text_pos = _evas_textblock_node_format_pos_get(n); | ||
3005 | #ifdef BIDI_SUPPORT | ||
3006 | fi->bidi_dir = (evas_bidi_is_rtl_char( | ||
3007 | c->par->bidi_props, | ||
3008 | 0, | ||
3009 | fi->parent.text_pos)) ? | ||
3010 | EVAS_BIDI_DIRECTION_RTL : EVAS_BIDI_DIRECTION_LTR; | ||
3011 | #else | ||
3012 | fi->bidi_dir = EVAS_BIDI_DIRECTION_LTR; | ||
3013 | #endif | ||
3014 | } | ||
3015 | return fi; | ||
3016 | } | ||
3017 | |||
3018 | /** | ||
3019 | * @internal | ||
3020 | * Should be call after we finish filling a format. | ||
3021 | * FIXME: doc. | ||
3022 | */ | ||
3023 | static void | ||
3024 | _format_finalize(Evas_Object *obj, Evas_Object_Textblock_Format *fmt) | ||
3025 | { | ||
3026 | void *of; | ||
3027 | |||
3028 | of = fmt->font.font; | ||
3029 | |||
3030 | fmt->font.font = evas_font_load(obj->layer->evas, fmt->font.fdesc, | ||
3031 | fmt->font.source, (int)(((double) fmt->font.size) * obj->cur.scale)); | ||
3032 | if (of) evas_font_free(obj->layer->evas, of); | ||
3033 | } | ||
3034 | |||
3035 | /** | ||
3036 | * @internal | ||
3037 | * Returns true if the item is a tab | ||
3038 | * @def _IS_TAB(item) | ||
3039 | */ | ||
3040 | #define _IS_TAB(item) \ | ||
3041 | (!strcmp(item, "tab") || !strcmp(item, "\t") || !strcmp(item, "\\t")) | ||
3042 | /** | ||
3043 | * @internal | ||
3044 | * Returns true if the item is a line spearator, false otherwise | ||
3045 | * @def _IS_LINE_SEPARATOR(item) | ||
3046 | */ | ||
3047 | #define _IS_LINE_SEPARATOR(item) \ | ||
3048 | (!strcmp(item, "br") || !strcmp(item, "\n") || !strcmp(item, "\\n")) | ||
3049 | /** | ||
3050 | * @internal | ||
3051 | * Returns true if the item is a paragraph separator, false otherwise | ||
3052 | * @def _IS_PARAGRAPH_SEPARATOR(item) | ||
3053 | */ | ||
3054 | #define _IS_PARAGRAPH_SEPARATOR_SIMPLE(item) \ | ||
3055 | (!strcmp(item, "ps")) | ||
3056 | /** | ||
3057 | * @internal | ||
3058 | * Returns true if the item is a paragraph separator, false otherwise | ||
3059 | * takes legacy mode into account. | ||
3060 | * @def _IS_PARAGRAPH_SEPARATOR(item) | ||
3061 | */ | ||
3062 | #define _IS_PARAGRAPH_SEPARATOR(o, item) \ | ||
3063 | (_IS_PARAGRAPH_SEPARATOR_SIMPLE(item) || \ | ||
3064 | (o->legacy_newline && _IS_LINE_SEPARATOR(item))) /* Paragraph separator */ | ||
3065 | |||
3066 | /** | ||
3067 | * @internal | ||
3068 | * Handles a format by processing a format node. It returns the relevant format | ||
3069 | * through _fmt and updates the padding through style_pad_*. If needed, | ||
3070 | * it creates a format item. | ||
3071 | * | ||
3072 | * @param obj the evas object - NOT NULL. | ||
3073 | * @param c the current context- NOT NULL. | ||
3074 | * @param _fmt the format that holds the result. | ||
3075 | * @param n the source format node - not null. | ||
3076 | * @param style_pad_l the pad to update. | ||
3077 | * @param style_pad_r the pad to update. | ||
3078 | * @param style_pad_t the pad to update. | ||
3079 | * @param style_pad_b the pad to update. | ||
3080 | * @param create_item Create a new format item if true, only process otherwise. | ||
3081 | */ | ||
3082 | static void | ||
3083 | _layout_do_format(const Evas_Object *obj __UNUSED__, Ctxt *c, | ||
3084 | Evas_Object_Textblock_Format **_fmt, Evas_Object_Textblock_Node_Format *n, | ||
3085 | int *style_pad_l, int *style_pad_r, int *style_pad_t, int *style_pad_b, | ||
3086 | Eina_Bool create_item) | ||
3087 | { | ||
3088 | Evas_Object_Textblock_Format *fmt = *_fmt; | ||
3089 | /* FIXME: comment the algo */ | ||
3090 | |||
3091 | const char *s; | ||
3092 | const char *item; | ||
3093 | int handled = 0; | ||
3094 | |||
3095 | s = n->format; | ||
3096 | if (!strncmp(s, "item ", 5)) | ||
3097 | { | ||
3098 | // one of: | ||
3099 | // item size=20x10 href=name | ||
3100 | // item relsize=20x10 href=name | ||
3101 | // item abssize=20x10 href=name | ||
3102 | // | ||
3103 | // optional arguments: | ||
3104 | // vsize=full | ||
3105 | // vsize=ascent | ||
3106 | // | ||
3107 | // size == item size (modifies line size) - can be multiplied by | ||
3108 | // scale factor | ||
3109 | // relsize == relative size (height is current font height, width | ||
3110 | // modified accordingly keeping aspect) | ||
3111 | // abssize == absolute size (modifies line size) - never mulitplied by | ||
3112 | // scale factor | ||
3113 | // href == name of item - to be found and matched later and used for | ||
3114 | // positioning | ||
3115 | Evas_Object_Textblock_Format_Item *fi; | ||
3116 | int w = 1, h = 1; | ||
3117 | int vsize = 0, size = 0; | ||
3118 | char *p; | ||
3119 | |||
3120 | // don't care | ||
3121 | //href = strstr(s, " href="); | ||
3122 | p = strstr(s, " vsize="); | ||
3123 | if (p) | ||
3124 | { | ||
3125 | p += 7; | ||
3126 | if (!strncmp(p, "full", 4)) vsize = VSIZE_FULL; | ||
3127 | else if (!strncmp(p, "ascent", 6)) vsize = VSIZE_ASCENT; | ||
3128 | } | ||
3129 | p = strstr(s, " size="); | ||
3130 | if (p) | ||
3131 | { | ||
3132 | p += 6; | ||
3133 | if (sscanf(p, "%ix%i", &w, &h) == 2) | ||
3134 | { | ||
3135 | /* this is handled somewhere else because it depends | ||
3136 | * on the current scaling factor of the object which | ||
3137 | * may change and break because the results of this | ||
3138 | * function are cached */ | ||
3139 | size = SIZE; | ||
3140 | } | ||
3141 | } | ||
3142 | else | ||
3143 | { | ||
3144 | p = strstr(s, " absize="); | ||
3145 | if (p) | ||
3146 | { | ||
3147 | p += 8; | ||
3148 | if (sscanf(p, "%ix%i", &w, &h) == 2) | ||
3149 | { | ||
3150 | size = SIZE_ABS; | ||
3151 | } | ||
3152 | } | ||
3153 | else | ||
3154 | { | ||
3155 | p = strstr(s, " relsize="); | ||
3156 | if (p) | ||
3157 | { | ||
3158 | /* this is handled somewhere else because it depends | ||
3159 | * on the line it resides in, which is not defined | ||
3160 | * at this point and will change anyway, which will | ||
3161 | * break because the results of this function are | ||
3162 | * cached */ | ||
3163 | size = SIZE_REL; | ||
3164 | } | ||
3165 | } | ||
3166 | } | ||
3167 | |||
3168 | if (create_item) | ||
3169 | { | ||
3170 | fi = _layout_format_item_add(c, n, s, fmt); | ||
3171 | fi->vsize = vsize; | ||
3172 | fi->size = size; | ||
3173 | fi->formatme = 1; | ||
3174 | /* For formats items it's usually | ||
3175 | the same, we don't handle the | ||
3176 | special cases yet. */ | ||
3177 | fi->parent.w = fi->parent.adv = w; | ||
3178 | fi->parent.h = h; | ||
3179 | } | ||
3180 | /* Not sure if it's the best handling, but will do it for now. */ | ||
3181 | fmt = _layout_format_push(c, fmt, n); | ||
3182 | handled = 1; | ||
3183 | } | ||
3184 | |||
3185 | if (!handled) | ||
3186 | { | ||
3187 | Eina_Bool push_fmt = EINA_FALSE; | ||
3188 | if (n->opener && !n->own_closer) | ||
3189 | { | ||
3190 | fmt = _layout_format_push(c, fmt, n); | ||
3191 | push_fmt = EINA_TRUE; | ||
3192 | } | ||
3193 | else if (!n->opener) | ||
3194 | { | ||
3195 | fmt = _layout_format_pop(c, n->orig_format); | ||
3196 | } | ||
3197 | while ((item = _format_parse(&s))) | ||
3198 | { | ||
3199 | if (_format_is_param(item)) | ||
3200 | { | ||
3201 | /* Only handle it if it's a push format, otherwise, | ||
3202 | * don't let overwrite the format stack.. */ | ||
3203 | if (push_fmt) | ||
3204 | { | ||
3205 | _layout_format_value_handle(c, fmt, item); | ||
3206 | } | ||
3207 | } | ||
3208 | else if (create_item) | ||
3209 | { | ||
3210 | if ((_IS_PARAGRAPH_SEPARATOR(c->o, item)) || | ||
3211 | (_IS_LINE_SEPARATOR(item))) | ||
3212 | { | ||
3213 | Evas_Object_Textblock_Format_Item *fi; | ||
3214 | |||
3215 | fi = _layout_format_item_add(c, n, item, fmt); | ||
3216 | |||
3217 | fi->parent.w = fi->parent.adv = 0; | ||
3218 | } | ||
3219 | else if (_IS_TAB(item)) | ||
3220 | { | ||
3221 | Evas_Object_Textblock_Format_Item *fi; | ||
3222 | |||
3223 | fi = _layout_format_item_add(c, n, item, fmt); | ||
3224 | fi->parent.w = fi->parent.adv = fmt->tabstops; | ||
3225 | fi->formatme = 1; | ||
3226 | } | ||
3227 | } | ||
3228 | } | ||
3229 | _format_finalize(c->obj, fmt); | ||
3230 | } | ||
3231 | |||
3232 | { | ||
3233 | Evas_Coord pad_l, pad_r, pad_t, pad_b; | ||
3234 | pad_l = pad_r = pad_t = pad_b = 0; | ||
3235 | evas_text_style_pad_get(fmt->style, &pad_l, &pad_r, &pad_t, &pad_b); | ||
3236 | if (pad_l > *style_pad_l) *style_pad_l = pad_l; | ||
3237 | if (pad_r > *style_pad_r) *style_pad_r = pad_r; | ||
3238 | if (pad_t > *style_pad_t) *style_pad_t = pad_t; | ||
3239 | if (pad_b > *style_pad_b) *style_pad_b = pad_b; | ||
3240 | } | ||
3241 | |||
3242 | if (fmt->underline2) | ||
3243 | c->have_underline2 = 1; | ||
3244 | else if (fmt->underline || fmt->underline_dash) | ||
3245 | c->have_underline = 1; | ||
3246 | *_fmt = fmt; | ||
3247 | } | ||
3248 | |||
3249 | static void | ||
3250 | _layout_update_par(Ctxt *c) | ||
3251 | { | ||
3252 | Evas_Object_Textblock_Paragraph *last_par; | ||
3253 | last_par = (Evas_Object_Textblock_Paragraph *) | ||
3254 | EINA_INLIST_GET(c->par)->prev; | ||
3255 | if (last_par) | ||
3256 | { | ||
3257 | c->par->y = last_par->y + last_par->h; | ||
3258 | } | ||
3259 | else | ||
3260 | { | ||
3261 | c->par->y = 0; | ||
3262 | } | ||
3263 | } | ||
3264 | |||
3265 | /* -1 means no wrap */ | ||
3266 | static int | ||
3267 | _layout_get_charwrap(Ctxt *c, Evas_Object_Textblock_Format *fmt, | ||
3268 | const Evas_Object_Textblock_Item *it, size_t line_start, | ||
3269 | const char *breaks) | ||
3270 | { | ||
3271 | int wrap; | ||
3272 | size_t uwrap; | ||
3273 | size_t len = eina_ustrbuf_length_get(it->text_node->unicode); | ||
3274 | /* Currently not being used, because it doesn't contain relevant | ||
3275 | * information */ | ||
3276 | (void) breaks; | ||
3277 | |||
3278 | { | ||
3279 | if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) | ||
3280 | wrap = 0; | ||
3281 | else | ||
3282 | wrap = _layout_text_cutoff_get(c, fmt, _ITEM_TEXT(it)); | ||
3283 | |||
3284 | if (wrap < 0) | ||
3285 | return -1; | ||
3286 | uwrap = (size_t) wrap + it->text_pos; | ||
3287 | } | ||
3288 | |||
3289 | |||
3290 | if ((uwrap == line_start) && (it->type == EVAS_TEXTBLOCK_ITEM_TEXT)) | ||
3291 | { | ||
3292 | uwrap = it->text_pos + | ||
3293 | (size_t) evas_common_text_props_cluster_next( | ||
3294 | &_ITEM_TEXT(it)->text_props, wrap); | ||
3295 | } | ||
3296 | if ((uwrap <= line_start) || (uwrap > len)) | ||
3297 | return -1; | ||
3298 | |||
3299 | return uwrap; | ||
3300 | } | ||
3301 | |||
3302 | /* -1 means no wrap */ | ||
3303 | #ifdef HAVE_LINEBREAK | ||
3304 | |||
3305 | /* Allow break means: if we can break after the current char */ | ||
3306 | #define ALLOW_BREAK(i) \ | ||
3307 | (breaks[i] <= LINEBREAK_ALLOWBREAK) | ||
3308 | |||
3309 | #else | ||
3310 | |||
3311 | #define ALLOW_BREAK(i) \ | ||
3312 | (_is_white(str[i])) | ||
3313 | |||
3314 | #endif | ||
3315 | static int | ||
3316 | _layout_get_word_mixwrap_common(Ctxt *c, Evas_Object_Textblock_Format *fmt, | ||
3317 | const Evas_Object_Textblock_Item *it, Eina_Bool mixed_wrap, | ||
3318 | size_t line_start, const char *breaks) | ||
3319 | { | ||
3320 | Eina_Bool wrap_after = EINA_FALSE; | ||
3321 | size_t wrap; | ||
3322 | size_t orig_wrap; | ||
3323 | const Eina_Unicode *str = eina_ustrbuf_string_get( | ||
3324 | it->text_node->unicode); | ||
3325 | int item_start = it->text_pos; | ||
3326 | size_t len = eina_ustrbuf_length_get(it->text_node->unicode); | ||
3327 | #ifndef HAVE_LINEBREAK | ||
3328 | /* Not used without liblinebreak ATM. */ | ||
3329 | (void) breaks; | ||
3330 | #endif | ||
3331 | |||
3332 | { | ||
3333 | int swrap = -1; | ||
3334 | if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) | ||
3335 | swrap = 0; | ||
3336 | else | ||
3337 | swrap = _layout_text_cutoff_get(c, fmt, _ITEM_TEXT(it)); | ||
3338 | /* Avoiding too small textblocks to even contain one char. | ||
3339 | * FIXME: This can cause breaking inside ligatures. */ | ||
3340 | |||
3341 | if (swrap < 0) | ||
3342 | return -1; | ||
3343 | |||
3344 | orig_wrap = wrap = swrap + item_start; | ||
3345 | } | ||
3346 | |||
3347 | if (wrap > line_start) | ||
3348 | { | ||
3349 | /* The wrapping point found is the first char of the next string | ||
3350 | the rest works on the last char of the previous string. | ||
3351 | If it's a whitespace, then it's ok, and no need to go back | ||
3352 | because we'll remove it anyway. */ | ||
3353 | if (!_is_white(str[wrap])) | ||
3354 | MOVE_PREV_UNTIL(line_start, wrap); | ||
3355 | /* If there's a breakable point inside the text, scan backwards until | ||
3356 | * we find it */ | ||
3357 | while (wrap > line_start) | ||
3358 | { | ||
3359 | if (ALLOW_BREAK(wrap)) | ||
3360 | break; | ||
3361 | wrap--; | ||
3362 | } | ||
3363 | |||
3364 | if ((wrap > line_start) || | ||
3365 | ((wrap == line_start) && (ALLOW_BREAK(wrap)) && (wrap < len))) | ||
3366 | { | ||
3367 | /* We found a suitable wrapping point, break here. */ | ||
3368 | MOVE_NEXT_UNTIL(len, wrap); | ||
3369 | return wrap; | ||
3370 | } | ||
3371 | else | ||
3372 | { | ||
3373 | if (mixed_wrap) | ||
3374 | { | ||
3375 | return ((orig_wrap >= line_start) && (orig_wrap < len)) ? | ||
3376 | ((int) orig_wrap) : -1; | ||
3377 | } | ||
3378 | else | ||
3379 | { | ||
3380 | /* Scan forward to find the next wrapping point */ | ||
3381 | wrap = orig_wrap; | ||
3382 | wrap_after = EINA_TRUE; | ||
3383 | } | ||
3384 | } | ||
3385 | } | ||
3386 | |||
3387 | /* If we need to find the position after the cutting point */ | ||
3388 | if ((wrap == line_start) || (wrap_after)) | ||
3389 | { | ||
3390 | if (mixed_wrap) | ||
3391 | { | ||
3392 | return _layout_get_charwrap(c, fmt, it, | ||
3393 | line_start, breaks); | ||
3394 | } | ||
3395 | else | ||
3396 | { | ||
3397 | while (wrap < len) | ||
3398 | { | ||
3399 | if (ALLOW_BREAK(wrap)) | ||
3400 | break; | ||
3401 | wrap++; | ||
3402 | } | ||
3403 | |||
3404 | |||
3405 | if ((wrap < len) && (wrap > line_start)) | ||
3406 | { | ||
3407 | MOVE_NEXT_UNTIL(len, wrap); | ||
3408 | return wrap; | ||
3409 | } | ||
3410 | else | ||
3411 | { | ||
3412 | return -1; | ||
3413 | } | ||
3414 | } | ||
3415 | } | ||
3416 | |||
3417 | return -1; | ||
3418 | } | ||
3419 | |||
3420 | /* -1 means no wrap */ | ||
3421 | static int | ||
3422 | _layout_get_wordwrap(Ctxt *c, Evas_Object_Textblock_Format *fmt, | ||
3423 | const Evas_Object_Textblock_Item *it, size_t line_start, | ||
3424 | const char *breaks) | ||
3425 | { | ||
3426 | return _layout_get_word_mixwrap_common(c, fmt, it, EINA_FALSE, line_start, | ||
3427 | breaks); | ||
3428 | } | ||
3429 | |||
3430 | /* -1 means no wrap */ | ||
3431 | static int | ||
3432 | _layout_get_mixedwrap(Ctxt *c, Evas_Object_Textblock_Format *fmt, | ||
3433 | const Evas_Object_Textblock_Item *it, size_t line_start, | ||
3434 | const char *breaks) | ||
3435 | { | ||
3436 | return _layout_get_word_mixwrap_common(c, fmt, it, EINA_TRUE, line_start, | ||
3437 | breaks); | ||
3438 | } | ||
3439 | |||
3440 | /* Should be moved inside _layout_ellipsis_item_new once we fix the hack in | ||
3441 | * textblock render */ | ||
3442 | static const Eina_Unicode _ellip_str[2] = { 0x2026, '\0' }; | ||
3443 | |||
3444 | static Evas_Object_Textblock_Text_Item * | ||
3445 | _layout_ellipsis_item_new(Ctxt *c, const Evas_Object_Textblock_Item *cur_it) | ||
3446 | { | ||
3447 | Evas_Object_Textblock_Text_Item *ellip_ti; | ||
3448 | Evas_Script_Type script; | ||
3449 | Evas_Font_Instance *script_fi = NULL, *cur_fi; | ||
3450 | size_t len = 1; /* The length of _ellip_str */ | ||
3451 | |||
3452 | /* We can free it here, cause there's only one ellipsis item per tb. */ | ||
3453 | if (c->o->ellip_ti) _item_free(c->obj, NULL, _ITEM(c->o->ellip_ti)); | ||
3454 | c->o->ellip_ti = ellip_ti = _layout_text_item_new(c, | ||
3455 | eina_list_data_get(eina_list_last(c->format_stack))); | ||
3456 | ellip_ti->parent.text_node = cur_it->text_node; | ||
3457 | ellip_ti->parent.text_pos = cur_it->text_pos; | ||
3458 | script = evas_common_language_script_type_get(_ellip_str, len); | ||
3459 | |||
3460 | evas_common_text_props_bidi_set(&ellip_ti->text_props, | ||
3461 | c->par->bidi_props, ellip_ti->parent.text_pos); | ||
3462 | evas_common_text_props_script_set (&ellip_ti->text_props, script); | ||
3463 | |||
3464 | if (ellip_ti->parent.format->font.font) | ||
3465 | { | ||
3466 | /* It's only 1 char anyway, we don't need the run end. */ | ||
3467 | (void) c->ENFN->font_run_end_get(c->ENDT, | ||
3468 | ellip_ti->parent.format->font.font, &script_fi, &cur_fi, | ||
3469 | script, _ellip_str, len); | ||
3470 | |||
3471 | c->ENFN->font_text_props_info_create(c->ENDT, | ||
3472 | cur_fi, _ellip_str, &ellip_ti->text_props, | ||
3473 | c->par->bidi_props, ellip_ti->parent.text_pos, len); | ||
3474 | } | ||
3475 | |||
3476 | _text_item_update_sizes(c, ellip_ti); | ||
3477 | |||
3478 | if (cur_it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
3479 | { | ||
3480 | ellip_ti->parent.text_pos += _ITEM_TEXT(cur_it)->text_props.text_len | ||
3481 | - 1; | ||
3482 | } | ||
3483 | else | ||
3484 | { | ||
3485 | ellip_ti->parent.text_pos++; | ||
3486 | } | ||
3487 | |||
3488 | return ellip_ti; | ||
3489 | } | ||
3490 | |||
3491 | /** | ||
3492 | * @internel | ||
3493 | * Handle ellipsis | ||
3494 | */ | ||
3495 | static inline void | ||
3496 | _layout_handle_ellipsis(Ctxt *c, Evas_Object_Textblock_Item *it, Eina_List *i) | ||
3497 | { | ||
3498 | Evas_Object_Textblock_Text_Item *ellip_ti; | ||
3499 | Evas_Object_Textblock_Item *last_it; | ||
3500 | Evas_Coord save_cx; | ||
3501 | int wrap; | ||
3502 | ellip_ti = _layout_ellipsis_item_new(c, it); | ||
3503 | last_it = it; | ||
3504 | |||
3505 | save_cx = c->x; | ||
3506 | c->w -= ellip_ti->parent.w; | ||
3507 | |||
3508 | if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
3509 | { | ||
3510 | Evas_Object_Textblock_Text_Item *ti = _ITEM_TEXT(it); | ||
3511 | |||
3512 | wrap = _layout_text_cutoff_get(c, last_it->format, ti); | ||
3513 | if ((wrap > 0) && !IS_AT_END(ti, (size_t) wrap)) | ||
3514 | { | ||
3515 | _layout_item_text_split_strip_white(c, ti, i, wrap); | ||
3516 | } | ||
3517 | else if ((wrap == 0) && (c->ln->items)) | ||
3518 | { | ||
3519 | last_it = _ITEM(EINA_INLIST_GET(c->ln->items)->last); | ||
3520 | } | ||
3521 | } | ||
3522 | else if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) | ||
3523 | { | ||
3524 | /* We don't want to add this format item. */ | ||
3525 | last_it = NULL; | ||
3526 | } | ||
3527 | |||
3528 | c->x = save_cx; | ||
3529 | c->w += ellip_ti->parent.w; | ||
3530 | /* If we should add this item, do it */ | ||
3531 | if (last_it == it) | ||
3532 | { | ||
3533 | c->ln->items = (Evas_Object_Textblock_Item *) | ||
3534 | eina_inlist_append(EINA_INLIST_GET(c->ln->items), | ||
3535 | EINA_INLIST_GET(it)); | ||
3536 | if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) | ||
3537 | { | ||
3538 | Evas_Object_Textblock_Format_Item *fi; | ||
3539 | fi = _ITEM_FORMAT(it); | ||
3540 | fi->y = c->y; | ||
3541 | } | ||
3542 | } | ||
3543 | c->ln->items = (Evas_Object_Textblock_Item *) | ||
3544 | eina_inlist_append(EINA_INLIST_GET(c->ln->items), | ||
3545 | EINA_INLIST_GET(_ITEM(ellip_ti))); | ||
3546 | _layout_line_finalize(c, ellip_ti->parent.format); | ||
3547 | } | ||
3548 | |||
3549 | #ifdef BIDI_SUPPORT | ||
3550 | static void | ||
3551 | _layout_paragraph_reorder_lines(Evas_Object_Textblock_Paragraph *par) | ||
3552 | { | ||
3553 | Evas_Object_Textblock_Line *ln; | ||
3554 | |||
3555 | EINA_INLIST_FOREACH(EINA_INLIST_GET(par->lines), ln) | ||
3556 | { | ||
3557 | _layout_line_reorder(ln); | ||
3558 | } | ||
3559 | } | ||
3560 | #endif | ||
3561 | |||
3562 | static void | ||
3563 | _layout_paragraph_render(Evas_Object_Textblock *o, | ||
3564 | Evas_Object_Textblock_Paragraph *par) | ||
3565 | { | ||
3566 | if (par->rendered) | ||
3567 | return; | ||
3568 | par->rendered = EINA_TRUE; | ||
3569 | |||
3570 | #ifdef BIDI_SUPPORT | ||
3571 | if (par->is_bidi) | ||
3572 | { | ||
3573 | _layout_update_bidi_props(o, par); | ||
3574 | _layout_paragraph_reorder_lines(par); | ||
3575 | /* Clear the bidi props because we don't need them anymore. */ | ||
3576 | if (par->bidi_props) | ||
3577 | { | ||
3578 | evas_bidi_paragraph_props_unref(par->bidi_props); | ||
3579 | par->bidi_props = NULL; | ||
3580 | } | ||
3581 | } | ||
3582 | #else | ||
3583 | (void) o; | ||
3584 | #endif | ||
3585 | } | ||
3586 | |||
3587 | /* 0 means go ahead, 1 means break without an error, 2 means | ||
3588 | * break with an error, should probably clean this a bit (enum/macro) | ||
3589 | * FIXME ^ */ | ||
3590 | static int | ||
3591 | _layout_par(Ctxt *c) | ||
3592 | { | ||
3593 | Evas_Object_Textblock_Item *it; | ||
3594 | Eina_List *i; | ||
3595 | int ret = 0; | ||
3596 | int wrap = -1; | ||
3597 | char *line_breaks = NULL; | ||
3598 | |||
3599 | if (!c->par->logical_items) | ||
3600 | return 2; | ||
3601 | |||
3602 | /* We want to show it. */ | ||
3603 | c->par->visible = 1; | ||
3604 | |||
3605 | /* Check if we need to skip this paragraph because it's already layouted | ||
3606 | * correctly, and mark handled nodes as dirty. */ | ||
3607 | c->par->line_no = c->line_no; | ||
3608 | |||
3609 | if (c->par->text_node) | ||
3610 | { | ||
3611 | /* Skip this paragraph if width is the same, there is no ellipsis | ||
3612 | * and we aren't just calculating. */ | ||
3613 | if (!c->par->text_node->is_new && !c->par->text_node->dirty && | ||
3614 | !c->width_changed && c->par->lines && | ||
3615 | !c->o->have_ellipsis) | ||
3616 | { | ||
3617 | Evas_Object_Textblock_Line *ln; | ||
3618 | /* Update c->line_no */ | ||
3619 | ln = (Evas_Object_Textblock_Line *) | ||
3620 | EINA_INLIST_GET(c->par->lines)->last; | ||
3621 | if (ln) | ||
3622 | c->line_no = c->par->line_no + ln->line_no + 1; | ||
3623 | return 0; | ||
3624 | } | ||
3625 | c->par->text_node->dirty = EINA_FALSE; | ||
3626 | c->par->text_node->is_new = EINA_FALSE; | ||
3627 | c->par->rendered = EINA_FALSE; | ||
3628 | |||
3629 | /* Merge back and clear the paragraph */ | ||
3630 | { | ||
3631 | Eina_List *itr, *itr_next; | ||
3632 | Evas_Object_Textblock_Item *ititr, *prev_it = NULL; | ||
3633 | _paragraph_clear(c->obj, c->par); | ||
3634 | EINA_LIST_FOREACH_SAFE(c->par->logical_items, itr, itr_next, ititr) | ||
3635 | { | ||
3636 | if (ititr->merge && prev_it && | ||
3637 | (prev_it->type == EVAS_TEXTBLOCK_ITEM_TEXT) && | ||
3638 | (ititr->type == EVAS_TEXTBLOCK_ITEM_TEXT)) | ||
3639 | { | ||
3640 | _layout_item_merge_and_free(c, _ITEM_TEXT(prev_it), | ||
3641 | _ITEM_TEXT(ititr)); | ||
3642 | c->par->logical_items = | ||
3643 | eina_list_remove_list(c->par->logical_items, itr); | ||
3644 | } | ||
3645 | else | ||
3646 | { | ||
3647 | prev_it = ititr; | ||
3648 | } | ||
3649 | } | ||
3650 | } | ||
3651 | } | ||
3652 | |||
3653 | c->y = c->par->y; | ||
3654 | |||
3655 | it = _ITEM(eina_list_data_get(c->par->logical_items)); | ||
3656 | _layout_line_new(c, it->format); | ||
3657 | /* We walk on our own because we want to be able to add items from | ||
3658 | * inside the list and then walk them on the next iteration. */ | ||
3659 | for (i = c->par->logical_items ; i ; ) | ||
3660 | { | ||
3661 | int adv_line = 0; | ||
3662 | int redo_item = 0; | ||
3663 | it = _ITEM(eina_list_data_get(i)); | ||
3664 | /* Skip visually deleted items */ | ||
3665 | if (it->visually_deleted) | ||
3666 | { | ||
3667 | i = eina_list_next(i); | ||
3668 | continue; | ||
3669 | } | ||
3670 | |||
3671 | if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
3672 | { | ||
3673 | Evas_Object_Textblock_Text_Item *ti = _ITEM_TEXT(it); | ||
3674 | _layout_format_ascent_descent_adjust(c->obj, &c->maxascent, | ||
3675 | &c->maxdescent, ti->parent.format); | ||
3676 | } | ||
3677 | else | ||
3678 | { | ||
3679 | Evas_Object_Textblock_Format_Item *fi = _ITEM_FORMAT(it); | ||
3680 | if (fi->formatme) | ||
3681 | { | ||
3682 | /* If there are no text items yet, calc ascent/descent | ||
3683 | * according to the current format. */ | ||
3684 | if (c->maxascent + c->maxdescent == 0) | ||
3685 | _layout_format_ascent_descent_adjust(c->obj, &c->maxascent, | ||
3686 | &c->maxdescent, it->format); | ||
3687 | |||
3688 | _layout_calculate_format_item_size(c->obj, fi, &c->maxascent, | ||
3689 | &c->maxdescent, &fi->y, &fi->parent.w, &fi->parent.h); | ||
3690 | fi->parent.adv = fi->parent.w; | ||
3691 | } | ||
3692 | } | ||
3693 | |||
3694 | |||
3695 | /* Check if we need to wrap, i.e the text is bigger than the width, | ||
3696 | or we already found a wrap point. */ | ||
3697 | if ((c->w >= 0) && | ||
3698 | (((c->x + it->adv) > | ||
3699 | (c->w - c->o->style_pad.l - c->o->style_pad.r - | ||
3700 | c->marginl - c->marginr)) || (wrap > 0))) | ||
3701 | { | ||
3702 | /* Handle ellipsis here. If we don't have more width left | ||
3703 | * and no height left, or no more width left and no wrapping. */ | ||
3704 | if ((it->format->ellipsis == 1.0) && (c->h >= 0) && | ||
3705 | ((2 * it->h + c->y > | ||
3706 | c->h - c->o->style_pad.t - c->o->style_pad.b) || | ||
3707 | (!it->format->wrap_word && !it->format->wrap_char && | ||
3708 | !it->format->wrap_mixed))) | ||
3709 | { | ||
3710 | _layout_handle_ellipsis(c, it, i); | ||
3711 | ret = 1; | ||
3712 | goto end; | ||
3713 | } | ||
3714 | /* If we want to wrap and it's worth checking for wrapping | ||
3715 | * (i.e there's actually text). */ | ||
3716 | else if ((it->format->wrap_word || it->format->wrap_char || | ||
3717 | it->format->wrap_mixed) && it->text_node) | ||
3718 | { | ||
3719 | size_t line_start; | ||
3720 | size_t it_len; | ||
3721 | |||
3722 | it_len = (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) ? | ||
3723 | 1 : _ITEM_TEXT(it)->text_props.text_len; | ||
3724 | |||
3725 | |||
3726 | #ifdef HAVE_LINEBREAK | ||
3727 | /* If we haven't calculated the linebreaks yet, | ||
3728 | * do */ | ||
3729 | if (!line_breaks) | ||
3730 | { | ||
3731 | /* Only relevant in those cases */ | ||
3732 | if (it->format->wrap_word || it->format->wrap_mixed) | ||
3733 | { | ||
3734 | const char *lang; | ||
3735 | lang = (it->format->font.fdesc) ? | ||
3736 | it->format->font.fdesc->lang : ""; | ||
3737 | size_t len = | ||
3738 | eina_ustrbuf_length_get( | ||
3739 | it->text_node->unicode); | ||
3740 | line_breaks = malloc(len); | ||
3741 | set_linebreaks_utf32((const utf32_t *) | ||
3742 | eina_ustrbuf_string_get( | ||
3743 | it->text_node->unicode), | ||
3744 | len, lang, line_breaks); | ||
3745 | } | ||
3746 | } | ||
3747 | #endif | ||
3748 | if (c->ln->items) | ||
3749 | line_start = c->ln->items->text_pos; | ||
3750 | else | ||
3751 | line_start = it->text_pos; | ||
3752 | |||
3753 | adv_line = 1; | ||
3754 | /* If we don't already have a wrap point from before */ | ||
3755 | if (wrap < 0) | ||
3756 | { | ||
3757 | if (it->format->wrap_word) | ||
3758 | wrap = _layout_get_wordwrap(c, it->format, it, | ||
3759 | line_start, line_breaks); | ||
3760 | else if (it->format->wrap_char) | ||
3761 | wrap = _layout_get_charwrap(c, it->format, it, | ||
3762 | line_start, line_breaks); | ||
3763 | else if (it->format->wrap_mixed) | ||
3764 | wrap = _layout_get_mixedwrap(c, it->format, it, | ||
3765 | line_start, line_breaks); | ||
3766 | else | ||
3767 | wrap = -1; | ||
3768 | } | ||
3769 | |||
3770 | /* If it's before the item, rollback and apply. | ||
3771 | if it's in the item, cut. | ||
3772 | If it's after the item, delay the cut */ | ||
3773 | if (wrap > 0) | ||
3774 | { | ||
3775 | size_t uwrap = (size_t) wrap; | ||
3776 | if (uwrap < it->text_pos) | ||
3777 | { | ||
3778 | /* Rollback latest additions, and cut that | ||
3779 | item */ | ||
3780 | i = eina_list_prev(i); | ||
3781 | it = eina_list_data_get(i); | ||
3782 | while (uwrap < it->text_pos) | ||
3783 | { | ||
3784 | c->ln->items = _ITEM( | ||
3785 | eina_inlist_remove( | ||
3786 | EINA_INLIST_GET(c->ln->items), | ||
3787 | EINA_INLIST_GET(it))); | ||
3788 | i = eina_list_prev(i); | ||
3789 | it = eina_list_data_get(i); | ||
3790 | } | ||
3791 | c->x = it->x; | ||
3792 | c->ln->items = _ITEM( | ||
3793 | eina_inlist_remove( | ||
3794 | EINA_INLIST_GET(c->ln->items), | ||
3795 | EINA_INLIST_GET(it))); | ||
3796 | continue; | ||
3797 | } | ||
3798 | /* If it points to the end, it means the previous | ||
3799 | * char is a whitespace we should remove, so this | ||
3800 | * is a wanted cutting point. */ | ||
3801 | else if (uwrap > it->text_pos + it_len) | ||
3802 | { | ||
3803 | /* FIXME: Should redo the ellipsis handling. | ||
3804 | * If we can do ellipsis, just cut here. */ | ||
3805 | if (it->format->ellipsis == 1.0) | ||
3806 | { | ||
3807 | _layout_handle_ellipsis(c, it, i); | ||
3808 | ret = 1; | ||
3809 | goto end; | ||
3810 | } | ||
3811 | else | ||
3812 | { | ||
3813 | /* Delay the cut in a smart way i.e use the | ||
3814 | item_pos as the line_start, because | ||
3815 | there's already no cut before*/ | ||
3816 | wrap = -1; | ||
3817 | } | ||
3818 | } | ||
3819 | else | ||
3820 | wrap -= it->text_pos; /* Cut here */ | ||
3821 | } | ||
3822 | |||
3823 | if (wrap > 0) | ||
3824 | { | ||
3825 | if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
3826 | { | ||
3827 | _layout_item_text_split_strip_white(c, | ||
3828 | _ITEM_TEXT(it), i, wrap); | ||
3829 | } | ||
3830 | } | ||
3831 | else if (wrap == 0) | ||
3832 | { | ||
3833 | /* Should wrap before the item */ | ||
3834 | adv_line = 0; | ||
3835 | redo_item = 1; | ||
3836 | _layout_line_advance(c, it->format); | ||
3837 | } | ||
3838 | /* Reset wrap */ | ||
3839 | wrap = -1; | ||
3840 | } | ||
3841 | } | ||
3842 | |||
3843 | if (!redo_item && !it->visually_deleted) | ||
3844 | { | ||
3845 | c->ln->items = (Evas_Object_Textblock_Item *) | ||
3846 | eina_inlist_append(EINA_INLIST_GET(c->ln->items), | ||
3847 | EINA_INLIST_GET(it)); | ||
3848 | if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) | ||
3849 | { | ||
3850 | Evas_Object_Textblock_Format_Item *fi; | ||
3851 | fi = _ITEM_FORMAT(it); | ||
3852 | fi->y = c->y; | ||
3853 | /* If it's a newline, and we are not in newline compat | ||
3854 | * mode, or we are in newline compat mode, and this is | ||
3855 | * not used as a paragraph separator, advance */ | ||
3856 | if (fi->item && _IS_LINE_SEPARATOR(fi->item) && | ||
3857 | (!c->o->legacy_newline || | ||
3858 | eina_list_next(i))) | ||
3859 | { | ||
3860 | adv_line = 1; | ||
3861 | } | ||
3862 | } | ||
3863 | c->x += it->adv; | ||
3864 | i = eina_list_next(i); | ||
3865 | } | ||
3866 | if (adv_line) | ||
3867 | { | ||
3868 | /* Each line is according to the first item in it, and here | ||
3869 | * i is already the next item (or the current if we redo it) */ | ||
3870 | if (i) | ||
3871 | { | ||
3872 | it = _ITEM(eina_list_data_get(i)); | ||
3873 | } | ||
3874 | _layout_line_advance(c, it->format); | ||
3875 | } | ||
3876 | } | ||
3877 | if (c->ln->items) | ||
3878 | { | ||
3879 | /* Here 'it' is the last format used */ | ||
3880 | _layout_line_finalize(c, it->format); | ||
3881 | } | ||
3882 | |||
3883 | end: | ||
3884 | #ifdef HAVE_LINEBREAK | ||
3885 | if (line_breaks) | ||
3886 | free(line_breaks); | ||
3887 | #endif | ||
3888 | |||
3889 | return ret; | ||
3890 | } | ||
3891 | |||
3892 | /** | ||
3893 | * @internal | ||
3894 | * Invalidate text nodes according to format changes | ||
3895 | * This goes through all the new format changes and marks the text nodes | ||
3896 | * that should be invalidated because of format changes. | ||
3897 | * | ||
3898 | * @param c the working context. | ||
3899 | */ | ||
3900 | static inline void | ||
3901 | _format_changes_invalidate_text_nodes(Ctxt *c) | ||
3902 | { | ||
3903 | Evas_Object_Textblock_Node_Format *fnode = c->o->format_nodes; | ||
3904 | Evas_Object_Textblock_Node_Text *start_n = NULL; | ||
3905 | Eina_List *fstack = NULL; | ||
3906 | int balance = 0; | ||
3907 | while (fnode) | ||
3908 | { | ||
3909 | if (fnode->is_new) | ||
3910 | { | ||
3911 | const char *fstr = fnode->orig_format; | ||
3912 | /* balance < 0 means we gave up and everything should be | ||
3913 | * invalidated */ | ||
3914 | if (fnode->opener && !fnode->own_closer) | ||
3915 | { | ||
3916 | balance++; | ||
3917 | if (!fstack) | ||
3918 | start_n = fnode->text_node; | ||
3919 | fstack = eina_list_prepend(fstack, fnode); | ||
3920 | } | ||
3921 | else if (!fnode->opener) | ||
3922 | { | ||
3923 | size_t fstr_len; | ||
3924 | fstr_len = strlen(fstr); | ||
3925 | /* Generic popper, just pop */ | ||
3926 | if (((fstr[0] == ' ') && !fstr[1]) || !fstr[0]) | ||
3927 | { | ||
3928 | fstack = eina_list_remove_list(fstack, fstack); | ||
3929 | balance--; | ||
3930 | } | ||
3931 | /* Find the matching format and pop it, if the matching format | ||
3932 | * is out format, i.e the last one, pop and break. */ | ||
3933 | else | ||
3934 | { | ||
3935 | Eina_List *i; | ||
3936 | Evas_Object_Textblock_Node_Format *fnode2; | ||
3937 | EINA_LIST_FOREACH(fstack, i, fnode2) | ||
3938 | { | ||
3939 | if (_FORMAT_IS_CLOSER_OF( | ||
3940 | fnode2->orig_format, fstr, fstr_len)) | ||
3941 | { | ||
3942 | fstack = eina_list_remove_list(fstack, i); | ||
3943 | break; | ||
3944 | } | ||
3945 | } | ||
3946 | balance--; | ||
3947 | } | ||
3948 | |||
3949 | if (!fstack) | ||
3950 | { | ||
3951 | Evas_Object_Textblock_Node_Text *f_tnode = | ||
3952 | fnode->text_node; | ||
3953 | while (start_n) | ||
3954 | { | ||
3955 | start_n->dirty = EINA_TRUE; | ||
3956 | if (start_n == f_tnode) | ||
3957 | break; | ||
3958 | start_n = | ||
3959 | _NODE_TEXT(EINA_INLIST_GET(start_n)->next); | ||
3960 | } | ||
3961 | start_n = NULL; | ||
3962 | } | ||
3963 | } | ||
3964 | else if (!fnode->visible) | ||
3965 | balance = -1; | ||
3966 | |||
3967 | if (balance < 0) | ||
3968 | { | ||
3969 | /* if we don't already have a starting point, use the | ||
3970 | * current paragraph. */ | ||
3971 | if (!start_n) | ||
3972 | start_n = fnode->text_node; | ||
3973 | break; | ||
3974 | } | ||
3975 | } | ||
3976 | fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); | ||
3977 | } | ||
3978 | |||
3979 | if (balance != 0) | ||
3980 | { | ||
3981 | while (start_n) | ||
3982 | { | ||
3983 | start_n->dirty = EINA_TRUE; | ||
3984 | start_n = _NODE_TEXT(EINA_INLIST_GET(start_n)->next); | ||
3985 | } | ||
3986 | } | ||
3987 | } | ||
3988 | |||
3989 | |||
3990 | /** FIXME: Document */ | ||
3991 | static void | ||
3992 | _layout_pre(Ctxt *c, int *style_pad_l, int *style_pad_r, int *style_pad_t, | ||
3993 | int *style_pad_b) | ||
3994 | { | ||
3995 | Evas_Object *obj = c->obj; | ||
3996 | Evas_Object_Textblock *o = c->o; | ||
3997 | /* Mark text nodes as dirty if format have changed. */ | ||
3998 | if (c->o->format_changed) | ||
3999 | { | ||
4000 | _format_changes_invalidate_text_nodes(c); | ||
4001 | } | ||
4002 | |||
4003 | if (o->content_changed) | ||
4004 | { | ||
4005 | Evas_Object_Textblock_Node_Text *n; | ||
4006 | c->o->have_ellipsis = 0; | ||
4007 | c->par = c->paragraphs = o->paragraphs; | ||
4008 | /* Go through all the text nodes to create the logical layout */ | ||
4009 | EINA_INLIST_FOREACH(c->o->text_nodes, n) | ||
4010 | { | ||
4011 | Evas_Object_Textblock_Node_Format *fnode; | ||
4012 | size_t start; | ||
4013 | int off; | ||
4014 | |||
4015 | /* If it's not a new paragraph, either update it or skip it. | ||
4016 | * Remove all the paragraphs that were deleted */ | ||
4017 | if (!n->is_new) | ||
4018 | { | ||
4019 | /* Remove all the deleted paragraphs at this point */ | ||
4020 | while (c->par->text_node != n) | ||
4021 | { | ||
4022 | Evas_Object_Textblock_Paragraph *tmp_par = | ||
4023 | (Evas_Object_Textblock_Paragraph *) | ||
4024 | EINA_INLIST_GET(c->par)->next; | ||
4025 | |||
4026 | c->paragraphs = (Evas_Object_Textblock_Paragraph *) | ||
4027 | eina_inlist_remove(EINA_INLIST_GET(c->paragraphs), | ||
4028 | EINA_INLIST_GET(c->par)); | ||
4029 | _paragraph_free(obj, c->par); | ||
4030 | |||
4031 | c->par = tmp_par; | ||
4032 | } | ||
4033 | |||
4034 | /* If it's dirty, remove and recreate, if it's clean, | ||
4035 | * skip to the next. */ | ||
4036 | if (n->dirty) | ||
4037 | { | ||
4038 | Evas_Object_Textblock_Paragraph *prev_par = c->par; | ||
4039 | |||
4040 | _layout_paragraph_new(c, n, EINA_TRUE); | ||
4041 | |||
4042 | c->paragraphs = (Evas_Object_Textblock_Paragraph *) | ||
4043 | eina_inlist_remove(EINA_INLIST_GET(c->paragraphs), | ||
4044 | EINA_INLIST_GET(prev_par)); | ||
4045 | _paragraph_free(obj, prev_par); | ||
4046 | } | ||
4047 | else | ||
4048 | { | ||
4049 | c->par = (Evas_Object_Textblock_Paragraph *) | ||
4050 | EINA_INLIST_GET(c->par)->next; | ||
4051 | |||
4052 | /* Update the format stack according to the node's | ||
4053 | * formats */ | ||
4054 | fnode = n->format_node; | ||
4055 | while (fnode && (fnode->text_node == n)) | ||
4056 | { | ||
4057 | /* Only do this if this actually changes format */ | ||
4058 | if (fnode->format_change) | ||
4059 | _layout_do_format(obj, c, &c->fmt, fnode, | ||
4060 | style_pad_l, style_pad_r, | ||
4061 | style_pad_t, style_pad_b, EINA_FALSE); | ||
4062 | fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); | ||
4063 | } | ||
4064 | continue; | ||
4065 | } | ||
4066 | } | ||
4067 | else | ||
4068 | { | ||
4069 | /* If it's a new paragraph, just add it. */ | ||
4070 | _layout_paragraph_new(c, n, EINA_FALSE); | ||
4071 | } | ||
4072 | |||
4073 | #ifdef BIDI_SUPPORT | ||
4074 | _layout_update_bidi_props(c->o, c->par); | ||
4075 | #endif | ||
4076 | |||
4077 | /* For each text node to thorugh all of it's format nodes | ||
4078 | * append text from the start to the offset of the next format | ||
4079 | * using the last format got. if needed it also creates format | ||
4080 | * items this is the core algorithm of the layout mechanism. | ||
4081 | * Skip the unicode replacement chars when there are because | ||
4082 | * we don't want to print them. */ | ||
4083 | fnode = n->format_node; | ||
4084 | start = off = 0; | ||
4085 | while (fnode && (fnode->text_node == n)) | ||
4086 | { | ||
4087 | off += fnode->offset; | ||
4088 | /* No need to skip on the first run, or a non-visible one */ | ||
4089 | _layout_text_append(c, c->fmt, n, start, off, o->repch); | ||
4090 | _layout_do_format(obj, c, &c->fmt, fnode, style_pad_l, | ||
4091 | style_pad_r, style_pad_t, style_pad_b, EINA_TRUE); | ||
4092 | if ((c->have_underline2) || (c->have_underline)) | ||
4093 | { | ||
4094 | if (*style_pad_b < c->underline_extend) | ||
4095 | *style_pad_b = c->underline_extend; | ||
4096 | c->have_underline = 0; | ||
4097 | c->have_underline2 = 0; | ||
4098 | c->underline_extend = 0; | ||
4099 | } | ||
4100 | start += off; | ||
4101 | if (fnode->visible) | ||
4102 | { | ||
4103 | off = -1; | ||
4104 | start++; | ||
4105 | } | ||
4106 | else | ||
4107 | { | ||
4108 | off = 0; | ||
4109 | } | ||
4110 | fnode->is_new = EINA_FALSE; | ||
4111 | fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); | ||
4112 | } | ||
4113 | _layout_text_append(c, c->fmt, n, start, -1, o->repch); | ||
4114 | #ifdef BIDI_SUPPORT | ||
4115 | /* Clear the bidi props because we don't need them anymore. */ | ||
4116 | if (c->par->bidi_props) | ||
4117 | { | ||
4118 | evas_bidi_paragraph_props_unref(c->par->bidi_props); | ||
4119 | c->par->bidi_props = NULL; | ||
4120 | } | ||
4121 | #endif | ||
4122 | c->par = (Evas_Object_Textblock_Paragraph *) | ||
4123 | EINA_INLIST_GET(c->par)->next; | ||
4124 | } | ||
4125 | |||
4126 | /* Delete the rest of the layout paragraphs */ | ||
4127 | while (c->par) | ||
4128 | { | ||
4129 | Evas_Object_Textblock_Paragraph *tmp_par = | ||
4130 | (Evas_Object_Textblock_Paragraph *) | ||
4131 | EINA_INLIST_GET(c->par)->next; | ||
4132 | |||
4133 | c->paragraphs = (Evas_Object_Textblock_Paragraph *) | ||
4134 | eina_inlist_remove(EINA_INLIST_GET(c->paragraphs), | ||
4135 | EINA_INLIST_GET(c->par)); | ||
4136 | _paragraph_free(obj, c->par); | ||
4137 | |||
4138 | c->par = tmp_par; | ||
4139 | } | ||
4140 | o->paragraphs = c->paragraphs; | ||
4141 | c->par = NULL; | ||
4142 | } | ||
4143 | |||
4144 | } | ||
4145 | |||
4146 | /** | ||
4147 | * @internal | ||
4148 | * Create the layout from the nodes. | ||
4149 | * | ||
4150 | * @param obj the evas object - NOT NULL. | ||
4151 | * @param calc_only true if should only calc sizes false if should also create the layout.. It assumes native size is being calculated, doesn't support formatted size atm. | ||
4152 | * @param w the object's w, -1 means no wrapping (i.e infinite size) | ||
4153 | * @param h the object's h, -1 means inifinte size. | ||
4154 | * @param w_ret the object's calculated w. | ||
4155 | * @param h_ret the object's calculated h. | ||
4156 | */ | ||
4157 | static void | ||
4158 | _layout(const Evas_Object *obj, int w, int h, int *w_ret, int *h_ret) | ||
4159 | { | ||
4160 | Evas_Object_Textblock *o; | ||
4161 | Ctxt ctxt, *c; | ||
4162 | int style_pad_l = 0, style_pad_r = 0, style_pad_t = 0, style_pad_b = 0; | ||
4163 | |||
4164 | /* setup context */ | ||
4165 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
4166 | c = &ctxt; | ||
4167 | c->obj = (Evas_Object *)obj; | ||
4168 | c->o = o; | ||
4169 | c->paragraphs = c->par = NULL; | ||
4170 | c->format_stack = NULL; | ||
4171 | c->fmt = NULL; | ||
4172 | c->x = c->y = 0; | ||
4173 | c->w = w; | ||
4174 | c->h = h; | ||
4175 | c->wmax = c->hmax = 0; | ||
4176 | c->maxascent = c->maxdescent = 0; | ||
4177 | c->marginl = c->marginr = 0; | ||
4178 | c->have_underline = 0; | ||
4179 | c->have_underline2 = 0; | ||
4180 | c->underline_extend = 0; | ||
4181 | c->line_no = 0; | ||
4182 | c->align = 0.0; | ||
4183 | c->align_auto = EINA_TRUE; | ||
4184 | c->ln = NULL; | ||
4185 | c->width_changed = (obj->cur.geometry.w != o->last_w); | ||
4186 | |||
4187 | /* Start of logical layout creation */ | ||
4188 | /* setup default base style */ | ||
4189 | { | ||
4190 | Eina_Bool finalize = EINA_FALSE; | ||
4191 | if ((c->o->style) && (c->o->style->default_tag)) | ||
4192 | { | ||
4193 | c->fmt = _layout_format_push(c, NULL, NULL); | ||
4194 | _format_fill(c->obj, c->fmt, c->o->style->default_tag); | ||
4195 | finalize = EINA_TRUE; | ||
4196 | } | ||
4197 | |||
4198 | if ((c->o->style_user) && (c->o->style_user->default_tag)) | ||
4199 | { | ||
4200 | if (!c->fmt) | ||
4201 | { | ||
4202 | c->fmt = _layout_format_push(c, NULL, NULL); | ||
4203 | } | ||
4204 | _format_fill(c->obj, c->fmt, c->o->style_user->default_tag); | ||
4205 | finalize = EINA_TRUE; | ||
4206 | } | ||
4207 | |||
4208 | if (finalize) | ||
4209 | _format_finalize(c->obj, c->fmt); | ||
4210 | } | ||
4211 | if (!c->fmt) | ||
4212 | { | ||
4213 | if (w_ret) *w_ret = 0; | ||
4214 | if (h_ret) *h_ret = 0; | ||
4215 | return; | ||
4216 | } | ||
4217 | |||
4218 | _layout_pre(c, &style_pad_l, &style_pad_r, &style_pad_t, &style_pad_b); | ||
4219 | c->paragraphs = o->paragraphs; | ||
4220 | |||
4221 | /* If there are no paragraphs, create the minimum needed, | ||
4222 | * if the last paragraph has no lines/text, create that as well */ | ||
4223 | if (!c->paragraphs) | ||
4224 | { | ||
4225 | _layout_paragraph_new(c, NULL, EINA_TRUE); | ||
4226 | o->paragraphs = c->paragraphs; | ||
4227 | } | ||
4228 | c->par = (Evas_Object_Textblock_Paragraph *) | ||
4229 | EINA_INLIST_GET(c->paragraphs)->last; | ||
4230 | if (!c->par->logical_items) | ||
4231 | { | ||
4232 | Evas_Object_Textblock_Text_Item *ti; | ||
4233 | ti = _layout_text_item_new(c, c->fmt); | ||
4234 | ti->parent.text_node = c->par->text_node; | ||
4235 | ti->parent.text_pos = 0; | ||
4236 | _layout_text_add_logical_item(c, ti, NULL); | ||
4237 | } | ||
4238 | |||
4239 | /* End of logical layout creation */ | ||
4240 | |||
4241 | /* Start of visual layout creation */ | ||
4242 | { | ||
4243 | Evas_Object_Textblock_Paragraph *last_vis_par = NULL; | ||
4244 | int par_index_step = o->num_paragraphs / TEXTBLOCK_PAR_INDEX_SIZE; | ||
4245 | int par_count = 1; /* Force it to take the first one */ | ||
4246 | int par_index_pos = 0; | ||
4247 | |||
4248 | if (par_index_step == 0) par_index_step = 1; | ||
4249 | |||
4250 | /* Clear all of the index */ | ||
4251 | memset(o->par_index, 0, sizeof(o->par_index)); | ||
4252 | |||
4253 | EINA_INLIST_FOREACH(c->paragraphs, c->par) | ||
4254 | { | ||
4255 | _layout_update_par(c); | ||
4256 | |||
4257 | /* Break if we should stop here. */ | ||
4258 | if (_layout_par(c)) | ||
4259 | { | ||
4260 | last_vis_par = c->par; | ||
4261 | break; | ||
4262 | } | ||
4263 | |||
4264 | if ((par_index_pos < TEXTBLOCK_PAR_INDEX_SIZE) && (--par_count == 0)) | ||
4265 | { | ||
4266 | par_count = par_index_step; | ||
4267 | |||
4268 | o->par_index[par_index_pos++] = c->par; | ||
4269 | } | ||
4270 | } | ||
4271 | |||
4272 | /* Mark all the rest of the paragraphs as invisible */ | ||
4273 | if (c->par) | ||
4274 | { | ||
4275 | c->par = (Evas_Object_Textblock_Paragraph *) | ||
4276 | EINA_INLIST_GET(c->par)->next; | ||
4277 | while (c->par) | ||
4278 | { | ||
4279 | c->par->visible = 0; | ||
4280 | c->par = (Evas_Object_Textblock_Paragraph *) | ||
4281 | EINA_INLIST_GET(c->par)->next; | ||
4282 | } | ||
4283 | } | ||
4284 | |||
4285 | /* Get the last visible paragraph in the layout */ | ||
4286 | if (!last_vis_par && c->paragraphs) | ||
4287 | last_vis_par = (Evas_Object_Textblock_Paragraph *) | ||
4288 | EINA_INLIST_GET(c->paragraphs)->last; | ||
4289 | |||
4290 | if (last_vis_par) | ||
4291 | c->hmax = last_vis_par->y + last_vis_par->h; | ||
4292 | } | ||
4293 | |||
4294 | /* Clean the rest of the format stack */ | ||
4295 | while (c->format_stack) | ||
4296 | { | ||
4297 | c->fmt = c->format_stack->data; | ||
4298 | c->format_stack = eina_list_remove_list(c->format_stack, c->format_stack); | ||
4299 | _format_unref_free(c->obj, c->fmt); | ||
4300 | } | ||
4301 | |||
4302 | if (w_ret) *w_ret = c->wmax; | ||
4303 | if (h_ret) *h_ret = c->hmax; | ||
4304 | |||
4305 | /* Vertically align the textblock */ | ||
4306 | if ((o->valign > 0.0) && (c->h > c->hmax)) | ||
4307 | { | ||
4308 | Evas_Coord adjustment = (c->h - c->hmax) * o->valign; | ||
4309 | Evas_Object_Textblock_Paragraph *par; | ||
4310 | EINA_INLIST_FOREACH(c->paragraphs, par) | ||
4311 | { | ||
4312 | par->y += adjustment; | ||
4313 | } | ||
4314 | } | ||
4315 | |||
4316 | if ((o->style_pad.l != style_pad_l) || (o->style_pad.r != style_pad_r) || | ||
4317 | (o->style_pad.t != style_pad_t) || (o->style_pad.b != style_pad_b)) | ||
4318 | { | ||
4319 | o->style_pad.l = style_pad_l; | ||
4320 | o->style_pad.r = style_pad_r; | ||
4321 | o->style_pad.t = style_pad_t; | ||
4322 | o->style_pad.b = style_pad_b; | ||
4323 | _paragraphs_clear(obj, c->paragraphs); | ||
4324 | _layout(obj, w, h, w_ret, h_ret); | ||
4325 | } | ||
4326 | } | ||
4327 | |||
4328 | /* | ||
4329 | * @internal | ||
4330 | * Relayout the object according to current object size. | ||
4331 | * | ||
4332 | * @param obj the evas object - NOT NULL. | ||
4333 | */ | ||
4334 | static void | ||
4335 | _relayout(const Evas_Object *obj) | ||
4336 | { | ||
4337 | Evas_Object_Textblock *o; | ||
4338 | |||
4339 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
4340 | _layout(obj, obj->cur.geometry.w, obj->cur.geometry.h, | ||
4341 | &o->formatted.w, &o->formatted.h); | ||
4342 | o->formatted.valid = 1; | ||
4343 | o->last_w = obj->cur.geometry.w; | ||
4344 | o->last_h = obj->cur.geometry.h; | ||
4345 | o->changed = 0; | ||
4346 | o->content_changed = 0; | ||
4347 | o->format_changed = EINA_FALSE; | ||
4348 | o->redraw = 1; | ||
4349 | } | ||
4350 | |||
4351 | /** | ||
4352 | * @internal | ||
4353 | * Find the layout item and line that match the text node and position passed. | ||
4354 | * | ||
4355 | * @param obj the evas object - NOT NULL. | ||
4356 | * @param n the text node - Not null. | ||
4357 | * @param pos the position to look for - valid. | ||
4358 | * @param[out] lnr the line found - not null. | ||
4359 | * @param[out] tir the item found - not null. | ||
4360 | * @see _find_layout_format_item_line_match() | ||
4361 | */ | ||
4362 | static void | ||
4363 | _find_layout_item_line_match(Evas_Object *obj, Evas_Object_Textblock_Node_Text *n, int pos, Evas_Object_Textblock_Line **lnr, Evas_Object_Textblock_Item **itr) | ||
4364 | { | ||
4365 | Evas_Object_Textblock_Paragraph *found_par; | ||
4366 | Evas_Object_Textblock_Line *ln; | ||
4367 | Evas_Object_Textblock *o; | ||
4368 | |||
4369 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
4370 | if (!o->formatted.valid) _relayout(obj); | ||
4371 | |||
4372 | found_par = n->par; | ||
4373 | if (found_par) | ||
4374 | { | ||
4375 | _layout_paragraph_render(o, found_par); | ||
4376 | EINA_INLIST_FOREACH(found_par->lines, ln) | ||
4377 | { | ||
4378 | Evas_Object_Textblock_Item *it; | ||
4379 | |||
4380 | EINA_INLIST_FOREACH(ln->items, it) | ||
4381 | { | ||
4382 | /* FIXME: p should be size_t, same goes for pos */ | ||
4383 | int p = (int) it->text_pos; | ||
4384 | |||
4385 | if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
4386 | { | ||
4387 | Evas_Object_Textblock_Text_Item *ti = | ||
4388 | _ITEM_TEXT(it); | ||
4389 | |||
4390 | p += (int) ti->text_props.text_len; | ||
4391 | } | ||
4392 | else | ||
4393 | { | ||
4394 | p++; | ||
4395 | } | ||
4396 | |||
4397 | if (((pos >= (int) it->text_pos) && (pos < p))) | ||
4398 | { | ||
4399 | *lnr = ln; | ||
4400 | *itr = it; | ||
4401 | return; | ||
4402 | } | ||
4403 | else if (p == pos) | ||
4404 | { | ||
4405 | *lnr = ln; | ||
4406 | *itr = it; | ||
4407 | } | ||
4408 | } | ||
4409 | } | ||
4410 | } | ||
4411 | } | ||
4412 | |||
4413 | /** | ||
4414 | * @internal | ||
4415 | * Return the line number 'line'. | ||
4416 | * | ||
4417 | * @param obj the evas object - NOT NULL. | ||
4418 | * @param line the line to find | ||
4419 | * @return the line of line number or NULL if no line found. | ||
4420 | */ | ||
4421 | static Evas_Object_Textblock_Line * | ||
4422 | _find_layout_line_num(const Evas_Object *obj, int line) | ||
4423 | { | ||
4424 | Evas_Object_Textblock_Paragraph *par; | ||
4425 | Evas_Object_Textblock_Line *ln; | ||
4426 | Evas_Object_Textblock *o; | ||
4427 | |||
4428 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
4429 | |||
4430 | par = _layout_find_paragraph_by_line_no(o, line); | ||
4431 | if (par) | ||
4432 | { | ||
4433 | _layout_paragraph_render(o, par); | ||
4434 | EINA_INLIST_FOREACH(par->lines, ln) | ||
4435 | { | ||
4436 | if (par->line_no + ln->line_no == line) return ln; | ||
4437 | } | ||
4438 | } | ||
4439 | return NULL; | ||
4440 | } | ||
4441 | |||
4442 | EAPI Evas_Object * | ||
4443 | evas_object_textblock_add(Evas *e) | ||
4444 | { | ||
4445 | Evas_Object *obj; | ||
4446 | |||
4447 | MAGIC_CHECK(e, Evas, MAGIC_EVAS); | ||
4448 | return NULL; | ||
4449 | MAGIC_CHECK_END(); | ||
4450 | obj = evas_object_new(e); | ||
4451 | evas_object_textblock_init(obj); | ||
4452 | evas_object_inject(obj, e); | ||
4453 | return obj; | ||
4454 | } | ||
4455 | |||
4456 | EAPI Evas_Textblock_Style * | ||
4457 | evas_textblock_style_new(void) | ||
4458 | { | ||
4459 | Evas_Textblock_Style *ts; | ||
4460 | |||
4461 | ts = calloc(1, sizeof(Evas_Textblock_Style)); | ||
4462 | return ts; | ||
4463 | } | ||
4464 | |||
4465 | EAPI void | ||
4466 | evas_textblock_style_free(Evas_Textblock_Style *ts) | ||
4467 | { | ||
4468 | if (!ts) return; | ||
4469 | if (ts->objects) | ||
4470 | { | ||
4471 | ts->delete_me = 1; | ||
4472 | return; | ||
4473 | } | ||
4474 | _style_clear(ts); | ||
4475 | free(ts); | ||
4476 | } | ||
4477 | |||
4478 | EAPI void | ||
4479 | evas_textblock_style_set(Evas_Textblock_Style *ts, const char *text) | ||
4480 | { | ||
4481 | Eina_List *l; | ||
4482 | Evas_Object *obj; | ||
4483 | |||
4484 | if (!ts) return; | ||
4485 | /* If the style wasn't really changed, abort. */ | ||
4486 | if ((!ts->style_text && !text) || | ||
4487 | (ts->style_text && text && !strcmp(text, ts->style_text))) | ||
4488 | return; | ||
4489 | |||
4490 | EINA_LIST_FOREACH(ts->objects, l, obj) | ||
4491 | { | ||
4492 | Evas_Object_Textblock *o; | ||
4493 | |||
4494 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
4495 | _evas_textblock_invalidate_all(o); | ||
4496 | _evas_textblock_changed(o, obj); | ||
4497 | } | ||
4498 | |||
4499 | _style_replace(ts, text); | ||
4500 | |||
4501 | if (ts->style_text) | ||
4502 | { | ||
4503 | // format MUST be KEY='VALUE'[KEY='VALUE']... | ||
4504 | const char *p; | ||
4505 | const char *key_start, *key_stop, *val_start; | ||
4506 | |||
4507 | key_start = key_stop = val_start = NULL; | ||
4508 | p = ts->style_text; | ||
4509 | while (*p) | ||
4510 | { | ||
4511 | if (!key_start) | ||
4512 | { | ||
4513 | if (!isspace((unsigned char)(*p))) | ||
4514 | key_start = p; | ||
4515 | } | ||
4516 | else if (!key_stop) | ||
4517 | { | ||
4518 | if ((*p == '=') || (isspace((unsigned char)(*p)))) | ||
4519 | key_stop = p; | ||
4520 | } | ||
4521 | else if (!val_start) | ||
4522 | { | ||
4523 | if (((*p) == '\'') && (*(p + 1))) | ||
4524 | { | ||
4525 | val_start = ++p; | ||
4526 | } | ||
4527 | } | ||
4528 | if ((key_start) && (key_stop) && (val_start)) | ||
4529 | { | ||
4530 | char *tags, *replaces = NULL; | ||
4531 | Evas_Object_Style_Tag *tag; | ||
4532 | const char *val_stop = NULL; | ||
4533 | size_t tag_len; | ||
4534 | size_t replace_len; | ||
4535 | |||
4536 | { | ||
4537 | Eina_Strbuf *buf = eina_strbuf_new(); | ||
4538 | val_stop = val_start; | ||
4539 | while(*p) | ||
4540 | { | ||
4541 | if (*p == '\'') | ||
4542 | { | ||
4543 | /* Break if we found the tag end */ | ||
4544 | if (p[-1] != '\\') | ||
4545 | { | ||
4546 | eina_strbuf_append_length(buf, val_stop, | ||
4547 | p - val_stop); | ||
4548 | break; | ||
4549 | } | ||
4550 | else | ||
4551 | { | ||
4552 | eina_strbuf_append_length(buf, val_stop, | ||
4553 | p - val_stop - 1); | ||
4554 | eina_strbuf_append_char(buf, '\''); | ||
4555 | val_stop = p + 1; | ||
4556 | } | ||
4557 | } | ||
4558 | p++; | ||
4559 | } | ||
4560 | replaces = eina_strbuf_string_steal(buf); | ||
4561 | eina_strbuf_free(buf); | ||
4562 | } | ||
4563 | /* If we didn't find an end, just aboart. */ | ||
4564 | if (!*p) | ||
4565 | { | ||
4566 | if (replaces) free(replaces); | ||
4567 | break; | ||
4568 | } | ||
4569 | |||
4570 | tag_len = key_stop - key_start; | ||
4571 | replace_len = val_stop - val_start; | ||
4572 | |||
4573 | tags = malloc(tag_len + 1); | ||
4574 | if (tags) | ||
4575 | { | ||
4576 | memcpy(tags, key_start, tag_len); | ||
4577 | tags[tag_len] = 0; | ||
4578 | } | ||
4579 | |||
4580 | if ((tags) && (replaces)) | ||
4581 | { | ||
4582 | if (!strcmp(tags, "DEFAULT")) | ||
4583 | { | ||
4584 | ts->default_tag = replaces; | ||
4585 | free(tags); | ||
4586 | } | ||
4587 | else | ||
4588 | { | ||
4589 | tag = calloc(1, sizeof(Evas_Object_Style_Tag)); | ||
4590 | if (tag) | ||
4591 | { | ||
4592 | tag->tag.tag = tags; | ||
4593 | tag->tag.replace = replaces; | ||
4594 | tag->tag.tag_len = tag_len; | ||
4595 | tag->tag.replace_len = replace_len; | ||
4596 | ts->tags = (Evas_Object_Style_Tag *)eina_inlist_append(EINA_INLIST_GET(ts->tags), EINA_INLIST_GET(tag)); | ||
4597 | } | ||
4598 | else | ||
4599 | { | ||
4600 | free(tags); | ||
4601 | free(replaces); | ||
4602 | } | ||
4603 | } | ||
4604 | } | ||
4605 | else | ||
4606 | { | ||
4607 | if (tags) free(tags); | ||
4608 | if (replaces) free(replaces); | ||
4609 | } | ||
4610 | key_start = key_stop = val_start = NULL; | ||
4611 | } | ||
4612 | p++; | ||
4613 | } | ||
4614 | } | ||
4615 | } | ||
4616 | |||
4617 | EAPI const char * | ||
4618 | evas_textblock_style_get(const Evas_Textblock_Style *ts) | ||
4619 | { | ||
4620 | if (!ts) return NULL; | ||
4621 | return ts->style_text; | ||
4622 | } | ||
4623 | |||
4624 | /* textblock styles */ | ||
4625 | |||
4626 | static void | ||
4627 | _textblock_style_generic_set(Evas_Object *obj, Evas_Textblock_Style *ts, | ||
4628 | Evas_Textblock_Style **obj_ts) | ||
4629 | { | ||
4630 | TB_HEAD(); | ||
4631 | if (ts == *obj_ts) return; | ||
4632 | if ((ts) && (ts->delete_me)) return; | ||
4633 | if (*obj_ts) | ||
4634 | { | ||
4635 | Evas_Textblock_Style *old_ts; | ||
4636 | if (o->markup_text) | ||
4637 | { | ||
4638 | free(o->markup_text); | ||
4639 | o->markup_text = NULL; | ||
4640 | } | ||
4641 | |||
4642 | old_ts = *obj_ts; | ||
4643 | old_ts->objects = eina_list_remove(old_ts->objects, obj); | ||
4644 | if ((old_ts->delete_me) && (!old_ts->objects)) | ||
4645 | evas_textblock_style_free(old_ts); | ||
4646 | } | ||
4647 | if (ts) | ||
4648 | { | ||
4649 | ts->objects = eina_list_append(ts->objects, obj); | ||
4650 | } | ||
4651 | *obj_ts = ts; | ||
4652 | |||
4653 | _evas_textblock_invalidate_all(o); | ||
4654 | _evas_textblock_changed(o, obj); | ||
4655 | } | ||
4656 | |||
4657 | EAPI void | ||
4658 | evas_object_textblock_style_set(Evas_Object *obj, Evas_Textblock_Style *ts) | ||
4659 | { | ||
4660 | TB_HEAD(); | ||
4661 | _textblock_style_generic_set(obj, ts, &(o->style)); | ||
4662 | } | ||
4663 | |||
4664 | EAPI const Evas_Textblock_Style * | ||
4665 | evas_object_textblock_style_get(const Evas_Object *obj) | ||
4666 | { | ||
4667 | TB_HEAD_RETURN(NULL); | ||
4668 | return o->style; | ||
4669 | } | ||
4670 | |||
4671 | EAPI void | ||
4672 | evas_object_textblock_style_user_push(Evas_Object *obj, Evas_Textblock_Style *ts) | ||
4673 | { | ||
4674 | TB_HEAD(); | ||
4675 | _textblock_style_generic_set(obj, ts, &(o->style_user)); | ||
4676 | } | ||
4677 | |||
4678 | EAPI const Evas_Textblock_Style * | ||
4679 | evas_object_textblock_style_user_peek(const Evas_Object *obj) | ||
4680 | { | ||
4681 | TB_HEAD_RETURN(NULL); | ||
4682 | return o->style_user; | ||
4683 | } | ||
4684 | |||
4685 | EAPI void | ||
4686 | evas_object_textblock_style_user_pop(Evas_Object *obj) | ||
4687 | { | ||
4688 | TB_HEAD(); | ||
4689 | _textblock_style_generic_set(obj, NULL, &(o->style_user)); | ||
4690 | } | ||
4691 | |||
4692 | EAPI void | ||
4693 | evas_object_textblock_replace_char_set(Evas_Object *obj, const char *ch) | ||
4694 | { | ||
4695 | TB_HEAD(); | ||
4696 | if (o->repch) eina_stringshare_del(o->repch); | ||
4697 | if (ch) o->repch = eina_stringshare_add(ch); | ||
4698 | else o->repch = NULL; | ||
4699 | _evas_textblock_invalidate_all(o); | ||
4700 | _evas_textblock_changed(o, obj); | ||
4701 | } | ||
4702 | |||
4703 | EAPI void | ||
4704 | evas_object_textblock_legacy_newline_set(Evas_Object *obj, Eina_Bool mode) | ||
4705 | { | ||
4706 | TB_HEAD(); | ||
4707 | if (o->legacy_newline == mode) | ||
4708 | return; | ||
4709 | |||
4710 | o->legacy_newline = mode; | ||
4711 | /* FIXME: Should recreate all the textnodes... For now, it's just | ||
4712 | * for new text inserted. */ | ||
4713 | } | ||
4714 | |||
4715 | EAPI Eina_Bool | ||
4716 | evas_object_textblock_legacy_newline_get(const Evas_Object *obj) | ||
4717 | { | ||
4718 | TB_HEAD_RETURN(EINA_FALSE); | ||
4719 | return o->legacy_newline; | ||
4720 | } | ||
4721 | |||
4722 | EAPI void | ||
4723 | evas_object_textblock_valign_set(Evas_Object *obj, double align) | ||
4724 | { | ||
4725 | TB_HEAD(); | ||
4726 | if (align < 0.0) align = 0.0; | ||
4727 | else if (align > 1.0) align = 1.0; | ||
4728 | if (o->valign == align) return; | ||
4729 | o->valign = align; | ||
4730 | _evas_textblock_changed(o, obj); | ||
4731 | } | ||
4732 | |||
4733 | EAPI double | ||
4734 | evas_object_textblock_valign_get(const Evas_Object *obj) | ||
4735 | { | ||
4736 | TB_HEAD_RETURN(0.0); | ||
4737 | return o->valign; | ||
4738 | } | ||
4739 | |||
4740 | EAPI void | ||
4741 | evas_object_textblock_bidi_delimiters_set(Evas_Object *obj, const char *delim) | ||
4742 | { | ||
4743 | TB_HEAD(); | ||
4744 | eina_stringshare_replace(&o->bidi_delimiters, delim); | ||
4745 | } | ||
4746 | |||
4747 | EAPI const char * | ||
4748 | evas_object_textblock_bidi_delimiters_get(const Evas_Object *obj) | ||
4749 | { | ||
4750 | TB_HEAD_RETURN(NULL); | ||
4751 | return o->bidi_delimiters; | ||
4752 | } | ||
4753 | |||
4754 | EAPI const char * | ||
4755 | evas_object_textblock_replace_char_get(Evas_Object *obj) | ||
4756 | { | ||
4757 | TB_HEAD_RETURN(NULL); | ||
4758 | return o->repch; | ||
4759 | } | ||
4760 | |||
4761 | /** | ||
4762 | * @internal | ||
4763 | * Advance p_buff to point after the end of the string. It's used with the | ||
4764 | * @ref escaped_strings[] variable. | ||
4765 | * | ||
4766 | * @param p_buff the pointer to the current string. | ||
4767 | */ | ||
4768 | static inline void | ||
4769 | _escaped_advance_after_end_of_string(const char **p_buf) | ||
4770 | { | ||
4771 | while (**p_buf != 0) (*p_buf)++; | ||
4772 | (*p_buf)++; | ||
4773 | } | ||
4774 | |||
4775 | /** | ||
4776 | * @internal | ||
4777 | * Advance p_buff to point after the end of the string. It's used with the | ||
4778 | * @ref escaped_strings[] variable. Also chec if matches. | ||
4779 | * FIXME: doc. | ||
4780 | * | ||
4781 | * @param p_buff the pointer to the current string. | ||
4782 | */ | ||
4783 | static inline int | ||
4784 | _escaped_is_eq_and_advance(const char *s, const char *s_end, | ||
4785 | const char **p_m, const char *m_end) | ||
4786 | { | ||
4787 | Eina_Bool reached_end; | ||
4788 | for (;((s < s_end) && (*p_m < m_end)); s++, (*p_m)++) | ||
4789 | { | ||
4790 | if (*s != **p_m) | ||
4791 | { | ||
4792 | _escaped_advance_after_end_of_string(p_m); | ||
4793 | return 0; | ||
4794 | } | ||
4795 | } | ||
4796 | |||
4797 | reached_end = !**p_m; | ||
4798 | if (*p_m < m_end) | ||
4799 | _escaped_advance_after_end_of_string(p_m); | ||
4800 | |||
4801 | return ((s == s_end) && reached_end); | ||
4802 | } | ||
4803 | |||
4804 | /** | ||
4805 | * @internal | ||
4806 | * | ||
4807 | * @param s the string to match | ||
4808 | */ | ||
4809 | static inline const char * | ||
4810 | _escaped_char_match(const char *s, int *adv) | ||
4811 | { | ||
4812 | const char *map_itr, *map_end, *mc, *sc; | ||
4813 | |||
4814 | map_itr = escape_strings; | ||
4815 | map_end = map_itr + sizeof(escape_strings); | ||
4816 | |||
4817 | while (map_itr < map_end) | ||
4818 | { | ||
4819 | const char *escape; | ||
4820 | int match; | ||
4821 | |||
4822 | escape = map_itr; | ||
4823 | _escaped_advance_after_end_of_string(&map_itr); | ||
4824 | if (map_itr >= map_end) break; | ||
4825 | |||
4826 | mc = map_itr; | ||
4827 | sc = s; | ||
4828 | match = 1; | ||
4829 | while ((*mc) && (*sc)) | ||
4830 | { | ||
4831 | if ((unsigned char)*sc < (unsigned char)*mc) return NULL; | ||
4832 | if (*sc != *mc) | ||
4833 | { | ||
4834 | match = 0; | ||
4835 | break; | ||
4836 | } | ||
4837 | mc++; | ||
4838 | sc++; | ||
4839 | } | ||
4840 | if (match) | ||
4841 | { | ||
4842 | *adv = mc - map_itr; | ||
4843 | return escape; | ||
4844 | } | ||
4845 | _escaped_advance_after_end_of_string(&map_itr); | ||
4846 | } | ||
4847 | return NULL; | ||
4848 | } | ||
4849 | |||
4850 | /** | ||
4851 | * @internal | ||
4852 | * FIXME: TBD. | ||
4853 | * | ||
4854 | * @param s the string to match | ||
4855 | */ | ||
4856 | static inline const char * | ||
4857 | _escaped_char_get(const char *s, const char *s_end) | ||
4858 | { | ||
4859 | /* Handle numeric escape codes. */ | ||
4860 | if (s[1] == '#') | ||
4861 | { | ||
4862 | static char utf8_escape[7]; /* Support up to 6 bytes utf8 */ | ||
4863 | char ustr[10]; | ||
4864 | Eina_Unicode uchar[2] = { 0, 0 }; | ||
4865 | char *utf8_char; | ||
4866 | size_t len = 0; | ||
4867 | int base = 10; | ||
4868 | s += 2; /* Skip "&#" */ | ||
4869 | |||
4870 | if (tolower((unsigned char)(*s)) == 'x') | ||
4871 | { | ||
4872 | s++; | ||
4873 | base = 16; | ||
4874 | } | ||
4875 | |||
4876 | len = s_end - s; | ||
4877 | if (len >= sizeof(ustr) + 1) | ||
4878 | len = sizeof(ustr); | ||
4879 | |||
4880 | memcpy(ustr, s, len); | ||
4881 | ustr[len] = '\0'; | ||
4882 | uchar[0] = strtol(ustr, NULL, base); | ||
4883 | |||
4884 | if (uchar[0] == 0) | ||
4885 | return NULL; | ||
4886 | |||
4887 | utf8_char = eina_unicode_unicode_to_utf8(uchar, NULL); | ||
4888 | strcpy(utf8_escape, utf8_char); | ||
4889 | free(utf8_char); | ||
4890 | |||
4891 | return utf8_escape; | ||
4892 | } | ||
4893 | else | ||
4894 | { | ||
4895 | const char *map_itr, *map_end; | ||
4896 | |||
4897 | map_itr = escape_strings; | ||
4898 | map_end = map_itr + sizeof(escape_strings); | ||
4899 | |||
4900 | while (map_itr < map_end) | ||
4901 | { | ||
4902 | if (_escaped_is_eq_and_advance(s, s_end, &map_itr, map_end)) | ||
4903 | return map_itr; | ||
4904 | if (map_itr < map_end) | ||
4905 | _escaped_advance_after_end_of_string(&map_itr); | ||
4906 | } | ||
4907 | } | ||
4908 | |||
4909 | return NULL; | ||
4910 | } | ||
4911 | |||
4912 | EAPI const char * | ||
4913 | evas_textblock_escape_string_get(const char *escape) | ||
4914 | { | ||
4915 | /* & -> & */ | ||
4916 | return _escaped_char_get(escape, escape + strlen(escape)); | ||
4917 | } | ||
4918 | |||
4919 | EAPI const char * | ||
4920 | evas_textblock_escape_string_range_get(const char *escape_start, const char *escape_end) | ||
4921 | { | ||
4922 | return _escaped_char_get(escape_start, escape_end); | ||
4923 | } | ||
4924 | |||
4925 | EAPI const char * | ||
4926 | evas_textblock_string_escape_get(const char *string, int *len_ret) | ||
4927 | { | ||
4928 | /* & -> & */ | ||
4929 | return _escaped_char_match(string, len_ret); | ||
4930 | } | ||
4931 | |||
4932 | /** | ||
4933 | * @internal | ||
4934 | * Appends the escaped char beteewn s and s_end to the curosr | ||
4935 | * | ||
4936 | * | ||
4937 | * @param s the start of the string | ||
4938 | * @param s_end the end of the string. | ||
4939 | */ | ||
4940 | static inline void | ||
4941 | _append_escaped_char(Evas_Textblock_Cursor *cur, const char *s, | ||
4942 | const char *s_end) | ||
4943 | { | ||
4944 | const char *escape; | ||
4945 | |||
4946 | escape = _escaped_char_get(s, s_end); | ||
4947 | if (escape) | ||
4948 | evas_textblock_cursor_text_append(cur, escape); | ||
4949 | } | ||
4950 | |||
4951 | /** | ||
4952 | * @internal | ||
4953 | * prepends the escaped char beteewn s and s_end to the curosr | ||
4954 | * | ||
4955 | * | ||
4956 | * @param s the start of the string | ||
4957 | * @param s_end the end of the string. | ||
4958 | */ | ||
4959 | static inline void | ||
4960 | _prepend_escaped_char(Evas_Textblock_Cursor *cur, const char *s, | ||
4961 | const char *s_end) | ||
4962 | { | ||
4963 | const char *escape; | ||
4964 | |||
4965 | escape = _escaped_char_get(s, s_end); | ||
4966 | if (escape) | ||
4967 | evas_textblock_cursor_text_prepend(cur, escape); | ||
4968 | } | ||
4969 | |||
4970 | |||
4971 | EAPI void | ||
4972 | evas_object_textblock_text_markup_set(Evas_Object *obj, const char *text) | ||
4973 | { | ||
4974 | TB_HEAD(); | ||
4975 | if ((text != o->markup_text) && (o->markup_text)) | ||
4976 | { | ||
4977 | free(o->markup_text); | ||
4978 | o->markup_text = NULL; | ||
4979 | } | ||
4980 | _nodes_clear(obj); | ||
4981 | if (!o->style && !o->style_user) | ||
4982 | { | ||
4983 | if (text != o->markup_text) | ||
4984 | { | ||
4985 | if (text) o->markup_text = strdup(text); | ||
4986 | } | ||
4987 | return; | ||
4988 | } | ||
4989 | evas_textblock_cursor_paragraph_first(o->cursor); | ||
4990 | |||
4991 | evas_object_textblock_text_markup_prepend(o->cursor, text); | ||
4992 | /* Point all the cursors to the starrt */ | ||
4993 | { | ||
4994 | Eina_List *l; | ||
4995 | Evas_Textblock_Cursor *data; | ||
4996 | |||
4997 | evas_textblock_cursor_paragraph_first(o->cursor); | ||
4998 | EINA_LIST_FOREACH(o->cursors, l, data) | ||
4999 | evas_textblock_cursor_paragraph_first(data); | ||
5000 | } | ||
5001 | } | ||
5002 | |||
5003 | EAPI void | ||
5004 | evas_object_textblock_text_markup_prepend(Evas_Textblock_Cursor *cur, const char *text) | ||
5005 | { | ||
5006 | Evas_Object *obj = cur->obj; | ||
5007 | TB_HEAD(); | ||
5008 | if (text) | ||
5009 | { | ||
5010 | char *s, *p; | ||
5011 | char *tag_start, *tag_end, *esc_start, *esc_end; | ||
5012 | |||
5013 | tag_start = tag_end = esc_start = esc_end = NULL; | ||
5014 | p = (char *)text; | ||
5015 | s = p; | ||
5016 | /* This loop goes through all of the mark up text until it finds format | ||
5017 | * tags, escape sequences or the terminating NULL. When it finds either | ||
5018 | * of those, it appends the text found up until that point to the textblock | ||
5019 | * proccesses whatever found. It repeats itself until the termainating | ||
5020 | * NULL is reached. */ | ||
5021 | for (;;) | ||
5022 | { | ||
5023 | size_t text_len; | ||
5024 | /* If we got to the end of string or just finished/started tag | ||
5025 | * or escape sequence handling. */ | ||
5026 | if ((*p == 0) || | ||
5027 | (tag_end) || (esc_end) || | ||
5028 | (tag_start) || (esc_start)) | ||
5029 | { | ||
5030 | if (tag_end) | ||
5031 | { | ||
5032 | /* If we reached to a tag ending, analyze the tag */ | ||
5033 | char *ttag; | ||
5034 | size_t ttag_len = tag_end - tag_start; | ||
5035 | |||
5036 | |||
5037 | ttag = malloc(ttag_len + 1); | ||
5038 | if (ttag) | ||
5039 | { | ||
5040 | memcpy(ttag, tag_start, ttag_len); | ||
5041 | ttag[ttag_len] = 0; | ||
5042 | evas_textblock_cursor_format_prepend(cur, ttag); | ||
5043 | free(ttag); | ||
5044 | } | ||
5045 | tag_start = tag_end = NULL; | ||
5046 | } | ||
5047 | else if (esc_end) | ||
5048 | { | ||
5049 | _prepend_escaped_char(cur, esc_start, esc_end + 1); | ||
5050 | esc_start = esc_end = NULL; | ||
5051 | } | ||
5052 | else if (*p == 0) | ||
5053 | { | ||
5054 | _prepend_text_run(cur, s, p); | ||
5055 | s = NULL; | ||
5056 | } | ||
5057 | if (*p == 0) | ||
5058 | break; | ||
5059 | } | ||
5060 | if (*p == '<') | ||
5061 | { | ||
5062 | if (!esc_start) | ||
5063 | { | ||
5064 | /* Append the text prior to this to the textblock and mark | ||
5065 | * the start of the tag */ | ||
5066 | tag_start = p; | ||
5067 | tag_end = NULL; | ||
5068 | _prepend_text_run(cur, s, p); | ||
5069 | s = NULL; | ||
5070 | } | ||
5071 | } | ||
5072 | else if (*p == '>') | ||
5073 | { | ||
5074 | if (tag_start) | ||
5075 | { | ||
5076 | tag_end = p + 1; | ||
5077 | s = p + 1; | ||
5078 | } | ||
5079 | } | ||
5080 | else if (*p == '&') | ||
5081 | { | ||
5082 | if (!tag_start) | ||
5083 | { | ||
5084 | /* Append the text prior to this to the textblock and mark | ||
5085 | * the start of the escape sequence */ | ||
5086 | esc_start = p; | ||
5087 | esc_end = NULL; | ||
5088 | _prepend_text_run(cur, s, p); | ||
5089 | s = NULL; | ||
5090 | } | ||
5091 | } | ||
5092 | else if (*p == ';') | ||
5093 | { | ||
5094 | if (esc_start) | ||
5095 | { | ||
5096 | esc_end = p; | ||
5097 | s = p + 1; | ||
5098 | } | ||
5099 | } | ||
5100 | /* Unicode object replcament char */ | ||
5101 | else if (!strncmp(_REPLACEMENT_CHAR_UTF8, p, | ||
5102 | text_len = strlen(_REPLACEMENT_CHAR_UTF8)) || | ||
5103 | !strncmp(_NEWLINE_UTF8, p, | ||
5104 | text_len = strlen(_NEWLINE_UTF8)) || | ||
5105 | !strncmp(_TAB_UTF8, p, | ||
5106 | text_len = strlen(_TAB_UTF8)) || | ||
5107 | !strncmp(_PARAGRAPH_SEPARATOR_UTF8, p, | ||
5108 | text_len = strlen(_PARAGRAPH_SEPARATOR_UTF8))) | ||
5109 | { | ||
5110 | /*FIXME: currently just remove them, maybe do something | ||
5111 | * fancier in the future, atm it breaks if this char | ||
5112 | * is inside <> */ | ||
5113 | _prepend_text_run(cur, s, p); | ||
5114 | /* it's also advanced later in this loop need +text_len | ||
5115 | in total*/ | ||
5116 | p += text_len - 1; | ||
5117 | s = p + 1; /* One after the end of the replacement char */ | ||
5118 | } | ||
5119 | p++; | ||
5120 | } | ||
5121 | } | ||
5122 | _evas_textblock_changed(o, obj); | ||
5123 | } | ||
5124 | |||
5125 | |||
5126 | /** | ||
5127 | * @internal | ||
5128 | * An helper function to markup get. Appends the format from fnode to the strbugf txt. | ||
5129 | * | ||
5130 | * @param o the textblock object. | ||
5131 | * @param txt the strbuf to append to. | ||
5132 | * @param fnode the format node to process. | ||
5133 | */ | ||
5134 | static void | ||
5135 | _markup_get_format_append(Eina_Strbuf *txt, Evas_Object_Textblock_Node_Format *fnode) | ||
5136 | { | ||
5137 | eina_strbuf_append_char(txt, '<'); | ||
5138 | { | ||
5139 | const char *s; | ||
5140 | |||
5141 | // FIXME: need to escape | ||
5142 | s = fnode->orig_format; | ||
5143 | if (!fnode->opener && !fnode->own_closer) | ||
5144 | eina_strbuf_append_char(txt, '/'); | ||
5145 | eina_strbuf_append(txt, s); | ||
5146 | if (fnode->own_closer) | ||
5147 | eina_strbuf_append_char(txt, '/'); | ||
5148 | } | ||
5149 | eina_strbuf_append_char(txt, '>'); | ||
5150 | } | ||
5151 | |||
5152 | /** | ||
5153 | * @internal | ||
5154 | * An helper function to markup get. Appends the text in text. | ||
5155 | * | ||
5156 | * @param txt the strbuf to append to. | ||
5157 | * @param text the text to process. | ||
5158 | */ | ||
5159 | static void | ||
5160 | _markup_get_text_append(Eina_Strbuf *txt, const Eina_Unicode *text) | ||
5161 | { | ||
5162 | char *p = eina_unicode_unicode_to_utf8(text, NULL); | ||
5163 | char *base = p; | ||
5164 | while (*p) | ||
5165 | { | ||
5166 | const char *escape; | ||
5167 | int adv; | ||
5168 | |||
5169 | escape = _escaped_char_match(p, &adv); | ||
5170 | if (escape) | ||
5171 | { | ||
5172 | p += adv; | ||
5173 | eina_strbuf_append(txt, escape); | ||
5174 | } | ||
5175 | else | ||
5176 | { | ||
5177 | eina_strbuf_append_char(txt, *p); | ||
5178 | p++; | ||
5179 | } | ||
5180 | } | ||
5181 | free(base); | ||
5182 | } | ||
5183 | EAPI const char * | ||
5184 | evas_object_textblock_text_markup_get(const Evas_Object *obj) | ||
5185 | { | ||
5186 | Evas_Object_Textblock_Node_Text *n; | ||
5187 | Eina_Strbuf *txt = NULL; | ||
5188 | |||
5189 | TB_HEAD_RETURN(NULL); | ||
5190 | if (o->markup_text) return(o->markup_text); | ||
5191 | txt = eina_strbuf_new(); | ||
5192 | EINA_INLIST_FOREACH(o->text_nodes, n) | ||
5193 | { | ||
5194 | Evas_Object_Textblock_Node_Format *fnode; | ||
5195 | Eina_Unicode *text_base, *text; | ||
5196 | int off; | ||
5197 | |||
5198 | /* For each text node to thorugh all of it's format nodes | ||
5199 | * append text from the start to the offset of the next format | ||
5200 | * using the last format got. if needed it also creates format items | ||
5201 | * this is the core algorithm of the layout mechanism. | ||
5202 | * Skip the unicode replacement chars when there are because | ||
5203 | * we don't want to print them. */ | ||
5204 | text_base = text = | ||
5205 | eina_unicode_strndup(eina_ustrbuf_string_get(n->unicode), | ||
5206 | eina_ustrbuf_length_get(n->unicode)); | ||
5207 | fnode = n->format_node; | ||
5208 | off = 0; | ||
5209 | while (fnode && (fnode->text_node == n)) | ||
5210 | { | ||
5211 | Eina_Unicode tmp_ch; | ||
5212 | off += fnode->offset; | ||
5213 | /* No need to skip on the first run */ | ||
5214 | tmp_ch = text[off]; | ||
5215 | text[off] = 0; /* Null terminate the part of the string */ | ||
5216 | _markup_get_text_append(txt, text); | ||
5217 | _markup_get_format_append(txt, fnode); | ||
5218 | text[off] = tmp_ch; /* Restore the char */ | ||
5219 | text += off; | ||
5220 | if (fnode->visible) | ||
5221 | { | ||
5222 | off = -1; | ||
5223 | text++; | ||
5224 | } | ||
5225 | else | ||
5226 | { | ||
5227 | off = 0; | ||
5228 | } | ||
5229 | fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); | ||
5230 | } | ||
5231 | /* Add the rest, skip replacement */ | ||
5232 | _markup_get_text_append(txt, text); | ||
5233 | free(text_base); | ||
5234 | } | ||
5235 | |||
5236 | |||
5237 | o->markup_text = eina_strbuf_string_steal(txt); | ||
5238 | eina_strbuf_free(txt); | ||
5239 | return o->markup_text; | ||
5240 | } | ||
5241 | |||
5242 | EAPI char * | ||
5243 | evas_textblock_text_markup_to_utf8(const Evas_Object *obj, const char *text) | ||
5244 | { | ||
5245 | /* FIXME: Redundant and awful, should be merged with markup_prepend */ | ||
5246 | Eina_Strbuf *sbuf; | ||
5247 | char *s, *p, *ret; | ||
5248 | char *tag_start, *tag_end, *esc_start, *esc_end; | ||
5249 | |||
5250 | if (!text) return NULL; | ||
5251 | |||
5252 | |||
5253 | tag_start = tag_end = esc_start = esc_end = NULL; | ||
5254 | sbuf = eina_strbuf_new(); | ||
5255 | p = (char *)text; | ||
5256 | s = p; | ||
5257 | /* This loop goes through all of the mark up text until it finds format | ||
5258 | * tags, escape sequences or the terminating NULL. When it finds either | ||
5259 | * of those, it appends the text found up until that point to the textblock | ||
5260 | * proccesses whatever found. It repeats itself until the termainating | ||
5261 | * NULL is reached. */ | ||
5262 | for (;;) | ||
5263 | { | ||
5264 | /* If we got to the end of string or just finished/started tag | ||
5265 | * or escape sequence handling. */ | ||
5266 | if ((*p == 0) || | ||
5267 | (tag_end) || (esc_end) || | ||
5268 | (tag_start) || (esc_start)) | ||
5269 | { | ||
5270 | if (tag_end) | ||
5271 | { | ||
5272 | /* If we reached to a tag ending, analyze the tag */ | ||
5273 | char *ttag; | ||
5274 | size_t ttag_len; | ||
5275 | |||
5276 | tag_start++; /* Skip the < */ | ||
5277 | tag_end--; /* Skip the > */ | ||
5278 | if ((tag_end > tag_start) && (*(tag_end - 1) == '/')) | ||
5279 | { | ||
5280 | tag_end --; /* Skip the terminating '/' */ | ||
5281 | while (*(tag_end - 1) == ' ') | ||
5282 | tag_end--; /* skip trailing ' ' */ | ||
5283 | } | ||
5284 | |||
5285 | ttag_len = tag_end - tag_start; | ||
5286 | |||
5287 | ttag = malloc(ttag_len + 1); | ||
5288 | if (ttag) | ||
5289 | { | ||
5290 | const char *match = NULL; | ||
5291 | size_t replace_len; | ||
5292 | memcpy(ttag, tag_start, ttag_len); | ||
5293 | ttag[ttag_len] = 0; | ||
5294 | |||
5295 | |||
5296 | if (obj) | ||
5297 | { | ||
5298 | match = _style_match_tag( | ||
5299 | evas_object_textblock_style_get(obj), | ||
5300 | ttag, ttag_len, &replace_len); | ||
5301 | } | ||
5302 | |||
5303 | if (!match) match = ttag; | ||
5304 | |||
5305 | if (_IS_PARAGRAPH_SEPARATOR_SIMPLE(match)) | ||
5306 | eina_strbuf_append(sbuf, _PARAGRAPH_SEPARATOR_UTF8); | ||
5307 | else if (_IS_LINE_SEPARATOR(match)) | ||
5308 | eina_strbuf_append(sbuf, _NEWLINE_UTF8); | ||
5309 | else if (_IS_TAB(match)) | ||
5310 | eina_strbuf_append(sbuf, _TAB_UTF8); | ||
5311 | else if (!strncmp(match, "item", 4)) | ||
5312 | eina_strbuf_append(sbuf, _REPLACEMENT_CHAR_UTF8); | ||
5313 | |||
5314 | free(ttag); | ||
5315 | } | ||
5316 | tag_start = tag_end = NULL; | ||
5317 | } | ||
5318 | else if (esc_end) | ||
5319 | { | ||
5320 | const char *escape; | ||
5321 | |||
5322 | escape = _escaped_char_get(esc_start, esc_end + 1); | ||
5323 | if (escape) eina_strbuf_append(sbuf, escape); | ||
5324 | esc_start = esc_end = NULL; | ||
5325 | } | ||
5326 | else if (*p == 0) | ||
5327 | { | ||
5328 | eina_strbuf_append_length(sbuf, s, p - s); | ||
5329 | s = NULL; | ||
5330 | } | ||
5331 | if (*p == 0) | ||
5332 | break; | ||
5333 | } | ||
5334 | if (*p == '<') | ||
5335 | { | ||
5336 | if (!esc_start) | ||
5337 | { | ||
5338 | /* Append the text prior to this to the textblock and | ||
5339 | * mark the start of the tag */ | ||
5340 | tag_start = p; | ||
5341 | tag_end = NULL; | ||
5342 | eina_strbuf_append_length(sbuf, s, p - s); | ||
5343 | s = NULL; | ||
5344 | } | ||
5345 | } | ||
5346 | else if (*p == '>') | ||
5347 | { | ||
5348 | if (tag_start) | ||
5349 | { | ||
5350 | tag_end = p + 1; | ||
5351 | s = p + 1; | ||
5352 | } | ||
5353 | } | ||
5354 | else if (*p == '&') | ||
5355 | { | ||
5356 | if (!tag_start) | ||
5357 | { | ||
5358 | /* Append the text prior to this to the textblock and mark | ||
5359 | * the start of the escape sequence */ | ||
5360 | esc_start = p; | ||
5361 | esc_end = NULL; | ||
5362 | eina_strbuf_append_length(sbuf, s, p - s); | ||
5363 | s = NULL; | ||
5364 | } | ||
5365 | } | ||
5366 | else if (*p == ';') | ||
5367 | { | ||
5368 | if (esc_start) | ||
5369 | { | ||
5370 | esc_end = p; | ||
5371 | s = p + 1; | ||
5372 | } | ||
5373 | } | ||
5374 | p++; | ||
5375 | } | ||
5376 | |||
5377 | ret = eina_strbuf_string_steal(sbuf); | ||
5378 | eina_strbuf_free(sbuf); | ||
5379 | return ret; | ||
5380 | } | ||
5381 | |||
5382 | EAPI char * | ||
5383 | evas_textblock_text_utf8_to_markup(const Evas_Object *obj, const char *text) | ||
5384 | { | ||
5385 | Eina_Strbuf *sbuf; | ||
5386 | char *str = NULL; | ||
5387 | int ch, pos = 0, pos2 = 0; | ||
5388 | |||
5389 | (void) obj; | ||
5390 | |||
5391 | if (!text) return NULL; | ||
5392 | |||
5393 | sbuf = eina_strbuf_new(); | ||
5394 | |||
5395 | for (;;) | ||
5396 | { | ||
5397 | pos = pos2; | ||
5398 | pos2 = evas_string_char_next_get(text, pos2, &ch); | ||
5399 | if ((ch <= 0) || (pos2 <= 0)) break; | ||
5400 | |||
5401 | if (ch == _NEWLINE) | ||
5402 | eina_strbuf_append(sbuf, "<br/>"); | ||
5403 | else if (ch == _TAB) | ||
5404 | eina_strbuf_append(sbuf, "<tab/>"); | ||
5405 | else if (ch == '<') | ||
5406 | eina_strbuf_append(sbuf, "<"); | ||
5407 | else if (ch == '>') | ||
5408 | eina_strbuf_append(sbuf, ">"); | ||
5409 | else if (ch == '&') | ||
5410 | eina_strbuf_append(sbuf, "&"); | ||
5411 | else if (ch == _PARAGRAPH_SEPARATOR) | ||
5412 | eina_strbuf_append(sbuf, "<ps/>"); | ||
5413 | else if (ch == _REPLACEMENT_CHAR) | ||
5414 | eina_strbuf_append(sbuf, ""); | ||
5415 | else | ||
5416 | { | ||
5417 | eina_strbuf_append_length(sbuf, text + pos, pos2 - pos); | ||
5418 | } | ||
5419 | } | ||
5420 | str = eina_strbuf_string_steal(sbuf); | ||
5421 | eina_strbuf_free(sbuf); | ||
5422 | return str; | ||
5423 | |||
5424 | } | ||
5425 | |||
5426 | /* cursors */ | ||
5427 | |||
5428 | /** | ||
5429 | * @internal | ||
5430 | * Merge the current node with the next, no need to remove PS, already | ||
5431 | * not there. | ||
5432 | * | ||
5433 | * @param o the text block object. | ||
5434 | * @param to merge into to. | ||
5435 | */ | ||
5436 | static void | ||
5437 | _evas_textblock_nodes_merge(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Text *to) | ||
5438 | { | ||
5439 | Evas_Object_Textblock_Node_Format *itr; | ||
5440 | Evas_Object_Textblock_Node_Format *pnode; | ||
5441 | Evas_Object_Textblock_Node_Text *from; | ||
5442 | const Eina_Unicode *text; | ||
5443 | int to_len, len; | ||
5444 | |||
5445 | if (!to) return; | ||
5446 | from = _NODE_TEXT(EINA_INLIST_GET(to)->next); | ||
5447 | |||
5448 | to_len = eina_ustrbuf_length_get(to->unicode); | ||
5449 | text = eina_ustrbuf_string_get(from->unicode); | ||
5450 | len = eina_ustrbuf_length_get(from->unicode); | ||
5451 | eina_ustrbuf_append_length(to->unicode, text, len); | ||
5452 | |||
5453 | itr = from->format_node; | ||
5454 | if (itr && (itr->text_node == from)) | ||
5455 | { | ||
5456 | pnode = _NODE_FORMAT(EINA_INLIST_GET(itr)->prev); | ||
5457 | if (pnode && (pnode->text_node == to)) | ||
5458 | { | ||
5459 | itr->offset += to_len - _evas_textblock_node_format_pos_get(pnode); | ||
5460 | } | ||
5461 | else | ||
5462 | { | ||
5463 | itr->offset += to_len; | ||
5464 | } | ||
5465 | } | ||
5466 | |||
5467 | while (itr && (itr->text_node == from)) | ||
5468 | { | ||
5469 | itr->text_node = to; | ||
5470 | itr = _NODE_FORMAT(EINA_INLIST_GET(itr)->next); | ||
5471 | } | ||
5472 | if (!to->format_node || (to->format_node->text_node != to)) | ||
5473 | { | ||
5474 | to->format_node = from->format_node; | ||
5475 | } | ||
5476 | |||
5477 | /* When it comes to how we handle it, merging is like removing both nodes | ||
5478 | * and creating a new one, se we need to do the needed cleanups. */ | ||
5479 | if (to->par) | ||
5480 | to->par->text_node = NULL; | ||
5481 | to->par = NULL; | ||
5482 | |||
5483 | to->is_new = EINA_TRUE; | ||
5484 | |||
5485 | _evas_textblock_cursors_set_node(o, from, to); | ||
5486 | _evas_textblock_node_text_remove(o, from); | ||
5487 | } | ||
5488 | |||
5489 | /** | ||
5490 | * @internal | ||
5491 | * Merge the current node with the next, no need to remove PS, already | ||
5492 | * not there. | ||
5493 | * | ||
5494 | * @param cur the cursor that points to the current node | ||
5495 | */ | ||
5496 | static void | ||
5497 | _evas_textblock_cursor_nodes_merge(Evas_Textblock_Cursor *cur) | ||
5498 | { | ||
5499 | Evas_Object_Textblock_Node_Text *nnode; | ||
5500 | Evas_Object_Textblock *o; | ||
5501 | int len; | ||
5502 | if (!cur) return; | ||
5503 | |||
5504 | len = eina_ustrbuf_length_get(cur->node->unicode); | ||
5505 | |||
5506 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
5507 | nnode = _NODE_TEXT(EINA_INLIST_GET(cur->node)->next); | ||
5508 | _evas_textblock_nodes_merge(o, cur->node); | ||
5509 | _evas_textblock_cursors_update_offset(cur, nnode, 0, len); | ||
5510 | _evas_textblock_cursors_set_node(o, nnode, cur->node); | ||
5511 | if (nnode == o->cursor->node) | ||
5512 | { | ||
5513 | o->cursor->node = cur->node; | ||
5514 | o->cursor->pos += len; | ||
5515 | } | ||
5516 | } | ||
5517 | |||
5518 | /** | ||
5519 | * @internal | ||
5520 | * Return the format at a specific position. | ||
5521 | * | ||
5522 | * @param cur the cursor to the position. | ||
5523 | * @return the format node at the specific position or NULL if not found. | ||
5524 | */ | ||
5525 | static Evas_Object_Textblock_Node_Format * | ||
5526 | _evas_textblock_cursor_node_format_at_pos_get(const Evas_Textblock_Cursor *cur) | ||
5527 | { | ||
5528 | Evas_Object_Textblock_Node_Format *node; | ||
5529 | Evas_Object_Textblock_Node_Format *itr; | ||
5530 | int position = 0; | ||
5531 | |||
5532 | if (!cur->node) return NULL; | ||
5533 | |||
5534 | node = cur->node->format_node; | ||
5535 | if (!node) return NULL; | ||
5536 | /* If there is no exclusive format node to this paragraph return the | ||
5537 | * previous's node */ | ||
5538 | /* Find the main format node */ | ||
5539 | EINA_INLIST_FOREACH(node, itr) | ||
5540 | { | ||
5541 | if (itr->text_node != cur->node) | ||
5542 | { | ||
5543 | return NULL; | ||
5544 | } | ||
5545 | if ((position + itr->offset) == cur->pos) | ||
5546 | { | ||
5547 | return itr; | ||
5548 | } | ||
5549 | position += itr->offset; | ||
5550 | } | ||
5551 | return NULL; | ||
5552 | } | ||
5553 | |||
5554 | /** | ||
5555 | * @internal | ||
5556 | * Return the last format node at the position of the format node n. | ||
5557 | * | ||
5558 | * @param n a format node at the position. | ||
5559 | * @return the last format node at the position of n. | ||
5560 | */ | ||
5561 | static Evas_Object_Textblock_Node_Format * | ||
5562 | _evas_textblock_node_format_last_at_off(const Evas_Object_Textblock_Node_Format *n) | ||
5563 | { | ||
5564 | const Evas_Object_Textblock_Node_Format *nnode; | ||
5565 | const Evas_Object_Textblock_Node_Text *tnode; | ||
5566 | if (!n) return NULL; | ||
5567 | nnode = n; | ||
5568 | tnode = n->text_node; | ||
5569 | do | ||
5570 | { | ||
5571 | n = nnode; | ||
5572 | nnode = _NODE_FORMAT(EINA_INLIST_GET(nnode)->next); | ||
5573 | } | ||
5574 | while (nnode && (nnode->text_node == tnode) && (nnode->offset == 0)); | ||
5575 | |||
5576 | return (Evas_Object_Textblock_Node_Format *) n; | ||
5577 | } | ||
5578 | |||
5579 | /** | ||
5580 | * @internal | ||
5581 | * Returns the visible format at a specific location. | ||
5582 | * | ||
5583 | * @param n a format at the specific position. | ||
5584 | * @return the format node at the specific position or NULL if not found. | ||
5585 | */ | ||
5586 | static Evas_Object_Textblock_Node_Format * | ||
5587 | _evas_textblock_node_visible_at_pos_get(const Evas_Object_Textblock_Node_Format *n) | ||
5588 | { | ||
5589 | const Evas_Object_Textblock_Node_Format *nnode; | ||
5590 | if (!n) return NULL; | ||
5591 | /* The visible format is the last one, because it inserts a replacement | ||
5592 | * char that advances the next formats. */ | ||
5593 | |||
5594 | nnode = n; | ||
5595 | do | ||
5596 | { | ||
5597 | n = nnode; | ||
5598 | if (n->visible) return (Evas_Object_Textblock_Node_Format *) n; | ||
5599 | nnode = _NODE_FORMAT(EINA_INLIST_GET(nnode)->next); | ||
5600 | } | ||
5601 | while (nnode && (nnode->offset == 0)); | ||
5602 | |||
5603 | return NULL; | ||
5604 | } | ||
5605 | |||
5606 | /** | ||
5607 | * @internal | ||
5608 | * Return the last format that applies to a specific cursor or at the specific | ||
5609 | * position the cursor points to. This means either a cursor at or before the | ||
5610 | * position of the cursor in the text node is returned or the previous's text | ||
5611 | * node's format node. | ||
5612 | * | ||
5613 | * @param cur the position to look at. | ||
5614 | * @return the format node found. | ||
5615 | */ | ||
5616 | static Evas_Object_Textblock_Node_Format * | ||
5617 | _evas_textblock_cursor_node_format_before_or_at_pos_get(const Evas_Textblock_Cursor *cur) | ||
5618 | { | ||
5619 | Evas_Object_Textblock_Node_Format *node, *pitr = NULL; | ||
5620 | Evas_Object_Textblock_Node_Format *itr; | ||
5621 | size_t position = 0; | ||
5622 | |||
5623 | if (!cur->node) return NULL; | ||
5624 | |||
5625 | node = cur->node->format_node; | ||
5626 | if (!node) return NULL; | ||
5627 | /* If there is no exclusive format node to this paragraph return the | ||
5628 | * previous's node */ | ||
5629 | if (node->text_node != cur->node) | ||
5630 | { | ||
5631 | return node; | ||
5632 | } | ||
5633 | else if (node->offset > cur->pos) | ||
5634 | { | ||
5635 | return _NODE_FORMAT(EINA_INLIST_GET(node)->prev); | ||
5636 | } | ||
5637 | /* Find the main format node */ | ||
5638 | pitr = _NODE_FORMAT(EINA_INLIST_GET(node)->prev); | ||
5639 | EINA_INLIST_FOREACH(node, itr) | ||
5640 | { | ||
5641 | if ((itr->text_node != cur->node) || | ||
5642 | ((position + itr->offset) > cur->pos)) | ||
5643 | { | ||
5644 | return pitr; | ||
5645 | } | ||
5646 | else if ((position + itr->offset) == cur->pos) | ||
5647 | { | ||
5648 | return itr; | ||
5649 | } | ||
5650 | pitr = itr; | ||
5651 | position += itr->offset; | ||
5652 | } | ||
5653 | return pitr; | ||
5654 | } | ||
5655 | |||
5656 | /** | ||
5657 | * @internal | ||
5658 | * Find the layout item and line that match the cursor. | ||
5659 | * | ||
5660 | * @param cur the cursor we are currently at. - NOT NULL. | ||
5661 | * @param[out] lnr the line found - not null. | ||
5662 | * @param[out] itr the item found - not null. | ||
5663 | * @return EINA_TRUE if we matched the previous format, EINA_FALSE otherwise. | ||
5664 | */ | ||
5665 | static Eina_Bool | ||
5666 | _find_layout_item_match(const Evas_Textblock_Cursor *cur, Evas_Object_Textblock_Line **lnr, Evas_Object_Textblock_Item **itr) | ||
5667 | { | ||
5668 | Evas_Textblock_Cursor cur2; | ||
5669 | Eina_Bool previous_format = EINA_FALSE; | ||
5670 | |||
5671 | cur2.obj = cur->obj; | ||
5672 | evas_textblock_cursor_copy(cur, &cur2); | ||
5673 | if (cur2.pos > 0) | ||
5674 | { | ||
5675 | cur2.pos--; | ||
5676 | } | ||
5677 | |||
5678 | if (_evas_textblock_cursor_is_at_the_end(cur) && | ||
5679 | evas_textblock_cursor_format_is_visible_get(&cur2)) | ||
5680 | { | ||
5681 | _find_layout_item_line_match(cur2.obj, cur2.node, cur2.pos, lnr, itr); | ||
5682 | previous_format = EINA_TRUE; | ||
5683 | } | ||
5684 | else | ||
5685 | { | ||
5686 | _find_layout_item_line_match(cur->obj, cur->node, cur->pos, lnr, itr); | ||
5687 | } | ||
5688 | return previous_format; | ||
5689 | } | ||
5690 | |||
5691 | EAPI Evas_Textblock_Cursor * | ||
5692 | evas_object_textblock_cursor_get(const Evas_Object *obj) | ||
5693 | { | ||
5694 | TB_HEAD_RETURN(NULL); | ||
5695 | return o->cursor; | ||
5696 | } | ||
5697 | |||
5698 | EAPI Evas_Textblock_Cursor * | ||
5699 | evas_object_textblock_cursor_new(const Evas_Object *obj) | ||
5700 | { | ||
5701 | Evas_Textblock_Cursor *cur; | ||
5702 | |||
5703 | TB_HEAD_RETURN(NULL); | ||
5704 | cur = calloc(1, sizeof(Evas_Textblock_Cursor)); | ||
5705 | cur->obj = (Evas_Object *) obj; | ||
5706 | cur->node = o->text_nodes; | ||
5707 | cur->pos = 0; | ||
5708 | |||
5709 | o->cursors = eina_list_append(o->cursors, cur); | ||
5710 | return cur; | ||
5711 | } | ||
5712 | |||
5713 | EAPI void | ||
5714 | evas_textblock_cursor_free(Evas_Textblock_Cursor *cur) | ||
5715 | { | ||
5716 | Evas_Object_Textblock *o; | ||
5717 | |||
5718 | if (!cur) return; | ||
5719 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
5720 | if (cur == o->cursor) return; | ||
5721 | o->cursors = eina_list_remove(o->cursors, cur); | ||
5722 | free(cur); | ||
5723 | } | ||
5724 | |||
5725 | EAPI Eina_Bool | ||
5726 | evas_textblock_cursor_is_format(const Evas_Textblock_Cursor *cur) | ||
5727 | { | ||
5728 | if (!cur || !cur->node) return EINA_FALSE; | ||
5729 | return (_evas_textblock_cursor_node_format_at_pos_get(cur)) ? | ||
5730 | EINA_TRUE : EINA_FALSE; | ||
5731 | } | ||
5732 | |||
5733 | EAPI const Eina_List * | ||
5734 | evas_textblock_node_format_list_get(const Evas_Object *obj, const char *anchor) | ||
5735 | { | ||
5736 | TB_HEAD_RETURN(NULL); | ||
5737 | if (!strcmp(anchor, "a")) | ||
5738 | return o->anchors_a; | ||
5739 | else if (!strcmp(anchor, "item")) | ||
5740 | return o->anchors_item; | ||
5741 | |||
5742 | return NULL; | ||
5743 | } | ||
5744 | |||
5745 | EAPI const Evas_Object_Textblock_Node_Format * | ||
5746 | evas_textblock_node_format_first_get(const Evas_Object *obj) | ||
5747 | { | ||
5748 | TB_HEAD_RETURN(NULL); | ||
5749 | return o->format_nodes; | ||
5750 | } | ||
5751 | |||
5752 | EAPI const Evas_Object_Textblock_Node_Format * | ||
5753 | evas_textblock_node_format_last_get(const Evas_Object *obj) | ||
5754 | { | ||
5755 | TB_HEAD_RETURN(NULL); | ||
5756 | if (o->format_nodes) | ||
5757 | { | ||
5758 | return _NODE_FORMAT(EINA_INLIST_GET(o->format_nodes)->last); | ||
5759 | } | ||
5760 | return NULL; | ||
5761 | } | ||
5762 | |||
5763 | EAPI const Evas_Object_Textblock_Node_Format * | ||
5764 | evas_textblock_node_format_next_get(const Evas_Object_Textblock_Node_Format *n) | ||
5765 | { | ||
5766 | return _NODE_FORMAT(EINA_INLIST_GET(n)->next); | ||
5767 | } | ||
5768 | |||
5769 | EAPI const Evas_Object_Textblock_Node_Format * | ||
5770 | evas_textblock_node_format_prev_get(const Evas_Object_Textblock_Node_Format *n) | ||
5771 | { | ||
5772 | return _NODE_FORMAT(EINA_INLIST_GET(n)->prev); | ||
5773 | } | ||
5774 | |||
5775 | EAPI void | ||
5776 | evas_textblock_node_format_remove_pair(Evas_Object *obj, | ||
5777 | Evas_Object_Textblock_Node_Format *n) | ||
5778 | { | ||
5779 | Evas_Object_Textblock_Node_Text *tnode1; | ||
5780 | Evas_Object_Textblock_Node_Format *fmt, *found_node = NULL; | ||
5781 | Eina_List *fstack = NULL; | ||
5782 | TB_HEAD(); | ||
5783 | |||
5784 | if (!n) return; | ||
5785 | |||
5786 | fmt = n; | ||
5787 | |||
5788 | do | ||
5789 | { | ||
5790 | const char *fstr = fmt->orig_format; | ||
5791 | |||
5792 | if (fmt->opener && !fmt->own_closer) | ||
5793 | { | ||
5794 | fstack = eina_list_prepend(fstack, fmt); | ||
5795 | } | ||
5796 | else if (fstr && !fmt->opener) | ||
5797 | { | ||
5798 | size_t fstr_len; | ||
5799 | fstr_len = strlen(fstr); | ||
5800 | /* Generic popper, just pop */ | ||
5801 | if (((fstr[0] == ' ') && !fstr[1]) || !fstr[0]) | ||
5802 | { | ||
5803 | fstack = eina_list_remove_list(fstack, fstack); | ||
5804 | if (!fstack) | ||
5805 | { | ||
5806 | found_node = fmt; | ||
5807 | goto found; | ||
5808 | } | ||
5809 | } | ||
5810 | /* Find the matching format and pop it, if the matching format | ||
5811 | * is out format, i.e the last one, pop and break. */ | ||
5812 | else | ||
5813 | { | ||
5814 | Eina_List *i; | ||
5815 | Evas_Object_Textblock_Node_Format *fnode; | ||
5816 | EINA_LIST_FOREACH(fstack, i, fnode) | ||
5817 | { | ||
5818 | if (_FORMAT_IS_CLOSER_OF( | ||
5819 | fnode->orig_format, fstr, fstr_len)) | ||
5820 | { | ||
5821 | /* Last one, this is our item! */ | ||
5822 | if (!eina_list_next(i)) | ||
5823 | { | ||
5824 | found_node = fmt; | ||
5825 | goto found; | ||
5826 | } | ||
5827 | fstack = eina_list_remove_list(fstack, i); | ||
5828 | break; | ||
5829 | } | ||
5830 | } | ||
5831 | } | ||
5832 | } | ||
5833 | |||
5834 | fmt = _NODE_FORMAT(EINA_INLIST_GET(fmt)->next); | ||
5835 | } | ||
5836 | while (fmt && fstack); | ||
5837 | |||
5838 | found: | ||
5839 | |||
5840 | fstack = eina_list_free(fstack); | ||
5841 | |||
5842 | if (n->visible) | ||
5843 | { | ||
5844 | size_t ind = _evas_textblock_node_format_pos_get(n); | ||
5845 | const char *format = n->format; | ||
5846 | Evas_Textblock_Cursor cur; | ||
5847 | cur.obj = obj; | ||
5848 | |||
5849 | eina_ustrbuf_remove(n->text_node->unicode, ind, ind + 1); | ||
5850 | if (format && _IS_PARAGRAPH_SEPARATOR(o, format)) | ||
5851 | { | ||
5852 | evas_textblock_cursor_at_format_set(&cur, n); | ||
5853 | _evas_textblock_cursor_nodes_merge(&cur); | ||
5854 | } | ||
5855 | _evas_textblock_cursors_update_offset(&cur, n->text_node, ind, -1); | ||
5856 | } | ||
5857 | tnode1 = n->text_node; | ||
5858 | _evas_textblock_node_format_remove(o, n, 0); | ||
5859 | if (found_node && (found_node != n)) | ||
5860 | { | ||
5861 | Evas_Object_Textblock_Node_Text *tnode2; | ||
5862 | tnode2 = found_node->text_node; | ||
5863 | /* found_node can never be visible! (it's the closing format) */ | ||
5864 | _evas_textblock_node_format_remove(o, found_node, 0); | ||
5865 | |||
5866 | /* FIXME: Should be unified in the layout, for example, added to a list | ||
5867 | * that checks this kind of removals. But until then, this is very fast | ||
5868 | * and works. */ | ||
5869 | /* Mark all the text nodes in between the removed formats as dirty. */ | ||
5870 | while (tnode1) | ||
5871 | { | ||
5872 | tnode1->dirty = EINA_TRUE; | ||
5873 | if (tnode1 == tnode2) | ||
5874 | break; | ||
5875 | tnode1 = | ||
5876 | _NODE_TEXT(EINA_INLIST_GET(tnode1)->next); | ||
5877 | } | ||
5878 | } | ||
5879 | |||
5880 | _evas_textblock_changed(o, obj); | ||
5881 | } | ||
5882 | |||
5883 | EAPI void | ||
5884 | evas_textblock_cursor_paragraph_first(Evas_Textblock_Cursor *cur) | ||
5885 | { | ||
5886 | Evas_Object_Textblock *o; | ||
5887 | if (!cur) return; | ||
5888 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
5889 | cur->node = o->text_nodes; | ||
5890 | cur->pos = 0; | ||
5891 | |||
5892 | } | ||
5893 | |||
5894 | EAPI void | ||
5895 | evas_textblock_cursor_paragraph_last(Evas_Textblock_Cursor *cur) | ||
5896 | { | ||
5897 | Evas_Object_Textblock *o; | ||
5898 | Evas_Object_Textblock_Node_Text *node; | ||
5899 | |||
5900 | if (!cur) return; | ||
5901 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
5902 | node = o->text_nodes; | ||
5903 | if (node) | ||
5904 | { | ||
5905 | node = _NODE_TEXT(EINA_INLIST_GET(node)->last); | ||
5906 | cur->node = node; | ||
5907 | cur->pos = 0; | ||
5908 | |||
5909 | evas_textblock_cursor_paragraph_char_last(cur); | ||
5910 | } | ||
5911 | else | ||
5912 | { | ||
5913 | cur->node = NULL; | ||
5914 | cur->pos = 0; | ||
5915 | |||
5916 | } | ||
5917 | } | ||
5918 | |||
5919 | EAPI Eina_Bool | ||
5920 | evas_textblock_cursor_paragraph_next(Evas_Textblock_Cursor *cur) | ||
5921 | { | ||
5922 | if (!cur) return EINA_FALSE; | ||
5923 | if (!cur->node) return EINA_FALSE; | ||
5924 | /* If there is a current text node, return the next text node (if exists) | ||
5925 | * otherwise, just return False. */ | ||
5926 | if (cur->node) | ||
5927 | { | ||
5928 | Evas_Object_Textblock_Node_Text *nnode; | ||
5929 | nnode = _NODE_TEXT(EINA_INLIST_GET(cur->node)->next); | ||
5930 | if (nnode) | ||
5931 | { | ||
5932 | cur->node = nnode; | ||
5933 | cur->pos = 0; | ||
5934 | |||
5935 | return EINA_TRUE; | ||
5936 | } | ||
5937 | } | ||
5938 | return EINA_FALSE; | ||
5939 | } | ||
5940 | |||
5941 | EAPI Eina_Bool | ||
5942 | evas_textblock_cursor_paragraph_prev(Evas_Textblock_Cursor *cur) | ||
5943 | { | ||
5944 | Evas_Object_Textblock_Node_Text *node; | ||
5945 | if (!cur) return EINA_FALSE; | ||
5946 | if (!cur->node) return EINA_FALSE; | ||
5947 | /* If the current node is a text node, just get the prev if any, | ||
5948 | * if it's a format, get the current text node out of the format and return | ||
5949 | * the prev text node if any. */ | ||
5950 | node = cur->node; | ||
5951 | /* If there is a current text node, return the prev text node | ||
5952 | * (if exists) otherwise, just return False. */ | ||
5953 | if (node) | ||
5954 | { | ||
5955 | Evas_Object_Textblock_Node_Text *pnode; | ||
5956 | pnode = _NODE_TEXT(EINA_INLIST_GET(cur->node)->prev); | ||
5957 | if (pnode) | ||
5958 | { | ||
5959 | cur->node = pnode; | ||
5960 | evas_textblock_cursor_paragraph_char_last(cur); | ||
5961 | return EINA_TRUE; | ||
5962 | } | ||
5963 | } | ||
5964 | return EINA_FALSE; | ||
5965 | } | ||
5966 | |||
5967 | EAPI void | ||
5968 | evas_textblock_cursor_set_at_format(Evas_Textblock_Cursor *cur, const Evas_Object_Textblock_Node_Format *n) | ||
5969 | { | ||
5970 | evas_textblock_cursor_at_format_set(cur, n); | ||
5971 | } | ||
5972 | |||
5973 | EAPI Eina_Bool | ||
5974 | evas_textblock_cursor_format_next(Evas_Textblock_Cursor *cur) | ||
5975 | { | ||
5976 | Evas_Object_Textblock_Node_Format *node; | ||
5977 | |||
5978 | if (!cur) return EINA_FALSE; | ||
5979 | if (!cur->node) return EINA_FALSE; | ||
5980 | /* If the current node is a format node, just get the next if any, | ||
5981 | * if it's a text, get the current format node out of the text and return | ||
5982 | * the next format node if any. */ | ||
5983 | node = _evas_textblock_cursor_node_format_before_or_at_pos_get(cur); | ||
5984 | node = _evas_textblock_node_format_last_at_off(node); | ||
5985 | if (!node) | ||
5986 | { | ||
5987 | if (cur->node->format_node) | ||
5988 | { | ||
5989 | cur->pos = _evas_textblock_node_format_pos_get(node); | ||
5990 | return EINA_TRUE; | ||
5991 | } | ||
5992 | } | ||
5993 | /* If there is a current text node, return the next format node (if exists) | ||
5994 | * otherwise, just return False. */ | ||
5995 | else | ||
5996 | { | ||
5997 | Evas_Object_Textblock_Node_Format *nnode; | ||
5998 | nnode = _NODE_FORMAT(EINA_INLIST_GET(node)->next); | ||
5999 | if (nnode) | ||
6000 | { | ||
6001 | cur->node = nnode->text_node; | ||
6002 | cur->pos = _evas_textblock_node_format_pos_get(nnode); | ||
6003 | |||
6004 | return EINA_TRUE; | ||
6005 | } | ||
6006 | } | ||
6007 | return EINA_FALSE; | ||
6008 | } | ||
6009 | |||
6010 | EAPI Eina_Bool | ||
6011 | evas_textblock_cursor_format_prev(Evas_Textblock_Cursor *cur) | ||
6012 | { | ||
6013 | const Evas_Object_Textblock_Node_Format *node; | ||
6014 | if (!cur) return EINA_FALSE; | ||
6015 | if (!cur->node) return EINA_FALSE; | ||
6016 | node = evas_textblock_cursor_format_get(cur); | ||
6017 | if (!node) | ||
6018 | { | ||
6019 | node = _evas_textblock_cursor_node_format_before_or_at_pos_get(cur); | ||
6020 | if (node) | ||
6021 | { | ||
6022 | cur->node = node->text_node; | ||
6023 | cur->pos = _evas_textblock_node_format_pos_get(node); | ||
6024 | |||
6025 | return EINA_TRUE; | ||
6026 | } | ||
6027 | } | ||
6028 | /* If there is a current text node, return the next text node (if exists) | ||
6029 | * otherwise, just return False. */ | ||
6030 | if (node) | ||
6031 | { | ||
6032 | Evas_Object_Textblock_Node_Format *pnode; | ||
6033 | pnode = _NODE_FORMAT(EINA_INLIST_GET(node)->prev); | ||
6034 | if (pnode) | ||
6035 | { | ||
6036 | cur->node = pnode->text_node; | ||
6037 | cur->pos = _evas_textblock_node_format_pos_get(pnode); | ||
6038 | |||
6039 | return EINA_TRUE; | ||
6040 | } | ||
6041 | } | ||
6042 | return EINA_FALSE; | ||
6043 | } | ||
6044 | |||
6045 | #ifdef HAVE_LINEBREAK | ||
6046 | |||
6047 | /* BREAK_AFTER: true if we can break after the current char. | ||
6048 | * Both macros assume str[i] is not the terminating nul */ | ||
6049 | #define BREAK_AFTER(i) \ | ||
6050 | (breaks[i] == WORDBREAK_BREAK) | ||
6051 | |||
6052 | #else | ||
6053 | |||
6054 | #define BREAK_AFTER(i) \ | ||
6055 | ((!text[i + 1]) || \ | ||
6056 | (_is_white(text[i]) && !_is_white(text[i + 1])) || \ | ||
6057 | (!_is_white(text[i]) && _is_white(text[i + 1]))) | ||
6058 | |||
6059 | #endif | ||
6060 | |||
6061 | EAPI Eina_Bool | ||
6062 | evas_textblock_cursor_word_start(Evas_Textblock_Cursor *cur) | ||
6063 | { | ||
6064 | const Eina_Unicode *text; | ||
6065 | size_t i; | ||
6066 | #ifdef HAVE_LINEBREAK | ||
6067 | char *breaks; | ||
6068 | #endif | ||
6069 | |||
6070 | if (!cur) return EINA_FALSE; | ||
6071 | if (!cur->node) return EINA_FALSE; | ||
6072 | |||
6073 | text = eina_ustrbuf_string_get(cur->node->unicode); | ||
6074 | |||
6075 | #ifdef HAVE_LINEBREAK | ||
6076 | { | ||
6077 | const char *lang = ""; /* FIXME: get lang */ | ||
6078 | size_t len = eina_ustrbuf_length_get(cur->node->unicode); | ||
6079 | breaks = malloc(len); | ||
6080 | set_wordbreaks_utf32((const utf32_t *) text, len, lang, breaks); | ||
6081 | } | ||
6082 | #endif | ||
6083 | |||
6084 | i = cur->pos; | ||
6085 | |||
6086 | /* Skip the first one. This ensures we don't point to the nul, and also | ||
6087 | * we just don't care about it anyway. */ | ||
6088 | if (i > 0) i--; | ||
6089 | |||
6090 | for ( ; i > 0 ; i--) | ||
6091 | { | ||
6092 | if (BREAK_AFTER(i)) | ||
6093 | { | ||
6094 | /* Advance to the current char */ | ||
6095 | i++; | ||
6096 | break; | ||
6097 | } | ||
6098 | } | ||
6099 | |||
6100 | cur->pos = i; | ||
6101 | |||
6102 | #ifdef HAVE_LINEBREAK | ||
6103 | free(breaks); | ||
6104 | #endif | ||
6105 | return EINA_TRUE; | ||
6106 | } | ||
6107 | |||
6108 | EAPI Eina_Bool | ||
6109 | evas_textblock_cursor_word_end(Evas_Textblock_Cursor *cur) | ||
6110 | { | ||
6111 | const Eina_Unicode *text; | ||
6112 | size_t i; | ||
6113 | #ifdef HAVE_LINEBREAK | ||
6114 | char *breaks; | ||
6115 | #endif | ||
6116 | |||
6117 | if (!cur) return EINA_FALSE; | ||
6118 | if (!cur->node) return EINA_FALSE; | ||
6119 | |||
6120 | text = eina_ustrbuf_string_get(cur->node->unicode); | ||
6121 | |||
6122 | #ifdef HAVE_LINEBREAK | ||
6123 | { | ||
6124 | const char *lang = ""; /* FIXME: get lang */ | ||
6125 | size_t len = eina_ustrbuf_length_get(cur->node->unicode); | ||
6126 | breaks = malloc(len); | ||
6127 | set_wordbreaks_utf32((const utf32_t *) text, len, lang, breaks); | ||
6128 | } | ||
6129 | #endif | ||
6130 | |||
6131 | i = cur->pos; | ||
6132 | |||
6133 | for ( ; text[i] ; i++) | ||
6134 | { | ||
6135 | if (BREAK_AFTER(i)) | ||
6136 | { | ||
6137 | /* This is the one to break after. */ | ||
6138 | break; | ||
6139 | } | ||
6140 | } | ||
6141 | |||
6142 | cur->pos = i; | ||
6143 | |||
6144 | #ifdef HAVE_LINEBREAK | ||
6145 | free(breaks); | ||
6146 | #endif | ||
6147 | return EINA_TRUE;; | ||
6148 | } | ||
6149 | |||
6150 | EAPI Eina_Bool | ||
6151 | evas_textblock_cursor_char_next(Evas_Textblock_Cursor *cur) | ||
6152 | { | ||
6153 | int ind; | ||
6154 | const Eina_Unicode *text; | ||
6155 | |||
6156 | if (!cur) return EINA_FALSE; | ||
6157 | if (!cur->node) return EINA_FALSE; | ||
6158 | |||
6159 | ind = cur->pos; | ||
6160 | text = eina_ustrbuf_string_get(cur->node->unicode); | ||
6161 | if (text[ind]) ind++; | ||
6162 | /* Only allow pointing a null if it's the last paragraph. | ||
6163 | * because we don't have a PS there. */ | ||
6164 | if (text[ind]) | ||
6165 | { | ||
6166 | cur->pos = ind; | ||
6167 | return EINA_TRUE; | ||
6168 | } | ||
6169 | else | ||
6170 | { | ||
6171 | if (!evas_textblock_cursor_paragraph_next(cur)) | ||
6172 | { | ||
6173 | /* If we already were at the end, that means we don't have | ||
6174 | * where to go next we should return FALSE */ | ||
6175 | if (cur->pos == (size_t) ind) | ||
6176 | return EINA_FALSE; | ||
6177 | |||
6178 | cur->pos = ind; | ||
6179 | return EINA_TRUE; | ||
6180 | } | ||
6181 | else | ||
6182 | { | ||
6183 | return EINA_TRUE; | ||
6184 | } | ||
6185 | } | ||
6186 | } | ||
6187 | |||
6188 | EAPI Eina_Bool | ||
6189 | evas_textblock_cursor_char_prev(Evas_Textblock_Cursor *cur) | ||
6190 | { | ||
6191 | if (!cur) return EINA_FALSE; | ||
6192 | if (!cur->node) return EINA_FALSE; | ||
6193 | |||
6194 | if (cur->pos != 0) | ||
6195 | { | ||
6196 | cur->pos--; | ||
6197 | return EINA_TRUE; | ||
6198 | } | ||
6199 | return evas_textblock_cursor_paragraph_prev(cur); | ||
6200 | } | ||
6201 | |||
6202 | EAPI void | ||
6203 | evas_textblock_cursor_paragraph_char_first(Evas_Textblock_Cursor *cur) | ||
6204 | { | ||
6205 | if (!cur) return; | ||
6206 | cur->pos = 0; | ||
6207 | |||
6208 | } | ||
6209 | |||
6210 | EAPI void | ||
6211 | evas_textblock_cursor_paragraph_char_last(Evas_Textblock_Cursor *cur) | ||
6212 | { | ||
6213 | int ind; | ||
6214 | |||
6215 | if (!cur) return; | ||
6216 | if (!cur->node) return; | ||
6217 | ind = eina_ustrbuf_length_get(cur->node->unicode); | ||
6218 | /* If it's not the last paragraph, go back one, because we want to point | ||
6219 | * to the PS, not the NULL */ | ||
6220 | if (EINA_INLIST_GET(cur->node)->next) | ||
6221 | ind--; | ||
6222 | |||
6223 | if (ind >= 0) | ||
6224 | cur->pos = ind; | ||
6225 | else | ||
6226 | cur->pos = 0; | ||
6227 | |||
6228 | } | ||
6229 | |||
6230 | EAPI void | ||
6231 | evas_textblock_cursor_line_char_first(Evas_Textblock_Cursor *cur) | ||
6232 | { | ||
6233 | Evas_Object_Textblock *o; | ||
6234 | Evas_Object_Textblock_Line *ln = NULL; | ||
6235 | Evas_Object_Textblock_Item *it = NULL; | ||
6236 | |||
6237 | if (!cur) return; | ||
6238 | if (!cur->node) return; | ||
6239 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
6240 | if (!o->formatted.valid) _relayout(cur->obj); | ||
6241 | |||
6242 | _find_layout_item_match(cur, &ln, &it); | ||
6243 | |||
6244 | if (!ln) return; | ||
6245 | if (ln->items) | ||
6246 | { | ||
6247 | Evas_Object_Textblock_Item *i; | ||
6248 | it = ln->items; | ||
6249 | EINA_INLIST_FOREACH(ln->items, i) | ||
6250 | { | ||
6251 | if (it->text_pos > i->text_pos) | ||
6252 | { | ||
6253 | it = i; | ||
6254 | } | ||
6255 | } | ||
6256 | } | ||
6257 | if (it) | ||
6258 | { | ||
6259 | cur->pos = it->text_pos; | ||
6260 | cur->node = it->text_node; | ||
6261 | } | ||
6262 | } | ||
6263 | |||
6264 | EAPI void | ||
6265 | evas_textblock_cursor_line_char_last(Evas_Textblock_Cursor *cur) | ||
6266 | { | ||
6267 | Evas_Object_Textblock *o; | ||
6268 | Evas_Object_Textblock_Line *ln = NULL; | ||
6269 | Evas_Object_Textblock_Item *it = NULL; | ||
6270 | |||
6271 | if (!cur) return; | ||
6272 | if (!cur->node) return; | ||
6273 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
6274 | if (!o->formatted.valid) _relayout(cur->obj); | ||
6275 | |||
6276 | _find_layout_item_match(cur, &ln, &it); | ||
6277 | |||
6278 | if (!ln) return; | ||
6279 | if (ln->items) | ||
6280 | { | ||
6281 | Evas_Object_Textblock_Item *i; | ||
6282 | it = ln->items; | ||
6283 | EINA_INLIST_FOREACH(ln->items, i) | ||
6284 | { | ||
6285 | if (it->text_pos < i->text_pos) | ||
6286 | { | ||
6287 | it = i; | ||
6288 | } | ||
6289 | } | ||
6290 | } | ||
6291 | if (it) | ||
6292 | { | ||
6293 | size_t ind; | ||
6294 | |||
6295 | cur->node = it->text_node; | ||
6296 | cur->pos = it->text_pos; | ||
6297 | if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
6298 | { | ||
6299 | ind = _ITEM_TEXT(it)->text_props.text_len - 1; | ||
6300 | if (!IS_AT_END(_ITEM_TEXT(it), ind)) ind++; | ||
6301 | cur->pos += ind; | ||
6302 | } | ||
6303 | else if (!EINA_INLIST_GET(ln)->next && !EINA_INLIST_GET(ln->par)->next) | ||
6304 | { | ||
6305 | cur->pos++; | ||
6306 | } | ||
6307 | } | ||
6308 | } | ||
6309 | |||
6310 | /** | ||
6311 | * @internal | ||
6312 | * checks if a format (as a string) is visible/changes format and sets the | ||
6313 | * fnode properties accordingly. | ||
6314 | * | ||
6315 | * @param fnode the format node | ||
6316 | * @param s the string. | ||
6317 | */ | ||
6318 | static void | ||
6319 | _evas_textblock_format_is_visible(Evas_Object_Textblock_Node_Format *fnode, | ||
6320 | const char *s) | ||
6321 | { | ||
6322 | const char *item; | ||
6323 | Eina_Bool is_opener = EINA_TRUE; | ||
6324 | |||
6325 | fnode->visible = fnode->format_change = EINA_FALSE; | ||
6326 | fnode->anchor = ANCHOR_NONE; | ||
6327 | if (!s) return; | ||
6328 | |||
6329 | if (!fnode->own_closer) | ||
6330 | { | ||
6331 | is_opener = fnode->opener; | ||
6332 | fnode->format_change = EINA_TRUE; | ||
6333 | } | ||
6334 | |||
6335 | while ((item = _format_parse(&s))) | ||
6336 | { | ||
6337 | int itlen = s - item; | ||
6338 | /* We care about all of the formats even after a - except for | ||
6339 | * item which we don't care after a - because it's just a standard | ||
6340 | * closing */ | ||
6341 | if ((!strncmp(item, "\n", itlen) || !strncmp(item, "\\n", itlen)) || | ||
6342 | (!strncmp(item, "\t", itlen) || !strncmp(item, "\\t", itlen)) || | ||
6343 | (!strncmp(item, "br", itlen) && (itlen >= 2)) || | ||
6344 | (!strncmp(item, "tab", itlen) && (itlen >= 3)) || | ||
6345 | (!strncmp(item, "ps", itlen) && (itlen >= 2)) || | ||
6346 | (!strncmp(item, "item", itlen) && (itlen >= 4) && is_opener)) | ||
6347 | { | ||
6348 | fnode->visible = EINA_TRUE; | ||
6349 | } | ||
6350 | |||
6351 | if (is_opener && !strncmp(item, "a", itlen)) | ||
6352 | { | ||
6353 | fnode->anchor = ANCHOR_A; | ||
6354 | } | ||
6355 | else if (is_opener && !strncmp(item, "item", itlen) && (itlen >= 4)) | ||
6356 | { | ||
6357 | fnode->anchor = ANCHOR_ITEM; | ||
6358 | } | ||
6359 | } | ||
6360 | } | ||
6361 | |||
6362 | /** | ||
6363 | * Sets the cursor to the position of where the fmt points to. | ||
6364 | * | ||
6365 | * @param cur the cursor to update. | ||
6366 | * @param fmt the format to set according to. | ||
6367 | * @return nothing. | ||
6368 | */ | ||
6369 | static void __UNUSED__ | ||
6370 | _evas_textblock_cursor_node_text_at_format(Evas_Textblock_Cursor *cur, Evas_Object_Textblock_Node_Format *fmt) | ||
6371 | { | ||
6372 | Evas_Object_Textblock_Node_Text *text; | ||
6373 | Evas_Object_Textblock_Node_Format *base_format; | ||
6374 | Evas_Object_Textblock_Node_Format *itr; | ||
6375 | size_t position = 0; | ||
6376 | |||
6377 | if (!cur || !fmt) return; | ||
6378 | /* Find the main format node */ | ||
6379 | text = fmt->text_node; | ||
6380 | cur->node = text; | ||
6381 | base_format = text->format_node; | ||
6382 | EINA_INLIST_FOREACH(base_format, itr) | ||
6383 | { | ||
6384 | if (itr == fmt) | ||
6385 | { | ||
6386 | break; | ||
6387 | } | ||
6388 | position += itr->offset; | ||
6389 | } | ||
6390 | cur->pos = position; | ||
6391 | |||
6392 | } | ||
6393 | |||
6394 | |||
6395 | /** | ||
6396 | * @internal | ||
6397 | * Remove pairs of + and - formats and also remove formats without + or - | ||
6398 | * i.e formats that pair to themselves. Only removes invisible formats | ||
6399 | * that pair themselves, if you want to remove invisible formats that pair | ||
6400 | * themselves, please first change fmt->visible to EINA_FALSE. | ||
6401 | * | ||
6402 | * @param o the textblock object. | ||
6403 | * @param fmt the current format. | ||
6404 | */ | ||
6405 | static void | ||
6406 | _evas_textblock_node_format_remove_matching(Evas_Object_Textblock *o, | ||
6407 | Evas_Object_Textblock_Node_Format *fmt) | ||
6408 | { | ||
6409 | Evas_Object_Textblock_Node_Text *tnode; | ||
6410 | Eina_List *formats = NULL; | ||
6411 | size_t offset = 0; | ||
6412 | |||
6413 | if (!fmt) return; | ||
6414 | |||
6415 | tnode = fmt->text_node; | ||
6416 | |||
6417 | do | ||
6418 | { | ||
6419 | Evas_Object_Textblock_Node_Format *nnode; | ||
6420 | const char *fstr = fmt->orig_format; | ||
6421 | |||
6422 | nnode = _NODE_FORMAT(EINA_INLIST_GET(fmt)->next); | ||
6423 | if (nnode) | ||
6424 | { | ||
6425 | offset = nnode->offset; | ||
6426 | } | ||
6427 | |||
6428 | |||
6429 | if (fmt->opener && !fmt->own_closer) | ||
6430 | { | ||
6431 | formats = eina_list_prepend(formats, fmt); | ||
6432 | } | ||
6433 | else if (fstr && !fmt->opener) | ||
6434 | { | ||
6435 | Evas_Object_Textblock_Node_Format *fnode; | ||
6436 | size_t fstr_len; | ||
6437 | fstr_len = strlen(fstr); | ||
6438 | /* Generic popper, just pop (if there's anything to pop). */ | ||
6439 | if (formats && (((fstr[0] == ' ') && !fstr[1]) || !fstr[0])) | ||
6440 | { | ||
6441 | fnode = eina_list_data_get(formats); | ||
6442 | formats = eina_list_remove_list(formats, formats); | ||
6443 | _evas_textblock_node_format_remove(o, fnode, 0); | ||
6444 | _evas_textblock_node_format_remove(o, fmt, 0); | ||
6445 | } | ||
6446 | /* Find the matching format and pop it, if the matching format | ||
6447 | * is our format, i.e the last one, pop and break. */ | ||
6448 | else | ||
6449 | { | ||
6450 | Eina_List *i, *next; | ||
6451 | EINA_LIST_FOREACH_SAFE(formats, i, next, fnode) | ||
6452 | { | ||
6453 | if (_FORMAT_IS_CLOSER_OF( | ||
6454 | fnode->orig_format, fstr, fstr_len)) | ||
6455 | { | ||
6456 | fnode = eina_list_data_get(i); | ||
6457 | formats = eina_list_remove_list(formats, i); | ||
6458 | _evas_textblock_node_format_remove(o, fnode, 0); | ||
6459 | _evas_textblock_node_format_remove(o, fmt, 0); | ||
6460 | break; | ||
6461 | } | ||
6462 | } | ||
6463 | } | ||
6464 | } | ||
6465 | else if (!fmt->visible) | ||
6466 | { | ||
6467 | _evas_textblock_node_format_remove(o, fmt, 0); | ||
6468 | } | ||
6469 | fmt = nnode; | ||
6470 | } | ||
6471 | while (fmt && (offset == 0) && (fmt->text_node == tnode)); | ||
6472 | eina_list_free(formats); | ||
6473 | } | ||
6474 | /** | ||
6475 | * @internal | ||
6476 | * Add the offset (may be negative) to the first node after fmt which is | ||
6477 | * pointing to the text node tnode or to o->format_nodes if fmt is null | ||
6478 | * and it points to tnode. | ||
6479 | * | ||
6480 | * @param o the textblock object. | ||
6481 | * @param tnode the text node the format should point to. | ||
6482 | * @param fmt the current format. | ||
6483 | * @param offset the offest to add (may be negative). | ||
6484 | */ | ||
6485 | static void | ||
6486 | _evas_textblock_node_format_adjust_offset(Evas_Object_Textblock *o, | ||
6487 | Evas_Object_Textblock_Node_Text *tnode, | ||
6488 | Evas_Object_Textblock_Node_Format *fmt, int offset) | ||
6489 | { | ||
6490 | if (fmt) | ||
6491 | { | ||
6492 | fmt = _NODE_FORMAT(EINA_INLIST_GET(fmt)->next); | ||
6493 | } | ||
6494 | else | ||
6495 | { | ||
6496 | fmt = o->format_nodes; | ||
6497 | } | ||
6498 | if (fmt && (tnode == fmt->text_node)) | ||
6499 | { | ||
6500 | fmt->offset += offset; | ||
6501 | } | ||
6502 | } | ||
6503 | |||
6504 | /** | ||
6505 | * @internal | ||
6506 | * Removes a format node updating the offset of the next format node and the | ||
6507 | * text nodes pointing to this node. | ||
6508 | * | ||
6509 | * @param o the textblock object. | ||
6510 | * @param n the fromat node to remove | ||
6511 | */ | ||
6512 | static void | ||
6513 | _evas_textblock_node_format_remove(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Format *n, int visible_adjustment) | ||
6514 | { | ||
6515 | /* Update the text nodes about the change */ | ||
6516 | { | ||
6517 | Evas_Object_Textblock_Node_Format *nnode; | ||
6518 | nnode = _NODE_FORMAT(EINA_INLIST_GET(n)->next); | ||
6519 | /* If there's a next node that belongs to the same text node | ||
6520 | * and the curret node was the main one, advance the format node */ | ||
6521 | if (nnode && (nnode->text_node == n->text_node)) | ||
6522 | { | ||
6523 | if (nnode->text_node->format_node == n) | ||
6524 | { | ||
6525 | nnode->text_node->format_node = nnode; | ||
6526 | } | ||
6527 | } | ||
6528 | else | ||
6529 | { | ||
6530 | Evas_Object_Textblock_Node_Text *tnode; | ||
6531 | /* If there's no next one update the text nodes */ | ||
6532 | nnode = _NODE_FORMAT(EINA_INLIST_GET(n)->prev); | ||
6533 | tnode = n->text_node; | ||
6534 | /* Even if it's not the current text_node's main node | ||
6535 | * it can still be the next's. */ | ||
6536 | if (tnode && (tnode->format_node != n)) | ||
6537 | { | ||
6538 | tnode = _NODE_TEXT(EINA_INLIST_GET(tnode)->next); | ||
6539 | } | ||
6540 | while (tnode && (tnode->format_node == n)) | ||
6541 | { | ||
6542 | tnode->format_node = nnode; | ||
6543 | tnode = _NODE_TEXT(EINA_INLIST_GET(tnode)->next); | ||
6544 | } | ||
6545 | } | ||
6546 | } | ||
6547 | _evas_textblock_node_format_adjust_offset(o, n->text_node, n, | ||
6548 | n->offset - visible_adjustment); | ||
6549 | |||
6550 | o->format_nodes = _NODE_FORMAT(eina_inlist_remove( | ||
6551 | EINA_INLIST_GET(o->format_nodes), EINA_INLIST_GET(n))); | ||
6552 | _evas_textblock_node_format_free(o, n); | ||
6553 | } | ||
6554 | |||
6555 | /** | ||
6556 | * @internal | ||
6557 | * Sets all the offsets of the format nodes between start and end in the text | ||
6558 | * node n to 0 and sets visibility to EINA_FALSE. | ||
6559 | * If end == -1 end means the end of the string. | ||
6560 | * Assumes there is a prev node or the current node will be preserved. | ||
6561 | * | ||
6562 | * @param n the text node the positinos refer to. | ||
6563 | * @param start the start of where to delete from. | ||
6564 | * @param end the end of the section to delete, if end == -1 it means the end of the string. | ||
6565 | * @returns #EINA_TRUE if removed a PS, false otherwise. | ||
6566 | */ | ||
6567 | static Eina_Bool | ||
6568 | _evas_textblock_node_text_adjust_offsets_to_start(Evas_Object_Textblock *o, | ||
6569 | Evas_Object_Textblock_Node_Text *n, size_t start, int end) | ||
6570 | { | ||
6571 | Evas_Object_Textblock_Node_Format *last_node, *itr; | ||
6572 | Evas_Object_Textblock_Node_Text *new_node; | ||
6573 | int use_end = 1; | ||
6574 | int delta = 0; | ||
6575 | int first = 1; | ||
6576 | int update_format_node; | ||
6577 | size_t pos = 0; | ||
6578 | int orig_end; | ||
6579 | |||
6580 | itr = n->format_node; | ||
6581 | if (!itr || (itr->text_node != n)) return EINA_FALSE; | ||
6582 | |||
6583 | orig_end = end; | ||
6584 | if ((end < 0) || ((size_t) end == eina_ustrbuf_length_get(n->unicode))) | ||
6585 | { | ||
6586 | use_end = 0; | ||
6587 | } | ||
6588 | else if (end > 0) | ||
6589 | { | ||
6590 | /* We don't want the last one */ | ||
6591 | end--; | ||
6592 | } | ||
6593 | |||
6594 | /* If we are not removing the text node, all should stay in this text | ||
6595 | * node, otherwise, everything should move to the previous node */ | ||
6596 | if ((start == 0) && !use_end) | ||
6597 | { | ||
6598 | new_node = _NODE_TEXT(EINA_INLIST_GET(n)->prev); | ||
6599 | if (!new_node) | ||
6600 | { | ||
6601 | new_node = n; | ||
6602 | } | ||
6603 | } | ||
6604 | else | ||
6605 | { | ||
6606 | new_node = n; | ||
6607 | } | ||
6608 | |||
6609 | /* Find the first node after start */ | ||
6610 | while (itr && (itr->text_node == n)) | ||
6611 | { | ||
6612 | pos += itr->offset; | ||
6613 | if (pos >= start) | ||
6614 | { | ||
6615 | break; | ||
6616 | } | ||
6617 | itr = _NODE_FORMAT(EINA_INLIST_GET(itr)->next); | ||
6618 | } | ||
6619 | |||
6620 | if (!itr || (itr->text_node != n)) | ||
6621 | { | ||
6622 | return EINA_FALSE; | ||
6623 | } | ||
6624 | |||
6625 | update_format_node = ((itr == n->format_node) && (new_node != n)); | ||
6626 | delta = orig_end - pos; | ||
6627 | itr->offset -= pos - start; | ||
6628 | |||
6629 | while (itr && (itr->text_node == n)) | ||
6630 | { | ||
6631 | last_node = itr; | ||
6632 | itr = _NODE_FORMAT(EINA_INLIST_GET(itr)->next); | ||
6633 | |||
6634 | if (!first) | ||
6635 | { | ||
6636 | pos += last_node->offset; | ||
6637 | } | ||
6638 | |||
6639 | /* start is negative when this gets relevant */ | ||
6640 | if (use_end && (pos > (size_t) end)) | ||
6641 | { | ||
6642 | last_node->offset -= delta; | ||
6643 | break; | ||
6644 | } | ||
6645 | |||
6646 | delta = orig_end - pos; | ||
6647 | if (!first) | ||
6648 | { | ||
6649 | last_node->offset = 0; | ||
6650 | } | ||
6651 | else | ||
6652 | { | ||
6653 | first = 0; | ||
6654 | } | ||
6655 | last_node->visible = EINA_FALSE; | ||
6656 | |||
6657 | if (!itr || (itr && (itr->text_node != n))) | ||
6658 | { | ||
6659 | /* Remove the PS, and return since it's the end of the node */ | ||
6660 | if (_IS_PARAGRAPH_SEPARATOR(o, last_node->format)) | ||
6661 | { | ||
6662 | _evas_textblock_node_format_remove(o, last_node, 0); | ||
6663 | return EINA_TRUE; | ||
6664 | } | ||
6665 | |||
6666 | } | ||
6667 | last_node->text_node = new_node; | ||
6668 | if (update_format_node) | ||
6669 | { | ||
6670 | n->format_node = last_node; | ||
6671 | } | ||
6672 | } | ||
6673 | |||
6674 | return EINA_FALSE; | ||
6675 | } | ||
6676 | |||
6677 | /** | ||
6678 | * @internal | ||
6679 | * Removes all the format nodes between start and end in the text node n. | ||
6680 | * This function updates the offset of the next format node and the | ||
6681 | * text nodes pointing to it. if end == -1 end means the end of the string. | ||
6682 | * | ||
6683 | * @param o the textblock object. | ||
6684 | * @param n the text node the positinos refer to. | ||
6685 | * @param start the start of where to delete from. | ||
6686 | * @param end the end of the section to delete, if end == -1 it means the end of the string. | ||
6687 | */ | ||
6688 | static void | ||
6689 | _evas_textblock_node_text_remove_formats_between(Evas_Object_Textblock *o, | ||
6690 | Evas_Object_Textblock_Node_Text *n, int start, int end) | ||
6691 | { | ||
6692 | Evas_Object_Textblock_Node_Format *itr; | ||
6693 | int use_end = 1; | ||
6694 | int offset = end - start; | ||
6695 | itr = n->format_node; | ||
6696 | |||
6697 | if (itr) | ||
6698 | start -= itr->offset; | ||
6699 | if (offset < 0) offset = 0; | ||
6700 | if (end < 0) use_end = 0; | ||
6701 | while (itr && (itr->text_node == n)) | ||
6702 | { | ||
6703 | Evas_Object_Textblock_Node_Format *nnode; | ||
6704 | int tmp_offset = 0; | ||
6705 | |||
6706 | /* start is negative when this gets relevant */ | ||
6707 | if ((offset + start < 0) && use_end) | ||
6708 | { | ||
6709 | break; | ||
6710 | } | ||
6711 | nnode = _NODE_FORMAT(EINA_INLIST_GET(itr)->next); | ||
6712 | if (nnode) | ||
6713 | { | ||
6714 | tmp_offset = nnode->offset; | ||
6715 | } | ||
6716 | if (start <= 0) | ||
6717 | { | ||
6718 | /* Don't do visible adjustments because we are removing the visual | ||
6719 | * chars anyway and taking those into account */ | ||
6720 | _evas_textblock_node_format_remove(o, itr, 0); | ||
6721 | } | ||
6722 | start -= tmp_offset; | ||
6723 | itr = nnode; | ||
6724 | } | ||
6725 | } | ||
6726 | |||
6727 | /** | ||
6728 | * @internal | ||
6729 | * Returns the first format in the range between start and end in the textblock | ||
6730 | * n. | ||
6731 | * | ||
6732 | * @param o the textblock object. | ||
6733 | * @param n the text node the positinos refer to. | ||
6734 | * @param start the start of where to delete from. | ||
6735 | * @param end the end of the section to delete, if end == -1 it means the end of the string. | ||
6736 | */ | ||
6737 | static Evas_Object_Textblock_Node_Format * | ||
6738 | _evas_textblock_node_text_get_first_format_between( | ||
6739 | Evas_Object_Textblock_Node_Text *n, int start, int end) | ||
6740 | { | ||
6741 | Evas_Object_Textblock_Node_Format *itr; | ||
6742 | int use_end = 1; | ||
6743 | itr = n->format_node; | ||
6744 | if (end < 0) use_end = 0; | ||
6745 | while (itr && (itr->text_node == n)) | ||
6746 | { | ||
6747 | start -= itr->offset; | ||
6748 | end -= itr->offset; | ||
6749 | if ((end <= 0) && use_end) | ||
6750 | { | ||
6751 | break; | ||
6752 | } | ||
6753 | if (start <= 0) | ||
6754 | { | ||
6755 | return itr; | ||
6756 | } | ||
6757 | itr = _NODE_FORMAT(EINA_INLIST_GET(itr)->next); | ||
6758 | } | ||
6759 | return NULL; | ||
6760 | } | ||
6761 | |||
6762 | /** | ||
6763 | * Removes a text node and the corresponding format nodes. | ||
6764 | * | ||
6765 | * @param o the textblock objec.t | ||
6766 | * @param n the node to remove. | ||
6767 | */ | ||
6768 | static void | ||
6769 | _evas_textblock_node_text_remove(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Text *n) | ||
6770 | { | ||
6771 | _evas_textblock_node_text_adjust_offsets_to_start(o, n, 0, -1); | ||
6772 | |||
6773 | o->text_nodes = _NODE_TEXT(eina_inlist_remove( | ||
6774 | EINA_INLIST_GET(o->text_nodes), EINA_INLIST_GET(n))); | ||
6775 | _evas_textblock_node_text_free(n); | ||
6776 | } | ||
6777 | |||
6778 | /** | ||
6779 | * @internal | ||
6780 | * Return the position where the formats starts at. | ||
6781 | * | ||
6782 | * @param fmt the format to return the position of. | ||
6783 | * @return the position of the format in the text node it points to. | ||
6784 | */ | ||
6785 | static size_t | ||
6786 | _evas_textblock_node_format_pos_get(const Evas_Object_Textblock_Node_Format *fmt) | ||
6787 | { | ||
6788 | Evas_Object_Textblock_Node_Text *text; | ||
6789 | Evas_Object_Textblock_Node_Format *base_format; | ||
6790 | Evas_Object_Textblock_Node_Format *itr; | ||
6791 | size_t position = 0; | ||
6792 | |||
6793 | if (!fmt) return 0; | ||
6794 | /* Find the main format node */ | ||
6795 | text = fmt->text_node; | ||
6796 | base_format = text->format_node; | ||
6797 | EINA_INLIST_FOREACH(base_format, itr) | ||
6798 | { | ||
6799 | if (itr == fmt) | ||
6800 | { | ||
6801 | break; | ||
6802 | } | ||
6803 | position += itr->offset; | ||
6804 | } | ||
6805 | return position + fmt->offset; | ||
6806 | } | ||
6807 | |||
6808 | EAPI int | ||
6809 | evas_textblock_cursor_pos_get(const Evas_Textblock_Cursor *cur) | ||
6810 | { | ||
6811 | Evas_Object_Textblock *o; | ||
6812 | Evas_Object_Textblock_Node_Text *n; | ||
6813 | size_t npos = 0; | ||
6814 | |||
6815 | if (!cur) return -1; | ||
6816 | if (!cur->node) return 0; | ||
6817 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
6818 | n = o->text_nodes; | ||
6819 | while (n != cur->node) | ||
6820 | { | ||
6821 | npos += eina_ustrbuf_length_get(n->unicode); | ||
6822 | n = _NODE_TEXT(EINA_INLIST_GET(n)->next); | ||
6823 | } | ||
6824 | return npos + cur->pos; | ||
6825 | } | ||
6826 | |||
6827 | EAPI void | ||
6828 | evas_textblock_cursor_pos_set(Evas_Textblock_Cursor *cur, int _pos) | ||
6829 | { | ||
6830 | Evas_Object_Textblock *o; | ||
6831 | Evas_Object_Textblock_Node_Text *n; | ||
6832 | size_t pos; | ||
6833 | |||
6834 | if (!cur) return; | ||
6835 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
6836 | |||
6837 | if (_pos < 0) | ||
6838 | { | ||
6839 | pos = 0; | ||
6840 | } | ||
6841 | else | ||
6842 | { | ||
6843 | pos = (size_t) _pos; | ||
6844 | } | ||
6845 | |||
6846 | n = o->text_nodes; | ||
6847 | while (n && (pos >= eina_ustrbuf_length_get(n->unicode))) | ||
6848 | { | ||
6849 | pos -= eina_ustrbuf_length_get(n->unicode); | ||
6850 | n = _NODE_TEXT(EINA_INLIST_GET(n)->next); | ||
6851 | } | ||
6852 | |||
6853 | if (n) | ||
6854 | { | ||
6855 | cur->node = n; | ||
6856 | cur->pos = pos; | ||
6857 | } | ||
6858 | else if (o->text_nodes) | ||
6859 | { | ||
6860 | /* In case we went pass the last node, we need to put the cursor | ||
6861 | * at the absolute end. */ | ||
6862 | Evas_Object_Textblock_Node_Text *last_n; | ||
6863 | |||
6864 | last_n = _NODE_TEXT(EINA_INLIST_GET(o->text_nodes)->last); | ||
6865 | pos = eina_ustrbuf_length_get(last_n->unicode); | ||
6866 | |||
6867 | cur->node = last_n; | ||
6868 | cur->pos = pos; | ||
6869 | } | ||
6870 | |||
6871 | } | ||
6872 | |||
6873 | EAPI Eina_Bool | ||
6874 | evas_textblock_cursor_line_set(Evas_Textblock_Cursor *cur, int line) | ||
6875 | { | ||
6876 | Evas_Object_Textblock *o; | ||
6877 | Evas_Object_Textblock_Line *ln; | ||
6878 | Evas_Object_Textblock_Item *it; | ||
6879 | |||
6880 | if (!cur) return EINA_FALSE; | ||
6881 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
6882 | if (!o->formatted.valid) _relayout(cur->obj); | ||
6883 | |||
6884 | ln = _find_layout_line_num(cur->obj, line); | ||
6885 | if (!ln) return EINA_FALSE; | ||
6886 | it = (Evas_Object_Textblock_Item *)ln->items; | ||
6887 | if (it) | ||
6888 | { | ||
6889 | cur->pos = it->text_pos; | ||
6890 | cur->node = it->text_node; | ||
6891 | } | ||
6892 | else | ||
6893 | { | ||
6894 | cur->pos = 0; | ||
6895 | |||
6896 | cur->node = o->text_nodes; | ||
6897 | } | ||
6898 | return EINA_TRUE; | ||
6899 | } | ||
6900 | |||
6901 | EAPI int | ||
6902 | evas_textblock_cursor_compare(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *cur2) | ||
6903 | { | ||
6904 | Eina_Inlist *l1, *l2; | ||
6905 | |||
6906 | if (!cur1) return 0; | ||
6907 | if (!cur2) return 0; | ||
6908 | if (cur1->obj != cur2->obj) return 0; | ||
6909 | if ((!cur1->node) || (!cur2->node)) return 0; | ||
6910 | if (cur1->node == cur2->node) | ||
6911 | { | ||
6912 | if (cur1->pos < cur2->pos) return -1; /* cur1 < cur2 */ | ||
6913 | else if (cur1->pos > cur2->pos) return 1; /* cur2 < cur1 */ | ||
6914 | return 0; | ||
6915 | } | ||
6916 | for (l1 = EINA_INLIST_GET(cur1->node), | ||
6917 | l2 = EINA_INLIST_GET(cur1->node); (l1) || (l2);) | ||
6918 | { | ||
6919 | if (l1 == EINA_INLIST_GET(cur2->node)) return 1; /* cur2 < cur 1 */ | ||
6920 | else if (l2 == EINA_INLIST_GET(cur2->node)) return -1; /* cur1 < cur 2 */ | ||
6921 | else if (!l1) return -1; /* cur1 < cur 2 */ | ||
6922 | else if (!l2) return 1; /* cur2 < cur 1 */ | ||
6923 | l1 = l1->prev; | ||
6924 | l2 = l2->next; | ||
6925 | } | ||
6926 | return 0; | ||
6927 | } | ||
6928 | |||
6929 | EAPI void | ||
6930 | evas_textblock_cursor_copy(const Evas_Textblock_Cursor *cur, Evas_Textblock_Cursor *cur_dest) | ||
6931 | { | ||
6932 | if (!cur) return; | ||
6933 | if (!cur_dest) return; | ||
6934 | if (cur->obj != cur_dest->obj) return; | ||
6935 | cur_dest->pos = cur->pos; | ||
6936 | cur_dest->node = cur->node; | ||
6937 | |||
6938 | } | ||
6939 | |||
6940 | |||
6941 | /* text controls */ | ||
6942 | /** | ||
6943 | * @internal | ||
6944 | * Free a text node. Shouldn't be used usually, it's better to use | ||
6945 | * @ref _evas_textblock_node_text_remove for most cases . | ||
6946 | * | ||
6947 | * @param n the text node to free | ||
6948 | * @see _evas_textblock_node_text_remove | ||
6949 | */ | ||
6950 | static void | ||
6951 | _evas_textblock_node_text_free(Evas_Object_Textblock_Node_Text *n) | ||
6952 | { | ||
6953 | if (!n) return; | ||
6954 | eina_ustrbuf_free(n->unicode); | ||
6955 | if (n->utf8) | ||
6956 | free(n->utf8); | ||
6957 | if (n->par) | ||
6958 | n->par->text_node = NULL; | ||
6959 | free(n); | ||
6960 | } | ||
6961 | |||
6962 | /** | ||
6963 | * @internal | ||
6964 | * Create a new text node | ||
6965 | * | ||
6966 | * @return the new text node. | ||
6967 | */ | ||
6968 | static Evas_Object_Textblock_Node_Text * | ||
6969 | _evas_textblock_node_text_new(void) | ||
6970 | { | ||
6971 | Evas_Object_Textblock_Node_Text *n; | ||
6972 | |||
6973 | n = calloc(1, sizeof(Evas_Object_Textblock_Node_Text)); | ||
6974 | n->unicode = eina_ustrbuf_new(); | ||
6975 | /* We want to layout each paragraph at least once. */ | ||
6976 | n->dirty = EINA_TRUE; | ||
6977 | n->is_new = EINA_TRUE; | ||
6978 | |||
6979 | return n; | ||
6980 | } | ||
6981 | |||
6982 | /** | ||
6983 | * @internal | ||
6984 | * Break a paragraph. This does not add a PS but only splits the paragraph | ||
6985 | * where a ps was just added! | ||
6986 | * | ||
6987 | * @param cur the cursor to break at. | ||
6988 | * @param fnode the format node of the PS just added. | ||
6989 | * @return Returns no value. | ||
6990 | */ | ||
6991 | static void | ||
6992 | _evas_textblock_cursor_break_paragraph(Evas_Textblock_Cursor *cur, | ||
6993 | Evas_Object_Textblock_Node_Format *fnode) | ||
6994 | { | ||
6995 | Evas_Object_Textblock *o; | ||
6996 | Evas_Object_Textblock_Node_Text *n; | ||
6997 | |||
6998 | if (!cur) return; | ||
6999 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
7000 | |||
7001 | n = _evas_textblock_node_text_new(); | ||
7002 | o->text_nodes = _NODE_TEXT(eina_inlist_append_relative( | ||
7003 | EINA_INLIST_GET(o->text_nodes), | ||
7004 | EINA_INLIST_GET(n), | ||
7005 | EINA_INLIST_GET(cur->node))); | ||
7006 | /* Handle text and format changes. */ | ||
7007 | if (cur->node) | ||
7008 | { | ||
7009 | Evas_Object_Textblock_Node_Format *nnode; | ||
7010 | size_t len, start; | ||
7011 | const Eina_Unicode *text; | ||
7012 | |||
7013 | /* If there was a format node in the delete range, | ||
7014 | * make it our format and update the text_node fields, | ||
7015 | * otherwise, use the paragraph separator | ||
7016 | * of the previous paragraph. */ | ||
7017 | nnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); | ||
7018 | if (nnode && (nnode->text_node == cur->node)) | ||
7019 | { | ||
7020 | n->format_node = nnode; | ||
7021 | nnode->offset--; /* We don't have to take the replacement char | ||
7022 | into account anymore */ | ||
7023 | while (nnode && (nnode->text_node == cur->node)) | ||
7024 | { | ||
7025 | nnode->text_node = n; | ||
7026 | nnode = _NODE_FORMAT(EINA_INLIST_GET(nnode)->next); | ||
7027 | } | ||
7028 | } | ||
7029 | else | ||
7030 | { | ||
7031 | n->format_node = fnode; | ||
7032 | } | ||
7033 | |||
7034 | /* cur->pos now points to the PS, move after. */ | ||
7035 | start = cur->pos + 1; | ||
7036 | len = eina_ustrbuf_length_get(cur->node->unicode) - start; | ||
7037 | if (len > 0) | ||
7038 | { | ||
7039 | text = eina_ustrbuf_string_get(cur->node->unicode); | ||
7040 | eina_ustrbuf_append_length(n->unicode, text + start, len); | ||
7041 | eina_ustrbuf_remove(cur->node->unicode, start, start + len); | ||
7042 | cur->node->dirty = EINA_TRUE; | ||
7043 | } | ||
7044 | } | ||
7045 | else | ||
7046 | { | ||
7047 | fnode = o->format_nodes; | ||
7048 | if (fnode) | ||
7049 | { | ||
7050 | fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->last); | ||
7051 | } | ||
7052 | n->format_node = fnode; | ||
7053 | } | ||
7054 | } | ||
7055 | |||
7056 | /** | ||
7057 | * @internal | ||
7058 | * Set the node and offset of all the curs after cur. | ||
7059 | * | ||
7060 | * @param cur the cursor. | ||
7061 | * @param n the current textblock node. | ||
7062 | * @param new_node the new node to set. | ||
7063 | */ | ||
7064 | static void | ||
7065 | _evas_textblock_cursors_set_node(Evas_Object_Textblock *o, | ||
7066 | const Evas_Object_Textblock_Node_Text *n, | ||
7067 | Evas_Object_Textblock_Node_Text *new_node) | ||
7068 | { | ||
7069 | Eina_List *l; | ||
7070 | Evas_Textblock_Cursor *data; | ||
7071 | |||
7072 | if (n == o->cursor->node) | ||
7073 | { | ||
7074 | o->cursor->pos = 0; | ||
7075 | o->cursor->node = new_node; | ||
7076 | } | ||
7077 | EINA_LIST_FOREACH(o->cursors, l, data) | ||
7078 | { | ||
7079 | if (n == data->node) | ||
7080 | { | ||
7081 | data->pos = 0; | ||
7082 | data->node = new_node; | ||
7083 | } | ||
7084 | } | ||
7085 | } | ||
7086 | |||
7087 | /** | ||
7088 | * @internal | ||
7089 | * Update the offset of all the cursors after cur. | ||
7090 | * | ||
7091 | * @param cur the cursor. | ||
7092 | * @param n the current textblock node. | ||
7093 | * @param start the starting pos. | ||
7094 | * @param offset how much to adjust (can be negative). | ||
7095 | */ | ||
7096 | static void | ||
7097 | _evas_textblock_cursors_update_offset(const Evas_Textblock_Cursor *cur, | ||
7098 | const Evas_Object_Textblock_Node_Text *n, | ||
7099 | size_t start, int offset) | ||
7100 | { | ||
7101 | Eina_List *l; | ||
7102 | Evas_Textblock_Cursor *data; | ||
7103 | Evas_Object_Textblock *o; | ||
7104 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
7105 | |||
7106 | if (cur != o->cursor) | ||
7107 | { | ||
7108 | if ((n == o->cursor->node) && | ||
7109 | (o->cursor->pos > start)) | ||
7110 | { | ||
7111 | if ((offset < 0) && (o->cursor->pos <= (size_t) (-1 * offset))) | ||
7112 | { | ||
7113 | o->cursor->pos = 0; | ||
7114 | } | ||
7115 | else | ||
7116 | { | ||
7117 | o->cursor->pos += offset; | ||
7118 | } | ||
7119 | } | ||
7120 | } | ||
7121 | EINA_LIST_FOREACH(o->cursors, l, data) | ||
7122 | { | ||
7123 | if (data != cur) | ||
7124 | { | ||
7125 | if ((n == data->node) && | ||
7126 | (data->pos > start)) | ||
7127 | { | ||
7128 | if ((offset < 0) && (data->pos <= (size_t) (-1 * offset))) | ||
7129 | { | ||
7130 | data->pos = 0; | ||
7131 | } | ||
7132 | else | ||
7133 | { | ||
7134 | data->pos += offset; | ||
7135 | } | ||
7136 | } | ||
7137 | else if (!data->node) | ||
7138 | { | ||
7139 | data->node = o->text_nodes; | ||
7140 | data->pos = 0; | ||
7141 | } | ||
7142 | } | ||
7143 | } | ||
7144 | } | ||
7145 | |||
7146 | /** | ||
7147 | * @internal | ||
7148 | * Mark that the textblock has changed. | ||
7149 | * | ||
7150 | * @param o the textblock object. | ||
7151 | * @param obj the evas object. | ||
7152 | */ | ||
7153 | static void | ||
7154 | _evas_textblock_changed(Evas_Object_Textblock *o, Evas_Object *obj) | ||
7155 | { | ||
7156 | o->formatted.valid = 0; | ||
7157 | o->native.valid = 0; | ||
7158 | o->content_changed = 1; | ||
7159 | if (o->markup_text) | ||
7160 | { | ||
7161 | free(o->markup_text); | ||
7162 | o->markup_text = NULL; | ||
7163 | } | ||
7164 | |||
7165 | evas_object_change(obj); | ||
7166 | } | ||
7167 | |||
7168 | static void | ||
7169 | _evas_textblock_invalidate_all(Evas_Object_Textblock *o) | ||
7170 | { | ||
7171 | Evas_Object_Textblock_Node_Text *n; | ||
7172 | |||
7173 | EINA_INLIST_FOREACH(o->text_nodes, n) | ||
7174 | { | ||
7175 | n->dirty = EINA_TRUE; | ||
7176 | } | ||
7177 | } | ||
7178 | |||
7179 | EAPI int | ||
7180 | evas_textblock_cursor_text_append(Evas_Textblock_Cursor *cur, const char *_text) | ||
7181 | { | ||
7182 | Evas_Object_Textblock *o; | ||
7183 | Evas_Object_Textblock_Node_Text *n; | ||
7184 | Evas_Object_Textblock_Node_Format *fnode = NULL; | ||
7185 | Eina_Unicode *text; | ||
7186 | int len = 0; | ||
7187 | |||
7188 | if (!cur) return 0; | ||
7189 | text = eina_unicode_utf8_to_unicode(_text, &len); | ||
7190 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
7191 | |||
7192 | n = cur->node; | ||
7193 | if (n) | ||
7194 | { | ||
7195 | Evas_Object_Textblock_Node_Format *nnode; | ||
7196 | fnode = _evas_textblock_cursor_node_format_before_or_at_pos_get(cur); | ||
7197 | fnode = _evas_textblock_node_format_last_at_off(fnode); | ||
7198 | /* find the node after the current in the same paragraph | ||
7199 | * either we find one and then take the next, or we try to get | ||
7200 | * the first for the paragraph which must be after our position */ | ||
7201 | if (fnode) | ||
7202 | { | ||
7203 | if (!evas_textblock_cursor_format_is_visible_get(cur)) | ||
7204 | { | ||
7205 | nnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); | ||
7206 | if (nnode && (nnode->text_node == n)) | ||
7207 | { | ||
7208 | fnode = nnode; | ||
7209 | } | ||
7210 | else | ||
7211 | { | ||
7212 | fnode = NULL; | ||
7213 | } | ||
7214 | } | ||
7215 | } | ||
7216 | else | ||
7217 | { | ||
7218 | fnode = n->format_node; | ||
7219 | } | ||
7220 | } | ||
7221 | else if (o->text_nodes) | ||
7222 | { | ||
7223 | n = cur->node = o->text_nodes; | ||
7224 | cur->pos = 0; | ||
7225 | } | ||
7226 | else | ||
7227 | { | ||
7228 | n = _evas_textblock_node_text_new(); | ||
7229 | o->text_nodes = _NODE_TEXT(eina_inlist_append( | ||
7230 | EINA_INLIST_GET(o->text_nodes), | ||
7231 | EINA_INLIST_GET(n))); | ||
7232 | cur->node = n; | ||
7233 | } | ||
7234 | |||
7235 | eina_ustrbuf_insert_length(n->unicode, text, len, cur->pos); | ||
7236 | /* Advance the formats */ | ||
7237 | if (fnode && (fnode->text_node == cur->node)) | ||
7238 | fnode->offset += len; | ||
7239 | |||
7240 | /* Update all the cursors after our position. */ | ||
7241 | _evas_textblock_cursors_update_offset(cur, cur->node, cur->pos, len); | ||
7242 | |||
7243 | _evas_textblock_changed(o, cur->obj); | ||
7244 | n->dirty = EINA_TRUE; | ||
7245 | free(text); | ||
7246 | |||
7247 | if (!o->cursor->node) | ||
7248 | o->cursor->node = o->text_nodes; | ||
7249 | return len; | ||
7250 | } | ||
7251 | |||
7252 | EAPI int | ||
7253 | evas_textblock_cursor_text_prepend(Evas_Textblock_Cursor *cur, const char *_text) | ||
7254 | { | ||
7255 | int len; | ||
7256 | /*append is essentially prepend without advancing */ | ||
7257 | len = evas_textblock_cursor_text_append(cur, _text); | ||
7258 | cur->pos += len; /*Advance */ | ||
7259 | return len; | ||
7260 | } | ||
7261 | |||
7262 | /** | ||
7263 | * @internal | ||
7264 | * Free a format node | ||
7265 | * | ||
7266 | * @param o the textblock object | ||
7267 | * @param n the format node to free | ||
7268 | */ | ||
7269 | static void | ||
7270 | _evas_textblock_node_format_free(Evas_Object_Textblock *o, | ||
7271 | Evas_Object_Textblock_Node_Format *n) | ||
7272 | { | ||
7273 | if (!n) return; | ||
7274 | eina_stringshare_del(n->format); | ||
7275 | eina_stringshare_del(n->orig_format); | ||
7276 | if (n->anchor == ANCHOR_ITEM) | ||
7277 | o->anchors_item = eina_list_remove(o->anchors_item, n); | ||
7278 | else if (n->anchor == ANCHOR_A) | ||
7279 | o->anchors_a = eina_list_remove(o->anchors_a, n); | ||
7280 | free(n); | ||
7281 | } | ||
7282 | |||
7283 | /** | ||
7284 | * @internal | ||
7285 | * Create a new format node. | ||
7286 | * | ||
7287 | * @param format the text to create the format node from. | ||
7288 | * @param o the textblock object. | ||
7289 | * @return Returns the new format node | ||
7290 | */ | ||
7291 | static Evas_Object_Textblock_Node_Format * | ||
7292 | _evas_textblock_node_format_new(Evas_Object_Textblock *o, const char *_format) | ||
7293 | { | ||
7294 | Evas_Object_Textblock_Node_Format *n; | ||
7295 | const char *format = _format; | ||
7296 | const char *pre_stripped_format = NULL; | ||
7297 | |||
7298 | n = calloc(1, sizeof(Evas_Object_Textblock_Node_Format)); | ||
7299 | /* Create orig_format and format */ | ||
7300 | if (format[0] == '<') | ||
7301 | { | ||
7302 | const char *match; | ||
7303 | size_t format_len; | ||
7304 | size_t replace_len; | ||
7305 | |||
7306 | format++; /* Advance after '<' */ | ||
7307 | format_len = strlen(format); | ||
7308 | if ((format_len > 0) && format[format_len - 1] == '>') | ||
7309 | { | ||
7310 | format_len--; /* We don't care about '>' */ | ||
7311 | /* Check if it closes itself. Skip the </> case. */ | ||
7312 | if ((format_len > 1) && format[format_len - 1] == '/') | ||
7313 | { | ||
7314 | format_len--; /* We don't care about '/' */ | ||
7315 | n->own_closer = EINA_TRUE; | ||
7316 | } | ||
7317 | } | ||
7318 | |||
7319 | if (!o->style_user || !(match = _style_match_tag(o->style_user, format, | ||
7320 | format_len, &replace_len))) | ||
7321 | { | ||
7322 | match = _style_match_tag(o->style, format, format_len, | ||
7323 | &replace_len); | ||
7324 | } | ||
7325 | |||
7326 | if (match) | ||
7327 | { | ||
7328 | if (match[0] != '-') | ||
7329 | { | ||
7330 | n->opener = EINA_TRUE; | ||
7331 | if (match[0] != '+') | ||
7332 | { | ||
7333 | n->own_closer = EINA_TRUE; | ||
7334 | } | ||
7335 | } | ||
7336 | |||
7337 | pre_stripped_format = match; | ||
7338 | } | ||
7339 | else | ||
7340 | { | ||
7341 | if (format[0] == '/') | ||
7342 | { | ||
7343 | format++; | ||
7344 | format_len--; | ||
7345 | } | ||
7346 | else | ||
7347 | { | ||
7348 | n->opener = EINA_TRUE; | ||
7349 | } | ||
7350 | } | ||
7351 | |||
7352 | n->orig_format = eina_stringshare_add_length(format, format_len); | ||
7353 | |||
7354 | if (!pre_stripped_format) | ||
7355 | pre_stripped_format = n->orig_format; | ||
7356 | } | ||
7357 | /* Just use as is, it's a special format. */ | ||
7358 | else | ||
7359 | { | ||
7360 | const char *tmp = format; | ||
7361 | if (format[0] != '-') | ||
7362 | { | ||
7363 | n->opener = EINA_TRUE; | ||
7364 | if (format[0] != '+') | ||
7365 | { | ||
7366 | n->own_closer = EINA_TRUE; | ||
7367 | } | ||
7368 | } | ||
7369 | if ((*tmp == '+') || (*tmp == '-')) | ||
7370 | { | ||
7371 | tmp++; | ||
7372 | while (*tmp == ' ') tmp++; | ||
7373 | } | ||
7374 | n->orig_format = eina_stringshare_add(tmp); | ||
7375 | pre_stripped_format = n->orig_format; | ||
7376 | } | ||
7377 | |||
7378 | /* Strip format */ | ||
7379 | { | ||
7380 | const char *tmp = pre_stripped_format; | ||
7381 | if ((*tmp == '+') || (*tmp == '-')) | ||
7382 | { | ||
7383 | tmp++; | ||
7384 | while (*tmp == ' ') tmp++; | ||
7385 | } | ||
7386 | n->format = eina_stringshare_add(tmp); | ||
7387 | } | ||
7388 | format = n->format; | ||
7389 | |||
7390 | _evas_textblock_format_is_visible(n, format); | ||
7391 | if (n->anchor == ANCHOR_A) | ||
7392 | { | ||
7393 | o->anchors_a = eina_list_append(o->anchors_a, n); | ||
7394 | } | ||
7395 | else if (n->anchor == ANCHOR_ITEM) | ||
7396 | { | ||
7397 | o->anchors_item = eina_list_append(o->anchors_item, n); | ||
7398 | } | ||
7399 | n->is_new = EINA_TRUE; | ||
7400 | |||
7401 | return n; | ||
7402 | } | ||
7403 | |||
7404 | static Eina_Bool | ||
7405 | _evas_textblock_cursor_is_at_the_end(const Evas_Textblock_Cursor *cur) | ||
7406 | { | ||
7407 | const Eina_Unicode *text; | ||
7408 | |||
7409 | if (!cur) return EINA_FALSE; | ||
7410 | if (!cur->node) return EINA_FALSE; | ||
7411 | text = eina_ustrbuf_string_get(cur->node->unicode); | ||
7412 | return ((text[cur->pos] == 0) && (!EINA_INLIST_GET(cur->node)->next)) ? | ||
7413 | EINA_TRUE : EINA_FALSE; | ||
7414 | } | ||
7415 | |||
7416 | EAPI Eina_Bool | ||
7417 | evas_textblock_cursor_format_append(Evas_Textblock_Cursor *cur, const char *format) | ||
7418 | { | ||
7419 | Evas_Object_Textblock *o; | ||
7420 | Evas_Object_Textblock_Node_Format *n; | ||
7421 | Eina_Bool is_visible; | ||
7422 | |||
7423 | if (!cur) return EINA_FALSE; | ||
7424 | if ((!format) || (format[0] == 0)) return EINA_FALSE; | ||
7425 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
7426 | /* We should always have at least one text node */ | ||
7427 | if (!o->text_nodes) | ||
7428 | { | ||
7429 | evas_textblock_cursor_text_prepend(cur, ""); | ||
7430 | } | ||
7431 | |||
7432 | n = _evas_textblock_node_format_new(o, format); | ||
7433 | is_visible = n->visible; | ||
7434 | format = n->format; | ||
7435 | if (!cur->node) | ||
7436 | { | ||
7437 | o->format_nodes = _NODE_FORMAT(eina_inlist_append( | ||
7438 | EINA_INLIST_GET(o->format_nodes), | ||
7439 | EINA_INLIST_GET(n))); | ||
7440 | cur->pos = 0; | ||
7441 | n->text_node = (EINA_INLIST_GET(n)->prev) ? | ||
7442 | _NODE_FORMAT(EINA_INLIST_GET(n)->prev)->text_node : | ||
7443 | o->text_nodes; | ||
7444 | cur->node = n->text_node; | ||
7445 | } | ||
7446 | else | ||
7447 | { | ||
7448 | Evas_Object_Textblock_Node_Format *fmt; | ||
7449 | fmt = _evas_textblock_cursor_node_format_before_or_at_pos_get(cur); | ||
7450 | n->text_node = cur->node; | ||
7451 | if (!fmt) | ||
7452 | { | ||
7453 | o->format_nodes = _NODE_FORMAT(eina_inlist_prepend( | ||
7454 | EINA_INLIST_GET(o->format_nodes), | ||
7455 | EINA_INLIST_GET(n))); | ||
7456 | n->offset = cur->pos; | ||
7457 | } | ||
7458 | else | ||
7459 | { | ||
7460 | if (evas_textblock_cursor_format_is_visible_get(cur)) | ||
7461 | { | ||
7462 | o->format_nodes = _NODE_FORMAT(eina_inlist_prepend_relative( | ||
7463 | EINA_INLIST_GET(o->format_nodes), | ||
7464 | EINA_INLIST_GET(n), | ||
7465 | EINA_INLIST_GET(fmt) | ||
7466 | )); | ||
7467 | n->offset = fmt->offset; | ||
7468 | if (fmt->text_node->format_node == fmt) | ||
7469 | { | ||
7470 | fmt->text_node->format_node = n; | ||
7471 | } | ||
7472 | } | ||
7473 | else | ||
7474 | { | ||
7475 | fmt = _evas_textblock_node_format_last_at_off(fmt); | ||
7476 | o->format_nodes = _NODE_FORMAT(eina_inlist_append_relative( | ||
7477 | EINA_INLIST_GET(o->format_nodes), | ||
7478 | EINA_INLIST_GET(n), | ||
7479 | EINA_INLIST_GET(fmt) | ||
7480 | )); | ||
7481 | if (fmt->text_node != cur->node) | ||
7482 | { | ||
7483 | n->offset = cur->pos; | ||
7484 | } | ||
7485 | else | ||
7486 | { | ||
7487 | n->offset = cur->pos - | ||
7488 | _evas_textblock_node_format_pos_get(fmt); | ||
7489 | } | ||
7490 | } | ||
7491 | } | ||
7492 | /* Adjust differently if we insert a format char */ | ||
7493 | if (is_visible) | ||
7494 | { | ||
7495 | _evas_textblock_node_format_adjust_offset(o, cur->node, n, | ||
7496 | -(n->offset - 1)); | ||
7497 | } | ||
7498 | else | ||
7499 | { | ||
7500 | _evas_textblock_node_format_adjust_offset(o, cur->node, n, | ||
7501 | -n->offset); | ||
7502 | } | ||
7503 | |||
7504 | if (!fmt || (fmt->text_node != cur->node)) | ||
7505 | { | ||
7506 | cur->node->format_node = n; | ||
7507 | } | ||
7508 | } | ||
7509 | if (is_visible && cur->node) | ||
7510 | { | ||
7511 | Eina_Unicode insert_char; | ||
7512 | /* Insert a visual representation according to the type of the | ||
7513 | format */ | ||
7514 | if (_IS_PARAGRAPH_SEPARATOR(o, format)) | ||
7515 | insert_char = _PARAGRAPH_SEPARATOR; | ||
7516 | else if (_IS_LINE_SEPARATOR(format)) | ||
7517 | insert_char = _NEWLINE; | ||
7518 | else if (_IS_TAB(format)) | ||
7519 | insert_char = _TAB; | ||
7520 | else | ||
7521 | insert_char = _REPLACEMENT_CHAR; | ||
7522 | |||
7523 | eina_ustrbuf_insert_char(cur->node->unicode, insert_char, cur->pos); | ||
7524 | |||
7525 | /* Advance all the cursors after our cursor */ | ||
7526 | _evas_textblock_cursors_update_offset(cur, cur->node, cur->pos, 1); | ||
7527 | if (_IS_PARAGRAPH_SEPARATOR(o, format)) | ||
7528 | { | ||
7529 | _evas_textblock_cursor_break_paragraph(cur, n); | ||
7530 | } | ||
7531 | else | ||
7532 | { | ||
7533 | /* Handle visible format nodes here */ | ||
7534 | cur->node->dirty = EINA_TRUE; | ||
7535 | n->is_new = EINA_FALSE; | ||
7536 | } | ||
7537 | } | ||
7538 | else | ||
7539 | { | ||
7540 | o->format_changed = EINA_TRUE; | ||
7541 | } | ||
7542 | |||
7543 | _evas_textblock_changed(o, cur->obj); | ||
7544 | |||
7545 | if (!o->cursor->node) | ||
7546 | o->cursor->node = o->text_nodes; | ||
7547 | return is_visible; | ||
7548 | } | ||
7549 | |||
7550 | EAPI Eina_Bool | ||
7551 | evas_textblock_cursor_format_prepend(Evas_Textblock_Cursor *cur, const char *format) | ||
7552 | { | ||
7553 | Eina_Bool is_visible; | ||
7554 | /* append is essentially prepend without advancing */ | ||
7555 | is_visible = evas_textblock_cursor_format_append(cur, format); | ||
7556 | if (is_visible) | ||
7557 | { | ||
7558 | /* Advance after the replacement char */ | ||
7559 | evas_textblock_cursor_char_next(cur); | ||
7560 | } | ||
7561 | |||
7562 | return is_visible; | ||
7563 | } | ||
7564 | |||
7565 | |||
7566 | EAPI void | ||
7567 | evas_textblock_cursor_char_delete(Evas_Textblock_Cursor *cur) | ||
7568 | { | ||
7569 | Evas_Object_Textblock *o; | ||
7570 | Evas_Object_Textblock_Node_Text *n, *n2; | ||
7571 | const Eina_Unicode *text; | ||
7572 | int chr, ind, ppos; | ||
7573 | |||
7574 | if (!cur || !cur->node) return; | ||
7575 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
7576 | n = cur->node; | ||
7577 | |||
7578 | text = eina_ustrbuf_string_get(n->unicode); | ||
7579 | ind = cur->pos; | ||
7580 | if (text[ind]) | ||
7581 | chr = text[ind++]; | ||
7582 | else | ||
7583 | chr = 0; | ||
7584 | |||
7585 | if (chr == 0) return; | ||
7586 | ppos = cur->pos; | ||
7587 | eina_ustrbuf_remove(n->unicode, cur->pos, ind); | ||
7588 | /* Remove a format node if needed, and remove the char only if the | ||
7589 | * fmt node is not visible */ | ||
7590 | { | ||
7591 | Eina_Bool should_merge = EINA_FALSE; | ||
7592 | Evas_Object_Textblock_Node_Format *fmt, *fmt2; | ||
7593 | fmt = _evas_textblock_cursor_node_format_at_pos_get(cur); | ||
7594 | if (fmt) | ||
7595 | { | ||
7596 | const char *format = NULL; | ||
7597 | Evas_Object_Textblock_Node_Format *last_fmt; | ||
7598 | /* If there's a PS it must be the last become it delimits paragraphs */ | ||
7599 | last_fmt = _evas_textblock_node_format_last_at_off(fmt); | ||
7600 | format = last_fmt->format; | ||
7601 | if (format && _IS_PARAGRAPH_SEPARATOR(o, format)) | ||
7602 | { | ||
7603 | /* If it was a paragraph separator, we should merge the | ||
7604 | * current with the next, there must be a next. */ | ||
7605 | should_merge = EINA_TRUE; | ||
7606 | } | ||
7607 | /* If a singnular, mark as invisible, so we'll delete it. */ | ||
7608 | if (!format || last_fmt->own_closer) | ||
7609 | { | ||
7610 | last_fmt->visible = EINA_FALSE; | ||
7611 | } | ||
7612 | } | ||
7613 | |||
7614 | fmt2 = _evas_textblock_cursor_node_format_before_or_at_pos_get(cur); | ||
7615 | fmt2 = _evas_textblock_node_format_last_at_off(fmt2); | ||
7616 | _evas_textblock_node_format_adjust_offset(o, cur->node, fmt2, | ||
7617 | -(ind - cur->pos)); | ||
7618 | |||
7619 | if (should_merge) | ||
7620 | { | ||
7621 | _evas_textblock_cursor_nodes_merge(cur); | ||
7622 | } | ||
7623 | |||
7624 | _evas_textblock_node_format_remove_matching(o, fmt); | ||
7625 | } | ||
7626 | |||
7627 | if (cur->pos == eina_ustrbuf_length_get(n->unicode)) | ||
7628 | { | ||
7629 | n2 = _NODE_TEXT(EINA_INLIST_GET(n)->next); | ||
7630 | if (n2) | ||
7631 | { | ||
7632 | cur->node = n2; | ||
7633 | cur->pos = 0; | ||
7634 | } | ||
7635 | } | ||
7636 | |||
7637 | _evas_textblock_cursors_update_offset(cur, n, ppos, -(ind - ppos)); | ||
7638 | _evas_textblock_changed(o, cur->obj); | ||
7639 | cur->node->dirty = EINA_TRUE; | ||
7640 | } | ||
7641 | |||
7642 | EAPI void | ||
7643 | evas_textblock_cursor_range_delete(Evas_Textblock_Cursor *cur1, Evas_Textblock_Cursor *cur2) | ||
7644 | { | ||
7645 | Evas_Object_Textblock_Node_Format *fnode = NULL; | ||
7646 | Evas_Object_Textblock *o; | ||
7647 | Evas_Object_Textblock_Node_Text *n1, *n2; | ||
7648 | Eina_Bool should_merge = EINA_FALSE, reset_cursor = EINA_FALSE; | ||
7649 | |||
7650 | if (!cur1 || !cur1->node) return; | ||
7651 | if (!cur2 || !cur2->node) return; | ||
7652 | if (cur1->obj != cur2->obj) return; | ||
7653 | o = (Evas_Object_Textblock *)(cur1->obj->object_data); | ||
7654 | if (evas_textblock_cursor_compare(cur1, cur2) > 0) | ||
7655 | { | ||
7656 | Evas_Textblock_Cursor *tc; | ||
7657 | |||
7658 | tc = cur1; | ||
7659 | cur1 = cur2; | ||
7660 | cur2 = tc; | ||
7661 | } | ||
7662 | n1 = cur1->node; | ||
7663 | n2 = cur2->node; | ||
7664 | if ((evas_textblock_cursor_compare(o->cursor, cur1) >= 0) && | ||
7665 | (evas_textblock_cursor_compare(cur2, o->cursor) >= 0)) | ||
7666 | { | ||
7667 | reset_cursor = EINA_TRUE; | ||
7668 | } | ||
7669 | |||
7670 | |||
7671 | if (n1 == n2) | ||
7672 | { | ||
7673 | if ((cur1->pos == 0) && | ||
7674 | (cur2->pos == eina_ustrbuf_length_get(n1->unicode))) | ||
7675 | { | ||
7676 | _evas_textblock_node_text_remove_formats_between(o, n1, 0, -1); | ||
7677 | } | ||
7678 | else | ||
7679 | { | ||
7680 | should_merge = _evas_textblock_node_text_adjust_offsets_to_start(o, | ||
7681 | n1, cur1->pos, cur2->pos); | ||
7682 | } | ||
7683 | eina_ustrbuf_remove(n1->unicode, cur1->pos, cur2->pos); | ||
7684 | _evas_textblock_cursors_update_offset(cur1, cur1->node, cur1->pos, - (cur2->pos - cur1->pos)); | ||
7685 | } | ||
7686 | else | ||
7687 | { | ||
7688 | Evas_Object_Textblock_Node_Text *n; | ||
7689 | int len; | ||
7690 | _evas_textblock_node_text_adjust_offsets_to_start(o, n1, cur1->pos, -1); | ||
7691 | n = _NODE_TEXT(EINA_INLIST_GET(n1)->next); | ||
7692 | /* Remove all the text nodes between */ | ||
7693 | while (n && (n != n2)) | ||
7694 | { | ||
7695 | Evas_Object_Textblock_Node_Text *nnode; | ||
7696 | |||
7697 | nnode = _NODE_TEXT(EINA_INLIST_GET(n)->next); | ||
7698 | _evas_textblock_cursors_set_node(o, n, n1); | ||
7699 | _evas_textblock_node_text_remove(o, n); | ||
7700 | n = nnode; | ||
7701 | } | ||
7702 | should_merge = _evas_textblock_node_text_adjust_offsets_to_start(o, n2, | ||
7703 | 0, cur2->pos); | ||
7704 | |||
7705 | /* Remove the formats and the strings in the first and last nodes */ | ||
7706 | len = eina_ustrbuf_length_get(n1->unicode); | ||
7707 | eina_ustrbuf_remove(n1->unicode, cur1->pos, len); | ||
7708 | eina_ustrbuf_remove(n2->unicode, 0, cur2->pos); | ||
7709 | /* Merge the nodes because we removed the PS */ | ||
7710 | _evas_textblock_cursors_update_offset(cur1, cur1->node, cur1->pos, | ||
7711 | - cur1->pos); | ||
7712 | _evas_textblock_cursors_update_offset(cur2, cur2->node, 0, - cur2->pos); | ||
7713 | _evas_textblock_nodes_merge(o, n1); | ||
7714 | } | ||
7715 | fnode = _evas_textblock_cursor_node_format_at_pos_get(cur1); | ||
7716 | |||
7717 | n1->dirty = n2->dirty = EINA_TRUE; | ||
7718 | if (should_merge) | ||
7719 | { | ||
7720 | /* We call this function instead of the cursor one because we already | ||
7721 | * updated the cursors */ | ||
7722 | _evas_textblock_nodes_merge(o, n1); | ||
7723 | } | ||
7724 | _evas_textblock_node_format_remove_matching(o, fnode); | ||
7725 | |||
7726 | evas_textblock_cursor_copy(cur1, cur2); | ||
7727 | if (reset_cursor) | ||
7728 | evas_textblock_cursor_copy(cur1, o->cursor); | ||
7729 | |||
7730 | _evas_textblock_changed(o, cur1->obj); | ||
7731 | } | ||
7732 | |||
7733 | |||
7734 | EAPI char * | ||
7735 | evas_textblock_cursor_content_get(const Evas_Textblock_Cursor *cur) | ||
7736 | { | ||
7737 | if (!cur || !cur->node) return NULL; | ||
7738 | if (evas_textblock_cursor_format_is_visible_get(cur)) | ||
7739 | { | ||
7740 | Eina_Strbuf *buf; | ||
7741 | Evas_Object_Textblock_Node_Format *fnode; | ||
7742 | char *ret; | ||
7743 | fnode = _evas_textblock_node_visible_at_pos_get( | ||
7744 | evas_textblock_cursor_format_get(cur)); | ||
7745 | |||
7746 | buf = eina_strbuf_new(); | ||
7747 | _markup_get_format_append(buf, fnode); | ||
7748 | ret = eina_strbuf_string_steal(buf); | ||
7749 | eina_strbuf_free(buf); | ||
7750 | |||
7751 | return ret; | ||
7752 | } | ||
7753 | else | ||
7754 | { | ||
7755 | const Eina_Unicode *ustr; | ||
7756 | Eina_Unicode buf[2]; | ||
7757 | char *s; | ||
7758 | |||
7759 | ustr = eina_ustrbuf_string_get(cur->node->unicode); | ||
7760 | buf[0] = ustr[cur->pos]; | ||
7761 | buf[1] = 0; | ||
7762 | s = eina_unicode_unicode_to_utf8(buf, NULL); | ||
7763 | |||
7764 | return s; | ||
7765 | } | ||
7766 | } | ||
7767 | |||
7768 | static char * | ||
7769 | _evas_textblock_cursor_range_text_markup_get(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *_cur2) | ||
7770 | { | ||
7771 | Evas_Object_Textblock_Node_Text *tnode; | ||
7772 | Eina_Strbuf *buf; | ||
7773 | Evas_Textblock_Cursor *cur2; | ||
7774 | buf = eina_strbuf_new(); | ||
7775 | |||
7776 | if (!cur1 || !cur1->node) return NULL; | ||
7777 | if (!_cur2 || !_cur2->node) return NULL; | ||
7778 | if (cur1->obj != _cur2->obj) return NULL; | ||
7779 | if (evas_textblock_cursor_compare(cur1, _cur2) > 0) | ||
7780 | { | ||
7781 | const Evas_Textblock_Cursor *tc; | ||
7782 | |||
7783 | tc = cur1; | ||
7784 | cur1 = _cur2; | ||
7785 | _cur2 = tc; | ||
7786 | } | ||
7787 | /* Work on a local copy of the cur */ | ||
7788 | cur2 = alloca(sizeof(Evas_Textblock_Cursor)); | ||
7789 | cur2->obj = _cur2->obj; | ||
7790 | evas_textblock_cursor_copy(_cur2, cur2); | ||
7791 | |||
7792 | /* Parse the text between the cursors. */ | ||
7793 | for (tnode = cur1->node ; tnode ; | ||
7794 | tnode = _NODE_TEXT(EINA_INLIST_GET(tnode)->next)) | ||
7795 | { | ||
7796 | Evas_Object_Textblock_Node_Format *fnode; | ||
7797 | Eina_Unicode *text_base, *text; | ||
7798 | int off = 0; | ||
7799 | |||
7800 | text_base = text = | ||
7801 | eina_unicode_strndup(eina_ustrbuf_string_get(tnode->unicode), | ||
7802 | eina_ustrbuf_length_get(tnode->unicode)); | ||
7803 | if (tnode == cur2->node) | ||
7804 | { | ||
7805 | fnode = _evas_textblock_node_text_get_first_format_between(tnode, | ||
7806 | cur1->pos, cur2->pos); | ||
7807 | } | ||
7808 | else if (tnode == cur1->node) | ||
7809 | { | ||
7810 | fnode = _evas_textblock_node_text_get_first_format_between(tnode, | ||
7811 | cur1->pos, -1); | ||
7812 | } | ||
7813 | else | ||
7814 | { | ||
7815 | fnode = _evas_textblock_node_text_get_first_format_between(tnode, | ||
7816 | 0, -1); | ||
7817 | } | ||
7818 | /* Init the offset so the first one will count starting from cur1->pos | ||
7819 | * and not the previous format node */ | ||
7820 | if (tnode == cur1->node) | ||
7821 | { | ||
7822 | if (fnode) | ||
7823 | { | ||
7824 | off = _evas_textblock_node_format_pos_get(fnode) - | ||
7825 | cur1->pos - fnode->offset; | ||
7826 | } | ||
7827 | text += cur1->pos; | ||
7828 | } | ||
7829 | else | ||
7830 | { | ||
7831 | off = 0; | ||
7832 | } | ||
7833 | while (fnode && (fnode->text_node == tnode)) | ||
7834 | { | ||
7835 | Eina_Unicode tmp_ch; | ||
7836 | off += fnode->offset; | ||
7837 | if ((tnode == cur2->node) && | ||
7838 | ((size_t) (text - text_base + off) >= cur2->pos)) | ||
7839 | { | ||
7840 | break; | ||
7841 | } | ||
7842 | /* No need to skip on the first run */ | ||
7843 | tmp_ch = text[off]; | ||
7844 | text[off] = 0; /* Null terminate the part of the string */ | ||
7845 | _markup_get_text_append(buf, text); | ||
7846 | _markup_get_format_append(buf, fnode); | ||
7847 | text[off] = tmp_ch; /* Restore the char */ | ||
7848 | text += off; | ||
7849 | if (fnode->visible) | ||
7850 | { | ||
7851 | off = -1; | ||
7852 | text++; | ||
7853 | } | ||
7854 | else | ||
7855 | { | ||
7856 | off = 0; | ||
7857 | } | ||
7858 | fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); | ||
7859 | } | ||
7860 | /* If we got to the last node, stop and add the rest outside */ | ||
7861 | if (cur2->node == tnode) | ||
7862 | { | ||
7863 | /* Add the rest, skip replacement */ | ||
7864 | /* Don't go past the second cursor pos */ | ||
7865 | text_base[cur2->pos] = '\0'; | ||
7866 | _markup_get_text_append(buf, text); | ||
7867 | free(text_base); | ||
7868 | break; | ||
7869 | } | ||
7870 | else | ||
7871 | { | ||
7872 | /* Add the rest, skip replacement */ | ||
7873 | _markup_get_text_append(buf, text); | ||
7874 | free(text_base); | ||
7875 | } | ||
7876 | } | ||
7877 | /* return the string */ | ||
7878 | { | ||
7879 | char *ret; | ||
7880 | ret = eina_strbuf_string_steal(buf); | ||
7881 | eina_strbuf_free(buf); | ||
7882 | return ret; | ||
7883 | } | ||
7884 | } | ||
7885 | |||
7886 | static char * | ||
7887 | _evas_textblock_cursor_range_text_plain_get(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *_cur2) | ||
7888 | { | ||
7889 | Eina_UStrbuf *buf; | ||
7890 | Evas_Object_Textblock_Node_Text *n1, *n2; | ||
7891 | Evas_Textblock_Cursor *cur2; | ||
7892 | |||
7893 | buf = eina_ustrbuf_new(); | ||
7894 | |||
7895 | if (!cur1 || !cur1->node) return NULL; | ||
7896 | if (!_cur2 || !_cur2->node) return NULL; | ||
7897 | if (cur1->obj != _cur2->obj) return NULL; | ||
7898 | if (evas_textblock_cursor_compare(cur1, _cur2) > 0) | ||
7899 | { | ||
7900 | const Evas_Textblock_Cursor *tc; | ||
7901 | |||
7902 | tc = cur1; | ||
7903 | cur1 = _cur2; | ||
7904 | _cur2 = tc; | ||
7905 | } | ||
7906 | n1 = cur1->node; | ||
7907 | n2 = _cur2->node; | ||
7908 | /* Work on a local copy of the cur */ | ||
7909 | cur2 = alloca(sizeof(Evas_Textblock_Cursor)); | ||
7910 | cur2->obj = _cur2->obj; | ||
7911 | evas_textblock_cursor_copy(_cur2, cur2); | ||
7912 | |||
7913 | |||
7914 | if (n1 == n2) | ||
7915 | { | ||
7916 | const Eina_Unicode *tmp; | ||
7917 | tmp = eina_ustrbuf_string_get(n1->unicode); | ||
7918 | eina_ustrbuf_append_length(buf, tmp + cur1->pos, cur2->pos - cur1->pos); | ||
7919 | } | ||
7920 | else | ||
7921 | { | ||
7922 | const Eina_Unicode *tmp; | ||
7923 | tmp = eina_ustrbuf_string_get(n1->unicode); | ||
7924 | eina_ustrbuf_append(buf, tmp + cur1->pos); | ||
7925 | n1 = _NODE_TEXT(EINA_INLIST_GET(n1)->next); | ||
7926 | while (n1 != n2) | ||
7927 | { | ||
7928 | tmp = eina_ustrbuf_string_get(n1->unicode); | ||
7929 | eina_ustrbuf_append_length(buf, tmp, | ||
7930 | eina_ustrbuf_length_get(n1->unicode)); | ||
7931 | n1 = _NODE_TEXT(EINA_INLIST_GET(n1)->next); | ||
7932 | } | ||
7933 | tmp = eina_ustrbuf_string_get(n2->unicode); | ||
7934 | eina_ustrbuf_append_length(buf, tmp, cur2->pos); | ||
7935 | } | ||
7936 | |||
7937 | /* Free and return */ | ||
7938 | { | ||
7939 | char *ret; | ||
7940 | ret = eina_unicode_unicode_to_utf8(eina_ustrbuf_string_get(buf), NULL); | ||
7941 | eina_ustrbuf_free(buf); | ||
7942 | return ret; | ||
7943 | } | ||
7944 | } | ||
7945 | |||
7946 | EAPI Eina_List * | ||
7947 | evas_textblock_cursor_range_formats_get(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *cur2) | ||
7948 | { | ||
7949 | Evas_Object *obj = cur1->obj; | ||
7950 | Eina_List *ret = NULL; | ||
7951 | Evas_Object_Textblock_Node_Text *n1, *n2; | ||
7952 | Evas_Object_Textblock_Node_Format *first, *last; | ||
7953 | TB_HEAD_RETURN(NULL); | ||
7954 | if (!cur1 || !cur1->node) return NULL; | ||
7955 | if (!cur2 || !cur2->node) return NULL; | ||
7956 | if (cur1->obj != cur2->obj) return NULL; | ||
7957 | if (evas_textblock_cursor_compare(cur1, cur2) > 0) | ||
7958 | { | ||
7959 | const Evas_Textblock_Cursor *tc; | ||
7960 | |||
7961 | tc = cur1; | ||
7962 | cur1 = cur2; | ||
7963 | cur2 = tc; | ||
7964 | } | ||
7965 | n1 = cur1->node; | ||
7966 | n2 = cur2->node; | ||
7967 | |||
7968 | /* FIXME: Change first and last getting to format_before_or_at_pos_get */ | ||
7969 | |||
7970 | last = n2->format_node; | ||
7971 | |||
7972 | /* If n2->format_node is NULL, we don't have formats in the tb/range. */ | ||
7973 | if (!last) | ||
7974 | return NULL; | ||
7975 | /* If the found format is on our text node, we should go to the last | ||
7976 | * one, otherwise, the one we found is good enough. */ | ||
7977 | if (last->text_node == n2) | ||
7978 | { | ||
7979 | Evas_Object_Textblock_Node_Format *fnode = last; | ||
7980 | while (fnode && (fnode->text_node == n2)) | ||
7981 | { | ||
7982 | last = fnode; | ||
7983 | fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); | ||
7984 | } | ||
7985 | } | ||
7986 | |||
7987 | /* If the first format node is within the range (i.e points to n1) or if | ||
7988 | * we have other formats in the range, go through them */ | ||
7989 | first = n1->format_node; | ||
7990 | if ((first->text_node == n1) || (first != last)) | ||
7991 | { | ||
7992 | Evas_Object_Textblock_Node_Format *fnode = first; | ||
7993 | /* Go to the first one in the range */ | ||
7994 | if (first->text_node != n1) | ||
7995 | { | ||
7996 | first = _NODE_FORMAT(EINA_INLIST_GET(first)->next); | ||
7997 | } | ||
7998 | |||
7999 | while (fnode) | ||
8000 | { | ||
8001 | ret = eina_list_append(ret, fnode); | ||
8002 | if (fnode == last) | ||
8003 | break; | ||
8004 | fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); | ||
8005 | } | ||
8006 | } | ||
8007 | |||
8008 | return ret; | ||
8009 | |||
8010 | } | ||
8011 | |||
8012 | EAPI char * | ||
8013 | evas_textblock_cursor_range_text_get(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *cur2, Evas_Textblock_Text_Type format) | ||
8014 | { | ||
8015 | if (format == EVAS_TEXTBLOCK_TEXT_MARKUP) | ||
8016 | return _evas_textblock_cursor_range_text_markup_get(cur1, cur2); | ||
8017 | else if (format == EVAS_TEXTBLOCK_TEXT_PLAIN) | ||
8018 | return _evas_textblock_cursor_range_text_plain_get(cur1, cur2); | ||
8019 | else | ||
8020 | return NULL; /* Not yet supported */ | ||
8021 | } | ||
8022 | |||
8023 | EAPI const char * | ||
8024 | evas_textblock_cursor_paragraph_text_get(const Evas_Textblock_Cursor *cur) | ||
8025 | { | ||
8026 | Evas_Textblock_Cursor cur1, cur2; | ||
8027 | if (!cur) return NULL; | ||
8028 | if (!cur->node) return NULL; | ||
8029 | if (cur->node->utf8) | ||
8030 | { | ||
8031 | free(cur->node->utf8); | ||
8032 | } | ||
8033 | cur1.obj = cur2.obj = cur->obj; | ||
8034 | cur1.node = cur2.node = cur->node; | ||
8035 | evas_textblock_cursor_paragraph_char_first(&cur1); | ||
8036 | evas_textblock_cursor_paragraph_char_last(&cur2); | ||
8037 | |||
8038 | cur->node->utf8 = evas_textblock_cursor_range_text_get(&cur1, &cur2, | ||
8039 | EVAS_TEXTBLOCK_TEXT_MARKUP); | ||
8040 | return cur->node->utf8; | ||
8041 | } | ||
8042 | |||
8043 | EAPI int | ||
8044 | evas_textblock_cursor_paragraph_text_length_get(const Evas_Textblock_Cursor *cur) | ||
8045 | { | ||
8046 | int len; | ||
8047 | if (!cur) return -1; | ||
8048 | if (!cur->node) return -1; | ||
8049 | len = eina_ustrbuf_length_get(cur->node->unicode); | ||
8050 | |||
8051 | if (EINA_INLIST_GET(cur->node)->next) | ||
8052 | return len - 1; /* Remove the paragraph separator */ | ||
8053 | else | ||
8054 | return len; | ||
8055 | } | ||
8056 | |||
8057 | EAPI const Evas_Object_Textblock_Node_Format * | ||
8058 | evas_textblock_cursor_format_get(const Evas_Textblock_Cursor *cur) | ||
8059 | { | ||
8060 | if (!cur) return NULL; | ||
8061 | if (!cur->node) return NULL; | ||
8062 | return _evas_textblock_cursor_node_format_at_pos_get(cur); | ||
8063 | } | ||
8064 | |||
8065 | EAPI const char * | ||
8066 | evas_textblock_node_format_text_get(const Evas_Object_Textblock_Node_Format *fmt) | ||
8067 | { | ||
8068 | static char *ret = NULL; | ||
8069 | char *tmp; | ||
8070 | |||
8071 | if (!fmt) return NULL; | ||
8072 | |||
8073 | if (ret) free(ret); | ||
8074 | ret = malloc(strlen(fmt->orig_format) + 2 + 1); | ||
8075 | tmp = ret; | ||
8076 | |||
8077 | if (fmt->opener && !fmt->own_closer) | ||
8078 | { | ||
8079 | *(tmp++) = '+'; | ||
8080 | *(tmp++) = ' '; | ||
8081 | } | ||
8082 | else if (!fmt->opener) | ||
8083 | { | ||
8084 | *(tmp++) = '-'; | ||
8085 | *(tmp++) = ' '; | ||
8086 | } | ||
8087 | strcpy(tmp, fmt->orig_format); | ||
8088 | return ret; | ||
8089 | } | ||
8090 | |||
8091 | EAPI void | ||
8092 | evas_textblock_cursor_at_format_set(Evas_Textblock_Cursor *cur, const Evas_Object_Textblock_Node_Format *fmt) | ||
8093 | { | ||
8094 | if (!fmt || !cur) return; | ||
8095 | cur->node = fmt->text_node; | ||
8096 | cur->pos = _evas_textblock_node_format_pos_get(fmt); | ||
8097 | } | ||
8098 | |||
8099 | EAPI Eina_Bool | ||
8100 | evas_textblock_cursor_format_is_visible_get(const Evas_Textblock_Cursor *cur) | ||
8101 | { | ||
8102 | const Eina_Unicode *text; | ||
8103 | |||
8104 | if (!cur) return EINA_FALSE; | ||
8105 | if (!cur->node) return EINA_FALSE; | ||
8106 | if (!evas_textblock_cursor_is_format(cur)) return EINA_FALSE; | ||
8107 | text = eina_ustrbuf_string_get(cur->node->unicode); | ||
8108 | return EVAS_TEXTBLOCK_IS_VISIBLE_FORMAT_CHAR(text[cur->pos]); | ||
8109 | } | ||
8110 | |||
8111 | EAPI int | ||
8112 | evas_textblock_cursor_geometry_get(const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch, Evas_BiDi_Direction *dir, Evas_Textblock_Cursor_Type ctype) | ||
8113 | { | ||
8114 | int ret = -1; | ||
8115 | const Evas_Textblock_Cursor *dir_cur; | ||
8116 | Evas_Textblock_Cursor cur2; | ||
8117 | Evas_Object_Textblock *o; | ||
8118 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
8119 | if (!o->formatted.valid) _relayout(cur->obj); | ||
8120 | |||
8121 | dir_cur = cur; | ||
8122 | if (ctype == EVAS_TEXTBLOCK_CURSOR_UNDER) | ||
8123 | { | ||
8124 | ret = evas_textblock_cursor_pen_geometry_get(cur, cx, cy, cw, ch); | ||
8125 | } | ||
8126 | else if (ctype == EVAS_TEXTBLOCK_CURSOR_BEFORE) | ||
8127 | { | ||
8128 | /* In the case of a "before cursor", we should get the coordinates | ||
8129 | * of just after the previous char (which in bidi text may not be | ||
8130 | * just before the current char). */ | ||
8131 | Evas_Coord x, y, h, w; | ||
8132 | Evas_Object_Textblock_Node_Format *fmt; | ||
8133 | |||
8134 | /* If it's at the end of the line, we want to get the position, not | ||
8135 | * the position of the previous */ | ||
8136 | if ((cur->pos > 0) && !_evas_textblock_cursor_is_at_the_end(cur)) | ||
8137 | { | ||
8138 | #ifdef BIDI_SUPPORT | ||
8139 | Eina_Bool before_char = EINA_FALSE; | ||
8140 | #endif | ||
8141 | cur2.obj = cur->obj; | ||
8142 | evas_textblock_cursor_copy(cur, &cur2); | ||
8143 | evas_textblock_cursor_char_prev(&cur2); | ||
8144 | |||
8145 | fmt = _evas_textblock_cursor_node_format_at_pos_get(&cur2); | ||
8146 | |||
8147 | if (!fmt || !_IS_LINE_SEPARATOR(fmt->format)) | ||
8148 | { | ||
8149 | dir_cur = &cur2; | ||
8150 | #ifdef BIDI_SUPPORT | ||
8151 | before_char = EINA_FALSE; | ||
8152 | #endif | ||
8153 | } | ||
8154 | #ifdef BIDI_SUPPORT | ||
8155 | else | ||
8156 | { | ||
8157 | before_char = EINA_TRUE; | ||
8158 | } | ||
8159 | #endif | ||
8160 | ret = evas_textblock_cursor_pen_geometry_get( | ||
8161 | dir_cur, &x, &y, &w, &h); | ||
8162 | #ifdef BIDI_SUPPORT | ||
8163 | /* Adjust if the char is an rtl char */ | ||
8164 | if (ret >= 0) | ||
8165 | { | ||
8166 | Eina_Bool is_rtl = EINA_FALSE; | ||
8167 | if (dir_cur->node->par->is_bidi) | ||
8168 | { | ||
8169 | Evas_Object_Textblock_Line *ln; | ||
8170 | Evas_Object_Textblock_Item *it; | ||
8171 | _find_layout_item_match(dir_cur, &ln, &it); | ||
8172 | if ((it->type == EVAS_TEXTBLOCK_ITEM_TEXT) && | ||
8173 | (_ITEM_TEXT(it)->text_props.bidi.dir == | ||
8174 | EVAS_BIDI_DIRECTION_RTL)) | ||
8175 | is_rtl = EINA_TRUE; | ||
8176 | else if ((it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) && | ||
8177 | (_ITEM_FORMAT(it)->bidi_dir == | ||
8178 | EVAS_BIDI_DIRECTION_RTL)) | ||
8179 | is_rtl = EINA_TRUE; | ||
8180 | } | ||
8181 | |||
8182 | if ((!before_char && is_rtl) || | ||
8183 | (before_char && !is_rtl)) | ||
8184 | { | ||
8185 | /* Just don't advance the width */ | ||
8186 | w = 0; | ||
8187 | } | ||
8188 | } | ||
8189 | #endif | ||
8190 | } | ||
8191 | else if (cur->pos == 0) | ||
8192 | { | ||
8193 | ret = evas_textblock_cursor_pen_geometry_get( | ||
8194 | dir_cur, &x, &y, &w, &h); | ||
8195 | #ifdef BIDI_SUPPORT | ||
8196 | Eina_Bool is_rtl = EINA_FALSE; | ||
8197 | if (dir_cur->node && dir_cur->node->par->is_bidi) | ||
8198 | { | ||
8199 | Evas_Object_Textblock_Line *ln; | ||
8200 | Evas_Object_Textblock_Item *it; | ||
8201 | _find_layout_item_match(dir_cur, &ln, &it); | ||
8202 | if ((it->type == EVAS_TEXTBLOCK_ITEM_TEXT) && | ||
8203 | (_ITEM_TEXT(it)->text_props.bidi.dir == | ||
8204 | EVAS_BIDI_DIRECTION_RTL)) | ||
8205 | is_rtl = EINA_TRUE; | ||
8206 | else if ((it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) && | ||
8207 | (_ITEM_FORMAT(it)->bidi_dir == | ||
8208 | EVAS_BIDI_DIRECTION_RTL)) | ||
8209 | is_rtl = EINA_TRUE; | ||
8210 | } | ||
8211 | |||
8212 | /* Adjust if the char is an rtl char */ | ||
8213 | if ((ret >= 0) && (!is_rtl)) | ||
8214 | { | ||
8215 | /* Just don't advance the width */ | ||
8216 | w = 0; | ||
8217 | } | ||
8218 | #endif | ||
8219 | } | ||
8220 | else | ||
8221 | { | ||
8222 | ret = evas_textblock_cursor_pen_geometry_get( | ||
8223 | dir_cur, &x, &y, &w, &h); | ||
8224 | } | ||
8225 | if (ret >= 0) | ||
8226 | { | ||
8227 | if (cx) *cx = x + w; | ||
8228 | if (cy) *cy = y; | ||
8229 | if (cw) *cw = 0; | ||
8230 | if (ch) *ch = h; | ||
8231 | } | ||
8232 | } | ||
8233 | |||
8234 | if (dir && dir_cur && dir_cur->node) | ||
8235 | { | ||
8236 | #ifdef BIDI_SUPPORT | ||
8237 | Eina_Bool is_rtl = EINA_FALSE; | ||
8238 | if (dir_cur->node->par->is_bidi) | ||
8239 | { | ||
8240 | Evas_Object_Textblock_Line *ln; | ||
8241 | Evas_Object_Textblock_Item *it; | ||
8242 | _find_layout_item_match(dir_cur, &ln, &it); | ||
8243 | if ((it->type == EVAS_TEXTBLOCK_ITEM_TEXT) && | ||
8244 | (_ITEM_TEXT(it)->text_props.bidi.dir == | ||
8245 | EVAS_BIDI_DIRECTION_RTL)) | ||
8246 | is_rtl = EINA_TRUE; | ||
8247 | else if ((it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) && | ||
8248 | (_ITEM_FORMAT(it)->bidi_dir == | ||
8249 | EVAS_BIDI_DIRECTION_RTL)) | ||
8250 | is_rtl = EINA_TRUE; | ||
8251 | } | ||
8252 | |||
8253 | if (_evas_textblock_cursor_is_at_the_end(dir_cur) && (dir_cur->pos > 0)) | ||
8254 | { | ||
8255 | *dir = (is_rtl) ? | ||
8256 | EVAS_BIDI_DIRECTION_RTL : EVAS_BIDI_DIRECTION_LTR; | ||
8257 | } | ||
8258 | else if (dir_cur->pos > 0) | ||
8259 | { | ||
8260 | *dir = (is_rtl) ? | ||
8261 | EVAS_BIDI_DIRECTION_RTL : EVAS_BIDI_DIRECTION_LTR; | ||
8262 | } | ||
8263 | else | ||
8264 | #endif | ||
8265 | { | ||
8266 | *dir = EVAS_BIDI_DIRECTION_LTR; | ||
8267 | } | ||
8268 | } | ||
8269 | return ret; | ||
8270 | } | ||
8271 | |||
8272 | /** | ||
8273 | * @internal | ||
8274 | * Returns the geometry/pen position (depending on query_func) of the char | ||
8275 | * at pos. | ||
8276 | * | ||
8277 | * @param cur the position of the char. | ||
8278 | * @param query_func the query function to use. | ||
8279 | * @param cx the x of the char (or pen_x in the case of pen position). | ||
8280 | * @param cy the y of the char. | ||
8281 | * @param cw the w of the char (or advance in the case pen position). | ||
8282 | * @param ch the h of the char. | ||
8283 | * @return line number of the char on success, -1 on error. | ||
8284 | */ | ||
8285 | static int | ||
8286 | _evas_textblock_cursor_char_pen_geometry_common_get(int (*query_func) (void *data, Evas_Font_Set *font, const Evas_Text_Props *intl_props, int pos, int *cx, int *cy, int *cw, int *ch), const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch) | ||
8287 | { | ||
8288 | Evas_Object_Textblock *o; | ||
8289 | Evas_Object_Textblock_Line *ln = NULL; | ||
8290 | Evas_Object_Textblock_Item *it = NULL; | ||
8291 | Evas_Object_Textblock_Text_Item *ti = NULL; | ||
8292 | Evas_Object_Textblock_Format_Item *fi = NULL; | ||
8293 | int x = 0, y = 0, w = 0, h = 0; | ||
8294 | int pos; | ||
8295 | Eina_Bool previous_format; | ||
8296 | |||
8297 | if (!cur) return -1; | ||
8298 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
8299 | if (!o->formatted.valid) _relayout(cur->obj); | ||
8300 | |||
8301 | if (!cur->node) | ||
8302 | { | ||
8303 | if (!o->text_nodes) | ||
8304 | { | ||
8305 | if (!o->paragraphs) return -1; | ||
8306 | ln = o->paragraphs->lines; | ||
8307 | if (!ln) return -1; | ||
8308 | if (cx) *cx = ln->x; | ||
8309 | if (cy) *cy = ln->par->y + ln->y; | ||
8310 | if (cw) *cw = ln->w; | ||
8311 | if (ch) *ch = ln->h; | ||
8312 | return ln->par->line_no + ln->line_no; | ||
8313 | } | ||
8314 | else | ||
8315 | return -1; | ||
8316 | } | ||
8317 | |||
8318 | previous_format = _find_layout_item_match(cur, &ln, &it); | ||
8319 | if (!it) | ||
8320 | { | ||
8321 | return -1; | ||
8322 | } | ||
8323 | if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
8324 | { | ||
8325 | ti = _ITEM_TEXT(it); | ||
8326 | } | ||
8327 | else | ||
8328 | { | ||
8329 | fi = _ITEM_FORMAT(it); | ||
8330 | } | ||
8331 | |||
8332 | if (ln && ti) | ||
8333 | { | ||
8334 | pos = cur->pos - ti->parent.text_pos; | ||
8335 | |||
8336 | if (pos < 0) pos = 0; | ||
8337 | if (ti->parent.format->font.font) | ||
8338 | { | ||
8339 | query_func(cur->ENDT, | ||
8340 | ti->parent.format->font.font, | ||
8341 | &ti->text_props, | ||
8342 | pos, | ||
8343 | &x, &y, &w, &h); | ||
8344 | } | ||
8345 | |||
8346 | x += ln->x + _ITEM(ti)->x; | ||
8347 | |||
8348 | if (x < ln->x) | ||
8349 | { | ||
8350 | x = ln->x; | ||
8351 | } | ||
8352 | y = ln->par->y + ln->y; | ||
8353 | h = ln->h; | ||
8354 | } | ||
8355 | else if (ln && fi) | ||
8356 | { | ||
8357 | if (previous_format) | ||
8358 | { | ||
8359 | if (_IS_LINE_SEPARATOR(fi->item)) | ||
8360 | { | ||
8361 | x = 0; | ||
8362 | y = ln->par->y + ln->y + ln->h; | ||
8363 | } | ||
8364 | else | ||
8365 | { | ||
8366 | #ifdef BIDI_SUPPORT | ||
8367 | if (ln->par->direction == EVAS_BIDI_DIRECTION_RTL) | ||
8368 | { | ||
8369 | x = ln->x; | ||
8370 | } | ||
8371 | else | ||
8372 | #endif | ||
8373 | { | ||
8374 | x = ln->x + ln->w; | ||
8375 | } | ||
8376 | y = ln->par->y + ln->y; | ||
8377 | } | ||
8378 | w = 0; | ||
8379 | h = ln->h; | ||
8380 | } | ||
8381 | else | ||
8382 | { | ||
8383 | x = ln->x + _ITEM(fi)->x; | ||
8384 | y = ln->par->y + ln->y; | ||
8385 | w = _ITEM(fi)->w; | ||
8386 | h = ln->h; | ||
8387 | } | ||
8388 | } | ||
8389 | else | ||
8390 | { | ||
8391 | return -1; | ||
8392 | } | ||
8393 | if (cx) *cx = x; | ||
8394 | if (cy) *cy = y; | ||
8395 | if (cw) *cw = w; | ||
8396 | if (ch) *ch = h; | ||
8397 | return ln->par->line_no + ln->line_no; | ||
8398 | } | ||
8399 | |||
8400 | EAPI int | ||
8401 | evas_textblock_cursor_char_geometry_get(const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch) | ||
8402 | { | ||
8403 | return _evas_textblock_cursor_char_pen_geometry_common_get( | ||
8404 | cur->ENFN->font_char_coords_get, cur, cx, cy, cw, ch); | ||
8405 | } | ||
8406 | |||
8407 | EAPI int | ||
8408 | evas_textblock_cursor_pen_geometry_get(const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch) | ||
8409 | { | ||
8410 | return _evas_textblock_cursor_char_pen_geometry_common_get( | ||
8411 | cur->ENFN->font_pen_coords_get, cur, cx, cy, cw, ch); | ||
8412 | } | ||
8413 | |||
8414 | EAPI int | ||
8415 | evas_textblock_cursor_line_geometry_get(const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch) | ||
8416 | { | ||
8417 | Evas_Object_Textblock *o; | ||
8418 | Evas_Object_Textblock_Line *ln = NULL; | ||
8419 | Evas_Object_Textblock_Item *it = NULL; | ||
8420 | int x, y, w, h; | ||
8421 | |||
8422 | if (!cur) return -1; | ||
8423 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
8424 | if (!o->formatted.valid) _relayout(cur->obj); | ||
8425 | if (!cur->node) | ||
8426 | { | ||
8427 | ln = o->paragraphs->lines; | ||
8428 | } | ||
8429 | else | ||
8430 | { | ||
8431 | _find_layout_item_match(cur, &ln, &it); | ||
8432 | } | ||
8433 | if (!ln) return -1; | ||
8434 | x = ln->x; | ||
8435 | y = ln->par->y + ln->y; | ||
8436 | w = ln->w; | ||
8437 | h = ln->h; | ||
8438 | if (cx) *cx = x; | ||
8439 | if (cy) *cy = y; | ||
8440 | if (cw) *cw = w; | ||
8441 | if (ch) *ch = h; | ||
8442 | return ln->par->line_no + ln->line_no; | ||
8443 | } | ||
8444 | |||
8445 | EAPI Eina_Bool | ||
8446 | evas_textblock_cursor_visible_range_get(Evas_Textblock_Cursor *start, Evas_Textblock_Cursor *end) | ||
8447 | { | ||
8448 | Evas *e; | ||
8449 | Evas_Coord cy, ch; | ||
8450 | Evas_Object *obj = start->obj; | ||
8451 | TB_HEAD_RETURN(EINA_FALSE); | ||
8452 | e = evas_object_evas_get(obj); | ||
8453 | cy = 0 - obj->cur.geometry.y; | ||
8454 | ch = e->viewport.h; | ||
8455 | evas_textblock_cursor_line_coord_set(start, cy); | ||
8456 | evas_textblock_cursor_line_coord_set(end, cy + ch); | ||
8457 | evas_textblock_cursor_line_char_last(end); | ||
8458 | |||
8459 | return EINA_TRUE; | ||
8460 | } | ||
8461 | |||
8462 | EAPI Eina_Bool | ||
8463 | evas_textblock_cursor_char_coord_set(Evas_Textblock_Cursor *cur, Evas_Coord x, Evas_Coord y) | ||
8464 | { | ||
8465 | Evas_Object_Textblock *o; | ||
8466 | Evas_Object_Textblock_Paragraph *found_par; | ||
8467 | Evas_Object_Textblock_Line *ln; | ||
8468 | Evas_Object_Textblock_Item *it = NULL; | ||
8469 | |||
8470 | if (!cur) return EINA_FALSE; | ||
8471 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
8472 | if (!o->formatted.valid) _relayout(cur->obj); | ||
8473 | x += o->style_pad.l; | ||
8474 | y += o->style_pad.t; | ||
8475 | |||
8476 | found_par = _layout_find_paragraph_by_y(o, y); | ||
8477 | if (found_par) | ||
8478 | { | ||
8479 | _layout_paragraph_render(o, found_par); | ||
8480 | EINA_INLIST_FOREACH(found_par->lines, ln) | ||
8481 | { | ||
8482 | if (ln->par->y + ln->y > y) break; | ||
8483 | if ((ln->par->y + ln->y <= y) && ((ln->par->y + ln->y + ln->h) > y)) | ||
8484 | { | ||
8485 | /* If before or after the line, go to start/end according | ||
8486 | * to paragraph direction. */ | ||
8487 | if (x < ln->x) | ||
8488 | { | ||
8489 | cur->pos = ln->items->text_pos; | ||
8490 | cur->node = found_par->text_node; | ||
8491 | if (found_par->direction == EVAS_BIDI_DIRECTION_RTL) | ||
8492 | { | ||
8493 | evas_textblock_cursor_line_char_last(cur); | ||
8494 | } | ||
8495 | else | ||
8496 | { | ||
8497 | evas_textblock_cursor_line_char_first(cur); | ||
8498 | } | ||
8499 | return EINA_TRUE; | ||
8500 | } | ||
8501 | else if (x >= ln->x + ln->w) | ||
8502 | { | ||
8503 | cur->pos = ln->items->text_pos; | ||
8504 | cur->node = found_par->text_node; | ||
8505 | if (found_par->direction == EVAS_BIDI_DIRECTION_RTL) | ||
8506 | { | ||
8507 | evas_textblock_cursor_line_char_first(cur); | ||
8508 | } | ||
8509 | else | ||
8510 | { | ||
8511 | evas_textblock_cursor_line_char_last(cur); | ||
8512 | } | ||
8513 | return EINA_TRUE; | ||
8514 | } | ||
8515 | |||
8516 | EINA_INLIST_FOREACH(ln->items, it) | ||
8517 | { | ||
8518 | if (((it->x + ln->x) <= x) && (((it->x + ln->x) + it->adv) > x)) | ||
8519 | { | ||
8520 | if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
8521 | { | ||
8522 | int pos; | ||
8523 | int cx, cy, cw, ch; | ||
8524 | Evas_Object_Textblock_Text_Item *ti; | ||
8525 | ti = _ITEM_TEXT(it); | ||
8526 | |||
8527 | pos = -1; | ||
8528 | if (ti->parent.format->font.font) | ||
8529 | pos = cur->ENFN->font_char_at_coords_get( | ||
8530 | cur->ENDT, | ||
8531 | ti->parent.format->font.font, | ||
8532 | &ti->text_props, | ||
8533 | x - it->x - ln->x, 0, | ||
8534 | &cx, &cy, &cw, &ch); | ||
8535 | if (pos < 0) | ||
8536 | return EINA_FALSE; | ||
8537 | cur->pos = pos + it->text_pos; | ||
8538 | cur->node = it->text_node; | ||
8539 | return EINA_TRUE; | ||
8540 | } | ||
8541 | else | ||
8542 | { | ||
8543 | Evas_Object_Textblock_Format_Item *fi; | ||
8544 | fi = _ITEM_FORMAT(it); | ||
8545 | cur->pos = fi->parent.text_pos; | ||
8546 | cur->node = found_par->text_node; | ||
8547 | return EINA_TRUE; | ||
8548 | } | ||
8549 | } | ||
8550 | } | ||
8551 | } | ||
8552 | } | ||
8553 | } | ||
8554 | else if (o->paragraphs && (y >= o->paragraphs->y + o->formatted.h)) | ||
8555 | { | ||
8556 | /* If we are after the last paragraph, use the last position in the | ||
8557 | * text. */ | ||
8558 | evas_textblock_cursor_paragraph_last(cur); | ||
8559 | return EINA_TRUE; | ||
8560 | } | ||
8561 | else if (o->paragraphs && (y < o->paragraphs->y)) | ||
8562 | { | ||
8563 | evas_textblock_cursor_paragraph_first(cur); | ||
8564 | return EINA_TRUE; | ||
8565 | } | ||
8566 | |||
8567 | return EINA_FALSE; | ||
8568 | } | ||
8569 | |||
8570 | EAPI int | ||
8571 | evas_textblock_cursor_line_coord_set(Evas_Textblock_Cursor *cur, Evas_Coord y) | ||
8572 | { | ||
8573 | Evas_Object_Textblock *o; | ||
8574 | Evas_Object_Textblock_Paragraph *found_par; | ||
8575 | Evas_Object_Textblock_Line *ln; | ||
8576 | |||
8577 | if (!cur) return -1; | ||
8578 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
8579 | if (!o->formatted.valid) _relayout(cur->obj); | ||
8580 | y += o->style_pad.t; | ||
8581 | |||
8582 | found_par = _layout_find_paragraph_by_y(o, y); | ||
8583 | |||
8584 | if (found_par) | ||
8585 | { | ||
8586 | _layout_paragraph_render(o, found_par); | ||
8587 | EINA_INLIST_FOREACH(found_par->lines, ln) | ||
8588 | { | ||
8589 | if (ln->par->y + ln->y > y) break; | ||
8590 | if ((ln->par->y + ln->y <= y) && ((ln->par->y + ln->y + ln->h) > y)) | ||
8591 | { | ||
8592 | evas_textblock_cursor_line_set(cur, ln->par->line_no + | ||
8593 | ln->line_no); | ||
8594 | return ln->par->line_no + ln->line_no; | ||
8595 | } | ||
8596 | } | ||
8597 | } | ||
8598 | else if (o->paragraphs && (y >= o->paragraphs->y + o->formatted.h)) | ||
8599 | { | ||
8600 | int line_no = 0; | ||
8601 | /* If we are after the last paragraph, use the last position in the | ||
8602 | * text. */ | ||
8603 | evas_textblock_cursor_paragraph_last(cur); | ||
8604 | if (cur->node && cur->node->par) | ||
8605 | { | ||
8606 | line_no = cur->node->par->line_no; | ||
8607 | if (cur->node->par->lines) | ||
8608 | { | ||
8609 | line_no += ((Evas_Object_Textblock_Line *) | ||
8610 | EINA_INLIST_GET(cur->node->par->lines)->last)->line_no; | ||
8611 | } | ||
8612 | } | ||
8613 | return line_no; | ||
8614 | } | ||
8615 | else if (o->paragraphs && (y < o->paragraphs->y)) | ||
8616 | { | ||
8617 | int line_no = 0; | ||
8618 | evas_textblock_cursor_paragraph_first(cur); | ||
8619 | if (cur->node && cur->node->par) | ||
8620 | { | ||
8621 | line_no = cur->node->par->line_no; | ||
8622 | } | ||
8623 | return line_no; | ||
8624 | } | ||
8625 | return -1; | ||
8626 | } | ||
8627 | |||
8628 | /** | ||
8629 | * @internal | ||
8630 | * Updates x and w according to the text direction, position in text and | ||
8631 | * if it's a special case switch | ||
8632 | * | ||
8633 | * @param ti the text item we are working on | ||
8634 | * @param x the current x (we get) and the x we return | ||
8635 | * @param w the current w (we get) and the w we return | ||
8636 | * @param start if this is the first item or not | ||
8637 | * @param switch_items toogles item switching (rtl cases) | ||
8638 | */ | ||
8639 | static void | ||
8640 | _evas_textblock_range_calc_x_w(const Evas_Object_Textblock_Item *it, | ||
8641 | Evas_Coord *x, Evas_Coord *w, Eina_Bool start, Eina_Bool switch_items) | ||
8642 | { | ||
8643 | if ((start && !switch_items) || (!start && switch_items)) | ||
8644 | { | ||
8645 | #ifdef BIDI_SUPPORT | ||
8646 | if (((it->type == EVAS_TEXTBLOCK_ITEM_TEXT) && | ||
8647 | _ITEM_TEXT(it)->text_props.bidi.dir == EVAS_BIDI_DIRECTION_RTL) | ||
8648 | || | ||
8649 | ((it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) && | ||
8650 | _ITEM_FORMAT(it)->bidi_dir == EVAS_BIDI_DIRECTION_RTL)) | ||
8651 | { | ||
8652 | *w = *x + *w; | ||
8653 | *x = 0; | ||
8654 | } | ||
8655 | else | ||
8656 | #endif | ||
8657 | { | ||
8658 | *w = it->adv - *x; | ||
8659 | } | ||
8660 | } | ||
8661 | else | ||
8662 | { | ||
8663 | #ifdef BIDI_SUPPORT | ||
8664 | if (((it->type == EVAS_TEXTBLOCK_ITEM_TEXT) && | ||
8665 | _ITEM_TEXT(it)->text_props.bidi.dir == EVAS_BIDI_DIRECTION_RTL) | ||
8666 | || | ||
8667 | ((it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) && | ||
8668 | _ITEM_FORMAT(it)->bidi_dir == EVAS_BIDI_DIRECTION_RTL)) | ||
8669 | { | ||
8670 | *x = *x + *w; | ||
8671 | *w = it->adv - *x; | ||
8672 | } | ||
8673 | else | ||
8674 | #endif | ||
8675 | { | ||
8676 | *w = *x; | ||
8677 | *x = 0; | ||
8678 | } | ||
8679 | } | ||
8680 | |||
8681 | } | ||
8682 | |||
8683 | /** | ||
8684 | * @internal | ||
8685 | * Returns the geometry of the range in line ln. Cur1 is the start cursor, | ||
8686 | * cur2 is the end cursor, NULL means from the start or to the end accordingly. | ||
8687 | * Assumes that ln is valid, and that at least one of cur1 and cur2 is not NULL. | ||
8688 | * | ||
8689 | * @param ln the line to work on. | ||
8690 | * @param cur1 the start cursor | ||
8691 | * @param cur2 the end cursor | ||
8692 | * @return Returns the geometry of the range | ||
8693 | */ | ||
8694 | static Eina_List * | ||
8695 | _evas_textblock_cursor_range_in_line_geometry_get( | ||
8696 | const Evas_Object_Textblock_Line *ln, const Evas_Textblock_Cursor *cur1, | ||
8697 | const Evas_Textblock_Cursor *cur2) | ||
8698 | { | ||
8699 | Evas_Object_Textblock_Item *it; | ||
8700 | Evas_Object_Textblock_Item *it1, *it2; | ||
8701 | Eina_List *rects = NULL; | ||
8702 | Evas_Textblock_Rectangle *tr; | ||
8703 | size_t start, end; | ||
8704 | Eina_Bool switch_items; | ||
8705 | const Evas_Textblock_Cursor *cur; | ||
8706 | |||
8707 | cur = (cur1) ? cur1 : cur2; | ||
8708 | |||
8709 | if (!cur) return NULL; | ||
8710 | |||
8711 | /* Find the first and last items */ | ||
8712 | it1 = it2 = NULL; | ||
8713 | start = end = 0; | ||
8714 | EINA_INLIST_FOREACH(ln->items, it) | ||
8715 | { | ||
8716 | size_t item_len; | ||
8717 | item_len = (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? | ||
8718 | _ITEM_TEXT(it)->text_props.text_len | ||
8719 | : 1; | ||
8720 | if ((!cur1 || (cur1->pos < it->text_pos + item_len)) && | ||
8721 | (!cur2 || (cur2->pos >= it->text_pos))) | ||
8722 | { | ||
8723 | if (!it1) | ||
8724 | { | ||
8725 | it1 = it; | ||
8726 | start = item_len; /* start stores the first item_len */ | ||
8727 | } | ||
8728 | it2 = it; | ||
8729 | end = item_len; /* end stores the last item_len */ | ||
8730 | } | ||
8731 | } | ||
8732 | |||
8733 | /* If we couldn't find even one item, return */ | ||
8734 | if (!it1) return NULL; | ||
8735 | |||
8736 | /* If the first item is logically before or equal the second item | ||
8737 | * we have to set start and end differently than in the other case */ | ||
8738 | if (it1->text_pos <= it2->text_pos) | ||
8739 | { | ||
8740 | start = (cur1) ? (cur1->pos - it1->text_pos) : 0; | ||
8741 | end = (cur2) ? (cur2->pos - it2->text_pos) : end; | ||
8742 | switch_items = EINA_FALSE; | ||
8743 | } | ||
8744 | else | ||
8745 | { | ||
8746 | start = (cur2) ? (cur2->pos - it1->text_pos) : start; | ||
8747 | end = (cur1) ? (cur1->pos - it2->text_pos) : 0; | ||
8748 | switch_items = EINA_TRUE; | ||
8749 | } | ||
8750 | |||
8751 | /* IMPORTANT: Don't use cur1/cur2 past this point (because they probably | ||
8752 | * don't make sense anymore. That's why there are start and end), | ||
8753 | * unless you know what you are doing */ | ||
8754 | |||
8755 | /* Special case when they share the same item and it's a text item */ | ||
8756 | if ((it1 == it2) && (it1->type == EVAS_TEXTBLOCK_ITEM_TEXT)) | ||
8757 | { | ||
8758 | Evas_Coord x1, w1, x2, w2; | ||
8759 | Evas_Coord x, w, y, h; | ||
8760 | Evas_Object_Textblock_Text_Item *ti; | ||
8761 | int ret = 0; | ||
8762 | |||
8763 | ti = _ITEM_TEXT(it1); | ||
8764 | if (ti->parent.format->font.font) | ||
8765 | { | ||
8766 | ret = cur->ENFN->font_pen_coords_get(cur->ENDT, | ||
8767 | ti->parent.format->font.font, | ||
8768 | &ti->text_props, | ||
8769 | start, | ||
8770 | &x1, &y, &w1, &h); | ||
8771 | } | ||
8772 | if (!ret) | ||
8773 | { | ||
8774 | return NULL; | ||
8775 | } | ||
8776 | ret = cur->ENFN->font_pen_coords_get(cur->ENDT, | ||
8777 | ti->parent.format->font.font, | ||
8778 | &ti->text_props, | ||
8779 | end, | ||
8780 | &x2, &y, &w2, &h); | ||
8781 | if (!ret) | ||
8782 | { | ||
8783 | return NULL; | ||
8784 | } | ||
8785 | |||
8786 | /* Make x2 the one on the right */ | ||
8787 | if (x2 < x1) | ||
8788 | { | ||
8789 | Evas_Coord tmp; | ||
8790 | tmp = x1; | ||
8791 | x1 = x2; | ||
8792 | x2 = tmp; | ||
8793 | |||
8794 | tmp = w1; | ||
8795 | w1 = w2; | ||
8796 | w2 = tmp; | ||
8797 | } | ||
8798 | |||
8799 | #ifdef BIDI_SUPPORT | ||
8800 | if (ti->text_props.bidi.dir == EVAS_BIDI_DIRECTION_RTL) | ||
8801 | { | ||
8802 | x = x1 + w1; | ||
8803 | w = x2 + w2 - x; | ||
8804 | } | ||
8805 | else | ||
8806 | #endif | ||
8807 | { | ||
8808 | x = x1; | ||
8809 | w = x2 - x1; | ||
8810 | } | ||
8811 | if (w > 0) | ||
8812 | { | ||
8813 | tr = calloc(1, sizeof(Evas_Textblock_Rectangle)); | ||
8814 | rects = eina_list_append(rects, tr); | ||
8815 | tr->x = ln->x + it1->x + x; | ||
8816 | tr->y = ln->par->y + ln->y; | ||
8817 | tr->h = ln->h; | ||
8818 | tr->w = w; | ||
8819 | } | ||
8820 | } | ||
8821 | else if (it1 != it2) | ||
8822 | { | ||
8823 | /* Get the middle items */ | ||
8824 | Evas_Coord min_x, max_x; | ||
8825 | Evas_Coord x, w; | ||
8826 | it = _ITEM(EINA_INLIST_GET(it1)->next); | ||
8827 | min_x = max_x = it->x; | ||
8828 | |||
8829 | if (it1->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
8830 | { | ||
8831 | Evas_Coord y, h; | ||
8832 | Evas_Object_Textblock_Text_Item *ti; | ||
8833 | int ret; | ||
8834 | ti = _ITEM_TEXT(it1); | ||
8835 | |||
8836 | ret = cur->ENFN->font_pen_coords_get(cur->ENDT, | ||
8837 | ti->parent.format->font.font, | ||
8838 | &ti->text_props, | ||
8839 | start, | ||
8840 | &x, &y, &w, &h); | ||
8841 | if (!ret) | ||
8842 | { | ||
8843 | /* BUG! Skip the first item */ | ||
8844 | x = w = 0; | ||
8845 | } | ||
8846 | else | ||
8847 | { | ||
8848 | _evas_textblock_range_calc_x_w(it1, &x, &w, EINA_TRUE, | ||
8849 | switch_items); | ||
8850 | } | ||
8851 | } | ||
8852 | else | ||
8853 | { | ||
8854 | x = 0; | ||
8855 | w = it1->w; | ||
8856 | _evas_textblock_range_calc_x_w(it1, &x, &w, EINA_TRUE, | ||
8857 | switch_items); | ||
8858 | } | ||
8859 | if (w > 0) | ||
8860 | { | ||
8861 | tr = calloc(1, sizeof(Evas_Textblock_Rectangle)); | ||
8862 | rects = eina_list_append(rects, tr); | ||
8863 | tr->x = ln->x + it1->x + x; | ||
8864 | tr->y = ln->par->y + ln->y; | ||
8865 | tr->h = ln->h; | ||
8866 | tr->w = w; | ||
8867 | } | ||
8868 | |||
8869 | while (it && (it != it2)) | ||
8870 | { | ||
8871 | max_x = it->x + it->adv; | ||
8872 | it = (Evas_Object_Textblock_Item *) EINA_INLIST_GET(it)->next; | ||
8873 | } | ||
8874 | if (min_x != max_x) | ||
8875 | { | ||
8876 | tr = calloc(1, sizeof(Evas_Textblock_Rectangle)); | ||
8877 | rects = eina_list_append(rects, tr); | ||
8878 | tr->x = ln->x + min_x; | ||
8879 | tr->y = ln->par->y + ln->y; | ||
8880 | tr->h = ln->h; | ||
8881 | tr->w = max_x - min_x; | ||
8882 | } | ||
8883 | if (it2->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
8884 | { | ||
8885 | Evas_Coord y, h; | ||
8886 | Evas_Object_Textblock_Text_Item *ti; | ||
8887 | int ret; | ||
8888 | ti = _ITEM_TEXT(it2); | ||
8889 | |||
8890 | ret = cur->ENFN->font_pen_coords_get(cur->ENDT, | ||
8891 | ti->parent.format->font.font, | ||
8892 | &ti->text_props, | ||
8893 | end, | ||
8894 | &x, &y, &w, &h); | ||
8895 | if (!ret) | ||
8896 | { | ||
8897 | /* BUG! skip the last item */ | ||
8898 | x = w = 0; | ||
8899 | } | ||
8900 | else | ||
8901 | { | ||
8902 | _evas_textblock_range_calc_x_w(it2, &x, &w, EINA_FALSE, | ||
8903 | switch_items); | ||
8904 | } | ||
8905 | } | ||
8906 | else | ||
8907 | { | ||
8908 | x = 0; | ||
8909 | w = it2->w; | ||
8910 | _evas_textblock_range_calc_x_w(it2, &x, &w, EINA_FALSE, | ||
8911 | switch_items); | ||
8912 | } | ||
8913 | if (w > 0) | ||
8914 | { | ||
8915 | tr = calloc(1, sizeof(Evas_Textblock_Rectangle)); | ||
8916 | rects = eina_list_append(rects, tr); | ||
8917 | tr->x = ln->x + it2->x + x; | ||
8918 | tr->y = ln->par->y + ln->y; | ||
8919 | tr->h = ln->h; | ||
8920 | tr->w = w; | ||
8921 | } | ||
8922 | } | ||
8923 | return rects; | ||
8924 | } | ||
8925 | |||
8926 | EAPI Eina_List * | ||
8927 | evas_textblock_cursor_range_geometry_get(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *cur2) | ||
8928 | { | ||
8929 | Evas_Object_Textblock *o; | ||
8930 | Evas_Object_Textblock_Line *ln1, *ln2; | ||
8931 | Evas_Object_Textblock_Item *it1, *it2; | ||
8932 | Eina_List *rects = NULL; | ||
8933 | Evas_Textblock_Rectangle *tr; | ||
8934 | |||
8935 | if (!cur1 || !cur1->node) return NULL; | ||
8936 | if (!cur2 || !cur2->node) return NULL; | ||
8937 | if (cur1->obj != cur2->obj) return NULL; | ||
8938 | o = (Evas_Object_Textblock *)(cur1->obj->object_data); | ||
8939 | if (!o->formatted.valid) _relayout(cur1->obj); | ||
8940 | if (evas_textblock_cursor_compare(cur1, cur2) > 0) | ||
8941 | { | ||
8942 | const Evas_Textblock_Cursor *tc; | ||
8943 | |||
8944 | tc = cur1; | ||
8945 | cur1 = cur2; | ||
8946 | cur2 = tc; | ||
8947 | } | ||
8948 | |||
8949 | ln1 = ln2 = NULL; | ||
8950 | it1 = it2 = NULL; | ||
8951 | _find_layout_item_match(cur1, &ln1, &it1); | ||
8952 | if (!ln1 || !it1) return NULL; | ||
8953 | _find_layout_item_match(cur2, &ln2, &it2); | ||
8954 | if (!ln2 || !it2) return NULL; | ||
8955 | |||
8956 | if (ln1 == ln2) | ||
8957 | { | ||
8958 | rects = _evas_textblock_cursor_range_in_line_geometry_get(ln1, | ||
8959 | cur1, cur2); | ||
8960 | } | ||
8961 | else | ||
8962 | { | ||
8963 | Evas_Object_Textblock_Line *plni, *lni; | ||
8964 | Eina_List *rects2 = NULL; | ||
8965 | /* Handle the first line */ | ||
8966 | rects = _evas_textblock_cursor_range_in_line_geometry_get(ln1, | ||
8967 | cur1, NULL); | ||
8968 | |||
8969 | /* Handle the lines between the first and the last line */ | ||
8970 | lni = (Evas_Object_Textblock_Line *) EINA_INLIST_GET(ln1)->next; | ||
8971 | if (!lni && (ln1->par != ln2->par)) | ||
8972 | { | ||
8973 | lni = ((Evas_Object_Textblock_Paragraph *) | ||
8974 | EINA_INLIST_GET(ln1->par)->next)->lines; | ||
8975 | } | ||
8976 | while (lni && (lni != ln2)) | ||
8977 | { | ||
8978 | tr = calloc(1, sizeof(Evas_Textblock_Rectangle)); | ||
8979 | rects = eina_list_append(rects, tr); | ||
8980 | tr->x = lni->x; | ||
8981 | tr->y = lni->par->y + lni->y; | ||
8982 | tr->h = lni->h; | ||
8983 | tr->w = lni->w; | ||
8984 | plni = lni; | ||
8985 | lni = (Evas_Object_Textblock_Line *) EINA_INLIST_GET(lni)->next; | ||
8986 | if (!lni && (plni->par != ln2->par)) | ||
8987 | { | ||
8988 | lni = ((Evas_Object_Textblock_Paragraph *) | ||
8989 | EINA_INLIST_GET(plni->par)->next)->lines; | ||
8990 | } | ||
8991 | } | ||
8992 | rects2 = _evas_textblock_cursor_range_in_line_geometry_get(ln2, | ||
8993 | NULL, cur2); | ||
8994 | rects = eina_list_merge(rects, rects2); | ||
8995 | } | ||
8996 | return rects; | ||
8997 | } | ||
8998 | |||
8999 | EAPI Eina_Bool | ||
9000 | evas_textblock_cursor_format_item_geometry_get(const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch) | ||
9001 | { | ||
9002 | Evas_Object_Textblock *o; | ||
9003 | Evas_Object_Textblock_Line *ln = NULL; | ||
9004 | Evas_Object_Textblock_Format_Item *fi; | ||
9005 | Evas_Object_Textblock_Item *it = NULL; | ||
9006 | Evas_Coord x, y, w, h; | ||
9007 | |||
9008 | if (!cur || !evas_textblock_cursor_format_is_visible_get(cur)) return EINA_FALSE; | ||
9009 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
9010 | if (!o->formatted.valid) _relayout(cur->obj); | ||
9011 | if (!evas_textblock_cursor_format_is_visible_get(cur)) return EINA_FALSE; | ||
9012 | _find_layout_item_line_match(cur->obj, cur->node, cur->pos, &ln, &it); | ||
9013 | fi = _ITEM_FORMAT(it); | ||
9014 | if ((!ln) || (!fi)) return EINA_FALSE; | ||
9015 | x = ln->x + fi->parent.x; | ||
9016 | y = ln->par->y + ln->y + ln->baseline + fi->y; | ||
9017 | w = fi->parent.w; | ||
9018 | h = fi->parent.h; | ||
9019 | if (cx) *cx = x; | ||
9020 | if (cy) *cy = y; | ||
9021 | if (cw) *cw = w; | ||
9022 | if (ch) *ch = h; | ||
9023 | return EINA_TRUE; | ||
9024 | } | ||
9025 | |||
9026 | EAPI Eina_Bool | ||
9027 | evas_textblock_cursor_eol_get(const Evas_Textblock_Cursor *cur) | ||
9028 | { | ||
9029 | Eina_Bool ret = EINA_FALSE; | ||
9030 | Evas_Textblock_Cursor cur2; | ||
9031 | if (!cur) return EINA_FALSE; | ||
9032 | |||
9033 | cur2.obj = cur->obj; | ||
9034 | evas_textblock_cursor_copy(cur, &cur2); | ||
9035 | evas_textblock_cursor_line_char_last(&cur2); | ||
9036 | if (cur2.pos == cur->pos) | ||
9037 | { | ||
9038 | ret = EINA_TRUE; | ||
9039 | } | ||
9040 | return ret; | ||
9041 | } | ||
9042 | |||
9043 | /* general controls */ | ||
9044 | EAPI Eina_Bool | ||
9045 | evas_object_textblock_line_number_geometry_get(const Evas_Object *obj, int line, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch) | ||
9046 | { | ||
9047 | Evas_Object_Textblock_Line *ln; | ||
9048 | |||
9049 | TB_HEAD_RETURN(0); | ||
9050 | ln = _find_layout_line_num(obj, line); | ||
9051 | if (!ln) return EINA_FALSE; | ||
9052 | if (cx) *cx = ln->x; | ||
9053 | if (cy) *cy = ln->par->y + ln->y; | ||
9054 | if (cw) *cw = ln->w; | ||
9055 | if (ch) *ch = ln->h; | ||
9056 | return EINA_TRUE; | ||
9057 | } | ||
9058 | |||
9059 | EAPI void | ||
9060 | evas_object_textblock_clear(Evas_Object *obj) | ||
9061 | { | ||
9062 | Eina_List *l; | ||
9063 | Evas_Textblock_Cursor *cur; | ||
9064 | |||
9065 | TB_HEAD(); | ||
9066 | if (o->paragraphs) | ||
9067 | { | ||
9068 | _paragraphs_free(obj, o->paragraphs); | ||
9069 | o->paragraphs = NULL; | ||
9070 | } | ||
9071 | |||
9072 | _nodes_clear(obj); | ||
9073 | o->cursor->node = NULL; | ||
9074 | o->cursor->pos = 0; | ||
9075 | EINA_LIST_FOREACH(o->cursors, l, cur) | ||
9076 | { | ||
9077 | cur->node = NULL; | ||
9078 | cur->pos = 0; | ||
9079 | |||
9080 | } | ||
9081 | _evas_textblock_changed(o, obj); | ||
9082 | } | ||
9083 | |||
9084 | EAPI void | ||
9085 | evas_object_textblock_size_formatted_get(const Evas_Object *obj, Evas_Coord *w, Evas_Coord *h) | ||
9086 | { | ||
9087 | TB_HEAD(); | ||
9088 | if (!o->formatted.valid) _relayout(obj); | ||
9089 | if (w) *w = o->formatted.w; | ||
9090 | if (h) *h = o->formatted.h; | ||
9091 | } | ||
9092 | |||
9093 | static void | ||
9094 | _size_native_calc_line_finalize(const Evas_Object *obj, Eina_List *items, | ||
9095 | Evas_Coord *ascent, Evas_Coord *descent, Evas_Coord *w) | ||
9096 | { | ||
9097 | Evas_Object_Textblock_Item *it; | ||
9098 | Eina_List *i; | ||
9099 | |||
9100 | it = eina_list_data_get(items); | ||
9101 | *w = 0; | ||
9102 | |||
9103 | if (it) | ||
9104 | { | ||
9105 | /* If there are no text items yet, calc ascent/descent | ||
9106 | * according to the current format. */ | ||
9107 | if (*ascent + *descent == 0) | ||
9108 | _layout_format_ascent_descent_adjust(obj, ascent, descent, | ||
9109 | it->format); | ||
9110 | |||
9111 | /* Add margins. */ | ||
9112 | if (it->format) | ||
9113 | *w = it->format->margin.l + it->format->margin.r; | ||
9114 | } | ||
9115 | |||
9116 | |||
9117 | /* Adjust all the item sizes according to the final line size, | ||
9118 | * and update the x positions of all the items of the line. */ | ||
9119 | EINA_LIST_FOREACH(items, i, it) | ||
9120 | { | ||
9121 | if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) | ||
9122 | { | ||
9123 | Evas_Coord fw, fh, fy; | ||
9124 | |||
9125 | Evas_Object_Textblock_Format_Item *fi = _ITEM_FORMAT(it); | ||
9126 | if (!fi->formatme) goto loop_advance; | ||
9127 | _layout_calculate_format_item_size(obj, fi, ascent, | ||
9128 | descent, &fy, &fw, &fh); | ||
9129 | } | ||
9130 | |||
9131 | loop_advance: | ||
9132 | *w += it->adv; | ||
9133 | } | ||
9134 | } | ||
9135 | |||
9136 | /* FIXME: doc */ | ||
9137 | static void | ||
9138 | _size_native_calc_paragraph_size(const Evas_Object *obj, | ||
9139 | const Evas_Object_Textblock *o, | ||
9140 | const Evas_Object_Textblock_Paragraph *par, | ||
9141 | Evas_Coord *_w, Evas_Coord *_h) | ||
9142 | { | ||
9143 | Eina_List *i; | ||
9144 | Evas_Object_Textblock_Item *it; | ||
9145 | Eina_List *line_items = NULL; | ||
9146 | Evas_Coord w = 0, y = 0, wmax = 0, h = 0, ascent = 0, descent = 0; | ||
9147 | |||
9148 | EINA_LIST_FOREACH(par->logical_items, i, it) | ||
9149 | { | ||
9150 | line_items = eina_list_append(line_items, it); | ||
9151 | if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) | ||
9152 | { | ||
9153 | Evas_Object_Textblock_Format_Item *fi = _ITEM_FORMAT(it); | ||
9154 | if (fi->item && (_IS_LINE_SEPARATOR(fi->item) || | ||
9155 | _IS_PARAGRAPH_SEPARATOR(o, fi->item))) | ||
9156 | { | ||
9157 | _size_native_calc_line_finalize(obj, line_items, &ascent, | ||
9158 | &descent, &w); | ||
9159 | |||
9160 | if (ascent + descent > h) | ||
9161 | h = ascent + descent; | ||
9162 | |||
9163 | y += h; | ||
9164 | if (w > wmax) | ||
9165 | wmax = w; | ||
9166 | h = 0; | ||
9167 | ascent = descent = 0; | ||
9168 | line_items = eina_list_free(line_items); | ||
9169 | } | ||
9170 | else | ||
9171 | { | ||
9172 | Evas_Coord fw, fh, fy; | ||
9173 | /* If there are no text items yet, calc ascent/descent | ||
9174 | * according to the current format. */ | ||
9175 | if (it && (ascent + descent == 0)) | ||
9176 | _layout_format_ascent_descent_adjust(obj, &ascent, | ||
9177 | &descent, it->format); | ||
9178 | |||
9179 | _layout_calculate_format_item_size(obj, fi, &ascent, | ||
9180 | &descent, &fy, &fw, &fh); | ||
9181 | } | ||
9182 | } | ||
9183 | else | ||
9184 | { | ||
9185 | _layout_format_ascent_descent_adjust(obj, &ascent, | ||
9186 | &descent, it->format); | ||
9187 | } | ||
9188 | } | ||
9189 | |||
9190 | _size_native_calc_line_finalize(obj, line_items, &ascent, &descent, &w); | ||
9191 | |||
9192 | line_items = eina_list_free(line_items); | ||
9193 | |||
9194 | /* Do the last addition */ | ||
9195 | if (ascent + descent > h) | ||
9196 | h = ascent + descent; | ||
9197 | |||
9198 | if (w > wmax) | ||
9199 | wmax = w; | ||
9200 | |||
9201 | *_h = y + h; | ||
9202 | *_w = wmax; | ||
9203 | } | ||
9204 | |||
9205 | EAPI void | ||
9206 | evas_object_textblock_size_native_get(const Evas_Object *obj, Evas_Coord *w, Evas_Coord *h) | ||
9207 | { | ||
9208 | TB_HEAD(); | ||
9209 | if (!o->native.valid) | ||
9210 | { | ||
9211 | Evas_Coord wmax = 0, hmax = 0; | ||
9212 | Evas_Object_Textblock_Paragraph *par; | ||
9213 | /* We just want the layout objects to update, should probably | ||
9214 | * split that. */ | ||
9215 | if (!o->formatted.valid) _relayout(obj); | ||
9216 | EINA_INLIST_FOREACH(o->paragraphs, par) | ||
9217 | { | ||
9218 | Evas_Coord tw, th; | ||
9219 | _size_native_calc_paragraph_size(obj, o, par, &tw, &th); | ||
9220 | if (tw > wmax) | ||
9221 | wmax = tw; | ||
9222 | hmax += th; | ||
9223 | } | ||
9224 | |||
9225 | o->native.w = wmax; | ||
9226 | o->native.h = hmax; | ||
9227 | |||
9228 | o->native.valid = 1; | ||
9229 | o->content_changed = 0; | ||
9230 | o->format_changed = EINA_FALSE; | ||
9231 | } | ||
9232 | if (w) *w = o->native.w; | ||
9233 | if (h) *h = o->native.h; | ||
9234 | } | ||
9235 | |||
9236 | EAPI void | ||
9237 | evas_object_textblock_style_insets_get(const Evas_Object *obj, Evas_Coord *l, Evas_Coord *r, Evas_Coord *t, Evas_Coord *b) | ||
9238 | { | ||
9239 | TB_HEAD(); | ||
9240 | if (!o->formatted.valid) _relayout(obj); | ||
9241 | if (l) *l = o->style_pad.l; | ||
9242 | if (r) *r = o->style_pad.r; | ||
9243 | if (t) *t = o->style_pad.t; | ||
9244 | if (b) *b = o->style_pad.b; | ||
9245 | } | ||
9246 | |||
9247 | /** @internal | ||
9248 | * FIXME: DELETE ME! DELETE ME! | ||
9249 | * This is an ugly workaround to get around the fact that | ||
9250 | * evas_object_textblock_coords_recalc isn't really called when it's supposed | ||
9251 | * to. When that bug is fixed please remove this. */ | ||
9252 | static void | ||
9253 | _workaround_object_coords_recalc(void *data __UNUSED__, Evas *e __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__) | ||
9254 | { | ||
9255 | evas_object_textblock_coords_recalc(obj); | ||
9256 | } | ||
9257 | |||
9258 | /* all nice and private */ | ||
9259 | static void | ||
9260 | evas_object_textblock_init(Evas_Object *obj) | ||
9261 | { | ||
9262 | Evas_Object_Textblock *o; | ||
9263 | #ifdef HAVE_LINEBREAK | ||
9264 | static Eina_Bool linebreak_init = EINA_FALSE; | ||
9265 | if (!linebreak_init) | ||
9266 | { | ||
9267 | linebreak_init = EINA_TRUE; | ||
9268 | init_linebreak(); | ||
9269 | init_wordbreak(); | ||
9270 | } | ||
9271 | #endif | ||
9272 | |||
9273 | /* alloc image ob, setup methods and default values */ | ||
9274 | obj->object_data = evas_object_textblock_new(); | ||
9275 | /* set up default settings for this kind of object */ | ||
9276 | obj->cur.color.r = 255; | ||
9277 | obj->cur.color.g = 255; | ||
9278 | obj->cur.color.b = 255; | ||
9279 | obj->cur.color.a = 255; | ||
9280 | obj->cur.geometry.x = 0.0; | ||
9281 | obj->cur.geometry.y = 0.0; | ||
9282 | obj->cur.geometry.w = 0.0; | ||
9283 | obj->cur.geometry.h = 0.0; | ||
9284 | obj->cur.layer = 0; | ||
9285 | /* set up object-specific settings */ | ||
9286 | obj->prev = obj->cur; | ||
9287 | /* set up methods (compulsory) */ | ||
9288 | obj->func = &object_func; | ||
9289 | obj->type = o_type; | ||
9290 | |||
9291 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
9292 | o->cursor->obj = obj; | ||
9293 | o->legacy_newline = EINA_TRUE; | ||
9294 | evas_object_event_callback_priority_add(obj, EVAS_CALLBACK_RESIZE, -1000, | ||
9295 | _workaround_object_coords_recalc, NULL); | ||
9296 | } | ||
9297 | |||
9298 | static void * | ||
9299 | evas_object_textblock_new(void) | ||
9300 | { | ||
9301 | Evas_Object_Textblock *o; | ||
9302 | |||
9303 | /* alloc obj private data */ | ||
9304 | EVAS_MEMPOOL_INIT(_mp_obj, "evas_object_textblock", Evas_Object_Textblock, 64, NULL); | ||
9305 | o = EVAS_MEMPOOL_ALLOC(_mp_obj, Evas_Object_Textblock); | ||
9306 | if (!o) return NULL; | ||
9307 | EVAS_MEMPOOL_PREP(_mp_obj, o, Evas_Object_Textblock); | ||
9308 | o->magic = MAGIC_OBJ_TEXTBLOCK; | ||
9309 | o->cursor = calloc(1, sizeof(Evas_Textblock_Cursor)); | ||
9310 | _format_command_init(); | ||
9311 | return o; | ||
9312 | } | ||
9313 | |||
9314 | static void | ||
9315 | evas_object_textblock_free(Evas_Object *obj) | ||
9316 | { | ||
9317 | Evas_Object_Textblock *o; | ||
9318 | |||
9319 | evas_object_textblock_clear(obj); | ||
9320 | evas_object_textblock_style_set(obj, NULL); | ||
9321 | while (evas_object_textblock_style_user_peek(obj)) | ||
9322 | { | ||
9323 | evas_object_textblock_style_user_pop(obj); | ||
9324 | } | ||
9325 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
9326 | free(o->cursor); | ||
9327 | while (o->cursors) | ||
9328 | { | ||
9329 | Evas_Textblock_Cursor *cur; | ||
9330 | |||
9331 | cur = (Evas_Textblock_Cursor *)o->cursors->data; | ||
9332 | o->cursors = eina_list_remove_list(o->cursors, o->cursors); | ||
9333 | free(cur); | ||
9334 | } | ||
9335 | if (o->repch) eina_stringshare_del(o->repch); | ||
9336 | if (o->ellip_ti) _item_free(obj, NULL, _ITEM(o->ellip_ti)); | ||
9337 | o->magic = 0; | ||
9338 | EVAS_MEMPOOL_FREE(_mp_obj, o); | ||
9339 | _format_command_shutdown(); | ||
9340 | } | ||
9341 | |||
9342 | |||
9343 | static void | ||
9344 | evas_object_textblock_render(Evas_Object *obj, void *output, void *context, void *surface, int x, int y) | ||
9345 | { | ||
9346 | Evas_Object_Textblock_Paragraph *par, *start = NULL; | ||
9347 | Evas_Object_Textblock_Line *ln; | ||
9348 | Evas_Object_Textblock *o; | ||
9349 | int i, j; | ||
9350 | int cx, cy, cw, ch, clip; | ||
9351 | const char vals[5][5] = | ||
9352 | { | ||
9353 | {0, 1, 2, 1, 0}, | ||
9354 | {1, 3, 4, 3, 1}, | ||
9355 | {2, 4, 5, 4, 2}, | ||
9356 | {1, 3, 4, 3, 1}, | ||
9357 | {0, 1, 2, 1, 0} | ||
9358 | }; | ||
9359 | |||
9360 | /* render object to surface with context, and offxet by x,y */ | ||
9361 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
9362 | obj->layer->evas->engine.func->context_multiplier_unset(output, | ||
9363 | context); | ||
9364 | /* FIXME: This clipping is just until we fix inset handling correctly. */ | ||
9365 | ENFN->context_clip_clip(output, context, | ||
9366 | obj->cur.geometry.x + x, | ||
9367 | obj->cur.geometry.y + y, | ||
9368 | obj->cur.geometry.w, | ||
9369 | obj->cur.geometry.h); | ||
9370 | clip = ENFN->context_clip_get(output, context, &cx, &cy, &cw, &ch); | ||
9371 | /* If there are no paragraphs and thus there are no lines, | ||
9372 | * there's nothing left to do. */ | ||
9373 | if (!o->paragraphs) return; | ||
9374 | |||
9375 | #define ITEM_WALK() \ | ||
9376 | EINA_INLIST_FOREACH(start, par) \ | ||
9377 | { \ | ||
9378 | if (!par->visible) continue; \ | ||
9379 | if (clip) \ | ||
9380 | { \ | ||
9381 | if ((obj->cur.geometry.y + y + par->y + par->h) < (cy - 20)) \ | ||
9382 | continue; \ | ||
9383 | if ((obj->cur.geometry.y + y + par->y) > (cy + ch + 20)) \ | ||
9384 | break; \ | ||
9385 | } \ | ||
9386 | _layout_paragraph_render(o, par); \ | ||
9387 | EINA_INLIST_FOREACH(par->lines, ln) \ | ||
9388 | { \ | ||
9389 | Evas_Object_Textblock_Item *itr; \ | ||
9390 | \ | ||
9391 | if (clip) \ | ||
9392 | { \ | ||
9393 | if ((obj->cur.geometry.y + y + par->y + ln->y + ln->h) < (cy - 20)) \ | ||
9394 | continue; \ | ||
9395 | if ((obj->cur.geometry.y + y + par->y + ln->y) > (cy + ch + 20)) \ | ||
9396 | break; \ | ||
9397 | } \ | ||
9398 | EINA_INLIST_FOREACH(ln->items, itr) \ | ||
9399 | { \ | ||
9400 | Evas_Coord yoff; \ | ||
9401 | yoff = ln->baseline; \ | ||
9402 | if (itr->format->valign != -1.0) \ | ||
9403 | { \ | ||
9404 | yoff += itr->format->valign * (ln->h - itr->h); \ | ||
9405 | } \ | ||
9406 | if (clip) \ | ||
9407 | { \ | ||
9408 | if ((obj->cur.geometry.x + x + ln->x + itr->x + itr->w) < (cx - 20)) \ | ||
9409 | continue; \ | ||
9410 | if ((obj->cur.geometry.x + x + ln->x + itr->x) > (cx + cw + 20)) \ | ||
9411 | break; \ | ||
9412 | } \ | ||
9413 | if ((ln->x + itr->x + itr->w) <= 0) continue; \ | ||
9414 | if (ln->x + itr->x > obj->cur.geometry.w) break; \ | ||
9415 | do | ||
9416 | |||
9417 | #define ITEM_WALK_END() \ | ||
9418 | while (0); \ | ||
9419 | } \ | ||
9420 | } \ | ||
9421 | } \ | ||
9422 | do {} while(0) | ||
9423 | #define COLOR_SET(col) \ | ||
9424 | ENFN->context_color_set(output, context, \ | ||
9425 | (obj->cur.cache.clip.r * ti->parent.format->color.col.r) / 255, \ | ||
9426 | (obj->cur.cache.clip.g * ti->parent.format->color.col.g) / 255, \ | ||
9427 | (obj->cur.cache.clip.b * ti->parent.format->color.col.b) / 255, \ | ||
9428 | (obj->cur.cache.clip.a * ti->parent.format->color.col.a) / 255); | ||
9429 | #define COLOR_SET_AMUL(col, amul) \ | ||
9430 | ENFN->context_color_set(output, context, \ | ||
9431 | (obj->cur.cache.clip.r * ti->parent.format->color.col.r * (amul)) / 65025, \ | ||
9432 | (obj->cur.cache.clip.g * ti->parent.format->color.col.g * (amul)) / 65025, \ | ||
9433 | (obj->cur.cache.clip.b * ti->parent.format->color.col.b * (amul)) / 65025, \ | ||
9434 | (obj->cur.cache.clip.a * ti->parent.format->color.col.a * (amul)) / 65025); | ||
9435 | #define DRAW_TEXT(ox, oy) \ | ||
9436 | if (ti->parent.format->font.font) ENFN->font_draw(output, context, surface, ti->parent.format->font.font, \ | ||
9437 | obj->cur.geometry.x + ln->x + ti->parent.x + x + (ox), \ | ||
9438 | obj->cur.geometry.y + ln->par->y + ln->y + yoff + y + (oy), \ | ||
9439 | ti->parent.w, ti->parent.h, ti->parent.w, ti->parent.h, \ | ||
9440 | &ti->text_props); | ||
9441 | |||
9442 | /* backing */ | ||
9443 | #define DRAW_RECT(ox, oy, ow, oh, or, og, ob, oa) \ | ||
9444 | do \ | ||
9445 | { \ | ||
9446 | ENFN->context_color_set(output, \ | ||
9447 | context, \ | ||
9448 | (obj->cur.cache.clip.r * or) / 255, \ | ||
9449 | (obj->cur.cache.clip.g * og) / 255, \ | ||
9450 | (obj->cur.cache.clip.b * ob) / 255, \ | ||
9451 | (obj->cur.cache.clip.a * oa) / 255); \ | ||
9452 | ENFN->rectangle_draw(output, \ | ||
9453 | context, \ | ||
9454 | surface, \ | ||
9455 | obj->cur.geometry.x + ln->x + x + (ox), \ | ||
9456 | obj->cur.geometry.y + ln->par->y + ln->y + y + (oy), \ | ||
9457 | (ow), \ | ||
9458 | (oh)); \ | ||
9459 | } \ | ||
9460 | while (0) | ||
9461 | |||
9462 | #define DRAW_FORMAT_DASHED(oname, oy, oh, dw, dp) \ | ||
9463 | do \ | ||
9464 | { \ | ||
9465 | if (itr->format->oname) \ | ||
9466 | { \ | ||
9467 | unsigned char _or, _og, _ob, _oa; \ | ||
9468 | int _ind, _dx = 0, _dn, _dr; \ | ||
9469 | _or = itr->format->color.oname.r; \ | ||
9470 | _og = itr->format->color.oname.g; \ | ||
9471 | _ob = itr->format->color.oname.b; \ | ||
9472 | _oa = itr->format->color.oname.a; \ | ||
9473 | if (!EINA_INLIST_GET(itr)->next) \ | ||
9474 | { \ | ||
9475 | _dn = itr->w / (dw + dp); \ | ||
9476 | _dr = itr->w % (dw + dp); \ | ||
9477 | } \ | ||
9478 | else \ | ||
9479 | { \ | ||
9480 | _dn = itr->adv / (dw + dp); \ | ||
9481 | _dr = itr->adv % (dw + dp); \ | ||
9482 | } \ | ||
9483 | if (_dr > dw) _dr = dw; \ | ||
9484 | for (_ind = 0 ; _ind < _dn ; _ind++) \ | ||
9485 | { \ | ||
9486 | DRAW_RECT(itr->x + _dx, oy, dw, oh, _or, _og, _ob, _oa); \ | ||
9487 | _dx += dw + dp; \ | ||
9488 | } \ | ||
9489 | DRAW_RECT(itr->x + _dx, oy, _dr, oh, _or, _og, _ob, _oa); \ | ||
9490 | } \ | ||
9491 | } \ | ||
9492 | while (0) | ||
9493 | |||
9494 | #define DRAW_FORMAT(oname, oy, oh) \ | ||
9495 | do \ | ||
9496 | { \ | ||
9497 | if (itr->format->oname) \ | ||
9498 | { \ | ||
9499 | unsigned char _or, _og, _ob, _oa; \ | ||
9500 | _or = itr->format->color.oname.r; \ | ||
9501 | _og = itr->format->color.oname.g; \ | ||
9502 | _ob = itr->format->color.oname.b; \ | ||
9503 | _oa = itr->format->color.oname.a; \ | ||
9504 | if (!EINA_INLIST_GET(itr)->next) \ | ||
9505 | { \ | ||
9506 | DRAW_RECT(itr->x, oy, itr->w, oh, _or, _og, _ob, _oa); \ | ||
9507 | } \ | ||
9508 | else \ | ||
9509 | { \ | ||
9510 | DRAW_RECT(itr->x, oy, itr->adv, oh, _or, _og, _ob, _oa); \ | ||
9511 | } \ | ||
9512 | } \ | ||
9513 | } \ | ||
9514 | while (0) | ||
9515 | |||
9516 | { | ||
9517 | Evas_Coord look_for_y = 0 - (obj->cur.geometry.y + y); | ||
9518 | if (clip) | ||
9519 | { | ||
9520 | Evas_Coord tmp_lfy = cy - (obj->cur.geometry.y + y); | ||
9521 | if (tmp_lfy > look_for_y) | ||
9522 | look_for_y = tmp_lfy; | ||
9523 | } | ||
9524 | |||
9525 | if (look_for_y >= 0) | ||
9526 | start = _layout_find_paragraph_by_y(o, look_for_y); | ||
9527 | |||
9528 | if (!start) | ||
9529 | start = o->paragraphs; | ||
9530 | } | ||
9531 | |||
9532 | ITEM_WALK() | ||
9533 | { | ||
9534 | DRAW_FORMAT(backing, 0, ln->h); | ||
9535 | } | ||
9536 | ITEM_WALK_END(); | ||
9537 | |||
9538 | /* There are size adjustments that depend on the styles drawn here back | ||
9539 | * in "_text_item_update_sizes" should not modify one without the other. */ | ||
9540 | |||
9541 | /* prepare everything for text draw */ | ||
9542 | |||
9543 | /* shadows */ | ||
9544 | ITEM_WALK() | ||
9545 | { | ||
9546 | int shad_dst, shad_sz, dx, dy, haveshad; | ||
9547 | Evas_Object_Textblock_Text_Item *ti; | ||
9548 | ti = (itr->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? _ITEM_TEXT(itr) : NULL; | ||
9549 | if (!ti) continue; | ||
9550 | |||
9551 | shad_dst = shad_sz = dx = dy = haveshad = 0; | ||
9552 | switch (ti->parent.format->style & EVAS_TEXT_STYLE_MASK_BASIC) | ||
9553 | { | ||
9554 | case EVAS_TEXT_STYLE_SHADOW: | ||
9555 | case EVAS_TEXT_STYLE_OUTLINE_SOFT_SHADOW: | ||
9556 | shad_dst = 1; | ||
9557 | haveshad = 1; | ||
9558 | break; | ||
9559 | case EVAS_TEXT_STYLE_OUTLINE_SHADOW: | ||
9560 | case EVAS_TEXT_STYLE_FAR_SHADOW: | ||
9561 | shad_dst = 2; | ||
9562 | haveshad = 1; | ||
9563 | break; | ||
9564 | case EVAS_TEXT_STYLE_FAR_SOFT_SHADOW: | ||
9565 | shad_dst = 2; | ||
9566 | shad_sz = 2; | ||
9567 | haveshad = 1; | ||
9568 | break; | ||
9569 | case EVAS_TEXT_STYLE_SOFT_SHADOW: | ||
9570 | shad_dst = 1; | ||
9571 | shad_sz = 2; | ||
9572 | haveshad = 1; | ||
9573 | break; | ||
9574 | default: | ||
9575 | break; | ||
9576 | } | ||
9577 | if (haveshad) | ||
9578 | { | ||
9579 | if (shad_dst > 0) | ||
9580 | { | ||
9581 | switch (ti->parent.format->style & EVAS_TEXT_STYLE_MASK_SHADOW_DIRECTION) | ||
9582 | { | ||
9583 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_RIGHT: | ||
9584 | dx = 1; | ||
9585 | dy = 1; | ||
9586 | break; | ||
9587 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM: | ||
9588 | dx = 0; | ||
9589 | dy = 1; | ||
9590 | break; | ||
9591 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_LEFT: | ||
9592 | dx = -1; | ||
9593 | dy = 1; | ||
9594 | break; | ||
9595 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_LEFT: | ||
9596 | dx = -1; | ||
9597 | dy = 0; | ||
9598 | break; | ||
9599 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP_LEFT: | ||
9600 | dx = -1; | ||
9601 | dy = -1; | ||
9602 | break; | ||
9603 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP: | ||
9604 | dx = 0; | ||
9605 | dy = -1; | ||
9606 | break; | ||
9607 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP_RIGHT: | ||
9608 | dx = 1; | ||
9609 | dy = -1; | ||
9610 | break; | ||
9611 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_RIGHT: | ||
9612 | dx = 1; | ||
9613 | dy = 0; | ||
9614 | default: | ||
9615 | break; | ||
9616 | } | ||
9617 | dx *= shad_dst; | ||
9618 | dy *= shad_dst; | ||
9619 | } | ||
9620 | switch (shad_sz) | ||
9621 | { | ||
9622 | case 0: | ||
9623 | COLOR_SET(shadow); | ||
9624 | DRAW_TEXT(dx, dy); | ||
9625 | break; | ||
9626 | case 2: | ||
9627 | for (j = 0; j < 5; j++) | ||
9628 | { | ||
9629 | for (i = 0; i < 5; i++) | ||
9630 | { | ||
9631 | if (vals[i][j] != 0) | ||
9632 | { | ||
9633 | COLOR_SET_AMUL(shadow, vals[i][j] * 50); | ||
9634 | DRAW_TEXT(i - 2 + dx, j - 2 + dy); | ||
9635 | } | ||
9636 | } | ||
9637 | } | ||
9638 | break; | ||
9639 | default: | ||
9640 | break; | ||
9641 | } | ||
9642 | } | ||
9643 | } | ||
9644 | ITEM_WALK_END(); | ||
9645 | |||
9646 | /* glows */ | ||
9647 | ITEM_WALK() | ||
9648 | { | ||
9649 | Evas_Object_Textblock_Text_Item *ti; | ||
9650 | ti = (itr->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? _ITEM_TEXT(itr) : NULL; | ||
9651 | if (!ti) continue; | ||
9652 | |||
9653 | if (ti->parent.format->style == EVAS_TEXT_STYLE_GLOW) | ||
9654 | { | ||
9655 | for (j = 0; j < 5; j++) | ||
9656 | { | ||
9657 | for (i = 0; i < 5; i++) | ||
9658 | { | ||
9659 | if (vals[i][j] != 0) | ||
9660 | { | ||
9661 | COLOR_SET_AMUL(glow, vals[i][j] * 50); | ||
9662 | DRAW_TEXT(i - 2, j - 2); | ||
9663 | } | ||
9664 | } | ||
9665 | } | ||
9666 | COLOR_SET(glow2); | ||
9667 | DRAW_TEXT(-1, 0); | ||
9668 | DRAW_TEXT(1, 0); | ||
9669 | DRAW_TEXT(0, -1); | ||
9670 | DRAW_TEXT(0, 1); | ||
9671 | } | ||
9672 | } | ||
9673 | ITEM_WALK_END(); | ||
9674 | |||
9675 | /* outlines */ | ||
9676 | ITEM_WALK() | ||
9677 | { | ||
9678 | Evas_Object_Textblock_Text_Item *ti; | ||
9679 | ti = (itr->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? _ITEM_TEXT(itr) : NULL; | ||
9680 | if (!ti) continue; | ||
9681 | |||
9682 | if ((ti->parent.format->style == EVAS_TEXT_STYLE_OUTLINE) || | ||
9683 | (ti->parent.format->style == EVAS_TEXT_STYLE_OUTLINE_SHADOW) || | ||
9684 | (ti->parent.format->style == EVAS_TEXT_STYLE_OUTLINE_SOFT_SHADOW)) | ||
9685 | { | ||
9686 | COLOR_SET(outline); | ||
9687 | DRAW_TEXT(-1, 0); | ||
9688 | DRAW_TEXT(1, 0); | ||
9689 | DRAW_TEXT(0, -1); | ||
9690 | DRAW_TEXT(0, 1); | ||
9691 | } | ||
9692 | else if (ti->parent.format->style == EVAS_TEXT_STYLE_SOFT_OUTLINE) | ||
9693 | { | ||
9694 | for (j = 0; j < 5; j++) | ||
9695 | { | ||
9696 | for (i = 0; i < 5; i++) | ||
9697 | { | ||
9698 | if (((i != 2) || (j != 2)) && (vals[i][j] != 0)) | ||
9699 | { | ||
9700 | COLOR_SET_AMUL(outline, vals[i][j] * 50); | ||
9701 | DRAW_TEXT(i - 2, j - 2); | ||
9702 | } | ||
9703 | } | ||
9704 | } | ||
9705 | } | ||
9706 | } | ||
9707 | ITEM_WALK_END(); | ||
9708 | |||
9709 | /* normal text and lines */ | ||
9710 | ITEM_WALK() | ||
9711 | { | ||
9712 | Evas_Object_Textblock_Text_Item *ti; | ||
9713 | ti = (itr->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? _ITEM_TEXT(itr) : NULL; | ||
9714 | /* NORMAL TEXT */ | ||
9715 | if (ti) | ||
9716 | { | ||
9717 | COLOR_SET(normal); | ||
9718 | DRAW_TEXT(0, 0); | ||
9719 | } | ||
9720 | |||
9721 | /* STRIKETHROUGH */ | ||
9722 | DRAW_FORMAT(strikethrough, (ln->h / 2), 1); | ||
9723 | |||
9724 | /* UNDERLINE */ | ||
9725 | DRAW_FORMAT(underline, ln->baseline + 1, 1); | ||
9726 | |||
9727 | /* UNDERLINE DASHED */ | ||
9728 | DRAW_FORMAT_DASHED(underline_dash, ln->baseline + 1, 1, | ||
9729 | ti->parent.format->underline_dash_width, | ||
9730 | ti->parent.format->underline_dash_gap); | ||
9731 | |||
9732 | /* UNDERLINE2 */ | ||
9733 | DRAW_FORMAT(underline2, ln->baseline + 3, 1); | ||
9734 | } | ||
9735 | ITEM_WALK_END(); | ||
9736 | } | ||
9737 | |||
9738 | static void | ||
9739 | evas_object_textblock_render_pre(Evas_Object *obj) | ||
9740 | { | ||
9741 | Evas_Object_Textblock *o; | ||
9742 | int is_v, was_v; | ||
9743 | |||
9744 | /* dont pre-render the obj twice! */ | ||
9745 | if (obj->pre_render_done) return; | ||
9746 | obj->pre_render_done = 1; | ||
9747 | /* pre-render phase. this does anything an object needs to do just before */ | ||
9748 | /* rendering. this could mean loading the image data, retrieving it from */ | ||
9749 | /* elsewhere, decoding video etc. */ | ||
9750 | /* then when this is done the object needs to figure if it changed and */ | ||
9751 | /* if so what and where and add the appropriate redraw textblocks */ | ||
9752 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
9753 | if ((o->changed) || (o->content_changed) || (o->format_changed) || | ||
9754 | ((obj->cur.geometry.w != o->last_w) || | ||
9755 | (((o->valign != 0.0) || (o->have_ellipsis)) && | ||
9756 | (obj->cur.geometry.h != o->last_h)))) | ||
9757 | { | ||
9758 | _relayout(obj); | ||
9759 | o->redraw = 0; | ||
9760 | evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj); | ||
9761 | is_v = evas_object_is_visible(obj); | ||
9762 | was_v = evas_object_was_visible(obj); | ||
9763 | goto done; | ||
9764 | } | ||
9765 | if (o->redraw) | ||
9766 | { | ||
9767 | o->redraw = 0; | ||
9768 | evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj); | ||
9769 | is_v = evas_object_is_visible(obj); | ||
9770 | was_v = evas_object_was_visible(obj); | ||
9771 | goto done; | ||
9772 | } | ||
9773 | /* if someone is clipping this obj - go calculate the clipper */ | ||
9774 | if (obj->cur.clipper) | ||
9775 | { | ||
9776 | if (obj->cur.cache.clip.dirty) | ||
9777 | evas_object_clip_recalc(obj->cur.clipper); | ||
9778 | obj->cur.clipper->func->render_pre(obj->cur.clipper); | ||
9779 | } | ||
9780 | /* now figure what changed and add draw rects */ | ||
9781 | /* if it just became visible or invisible */ | ||
9782 | is_v = evas_object_is_visible(obj); | ||
9783 | was_v = evas_object_was_visible(obj); | ||
9784 | if (is_v != was_v) | ||
9785 | { | ||
9786 | evas_object_render_pre_visible_change(&obj->layer->evas->clip_changes, obj, is_v, was_v); | ||
9787 | goto done; | ||
9788 | } | ||
9789 | if ((obj->cur.map != obj->prev.map) || | ||
9790 | (obj->cur.usemap != obj->prev.usemap)) | ||
9791 | { | ||
9792 | evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj); | ||
9793 | goto done; | ||
9794 | } | ||
9795 | /* it's not visible - we accounted for it appearing or not so just abort */ | ||
9796 | if (!is_v) goto done; | ||
9797 | /* clipper changed this is in addition to anything else for obj */ | ||
9798 | evas_object_render_pre_clipper_change(&obj->layer->evas->clip_changes, obj); | ||
9799 | /* if we restacked (layer or just within a layer) and don't clip anyone */ | ||
9800 | if (obj->restack) | ||
9801 | { | ||
9802 | evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj); | ||
9803 | goto done; | ||
9804 | } | ||
9805 | /* if it changed color */ | ||
9806 | if ((obj->cur.color.r != obj->prev.color.r) || | ||
9807 | (obj->cur.color.g != obj->prev.color.g) || | ||
9808 | (obj->cur.color.b != obj->prev.color.b) || | ||
9809 | (obj->cur.color.a != obj->prev.color.a)) | ||
9810 | { | ||
9811 | evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj); | ||
9812 | goto done; | ||
9813 | } | ||
9814 | /* if it changed geometry - and obviously not visibility or color */ | ||
9815 | /* calculate differences since we have a constant color fill */ | ||
9816 | /* we really only need to update the differences */ | ||
9817 | if ((obj->cur.geometry.x != obj->prev.geometry.x) || | ||
9818 | (obj->cur.geometry.y != obj->prev.geometry.y) || | ||
9819 | (obj->cur.geometry.w != obj->prev.geometry.w) || | ||
9820 | (obj->cur.geometry.h != obj->prev.geometry.h)) | ||
9821 | { | ||
9822 | evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj); | ||
9823 | goto done; | ||
9824 | } | ||
9825 | done: | ||
9826 | evas_object_render_pre_effect_updates(&obj->layer->evas->clip_changes, obj, is_v, was_v); | ||
9827 | } | ||
9828 | |||
9829 | static void | ||
9830 | evas_object_textblock_render_post(Evas_Object *obj) | ||
9831 | { | ||
9832 | /* Evas_Object_Textblock *o; */ | ||
9833 | |||
9834 | /* this moves the current data to the previous state parts of the object */ | ||
9835 | /* in whatever way is safest for the object. also if we don't need object */ | ||
9836 | /* data anymore we can free it if the object deems this is a good idea */ | ||
9837 | /* o = (Evas_Object_Textblock *)(obj->object_data); */ | ||
9838 | /* remove those pesky changes */ | ||
9839 | evas_object_clip_changes_clean(obj); | ||
9840 | /* move cur to prev safely for object data */ | ||
9841 | obj->prev = obj->cur; | ||
9842 | /* o->prev = o->cur; */ | ||
9843 | /* o->changed = 0; */ | ||
9844 | } | ||
9845 | |||
9846 | static unsigned int evas_object_textblock_id_get(Evas_Object *obj) | ||
9847 | { | ||
9848 | Evas_Object_Textblock *o; | ||
9849 | |||
9850 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
9851 | if (!o) return 0; | ||
9852 | return MAGIC_OBJ_TEXTBLOCK; | ||
9853 | } | ||
9854 | |||
9855 | static unsigned int evas_object_textblock_visual_id_get(Evas_Object *obj) | ||
9856 | { | ||
9857 | Evas_Object_Textblock *o; | ||
9858 | |||
9859 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
9860 | if (!o) return 0; | ||
9861 | return MAGIC_OBJ_CUSTOM; | ||
9862 | } | ||
9863 | |||
9864 | static void *evas_object_textblock_engine_data_get(Evas_Object *obj) | ||
9865 | { | ||
9866 | Evas_Object_Textblock *o; | ||
9867 | |||
9868 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
9869 | if (!o) return NULL; | ||
9870 | return o->engine_data; | ||
9871 | } | ||
9872 | |||
9873 | static int | ||
9874 | evas_object_textblock_is_opaque(Evas_Object *obj __UNUSED__) | ||
9875 | { | ||
9876 | /* this returns 1 if the internal object data implies that the object is */ | ||
9877 | /* currently fulyl opque over the entire gradient it occupies */ | ||
9878 | return 0; | ||
9879 | } | ||
9880 | |||
9881 | static int | ||
9882 | evas_object_textblock_was_opaque(Evas_Object *obj __UNUSED__) | ||
9883 | { | ||
9884 | /* this returns 1 if the internal object data implies that the object was */ | ||
9885 | /* currently fulyl opque over the entire gradient it occupies */ | ||
9886 | return 0; | ||
9887 | } | ||
9888 | |||
9889 | static void | ||
9890 | evas_object_textblock_coords_recalc(Evas_Object *obj) | ||
9891 | { | ||
9892 | Evas_Object_Textblock *o; | ||
9893 | |||
9894 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
9895 | if ((obj->cur.geometry.w != o->last_w) || | ||
9896 | (((o->valign != 0.0) || (o->have_ellipsis)) && | ||
9897 | (obj->cur.geometry.h != o->last_h))) | ||
9898 | { | ||
9899 | o->formatted.valid = 0; | ||
9900 | o->changed = 1; | ||
9901 | } | ||
9902 | } | ||
9903 | |||
9904 | static void | ||
9905 | evas_object_textblock_scale_update(Evas_Object *obj) | ||
9906 | { | ||
9907 | Evas_Object_Textblock *o; | ||
9908 | |||
9909 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
9910 | _evas_textblock_invalidate_all(o); | ||
9911 | _evas_textblock_changed(o, obj); | ||
9912 | } | ||
9913 | |||
9914 | void | ||
9915 | _evas_object_textblock_rehint(Evas_Object *obj) | ||
9916 | { | ||
9917 | Evas_Object_Textblock *o; | ||
9918 | Evas_Object_Textblock_Paragraph *par; | ||
9919 | Evas_Object_Textblock_Line *ln; | ||
9920 | |||
9921 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
9922 | EINA_INLIST_FOREACH(o->paragraphs, par) | ||
9923 | { | ||
9924 | EINA_INLIST_FOREACH(par->lines, ln) | ||
9925 | { | ||
9926 | Evas_Object_Textblock_Item *it; | ||
9927 | |||
9928 | EINA_INLIST_FOREACH(ln->items, it) | ||
9929 | { | ||
9930 | if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
9931 | { | ||
9932 | Evas_Object_Textblock_Text_Item *ti = _ITEM_TEXT(it); | ||
9933 | if (ti->parent.format->font.font) | ||
9934 | { | ||
9935 | #ifdef EVAS_FRAME_QUEUING | ||
9936 | evas_common_pipe_op_text_flush((RGBA_Font *) ti->parent.format->font.font); | ||
9937 | #endif | ||
9938 | evas_font_load_hinting_set(obj->layer->evas, | ||
9939 | ti->parent.format->font.font, | ||
9940 | obj->layer->evas->hinting); | ||
9941 | } | ||
9942 | } | ||
9943 | } | ||
9944 | } | ||
9945 | } | ||
9946 | _evas_textblock_invalidate_all(o); | ||
9947 | _evas_textblock_changed(o, obj); | ||
9948 | } | ||
9949 | |||
9950 | /** | ||
9951 | * @} | ||
9952 | */ | ||
9953 | |||
9954 | #ifdef HAVE_TESTS | ||
9955 | /* return EINA_FALSE on error, used in unit_testing */ | ||
9956 | EAPI Eina_Bool | ||
9957 | _evas_textblock_check_item_node_link(Evas_Object *obj) | ||
9958 | { | ||
9959 | Evas_Object_Textblock *o; | ||
9960 | Evas_Object_Textblock_Paragraph *par; | ||
9961 | Evas_Object_Textblock_Line *ln; | ||
9962 | Evas_Object_Textblock_Item *it; | ||
9963 | |||
9964 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
9965 | if (!o) return EINA_FALSE; | ||
9966 | |||
9967 | if (!o->formatted.valid) _relayout(obj); | ||
9968 | |||
9969 | EINA_INLIST_FOREACH(o->paragraphs, par) | ||
9970 | { | ||
9971 | EINA_INLIST_FOREACH(par->lines, ln) | ||
9972 | { | ||
9973 | EINA_INLIST_FOREACH(ln->items, it) | ||
9974 | { | ||
9975 | if (it->text_node != par->text_node) | ||
9976 | return EINA_FALSE; | ||
9977 | } | ||
9978 | } | ||
9979 | } | ||
9980 | return EINA_TRUE; | ||
9981 | } | ||
9982 | |||
9983 | EAPI int | ||
9984 | _evas_textblock_format_offset_get(const Evas_Object_Textblock_Node_Format *n) | ||
9985 | { | ||
9986 | return n->offset; | ||
9987 | } | ||
9988 | #endif | ||
9989 | |||
9990 | #if 0 | ||
9991 | /* Good for debugging */ | ||
9992 | void | ||
9993 | pfnode(Evas_Object_Textblock_Node_Format *n) | ||
9994 | { | ||
9995 | printf("Format Node: %p\n", n); | ||
9996 | printf("next = %p, prev = %p, last = %p\n", EINA_INLIST_GET(n)->next, EINA_INLIST_GET(n)->prev, EINA_INLIST_GET(n)->last); | ||
9997 | printf("text_node = %p, offset = %u, visible = %d\n", n->text_node, n->offset, n->visible); | ||
9998 | printf("'%s'\n", eina_strbuf_string_get(n->format)); | ||
9999 | } | ||
10000 | |||
10001 | void | ||
10002 | ptnode(Evas_Object_Textblock_Node_Text *n) | ||
10003 | { | ||
10004 | printf("Text Node: %p\n", n); | ||
10005 | printf("next = %p, prev = %p, last = %p\n", EINA_INLIST_GET(n)->next, EINA_INLIST_GET(n)->prev, EINA_INLIST_GET(n)->last); | ||
10006 | printf("format_node = %p\n", n->format_node); | ||
10007 | printf("'%ls'\n", eina_ustrbuf_string_get(n->unicode)); | ||
10008 | } | ||
10009 | |||
10010 | void | ||
10011 | pitem(Evas_Object_Textblock_Item *it) | ||
10012 | { | ||
10013 | Evas_Object_Textblock_Text_Item *ti; | ||
10014 | Evas_Object_Textblock_Format_Item *fi; | ||
10015 | printf("Item: %p\n", it); | ||
10016 | printf("Type: %s (%d)\n", (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? | ||
10017 | "TEXT" : "FORMAT", it->type); | ||
10018 | printf("Text pos: %d Visual pos: %d\n", it->text_pos, | ||
10019 | #ifdef BIDI_SUPPORT | ||
10020 | it->visual_pos | ||
10021 | #else | ||
10022 | it->text_pos | ||
10023 | #endif | ||
10024 | ); | ||
10025 | printf("Coords: x = %d w = %d adv = %d\n", (int) it->x, (int) it->w, | ||
10026 | (int) it->adv); | ||
10027 | if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
10028 | { | ||
10029 | ti = _ITEM_TEXT(it); | ||
10030 | printf("Text: '%*ls'\n", ti->text_props.text_len, GET_ITEM_TEXT(ti)); | ||
10031 | } | ||
10032 | else | ||
10033 | { | ||
10034 | fi = _ITEM_FORMAT(it); | ||
10035 | printf("Format: '%s'\n", fi->item); | ||
10036 | } | ||
10037 | } | ||
10038 | |||
10039 | void | ||
10040 | ppar(Evas_Object_Textblock_Paragraph *par) | ||
10041 | { | ||
10042 | Evas_Object_Textblock_Item *it; | ||
10043 | Eina_List *i; | ||
10044 | EINA_LIST_FOREACH(par->logical_items, i, it) | ||
10045 | { | ||
10046 | printf("***********************\n"); | ||
10047 | pitem(it); | ||
10048 | } | ||
10049 | } | ||
10050 | |||
10051 | #endif | ||
10052 | |||