1 /*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright (C) 2010 Wayne Stambaugh <stambaughw@gmail.com>
6 * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
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 <kiface_base.h>
27 #include <hotkeys_basic.h>
28 #include <id.h>
29 #include <string_utils.h>
30 #include <eda_base_frame.h>
31 #include <eda_draw_frame.h>
32 #include <wildcards_and_files_ext.h>
33 #include <settings/settings_manager.h>
34
35 #include <tool/tool_manager.h>
36 #include "dialogs/dialog_hotkey_list.h"
37 #include <wx/apptrait.h>
38 #include <wx/stdpaths.h>
39 #include <wx/tokenzr.h>
40 #include <wx/txtstrm.h>
41 #include <wx/wfstream.h>
42 #include <tool/tool_action.h>
43
44
45 /*
46 * class to handle the printable name and the keycode
47 */
48 struct hotkey_name_descr
49 {
50 const wxChar* m_Name;
51 int m_KeyCode;
52 };
53
54
55 /* table giving the hotkey name from the hotkey code, for special keys
56 * Note : when modifiers (ATL, SHIFT, CTRL) do not modify
57 * the code of the key, do need to enter the modified key code
58 * For instance wxT( "F1" ), WXK_F1 handle F1, AltF1, CtrlF1 ...
59 * Key names are:
60 * "Space","Ctrl+Space","Alt+Space" or
61 * "Alt+A","Ctrl+F1", ...
62 */
63 #define KEY_NON_FOUND -1
64 static struct hotkey_name_descr hotkeyNameList[] =
65 {
66 { wxT( "F1" ), WXK_F1 },
67 { wxT( "F2" ), WXK_F2 },
68 { wxT( "F3" ), WXK_F3 },
69 { wxT( "F4" ), WXK_F4 },
70 { wxT( "F5" ), WXK_F5 },
71 { wxT( "F6" ), WXK_F6 },
72 { wxT( "F7" ), WXK_F7 },
73 { wxT( "F8" ), WXK_F8 },
74 { wxT( "F9" ), WXK_F9 },
75 { wxT( "F10" ), WXK_F10 },
76 { wxT( "F11" ), WXK_F11 },
77 { wxT( "F12" ), WXK_F12 },
78
79 { wxT( "Esc" ), WXK_ESCAPE },
80 { wxT( "Del" ), WXK_DELETE },
81 { wxT( "Tab" ), WXK_TAB },
82 { wxT( "Back" ), WXK_BACK },
83 { wxT( "Ins" ), WXK_INSERT },
84
85 { wxT( "Home" ), WXK_HOME },
86 { wxT( "End" ), WXK_END },
87 { wxT( "PgUp" ), WXK_PAGEUP },
88 { wxT( "PgDn" ), WXK_PAGEDOWN },
89
90 { wxT( "Up" ), WXK_UP },
91 { wxT( "Down" ), WXK_DOWN },
92 { wxT( "Left" ), WXK_LEFT },
93 { wxT( "Right" ), WXK_RIGHT },
94
95 { wxT( "Return" ), WXK_RETURN },
96
97 { wxT( "Space" ), WXK_SPACE },
98
99 { wxT( "Num Pad 0" ), WXK_NUMPAD0 },
100 { wxT( "Num Pad 1" ), WXK_NUMPAD1 },
101 { wxT( "Num Pad 2" ), WXK_NUMPAD2 },
102 { wxT( "Num Pad 3" ), WXK_NUMPAD3 },
103 { wxT( "Num Pad 4" ), WXK_NUMPAD4 },
104 { wxT( "Num Pad 5" ), WXK_NUMPAD5 },
105 { wxT( "Num Pad 6" ), WXK_NUMPAD6 },
106 { wxT( "Num Pad 7" ), WXK_NUMPAD7 },
107 { wxT( "Num Pad 8" ), WXK_NUMPAD8 },
108 { wxT( "Num Pad 9" ), WXK_NUMPAD9 },
109 { wxT( "Num Pad +" ), WXK_NUMPAD_ADD },
110 { wxT( "Num Pad -" ), WXK_NUMPAD_SUBTRACT },
111 { wxT( "Num Pad *" ), WXK_NUMPAD_MULTIPLY },
112 { wxT( "Num Pad /" ), WXK_NUMPAD_DIVIDE },
113 { wxT( "Num Pad ." ), WXK_NUMPAD_SEPARATOR },
114
115 { wxT( "" ), 0 },
116
117 { wxT( "Click" ), PSEUDO_WXK_CLICK },
118 { wxT( "DblClick" ), PSEUDO_WXK_DBLCLICK },
119 { wxT( "Wheel" ), PSEUDO_WXK_WHEEL },
120
121 // Do not change this line: end of list
122 { wxT( "" ), KEY_NON_FOUND }
123 };
124
125
126 // name of modifier keys.
127 // Note: the Ctrl key is Cmd key on Mac OS X.
128 // However, in wxWidgets defs, the key WXK_CONTROL is the Cmd key,
129 // so the code using WXK_CONTROL should be ok on any system.
130 // (on Mac OS X the actual Ctrl key code is WXK_RAW_CONTROL)
131 #ifdef __WXMAC__
132 #define USING_MAC_CMD
133 #endif
134
135 #ifdef USING_MAC_CMD
136 #define MODIFIER_CTRL wxT( "Cmd+" )
137 #define MODIFIER_ALT wxT( "Option+" )
138 #else
139 #define MODIFIER_CTRL wxT( "Ctrl+" )
140 #define MODIFIER_ALT wxT( "Alt+" )
141 #endif
142 #define MODIFIER_CMD_MAC wxT( "Cmd+" )
143 #define MODIFIER_CTRL_BASE wxT( "Ctrl+" )
144 #define MODIFIER_SHIFT wxT( "Shift+" )
145
146
147 /**
148 * Return the key name from the key code.
149 *
150 * Only some wxWidgets key values are handled for function key ( see hotkeyNameList[] )
151 *
152 * @param aKeycode key code (ASCII value, or wxWidgets value for function keys).
153 * @param aIsFound a pointer to a bool to return true if found, or false. an be nullptr default).
154 * @return the key name in a wxString.
155 */
KeyNameFromKeyCode(int aKeycode,bool * aIsFound)156 wxString KeyNameFromKeyCode( int aKeycode, bool* aIsFound )
157 {
158 wxString keyname, modifier, fullkeyname;
159 int ii;
160 bool found = false;
161
162 // Assume keycode of 0 is "unassigned"
163 if( (aKeycode & MD_CTRL) != 0 )
164 modifier << MODIFIER_CTRL;
165
166 if( (aKeycode & MD_ALT) != 0 )
167 modifier << MODIFIER_ALT;
168
169 if( (aKeycode & MD_SHIFT) != 0 )
170 modifier << MODIFIER_SHIFT;
171
172 aKeycode &= ~( MD_CTRL | MD_ALT | MD_SHIFT );
173
174 if( (aKeycode > ' ') && (aKeycode < 0x7F ) )
175 {
176 found = true;
177 keyname.Append( (wxChar)aKeycode );
178 }
179 else
180 {
181 for( ii = 0; ; ii++ )
182 {
183 if( hotkeyNameList[ii].m_KeyCode == KEY_NON_FOUND ) // End of list
184 {
185 keyname = wxT( "<unknown>" );
186 break;
187 }
188
189 if( hotkeyNameList[ii].m_KeyCode == aKeycode )
190 {
191 keyname = hotkeyNameList[ii].m_Name;
192 found = true;
193 break;
194 }
195 }
196 }
197
198 if( aIsFound )
199 *aIsFound = found;
200
201 fullkeyname = modifier + keyname;
202 return fullkeyname;
203 }
204
205
206 /**
207 * @param aText the base text on which to append the hotkey.
208 * @param aHotKey the hotkey keycode.
209 * @param aStyle IS_HOTKEY to add <tab><keyname> (shortcuts in menus, same as hotkeys).
210 * IS_COMMENT to add <spaces><(keyname)> mainly in tool tips.
211 */
AddHotkeyName(const wxString & aText,int aHotKey,HOTKEY_ACTION_TYPE aStyle)212 wxString AddHotkeyName( const wxString& aText, int aHotKey, HOTKEY_ACTION_TYPE aStyle )
213 {
214 wxString msg = aText;
215 wxString keyname = KeyNameFromKeyCode( aHotKey );
216
217 if( !keyname.IsEmpty() )
218 {
219 switch( aStyle )
220 {
221 case IS_HOTKEY:
222 {
223 // Don't add a suffix for unassigned hotkeys:
224 // WX spews debug from wxAcceleratorEntry::ParseAccel if it doesn't
225 // recognize the keyname, which is the case for <unassigned>.
226 if( aHotKey != 0 )
227 {
228 msg << wxT( "\t" ) << keyname;
229 }
230 break;
231 }
232 case IS_COMMENT:
233 {
234 msg << wxT( " (" ) << keyname << wxT( ")" );
235 break;
236 }
237 }
238 }
239
240 #ifdef USING_MAC_CMD
241 // On OSX, the modifier equivalent to the Ctrl key of PCs
242 // is the Cmd key, but in code we should use Ctrl as prefix in menus
243 msg.Replace( MODIFIER_CMD_MAC, MODIFIER_CTRL_BASE );
244 #endif
245
246 return msg;
247 }
248
249
250 /**
251 * Return the key code from its user-friendly key name (ie: "Ctrl+M").
252 */
KeyCodeFromKeyName(const wxString & keyname)253 int KeyCodeFromKeyName( const wxString& keyname )
254 {
255 int ii, keycode = KEY_NON_FOUND;
256
257 // Search for modifiers: Ctrl+ Alt+ and Shift+
258 // Note: on Mac OSX, the Cmd key is equiv here to Ctrl
259 wxString key = keyname;
260 wxString prefix;
261 int modifier = 0;
262
263 while( true )
264 {
265 prefix.Empty();
266
267 if( key.StartsWith( MODIFIER_CTRL_BASE ) )
268 {
269 modifier |= MD_CTRL;
270 prefix = MODIFIER_CTRL_BASE;
271 }
272 else if( key.StartsWith( MODIFIER_CMD_MAC ) )
273 {
274 modifier |= MD_CTRL;
275 prefix = MODIFIER_CMD_MAC;
276 }
277 else if( key.StartsWith( MODIFIER_ALT ) )
278 {
279 modifier |= MD_ALT;
280 prefix = MODIFIER_ALT;
281 }
282 else if( key.StartsWith( MODIFIER_SHIFT ) )
283 {
284 modifier |= MD_SHIFT;
285 prefix = MODIFIER_SHIFT;
286 }
287 else
288 {
289 break;
290 }
291
292 if( !prefix.IsEmpty() )
293 key.Remove( 0, prefix.Len() );
294 }
295
296 if( (key.length() == 1) && (key[0] > ' ') && (key[0] < 0x7F) )
297 {
298 keycode = key[0];
299 keycode += modifier;
300 return keycode;
301 }
302
303 for( ii = 0; hotkeyNameList[ii].m_KeyCode != KEY_NON_FOUND; ii++ )
304 {
305 if( key.CmpNoCase( hotkeyNameList[ii].m_Name ) == 0 )
306 {
307 keycode = hotkeyNameList[ii].m_KeyCode + modifier;
308 break;
309 }
310 }
311
312 return keycode;
313 }
314
315
316 /*
317 * Displays the hotkeys registered with the given tool manager.
318 */
DisplayHotkeyList(EDA_BASE_FRAME * aParent,TOOL_MANAGER * aToolManager)319 void DisplayHotkeyList( EDA_BASE_FRAME* aParent, TOOL_MANAGER* aToolManager )
320 {
321 DIALOG_LIST_HOTKEYS dlg( aParent, aToolManager );
322 dlg.ShowModal();
323 }
324
325
ReadHotKeyConfig(const wxString & aFileName,std::map<std::string,int> & aHotKeys)326 void ReadHotKeyConfig( const wxString& aFileName, std::map<std::string, int>& aHotKeys )
327 {
328 wxString fileName = aFileName;
329
330 if( fileName.IsEmpty() )
331 {
332 wxFileName fn( "user" );
333 fn.SetExt( HotkeyFileExtension );
334 fn.SetPath( SETTINGS_MANAGER::GetUserSettingsPath() );
335 fileName = fn.GetFullPath();
336 }
337
338 if( !wxFile::Exists( fileName ) )
339 return;
340
341 wxFFile file( fileName, "rb" );
342
343 if( !file.IsOpened() ) // There is a problem to open file
344 return;
345
346 wxString input;
347 file.ReadAll( &input );
348 input.Replace( "\r\n", "\n" ); // Convert Windows files to Unix line-ends
349 wxStringTokenizer fileTokenizer( input, "\n", wxTOKEN_STRTOK );
350
351 while( fileTokenizer.HasMoreTokens() )
352 {
353 wxStringTokenizer lineTokenizer( fileTokenizer.GetNextToken(), "\t" );
354
355 wxString cmdName = lineTokenizer.GetNextToken();
356 wxString keyName = lineTokenizer.GetNextToken();
357
358 if( !cmdName.IsEmpty() )
359 aHotKeys[ cmdName.ToStdString() ] = KeyCodeFromKeyName( keyName );
360 }
361 }
362
363
WriteHotKeyConfig(const std::map<std::string,TOOL_ACTION * > & aActionMap)364 int WriteHotKeyConfig( const std::map<std::string, TOOL_ACTION*>& aActionMap )
365 {
366 std::map<std::string, int> hotkeys;
367 wxFileName fn( "user" );
368
369 fn.SetExt( HotkeyFileExtension );
370 fn.SetPath( SETTINGS_MANAGER::GetUserSettingsPath() );
371
372 // Read the existing config (all hotkeys)
373 ReadHotKeyConfig( fn.GetFullPath(), hotkeys );
374
375 // Overlay the current app's hotkey definitions onto the map
376 for( const auto& ii : aActionMap )
377 hotkeys[ ii.first ] = ii.second->GetHotKey();
378
379 // Write entire hotkey set
380 wxFFileOutputStream outStream( fn.GetFullPath() );
381 wxTextOutputStream txtStream( outStream, wxEOL_UNIX );
382
383 for( const auto& ii : hotkeys )
384 txtStream << wxString::Format( "%s\t%s", ii.first,
385 KeyNameFromKeyCode( ii.second ) ) << endl;
386
387 txtStream.Flush();
388 outStream.Close();
389
390 return 1;
391 }
392
393
ReadLegacyHotkeyConfig(const wxString & aAppname,std::map<std::string,int> & aMap)394 int ReadLegacyHotkeyConfig( const wxString& aAppname, std::map<std::string, int>& aMap )
395 {
396 // For Eeschema and Pcbnew frames, we read the new combined file.
397 // For other kifaces, we read the frame-based file
398 if( aAppname == LIB_EDIT_FRAME_NAME || aAppname == SCH_EDIT_FRAME_NAME )
399 {
400 return ReadLegacyHotkeyConfigFile( EESCHEMA_HOTKEY_NAME, aMap );
401 }
402 else if( aAppname == PCB_EDIT_FRAME_NAME || aAppname == FOOTPRINT_EDIT_FRAME_NAME )
403 {
404 return ReadLegacyHotkeyConfigFile( PCBNEW_HOTKEY_NAME, aMap );
405 }
406
407 return ReadLegacyHotkeyConfigFile( aAppname, aMap );
408 }
409
410
ReadLegacyHotkeyConfigFile(const wxString & aFilename,std::map<std::string,int> & aMap)411 int ReadLegacyHotkeyConfigFile( const wxString& aFilename, std::map<std::string, int>& aMap )
412 {
413 wxFileName fn( aFilename );
414
415 fn.SetExt( HotkeyFileExtension );
416 fn.SetPath( SETTINGS_MANAGER::GetUserSettingsPath() );
417
418 if( !wxFile::Exists( fn.GetFullPath() ) )
419 return 0;
420
421 wxFFile cfgfile( fn.GetFullPath(), "rb" );
422
423 if( !cfgfile.IsOpened() ) // There is a problem to open file
424 return 0;
425
426 // get length
427 wxFileOffset size = cfgfile.Length();
428
429 // read data
430 std::vector<char> buffer( size );
431 cfgfile.Read( buffer.data(), size );
432 wxString data( buffer.data(), wxConvUTF8, size );
433
434 // Is this the wxConfig format? If so, remove "Keys=" and parse the newlines.
435 if( data.StartsWith( wxT("Keys="), &data ) )
436 data.Replace( "\\n", "\n", true );
437
438 // parse
439 wxStringTokenizer tokenizer( data, L"\r\n", wxTOKEN_STRTOK );
440
441 while( tokenizer.HasMoreTokens() )
442 {
443 wxString line = tokenizer.GetNextToken();
444 wxStringTokenizer lineTokenizer( line );
445
446 wxString line_type = lineTokenizer.GetNextToken();
447
448 if( line_type[0] == '#' ) // comment
449 continue;
450
451 if( line_type[0] == '[' ) // tags ignored reading legacy hotkeys
452 continue;
453
454 if( line_type == wxT( "$Endlist" ) )
455 break;
456
457 if( line_type != wxT( "shortcut" ) )
458 continue;
459
460 // Get the key name
461 lineTokenizer.SetString( lineTokenizer.GetString(), L"\"\r\n\t ", wxTOKEN_STRTOK );
462 wxString keyname = lineTokenizer.GetNextToken();
463
464 wxString remainder = lineTokenizer.GetString();
465
466 // Get the command name
467 wxString fctname = remainder.AfterFirst( '\"' ).BeforeFirst( '\"' );
468
469 // Add the pair to the map
470 aMap[ fctname.ToStdString() ] = KeyCodeFromKeyName( keyname );
471 }
472
473 // cleanup
474 cfgfile.Close();
475 return 1;
476 }
477
478
479