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 // See if this key code is handled in hotkeys names list
343 bool exists;
344 KeyNameFromKeyCode( aKey, &exists );
345
346 if( exists && aHotkey.m_EditKeycode != aKey )
347 {
348 if( aKey == 0 || resolveKeyConflicts( aHotkey.m_Actions[ 0 ], aKey ) )
349 {
350 if( alternate )
351 aHotkey.m_EditKeycodeAlt = aKey;
352 else
353 aHotkey.m_EditKeycode = aKey;
354 }
355 }
356}
357
358
359void WIDGET_HOTKEY_LIST::editItem( wxTreeListItem aItem, int aEditId )
360{
362
363 if( !hkdata )
364 return;
365
366 wxString name = GetItemText( aItem, 0 );
367 wxString current_key = aEditId == ID_EDIT_HOTKEY ? GetItemText( aItem, 1 )
368 : GetItemText( aItem, 2 );
369
370 std::optional<long> key = HK_PROMPT_DIALOG::PromptForKey( this, name, current_key );
371
372 // An empty optional means don't change the key
373 if( key.has_value() )
374 {
375 auto it = m_reservedHotkeys.find( key.value() );
376
377 if( it != m_reservedHotkeys.end() )
378 {
379 wxString msg = wxString::Format( _( "'%s' is a reserved hotkey in KiCad and cannot "
380 "be assigned." ),
381 it->second );
382
383 DisplayErrorMessage( this, msg );
384 return;
385 }
386
387 changeHotkey( hkdata->GetChangedHotkey(), key.value(), aEditId == ID_EDIT_ALT );
389 }
390}
391
392
393void WIDGET_HOTKEY_LIST::resetItem( wxTreeListItem aItem, int aResetId )
394{
396
397 if( !hkdata )
398 return;
399
400 HOTKEY& changed_hk = hkdata->GetChangedHotkey();
401
402 if( aResetId == ID_RESET )
403 {
404 changeHotkey( changed_hk, changed_hk.m_Actions[0]->GetHotKey(), false );
405 changeHotkey( changed_hk, changed_hk.m_Actions[0]->GetHotKey(), true );
406 }
407 else if( aResetId == ID_CLEAR )
408 {
409 changeHotkey( changed_hk, 0, false );
410 }
411 else if( aResetId == ID_CLEAR_ALT )
412 {
413 changeHotkey( changed_hk, 0, true );
414 }
415 else if( aResetId == ID_DEFAULT )
416 {
417 changeHotkey( changed_hk, changed_hk.m_Actions[0]->GetDefaultHotKey(), false );
418 changeHotkey( changed_hk, changed_hk.m_Actions[0]->GetDefaultHotKeyAlt(), true );
419 }
420
422}
423
424
425void WIDGET_HOTKEY_LIST::onActivated( wxTreeListEvent& aEvent )
426{
427 editItem( aEvent.GetItem(), ID_EDIT_HOTKEY );
428}
429
430
431void WIDGET_HOTKEY_LIST::onContextMenu( wxTreeListEvent& aEvent )
432{
433 // Save the active event for use in OnMenu
434 m_context_menu_item = aEvent.GetItem();
435
436 wxMenu menu;
437
439
440 // Some actions only apply if the row is hotkey data
441 if( hkdata )
442 {
443 menu.Append( ID_EDIT_HOTKEY, _( "Edit..." ) );
444 menu.Append( ID_EDIT_ALT, _( "Edit Alternate..." ) );
445 menu.Append( ID_RESET, _( "Undo Changes" ) );
446 menu.Append( ID_CLEAR, _( "Clear Assigned Hotkey" ) );
447 menu.Append( ID_CLEAR_ALT, _( "Clear Assigned Alternate" ) );
448 menu.Append( ID_DEFAULT, _( "Restore Defaults" ) );
449 menu.Append( wxID_SEPARATOR );
450
451 PopupMenu( &menu );
452 }
453}
454
455
456void WIDGET_HOTKEY_LIST::onMenu( wxCommandEvent& aEvent )
457{
458 switch( aEvent.GetId() )
459 {
460 case ID_EDIT_HOTKEY:
461 case ID_EDIT_ALT:
462 editItem( m_context_menu_item, aEvent.GetId() );
463 break;
464
465 case ID_RESET:
466 case ID_CLEAR:
467 case ID_CLEAR_ALT:
468 case ID_DEFAULT:
469 resetItem( m_context_menu_item, aEvent.GetId() );
470 break;
471
472 default:
473 wxFAIL_MSG( wxT( "Unknown ID in context menu event" ) );
474 }
475}
476
477
478void WIDGET_HOTKEY_LIST::onCharHook( wxKeyEvent& aEvent )
479{
480 // On macOS with international keyboards, "dead keys" (like backtick or circumflex on
481 // French keyboards) are used for composing accented characters. When pressed on a
482 // wxTreeListCtrl, these can trigger IME composition handling that the control doesn't
483 // support, causing a crash.
484 //
485 // Per wxWidgets documentation: if both GetUnicodeKey() and GetKeyCode() return WXK_NONE,
486 // the key has no WXK_xxx mapping (e.g., dead keys). We filter these out to prevent crashes.
487 // Navigation keys (arrows, function keys, etc.) have valid GetKeyCode() values even when
488 // GetUnicodeKey() returns WXK_NONE, so they pass through correctly.
489 if( aEvent.GetUnicodeKey() == WXK_NONE && aEvent.GetKeyCode() == WXK_NONE )
490 return;
491
492 aEvent.Skip();
493}
494
495
497{
498 HOTKEY* conflictingHotKey = nullptr;
499
500 m_hk_store.CheckKeyConflicts( aAction, aKey, &conflictingHotKey );
501
502 if( !conflictingHotKey )
503 return true;
504
505 TOOL_ACTION* conflictingAction = conflictingHotKey->m_Actions[ 0 ];
506 wxString msg = wxString::Format( _( "'%s' is already assigned to '%s' in section '%s'. "
507 "Both bindings are kept, but only one runs per key "
508 "press. Continue?" ),
509 KeyNameFromKeyCode( aKey ), conflictingAction->GetFriendlyName(),
510 HOTKEY_STORE::GetSectionName( conflictingAction ) );
511
512 KICAD_MESSAGE_DIALOG dlg( GetParent(), msg, _( "Hotkey conflict" ), wxYES_NO | wxNO_DEFAULT );
513
514 return dlg.ShowModal() == wxID_YES;
515}
516
517
518WIDGET_HOTKEY_LIST::WIDGET_HOTKEY_LIST( wxWindow* aParent, HOTKEY_STORE& aHotkeyStore, bool readOnly ) :
519 wxTreeListCtrl( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTL_SINGLE ),
520 m_hk_store( aHotkeyStore )
521{
522 wxString command_header;
523
524 if( readOnly )
525 command_header = _( "Command" );
526 else
527 command_header = _( "Command (double-click to edit)" );
528
529 AppendColumn( command_header, 450, wxALIGN_LEFT, wxCOL_RESIZABLE | wxCOL_SORTABLE );
530 AppendColumn( _( "Hotkey" ), 120, wxALIGN_LEFT, wxCOL_RESIZABLE | wxCOL_SORTABLE );
531 AppendColumn( _( "Alternate" ), 120, wxALIGN_LEFT, wxCOL_RESIZABLE | wxCOL_SORTABLE );
532 AppendColumn( _( "Description" ), 900, wxALIGN_LEFT, wxCOL_RESIZABLE | wxCOL_SORTABLE );
533
534
535#if defined( __WXGTK__ )// && !wxCHECK_VERSION( 3, 1, 0 )
536 // Automatic column widths are broken in wxGTK 3.0.x; set min widths to ensure visibility
537 // They are also broken in wxGTK 3.1.4
538
539 wxDataViewCtrl* dv = GetDataView();
540
541 wxString longKey = wxT( "Ctrl+Alt+Shift+X" );
542 int pad = 20;
543
544 dv->GetColumn( 0 )->SetMinWidth( aParent->GetTextExtent( command_header ).x * 2 + pad );
545 dv->GetColumn( 1 )->SetMinWidth( aParent->GetTextExtent( longKey ).x + pad );
546 dv->GetColumn( 2 )->SetMinWidth( aParent->GetTextExtent( longKey ).x + pad );
547 dv->GetColumn( 3 )->SetMinWidth( aParent->GetTextExtent( command_header ).x * 5 + pad );
548
549 CallAfter( [this]()
550 {
551 GetDataView()->Update();
552 } );
553#endif
554
555 std::vector<wxString> reserved_keys =
556 {
557 wxS( "Ctrl+Tab" ),
558 wxS( "Ctrl+Shift+Tab" )
559 };
560
561 for( const wxString& key : reserved_keys )
562 {
563 long code = KeyCodeFromKeyName( key );
564
565 if( code )
566 m_reservedHotkeys[code] = key;
567 else
568 wxLogWarning( wxS( "Unknown reserved keycode %s\n" ), key );
569 }
570
571 GetDataView()->SetIndent( 10 );
572
573 // The event only apply if the widget is in editable mode
574 if( !readOnly )
575 {
576 Bind( wxEVT_TREELIST_ITEM_ACTIVATED, &WIDGET_HOTKEY_LIST::onActivated, this );
577 Bind( wxEVT_TREELIST_ITEM_CONTEXT_MENU, &WIDGET_HOTKEY_LIST::onContextMenu, this );
578 Bind( wxEVT_MENU, &WIDGET_HOTKEY_LIST::onMenu, this );
579 }
580
581 // Filter out dead keys that can crash on macOS with international keyboards
582 Bind( wxEVT_CHAR_HOOK, &WIDGET_HOTKEY_LIST::onCharHook, this );
583}
584
585
586void WIDGET_HOTKEY_LIST::ApplyFilterString( const wxString& aFilterStr )
587{
588 updateShownItems( aFilterStr );
589}
590
591
592void WIDGET_HOTKEY_LIST::ResetAllHotkeys( bool aResetToDefault )
593{
594 wxWindowUpdateLocker updateLock( this );
595
596 // Reset all the hotkeys, not just the ones shown
597 // Should not need to check conflicts, as the state we're about
598 // to set to a should be consistent
599 if( aResetToDefault )
600 m_hk_store.ResetAllHotkeysToDefault();
601 else
602 m_hk_store.ResetAllHotkeysToOriginal();
603
606}
607
608
610{
611 updateShownItems( "" );
613
614 return true;
615}
616
617
619{
620 wxDataViewColumn* col = GetDataView()->GetColumn( 0 );
621 col->SetWidth( wxCOL_WIDTH_AUTOSIZE );
622 col->SetWidth( col->GetWidth() );
623
624 col = GetDataView()->GetColumn( 1 );
625 col->SetWidth( wxCOL_WIDTH_AUTOSIZE );
626 col->SetWidth( col->GetWidth() );
627
628 col = GetDataView()->GetColumn( 2 );
629 col->SetWidth( wxCOL_WIDTH_AUTOSIZE );
630 col->SetWidth( col->GetWidth() );
631
632 col = GetDataView()->GetColumn( 3 );
633 col->SetWidth( wxCOL_WIDTH_AUTOSIZE );
634 col->SetWidth( col->GetWidth() );
635}
636
637
638void WIDGET_HOTKEY_LIST::updateShownItems( const wxString& aFilterStr )
639{
640 wxWindowUpdateLocker updateLock( this );
641
642 DeleteAllItems();
643
644 HOTKEY_FILTER filter( aFilterStr );
645
646 for( HOTKEY_SECTION& section: m_hk_store.GetSections() )
647 {
648 // Create parent tree item
649 wxTreeListItem parent = AppendItem( GetRootItem(), section.m_SectionName );
650
651 for( HOTKEY& hotkey: section.m_HotKeys )
652 {
653 if( filter.FilterMatches( hotkey ) )
654 {
655 wxTreeListItem item = AppendItem( parent, wxEmptyString );
656 SetItemData( item, new WIDGET_HOTKEY_CLIENT_DATA( hotkey ) );
657 }
658 }
659
660 Expand( parent );
661 }
662
664}
665
666
668{
669 m_hk_store.SaveAllHotkeys();
670 return true;
671}
672
673
674long WIDGET_HOTKEY_LIST::MapKeypressToKeycode( const wxKeyEvent& aEvent )
675{
676 long key = aEvent.GetKeyCode();
677 bool is_tab = aEvent.IsKeyInCategory( WXK_CATEGORY_TAB );
678
679 if( key == WXK_ESCAPE )
680 {
681 return 0;
682 }
683 else
684 {
685 if( key >= 'a' && key <= 'z' ) // convert to uppercase
686 key = key + ('A' - 'a');
687
688 // Remap Ctrl A (=1+GR_KB_CTRL) to Ctrl Z(=26+GR_KB_CTRL)
689 // to GR_KB_CTRL+'A' .. GR_KB_CTRL+'Z'
690 if( !is_tab && aEvent.ControlDown() && key >= WXK_CONTROL_A && key <= WXK_CONTROL_Z )
691 key += 'A' - 1;
692
693 /* Disallow shift for keys that have two keycodes on them (e.g. number and
694 * punctuation keys) leaving only the "letter keys" of A-Z, tab and space
695 * Then, you can have, e.g. Ctrl-5 and Ctrl-% (GB layout)
696 * and Ctrl-( and Ctrl-5 (FR layout).
697 * Otherwise, you'd have to have to say Ctrl-Shift-5 on a FR layout
698 */
699 bool keyIsLetter = key >= 'A' && key <= 'Z';
700
701 int mods = aEvent.GetModifiers();
702
703 if( ( mods & wxMOD_SHIFT ) && ( keyIsLetter || key > 256 || key == 9 || key == 32 ) )
704 key |= MD_SHIFT;
705
706 // the flag wxMOD_ALTGR is defined in wxWidgets as wxMOD_CONTROL|wxMOD_ALT
707 // So AltGr key cannot used as modifier key because it is the same as Alt key + Ctrl key.
708 #if CAN_USE_ALTGR_KEY
709 if( wxmods & wxMOD_ALTGR )
710 mods |= MD_ALTGR;
711 else
712 #endif
713 {
714 if( mods & wxMOD_CONTROL )
715 key |= MD_CTRL;
716
717 if( mods & wxMOD_ALT )
718 key |= MD_ALT;
719 }
720
721#ifdef wxMOD_META
722 if( mods & wxMOD_META )
723 key |= MD_META;
724#endif
725
726#ifdef wxMOD_WIN
727 if( mods & wxMOD_WIN )
728 key |= MD_SUPER;
729#endif
730
731 return key;
732 }
733}
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:79
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.
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