From baada94567eb4f2a49d821abaf2c7f977726a2f3 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Fri, 4 Jan 2019 09:37:42 -0500 Subject: [PATCH] Define AttachedItem registration struct for menu items --- src/Menus.cpp | 80 ++++++++++++++++++++++++++++++++++- src/commands/CommandManager.h | 38 ++++++++++++++++- 2 files changed, 114 insertions(+), 4 deletions(-) diff --git a/src/Menus.cpp b/src/Menus.cpp index 2b73c8cf3..ba1be1b36 100644 --- a/src/Menus.cpp +++ b/src/Menus.cpp @@ -178,6 +178,78 @@ CommandHandlerFinder FinderScope::sFinder = } + +namespace Registry { + +void RegisterItem( GroupItem ®istry, const Placement &placement, + BaseItemPtr pItem ) +{ + // Since registration determines only an unordered tree of menu items, + // we can sort children of each node lexicographically for our convenience. + BaseItemPtrs *pItems; + struct Comparator { + bool operator() + ( const Identifier &component, const BaseItemPtr& pItem ) const { + return component < pItem->name; } + bool operator() + ( const BaseItemPtr& pItem, const Identifier &component ) const { + return pItem->name < component; } + }; + auto find = [&pItems]( const Identifier &component ){ return std::equal_range( + pItems->begin(), pItems->end(), component, Comparator() ); }; + + auto pNode = ®istry; + pItems = &pNode->items; + + auto pathComponents = ::wxSplit( placement.path, '/' ); + auto pComponent = pathComponents.begin(), end = pathComponents.end(); + + // Descend the registry hierarchy, while groups matching the path components + // can be found + auto debugPath = wxString{'/'} + registry.name.GET(); + while ( pComponent != end ) { + const auto &pathComponent = *pComponent; + + // Try to find an item already present that is a group item with the + // same name; we don't care which if there is more than one. + const auto range = find( pathComponent ); + const auto iter2 = std::find_if( range.first, range.second, + [](const BaseItemPtr &pItem){ + return dynamic_cast< GroupItem* >( pItem.get() ); } ); + + if ( iter2 != range.second ) { + // A matching group in the registry, so descend + pNode = static_cast< GroupItem* >( iter2->get() ); + pItems = &pNode->items; + debugPath += '/' + pathComponent; + ++pComponent; + } + else + // Insert at this level; + // If there are no more path components, and a name collision of + // the added item with something already in the registry, don't resolve + // it yet in this function, but see MergeItems(). + break; + } + + // Create path group items for remaining components + while ( pComponent != end ) { + auto newNode = std::make_unique>( *pComponent ); + pNode = newNode.get(); + pItems->insert( find( newNode->name ).second, std::move( newNode ) ); + pItems = &pNode->items; + ++pComponent; + } + + // Remember the hint, to be used later in merging. + pItem->orderingHint = placement.hint; + + // Now insert the item. + pItems->insert( find( pItem->name ).second, std::move( pItem ) ); +} + +} + namespace { const auto MenuPathStart = wxT("MenuBar"); @@ -301,7 +373,6 @@ void CollectItem( Registry::Visitor &visitor, using Path = std::vector< Identifier >; -namespace { std::unordered_set< wxString > sBadPaths; void BadPath( const TranslatableString &format, const wxString &key, const Identifier &name ) @@ -339,7 +410,6 @@ XO("Plug-in item at %s conflicts with a previously defined item and was discarde XO("Plug-in items at %s specify conflicting placements"), key, name); } -} struct ItemOrdering { wxString key; @@ -763,6 +833,12 @@ static Registry::GroupItem &sRegistry() } } +MenuTable::AttachedItem::AttachedItem( + const Placement &placement, BaseItemPtr pItem ) +{ + Registry::RegisterItem( sRegistry(), placement, std::move( pItem ) ); +} + // Table of menu factories. // TODO: devise a registration system instead. static const auto menuTree = MenuTable::Items( MenuPathStart diff --git a/src/commands/CommandManager.h b/src/commands/CommandManager.h index 32ce9573b..9c773cb86 100644 --- a/src/commands/CommandManager.h +++ b/src/commands/CommandManager.h @@ -602,6 +602,25 @@ namespace Registry { ~TransparentGroupItem() override {} }; + // The /-separated path is relative to the GroupItem supplied to + // RegisterItem. + // For instance, wxT("Transport/Cursor") to locate an item under a sub-menu + // of a main menu + struct Placement { + wxString path; + OrderingHint hint; + + Placement( const wxString &path_, const OrderingHint &hint_ = {} ) + : path( path_ ), hint( hint_ ) + {} + }; + + // registry collects items, before consulting preferences and ordering + // hints, and applying the merge procedure to them. + // This function puts one more item into the registry. + void RegisterItem( GroupItem ®istry, const Placement &placement, + BaseItemPtr pItem ); + // Define actions to be done in Visit. // Default implementations do nothing // The supplied path does not include the name of the item @@ -800,7 +819,8 @@ namespace MenuTable { using MenuItems = MenuPart< true >; using MenuSection = MenuPart< false >; - // Following are the functions to use directly in writing table definitions. + // The following, and Shared(), are the functions to use directly + // in writing table definitions. // Group items can be constructed two ways. // Pointers to subordinate items are moved into the result. @@ -825,7 +845,7 @@ namespace MenuTable { const wxString &internalName, Args&&... args ) { return std::make_unique< MenuSection >( internalName, std::forward(args)... ); } - + // Menu items can be constructed two ways, as for group items // Items will appear in a main toolbar menu or in a sub-menu. // The name is untranslated. Try to keep the name stable across Audacity @@ -913,6 +933,20 @@ namespace MenuTable { inline std::unique_ptr Special( const wxString &name, const SpecialItem::Appender &fn ) { return std::make_unique( name, fn ); } + + // Typically you make a static object of this type in the .cpp file that + // also defines the added menu actions. + // pItem can be specified by an expression using the inline functions above. + struct AttachedItem final + { + AttachedItem( const Placement &placement, BaseItemPtr pItem ); + + AttachedItem( const wxString &path, BaseItemPtr pItem ) + // Delegating constructor + : AttachedItem( Placement{ path }, std::move( pItem ) ) + {} + }; + } #endif