implemented wxMenuBar ctor taking array of menus/titles for all ports; added optional...
[wxWidgets.git] / src / gtk1 / 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 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
11 #pragma implementation "menu.h"
12 #pragma implementation "menuitem.h"
13 #endif
14
15 // For compilers that support precompilation, includes "wx.h".
16 #include "wx/wxprec.h"
17
18 #include "wx/menu.h"
19 #include "wx/log.h"
20 #include "wx/intl.h"
21 #include "wx/app.h"
22 #include "wx/bitmap.h"
23
24 #if wxUSE_ACCEL
25 #include "wx/accel.h"
26 #endif // wxUSE_ACCEL
27
28 #include "wx/gtk/private.h"
29
30 #include <gdk/gdkkeysyms.h>
31
32 // FIXME: is this right? somehow I don't think so (VZ)
33 #ifdef __WXGTK20__
34 #include <glib-object.h>
35
36 #define gtk_accel_group_attach(g, o) gtk_window_add_accel_group((o), (g))
37 #define gtk_accel_group_detach(g, o) gtk_window_remove_accel_group((o), (g))
38 #define gtk_menu_ensure_uline_accel_group(m) gtk_menu_get_accel_group(m)
39
40 #define ACCEL_OBJECT GtkWindow
41 #define ACCEL_OBJECTS(a) (a)->acceleratables
42 #define ACCEL_OBJ_CAST(obj) ((GtkWindow*) obj)
43 #else // GTK+ 1.x
44 #define ACCEL_OBJECT GtkObject
45 #define ACCEL_OBJECTS(a) (a)->attach_objects
46 #define ACCEL_OBJ_CAST(obj) GTK_OBJECT(obj)
47 #endif
48
49 // we use normal item but with a special id for the menu title
50 static const int wxGTK_TITLE_ID = -3;
51
52 //-----------------------------------------------------------------------------
53 // idle system
54 //-----------------------------------------------------------------------------
55
56 extern void wxapp_install_idle_handler();
57 extern bool g_isIdle;
58
59 #if wxUSE_ACCEL
60 static wxString GetHotKey( const wxMenuItem& item );
61 #endif
62
63 //-----------------------------------------------------------------------------
64 // substitute for missing GtkPixmapMenuItem
65 //-----------------------------------------------------------------------------
66
67 #ifndef __WXGTK20__
68
69 #define GTK_TYPE_PIXMAP_MENU_ITEM (gtk_pixmap_menu_item_get_type ())
70 #define GTK_PIXMAP_MENU_ITEM(obj) (GTK_CHECK_CAST ((obj), GTK_TYPE_PIXMAP_MENU_ITEM, GtkPixmapMenuItem))
71 #define GTK_PIXMAP_MENU_ITEM_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), GTK_TYPE_PIXMAP_MENU_ITEM, GtkPixmapMenuItemClass))
72 #define GTK_IS_PIXMAP_MENU_ITEM(obj) (GTK_CHECK_TYPE ((obj), GTK_TYPE_PIXMAP_MENU_ITEM))
73 #define GTK_IS_PIXMAP_MENU_ITEM_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GTK_TYPE_PIXMAP_MENU_ITEM))
74 //#define GTK_PIXMAP_MENU_ITEM_GET_CLASS(obj) (GTK_CHECK_GET_CLASS ((obj), GTK_TYPE_PIXMAP_MENU_ITEM))
75 #define GTK_PIXMAP_MENU_ITEM_GET_CLASS(obj) (GTK_PIXMAP_MENU_ITEM_CLASS( GTK_OBJECT_GET_CLASS(obj)))
76
77 #ifndef GTK_MENU_ITEM_GET_CLASS
78 #define GTK_MENU_ITEM_GET_CLASS(obj) (GTK_MENU_ITEM_CLASS( GTK_OBJECT_GET_CLASS(obj)))
79 #endif
80
81 typedef struct _GtkPixmapMenuItem GtkPixmapMenuItem;
82 typedef struct _GtkPixmapMenuItemClass GtkPixmapMenuItemClass;
83
84 struct _GtkPixmapMenuItem
85 {
86 GtkMenuItem menu_item;
87
88 GtkWidget *pixmap;
89 };
90
91 struct _GtkPixmapMenuItemClass
92 {
93 GtkMenuItemClass parent_class;
94
95 guint orig_toggle_size;
96 guint have_pixmap_count;
97 };
98
99
100 GtkType gtk_pixmap_menu_item_get_type (void);
101 GtkWidget* gtk_pixmap_menu_item_new (void);
102 void gtk_pixmap_menu_item_set_pixmap (GtkPixmapMenuItem *menu_item,
103 GtkWidget *pixmap);
104 #endif // GTK 2.0
105
106 //-----------------------------------------------------------------------------
107 // idle system
108 //-----------------------------------------------------------------------------
109
110 static wxString wxReplaceUnderscore( const wxString& title )
111 {
112 const wxChar *pc;
113
114 // GTK 1.2 wants to have "_" instead of "&" for accelerators
115 wxString str;
116 pc = title;
117 while (*pc != wxT('\0'))
118 {
119 if ((*pc == wxT('&')) && (*(pc+1) == wxT('&')))
120 {
121 // "&" is doubled to indicate "&" instead of accelerator
122 ++pc;
123 str << wxT('&');
124 }
125 else if (*pc == wxT('&'))
126 {
127 str << wxT('_');
128 }
129 else
130 {
131 if ( *pc == wxT('_') )
132 {
133 // underscores must be doubled to prevent them from being
134 // interpreted as accelerator character prefix by GTK
135 str << *pc;
136 }
137
138 str << *pc;
139 }
140 ++pc;
141 }
142
143 // wxPrintf( wxT("before %s after %s\n"), title.c_str(), str.c_str() );
144
145 return str;
146 }
147
148 //-----------------------------------------------------------------------------
149 // activate message from GTK
150 //-----------------------------------------------------------------------------
151
152 static void gtk_menu_open_callback( GtkWidget *widget, wxMenu *menu )
153 {
154 if (g_isIdle) wxapp_install_idle_handler();
155
156 wxMenuEvent event( wxEVT_MENU_OPEN, -1, menu );
157 event.SetEventObject( menu );
158
159 wxEvtHandler* handler = menu->GetEventHandler();
160 if (handler && handler->ProcessEvent(event))
161 return;
162
163 wxWindow *win = menu->GetInvokingWindow();
164 if (win) win->GetEventHandler()->ProcessEvent( event );
165 }
166
167 //-----------------------------------------------------------------------------
168 // wxMenuBar
169 //-----------------------------------------------------------------------------
170
171 IMPLEMENT_DYNAMIC_CLASS(wxMenuBar,wxWindow)
172
173 void wxMenuBar::Init(size_t n, wxMenu *menus[], const wxString titles[], long style)
174 {
175 // the parent window is known after wxFrame::SetMenu()
176 m_needParent = FALSE;
177 m_style = style;
178 m_invokingWindow = (wxWindow*) NULL;
179
180 if (!PreCreation( (wxWindow*) NULL, wxDefaultPosition, wxDefaultSize ) ||
181 !CreateBase( (wxWindow*) NULL, -1, wxDefaultPosition, wxDefaultSize, style, wxDefaultValidator, wxT("menubar") ))
182 {
183 wxFAIL_MSG( wxT("wxMenuBar creation failed") );
184 return;
185 }
186
187 m_menubar = gtk_menu_bar_new();
188 #ifndef __WXGTK20__
189 m_accel = gtk_accel_group_new();
190 #endif
191
192 if (style & wxMB_DOCKABLE)
193 {
194 m_widget = gtk_handle_box_new();
195 gtk_container_add( GTK_CONTAINER(m_widget), GTK_WIDGET(m_menubar) );
196 gtk_widget_show( GTK_WIDGET(m_menubar) );
197 }
198 else
199 {
200 m_widget = GTK_WIDGET(m_menubar);
201 }
202
203 PostCreation();
204
205 ApplyWidgetStyle();
206
207 for (size_t i = 0; i < n; ++i )
208 Append(menus[i], titles[i]);
209 }
210
211 wxMenuBar::wxMenuBar(size_t n, wxMenu *menus[], const wxString titles[], long style)
212 {
213 Init(n, menus, titles, style);
214 }
215
216 wxMenuBar::wxMenuBar(long style)
217 {
218 Init(0, NULL, NULL, style);
219 }
220
221 wxMenuBar::wxMenuBar()
222 {
223 Init(0, NULL, NULL, 0);
224 }
225
226 wxMenuBar::~wxMenuBar()
227 {
228 }
229
230 static void wxMenubarUnsetInvokingWindow( wxMenu *menu, wxWindow *win )
231 {
232 menu->SetInvokingWindow( (wxWindow*) NULL );
233
234 wxWindow *top_frame = win;
235 while (top_frame->GetParent() && !(top_frame->IsTopLevel()))
236 top_frame = top_frame->GetParent();
237
238 #ifndef __WXGTK20__
239 // support for native hot keys
240 gtk_accel_group_detach( menu->m_accel, ACCEL_OBJ_CAST(top_frame->m_widget) );
241 #endif
242
243 wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
244 while (node)
245 {
246 wxMenuItem *menuitem = node->GetData();
247 if (menuitem->IsSubMenu())
248 wxMenubarUnsetInvokingWindow( menuitem->GetSubMenu(), win );
249 node = node->GetNext();
250 }
251 }
252
253 static void wxMenubarSetInvokingWindow( wxMenu *menu, wxWindow *win )
254 {
255 menu->SetInvokingWindow( win );
256
257 wxWindow *top_frame = win;
258 while (top_frame->GetParent() && !(top_frame->IsTopLevel()))
259 top_frame = top_frame->GetParent();
260
261 // support for native hot keys
262 ACCEL_OBJECT *obj = ACCEL_OBJ_CAST(top_frame->m_widget);
263 if ( !g_slist_find( ACCEL_OBJECTS(menu->m_accel), obj ) )
264 gtk_accel_group_attach( menu->m_accel, obj );
265
266 wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
267 while (node)
268 {
269 wxMenuItem *menuitem = node->GetData();
270 if (menuitem->IsSubMenu())
271 wxMenubarSetInvokingWindow( menuitem->GetSubMenu(), win );
272 node = node->GetNext();
273 }
274 }
275
276 void wxMenuBar::SetInvokingWindow( wxWindow *win )
277 {
278 m_invokingWindow = win;
279 wxWindow *top_frame = win;
280 while (top_frame->GetParent() && !(top_frame->IsTopLevel()))
281 top_frame = top_frame->GetParent();
282
283 #ifndef __WXGTK20__
284 // support for native key accelerators indicated by underscroes
285 ACCEL_OBJECT *obj = ACCEL_OBJ_CAST(top_frame->m_widget);
286 if ( !g_slist_find( ACCEL_OBJECTS(m_accel), obj ) )
287 gtk_accel_group_attach( m_accel, obj );
288 #endif
289
290 wxMenuList::compatibility_iterator node = m_menus.GetFirst();
291 while (node)
292 {
293 wxMenu *menu = node->GetData();
294 wxMenubarSetInvokingWindow( menu, win );
295 node = node->GetNext();
296 }
297 }
298
299 void wxMenuBar::UnsetInvokingWindow( wxWindow *win )
300 {
301 m_invokingWindow = (wxWindow*) NULL;
302 wxWindow *top_frame = win;
303 while (top_frame->GetParent() && !(top_frame->IsTopLevel()))
304 top_frame = top_frame->GetParent();
305
306 #ifndef __WXGTK20__
307 // support for native key accelerators indicated by underscroes
308 gtk_accel_group_detach( m_accel, ACCEL_OBJ_CAST(top_frame->m_widget) );
309 #endif
310
311 wxMenuList::compatibility_iterator node = m_menus.GetFirst();
312 while (node)
313 {
314 wxMenu *menu = node->GetData();
315 wxMenubarUnsetInvokingWindow( menu, win );
316 node = node->GetNext();
317 }
318 }
319
320 bool wxMenuBar::Append( wxMenu *menu, const wxString &title )
321 {
322 if ( !wxMenuBarBase::Append( menu, title ) )
323 return FALSE;
324
325 return GtkAppend(menu, title);
326 }
327
328 bool wxMenuBar::GtkAppend(wxMenu *menu, const wxString& title, int pos)
329 {
330 wxString str( wxReplaceUnderscore( title ) );
331
332 // This doesn't have much effect right now.
333 menu->SetTitle( str );
334
335 // The "m_owner" is the "menu item"
336 #ifdef __WXGTK20__
337 menu->m_owner = gtk_menu_item_new_with_mnemonic( wxGTK_CONV( str ) );
338 #else
339 menu->m_owner = gtk_menu_item_new_with_label( wxGTK_CONV( str ) );
340 GtkLabel *label = GTK_LABEL( GTK_BIN(menu->m_owner)->child );
341 // set new text
342 gtk_label_set_text( label, wxGTK_CONV( str ) );
343 // reparse key accel
344 guint accel_key = gtk_label_parse_uline (GTK_LABEL(label), wxGTK_CONV( str ) );
345 if (accel_key != GDK_VoidSymbol)
346 {
347 gtk_widget_add_accelerator (menu->m_owner,
348 "activate_item",
349 m_accel,//gtk_menu_ensure_uline_accel_group(GTK_MENU(m_menubar)),
350 accel_key,
351 GDK_MOD1_MASK,
352 GTK_ACCEL_LOCKED);
353 }
354 #endif
355
356 gtk_widget_show( menu->m_owner );
357
358 gtk_menu_item_set_submenu( GTK_MENU_ITEM(menu->m_owner), menu->m_menu );
359
360 if (pos == -1)
361 gtk_menu_shell_append( GTK_MENU_SHELL(m_menubar), menu->m_owner );
362 else
363 gtk_menu_shell_insert( GTK_MENU_SHELL(m_menubar), menu->m_owner, pos );
364
365 gtk_signal_connect( GTK_OBJECT(menu->m_owner), "activate",
366 GTK_SIGNAL_FUNC(gtk_menu_open_callback),
367 (gpointer)menu );
368
369 // m_invokingWindow is set after wxFrame::SetMenuBar(). This call enables
370 // addings menu later on.
371 if (m_invokingWindow)
372 {
373 wxMenubarSetInvokingWindow( menu, m_invokingWindow );
374
375 // OPTIMISE ME: we should probably cache this, or pass it
376 // directly, but for now this is a minimal
377 // change to validate the new dynamic sizing.
378 // see (and refactor :) similar code in Remove
379 // below.
380
381 wxFrame *frame = wxDynamicCast( m_invokingWindow, wxFrame );
382
383 if( frame )
384 frame->UpdateMenuBarSize();
385 }
386
387 return TRUE;
388 }
389
390 bool wxMenuBar::Insert(size_t pos, wxMenu *menu, const wxString& title)
391 {
392 if ( !wxMenuBarBase::Insert(pos, menu, title) )
393 return FALSE;
394
395 // TODO
396
397 if ( !GtkAppend(menu, title, (int)pos) )
398 return FALSE;
399
400 return TRUE;
401 }
402
403 wxMenu *wxMenuBar::Replace(size_t pos, wxMenu *menu, const wxString& title)
404 {
405 // remove the old item and insert a new one
406 wxMenu *menuOld = Remove(pos);
407 if ( menuOld && !Insert(pos, menu, title) )
408 {
409 return (wxMenu*) NULL;
410 }
411
412 // either Insert() succeeded or Remove() failed and menuOld is NULL
413 return menuOld;
414 }
415
416 wxMenu *wxMenuBar::Remove(size_t pos)
417 {
418 wxMenu *menu = wxMenuBarBase::Remove(pos);
419 if ( !menu )
420 return (wxMenu*) NULL;
421
422 gtk_menu_item_remove_submenu( GTK_MENU_ITEM(menu->m_owner) );
423 gtk_container_remove(GTK_CONTAINER(m_menubar), menu->m_owner);
424
425 gtk_widget_destroy( menu->m_owner );
426 menu->m_owner = NULL;
427
428 if (m_invokingWindow)
429 {
430 // OPTIMISE ME: see comment in GtkAppend
431 wxFrame *frame = wxDynamicCast( m_invokingWindow, wxFrame );
432
433 if( frame )
434 frame->UpdateMenuBarSize();
435 }
436
437 return menu;
438 }
439
440 static int FindMenuItemRecursive( const wxMenu *menu, const wxString &menuString, const wxString &itemString )
441 {
442 if (wxMenuItem::GetLabelFromText(menu->GetTitle()) == wxMenuItem::GetLabelFromText(menuString))
443 {
444 int res = menu->FindItem( itemString );
445 if (res != wxNOT_FOUND)
446 return res;
447 }
448
449 wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
450 while (node)
451 {
452 wxMenuItem *item = node->GetData();
453 if (item->IsSubMenu())
454 return FindMenuItemRecursive(item->GetSubMenu(), menuString, itemString);
455
456 node = node->GetNext();
457 }
458
459 return wxNOT_FOUND;
460 }
461
462 int wxMenuBar::FindMenuItem( const wxString &menuString, const wxString &itemString ) const
463 {
464 wxMenuList::compatibility_iterator node = m_menus.GetFirst();
465 while (node)
466 {
467 wxMenu *menu = node->GetData();
468 int res = FindMenuItemRecursive( menu, menuString, itemString);
469 if (res != -1)
470 return res;
471 node = node->GetNext();
472 }
473
474 return wxNOT_FOUND;
475 }
476
477 // Find a wxMenuItem using its id. Recurses down into sub-menus
478 static wxMenuItem* FindMenuItemByIdRecursive(const wxMenu* menu, int id)
479 {
480 wxMenuItem* result = menu->FindChildItem(id);
481
482 wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
483 while ( node && result == NULL )
484 {
485 wxMenuItem *item = node->GetData();
486 if (item->IsSubMenu())
487 {
488 result = FindMenuItemByIdRecursive( item->GetSubMenu(), id );
489 }
490 node = node->GetNext();
491 }
492
493 return result;
494 }
495
496 wxMenuItem* wxMenuBar::FindItem( int id, wxMenu **menuForItem ) const
497 {
498 wxMenuItem* result = 0;
499 wxMenuList::compatibility_iterator node = m_menus.GetFirst();
500 while (node && result == 0)
501 {
502 wxMenu *menu = node->GetData();
503 result = FindMenuItemByIdRecursive( menu, id );
504 node = node->GetNext();
505 }
506
507 if ( menuForItem )
508 {
509 *menuForItem = result ? result->GetMenu() : (wxMenu *)NULL;
510 }
511
512 return result;
513 }
514
515 void wxMenuBar::EnableTop( size_t pos, bool flag )
516 {
517 wxMenuList::compatibility_iterator node = m_menus.Item( pos );
518
519 wxCHECK_RET( node, wxT("menu not found") );
520
521 wxMenu* menu = node->GetData();
522
523 if (menu->m_owner)
524 gtk_widget_set_sensitive( menu->m_owner, flag );
525 }
526
527 wxString wxMenuBar::GetLabelTop( size_t pos ) const
528 {
529 wxMenuList::compatibility_iterator node = m_menus.Item( pos );
530
531 wxCHECK_MSG( node, wxT("invalid"), wxT("menu not found") );
532
533 wxMenu* menu = node->GetData();
534
535 wxString label;
536 wxString text( menu->GetTitle() );
537 for ( const wxChar *pc = text.c_str(); *pc; pc++ )
538 {
539 if ( *pc == wxT('_') )
540 {
541 // '_' is the escape character for GTK+
542 continue;
543 }
544
545 // don't remove ampersands '&' since if we have them in the menu title
546 // it means that they were doubled to indicate "&" instead of accelerator
547
548 label += *pc;
549 }
550
551 return label;
552 }
553
554 void wxMenuBar::SetLabelTop( size_t pos, const wxString& label )
555 {
556 wxMenuList::compatibility_iterator node = m_menus.Item( pos );
557
558 wxCHECK_RET( node, wxT("menu not found") );
559
560 wxMenu* menu = node->GetData();
561
562 wxString str( wxReplaceUnderscore( label ) );
563
564 menu->SetTitle( str );
565
566 if (menu->m_owner)
567 {
568 GtkLabel *label = GTK_LABEL( GTK_BIN(menu->m_owner)->child );
569
570 /* set new text */
571 gtk_label_set( label, wxGTK_CONV( str ) );
572
573 /* reparse key accel */
574 (void)gtk_label_parse_uline (GTK_LABEL(label), wxGTK_CONV( str ) );
575 gtk_accel_label_refetch( GTK_ACCEL_LABEL(label) );
576 }
577
578 }
579
580 //-----------------------------------------------------------------------------
581 // "activate"
582 //-----------------------------------------------------------------------------
583
584 static void gtk_menu_clicked_callback( GtkWidget *widget, wxMenu *menu )
585 {
586 if (g_isIdle)
587 wxapp_install_idle_handler();
588
589 int id = menu->FindMenuIdByMenuItem(widget);
590
591 /* should find it for normal (not popup) menu */
592 wxASSERT_MSG( (id != -1) || (menu->GetInvokingWindow() != NULL),
593 _T("menu item not found in gtk_menu_clicked_callback") );
594
595 if (!menu->IsEnabled(id))
596 return;
597
598 wxMenuItem* item = menu->FindChildItem( id );
599 wxCHECK_RET( item, wxT("error in menu item callback") );
600
601 if ( item->GetId() == wxGTK_TITLE_ID )
602 {
603 // ignore events from the menu title
604 return;
605 }
606
607 if (item->IsCheckable())
608 {
609 bool isReallyChecked = item->IsChecked(),
610 isInternallyChecked = item->wxMenuItemBase::IsChecked();
611
612 // ensure that the internal state is always consistent with what is
613 // shown on the screen
614 item->wxMenuItemBase::Check(isReallyChecked);
615
616 // we must not report the events for the radio button going up nor the
617 // events resulting from the calls to wxMenuItem::Check()
618 if ( (item->GetKind() == wxITEM_RADIO && !isReallyChecked) ||
619 (isInternallyChecked == isReallyChecked) )
620 {
621 return;
622 }
623 }
624
625
626 // Is this menu on a menubar? (possibly nested)
627 wxFrame* frame = NULL;
628 if(menu->IsAttached())
629 frame = menu->GetMenuBar()->GetFrame();
630
631 // FIXME: why do we have to call wxFrame::GetEventHandler() directly here?
632 // normally wxMenu::SendEvent() should be enough, if it doesn't work
633 // in wxGTK then we have a bug in wxMenu::GetInvokingWindow() which
634 // should be fixed instead of working around it here...
635 if (frame)
636 {
637 // If it is attached then let the frame send the event.
638 // Don't call frame->ProcessCommand(id) because it toggles
639 // checkable items and we've already done that above.
640 wxCommandEvent commandEvent(wxEVT_COMMAND_MENU_SELECTED, id);
641 commandEvent.SetEventObject(frame);
642 if (item->IsCheckable())
643 commandEvent.SetInt(item->IsChecked());
644 commandEvent.SetEventObject(menu);
645
646 frame->GetEventHandler()->ProcessEvent(commandEvent);
647 }
648 else
649 {
650 // otherwise let the menu have it
651 menu->SendEvent(id, item->IsCheckable() ? item->IsChecked() : -1);
652 }
653 }
654
655 //-----------------------------------------------------------------------------
656 // "select"
657 //-----------------------------------------------------------------------------
658
659 static void gtk_menu_hilight_callback( GtkWidget *widget, wxMenu *menu )
660 {
661 if (g_isIdle) wxapp_install_idle_handler();
662
663 int id = menu->FindMenuIdByMenuItem(widget);
664
665 wxASSERT( id != -1 ); // should find it!
666
667 if (!menu->IsEnabled(id))
668 return;
669
670 wxMenuEvent event( wxEVT_MENU_HIGHLIGHT, id );
671 event.SetEventObject( menu );
672
673 wxEvtHandler* handler = menu->GetEventHandler();
674 if (handler && handler->ProcessEvent(event))
675 return;
676
677 wxWindow *win = menu->GetInvokingWindow();
678 if (win) win->GetEventHandler()->ProcessEvent( event );
679 }
680
681 //-----------------------------------------------------------------------------
682 // "deselect"
683 //-----------------------------------------------------------------------------
684
685 static void gtk_menu_nolight_callback( GtkWidget *widget, wxMenu *menu )
686 {
687 if (g_isIdle) wxapp_install_idle_handler();
688
689 int id = menu->FindMenuIdByMenuItem(widget);
690
691 wxASSERT( id != -1 ); // should find it!
692
693 if (!menu->IsEnabled(id))
694 return;
695
696 wxMenuEvent event( wxEVT_MENU_HIGHLIGHT, -1 );
697 event.SetEventObject( menu );
698
699 wxEvtHandler* handler = menu->GetEventHandler();
700 if (handler && handler->ProcessEvent(event))
701 return;
702
703 wxWindow *win = menu->GetInvokingWindow();
704 if (win)
705 win->GetEventHandler()->ProcessEvent( event );
706 }
707
708 //-----------------------------------------------------------------------------
709 // wxMenuItem
710 //-----------------------------------------------------------------------------
711
712 IMPLEMENT_DYNAMIC_CLASS(wxMenuItem, wxObject)
713
714 wxMenuItem *wxMenuItemBase::New(wxMenu *parentMenu,
715 int id,
716 const wxString& name,
717 const wxString& help,
718 wxItemKind kind,
719 wxMenu *subMenu)
720 {
721 return new wxMenuItem(parentMenu, id, name, help, kind, subMenu);
722 }
723
724 wxMenuItem::wxMenuItem(wxMenu *parentMenu,
725 int id,
726 const wxString& text,
727 const wxString& help,
728 wxItemKind kind,
729 wxMenu *subMenu)
730 : wxMenuItemBase(parentMenu, id, text, help, kind, subMenu)
731 {
732 Init(text);
733 }
734
735 wxMenuItem::wxMenuItem(wxMenu *parentMenu,
736 int id,
737 const wxString& text,
738 const wxString& help,
739 bool isCheckable,
740 wxMenu *subMenu)
741 : wxMenuItemBase(parentMenu, id, text, help,
742 isCheckable ? wxITEM_CHECK : wxITEM_NORMAL, subMenu)
743 {
744 Init(text);
745 }
746
747 void wxMenuItem::Init(const wxString& text)
748 {
749 m_labelWidget = (GtkWidget *) NULL;
750 m_menuItem = (GtkWidget *) NULL;
751
752 DoSetText(text);
753 }
754
755 wxMenuItem::~wxMenuItem()
756 {
757 // don't delete menu items, the menus take care of that
758 }
759
760 // return the menu item text without any menu accels
761 /* static */
762 wxString wxMenuItemBase::GetLabelFromText(const wxString& text)
763 {
764 wxString label;
765
766 for ( const wxChar *pc = text.c_str(); *pc; pc++ )
767 {
768 if ( *pc == wxT('\t'))
769 break;
770
771 if ( *pc == wxT('_') )
772 {
773 // GTK 1.2 escapes "xxx_xxx" to "xxx__xxx"
774 pc++;
775 label += *pc;
776 continue;
777 }
778
779 #ifdef __WXGTK20__
780 if ( *pc == wxT('\\') )
781 {
782 // GTK 2.0 escapes "xxx/xxx" to "xxx\/xxx"
783 pc++;
784 label += *pc;
785 continue;
786 }
787 #endif
788
789 if ( (*pc == wxT('&')) && (*(pc+1) != wxT('&')) )
790 {
791 // wxMSW escapes "&"
792 // "&" is doubled to indicate "&" instead of accelerator
793 continue;
794 }
795
796 label += *pc;
797 }
798
799 // wxPrintf( wxT("GetLabelFromText(): text %s label %s\n"), text.c_str(), label.c_str() );
800
801 return label;
802 }
803
804 void wxMenuItem::SetText( const wxString& str )
805 {
806 // Some optimization to avoid flicker
807 wxString oldLabel = m_text;
808 oldLabel = wxStripMenuCodes(oldLabel.BeforeFirst('\t'));
809 oldLabel.Replace(wxT("_"), wxT(""));
810 wxString label1 = wxStripMenuCodes(str.BeforeFirst('\t'));
811 if (oldLabel == label1)
812 return;
813
814 DoSetText(str);
815
816 if (m_menuItem)
817 {
818 GtkLabel *label;
819 if (m_labelWidget)
820 label = (GtkLabel*) m_labelWidget;
821 else
822 label = GTK_LABEL( GTK_BIN(m_menuItem)->child );
823
824 #ifdef __WXGTK20__
825 gtk_label_set_text_with_mnemonic( GTK_LABEL(label), wxGTK_CONV(m_text) );
826 #else
827 // set new text
828 gtk_label_set( label, wxGTK_CONV( m_text ) );
829
830 // reparse key accel
831 (void)gtk_label_parse_uline (GTK_LABEL(label), wxGTK_CONV(m_text) );
832 gtk_accel_label_refetch( GTK_ACCEL_LABEL(label) );
833 #endif
834 }
835 }
836
837 // it's valid for this function to be called even if m_menuItem == NULL
838 void wxMenuItem::DoSetText( const wxString& str )
839 {
840 // '\t' is the deliminator indicating a hot key
841 m_text.Empty();
842 const wxChar *pc = str;
843 while ( (*pc != wxT('\0')) && (*pc != wxT('\t')) )
844 {
845 if ((*pc == wxT('&')) && (*(pc+1) == wxT('&')))
846 {
847 // "&" is doubled to indicate "&" instead of accelerator
848 ++pc;
849 m_text << wxT('&');
850 }
851 else if (*pc == wxT('&'))
852 {
853 m_text << wxT('_');
854 }
855 else if ( *pc == wxT('_') ) // escape underscores
856 {
857 m_text << wxT("__");
858 }
859 else
860 {
861 m_text << *pc;
862 }
863 ++pc;
864 }
865
866 // wxPrintf( wxT("DoSetText(): str %s m_text %s\n"), str.c_str(), m_text.c_str() );
867
868 m_hotKey = wxT("");
869
870 if(*pc == wxT('\t'))
871 {
872 pc++;
873 m_hotKey = pc;
874 }
875 }
876
877 #if wxUSE_ACCEL
878
879 wxAcceleratorEntry *wxMenuItem::GetAccel() const
880 {
881 if ( !GetHotKey() )
882 {
883 // nothing
884 return (wxAcceleratorEntry *)NULL;
885 }
886
887 // as wxGetAccelFromString() looks for TAB, insert a dummy one here
888 wxString label;
889 label << wxT('\t') << GetHotKey();
890
891 return wxGetAccelFromString(label);
892 }
893
894 #endif // wxUSE_ACCEL
895
896 void wxMenuItem::Check( bool check )
897 {
898 wxCHECK_RET( m_menuItem, wxT("invalid menu item") );
899
900 if (check == m_isChecked)
901 return;
902
903 wxMenuItemBase::Check( check );
904
905 switch ( GetKind() )
906 {
907 case wxITEM_CHECK:
908 case wxITEM_RADIO:
909 gtk_check_menu_item_set_state( (GtkCheckMenuItem*)m_menuItem, (gint)check );
910 break;
911
912 default:
913 wxFAIL_MSG( _T("can't check this item") );
914 }
915 }
916
917 void wxMenuItem::Enable( bool enable )
918 {
919 wxCHECK_RET( m_menuItem, wxT("invalid menu item") );
920
921 gtk_widget_set_sensitive( m_menuItem, enable );
922 wxMenuItemBase::Enable( enable );
923 }
924
925 bool wxMenuItem::IsChecked() const
926 {
927 wxCHECK_MSG( m_menuItem, FALSE, wxT("invalid menu item") );
928
929 wxCHECK_MSG( IsCheckable(), FALSE,
930 wxT("can't get state of uncheckable item!") );
931
932 return ((GtkCheckMenuItem*)m_menuItem)->active != 0;
933 }
934
935 //-----------------------------------------------------------------------------
936 // wxMenu
937 //-----------------------------------------------------------------------------
938
939 IMPLEMENT_DYNAMIC_CLASS(wxMenu,wxEvtHandler)
940
941 void wxMenu::Init()
942 {
943 m_accel = gtk_accel_group_new();
944 m_menu = gtk_menu_new();
945 // NB: keep reference to the menu so that it is not destroyed behind
946 // our back by GTK+ e.g. when it is removed from menubar:
947 gtk_widget_ref(m_menu);
948
949 m_owner = (GtkWidget*) NULL;
950
951 // Tearoffs are entries, just like separators. So if we want this
952 // menu to be a tear-off one, we just append a tearoff entry
953 // immediately.
954 if ( m_style & wxMENU_TEAROFF )
955 {
956 GtkWidget *tearoff = gtk_tearoff_menu_item_new();
957
958 gtk_menu_append(GTK_MENU(m_menu), tearoff);
959 }
960
961 m_prevRadio = NULL;
962
963 // append the title as the very first entry if we have it
964 if ( !m_title.empty() )
965 {
966 Append(wxGTK_TITLE_ID, m_title);
967 AppendSeparator();
968 }
969 }
970
971 wxMenu::~wxMenu()
972 {
973 WX_CLEAR_LIST(wxMenuItemList, m_items);
974
975 if ( GTK_IS_WIDGET( m_menu ))
976 {
977 // see wxMenu::Init
978 gtk_widget_unref( m_menu );
979 // if the menu is inserted in another menu at this time, there was
980 // one more reference to it:
981 if ( m_owner )
982 gtk_widget_destroy( m_menu );
983 }
984 }
985
986 bool wxMenu::GtkAppend(wxMenuItem *mitem, int pos)
987 {
988 GtkWidget *menuItem;
989
990 if ( mitem->IsSeparator() )
991 {
992 #ifdef __WXGTK20__
993 menuItem = gtk_separator_menu_item_new();
994 #else
995 // TODO
996 menuItem = gtk_menu_item_new();
997 #endif
998 if (pos == -1)
999 gtk_menu_shell_append(GTK_MENU_SHELL(m_menu), menuItem);
1000 else
1001 gtk_menu_shell_insert(GTK_MENU_SHELL(m_menu), menuItem, pos);
1002 }
1003 else if ( mitem->IsSubMenu() )
1004 {
1005 // text has "_" instead of "&" after mitem->SetText()
1006 wxString text( mitem->GetText() );
1007
1008 #ifdef __WXGTK20__
1009 menuItem = gtk_menu_item_new_with_mnemonic( wxGTK_CONV( text ) );
1010 #else
1011 menuItem = gtk_menu_item_new_with_label( wxGTK_CONV( text ) );
1012 GtkLabel *label = GTK_LABEL( GTK_BIN(menuItem)->child );
1013 // set new text
1014 gtk_label_set_text( label, wxGTK_CONV( text ) );
1015 // reparse key accel
1016 guint accel_key = gtk_label_parse_uline (GTK_LABEL(label), wxGTK_CONV( text ) );
1017 if (accel_key != GDK_VoidSymbol)
1018 {
1019 gtk_widget_add_accelerator (menuItem,
1020 "activate_item",
1021 gtk_menu_ensure_uline_accel_group(GTK_MENU(m_menu)),
1022 accel_key,
1023 GDK_MOD1_MASK,
1024 GTK_ACCEL_LOCKED);
1025 }
1026 #endif
1027
1028 gtk_menu_item_set_submenu( GTK_MENU_ITEM(menuItem), mitem->GetSubMenu()->m_menu );
1029 if (pos == -1)
1030 gtk_menu_shell_append(GTK_MENU_SHELL(m_menu), menuItem);
1031 else
1032 gtk_menu_shell_insert(GTK_MENU_SHELL(m_menu), menuItem, pos);
1033
1034 gtk_widget_show( mitem->GetSubMenu()->m_menu );
1035
1036 // if adding a submenu to a menu already existing in the menu bar, we
1037 // must set invoking window to allow processing events from this
1038 // submenu
1039 if ( m_invokingWindow )
1040 wxMenubarSetInvokingWindow(mitem->GetSubMenu(), m_invokingWindow);
1041
1042 m_prevRadio = NULL;
1043 }
1044 else if (mitem->GetBitmap().Ok())
1045 {
1046 wxString text = mitem->GetText();
1047 const wxBitmap *bitmap = &mitem->GetBitmap();
1048
1049 #ifdef __WXGTK20__
1050 menuItem = gtk_image_menu_item_new_with_mnemonic( wxGTK_CONV( text ) );
1051
1052 GtkWidget *image;
1053 if (bitmap->HasPixbuf())
1054 {
1055 image = gtk_image_new_from_pixbuf(bitmap->GetPixbuf());
1056 }
1057 else
1058 {
1059 GdkPixmap *gdk_pixmap = bitmap->GetPixmap();
1060 GdkBitmap *gdk_bitmap = bitmap->GetMask() ?
1061 bitmap->GetMask()->GetBitmap() :
1062 (GdkBitmap*) NULL;
1063 image = gtk_image_new_from_pixmap( gdk_pixmap, gdk_bitmap );
1064 }
1065
1066 gtk_widget_show(image);
1067
1068 gtk_image_menu_item_set_image( GTK_IMAGE_MENU_ITEM(menuItem), image );
1069
1070 gtk_signal_connect( GTK_OBJECT(menuItem), "activate",
1071 GTK_SIGNAL_FUNC(gtk_menu_clicked_callback),
1072 (gpointer)this );
1073
1074 if (pos == -1)
1075 gtk_menu_shell_append(GTK_MENU_SHELL(m_menu), menuItem);
1076 else
1077 gtk_menu_shell_insert(GTK_MENU_SHELL(m_menu), menuItem, pos);
1078 #else
1079 GdkPixmap *gdk_pixmap = bitmap->GetPixmap();
1080 GdkBitmap *gdk_bitmap = bitmap->GetMask() ? bitmap->GetMask()->GetBitmap() : (GdkBitmap*) NULL;
1081
1082 menuItem = gtk_pixmap_menu_item_new ();
1083 GtkWidget *label = gtk_accel_label_new ( wxGTK_CONV( text ) );
1084 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1085 gtk_container_add (GTK_CONTAINER (menuItem), label);
1086
1087 gtk_accel_label_set_accel_widget (GTK_ACCEL_LABEL (label), menuItem);
1088 guint accel_key;
1089 GdkModifierType accel_mods;
1090
1091 // accelerator for the item, as specified by its label
1092 // (ex. Ctrl+O for open)
1093 gtk_accelerator_parse(GetHotKey(*mitem).c_str(), &accel_key,
1094 &accel_mods);
1095 if (accel_key != GDK_VoidSymbol)
1096 {
1097 gtk_widget_add_accelerator (menuItem,
1098 "activate_item",
1099 m_accel,
1100 accel_key, accel_mods,
1101 GTK_ACCEL_VISIBLE);
1102 }
1103
1104 // accelerator for the underlined char (ex ALT+F for the File menu)
1105 accel_key = gtk_label_parse_uline (GTK_LABEL(label), wxGTK_CONV( text ) );
1106 if (accel_key != GDK_VoidSymbol)
1107 {
1108 gtk_widget_add_accelerator (menuItem,
1109 "activate_item",
1110 gtk_menu_ensure_uline_accel_group(GTK_MENU (m_menu)),
1111 accel_key,
1112 GDK_MOD1_MASK,
1113 GTK_ACCEL_LOCKED);
1114 }
1115
1116 gtk_widget_show (label);
1117
1118 mitem->SetLabelWidget(label);
1119
1120 GtkWidget* pixmap = gtk_pixmap_new( gdk_pixmap, gdk_bitmap );
1121 gtk_widget_show(pixmap);
1122 gtk_pixmap_menu_item_set_pixmap(GTK_PIXMAP_MENU_ITEM( menuItem ), pixmap);
1123
1124 gtk_signal_connect( GTK_OBJECT(menuItem), "activate",
1125 GTK_SIGNAL_FUNC(gtk_menu_clicked_callback),
1126 (gpointer)this );
1127
1128 if (pos == -1)
1129 gtk_menu_append( GTK_MENU(m_menu), menuItem );
1130 else
1131 gtk_menu_insert( GTK_MENU(m_menu), menuItem, pos );
1132 gtk_widget_show( menuItem );
1133 #endif
1134
1135 m_prevRadio = NULL;
1136 }
1137 else // a normal item
1138 {
1139 // text has "_" instead of "&" after mitem->SetText() so don't use it
1140 wxString text( mitem->GetText() );
1141
1142 switch ( mitem->GetKind() )
1143 {
1144 case wxITEM_CHECK:
1145 {
1146 #ifdef __WXGTK20__
1147 menuItem = gtk_check_menu_item_new_with_mnemonic( wxGTK_CONV( text ) );
1148 #else
1149 menuItem = gtk_check_menu_item_new_with_label( wxGTK_CONV( text ) );
1150 GtkLabel *label = GTK_LABEL( GTK_BIN(menuItem)->child );
1151 // set new text
1152 gtk_label_set_text( label, wxGTK_CONV( text ) );
1153 // reparse key accel
1154 guint accel_key = gtk_label_parse_uline (GTK_LABEL(label), wxGTK_CONV( text ) );
1155 if (accel_key != GDK_VoidSymbol)
1156 {
1157 gtk_widget_add_accelerator (menuItem,
1158 "activate_item",
1159 gtk_menu_ensure_uline_accel_group(GTK_MENU(m_menu)),
1160 accel_key,
1161 GDK_MOD1_MASK,
1162 GTK_ACCEL_LOCKED);
1163 }
1164 #endif
1165 m_prevRadio = NULL;
1166 break;
1167 }
1168
1169 case wxITEM_RADIO:
1170 {
1171 GSList *group = NULL;
1172 if ( m_prevRadio == NULL )
1173 {
1174 // start of a new radio group
1175 #ifdef __WXGTK20__
1176 m_prevRadio = menuItem = gtk_radio_menu_item_new_with_mnemonic( group, wxGTK_CONV( text ) );
1177 #else
1178 m_prevRadio = menuItem = gtk_radio_menu_item_new_with_label( group, wxGTK_CONV( text ) );
1179 GtkLabel *label = GTK_LABEL( GTK_BIN(menuItem)->child );
1180 // set new text
1181 gtk_label_set_text( label, wxGTK_CONV( text ) );
1182 // reparse key accel
1183 guint accel_key = gtk_label_parse_uline (GTK_LABEL(label), wxGTK_CONV( text ) );
1184 if (accel_key != GDK_VoidSymbol)
1185 {
1186 gtk_widget_add_accelerator (menuItem,
1187 "activate_item",
1188 gtk_menu_ensure_uline_accel_group(GTK_MENU(m_menu)),
1189 accel_key,
1190 GDK_MOD1_MASK,
1191 GTK_ACCEL_LOCKED);
1192 }
1193 #endif
1194 }
1195 else // continue the radio group
1196 {
1197 #ifdef __WXGTK20__
1198 group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (m_prevRadio));
1199 m_prevRadio = menuItem = gtk_radio_menu_item_new_with_mnemonic( group, wxGTK_CONV( text ) );
1200 #else
1201 group = gtk_radio_menu_item_group (GTK_RADIO_MENU_ITEM (m_prevRadio));
1202 m_prevRadio = menuItem = gtk_radio_menu_item_new_with_label( group, wxGTK_CONV( text ) );
1203 GtkLabel *label = GTK_LABEL( GTK_BIN(menuItem)->child );
1204 // set new text
1205 gtk_label_set_text( label, wxGTK_CONV( text ) );
1206 // reparse key accel
1207 guint accel_key = gtk_label_parse_uline (GTK_LABEL(label), wxGTK_CONV( text ) );
1208 if (accel_key != GDK_VoidSymbol)
1209 {
1210 gtk_widget_add_accelerator (menuItem,
1211 "activate_item",
1212 gtk_menu_ensure_uline_accel_group(GTK_MENU(m_menu)),
1213 accel_key,
1214 GDK_MOD1_MASK,
1215 GTK_ACCEL_LOCKED);
1216 }
1217 #endif
1218 }
1219 break;
1220 }
1221
1222 default:
1223 wxFAIL_MSG( _T("unexpected menu item kind") );
1224 // fall through
1225
1226 case wxITEM_NORMAL:
1227 {
1228 #ifdef __WXGTK20__
1229 menuItem = gtk_menu_item_new_with_mnemonic( wxGTK_CONV( text ) );
1230 #else
1231 menuItem = gtk_menu_item_new_with_label( wxGTK_CONV( text ) );
1232 GtkLabel *label = GTK_LABEL( GTK_BIN(menuItem)->child );
1233 // set new text
1234 gtk_label_set_text( label, wxGTK_CONV( text ) );
1235 // reparse key accel
1236 guint accel_key = gtk_label_parse_uline (GTK_LABEL(label), wxGTK_CONV( text ) );
1237 if (accel_key != GDK_VoidSymbol)
1238 {
1239 gtk_widget_add_accelerator (menuItem,
1240 "activate_item",
1241 gtk_menu_ensure_uline_accel_group(GTK_MENU(m_menu)),
1242 accel_key,
1243 GDK_MOD1_MASK,
1244 GTK_ACCEL_LOCKED);
1245 }
1246 #endif
1247 m_prevRadio = NULL;
1248 break;
1249 }
1250 }
1251
1252 gtk_signal_connect( GTK_OBJECT(menuItem), "activate",
1253 GTK_SIGNAL_FUNC(gtk_menu_clicked_callback),
1254 (gpointer)this );
1255
1256 if (pos == -1)
1257 gtk_menu_shell_append(GTK_MENU_SHELL(m_menu), menuItem);
1258 else
1259 gtk_menu_shell_insert(GTK_MENU_SHELL(m_menu), menuItem, pos);
1260 }
1261
1262 guint accel_key;
1263 GdkModifierType accel_mods;
1264 wxCharBuffer buf = wxGTK_CONV( GetHotKey(*mitem) );
1265
1266 // wxPrintf( wxT("item: %s hotkey %s\n"), mitem->GetText().c_str(), GetHotKey(*mitem).c_str() );
1267
1268 gtk_accelerator_parse( (const char*) buf, &accel_key, &accel_mods);
1269 if (accel_key != 0)
1270 {
1271 gtk_widget_add_accelerator (GTK_WIDGET(menuItem),
1272 "activate",
1273 m_accel,
1274 accel_key,
1275 accel_mods,
1276 GTK_ACCEL_VISIBLE);
1277 }
1278
1279 gtk_widget_show( menuItem );
1280
1281 if ( !mitem->IsSeparator() )
1282 {
1283 wxASSERT_MSG( menuItem, wxT("invalid menuitem") );
1284
1285 gtk_signal_connect( GTK_OBJECT(menuItem), "select",
1286 GTK_SIGNAL_FUNC(gtk_menu_hilight_callback),
1287 (gpointer)this );
1288
1289 gtk_signal_connect( GTK_OBJECT(menuItem), "deselect",
1290 GTK_SIGNAL_FUNC(gtk_menu_nolight_callback),
1291 (gpointer)this );
1292 }
1293
1294 mitem->SetMenuItem(menuItem);
1295
1296 if (ms_locked)
1297 {
1298 // This doesn't even exist!
1299 // gtk_widget_lock_accelerators(mitem->GetMenuItem());
1300 }
1301
1302 return TRUE;
1303 }
1304
1305 wxMenuItem* wxMenu::DoAppend(wxMenuItem *mitem)
1306 {
1307 if (!GtkAppend(mitem))
1308 return NULL;
1309
1310 return wxMenuBase::DoAppend(mitem);
1311 }
1312
1313 wxMenuItem* wxMenu::DoInsert(size_t pos, wxMenuItem *item)
1314 {
1315 if ( !wxMenuBase::DoInsert(pos, item) )
1316 return NULL;
1317
1318 // TODO
1319 if ( !GtkAppend(item, (int)pos) )
1320 return NULL;
1321
1322 return item;
1323 }
1324
1325 wxMenuItem *wxMenu::DoRemove(wxMenuItem *item)
1326 {
1327 if ( !wxMenuBase::DoRemove(item) )
1328 return (wxMenuItem *)NULL;
1329
1330 // TODO: this code doesn't delete the item factory item and this seems
1331 // impossible as of GTK 1.2.6.
1332 gtk_widget_destroy( item->GetMenuItem() );
1333
1334 return item;
1335 }
1336
1337 int wxMenu::FindMenuIdByMenuItem( GtkWidget *menuItem ) const
1338 {
1339 wxMenuItemList::compatibility_iterator node = m_items.GetFirst();
1340 while (node)
1341 {
1342 wxMenuItem *item = node->GetData();
1343 if (item->GetMenuItem() == menuItem)
1344 return item->GetId();
1345 node = node->GetNext();
1346 }
1347
1348 return wxNOT_FOUND;
1349 }
1350
1351 // ----------------------------------------------------------------------------
1352 // helpers
1353 // ----------------------------------------------------------------------------
1354
1355 #if wxUSE_ACCEL
1356
1357 static wxString GetHotKey( const wxMenuItem& item )
1358 {
1359 wxString hotkey;
1360
1361 wxAcceleratorEntry *accel = item.GetAccel();
1362 if ( accel )
1363 {
1364 int flags = accel->GetFlags();
1365 if ( flags & wxACCEL_ALT )
1366 hotkey += wxT("<alt>");
1367 if ( flags & wxACCEL_CTRL )
1368 hotkey += wxT("<control>");
1369 if ( flags & wxACCEL_SHIFT )
1370 hotkey += wxT("<shift>");
1371
1372 int code = accel->GetKeyCode();
1373 switch ( code )
1374 {
1375 case WXK_F1:
1376 case WXK_F2:
1377 case WXK_F3:
1378 case WXK_F4:
1379 case WXK_F5:
1380 case WXK_F6:
1381 case WXK_F7:
1382 case WXK_F8:
1383 case WXK_F9:
1384 case WXK_F10:
1385 case WXK_F11:
1386 case WXK_F12:
1387 hotkey += wxString::Format(wxT("F%d"), code - WXK_F1 + 1);
1388 break;
1389
1390 // TODO: we should use gdk_keyval_name() (a.k.a.
1391 // XKeysymToString) here as well as hardcoding the keysym
1392 // names this might be not portable
1393 case WXK_NUMPAD_INSERT:
1394 hotkey << wxT("KP_Insert" );
1395 break;
1396 case WXK_NUMPAD_DELETE:
1397 hotkey << wxT("KP_Delete" );
1398 break;
1399 case WXK_INSERT:
1400 hotkey << wxT("Insert" );
1401 break;
1402 case WXK_DELETE:
1403 hotkey << wxT("Delete" );
1404 break;
1405 case WXK_UP:
1406 hotkey << wxT("Up" );
1407 break;
1408 case WXK_DOWN:
1409 hotkey << wxT("Down" );
1410 break;
1411 case WXK_PAGEUP:
1412 case WXK_PRIOR:
1413 hotkey << wxT("Prior" );
1414 break;
1415 case WXK_PAGEDOWN:
1416 case WXK_NEXT:
1417 hotkey << wxT("Next" );
1418 break;
1419 case WXK_LEFT:
1420 hotkey << wxT("Left" );
1421 break;
1422 case WXK_RIGHT:
1423 hotkey << wxT("Right" );
1424 break;
1425 case WXK_HOME:
1426 hotkey << wxT("Home" );
1427 break;
1428 case WXK_END:
1429 hotkey << wxT("End" );
1430 break;
1431 case WXK_RETURN:
1432 hotkey << wxT("Return" );
1433 break;
1434
1435 // if there are any other keys wxGetAccelFromString() may
1436 // return, we should process them here
1437
1438 default:
1439 if ( code < 127 )
1440 {
1441 wxString name = wxGTK_CONV_BACK( gdk_keyval_name((guint)code) );
1442 if ( name )
1443 {
1444 hotkey << name;
1445 break;
1446 }
1447 }
1448
1449 wxFAIL_MSG( wxT("unknown keyboard accel") );
1450 }
1451
1452 delete accel;
1453 }
1454
1455 return hotkey;
1456 }
1457
1458 #endif // wxUSE_ACCEL
1459
1460
1461 //-----------------------------------------------------------------------------
1462 // substitute for missing GtkPixmapMenuItem
1463 //-----------------------------------------------------------------------------
1464
1465 #ifndef __WXGTK20__
1466
1467 /*
1468 * Copyright (C) 1998, 1999, 2000 Free Software Foundation
1469 * All rights reserved.
1470 *
1471 * This file is part of the Gnome Library.
1472 *
1473 * The Gnome Library is free software; you can redistribute it and/or
1474 * modify it under the terms of the GNU Library General Public License as
1475 * published by the Free Software Foundation; either version 2 of the
1476 * License, or (at your option) any later version.
1477 *
1478 * The Gnome Library is distributed in the hope that it will be useful,
1479 * but WITHOUT ANY WARRANTY; without even the implied warranty of
1480 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1481 * Library General Public License for more details.
1482 *
1483 * You should have received a copy of the GNU Library General Public
1484 * License along with the Gnome Library; see the file COPYING.LIB. If not,
1485 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
1486 * Boston, MA 02111-1307, USA.
1487 */
1488 /*
1489 @NOTATION@
1490 */
1491
1492 /* Author: Dietmar Maurer <dm@vlsivie.tuwien.ac.at> */
1493
1494 #include <gtk/gtkaccellabel.h>
1495 #include <gtk/gtksignal.h>
1496 #include <gtk/gtkmenuitem.h>
1497 #include <gtk/gtkmenu.h>
1498 #include <gtk/gtkcontainer.h>
1499
1500 extern "C"
1501 {
1502
1503 static void gtk_pixmap_menu_item_class_init (GtkPixmapMenuItemClass *klass);
1504 static void gtk_pixmap_menu_item_init (GtkPixmapMenuItem *menu_item);
1505 static void gtk_pixmap_menu_item_draw (GtkWidget *widget,
1506 GdkRectangle *area);
1507 static gint gtk_pixmap_menu_item_expose (GtkWidget *widget,
1508 GdkEventExpose *event);
1509
1510 /* we must override the following functions */
1511
1512 static void gtk_pixmap_menu_item_map (GtkWidget *widget);
1513 static void gtk_pixmap_menu_item_size_allocate (GtkWidget *widget,
1514 GtkAllocation *allocation);
1515 static void gtk_pixmap_menu_item_forall (GtkContainer *container,
1516 gboolean include_internals,
1517 GtkCallback callback,
1518 gpointer callback_data);
1519 static void gtk_pixmap_menu_item_size_request (GtkWidget *widget,
1520 GtkRequisition *requisition);
1521 static void gtk_pixmap_menu_item_remove (GtkContainer *container,
1522 GtkWidget *child);
1523
1524 static void changed_have_pixmap_status (GtkPixmapMenuItem *menu_item);
1525
1526 static GtkMenuItemClass *parent_class = NULL;
1527
1528 }
1529
1530 #define BORDER_SPACING 3
1531 #define PMAP_WIDTH 20
1532
1533 GtkType
1534 gtk_pixmap_menu_item_get_type (void)
1535 {
1536 static GtkType pixmap_menu_item_type = 0;
1537
1538 if (!pixmap_menu_item_type)
1539 {
1540 GtkTypeInfo pixmap_menu_item_info =
1541 {
1542 (char *)"GtkPixmapMenuItem",
1543 sizeof (GtkPixmapMenuItem),
1544 sizeof (GtkPixmapMenuItemClass),
1545 (GtkClassInitFunc) gtk_pixmap_menu_item_class_init,
1546 (GtkObjectInitFunc) gtk_pixmap_menu_item_init,
1547 /* reserved_1 */ NULL,
1548 /* reserved_2 */ NULL,
1549 (GtkClassInitFunc) NULL,
1550 };
1551
1552 pixmap_menu_item_type = gtk_type_unique (gtk_menu_item_get_type (),
1553 &pixmap_menu_item_info);
1554 }
1555
1556 return pixmap_menu_item_type;
1557 }
1558
1559 /**
1560 * gtk_pixmap_menu_item_new
1561 *
1562 * Creates a new pixmap menu item. Use gtk_pixmap_menu_item_set_pixmap()
1563 * to set the pixmap wich is displayed at the left side.
1564 *
1565 * Returns:
1566 * &GtkWidget pointer to new menu item
1567 **/
1568
1569 GtkWidget*
1570 gtk_pixmap_menu_item_new (void)
1571 {
1572 return GTK_WIDGET (gtk_type_new (gtk_pixmap_menu_item_get_type ()));
1573 }
1574
1575 static void
1576 gtk_pixmap_menu_item_class_init (GtkPixmapMenuItemClass *klass)
1577 {
1578 GtkObjectClass *object_class;
1579 GtkWidgetClass *widget_class;
1580 GtkMenuItemClass *menu_item_class;
1581 GtkContainerClass *container_class;
1582
1583 object_class = (GtkObjectClass*) klass;
1584 widget_class = (GtkWidgetClass*) klass;
1585 menu_item_class = (GtkMenuItemClass*) klass;
1586 container_class = (GtkContainerClass*) klass;
1587
1588 parent_class = (GtkMenuItemClass*) gtk_type_class (gtk_menu_item_get_type ());
1589
1590 widget_class->draw = gtk_pixmap_menu_item_draw;
1591 widget_class->expose_event = gtk_pixmap_menu_item_expose;
1592 widget_class->map = gtk_pixmap_menu_item_map;
1593 widget_class->size_allocate = gtk_pixmap_menu_item_size_allocate;
1594 widget_class->size_request = gtk_pixmap_menu_item_size_request;
1595
1596 container_class->forall = gtk_pixmap_menu_item_forall;
1597 container_class->remove = gtk_pixmap_menu_item_remove;
1598
1599 klass->orig_toggle_size = menu_item_class->toggle_size;
1600 klass->have_pixmap_count = 0;
1601 }
1602
1603 static void
1604 gtk_pixmap_menu_item_init (GtkPixmapMenuItem *menu_item)
1605 {
1606 GtkMenuItem *mi;
1607
1608 mi = GTK_MENU_ITEM (menu_item);
1609
1610 menu_item->pixmap = NULL;
1611 }
1612
1613 static void
1614 gtk_pixmap_menu_item_draw (GtkWidget *widget,
1615 GdkRectangle *area)
1616 {
1617 g_return_if_fail (widget != NULL);
1618 g_return_if_fail (GTK_IS_PIXMAP_MENU_ITEM (widget));
1619 g_return_if_fail (area != NULL);
1620
1621 if (GTK_WIDGET_CLASS (parent_class)->draw)
1622 (* GTK_WIDGET_CLASS (parent_class)->draw) (widget, area);
1623
1624 if (GTK_WIDGET_DRAWABLE (widget) &&
1625 GTK_PIXMAP_MENU_ITEM(widget)->pixmap) {
1626 gtk_widget_draw(GTK_WIDGET(GTK_PIXMAP_MENU_ITEM(widget)->pixmap),NULL);
1627 }
1628 }
1629
1630 static gint
1631 gtk_pixmap_menu_item_expose (GtkWidget *widget,
1632 GdkEventExpose *event)
1633 {
1634 g_return_val_if_fail (widget != NULL, FALSE);
1635 g_return_val_if_fail (GTK_IS_PIXMAP_MENU_ITEM (widget), FALSE);
1636 g_return_val_if_fail (event != NULL, FALSE);
1637
1638 if (GTK_WIDGET_CLASS (parent_class)->expose_event)
1639 (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
1640
1641 if (GTK_WIDGET_DRAWABLE (widget) &&
1642 GTK_PIXMAP_MENU_ITEM(widget)->pixmap) {
1643 gtk_widget_draw(GTK_WIDGET(GTK_PIXMAP_MENU_ITEM(widget)->pixmap),NULL);
1644 }
1645
1646 return FALSE;
1647 }
1648
1649 /**
1650 * gtk_pixmap_menu_item_set_pixmap
1651 * @menu_item: Pointer to the pixmap menu item
1652 * @pixmap: Pointer to a pixmap widget
1653 *
1654 * Set the pixmap of the menu item.
1655 *
1656 **/
1657
1658 void
1659 gtk_pixmap_menu_item_set_pixmap (GtkPixmapMenuItem *menu_item,
1660 GtkWidget *pixmap)
1661 {
1662 g_return_if_fail (menu_item != NULL);
1663 g_return_if_fail (pixmap != NULL);
1664 g_return_if_fail (GTK_IS_PIXMAP_MENU_ITEM (menu_item));
1665 g_return_if_fail (GTK_IS_WIDGET (pixmap));
1666 g_return_if_fail (menu_item->pixmap == NULL);
1667
1668 gtk_widget_set_parent (pixmap, GTK_WIDGET (menu_item));
1669 menu_item->pixmap = pixmap;
1670
1671 if (GTK_WIDGET_REALIZED (pixmap->parent) &&
1672 !GTK_WIDGET_REALIZED (pixmap))
1673 gtk_widget_realize (pixmap);
1674
1675 if (GTK_WIDGET_VISIBLE (pixmap->parent)) {
1676 if (GTK_WIDGET_MAPPED (pixmap->parent) &&
1677 GTK_WIDGET_VISIBLE(pixmap) &&
1678 !GTK_WIDGET_MAPPED (pixmap))
1679 gtk_widget_map (pixmap);
1680 }
1681
1682 changed_have_pixmap_status(menu_item);
1683
1684 if (GTK_WIDGET_VISIBLE (pixmap) && GTK_WIDGET_VISIBLE (menu_item))
1685 gtk_widget_queue_resize (pixmap);
1686 }
1687
1688 static void
1689 gtk_pixmap_menu_item_map (GtkWidget *widget)
1690 {
1691 GtkPixmapMenuItem *menu_item;
1692
1693 g_return_if_fail (widget != NULL);
1694 g_return_if_fail (GTK_IS_PIXMAP_MENU_ITEM (widget));
1695
1696 menu_item = GTK_PIXMAP_MENU_ITEM(widget);
1697
1698 GTK_WIDGET_CLASS(parent_class)->map(widget);
1699
1700 if (menu_item->pixmap &&
1701 GTK_WIDGET_VISIBLE (menu_item->pixmap) &&
1702 !GTK_WIDGET_MAPPED (menu_item->pixmap))
1703 gtk_widget_map (menu_item->pixmap);
1704 }
1705
1706 static void
1707 gtk_pixmap_menu_item_size_allocate (GtkWidget *widget,
1708 GtkAllocation *allocation)
1709 {
1710 GtkPixmapMenuItem *pmenu_item;
1711
1712 pmenu_item = GTK_PIXMAP_MENU_ITEM(widget);
1713
1714 if (pmenu_item->pixmap && GTK_WIDGET_VISIBLE(pmenu_item))
1715 {
1716 GtkAllocation child_allocation;
1717 int border_width;
1718
1719 border_width = GTK_CONTAINER (widget)->border_width;
1720
1721 child_allocation.width = pmenu_item->pixmap->requisition.width;
1722 child_allocation.height = pmenu_item->pixmap->requisition.height;
1723 child_allocation.x = border_width + BORDER_SPACING;
1724 child_allocation.y = (border_width + BORDER_SPACING
1725 + (((allocation->height - child_allocation.height) - child_allocation.x)
1726 / 2)); /* center pixmaps vertically */
1727 gtk_widget_size_allocate (pmenu_item->pixmap, &child_allocation);
1728 }
1729
1730 if (GTK_WIDGET_CLASS (parent_class)->size_allocate)
1731 GTK_WIDGET_CLASS(parent_class)->size_allocate (widget, allocation);
1732 }
1733
1734 static void
1735 gtk_pixmap_menu_item_forall (GtkContainer *container,
1736 gboolean include_internals,
1737 GtkCallback callback,
1738 gpointer callback_data)
1739 {
1740 GtkPixmapMenuItem *menu_item;
1741
1742 g_return_if_fail (container != NULL);
1743 g_return_if_fail (GTK_IS_PIXMAP_MENU_ITEM (container));
1744 g_return_if_fail (callback != NULL);
1745
1746 menu_item = GTK_PIXMAP_MENU_ITEM (container);
1747
1748 if (menu_item->pixmap)
1749 (* callback) (menu_item->pixmap, callback_data);
1750
1751 GTK_CONTAINER_CLASS(parent_class)->forall(container,include_internals,
1752 callback,callback_data);
1753 }
1754
1755 static void
1756 gtk_pixmap_menu_item_size_request (GtkWidget *widget,
1757 GtkRequisition *requisition)
1758 {
1759 GtkPixmapMenuItem *menu_item;
1760 GtkRequisition req = {0, 0};
1761
1762 g_return_if_fail (widget != NULL);
1763 g_return_if_fail (GTK_IS_MENU_ITEM (widget));
1764 g_return_if_fail (requisition != NULL);
1765
1766 GTK_WIDGET_CLASS(parent_class)->size_request(widget,requisition);
1767
1768 menu_item = GTK_PIXMAP_MENU_ITEM (widget);
1769
1770 if (menu_item->pixmap)
1771 gtk_widget_size_request(menu_item->pixmap, &req);
1772
1773 requisition->height = MAX(req.height + GTK_CONTAINER(widget)->border_width + BORDER_SPACING, (unsigned int) requisition->height);
1774 requisition->width += (req.width + GTK_CONTAINER(widget)->border_width + BORDER_SPACING);
1775 }
1776
1777 static void
1778 gtk_pixmap_menu_item_remove (GtkContainer *container,
1779 GtkWidget *child)
1780 {
1781 GtkBin *bin;
1782 gboolean widget_was_visible;
1783
1784 g_return_if_fail (container != NULL);
1785 g_return_if_fail (GTK_IS_PIXMAP_MENU_ITEM (container));
1786 g_return_if_fail (child != NULL);
1787 g_return_if_fail (GTK_IS_WIDGET (child));
1788
1789 bin = GTK_BIN (container);
1790 g_return_if_fail ((bin->child == child ||
1791 (GTK_PIXMAP_MENU_ITEM(container)->pixmap == child)));
1792
1793 widget_was_visible = GTK_WIDGET_VISIBLE (child);
1794
1795 gtk_widget_unparent (child);
1796 if (bin->child == child)
1797 bin->child = NULL;
1798 else {
1799 GTK_PIXMAP_MENU_ITEM(container)->pixmap = NULL;
1800 changed_have_pixmap_status(GTK_PIXMAP_MENU_ITEM(container));
1801 }
1802
1803 if (widget_was_visible)
1804 gtk_widget_queue_resize (GTK_WIDGET (container));
1805 }
1806
1807
1808 /* important to only call this if there was actually a _change_ in pixmap == NULL */
1809 static void
1810 changed_have_pixmap_status (GtkPixmapMenuItem *menu_item)
1811 {
1812 if (menu_item->pixmap != NULL) {
1813 GTK_PIXMAP_MENU_ITEM_GET_CLASS(menu_item)->have_pixmap_count += 1;
1814
1815 if (GTK_PIXMAP_MENU_ITEM_GET_CLASS(menu_item)->have_pixmap_count == 1) {
1816 /* Install pixmap toggle size */
1817 GTK_MENU_ITEM_GET_CLASS(menu_item)->toggle_size = MAX(GTK_PIXMAP_MENU_ITEM_GET_CLASS(menu_item)->orig_toggle_size, PMAP_WIDTH);
1818 }
1819 } else {
1820 GTK_PIXMAP_MENU_ITEM_GET_CLASS(menu_item)->have_pixmap_count -= 1;
1821
1822 if (GTK_PIXMAP_MENU_ITEM_GET_CLASS(menu_item)->have_pixmap_count == 0) {
1823 /* Install normal toggle size */
1824 GTK_MENU_ITEM_GET_CLASS(menu_item)->toggle_size = GTK_PIXMAP_MENU_ITEM_GET_CLASS(menu_item)->orig_toggle_size;
1825 }
1826 }
1827
1828 /* Note that we actually need to do this for _all_ GtkPixmapMenuItem
1829 whenever the klass->toggle_size changes; but by doing it anytime
1830 this function is called, we get the same effect, just because of
1831 how the preferences option to show pixmaps works. Bogus, broken.
1832 */
1833 if (GTK_WIDGET_VISIBLE(GTK_WIDGET(menu_item)))
1834 gtk_widget_queue_resize(GTK_WIDGET(menu_item));
1835 }
1836
1837 #endif
1838