From cfa234ef09ce54b76fdbafd240eeb44320175db7 Mon Sep 17 00:00:00 2001 From: Cheng Date: Wed, 8 Nov 2023 07:23:12 +0000 Subject: [PATCH] Amended the dynamic menu handling for the case where we have many windows and you want to change the menu on change of focus Which is a classic example of writing code before it is needed. --- src/display_wallet.cpp | 21 +++++----- src/display_wallet.h | 2 +- src/frame.cpp | 88 ++++++++++++++++++++++++++++++++++++------ src/frame.h | 35 +++++++++++++++++ src/stdafx.h | 2 +- 5 files changed, 126 insertions(+), 22 deletions(-) diff --git a/src/display_wallet.cpp b/src/display_wallet.cpp index bc2b98b..5fbdadd 100644 --- a/src/display_wallet.cpp +++ b/src/display_wallet.cpp @@ -3,8 +3,7 @@ using ro::base58; static constexpr char SrcFilename[]{ "src/display_wallet.cpp" }; display_wallet::display_wallet(wxWindow* parent, wxFileName& walletfile) : wxPanel(parent, myID_WALLET_UI, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, wxT("Wallet")), - m_db(nullptr), - m_menuitem_add_name(this, &display_wallet::add_name_event_handler) + m_db(nullptr), m_DisplayWalletEditMenu(1) { wxLogMessage(wxT("Loading %s"), walletfile.GetFullPath()); try { @@ -73,10 +72,16 @@ display_wallet::display_wallet(wxWindow* parent, wxFileName& walletfile) : this->SetSize(this->GetParent()->GetClientSize()); singletonFrame->m_LastUsedWallet.Assign(walletfile); - wxMenu* menuFile{ singletonFrame->GetMenuBar()->GetMenu(0) }; - singletonFrame->GetMenuBar()->EnableTop(1, true); //enable edit menu. - wxMenu* menuEdit{ singletonFrame->GetMenuBar()->GetMenu(1) }; - m_menuitem_add_name.Insert(menuEdit, 0, "add name", "create new Zooko identity"); + m_DisplayWalletEditMenu.Menu->Append( + myID_DISPLAY_WALLET_ADD_NAME, + "add name", "create new Zooko identity" + ); + m_DisplayWalletEditMenu.Menu->Bind( + wxEVT_MENU, + &display_wallet::add_name_event_handler, + this, myID_DISPLAY_WALLET_ADD_NAME + ); + m_DisplayWalletEditMenu.Replace(); } catch (const MyException&) { throw; @@ -91,10 +96,8 @@ display_wallet::display_wallet(wxWindow* parent, wxFileName& walletfile) : } -display_wallet::~display_wallet() { - assert(true); - singletonFrame->GetMenuBar()->EnableTop(1, false); //disable edit menu. +display_wallet::~display_wallet() { } void display_wallet::close_menu_event_handler(wxCommandEvent& event) { diff --git a/src/display_wallet.h b/src/display_wallet.h index 00243fa..0080c5e 100644 --- a/src/display_wallet.h +++ b/src/display_wallet.h @@ -14,6 +14,6 @@ private: wxBoxSizer* m_rSizer; void close_menu_event_handler(wxCommandEvent&); void add_name_event_handler(wxCommandEvent&); - MenuLink m_menuitem_add_name; void OnClose(wxCloseEvent& event); + wxMenuTracker m_DisplayWalletEditMenu; }; diff --git a/src/frame.cpp b/src/frame.cpp index a712e29..2ac8cb9 100644 --- a/src/frame.cpp +++ b/src/frame.cpp @@ -1,6 +1,36 @@ #include "stdafx.h" static constexpr char SrcFilename[]{ "src/frame.cpp" }; +// ---------------------------------------------------------------------------- +//wxMenuTracker +// ---------------------------------------------------------------------------- + +wxMenuTracker::wxMenuTracker(const int i) : Menu(new wxMenu), MenuPosition(i) {}; + +wxMenu* wxMenuTracker::InitialAndFinal[]{ nullptr, new wxMenu, nullptr }; + +void wxMenuTracker::Replace() { + singletonFrame->GetMenuBar()->Replace( + MenuPosition, + Menu, + menu_strings[MenuPosition].head + ); + singletonFrame->GetMenuBar()->EnableTop(MenuPosition, true); //enable edit menu. +}; +wxMenuTracker::~wxMenuTracker() { + auto menu_bar = singletonFrame->GetMenuBar(); + if (menu_bar->GetMenu(MenuPosition) == Menu) { + assert(InitialAndFinal[MenuPosition]); + menu_bar->Replace( + MenuPosition, + InitialAndFinal[MenuPosition], + menu_strings[MenuPosition].head + ); + menu_bar->EnableTop(MenuPosition, false); + delete Menu; + } +}; + // ---------------------------------------------------------------------------- // frame // ---------------------------------------------------------------------------- @@ -157,18 +187,36 @@ Frame::Frame(const wxString& wxs) menuFile->Bind(wxEVT_MENU, &Frame::OnDeleteConfiguration, this, myID_DELETECONFIG); menuFile->Append(wxID_EXIT); menuFile->Bind(wxEVT_MENU, &Frame::OnExit, this, wxID_EXIT); - + + wxMenu* menuEdit = wxMenuTracker::InitialAndFinal[1]; wxMenu* menuHelp = new wxMenu; menuHelp->Append(wxID_ABOUT); menuHelp->Bind(wxEVT_MENU, &Frame::OnAbout, this, wxID_ABOUT); wxMenuBar* menuBar = new wxMenuBar; menuBar->Append(menuFile, menu_strings[0].head); - menuBar->Append(new wxMenu, menu_strings[1].head); //Edit menu, initially empty and disabled + menuBar->Append(menuEdit, menu_strings[1].head); //Edit menu, initially empty and disabled menuBar->Append(menuHelp, menu_strings[2].head); SetMenuBar(menuBar); - CreateStatusBar(); menuBar->EnableTop(1, false); //disable edit menu. - // child controls + if constexpr (debug_mode) { + // Check that the initial values of all replaceable menus + // are at their default values as if the window handling them + // had just been destroyed and not yet replaced. + for (int i = 0; i < std::size(wxMenuTracker::InitialAndFinal); i++) { + assert( + !wxMenuTracker::InitialAndFinal[i] + || + ( + wxMenuTracker::InitialAndFinal[i] == menuBar->GetMenu(i) + && + !menuBar->IsEnabledTop(i) + ) + ); + + } + } + CreateStatusBar(); + // child controls wxPanel* panel{ nullptr }; m_panel = panel; wxConfigBase& Config = singletonApp->m_Config; @@ -511,11 +559,29 @@ Frame::~Frame() { wxConfigBase& Config = singletonApp->m_Config; StorePositionToConfig(); Config.SetPath(wxT("/TipOfTheDay")); - Config.Write("show", (int)m_showTipsAtStartup); - Config.Write("index", (int)m_TipOfTheDayIndex); - Config.SetPath(wxT("/FileDialog")); - Config.Write("index", (int)m_FileDialogFilterIndex); - Config.Write(wxT("LastUsed"), m_LastUsedWallet.GetFullPath()); - Config.SetPath(wxT("/")); - Config.Flush(); + Config.Write("show", (int)m_showTipsAtStartup); + Config.Write("index", (int)m_TipOfTheDayIndex); + Config.SetPath(wxT("/FileDialog")); + Config.Write("index", (int)m_FileDialogFilterIndex); + Config.Write(wxT("LastUsed"), m_LastUsedWallet.GetFullPath()); + Config.SetPath(wxT("/")); + Config.Flush(); + if constexpr (debug_mode) { + // Check that the final values of all replaceable menus + // are at what they should be because the window handling + // them should have been destroyed. + auto menuBar = this->GetMenuBar(); + for (int i = 0; i < std::size(wxMenuTracker::InitialAndFinal); i++) { + assert( + !wxMenuTracker::InitialAndFinal[i] + || + ( + wxMenuTracker::InitialAndFinal[i] == menuBar->GetMenu(i) + && + !menuBar->IsEnabledTop(i) + ) + ); + + } + } } diff --git a/src/frame.h b/src/frame.h index 056872c..40800b5 100644 --- a/src/frame.h +++ b/src/frame.h @@ -1,4 +1,39 @@ #pragma once + // wxWidgets has drop down menus, wxMenu, owned by the wxMenuBar, + // which is owned by the Frame window, but menus subject to replacement need + // to be destroyed by the child window. + // wxMenuOwner tracks a drop down menu that might be at a location in + // the menubar, places it in that location in the menubar on demand, + // and on destruction destroys it + // If, on destruction wxMenuBar is still pointing to this wxMenu, it + // replaces it with the default (empty) menu for that location. + // and disables it. + // We maintain an array of default empty wxMenu menus that are nullptr for + // locations not subject to replacement, and are the disabled initial and final + // wxMenuBar values for locations in the Menubar where the real drop down + // in the menubar will be installed by wxMenuTracker. +struct wxMenuTracker { + wxMenuTracker(int); + void Replace(); + ~wxMenuTracker() noexcept; + // Non nullptr members of InitialAndFinal have to be explicitly initially placed + // in the menubar in the frame constructor and explicitly disabled in + // the frame initializer. In the debug build, congruence between the menu bar + // and InitialAndFinal should be checked with assert. + static wxMenu* InitialAndFinal[3]; + wxMenuTracker(wxMenuTracker&&) = delete; // Move constructor + wxMenuTracker(const wxMenuTracker&) = delete; // Copy constructor + wxMenuTracker& operator=(wxMenuTracker&&) = delete; // Move assignment. + wxMenuTracker& operator=(const wxMenuTracker&) = delete; // Copy assignment. + bool operator==(const wxMenuTracker&) const = delete; + auto operator<=>(const wxMenuTracker&) const = delete; + wxMenu* Menu; + const int MenuPosition; + // The menu title is defined elsewhere as an array + // of const strings + // menu_strings[MenuPosition].head +}; + template // This class exists to record the needed to unbind a drop down menu action and delete // the corresponding item from the drop down menu when the handler is destroyed. diff --git a/src/stdafx.h b/src/stdafx.h index fbe0ac1..2e633c2 100644 --- a/src/stdafx.h +++ b/src/stdafx.h @@ -106,7 +106,7 @@ enum MyIDs { myID_DELETECONFIG = wxID_HIGHEST + 1, myID_ERRORMESSAGE, myID_Hello, myID_MAINFRAME, myID_MAINFRAME_PANEL, myID_TESTWINDOW, myID_WELCOME_TO_ROCOIN, myID_WALLET_UI, - mID_CLOSE_WALLET, myID_MYEXIT + mID_CLOSE_WALLET, myID_MYEXIT, myID_DISPLAY_WALLET_ADD_NAME }; #include "localization.h"