mirror of
https://github.com/cookiengineer/audacity
synced 2025-06-26 09:08:44 +02:00
Generalize menu tree visitation beyond populating of menus...
... and apply it to implement a new alpha-only command, that dumps the menu tree as text
This commit is contained in:
commit
cd907ad80d
268
src/Menus.cpp
268
src/Menus.cpp
@ -100,7 +100,7 @@ void MenuManager::UpdatePrefs()
|
||||
}
|
||||
|
||||
/// Namespace for structures that go into building a menu
|
||||
namespace MenuTable {
|
||||
namespace Registry {
|
||||
|
||||
BaseItem::~BaseItem() {}
|
||||
|
||||
@ -121,6 +121,12 @@ void GroupItem::AppendOne( BaseItemPtr&& ptr )
|
||||
GroupItem::~GroupItem() {}
|
||||
|
||||
TransparentGroupItem::~TransparentGroupItem() {}
|
||||
|
||||
Visitor::~Visitor(){}
|
||||
void Visitor::BeginGroup(GroupItem &, const Path &) {}
|
||||
void Visitor::EndGroup(GroupItem &, const Path &) {}
|
||||
void Visitor::Visit(SingleItem &, const Path &) {}
|
||||
|
||||
}
|
||||
|
||||
namespace MenuTable {
|
||||
@ -183,90 +189,112 @@ namespace {
|
||||
|
||||
const auto MenuPathStart = wxT("MenuBar");
|
||||
|
||||
void VisitItem( AudacityProject &project, MenuTable::BaseItem *pItem );
|
||||
|
||||
void VisitItems(
|
||||
AudacityProject &project, const MenuTable::BaseItemPtrs &items )
|
||||
using namespace MenuTable;
|
||||
struct CollectedItems
|
||||
{
|
||||
for ( auto &pSubItem : items )
|
||||
VisitItem( project, pSubItem.get() );
|
||||
}
|
||||
std::vector< BaseItem * > items;
|
||||
std::vector< BaseItemSharedPtr > &computedItems;
|
||||
};
|
||||
|
||||
void VisitItem( AudacityProject &project, MenuTable::BaseItem *pItem )
|
||||
// "Collection" of items is the first pass of visitation, and resolves
|
||||
// delegation and delayed computation and splices transparent group nodes.
|
||||
// This first pass is done at each group, starting with a top-level group.
|
||||
// This pass does not descend to the leaves. Rather, the visitation passes
|
||||
// alternate as the entire tree is recursively visited.
|
||||
|
||||
// forward declaration for mutually recursive functions
|
||||
void CollectItem( AudacityProject &project,
|
||||
CollectedItems &collection, BaseItem *Item );
|
||||
void CollectItems( AudacityProject &project,
|
||||
CollectedItems &collection, const BaseItemPtrs &items )
|
||||
{
|
||||
for ( auto &item : items )
|
||||
CollectItem( project, collection, item.get() );
|
||||
}
|
||||
void CollectItem( AudacityProject &project,
|
||||
CollectedItems &collection, BaseItem *pItem )
|
||||
{
|
||||
if (!pItem)
|
||||
return;
|
||||
|
||||
auto &manager = CommandManager::Get( project );
|
||||
|
||||
using namespace MenuTable;
|
||||
if (const auto pShared =
|
||||
dynamic_cast<SharedItem*>( pItem )) {
|
||||
auto delegate = pShared->ptr;
|
||||
VisitItem( project, delegate.get() );
|
||||
auto &delegate = pShared->ptr;
|
||||
// recursion
|
||||
CollectItem( project, collection, delegate.get() );
|
||||
}
|
||||
else
|
||||
if (const auto pComputed =
|
||||
dynamic_cast<ComputedItem*>( pItem )) {
|
||||
// TODO maybe? memo-ize the results of the function, but that requires
|
||||
// invalidating the memo at the right times
|
||||
auto result = pComputed->factory( project );
|
||||
if (result)
|
||||
if (result) {
|
||||
// Guarantee long enough lifetime of the result
|
||||
collection.computedItems.push_back( result );
|
||||
// recursion
|
||||
VisitItem( project, result.get() );
|
||||
CollectItem( project, collection, result.get() );
|
||||
}
|
||||
}
|
||||
else
|
||||
if (const auto pCommand =
|
||||
dynamic_cast<CommandItem*>( pItem )) {
|
||||
manager.AddItem( project,
|
||||
pCommand->name, pCommand->label_in,
|
||||
pCommand->finder, pCommand->callback,
|
||||
pCommand->flags, pCommand->options
|
||||
);
|
||||
}
|
||||
else
|
||||
if (const auto pCommandList =
|
||||
dynamic_cast<CommandGroupItem*>( pItem ) ) {
|
||||
manager.AddItemList(pCommandList->name,
|
||||
pCommandList->items.data(), pCommandList->items.size(),
|
||||
pCommandList->finder, pCommandList->callback,
|
||||
pCommandList->flags, pCommandList->isEffect);
|
||||
}
|
||||
else
|
||||
if (const auto pMenu =
|
||||
dynamic_cast<MenuItem*>( pItem )) {
|
||||
manager.BeginMenu( pMenu->title );
|
||||
if (auto pGroup = dynamic_cast<GroupItem*>(pItem)) {
|
||||
if (dynamic_cast<TransparentGroupItem*>(pItem) && pItem->name.empty())
|
||||
// nameless grouping item is transparent to path calculations
|
||||
// recursion
|
||||
VisitItems( project, pMenu->items );
|
||||
manager.EndMenu();
|
||||
}
|
||||
CollectItems( project, collection, pGroup->items );
|
||||
else
|
||||
if (const auto pConditionalGroup =
|
||||
dynamic_cast<ConditionalGroupItem*>( pItem )) {
|
||||
const auto flag = pConditionalGroup->condition();
|
||||
if (!flag)
|
||||
manager.BeginOccultCommands();
|
||||
// recursion
|
||||
VisitItems( project, pConditionalGroup->items );
|
||||
if (!flag)
|
||||
manager.EndOccultCommands();
|
||||
// all other group items
|
||||
collection.items.push_back( pItem );
|
||||
}
|
||||
else {
|
||||
wxASSERT( dynamic_cast<SingleItem*>(pItem) );
|
||||
// common to all single items
|
||||
collection.items.push_back( pItem );
|
||||
}
|
||||
}
|
||||
|
||||
using Path = std::vector< Identifier >;
|
||||
|
||||
// forward declaration for mutually recursive functions
|
||||
void VisitItem(
|
||||
Registry::Visitor &visitor,
|
||||
AudacityProject &project, CollectedItems &collection,
|
||||
Path &path, BaseItem *pItem );
|
||||
void VisitItems(
|
||||
Registry::Visitor &visitor,
|
||||
AudacityProject &project, CollectedItems &collection,
|
||||
Path &path, GroupItem *pGroup )
|
||||
{
|
||||
// Make a new collection for this subtree, sharing the memo cache
|
||||
CollectedItems newCollection{ {}, collection.computedItems };
|
||||
|
||||
// Gather items at this level
|
||||
CollectItems( project, newCollection, pGroup->items );
|
||||
|
||||
// Now visit them
|
||||
path.push_back( pGroup->name );
|
||||
for ( const auto &pSubItem : newCollection.items )
|
||||
VisitItem( visitor, project, collection, path, pSubItem );
|
||||
path.pop_back();
|
||||
}
|
||||
void VisitItem(
|
||||
Registry::Visitor &visitor,
|
||||
AudacityProject &project, CollectedItems &collection,
|
||||
Path &path, BaseItem *pItem )
|
||||
{
|
||||
if (!pItem)
|
||||
return;
|
||||
|
||||
if (const auto pSingle =
|
||||
dynamic_cast<SingleItem*>( pItem )) {
|
||||
visitor.Visit( *pSingle, path );
|
||||
}
|
||||
else
|
||||
if (const auto pGroup =
|
||||
dynamic_cast<TransparentGroupItem*>( pItem )) {
|
||||
dynamic_cast<GroupItem*>( pItem )) {
|
||||
visitor.BeginGroup( *pGroup, path );
|
||||
// recursion
|
||||
VisitItems( project, pGroup->items );
|
||||
}
|
||||
else
|
||||
if (dynamic_cast<SeparatorItem*>( pItem )) {
|
||||
manager.AddSeparator();
|
||||
}
|
||||
else
|
||||
if (const auto pSpecial =
|
||||
dynamic_cast<SpecialItem*>( pItem )) {
|
||||
const auto pCurrentMenu = manager.CurrentMenu();
|
||||
wxASSERT( pCurrentMenu );
|
||||
pSpecial->fn( project, *pCurrentMenu );
|
||||
VisitItems( visitor, project, collection, path, pGroup );
|
||||
visitor.EndGroup( *pGroup, path );
|
||||
}
|
||||
else
|
||||
wxASSERT( false );
|
||||
@ -274,6 +302,20 @@ void VisitItem( AudacityProject &project, MenuTable::BaseItem *pItem )
|
||||
|
||||
}
|
||||
|
||||
namespace Registry {
|
||||
|
||||
void Visit(
|
||||
Visitor &visitor, AudacityProject &project,
|
||||
BaseItem *pTopItem )
|
||||
{
|
||||
std::vector< BaseItemSharedPtr > computedItems;
|
||||
CollectedItems collection{ {}, computedItems };
|
||||
Path emptyPath;
|
||||
VisitItem( visitor, project, collection, emptyPath, pTopItem );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// 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,
|
||||
@ -319,6 +361,99 @@ static const auto menuTree = MenuTable::Items( MenuPathStart
|
||||
, HelpMenu()
|
||||
);
|
||||
|
||||
namespace {
|
||||
struct MenuItemVisitor : Registry::Visitor
|
||||
{
|
||||
MenuItemVisitor( AudacityProject &proj, CommandManager &man )
|
||||
: project(proj), manager( man ) {}
|
||||
|
||||
void BeginGroup( GroupItem &item, const Path& ) override
|
||||
{
|
||||
auto pItem = &item;
|
||||
if (const auto pMenu =
|
||||
dynamic_cast<MenuItem*>( pItem )) {
|
||||
manager.BeginMenu( pMenu->title );
|
||||
}
|
||||
else
|
||||
if (const auto pConditionalGroup =
|
||||
dynamic_cast<ConditionalGroupItem*>( pItem )) {
|
||||
const auto flag = pConditionalGroup->condition();
|
||||
if (!flag)
|
||||
manager.BeginOccultCommands();
|
||||
// to avoid repeated call of condition predicate in EndGroup():
|
||||
flags.push_back(flag);
|
||||
}
|
||||
else
|
||||
if (const auto pGroup =
|
||||
dynamic_cast<TransparentGroupItem*>( pItem )) {
|
||||
}
|
||||
else
|
||||
wxASSERT( false );
|
||||
}
|
||||
|
||||
void EndGroup( GroupItem &item, const Path& ) override
|
||||
{
|
||||
auto pItem = &item;
|
||||
if (const auto pMenu =
|
||||
dynamic_cast<MenuItem*>( pItem )) {
|
||||
manager.EndMenu();
|
||||
}
|
||||
else
|
||||
if (const auto pConditionalGroup =
|
||||
dynamic_cast<ConditionalGroupItem*>( pItem )) {
|
||||
const bool flag = flags.back();
|
||||
if (!flag)
|
||||
manager.EndOccultCommands();
|
||||
flags.pop_back();
|
||||
}
|
||||
else
|
||||
if (const auto pGroup =
|
||||
dynamic_cast<TransparentGroupItem*>( pItem )) {
|
||||
}
|
||||
else
|
||||
wxASSERT( false );
|
||||
}
|
||||
|
||||
void Visit( SingleItem &item, const Path& ) override
|
||||
{
|
||||
auto pItem = &item;
|
||||
if (const auto pCommand =
|
||||
dynamic_cast<CommandItem*>( pItem )) {
|
||||
manager.AddItem( project,
|
||||
pCommand->name, pCommand->label_in,
|
||||
pCommand->finder, pCommand->callback,
|
||||
pCommand->flags, pCommand->options
|
||||
);
|
||||
}
|
||||
else
|
||||
if (const auto pCommandList =
|
||||
dynamic_cast<CommandGroupItem*>( pItem ) ) {
|
||||
manager.AddItemList(pCommandList->name,
|
||||
pCommandList->items.data(), pCommandList->items.size(),
|
||||
pCommandList->finder, pCommandList->callback,
|
||||
pCommandList->flags, pCommandList->isEffect);
|
||||
}
|
||||
else
|
||||
if (dynamic_cast<SeparatorItem*>( pItem )) {
|
||||
manager.AddSeparator();
|
||||
}
|
||||
else
|
||||
if (const auto pSpecial =
|
||||
dynamic_cast<SpecialItem*>( pItem )) {
|
||||
const auto pCurrentMenu = manager.CurrentMenu();
|
||||
wxASSERT( pCurrentMenu );
|
||||
pSpecial->fn( project, *pCurrentMenu );
|
||||
}
|
||||
else
|
||||
wxASSERT( false );
|
||||
}
|
||||
|
||||
AudacityProject &project;
|
||||
CommandManager &manager;
|
||||
std::vector<bool> flags;
|
||||
};
|
||||
}
|
||||
|
||||
void MenuCreator::CreateMenusAndCommands(AudacityProject &project)
|
||||
{
|
||||
auto &commandManager = CommandManager::Get( project );
|
||||
@ -330,7 +465,9 @@ void MenuCreator::CreateMenusAndCommands(AudacityProject &project)
|
||||
auto menubar = commandManager.AddMenuBar(wxT("appmenu"));
|
||||
wxASSERT(menubar);
|
||||
|
||||
VisitItem( project, menuTree.get() );
|
||||
MenuItemVisitor visitor{ project, commandManager };
|
||||
MenuManager::Visit( visitor, project );
|
||||
|
||||
GetProjectFrame( project ).SetMenuBar(menubar.release());
|
||||
|
||||
mLastFlags = AlwaysEnabledFlag;
|
||||
@ -340,6 +477,11 @@ void MenuCreator::CreateMenusAndCommands(AudacityProject &project)
|
||||
#endif
|
||||
}
|
||||
|
||||
void MenuManager::Visit( Registry::Visitor &visitor, AudacityProject &project )
|
||||
{
|
||||
Registry::Visit( visitor, project, menuTree.get() );
|
||||
}
|
||||
|
||||
// TODO: This surely belongs in CommandManager?
|
||||
void MenuManager::ModifyUndoMenuItems(AudacityProject &project)
|
||||
{
|
||||
|
@ -35,6 +35,8 @@ enum EffectType : int;
|
||||
typedef wxString PluginID;
|
||||
typedef wxArrayString PluginIDs;
|
||||
|
||||
namespace Registry{ class Visitor; }
|
||||
|
||||
class MenuCreator
|
||||
{
|
||||
public:
|
||||
@ -68,6 +70,9 @@ public:
|
||||
MenuManager &operator=( const MenuManager & ) PROHIBITED;
|
||||
~MenuManager();
|
||||
|
||||
static void Visit(
|
||||
Registry::Visitor &visitor, AudacityProject &project );
|
||||
|
||||
static void ModifyUndoMenuItems(AudacityProject &project);
|
||||
static void ModifyToolbarMenus(AudacityProject &project);
|
||||
// Calls ModifyToolbarMenus() on all projects
|
||||
|
@ -407,7 +407,7 @@ private:
|
||||
|
||||
// Define classes and functions that associate parts of the user interface
|
||||
// with path names
|
||||
namespace MenuTable {
|
||||
namespace Registry {
|
||||
// TODO C++17: maybe use std::variant (discriminated unions) to achieve
|
||||
// polymorphism by other means, not needing unique_ptr and dynamic_cast
|
||||
// and using less heap.
|
||||
@ -525,7 +525,27 @@ namespace MenuTable {
|
||||
~TransparentGroupItem() override;
|
||||
};
|
||||
|
||||
// Define actions to be done in Visit.
|
||||
// Default implementations do nothing
|
||||
// The supplied path does not include the name of the item
|
||||
class Visitor
|
||||
{
|
||||
public:
|
||||
virtual ~Visitor();
|
||||
using Path = std::vector< Identifier >;
|
||||
virtual void BeginGroup( GroupItem &item, const Path &path );
|
||||
virtual void EndGroup( GroupItem &item, const Path &path );
|
||||
virtual void Visit( SingleItem &item, const Path &path );
|
||||
};
|
||||
|
||||
// Top-down visitation of all items and groups in a tree
|
||||
void Visit(
|
||||
Visitor &visitor, AudacityProject &project, BaseItem *pTopItem );
|
||||
}
|
||||
|
||||
// Define items that populate tables that specifically describe menu trees
|
||||
namespace MenuTable {
|
||||
using namespace Registry;
|
||||
|
||||
// Describes a main menu in the toolbar, or a sub-menu
|
||||
struct MenuItem final : GroupItem {
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "../Dependencies.h"
|
||||
#include "../FileNames.h"
|
||||
#include "../HelpText.h"
|
||||
#include "../Menus.h"
|
||||
#include "../Prefs.h"
|
||||
#include "../Project.h"
|
||||
#include "../ProjectSelectionManager.h"
|
||||
@ -35,7 +36,8 @@ namespace {
|
||||
|
||||
void ShowDiagnostics(
|
||||
AudacityProject &project, const wxString &info,
|
||||
const TranslatableString &description, const wxString &defaultPath)
|
||||
const TranslatableString &description, const wxString &defaultPath,
|
||||
bool fixedWidth = false)
|
||||
{
|
||||
auto &window = GetProjectFrame( project );
|
||||
wxDialogWrapper dlg( &window, wxID_ANY, description);
|
||||
@ -46,12 +48,21 @@ void ShowDiagnostics(
|
||||
S.StartVerticalLay();
|
||||
{
|
||||
text = S.Id(wxID_STATIC)
|
||||
.Style(wxTE_MULTILINE | wxTE_READONLY)
|
||||
.AddTextWindow(info);
|
||||
.Style(wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH)
|
||||
.AddTextWindow("");
|
||||
|
||||
S.AddStandardButtons(eOkButton | eCancelButton);
|
||||
}
|
||||
S.EndVerticalLay();
|
||||
|
||||
if (fixedWidth) {
|
||||
auto style = text->GetDefaultStyle();
|
||||
style.SetFontFamily( wxFONTFAMILY_TELETYPE );
|
||||
text->SetDefaultStyle(style);
|
||||
}
|
||||
|
||||
*text << info;
|
||||
|
||||
dlg.FindWindowById(wxID_OK)->SetLabel(_("&Save"));
|
||||
dlg.SetSize(350, 450);
|
||||
|
||||
@ -364,6 +375,54 @@ void OnCheckDependencies(const CommandContext &context)
|
||||
::ShowDependencyDialogIfNeeded(&project, false);
|
||||
}
|
||||
|
||||
void OnMenuTree(const CommandContext &context)
|
||||
{
|
||||
auto &project = context.project;
|
||||
|
||||
using namespace MenuTable;
|
||||
struct MyVisitor : Visitor
|
||||
{
|
||||
enum : unsigned { TAB = 3 };
|
||||
void BeginGroup( GroupItem &item, const Path& ) override
|
||||
{
|
||||
Indent();
|
||||
// using GET for alpha only diagnostic tool
|
||||
info += item.name.GET();
|
||||
Return();
|
||||
indentation = wxString{ ' ', TAB * ++level };
|
||||
}
|
||||
|
||||
void EndGroup( GroupItem &, const Path& ) override
|
||||
{
|
||||
indentation = wxString{ ' ', TAB * --level };
|
||||
}
|
||||
|
||||
void Visit( SingleItem &item, const Path& ) override
|
||||
{
|
||||
static const wxString separatorName{ '=', 20 };
|
||||
|
||||
Indent();
|
||||
info += dynamic_cast<SeparatorItem*>(&item)
|
||||
? separatorName
|
||||
// using GET for alpha only diagnostic tool
|
||||
: item.name.GET();
|
||||
Return();
|
||||
}
|
||||
|
||||
void Indent() { info += indentation; }
|
||||
void Return() { info += '\n'; }
|
||||
|
||||
unsigned level{};
|
||||
wxString indentation;
|
||||
wxString info;
|
||||
} visitor;
|
||||
|
||||
MenuManager::Visit( visitor, project );
|
||||
|
||||
ShowDiagnostics( project, visitor.info,
|
||||
XO("Menu Tree"), wxT("menutree.txt"), true );
|
||||
}
|
||||
|
||||
void OnCheckForUpdates(const CommandContext &WXUNUSED(context))
|
||||
{
|
||||
::OpenInDefaultBrowser( VerCheckUrl());
|
||||
@ -463,6 +522,14 @@ MenuTable::BaseItemSharedPtr HelpMenu()
|
||||
Command( wxT("CheckDeps"), XXO("Chec&k Dependencies..."),
|
||||
FN(OnCheckDependencies),
|
||||
AudioIONotBusyFlag )
|
||||
|
||||
#ifdef IS_ALPHA
|
||||
,
|
||||
// Menu explorer. Perhaps this should become a macro command
|
||||
Command( wxT("MenuTree"), XXO("Menu Tree..."),
|
||||
FN(OnMenuTree),
|
||||
AlwaysEnabledFlag )
|
||||
#endif
|
||||
),
|
||||
|
||||
#ifndef __WXMAC__
|
||||
|
Loading…
x
Reference in New Issue
Block a user