1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2012 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 "gtkcssmatcherprivate.h"
21 
22 #include "gtkcssnodedeclarationprivate.h"
23 #include "gtkcssnodeprivate.h"
24 #include "gtkwidgetpath.h"
25 
26 /* GTK_CSS_MATCHER_WIDGET_PATH */
27 
28 static gboolean
gtk_css_matcher_widget_path_get_parent(GtkCssMatcher * matcher,const GtkCssMatcher * child)29 gtk_css_matcher_widget_path_get_parent (GtkCssMatcher       *matcher,
30                                         const GtkCssMatcher *child)
31 {
32   if (child->path.index == 0)
33     return FALSE;
34 
35   matcher->path.klass = child->path.klass;
36   matcher->path.decl = NULL;
37   matcher->path.path = child->path.path;
38   matcher->path.index = child->path.index - 1;
39   matcher->path.sibling_index = gtk_widget_path_iter_get_sibling_index (matcher->path.path, matcher->path.index);
40 
41   return TRUE;
42 }
43 
44 static gboolean
gtk_css_matcher_widget_path_get_previous(GtkCssMatcher * matcher,const GtkCssMatcher * next)45 gtk_css_matcher_widget_path_get_previous (GtkCssMatcher       *matcher,
46                                           const GtkCssMatcher *next)
47 {
48   if (next->path.sibling_index == 0)
49     return FALSE;
50 
51   matcher->path.klass = next->path.klass;
52   matcher->path.decl = NULL;
53   matcher->path.path = next->path.path;
54   matcher->path.index = next->path.index;
55   matcher->path.sibling_index = next->path.sibling_index - 1;
56 
57   return TRUE;
58 }
59 
60 static GtkStateFlags
gtk_css_matcher_widget_path_get_state(const GtkCssMatcher * matcher)61 gtk_css_matcher_widget_path_get_state (const GtkCssMatcher *matcher)
62 {
63   const GtkWidgetPath *siblings;
64 
65   if (matcher->path.decl)
66     return gtk_css_node_declaration_get_state (matcher->path.decl);
67 
68   siblings = gtk_widget_path_iter_get_siblings (matcher->path.path, matcher->path.index);
69   if (siblings && matcher->path.sibling_index != gtk_widget_path_iter_get_sibling_index (matcher->path.path, matcher->path.index))
70     return gtk_widget_path_iter_get_state (siblings, matcher->path.sibling_index);
71   else
72     return gtk_widget_path_iter_get_state (matcher->path.path, matcher->path.index);
73 }
74 
75 static gboolean
gtk_css_matcher_widget_path_has_name(const GtkCssMatcher * matcher,const char * name)76 gtk_css_matcher_widget_path_has_name (const GtkCssMatcher     *matcher,
77                                       /*interned*/ const char *name)
78 {
79   const GtkWidgetPath *siblings;
80 
81   siblings = gtk_widget_path_iter_get_siblings (matcher->path.path, matcher->path.index);
82   if (siblings && matcher->path.sibling_index != gtk_widget_path_iter_get_sibling_index (matcher->path.path, matcher->path.index))
83     {
84       const char *path_name = gtk_widget_path_iter_get_object_name (siblings, matcher->path.sibling_index);
85 
86       if (path_name == NULL)
87         path_name = g_type_name (gtk_widget_path_iter_get_object_type (siblings, matcher->path.sibling_index));
88 
89       return path_name == name;
90     }
91   else
92     {
93       const char *path_name = gtk_widget_path_iter_get_object_name (matcher->path.path, matcher->path.index);
94 
95       if (path_name == NULL)
96         path_name = g_type_name (gtk_widget_path_iter_get_object_type (matcher->path.path, matcher->path.index));
97 
98       return path_name == name;
99     }
100 }
101 
102 static gboolean
gtk_css_matcher_widget_path_has_class(const GtkCssMatcher * matcher,GQuark class_name)103 gtk_css_matcher_widget_path_has_class (const GtkCssMatcher *matcher,
104                                        GQuark               class_name)
105 {
106   const GtkWidgetPath *siblings;
107 
108   if (matcher->path.decl &&
109       gtk_css_node_declaration_has_class (matcher->path.decl, class_name))
110     return TRUE;
111 
112   siblings = gtk_widget_path_iter_get_siblings (matcher->path.path, matcher->path.index);
113   if (siblings && matcher->path.sibling_index != gtk_widget_path_iter_get_sibling_index (matcher->path.path, matcher->path.index))
114     return gtk_widget_path_iter_has_qclass (siblings, matcher->path.sibling_index, class_name);
115   else
116     return gtk_widget_path_iter_has_qclass (matcher->path.path, matcher->path.index, class_name);
117 }
118 
119 static gboolean
gtk_css_matcher_widget_path_has_id(const GtkCssMatcher * matcher,const char * id)120 gtk_css_matcher_widget_path_has_id (const GtkCssMatcher *matcher,
121                                     const char          *id)
122 {
123   const GtkWidgetPath *siblings;
124 
125   siblings = gtk_widget_path_iter_get_siblings (matcher->path.path, matcher->path.index);
126   if (siblings && matcher->path.sibling_index != gtk_widget_path_iter_get_sibling_index (matcher->path.path, matcher->path.index))
127     return gtk_widget_path_iter_has_name (siblings, matcher->path.sibling_index, id);
128   else
129     return gtk_widget_path_iter_has_name (matcher->path.path, matcher->path.index, id);
130 }
131 
132 static gboolean
gtk_css_matcher_widget_path_has_position(const GtkCssMatcher * matcher,gboolean forward,int a,int b)133 gtk_css_matcher_widget_path_has_position (const GtkCssMatcher *matcher,
134                                           gboolean             forward,
135                                           int                  a,
136                                           int                  b)
137 {
138   const GtkWidgetPath *siblings;
139   int x;
140 
141   siblings = gtk_widget_path_iter_get_siblings (matcher->path.path, matcher->path.index);
142   if (!siblings)
143     return FALSE;
144 
145   if (forward)
146     x = matcher->path.sibling_index + 1;
147   else
148     x = gtk_widget_path_length (siblings) - matcher->path.sibling_index;
149 
150   x -= b;
151 
152   if (a == 0)
153     return x == 0;
154 
155   if (x % a)
156     return FALSE;
157 
158   return x / a >= 0;
159 }
160 
161 static const GtkCssMatcherClass GTK_CSS_MATCHER_WIDGET_PATH = {
162   gtk_css_matcher_widget_path_get_parent,
163   gtk_css_matcher_widget_path_get_previous,
164   gtk_css_matcher_widget_path_get_state,
165   gtk_css_matcher_widget_path_has_name,
166   gtk_css_matcher_widget_path_has_class,
167   gtk_css_matcher_widget_path_has_id,
168   gtk_css_matcher_widget_path_has_position,
169   FALSE
170 };
171 
172 gboolean
_gtk_css_matcher_init(GtkCssMatcher * matcher,const GtkWidgetPath * path,const GtkCssNodeDeclaration * decl)173 _gtk_css_matcher_init (GtkCssMatcher               *matcher,
174                        const GtkWidgetPath         *path,
175                        const GtkCssNodeDeclaration *decl)
176 {
177   if (gtk_widget_path_length (path) == 0)
178     return FALSE;
179 
180   matcher->path.klass = &GTK_CSS_MATCHER_WIDGET_PATH;
181   matcher->path.decl = decl;
182   matcher->path.path = path;
183   matcher->path.index = gtk_widget_path_length (path) - 1;
184   matcher->path.sibling_index = gtk_widget_path_iter_get_sibling_index (path, matcher->path.index);
185 
186   return TRUE;
187 }
188 
189 /* GTK_CSS_MATCHER_NODE */
190 
191 static gboolean
gtk_css_matcher_node_get_parent(GtkCssMatcher * matcher,const GtkCssMatcher * child)192 gtk_css_matcher_node_get_parent (GtkCssMatcher       *matcher,
193                                  const GtkCssMatcher *child)
194 {
195   GtkCssNode *node;
196 
197   node = gtk_css_node_get_parent (child->node.node);
198   if (node == NULL)
199     return FALSE;
200 
201   return gtk_css_node_init_matcher (node, matcher);
202 }
203 
204 static GtkCssNode *
get_previous_visible_sibling(GtkCssNode * node)205 get_previous_visible_sibling (GtkCssNode *node)
206 {
207   do {
208     node = gtk_css_node_get_previous_sibling (node);
209   } while (node && !gtk_css_node_get_visible (node));
210 
211   return node;
212 }
213 
214 static GtkCssNode *
get_next_visible_sibling(GtkCssNode * node)215 get_next_visible_sibling (GtkCssNode *node)
216 {
217   do {
218     node = gtk_css_node_get_next_sibling (node);
219   } while (node && !gtk_css_node_get_visible (node));
220 
221   return node;
222 }
223 
224 static gboolean
gtk_css_matcher_node_get_previous(GtkCssMatcher * matcher,const GtkCssMatcher * next)225 gtk_css_matcher_node_get_previous (GtkCssMatcher       *matcher,
226                                    const GtkCssMatcher *next)
227 {
228   GtkCssNode *node;
229 
230   node = get_previous_visible_sibling (next->node.node);
231   if (node == NULL)
232     return FALSE;
233 
234   return gtk_css_node_init_matcher (node, matcher);
235 }
236 
237 static GtkStateFlags
gtk_css_matcher_node_get_state(const GtkCssMatcher * matcher)238 gtk_css_matcher_node_get_state (const GtkCssMatcher *matcher)
239 {
240   return gtk_css_node_get_state (matcher->node.node);
241 }
242 
243 static gboolean
gtk_css_matcher_node_has_name(const GtkCssMatcher * matcher,const char * name)244 gtk_css_matcher_node_has_name (const GtkCssMatcher     *matcher,
245                                /*interned*/ const char *name)
246 {
247   return gtk_css_node_get_name (matcher->node.node) == name;
248 }
249 
250 static gboolean
gtk_css_matcher_node_has_class(const GtkCssMatcher * matcher,GQuark class_name)251 gtk_css_matcher_node_has_class (const GtkCssMatcher *matcher,
252                                 GQuark               class_name)
253 {
254   return gtk_css_node_has_class (matcher->node.node, class_name);
255 }
256 
257 static gboolean
gtk_css_matcher_node_has_id(const GtkCssMatcher * matcher,const char * id)258 gtk_css_matcher_node_has_id (const GtkCssMatcher *matcher,
259                              const char          *id)
260 {
261   /* assume all callers pass an interned string */
262   return gtk_css_node_get_id (matcher->node.node) == id;
263 }
264 
265 static gboolean
gtk_css_matcher_node_nth_child(GtkCssNode * node,GtkCssNode * (* prev_node_func)(GtkCssNode *),int a,int b)266 gtk_css_matcher_node_nth_child (GtkCssNode *node,
267                                 GtkCssNode *(* prev_node_func) (GtkCssNode *),
268                                 int         a,
269                                 int         b)
270 {
271   int pos, x;
272 
273   /* special-case the common "first-child" and "last-child" */
274   if (a == 0)
275     {
276       while (b > 0 && node != NULL)
277         {
278           b--;
279           node = prev_node_func (node);
280         }
281 
282       return b == 0 && node == NULL;
283     }
284 
285   /* count nodes */
286   for (pos = 0; node != NULL; pos++)
287     node = prev_node_func (node);
288 
289   /* solve pos = a * X + b
290    * and return TRUE if X is integer >= 0 */
291   x = pos - b;
292 
293   if (x % a)
294     return FALSE;
295 
296   return x / a >= 0;
297 }
298 
299 static gboolean
gtk_css_matcher_node_has_position(const GtkCssMatcher * matcher,gboolean forward,int a,int b)300 gtk_css_matcher_node_has_position (const GtkCssMatcher *matcher,
301                                    gboolean             forward,
302                                    int                  a,
303                                    int                  b)
304 {
305   return gtk_css_matcher_node_nth_child (matcher->node.node,
306                                          forward ? get_previous_visible_sibling
307                                                  : get_next_visible_sibling,
308                                          a, b);
309 }
310 
311 static const GtkCssMatcherClass GTK_CSS_MATCHER_NODE = {
312   gtk_css_matcher_node_get_parent,
313   gtk_css_matcher_node_get_previous,
314   gtk_css_matcher_node_get_state,
315   gtk_css_matcher_node_has_name,
316   gtk_css_matcher_node_has_class,
317   gtk_css_matcher_node_has_id,
318   gtk_css_matcher_node_has_position,
319   FALSE
320 };
321 
322 void
_gtk_css_matcher_node_init(GtkCssMatcher * matcher,GtkCssNode * node)323 _gtk_css_matcher_node_init (GtkCssMatcher *matcher,
324                             GtkCssNode    *node)
325 {
326   matcher->node.klass = &GTK_CSS_MATCHER_NODE;
327   matcher->node.node = node;
328 }
329 
330 /* GTK_CSS_MATCHER_WIDGET_ANY */
331 
332 static gboolean
gtk_css_matcher_any_get_parent(GtkCssMatcher * matcher,const GtkCssMatcher * child)333 gtk_css_matcher_any_get_parent (GtkCssMatcher       *matcher,
334                                 const GtkCssMatcher *child)
335 {
336   _gtk_css_matcher_any_init (matcher);
337 
338   return TRUE;
339 }
340 
341 static gboolean
gtk_css_matcher_any_get_previous(GtkCssMatcher * matcher,const GtkCssMatcher * next)342 gtk_css_matcher_any_get_previous (GtkCssMatcher       *matcher,
343                                   const GtkCssMatcher *next)
344 {
345   _gtk_css_matcher_any_init (matcher);
346 
347   return TRUE;
348 }
349 
350 static GtkStateFlags
gtk_css_matcher_any_get_state(const GtkCssMatcher * matcher)351 gtk_css_matcher_any_get_state (const GtkCssMatcher *matcher)
352 {
353   /* XXX: This gets tricky when we implement :not() */
354 
355   return GTK_STATE_FLAG_ACTIVE | GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_SELECTED
356     | GTK_STATE_FLAG_INSENSITIVE | GTK_STATE_FLAG_INCONSISTENT
357     | GTK_STATE_FLAG_FOCUSED | GTK_STATE_FLAG_BACKDROP | GTK_STATE_FLAG_LINK
358     | GTK_STATE_FLAG_VISITED;
359 }
360 
361 static gboolean
gtk_css_matcher_any_has_name(const GtkCssMatcher * matcher,const char * name)362 gtk_css_matcher_any_has_name (const GtkCssMatcher     *matcher,
363                               /*interned*/ const char *name)
364 {
365   return TRUE;
366 }
367 
368 static gboolean
gtk_css_matcher_any_has_class(const GtkCssMatcher * matcher,GQuark class_name)369 gtk_css_matcher_any_has_class (const GtkCssMatcher *matcher,
370                                GQuark               class_name)
371 {
372   return TRUE;
373 }
374 
375 static gboolean
gtk_css_matcher_any_has_id(const GtkCssMatcher * matcher,const char * id)376 gtk_css_matcher_any_has_id (const GtkCssMatcher *matcher,
377                                     const char          *id)
378 {
379   return TRUE;
380 }
381 
382 static gboolean
gtk_css_matcher_any_has_position(const GtkCssMatcher * matcher,gboolean forward,int a,int b)383 gtk_css_matcher_any_has_position (const GtkCssMatcher *matcher,
384                                   gboolean             forward,
385                                   int                  a,
386                                   int                  b)
387 {
388   return TRUE;
389 }
390 
391 static const GtkCssMatcherClass GTK_CSS_MATCHER_ANY = {
392   gtk_css_matcher_any_get_parent,
393   gtk_css_matcher_any_get_previous,
394   gtk_css_matcher_any_get_state,
395   gtk_css_matcher_any_has_name,
396   gtk_css_matcher_any_has_class,
397   gtk_css_matcher_any_has_id,
398   gtk_css_matcher_any_has_position,
399   TRUE
400 };
401 
402 void
_gtk_css_matcher_any_init(GtkCssMatcher * matcher)403 _gtk_css_matcher_any_init (GtkCssMatcher *matcher)
404 {
405   matcher->klass = &GTK_CSS_MATCHER_ANY;
406 }
407 
408 /* GTK_CSS_MATCHER_WIDGET_SUPERSET */
409 
410 static gboolean
gtk_css_matcher_superset_get_parent(GtkCssMatcher * matcher,const GtkCssMatcher * child)411 gtk_css_matcher_superset_get_parent (GtkCssMatcher       *matcher,
412                                      const GtkCssMatcher *child)
413 {
414   _gtk_css_matcher_any_init (matcher);
415 
416   return TRUE;
417 }
418 
419 static gboolean
gtk_css_matcher_superset_get_previous(GtkCssMatcher * matcher,const GtkCssMatcher * next)420 gtk_css_matcher_superset_get_previous (GtkCssMatcher       *matcher,
421                                        const GtkCssMatcher *next)
422 {
423   _gtk_css_matcher_any_init (matcher);
424 
425   return TRUE;
426 }
427 
428 static GtkStateFlags
gtk_css_matcher_superset_get_state(const GtkCssMatcher * matcher)429 gtk_css_matcher_superset_get_state (const GtkCssMatcher *matcher)
430 {
431   /* XXX: This gets tricky when we implement :not() */
432 
433   if (matcher->superset.relevant & GTK_CSS_CHANGE_STATE)
434     return _gtk_css_matcher_get_state (matcher->superset.subset);
435   else
436     return GTK_STATE_FLAG_ACTIVE | GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_SELECTED
437       | GTK_STATE_FLAG_INSENSITIVE | GTK_STATE_FLAG_INCONSISTENT
438       | GTK_STATE_FLAG_FOCUSED | GTK_STATE_FLAG_BACKDROP | GTK_STATE_FLAG_LINK
439       | GTK_STATE_FLAG_VISITED;
440 }
441 
442 static gboolean
gtk_css_matcher_superset_has_name(const GtkCssMatcher * matcher,const char * name)443 gtk_css_matcher_superset_has_name (const GtkCssMatcher     *matcher,
444                                    /*interned*/ const char *name)
445 {
446   if (matcher->superset.relevant & GTK_CSS_CHANGE_NAME)
447     return _gtk_css_matcher_has_name (matcher->superset.subset, name);
448   else
449     return TRUE;
450 }
451 
452 static gboolean
gtk_css_matcher_superset_has_class(const GtkCssMatcher * matcher,GQuark class_name)453 gtk_css_matcher_superset_has_class (const GtkCssMatcher *matcher,
454                                     GQuark               class_name)
455 {
456   if (matcher->superset.relevant & GTK_CSS_CHANGE_CLASS)
457     return _gtk_css_matcher_has_class (matcher->superset.subset, class_name);
458   else
459     return TRUE;
460 }
461 
462 static gboolean
gtk_css_matcher_superset_has_id(const GtkCssMatcher * matcher,const char * id)463 gtk_css_matcher_superset_has_id (const GtkCssMatcher *matcher,
464                                  const char          *id)
465 {
466   if (matcher->superset.relevant & GTK_CSS_CHANGE_NAME)
467     return _gtk_css_matcher_has_id (matcher->superset.subset, id);
468   else
469     return TRUE;
470 }
471 
472 static gboolean
gtk_css_matcher_superset_has_position(const GtkCssMatcher * matcher,gboolean forward,int a,int b)473 gtk_css_matcher_superset_has_position (const GtkCssMatcher *matcher,
474                                        gboolean             forward,
475                                        int                  a,
476                                        int                  b)
477 {
478   if (matcher->superset.relevant & GTK_CSS_CHANGE_POSITION)
479     return _gtk_css_matcher_has_position (matcher->superset.subset, forward, a, b);
480   else
481     return TRUE;
482 }
483 
484 static const GtkCssMatcherClass GTK_CSS_MATCHER_SUPERSET = {
485   gtk_css_matcher_superset_get_parent,
486   gtk_css_matcher_superset_get_previous,
487   gtk_css_matcher_superset_get_state,
488   gtk_css_matcher_superset_has_name,
489   gtk_css_matcher_superset_has_class,
490   gtk_css_matcher_superset_has_id,
491   gtk_css_matcher_superset_has_position,
492   FALSE
493 };
494 
495 void
_gtk_css_matcher_superset_init(GtkCssMatcher * matcher,const GtkCssMatcher * subset,GtkCssChange relevant)496 _gtk_css_matcher_superset_init (GtkCssMatcher       *matcher,
497                                 const GtkCssMatcher *subset,
498                                 GtkCssChange         relevant)
499 {
500   g_return_if_fail (subset != NULL);
501   g_return_if_fail ((relevant & ~(GTK_CSS_CHANGE_CLASS | GTK_CSS_CHANGE_NAME | GTK_CSS_CHANGE_POSITION | GTK_CSS_CHANGE_STATE)) == 0);
502 
503   matcher->superset.klass = &GTK_CSS_MATCHER_SUPERSET;
504   matcher->superset.subset = subset;
505   matcher->superset.relevant = relevant;
506 }
507 
508