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