Audacity 3.2.0
Registry.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5Registry.cpp
6
7Paul Licameli split from Menus.cpp
8
9**********************************************************************/
10
11#include "Registry.h"
12
13#include <unordered_set>
14
15#include <wx/log.h>
16
17#include "BasicUI.h"
18
19namespace {
20struct ItemOrdering;
21
22using namespace Registry;
23using namespace detail;
24
27 PlaceHolder(const Identifier &identifier, Ordering ordering)
28 : GroupItemBase{ identifier }
29 , ordering{ ordering == Strong ? Weak : ordering }
30 {}
31 ~PlaceHolder() = default;
32 Ordering GetOrdering() const override
33 {
34 return ordering;
35 }
37};
38
40{
41 struct Item{
42 // Predefined, or merged from registry already:
44 // Corresponding item from the registry, its sub-items to be merged:
46 // Ordering hint for the merged item:
48 };
49 std::vector< Item > items;
50 std::vector< BaseItemSharedPtr > &computedItems;
51
52 // A linear search. Smarter search may not be worth the effort.
53 using Iterator = decltype( items )::iterator;
54 auto Find( const Identifier &name ) -> Iterator
55 {
56 auto end = items.end();
57 return name.empty()
58 ? end
59 : std::find_if( items.begin(), end,
60 [&]( const Item& item ){
61 return name == item.visitNow->name; } );
62 }
63
64 auto InsertNewItemUsingPreferences(
65 ItemOrdering &itemOrdering, BaseItem *pItem ) -> bool;
66
67 auto InsertNewItemUsingHint(
68 BaseItem *pItem, const OrderingHint &hint, size_t endItemsCount,
69 bool force )
70 -> bool;
71
72 auto MergeLater(Item &found, const Identifier &name,
74
75 void SubordinateSingleItem(Item &found, BaseItem *pItem);
76
77 void SubordinateMultipleItems(Item &found, GroupItemBase &items);
78
79 auto MergeWithExistingItem(
80 Visitor &visitor, ItemOrdering &itemOrdering, BaseItem *pItem ) -> bool;
81
82 using NewItem = std::pair< BaseItem*, OrderingHint >;
83 using NewItems = std::vector< NewItem >;
84
85 auto MergeLikeNamedItems(
86 Visitor &visitor, ItemOrdering &itemOrdering,
87 NewItems::const_iterator left, NewItems::const_iterator right,
88 int iPass, size_t endItemsCount, bool force )
89 -> bool;
90
91 auto MergeItemsAscendingNamesPass(
92 Visitor &visitor, ItemOrdering &itemOrdering,
93 NewItems &newItems, int iPass, size_t endItemsCount, bool force )
94 -> void;
95
96 auto MergeItemsDescendingNamesPass(
97 Visitor &visitor, ItemOrdering &itemOrdering,
98 NewItems &newItems, int iPass, size_t endItemsCount, bool force )
99 -> void;
100
101 auto MergeItems(
102 Visitor &visitor, ItemOrdering &itemOrdering,
103 const GroupItemBase &toMerge, const OrderingHint &hint ) -> void;
104};
105
106// When a computed or indirect item, or nameless grouping, specifies a hint and
107// the subordinate does not, propagate the hint.
108const OrderingHint &ChooseHint(BaseItem *delegate, const OrderingHint &hint)
109{
110 return !delegate || delegate->orderingHint.type == OrderingHint::Unspecified
111 ? hint
112 : delegate->orderingHint;
113}
114
115// "Collection" of items is the first pass of visitation, and resolves
116// delegation and delayed computation and splices anonymous group nodes.
117// This first pass is done at each group, starting with a top-level group.
118// This pass does not descend to the leaves. Rather, the visitation passes
119// alternate as the entire tree is recursively visited.
120
121// forward declaration for mutually recursive functions
122void CollectItem( Registry::Visitor &visitor,
123 CollectedItems &collection, BaseItem *Item, const OrderingHint &hint );
125 CollectedItems &collection, const GroupItemBase &items,
126 const OrderingHint &hint )
127{
128 for ( auto &item : items )
129 CollectItem( visitor, collection, item.get(),
130 ChooseHint( item.get(), hint ) );
131}
133 CollectedItems &collection, BaseItem *pItem, const OrderingHint &hint )
134{
135 if (!pItem)
136 return;
137
138 using namespace Registry;
139 if (const auto pIndirect =
140 dynamic_cast<IndirectItemBase*>(pItem)) {
141 auto delegate = pIndirect->ptr.get();
142 if (delegate)
143 // recursion
144 CollectItem(visitor, collection, delegate,
145 ChooseHint(delegate, pIndirect->orderingHint));
146 }
147 else
148 if (const auto pComputed =
149 dynamic_cast<ComputedItemBase*>(pItem)) {
150 auto result = pComputed->factory(visitor.GetComputedItemContext());
151 if (result) {
152 // Guarantee long enough lifetime of the result
153 collection.computedItems.push_back( result );
154 // recursion
155 CollectItem( visitor, collection, result.get(),
156 ChooseHint( result.get(), pComputed->orderingHint ) );
157 }
158 }
159 else
160 if (auto pGroup = dynamic_cast<GroupItemBase*>(pItem)) {
161 if (pGroup->GetOrdering() == GroupItemBase::Anonymous)
162 // anonymous grouping item is transparent to path calculations
163 // collect group members now
164 // recursion
165 CollectItems(visitor, collection, *pGroup, ChooseHint(pGroup, hint));
166 else
167 // all other group items
168 // defer collection of members until collecting at next lower level
169 collection.items.push_back( {pItem, nullptr, hint} );
170 }
171 else {
172 wxASSERT( dynamic_cast<SingleItem*>(pItem) );
173 // common to all single items
174 collection.items.push_back( {pItem, nullptr, hint} );
175 }
176}
177
178using Path = std::vector< Identifier >;
179
180 std::unordered_set< wxString > sBadPaths;
182 const TranslatableString &format, const wxString &key, const Identifier &name )
183 {
184 // Warn, but not more than once in a session for each bad path
185 auto badPath = key + '/' + name.GET();
186 if ( sBadPaths.insert( badPath ).second ) {
187 auto msg = TranslatableString{ format }.Format( badPath );
188 // debug message
189 wxLogDebug( msg.Translation() );
190#ifdef IS_ALPHA
191 // user-visible message
193#endif
194 }
195 }
196
197 void ReportGroupGroupCollision( const wxString &key, const Identifier &name )
198 {
199 BadPath(
200XO("Plug-in group at %s was merged with a previously defined group"),
201 key, name);
202 }
203
204 void ReportItemItemCollision( const wxString &key, const Identifier &name )
205 {
206 BadPath(
207XO("Plug-in item at %s conflicts with a previously defined item and was discarded"),
208 key, name);
209 }
210
211 void ReportConflictingPlacements( const wxString &key, const Identifier &name )
212 {
213 BadPath(
214XO("Plug-in items at %s specify conflicting placements"),
215 key, name);
216 }
217
219 wxString key;
220
221 ItemOrdering( const Path &path )
222 {
223 // The set of path names determines only an unordered tree.
224 // We want an ordering of the tree that is stable across runs.
225 // The last used ordering for this node can be found in preferences at this
226 // key:
227 wxArrayString strings;
228 for (const auto &id : path)
229 strings.push_back( id.GET() );
230 key = '/' + ::wxJoin( strings, '/', '\0' );
231 }
232
233 // Retrieve the old ordering on demand, if needed to merge something.
234 bool gotOrdering = false;
235 wxString strValue;
236 wxArrayString ordering;
237
238 auto Get() -> wxArrayString & {
239 if ( !gotOrdering ) {
240 gPrefs->Read(key, &strValue);
241 ordering = ::wxSplit( strValue, ',' );
242 gotOrdering = true;
243 }
244 return ordering;
245 };
246};
247
248// For each group node, this is called only in the first pass of merging of
249// items. It might fail to place an item in the first visitation of a
250// registry, but then succeed in later visitations in the same or later
251// runs of the program, because of persistent side-effects on the
252// preferences done at the very end of the visitation.
253auto CollectedItems::InsertNewItemUsingPreferences(
254 ItemOrdering &itemOrdering, BaseItem *pItem )
255 -> bool
256{
257 // Note that if more than one plug-in registers items under the same
258 // node, then it is not specified which plug-in is handled first,
259 // the first time registration happens. It might happen that you
260 // add a plug-in, run the program, then add another, then run again;
261 // registration order determined by those actions might not
262 // correspond to the order of re-loading of modules in later
263 // sessions. But whatever ordering is chosen the first time some
264 // plug-in is seen -- that ordering gets remembered in preferences.
265
266 if ( !pItem->name.empty() ) {
267 // Check saved ordering first, and rebuild that as well as is possible
268 auto &ordering = itemOrdering.Get();
269 auto begin2 = ordering.begin(), end2 = ordering.end(),
270 found2 = std::find( begin2, end2, pItem->name );
271 if ( found2 != end2 ) {
272 auto insertPoint = items.end();
273 // Find the next name in the saved ordering that is known already
274 // in the collection.
275 while ( ++found2 != end2 ) {
276 auto known = Find( *found2 );
277 if ( known != insertPoint ) {
278 insertPoint = known;
279 break;
280 }
281 }
282 items.insert( insertPoint, {pItem, nullptr,
283 // Hints no longer matter:
284 {}} );
285 return true;
286 }
287 }
288
289 return false;
290}
291
292// For each group node, this may be called in the second and later passes
293// of merging of items
294auto CollectedItems::InsertNewItemUsingHint(
295 BaseItem *pItem, const OrderingHint &hint, size_t endItemsCount,
296 bool force ) -> bool
297{
298 auto begin = items.begin(), end = items.end(),
299 insertPoint = end - endItemsCount;
300
301 // pItem should have a name; if not, ignore the hint, and put it at the
302 // default place, but only if in the final pass.
303 if ( pItem->name.empty() ) {
304 if ( !force )
305 return false;
306 }
307 else {
308 switch ( hint.type ) {
309 case OrderingHint::Before:
310 case OrderingHint::After: {
311 // Default to the end if the name is not found.
312 auto found = Find( hint.name );
313 if ( found == end ) {
314 if ( !force )
315 return false;
316 else
317 insertPoint = found;
318 }
319 else {
320 insertPoint = found;
321 if ( hint.type == OrderingHint::After )
322 ++insertPoint;
323 }
324 break;
325 }
327 insertPoint = begin;
328 break;
329 case OrderingHint::End:
330 insertPoint = end;
331 break;
332 case OrderingHint::Unspecified:
333 default:
334 if ( !force )
335 return false;
336 break;
337 }
338 }
339
340 // Insert the item; the hint has been used and no longer matters
341 items.insert( insertPoint, {pItem, nullptr,
342 // Hints no longer matter:
343 {}} );
344 return true;
345}
346
347auto CollectedItems::MergeLater(Item &found, const Identifier &name,
349{
350 auto subGroup = found.mergeLater;
351 if (!subGroup) {
352 auto newGroup = std::make_shared<PlaceHolder>(name, ordering);
353 computedItems.push_back(newGroup);
354 subGroup = found.mergeLater = newGroup.get();
355 }
356 return subGroup;
357}
358
359void CollectedItems::SubordinateSingleItem(Item &found, BaseItem *pItem)
360{
361 MergeLater(found, pItem->name, GroupItemBase::Weak)->push_back(
362 std::make_unique<IndirectItemBase>(
363 // shared pointer with vacuous deleter
364 std::shared_ptr<BaseItem>(pItem, [](void*){})));
365}
366
367void CollectedItems::SubordinateMultipleItems(Item &found, GroupItemBase &items)
368{
369 auto subGroup = MergeLater(found, items.name, items.GetOrdering());
370 for (const auto &pItem : items)
371 subGroup->push_back(std::make_unique<IndirectItemBase>(
372 // shared pointer with vacuous deleter
373 std::shared_ptr<BaseItem>(pItem.get(), [](void*){})));
374}
375
376auto CollectedItems::MergeWithExistingItem(
377 Visitor &visitor, ItemOrdering &itemOrdering, BaseItem *pItem ) -> bool
378{
379 // Assume no null pointers remain after CollectItems:
380 const auto &name = pItem->name;
381 const auto found = Find( name );
382 if (found != items.end()) {
383 // Collision of names between collection and registry!
384 // There are 2 * 2 = 4 cases, as each of the two are group items or
385 // not.
386 auto pCollectionGroup = dynamic_cast< GroupItemBase * >( found->visitNow );
387 auto pRegistryGroup = dynamic_cast< GroupItemBase * >( pItem );
388 if (pCollectionGroup) {
389 if (pRegistryGroup) {
390 // This is the expected case of collision.
391 // Subordinate items from one of the groups will be merged in
392 // another call to MergeItems at a lower level of path.
393 // Note, however, that at most one of the two should be a
394 // strongly ordered item; if not, we must lose the extra
395 // information carried by one of them.
396 bool pCollectionGrouping =
397 (pCollectionGroup->GetOrdering() != GroupItemBase::Strong);
398 auto pRegistryGrouping =
399 (pRegistryGroup->GetOrdering() != GroupItemBase::Strong);
400 if ( !(pCollectionGrouping || pRegistryGrouping) )
401 ReportGroupGroupCollision( itemOrdering.key, name );
402
403 if ( pCollectionGrouping && !pRegistryGrouping ) {
404 // Swap their roles
405 found->visitNow = pRegistryGroup;
406 SubordinateMultipleItems(*found, *pCollectionGroup);
407 }
408 else
409 SubordinateMultipleItems(*found, *pRegistryGroup);
410 }
411 else {
412 // Registered non-group item collides with a previously defined
413 // group.
414 // Resolve this by subordinating the non-group item below
415 // that group.
416 SubordinateSingleItem(*found, pItem);
417 }
418 }
419 else {
420 if (pRegistryGroup) {
421 // Subordinate the previously merged single item below the
422 // newly merged group.
423 // In case the name occurred in two different static registries,
424 // the final merge is the same, no matter which is treated first.
425 auto demoted = found->visitNow;
426 found->visitNow = pRegistryGroup;
427 SubordinateSingleItem(*found, demoted);
428 }
429 else
430 // Collision of non-group items is the worst case!
431 // The later-registered item is lost.
432 // Which one you lose might be unpredictable when both originate
433 // from static registries.
434 ReportItemItemCollision( itemOrdering.key, name );
435 }
436 return true;
437 }
438 else
439 // A name is registered that is not known in the collection.
440 return false;
441}
442
443auto CollectedItems::MergeLikeNamedItems(
444 Visitor &visitor, ItemOrdering &itemOrdering,
445 NewItems::const_iterator left, NewItems::const_iterator right,
446 const int iPass, size_t endItemsCount, bool force )
447 -> bool
448{
449 // Try to place the first item of the range.
450 // If such an item is a group, then we always retain the kind of
451 // grouping that was registered. (Which doesn't always happen when
452 // there is name collision in MergeWithExistingItem.)
453 auto iter = left;
454 auto &item = *iter;
455 auto pItem = item.first;
456 const auto &hint = item.second;
457 bool success = false;
458 if ( iPass == -1 )
459 // A first pass consults preferences.
460 success = InsertNewItemUsingPreferences( itemOrdering, pItem );
461 else if ( iPass == hint.type ) {
462 // Later passes for choosing placements.
463 // Maybe it fails in this pass, because a placement refers to some
464 // other name that has not yet been placed.
465 success =
466 InsertNewItemUsingHint( pItem, hint, endItemsCount, force );
467 wxASSERT( !force || success );
468 }
469
470 if ( success ) {
471 // Resolve collisions among remaining like-named items.
472 ++iter;
473 if ( iter != right && iPass != 0 &&
474 iter->second.type != OrderingHint::Unspecified &&
475 !( iter->second == hint ) ) {
476 // A diagnostic message sometimes
477 ReportConflictingPlacements( itemOrdering.key, pItem->name );
478 }
479 while ( iter != right )
480 // Re-invoke MergeWithExistingItem for this item, which is known
481 // to have a name collision, so ignore the return value.
482 MergeWithExistingItem( visitor, itemOrdering, iter++ -> first );
483 }
484
485 return success;
486}
487
488inline bool MajorComp(
490 // Descending sort!
491 return a.first->name > b.first->name;
492};
493inline bool MinorComp(
495 // Sort by hint type.
496 // This sorts items with unspecified hints last.
497 return a.second < b.second;
498};
499inline bool Comp(
501 if ( MajorComp( a, b ) )
502 return true;
503 if ( MajorComp( b, a ) )
504 return false;
505 return MinorComp( a, b );
506};
507
508auto CollectedItems::MergeItemsAscendingNamesPass(
509 Visitor &visitor, ItemOrdering &itemOrdering, NewItems &newItems,
510 const int iPass, size_t endItemsCount, bool force ) -> void
511{
512 // Inner loop over ranges of like-named items.
513 auto rright = newItems.rbegin();
514 auto rend = newItems.rend();
515 while ( rright != rend ) {
516 // Find the range
517 using namespace std::placeholders;
518 auto rleft = std::find_if(
519 rright + 1, rend, std::bind( MajorComp, _1, *rright ) );
520
521 bool success = MergeLikeNamedItems(
522 visitor, itemOrdering, rleft.base(), rright.base(), iPass,
523 endItemsCount, force );
524
525 if ( success ) {
526 auto diff = rend - rleft;
527 newItems.erase( rleft.base(), rright.base() );
528 rend = newItems.rend();
529 rleft = rend - diff;
530 }
531 rright = rleft;
532 }
533}
534
535auto CollectedItems::MergeItemsDescendingNamesPass(
536 Visitor &visitor, ItemOrdering &itemOrdering, NewItems &newItems,
537 const int iPass, size_t endItemsCount, bool force ) -> void
538{
539 // Inner loop over ranges of like-named items.
540 auto left = newItems.begin();
541 while ( left != newItems.end() ) {
542 // Find the range
543 using namespace std::placeholders;
544 auto right = std::find_if(
545 left + 1, newItems.end(), std::bind( MajorComp, *left, _1 ) );
546
547 bool success = MergeLikeNamedItems(
548 visitor, itemOrdering, left, right, iPass,
549 endItemsCount, force );
550
551 if ( success )
552 left = newItems.erase( left, right );
553 else
554 left = right;
555 }
556};
557
558auto CollectedItems::MergeItems(
559 Visitor &visitor, ItemOrdering &itemOrdering,
560 const GroupItemBase &toMerge, const OrderingHint &hint ) -> void
561{
562 NewItems newItems;
563
564 {
565 // First do expansion of nameless groupings, and caching of computed
566 // items, just as for the previously collected items.
567 CollectedItems newCollection{ {}, computedItems };
568 CollectItems(visitor, newCollection, toMerge, hint);
569
570 // Try to merge each, resolving name collisions with items already in the
571 // tree, and collecting those with names that don't collide.
572 for (const auto &item : newCollection.items)
573 if (!MergeWithExistingItem(visitor, itemOrdering, item.visitNow))
574 newItems.push_back({ item.visitNow, item.hint });
575 }
576
577 // Choose placements for items with NEW names.
578
579 // First sort so that like named items are together, and for the same name,
580 // items with more specific ordering hints come earlier.
581 std::sort( newItems.begin(), newItems.end(), Comp );
582
583 // Outer loop over trial passes.
584 int iPass = -1;
585 bool force = false;
586 size_t oldSize = 0;
587 size_t endItemsCount = 0;
588 auto prevSize = newItems.size();
589 while( !newItems.empty() )
590 {
591 // If several items have the same hint, we try to preserve the sort by
592 // name (an internal identifier, not necessarily user visible), just to
593 // have some determinacy. That requires passing one or the other way
594 // over newItems.
595 bool descending =
596 ( iPass == OrderingHint::After || iPass == OrderingHint::Begin );
597
598 if ( descending )
599 MergeItemsDescendingNamesPass(
600 visitor, itemOrdering, newItems, iPass, endItemsCount, force );
601 else
602 MergeItemsAscendingNamesPass(
603 visitor, itemOrdering, newItems, iPass, endItemsCount, force );
604
605 auto newSize = newItems.size();
606 ++iPass;
607
608 if ( iPass == 0 )
609 // Just tried insertion by preferences. Don't try it again.
610 oldSize = newSize;
611 else if ( iPass == OrderingHint::Unspecified ) {
612 if ( !force ) {
613 iPass = 0, oldSize = newSize;
614 // Are we really ready for the final pass?
615 bool progress = ( oldSize > newSize );
616 if ( progress )
617 // No. While some progress is made, don't force final placements.
618 // Retry Before and After hints.
619 ;
620 else
621 force = true;
622 }
623 }
624 else if (iPass == OrderingHint::End && endItemsCount == 0)
625 {
626 assert(newSize >= prevSize || newSize == 0);
627 // Remember the size before we put the ending items in place
628 endItemsCount = newSize - prevSize;
629 }
630
631 prevSize = newSize;
632 }
633}
634
635// forward declaration for mutually recursive functions
636void VisitItem(
637 Registry::Visitor &visitor, CollectedItems &collection,
638 Path &path, BaseItem *pItem,
639 const GroupItemBase *pToMerge, const OrderingHint &hint,
640 bool &doFlush );
642 Registry::Visitor &visitor, CollectedItems &collection,
643 Path &path, const GroupItemBase &group,
644 const GroupItemBase *pToMerge, const OrderingHint &hint,
645 bool &doFlush )
646{
647 // Make a NEW collection for this subtree, sharing the memo cache
648 CollectedItems newCollection{ {}, collection.computedItems };
649
650 // Gather items at this level
651 // (The ordering hint is irrelevant when not merging items in)
652 CollectItems(visitor, newCollection, group, {});
653
654 path.push_back(group.name.GET());
655
656 // Merge with the registry
657 if ( pToMerge )
658 {
659 ItemOrdering itemOrdering{ path };
660 newCollection.MergeItems(visitor, itemOrdering, *pToMerge, hint);
661
662 // Remember the NEW ordering, if there was any need to use the old.
663 // This makes a side effect in preferences.
664 if ( itemOrdering.gotOrdering ) {
665 wxString newValue;
666 for ( const auto &item : newCollection.items ) {
667 const auto &name = item.visitNow->name;
668 if ( !name.empty() )
669 newValue += newValue.empty()
670 ? name.GET()
671 : ',' + name.GET();
672 }
673 if (newValue != itemOrdering.strValue) {
674 gPrefs->Write( itemOrdering.key, newValue );
675 doFlush = true;
676 }
677 }
678 }
679
680 // Now visit them
681 for ( const auto &item : newCollection.items )
682 VisitItem( visitor, collection, path,
683 item.visitNow, item.mergeLater, item.hint,
684 doFlush );
685
686 path.pop_back();
687}
689 Registry::Visitor &visitor, CollectedItems &collection,
690 Path &path, BaseItem *pItem,
691 const GroupItemBase *pToMerge, const OrderingHint &hint,
692 bool &doFlush )
693{
694 if (!pItem)
695 return;
696
697 if (const auto pSingle =
698 dynamic_cast<SingleItem*>( pItem )) {
699 wxASSERT( !pToMerge );
700 visitor.Visit( *pSingle, path );
701 }
702 else
703 if (const auto pGroup =
704 dynamic_cast<GroupItemBase*>( pItem )) {
705 visitor.BeginGroup( *pGroup, path );
706 // recursion
707 VisitItems(visitor, collection, path, *pGroup, pToMerge, hint, doFlush);
708 visitor.EndGroup(*pGroup, path);
709 }
710 else
711 wxASSERT( false );
712}
713
714}
715
716namespace Registry {
717
719
720IndirectItemBase::~IndirectItemBase() {}
721
722ComputedItemBase::~ComputedItemBase() {}
723
725
727auto GroupItemBase::GetOrdering() const -> Ordering { return Strong; }
728
731{
732 static EmptyContext context;
733 return &context;
734}
737void Visitor::Visit(SingleItem &, const Path &) {}
738
739void Visit( Visitor &visitor, BaseItem *pTopItem, const GroupItemBase *pRegistry )
740{
741 std::vector< BaseItemSharedPtr > computedItems;
742 bool doFlush = false;
743 CollectedItems collection{ {}, computedItems };
744 Path emptyPath;
745 VisitItem(
746 visitor, collection, emptyPath, pTopItem,
747 pRegistry, pRegistry->orderingHint, doFlush );
748 // Flush any writes done by MergeItems()
749 if (doFlush)
750 gPrefs->Flush();
751}
752
754 Literal root, Pairs pairs )
755 : mPairs{ std::move( pairs ) }
756 , mRoot{ root }
757{
758 (*this)();
759}
760
762{
763 bool doFlush = false;
764 for (const auto &pair : mPairs) {
765 const auto key = wxString{'/'} + mRoot + pair.first;
766 if ( gPrefs->Read(key).empty() ) {
767 gPrefs->Write( key, pair.second );
768 doFlush = true;
769 }
770 }
771
772 if (doFlush)
773 gPrefs->Flush();
774}
775
776void RegisterItem( GroupItemBase &registry, const Placement &placement,
777 BaseItemPtr pItem )
778{
779 // Since registration determines only an unordered tree of menu items,
780 // we can sort children of each node lexicographically for our convenience.
781 std::vector<BaseItemPtr> *pItems{};
782 struct Comparator {
783 bool operator()
784 ( const Identifier &component, const BaseItemPtr& pItem ) const {
785 return component < pItem->name; }
786 bool operator()
787 ( const BaseItemPtr& pItem, const Identifier &component ) const {
788 return pItem->name < component; }
789 };
790 auto find = [&pItems]( const Identifier &component ){ return std::equal_range(
791 pItems->begin(), pItems->end(), component, Comparator() ); };
792
793 auto pNode = &registry;
794 pItems = &pNode->items;
795
796 const auto pathComponents = ::wxSplit( placement.path, '/' );
797 auto pComponent = pathComponents.begin(), end = pathComponents.end();
798
799 // Descend the registry hierarchy, while groups matching the path components
800 // can be found
801 auto debugPath = wxString{'/'} + registry.name.GET();
802 while ( pComponent != end ) {
803 const auto &pathComponent = *pComponent;
804
805 // Try to find an item already present that is a group item with the
806 // same name; we don't care which if there is more than one.
807 const auto range = find( pathComponent );
808 const auto iter2 = std::find_if( range.first, range.second,
809 [](const BaseItemPtr &pItem){
810 return dynamic_cast< GroupItemBase* >( pItem.get() ); } );
811
812 if ( iter2 != range.second ) {
813 // A matching group in the registry, so descend
814 pNode = static_cast< GroupItemBase* >( iter2->get() );
815 pItems = &pNode->items;
816 debugPath += '/' + pathComponent;
817 ++pComponent;
818 }
819 else
820 // Insert at this level;
821 // If there are no more path components, and a name collision of
822 // the added item with something already in the registry, don't resolve
823 // it yet in this function, but see MergeItems().
824 break;
825 }
826
827 // Create path group items for remaining components
828 while ( pComponent != end ) {
829 auto newNode =
830 std::make_unique<PlaceHolder>(*pComponent, GroupItemBase::Weak);
831 pNode = newNode.get();
832 pItems->insert( find( pNode->name ).second, std::move( newNode ) );
833 pItems = &pNode->items;
834 ++pComponent;
835 }
836
837 // Remember the hint, to be used later in merging.
838 pItem->orderingHint = placement.hint;
839
840 // Now insert the item.
841 pItems->insert( find( pItem->name ).second, std::move( pItem ) );
842}
843
844template struct GroupItem<DefaultTraits>;
845}
Toolkit-neutral facade for basic user interface services.
static const AudacityProject::AttachedObjects::RegisteredFactory key
const TranslatableString name
Definition: Distortion.cpp:76
XO("Cut/Copy/Paste")
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
An explicitly nonlocalized string, not meant for the user to see.
Definition: Identifier.h:22
virtual void BeginGroup(GroupItemBase &item, const Path &path)
Definition: Registry.cpp:735
virtual void * GetComputedItemContext()
Get context given to Computed items during visitation.
Definition: Registry.cpp:730
virtual void EndGroup(GroupItemBase &item, const Path &path)
Definition: Registry.cpp:736
std::vector< Identifier > Path
Definition: Registry.h:304
virtual ~Visitor()
Definition: Registry.cpp:729
virtual void Visit(SingleItem &item, const Path &path)
Definition: Registry.cpp:737
Holds a msgid for the translation catalog; may also bind format arguments.
virtual bool Flush() noexcept=0
virtual bool Write(const wxString &key, bool value)=0
virtual bool Read(const wxString &key, bool *value) const =0
PROJECT_FILE_IO_API wxString Find(const FilePath &path)
MessageBoxResult ShowMessageBox(const TranslatableString &message, MessageBoxOptions options={})
Show a modal message box with either Ok or Yes and No, and optionally Cancel.
Definition: BasicUI.h:274
bool Begin(const FilePath &dataDir)
Definition: Journal.cpp:226
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:150
Definition: Menus.h:35
void RegisterItem(GroupItemBase &registry, const Placement &placement, BaseItemPtr pItem)
Definition: Registry.cpp:776
std::unique_ptr< BaseItem > BaseItemPtr
Definition: Registry.h:77
void Visit(Visitor &visitor, BaseItem *pTopItem, const GroupItemBase *pRegistry)
Definition: Registry.cpp:739
void CollectItem(Registry::Visitor &visitor, CollectedItems &collection, BaseItem *Item, const OrderingHint &hint)
Definition: Registry.cpp:132
bool MinorComp(const CollectedItems::NewItem &a, const CollectedItems::NewItem &b)
Definition: Registry.cpp:493
void ReportConflictingPlacements(const wxString &key, const Identifier &name)
Definition: Registry.cpp:211
std::vector< Identifier > Path
Definition: Registry.cpp:178
void VisitItems(Registry::Visitor &visitor, CollectedItems &collection, Path &path, const GroupItemBase &group, const GroupItemBase *pToMerge, const OrderingHint &hint, bool &doFlush)
Definition: Registry.cpp:641
void ReportItemItemCollision(const wxString &key, const Identifier &name)
Definition: Registry.cpp:204
std::unordered_set< wxString > sBadPaths
Definition: Registry.cpp:180
bool Comp(const CollectedItems::NewItem &a, const CollectedItems::NewItem &b)
Definition: Registry.cpp:499
const OrderingHint & ChooseHint(BaseItem *delegate, const OrderingHint &hint)
Definition: Registry.cpp:108
void VisitItem(Registry::Visitor &visitor, CollectedItems &collection, Path &path, BaseItem *pItem, const GroupItemBase *pToMerge, const OrderingHint &hint, bool &doFlush)
Definition: Registry.cpp:688
void BadPath(const TranslatableString &format, const wxString &key, const Identifier &name)
Definition: Registry.cpp:181
bool MajorComp(const CollectedItems::NewItem &a, const CollectedItems::NewItem &b)
Definition: Registry.cpp:488
void CollectItems(Registry::Visitor &visitor, CollectedItems &collection, const GroupItemBase &items, const OrderingHint &hint)
Definition: Registry.cpp:124
void ReportGroupGroupCollision(const wxString &key, const Identifier &name)
Definition: Registry.cpp:197
STL namespace.
const Identifier name
Definition: Registry.h:73
OrderingHint orderingHint
Definition: Registry.h:75
virtual ~BaseItem()
Definition: Registry.cpp:718
Common abstract base class for items that group other items.
Definition: Registry.h:170
~GroupItemBase() override=0
Definition: Registry.cpp:726
Ordering
Choose treatment of the children of the group when merging trees.
Definition: Registry.h:178
virtual Ordering GetOrdering() const
Default implementation returns Strong.
Definition: Registry.cpp:727
Extends GroupItemBase with a variadic constructor.
Definition: Registry.h:223
OrderingPreferenceInitializer(Literal root, Pairs pairs)
Definition: Registry.cpp:753
OrderingHint hint
Definition: Registry.h:236
Common abstract base class for items that are not groups.
Definition: Registry.h:162
~SingleItem() override=0
Definition: Registry.cpp:724
std::vector< BaseItemSharedPtr > & computedItems
Definition: Registry.cpp:50
auto Find(const Identifier &name) -> Iterator
Definition: Registry.cpp:54
std::pair< BaseItem *, OrderingHint > NewItem
Definition: Registry.cpp:82
PlaceHolder(const Identifier &identifier, Ordering ordering)
Definition: Registry.cpp:27
Ordering GetOrdering() const override
Default implementation returns Strong.
Definition: Registry.cpp:32