前言

  最近动画组这边也有类似的工具排布需求,Maya本身的工具架放了固定的工具图标。
  但是大家使用的习惯不太相同,真的众口难调,所以一个更好的自定义工具架才能让美术人员整理出自己喜欢的工具架。

  凑巧我无聊的时候在 Github 上搜索仓库,无意中找到了这个很棒的自定义工具架插件 SiShelf

工具改良

  原工具实现的功能已经非常非常棒了,但是我还是有自己的需求,想要做得更好。

moudle Installer 安装

  还是 RJ 大神的模块安装的思路厉害,可以很便捷地安装 Maya 的任意模块。
  这样可以简化安装难度。

alt

工具架图标添加获取更多信息

  原本的工具无法直接获取工具架图标的很多信息。
  因为 PyQt 的 drop 事件只提供了 工具架图标的 代码信息。
  我这里通过获取的信息找到当前工具架的具体图标,从而获取图标的一些数据。
  实现更好的交互。

alt

角标实现小图标窗口模式

  给插件添加了一个角标按钮,点击可以缩小成一个图标窗口。
  图标窗口右键可以激活 XPOP 菜单模式。

alt

  右上角的角标图标是 Maya 内置图标叫做 window_dragCorner.png,我通过 Qt pixmap 将角标翻转
  然后通过 gridLayout 让角标保持在窗口的右侧。
  后面就是添加按钮的窗口事件了。

  点击切换到小窗口模式,默认 Maya 的最小化 最大化 关闭按钮 顶住了最小宽度。
  为了让窗口宽度更小,需要将窗口转成只有一个关闭按钮的模式,我发现可以通过 windowsFlag 设置 drawer 来实现。
  缺点是这种做法无法使用 Maya Mixin ,我只好使用 QDialog Parent 到 Maya 主窗口的方式实现。


  我后面测试 Maya Mixin 也可以改为 Drawer 的形式,只是操作方法比较特殊
  需要先执行 show 命令让 Maya 获取到 窗口 的存在,然后设置 windowsFlag 才有效。
  这样设置 windowsFlags 会让窗口关闭,需要再执行一遍 show 命令

1
2
3
4
5
6
7
8
9
10
11
12
from maya.app.general import mayaMixin

from PySide2 import QtWidgets
from PySide2 import QtCore


class TestLabel(mayaMixin.MayaQWidgetBaseMixin,QtWidgets.QDialog):
pass
win = TestLabel()
win.show()
win.setWindowFlags(win.windowFlags() | QtCore.Qt.Drawer)
win.show()

  后面思考了一下其实可以重写 show 函数的方法,这样代码看起来更加合理
  查了 Mixin 类之后也可知道 _makeMayaStandaloneWindow() 让窗口 parent 到主窗口上是关键操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from maya.app.general import mayaMixin

from PySide2 import QtWidgets
from PySide2 import QtCore


class TestLabel(mayaMixin.MayaQWidgetBaseMixin,QtWidgets.QDialog):

def show(self):
if self.parent() is None:
self._makeMayaStandaloneWindow()
self.setWindowFlags(self.windowFlags() | QtCore.Qt.Drawer)
super(TestLabel,self).show()

win = TestLabel()
win.show()

总结

  最终我 fork 了这个仓库,并且将自己的修改添加到了 pull request 上,希望原作者接纳我的改良。 github仓库
  其实我改良的东西并不多,源工具已经设计得非常好了。
  而且看了部分工具的代码又学到了新的东西。
  原来 Maya 提供了 Mixin 内置类,可以轻松创建出基于 Qt 的 Dockable 窗口。
  不一定需要通过 OpenMaya 转 Qt 的方式这么麻烦。


  下面是 Maya Mixin 类的源码(可以在 Maya 的 python site-packages 中找到源码),代码中有具体的操作方法。
  通过 from maya.app.general import mayaMixin 进行导入

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
#!/usr/bin/env python
"""
Maya mixin classes to add common functionality for custom PyQt/PySide widgets in Maya.

* MayaQWidgetBaseMixin Mixin that should be applied to all custom QWidgets created for Maya
to automatically handle setting the objectName and parenting

* MayaQWidgetDockableMixin Mixin that adds dockable capabilities within Maya controlled with
the show() function
"""


import uuid

from maya import cmds
from maya import mel
from maya import OpenMayaUI as omui

