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 
22 #include "gtkprivate.h"
23 
24 #include <string.h>
25 
26 struct _GtkCssNodeDeclaration {
27   guint refcount;
28   GQuark name;
29   GQuark id;
30   GtkStateFlags state;
31   guint n_classes;
32   GQuark classes[0];
33 };
34 
35 static inline gsize
sizeof_node(guint n_classes)36 sizeof_node (guint n_classes)
37 {
38   return sizeof (GtkCssNodeDeclaration)
39        + sizeof (GQuark) * n_classes;
40 }
41 
42 static inline gsize
sizeof_this_node(GtkCssNodeDeclaration * decl)43 sizeof_this_node (GtkCssNodeDeclaration *decl)
44 {
45   return sizeof_node (decl->n_classes);
46 }
47 
48 static void
gtk_css_node_declaration_make_writable(GtkCssNodeDeclaration ** decl)49 gtk_css_node_declaration_make_writable (GtkCssNodeDeclaration **decl)
50 {
51   if ((*decl)->refcount == 1)
52     return;
53 
54   (*decl)->refcount--;
55 
56   *decl = g_memdup2 (*decl, sizeof_this_node (*decl));
57   (*decl)->refcount = 1;
58 }
59 
60 static void
gtk_css_node_declaration_make_writable_resize(GtkCssNodeDeclaration ** decl,gsize offset,gsize bytes_added,gsize bytes_removed)61 gtk_css_node_declaration_make_writable_resize (GtkCssNodeDeclaration **decl,
62                                                gsize                   offset,
63                                                gsize                   bytes_added,
64                                                gsize                   bytes_removed)
65 {
66   gsize old_size = sizeof_this_node (*decl);
67   gsize new_size = old_size + bytes_added - bytes_removed;
68 
69   if ((*decl)->refcount == 1)
70     {
71       if (bytes_removed > 0 && old_size - offset - bytes_removed > 0)
72         memmove (((char *) *decl) + offset, ((char *) *decl) + offset + bytes_removed, old_size - offset - bytes_removed);
73       *decl = g_realloc (*decl, new_size);
74       if (bytes_added > 0 && old_size - offset > 0)
75         memmove (((char *) *decl) + offset + bytes_added, ((char *) *decl) + offset, old_size - offset);
76     }
77   else
78     {
79       GtkCssNodeDeclaration *old = *decl;
80 
81       old->refcount--;
82 
83       *decl = g_malloc (new_size);
84       memcpy (*decl, old, offset);
85       if (old_size - offset - bytes_removed > 0)
86         memcpy (((char *) *decl) + offset + bytes_added, ((char *) old) + offset + bytes_removed, old_size - offset - bytes_removed);
87       (*decl)->refcount = 1;
88     }
89 }
90 
91 GtkCssNodeDeclaration *
gtk_css_node_declaration_new(void)92 gtk_css_node_declaration_new (void)
93 {
94   static GtkCssNodeDeclaration empty = {
95     1, /* need to own a ref ourselves so the copy-on-write path kicks in when people change things */
96     0,
97     0,
98     0,
99     0
100   };
101 
102   return gtk_css_node_declaration_ref (&empty);
103 }
104 
105 GtkCssNodeDeclaration *
gtk_css_node_declaration_ref(GtkCssNodeDeclaration * decl)106 gtk_css_node_declaration_ref (GtkCssNodeDeclaration *decl)
107 {
108   decl->refcount++;
109 
110   return decl;
111 }
112 
113 void
gtk_css_node_declaration_unref(GtkCssNodeDeclaration * decl)114 gtk_css_node_declaration_unref (GtkCssNodeDeclaration *decl)
115 {
116   decl->refcount--;
117   if (decl->refcount > 0)
118     return;
119 
120   g_free (decl);
121 }
122 
123 gboolean
gtk_css_node_declaration_set_name(GtkCssNodeDeclaration ** decl,GQuark name)124 gtk_css_node_declaration_set_name (GtkCssNodeDeclaration **decl,
125                                    GQuark                  name)
126 {
127   if ((*decl)->name == name)
128     return FALSE;
129 
130   gtk_css_node_declaration_make_writable (decl);
131   (*decl)->name = name;
132 
133   return TRUE;
134 }
135 
136 GQuark
gtk_css_node_declaration_get_name(const GtkCssNodeDeclaration * decl)137 gtk_css_node_declaration_get_name (const GtkCssNodeDeclaration *decl)
138 {
139   return decl->name;
140 }
141 
142 gboolean
gtk_css_node_declaration_set_id(GtkCssNodeDeclaration ** decl,GQuark id)143 gtk_css_node_declaration_set_id (GtkCssNodeDeclaration **decl,
144                                  GQuark                  id)
145 {
146   if ((*decl)->id == id)
147     return FALSE;
148 
149   gtk_css_node_declaration_make_writable (decl);
150   (*decl)->id = id;
151 
152   return TRUE;
153 }
154 
155 GQuark
gtk_css_node_declaration_get_id(const GtkCssNodeDeclaration * decl)156 gtk_css_node_declaration_get_id (const GtkCssNodeDeclaration *decl)
157 {
158   return decl->id;
159 }
160 
161 gboolean
gtk_css_node_declaration_set_state(GtkCssNodeDeclaration ** decl,GtkStateFlags state)162 gtk_css_node_declaration_set_state (GtkCssNodeDeclaration **decl,
163                                     GtkStateFlags           state)
164 {
165   if ((*decl)->state == state)
166     return FALSE;
167 
168   gtk_css_node_declaration_make_writable (decl);
169   (*decl)->state = state;
170 
171   return TRUE;
172 }
173 
174 GtkStateFlags
gtk_css_node_declaration_get_state(const GtkCssNodeDeclaration * decl)175 gtk_css_node_declaration_get_state (const GtkCssNodeDeclaration *decl)
176 {
177   return decl->state;
178 }
179 
180 static gboolean
find_class(const GtkCssNodeDeclaration * decl,GQuark class_quark,guint * position)181 find_class (const GtkCssNodeDeclaration *decl,
182             GQuark                       class_quark,
183             guint                       *position)
184 {
185   int min, max, mid;
186   gboolean found = FALSE;
187   guint pos;
188 
189   *position = 0;
190 
191   if (decl->n_classes == 0)
192     return FALSE;
193 
194   min = 0;
195   max = decl->n_classes - 1;
196 
197   do
198     {
199       GQuark item;
200 
201       mid = (min + max) / 2;
202       item = decl->classes[mid];
203 
204       if (class_quark == item)
205         {
206           found = TRUE;
207           pos = mid;
208           break;
209         }
210       else if (class_quark > item)
211         min = pos = mid + 1;
212       else
213         {
214           max = mid - 1;
215           pos = mid;
216         }
217     }
218   while (min <= max);
219 
220   *position = pos;
221 
222   return found;
223 }
224 
225 gboolean
gtk_css_node_declaration_add_class(GtkCssNodeDeclaration ** decl,GQuark class_quark)226 gtk_css_node_declaration_add_class (GtkCssNodeDeclaration **decl,
227                                     GQuark                  class_quark)
228 {
229   guint pos;
230 
231   if (find_class (*decl, class_quark, &pos))
232     return FALSE;
233 
234   gtk_css_node_declaration_make_writable_resize (decl,
235                                                  (char *) &(*decl)->classes[pos] - (char *) *decl,
236                                                  sizeof (GQuark),
237                                                  0);
238   (*decl)->n_classes++;
239   (*decl)->classes[pos] = class_quark;
240 
241   return TRUE;
242 }
243 
244 gboolean
gtk_css_node_declaration_remove_class(GtkCssNodeDeclaration ** decl,GQuark class_quark)245 gtk_css_node_declaration_remove_class (GtkCssNodeDeclaration **decl,
246                                        GQuark                  class_quark)
247 {
248   guint pos;
249 
250   if (!find_class (*decl, class_quark, &pos))
251     return FALSE;
252 
253   gtk_css_node_declaration_make_writable_resize (decl,
254                                                  (char *) &(*decl)->classes[pos] - (char *) *decl,
255                                                  0,
256                                                  sizeof (GQuark));
257   (*decl)->n_classes--;
258 
259   return TRUE;
260 }
261 
262 gboolean
gtk_css_node_declaration_clear_classes(GtkCssNodeDeclaration ** decl)263 gtk_css_node_declaration_clear_classes (GtkCssNodeDeclaration **decl)
264 {
265   if ((*decl)->n_classes == 0)
266     return FALSE;
267 
268   gtk_css_node_declaration_make_writable_resize (decl,
269                                                  (char *) (*decl)->classes - (char *) *decl,
270                                                  0,
271                                                  sizeof (GQuark) * (*decl)->n_classes);
272   (*decl)->n_classes = 0;
273 
274   return TRUE;
275 }
276 
277 gboolean
gtk_css_node_declaration_has_class(const GtkCssNodeDeclaration * decl,GQuark class_quark)278 gtk_css_node_declaration_has_class (const GtkCssNodeDeclaration *decl,
279                                     GQuark                       class_quark)
280 {
281   guint pos;
282 
283   switch (decl->n_classes)
284     {
285     case 3:
286       if (decl->classes[2] == class_quark)
287         return TRUE;
288       G_GNUC_FALLTHROUGH;
289 
290     case 2:
291       if (decl->classes[1] == class_quark)
292         return TRUE;
293       G_GNUC_FALLTHROUGH;
294 
295     case 1:
296       if (decl->classes[0] == class_quark)
297         return TRUE;
298       G_GNUC_FALLTHROUGH;
299 
300     case 0:
301       return FALSE;
302 
303     default:
304       return find_class (decl, class_quark, &pos);
305     }
306 }
307 
308 const GQuark *
gtk_css_node_declaration_get_classes(const GtkCssNodeDeclaration * decl,guint * n_classes)309 gtk_css_node_declaration_get_classes (const GtkCssNodeDeclaration *decl,
310                                       guint                       *n_classes)
311 {
312   *n_classes = decl->n_classes;
313 
314   return decl->classes;
315 }
316 
317 void
gtk_css_node_declaration_add_bloom_hashes(const GtkCssNodeDeclaration * decl,GtkCountingBloomFilter * filter)318 gtk_css_node_declaration_add_bloom_hashes (const GtkCssNodeDeclaration *decl,
319                                            GtkCountingBloomFilter      *filter)
320 {
321   guint i;
322 
323   if (decl->name)
324     gtk_counting_bloom_filter_add (filter, gtk_css_hash_name (decl->name));
325   if (decl->id)
326     gtk_counting_bloom_filter_add (filter, gtk_css_hash_id (decl->id));
327 
328   for (i = 0; i < decl->n_classes; i++)
329     {
330       gtk_counting_bloom_filter_add (filter, gtk_css_hash_class (decl->classes[i]));
331     }
332 }
333 
334 void
gtk_css_node_declaration_remove_bloom_hashes(const GtkCssNodeDeclaration * decl,GtkCountingBloomFilter * filter)335 gtk_css_node_declaration_remove_bloom_hashes (const GtkCssNodeDeclaration *decl,
336                                               GtkCountingBloomFilter      *filter)
337 {
338   guint i;
339 
340   if (decl->name)
341     gtk_counting_bloom_filter_remove (filter, gtk_css_hash_name (decl->name));
342   if (decl->id)
343     gtk_counting_bloom_filter_remove (filter, gtk_css_hash_id (decl->id));
344 
345   for (i = 0; i < decl->n_classes; i++)
346     {
347       gtk_counting_bloom_filter_remove (filter, gtk_css_hash_class (decl->classes[i]));
348     }
349 }
350 
351 guint
gtk_css_node_declaration_hash(gconstpointer elem)352 gtk_css_node_declaration_hash (gconstpointer elem)
353 {
354   const GtkCssNodeDeclaration *decl = elem;
355   guint hash, i;
356 
357   hash = GPOINTER_TO_UINT (decl->name);
358   hash <<= 5;
359   hash ^= GPOINTER_TO_UINT (decl->id);
360 
361   for (i = 0; i < decl->n_classes; i++)
362     {
363       hash <<= 5;
364       hash += decl->classes[i];
365     }
366 
367   hash ^= decl->state;
368 
369   return hash;
370 }
371 
372 gboolean
gtk_css_node_declaration_equal(gconstpointer elem1,gconstpointer elem2)373 gtk_css_node_declaration_equal (gconstpointer elem1,
374                                 gconstpointer elem2)
375 {
376   const GtkCssNodeDeclaration *decl1 = elem1;
377   const GtkCssNodeDeclaration *decl2 = elem2;
378   guint i;
379 
380   if (decl1 == decl2)
381     return TRUE;
382 
383   if (decl1->name != decl2->name)
384     return FALSE;
385 
386   if (decl1->state != decl2->state)
387     return FALSE;
388 
389   if (decl1->id != decl2->id)
390     return FALSE;
391 
392   if (decl1->n_classes != decl2->n_classes)
393     return FALSE;
394 
395   for (i = 0; i < decl1->n_classes; i++)
396     {
397       if (decl1->classes[i] != decl2->classes[i])
398         return FALSE;
399     }
400 
401   return TRUE;
402 }
403 
404 static int
cmpstr(gconstpointer a,gconstpointer b,gpointer data)405 cmpstr (gconstpointer a,
406         gconstpointer b,
407         gpointer      data)
408 {
409   char **ap = (char **) a;
410   char **bp = (char **) b;
411 
412   return g_ascii_strcasecmp (*ap, *bp);
413 }
414 
415 /* Append the declaration to the string, in selector format */
416 void
gtk_css_node_declaration_print(const GtkCssNodeDeclaration * decl,GString * string)417 gtk_css_node_declaration_print (const GtkCssNodeDeclaration *decl,
418                                 GString                     *string)
419 {
420   guint i;
421   char **classnames;
422 
423   if (decl->name)
424     g_string_append (string, g_quark_to_string (decl->name));
425   else
426     g_string_append (string, "*");
427 
428   if (decl->id)
429     {
430       g_string_append_c (string, '#');
431       g_string_append (string, g_quark_to_string (decl->id));
432     }
433 
434   classnames = g_new (char *, decl->n_classes);
435   for (i = 0; i < decl->n_classes; i++)
436     classnames[i] = (char *)g_quark_to_string (decl->classes[i]);
437 
438   g_qsort_with_data (classnames, decl->n_classes, sizeof (char *), cmpstr, NULL);
439 
440   for (i = 0; i < decl->n_classes; i++)
441     {
442       g_string_append_c (string, '.');
443       g_string_append (string, classnames[i]);
444     }
445   g_free (classnames);
446 
447   for (i = 0; i < sizeof (GtkStateFlags) * 8; i++)
448     {
449       if (decl->state & (1 << i))
450         {
451           const char *name = gtk_css_pseudoclass_name (1 << i);
452           g_assert (name);
453           g_string_append_c (string, ':');
454           g_string_append (string, name);
455         }
456     }
457 }
458 
459 char *
gtk_css_node_declaration_to_string(const GtkCssNodeDeclaration * decl)460 gtk_css_node_declaration_to_string (const GtkCssNodeDeclaration *decl)
461 {
462   GString *s = g_string_new (NULL);
463 
464   gtk_css_node_declaration_print (decl, s);
465 
466   return g_string_free (s, FALSE);
467 }
468