1 //
2 // Copyright 2016 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 //    names, trademarks, service marks, or product names of the Licensor
11 //    and its affiliates, except as required to comply with Section 4(c) of
12 //    the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 //     http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 #ifndef PXR_USD_SDF_LIST_EDITOR_PROXY_H
25 #define PXR_USD_SDF_LIST_EDITOR_PROXY_H
26 
27 /// \file sdf/listEditorProxy.h
28 
29 #include "pxr/pxr.h"
30 #include "pxr/usd/sdf/listEditor.h"
31 #include "pxr/usd/sdf/listProxy.h"
32 #include "pxr/usd/sdf/changeBlock.h"
33 
34 #include "pxr/base/vt/value.h"  // for Vt_DefaultValueFactory
35 
36 #include <boost/optional.hpp>
37 #include <boost/shared_ptr.hpp>
38 
39 #include <functional>
40 
41 PXR_NAMESPACE_OPEN_SCOPE
42 
43 /// \class SdfListEditorProxy
44 ///
45 /// Represents a set of list editing operations.
46 ///
47 /// An SdfListEditorProxy allows consumers to specify a transformation to be
48 /// applied to a list via a set of list editing operations. Given a starting
49 /// ordered list, it can either replace the result with another ordered list
50 /// or apply a sequence of three operations:  deleting keys, then adding keys
51 /// to the end (if they aren't already in the starting list), then reordering
52 /// keys.
53 ///
54 /// The type policy defines the value type that a particular proxy can operate
55 /// on.
56 template <class _TypePolicy>
57 class SdfListEditorProxy {
58 public:
59     typedef _TypePolicy TypePolicy;
60     typedef SdfListEditorProxy<TypePolicy> This;
61     typedef SdfListProxy<TypePolicy> ListProxy;
62     typedef typename TypePolicy::value_type value_type;
63     typedef std::vector<value_type> value_vector_type;
64 
65     // ApplyEdits types.
66     typedef std::function<boost::optional<value_type>
67                         (SdfListOpType, const value_type&)> ApplyCallback;
68 
69     // ModifyEdits types.
70     typedef std::function<boost::optional<value_type>
71                         (const value_type&)> ModifyCallback;
72 
73     /// Creates a default proxy object. The object evaluates to \c false in a
74     /// boolean context and all operations on this object have no effect.
SdfListEditorProxy()75     SdfListEditorProxy()
76     {
77     }
78 
79     /// Creates a new proxy object backed by the supplied list editor.
SdfListEditorProxy(const boost::shared_ptr<Sdf_ListEditor<TypePolicy>> & listEditor)80     explicit SdfListEditorProxy(
81         const boost::shared_ptr<Sdf_ListEditor<TypePolicy> >& listEditor)
82         : _listEditor(listEditor)
83     {
84     }
85 
86     /// Returns true if the list editor is expired.
IsExpired()87     bool IsExpired() const
88     {
89         if (!_listEditor) {
90             return false;
91         }
92 
93         return _listEditor->IsExpired();
94     }
95 
96     /// Returns \c true if the editor has an explicit list, \c false if
97     /// it has list operations.
IsExplicit()98     bool IsExplicit() const
99     {
100         return _Validate() ? _listEditor->IsExplicit() : true;
101     }
102 
103     /// Returns \c true if the editor is not explicit and allows ordering
104     /// only.
IsOrderedOnly()105     bool IsOrderedOnly() const
106     {
107         return _Validate() ? _listEditor->IsOrderedOnly() : false;
108     }
109 
110     /// Returns \c true if the editor has an explicit list (even if it's
111     /// empty) or it has any added, prepended, appended, deleted,
112     /// or ordered keys.
HasKeys()113     bool HasKeys() const
114     {
115         return _Validate() ? _listEditor->HasKeys() : true;
116     }
117 
118     /// Apply the edits to \p vec.
ApplyEditsToList(value_vector_type * vec)119     void ApplyEditsToList(value_vector_type* vec) const
120     {
121         if (_Validate()) {
122             _listEditor->ApplyEditsToList(vec, ApplyCallback());
123         }
124     }
125 
126     /// Apply the edits to \p vec.  If \p callback is valid then it's called
127     /// for every key in the editor before applying it to \p vec.  If the
128     /// returned key is invalid then the key will not be applied.  Otherwise
129     /// the returned key is applied, allowing callbacks to perform key
130     /// translation.
131     template <class CB>
ApplyEditsToList(value_vector_type * vec,CB callback)132     void ApplyEditsToList(value_vector_type* vec, CB callback) const
133     {
134         if (_Validate()) {
135             _listEditor->ApplyEditsToList(vec, ApplyCallback(callback));
136         }
137     }
138 
139     /// Copies the keys from \p other.  This differs from assignment
140     /// because assignment just makes two list editors refer to the
141     /// same lists.
142     ///
143     /// Not all list editors support changing their mode.  If the mode
144     /// can't be changed to the mode of \p other then this does nothing
145     /// and returns \c false, otherwise it returns \c true.
CopyItems(const This & other)146     bool CopyItems(const This& other)
147     {
148         return _Validate() && other._Validate() ?
149             _listEditor->CopyEdits(*other._listEditor) : false;
150     }
151 
152     /// Removes all keys and changes the editor to have list operations.
153     ///
154     /// Not all list editors support changing their mode.  If the mode
155     /// can't be changed to the mode of \p other then this does nothing
156     /// and returns \c false, otherwise it returns \c true.
ClearEdits()157     bool ClearEdits()
158     {
159         return _Validate() ? _listEditor->ClearEdits() : false;
160     }
161 
162     /// Removes all keys and changes the editor to be explicit.
163     ///
164     /// Not all list editors support changing their mode.  If the mode
165     /// can't be changed to the mode of \p other then this does nothing
166     /// and returns \c false, otherwise it returns \c true.
ClearEditsAndMakeExplicit()167     bool ClearEditsAndMakeExplicit()
168     {
169         return _Validate() ? _listEditor->ClearEditsAndMakeExplicit() : false;
170     }
171 
172     /// \p callback is called for every key.  If the returned key is
173     /// invalid then the key is removed, otherwise it's replaced with the
174     /// returned key.
175     template <class CB>
ModifyItemEdits(CB callback)176     void ModifyItemEdits(CB callback)
177     {
178         if (_Validate()) {
179             _listEditor->ModifyItemEdits(ModifyCallback(callback));
180         }
181     }
182 
183     /// Check if the given item is explicit, added, prepended, appended,
184     /// deleted, or ordered by this editor. If \p onlyAddOrExplicit is
185     /// \c true we only check the added or explicit items.
186     bool ContainsItemEdit(const value_type& item,
187                           bool onlyAddOrExplicit = false) const
188     {
189         if (_Validate()) {
190             size_t i;
191 
192             i = GetExplicitItems().Find(item);
193             if (i != size_t(-1)) {
194                 return true;
195             }
196 
197             i = GetAddedItems().Find(item);
198             if (i != size_t(-1)) {
199                 return true;
200             }
201 
202             i = GetPrependedItems().Find(item);
203             if (i != size_t(-1)) {
204                 return true;
205             }
206 
207             i = GetAppendedItems().Find(item);
208             if (i != size_t(-1)) {
209                 return true;
210             }
211 
212             if (!onlyAddOrExplicit) {
213                 i = GetDeletedItems().Find(item);
214                 if (i != size_t(-1)) {
215                     return true;
216                 }
217 
218                 i = GetOrderedItems().Find(item);
219                 if (i != size_t(-1)) {
220                     return true;
221                 }
222             }
223         }
224 
225         return false;
226     }
227 
228     /// Remove all occurrences of the given item, regardless of whether
229     /// the item is explicit, added, prepended, appended, deleted, or ordered.
RemoveItemEdits(const value_type & item)230     void RemoveItemEdits(const value_type& item)
231     {
232         if (_Validate()) {
233             SdfChangeBlock block;
234 
235             GetExplicitItems().Remove(item);
236             GetAddedItems().Remove(item);
237             GetPrependedItems().Remove(item);
238             GetAppendedItems().Remove(item);
239             GetDeletedItems().Remove(item);
240             GetOrderedItems().Remove(item);
241         }
242     }
243 
244     /// Replace all occurrences of the given item, regardless of
245     /// whether the item is explicit, added, prepended, appended,
246     /// deleted or ordered.
ReplaceItemEdits(const value_type & oldItem,const value_type & newItem)247     void ReplaceItemEdits(const value_type& oldItem, const value_type& newItem)
248     {
249         if (_Validate()) {
250             SdfChangeBlock block;
251 
252             GetExplicitItems().Replace(oldItem, newItem);
253             GetAddedItems().Replace(oldItem, newItem);
254             GetPrependedItems().Replace(oldItem, newItem);
255             GetAppendedItems().Replace(oldItem, newItem);
256             GetDeletedItems().Replace(oldItem, newItem);
257             GetOrderedItems().Replace(oldItem, newItem);
258         }
259     }
260 
261     /// Returns the explicitly set items.
GetExplicitItems()262     ListProxy GetExplicitItems() const
263     {
264         return ListProxy(_listEditor, SdfListOpTypeExplicit);
265     }
266 
267     /// Returns the items added by this list editor
GetAddedItems()268     ListProxy GetAddedItems() const
269     {
270         return ListProxy(_listEditor, SdfListOpTypeAdded);
271     }
272 
273     /// Returns the items prepended by this list editor
GetPrependedItems()274     ListProxy GetPrependedItems() const
275     {
276         return ListProxy(_listEditor, SdfListOpTypePrepended);
277     }
278 
279     /// Returns the items appended by this list editor
GetAppendedItems()280     ListProxy GetAppendedItems() const
281     {
282         return ListProxy(_listEditor, SdfListOpTypeAppended);
283     }
284 
285     /// Returns the items deleted by this list editor
GetDeletedItems()286     ListProxy GetDeletedItems() const
287     {
288         return ListProxy(_listEditor, SdfListOpTypeDeleted);
289     }
290 
291     /// Returns the items reordered by this list editor
GetOrderedItems()292     ListProxy GetOrderedItems() const
293     {
294         return ListProxy(_listEditor, SdfListOpTypeOrdered);
295     }
296 
297     /// Returns the added or explicitly set items.
GetAddedOrExplicitItems()298     value_vector_type GetAddedOrExplicitItems() const
299     {
300         value_vector_type result;
301         if (_Validate()) {
302             _listEditor->ApplyEditsToList(&result);
303         }
304         return result;
305     }
306 
Add(const value_type & value)307     void Add(const value_type& value)
308     {
309         if (_Validate()) {
310             if (!_listEditor->IsOrderedOnly()) {
311                 if (_listEditor->IsExplicit()) {
312                     _AddOrReplace(SdfListOpTypeExplicit, value);
313                 }
314                 else {
315                     GetDeletedItems().Remove(value);
316                     _AddOrReplace(SdfListOpTypeAdded, value);
317                 }
318             }
319         }
320     }
321 
Prepend(const value_type & value)322     void Prepend(const value_type& value)
323     {
324         if (_Validate()) {
325             if (!_listEditor->IsOrderedOnly()) {
326                 if (_listEditor->IsExplicit()) {
327                     _Prepend(SdfListOpTypeExplicit, value);
328                 }
329                 else {
330                     GetDeletedItems().Remove(value);
331                     _Prepend(SdfListOpTypePrepended, value);
332                 }
333             }
334         }
335     }
336 
Append(const value_type & value)337     void Append(const value_type& value)
338     {
339         if (_Validate()) {
340             if (!_listEditor->IsOrderedOnly()) {
341                 if (_listEditor->IsExplicit()) {
342                     _Append(SdfListOpTypeExplicit, value);
343                 }
344                 else {
345                     GetDeletedItems().Remove(value);
346                     _Append(SdfListOpTypeAppended, value);
347                 }
348             }
349         }
350     }
351 
Remove(const value_type & value)352     void Remove(const value_type& value)
353     {
354         if (_Validate()) {
355             if (_listEditor->IsExplicit()) {
356                 GetExplicitItems().Remove(value);
357             }
358             else if (!_listEditor->IsOrderedOnly()) {
359                 GetAddedItems().Remove(value);
360                 GetPrependedItems().Remove(value);
361                 GetAppendedItems().Remove(value);
362                 _AddIfMissing(SdfListOpTypeDeleted, value);
363             }
364         }
365     }
366 
Erase(const value_type & value)367     void Erase(const value_type& value)
368     {
369         if (_Validate()) {
370             if (!_listEditor->IsOrderedOnly()) {
371                 if (_listEditor->IsExplicit()) {
372                     GetExplicitItems().Remove(value);
373                 }
374                 else {
375                     GetAddedItems().Remove(value);
376                     GetPrependedItems().Remove(value);
377                     GetAppendedItems().Remove(value);
378                 }
379             }
380         }
381     }
382 
383     /// Explicit bool conversion operator. A ListEditorProxy object
384     /// converts to \c true iff the list editor is valid, converts to \c false
385     /// otherwise.
386     explicit operator bool() const
387     {
388         return _listEditor && _listEditor->IsValid();
389     }
390 
391 private:
_Validate()392     bool _Validate()
393     {
394         if (!_listEditor) {
395             return false;
396         }
397 
398         if (IsExpired()) {
399             TF_CODING_ERROR("Accessing expired list editor");
400             return false;
401         }
402         return true;
403     }
404 
_Validate()405     bool _Validate() const
406     {
407         if (!_listEditor) {
408             return false;
409         }
410 
411         if (IsExpired()) {
412             TF_CODING_ERROR("Accessing expired list editor");
413             return false;
414         }
415         return true;
416     }
417 
_AddIfMissing(SdfListOpType op,const value_type & value)418     void _AddIfMissing(SdfListOpType op, const value_type& value)
419     {
420         ListProxy proxy(_listEditor, op);
421         size_t index = proxy.Find(value);
422         if (index == size_t(-1)) {
423             proxy.push_back(value);
424         }
425     }
426 
_AddOrReplace(SdfListOpType op,const value_type & value)427     void _AddOrReplace(SdfListOpType op, const value_type& value)
428     {
429         ListProxy proxy(_listEditor, op);
430         size_t index = proxy.Find(value);
431         if (index == size_t(-1)) {
432             proxy.push_back(value);
433         }
434         else if (value != static_cast<value_type>(proxy[index])) {
435             proxy[index] = value;
436         }
437     }
438 
_Prepend(SdfListOpType op,const value_type & value)439     void _Prepend(SdfListOpType op, const value_type& value)
440     {
441         ListProxy proxy(_listEditor, op);
442         size_t index = proxy.Find(value);
443         if (index != 0) {
444             if (index != size_t(-1)) {
445                 proxy.Erase(index);
446             }
447             proxy.insert(proxy.begin(), value);
448         }
449     }
450 
_Append(SdfListOpType op,const value_type & value)451     void _Append(SdfListOpType op, const value_type& value)
452     {
453         ListProxy proxy(_listEditor, op);
454         size_t index = proxy.Find(value);
455         if (proxy.empty() || (index != proxy.size()-1)) {
456             if (index != size_t(-1)) {
457                 proxy.Erase(index);
458             }
459             proxy.push_back(value);
460         }
461     }
462 
463 private:
464     boost::shared_ptr<Sdf_ListEditor<TypePolicy> > _listEditor;
465 
466     friend class Sdf_ListEditorProxyAccess;
467     template <class T> friend class SdfPyWrapListEditorProxy;
468 };
469 
470 // Cannot get from a VtValue except as the correct type.
471 template <class TP>
472 struct Vt_DefaultValueFactory<SdfListEditorProxy<TP> > {
473     static Vt_DefaultValueHolder Invoke() = delete;
474 };
475 
476 PXR_NAMESPACE_CLOSE_SCOPE
477 
478 #endif // PXR_USD_SDF_LIST_EDITOR_PROXY_H
479