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