mirror of
https://github.com/cookiengineer/audacity
synced 2025-11-21 16:37:12 +01:00
Move namespace Registry into new source files
This commit is contained in:
@@ -234,6 +234,8 @@ list( APPEND SOURCES
|
|||||||
RealFFTf48x.h
|
RealFFTf48x.h
|
||||||
RefreshCode.h
|
RefreshCode.h
|
||||||
Registrar.h
|
Registrar.h
|
||||||
|
Registry.cpp
|
||||||
|
Registry.h
|
||||||
Resample.cpp
|
Resample.cpp
|
||||||
Resample.h
|
Resample.h
|
||||||
RingBuffer.cpp
|
RingBuffer.cpp
|
||||||
|
|||||||
815
src/Menus.cpp
815
src/Menus.cpp
@@ -100,26 +100,6 @@ void MenuManager::UpdatePrefs()
|
|||||||
mStopIfWasPaused = true; // not configurable for now, but could be later.
|
mStopIfWasPaused = true; // not configurable for now, but could be later.
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Namespace for structures that go into building a menu
|
|
||||||
namespace Registry {
|
|
||||||
|
|
||||||
BaseItem::~BaseItem() {}
|
|
||||||
|
|
||||||
SharedItem::~SharedItem() {}
|
|
||||||
|
|
||||||
ComputedItem::~ComputedItem() {}
|
|
||||||
|
|
||||||
SingleItem::~SingleItem() {}
|
|
||||||
|
|
||||||
GroupItem::~GroupItem() {}
|
|
||||||
|
|
||||||
Visitor::~Visitor(){}
|
|
||||||
void Visitor::BeginGroup(GroupItem &, const Path &) {}
|
|
||||||
void Visitor::EndGroup(GroupItem &, const Path &) {}
|
|
||||||
void Visitor::Visit(SingleItem &, const Path &) {}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void MenuVisitor::BeginGroup( Registry::GroupItem &item, const Path &path )
|
void MenuVisitor::BeginGroup( Registry::GroupItem &item, const Path &path )
|
||||||
{
|
{
|
||||||
bool isMenu = false;
|
bool isMenu = false;
|
||||||
@@ -256,803 +236,14 @@ 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;
|
|
||||||
|
|
||||||
const 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<TransparentGroupItem<>>( *pComponent );
|
|
||||||
pNode = newNode.get();
|
|
||||||
pItems->insert( find( pNode->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");
|
|
||||||
|
|
||||||
struct ItemOrdering;
|
|
||||||
|
|
||||||
using namespace Registry;
|
|
||||||
struct CollectedItems
|
|
||||||
{
|
|
||||||
struct Item{
|
|
||||||
// Predefined, or merged from registry already:
|
|
||||||
BaseItem *visitNow;
|
|
||||||
// Corresponding item from the registry, its sub-items to be merged:
|
|
||||||
GroupItem *mergeLater;
|
|
||||||
// Ordering hint for the merged item:
|
|
||||||
OrderingHint hint;
|
|
||||||
};
|
|
||||||
std::vector< Item > items;
|
|
||||||
std::vector< BaseItemSharedPtr > &computedItems;
|
|
||||||
|
|
||||||
// A linear search. Smarter search may not be worth the effort.
|
|
||||||
using Iterator = decltype( items )::iterator;
|
|
||||||
auto Find( const Identifier &name ) -> Iterator
|
|
||||||
{
|
|
||||||
auto end = items.end();
|
|
||||||
return name.empty()
|
|
||||||
? end
|
|
||||||
: std::find_if( items.begin(), end,
|
|
||||||
[&]( const Item& item ){
|
|
||||||
return name == item.visitNow->name; } );
|
|
||||||
}
|
|
||||||
|
|
||||||
auto InsertNewItemUsingPreferences(
|
|
||||||
ItemOrdering &itemOrdering, BaseItem *pItem ) -> bool;
|
|
||||||
|
|
||||||
auto InsertNewItemUsingHint(
|
|
||||||
BaseItem *pItem, const OrderingHint &hint, size_t endItemsCount,
|
|
||||||
bool force )
|
|
||||||
-> bool;
|
|
||||||
|
|
||||||
auto MergeLater( Item &found, const Identifier &name ) -> GroupItem *;
|
|
||||||
|
|
||||||
auto SubordinateSingleItem( Item &found, BaseItem *pItem ) -> void;
|
|
||||||
|
|
||||||
auto SubordinateMultipleItems( Item &found, GroupItem *pItems ) -> void;
|
|
||||||
|
|
||||||
auto MergeWithExistingItem(
|
|
||||||
Visitor &visitor, ItemOrdering &itemOrdering, BaseItem *pItem ) -> bool;
|
|
||||||
|
|
||||||
using NewItem = std::pair< BaseItem*, OrderingHint >;
|
|
||||||
using NewItems = std::vector< NewItem >;
|
|
||||||
|
|
||||||
auto MergeLikeNamedItems(
|
|
||||||
Visitor &visitor, ItemOrdering &itemOrdering,
|
|
||||||
NewItems::const_iterator left, NewItems::const_iterator right,
|
|
||||||
int iPass, size_t endItemsCount, bool force )
|
|
||||||
-> bool;
|
|
||||||
|
|
||||||
auto MergeItemsAscendingNamesPass(
|
|
||||||
Visitor &visitor, ItemOrdering &itemOrdering,
|
|
||||||
NewItems &newItems, int iPass, size_t endItemsCount, bool force )
|
|
||||||
-> void;
|
|
||||||
|
|
||||||
auto MergeItemsDescendingNamesPass(
|
|
||||||
Visitor &visitor, ItemOrdering &itemOrdering,
|
|
||||||
NewItems &newItems, int iPass, size_t endItemsCount, bool force )
|
|
||||||
-> void;
|
|
||||||
|
|
||||||
auto MergeItems(
|
|
||||||
Visitor &visitor, ItemOrdering &itemOrdering,
|
|
||||||
const BaseItemPtrs &toMerge, const OrderingHint &hint ) -> void;
|
|
||||||
};
|
|
||||||
|
|
||||||
// When a computed or shared item, or nameless grouping, specifies a hint and
|
|
||||||
// the subordinate does not, propagate the hint.
|
|
||||||
const OrderingHint &ChooseHint(BaseItem *delegate, const OrderingHint &hint)
|
|
||||||
{
|
|
||||||
return !delegate || delegate->orderingHint.type == OrderingHint::Unspecified
|
|
||||||
? hint
|
|
||||||
: delegate->orderingHint;
|
|
||||||
}
|
|
||||||
|
|
||||||
// "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( Registry::Visitor &visitor,
|
|
||||||
CollectedItems &collection, BaseItem *Item, const OrderingHint &hint );
|
|
||||||
void CollectItems( Registry::Visitor &visitor,
|
|
||||||
CollectedItems &collection, const BaseItemPtrs &items,
|
|
||||||
const OrderingHint &hint )
|
|
||||||
{
|
|
||||||
for ( auto &item : items )
|
|
||||||
CollectItem( visitor, collection, item.get(),
|
|
||||||
ChooseHint( item.get(), hint ) );
|
|
||||||
}
|
|
||||||
void CollectItem( Registry::Visitor &visitor,
|
|
||||||
CollectedItems &collection, BaseItem *pItem, const OrderingHint &hint )
|
|
||||||
{
|
|
||||||
if (!pItem)
|
|
||||||
return;
|
|
||||||
|
|
||||||
using namespace Registry;
|
|
||||||
if (const auto pShared =
|
|
||||||
dynamic_cast<SharedItem*>( pItem )) {
|
|
||||||
auto delegate = pShared->ptr.get();
|
|
||||||
if ( delegate )
|
|
||||||
// recursion
|
|
||||||
CollectItem( visitor, collection, delegate,
|
|
||||||
ChooseHint( delegate, pShared->orderingHint ) );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if (const auto pComputed =
|
|
||||||
dynamic_cast<ComputedItem*>( pItem )) {
|
|
||||||
auto result = pComputed->factory( visitor );
|
|
||||||
if (result) {
|
|
||||||
// Guarantee long enough lifetime of the result
|
|
||||||
collection.computedItems.push_back( result );
|
|
||||||
// recursion
|
|
||||||
CollectItem( visitor, collection, result.get(),
|
|
||||||
ChooseHint( result.get(), pComputed->orderingHint ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if (auto pGroup = dynamic_cast<GroupItem*>(pItem)) {
|
|
||||||
if (pGroup->Transparent() && pItem->name.empty())
|
|
||||||
// nameless grouping item is transparent to path calculations
|
|
||||||
// collect group members now
|
|
||||||
// recursion
|
|
||||||
CollectItems(
|
|
||||||
visitor, collection, pGroup->items, ChooseHint( pGroup, hint ) );
|
|
||||||
else
|
|
||||||
// all other group items
|
|
||||||
// defer collection of members until collecting at next lower level
|
|
||||||
collection.items.push_back( {pItem, nullptr, hint} );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
wxASSERT( dynamic_cast<SingleItem*>(pItem) );
|
|
||||||
// common to all single items
|
|
||||||
collection.items.push_back( {pItem, nullptr, hint} );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
using Path = std::vector< Identifier >;
|
|
||||||
|
|
||||||
std::unordered_set< wxString > sBadPaths;
|
|
||||||
void BadPath(
|
|
||||||
const TranslatableString &format, const wxString &key, const Identifier &name )
|
|
||||||
{
|
|
||||||
// Warn, but not more than once in a session for each bad path
|
|
||||||
auto badPath = key + '/' + name.GET();
|
|
||||||
if ( sBadPaths.insert( badPath ).second ) {
|
|
||||||
auto msg = TranslatableString{ format }.Format( badPath );
|
|
||||||
// debug message
|
|
||||||
wxLogDebug( msg.Translation() );
|
|
||||||
#ifdef IS_ALPHA
|
|
||||||
// user-visible message
|
|
||||||
AudacityMessageBox( msg );
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReportGroupGroupCollision( const wxString &key, const Identifier &name )
|
|
||||||
{
|
|
||||||
BadPath(
|
|
||||||
XO("Plug-in group at %s was merged with a previously defined group"),
|
|
||||||
key, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReportItemItemCollision( const wxString &key, const Identifier &name )
|
|
||||||
{
|
|
||||||
BadPath(
|
|
||||||
XO("Plug-in item at %s conflicts with a previously defined item and was discarded"),
|
|
||||||
key, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReportConflictingPlacements( const wxString &key, const Identifier &name )
|
|
||||||
{
|
|
||||||
BadPath(
|
|
||||||
XO("Plug-in items at %s specify conflicting placements"),
|
|
||||||
key, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ItemOrdering {
|
|
||||||
wxString key;
|
|
||||||
|
|
||||||
ItemOrdering( const Path &path )
|
|
||||||
{
|
|
||||||
// The set of path names determines only an unordered tree.
|
|
||||||
// We want an ordering of the tree that is stable across runs.
|
|
||||||
// The last used ordering for this node can be found in preferences at this
|
|
||||||
// key:
|
|
||||||
wxArrayString strings;
|
|
||||||
for (const auto &id : path)
|
|
||||||
strings.push_back( id.GET() );
|
|
||||||
key = '/' + ::wxJoin( strings, '/', '\0' );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the old ordering on demand, if needed to merge something.
|
|
||||||
bool gotOrdering = false;
|
|
||||||
wxString strValue;
|
|
||||||
wxArrayString ordering;
|
|
||||||
|
|
||||||
auto Get() -> wxArrayString & {
|
|
||||||
if ( !gotOrdering ) {
|
|
||||||
gPrefs->Read(key, &strValue);
|
|
||||||
ordering = ::wxSplit( strValue, ',' );
|
|
||||||
gotOrdering = true;
|
|
||||||
}
|
|
||||||
return ordering;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// For each group node, this is called only in the first pass of merging of
|
|
||||||
// items. It might fail to place an item in the first visitation of a
|
|
||||||
// registry, but then succeed in later visitations in the same or later
|
|
||||||
// runs of the program, because of persistent side-effects on the
|
|
||||||
// preferences done at the very end of the visitation.
|
|
||||||
auto CollectedItems::InsertNewItemUsingPreferences(
|
|
||||||
ItemOrdering &itemOrdering, BaseItem *pItem )
|
|
||||||
-> bool
|
|
||||||
{
|
|
||||||
// Note that if more than one plug-in registers items under the same
|
|
||||||
// node, then it is not specified which plug-in is handled first,
|
|
||||||
// the first time registration happens. It might happen that you
|
|
||||||
// add a plug-in, run the program, then add another, then run again;
|
|
||||||
// registration order determined by those actions might not
|
|
||||||
// correspond to the order of re-loading of modules in later
|
|
||||||
// sessions. But whatever ordering is chosen the first time some
|
|
||||||
// plug-in is seen -- that ordering gets remembered in preferences.
|
|
||||||
|
|
||||||
if ( !pItem->name.empty() ) {
|
|
||||||
// Check saved ordering first, and rebuild that as well as is possible
|
|
||||||
auto &ordering = itemOrdering.Get();
|
|
||||||
auto begin2 = ordering.begin(), end2 = ordering.end(),
|
|
||||||
found2 = std::find( begin2, end2, pItem->name );
|
|
||||||
if ( found2 != end2 ) {
|
|
||||||
auto insertPoint = items.end();
|
|
||||||
// Find the next name in the saved ordering that is known already
|
|
||||||
// in the collection.
|
|
||||||
while ( ++found2 != end2 ) {
|
|
||||||
auto known = Find( *found2 );
|
|
||||||
if ( known != insertPoint ) {
|
|
||||||
insertPoint = known;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
items.insert( insertPoint, {pItem, nullptr,
|
|
||||||
// Hints no longer matter:
|
|
||||||
{}} );
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For each group node, this may be called in the second and later passes
|
|
||||||
// of merging of items
|
|
||||||
auto CollectedItems::InsertNewItemUsingHint(
|
|
||||||
BaseItem *pItem, const OrderingHint &hint, size_t endItemsCount,
|
|
||||||
bool force ) -> bool
|
|
||||||
{
|
|
||||||
auto begin = items.begin(), end = items.end(),
|
|
||||||
insertPoint = end - endItemsCount;
|
|
||||||
|
|
||||||
// pItem should have a name; if not, ignore the hint, and put it at the
|
|
||||||
// default place, but only if in the final pass.
|
|
||||||
if ( pItem->name.empty() ) {
|
|
||||||
if ( !force )
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
switch ( hint.type ) {
|
|
||||||
case OrderingHint::Before:
|
|
||||||
case OrderingHint::After: {
|
|
||||||
// Default to the end if the name is not found.
|
|
||||||
auto found = Find( hint.name );
|
|
||||||
if ( found == end ) {
|
|
||||||
if ( !force )
|
|
||||||
return false;
|
|
||||||
else
|
|
||||||
insertPoint = found;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
insertPoint = found;
|
|
||||||
if ( hint.type == OrderingHint::After )
|
|
||||||
++insertPoint;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case OrderingHint::Begin:
|
|
||||||
insertPoint = begin;
|
|
||||||
break;
|
|
||||||
case OrderingHint::End:
|
|
||||||
insertPoint = end;
|
|
||||||
break;
|
|
||||||
case OrderingHint::Unspecified:
|
|
||||||
default:
|
|
||||||
if ( !force )
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert the item; the hint has been used and no longer matters
|
|
||||||
items.insert( insertPoint, {pItem, nullptr,
|
|
||||||
// Hints no longer matter:
|
|
||||||
{}} );
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto CollectedItems::MergeLater( Item &found, const Identifier &name )
|
|
||||||
-> GroupItem *
|
|
||||||
{
|
|
||||||
auto subGroup = found.mergeLater;
|
|
||||||
if ( !subGroup ) {
|
|
||||||
auto newGroup = std::make_shared<TransparentGroupItem<>>( name );
|
|
||||||
computedItems.push_back( newGroup );
|
|
||||||
subGroup = found.mergeLater = newGroup.get();
|
|
||||||
}
|
|
||||||
return subGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto CollectedItems::SubordinateSingleItem( Item &found, BaseItem *pItem )
|
|
||||||
-> void
|
|
||||||
{
|
|
||||||
MergeLater( found, pItem->name )->items.push_back(
|
|
||||||
std::make_unique<SharedItem>(
|
|
||||||
// shared pointer with vacuous deleter
|
|
||||||
std::shared_ptr<BaseItem>( pItem, [](void*){} ) ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
auto CollectedItems::SubordinateMultipleItems( Item &found, GroupItem *pItems )
|
|
||||||
-> void
|
|
||||||
{
|
|
||||||
auto subGroup = MergeLater( found, pItems->name );
|
|
||||||
for ( const auto &pItem : pItems->items )
|
|
||||||
subGroup->items.push_back( std::make_unique<SharedItem>(
|
|
||||||
// shared pointer with vacuous deleter
|
|
||||||
std::shared_ptr<BaseItem>( pItem.get(), [](void*){} ) ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
auto CollectedItems::MergeWithExistingItem(
|
|
||||||
Visitor &visitor, ItemOrdering &itemOrdering, BaseItem *pItem ) -> bool
|
|
||||||
{
|
|
||||||
// Assume no null pointers remain after CollectItems:
|
|
||||||
const auto &name = pItem->name;
|
|
||||||
const auto found = Find( name );
|
|
||||||
if (found != items.end()) {
|
|
||||||
// Collision of names between collection and registry!
|
|
||||||
// There are 2 * 2 = 4 cases, as each of the two are group items or
|
|
||||||
// not.
|
|
||||||
auto pCollectionGroup = dynamic_cast< GroupItem * >( found->visitNow );
|
|
||||||
auto pRegistryGroup = dynamic_cast< GroupItem * >( pItem );
|
|
||||||
if (pCollectionGroup) {
|
|
||||||
if (pRegistryGroup) {
|
|
||||||
// This is the expected case of collision.
|
|
||||||
// Subordinate items from one of the groups will be merged in
|
|
||||||
// another call to MergeItems at a lower level of path.
|
|
||||||
// Note, however, that at most one of the two should be other
|
|
||||||
// than a plain grouping item; if not, we must lose the extra
|
|
||||||
// information carried by one of them.
|
|
||||||
bool pCollectionGrouping = pCollectionGroup->Transparent();
|
|
||||||
auto pRegistryGrouping = pRegistryGroup->Transparent();
|
|
||||||
if ( !(pCollectionGrouping || pRegistryGrouping) )
|
|
||||||
ReportGroupGroupCollision( itemOrdering.key, name );
|
|
||||||
|
|
||||||
if ( pCollectionGrouping && !pRegistryGrouping ) {
|
|
||||||
// Swap their roles
|
|
||||||
found->visitNow = pRegistryGroup;
|
|
||||||
SubordinateMultipleItems( *found, pCollectionGroup );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
SubordinateMultipleItems( *found, pRegistryGroup );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Registered non-group item collides with a previously defined
|
|
||||||
// group.
|
|
||||||
// Resolve this by subordinating the non-group item below
|
|
||||||
// that group.
|
|
||||||
SubordinateSingleItem( *found, pItem );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (pRegistryGroup) {
|
|
||||||
// Subordinate the previously merged single item below the
|
|
||||||
// newly merged group.
|
|
||||||
// In case the name occurred in two different static registries,
|
|
||||||
// the final merge is the same, no matter which is treated first.
|
|
||||||
auto demoted = found->visitNow;
|
|
||||||
found->visitNow = pRegistryGroup;
|
|
||||||
SubordinateSingleItem( *found, demoted );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
// Collision of non-group items is the worst case!
|
|
||||||
// The later-registered item is lost.
|
|
||||||
// Which one you lose might be unpredictable when both originate
|
|
||||||
// from static registries.
|
|
||||||
ReportItemItemCollision( itemOrdering.key, name );
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
// A name is registered that is not known in the collection.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto CollectedItems::MergeLikeNamedItems(
|
|
||||||
Visitor &visitor, ItemOrdering &itemOrdering,
|
|
||||||
NewItems::const_iterator left, NewItems::const_iterator right,
|
|
||||||
const int iPass, size_t endItemsCount, bool force )
|
|
||||||
-> bool
|
|
||||||
{
|
|
||||||
// Try to place the first item of the range.
|
|
||||||
// If such an item is a group, then we always retain the kind of
|
|
||||||
// grouping that was registered. (Which doesn't always happen when
|
|
||||||
// there is name collision in MergeWithExistingItem.)
|
|
||||||
auto iter = left;
|
|
||||||
auto &item = *iter;
|
|
||||||
auto pItem = item.first;
|
|
||||||
const auto &hint = item.second;
|
|
||||||
bool success = false;
|
|
||||||
if ( iPass == -1 )
|
|
||||||
// A first pass consults preferences.
|
|
||||||
success = InsertNewItemUsingPreferences( itemOrdering, pItem );
|
|
||||||
else if ( iPass == hint.type ) {
|
|
||||||
// Later passes for choosing placements.
|
|
||||||
// Maybe it fails in this pass, because a placement refers to some
|
|
||||||
// other name that has not yet been placed.
|
|
||||||
success =
|
|
||||||
InsertNewItemUsingHint( pItem, hint, endItemsCount, force );
|
|
||||||
wxASSERT( !force || success );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( success ) {
|
|
||||||
// Resolve collisions among remaining like-named items.
|
|
||||||
++iter;
|
|
||||||
if ( iter != right && iPass != 0 &&
|
|
||||||
iter->second.type != OrderingHint::Unspecified &&
|
|
||||||
!( iter->second == hint ) ) {
|
|
||||||
// A diagnostic message sometimes
|
|
||||||
ReportConflictingPlacements( itemOrdering.key, pItem->name );
|
|
||||||
}
|
|
||||||
while ( iter != right )
|
|
||||||
// Re-invoke MergeWithExistingItem for this item, which is known
|
|
||||||
// to have a name collision, so ignore the return value.
|
|
||||||
MergeWithExistingItem( visitor, itemOrdering, iter++ -> first );
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool MajorComp(
|
|
||||||
const CollectedItems::NewItem &a, const CollectedItems::NewItem &b) {
|
|
||||||
// Descending sort!
|
|
||||||
return a.first->name > b.first->name;
|
|
||||||
};
|
|
||||||
inline bool MinorComp(
|
|
||||||
const CollectedItems::NewItem &a, const CollectedItems::NewItem &b){
|
|
||||||
// Sort by hint type.
|
|
||||||
// This sorts items with unspecified hints last.
|
|
||||||
return a.second < b.second;
|
|
||||||
};
|
|
||||||
inline bool Comp(
|
|
||||||
const CollectedItems::NewItem &a, const CollectedItems::NewItem &b){
|
|
||||||
if ( MajorComp( a, b ) )
|
|
||||||
return true;
|
|
||||||
if ( MajorComp( b, a ) )
|
|
||||||
return false;
|
|
||||||
return MinorComp( a, b );
|
|
||||||
};
|
|
||||||
|
|
||||||
auto CollectedItems::MergeItemsAscendingNamesPass(
|
|
||||||
Visitor &visitor, ItemOrdering &itemOrdering, NewItems &newItems,
|
|
||||||
const int iPass, size_t endItemsCount, bool force ) -> void
|
|
||||||
{
|
|
||||||
// Inner loop over ranges of like-named items.
|
|
||||||
auto rright = newItems.rbegin();
|
|
||||||
auto rend = newItems.rend();
|
|
||||||
while ( rright != rend ) {
|
|
||||||
// Find the range
|
|
||||||
using namespace std::placeholders;
|
|
||||||
auto rleft = std::find_if(
|
|
||||||
rright + 1, rend, std::bind( MajorComp, _1, *rright ) );
|
|
||||||
|
|
||||||
bool success = MergeLikeNamedItems(
|
|
||||||
visitor, itemOrdering, rleft.base(), rright.base(), iPass,
|
|
||||||
endItemsCount, force );
|
|
||||||
|
|
||||||
if ( success ) {
|
|
||||||
auto diff = rend - rleft;
|
|
||||||
newItems.erase( rleft.base(), rright.base() );
|
|
||||||
rend = newItems.rend();
|
|
||||||
rleft = rend - diff;
|
|
||||||
}
|
|
||||||
rright = rleft;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto CollectedItems::MergeItemsDescendingNamesPass(
|
|
||||||
Visitor &visitor, ItemOrdering &itemOrdering, NewItems &newItems,
|
|
||||||
const int iPass, size_t endItemsCount, bool force ) -> void
|
|
||||||
{
|
|
||||||
// Inner loop over ranges of like-named items.
|
|
||||||
auto left = newItems.begin();
|
|
||||||
while ( left != newItems.end() ) {
|
|
||||||
// Find the range
|
|
||||||
using namespace std::placeholders;
|
|
||||||
auto right = std::find_if(
|
|
||||||
left + 1, newItems.end(), std::bind( MajorComp, *left, _1 ) );
|
|
||||||
|
|
||||||
bool success = MergeLikeNamedItems(
|
|
||||||
visitor, itemOrdering, left, right, iPass,
|
|
||||||
endItemsCount, force );
|
|
||||||
|
|
||||||
if ( success )
|
|
||||||
left = newItems.erase( left, right );
|
|
||||||
else
|
|
||||||
left = right;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
auto CollectedItems::MergeItems(
|
|
||||||
Visitor &visitor, ItemOrdering &itemOrdering,
|
|
||||||
const BaseItemPtrs &toMerge, const OrderingHint &hint ) -> void
|
|
||||||
{
|
|
||||||
// First do expansion of nameless groupings, and caching of computed
|
|
||||||
// items, just as for the previously collected items.
|
|
||||||
CollectedItems newCollection{ {}, computedItems };
|
|
||||||
CollectItems( visitor, newCollection, toMerge, hint );
|
|
||||||
|
|
||||||
// Try to merge each, resolving name collisions with items already in the
|
|
||||||
// tree, and collecting those with names that don't collide.
|
|
||||||
NewItems newItems;
|
|
||||||
for ( const auto &item : newCollection.items )
|
|
||||||
if ( !MergeWithExistingItem( visitor, itemOrdering, item.visitNow ) )
|
|
||||||
newItems.push_back( { item.visitNow, item.hint } );
|
|
||||||
|
|
||||||
// Choose placements for items with NEW names.
|
|
||||||
|
|
||||||
// First sort so that like named items are together, and for the same name,
|
|
||||||
// items with more specific ordering hints come earlier.
|
|
||||||
std::sort( newItems.begin(), newItems.end(), Comp );
|
|
||||||
|
|
||||||
// Outer loop over trial passes.
|
|
||||||
int iPass = -1;
|
|
||||||
bool force = false;
|
|
||||||
size_t oldSize = 0;
|
|
||||||
size_t endItemsCount = 0;
|
|
||||||
auto prevSize = newItems.size();
|
|
||||||
while( !newItems.empty() )
|
|
||||||
{
|
|
||||||
// If several items have the same hint, we try to preserve the sort by
|
|
||||||
// name (an internal identifier, not necessarily user visible), just to
|
|
||||||
// have some determinacy. That requires passing one or the other way
|
|
||||||
// over newItems.
|
|
||||||
bool descending =
|
|
||||||
( iPass == OrderingHint::After || iPass == OrderingHint::Begin );
|
|
||||||
|
|
||||||
if ( descending )
|
|
||||||
MergeItemsDescendingNamesPass(
|
|
||||||
visitor, itemOrdering, newItems, iPass, endItemsCount, force );
|
|
||||||
else
|
|
||||||
MergeItemsAscendingNamesPass(
|
|
||||||
visitor, itemOrdering, newItems, iPass, endItemsCount, force );
|
|
||||||
|
|
||||||
auto newSize = newItems.size();
|
|
||||||
++iPass;
|
|
||||||
|
|
||||||
if ( iPass == 0 )
|
|
||||||
// Just tried insertion by preferences. Don't try it again.
|
|
||||||
oldSize = newSize;
|
|
||||||
else if ( iPass == OrderingHint::Unspecified ) {
|
|
||||||
if ( !force ) {
|
|
||||||
iPass = 0, oldSize = newSize;
|
|
||||||
// Are we really ready for the final pass?
|
|
||||||
bool progress = ( oldSize > newSize );
|
|
||||||
if ( progress )
|
|
||||||
// No. While some progress is made, don't force final placements.
|
|
||||||
// Retry Before and After hints.
|
|
||||||
;
|
|
||||||
else
|
|
||||||
force = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ( iPass == OrderingHint::End && endItemsCount == 0 )
|
|
||||||
// Remember the size before we put the ending items in place
|
|
||||||
endItemsCount = newSize - prevSize;
|
|
||||||
|
|
||||||
prevSize = newSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// forward declaration for mutually recursive functions
|
|
||||||
void VisitItem(
|
|
||||||
Registry::Visitor &visitor, CollectedItems &collection,
|
|
||||||
Path &path, BaseItem *pItem,
|
|
||||||
const GroupItem *pToMerge, const OrderingHint &hint,
|
|
||||||
bool &doFlush );
|
|
||||||
void VisitItems(
|
|
||||||
Registry::Visitor &visitor, CollectedItems &collection,
|
|
||||||
Path &path, GroupItem *pGroup,
|
|
||||||
const GroupItem *pToMerge, const OrderingHint &hint,
|
|
||||||
bool &doFlush )
|
|
||||||
{
|
|
||||||
// Make a NEW collection for this subtree, sharing the memo cache
|
|
||||||
CollectedItems newCollection{ {}, collection.computedItems };
|
|
||||||
|
|
||||||
// Gather items at this level
|
|
||||||
// (The ordering hint is irrelevant when not merging items in)
|
|
||||||
CollectItems( visitor, newCollection, pGroup->items, {} );
|
|
||||||
|
|
||||||
path.push_back( pGroup->name.GET() );
|
|
||||||
|
|
||||||
// Merge with the registry
|
|
||||||
if ( pToMerge )
|
|
||||||
{
|
|
||||||
ItemOrdering itemOrdering{ path };
|
|
||||||
newCollection.MergeItems( visitor, itemOrdering, pToMerge->items, hint );
|
|
||||||
|
|
||||||
// Remember the NEW ordering, if there was any need to use the old.
|
|
||||||
// This makes a side effect in preferences.
|
|
||||||
if ( itemOrdering.gotOrdering ) {
|
|
||||||
wxString newValue;
|
|
||||||
for ( const auto &item : newCollection.items ) {
|
|
||||||
const auto &name = item.visitNow->name;
|
|
||||||
if ( !name.empty() )
|
|
||||||
newValue += newValue.empty()
|
|
||||||
? name.GET()
|
|
||||||
: ',' + name.GET();
|
|
||||||
}
|
|
||||||
if (newValue != itemOrdering.strValue) {
|
|
||||||
gPrefs->Write( itemOrdering.key, newValue );
|
|
||||||
doFlush = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now visit them
|
|
||||||
for ( const auto &item : newCollection.items )
|
|
||||||
VisitItem( visitor, collection, path,
|
|
||||||
item.visitNow, item.mergeLater, item.hint,
|
|
||||||
doFlush );
|
|
||||||
|
|
||||||
path.pop_back();
|
|
||||||
}
|
|
||||||
void VisitItem(
|
|
||||||
Registry::Visitor &visitor, CollectedItems &collection,
|
|
||||||
Path &path, BaseItem *pItem,
|
|
||||||
const GroupItem *pToMerge, const OrderingHint &hint,
|
|
||||||
bool &doFlush )
|
|
||||||
{
|
|
||||||
if (!pItem)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (const auto pSingle =
|
|
||||||
dynamic_cast<SingleItem*>( pItem )) {
|
|
||||||
wxASSERT( !pToMerge );
|
|
||||||
visitor.Visit( *pSingle, path );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if (const auto pGroup =
|
|
||||||
dynamic_cast<GroupItem*>( pItem )) {
|
|
||||||
visitor.BeginGroup( *pGroup, path );
|
|
||||||
// recursion
|
|
||||||
VisitItems(
|
|
||||||
visitor, collection, path, pGroup, pToMerge, hint, doFlush );
|
|
||||||
visitor.EndGroup( *pGroup, path );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
wxASSERT( false );
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Registry {
|
|
||||||
|
|
||||||
void Visit( Visitor &visitor, BaseItem *pTopItem, const GroupItem *pRegistry )
|
|
||||||
{
|
|
||||||
std::vector< BaseItemSharedPtr > computedItems;
|
|
||||||
bool doFlush = false;
|
|
||||||
CollectedItems collection{ {}, computedItems };
|
|
||||||
Path emptyPath;
|
|
||||||
VisitItem(
|
|
||||||
visitor, collection, emptyPath, pTopItem,
|
|
||||||
pRegistry, pRegistry->orderingHint, doFlush );
|
|
||||||
// Flush any writes done by MergeItems()
|
|
||||||
if (doFlush)
|
|
||||||
gPrefs->Flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
OrderingPreferenceInitializer::OrderingPreferenceInitializer(
|
|
||||||
Literal root, Pairs pairs )
|
|
||||||
: mPairs{ std::move( pairs ) }
|
|
||||||
, mRoot{ root }
|
|
||||||
{
|
|
||||||
(*this)();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OrderingPreferenceInitializer::operator () ()
|
|
||||||
{
|
|
||||||
bool doFlush = false;
|
|
||||||
for (const auto &pair : mPairs) {
|
|
||||||
const auto key = wxString{'/'} + mRoot + pair.first;
|
|
||||||
if ( gPrefs->Read(key).empty() ) {
|
|
||||||
gPrefs->Write( key, pair.second );
|
|
||||||
doFlush = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (doFlush)
|
|
||||||
gPrefs->Flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// CreateMenusAndCommands builds the menus, and also rebuilds them after
|
/// CreateMenusAndCommands builds the menus, and also rebuilds them after
|
||||||
/// changes in configured preferences - for example changes in key-bindings
|
/// changes in configured preferences - for example changes in key-bindings
|
||||||
/// affect the short-cut key legend that appears beside each command,
|
/// affect the short-cut key legend that appears beside each command,
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
const auto MenuPathStart = wxT("MenuBar");
|
||||||
|
|
||||||
static Registry::GroupItem &sRegistry()
|
static Registry::GroupItem &sRegistry()
|
||||||
{
|
{
|
||||||
static Registry::TransparentGroupItem<> registry{ MenuPathStart };
|
static Registry::TransparentGroupItem<> registry{ MenuPathStart };
|
||||||
|
|||||||
817
src/Registry.cpp
Normal file
817
src/Registry.cpp
Normal file
@@ -0,0 +1,817 @@
|
|||||||
|
/**********************************************************************
|
||||||
|
|
||||||
|
Audacity: A Digital Audio Editor
|
||||||
|
|
||||||
|
Registry.cpp
|
||||||
|
|
||||||
|
Paul Licameli split from Menus.cpp
|
||||||
|
|
||||||
|
**********************************************************************/
|
||||||
|
|
||||||
|
#include "Registry.h"
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
#include <wx/log.h>
|
||||||
|
|
||||||
|
#include "widgets/AudacityMessageBox.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct ItemOrdering;
|
||||||
|
|
||||||
|
using namespace Registry;
|
||||||
|
struct CollectedItems
|
||||||
|
{
|
||||||
|
struct Item{
|
||||||
|
// Predefined, or merged from registry already:
|
||||||
|
BaseItem *visitNow;
|
||||||
|
// Corresponding item from the registry, its sub-items to be merged:
|
||||||
|
GroupItem *mergeLater;
|
||||||
|
// Ordering hint for the merged item:
|
||||||
|
OrderingHint hint;
|
||||||
|
};
|
||||||
|
std::vector< Item > items;
|
||||||
|
std::vector< BaseItemSharedPtr > &computedItems;
|
||||||
|
|
||||||
|
// A linear search. Smarter search may not be worth the effort.
|
||||||
|
using Iterator = decltype( items )::iterator;
|
||||||
|
auto Find( const Identifier &name ) -> Iterator
|
||||||
|
{
|
||||||
|
auto end = items.end();
|
||||||
|
return name.empty()
|
||||||
|
? end
|
||||||
|
: std::find_if( items.begin(), end,
|
||||||
|
[&]( const Item& item ){
|
||||||
|
return name == item.visitNow->name; } );
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InsertNewItemUsingPreferences(
|
||||||
|
ItemOrdering &itemOrdering, BaseItem *pItem ) -> bool;
|
||||||
|
|
||||||
|
auto InsertNewItemUsingHint(
|
||||||
|
BaseItem *pItem, const OrderingHint &hint, size_t endItemsCount,
|
||||||
|
bool force )
|
||||||
|
-> bool;
|
||||||
|
|
||||||
|
auto MergeLater( Item &found, const Identifier &name ) -> GroupItem *;
|
||||||
|
|
||||||
|
auto SubordinateSingleItem( Item &found, BaseItem *pItem ) -> void;
|
||||||
|
|
||||||
|
auto SubordinateMultipleItems( Item &found, GroupItem *pItems ) -> void;
|
||||||
|
|
||||||
|
auto MergeWithExistingItem(
|
||||||
|
Visitor &visitor, ItemOrdering &itemOrdering, BaseItem *pItem ) -> bool;
|
||||||
|
|
||||||
|
using NewItem = std::pair< BaseItem*, OrderingHint >;
|
||||||
|
using NewItems = std::vector< NewItem >;
|
||||||
|
|
||||||
|
auto MergeLikeNamedItems(
|
||||||
|
Visitor &visitor, ItemOrdering &itemOrdering,
|
||||||
|
NewItems::const_iterator left, NewItems::const_iterator right,
|
||||||
|
int iPass, size_t endItemsCount, bool force )
|
||||||
|
-> bool;
|
||||||
|
|
||||||
|
auto MergeItemsAscendingNamesPass(
|
||||||
|
Visitor &visitor, ItemOrdering &itemOrdering,
|
||||||
|
NewItems &newItems, int iPass, size_t endItemsCount, bool force )
|
||||||
|
-> void;
|
||||||
|
|
||||||
|
auto MergeItemsDescendingNamesPass(
|
||||||
|
Visitor &visitor, ItemOrdering &itemOrdering,
|
||||||
|
NewItems &newItems, int iPass, size_t endItemsCount, bool force )
|
||||||
|
-> void;
|
||||||
|
|
||||||
|
auto MergeItems(
|
||||||
|
Visitor &visitor, ItemOrdering &itemOrdering,
|
||||||
|
const BaseItemPtrs &toMerge, const OrderingHint &hint ) -> void;
|
||||||
|
};
|
||||||
|
|
||||||
|
// When a computed or shared item, or nameless grouping, specifies a hint and
|
||||||
|
// the subordinate does not, propagate the hint.
|
||||||
|
const OrderingHint &ChooseHint(BaseItem *delegate, const OrderingHint &hint)
|
||||||
|
{
|
||||||
|
return !delegate || delegate->orderingHint.type == OrderingHint::Unspecified
|
||||||
|
? hint
|
||||||
|
: delegate->orderingHint;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "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( Registry::Visitor &visitor,
|
||||||
|
CollectedItems &collection, BaseItem *Item, const OrderingHint &hint );
|
||||||
|
void CollectItems( Registry::Visitor &visitor,
|
||||||
|
CollectedItems &collection, const BaseItemPtrs &items,
|
||||||
|
const OrderingHint &hint )
|
||||||
|
{
|
||||||
|
for ( auto &item : items )
|
||||||
|
CollectItem( visitor, collection, item.get(),
|
||||||
|
ChooseHint( item.get(), hint ) );
|
||||||
|
}
|
||||||
|
void CollectItem( Registry::Visitor &visitor,
|
||||||
|
CollectedItems &collection, BaseItem *pItem, const OrderingHint &hint )
|
||||||
|
{
|
||||||
|
if (!pItem)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using namespace Registry;
|
||||||
|
if (const auto pShared =
|
||||||
|
dynamic_cast<SharedItem*>( pItem )) {
|
||||||
|
auto delegate = pShared->ptr.get();
|
||||||
|
if ( delegate )
|
||||||
|
// recursion
|
||||||
|
CollectItem( visitor, collection, delegate,
|
||||||
|
ChooseHint( delegate, pShared->orderingHint ) );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (const auto pComputed =
|
||||||
|
dynamic_cast<ComputedItem*>( pItem )) {
|
||||||
|
auto result = pComputed->factory( visitor );
|
||||||
|
if (result) {
|
||||||
|
// Guarantee long enough lifetime of the result
|
||||||
|
collection.computedItems.push_back( result );
|
||||||
|
// recursion
|
||||||
|
CollectItem( visitor, collection, result.get(),
|
||||||
|
ChooseHint( result.get(), pComputed->orderingHint ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (auto pGroup = dynamic_cast<GroupItem*>(pItem)) {
|
||||||
|
if (pGroup->Transparent() && pItem->name.empty())
|
||||||
|
// nameless grouping item is transparent to path calculations
|
||||||
|
// collect group members now
|
||||||
|
// recursion
|
||||||
|
CollectItems(
|
||||||
|
visitor, collection, pGroup->items, ChooseHint( pGroup, hint ) );
|
||||||
|
else
|
||||||
|
// all other group items
|
||||||
|
// defer collection of members until collecting at next lower level
|
||||||
|
collection.items.push_back( {pItem, nullptr, hint} );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
wxASSERT( dynamic_cast<SingleItem*>(pItem) );
|
||||||
|
// common to all single items
|
||||||
|
collection.items.push_back( {pItem, nullptr, hint} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using Path = std::vector< Identifier >;
|
||||||
|
|
||||||
|
std::unordered_set< wxString > sBadPaths;
|
||||||
|
void BadPath(
|
||||||
|
const TranslatableString &format, const wxString &key, const Identifier &name )
|
||||||
|
{
|
||||||
|
// Warn, but not more than once in a session for each bad path
|
||||||
|
auto badPath = key + '/' + name.GET();
|
||||||
|
if ( sBadPaths.insert( badPath ).second ) {
|
||||||
|
auto msg = TranslatableString{ format }.Format( badPath );
|
||||||
|
// debug message
|
||||||
|
wxLogDebug( msg.Translation() );
|
||||||
|
#ifdef IS_ALPHA
|
||||||
|
// user-visible message
|
||||||
|
AudacityMessageBox( msg );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReportGroupGroupCollision( const wxString &key, const Identifier &name )
|
||||||
|
{
|
||||||
|
BadPath(
|
||||||
|
XO("Plug-in group at %s was merged with a previously defined group"),
|
||||||
|
key, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReportItemItemCollision( const wxString &key, const Identifier &name )
|
||||||
|
{
|
||||||
|
BadPath(
|
||||||
|
XO("Plug-in item at %s conflicts with a previously defined item and was discarded"),
|
||||||
|
key, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReportConflictingPlacements( const wxString &key, const Identifier &name )
|
||||||
|
{
|
||||||
|
BadPath(
|
||||||
|
XO("Plug-in items at %s specify conflicting placements"),
|
||||||
|
key, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ItemOrdering {
|
||||||
|
wxString key;
|
||||||
|
|
||||||
|
ItemOrdering( const Path &path )
|
||||||
|
{
|
||||||
|
// The set of path names determines only an unordered tree.
|
||||||
|
// We want an ordering of the tree that is stable across runs.
|
||||||
|
// The last used ordering for this node can be found in preferences at this
|
||||||
|
// key:
|
||||||
|
wxArrayString strings;
|
||||||
|
for (const auto &id : path)
|
||||||
|
strings.push_back( id.GET() );
|
||||||
|
key = '/' + ::wxJoin( strings, '/', '\0' );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the old ordering on demand, if needed to merge something.
|
||||||
|
bool gotOrdering = false;
|
||||||
|
wxString strValue;
|
||||||
|
wxArrayString ordering;
|
||||||
|
|
||||||
|
auto Get() -> wxArrayString & {
|
||||||
|
if ( !gotOrdering ) {
|
||||||
|
gPrefs->Read(key, &strValue);
|
||||||
|
ordering = ::wxSplit( strValue, ',' );
|
||||||
|
gotOrdering = true;
|
||||||
|
}
|
||||||
|
return ordering;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// For each group node, this is called only in the first pass of merging of
|
||||||
|
// items. It might fail to place an item in the first visitation of a
|
||||||
|
// registry, but then succeed in later visitations in the same or later
|
||||||
|
// runs of the program, because of persistent side-effects on the
|
||||||
|
// preferences done at the very end of the visitation.
|
||||||
|
auto CollectedItems::InsertNewItemUsingPreferences(
|
||||||
|
ItemOrdering &itemOrdering, BaseItem *pItem )
|
||||||
|
-> bool
|
||||||
|
{
|
||||||
|
// Note that if more than one plug-in registers items under the same
|
||||||
|
// node, then it is not specified which plug-in is handled first,
|
||||||
|
// the first time registration happens. It might happen that you
|
||||||
|
// add a plug-in, run the program, then add another, then run again;
|
||||||
|
// registration order determined by those actions might not
|
||||||
|
// correspond to the order of re-loading of modules in later
|
||||||
|
// sessions. But whatever ordering is chosen the first time some
|
||||||
|
// plug-in is seen -- that ordering gets remembered in preferences.
|
||||||
|
|
||||||
|
if ( !pItem->name.empty() ) {
|
||||||
|
// Check saved ordering first, and rebuild that as well as is possible
|
||||||
|
auto &ordering = itemOrdering.Get();
|
||||||
|
auto begin2 = ordering.begin(), end2 = ordering.end(),
|
||||||
|
found2 = std::find( begin2, end2, pItem->name );
|
||||||
|
if ( found2 != end2 ) {
|
||||||
|
auto insertPoint = items.end();
|
||||||
|
// Find the next name in the saved ordering that is known already
|
||||||
|
// in the collection.
|
||||||
|
while ( ++found2 != end2 ) {
|
||||||
|
auto known = Find( *found2 );
|
||||||
|
if ( known != insertPoint ) {
|
||||||
|
insertPoint = known;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items.insert( insertPoint, {pItem, nullptr,
|
||||||
|
// Hints no longer matter:
|
||||||
|
{}} );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each group node, this may be called in the second and later passes
|
||||||
|
// of merging of items
|
||||||
|
auto CollectedItems::InsertNewItemUsingHint(
|
||||||
|
BaseItem *pItem, const OrderingHint &hint, size_t endItemsCount,
|
||||||
|
bool force ) -> bool
|
||||||
|
{
|
||||||
|
auto begin = items.begin(), end = items.end(),
|
||||||
|
insertPoint = end - endItemsCount;
|
||||||
|
|
||||||
|
// pItem should have a name; if not, ignore the hint, and put it at the
|
||||||
|
// default place, but only if in the final pass.
|
||||||
|
if ( pItem->name.empty() ) {
|
||||||
|
if ( !force )
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switch ( hint.type ) {
|
||||||
|
case OrderingHint::Before:
|
||||||
|
case OrderingHint::After: {
|
||||||
|
// Default to the end if the name is not found.
|
||||||
|
auto found = Find( hint.name );
|
||||||
|
if ( found == end ) {
|
||||||
|
if ( !force )
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
insertPoint = found;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
insertPoint = found;
|
||||||
|
if ( hint.type == OrderingHint::After )
|
||||||
|
++insertPoint;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OrderingHint::Begin:
|
||||||
|
insertPoint = begin;
|
||||||
|
break;
|
||||||
|
case OrderingHint::End:
|
||||||
|
insertPoint = end;
|
||||||
|
break;
|
||||||
|
case OrderingHint::Unspecified:
|
||||||
|
default:
|
||||||
|
if ( !force )
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert the item; the hint has been used and no longer matters
|
||||||
|
items.insert( insertPoint, {pItem, nullptr,
|
||||||
|
// Hints no longer matter:
|
||||||
|
{}} );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto CollectedItems::MergeLater( Item &found, const Identifier &name )
|
||||||
|
-> GroupItem *
|
||||||
|
{
|
||||||
|
auto subGroup = found.mergeLater;
|
||||||
|
if ( !subGroup ) {
|
||||||
|
auto newGroup = std::make_shared<TransparentGroupItem<>>( name );
|
||||||
|
computedItems.push_back( newGroup );
|
||||||
|
subGroup = found.mergeLater = newGroup.get();
|
||||||
|
}
|
||||||
|
return subGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto CollectedItems::SubordinateSingleItem( Item &found, BaseItem *pItem )
|
||||||
|
-> void
|
||||||
|
{
|
||||||
|
MergeLater( found, pItem->name )->items.push_back(
|
||||||
|
std::make_unique<SharedItem>(
|
||||||
|
// shared pointer with vacuous deleter
|
||||||
|
std::shared_ptr<BaseItem>( pItem, [](void*){} ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
auto CollectedItems::SubordinateMultipleItems( Item &found, GroupItem *pItems )
|
||||||
|
-> void
|
||||||
|
{
|
||||||
|
auto subGroup = MergeLater( found, pItems->name );
|
||||||
|
for ( const auto &pItem : pItems->items )
|
||||||
|
subGroup->items.push_back( std::make_unique<SharedItem>(
|
||||||
|
// shared pointer with vacuous deleter
|
||||||
|
std::shared_ptr<BaseItem>( pItem.get(), [](void*){} ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
auto CollectedItems::MergeWithExistingItem(
|
||||||
|
Visitor &visitor, ItemOrdering &itemOrdering, BaseItem *pItem ) -> bool
|
||||||
|
{
|
||||||
|
// Assume no null pointers remain after CollectItems:
|
||||||
|
const auto &name = pItem->name;
|
||||||
|
const auto found = Find( name );
|
||||||
|
if (found != items.end()) {
|
||||||
|
// Collision of names between collection and registry!
|
||||||
|
// There are 2 * 2 = 4 cases, as each of the two are group items or
|
||||||
|
// not.
|
||||||
|
auto pCollectionGroup = dynamic_cast< GroupItem * >( found->visitNow );
|
||||||
|
auto pRegistryGroup = dynamic_cast< GroupItem * >( pItem );
|
||||||
|
if (pCollectionGroup) {
|
||||||
|
if (pRegistryGroup) {
|
||||||
|
// This is the expected case of collision.
|
||||||
|
// Subordinate items from one of the groups will be merged in
|
||||||
|
// another call to MergeItems at a lower level of path.
|
||||||
|
// Note, however, that at most one of the two should be other
|
||||||
|
// than a plain grouping item; if not, we must lose the extra
|
||||||
|
// information carried by one of them.
|
||||||
|
bool pCollectionGrouping = pCollectionGroup->Transparent();
|
||||||
|
auto pRegistryGrouping = pRegistryGroup->Transparent();
|
||||||
|
if ( !(pCollectionGrouping || pRegistryGrouping) )
|
||||||
|
ReportGroupGroupCollision( itemOrdering.key, name );
|
||||||
|
|
||||||
|
if ( pCollectionGrouping && !pRegistryGrouping ) {
|
||||||
|
// Swap their roles
|
||||||
|
found->visitNow = pRegistryGroup;
|
||||||
|
SubordinateMultipleItems( *found, pCollectionGroup );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
SubordinateMultipleItems( *found, pRegistryGroup );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Registered non-group item collides with a previously defined
|
||||||
|
// group.
|
||||||
|
// Resolve this by subordinating the non-group item below
|
||||||
|
// that group.
|
||||||
|
SubordinateSingleItem( *found, pItem );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (pRegistryGroup) {
|
||||||
|
// Subordinate the previously merged single item below the
|
||||||
|
// newly merged group.
|
||||||
|
// In case the name occurred in two different static registries,
|
||||||
|
// the final merge is the same, no matter which is treated first.
|
||||||
|
auto demoted = found->visitNow;
|
||||||
|
found->visitNow = pRegistryGroup;
|
||||||
|
SubordinateSingleItem( *found, demoted );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// Collision of non-group items is the worst case!
|
||||||
|
// The later-registered item is lost.
|
||||||
|
// Which one you lose might be unpredictable when both originate
|
||||||
|
// from static registries.
|
||||||
|
ReportItemItemCollision( itemOrdering.key, name );
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// A name is registered that is not known in the collection.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto CollectedItems::MergeLikeNamedItems(
|
||||||
|
Visitor &visitor, ItemOrdering &itemOrdering,
|
||||||
|
NewItems::const_iterator left, NewItems::const_iterator right,
|
||||||
|
const int iPass, size_t endItemsCount, bool force )
|
||||||
|
-> bool
|
||||||
|
{
|
||||||
|
// Try to place the first item of the range.
|
||||||
|
// If such an item is a group, then we always retain the kind of
|
||||||
|
// grouping that was registered. (Which doesn't always happen when
|
||||||
|
// there is name collision in MergeWithExistingItem.)
|
||||||
|
auto iter = left;
|
||||||
|
auto &item = *iter;
|
||||||
|
auto pItem = item.first;
|
||||||
|
const auto &hint = item.second;
|
||||||
|
bool success = false;
|
||||||
|
if ( iPass == -1 )
|
||||||
|
// A first pass consults preferences.
|
||||||
|
success = InsertNewItemUsingPreferences( itemOrdering, pItem );
|
||||||
|
else if ( iPass == hint.type ) {
|
||||||
|
// Later passes for choosing placements.
|
||||||
|
// Maybe it fails in this pass, because a placement refers to some
|
||||||
|
// other name that has not yet been placed.
|
||||||
|
success =
|
||||||
|
InsertNewItemUsingHint( pItem, hint, endItemsCount, force );
|
||||||
|
wxASSERT( !force || success );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( success ) {
|
||||||
|
// Resolve collisions among remaining like-named items.
|
||||||
|
++iter;
|
||||||
|
if ( iter != right && iPass != 0 &&
|
||||||
|
iter->second.type != OrderingHint::Unspecified &&
|
||||||
|
!( iter->second == hint ) ) {
|
||||||
|
// A diagnostic message sometimes
|
||||||
|
ReportConflictingPlacements( itemOrdering.key, pItem->name );
|
||||||
|
}
|
||||||
|
while ( iter != right )
|
||||||
|
// Re-invoke MergeWithExistingItem for this item, which is known
|
||||||
|
// to have a name collision, so ignore the return value.
|
||||||
|
MergeWithExistingItem( visitor, itemOrdering, iter++ -> first );
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool MajorComp(
|
||||||
|
const CollectedItems::NewItem &a, const CollectedItems::NewItem &b) {
|
||||||
|
// Descending sort!
|
||||||
|
return a.first->name > b.first->name;
|
||||||
|
};
|
||||||
|
inline bool MinorComp(
|
||||||
|
const CollectedItems::NewItem &a, const CollectedItems::NewItem &b){
|
||||||
|
// Sort by hint type.
|
||||||
|
// This sorts items with unspecified hints last.
|
||||||
|
return a.second < b.second;
|
||||||
|
};
|
||||||
|
inline bool Comp(
|
||||||
|
const CollectedItems::NewItem &a, const CollectedItems::NewItem &b){
|
||||||
|
if ( MajorComp( a, b ) )
|
||||||
|
return true;
|
||||||
|
if ( MajorComp( b, a ) )
|
||||||
|
return false;
|
||||||
|
return MinorComp( a, b );
|
||||||
|
};
|
||||||
|
|
||||||
|
auto CollectedItems::MergeItemsAscendingNamesPass(
|
||||||
|
Visitor &visitor, ItemOrdering &itemOrdering, NewItems &newItems,
|
||||||
|
const int iPass, size_t endItemsCount, bool force ) -> void
|
||||||
|
{
|
||||||
|
// Inner loop over ranges of like-named items.
|
||||||
|
auto rright = newItems.rbegin();
|
||||||
|
auto rend = newItems.rend();
|
||||||
|
while ( rright != rend ) {
|
||||||
|
// Find the range
|
||||||
|
using namespace std::placeholders;
|
||||||
|
auto rleft = std::find_if(
|
||||||
|
rright + 1, rend, std::bind( MajorComp, _1, *rright ) );
|
||||||
|
|
||||||
|
bool success = MergeLikeNamedItems(
|
||||||
|
visitor, itemOrdering, rleft.base(), rright.base(), iPass,
|
||||||
|
endItemsCount, force );
|
||||||
|
|
||||||
|
if ( success ) {
|
||||||
|
auto diff = rend - rleft;
|
||||||
|
newItems.erase( rleft.base(), rright.base() );
|
||||||
|
rend = newItems.rend();
|
||||||
|
rleft = rend - diff;
|
||||||
|
}
|
||||||
|
rright = rleft;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto CollectedItems::MergeItemsDescendingNamesPass(
|
||||||
|
Visitor &visitor, ItemOrdering &itemOrdering, NewItems &newItems,
|
||||||
|
const int iPass, size_t endItemsCount, bool force ) -> void
|
||||||
|
{
|
||||||
|
// Inner loop over ranges of like-named items.
|
||||||
|
auto left = newItems.begin();
|
||||||
|
while ( left != newItems.end() ) {
|
||||||
|
// Find the range
|
||||||
|
using namespace std::placeholders;
|
||||||
|
auto right = std::find_if(
|
||||||
|
left + 1, newItems.end(), std::bind( MajorComp, *left, _1 ) );
|
||||||
|
|
||||||
|
bool success = MergeLikeNamedItems(
|
||||||
|
visitor, itemOrdering, left, right, iPass,
|
||||||
|
endItemsCount, force );
|
||||||
|
|
||||||
|
if ( success )
|
||||||
|
left = newItems.erase( left, right );
|
||||||
|
else
|
||||||
|
left = right;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto CollectedItems::MergeItems(
|
||||||
|
Visitor &visitor, ItemOrdering &itemOrdering,
|
||||||
|
const BaseItemPtrs &toMerge, const OrderingHint &hint ) -> void
|
||||||
|
{
|
||||||
|
// First do expansion of nameless groupings, and caching of computed
|
||||||
|
// items, just as for the previously collected items.
|
||||||
|
CollectedItems newCollection{ {}, computedItems };
|
||||||
|
CollectItems( visitor, newCollection, toMerge, hint );
|
||||||
|
|
||||||
|
// Try to merge each, resolving name collisions with items already in the
|
||||||
|
// tree, and collecting those with names that don't collide.
|
||||||
|
NewItems newItems;
|
||||||
|
for ( const auto &item : newCollection.items )
|
||||||
|
if ( !MergeWithExistingItem( visitor, itemOrdering, item.visitNow ) )
|
||||||
|
newItems.push_back( { item.visitNow, item.hint } );
|
||||||
|
|
||||||
|
// Choose placements for items with NEW names.
|
||||||
|
|
||||||
|
// First sort so that like named items are together, and for the same name,
|
||||||
|
// items with more specific ordering hints come earlier.
|
||||||
|
std::sort( newItems.begin(), newItems.end(), Comp );
|
||||||
|
|
||||||
|
// Outer loop over trial passes.
|
||||||
|
int iPass = -1;
|
||||||
|
bool force = false;
|
||||||
|
size_t oldSize = 0;
|
||||||
|
size_t endItemsCount = 0;
|
||||||
|
auto prevSize = newItems.size();
|
||||||
|
while( !newItems.empty() )
|
||||||
|
{
|
||||||
|
// If several items have the same hint, we try to preserve the sort by
|
||||||
|
// name (an internal identifier, not necessarily user visible), just to
|
||||||
|
// have some determinacy. That requires passing one or the other way
|
||||||
|
// over newItems.
|
||||||
|
bool descending =
|
||||||
|
( iPass == OrderingHint::After || iPass == OrderingHint::Begin );
|
||||||
|
|
||||||
|
if ( descending )
|
||||||
|
MergeItemsDescendingNamesPass(
|
||||||
|
visitor, itemOrdering, newItems, iPass, endItemsCount, force );
|
||||||
|
else
|
||||||
|
MergeItemsAscendingNamesPass(
|
||||||
|
visitor, itemOrdering, newItems, iPass, endItemsCount, force );
|
||||||
|
|
||||||
|
auto newSize = newItems.size();
|
||||||
|
++iPass;
|
||||||
|
|
||||||
|
if ( iPass == 0 )
|
||||||
|
// Just tried insertion by preferences. Don't try it again.
|
||||||
|
oldSize = newSize;
|
||||||
|
else if ( iPass == OrderingHint::Unspecified ) {
|
||||||
|
if ( !force ) {
|
||||||
|
iPass = 0, oldSize = newSize;
|
||||||
|
// Are we really ready for the final pass?
|
||||||
|
bool progress = ( oldSize > newSize );
|
||||||
|
if ( progress )
|
||||||
|
// No. While some progress is made, don't force final placements.
|
||||||
|
// Retry Before and After hints.
|
||||||
|
;
|
||||||
|
else
|
||||||
|
force = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( iPass == OrderingHint::End && endItemsCount == 0 )
|
||||||
|
// Remember the size before we put the ending items in place
|
||||||
|
endItemsCount = newSize - prevSize;
|
||||||
|
|
||||||
|
prevSize = newSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// forward declaration for mutually recursive functions
|
||||||
|
void VisitItem(
|
||||||
|
Registry::Visitor &visitor, CollectedItems &collection,
|
||||||
|
Path &path, BaseItem *pItem,
|
||||||
|
const GroupItem *pToMerge, const OrderingHint &hint,
|
||||||
|
bool &doFlush );
|
||||||
|
void VisitItems(
|
||||||
|
Registry::Visitor &visitor, CollectedItems &collection,
|
||||||
|
Path &path, GroupItem *pGroup,
|
||||||
|
const GroupItem *pToMerge, const OrderingHint &hint,
|
||||||
|
bool &doFlush )
|
||||||
|
{
|
||||||
|
// Make a NEW collection for this subtree, sharing the memo cache
|
||||||
|
CollectedItems newCollection{ {}, collection.computedItems };
|
||||||
|
|
||||||
|
// Gather items at this level
|
||||||
|
// (The ordering hint is irrelevant when not merging items in)
|
||||||
|
CollectItems( visitor, newCollection, pGroup->items, {} );
|
||||||
|
|
||||||
|
path.push_back( pGroup->name.GET() );
|
||||||
|
|
||||||
|
// Merge with the registry
|
||||||
|
if ( pToMerge )
|
||||||
|
{
|
||||||
|
ItemOrdering itemOrdering{ path };
|
||||||
|
newCollection.MergeItems( visitor, itemOrdering, pToMerge->items, hint );
|
||||||
|
|
||||||
|
// Remember the NEW ordering, if there was any need to use the old.
|
||||||
|
// This makes a side effect in preferences.
|
||||||
|
if ( itemOrdering.gotOrdering ) {
|
||||||
|
wxString newValue;
|
||||||
|
for ( const auto &item : newCollection.items ) {
|
||||||
|
const auto &name = item.visitNow->name;
|
||||||
|
if ( !name.empty() )
|
||||||
|
newValue += newValue.empty()
|
||||||
|
? name.GET()
|
||||||
|
: ',' + name.GET();
|
||||||
|
}
|
||||||
|
if (newValue != itemOrdering.strValue) {
|
||||||
|
gPrefs->Write( itemOrdering.key, newValue );
|
||||||
|
doFlush = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now visit them
|
||||||
|
for ( const auto &item : newCollection.items )
|
||||||
|
VisitItem( visitor, collection, path,
|
||||||
|
item.visitNow, item.mergeLater, item.hint,
|
||||||
|
doFlush );
|
||||||
|
|
||||||
|
path.pop_back();
|
||||||
|
}
|
||||||
|
void VisitItem(
|
||||||
|
Registry::Visitor &visitor, CollectedItems &collection,
|
||||||
|
Path &path, BaseItem *pItem,
|
||||||
|
const GroupItem *pToMerge, const OrderingHint &hint,
|
||||||
|
bool &doFlush )
|
||||||
|
{
|
||||||
|
if (!pItem)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (const auto pSingle =
|
||||||
|
dynamic_cast<SingleItem*>( pItem )) {
|
||||||
|
wxASSERT( !pToMerge );
|
||||||
|
visitor.Visit( *pSingle, path );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (const auto pGroup =
|
||||||
|
dynamic_cast<GroupItem*>( pItem )) {
|
||||||
|
visitor.BeginGroup( *pGroup, path );
|
||||||
|
// recursion
|
||||||
|
VisitItems(
|
||||||
|
visitor, collection, path, pGroup, pToMerge, hint, doFlush );
|
||||||
|
visitor.EndGroup( *pGroup, path );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
wxASSERT( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Registry {
|
||||||
|
|
||||||
|
BaseItem::~BaseItem() {}
|
||||||
|
|
||||||
|
SharedItem::~SharedItem() {}
|
||||||
|
|
||||||
|
ComputedItem::~ComputedItem() {}
|
||||||
|
|
||||||
|
SingleItem::~SingleItem() {}
|
||||||
|
|
||||||
|
GroupItem::~GroupItem() {}
|
||||||
|
|
||||||
|
Visitor::~Visitor(){}
|
||||||
|
void Visitor::BeginGroup(GroupItem &, const Path &) {}
|
||||||
|
void Visitor::EndGroup(GroupItem &, const Path &) {}
|
||||||
|
void Visitor::Visit(SingleItem &, const Path &) {}
|
||||||
|
|
||||||
|
void Visit( Visitor &visitor, BaseItem *pTopItem, const GroupItem *pRegistry )
|
||||||
|
{
|
||||||
|
std::vector< BaseItemSharedPtr > computedItems;
|
||||||
|
bool doFlush = false;
|
||||||
|
CollectedItems collection{ {}, computedItems };
|
||||||
|
Path emptyPath;
|
||||||
|
VisitItem(
|
||||||
|
visitor, collection, emptyPath, pTopItem,
|
||||||
|
pRegistry, pRegistry->orderingHint, doFlush );
|
||||||
|
// Flush any writes done by MergeItems()
|
||||||
|
if (doFlush)
|
||||||
|
gPrefs->Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
OrderingPreferenceInitializer::OrderingPreferenceInitializer(
|
||||||
|
Literal root, Pairs pairs )
|
||||||
|
: mPairs{ std::move( pairs ) }
|
||||||
|
, mRoot{ root }
|
||||||
|
{
|
||||||
|
(*this)();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OrderingPreferenceInitializer::operator () ()
|
||||||
|
{
|
||||||
|
bool doFlush = false;
|
||||||
|
for (const auto &pair : mPairs) {
|
||||||
|
const auto key = wxString{'/'} + mRoot + pair.first;
|
||||||
|
if ( gPrefs->Read(key).empty() ) {
|
||||||
|
gPrefs->Write( key, pair.second );
|
||||||
|
doFlush = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doFlush)
|
||||||
|
gPrefs->Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
const 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<TransparentGroupItem<>>( *pComponent );
|
||||||
|
pNode = newNode.get();
|
||||||
|
pItems->insert( find( pNode->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 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
292
src/Registry.h
Normal file
292
src/Registry.h
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
/**********************************************************************
|
||||||
|
|
||||||
|
Audacity: A Digital Audio Editor
|
||||||
|
|
||||||
|
Registry.h
|
||||||
|
|
||||||
|
Paul Licameli split from CommandManager.h
|
||||||
|
|
||||||
|
**********************************************************************/
|
||||||
|
|
||||||
|
#ifndef __AUDACITY_REGISTRY__
|
||||||
|
#define __AUDACITY_REGISTRY__
|
||||||
|
|
||||||
|
#include "Prefs.h"
|
||||||
|
|
||||||
|
// Define classes and functions that associate parts of the user interface
|
||||||
|
// with path names
|
||||||
|
namespace Registry {
|
||||||
|
// Items in the registry form an unordered tree, but each may also describe a
|
||||||
|
// desired insertion point among its peers. The request might not be honored
|
||||||
|
// (as when the other name is not found, or when more than one item requests
|
||||||
|
// the same ordering), but this is not treated as an error.
|
||||||
|
struct OrderingHint
|
||||||
|
{
|
||||||
|
// The default Unspecified hint is just like End, except that in case the
|
||||||
|
// item is delegated to (by a SharedItem, ComputedItem, or nameless
|
||||||
|
// transparent group), the delegating item's hint will be used instead
|
||||||
|
enum Type : int {
|
||||||
|
Before, After,
|
||||||
|
Begin, End,
|
||||||
|
Unspecified // keep this last
|
||||||
|
} type{ Unspecified };
|
||||||
|
|
||||||
|
// name of some other BaseItem; significant only when type is Before or
|
||||||
|
// After:
|
||||||
|
Identifier name;
|
||||||
|
|
||||||
|
OrderingHint() {}
|
||||||
|
OrderingHint( Type type_, const wxString &name_ = {} )
|
||||||
|
: type(type_), name(name_) {}
|
||||||
|
|
||||||
|
bool operator == ( const OrderingHint &other ) const
|
||||||
|
{ return name == other.name && type == other.type; }
|
||||||
|
|
||||||
|
bool operator < ( const OrderingHint &other ) const
|
||||||
|
{
|
||||||
|
// This sorts unspecified placements later
|
||||||
|
return std::make_pair( type, name ) <
|
||||||
|
std::make_pair( other.type, other.name );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// Most items in the table will be the large ones describing commands, so the
|
||||||
|
// waste of space in unions for separators and sub-menus should not be
|
||||||
|
// large.
|
||||||
|
struct BaseItem {
|
||||||
|
// declare at least one virtual function so dynamic_cast will work
|
||||||
|
explicit
|
||||||
|
BaseItem( const Identifier &internalName )
|
||||||
|
: name{ internalName }
|
||||||
|
{}
|
||||||
|
virtual ~BaseItem();
|
||||||
|
|
||||||
|
const Identifier name;
|
||||||
|
|
||||||
|
OrderingHint orderingHint;
|
||||||
|
};
|
||||||
|
using BaseItemPtr = std::unique_ptr<BaseItem>;
|
||||||
|
using BaseItemSharedPtr = std::shared_ptr<BaseItem>;
|
||||||
|
using BaseItemPtrs = std::vector<BaseItemPtr>;
|
||||||
|
|
||||||
|
class Visitor;
|
||||||
|
|
||||||
|
|
||||||
|
// An item that delegates to another held in a shared pointer; this allows
|
||||||
|
// static tables of items to be computed once and reused
|
||||||
|
// The name of the delegate is significant for path calculations, but the
|
||||||
|
// SharedItem's ordering hint is used if the delegate has none
|
||||||
|
struct SharedItem final : BaseItem {
|
||||||
|
explicit SharedItem( const BaseItemSharedPtr &ptr_ )
|
||||||
|
: BaseItem{ wxEmptyString }
|
||||||
|
, ptr{ ptr_ }
|
||||||
|
{}
|
||||||
|
~SharedItem() override;
|
||||||
|
|
||||||
|
BaseItemSharedPtr ptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A convenience function
|
||||||
|
inline std::unique_ptr<SharedItem> Shared( const BaseItemSharedPtr &ptr )
|
||||||
|
{ return std::make_unique<SharedItem>( ptr ); }
|
||||||
|
|
||||||
|
// An item that computes some other item to substitute for it, each time
|
||||||
|
// the ComputedItem is visited
|
||||||
|
// The name of the substitute is significant for path calculations, but the
|
||||||
|
// ComputedItem's ordering hint is used if the substitute has none
|
||||||
|
struct ComputedItem final : BaseItem {
|
||||||
|
// The type of functions that generate descriptions of items.
|
||||||
|
// Return type is a shared_ptr to let the function decide whether to
|
||||||
|
// recycle the object or rebuild it on demand each time.
|
||||||
|
// Return value from the factory may be null
|
||||||
|
template< typename VisitorType >
|
||||||
|
using Factory = std::function< BaseItemSharedPtr( VisitorType & ) >;
|
||||||
|
|
||||||
|
using DefaultVisitor = Visitor;
|
||||||
|
|
||||||
|
explicit ComputedItem( const Factory< DefaultVisitor > &factory_ )
|
||||||
|
: BaseItem( wxEmptyString )
|
||||||
|
, factory{ factory_ }
|
||||||
|
{}
|
||||||
|
~ComputedItem() override;
|
||||||
|
|
||||||
|
Factory< DefaultVisitor > factory;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Common abstract base class for items that are not groups
|
||||||
|
struct SingleItem : BaseItem {
|
||||||
|
using BaseItem::BaseItem;
|
||||||
|
~SingleItem() override = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Common abstract base class for items that group other items
|
||||||
|
struct GroupItem : BaseItem {
|
||||||
|
using BaseItem::BaseItem;
|
||||||
|
|
||||||
|
// Construction from an internal name and a previously built-up
|
||||||
|
// vector of pointers
|
||||||
|
GroupItem( const Identifier &internalName, BaseItemPtrs &&items_ )
|
||||||
|
: BaseItem{ internalName }, items{ std::move( items_ ) }
|
||||||
|
{}
|
||||||
|
~GroupItem() override = 0;
|
||||||
|
|
||||||
|
// Whether the item is non-significant for path naming
|
||||||
|
// when it also has an empty name
|
||||||
|
virtual bool Transparent() const = 0;
|
||||||
|
|
||||||
|
BaseItemPtrs items;
|
||||||
|
};
|
||||||
|
|
||||||
|
// GroupItem adding variadic constructor conveniences
|
||||||
|
template< typename VisitorType = ComputedItem::DefaultVisitor >
|
||||||
|
struct InlineGroupItem : GroupItem {
|
||||||
|
using GroupItem::GroupItem;
|
||||||
|
// In-line, variadic constructor that doesn't require building a vector
|
||||||
|
template< typename... Args >
|
||||||
|
InlineGroupItem( const Identifier &internalName, Args&&... args )
|
||||||
|
: GroupItem( internalName )
|
||||||
|
{ Append( std::forward< Args >( args )... ); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// nullary overload grounds the recursion
|
||||||
|
void Append() {}
|
||||||
|
// recursive overload
|
||||||
|
template< typename Arg, typename... Args >
|
||||||
|
void Append( Arg &&arg, Args&&... moreArgs )
|
||||||
|
{
|
||||||
|
// Dispatch one argument to the proper overload of AppendOne.
|
||||||
|
// std::forward preserves rvalue/lvalue distinction of the actual
|
||||||
|
// argument of the constructor call; that is, it inserts a
|
||||||
|
// std::move() if and only if the original argument is rvalue
|
||||||
|
AppendOne( std::forward<Arg>( arg ) );
|
||||||
|
// recur with the rest of the arguments
|
||||||
|
Append( std::forward<Args>(moreArgs)... );
|
||||||
|
};
|
||||||
|
|
||||||
|
// Move one unique_ptr to an item into our array
|
||||||
|
void AppendOne( BaseItemPtr&& ptr )
|
||||||
|
{
|
||||||
|
items.push_back( std::move( ptr ) );
|
||||||
|
}
|
||||||
|
// This overload allows a lambda or function pointer in the variadic
|
||||||
|
// argument lists without any other syntactic wrapping, and also
|
||||||
|
// allows implicit conversions to type Factory.
|
||||||
|
// (Thus, a lambda can return a unique_ptr<BaseItem> rvalue even though
|
||||||
|
// Factory's return type is shared_ptr, and the needed conversion is
|
||||||
|
// applied implicitly.)
|
||||||
|
void AppendOne( const ComputedItem::Factory<VisitorType> &factory )
|
||||||
|
{
|
||||||
|
auto adaptedFactory = [factory]( Registry::Visitor &visitor ){
|
||||||
|
return factory( dynamic_cast< VisitorType& >( visitor ) );
|
||||||
|
};
|
||||||
|
AppendOne( std::make_unique<ComputedItem>( adaptedFactory ) );
|
||||||
|
}
|
||||||
|
// This overload lets you supply a shared pointer to an item, directly
|
||||||
|
template<typename Subtype>
|
||||||
|
void AppendOne( const std::shared_ptr<Subtype> &ptr )
|
||||||
|
{ AppendOne( std::make_unique<SharedItem>(ptr) ); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Inline group item also specifying transparency
|
||||||
|
template< bool transparent,
|
||||||
|
typename VisitorType = ComputedItem::DefaultVisitor >
|
||||||
|
struct ConcreteGroupItem : InlineGroupItem< VisitorType >
|
||||||
|
{
|
||||||
|
using InlineGroupItem< VisitorType >::InlineGroupItem;
|
||||||
|
~ConcreteGroupItem() {}
|
||||||
|
bool Transparent() const override { return transparent; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Concrete subclass of GroupItem that adds nothing else
|
||||||
|
// TransparentGroupItem with an empty name is transparent to item path calculations
|
||||||
|
// and propagates its ordering hint if subordinates don't specify hints
|
||||||
|
// and it does specify one
|
||||||
|
template< typename VisitorType = ComputedItem::DefaultVisitor >
|
||||||
|
struct TransparentGroupItem final : ConcreteGroupItem< true, VisitorType >
|
||||||
|
{
|
||||||
|
using ConcreteGroupItem< true, VisitorType >::ConcreteGroupItem;
|
||||||
|
~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.
|
||||||
|
// The sequence of calls to RegisterItem has no significance for
|
||||||
|
// determining the visitation ordering. When sequence is important, register
|
||||||
|
// a GroupItem.
|
||||||
|
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
|
||||||
|
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 rooted in
|
||||||
|
// pTopItem, as merged with pRegistry.
|
||||||
|
// The merger of the trees is recomputed in each call, not saved.
|
||||||
|
// So neither given tree is modified.
|
||||||
|
// But there may be a side effect on preferences to remember the ordering
|
||||||
|
// imposed on each node of the unordered tree of registered items; each item
|
||||||
|
// seen in the registry for the first time is placed somehere, and that
|
||||||
|
// ordering should be kept the same thereafter in later runs (which may add
|
||||||
|
// yet other previously unknown items).
|
||||||
|
void Visit(
|
||||||
|
Visitor &visitor,
|
||||||
|
BaseItem *pTopItem,
|
||||||
|
const GroupItem *pRegistry = nullptr );
|
||||||
|
|
||||||
|
// Typically a static object. Constructor initializes certain preferences
|
||||||
|
// if they are not present. These preferences determine an extrinsic
|
||||||
|
// visitation ordering for registered items. This is needed in some
|
||||||
|
// places that have migrated from a system of exhaustive listings, to a
|
||||||
|
// registry of plug-ins, and something must be done to preserve old
|
||||||
|
// behavior. It can be done in the central place using string literal
|
||||||
|
// identifiers only, not requiring static compilation or linkage dependency.
|
||||||
|
struct OrderingPreferenceInitializer : PreferenceInitializer {
|
||||||
|
using Literal = const wxChar *;
|
||||||
|
using Pair = std::pair< Literal, Literal >;
|
||||||
|
using Pairs = std::vector< Pair >;
|
||||||
|
OrderingPreferenceInitializer(
|
||||||
|
// Specifies the topmost preference section:
|
||||||
|
Literal root,
|
||||||
|
// Specifies /-separated Registry paths relative to root
|
||||||
|
// (these should be blank or start with / and not end with /),
|
||||||
|
// each with a ,-separated sequence of identifiers, which specify a
|
||||||
|
// desired ordering at one node of the tree:
|
||||||
|
Pairs pairs );
|
||||||
|
|
||||||
|
void operator () () override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Pairs mPairs;
|
||||||
|
Literal mRoot;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "Keyboard.h"
|
#include "Keyboard.h"
|
||||||
|
|
||||||
#include "../Prefs.h"
|
#include "../Prefs.h"
|
||||||
|
#include "../Registry.h"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -362,281 +363,6 @@ private:
|
|||||||
std::unique_ptr< wxMenuBar > mTempMenuBar;
|
std::unique_ptr< wxMenuBar > mTempMenuBar;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Define classes and functions that associate parts of the user interface
|
|
||||||
// with path names
|
|
||||||
namespace Registry {
|
|
||||||
// Items in the registry form an unordered tree, but each may also describe a
|
|
||||||
// desired insertion point among its peers. The request might not be honored
|
|
||||||
// (as when the other name is not found, or when more than one item requests
|
|
||||||
// the same ordering), but this is not treated as an error.
|
|
||||||
struct OrderingHint
|
|
||||||
{
|
|
||||||
// The default Unspecified hint is just like End, except that in case the
|
|
||||||
// item is delegated to (by a SharedItem, ComputedItem, or nameless
|
|
||||||
// transparent group), the delegating item's hint will be used instead
|
|
||||||
enum Type : int {
|
|
||||||
Before, After,
|
|
||||||
Begin, End,
|
|
||||||
Unspecified // keep this last
|
|
||||||
} type{ Unspecified };
|
|
||||||
|
|
||||||
// name of some other BaseItem; significant only when type is Before or
|
|
||||||
// After:
|
|
||||||
Identifier name;
|
|
||||||
|
|
||||||
OrderingHint() {}
|
|
||||||
OrderingHint( Type type_, const wxString &name_ = {} )
|
|
||||||
: type(type_), name(name_) {}
|
|
||||||
|
|
||||||
bool operator == ( const OrderingHint &other ) const
|
|
||||||
{ return name == other.name && type == other.type; }
|
|
||||||
|
|
||||||
bool operator < ( const OrderingHint &other ) const
|
|
||||||
{
|
|
||||||
// This sorts unspecified placements later
|
|
||||||
return std::make_pair( type, name ) <
|
|
||||||
std::make_pair( other.type, other.name );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
// Most items in the table will be the large ones describing commands, so the
|
|
||||||
// waste of space in unions for separators and sub-menus should not be
|
|
||||||
// large.
|
|
||||||
struct BaseItem {
|
|
||||||
// declare at least one virtual function so dynamic_cast will work
|
|
||||||
explicit
|
|
||||||
BaseItem( const Identifier &internalName )
|
|
||||||
: name{ internalName }
|
|
||||||
{}
|
|
||||||
virtual ~BaseItem();
|
|
||||||
|
|
||||||
const Identifier name;
|
|
||||||
|
|
||||||
OrderingHint orderingHint;
|
|
||||||
};
|
|
||||||
using BaseItemPtr = std::unique_ptr<BaseItem>;
|
|
||||||
using BaseItemSharedPtr = std::shared_ptr<BaseItem>;
|
|
||||||
using BaseItemPtrs = std::vector<BaseItemPtr>;
|
|
||||||
|
|
||||||
class Visitor;
|
|
||||||
|
|
||||||
|
|
||||||
// An item that delegates to another held in a shared pointer; this allows
|
|
||||||
// static tables of items to be computed once and reused
|
|
||||||
// The name of the delegate is significant for path calculations, but the
|
|
||||||
// SharedItem's ordering hint is used if the delegate has none
|
|
||||||
struct SharedItem final : BaseItem {
|
|
||||||
explicit SharedItem( const BaseItemSharedPtr &ptr_ )
|
|
||||||
: BaseItem{ wxEmptyString }
|
|
||||||
, ptr{ ptr_ }
|
|
||||||
{}
|
|
||||||
~SharedItem() override;
|
|
||||||
|
|
||||||
BaseItemSharedPtr ptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
// A convenience function
|
|
||||||
inline std::unique_ptr<SharedItem> Shared( const BaseItemSharedPtr &ptr )
|
|
||||||
{ return std::make_unique<SharedItem>( ptr ); }
|
|
||||||
|
|
||||||
// An item that computes some other item to substitute for it, each time
|
|
||||||
// the ComputedItem is visited
|
|
||||||
// The name of the substitute is significant for path calculations, but the
|
|
||||||
// ComputedItem's ordering hint is used if the substitute has none
|
|
||||||
struct ComputedItem final : BaseItem {
|
|
||||||
// The type of functions that generate descriptions of items.
|
|
||||||
// Return type is a shared_ptr to let the function decide whether to
|
|
||||||
// recycle the object or rebuild it on demand each time.
|
|
||||||
// Return value from the factory may be null
|
|
||||||
template< typename VisitorType >
|
|
||||||
using Factory = std::function< BaseItemSharedPtr( VisitorType & ) >;
|
|
||||||
|
|
||||||
using DefaultVisitor = Visitor;
|
|
||||||
|
|
||||||
explicit ComputedItem( const Factory< DefaultVisitor > &factory_ )
|
|
||||||
: BaseItem( wxEmptyString )
|
|
||||||
, factory{ factory_ }
|
|
||||||
{}
|
|
||||||
~ComputedItem() override;
|
|
||||||
|
|
||||||
Factory< DefaultVisitor > factory;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Common abstract base class for items that are not groups
|
|
||||||
struct SingleItem : BaseItem {
|
|
||||||
using BaseItem::BaseItem;
|
|
||||||
~SingleItem() override = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Common abstract base class for items that group other items
|
|
||||||
struct GroupItem : BaseItem {
|
|
||||||
using BaseItem::BaseItem;
|
|
||||||
|
|
||||||
// Construction from an internal name and a previously built-up
|
|
||||||
// vector of pointers
|
|
||||||
GroupItem( const Identifier &internalName, BaseItemPtrs &&items_ )
|
|
||||||
: BaseItem{ internalName }, items{ std::move( items_ ) }
|
|
||||||
{}
|
|
||||||
~GroupItem() override = 0;
|
|
||||||
|
|
||||||
// Whether the item is non-significant for path naming
|
|
||||||
// when it also has an empty name
|
|
||||||
virtual bool Transparent() const = 0;
|
|
||||||
|
|
||||||
BaseItemPtrs items;
|
|
||||||
};
|
|
||||||
|
|
||||||
// GroupItem adding variadic constructor conveniences
|
|
||||||
template< typename VisitorType = ComputedItem::DefaultVisitor >
|
|
||||||
struct InlineGroupItem : GroupItem {
|
|
||||||
using GroupItem::GroupItem;
|
|
||||||
// In-line, variadic constructor that doesn't require building a vector
|
|
||||||
template< typename... Args >
|
|
||||||
InlineGroupItem( const Identifier &internalName, Args&&... args )
|
|
||||||
: GroupItem( internalName )
|
|
||||||
{ Append( std::forward< Args >( args )... ); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
// nullary overload grounds the recursion
|
|
||||||
void Append() {}
|
|
||||||
// recursive overload
|
|
||||||
template< typename Arg, typename... Args >
|
|
||||||
void Append( Arg &&arg, Args&&... moreArgs )
|
|
||||||
{
|
|
||||||
// Dispatch one argument to the proper overload of AppendOne.
|
|
||||||
// std::forward preserves rvalue/lvalue distinction of the actual
|
|
||||||
// argument of the constructor call; that is, it inserts a
|
|
||||||
// std::move() if and only if the original argument is rvalue
|
|
||||||
AppendOne( std::forward<Arg>( arg ) );
|
|
||||||
// recur with the rest of the arguments
|
|
||||||
Append( std::forward<Args>(moreArgs)... );
|
|
||||||
};
|
|
||||||
|
|
||||||
// Move one unique_ptr to an item into our array
|
|
||||||
void AppendOne( BaseItemPtr&& ptr )
|
|
||||||
{
|
|
||||||
items.push_back( std::move( ptr ) );
|
|
||||||
}
|
|
||||||
// This overload allows a lambda or function pointer in the variadic
|
|
||||||
// argument lists without any other syntactic wrapping, and also
|
|
||||||
// allows implicit conversions to type Factory.
|
|
||||||
// (Thus, a lambda can return a unique_ptr<BaseItem> rvalue even though
|
|
||||||
// Factory's return type is shared_ptr, and the needed conversion is
|
|
||||||
// applied implicitly.)
|
|
||||||
void AppendOne( const ComputedItem::Factory<VisitorType> &factory )
|
|
||||||
{
|
|
||||||
auto adaptedFactory = [factory]( Registry::Visitor &visitor ){
|
|
||||||
return factory( dynamic_cast< VisitorType& >( visitor ) );
|
|
||||||
};
|
|
||||||
AppendOne( std::make_unique<ComputedItem>( adaptedFactory ) );
|
|
||||||
}
|
|
||||||
// This overload lets you supply a shared pointer to an item, directly
|
|
||||||
template<typename Subtype>
|
|
||||||
void AppendOne( const std::shared_ptr<Subtype> &ptr )
|
|
||||||
{ AppendOne( std::make_unique<SharedItem>(ptr) ); }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Inline group item also specifying transparency
|
|
||||||
template< bool transparent,
|
|
||||||
typename VisitorType = ComputedItem::DefaultVisitor >
|
|
||||||
struct ConcreteGroupItem : InlineGroupItem< VisitorType >
|
|
||||||
{
|
|
||||||
using InlineGroupItem< VisitorType >::InlineGroupItem;
|
|
||||||
~ConcreteGroupItem() {}
|
|
||||||
bool Transparent() const override { return transparent; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Concrete subclass of GroupItem that adds nothing else
|
|
||||||
// TransparentGroupItem with an empty name is transparent to item path calculations
|
|
||||||
// and propagates its ordering hint if subordinates don't specify hints
|
|
||||||
// and it does specify one
|
|
||||||
template< typename VisitorType = ComputedItem::DefaultVisitor >
|
|
||||||
struct TransparentGroupItem final : ConcreteGroupItem< true, VisitorType >
|
|
||||||
{
|
|
||||||
using ConcreteGroupItem< true, VisitorType >::ConcreteGroupItem;
|
|
||||||
~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.
|
|
||||||
// The sequence of calls to RegisterItem has no significance for
|
|
||||||
// determining the visitation ordering. When sequence is important, register
|
|
||||||
// a GroupItem.
|
|
||||||
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
|
|
||||||
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 rooted in
|
|
||||||
// pTopItem, as merged with pRegistry.
|
|
||||||
// The merger of the trees is recomputed in each call, not saved.
|
|
||||||
// So neither given tree is modified.
|
|
||||||
// But there may be a side effect on preferences to remember the ordering
|
|
||||||
// imposed on each node of the unordered tree of registered items; each item
|
|
||||||
// seen in the registry for the first time is placed somehere, and that
|
|
||||||
// ordering should be kept the same thereafter in later runs (which may add
|
|
||||||
// yet other previously unknown items).
|
|
||||||
void Visit(
|
|
||||||
Visitor &visitor,
|
|
||||||
BaseItem *pTopItem,
|
|
||||||
const GroupItem *pRegistry = nullptr );
|
|
||||||
|
|
||||||
// Typically a static object. Constructor initializes certain preferences
|
|
||||||
// if they are not present. These preferences determine an extrinsic
|
|
||||||
// visitation ordering for registered items. This is needed in some
|
|
||||||
// places that have migrated from a system of exhaustive listings, to a
|
|
||||||
// registry of plug-ins, and something must be done to preserve old
|
|
||||||
// behavior. It can be done in the central place using string literal
|
|
||||||
// identifiers only, not requiring static compilation or linkage dependency.
|
|
||||||
struct OrderingPreferenceInitializer : PreferenceInitializer {
|
|
||||||
using Literal = const wxChar *;
|
|
||||||
using Pair = std::pair< Literal, Literal >;
|
|
||||||
using Pairs = std::vector< Pair >;
|
|
||||||
OrderingPreferenceInitializer(
|
|
||||||
// Specifies the topmost preference section:
|
|
||||||
Literal root,
|
|
||||||
// Specifies /-separated Registry paths relative to root
|
|
||||||
// (these should be blank or start with / and not end with /),
|
|
||||||
// each with a ,-separated sequence of identifiers, which specify a
|
|
||||||
// desired ordering at one node of the tree:
|
|
||||||
Pairs pairs );
|
|
||||||
|
|
||||||
void operator () () override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Pairs mPairs;
|
|
||||||
Literal mRoot;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MenuVisitor : Registry::Visitor
|
struct MenuVisitor : Registry::Visitor
|
||||||
{
|
{
|
||||||
// final overrides
|
// final overrides
|
||||||
|
|||||||
Reference in New Issue
Block a user