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