1 #include <glib.h>
2 #include <clutter/clutter.h>
3 #include <string.h>
4 
5 #include "tests/clutter-test-utils.h"
6 
7 typedef struct {
8   gunichar   unichar;
9   const char bytes[6];
10   gint       nbytes;
11 } TestData;
12 
13 static const TestData
14 test_text_data[] = {
15   { 0xe4,   "\xc3\xa4",     2 }, /* LATIN SMALL LETTER A WITH DIAERESIS */
16   { 0x2665, "\xe2\x99\xa5", 3 }  /* BLACK HEART SUIT */
17 };
18 
19 static void
text_utf8_validation(void)20 text_utf8_validation (void)
21 {
22   int i;
23 
24   for (i = 0; i < G_N_ELEMENTS (test_text_data); i++)
25     {
26       const TestData *t = &test_text_data[i];
27       gunichar unichar;
28       char bytes[6];
29       int nbytes;
30 
31       g_assert (g_unichar_validate (t->unichar));
32 
33       nbytes = g_unichar_to_utf8 (t->unichar, bytes);
34       bytes[nbytes] = '\0';
35       g_assert_cmpint (nbytes, ==, t->nbytes);
36       g_assert (memcmp (t->bytes, bytes, nbytes) == 0);
37 
38       unichar = g_utf8_get_char_validated (bytes, nbytes);
39       g_assert_cmpint (unichar, ==, t->unichar);
40     }
41 }
42 
43 static int
get_nbytes(ClutterText * text)44 get_nbytes (ClutterText *text)
45 {
46   const char *s = clutter_text_get_text (text);
47   return strlen (s);
48 }
49 
50 static int
get_nchars(ClutterText * text)51 get_nchars (ClutterText *text)
52 {
53   const char *s = clutter_text_get_text (text);
54   g_assert (g_utf8_validate (s, -1, NULL));
55   return g_utf8_strlen (s, -1);
56 }
57 
58 #define DONT_MOVE_CURSOR    (-2)
59 
60 static void
insert_unichar(ClutterText * text,gunichar unichar,int position)61 insert_unichar (ClutterText *text, gunichar unichar, int position)
62 {
63   if (position > DONT_MOVE_CURSOR)
64     {
65       clutter_text_set_cursor_position (text, position);
66       g_assert_cmpint (clutter_text_get_cursor_position (text), ==, position);
67     }
68 
69   clutter_text_insert_unichar (text, unichar);
70 }
71 
72 static void
text_set_empty(void)73 text_set_empty (void)
74 {
75   ClutterText *text = CLUTTER_TEXT (clutter_text_new ());
76   g_object_ref_sink (text);
77 
78   g_assert_cmpstr (clutter_text_get_text (text), ==, "");
79   g_assert_cmpint (*clutter_text_get_text (text), ==, '\0');
80   g_assert_cmpint (clutter_text_get_cursor_position (text), ==, -1);
81 
82   clutter_text_set_text (text, "");
83   g_assert_cmpint (get_nchars (text), ==, 0);
84   g_assert_cmpint (get_nbytes (text), ==, 0);
85   g_assert_cmpint (clutter_text_get_cursor_position (text), ==, -1);
86 
87   clutter_actor_destroy (CLUTTER_ACTOR (text));
88 }
89 
90 static void
text_set_text(void)91 text_set_text (void)
92 {
93   ClutterText *text = CLUTTER_TEXT (clutter_text_new ());
94   g_object_ref_sink (text);
95 
96   clutter_text_set_text (text, "abcdef");
97   g_assert_cmpint (get_nchars (text), ==, 6);
98   g_assert_cmpint (get_nbytes (text), ==, 6);
99   g_assert_cmpint (clutter_text_get_cursor_position (text), ==, -1);
100 
101   clutter_text_set_cursor_position (text, 5);
102   g_assert_cmpint (clutter_text_get_cursor_position (text), ==, 5);
103 
104   /* FIXME: cursor position should be -1?
105   clutter_text_set_text (text, "");
106   g_assert_cmpint (clutter_text_get_cursor_position (text), ==, -1);
107   */
108 
109   clutter_actor_destroy (CLUTTER_ACTOR (text));
110 }
111 
112 static void
text_append_some(void)113 text_append_some (void)
114 {
115   ClutterText *text = CLUTTER_TEXT (clutter_text_new ());
116   int i;
117 
118   g_object_ref_sink (text);
119 
120   for (i = 0; i < G_N_ELEMENTS (test_text_data); i++)
121     {
122       const TestData *t = &test_text_data[i];
123       int j;
124 
125       for (j = 1; j <= 4; j++)
126         {
127           insert_unichar (text, t->unichar, DONT_MOVE_CURSOR);
128 
129           g_assert_cmpint (get_nchars (text), ==, j);
130           g_assert_cmpint (get_nbytes (text), ==, j * t->nbytes);
131           g_assert_cmpint (clutter_text_get_cursor_position (text), ==, -1);
132         }
133 
134       clutter_text_set_text (text, "");
135     }
136 
137   clutter_actor_destroy (CLUTTER_ACTOR (text));
138 }
139 
140 static void
text_prepend_some(void)141 text_prepend_some (void)
142 {
143   ClutterText *text = CLUTTER_TEXT (clutter_text_new ());
144   int i;
145 
146   g_object_ref_sink (text);
147 
148   for (i = 0; i < G_N_ELEMENTS (test_text_data); i++)
149     {
150       const TestData *t = &test_text_data[i];
151       int j;
152 
153       clutter_text_insert_unichar (text, t->unichar);
154 
155       g_assert_cmpint (get_nchars (text), ==, 1);
156       g_assert_cmpint (get_nbytes (text), ==, 1 * t->nbytes);
157       g_assert_cmpint (clutter_text_get_cursor_position (text), ==, -1);
158 
159       for (j = 2; j <= 4; j++)
160         {
161           insert_unichar (text, t->unichar, 0);
162 
163           g_assert_cmpint (get_nchars (text), ==, j);
164           g_assert_cmpint (get_nbytes (text), ==, j * t->nbytes);
165           g_assert_cmpint (clutter_text_get_cursor_position (text), ==, 1);
166         }
167 
168       clutter_text_set_text (text, "");
169     }
170 
171   clutter_actor_destroy (CLUTTER_ACTOR (text));
172 }
173 
174 static void
text_insert(void)175 text_insert (void)
176 {
177   ClutterText *text = CLUTTER_TEXT (clutter_text_new ());
178   int i;
179 
180   g_object_ref_sink (text);
181 
182   for (i = 0; i < G_N_ELEMENTS (test_text_data); i++)
183     {
184       const TestData *t = &test_text_data[i];
185 
186       clutter_text_insert_unichar (text, t->unichar);
187       clutter_text_insert_unichar (text, t->unichar);
188 
189       insert_unichar (text, t->unichar, 1);
190 
191       g_assert_cmpint (get_nchars (text), ==, 3);
192       g_assert_cmpint (get_nbytes (text), ==, 3 * t->nbytes);
193       g_assert_cmpint (clutter_text_get_cursor_position (text), ==, 2);
194 
195       clutter_text_set_text (text, "");
196     }
197 
198   clutter_actor_destroy (CLUTTER_ACTOR (text));
199 }
200 
201 static void
text_delete_chars(void)202 text_delete_chars (void)
203 {
204   ClutterText *text = CLUTTER_TEXT (clutter_text_new ());
205   int i;
206 
207   g_object_ref_sink (text);
208 
209   for (i = 0; i < G_N_ELEMENTS (test_text_data); i++)
210     {
211       const TestData *t = &test_text_data[i];
212       int j;
213 
214       for (j = 0; j < 4; j++)
215         clutter_text_insert_unichar (text, t->unichar);
216 
217       if (!g_test_quiet ())
218         g_print ("text: %s\n", clutter_text_get_text (text));
219 
220       clutter_text_set_cursor_position (text, 2);
221       clutter_text_delete_chars (text, 1);
222       if (!g_test_quiet ())
223         g_print ("text: %s (cursor at: %d)\n",
224                  clutter_text_get_text (text),
225                  clutter_text_get_cursor_position (text));
226       g_assert_cmpint (get_nchars (text), ==, 3);
227       g_assert_cmpint (get_nbytes (text), ==, 3 * t->nbytes);
228       g_assert_cmpint (clutter_text_get_cursor_position (text), ==, 1);
229 
230       clutter_text_set_cursor_position (text, 2);
231       clutter_text_delete_chars (text, 1);
232       if (!g_test_quiet ())
233         g_print ("text: %s (cursor at: %d)\n",
234                  clutter_text_get_text (text),
235                  clutter_text_get_cursor_position (text));
236       g_assert_cmpint (get_nchars (text), ==, 2);
237       g_assert_cmpint (get_nbytes (text), ==, 2 * t->nbytes);
238       g_assert_cmpint (clutter_text_get_cursor_position (text), ==, 1);
239 
240       clutter_text_set_text (text, "");
241     }
242 
243   clutter_actor_destroy (CLUTTER_ACTOR (text));
244 }
245 
246 static void
text_get_chars(void)247 text_get_chars (void)
248 {
249   ClutterText *text = CLUTTER_TEXT (clutter_text_new ());
250   gchar *chars;
251 
252   g_object_ref_sink (text);
253 
254   clutter_text_set_text (text, "00abcdef11");
255   g_assert_cmpint (get_nchars (text), ==, 10);
256   g_assert_cmpint (get_nbytes (text), ==, 10);
257   g_assert_cmpstr (clutter_text_get_text (text), ==, "00abcdef11");
258 
259   chars = clutter_text_get_chars (text, 2, -1);
260   g_assert_cmpstr (chars, ==, "abcdef11");
261   g_free (chars);
262 
263   chars = clutter_text_get_chars (text, 0, 8);
264   g_assert_cmpstr (chars, ==, "00abcdef");
265   g_free (chars);
266 
267   chars = clutter_text_get_chars (text, 2, 8);
268   g_assert_cmpstr (chars, ==, "abcdef");
269   g_free (chars);
270 
271   chars = clutter_text_get_chars (text, 8, 12);
272   g_assert_cmpstr (chars, ==, "11");
273   g_free (chars);
274 
275   clutter_actor_destroy (CLUTTER_ACTOR (text));
276 }
277 
278 static void
text_delete_text(void)279 text_delete_text (void)
280 {
281   ClutterText *text = CLUTTER_TEXT (clutter_text_new ());
282   int i;
283 
284   g_object_ref_sink (text);
285 
286   for (i = 0; i < G_N_ELEMENTS (test_text_data); i++)
287     {
288       const TestData *t = &test_text_data[i];
289       int j;
290 
291       for (j = 0; j < 4; j++)
292         clutter_text_insert_unichar (text, t->unichar);
293 
294       clutter_text_set_cursor_position (text, 3);
295       clutter_text_delete_text (text, 2, 4);
296 
297       g_assert_cmpint (get_nchars (text), ==, 2);
298       g_assert_cmpint (get_nbytes (text), ==, 2 * t->nbytes);
299 
300       /* FIXME: cursor position should be -1?
301       g_assert_cmpint (clutter_text_get_cursor_position (text), ==, -1);
302       */
303 
304       clutter_text_set_text (text, "");
305     }
306 
307   clutter_actor_destroy (CLUTTER_ACTOR (text));
308 }
309 
310 static void
text_password_char(void)311 text_password_char (void)
312 {
313   ClutterText *text = CLUTTER_TEXT (clutter_text_new ());
314 
315   g_object_ref_sink (text);
316 
317   g_assert_cmpint (clutter_text_get_password_char (text), ==, 0);
318 
319   clutter_text_set_text (text, "hello");
320   g_assert_cmpstr (clutter_text_get_text (text), ==, "hello");
321 
322   clutter_text_set_password_char (text, '*');
323   g_assert_cmpint (clutter_text_get_password_char (text), ==, '*');
324 
325   g_assert_cmpstr (clutter_text_get_text (text), ==, "hello");
326 
327   clutter_actor_destroy (CLUTTER_ACTOR (text));
328 }
329 
330 static ClutterEvent *
init_event(void)331 init_event (void)
332 {
333   ClutterEvent *retval = clutter_event_new (CLUTTER_KEY_PRESS);
334 
335   clutter_event_set_time (retval, CLUTTER_CURRENT_TIME);
336   clutter_event_set_flags (retval, CLUTTER_EVENT_FLAG_SYNTHETIC);
337 
338   return retval;
339 }
340 
341 static void
send_keyval(ClutterText * text,int keyval)342 send_keyval (ClutterText *text, int keyval)
343 {
344   ClutterEvent *event = init_event ();
345 
346   /* Unicode should be ignored for cursor keys etc. */
347   clutter_event_set_key_unicode (event, 0);
348   clutter_event_set_key_symbol (event, keyval);
349 
350   clutter_actor_event (CLUTTER_ACTOR (text), event, FALSE);
351 
352   clutter_event_free (event);
353 }
354 
355 static void
send_unichar(ClutterText * text,gunichar unichar)356 send_unichar (ClutterText *text, gunichar unichar)
357 {
358   ClutterEvent *event = init_event ();
359 
360   /* Key symbol should be ignored for printable characters */
361   clutter_event_set_key_symbol (event, 0);
362   clutter_event_set_key_unicode (event, unichar);
363 
364   clutter_actor_event (CLUTTER_ACTOR (text), event, FALSE);
365 
366   clutter_event_free (event);
367 }
368 
369 static void
text_cursor(void)370 text_cursor (void)
371 {
372   ClutterText *text = CLUTTER_TEXT (clutter_text_new ());
373   int i;
374 
375   g_object_ref_sink (text);
376 
377   /* only editable entries listen to events */
378   clutter_text_set_editable (text, TRUE);
379 
380   for (i = 0; i < G_N_ELEMENTS (test_text_data); i++)
381     {
382       const TestData *t = &test_text_data[i];
383       int j;
384 
385       for (j = 0; j < 4; ++j)
386         clutter_text_insert_unichar (text, t->unichar);
387 
388       clutter_text_set_cursor_position (text, 2);
389 
390       /* test cursor moves and is clamped */
391       send_keyval (text, CLUTTER_KEY_Left);
392       g_assert_cmpint (clutter_text_get_cursor_position (text), ==, 1);
393 
394       send_keyval (text, CLUTTER_KEY_Left);
395       g_assert_cmpint (clutter_text_get_cursor_position (text), ==, 0);
396 
397       send_keyval (text, CLUTTER_KEY_Left);
398       g_assert_cmpint (clutter_text_get_cursor_position (text), ==, 0);
399 
400       /* delete text containing the cursor */
401       clutter_text_set_cursor_position (text, 3);
402       g_assert_cmpint (clutter_text_get_cursor_position (text), ==, 3);
403 
404       clutter_text_delete_text (text, 2, 4);
405       send_keyval (text, CLUTTER_KEY_Left);
406 
407       /* FIXME: cursor position should be -1?
408       g_assert_cmpint (clutter_text_get_cursor_position (text), ==, -1);
409       */
410 
411       clutter_text_set_text (text, "");
412     }
413 
414   clutter_actor_destroy (CLUTTER_ACTOR (text));
415 }
416 
417 static void
text_event(void)418 text_event (void)
419 {
420   ClutterText *text = CLUTTER_TEXT (clutter_text_new ());
421   int i;
422 
423   g_object_ref_sink (text);
424 
425   /* only editable entries listen to events */
426   clutter_text_set_editable (text, TRUE);
427 
428   for (i = 0; i < G_N_ELEMENTS (test_text_data); i++)
429     {
430       const TestData *t = &test_text_data[i];
431 
432       send_unichar (text, t->unichar);
433 
434       g_assert_cmpint (get_nchars (text), ==, 1);
435       g_assert_cmpint (get_nbytes (text), ==, 1 * t->nbytes);
436       g_assert_cmpint (clutter_text_get_cursor_position (text), ==, -1);
437 
438       clutter_text_set_text (text, "");
439     }
440 
441   clutter_actor_destroy (CLUTTER_ACTOR (text));
442 }
443 
444 static inline void
validate_markup_attributes(ClutterText * text,PangoAttrType attr_type,int start_index,int end_index)445 validate_markup_attributes (ClutterText   *text,
446                             PangoAttrType  attr_type,
447                             int            start_index,
448                             int            end_index)
449 {
450   PangoLayout *layout;
451   PangoAttrList *attrs;
452   PangoAttrIterator *iter;
453 
454   layout = clutter_text_get_layout (text);
455   g_assert (layout != NULL);
456 
457   attrs = pango_layout_get_attributes (layout);
458   g_assert (attrs != NULL);
459 
460   iter = pango_attr_list_get_iterator (attrs);
461   while (pango_attr_iterator_next (iter))
462     {
463       GSList *attributes = pango_attr_iterator_get_attrs (iter);
464       PangoAttribute *a;
465 
466       if (attributes == NULL)
467         break;
468 
469       g_assert (attributes->data != NULL);
470 
471       a = attributes->data;
472 
473       if (a->klass->type == PANGO_ATTR_SCALE)
474         {
475           PangoAttrFloat *scale = (PangoAttrFloat*) a;
476           float resource_scale;
477 
478           resource_scale = clutter_actor_get_resource_scale (CLUTTER_ACTOR (text));
479 
480           g_assert_cmpfloat (scale->value, ==, resource_scale);
481           g_slist_free_full (attributes, (GDestroyNotify) pango_attribute_destroy);
482           continue;
483         }
484 
485       g_assert (a->klass->type == attr_type);
486       g_assert_cmpint (a->start_index, ==, start_index);
487       g_assert_cmpint (a->end_index, ==, end_index);
488 
489       g_slist_free_full (attributes, (GDestroyNotify) pango_attribute_destroy);
490     }
491 
492   pango_attr_iterator_destroy (iter);
493 }
494 
495 static void
text_idempotent_use_markup(void)496 text_idempotent_use_markup (void)
497 {
498   ClutterText *text;
499   const char *contents = "foo <b>bar</b>";
500   const char *display = "foo bar";
501   int bar_start_index = strstr (display, "bar") - display;
502   int bar_end_index = bar_start_index + strlen ("bar");
503 
504   /* case 1: text -> use_markup */
505   if (!g_test_quiet ())
506     g_print ("text: '%s' -> use-markup: TRUE\n", contents);
507 
508   text = g_object_new (CLUTTER_TYPE_TEXT,
509                        "text", contents, "use-markup", TRUE,
510                        NULL);
511   g_object_ref_sink (text);
512 
513   if (!g_test_quiet ())
514     g_print ("Contents: '%s' (expected: '%s')\n",
515              clutter_text_get_text (text),
516              display);
517 
518   g_assert_cmpstr (clutter_text_get_text (text), ==, display);
519 
520   validate_markup_attributes (text,
521                               PANGO_ATTR_WEIGHT,
522                               bar_start_index,
523                               bar_end_index);
524 
525   clutter_actor_destroy (CLUTTER_ACTOR (text));
526 
527   /* case 2: use_markup -> text */
528   if (!g_test_quiet ())
529     g_print ("use-markup: TRUE -> text: '%s'\n", contents);
530 
531   text = g_object_new (CLUTTER_TYPE_TEXT,
532                        "use-markup", TRUE, "text", contents,
533                        NULL);
534 
535   if (!g_test_quiet ())
536     g_print ("Contents: '%s' (expected: '%s')\n",
537              clutter_text_get_text (text),
538              display);
539 
540   g_assert_cmpstr (clutter_text_get_text (text), ==, display);
541 
542   validate_markup_attributes (text,
543                               PANGO_ATTR_WEIGHT,
544                               bar_start_index,
545                               bar_end_index);
546 
547   clutter_actor_destroy (CLUTTER_ACTOR (text));
548 }
549 
550 CLUTTER_TEST_SUITE (
551   CLUTTER_TEST_UNIT ("/text/utf8-validation", text_utf8_validation)
552   CLUTTER_TEST_UNIT ("/text/set-empty", text_set_empty)
553   CLUTTER_TEST_UNIT ("/text/set-text", text_set_text)
554   CLUTTER_TEST_UNIT ("/text/append-some", text_append_some)
555   CLUTTER_TEST_UNIT ("/text/prepend-some", text_prepend_some)
556   CLUTTER_TEST_UNIT ("/text/insert", text_insert)
557   CLUTTER_TEST_UNIT ("/text/delete-chars", text_delete_chars)
558   CLUTTER_TEST_UNIT ("/text/get-chars", text_get_chars)
559   CLUTTER_TEST_UNIT ("/text/delete-text", text_delete_text)
560   CLUTTER_TEST_UNIT ("/text/password-char", text_password_char)
561   CLUTTER_TEST_UNIT ("/text/cursor", text_cursor)
562   CLUTTER_TEST_UNIT ("/text/event", text_event)
563   CLUTTER_TEST_UNIT ("/text/idempotent-use-markup", text_idempotent_use_markup)
564 )
565