]> git.saurik.com Git - wxWidgets.git/blob - src/gtk/menu.cpp
try to deal more gracefully (than simply not showing anything at all) with invalid...
[wxWidgets.git] / src / gtk / menu.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/menu.cpp
3 // Purpose:
4 // Author: Robert Roebling
5 // Id: $Id$
6 // Copyright: (c) 1998 Robert Roebling
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9
10 // For compilers that support precompilation, includes "wx.h".
11 #include "wx/wxprec.h"
12
13 #include "wx/menu.h"
14
15 #ifndef WX_PRECOMP
16 #include "wx/intl.h"
17 #include "wx/log.h"
18 #include "wx/app.h"
19 #include "wx/bitmap.h"
20 #endif
21
22 #if wxUSE_ACCEL
23 #include "wx/accel.h"
24 #endif // wxUSE_ACCEL
25
26 #include "wx/stockitem.h"
27 #include "wx/gtk/private.h"
28
29 #include <gdk/gdkkeysyms.h>
30
31 // FIXME: is this right? somehow I don't think so (VZ)
32
33 #define gtk_accel_group_attach(g, o) gtk_window_add_accel_group((o), (g))
34 //#define gtk_accel_group_detach(g, o) gtk_window_remove_accel_group((o), (g))
35 //#define gtk_menu_ensure_uline_accel_group(m) gtk_menu_get_accel_group(m)
36
37 #define ACCEL_OBJECT GtkWindow
38 #define ACCEL_OBJECTS(a) (a)->acceleratables
39 #define ACCEL_OBJ_CAST(obj) ((GtkWindow*) obj)
40
41 // we use normal item but with a special id for the menu title
42 static const int wxGTK_TITLE_ID = -3;
43
44 //-----------------------------------------------------------------------------
45 // idle system
46 //-----------------------------------------------------------------------------
47
48 #if wxUSE_ACCEL
49 static wxString GetGtkHotKey( const wxMenuItem& item );
50 #endif
51
52 //-----------------------------------------------------------------------------
53 // idle system
54 //-----------------------------------------------------------------------------
55
56 static wxString wxReplaceUnderscore( const wxString& title )
57 {
58 const wxChar *pc;
59
60 // GTK 1.2 wants to have "_" instead of "&" for accelerators
61 wxString str;
62 pc = title;
63 while (*pc != wxT('\0'))
64 {
65 if ((*pc == wxT('&')) && (*(pc+1) == wxT('&')))
66 {
67 // "&" is doubled to indicate "&" instead of accelerator
68 ++pc;
69 str << wxT('&');
70 }
71 else if (*pc == wxT('&'))
72 {
73 str << wxT('_');
74 }
75 else
76 {
77 if ( *pc == wxT('_') )
78 {
79 // underscores must be doubled to prevent them from being
80 // interpreted as accelerator character prefix by GTK
81 str << *pc;
82 }
83
84 str << *pc;
85 }
86 ++pc;
87 }
88
89 // wxPrintf( wxT("before %s after %s\n"), title.c_str(), str.c_str() );
90
91 return str;
92 }
93
94 //-----------------------------------------------------------------------------
95 // activate message from GTK
96 //-----------------------------------------------------------------------------
97
98 static void DoCommonMenuCallbackCode(wxMenu *menu, wxMenuEvent& event)
99 {
100 if (g_isIdle)
101 wxapp_install_idle_handler();
102
103 event.SetEventObject( menu );
104
105 wxEvtHandler* handler = menu->GetEventHandler();
106 if (handler && handler->ProcessEvent(event))
107 return;
108
109 wxWindow *win = menu->GetInvokingWindow();
110 if (win)
111 win->GetEventHandler()->ProcessEvent( event );
112 }
113
114 extern "C" {
115
116 static void gtk_menu_open_callback( GtkWidget *widget, wxMenu *menu )
117 {
118 wxMenuEvent event(wxEVT_MENU_OPEN, -1, menu);
119
120 DoCommonMenuCallbackCode(menu, event);
121 }
122
123 static void gtk_menu_close_callback( GtkWidget *widget, wxMenuBar *menubar )
124 {
125 if ( !menubar->GetMenuCount() )
126 {
127 // if menubar is empty we can't call GetMenu(0) below
128 return;
129 }
130
131 wxMenuEvent event( wxEVT_MENU_CLOSE, -1, NULL );
132
133 DoCommonMenuCallbackCode(menubar->GetMenu(0), event);
134 }
135
136 }
137
138 //-----------------------------------------------------------------------------
139 // wxMenuBar
140 //-----------------------------------------------------------------------------
141
142 IMPLEMENT_DYNAMIC_CLASS(wxMenuBar,wxWindow)
143
144 void wxMenuBar::Init(size_t n, wxMenu *menus[], const wxString titles[], long style)
145 {
146 // the parent window is known after wxFrame::SetMenu()
147 m_needParent = false;
148 m_style = style;
149 m_invokingWindow = (wxWindow*) NULL;
150
151 if (!PreCreation( (wxWindow*) NULL, wxDefaultPosition, wxDefaultSize ) ||
152 !CreateBase( (wxWindow*) NULL, -1, wxDefaultPosition, wxDefaultSize, style, wxDefaultValidator, wxT("menubar") ))
153 {
154 wxFAIL_MSG( wxT("wxMenuBar creation failed") );
155 return;
156 }
157
158 m_menubar = gtk_menu_bar_new();
159
160 if (style & wxMB_DOCKABLE)
161 {
162 m_widget = gtk_handle_box_new();
163 gtk_container_add( GTK_CONTAINER(m_widget), GTK_WIDGET(m_menubar) );
164 gtk_widget_show( GTK_WIDGET(m_menubar) );
165 }
166 else
167 {
168 m_widget = GTK_WIDGET(m_menubar);
169 }
170
171 PostCreation();
172
173 ApplyWidgetStyle();
174
175 for (size_t i = 0; i < n; ++i )
176 Append(menus[i], titles[i]);
177
178 // VZ: for some reason connecting to menus "deactivate" doesn't work (we
179 // don't get it when the menu is dismissed by clicking outside the
180 // toolbar) so we connect to the global one, even if it means that we
181 // can't pass the menu which was closed in wxMenuEvent object
182 g_signal_connect (m_menubar, "deactivate",
183 G_CALLBACK (gtk_menu_close_callback), this);
184
185 }
186
187 wxMenuBar::wxMenuBar(size_t n, wxMenu *menus[], const wxString titles[], long style)
188 {
189 Init(n, menus, titles, style);
190 }
191
192 wxMenuBar::wxMenuBar(long style)
193 {
194 Init(0, NULL, NULL, style);
195 }
196
197 wxMenuBar::wxMenuBar()
198 {
199 Init(0, NULL, NULL, 0);
200 }
201
202 wxMenuBar::~wxMenuBar()
203 {
204 }
205
206 static void wxMenubarUnsetInvokingWindow( wxMenu *menu, wxWindow *win )
207 {
208 menu->SetInvokingWindow( (wxWindow*) NULL );
209
210 wxWindow *top_frame = win;
211 while (top_frame->GetParent() && !(top_frame->IsTopLevel()))
212 top_frame = top_frame->GetParent();
213
214 wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
215 while (node)
216 {
217 wxMenuItem *menuitem = node->GetData();
218 if (menuitem->IsSubMenu())
219 wxMenubarUnsetInvokingWindow( menuitem->GetSubMenu(), win );
220 node = node->GetNext();
221 }
222 }
223
224 static void wxMenubarSetInvokingWindow( wxMenu *menu, wxWindow *win )
225 {
226 menu->SetInvokingWindow( win );
227
228 wxWindow *top_frame = win;
229 while (top_frame->GetParent() && !(top_frame->IsTopLevel()))
230 top_frame = top_frame->GetParent();
231
232 // support for native hot keys
233 ACCEL_OBJECT *obj = ACCEL_OBJ_CAST(top_frame->m_widget);
234 if ( !g_slist_find( ACCEL_OBJECTS(menu->m_accel), obj ) )
235 gtk_accel_group_attach( menu->m_accel, obj );
236
237 wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
238 while (node)
239 {
240 wxMenuItem *menuitem = node->GetData();
241 if (menuitem->IsSubMenu())
242 wxMenubarSetInvokingWindow( menuitem->GetSubMenu(), win );
243 node = node->GetNext();
244 }
245 }
246
247 void wxMenuBar::SetInvokingWindow( wxWindow *win )
248 {
249 m_invokingWindow = win;
250 wxWindow *top_frame = win;
251 while (top_frame->GetParent() && !(top_frame->IsTopLevel()))
252 top_frame = top_frame->GetParent();
253
254 wxMenuList::compatibility_iterator node = m_menus.GetFirst();
255 while (node)
256 {
257 wxMenu *menu = node->GetData();
258 wxMenubarSetInvokingWindow( menu, win );
259 node = node->GetNext();
260 }
261 }
262
263 void wxMenuBar::UnsetInvokingWindow( wxWindow *win )
264 {
265 m_invokingWindow = (wxWindow*) NULL;
266 wxWindow *top_frame = win;
267 while (top_frame->GetParent() && !(top_frame->IsTopLevel()))
268 top_frame = top_frame->GetParent();
269
270 wxMenuList::compatibility_iterator node = m_menus.GetFirst();
271 while (node)
272 {
273 wxMenu *menu = node->GetData();
274 wxMenubarUnsetInvokingWindow( menu, win );
275 node = node->GetNext();
276 }
277 }
278
279 bool wxMenuBar::Append( wxMenu *menu, const wxString &title )
280 {
281 if ( !wxMenuBarBase::Append( menu, title ) )
282 return false;
283
284 return GtkAppend(menu, title);
285 }
286
287 bool wxMenuBar::GtkAppend(wxMenu *menu, const wxString& title, int pos)
288 {
289 wxString str( wxReplaceUnderscore( title ) );
290
291 // This doesn't have much effect right now.
292 menu->SetTitle( str );
293
294 // The "m_owner" is the "menu item"
295 menu->m_owner = gtk_menu_item_new_with_mnemonic( wxGTK_CONV( str ) );
296
297 gtk_widget_show( menu->m_owner );
298
299 gtk_menu_item_set_submenu( GTK_MENU_ITEM(menu->m_owner), menu->m_menu );
300
301 if (pos == -1)
302 gtk_menu_shell_append( GTK_MENU_SHELL(m_menubar), menu->m_owner );
303 else
304 gtk_menu_shell_insert( GTK_MENU_SHELL(m_menubar), menu->m_owner, pos );
305
306 g_signal_connect (menu->m_owner, "activate",
307 G_CALLBACK (gtk_menu_open_callback),
308 menu);
309
310 // m_invokingWindow is set after wxFrame::SetMenuBar(). This call enables
311 // addings menu later on.
312 if (m_invokingWindow)
313 {
314 wxMenubarSetInvokingWindow( menu, m_invokingWindow );
315
316 // OPTIMISE ME: we should probably cache this, or pass it
317 // directly, but for now this is a minimal
318 // change to validate the new dynamic sizing.
319 // see (and refactor :) similar code in Remove
320 // below.
321
322 wxFrame *frame = wxDynamicCast( m_invokingWindow, wxFrame );
323
324 if( frame )
325 frame->UpdateMenuBarSize();
326 }
327
328 return true;
329 }
330
331 bool wxMenuBar::Insert(size_t pos, wxMenu *menu, const wxString& title)
332 {
333 if ( !wxMenuBarBase::Insert(pos, menu, title) )
334 return false;
335
336 // TODO
337
338 if ( !GtkAppend(menu, title, (int)pos) )
339 return false;
340
341 return true;
342 }
343
344 wxMenu *wxMenuBar::Replace(size_t pos, wxMenu *menu, const wxString& title)
345 {
346 // remove the old item and insert a new one
347 wxMenu *menuOld = Remove(pos);
348 if ( menuOld && !Insert(pos, menu, title) )
349 {
350 return (wxMenu*) NULL;
351 }
352
353 // either Insert() succeeded or Remove() failed and menuOld is NULL
354 return menuOld;
355 }
356
357 wxMenu *wxMenuBar::Remove(size_t pos)
358 {
359 wxMenu *menu = wxMenuBarBase::Remove(pos);
360 if ( !menu )
361 return (wxMenu*) NULL;
362
363 gtk_menu_item_remove_submenu( GTK_MENU_ITEM(menu->m_owner) );
364 gtk_container_remove(GTK_CONTAINER(m_menubar), menu->m_owner);
365
366 gtk_widget_destroy( menu->m_owner );
367 menu->m_owner = NULL;
368
369 if (m_invokingWindow)
370 {
371 // OPTIMISE ME: see comment in GtkAppend
372 wxFrame *frame = wxDynamicCast( m_invokingWindow, wxFrame );
373
374 if( frame )
375 frame->UpdateMenuBarSize();
376 }
377
378 return menu;
379 }
380
381 static int FindMenuItemRecursive( const wxMenu *menu, const wxString &menuString, const wxString &itemString )
382 {
383 if (wxMenuItem::GetLabelFromText(menu->GetTitle()) == wxMenuItem::GetLabelFromText(menuString))
384 {
385 int res = menu->FindItem( itemString );
386 if (res != wxNOT_FOUND)
387 return res;
388 }
389
390 wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
391 while (node)
392 {
393 wxMenuItem *item = node->GetData();
394 if (item->IsSubMenu())
395 return FindMenuItemRecursive(item->GetSubMenu(), menuString, itemString);
396
397 node = node->GetNext();
398 }
399
400 return wxNOT_FOUND;
401 }
402
403 int wxMenuBar::FindMenuItem( const wxString &menuString, const wxString &itemString ) const
404 {
405 wxMenuList::compatibility_iterator node = m_menus.GetFirst();
406 while (node)
407 {
408 wxMenu *menu = node->GetData();
409 int res = FindMenuItemRecursive( menu, menuString, itemString);
410 if (res != -1)
411 return res;
412 node = node->GetNext();
413 }
414
415 return wxNOT_FOUND;
416 }
417
418 // Find a wxMenuItem using its id. Recurses down into sub-menus
419 static wxMenuItem* FindMenuItemByIdRecursive(const wxMenu* menu, int id)
420 {
421 wxMenuItem* result = menu->FindChildItem(id);
422
423 wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
424 while ( node && result == NULL )
425 {
426 wxMenuItem *item = node->GetData();
427 if (item->IsSubMenu())
428 {
429 result = FindMenuItemByIdRecursive( item->GetSubMenu(), id );
430 }
431 node = node->GetNext();
432 }
433
434 return result;
435 }
436
437 wxMenuItem* wxMenuBar::FindItem( int id, wxMenu **menuForItem ) const
438 {
439 wxMenuItem* result = 0;
440 wxMenuList::compatibility_iterator node = m_menus.GetFirst();
441 while (node && result == 0)
442 {
443 wxMenu *menu = node->GetData();
444 result = FindMenuItemByIdRecursive( menu, id );
445 node = node->GetNext();
446 }
447
448 if ( menuForItem )
449 {
450 *menuForItem = result ? result->GetMenu() : (wxMenu *)NULL;
451 }
452
453 return result;
454 }
455
456 void wxMenuBar::EnableTop( size_t pos, bool flag )
457 {
458 wxMenuList::compatibility_iterator node = m_menus.Item( pos );
459
460 wxCHECK_RET( node, wxT("menu not found") );
461
462 wxMenu* menu = node->GetData();
463
464 if (menu->m_owner)
465 gtk_widget_set_sensitive( menu->m_owner, flag );
466 }
467
468 wxString wxMenuBar::GetLabelTop( size_t pos ) const
469 {
470 wxMenuList::compatibility_iterator node = m_menus.Item( pos );
471
472 wxCHECK_MSG( node, wxT("invalid"), wxT("menu not found") );
473
474 wxMenu* menu = node->GetData();
475
476 wxString label;
477 wxString text( menu->GetTitle() );
478 for ( const wxChar *pc = text.c_str(); *pc; pc++ )
479 {
480 if ( *pc == wxT('_') )
481 {
482 // '_' is the escape character for GTK+
483 continue;
484 }
485
486 // don't remove ampersands '&' since if we have them in the menu title
487 // it means that they were doubled to indicate "&" instead of accelerator
488
489 label += *pc;
490 }
491
492 return label;
493 }
494
495 void wxMenuBar::SetLabelTop( size_t pos, const wxString& label )
496 {
497 wxMenuList::compatibility_iterator node = m_menus.Item( pos );
498
499 wxCHECK_RET( node, wxT("menu not found") );
500
501 wxMenu* menu = node->GetData();
502
503 const wxString str( wxReplaceUnderscore( label ) );
504
505 menu->SetTitle( str );
506
507 if (menu->m_owner)
508 gtk_label_set_text_with_mnemonic( GTK_LABEL( GTK_BIN(menu->m_owner)->child), wxGTK_CONV(str) );
509 }
510
511 //-----------------------------------------------------------------------------
512 // "activate"
513 //-----------------------------------------------------------------------------
514
515 extern "C" {
516 static void gtk_menu_clicked_callback( GtkWidget *widget, wxMenu *menu )
517 {
518 if (g_isIdle)
519 wxapp_install_idle_handler();
520
521 int id = menu->FindMenuIdByMenuItem(widget);
522
523 /* should find it for normal (not popup) menu */
524 wxASSERT_MSG( (id != -1) || (menu->GetInvokingWindow() != NULL),
525 _T("menu item not found in gtk_menu_clicked_callback") );
526
527 if (!menu->IsEnabled(id))
528 return;
529
530 wxMenuItem* item = menu->FindChildItem( id );
531 wxCHECK_RET( item, wxT("error in menu item callback") );
532
533 if ( item->GetId() == wxGTK_TITLE_ID )
534 {
535 // ignore events from the menu title
536 return;
537 }
538
539 if (item->IsCheckable())
540 {
541 bool isReallyChecked = item->IsChecked(),
542 isInternallyChecked = item->wxMenuItemBase::IsChecked();
543
544 // ensure that the internal state is always consistent with what is
545 // shown on the screen
546 item->wxMenuItemBase::Check(isReallyChecked);
547
548 // we must not report the events for the radio button going up nor the
549 // events resulting from the calls to wxMenuItem::Check()
550 if ( (item->GetKind() == wxITEM_RADIO && !isReallyChecked) ||
551 (isInternallyChecked == isReallyChecked) )
552 {
553 return;
554 }
555 }
556
557
558 // Is this menu on a menubar? (possibly nested)
559 wxFrame* frame = NULL;
560 if(menu->IsAttached())
561 frame = menu->GetMenuBar()->GetFrame();
562
563 // FIXME: why do we have to call wxFrame::GetEventHandler() directly here?
564 // normally wxMenu::SendEvent() should be enough, if it doesn't work
565 // in wxGTK then we have a bug in wxMenu::GetInvokingWindow() which
566 // should be fixed instead of working around it here...
567 if (frame)
568 {
569 // If it is attached then let the frame send the event.
570 // Don't call frame->ProcessCommand(id) because it toggles
571 // checkable items and we've already done that above.
572 wxCommandEvent commandEvent(wxEVT_COMMAND_MENU_SELECTED, id);
573 commandEvent.SetEventObject(frame);
574 if (item->IsCheckable())
575 commandEvent.SetInt(item->IsChecked());
576 commandEvent.SetEventObject(menu);
577
578 frame->GetEventHandler()->ProcessEvent(commandEvent);
579 }
580 else
581 {
582 // otherwise let the menu have it
583 menu->SendEvent(id, item->IsCheckable() ? item->IsChecked() : -1);
584 }
585 }
586 }
587
588 //-----------------------------------------------------------------------------
589 // "select"
590 //-----------------------------------------------------------------------------
591
592 extern "C" {
593 static void gtk_menu_hilight_callback( GtkWidget *widget, wxMenu *menu )
594 {
595 if (g_isIdle) wxapp_install_idle_handler();
596
597 int id = menu->FindMenuIdByMenuItem(widget);
598
599 wxASSERT( id != -1 ); // should find it!
600
601 if (!menu->IsEnabled(id))
602 return;
603
604 wxMenuEvent event( wxEVT_MENU_HIGHLIGHT, id );
605 event.SetEventObject( menu );
606
607 wxEvtHandler* handler = menu->GetEventHandler();
608 if (handler && handler->ProcessEvent(event))
609 return;
610
611 wxWindow *win = menu->GetInvokingWindow();
612 if (win) win->GetEventHandler()->ProcessEvent( event );
613 }
614 }
615
616 //-----------------------------------------------------------------------------
617 // "deselect"
618 //-----------------------------------------------------------------------------
619
620 extern "C" {
621 static void gtk_menu_nolight_callback( GtkWidget *widget, wxMenu *menu )
622 {
623 if (g_isIdle) wxapp_install_idle_handler();
624
625 int id = menu->FindMenuIdByMenuItem(widget);
626
627 wxASSERT( id != -1 ); // should find it!
628
629 if (!menu->IsEnabled(id))
630 return;
631
632 wxMenuEvent event( wxEVT_MENU_HIGHLIGHT, -1 );
633 event.SetEventObject( menu );
634
635 wxEvtHandler* handler = menu->GetEventHandler();
636 if (handler && handler->ProcessEvent(event))
637 return;
638
639 wxWindow *win = menu->GetInvokingWindow();
640 if (win)
641 win->GetEventHandler()->ProcessEvent( event );
642 }
643 }
644
645 //-----------------------------------------------------------------------------
646 // wxMenuItem
647 //-----------------------------------------------------------------------------
648
649 IMPLEMENT_DYNAMIC_CLASS(wxMenuItem, wxObject)
650
651 wxMenuItem *wxMenuItemBase::New(wxMenu *parentMenu,
652 int id,
653 const wxString& name,
654 const wxString& help,
655 wxItemKind kind,
656 wxMenu *subMenu)
657 {
658 return new wxMenuItem(parentMenu, id, name, help, kind, subMenu);
659 }
660
661 wxMenuItem::wxMenuItem(wxMenu *parentMenu,
662 int id,
663 const wxString& text,
664 const wxString& help,
665 wxItemKind kind,
666 wxMenu *subMenu)
667 : wxMenuItemBase(parentMenu, id, text, help, kind, subMenu)
668 {
669 Init(text);
670 }
671
672 wxMenuItem::wxMenuItem(wxMenu *parentMenu,
673 int id,
674 const wxString& text,
675 const wxString& help,
676 bool isCheckable,
677 wxMenu *subMenu)
678 : wxMenuItemBase(parentMenu, id, text, help,
679 isCheckable ? wxITEM_CHECK : wxITEM_NORMAL, subMenu)
680 {
681 Init(text);
682 }
683
684 void wxMenuItem::Init(const wxString& text)
685 {
686 m_labelWidget = (GtkWidget *) NULL;
687 m_menuItem = (GtkWidget *) NULL;
688
689 DoSetText(text);
690 }
691
692 wxMenuItem::~wxMenuItem()
693 {
694 // don't delete menu items, the menus take care of that
695 }
696
697 // return the menu item text without any menu accels
698 /* static */
699 wxString wxMenuItemBase::GetLabelFromText(const wxString& text)
700 {
701 wxString label;
702
703 for ( const wxChar *pc = text.c_str(); *pc; pc++ )
704 {
705 if ( *pc == wxT('\t'))
706 break;
707
708 if ( *pc == wxT('_') )
709 {
710 // GTK 1.2 escapes "xxx_xxx" to "xxx__xxx"
711 pc++;
712 label += *pc;
713 continue;
714 }
715
716 if ( *pc == wxT('\\') )
717 {
718 // GTK 2.0 escapes "xxx/xxx" to "xxx\/xxx"
719 pc++;
720 label += *pc;
721 continue;
722 }
723
724 if ( (*pc == wxT('&')) && (*(pc+1) != wxT('&')) )
725 {
726 // wxMSW escapes "&"
727 // "&" is doubled to indicate "&" instead of accelerator
728 continue;
729 }
730
731 label += *pc;
732 }
733
734 // wxPrintf( wxT("GetLabelFromText(): text %s label %s\n"), text.c_str(), label.c_str() );
735
736 return label;
737 }
738
739 void wxMenuItem::SetText( const wxString& str )
740 {
741 // Some optimization to avoid flicker
742 wxString oldLabel = m_text;
743 oldLabel = wxStripMenuCodes(oldLabel);
744 oldLabel.Replace(wxT("_"), wxT(""));
745 wxString label1 = wxStripMenuCodes(str);
746 wxString oldhotkey = GetHotKey(); // Store the old hotkey in Ctrl-foo format
747 wxCharBuffer oldbuf = wxGTK_CONV_SYS( GetGtkHotKey(*this) ); // and as <control>foo
748
749 DoSetText(str);
750
751 if (oldLabel == label1 &&
752 oldhotkey == GetHotKey()) // Make sure we can change a hotkey even if the label is unaltered
753 return;
754
755 if (m_menuItem)
756 {
757 GtkLabel *label;
758 if (m_labelWidget)
759 label = (GtkLabel*) m_labelWidget;
760 else
761 label = GTK_LABEL( GTK_BIN(m_menuItem)->child );
762
763 gtk_label_set_text_with_mnemonic( GTK_LABEL(label), wxGTK_CONV_SYS(m_text) );
764 }
765
766 guint accel_key;
767 GdkModifierType accel_mods;
768 gtk_accelerator_parse( (const char*) oldbuf, &accel_key, &accel_mods);
769 if (accel_key != 0)
770 {
771 gtk_widget_remove_accelerator( GTK_WIDGET(m_menuItem),
772 m_parentMenu->m_accel,
773 accel_key,
774 accel_mods );
775 }
776
777 wxCharBuffer buf = wxGTK_CONV_SYS( GetGtkHotKey(*this) );
778 gtk_accelerator_parse( (const char*) buf, &accel_key, &accel_mods);
779 if (accel_key != 0)
780 {
781 gtk_widget_add_accelerator( GTK_WIDGET(m_menuItem),
782 "activate",
783 m_parentMenu->m_accel,
784 accel_key,
785 accel_mods,
786 GTK_ACCEL_VISIBLE);
787 }
788 }
789
790 // it's valid for this function to be called even if m_menuItem == NULL
791 void wxMenuItem::DoSetText( const wxString& str )
792 {
793 // '\t' is the deliminator indicating a hot key
794 m_text.Empty();
795 const wxChar *pc = str;
796 while ( (*pc != wxT('\0')) && (*pc != wxT('\t')) )
797 {
798 if ((*pc == wxT('&')) && (*(pc+1) == wxT('&')))
799 {
800 // "&" is doubled to indicate "&" instead of accelerator
801 ++pc;
802 m_text << wxT('&');
803 }
804 else if (*pc == wxT('&'))
805 {
806 m_text << wxT('_');
807 }
808 else if ( *pc == wxT('_') ) // escape underscores
809 {
810 m_text << wxT("__");
811 }
812 else
813 {
814 m_text << *pc;
815 }
816 ++pc;
817 }
818
819 m_hotKey = wxT("");
820
821 if(*pc == wxT('\t'))
822 {
823 pc++;
824 m_hotKey = pc;
825 }
826
827 // wxPrintf( wxT("DoSetText(): str %s m_text %s hotkey %s\n"), str.c_str(), m_text.c_str(), m_hotKey.c_str() );
828 }
829
830 #if wxUSE_ACCEL
831
832 wxAcceleratorEntry *wxMenuItem::GetAccel() const
833 {
834 if ( !GetHotKey() )
835 {
836 // nothing
837 return (wxAcceleratorEntry *)NULL;
838 }
839
840 // as wxGetAccelFromString() looks for TAB, insert a dummy one here
841 wxString label;
842 label << wxT('\t') << GetHotKey();
843
844 return wxGetAccelFromString(label);
845 }
846
847 #endif // wxUSE_ACCEL
848
849 void wxMenuItem::Check( bool check )
850 {
851 wxCHECK_RET( m_menuItem, wxT("invalid menu item") );
852
853 if (check == m_isChecked)
854 return;
855
856 wxMenuItemBase::Check( check );
857
858 switch ( GetKind() )
859 {
860 case wxITEM_CHECK:
861 case wxITEM_RADIO:
862 gtk_check_menu_item_set_active( (GtkCheckMenuItem*)m_menuItem, (gint)check );
863 break;
864
865 default:
866 wxFAIL_MSG( _T("can't check this item") );
867 }
868 }
869
870 void wxMenuItem::Enable( bool enable )
871 {
872 wxCHECK_RET( m_menuItem, wxT("invalid menu item") );
873
874 gtk_widget_set_sensitive( m_menuItem, enable );
875 wxMenuItemBase::Enable( enable );
876 }
877
878 bool wxMenuItem::IsChecked() const
879 {
880 wxCHECK_MSG( m_menuItem, false, wxT("invalid menu item") );
881
882 wxCHECK_MSG( IsCheckable(), false,
883 wxT("can't get state of uncheckable item!") );
884
885 return ((GtkCheckMenuItem*)m_menuItem)->active != 0;
886 }
887
888 //-----------------------------------------------------------------------------
889 // wxMenu
890 //-----------------------------------------------------------------------------
891
892 IMPLEMENT_DYNAMIC_CLASS(wxMenu,wxEvtHandler)
893
894 void wxMenu::Init()
895 {
896 m_accel = gtk_accel_group_new();
897 m_menu = gtk_menu_new();
898 // NB: keep reference to the menu so that it is not destroyed behind
899 // our back by GTK+ e.g. when it is removed from menubar:
900 gtk_widget_ref(m_menu);
901
902 m_owner = (GtkWidget*) NULL;
903
904 // Tearoffs are entries, just like separators. So if we want this
905 // menu to be a tear-off one, we just append a tearoff entry
906 // immediately.
907 if ( m_style & wxMENU_TEAROFF )
908 {
909 GtkWidget *tearoff = gtk_tearoff_menu_item_new();
910
911 gtk_menu_shell_append(GTK_MENU_SHELL(m_menu), tearoff);
912 }
913
914 m_prevRadio = NULL;
915
916 // append the title as the very first entry if we have it
917 if ( !m_title.empty() )
918 {
919 Append(wxGTK_TITLE_ID, m_title);
920 AppendSeparator();
921 }
922 }
923
924 wxMenu::~wxMenu()
925 {
926 WX_CLEAR_LIST(wxMenuItemList, m_items);
927
928 if ( GTK_IS_WIDGET( m_menu ))
929 {
930 // see wxMenu::Init
931 gtk_widget_unref( m_menu );
932 // if the menu is inserted in another menu at this time, there was
933 // one more reference to it:
934 if ( m_owner )
935 gtk_widget_destroy( m_menu );
936 }
937 }
938
939 bool wxMenu::GtkAppend(wxMenuItem *mitem, int pos)
940 {
941 GtkWidget *menuItem;
942
943 wxString text;
944
945 if ( mitem->IsSeparator() )
946 {
947 menuItem = gtk_separator_menu_item_new();
948 }
949 else if ( mitem->GetBitmap().Ok() ||
950 (mitem->GetKind() == wxITEM_NORMAL &&
951 wxIsStockID(mitem->GetId())) )
952 {
953 text = mitem->GetText();
954 wxBitmap bitmap(mitem->GetBitmap());
955
956 menuItem = gtk_image_menu_item_new_with_mnemonic( wxGTK_CONV_SYS( text ) );
957
958 GtkWidget *image;
959 if ( !bitmap.Ok() )
960 {
961 // use stock bitmap for this item if available on the assumption
962 // that it never hurts to follow GTK+ conventions more closely
963 const char *stock = wxGetStockGtkID(mitem->GetId());
964 image = stock ? gtk_image_new_from_stock(stock, GTK_ICON_SIZE_MENU)
965 : NULL;
966 }
967 else // we have a custom bitmap
968 {
969 wxASSERT_MSG( mitem->GetKind() == wxITEM_NORMAL,
970 _T("only normal menu items can have bitmaps") );
971
972 if ( bitmap.HasPixbuf() )
973 {
974 image = gtk_image_new_from_pixbuf(bitmap.GetPixbuf());
975 }
976 else
977 {
978 GdkPixmap *gdk_pixmap = bitmap.GetPixmap();
979 GdkBitmap *gdk_bitmap = bitmap.GetMask() ?
980 bitmap.GetMask()->GetBitmap() :
981 (GdkBitmap*) NULL;
982 image = gtk_image_new_from_pixmap( gdk_pixmap, gdk_bitmap );
983 }
984 }
985
986 if ( image )
987 {
988 gtk_widget_show(image);
989
990 gtk_image_menu_item_set_image( GTK_IMAGE_MENU_ITEM(menuItem), image );
991 }
992
993 m_prevRadio = NULL;
994 }
995 else // a normal item
996 {
997 // text has "_" instead of "&" after mitem->SetText() so don't use it
998 text = mitem->GetText() ;
999
1000 switch ( mitem->GetKind() )
1001 {
1002 case wxITEM_CHECK:
1003 {
1004 menuItem = gtk_check_menu_item_new_with_mnemonic( wxGTK_CONV_SYS( text ) );
1005 m_prevRadio = NULL;
1006 break;
1007 }
1008
1009 case wxITEM_RADIO:
1010 {
1011 GSList *group = NULL;
1012 if ( m_prevRadio == NULL )
1013 {
1014 // start of a new radio group
1015 m_prevRadio = menuItem =
1016 gtk_radio_menu_item_new_with_mnemonic( group, wxGTK_CONV_SYS( text ) );
1017 }
1018 else // continue the radio group
1019 {
1020 group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (m_prevRadio));
1021 m_prevRadio = menuItem =
1022 gtk_radio_menu_item_new_with_mnemonic( group, wxGTK_CONV_SYS( text ) );
1023 }
1024 break;
1025 }
1026
1027 default:
1028 wxFAIL_MSG( _T("unexpected menu item kind") );
1029 // fall through
1030
1031 case wxITEM_NORMAL:
1032 {
1033 menuItem = gtk_menu_item_new_with_mnemonic( wxGTK_CONV_SYS( text ) );
1034 m_prevRadio = NULL;
1035 break;
1036 }
1037 }
1038
1039 }
1040
1041 guint accel_key;
1042 GdkModifierType accel_mods;
1043 wxCharBuffer buf = wxGTK_CONV_SYS( GetGtkHotKey(*mitem) );
1044
1045 // wxPrintf( wxT("item: %s hotkey %s\n"), mitem->GetText().c_str(), GetGtkHotKey(*mitem).c_str() );
1046 gtk_accelerator_parse( (const char*) buf, &accel_key, &accel_mods);
1047 if (accel_key != 0)
1048 {
1049 gtk_widget_add_accelerator (GTK_WIDGET(menuItem),
1050 "activate",
1051 m_accel,
1052 accel_key,
1053 accel_mods,
1054 GTK_ACCEL_VISIBLE);
1055 }
1056
1057 if (pos == -1)
1058 gtk_menu_shell_append(GTK_MENU_SHELL(m_menu), menuItem);
1059 else
1060 gtk_menu_shell_insert(GTK_MENU_SHELL(m_menu), menuItem, pos);
1061
1062 gtk_widget_show( menuItem );
1063
1064 if ( !mitem->IsSeparator() )
1065 {
1066 wxASSERT_MSG( menuItem, wxT("invalid menuitem") );
1067
1068 g_signal_connect (menuItem, "select",
1069 G_CALLBACK (gtk_menu_hilight_callback), this);
1070 g_signal_connect (menuItem, "deselect",
1071 G_CALLBACK (gtk_menu_nolight_callback), this);
1072
1073 if ( mitem->IsSubMenu() && mitem->GetKind() != wxITEM_RADIO && mitem->GetKind() != wxITEM_CHECK )
1074 {
1075 gtk_menu_item_set_submenu( GTK_MENU_ITEM(menuItem), mitem->GetSubMenu()->m_menu );
1076
1077 gtk_widget_show( mitem->GetSubMenu()->m_menu );
1078
1079 // if adding a submenu to a menu already existing in the menu bar, we
1080 // must set invoking window to allow processing events from this
1081 // submenu
1082 if ( m_invokingWindow )
1083 wxMenubarSetInvokingWindow(mitem->GetSubMenu(), m_invokingWindow);
1084 }
1085 else
1086 {
1087 g_signal_connect (menuItem, "activate",
1088 G_CALLBACK (gtk_menu_clicked_callback),
1089 this);
1090 }
1091 }
1092
1093 mitem->SetMenuItem(menuItem);
1094
1095 if (ms_locked)
1096 {
1097 // This doesn't even exist!
1098 // gtk_widget_lock_accelerators(mitem->GetMenuItem());
1099 }
1100
1101 return true;
1102 }
1103
1104 wxMenuItem* wxMenu::DoAppend(wxMenuItem *mitem)
1105 {
1106 if (!GtkAppend(mitem))
1107 return NULL;
1108
1109 return wxMenuBase::DoAppend(mitem);
1110 }
1111
1112 wxMenuItem* wxMenu::DoInsert(size_t pos, wxMenuItem *item)
1113 {
1114 if ( !wxMenuBase::DoInsert(pos, item) )
1115 return NULL;
1116
1117 // TODO
1118 if ( !GtkAppend(item, (int)pos) )
1119 return NULL;
1120
1121 return item;
1122 }
1123
1124 wxMenuItem *wxMenu::DoRemove(wxMenuItem *item)
1125 {
1126 if ( !wxMenuBase::DoRemove(item) )
1127 return (wxMenuItem *)NULL;
1128
1129 // TODO: this code doesn't delete the item factory item and this seems
1130 // impossible as of GTK 1.2.6.
1131 gtk_widget_destroy( item->GetMenuItem() );
1132
1133 return item;
1134 }
1135
1136 int wxMenu::FindMenuIdByMenuItem( GtkWidget *menuItem ) const
1137 {
1138 wxMenuItemList::compatibility_iterator node = m_items.GetFirst();
1139 while (node)
1140 {
1141 wxMenuItem *item = node->GetData();
1142 if (item->GetMenuItem() == menuItem)
1143 return item->GetId();
1144 node = node->GetNext();
1145 }
1146
1147 return wxNOT_FOUND;
1148 }
1149
1150 // ----------------------------------------------------------------------------
1151 // helpers
1152 // ----------------------------------------------------------------------------
1153
1154 #if wxUSE_ACCEL
1155
1156 static wxString GetGtkHotKey( const wxMenuItem& item )
1157 {
1158 wxString hotkey;
1159
1160 wxAcceleratorEntry *accel = item.GetAccel();
1161 if ( accel )
1162 {
1163 int flags = accel->GetFlags();
1164 if ( flags & wxACCEL_ALT )
1165 hotkey += wxT("<alt>");
1166 if ( flags & wxACCEL_CTRL )
1167 hotkey += wxT("<control>");
1168 if ( flags & wxACCEL_SHIFT )
1169 hotkey += wxT("<shift>");
1170
1171 int code = accel->GetKeyCode();
1172 switch ( code )
1173 {
1174 case WXK_F1:
1175 case WXK_F2:
1176 case WXK_F3:
1177 case WXK_F4:
1178 case WXK_F5:
1179 case WXK_F6:
1180 case WXK_F7:
1181 case WXK_F8:
1182 case WXK_F9:
1183 case WXK_F10:
1184 case WXK_F11:
1185 case WXK_F12:
1186 case WXK_F13:
1187 case WXK_F14:
1188 case WXK_F15:
1189 case WXK_F16:
1190 case WXK_F17:
1191 case WXK_F18:
1192 case WXK_F19:
1193 case WXK_F20:
1194 case WXK_F21:
1195 case WXK_F22:
1196 case WXK_F23:
1197 case WXK_F24:
1198 hotkey += wxString::Format(wxT("F%d"), code - WXK_F1 + 1);
1199 break;
1200
1201 // TODO: we should use gdk_keyval_name() (a.k.a.
1202 // XKeysymToString) here as well as hardcoding the keysym
1203 // names this might be not portable
1204 case WXK_INSERT:
1205 hotkey << wxT("Insert" );
1206 break;
1207 case WXK_DELETE:
1208 hotkey << wxT("Delete" );
1209 break;
1210 case WXK_UP:
1211 hotkey << wxT("Up" );
1212 break;
1213 case WXK_DOWN:
1214 hotkey << wxT("Down" );
1215 break;
1216 case WXK_PAGEUP:
1217 hotkey << wxT("PgUp" );
1218 break;
1219 case WXK_PAGEDOWN:
1220 hotkey << wxT("PgDn" );
1221 break;
1222 case WXK_LEFT:
1223 hotkey << wxT("Left" );
1224 break;
1225 case WXK_RIGHT:
1226 hotkey << wxT("Right" );
1227 break;
1228 case WXK_HOME:
1229 hotkey << wxT("Home" );
1230 break;
1231 case WXK_END:
1232 hotkey << wxT("End" );
1233 break;
1234 case WXK_RETURN:
1235 hotkey << wxT("Return" );
1236 break;
1237 case WXK_BACK:
1238 hotkey << wxT("BackSpace" );
1239 break;
1240 case WXK_TAB:
1241 hotkey << wxT("Tab" );
1242 break;
1243 case WXK_ESCAPE:
1244 hotkey << wxT("Esc" );
1245 break;
1246 case WXK_SPACE:
1247 hotkey << wxT("space" );
1248 break;
1249 case WXK_MULTIPLY:
1250 hotkey << wxT("Multiply" );
1251 break;
1252 case WXK_ADD:
1253 hotkey << wxT("Add" );
1254 break;
1255 case WXK_SEPARATOR:
1256 hotkey << wxT("Separator" );
1257 break;
1258 case WXK_SUBTRACT:
1259 hotkey << wxT("Subtract" );
1260 break;
1261 case WXK_DECIMAL:
1262 hotkey << wxT("Decimal" );
1263 break;
1264 case WXK_DIVIDE:
1265 hotkey << wxT("Divide" );
1266 break;
1267 case WXK_CANCEL:
1268 hotkey << wxT("Cancel" );
1269 break;
1270 case WXK_CLEAR:
1271 hotkey << wxT("Clear" );
1272 break;
1273 case WXK_MENU:
1274 hotkey << wxT("Menu" );
1275 break;
1276 case WXK_PAUSE:
1277 hotkey << wxT("Pause" );
1278 break;
1279 case WXK_CAPITAL:
1280 hotkey << wxT("Capital" );
1281 break;
1282 case WXK_SELECT:
1283 hotkey << wxT("Select" );
1284 break;
1285 case WXK_PRINT:
1286 hotkey << wxT("Print" );
1287 break;
1288 case WXK_EXECUTE:
1289 hotkey << wxT("Execute" );
1290 break;
1291 case WXK_SNAPSHOT:
1292 hotkey << wxT("Snapshot" );
1293 break;
1294 case WXK_HELP:
1295 hotkey << wxT("Help" );
1296 break;
1297 case WXK_NUMLOCK:
1298 hotkey << wxT("Num_Lock" );
1299 break;
1300 case WXK_SCROLL:
1301 hotkey << wxT("Scroll_Lock" );
1302 break;
1303 case WXK_NUMPAD_INSERT:
1304 hotkey << wxT("KP_Insert" );
1305 break;
1306 case WXK_NUMPAD_DELETE:
1307 hotkey << wxT("KP_Delete" );
1308 break;
1309 case WXK_NUMPAD_SPACE:
1310 hotkey << wxT("KP_Space" );
1311 break;
1312 case WXK_NUMPAD_TAB:
1313 hotkey << wxT("KP_Tab" );
1314 break;
1315 case WXK_NUMPAD_ENTER:
1316 hotkey << wxT("KP_Enter" );
1317 break;
1318 case WXK_NUMPAD_F1: case WXK_NUMPAD_F2: case WXK_NUMPAD_F3:
1319 case WXK_NUMPAD_F4:
1320 hotkey += wxString::Format(wxT("KP_F%d"), code - WXK_NUMPAD_F1 + 1);
1321 break;
1322 case WXK_NUMPAD_HOME:
1323 hotkey << wxT("KP_Home" );
1324 break;
1325 case WXK_NUMPAD_LEFT:
1326 hotkey << wxT("KP_Left" );
1327 break;
1328 case WXK_NUMPAD_UP:
1329 hotkey << wxT("KP_Up" );
1330 break;
1331 case WXK_NUMPAD_RIGHT:
1332 hotkey << wxT("KP_Right" );
1333 break;
1334 case WXK_NUMPAD_DOWN:
1335 hotkey << wxT("KP_Down" );
1336 break;
1337 case WXK_NUMPAD_PAGEUP:
1338 hotkey << wxT("KP_PgUp" );
1339 break;
1340 case WXK_NUMPAD_PAGEDOWN:
1341 hotkey << wxT("KP_PgDn" );
1342 break;
1343 case WXK_NUMPAD_END:
1344 hotkey << wxT("KP_End" );
1345 break;
1346 case WXK_NUMPAD_BEGIN:
1347 hotkey << wxT("KP_Begin" );
1348 break;
1349 case WXK_NUMPAD_EQUAL:
1350 hotkey << wxT("KP_Equal" );
1351 break;
1352 case WXK_NUMPAD_MULTIPLY:
1353 hotkey << wxT("KP_Multiply" );
1354 break;
1355 case WXK_NUMPAD_ADD:
1356 hotkey << wxT("KP_Add" );
1357 break;
1358 case WXK_NUMPAD_SEPARATOR:
1359 hotkey << wxT("KP_Separator" );
1360 break;
1361 case WXK_NUMPAD_SUBTRACT:
1362 hotkey << wxT("KP_Subtract" );
1363 break;
1364 case WXK_NUMPAD_DECIMAL:
1365 hotkey << wxT("KP_Decimal" );
1366 break;
1367 case WXK_NUMPAD_DIVIDE:
1368 hotkey << wxT("KP_Divide" );
1369 break;
1370 case WXK_NUMPAD0: case WXK_NUMPAD1: case WXK_NUMPAD2:
1371 case WXK_NUMPAD3: case WXK_NUMPAD4: case WXK_NUMPAD5:
1372 case WXK_NUMPAD6: case WXK_NUMPAD7: case WXK_NUMPAD8: case WXK_NUMPAD9:
1373 hotkey += wxString::Format(wxT("KP_%d"), code - WXK_NUMPAD0);
1374 break;
1375 case WXK_WINDOWS_LEFT:
1376 hotkey << wxT("Super_L" );
1377 break;
1378 case WXK_WINDOWS_RIGHT:
1379 hotkey << wxT("Super_R" );
1380 break;
1381 case WXK_WINDOWS_MENU:
1382 hotkey << wxT("Menu" );
1383 break;
1384 case WXK_COMMAND:
1385 hotkey << wxT("Command" );
1386 break;
1387 /* These probably wouldn't work as there is no SpecialX in gdk/keynames.txt
1388 case WXK_SPECIAL1: case WXK_SPECIAL2: case WXK_SPECIAL3: case WXK_SPECIAL4:
1389 case WXK_SPECIAL5: case WXK_SPECIAL6: case WXK_SPECIAL7: case WXK_SPECIAL8:
1390 case WXK_SPECIAL9: case WXK_SPECIAL10: case WXK_SPECIAL11: case WXK_SPECIAL12:
1391 case WXK_SPECIAL13: case WXK_SPECIAL14: case WXK_SPECIAL15: case WXK_SPECIAL16:
1392 case WXK_SPECIAL17: case WXK_SPECIAL18: case WXK_SPECIAL19: case WXK_SPECIAL20:
1393 hotkey += wxString::Format(wxT("Special%d"), code - WXK_SPECIAL1 + 1);
1394 break;
1395 */
1396 // if there are any other keys wxGetAccelFromString() may
1397 // return, we should process them here
1398
1399 default:
1400 if ( code < 127 )
1401 {
1402 wxString name = wxGTK_CONV_BACK( gdk_keyval_name((guint)code) );
1403 if ( name )
1404 {
1405 hotkey << name;
1406 break;
1407 }
1408 }
1409
1410 wxFAIL_MSG( wxT("unknown keyboard accel") );
1411 }
1412
1413 delete accel;
1414 }
1415
1416 return hotkey;
1417 }
1418
1419 #endif // wxUSE_ACCEL
1420
1421 // ----------------------------------------------------------------------------
1422 // Pop-up menu stuff
1423 // ----------------------------------------------------------------------------
1424
1425 #if wxUSE_MENUS_NATIVE
1426
1427 extern "C" WXDLLIMPEXP_CORE
1428 void gtk_pop_hide_callback( GtkWidget *WXUNUSED(widget), bool* is_waiting )
1429 {
1430 *is_waiting = false;
1431 }
1432
1433 WXDLLIMPEXP_CORE void SetInvokingWindow( wxMenu *menu, wxWindow* win )
1434 {
1435 menu->SetInvokingWindow( win );
1436
1437 wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
1438 while (node)
1439 {
1440 wxMenuItem *menuitem = node->GetData();
1441 if (menuitem->IsSubMenu())
1442 {
1443 SetInvokingWindow( menuitem->GetSubMenu(), win );
1444 }
1445
1446 node = node->GetNext();
1447 }
1448 }
1449
1450 extern "C" WXDLLIMPEXP_CORE
1451 void wxPopupMenuPositionCallback( GtkMenu *menu,
1452 gint *x, gint *y,
1453 gboolean * WXUNUSED(whatever),
1454 gpointer user_data )
1455 {
1456 // ensure that the menu appears entirely on screen
1457 GtkRequisition req;
1458 gtk_widget_get_child_requisition(GTK_WIDGET(menu), &req);
1459
1460 wxSize sizeScreen = wxGetDisplaySize();
1461 wxPoint *pos = (wxPoint*)user_data;
1462
1463 gint xmax = sizeScreen.x - req.width,
1464 ymax = sizeScreen.y - req.height;
1465
1466 *x = pos->x < xmax ? pos->x : xmax;
1467 *y = pos->y < ymax ? pos->y : ymax;
1468 }
1469
1470 bool wxWindowGTK::DoPopupMenu( wxMenu *menu, int x, int y )
1471 {
1472 wxCHECK_MSG( m_widget != NULL, false, wxT("invalid window") );
1473
1474 wxCHECK_MSG( menu != NULL, false, wxT("invalid popup-menu") );
1475
1476 // NOTE: if you change this code, you need to update
1477 // the same code in taskbar.cpp as well. This
1478 // is ugly code duplication, I know.
1479
1480 SetInvokingWindow( menu, this );
1481
1482 menu->UpdateUI();
1483
1484 bool is_waiting = true;
1485
1486 gulong handler = g_signal_connect (menu->m_menu, "hide",
1487 G_CALLBACK (gtk_pop_hide_callback),
1488 &is_waiting);
1489
1490 wxPoint pos;
1491 gpointer userdata;
1492 GtkMenuPositionFunc posfunc;
1493 if ( x == -1 && y == -1 )
1494 {
1495 // use GTK's default positioning algorithm
1496 userdata = NULL;
1497 posfunc = NULL;
1498 }
1499 else
1500 {
1501 pos = ClientToScreen(wxPoint(x, y));
1502 userdata = &pos;
1503 posfunc = wxPopupMenuPositionCallback;
1504 }
1505
1506 wxMenuEvent eventOpen(wxEVT_MENU_OPEN, -1, menu);
1507 DoCommonMenuCallbackCode(menu, eventOpen);
1508
1509 gtk_menu_popup(
1510 GTK_MENU(menu->m_menu),
1511 (GtkWidget *) NULL, // parent menu shell
1512 (GtkWidget *) NULL, // parent menu item
1513 posfunc, // function to position it
1514 userdata, // client data
1515 0, // button used to activate it
1516 gtk_get_current_event_time()
1517 );
1518
1519 while (is_waiting)
1520 {
1521 gtk_main_iteration();
1522 }
1523
1524 g_signal_handler_disconnect (menu->m_menu, handler);
1525
1526 wxMenuEvent eventClose(wxEVT_MENU_CLOSE, -1, menu);
1527 DoCommonMenuCallbackCode(menu, eventClose);
1528
1529 return true;
1530 }
1531
1532 #endif // wxUSE_MENUS_NATIVE