1 /**********************************************************************
2 
3 Audacity: A Digital Audio Editor
4 
5 Registry.h
6 
7 Paul Licameli split from CommandManager.h
8 
9 **********************************************************************/
10 
11 #ifndef __AUDACITY_REGISTRY__
12 #define __AUDACITY_REGISTRY__
13 
14 #include "Prefs.h"
15 
16 // Define classes and functions that associate parts of the user interface
17 // with path names
18 namespace Registry {
19    // Items in the registry form an unordered tree, but each may also describe a
20    // desired insertion point among its peers.  The request might not be honored
21    // (as when the other name is not found, or when more than one item requests
22    // the same ordering), but this is not treated as an error.
23    struct OrderingHint
24    {
25       // The default Unspecified hint is just like End, except that in case the
26       // item is delegated to (by a SharedItem, ComputedItem, or nameless
27       // transparent group), the delegating item's hint will be used instead
28       enum Type : int {
29          Before, After,
30          Begin, End,
31          Unspecified // keep this last
32       } type{ Unspecified };
33 
34       // name of some other BaseItem; significant only when type is Before or
35       // After:
36       Identifier name;
37 
OrderingHintOrderingHint38       OrderingHint() {}
39       OrderingHint( Type type_, const wxString &name_ = {} )
typeOrderingHint40          : type(type_), name(name_) {}
41 
42       bool operator == ( const OrderingHint &other ) const
43       { return name == other.name && type == other.type; }
44 
45       bool operator < ( const OrderingHint &other ) const
46       {
47          // This sorts unspecified placements later
48          return std::make_pair( type, name ) <
49             std::make_pair( other.type, other.name );
50       }
51    };
52 
53    // TODO C++17: maybe use std::variant (discriminated unions) to achieve
54    // polymorphism by other means, not needing unique_ptr and dynamic_cast
55    // and using less heap.
56    // Most items in the table will be the large ones describing commands, so the
57    // waste of space in unions for separators and sub-menus should not be
58    // large.
59    struct REGISTRIES_API BaseItem {
60       // declare at least one virtual function so dynamic_cast will work
61       explicit
BaseItemBaseItem62       BaseItem( const Identifier &internalName )
63          : name{ internalName }
64       {}
65       virtual ~BaseItem();
66 
67       const Identifier name;
68 
69       OrderingHint orderingHint;
70    };
71    using BaseItemPtr = std::unique_ptr<BaseItem>;
72    using BaseItemSharedPtr = std::shared_ptr<BaseItem>;
73    using BaseItemPtrs = std::vector<BaseItemPtr>;
74 
75    class Visitor;
76 
77 
78    // An item that delegates to another held in a shared pointer; this allows
79    // static tables of items to be computed once and reused
80    // The name of the delegate is significant for path calculations, but the
81    // SharedItem's ordering hint is used if the delegate has none
82    struct REGISTRIES_API SharedItem final : BaseItem {
SharedItemfinal83       explicit SharedItem( const BaseItemSharedPtr &ptr_ )
84          : BaseItem{ wxEmptyString }
85          , ptr{ ptr_ }
86       {}
87       ~SharedItem() override;
88 
89       BaseItemSharedPtr ptr;
90    };
91 
92    // A convenience function
Shared(const BaseItemSharedPtr & ptr)93    inline std::unique_ptr<SharedItem> Shared( const BaseItemSharedPtr &ptr )
94       { return std::make_unique<SharedItem>( ptr ); }
95 
96    // An item that computes some other item to substitute for it, each time
97    // the ComputedItem is visited
98    // The name of the substitute is significant for path calculations, but the
99    // ComputedItem's ordering hint is used if the substitute has none
100    struct REGISTRIES_API ComputedItem final : BaseItem {
101       // The type of functions that generate descriptions of items.
102       // Return type is a shared_ptr to let the function decide whether to
103       // recycle the object or rebuild it on demand each time.
104       // Return value from the factory may be null
105       template< typename VisitorType >
106       using Factory = std::function< BaseItemSharedPtr( VisitorType & ) >;
107 
108       using DefaultVisitor = Visitor;
109 
ComputedItemfinal110       explicit ComputedItem( const Factory< DefaultVisitor > &factory_ )
111          : BaseItem( wxEmptyString )
112          , factory{ factory_ }
113       {}
114       ~ComputedItem() override;
115 
116       Factory< DefaultVisitor > factory;
117    };
118 
119    // Common abstract base class for items that are not groups
120    struct REGISTRIES_API SingleItem : BaseItem {
121       using BaseItem::BaseItem;
122       ~SingleItem() override = 0;
123    };
124 
125    // Common abstract base class for items that group other items
126    struct REGISTRIES_API GroupItem : BaseItem {
127       using BaseItem::BaseItem;
128 
129       // Construction from an internal name and a previously built-up
130       // vector of pointers
GroupItemGroupItem131       GroupItem( const Identifier &internalName, BaseItemPtrs &&items_ )
132          : BaseItem{ internalName }, items{ std::move( items_ ) }
133       {}
134       GroupItem( const GroupItem& ) PROHIBITED;
135       ~GroupItem() override = 0;
136 
137       // Whether the item is non-significant for path naming
138       // when it also has an empty name
139       virtual bool Transparent() const = 0;
140 
141       BaseItemPtrs items;
142    };
143 
144    // GroupItem adding variadic constructor conveniences
145    template< typename VisitorType = ComputedItem::DefaultVisitor >
146    struct InlineGroupItem : GroupItem {
147       using GroupItem::GroupItem;
148       // In-line, variadic constructor that doesn't require building a vector
149       template< typename... Args >
InlineGroupItemInlineGroupItem150          InlineGroupItem( const Identifier &internalName, Args&&... args )
151          : GroupItem( internalName )
152          { Append( std::forward< Args >( args )... ); }
153 
154    private:
155       // nullary overload grounds the recursion
AppendInlineGroupItem156       void Append() {}
157       // recursive overload
158       template< typename Arg, typename... Args >
AppendInlineGroupItem159          void Append( Arg &&arg, Args&&... moreArgs )
160          {
161             // Dispatch one argument to the proper overload of AppendOne.
162             // std::forward preserves rvalue/lvalue distinction of the actual
163             // argument of the constructor call; that is, it inserts a
164             // std::move() if and only if the original argument is rvalue
165             AppendOne( std::forward<Arg>( arg ) );
166             // recur with the rest of the arguments
167             Append( std::forward<Args>(moreArgs)... );
168          };
169 
170       // Move one unique_ptr to an item into our array
AppendOneInlineGroupItem171       void AppendOne( BaseItemPtr&& ptr )
172       {
173          items.push_back( std::move( ptr ) );
174       }
175       // This overload allows a lambda or function pointer in the variadic
176       // argument lists without any other syntactic wrapping, and also
177       // allows implicit conversions to type Factory.
178       // (Thus, a lambda can return a unique_ptr<BaseItem> rvalue even though
179       // Factory's return type is shared_ptr, and the needed conversion is
180       // applied implicitly.)
AppendOneInlineGroupItem181       void AppendOne( const ComputedItem::Factory<VisitorType> &factory )
182       {
183          auto adaptedFactory = [factory]( Registry::Visitor &visitor ){
184             return factory( dynamic_cast< VisitorType& >( visitor ) );
185          };
186          AppendOne( std::make_unique<ComputedItem>( adaptedFactory ) );
187       }
188       // This overload lets you supply a shared pointer to an item, directly
189       template<typename Subtype>
AppendOneInlineGroupItem190       void AppendOne( const std::shared_ptr<Subtype> &ptr )
191       { AppendOne( std::make_unique<SharedItem>(ptr) ); }
192    };
193 
194    // Inline group item also specifying transparency
195    template< bool transparent,
196       typename VisitorType = ComputedItem::DefaultVisitor >
197    struct ConcreteGroupItem : InlineGroupItem< VisitorType >
198    {
199       using InlineGroupItem< VisitorType >::InlineGroupItem;
~ConcreteGroupItemConcreteGroupItem200       ~ConcreteGroupItem() {}
TransparentConcreteGroupItem201       bool Transparent() const override { return transparent; }
202    };
203 
204    // Concrete subclass of GroupItem that adds nothing else
205    // TransparentGroupItem with an empty name is transparent to item path calculations
206    // and propagates its ordering hint if subordinates don't specify hints
207    // and it does specify one
208    template< typename VisitorType = ComputedItem::DefaultVisitor >
209    struct TransparentGroupItem final : ConcreteGroupItem< true, VisitorType >
210    {
211       using ConcreteGroupItem< true, VisitorType >::ConcreteGroupItem;
~TransparentGroupItemfinal212       ~TransparentGroupItem() override {}
213    };
214 
215    // The /-separated path is relative to the GroupItem supplied to
216    // RegisterItem.
217    // For instance, wxT("Transport/Cursor") to locate an item under a sub-menu
218    // of a main menu
219    struct Placement {
220       wxString path;
221       OrderingHint hint;
222 
223       Placement( const wxString &path_, const OrderingHint &hint_ = {} )
pathPlacement224          : path( path_ ), hint( hint_ )
225       {}
226    };
227 
228    // registry collects items, before consulting preferences and ordering
229    // hints, and applying the merge procedure to them.
230    // This function puts one more item into the registry.
231    // The sequence of calls to RegisterItem has no significance for
232    // determining the visitation ordering.  When sequence is important, register
233    // a GroupItem.
234    REGISTRIES_API
235    void RegisterItem( GroupItem &registry, const Placement &placement,
236       BaseItemPtr pItem );
237 
238    // Define actions to be done in Visit.
239    // Default implementations do nothing
240    // The supplied path does not include the name of the item
241    class REGISTRIES_API Visitor
242    {
243    public:
244       virtual ~Visitor();
245       using Path = std::vector< Identifier >;
246       virtual void BeginGroup( GroupItem &item, const Path &path );
247       virtual void EndGroup( GroupItem &item, const Path &path );
248       virtual void Visit( SingleItem &item, const Path &path );
249    };
250 
251    // Top-down visitation of all items and groups in a tree rooted in
252    // pTopItem, as merged with pRegistry.
253    // The merger of the trees is recomputed in each call, not saved.
254    // So neither given tree is modified.
255    // But there may be a side effect on preferences to remember the ordering
256    // imposed on each node of the unordered tree of registered items; each item
257    // seen in the registry for the first time is placed somehere, and that
258    // ordering should be kept the same thereafter in later runs (which may add
259    // yet other previously unknown items).
260    REGISTRIES_API void Visit(
261       Visitor &visitor,
262       BaseItem *pTopItem,
263       const GroupItem *pRegistry = nullptr );
264 
265    // Typically a static object.  Constructor initializes certain preferences
266    // if they are not present.  These preferences determine an extrinsic
267    // visitation ordering for registered items.  This is needed in some
268    // places that have migrated from a system of exhaustive listings, to a
269    // registry of plug-ins, and something must be done to preserve old
270    // behavior.  It can be done in the central place using string literal
271    // identifiers only, not requiring static compilation or linkage dependency.
272    struct REGISTRIES_API
273    OrderingPreferenceInitializer : PreferenceInitializer {
274       using Literal = const wxChar *;
275       using Pair = std::pair< Literal, Literal >;
276       using Pairs = std::vector< Pair >;
277       OrderingPreferenceInitializer(
278          // Specifies the topmost preference section:
279          Literal root,
280          // Specifies /-separated Registry paths relative to root
281          // (these should be blank or start with / and not end with /),
282          // each with a ,-separated sequence of identifiers, which specify a
283          // desired ordering at one node of the tree:
284          Pairs pairs );
285 
286       void operator () () override;
287 
288    private:
289       Pairs mPairs;
290       Literal mRoot;
291    };
292 }
293 
294 #endif
295 
296