# Import available PySide or PyQt package, as it will work with both
try:
from PySide2.QtCore import Qt, QPoint, QSize
from PySide2.QtCore import Signal
from PySide2.QtGui import *
from PySide2.QtWidgets import *
from shiboken2 import wrapInstance, getCppPointer
_qtImported = 'PySide2'
except ImportError, e1:
try:
from PyQt4.QtCore import Qt, QPoint, QSize
from PyQt4.QtCore import pyqtSignal as Signal
from PyQt4.QtGui import *
from sip import wrapinstance as wrapInstance
_qtImported = 'PyQt4'
except ImportError, e2:
raise ImportError, '%s, %s'%(e1,e2)


mixinWorkspaceControls = dict()

def workspaceControlDeleted(controlName):
global mixinWorkspaceControls
if controlName in mixinWorkspaceControls:
del mixinWorkspaceControls[controlName]

def workspaceControlClosed(controlName):
global mixinWorkspaceControls
if controlName in mixinWorkspaceControls:
mixinWorkspaceControls[controlName].dockCloseEventTriggered()

def workspaceControlReparented(controlName, isFloating):
global mixinWorkspaceControls
if controlName in mixinWorkspaceControls:
mixinWorkspaceControls[controlName].floatingChanged(isFloating)

class MayaQWidgetBaseMixin(object):
'''
Handle common actions for Maya Qt widgets during initialization:
* auto-naming a Widget so it can be looked up as a string through
maya.OpenMayaUI.MQtUtil.findControl()
* parenting the widget under the main maya window if no parent is explicitly
specified so not to have the Window disappear when the instance variable
goes out of scope

Integration Notes:
Inheritance ordering: This class must be placed *BEFORE* the Qt class for proper execution
This is needed to workaround a bug where PyQt/PySide does not call super() in its own __init__ functions

Example:
class MyQWidget(MayaQWidgetBaseMixin, QPushButton):
def __init__(self, parent=None):
super(MyQWidget, self).__init__(parent=parent)
self.setText('Push Me')
myWidget = MyQWidget()
myWidget.show()
print myWidget.objectName()
'''
def __init__(self, parent=None, *args, **kwargs):
super(MayaQWidgetBaseMixin, self).__init__(parent=parent, *args, **kwargs) # Init all baseclasses (including QWidget) of the main class
self._initForMaya(parent=parent)


def _initForMaya(self, parent=None, *args, **kwargs):
'''
Handle the auto-parenting and auto-naming.

:Parameters:
parent (string)
Explicitly specify the QWidget parent. If 'None', then automatically
parent under the main Maya window
'''

# If the input parent happens to be a Native window (such as the main Maya
# window) then when we are parented to it, we also become a Native window.
# Being a Native window is okay, but we don't want our ancestors to be
# switched to Native, such as when we are docked inside a tabWidget.
self.setAttribute(Qt.WA_DontCreateNativeAncestors)


# Set a unique object name string so Maya can easily look it up
if self.objectName() == '':
self.setObjectName('%s_%s'%(self.__class__.__name__, uuid.uuid4()))


def _makeMayaStandaloneWindow(self):
'''Make a standalone window, though parented under Maya's mainWindow.
The parenting under Maya's mainWindow is done so that the QWidget will not
auto-destroy itself when the instance variable goes out of scope.
'''
origParent = self.parent()

# Parent under the main Maya window
mainWindowPtr = omui.MQtUtil.mainWindow()
mainWindow = wrapInstance(long(mainWindowPtr), QMainWindow)
self.setParent(mainWindow)

# Make this widget appear as a standalone window even though it is parented
if isinstance(self, QDockWidget):
self.setWindowFlags(Qt.Dialog|Qt.FramelessWindowHint)
else:
self.setWindowFlags(Qt.Window)

# Delete the parent workspace control if applicable
if origParent:
parentName = origParent.objectName()
if parentName and len(parentName) and cmds.workspaceControl(parentName, q=True, exists=True):
cmds.deleteUI(parentName, control=True)

def show(self):
'''Show the widget. Overrides standard QWidget.show()
'''
# Set parent to Maya main window if parent=None
if self.parent() is None:
self._makeMayaStandaloneWindow()

QWidget.show(self)

def setVisible(self, makeVisible):
'''
Show/hide the widget. Overrides standard QWidget.setVisible()
'''
# If showing, set parent to Maya main window if parent=None
if (makeVisible == True) and self.parent() is None:
self._makeMayaStandaloneWindow()

QWidget.setVisible(self, makeVisible)


