3
Utilities and classes useful for user interfaces
5
__docformat__ = "restructuredtext"
7
from mrv.util import EventSender, Event, Call, WeakInstFunction
8
import maya.cmds as cmds
9
from mrv.enum import create as enum
12
#{ MEL Function Wrappers
14
def makeEditOrQueryMethod( flag, isEdit=False, methodName=None ):
15
"""Create a function calling inFunc with an edit or query flag set.
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"""
25
def editFunc(self, val, **kwargs):
26
kwargs[ 'edit' ] = True
28
return self.__melcmd__( self, **kwargs )
33
def queryFunc(self, **kwargs):
34
kwargs[ 'query' ] = True
36
return self.__melcmd__( self, **kwargs )
43
func.__name__ = methodName
48
def queryMethod( flag, methodName = None ):
49
""" Shorthand query version of makeEditOrQueryMethod """
50
return makeEditOrQueryMethod( flag, isEdit=False, methodName=methodName )
52
def editMethod( flag, methodName = None ):
53
""" Shorthand edit version of makeEditOrQueryMethod """
54
return makeEditOrQueryMethod( flag, isEdit=True, methodName=methodName )
56
def propertyQE( flag, methodName = None ):
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 )
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.
70
Calls generated from this event will not put the sender as first argument, but
71
you may retrieve it using ``self.sender()``."""
74
remove_on_error = False
75
sender_as_argument = False
79
class EventSenderUI( EventSender ):
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
86
To use this class , see the documentation of ``EventSender``, but use the Event
88
If you want to add your own events, use your own ``Signal`` s.
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
93
When registered for an event, the sender will be provided to each callback as first
97
# we are to be put as first arguemnt, allowing listeners to do something
98
# with the sender when handling the event
99
sender_as_argument = True
100
reraise_on_error =True
104
class _UIEvent( Event ):
105
"""Event suitable to deal with user interface callback"""
108
remove_on_error = True
111
def __init__( self, eventname, **kwargs ):
112
"""Allows to set additional arguments to be given when a callback
114
super( EventSenderUI._UIEvent, self ).__init__( **kwargs )
115
self._name =eventname
116
self._kwargs = kwargs
118
def send( self, inst, *args, **kwargs ):
119
"""Sets our instance prior to calling the super class"""
120
self._last_inst_ref = weakref.ref(inst)
121
super(EventSenderUI._UIEvent, self).send(*args, **kwargs)
123
def __set__( self, inst, eventfunc ):
124
"""Set the given event to be called when this event is being triggered"""
125
eventset = self._getFunctionSet( inst )
127
# REGISTER TO MEL IF THIS IS THE FIRST EVENT
128
# do we have to register the callback ?
131
# generic call that will receive maya's own arguments and pass them on
132
dyncall = lambda *args, **kwargs: self.send(inst, *args, **kwargs)
135
kwargs[ self._name ] = dyncall
136
kwargs.update( self._kwargs ) # allow user kwargs
137
inst.__melcmd__( str( inst ) , **kwargs )
140
super( EventSenderUI._UIEvent, self ).__set__( inst, eventfunc )
144
class UIContainerBase( object ):
145
"""A ui container is a base for all classes that can have child controls or
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
151
Child-Instances are always unique, thus adding them several times will not
152
keep them several times , but just once"""
155
def __init__( self, *args, **kwargs ):
156
self._children = list()
157
super( UIContainerBase, self ).__init__( *args, **kwargs )
159
def __getitem__( self, key ):
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
164
if isinstance( key, basestring ):
165
return self.childByName( key )
167
return self._children[ key ]
172
def __exit__(self, type, value, traceback):
173
if type is None and hasattr(self, 'setParentActive'):
174
self.setParentActive()
175
# END only undo parenting if there is no exception
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
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:
191
prevparent = self.activeParent()
193
# END set active handling
195
self._children.append( child )
197
if revert_to_previous_parent and prevparent:
198
prevparent.setActive()
202
def removeChild( self, child ):
203
"""Remove the given child from our list
205
:return: True if the child was found and has been removed, False otherwise"""
207
self._children.remove( child )
212
def deleteChild( self, child ):
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 ):
218
def listChildren( self, predicate = lambda c: True ):
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 ) ]
227
def childByName( self, childname ):
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"""
233
for child in self._children:
234
if child == childname:
239
childname = childname.split( '|' )[-1] # |hello|world -> world
240
for child in self._children:
241
if child.basename() == childname:
243
# END non- fqn handling
245
raise KeyError( "Child named %s could not be found below %s" % ( childname, self ) )
247
def setActive( self ):
248
"""Set this container active, such that newly created items will be children
252
:note: always use the addChild function to add the children !"""
253
cmds.setParent( self )
256
def clearChildren( self ):
257
"""Clear our child arrays to quickly forget about our children"""
258
self._children = list()
261
class iItemSet( object ):
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"""
266
# identify the type of event to handle as called during setItems
267
eSetItemCBID = enum( "preCreate", "preUpdate", "preRemove", "postCreate", "postUpdate", "postRemove" )
270
def setItems( self, item_ids, **kwargs ):
271
"""Set the UI to display items identified by the given item_ids
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
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 )
283
items_to_create = todo_items - existing_items
284
items_to_remove = existing_items - todo_items
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
# END if there are items to remove
298
self.handleEvent( self.eSetItemCBID.preCreate, **kwargs )
299
for item in items_to_create:
300
result = self.createItem( item, **kwargs )
301
# something didnt work, assure we do not proceed with this one
303
todo_items.remove( item )
304
# END for each item to create
305
self.handleEvent( self.eSetItemCBID.postCreate, **kwargs )
306
# END if there are items to create
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
# END if there are items to update
317
return ( items_to_remove, items_to_create )
321
#{ SubClass Implementation
323
def currentItemIds( self, **kwargs ):
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" )
331
def handleEvent( self, eventid, **kwargs ):
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
335
:param eventid: eSetItemCBID identifying the event to handle"""
338
def createItem( self, itemid, **kwargs ):
339
"""Create an item identified by the given itemid and add it to your layout
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" )
345
def updateItem( self, itemid, **kwargs ):
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" )
350
def removeItem( self, itemid, **kwargs ):
351
"""Remove the given item identified by itemid so it does not show up in this
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" )
357
#} END subclass implementation