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, const wxString& aName,
80 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, wxDefaultPosition, wxDefaultSize,
97 wxALIGN_CENTRE_HORIZONTAL );
98
99 inst_label->SetLabelText( _( "Press a new hotkey, or press Esc to cancel..." ) );
100 mainSizer->Add( inst_label, 0, wxALL, 10 );
101
102 mainSizer->Add( new wxStaticLine( this ), 0, wxALL | wxEXPAND, 2 );
103
104 wxPanel* panelDisplayCurrent = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize );
105 mainSizer->Add( panelDisplayCurrent, 0, wxALL | wxEXPAND, 5 );
106
107 wxFlexGridSizer* fgsizer = new wxFlexGridSizer( 2 );
108 panelDisplayCurrent->SetSizer( fgsizer );
109
110 wxStaticText* cmd_label_0 = new wxStaticText( panelDisplayCurrent, wxID_ANY, _( "Command:" ) );
111 fgsizer->Add( cmd_label_0, 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5 );
112
113 wxStaticText* cmd_label_1 = new wxStaticText( panelDisplayCurrent, wxID_ANY, wxEmptyString );
114 cmd_label_1->SetFont( cmd_label_1->GetFont().Bold() );
115 cmd_label_1->SetLabel( aName );
116 fgsizer->Add( cmd_label_1, 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5 );
117
118 wxStaticText* key_label_0 = new wxStaticText( panelDisplayCurrent, wxID_ANY, _( "Current key:" ) );
119 fgsizer->Add( key_label_0, 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5 );
120
121 wxStaticText* key_label_1 = new wxStaticText( panelDisplayCurrent, wxID_ANY, wxEmptyString );
122 key_label_1->SetFont( key_label_1->GetFont().Bold() );
123 key_label_1->SetLabel( aCurrentKey );
124 fgsizer->Add( key_label_1, 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5 );
125
126 fgsizer->AddStretchSpacer();
127
128 wxButton* resetButton = new wxButton( this, wxID_ANY, _( "Clear assigned hotkey" ) );
129
130 mainSizer->Add( resetButton, 0, wxALL | wxALIGN_CENTRE_HORIZONTAL, 5 );
131
132 Layout();
133 mainSizer->Fit( this );
134 Center();
135
136 SetMinClientSize( GetClientSize() );
137
138 // Binding both EVT_CHAR and EVT_CHAR_HOOK ensures that all key events, including
139 // specials like Tab and Return, are received, particularly on MSW.
140 panelDisplayCurrent->Bind( wxEVT_CHAR, &HK_PROMPT_DIALOG::OnChar, this );
141 panelDisplayCurrent->Bind( wxEVT_CHAR_HOOK, &HK_PROMPT_DIALOG::OnCharHook, this );
142 panelDisplayCurrent->Bind( wxEVT_KEY_UP, &HK_PROMPT_DIALOG::OnKeyUp, this );
143
144 resetButton->Bind( wxEVT_COMMAND_BUTTON_CLICKED, &HK_PROMPT_DIALOG::onResetButton, this );
145
146 SetInitialFocus( panelDisplayCurrent );
147 }
148
149 static std::optional<long> PromptForKey( wxWindow* aParent, const wxString& aName, const wxString& aCurrentKey )
150 {
151 HK_PROMPT_DIALOG dialog( aParent, wxID_ANY, _( "Set Hotkey" ), aName, aCurrentKey );
152
153 if( dialog.ShowModal() == wxID_OK )
154 {
155 if( dialog.m_resetkey )
156 {
157 return std::make_optional( 0 );
158 }
159 else
160 {
162
163 if( key )
164 return std::make_optional( key );
165 else // The ESC key was used to close the dialog
166 return std::nullopt;
167 }
168 }
169 else
170 {
171 return std::nullopt;
172 }
173 }
174
175protected:
176 void OnCharHook( wxKeyEvent& aEvent ) override
177 {
178 // On certain platforms, EVT_CHAR_HOOK is the only handler that receives certain
179 // "special" keys. However, it doesn't always receive "normal" keys correctly. For
180 // example, with a US keyboard, it sees ? as shift+/.
181 //
182 // Untangling these incorrect keys would be too much trouble, so we bind both events,
183 // and simply skip the EVT_CHAR_HOOK if it receives a "normal" key.
184
185 const enum wxKeyCode skipped_keys[] =
186 {
187 WXK_NONE, WXK_SHIFT, WXK_ALT, WXK_CONTROL, WXK_CAPITAL, WXK_NUMLOCK, WXK_SCROLL,
188 WXK_RAW_CONTROL
189 };
190
191 int key = aEvent.GetKeyCode();
192
193 for( wxKeyCode skipped_key : skipped_keys )
194 {
195 if( key == skipped_key )
196 return;
197 }
198
199 if( key <= 255 && isprint( key ) && !isspace( key ) )
200 {
201 // Let EVT_CHAR handle this one
202 aEvent.DoAllowNextEvent();
203
204#ifdef __WXMSW__
205 // On Windows, looks like a OnChar event is not generated when
206 // using the Alt key modifier. So ensure m_event is up to date
207 // to handle the right key code when releasing the key and therefore
208 // closing the dialog
209 m_event = aEvent;
210#endif
211 aEvent.Skip();
212 }
213 else
214 {
215 OnChar( aEvent );
216 }
217 }
218
219 void OnChar( wxKeyEvent& aEvent )
220 {
221 m_event = aEvent;
222 }
223
224 void OnKeyUp( wxKeyEvent& aEvent )
225 {
226 // If dialog opened using Enter key, prevent closing when releasing Enter.
227 if( m_event.GetEventType() != wxEVT_NULL )
228 {
230 wxPostEvent( this, wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
231 }
232 }
233
234 void onResetButton( wxCommandEvent& aEvent )
235 {
236 m_resetkey = true;
237 wxPostEvent( this, wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
238 }
239
240private:
241 bool m_resetkey = false;
242 wxKeyEvent m_event;
243};
244
245
250{
251public:
252 HOTKEY_FILTER( const wxString& aFilterStr )
253 {
254 m_normalised_filter_str = aFilterStr.Upper();
255 m_valid = m_normalised_filter_str.size() > 0;
256 }
257
263 bool FilterMatches( const HOTKEY& aHotkey ) const
264 {
265 if( !m_valid )
266 return true;
267
268 // Match in the (translated) filter string
269 const auto normedInfo = wxGetTranslation( aHotkey.m_Actions[0]->GetFriendlyName() ).Upper();
270
271 if( normedInfo.Contains( m_normalised_filter_str ) )
272 return true;
273
274 const wxString keyName = KeyNameFromKeyCode( aHotkey.m_EditKeycode );
275
276 if( keyName.Upper().Contains( m_normalised_filter_str ) )
277 return true;
278
279 return false;
280 }
281
282private:
285};
286
287
289{
290 if( aItem.IsOk() )
291 {
292 wxClientData* data = GetItemData( aItem );
293
294 if( data )
295 return static_cast<WIDGET_HOTKEY_CLIENT_DATA*>( data );
296 }
297
298 return nullptr;
299}
300
301
303{
304 for( wxTreeListItem i = GetFirstItem(); i.IsOk(); i = GetNextItem( i ) )
305 {
307
308 if( hkdata )
309 {
310 const HOTKEY& changed_hk = hkdata->GetChangedHotkey();
311 wxString label = changed_hk.m_Actions[ 0 ]->GetFriendlyName();
312 wxString key_text = KeyNameFromKeyCode( changed_hk.m_EditKeycode );
313 wxString alt_text = KeyNameFromKeyCode( changed_hk.m_EditKeycodeAlt );
314 wxString description = changed_hk.m_Actions[ 0 ]->GetDescription();
315
316 if( label.IsEmpty() )
317 label = changed_hk.m_Actions[ 0 ]->GetName();
318
319 label.Replace( wxT( "..." ), wxEmptyString );
320 label.Replace( wxT( "…" ), wxEmptyString );
321
322 // mark unsaved changes
323 if( changed_hk.m_EditKeycode != changed_hk.m_Actions[ 0 ]->GetHotKey() )
324 label += wxS( " *" );
325
326 description.Replace( wxS( "\n" ), wxS( " " ) );
327 description.Replace( wxS( "\r" ), wxS( " " ) );
328
329 SetItemText( i, 0, label );
330 SetItemText( i, 1, key_text );
331 SetItemText( i, 2, alt_text );
332 SetItemText( i, 3, description );
333 }
334 }
335}
336
337
338void WIDGET_HOTKEY_LIST::changeHotkey( HOTKEY& aHotkey, long aKey, bool alternate )
339{
340 int& keycode = alternate ? aHotkey.m_EditKeycodeAlt : aHotkey.m_EditKeycode;
341
342 if( keycode == aKey )
343 return;
344
345 if( aKey == 0 )
346 {
347 keycode = 0;
348 return;
349 }
350
351 // See if this key code is handled in hotkeys names list
352 bool exists;
353 KeyNameFromKeyCode( aKey, &exists );
354
355 if( exists && resolveKeyConflicts( aHotkey.m_Actions[0], aKey ) )
356 keycode = aKey;
357}
358
359
360bool WIDGET_HOTKEY_LIST::IsReservedHotkey( long aKey, wxString* aKeyName ) const
361{
362 auto it = m_reservedHotkeys.find( aKey );
363
364 if( it == m_reservedHotkeys.end() )
365 return false;
366
367 if( aKeyName )
368 *aKeyName = it->second;
369
370 return true;
371}
372
373
374void WIDGET_HOTKEY_LIST::editItem( wxTreeListItem aItem, int aEditId )
375{
377
378 if( !hkdata )
379 return;
380
381 wxString name = GetItemText( aItem, 0 );
382 wxString current_key = aEditId == ID_EDIT_HOTKEY ? GetItemText( aItem, 1 )
383 : GetItemText( aItem, 2 );
384
385 std::optional<long> key = HK_PROMPT_DIALOG::PromptForKey( this, name, current_key );
386
387 // An empty optional means don't change the key
388 if( key.has_value() )
389 {
390 wxString reservedKeyName;
391
392 if( IsReservedHotkey( key.value(), &reservedKeyName ) )
393 {
394 wxString msg = wxString::Format( _( "'%s' is a reserved hotkey in KiCad and cannot be assigned." ),
395 reservedKeyName );
396
397 DisplayErrorMessage( this, msg );
398 return;
399 }
400
401 changeHotkey( hkdata->GetChangedHotkey(), key.value(), aEditId == ID_EDIT_ALT );
403 }
404}
405
406
407void WIDGET_HOTKEY_LIST::resetItem( wxTreeListItem aItem, int aResetId )
408{
410
411 if( !hkdata )
412 return;
413
414 HOTKEY& changed_hk = hkdata->GetChangedHotkey();
415
416 if( aResetId == ID_RESET )
417 {
418 changeHotkey( changed_hk, changed_hk.m_Actions[0]->GetHotKey(), false );
419 changeHotkey( changed_hk, changed_hk.m_Actions[0]->GetHotKeyAlt(), true );
420 }
421 else if( aResetId == ID_CLEAR )
422 {
423 changeHotkey( changed_hk, 0, false );
424 }
425 else if( aResetId == ID_CLEAR_ALT )
426 {
427 changeHotkey( changed_hk, 0, true );
428 }
429 else if( aResetId == ID_DEFAULT )
430 {
431 changeHotkey( changed_hk, changed_hk.m_Actions[0]->GetDefaultHotKey(), false );
432 changeHotkey( changed_hk, changed_hk.m_Actions[0]->GetDefaultHotKeyAlt(), true );
433 }
434
436}
437
438
439void WIDGET_HOTKEY_LIST::onActivated( wxTreeListEvent& aEvent )
440{
441 editItem( aEvent.GetItem(), ID_EDIT_HOTKEY );
442}
443
444
445void WIDGET_HOTKEY_LIST::onContextMenu( wxTreeListEvent& aEvent )
446{
447 // Save the active event for use in OnMenu
448 m_context_menu_item = aEvent.GetItem();
449
450 wxMenu menu;
451
453
454 // Some actions only apply if the row is hotkey data
455 if( hkdata )
456 {
457 menu.Append( ID_EDIT_HOTKEY, _( "Edit..." ) );
458 menu.Append( ID_EDIT_ALT, _( "Edit Alternate..." ) );
459 menu.Append( ID_RESET, _( "Undo Changes" ) );
460 menu.Append( ID_CLEAR, _( "Clear Assigned Hotkey" ) );
461 menu.Append( ID_CLEAR_ALT, _( "Clear Assigned Alternate" ) );
462 menu.Append( ID_DEFAULT, _( "Restore Defaults" ) );
463 menu.Append( wxID_SEPARATOR );
464
465 PopupMenu( &menu );
466 }
467}
468
469
470void WIDGET_HOTKEY_LIST::onMenu( wxCommandEvent& aEvent )
471{
472 switch( aEvent.GetId() )
473 {
474 case ID_EDIT_HOTKEY:
475 case ID_EDIT_ALT:
476 editItem( m_context_menu_item, aEvent.GetId() );
477 break;
478
479 case ID_RESET:
480 case ID_CLEAR:
481 case ID_CLEAR_ALT:
482 case ID_DEFAULT:
483 resetItem( m_context_menu_item, aEvent.GetId() );
484 break;
485
486 default:
487 wxFAIL_MSG( wxT( "Unknown ID in context menu event" ) );
488 }
489}
490
491
492void WIDGET_HOTKEY_LIST::onCharHook( wxKeyEvent& aEvent )
493{
494 // On macOS with international keyboards, "dead keys" (like backtick or circumflex on
495 // French keyboards) are used for composing accented characters. When pressed on a
496 // wxTreeListCtrl, these can trigger IME composition handling that the control doesn't
497 // support, causing a crash.
498 //
499 // Per wxWidgets documentation: if both GetUnicodeKey() and GetKeyCode() return WXK_NONE,
500 // the key has no WXK_xxx mapping (e.g., dead keys). We filter these out to prevent crashes.
501 // Navigation keys (arrows, function keys, etc.) have valid GetKeyCode() values even when
502 // GetUnicodeKey() returns WXK_NONE, so they pass through correctly.
503 if( aEvent.GetUnicodeKey() == WXK_NONE && aEvent.GetKeyCode() == WXK_NONE )
504 return;
505
506 aEvent.Skip();
507}
508
509
511{
512 HOTKEY* conflictingHotKey = nullptr;
513
514 m_hk_store.CheckKeyConflicts( aAction, aKey, &conflictingHotKey );
515
516 if( !conflictingHotKey )
517 return true;
518
519 TOOL_ACTION* conflictingAction = conflictingHotKey->m_Actions[ 0 ];
520 wxString msg = wxString::Format( _( "'%s' is already assigned to '%s' in section '%s'. Both "
521 "bindings will be kept, but only one will run per key press. "
522 "Continue?" ),
523 KeyNameFromKeyCode( aKey ), conflictingAction->GetFriendlyName(),
524 HOTKEY_STORE::GetSectionName( conflictingAction ) );
525
526 KICAD_MESSAGE_DIALOG dlg( GetParent(), msg, _( "Hotkey conflict" ), wxYES_NO | wxNO_DEFAULT );
527
528 return dlg.ShowModal() == wxID_YES;
529}
530
531
532WIDGET_HOTKEY_LIST::WIDGET_HOTKEY_LIST( wxWindow* aParent, HOTKEY_STORE& aHotkeyStore, bool readOnly ) :
533 wxTreeListCtrl( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTL_SINGLE ),
534 m_hk_store( aHotkeyStore )
535{
536 wxString command_header;
537
538 if( readOnly )
539 command_header = _( "Command" );
540 else
541 command_header = _( "Command (double-click to edit)" );
542
543 AppendColumn( command_header, 450, wxALIGN_LEFT, wxCOL_RESIZABLE | wxCOL_SORTABLE );
544 AppendColumn( _( "Hotkey" ), 120, wxALIGN_LEFT, wxCOL_RESIZABLE | wxCOL_SORTABLE );
545 AppendColumn( _( "Alternate" ), 120, wxALIGN_LEFT, wxCOL_RESIZABLE | wxCOL_SORTABLE );
546 AppendColumn( _( "Description" ), 900, wxALIGN_LEFT, wxCOL_RESIZABLE | wxCOL_SORTABLE );
547
548
549#if defined( __WXGTK__ )// && !wxCHECK_VERSION( 3, 1, 0 )
550 // Automatic column widths are broken in wxGTK 3.0.x; set min widths to ensure visibility
551 // They are also broken in wxGTK 3.1.4
552
553 wxDataViewCtrl* dv = GetDataView();
554
555 wxString longKey = wxT( "Ctrl+Alt+Shift+X" );
556 int pad = 20;
557
558 dv->GetColumn( 0 )->SetMinWidth( aParent->GetTextExtent( command_header ).x * 2 + pad );
559 dv->GetColumn( 1 )->SetMinWidth( aParent->GetTextExtent( longKey ).x + pad );
560 dv->GetColumn( 2 )->SetMinWidth( aParent->GetTextExtent( longKey ).x + pad );
561 dv->GetColumn( 3 )->SetMinWidth( aParent->GetTextExtent( command_header ).x * 5 + pad );
562
563 CallAfter( [this]()
564 {
565 GetDataView()->Update();
566 } );
567#endif
568
569 std::vector<wxString> reserved_keys =
570 {
571 wxS( "Ctrl+Tab" ),
572 wxS( "Ctrl+Shift+Tab" )
573 };
574
575 for( const wxString& key : reserved_keys )
576 {
577 long code = KeyCodeFromKeyName( key );
578
579 if( code )
580 m_reservedHotkeys[code] = key;
581 else
582 wxLogWarning( wxS( "Unknown reserved keycode %s\n" ), key );
583 }
584
585 GetDataView()->SetIndent( 10 );
586
587 // The event only apply if the widget is in editable mode
588 if( !readOnly )
589 {
590 Bind( wxEVT_TREELIST_ITEM_ACTIVATED, &WIDGET_HOTKEY_LIST::onActivated, this );
591 Bind( wxEVT_TREELIST_ITEM_CONTEXT_MENU, &WIDGET_HOTKEY_LIST::onContextMenu, this );
592 Bind( wxEVT_MENU, &WIDGET_HOTKEY_LIST::onMenu, this );
593 }
594
595 // Filter out dead keys that can crash on macOS with international keyboards
596 Bind( wxEVT_CHAR_HOOK, &WIDGET_HOTKEY_LIST::onCharHook, this );
597}
598
599
600void WIDGET_HOTKEY_LIST::ApplyFilterString( const wxString& aFilterStr )
601{
602 updateShownItems( aFilterStr );
603}
604
605
606void WIDGET_HOTKEY_LIST::ResetAllHotkeys( bool aResetToDefault )
607{
608 wxWindowUpdateLocker updateLock( this );
609
610 // Reset all the hotkeys, not just the ones shown
611 // Should not need to check conflicts, as the state we're about
612 // to set to a should be consistent
613 if( aResetToDefault )
614 m_hk_store.ResetAllHotkeysToDefault();
615 else
616 m_hk_store.ResetAllHotkeysToOriginal();
617
620}
621
622
624{
625 updateShownItems( "" );
627
628 return true;
629}
630
631
633{
634 wxDataViewColumn* col = GetDataView()->GetColumn( 0 );
635 col->SetWidth( wxCOL_WIDTH_AUTOSIZE );
636 col->SetWidth( col->GetWidth() );
637
638 col = GetDataView()->GetColumn( 1 );
639 col->SetWidth( wxCOL_WIDTH_AUTOSIZE );
640 col->SetWidth( col->GetWidth() );
641
642 col = GetDataView()->GetColumn( 2 );
643 col->SetWidth( wxCOL_WIDTH_AUTOSIZE );
644 col->SetWidth( col->GetWidth() );
645
646 col = GetDataView()->GetColumn( 3 );
647 col->SetWidth( wxCOL_WIDTH_AUTOSIZE );
648 col->SetWidth( col->GetWidth() );
649}
650
651
652void WIDGET_HOTKEY_LIST::updateShownItems( const wxString& aFilterStr )
653{
654 wxWindowUpdateLocker updateLock( this );
655
656 DeleteAllItems();
657
658 HOTKEY_FILTER filter( aFilterStr );
659
660 for( HOTKEY_SECTION& section: m_hk_store.GetSections() )
661 {
662 // Create parent tree item
663 wxTreeListItem parent = AppendItem( GetRootItem(), section.m_SectionName );
664
665 for( HOTKEY& hotkey: section.m_HotKeys )
666 {
667 if( filter.FilterMatches( hotkey ) )
668 {
669 wxTreeListItem item = AppendItem( parent, wxEmptyString );
670 SetItemData( item, new WIDGET_HOTKEY_CLIENT_DATA( hotkey ) );
671 }
672 }
673
674 Expand( parent );
675 }
676
678}
679
680
682{
683 m_hk_store.SaveAllHotkeys();
684 return true;
685}
686
687
688long WIDGET_HOTKEY_LIST::MapKeypressToKeycode( const wxKeyEvent& aEvent )
689{
690 long key = aEvent.GetKeyCode();
691 bool is_tab = aEvent.IsKeyInCategory( WXK_CATEGORY_TAB );
692
693 if( key == WXK_ESCAPE )
694 {
695 return 0;
696 }
697 else
698 {
699 if( key >= 'a' && key <= 'z' ) // convert to uppercase
700 key = key + ('A' - 'a');
701
702 // Remap Ctrl A (=1+GR_KB_CTRL) to Ctrl Z(=26+GR_KB_CTRL)
703 // to GR_KB_CTRL+'A' .. GR_KB_CTRL+'Z'
704 if( !is_tab && aEvent.ControlDown() && key >= WXK_CONTROL_A && key <= WXK_CONTROL_Z )
705 key += 'A' - 1;
706
707 /* Disallow shift for keys that have two keycodes on them (e.g. number and
708 * punctuation keys) leaving only the "letter keys" of A-Z, tab and space
709 * Then, you can have, e.g. Ctrl-5 and Ctrl-% (GB layout)
710 * and Ctrl-( and Ctrl-5 (FR layout).
711 * Otherwise, you'd have to have to say Ctrl-Shift-5 on a FR layout
712 */
713 bool keyIsLetter = key >= 'A' && key <= 'Z';
714
715 int mods = aEvent.GetModifiers();
716
717 if( ( mods & wxMOD_SHIFT ) && ( keyIsLetter || key > 256 || key == 9 || key == 32 ) )
718 key |= MD_SHIFT;
719
720 // the flag wxMOD_ALTGR is defined in wxWidgets as wxMOD_CONTROL|wxMOD_ALT
721 // So AltGr key cannot used as modifier key because it is the same as Alt key + Ctrl key.
722 #if CAN_USE_ALTGR_KEY
723 if( wxmods & wxMOD_ALTGR )
724 mods |= MD_ALTGR;
725 else
726 #endif
727 {
728 if( mods & wxMOD_CONTROL )
729 key |= MD_CTRL;
730
731 if( mods & wxMOD_ALT )
732 key |= MD_ALT;
733 }
734
735#ifdef wxMOD_META
736 if( mods & wxMOD_META )
737 key |= MD_META;
738#endif
739
740#ifdef wxMOD_WIN
741 if( mods & wxMOD_WIN )
742 key |= MD_SUPER;
743#endif
744
745 return key;
746 }
747}
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