class MayaQDockWidget(MayaQWidgetBaseMixin,QDockWidget):
'''QDockWidget tailored for use with Maya.
Mimics the behavior performed by Maya's internal QMayaDockWidget class and the dockControl command

:Signals:
closeEventTriggered: emitted when a closeEvent occurs

:Known Issues:
* Manually dragging the DockWidget to dock in the Main MayaWindow will have it resize to the 'sizeHint' size
of the child widget() instead of preserving its existing size.
'''
# Custom Signals
closeEventTriggered = Signal() # Qt Signal triggered when closeEvent occurs
windowStateChanged = Signal() # Qt Signal triggered when the window is moved or resized.

def __init__(self, parent=None, *args, **kwargs):
super(MayaQDockWidget, self).__init__(parent=parent, *args, **kwargs) # Init all baseclasses (including QWidget) of the main class

# == Mimic operations performed by Maya internal QmayaDockWidget ==
self.setAttribute(Qt.WA_MacAlwaysShowToolWindow)

# WORKAROUND: The mainWindow.handleDockWidgetVisChange may not be present on some PyQt and PySide systems.
# Handle case if it fails to connect to the attr.
mainWindowPtr = omui.MQtUtil.mainWindow()
mainWindow = wrapInstance(long(mainWindowPtr), QMainWindow)
try:
self.visibilityChanged.connect(mainWindow.handleDockWidgetVisChange)
except AttributeError, e:
# Error connecting visibilityChanged trigger to mainWindow.handleDockWidgetVisChange.
# Falling back to using MEL command directly.
mel.eval('evalDeferred("updateEditorToggleCheckboxes()")') # Currently mainWindow.handleDockWidgetVisChange only makes this updateEditorToggleCheckboxes call


def setArea(self, area):
'''Set the docking area
'''
# Skip setting the area if no area value passed in
if area == Qt.NoDockWidgetArea:
return

# Mimic operations performed by Maya dockControl command
mainWindow = self.parent() if isinstance(self.parent(), QMainWindow) else wrapInstance(long(omui.MQtUtil.mainWindow()), QMainWindow)

childrenList = mainWindow.children()
foundDockWidgetToTab = False
for child in childrenList:
# Create Tabbed dock if a QDockWidget already at that area
if (child != self) and (isinstance(child, QDockWidget)):
if not child.isHidden() and not child.isFloating():
if mainWindow.dockWidgetArea(child) == area:
mainWindow.tabifyDockWidget(child, self)
self.raise_()
foundDockWidgetToTab = True
break
# If no other QDockWidget at that area, then just add it
if not foundDockWidgetToTab:
mainWindow.addDockWidget(area, self)

def resizeEvent(self, event):
super(MayaQDockWidget, self).resizeEvent(event)
if event.isAccepted():
self.windowStateChanged.emit()

def moveEvent(self, event):
super(MayaQDockWidget, self).moveEvent(event)
if event.isAccepted():
self.windowStateChanged.emit()

def closeEvent(self, evt):
'''Hide the QDockWidget and trigger the closeEventTriggered signal
'''
# Handle the standard closeEvent()
super(MayaQDockWidget, self).closeEvent(evt)

if evt.isAccepted():
# Force visibility to False
self.setVisible(False) # since this does not seem to have happened already

# Emit that a close event is occurring
self.closeEventTriggered.emit()


class MayaQWidgetDockableMixin(MayaQWidgetBaseMixin):
'''
Handle Maya dockable actions controlled with the show() function.

Integration Notes:
Inheritance ordering: This class must be placed *BEFORE* the Qt class for proper execution
This is needed to workaround a bug where PyQt/PySide does not call super() in its own __init__ functions

Example:
class MyQWidget(MayaQWidgetDockableMixin, QPushButton):
def __init__(self, parent=None):
super(MyQWidget, self).__init__(parent=parent)
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred )
self.setText('Push Me')
myWidget = MyQWidget()
myWidget.show(dockable=True)
myWidget.show(dockable=False)
print myWidget.showRepr()
'''

# Custom Signals
closeEventTriggered = Signal() # Qt Signal triggered when closeEvent occurs
windowStateChanged = Signal() # Qt Signal triggered when the window is moved or resized.

def __del__(self):
global mixinWorkspaceControls
workspaceControlName = self.objectName() + 'WorkspaceControl'
if workspaceControlName in mixinWorkspaceControls :
del mixinWorkspaceControls[workspaceControlName]


