-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathbaseMelUI.py
2549 lines (2043 loc) · 79.3 KB
/
baseMelUI.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
'''
This module abstracts and packages up the maya UI that is available to script in a more
object oriented fashion. For the most part the framework tries to make working with maya
UI a bit more like proper UI toolkits where possible.
For more information there is some high level documentation on how to use this code here:
http://www.macaronikazoo.com/?page_id=311
'''
import re
import maya
import names
import common
import inspect
import maya.cmds as cmd
import filesystem
import typeFactories
from maya.OpenMaya import MGlobal
mayaVer = int( maya.mel.eval( 'getApplicationVersionAsFloat' ) )
removeDupes = filesystem.removeDupes
_DEBUG = True
#try to import wing...
try:
import wingdbstub
except:
_DEBUG = False
class MelUIError(Exception): pass
def iterBy( iterable, count ):
'''
returns an generator which will yield "chunks" of the iterable supplied of size "count". eg:
for chunk in iterBy( range( 7 ), 3 ): print chunk
results in the following output:
[0, 1, 2]
[3, 4, 5]
[6]
'''
cur = 0
i = iter( iterable )
while True:
try:
toYield = []
for n in range( count ): toYield.append( i.next() )
yield toYield
except StopIteration:
if toYield: yield toYield
break
class Callback(object):
'''
stupid little callable object for when you need to "bake" temporary args into a
callback - useful mainly when creating callbacks for dynamicly generated UI items
'''
def __init__( self, func, *a, **kw ):
self.f = func
self.a = a
self.kw = kw
def __call__( self, *a, **kw ):
args = self.a + a
kw.update( self.kw )
return self.f( *args, **kw )
#this maps ui type strings to actual command objects - they're not always called the same
TYPE_NAMES_TO_CMDS = { u'staticText': cmd.text,
u'field': cmd.textField,
u'cmdScrollField': cmd.scrollField,
u'commandMenuItem': cmd.menuItem,
u'dividorMenuItem': cmd.menuItem }
#stores a list of widget cmds that don't have docTag support - classes that wrap this command need to skip encoding the classname into the docTag. obviously.
WIDGETS_WITHOUT_DOC_TAG_SUPPORT = [ cmd.popupMenu ]
class BaseMelUI(typeFactories.trackableClassFactory( unicode )):
'''
This is a wrapper class for a mel widget to make it behave a little more like an object. It
inherits from str because thats essentially what a mel widget is - a name coupled with a mel
command. To interact with the widget the mel command is called with the UI name as the first arg.
As a shortcut objects of this type are callable - the args taken depend on the specific command,
and can be found in the mel docs.
example:
class AButtonClass(BaseMelUI):
WIDGET_CMD = cmd.button
aButton = AButtonClass( parentName, label='hello' )
aButton( edit=True, label='new label!' )
'''
#this should be set to the mel widget command used by this widget wrapped - ie cmd.button, or cmd.formLayout
WIDGET_CMD = cmd.control
#if not None, this is used to set the default width of the widget when created
DEFAULT_WIDTH = None
#default heights in 2011 aren't consistent - buttons are 24 pixels high by default (this is a minimum possible value too) while input fields are 20
DEFAULT_HEIGHT = None if mayaVer < 2011 else 24
#this is the name of the kwarg used to set and get the "value" of the widget - most widgets use the "value" or "v" kwarg, but others have special names. three cheers for mel!
KWARG_VALUE_NAME = 'v'
KWARG_VALUE_LONG_NAME = 'value'
#populate this dictionary with variable names you want created on instances. values are default values. having this dict saves having to override __new__ and __init__ methods on subclasses just to create instance variables on creation
#the variables are also popped out of the **kw arg by both __new__ and __init__, so specifying variable names and default values here is the equivalent of making them available on the constructor method
_INSTANCE_VARIABLES = {}
#this is the name of the "main" change command kwarg. some widgets have multiple change callbacks that can be set, and they're not abstracted, but this is the name of the change cb name you want to be referenced by the setChangeCB method
KWARG_CHANGE_CB_NAME = 'cc'
#track instances so we can send them update messages -
_INSTANCE_LIST = []
@classmethod
def Exists( cls, theControl ):
if isinstance( theControl, BaseMelUI ):
return theControl.exists()
return cmd.control( theControl, q=True, exists=True )
@classmethod
def IterWidgetClasses( cls, widgetCmd ):
if widgetCmd is None:
return
for subCls in BaseMelUI.IterSubclasses():
if subCls.WIDGET_CMD is widgetCmd:
yield subCls
def __new__( cls, parent, *a, **kw ):
WIDGET_CMD = cls.WIDGET_CMD
kw.pop( 'p', None ) #pop any parent specified in teh kw dict - set it explicitly to the parent specified
if parent is not None:
kw[ 'parent' ] = parent
#pop out any instance variables defined in the _INSTANCE_VARIABLES dict
instanceVariables = {}
for attrName, attrDefaultValue in cls._INSTANCE_VARIABLES.iteritems():
instanceVariables[ attrName ] = kw.pop( attrName, attrDefaultValue )
#set default sizes if applicable
width = kw.pop( 'w', kw.pop( 'width', cls.DEFAULT_WIDTH ) )
if isinstance( width, int ):
kw[ 'width' ] = width
height = kw.pop( 'h', kw.pop( 'height', cls.DEFAULT_HEIGHT ) )
if isinstance( height, int ):
kw[ 'height' ] = height
#not all mel widgets support docTags... :(
if WIDGET_CMD not in WIDGETS_WITHOUT_DOC_TAG_SUPPORT:
#store the name of this class in the widget's docTag - we can use this to re-instantiate the widget once it's been created
kw.pop( 'dtg', kw.pop( 'docTag', None ) )
kw[ 'docTag' ] = docTag=cls.__name__
#pop out the change callback if its been passed in with the kw dict, and run it through the setChangeCB method so it gets registered appropriately
changeCB = kw.pop( cls.KWARG_CHANGE_CB_NAME, None )
#get the leaf name for the parent
parentNameTok = str( parent ).split( '|' )[-1]
#this has the potential to be slow: it generates a unique name for the widget we're about to create, the benefit of doing this is that we're
#guaranteed the widget LEAF name will be unique. I'm assuming maya also does this, but I'm unsure. if there are weird ui naming conflicts
#it might be nessecary to uncomment this code
formatStr = '%s%d__'#+ parentNameTok
baseName, n = cls.__name__, len( cls._INSTANCE_LIST )
uniqueName = formatStr % (baseName, n)
while WIDGET_CMD( uniqueName, q=True, exists=True ):
n += 1
uniqueName = formatStr % (baseName, n)
WIDGET_CMD( uniqueName, **kw )
new = unicode.__new__( cls, uniqueName )
new.parent = parent
new._cbDict = cbDict = {}
cls._INSTANCE_LIST.append( new )
#add the instance variables to the instance
for attrName, attrValue in instanceVariables.iteritems():
new.__dict__[ attrName ] = attrValue
#if the changeCB is valid, add it to the cd dict
if changeCB:
new.setCB( cls.KWARG_CHANGE_CB_NAME, changeCB )
return new
def __init__( self, parent, *a, **kw ):
pass
def __call__( self, *a, **kw ):
return self.WIDGET_CMD( unicode( self ), *a, **kw )
def setChangeCB( self, cb ):
self.setCB( self.KWARG_CHANGE_CB_NAME, cb )
def getChangeCB( self ):
return self.getCB( self.KWARG_CHANGE_CB_NAME )
def setCB( self, cbFlagName, cb ):
if cb is None:
if cbFlagName in self._cbDict:
self._cbDict.pop( cbFlagName )
else:
self.WIDGET_CMD( self, **{ 'e': True, cbFlagName: cb } )
self._cbDict[ cbFlagName ] = cb
def getCB( self, cbFlagName ):
return self._cbDict.get( cbFlagName, None )
def _executeCB( self, cbFlagName=None ):
if cbFlagName is None:
cbFlagName = self.KWARG_CHANGE_CB_NAME
cb = self.getCB( cbFlagName )
if cb is None:
return
if callable( cb ):
try:
cb()
except Exception, x:
common.printErrorStr( "The %s callback failed" % cbFlagName )
def getFullName( self ):
'''
returns the fullname to the UI widget
'''
parents = list( self.iterParents() )
parents.reverse()
parents.append( self )
parents = [ uiFullName.split( '|' )[ -1 ] for uiFullName in parents ]
return '|'.join( parents )
def sendEvent( self, methodName, *methodArgs, **methodKwargs ):
'''
events are nothing more than a tuple containing a methodName, argument list and
keyword dict that gets propagated up the UI hierarchy.
Each widget in the hierarchy is asked whether it has a method of the given name,
and if it does, the method is called with the argumentList and keywordDict and
propagation ends.
'''
self.parent.processEvent( methodName, methodArgs, methodKwargs )
def processEvent( self, methodName, methodArgs, methodKwargs ):
method = getattr( self, methodName, None )
if callable( method ):
#run the method in the buff if we're in debug mode
if _DEBUG:
method( *methodArgs, **methodKwargs )
else:
try:
method( *methodArgs, **methodKwargs )
except:
common.printErrorStr( 'Event Failed: %s, %s, %s' % (methodName, methodArgs, methodKwargs) )
else:
self.parent.processEvent( methodName, methodArgs, methodKwargs )
def getVisibility( self ):
return self( q=True, vis=True )
def setVisibility( self, visibility=True ):
'''
hides the widget
'''
self( e=True, vis=visibility )
def hide( self ):
self.setVisibility( False )
def show( self ):
self.setVisibility( True )
def setWidth( self, width ):
self( e=True, width=width )
def getWidth( self ):
return self( q=True, width=True )
def setHeight( self, height ):
self( e=True, height=height )
def getHeight( self ):
return self( q=True, height=True )
def setSize( self, widthHeight ):
self( e=True, w=widthHeight[ 0 ], h=widthHeight[ 1 ] )
def getSize( self ):
w = self( q=True, w=True )
h = self( q=True, h=True )
return w, h
def getColour( self ):
return self( q=True, bgc=True )
getColor = getColour
def setColour( self, colour ):
initialVis = self( q=True, vis=True )
self( e=True, bgc=colour, vis=False )
if initialVis:
self( e=True, vis=True )
setColor = setColour
def resetColour( self ):
self( e=True, enableBackground=False )
resetColor = resetColour
def getParent( self ):
'''
returns the widget's parent.
NOTE: its not possible to change a widget's parent once its been created
'''
return self.parent #cmd.control( self, q=True, parent=True )
def iterParents( self ):
'''
returns a generator that walks up the widget hierarchy
'''
try:
parent = self.parent
while True:
yield parent
#if the parent isn't an instance of BaseMelUI (the user has mixed baseMelUI and plain old mel) this should throw an attribute error
try:
parent = parent.parent
#in which case, try to cast the widget as a BaseMelUI instance and yield that instead
except AttributeError:
parent = BaseMelUI.FromStr( cmd.control( parent, q=True, parent=True ) )
except RuntimeError: return
def getTopParent( self ):
'''
returns the top widget at the top of this widget's hierarchy
'''
parent = self.parent
while True:
try:
parent = parent.parent
except AttributeError: return parent
def getParentOfType( self, parentClass, exactMatch=False ):
'''
if exactMatch is True the class of the parent must be the parentClass, otherwise it checks
whether the parent is a subclass of parentClass
'''
for parent in self.iterParents():
if exactMatch:
if parent.__class__ is parentClass:
return parent
else:
if issubclass( parent.__class__, parentClass ):
return parent
def exists( self ):
return cmd.control( self, ex=True )
def delete( self ):
cmd.deleteUI( self )
if self in self._INSTANCE_LIST:
self._INSTANCE_LIST.remove( self )
def setSelectionChangeCB( self, cb, compressUndo=True, **kw ):
'''
creates a scriptJob to monitor selection, and fires the given callback when the selection changes
the scriptJob is parented to this widget so it dies when the UI is closed
NOTE: selection callbacks don't take any args
'''
return cmd.scriptJob( compressUndo=compressUndo, parent=self, event=('SelectionChanged', cb), **kw )
def setSceneChangeCB( self, cb, compressUndo=True, **kw ):
'''
creates a scriptJob which will fire when the currently open scene changes
the scriptJob is parented to this widget so it dies when the UI is closed
NOTE: scene change callbacks don't take any args
'''
return cmd.scriptJob( compressUndo=compressUndo, parent=self, event=('SceneOpened', cb), **kw )
def setTimeChangeCB( self, cb, compressUndo=True, **kw ):
'''
creates a scriptJob which will fire when the current time changes
the scriptJob is parented to this widget so it dies when the UI is closed
NOTE: time change callbacks don't take any args
'''
return cmd.scriptJob( compressUndo=compressUndo, parent=self, event=('timeChanged', cb), **kw )
def setAttributeChangeCB( self, attrpath, cb, compressUndo=True, allChildren=False, disregardIndex=False, **kw ):
'''
creates a scriptjob which will fire when the given attribute gets changed
'''
return cmd.scriptJob( compressUndo=compressUndo, parent=self, attributeChange=(attrpath, cb), allChildren=allChildren, disregardIndex=disregardIndex, **kw )
def setDeletionCB( self, cb, compressUndo=True, **kw ):
'''
define a callback that gets triggered when this piece of UI gets deleted
'''
return cmd.scriptJob( compressUndo=compressUndo, uiDeleted=(self, cb), **kw )
def setUndoCB( self, cb, compressUndo=True, **kw ):
'''
define a callback that gets triggered when an undo event is issued
'''
return cmd.scriptJob( compressUndo=compressUndo, parent=self, event=('Undo', cb), **kw )
@classmethod
def FromStr( cls, theStr ):
'''
given a ui name, this will cast the string as a widget instance
'''
#if the instance is in the instance list, return it
if theStr in cls._INSTANCE_LIST:
idx = cls._INSTANCE_LIST.index( theStr )
return cls._INSTANCE_LIST[ idx ]
else:
theStrLeaf = theStr.split( '|' )[-1]
if theStrLeaf in cls._INSTANCE_LIST:
idx = cls._INSTANCE_LIST.index( theStrLeaf )
return cls._INSTANCE_LIST[ idx ]
try:
uiTypeStr = cmd.objectTypeUI( theStr )
except RuntimeError:
try:
uiTypeStr = cmd.objectTypeUI( theStr.split( '|' )[ -1 ] )
except RuntimeError:
uiTypeStr = ''
uiCmd = TYPE_NAMES_TO_CMDS.get( uiTypeStr, getattr( cmd, uiTypeStr, cmd.control ) )
theCls = None
if uiCmd not in WIDGETS_WITHOUT_DOC_TAG_SUPPORT:
#see if the data stored in the docTag is a valid class name - it might not be if teh user has used the docTag for something (why would they? there is no need, but still check...)
try:
possibleClassName = uiCmd( theStr, q=True, docTag=True )
#menu item dividers have a weird type name when queried - "dividorMenuItem", which is a menuItem technically, but you can't query its docTag, so this catch statement is exclusively for this case as far as I know...
except RuntimeError: pass
else:
theCls = BaseMelUI.GetNamedSubclass( possibleClassName )
#if the data stored in the docTag doesn't map to a subclass, then we'll have to guess at the best class...
if theCls is None:
#common.printInfoStr( cmd.objectTypeUI( theStr ) ) ##NOTE: the typestr isn't ALWAYS the same name as the function used to interact with said control, so this debug line can be useful for spewing object type names...
theCls = BaseMelUI #at this point default to be an instance of the base widget class
candidates = list( BaseMelUI.IterWidgetClasses( uiCmd ) )
if candidates:
theCls = candidates[ 0 ]
new = unicode.__new__( theCls, theStr ) #we don't want to run initialize on the object - just cast it appropriately
new._cbDict = cbDict = {}
cls._INSTANCE_LIST.append( new )
#try to grab the parent...
try:
new.parent = cmd.control( theStr, q=True, parent=True )
except RuntimeError: new.parent = None
return new
@classmethod
def ValidateInstanceList( cls ):
control = cmd.control
_INSTANCE_LIST = cls._INSTANCE_LIST
cls._INSTANCE_LIST = [ ui for ui in _INSTANCE_LIST if control( ui, exists=True ) ]
@classmethod
def IterInstances( cls ):
existingInstList = []
for inst in cls._INSTANCE_LIST:
if not isinstance( inst, cls ):
continue
if cls.WIDGET_CMD( inst, q=True, exists=True ):
existingInstList.append( inst )
yield inst
cls._INSTANCE_LIST = existingInstList
class BaseMelLayout(BaseMelUI):
'''
base class for layout UI
'''
WIDGET_CMD = cmd.layout
DEFAULT_WIDTH = None
DEFAULT_HEIGHT = None
def getChildren( self ):
'''
returns a list of all children UI items
'''
children = self( q=True, ca=True ) or []
children = [ BaseMelUI.FromStr( c ) for c in children ]
return children
def getNumChildren( self ):
return len( self.getChildren() )
def printUIHierarchy( self ):
def printChildren( children, depth ):
for child in children:
common.printInfoStr( '%s%s' % (' ' * depth, child) )
if isinstance( child, BaseMelLayout ):
printChildren( child.getChildren(), depth+1 )
common.printInfoStr( self )
printChildren( self.getChildren(), 1 )
def clear( self ):
'''
deletes all children from the layout
'''
for childUI in self.getChildren():
cmd.deleteUI( childUI )
class MelFormLayout(BaseMelLayout):
WIDGET_CMD = cmd.formLayout
ALL_EDGES = 'top', 'left', 'right', 'bottom'
def getOtherEdges( self, edges ):
otherEdges = list( self.ALL_EDGES )
for edge in edges:
if edge in otherEdges:
otherEdges.remove( edge )
return otherEdges
MelForm = MelFormLayout
class MelSingleLayout(MelForm):
'''
Simple layout that causes the child to stretch to the extents of the layout in all directions.
NOTE: make sure to call layout() after the child has been created to setup the layout data
'''
_INSTANCE_VARIABLES = { 'padding': 0 }
def __init__( self, parent, padding=0, *a, **kw ):
MelForm.__init__( self, parent, *a, **kw )
self._padding = padding
def getPadding( self ):
return self._padding
def setPadding( self, padding ):
self._padding = padding
self.layout()
def layout( self ):
children = self.getChildren()
assert len( children ) == 1, "Can only support one child!"
padding = self._padding
theChild = children[ 0 ]
self( e=True, af=((theChild, 'top', padding), (theChild, 'left', padding), (theChild, 'right', padding), (theChild, 'bottom', padding)) )
class _AlignedFormLayout(MelForm):
_EDGES = 'left', 'right'
def getOtherEdges( self ):
return MelFormLayout.getOtherEdges( self, self._EDGES )
def layoutExpand( self, children ):
edge1, edge2 = self._EDGES
try:
padding = self.padding
except AttributeError:
padding = 0
otherEdges = self.getOtherEdges()
otherEdge1, otherEdge2 = otherEdges
for child in children:
self( e=True, af=((child, otherEdge1, padding), (child, otherEdge2, padding)) )
class MelHLayout(_AlignedFormLayout):
'''
emulates a horizontal layout sizer - the only caveat is that you need to explicitly call
the layout method to setup sizing.
NOTE: you need to call layout() once the children have been created to initialize the
layout relationships
example:
row = MelHLayout( self )
MelButton( row, l='apples' )
MelButton( row, l='bananas' )
MelButton( row, l='oranges' )
row.layout()
'''
_INSTANCE_VARIABLES = { '_weights': {},
'padding': 0,
#if True the layout will expand to fill the layout in the "other" direction. Ie HLayouts will expand vertically and VLayouts will expand horizontally to the extents of the layout
'expand': False }
def setWeight( self, widget, weight=1 ):
self._weights[ widget ] = weight
def getHeight( self ):
return max( [ ui.getHeight() for ui in self.getChildren() ] )
def getWidth( self ):
return sum( [ ui.getWidth() for ui in self.getChildren() ] )
def layout( self, expand=None ):
if expand is not None:
self.expand = expand
padding = self.padding
children = self.getChildren()
weightsDict = self._weights
weightsList = []
for child in children:
weight = weightsDict.get( child, 1 )
weightsList.append( weight )
weightSum = float( sum( weightsList ) )
#if not weightSum:
#return
weightAccum = 0
positions = []
for weight in weightsList:
actualWeight = 100 * weightAccum / weightSum
weightAccum += weight
positions.append( actualWeight )
edge1, edge2 = self._EDGES
positions.append( 100 )
for n, child in enumerate( children ):
self( e=True, ap=((child, edge1, padding, positions[n]), (child, edge2, padding, positions[n+1])) )
#if any children have a weight of zero, set the next item in the list to attach to the widget instead of a position
for n, weight in enumerate( weightsList ):
if weight == 0:
thisW = children[ n ]
try:
nextW = children[ n+1 ]
self( e=True, ac=(nextW, edge1, padding, thisW), an=(thisW, edge2) )
except IndexError:
prevW = children[ n-1 ]
self( e=True, ac=(prevW, edge2, padding, thisW), an=(thisW, edge1) )
if self.expand:
self.layoutExpand( children )
class MelVLayout(MelHLayout):
_EDGES = 'top', 'bottom'
def getHeight( self ):
return sum( [ ui.getHeight() for ui in self.getChildren() ] )
def getWidth( self ):
return max( [ ui.getWidth() for ui in self.getChildren() ] )
class MelHRowLayout(_AlignedFormLayout):
'''
Simple row layout - the rowLayout mel command isn't so hot because you have to know ahead
of time how many columns to build, and dynamic sizing is rubbish. This makes writing a
simple row of widgets super easy.
NOTE: like all subclasses of MelFormLayout, make sure to call .layout() after children
have been built
'''
_INSTANCE_VARIABLES = { 'padding': 5,
#if True the layout will expand to fill the layout in the "other" direction. Ie HLayouts will expand vertically and VLayouts will expand horizontally to the extents of the layout
'expand': False }
def layout( self, expand=None ):
if expand is not None:
self.expand = expand
padding = self.padding
children = self.getChildren()
edge1, edge2 = self._EDGES
for n, child in enumerate( children ):
if n:
self( e=True, ac=(child, edge1, padding, children[ n-1 ]) )
else:
self( e=True, af=(child, edge1, 0) )
if self.expand:
self.layoutExpand( children )
class MelVRowLayout(MelHRowLayout):
_EDGES = 'top', 'bottom'
class MelHSingleStretchLayout(_AlignedFormLayout):
'''
Provides an easy interface to a common layout pattern where a single widget in the
row/column is stretchy and the others are statically sized.
Make sure to call setStretchWidget() before calling layout()
'''
_stretchWidget = None
_INSTANCE_VARIABLES = { 'padding': 5,
#if True the layout will expand to fill the layout in the "other" direction. Ie HLayouts will expand vertically and VLayouts will expand horizontally to the extents of the layout
'expand': False }
def setPadding( self, padding ):
self.padding = padding
def setExpand( self, expand ):
'''
make sure to call layout() after changing the expand value
'''
self.expand = expand
def setStretchWidget( self, widget ):
if not isinstance( widget, BaseMelUI ):
widget = BaseMelUI.FromStr( widget )
self._stretchWidget = widget
self.layout()
def layout( self, expand=None ):
if expand is not None:
self.expand = expand
padding = self.padding
children = self.getChildren()
stretchWidget = self._stretchWidget
if stretchWidget is None:
stretchWidget = children[ 0 ]
idx = children.index( stretchWidget )
leftChildren = children[ :idx ]
rightChildren = children[ idx+1: ]
edge1, edge2 = self._EDGES
for n, child in enumerate( leftChildren ):
if n:
self( e=True, ac=(child, edge1, padding, children[ n-1 ]) )
else:
self( e=True, af=(child, edge1, 0) )
if rightChildren:
self( e=True, af=(rightChildren[-1], edge2, 0) )
for n, child in enumerate( rightChildren[ :-1 ] ):
self( e=True, ac=(child, edge2, padding, rightChildren[ n+1 ]) )
if leftChildren and rightChildren:
self( e=True, ac=((stretchWidget, edge1, padding, leftChildren[-1]), (stretchWidget, edge2, padding, rightChildren[0])) )
elif leftChildren and not rightChildren:
self( e=True, ac=(stretchWidget, edge1, padding, leftChildren[-1]), af=(stretchWidget, edge2, 0) )
elif not leftChildren and rightChildren:
self( e=True, af=(stretchWidget, edge1, 0), ac=(stretchWidget, edge2, padding, rightChildren[0]) )
if self.expand:
self.layoutExpand( children )
class MelVSingleStretchLayout(MelHSingleStretchLayout):
_EDGES = 'top', 'bottom'
_INSTANCE_VARIABLES = { 'padding': 5,
'expand': True }
class MelColumnLayout(BaseMelLayout):
WIDGET_CMD = cmd.columnLayout
STRETCHY = True
def __new__( cls, parent, *a, **kw ):
stretchy = kw.pop( 'adjustableColumn', kw.pop( 'adj', cls.STRETCHY ) )
kw.setdefault( 'adjustableColumn', stretchy )
return BaseMelLayout.__new__( cls, parent, *a, **kw )
MelColumn = MelColumnLayout
class MelRowLayout(BaseMelLayout): WIDGET_CMD = cmd.rowLayout
MelRow = MelRowLayout
class MelGridLayout(BaseMelLayout):
WIDGET_CMD = cmd.gridLayout
AUTO_GROW = True
def __new__( cls, parent, **kw ):
autoGrow = kw.pop( 'autoGrow', self.AUTO_GROW )
kw.setdefault( 'ag', autoGrow )
return BaseMelLayout.__new__( cls, parent, **kw )
MelGrid = MelGridLayout
class MelScrollLayout(BaseMelLayout):
WIDGET_CMD = cmd.scrollLayout
def __new__( cls, parent, *a, **kw ):
kw.setdefault( 'childResizable', kw.pop( 'cr', True ) )
return BaseMelLayout.__new__( cls, parent, *a, **kw )
MelScroll = MelScrollLayout
class MelHScrollLayout(MelScrollLayout):
_ORIENTATION_ATTR = 'verticalScrollBarThickness'
def __new__( cls, parent, *a, **kw ):
kw[ cls._ORIENTATION_ATTR ] = 0
return MelScrollLayout.__new__( cls, parent, *a, **kw )
class MelVScrollLayout(MelHScrollLayout):
_ORIENTATION_ATTR = 'horizontalScrollBarThickness'
class MelTabLayout(BaseMelLayout):
WIDGET_CMD = cmd.tabLayout
def __init__( self, parent, *a, **kw ):
BaseMelLayout.__init__( self, parent, *a, **kw )
ccCB = kw.get( 'changeCommand', kw.get( 'cc', None ) )
self.setChangeCB( ccCB )
pscCB = kw.get( 'preSelectCommand', kw.get( 'psc', None ) )
self.setPreSelectCB( pscCB )
dccCB = kw.get( 'doubleClickCommand', kw.get( 'dcc', None ) )
self.setDoubleClickCB( dccCB )
def numTabs( self ):
return self( q=True, numberOfChildren=True )
__len__ = numTabs
def setLabel( self, idx, label ):
self( e=True, tabLabelIndex=(idx+1, label) )
def getLabel( self, idx ):
self( q=True, tabLabelIndex=idx+1 )
def getSelectedTab( self ):
return self( q=True, selectTab=True )
def setSelectedTab( self, child, executeChangeCB=True ):
self( e=True, selectTab=child )
if executeChangeCB:
self._executeCB( 'selectCommand' )
def getSelectedTabIdx( self ):
return self( q=True, selectTabIndex=True )-1 #indices are 1-based... fuuuuuuu alias!
def setSelectedTabIdx( self, idx, executeChangeCB=True ):
self( e=True, selectTabIndex=idx+1 ) #indices are 1-based... fuuuuuuu alias!
if executeChangeCB:
self._executeCB( 'selectCommand' )
def setChangeCB( self, cb ):
self.setCB( 'selectCommand', cb )
def setPreSelectCB( self, cb ):
self.setCB( 'preSelectCommand', cb )
def setDoubleClickCB( self, cb ):
self.setCB( 'doubleClickCommand', cb )
class MelPaneLayout(BaseMelLayout):
WIDGET_CMD = cmd.paneLayout
PREF_OPTION_VAR = None
POSSIBLE_CONFIGS = \
CFG_SINGLE, CFG_HORIZ2, CFG_VERT2, CFG_HORIZ3, CFG_VERT3, CFG_TOP3, CFG_LEFT3, CFG_BOTTOM3, CFG_RIGHT3, CFG_HORIZ4, CFG_VERT4, CFG_TOP4, CFG_LEFT4, CFG_BOTTOM4, CFG_RIGHT4, CFG_QUAD = \
"single", "horizontal2", "vertical2", "horizontal3", "vertical3", "top3", "left3", "bottom3", "right3", "horizontal4", "vertical4", "top4", "left4", "bottom4", "right4", "quad"
CONFIG = CFG_VERT2
KWARG_CHANGE_CB_NAME = 'separatorMovedCommand'
def __init__( self, parent, configuration=None, *a, **kw ):
kw.setdefault( 'separatorMovedCommand', self.on_resize )
if configuration is None:
assert self.CONFIG in self.POSSIBLE_CONFIGS
configuration = self.CONFIG
kw[ 'configuration' ] = configuration
kw.pop( 'cn', None )
BaseMelLayout.__init__( self, parent, *a, **kw )
self( e=True, **kw )
if self.PREF_OPTION_VAR:
if cmd.optionVar( ex=self.PREF_OPTION_VAR ):
storedSize = cmd.optionVar( q=self.PREF_OPTION_VAR )
for idx, size in enumerate( filesystem.iterBy( storedSize, 2 ) ):
self.setPaneSize( idx, size )
def __getitem__( self, idx ):
idx += 1 #indices are 1-based... fuuuuuuu alias!
kw = { 'q': True, 'pane%d' % idx: True }
return BaseMelUI.FromStr( self( **kw ) )
def __setitem__( self, idx, ui ):
idx += 1 #indices are 1-based... fuuuuuuu alias!
return self( e=True, setPane=(ui, idx) )
def getConfiguration( self ):
return self( q=True, configuration=True )
def setConfiguration( self, ui ):
return self( e=True, configuration=ui )
def getPaneUnderPointer( self ):
return BaseMelUI.FromStr( self( q=True, paneUnderPointer=True ) )
def getPaneActive( self ):
return BaseMelUI.FromStr( self( q=True, activePane=True ) )
def getPaneActiveIdx( self ):
return self( q=True, activePaneIndex=True ) - 1 #indices are 1-based...
def getPaneSize( self, idx ):
idx += 1
return self( q=True, paneSize=idx )
def setPaneSize( self, idx, size ):
idx += 1
size = idx, size[0], size[1]
return self( e=True, paneSize=size )
def setPaneWidth( self, idx, size ):
idx += 1
curSize = self.getPaneSize( idx )
return self( e=True, paneSize=(idx, curSize[0], size) )
def setPaneHeight( self, idx, size ):
idx += 1
curSize = self.getPaneSize( idx )
return self( e=True, paneSize=(idx, size, curSize[1]) )
### EVENT HANDLERS ###
def on_resize( self, *a ):
if self.PREF_OPTION_VAR:
size = self.getPaneSize( 0 )
cmd.optionVar( clearArray=self.PREF_OPTION_VAR )
for i in size:
cmd.optionVar( iva=(self.PREF_OPTION_VAR, i) )
class MelFrameLayout(BaseMelLayout):
WIDGET_CMD = cmd.frameLayout
def setCollapseCB( self, cb ):
BaseMelLayout.setChangeCB( self, cb )
def getCollapseCB( self ):
return BaseMelLayout.getChangeCB( self )
def setExpandCB( self, cb ):
self.setCB( 'expandCommand', cb )
def getExpandCB( self ):
return self.getCB( 'expandCommand' )
def getCollapse( self ):
return self( q=True, collapse=True )
def setCollapse( self, state, executeChangeCB=True ):
self( e=True, collapse=state )
if executeChangeCB:
if state:
collapseCB = self.getCollapseCB()
if callable( collapseCB ):
collapseCB()
else:
expandCB = self.getExpandCB()
if callable( expandCB ):
expandCB()
class BaseMelWidget(BaseMelUI):
def setValue( self, value, executeChangeCB=True ):
try:
kw = { 'e': True, self.KWARG_VALUE_NAME: value }
self.WIDGET_CMD( self, **kw )
except TypeError, x:
common.printErrorStr( 'Running setValue method using %s command' % self.WIDGET_CMD )
raise
if executeChangeCB:
self._executeCB()
def getValue( self ):
kw = { 'q': True, self.KWARG_VALUE_NAME: True }
return self.WIDGET_CMD( self, **kw )
def enable( self, state=True ):
try: self( e=True, enable=bool( state ) ) #explicitly cast here in case we've been passed a non-bool. the maya.cmds binding interprets any non-boolean object as True it seems...
except: pass
def disable( self ):
self.enable( False )
def getAnnotation( self ):
return self( q=True, ann=True )
def setAnnotation( self, annotation ):
self( e=True, ann=annotation )
setEnabled = enable
def getEnabled( self ):
try: return self( q=True, enable=True )
except: return True
def editable( self, state=True ):
try: self( e=True, editable=bool( state ) )
except: pass
def setEditable( self, state ):
self.editable( state )
def getEditable( self ):
return bool( self( q=True, ed=True ) )
def setFocus( self ):
cmd.setFocus( self )
class MelLabel(BaseMelWidget):
WIDGET_CMD = cmd.text
KWARG_VALUE_NAME = 'l'
KWARG_VALUE_LONG_NAME = 'label'
def bold( self, state=True ):
self( e=True, font='boldLabelFont' if state else 'plainLabelFont' )
getLabel = BaseMelWidget.getValue
setLabel = BaseMelWidget.setValue