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

Source Code for Module mrv.maya.ui.base

  1  # -*- coding: utf-8 -*- 
  2  """ 
  3  Contains some basic  classes that are required to run the UI system 
  4  """ 
  5  __docformat__ = "restructuredtext" 
  6  import maya.cmds as cmds 
  7  from mrv.util import capitalize 
  8  from mrv.maya.util import noneToList 
  9  from mrv.interface import iDagItem 
 10  from util import EventSenderUI 
 11  import util as uiutil 
 12  from mrv.exc import MRVError 
 13  import typ 
 14  _uidict = None                  # set during initialization 
15 16 ############################ 17 #### Methods #### 18 ########################## 19 20 -def getUIType( uiname ):
21 """ 22 :return: uitype string having a corresponding mel command - some types returned do not correspond 23 to the actual name of the command used to manipulate the type """ 24 uitype = cmds.objectTypeUI( uiname ) 25 return typ._typemap.get( uitype, uitype )
26
27 28 -def wrapUI( uinameOrList, ignore_errors = False ):
29 """ 30 :return: a new instance ( or list of instances ) of a suitable python UI wrapper class for the 31 UI with the given uiname(s) 32 :param uinameOrList: if single name, a single instance will be returned, if a list of names is given, 33 a list of respective instances. None will be interpreted as empty list 34 :param ignore_errors: ignore ui items that cannot be wrapped as the type is unknown. 35 :raise RuntimeError: if uiname does not exist or is not wrapped in python """ 36 uinames = uinameOrList 37 islisttype = isinstance( uinameOrList, ( tuple, list, set ) ) 38 if not islisttype: 39 if uinameOrList is None: 40 islisttype = True 41 uinames = list() 42 else: 43 uinames = [ uinameOrList ] 44 # END input list handling 45 out = list() 46 for uiname in uinames: 47 uitype = getUIType( uiname ) 48 clsname = capitalize( uitype ) 49 50 try: 51 out.append( _uidict[clsname]( name=uiname, wrap_only = 1 ) ) 52 except KeyError: 53 if not ignore_errors: 54 raise RuntimeError( "ui module has no class named %s, failed to wrap %s" % ( clsname, uiname ) ) 55 # END for each uiname 56 57 if islisttype: 58 return out 59 60 return out[0]
61 62 # alias, allowing new items to be easily wrapped 63 UI = wrapUI
64 65 -def uiExists(uiname):
66 """:return: True if the user interface element with the given name exists""" 67 try: 68 wrapUI(uiname) 69 return True 70 except RuntimeError: 71 return False
72 # END exception handling
73 74 -def lsUI( **kwargs ):
75 """ List UI elements as python wrapped types 76 77 :param kwargs: flags from the respective maya command are valid 78 If no special type keyword is specified, all item types will be returned 79 :return: list of NamedUI instances of respective UI elements """ 80 long = kwargs.pop( 'long', kwargs.pop( 'l', True ) ) 81 head = kwargs.pop( 'head', kwargs.pop( 'hd', None ) ) 82 tail = kwargs.pop( 'tail', kwargs.pop( 'tl', None) ) 83 84 if not kwargs: 85 kwargs = { 86 'windows': 1, 'panels' : 1, 'editors' : 1, 'controls' : 1, 87 'controlLayouts' : 1,'collection' : 1, 'radioMenuItemCollections' : 1, 88 'menus' : 1, 'menuItems' : 1, 'contexts' : 1, 'cmdTemplates' : 1 } 89 # END kwargs handling 90 91 kwargs['long'] = long 92 if head is not None: kwargs['head'] = head 93 if tail is not None: kwargs['tail'] = tail 94 95 # NOTE: controls and controlLayout will remove duplcate entries - we have to 96 # prune them ! Unfortunately, you need both flags to get all items, even layouts 97 # NOTE: have to ignore errors as there are still plenty of items that we cannot 98 # wrap 99 return wrapUI( set( cmds.lsUI( **kwargs ) ), ignore_errors = True )
100
101 102 ############################ 103 #### Classes #### 104 ########################## 105 106 -class BaseUI( object ):
107 108 __melcmd__ = None # every class deriving directly from it must define this ! 109
110 - def __init__( self, *args, **kwargs ):
111 if self.__class__ == BaseUI: 112 raise MRVError( "Cannot instantiate" + self.__class__.__name__ + " directly - it can only be a base class" ) 113 114 super(BaseUI, self).__init__(*args, **kwargs)
115
116 117 -class NamedUI( unicode, BaseUI , iDagItem, EventSenderUI ):
118 """Implements a simple UI element having a name and most common methods one 119 can apply to it. Derived classes should override these if they can deliver a 120 faster implementation. 121 If the 'name' keyword is supplied, an existing UI element will be wrapped 122 123 **Events** 124 125 As subclass of EventSenderUI, it can provide events that are automatically 126 added by the metaclass as described by the _events_ attribute list. 127 This allows any number of clients to register for one maya event. Derived classes 128 may also use their own events which is useful if you create components 129 130 Register for an event like: 131 132 >>> uiinstance.e_eventlongname = yourFunction( sender, *args, **kwargs ) 133 >>> *args and **kwargs are determined by maya 134 135 :note: although many access methods look quite 'repeated' as they are quite 136 similar except for a changing flag, they are hand-written to provide proper docs for them""" 137 __metaclass__ = typ.MetaClassCreatorUI 138 139 #( Configurtation 140 _sep = "|" # separator for ui elements in their name, same as for dag paths 141 _is_menu = False # if True, some methods will handle special cases for menus 142 #) end configuration 143 144 #{ Overridden Methods 145 @classmethod
146 - def _exists( cls, uiname ):
147 """ 148 :return: 1 if the given UI element exists, 0 if it does not exist 149 and 2 it exists but the passed in name does not guarantee there are not more 150 objects with the same name""" 151 try: 152 uitype = cmds.objectTypeUI( uiname ) 153 except RuntimeError: 154 return 0 155 else: 156 # short names can only be used with top level items like 157 # windows - for everything else we cannot know how many items 158 # with the same name exist and which one we should actually wrap 159 # Claim it does not exist 160 if "Window" not in uitype and cls._sep not in uiname: 161 return 2 162 return 1
163
164 - def __new__( cls, *args, **kwargs ):
165 """If name is given, the newly created UI will wrap the UI with the given name. 166 Otherwise the UIelement will be created 167 168 :param kwargs: 169 170 * name: 171 name of the user interface to wrap or the target name of a new elf element. 172 Valid names for creation are short names ( without a | in it's path ), valid names 173 for wrapping are short and preferably long names. 174 175 * wrap_only: 176 if True, default False, a wrap will be done even if the passed 177 in name uses the short form ( for non-window elements ). If it exists, one cannot be sure 178 whether more elements with the given name exist. If False, the system will create a new 179 element of our type. 180 181 * force_creation: 182 if True, default False, a new item will be created 183 even if an item with the given name uniquely exists. This might be necessary that 184 you wish to create the given named item under the current parent, although an item 185 with that name might already exist below another parent. This is required if 186 you have a short name only 187 188 :note: you can use args safely for your own purposes 189 :note: if name is set but does not name a valid user interface, a new one 190 will be created, and passed to the constructor""" 191 name = kwargs.pop( "name", None ) 192 exists = (( name is not None) and NamedUI._exists(str(name))) or False 193 force_creation = kwargs.pop( "force_creation", False ) 194 # never try to create names with '|' in them, wrap only in that case 195 # This also works around a maya 2011 bug that causes objectTypeUI fail on 196 # menuItems 197 has_valid_name = True 198 if name and ('|' in name): 199 has_valid_name = False 200 201 202 # pretend named element does not exist if existance is ambigous 203 if not kwargs.pop( "wrap_only", False ) and exists == 2: 204 exists = 0 205 206 # END verify name 207 created = False 208 if has_valid_name and (name is None or not exists or force_creation): 209 try: 210 if name: # use name to create named object 211 name = cls.__melcmd__( name, **kwargs ) 212 213 # assure we have a long name - mel sometimes returns short ones 214 # that are ambigous ... 215 if cls._sep not in name and Window not in cls.mro(): 216 raise AssertionError( "%s instance named '%s' does not have a long name after creation" % ( cls, name ) ) 217 else: 218 name = cls.__melcmd__( **kwargs ) 219 # END create item 220 created = True 221 except (RuntimeError,TypeError), e: 222 raise RuntimeError( "Creation of %s using melcmd %s failed: %s" % ( cls, cls.__melcmd__, str( e ) ) ) 223 # END name handling 224 # END auto-creation as required 225 226 inst = unicode.__new__( cls, name ) 227 228 # UI DELETED HANDLING 229 # check for ui deleted override on subclass. If so, we initialize a run-once event 230 # to get notification. We use cmds for this as we can spare the callbackID handling 231 # in that case ( run-once is not available in the API ) 232 if created and cls.uiDeleted != NamedUI.uiDeleted: 233 cmds.scriptJob(uiDeleted=(name, inst.uiDeleted), runOnce=1) 234 # END register ui deleted event 235 236 return inst
237
238 - def __repr__( self ):
239 return u"%s('%s')" % ( self.__class__.__name__, self )
240
241 - def __setattr__( self, attr, value ):
242 """Prevent properties or events that do not exist to be used by anyone, 243 everything else is allowed though""" 244 if ( attr.startswith( "p_" ) or attr.startswith( "e_" ) ): 245 try: 246 getattr( self, attr ) 247 except AttributeError: 248 raise AttributeError( "Cannot create per-instance properties or events: %s.%s ( did you misspell an existing one ? )" % ( self, attr ) ) 249 except Exception: 250 # if there was another exception , then the attribute is at least valid and MEL did not want to 251 # accept the querying of it 252 pass 253 # END exception handling 254 # END check attribute validity 255 return super( NamedUI, self ).__setattr__( attr, value )
256
257 - def __init__( self , *args, **kwargs ):
258 """ Initialize instance and check arguments """ 259 # assure that new instances are being created initially 260 forbiddenKeys = [ 'edit', 'e' , 'query', 'q' ] 261 for fkey in forbiddenKeys: 262 if fkey in kwargs: 263 raise ValueError( "Edit or query flags are not permitted during initialization as interfaces must be created onclass instantiation" ) 264 # END if key found in kwargs 265 # END for each forbidden key 266 267 super( NamedUI, self ).__init__( *args, **kwargs )
268 #} END overridden methods 269
270 - def children( self, **kwargs ):
271 """:return: all intermediate child instances 272 :note: the order of children is lexically ordered at this time 273 :note: this implementation is slow and should be overridden by more specialized subclasses""" 274 return filter( lambda x: len( x.replace( self , '' ).split('|') ) - 1 ==len( self.split( '|' ) ), self.childrenDeep() )
275
276 - def childrenDeep( self, **kwargs ):
277 """:return: all child instances recursively 278 :note: the order of children is lexically ordered at this time 279 :note: this implementation is slow and should be overridden by more specialized subclasses""" 280 kwargs['long'] = True 281 return filter( lambda x: x.startswith(self) and not x == self, lsUI(**kwargs))
282
283 - def _parentString(self):
284 """:return: string of the parent, without a wrap 285 :note: this helps mainly as a workaround for a maya 2011 issues, causing 286 objectTypeUI not to work on many items""" 287 return '|'.join(self.split('|')[:-1])
288
289 - def parent( self ):
290 """:return: parent instance of this ui element""" 291 return wrapUI(self._parentString())
292 293 #{ Hierachy Handling 294 295 @classmethod
296 - def activeParent( cls ):
297 """:return: NameUI of the currently set parent 298 :raise RuntimeError: if no active parent was set""" 299 # MENU 300 wrapuiname = None 301 if cls._is_menu: 302 wrapuiname = cmds.setParent( q=1, m=1 ) 303 else: 304 # NON-MENU 305 wrapuiname = cmds.setParent( q=1 ) 306 307 if not wrapuiname or wrapuiname == "NONE": 308 raise RuntimeError( "No current parent set" ) 309 310 return wrapUI( wrapuiname )
311 312 #} END hierarchy handling 313
314 - def uiDeleted(self):
315 """If overridden in subclass, it will be called once the UI gets deleted 316 within maya ( i.e. the user closed the window )eee 317 The base implementation assures that all event-receivers that are bound to 318 your events will be freed, allowing them to possibly be destroyed as well. 319 320 Use this callback to register yourself from all your event senders, then call 321 the base class method. 322 323 :note: This is not related to the __del__ method of your object. Its worth 324 noting that your instance will be strongly bound to a maya event, hence 325 your instance will exist as long as your user interface element exists 326 within maya.""" 327 self.clearAllEvents()
328
329 - def type( self ):
330 """:return: the python class able to create this class 331 :note: The return value is NOT the type string, but a class """ 332 uitype = getUIType( self ) 333 return getattr( ui, capitalize( uitype ) )
334
335 - def shortName( self ):
336 """:return: shortname of the ui ( name without pipes )""" 337 return self.split('|')[-1]
338
339 - def delete( self ):
340 """Delete this UI - the wrapper instance must not be used after this call""" 341 cmds.deleteUI( self )
342
343 - def exists( self ):
344 """:return: True if this instance still exists in maya""" 345 try: 346 return self.__melcmd__( self, ex=1 ) 347 except RuntimeError: 348 # although it should just return False if it does NOT exist, it raises 349 return False
350 351 #{ Properties 352 p_exists = property(exists) 353 p_ex = p_exists
354 #} END properties
355 356 357 -class SizedControl( NamedUI ):
358 """Base Class for all controls having a dimension""" 359 __metaclass__ = typ.MetaClassCreatorUI 360 _properties_ = ( "dt", "defineTemplate", 361 "ut", "useTemplate", 362 "w","width", 363 "h", "height", 364 "vis", "visible", 365 "m", "manage", 366 "en", "enable", 367 "io", "isObscured", 368 "npm", "numberOfPopupMenus", 369 "po", "preventOverride", 370 "bgc", "backgroundColor", 371 "dtg", "doctTag", 372 # maya >=2011) 373 "fpn", "fullPathName", 374 "ebg", "enableBackground" 375 ) 376 377 _events_ = ( "dgc", "dragCallback" , 378 "dpc", "dropCallback" , 379 # maya >= 2011 380 "vcc", "visibleChangeCommand") 381 382 #{ Query Methods 383
384 - def annotation( self ):
385 """:return : the annotation string """ 386 try: 387 return self.__melcmd__( self, q=1, ann=1 ) 388 except TypeError: 389 return ""
390
391 - def dimension( self ):
392 """:return: (x,y) tuple of x and y dimensions of the UI element""" 393 return ( self.__melcmd__( self, q=1, w=1 ), self.__melcmd__( self, q=1, h=1 ) )
394
395 - def popupMenuArray( self ):
396 """:return: popup menus attached to this control""" 397 return wrapUI( self.__melcmd__( self, q=1, pma=1 ) )
398 399 #}END query methods 400 401 #{ Edit Methods 402
403 - def setAnnotation( self, ann ):
404 """Set the UI element's annotation 405 :note: not all named UI elements can have their annotation set""" 406 self.__melcmd__( self, e=1, ann=ann )
407
408 - def setDimension( self, dimension ):
409 """Set the UI elements dimension 410 :param dimension: (x,y) : tuple holding desired x and y dimension""" 411 self.__melcmd__( self, e=1, w=dimension[0] ) 412 self.__melcmd__( self, e=1, h=dimension[1] )
413
414 - def setFocus(self):
415 """Set the global keyboard focus to this control""" 416 cmds.setFocus(self)
417 418 #}END edit methods 419 420 p_annotation = property( annotation, setAnnotation ) 421 p_ann = p_annotation 422 p_dimension = property( dimension, setDimension ) 423 p_pma = property( popupMenuArray ) 424 p_popupMenuArray = property( popupMenuArray )
425
426 427 428 -class Window( SizedControl, uiutil.UIContainerBase ):
429 """Simple Window Wrapper 430 431 :note: Window does not support some of the properties provided by sizedControl""" 432 __metaclass__ = typ.MetaClassCreatorUI 433 _properties_ = ( "t", "title", 434 "i", "iconify", 435 "s", "sizeable", 436 "wh", "widthHeight" 437 "in", "iconName", 438 "tb","titleBar", 439 "mnb", "minimizeButton", 440 "mxb", "maximizeButton", 441 "tlb", "toolbox", 442 "tbm", "titleBarMenu", 443 "mbv", "menuBarVisible", 444 "tlc", "topLeftCorner", 445 "te", "topEdge", 446 "tl", "leftEdge", 447 "mw", "mainWindow", 448 "rtf", "resizeToFitChildren", 449 "dt", "docTag" ) 450 451 _events_ = ( "rc", "restoreCommand", "mnc", "minimizeCommand" ) 452 453 454 #{ Window Specific Methods 455
456 - def show( self ):
457 """ Show Window 458 :return: self""" 459 cmds.showWindow( self ) 460 return self
461
462 - def numberOfMenus( self ):
463 """:return: number of menus in the menu array""" 464 return int( self.__melcmd__( self, q=1, numberOfMenus=1 ) )
465
466 - def menuArray( self ):
467 """:return: Menu instances attached to this window""" 468 return wrapUI( self.__melcmd__( self, q=1, menuArray=1 ) )
469
470 - def isFrontWindow( self ):
471 """:return: True if we are the front window """ 472 return bool( self.__melcmd__( self, q=1, frontWindow=1 ) )
473
474 - def setMenuIndex( self, menu, index ):
475 """Set the menu index of the specified menu 476 477 :param menu: name of child menu to set 478 :param index: new index at which the menu should appear""" 479 return self.__melcmd__( self, e=1, menuIndex=( menu, index ) )
480 481 #} END window speciic 482 483 p_numberOfMenus = property( numberOfMenus ) 484 p_nm = p_numberOfMenus
485 506
507 508 -class ContainerMenuBase( uiutil.UIContainerBase ):
509 """Implements the container abilities of all menu types""" 510
511 - def setActive( self ):
512 """Make ourselves the active menu 513 514 :return: self""" 515 cmds.setParent( self, m=1 ) 516 return self
517
518 - def setParentActive( self ):
519 """Make our parent the active menu layout 520 521 :return: self 522 :note: only useful self is a submenu""" 523 cmds.setParent( ".." , m=1 ) 524 return self
525 541
542 543 -class PopupMenu(NamedUI, ContainerMenuBase):
544 _properties_ = ( 545 "alt", "altModifier", 546 "ctl", "ctrlModifier", 547 "sh", "shiftModifier", 548 "b", "button", 549 "aob", "allowOptionBoxes", 550 "mm", "markingMenu", 551 "ni", "numberOfItems", 552 "ia", "itemArray", 553 "dai", "deleteAllItems" 554 ) 555 556 _events_ = ( 557 "pmc", "postMenuCommand", 558 "pmo", "postMenuCommandOnce", 559 )
560 601 619 620 621 # type is returned in some cases by objectTypeUI 622 CommandMenuItem = MenuItem 623