diff --git a/mac/Audacity.xcodeproj/project.pbxproj b/mac/Audacity.xcodeproj/project.pbxproj index da5cd3293..026a087b2 100644 --- a/mac/Audacity.xcodeproj/project.pbxproj +++ b/mac/Audacity.xcodeproj/project.pbxproj @@ -1238,6 +1238,7 @@ 5ED1D0B11CDE560C00471E3C /* BackedPanel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5ED1D0AF1CDE560C00471E3C /* BackedPanel.cpp */; }; 5EF17C231D1F0A690090A642 /* ScrubbingToolBar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5EF17C211D1F0A690090A642 /* ScrubbingToolBar.cpp */; }; 5EF958851DEB121800191280 /* InconsistencyException.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5EF958831DEB121800191280 /* InconsistencyException.cpp */; }; + 5E73963B1DAFD82D00BA0A4D /* PopupMenuTable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E7396391DAFD82D00BA0A4D /* PopupMenuTable.cpp */; }; 8406A93812D0F2510011EA01 /* EQDefaultCurves.xml in Resources */ = {isa = PBXBuildFile; fileRef = 8406A93712D0F2510011EA01 /* EQDefaultCurves.xml */; }; 8484F31413086237002DF7F0 /* DeviceManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8484F31213086237002DF7F0 /* DeviceManager.cpp */; }; AA0084191EA8C6E70070CCE3 /* TracksBehaviorsPrefs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AA0084181EA8C6E70070CCE3 /* TracksBehaviorsPrefs.cpp */; }; @@ -3072,6 +3073,8 @@ 5EF17C221D1F0A690090A642 /* ScrubbingToolBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScrubbingToolBar.h; sourceTree = ""; }; 5EF958831DEB121800191280 /* InconsistencyException.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InconsistencyException.cpp; sourceTree = ""; }; 5EF958841DEB121800191280 /* InconsistencyException.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InconsistencyException.h; sourceTree = ""; }; + 5E7396391DAFD82D00BA0A4D /* PopupMenuTable.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PopupMenuTable.cpp; sourceTree = ""; }; + 5E73963A1DAFD82D00BA0A4D /* PopupMenuTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PopupMenuTable.h; sourceTree = ""; }; 82FF184D13CF01A600C1B664 /* dBTable.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dBTable.cpp; path = sbsms/src/dBTable.cpp; sourceTree = ""; }; 82FF184E13CF01A600C1B664 /* dBTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dBTable.h; path = sbsms/src/dBTable.h; sourceTree = ""; }; 82FF184F13CF01A600C1B664 /* slide.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = slide.cpp; path = sbsms/src/slide.cpp; sourceTree = ""; }; @@ -4442,6 +4445,8 @@ 5ED1D0A91CDE55BD00471E3C /* Overlay.cpp */, 5ED1D0AA1CDE55BD00471E3C /* Overlay.h */, 5ED1D0AB1CDE55BD00471E3C /* OverlayPanel.cpp */, + 5E7396391DAFD82D00BA0A4D /* PopupMenuTable.cpp */, + 5E73963A1DAFD82D00BA0A4D /* PopupMenuTable.h */, 5ED1D0AC1CDE55BD00471E3C /* OverlayPanel.h */, 28530C4A0DF2105200555C94 /* ProgressDialog.cpp */, 28530C4B0DF2105200555C94 /* ProgressDialog.h */, @@ -7606,6 +7611,7 @@ 1790B18709883BFD008A330A /* QualityPrefs.cpp in Sources */, 1790B18809883BFD008A330A /* SpectrumPrefs.cpp in Sources */, 1790B18909883BFD008A330A /* Prefs.cpp in Sources */, + 5E73963B1DAFD82D00BA0A4D /* PopupMenuTable.cpp in Sources */, 1790B18A09883BFD008A330A /* Printing.cpp in Sources */, 1790B18B09883BFD008A330A /* Project.cpp in Sources */, 1790B18C09883BFD008A330A /* Resample.cpp in Sources */, diff --git a/src/Makefile.am b/src/Makefile.am index 4feaa1f2f..d97dfa792 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -622,6 +622,8 @@ audacity_SOURCES = \ widgets/Overlay.h \ widgets/OverlayPanel.cpp \ widgets/OverlayPanel.h \ + widgets/PopupMenuTable.cpp \ + widgets/PopupMenuTable.h \ widgets/ProgressDialog.cpp \ widgets/ProgressDialog.h \ widgets/Ruler.cpp \ diff --git a/src/widgets/PopupMenuTable.cpp b/src/widgets/PopupMenuTable.cpp new file mode 100644 index 000000000..6aefbcd59 --- /dev/null +++ b/src/widgets/PopupMenuTable.cpp @@ -0,0 +1,94 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +PopupMenuTable.cpp + +Paul Licameli split from TrackPanel.cpp + +**********************************************************************/ + +#include "../Audacity.h" +#include "PopupMenuTable.h" + +PopupMenuTable::Menu::~Menu() +{ + Disconnect(); +} + +void PopupMenuTable::Menu::Extend(PopupMenuTable *pTable) +{ + auto connect = [&]( const PopupMenuTable::Entry *pEntry ) { + this->pParent->Connect + (pEntry->id, wxEVT_COMMAND_MENU_SELECTED, + pEntry->func, NULL, pTable); + }; + + for (const PopupMenuTable::Entry *pEntry = &*pTable->Get().begin(); + pEntry->IsValid(); ++pEntry) { + switch (pEntry->type) { + case PopupMenuTable::Entry::Item: + { + this->Append(pEntry->id, pEntry->caption); + connect( pEntry ); + break; + } + case PopupMenuTable::Entry::RadioItem: + { + this->AppendRadioItem(pEntry->id, pEntry->caption); + connect( pEntry ); + break; + } + case PopupMenuTable::Entry::CheckItem: + { + this->AppendCheckItem(pEntry->id, pEntry->caption); + connect( pEntry ); + break; + } + case PopupMenuTable::Entry::Separator: + this->AppendSeparator(); + break; + case PopupMenuTable::Entry::SubMenu: + { + const auto subTable = pEntry->subTable; + auto subMenu = BuildMenu( this->pParent, subTable, pUserData ); + this->AppendSubMenu( subMenu.release(), pEntry->caption ); + } + default: + break; + } + } + + pTable->InitMenu(this, pUserData); +} + +void PopupMenuTable::Menu::DisconnectTable(PopupMenuTable *pTable) +{ + for (const PopupMenuTable::Entry *pEntry = &*pTable->Get().begin(); + pEntry->IsValid(); ++pEntry) { + if ( pEntry->IsItem() ) + pParent->Disconnect( pEntry->id, wxEVT_COMMAND_MENU_SELECTED, + pEntry->func, NULL, pTable ); + else if ( pEntry->IsSubMenu() ) + // recur + DisconnectTable(pEntry->subTable); + } + + pTable->DestroyMenu(); +} + +void PopupMenuTable::Menu::Disconnect() +{ + for ( auto pTable : tables ) + DisconnectTable(pTable); +} + +// static +std::unique_ptr PopupMenuTable::BuildMenu +( wxEvtHandler *pParent, PopupMenuTable *pTable, void *pUserData ) +{ + // Rebuild as needed each time. That makes it safe in case of language change. + std::unique_ptr theMenu{ safenew Menu( pParent, pUserData ) }; + theMenu->Extend(pTable); + return std::move( theMenu ); +} diff --git a/src/widgets/PopupMenuTable.h b/src/widgets/PopupMenuTable.h new file mode 100644 index 000000000..b2ebed7fa --- /dev/null +++ b/src/widgets/PopupMenuTable.h @@ -0,0 +1,196 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +PopupMenuTable.h + +Paul Licameli + +This file defines PopupMenuTable, which inherits from wxEventHandler, + +associated macros simplifying the population of tables, + +and PopupMenuTable::Menu which is buildable from one or more such +tables, and automatically attaches and detaches the event handlers. + +**********************************************************************/ + +#ifndef __AUDACITY_POPUP_MENU_TABLE__ +#define __AUDACITY_POPUP_MENU_TABLE__ + +class wxCommandEvent; +class wxString; +#include +#include +#include +#include "../MemoryX.h" + +#include "../TranslatableStringArray.h" + +class PopupMenuTable; + +struct PopupMenuTableEntry +{ + enum Type { Item, RadioItem, CheckItem, Separator, SubMenu, Invalid }; + + Type type; + int id; + wxString caption; + wxObjectEventFunction func; + PopupMenuTable *subTable; + + PopupMenuTableEntry(Type type_, int id_, wxString caption_, + wxObjectEventFunction func_, PopupMenuTable *subTable_) + : type(type_) + , id(id_) + , caption(caption_) + , func(func_) + , subTable(subTable_) + {} + + bool IsItem() const { return type == Item || type == RadioItem || type == CheckItem; } + bool IsSubMenu() const { return type == SubMenu; } + bool IsValid() const { return type != Invalid; } +}; + +class PopupMenuTable : public TranslatableArray< std::vector > +{ +public: + typedef PopupMenuTableEntry Entry; + + class Menu + : public wxMenu + { + friend class PopupMenuTable; + + Menu(wxEvtHandler *pParent_, void *pUserData_) + : pParent{ pParent_ }, tables{}, pUserData{ pUserData_ } {} + + public: + virtual ~Menu(); + + void Extend(PopupMenuTable *pTable); + void DisconnectTable(PopupMenuTable *pTable); + void Disconnect(); + + private: + wxEvtHandler *pParent; + std::vector tables; + void *pUserData; + }; + + // Called when the menu is about to pop up. + // Your chance to enable and disable items. + virtual void InitMenu(Menu *pMenu, void *pUserData) = 0; + + // Called when menu is destroyed. + virtual void DestroyMenu() = 0; + + // Optional pUserData gets passed to the InitMenu routines of tables. + // No memory management responsibility is assumed by this function. + static std::unique_ptr BuildMenu + (wxEvtHandler *pParent, PopupMenuTable *pTable, void *pUserData = NULL); +}; + +/* +The following macros make it easy to attach a popup menu to a window. + +Exmple of usage: + +In class MyTable (maybe in the private section), +which inherits from PopupMenuTable, + +DECLARE_POPUP_MENU(MyTable); +virtual void InitMenu(Menu *pMenu, void *pUserData); +virtual void DestroyMenu(); + +Then in MyTable.cpp, + +void MyTable::InitMenu(Menu *pMenu, void *pUserData) +{ + auto pData = static_cast(pUserData); + // Remember pData, enable or disable menu items +} + +void MyTable::DestroyMenu() +{ +// Do cleanup +} + +BEGIN_POPUP_MENU(MyTable) + // This is inside a function and can contain arbitrary code. But usually + // you only need a sequence of macro calls: + + POPUP_MENU_ITEM(OnCutSelectedTextID, _("Cu&t"), OnCutSelectedText) + // etc. + +END_POPUP_MENU() + +where OnCutSelectedText is a (maybe private) member function of MyTable. + +Elswhere, + +MyTable myTable; +MyData data; +auto pMenu = PopupMenuTable::BuildMenu(pParent, &myTable, &data); + +// Optionally: +OtherTable otherTable; +pMenu->Extend(&otherTable); + +pParent->PopupMenu(pMenu.get(), event.m_x, event.m_y); + +That's all! +*/ + +#define DECLARE_POPUP_MENU(HandlerClass) \ + virtual void Populate(); + +// begins function +#define BEGIN_POPUP_MENU(HandlerClass) \ +void HandlerClass::Populate() { \ + typedef HandlerClass My; + +#define POPUP_MENU_APPEND(type, id, string, memFn, subTable) \ + mContents.push_back( Entry { \ + type, \ + id, \ + string, \ + memFn, \ + subTable \ + } ); + +#define POPUP_MENU_APPEND_ITEM(type, id, string, memFn) \ + POPUP_MENU_APPEND( \ + type, \ + id, \ + string, \ + (wxObjectEventFunction)(wxEventFunction)(wxCommandEventFunction) \ + (&My::memFn), \ + nullptr ) + +#define POPUP_MENU_ITEM(id, string, memFn) \ + POPUP_MENU_APPEND_ITEM(Entry::Item, id, string, memFn); + +#define POPUP_MENU_RADIO_ITEM(id, string, memFn) \ + POPUP_MENU_APPEND_ITEM(Entry::RadioItem, id, string, memFn); + +#define POPUP_MENU_CHECK_ITEM(id, string, memFn) \ + POPUP_MENU_APPEND_ITEM(Entry::CheckItem, id, string, memFn); + +// classname names a class that derives from MenuTable and defines Instance() +#define POPUP_MENU_SUB_MENU(id, string, classname) \ + POPUP_MENU_APPEND( \ + Entry::SubMenu, id, string, nullptr, &classname::Instance() ); + +#define POPUP_MENU_SEPARATOR() \ + POPUP_MENU_APPEND( \ + Entry::Separator, -1, wxT(""), nullptr, nullptr ); + +// ends function +#define END_POPUP_MENU() \ + POPUP_MENU_APPEND( \ + Entry::Invalid, -1, wxT(""), nullptr, nullptr ) \ + } + +#endif diff --git a/win/Projects/Audacity/Audacity.vcxproj b/win/Projects/Audacity/Audacity.vcxproj index 9c685eeba..80bad6a53 100755 --- a/win/Projects/Audacity/Audacity.vcxproj +++ b/win/Projects/Audacity/Audacity.vcxproj @@ -256,6 +256,7 @@ + @@ -511,6 +512,7 @@ + @@ -1169,4 +1171,4 @@ - \ No newline at end of file + diff --git a/win/Projects/Audacity/Audacity.vcxproj.filters b/win/Projects/Audacity/Audacity.vcxproj.filters index 37acb2219..6247ceaa0 100755 --- a/win/Projects/Audacity/Audacity.vcxproj.filters +++ b/win/Projects/Audacity/Audacity.vcxproj.filters @@ -986,6 +986,9 @@ src\tracks\playabletrack\wavetrack\ui + + src\widgets + @@ -1966,6 +1969,9 @@ src\tracks\playabletrack\wavetrack\ui + + src\widgets +