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 ®istry, 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