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