]> Pileus Git - ~andy/gtk/blob - gtk/gtkmenu.c
7d3f2e879b358904dc6d156a778265be41b193c3
[~andy/gtk] / gtk / gtkmenu.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 <ctype.h>
28 #include "gdk/gdkkeysyms.h"
29 #include "gtkbindings.h"
30 #include "gtklabel.h"
31 #include "gtkmain.h"
32 #include "gtkmenu.h"
33 #include "gtkmenuitem.h"
34 #include "gtksignal.h"
35 #include "gtkwindow.h"
36 #include "gtkhbox.h"
37 #include "gtkvscrollbar.h"
38
39
40 #define MENU_ITEM_CLASS(w)   GTK_MENU_ITEM_GET_CLASS (w)
41 #define MENU_NEEDS_RESIZE(m) GTK_MENU_SHELL (m)->menu_flag
42
43 #define SUBMENU_NAV_REGION_PADDING 2
44 #define SUBMENU_NAV_HYSTERESIS_TIMEOUT 333
45
46 #define MENU_SCROLL_STEP 10
47 #define MENU_SCROLL_ARROW_HEIGHT 16
48 #define MENU_SCROLL_FAST_ZONE 4
49 #define MENU_SCROLL_TIMEOUT1 150
50 #define MENU_SCROLL_TIMEOUT2 50
51
52 typedef struct _GtkMenuAttachData       GtkMenuAttachData;
53
54 struct _GtkMenuAttachData
55 {
56   GtkWidget *attach_widget;
57   GtkMenuDetachFunc detacher;
58 };
59
60
61 static void     gtk_menu_class_init        (GtkMenuClass     *klass);
62 static void     gtk_menu_init              (GtkMenu          *menu);
63 static void     gtk_menu_destroy           (GtkObject        *object);
64 static void     gtk_menu_realize           (GtkWidget        *widget);
65 static void     gtk_menu_unrealize         (GtkWidget        *widget);
66 static void     gtk_menu_size_request      (GtkWidget        *widget,
67                                             GtkRequisition   *requisition);
68 static void     gtk_menu_size_allocate     (GtkWidget        *widget,
69                                             GtkAllocation    *allocation);
70 static void     gtk_menu_paint             (GtkWidget        *widget);
71 static void     gtk_menu_draw              (GtkWidget        *widget,
72                                             GdkRectangle     *area);
73 static gboolean gtk_menu_expose            (GtkWidget        *widget,
74                                             GdkEventExpose   *event);
75 static gboolean gtk_menu_key_press         (GtkWidget        *widget,
76                                             GdkEventKey      *event);
77 static gboolean gtk_menu_motion_notify     (GtkWidget        *widget,
78                                             GdkEventMotion   *event);
79 static gboolean gtk_menu_enter_notify      (GtkWidget        *widget,
80                                             GdkEventCrossing *event);
81 static gboolean gtk_menu_leave_notify      (GtkWidget        *widget,
82                                             GdkEventCrossing *event);
83 static void     gtk_menu_scroll_to         (GtkMenu          *menu,
84                                             gint              offset);
85 static void     gtk_menu_stop_scrolling    (GtkMenu          *menu);
86 static gboolean gtk_menu_scroll_timeout    (gpointer          data);
87 static void     gtk_menu_select_item       (GtkMenuShell     *menu_shell,
88                                             GtkWidget        *menu_item);
89 static void     gtk_menu_insert            (GtkMenuShell     *menu_shell,
90                                             GtkWidget        *child,
91                                             gint              position);
92 static void     gtk_menu_scrollbar_changed (GtkAdjustment    *adjustment,
93                                             GtkMenu          *menu);
94 static void     gtk_menu_handle_scrolling  (GtkMenu          *menu,
95                                             gboolean         enter);
96 static void     gtk_menu_set_tearoff_hints (GtkMenu          *menu,
97                                             gint             width);
98
99 static void     gtk_menu_stop_navigating_submenu       (GtkMenu          *menu);
100 static gboolean gtk_menu_stop_navigating_submenu_cb    (gpointer          user_data);
101 static gboolean gtk_menu_navigating_submenu            (GtkMenu          *menu,
102                                                         gint              event_x,
103                                                         gint              event_y);
104 static void     gtk_menu_set_submenu_navigation_region (GtkMenu          *menu,
105                                                         GtkMenuItem      *menu_item,
106                                                         GdkEventCrossing *event);
107  
108 static void gtk_menu_deactivate     (GtkMenuShell      *menu_shell);
109 static void gtk_menu_show_all       (GtkWidget         *widget);
110 static void gtk_menu_hide_all       (GtkWidget         *widget);
111 static void gtk_menu_position       (GtkMenu           *menu);
112 static void gtk_menu_reparent       (GtkMenu           *menu, 
113                                      GtkWidget         *new_parent, 
114                                      gboolean           unrealize);
115
116 static GtkMenuShellClass *parent_class = NULL;
117 static const gchar       *attach_data_key = "gtk-menu-attach-data";
118 static GQuark             quark_uline_accel_group = 0;
119
120 GtkType
121 gtk_menu_get_type (void)
122 {
123   static GtkType menu_type = 0;
124   
125   if (!menu_type)
126     {
127       static const GtkTypeInfo menu_info =
128       {
129         "GtkMenu",
130         sizeof (GtkMenu),
131         sizeof (GtkMenuClass),
132         (GtkClassInitFunc) gtk_menu_class_init,
133         (GtkObjectInitFunc) gtk_menu_init,
134         /* reserved_1 */ NULL,
135         /* reserved_2 */ NULL,
136         (GtkClassInitFunc) NULL,
137       };
138       
139       menu_type = gtk_type_unique (gtk_menu_shell_get_type (), &menu_info);
140     }
141   
142   return menu_type;
143 }
144
145 static void
146 gtk_menu_class_init (GtkMenuClass *class)
147 {
148   GtkObjectClass *object_class;
149   GtkWidgetClass *widget_class;
150   GtkContainerClass *container_class;
151   GtkMenuShellClass *menu_shell_class;
152
153   GtkBindingSet *binding_set;
154   
155   object_class = (GtkObjectClass*) class;
156   widget_class = (GtkWidgetClass*) class;
157   container_class = (GtkContainerClass*) class;
158   menu_shell_class = (GtkMenuShellClass*) class;
159   parent_class = gtk_type_class (gtk_menu_shell_get_type ());
160   
161   object_class->destroy = gtk_menu_destroy;
162   
163   widget_class->realize = gtk_menu_realize;
164   widget_class->unrealize = gtk_menu_unrealize;
165   widget_class->draw = gtk_menu_draw;
166   widget_class->size_request = gtk_menu_size_request;
167   widget_class->size_allocate = gtk_menu_size_allocate;
168   widget_class->expose_event = gtk_menu_expose;
169   widget_class->key_press_event = gtk_menu_key_press;
170   widget_class->motion_notify_event = gtk_menu_motion_notify;
171   widget_class->show_all = gtk_menu_show_all;
172   widget_class->hide_all = gtk_menu_hide_all;
173   widget_class->enter_notify_event = gtk_menu_enter_notify;
174   widget_class->leave_notify_event = gtk_menu_leave_notify;
175   
176   menu_shell_class->submenu_placement = GTK_LEFT_RIGHT;
177   menu_shell_class->deactivate = gtk_menu_deactivate;
178   menu_shell_class->select_item = gtk_menu_select_item;
179   menu_shell_class->insert = gtk_menu_insert;
180
181   binding_set = gtk_binding_set_by_class (class);
182   gtk_binding_entry_add_signal (binding_set,
183                                 GDK_Up, 0,
184                                 "move_current", 1,
185                                 GTK_TYPE_MENU_DIRECTION_TYPE,
186                                 GTK_MENU_DIR_PREV);
187   gtk_binding_entry_add_signal (binding_set,
188                                 GDK_Down, 0,
189                                 "move_current", 1,
190                                 GTK_TYPE_MENU_DIRECTION_TYPE,
191                                 GTK_MENU_DIR_NEXT);
192   gtk_binding_entry_add_signal (binding_set,
193                                 GDK_Left, 0,
194                                 "move_current", 1,
195                                 GTK_TYPE_MENU_DIRECTION_TYPE,
196                                 GTK_MENU_DIR_PARENT);
197   gtk_binding_entry_add_signal (binding_set,
198                                 GDK_Right, 0,
199                                 "move_current", 1,
200                                 GTK_TYPE_MENU_DIRECTION_TYPE,
201                                 GTK_MENU_DIR_CHILD);
202 }
203
204 static gboolean
205 gtk_menu_window_event (GtkWidget *window,
206                        GdkEvent  *event,
207                        GtkWidget *menu)
208 {
209   gboolean handled = FALSE;
210
211   gtk_widget_ref (window);
212   gtk_widget_ref (menu);
213
214   switch (event->type)
215     {
216     case GDK_KEY_PRESS:
217     case GDK_KEY_RELEASE:
218       handled = gtk_widget_event (menu, event);
219       break;
220     default:
221       break;
222     }
223
224   gtk_widget_unref (window);
225   gtk_widget_unref (menu);
226
227   return handled;
228 }
229
230 static void
231 gtk_menu_init (GtkMenu *menu)
232 {
233   menu->parent_menu_item = NULL;
234   menu->old_active_menu_item = NULL;
235   menu->accel_group = NULL;
236   menu->position_func = NULL;
237   menu->position_func_data = NULL;
238   menu->toggle_size = 0;
239
240   menu->toplevel = gtk_widget_new (GTK_TYPE_WINDOW,
241                                    "type", GTK_WINDOW_POPUP,
242                                    "signal::event", gtk_menu_window_event, menu,
243                                    "signal::destroy", gtk_widget_destroyed, &menu->toplevel,
244                                    "child", menu,
245                                    NULL);
246   gtk_window_set_policy (GTK_WINDOW (menu->toplevel),
247                          FALSE, FALSE, TRUE);
248
249   /* Refloat the menu, so that reference counting for the menu isn't
250    * affected by it being a child of the toplevel
251    */
252   GTK_WIDGET_SET_FLAGS (menu, GTK_FLOATING);
253   menu->needs_destruction_ref_count = TRUE;
254
255   menu->view_window = NULL;
256   menu->bin_window = NULL;
257
258   menu->scroll_offset = 0;
259   menu->scroll_step  = 0;
260   menu->timeout_id = 0;
261   menu->scroll_fast = FALSE;
262   
263   menu->tearoff_window = NULL;
264   menu->tearoff_hbox = NULL;
265   menu->torn_off = FALSE;
266   menu->tearoff_active = FALSE;
267   menu->tearoff_adjustment = NULL;
268   menu->tearoff_scrollbar = NULL;
269
270   menu->upper_arrow_visible = FALSE;
271   menu->lower_arrow_visible = FALSE;
272   menu->upper_arrow_prelight = FALSE;
273   menu->lower_arrow_prelight = FALSE;
274   
275   MENU_NEEDS_RESIZE (menu) = TRUE;
276 }
277
278 static void
279 gtk_menu_destroy (GtkObject *object)
280 {
281   GtkMenu *menu;
282   GtkMenuAttachData *data;
283
284   g_return_if_fail (GTK_IS_MENU (object));
285
286   menu = GTK_MENU (object);
287
288   gtk_menu_stop_scrolling (menu);
289   
290   data = gtk_object_get_data (object, attach_data_key);
291   if (data)
292     gtk_menu_detach (menu);
293   
294   gtk_menu_stop_navigating_submenu (menu);
295
296   gtk_menu_set_accel_group (menu, NULL);
297
298   if (menu->old_active_menu_item)
299     {
300       gtk_widget_unref (menu->old_active_menu_item);
301       menu->old_active_menu_item = NULL;
302     }
303
304   /* Add back the reference count for being a child */
305   if (menu->needs_destruction_ref_count)
306     {
307       menu->needs_destruction_ref_count = FALSE;
308       gtk_object_ref (object);
309     }
310   
311   if (menu->toplevel)
312     gtk_widget_destroy (menu->toplevel);
313   if (menu->tearoff_window)
314     gtk_widget_destroy (menu->tearoff_window);
315
316   GTK_OBJECT_CLASS (parent_class)->destroy (object);
317 }
318
319
320 void
321 gtk_menu_attach_to_widget (GtkMenu             *menu,
322                            GtkWidget           *attach_widget,
323                            GtkMenuDetachFunc    detacher)
324 {
325   GtkMenuAttachData *data;
326   
327   g_return_if_fail (menu != NULL);
328   g_return_if_fail (GTK_IS_MENU (menu));
329   g_return_if_fail (attach_widget != NULL);
330   g_return_if_fail (GTK_IS_WIDGET (attach_widget));
331   g_return_if_fail (detacher != NULL);
332   
333   /* keep this function in sync with gtk_widget_set_parent()
334    */
335   
336   data = gtk_object_get_data (GTK_OBJECT (menu), attach_data_key);
337   if (data)
338     {
339       g_warning ("gtk_menu_attach_to_widget(): menu already attached to %s",
340                  gtk_type_name (GTK_OBJECT_TYPE (data->attach_widget)));
341       return;
342     }
343   
344   gtk_object_ref (GTK_OBJECT (menu));
345   gtk_object_sink (GTK_OBJECT (menu));
346   
347   data = g_new (GtkMenuAttachData, 1);
348   data->attach_widget = attach_widget;
349   data->detacher = detacher;
350   gtk_object_set_data (GTK_OBJECT (menu), attach_data_key, data);
351   
352   if (GTK_WIDGET_STATE (menu) != GTK_STATE_NORMAL)
353     gtk_widget_set_state (GTK_WIDGET (menu), GTK_STATE_NORMAL);
354   
355   /* we don't need to set the style here, since
356    * we are a toplevel widget.
357    */
358 }
359
360 GtkWidget*
361 gtk_menu_get_attach_widget (GtkMenu *menu)
362 {
363   GtkMenuAttachData *data;
364   
365   g_return_val_if_fail (menu != NULL, NULL);
366   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
367   
368   data = gtk_object_get_data (GTK_OBJECT (menu), attach_data_key);
369   if (data)
370     return data->attach_widget;
371   return NULL;
372 }
373
374 void
375 gtk_menu_detach (GtkMenu *menu)
376 {
377   GtkMenuAttachData *data;
378   
379   g_return_if_fail (menu != NULL);
380   g_return_if_fail (GTK_IS_MENU (menu));
381   
382   /* keep this function in sync with gtk_widget_unparent()
383    */
384   data = gtk_object_get_data (GTK_OBJECT (menu), attach_data_key);
385   if (!data)
386     {
387       g_warning ("gtk_menu_detach(): menu is not attached");
388       return;
389     }
390   gtk_object_remove_data (GTK_OBJECT (menu), attach_data_key);
391   
392   data->detacher (data->attach_widget, menu);
393   
394   if (GTK_WIDGET_REALIZED (menu))
395     gtk_widget_unrealize (GTK_WIDGET (menu));
396   
397   g_free (data);
398   
399   gtk_widget_unref (GTK_WIDGET (menu));
400 }
401
402 GtkWidget*
403 gtk_menu_new (void)
404 {
405   return GTK_WIDGET (gtk_type_new (gtk_menu_get_type ()));
406 }
407
408 static void
409 gtk_menu_insert (GtkMenuShell     *menu_shell,
410                  GtkWidget        *child,
411                  gint              position)
412 {
413   GTK_MENU_SHELL_CLASS (parent_class)->insert (menu_shell, child, position);
414   
415   gtk_widget_set_parent_window (child, GTK_MENU (menu_shell)->bin_window);
416 }
417
418 static void
419 gtk_menu_tearoff_bg_copy (GtkMenu *menu)
420 {
421   GtkWidget *widget;
422   gint width, height;
423
424   widget = GTK_WIDGET (menu);
425
426   if (menu->torn_off)
427     {
428       GdkPixmap *pixmap;
429       GdkGC *gc;
430       GdkGCValues gc_values;
431
432       menu->tearoff_active = FALSE;
433       menu->saved_scroll_offset = menu->scroll_offset;
434       
435       gc_values.subwindow_mode = GDK_INCLUDE_INFERIORS;
436       gc = gdk_gc_new_with_values (widget->window,
437                                    &gc_values, GDK_GC_SUBWINDOW);
438       
439       gdk_window_get_size (menu->tearoff_window->window, &width, &height);
440       
441       pixmap = gdk_pixmap_new (menu->tearoff_window->window,
442                                width,
443                                height,
444                                -1);
445
446       gdk_draw_pixmap (pixmap, gc,
447                        menu->tearoff_window->window,
448                        0, 0, 0, 0, -1, -1);
449       gdk_gc_unref (gc);
450
451       gtk_widget_set_usize (menu->tearoff_window,
452                             width,
453                             height);
454
455       gdk_window_set_back_pixmap (menu->tearoff_window->window, pixmap, FALSE);
456       gdk_pixmap_unref (pixmap);
457     }
458 }
459
460 void
461 gtk_menu_popup (GtkMenu             *menu,
462                 GtkWidget           *parent_menu_shell,
463                 GtkWidget           *parent_menu_item,
464                 GtkMenuPositionFunc  func,
465                 gpointer             data,
466                 guint                button,
467                 guint32              activate_time)
468 {
469   GtkWidget *widget;
470   GtkWidget *xgrab_shell;
471   GtkWidget *parent;
472   GdkEvent *current_event;
473   GtkMenuShell *menu_shell;
474
475   g_return_if_fail (menu != NULL);
476   g_return_if_fail (GTK_IS_MENU (menu));
477   
478   widget = GTK_WIDGET (menu);
479   menu_shell = GTK_MENU_SHELL (menu);
480   
481   menu_shell->parent_menu_shell = parent_menu_shell;
482   menu_shell->active = TRUE;
483   menu_shell->button = button;
484
485   /* If we are popping up the menu from something other than, a button
486    * press then, as a heuristic, we ignore enter events for the menu
487    * until we get a MOTION_NOTIFY.  
488    */
489
490   current_event = gtk_get_current_event();
491   if (current_event)
492     {
493       if ((current_event->type != GDK_BUTTON_PRESS) &&
494           (current_event->type != GDK_ENTER_NOTIFY))
495         menu_shell->ignore_enter = TRUE;
496       gdk_event_free (current_event);
497     }
498
499   if (menu->torn_off)
500     {
501       gtk_menu_tearoff_bg_copy (menu);
502
503       gtk_menu_reparent (menu, menu->toplevel, FALSE);
504     }
505   
506   menu->parent_menu_item = parent_menu_item;
507   menu->position_func = func;
508   menu->position_func_data = data;
509   menu_shell->activate_time = activate_time;
510
511   gtk_menu_position (menu);
512
513   /* We need to show the menu _here_ because code expects to be
514    * able to tell if the menu is onscreen by looking at the
515    * GTK_WIDGET_VISIBLE (menu)
516    */
517   gtk_widget_show (GTK_WIDGET (menu));
518   gtk_widget_show (menu->toplevel);
519
520   /* Find the last viewable ancestor, and make an X grab on it
521    */
522   parent = GTK_WIDGET (menu);
523   xgrab_shell = NULL;
524   while (parent)
525     {
526       gboolean viewable = TRUE;
527       GtkWidget *tmp = parent;
528       
529       while (tmp)
530         {
531           if (!GTK_WIDGET_MAPPED (tmp))
532             {
533               viewable = FALSE;
534               break;
535             }
536           tmp = tmp->parent;
537         }
538       
539       if (viewable)
540         xgrab_shell = parent;
541       
542       parent = GTK_MENU_SHELL (parent)->parent_menu_shell;
543     }
544   
545   if (xgrab_shell && (!GTK_MENU_SHELL (xgrab_shell)->have_xgrab))
546     {
547       GdkCursor *cursor = gdk_cursor_new (GDK_ARROW);
548
549       if ((gdk_pointer_grab (xgrab_shell->window, TRUE,
550                              GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
551                              GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
552                              GDK_POINTER_MOTION_MASK,
553                              NULL, cursor, activate_time) == 0))
554         {
555           if (gdk_keyboard_grab (xgrab_shell->window, TRUE,
556                               activate_time) == 0)
557             GTK_MENU_SHELL (xgrab_shell)->have_xgrab = TRUE;
558           else
559             {
560               gdk_pointer_ungrab (activate_time);
561             }
562         }
563
564       gdk_cursor_destroy (cursor);
565     }
566
567   gtk_menu_scroll_to (menu, menu->scroll_offset);
568
569   gtk_grab_add (GTK_WIDGET (menu));
570 }
571
572 void
573 gtk_menu_popdown (GtkMenu *menu)
574 {
575   GtkMenuShell *menu_shell;
576
577   g_return_if_fail (menu != NULL);
578   g_return_if_fail (GTK_IS_MENU (menu));
579   
580   menu_shell = GTK_MENU_SHELL (menu);
581   
582   menu_shell->parent_menu_shell = NULL;
583   menu_shell->active = FALSE;
584   menu_shell->ignore_enter = FALSE;
585
586   gtk_menu_stop_scrolling (menu);
587   
588   gtk_menu_stop_navigating_submenu (menu);
589   
590   if (menu_shell->active_menu_item)
591     {
592       if (menu->old_active_menu_item)
593         gtk_widget_unref (menu->old_active_menu_item);
594       menu->old_active_menu_item = menu_shell->active_menu_item;
595       gtk_widget_ref (menu->old_active_menu_item);
596     }
597
598   gtk_menu_shell_deselect (menu_shell);
599   
600   /* The X Grab, if present, will automatically be removed when we hide
601    * the window */
602   gtk_widget_hide (menu->toplevel);
603
604   if (menu->torn_off)
605     {
606       gint width, height;
607       gdk_window_get_size (menu->tearoff_window->window, &width, &height);
608       gtk_widget_set_usize (menu->tearoff_window,
609                             -1,
610                             height);
611       
612       if (GTK_BIN (menu->toplevel)->child) 
613         {
614           gtk_menu_reparent (menu, menu->tearoff_hbox, TRUE);
615         } 
616       else
617         {
618           /* We popped up the menu from the tearoff, so we need to 
619            * release the grab - we aren't actually hiding the menu.
620            */
621           if (menu_shell->have_xgrab)
622             {
623               gdk_pointer_ungrab (GDK_CURRENT_TIME);
624               gdk_keyboard_ungrab (GDK_CURRENT_TIME);
625             }
626         }
627
628       /* gtk_menu_popdown is called each time a menu item is selected from
629        * a torn off menu. Only scroll back to the saved position if the
630        * non-tearoff menu was popped down.
631        */
632       if (!menu->tearoff_active)
633         gtk_menu_scroll_to (menu, menu->saved_scroll_offset);
634       menu->tearoff_active = TRUE;
635     }
636   else
637     gtk_widget_hide (GTK_WIDGET (menu));
638
639   menu_shell->have_xgrab = FALSE;
640   gtk_grab_remove (GTK_WIDGET (menu));
641 }
642
643 GtkWidget*
644 gtk_menu_get_active (GtkMenu *menu)
645 {
646   GtkWidget *child;
647   GList *children;
648   
649   g_return_val_if_fail (menu != NULL, NULL);
650   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
651   
652   if (!menu->old_active_menu_item)
653     {
654       child = NULL;
655       children = GTK_MENU_SHELL (menu)->children;
656       
657       while (children)
658         {
659           child = children->data;
660           children = children->next;
661           
662           if (GTK_BIN (child)->child)
663             break;
664           child = NULL;
665         }
666       
667       menu->old_active_menu_item = child;
668       if (menu->old_active_menu_item)
669         gtk_widget_ref (menu->old_active_menu_item);
670     }
671   
672   return menu->old_active_menu_item;
673 }
674
675 void
676 gtk_menu_set_active (GtkMenu *menu,
677                      guint    index)
678 {
679   GtkWidget *child;
680   GList *tmp_list;
681   
682   g_return_if_fail (menu != NULL);
683   g_return_if_fail (GTK_IS_MENU (menu));
684   
685   tmp_list = g_list_nth (GTK_MENU_SHELL (menu)->children, index);
686   if (tmp_list)
687     {
688       child = tmp_list->data;
689       if (GTK_BIN (child)->child)
690         {
691           if (menu->old_active_menu_item)
692             gtk_widget_unref (menu->old_active_menu_item);
693           menu->old_active_menu_item = child;
694           gtk_widget_ref (menu->old_active_menu_item);
695         }
696     }
697 }
698
699 void
700 gtk_menu_set_accel_group (GtkMenu       *menu,
701                           GtkAccelGroup *accel_group)
702 {
703   g_return_if_fail (GTK_IS_MENU (menu));
704   
705   if (menu->accel_group != accel_group)
706     {
707       if (menu->accel_group)
708         gtk_accel_group_unref (menu->accel_group);
709       menu->accel_group = accel_group;
710       if (menu->accel_group)
711         gtk_accel_group_ref (menu->accel_group);
712     }
713 }
714
715 GtkAccelGroup*
716 gtk_menu_get_accel_group (GtkMenu *menu)
717 {
718   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
719
720   return menu->accel_group;
721 }
722
723 GtkAccelGroup*
724 gtk_menu_ensure_uline_accel_group (GtkMenu *menu)
725 {
726   GtkAccelGroup *accel_group;
727
728   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
729
730   if (!quark_uline_accel_group)
731     quark_uline_accel_group = g_quark_from_static_string ("GtkMenu-uline-accel-group");
732
733   accel_group = gtk_object_get_data_by_id (GTK_OBJECT (menu), quark_uline_accel_group);
734   if (!accel_group)
735     {
736       accel_group = gtk_accel_group_new ();
737       gtk_accel_group_attach (accel_group, GTK_OBJECT (menu));
738       gtk_object_set_data_by_id_full (GTK_OBJECT (menu),
739                                       quark_uline_accel_group,
740                                       accel_group,
741                                       (GtkDestroyNotify) gtk_accel_group_unref);
742     }
743
744   return accel_group;
745 }
746
747 GtkAccelGroup*
748 gtk_menu_get_uline_accel_group (GtkMenu *menu)
749 {
750   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
751
752   return gtk_object_get_data_by_id (GTK_OBJECT (menu), quark_uline_accel_group);
753 }
754
755 void
756 gtk_menu_reposition (GtkMenu *menu)
757 {
758   g_return_if_fail (menu != NULL);
759   g_return_if_fail (GTK_IS_MENU (menu));
760
761   if (GTK_WIDGET_DRAWABLE (menu) && !menu->torn_off)
762     gtk_menu_position (menu);
763 }
764
765 static void
766 gtk_menu_scrollbar_changed (GtkAdjustment *adjustment,
767                             GtkMenu       *menu)
768 {
769   g_return_if_fail (menu != NULL);
770   g_return_if_fail (GTK_IS_MENU (menu));
771
772   if (adjustment->value != menu->scroll_offset)
773     gtk_menu_scroll_to (menu, adjustment->value);
774 }
775
776 static void
777 gtk_menu_set_tearoff_hints(GtkMenu *menu,
778                            gint     width)
779 {
780   GdkGeometry geometry_hints;
781   
782   if (!menu->tearoff_window)
783     return;
784
785   geometry_hints.min_width = width;
786   geometry_hints.max_width = width;
787     
788   geometry_hints.min_height = 0;
789   geometry_hints.max_height = GTK_WIDGET (menu)->requisition.height;
790   gtk_window_set_geometry_hints (GTK_WINDOW (menu->tearoff_window),
791                                  NULL,
792                                  &geometry_hints,
793                                  GDK_HINT_MAX_SIZE|GDK_HINT_MIN_SIZE);
794 }
795
796
797 void       
798 gtk_menu_set_tearoff_state (GtkMenu  *menu,
799                             gboolean  torn_off)
800 {
801   gint width, height;
802   
803   g_return_if_fail (menu != NULL);
804   g_return_if_fail (GTK_IS_MENU (menu));
805
806   if (menu->torn_off != torn_off)
807     {
808       menu->torn_off = torn_off;
809       menu->tearoff_active = torn_off;
810       
811       if (menu->torn_off)
812         {
813           if (GTK_WIDGET_VISIBLE (menu))
814             gtk_menu_popdown (menu);
815
816           if (!menu->tearoff_window)
817             {
818               GtkWidget *attach_widget;
819               gchar *title;
820               
821               menu->tearoff_window = gtk_widget_new (GTK_TYPE_WINDOW,
822                                                      "type", GTK_WINDOW_TOPLEVEL,
823                                                      "signal::destroy", gtk_widget_destroyed, &menu->tearoff_window,
824                                                      NULL);
825               gtk_widget_set_app_paintable (menu->tearoff_window, TRUE);
826               gtk_signal_connect (GTK_OBJECT (menu->tearoff_window),  
827                                   "event",
828                                   GTK_SIGNAL_FUNC (gtk_menu_window_event), 
829                                   GTK_OBJECT (menu));
830               gtk_widget_realize (menu->tearoff_window);
831               
832               title = gtk_object_get_data (GTK_OBJECT (menu), "gtk-menu-title");
833               if (!title)
834                 {
835                   attach_widget = gtk_menu_get_attach_widget (menu);
836                   if (GTK_IS_MENU_ITEM (attach_widget))
837                     {
838                       GtkWidget *child = GTK_BIN (attach_widget)->child;
839                       if (GTK_IS_LABEL (child))
840                         gtk_label_get (GTK_LABEL (child), &title);
841                     }
842                 }
843
844               if (title)
845                 gdk_window_set_title (menu->tearoff_window->window, title);
846
847               gdk_window_set_decorations (menu->tearoff_window->window, 
848                                           GDK_DECOR_ALL |
849                                           GDK_DECOR_RESIZEH |
850                                           GDK_DECOR_MINIMIZE |
851                                           GDK_DECOR_MAXIMIZE);
852               gtk_window_set_policy (GTK_WINDOW (menu->tearoff_window),
853                                      FALSE, FALSE, TRUE);
854
855               menu->tearoff_hbox = gtk_hbox_new (FALSE, FALSE);
856               gtk_container_add (GTK_CONTAINER (menu->tearoff_window), menu->tearoff_hbox);
857
858               gdk_window_get_size (GTK_WIDGET (menu)->window, &width, &height);
859               menu->tearoff_adjustment =
860                 GTK_ADJUSTMENT (gtk_adjustment_new (0,
861                                                     0,
862                                                     GTK_WIDGET (menu)->requisition.height,
863                                                     MENU_SCROLL_STEP,
864                                                     height/2,
865                                                     height));
866               gtk_signal_connect (GTK_OBJECT (menu->tearoff_adjustment), "value_changed",
867                                   gtk_menu_scrollbar_changed,
868                                   menu);
869               menu->tearoff_scrollbar = gtk_vscrollbar_new (menu->tearoff_adjustment);
870
871               gtk_box_pack_end (GTK_BOX (menu->tearoff_hbox),
872                                 menu->tearoff_scrollbar,
873                                 FALSE, FALSE, 0);
874               
875               if (menu->tearoff_adjustment->upper > height)
876                 gtk_widget_show (menu->tearoff_scrollbar);
877               
878               gtk_widget_show (menu->tearoff_hbox);
879             }
880           
881           gtk_menu_reparent (menu, menu->tearoff_hbox, FALSE);
882           
883           gdk_window_get_size (GTK_WIDGET (menu)->window, &width, &height);
884           if (GTK_WIDGET_VISIBLE (menu->tearoff_scrollbar))
885             width += menu->tearoff_scrollbar->requisition.width;
886             
887           gtk_menu_set_tearoff_hints (menu, width);
888             
889           gtk_widget_realize (menu->tearoff_window);
890           gtk_menu_position (menu);
891           
892           gtk_widget_show (GTK_WIDGET (menu));
893           gtk_widget_show (menu->tearoff_window);
894
895           gtk_menu_scroll_to (menu, 0);
896
897         }
898       else
899         {
900           gtk_widget_hide (menu->tearoff_window);
901           gtk_menu_reparent (menu, menu->toplevel, FALSE);
902         }
903     }
904 }
905
906 void       
907 gtk_menu_set_title (GtkMenu     *menu,
908                     const gchar *title)
909 {
910   g_return_if_fail (menu != NULL);
911   g_return_if_fail (GTK_IS_MENU (menu));
912
913   gtk_object_set_data_full (GTK_OBJECT (menu), "gtk-menu-title",
914                             g_strdup (title), (GtkDestroyNotify) g_free);
915 }
916
917 void
918 gtk_menu_reorder_child (GtkMenu   *menu,
919                         GtkWidget *child,
920                         gint       position)
921 {
922   GtkMenuShell *menu_shell;
923   g_return_if_fail (GTK_IS_MENU (menu));
924   g_return_if_fail (GTK_IS_MENU_ITEM (child));
925   menu_shell = GTK_MENU_SHELL (menu);
926   if (g_list_find (menu_shell->children, child))
927     {   
928       menu_shell->children = g_list_remove (menu_shell->children, child);
929       menu_shell->children = g_list_insert (menu_shell->children, child, position);   
930       if (GTK_WIDGET_VISIBLE (menu_shell))
931         gtk_widget_queue_resize (GTK_WIDGET (menu_shell));
932     }   
933 }
934
935 static void
936 gtk_menu_realize (GtkWidget *widget)
937 {
938   GdkWindowAttr attributes;
939   gint attributes_mask;
940   gint border_width;
941   GtkMenu *menu;
942   GtkWidget *child;
943   GList *children;
944
945   g_return_if_fail (widget != NULL);
946   g_return_if_fail (GTK_IS_MENU (widget));
947
948   menu = GTK_MENU (widget);
949   
950   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
951   
952   attributes.window_type = GDK_WINDOW_CHILD;
953   attributes.x = widget->allocation.x;
954   attributes.y = widget->allocation.y;
955   attributes.width = widget->allocation.width;
956   attributes.height = widget->allocation.height;
957   attributes.wclass = GDK_INPUT_OUTPUT;
958   attributes.visual = gtk_widget_get_visual (widget);
959   attributes.colormap = gtk_widget_get_colormap (widget);
960   
961   attributes.event_mask = gtk_widget_get_events (widget);
962   attributes.event_mask |= (GDK_EXPOSURE_MASK | GDK_KEY_PRESS_MASK |
963                             GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK );
964   
965   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
966   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
967   gdk_window_set_user_data (widget->window, widget);
968   
969   border_width = GTK_CONTAINER (widget)->border_width;
970   
971   attributes.x = border_width + widget->style->xthickness;
972   attributes.y = border_width + widget->style->ythickness;
973   attributes.width = MAX (1, widget->allocation.width - attributes.x * 2);
974   attributes.height = MAX (1, widget->allocation.height - attributes.y * 2);
975
976   if (menu->upper_arrow_visible)
977     {
978       attributes.y += MENU_SCROLL_ARROW_HEIGHT;
979       attributes.height -= MENU_SCROLL_ARROW_HEIGHT;
980     }
981   if (menu->lower_arrow_visible)
982     attributes.height -= MENU_SCROLL_ARROW_HEIGHT;
983
984   menu->view_window = gdk_window_new (widget->window, &attributes, attributes_mask);
985   gdk_window_set_user_data (menu->view_window, menu);
986
987   attributes.x = 0;
988   attributes.y = 0;
989   attributes.height = MAX (1, widget->requisition.height - (border_width + widget->style->ythickness) * 2);
990   
991   menu->bin_window = gdk_window_new (menu->view_window, &attributes, attributes_mask);
992   gdk_window_set_user_data (menu->bin_window, menu);
993
994   children = GTK_MENU_SHELL (menu)->children;
995   while (children)
996     {
997       child = children->data;
998       children = children->next;
999           
1000       gtk_widget_set_parent_window (child, menu->bin_window);
1001     }
1002   
1003   widget->style = gtk_style_attach (widget->style, widget->window);
1004   gtk_style_set_background (widget->style, menu->bin_window, GTK_STATE_NORMAL);
1005   gtk_style_set_background (widget->style, menu->view_window, GTK_STATE_NORMAL);
1006   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
1007
1008   gtk_menu_paint (widget);
1009   
1010   gdk_window_show (menu->bin_window);
1011   gdk_window_show (menu->view_window);
1012 }
1013
1014 static void
1015 gtk_menu_unrealize (GtkWidget *widget)
1016 {
1017   GtkMenu *menu;
1018
1019   g_return_if_fail (widget != NULL);
1020   g_return_if_fail (GTK_IS_MENU (widget));
1021
1022   menu = GTK_MENU (widget);
1023
1024   gdk_window_set_user_data (menu->view_window, NULL);
1025   gdk_window_destroy (menu->view_window);
1026   menu->view_window = NULL;
1027
1028   gdk_window_set_user_data (menu->bin_window, NULL);
1029   gdk_window_destroy (menu->bin_window);
1030   menu->bin_window = NULL;
1031
1032   (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
1033 }
1034
1035 static void
1036 gtk_menu_size_request (GtkWidget      *widget,
1037                        GtkRequisition *requisition)
1038 {
1039   GtkMenu *menu;
1040   GtkMenuShell *menu_shell;
1041   GtkWidget *child;
1042   GList *children;
1043   guint max_toggle_size;
1044   guint max_accel_width;
1045   GtkRequisition child_requisition;
1046   gint width;
1047   
1048   g_return_if_fail (widget != NULL);
1049   g_return_if_fail (GTK_IS_MENU (widget));
1050   g_return_if_fail (requisition != NULL);
1051   
1052   menu = GTK_MENU (widget);
1053   menu_shell = GTK_MENU_SHELL (widget);
1054   
1055   requisition->width = 0;
1056   requisition->height = 0;
1057   
1058   max_toggle_size = 0;
1059   max_accel_width = 0;
1060   
1061   children = menu_shell->children;
1062   while (children)
1063     {
1064       child = children->data;
1065       children = children->next;
1066       
1067       if (GTK_WIDGET_VISIBLE (child))
1068         {
1069           guint16 toggle_size;
1070
1071           GTK_MENU_ITEM (child)->show_submenu_indicator = TRUE;
1072           gtk_widget_size_request (child, &child_requisition);
1073           
1074           requisition->width = MAX (requisition->width, child_requisition.width);
1075           requisition->height += child_requisition.height;
1076
1077           gtk_menu_item_toggle_size_request (GTK_MENU_ITEM (child), &toggle_size);
1078           max_toggle_size = MAX (max_toggle_size, toggle_size);
1079           max_accel_width = MAX (max_accel_width, GTK_MENU_ITEM (child)->accelerator_width);
1080         }
1081     }
1082   
1083   requisition->width += max_toggle_size + max_accel_width;
1084   requisition->width += (GTK_CONTAINER (menu)->border_width +
1085                          widget->style->xthickness) * 2;
1086   requisition->height += (GTK_CONTAINER (menu)->border_width +
1087                           widget->style->ythickness) * 2;
1088   
1089   menu->toggle_size = max_toggle_size;
1090
1091   /* If the requested width was different than the allocated width, we need to change
1092    * the geometry hints for the tear off window so that the window can actually be resized.
1093    * Don't resize the tearoff if it is not active, beacuse it won't redraw (it is only a background pixmap).
1094    */
1095   if ((requisition->width != GTK_WIDGET (menu)->allocation.width) && menu->tearoff_active)
1096     {
1097       width = requisition->width;
1098       if (menu->tearoff_scrollbar && GTK_WIDGET_VISIBLE (menu->tearoff_scrollbar))
1099         width += menu->tearoff_scrollbar->requisition.width;
1100       gtk_menu_set_tearoff_hints (menu, width);
1101     }
1102 }
1103
1104 static void
1105 gtk_menu_size_allocate (GtkWidget     *widget,
1106                         GtkAllocation *allocation)
1107 {
1108   GtkMenu *menu;
1109   GtkMenuShell *menu_shell;
1110   GtkWidget *child;
1111   GtkAllocation child_allocation;
1112   GList *children;
1113   gint x, y;
1114   gint width, height;
1115
1116   g_return_if_fail (widget != NULL);
1117   g_return_if_fail (GTK_IS_MENU (widget));
1118   g_return_if_fail (allocation != NULL);
1119   
1120   menu = GTK_MENU (widget);
1121   menu_shell = GTK_MENU_SHELL (widget);
1122
1123   widget->allocation = *allocation;
1124
1125   x = GTK_CONTAINER (menu)->border_width + widget->style->xthickness;
1126   y = GTK_CONTAINER (menu)->border_width + widget->style->ythickness;
1127   
1128   width = MAX (1, allocation->width - x * 2);
1129   height = MAX (1, allocation->height - y * 2);
1130   
1131   if (menu->upper_arrow_visible && !menu->tearoff_active)
1132     {
1133       y += MENU_SCROLL_ARROW_HEIGHT;
1134       height -= MENU_SCROLL_ARROW_HEIGHT;
1135     }
1136   
1137   if (menu->lower_arrow_visible && !menu->tearoff_active)
1138     height -= MENU_SCROLL_ARROW_HEIGHT;
1139   
1140   if (GTK_WIDGET_REALIZED (widget))
1141     {
1142       gdk_window_move_resize (widget->window,
1143                               allocation->x, allocation->y,
1144                               allocation->width, allocation->height);
1145
1146       gdk_window_move_resize (menu->view_window,
1147                               x,
1148                               y,
1149                               width,
1150                               height);
1151     }
1152
1153   if (menu_shell->children)
1154     {
1155       child_allocation.x = 0;
1156       child_allocation.y = 0;
1157       child_allocation.width = width;
1158       
1159       children = menu_shell->children;
1160       while (children)
1161         {
1162           child = children->data;
1163           children = children->next;
1164           
1165           if (GTK_WIDGET_VISIBLE (child))
1166             {
1167               GtkRequisition child_requisition;
1168               gtk_widget_get_child_requisition (child, &child_requisition);
1169               
1170               child_allocation.height = child_requisition.height;
1171
1172               gtk_menu_item_toggle_size_allocate (GTK_MENU_ITEM (child),
1173                                                   menu->toggle_size);
1174               gtk_widget_size_allocate (child, &child_allocation);
1175               gtk_widget_queue_draw (child);
1176               
1177               child_allocation.y += child_allocation.height;
1178             }
1179         }
1180       
1181       /* Resize the item window */
1182       if (GTK_WIDGET_REALIZED (widget))
1183         {
1184           gdk_window_resize (menu->bin_window,
1185                              child_allocation.width,
1186                              child_allocation.y);
1187         }
1188
1189
1190       if (menu->tearoff_active)
1191         {
1192           if (allocation->height >= widget->requisition.height)
1193             {
1194               if (GTK_WIDGET_VISIBLE (menu->tearoff_scrollbar))
1195                 {
1196                   gtk_widget_hide (menu->tearoff_scrollbar);
1197                   gtk_menu_set_tearoff_hints (menu, allocation->width);
1198                   gtk_widget_set_usize (menu->tearoff_window, -1, allocation->height);
1199
1200                   gtk_menu_scroll_to (menu, 0);
1201                 }
1202             }
1203           else
1204             {
1205               menu->tearoff_adjustment->upper = widget->requisition.height;
1206               menu->tearoff_adjustment->page_size = allocation->height;
1207               
1208               if (menu->tearoff_adjustment->value + menu->tearoff_adjustment->page_size >
1209                   menu->tearoff_adjustment->upper)
1210                 {
1211                   gint value;
1212                   value = menu->tearoff_adjustment->upper - menu->tearoff_adjustment->page_size;
1213                   if (value < 0)
1214                     value = 0;
1215                   gtk_menu_scroll_to (menu, value);
1216                 }
1217               
1218               gtk_adjustment_changed (menu->tearoff_adjustment);
1219               
1220               if (!GTK_WIDGET_VISIBLE (menu->tearoff_scrollbar))
1221                 {
1222                   gtk_menu_set_tearoff_hints (menu,
1223                                               allocation->width + menu->tearoff_scrollbar->requisition.width);
1224                   gtk_widget_show (menu->tearoff_scrollbar);
1225                   gtk_widget_set_usize (menu->tearoff_window, -1, allocation->height);
1226                 }
1227             }
1228         }
1229     }
1230 }
1231
1232 static void
1233 gtk_menu_paint (GtkWidget *widget)
1234 {
1235   gint border_x;
1236   gint border_y;
1237   gint width, height;
1238   gint menu_height;
1239   gint top_pos;
1240   GtkMenu *menu;
1241   
1242   g_return_if_fail (widget != NULL);
1243   g_return_if_fail (GTK_IS_MENU (widget));
1244
1245   menu = GTK_MENU (widget);
1246   
1247   if (GTK_WIDGET_DRAWABLE (widget))
1248     {
1249       gtk_paint_box (widget->style,
1250                      widget->window,
1251                      GTK_STATE_NORMAL,
1252                      GTK_SHADOW_OUT,
1253                      NULL, widget, "menu",
1254                      0, 0, -1, -1);
1255
1256       border_x = GTK_CONTAINER (widget)->border_width + widget->style->xthickness;
1257       border_y = GTK_CONTAINER (widget)->border_width + widget->style->ythickness;
1258       gdk_window_get_size (widget->window, &width, &height);
1259
1260       if (menu->upper_arrow_visible && !menu->tearoff_active)
1261         {
1262           gtk_paint_box (widget->style,
1263                          widget->window,
1264                          menu->upper_arrow_prelight ?
1265                          GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
1266                          GTK_SHADOW_OUT,
1267                          NULL, widget, "menu",
1268                          border_x,
1269                          border_y,
1270                          width - 2*border_x,
1271                          MENU_SCROLL_ARROW_HEIGHT);
1272           
1273           gtk_paint_arrow (widget->style,
1274                            widget->window,
1275                            menu->upper_arrow_prelight ?
1276                            GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
1277                            GTK_SHADOW_OUT,
1278                            NULL, widget, "menu",
1279                            GTK_ARROW_UP,
1280                            TRUE,
1281                            width / 2 - MENU_SCROLL_ARROW_HEIGHT / 2 + 1,
1282                            2 * border_y + 1,
1283                            MENU_SCROLL_ARROW_HEIGHT - 2 * border_y - 2,
1284                            MENU_SCROLL_ARROW_HEIGHT - 2 * border_y - 2);
1285         }
1286   
1287       if (menu->lower_arrow_visible && !menu->tearoff_active)
1288         {
1289           gtk_paint_box (widget->style,
1290                          widget->window,
1291                          menu->lower_arrow_prelight ?
1292                          GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
1293                          GTK_SHADOW_OUT,
1294                          NULL, widget, "menu",
1295                          border_x,
1296                          height - border_y - MENU_SCROLL_ARROW_HEIGHT + 1,
1297                          width - 2*border_x,
1298                          MENU_SCROLL_ARROW_HEIGHT);
1299           
1300           gtk_paint_arrow (widget->style,
1301                            widget->window,
1302                            menu->lower_arrow_prelight ?
1303                            GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
1304                            GTK_SHADOW_OUT,
1305                            NULL, widget, "menu",
1306                            GTK_ARROW_DOWN,
1307                            TRUE,
1308                            width / 2 - MENU_SCROLL_ARROW_HEIGHT / 2 + 1,
1309                            height - MENU_SCROLL_ARROW_HEIGHT + 1,
1310                            MENU_SCROLL_ARROW_HEIGHT - 2 * border_y - 2,
1311                            MENU_SCROLL_ARROW_HEIGHT - 2 * border_y - 2);
1312         }
1313
1314       if (menu->scroll_offset < 0)
1315         gtk_paint_box (widget->style,
1316                        menu->view_window,
1317                        GTK_STATE_ACTIVE,
1318                        GTK_SHADOW_IN,
1319                        NULL, widget, "menu",
1320                        0, 0,
1321                        -1,
1322                        -menu->scroll_offset);
1323
1324       menu_height = widget->requisition.height - 2*border_y;
1325       top_pos = height - 2*border_y - (menu->upper_arrow_visible ? MENU_SCROLL_ARROW_HEIGHT : 0);
1326
1327       if (menu_height - menu->scroll_offset < top_pos)
1328         gtk_paint_box (widget->style,
1329                        menu->view_window,
1330                        GTK_STATE_ACTIVE,
1331                        GTK_SHADOW_IN,
1332                        NULL, widget, "menu",
1333                        0,
1334                        menu_height - menu->scroll_offset,
1335                        -1,
1336                        top_pos - (menu_height - menu->scroll_offset));
1337     }
1338 }
1339
1340 static void
1341 gtk_menu_draw (GtkWidget    *widget,
1342                GdkRectangle *area)
1343 {
1344   GtkMenuShell *menu_shell;
1345   GtkWidget *child;
1346   GdkRectangle child_area;
1347   GList *children;
1348   
1349   g_return_if_fail (widget != NULL);
1350   g_return_if_fail (GTK_IS_MENU (widget));
1351   g_return_if_fail (area != NULL);
1352   
1353   if (GTK_WIDGET_DRAWABLE (widget))
1354     {
1355       gtk_menu_paint (widget);
1356       
1357       menu_shell = GTK_MENU_SHELL (widget);
1358       
1359       children = menu_shell->children;
1360       while (children)
1361         {
1362           child = children->data;
1363           children = children->next;
1364           
1365           if (gtk_widget_intersect (child, area, &child_area))
1366             gtk_widget_draw (child, &child_area);
1367         }
1368     }
1369 }
1370
1371 static gboolean
1372 gtk_menu_expose (GtkWidget      *widget,
1373                  GdkEventExpose *event)
1374 {
1375   GtkMenuShell *menu_shell;
1376   GtkWidget *child;
1377   GdkEventExpose child_event;
1378   GList *children;
1379   GtkMenu *menu;
1380   
1381   g_return_val_if_fail (widget != NULL, FALSE);
1382   g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
1383   g_return_val_if_fail (event != NULL, FALSE);
1384
1385   menu_shell = GTK_MENU_SHELL (widget);
1386   menu = GTK_MENU (widget);
1387   
1388   if (GTK_WIDGET_DRAWABLE (widget))
1389     {
1390       gtk_menu_paint (widget);
1391       
1392       child_event = *event;
1393       
1394       children = menu_shell->children;
1395       while (children)
1396         {
1397           child = children->data;
1398           children = children->next;
1399           
1400           if (GTK_WIDGET_NO_WINDOW (child) &&
1401               gtk_widget_intersect (child, &event->area, &child_event.area))
1402             gtk_widget_event (child, (GdkEvent*) &child_event);
1403         }
1404     }
1405   
1406   return FALSE;
1407 }
1408
1409 static gboolean
1410 gtk_menu_key_press (GtkWidget   *widget,
1411                     GdkEventKey *event)
1412 {
1413   GtkMenuShell *menu_shell;
1414   gboolean delete = FALSE;
1415
1416   g_return_val_if_fail (widget != NULL, FALSE);
1417   g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
1418   g_return_val_if_fail (event != NULL, FALSE);
1419       
1420   menu_shell = GTK_MENU_SHELL (widget);
1421
1422   gtk_menu_stop_navigating_submenu (GTK_MENU (widget));
1423
1424   if (GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event))
1425     return TRUE;
1426
1427   switch (event->keyval)
1428     {
1429     case GDK_Delete:
1430     case GDK_KP_Delete:
1431     case GDK_BackSpace:
1432       delete = TRUE;
1433       break;
1434     default:
1435       break;
1436     }
1437
1438   /* Modify the accelerators */
1439   if (menu_shell->active_menu_item &&
1440       GTK_BIN (menu_shell->active_menu_item)->child &&
1441       GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu == NULL &&
1442       !gtk_widget_accelerators_locked (menu_shell->active_menu_item) &&
1443       (delete ||
1444        (gtk_accelerator_valid (event->keyval, event->state) &&
1445         (event->state ||
1446          !gtk_menu_get_uline_accel_group (GTK_MENU (menu_shell)) ||
1447          (event->keyval >= GDK_F1 && event->keyval <= GDK_F35)))))
1448     {
1449       GtkMenuItem *menu_item;
1450       GtkAccelGroup *accel_group;
1451       
1452       menu_item = GTK_MENU_ITEM (menu_shell->active_menu_item);
1453       
1454       if (!GTK_MENU (widget)->accel_group)
1455         accel_group = gtk_accel_group_get_default ();
1456       else
1457         accel_group = GTK_MENU (widget)->accel_group;
1458       
1459       gtk_widget_remove_accelerators (GTK_WIDGET (menu_item),
1460                                       gtk_signal_name (menu_item->accelerator_signal),
1461                                       TRUE);
1462       
1463       if (!delete &&
1464           0 == gtk_widget_accelerator_signal (GTK_WIDGET (menu_item),
1465                                               accel_group,
1466                                               event->keyval,
1467                                               event->state))
1468         {
1469           GSList *slist;
1470           
1471           slist = gtk_accel_group_entries_from_object (GTK_OBJECT (menu_item));
1472           while (slist)
1473             {
1474               GtkAccelEntry *ac_entry;
1475               
1476               ac_entry = slist->data;
1477               
1478               if (ac_entry->signal_id == menu_item->accelerator_signal)
1479                 break;
1480               
1481               slist = slist->next;
1482             }
1483           
1484           if (!slist)
1485             gtk_widget_add_accelerator (GTK_WIDGET (menu_item),
1486                                         gtk_signal_name (menu_item->accelerator_signal),
1487                                         accel_group,
1488                                         event->keyval,
1489                                         event->state,
1490                                         GTK_ACCEL_VISIBLE);
1491         }
1492     }
1493   
1494   return TRUE;
1495 }
1496
1497 static gboolean
1498 gtk_menu_motion_notify  (GtkWidget         *widget,
1499                          GdkEventMotion    *event)
1500 {
1501   GtkWidget *menu_item;
1502   GtkMenu *menu;
1503   GtkMenuShell *menu_shell;
1504
1505   gboolean need_enter;
1506
1507
1508   if (GTK_IS_MENU (widget))
1509     gtk_menu_handle_scrolling (GTK_MENU (widget), TRUE);
1510   
1511   /* We received the event for one of two reasons:
1512    *
1513    * a) We are the active menu, and did gtk_grab_add()
1514    * b) The widget is a child of ours, and the event was propagated
1515    *
1516    * Since for computation of navigation regions, we want the menu which
1517    * is the parent of the menu item, for a), we need to find that menu,
1518    * which may be different from 'widget'.
1519    */
1520   menu_item = gtk_get_event_widget ((GdkEvent*) event);
1521   if (!menu_item || !GTK_IS_MENU_ITEM (menu_item) || !GTK_WIDGET_IS_SENSITIVE (menu_item) ||
1522       !GTK_IS_MENU (menu_item->parent))
1523     return FALSE;
1524
1525   menu_shell = GTK_MENU_SHELL (menu_item->parent);
1526   menu = GTK_MENU (menu_shell);
1527   
1528   need_enter = (menu->navigation_region != NULL || menu_shell->ignore_enter);
1529
1530   /* Check to see if we are within an active submenu's navigation region
1531    */
1532   if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root))
1533     return TRUE; 
1534
1535   if (need_enter)
1536     {
1537       /* The menu is now sensitive to enter events on its items, but
1538        * was previously sensitive.  So we fake an enter event.
1539        */
1540       gint width, height;
1541       
1542       menu_shell->ignore_enter = FALSE; 
1543       
1544       gdk_window_get_size (event->window, &width, &height);
1545       if (event->x >= 0 && event->x < width &&
1546           event->y >= 0 && event->y < height)
1547         {
1548           GdkEvent send_event;
1549           
1550           send_event.crossing.type = GDK_ENTER_NOTIFY;
1551           send_event.crossing.window = event->window;
1552           send_event.crossing.time = event->time;
1553           send_event.crossing.send_event = TRUE;
1554           send_event.crossing.x_root = event->x_root;
1555           send_event.crossing.y_root = event->y_root;
1556           send_event.crossing.x = event->x;
1557           send_event.crossing.y = event->y;
1558
1559           /* We send the event to 'widget', the currently active menu,
1560            * instead of 'menu', the menu that the pointer is in. This
1561            * will ensure that the event will be ignored unless the
1562            * menuitem is a child of the active menu or some parent
1563            * menu of the active menu.
1564            */
1565           return gtk_widget_event (widget, &send_event);
1566         }
1567     }
1568
1569   return FALSE;
1570 }
1571
1572 static gboolean
1573 gtk_menu_scroll_timeout (gpointer  data)
1574 {
1575   GtkMenu *menu;
1576   GtkWidget *widget;
1577   gint offset;
1578   gint view_width, view_height;
1579
1580   menu = GTK_MENU (data);
1581   widget = GTK_WIDGET (menu);
1582
1583   offset = menu->scroll_offset + menu->scroll_step;
1584
1585   /* If we scroll upward and the non-visible top part
1586    * is smaller than the scroll arrow it would be
1587    * pretty stupid to show the arrow and taking more
1588    * screen space than just scrolling to the top.
1589    */
1590   if ((menu->scroll_step < 0) && (offset < MENU_SCROLL_ARROW_HEIGHT))
1591     offset = 0;
1592
1593   /* Don't scroll over the top if we weren't before: */
1594   if ((menu->scroll_offset >= 0) && (offset < 0))
1595     offset = 0;
1596
1597   gdk_window_get_size (widget->window, &view_width, &view_height);
1598
1599   /* Don't scroll past the bottom if we weren't before: */
1600   if (menu->scroll_offset > 0)
1601     view_height -= MENU_SCROLL_ARROW_HEIGHT;
1602   
1603   if ((menu->scroll_offset + view_height <= widget->requisition.height) &&
1604       (offset + view_height > widget->requisition.height))
1605     offset = widget->requisition.height - view_height;
1606
1607   gtk_menu_scroll_to (menu, offset);
1608
1609   return TRUE;
1610 }
1611
1612 static void
1613 gtk_menu_handle_scrolling (GtkMenu *menu, gboolean enter)
1614 {
1615   GtkMenuShell *menu_shell;
1616   gint width, height;
1617   gint x, y;
1618   gint border;
1619   GdkRectangle rect;
1620   gboolean in_arrow;
1621   gboolean scroll_fast = FALSE;
1622
1623   menu_shell = GTK_MENU_SHELL (menu);
1624
1625   gdk_window_get_pointer (GTK_WIDGET (menu)->window, &x, &y, NULL);
1626   gdk_window_get_size (GTK_WIDGET (menu)->window, &width, &height);
1627
1628   border = GTK_CONTAINER (menu)->border_width + GTK_WIDGET (menu)->style->ythickness;
1629
1630   if (menu->upper_arrow_visible && !menu->tearoff_active)
1631     {
1632       rect.x = 0;
1633       rect.y = 0;
1634       rect.width = width;
1635       rect.height = MENU_SCROLL_ARROW_HEIGHT + border;
1636       
1637       in_arrow = FALSE;
1638       if ((x >= rect.x) && (x < rect.x + rect.width) &&
1639           (y >= rect.y) && (y < rect.y + rect.height))
1640         {
1641           in_arrow = TRUE;
1642           scroll_fast = (y < rect.y + MENU_SCROLL_FAST_ZONE);
1643         }
1644         
1645       if (enter && in_arrow &&
1646           (!menu->upper_arrow_prelight || menu->scroll_fast != scroll_fast))
1647         {
1648           menu->upper_arrow_prelight = TRUE;
1649           menu->scroll_fast = scroll_fast;
1650           gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
1651           
1652           /* Deselect the active item so that any submenus are poped down */
1653           gtk_menu_shell_deselect (menu_shell);
1654
1655           gtk_menu_stop_scrolling (menu);
1656           menu->scroll_step = -MENU_SCROLL_STEP;
1657           menu->timeout_id = g_timeout_add ((scroll_fast) ? MENU_SCROLL_TIMEOUT2 : MENU_SCROLL_TIMEOUT1,
1658                                             gtk_menu_scroll_timeout,
1659                                             menu);
1660         }
1661       else if (!enter && !in_arrow && menu->upper_arrow_prelight)
1662         {
1663           menu->upper_arrow_prelight = FALSE;
1664           gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
1665           
1666           gtk_menu_stop_scrolling (menu);
1667         }
1668     }
1669   
1670   if (menu->lower_arrow_visible && !menu->tearoff_active)
1671     {
1672       rect.x = 0;
1673       rect.y = height - border - MENU_SCROLL_ARROW_HEIGHT;
1674       rect.width = width;
1675       rect.height = MENU_SCROLL_ARROW_HEIGHT + border;
1676
1677       in_arrow = FALSE;
1678       if ((x >= rect.x) && (x < rect.x + rect.width) &&
1679           (y >= rect.y) && (y < rect.y + rect.height))
1680         {
1681           in_arrow = TRUE;
1682           scroll_fast = (y > rect.y + rect.height - MENU_SCROLL_FAST_ZONE);
1683         }
1684
1685       if (enter && in_arrow &&
1686           (!menu->lower_arrow_prelight || menu->scroll_fast != scroll_fast))
1687         {
1688           menu->lower_arrow_prelight = TRUE;
1689           menu->scroll_fast = scroll_fast;
1690           gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
1691
1692           /* Deselect the active item so that any submenus are poped down */
1693           gtk_menu_shell_deselect (menu_shell);
1694
1695           gtk_menu_stop_scrolling (menu);
1696           menu->scroll_step = MENU_SCROLL_STEP;
1697           menu->timeout_id = g_timeout_add ((scroll_fast) ? MENU_SCROLL_TIMEOUT2 : MENU_SCROLL_TIMEOUT1,
1698                                             gtk_menu_scroll_timeout,
1699                                             menu);
1700         }
1701       else if (!enter && !in_arrow && menu->lower_arrow_prelight)
1702         {
1703           menu->lower_arrow_prelight = FALSE;
1704           gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
1705           
1706           gtk_menu_stop_scrolling (menu);
1707         }
1708     }
1709 }
1710
1711 static gboolean
1712 gtk_menu_enter_notify (GtkWidget        *widget,
1713                        GdkEventCrossing *event)
1714 {
1715   GtkWidget *menu_item;
1716
1717   if (widget && GTK_IS_MENU (widget))
1718     gtk_menu_handle_scrolling (GTK_MENU (widget), TRUE);
1719       
1720   /* If this is a faked enter (see gtk_menu_motion_notify), 'widget'
1721    * will not correspond to the event widget's parent.  Check to see
1722    * if we are in the parent's navigation region.
1723    */
1724   menu_item = gtk_get_event_widget ((GdkEvent*) event);
1725   if (menu_item && GTK_IS_MENU_ITEM (menu_item) && GTK_IS_MENU (menu_item->parent) &&
1726       gtk_menu_navigating_submenu (GTK_MENU (menu_item->parent), event->x_root, event->y_root))
1727     return TRUE;
1728
1729   return GTK_WIDGET_CLASS (parent_class)->enter_notify_event (widget, event); 
1730 }
1731
1732 static gboolean
1733 gtk_menu_leave_notify (GtkWidget        *widget,
1734                        GdkEventCrossing *event)
1735 {
1736   GtkMenuShell *menu_shell;
1737   GtkMenu *menu;
1738   GtkMenuItem *menu_item;
1739   GtkWidget *event_widget;
1740
1741   menu = GTK_MENU (widget);
1742   menu_shell = GTK_MENU_SHELL (widget); 
1743   
1744   if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root))
1745     return TRUE; 
1746
1747   gtk_menu_handle_scrolling (menu, FALSE);
1748   
1749   event_widget = gtk_get_event_widget ((GdkEvent*) event);
1750   
1751   if (!event_widget || !GTK_IS_MENU_ITEM (event_widget))
1752     return TRUE;
1753   
1754   menu_item = GTK_MENU_ITEM (event_widget); 
1755   
1756   /* Here we check to see if we're leaving an active menu item with a submenu, 
1757    * in which case we enter submenu navigation mode. 
1758    */
1759   if (menu_shell->active_menu_item != NULL
1760       && menu_item->submenu != NULL
1761       && menu_item->submenu_placement == GTK_LEFT_RIGHT)
1762     {
1763       if (menu_item->submenu->window != NULL) 
1764         {
1765           gtk_menu_set_submenu_navigation_region (menu, menu_item, event);
1766           return TRUE;
1767         }
1768     }
1769   
1770   return GTK_WIDGET_CLASS (parent_class)->leave_notify_event (widget, event); 
1771 }
1772
1773 static void 
1774 gtk_menu_stop_navigating_submenu (GtkMenu *menu)
1775 {
1776   if (menu->navigation_region) 
1777     {
1778       gdk_region_destroy (menu->navigation_region);
1779       menu->navigation_region = NULL;
1780     }
1781   
1782   if (menu->navigation_timeout)
1783     {
1784       gtk_timeout_remove (menu->navigation_timeout);
1785       menu->navigation_timeout = 0;
1786     }
1787 }
1788
1789 /* When the timeout is elapsed, the navigation region is destroyed
1790  * and the menuitem under the pointer (if any) is selected.
1791  */
1792 static gboolean
1793 gtk_menu_stop_navigating_submenu_cb (gpointer user_data)
1794 {
1795   GdkEventCrossing send_event;
1796
1797   GtkMenu *menu = user_data;
1798   GdkWindow *child_window;
1799
1800   gtk_menu_stop_navigating_submenu (menu);
1801   
1802   if (GTK_WIDGET_REALIZED (menu))
1803     {
1804       child_window = gdk_window_get_pointer (GTK_WIDGET (menu)->window, NULL, NULL, NULL);
1805
1806       if (child_window)
1807         {
1808           send_event.window = child_window;
1809           send_event.type = GDK_ENTER_NOTIFY;
1810           send_event.time = GDK_CURRENT_TIME; /* Bogus */
1811           send_event.send_event = TRUE;
1812
1813           GTK_WIDGET_CLASS (parent_class)->enter_notify_event (GTK_WIDGET (menu), &send_event); 
1814         }
1815     }
1816
1817   return FALSE; 
1818 }
1819
1820 static gboolean
1821 gtk_menu_navigating_submenu (GtkMenu *menu,
1822                              gint     event_x,
1823                              gint     event_y)
1824 {
1825   if (menu->navigation_region)
1826     {
1827       if (gdk_region_point_in (menu->navigation_region, event_x, event_y))
1828         return TRUE;
1829       else
1830         {
1831           gtk_menu_stop_navigating_submenu (menu);
1832           return FALSE;
1833         }
1834     }
1835   return FALSE;
1836 }
1837
1838 static void
1839 gtk_menu_set_submenu_navigation_region (GtkMenu          *menu,
1840                                         GtkMenuItem      *menu_item,
1841                                         GdkEventCrossing *event)
1842 {
1843   gint submenu_left = 0;
1844   gint submenu_right = 0;
1845   gint submenu_top = 0;
1846   gint submenu_bottom = 0;
1847   gint width = 0;
1848   gint height = 0;
1849   GdkPoint point[3];
1850   GtkWidget *event_widget;
1851
1852   g_return_if_fail (menu_item->submenu != NULL);
1853   g_return_if_fail (event != NULL);
1854   
1855   event_widget = gtk_get_event_widget ((GdkEvent*) event);
1856   
1857   gdk_window_get_origin (menu_item->submenu->window, &submenu_left, &submenu_top);
1858   gdk_window_get_size (menu_item->submenu->window, &width, &height);
1859   submenu_right = submenu_left + width;
1860   submenu_bottom = submenu_top + height;
1861   
1862   gdk_window_get_size (event_widget->window, &width, &height);
1863   
1864   if (event->x >= 0 && event->x < width)
1865     {
1866       /* Set navigation region */
1867       /* We fudge/give a little padding in case the user
1868        * ``misses the vertex'' of the triangle/is off by a pixel or two.
1869        */ 
1870       if (menu_item->submenu_direction == GTK_DIRECTION_RIGHT)
1871         point[0].x = event->x_root - SUBMENU_NAV_REGION_PADDING; 
1872       else                             
1873         point[0].x = event->x_root + SUBMENU_NAV_REGION_PADDING;  
1874       
1875       /* Exiting the top or bottom? */ 
1876       if (event->y < 0)
1877         { /* top */
1878           point[0].y = event->y_root + SUBMENU_NAV_REGION_PADDING;
1879           point[1].y = submenu_top;
1880
1881           if (point[0].y <= point[1].y)
1882             return;
1883         }
1884       else
1885         { /* bottom */
1886           point[0].y = event->y_root - SUBMENU_NAV_REGION_PADDING; 
1887           point[1].y = submenu_bottom;
1888
1889           if (point[0].y >= point[1].y)
1890             return;
1891         }
1892       
1893       /* Submenu is to the left or right? */ 
1894       if (menu_item->submenu_direction == GTK_DIRECTION_RIGHT)
1895         point[1].x = submenu_left;  /* right */
1896       else
1897         point[1].x = submenu_right; /* left */
1898       
1899       point[2].x = point[1].x;
1900       point[2].y = point[0].y;
1901
1902       gtk_menu_stop_navigating_submenu (menu);
1903       
1904       menu->navigation_region = gdk_region_polygon (point, 3, GDK_WINDING_RULE);
1905
1906       menu->navigation_timeout = gtk_timeout_add (SUBMENU_NAV_HYSTERESIS_TIMEOUT, 
1907                                                   gtk_menu_stop_navigating_submenu_cb, menu);
1908     }
1909 }
1910
1911 static void
1912 gtk_menu_deactivate (GtkMenuShell *menu_shell)
1913 {
1914   GtkWidget *parent;
1915   
1916   g_return_if_fail (menu_shell != NULL);
1917   g_return_if_fail (GTK_IS_MENU (menu_shell));
1918   
1919   parent = menu_shell->parent_menu_shell;
1920   
1921   menu_shell->activate_time = 0;
1922   gtk_menu_popdown (GTK_MENU (menu_shell));
1923   
1924   if (parent)
1925     gtk_menu_shell_deactivate (GTK_MENU_SHELL (parent));
1926 }
1927
1928 static void
1929 gtk_menu_position (GtkMenu *menu)
1930 {
1931   GtkWidget *widget;
1932   GtkRequisition requisition;
1933   gint x, y;
1934   gint screen_width;
1935   gint screen_height;
1936   gint scroll_offset;
1937   gint menu_height;
1938   gboolean push_in;
1939   
1940   g_return_if_fail (menu != NULL);
1941   g_return_if_fail (GTK_IS_MENU (menu));
1942
1943   widget = GTK_WIDGET (menu);
1944
1945   screen_width = gdk_screen_width ();
1946   screen_height = gdk_screen_height ();
1947
1948   gdk_window_get_pointer (NULL, &x, &y, NULL);
1949
1950   /* We need the requisition to figure out the right place to
1951    * popup the menu. In fact, we always need to ask here, since
1952    * if a size_request was queued while we weren't popped up,
1953    * the requisition won't have been recomputed yet.
1954    */
1955   gtk_widget_size_request (widget, &requisition);
1956
1957   push_in = FALSE;
1958   
1959   if (menu->position_func)
1960     (* menu->position_func) (menu, &x, &y, &push_in, menu->position_func_data);
1961   else
1962     {
1963       x = CLAMP (x - 2, 0, MAX (0, screen_width - requisition.width));
1964       y = CLAMP (y - 2, 0, MAX (0, screen_height - requisition.height));
1965     }
1966
1967   scroll_offset = 0;
1968
1969   if (push_in)
1970     {
1971       menu_height = GTK_WIDGET (menu)->requisition.height;
1972
1973       if (y + menu_height > screen_height)
1974         {
1975           scroll_offset -= y + menu_height - screen_height;
1976           y = screen_height - menu_height;
1977         }
1978   
1979       if (y < 0)
1980         {
1981           scroll_offset -= y;
1982           y = 0;
1983         }
1984     }
1985
1986   if (y + requisition.height > screen_height)
1987     requisition.height = screen_height - y;
1988   
1989   if (y < 0)
1990     {
1991       scroll_offset -= y;
1992       requisition.height -= -y;
1993       y = 0;
1994     }
1995
1996   if (scroll_offset > 0)
1997     scroll_offset += MENU_SCROLL_ARROW_HEIGHT;
1998   
1999   /* FIXME: The MAX() here is because gtk_widget_set_uposition
2000    * is broken. Once we provide an alternate interface that
2001    * allows negative values, then we can remove them.
2002    */
2003   x = MAX (x, 0);
2004   gtk_widget_set_uposition (GTK_MENU_SHELL (menu)->active ?
2005                             menu->toplevel : menu->tearoff_window, 
2006                             x, y);
2007   gtk_widget_set_usize (GTK_MENU_SHELL (menu)->active ?
2008                         menu->toplevel : menu->tearoff_hbox,
2009                         -1, requisition.height);
2010   menu->scroll_offset = scroll_offset;
2011 }
2012
2013 static void
2014 gtk_menu_stop_scrolling (GtkMenu *menu)
2015 {
2016   if (menu->timeout_id)
2017     {
2018       g_source_remove (menu->timeout_id);
2019       menu->timeout_id = 0;
2020     }
2021 }
2022
2023
2024 static void
2025 gtk_menu_scroll_to (GtkMenu *menu,
2026                     gint    offset)
2027 {
2028   GtkWidget *widget;
2029   gint x, y;
2030   gint view_width, view_height;
2031   gint border_width;
2032   gboolean last_visible;
2033   gint menu_height;
2034
2035   widget = GTK_WIDGET (menu);
2036
2037   if (menu->tearoff_active &&
2038       menu->tearoff_adjustment &&
2039       (menu->tearoff_adjustment->value != offset))
2040     {
2041       menu->tearoff_adjustment->value = offset;
2042       gtk_adjustment_value_changed (menu->tearoff_adjustment);
2043     }
2044   
2045   /* Scroll the menu: */
2046   gdk_window_move (menu->bin_window, 0, -offset);
2047
2048   /* Move/resize the viewport according to arrows: */
2049   gdk_window_get_size (widget->window, &view_width, &view_height);
2050
2051   border_width = GTK_CONTAINER (menu)->border_width;
2052   view_width -= (border_width + widget->style->xthickness) * 2;
2053   view_height -= (border_width + widget->style->ythickness) * 2;
2054   menu_height = widget->requisition.height - (border_width + widget->style->ythickness) * 2;
2055
2056   x = border_width + widget->style->xthickness;
2057   y = border_width + widget->style->ythickness;
2058   
2059   if (!menu->tearoff_active)
2060     {
2061       last_visible = menu->upper_arrow_visible;
2062       menu->upper_arrow_visible = (offset > 0);
2063
2064       if (menu->upper_arrow_visible)
2065         view_height -= MENU_SCROLL_ARROW_HEIGHT;
2066       
2067       if ( (last_visible != menu->upper_arrow_visible) &&
2068            !menu->upper_arrow_visible)
2069         {
2070           menu->upper_arrow_prelight = FALSE;
2071           
2072           /* If we hid the upper arrow, possibly remove timeout */
2073           if (menu->scroll_step < 0)
2074             gtk_menu_stop_scrolling (menu);
2075         }
2076       
2077       last_visible = menu->lower_arrow_visible;
2078       menu->lower_arrow_visible = (view_height + offset < menu_height);
2079       
2080       if (menu->lower_arrow_visible)
2081         view_height -= MENU_SCROLL_ARROW_HEIGHT;
2082       
2083       if ( (last_visible != menu->lower_arrow_visible) &&
2084            !menu->lower_arrow_visible)
2085         {
2086           menu->lower_arrow_prelight = FALSE;
2087           
2088           /* If we hid the lower arrow, possibly remove timeout */
2089           if (menu->scroll_step > 0)
2090             gtk_menu_stop_scrolling (menu);
2091         }
2092       
2093       if (menu->upper_arrow_visible)
2094         y += MENU_SCROLL_ARROW_HEIGHT;
2095     }
2096   
2097   
2098   gdk_window_move_resize (menu->view_window,
2099                           x,
2100                           y,
2101                           view_width,
2102                           view_height);
2103
2104   menu->scroll_offset = offset;
2105 }
2106
2107 static void
2108 gtk_menu_select_item (GtkMenuShell  *menu_shell,
2109                       GtkWidget     *menu_item)
2110 {
2111   GtkMenu *menu;
2112   GtkWidget *child;
2113   GList *children;
2114   GtkRequisition child_requisition;
2115   gint child_offset, child_height;
2116   gint width, height;
2117   gint y;
2118   gint arrow_height;
2119   gboolean last_child = 0;
2120   
2121   g_return_if_fail (menu_shell != NULL);
2122   g_return_if_fail (GTK_IS_MENU (menu_shell));
2123
2124   menu = GTK_MENU (menu_shell);
2125
2126   /* We need to check if the selected item fully visible.
2127    * If not we need to scroll the menu so that it becomes fully
2128    * visible.
2129    */
2130
2131   child = NULL;
2132   child_offset = 0;
2133   child_height = 0;
2134   children = menu_shell->children;
2135   while (children)
2136     {
2137       child = children->data;
2138       children = children->next;
2139       
2140       if (GTK_WIDGET_VISIBLE (child))
2141         {
2142           gtk_widget_size_request (child, &child_requisition);
2143           child_offset += child_height;
2144           child_height = child_requisition.height;
2145         }
2146       
2147       if (child == menu_item)
2148         {
2149           last_child = (children == NULL);
2150           break;
2151         }
2152     }
2153
2154   if (child == menu_item)
2155     {
2156       y = menu->scroll_offset;
2157       gdk_window_get_size (GTK_WIDGET (menu)->window, &width, &height);
2158
2159       height -= 2*GTK_CONTAINER (menu)->border_width + 2*GTK_WIDGET (menu)->style->ythickness;
2160       
2161       if (child_offset + child_height <= y)
2162         {
2163           /* Ignore the enter event we might get if the pointer is on the menu
2164            */
2165           menu_shell->ignore_enter = TRUE;
2166           gtk_menu_scroll_to (menu, child_offset);
2167         }
2168       else
2169         {
2170           arrow_height = 0;
2171           if (menu->upper_arrow_visible && !menu->tearoff_active)
2172             arrow_height += MENU_SCROLL_ARROW_HEIGHT;
2173           if (menu->lower_arrow_visible && !menu->tearoff_active)
2174             arrow_height += MENU_SCROLL_ARROW_HEIGHT;
2175           
2176           if (child_offset >= y + height - arrow_height)
2177             {
2178               arrow_height = 0;
2179               if (!last_child && !menu->tearoff_active)
2180                 arrow_height += MENU_SCROLL_ARROW_HEIGHT;
2181               
2182               y = child_offset + child_height - height + arrow_height;
2183               if ((y > 0) && !menu->tearoff_active)
2184                 {
2185                   /* Need upper arrow */
2186                   arrow_height += MENU_SCROLL_ARROW_HEIGHT;
2187                   y = child_offset + child_height - height + arrow_height;
2188                 }
2189               /* Ignore the enter event we might get if the pointer is on the menu
2190                */
2191               menu_shell->ignore_enter = TRUE;
2192               gtk_menu_scroll_to (menu, y);
2193             }
2194         }    
2195       
2196     }
2197
2198   GTK_MENU_SHELL_CLASS (parent_class)->select_item (menu_shell, menu_item);
2199 }
2200
2201
2202 /* Reparent the menu, taking care of the refcounting
2203  *
2204  * If unrealize is true we force a unrealize while reparenting the parent.
2205  * This can help eliminate flicker in some cases.
2206  *
2207  * What happens is that when the menu is unrealized and then re-realized,
2208  * the allocations are as follows:
2209  *
2210  *  parent - 1x1 at (0,0) 
2211  *  child1 - 100x20 at (0,0)
2212  *  child2 - 100x20 at (0,20)
2213  *  child3 - 100x20 at (0,40)
2214  *
2215  * That is, the parent is small but the children are full sized. Then,
2216  * when the queued_resize gets processed, the parent gets resized to
2217  * full size. 
2218  *
2219  * But in order to eliminate flicker when scrolling, gdkgeometry-x11.c
2220  * contains the following logic:
2221  * 
2222  * - if a move or resize operation on a window would change the clip 
2223  *   region on the children, then before the window is resized
2224  *   the background for children is temporarily set to None, the
2225  *   move/resize done, and the background for the children restored.
2226  *
2227  * So, at the point where the parent is resized to final size, the
2228  * background for the children is temporarily None, and thus they
2229  * are not cleared to the background color and the previous background
2230  * (the image of the menu) is left in place.
2231  */
2232 static void 
2233 gtk_menu_reparent (GtkMenu      *menu, 
2234                    GtkWidget    *new_parent, 
2235                    gboolean      unrealize)
2236 {
2237   GtkObject *object = GTK_OBJECT (menu);
2238   GtkWidget *widget = GTK_WIDGET (menu);
2239   gboolean was_floating = GTK_OBJECT_FLOATING (object);
2240
2241   gtk_object_ref (object);
2242   gtk_object_sink (object);
2243
2244   if (unrealize)
2245     {
2246       gtk_object_ref (object);
2247       gtk_container_remove (GTK_CONTAINER (widget->parent), widget);
2248       gtk_container_add (GTK_CONTAINER (new_parent), widget);
2249       gtk_object_unref (object);
2250     }
2251   else
2252     gtk_widget_reparent (GTK_WIDGET (menu), new_parent);
2253   
2254   if (was_floating)
2255     GTK_OBJECT_SET_FLAGS (object, GTK_FLOATING);
2256   else
2257     gtk_object_unref (object);
2258 }
2259
2260 static void
2261 gtk_menu_show_all (GtkWidget *widget)
2262 {
2263   g_return_if_fail (widget != NULL);
2264   g_return_if_fail (GTK_IS_MENU (widget));
2265
2266   /* Show children, but not self. */
2267   gtk_container_foreach (GTK_CONTAINER (widget), (GtkCallback) gtk_widget_show_all, NULL);
2268 }
2269
2270
2271 static void
2272 gtk_menu_hide_all (GtkWidget *widget)
2273 {
2274   g_return_if_fail (widget != NULL);
2275   g_return_if_fail (GTK_IS_MENU (widget));
2276
2277   /* Hide children, but not self. */
2278   gtk_container_foreach (GTK_CONTAINER (widget), (GtkCallback) gtk_widget_hide_all, NULL);
2279 }
2280
2281