]> git.saurik.com Git - wxWidgets.git/blob - src/gtk/menu.cpp
Committing in .
[wxWidgets.git] / src / gtk / menu.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: menu.cpp
3 // Purpose:
4 // Author: Robert Roebling
5 // Id: $Id$
6 // Copyright: (c) 1998 Robert Roebling
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9
10 #ifdef __GNUG__
11 #pragma implementation "menu.h"
12 #pragma implementation "menuitem.h"
13 #endif
14
15 #include "wx/log.h"
16 #include "wx/intl.h"
17 #include "wx/app.h"
18 #include "wx/menu.h"
19
20 #if wxUSE_ACCEL
21 #include "wx/accel.h"
22 #endif // wxUSE_ACCEL
23
24 #include <gdk/gdk.h>
25 #include <gtk/gtk.h>
26
27 //-----------------------------------------------------------------------------
28 // idle system
29 //-----------------------------------------------------------------------------
30
31 extern void wxapp_install_idle_handler();
32 extern bool g_isIdle;
33
34 #if (GTK_MINOR_VERSION > 0) && wxUSE_ACCEL
35 static wxString GetHotKey( const wxMenuItem& item );
36 #endif
37
38 //-----------------------------------------------------------------------------
39 // idle system
40 //-----------------------------------------------------------------------------
41
42 static 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
77 //-----------------------------------------------------------------------------
78 // wxMenuBar
79 //-----------------------------------------------------------------------------
80
81 IMPLEMENT_DYNAMIC_CLASS(wxMenuBar,wxWindow)
82
83 wxMenuBar::wxMenuBar( long style )
84 {
85 /* the parent window is known after wxFrame::SetMenu() */
86 m_needParent = FALSE;
87 m_style = style;
88 m_invokingWindow = (wxWindow*) NULL;
89
90 if (!PreCreation( (wxWindow*) NULL, wxDefaultPosition, wxDefaultSize ) ||
91 !CreateBase( (wxWindow*) NULL, -1, wxDefaultPosition, wxDefaultSize, style, wxDefaultValidator, wxT("menubar") ))
92 {
93 wxFAIL_MSG( wxT("wxMenuBar creation failed") );
94 return;
95 }
96
97 m_menus.DeleteContents( TRUE );
98
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>" );
104 #else
105 m_menubar = gtk_menu_bar_new();
106 #endif
107
108 if (style & wxMB_DOCKABLE)
109 {
110 m_widget = gtk_handle_box_new();
111 gtk_container_add( GTK_CONTAINER(m_widget), GTK_WIDGET(m_menubar) );
112 gtk_widget_show( GTK_WIDGET(m_menubar) );
113 }
114 else
115 {
116 m_widget = GTK_WIDGET(m_menubar);
117 }
118
119 PostCreation();
120
121 ApplyWidgetStyle();
122 }
123
124 wxMenuBar::wxMenuBar()
125 {
126 /* the parent window is known after wxFrame::SetMenu() */
127 m_needParent = FALSE;
128 m_style = 0;
129 m_invokingWindow = (wxWindow*) NULL;
130
131 if (!PreCreation( (wxWindow*) NULL, wxDefaultPosition, wxDefaultSize ) ||
132 !CreateBase( (wxWindow*) NULL, -1, wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, wxT("menubar") ))
133 {
134 wxFAIL_MSG( wxT("wxMenuBar creation failed") );
135 return;
136 }
137
138 m_menus.DeleteContents( TRUE );
139
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>" );
145 #else
146 m_menubar = gtk_menu_bar_new();
147 #endif
148
149 m_widget = GTK_WIDGET(m_menubar);
150
151 PostCreation();
152
153 ApplyWidgetStyle();
154 }
155
156 wxMenuBar::~wxMenuBar()
157 {
158 // gtk_object_unref( GTK_OBJECT(m_factory) ); why not ?
159 }
160
161 static void wxMenubarUnsetInvokingWindow( wxMenu *menu, wxWindow *win )
162 {
163 menu->SetInvokingWindow( (wxWindow*) NULL );
164
165 #if (GTK_MINOR_VERSION > 0)
166 wxWindow *top_frame = win;
167 while (top_frame->GetParent() && !(top_frame->IsTopLevel()))
168 top_frame = top_frame->GetParent();
169
170 /* support for native hot keys */
171 gtk_accel_group_detach( menu->m_accel, GTK_OBJECT(top_frame->m_widget) );
172 #endif
173
174 wxMenuItemList::Node *node = menu->GetMenuItems().GetFirst();
175 while (node)
176 {
177 wxMenuItem *menuitem = node->GetData();
178 if (menuitem->IsSubMenu())
179 wxMenubarUnsetInvokingWindow( menuitem->GetSubMenu(), win );
180 node = node->GetNext();
181 }
182 }
183
184 static void wxMenubarSetInvokingWindow( wxMenu *menu, wxWindow *win )
185 {
186 menu->SetInvokingWindow( win );
187
188 #if (GTK_MINOR_VERSION > 0) && (GTK_MICRO_VERSION > 0)
189 wxWindow *top_frame = win;
190 while (top_frame->GetParent() && !(top_frame->IsTopLevel()))
191 top_frame = top_frame->GetParent();
192
193 /* support for native hot keys */
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 );
197 #endif
198
199 wxMenuItemList::Node *node = menu->GetMenuItems().GetFirst();
200 while (node)
201 {
202 wxMenuItem *menuitem = node->GetData();
203 if (menuitem->IsSubMenu())
204 wxMenubarSetInvokingWindow( menuitem->GetSubMenu(), win );
205 node = node->GetNext();
206 }
207 }
208
209 void wxMenuBar::SetInvokingWindow( wxWindow *win )
210 {
211 m_invokingWindow = win;
212 #if (GTK_MINOR_VERSION > 0) && (GTK_MICRO_VERSION > 0)
213 wxWindow *top_frame = win;
214 while (top_frame->GetParent() && !(top_frame->IsTopLevel()))
215 top_frame = top_frame->GetParent();
216
217 /* support for native key accelerators indicated by underscroes */
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 );
221 #endif
222
223 wxMenuList::Node *node = m_menus.GetFirst();
224 while (node)
225 {
226 wxMenu *menu = node->GetData();
227 wxMenubarSetInvokingWindow( menu, win );
228 node = node->GetNext();
229 }
230 }
231
232 void wxMenuBar::UnsetInvokingWindow( wxWindow *win )
233 {
234 m_invokingWindow = (wxWindow*) NULL;
235 #if (GTK_MINOR_VERSION > 0) && (GTK_MICRO_VERSION > 0)
236 wxWindow *top_frame = win;
237 while (top_frame->GetParent() && !(top_frame->IsTopLevel()))
238 top_frame = top_frame->GetParent();
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
244 wxMenuList::Node *node = m_menus.GetFirst();
245 while (node)
246 {
247 wxMenu *menu = node->GetData();
248 wxMenubarUnsetInvokingWindow( menu, win );
249 node = node->GetNext();
250 }
251 }
252
253 bool wxMenuBar::Append( wxMenu *menu, const wxString &title )
254 {
255 if ( !wxMenuBarBase::Append( menu, title ) )
256 return FALSE;
257
258 return GtkAppend(menu, title);
259 }
260
261 bool wxMenuBar::GtkAppend(wxMenu *menu, const wxString& title)
262 {
263 wxString str( wxReplaceUnderscore( title ) );
264
265 /* this doesn't have much effect right now */
266 menu->SetTitle( str );
267
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 */
272 wxString buf;
273 buf << wxT('/') << str.c_str();
274
275 char *cbuf = new char[buf.Length()+1];
276 strcpy(cbuf, buf.mbc_str());
277
278 GtkItemFactoryEntry entry;
279 entry.path = (gchar *)cbuf; // const_cast
280 entry.accelerator = (gchar*) NULL;
281 entry.callback = (GtkItemFactoryCallback) NULL;
282 entry.callback_action = 0;
283 entry.item_type = "<Branch>";
284
285 gtk_item_factory_create_item( m_factory, &entry, (gpointer) this, 2 ); /* what is 2 ? */
286 /* in order to get the pointer to the item we need the item text _without_ underscores */
287 wxString tmp = wxT("<main>/");
288 const wxChar *pc;
289 for ( pc = str; *pc != wxT('\0'); pc++ )
290 {
291 // contrary to the common sense, we must throw out _all_ underscores,
292 // (i.e. "Hello__World" => "HelloWorld" and not "Hello_World" as we
293 // might naively think). IMHO it's a bug in GTK+ (VZ)
294 while (*pc == wxT('_'))
295 pc++;
296 tmp << *pc;
297 }
298 menu->m_owner = gtk_item_factory_get_item( m_factory, tmp.mb_str() );
299 gtk_menu_item_set_submenu( GTK_MENU_ITEM(menu->m_owner), menu->m_menu );
300 delete [] cbuf;
301 #else
302
303 menu->m_owner = gtk_menu_item_new_with_label( str.mb_str() );
304 gtk_widget_show( menu->m_owner );
305 gtk_menu_item_set_submenu( GTK_MENU_ITEM(menu->m_owner), menu->m_menu );
306
307 gtk_menu_bar_append( GTK_MENU_BAR(m_menubar), menu->m_owner );
308
309 #endif
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 );
315
316 return TRUE;
317 }
318
319 bool wxMenuBar::Insert(size_t pos, wxMenu *menu, const wxString& title)
320 {
321 if ( !wxMenuBarBase::Insert(pos, menu, title) )
322 return FALSE;
323
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
331 if (pos+1 >= m_menus.GetCount())
332 return TRUE;
333
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") );
345
346 return FALSE;
347 #endif // GTK 1.2/1.0
348 }
349
350 wxMenu *wxMenuBar::Replace(size_t pos, wxMenu *menu, const wxString& title)
351 {
352 // remove the old item and insert a new one
353 wxMenu *menuOld = Remove(pos);
354 if ( menuOld && !Insert(pos, menu, title) )
355 {
356 return (wxMenu*) NULL;
357 }
358
359 // either Insert() succeeded or Remove() failed and menuOld is NULL
360 return menuOld;
361 }
362
363 wxMenu *wxMenuBar::Remove(size_t pos)
364 {
365 wxMenu *menu = wxMenuBarBase::Remove(pos);
366 if ( !menu )
367 return (wxMenu*) NULL;
368
369 /*
370 GtkMenuShell *menu_shell = GTK_MENU_SHELL(m_factory->widget);
371
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 ) );
374 */
375
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 );
380
381 gtk_widget_destroy( menu->m_owner );
382
383 /*
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 ) );
386 */
387
388 return menu;
389 }
390
391 static int FindMenuItemRecursive( const wxMenu *menu, const wxString &menuString, const wxString &itemString )
392 {
393 if (wxMenuItem::GetLabelFromText(menu->GetTitle()) == wxMenuItem::GetLabelFromText(menuString))
394 {
395 int res = menu->FindItem( itemString );
396 if (res != wxNOT_FOUND)
397 return res;
398 }
399
400 wxMenuItemList::Node *node = menu->GetMenuItems().GetFirst();
401 while (node)
402 {
403 wxMenuItem *item = node->GetData();
404 if (item->IsSubMenu())
405 return FindMenuItemRecursive(item->GetSubMenu(), menuString, itemString);
406
407 node = node->GetNext();
408 }
409
410 return wxNOT_FOUND;
411 }
412
413 int wxMenuBar::FindMenuItem( const wxString &menuString, const wxString &itemString ) const
414 {
415 wxMenuList::Node *node = m_menus.GetFirst();
416 while (node)
417 {
418 wxMenu *menu = node->GetData();
419 int res = FindMenuItemRecursive( menu, menuString, itemString);
420 if (res != -1)
421 return res;
422 node = node->GetNext();
423 }
424
425 return wxNOT_FOUND;
426 }
427
428 // Find a wxMenuItem using its id. Recurses down into sub-menus
429 static wxMenuItem* FindMenuItemByIdRecursive(const wxMenu* menu, int id)
430 {
431 wxMenuItem* result = menu->FindChildItem(id);
432
433 wxMenuItemList::Node *node = menu->GetMenuItems().GetFirst();
434 while ( node && result == NULL )
435 {
436 wxMenuItem *item = node->GetData();
437 if (item->IsSubMenu())
438 {
439 result = FindMenuItemByIdRecursive( item->GetSubMenu(), id );
440 }
441 node = node->GetNext();
442 }
443
444 return result;
445 }
446
447 wxMenuItem* wxMenuBar::FindItem( int id, wxMenu **menuForItem ) const
448 {
449 wxMenuItem* result = 0;
450 wxMenuList::Node *node = m_menus.GetFirst();
451 while (node && result == 0)
452 {
453 wxMenu *menu = node->GetData();
454 result = FindMenuItemByIdRecursive( menu, id );
455 node = node->GetNext();
456 }
457
458 if ( menuForItem )
459 {
460 *menuForItem = result ? result->GetMenu() : (wxMenu *)NULL;
461 }
462
463 return result;
464 }
465
466 void wxMenuBar::EnableTop( size_t pos, bool flag )
467 {
468 wxMenuList::Node *node = m_menus.Item( pos );
469
470 wxCHECK_RET( node, wxT("menu not found") );
471
472 wxMenu* menu = node->GetData();
473
474 if (menu->m_owner)
475 gtk_widget_set_sensitive( menu->m_owner, flag );
476 }
477
478 wxString wxMenuBar::GetLabelTop( size_t pos ) const
479 {
480 wxMenuList::Node *node = m_menus.Item( pos );
481
482 wxCHECK_MSG( node, wxT("invalid"), wxT("menu not found") );
483
484 wxMenu* menu = node->GetData();
485
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;
505 }
506
507 void wxMenuBar::SetLabelTop( size_t pos, const wxString& label )
508 {
509 wxMenuList::Node *node = m_menus.Item( pos );
510
511 wxCHECK_RET( node, wxT("menu not found") );
512
513 wxMenu* menu = node->GetData();
514
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
531 }
532
533 //-----------------------------------------------------------------------------
534 // "activate"
535 //-----------------------------------------------------------------------------
536
537 static void gtk_menu_clicked_callback( GtkWidget *widget, wxMenu *menu )
538 {
539 if (g_isIdle) wxapp_install_idle_handler();
540
541 int id = menu->FindMenuIdByMenuItem(widget);
542
543 /* should find it for normal (not popup) menu */
544 wxASSERT( (id != -1) || (menu->GetInvokingWindow() != NULL) );
545
546 if (!menu->IsEnabled(id))
547 return;
548
549 wxMenuItem* item = menu->FindChildItem( id );
550 wxCHECK_RET( item, wxT("error in menu item callback") );
551
552 if (item->IsCheckable())
553 {
554 bool isReallyChecked = item->IsChecked();
555 if ( item->wxMenuItemBase::IsChecked() == isReallyChecked )
556 {
557 /* the menu item has been checked by calling wxMenuItem->Check() */
558 return;
559 }
560 else
561 {
562 /* the user pressed on the menu item -> report and make consistent
563 * again */
564 item->wxMenuItemBase::Check(isReallyChecked);
565 }
566 }
567
568 wxCommandEvent event( wxEVT_COMMAND_MENU_SELECTED, id );
569 event.SetEventObject( menu );
570 if (item->IsCheckable())
571 event.SetInt( item->IsChecked() );
572
573 #if wxUSE_MENU_CALLBACK
574 if (menu->GetCallback())
575 {
576 (void) (*(menu->GetCallback())) (*menu, event);
577 return;
578 }
579 #endif // wxUSE_MENU_CALLBACK
580
581 if (menu->GetEventHandler()->ProcessEvent(event))
582 return;
583
584 wxWindow *win = menu->GetInvokingWindow();
585 if (win)
586 win->GetEventHandler()->ProcessEvent( event );
587 }
588
589 //-----------------------------------------------------------------------------
590 // "select"
591 //-----------------------------------------------------------------------------
592
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 if (menu->GetEventHandler()->ProcessEvent(event))
608 return;
609
610 wxWindow *win = menu->GetInvokingWindow();
611 if (win) win->GetEventHandler()->ProcessEvent( event );
612 }
613
614 //-----------------------------------------------------------------------------
615 // "deselect"
616 //-----------------------------------------------------------------------------
617
618 static void gtk_menu_nolight_callback( GtkWidget *widget, wxMenu *menu )
619 {
620 if (g_isIdle) wxapp_install_idle_handler();
621
622 int id = menu->FindMenuIdByMenuItem(widget);
623
624 wxASSERT( id != -1 ); // should find it!
625
626 if (!menu->IsEnabled(id))
627 return;
628
629 wxMenuEvent event( wxEVT_MENU_HIGHLIGHT, -1 );
630 event.SetEventObject( menu );
631
632 if (menu->GetEventHandler()->ProcessEvent(event))
633 return;
634
635 wxWindow *win = menu->GetInvokingWindow();
636 if (win)
637 win->GetEventHandler()->ProcessEvent( event );
638 }
639
640 //-----------------------------------------------------------------------------
641 // wxMenuItem
642 //-----------------------------------------------------------------------------
643
644 IMPLEMENT_DYNAMIC_CLASS(wxMenuItem, wxMenuItemBase)
645
646 wxMenuItem *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 }
655
656 wxMenuItem::wxMenuItem(wxMenu *parentMenu,
657 int id,
658 const wxString& text,
659 const wxString& help,
660 bool isCheckable,
661 wxMenu *subMenu)
662 {
663 m_id = id;
664 m_isCheckable = isCheckable;
665 m_isChecked = FALSE;
666 m_isEnabled = TRUE;
667 m_subMenu = subMenu;
668 m_parentMenu = parentMenu;
669 m_help = help;
670
671 m_menuItem = (GtkWidget *) NULL;
672
673 DoSetText(text);
674 }
675
676 wxMenuItem::~wxMenuItem()
677 {
678 // don't delete menu items, the menus take care of that
679 }
680
681 // return the menu item text without any menu accels
682 /* static */
683 wxString wxMenuItemBase::GetLabelFromText(const wxString& text)
684 {
685 wxString label;
686 #if (GTK_MINOR_VERSION > 0)
687 for ( const wxChar *pc = text.c_str(); *pc; pc++ )
688 {
689 if ( *pc == wxT('_') || *pc == wxT('&') )
690 {
691 // '_' is the escape character for GTK+ and '&' is the one for
692 // wxWindows - skip both of them
693 continue;
694 }
695
696 label += *pc;
697 }
698 #else // GTK+ 1.0
699 label = text;
700 #endif // GTK+ 1.2/1.0
701
702 return label;
703 }
704
705 void wxMenuItem::SetText( const wxString& str )
706 {
707 DoSetText(str);
708
709 if (m_menuItem)
710 {
711 GtkLabel *label = GTK_LABEL( GTK_BIN(m_menuItem)->child );
712
713 /* set new text */
714 gtk_label_set( label, m_text.mb_str());
715
716 /* reparse key accel */
717 (void)gtk_label_parse_uline (GTK_LABEL(label), m_text.mb_str() );
718 gtk_accel_label_refetch( GTK_ACCEL_LABEL(label) );
719 }
720 }
721
722 // it's valid for this function to be called even if m_menuItem == NULL
723 void wxMenuItem::DoSetText( const wxString& str )
724 {
725 /* '\t' is the deliminator indicating a hot key */
726 m_text.Empty();
727 const wxChar *pc = str;
728 for (; (*pc != wxT('\0')) && (*pc != wxT('\t')); pc++ )
729 {
730 if (*pc == wxT('&'))
731 {
732 #if (GTK_MINOR_VERSION > 0)
733 m_text << wxT('_');
734 }
735 else if ( *pc == wxT('_') ) // escape underscores
736 {
737 m_text << wxT("__");
738 }
739 else if (*pc == wxT('/')) /* we have to filter out slashes ... */
740 {
741 m_text << wxT('\\'); /* ... and replace them with back slashes */
742 #endif
743 }
744 else
745 m_text << *pc;
746 }
747
748 /* only GTK 1.2 knows about hot keys */
749 m_hotKey = wxT("");
750 #if (GTK_MINOR_VERSION > 0)
751 if(*pc == wxT('\t'))
752 {
753 pc++;
754 m_hotKey = pc;
755 }
756 #endif
757 }
758
759 #if wxUSE_ACCEL
760
761 wxAcceleratorEntry *wxMenuItem::GetAccel() const
762 {
763 if ( !GetHotKey() )
764 {
765 // nothing
766 return (wxAcceleratorEntry *)NULL;
767 }
768
769 // as wxGetAccelFromString() looks for TAB, insert a dummy one here
770 wxString label;
771 label << wxT('\t') << GetHotKey();
772
773 return wxGetAccelFromString(label);
774 }
775
776 #endif // wxUSE_ACCEL
777
778 void wxMenuItem::Check( bool check )
779 {
780 wxCHECK_RET( m_menuItem, wxT("invalid menu item") );
781
782 wxCHECK_RET( IsCheckable(), wxT("Can't check uncheckable item!") )
783
784 if (check == m_isChecked)
785 return;
786
787 wxMenuItemBase::Check( check );
788 gtk_check_menu_item_set_state( (GtkCheckMenuItem*)m_menuItem, (gint)check );
789 }
790
791 void wxMenuItem::Enable( bool enable )
792 {
793 wxCHECK_RET( m_menuItem, wxT("invalid menu item") );
794
795 gtk_widget_set_sensitive( m_menuItem, enable );
796 wxMenuItemBase::Enable( enable );
797 }
798
799 bool wxMenuItem::IsChecked() const
800 {
801 wxCHECK_MSG( m_menuItem, FALSE, wxT("invalid menu item") );
802
803 wxCHECK_MSG( IsCheckable(), FALSE,
804 wxT("can't get state of uncheckable item!") );
805
806 return ((GtkCheckMenuItem*)m_menuItem)->active != 0;
807 }
808
809 wxString wxMenuItem::GetFactoryPath() const
810 {
811 /* in order to get the pointer to the item we need the item text _without_
812 underscores */
813 wxString path( wxT("<main>/") );
814 path += GetLabel();
815
816 return path;
817 }
818
819 //-----------------------------------------------------------------------------
820 // wxMenu
821 //-----------------------------------------------------------------------------
822
823 IMPLEMENT_DYNAMIC_CLASS(wxMenu,wxEvtHandler)
824
825 void wxMenu::Init()
826 {
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>" );
831 #else
832 m_menu = gtk_menu_new(); // Do not show!
833 #endif
834
835 m_owner = (GtkWidget*) NULL;
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 ? */
850 //GtkWidget *menuItem = gtk_item_factory_get_widget( m_factory, "<main>/tearoff" );
851 }
852 #endif
853
854 // append the title as the very first entry if we have it
855 if ( !!m_title )
856 {
857 Append(-2, m_title);
858 AppendSeparator();
859 }
860 }
861
862 wxMenu::~wxMenu()
863 {
864 m_items.Clear();
865
866 gtk_widget_destroy( m_menu );
867
868 gtk_object_unref( GTK_OBJECT(m_factory) );
869 }
870
871 bool wxMenu::GtkAppend(wxMenuItem *mitem)
872 {
873 GtkWidget *menuItem;
874
875 if ( mitem->IsSeparator() )
876 {
877 #if (GTK_MINOR_VERSION > 0)
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() )
894 {
895 #if (GTK_MINOR_VERSION > 0)
896 /* text has "_" instead of "&" after mitem->SetText() */
897 wxString text( mitem->GetText() );
898
899 /* local buffer in multibyte form */
900 char buf[200];
901 strcpy( buf, "/" );
902 strcat( buf, text.mb_str() );
903
904 GtkItemFactoryEntry entry;
905 entry.path = buf;
906 entry.callback = (GtkItemFactoryCallback) 0;
907 entry.callback_action = 0;
908 entry.item_type = "<Branch>";
909 entry.accelerator = (gchar*) NULL;
910
911 gtk_item_factory_create_item( m_factory, &entry, (gpointer) this, 2 ); /* what is 2 ? */
912
913 wxString path( mitem->GetFactoryPath() );
914 menuItem = gtk_item_factory_get_item( m_factory, path.mb_str() );
915 #else // GTK+ 1.0
916 menuItem = gtk_menu_item_new_with_label(mitem->GetText().mbc_str());
917 #endif // GTK 1.2/1.0
918
919 gtk_menu_item_set_submenu( GTK_MENU_ITEM(menuItem), mitem->GetSubMenu()->m_menu );
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);
926 }
927 else // a normal item
928 {
929 #if (GTK_MINOR_VERSION > 0)
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;
942 if ( mitem->IsCheckable() )
943 entry.item_type = "<CheckItem>";
944 else
945 entry.item_type = "<Item>";
946 entry.accelerator = (gchar*) NULL;
947
948 #if wxUSE_ACCEL
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)
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));
955 entry.accelerator = s_accel;
956 #else // !wxUSE_ACCEL
957 entry.accelerator = (char*) NULL;
958 #endif // wxUSE_ACCEL/!wxUSE_ACCEL
959
960 gtk_item_factory_create_item( m_factory, &entry, (gpointer) this, 2 ); /* what is 2 ? */
961
962 wxString path( mitem->GetFactoryPath() );
963 menuItem = gtk_item_factory_get_widget( m_factory, path.mb_str() );
964 #else // GTK+ 1.0
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() );
967
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 }
973
974 if ( !mitem->IsSeparator() )
975 {
976 gtk_signal_connect( GTK_OBJECT(menuItem), "select",
977 GTK_SIGNAL_FUNC(gtk_menu_hilight_callback),
978 (gpointer)this );
979
980 gtk_signal_connect( GTK_OBJECT(menuItem), "deselect",
981 GTK_SIGNAL_FUNC(gtk_menu_nolight_callback),
982 (gpointer)this );
983 }
984
985 #if GTK_MINOR_VERSION == 0
986 gtk_menu_append( GTK_MENU(m_menu), menuItem );
987 gtk_widget_show( menuItem );
988 #endif // GTK+ 1.0
989
990 mitem->SetMenuItem(menuItem);
991
992 return TRUE;
993 }
994
995 bool wxMenu::DoAppend(wxMenuItem *mitem)
996 {
997 return GtkAppend(mitem) && wxMenuBase::DoAppend(mitem);
998 }
999
1000 bool wxMenu::DoInsert(size_t pos, wxMenuItem *item)
1001 {
1002 if ( !wxMenuBase::DoInsert(pos, item) )
1003 return FALSE;
1004
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
1012 if ( m_style & wxMENU_TEAROFF )
1013 {
1014 // change the position as the first item is the tear-off marker
1015 pos++;
1016 }
1017
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") );
1027
1028 return FALSE;
1029 #endif // GTK 1.2/1.0
1030 }
1031
1032 wxMenuItem *wxMenu::DoRemove(wxMenuItem *item)
1033 {
1034 if ( !wxMenuBase::DoRemove(item) )
1035 return (wxMenuItem *)NULL;
1036
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() );
1040
1041 return item;
1042 }
1043
1044 int wxMenu::FindMenuIdByMenuItem( GtkWidget *menuItem ) const
1045 {
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 }
1054
1055 return wxNOT_FOUND;
1056 }
1057
1058 // ----------------------------------------------------------------------------
1059 // helpers
1060 // ----------------------------------------------------------------------------
1061
1062 #if (GTK_MINOR_VERSION > 0) && wxUSE_ACCEL
1063 static wxString GetHotKey( const wxMenuItem& item )
1064 {
1065 wxString hotkey;
1066
1067 wxAcceleratorEntry *accel = item.GetAccel();
1068 if ( accel )
1069 {
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 )
1080 {
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;
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;
1109
1110 // if there are any other keys wxGetAccelFromString() may return,
1111 // we should process them here
1112
1113 default:
1114 if ( wxIsalnum(code) )
1115 {
1116 hotkey << (wxChar)code;
1117
1118 break;
1119 }
1120
1121 wxFAIL_MSG( wxT("unknown keyboard accel") );
1122 }
1123
1124 delete accel;
1125 }
1126
1127 return hotkey;
1128 }
1129 #endif // wxUSE_ACCEL
1130