]> Pileus Git - ~andy/gtk/blob - gtk/gtktogglebutton.c
reworked the redrawing heuristics somewhat, this fixed a bunch of existing
[~andy/gtk] / gtk / gtktogglebutton.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 Library 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  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library 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 #include "gtklabel.h"
20 #include "gtkmain.h"
21 #include "gtksignal.h"
22 #include "gtktogglebutton.h"
23
24
25 #define DEFAULT_LEFT_POS  4
26 #define DEFAULT_TOP_POS   4
27 #define DEFAULT_SPACING   7
28
29 enum {
30   TOGGLED,
31   LAST_SIGNAL
32 };
33
34 enum {
35   ARG_0,
36   ARG_ACTIVE,
37   ARG_DRAW_INDICATOR
38 };
39
40
41 static void gtk_toggle_button_class_init (GtkToggleButtonClass *klass);
42 static void gtk_toggle_button_init       (GtkToggleButton      *toggle_button);
43 static void gtk_toggle_button_paint      (GtkWidget            *widget,
44                                           GdkRectangle         *area);
45 static void gtk_toggle_button_size_allocate (GtkWidget         *widget,
46                                              GtkAllocation     *allocation);
47 static void gtk_toggle_button_draw       (GtkWidget            *widget,
48                                           GdkRectangle         *area);
49 static gint gtk_toggle_button_expose     (GtkWidget            *widget,
50                                           GdkEventExpose       *event);
51 static void gtk_toggle_button_pressed    (GtkButton            *button);
52 static void gtk_toggle_button_released   (GtkButton            *button);
53 static void gtk_toggle_button_clicked    (GtkButton            *button);
54 static void gtk_toggle_button_enter      (GtkButton            *button);
55 static void gtk_toggle_button_leave      (GtkButton            *button);
56 static void gtk_toggle_button_set_arg    (GtkObject            *object,
57                                           GtkArg               *arg,
58                                           guint                 arg_id);
59 static void gtk_toggle_button_get_arg    (GtkObject            *object,
60                                           GtkArg               *arg,
61                                           guint                 arg_id);
62 static void gtk_toggle_button_leave      (GtkButton            *button);
63 static void gtk_toggle_button_realize    (GtkWidget            *widget);
64 static void gtk_toggle_button_unrealize  (GtkWidget            *widget);
65 static void gtk_toggle_button_map        (GtkWidget            *widget);
66 static void gtk_toggle_button_unmap      (GtkWidget            *widget);
67
68 static guint toggle_button_signals[LAST_SIGNAL] = { 0 };
69 static GtkContainerClass *parent_class = NULL;
70
71 GtkType
72 gtk_toggle_button_get_type (void)
73 {
74   static GtkType toggle_button_type = 0;
75
76   if (!toggle_button_type)
77     {
78       static const GtkTypeInfo toggle_button_info =
79       {
80         "GtkToggleButton",
81         sizeof (GtkToggleButton),
82         sizeof (GtkToggleButtonClass),
83         (GtkClassInitFunc) gtk_toggle_button_class_init,
84         (GtkObjectInitFunc) gtk_toggle_button_init,
85         /* reserved_1 */ NULL,
86         /* reserved_2 */ NULL,
87         (GtkClassInitFunc) NULL,
88       };
89
90       toggle_button_type = gtk_type_unique (GTK_TYPE_BUTTON, &toggle_button_info);
91     }
92
93   return toggle_button_type;
94 }
95
96 static void
97 gtk_toggle_button_class_init (GtkToggleButtonClass *class)
98 {
99   GtkObjectClass *object_class;
100   GtkWidgetClass *widget_class;
101   GtkContainerClass *container_class;
102   GtkButtonClass *button_class;
103
104   object_class = (GtkObjectClass*) class;
105   widget_class = (GtkWidgetClass*) class;
106   container_class = (GtkContainerClass*) class;
107   button_class = (GtkButtonClass*) class;
108
109   parent_class = gtk_type_class (GTK_TYPE_BUTTON);
110
111   gtk_object_add_arg_type ("GtkToggleButton::active", GTK_TYPE_BOOL, GTK_ARG_READWRITE, ARG_ACTIVE);
112   gtk_object_add_arg_type ("GtkToggleButton::draw_indicator", GTK_TYPE_BOOL, GTK_ARG_READWRITE, ARG_DRAW_INDICATOR);
113
114   toggle_button_signals[TOGGLED] =
115     gtk_signal_new ("toggled",
116                     GTK_RUN_FIRST,
117                     object_class->type,
118                     GTK_SIGNAL_OFFSET (GtkToggleButtonClass, toggled),
119                     gtk_marshal_NONE__NONE,
120                     GTK_TYPE_NONE, 0);
121
122   gtk_object_class_add_signals (object_class, toggle_button_signals, LAST_SIGNAL);
123
124   object_class->set_arg = gtk_toggle_button_set_arg;
125   object_class->get_arg = gtk_toggle_button_get_arg;
126
127   widget_class->size_allocate = gtk_toggle_button_size_allocate;
128   widget_class->draw = gtk_toggle_button_draw;
129   widget_class->expose_event = gtk_toggle_button_expose;
130   widget_class->realize = gtk_toggle_button_realize;
131   widget_class->unrealize = gtk_toggle_button_unrealize;
132   widget_class->map = gtk_toggle_button_map;
133   widget_class->unmap = gtk_toggle_button_unmap;
134
135   button_class->pressed = gtk_toggle_button_pressed;
136   button_class->released = gtk_toggle_button_released;
137   button_class->clicked = gtk_toggle_button_clicked;
138   button_class->enter = gtk_toggle_button_enter;
139   button_class->leave = gtk_toggle_button_leave;
140
141   class->toggled = NULL;
142 }
143
144 static void
145 gtk_toggle_button_init (GtkToggleButton *toggle_button)
146 {
147   toggle_button->active = FALSE;
148   toggle_button->draw_indicator = FALSE;
149   GTK_WIDGET_UNSET_FLAGS (toggle_button, GTK_NO_WINDOW);
150 }
151
152
153 GtkWidget*
154 gtk_toggle_button_new (void)
155 {
156   return GTK_WIDGET (gtk_type_new (gtk_toggle_button_get_type ()));
157 }
158
159 GtkWidget*
160 gtk_toggle_button_new_with_label (const gchar *label)
161 {
162   GtkWidget *toggle_button;
163   GtkWidget *label_widget;
164
165   toggle_button = gtk_toggle_button_new ();
166   label_widget = gtk_label_new (label);
167   gtk_misc_set_alignment (GTK_MISC (label_widget), 0.5, 0.5);
168
169   gtk_container_add (GTK_CONTAINER (toggle_button), label_widget);
170   gtk_widget_show (label_widget);
171
172   return toggle_button;
173 }
174
175 static void
176 gtk_toggle_button_set_arg (GtkObject *object,
177                            GtkArg    *arg,
178                            guint      arg_id)
179 {
180   GtkToggleButton *tb;
181
182   tb = GTK_TOGGLE_BUTTON (object);
183
184   switch (arg_id)
185     {
186     case ARG_ACTIVE:
187       gtk_toggle_button_set_active (tb, GTK_VALUE_BOOL (*arg));
188       break;
189     case ARG_DRAW_INDICATOR:
190       gtk_toggle_button_set_mode (tb, GTK_VALUE_BOOL (*arg));
191       break;
192     default:
193       break;
194     }
195 }
196
197 static void
198 gtk_toggle_button_get_arg (GtkObject *object,
199                            GtkArg    *arg,
200                            guint      arg_id)
201 {
202   GtkToggleButton *tb;
203
204   tb = GTK_TOGGLE_BUTTON (object);
205
206   switch (arg_id)
207     {
208     case ARG_ACTIVE:
209       GTK_VALUE_BOOL (*arg) = tb->active;
210       break;
211     case ARG_DRAW_INDICATOR:
212       GTK_VALUE_BOOL (*arg) = tb->draw_indicator;
213       break;
214     default:
215       arg->type = GTK_TYPE_INVALID;
216       break;
217     }
218 }
219
220 void
221 gtk_toggle_button_set_mode (GtkToggleButton *toggle_button,
222                             gboolean         draw_indicator)
223 {
224   GtkWidget *widget;
225
226   g_return_if_fail (toggle_button != NULL);
227   g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button));
228
229   widget = GTK_WIDGET (toggle_button);
230
231   draw_indicator = draw_indicator ? TRUE : FALSE;
232
233   if (toggle_button->draw_indicator != draw_indicator)
234     {
235       if (GTK_WIDGET_REALIZED (toggle_button))
236         {
237           gboolean visible = GTK_WIDGET_VISIBLE (toggle_button);
238
239           if (visible)
240             gtk_widget_hide (widget);
241
242           gtk_widget_unrealize (widget);
243           toggle_button->draw_indicator = draw_indicator;
244
245           if (toggle_button->draw_indicator)
246             GTK_WIDGET_SET_FLAGS (toggle_button, GTK_NO_WINDOW);
247           else
248             GTK_WIDGET_UNSET_FLAGS (toggle_button, GTK_NO_WINDOW);
249           
250           gtk_widget_realize (widget);
251
252           if (visible)
253             gtk_widget_show (widget);
254         }
255       else
256         {
257           toggle_button->draw_indicator = draw_indicator;
258
259           if (toggle_button->draw_indicator)
260             GTK_WIDGET_SET_FLAGS (toggle_button, GTK_NO_WINDOW);
261           else
262             GTK_WIDGET_UNSET_FLAGS (toggle_button, GTK_NO_WINDOW);
263         }
264
265       if (GTK_WIDGET_VISIBLE (toggle_button))
266         gtk_widget_queue_resize (GTK_WIDGET (toggle_button));
267     }
268 }
269
270 void
271 gtk_toggle_button_set_active (GtkToggleButton *toggle_button,
272                               gboolean         is_active)
273 {
274   g_return_if_fail (toggle_button != NULL);
275   g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button));
276
277   is_active = is_active != 0;
278
279   if (toggle_button->active != is_active)
280     gtk_button_clicked (GTK_BUTTON (toggle_button));
281 }
282
283 void
284 gtk_toggle_button_toggled (GtkToggleButton *toggle_button)
285 {
286   g_return_if_fail (toggle_button != NULL);
287   g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button));
288
289   gtk_signal_emit (GTK_OBJECT (toggle_button), toggle_button_signals[TOGGLED]);
290 }
291
292
293 static void
294 gtk_toggle_button_paint (GtkWidget    *widget,
295                          GdkRectangle *area)
296 {
297   GtkButton *button;
298   GtkToggleButton *toggle_button;
299   GtkShadowType shadow_type;
300   gint width, height;
301   gint x, y;
302
303   button = GTK_BUTTON (widget);
304   toggle_button = GTK_TOGGLE_BUTTON (widget);
305
306   if (GTK_WIDGET_DRAWABLE (widget))
307     {
308       x = 0;
309       y = 0;
310       width = widget->allocation.width - GTK_CONTAINER (widget)->border_width * 2;
311       height = widget->allocation.height - GTK_CONTAINER (widget)->border_width * 2;
312
313       gdk_window_set_back_pixmap (widget->window, NULL, TRUE);
314       gdk_window_clear_area (widget->window, area->x, area->y, area->width, area->height);
315
316       if (GTK_WIDGET_HAS_DEFAULT (widget) &&
317           GTK_BUTTON (widget)->relief == GTK_RELIEF_NORMAL)
318         {
319           gtk_paint_box (widget->style, widget->window,
320                          GTK_STATE_NORMAL, GTK_SHADOW_IN,
321                          area, widget, "togglebuttondefault",
322                          x, y, width, height);
323         }
324
325       if (GTK_WIDGET_CAN_DEFAULT (widget))
326         {
327           x += widget->style->klass->xthickness;
328           y += widget->style->klass->ythickness;
329           width -= 2 * x + DEFAULT_SPACING;
330           height -= 2 * y + DEFAULT_SPACING;
331           x += DEFAULT_LEFT_POS;
332           y += DEFAULT_TOP_POS;
333         }
334
335       if (GTK_WIDGET_HAS_FOCUS (widget))
336         {
337           x += 1;
338           y += 1;
339           width -= 2;
340           height -= 2;
341         }
342
343       if ((GTK_WIDGET_STATE (widget) == GTK_STATE_ACTIVE) ||
344           toggle_button->active)
345         shadow_type = GTK_SHADOW_IN;
346       else
347         shadow_type = GTK_SHADOW_OUT;
348       
349       if (button->relief != GTK_RELIEF_NONE ||
350           (GTK_WIDGET_STATE(widget) != GTK_STATE_NORMAL &&
351            GTK_WIDGET_STATE(widget) != GTK_STATE_INSENSITIVE))
352         gtk_paint_box (widget->style, widget->window,
353                        GTK_WIDGET_STATE (widget),
354                        shadow_type, area, widget, "togglebutton",
355                        x, y, width, height);
356       
357       if (GTK_WIDGET_HAS_FOCUS (widget))
358         {
359           x -= 1;
360           y -= 1;
361           width += 2;
362           height += 2;
363
364           gtk_paint_focus (widget->style, widget->window,
365                            area, widget, "togglebutton",
366                            x, y, width - 1, height - 1);
367         }
368     }
369 }
370
371 static void
372 gtk_toggle_button_size_allocate (GtkWidget     *widget,
373                                  GtkAllocation *allocation)
374 {
375   if (!GTK_WIDGET_NO_WINDOW (widget) &&
376       GTK_WIDGET_CLASS (parent_class)->size_allocate)
377     GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
378 }
379
380 static gint
381 gtk_toggle_button_expose (GtkWidget      *widget,
382                           GdkEventExpose *event)
383 {
384   if (!GTK_WIDGET_NO_WINDOW (widget) &&
385       GTK_WIDGET_CLASS (parent_class)->expose_event)
386     return GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
387   else
388     return FALSE;
389 }
390
391 static void
392 gtk_toggle_button_draw (GtkWidget    *widget,
393                         GdkRectangle *area)
394 {
395   GdkRectangle child_area;
396   GdkRectangle tmp_area;
397   GtkBin *bin;
398
399   g_return_if_fail (widget != NULL);
400   g_return_if_fail (GTK_IS_TOGGLE_BUTTON (widget));
401   g_return_if_fail (area != NULL);
402
403   bin = GTK_BIN (widget);
404
405   if (GTK_WIDGET_DRAWABLE (widget) && !GTK_WIDGET_NO_WINDOW (widget))
406     {
407       tmp_area = *area;
408       tmp_area.x -= GTK_CONTAINER (widget)->border_width;
409       tmp_area.y -= GTK_CONTAINER (widget)->border_width;
410
411       gtk_toggle_button_paint (widget, &tmp_area);
412
413       if (bin->child && gtk_widget_intersect (bin->child, &tmp_area, &child_area))
414         gtk_widget_draw (bin->child, &child_area);
415     }
416 }
417
418 static void
419 gtk_toggle_button_pressed (GtkButton *button)
420 {
421   GtkToggleButton *toggle_button;
422   GtkStateType new_state;
423
424   g_return_if_fail (button != NULL);
425   g_return_if_fail (GTK_IS_TOGGLE_BUTTON (button));
426
427   toggle_button = GTK_TOGGLE_BUTTON (button);
428
429   button->button_down = TRUE;
430
431   if (toggle_button->active)
432     new_state = (button->in_button ? GTK_STATE_NORMAL : GTK_STATE_ACTIVE);
433   else
434     new_state = (button->in_button ? GTK_STATE_ACTIVE : GTK_STATE_NORMAL);
435
436   if (GTK_WIDGET_STATE (button) != new_state)
437     gtk_widget_set_state (GTK_WIDGET (button), new_state);
438 }
439
440 static void
441 gtk_toggle_button_released (GtkButton *button)
442 {
443   GtkToggleButton *toggle_button;
444   GtkStateType new_state;
445
446   g_return_if_fail (button != NULL);
447   g_return_if_fail (GTK_IS_TOGGLE_BUTTON (button));
448
449   if (button->button_down)
450     {
451       toggle_button = GTK_TOGGLE_BUTTON (button);
452
453       button->button_down = FALSE;
454
455       if (button->in_button)
456         {
457           gtk_button_clicked (button);
458         }
459       else
460         {
461           if (toggle_button->active)
462             new_state = (button->in_button ? GTK_STATE_PRELIGHT : GTK_STATE_ACTIVE);
463           else
464             new_state = (button->in_button ? GTK_STATE_PRELIGHT : GTK_STATE_NORMAL);
465
466           if (GTK_WIDGET_STATE (button) != new_state)
467             gtk_widget_set_state (GTK_WIDGET (button), new_state);
468         }
469     }
470 }
471
472 static void
473 gtk_toggle_button_clicked (GtkButton *button)
474 {
475   GtkToggleButton *toggle_button;
476   GtkStateType new_state;
477
478   g_return_if_fail (button != NULL);
479   g_return_if_fail (GTK_IS_TOGGLE_BUTTON (button));
480
481   toggle_button = GTK_TOGGLE_BUTTON (button);
482   toggle_button->active = !toggle_button->active;
483
484   gtk_toggle_button_toggled (toggle_button);
485
486   if (toggle_button->active)
487     new_state = (button->in_button ? GTK_STATE_PRELIGHT : GTK_STATE_ACTIVE);
488   else
489     new_state = (button->in_button ? GTK_STATE_PRELIGHT : GTK_STATE_NORMAL);
490
491   if (GTK_WIDGET_STATE (button) != new_state)
492     gtk_widget_set_state (GTK_WIDGET (button), new_state);
493   else
494     gtk_widget_queue_draw (GTK_WIDGET (button));
495 }
496
497 static void
498 gtk_toggle_button_enter (GtkButton *button)
499 {
500   GtkToggleButton *toggle_button;
501   GtkStateType new_state;
502
503   g_return_if_fail (button != NULL);
504   g_return_if_fail (GTK_IS_TOGGLE_BUTTON (button));
505
506   toggle_button = GTK_TOGGLE_BUTTON (button);
507
508   if (toggle_button->active)
509     new_state = (button->button_down ? GTK_STATE_NORMAL : GTK_STATE_PRELIGHT);
510   else
511     new_state = (button->button_down ? GTK_STATE_ACTIVE : GTK_STATE_PRELIGHT);
512
513   if (GTK_WIDGET_STATE (button) != new_state)
514     gtk_widget_set_state (GTK_WIDGET (button), new_state);
515 }
516
517 static void
518 gtk_toggle_button_leave (GtkButton *button)
519 {
520   GtkToggleButton *toggle_button;
521   GtkStateType new_state;
522
523   g_return_if_fail (button != NULL);
524   g_return_if_fail (GTK_IS_TOGGLE_BUTTON (button));
525
526   toggle_button = GTK_TOGGLE_BUTTON (button);
527
528   new_state = (toggle_button->active ? GTK_STATE_ACTIVE : GTK_STATE_NORMAL);
529
530   if (GTK_WIDGET_STATE (button) != new_state)
531     gtk_widget_set_state (GTK_WIDGET (button), new_state);
532 }
533
534 static void
535 gtk_toggle_button_realize (GtkWidget *widget)
536 {
537   GtkToggleButton *toggle_button;
538   GdkWindowAttr attributes;
539   gint attributes_mask;
540   gint border_width;
541   
542   g_return_if_fail (widget != NULL);
543   g_return_if_fail (GTK_IS_TOGGLE_BUTTON (widget));
544   
545   toggle_button = GTK_TOGGLE_BUTTON (widget);
546   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
547   
548   border_width = GTK_CONTAINER (widget)->border_width;
549   
550   attributes.window_type = GDK_WINDOW_CHILD;
551   attributes.x = widget->allocation.x + border_width;
552   attributes.y = widget->allocation.y + border_width;
553   attributes.width = widget->allocation.width - border_width * 2;
554   attributes.height = widget->allocation.height - border_width * 2;
555   attributes.event_mask = gtk_widget_get_events (widget);
556   attributes.event_mask |= (GDK_EXPOSURE_MASK |
557                             GDK_BUTTON_PRESS_MASK |
558                             GDK_BUTTON_RELEASE_MASK |
559                             GDK_ENTER_NOTIFY_MASK |
560                             GDK_LEAVE_NOTIFY_MASK);
561
562   if (GTK_WIDGET_NO_WINDOW (widget))
563     {
564       attributes.wclass = GDK_INPUT_ONLY;
565       attributes_mask = GDK_WA_X | GDK_WA_Y;
566
567       widget->window = gtk_widget_get_parent_window (widget);
568       gdk_window_ref (widget->window);
569       
570       toggle_button->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
571                                                     &attributes, attributes_mask);
572       gdk_window_set_user_data (toggle_button->event_window, toggle_button);
573     }
574   else
575     {
576       attributes.wclass = GDK_INPUT_OUTPUT;
577       attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
578       attributes.visual = gtk_widget_get_visual (widget);
579       attributes.colormap = gtk_widget_get_colormap (widget);
580       widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
581                                        &attributes, attributes_mask);
582       gdk_window_set_user_data (widget->window, toggle_button);
583     }
584
585   widget->style = gtk_style_attach (widget->style, widget->window);
586
587   if (!GTK_WIDGET_NO_WINDOW (widget))
588     gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
589 }
590   
591 static void
592 gtk_toggle_button_unrealize (GtkWidget *widget)
593 {
594   GtkToggleButton *toggle_button;
595   
596   g_return_if_fail (widget != NULL);
597   g_return_if_fail (GTK_IS_TOGGLE_BUTTON (widget));
598
599   toggle_button = GTK_TOGGLE_BUTTON (widget);
600   
601   if (GTK_WIDGET_NO_WINDOW (widget))
602     {
603       gdk_window_set_user_data (toggle_button->event_window, NULL);
604       gdk_window_destroy (toggle_button->event_window);
605       toggle_button->event_window = NULL;
606     }
607
608   if (GTK_WIDGET_CLASS (parent_class)->unrealize)
609     (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
610 }
611
612 static void
613 gtk_toggle_button_map (GtkWidget *widget)
614 {
615   g_return_if_fail (widget != NULL);
616   g_return_if_fail (GTK_IS_TOGGLE_BUTTON (widget));
617
618   if (GTK_WIDGET_NO_WINDOW (widget))
619     gdk_window_show (GTK_TOGGLE_BUTTON (widget)->event_window);
620
621   GTK_WIDGET_CLASS (parent_class)->map (widget);
622 }
623
624 static void
625 gtk_toggle_button_unmap (GtkWidget *widget)
626 {
627   g_return_if_fail (widget != NULL);
628   g_return_if_fail (GTK_IS_TOGGLE_BUTTON (widget));
629
630   if (GTK_WIDGET_NO_WINDOW (widget))
631     gdk_window_hide (GTK_TOGGLE_BUTTON (widget)->event_window);
632
633   GTK_WIDGET_CLASS (parent_class)->unmap (widget);
634 }