def setDockableParameters(self, dockable=None, floating=None, area=None, allowedArea=None, width=None, widthSizingProperty=None, initWidthAsMinimum=None, height=None, heightSizingProperty=None, x=None, y=None, retain=True, plugins=None, controls=None, uiScript=None, closeCallback=None, *args, **kwargs):
'''
Set the dockable parameters.

:Parameters:
dockable (bool)
Specify if the window is dockable (default=False)
floating (bool)
Should the window be floating or docked (default=True)
area (string)
Default area to dock into (default='left')
Options: 'top', 'left', 'right', 'bottom'
allowedArea (string)
Allowed dock areas (default='all')
Options: 'top', 'left', 'right', 'bottom', 'all'
width (int)
Width of the window
height (int)
Height of the window
x (int)
left edge of the window
y (int)
top edge of the window

:See: show(), hide(), and setVisible()
'''
if ((dockable == True) or (dockable is None and self.isDockable())): # == Handle docked window ==
# By default, when making dockable, make it floating
# This addresses an issue on Windows with the window decorators not showing up.
if floating is None and area is None:
floating = True

# Create workspaceControl if needed
if dockable == True and not self.isDockable():
# Retrieve original position and size
# Position
if x is None:
x = self.x()
# Give suitable default value if null
if x == 0:
x = 250
if y is None:
y = self.y()
# Give suitable default value if null
if y == 0:
y = 200
# Size
unininitializedSize = QSize(640,480) # Hardcode: (640,480) is the default size for a QWidget
if self.size() == unininitializedSize:
# Get size from widget sizeHint if size not yet initialized (before the first show())
widgetSizeHint = self.sizeHint()
else:
widgetSizeHint = self.size() # use the current size of the widget
if width is None:
width = widgetSizeHint.width()
if height is None:
height = widgetSizeHint.height()
if widthSizingProperty is None:
widthSizingProperty = 'free'
if heightSizingProperty is None:
heightSizingProperty = 'free'
if initWidthAsMinimum is None:
initWidthAsMinimum = False

if controls is None:
controls = []
if plugins is None:
plugins = []

workspaceControlName = self.objectName() + 'WorkspaceControl'
# Set to floating if requested or if no docking area given
if floating == True or area is None:
workspaceControlName = cmds.workspaceControl(workspaceControlName, label=self.windowTitle(), retain=retain, loadImmediately=True, floating=True, initialWidth=width, widthProperty=widthSizingProperty, minimumWidth=initWidthAsMinimum, initialHeight=height, heightProperty=heightSizingProperty, requiredPlugin=plugins, requiredControl=controls)
else:
if self.parent() is None or (long(getCppPointer(self.parent())[0]) == long(omui.MQtUtil.mainWindow())):
# If parented to the Maya main window or nothing, dock into the Maya main window
workspaceControlName = cmds.workspaceControl(workspaceControlName, label=self.windowTitle(), retain=retain, loadImmediately=True, dockToMainWindow=(area, False), initialWidth=width, widthProperty=widthSizingProperty, minimumWidth=initWidthAsMinimum, initialHeight=height, heightProperty=heightSizingProperty, requiredPlugin=plugins, requiredControl=controls)
else:
# Otherwise, the parent should be within a workspace control - need to go up the hierarchy to find it
foundParentWorkspaceControl = False
nextParent = self.parent()
while nextParent is not None:
dockToWorkspaceControlName = nextParent.objectName()
if cmds.workspaceControl(dockToWorkspaceControlName, q=True, exists=True):
workspaceControlName = cmds.workspaceControl(workspaceControlName, label=self.windowTitle(), retain=retain, loadImmediately=True, dockToControl=(dockToWorkspaceControlName, area), initialWidth=width, widthProperty=widthSizingProperty, minimumWidth=initWidthAsMinimum, initialHeight=height, heightProperty=heightSizingProperty, requiredPlugin=plugins, requiredControl=controls)
foundParentWorkspaceControl = True
break
else:
nextParent = nextParent.parent()

if foundParentWorkspaceControl == False:
# If parent workspace control cannot be found, just make the workspace control a floating window
workspaceControlName = cmds.workspaceControl(workspaceControlName, label=self.windowTitle(), retain=retain, loadImmediately=True, floating=True, initialWidth=width, widthProperty=widthSizingProperty, minimumWidth=initWidthAsMinimum, initialHeight=height, heightProperty=heightSizingProperty, requiredPlugin=plugins, requiredControl=controls)

