1 /*
2 * Copyright © 2014 Benjamin Otte <otte@gnome.org>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "config.h"
19
20 #include "gtkcssnodedeclarationprivate.h"
21 #include "gtkwidgetpathprivate.h"
22
23 #include <string.h>
24
25 typedef struct _GtkRegion GtkRegion;
26
27 struct _GtkRegion
28 {
29 GQuark class_quark;
30 GtkRegionFlags flags;
31 };
32
33 struct _GtkCssNodeDeclaration {
34 guint refcount;
35 GtkJunctionSides junction_sides;
36 GType type;
37 const /* interned */ char *name;
38 const /* interned */ char *id;
39 GtkStateFlags state;
40 guint n_classes;
41 guint n_regions;
42 /* GQuark classes[n_classes]; */
43 /* GtkRegion region[n_regions]; */
44 };
45
46 static inline GQuark *
get_classes(const GtkCssNodeDeclaration * decl)47 get_classes (const GtkCssNodeDeclaration *decl)
48 {
49 return (GQuark *) (decl + 1);
50 }
51
52 static inline GtkRegion *
get_regions(const GtkCssNodeDeclaration * decl)53 get_regions (const GtkCssNodeDeclaration *decl)
54 {
55 return (GtkRegion *) (get_classes (decl) + decl->n_classes);
56 }
57
58 static inline gsize
sizeof_node(guint n_classes,guint n_regions)59 sizeof_node (guint n_classes,
60 guint n_regions)
61 {
62 return sizeof (GtkCssNodeDeclaration)
63 + sizeof (GQuark) * n_classes
64 + sizeof (GtkRegion) * n_regions;
65 }
66
67 static inline gsize
sizeof_this_node(GtkCssNodeDeclaration * decl)68 sizeof_this_node (GtkCssNodeDeclaration *decl)
69 {
70 return sizeof_node (decl->n_classes, decl->n_regions);
71 }
72
73 static void
gtk_css_node_declaration_make_writable(GtkCssNodeDeclaration ** decl)74 gtk_css_node_declaration_make_writable (GtkCssNodeDeclaration **decl)
75 {
76 if ((*decl)->refcount == 1)
77 return;
78
79 (*decl)->refcount--;
80
81 *decl = g_memdup (*decl, sizeof_this_node (*decl));
82 (*decl)->refcount = 1;
83 }
84
85 static void
gtk_css_node_declaration_make_writable_resize(GtkCssNodeDeclaration ** decl,gsize offset,gsize bytes_added,gsize bytes_removed)86 gtk_css_node_declaration_make_writable_resize (GtkCssNodeDeclaration **decl,
87 gsize offset,
88 gsize bytes_added,
89 gsize bytes_removed)
90 {
91 gsize old_size = sizeof_this_node (*decl);
92 gsize new_size = old_size + bytes_added - bytes_removed;
93
94 if ((*decl)->refcount == 1)
95 {
96 if (bytes_removed > 0 && old_size - offset - bytes_removed > 0)
97 memmove (((char *) *decl) + offset, ((char *) *decl) + offset + bytes_removed, old_size - offset - bytes_removed);
98 *decl = g_realloc (*decl, new_size);
99 if (bytes_added > 0 && old_size - offset > 0)
100 memmove (((char *) *decl) + offset + bytes_added, ((char *) *decl) + offset, old_size - offset);
101 }
102 else
103 {
104 GtkCssNodeDeclaration *old = *decl;
105
106 old->refcount--;
107
108 *decl = g_malloc (new_size);
109 memcpy (*decl, old, offset);
110 if (old_size - offset - bytes_removed > 0)
111 memcpy (((char *) *decl) + offset + bytes_added, ((char *) old) + offset + bytes_removed, old_size - offset - bytes_removed);
112 (*decl)->refcount = 1;
113 }
114 }
115
116 GtkCssNodeDeclaration *
gtk_css_node_declaration_new(void)117 gtk_css_node_declaration_new (void)
118 {
119 static GtkCssNodeDeclaration empty = {
120 1, /* need to own a ref ourselves so the copy-on-write path kicks in when people change things */
121 0,
122 0,
123 NULL,
124 NULL,
125 0,
126 0,
127 0
128 };
129
130 return gtk_css_node_declaration_ref (&empty);
131 }
132
133 GtkCssNodeDeclaration *
gtk_css_node_declaration_ref(GtkCssNodeDeclaration * decl)134 gtk_css_node_declaration_ref (GtkCssNodeDeclaration *decl)
135 {
136 decl->refcount++;
137
138 return decl;
139 }
140
141 void
gtk_css_node_declaration_unref(GtkCssNodeDeclaration * decl)142 gtk_css_node_declaration_unref (GtkCssNodeDeclaration *decl)
143 {
144 decl->refcount--;
145 if (decl->refcount > 0)
146 return;
147
148 g_free (decl);
149 }
150
151 gboolean
gtk_css_node_declaration_set_junction_sides(GtkCssNodeDeclaration ** decl,GtkJunctionSides junction_sides)152 gtk_css_node_declaration_set_junction_sides (GtkCssNodeDeclaration **decl,
153 GtkJunctionSides junction_sides)
154 {
155 if ((*decl)->junction_sides == junction_sides)
156 return FALSE;
157
158 gtk_css_node_declaration_make_writable (decl);
159 (*decl)->junction_sides = junction_sides;
160
161 return TRUE;
162 }
163
164 GtkJunctionSides
gtk_css_node_declaration_get_junction_sides(const GtkCssNodeDeclaration * decl)165 gtk_css_node_declaration_get_junction_sides (const GtkCssNodeDeclaration *decl)
166 {
167 return decl->junction_sides;
168 }
169
170 gboolean
gtk_css_node_declaration_set_type(GtkCssNodeDeclaration ** decl,GType type)171 gtk_css_node_declaration_set_type (GtkCssNodeDeclaration **decl,
172 GType type)
173 {
174 if ((*decl)->type == type)
175 return FALSE;
176
177 gtk_css_node_declaration_make_writable (decl);
178 (*decl)->type = type;
179
180 return TRUE;
181 }
182
183 GType
gtk_css_node_declaration_get_type(const GtkCssNodeDeclaration * decl)184 gtk_css_node_declaration_get_type (const GtkCssNodeDeclaration *decl)
185 {
186 return decl->type;
187 }
188
189 gboolean
gtk_css_node_declaration_set_name(GtkCssNodeDeclaration ** decl,const char * name)190 gtk_css_node_declaration_set_name (GtkCssNodeDeclaration **decl,
191 /*interned*/ const char *name)
192 {
193 if ((*decl)->name == name)
194 return FALSE;
195
196 gtk_css_node_declaration_make_writable (decl);
197 (*decl)->name = name;
198
199 return TRUE;
200 }
201
202 /*interned*/ const char *
gtk_css_node_declaration_get_name(const GtkCssNodeDeclaration * decl)203 gtk_css_node_declaration_get_name (const GtkCssNodeDeclaration *decl)
204 {
205 return decl->name;
206 }
207
208 gboolean
gtk_css_node_declaration_set_id(GtkCssNodeDeclaration ** decl,const char * id)209 gtk_css_node_declaration_set_id (GtkCssNodeDeclaration **decl,
210 const char *id)
211 {
212 id = g_intern_string (id);
213
214 if ((*decl)->id == id)
215 return FALSE;
216
217 gtk_css_node_declaration_make_writable (decl);
218 (*decl)->id = id;
219
220 return TRUE;
221 }
222
223 const char *
gtk_css_node_declaration_get_id(const GtkCssNodeDeclaration * decl)224 gtk_css_node_declaration_get_id (const GtkCssNodeDeclaration *decl)
225 {
226 return decl->id;
227 }
228
229 gboolean
gtk_css_node_declaration_set_state(GtkCssNodeDeclaration ** decl,GtkStateFlags state)230 gtk_css_node_declaration_set_state (GtkCssNodeDeclaration **decl,
231 GtkStateFlags state)
232 {
233 if ((*decl)->state == state)
234 return FALSE;
235
236 gtk_css_node_declaration_make_writable (decl);
237 (*decl)->state = state;
238
239 return TRUE;
240 }
241
242 GtkStateFlags
gtk_css_node_declaration_get_state(const GtkCssNodeDeclaration * decl)243 gtk_css_node_declaration_get_state (const GtkCssNodeDeclaration *decl)
244 {
245 return decl->state;
246 }
247
248 static gboolean
find_class(const GtkCssNodeDeclaration * decl,GQuark class_quark,guint * position)249 find_class (const GtkCssNodeDeclaration *decl,
250 GQuark class_quark,
251 guint *position)
252 {
253 gint min, max, mid;
254 gboolean found = FALSE;
255 GQuark *classes;
256 guint pos;
257
258 *position = 0;
259
260 if (decl->n_classes == 0)
261 return FALSE;
262
263 min = 0;
264 max = decl->n_classes - 1;
265 classes = get_classes (decl);
266
267 do
268 {
269 GQuark item;
270
271 mid = (min + max) / 2;
272 item = classes[mid];
273
274 if (class_quark == item)
275 {
276 found = TRUE;
277 pos = mid;
278 break;
279 }
280 else if (class_quark > item)
281 min = pos = mid + 1;
282 else
283 {
284 max = mid - 1;
285 pos = mid;
286 }
287 }
288 while (min <= max);
289
290 *position = pos;
291
292 return found;
293 }
294
295 gboolean
gtk_css_node_declaration_add_class(GtkCssNodeDeclaration ** decl,GQuark class_quark)296 gtk_css_node_declaration_add_class (GtkCssNodeDeclaration **decl,
297 GQuark class_quark)
298 {
299 guint pos;
300
301 if (find_class (*decl, class_quark, &pos))
302 return FALSE;
303
304 gtk_css_node_declaration_make_writable_resize (decl,
305 (char *) &get_classes (*decl)[pos] - (char *) *decl,
306 sizeof (GQuark),
307 0);
308 (*decl)->n_classes++;
309 get_classes(*decl)[pos] = class_quark;
310
311 return TRUE;
312 }
313
314 gboolean
gtk_css_node_declaration_remove_class(GtkCssNodeDeclaration ** decl,GQuark class_quark)315 gtk_css_node_declaration_remove_class (GtkCssNodeDeclaration **decl,
316 GQuark class_quark)
317 {
318 guint pos;
319
320 if (!find_class (*decl, class_quark, &pos))
321 return FALSE;
322
323 gtk_css_node_declaration_make_writable_resize (decl,
324 (char *) &get_classes (*decl)[pos] - (char *) *decl,
325 0,
326 sizeof (GQuark));
327 (*decl)->n_classes--;
328
329 return TRUE;
330 }
331
332 gboolean
gtk_css_node_declaration_clear_classes(GtkCssNodeDeclaration ** decl)333 gtk_css_node_declaration_clear_classes (GtkCssNodeDeclaration **decl)
334 {
335 if ((*decl)->n_classes == 0)
336 return FALSE;
337
338 gtk_css_node_declaration_make_writable_resize (decl,
339 (char *) get_classes (*decl) - (char *) *decl,
340 0,
341 sizeof (GQuark) * (*decl)->n_classes);
342 (*decl)->n_classes = 0;
343
344 return TRUE;
345 }
346
347 gboolean
gtk_css_node_declaration_has_class(const GtkCssNodeDeclaration * decl,GQuark class_quark)348 gtk_css_node_declaration_has_class (const GtkCssNodeDeclaration *decl,
349 GQuark class_quark)
350 {
351 guint pos;
352 GQuark *classes = get_classes (decl);
353
354 switch (decl->n_classes)
355 {
356 case 3:
357 if (classes[2] == class_quark)
358 return TRUE;
359
360 case 2:
361 if (classes[1] == class_quark)
362 return TRUE;
363
364 case 1:
365 if (classes[0] == class_quark)
366 return TRUE;
367
368 case 0:
369 return FALSE;
370
371 default:
372 return find_class (decl, class_quark, &pos);
373 }
374 }
375
376 const GQuark *
gtk_css_node_declaration_get_classes(const GtkCssNodeDeclaration * decl,guint * n_classes)377 gtk_css_node_declaration_get_classes (const GtkCssNodeDeclaration *decl,
378 guint *n_classes)
379 {
380 *n_classes = decl->n_classes;
381
382 return get_classes (decl);
383 }
384
385 static gboolean
find_region(const GtkCssNodeDeclaration * decl,GQuark region_quark,guint * position)386 find_region (const GtkCssNodeDeclaration *decl,
387 GQuark region_quark,
388 guint *position)
389 {
390 gint min, max, mid;
391 gboolean found = FALSE;
392 GtkRegion *regions;
393 guint pos;
394
395 if (position)
396 *position = 0;
397
398 if (decl->n_regions == 0)
399 return FALSE;
400
401 min = 0;
402 max = decl->n_regions - 1;
403 regions = get_regions (decl);
404
405 do
406 {
407 GQuark item;
408
409 mid = (min + max) / 2;
410 item = regions[mid].class_quark;
411
412 if (region_quark == item)
413 {
414 found = TRUE;
415 pos = mid;
416 break;
417 }
418 else if (region_quark > item)
419 min = pos = mid + 1;
420 else
421 {
422 max = mid - 1;
423 pos = mid;
424 }
425 }
426 while (min <= max);
427
428 if (position)
429 *position = pos;
430
431 return found;
432 }
433
434 gboolean
gtk_css_node_declaration_add_region(GtkCssNodeDeclaration ** decl,GQuark region_quark,GtkRegionFlags flags)435 gtk_css_node_declaration_add_region (GtkCssNodeDeclaration **decl,
436 GQuark region_quark,
437 GtkRegionFlags flags)
438 {
439 GtkRegion *regions;
440 guint pos;
441
442 if (find_region (*decl, region_quark, &pos))
443 return FALSE;
444
445 gtk_css_node_declaration_make_writable_resize (decl,
446 (char *) &get_regions (*decl)[pos] - (char *) *decl,
447 sizeof (GtkRegion),
448 0);
449 (*decl)->n_regions++;
450 regions = get_regions(*decl);
451 regions[pos].class_quark = region_quark;
452 regions[pos].flags = flags;
453
454 return TRUE;
455 }
456
457 gboolean
gtk_css_node_declaration_remove_region(GtkCssNodeDeclaration ** decl,GQuark region_quark)458 gtk_css_node_declaration_remove_region (GtkCssNodeDeclaration **decl,
459 GQuark region_quark)
460 {
461 guint pos;
462
463 if (!find_region (*decl, region_quark, &pos))
464 return FALSE;
465
466 gtk_css_node_declaration_make_writable_resize (decl,
467 (char *) &get_regions (*decl)[pos] - (char *) *decl,
468 0,
469 sizeof (GtkRegion));
470 (*decl)->n_regions--;
471
472 return TRUE;
473 }
474
475 gboolean
gtk_css_node_declaration_clear_regions(GtkCssNodeDeclaration ** decl)476 gtk_css_node_declaration_clear_regions (GtkCssNodeDeclaration **decl)
477 {
478 if ((*decl)->n_regions == 0)
479 return FALSE;
480
481 gtk_css_node_declaration_make_writable_resize (decl,
482 (char *) get_regions (*decl) - (char *) *decl,
483 0,
484 sizeof (GtkRegion) * (*decl)->n_regions);
485 (*decl)->n_regions = 0;
486
487 return TRUE;
488 }
489
490 gboolean
gtk_css_node_declaration_has_region(const GtkCssNodeDeclaration * decl,GQuark region_quark,GtkRegionFlags * flags_return)491 gtk_css_node_declaration_has_region (const GtkCssNodeDeclaration *decl,
492 GQuark region_quark,
493 GtkRegionFlags *flags_return)
494 {
495 guint pos;
496
497 if (!find_region (decl, region_quark, &pos))
498 {
499 if (flags_return)
500 *flags_return = 0;
501 return FALSE;
502 }
503
504 if (flags_return)
505 *flags_return = get_regions (decl)[pos].flags;
506
507 return TRUE;
508 }
509
510 GList *
gtk_css_node_declaration_list_regions(const GtkCssNodeDeclaration * decl)511 gtk_css_node_declaration_list_regions (const GtkCssNodeDeclaration *decl)
512 {
513 GtkRegion *regions;
514 GList *result;
515 guint i;
516
517 regions = get_regions (decl);
518 result = NULL;
519
520 for (i = 0; i < decl->n_regions; i++)
521 {
522 result = g_list_prepend (result, GUINT_TO_POINTER (regions[i].class_quark));
523 }
524
525 return result;
526 }
527
528 guint
gtk_css_node_declaration_hash(gconstpointer elem)529 gtk_css_node_declaration_hash (gconstpointer elem)
530 {
531 const GtkCssNodeDeclaration *decl = elem;
532 GQuark *classes;
533 GtkRegion *regions;
534 guint hash, i;
535
536 hash = (guint) decl->type;
537 hash ^= GPOINTER_TO_UINT (decl->name);
538 hash <<= 5;
539 hash ^= GPOINTER_TO_UINT (decl->id);
540
541 classes = get_classes (decl);
542 for (i = 0; i < decl->n_classes; i++)
543 {
544 hash <<= 5;
545 hash += classes[i];
546 }
547
548 regions = get_regions (decl);
549 for (i = 0; i < decl->n_regions; i++)
550 {
551 hash <<= 5;
552 hash += regions[i].class_quark;
553 hash += regions[i].flags;
554 }
555
556 hash ^= ((guint) decl->junction_sides) << (sizeof (guint) * 8 - 5);
557 hash ^= decl->state;
558
559 return hash;
560 }
561
562 gboolean
gtk_css_node_declaration_equal(gconstpointer elem1,gconstpointer elem2)563 gtk_css_node_declaration_equal (gconstpointer elem1,
564 gconstpointer elem2)
565 {
566 const GtkCssNodeDeclaration *decl1 = elem1;
567 const GtkCssNodeDeclaration *decl2 = elem2;
568 GQuark *classes1, *classes2;
569 GtkRegion *regions1, *regions2;
570 guint i;
571
572 if (decl1 == decl2)
573 return TRUE;
574
575 if (decl1->type != decl2->type)
576 return FALSE;
577
578 if (decl1->name != decl2->name)
579 return FALSE;
580
581 if (decl1->state != decl2->state)
582 return FALSE;
583
584 if (decl1->id != decl2->id)
585 return FALSE;
586
587 if (decl1->n_classes != decl2->n_classes)
588 return FALSE;
589
590 classes1 = get_classes (decl1);
591 classes2 = get_classes (decl2);
592 for (i = 0; i < decl1->n_classes; i++)
593 {
594 if (classes1[i] != classes2[i])
595 return FALSE;
596 }
597
598 if (decl1->n_regions != decl2->n_regions)
599 return FALSE;
600
601 regions1 = get_regions (decl1);
602 regions2 = get_regions (decl2);
603 for (i = 0; i < decl1->n_regions; i++)
604 {
605 if (regions1[i].class_quark != regions2[i].class_quark ||
606 regions1[i].flags != regions2[i].flags)
607 return FALSE;
608 }
609
610 if (decl1->junction_sides != decl2->junction_sides)
611 return FALSE;
612
613 return TRUE;
614 }
615
616 void
gtk_css_node_declaration_add_to_widget_path(const GtkCssNodeDeclaration * decl,GtkWidgetPath * path,guint pos)617 gtk_css_node_declaration_add_to_widget_path (const GtkCssNodeDeclaration *decl,
618 GtkWidgetPath *path,
619 guint pos)
620 {
621 GQuark *classes;
622 GtkRegion *regions;
623 guint i;
624
625 /* Set name and id */
626 gtk_widget_path_iter_set_object_name (path, pos, decl->name);
627 if (decl->id)
628 gtk_widget_path_iter_set_name (path, pos, decl->id);
629
630 /* Set widget regions */
631 regions = get_regions (decl);
632 for (i = 0; i < decl->n_regions; i++)
633 {
634 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
635 gtk_widget_path_iter_add_region (path, pos,
636 g_quark_to_string (regions[i].class_quark),
637 regions[i].flags);
638 G_GNUC_END_IGNORE_DEPRECATIONS
639 }
640
641 /* Set widget classes */
642 classes = get_classes (decl);
643 for (i = 0; i < decl->n_classes; i++)
644 {
645 gtk_widget_path_iter_add_qclass (path, pos, classes[i]);
646 }
647
648 /* Set widget state */
649 gtk_widget_path_iter_set_state (path, pos, decl->state);
650 }
651
652 /* Append the declaration to the string, in selector format */
653 void
gtk_css_node_declaration_print(const GtkCssNodeDeclaration * decl,GString * string)654 gtk_css_node_declaration_print (const GtkCssNodeDeclaration *decl,
655 GString *string)
656 {
657 static const char *state_names[] = {
658 "active",
659 "hover",
660 "selected",
661 "disabled",
662 "indeterminate",
663 "focus",
664 "backdrop",
665 "dir(ltr)",
666 "dir(rtl)",
667 "link",
668 "visited",
669 "checked",
670 "drop(active)"
671 };
672 const GQuark *classes;
673 guint i;
674
675 if (decl->name)
676 g_string_append (string, decl->name);
677 else
678 g_string_append (string, g_type_name (decl->type));
679
680 if (decl->id)
681 {
682 g_string_append_c (string, '#');
683 g_string_append (string, decl->id);
684 }
685
686 classes = get_classes (decl);
687 for (i = 0; i < decl->n_classes; i++)
688 {
689 g_string_append_c (string, '.');
690 g_string_append (string, g_quark_to_string (classes[i]));
691 }
692
693 for (i = 0; i < G_N_ELEMENTS (state_names); i++)
694 {
695 if (decl->state & (1 << i))
696 {
697 g_string_append_c (string, ':');
698 g_string_append (string, state_names[i]);
699 }
700 }
701 }
702