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