]> Pileus Git - ~andy/gtk/blob - gtk/gtkradiobutton.c
Don't do special focus handling if draw_indicator is not set. (#64723,
[~andy/gtk] / gtk / gtkradiobutton.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 "gtklabel.h"
28 #include "gtkradiobutton.h"
29 #include "gtksignal.h"
30
31
32 enum {
33   ARG_0,
34   ARG_GROUP
35 };
36
37
38 static void     gtk_radio_button_class_init     (GtkRadioButtonClass *klass);
39 static void     gtk_radio_button_init           (GtkRadioButton      *radio_button);
40 static void     gtk_radio_button_destroy        (GtkObject           *object);
41 static gboolean gtk_radio_button_focus          (GtkWidget           *widget,
42                                                  GtkDirectionType     direction);
43 static void     gtk_radio_button_clicked        (GtkButton           *button);
44 static void     gtk_radio_button_draw_indicator (GtkCheckButton      *check_button,
45                                                  GdkRectangle        *area);
46 static void     gtk_radio_button_set_arg        (GtkObject           *object,
47                                                  GtkArg              *arg,
48                                                  guint                arg_id);
49 static void     gtk_radio_button_get_arg        (GtkObject           *object,
50                                                  GtkArg              *arg,
51                                                  guint                arg_id);
52
53 static GtkCheckButtonClass *parent_class = NULL;
54
55
56 GtkType
57 gtk_radio_button_get_type (void)
58 {
59   static GtkType radio_button_type = 0;
60
61   if (!radio_button_type)
62     {
63       static const GtkTypeInfo radio_button_info =
64       {
65         "GtkRadioButton",
66         sizeof (GtkRadioButton),
67         sizeof (GtkRadioButtonClass),
68         (GtkClassInitFunc) gtk_radio_button_class_init,
69         (GtkObjectInitFunc) gtk_radio_button_init,
70         /* reserved_1 */ NULL,
71         /* reserved_2 */ NULL,
72         (GtkClassInitFunc) NULL,
73       };
74
75       radio_button_type = gtk_type_unique (gtk_check_button_get_type (), &radio_button_info);
76     }
77
78   return radio_button_type;
79 }
80
81 static void
82 gtk_radio_button_class_init (GtkRadioButtonClass *class)
83 {
84   GtkObjectClass *object_class;
85   GtkButtonClass *button_class;
86   GtkCheckButtonClass *check_button_class;
87   GtkWidgetClass *widget_class;
88
89   object_class = (GtkObjectClass*) class;
90   widget_class = (GtkWidgetClass*) class;
91   button_class = (GtkButtonClass*) class;
92   check_button_class = (GtkCheckButtonClass*) class;
93
94   parent_class = gtk_type_class (gtk_check_button_get_type ());
95
96   gtk_object_add_arg_type ("GtkRadioButton::group", GTK_TYPE_RADIO_BUTTON, GTK_ARG_WRITABLE, ARG_GROUP);
97
98   object_class->set_arg = gtk_radio_button_set_arg;
99   object_class->get_arg = gtk_radio_button_get_arg;
100   object_class->destroy = gtk_radio_button_destroy;
101
102   widget_class->focus = gtk_radio_button_focus;
103
104   button_class->clicked = gtk_radio_button_clicked;
105
106   check_button_class->draw_indicator = gtk_radio_button_draw_indicator;
107 }
108
109 static void
110 gtk_radio_button_init (GtkRadioButton *radio_button)
111 {
112   GTK_WIDGET_SET_FLAGS (radio_button, GTK_NO_WINDOW);
113   GTK_WIDGET_UNSET_FLAGS (radio_button, GTK_RECEIVES_DEFAULT);
114
115   GTK_TOGGLE_BUTTON (radio_button)->active = TRUE;
116
117   GTK_BUTTON (radio_button)->depress_on_activate = FALSE;
118
119   radio_button->group = g_slist_prepend (NULL, radio_button);
120
121   _gtk_button_set_depressed (GTK_BUTTON (radio_button), TRUE);
122   gtk_widget_set_state (GTK_WIDGET (radio_button), GTK_STATE_ACTIVE);
123 }
124
125 static void
126 gtk_radio_button_set_arg (GtkObject      *object,
127                           GtkArg         *arg,
128                           guint           arg_id)
129 {
130   GtkRadioButton *radio_button;
131
132   radio_button = GTK_RADIO_BUTTON (object);
133
134   switch (arg_id)
135     {
136       GSList *slist;
137
138     case ARG_GROUP:
139       if (GTK_VALUE_OBJECT (*arg))
140         slist = gtk_radio_button_get_group ((GtkRadioButton*) GTK_VALUE_OBJECT (*arg));
141       else
142         slist = NULL;
143       gtk_radio_button_set_group (radio_button, slist);
144       break;
145     default:
146       break;
147     }
148 }
149
150 static void
151 gtk_radio_button_get_arg (GtkObject      *object,
152                           GtkArg         *arg,
153                           guint           arg_id)
154 {
155   GtkRadioButton *radio_button;
156
157   radio_button = GTK_RADIO_BUTTON (object);
158
159   switch (arg_id)
160     {
161     default:
162       arg->type = GTK_TYPE_INVALID;
163       break;
164     }
165 }
166
167 void
168 gtk_radio_button_set_group (GtkRadioButton *radio_button,
169                             GSList         *group)
170 {
171   g_return_if_fail (GTK_IS_RADIO_BUTTON (radio_button));
172   g_return_if_fail (!g_slist_find (group, radio_button));
173
174   if (radio_button->group)
175     {
176       GSList *slist;
177       
178       radio_button->group = g_slist_remove (radio_button->group, radio_button);
179       
180       for (slist = radio_button->group; slist; slist = slist->next)
181         {
182           GtkRadioButton *tmp_button;
183           
184           tmp_button = slist->data;
185           
186           tmp_button->group = radio_button->group;
187         }
188     }
189   
190   radio_button->group = g_slist_prepend (group, radio_button);
191   
192   if (group)
193     {
194       GSList *slist;
195       
196       for (slist = group; slist; slist = slist->next)
197         {
198           GtkRadioButton *tmp_button;
199           
200           tmp_button = slist->data;
201           
202           tmp_button->group = radio_button->group;
203         }
204     }
205
206   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio_button), group == NULL);
207 }
208
209 GtkWidget*
210 gtk_radio_button_new (GSList *group)
211 {
212   GtkRadioButton *radio_button;
213
214   radio_button = gtk_type_new (gtk_radio_button_get_type ());
215
216   if (group)
217     gtk_radio_button_set_group (radio_button, group);
218
219   return GTK_WIDGET (radio_button);
220 }
221
222 GtkWidget*
223 gtk_radio_button_new_with_label (GSList      *group,
224                                  const gchar *label)
225 {
226   GtkWidget *radio_button;
227
228   radio_button = g_object_new (GTK_TYPE_RADIO_BUTTON, "label", label, NULL) ;
229
230   if (group)
231     gtk_radio_button_set_group (GTK_RADIO_BUTTON (radio_button), group);
232
233   return radio_button;
234 }
235
236
237 /**
238  * gtk_radio_button_new_with_mnemonic:
239  * @group: the radio button group
240  * @label: the text of the button, with an underscore in front of the
241  *         mnemonic character
242  * @returns: a new #GtkRadioButton
243  *
244  * Creates a new #GtkRadioButton containing a label, adding it to the same 
245  * group as @group. The label will be created using 
246  * gtk_label_new_with_mnemonic(), so underscores in @label indicate the 
247  * mnemonic for the button.
248  **/
249 GtkWidget*
250 gtk_radio_button_new_with_mnemonic (GSList      *group,
251                                     const gchar *label)
252 {
253   GtkWidget *radio_button;
254
255   radio_button = g_object_new (GTK_TYPE_RADIO_BUTTON, "label", label, "use_underline", TRUE, NULL);
256
257   if (group)
258     gtk_radio_button_set_group (GTK_RADIO_BUTTON (radio_button), group);
259
260   return radio_button;
261 }
262
263 GtkWidget*
264 gtk_radio_button_new_from_widget (GtkRadioButton *group)
265 {
266   GSList *l = NULL;
267   if (group)
268     l = gtk_radio_button_get_group (group);
269   return gtk_radio_button_new (l);
270 }
271
272
273 GtkWidget*
274 gtk_radio_button_new_with_label_from_widget (GtkRadioButton *group,
275                                              const gchar    *label)
276 {
277   GSList *l = NULL;
278   if (group)
279     l = gtk_radio_button_get_group (group);
280   return gtk_radio_button_new_with_label (l, label);
281 }
282
283 /**
284  * gtk_radio_button_new_with_mnemonic_from_widget:
285  * @group: widget to get radio group from
286  * @label: the text of the button, with an underscore in front of the
287  *         mnemonic character
288  * @returns: a new #GtkRadioButton
289  *
290  * Creates a new #GtkRadioButton containing a label. The label
291  * will be created using gtk_label_new_with_mnemonic(), so underscores
292  * in @label indicate the mnemonic for the button.
293  **/
294 GtkWidget*
295 gtk_radio_button_new_with_mnemonic_from_widget (GtkRadioButton *group,
296                                                 const gchar    *label)
297 {
298   GSList *l = NULL;
299   if (group)
300     l = gtk_radio_button_get_group (group);
301   return gtk_radio_button_new_with_mnemonic (l, label);
302 }
303
304 GSList*
305 gtk_radio_button_get_group (GtkRadioButton *radio_button)
306 {
307   g_return_val_if_fail (GTK_IS_RADIO_BUTTON (radio_button), NULL);
308
309   return radio_button->group;
310 }
311
312
313 static void
314 gtk_radio_button_destroy (GtkObject *object)
315 {
316   GtkRadioButton *radio_button;
317   GtkRadioButton *tmp_button;
318   GSList *tmp_list;
319
320   radio_button = GTK_RADIO_BUTTON (object);
321
322   radio_button->group = g_slist_remove (radio_button->group, radio_button);
323   tmp_list = radio_button->group;
324
325   while (tmp_list)
326     {
327       tmp_button = tmp_list->data;
328       tmp_list = tmp_list->next;
329
330       tmp_button->group = radio_button->group;
331     }
332
333   /* this button is no longer in the group */
334   radio_button->group = NULL;
335   
336   if (GTK_OBJECT_CLASS (parent_class)->destroy)
337     (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
338 }
339
340 static void
341 get_coordinates (GtkWidget    *widget,
342                  GtkWidget    *reference,
343                  gint         *x,
344                  gint         *y)
345 {
346   *x = widget->allocation.x + widget->allocation.width / 2;
347   *y = widget->allocation.y + widget->allocation.height / 2;
348   
349   gtk_widget_translate_coordinates (widget, reference, *x, *y, x, y);
350 }
351
352 static gint
353 left_right_compare (gconstpointer a,
354                     gconstpointer b,
355                     gpointer      data)
356 {
357   gint x1, y1, x2, y2;
358
359   get_coordinates ((GtkWidget *)a, data, &x1, &y1);
360   get_coordinates ((GtkWidget *)b, data, &x2, &y2);
361
362   if (y1 == y2)
363     return (x1 < x2) ? -1 : ((x1 == x2) ? 0 : 1);
364   else
365     return (y1 < y2) ? -1 : 1;
366 }
367
368 static gint
369 up_down_compare (gconstpointer a,
370                  gconstpointer b,
371                  gpointer      data)
372 {
373   gint x1, y1, x2, y2;
374   
375   get_coordinates ((GtkWidget *)a, data, &x1, &y1);
376   get_coordinates ((GtkWidget *)b, data, &x2, &y2);
377   
378   if (x1 == x2)
379     return (y1 < y2) ? -1 : ((y1 == y2) ? 0 : 1);
380   else
381     return (x1 < x2) ? -1 : 1;
382 }
383
384 static gboolean
385 gtk_radio_button_focus (GtkWidget         *widget,
386                         GtkDirectionType   direction)
387 {
388   GtkRadioButton *radio_button = GTK_RADIO_BUTTON (widget);
389   GSList *tmp_slist;
390
391   /* Radio buttons with draw_indicator unset focus "normally", since
392    * they look like buttons to the user.
393    */
394   if (!GTK_TOGGLE_BUTTON (widget)->draw_indicator)
395     return GTK_WIDGET_CLASS (parent_class)->focus (widget, direction);
396   
397   if (gtk_widget_is_focus (widget))
398     {
399       GSList *focus_list, *tmp_list;
400       GtkWidget *toplevel = gtk_widget_get_toplevel (widget);
401       GtkWidget *new_focus = NULL;
402
403       focus_list = g_slist_copy (radio_button->group);
404       
405       switch (direction)
406         {
407         case GTK_DIR_TAB_FORWARD:
408         case GTK_DIR_TAB_BACKWARD:
409           return FALSE;
410         case GTK_DIR_LEFT:
411         case GTK_DIR_RIGHT:
412           focus_list = g_slist_sort_with_data (focus_list, left_right_compare, toplevel);
413           break;
414         case GTK_DIR_UP:
415         case GTK_DIR_DOWN:
416           focus_list = g_slist_sort_with_data (focus_list, up_down_compare, toplevel);
417           break;
418         }
419
420       if (direction == GTK_DIR_LEFT || direction == GTK_DIR_UP)
421         focus_list = g_slist_reverse (focus_list);
422
423       tmp_list = g_slist_find (focus_list, widget);
424
425       if (tmp_list)
426         {
427           tmp_list = tmp_list->next;
428           
429           while (tmp_list)
430             {
431               GtkWidget *child = tmp_list->data;
432               
433               if (GTK_WIDGET_VISIBLE (child) && GTK_WIDGET_IS_SENSITIVE (child))
434                 {
435                   new_focus = child;
436                   break;
437                 }
438
439               tmp_list = tmp_list->next;
440             }
441         }
442
443       if (!new_focus)
444         {
445           tmp_list = focus_list;
446
447           while (tmp_list)
448             {
449               GtkWidget *child = tmp_list->data;
450               
451               if (GTK_WIDGET_VISIBLE (child) && GTK_WIDGET_IS_SENSITIVE (child))
452                 {
453                   new_focus = child;
454                   break;
455                 }
456               
457               tmp_list = tmp_list->next;
458             }
459         }
460       
461       g_slist_free (focus_list);
462
463       if (new_focus)
464         {
465           gtk_widget_grab_focus (new_focus);
466           gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (new_focus), TRUE);
467         }
468
469       return TRUE;
470     }
471   else
472     {
473       GtkRadioButton *selected_button = NULL;
474       
475       /* We accept the focus if, we don't have the focus and
476        *  - we are the currently active button in the group
477        *  - there is no currently active radio button.
478        */
479       
480       tmp_slist = radio_button->group;
481       while (tmp_slist)
482         {
483           if (GTK_TOGGLE_BUTTON (tmp_slist->data)->active)
484             selected_button = tmp_slist->data;
485           tmp_slist = tmp_slist->next;
486         }
487       
488       if (selected_button && selected_button != radio_button)
489         return FALSE;
490
491       gtk_widget_grab_focus (widget);
492       return TRUE;
493     }
494 }
495
496 static void
497 gtk_radio_button_clicked (GtkButton *button)
498 {
499   GtkToggleButton *toggle_button;
500   GtkRadioButton *radio_button;
501   GtkToggleButton *tmp_button;
502   GtkStateType new_state;
503   GSList *tmp_list;
504   gint toggled;
505   gboolean depressed;
506
507   radio_button = GTK_RADIO_BUTTON (button);
508   toggle_button = GTK_TOGGLE_BUTTON (button);
509   toggled = FALSE;
510
511   gtk_widget_ref (GTK_WIDGET (button));
512
513   if (toggle_button->active)
514     {
515       tmp_button = NULL;
516       tmp_list = radio_button->group;
517
518       while (tmp_list)
519         {
520           tmp_button = tmp_list->data;
521           tmp_list = tmp_list->next;
522
523           if (tmp_button->active && tmp_button != toggle_button)
524             break;
525
526           tmp_button = NULL;
527         }
528
529       if (!tmp_button)
530         {
531           new_state = (button->in_button ? GTK_STATE_PRELIGHT : GTK_STATE_ACTIVE);
532         }
533       else
534         {
535           toggled = TRUE;
536           toggle_button->active = !toggle_button->active;
537           new_state = (button->in_button ? GTK_STATE_PRELIGHT : GTK_STATE_NORMAL);
538         }
539     }
540   else
541     {
542       toggled = TRUE;
543       toggle_button->active = !toggle_button->active;
544
545       tmp_list = radio_button->group;
546       while (tmp_list)
547         {
548           tmp_button = tmp_list->data;
549           tmp_list = tmp_list->next;
550
551           if (tmp_button->active && (tmp_button != toggle_button))
552             {
553               gtk_button_clicked (GTK_BUTTON (tmp_button));
554               break;
555             }
556         }
557
558       new_state = (button->in_button ? GTK_STATE_PRELIGHT : GTK_STATE_ACTIVE);
559     }
560
561   if (toggle_button->inconsistent)
562     depressed = FALSE;
563   else if (button->in_button && button->button_down)
564     depressed = !toggle_button->active;
565   else
566     depressed = toggle_button->active;
567
568   if (GTK_WIDGET_STATE (button) != new_state)
569     gtk_widget_set_state (GTK_WIDGET (button), new_state);
570
571   if (toggled)
572     gtk_toggle_button_toggled (toggle_button);
573
574   _gtk_button_set_depressed (button, depressed);
575
576   gtk_widget_queue_draw (GTK_WIDGET (button));
577
578   gtk_widget_unref (GTK_WIDGET (button));
579 }
580
581 static void
582 gtk_radio_button_draw_indicator (GtkCheckButton *check_button,
583                                  GdkRectangle   *area)
584 {
585   GtkWidget *widget;
586   GtkButton *button;
587   GtkToggleButton *toggle_button;
588   GtkStateType state_type;
589   GtkShadowType shadow_type;
590   gint x, y;
591   gint indicator_size, indicator_spacing;
592   gint focus_width;
593   gint focus_pad;
594   gboolean interior_focus;
595
596   if (GTK_WIDGET_DRAWABLE (check_button))
597     {
598       widget = GTK_WIDGET (check_button);
599       button = GTK_BUTTON (check_button);
600       toggle_button = GTK_TOGGLE_BUTTON (check_button);
601
602       gtk_widget_style_get (widget,
603                             "interior_focus", &interior_focus,
604                             "focus-line-width", &focus_width,
605                             "focus-padding", &focus_pad,
606                             NULL);
607
608       _gtk_check_button_get_props (check_button, &indicator_size, &indicator_spacing);
609
610       x = widget->allocation.x + indicator_spacing + GTK_CONTAINER (widget)->border_width;
611       y = widget->allocation.y + (widget->allocation.height - indicator_size) / 2;
612
613       if (!interior_focus)
614         x += focus_width + focus_pad;      
615
616       if (toggle_button->inconsistent)
617         shadow_type = GTK_SHADOW_ETCHED_IN;
618       else if (toggle_button->active)
619         shadow_type = GTK_SHADOW_IN;
620       else
621         shadow_type = GTK_SHADOW_OUT;
622
623       if (button->activate_timeout || (button->button_down && button->in_button))
624         state_type = GTK_STATE_ACTIVE;
625       else if (button->in_button)
626         state_type = GTK_STATE_PRELIGHT;
627       else
628         state_type = GTK_STATE_NORMAL;
629
630       if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
631         x = widget->allocation.x + widget->allocation.width - (indicator_size + x - widget->allocation.x);
632
633       if (GTK_WIDGET_STATE (toggle_button) == GTK_STATE_PRELIGHT)
634         {
635           GdkRectangle restrict_area;
636           GdkRectangle new_area;
637               
638           restrict_area.x = widget->allocation.x + GTK_CONTAINER (widget)->border_width;
639           restrict_area.y = widget->allocation.y + GTK_CONTAINER (widget)->border_width;
640           restrict_area.width = widget->allocation.width - (2 * GTK_CONTAINER (widget)->border_width);
641           restrict_area.height = widget->allocation.height - (2 * GTK_CONTAINER (widget)->border_width);
642           
643           if (gdk_rectangle_intersect (area, &restrict_area, &new_area))
644             {
645               gtk_paint_flat_box (widget->style, widget->window, GTK_STATE_PRELIGHT,
646                                   GTK_SHADOW_ETCHED_OUT, 
647                                   area, widget, "checkbutton",
648                                   new_area.x, new_area.y,
649                                   new_area.width, new_area.height);
650             }
651         }
652
653       gtk_paint_option (widget->style, widget->window,
654                         state_type, shadow_type,
655                         area, widget, "radiobutton",
656                         x, y, indicator_size, indicator_size);
657     }
658 }