3
Contains some basic classes that are required to run the UI system
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
12
from mrv.exc import MRVError
14
_uidict = None # set during initialization
16
############################
18
##########################
20
def getUIType( uiname ):
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 )
28
def wrapUI( uinameOrList, ignore_errors = False ):
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 ) )
39
if uinameOrList is None:
43
uinames = [ uinameOrList ]
44
# END input list handling
46
for uiname in uinames:
47
uitype = getUIType( uiname )
48
clsname = capitalize( uitype )
51
out.append( _uidict[clsname]( name=uiname, wrap_only = 1 ) )
54
raise RuntimeError( "ui module has no class named %s, failed to wrap %s" % ( clsname, uiname ) )
62
# alias, allowing new items to be easily wrapped
66
""":return: True if the user interface element with the given name exists"""
72
# END exception handling
75
""" List UI elements as python wrapped types
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) )
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 }
92
if head is not None: kwargs['head'] = head
93
if tail is not None: kwargs['tail'] = tail
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
99
return wrapUI( set( cmds.lsUI( **kwargs ) ), ignore_errors = True )
102
############################
104
##########################
106
class BaseUI( object ):
108
__melcmd__ = None # every class deriving directly from it must define this !
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" )
114
super(BaseUI, self).__init__(*args, **kwargs)
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
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
130
Register for an event like:
132
>>> uiinstance.e_eventlongname = yourFunction( sender, *args, **kwargs )
133
>>> *args and **kwargs are determined by maya
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
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
144
#{ Overridden Methods
146
def _exists( cls, uiname ):
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"""
152
uitype = cmds.objectTypeUI( uiname )
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:
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
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.
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
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
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
197
has_valid_name = True
198
if name and ('|' in name):
199
has_valid_name = False
202
# pretend named element does not exist if existance is ambigous
203
if not kwargs.pop( "wrap_only", False ) and exists == 2:
208
if has_valid_name and (name is None or not exists or force_creation):
210
if name: # use name to create named object
211
name = cls.__melcmd__( name, **kwargs )
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 ) )
218
name = cls.__melcmd__( **kwargs )
221
except (RuntimeError,TypeError), e:
222
raise RuntimeError( "Creation of %s using melcmd %s failed: %s" % ( cls, cls.__melcmd__, str( e ) ) )
224
# END auto-creation as required
226
inst = unicode.__new__( cls, name )
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
238
def __repr__( self ):
239
return u"%s('%s')" % ( self.__class__.__name__, self )
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_" ) ):
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 ) )
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
253
# END exception handling
254
# END check attribute validity
255
return super( NamedUI, self ).__setattr__( attr, value )
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:
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
267
super( NamedUI, self ).__init__( *args, **kwargs )
268
#} END overridden methods
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() )
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))
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])
290
""":return: parent instance of this ui element"""
291
return wrapUI(self._parentString())
296
def activeParent( cls ):
297
""":return: NameUI of the currently set parent
298
:raise RuntimeError: if no active parent was set"""
302
wrapuiname = cmds.setParent( q=1, m=1 )
305
wrapuiname = cmds.setParent( q=1 )
307
if not wrapuiname or wrapuiname == "NONE":
308
raise RuntimeError( "No current parent set" )
310
return wrapUI( wrapuiname )
312
#} END hierarchy handling
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.
320
Use this callback to register yourself from all your event senders, then call
321
the base class method.
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
327
self.clearAllEvents()
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 ) )
335
def shortName( self ):
336
""":return: shortname of the ui ( name without pipes )"""
337
return self.split('|')[-1]
340
"""Delete this UI - the wrapper instance must not be used after this call"""
341
cmds.deleteUI( self )
344
""":return: True if this instance still exists in maya"""
346
return self.__melcmd__( self, ex=1 )
348
# although it should just return False if it does NOT exist, it raises
352
p_exists = property(exists)
357
class SizedControl( NamedUI ):
358
"""Base Class for all controls having a dimension"""
359
__metaclass__ = typ.MetaClassCreatorUI
360
_properties_ = ( "dt", "defineTemplate",
368
"npm", "numberOfPopupMenus",
369
"po", "preventOverride",
370
"bgc", "backgroundColor",
373
"fpn", "fullPathName",
374
"ebg", "enableBackground"
377
_events_ = ( "dgc", "dragCallback" ,
378
"dpc", "dropCallback" ,
380
"vcc", "visibleChangeCommand")
384
def annotation( self ):
385
""":return : the annotation string """
387
return self.__melcmd__( self, q=1, ann=1 )
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 ) )
395
def popupMenuArray( self ):
396
""":return: popup menus attached to this control"""
397
return wrapUI( self.__melcmd__( self, q=1, pma=1 ) )
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 )
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] )
415
"""Set the global keyboard focus to this control"""
420
p_annotation = property( annotation, setAnnotation )
422
p_dimension = property( dimension, setDimension )
423
p_pma = property( popupMenuArray )
424
p_popupMenuArray = property( popupMenuArray )
428
class Window( SizedControl, uiutil.UIContainerBase ):
429
"""Simple Window Wrapper
431
:note: Window does not support some of the properties provided by sizedControl"""
432
__metaclass__ = typ.MetaClassCreatorUI
433
_properties_ = ( "t", "title",
439
"mnb", "minimizeButton",
440
"mxb", "maximizeButton",
442
"tbm", "titleBarMenu",
443
"mbv", "menuBarVisible",
444
"tlc", "topLeftCorner",
448
"rtf", "resizeToFitChildren",
451
_events_ = ( "rc", "restoreCommand", "mnc", "minimizeCommand" )
454
#{ Window Specific Methods
459
cmds.showWindow( self )
462
def numberOfMenus( self ):
463
""":return: number of menus in the menu array"""
464
return int( self.__melcmd__( self, q=1, numberOfMenus=1 ) )
466
def menuArray( self ):
467
""":return: Menu instances attached to this window"""
468
return wrapUI( self.__melcmd__( self, q=1, menuArray=1 ) )
470
def isFrontWindow( self ):
471
""":return: True if we are the front window """
472
return bool( self.__melcmd__( self, q=1, frontWindow=1 ) )
474
def setMenuIndex( self, menu, index ):
475
"""Set the menu index of the specified menu
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 ) )
481
#} END window speciic
483
p_numberOfMenus = property( numberOfMenus )
484
p_nm = p_numberOfMenus
487
class MenuBase( NamedUI ):
488
"""Common base for all menus"""
498
"aob", "allowOptionBoxes",
503
"pmc", "postMenuCommand",
504
"pmo", "postMenuCommandOnce"
508
class ContainerMenuBase( uiutil.UIContainerBase ):
509
"""Implements the container abilities of all menu types"""
511
def setActive( self ):
512
"""Make ourselves the active menu
515
cmds.setParent( self, m=1 )
518
def setParentActive( self ):
519
"""Make our parent the active menu layout
522
:note: only useful self is a submenu"""
523
cmds.setParent( ".." , m=1 )
527
class Menu( MenuBase, ContainerMenuBase ):
531
"ni", "numberOfItems",
532
"dai", "deleteAllItems",
537
""":return: An array of our menuItems
538
:note: This method worksaround a maya 2011 problem that makes it impossible
539
to properly wrap menuItems with short names as returned by p_itemArray"""
540
return [MenuItem(name="%s|%s" % (self, i)) for i in noneToList(self.p_itemArray)]
543
class PopupMenu(NamedUI, ContainerMenuBase):
545
"alt", "altModifier",
546
"ctl", "ctrlModifier",
547
"sh", "shiftModifier",
549
"aob", "allowOptionBoxes",
551
"ni", "numberOfItems",
553
"dai", "deleteAllItems"
557
"pmc", "postMenuCommand",
558
"pmo", "postMenuCommandOnce",
562
class MenuItem( MenuBase ):
569
"irb", "isRadioButton",
570
"iob", "isOptionBox",
573
"iol", "imageOverlayLabel",
577
"rp", "radialPosition",
578
"ke", "keyEquivalent",
579
"opt", "optionModifier",
580
"ctl", "controlModifier",
581
"sh", "shiftModifier",
582
"ecr", "enableCommandRepeat",
589
"dmc", "dragMenuCommand",
590
"ddc", "dragDoubleClickCommand",
595
""":return: Menu representing self if it is a submenu
596
:raise TypeError: if self i no submenu"""
598
raise TypeError( "%s is not a submenu and cannot be used as menu" )
600
return Menu(name=self)
603
class SubMenuItem(MenuItem):
604
"""A menu which is always a submenu. This type greatly facilitates subclasses to
605
enforce being a MenuItem which is a submenu as no additional code is required"""
608
# Override these creation-time options to be able to alter them
610
allowOptionBoxes = False
613
def __new__(cls, *args, **kwargs):
614
kwargs.pop("sm", kwargs.pop("subMenu", None))
616
kwargs['tearOff'] = cls.tearOff
617
kwargs['allowOptionBoxes'] = cls.allowOptionBoxes
618
return super(SubMenuItem, cls).__new__(cls, *args, **kwargs)
621
# type is returned in some cases by objectTypeUI
622
CommandMenuItem = MenuItem