Audacity 3.2.0
KeyConfigPrefs.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 KeyConfigPrefs.cpp
6
7 Brian Gunlogson
8 Dominic Mazzoni
9 James Crook
10
11*******************************************************************//*********************************************************************/
21#include "KeyConfigPrefs.h"
22
23#include <wx/setup.h> // for wxUSE_* macros
24#include <wx/defs.h>
25#include <wx/ffile.h>
26#include <wx/menu.h>
27#include <wx/button.h>
28#include <wx/radiobut.h>
29#include <wx/stattext.h>
30#include <wx/statbox.h>
31#include <wx/textctrl.h>
32
33#include "ActiveProject.h"
34#include "MenuCreator.h" // for KeyEventToKeyString
35#include "Prefs.h"
36#include "Project.h"
37#include "../ProjectWindows.h"
38#include "XMLFileReader.h"
39
40#include "SelectFile.h"
41#include "ShuttleGui.h"
42
43#include "FileNames.h"
44
45#include "../widgets/BasicMenu.h"
46#include "../widgets/KeyView.h"
47#include "AudacityMessageBox.h"
49
50#if wxUSE_ACCESSIBILITY
51#include "WindowAccessible.h"
52#endif
53
54//
55// KeyConfigPrefs
56//
57#define AssignDefaultsButtonID 17001
58#define CurrentComboID 17002
59#define SetButtonID 17003
60#define ClearButtonID 17004
61#define CommandsListID 17005
62#define ExportButtonID 17006
63#define ImportButtonID 17007
64#define FilterID 17008
65#define ViewByTreeID 17009
66#define ViewByNameID 17010
67#define ViewByKeyID 17011
68#define FilterTimerID 17012
69
70// EMPTY_SHORTCUT means "user chose to have no shortcut"
71#define EMPTY_SHORTCUT ("")
72// NO_SHORTCUT means "user made no choice"
73#define NO_SHORTCUT (wxString)((wxChar)7)
74
75BEGIN_EVENT_TABLE(KeyConfigPrefs, PrefsPanel)
87
89 wxWindow * parent, wxWindowID winid, AudacityProject *pProject,
90 const CommandID &name)
91: PrefsPanel(parent, winid, XO("Shortcuts")),
92 mView(NULL),
93 mKey(NULL),
94 mFilter(NULL),
95 mFilterTimer(this, FilterTimerID),
96 mFilterPending(false)
97 , mProject{ pProject }
98{
99 Populate();
100 if (!name.empty()) {
101 auto index = mView->GetIndexByName(name);
102 mView->SelectNode(index);
103 }
104
105 // See bug #2315 for discussion. This should be reviewed
106 // and (possibly) removed after wx3.1.3.
107 Bind(wxEVT_SHOW, &KeyConfigPrefs::OnShow, this);
108}
109
111{
113}
114
116{
117 return XO("Preferences for KeyConfig");
118}
119
121{
122 return "Keyboard_Preferences";
123}
124
126{
128
129 if (!mProject) {
130 S.StartVerticalLay(true);
131 {
132 S.StartStatic( {}, true);
133 {
134 S.AddTitle(XO("Keyboard preferences currently unavailable."));
135 S.AddTitle(XO("Open a new project to modify keyboard shortcuts."));
136 }
137 S.EndStatic();
138 }
139 S.EndVerticalLay();
140
141 return;
142 }
143
145
146 mCommandSelected = wxNOT_FOUND;
147
149
150 // For speed, don't sort here. We're just creating.
151 // Instead sort when we do SetView later in this function.
152 RefreshBindings(false);
153
154 if (mViewByTree->GetValue()) {
156 }
157 else if (mViewByName->GetValue()) {
159 }
160 else if (mViewByKey->GetValue()) {
162 mFilterLabel->SetLabel(_("&Hotkey:"));
163 mFilter->SetName(wxStripMenuCodes(mFilterLabel->GetLabel()));
164 }
165
167}
168
174{
175 ChoiceSetting Setting{ L"/Prefs/KeyConfig/ViewBy",
176 {
177 { wxT("tree"), XXO("&Tree") },
178 { wxT("name"), XXO("&Name") },
179 { wxT("key"), XXO("&Key") },
180 },
181 0 // tree
182 };
183
184 S.SetBorder(2);
185
186 S.StartStatic(XO("Key Bindings"), 1);
187 {
188 S.StartHorizontalLay(wxEXPAND, 0);
189 {
190 S.Position(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL).AddTitle(XO("View by:"));
191
192 // Bug 2692: Place button group in panel so tabbing will work and,
193 // on the Mac, VoiceOver will announce as radio buttons.
194 S.StartPanel();
195 {
196 S.StartHorizontalLay();
197 {
198 S.StartRadioButtonGroup(Setting);
199 {
201 .Name(XO("View by tree"))
202 .TieRadioButton();
204 .Name(XO("View by name"))
205 .TieRadioButton();
207 .Name(XO("View by key"))
208 .TieRadioButton();
209#if !defined(__WXMAC__) && wxUSE_ACCESSIBILITY
210 // so that name can be set on a standard control
214#endif
215 }
216 S.EndRadioButtonGroup();
217 }
218 S.EndHorizontalLay();
219 }
220 S.EndPanel();
221
222 S.AddSpace(wxDefaultCoord, wxDefaultCoord, 1);
223
224 S.StartHorizontalLay(wxALIGN_CENTER_VERTICAL, 0);
225 {
226 mFilterLabel = S.Position(wxALIGN_CENTER_VERTICAL).AddVariableText(XO("Searc&h:"));
227
228 if (!mFilter) {
229 mFilter = safenew wxTextCtrl(S.GetParent(),
230 FilterID,
231 wxT(""),
232 wxDefaultPosition,
233#if defined(__WXMAC__)
234 wxSize(300, -1),
235#else
236 wxSize(210, -1),
237#endif
238 wxTE_PROCESS_ENTER);
239 mFilter->SetName(wxStripMenuCodes(mFilterLabel->GetLabel()));
240 }
241 S.Position(wxALIGN_NOT | wxALIGN_LEFT)
242 .ConnectRoot(wxEVT_KEY_DOWN,
244 .ConnectRoot(wxEVT_CHAR,
246 .AddWindow(mFilter);
247 }
248 S.EndHorizontalLay();
249 }
250 S.EndHorizontalLay();
251
252 S.AddSpace(wxDefaultCoord, 2);
253
254 S.StartHorizontalLay(wxEXPAND, 1);
255 {
256 if (!mView) {
257 mView = safenew KeyView(S.GetParent(), CommandsListID);
258 mView->SetName(_("Bindings"));
259 }
260 S.Prop(true)
261 .Position(wxEXPAND)
262 .AddWindow(mView);
263 }
264 S.EndHorizontalLay();
265
266 S.StartThreeColumn();
267 {
268 if (!mKey) {
269 mKey = safenew wxTextCtrl(S.GetParent(),
271 wxT(""),
272 wxDefaultPosition,
273#if defined(__WXMAC__)
274 wxSize(300, -1),
275#else
276 wxSize(210, -1),
277#endif
278 wxTE_PROCESS_ENTER);
279#if !defined(__WXMAC__) && wxUSE_ACCESSIBILITY
280 // so that name can be set on a standard control
281 mKey->SetAccessible(safenew WindowAccessible(mKey));
282#endif
283 mKey->SetName(_("Short cut"));
284 }
285 S
286 .ConnectRoot(wxEVT_KEY_DOWN,
288 .ConnectRoot(wxEVT_CHAR,
290 .ConnectRoot(wxEVT_KILL_FOCUS,
292 .ConnectRoot(wxEVT_CONTEXT_MENU,
294 .AddWindow(mKey);
295
296 /* i18n-hint: (verb)*/
297 mSet = S.Id(SetButtonID).AddButton(XXO("&Set"));
298 /* i18n-hint: (verb)*/
299 mClear = S.Id(ClearButtonID).AddButton(XXO("Cl&ear"));
300 }
301 S.EndThreeColumn();
302
303#if defined(__WXMAC__)
304 S.AddFixedText(XO("Note: Pressing Cmd+Q will quit. All other keys are valid."));
305#endif
306
307 S.StartThreeColumn();
308 {
309 S.Id(ImportButtonID).AddButton(XXO("&Import..."));
310 S.Id(ExportButtonID).AddButton(XXO("&Export..."));
311 S.Id(AssignDefaultsButtonID).AddButton(XXO("&Defaults"));
312 }
313 S.EndThreeColumn();
314 }
315 S.EndStatic();
316
317
318 // Need to layout so that the KeyView is properly sized before populating.
319 // Otherwise, the initial selection is not scrolled into view.
320 Layout();
321}
322
324{
325 TranslatableStrings Labels;
326 TranslatableStrings Categories;
327 TranslatableStrings Prefixes;
328
329 mNames.clear();
330 mKeys.clear();
331 mDefaultKeys.clear();
332 mStandardDefaultKeys.clear();
334 mNames,
335 mKeys,
337 Labels,
338 Categories,
339 Prefixes,
340 true); // True to include effects (list items), false otherwise.
341
344
346 Categories,
347 Prefixes,
348 Labels,
349 mKeys,
350 bSort);
351 //Not needed as NEW nodes are already shown expanded.
352 //mView->ExpandAll();
353
354 mNewKeys = mKeys;
355}
356
357// RefreshKeyInfo is used to update mKeys vector only
358// Introduced for efficiency purposes to avoid unnecessary usage of RefreshBinding
360{
361 mKeys.clear();
362
363 for (const auto & name : mNames)
364 mKeys.push_back(mManager->GetKeyFromName(name));
365}
366
367// Removes all shortcuts
368// Doesn't call RefreshBindings()
370{
371 const NormalizedKeyString noKey{ NO_SHORTCUT };
372 for (const auto & command : mNames)
373 mManager->SetKeyFromName(command, noKey);
374}
375
376// Checks if the given vector of keys contains illegal duplicates.
377// In case it does, stores the prefixed labels of operations
378// with illegal key duplicates in fMatching and sMatching.
379// Search for duplicates fully implemented here
380// to avoid possible problems with legal shortcut duplicates.
382 TranslatableString & fMatching, TranslatableString & sMatching) const
383{
384 using IndexesArray = std::vector<int>;
385 std::unordered_map<NormalizedKeyString, IndexesArray> seen;
386
387 for (size_t i{ 0 }; i < mKeys.size(); i++)
388 {
389 if (mKeys[i] == EMPTY_SHORTCUT || mKeys[i] == NO_SHORTCUT)
390 continue;
391
392 if (seen.count(mKeys[i]) == 0)
393 seen.insert({ mKeys[i], {(int)i} });
394 else
395 {
396 IndexesArray checkMe{ seen.at(mKeys[i]) };
397 for (int index : checkMe)
398 {
399 if (mDefaultKeys[i] == EMPTY_SHORTCUT ||
400 mDefaultKeys[i] != mDefaultKeys[index])
401 {
403 sMatching = mManager->GetPrefixedLabelFromName(mNames[index]);
404 return true;
405 }
406 else
407 seen.at(mKeys[i]).push_back(index);
408 }
409 }
410 }
411 return false;
412}
413
414
415// This function tries to add the given shortcuts(keys) "toAdd"
416// to the already existing shortcuts(keys). Shortcuts are added only if
417// 1. the shortcut for the operation isn't defined already
418// 2. the added shortcut doesn't create illegal shortcut duplicate
419// The names of operations for which the second condition was violated
420// are returned in a single error message
422 const std::vector<NormalizedKeyString> &toAdd)
423{
424 TranslatableString disabledShortcuts;
425
426 auto searchAddInKeys = [&](size_t index)
427 {
428 for (size_t k{ 0 }; k < toAdd.size(); k++)
429 if (k == index)
430 continue;
431 else if (toAdd[index] == mKeys[k] &&
433 mDefaultKeys[k] != mDefaultKeys[index]))
434 return (int)k;
435
436 return -1;
437 };
438
439 const NormalizedKeyString noKey{ EMPTY_SHORTCUT };
440
441 for (size_t i{ 0 }; i < toAdd.size(); i++)
442 {
443 if (mKeys[i] != NO_SHORTCUT)
444 continue;
445 else if (toAdd[i] == EMPTY_SHORTCUT)
446 mManager->SetKeyFromIndex(i, noKey);
447 else
448 {
449 int sRes{ searchAddInKeys(i) };
450
451 if (sRes == -1)
452 mManager->SetKeyFromIndex(i, toAdd[i]);
453 else
454 {
456
457 disabledShortcuts +=
458 XO(
459"\n * \"%s\" (because the shortcut \'%s\' is used by \"%s\")\n")
460 .Format(
462 name,
464
465 mManager->SetKeyFromIndex(i, noKey);
466 }
467 }
468 }
469
470 return disabledShortcuts;
471}
472
473// See bug #2315 for discussion. This should be reviewed
474// and (possibly) removed after wx3.1.3.
475void KeyConfigPrefs::OnShow(wxShowEvent & event)
476{
477 event.Skip();
478
479 // This is required to prevent a crash if Preferences
480 // were opened without a project.
481 if (event.IsShown() && mView != nullptr)
482 {
483 mView->Refresh();
484 }
485}
486
487void KeyConfigPrefs::OnImport(wxCommandEvent & WXUNUSED(event))
488{
489 wxString file = wxT("Audacity-keys.xml");
490
491 file = SelectFile(FileNames::Operation::Open,
492 XO("Select an XML file containing Audacity keyboard shortcuts..."),
493 wxEmptyString,
494 file,
495 wxT(""),
497 wxRESIZE_BORDER,
498 this);
499
500 if (!file) {
501 return;
502 }
503
504 // this RefreshKeyInfo is here to account for
505 // potential OnSet() function executions before importing
507
508 // saving pre-import settings
509 const std::vector<NormalizedKeyString> oldKeys{ mKeys };
510
511 // clearing all pre-import settings
512 ClearAllKeys();
513
514 // getting new settings
515 XMLFileReader reader;
516 if (!reader.Parse(mManager, file)) {
518 reader.GetErrorStr(),
519 XO("Error Importing Keyboard Shortcuts"),
520 wxOK | wxCENTRE,
521 this);
522 }
523
525
526 // checking new setting for duplicates
527 // if there are duplicates, throwing error and returning to pre-import state
528 TranslatableString fMatching;
529 TranslatableString sMatching;
530
531 if (ContainsIllegalDups(fMatching, sMatching))
532 {
533 // restore the old pre-import hotkeys stored in oldKeys
534 for (size_t k{ 0 }; k < mNames.size(); k++)
535 mManager->SetKeyFromName(mNames[k], oldKeys[k]);
536 mKeys = oldKeys;
537
538 // output an error message
540 XO(
541"The file with the shortcuts contains illegal shortcut duplicates for \"%s\" and \"%s\".\nNothing is imported.")
542 .Format( fMatching, sMatching ),
543 XO("Error Importing Keyboard Shortcuts"),
544 wxICON_ERROR | wxCENTRE, this);
545
546 // stop the function
547 return;
548 }
549
550 // adding possible old settings to the new settings and recording the conflicting ones
551 TranslatableString disabledShortcuts{ MergeWithExistingKeys(oldKeys) };
552
553 RefreshBindings(true);
554
555 TranslatableString message{
556 XO("Loaded %d keyboard shortcuts\n").Format(mManager->GetNumberOfKeysRead()) };
557
558 if (disabledShortcuts.Translation() != (""))
559 message += XO("\nThe following commands are not mentioned in the imported file, "
560 "but have their shortcuts removed because of the conflict with other new shortcuts:\n") +
561 disabledShortcuts;
562
563 AudacityMessageBox(message, XO("Loading Keyboard Shortcuts"), wxOK | wxCENTRE);
564}
565
566void KeyConfigPrefs::OnExport(wxCommandEvent & WXUNUSED(event))
567{
568 wxString file = wxT("Audacity-keys.xml");
569
570 file = SelectFile(FileNames::Operation::Export,
571 XO("Export Keyboard Shortcuts As:"),
572 wxEmptyString,
573 file,
574 wxT("xml"),
576 wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER,
577 this);
578
579 if (!file) {
580 return;
581 }
582
583 GuardedCall( [&] {
584 XMLFileWriter prefFile{ file, XO("Error Exporting Keyboard Shortcuts") };
585 mManager->WriteXML(prefFile);
586 prefFile.Commit();
587 } );
588}
589
590
591
592// There currently is only one clickable AButton
593// so we just do what it needs.
594void KeyConfigPrefs::OnDefaults(wxCommandEvent & WXUNUSED(event))
595{
596 wxMenu Menu;
597 Menu.Append( 1, _("Standard") );
598 Menu.Append( 2, _("Full") );
599 Menu.Bind( wxEVT_COMMAND_MENU_SELECTED, &KeyConfigPrefs::OnImportDefaults, this );
601}
602
603void KeyConfigPrefs::FilterKeys( std::vector<NormalizedKeyString> & arr )
604{
605 const auto &MaxListOnly = CommandManager::ExcludedList();
606
607 // Remove items that are in MaxList.
608 for (size_t i = 0; i < arr.size(); i++) {
609 if( std::binary_search(MaxListOnly.begin(), MaxListOnly.end(), arr[i]) )
610 arr[i] = {};
611 }
612}
613
614void KeyConfigPrefs::OnImportDefaults(wxCommandEvent & event)
615{
616 gPrefs->DeleteEntry(wxT("/GUI/Shortcuts/FullDefaults"));
617 gPrefs->Flush();
618
620 if( event.GetId() == 1 )
622
623 for (size_t i = 0; i < mNewKeys.size(); i++) {
625 }
626
627 RefreshBindings(true);
628}
629
631{
632 wxTextCtrl *t = (wxTextCtrl *)e.GetEventObject();
633
634 // Make sure we can navigate away from the hotkey textctrl.
635 // On Linux and OSX, it can get stuck, but it doesn't hurt
636 // to do it for Windows as well.
637 //
638 // Mac note: Don't waste time trying to figure out why the
639 // focus goes back to the prefs tree. Unless Voiceover is
640 // active, buttons on the Mac do not accept focus and all the
641 // controls between this one and the tree control are buttons.
642 if (e.GetKeyCode() == WXK_TAB) {
643 t->Navigate(e.ShiftDown()
644 ? wxNavigationKeyEvent::IsBackward
645 : wxNavigationKeyEvent::IsForward);
646 return;
647 }
648
649 t->SetValue(KeyEventToKeyString(e).Display());
650}
651
652void KeyConfigPrefs::OnHotkeyChar(wxEvent & WXUNUSED(e))
653{
654 // event.Skip() not performed, so event will not be processed further.
655}
656
658{
659 if (mKey->GetValue().empty() && mCommandSelected != wxNOT_FOUND) {
660 mKey->AppendText(mView->GetKey(mCommandSelected).Display());
661 }
662
663 e.Skip();
664}
665
666void KeyConfigPrefs::OnHotkeyContext(wxEvent & WXUNUSED(e))
667{
668 // event.Skip() not performed, so event will not be processed further.
669}
670
671void KeyConfigPrefs::OnFilterTimer(wxTimerEvent & WXUNUSED(e))
672{
673 // The filter timer has expired, so set the filter
674 if (mFilterPending)
675 {
676 // Do not reset mFilterPending here...possible race
677 mView->SetFilter(mFilter->GetValue());
678 }
679}
680
682{
683 wxTextCtrl *t = (wxTextCtrl *)e.GetEventObject();
684 int keycode = e.GetKeyCode();
685
686 // Make sure we can navigate away from the hotkey textctrl.
687 // On Linux and OSX, it an get stuck, but it doesn't hurt
688 // to do it for Windows as well.
689 if (keycode == WXK_TAB) {
690 wxNavigationKeyEvent nevent;
691 nevent.SetWindowChange(e.ControlDown());
692 nevent.SetDirection(!e.ShiftDown());
693 nevent.SetEventObject(t);
694 nevent.SetCurrentFocus(t);
695 t->GetParent()->GetEventHandler()->ProcessEvent(nevent);
696
697 return;
698 }
699
700 if (mViewType == ViewByKey) {
701 wxString key = KeyEventToKeyString(e).Display();
702 t->SetValue(key);
703
704 if (!key.empty()) {
705 mView->SetFilter(t->GetValue());
706 }
707 }
708 else
709 {
710 if (keycode == WXK_RETURN) {
711 mFilterPending = false;
712 mView->SetFilter(t->GetValue());
713 }
714 else {
715 mFilterPending = true;
716 mFilterTimer.Start(500, wxTIMER_ONE_SHOT);
717
718 e.Skip();
719 }
720 }
721}
722
724{
725 if (mViewType != ViewByKey)
726 {
727 e.Skip();
728 }
729}
730
731// Given a hotkey combination, returns the name (description) of the
732// corresponding command, or the empty string if none is found.
734{
735 return mView->GetNameByKey(key);
736}
737
738// Sets the selected command to have this key
739// This is not yet a committed change, which will happen on a save.
741{
743
745 {
747 XO("You may not assign a key to this entry"),
748 XO("Error"),
749 wxICON_ERROR | wxCENTRE,
750 this);
751 return;
752 }
753
756 mNewKeys[ make_iterator_range( mNames ).index( name ) ] = key;
757}
758
759
760void KeyConfigPrefs::OnSet(wxCommandEvent & WXUNUSED(event))
761{
762 if (mCommandSelected == wxNOT_FOUND) {
764 XO("You must select a binding before assigning a shortcut"),
765 XO("Error"),
766 wxICON_WARNING | wxCENTRE,
767 this);
768 return;
769 }
770
771 CommandID newCommand{ mView->GetName(mCommandSelected) };
772 NormalizedKeyString enteredKey{ mKey->GetValue() };
773 NormalizedKeyString newComDefaultKey{
774 mManager->GetDefaultKeyFromName(newCommand) };
775 CommandIDs oldCommands;
776
777 // collecting commands competing for the same shortcut
778 for (size_t i{ 0 }; i < mNames.size(); i++)
779 {
780 if (mNewKeys[i] == enteredKey)
781 {
782 // ignore the Set button if the same shortcut is used
783 if (mNames[i] == newCommand)
784 return;
785
786 if (newComDefaultKey == EMPTY_SHORTCUT ||
787 mDefaultKeys[i] != newComDefaultKey)
788 {
789 oldCommands.push_back(mNames[i]);
790 }
791 }
792 }
793
794 // Prevent same hotkey combination being used twice.
795 if (!oldCommands.empty()) {
796 auto newlabel = Verbatim( wxT("'%s - %s'") )
797 .Format(
798 mManager->GetCategoryFromName(newCommand),
799 mManager->GetPrefixedLabelFromName(newCommand) );
800 auto oldlabel = Verbatim(wxT("'%s - %s'"))
801 .Format(
802 mManager->GetCategoryFromName(oldCommands[0]),
803 mManager->GetPrefixedLabelFromName(oldCommands[0]));
804
805 for (size_t i{ 1 }; i < oldCommands.size(); i++)
806 oldlabel += XO("\n\n\t and\n\n\t") +
807 Verbatim(wxT("'%s - %s'")).Format(
808 mManager->GetCategoryFromName(oldCommands[i]),
809 mManager->GetPrefixedLabelFromName(oldCommands[i]));
810
811 if (wxCANCEL == AudacityMessageBox(
812 XO(
813"The keyboard shortcut '%s' is already assigned to:\n\n\t%s\n\n\nClick OK to assign the shortcut to\n\n\t%s\n\ninstead. Otherwise, click Cancel.")
814 .Format(
815 mKey->GetValue(),
816 oldlabel,
817 newlabel
818 ),
819 XO("Warning"),
820 wxOK | wxCANCEL | wxICON_STOP | wxCENTRE,
821 this))
822 {
823 return;
824 }
825
826 for (const auto & command : oldCommands)
827 {
828 mView->SetKeyByName(command, {});
829 mManager->SetKeyFromName(command, {});
830 mNewKeys[make_iterator_range(mNames).index(command)] = {};
831 }
832 }
833
834 SetKeyForSelected(enteredKey);
835}
836
837void KeyConfigPrefs::OnClear(wxCommandEvent& WXUNUSED(event))
838{
839 mKey->Clear();
840
841 if (mCommandSelected != wxNOT_FOUND) {
843 }
844}
845
846void KeyConfigPrefs::OnSelected(wxCommandEvent & WXUNUSED(e))
847{
849 mKey->Clear();
850
851 if (mCommandSelected != wxNOT_FOUND) {
852 bool canset = mView->CanSetKey(mCommandSelected);
853 if (canset) {
854 mKey->AppendText(mView->GetKey(mCommandSelected).Display());
855 }
856
857 mKey->Enable(canset);
858 mSet->Enable(canset);
859 mClear->Enable(canset);
860 }
861}
862
863void KeyConfigPrefs::OnViewBy(wxCommandEvent & e)
864{
865 switch (e.GetId())
866 {
867 case ViewByTreeID:
869 mFilterLabel->SetLabel(_("Searc&h:"));
870 break;
871
872 case ViewByNameID:
874 mFilterLabel->SetLabel(_("Searc&h:"));
875 break;
876
877 case ViewByKeyID:
879 mFilterLabel->SetLabel(_("&Hotkey:"));
880 break;
881 }
882
884 mFilter->SetName(wxStripMenuCodes(mFilterLabel->GetLabel()));
885}
886
888{
889 // On the Mac, preferences may be changed without any active
890 // projects. This means that the CommandManager isn't available
891 // either. So we can't attempt to save preferences, otherwise
892 // NULL ptr dereferences will happen in ShuttleGui because the
893 // radio buttons are never created. (See Populate() above.)
894 if ( !mProject ) {
895 return true;
896 }
897
900
901 bool bFull = gPrefs->ReadBool(wxT("/GUI/Shortcuts/FullDefaults"), false);
902 for (size_t i = 0; i < mNames.size(); i++) {
903 const auto &dkey = bFull ? mDefaultKeys[i] : mStandardDefaultKeys[i];
904 // using GET to interpret CommandID as a config path component
905 auto name = wxT("/NewKeys/") + mNames[i].GET();
906 const auto &key = mNewKeys[i];
907
908 if (gPrefs->HasEntry(name)) {
910 gPrefs->Write(name, key);
911 }
912 if (key == dkey) {
914 }
915 }
916 else {
917 if (key != dkey) {
918 gPrefs->Write(name, key);
919 }
920 }
921 }
922
923 return gPrefs->Flush();
924}
925
927{
928 // Restore original key values
929 for (size_t i = 0; i < mNames.size(); i++) {
931 }
932
933 return;
934}
935
938{
939 return [=](wxWindow *parent, wxWindowID winid, AudacityProject *pProject)
940 {
941 wxASSERT(parent); // to justify safenew
942 auto result = safenew KeyConfigPrefs{ parent, winid, pProject, name };
943 return result;
944 };
945}
946namespace{
949};
950}
Handle changing of active project and keep global project pointer.
wxT("CloseDown"))
R GuardedCall(const F1 &body, const F2 &handler=F2::Default(), F3 delayedHandler=DefaultDelayedHandlerAction) noexcept(noexcept(handler(std::declval< AudacityException * >())) &&noexcept(handler(nullptr)) &&noexcept(std::function< void(AudacityException *)>{std::move(delayedHandler)}))
Execute some code on any thread; catch any AudacityException; enqueue error report on the main thread...
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
END_EVENT_TABLE()
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
const TranslatableString name
Definition: Distortion.cpp:76
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
std::vector< CommandID > CommandIDs
Definition: Identifier.h:233
#define _(s)
Definition: Internat.h:73
IteratorRange< Iterator > make_iterator_range(const Iterator &i1, const Iterator &i2)
Definition: IteratorX.h:210
PrefsPanel::Factory KeyConfigPrefsFactory(const CommandID &name)
#define CurrentComboID
#define EMPTY_SHORTCUT
#define NO_SHORTCUT
#define ClearButtonID
#define ImportButtonID
#define CommandsListID
#define FilterTimerID
#define ExportButtonID
#define ViewByTreeID
#define SetButtonID
#define ViewByNameID
#define AssignDefaultsButtonID
#define ViewByKeyID
#define FilterID
#define KEY_CONFIG_PREFS_PLUGIN_SYMBOL
@ ViewByTree
Definition: KeyView.h:62
@ ViewByKey
Definition: KeyView.h:64
@ ViewByName
Definition: KeyView.h:63
#define safenew
Definition: MemoryX.h:9
NormalizedKeyString KeyEventToKeyString(const wxKeyEvent &event)
static const AudacityProject::AttachedObjects::RegisteredFactory key
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
FilePath SelectFile(FileNames::Operation op, const TranslatableString &message, const FilePath &default_path, const FilePath &default_filename, const FileExtension &default_extension, const FileTypes &fileTypes, int flags, wxWindow *parent)
Definition: SelectFile.cpp:17
@ eIsCreatingFromPrefs
Definition: ShuttleGui.h:46
@ eIsSavingToPrefs
Definition: ShuttleGui.h:47
#define S(N)
Definition: ToChars.cpp:64
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
std::vector< TranslatableString > TranslatableStrings
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
void Popup(const BasicUI::WindowPlacement &window, const Point &pos={})
Display the menu at pos, invoke at most one action, then hide it.
Definition: BasicMenu.cpp:209
void WriteXML(XMLWriter &xmlFile) const
TranslatableString GetCategoryFromName(const CommandID &name) const
NormalizedKeyString GetDefaultKeyFromName(const CommandID &name) const
static CommandManager & Get(AudacityProject &project)
static const std::vector< NormalizedKeyString > & ExcludedList()
void GetAllCommandData(CommandIDs &names, std::vector< NormalizedKeyString > &keys, std::vector< NormalizedKeyString > &default_keys, TranslatableStrings &labels, TranslatableStrings &categories, TranslatableStrings &prefixes, bool includeMultis)
void SetKeyFromName(const CommandID &name, const NormalizedKeyString &key)
NormalizedKeyString GetKeyFromName(const CommandID &name) const
TranslatableString GetPrefixedLabelFromName(const CommandID &name) const
void SetKeyFromIndex(int i, const NormalizedKeyString &key)
int GetNumberOfKeysRead() const
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
FILES_API const FileType XMLFiles
Definition: FileNames.h:73
FILES_API const FileType AllFiles
Definition: FileNames.h:70
Abstract base class used in importing a file.
const wxString & GET() const
Explicit conversion to wxString, meant to be ugly-looking and demanding of a comment why it's correct...
Definition: Identifier.h:66
A PrefsPanel for keybindings.
void OnFilterKeyDown(wxKeyEvent &e)
void OnImport(wxCommandEvent &e)
void OnHotkeyChar(wxEvent &e)
void OnImportDefaults(wxCommandEvent &e)
TranslatableString MergeWithExistingKeys(const std::vector< NormalizedKeyString > &toAdd)
void OnHotkeyKillFocus(wxEvent &e)
wxStaticText * mFilterLabel
TranslatableString GetDescription() const override
wxTextCtrl * mKey
CommandManager * mManager
void PopulateOrExchange(ShuttleGui &S) override
std::vector< NormalizedKeyString > mDefaultKeys
AudacityProject * mProject
ComponentInterfaceSymbol GetSymbol() const override
void FilterKeys(std::vector< NormalizedKeyString > &arr)
void OnSet(wxCommandEvent &e)
bool ContainsIllegalDups(TranslatableString &fMatching, TranslatableString &sMatching) const
void OnHotkeyContext(wxEvent &e)
void OnFilterChar(wxEvent &e)
ManualPageID HelpPageName() override
If not empty string, the Help button is added below the panel.
wxButton * mClear
void OnFilterTimer(wxTimerEvent &e)
CommandID NameFromKey(const NormalizedKeyString &key)
KeyView * mView
bool Commit() override
std::vector< NormalizedKeyString > mStandardDefaultKeys
std::vector< NormalizedKeyString > mNewKeys
std::vector< NormalizedKeyString > mKeys
void Cancel() override
wxButton * mSet
void OnClear(wxCommandEvent &e)
wxRadioButton * mViewByKey
wxTextCtrl * mFilter
wxRadioButton * mViewByTree
void OnExport(wxCommandEvent &e)
void OnSelected(wxCommandEvent &e)
CommandIDs mNames
ViewByType mViewType
wxRadioButton * mViewByName
void OnViewBy(wxCommandEvent &e)
wxTimer mFilterTimer
void OnDefaults(wxCommandEvent &e)
void RefreshBindings(bool bSort)
void OnShow(wxShowEvent &e)
void OnHotkeyKeyDown(wxKeyEvent &e)
void SetKeyForSelected(const NormalizedKeyString &key)
Provides multiple views of keyboard shortcuts.
Definition: KeyView.h:73
bool CanSetKey(int index) const
Definition: KeyView.cpp:338
int GetSelected() const
Definition: KeyView.cpp:185
bool SetKey(int index, const NormalizedKeyString &key)
Definition: KeyView.cpp:355
void SetView(ViewByType type)
Definition: KeyView.cpp:414
void SetFilter(const wxString &filter)
Definition: KeyView.cpp:472
NormalizedKeyString GetKey(int index) const
Definition: KeyView.cpp:322
void RefreshBindings(const CommandIDs &names, const TranslatableStrings &categories, const TranslatableStrings &prefixes, const TranslatableStrings &labels, const std::vector< NormalizedKeyString > &keys, bool bSort)
Definition: KeyView.cpp:636
bool SetKeyByName(const CommandID &name, const NormalizedKeyString &key)
Definition: KeyView.cpp:396
wxString GetName() const override
Definition: KeyView.cpp:194
CommandID GetNameByKey(const NormalizedKeyString &key) const
Definition: KeyView.cpp:282
Base class for a panel in the PrefsDialog. Classes derived from this class include BatchPrefs,...
Definition: PrefsPanel.h:51
std::function< PrefsPanel *(wxWindow *parent, wxWindowID winid, AudacityProject *) > Factory
Definition: PrefsPanel.h:82
Definition: Prefs.h:178
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:640
Holds a msgid for the translation catalog; may also bind format arguments.
TranslatableString & Format(Args &&...args) &
Capture variadic format arguments (by copy) when there is no plural.
An alternative to using wxWindowAccessible, which in wxWidgets 3.1.1 contained GetParent() which was ...
Reads a file and passes the results through an XMLTagHandler.
Definition: XMLFileReader.h:19
const TranslatableString & GetErrorStr() const
bool Parse(XMLTagHandler *baseHandler, const FilePath &fname)
Wrapper to output XML data to files.
Definition: XMLWriter.h:84
T ReadObject(const wxString &key, const T &defaultValue) const
virtual bool Flush() noexcept=0
virtual bool HasEntry(const wxString &key) const =0
Checks whether specified key exists within the current group.
virtual bool Write(const wxString &key, bool value)=0
bool ReadBool(const wxString &key, bool defaultValue) const
bool DeleteEntry(const wxString &key)
Deletes specified entry if exists.
constexpr auto Menu
Items will appear in a main toolbar menu or in a sub-menu.
Definition: MenuRegistry.h:445
wxString Display(bool usesSpecialChars=false) const
Definition: Keyboard.cpp:56
Window placement information for wxWidgetsBasicUI can be constructed from a wxWindow pointer.