KiCad PCB EDA Suite
Loading...
Searching...
No Matches
action_manager.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-2023 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Maciej Suminski <[email protected]>
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, see <https://www.gnu.org/licenses/>.
20 */
21
22#include <eda_draw_frame.h>
23#include <tool/action_manager.h>
24#include <tool/tool_action.h>
25#include <tool/tool_manager.h>
26#include <trace_helpers.h>
27#include <wx/log.h>
28
29#include <hotkeys_basic.h>
30#include <algorithm>
31#include <cctype>
32
33
35 m_toolMgr( aToolManager )
36{
37 // Register known actions
38 std::list<TOOL_ACTION*>& actionList = GetActionList();
39
40 for( TOOL_ACTION* action : actionList )
41 {
42 if( action->m_id == -1 )
43 action->m_id = MakeActionId( action->m_name );
44
45 int groupID = 0;
46 std::string groupName = "none";
47
48 std::optional<TOOL_ACTION_GROUP> group = action->GetActionGroup();
49
50 if( group.has_value() )
51 {
52 groupID = group.value().GetGroupID();
53 groupName = group.value().GetName();
54 }
55
56 wxLogTrace( kicadTraceToolStack,
57 "ACTION_MANAGER::ACTION_MANAGER: Registering action %s with ID %d, UI ID %d, "
58 "group %s(%d), toolbar state %s",
59 action->m_name, action->m_id, action->GetUIId(), groupName, groupID,
60 action->m_toolbarState.to_string() );
61
62 RegisterAction( action );
63 }
64}
65
66
70
71
73{
74 // TOOL_ACTIONs are supposed to be named [appName.]toolName.actionName (with dots between)
75 // action name without specifying at least toolName is not valid
76 wxASSERT( aAction->GetName().find( '.', 0 ) != std::string::npos );
77
78 // TOOL_ACTIONs must have unique names & ids
79 wxASSERT_MSG( m_actionNameIndex.find( aAction->m_name ) == m_actionNameIndex.end(),
80 wxString::Format( "Action '%s' already registered", aAction->m_name ) );
81
82 m_actionNameIndex[aAction->m_name] = aAction;
83
84 if( aAction->HasCustomUIId() )
85 m_customUIIdIndex[aAction->GetUIId()] = aAction;
86}
87
88
90 const ACTION_CONDITIONS& aConditions )
91{
92 // Remove any existing handlers with the old conditions to ensure the UI layer doesn't have
93 // stale data.
94 if( m_toolMgr )
95 m_toolMgr->GetToolHolder()->UnregisterUIUpdateHandler( aAction );
96
97 m_uiConditions[aAction.GetId()] = aConditions;
98
99 wxLogTrace( kicadTraceToolStack,
100 wxS( "ACTION_MANAGER::SetConditions: Registering conditions for ID %d - %s" ),
101 aAction.GetId(), aAction.GetName() );
102
103 // Register a new handler with the new conditions
104 if( m_toolMgr )
105 m_toolMgr->GetToolHolder()->RegisterUIUpdateHandler( aAction, aConditions );
106}
107
108
110{
111 const auto it = m_uiConditions.find( aAction.GetId() );
112
113 // If the action doesn't have something registered, then return null
114 if( it == m_uiConditions.end() )
115 return nullptr;
116 else
117 return &it->second;
118}
119
120
121int ACTION_MANAGER::MakeActionId( const std::string& aActionName )
122{
123 static int currentActionId = 1;
124
125 return currentActionId++;
126}
127
128
129TOOL_ACTION* ACTION_MANAGER::FindAction( const std::string& aActionName ) const
130{
131 std::map<std::string, TOOL_ACTION*>::const_iterator it = m_actionNameIndex.find( aActionName );
132
133 if( it != m_actionNameIndex.end() )
134 return it->second;
135
136 return nullptr;
137}
138
139
140bool ACTION_MANAGER::RunHotKey( int aHotKey ) const
141{
142 int key = aHotKey & ~MD_MODIFIER_MASK;
143 int mod = aHotKey & MD_MODIFIER_MASK;
144
145 if( key >= 'a' && key <= 'z' )
146 key = std::toupper( key );
147
148 wxLogTrace( kicadTraceToolStack, wxS( "ACTION_MANAGER::RunHotKey Key: %s" ),
149 KeyNameFromKeyCode( aHotKey ) );
150
151 int matchedHotKey = key | mod;
152 HOTKEY_LIST::const_iterator it = m_actionHotKeys.find( matchedHotKey );
153
154 // If no luck, try without Shift, to handle keys that require it
155 // e.g. to get ? you need to press Shift+/ without US keyboard layout
156 // Hardcoding ? as Shift+/ is a bad idea, as on another layout you may need to press a
157 // different combination.
158 // This doesn't apply for letters, as we already handled case normalisation.
159 if( it == m_actionHotKeys.end() && !std::isalpha( key ) )
160 {
161 matchedHotKey = key | ( mod & ~MD_SHIFT );
162
163 wxLogTrace( kicadTraceToolStack, wxS( "ACTION_MANAGER::RunHotKey No actions found, searching with key: %s" ),
164 KeyNameFromKeyCode( matchedHotKey ) );
165
166 it = m_actionHotKeys.find( matchedHotKey );
167 }
168
169 // Still no luck, we're done without a match
170 if( it == m_actionHotKeys.end() )
171 return false; // no appropriate action found for the hotkey
172
173 const std::list<TOOL_ACTION*>& actions = it->second;
174
175 // Choose the action that has the highest priority on the active tools stack
176 // If there is none, run the global action associated with the hot key
177 int highestPriority = -1, priority = -1;
178 const TOOL_ACTION* context = nullptr; // pointer to context action of the highest priority tool
179 std::vector<const TOOL_ACTION*> global; // pointers to global actions
180 // if there is no context action
181
182 for( const TOOL_ACTION* action : actions )
183 {
184 if( action->GetScope() == AS_GLOBAL )
185 {
186 // Store the global action in case there are no context actions to run
187 global.emplace_back( action );
188 continue;
189 }
190
191 TOOL_BASE* tool = m_toolMgr->FindTool( action->GetToolName() );
192
193 if( tool )
194 {
195 // Choose the action that goes to the tool with highest priority
196 // (i.e. is on the top of active tools stack)
197 priority = m_toolMgr->GetPriority( tool->GetId() );
198
199 if( priority >= 0 && priority > highestPriority )
200 {
201 highestPriority = priority;
202 context = action;
203 }
204 }
205 }
206
207 if( !context && global.size() > 1 )
208 {
209 if( EDA_BASE_FRAME* frame = dynamic_cast<EDA_BASE_FRAME*>( m_toolMgr->GetToolHolder() ) )
210 PromoteUserBoundFrameAction( global, frame->GetFrameType(), matchedHotKey );
211 }
212
213 // Get the selection to use to test if the action is enabled
214 SELECTION& sel = m_toolMgr->GetToolHolder()->GetCurrentSelection();
215
216 if( context )
217 {
218 bool runAction = true;
219
220 if( const ACTION_CONDITIONS* aCond = GetCondition( *context ) )
221 runAction = aCond->enableCondition( sel );
222
223 wxLogTrace( kicadTraceToolStack,
224 wxS( "ACTION_MANAGER::RunHotKey %s context action: %s for hotkey %s" ),
225 runAction ? wxS( "Running" ) : wxS( "Not running" ),
226 context->GetName(),
227 KeyNameFromKeyCode( aHotKey ) );
228
229 if( runAction )
230 return m_toolMgr->RunAction( *context );
231 }
232 else if( !global.empty() )
233 {
234 for( const TOOL_ACTION* act : global )
235 {
236 bool runAction = true;
237
238 if( const ACTION_CONDITIONS* aCond = GetCondition( *act ) )
239 runAction = aCond->enableCondition( sel );
240
241 wxLogTrace( kicadTraceToolStack,
242 wxS( "ACTION_MANAGER::RunHotKey %s global action: %s for hotkey %s" ),
243 runAction ? wxS( "Running" ) : wxS( "Not running" ),
244 act->GetName(),
245 KeyNameFromKeyCode( aHotKey ) );
246
247 if( runAction && m_toolMgr->RunAction( *act ) )
248 return true;
249 }
250 }
251
252 wxLogTrace( kicadTraceToolStack,
253 wxS( "ACTION_MANAGER::RunHotKey No action found for key %s" ),
254 KeyNameFromKeyCode( aHotKey ) );
255
256 return false;
257}
258
259
261{
262 switch( aFrameType )
263 {
264 case FRAME_PCB_EDITOR:
266 case FRAME_FOOTPRINT_VIEWER: return "pcbnew.";
267
268 case FRAME_SCH:
270 case FRAME_SCH_VIEWER:
271 case FRAME_SIMULATOR: return "eeschema.";
272
273 case FRAME_GERBER: return "gerbview.";
274
275 case FRAME_PL_EDITOR: return "plEditor.";
276
277 default: return "";
278 }
279}
280
281
282void ACTION_MANAGER::PromoteUserBoundFrameAction( std::vector<const TOOL_ACTION*>& aGlobalActions, FRAME_T aFrameType,
283 int aMatchedHotKey )
284{
285 const std::string framePrefix = FrameNamespacePrefix( aFrameType );
286
287 if( framePrefix.empty() )
288 return;
289
290 auto userBoundInFrame = [&]( const TOOL_ACTION* aAction )
291 {
292 return aAction->GetName().starts_with( framePrefix ) && aAction->IsHotKeyUserBound( aMatchedHotKey );
293 };
294
295 if( std::any_of( aGlobalActions.begin(), aGlobalActions.end(), userBoundInFrame ) )
296 std::stable_partition( aGlobalActions.begin(), aGlobalActions.end(), userBoundInFrame );
297}
298
299
300bool ACTION_MANAGER::IsActionUIId( int aId ) const
301{
302 // Automatically assigned IDs are always in this range
303 if( aId >= TOOL_ACTION::GetBaseUIId() )
304 return true;
305
306 // Search the custom assigned UI IDs
307 auto it = m_customUIIdIndex.find( aId );
308
309 return ( it != m_customUIIdIndex.end() );
310}
311
312
313const std::map<std::string, TOOL_ACTION*>& ACTION_MANAGER::GetActions() const
314{
315 return m_actionNameIndex;
316}
317
318
319int ACTION_MANAGER::GetHotKey( const TOOL_ACTION& aAction ) const
320{
321 std::map<int, int>::const_iterator it = m_hotkeys.find( aAction.GetId() );
322
323 if( it == m_hotkeys.end() )
324 return 0;
325
326 return it->second;
327}
328
329
330void ACTION_MANAGER::UpdateHotKeys( bool aFullUpdate )
331{
332 static std::map<std::string, int> legacyHotKeyMap;
333 static std::map<std::string, std::pair<int, int>> userHotKeyMap;
334 static bool mapsInitialized = false;
335
336 m_actionHotKeys.clear();
337 m_hotkeys.clear();
338
339 if( m_toolMgr->GetToolHolder() && ( aFullUpdate || !mapsInitialized ) )
340 {
341 ReadLegacyHotkeyConfig( m_toolMgr->GetToolHolder()->ConfigBaseName(), legacyHotKeyMap );
342 ReadHotKeyConfig( wxEmptyString, userHotKeyMap );
343 mapsInitialized = true;
344 }
345
346 for( const auto& ii : m_actionNameIndex )
347 {
348 TOOL_ACTION* action = ii.second;
349 int hotkey = 0;
350 int alt = 0;
351
352 if( aFullUpdate )
353 processHotKey( action, legacyHotKeyMap, userHotKeyMap );
354
355 hotkey = action->GetHotKey();
356 alt = action->GetHotKeyAlt();
357
358 if( hotkey > 0 )
359 m_actionHotKeys[hotkey].push_back( action );
360
361 if( alt > 0 )
362 m_actionHotKeys[alt].push_back( action );
363
364 m_hotkeys[action->GetId()] = hotkey;
365 }
366}
367
368
370 const std::map<std::string, int>& aLegacyMap,
371 const std::map<std::string, std::pair<int, int>>& aHotKeyMap )
372{
373 aAction->m_hotKey = aAction->m_defaultHotKey;
374
375 if( !aAction->m_legacyName.empty() && aLegacyMap.count( aAction->m_legacyName ) )
376 aAction->SetHotKey( aLegacyMap.at( aAction->m_legacyName ) );
377
378 if( aHotKeyMap.count( aAction->m_name ) )
379 {
380 std::pair<int, int> keys = aHotKeyMap.at( aAction->m_name );
381 aAction->SetHotKey( keys.first, keys.second );
382 }
383}
~ACTION_MANAGER()
Unregister every registered action.
HOTKEY_LIST m_actionHotKeys
Quick action<->hot key lookup.
const ACTION_CONDITIONS * GetCondition(const TOOL_ACTION &aAction) const
Get the conditions to use for a specific tool action.
std::map< int, TOOL_ACTION * > m_customUIIdIndex
Map for indexing actions by their hotkeys.
TOOL_MANAGER * m_toolMgr
Map for indexing actions by their names.
void processHotKey(TOOL_ACTION *aAction, const std::map< std::string, int > &aLegacyMap, const std::map< std::string, std::pair< int, int > > &aHotKeyMap)
Tool manager needed to run actions.
const std::map< std::string, TOOL_ACTION * > & GetActions() const
Get a list of currently-registered actions mapped by their name.
std::map< std::string, TOOL_ACTION * > m_actionNameIndex
Map for recording actions that have custom UI IDs.
std::map< int, int > m_hotkeys
bool RunHotKey(int aHotKey) const
Run an action associated with a hotkey (if there is one available).
void SetConditions(const TOOL_ACTION &aAction, const ACTION_CONDITIONS &aConditions)
Set the conditions the UI elements for activating a specific tool action should use for determining t...
void RegisterAction(TOOL_ACTION *aAction)
Add a tool action to the manager and sets it up.
TOOL_ACTION * FindAction(const std::string &aActionName) const
Find an action with a given name (if there is one available).
static void PromoteUserBoundFrameAction(std::vector< const TOOL_ACTION * > &aGlobalActions, FRAME_T aFrameType, int aMatchedHotKey)
Reorder global actions sharing a hotkey so that one native to the given frame is tried first when the...
static std::list< TOOL_ACTION * > & GetActionList()
Return list of TOOL_ACTIONs.
bool IsActionUIId(int aId) const
Test if a UI ID corresponds to an action ID in our system.
int GetHotKey(const TOOL_ACTION &aAction) const
Return the hot key associated with a given action or 0 if there is none.
static int MakeActionId(const std::string &aActionName)
Generate an unique ID from for an action with given name.
std::map< int, ACTION_CONDITIONS > m_uiConditions
Map the command ID that wx uses for the action to the UI conditions for the menu/toolbar items.
void UpdateHotKeys(bool aFullUpdate)
Optionally read the hotkey config files and then rebuilds the internal hotkey maps.
ACTION_MANAGER(TOOL_MANAGER *aToolManager)
static std::string FrameNamespacePrefix(FRAME_T aFrameType)
Return the action-name namespace prefix (e.g.
The base frame for deriving all KiCad main window classes.
Represent a single user action.
bool HasCustomUIId() const
Return true if this action has a custom UI ID set.
static int GetBaseUIId()
Get the base value used to offset the user interface IDs for the actions.
void SetHotKey(int aKeycode, int aKeycodeAlt=0)
int GetId() const
Return the unique id of the TOOL_ACTION object.
const std::string & GetName() const
Return name of the action.
int GetHotKeyAlt() const
int GetHotKey() const
Return the hotkey keycode which initiates the action.
const int m_defaultHotKey
Default hot key.
int m_hotKey
The current hotkey (post-user-settings-application).
const std::string m_legacyName
Name for reading legacy hotkey settings.
std::string m_name
Name of the action (convention is "app.tool.actionName")
int GetUIId() const
Get the unique ID for this action in the user interface system.
Base abstract interface for all kinds of tools.
Definition tool_base.h:62
TOOL_ID GetId() const
Return the unique identifier of the tool.
Definition tool_base.h:119
Master controller class:
FRAME_T
The set of EDA_BASE_FRAME derivatives, typically stored in EDA_BASE_FRAME::m_Ident.
Definition frame_type.h:29
@ FRAME_PCB_EDITOR
Definition frame_type.h:38
@ FRAME_SCH_SYMBOL_EDITOR
Definition frame_type.h:31
@ FRAME_FOOTPRINT_VIEWER
Definition frame_type.h:41
@ FRAME_SCH_VIEWER
Definition frame_type.h:32
@ FRAME_SCH
Definition frame_type.h:30
@ FRAME_SIMULATOR
Definition frame_type.h:34
@ FRAME_PL_EDITOR
Definition frame_type.h:55
@ FRAME_FOOTPRINT_EDITOR
Definition frame_type.h:39
@ FRAME_GERBER
Definition frame_type.h:53
const wxChar *const kicadTraceToolStack
Flag to enable tracing of the tool handling stack.
void ReadHotKeyConfig(const wxString &aFileName, std::map< std::string, std::pair< int, int > > &aHotKeys)
Read a hotkey config file into a map.
int ReadLegacyHotkeyConfig(const wxString &aAppname, std::map< std::string, int > &aMap)
Read configuration data and fill the current hotkey list with hotkeys.
wxString KeyNameFromKeyCode(int aKeycode, bool *aIsFound)
Return the key name from the key code.
Functors that can be used to figure out how the action controls should be displayed in the UI and if ...
@ AS_GLOBAL
Global action (toolbar/main menu event, global shortcut)
Definition tool_action.h:45
@ MD_MODIFIER_MASK
Definition tool_event.h:145
@ MD_SHIFT
Definition tool_event.h:139
wxLogTrace helper definitions.