1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2013 CERN
5  * Copyright (C) 2019-2021 KiCad Developers, see AUTHORS.txt for contributors.
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 <eda_draw_frame.h>
27 #include <tool/action_manager.h>
28 #include <tool/tool_action.h>
29 #include <tool/tool_manager.h>
30 #include <trace_helpers.h>
31 #include <wx/log.h>
32 
33 #include <hotkeys_basic.h>
34 #include <cctype>
35 
ACTION_MANAGER(TOOL_MANAGER * aToolManager)36 ACTION_MANAGER::ACTION_MANAGER( TOOL_MANAGER* aToolManager ) :
37     m_toolMgr( aToolManager )
38 {
39     // Register known actions
40     std::list<TOOL_ACTION*>& actionList = GetActionList();
41 
42     for( TOOL_ACTION* action : actionList )
43     {
44         if( action->m_id == -1 )
45             action->m_id = MakeActionId( action->m_name );
46 
47         RegisterAction( action );
48     }
49 }
50 
51 
~ACTION_MANAGER()52 ACTION_MANAGER::~ACTION_MANAGER()
53 {
54 }
55 
56 
RegisterAction(TOOL_ACTION * aAction)57 void ACTION_MANAGER::RegisterAction( TOOL_ACTION* aAction )
58 {
59     // TOOL_ACTIONs are supposed to be named [appName.]toolName.actionName (with dots between)
60     // action name without specifying at least toolName is not valid
61     wxASSERT( aAction->GetName().find( '.', 0 ) != std::string::npos );
62 
63     // TOOL_ACTIONs must have unique names & ids
64     wxASSERT( m_actionNameIndex.find( aAction->m_name ) == m_actionNameIndex.end() );
65 
66     m_actionNameIndex[aAction->m_name] = aAction;
67 }
68 
69 
SetConditions(const TOOL_ACTION & aAction,const ACTION_CONDITIONS & aConditions)70 void ACTION_MANAGER::SetConditions( const TOOL_ACTION& aAction,
71                                     const ACTION_CONDITIONS& aConditions )
72 {
73     // Remove any existing handlers with the old conditions to ensure the UI layer doesn't have
74     // stale data.
75     if( m_toolMgr )
76         m_toolMgr->GetToolHolder()->UnregisterUIUpdateHandler( aAction );
77 
78     m_uiConditions[aAction.GetId()] = aConditions;
79 
80     wxLogTrace( kicadTraceToolStack,
81                 "ACTION_MANAGER::SetConditions: Registering conditions for ID %d - %s",
82                 aAction.GetId(), aAction.GetName() );
83 
84     // Register a new handler with the new conditions
85     if( m_toolMgr )
86         m_toolMgr->GetToolHolder()->RegisterUIUpdateHandler( aAction, aConditions );
87 }
88 
89 
GetCondition(const TOOL_ACTION & aAction) const90 const ACTION_CONDITIONS* ACTION_MANAGER::GetCondition( const TOOL_ACTION& aAction ) const
91 {
92     const auto it = m_uiConditions.find( aAction.GetId() );
93 
94     // If the action doesn't have something registered, then return null
95     if( it == m_uiConditions.end() )
96         return nullptr;
97     else
98         return &it->second;
99 }
100 
101 
MakeActionId(const std::string & aActionName)102 int ACTION_MANAGER::MakeActionId( const std::string& aActionName )
103 {
104     static int currentActionId = 1;
105 
106     return currentActionId++;
107 }
108 
109 
FindAction(const std::string & aActionName) const110 TOOL_ACTION* ACTION_MANAGER::FindAction( const std::string& aActionName ) const
111 {
112     std::map<std::string, TOOL_ACTION*>::const_iterator it = m_actionNameIndex.find( aActionName );
113 
114     if( it != m_actionNameIndex.end() )
115         return it->second;
116 
117     return nullptr;
118 }
119 
120 
RunHotKey(int aHotKey) const121 bool ACTION_MANAGER::RunHotKey( int aHotKey ) const
122 {
123     int key = aHotKey & ~MD_MODIFIER_MASK;
124     int mod = aHotKey & MD_MODIFIER_MASK;
125 
126     if( key >= 'a' && key <= 'z' )
127         key = std::toupper( key );
128 
129     wxLogTrace( kicadTraceToolStack, "ACTION_MANAGER::RunHotKey Key: %s",
130                 KeyNameFromKeyCode( aHotKey ) );
131 
132     HOTKEY_LIST::const_iterator it = m_actionHotKeys.find( key | mod );
133 
134     // If no luck, try without Shift, to handle keys that require it
135     // e.g. to get ? you need to press Shift+/ without US keyboard layout
136     // Hardcoding ? as Shift+/ is a bad idea, as on another layout you may need to press a
137     // different combination
138     if( it == m_actionHotKeys.end() )
139     {
140         wxLogTrace( kicadTraceToolStack,
141                     "ACTION_MANAGER::RunHotKey No actions found, searching with key: %s",
142                     KeyNameFromKeyCode( key | ( mod & ~MD_SHIFT ) ) );
143 
144         it = m_actionHotKeys.find( key | ( mod & ~MD_SHIFT ) );
145 
146         if( it == m_actionHotKeys.end() )
147             return false; // no appropriate action found for the hotkey
148     }
149 
150     const std::list<TOOL_ACTION*>& actions = it->second;
151 
152     // Choose the action that has the highest priority on the active tools stack
153     // If there is none, run the global action associated with the hot key
154     int highestPriority = -1, priority = -1;
155     const TOOL_ACTION* context = nullptr; // pointer to context action of the highest priority tool
156     std::vector<const TOOL_ACTION*> global; // pointers to global actions
157                                             // if there is no context action
158 
159     for( const TOOL_ACTION* action : actions )
160     {
161         if( action->GetScope() == AS_GLOBAL )
162         {
163             // Store the global action in case there are no context actions to run
164             global.emplace_back( action );
165             continue;
166         }
167 
168         TOOL_BASE* tool = m_toolMgr->FindTool( action->GetToolName() );
169 
170         if( tool )
171         {
172             // Choose the action that goes to the tool with highest priority
173             // (i.e. is on the top of active tools stack)
174             priority = m_toolMgr->GetPriority( tool->GetId() );
175 
176             if( priority >= 0 && priority > highestPriority )
177             {
178                 highestPriority = priority;
179                 context = action;
180             }
181         }
182     }
183 
184     // Get the selection to use to test if the action is enabled
185     SELECTION& sel = m_toolMgr->GetToolHolder()->GetCurrentSelection();
186 
187     if( context )
188     {
189         bool runAction = true;
190 
191         if( const ACTION_CONDITIONS* aCond = GetCondition( *context ) )
192             runAction = aCond->enableCondition( sel );
193 
194         wxLogTrace( kicadTraceToolStack,
195                     "ACTION_MANAGER::RunHotKey %s context action: %s for hotkey %s",
196                     runAction ? "Running" : "Not running",
197                     context->GetName(),
198                     KeyNameFromKeyCode( aHotKey ) );
199 
200         if( runAction )
201             return m_toolMgr->RunAction( *context, true );
202     }
203     else if( !global.empty() )
204     {
205         for( auto act : global )
206         {
207             bool runAction = true;
208 
209             if( const ACTION_CONDITIONS* aCond = GetCondition( *act ) )
210                 runAction = aCond->enableCondition( sel );
211 
212             wxLogTrace( kicadTraceToolStack,
213                         "ACTION_MANAGER::RunHotKey %s global action: %s for hotkey %s",
214                         runAction ? "Running" : "Not running",
215                         act->GetName(),
216                         KeyNameFromKeyCode( aHotKey ) );
217 
218             if( runAction && m_toolMgr->RunAction( *act, true ) )
219                 return true;
220         }
221     }
222 
223     wxLogTrace( kicadTraceToolStack,
224                 "ACTION_MANAGER::RunHotKey No action found for key %s",
225                 KeyNameFromKeyCode( aHotKey ) );
226 
227     return false;
228 }
229 
230 
GetActions() const231 const std::map<std::string, TOOL_ACTION*>& ACTION_MANAGER::GetActions() const
232 {
233     return m_actionNameIndex;
234 }
235 
236 
GetHotKey(const TOOL_ACTION & aAction) const237 int ACTION_MANAGER::GetHotKey( const TOOL_ACTION& aAction ) const
238 {
239     std::map<int, int>::const_iterator it = m_hotkeys.find( aAction.GetId() );
240 
241     if( it == m_hotkeys.end() )
242         return 0;
243 
244     return it->second;
245 }
246 
247 
UpdateHotKeys(bool aFullUpdate)248 void ACTION_MANAGER::UpdateHotKeys( bool aFullUpdate )
249 {
250     std::map<std::string, int> legacyHotKeyMap;
251     std::map<std::string, int> userHotKeyMap;
252 
253     m_actionHotKeys.clear();
254     m_hotkeys.clear();
255 
256     if( aFullUpdate && m_toolMgr->GetToolHolder() )
257     {
258         ReadLegacyHotkeyConfig( m_toolMgr->GetToolHolder()->ConfigBaseName(), legacyHotKeyMap );
259         ReadHotKeyConfig( wxEmptyString, userHotKeyMap );
260     }
261 
262     for( const auto& ii : m_actionNameIndex )
263     {
264         TOOL_ACTION* action = ii.second;
265         int          hotkey = 0;
266 
267         if( aFullUpdate )
268             hotkey = processHotKey( action, legacyHotKeyMap, userHotKeyMap );
269         else
270             hotkey = action->GetHotKey();
271 
272         if( hotkey > 0 )
273             m_actionHotKeys[hotkey].push_back( action );
274 
275         m_hotkeys[action->GetId()] = hotkey;
276     }
277 }
278 
279 
processHotKey(TOOL_ACTION * aAction,std::map<std::string,int> aLegacyMap,std::map<std::string,int> aHotKeyMap)280 int ACTION_MANAGER::processHotKey( TOOL_ACTION* aAction, std::map<std::string, int> aLegacyMap,
281                                    std::map<std::string, int> aHotKeyMap )
282 {
283     aAction->m_hotKey = aAction->m_defaultHotKey;
284 
285     if( !aAction->m_legacyName.empty() && aLegacyMap.count( aAction->m_legacyName ) )
286         aAction->SetHotKey( aLegacyMap[ aAction->m_legacyName ] );
287 
288     if( aHotKeyMap.count( aAction->m_name ) )
289         aAction->SetHotKey( aHotKeyMap[ aAction->m_name ] );
290 
291     return aAction->m_hotKey;
292 }
293