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