1/* GTK - The GIMP Toolkit
2 * Copyright (C) 2010 Carlos Garnacho <carlosg@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 "gtkstylecontextprivate.h"
21
22#include <gdk/gdk.h>
23
24#include "gtkcsscolorvalueprivate.h"
25#include "gtkcssnumbervalueprivate.h"
26#include "gtkcsstransientnodeprivate.h"
27#include "gtkdebug.h"
28#include "gtkintl.h"
29#include "gtkprivate.h"
30#include "gtksettings.h"
31#include "gtksettingsprivate.h"
32#include "gtksnapshot.h"
33
34
35/**
36 * GtkStyleContext:
37 *
38 * `GtkStyleContext` stores styling information affecting a widget.
39 *
40 * In order to construct the final style information, `GtkStyleContext`
41 * queries information from all attached `GtkStyleProviders`. Style
42 * providers can be either attached explicitly to the context through
43 * [method@Gtk.StyleContext.add_provider], or to the display through
44 * [func@Gtk.StyleContext.add_provider_for_display]. The resulting
45 * style is a combination of all providers’ information in priority order.
46 *
47 * For GTK widgets, any `GtkStyleContext` returned by
48 * [method@Gtk.Widget.get_style_context] will already have a `GdkDisplay`
49 * and RTL/LTR information set. The style context will also be updated
50 * automatically if any of these settings change on the widget.
51 *
52 * # Style Classes
53 *
54 * Widgets can add style classes to their context, which can be used to associate
55 * different styles by class. The documentation for individual widgets lists
56 * which style classes it uses itself, and which style classes may be added by
57 * applications to affect their appearance.
58 *
59 * # Custom styling in UI libraries and applications
60 *
61 * If you are developing a library with custom widgets that render differently
62 * than standard components, you may need to add a `GtkStyleProvider` yourself
63 * with the %GTK_STYLE_PROVIDER_PRIORITY_FALLBACK priority, either a
64 * `GtkCssProvider` or a custom object implementing the `GtkStyleProvider`
65 * interface. This way themes may still attempt to style your UI elements in
66 * a different way if needed so.
67 *
68 * If you are using custom styling on an applications, you probably want then
69 * to make your style information prevail to the theme’s, so you must use
70 * a `GtkStyleProvider` with the %GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
71 * priority, keep in mind that the user settings in
72 * `XDG_CONFIG_HOME/gtk-4.0/gtk.css` will
73 * still take precedence over your changes, as it uses the
74 * %GTK_STYLE_PROVIDER_PRIORITY_USER priority.
75 */
76
77#define CURSOR_ASPECT_RATIO (0.04)
78
79struct _GtkStyleContextPrivate
80{
81 GdkDisplay *display;
82
83 guint cascade_changed_id;
84 GtkStyleCascade *cascade;
85 GtkCssNode *cssnode;
86 GSList *saved_nodes;
87};
88typedef struct _GtkStyleContextPrivate GtkStyleContextPrivate;
89
90enum {
91 PROP_0,
92 PROP_DISPLAY,
93 LAST_PROP
94};
95
96static GParamSpec *properties[LAST_PROP] = { NULL, };
97
98static void gtk_style_context_finalize (GObject *object);
99
100static void gtk_style_context_impl_set_property (GObject *object,
101 guint prop_id,
102 const GValue *value,
103 GParamSpec *pspec);
104static void gtk_style_context_impl_get_property (GObject *object,
105 guint prop_id,
106 GValue *value,
107 GParamSpec *pspec);
108
109static GtkCssNode * gtk_style_context_get_root (GtkStyleContext *context);
110
111G_DEFINE_TYPE_WITH_PRIVATE (GtkStyleContext, gtk_style_context, G_TYPE_OBJECT)
112
113static void
114gtk_style_context_class_init (GtkStyleContextClass *klass)
115{
116 GObjectClass *object_class = G_OBJECT_CLASS (klass);
117
118 object_class->finalize = gtk_style_context_finalize;
119 object_class->set_property = gtk_style_context_impl_set_property;
120 object_class->get_property = gtk_style_context_impl_get_property;
121
122 properties[PROP_DISPLAY] =
123 g_param_spec_object (name: "display",
124 P_("Display"),
125 P_("The associated GdkDisplay"),
126 GDK_TYPE_DISPLAY,
127 GTK_PARAM_READWRITE);
128
129 g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_PROP, pspecs: properties);
130}
131
132static void
133gtk_style_context_pop_style_node (GtkStyleContext *context)
134{
135 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
136
137 g_return_if_fail (priv->saved_nodes != NULL);
138
139 if (GTK_IS_CSS_TRANSIENT_NODE (priv->cssnode))
140 gtk_css_node_set_parent (cssnode: priv->cssnode, NULL);
141 g_object_unref (object: priv->cssnode);
142 priv->cssnode = priv->saved_nodes->data;
143 priv->saved_nodes = g_slist_remove (list: priv->saved_nodes, data: priv->cssnode);
144}
145
146static void
147gtk_style_context_cascade_changed (GtkStyleCascade *cascade,
148 GtkStyleContext *context)
149{
150 gtk_css_node_invalidate_style_provider (cssnode: gtk_style_context_get_root (context));
151}
152
153static void
154gtk_style_context_set_cascade (GtkStyleContext *context,
155 GtkStyleCascade *cascade)
156{
157 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
158
159 if (priv->cascade == cascade)
160 return;
161
162 if (priv->cascade)
163 {
164 g_signal_handler_disconnect (instance: priv->cascade, handler_id: priv->cascade_changed_id);
165 priv->cascade_changed_id = 0;
166 g_object_unref (object: priv->cascade);
167 }
168
169 if (cascade)
170 {
171 g_object_ref (cascade);
172 priv->cascade_changed_id = g_signal_connect (cascade,
173 "gtk-private-changed",
174 G_CALLBACK (gtk_style_context_cascade_changed),
175 context);
176 }
177
178 priv->cascade = cascade;
179
180 if (cascade && priv->cssnode != NULL)
181 gtk_style_context_cascade_changed (cascade, context);
182}
183
184static void
185gtk_style_context_init (GtkStyleContext *context)
186{
187 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
188
189 priv->display = gdk_display_get_default ();
190
191 if (priv->display == NULL)
192 g_error ("Can't create a GtkStyleContext without a display connection");
193
194 gtk_style_context_set_cascade (context,
195 cascade: _gtk_settings_get_style_cascade (settings: gtk_settings_get_for_display (display: priv->display), scale: 1));
196}
197
198static void
199gtk_style_context_finalize (GObject *object)
200{
201 GtkStyleContext *context = GTK_STYLE_CONTEXT (object);
202 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
203
204 while (priv->saved_nodes)
205 gtk_style_context_pop_style_node (context);
206
207 gtk_style_context_set_cascade (context, NULL);
208
209 if (priv->cssnode)
210 g_object_unref (object: priv->cssnode);
211
212 G_OBJECT_CLASS (gtk_style_context_parent_class)->finalize (object);
213}
214
215static void
216gtk_style_context_impl_set_property (GObject *object,
217 guint prop_id,
218 const GValue *value,
219 GParamSpec *pspec)
220{
221 GtkStyleContext *context = GTK_STYLE_CONTEXT (object);
222
223 switch (prop_id)
224 {
225 case PROP_DISPLAY:
226 gtk_style_context_set_display (context, display: g_value_get_object (value));
227 break;
228 default:
229 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
230 break;
231 }
232}
233
234static void
235gtk_style_context_impl_get_property (GObject *object,
236 guint prop_id,
237 GValue *value,
238 GParamSpec *pspec)
239{
240 GtkStyleContext *context = GTK_STYLE_CONTEXT (object);
241 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
242
243 switch (prop_id)
244 {
245 case PROP_DISPLAY:
246 g_value_set_object (value, v_object: priv->display);
247 break;
248 default:
249 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
250 break;
251 }
252}
253
254/* returns TRUE if someone called gtk_style_context_save() but hasn’t
255 * called gtk_style_context_restore() yet.
256 * In those situations we don’t invalidate the context when somebody
257 * changes state/classes.
258 */
259static gboolean
260gtk_style_context_is_saved (GtkStyleContext *context)
261{
262 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
263
264 return priv->saved_nodes != NULL;
265}
266
267static GtkCssNode *
268gtk_style_context_get_root (GtkStyleContext *context)
269{
270 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
271
272 if (priv->saved_nodes != NULL)
273 return g_slist_last (list: priv->saved_nodes)->data;
274 else
275 return priv->cssnode;
276}
277
278GtkStyleProvider *
279gtk_style_context_get_style_provider (GtkStyleContext *context)
280{
281 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
282
283 return GTK_STYLE_PROVIDER (priv->cascade);
284}
285
286static gboolean
287gtk_style_context_has_custom_cascade (GtkStyleContext *context)
288{
289 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
290 GtkSettings *settings = gtk_settings_get_for_display (display: priv->display);
291
292 return priv->cascade != _gtk_settings_get_style_cascade (settings, scale: _gtk_style_cascade_get_scale (cascade: priv->cascade));
293}
294
295GtkCssStyle *
296gtk_style_context_lookup_style (GtkStyleContext *context)
297{
298 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
299
300 /* Code will recreate style if it was changed */
301 return gtk_css_node_get_style (cssnode: priv->cssnode);
302}
303
304GtkCssNode*
305gtk_style_context_get_node (GtkStyleContext *context)
306{
307 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
308
309 return priv->cssnode;
310}
311
312GtkStyleContext *
313gtk_style_context_new_for_node (GtkCssNode *node)
314{
315 GtkStyleContext *context;
316 GtkStyleContextPrivate *priv;
317
318 g_return_val_if_fail (GTK_IS_CSS_NODE (node), NULL);
319
320 context = g_object_new (GTK_TYPE_STYLE_CONTEXT, NULL);
321 priv = gtk_style_context_get_instance_private (self: context);
322 priv->cssnode = g_object_ref (node);
323
324 return context;
325}
326
327/**
328 * gtk_style_context_add_provider:
329 * @context: a `GtkStyleContext`
330 * @provider: a `GtkStyleProvider`
331 * @priority: the priority of the style provider. The lower
332 * it is, the earlier it will be used in the style construction.
333 * Typically this will be in the range between
334 * %GTK_STYLE_PROVIDER_PRIORITY_FALLBACK and
335 * %GTK_STYLE_PROVIDER_PRIORITY_USER
336 *
337 * Adds a style provider to @context, to be used in style construction.
338 *
339 * Note that a style provider added by this function only affects
340 * the style of the widget to which @context belongs. If you want
341 * to affect the style of all widgets, use
342 * [func@Gtk.StyleContext.add_provider_for_display].
343 *
344 * Note: If both priorities are the same, a `GtkStyleProvider`
345 * added through this function takes precedence over another added
346 * through [func@Gtk.StyleContext.add_provider_for_display].
347 */
348void
349gtk_style_context_add_provider (GtkStyleContext *context,
350 GtkStyleProvider *provider,
351 guint priority)
352{
353 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
354
355 g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
356 g_return_if_fail (GTK_IS_STYLE_PROVIDER (provider));
357
358 if (!gtk_style_context_has_custom_cascade (context))
359 {
360 GtkStyleCascade *new_cascade;
361
362 new_cascade = _gtk_style_cascade_new ();
363 _gtk_style_cascade_set_scale (cascade: new_cascade, scale: _gtk_style_cascade_get_scale (cascade: priv->cascade));
364 _gtk_style_cascade_set_parent (cascade: new_cascade,
365 parent: _gtk_settings_get_style_cascade (settings: gtk_settings_get_for_display (display: priv->display), scale: 1));
366 _gtk_style_cascade_add_provider (cascade: new_cascade, provider, priority);
367 gtk_style_context_set_cascade (context, cascade: new_cascade);
368 g_object_unref (object: new_cascade);
369 }
370 else
371 {
372 _gtk_style_cascade_add_provider (cascade: priv->cascade, provider, priority);
373 }
374}
375
376/**
377 * gtk_style_context_remove_provider:
378 * @context: a `GtkStyleContext`
379 * @provider: a `GtkStyleProvider`
380 *
381 * Removes @provider from the style providers list in @context.
382 */
383void
384gtk_style_context_remove_provider (GtkStyleContext *context,
385 GtkStyleProvider *provider)
386{
387 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
388
389 g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
390 g_return_if_fail (GTK_IS_STYLE_PROVIDER (provider));
391
392 if (!gtk_style_context_has_custom_cascade (context))
393 return;
394
395 _gtk_style_cascade_remove_provider (cascade: priv->cascade, provider);
396}
397
398/**
399 * gtk_style_context_add_provider_for_display:
400 * @display: a `GdkDisplay`
401 * @provider: a `GtkStyleProvider`
402 * @priority: the priority of the style provider. The lower
403 * it is, the earlier it will be used in the style construction.
404 * Typically this will be in the range between
405 * %GTK_STYLE_PROVIDER_PRIORITY_FALLBACK and
406 * %GTK_STYLE_PROVIDER_PRIORITY_USER
407 *
408 * Adds a global style provider to @display, which will be used
409 * in style construction for all `GtkStyleContexts` under @display.
410 *
411 * GTK uses this to make styling information from `GtkSettings`
412 * available.
413 *
414 * Note: If both priorities are the same, A `GtkStyleProvider`
415 * added through [method@Gtk.StyleContext.add_provider] takes
416 * precedence over another added through this function.
417 **/
418void
419gtk_style_context_add_provider_for_display (GdkDisplay *display,
420 GtkStyleProvider *provider,
421 guint priority)
422{
423 GtkStyleCascade *cascade;
424
425 g_return_if_fail (GDK_IS_DISPLAY (display));
426 g_return_if_fail (GTK_IS_STYLE_PROVIDER (provider));
427 g_return_if_fail (!GTK_IS_SETTINGS (provider) || _gtk_settings_get_display (GTK_SETTINGS (provider)) == display);
428
429 cascade = _gtk_settings_get_style_cascade (settings: gtk_settings_get_for_display (display), scale: 1);
430 _gtk_style_cascade_add_provider (cascade, provider, priority);
431}
432
433/**
434 * gtk_style_context_remove_provider_for_display:
435 * @display: a `GdkDisplay`
436 * @provider: a `GtkStyleProvider`
437 *
438 * Removes @provider from the global style providers list in @display.
439 */
440void
441gtk_style_context_remove_provider_for_display (GdkDisplay *display,
442 GtkStyleProvider *provider)
443{
444 GtkStyleCascade *cascade;
445
446 g_return_if_fail (GDK_IS_DISPLAY (display));
447 g_return_if_fail (GTK_IS_STYLE_PROVIDER (provider));
448 g_return_if_fail (!GTK_IS_SETTINGS (provider));
449
450 cascade = _gtk_settings_get_style_cascade (settings: gtk_settings_get_for_display (display), scale: 1);
451 _gtk_style_cascade_remove_provider (cascade, provider);
452}
453
454/**
455 * gtk_style_context_set_state:
456 * @context: a `GtkStyleContext`
457 * @flags: state to represent
458 *
459 * Sets the state to be used for style matching.
460 */
461void
462gtk_style_context_set_state (GtkStyleContext *context,
463 GtkStateFlags flags)
464{
465 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
466
467 g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
468
469 gtk_css_node_set_state (cssnode: priv->cssnode, state_flags: flags);
470}
471
472/**
473 * gtk_style_context_get_state:
474 * @context: a `GtkStyleContext`
475 *
476 * Returns the state used for style matching.
477 *
478 * This method should only be used to retrieve the `GtkStateFlags`
479 * to pass to `GtkStyleContext` methods, like
480 * [method@Gtk.StyleContext.get_padding].
481 * If you need to retrieve the current state of a `GtkWidget`, use
482 * [method@Gtk.Widget.get_state_flags].
483 *
484 * Returns: the state flags
485 **/
486GtkStateFlags
487gtk_style_context_get_state (GtkStyleContext *context)
488{
489 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
490
491 g_return_val_if_fail (GTK_IS_STYLE_CONTEXT (context), 0);
492
493 return gtk_css_node_get_state (cssnode: priv->cssnode);
494}
495
496/**
497 * gtk_style_context_set_scale:
498 * @context: a `GtkStyleContext`
499 * @scale: scale
500 *
501 * Sets the scale to use when getting image assets for the style.
502 **/
503void
504gtk_style_context_set_scale (GtkStyleContext *context,
505 int scale)
506{
507 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
508
509 g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
510
511 if (scale == _gtk_style_cascade_get_scale (cascade: priv->cascade))
512 return;
513
514 if (gtk_style_context_has_custom_cascade (context))
515 {
516 _gtk_style_cascade_set_scale (cascade: priv->cascade, scale);
517 }
518 else
519 {
520 GtkStyleCascade *new_cascade;
521
522 new_cascade = _gtk_settings_get_style_cascade (settings: gtk_settings_get_for_display (display: priv->display),
523 scale);
524 gtk_style_context_set_cascade (context, cascade: new_cascade);
525 }
526}
527
528/**
529 * gtk_style_context_get_scale:
530 * @context: a `GtkStyleContext`
531 *
532 * Returns the scale used for assets.
533 *
534 * Returns: the scale
535 **/
536int
537gtk_style_context_get_scale (GtkStyleContext *context)
538{
539 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
540
541 g_return_val_if_fail (GTK_IS_STYLE_CONTEXT (context), 0);
542
543 return _gtk_style_cascade_get_scale (cascade: priv->cascade);
544}
545
546/*
547 * gtk_style_context_save_to_node:
548 * @context: a `GtkStyleContext`
549 * @node: the node to save to
550 *
551 * Saves the @context state to a node.
552 *
553 * This allows temporary modifications done through
554 * [method@Gtk.StyleContext.add_class],
555 * [method@Gtk.StyleContext.remove_class],
556 * [method@Gtk.StyleContext.set_state] etc.
557 *
558 * Rendering using [func@Gtk.render_background] or similar
559 * functions are done using the given @node.
560 *
561 * To undo, call [method@Gtk.StyleContext.restore].
562 * The matching call to [method@Gtk.StyleContext.restore]
563 * must be done before GTK returns to the main loop.
564 */
565void
566gtk_style_context_save_to_node (GtkStyleContext *context,
567 GtkCssNode *node)
568{
569 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
570
571 g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
572 g_return_if_fail (GTK_IS_CSS_NODE (node));
573
574 priv->saved_nodes = g_slist_prepend (list: priv->saved_nodes, data: priv->cssnode);
575 priv->cssnode = g_object_ref (node);
576}
577
578/**
579 * gtk_style_context_save:
580 * @context: a `GtkStyleContext`
581 *
582 * Saves the @context state.
583 *
584 * This allows temporary modifications done through
585 * [method@Gtk.StyleContext.add_class],
586 * [method@Gtk.StyleContext.remove_class],
587 * [method@Gtk.StyleContext.set_state] to be quickly
588 * reverted in one go through [method@Gtk.StyleContext.restore].
589 *
590 * The matching call to [method@Gtk.StyleContext.restore]
591 * must be done before GTK returns to the main loop.
592 **/
593void
594gtk_style_context_save (GtkStyleContext *context)
595{
596 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
597 GtkCssNode *cssnode;
598
599 g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
600
601
602 /* Make sure we have the style existing. It is the
603 * parent of the new saved node after all.
604 */
605 if (!gtk_style_context_is_saved (context))
606 gtk_style_context_lookup_style (context);
607
608 cssnode = gtk_css_transient_node_new (parent: priv->cssnode);
609 gtk_css_node_set_parent (cssnode, parent: gtk_style_context_get_root (context));
610 gtk_style_context_save_to_node (context, node: cssnode);
611
612 g_object_unref (object: cssnode);
613}
614
615/**
616 * gtk_style_context_restore:
617 * @context: a `GtkStyleContext`
618 *
619 * Restores @context state to a previous stage.
620 *
621 * See [method@Gtk.StyleContext.save].
622 **/
623void
624gtk_style_context_restore (GtkStyleContext *context)
625{
626 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
627
628 g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
629
630 if (priv->saved_nodes == NULL)
631 {
632 g_warning ("Unpaired gtk_style_context_restore() call");
633 return;
634 }
635
636 gtk_style_context_pop_style_node (context);
637}
638
639/**
640 * gtk_style_context_add_class:
641 * @context: a `GtkStyleContext`
642 * @class_name: class name to use in styling
643 *
644 * Adds a style class to @context, so later uses of the
645 * style context will make use of this new class for styling.
646 *
647 * In the CSS file format, a `GtkEntry` defining a “search”
648 * class, would be matched by:
649 *
650 * ```css
651 * entry.search { ... }
652 * ```
653 *
654 * While any widget defining a “search” class would be
655 * matched by:
656 * ```css
657 * .search { ... }
658 * ```
659 */
660void
661gtk_style_context_add_class (GtkStyleContext *context,
662 const char *class_name)
663{
664 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
665 GQuark class_quark;
666
667 g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
668 g_return_if_fail (class_name != NULL);
669
670 class_quark = g_quark_from_string (string: class_name);
671
672 gtk_css_node_add_class (cssnode: priv->cssnode, style_class: class_quark);
673}
674
675/**
676 * gtk_style_context_remove_class:
677 * @context: a `GtkStyleContext`
678 * @class_name: class name to remove
679 *
680 * Removes @class_name from @context.
681 */
682void
683gtk_style_context_remove_class (GtkStyleContext *context,
684 const char *class_name)
685{
686 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
687 GQuark class_quark;
688
689 g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
690 g_return_if_fail (class_name != NULL);
691
692 class_quark = g_quark_try_string (string: class_name);
693 if (!class_quark)
694 return;
695
696 gtk_css_node_remove_class (cssnode: priv->cssnode, style_class: class_quark);
697}
698
699/**
700 * gtk_style_context_has_class:
701 * @context: a `GtkStyleContext`
702 * @class_name: a class name
703 *
704 * Returns %TRUE if @context currently has defined the
705 * given class name.
706 *
707 * Returns: %TRUE if @context has @class_name defined
708 **/
709gboolean
710gtk_style_context_has_class (GtkStyleContext *context,
711 const char *class_name)
712{
713 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
714 GQuark class_quark;
715
716 g_return_val_if_fail (GTK_IS_STYLE_CONTEXT (context), FALSE);
717 g_return_val_if_fail (class_name != NULL, FALSE);
718
719 class_quark = g_quark_try_string (string: class_name);
720 if (!class_quark)
721 return FALSE;
722
723 return gtk_css_node_has_class (cssnode: priv->cssnode, style_class: class_quark);
724}
725
726GtkCssValue *
727_gtk_style_context_peek_property (GtkStyleContext *context,
728 guint property_id)
729{
730 GtkCssStyle *values = gtk_style_context_lookup_style (context);
731
732 return gtk_css_style_get_value (style: values, id: property_id);
733}
734
735/**
736 * gtk_style_context_set_display:
737 * @context: a `GtkStyleContext`
738 * @display: a `GdkDisplay`
739 *
740 * Attaches @context to the given display.
741 *
742 * The display is used to add style information from “global”
743 * style providers, such as the display's `GtkSettings` instance.
744 *
745 * If you are using a `GtkStyleContext` returned from
746 * [method@Gtk.Widget.get_style_context], you do not need to
747 * call this yourself.
748 */
749void
750gtk_style_context_set_display (GtkStyleContext *context,
751 GdkDisplay *display)
752{
753 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
754 GtkStyleCascade *display_cascade;
755
756 g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
757 g_return_if_fail (GDK_IS_DISPLAY (display));
758
759 if (priv->display == display)
760 return;
761
762 if (gtk_style_context_has_custom_cascade (context))
763 {
764 display_cascade = _gtk_settings_get_style_cascade (settings: gtk_settings_get_for_display (display), scale: 1);
765 _gtk_style_cascade_set_parent (cascade: priv->cascade, parent: display_cascade);
766 }
767 else
768 {
769 display_cascade = _gtk_settings_get_style_cascade (settings: gtk_settings_get_for_display (display),
770 scale: _gtk_style_cascade_get_scale (cascade: priv->cascade));
771 gtk_style_context_set_cascade (context, cascade: display_cascade);
772 }
773
774 priv->display = display;
775
776 g_object_notify_by_pspec (G_OBJECT (context), pspec: properties[PROP_DISPLAY]);
777}
778
779/**
780 * gtk_style_context_get_display:
781 * @context: a `GtkStyleContext`
782 *
783 * Returns the `GdkDisplay` to which @context is attached.
784 *
785 * Returns: (transfer none): a `GdkDisplay`.
786 */
787GdkDisplay *
788gtk_style_context_get_display (GtkStyleContext *context)
789{
790 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
791
792 g_return_val_if_fail (GTK_IS_STYLE_CONTEXT (context), NULL);
793
794 return priv->display;
795}
796
797static gboolean
798gtk_style_context_resolve_color (GtkStyleContext *context,
799 GtkCssValue *color,
800 GdkRGBA *result)
801{
802 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
803 GtkCssValue *val;
804
805 g_return_val_if_fail (GTK_IS_STYLE_CONTEXT (context), FALSE);
806 g_return_val_if_fail (color != NULL, FALSE);
807 g_return_val_if_fail (result != NULL, FALSE);
808
809 val = _gtk_css_color_value_resolve (color,
810 GTK_STYLE_PROVIDER (priv->cascade),
811 current: _gtk_style_context_peek_property (context, property_id: GTK_CSS_PROPERTY_COLOR),
812 NULL);
813 if (val == NULL)
814 return FALSE;
815
816 *result = *gtk_css_color_value_get_rgba (color: val);
817 _gtk_css_value_unref (value: val);
818 return TRUE;
819}
820
821/**
822 * gtk_style_context_lookup_color:
823 * @context: a `GtkStyleContext`
824 * @color_name: color name to lookup
825 * @color: (out): Return location for the looked up color
826 *
827 * Looks up and resolves a color name in the @context color map.
828 *
829 * Returns: %TRUE if @color_name was found and resolved, %FALSE otherwise
830 */
831gboolean
832gtk_style_context_lookup_color (GtkStyleContext *context,
833 const char *color_name,
834 GdkRGBA *color)
835{
836 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
837 GtkCssValue *value;
838
839 g_return_val_if_fail (GTK_IS_STYLE_CONTEXT (context), FALSE);
840 g_return_val_if_fail (color_name != NULL, FALSE);
841 g_return_val_if_fail (color != NULL, FALSE);
842
843 value = gtk_style_provider_get_color (GTK_STYLE_PROVIDER (priv->cascade), name: color_name);
844 if (value == NULL)
845 return FALSE;
846
847 return gtk_style_context_resolve_color (context, color: value, result: color);
848}
849
850/**
851 * gtk_style_context_get_color:
852 * @context: a `GtkStyleContext`
853 * @color: (out): return value for the foreground color
854 *
855 * Gets the foreground color for a given state.
856 */
857void
858gtk_style_context_get_color (GtkStyleContext *context,
859 GdkRGBA *color)
860{
861 g_return_if_fail (color != NULL);
862 g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
863
864 *color = *gtk_css_color_value_get_rgba (color: _gtk_style_context_peek_property (context, property_id: GTK_CSS_PROPERTY_COLOR));
865}
866
867/**
868 * gtk_style_context_get_border:
869 * @context: a `GtkStyleContext`
870 * @border: (out): return value for the border settings
871 *
872 * Gets the border for a given state as a `GtkBorder`.
873 */
874void
875gtk_style_context_get_border (GtkStyleContext *context,
876 GtkBorder *border)
877{
878 GtkCssStyle *style;
879
880 g_return_if_fail (border != NULL);
881 g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
882
883 style = gtk_style_context_lookup_style (context);
884
885 border->top = round (x: _gtk_css_number_value_get (number: style->border->border_top_width, one_hundred_percent: 100));
886 border->right = round (x: _gtk_css_number_value_get (number: style->border->border_right_width, one_hundred_percent: 100));
887 border->bottom = round (x: _gtk_css_number_value_get (number: style->border->border_bottom_width, one_hundred_percent: 100));
888 border->left = round (x: _gtk_css_number_value_get (number: style->border->border_left_width, one_hundred_percent: 100));
889}
890
891/**
892 * gtk_style_context_get_padding:
893 * @context: a `GtkStyleContext`
894 * @padding: (out): return value for the padding settings
895 *
896 * Gets the padding for a given state as a `GtkBorder`.
897 */
898void
899gtk_style_context_get_padding (GtkStyleContext *context,
900 GtkBorder *padding)
901{
902 GtkCssStyle *style;
903
904 g_return_if_fail (padding != NULL);
905 g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
906
907 style = gtk_style_context_lookup_style (context);
908
909 padding->top = round (x: _gtk_css_number_value_get (number: style->size->padding_top, one_hundred_percent: 100));
910 padding->right = round (x: _gtk_css_number_value_get (number: style->size->padding_right, one_hundred_percent: 100));
911 padding->bottom = round (x: _gtk_css_number_value_get (number: style->size->padding_bottom, one_hundred_percent: 100));
912 padding->left = round (x: _gtk_css_number_value_get (number: style->size->padding_left, one_hundred_percent: 100));
913}
914
915/**
916 * gtk_style_context_get_margin:
917 * @context: a `GtkStyleContext`
918 * @margin: (out): return value for the margin settings
919 *
920 * Gets the margin for a given state as a `GtkBorder`.
921 */
922void
923gtk_style_context_get_margin (GtkStyleContext *context,
924 GtkBorder *margin)
925{
926 GtkCssStyle *style;
927
928 g_return_if_fail (margin != NULL);
929 g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
930
931 style = gtk_style_context_lookup_style (context);
932
933 margin->top = round (x: _gtk_css_number_value_get (number: style->size->margin_top, one_hundred_percent: 100));
934 margin->right = round (x: _gtk_css_number_value_get (number: style->size->margin_right, one_hundred_percent: 100));
935 margin->bottom = round (x: _gtk_css_number_value_get (number: style->size->margin_bottom, one_hundred_percent: 100));
936 margin->left = round (x: _gtk_css_number_value_get (number: style->size->margin_left, one_hundred_percent: 100));
937}
938
939void
940_gtk_style_context_get_cursor_color (GtkStyleContext *context,
941 GdkRGBA *primary_color,
942 GdkRGBA *secondary_color)
943{
944 GtkCssStyle *style;
945
946 style = gtk_style_context_lookup_style (context);
947
948 if (primary_color)
949 *primary_color = *gtk_css_color_value_get_rgba (color: style->font->caret_color ? style->font->caret_color : style->core->color);
950
951 if (secondary_color)
952 *secondary_color = *gtk_css_color_value_get_rgba (color: style->font->secondary_caret_color ? style->font->secondary_caret_color : style->core->color);
953}
954
955static void
956draw_insertion_cursor (GtkStyleContext *context,
957 cairo_t *cr,
958 double x,
959 double y,
960 double width,
961 double height,
962 double aspect_ratio,
963 gboolean is_primary,
964 PangoDirection direction,
965 gboolean draw_arrow)
966{
967 GdkRGBA primary_color;
968 GdkRGBA secondary_color;
969 int stem_width;
970 double angle;
971 double dx, dy;
972 double xx1, yy1, xx2, yy2;
973
974 cairo_save (cr);
975 cairo_new_path (cr);
976
977 _gtk_style_context_get_cursor_color (context, primary_color: &primary_color, secondary_color: &secondary_color);
978 gdk_cairo_set_source_rgba (cr, rgba: is_primary ? &primary_color : &secondary_color);
979
980 stem_width = height * aspect_ratio + 1;
981
982 yy1 = y;
983 yy2 = y + height;
984
985 if (width < 0)
986 {
987 xx1 = x;
988 xx2 = x - width;
989 }
990 else
991 {
992 xx1 = x + width;
993 xx2 = x;
994 }
995
996 angle = atan2 (y: height, x: width);
997
998 dx = (stem_width/2.0) * cos (M_PI/2 - angle);
999 dy = (stem_width/2.0) * sin (M_PI/2 - angle);
1000
1001 if (draw_arrow)
1002 {
1003 if (direction == PANGO_DIRECTION_RTL)
1004 {
1005 double x0, y0, x1, y1, x2, y2;
1006
1007 x0 = xx2 - dx + 2 * dy;
1008 y0 = yy2 - dy - 2 * dx;
1009
1010 x1 = x0 + 4 * dy;
1011 y1 = y0 - 4 * dx;
1012 x2 = x0 + 2 * dy - 3 * dx;
1013 y2 = y0 - 2 * dx - 3 * dy;
1014
1015 cairo_move_to (cr, x: xx1 + dx, y: yy1 + dy);
1016 cairo_line_to (cr, x: xx2 + dx, y: yy2 + dy);
1017 cairo_line_to (cr, x: x2, y: y2);
1018 cairo_line_to (cr, x: x1, y: y1);
1019 cairo_line_to (cr, x: xx1 - dx, y: yy1 - dy);
1020 }
1021 else if (direction == PANGO_DIRECTION_LTR)
1022 {
1023 double x0, y0, x1, y1, x2, y2;
1024
1025 x0 = xx2 + dx + 2 * dy;
1026 y0 = yy2 + dy - 2 * dx;
1027
1028 x1 = x0 + 4 * dy;
1029 y1 = y0 - 4 * dx;
1030 x2 = x0 + 2 * dy + 3 * dx;
1031 y2 = y0 - 2 * dx + 3 * dy;
1032
1033 cairo_move_to (cr, x: xx1 - dx, y: yy1 - dy);
1034 cairo_line_to (cr, x: xx2 - dx, y: yy2 - dy);
1035 cairo_line_to (cr, x: x2, y: y2);
1036 cairo_line_to (cr, x: x1, y: y1);
1037 cairo_line_to (cr, x: xx1 + dx, y: yy1 + dy);
1038 }
1039 else
1040 g_assert_not_reached();
1041 }
1042 else
1043 {
1044 cairo_move_to (cr, x: xx1 + dx, y: yy1 + dy);
1045 cairo_line_to (cr, x: xx2 + dx, y: yy2 + dy);
1046 cairo_line_to (cr, x: xx2 - dx, y: yy2 - dy);
1047 cairo_line_to (cr, x: xx1 - dx, y: yy1 - dy);
1048 }
1049
1050 cairo_fill (cr);
1051
1052 cairo_restore (cr);
1053}
1054
1055static void
1056get_insertion_cursor_bounds (double width,
1057 double height,
1058 double aspect_ratio,
1059 PangoDirection direction,
1060 gboolean draw_arrow,
1061 graphene_rect_t *bounds)
1062{
1063 int stem_width;
1064
1065 if (width < 0)
1066 width = - width;
1067
1068 stem_width = height * aspect_ratio + 1;
1069
1070 graphene_rect_init (r: bounds,
1071 x: - 2 * stem_width, y: - stem_width,
1072 width: width + 4 * stem_width, height: height + 2 * stem_width);
1073}
1074
1075static void
1076snapshot_insertion_cursor (GtkSnapshot *snapshot,
1077 GtkStyleContext *context,
1078 double width,
1079 double height,
1080 double aspect_ratio,
1081 gboolean is_primary,
1082 PangoDirection direction,
1083 gboolean draw_arrow)
1084{
1085 if (width != 0 || draw_arrow)
1086 {
1087 cairo_t *cr;
1088 graphene_rect_t bounds;
1089
1090 get_insertion_cursor_bounds (width, height, aspect_ratio, direction, draw_arrow, bounds: &bounds);
1091 cr = gtk_snapshot_append_cairo (snapshot, bounds: &bounds);
1092
1093 draw_insertion_cursor (context, cr, x: 0, y: 0, width, height, aspect_ratio, is_primary, direction, draw_arrow);
1094
1095 cairo_destroy (cr);
1096 }
1097 else
1098 {
1099 GdkRGBA primary_color;
1100 GdkRGBA secondary_color;
1101 int stem_width;
1102 int offset;
1103
1104 _gtk_style_context_get_cursor_color (context, primary_color: &primary_color, secondary_color: &secondary_color);
1105
1106 stem_width = height * aspect_ratio + 1;
1107
1108 /* put (stem_width % 2) on the proper side of the cursor */
1109 if (direction == PANGO_DIRECTION_LTR)
1110 offset = stem_width / 2;
1111 else
1112 offset = stem_width - stem_width / 2;
1113
1114 gtk_snapshot_append_color (snapshot,
1115 color: is_primary ? &primary_color : &secondary_color,
1116 bounds: &GRAPHENE_RECT_INIT (- offset, 0, stem_width, height));
1117 }
1118}
1119
1120/**
1121 * gtk_snapshot_render_insertion_cursor:
1122 * @snapshot: snapshot to render to
1123 * @context: a `GtkStyleContext`
1124 * @x: X origin
1125 * @y: Y origin
1126 * @layout: the `PangoLayout` of the text
1127 * @index: the index in the `PangoLayout`
1128 * @direction: the `PangoDirection` of the text
1129 *
1130 * Draws a text caret using @snapshot at the specified index of @layout.
1131 */
1132void
1133gtk_snapshot_render_insertion_cursor (GtkSnapshot *snapshot,
1134 GtkStyleContext *context,
1135 double x,
1136 double y,
1137 PangoLayout *layout,
1138 int index,
1139 PangoDirection direction)
1140{
1141 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
1142 gboolean split_cursor;
1143 double aspect_ratio;
1144 PangoRectangle strong_pos, weak_pos;
1145 PangoRectangle *cursor1, *cursor2;
1146 GdkSeat *seat;
1147 PangoDirection keyboard_direction;
1148 PangoDirection direction2;
1149
1150 g_return_if_fail (snapshot != NULL);
1151 g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
1152 g_return_if_fail (PANGO_IS_LAYOUT (layout));
1153 g_return_if_fail (index >= 0);
1154
1155 g_object_get (object: gtk_settings_get_for_display (display: priv->display),
1156 first_property_name: "gtk-split-cursor", &split_cursor,
1157 "gtk-cursor-aspect-ratio", &aspect_ratio,
1158 NULL);
1159
1160 keyboard_direction = PANGO_DIRECTION_LTR;
1161 seat = gdk_display_get_default_seat (display: priv->display);
1162 if (seat)
1163 {
1164 GdkDevice *keyboard = gdk_seat_get_keyboard (seat);
1165
1166 if (keyboard)
1167 keyboard_direction = gdk_device_get_direction (device: keyboard);
1168 }
1169
1170 pango_layout_get_caret_pos (layout, index_: index, strong_pos: &strong_pos, weak_pos: &weak_pos);
1171
1172 direction2 = PANGO_DIRECTION_NEUTRAL;
1173
1174 if (split_cursor)
1175 {
1176 cursor1 = &strong_pos;
1177
1178 if (strong_pos.x != weak_pos.x || strong_pos.y != weak_pos.y)
1179 {
1180 direction2 = (direction == PANGO_DIRECTION_LTR) ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR;
1181 cursor2 = &weak_pos;
1182 }
1183 }
1184 else
1185 {
1186 if (keyboard_direction == direction)
1187 cursor1 = &strong_pos;
1188 else
1189 cursor1 = &weak_pos;
1190 }
1191
1192 gtk_snapshot_save (snapshot);
1193 gtk_snapshot_translate (snapshot, point: &GRAPHENE_POINT_INIT (x + PANGO_PIXELS (MIN (cursor1->x, cursor1->x + cursor1->width)), y + PANGO_PIXELS (cursor1->y)));
1194 snapshot_insertion_cursor (snapshot,
1195 context,
1196 PANGO_PIXELS (cursor1->width),
1197 PANGO_PIXELS (cursor1->height),
1198 aspect_ratio,
1199 TRUE,
1200 direction,
1201 draw_arrow: direction2 != PANGO_DIRECTION_NEUTRAL);
1202 gtk_snapshot_restore (snapshot);
1203
1204 if (direction2 != PANGO_DIRECTION_NEUTRAL)
1205 {
1206 gtk_snapshot_save (snapshot);
1207 gtk_snapshot_translate (snapshot, point: &GRAPHENE_POINT_INIT (x + PANGO_PIXELS (MIN (cursor2->x, cursor2->x + cursor2->width)), y + PANGO_PIXELS (cursor2->y)));
1208 snapshot_insertion_cursor (snapshot,
1209 context,
1210 PANGO_PIXELS (cursor2->width),
1211 PANGO_PIXELS (cursor2->height),
1212 aspect_ratio,
1213 FALSE,
1214 direction: direction2,
1215 TRUE);
1216 gtk_snapshot_restore (snapshot);
1217 }
1218}
1219
1220/**
1221 * GtkStyleContextPrintFlags:
1222 * @GTK_STYLE_CONTEXT_PRINT_NONE: Default value.
1223 * @GTK_STYLE_CONTEXT_PRINT_RECURSE: Print the entire tree of
1224 * CSS nodes starting at the style context's node
1225 * @GTK_STYLE_CONTEXT_PRINT_SHOW_STYLE: Show the values of the
1226 * CSS properties for each node
1227 * @GTK_STYLE_CONTEXT_PRINT_SHOW_CHANGE: Show information about
1228 * what changes affect the styles
1229 *
1230 * Flags that modify the behavior of gtk_style_context_to_string().
1231 *
1232 * New values may be added to this enumeration.
1233 */
1234
1235/**
1236 * gtk_style_context_to_string:
1237 * @context: a `GtkStyleContext`
1238 * @flags: Flags that determine what to print
1239 *
1240 * Converts the style context into a string representation.
1241 *
1242 * The string representation always includes information about
1243 * the name, state, id, visibility and style classes of the CSS
1244 * node that is backing @context. Depending on the flags, more
1245 * information may be included.
1246 *
1247 * This function is intended for testing and debugging of the
1248 * CSS implementation in GTK. There are no guarantees about
1249 * the format of the returned string, it may change.
1250 *
1251 * Returns: a newly allocated string representing @context
1252 */
1253char *
1254gtk_style_context_to_string (GtkStyleContext *context,
1255 GtkStyleContextPrintFlags flags)
1256{
1257 GtkStyleContextPrivate *priv = gtk_style_context_get_instance_private (self: context);
1258 GString *string;
1259
1260 g_return_val_if_fail (GTK_IS_STYLE_CONTEXT (context), NULL);
1261
1262 string = g_string_new (init: "");
1263
1264 gtk_css_node_print (cssnode: priv->cssnode, flags, string, indent: 0);
1265
1266 return g_string_free (string, FALSE);
1267}
1268

source code of gtk/gtk/gtkstylecontext.c