KiCad PCB EDA Suite
action_menu.cpp
Go to the documentation of this file.
1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2013-2017 CERN
5  * Copyright (C) 2013-2019 KiCad Developers, see CHANGELOG.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 <functional>
28 #include <tool/actions.h>
29 #include <tool/tool_event.h>
30 #include <tool/tool_manager.h>
31 #include <tool/tool_interactive.h>
32 #include <tool/action_menu.h>
33 #include <wx/log.h>
34 #include <pgm_base.h>
35 
36 
37 using namespace std::placeholders;
38 
39 
41  m_Dirty( true ),
42  m_titleDisplayed( false ),
43  m_selected( -1 ),
44  m_tool( nullptr ),
45  m_icon( nullptr )
46 {
47  setupEvents();
48 }
49 
50 
52 {
53  // Set parent to NULL to prevent submenus from unregistering from a notexisting object
54  for( auto menu : m_submenus )
55  menu->SetParent( nullptr );
56 
57  ACTION_MENU* parent = dynamic_cast<ACTION_MENU*>( GetParent() );
58 
59  if( parent )
60  parent->m_submenus.remove( this );
61 }
62 
63 /*
64  * Helper function.
65  * Assigns an icon to the wxMenuItem aMenu.
66  * aIcon is the icon to be assigned can be NULL.
67  */
68 static void set_wxMenuIcon( wxMenuItem* aMenu, const BITMAP_OPAQUE* aIcon )
69 {
70  if( !Pgm().CommonSettings() )
71  return;
72 
73  // Retrieve the global applicaton show icon option:
74  bool useImagesInMenus;
75  Pgm().CommonSettings()->Read( USE_ICONS_IN_MENUS_KEY, &useImagesInMenus );
76 
77  if( aIcon && useImagesInMenus )
78  aMenu->SetBitmap( KiBitmap( aIcon ) );
79 }
80 
81 
82 void ACTION_MENU::SetIcon( const BITMAP_OPAQUE* aIcon )
83 {
84  m_icon = aIcon;
85 }
86 
87 
89 {
90  Connect( wxEVT_MENU_OPEN, wxMenuEventHandler( ACTION_MENU::onMenuEvent ), NULL, this );
91  Connect( wxEVT_MENU_HIGHLIGHT, wxMenuEventHandler( ACTION_MENU::onMenuEvent ), NULL, this );
92  Connect( wxEVT_COMMAND_MENU_SELECTED, wxMenuEventHandler( ACTION_MENU::onMenuEvent ), NULL, this );
93 }
94 
95 
96 void ACTION_MENU::SetTitle( const wxString& aTitle )
97 {
98  // Unfortunately wxMenu::SetTitle() does not work very well, so this is an alternative version
99  m_title = aTitle;
100 
101  // Update the menu title
102  if( m_titleDisplayed )
103  DisplayTitle( true );
104 }
105 
106 
107 void ACTION_MENU::DisplayTitle( bool aDisplay )
108 {
109  if( ( !aDisplay || m_title.IsEmpty() ) && m_titleDisplayed )
110  {
111  // Destroy the menu entry keeping the title..
112  wxMenuItem* item = FindItemByPosition( 0 );
113  wxASSERT( item->GetItemLabelText() == GetTitle() );
114  Destroy( item );
115  // ..and separator
116  item = FindItemByPosition( 0 );
117  wxASSERT( item->IsSeparator() );
118  Destroy( item );
119  m_titleDisplayed = false;
120  }
121 
122  else if( aDisplay && !m_title.IsEmpty() )
123  {
124  if( m_titleDisplayed )
125  {
126  // Simply update the title
127  FindItemByPosition( 0 )->SetItemLabel( m_title );
128  }
129  else
130  {
131  // Add a separator and a menu entry to display the title
132  InsertSeparator( 0 );
133  Insert( 0, new wxMenuItem( this, wxID_NONE, m_title, wxEmptyString, wxITEM_NORMAL ) );
134 
135  if( m_icon )
136  set_wxMenuIcon( FindItemByPosition( 0 ), m_icon );
137 
138  m_titleDisplayed = true;
139  }
140  }
141 }
142 
143 
144 wxMenuItem* ACTION_MENU::Add( const wxString& aLabel, int aId, const BITMAP_OPAQUE* aIcon )
145 {
146 #ifdef DEBUG
147  if( FindItem( aId ) != NULL )
148  wxLogWarning( wxT( "Adding more than one menu entry with the same ID may result in"
149  "undefined behaviour" ) );
150 #endif
151 
152  wxMenuItem* item = new wxMenuItem( this, aId, aLabel, wxEmptyString, wxITEM_NORMAL );
153  set_wxMenuIcon( item, aIcon );
154 
155  return Append( item );
156 }
157 
158 
159 wxMenuItem* ACTION_MENU::Add( const TOOL_ACTION& aAction, bool aIsCheckmarkEntry )
160 {
162  const BITMAP_OPAQUE* icon = aAction.GetIcon();
163 
164  wxMenuItem* item = new wxMenuItem( this, getMenuId( aAction ), aAction.GetMenuItem(),
165  aAction.GetDescription(),
166  aIsCheckmarkEntry ? wxITEM_CHECK : wxITEM_NORMAL );
167 
168  set_wxMenuIcon( item, icon );
169 
170  m_toolActions[getMenuId( aAction )] = &aAction;
171 
172  wxMenuItem* i = Append( item );
173  return i;
174 }
175 
176 
177 wxMenuItem* ACTION_MENU::Add( ACTION_MENU* aMenu )
178 {
179  ACTION_MENU* menuCopy = aMenu->Clone();
180  m_submenus.push_back( menuCopy );
181 
182  wxASSERT_MSG( !menuCopy->m_title.IsEmpty(), "Set a title for ACTION_MENU using SetTitle()" );
183 
184  if( aMenu->m_icon )
185  {
186  wxMenuItem* newItem = new wxMenuItem( this, -1, menuCopy->m_title );
187  set_wxMenuIcon( newItem, aMenu->m_icon );
188  newItem->SetSubMenu( menuCopy );
189  return Append( newItem );
190  }
191  else
192  {
193  return AppendSubMenu( menuCopy, menuCopy->m_title );
194  }
195 }
196 
197 
199 {
200  m_titleDisplayed = false;
201 
202  for( int i = GetMenuItemCount() - 1; i >= 0; --i )
203  Destroy( FindItemByPosition( i ) );
204 
205  m_toolActions.clear();
206  m_submenus.clear();
207 
208  wxASSERT( GetMenuItemCount() == 0 );
209 }
210 
211 
213 {
214  bool hasEnabled = false;
215 
216  auto& items = GetMenuItems();
217 
218  for( auto item : items )
219  {
220  if( item->IsEnabled() && !item->IsSeparator() )
221  {
222  hasEnabled = true;
223  break;
224  }
225  }
226 
227  return hasEnabled;
228 }
229 
230 
232 {
233  try
234  {
235  update();
236  }
237  catch( std::exception& e )
238  {
239  wxLogDebug( wxString::Format( "ACTION_MENU update handler exception: %s", e.what() ) );
240  }
241 
242  if( m_tool )
243  updateHotKeys();
244 
245  runOnSubmenus( std::bind( &ACTION_MENU::UpdateAll, _1 ) );
246 }
247 
248 
250 {
251  m_tool = aTool;
252  runOnSubmenus( std::bind( &ACTION_MENU::SetTool, _1, aTool ) );
253 }
254 
255 
257 {
258  ACTION_MENU* clone = create();
259  clone->Clear();
260  clone->copyFrom( *this );
261  return clone;
262 }
263 
264 
266 {
267  ACTION_MENU* menu = new ACTION_MENU();
268 
269  wxASSERT_MSG( typeid( *this ) == typeid( *menu ),
270  wxString::Format( "You need to override create() method for class %s", typeid(*this).name() ) );
271 
272  return menu;
273 }
274 
275 
277 {
278  wxASSERT( m_tool );
279  return m_tool ? m_tool->GetManager() : nullptr;
280 }
281 
282 
284 {
285  TOOL_MANAGER* toolMgr = getToolManager();
286 
287  for( std::map<int, const TOOL_ACTION*>::const_iterator it = m_toolActions.begin();
288  it != m_toolActions.end(); ++it )
289  {
290  int id = it->first;
291  const TOOL_ACTION& action = *it->second;
292  int key = toolMgr->GetHotKey( action ) & ~MD_MODIFIER_MASK;
293 
294  if( key )
295  {
296  int mod = toolMgr->GetHotKey( action ) & MD_MODIFIER_MASK;
297  int flags = 0;
298  wxMenuItem* item = FindChildItem( id );
299 
300  if( item )
301  {
302  flags |= ( mod & MD_ALT ) ? wxACCEL_ALT : 0;
303  flags |= ( mod & MD_CTRL ) ? wxACCEL_CTRL : 0;
304  flags |= ( mod & MD_SHIFT ) ? wxACCEL_SHIFT : 0;
305 
306  if( !flags )
307  flags = wxACCEL_NORMAL;
308 
309  wxAcceleratorEntry accel( flags, key, id, item );
310  item->SetAccel( &accel );
311  }
312  }
313  }
314 }
315 
316 
317 void ACTION_MENU::onMenuEvent( wxMenuEvent& aEvent )
318 {
319  OPT_TOOL_EVENT evt;
320  wxString menuText;
321 
322  wxEventType type = aEvent.GetEventType();
323 
324  if( type == wxEVT_MENU_OPEN && m_Dirty )
325  {
326  getToolManager()->RunAction( ACTIONS::updateMenu, true, this );
327  aEvent.Skip();
328  return;
329  }
330 
331  // When the currently chosen item in the menu is changed, an update event is issued.
332  // For example, the selection tool can use this to dynamically highlight the current item
333  // from selection clarification popup.
334  else if( type == wxEVT_MENU_HIGHLIGHT )
335  evt = TOOL_EVENT( TC_COMMAND, TA_CONTEXT_MENU_UPDATE, aEvent.GetId() );
336 
337  // One of menu entries was selected..
338  else if( type == wxEVT_COMMAND_MENU_SELECTED )
339  {
340  // Store the selected position, so it can be checked by the tools
341  m_selected = aEvent.GetId();
342 
343  ACTION_MENU* parent = dynamic_cast<ACTION_MENU*>( GetParent() );
344 
345  while( parent )
346  {
347  parent->m_selected = m_selected;
348  parent = dynamic_cast<ACTION_MENU*>( parent->GetParent() );
349  }
350 
351  // Check if there is a TOOL_ACTION for the given ID
352  if( m_selected >= ACTION_ID )
353  evt = findToolAction( m_selected );
354 
355  if( !evt )
356  {
357 #ifdef __WINDOWS__
358  if( !evt )
359  {
360  // Try to find the submenu which holds the selected item
361  wxMenu* menu = nullptr;
362  FindItem( m_selected, &menu );
363 
364  // This conditional compilation is probably not needed.
365  // It will be removed later, for the Kicad V 6.x version.
366  // But in "old" 3.0 version, the "&& menu != this" contition was added to avoid hang
367  // This hang is no longer encountered in wxWidgets 3.0.4 version, and this condition is no longer needed.
368  // And in 3.1.2, we have to remove it, as "menu != this" never happens
369  // ("menu != this" always happens in 3.1.1 and older!).
370  #if wxCHECK_VERSION(3, 1, 2)
371  if( menu )
372  #else
373  if( menu && menu != this )
374  #endif
375  {
376  ACTION_MENU* cxmenu = static_cast<ACTION_MENU*>( menu );
377  evt = cxmenu->eventHandler( aEvent );
378  }
379  }
380 #else
381  if( !evt )
382  runEventHandlers( aEvent, evt );
383 #endif
384 
385  // Handling non-action menu entries (e.g. items in clarification list)
386  if( !evt && m_selected < wxID_LOWEST )
387  {
388  menuText = GetLabelText( aEvent.GetId() );
390  &menuText );
391  }
392  }
393  }
394 
395  // forward the action/update event to the TOOL_MANAGER
396  // clients that don't supply a tool will have to check GetSelected() themselves
397  if( evt && m_tool )
398  {
399  //aEvent.StopPropagation();
400  m_tool->GetManager()->ProcessEvent( *evt );
401  }
402  else
403  {
404  aEvent.Skip();
405  }
406 }
407 
408 
409 void ACTION_MENU::runEventHandlers( const wxMenuEvent& aMenuEvent, OPT_TOOL_EVENT& aToolEvent )
410 {
411  aToolEvent = eventHandler( aMenuEvent );
412 
413  if( !aToolEvent )
414  runOnSubmenus( std::bind( &ACTION_MENU::runEventHandlers, _1, aMenuEvent, aToolEvent ) );
415 }
416 
417 
418 void ACTION_MENU::runOnSubmenus( std::function<void(ACTION_MENU*)> aFunction )
419 {
420  try
421  {
422  std::for_each( m_submenus.begin(), m_submenus.end(), [&]( ACTION_MENU* m ) {
423  aFunction( m );
424  m->runOnSubmenus( aFunction );
425  } );
426  }
427  catch( std::exception& e )
428  {
429  wxLogDebug( wxString::Format( "ACTION_MENU runOnSubmenus exception: %s", e.what() ) );
430  }
431 }
432 
433 
435 {
436  OPT_TOOL_EVENT evt;
437 
438  auto findFunc = [&]( ACTION_MENU* m ) {
439  if( evt )
440  return;
441 
442  const auto it = m->m_toolActions.find( aId );
443 
444  if( it != m->m_toolActions.end() )
445  evt = it->second->MakeEvent();
446  };
447 
448  findFunc( this );
449 
450  if( !evt )
451  runOnSubmenus( findFunc );
452 
453  return evt;
454 }
455 
456 
457 void ACTION_MENU::copyFrom( const ACTION_MENU& aMenu )
458 {
459  m_icon = aMenu.m_icon;
460  m_title = aMenu.m_title;
462  m_selected = -1; // aMenu.m_selected;
463  m_tool = aMenu.m_tool;
465 
466  // Copy all menu entries
467  for( int i = 0; i < (int) aMenu.GetMenuItemCount(); ++i )
468  {
469  wxMenuItem* item = aMenu.FindItemByPosition( i );
470  appendCopy( item );
471  }
472 }
473 
474 
475 wxMenuItem* ACTION_MENU::appendCopy( const wxMenuItem* aSource )
476 {
477  wxMenuItem* newItem = new wxMenuItem( this, aSource->GetId(), aSource->GetItemLabel(),
478  aSource->GetHelp(), aSource->GetKind() );
479 
480  bool useImagesInMenus;
481  Pgm().CommonSettings()->Read( USE_ICONS_IN_MENUS_KEY, &useImagesInMenus );
482 
483  if( aSource->GetKind() == wxITEM_NORMAL && useImagesInMenus )
484  newItem->SetBitmap( aSource->GetBitmap() );
485 
486  if( aSource->IsSubMenu() )
487  {
488  ACTION_MENU* menu = dynamic_cast<ACTION_MENU*>( aSource->GetSubMenu() );
489  wxASSERT_MSG( menu, "Submenus are expected to be a ACTION_MENU" );
490 
491  if( menu )
492  {
493  ACTION_MENU* menuCopy = menu->Clone();
494  newItem->SetSubMenu( menuCopy );
495  m_submenus.push_back( menuCopy );
496  }
497  }
498 
499  // wxMenuItem has to be added before enabling/disabling or checking
500  Append( newItem );
501 
502  if( aSource->IsCheckable() )
503  newItem->Check( aSource->IsChecked() );
504 
505  newItem->Enable( aSource->IsEnabled() );
506 
507  return newItem;
508 }
virtual void update()
Update menu state stub.
Definition: action_menu.h:165
bool m_titleDisplayed
Flag indicating that the menu title was set up.
Definition: action_menu.h:211
OPT_TOOL_EVENT findToolAction(int aId)
Checks if any of submenus contains a TOOL_ACTION with a specific ID.
int GetHotKey(const TOOL_ACTION &aAction)
static int getMenuId(const TOOL_ACTION &aAction)
Returns the corresponding wxMenuItem identifier for a TOOL_ACTION object.
Definition: action_menu.h:156
Class ACTION_MENU.
Definition: action_menu.h:43
const BITMAP_OPAQUE * m_icon
Optional icon
Definition: action_menu.h:232
const wxString & GetDescription() const
Definition: tool_action.h:115
bool RunAction(const std::string &aActionName, bool aNow=false, T aParam=NULL)
Function RunAction() Runs the specified action.
Definition: tool_manager.h:125
PGM_BASE & Pgm()
The global Program "get" accessor.
Definition: kicad.cpp:66
void runEventHandlers(const wxMenuEvent &aMenuEvent, OPT_TOOL_EVENT &aToolEvent)
Traverses the submenus tree looking for a submenu capable of handling a particular menu event.
static void set_wxMenuIcon(wxMenuItem *aMenu, const BITMAP_OPAQUE *aIcon)
Definition: action_menu.cpp:68
void UpdateAll()
Function UpdateAll() Runs update handlers for the menu and its submenus.
ACTION_MENU()
Default constructor
Definition: action_menu.cpp:40
virtual ACTION_MENU * create() const
Returns an instance of this class. It has to be overridden in inheriting classes.
const BITMAP_OPAQUE * GetIcon() const
Returns an icon associated with the action.
Definition: tool_action.h:156
void SetTool(TOOL_INTERACTIVE *aTool)
Function SetTool() Sets a tool that is the creator of the menu.
TOOL_MANAGER * GetManager() const
Function GetManager() Returns the instance of TOOL_MANAGER that takes care of the tool.
Definition: tool_base.h:144
virtual OPT_TOOL_EVENT eventHandler(const wxMenuEvent &)
Event handler stub.
Definition: action_menu.h:173
Class TOOL_MANAGER.
Definition: tool_manager.h:49
wxBitmap KiBitmap(BITMAP_DEF aBitmap)
Construct a wxBitmap from a memory record, held in a BITMAP_DEF.
Definition: bitmap.cpp:79
void copyFrom(const ACTION_MENU &aMenu)
Copies another menus data to this instance.
virtual ~ACTION_MENU()
Definition: action_menu.cpp:51
bool ProcessEvent(const TOOL_EVENT &aEvent)
Propagates an event to tools that requested events of matching type(s).
wxString m_title
Menu title
Definition: action_menu.h:214
std::list< ACTION_MENU * > m_submenus
List of submenus.
Definition: action_menu.h:229
void SetIcon(const BITMAP_OPAQUE *aIcon)
Function SetIcon() Assigns an icon for the entry.
Definition: action_menu.cpp:82
Class TOOL_EVENT.
Definition: tool_event.h:167
int m_selected
Stores the id number of selected item.
Definition: action_menu.h:217
ACTION_MENU * Clone() const
Creates a deep, recursive copy of this ACTION_MENU.
static const int ACTION_ID
Menu items with ID higher than that are considered TOOL_ACTIONs
Definition: action_menu.h:223
static TOOL_ACTION updateMenu
Definition: actions.h:49
All active tools
Definition: tool_event.h:143
VTBL_ENTRY wxConfigBase * CommonSettings() const
Definition: pgm_base.h:190
TOOL_INTERACTIVE * m_tool
Creator of the menu
Definition: action_menu.h:220
wxMenuItem * appendCopy(const wxMenuItem *aSource)
Function appendCopy Appends a copy of wxMenuItem.
void runOnSubmenus(std::function< void(ACTION_MENU *)> aFunction)
Runs a function on the menu and all its submenus.
std::map< int, const TOOL_ACTION * > m_toolActions
Associates tool actions with menu item IDs. Non-owning.
Definition: action_menu.h:226
bool m_Dirty
Menu requires updating before display.
Definition: action_menu.h:146
void SetTitle(const wxString &aTitle) override
Function SetTitle() Sets title for the menu.
Definition: action_menu.cpp:96
see class PGM_BASE
const wxString & GetMenuItem() const
Definition: tool_action.h:105
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, CPTREE &aTree)
Function Format outputs a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:205
TOOL_MANAGER * getToolManager() const
Returns an instance of TOOL_MANAGER class.
Class TOOL_ACTION.
Definition: tool_action.h:46
size_t i
Definition: json11.cpp:597
#define USE_ICONS_IN_MENUS_KEY
Definition: pgm_base.h:45
void setupEvents()
Initializes handlers for events.
Definition: action_menu.cpp:88
void Clear()
Function Clear() Removes all the entries from the menu (as well as its title).
bool HasEnabledItems() const
Function HasEnabledItems();.
void onMenuEvent(wxMenuEvent &aEvent)
The default menu event handler.
wxMenuItem * Add(const wxString &aLabel, int aId, const BITMAP_OPAQUE *aIcon=NULL)
Function Add() Adds an entry to the menu.
void updateHotKeys()
Updates hot key settings for TOOL_ACTIONs in this menu.
#define mod(a, n)
Definition: greymap.cpp:24
OPT< TOOL_EVENT > OPT_TOOL_EVENT
Definition: tool_event.h:486
void DisplayTitle(bool aDisplay=true)
Function DisplayTitle() Decides whether a title for a pop up menu should be displayed.