]> git.saurik.com Git - wxWidgets.git/blame_incremental - src/gtk1/menu.cpp
fixed bug due to which all items in a virtual control stayed sometimes selected even...
[wxWidgets.git] / src / gtk1 / menu.cpp
... / ...
CommitLineData
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
50extern void wxapp_install_idle_handler();
51extern 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
80typedef struct _GtkPixmapMenuItem GtkPixmapMenuItem;
81typedef struct _GtkPixmapMenuItemClass GtkPixmapMenuItemClass;
82
83struct _GtkPixmapMenuItem
84{
85 GtkMenuItem menu_item;
86
87 GtkWidget *pixmap;
88};
89
90struct _GtkPixmapMenuItemClass
91{
92 GtkMenuItemClass parent_class;
93
94 guint orig_toggle_size;
95 guint have_pixmap_count;
96};
97
98
99GtkType gtk_pixmap_menu_item_get_type (void);
100GtkWidget* gtk_pixmap_menu_item_new (void);
101void 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
110static 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
160IMPLEMENT_DYNAMIC_CLASS(wxMenuBar,wxWindow)
161
162wxMenuBar::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
203wxMenuBar::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
235wxMenuBar::~wxMenuBar()
236{
237// gtk_object_unref( GTK_OBJECT(m_factory) ); why not ?
238}
239
240static 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
263static 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
288void 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
311void 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
332bool 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
340bool 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
398bool 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
429wxMenu *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
442wxMenu *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
470static 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
492int 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
508static 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
526wxMenuItem* 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
545void 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
557wxString 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
586void 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
616static 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
660static 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
685static 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
711IMPLEMENT_DYNAMIC_CLASS(wxMenuItem, wxObject)
712
713wxMenuItem *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
723wxMenuItem::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
734wxMenuItem::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
746void wxMenuItem::Init(const wxString& text)
747{
748 m_labelWidget = (GtkWidget *) NULL;
749 m_menuItem = (GtkWidget *) NULL;
750
751 DoSetText(text);
752}
753
754wxMenuItem::~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 */
761wxString 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
787void 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
817void 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
871wxAcceleratorEntry *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
888void 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
909void 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
917bool 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
927wxString 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
951IMPLEMENT_DYNAMIC_CLASS(wxMenu,wxEvtHandler)
952
953void 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
990wxMenu::~wxMenu()
991{
992 m_items.Clear();
993
994 gtk_widget_destroy( m_menu );
995
996 gtk_object_unref( GTK_OBJECT(m_factory) );
997}
998
999bool 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
1217bool wxMenu::DoAppend(wxMenuItem *mitem)
1218{
1219 return GtkAppend(mitem) && wxMenuBase::DoAppend(mitem);
1220}
1221
1222bool 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
1254wxMenuItem *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
1266int 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
1286static 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
1400extern "C"
1401{
1402
1403static void gtk_pixmap_menu_item_class_init (GtkPixmapMenuItemClass *klass);
1404static void gtk_pixmap_menu_item_init (GtkPixmapMenuItem *menu_item);
1405static void gtk_pixmap_menu_item_draw (GtkWidget *widget,
1406 GdkRectangle *area);
1407static gint gtk_pixmap_menu_item_expose (GtkWidget *widget,
1408 GdkEventExpose *event);
1409
1410/* we must override the following functions */
1411
1412static void gtk_pixmap_menu_item_map (GtkWidget *widget);
1413static void gtk_pixmap_menu_item_size_allocate (GtkWidget *widget,
1414 GtkAllocation *allocation);
1415static void gtk_pixmap_menu_item_forall (GtkContainer *container,
1416 gboolean include_internals,
1417 GtkCallback callback,
1418 gpointer callback_data);
1419static void gtk_pixmap_menu_item_size_request (GtkWidget *widget,
1420 GtkRequisition *requisition);
1421static void gtk_pixmap_menu_item_remove (GtkContainer *container,
1422 GtkWidget *child);
1423
1424static void changed_have_pixmap_status (GtkPixmapMenuItem *menu_item);
1425
1426static GtkMenuItemClass *parent_class = NULL;
1427
1428}
1429
1430#define BORDER_SPACING 3
1431#define PMAP_WIDTH 20
1432
1433GtkType
1434gtk_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
1469GtkWidget*
1470gtk_pixmap_menu_item_new (void)
1471{
1472 return GTK_WIDGET (gtk_type_new (gtk_pixmap_menu_item_get_type ()));
1473}
1474
1475static void
1476gtk_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
1503static void
1504gtk_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
1513static void
1514gtk_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
1530static gint
1531gtk_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
1558void
1559gtk_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
1588static void
1589gtk_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
1606static void
1607gtk_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
1634static void
1635gtk_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
1655static void
1656gtk_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
1677static void
1678gtk_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 */
1709static void
1710changed_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