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