currParent = omui.MQtUtil.getCurrentParent()
mixinPtr = omui.MQtUtil.findControl(self.objectName())
omui.MQtUtil.addWidgetToMayaLayout(long(mixinPtr), long(currParent))

if uiScript is not None and len(uiScript):
cmds.workspaceControl(workspaceControlName, e=True, uiScript=uiScript)

if closeCallback is not None:
cmds.workspaceControl(workspaceControlName, e=True, closeCommand=closeCallback)

# Add this control to the list of controls created in Python
global mixinWorkspaceControls
mixinWorkspaceControls[workspaceControlName] = self

# Hook up signals
#dockWidget.topLevelChanged.connect(self.floatingChanged)
#dockWidget.closeEventTriggered.connect(self.dockCloseEventTriggered)

else: # == Handle Standalone Window ==
# Make standalone as needed
if not dockable and self.isDockable():
# Retrieve original position and size
dockPos = self.parent().pos()
if x == None:
x = dockPos.x()
if y == None:
y = dockPos.y()
if width == None:
width = self.width()
if height == None:
height = self.height()
# Turn into a standalone window and reposition
currentVisibility = self.isVisible()
self._makeMayaStandaloneWindow() # Set the parent back to Maya and remove the parent dock widget
self.setVisible(currentVisibility)

# Handle position and sizing
if (width != None) or (height != None):
if width == None:
width = self.width()
if height == None:
height = self.height()
self.resize(width, height)
if (x != None) or (y != None):
if x == None:
x = self.x()
if y == None:
y = self.y()
self.move(x,y)

def setSizeHint(self, size):
'''
Virtual method used to pass the user settable width and height down to the widget whose
size policy controls the actual size most of the time.
'''
pass

def show(self, *args, **kwargs):
'''
Show the QWidget window. Overrides standard QWidget.show()

:See: setDockableParameters() for a list of parameters
'''
# Update the dockable parameters first (if supplied)
if len(args) or len(kwargs):
self.setDockableParameters(*args, **kwargs)
elif self.parent() is None:
# Set parent to Maya main window if parent=None and no dockable parameters provided
self._makeMayaStandaloneWindow()

# Handle the standard setVisible() operation of show()
QWidget.setVisible(self, True) # NOTE: Explicitly calling QWidget.setVisible() as using super() breaks in PySide: super(self.__class__, self).show()

# Handle special case if the parent is a QDockWidget (dockControl)
parent = self.parent()
if parent:
parentName = parent.objectName()
if parentName and len(parentName) and cmds.workspaceControl(parentName, q=True, exists=True):
if cmds.workspaceControl(parentName, q=True, visible=True):
cmds.workspaceControl(parentName, e=True, restore=True)
else:
cmds.workspaceControl(parentName, e=True, visible=True)


def hide(self, *args, **kwargs):
'''Hides the widget. Will hide the parent widget if it is a QDockWidget.
Overrides standard QWidget.hide()
'''
# Handle special case if the parent is a QDockWidget (dockControl)
parent = self.parent()
if parent:
parentName = parent.objectName()
if parentName and len(parentName) and cmds.workspaceControl(parentName, q=True, exists=True):
cmds.workspaceControl(parentName, e=True, visible=False)
else:
QWidget.setVisible(self, False) # NOTE: Explicitly calling QWidget.setVisible() as using super() breaks in PySide: super(self.__class__, self).show()

def close(self):
'''Closes the widget. Overrides standard QWidget.close()
'''
parent = self.parent()
if parent:
parentName = parent.objectName()
if parentName and len(parentName) and cmds.workspaceControl(parentName, q=True, exists=True):
cmds.workspaceControl(parentName, e=True, close=True)
else:
QWidget.close(self)

def isVisible(self):
'''Return if the widget is currently visible. Overrides standard QWidget.isVisible()

:Return: bool
'''
parent = self.parent()
if parent:
parentName = parent.objectName()
if parentName and len(parentName) and cmds.workspaceControl(parentName, q=True, exists=True):
return cmds.workspaceControl(parentName, q=True, visible=True)
return QWidget.isVisible(self)

def setVisible(self, makeVisible, *args, **kwargs):
'''
Show/hide the QWidget window. Overrides standard QWidget.setVisible() to pass along additional arguments

:See: show() and hide()
'''
if (makeVisible == True):
return self.show(*args, **kwargs)
else:
return self.hide(*args, **kwargs)


