Skip to content
Snippets Groups Projects
Select Git revision
  • ab2d11eeed673e74adf54222d8d21d1b58b6dcdb
  • master default protected
  • Audacity_3.5.1_FontBold
  • Audacity_3.5.1
  • release-3.2.3
  • alpha-3.2.0
  • darkaudacity
  • mac-2.1.1-vi
  • DarkAudacity-2.3.2x
  • Audacity-2.3.2
  • Audacity-2.3.1
  • Audacity-2.3.0
  • Audacity-2.2.2
  • Audacity-2.2.2-beta-20180128
  • Audacity-2.2.1
  • Audacity-2.2.1-rc3
  • Audacity-2.2.1-rc2
  • Audacity-2.2.1-rc1
  • Audacity-2.2.0
  • Audacity-2.2.0-rc1
  • Audacity-2.2.0-beta-20170901
  • Audacity-2.1.3
  • DarkAudacity-2.1.3x
  • Audacity-2.1.2
  • wx3-stable
  • wx3-unstable
  • Audacity-2.1.1
  • Audacity-2.1.0
28 results

MenuCreator.cpp

Blame
  • Forked from Hwanyong Lee / audacity-kr-font
    Source project has a limited visibility.
    MenuCreator.cpp 28.75 KiB
    /**********************************************************************
    
      Audacity: A Digital Audio Editor
    
      MenuCreator.cpp
    
      Dominic Mazzoni
      Brian Gunlogson
      et al.
    
      Paul Licameli split from Menus.cpp
    
    *******************************************************************//**
    
    \file MenuCreator.cpp
    \brief Functions for building toobar menus and enabling and disabling items
    
    *//****************************************************************//**
    
    \class MenuCreator
    \brief MenuCreator is responsible for creating the main menu bar.
    
    *//*******************************************************************/
    #include "MenuCreator.h"
    
    #include "ActiveProject.h"
    #include "CommandContext.h"
    #include "commands/CommandManagerWindowClasses.h"
    #include "KeyboardCapture.h"
    #include "Journal.h"
    #include "JournalOutput.h"
    #include "JournalRegistry.h"
    #include "Registry.h"
    #include "ProjectWindows.h"
    #include "AudacityMessageBox.h"
    
    #include <wx/evtloop.h>
    #include <wx/frame.h>
    #include <wx/log.h>
    #include <wx/menu.h>
    #include <wx/windowptr.h>
    
    namespace {
    struct MenuBarListEntry
    {
       MenuBarListEntry(const wxString &name, wxMenuBar *menubar);
       ~MenuBarListEntry();
    
       wxString name;
       wxWeakRef<wxMenuBar> menubar; // This structure does not assume memory ownership!
    };
    
    struct SubMenuListEntry
    {
       SubMenuListEntry();
       SubMenuListEntry(SubMenuListEntry&&) = default;
       ~SubMenuListEntry();
    
       TranslatableString name;
       std::unique_ptr<wxMenu> menu;
    };
    
    MenuBarListEntry::MenuBarListEntry(const wxString &name, wxMenuBar *menubar)
       : name{ name }, menubar{ menubar }
    {
    }
    
    MenuBarListEntry::~MenuBarListEntry()
    {
    }
    
    SubMenuListEntry::SubMenuListEntry()
       : menu{ std::make_unique<wxMenu>() }
    {
    }
    
    SubMenuListEntry::~SubMenuListEntry()
    {
    }
    }
    
    MenuCreator::SpecialItem::~SpecialItem() = default;
    
    MenuCreator::MenuCreator(AudacityProject &project)
       : CommandManager{ project }
    {
    }
    
    MenuCreator::~MenuCreator() = default;
    
    MenuCreator &MenuCreator::Get(AudacityProject &project)
    {
       return static_cast<MenuCreator&>(CommandManager::Get(project));
    }
    
    const MenuCreator &MenuCreator::Get(const AudacityProject &project)
    {
       return static_cast<const MenuCreator&>(CommandManager::Get(project));
    }
    
    /// CreateMenusAndCommands builds the menus, and also rebuilds them after
    /// changes in configured preferences - for example changes in key-bindings
    /// affect the short-cut key legend that appears beside each command,
    
    namespace {
    
    using namespace MenuRegistry;
    
    struct MenuItemVisitor final : CommandManager::Populator {
       explicit MenuItemVisitor(AudacityProject &proj)
       : CommandManager::Populator { proj,
          // leaf visit
          [this](const auto &item, const auto&) {
             const auto pCurrentMenu = CurrentMenu();
             if (!pCurrentMenu) {
                // There may have been a mistake in the placement hint that
                // registered this single item.  It's not within any menu.
                assert(false);
             }
             else TypeSwitch::VDispatch<void, LeafTypes>(item,
                [&](const SpecialItem &special) {
                   if (auto pSpecial =
                      dynamic_cast<const MenuCreator::SpecialItem*>(&special))
                      pSpecial->fn(mProject, *pCurrentMenu);
                },
                [this](auto &item){ DoVisit(item); }
             );
          },
    
          [this]{
             if (mbSeparatorAllowed)
                CurrentMenu()->AppendSeparator();
             Populator::DoSeparator();
          }
       }
       {
          auto menubar = AddMenuBar(wxT("appmenu"));
          assert(menubar);
    
          MenuRegistry::Visit(*this, mProject);
    
          GetProjectFrame(mProject).SetMenuBar(menubar.release());
       }
    
       ~MenuItemVisitor() override;
    
       struct CommandListEntryEx final : CommandManager::CommandListEntry {
          ~CommandListEntryEx() final;
          void UpdateCheckmark(AudacityProject &project) final;
          void Modify(const TranslatableString &newLabel) final;
          bool GetEnabled() const final;
          void Check(bool checked) final;
          void Enable(bool enabled) final;
          void EnableMultiItem(bool enabled) final;
    
          wxMenu *menu{};
       };
    
    private:
       std::unique_ptr<CommandManager::CommandListEntry>
          AllocateEntry(const MenuRegistry::Options &options) final;
       void VisitEntry(CommandManager::CommandListEntry &,
          const MenuRegistry::Options *pOptions) final;
       void BeginMenu(const TranslatableString & tName) final;
       void EndMenu() final;
       void BeginMainMenu(const TranslatableString & tName);
       void EndMainMenu();
       void BeginSubMenu(const TranslatableString & tName);
       void EndSubMenu();
       void BeginOccultCommands() final;
       void EndOccultCommands() final;
    
       std::unique_ptr<wxMenuBar> AddMenuBar(const wxString & sMenu);
       wxMenuBar * CurrentMenuBar() const;
       wxMenuBar * GetMenuBar(const wxString & sMenu) const;
       wxMenu * CurrentSubMenu() const;
       wxMenu * CurrentMenu() const;
    
       std::vector<MenuBarListEntry> mMenuBarList;
       std::vector<SubMenuListEntry> mSubMenuList;
       std::unique_ptr<wxMenuBar> mTempMenuBar;
       std::unique_ptr<wxMenu> uCurrentMenu;
       wxMenu *mCurrentMenu {};
    };
    
    MenuItemVisitor::CommandListEntryEx::~CommandListEntryEx() = default;
    
    void
    MenuItemVisitor::CommandListEntryEx::UpdateCheckmark(AudacityProject &project)
    {
       if (menu && checkmarkFn && !isOccult) {
          menu->Check(id, checkmarkFn(project));
       }
    }
    
    void
    MenuItemVisitor::CommandListEntryEx::Modify(const TranslatableString &newLabel)
    {
       if (menu) {
          label = newLabel;
          menu->SetLabel(id, FormatLabelForMenu());
       }
    }
    
    bool MenuItemVisitor::CommandListEntryEx::GetEnabled() const
    {
       if (!menu)
          return false;
       return enabled;
    }
    
    void MenuItemVisitor::CommandListEntryEx::Check(bool checked)
    {
       if (!menu || isOccult)
          return;
       menu->Check(id, checked);
    }
    
    void MenuItemVisitor::CommandListEntryEx::Enable(bool b)
    {
       if (!menu) {
          enabled = b;
          return;
       }
    
       // LL:  Refresh from real state as we can get out of sync on the
       //      Mac due to its reluctance to enable menus when in a modal
       //      state.
       enabled = menu->IsEnabled(id);
    
       // Only enabled if needed
       if (enabled != b) {
          menu->Enable(id, b);
          enabled = menu->IsEnabled(id);
       }
    }
    
    void MenuItemVisitor::CommandListEntryEx::EnableMultiItem(bool b)
    {
       if (menu) {
          const auto item = menu->FindItem(id);
          if (item) {
             item->Enable(b);
             return;
          }
       }
       // using GET in a log message for devs' eyes only
       wxLogDebug(wxT("Warning: Menu entry with id %i in %s not found"),
           id, name.GET());
    }
    
    auto MenuItemVisitor::AllocateEntry(const MenuRegistry::Options &options)
       -> std::unique_ptr<CommandManager::CommandListEntry>
    {
       auto result = std::make_unique<CommandListEntryEx>();
       if (!options.global)
          result->menu = CurrentMenu();
       return result;
    }
    
    // A label that may have its accelerator disabled.
    // The problem is that as soon as we show accelerators in the menu, the menu might
    // catch them in normal wxWidgets processing, rather than passing the key presses on
    // to the controls that had the focus.  We would like all the menu accelerators to be
    // disabled, in fact.
    static wxString
    FormatLabelWithDisabledAccel(const CommandManager::CommandListEntry &entry)
    {
       auto label = entry.label.Translation();
    #if 1
       wxString Accel;
       do{
          if (!entry.key.empty())
          {
             // Dummy accelerator that looks Ok in menus but is non functional.
             // Note the space before the key.
    #ifdef __WXMSW__
             // using GET to compose menu item name for wxWidgets
             auto key = entry.key.GET();
             Accel = wxString("\t ") + key;
             if( key.StartsWith("Left" )) break;
             if( key.StartsWith("Right")) break;
             if( key.StartsWith("Up" )) break;
             if( key.StartsWith("Down")) break;
             if( key.StartsWith("Return")) break;
             if( key.StartsWith("Tab")) break;
             if( key.StartsWith("Shift+Tab")) break;
             if( key.StartsWith("0")) break;
             if( key.StartsWith("1")) break;
             if( key.StartsWith("2")) break;
             if( key.StartsWith("3")) break;
             if( key.StartsWith("4")) break;
             if( key.StartsWith("5")) break;
             if( key.StartsWith("6")) break;
             if( key.StartsWith("7")) break;
             if( key.StartsWith("8")) break;
             if( key.StartsWith("9")) break;
             // Uncomment the below so as not to add the illegal accelerators.
             // Accel = "";
             //if( entry.key.StartsWith("Space" )) break;
             // These ones appear to be illegal already and mess up accelerator processing.
             if( key.StartsWith("NUMPAD_ENTER" )) break;
             if( key.StartsWith("Backspace" )) break;
             if( key.StartsWith("Delete" )) break;
    
             // https://github.com/audacity/audacity/issues/4457
             // This code was proposed by David Bailes to fix
             // the decimal separator input in wxTextCtrls that
             // are children of the main window.
             if( key.StartsWith(",") ) break;
             if( key.StartsWith(".") ) break;
    
             // https://github.com/audacity/audacity/issues/5868
             // On German and Norwegian keyboards, [ and ] are
             // AltGr 8 and AltGr 9. On Windows typing 8 or 9 match
             // [ or ] repectively when they are accelerators in menus.
             if ( key.StartsWith("[") ) break;
             if ( key.StartsWith("]") ) break;
    
    #endif
             //wxLogDebug("Added Accel:[%s][%s]", entry.label, entry.key );
             // Normal accelerator.
             // using GET to compose menu item name for wxWidgets
             Accel = wxString("\t") + entry.key.GET();
          }
       } while (false );
       label += Accel;
    #endif
       return label;
    }
    
    //추가된 코드
    wxFont fontB(wxFontInfo(10).FaceName("Malgun Gothic").Bold());
    
    void MenuItemVisitor::VisitEntry(CommandManager::CommandListEntry &entry,
       const MenuRegistry::Options *pOptions)
    {
       if (!pOptions) {
          // 추가된코드
          wxMenuItem* menu_item = new wxMenuItem(NULL, entry.id, entry.FormatLabelForMenu());
          menu_item->SetFont(fontB);
          CurrentMenu()->Append(menu_item);
          // command list item
          //CurrentMenu()->Append(entry.id, entry.FormatLabelForMenu());
       }
       else if (pOptions->global)
          ;
       else {
          auto ID = entry.id;
          auto label = FormatLabelWithDisabledAccel(entry);
          auto &checker = pOptions->checker;
          if (checker) {
             //추가된코드
             wxMenuItem* menu_item = new wxMenuItem(NULL, ID, label, wxEmptyString, wxITEM_CHECK, NULL);
             menu_item->SetFont(fontB);
             CurrentMenu()->Append(menu_item);
    
             //CurrentMenu()->AppendCheckItem(ID, label);
             CurrentMenu()->Check(ID, checker(mProject));
          }
          else {
             //추가된 코드
             wxMenuItem* menu_item = new wxMenuItem(NULL, ID, label);
             menu_item->SetFont(fontB);
             CurrentMenu()->Append(menu_item);
    
             //CurrentMenu()->Append(ID, label);
          }
       }
    }
    
    MenuItemVisitor::~MenuItemVisitor() = default;
    
    void MenuItemVisitor::BeginMenu(const TranslatableString & tName)
    {
       if (mCurrentMenu)
          return BeginSubMenu(tName);
       else
          return BeginMainMenu(tName);
    }
    
    /// This attaches a menu, if it's main, to the menubar
    //  and in all cases ends the menu
    void MenuItemVisitor::EndMenu()
    {
       if (mSubMenuList.empty())
          EndMainMenu();
       else
          EndSubMenu();
    }
    
    void MenuItemVisitor::BeginMainMenu(const TranslatableString & tName)
    {
       uCurrentMenu = std::make_unique<wxMenu>();
       mCurrentMenu = uCurrentMenu.get();
    }
    
    /// This attaches a menu to the menubar and ends the menu
    void MenuItemVisitor::EndMainMenu()
    {
       // Add the menu to the menubar after all menu items have been
       // added to the menu to allow OSX to rearrange special menu
       // items like Preferences, About, and Quit.
       assert(uCurrentMenu);
       CurrentMenuBar()->Append(
          uCurrentMenu.release(), MenuNames()[0].Translation());
       mCurrentMenu = nullptr;
    }
    
    /// This starts a new submenu, and names it according to
    /// the function's argument.
    void MenuItemVisitor::BeginSubMenu(const TranslatableString & tName)
    {
       mSubMenuList.emplace_back();
       mbSeparatorAllowed = false;
    }
    
    /// This function is called after the final item of a SUBmenu is added.
    /// Submenu items are added just like regular menu items; they just happen
    /// after BeginSubMenu() is called but before EndSubMenu() is called.
    void MenuItemVisitor::EndSubMenu()
    {
       //Save the submenu's information
       SubMenuListEntry tmpSubMenu{ std::move( mSubMenuList.back() ) };
    
       //Pop off the NEW submenu so CurrentMenu returns the parent of the submenu
       mSubMenuList.pop_back();
    
       //Add the submenu to the current menu
       auto name = MenuNames().back().Translation();
    
       //추가된코드
       wxMenuItem* menu_item = new wxMenuItem(NULL, 0, name, name, wxITEM_NORMAL, tmpSubMenu.menu.release());
       menu_item->SetFont(fontB);
       CurrentMenu()->Append(menu_item);
    
       //CurrentMenu()->Append(0, name, tmpSubMenu.menu.release(), name /* help string */ );
       mbSeparatorAllowed = true;
    }
    
    void MenuItemVisitor::BeginOccultCommands()
    {
       // To do:  perhaps allow occult item switching at lower levels of the
       // menu tree.
       assert(!CurrentMenu());
    
       // Make a temporary menu bar collecting items added after.
       // This bar will be discarded but other side effects on the command
       // manager persist.
       mTempMenuBar = AddMenuBar(wxT("ext-menu"));
    }
    
    void MenuItemVisitor::EndOccultCommands()
    {
       auto iter = mMenuBarList.end();
       if (iter != mMenuBarList.begin())
          mMenuBarList.erase(--iter);
       else
          assert(false);
       mTempMenuBar.reset();
    }
    
    }
    
    void MenuCreator::CreateMenusAndCommands()
    {
       {
          MenuItemVisitor visitor{ mProject };
       }
    
       mLastFlags = AlwaysEnabledFlag;
    
    #if defined(_DEBUG)
    //   c->CheckDups();
    #endif
    }
    
    // Get hackcess to a protected method
    class wxFrameEx : public wxFrame
    {
    public:
       using wxFrame::DetachMenuBar;
    };
    
    void MenuCreator::RebuildMenuBar()
    {
       auto &project = mProject;
       // On OSX, we can't rebuild the menus while a modal dialog is being shown
       // since the enabled state for menus like Quit and Preference gets out of
       // sync with wxWidgets idea of what it should be.
    #if defined(__WXMAC__) && defined(_DEBUG)
       {
          wxDialog *dlg =
             wxDynamicCast(wxGetTopLevelParent(wxWindow::FindFocus()), wxDialog);
          assert((!dlg || !dlg->IsModal()));
       }
    #endif
    
       // Delete the menus, since we will soon recreate them.
       // Rather oddly, the menus don't vanish as a result of doing this.
       {
          auto &window = static_cast<wxFrameEx&>( GetProjectFrame( project ) );
          wxWindowPtr<wxMenuBar> menuBar{ window.GetMenuBar() };
          window.DetachMenuBar();
          // menuBar gets deleted here
       }
    
       PurgeData();
       CreateMenusAndCommands();
    }
    
    constexpr auto JournalCode = wxT("CM");  // for CommandManager
    
    void MenuCreator::ExecuteCommand(const CommandContext &context,
       const wxEvent *evt, const CommandListEntry &entry)
    {
       Journal::Output({ JournalCode, entry.name.GET() });
       return CommandManager::ExecuteCommand(context, evt, entry);
    }
    
    // a temporary hack that should be removed as soon as we
    // get multiple effect preview working
    bool MenuCreator::ReallyDoQuickCheck()
    {
       return !GetProjectFrame(mProject).IsActive();
    }
    
    /// The following method moves to the previous track
    /// selecting and unselecting depending if you are on the start of a
    /// block or not.
    
    void MenuCreator::RebuildAllMenuBars()
    {
       for( auto p : AllProjects{} ) {
          Get(*p).RebuildMenuBar();
    #if defined(__WXGTK__)
          // Workaround for:
          //
          //   http://bugzilla.audacityteam.org/show_bug.cgi?id=458
          //
          // This workaround should be removed when Audacity updates to wxWidgets 3.x which has a fix.
          auto &window = GetProjectFrame( *p );
          wxRect r = window.GetRect();
          window.SetSize(wxSize(1,1));
          window.SetSize(r.GetSize());
    #endif
       }
    }
    
    void MenuCreator::RemoveDuplicateShortcuts()
    {
       const auto disabledShortcuts = ReportDuplicateShortcuts();
       if (!disabledShortcuts.Translation().empty()) {
          TranslatableString message = XO("The following commands have had their shortcuts removed,"
          " because their default shortcut is new or changed, and is the same shortcut"
          " that you have assigned to another command.")
             + disabledShortcuts;
          AudacityMessageBox(message, XO("Shortcuts have been removed"), wxOK | wxCENTRE);
    
          gPrefs->Flush();
          RebuildAllMenuBars();
       }
    }
    
    static CommandManager::Factory::SubstituteInShared<MenuCreator> scope;
    
    namespace {
    
    // Register a callback for the journal
    Journal::RegisteredCommand sCommand{ JournalCode,
    []( const wxArrayStringEx &fields )
    {
       // Expect JournalCode and the command name.
       // To do, perhaps, is to include some parameters.
       bool handled = false;
       if ( fields.size() == 2 ) {
          if (auto project = GetActiveProject().lock()) {
             auto pManager = &CommandManager::Get( *project );
             auto flags = CommandManager::Get( *project ).GetUpdateFlags();
             const CommandContext context( *project );
             auto &command = fields[1];
             handled =
                pManager->HandleTextualCommand( command, context, flags, false );
          }
       }
       return handled;
    }
    };
    
    }
    
    bool MenuCreator::FilterKeyEvent(
       AudacityProject &project, const wxKeyEvent & evt, bool permit)
    {
       auto &cm = Get(project);
       
       auto pWindow = FindProjectFrame(&project);
       CommandListEntry *entry = cm.mCommandKeyHash[KeyEventToKeyString(evt)];
       if (entry == NULL)
       {
          return false;
       }
    
       int type = evt.GetEventType();
    
       // Global commands aren't tied to any specific project
       if (entry->isGlobal && type == wxEVT_KEY_DOWN)
       {
          // Global commands are always disabled so they do not interfere with the
          // rest of the command handling.  But, to use the common handler, we
          // enable them temporarily and then disable them again after handling.
          // LL:  Why do they need to be disabled???
          entry->enabled = false;
          auto cleanup = valueRestorer( entry->enabled, true );
          return cm.HandleCommandEntry(entry, NoFlagsSpecified, false, &evt);
       }
    
       wxWindow * pFocus = wxWindow::FindFocus();
       wxWindow * pParent = wxGetTopLevelParent( pFocus );
       bool validTarget = pParent == pWindow;
       // Bug 1557.  MixerBoard should count as 'destined for project'
       // MixerBoard IS a TopLevelWindow, and its parent is the project.
       if( pParent && pParent->GetParent() == pWindow ){
          if(auto keystrokeHandlingWindow = dynamic_cast< TopLevelKeystrokeHandlingWindow* >( pParent ))
             validTarget = keystrokeHandlingWindow->HandleCommandKeystrokes();
       }
       validTarget = validTarget && wxEventLoop::GetActive()->IsMain();
    
       // Any other keypresses must be destined for this project window
       if (!permit && !validTarget )
       {
          return false;
       }
    
       auto flags = cm.GetUpdateFlags();
    
       wxKeyEvent temp = evt;
    
       // Possibly let wxWidgets do its normal key handling IF it is one of
       // the standard navigation keys.
       if((type == wxEVT_KEY_DOWN) || (type == wxEVT_KEY_UP ))
       {
          wxWindow * pWnd = wxWindow::FindFocus();
          bool bIntercept =
             pWnd && !dynamic_cast< NonKeystrokeInterceptingWindow * >( pWnd );
    
          //wxLogDebug("Focus: %p TrackPanel: %p", pWnd, pTrackPanel );
          // We allow the keystrokes below to be handled by wxWidgets controls IF we are
          // in some sub window rather than in the TrackPanel itself.
          // Otherwise they will go to our command handler and if it handles them
          // they will NOT be available to wxWidgets.
          if( bIntercept ){
             switch( evt.GetKeyCode() ){
             case WXK_LEFT:
             case WXK_RIGHT:
             case WXK_UP:
             case WXK_DOWN:
             // Don't trap WXK_SPACE (Bug 1727 - SPACE not starting/stopping playback
             // when cursor is in a time control)
             // case WXK_SPACE:
             case WXK_TAB:
             case WXK_BACK:
             case WXK_HOME:
             case WXK_END:
             case WXK_RETURN:
             case WXK_NUMPAD_ENTER:
             case WXK_DELETE:
             case '0':
             case '1':
             case '2':
             case '3':
             case '4':
             case '5':
             case '6':
             case '7':
             case '8':
             case '9':
                return false;
             case ',':
             case '.':
                if (!evt.HasAnyModifiers())
                   return false;
             }
          }
       }
    
       if (type == wxEVT_KEY_DOWN)
       {
          if (entry->skipKeydown)
          {
             return true;
          }
          return cm.HandleCommandEntry(entry, flags, false, &temp);
       }
    
       if (type == wxEVT_KEY_UP && entry->wantKeyup)
       {
          return cm.HandleCommandEntry(entry, flags, false, &temp);
       }
    
       return false;
    }
    
    static KeyboardCapture::PreFilter::Scope scope1{
    []( wxKeyEvent & ) {
       // We must have a project since we will be working with the
       // CommandManager, which is tied to individual projects.
       auto project = GetActiveProject().lock();
       return project && GetProjectFrame( *project ).IsEnabled();
    } };
    static KeyboardCapture::PostFilter::Scope scope2{
    []( wxKeyEvent &key ) {
       // Capture handler window didn't want it, so ask the CommandManager.
       if (auto project = GetActiveProject().lock()) {
          return MenuCreator::FilterKeyEvent(*project, key);
       }
       else
          return false;
    } };
    
    
    NormalizedKeyString KeyEventToKeyString(const wxKeyEvent & event)
    {
       wxString newStr;
    
       long key = event.GetKeyCode();
    
       if (event.ControlDown())
          newStr += wxT("Ctrl+");
    
       if (event.AltDown())
          newStr += wxT("Alt+");
    
       if (event.ShiftDown())
          newStr += wxT("Shift+");
    
    #if defined(__WXMAC__)
       if (event.RawControlDown())
          newStr += wxT("RawCtrl+");
    #endif
    
       if (event.RawControlDown() && key >= 1 && key <= 26)
          newStr += (wxChar)(64 + key);
       else if (key >= 33 && key <= 255 && key != 127)
          newStr += (wxChar)key;
       else
       {
          switch(key)
          {
          case WXK_BACK:
             newStr += wxT("Backspace");
             break;
          case WXK_DELETE:
             newStr += wxT("Delete");
             break;
          case WXK_SPACE:
             newStr += wxT("Space");
             break;
          case WXK_TAB:
             newStr += wxT("Tab");
             break;
          case WXK_RETURN:
             newStr += wxT("Return");
             break;
          case WXK_PAGEUP:
             newStr += wxT("PageUp");
             break;
          case WXK_PAGEDOWN:
             newStr += wxT("PageDown");
             break;
          case WXK_END:
             newStr += wxT("End");
             break;
          case WXK_HOME:
             newStr += wxT("Home");
             break;
          case WXK_LEFT:
             newStr += wxT("Left");
             break;
          case WXK_UP:
             newStr += wxT("Up");
             break;
          case WXK_RIGHT:
             newStr += wxT("Right");
             break;
          case WXK_DOWN:
             newStr += wxT("Down");
             break;
          case WXK_ESCAPE:
             newStr += wxT("Escape");
             break;
          case WXK_INSERT:
             newStr += wxT("Insert");
             break;
          case WXK_NUMPAD0:
             newStr += wxT("NUMPAD0");
             break;
          case WXK_NUMPAD1:
             newStr += wxT("NUMPAD1");
             break;
          case WXK_NUMPAD2:
             newStr += wxT("NUMPAD2");
             break;
          case WXK_NUMPAD3:
             newStr += wxT("NUMPAD3");
             break;
          case WXK_NUMPAD4:
             newStr += wxT("NUMPAD4");
             break;
          case WXK_NUMPAD5:
             newStr += wxT("NUMPAD5");
             break;
          case WXK_NUMPAD6:
             newStr += wxT("NUMPAD6");
             break;
          case WXK_NUMPAD7:
             newStr += wxT("NUMPAD7");
             break;
          case WXK_NUMPAD8:
             newStr += wxT("NUMPAD8");
             break;
          case WXK_NUMPAD9:
             newStr += wxT("NUMPAD9");
             break;
          case WXK_MULTIPLY:
             newStr += wxT("*");
             break;
          case WXK_ADD:
             newStr += wxT("+");
             break;
          case WXK_SUBTRACT:
             newStr += wxT("-");
             break;
          case WXK_DECIMAL:
             newStr += wxT(".");
             break;
          case WXK_DIVIDE:
             newStr += wxT("/");
             break;
          case WXK_F1:
             newStr += wxT("F1");
             break;
          case WXK_F2:
             newStr += wxT("F2");
             break;
          case WXK_F3:
             newStr += wxT("F3");
             break;
          case WXK_F4:
             newStr += wxT("F4");
             break;
          case WXK_F5:
             newStr += wxT("F5");
             break;
          case WXK_F6:
             newStr += wxT("F6");
             break;
          case WXK_F7:
             newStr += wxT("F7");
             break;
          case WXK_F8:
             newStr += wxT("F8");
             break;
          case WXK_F9:
             newStr += wxT("F9");
             break;
          case WXK_F10:
             newStr += wxT("F10");
             break;
          case WXK_F11:
             newStr += wxT("F11");
             break;
          case WXK_F12:
             newStr += wxT("F12");
             break;
          case WXK_F13:
             newStr += wxT("F13");
             break;
          case WXK_F14:
             newStr += wxT("F14");
             break;
          case WXK_F15:
             newStr += wxT("F15");
             break;
          case WXK_F16:
             newStr += wxT("F16");
             break;
          case WXK_F17:
             newStr += wxT("F17");
             break;
          case WXK_F18:
             newStr += wxT("F18");
             break;
          case WXK_F19:
             newStr += wxT("F19");
             break;
          case WXK_F20:
             newStr += wxT("F20");
             break;
          case WXK_F21:
             newStr += wxT("F21");
             break;
          case WXK_F22:
             newStr += wxT("F22");
             break;
          case WXK_F23:
             newStr += wxT("F23");
             break;
          case WXK_F24:
             newStr += wxT("F24");
             break;
          case WXK_NUMPAD_ENTER:
             newStr += wxT("NUMPAD_ENTER");
             break;
          case WXK_NUMPAD_F1:
             newStr += wxT("NUMPAD_F1");
             break;
          case WXK_NUMPAD_F2:
             newStr += wxT("NUMPAD_F2");
             break;
          case WXK_NUMPAD_F3:
             newStr += wxT("NUMPAD_F3");
             break;
          case WXK_NUMPAD_F4:
             newStr += wxT("NUMPAD_F4");
             break;
          case WXK_NUMPAD_HOME:
             newStr += wxT("NUMPAD_HOME");
             break;
          case WXK_NUMPAD_LEFT:
             newStr += wxT("NUMPAD_LEFT");
             break;
          case WXK_NUMPAD_UP:
             newStr += wxT("NUMPAD_UP");
             break;
          case WXK_NUMPAD_RIGHT:
             newStr += wxT("NUMPAD_RIGHT");
             break;
          case WXK_NUMPAD_DOWN:
             newStr += wxT("NUMPAD_DOWN");
             break;
          case WXK_NUMPAD_PAGEUP:
             newStr += wxT("NUMPAD_PAGEUP");
             break;
          case WXK_NUMPAD_PAGEDOWN:
             newStr += wxT("NUMPAD_PAGEDOWN");
             break;
          case WXK_NUMPAD_END:
             newStr += wxT("NUMPAD_END");
             break;
          case WXK_NUMPAD_BEGIN:
             newStr += wxT("NUMPAD_HOME");
             break;
          case WXK_NUMPAD_INSERT:
             newStr += wxT("NUMPAD_INSERT");
             break;
          case WXK_NUMPAD_DELETE:
             newStr += wxT("NUMPAD_DELETE");
             break;
          case WXK_NUMPAD_EQUAL:
             newStr += wxT("NUMPAD_EQUAL");
             break;
          case WXK_NUMPAD_MULTIPLY:
             newStr += wxT("NUMPAD_MULTIPLY");
             break;
          case WXK_NUMPAD_ADD:
             newStr += wxT("NUMPAD_ADD");
             break;
          case WXK_NUMPAD_SUBTRACT:
             newStr += wxT("NUMPAD_SUBTRACT");
             break;
          case WXK_NUMPAD_DECIMAL:
             newStr += wxT("NUMPAD_DECIMAL");
             break;
          case WXK_NUMPAD_DIVIDE:
             newStr += wxT("NUMPAD_DIVIDE");
             break;
          default:
             return {}; // Don't do anything if we don't recognize the key
          }
       }
    
       return NormalizedKeyString{ newStr };
    }
    
    ///
    /// Makes a NEW menubar for placement on the top of a project
    /// Names it according to the passed-in string argument.
    ///
    /// If the menubar already exists, that's unexpected.
    std::unique_ptr<wxMenuBar> MenuItemVisitor::AddMenuBar(const wxString & sMenu)
    {
       wxMenuBar *menuBar = GetMenuBar(sMenu);
       if (menuBar) {
          wxASSERT(false);
          return {};
       }
    
       auto result = std::make_unique<wxMenuBar>();
       mMenuBarList.emplace_back(sMenu, result.get());
    
       return result;
    }
    
    ///
    /// Retrieves the menubar based on the name given in AddMenuBar(name)
    ///
    wxMenuBar *MenuItemVisitor::GetMenuBar(const wxString & sMenu) const
    {
       for (const auto &entry : mMenuBarList)
       {
          if(entry.name == sMenu)
             return entry.menubar;
       }
    
       return NULL;
    }
    
    ///
    /// Retrieve the 'current' menubar; either NULL or the
    /// last on in the mMenuBarList.
    wxMenuBar *MenuItemVisitor::CurrentMenuBar() const
    {
       if(mMenuBarList.empty())
          return NULL;
    
       return mMenuBarList.back().menubar;
    }
    
    /// This returns the 'Current' Submenu, which is the one at the
    ///  end of the mSubMenuList (or NULL, if it doesn't exist).
    wxMenu *MenuItemVisitor::CurrentSubMenu() const
    {
       if(mSubMenuList.empty())
          return NULL;
    
       return mSubMenuList.back().menu.get();
    }
    
    ///
    /// This returns the current menu that we're appending to - note that
    /// it could be a submenu if BeginSubMenu was called and we haven't
    /// reached EndSubMenu yet.
    wxMenu * MenuItemVisitor::CurrentMenu() const
    {
       if(!mCurrentMenu)
          return NULL;
    
       wxMenu * tmpCurrentSubMenu = CurrentSubMenu();
    
       if(!tmpCurrentSubMenu)
       {
          return mCurrentMenu;
       }
    
       return tmpCurrentSubMenu;
    }