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