def raise_(self):
'''Raises the widget to the top. Will raise the parent widget if it is a QDockWidget.
Overrides standard QWidget.raise_()
'''
# Handle special case if the parent is a QDockWidget (dockControl)
parent = self.parent()
if parent:
parentName = parent.objectName()
if parentName and len(parentName) and cmds.workspaceControl(parentName, q=True, exists=True):
# Raise the workspace control
cmds.workspaceControl(parentName, e=True, restore=True)
else:
QWidget.raise_(self) # NOTE: Explicitly using QWidget as using super() breaks in PySide: super(self.__class__, self).show()


def isDockable(self):
'''Return if the widget is currently dockable (under a QDockWidget)

:Return: bool
'''
parent = self.parent()
if parent:
parentName = parent.objectName()
if parentName and len(parentName):
return cmds.workspaceControl(parentName, q=True, exists=True)
else:
return False
return False


def isFloating(self):
'''Return if the widget is currently floating (under a QDockWidget)
Will return True if is a standalone window OR is a floating dockable window.

:Return: bool
'''
parent = self.parent()
if parent:
parentName = parent.objectName()
if parentName and len(parentName) and cmds.workspaceControl(parentName, q=True, exists=True):
return cmds.workspaceControl(parentName, q=True, floating=True)
else:
return True
return True


def floatingChanged(self, isFloating):
'''Triggered when QDockWidget.topLevelChanged() signal is triggered.
Stub function. Override to perform actions when this happens.
'''
pass


def dockCloseEventTriggered(self):
'''Triggered when QDockWidget.closeEventTriggered() signal is triggered.
Stub function. Override to perform actions when this happens.
'''
pass


def dockArea(self):
'''Return area if the widget is currently docked to the Maya MainWindow
Will return None if not dockable

:Return: str
'''
dockControlQt = self.parent()

if not isinstance(dockControlQt, QDockWidget):
return None
else:
mainWindow = self.parent().parent() if isinstance(self.parent().parent(), QMainWindow) \
else wrapInstance(long(omui.MQtUtil.mainWindow()), QMainWindow)

dockAreaMap = {
Qt.LeftDockWidgetArea : 'left',
Qt.RightDockWidgetArea : 'right',
Qt.TopDockWidgetArea : 'top',
Qt.BottomDockWidgetArea : 'bottom',
Qt.AllDockWidgetAreas : 'all',
Qt.NoDockWidgetArea : 'none', # Note: 'none' not supported in maya dockControl command
}
dockWidgetAreaBitmap = mainWindow.dockWidgetArea(dockControlQt)
return dockAreaMap[dockWidgetAreaBitmap]


def setWindowTitle(self, val):
'''Sets the QWidget's title and also it's parent QDockWidget's title if dockable.

:Return: None
'''
# Handle the standard setVisible() operation of show()
QWidget.setWindowTitle(self, val) # NOTE: Explicitly calling QWidget.setWindowTitle() as using super() breaks in PySide: super(self.__class__, self).show()

# Handle special case if the parent is a QDockWidget (dockControl)
parent = self.parent()
if parent:
parentName = parent.objectName()
if parentName and len(parentName) and cmds.workspaceControl(parentName, q=True, exists=True):
cmds.workspaceControl(parentName, e=True, label=val)

def showRepr(self):
'''Present a string of the parameters used to reproduce the current state of the
widget used in the show() command.

:Return: str
'''
reprDict = {}
reprDict['dockable'] = self.isDockable()
reprDict['floating'] = self.isFloating()
reprDict['area'] = self.dockArea()
#reprDict['allowedArea'] = ??
if reprDict['floating']:
if reprDict['dockable']:
pos = self.parent().pos()
else:
pos = self.pos()
reprDict['x'] = pos.x()
reprDict['y'] = pos.y()

sz = self.geometry().size()
reprDict['width'] = sz.width()
reprDict['height'] = sz.height()

# Construct the repr show() string
reprShowList = ['%s=%r'%(k,v) for k,v in reprDict.items() if v != None]
reprShowStr = 'show(%s)'%(', '.join(reprShowList))
return reprShowStr
# ===========================================================================
# Copyright 2016 Autodesk, Inc. All rights reserved.
#
# Use of this software is subject to the terms of the Autodesk license
# agreement provided at the time of installation or download, or which
# otherwise accompanies this software in either electronic or hard copy form.
# ===========================================================================