]> Pileus Git - ~andy/gtk/blob - gtk/gtkcheckbutton.c
GtkCheckButton: Look active when the pointer button is pressed and hovering
[~andy/gtk] / gtk / gtkcheckbutton.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
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, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /*
21  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
22  * file for a list of people on the GTK+ Team.  See the ChangeLog
23  * files for a list of changes.  These files are distributed with
24  * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
25  */
26
27 #include "config.h"
28
29 #include "gtkcheckbutton.h"
30
31 #include "gtkbuttonprivate.h"
32 #include "gtklabel.h"
33
34 #include "gtkprivate.h"
35 #include "gtkintl.h"
36
37
38 #define INDICATOR_SIZE     13
39 #define INDICATOR_SPACING  2
40
41
42 static void gtk_check_button_get_preferred_width  (GtkWidget          *widget,
43                                                    gint               *minimum,
44                                                    gint               *natural);
45 static void gtk_check_button_get_preferred_height (GtkWidget          *widget,
46                                                    gint               *minimum,
47                                                    gint               *natural);
48 static void gtk_check_button_size_allocate       (GtkWidget           *widget,
49                                                   GtkAllocation       *allocation);
50 static gboolean gtk_check_button_draw            (GtkWidget           *widget,
51                                                   cairo_t             *cr);
52 static void gtk_check_button_paint               (GtkWidget           *widget,
53                                                   cairo_t             *cr);
54 static void gtk_check_button_draw_indicator      (GtkCheckButton      *check_button,
55                                                   cairo_t             *cr);
56 static void gtk_real_check_button_draw_indicator (GtkCheckButton      *check_button,
57                                                   cairo_t             *cr);
58
59 G_DEFINE_TYPE (GtkCheckButton, gtk_check_button, GTK_TYPE_TOGGLE_BUTTON)
60
61 static void
62 gtk_check_button_class_init (GtkCheckButtonClass *class)
63 {
64   GtkWidgetClass *widget_class;
65
66   widget_class = (GtkWidgetClass*) class;
67
68   widget_class->get_preferred_width = gtk_check_button_get_preferred_width;
69   widget_class->get_preferred_height = gtk_check_button_get_preferred_height;
70   widget_class->size_allocate = gtk_check_button_size_allocate;
71   widget_class->draw = gtk_check_button_draw;
72
73   class->draw_indicator = gtk_real_check_button_draw_indicator;
74
75   gtk_widget_class_install_style_property (widget_class,
76                                            g_param_spec_int ("indicator-size",
77                                                              P_("Indicator Size"),
78                                                              P_("Size of check or radio indicator"),
79                                                              0,
80                                                              G_MAXINT,
81                                                              INDICATOR_SIZE,
82                                                              GTK_PARAM_READABLE));
83   gtk_widget_class_install_style_property (widget_class,
84                                            g_param_spec_int ("indicator-spacing",
85                                                              P_("Indicator Spacing"),
86                                                              P_("Spacing around check or radio indicator"),
87                                                              0,
88                                                              G_MAXINT,
89                                                              INDICATOR_SPACING,
90                                                              GTK_PARAM_READABLE));
91 }
92
93 static void
94 gtk_check_button_init (GtkCheckButton *check_button)
95 {
96   gtk_widget_set_has_window (GTK_WIDGET (check_button), FALSE);
97   gtk_widget_set_receives_default (GTK_WIDGET (check_button), FALSE);
98   gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (check_button), TRUE);
99 }
100
101 GtkWidget*
102 gtk_check_button_new (void)
103 {
104   return g_object_new (GTK_TYPE_CHECK_BUTTON, NULL);
105 }
106
107
108 GtkWidget*
109 gtk_check_button_new_with_label (const gchar *label)
110 {
111   return g_object_new (GTK_TYPE_CHECK_BUTTON, "label", label, NULL);
112 }
113
114 /**
115  * gtk_check_button_new_with_mnemonic:
116  * @label: The text of the button, with an underscore in front of the
117  *         mnemonic character
118  * @returns: a new #GtkCheckButton
119  *
120  * Creates a new #GtkCheckButton containing a label. The label
121  * will be created using gtk_label_new_with_mnemonic(), so underscores
122  * in @label indicate the mnemonic for the check button.
123  **/
124 GtkWidget*
125 gtk_check_button_new_with_mnemonic (const gchar *label)
126 {
127   return g_object_new (GTK_TYPE_CHECK_BUTTON, 
128                        "label", label, 
129                        "use-underline", TRUE, 
130                        NULL);
131 }
132
133
134 /* This should only be called when toggle_button->draw_indicator
135  * is true.
136  */
137 static void
138 gtk_check_button_paint (GtkWidget    *widget,
139                         cairo_t      *cr)
140 {
141   GtkCheckButton *check_button = GTK_CHECK_BUTTON (widget);
142   gint border_width;
143   gint interior_focus;
144   gint focus_width;
145   gint focus_pad;
146       
147   gtk_widget_style_get (widget,
148                         "interior-focus", &interior_focus,
149                         "focus-line-width", &focus_width,
150                         "focus-padding", &focus_pad,
151                         NULL);
152
153   gtk_check_button_draw_indicator (check_button, cr);
154
155   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
156   if (gtk_widget_has_focus (widget))
157     {
158       GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget));
159       GtkStyleContext *context;
160       GtkStateFlags state;
161       GtkAllocation allocation;
162
163       gtk_widget_get_allocation (widget, &allocation);
164       context = gtk_widget_get_style_context (widget);
165       state = gtk_widget_get_state_flags (widget);
166
167       gtk_style_context_set_state (context, state);
168
169       if (interior_focus && child && gtk_widget_get_visible (child))
170         {
171           GtkAllocation child_allocation;
172
173           gtk_widget_get_allocation (child, &child_allocation);
174           gtk_render_focus (context, cr,
175                             child_allocation.x - allocation.x - focus_width - focus_pad,
176                             child_allocation.y - allocation.y - focus_width - focus_pad,
177                             child_allocation.width + 2 * (focus_width + focus_pad),
178                             child_allocation.height + 2 * (focus_width + focus_pad));
179         }
180       else
181         gtk_render_focus (context, cr,
182                           border_width, border_width,
183                           allocation.width - 2 * border_width,
184                           allocation.height - 2 * border_width);
185     }
186 }
187
188 void
189 _gtk_check_button_get_props (GtkCheckButton *check_button,
190                              gint           *indicator_size,
191                              gint           *indicator_spacing)
192 {
193   GtkWidget *widget =  GTK_WIDGET (check_button);
194
195   if (indicator_size)
196     gtk_widget_style_get (widget, "indicator-size", indicator_size, NULL);
197
198   if (indicator_spacing)
199     gtk_widget_style_get (widget, "indicator-spacing", indicator_spacing, NULL);
200 }
201
202 static void
203 gtk_check_button_get_preferred_width (GtkWidget *widget,
204                                       gint      *minimum,
205                                       gint      *natural)
206 {
207   GtkToggleButton *toggle_button = GTK_TOGGLE_BUTTON (widget);
208   
209   if (gtk_toggle_button_get_mode (toggle_button))
210     {
211       GtkWidget *child;
212       gint indicator_size;
213       gint indicator_spacing;
214       gint focus_width;
215       gint focus_pad;
216       guint border_width;
217
218       border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
219
220       gtk_widget_style_get (GTK_WIDGET (widget),
221                             "focus-line-width", &focus_width,
222                             "focus-padding", &focus_pad,
223                             NULL);
224       *minimum = 2 * border_width;
225       *natural = 2 * border_width;
226
227       _gtk_check_button_get_props (GTK_CHECK_BUTTON (widget),
228                                    &indicator_size, &indicator_spacing);
229
230       child = gtk_bin_get_child (GTK_BIN (widget));
231       if (child && gtk_widget_get_visible (child))
232         {
233           gint child_min, child_nat;
234
235           gtk_widget_get_preferred_width (child, &child_min, &child_nat);
236
237           *minimum += child_min + indicator_spacing;
238           *natural += child_nat + indicator_spacing;
239         }
240
241       *minimum += (indicator_size + indicator_spacing * 2 + 2 * (focus_width + focus_pad));
242       *natural += (indicator_size + indicator_spacing * 2 + 2 * (focus_width + focus_pad));
243     }
244   else
245     GTK_WIDGET_CLASS (gtk_check_button_parent_class)->get_preferred_width (widget, minimum, natural);
246 }
247
248 static void
249 gtk_check_button_get_preferred_height (GtkWidget *widget,
250                                        gint      *minimum,
251                                        gint      *natural)
252 {
253   GtkToggleButton *toggle_button = GTK_TOGGLE_BUTTON (widget);
254
255   if (gtk_toggle_button_get_mode (toggle_button))
256     {
257       GtkWidget *child;
258       gint temp;
259       gint indicator_size;
260       gint indicator_spacing;
261       gint focus_width;
262       gint focus_pad;
263       guint border_width;
264
265       border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
266
267       gtk_widget_style_get (GTK_WIDGET (widget),
268                             "focus-line-width", &focus_width,
269                             "focus-padding", &focus_pad,
270                             NULL);
271
272       *minimum = border_width * 2;
273       *natural = border_width * 2;
274
275       _gtk_check_button_get_props (GTK_CHECK_BUTTON (widget),
276                                    &indicator_size, &indicator_spacing);
277
278       child = gtk_bin_get_child (GTK_BIN (widget));
279       if (child && gtk_widget_get_visible (child))
280         {
281           gint child_min, child_nat;
282
283           gtk_widget_get_preferred_height (child, &child_min, &child_nat);
284
285           *minimum += child_min;
286           *natural += child_nat;
287         }
288
289       temp = indicator_size + indicator_spacing * 2;
290       *minimum = MAX (*minimum, temp) + 2 * (focus_width + focus_pad);
291       *natural = MAX (*natural, temp) + 2 * (focus_width + focus_pad);
292     }
293   else
294     GTK_WIDGET_CLASS (gtk_check_button_parent_class)->get_preferred_height (widget, minimum, natural);
295 }
296
297 static void
298 gtk_check_button_size_allocate (GtkWidget     *widget,
299                                 GtkAllocation *allocation)
300 {
301   GtkCheckButton *check_button;
302   GtkToggleButton *toggle_button;
303   GtkButton *button;
304   GtkAllocation child_allocation;
305
306   button = GTK_BUTTON (widget);
307   check_button = GTK_CHECK_BUTTON (widget);
308   toggle_button = GTK_TOGGLE_BUTTON (widget);
309
310   if (gtk_toggle_button_get_mode (toggle_button))
311     {
312       GtkWidget *child;
313       gint indicator_size;
314       gint indicator_spacing;
315       gint focus_width;
316       gint focus_pad;
317       
318       _gtk_check_button_get_props (check_button, &indicator_size, &indicator_spacing);
319       gtk_widget_style_get (widget,
320                             "focus-line-width", &focus_width,
321                             "focus-padding", &focus_pad,
322                             NULL);
323
324       gtk_widget_set_allocation (widget, allocation);
325
326       if (gtk_widget_get_realized (widget))
327         gdk_window_move_resize (gtk_button_get_event_window (button),
328                                 allocation->x, allocation->y,
329                                 allocation->width, allocation->height);
330
331       child = gtk_bin_get_child (GTK_BIN (button));
332       if (child && gtk_widget_get_visible (child))
333         {
334           GtkRequisition child_requisition;
335           guint border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
336
337           gtk_widget_get_preferred_size (child, &child_requisition, NULL);
338
339           child_allocation.width = MIN (child_requisition.width,
340                                         allocation->width -
341                                         ((border_width + focus_width + focus_pad) * 2
342                                          + indicator_size + indicator_spacing * 3));
343           child_allocation.width = MAX (child_allocation.width, 1);
344
345           child_allocation.height = MIN (child_requisition.height,
346                                          allocation->height - (border_width + focus_width + focus_pad) * 2);
347           child_allocation.height = MAX (child_allocation.height, 1);
348           
349           child_allocation.x = (border_width + indicator_size + indicator_spacing * 3 +
350                                 allocation->x + focus_width + focus_pad);
351           child_allocation.y = allocation->y + (allocation->height - child_allocation.height) / 2;
352
353           if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
354             child_allocation.x = allocation->x + allocation->width
355               - (child_allocation.x - allocation->x + child_allocation.width);
356           
357           gtk_widget_size_allocate (child, &child_allocation);
358         }
359     }
360   else
361     GTK_WIDGET_CLASS (gtk_check_button_parent_class)->size_allocate (widget, allocation);
362 }
363
364 static gint
365 gtk_check_button_draw (GtkWidget *widget,
366                        cairo_t   *cr)
367 {
368   GtkToggleButton *toggle_button;
369   GtkBin *bin;
370   GtkWidget *child;
371   
372   toggle_button = GTK_TOGGLE_BUTTON (widget);
373   bin = GTK_BIN (widget);
374
375   if (gtk_toggle_button_get_mode (toggle_button))
376     {
377       gtk_check_button_paint (widget, cr);
378
379       child = gtk_bin_get_child (bin);
380       if (child)
381         gtk_container_propagate_draw (GTK_CONTAINER (widget),
382                                       child,
383                                       cr);
384     }
385   else if (GTK_WIDGET_CLASS (gtk_check_button_parent_class)->draw)
386     GTK_WIDGET_CLASS (gtk_check_button_parent_class)->draw (widget, cr);
387
388   return FALSE;
389 }
390
391
392 static void
393 gtk_check_button_draw_indicator (GtkCheckButton *check_button,
394                                  cairo_t        *cr)
395 {
396   GtkCheckButtonClass *class = GTK_CHECK_BUTTON_GET_CLASS (check_button);
397
398   if (class->draw_indicator)
399     class->draw_indicator (check_button, cr);
400 }
401
402 static void
403 gtk_real_check_button_draw_indicator (GtkCheckButton *check_button,
404                                       cairo_t        *cr)
405 {
406   GtkWidget *widget;
407   GtkWidget *child;
408   GtkButton *button;
409   GtkToggleButton *toggle_button;
410   GtkStateFlags state = 0;
411   gint x, y;
412   gint indicator_size;
413   gint indicator_spacing;
414   gint focus_width;
415   gint focus_pad;
416   guint border_width;
417   gboolean interior_focus;
418   GtkAllocation allocation;
419   GtkStyleContext *context;
420
421   widget = GTK_WIDGET (check_button);
422   button = GTK_BUTTON (check_button);
423   toggle_button = GTK_TOGGLE_BUTTON (check_button);
424
425   gtk_widget_get_allocation (widget, &allocation);
426   context = gtk_widget_get_style_context (widget);
427
428   gtk_widget_style_get (widget, 
429                         "interior-focus", &interior_focus,
430                         "focus-line-width", &focus_width, 
431                         "focus-padding", &focus_pad, 
432                         NULL);
433
434   _gtk_check_button_get_props (check_button, &indicator_size, &indicator_spacing);
435
436   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
437
438   x = indicator_spacing + border_width;
439   y = (allocation.height - indicator_size) / 2;
440
441   child = gtk_bin_get_child (GTK_BIN (check_button));
442   if (!interior_focus || !(child && gtk_widget_get_visible (child)))
443     x += focus_width + focus_pad;      
444
445   if (gtk_toggle_button_get_inconsistent (toggle_button))
446     state |= GTK_STATE_FLAG_INCONSISTENT;
447   else if (gtk_toggle_button_get_active (toggle_button) ||
448            (button->priv->button_down && button->priv->in_button))
449     state |= GTK_STATE_FLAG_ACTIVE;
450
451   if (button->priv->activate_timeout || (button->priv->button_down && button->priv->in_button))
452     state |= GTK_STATE_FLAG_SELECTED;
453
454   if (button->priv->in_button)
455     state |= GTK_STATE_FLAG_PRELIGHT;
456   else if (!gtk_widget_is_sensitive (widget))
457     state |= GTK_STATE_FLAG_INSENSITIVE;
458
459   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
460     x = allocation.width - (indicator_size + x);
461
462   gtk_style_context_set_state (context, state);
463
464   if (state & GTK_STATE_FLAG_PRELIGHT)
465     gtk_render_background (context, cr,
466                            border_width, border_width,
467                            allocation.width - (2 * border_width),
468                            allocation.height - (2 * border_width));
469
470   gtk_style_context_save (context);
471   gtk_style_context_add_class (context, GTK_STYLE_CLASS_CHECK);
472
473   gtk_render_check (context, cr,
474                     x, y, indicator_size, indicator_size);
475
476   gtk_style_context_restore (context);
477 }