mrv.maya.ui.base
Covered: 321 lines
Missed: 113 lines
Skipped 189 lines
Percent: 73 %
  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
 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 )
 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 ]
 45
	out = list()
 46
	for uiname in uinames:
 47
		uitype = getUIType( uiname )
 48
		clsname = capitalize( uitype )
 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 ) )
 57
	if islisttype:
 58
		return out
 60
	return out[0]
 63
UI = wrapUI
 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
 74
def lsUI( **kwargs ):
 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) )
 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 }
 91
	kwargs['long'] = long
 92
	if head is not None: kwargs['head'] = head
 93
	if tail is not None: kwargs['tail'] = tail
 99
	return wrapUI( set( cmds.lsUI( **kwargs ) ), ignore_errors = True )
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
123
	**Events**
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
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:
160
			if "Window" not in uitype and cls._sep not in uiname:
161
				return 2
162
			return 1
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
168
		:param kwargs:
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.
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.
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
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 )
197
		has_valid_name = True
198
		if name and ('|' in name):
199
			has_valid_name = False
203
		if not kwargs.pop( "wrap_only", False ) and exists == 2:
204
			exists = 0
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 )
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 )
220
				created = True
221
			except (RuntimeError,TypeError), e:
222
				raise RuntimeError( "Creation of %s using melcmd %s failed: %s" % ( cls, cls.__melcmd__, str( e ) ) )
226
		inst = unicode.__new__( cls, name )
232
		if created and cls.uiDeleted != NamedUI.uiDeleted:
233
			cmds.scriptJob(uiDeleted=(name, inst.uiDeleted), runOnce=1) 
236
		return inst
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_" ) ):
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:
252
				pass
255
		return super( NamedUI, self ).__setattr__( attr, value )
257
	def __init__( self , *args, **kwargs ):
258
		""" Initialize instance and check arguments """
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" )
267
		super( NamedUI, self ).__init__( *args, **kwargs )
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])
289
	def parent( self ):
290
		""":return: parent instance of this ui element"""
291
		return wrapUI(self._parentString())
295
	@classmethod
296
	def activeParent( cls ):
297
		""":return: NameUI of the currently set parent
298
		:raise RuntimeError: if no active parent was set"""
300
		wrapuiname = None
301
		if cls._is_menu:
302
			wrapuiname = cmds.setParent( q=1, m=1 )
303
		else:
305
			wrapuiname = cmds.setParent( q=1 )
307
		if not wrapuiname or wrapuiname == "NONE":
308
			raise RuntimeError( "No current parent set" )
310
		return wrapUI( wrapuiname )
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.
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 
326
			within maya."""
327
		self.clearAllEvents()
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 ) )
335
	def shortName( self ):
336
		""":return: shortname of the ui ( name without pipes )"""
337
		return self.split('|')[-1]
339
	def delete( self ):
340
		"""Delete this UI - the wrapper instance must not be used after this call"""
341
		cmds.deleteUI( self )
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:
349
			return False
352
	p_exists = property(exists)
353
	p_ex = p_exists
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",
373
						"fpn", "fullPathName",
374
						"ebg", "enableBackground"
375
					)
377
	_events_ = ( 	"dgc", "dragCallback" ,
378
					"dpc", "dropCallback" ,
380
					"vcc", "visibleChangeCommand")
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 ""
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] )
414
	def setFocus(self):
415
		"""Set the global keyboard focus to this control"""
416
		cmds.setFocus(self)
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 )
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",
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" )
451
	_events_ = ( "rc", "restoreCommand", "mnc", "minimizeCommand" )
456
	def show( self ):
457
		""" Show Window
458
		:return: self"""
459
		cmds.showWindow( self )
460
		return 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 ) )
483
	p_numberOfMenus = property( numberOfMenus )
484
	p_nm = p_numberOfMenus
487
class MenuBase( NamedUI ):
488
	"""Common base for all menus"""
491
	_is_menu = True
494
	_properties_ = (
495
					   "en", "enable",
496
					   	"l", "label",
497
						"mn", "mnemonic",
498
						"aob", "allowOptionBoxes",
499
						"dt", "docTag"
500
					 )
502
	_events_ = (
503
					 	"pmc", "postMenuCommand",
504
						"pmo", "postMenuCommandOnce"
505
				)
508
class ContainerMenuBase( uiutil.UIContainerBase ):
509
	"""Implements the container abilities of all menu types"""
511
	def setActive( self ):
512
		"""Make ourselves the active menu
514
		:return: self"""
515
		cmds.setParent( self, m=1 )
516
		return self
518
	def setParentActive( self ):
519
		"""Make our parent the active menu layout
521
		:return: self
522
		:note: only useful self is a submenu"""
523
		cmds.setParent( ".." , m=1 )
524
		return self
527
class Menu( MenuBase, ContainerMenuBase ):
528
	_properties_ = (
529
					  	"hm", "helpMenu",
530
						"ia", "itemArray",
531
						"ni", "numberOfItems",
532
						"dai", "deleteAllItems",
533
						"fi", "familyImage"
534
					)
536
	def itemArray(self):
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):
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
					)
556
	_events_ = (
557
						"pmc", "postMenuCommand", 
558
						"pmo", "postMenuCommandOnce", 
559
				)
562
class MenuItem( MenuBase ):
564
	_properties_ = (
565
						"d", "divider",
566
						"cb", "checkBox",
567
						"icb", "isCheckBox",
568
						"rb", "radioButton",
569
						"irb", "isRadioButton",
570
						"iob", "isOptionBox",
571
						"cl", "collection",
572
						"i", "image",
573
						"iol", "imageOverlayLabel",
574
						"sm", "subMenu",
575
						"ann", "annotation",
576
						"da", "data",
577
						"rp", "radialPosition",
578
						"ke", "keyEquivalent",
579
						"opt", "optionModifier",
580
						"ctl", "controlModifier",
581
						"sh", "shiftModifier",
582
						"ecr", "enableCommandRepeat",
583
						"ec", "echoCommand",
584
						"it", "italicized",
585
						"bld", "boldFont"
586
					)
588
	_events_ = (
589
						"dmc", "dragMenuCommand",
590
						"ddc", "dragDoubleClickCommand",
591
						"c", "command"
592
				)
594
	def toMenu(self):
595
		""":return: Menu representing self if it is a submenu
596
		:raise TypeError: if self i no submenu"""
597
		if not self.p_sm:
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"""
609
	tearOff = False
610
	allowOptionBoxes = False
613
	def __new__(cls, *args, **kwargs):
614
		kwargs.pop("sm", kwargs.pop("subMenu", None))
615
		kwargs['sm'] = True
616
		kwargs['tearOff'] = cls.tearOff
617
		kwargs['allowOptionBoxes'] = cls.allowOptionBoxes
618
		return super(SubMenuItem, cls).__new__(cls, *args, **kwargs)
622
CommandMenuItem = MenuItem