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