Package mrv :: Package maya :: Package ui :: Module util
[hide private]
[frames] | no frames]

Source Code for Module mrv.maya.ui.util

  1  # -*- coding: utf-8 -*- 
  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  #{ MEL Function Wrappers 
 13   
14 -def makeEditOrQueryMethod( flag, isEdit=False, methodName=None ):
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 # END if edit 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 # END if query 40 41 if not methodName: 42 methodName = flag 43 func.__name__ = methodName 44 45 return func 46 47
48 -def queryMethod( flag, methodName = None ):
49 """ Shorthand query version of makeEditOrQueryMethod """ 50 return makeEditOrQueryMethod( flag, isEdit=False, methodName=methodName )
51
52 -def editMethod( flag, methodName = None ):
53 """ Shorthand edit version of makeEditOrQueryMethod """ 54 return makeEditOrQueryMethod( flag, isEdit=True, methodName=methodName )
55
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 )
61 62 #} 63 64
65 -class Signal( Event ):
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 #{ Configuration 73 use_weakref = False 74 remove_on_error = False 75 sender_as_argument = False
76 #} END configuration 77 78
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 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 #( Configuration 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 101 #} END configuration 102 103
104 - class _UIEvent( Event ):
105 """Event suitable to deal with user interface callback""" 106 #( Configuration 107 use_weakref = False 108 remove_on_error = True 109 #) END configuration 110
111 - def __init__( self, eventname, **kwargs ):
112 """Allows to set additional arguments to be given when a callback 113 is actually set""" 114 super( EventSenderUI._UIEvent, self ).__init__( **kwargs ) 115 self._name =eventname 116 self._kwargs = kwargs
117
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)
122
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 ) 126 127 # REGISTER TO MEL IF THIS IS THE FIRST EVENT 128 # do we have to register the callback ? 129 if not eventset: 130 kwargs = dict() 131 # generic call that will receive maya's own arguments and pass them on 132 dyncall = lambda *args, **kwargs: self.send(inst, *args, **kwargs) 133 134 kwargs[ 'e' ] = 1 135 kwargs[ self._name ] = dyncall 136 kwargs.update( self._kwargs ) # allow user kwargs 137 inst.__melcmd__( str( inst ) , **kwargs ) 138 # END create event 139 140 super( EventSenderUI._UIEvent, self ).__set__( inst, eventfunc )
141 # END _UIEvent 142 143
144 -class UIContainerBase( object ):
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
155 - def __init__( self, *args, **kwargs ):
156 self._children = list() 157 super( UIContainerBase, self ).__init__( *args, **kwargs )
158
159 - def __getitem__( self, key ):
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
169 - def __enter__(self):
170 return self
171
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 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 # END set active handling 194 195 self._children.append( child ) 196 197 if revert_to_previous_parent and prevparent: 198 prevparent.setActive() 199 200 return child
201
202 - def removeChild( self, child ):
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
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 ): 216 child.delete()
217
218 - def listChildren( self, predicate = lambda c: True ):
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
227 - def childByName( self, childname ):
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 # END for each chld 237 # END fqn handling 238 239 childname = childname.split( '|' )[-1] # |hello|world -> world 240 for child in self._children: 241 if child.basename() == childname: 242 return child 243 # END non- fqn handling 244 245 raise KeyError( "Child named %s could not be found below %s" % ( childname, self ) )
246
247 - def setActive( self ):
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
256 - def clearChildren( self ):
257 """Clear our child arrays to quickly forget about our children""" 258 self._children = list()
259 260
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""" 265 266 # identify the type of event to handle as called during setItems 267 eSetItemCBID = enum( "preCreate", "preUpdate", "preRemove", "postCreate", "postUpdate", "postRemove" ) 268 269 #{ Interface
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 # REMOVE OBSOLETE 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 # END if there are items to remove 294 295 # CREATE NEW 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 # something didnt work, assure we do not proceed with this one 302 if result is None: 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 307 308 # UPDATE EXISTING 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 # END if there are items to update 316 317 return ( items_to_remove, items_to_create )
318 319 #} END interace 320 321 #{ SubClass Implementation 322
323 - def currentItemIds( self, **kwargs ):
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
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 334 335 :param eventid: eSetItemCBID identifying the event to handle""" 336 pass
337
338 - def createItem( self, itemid, **kwargs ):
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
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" )
349
350 - def removeItem( self, itemid, **kwargs ):
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 #} END subclass implementation 358