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 |
36 | sizeof_node (guint n_classes) |
37 | { |
38 | return sizeof (GtkCssNodeDeclaration) |
39 | + sizeof (GQuark) * n_classes; |
40 | } |
41 | |
42 | static inline gsize |
43 | sizeof_this_node (GtkCssNodeDeclaration *decl) |
44 | { |
45 | return sizeof_node (n_classes: decl->n_classes); |
46 | } |
47 | |
48 | static void |
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 (mem: *decl, byte_size: sizeof_this_node (decl: *decl)); |
57 | (*decl)->refcount = 1; |
58 | } |
59 | |
60 | static void |
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: *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 (dest: ((char *) *decl) + offset, src: ((char *) *decl) + offset + bytes_removed, n: old_size - offset - bytes_removed); |
73 | *decl = g_realloc (mem: *decl, n_bytes: new_size); |
74 | if (bytes_added > 0 && old_size - offset > 0) |
75 | memmove (dest: ((char *) *decl) + offset + bytes_added, src: ((char *) *decl) + offset, n: old_size - offset); |
76 | } |
77 | else |
78 | { |
79 | GtkCssNodeDeclaration *old = *decl; |
80 | |
81 | old->refcount--; |
82 | |
83 | *decl = g_malloc (n_bytes: new_size); |
84 | memcpy (dest: *decl, src: old, n: offset); |
85 | if (old_size - offset - bytes_removed > 0) |
86 | memcpy (dest: ((char *) *decl) + offset + bytes_added, src: ((char *) old) + offset + bytes_removed, n: old_size - offset - bytes_removed); |
87 | (*decl)->refcount = 1; |
88 | } |
89 | } |
90 | |
91 | GtkCssNodeDeclaration * |
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 (decl: &empty); |
103 | } |
104 | |
105 | GtkCssNodeDeclaration * |
106 | gtk_css_node_declaration_ref (GtkCssNodeDeclaration *decl) |
107 | { |
108 | decl->refcount++; |
109 | |
110 | return decl; |
111 | } |
112 | |
113 | void |
114 | gtk_css_node_declaration_unref (GtkCssNodeDeclaration *decl) |
115 | { |
116 | decl->refcount--; |
117 | if (decl->refcount > 0) |
118 | return; |
119 | |
120 | g_free (mem: decl); |
121 | } |
122 | |
123 | gboolean |
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 |
137 | gtk_css_node_declaration_get_name (const GtkCssNodeDeclaration *decl) |
138 | { |
139 | return decl->name; |
140 | } |
141 | |
142 | gboolean |
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 |
156 | gtk_css_node_declaration_get_id (const GtkCssNodeDeclaration *decl) |
157 | { |
158 | return decl->id; |
159 | } |
160 | |
161 | gboolean |
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 |
175 | gtk_css_node_declaration_get_state (const GtkCssNodeDeclaration *decl) |
176 | { |
177 | return decl->state; |
178 | } |
179 | |
180 | static gboolean |
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 |
226 | gtk_css_node_declaration_add_class (GtkCssNodeDeclaration **decl, |
227 | GQuark class_quark) |
228 | { |
229 | guint pos; |
230 | |
231 | if (find_class (decl: *decl, class_quark, position: &pos)) |
232 | return FALSE; |
233 | |
234 | gtk_css_node_declaration_make_writable_resize (decl, |
235 | offset: (char *) &(*decl)->classes[pos] - (char *) *decl, |
236 | bytes_added: sizeof (GQuark), |
237 | bytes_removed: 0); |
238 | (*decl)->n_classes++; |
239 | (*decl)->classes[pos] = class_quark; |
240 | |
241 | return TRUE; |
242 | } |
243 | |
244 | gboolean |
245 | gtk_css_node_declaration_remove_class (GtkCssNodeDeclaration **decl, |
246 | GQuark class_quark) |
247 | { |
248 | guint pos; |
249 | |
250 | if (!find_class (decl: *decl, class_quark, position: &pos)) |
251 | return FALSE; |
252 | |
253 | gtk_css_node_declaration_make_writable_resize (decl, |
254 | offset: (char *) &(*decl)->classes[pos] - (char *) *decl, |
255 | bytes_added: 0, |
256 | bytes_removed: sizeof (GQuark)); |
257 | (*decl)->n_classes--; |
258 | |
259 | return TRUE; |
260 | } |
261 | |
262 | gboolean |
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 | offset: (char *) (*decl)->classes - (char *) *decl, |
270 | bytes_added: 0, |
271 | bytes_removed: sizeof (GQuark) * (*decl)->n_classes); |
272 | (*decl)->n_classes = 0; |
273 | |
274 | return TRUE; |
275 | } |
276 | |
277 | gboolean |
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, position: &pos); |
305 | } |
306 | } |
307 | |
308 | const GQuark * |
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 |
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 (self: filter, hash: gtk_css_hash_name (name: decl->name)); |
325 | if (decl->id) |
326 | gtk_counting_bloom_filter_add (self: filter, hash: gtk_css_hash_id (id: decl->id)); |
327 | |
328 | for (i = 0; i < decl->n_classes; i++) |
329 | { |
330 | gtk_counting_bloom_filter_add (self: filter, hash: gtk_css_hash_class (klass: decl->classes[i])); |
331 | } |
332 | } |
333 | |
334 | void |
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 (self: filter, hash: gtk_css_hash_name (name: decl->name)); |
342 | if (decl->id) |
343 | gtk_counting_bloom_filter_remove (self: filter, hash: gtk_css_hash_id (id: decl->id)); |
344 | |
345 | for (i = 0; i < decl->n_classes; i++) |
346 | { |
347 | gtk_counting_bloom_filter_remove (self: filter, hash: gtk_css_hash_class (klass: decl->classes[i])); |
348 | } |
349 | } |
350 | |
351 | guint |
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 |
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 |
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 (s1: *ap, s2: *bp); |
413 | } |
414 | |
415 | /* Append the declaration to the string, in selector format */ |
416 | void |
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, val: g_quark_to_string (quark: decl->name)); |
425 | else |
426 | g_string_append (string, val: "*" ); |
427 | |
428 | if (decl->id) |
429 | { |
430 | g_string_append_c (string, '#'); |
431 | g_string_append (string, val: g_quark_to_string (quark: 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 (quark: decl->classes[i]); |
437 | |
438 | g_qsort_with_data (pbase: classnames, total_elems: decl->n_classes, size: sizeof (char *), compare_func: cmpstr, NULL); |
439 | |
440 | for (i = 0; i < decl->n_classes; i++) |
441 | { |
442 | g_string_append_c (string, '.'); |
443 | g_string_append (string, val: classnames[i]); |
444 | } |
445 | g_free (mem: 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 (flags: 1 << i); |
452 | g_assert (name); |
453 | g_string_append_c (string, ':'); |
454 | g_string_append (string, val: name); |
455 | } |
456 | } |
457 | } |
458 | |
459 | char * |
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, string: s); |
465 | |
466 | return g_string_free (string: s, FALSE); |
467 | } |
468 | |