]> git.saurik.com Git - wxWidgets.git/blame - src/gtk/menu.cpp
Added chapter on collection and container classes to contents
[wxWidgets.git] / src / gtk / menu.cpp
CommitLineData
c801d85f
KB
1/////////////////////////////////////////////////////////////////////////////
2// Name: menu.cpp
3// Purpose:
4// Author: Robert Roebling
96fd301f 5// Id: $Id$
a81258be 6// Copyright: (c) 1998 Robert Roebling
96fd301f 7// Licence: wxWindows licence
c801d85f
KB
8/////////////////////////////////////////////////////////////////////////////
9
c801d85f
KB
10#ifdef __GNUG__
11#pragma implementation "menu.h"
6ca41e57 12#pragma implementation "menuitem.h"
c801d85f
KB
13#endif
14
96fd301f 15#include "wx/log.h"
30dea054 16#include "wx/intl.h"
06cfab17 17#include "wx/app.h"
3dfac970 18#include "wx/menu.h"
c801d85f 19
974e8d94
VZ
20#if wxUSE_ACCEL
21 #include "wx/accel.h"
22#endif // wxUSE_ACCEL
23
16c1f79c
RR
24#include <gdk/gdk.h>
25#include <gtk/gtk.h>
83624f79 26
acfd422a
RR
27//-----------------------------------------------------------------------------
28// idle system
29//-----------------------------------------------------------------------------
30
31extern void wxapp_install_idle_handler();
32extern bool g_isIdle;
33
717a57c2
VZ
34#if (GTK_MINOR_VERSION > 0) && wxUSE_ACCEL
35static wxString GetHotKey( const wxMenuItem& item );
36#endif
37
f6bcfd97
BP
38//-----------------------------------------------------------------------------
39// idle system
40//-----------------------------------------------------------------------------
41
42static wxString wxReplaceUnderscore( const wxString& title )
43{
44 const wxChar *pc;
45
46 /* GTK 1.2 wants to have "_" instead of "&" for accelerators */
47 wxString str;
48 for ( pc = title; *pc != wxT('\0'); pc++ )
49 {
50 if (*pc == wxT('&'))
51 {
52#if (GTK_MINOR_VERSION > 0) && (GTK_MICRO_VERSION > 0)
53 str << wxT('_');
54 }
55 else if (*pc == wxT('/'))
56 {
57 str << wxT('\\');
58#endif
59 }
60 else
61 {
62#if __WXGTK12__
63 if ( *pc == wxT('_') )
64 {
65 // underscores must be doubled to prevent them from being
66 // interpreted as accelerator character prefix by GTK
67 str << *pc;
68 }
69#endif // GTK+ 1.2
70
71 str << *pc;
72 }
73 }
74 return str;
75}
76
c801d85f
KB
77//-----------------------------------------------------------------------------
78// wxMenuBar
79//-----------------------------------------------------------------------------
80
81IMPLEMENT_DYNAMIC_CLASS(wxMenuBar,wxWindow)
82
3502e687
RR
83wxMenuBar::wxMenuBar( long style )
84{
1e133b7d 85 /* the parent window is known after wxFrame::SetMenu() */
23280650 86 m_needParent = FALSE;
ae53c98c 87 m_style = style;
9c884972 88 m_invokingWindow = (wxWindow*) NULL;
23280650 89
4dcaf11a 90 if (!PreCreation( (wxWindow*) NULL, wxDefaultPosition, wxDefaultSize ) ||
223d09f6 91 !CreateBase( (wxWindow*) NULL, -1, wxDefaultPosition, wxDefaultSize, style, wxDefaultValidator, wxT("menubar") ))
4dcaf11a 92 {
223d09f6 93 wxFAIL_MSG( wxT("wxMenuBar creation failed") );
455fadaa 94 return;
4dcaf11a 95 }
3502e687
RR
96
97 m_menus.DeleteContents( TRUE );
98
1e133b7d
RR
99 /* GTK 1.2.0 doesn't have gtk_item_factory_get_item(), but GTK 1.2.1 has. */
100#if (GTK_MINOR_VERSION > 0) && (GTK_MICRO_VERSION > 0)
101 m_accel = gtk_accel_group_new();
102 m_factory = gtk_item_factory_new( GTK_TYPE_MENU_BAR, "<main>", m_accel );
103 m_menubar = gtk_item_factory_get_widget( m_factory, "<main>" );
23280650 104#else
3502e687 105 m_menubar = gtk_menu_bar_new();
1e133b7d 106#endif
3502e687
RR
107
108 if (style & wxMB_DOCKABLE)
109 {
110 m_widget = gtk_handle_box_new();
c626a8b7
VZ
111 gtk_container_add( GTK_CONTAINER(m_widget), GTK_WIDGET(m_menubar) );
112 gtk_widget_show( GTK_WIDGET(m_menubar) );
3502e687
RR
113 }
114 else
115 {
116 m_widget = GTK_WIDGET(m_menubar);
117 }
118
119 PostCreation();
c4608a8a 120
db434467 121 ApplyWidgetStyle();
3502e687
RR
122}
123
96fd301f 124wxMenuBar::wxMenuBar()
c801d85f 125{
1e133b7d
RR
126 /* the parent window is known after wxFrame::SetMenu() */
127 m_needParent = FALSE;
ae53c98c 128 m_style = 0;
9c884972 129 m_invokingWindow = (wxWindow*) NULL;
23280650 130
4dcaf11a 131 if (!PreCreation( (wxWindow*) NULL, wxDefaultPosition, wxDefaultSize ) ||
223d09f6 132 !CreateBase( (wxWindow*) NULL, -1, wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, wxT("menubar") ))
4dcaf11a 133 {
223d09f6 134 wxFAIL_MSG( wxT("wxMenuBar creation failed") );
455fadaa 135 return;
4dcaf11a 136 }
974e8d94 137
83624f79 138 m_menus.DeleteContents( TRUE );
96fd301f 139
1e133b7d
RR
140 /* GTK 1.2.0 doesn't have gtk_item_factory_get_item(), but GTK 1.2.1 has. */
141#if (GTK_MINOR_VERSION > 0) && (GTK_MICRO_VERSION > 0)
142 m_accel = gtk_accel_group_new();
143 m_factory = gtk_item_factory_new( GTK_TYPE_MENU_BAR, "<main>", m_accel );
144 m_menubar = gtk_item_factory_get_widget( m_factory, "<main>" );
23280650 145#else
83624f79 146 m_menubar = gtk_menu_bar_new();
1e133b7d 147#endif
8bbe427f 148
828f655f 149 m_widget = GTK_WIDGET(m_menubar);
96fd301f 150
83624f79 151 PostCreation();
c4608a8a 152
db434467 153 ApplyWidgetStyle();
6de97a3b 154}
c801d85f 155
1e133b7d
RR
156wxMenuBar::~wxMenuBar()
157{
d1b15f03 158// gtk_object_unref( GTK_OBJECT(m_factory) ); why not ?
1e133b7d
RR
159}
160
5bd9e519
RR
161static void wxMenubarUnsetInvokingWindow( wxMenu *menu, wxWindow *win )
162{
163 menu->SetInvokingWindow( (wxWindow*) NULL );
164
165#if (GTK_MINOR_VERSION > 0)
166 wxWindow *top_frame = win;
8487f887 167 while (top_frame->GetParent() && !(top_frame->IsTopLevel()))
bd77da97 168 top_frame = top_frame->GetParent();
5bd9e519
RR
169
170 /* support for native hot keys */
171 gtk_accel_group_detach( menu->m_accel, GTK_OBJECT(top_frame->m_widget) );
172#endif
173
1987af7e 174 wxMenuItemList::Node *node = menu->GetMenuItems().GetFirst();
5bd9e519
RR
175 while (node)
176 {
1987af7e 177 wxMenuItem *menuitem = node->GetData();
5bd9e519
RR
178 if (menuitem->IsSubMenu())
179 wxMenubarUnsetInvokingWindow( menuitem->GetSubMenu(), win );
1987af7e 180 node = node->GetNext();
5bd9e519
RR
181 }
182}
183
184static void wxMenubarSetInvokingWindow( wxMenu *menu, wxWindow *win )
185{
186 menu->SetInvokingWindow( win );
187
b4da05a6 188#if (GTK_MINOR_VERSION > 0) && (GTK_MICRO_VERSION > 0)
5bd9e519 189 wxWindow *top_frame = win;
8487f887 190 while (top_frame->GetParent() && !(top_frame->IsTopLevel()))
bd77da97 191 top_frame = top_frame->GetParent();
5bd9e519
RR
192
193 /* support for native hot keys */
b4da05a6
VZ
194 GtkObject *obj = GTK_OBJECT(top_frame->m_widget);
195 if ( !g_slist_find( menu->m_accel->attach_objects, obj ) )
196 gtk_accel_group_attach( menu->m_accel, obj );
5bd9e519
RR
197#endif
198
1987af7e 199 wxMenuItemList::Node *node = menu->GetMenuItems().GetFirst();
5bd9e519
RR
200 while (node)
201 {
1987af7e 202 wxMenuItem *menuitem = node->GetData();
5bd9e519
RR
203 if (menuitem->IsSubMenu())
204 wxMenubarSetInvokingWindow( menuitem->GetSubMenu(), win );
1987af7e 205 node = node->GetNext();
5bd9e519
RR
206 }
207}
208
209void wxMenuBar::SetInvokingWindow( wxWindow *win )
210{
9c884972 211 m_invokingWindow = win;
5bd9e519
RR
212#if (GTK_MINOR_VERSION > 0) && (GTK_MICRO_VERSION > 0)
213 wxWindow *top_frame = win;
8487f887 214 while (top_frame->GetParent() && !(top_frame->IsTopLevel()))
bd77da97 215 top_frame = top_frame->GetParent();
5bd9e519
RR
216
217 /* support for native key accelerators indicated by underscroes */
b4da05a6
VZ
218 GtkObject *obj = GTK_OBJECT(top_frame->m_widget);
219 if ( !g_slist_find( m_accel->attach_objects, obj ) )
220 gtk_accel_group_attach( m_accel, obj );
5bd9e519
RR
221#endif
222
1987af7e 223 wxMenuList::Node *node = m_menus.GetFirst();
5bd9e519
RR
224 while (node)
225 {
1987af7e 226 wxMenu *menu = node->GetData();
5bd9e519 227 wxMenubarSetInvokingWindow( menu, win );
1987af7e 228 node = node->GetNext();
5bd9e519
RR
229 }
230}
231
232void wxMenuBar::UnsetInvokingWindow( wxWindow *win )
233{
9c884972 234 m_invokingWindow = (wxWindow*) NULL;
5bd9e519
RR
235#if (GTK_MINOR_VERSION > 0) && (GTK_MICRO_VERSION > 0)
236 wxWindow *top_frame = win;
8487f887 237 while (top_frame->GetParent() && !(top_frame->IsTopLevel()))
bd77da97 238 top_frame = top_frame->GetParent();
5bd9e519
RR
239
240 /* support for native key accelerators indicated by underscroes */
241 gtk_accel_group_detach( m_accel, GTK_OBJECT(top_frame->m_widget) );
242#endif
243
1987af7e 244 wxMenuList::Node *node = m_menus.GetFirst();
5bd9e519
RR
245 while (node)
246 {
1987af7e 247 wxMenu *menu = node->GetData();
5bd9e519 248 wxMenubarUnsetInvokingWindow( menu, win );
1987af7e 249 node = node->GetNext();
5bd9e519
RR
250 }
251}
252
3dfac970 253bool wxMenuBar::Append( wxMenu *menu, const wxString &title )
c801d85f 254{
f03ec224
VZ
255 if ( !wxMenuBarBase::Append( menu, title ) )
256 return FALSE;
257
258 return GtkAppend(menu, title);
259}
23280650 260
f03ec224
VZ
261bool wxMenuBar::GtkAppend(wxMenu *menu, const wxString& title)
262{
f6bcfd97 263 wxString str( wxReplaceUnderscore( title ) );
1e133b7d
RR
264
265 /* this doesn't have much effect right now */
266 menu->SetTitle( str );
23280650 267
1e133b7d
RR
268 /* GTK 1.2.0 doesn't have gtk_item_factory_get_item(), but GTK 1.2.1 has. */
269#if (GTK_MINOR_VERSION > 0) && (GTK_MICRO_VERSION > 0)
270
271 /* local buffer in multibyte form */
2b2edbed 272 wxString buf;
223d09f6 273 buf << wxT('/') << str.c_str();
c980c992 274
dc6c62a9 275 char *cbuf = new char[buf.Length()+1];
c980c992
GL
276 strcpy(cbuf, buf.mbc_str());
277
1e133b7d 278 GtkItemFactoryEntry entry;
c980c992 279 entry.path = (gchar *)cbuf; // const_cast
1e133b7d
RR
280 entry.accelerator = (gchar*) NULL;
281 entry.callback = (GtkItemFactoryCallback) NULL;
282 entry.callback_action = 0;
2b2edbed
KB
283 entry.item_type = "<Branch>";
284
1e133b7d 285 gtk_item_factory_create_item( m_factory, &entry, (gpointer) this, 2 ); /* what is 2 ? */
1e133b7d 286 /* in order to get the pointer to the item we need the item text _without_ underscores */
223d09f6 287 wxString tmp = wxT("<main>/");
f6bcfd97 288 const wxChar *pc;
223d09f6 289 for ( pc = str; *pc != wxT('\0'); pc++ )
1e133b7d 290 {
455fadaa
VZ
291 // contrary to the common sense, we must throw out _all_ underscores,
292 // (i.e. "Hello__World" => "HelloWorld" and not "Hello_World" as we
974e8d94 293 // might naively think). IMHO it's a bug in GTK+ (VZ)
223d09f6 294 while (*pc == wxT('_'))
455fadaa 295 pc++;
2b2edbed 296 tmp << *pc;
034be888 297 }
1e133b7d 298 menu->m_owner = gtk_item_factory_get_item( m_factory, tmp.mb_str() );
1e133b7d 299 gtk_menu_item_set_submenu( GTK_MENU_ITEM(menu->m_owner), menu->m_menu );
2b2edbed 300 delete [] cbuf;
1e133b7d 301#else
96fd301f 302
1e133b7d 303 menu->m_owner = gtk_menu_item_new_with_label( str.mb_str() );
2b1c162e
RR
304 gtk_widget_show( menu->m_owner );
305 gtk_menu_item_set_submenu( GTK_MENU_ITEM(menu->m_owner), menu->m_menu );
96fd301f 306
2b1c162e 307 gtk_menu_bar_append( GTK_MENU_BAR(m_menubar), menu->m_owner );
23280650 308
1e133b7d 309#endif
9c884972
RR
310
311 // m_invokingWindow is set after wxFrame::SetMenuBar(). This call enables
312 // adding menu later on.
313 if (m_invokingWindow)
314 wxMenubarSetInvokingWindow( menu, m_invokingWindow );
3dfac970
VZ
315
316 return TRUE;
317}
318
319bool wxMenuBar::Insert(size_t pos, wxMenu *menu, const wxString& title)
320{
321 if ( !wxMenuBarBase::Insert(pos, menu, title) )
322 return FALSE;
323
f03ec224
VZ
324#if __WXGTK12__
325 // GTK+ doesn't have a function to insert a menu using GtkItemFactory (as
326 // of version 1.2.6), so we first append the item and then change its
327 // index
328 if ( !GtkAppend(menu, title) )
329 return FALSE;
330
186baeb2
RR
331 if (pos+1 >= m_menus.GetCount())
332 return TRUE;
333
f03ec224
VZ
334 GtkMenuShell *menu_shell = GTK_MENU_SHELL(m_factory->widget);
335 gpointer data = g_list_last(menu_shell->children)->data;
336 menu_shell->children = g_list_remove(menu_shell->children, data);
337 menu_shell->children = g_list_insert(menu_shell->children, data, pos);
338
339 return TRUE;
340#else // GTK < 1.2
341 // this should be easy to do with GTK 1.0 - can use standard functions for
342 // this and don't need any hacks like above, but as I don't have GTK 1.0
343 // any more I can't do it
344 wxFAIL_MSG( wxT("TODO") );
3dfac970
VZ
345
346 return FALSE;
f03ec224 347#endif // GTK 1.2/1.0
3dfac970
VZ
348}
349
350wxMenu *wxMenuBar::Replace(size_t pos, wxMenu *menu, const wxString& title)
351{
f03ec224
VZ
352 // remove the old item and insert a new one
353 wxMenu *menuOld = Remove(pos);
354 if ( menuOld && !Insert(pos, menu, title) )
355 {
1d62a8b4 356 return (wxMenu*) NULL;
f03ec224 357 }
3dfac970 358
f03ec224
VZ
359 // either Insert() succeeded or Remove() failed and menuOld is NULL
360 return menuOld;
3dfac970
VZ
361}
362
363wxMenu *wxMenuBar::Remove(size_t pos)
364{
f03ec224
VZ
365 wxMenu *menu = wxMenuBarBase::Remove(pos);
366 if ( !menu )
1d62a8b4 367 return (wxMenu*) NULL;
f03ec224 368
589c9fff 369/*
c4608a8a 370 GtkMenuShell *menu_shell = GTK_MENU_SHELL(m_factory->widget);
589c9fff 371
1d62a8b4
RR
372 printf( "factory entries before %d\n", (int)g_slist_length(m_factory->items) );
373 printf( "menu shell entries before %d\n", (int)g_list_length( menu_shell->children ) );
589c9fff 374*/
c4608a8a 375
1d62a8b4
RR
376 // unparent calls unref() and that would delete the widget so we raise
377 // the ref count to 2 artificially before invoking unparent.
378 gtk_widget_ref( menu->m_menu );
379 gtk_widget_unparent( menu->m_menu );
c4608a8a 380
1d62a8b4 381 gtk_widget_destroy( menu->m_owner );
c4608a8a 382
589c9fff 383/*
1d62a8b4
RR
384 printf( "factory entries after %d\n", (int)g_slist_length(m_factory->items) );
385 printf( "menu shell entries after %d\n", (int)g_list_length( menu_shell->children ) );
589c9fff 386*/
c4608a8a 387
1d62a8b4 388 return menu;
6de97a3b 389}
96fd301f 390
716b7364 391static int FindMenuItemRecursive( const wxMenu *menu, const wxString &menuString, const wxString &itemString )
c801d85f 392{
f6bcfd97 393 if (wxMenuItem::GetLabelFromText(menu->GetTitle()) == wxMenuItem::GetLabelFromText(menuString))
83624f79
RR
394 {
395 int res = menu->FindItem( itemString );
c626a8b7
VZ
396 if (res != wxNOT_FOUND)
397 return res;
83624f79 398 }
c626a8b7 399
1987af7e 400 wxMenuItemList::Node *node = menu->GetMenuItems().GetFirst();
83624f79
RR
401 while (node)
402 {
1987af7e 403 wxMenuItem *item = node->GetData();
83624f79
RR
404 if (item->IsSubMenu())
405 return FindMenuItemRecursive(item->GetSubMenu(), menuString, itemString);
2b1c162e 406
1987af7e 407 node = node->GetNext();
83624f79
RR
408 }
409
c626a8b7
VZ
410 return wxNOT_FOUND;
411}
412
c801d85f
KB
413int wxMenuBar::FindMenuItem( const wxString &menuString, const wxString &itemString ) const
414{
1987af7e 415 wxMenuList::Node *node = m_menus.GetFirst();
83624f79
RR
416 while (node)
417 {
1987af7e 418 wxMenu *menu = node->GetData();
83624f79 419 int res = FindMenuItemRecursive( menu, menuString, itemString);
1987af7e
VZ
420 if (res != -1)
421 return res;
422 node = node->GetNext();
83624f79 423 }
1987af7e
VZ
424
425 return wxNOT_FOUND;
6de97a3b 426}
c801d85f 427
c626a8b7 428// Find a wxMenuItem using its id. Recurses down into sub-menus
96fd301f 429static wxMenuItem* FindMenuItemByIdRecursive(const wxMenu* menu, int id)
716b7364 430{
717a57c2 431 wxMenuItem* result = menu->FindChildItem(id);
716b7364 432
1987af7e 433 wxMenuItemList::Node *node = menu->GetMenuItems().GetFirst();
c626a8b7 434 while ( node && result == NULL )
83624f79 435 {
1987af7e 436 wxMenuItem *item = node->GetData();
83624f79 437 if (item->IsSubMenu())
c626a8b7 438 {
83624f79 439 result = FindMenuItemByIdRecursive( item->GetSubMenu(), id );
c626a8b7 440 }
1987af7e 441 node = node->GetNext();
83624f79 442 }
96fd301f 443
83624f79 444 return result;
6de97a3b 445}
716b7364 446
3dfac970 447wxMenuItem* wxMenuBar::FindItem( int id, wxMenu **menuForItem ) const
716b7364 448{
83624f79 449 wxMenuItem* result = 0;
1987af7e 450 wxMenuList::Node *node = m_menus.GetFirst();
83624f79
RR
451 while (node && result == 0)
452 {
1987af7e 453 wxMenu *menu = node->GetData();
83624f79 454 result = FindMenuItemByIdRecursive( menu, id );
1987af7e 455 node = node->GetNext();
83624f79 456 }
c626a8b7 457
3dfac970
VZ
458 if ( menuForItem )
459 {
460 *menuForItem = result ? result->GetMenu() : (wxMenu *)NULL;
461 }
c626a8b7 462
3dfac970 463 return result;
bbe0af5b
RR
464}
465
3dfac970 466void wxMenuBar::EnableTop( size_t pos, bool flag )
bbe0af5b 467{
3dfac970 468 wxMenuList::Node *node = m_menus.Item( pos );
c626a8b7 469
223d09f6 470 wxCHECK_RET( node, wxT("menu not found") );
c626a8b7 471
3dfac970 472 wxMenu* menu = node->GetData();
c626a8b7
VZ
473
474 if (menu->m_owner)
475 gtk_widget_set_sensitive( menu->m_owner, flag );
bbe0af5b
RR
476}
477
3dfac970 478wxString wxMenuBar::GetLabelTop( size_t pos ) const
bbe0af5b 479{
3dfac970 480 wxMenuList::Node *node = m_menus.Item( pos );
c626a8b7 481
223d09f6 482 wxCHECK_MSG( node, wxT("invalid"), wxT("menu not found") );
c626a8b7 483
3dfac970 484 wxMenu* menu = node->GetData();
c626a8b7 485
f6bcfd97
BP
486 wxString label;
487 wxString text( menu->GetTitle() );
488#if (GTK_MINOR_VERSION > 0)
489 for ( const wxChar *pc = text.c_str(); *pc; pc++ )
490 {
491 if ( *pc == wxT('_') || *pc == wxT('&') )
492 {
493 // '_' is the escape character for GTK+ and '&' is the one for
494 // wxWindows - skip both of them
495 continue;
496 }
497
498 label += *pc;
499 }
500#else // GTK+ 1.0
501 label = text;
502#endif // GTK+ 1.2/1.0
503
504 return label;
bbe0af5b
RR
505}
506
3dfac970 507void wxMenuBar::SetLabelTop( size_t pos, const wxString& label )
bbe0af5b 508{
3dfac970 509 wxMenuList::Node *node = m_menus.Item( pos );
c626a8b7 510
223d09f6 511 wxCHECK_RET( node, wxT("menu not found") );
c626a8b7 512
3dfac970 513 wxMenu* menu = node->GetData();
c626a8b7 514
f6bcfd97
BP
515 wxString str( wxReplaceUnderscore( label ) );
516
517 menu->SetTitle( str );
518
519 if (menu->m_owner)
520 {
521 GtkLabel *label = GTK_LABEL( GTK_BIN(menu->m_owner)->child );
522
523 /* set new text */
524 gtk_label_set( label, str.mb_str());
525
526 /* reparse key accel */
527 (void)gtk_label_parse_uline (GTK_LABEL(label), str.mb_str() );
528 gtk_accel_label_refetch( GTK_ACCEL_LABEL(label) );
529 }
530
bbe0af5b
RR
531}
532
c801d85f 533//-----------------------------------------------------------------------------
cf7a7e13 534// "activate"
c801d85f
KB
535//-----------------------------------------------------------------------------
536
6de97a3b 537static void gtk_menu_clicked_callback( GtkWidget *widget, wxMenu *menu )
c801d85f 538{
acfd422a 539 if (g_isIdle) wxapp_install_idle_handler();
c4608a8a 540
83624f79 541 int id = menu->FindMenuIdByMenuItem(widget);
96fd301f 542
83624f79 543 /* should find it for normal (not popup) menu */
c626a8b7 544 wxASSERT( (id != -1) || (menu->GetInvokingWindow() != NULL) );
96fd301f 545
c626a8b7
VZ
546 if (!menu->IsEnabled(id))
547 return;
96fd301f 548
717a57c2 549 wxMenuItem* item = menu->FindChildItem( id );
223d09f6 550 wxCHECK_RET( item, wxT("error in menu item callback") );
c626a8b7
VZ
551
552 if (item->IsCheckable())
2d17d68f 553 {
974e8d94
VZ
554 bool isReallyChecked = item->IsChecked();
555 if ( item->wxMenuItemBase::IsChecked() == isReallyChecked )
2d17d68f 556 {
c626a8b7 557 /* the menu item has been checked by calling wxMenuItem->Check() */
2d17d68f 558 return;
c626a8b7
VZ
559 }
560 else
561 {
974e8d94
VZ
562 /* the user pressed on the menu item -> report and make consistent
563 * again */
564 item->wxMenuItemBase::Check(isReallyChecked);
c626a8b7 565 }
2d17d68f
RR
566 }
567
83624f79
RR
568 wxCommandEvent event( wxEVT_COMMAND_MENU_SELECTED, id );
569 event.SetEventObject( menu );
3ca6a5f0
BP
570 if (item->IsCheckable())
571 event.SetInt( item->IsChecked() );
8bbe427f 572
9739d9ee 573#if wxUSE_MENU_CALLBACK
c626a8b7 574 if (menu->GetCallback())
83624f79 575 {
c626a8b7 576 (void) (*(menu->GetCallback())) (*menu, event);
83624f79
RR
577 return;
578 }
9739d9ee 579#endif // wxUSE_MENU_CALLBACK
cf7a7e13 580
c626a8b7
VZ
581 if (menu->GetEventHandler()->ProcessEvent(event))
582 return;
cf7a7e13 583
83624f79 584 wxWindow *win = menu->GetInvokingWindow();
c626a8b7
VZ
585 if (win)
586 win->GetEventHandler()->ProcessEvent( event );
cf7a7e13
RR
587}
588
589//-----------------------------------------------------------------------------
590// "select"
591//-----------------------------------------------------------------------------
592
593static void gtk_menu_hilight_callback( GtkWidget *widget, wxMenu *menu )
594{
acfd422a
RR
595 if (g_isIdle) wxapp_install_idle_handler();
596
83624f79
RR
597 int id = menu->FindMenuIdByMenuItem(widget);
598
599 wxASSERT( id != -1 ); // should find it!
cf7a7e13 600
c626a8b7
VZ
601 if (!menu->IsEnabled(id))
602 return;
cf7a7e13 603
342b6a2f 604 wxMenuEvent event( wxEVT_MENU_HIGHLIGHT, id );
83624f79 605 event.SetEventObject( menu );
cf7a7e13 606
c626a8b7
VZ
607 if (menu->GetEventHandler()->ProcessEvent(event))
608 return;
6de97a3b 609
83624f79
RR
610 wxWindow *win = menu->GetInvokingWindow();
611 if (win) win->GetEventHandler()->ProcessEvent( event );
6de97a3b 612}
c801d85f 613
cd743a6f
RR
614//-----------------------------------------------------------------------------
615// "deselect"
616//-----------------------------------------------------------------------------
617
618static void gtk_menu_nolight_callback( GtkWidget *widget, wxMenu *menu )
619{
acfd422a
RR
620 if (g_isIdle) wxapp_install_idle_handler();
621
cd743a6f
RR
622 int id = menu->FindMenuIdByMenuItem(widget);
623
624 wxASSERT( id != -1 ); // should find it!
625
c626a8b7
VZ
626 if (!menu->IsEnabled(id))
627 return;
cd743a6f
RR
628
629 wxMenuEvent event( wxEVT_MENU_HIGHLIGHT, -1 );
630 event.SetEventObject( menu );
631
c626a8b7
VZ
632 if (menu->GetEventHandler()->ProcessEvent(event))
633 return;
cd743a6f
RR
634
635 wxWindow *win = menu->GetInvokingWindow();
c626a8b7
VZ
636 if (win)
637 win->GetEventHandler()->ProcessEvent( event );
cd743a6f
RR
638}
639
cf7a7e13 640//-----------------------------------------------------------------------------
db1b4961 641// wxMenuItem
cf7a7e13
RR
642//-----------------------------------------------------------------------------
643
974e8d94
VZ
644IMPLEMENT_DYNAMIC_CLASS(wxMenuItem, wxMenuItemBase)
645
646wxMenuItem *wxMenuItemBase::New(wxMenu *parentMenu,
647 int id,
648 const wxString& name,
649 const wxString& help,
650 bool isCheckable,
651 wxMenu *subMenu)
652{
653 return new wxMenuItem(parentMenu, id, name, help, isCheckable, subMenu);
654}
96fd301f 655
974e8d94
VZ
656wxMenuItem::wxMenuItem(wxMenu *parentMenu,
657 int id,
658 const wxString& text,
659 const wxString& help,
660 bool isCheckable,
661 wxMenu *subMenu)
c801d85f 662{
974e8d94
VZ
663 m_id = id;
664 m_isCheckable = isCheckable;
83624f79
RR
665 m_isChecked = FALSE;
666 m_isEnabled = TRUE;
974e8d94
VZ
667 m_subMenu = subMenu;
668 m_parentMenu = parentMenu;
669 m_help = help;
670
83624f79 671 m_menuItem = (GtkWidget *) NULL;
974e8d94 672
974e8d94 673 DoSetText(text);
6de97a3b 674}
c801d85f 675
d1b15f03
RR
676wxMenuItem::~wxMenuItem()
677{
678 // don't delete menu items, the menus take care of that
679}
680
717a57c2 681// return the menu item text without any menu accels
3b59cdbf
VZ
682/* static */
683wxString wxMenuItemBase::GetLabelFromText(const wxString& text)
717a57c2
VZ
684{
685 wxString label;
686#if (GTK_MINOR_VERSION > 0)
3b59cdbf 687 for ( const wxChar *pc = text.c_str(); *pc; pc++ )
717a57c2 688 {
5f170f33 689 if ( *pc == wxT('_') || *pc == wxT('&') )
717a57c2 690 {
5f170f33
VZ
691 // '_' is the escape character for GTK+ and '&' is the one for
692 // wxWindows - skip both of them
717a57c2
VZ
693 continue;
694 }
695
696 label += *pc;
697 }
698#else // GTK+ 1.0
3b59cdbf 699 label = text;
717a57c2
VZ
700#endif // GTK+ 1.2/1.0
701
702 return label;
703}
704
705void wxMenuItem::SetText( const wxString& str )
706{
707 DoSetText(str);
354aa1e3
RR
708
709 if (m_menuItem)
710 {
711 GtkLabel *label = GTK_LABEL( GTK_BIN(m_menuItem)->child );
717a57c2
VZ
712
713 /* set new text */
354aa1e3 714 gtk_label_set( label, m_text.mb_str());
717a57c2
VZ
715
716 /* reparse key accel */
1987af7e 717 (void)gtk_label_parse_uline (GTK_LABEL(label), m_text.mb_str() );
354aa1e3
RR
718 gtk_accel_label_refetch( GTK_ACCEL_LABEL(label) );
719 }
720}
721
c626a8b7 722// it's valid for this function to be called even if m_menuItem == NULL
974e8d94 723void wxMenuItem::DoSetText( const wxString& str )
716b7364 724{
ab46dc18 725 /* '\t' is the deliminator indicating a hot key */
974e8d94 726 m_text.Empty();
ab46dc18 727 const wxChar *pc = str;
223d09f6 728 for (; (*pc != wxT('\0')) && (*pc != wxT('\t')); pc++ )
83624f79 729 {
223d09f6 730 if (*pc == wxT('&'))
23280650 731 {
034be888 732#if (GTK_MINOR_VERSION > 0)
223d09f6 733 m_text << wxT('_');
572d7461 734 }
223d09f6 735 else if ( *pc == wxT('_') ) // escape underscores
572d7461 736 {
223d09f6 737 m_text << wxT("__");
572d7461 738 }
223d09f6 739 else if (*pc == wxT('/')) /* we have to filter out slashes ... */
23280650 740 {
223d09f6 741 m_text << wxT('\\'); /* ... and replace them with back slashes */
034be888
RR
742#endif
743 }
d38ceae8 744 else
354aa1e3 745 m_text << *pc;
83624f79 746 }
23280650 747
837904f2 748 /* only GTK 1.2 knows about hot keys */
223d09f6 749 m_hotKey = wxT("");
ab46dc18 750#if (GTK_MINOR_VERSION > 0)
223d09f6 751 if(*pc == wxT('\t'))
d7dbc98a
KB
752 {
753 pc++;
754 m_hotKey = pc;
755 }
ab46dc18 756#endif
716b7364
RR
757}
758
717a57c2
VZ
759#if wxUSE_ACCEL
760
761wxAcceleratorEntry *wxMenuItem::GetAccel() const
762{
1987af7e 763 if ( !GetHotKey() )
717a57c2
VZ
764 {
765 // nothing
766 return (wxAcceleratorEntry *)NULL;
767 }
768
769 // as wxGetAccelFromString() looks for TAB, insert a dummy one here
770 wxString label;
1987af7e 771 label << wxT('\t') << GetHotKey();
717a57c2
VZ
772
773 return wxGetAccelFromString(label);
774}
775
776#endif // wxUSE_ACCEL
777
96fd301f 778void wxMenuItem::Check( bool check )
716b7364 779{
223d09f6 780 wxCHECK_RET( m_menuItem, wxT("invalid menu item") );
db1b4961 781
223d09f6 782 wxCHECK_RET( IsCheckable(), wxT("Can't check uncheckable item!") )
96fd301f 783
974e8d94
VZ
784 if (check == m_isChecked)
785 return;
2d17d68f 786
974e8d94 787 wxMenuItemBase::Check( check );
83624f79 788 gtk_check_menu_item_set_state( (GtkCheckMenuItem*)m_menuItem, (gint)check );
716b7364
RR
789}
790
8bbe427f
VZ
791void wxMenuItem::Enable( bool enable )
792{
223d09f6 793 wxCHECK_RET( m_menuItem, wxT("invalid menu item") );
db1b4961 794
83624f79 795 gtk_widget_set_sensitive( m_menuItem, enable );
974e8d94 796 wxMenuItemBase::Enable( enable );
a9c96bcc
RR
797}
798
96fd301f 799bool wxMenuItem::IsChecked() const
716b7364 800{
223d09f6 801 wxCHECK_MSG( m_menuItem, FALSE, wxT("invalid menu item") );
db1b4961 802
974e8d94
VZ
803 wxCHECK_MSG( IsCheckable(), FALSE,
804 wxT("can't get state of uncheckable item!") );
96fd301f 805
974e8d94 806 return ((GtkCheckMenuItem*)m_menuItem)->active != 0;
716b7364
RR
807}
808
354aa1e3
RR
809wxString wxMenuItem::GetFactoryPath() const
810{
f03ec224
VZ
811 /* in order to get the pointer to the item we need the item text _without_
812 underscores */
354aa1e3 813 wxString path( wxT("<main>/") );
717a57c2
VZ
814 path += GetLabel();
815
354aa1e3
RR
816 return path;
817}
818
db1b4961 819//-----------------------------------------------------------------------------
83624f79 820// wxMenu
db1b4961
RR
821//-----------------------------------------------------------------------------
822
c801d85f
KB
823IMPLEMENT_DYNAMIC_CLASS(wxMenu,wxEvtHandler)
824
717a57c2 825void wxMenu::Init()
c801d85f 826{
034be888
RR
827#if (GTK_MINOR_VERSION > 0)
828 m_accel = gtk_accel_group_new();
829 m_factory = gtk_item_factory_new( GTK_TYPE_MENU, "<main>", m_accel );
830 m_menu = gtk_item_factory_get_widget( m_factory, "<main>" );
23280650 831#else
83624f79 832 m_menu = gtk_menu_new(); // Do not show!
034be888 833#endif
8bbe427f 834
2b1c162e 835 m_owner = (GtkWidget*) NULL;
2b2edbed
KB
836
837#if (GTK_MINOR_VERSION > 0)
838 /* Tearoffs are entries, just like separators. So if we want this
839 menu to be a tear-off one, we just append a tearoff entry
840 immediately. */
841 if(m_style & wxMENU_TEAROFF)
842 {
843 GtkItemFactoryEntry entry;
844 entry.path = "/tearoff";
845 entry.callback = (GtkItemFactoryCallback) NULL;
846 entry.callback_action = 0;
847 entry.item_type = "<Tearoff>";
848 entry.accelerator = (gchar*) NULL;
849 gtk_item_factory_create_item( m_factory, &entry, (gpointer) this, 2 ); /* what is 2 ? */
23280650 850 //GtkWidget *menuItem = gtk_item_factory_get_widget( m_factory, "<main>/tearoff" );
2b2edbed
KB
851 }
852#endif
c801d85f 853
717a57c2
VZ
854 // append the title as the very first entry if we have it
855 if ( !!m_title )
d1b15f03 856 {
717a57c2
VZ
857 Append(-2, m_title);
858 AppendSeparator();
d1b15f03 859 }
717a57c2 860}
15a2076a 861
717a57c2
VZ
862wxMenu::~wxMenu()
863{
a583e623 864 m_items.Clear();
f03ec224 865
d1b15f03 866 gtk_widget_destroy( m_menu );
974e8d94 867
d1b15f03 868 gtk_object_unref( GTK_OBJECT(m_factory) );
c2dd8380
GL
869}
870
32db328c 871bool wxMenu::GtkAppend(wxMenuItem *mitem)
c2dd8380 872{
717a57c2 873 GtkWidget *menuItem;
96fd301f 874
717a57c2
VZ
875 if ( mitem->IsSeparator() )
876 {
08fc1744 877#if (GTK_MINOR_VERSION > 0)
717a57c2
VZ
878 GtkItemFactoryEntry entry;
879 entry.path = "/sep";
880 entry.callback = (GtkItemFactoryCallback) NULL;
881 entry.callback_action = 0;
882 entry.item_type = "<Separator>";
883 entry.accelerator = (gchar*) NULL;
884
885 gtk_item_factory_create_item( m_factory, &entry, (gpointer) this, 2 ); /* what is 2 ? */
886
887 /* this will be wrong for more than one separator. do we care? */
888 menuItem = gtk_item_factory_get_widget( m_factory, "<main>/sep" );
889#else // GTK+ 1.0
890 menuItem = gtk_menu_item_new();
891#endif // GTK 1.2/1.0
892 }
893 else if ( mitem->IsSubMenu() )
837904f2 894 {
717a57c2
VZ
895#if (GTK_MINOR_VERSION > 0)
896 /* text has "_" instead of "&" after mitem->SetText() */
897 wxString text( mitem->GetText() );
974e8d94 898
717a57c2
VZ
899 /* local buffer in multibyte form */
900 char buf[200];
901 strcpy( buf, "/" );
902 strcat( buf, text.mb_str() );
50592885 903
717a57c2
VZ
904 GtkItemFactoryEntry entry;
905 entry.path = buf;
906 entry.callback = (GtkItemFactoryCallback) 0;
907 entry.callback_action = 0;
908 entry.item_type = "<Branch>";
a583e623 909 entry.accelerator = (gchar*) NULL;
50592885 910
717a57c2 911 gtk_item_factory_create_item( m_factory, &entry, (gpointer) this, 2 ); /* what is 2 ? */
50592885 912
717a57c2 913 wxString path( mitem->GetFactoryPath() );
1987af7e 914 menuItem = gtk_item_factory_get_item( m_factory, path.mb_str() );
717a57c2 915#else // GTK+ 1.0
1987af7e 916 menuItem = gtk_menu_item_new_with_label(mitem->GetText().mbc_str());
717a57c2 917#endif // GTK 1.2/1.0
50592885 918
1987af7e 919 gtk_menu_item_set_submenu( GTK_MENU_ITEM(menuItem), mitem->GetSubMenu()->m_menu );
e7200790
VZ
920
921 // if adding a submenu to a menu already existing in the menu bar, we
922 // must set invoking window to allow processing events from this
923 // submenu
924 if ( m_invokingWindow )
925 wxMenubarSetInvokingWindow(mitem->GetSubMenu(), m_invokingWindow);
837904f2 926 }
717a57c2
VZ
927 else // a normal item
928 {
034be888 929#if (GTK_MINOR_VERSION > 0)
717a57c2
VZ
930 /* text has "_" instead of "&" after mitem->SetText() */
931 wxString text( mitem->GetText() );
932
933 /* local buffer in multibyte form */
934 char buf[200];
935 strcpy( buf, "/" );
936 strcat( buf, text.mb_str() );
937
938 GtkItemFactoryEntry entry;
939 entry.path = buf;
940 entry.callback = (GtkItemFactoryCallback) gtk_menu_clicked_callback;
941 entry.callback_action = 0;
1987af7e 942 if ( mitem->IsCheckable() )
717a57c2
VZ
943 entry.item_type = "<CheckItem>";
944 else
945 entry.item_type = "<Item>";
a583e623 946 entry.accelerator = (gchar*) NULL;
23280650 947
974e8d94 948#if wxUSE_ACCEL
717a57c2
VZ
949 // due to an apparent bug in GTK+, we have to use a static buffer here -
950 // otherwise GTK+ 1.2.2 manages to override the memory we pass to it
951 // somehow! (VZ)
3ca6a5f0
BP
952 static char s_accel[50]; // must be big enougg
953 wxString tmp( GetHotKey(*mitem) );
954 strncpy(s_accel, tmp.mb_str(), WXSIZEOF(s_accel));
717a57c2
VZ
955 entry.accelerator = s_accel;
956#else // !wxUSE_ACCEL
957 entry.accelerator = (char*) NULL;
958#endif // wxUSE_ACCEL/!wxUSE_ACCEL
96fd301f 959
717a57c2 960 gtk_item_factory_create_item( m_factory, &entry, (gpointer) this, 2 ); /* what is 2 ? */
cf7a7e13 961
717a57c2 962 wxString path( mitem->GetFactoryPath() );
1987af7e 963 menuItem = gtk_item_factory_get_widget( m_factory, path.mb_str() );
717a57c2 964#else // GTK+ 1.0
1987af7e
VZ
965 menuItem = checkable ? gtk_check_menu_item_new_with_label( mitem->GetText().mb_str() )
966 : gtk_menu_item_new_with_label( mitem->GetText().mb_str() );
034be888 967
717a57c2
VZ
968 gtk_signal_connect( GTK_OBJECT(menuItem), "activate",
969 GTK_SIGNAL_FUNC(gtk_menu_clicked_callback),
970 (gpointer)this );
971#endif // GTK+ 1.2/1.0
972 }
23280650 973
717a57c2
VZ
974 if ( !mitem->IsSeparator() )
975 {
976 gtk_signal_connect( GTK_OBJECT(menuItem), "select",
977 GTK_SIGNAL_FUNC(gtk_menu_hilight_callback),
978 (gpointer)this );
837904f2 979
717a57c2
VZ
980 gtk_signal_connect( GTK_OBJECT(menuItem), "deselect",
981 GTK_SIGNAL_FUNC(gtk_menu_nolight_callback),
982 (gpointer)this );
983 }
23280650 984
717a57c2 985#if GTK_MINOR_VERSION == 0
837904f2
RR
986 gtk_menu_append( GTK_MENU(m_menu), menuItem );
987 gtk_widget_show( menuItem );
717a57c2 988#endif // GTK+ 1.0
23280650 989
837904f2 990 mitem->SetMenuItem(menuItem);
837904f2 991
32db328c 992 return TRUE;
6de97a3b 993}
c801d85f 994
32db328c 995bool wxMenu::DoAppend(wxMenuItem *mitem)
828f655f 996{
32db328c 997 return GtkAppend(mitem) && wxMenuBase::DoAppend(mitem);
c33c4050
RR
998}
999
717a57c2 1000bool wxMenu::DoInsert(size_t pos, wxMenuItem *item)
c33c4050 1001{
717a57c2
VZ
1002 if ( !wxMenuBase::DoInsert(pos, item) )
1003 return FALSE;
c626a8b7 1004
32db328c
VZ
1005#ifdef __WXGTK12__
1006 // GTK+ doesn't have a function to insert a menu using GtkItemFactory (as
1007 // of version 1.2.6), so we first append the item and then change its
1008 // index
1009 if ( !GtkAppend(item) )
1010 return FALSE;
1011
f6bcfd97
BP
1012 if ( m_style & wxMENU_TEAROFF )
1013 {
1014 // change the position as the first item is the tear-off marker
1015 pos++;
1016 }
1017
32db328c
VZ
1018 GtkMenuShell *menu_shell = GTK_MENU_SHELL(m_factory->widget);
1019 gpointer data = g_list_last(menu_shell->children)->data;
1020 menu_shell->children = g_list_remove(menu_shell->children, data);
1021 menu_shell->children = g_list_insert(menu_shell->children, data, pos);
1022
1023 return TRUE;
1024#else // GTK < 1.2
1025 // this should be easy to do...
1026 wxFAIL_MSG( wxT("not implemented") );
c626a8b7 1027
717a57c2 1028 return FALSE;
96fa7876 1029#endif // GTK 1.2/1.0
c33c4050
RR
1030}
1031
717a57c2 1032wxMenuItem *wxMenu::DoRemove(wxMenuItem *item)
c33c4050 1033{
717a57c2
VZ
1034 if ( !wxMenuBase::DoRemove(item) )
1035 return (wxMenuItem *)NULL;
c626a8b7 1036
717a57c2
VZ
1037 // TODO: this code doesn't delete the item factory item and this seems
1038 // impossible as of GTK 1.2.6.
1039 gtk_widget_destroy( item->GetMenuItem() );
c626a8b7 1040
717a57c2 1041 return item;
c33c4050
RR
1042}
1043
96fd301f
VZ
1044int wxMenu::FindMenuIdByMenuItem( GtkWidget *menuItem ) const
1045{
83624f79
RR
1046 wxNode *node = m_items.First();
1047 while (node)
1048 {
1049 wxMenuItem *item = (wxMenuItem*)node->Data();
1050 if (item->GetMenuItem() == menuItem)
1051 return item->GetId();
1052 node = node->Next();
1053 }
96fd301f 1054
c626a8b7 1055 return wxNOT_FOUND;
6de97a3b 1056}
c801d85f 1057
717a57c2
VZ
1058// ----------------------------------------------------------------------------
1059// helpers
1060// ----------------------------------------------------------------------------
1061
1062#if (GTK_MINOR_VERSION > 0) && wxUSE_ACCEL
1063static wxString GetHotKey( const wxMenuItem& item )
c801d85f 1064{
717a57c2
VZ
1065 wxString hotkey;
1066
1067 wxAcceleratorEntry *accel = item.GetAccel();
1068 if ( accel )
83624f79 1069 {
717a57c2
VZ
1070 int flags = accel->GetFlags();
1071 if ( flags & wxACCEL_ALT )
1072 hotkey += wxT("<alt>");
1073 if ( flags & wxACCEL_CTRL )
1074 hotkey += wxT("<control>");
1075 if ( flags & wxACCEL_SHIFT )
1076 hotkey += wxT("<shift>");
1077
1078 int code = accel->GetKeyCode();
1079 switch ( code )
c626a8b7 1080 {
717a57c2
VZ
1081 case WXK_F1:
1082 case WXK_F2:
1083 case WXK_F3:
1084 case WXK_F4:
1085 case WXK_F5:
1086 case WXK_F6:
1087 case WXK_F7:
1088 case WXK_F8:
1089 case WXK_F9:
1090 case WXK_F10:
1091 case WXK_F11:
1092 case WXK_F12:
1093 hotkey << wxT('F') << code - WXK_F1 + 1;
1094 break;
3ca6a5f0
BP
1095
1096 // GTK seems to use XStringToKeySym here
1097 case WXK_NUMPAD_INSERT:
1098 hotkey << wxT("KP_Insert" );
1099 break;
1100 case WXK_NUMPAD_DELETE:
1101 hotkey << wxT("KP_Delete" );
1102 break;
1103 case WXK_INSERT:
1104 hotkey << wxT("Insert" );
1105 break;
1106 case WXK_DELETE:
1107 hotkey << wxT("Delete" );
1108 break;
96fd301f 1109
717a57c2
VZ
1110 // if there are any other keys wxGetAccelFromString() may return,
1111 // we should process them here
8bbe427f 1112
717a57c2
VZ
1113 default:
1114 if ( wxIsalnum(code) )
1115 {
1116 hotkey << (wxChar)code;
c801d85f 1117
717a57c2
VZ
1118 break;
1119 }
c801d85f 1120
717a57c2
VZ
1121 wxFAIL_MSG( wxT("unknown keyboard accel") );
1122 }
c801d85f 1123
717a57c2 1124 delete accel;
631f1bfe 1125 }
717a57c2
VZ
1126
1127 return hotkey;
631f1bfe 1128}
717a57c2 1129#endif // wxUSE_ACCEL
631f1bfe 1130