1 /*
2 * Copyright © 1998, 2003 Jonathan Blandford <jrb@mit.edu>
3 * Copyright © 2007, 2008, 2009, 2010 Christian Persch
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <config.h>
20
21 #include "ar-style-gtk.h"
22
23 #include "ar-style-private.h"
24
25 #include "ar-debug.h"
26
27 /**
28 * SECTION: ar-style-gtk
29 * @short_description: common functions to set #ArStyle properties
30 *
31 * Tie #ArStyle properties to #GtkWidget style properties, and to
32 * #GtkSettings properties.
33 */
34
35 #define I_(string) (g_intern_static_string (string))
36
37 /* private functions */
38
39 /*
40 * ar_style_provider_new:
41 *
42 * Returns: (transfer full): a new #GtkStyleProvider
43 */
44 static GtkStyleProvider *
ar_style_provider_new(void)45 ar_style_provider_new (void)
46 {
47 static const char css[] =
48 " AisleriotBoard, aisleriot-board {\n"
49 "background-color: " DEFAULT_BACKGROUND_COLOR_STRING ";\n"
50 "-AisleriotBoard-selection-color: " DEFAULT_SELECTION_COLOR_STRING ";\n"
51 "background-image: url('resource:///org/gnome/aisleriot/art/baize.png');\n"
52 "background-repeat: repeat;\n"
53 "}\n";
54 #undef NAME
55
56 GtkCssProvider *provider;
57 GError *err = NULL;
58
59 provider = gtk_css_provider_new ();
60 gtk_css_provider_load_from_data (provider, css, -1, &err);
61 if (err)
62 g_error ("ERROR: %s\n", err->message);
63
64 return GTK_STYLE_PROVIDER (provider);
65 }
66
67 static void
sync_settings(GtkSettings * settings,GParamSpec * pspec,ArStyle * style)68 sync_settings (GtkSettings *settings,
69 GParamSpec *pspec,
70 ArStyle *style)
71 {
72 ArStylePrivate *style_priv = style->priv;
73 GObject *style_object = G_OBJECT (style);
74 const char *pspec_name;
75
76 if (pspec)
77 pspec_name = pspec->name;
78 else
79 pspec_name = NULL;
80
81 ar_debug_print (AR_DEBUG_GAME_STYLE,
82 "[ArStyle %p] Syncing GtkSettings:%s\n",
83 style, pspec_name ? pspec_name : "*");
84
85 g_object_freeze_notify (style_object);
86
87 if (pspec_name == NULL || pspec_name == I_("gtk-dnd-drag-threshold")) {
88 int threshold;
89
90 g_object_get (settings, "gtk-dnd-drag-threshold", &threshold, NULL);
91
92 if (threshold != style_priv->dnd_drag_threshold) {
93 style_priv->dnd_drag_threshold = threshold;
94
95 g_object_notify (style_object, AR_STYLE_PROP_DND_DRAG_THRESHOLD);
96 }
97 }
98
99 if (pspec_name == NULL || pspec_name == I_("gtk-double-click-time")) {
100 int double_click_time;
101
102 g_object_get (settings, "gtk-double-click-time", &double_click_time, NULL);
103
104 if (double_click_time != style_priv->double_click_time) {
105 style_priv->double_click_time = double_click_time;
106
107 g_object_notify (style_object, AR_STYLE_PROP_DOUBLE_CLICK_TIME);
108 }
109 }
110
111 if (pspec_name == NULL || pspec_name == I_("gtk-enable-animations")) {
112 gboolean enable;
113
114 g_object_get (settings, "gtk-enable-animations", &enable, NULL);
115
116 if (enable != style_priv->enable_animations_gtk) {
117 style_priv->enable_animations_gtk = enable;
118
119 /* FIXMEchpe: only notify if the effective setting changed */
120 g_object_notify (style_object, AR_STYLE_PROP_ENABLE_ANIMATIONS);
121 }
122 }
123
124 if (pspec_name == NULL || pspec_name == I_("gtk-enable-event-sounds")) {
125 gboolean enable;
126
127 g_object_get (settings, "gtk-enable-event-sounds", &enable, NULL);
128
129 if (enable != style_priv->enable_sound_gtk) {
130 style_priv->enable_sound_gtk = enable;
131
132 /* FIXMEchpe: only notify if the effective setting changed */
133 g_object_notify (style_object, AR_STYLE_PROP_ENABLE_SOUND);
134 }
135 }
136
137 if (pspec_name == NULL || pspec_name == I_("gtk-touchscreen-mode")) {
138 gboolean enable;
139
140 g_object_get (settings, "gtk-touchscreen-mode", &enable, NULL);
141
142 if (enable != style_priv->touchscreen_mode) {
143 style_priv->touchscreen_mode = enable;
144
145 /* FIXMEchpe: only notify if the effective setting changed */
146 g_object_notify (style_object, AR_STYLE_PROP_TOUCHSCREEN_MODE);
147 }
148 }
149
150 g_object_thaw_notify (style_object);
151 }
152
153 static void
direction_changed_cb(GtkWidget * widget,GtkTextDirection previous_direction,ArStyle * style)154 direction_changed_cb (GtkWidget *widget,
155 GtkTextDirection previous_direction,
156 ArStyle *style)
157 {
158 ArStylePrivate *style_priv = style->priv;
159 GObject *style_object = G_OBJECT (style);
160 GtkTextDirection direction;
161 gboolean rtl;
162
163 direction = gtk_widget_get_direction (widget);
164
165 ar_debug_print (AR_DEBUG_GAME_STYLE,
166 "[ArStyle %p] Widget direction-changed direction %d previous-direction %d\n",
167 style, direction, previous_direction);
168
169 if (direction == previous_direction)
170 return;
171
172 g_object_freeze_notify (style_object);
173
174 rtl = (direction == GTK_TEXT_DIR_RTL);
175
176 if (style_priv->rtl != rtl) {
177 style_priv->rtl = rtl;
178
179 g_object_notify (style_object, AR_STYLE_PROP_RTL);
180 }
181
182 g_object_thaw_notify (style_object);
183 }
184
185 static void
screen_changed_cb(GtkWidget * widget,GdkScreen * previous_screen,ArStyle * style)186 screen_changed_cb (GtkWidget *widget,
187 GdkScreen *previous_screen,
188 ArStyle *style)
189 {
190 GdkScreen *screen;
191 GtkSettings *settings;
192
193 g_assert (style != NULL);
194
195 screen = gtk_widget_get_screen (widget);
196
197 ar_debug_print (AR_DEBUG_GAME_STYLE,
198 "[ArStyle %p] Widget screen-changed screen %p previous-screen %p\n",
199 style, screen, previous_screen);
200
201 if (screen == previous_screen)
202 return;
203
204 if (previous_screen != NULL) {
205 g_signal_handlers_disconnect_by_func (gtk_settings_get_for_screen (previous_screen),
206 G_CALLBACK (sync_settings),
207 style);
208 }
209
210 if (screen == NULL)
211 return;
212
213 settings = gtk_settings_get_for_screen (screen);
214
215 sync_settings (settings, NULL, style);
216
217 g_signal_connect (settings, "notify::gtk-double-click-time",
218 G_CALLBACK (sync_settings), style);
219 g_signal_connect (settings, "notify::gtk-enable-animations",
220 G_CALLBACK (sync_settings), style);
221 g_signal_connect (settings, "notify::gtk-enable-event-sounds",
222 G_CALLBACK (sync_settings), style);
223 g_signal_connect (settings, "notify::gtk-touchscreen-mode",
224 G_CALLBACK (sync_settings), style);
225 }
226
227 static void
style_updated_cb(GtkWidget * widget,ArStyle * style)228 style_updated_cb (GtkWidget *widget,
229 ArStyle *style)
230 {
231 ArStylePrivate *style_priv = style->priv;
232 GObject *style_object = G_OBJECT (style);
233 GdkRGBA *selection_color;
234 int focus_line_width, focus_padding;
235 gboolean interior_focus;
236 double card_slot_ratio, card_overhang, card_step;
237
238 ar_debug_print (AR_DEBUG_GAME_STYLE,
239 "[ArStyle %p] Syncing widget style properties\n",
240 style);
241
242 g_object_freeze_notify (style_object);
243
244 gtk_widget_style_get (widget,
245 "interior-focus", &interior_focus,
246 "focus-line-width", &focus_line_width,
247 "focus-padding", &focus_padding,
248 "card-slot-ratio", &card_slot_ratio,
249 "card-overhang", &card_overhang,
250 "card-step", &card_step,
251 "selection-color", &selection_color,
252 NULL);
253
254 if (style_priv->interior_focus != interior_focus) {
255 style_priv->interior_focus = interior_focus;
256
257 g_object_notify (style_object, AR_STYLE_PROP_INTERIOR_FOCUS);
258 }
259
260 if (style_priv->focus_line_width != focus_line_width) {
261 style_priv->focus_line_width = focus_line_width;
262
263 g_object_notify (style_object, AR_STYLE_PROP_FOCUS_LINE_WIDTH);
264 }
265
266 if (style_priv->focus_padding != focus_padding) {
267 style_priv->focus_padding = focus_padding;
268
269 g_object_notify (style_object, AR_STYLE_PROP_FOCUS_PADDING);
270 }
271
272 if (style_priv->card_slot_ratio != card_slot_ratio) {
273 style_priv->card_slot_ratio = card_slot_ratio;
274
275 g_object_notify (style_object, AR_STYLE_PROP_CARD_SLOT_RATIO);
276 }
277
278 if (style_priv->card_overhang != card_overhang) {
279 style_priv->card_overhang = card_overhang;
280
281 g_object_notify (style_object, AR_STYLE_PROP_CARD_OVERHANG);
282 }
283
284 if (style_priv->card_step != card_step) {
285 style_priv->card_step = card_step;
286
287 g_object_notify (style_object, AR_STYLE_PROP_CARD_STEP);
288 }
289
290 if (!gdk_rgba_equal (&style_priv->selection_color, selection_color)) {
291 style_priv->selection_color = *selection_color;
292
293 g_object_notify (style_object, AR_STYLE_PROP_SELECTION_COLOR);
294 }
295 gdk_rgba_free (selection_color);
296
297 g_object_thaw_notify (style_object);
298 }
299
300 /* private API */
301
302 /**
303 * _ar_style_gtk_class_install_style_properties:
304 * @widget_class: a #GtkWidgetClass
305 *
306 * Installs style properties in @widget_class.
307 */
308 void
_ar_style_gtk_class_install_style_properties(GtkWidgetClass * widget_class)309 _ar_style_gtk_class_install_style_properties (GtkWidgetClass *widget_class)
310 {
311 /**
312 * ArClutterEmbed:selection-color:
313 *
314 * The card selection colour.
315 */
316 gtk_widget_class_install_style_property
317 (widget_class,
318 g_param_spec_boxed ("selection-color", NULL, NULL,
319 GDK_TYPE_RGBA,
320 G_PARAM_READWRITE |
321 G_PARAM_STATIC_STRINGS));
322
323 /**
324 * ArClutterEmbed:card-slot-ratio:
325 *
326 * The ratio of card to slot size. Note that this is the ratio of
327 * card width/slot width and card height/slot height, not of
328 * card area/slot area.
329 */
330 gtk_widget_class_install_style_property
331 (widget_class,
332 g_param_spec_double ("card-slot-ratio", NULL, NULL,
333 0.1, 1.0, DEFAULT_CARD_SLOT_RATIO,
334 G_PARAM_READWRITE |
335 G_PARAM_STATIC_STRINGS));
336
337 /**
338 * ArClutterEmbed:card-overhang:
339 *
340 * This controls how much of a card is allowed to hang off of the bottom
341 * of the screen. If set to %0.0, the last card is always fully visible.
342 */
343 gtk_widget_class_install_style_property
344 (widget_class,
345 g_param_spec_double ("card-overhang", NULL, NULL,
346 0.0, 1.0, DEFAULT_CARD_OVERHANG,
347 G_PARAM_READWRITE |
348 G_PARAM_STATIC_STRINGS));
349
350 /**
351 * ArClutterEmbed:card-step:
352 *
353 * This controls how much one card is offset the previous one in card stacks.
354 * A game-specified a value for the card step takes precedence over this.
355 */
356 gtk_widget_class_install_style_property
357 (widget_class,
358 g_param_spec_double ("card-step", NULL, NULL,
359 MIN_CARD_STEP, MAX_CARD_STEP, DEFAULT_CARD_STEP,
360 G_PARAM_READWRITE |
361 G_PARAM_STATIC_STRINGS));
362 }
363
364 /**
365 * _ar_style_gtk_attach:
366 * @style: a #ArStyle
367 * @widget: a #GtkWidget
368 *
369 * Attaches @style to @widget's style properties and the properties
370 * of its #GdkScreen's #GtkSettings object.
371 */
372 void
_ar_style_gtk_attach(ArStyle * style,GtkWidget * widget)373 _ar_style_gtk_attach (ArStyle *style,
374 GtkWidget *widget)
375 {
376 GtkStyleContext *context;
377 GtkStyleProvider *provider;
378
379 g_return_if_fail (AR_IS_STYLE (style));
380 g_return_if_fail (GTK_IS_WIDGET (widget));
381
382 ar_debug_print (AR_DEBUG_GAME_STYLE,
383 "[ArStyle %p] Attaching to widget %p\n", style, widget);
384
385 context = gtk_widget_get_style_context (widget);
386 provider = ar_style_provider_new ();
387 gtk_style_context_add_provider (context, provider,
388 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
389 g_object_unref (provider);
390
391 g_assert (g_object_get_data (G_OBJECT (widget), "Ar::Style") == NULL);
392 g_object_set_data (G_OBJECT (widget), "Ar::Style", style);
393
394 /* This is necessary since we don't get an initial change notification! */
395 direction_changed_cb (widget, GTK_TEXT_DIR_LTR, style);
396 style_updated_cb (widget, style);
397
398 g_signal_connect (widget, "style-updated",
399 G_CALLBACK (style_updated_cb), style);
400 g_signal_connect (widget, "screen-changed",
401 G_CALLBACK (screen_changed_cb), style);
402 g_signal_connect (widget, "direction-changed",
403 G_CALLBACK (direction_changed_cb), style);
404 }
405
406 /**
407 * _ar_style_gtk_detach:
408 * @style: a #ArStyle
409 * @widget: a #GtkWidget
410 *
411 * Detaches @style from @widget.
412 */
413 void
_ar_style_gtk_detach(ArStyle * style,GtkWidget * widget)414 _ar_style_gtk_detach (ArStyle *style,
415 GtkWidget *widget)
416 {
417 g_return_if_fail (AR_IS_STYLE (style));
418 g_return_if_fail (GTK_IS_WIDGET (widget));
419
420 ar_debug_print (AR_DEBUG_GAME_STYLE,
421 "[ArStyle %p] Detaching from widget %p\n", style, widget);
422
423 g_assert (g_object_get_data (G_OBJECT (widget), "Ar::Style") == style);
424 g_object_set_data (G_OBJECT (widget), "Ar::Style", NULL);
425
426 g_signal_handlers_disconnect_by_func (widget, G_CALLBACK (style_updated_cb), style);
427 g_signal_handlers_disconnect_by_func (widget, G_CALLBACK (screen_changed_cb), style);
428 g_signal_handlers_disconnect_by_func (widget, G_CALLBACK (direction_changed_cb), style);
429
430 g_signal_handlers_disconnect_by_func (gtk_widget_get_settings (widget),
431 G_CALLBACK (sync_settings),
432 style);
433 }
434