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  if( !Pgm().CommonSettings() )
64  return;
65 
66  // Retrieve the global applicaton show icon option:
67  bool useImagesInMenus;
68  Pgm().CommonSettings()->Read( USE_ICONS_IN_MENUS_KEY, &useImagesInMenus );
69 
70  if( aIcon && useImagesInMenus )
71  aMenu->SetBitmap( KiBitmap( aIcon ) );
72 }
73 
74 
76 {
77  m_icon = aIcon;
78 }
79 
80 
82 {
83  Connect( wxEVT_MENU_HIGHLIGHT, wxMenuEventHandler( CONTEXT_MENU::onMenuEvent ), NULL, this );
84  Connect( wxEVT_COMMAND_MENU_SELECTED, wxMenuEventHandler( CONTEXT_MENU::onMenuEvent ), NULL, this );
85 }
86 
87 
88 void CONTEXT_MENU::SetTitle( const wxString& aTitle )
89 {
90  // Unfortunately wxMenu::SetTitle() does not work very well, so this is an alternative version
91  m_title = aTitle;
92 
93  // Update the menu title
94  if( m_titleDisplayed )
95  DisplayTitle( true );
96 }
97 
98 
99 void CONTEXT_MENU::DisplayTitle( bool aDisplay )
100 {
101  if( ( !aDisplay || m_title.IsEmpty() ) && m_titleDisplayed )
102  {
103  // Destroy the menu entry keeping the title..
104  wxMenuItem* item = FindItemByPosition( 0 );
105  wxASSERT( item->GetItemLabelText() == GetTitle() );
106  Destroy( item );
107  // ..and separator
108  item = FindItemByPosition( 0 );
109  wxASSERT( item->IsSeparator() );
110  Destroy( item );
111  m_titleDisplayed = false;
112  }
113 
114  else if( aDisplay && !m_title.IsEmpty() )
115  {
116  if( m_titleDisplayed )
117  {
118  // Simply update the title
119  FindItemByPosition( 0 )->SetItemLabel( m_title );
120  }
121  else
122  {
123  // Add a separator and a menu entry to display the title
124  InsertSeparator( 0 );
125  Insert( 0, new wxMenuItem( this, wxID_NONE, m_title, wxEmptyString, wxITEM_NORMAL ) );
126 
127  if( m_icon )
128  set_wxMenuIcon( FindItemByPosition( 0 ), m_icon );
129 
130  m_titleDisplayed = true;
131  }
132  }
133 }
134 
135 
136 wxMenuItem* CONTEXT_MENU::Add( const wxString& aLabel, int aId, const BITMAP_OPAQUE* aIcon )
137 {
138 #ifdef DEBUG
139  if( FindItem( aId ) != NULL )
140  wxLogWarning( wxT( "Adding more than one menu entry with the same ID may result in"
141  "undefined behaviour" ) );
142 #endif
143 
144  wxMenuItem* item = new wxMenuItem( this, aId, aLabel, wxEmptyString, wxITEM_NORMAL );
145  set_wxMenuIcon( item, aIcon );
146 
147  return Append( item );
148 }
149 
150 
151 wxMenuItem* CONTEXT_MENU::Add( const TOOL_ACTION& aAction )
152 {
154  const BITMAP_OPAQUE* icon = aAction.GetIcon();
155 
156  wxMenuItem* item = new wxMenuItem( this, getMenuId( aAction ), aAction.GetMenuItem(),
157  aAction.GetDescription(), wxITEM_NORMAL );
158 
159  set_wxMenuIcon( item, icon );
160 
161  m_toolActions[getMenuId( aAction )] = &aAction;
162 
163  wxMenuItem* i = Append( item );
164  return i;
165 }
166 
167 
168 std::list<wxMenuItem*> CONTEXT_MENU::Add( CONTEXT_MENU* aMenu, bool aExpand )
169 {
170  std::list<wxMenuItem*> items;
171  CONTEXT_MENU* menuCopy = aMenu->Clone();
172  m_submenus.push_back( menuCopy );
173 
174  if( aExpand )
175  {
176  for( int i = 0; i < (int) aMenu->GetMenuItemCount(); ++i )
177  {
178  wxMenuItem* item = aMenu->FindItemByPosition( i );
179  items.push_back( appendCopy( item ) );
180  }
181  }
182  else
183  {
184  wxASSERT_MSG( !menuCopy->m_title.IsEmpty(), "Set a title for CONTEXT_MENU using SetTitle()" );
185 
186  if( aMenu->m_icon )
187  {
188  wxMenuItem* newItem = new wxMenuItem( this, -1, menuCopy->m_title );
189  set_wxMenuIcon( newItem, aMenu->m_icon );
190  newItem->SetSubMenu( menuCopy );
191  items.push_back( Append( newItem ) );
192  }
193  else
194  {
195  items.push_back( AppendSubMenu( menuCopy, menuCopy->m_title ) );
196  }
197  }
198 
199  return items;
200 }
201 
202 
204 {
205  m_titleDisplayed = false;
206 
207  for( int i = GetMenuItemCount() - 1; i >= 0; --i )
208  Destroy( FindItemByPosition( i ) );
209 
210  m_toolActions.clear();
211  m_submenus.clear();
212 
213  wxASSERT( GetMenuItemCount() == 0 );
214 }
215 
216 
218 {
219  bool hasEnabled = false;
220 
221  auto& items = GetMenuItems();
222 
223  for( auto item : items )
224  {
225  if( item->IsEnabled() && !item->IsSeparator() )
226  {
227  hasEnabled = true;
228  break;
229  }
230  }
231 
232  return hasEnabled;
233 }
234 
235 
237 {
238  try
239  {
240  update();
241  }
242  catch( std::exception& e )
243  {
244  wxLogDebug( wxString::Format( "CONTEXT_MENU update handler exception: %s", e.what() ) );
245  }
246 
247  if( m_tool )
248  updateHotKeys();
249 
250  runOnSubmenus( std::bind( &CONTEXT_MENU::UpdateAll, _1 ) );
251 }
252 
253 
255 {
256  m_tool = aTool;
257  runOnSubmenus( std::bind( &CONTEXT_MENU::SetTool, _1, aTool ) );
258 }
259 
260 
262 {
263  CONTEXT_MENU* clone = create();
264  clone->Clear();
265  clone->copyFrom( *this );
266  return clone;
267 }
268 
269 
271 {
272  CONTEXT_MENU* menu = new CONTEXT_MENU();
273 
274  wxASSERT_MSG( typeid( *this ) == typeid( *menu ),
275  wxString::Format( "You need to override create() method for class %s", typeid(*this).name() ) );
276 
277  return menu;
278 }
279 
280 
282 {
283  wxASSERT( m_tool );
284  return m_tool ? m_tool->GetManager() : nullptr;
285 }
286 
287 
289 {
290  TOOL_MANAGER* toolMgr = getToolManager();
291 
292  for( std::map<int, const TOOL_ACTION*>::const_iterator it = m_toolActions.begin();
293  it != m_toolActions.end(); ++it )
294  {
295  int id = it->first;
296  const TOOL_ACTION& action = *it->second;
297  int key = toolMgr->GetHotKey( action ) & ~MD_MODIFIER_MASK;
298 
299  if( key )
300  {
301  int mod = toolMgr->GetHotKey( action ) & MD_MODIFIER_MASK;
302  int flags = 0;
303  wxMenuItem* item = FindChildItem( id );
304 
305  if( item )
306  {
307  flags |= ( mod & MD_ALT ) ? wxACCEL_ALT : 0;
308  flags |= ( mod & MD_CTRL ) ? wxACCEL_CTRL : 0;
309  flags |= ( mod & MD_SHIFT ) ? wxACCEL_SHIFT : 0;
310 
311  if( !flags )
312  flags = wxACCEL_NORMAL;
313 
314  wxAcceleratorEntry accel( flags, key, id, item );
315  item->SetAccel( &accel );
316  }
317  }
318  }
319 }
320 
321 
322 void CONTEXT_MENU::onMenuEvent( wxMenuEvent& aEvent )
323 {
324  OPT_TOOL_EVENT evt;
325 
326  wxEventType type = aEvent.GetEventType();
327 
328  // When the currently chosen item in the menu is changed, an update event is issued.
329  // For example, the selection tool can use this to dynamically highlight the current item
330  // from selection clarification popup.
331  if( type == wxEVT_MENU_HIGHLIGHT )
332  evt = TOOL_EVENT( TC_COMMAND, TA_CONTEXT_MENU_UPDATE, aEvent.GetId() );
333 
334  // One of menu entries was selected..
335  else if( type == wxEVT_COMMAND_MENU_SELECTED )
336  {
337  // Store the selected position, so it can be checked by the tools
338  m_selected = aEvent.GetId();
339 
340  CONTEXT_MENU* parent = dynamic_cast<CONTEXT_MENU*>( GetParent() );
341 
342  while( parent )
343  {
344  parent->m_selected = m_selected;
345  parent = dynamic_cast<CONTEXT_MENU*>( parent->GetParent() );
346  }
347 
348  // Check if there is a TOOL_ACTION for the given ID
349  if( m_selected >= ACTION_ID )
350  evt = findToolAction( m_selected );
351 
352  if( !evt )
353  {
354 #ifdef __WINDOWS__
355  if( !evt )
356  {
357  // Try to find the submenu which holds the selected item
358  wxMenu* menu = nullptr;
359  FindItem( m_selected, &menu );
360 
361  if( menu && menu != this )
362  {
363  CONTEXT_MENU* cxmenu = static_cast<CONTEXT_MENU*>( menu );
364  evt = cxmenu->eventHandler( aEvent );
365  }
366  }
367 #else
368  if( !evt )
369  runEventHandlers( aEvent, evt );
370 #endif
371 
372  // Handling non-action menu entries (e.g. items in clarification list)
373  if( !evt )
374  evt = TOOL_EVENT( TC_COMMAND, TA_CONTEXT_MENU_CHOICE, aEvent.GetId() );
375  }
376  }
377 
378  // forward the action/update event to the TOOL_MANAGER
379  // clients that don't supply a tool will have to check GetSelected() themselves
380  if( evt && m_tool )
381  {
382  //aEvent.StopPropagation();
383  m_tool->GetManager()->ProcessEvent( *evt );
384  }
385 }
386 
387 
388 void CONTEXT_MENU::runEventHandlers( const wxMenuEvent& aMenuEvent, OPT_TOOL_EVENT& aToolEvent )
389 {
390  aToolEvent = eventHandler( aMenuEvent );
391 
392  if( !aToolEvent )
393  runOnSubmenus( std::bind( &CONTEXT_MENU::runEventHandlers, _1, aMenuEvent, aToolEvent ) );
394 }
395 
396 
397 void CONTEXT_MENU::runOnSubmenus( std::function<void(CONTEXT_MENU*)> aFunction )
398 {
399  try
400  {
401  std::for_each( m_submenus.begin(), m_submenus.end(), [&]( CONTEXT_MENU* m ) {
402  aFunction( m );
403  m->runOnSubmenus( aFunction );
404  } );
405  }
406  catch( std::exception& e )
407  {
408  wxLogDebug( wxString::Format( "CONTEXT_MENU runOnSubmenus exception: %s", e.what() ) );
409  }
410 }
411 
412 
414 {
415  OPT_TOOL_EVENT evt;
416 
417  auto findFunc = [&]( CONTEXT_MENU* m ) {
418  if( evt )
419  return;
420 
421  const auto it = m->m_toolActions.find( aId );
422 
423  if( it != m->m_toolActions.end() )
424  evt = it->second->MakeEvent();
425  };
426 
427  findFunc( this );
428 
429  if( !evt )
430  runOnSubmenus( findFunc );
431 
432  return evt;
433 }
434 
435 
437 {
438  m_icon = aMenu.m_icon;
439  m_title = aMenu.m_title;
441  m_selected = -1; // aMenu.m_selected;
442  m_tool = aMenu.m_tool;
444 
445  // Copy all menu entries
446  for( int i = 0; i < (int) aMenu.GetMenuItemCount(); ++i )
447  {
448  wxMenuItem* item = aMenu.FindItemByPosition( i );
449  appendCopy( item );
450  }
451 }
452 
453 
454 wxMenuItem* CONTEXT_MENU::appendCopy( const wxMenuItem* aSource )
455 {
456  wxMenuItem* newItem = new wxMenuItem( this, aSource->GetId(), aSource->GetItemLabel(),
457  aSource->GetHelp(), aSource->GetKind() );
458 
459  bool useImagesInMenus;
460  Pgm().CommonSettings()->Read( USE_ICONS_IN_MENUS_KEY, &useImagesInMenus );
461 
462  if( aSource->GetKind() == wxITEM_NORMAL && useImagesInMenus )
463  newItem->SetBitmap( aSource->GetBitmap() );
464 
465  if( aSource->IsSubMenu() )
466  {
467  CONTEXT_MENU* menu = dynamic_cast<CONTEXT_MENU*>( aSource->GetSubMenu() );
468  wxASSERT_MSG( menu, "Submenus are expected to be a CONTEXT_MENU" );
469 
470  if( menu )
471  {
472  CONTEXT_MENU* menuCopy = menu->Clone();
473  newItem->SetSubMenu( menuCopy );
474  m_submenus.push_back( menuCopy );
475  }
476  }
477 
478  // wxMenuItem has to be added before enabling/disabling or checking
479  Append( newItem );
480 
481  if( aSource->IsCheckable() )
482  newItem->Check( aSource->IsChecked() );
483 
484  newItem->Enable( aSource->IsEnabled() );
485 
486  return newItem;
487 }
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:168
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:187
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:44
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:495