1
2 """
3 Utilities and classes useful for user interfaces
4 """
5 __docformat__ = "restructuredtext"
6
7 from mrv.util import EventSender, Event, Call, WeakInstFunction
8 import maya.cmds as cmds
9 from mrv.enum import create as enum
10 import weakref
11
12
13
15 """Create a function calling inFunc with an edit or query flag set.
16
17 :note: only works on mrv wrapped ui elements
18 :note: THIS IS MOSTLY A DUPLICATION OF PROVEN CODE FROM ``mrv.maya.util`` !
19 :param flag: name of the query or edit flag
20 :param isEdit: If not False, the method returned will be an edit function
21 :param methodName: the name of the method returned, defaults to inCmd name"""
22
23 func = None
24 if isEdit:
25 def editFunc(self, val, **kwargs):
26 kwargs[ 'edit' ] = True
27 kwargs[ flag ] = val
28 return self.__melcmd__( self, **kwargs )
29
30 func = editFunc
31
32 else:
33 def queryFunc(self, **kwargs):
34 kwargs[ 'query' ] = True
35 kwargs[ flag ] = True
36 return self.__melcmd__( self, **kwargs )
37
38 func = queryFunc
39
40
41 if not methodName:
42 methodName = flag
43 func.__name__ = methodName
44
45 return func
46
47
49 """ Shorthand query version of makeEditOrQueryMethod """
50 return makeEditOrQueryMethod( flag, isEdit=False, methodName=methodName )
51
53 """ Shorthand edit version of makeEditOrQueryMethod """
54 return makeEditOrQueryMethod( flag, isEdit=True, methodName=methodName )
55
57 """ Shorthand for simple query and edit properties """
58 editFunc = editMethod( flag, methodName = methodName )
59 queryFunc = queryMethod( flag, methodName = methodName )
60 return property( queryFunc, editFunc )
61
62
63
64
66 """User interface signal which keeps assigned functions as strong reference to
67 assure we can always call it. This implies that we may leave many dangling objects
68 unless we are being properly cleaned up on deletion.
69
70 Calls generated from this event will not put the sender as first argument, but
71 you may retrieve it using ``self.sender()``."""
72
73 use_weakref = False
74 remove_on_error = False
75 sender_as_argument = False
76
77
78
80 """Allows registration of a typical UI callback
81 It basically streamlines the registration for a callback such that any
82 number of listeners can be called when an event occours - this works by
83 keeping an own list of callbacks registered for a specific event, and calling them
84 whenever the maya ui callback has been triggered
85
86 To use this class , see the documentation of ``EventSender``, but use the Event
87 instead.
88 If you want to add your own events, use your own ``Signal`` s.
89
90 The class does NOT use weakreferences for the main callbacks to make it easier to use.
91 Use the ``WeakFunction`` to properly and weakly bind an instance function
92
93 When registered for an event, the sender will be provided to each callback as first
94 argument.
95 """
96
97
98
99 sender_as_argument = True
100 reraise_on_error =True
101
102
103
105 """Event suitable to deal with user interface callback"""
106
107 use_weakref = False
108 remove_on_error = True
109
110
111 - def __init__( self, eventname, **kwargs ):
117
118 - def send( self, inst, *args, **kwargs ):
122
123 - def __set__( self, inst, eventfunc ):
141
142
143
145 """A ui container is a base for all classes that can have child controls or
146 other containers.
147
148 This class is just supposed to keep references to it's children so that additional
149 information stored in python will not get lost
150
151 Child-Instances are always unique, thus adding them several times will not
152 keep them several times , but just once"""
153
154
158
160 """
161 :return: the child with the given name, see `childByName`
162 :param key: if integer, will return the given list index, if string, the child
163 matching the id"""
164 if isinstance( key, basestring ):
165 return self.childByName( key )
166 else:
167 return self._children[ key ]
168
171
172 - def __exit__(self, type, value, traceback):
175
176
177 - def add( self, child, set_self_active = False, revert_to_previous_parent = True ):
178 """Add the given child UI item to our list of children
179
180 :param set_self_active: if True, we explicitly make ourselves the current parent
181 for newly created UI elements
182 :param revert_to_previous_parent: if True, the previous parent will be restored
183 once we are done, if False we keep the parent - only effective if set_self_active is True
184 :return: the newly added child, allowing contructs like
185 button = layout.addChild( Button( ) )"""
186 if child in self._children:
187 return child
188
189 prevparent = None
190 if set_self_active:
191 prevparent = self.activeParent()
192 self.setActive( )
193
194
195 self._children.append( child )
196
197 if revert_to_previous_parent and prevparent:
198 prevparent.setActive()
199
200 return child
201
203 """Remove the given child from our list
204
205 :return: True if the child was found and has been removed, False otherwise"""
206 try:
207 self._children.remove( child )
208 return True
209 except ValueError:
210 return False
211
213 """Delete the given child ui physically so it will not be shown anymore
214 after removing it from our list of children"""
215 if self.removeChild( child ):
216 child.delete()
217
219 """
220 :return: list with our child instances
221 :param predicate: function returning True for each child to include in result,
222 allows to easily filter children
223 :note: it's a copy, so you can freely act on the list
224 :note: children will be returned in the order in which they have been added"""
225 return [ c for c in self._children if predicate( c ) ]
226
228 """
229 :return: stored child instance, specified either as short name ( without pipes )
230 or fully qualified ( i.e. mychild or parent|subparent|mychild" )
231 :raise KeyError: if a child with that name does not exist"""
232 if "|" in childname:
233 for child in self._children:
234 if child == childname:
235 return child
236
237
238
239 childname = childname.split( '|' )[-1]
240 for child in self._children:
241 if child.basename() == childname:
242 return child
243
244
245 raise KeyError( "Child named %s could not be found below %s" % ( childname, self ) )
246
248 """Set this container active, such that newly created items will be children
249 of this layout
250
251 :return: self
252 :note: always use the addChild function to add the children !"""
253 cmds.setParent( self )
254 return self
255
257 """Clear our child arrays to quickly forget about our children"""
258 self._children = list()
259
260
262 """Interface allowing to dynamically add, update and remove items to a layout
263 to match a given input set of item ids.
264 Its abstacted to be implemented by subclasses"""
265
266
267 eSetItemCBID = enum( "preCreate", "preUpdate", "preRemove", "postCreate", "postUpdate", "postRemove" )
268
269
270 - def setItems( self, item_ids, **kwargs ):
271 """Set the UI to display items identified by the given item_ids
272
273 :param item_ids: ids behaving appropriately if put into a set
274 :param kwargs: passed on to the handler methods ( which are implemented by the subclass ).
275 Use these to pass on additional data that you might want to use to keep additional information about
276 your item ids
277 :note: you are responsible for generating a list of item_ids and call this
278 method to trigger the update
279 :return: tuple( SetOfDeletedItemIds, SetOfCreatedItemIds ) """
280 existing_items = set( self.currentItemIds( **kwargs ) )
281 todo_items = set( item_ids )
282
283 items_to_create = todo_items - existing_items
284 items_to_remove = existing_items - todo_items
285
286
287
288 if items_to_remove:
289 self.handleEvent( self.eSetItemCBID.preRemove, **kwargs )
290 for item in items_to_remove:
291 self.removeItem( item, **kwargs )
292 self.handleEvent( self.eSetItemCBID.postRemove, **kwargs )
293
294
295
296
297 if items_to_create:
298 self.handleEvent( self.eSetItemCBID.preCreate, **kwargs )
299 for item in items_to_create:
300 result = self.createItem( item, **kwargs )
301
302 if result is None:
303 todo_items.remove( item )
304
305 self.handleEvent( self.eSetItemCBID.postCreate, **kwargs )
306
307
308
309
310 if todo_items:
311 self.handleEvent( self.eSetItemCBID.preUpdate, **kwargs )
312 for item in todo_items:
313 self.updateItem( item, **kwargs )
314 self.handleEvent( self.eSetItemCBID.postUpdate, **kwargs )
315
316
317 return ( items_to_remove, items_to_create )
318
319
320
321
322
324 """
325 :return: list of item ids that are currently available in your layout.
326 They will be passed around to the `createItem`, `updateItem` and`removeItem`
327 methods and is the foundation of the `setItems` method. Ids returned here
328 must be compatible to the ids passed in to `setItems`"""
329 raise NotImplementedError( "To be implemented by subClass" )
330
332 """Called whenever a block of items is being handled for an operation identified
333 by eventid, allowing you to prepare for such a block or finish it
334
335 :param eventid: eSetItemCBID identifying the event to handle"""
336 pass
337
339 """Create an item identified by the given itemid and add it to your layout
340
341 :return: created item or None to indicate error. On error, the item will not
342 be updated anymore"""
343 raise NotImplementedError( "To be implemented by subClass" )
344
346 """Update the item identified by the given itemid so that it represents the
347 current state of the application"""
348 raise NotImplementedError( "To be implemented by subClass" )
349
351 """Remove the given item identified by itemid so it does not show up in this
352 layout anymore
353
354 :note: its up to you how you remove the item, as long as it is not visible anymore"""
355 raise NotImplementedError( "To be implemented by subClass" )
356
357
358