KiCad PCB EDA Suite
context_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  * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
6  * @author Maciej Suminski <maciej.suminski@cern.ch>
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, you may find one here:
20  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21  * or you may search the http://www.gnu.org website for the version 2 license,
22  * or you may write to the Free Software Foundation, Inc.,
23  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24  */
25 
26 #include <tool/tool_event.h>
27 #include <tool/tool_manager.h>
28 #include <tool/tool_interactive.h>
29 #include <tool/context_menu.h>
30 #include <wx/log.h>
31 #include <pgm_base.h>
32 
33 #include <functional>
34 using namespace std::placeholders;
35 
37  m_titleDisplayed( false ), m_selected( -1 ), m_tool( nullptr ), m_icon( nullptr )
38 {
39  setupEvents();
40 }
41 
42 
44 {
45  // Set parent to NULL to prevent submenus from unregistering from a notexisting object
46  for( auto menu : m_submenus )
47  menu->SetParent( nullptr );
48 
49  CONTEXT_MENU* parent = dynamic_cast<CONTEXT_MENU*>( GetParent() );
50  wxASSERT( parent || !GetParent() );
51 
52  if( parent )
53  parent->m_submenus.remove( this );
54 }
55 
56 /*
57  * Helper function.
58  * Assigns an icon to the wxMenuItem aMenu.
59  * aIcon is the icon to be assigned can be NULL.
60  */
61 static void set_wxMenuIcon( wxMenuItem* aMenu, const BITMAP_OPAQUE* aIcon )
62 {
63  // Retrieve the global applicaton show icon option:
64  bool useImagesInMenus;
65  Pgm().CommonSettings()->Read( USE_ICONS_IN_MENUS_KEY, &useImagesInMenus );
66 
67  if( aIcon && useImagesInMenus )
68  aMenu->SetBitmap( KiBitmap( aIcon ) );
69 }
70 
71 
73 {
74  m_icon = aIcon;
75 }
76 
77 
79 {
80  Connect( wxEVT_MENU_HIGHLIGHT, wxMenuEventHandler( CONTEXT_MENU::onMenuEvent ), NULL, this );
81  Connect( wxEVT_COMMAND_MENU_SELECTED, wxMenuEventHandler( CONTEXT_MENU::onMenuEvent ), NULL, this );
82 }
83 
84 
85 void CONTEXT_MENU::SetTitle( const wxString& aTitle )
86 {
87  // Unfortunately wxMenu::SetTitle() does not work very well, so this is an alternative version
88  m_title = aTitle;
89 
90  // Update the menu title
91  if( m_titleDisplayed )
92  DisplayTitle( true );
93 }
94 
95 
96 void CONTEXT_MENU::DisplayTitle( bool aDisplay )
97 {
98  if( ( !aDisplay || m_title.IsEmpty() ) && m_titleDisplayed )
99  {
100  // Destroy the menu entry keeping the title..
101  wxMenuItem* item = FindItemByPosition( 0 );
102  wxASSERT( item->GetItemLabelText() == GetTitle() );
103  Destroy( item );
104  // ..and separator
105  item = FindItemByPosition( 0 );
106  wxASSERT( item->IsSeparator() );
107  Destroy( item );
108  m_titleDisplayed = false;
109  }
110 
111  else if( aDisplay && !m_title.IsEmpty() )
112  {
113  if( m_titleDisplayed )
114  {
115  // Simply update the title
116  FindItemByPosition( 0 )->SetItemLabel( m_title );
117  }
118  else
119  {
120  // Add a separator and a menu entry to display the title
121  InsertSeparator( 0 );
122  Insert( 0, new wxMenuItem( this, wxID_NONE, m_title, wxEmptyString, wxITEM_NORMAL ) );
123 
124  if( m_icon )
125  set_wxMenuIcon( FindItemByPosition( 0 ), m_icon );
126 
127  m_titleDisplayed = true;
128  }
129  }
130 }
131 
132 
133 wxMenuItem* CONTEXT_MENU::Add( const wxString& aLabel, int aId, const BITMAP_OPAQUE* aIcon )
134 {
135 #ifdef DEBUG
136  if( FindItem( aId ) != NULL )
137  wxLogWarning( wxT( "Adding more than one menu entry with the same ID may result in"
138  "undefined behaviour" ) );
139 #endif
140 
141  wxMenuItem* item = new wxMenuItem( this, aId, aLabel, wxEmptyString, wxITEM_NORMAL );
142  set_wxMenuIcon( item, aIcon );
143 
144  return Append( item );
145 }
146 
147 
148 wxMenuItem* CONTEXT_MENU::Add( const TOOL_ACTION& aAction )
149 {
151  const BITMAP_OPAQUE* icon = aAction.GetIcon();
152 
153  wxMenuItem* item = new wxMenuItem( this, getMenuId( aAction ), aAction.GetMenuItem(),
154  aAction.GetDescription(), wxITEM_NORMAL );
155 
156  set_wxMenuIcon( item, icon );
157 
158  m_toolActions[getMenuId( aAction )] = &aAction;
159 
160  wxMenuItem* i = Append( item );
161  return i;
162 }
163 
164 
165 std::list<wxMenuItem*> CONTEXT_MENU::Add( CONTEXT_MENU* aMenu, bool aExpand )
166 {
167  std::list<wxMenuItem*> items;
168  CONTEXT_MENU* menuCopy = aMenu->Clone();
169  m_submenus.push_back( menuCopy );
170 
171  if( aExpand )
172  {
173  for( int i = 0; i < (int) aMenu->GetMenuItemCount(); ++i )
174  {
175  wxMenuItem* item = aMenu->FindItemByPosition( i );
176  items.push_back( appendCopy( item ) );
177  }
178  }
179  else
180  {
181  wxASSERT_MSG( !menuCopy->m_title.IsEmpty(), "Set a title for CONTEXT_MENU using SetTitle()" );
182 
183  if( aMenu->m_icon )
184  {
185  wxMenuItem* newItem = new wxMenuItem( this, -1, menuCopy->m_title );
186  set_wxMenuIcon( newItem, aMenu->m_icon );
187  newItem->SetSubMenu( menuCopy );
188  items.push_back( Append( newItem ) );
189  }
190  else
191  {
192  items.push_back( AppendSubMenu( menuCopy, menuCopy->m_title ) );
193  }
194  }
195 
196  return items;
197 }
198 
199 
201 {
202  m_titleDisplayed = false;
203 
204  for( int i = GetMenuItemCount() - 1; i >= 0; --i )
205  Destroy( FindItemByPosition( i ) );
206 
207  m_toolActions.clear();
208  m_submenus.clear();
209 
210  wxASSERT( GetMenuItemCount() == 0 );
211 }
212 
213 
215 {
216  bool hasEnabled = false;
217 
218  auto& items = GetMenuItems();
219 
220  for( auto item : items )
221  {
222  if( item->IsEnabled() && !item->IsSeparator() )
223  {
224  hasEnabled = true;
225  break;
226  }
227  }
228 
229  return hasEnabled;
230 }
231 
232 
234 {
235  try
236  {
237  update();
238  }
239  catch( std::exception& e )
240  {
241  wxLogDebug( wxString::Format( "CONTEXT_MENU update handler exception: %s", e.what() ) );
242  }
243 
244  if( m_tool )
245  updateHotKeys();
246 
247  runOnSubmenus( std::bind( &CONTEXT_MENU::UpdateAll, _1 ) );
248 }
249 
250 
252 {
253  m_tool = aTool;
254  runOnSubmenus( std::bind( &CONTEXT_MENU::SetTool, _1, aTool ) );
255 }
256 
257 
259 {
260  CONTEXT_MENU* clone = create();
261  clone->Clear();
262  clone->copyFrom( *this );
263  return clone;
264 }
265 
266 
268 {
269  CONTEXT_MENU* menu = new CONTEXT_MENU();
270 
271  wxASSERT_MSG( typeid( *this ) == typeid( *menu ),
272  wxString::Format( "You need to override create() method for class %s", typeid(*this).name() ) );
273 
274  return menu;
275 }
276 
277 
279 {
280  wxASSERT( m_tool );
281  return m_tool ? m_tool->GetManager() : nullptr;
282 }
283 
284 
286 {
287  TOOL_MANAGER* toolMgr = getToolManager();
288 
289  for( std::map<int, const TOOL_ACTION*>::const_iterator it = m_toolActions.begin();
290  it != m_toolActions.end(); ++it )
291  {
292  int id = it->first;
293  const TOOL_ACTION& action = *it->second;
294  int key = toolMgr->GetHotKey( action ) & ~MD_MODIFIER_MASK;
295 
296  if( key )
297  {
298  int mod = toolMgr->GetHotKey( action ) & MD_MODIFIER_MASK;
299  int flags = 0;
300  wxMenuItem* item = FindChildItem( id );
301 
302  if( item )
303  {
304  flags |= ( mod & MD_ALT ) ? wxACCEL_ALT : 0;
305  flags |= ( mod & MD_CTRL ) ? wxACCEL_CTRL : 0;
306  flags |= ( mod & MD_SHIFT ) ? wxACCEL_SHIFT : 0;
307 
308  if( !flags )
309  flags = wxACCEL_NORMAL;
310 
311  wxAcceleratorEntry accel( flags, key, id, item );
312  item->SetAccel( &accel );
313  }
314  }
315  }
316 }
317 
318 
319 void CONTEXT_MENU::onMenuEvent( wxMenuEvent& aEvent )
320 {
321  OPT_TOOL_EVENT evt;
322 
323  wxEventType type = aEvent.GetEventType();
324 
325  // When the currently chosen item in the menu is changed, an update event is issued.
326  // For example, the selection tool can use this to dynamically highlight the current item
327  // from selection clarification popup.
328  if( type == wxEVT_MENU_HIGHLIGHT )
329  evt = TOOL_EVENT( TC_COMMAND, TA_CONTEXT_MENU_UPDATE, aEvent.GetId() );
330 
331  // One of menu entries was selected..
332  else if( type == wxEVT_COMMAND_MENU_SELECTED )
333  {
334  // Store the selected position, so it can be checked by the tools
335  m_selected = aEvent.GetId();
336 
337  CONTEXT_MENU* parent = dynamic_cast<CONTEXT_MENU*>( GetParent() );
338 
339  while( parent )
340  {
341  parent->m_selected = m_selected;
342  parent = dynamic_cast<CONTEXT_MENU*>( parent->GetParent() );
343  }
344 
345  // Check if there is a TOOL_ACTION for the given ID
346  if( m_selected >= ACTION_ID )
347  evt = findToolAction( m_selected );
348 
349  if( !evt )
350  {
351 #ifdef __WINDOWS__
352  if( !evt )
353  {
354  // Try to find the submenu which holds the selected item
355  wxMenu* menu = nullptr;
356  FindItem( m_selected, &menu );
357 
358  if( menu && menu != this )
359  {
360  CONTEXT_MENU* cxmenu = static_cast<CONTEXT_MENU*>( menu );
361  evt = cxmenu->eventHandler( aEvent );
362  }
363  }
364 #else
365  if( !evt )
366  runEventHandlers( aEvent, evt );
367 #endif
368 
369  // Handling non-action menu entries (e.g. items in clarification list)
370  if( !evt )
371  evt = TOOL_EVENT( TC_COMMAND, TA_CONTEXT_MENU_CHOICE, aEvent.GetId() );
372  }
373  }
374 
375  // forward the action/update event to the TOOL_MANAGER
376  // clients that don't supply a tool will have to check GetSelected() themselves
377  if( evt && m_tool )
378  {
379  //aEvent.StopPropagation();
380  m_tool->GetManager()->ProcessEvent( *evt );
381  }
382 }
383 
384 
385 void CONTEXT_MENU::runEventHandlers( const wxMenuEvent& aMenuEvent, OPT_TOOL_EVENT& aToolEvent )
386 {
387  aToolEvent = eventHandler( aMenuEvent );
388 
389  if( !aToolEvent )
390  runOnSubmenus( std::bind( &CONTEXT_MENU::runEventHandlers, _1, aMenuEvent, aToolEvent ) );
391 }
392 
393 
394 void CONTEXT_MENU::runOnSubmenus( std::function<void(CONTEXT_MENU*)> aFunction )
395 {
396  try
397  {
398  std::for_each( m_submenus.begin(), m_submenus.end(), [&]( CONTEXT_MENU* m ) {
399  aFunction( m );
400  m->runOnSubmenus( aFunction );
401  } );
402  }
403  catch( std::exception& e )
404  {
405  wxLogDebug( wxString::Format( "CONTEXT_MENU runOnSubmenus exception: %s", e.what() ) );
406  }
407 }
408 
409 
411 {
412  OPT_TOOL_EVENT evt;
413 
414  auto findFunc = [&]( CONTEXT_MENU* m ) {
415  if( evt )
416  return;
417 
418  const auto it = m->m_toolActions.find( aId );
419 
420  if( it != m->m_toolActions.end() )
421  evt = it->second->MakeEvent();
422  };
423 
424  findFunc( this );
425 
426  if( !evt )
427  runOnSubmenus( findFunc );
428 
429  return evt;
430 }
431 
432 
434 {
435  m_icon = aMenu.m_icon;
436  m_title = aMenu.m_title;
438  m_selected = -1; // aMenu.m_selected;
439  m_tool = aMenu.m_tool;
441 
442  // Copy all menu entries
443  for( int i = 0; i < (int) aMenu.GetMenuItemCount(); ++i )
444  {
445  wxMenuItem* item = aMenu.FindItemByPosition( i );
446  appendCopy( item );
447  }
448 }
449 
450 
451 wxMenuItem* CONTEXT_MENU::appendCopy( const wxMenuItem* aSource )
452 {
453  wxMenuItem* newItem = new wxMenuItem( this, aSource->GetId(), aSource->GetItemLabel(),
454  aSource->GetHelp(), aSource->GetKind() );
455 
456  bool useImagesInMenus;
457  Pgm().CommonSettings()->Read( USE_ICONS_IN_MENUS_KEY, &useImagesInMenus );
458 
459  if( aSource->GetKind() == wxITEM_NORMAL && useImagesInMenus )
460  newItem->SetBitmap( aSource->GetBitmap() );
461 
462  if( aSource->IsSubMenu() )
463  {
464  CONTEXT_MENU* menu = dynamic_cast<CONTEXT_MENU*>( aSource->GetSubMenu() );
465  wxASSERT_MSG( menu, "Submenus are expected to be a CONTEXT_MENU" );
466 
467  if( menu )
468  {
469  CONTEXT_MENU* menuCopy = menu->Clone();
470  newItem->SetSubMenu( menuCopy );
471  m_submenus.push_back( menuCopy );
472  }
473  }
474 
475  // wxMenuItem has to be added before enabling/disabling or checking
476  Append( newItem );
477 
478  if( aSource->IsCheckable() )
479  newItem->Check( aSource->IsChecked() );
480 
481  newItem->Enable( aSource->IsEnabled() );
482 
483  return newItem;
484 }
TOOL_INTERACTIVE * m_tool
Creator of the menu
Definition: context_menu.h:219
void updateHotKeys()
Updates hot key settings for TOOL_ACTIONs in this menu.
void runEventHandlers(const wxMenuEvent &aMenuEvent, OPT_TOOL_EVENT &aToolEvent)
Traverses the submenus tree looking for a submenu capable of handling a particular menu event...
int GetHotKey(const TOOL_ACTION &aAction)
>
PNG memory record (file in memory).
Definition: bitmap_types.h:41
int m_selected
Stores the id number of selected item.
Definition: context_menu.h:216
OPT_TOOL_EVENT findToolAction(int aId)
Checks if any of submenus contains a TOOL_ACTION with a specific ID.
Class CONTEXT_MENU.
Definition: context_menu.h:44
const wxString & GetDescription() const
Definition: tool_action.h:124
virtual void update()
Update menu state stub.
Definition: context_menu.h:164
const BITMAP_OPAQUE * m_icon
Optional icon
Definition: context_menu.h:231
virtual OPT_TOOL_EVENT eventHandler(const wxMenuEvent &)
Event handler stub.
Definition: context_menu.h:172
PGM_BASE & Pgm()
The global Program "get" accessor.
Definition: kicad.cpp:66
static const int ACTION_ID
Menu items with ID higher than that are considered TOOL_ACTIONs
Definition: context_menu.h:222
void runOnSubmenus(std::function< void(CONTEXT_MENU *)> aFunction)
Runs a function on the menu and all its submenus.
void copyFrom(const CONTEXT_MENU &aMenu)
Copies another menus data to this instance.
wxMenuItem * Add(const wxString &aLabel, int aId, const BITMAP_OPAQUE *aIcon=NULL)
Function Add() Adds an entry to the menu.
Class TOOL_MANAGER.
Definition: tool_manager.h:49
wxBitmap KiBitmap(BITMAP_DEF aBitmap)
Function KiBitmap constructs a wxBitmap from a memory record, held in a BITMAP_DEF.
Definition: bitmap.cpp:78
CONTEXT_MENU * Clone() const
Creates a deep, recursive copy of this CONTEXT_MENU.
void setupEvents()
Initializes handlers for events.
bool ProcessEvent(const TOOL_EVENT &aEvent)
Propagates an event to tools that requested events of matching type(s).
std::map< int, const TOOL_ACTION * > m_toolActions
Associates tool actions with menu item IDs. Non-owning.
Definition: context_menu.h:225
Class TOOL_EVENT.
Definition: tool_event.h:162
void SetTool(TOOL_INTERACTIVE *aTool)
Function SetTool() Sets a tool that is the creator of the menu.
CONTEXT_MENU()
Default constructor
void Clear()
Function Clear() Removes all the entries from the menu (as well as its title).
const wxString & GetMenuItem() const
Definition: tool_action.h:114
bool HasEnabledItems() const
Function HasEnabledItems();.
static int getMenuId(const TOOL_ACTION &aAction)
Returns the corresponding wxMenuItem identifier for a TOOL_ACTION object.
Definition: context_menu.h:155
void SetIcon(const BITMAP_OPAQUE *aIcon)
Function SetIcon() Assigns an icon for the entry.
TOOL_MANAGER * getToolManager() const
Returns an instance of TOOL_MANAGER class.
virtual CONTEXT_MENU * create() const
Returns an instance of this class. It has to be overridden in inheriting classes. ...
void onMenuEvent(wxMenuEvent &aEvent)
The default menu event handler.
see class PGM_BASE
void UpdateAll()
Function UpdateAll() Runs update handlers for the menu and its submenus.
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
void SetTitle(const wxString &aTitle) override
Function SetTitle() Sets title for the context menu.
VTBL_ENTRY wxConfigBase * CommonSettings() const
Definition: pgm_base.h:162
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:43
std::list< CONTEXT_MENU * > m_submenus
List of submenus.
Definition: context_menu.h:228
TOOL_MANAGER * GetManager() const
Function GetManager() Returns the instance of TOOL_MANAGER that takes care of the tool...
Definition: tool_base.h:144
virtual ~CONTEXT_MENU()
const BITMAP_OPAQUE * GetIcon() const
Returns an icon associated with the action.
Definition: tool_action.h:165
void DisplayTitle(bool aDisplay=true)
Function DisplayTitle() Decides whether a title for a pop up menu should be displayed.
wxMenuItem * appendCopy(const wxMenuItem *aSource)
Function appendCopy Appends a copy of wxMenuItem.
wxString m_title
Menu title
Definition: context_menu.h:213
#define mod(a, n)
Definition: greymap.cpp:24
static void set_wxMenuIcon(wxMenuItem *aMenu, const BITMAP_OPAQUE *aIcon)
bool m_titleDisplayed
Flag indicating that the menu title was set up.
Definition: context_menu.h:210
OPT< TOOL_EVENT > OPT_TOOL_EVENT
Definition: tool_event.h:465