]> Pileus Git - ~andy/gtk/blob - gtk/gtkoptionmenu.c
docs/gtkfaq.sgml: fix the spelling of Helge Hess's name
[~andy/gtk] / gtk / gtkoptionmenu.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 Free
16  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18 #include "gtkmenu.h"
19 #include "gtkmenuitem.h"
20 #include "gtkoptionmenu.h"
21 #include "gtksignal.h"
22
23
24 #define CHILD_LEFT_SPACING        5
25 #define CHILD_RIGHT_SPACING       1
26 #define CHILD_TOP_SPACING         1
27 #define CHILD_BOTTOM_SPACING      1
28 #define OPTION_INDICATOR_WIDTH    12
29 #define OPTION_INDICATOR_HEIGHT   8
30 #define OPTION_INDICATOR_SPACING  2
31
32
33 static void gtk_option_menu_class_init      (GtkOptionMenuClass *klass);
34 static void gtk_option_menu_init            (GtkOptionMenu      *option_menu);
35 static void gtk_option_menu_destroy         (GtkObject          *object);
36 static void gtk_option_menu_size_request    (GtkWidget          *widget,
37                                              GtkRequisition     *requisition);
38 static void gtk_option_menu_size_allocate   (GtkWidget          *widget,
39                                              GtkAllocation      *allocation);
40 static void gtk_option_menu_paint           (GtkWidget          *widget,
41                                              GdkRectangle       *area);
42 static void gtk_option_menu_draw            (GtkWidget          *widget,
43                                              GdkRectangle       *area);
44 static gint gtk_option_menu_expose          (GtkWidget          *widget,
45                                              GdkEventExpose     *event);
46 static gint gtk_option_menu_button_press    (GtkWidget          *widget,
47                                              GdkEventButton     *event);
48 static void gtk_option_menu_deactivate      (GtkMenuShell       *menu_shell,
49                                              GtkOptionMenu      *option_menu);
50 static void gtk_option_menu_update_contents (GtkOptionMenu      *option_menu);
51 static void gtk_option_menu_remove_contents (GtkOptionMenu      *option_menu);
52 static void gtk_option_menu_calc_size       (GtkOptionMenu      *option_menu);
53 static void gtk_option_menu_position        (GtkMenu            *menu,
54                                              gint               *x,
55                                              gint               *y,
56                                              gpointer            user_data);
57 static void gtk_option_menu_show_all        (GtkWidget          *widget);
58 static void gtk_option_menu_hide_all        (GtkWidget          *widget);
59
60
61 static GtkButtonClass *parent_class = NULL;
62
63
64 guint
65 gtk_option_menu_get_type ()
66 {
67   static guint option_menu_type = 0;
68
69   if (!option_menu_type)
70     {
71       GtkTypeInfo option_menu_info =
72       {
73         "GtkOptionMenu",
74         sizeof (GtkOptionMenu),
75         sizeof (GtkOptionMenuClass),
76         (GtkClassInitFunc) gtk_option_menu_class_init,
77         (GtkObjectInitFunc) gtk_option_menu_init,
78         (GtkArgSetFunc) NULL,
79         (GtkArgGetFunc) NULL,
80       };
81
82       option_menu_type = gtk_type_unique (gtk_button_get_type (), &option_menu_info);
83     }
84
85   return option_menu_type;
86 }
87
88 static void
89 gtk_option_menu_class_init (GtkOptionMenuClass *class)
90 {
91   GtkObjectClass *object_class;
92   GtkWidgetClass *widget_class;
93   GtkButtonClass *button_class;
94
95   object_class = (GtkObjectClass*) class;
96   widget_class = (GtkWidgetClass*) class;
97   button_class = (GtkButtonClass*) class;
98
99   parent_class = gtk_type_class (gtk_button_get_type ());
100
101   object_class->destroy = gtk_option_menu_destroy;
102
103   widget_class->draw = gtk_option_menu_draw;
104   widget_class->draw_focus = NULL;
105   widget_class->size_request = gtk_option_menu_size_request;
106   widget_class->size_allocate = gtk_option_menu_size_allocate;
107   widget_class->expose_event = gtk_option_menu_expose;
108   widget_class->button_press_event = gtk_option_menu_button_press;
109   widget_class->show_all = gtk_option_menu_show_all;
110   widget_class->hide_all = gtk_option_menu_hide_all;
111 }
112
113 static void
114 gtk_option_menu_init (GtkOptionMenu *option_menu)
115 {
116   GTK_WIDGET_UNSET_FLAGS (option_menu, GTK_CAN_FOCUS);
117
118   option_menu->menu = NULL;
119   option_menu->menu_item = NULL;
120   option_menu->width = 0;
121   option_menu->height = 0;
122 }
123
124 GtkWidget*
125 gtk_option_menu_new ()
126 {
127   return GTK_WIDGET (gtk_type_new (gtk_option_menu_get_type ()));
128 }
129
130 GtkWidget*
131 gtk_option_menu_get_menu (GtkOptionMenu *option_menu)
132 {
133   g_return_val_if_fail (option_menu != NULL, NULL);
134   g_return_val_if_fail (GTK_IS_OPTION_MENU (option_menu), NULL);
135
136   return option_menu->menu;
137 }
138
139 static void
140 gtk_option_menu_detacher (GtkWidget     *widget,
141                           GtkMenu       *menu)
142 {
143   GtkOptionMenu *option_menu;
144
145   g_return_if_fail (widget != NULL);
146   g_return_if_fail (GTK_IS_OPTION_MENU (widget));
147
148   option_menu = GTK_OPTION_MENU (widget);
149   g_return_if_fail (option_menu->menu == (GtkWidget*) menu);
150
151   gtk_option_menu_remove_contents (option_menu);
152   gtk_signal_disconnect_by_data (GTK_OBJECT (option_menu->menu),
153                                  option_menu);
154   
155   option_menu->menu = NULL;
156 }
157
158 void
159 gtk_option_menu_set_menu (GtkOptionMenu *option_menu,
160                           GtkWidget     *menu)
161 {
162   g_return_if_fail (option_menu != NULL);
163   g_return_if_fail (GTK_IS_OPTION_MENU (option_menu));
164   g_return_if_fail (menu != NULL);
165   g_return_if_fail (GTK_IS_MENU (menu));
166
167   if (option_menu->menu != menu)
168     {
169       gtk_option_menu_remove_menu (option_menu);
170
171       option_menu->menu = menu;
172       gtk_menu_attach_to_widget (GTK_MENU (menu),
173                                  GTK_WIDGET (option_menu),
174                                  gtk_option_menu_detacher);
175
176       gtk_option_menu_calc_size (option_menu);
177
178       gtk_signal_connect (GTK_OBJECT (option_menu->menu), "deactivate",
179                           (GtkSignalFunc) gtk_option_menu_deactivate,
180                           option_menu);
181
182       if (GTK_WIDGET (option_menu)->parent)
183         gtk_widget_queue_resize (GTK_WIDGET (option_menu));
184
185       gtk_option_menu_update_contents (option_menu);
186     }
187 }
188
189 void
190 gtk_option_menu_remove_menu (GtkOptionMenu *option_menu)
191 {
192   g_return_if_fail (option_menu != NULL);
193   g_return_if_fail (GTK_IS_OPTION_MENU (option_menu));
194
195   if (option_menu->menu)
196     gtk_menu_detach (GTK_MENU (option_menu->menu));
197 }
198
199 void
200 gtk_option_menu_set_history (GtkOptionMenu *option_menu,
201                              guint          index)
202 {
203   GtkWidget *menu_item;
204
205   g_return_if_fail (option_menu != NULL);
206   g_return_if_fail (GTK_IS_OPTION_MENU (option_menu));
207
208   if (option_menu->menu)
209     {
210       gtk_menu_set_active (GTK_MENU (option_menu->menu), index);
211       menu_item = gtk_menu_get_active (GTK_MENU (option_menu->menu));
212
213       if (menu_item != option_menu->menu_item)
214         {
215           gtk_option_menu_remove_contents (option_menu);
216           gtk_option_menu_update_contents (option_menu);
217         }
218     }
219 }
220
221
222 static void
223 gtk_option_menu_destroy (GtkObject *object)
224 {
225   GtkOptionMenu *option_menu;
226
227   g_return_if_fail (object != NULL);
228   g_return_if_fail (GTK_IS_OPTION_MENU (object));
229
230   option_menu = GTK_OPTION_MENU (object);
231
232   if (option_menu->menu)
233     gtk_widget_destroy (option_menu->menu);
234
235   if (GTK_OBJECT_CLASS (parent_class)->destroy)
236     (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
237 }
238
239 static void
240 gtk_option_menu_size_request (GtkWidget      *widget,
241                               GtkRequisition *requisition)
242 {
243   GtkOptionMenu *option_menu;
244   gint tmp;
245
246   g_return_if_fail (widget != NULL);
247   g_return_if_fail (GTK_IS_OPTION_MENU (widget));
248   g_return_if_fail (requisition != NULL);
249
250   option_menu = GTK_OPTION_MENU (widget);
251
252   requisition->width = ((GTK_CONTAINER (widget)->border_width +
253                          GTK_WIDGET (widget)->style->klass->xthickness) * 2 +
254                         option_menu->width +
255                         OPTION_INDICATOR_WIDTH +
256                         OPTION_INDICATOR_SPACING * 5 +
257                         CHILD_LEFT_SPACING + CHILD_RIGHT_SPACING);
258   requisition->height = ((GTK_CONTAINER (widget)->border_width +
259                           GTK_WIDGET (widget)->style->klass->ythickness) * 2 +
260                          option_menu->height +
261                          CHILD_TOP_SPACING + CHILD_BOTTOM_SPACING);
262
263   tmp = (requisition->height - option_menu->height +
264          OPTION_INDICATOR_HEIGHT + OPTION_INDICATOR_SPACING * 2);
265   requisition->height = MAX (requisition->height, tmp);
266 }
267
268 static void
269 gtk_option_menu_size_allocate (GtkWidget     *widget,
270                                GtkAllocation *allocation)
271 {
272   GtkWidget *child;
273   GtkAllocation child_allocation;
274
275   g_return_if_fail (widget != NULL);
276   g_return_if_fail (GTK_IS_OPTION_MENU (widget));
277   g_return_if_fail (allocation != NULL);
278
279   widget->allocation = *allocation;
280   if (GTK_WIDGET_REALIZED (widget))
281     gdk_window_move_resize (widget->window,
282                             allocation->x, allocation->y,
283                             allocation->width, allocation->height);
284
285   child = GTK_BUTTON (widget)->child;
286   if (child && GTK_WIDGET_VISIBLE (child))
287     {
288       child_allocation.x = (GTK_CONTAINER (widget)->border_width +
289                             GTK_WIDGET (widget)->style->klass->xthickness);
290       child_allocation.y = (GTK_CONTAINER (widget)->border_width +
291                             GTK_WIDGET (widget)->style->klass->ythickness);
292       child_allocation.width = (allocation->width - child_allocation.x * 2 -
293                                 OPTION_INDICATOR_WIDTH - OPTION_INDICATOR_SPACING * 5 -
294                                 CHILD_LEFT_SPACING - CHILD_RIGHT_SPACING);
295       child_allocation.height = (allocation->height - child_allocation.y * 2 -
296                                  CHILD_TOP_SPACING - CHILD_BOTTOM_SPACING);
297       child_allocation.x += CHILD_LEFT_SPACING;
298       child_allocation.y += CHILD_RIGHT_SPACING;
299
300       gtk_widget_size_allocate (child, &child_allocation);
301     }
302 }
303
304 static void
305 gtk_option_menu_paint (GtkWidget    *widget,
306                        GdkRectangle *area)
307 {
308   GdkRectangle restrict_area;
309   GdkRectangle new_area;
310
311   g_return_if_fail (widget != NULL);
312   g_return_if_fail (GTK_IS_OPTION_MENU (widget));
313   g_return_if_fail (area != NULL);
314
315   if (GTK_WIDGET_DRAWABLE (widget))
316     {
317       restrict_area.x = GTK_CONTAINER (widget)->border_width;
318       restrict_area.y = GTK_CONTAINER (widget)->border_width;
319       restrict_area.width = widget->allocation.width - restrict_area.x * 2;
320       restrict_area.height = widget->allocation.height - restrict_area.y * 2;
321
322       if (gdk_rectangle_intersect (area, &restrict_area, &new_area))
323         {
324           gtk_style_set_background (widget->style, widget->window, GTK_WIDGET_STATE (widget));
325           gdk_window_clear_area (widget->window,
326                                  new_area.x, new_area.y,
327                                  new_area.width, new_area.height);
328
329           gtk_draw_shadow (widget->style, widget->window,
330                            GTK_WIDGET_STATE (widget), GTK_SHADOW_OUT,
331                            restrict_area.x, restrict_area.y,
332                            restrict_area.width, restrict_area.height);
333
334           gtk_draw_shadow (widget->style, widget->window,
335                            GTK_WIDGET_STATE (widget), GTK_SHADOW_OUT,
336                            restrict_area.x + restrict_area.width - restrict_area.x -
337                            OPTION_INDICATOR_WIDTH - OPTION_INDICATOR_SPACING * 4,
338                            restrict_area.y + (restrict_area.height - OPTION_INDICATOR_HEIGHT) / 2,
339                            OPTION_INDICATOR_WIDTH, OPTION_INDICATOR_HEIGHT);
340         }
341     }
342 }
343
344 static void
345 gtk_option_menu_draw (GtkWidget    *widget,
346                       GdkRectangle *area)
347 {
348   GtkWidget *child;
349   GdkRectangle child_area;
350
351   g_return_if_fail (widget != NULL);
352   g_return_if_fail (GTK_IS_OPTION_MENU (widget));
353   g_return_if_fail (area != NULL);
354
355   if (GTK_WIDGET_DRAWABLE (widget))
356     {
357       gtk_option_menu_paint (widget, area);
358
359       child = GTK_BUTTON (widget)->child;
360       if (child && gtk_widget_intersect (child, area, &child_area))
361         gtk_widget_draw (child, &child_area);
362     }
363 }
364
365 static gint
366 gtk_option_menu_expose (GtkWidget      *widget,
367                         GdkEventExpose *event)
368 {
369   GtkWidget *child;
370   GdkEventExpose child_event;
371   gint remove_child;
372
373   g_return_val_if_fail (widget != NULL, FALSE);
374   g_return_val_if_fail (GTK_IS_OPTION_MENU (widget), FALSE);
375   g_return_val_if_fail (event != NULL, FALSE);
376
377   if (GTK_WIDGET_DRAWABLE (widget))
378     {
379       gtk_option_menu_paint (widget, &event->area);
380
381       remove_child = FALSE;
382       child = GTK_BUTTON (widget)->child;
383
384       if (!child)
385         {
386           if (!GTK_OPTION_MENU (widget)->menu)
387             return FALSE;
388           gtk_option_menu_update_contents (GTK_OPTION_MENU (widget));
389           child = GTK_BUTTON (widget)->child;
390           if (!child)
391             return FALSE;
392           remove_child = TRUE;
393         }
394
395       child_event = *event;
396
397       if (GTK_WIDGET_NO_WINDOW (child) &&
398           gtk_widget_intersect (child, &event->area, &child_event.area))
399         gtk_widget_event (child, (GdkEvent*) &child_event);
400
401       if (remove_child)
402         gtk_option_menu_remove_contents (GTK_OPTION_MENU (widget));
403     }
404
405   return FALSE;
406 }
407
408 static gint
409 gtk_option_menu_button_press (GtkWidget      *widget,
410                               GdkEventButton *event)
411 {
412   GtkOptionMenu *option_menu;
413
414   g_return_val_if_fail (widget != NULL, FALSE);
415   g_return_val_if_fail (GTK_IS_OPTION_MENU (widget), FALSE);
416   g_return_val_if_fail (event != NULL, FALSE);
417
418   if ((event->type == GDK_BUTTON_PRESS) &&
419       (event->button == 1))
420     {
421       option_menu = GTK_OPTION_MENU (widget);
422       gtk_option_menu_remove_contents (option_menu);
423       gtk_menu_popup (GTK_MENU (option_menu->menu), NULL, NULL,
424                       gtk_option_menu_position, option_menu,
425                       event->button, event->time);
426     }
427
428   return FALSE;
429 }
430
431 static void
432 gtk_option_menu_deactivate (GtkMenuShell  *menu_shell,
433                             GtkOptionMenu *option_menu)
434 {
435   g_return_if_fail (menu_shell != NULL);
436   g_return_if_fail (option_menu != NULL);
437   g_return_if_fail (GTK_IS_OPTION_MENU (option_menu));
438
439   gtk_option_menu_update_contents (option_menu);
440 }
441
442 static void
443 gtk_option_menu_update_contents (GtkOptionMenu *option_menu)
444 {
445   GtkWidget *child;
446
447   g_return_if_fail (option_menu != NULL);
448   g_return_if_fail (GTK_IS_OPTION_MENU (option_menu));
449
450   if (option_menu->menu)
451     {
452       gtk_option_menu_remove_contents (option_menu);
453
454       option_menu->menu_item = gtk_menu_get_active (GTK_MENU (option_menu->menu));
455       if (option_menu->menu_item)
456         {
457           gtk_widget_ref (option_menu->menu_item);
458           child = GTK_BIN (option_menu->menu_item)->child;
459           if (child)
460             {
461               gtk_container_block_resize (GTK_CONTAINER (option_menu));
462               if (GTK_BUTTON (option_menu)->child)
463                 gtk_container_remove (GTK_CONTAINER (option_menu),
464                                       GTK_BUTTON (option_menu)->child);
465               if (GTK_WIDGET (option_menu)->state != child->state)
466                 gtk_widget_set_state (child, GTK_WIDGET (option_menu)->state);
467               gtk_widget_reparent (child, GTK_WIDGET (option_menu));
468               gtk_container_unblock_resize (GTK_CONTAINER (option_menu));
469             }
470
471           gtk_widget_size_request (child, &child->requisition);
472           gtk_widget_size_allocate (GTK_WIDGET (option_menu),
473                                     &(GTK_WIDGET (option_menu)->allocation));
474
475           if (GTK_WIDGET_DRAWABLE (option_menu))
476             gtk_widget_queue_draw (GTK_WIDGET (option_menu));
477         }
478     }
479 }
480
481 static void
482 gtk_option_menu_remove_contents (GtkOptionMenu *option_menu)
483 {
484   g_return_if_fail (option_menu != NULL);
485   g_return_if_fail (GTK_IS_OPTION_MENU (option_menu));
486
487   if (GTK_BUTTON (option_menu)->child)
488     {
489       gtk_container_block_resize (GTK_CONTAINER (option_menu));
490       if (GTK_WIDGET (option_menu->menu_item)->state != GTK_BUTTON (option_menu)->child->state)
491         gtk_widget_set_state (GTK_BUTTON (option_menu)->child,
492                               GTK_WIDGET (option_menu->menu_item)->state);
493       gtk_widget_unrealize (GTK_BUTTON (option_menu)->child);
494       gtk_widget_reparent (GTK_BUTTON (option_menu)->child, option_menu->menu_item);
495       gtk_widget_unref (option_menu->menu_item);
496       option_menu->menu_item = NULL;
497       gtk_container_unblock_resize (GTK_CONTAINER (option_menu));
498     }
499 }
500
501 static void
502 gtk_option_menu_calc_size (GtkOptionMenu *option_menu)
503 {
504   GtkWidget *child;
505   GList *children;
506
507   g_return_if_fail (option_menu != NULL);
508   g_return_if_fail (GTK_IS_OPTION_MENU (option_menu));
509
510   option_menu->width = 0;
511   option_menu->height = 0;
512
513   if (option_menu->menu)
514     {
515       children = GTK_MENU_SHELL (option_menu->menu)->children;
516       while (children)
517         {
518           child = children->data;
519           children = children->next;
520
521           if (GTK_WIDGET_VISIBLE (child))
522             {
523               gtk_widget_size_request (child, &child->requisition);
524
525               option_menu->width = MAX (option_menu->width, child->requisition.width);
526               option_menu->height = MAX (option_menu->height, child->requisition.height);
527             }
528         }
529     }
530 }
531
532 static void
533 gtk_option_menu_position (GtkMenu  *menu,
534                           gint     *x,
535                           gint     *y,
536                           gpointer  user_data)
537 {
538   GtkOptionMenu *option_menu;
539   GtkWidget *active;
540   GtkWidget *child;
541   GList *children;
542   gint shift_menu;
543   gint screen_width;
544   gint screen_height;
545   gint menu_xpos;
546   gint menu_ypos;
547   gint width;
548   gint height;
549
550   g_return_if_fail (user_data != NULL);
551   g_return_if_fail (GTK_IS_OPTION_MENU (user_data));
552
553   option_menu = GTK_OPTION_MENU (user_data);
554
555   width = GTK_WIDGET (menu)->allocation.width;
556   height = GTK_WIDGET (menu)->allocation.height;
557
558   active = gtk_menu_get_active (GTK_MENU (option_menu->menu));
559   children = GTK_MENU_SHELL (option_menu->menu)->children;
560   gdk_window_get_origin (GTK_WIDGET (option_menu)->window, &menu_xpos, &menu_ypos);
561
562   menu_ypos += GTK_WIDGET (option_menu)->allocation.height / 2 - 2;
563
564   if (active != NULL)
565     menu_ypos -= active->requisition.height / 2;
566
567   while (children)
568     {
569       child = children->data;
570
571       if (active == child)
572         break;
573
574       menu_ypos -= child->allocation.height;
575       children = children->next;
576     }
577
578   screen_width = gdk_screen_width ();
579   screen_height = gdk_screen_height ();
580
581   shift_menu = FALSE;
582   if (menu_ypos < 0)
583     {
584       menu_ypos = 0;
585       shift_menu = TRUE;
586     }
587   else if ((menu_ypos + height) > screen_height)
588     {
589       menu_ypos -= ((menu_ypos + height) - screen_height);
590       shift_menu = TRUE;
591     }
592
593   if (shift_menu)
594     {
595       if ((menu_xpos + GTK_WIDGET (option_menu)->allocation.width + width) <= screen_width)
596         menu_xpos += GTK_WIDGET (option_menu)->allocation.width;
597       else
598         menu_xpos -= width;
599     }
600
601   if (menu_xpos < 0)
602     menu_xpos = 0;
603   else if ((menu_xpos + width) > screen_width)
604     menu_xpos -= ((menu_xpos + width) - screen_width);
605
606   *x = menu_xpos;
607   *y = menu_ypos;
608 }
609
610
611 static void
612 gtk_option_menu_show_all (GtkWidget *widget)
613 {
614   GtkContainer *container;
615   GtkOptionMenu *option_menu;
616   
617   g_return_if_fail (widget != NULL);
618   g_return_if_fail (GTK_IS_OPTION_MENU (widget));
619   container = GTK_CONTAINER (widget);
620   option_menu = GTK_OPTION_MENU (widget);
621
622   gtk_widget_show (widget);
623   gtk_container_foreach (container, (GtkCallback) gtk_widget_show_all, NULL);
624   if (option_menu->menu)
625     gtk_widget_show_all (option_menu->menu);
626   if (option_menu->menu_item)
627     gtk_widget_show_all (option_menu->menu_item);
628 }
629
630
631 static void
632 gtk_option_menu_hide_all (GtkWidget *widget)
633 {
634   GtkContainer *container;
635
636   g_return_if_fail (widget != NULL);
637   g_return_if_fail (GTK_IS_OPTION_MENU (widget));
638   container = GTK_CONTAINER (widget);
639
640   gtk_widget_hide (widget);
641   gtk_container_foreach (container, (GtkCallback) gtk_widget_hide_all, NULL);
642 }
643