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