]> git.saurik.com Git - wxWidgets.git/blame - src/gtk1/menu.cpp
another small error
[wxWidgets.git] / src / gtk1 / 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 );
570 event.SetInt(id );
8bbe427f 571
9739d9ee 572#if wxUSE_MENU_CALLBACK
c626a8b7 573 if (menu->GetCallback())
83624f79 574 {
c626a8b7 575 (void) (*(menu->GetCallback())) (*menu, event);
83624f79
RR
576 return;
577 }
9739d9ee 578#endif // wxUSE_MENU_CALLBACK
cf7a7e13 579
c626a8b7
VZ
580 if (menu->GetEventHandler()->ProcessEvent(event))
581 return;
cf7a7e13 582
83624f79 583 wxWindow *win = menu->GetInvokingWindow();
c626a8b7
VZ
584 if (win)
585 win->GetEventHandler()->ProcessEvent( event );
cf7a7e13
RR
586}
587
588//-----------------------------------------------------------------------------
589// "select"
590//-----------------------------------------------------------------------------
591
592static void gtk_menu_hilight_callback( GtkWidget *widget, wxMenu *menu )
593{
acfd422a
RR
594 if (g_isIdle) wxapp_install_idle_handler();
595
83624f79
RR
596 int id = menu->FindMenuIdByMenuItem(widget);
597
598 wxASSERT( id != -1 ); // should find it!
cf7a7e13 599
c626a8b7
VZ
600 if (!menu->IsEnabled(id))
601 return;
cf7a7e13 602
342b6a2f 603 wxMenuEvent event( wxEVT_MENU_HIGHLIGHT, id );
83624f79 604 event.SetEventObject( menu );
cf7a7e13 605
c626a8b7
VZ
606 if (menu->GetEventHandler()->ProcessEvent(event))
607 return;
6de97a3b 608
83624f79
RR
609 wxWindow *win = menu->GetInvokingWindow();
610 if (win) win->GetEventHandler()->ProcessEvent( event );
6de97a3b 611}
c801d85f 612
cd743a6f
RR
613//-----------------------------------------------------------------------------
614// "deselect"
615//-----------------------------------------------------------------------------
616
617static void gtk_menu_nolight_callback( GtkWidget *widget, wxMenu *menu )
618{
acfd422a
RR
619 if (g_isIdle) wxapp_install_idle_handler();
620
cd743a6f
RR
621 int id = menu->FindMenuIdByMenuItem(widget);
622
623 wxASSERT( id != -1 ); // should find it!
624
c626a8b7
VZ
625 if (!menu->IsEnabled(id))
626 return;
cd743a6f
RR
627
628 wxMenuEvent event( wxEVT_MENU_HIGHLIGHT, -1 );
629 event.SetEventObject( menu );
630
c626a8b7
VZ
631 if (menu->GetEventHandler()->ProcessEvent(event))
632 return;
cd743a6f
RR
633
634 wxWindow *win = menu->GetInvokingWindow();
c626a8b7
VZ
635 if (win)
636 win->GetEventHandler()->ProcessEvent( event );
cd743a6f
RR
637}
638
cf7a7e13 639//-----------------------------------------------------------------------------
db1b4961 640// wxMenuItem
cf7a7e13
RR
641//-----------------------------------------------------------------------------
642
974e8d94
VZ
643IMPLEMENT_DYNAMIC_CLASS(wxMenuItem, wxMenuItemBase)
644
645wxMenuItem *wxMenuItemBase::New(wxMenu *parentMenu,
646 int id,
647 const wxString& name,
648 const wxString& help,
649 bool isCheckable,
650 wxMenu *subMenu)
651{
652 return new wxMenuItem(parentMenu, id, name, help, isCheckable, subMenu);
653}
96fd301f 654
974e8d94
VZ
655wxMenuItem::wxMenuItem(wxMenu *parentMenu,
656 int id,
657 const wxString& text,
658 const wxString& help,
659 bool isCheckable,
660 wxMenu *subMenu)
c801d85f 661{
974e8d94
VZ
662 m_id = id;
663 m_isCheckable = isCheckable;
83624f79
RR
664 m_isChecked = FALSE;
665 m_isEnabled = TRUE;
974e8d94
VZ
666 m_subMenu = subMenu;
667 m_parentMenu = parentMenu;
668 m_help = help;
669
83624f79 670 m_menuItem = (GtkWidget *) NULL;
974e8d94 671
974e8d94 672 DoSetText(text);
6de97a3b 673}
c801d85f 674
d1b15f03
RR
675wxMenuItem::~wxMenuItem()
676{
677 // don't delete menu items, the menus take care of that
678}
679
717a57c2 680// return the menu item text without any menu accels
3b59cdbf
VZ
681/* static */
682wxString wxMenuItemBase::GetLabelFromText(const wxString& text)
717a57c2
VZ
683{
684 wxString label;
685#if (GTK_MINOR_VERSION > 0)
3b59cdbf 686 for ( const wxChar *pc = text.c_str(); *pc; pc++ )
717a57c2 687 {
5f170f33 688 if ( *pc == wxT('_') || *pc == wxT('&') )
717a57c2 689 {
5f170f33
VZ
690 // '_' is the escape character for GTK+ and '&' is the one for
691 // wxWindows - skip both of them
717a57c2
VZ
692 continue;
693 }
694
695 label += *pc;
696 }
697#else // GTK+ 1.0
3b59cdbf 698 label = text;
717a57c2
VZ
699#endif // GTK+ 1.2/1.0
700
701 return label;
702}
703
704void wxMenuItem::SetText( const wxString& str )
705{
706 DoSetText(str);
354aa1e3
RR
707
708 if (m_menuItem)
709 {
710 GtkLabel *label = GTK_LABEL( GTK_BIN(m_menuItem)->child );
717a57c2
VZ
711
712 /* set new text */
354aa1e3 713 gtk_label_set( label, m_text.mb_str());
717a57c2
VZ
714
715 /* reparse key accel */
1987af7e 716 (void)gtk_label_parse_uline (GTK_LABEL(label), m_text.mb_str() );
354aa1e3
RR
717 gtk_accel_label_refetch( GTK_ACCEL_LABEL(label) );
718 }
719}
720
c626a8b7 721// it's valid for this function to be called even if m_menuItem == NULL
974e8d94 722void wxMenuItem::DoSetText( const wxString& str )
716b7364 723{
ab46dc18 724 /* '\t' is the deliminator indicating a hot key */
974e8d94 725 m_text.Empty();
ab46dc18 726 const wxChar *pc = str;
223d09f6 727 for (; (*pc != wxT('\0')) && (*pc != wxT('\t')); pc++ )
83624f79 728 {
223d09f6 729 if (*pc == wxT('&'))
23280650 730 {
034be888 731#if (GTK_MINOR_VERSION > 0)
223d09f6 732 m_text << wxT('_');
572d7461 733 }
223d09f6 734 else if ( *pc == wxT('_') ) // escape underscores
572d7461 735 {
223d09f6 736 m_text << wxT("__");
572d7461 737 }
223d09f6 738 else if (*pc == wxT('/')) /* we have to filter out slashes ... */
23280650 739 {
223d09f6 740 m_text << wxT('\\'); /* ... and replace them with back slashes */
034be888
RR
741#endif
742 }
d38ceae8 743 else
354aa1e3 744 m_text << *pc;
83624f79 745 }
23280650 746
837904f2 747 /* only GTK 1.2 knows about hot keys */
223d09f6 748 m_hotKey = wxT("");
ab46dc18 749#if (GTK_MINOR_VERSION > 0)
223d09f6 750 if(*pc == wxT('\t'))
d7dbc98a
KB
751 {
752 pc++;
753 m_hotKey = pc;
754 }
ab46dc18 755#endif
716b7364
RR
756}
757
717a57c2
VZ
758#if wxUSE_ACCEL
759
760wxAcceleratorEntry *wxMenuItem::GetAccel() const
761{
1987af7e 762 if ( !GetHotKey() )
717a57c2
VZ
763 {
764 // nothing
765 return (wxAcceleratorEntry *)NULL;
766 }
767
768 // as wxGetAccelFromString() looks for TAB, insert a dummy one here
769 wxString label;
1987af7e 770 label << wxT('\t') << GetHotKey();
717a57c2
VZ
771
772 return wxGetAccelFromString(label);
773}
774
775#endif // wxUSE_ACCEL
776
96fd301f 777void wxMenuItem::Check( bool check )
716b7364 778{
223d09f6 779 wxCHECK_RET( m_menuItem, wxT("invalid menu item") );
db1b4961 780
223d09f6 781 wxCHECK_RET( IsCheckable(), wxT("Can't check uncheckable item!") )
96fd301f 782
974e8d94
VZ
783 if (check == m_isChecked)
784 return;
2d17d68f 785
974e8d94 786 wxMenuItemBase::Check( check );
83624f79 787 gtk_check_menu_item_set_state( (GtkCheckMenuItem*)m_menuItem, (gint)check );
716b7364
RR
788}
789
8bbe427f
VZ
790void wxMenuItem::Enable( bool enable )
791{
223d09f6 792 wxCHECK_RET( m_menuItem, wxT("invalid menu item") );
db1b4961 793
83624f79 794 gtk_widget_set_sensitive( m_menuItem, enable );
974e8d94 795 wxMenuItemBase::Enable( enable );
a9c96bcc
RR
796}
797
96fd301f 798bool wxMenuItem::IsChecked() const
716b7364 799{
223d09f6 800 wxCHECK_MSG( m_menuItem, FALSE, wxT("invalid menu item") );
db1b4961 801
974e8d94
VZ
802 wxCHECK_MSG( IsCheckable(), FALSE,
803 wxT("can't get state of uncheckable item!") );
96fd301f 804
974e8d94 805 return ((GtkCheckMenuItem*)m_menuItem)->active != 0;
716b7364
RR
806}
807
354aa1e3
RR
808wxString wxMenuItem::GetFactoryPath() const
809{
f03ec224
VZ
810 /* in order to get the pointer to the item we need the item text _without_
811 underscores */
354aa1e3 812 wxString path( wxT("<main>/") );
717a57c2
VZ
813 path += GetLabel();
814
354aa1e3
RR
815 return path;
816}
817
db1b4961 818//-----------------------------------------------------------------------------
83624f79 819// wxMenu
db1b4961
RR
820//-----------------------------------------------------------------------------
821
c801d85f
KB
822IMPLEMENT_DYNAMIC_CLASS(wxMenu,wxEvtHandler)
823
717a57c2 824void wxMenu::Init()
c801d85f 825{
034be888
RR
826#if (GTK_MINOR_VERSION > 0)
827 m_accel = gtk_accel_group_new();
828 m_factory = gtk_item_factory_new( GTK_TYPE_MENU, "<main>", m_accel );
829 m_menu = gtk_item_factory_get_widget( m_factory, "<main>" );
23280650 830#else
83624f79 831 m_menu = gtk_menu_new(); // Do not show!
034be888 832#endif
8bbe427f 833
2b1c162e 834 m_owner = (GtkWidget*) NULL;
2b2edbed
KB
835
836#if (GTK_MINOR_VERSION > 0)
837 /* Tearoffs are entries, just like separators. So if we want this
838 menu to be a tear-off one, we just append a tearoff entry
839 immediately. */
840 if(m_style & wxMENU_TEAROFF)
841 {
842 GtkItemFactoryEntry entry;
843 entry.path = "/tearoff";
844 entry.callback = (GtkItemFactoryCallback) NULL;
845 entry.callback_action = 0;
846 entry.item_type = "<Tearoff>";
847 entry.accelerator = (gchar*) NULL;
848 gtk_item_factory_create_item( m_factory, &entry, (gpointer) this, 2 ); /* what is 2 ? */
23280650 849 //GtkWidget *menuItem = gtk_item_factory_get_widget( m_factory, "<main>/tearoff" );
2b2edbed
KB
850 }
851#endif
c801d85f 852
717a57c2
VZ
853 // append the title as the very first entry if we have it
854 if ( !!m_title )
d1b15f03 855 {
717a57c2
VZ
856 Append(-2, m_title);
857 AppendSeparator();
d1b15f03 858 }
717a57c2 859}
15a2076a 860
717a57c2
VZ
861wxMenu::~wxMenu()
862{
a583e623 863 m_items.Clear();
f03ec224 864
d1b15f03 865 gtk_widget_destroy( m_menu );
974e8d94 866
d1b15f03 867 gtk_object_unref( GTK_OBJECT(m_factory) );
c2dd8380
GL
868}
869
32db328c 870bool wxMenu::GtkAppend(wxMenuItem *mitem)
c2dd8380 871{
717a57c2 872 GtkWidget *menuItem;
96fd301f 873
717a57c2
VZ
874 if ( mitem->IsSeparator() )
875 {
08fc1744 876#if (GTK_MINOR_VERSION > 0)
717a57c2
VZ
877 GtkItemFactoryEntry entry;
878 entry.path = "/sep";
879 entry.callback = (GtkItemFactoryCallback) NULL;
880 entry.callback_action = 0;
881 entry.item_type = "<Separator>";
882 entry.accelerator = (gchar*) NULL;
883
884 gtk_item_factory_create_item( m_factory, &entry, (gpointer) this, 2 ); /* what is 2 ? */
885
886 /* this will be wrong for more than one separator. do we care? */
887 menuItem = gtk_item_factory_get_widget( m_factory, "<main>/sep" );
888#else // GTK+ 1.0
889 menuItem = gtk_menu_item_new();
890#endif // GTK 1.2/1.0
891 }
892 else if ( mitem->IsSubMenu() )
837904f2 893 {
717a57c2
VZ
894#if (GTK_MINOR_VERSION > 0)
895 /* text has "_" instead of "&" after mitem->SetText() */
896 wxString text( mitem->GetText() );
974e8d94 897
717a57c2
VZ
898 /* local buffer in multibyte form */
899 char buf[200];
900 strcpy( buf, "/" );
901 strcat( buf, text.mb_str() );
50592885 902
717a57c2
VZ
903 GtkItemFactoryEntry entry;
904 entry.path = buf;
905 entry.callback = (GtkItemFactoryCallback) 0;
906 entry.callback_action = 0;
907 entry.item_type = "<Branch>";
a583e623 908 entry.accelerator = (gchar*) NULL;
50592885 909
717a57c2 910 gtk_item_factory_create_item( m_factory, &entry, (gpointer) this, 2 ); /* what is 2 ? */
50592885 911
717a57c2 912 wxString path( mitem->GetFactoryPath() );
1987af7e 913 menuItem = gtk_item_factory_get_item( m_factory, path.mb_str() );
717a57c2 914#else // GTK+ 1.0
1987af7e 915 menuItem = gtk_menu_item_new_with_label(mitem->GetText().mbc_str());
717a57c2 916#endif // GTK 1.2/1.0
50592885 917
1987af7e 918 gtk_menu_item_set_submenu( GTK_MENU_ITEM(menuItem), mitem->GetSubMenu()->m_menu );
e7200790
VZ
919
920 // if adding a submenu to a menu already existing in the menu bar, we
921 // must set invoking window to allow processing events from this
922 // submenu
923 if ( m_invokingWindow )
924 wxMenubarSetInvokingWindow(mitem->GetSubMenu(), m_invokingWindow);
837904f2 925 }
717a57c2
VZ
926 else // a normal item
927 {
034be888 928#if (GTK_MINOR_VERSION > 0)
717a57c2
VZ
929 /* text has "_" instead of "&" after mitem->SetText() */
930 wxString text( mitem->GetText() );
931
932 /* local buffer in multibyte form */
933 char buf[200];
934 strcpy( buf, "/" );
935 strcat( buf, text.mb_str() );
936
937 GtkItemFactoryEntry entry;
938 entry.path = buf;
939 entry.callback = (GtkItemFactoryCallback) gtk_menu_clicked_callback;
940 entry.callback_action = 0;
1987af7e 941 if ( mitem->IsCheckable() )
717a57c2
VZ
942 entry.item_type = "<CheckItem>";
943 else
944 entry.item_type = "<Item>";
a583e623 945 entry.accelerator = (gchar*) NULL;
23280650 946
974e8d94 947#if wxUSE_ACCEL
717a57c2
VZ
948 // due to an apparent bug in GTK+, we have to use a static buffer here -
949 // otherwise GTK+ 1.2.2 manages to override the memory we pass to it
950 // somehow! (VZ)
951 static char s_accel[32]; // must be big enough for <control><alt><shift>F12
952 strncpy(s_accel, GetHotKey(*mitem).mb_str(), WXSIZEOF(s_accel));
953 entry.accelerator = s_accel;
954#else // !wxUSE_ACCEL
955 entry.accelerator = (char*) NULL;
956#endif // wxUSE_ACCEL/!wxUSE_ACCEL
96fd301f 957
717a57c2 958 gtk_item_factory_create_item( m_factory, &entry, (gpointer) this, 2 ); /* what is 2 ? */
cf7a7e13 959
717a57c2 960 wxString path( mitem->GetFactoryPath() );
1987af7e 961 menuItem = gtk_item_factory_get_widget( m_factory, path.mb_str() );
717a57c2 962#else // GTK+ 1.0
1987af7e
VZ
963 menuItem = checkable ? gtk_check_menu_item_new_with_label( mitem->GetText().mb_str() )
964 : gtk_menu_item_new_with_label( mitem->GetText().mb_str() );
034be888 965
717a57c2
VZ
966 gtk_signal_connect( GTK_OBJECT(menuItem), "activate",
967 GTK_SIGNAL_FUNC(gtk_menu_clicked_callback),
968 (gpointer)this );
969#endif // GTK+ 1.2/1.0
970 }
23280650 971
717a57c2
VZ
972 if ( !mitem->IsSeparator() )
973 {
974 gtk_signal_connect( GTK_OBJECT(menuItem), "select",
975 GTK_SIGNAL_FUNC(gtk_menu_hilight_callback),
976 (gpointer)this );
837904f2 977
717a57c2
VZ
978 gtk_signal_connect( GTK_OBJECT(menuItem), "deselect",
979 GTK_SIGNAL_FUNC(gtk_menu_nolight_callback),
980 (gpointer)this );
981 }
23280650 982
717a57c2 983#if GTK_MINOR_VERSION == 0
837904f2
RR
984 gtk_menu_append( GTK_MENU(m_menu), menuItem );
985 gtk_widget_show( menuItem );
717a57c2 986#endif // GTK+ 1.0
23280650 987
837904f2 988 mitem->SetMenuItem(menuItem);
837904f2 989
32db328c 990 return TRUE;
6de97a3b 991}
c801d85f 992
32db328c 993bool wxMenu::DoAppend(wxMenuItem *mitem)
828f655f 994{
32db328c 995 return GtkAppend(mitem) && wxMenuBase::DoAppend(mitem);
c33c4050
RR
996}
997
717a57c2 998bool wxMenu::DoInsert(size_t pos, wxMenuItem *item)
c33c4050 999{
717a57c2
VZ
1000 if ( !wxMenuBase::DoInsert(pos, item) )
1001 return FALSE;
c626a8b7 1002
32db328c
VZ
1003#ifdef __WXGTK12__
1004 // GTK+ doesn't have a function to insert a menu using GtkItemFactory (as
1005 // of version 1.2.6), so we first append the item and then change its
1006 // index
1007 if ( !GtkAppend(item) )
1008 return FALSE;
1009
f6bcfd97
BP
1010 if ( m_style & wxMENU_TEAROFF )
1011 {
1012 // change the position as the first item is the tear-off marker
1013 pos++;
1014 }
1015
32db328c
VZ
1016 GtkMenuShell *menu_shell = GTK_MENU_SHELL(m_factory->widget);
1017 gpointer data = g_list_last(menu_shell->children)->data;
1018 menu_shell->children = g_list_remove(menu_shell->children, data);
1019 menu_shell->children = g_list_insert(menu_shell->children, data, pos);
1020
1021 return TRUE;
1022#else // GTK < 1.2
1023 // this should be easy to do...
1024 wxFAIL_MSG( wxT("not implemented") );
c626a8b7 1025
717a57c2 1026 return FALSE;
96fa7876 1027#endif // GTK 1.2/1.0
c33c4050
RR
1028}
1029
717a57c2 1030wxMenuItem *wxMenu::DoRemove(wxMenuItem *item)
c33c4050 1031{
717a57c2
VZ
1032 if ( !wxMenuBase::DoRemove(item) )
1033 return (wxMenuItem *)NULL;
c626a8b7 1034
717a57c2
VZ
1035 // TODO: this code doesn't delete the item factory item and this seems
1036 // impossible as of GTK 1.2.6.
1037 gtk_widget_destroy( item->GetMenuItem() );
c626a8b7 1038
717a57c2 1039 return item;
c33c4050
RR
1040}
1041
96fd301f
VZ
1042int wxMenu::FindMenuIdByMenuItem( GtkWidget *menuItem ) const
1043{
83624f79
RR
1044 wxNode *node = m_items.First();
1045 while (node)
1046 {
1047 wxMenuItem *item = (wxMenuItem*)node->Data();
1048 if (item->GetMenuItem() == menuItem)
1049 return item->GetId();
1050 node = node->Next();
1051 }
96fd301f 1052
c626a8b7 1053 return wxNOT_FOUND;
6de97a3b 1054}
c801d85f 1055
717a57c2
VZ
1056// ----------------------------------------------------------------------------
1057// helpers
1058// ----------------------------------------------------------------------------
1059
1060#if (GTK_MINOR_VERSION > 0) && wxUSE_ACCEL
1061static wxString GetHotKey( const wxMenuItem& item )
c801d85f 1062{
717a57c2
VZ
1063 wxString hotkey;
1064
1065 wxAcceleratorEntry *accel = item.GetAccel();
1066 if ( accel )
83624f79 1067 {
717a57c2
VZ
1068 int flags = accel->GetFlags();
1069 if ( flags & wxACCEL_ALT )
1070 hotkey += wxT("<alt>");
1071 if ( flags & wxACCEL_CTRL )
1072 hotkey += wxT("<control>");
1073 if ( flags & wxACCEL_SHIFT )
1074 hotkey += wxT("<shift>");
1075
1076 int code = accel->GetKeyCode();
1077 switch ( code )
c626a8b7 1078 {
717a57c2
VZ
1079 case WXK_F1:
1080 case WXK_F2:
1081 case WXK_F3:
1082 case WXK_F4:
1083 case WXK_F5:
1084 case WXK_F6:
1085 case WXK_F7:
1086 case WXK_F8:
1087 case WXK_F9:
1088 case WXK_F10:
1089 case WXK_F11:
1090 case WXK_F12:
1091 hotkey << wxT('F') << code - WXK_F1 + 1;
1092 break;
96fd301f 1093
717a57c2
VZ
1094 // if there are any other keys wxGetAccelFromString() may return,
1095 // we should process them here
8bbe427f 1096
717a57c2
VZ
1097 default:
1098 if ( wxIsalnum(code) )
1099 {
1100 hotkey << (wxChar)code;
c801d85f 1101
717a57c2
VZ
1102 break;
1103 }
c801d85f 1104
717a57c2
VZ
1105 wxFAIL_MSG( wxT("unknown keyboard accel") );
1106 }
c801d85f 1107
717a57c2 1108 delete accel;
631f1bfe 1109 }
717a57c2
VZ
1110
1111 return hotkey;
631f1bfe 1112}
717a57c2 1113#endif // wxUSE_ACCEL
631f1bfe 1114