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