1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2013-2019 CERN
5  * Copyright (C) 2013-2021 KiCad Developers, see AUTHORS.txt for contributors.
6  * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
7  * @author Maciej Suminski <maciej.suminski@cern.ch>
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, you may find one here:
21  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
22  * or you may search the http://www.gnu.org website for the version 2 license,
23  * or you may write to the Free Software Foundation, Inc.,
24  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
25  */
26 
27 #include <bitmaps.h>
28 #include <eda_base_frame.h>
29 #include <functional>
30 #include <id.h>
31 #include <kiface_base.h>
32 #include <menus_helpers.h>
33 #include <tool/action_menu.h>
34 #include <tool/actions.h>
35 #include <tool/tool_event.h>
36 #include <tool/tool_interactive.h>
37 #include <tool/tool_manager.h>
38 #include <trace_helpers.h>
39 #include <wx/log.h>
40 #include <wx/stc/stc.h>
41 #include <textentry_tricks.h>
42 #include <wx/listctrl.h>
43 
44 using namespace std::placeholders;
45 
46 
ACTION_MENU(bool isContextMenu,TOOL_INTERACTIVE * aTool)47 ACTION_MENU::ACTION_MENU( bool isContextMenu, TOOL_INTERACTIVE* aTool ) :
48     m_isForcedPosition( false ),
49     m_dirty( true ),
50     m_titleDisplayed( false ),
51     m_isContextMenu( isContextMenu ),
52     m_icon( BITMAPS::INVALID_BITMAP ),
53     m_selected( -1 ),
54     m_tool( aTool )
55 {
56     setupEvents();
57 }
58 
59 
~ACTION_MENU()60 ACTION_MENU::~ACTION_MENU()
61 {
62     // Set parent to NULL to prevent submenus from unregistering from a nonexistent object
63     for( auto menu : m_submenus )
64         menu->SetParent( nullptr );
65 
66     ACTION_MENU* parent = dynamic_cast<ACTION_MENU*>( GetParent() );
67 
68     if( parent )
69         parent->m_submenus.remove( this );
70 }
71 
72 
SetIcon(BITMAPS aIcon)73 void ACTION_MENU::SetIcon( BITMAPS aIcon )
74 {
75     m_icon = aIcon;
76 }
77 
78 
setupEvents()79 void ACTION_MENU::setupEvents()
80 {
81     Connect( wxEVT_COMMAND_MENU_SELECTED, wxMenuEventHandler( ACTION_MENU::OnMenuEvent ), nullptr,
82              this );
83     Connect( wxEVT_IDLE, wxIdleEventHandler( ACTION_MENU::OnIdle ), nullptr, this );
84 }
85 
86 
SetTitle(const wxString & aTitle)87 void ACTION_MENU::SetTitle( const wxString& aTitle )
88 {
89     // Unfortunately wxMenu::SetTitle() does not work very well, so this is an alternative version
90     m_title = aTitle;
91 
92     // Update the menu title
93     if( m_titleDisplayed )
94         DisplayTitle( true );
95 }
96 
97 
DisplayTitle(bool aDisplay)98 void ACTION_MENU::DisplayTitle( bool aDisplay )
99 {
100     if( ( !aDisplay || m_title.IsEmpty() ) && m_titleDisplayed )
101     {
102         // Destroy the menu entry keeping the title..
103         wxMenuItem* item = FindItemByPosition( 0 );
104         wxASSERT( item->GetItemLabelText() == GetTitle() );
105         Destroy( item );
106         // ..and separator
107         item = FindItemByPosition( 0 );
108         wxASSERT( item->IsSeparator() );
109         Destroy( item );
110         m_titleDisplayed = false;
111     }
112 
113     else if( aDisplay && !m_title.IsEmpty() )
114     {
115         if( m_titleDisplayed )
116         {
117             // Simply update the title
118             FindItemByPosition( 0 )->SetItemLabel( m_title );
119         }
120         else
121         {
122             // Add a separator and a menu entry to display the title
123             InsertSeparator( 0 );
124             Insert( 0, new wxMenuItem( this, wxID_NONE, m_title, wxEmptyString, wxITEM_NORMAL ) );
125 
126             if( !!m_icon )
127                 AddBitmapToMenuItem( FindItemByPosition( 0 ), KiBitmap( m_icon ) );
128 
129             m_titleDisplayed = true;
130         }
131     }
132 }
133 
134 
Add(const wxString & aLabel,int aId,BITMAPS aIcon)135 wxMenuItem* ACTION_MENU::Add( const wxString& aLabel, int aId, BITMAPS aIcon )
136 {
137     wxASSERT_MSG( FindItem( aId ) == nullptr, "Duplicate menu IDs!" );
138 
139     wxMenuItem* item = new wxMenuItem( this, aId, aLabel, wxEmptyString, wxITEM_NORMAL );
140 
141     if( !!aIcon )
142         AddBitmapToMenuItem( item, KiBitmap( aIcon ) );
143 
144     return Append( item );
145 }
146 
147 
Add(const wxString & aLabel,const wxString & aTooltip,int aId,BITMAPS aIcon,bool aIsCheckmarkEntry)148 wxMenuItem* ACTION_MENU::Add( const wxString& aLabel, const wxString& aTooltip, int aId,
149                               BITMAPS aIcon, bool aIsCheckmarkEntry )
150 {
151     wxASSERT_MSG( FindItem( aId ) == nullptr, "Duplicate menu IDs!" );
152 
153     wxMenuItem* item = new wxMenuItem( this, aId, aLabel, aTooltip,
154                                        aIsCheckmarkEntry ? wxITEM_CHECK : wxITEM_NORMAL );
155 
156     if( !!aIcon )
157         AddBitmapToMenuItem( item, KiBitmap( aIcon ) );
158 
159     return Append( item );
160 }
161 
162 
Add(const TOOL_ACTION & aAction,bool aIsCheckmarkEntry,const wxString & aOverrideLabel)163 wxMenuItem* ACTION_MENU::Add( const TOOL_ACTION& aAction, bool aIsCheckmarkEntry,
164                               const wxString& aOverrideLabel )
165 {
166     /// ID numbers for tool actions are assigned above ACTION_BASE_UI_ID inside TOOL_EVENT
167     BITMAPS icon = aAction.GetIcon();
168 
169     // Allow the label to be overridden at point of use
170     wxString menuLabel = aOverrideLabel.IsEmpty() ? aAction.GetMenuItem() :  aOverrideLabel;
171 
172     wxMenuItem* item = new wxMenuItem( this, aAction.GetUIId(), menuLabel,
173                                        aAction.GetDescription(),
174                                        aIsCheckmarkEntry ? wxITEM_CHECK : wxITEM_NORMAL );
175     if( !!icon )
176         AddBitmapToMenuItem( item, KiBitmap( icon ) );
177 
178     m_toolActions[aAction.GetUIId()] = &aAction;
179 
180     return Append( item );
181 }
182 
183 
Add(ACTION_MENU * aMenu)184 wxMenuItem* ACTION_MENU::Add( ACTION_MENU* aMenu )
185 {
186     ACTION_MENU* menuCopy = aMenu->Clone();
187     m_submenus.push_back( menuCopy );
188 
189     wxASSERT_MSG( !menuCopy->m_title.IsEmpty(), "Set a title for ACTION_MENU using SetTitle()" );
190 
191     if( !!aMenu->m_icon )
192     {
193         wxMenuItem* newItem = new wxMenuItem( this, -1, menuCopy->m_title );
194         AddBitmapToMenuItem( newItem, KiBitmap( aMenu->m_icon ) );
195         newItem->SetSubMenu( menuCopy );
196         return Append( newItem );
197     }
198     else
199     {
200         return AppendSubMenu( menuCopy, menuCopy->m_title );
201     }
202 }
203 
204 
AddClose(const wxString & aAppname)205 void ACTION_MENU::AddClose( const wxString& aAppname )
206 {
207 #ifdef __WINDOWS__
208     Add( _( "Close" ),
209          wxString::Format( _( "Close %s" ), aAppname ),
210          wxID_CLOSE,
211          BITMAPS::exit );
212 #else
213     Add( _( "Close" ) + "\tCtrl+W",
214          wxString::Format( _( "Close %s" ), aAppname ),
215          wxID_CLOSE,
216          BITMAPS::exit );
217 #endif
218 }
219 
220 
AddQuitOrClose(KIFACE_BASE * aKiface,wxString aAppname)221 void ACTION_MENU::AddQuitOrClose( KIFACE_BASE* aKiface, wxString aAppname )
222 {
223     if( !aKiface || aKiface->IsSingle() ) // not when under a project mgr
224     {
225         // Don't use ACTIONS::quit; wxWidgets moves this on OSX and expects to find it via
226         // wxID_EXIT
227         Add( _( "Quit" ) + "\tCtrl+Q",
228              wxString::Format( _( "Quit %s" ), aAppname ),
229              wxID_EXIT,
230              BITMAPS::exit );
231     }
232     else
233     {
234         AddClose( aAppname );
235     }
236 }
237 
238 
Clear()239 void ACTION_MENU::Clear()
240 {
241     m_titleDisplayed = false;
242 
243     for( int i = GetMenuItemCount() - 1; i >= 0; --i )
244         Destroy( FindItemByPosition( i ) );
245 
246     m_toolActions.clear();
247     m_submenus.clear();
248 
249     wxASSERT( GetMenuItemCount() == 0 );
250 }
251 
252 
HasEnabledItems() const253 bool ACTION_MENU::HasEnabledItems() const
254 {
255     for( wxMenuItem* item : GetMenuItems() )
256     {
257         if( item->IsEnabled() && !item->IsSeparator() )
258             return true;
259     }
260 
261     return false;
262 }
263 
264 
UpdateAll()265 void ACTION_MENU::UpdateAll()
266 {
267     try
268     {
269         update();
270     }
271     catch( std::exception& )
272     {
273     }
274 
275     if( m_tool )
276         updateHotKeys();
277 
278     runOnSubmenus( std::bind( &ACTION_MENU::UpdateAll, _1 ) );
279 }
280 
281 
ClearDirty()282 void ACTION_MENU::ClearDirty()
283 {
284     m_dirty = false;
285     runOnSubmenus( std::bind( &ACTION_MENU::ClearDirty, _1 ) );
286 }
287 
288 
SetDirty()289 void ACTION_MENU::SetDirty()
290 {
291     m_dirty = true;
292     runOnSubmenus( std::bind( &ACTION_MENU::SetDirty, _1 ) );
293 }
294 
295 
SetTool(TOOL_INTERACTIVE * aTool)296 void ACTION_MENU::SetTool( TOOL_INTERACTIVE* aTool )
297 {
298     m_tool = aTool;
299     runOnSubmenus( std::bind( &ACTION_MENU::SetTool, _1, aTool ) );
300 }
301 
302 
Clone() const303 ACTION_MENU* ACTION_MENU::Clone() const
304 {
305     ACTION_MENU* clone = create();
306     clone->Clear();
307     clone->copyFrom( *this );
308     return clone;
309 }
310 
311 
create() const312 ACTION_MENU* ACTION_MENU::create() const
313 {
314     ACTION_MENU* menu = new ACTION_MENU( false );
315 
316     wxASSERT_MSG( typeid( *this ) == typeid( *menu ),
317                   wxString::Format( "You need to override create() method for class %s",
318                                     typeid( *this ).name() ) );
319 
320     return menu;
321 }
322 
323 
getToolManager() const324 TOOL_MANAGER* ACTION_MENU::getToolManager() const
325 {
326     wxASSERT( m_tool );
327     return m_tool ? m_tool->GetManager() : nullptr;
328 }
329 
330 
updateHotKeys()331 void ACTION_MENU::updateHotKeys()
332 {
333     TOOL_MANAGER* toolMgr = getToolManager();
334 
335     for( std::pair<const int, const TOOL_ACTION*>& ii : m_toolActions )
336     {
337         int                id = ii.first;
338         const TOOL_ACTION& action = *ii.second;
339         int                key = toolMgr->GetHotKey( action ) & ~MD_MODIFIER_MASK;
340 
341         if( key > 0 )
342         {
343             int mod = toolMgr->GetHotKey( action ) & MD_MODIFIER_MASK;
344             int flags = 0;
345             wxMenuItem* item = FindChildItem( id );
346 
347             if( item )
348             {
349                 flags |= ( mod & MD_ALT ) ? wxACCEL_ALT : 0;
350                 flags |= ( mod & MD_CTRL ) ? wxACCEL_CTRL : 0;
351                 flags |= ( mod & MD_SHIFT ) ? wxACCEL_SHIFT : 0;
352 
353                 if( !flags )
354                     flags = wxACCEL_NORMAL;
355 
356                 wxAcceleratorEntry accel( flags, key, id, item );
357                 item->SetAccel( &accel );
358             }
359         }
360     }
361 }
362 
363 
364 // wxWidgets doesn't tell us when a menu command was generated from a hotkey or from
365 // a menu selection.  It's important to us because a hotkey can be an immediate action
366 // while the menu selection can not (as it has no associated position).
367 //
368 // We get around this by storing the last highlighted menuId.  If it matches the command
369 // id then we know this is a menu selection.  (You might think we could use the menuOpen
370 // menuClose events, but these are actually generated for hotkeys as well.)
371 
372 static int g_last_menu_highlighted_id = 0;
373 
374 
375 // We need to store the position of the mouse when the menu was opened so it can be passed
376 // to the command event generated when the menu item is selected.
377 static VECTOR2D g_menu_open_position;
378 
379 
OnIdle(wxIdleEvent & event)380 void ACTION_MENU::OnIdle( wxIdleEvent& event )
381 {
382     g_last_menu_highlighted_id = 0;
383     g_menu_open_position.x = 0.0;
384     g_menu_open_position.y = 0.0;
385 }
386 
387 
OnMenuEvent(wxMenuEvent & aEvent)388 void ACTION_MENU::OnMenuEvent( wxMenuEvent& aEvent )
389 {
390     OPT_TOOL_EVENT evt;
391     wxString       menuText;
392     wxEventType    type = aEvent.GetEventType();
393     wxWindow*      focus = wxWindow::FindFocus();
394 
395     if( type == wxEVT_MENU_OPEN )
396     {
397         if( m_dirty && m_tool )
398             getToolManager()->RunAction( ACTIONS::updateMenu, true, this );
399 
400         wxMenu* parent = dynamic_cast<wxMenu*>( GetParent() );
401 
402         // Don't update the position if this menu has a parent
403         if( !parent && m_tool )
404             g_menu_open_position = getToolManager()->GetMousePosition();
405 
406         g_last_menu_highlighted_id = 0;
407     }
408     else if( type == wxEVT_MENU_HIGHLIGHT )
409     {
410         if( aEvent.GetId() > 0 )
411             g_last_menu_highlighted_id = aEvent.GetId();
412 
413         evt = TOOL_EVENT( TC_COMMAND, TA_CHOICE_MENU_UPDATE, aEvent.GetId() );
414     }
415     else if( type == wxEVT_COMMAND_MENU_SELECTED )
416     {
417         // Despite our attempts to catch the theft of text editor CHAR_HOOK and CHAR events
418         // in TOOL_DISPATCHER::DispatchWxEvent, wxWidgets sometimes converts those it knows
419         // about into menu commands without ever generating the appropriate CHAR_HOOK and CHAR
420         // events first.
421         if( dynamic_cast<wxTextEntry*>( focus )
422                 || dynamic_cast<wxStyledTextCtrl*>( focus )
423                 || dynamic_cast<wxListView*>( focus ) )
424         {
425             // Original key event has been lost, so we have to re-create it from the menu's
426             // wxAcceleratorEntry.
427             wxMenuItem* menuItem = FindItem( aEvent.GetId() );
428             wxAcceleratorEntry* acceleratorKey = menuItem ? menuItem->GetAccel() : nullptr;
429 
430             if( acceleratorKey )
431             {
432                 wxKeyEvent keyEvent( wxEVT_CHAR_HOOK );
433                 keyEvent.m_keyCode = acceleratorKey->GetKeyCode();
434                 keyEvent.m_controlDown = ( acceleratorKey->GetFlags() & wxMOD_CONTROL ) > 0;
435                 keyEvent.m_shiftDown = ( acceleratorKey->GetFlags() & wxMOD_SHIFT ) > 0;
436                 keyEvent.m_altDown = ( acceleratorKey->GetFlags() & wxMOD_ALT ) > 0;
437 
438                 if( auto ctrl = dynamic_cast<wxTextEntry*>( focus ) )
439                     TEXTENTRY_TRICKS::OnCharHook( ctrl, keyEvent );
440                 else
441                     focus->HandleWindowEvent( keyEvent );
442 
443                 if( keyEvent.GetSkipped() )
444                 {
445                     keyEvent.SetEventType( wxEVT_CHAR );
446                     focus->HandleWindowEvent( keyEvent );
447                 }
448 
449                 // If the event was used as KEY event (not skipped) by the focused window,
450                 // just finish.
451                 // Otherwise this is actually a wxEVT_COMMAND_MENU_SELECTED, or the
452                 // focused window is read only
453                 if( !keyEvent.GetSkipped() )
454                     return;
455             }
456         }
457 
458         // Store the selected position, so it can be checked by the tools
459         m_selected = aEvent.GetId();
460 
461         ACTION_MENU* parent = dynamic_cast<ACTION_MENU*>( GetParent() );
462 
463         while( parent )
464         {
465             parent->m_selected = m_selected;
466             parent = dynamic_cast<ACTION_MENU*>( parent->GetParent() );
467         }
468 
469         // Check if there is a TOOL_ACTION for the given ID
470         if( m_selected >= TOOL_ACTION::GetBaseUIId() )
471             evt = findToolAction( m_selected );
472 
473         if( !evt )
474         {
475 #ifdef __WINDOWS__
476             if( !evt )
477             {
478                 // Try to find the submenu which holds the selected item
479                 wxMenu* menu = nullptr;
480                 FindItem( m_selected, &menu );
481 
482                 // This conditional compilation is probably not needed.
483                 // It will be removed later, for the Kicad V 6.x version.
484                 // But in "old" 3.0 version, the "&& menu != this" contition was added to avoid
485                 // hang.  This hang is no longer encountered in wxWidgets 3.0.4 version, and this
486                 // condition is no longer needed.  And in 3.1.2, we have to remove it, as
487                 // "menu != this" never happens ("menu != this" always happens in 3.1.1 and older!).
488 #if wxCHECK_VERSION( 3, 1, 2 )
489                 if( menu )
490 #else
491                 if( menu && menu != this )
492 #endif
493                 {
494                     ACTION_MENU* cxmenu = static_cast<ACTION_MENU*>( menu );
495                     evt = cxmenu->eventHandler( aEvent );
496                 }
497             }
498 #else
499             if( !evt )
500                 runEventHandlers( aEvent, evt );
501 #endif
502 
503             // Handling non-ACTION menu entries.  Two ranges of ids are supported:
504             //   between 0 and ID_CONTEXT_MENU_ID_MAX
505             //   between ID_POPUP_MENU_START and ID_POPUP_MENU_END
506 
507             #define ID_CONTEXT_MENU_ID_MAX wxID_LOWEST  /* = 100 should be plenty */
508 
509             if( !evt &&
510                     ( ( m_selected >= 0 && m_selected < ID_CONTEXT_MENU_ID_MAX ) ||
511                       ( m_selected >= ID_POPUP_MENU_START && m_selected <= ID_POPUP_MENU_END ) ) )
512             {
513                 ACTION_MENU* actionMenu = dynamic_cast<ACTION_MENU*>( GetParent() );
514 
515                 if( actionMenu && actionMenu->PassHelpTextToHandler() )
516                     menuText = GetHelpString( aEvent.GetId() );
517                 else
518                     menuText = GetLabelText( aEvent.GetId() );
519 
520                 evt = TOOL_EVENT( TC_COMMAND, TA_CHOICE_MENU_CHOICE, m_selected, AS_GLOBAL,
521                                   &menuText );
522             }
523         }
524     }
525 
526     // forward the action/update event to the TOOL_MANAGER
527     // clients that don't supply a tool will have to check GetSelected() themselves
528     if( evt && m_tool )
529     {
530         wxLogTrace( kicadTraceToolStack, "ACTION_MENU::OnMenuEvent %s", evt->Format() );
531 
532         // WARNING: if you're squeamish, look away.
533         // What follows is a series of egregious hacks necessitated by a lack of information from
534         // wxWidgets on where context-menu-commands and command-key-events originated.
535 
536         // If it's a context menu then fetch the mouse position from our context-menu-position
537         // hack.
538         if( m_isContextMenu )
539         {
540             evt->SetMousePosition( g_menu_open_position );
541         }
542         // Otherwise, if g_last_menu_highlighted_id matches then it's a menubar menu event and has
543         // no position.
544         else if( g_last_menu_highlighted_id == aEvent.GetId() )
545         {
546             evt->SetHasPosition( false );
547         }
548         // Otherwise it's a command-key-event and we need to get the mouse position from the tool
549         // manager so that immediate actions work.
550         else
551         {
552             evt->SetMousePosition( getToolManager()->GetMousePosition() );
553         }
554 
555         if( m_tool->GetManager() )
556             m_tool->GetManager()->ProcessEvent( *evt );
557     }
558     else
559     {
560         aEvent.Skip();
561     }
562 }
563 
564 
runEventHandlers(const wxMenuEvent & aMenuEvent,OPT_TOOL_EVENT & aToolEvent)565 void ACTION_MENU::runEventHandlers( const wxMenuEvent& aMenuEvent, OPT_TOOL_EVENT& aToolEvent )
566 {
567     aToolEvent = eventHandler( aMenuEvent );
568 
569     if( !aToolEvent )
570         runOnSubmenus( std::bind( &ACTION_MENU::runEventHandlers, _1, aMenuEvent, aToolEvent ) );
571 }
572 
573 
runOnSubmenus(std::function<void (ACTION_MENU *)> aFunction)574 void ACTION_MENU::runOnSubmenus( std::function<void(ACTION_MENU*)> aFunction )
575 {
576     try
577     {
578         std::for_each( m_submenus.begin(), m_submenus.end(), [&]( ACTION_MENU* m ) {
579             aFunction( m );
580             m->runOnSubmenus( aFunction );
581         } );
582     }
583     catch( std::exception& )
584     {
585     }
586 }
587 
588 
findToolAction(int aId)589 OPT_TOOL_EVENT ACTION_MENU::findToolAction( int aId )
590 {
591     OPT_TOOL_EVENT evt;
592 
593     auto findFunc = [&]( ACTION_MENU* m ) {
594         if( evt )
595             return;
596 
597         const auto it = m->m_toolActions.find( aId );
598 
599         if( it != m->m_toolActions.end() )
600             evt = it->second->MakeEvent();
601     };
602 
603     findFunc( this );
604 
605     if( !evt )
606         runOnSubmenus( findFunc );
607 
608     return evt;
609 }
610 
611 
copyFrom(const ACTION_MENU & aMenu)612 void ACTION_MENU::copyFrom( const ACTION_MENU& aMenu )
613 {
614     m_icon = aMenu.m_icon;
615     m_title = aMenu.m_title;
616     m_titleDisplayed = aMenu.m_titleDisplayed;
617     m_selected = -1; // aMenu.m_selected;
618     m_tool = aMenu.m_tool;
619     m_toolActions = aMenu.m_toolActions;
620 
621     // Copy all menu entries
622     for( int i = 0; i < (int) aMenu.GetMenuItemCount(); ++i )
623     {
624         wxMenuItem* item = aMenu.FindItemByPosition( i );
625         appendCopy( item );
626     }
627 }
628 
629 
appendCopy(const wxMenuItem * aSource)630 wxMenuItem* ACTION_MENU::appendCopy( const wxMenuItem* aSource )
631 {
632     wxMenuItem* newItem = new wxMenuItem( this, aSource->GetId(), aSource->GetItemLabel(),
633                                           aSource->GetHelp(), aSource->GetKind() );
634 
635     // Add the source bitmap if it is not the wxNullBitmap
636     // On Windows, for Checkable Menu items, adding a bitmap adds also
637     // our predefined checked alternate bitmap
638     // On other OS, wxITEM_CHECK and wxITEM_RADIO Menu items do not use custom bitmaps.
639 #if defined( _WIN32 )
640     // On Windows, AddBitmapToMenuItem() uses the unchecked bitmap for wxITEM_CHECK and
641     // wxITEM_RADIO menuitems and autoamtically adds a checked bitmap.
642     // For other menuitrms, use the "checked" bitmap.
643     bool use_checked_bm = ( aSource->GetKind() == wxITEM_CHECK ||
644                             aSource->GetKind() == wxITEM_RADIO ) ? false : true;
645     const wxBitmap& src_bitmap = aSource->GetBitmap( use_checked_bm );
646 #else
647     const wxBitmap& src_bitmap = aSource->GetBitmap();
648 #endif
649 
650     if( src_bitmap.IsOk() && src_bitmap.GetHeight() > 1 )    // a null bitmap has a 0 size
651         AddBitmapToMenuItem( newItem, src_bitmap );
652 
653     if( aSource->IsSubMenu() )
654     {
655         ACTION_MENU* menu = dynamic_cast<ACTION_MENU*>( aSource->GetSubMenu() );
656         wxASSERT_MSG( menu, "Submenus are expected to be a ACTION_MENU" );
657 
658         if( menu )
659         {
660             ACTION_MENU* menuCopy = menu->Clone();
661             newItem->SetSubMenu( menuCopy );
662             m_submenus.push_back( menuCopy );
663         }
664     }
665 
666     // wxMenuItem has to be added before enabling/disabling or checking
667     Append( newItem );
668 
669     if( aSource->IsCheckable() )
670         newItem->Check( aSource->IsChecked() );
671 
672     newItem->Enable( aSource->IsEnabled() );
673 
674     return newItem;
675 }
676