KiCad PCB EDA Suite
Loading...
Searching...
No Matches
widget_hotkey_list.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) 2016 Chris Pavlina <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 3
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <cctype>
22
23#include <confirm.h>
25#include <tool/tool_event.h>
26#include <dialog_shim.h>
27
28#include <wx/button.h>
29#include <wx/log.h>
30#include <wx/dcclient.h>
31#include <wx/menu.h>
32#include <wx/msgdlg.h>
33#include <wx/panel.h>
34#include <wx/sizer.h>
35#include <wx/statline.h>
36#include <wx/stattext.h>
37#include <wx/treelist.h>
38#include <wx/wupdlock.h>
39
52
53
60class WIDGET_HOTKEY_CLIENT_DATA : public wxClientData
61{
63
64public:
65 WIDGET_HOTKEY_CLIENT_DATA( HOTKEY& aChangedHotkey ) :
66 m_changed_hotkey( aChangedHotkey )
67 {}
68
70};
71
72
77{
78public:
79 HK_PROMPT_DIALOG( wxWindow* aParent, wxWindowID aId, const wxString& aTitle,
80 const wxString& aName, const wxString& aCurrentKey ) :
81 DIALOG_SHIM( aParent, aId, aTitle, wxDefaultPosition, wxDefaultSize )
82 {
83 wxBoxSizer* mainSizer = new wxBoxSizer( wxVERTICAL );
84 SetSizer( mainSizer );
85
86 /* Dialog layout:
87 *
88 * inst_label........................
89 * ----------------------------------
90 *
91 * cmd_label_0 cmd_label_1 \
92 * | fgsizer
93 * key_label_0 key_label_1 /
94 */
95
96 wxStaticText* inst_label = new wxStaticText( this, wxID_ANY, wxEmptyString,
97 wxDefaultPosition, wxDefaultSize,
98 wxALIGN_CENTRE_HORIZONTAL );
99
100 inst_label->SetLabelText( _( "Press a new hotkey, or press Esc to cancel..." ) );
101 mainSizer->Add( inst_label, 0, wxALL, 10 );
102
103 mainSizer->Add( new wxStaticLine( this ), 0, wxALL | wxEXPAND, 2 );
104
105 wxPanel* panelDisplayCurrent = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize );
106 mainSizer->Add( panelDisplayCurrent, 0, wxALL | wxEXPAND, 5 );
107
108 wxFlexGridSizer* fgsizer = new wxFlexGridSizer( 2 );
109 panelDisplayCurrent->SetSizer( fgsizer );
110
111 wxStaticText* cmd_label_0 = new wxStaticText( panelDisplayCurrent, wxID_ANY, _( "Command:" ) );
112 fgsizer->Add( cmd_label_0, 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5 );
113
114 wxStaticText* cmd_label_1 = new wxStaticText( panelDisplayCurrent, wxID_ANY, wxEmptyString );
115 cmd_label_1->SetFont( cmd_label_1->GetFont().Bold() );
116 cmd_label_1->SetLabel( aName );
117 fgsizer->Add( cmd_label_1, 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5 );
118
119 wxStaticText* key_label_0 = new wxStaticText( panelDisplayCurrent, wxID_ANY, _( "Current key:" ) );
120 fgsizer->Add( key_label_0, 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5 );
121
122 wxStaticText* key_label_1 = new wxStaticText( panelDisplayCurrent, wxID_ANY, wxEmptyString );
123 key_label_1->SetFont( key_label_1->GetFont().Bold() );
124 key_label_1->SetLabel( aCurrentKey );
125 fgsizer->Add( key_label_1, 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5 );
126
127 fgsizer->AddStretchSpacer();
128
129 wxButton* resetButton = new wxButton( this, wxID_ANY, _( "Clear assigned hotkey" ) );
130
131 mainSizer->Add( resetButton, 0, wxALL | wxALIGN_CENTRE_HORIZONTAL, 5 );
132
133 Layout();
134 mainSizer->Fit( this );
135 Center();
136
137 SetMinClientSize( GetClientSize() );
138
139 // Binding both EVT_CHAR and EVT_CHAR_HOOK ensures that all key events, including
140 // specials like Tab and Return, are received, particularly on MSW.
141 panelDisplayCurrent->Bind( wxEVT_CHAR, &HK_PROMPT_DIALOG::OnChar, this );
142 panelDisplayCurrent->Bind( wxEVT_CHAR_HOOK, &HK_PROMPT_DIALOG::OnCharHook, this );
143 panelDisplayCurrent->Bind( wxEVT_KEY_UP, &HK_PROMPT_DIALOG::OnKeyUp, this );
144
145 resetButton->Bind( wxEVT_COMMAND_BUTTON_CLICKED, &HK_PROMPT_DIALOG::onResetButton, this );
146
147 SetInitialFocus( panelDisplayCurrent );
148 }
149
150 static std::optional<long> PromptForKey( wxWindow* aParent, const wxString& aName,
151 const wxString& aCurrentKey )
152 {
153 HK_PROMPT_DIALOG dialog( aParent, wxID_ANY, _( "Set Hotkey" ), aName, aCurrentKey );
154
155 if( dialog.ShowModal() == wxID_OK )
156 {
157 if( dialog.m_resetkey )
158 {
159 return std::make_optional( 0 );
160 }
161 else
162 {
164
165 if( key )
166 return std::make_optional( key );
167 else // The ESC key was used to close the dialog
168 return std::nullopt;
169 }
170 }
171 else
172 {
173 return std::nullopt;
174 }
175 }
176
177protected:
178 void OnCharHook( wxKeyEvent& aEvent ) override
179 {
180 // On certain platforms, EVT_CHAR_HOOK is the only handler that receives certain
181 // "special" keys. However, it doesn't always receive "normal" keys correctly. For
182 // example, with a US keyboard, it sees ? as shift+/.
183 //
184 // Untangling these incorrect keys would be too much trouble, so we bind both events,
185 // and simply skip the EVT_CHAR_HOOK if it receives a "normal" key.
186
187 const enum wxKeyCode skipped_keys[] =
188 {
189 WXK_NONE, WXK_SHIFT, WXK_ALT, WXK_CONTROL, WXK_CAPITAL, WXK_NUMLOCK, WXK_SCROLL,
190 WXK_RAW_CONTROL
191 };
192
193 int key = aEvent.GetKeyCode();
194
195 for( wxKeyCode skipped_key : skipped_keys )
196 {
197 if( key == skipped_key )
198 return;
199 }
200
201 if( key <= 255 && isprint( key ) && !isspace( key ) )
202 {
203 // Let EVT_CHAR handle this one
204 aEvent.DoAllowNextEvent();
205
206#ifdef __WXMSW__
207 // On Windows, looks like a OnChar event is not generated when
208 // using the Alt key modifier. So ensure m_event is up to date
209 // to handle the right key code when releasing the key and therefore
210 // closing the dialog
211 m_event = aEvent;
212#endif
213 aEvent.Skip();
214 }
215 else
216 {
217 OnChar( aEvent );
218 }
219 }
220
221 void OnChar( wxKeyEvent& aEvent )
222 {
223 m_event = aEvent;
224 }
225
226 void OnKeyUp( wxKeyEvent& aEvent )
227 {
228 // If dialog opened using Enter key, prevent closing when releasing Enter.
229 if( m_event.GetEventType() != wxEVT_NULL )
230 {
232 wxPostEvent( this, wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
233 }
234 }
235
236 void onResetButton( wxCommandEvent& aEvent )
237 {
238 m_resetkey = true;
239 wxPostEvent( this, wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
240 }
241
242private:
243 bool m_resetkey = false;
244 wxKeyEvent m_event;
245};
246
247
252{
253public:
254 HOTKEY_FILTER( const wxString& aFilterStr )
255 {
256 m_normalised_filter_str = aFilterStr.Upper();
257 m_valid = m_normalised_filter_str.size() > 0;
258 }
259
265 bool FilterMatches( const HOTKEY& aHotkey ) const
266 {
267 if( !m_valid )
268 return true;
269
270 // Match in the (translated) filter string
271 const auto normedInfo = wxGetTranslation( aHotkey.m_Actions[0]->GetFriendlyName() ).Upper();
272
273 if( normedInfo.Contains( m_normalised_filter_str ) )
274 return true;
275
276 const wxString keyName = KeyNameFromKeyCode( aHotkey.m_EditKeycode );
277
278 if( keyName.Upper().Contains( m_normalised_filter_str ) )
279 return true;
280
281 return false;
282 }
283
284private:
287};
288
289
291{
292 if( aItem.IsOk() )
293 {
294 wxClientData* data = GetItemData( aItem );
295
296 if( data )
297 return static_cast<WIDGET_HOTKEY_CLIENT_DATA*>( data );
298 }
299
300 return nullptr;
301}
302
303
305{
306 for( wxTreeListItem i = GetFirstItem(); i.IsOk(); i = GetNextItem( i ) )
307 {
309
310 if( hkdata )
311 {
312 const HOTKEY& changed_hk = hkdata->GetChangedHotkey();
313 wxString label = changed_hk.m_Actions[ 0 ]->GetFriendlyName();
314 wxString key_text = KeyNameFromKeyCode( changed_hk.m_EditKeycode );
315 wxString alt_text = KeyNameFromKeyCode( changed_hk.m_EditKeycodeAlt );
316 wxString description = changed_hk.m_Actions[ 0 ]->GetDescription();
317
318 if( label.IsEmpty() )
319 label = changed_hk.m_Actions[ 0 ]->GetName();
320
321 label.Replace( wxT( "..." ), wxEmptyString );
322 label.Replace( wxT( "…" ), wxEmptyString );
323
324 // mark unsaved changes
325 if( changed_hk.m_EditKeycode != changed_hk.m_Actions[ 0 ]->GetHotKey() )
326 label += wxS( " *" );
327
328 description.Replace( wxS( "\n" ), wxS( " " ) );
329 description.Replace( wxS( "\r" ), wxS( " " ) );
330
331 SetItemText( i, 0, label );
332 SetItemText( i, 1, key_text );
333 SetItemText( i, 2, alt_text );
334 SetItemText( i, 3, description );
335 }
336 }
337}
338
339
340void WIDGET_HOTKEY_LIST::changeHotkey( HOTKEY& aHotkey, long aKey, bool alternate )
341{
342 int& keycode = alternate ? aHotkey.m_EditKeycodeAlt : aHotkey.m_EditKeycode;
343
344 if( keycode == aKey )
345 return;
346
347 if( aKey == 0 )
348 {
349 keycode = 0;
350 return;
351 }
352
353 // See if this key code is handled in hotkeys names list
354 bool exists;
355 KeyNameFromKeyCode( aKey, &exists );
356
357 if( exists && resolveKeyConflicts( aHotkey.m_Actions[0], aKey ) )
358 keycode = aKey;
359}
360
361
362bool WIDGET_HOTKEY_LIST::IsReservedHotkey( long aKey, wxString* aKeyName ) const
363{
364 auto it = m_reservedHotkeys.find( aKey );
365
366 if( it == m_reservedHotkeys.end() )
367 return false;
368
369 if( aKeyName )
370 *aKeyName = it->second;
371
372 return true;
373}
374
375
376void WIDGET_HOTKEY_LIST::editItem( wxTreeListItem aItem, int aEditId )
377{
379
380 if( !hkdata )
381 return;
382
383 wxString name = GetItemText( aItem, 0 );
384 wxString current_key = aEditId == ID_EDIT_HOTKEY ? GetItemText( aItem, 1 )
385 : GetItemText( aItem, 2 );
386
387 std::optional<long> key = HK_PROMPT_DIALOG::PromptForKey( this, name, current_key );
388
389 // An empty optional means don't change the key
390 if( key.has_value() )
391 {
392 wxString reservedKeyName;
393
394 if( IsReservedHotkey( key.value(), &reservedKeyName ) )
395 {
396 wxString msg = wxString::Format( _( "'%s' is a reserved hotkey in KiCad and cannot "
397 "be assigned." ),
398 reservedKeyName );
399
400 DisplayErrorMessage( this, msg );
401 return;
402 }
403
404 changeHotkey( hkdata->GetChangedHotkey(), key.value(), aEditId == ID_EDIT_ALT );
406 }
407}
408
409
410void WIDGET_HOTKEY_LIST::resetItem( wxTreeListItem aItem, int aResetId )
411{
413
414 if( !hkdata )
415 return;
416
417 HOTKEY& changed_hk = hkdata->GetChangedHotkey();
418
419 if( aResetId == ID_RESET )
420 {
421 changeHotkey( changed_hk, changed_hk.m_Actions[0]->GetHotKey(), false );
422 changeHotkey( changed_hk, changed_hk.m_Actions[0]->GetHotKeyAlt(), true );
423 }
424 else if( aResetId == ID_CLEAR )
425 {
426 changeHotkey( changed_hk, 0, false );
427 }
428 else if( aResetId == ID_CLEAR_ALT )
429 {
430 changeHotkey( changed_hk, 0, true );
431 }
432 else if( aResetId == ID_DEFAULT )
433 {
434 changeHotkey( changed_hk, changed_hk.m_Actions[0]->GetDefaultHotKey(), false );
435 changeHotkey( changed_hk, changed_hk.m_Actions[0]->GetDefaultHotKeyAlt(), true );
436 }
437
439}
440
441
442void WIDGET_HOTKEY_LIST::onActivated( wxTreeListEvent& aEvent )
443{
444 editItem( aEvent.GetItem(), ID_EDIT_HOTKEY );
445}
446
447
448void WIDGET_HOTKEY_LIST::onContextMenu( wxTreeListEvent& aEvent )
449{
450 // Save the active event for use in OnMenu
451 m_context_menu_item = aEvent.GetItem();
452
453 wxMenu menu;
454
456
457 // Some actions only apply if the row is hotkey data
458 if( hkdata )
459 {
460 menu.Append( ID_EDIT_HOTKEY, _( "Edit..." ) );
461 menu.Append( ID_EDIT_ALT, _( "Edit Alternate..." ) );
462 menu.Append( ID_RESET, _( "Undo Changes" ) );
463 menu.Append( ID_CLEAR, _( "Clear Assigned Hotkey" ) );
464 menu.Append( ID_CLEAR_ALT, _( "Clear Assigned Alternate" ) );
465 menu.Append( ID_DEFAULT, _( "Restore Defaults" ) );
466 menu.Append( wxID_SEPARATOR );
467
468 PopupMenu( &menu );
469 }
470}
471
472
473void WIDGET_HOTKEY_LIST::onMenu( wxCommandEvent& aEvent )
474{
475 switch( aEvent.GetId() )
476 {
477 case ID_EDIT_HOTKEY:
478 case ID_EDIT_ALT:
479 editItem( m_context_menu_item, aEvent.GetId() );
480 break;
481
482 case ID_RESET:
483 case ID_CLEAR:
484 case ID_CLEAR_ALT:
485 case ID_DEFAULT:
486 resetItem( m_context_menu_item, aEvent.GetId() );
487 break;
488
489 default:
490 wxFAIL_MSG( wxT( "Unknown ID in context menu event" ) );
491 }
492}
493
494
495void WIDGET_HOTKEY_LIST::onCharHook( wxKeyEvent& aEvent )
496{
497 // On macOS with international keyboards, "dead keys" (like backtick or circumflex on
498 // French keyboards) are used for composing accented characters. When pressed on a
499 // wxTreeListCtrl, these can trigger IME composition handling that the control doesn't
500 // support, causing a crash.
501 //
502 // Per wxWidgets documentation: if both GetUnicodeKey() and GetKeyCode() return WXK_NONE,
503 // the key has no WXK_xxx mapping (e.g., dead keys). We filter these out to prevent crashes.
504 // Navigation keys (arrows, function keys, etc.) have valid GetKeyCode() values even when
505 // GetUnicodeKey() returns WXK_NONE, so they pass through correctly.
506 if( aEvent.GetUnicodeKey() == WXK_NONE && aEvent.GetKeyCode() == WXK_NONE )
507 return;
508
509 aEvent.Skip();
510}
511
512
514{
515 HOTKEY* conflictingHotKey = nullptr;
516
517 m_hk_store.CheckKeyConflicts( aAction, aKey, &conflictingHotKey );
518
519 if( !conflictingHotKey )
520 return true;
521
522 TOOL_ACTION* conflictingAction = conflictingHotKey->m_Actions[ 0 ];
523 wxString msg = wxString::Format( _( "'%s' is already assigned to '%s' in section '%s'. "
524 "Both bindings are kept, but only one runs per key "
525 "press. Continue?" ),
526 KeyNameFromKeyCode( aKey ), conflictingAction->GetFriendlyName(),
527 HOTKEY_STORE::GetSectionName( conflictingAction ) );
528
529 KICAD_MESSAGE_DIALOG dlg( GetParent(), msg, _( "Hotkey conflict" ), wxYES_NO | wxNO_DEFAULT );
530
531 return dlg.ShowModal() == wxID_YES;
532}
533
534
535WIDGET_HOTKEY_LIST::WIDGET_HOTKEY_LIST( wxWindow* aParent, HOTKEY_STORE& aHotkeyStore, bool readOnly ) :
536 wxTreeListCtrl( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTL_SINGLE ),
537 m_hk_store( aHotkeyStore )
538{
539 wxString command_header;
540
541 if( readOnly )
542 command_header = _( "Command" );
543 else
544 command_header = _( "Command (double-click to edit)" );
545
546 AppendColumn( command_header, 450, wxALIGN_LEFT, wxCOL_RESIZABLE | wxCOL_SORTABLE );
547 AppendColumn( _( "Hotkey" ), 120, wxALIGN_LEFT, wxCOL_RESIZABLE | wxCOL_SORTABLE );
548 AppendColumn( _( "Alternate" ), 120, wxALIGN_LEFT, wxCOL_RESIZABLE | wxCOL_SORTABLE );
549 AppendColumn( _( "Description" ), 900, wxALIGN_LEFT, wxCOL_RESIZABLE | wxCOL_SORTABLE );
550
551
552#if defined( __WXGTK__ )// && !wxCHECK_VERSION( 3, 1, 0 )
553 // Automatic column widths are broken in wxGTK 3.0.x; set min widths to ensure visibility
554 // They are also broken in wxGTK 3.1.4
555
556 wxDataViewCtrl* dv = GetDataView();
557
558 wxString longKey = wxT( "Ctrl+Alt+Shift+X" );
559 int pad = 20;
560
561 dv->GetColumn( 0 )->SetMinWidth( aParent->GetTextExtent( command_header ).x * 2 + pad );
562 dv->GetColumn( 1 )->SetMinWidth( aParent->GetTextExtent( longKey ).x + pad );
563 dv->GetColumn( 2 )->SetMinWidth( aParent->GetTextExtent( longKey ).x + pad );
564 dv->GetColumn( 3 )->SetMinWidth( aParent->GetTextExtent( command_header ).x * 5 + pad );
565
566 CallAfter( [this]()
567 {
568 GetDataView()->Update();
569 } );
570#endif
571
572 std::vector<wxString> reserved_keys =
573 {
574 wxS( "Ctrl+Tab" ),
575 wxS( "Ctrl+Shift+Tab" )
576 };
577
578 for( const wxString& key : reserved_keys )
579 {
580 long code = KeyCodeFromKeyName( key );
581
582 if( code )
583 m_reservedHotkeys[code] = key;
584 else
585 wxLogWarning( wxS( "Unknown reserved keycode %s\n" ), key );
586 }
587
588 GetDataView()->SetIndent( 10 );
589
590 // The event only apply if the widget is in editable mode
591 if( !readOnly )
592 {
593 Bind( wxEVT_TREELIST_ITEM_ACTIVATED, &WIDGET_HOTKEY_LIST::onActivated, this );
594 Bind( wxEVT_TREELIST_ITEM_CONTEXT_MENU, &WIDGET_HOTKEY_LIST::onContextMenu, this );
595 Bind( wxEVT_MENU, &WIDGET_HOTKEY_LIST::onMenu, this );
596 }
597
598 // Filter out dead keys that can crash on macOS with international keyboards
599 Bind( wxEVT_CHAR_HOOK, &WIDGET_HOTKEY_LIST::onCharHook, this );
600}
601
602
603void WIDGET_HOTKEY_LIST::ApplyFilterString( const wxString& aFilterStr )
604{
605 updateShownItems( aFilterStr );
606}
607
608
609void WIDGET_HOTKEY_LIST::ResetAllHotkeys( bool aResetToDefault )
610{
611 wxWindowUpdateLocker updateLock( this );
612
613 // Reset all the hotkeys, not just the ones shown
614 // Should not need to check conflicts, as the state we're about
615 // to set to a should be consistent
616 if( aResetToDefault )
617 m_hk_store.ResetAllHotkeysToDefault();
618 else
619 m_hk_store.ResetAllHotkeysToOriginal();
620
623}
624
625
627{
628 updateShownItems( "" );
630
631 return true;
632}
633
634
636{
637 wxDataViewColumn* col = GetDataView()->GetColumn( 0 );
638 col->SetWidth( wxCOL_WIDTH_AUTOSIZE );
639 col->SetWidth( col->GetWidth() );
640
641 col = GetDataView()->GetColumn( 1 );
642 col->SetWidth( wxCOL_WIDTH_AUTOSIZE );
643 col->SetWidth( col->GetWidth() );
644
645 col = GetDataView()->GetColumn( 2 );
646 col->SetWidth( wxCOL_WIDTH_AUTOSIZE );
647 col->SetWidth( col->GetWidth() );
648
649 col = GetDataView()->GetColumn( 3 );
650 col->SetWidth( wxCOL_WIDTH_AUTOSIZE );
651 col->SetWidth( col->GetWidth() );
652}
653
654
655void WIDGET_HOTKEY_LIST::updateShownItems( const wxString& aFilterStr )
656{
657 wxWindowUpdateLocker updateLock( this );
658
659 DeleteAllItems();
660
661 HOTKEY_FILTER filter( aFilterStr );
662
663 for( HOTKEY_SECTION& section: m_hk_store.GetSections() )
664 {
665 // Create parent tree item
666 wxTreeListItem parent = AppendItem( GetRootItem(), section.m_SectionName );
667
668 for( HOTKEY& hotkey: section.m_HotKeys )
669 {
670 if( filter.FilterMatches( hotkey ) )
671 {
672 wxTreeListItem item = AppendItem( parent, wxEmptyString );
673 SetItemData( item, new WIDGET_HOTKEY_CLIENT_DATA( hotkey ) );
674 }
675 }
676
677 Expand( parent );
678 }
679
681}
682
683
685{
686 m_hk_store.SaveAllHotkeys();
687 return true;
688}
689
690
691long WIDGET_HOTKEY_LIST::MapKeypressToKeycode( const wxKeyEvent& aEvent )
692{
693 long key = aEvent.GetKeyCode();
694 bool is_tab = aEvent.IsKeyInCategory( WXK_CATEGORY_TAB );
695
696 if( key == WXK_ESCAPE )
697 {
698 return 0;
699 }
700 else
701 {
702 if( key >= 'a' && key <= 'z' ) // convert to uppercase
703 key = key + ('A' - 'a');
704
705 // Remap Ctrl A (=1+GR_KB_CTRL) to Ctrl Z(=26+GR_KB_CTRL)
706 // to GR_KB_CTRL+'A' .. GR_KB_CTRL+'Z'
707 if( !is_tab && aEvent.ControlDown() && key >= WXK_CONTROL_A && key <= WXK_CONTROL_Z )
708 key += 'A' - 1;
709
710 /* Disallow shift for keys that have two keycodes on them (e.g. number and
711 * punctuation keys) leaving only the "letter keys" of A-Z, tab and space
712 * Then, you can have, e.g. Ctrl-5 and Ctrl-% (GB layout)
713 * and Ctrl-( and Ctrl-5 (FR layout).
714 * Otherwise, you'd have to have to say Ctrl-Shift-5 on a FR layout
715 */
716 bool keyIsLetter = key >= 'A' && key <= 'Z';
717
718 int mods = aEvent.GetModifiers();
719
720 if( ( mods & wxMOD_SHIFT ) && ( keyIsLetter || key > 256 || key == 9 || key == 32 ) )
721 key |= MD_SHIFT;
722
723 // the flag wxMOD_ALTGR is defined in wxWidgets as wxMOD_CONTROL|wxMOD_ALT
724 // So AltGr key cannot used as modifier key because it is the same as Alt key + Ctrl key.
725 #if CAN_USE_ALTGR_KEY
726 if( wxmods & wxMOD_ALTGR )
727 mods |= MD_ALTGR;
728 else
729 #endif
730 {
731 if( mods & wxMOD_CONTROL )
732 key |= MD_CTRL;
733
734 if( mods & wxMOD_ALT )
735 key |= MD_ALT;
736 }
737
738#ifdef wxMOD_META
739 if( mods & wxMOD_META )
740 key |= MD_META;
741#endif
742
743#ifdef wxMOD_WIN
744 if( mods & wxMOD_WIN )
745 key |= MD_SUPER;
746#endif
747
748 return key;
749 }
750}
const char * name
void SetInitialFocus(wxWindow *aWindow)
Sets the window (usually a wxTextCtrl) that should be focused when the dialog is shown.
Definition dialog_shim.h:94
DIALOG_SHIM(wxWindow *aParent, wxWindowID id, const wxString &title, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxDEFAULT_FRAME_STYLE|wxRESIZE_BORDER, const wxString &name=wxDialogNameStr)
int ShowModal() override
static std::optional< long > PromptForKey(wxWindow *aParent, const wxString &aName, const wxString &aCurrentKey)
void OnCharHook(wxKeyEvent &aEvent) override
HK_PROMPT_DIALOG(wxWindow *aParent, wxWindowID aId, const wxString &aTitle, const wxString &aName, const wxString &aCurrentKey)
void OnKeyUp(wxKeyEvent &aEvent)
void onResetButton(wxCommandEvent &aEvent)
void OnChar(wxKeyEvent &aEvent)
Manage logic for filtering hotkeys based on user input.
wxString m_normalised_filter_str
bool FilterMatches(const HOTKEY &aHotkey) const
Check if the filter matches the given hotkey.
HOTKEY_FILTER(const wxString &aFilterStr)
A class that contains a set of hotkeys, arranged into "sections" and provides some book-keeping funct...
static wxString GetSectionName(TOOL_ACTION *aAction)
Represent a single user action.
wxString GetFriendlyName() const
Return the translated user-friendly name of the action.
Store the hotkey change data associated with each row.
WIDGET_HOTKEY_CLIENT_DATA(HOTKEY &aChangedHotkey)
void ResetAllHotkeys(bool aResetToDefault)
Set hotkeys in the control to default or original values.
void onActivated(wxTreeListEvent &aEvent)
Handle activation of a row.
void updateShownItems(const wxString &aFilterStr)
Update the items shown in the widget based on a given filter string.
bool IsReservedHotkey(long aKey, wxString *aKeyName=nullptr) const
void changeHotkey(HOTKEY &aHotkey, long aKey, bool alternate)
Attempt to change the given hotkey to the given key code.
void editItem(wxTreeListItem aItem, int aEditId)
Prompt the user for a new hotkey given a list item.
bool TransferDataToControl()
Load the hotkey data from the store into the control.
void onMenu(wxCommandEvent &aEvent)
Handle activation of a context menu item.
wxTreeListItem m_context_menu_item
bool TransferDataFromControl()
Save the hotkey data from the control.
void updateColumnWidths()
Recalculate column widths after model has changed.
void ApplyFilterString(const wxString &aFilterStr)
Apply a filter string to the hotkey list, selecting which hotkeys to show.
void updateFromClientData()
Refresh the visible text on the widget from the rows' client data objects.
void resetItem(wxTreeListItem aItem, int aResetId)
Reset the item to either the default, the value when the dialog was opened, or none.
WIDGET_HOTKEY_CLIENT_DATA * getHKClientData(wxTreeListItem aItem)
Return the WIDGET_HOTKEY_CLIENT_DATA for the given item, or NULL if the item is invalid.
void onContextMenu(wxTreeListEvent &aEvent)
Handle right-click on a row.
static long MapKeypressToKeycode(const wxKeyEvent &aEvent)
Map a keypress event to the correct key code for use as a hotkey.
WIDGET_HOTKEY_LIST(wxWindow *aParent, HOTKEY_STORE &aHotkeyStore, bool readOnly)
Create a WIDGET_HOTKEY_LIST.
void onCharHook(wxKeyEvent &aEvent)
Handle key events to filter out dead keys that can crash on macOS.
HOTKEY_STORE & m_hk_store
std::unordered_map< long, wxString > m_reservedHotkeys
bool resolveKeyConflicts(TOOL_ACTION *aAction, long aKey)
Check if we can set a hotkey, and prompt the user if there is a conflict between keys.
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:217
This file is part of the common library.
#define KICAD_MESSAGE_DIALOG
Definition confirm.h:48
#define _(s)
wxString KeyNameFromKeyCode(int aKeycode, bool *aIsFound)
Return the key name from the key code.
int KeyCodeFromKeyName(const wxString &keyname)
Return the key code from its user-friendly key name (ie: "Ctrl+M").
int m_EditKeycodeAlt
int m_EditKeycode
std::vector< TOOL_ACTION * > m_Actions
@ MD_META
Definition tool_event.h:143
@ MD_ALT
Definition tool_event.h:141
@ MD_CTRL
Definition tool_event.h:140
@ MD_SUPER
Definition tool_event.h:142
@ MD_ALTGR
Definition tool_event.h:144
@ MD_SHIFT
Definition tool_event.h:139
ID_WHKL_MENU_IDS
Menu IDs for the hotkey context menu.
@ ID_EDIT_ALT
@ ID_CLEAR_ALT
@ ID_EDIT_HOTKEY