Beispiel #1
0
class BandMixin(object):
	def __init__(self, **kwargs):
		super(BandMixin, self).__init__(**kwargs)
		self.__band = None

	def showBand(self, *geom):
		if not self.__band:
			self.__band = QRubberBand(QRubberBand.Rectangle, parent=self)
		self.__band.setGeometry(*geom)
		self.__band.show()

	def hideBand(self):
		if self.__band:
			self.__band.hide()
			self.__band.setParent(None)
			self.__band = None
Beispiel #2
0
class CBlueprintView(QGraphicsView):
    def __init__(self, graphicID, parent=None):
        super(CBlueprintView, self).__init__(parent)
        self.m_GraphicID = graphicID
        self.m_Scale = 1
        self.m_StartPos = None
        self.m_IsHasMove = False  # view视图是否有移动
        self.m_SelectPos = None  # 框选初始坐标
        self.m_RubberBand = None  # 框选框对象
        self.m_Scene = scene.CBlueprintScene(graphicID, self)
        self._InitUI()
        self._InitSignal()
        self._LoadData()

    def _InitUI(self):
        self.setWindowTitle("蓝图")
        self.setScene(self.m_Scene)
        # self.setBackgroundBrush(QBrush(QColor(103, 103, 103), Qt.SolidPattern))
        self.setDragMode(QGraphicsView.NoDrag)
        self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
        self.setResizeAnchor(QGraphicsView.NoAnchor)
        # 隐藏滚动条
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

    def _InitSignal(self):
        GetSignal().UI_FOCUS_NODE.connect(self.S_FocusNode)

    def _LoadData(self):
        lstNode = interface.GetGraphicAttr(self.m_GraphicID,
                                           eddefine.GraphicAttrName.NODE_LIST)
        for nodeID in lstNode:
            self.m_Scene._NewNodeUI(nodeID)
        lstLine = interface.GetGraphicAttr(self.m_GraphicID,
                                           eddefine.GraphicAttrName.LINE_LIST)
        for lineID in lstLine:
            iPinID, oPinID = interface.GetLinePinInfo(lineID)
            self.m_Scene.AddLineUI(lineID, iPinID, oPinID)

    def GetGraphicID(self):
        return self.m_GraphicID

    def mousePressEvent(self, event):
        super(CBlueprintView, self).mousePressEvent(event)
        if event.button() == Qt.LeftButton:
            if self.itemAt(event.pos()) is None:
                self.m_SelectPos = event.pos()
                self.setDragMode(QGraphicsView.NoDrag)
                self.setTransformationAnchor(QGraphicsView.NoAnchor)
                self.m_RubberBand = QRubberBand(QRubberBand.Rectangle,
                                                self.viewport())
                self.m_RubberBand.show()

        if event.button() == Qt.MidButton:
            self.setTransformationAnchor(QGraphicsView.NoAnchor)
            self.setDragMode(QGraphicsView.ScrollHandDrag)
            self.m_StartPos = event.pos()

    def mouseMoveEvent(self, event):
        super(CBlueprintView, self).mouseMoveEvent(event)
        self.setTransformationAnchor(QGraphicsView.NoAnchor)
        pos = event.pos()
        if self.m_StartPos:
            offsetX, offsetY = pos.x() - self.m_StartPos.x(), pos.y(
            ) - self.m_StartPos.y()
            offsetX /= self.m_Scale
            offsetY /= self.m_Scale
            self.translate(offsetX, offsetY)
            self.m_StartPos = pos
            self.m_IsHasMove = True

        if self.m_SelectPos:
            rect = QRect(min(self.m_SelectPos.x(), pos.x()),
                         min(self.m_SelectPos.y(), pos.y()),
                         abs(self.m_SelectPos.x() - pos.x()),
                         abs(self.m_SelectPos.y() - pos.y()))
            path = QPainterPath()
            path.addPolygon(self.mapToScene(rect))
            path.closeSubpath()
            self.m_Scene.RubberBandSelecNodeUI(path,
                                               self.rubberBandSelectionMode(),
                                               self.viewportTransform())
            self.m_RubberBand.setGeometry(rect)

    def mouseReleaseEvent(self, event):
        super(CBlueprintView, self).mouseReleaseEvent(event)
        if event.button() == Qt.MidButton:
            self.m_StartPos = None
            self.setDragMode(QGraphicsView.NoDrag)
        if event.button() == Qt.LeftButton:
            self.m_SelectPos = None
            if self.m_RubberBand:
                self.m_RubberBand.setParent(None)
                self.m_RubberBand = None

    def wheelEvent(self, event):
        """ctrl+滑轮滚动缩放"""
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        fAngleDelta = event.angleDelta().y()
        factor = 1.41**(fAngleDelta / 240.0)
        minScale, maxScale = 0.1, 2.0  # 控制缩放的值
        fNewScale = self.m_Scale * factor
        if fNewScale > maxScale:
            fNewScale = maxScale
        elif fNewScale < minScale:
            fNewScale = minScale
        fScale = fNewScale / self.m_Scale
        self.scale(fScale, fScale)
        self.m_Scale = fNewScale
        # 没有缩放信号传递给下层
        super(CBlueprintView, self).wheelEvent(event)
        event.ignore()

    def contextMenuEvent(self, event):
        """右键上下文事件"""
        super(CBlueprintView, self).contextMenuEvent(event)
        if self.m_IsHasMove:
            self.m_IsHasMove = False
            return
        if event.isAccepted():
            return
        lPos = event.pos()
        gPos = self.mapToGlobal(lPos)
        sPos = self.mapToScene(lPos)
        tPos = sPos.x(), sPos.y()
        menu = QMenu(self)
        for sNodeName in interface.GetAllDefineNodeName():
            func = functor.Functor(self.S_OnCreateNodeUI, sNodeName, tPos)
            menu.addAction(sNodeName, func)
        menu.exec_(gPos)

    def dragEnterEvent(self, event):
        """拖动操作进入本窗口"""
        super(CBlueprintView, self).dragEnterEvent(event)
        event.accept()
        event.acceptProposedAction()

    def dragMoveEvent(self, event):
        """拖拽移动中"""
        event.accept()
        event.acceptProposedAction()

    def dropEvent(self, event):
        """放开了鼠标完成drop操作"""
        super(CBlueprintView, self).dropEvent(event)
        event.acceptProposedAction()
        oMimeData = event.mimeData()
        from bpwidget.bpattrui import basetree
        from bpwidget import define as bwdefine
        if not isinstance(oMimeData, basetree.CBPAttrMimeData):
            return
        sType, varID = oMimeData.GetItemInfo()
        if sType != bwdefine.BP_ATTR_VARIABLE:
            return
        lPos = event.pos()
        sPos = self.mapToScene(lPos)
        tPos = sPos.x(), sPos.y()
        menu = QMenu(self)
        for sNodeName in (bddefine.NodeName.GET_VARIABLE,
                          bddefine.NodeName.SET_VARIABLE):
            func = functor.Functor(self.S_OnCreateNodeUI, sNodeName, tPos,
                                   varID)
            menu.addAction(sNodeName, func)
        menu.exec_(QCursor.pos())

    def S_OnCreateNodeUI(self, sNodeName, tPos=(0, 0), varID=None):
        interface.AddNode(self.m_GraphicID, sNodeName, tPos, varID)

    def S_FocusNode(self, graphicID, nodeID):
        if self.m_GraphicID != graphicID:
            return

        bpID = interface.GetBPIDByGraphicID(graphicID)
        GetSignal().UI_FOCUS_GRAPHIC.emit(bpID, graphicID)

        pos = interface.GetNodeAttr(nodeID, bddefine.NodeAttrName.POSITION)
        x, y = pos[0], pos[1]
        oNodeUI = GetUIMgr().GetNodeUI(nodeID)
        if oNodeUI:
            x += oNodeUI.size().width() / 2
            y += oNodeUI.size().height() / 2
        point = QPointF(x, y)
        self.centerOn(point)
        GetStatusMgr().SelectOneNode(graphicID, nodeID)
class QToolWindowManager ( QWidget ):
	toolWindowVisibilityChanged = pyqtSignal ( QWidget, bool )
	layoutChanged = pyqtSignal ()
	updateTrackingTooltip = pyqtSignal ( str, QPoint )

	def __init__ ( self, parent, config, factory = None ):
		super ( QToolWindowManager, self ).__init__ ( parent )
		self.factory = factory if factory != None else QToolWindowManagerClassFactory ()
		self.dragHandler = None
		self.config = config
		self.closingWindow = 0

		self.areas = []
		self.wrappers = []
		self.toolWindows = []
		self.toolWindowsTypes = {}
		self.draggedToolWindows = []
		self.layoutChangeNotifyLocks = 0

		if self.factory.parent () == None:
			self.factory.setParent ( self )

		self.mainWrapper = QToolWindowWrapper ( self )
		self.mainWrapper.getWidget ().setObjectName ( 'mainWrapper' )
		self.setLayout ( QVBoxLayout ( self ) )
		self.layout ().setContentsMargins ( 2, 2, 2, 2 )
		self.layout ().setSpacing ( 0 )
		self.layout ().addWidget ( self.mainWrapper.getWidget () )
		self.lastArea = self.createArea ()
		self.draggedWrapper = None
		self.resizedWrapper = None
		self.mainWrapper.setContents ( self.lastArea.getWidget () )

		self.dragHandler = self.createDragHandler ()

		self.preview = QRubberBand ( QRubberBand.Rectangle )
		self.preview.hide ()

		self.raiseTimer = QTimer ( self )
		self.raiseTimer.timeout.connect ( self.raiseCurrentArea )
		self.setSizePolicy ( QSizePolicy.Expanding, QSizePolicy.Expanding )

		qApp.installEventFilter ( self )

	def __del__ ( self ):
		self.suspendLayoutNotifications ()
		while len ( self.areas ) != 0:
			a = self.areas[ 0 ]
			a.setParent ( None )
			self.areas.remove ( a )

		while len ( self.wrappers ) != 0:
			w = self.wrappers[ 0 ]
			w.setParent ( None )
			self.areas.remove ( w )

		del self.dragHandler
		del self.mainWrapper

	def empty ( self ) -> bool:
		return len ( self.areas ) == 0

	def removeArea ( self, area ):
		"""Summary line.

		Args:
			area (QToolWindowArea): Description of param1

	    Returns:
	        QToolWindowArea: Description of return value
	    """
		# print ( "[QToolWindowManager] removeArea %s" % area )
		if area in self.areas:
			self.areas.remove ( area )
		if self.lastArea == area:
			self.lastArea = None

	def removeWrapper ( self, wrapper ):
		"""Summary line.

		Args:
			wrapper (QToolWindowWrapper): Description of param1
	    """
		if self.ownsWrapper ( wrapper ):
			self.wrappers.remove ( wrapper )

	def ownsArea ( self, area ) -> bool:
		"""Summary line.

		Args:
			area (QToolWindowArea): Description of param1

	    Returns:
	        bool: Description of return value
	    """
		return area in self.areas

	def ownsWrapper ( self, wrapper ) -> bool:
		"""Summary line.

		Args:
			wrapper (QToolWindowWrapper): Description of param1

	    Returns:
	        bool: Description of return value
	    """
		return wrapper in self.wrappers

	def ownsToolWindow ( self, toolWindow ) -> bool:
		"""Summary line.

		Args:
			wrapper (QToolWindowWrapper): Description of param1

	    Returns:
	        bool: Description of return value
	    """
		return toolWindow in self.toolWindows

	def startDrag ( self, toolWindows, area ):
		if len ( toolWindows ) == 0:
			return
		# print ( "[QToolWindowManager] startDrag", toolWindows )
		self.dragHandler.startDrag ()
		self.draggedToolWindows = toolWindows

		floatingGeometry = QRect ( QCursor.pos (), area.size () )
		self.moveToolWindows ( toolWindows, area, QToolWindowAreaReference.Drag, -1, floatingGeometry )

	def startDragWrapper ( self, wrapper ):
		self.dragHandler.startDrag ()
		self.draggedWrapper = wrapper
		self.lastArea = None
		self.updateDragPosition ()

	def startResize ( self, wrapper ):
		self.resizedWrapper = wrapper

	def addToolWindowTarget ( self, toolWindow, target, toolType = QTWMToolType.ttStandard ):
		self.insertToToolTypes ( toolWindow, toolType )
		self.addToolWindow ( toolWindow, target.area, target.reference, target.index, target.geometry )

	def addToolWindow ( self, toolWindow, area = None, reference = QToolWindowAreaReference.Combine, index = -1,
	                    geometry = QRect () ):
		self.addToolWindows ( [ toolWindow ], area, reference, index, geometry )

	def addToolWindowsTarget ( self, toolWindows, target, toolType ):
		for toolWindow in toolWindows:
			self.insertToToolTypes ( toolWindow, toolType )
		self.addToolWindows ( toolWindows, target.area, target.reference, target.index, target.geometry )

	def addToolWindows ( self, toolWindows, area = None, reference = QToolWindowAreaReference.Combine, index = -1,
	                     geometry = QRect () ):
		for toolWindow in toolWindows:
			if not self.ownsToolWindow ( toolWindow ):
				toolWindow.hide ()
				toolWindow.setParent ( None )
				toolWindow.installEventFilter ( self )
				self.insertToToolTypes ( toolWindow, QTWMToolType.ttStandard )
				self.toolWindows.append ( toolWindow )

				# from qt.controls.QToolWindowManager.QToolTabManager import QTabPane
				#
				# if isinstance ( toolWindow, QTabPane ):
				# 	print ( "[QToolWindowManager] addToolWindow: QTabPane %s" % toolWindow.pane )
				# else:
				# 	print ( "[QToolWindowManager] addToolWindow: %s" % toolWindow )
		self.moveToolWindows ( toolWindows, area, reference, index, geometry )

	def moveToolWindowTarget ( self, toolWindow, target, toolType = QTWMToolType.ttStandard ):
		self.insertToToolTypes ( toolWindow, toolType )
		self.addToolWindow ( toolWindow, target.area, target.reference, target.index, target.geometry )

	def moveToolWindow ( self, toolWindow, area = None, reference = QToolWindowAreaReference.Combine, index = -1,
	                     geometry = QRect () ):
		self.moveToolWindows ( [ toolWindow ], area, reference, index, geometry )

	def moveToolWindowsTarget ( self, toolWindows, target, toolType = QTWMToolType.ttStandard ):
		for toolWindow in toolWindows:
			self.insertToToolTypes ( toolWindow, toolType )
		self.addToolWindows ( toolWindows, target.area, target.reference, target.index, target.geometry )

	def moveToolWindows ( self, toolWindows, area = None, reference = QToolWindowAreaReference.Combine, index = -1,
	                      geometry = None ):

		# qWarning ( "moveToolWindows %s" % toolWindows )

		# If no area find one
		if area == None:
			if self.lastArea != None:
				area = self.lastArea
			elif len ( self.areas ) != 0:
				area = self.areas[ 0 ]
			else:
				qWarning ( "lastArea is None, and areas is 0, self.createArea ()" )
				area = self.createArea ()
				self.lastArea = area
				self.mainWrapper.setContents ( self.lastArea.getWidget () )

		dragOffset = QPoint ()
		# Get the current mouse position and offset from the area before we remove the tool windows from it
		if area != None and reference == QToolWindowAreaReference.Drag:
			widgetPos = area.mapToGlobal ( area.rect ().topLeft () )
			dragOffset = widgetPos - QCursor.pos ()

		wrapper = None
		currentAreaIsSimple = True

		for toolWindow in toolWindows:
			# when iterating over the tool windows, we will figure out if the current one is actually roll-ups and not tabs
			currentArea = findClosestParent ( toolWindow, [ QToolWindowArea, QToolWindowRollupBarArea ] )
			if currentAreaIsSimple and currentArea != None and currentArea.areaType () == QTWMWrapperAreaType.watTabs:
				currentAreaIsSimple = False
			self.releaseToolWindow ( toolWindow, False )

		if reference == QToolWindowAreaReference.Top or \
				reference == QToolWindowAreaReference.Bottom or \
				reference == QToolWindowAreaReference.Left or \
				reference == QToolWindowAreaReference.Right:
			area = cast ( self.splitArea ( self.getFurthestParentArea ( area.getWidget () ).getWidget (), reference ), [ QToolWindowArea, QToolWindowRollupBarArea ] )
		elif reference == QToolWindowAreaReference.HSplitTop or \
				reference == QToolWindowAreaReference.HSplitBottom or \
				reference == QToolWindowAreaReference.VSplitLeft or \
				reference == QToolWindowAreaReference.VSplitRight:
			area = cast ( self.splitArea ( area.getWidget (), reference ), [ QToolWindowArea, QToolWindowRollupBarArea ] )
		elif reference == QToolWindowAreaReference.Floating or \
				reference == QToolWindowAreaReference.Drag:
			# when dragging we will try to determine target are type, from window types.
			areaType = QTWMWrapperAreaType.watTabs
			if len ( toolWindows ) > 1:
				if currentAreaIsSimple:
					areaType = QTWMWrapperAreaType.watRollups
			elif self.toolWindowsTypes[ toolWindows[ 0 ] ] == QTWMToolType.ttSimple:
				areaType = QTWMWrapperAreaType.watRollups

			# create new window
			area = self.createArea ( areaType )
			wrapper = self.createWrapper ()
			wrapper.setContents ( area.getWidget () )
			# wrapper.move ( QCursor.pos () )
			wrapper.getWidget ().show ()
			# wrapper.getWidget ().grabMouse ()

			# qWarning ( "[QToolWindowManager] moveToolWindows create new window reference: %s area's wrapper: %s" % (
			# self.getAreaReferenceString ( reference ), area.wrapper ()) )\\\

			if geometry != None: # we have geometry, apply the mouse offset
				# If we have a title bar we want to move the mouse to half the height of it
				titleBar = wrapper.getWidget ().findChild ( QCustomTitleBar )
				if titleBar:
					dragOffset.setY ( -titleBar.height () / 2 )
				# apply the mouse offset to the current rect
				geometry.moveTopLeft ( geometry.topLeft () + dragOffset )
				wrapper.getWidget ().setGeometry ( geometry )
				wrapper.getWidget ().move ( QCursor.pos () )
				if titleBar:
					currentTitle = titleBar.caption.text ()
					if len ( toolWindows ) > 0:
						titleBar.caption.setText ( toolWindows[ 0 ].windowTitle () )
					wrapper.getWidget ().move (
						- ( titleBar.caption.mapToGlobal ( titleBar.caption.rect ().topLeft () ) - wrapper.mapToGlobal ( wrapper.rect ().topLeft () ) )
						+ QPoint ( -titleBar.caption.fontMetrics ().boundingRect ( titleBar.caption.text () ).width () / 2, -titleBar.caption.height () / 2 )
						+ wrapper.pos ()
					)
					titleBar.caption.setText ( currentTitle )
			else: # with no present geometry we just create a new one
				wrapper.getWidget ().setGeometry ( QRect ( QPoint ( 0, 0 ), toolWindows[ 0 ].sizeHint () ) )
				wrapper.getWidget ().move ( QCursor.pos () )

			# when create new wrapper, we should transfer mouse-event to wrapper's titleBar for we can continue move.
			if reference == QToolWindowAreaReference.Drag:
				titleBar = wrapper.getWidget ().findChild ( QCustomTitleBar )
				if titleBar:
					# set flag for titleBar releaseMouse
					titleBar.createFromDraging = True
					startDragEvent = QMouseEvent ( QEvent.MouseButtonPress, QCursor.pos (), QtCore.Qt.LeftButton,
					                               QtCore.Qt.LeftButton, QtCore.Qt.NoModifier )
					qApp.sendEvent ( titleBar, startDragEvent )
					titleBar.grabMouse ()

		if reference != QToolWindowAreaReference.Hidden:
			area.addToolWindows ( toolWindows, index )
			self.lastArea = area

		# This will remove the previous area the tool windows where attached to
		self.simplifyLayout ()
		for toolWindow in toolWindows:
			self.toolWindowVisibilityChanged.emit ( toolWindow, toolWindow.parent () != None )
		self.notifyLayoutChange ()

		if reference == QToolWindowAreaReference.Drag and wrapper != None:
			self.draggedWrapper = wrapper
			# start the drag on the new wrapper, will end up calling QToolWindowManager::startDrag(IToolWindowWrapper* wrapper)
			wrapper.startDrag ()

	def getAreaReferenceString ( self, reference ):
		# Top = 0
		# Bottom = 1
		# Left = 2
		# Right = 3
		# HSplitTop = 4
		# HSplitBottom = 5
		# VSplitLeft = 6
		# VSplitRight = 7
		# Combine = 8
		# Floating = 9
		# Drag = 10
		# Hidden = 11
		if reference == QToolWindowAreaReference.Top:
			return "Top"
		elif reference == QToolWindowAreaReference.Bottom:
			return "Bottom"
		elif reference == QToolWindowAreaReference.Left:
			return "Left"
		elif reference == QToolWindowAreaReference.Right:
			return "Right"
		elif reference == QToolWindowAreaReference.HSplitTop:
			return "HSplitTop"
		elif reference == QToolWindowAreaReference.HSplitBottom:
			return "HSplitBottom"
		elif reference == QToolWindowAreaReference.VSplitLeft:
			return "VSplitLeft"
		elif reference == QToolWindowAreaReference.VSplitRight:
			return "VSplitRight"
		elif reference == QToolWindowAreaReference.Combine:
			return "Combine"
		elif reference == QToolWindowAreaReference.Floating:
			return "Floating"
		elif reference == QToolWindowAreaReference.Drag:
			return "Drag"
		elif reference == QToolWindowAreaReference.Hidden:
			return "Hidden"

	def releaseToolWindow ( self, toolWindow, allowClose = False ) -> bool:
		# No parent, so can't possibly be inside an IToolWindowArea
		if toolWindow.parentWidget () == None:
			# qWarning ( "[QToolWindowManager] releaseToolWindow %s, but toolWindow.parentWidget () == None" % toolWindow )
			return False

		if not self.ownsToolWindow ( toolWindow ):
			qWarning ( "Unknown tool window %s" % toolWindow )
			return False

		previousTabWidget = findClosestParent ( toolWindow, [ QToolWindowArea, QToolWindowRollupBarArea ] )
		if previousTabWidget is None:
			qWarning ( "[QToolWindowManager] cannot find area for tool window %s" % toolWindow )
			return False

		if allowClose:
			releasePolicy = self.config.setdefault ( QTWM_RELEASE_POLICY, QTWMReleaseCachingPolicy.rcpWidget )
			if releasePolicy == QTWMReleaseCachingPolicy.rcpKeep:
				self.moveToolWindow ( toolWindow, None, QToolWindowAreaReference.Hidden )
				if self.config.setdefault ( QTWM_ALWAYS_CLOSE_WIDGETS, True ) and not self.tryCloseToolWindow ( toolWindow ):
					return False
			elif releasePolicy == QTWMReleaseCachingPolicy.rcpWidget:
				if not toolWindow.testAttribute ( QtCore.Qt.WA_DeleteOnClose ):
					if self.config.setdefault ( QTWM_ALWAYS_CLOSE_WIDGETS, True ) and not self.tryCloseToolWindow (
							toolWindow ):
						return False
					self.moveToolWindow ( toolWindow, None, QToolWindowAreaReference.Hidden )
			elif releasePolicy == QTWMReleaseCachingPolicy.rcpForget or releasePolicy == QTWMReleaseCachingPolicy.rcpDelete:
				if not self.tryCloseToolWindow ( toolWindow ):
					return False
				self.moveToolWindow ( toolWindow, None, QToolWindowAreaReference.Hidden )
				self.toolWindows.remove ( toolWindow )

				if releasePolicy == QTWMReleaseCachingPolicy.rcpDelete:
					toolWindow.deleteLater ()
				return True

		previousTabWidget.removeToolWindow ( toolWindow )

		if allowClose:
			self.simplifyLayout ()
		else:
			previousTabWidget.adjustDragVisuals ()

		toolWindow.hide ()
		toolWindow.setParent ( None )
		return True

	def releaseToolWindows ( self, toolWindows, allowClose = False ) -> bool:
		# print ( "[QToolWindowManager] releaseToolWindows", toolWindows )
		result = True
		for i in range ( len ( toolWindows ) - 1, -1, -1 ):
		# for toolWindow in toolWindows:
			# if i >= 0 and i < len ( toolWindows ):
			result &= self.releaseToolWindow ( toolWindows[ i ], allowClose )
		# while len ( toolWindows ) != 0:
		# 	result &= self.releaseToolWindow ( toolWindows[ 0 ], allowClose )
		return result

	def areaOf ( self, toolWindow ) -> QToolWindowArea:
		area = findClosestParent ( toolWindow, [ QToolWindowArea, QToolWindowRollupBarArea ] )
		# print ( "[QToolWindowManager] areaOf %s is %s" % ( toolWindow, area ) )
		return area

	def swapAreaType ( self, oldArea, areaType = QTWMWrapperAreaType.watTabs ):
		from QToolWindowManager.QToolWindowWrapper import QToolWindowWrapper
		from QToolWindowManager.QToolWindowCustomWrapper import QToolWindowCustomWrapper
		targetWrapper = findClosestParent ( oldArea.getWidget (), [ QToolWindowWrapper, QToolWindowCustomWrapper ] )
		parentSplitter = cast ( oldArea.parentWidget (), QSplitter )
		newArea = self.createArea ( areaType )

		if parentSplitter is None and targetWrapper is None:
			qWarning ( "[QToolWindowManager] Could not determine area parent" )
			return

		newArea.addToolWindow ( oldArea.toolWindows () )
		if parentSplitter != None:
			targetIndex = parentSplitter.indexOf ( oldArea.getWidget () )
			parentSplitter.insertWidget ( targetIndex, newArea.getWidget () )
		else:
			targetWrapper.setContents ( newArea.getWidget () )

		if self.lastArea == oldArea:
			self.lastArea = newArea

		oldAreaIndex = self.areas.index ( oldArea )
		self.areas.remove ( oldArea )
		self.areas.insert ( oldAreaIndex, newArea )
		oldArea.getWidget ().setParent ( None )
		newArea.adjustDragVisuals ()

	def isWrapper ( self, w ) -> bool:
		if w is None:
			return False
		from QToolWindowManager.QToolWindowWrapper import QToolWindowWrapper
		from QToolWindowManager.QToolWindowCustomWrapper import QToolWindowCustomWrapper
		return cast ( w, [ QToolWindowWrapper, QToolWindowCustomWrapper ] ) or (
				not cast ( w, QSplitter ) and cast ( w.parentWidget (), [ QToolWindowWrapper, QToolWindowCustomWrapper ] ) )

	def isMainWrapper ( self, w ) -> bool:
		from QToolWindowManager.QToolWindowWrapper import QToolWindowWrapper
		from QToolWindowManager.QToolWindowCustomWrapper import QToolWindowCustomWrapper
		return self.isWrapper ( w ) and ( cast ( w, [ QToolWindowWrapper, QToolWindowCustomWrapper ] ) == self.mainWrapper or cast ( w.parentWidget (), [ QToolWindowWrapper, QToolWindowCustomWrapper ] ) == self.mainWrapper )

	def isFloatingWrapper ( self, w ) -> bool:
		if not self.isWrapper ( w ):
			return False
		from QToolWindowManager.QToolWindowWrapper import QToolWindowWrapper
		from QToolWindowManager.QToolWindowCustomWrapper import QToolWindowCustomWrapper
		if cast ( w, [ QToolWindowWrapper, QToolWindowCustomWrapper ] ) and cast ( w, [ QToolWindowWrapper, QToolWindowCustomWrapper ] ) != self.mainWrapper:
			return True
		return not cast ( w, QSplitter ) and cast ( w.parentWidget (),
		                                                        [ QToolWindowWrapper, QToolWindowCustomWrapper ] ) and cast ( w.parentWidget (), [ QToolWindowWrapper, QToolWindowCustomWrapper ] ) != self.mainWrapper

	def saveState ( self ):
		result = { }
		result[ "toolWindowManagerStateFormat" ] = 2
		if self.mainWrapper.getContents () and self.mainWrapper.getContents ().metaObject ():
			result[ "mainWrapper" ] = self.saveWrapperState ( self.mainWrapper )

		floatingWindowsData = [ ]
		for wrapper in self.wrappers:
			if wrapper.getWidget ().isWindow () and wrapper.getContents () and wrapper.getContents ().metaObject ():
				m = self.saveWrapperState ( wrapper )
				if len ( m ) != 0:
					floatingWindowsData.append ( m )

		result[ "floatingWindows" ] = floatingWindowsData
		return result

	def restoreState ( self, data ):
		if data is None:
			return
		stateFormat = data[ "toolWindowManagerStateFormat" ]
		if stateFormat != 1 and stateFormat != 2:
			qWarning ( "state format is not recognized" )
			return

		self.suspendLayoutNotifications ()
		self.mainWrapper.getWidget ().hide ()
		for wrapper in self.wrappers:
			wrapper.getWidget ().hide ()
		self.moveToolWindow ( self.toolWindows, None, QToolWindowAreaReference.Hidden )
		self.simplifyLayout ( True )
		self.mainWrapper.setContents ( None )
		self.restoreWrapperState ( data[ "mainWrapper" ], stateFormat, self.mainWrapper )
		for windowData in data[ "floatingWindows" ]:
			self.restoreWrapperState ( windowData, stateFormat )
		self.simplifyLayout ()
		for toolWindow in self.toolWindows:
			self.toolWindowVisibilityChanged.emit ( toolWindow, toolWindow.parentWidget () != None )
		self.resumeLayoutNotifications ()
		self.notifyLayoutChange ()

	def isAnyWindowActive ( self ) -> bool:
		for tool in self.toolWindows:
			if tool.isAnyWindowActive ():
				return True
		return False

	def updateDragPosition ( self ):
		if self.draggedWrapper == None:
			return

		# print ( "[QToolWindowManager] updateDragPosition" )

		self.draggedWrapper.getWidget ().raise_ ()

		hoveredWindow = windowBelow ( self.draggedWrapper.getWidget () )
		if hoveredWindow != None and hoveredWindow != self.draggedWrapper.window ():
			handlerWidget = hoveredWindow.childAt ( hoveredWindow.mapFromGlobal ( QCursor.pos () ) )
			if handlerWidget != None and not self.dragHandler.isHandlerWidget ( handlerWidget ):
				area = self.getClosestParentArea ( handlerWidget )
				if area != None and self.lastArea != area:
					self.dragHandler.switchedArea ( self.lastArea, area )
					self.lastArea = area
					delayTime = self.config.setdefault ( QTWM_RAISE_DELAY, 500 )
					self.raiseTimer.stop ()
					if delayTime > 0:
						self.raiseTimer.start ( delayTime )

		target = self.dragHandler.getTargetFromPosition ( self.lastArea )
		if self.lastArea != None and target.reference != QToolWindowAreaReference.Floating:
			if QToolWindowAreaReference.isOuter ( target.reference ):
				from QToolWindowManager.QToolWindowCustomWrapper import QToolWindowCustomWrapper
				previewArea = findClosestParent ( self.lastArea.getWidget (), [ QToolWindowWrapper, QToolWindowCustomWrapper ] )
				previewAreaContents = previewArea.getContents ()
				self.preview.setParent ( previewArea.getWidget () )
				self.preview.setGeometry (
					self.dragHandler.getRectFromCursorPos ( previewAreaContents, self.lastArea ).translated (
						previewAreaContents.pos () ) )
			else:
				previewArea = self.lastArea.getWidget ()
				self.preview.setParent ( previewArea )
				self.preview.setGeometry ( self.dragHandler.getRectFromCursorPos ( previewArea, self.lastArea ) )

			self.preview.show ()
		else:
			self.preview.hide ()

		self.updateTrackingTooltip.emit ( self.textForPosition ( target.reference ),
		                                  QCursor.pos () + self.config.setdefault ( QTWM_TOOLTIP_OFFSET, QPoint ( 1, 20 ) ) )

	def finishWrapperDrag ( self ):
		target = self.dragHandler.finishDrag ( self.draggedToolWindows, None, self.lastArea )
		self.lastArea = None

		wrapper = self.draggedWrapper
		self.draggedWrapper = None
		self.raiseTimer.stop ()
		self.preview.setParent ( None )
		self.preview.hide ()

		if wrapper is None:
			qWarning ( "[QToolWindowManager] finishWrapperDrag wrapper == None." )
			return

		# print ( "[QToolWindowManager] finishWrapperDrag %s %s" % ( self.draggedWrapper, self.draggedToolWindows ) )

		contents = wrapper.getContents ()
		toolWindows = []
		contentsWidgets = contents.findChildren ( QWidget )
		contentsWidgets.append ( contents )

		for w in contentsWidgets:
			area = cast ( w, [ QToolWindowArea, QToolWindowRollupBarArea ] )
			if area != None and self.ownsArea ( area ):
				toolWindows += area.toolWindows ()

		if target.reference != QToolWindowAreaReference.Floating:
			contents = wrapper.getContents ()
			wrapper.setContents ( None )

			if target.reference == QToolWindowAreaReference.Top or \
					target.reference == QToolWindowAreaReference.Bottom or \
					target.reference == QToolWindowAreaReference.Left or \
					target.reference == QToolWindowAreaReference.Right:
				self.splitArea ( self.getFurthestParentArea ( target.area.getWidget () ).getWidget (), target.reference,
				                 contents )
			elif target.reference == QToolWindowAreaReference.HSplitTop or \
					target.reference == QToolWindowAreaReference.HSplitBottom or \
					target.reference == QToolWindowAreaReference.VSplitLeft or \
					target.reference == QToolWindowAreaReference.VSplitRight:
				self.splitArea ( target.area.getWidget (), target.reference, contents )
			elif target.reference == QToolWindowAreaReference.Combine:
				self.moveToolWindowsTarget ( toolWindows, target )

			wrapper.getWidget ().close ()
			self.simplifyLayout ()

		if target.reference != QToolWindowAreaReference.Combine:
			for w in toolWindows:
				self.toolWindowVisibilityChanged.emit ( w, True )

		if target.reference != QToolWindowAreaReference.Floating:
			self.notifyLayoutChange ()

		self.updateTrackingTooltip.emit ( "", QPoint () )

	def finishWrapperResize ( self ):
		toolWindows = []
		contents = self.resizedWrapper.getContents ()
		contentsWidgets = contents.findChildren ( QWidget )
		contentsWidgets.append ( contents )

		for w in contentsWidgets:
			area = cast ( w, [ QToolWindowArea, QToolWindowRollupBarArea ] )
			if area and self.ownsArea ( area ):
				toolWindows.append ( area.toolWindows () )

		for w in toolWindows:
			self.toolWindowVisibilityChanged.emit ( w, True )

		self.resizedWrapper = None

	def saveWrapperState ( self, wrapper ):
		if wrapper.getContents () == None:
			qWarning ( "[QToolWindowManager] saveWrapperState Empty top level wrapper." )
			return { }

		result = { }
		result[ "geometry" ] = wrapper.getWidget ().saveGeometry ().toBase64 ()
		result[ "name" ] = wrapper.getWidget ().objectName ()

		obj = wrapper.getContents ()
		if cast ( obj, QSplitter ):
			result[ "splitter" ] = self.saveSplitterState ( obj )
			return result

		if isinstance ( obj, QToolWindowArea ):
			result[ "area" ] = obj.saveState ()
			return result

		qWarning ( "[QToolWindowManager] saveWrapperState Couldn't find valid child widget" )
		return { }

	def restoreWrapperState ( self, data, stateFormat, wrapper = None ):
		newContents = None

		if wrapper and data[ "name" ]:
			wrapper.getWidget ().setObjectName ( data[ "name" ] )

		if data[ "splitter" ]:
			newContents = self.restoreSplitterState ( data[ "splitter" ], stateFormat )
		elif data[ "area" ]:
			areaType = QTWMWrapperAreaType.watTabs
			if data[ "area" ][ "type" ] and data[ "area" ][ "type" ] == "rollup":
				areaType = QTWMWrapperAreaType.watRollups

			area = self.createArea ( areaType )
			area.restoreState ( data[ "area" ], stateFormat )
			if area.count () > 0:
				newContents = area.getWidget ()
			else:
				area.deleteLater ()

		if not wrapper:
			if newContents:
				wrapper = self.createWrapper ()
				if data[ "name" ]:
					wrapper.getWidget ().setObjectName ( data[ "name" ] )
			else:
				return None

		wrapper.setContents ( newContents )

		if stateFormat == 1:
			if data[ "geometry" ]:
				if not wrapper.getWidget ().restoreGeometry ( data[ "geometry" ] ):
					print ( "Failed to restore wrapper geometry" )
		elif stateFormat == 2:
			if data[ "geometry" ]:
				if not wrapper.getWidget ().restoreGeometry ( QByteArray.fromBase64 ( data[ "geometry" ] ) ):
					print ( "Failed to restore wrapper geometry" )
		else:
			print ( "Unknown state format" )

		if data[ "geometry" ]:
			wrapper.getWidget ().show ()

		return wrapper

	def saveSplitterState ( self, splitter ):
		result = { }
		result[ "state" ] = splitter.saveState ().toBase64 ()
		result[ "type" ] = "splitter"
		items = [ ]
		for i in range ( splitter.count () ):
			item = splitter.widget ( i )
			itemValue = { }
			area = item
			if area != None:
				itemValue = area.saveState ()
			else:
				childSplitter = item
				if childSplitter:
					itemValue = self.saveSplitterState ( childSplitter )
				else:
					qWarning ( "[QToolWindowManager] saveSplitterState Unknown splitter item" )
			items.append ( itemValue )
		result[ "items" ] = items
		return result

	def restoreSplitterState ( self, data, stateFormat ):
		if len ( data[ "items" ] ) < 2:
			print ( "Invalid splitter encountered" )
			if len ( data[ "items" ] ) == 0:
				return None

		splitter = self.createSplitter ()

		for itemData in data[ "items" ]:
			itemValue = itemData
			itemType = itemValue[ "type" ]
			if itemType == "splitter":
				w = self.restoreSplitterState ( itemValue, stateFormat )
				if w:
					splitter.addWidget ( w )
			elif itemType == "area":
				area = self.createArea ()
				area.restoreState ( itemValue, stateFormat )
				splitter.addWidget ( area.getWidget () )
			elif itemType == "rollup":
				area = self.createArea ()
				area.restoreState ( itemValue, stateFormat )
				splitter.addWidget ( area.getWidget () )
			else:
				print ( "Unknown item type" )

		if stateFormat == 1:
			if data[ "state" ]:
				if not splitter.restoreState ( data[ "state" ] ):
					print ( "Failed to restore splitter state" )
		elif stateFormat == 2:
			if data[ "state" ]:
				if not splitter.restoreState ( QByteArray.fromBase64 ( data[ "state" ] ) ):
					print ( "Failed to restore splitter state" )

		else:
			print ( "Unknown state format" )

		return splitter

	def resizeSplitter ( self, widget, sizes ):
		s = cast ( widget, QSplitter )
		if s == None:
			s = findClosestParent ( widget, QSplitter )
		if s == None:
			qWarning ( "Could not find a matching splitter!" )
			return

		scaleFactor = s.width () if s.orientation () == QtCore.Qt.Horizontal else s.height ()

		for i in range ( 0, len ( sizes ) ):
			sizes[ i ] *= scaleFactor

		s.setSizes ( sizes )

	def createArea ( self, areaType = QTWMWrapperAreaType.watTabs ):
		# qWarning ( "[QToolWindowManager] createArea %s" % areaType )
		a = self.factory.createArea ( self, None, areaType )
		self.lastArea = a
		self.areas.append ( a )
		return a

	def createWrapper ( self ):
		w = self.factory.createWrapper ( self )
		name = None
		while True:
			i = QtCore.qrand ()
			name = "wrapper#%s" % i
			for w2 in self.wrappers:
				if name == w2.getWidget ().objectName ():
					continue
			break
		w.getWidget ().setObjectName ( name )
		self.wrappers.append ( w )
		return w

	def getNotifyLock ( self, allowNotify = True ):
		return QTWMNotifyLock ( self, allowNotify )

	def hide ( self ):
		self.mainWrapper.getWidget ().hide ()

	def show ( self ):
		self.mainWrapper.getWidget ().show ()

	def clear ( self ):
		self.releaseToolWindows ( self.toolWindows, True )
		if len ( self.areas ) != 0:
			self.lastArea = self.areas[ 0 ]
		else:
			self.lastArea = None

	def bringAllToFront ( self ):
		# TODO: Win64 / OSX
		from QToolWindowManager.QToolWindowCustomWrapper import QToolWindowCustomWrapper
		list = qApp.topLevelWidgets ()
		for w in list:
			if w != None and not w.windowState ().testFlag ( QtCore.Qt.WindowMinimized ) and ( isinstance ( w, QToolWindowWrapper ) or isinstance ( w, QToolWindowCustomWrapper ) or isinstance ( w, QCustomWindowFrame ) ):
				w.show ()
				w.raise_ ()

	def bringToFront ( self, toolWindow ):
		area = self.areaOf ( toolWindow )
		if not area:
			return
		while area.indexOf ( toolWindow ) == -1:
			toolWindow = toolWindow.parentWidget ()
		area.setCurrentWidget ( toolWindow )

		window = area.getWidget ().window ()
		window.setWindowState ( window.windowState () & ~QtCore.Qt.WindowMinimized )
		window.show ()
		window.raise_ ()
		window.activateWindow ()

		toolWindow.setFocus ()

	def getToolPath ( self, toolWindow ):
		w = toolWindow
		result = ''
		pw = w.parentWidget ()
		while pw:
			if isinstance ( pw, QSplitter ):
				orientation = None
				if pw.orientation () == QtCore.Qt.Horizontal:
					orientation = 'h'
				else:
					orientation = 'v'
				result += "/%c/%d" % (orientation, pw.indexOf ( w ))
			elif isinstance ( pw, QToolWindowWrapper ):
				result += toolWindow.window ().objectName ()
				break
			w = pw
			pw = w.parentWidget ()
		return result

	def targetFromPath ( self, toolPath ) -> QToolWindowAreaTarget:
		for w in self.areas:
			if w.count () < 0 and self.getToolPath ( w.widget ( 0 ) ) == toolPath:
				return QToolWindowAreaTarget.createByArea ( w, QToolWindowAreaReference.Combine )
		return QToolWindowAreaTarget ( QToolWindowAreaReference.Floating )

	def eventFilter ( self, o, e ):
		"""Summary line.

		Args:
			o (QObject): Description of param1
			e (QEvent): Description of param2
	    """
		if o == qApp:
			if e.type () == QEvent.ApplicationActivate and (
					self.config.setdefault ( QTWM_WRAPPERS_ARE_CHILDREN, False )) and (
					self.config.setdefault ( QTWM_BRING_ALL_TO_FRONT, True )):
				self.bringAllToFront ()
			return False

		if e.type () == 16: # event Destroy
			w = cast ( o, QWidget )
			if not self.closingWindow and self.ownsToolWindow ( w ) and w.isVisible ():
				self.releaseToolWindow ( w, True )
				return False

		# if self.draggedWrapper and o == self.draggedWrapper:
		# 	if e.type () == QEvent.MouseMove:
		# 		qWarning ( "Manager eventFilter: send MouseMove" )
		# 		qApp.sendEvent ( self.draggedWrapper, e )

		# print ( "QToolWindowManager::eventFilter", type ( o ), EventTypes ().as_string ( e.type () ) )

		# if self.draggedWrapper and o == self.draggedWrapper:
		# 	if e.type () == QEvent.MouseButtonRelease:
		# 		qWarning ( f"Manager eventFilter: send MouseButtonRelease {self.draggedWrapper}" )

		# if e.type () == Qt.QEvent.Destroy:
		# 	if not self.closingWindow and self.ownsToolWindow ( w ) and w.isVisible ():
		# 		self.releaseToolWindow ( w, True )
		# 		return False

		return super ().eventFilter ( o, e )

	def event ( self, e ):
		if e.type () == QEvent.StyleChange:
			if self.parentWidget () and self.parentWidget ().styleSheet () != self.styleSheet ():
				self.setStyleSheet ( self.parentWidget ().styleSheet () )

		return super ().event ( e )

	def tryCloseToolWindow ( self, toolWindow ):
		self.closingWindow += 1
		result = True

		if not toolWindow.close ():
			qWarning ( "Widget could not be closed" )
			result = False

		self.closingWindow -= 1
		return result

	def createSplitter ( self ) -> QSplitter:
		return self.factory.createSplitter ( self )

	def splitArea ( self, area, reference, insertWidget = None ):
		from QToolWindowManager.QToolWindowWrapper import QToolWindowWrapper
		from QToolWindowManager.QToolWindowCustomWrapper import QToolWindowCustomWrapper

		residingWidget = insertWidget
		if residingWidget == None:
			residingWidget = self.createArea ().getWidget ()

		if not QToolWindowAreaReference.requiresSplit ( reference ):
			qWarning ( "Invalid reference for area split" )
			return None

		forceOuter = QToolWindowAreaReference.isOuter ( reference )
		reference = reference & 0x3
		parentSplitter = None
		targetWrapper = findClosestParent ( area, [ QToolWindowWrapper, QToolWindowCustomWrapper ] )
		if not forceOuter:
			parentSplitter = cast ( area.parentWidget (), QSplitter )
		if parentSplitter == None and targetWrapper == None:
			qWarning ( "Could not determine area parent" )
			return None
		useParentSplitter = False
		targetIndex = 0
		parentSizes = [ QSize () ]
		if parentSplitter != None:
			parentSizes = parentSplitter.sizes ()
			targetIndex += parentSplitter.indexOf ( area )
			useParentSplitter = parentSplitter.orientation () == QToolWindowAreaReference.splitOrientation ( reference )

		if useParentSplitter:
			origIndex = targetIndex
			targetIndex += reference & 0x1
			newSizes = self.dragHandler.getSplitSizes ( parentSizes[ origIndex ] )
			parentSizes[ origIndex ] = newSizes.oldSize
			parentSizes.insert ( targetIndex, newSizes.newSize )
			parentSplitter.insertWidget ( targetIndex, residingWidget )
			parentSplitter.setSizes ( parentSizes )
			return residingWidget

		splitter = self.createSplitter ()
		splitter.setOrientation ( QToolWindowAreaReference.splitOrientation ( reference ) )

		if forceOuter or area == targetWrapper.getContents ():
			firstChild = targetWrapper.getContents ()
			targetWrapper.setContents ( None )
			splitter.addWidget ( firstChild )
		else:
			area.hide ()
			area.setParent ( None )
			splitter.addWidget ( area )
			area.show ()

		splitter.insertWidget ( reference & 0x1, residingWidget )
		if parentSplitter != None:
			parentSplitter.insertWidget ( targetIndex, splitter )
			parentSplitter.setSizes ( parentSizes )
		else:
			targetWrapper.setContents ( splitter )

		sizes = [ ]
		baseSize = splitter.height () if splitter.orientation () == QtCore.Qt.Vertical else splitter.width ()
		newSizes = self.dragHandler.getSplitSizes ( baseSize )
		sizes.append ( newSizes.oldSize )
		sizes.insert ( reference & 0x1, newSizes.newSize )
		splitter.setSizes ( sizes )

		contentsWidgets = residingWidget.findChildren ( QWidget )
		for w in contentsWidgets:
			qApp.sendEvent ( w, QEvent ( QEvent.ParentChange ) )

		return residingWidget

	def getClosestParentArea ( self, widget ):
		area = findClosestParent ( widget, [ QToolWindowArea, QToolWindowRollupBarArea ] )
		while area != None and not self.ownsArea ( area ):
			area = findClosestParent ( area.getWidget ().parentWidget (), [ QToolWindowArea, QToolWindowRollupBarArea ] )

		# qWarning ( "[QToolWindowManager] getClosestParentArea %s is %s" % ( widget, area ) )

		return area

	def getFurthestParentArea ( self, widget ):
		area = findClosestParent ( widget, [ QToolWindowArea, QToolWindowRollupBarArea ] )
		previousArea = area
		while area != None and not self.ownsArea ( area ):
			area = findClosestParent ( area.getWidget ().parentWidget (), [ QToolWindowArea, QToolWindowRollupBarArea ] )

			if area == None:
				return previousArea
			else:
				previousArea = area

		# qWarning ( "getFurthestParentArea %s is %s" % ( widget, area ) )

		return area

	def textForPosition ( self, reference ):
		texts = [
			"Place at top of window",
			"Place at bottom of window",
			"Place on left side of window",
			"Place on right side of window",
			"Split horizontally, place above",
			"Split horizontally, place below",
			"Split vertically, place left",
			"Split vertically, place right",
			"Add to tab list",
			""
		]
		return texts[ reference ]

	def simplifyLayout ( self, clearMain = False ):
		from QToolWindowManager.QToolWindowWrapper import QToolWindowWrapper
		from QToolWindowManager.QToolWindowCustomWrapper import QToolWindowCustomWrapper

		self.suspendLayoutNotifications ()
		madeChanges = True # Some layout changes may require multiple iterations to fully simplify.

		while madeChanges:
			madeChanges = False
			areasToRemove = [ ]
			for area in self.areas:
				# remove empty areas (if this is only area in main wrapper, only remove it when we explicitly ask for that)
				if area.count () == 0 and ( clearMain or cast ( area.parent (), [ QToolWindowWrapper, QToolWindowCustomWrapper ] ) != self.mainWrapper ):
					areasToRemove.append ( area )
					madeChanges = True

				s = cast ( area.parentWidget (), QSplitter )
				while s != None and s.parentWidget () != None:
					sp_s = cast ( s.parentWidget (), QSplitter )
					sp_w = cast ( s.parentWidget (), [ QToolWindowWrapper, QToolWindowCustomWrapper ] )
					sw = findClosestParent ( s, [ QToolWindowWrapper, QToolWindowCustomWrapper ] )

					# If splitter only contains one object, replace the splitter with the contained object
					if s.count () == 1:
						if sp_s != None:
							index = sp_s.indexOf ( s )
							sizes = sp_s.sizes ()
							sp_s.insertWidget ( index, s.widget ( 0 ) )
							s.hide ()
							s.setParent ( None )
							s.deleteLater ()
							sp_s.setSizes ( sizes )
							madeChanges = True
						elif sp_w != None:
							sp_w.setContents ( s.widget ( 0 ) )
							s.setParent ( None )
							s.deleteLater ()
							madeChanges = True
						else:
							qWarning ( "Unexpected splitter parent" )

					# If splitter's parent is also a splitter, and both have same orientation, replace splitter with contents
					elif sp_s != None and s.orientation () == sp_s.orientation ():
						index = sp_s.indexOf ( s )
						newSizes = sp_s.sizes ()
						oldSizes = s.sizes ()
						newSum = newSizes[ index ]
						oldSum = 0
						for i in oldSizes:
							oldSum += i
						for i in range ( len ( oldSizes ) - 1, -1, -1 ):
							sp_s.insertWidget ( index, s.widget ( i ) )
							newSizes.insert ( index, oldSizes[ i ] / oldSum * newSum )
						s.hide ()
						s.setParent ( None )
						s.deleteLater ()
						sp_s.setSizes ( newSizes )
						madeChanges = True

					s = sp_s

			for area in areasToRemove:
				area.hide ()
				aw = cast ( area.parentWidget (), [ QToolWindowWrapper, QToolWindowCustomWrapper ] )
				if aw != None:
					aw.setContents ( None )

				area.setParent ( None )
				self.areas.remove ( area )
				area.deleteLater ()

		wrappersToRemove = [ ]
		for wrapper in self.wrappers:
			if wrapper.getWidget ().isWindow () and wrapper.getContents () == None:
				wrappersToRemove.append ( wrapper )

		for wrapper in wrappersToRemove:
			self.wrappers.remove ( wrapper )
			wrapper.hide ()
			wrapper.deferDeletion ()

		for area in self.areas:
			area.adjustDragVisuals ()

		self.resumeLayoutNotifications ()
		self.notifyLayoutChange ()

	def createDragHandler ( self ):
		if self.dragHandler:
			del self.dragHandler
		return self.factory.createDragHandler ( self )

	def suspendLayoutNotifications ( self ):
		self.layoutChangeNotifyLocks += 1

	def resumeLayoutNotifications ( self ):
		if self.layoutChangeNotifyLocks > 0:
			self.layoutChangeNotifyLocks -= 1

	def notifyLayoutChange ( self ):
		if self.layoutChangeNotifyLocks == 0:
			QtCore.QMetaObject.invokeMethod ( self, 'layoutChanged', QtCore.Qt.QueuedConnection )

	def insertToToolTypes ( self, tool, toolType ):
		if tool not in self.toolWindowsTypes:
			self.toolWindowsTypes[ tool ] = toolType

	def raiseCurrentArea ( self ):
		if self.lastArea:
			w = self.lastArea.getWidget ()
			while w.parentWidget () and not w.isWindow ():
				w = w.parentWidget ()
			w.raise_ ()
			self.updateDragPosition ()
		self.raiseTimer.stop ()
Beispiel #4
0
class ToolWindowManager ( QWidget ):
	'''docstring for ToolWindowManager'''
	#! The area tool windows has been added to most recently.
	LastUsedArea = 1
	#! New area in a detached window.
	NewFloatingArea = 2
	#! Area inside the manager widget (only available when there is no tool windows in it).
	EmptySpace = 3
	#! Tool window is hidden.
	NoArea = 4
	#! Existing area specified in AreaReference argument.
	AddTo = 5
	#! New area to the left of the area specified in AreaReference argument.
	LeftOf = 6
	#! New area to the right of the area specified in AreaReference argument.
	RightOf = 7
	#! New area to the top of the area specified in AreaReference argument.
	TopOf = 8
	#! New area to the bottom of the area specified in AreaReference argument.
	BottomOf = 9

	#signal
	toolWindowVisibilityChanged = pyqtSignal ( QWidget, bool )

	def __init__ ( self, parent = None ):
		super ( ToolWindowManager, self ).__init__ ( parent )
		self.lastUsedArea = None
		self.suggestions = []
		self.wrappers = []
		self.areas = []
		self.toolWindowList = []
		self.draggedToolWindows = []
		#----
		self.borderSensitivity = 12
		testSplitter = QSplitter ()
		self.rubberBandLineWidth = testSplitter.handleWidth ()
		self.dragIndicator = QtWidgets.QLabel ( None, Qt.ToolTip) # 使用QLabel来显示拖拽的提示
		self.dragIndicator.setAttribute ( Qt.WA_ShowWithoutActivating)
		mainLayout = QtWidgets.QVBoxLayout ( self )
		mainLayout.setContentsMargins ( 0, 0, 0, 0 )
		
		wrapper = ToolWindowManagerWrapper ( self )
		wrapper.setWindowFlags ( wrapper.windowFlags () & ~Qt.Tool )
		mainLayout.addWidget ( wrapper )

		self.dropSuggestionSwitchTimer = QtCore.QTimer ( self )
		self.dropSuggestionSwitchTimer.timeout.connect ( self.showNextDropSuggestion )
		self.dropSuggestionSwitchTimer.setInterval ( 800 )
		self.dropCurrentSuggestionIndex = 0

		palette = QtGui.QPalette ()
		color = QtGui.QColor ( Qt.blue )
		color.setAlpha ( 80 )
		palette.setBrush ( QtGui.QPalette.Highlight, QtGui.QBrush ( color ) )
		
		self.rectRubberBand = QRubberBand ( QRubberBand.Rectangle, self )
		self.lineRubberBand = QRubberBand ( QRubberBand.Line, self )
		self.rectRubberBand.setPalette ( palette )
		self.lineRubberBand.setPalette ( palette )

	def hideToolWindow ( self, toolWindow ):
		self.moveToolWindow ( toolWindow, ToolWindowManager.NoArea )

	def toolWindows ( self ):
		return self.toolWindowList

	def hasToolWindow ( self, toolWindow ):
		return toolWindow in self.toolWindowList

	def addToolWindow ( self, toolWindow, area ):
		return self.addToolWindows ( [toolWindow], area )

	def addToolWindows ( self, toolWindows, area ):
		for toolWindow in toolWindows:
			if self.hasToolWindow ( toolWindow ): continue
			toolWindow.hide ()
			toolWindow.setParent ( None )
			self.toolWindowList.append ( toolWindow )
		self.moveToolWindows ( toolWindows, area )

	def areaOf ( self, toolWindow ) -> ToolWindowManagerArea:
		'''
		查找toolWindow所属的area
		'''
		return findClosestParent ( toolWindow, ToolWindowManagerArea )

	def moveToolWindow ( self, toolWindow, area ):
		self.moveToolWindows ( [ toolWindow ], area )

	def moveToolWindows ( self, toolWindows, area ):
		if type ( area ) == int:
			area = AreaReference ( area )

		for toolWindow in toolWindows:
			if not self.hasToolWindow ( toolWindow ): return
			if toolWindow.parentWidget ():
				self.releaseToolWindow ( toolWindow )

		areaType = area.type

		if areaType == ToolWindowManager.LastUsedArea and not self.lastUsedArea:
			foundArea = self.findChild ( ToolWindowManagerArea )
			if foundArea:
				area = AreaReference ( ToolWindowManager.AddTo, foundArea )
			else:
				area = ToolWindowManager.EmptySpace

		if areaType == ToolWindowManager.NoArea:
			#do nothing
			# qWarning ( '[moveToolWindows] areaType = NoArea' )
			pass

		elif areaType == ToolWindowManager.NewFloatingArea:
			area = self.createArea ()
			area.addToolWindows ( toolWindows )
			wrapper = ToolWindowManagerWrapper ( self )
			wrapper.layout ().addWidget ( area )
			wrapper.move ( QCursor.pos () )
			wrapper.show ()

			# qWarning ( '[moveToolWindows] areaType = NewFloatingArea' )

		elif areaType == ToolWindowManager.AddTo:
			area.area ().addToolWindows ( toolWindows )

			# qWarning ( '[moveToolWindows] areaType = AddTo' )

		elif areaType in ( ToolWindowManager.LeftOf, ToolWindowManager.RightOf, ToolWindowManager.TopOf, ToolWindowManager.BottomOf ):
			parentSplitter = cast ( area.widget.parentWidget (), QSplitter )
			wrapper = cast ( area.widget.parentWidget (), ToolWindowManagerWrapper )
			if not ( parentSplitter or wrapper ):
				qWarning ( 'unknown parent type' )
				return
			# import pudb; pu.db
			useParentSplitter = False
			indexInParentSplitter = 0
			if parentSplitter:
				indexInParentSplitter = parentSplitter.indexOf ( area.widget )
				if parentSplitter.orientation () == Qt.Vertical:
					useParentSplitter = areaType in ( ToolWindowManager.TopOf, ToolWindowManager.BottomOf )
				else:
					useParentSplitter = areaType in ( ToolWindowManager.LeftOf, ToolWindowManager.RightOf )

			if useParentSplitter:
				if areaType in ( ToolWindowManager.BottomOf , ToolWindowManager.RightOf ):
					indexInParentSplitter += 1
				newArea = self.createArea ()
				newArea.addToolWindows ( toolWindows )
				parentSplitter.insertWidget ( indexInParentSplitter, newArea )

			else:
				area.widget.hide ()
				area.widget.setParent ( None )
				splitter = self.createSplitter ()
				if areaType in ( ToolWindowManager.TopOf, ToolWindowManager.BottomOf ):
					splitter.setOrientation (Qt.Vertical)
				else:
					splitter.setOrientation (Qt.Horizontal)

				splitter.addWidget ( area.widget )
				area.widget.show ()
				newArea = self.createArea ()
				if areaType in ( ToolWindowManager.TopOf, ToolWindowManager.LeftOf ):
					splitter.insertWidget ( 0, newArea )
				else:
					splitter.addWidget ( newArea )

				if parentSplitter:
					parentSplitter.insertWidget ( indexInParentSplitter, splitter )
				else:
					wrapper.layout ().addWidget ( splitter )
				newArea.addToolWindows ( toolWindows )

			# qWarning ( '[moveToolWindows] areaType = LeftOf/RightOf/TopOf/BottomOf' )

		elif areaType == ToolWindowManager.EmptySpace:
			wrapper = self.findChild ( ToolWindowManagerWrapper )
			if wrapper.isOccupied ():
				self.lastUsedArea.addToolWindows ( toolWindows )
			else:
				newArea = self.createArea ()
				wrapper.layout ().addWidget ( newArea )
				newArea.addToolWindows ( toolWindows )

			# qWarning ( '[moveToolWindows] areaType = EmptySpace' )

		elif areaType == ToolWindowManager.LastUsedArea:
			self.lastUsedArea.addToolWindows ( toolWindows )

			# qWarning ( '[moveToolWindows] areaType = areaType == ToolWindowManager.LastUsedArea' )

		else:
			qWarning ( 'invalid type' )

		self.simplifyLayout ()
		for toolWindow in toolWindows:
			self.toolWindowVisibilityChanged.emit ( toolWindow, toolWindow.parent () != None )

	def removeToolWindow ( self, toolWindow ):
		if toolWindow not in self.toolWindowList:
			qWarning ( 'unknown tool window' )
			return
		self.moveToolWindow ( toolWindow, ToolWindowManager.NoArea )
		self.toolWindowList.removeOne (toolWindow)

	def setSuggestionSwitchInterval ( self, msec ):
		self.dropSuggestionSwitchTimer.setInterval (msec)

	def suggestionSwitchInterval ( self ):
		return self.dropSuggestionSwitchTimer.interval ()

	def setBorderSensitivity ( self, pixels ):
		self.borderSensitivity = pixels

	def setRubberBandLineWidth ( self, pixels ):
		self.rubberBandLineWidth = pixels

	def saveState ( self ):
		result = {}
		result[ 'toolWindowManagerStateFormat' ] = 1
		mainWrapper = self.findChild ( ToolWindowManagerWrapper )
		if mainWrapper == None:
			qWarning ( 'can not find main wrapper' )
			return {}

		result[ 'mainWrapper' ] = mainWrapper.saveState ()
		floatingWindowsData = []
		for wrapper in self.wrappers:
			if not wrapper.isWindow (): continue
			floatingWindowsData.append ( wrapper.saveState () )
		result[ 'floatingWindows' ] = floatingWindowsData
		return result

	def restoreState ( self, data ):
		if not isinstance ( data, dict ): return
		if data[ 'toolWindowManagerStateFormat' ] != 1:
			qWarning ( 'state format is not recognized' )
			return
		self.moveToolWindows ( self.toolWindowList, ToolWindowManager.NoArea )
		mainWrapper = self.findChild ( ToolWindowManagerWrapper )
		if not mainWrapper:
			qWarning ( 'can not find main wrapper' )

		mainWrapper.restoreState ( data['mainWrapper'] )
		for windowData in data['floatingWindows']:
			wrapper = ToolWindowManagerWrapper ( self )
			wrapper.restoreState ( windowData )
			wrapper.show ()

		self.simplifyLayout ()
		for toolWindow in self.toolWindowList:
			self.toolWindowVisibilityChanged.emit ( toolWindow, toolWindow.parentWidget () != None )

	def createArea ( self ) -> ToolWindowManagerArea:
		'''
		创建area并监听area的tabClose事件
		'''
		area = ToolWindowManagerArea ( self, None )
		area.tabCloseRequested.connect ( self.tabCloseRequested )
		return area

	def handleNoSuggestions ( self ):
		self.rectRubberBand.hide ()
		self.lineRubberBand.hide ()
		self.lineRubberBand.setParent (self)
		self.rectRubberBand.setParent (self)
		self.suggestions = []
		self.dropCurrentSuggestionIndex = 0
		if self.dropSuggestionSwitchTimer.isActive ():
			self.dropSuggestionSwitchTimer.stop ()

	def releaseToolWindow ( self, toolWindow ):
		previousTabWidget = findClosestParent ( toolWindow, ToolWindowManagerArea )
		if not previousTabWidget:
			qWarning ( 'cannot find tab widget for tool window' )
			return
		previousTabWidget.removeTab ( previousTabWidget.indexOf (toolWindow) )
		toolWindow.hide ()
		toolWindow.setParent ( None )

	def removeArea ( self, area ):
		area.manager = None
		area.deleteLater ()

	def removeWrapper ( self, wrapper ):
		wrapper.deleteLater ()
		self.wrappers.remove ( wrapper )

	def simplifyLayout ( self ):
		newAreas = []
		currentAreas = self.areas
		for area in currentAreas:
			if area.parentWidget () is None:
				if area.count () == 0:
					if area == self.lastUsedArea: self.lastUsedArea = None
					self.removeArea ( area )
				continue

			splitter = cast ( area.parentWidget (), QSplitter )
			validSplitter   = None # least top level splitter that should remain
			invalidSplitter = None #most top level splitter that should be deleted
			while ( splitter ):
				if splitter.count () > 1:
					validSplitter = splitter
					break
				else:
					invalidSplitter = splitter
					splitter = cast ( splitter.parentWidget (), QSplitter )
			if not validSplitter:
				wrapper = findClosestParent ( area, ToolWindowManagerWrapper )
				if not wrapper:
					qWarning ( 'can not find wrapper' )
					print ( findClosestParent ( area, ToolWindowManagerWrapper ) )
					print ( type ( area.parentWidget () ) == ToolWindowManagerWrapper )
					return
				if area.count () == 0 and wrapper.isWindow ():
					wrapper.hide ()
					wrapper.setParent ( None )
					# can not deleteLater immediately (strange MacOS bug)
					self.removeWrapper ( wrapper )
					
				elif area.parent () != wrapper:
					wrapper.layout ().addWidget ( area )

			else:
				if area.count () > 0:
					if validSplitter and area.parent () != validSplitter:
						index = validSplitter.indexOf ( invalidSplitter )
						validSplitter.insertWidget ( index, area )

			if not invalidSplitter is None:
				invalidSplitter.hide ()
				invalidSplitter.setParent ( None )
				invalidSplitter.deleteLater ()

			if area.count () == 0:
				area.hide ()
				area.setParent ( None )
				if area == self.lastUsedArea: self.lastUsedArea = None
				self.removeArea ( area )
				continue

			newAreas.append ( area )
		#keep
		self.areas = newAreas

	def dragInProgress ( self ):
		'''
		指示当前是否正处于拖拽状态
		'''
		return len ( self.draggedToolWindows ) > 0

	def startDrag ( self, toolWindows ):
		'''
		开始拖拽,在指针位置显示拖拽toolWindows的简略快照
		'''
		if self.dragInProgress ():
			qWarning ( 'ToolWindowManager::execDrag: drag is already in progress' )
			return
		if not toolWindows: return
		self.draggedToolWindows = toolWindows
		self.dragIndicator.setPixmap ( self.generateDragPixmap ( toolWindows ) )
		self.updateDragPosition ()
		self.dragIndicator.show ()

	def saveSplitterState ( self, splitter ):
		result = {}
		result['state'] = splitter.saveState ()
		result['type'] = 'splitter'
		
		items = []
		for i in range ( splitter.count () ):
			item = splitter.widget ( i )
			area = cast ( item, ToolWindowManagerArea )
			if area:
				items.append ( area.saveState () )
			else:
				childSplitter = cast ( item, QSplitter )
				if childSplitter:
					items.append ( self. saveSplitterState ( childSplitter ) )
				else:
					qWarning ( 'unknown splitter item' )
		result[ 'items' ] = items
		return result

	def restoreSplitterState ( self, data ):
		if len ( data[ 'items' ] )< 2:
			qWarning ( 'invalid splitter encountered' )

		splitter = self.createSplitter ()

		for itemData in data[ 'items' ]:
			itemType = itemData['type']
			if itemType == 'splitter':
				splitter.addWidget ( self.restoreSplitterState ( itemData ) )
			elif itemType == 'area':
				area = self.createArea ()
				area.restoreState ( itemData )
				splitter.addWidget ( area )
			else:
				qWarning ( 'unknown item type' )
		splitter.restoreState ( data['state'] )
		return splitter

	def generateDragPixmap ( self, toolWindows ):
		'''
		生成一个QTabBar的快照
		'''
		widget = QTabBar ()
		widget.setDocumentMode (True)
		for toolWindow in toolWindows:
			widget.addTab (toolWindow.windowIcon (), toolWindow.windowTitle ())
		#if QT_VERSION >= 0x050000 # Qt5
			return widget.grab ()
		#else #Qt4
		# return QtGui.QPixmap.grabWidget ( widget )
		#endif

	def showNextDropSuggestion ( self ):
		if len ( self.suggestions ) == 0:
			qWarning ( 'showNextDropSuggestion called but no suggestions' )
			return

		self.dropCurrentSuggestionIndex += 1
		if self.dropCurrentSuggestionIndex >= len ( self.suggestions ):
			self.dropCurrentSuggestionIndex = 0
		
		suggestion = self.suggestions[ self.dropCurrentSuggestionIndex ]
		if suggestion.type in ( ToolWindowManager.AddTo , ToolWindowManager.EmptySpace ):
			if suggestion.type == ToolWindowManager.EmptySpace:
				widget = self.findChild ( ToolWindowManagerWrapper )
			else:
				widget = suggestion.widget
			
			if widget.window () == self.window ():
				placeHolderParent = self
			else:
				placeHolderParent = widget.window ()

			placeHolderGeometry = widget.rect ()
			placeHolderGeometry.moveTopLeft (
				widget.mapTo ( placeHolderParent, placeHolderGeometry.topLeft () )
			)
			self.rectRubberBand.setGeometry ( placeHolderGeometry )
			self.rectRubberBand.setParent ( placeHolderParent )
			self.rectRubberBand.show ()
			self.lineRubberBand.hide ()

		elif suggestion.type in (
			ToolWindowManager.LeftOf , ToolWindowManager.RightOf,
			ToolWindowManager.TopOf , ToolWindowManager.BottomOf ):
			if suggestion.widget.window () == self.window ():
				placeHolderParent = self
			else:
				placeHolderParent = suggestion.widget.window ()

			placeHolderGeometry = self.sidePlaceHolderRect ( suggestion.widget, suggestion.type )
			placeHolderGeometry.moveTopLeft (
				suggestion.widget.mapTo ( placeHolderParent, placeHolderGeometry.topLeft () )
			)
			self.lineRubberBand.setGeometry (placeHolderGeometry)
			self.lineRubberBand.setParent (placeHolderParent)
			self.lineRubberBand.show ()
			self.rectRubberBand.hide ()

		else:
			qWarning ( 'unsupported suggestion type' )

	def findSuggestions ( self, wrapper ):
		self.suggestions = []
		self.dropCurrentSuggestionIndex = -1
		globalPos  = QCursor.pos ()
		candidates = []
		for splitter in wrapper.findChildren ( QSplitter ):
			candidates.append ( splitter )

		for area in self.areas:
			if area.window () == wrapper.window ():
				candidates.append ( area )

		for widget in candidates:
			splitter = cast ( widget, QSplitter )
			area = cast ( widget, ToolWindowManagerArea )
			if not ( splitter or area ):
				qWarning ( 'unexpected widget type' )
				continue

			parentSplitter = cast ( widget.parentWidget (), QSplitter )
			lastInSplitter = parentSplitter and \
					parentSplitter.indexOf (widget) == parentSplitter.count () - 1

			allowedSides = []
			if not splitter or splitter.orientation () == Qt.Vertical:
				allowedSides.append ( ToolWindowManager.LeftOf )

			if not splitter or splitter.orientation () == Qt.Horizontal:
				allowedSides.append ( ToolWindowManager.TopOf )

			if not parentSplitter or parentSplitter.orientation () == Qt.Vertical or lastInSplitter:
				if not splitter or splitter.orientation () == Qt.Vertical:
					allowedSides.append ( ToolWindowManager.RightOf )

			if not parentSplitter or parentSplitter.orientation () == Qt.Horizontal or lastInSplitter:
				if not splitter or splitter.orientation () == Qt.Horizontal:
					allowedSides.append ( ToolWindowManager.BottomOf )
			for side in allowedSides:
				rect = self.sideSensitiveArea ( widget, side )
				pos  = widget.mapFromGlobal ( globalPos )
				if rect.contains ( pos ):
					self.suggestions.append ( AreaReference ( side, widget ) )
			if area:
				rect = area.rect ()
				pos  = area.mapFromGlobal ( globalPos )
				if rect.contains ( pos ):
					self.suggestions.append ( AreaReference ( ToolWindowManager.AddTo, area ) )

		#end of for
		if not candidates:
			self.suggestions.append ( AreaReference ( ToolWindowManager.EmptySpace ) )

		if len ( self.suggestions ) == 0:
			self.handleNoSuggestions ()
		else:
			self.showNextDropSuggestion ()

	def sideSensitiveArea ( self, widget, side ):
		widgetRect = widget.rect ()
		if side == ToolWindowManager.TopOf:
			return QRect (QPoint (widgetRect.left (), widgetRect.top () - self.borderSensitivity),
									 QSize (widgetRect.width (), self.borderSensitivity * 2))
		elif side == ToolWindowManager.LeftOf:
			return QRect (QPoint (widgetRect.left () - self.borderSensitivity, widgetRect.top ()),
									 QSize (self.borderSensitivity * 2, widgetRect.height ()))

		elif side == ToolWindowManager.BottomOf:
			return QRect (QPoint (widgetRect.left (), widgetRect.top () + widgetRect.height () - self.borderSensitivity),
									 QSize (widgetRect.width (), self.borderSensitivity * 2))
		elif side == ToolWindowManager.RightOf:
			return QRect (QPoint (widgetRect.left () + widgetRect.width () - self.borderSensitivity, widgetRect.top ()),
									 QSize (self.borderSensitivity * 2, widgetRect.height ()))
		else:
			qWarning ( 'invalid side' )
			return QRect ()

	def sidePlaceHolderRect ( self, widget, side ):
		widgetRect = widget.rect ()
		parentSplitter = cast ( widget.parentWidget (), QSplitter )
		if parentSplitter and parentSplitter.indexOf (widget) > 0:
			delta = parentSplitter.handleWidth () / 2 + self.rubberBandLineWidth / 2
			if side == ToolWindowManager.TopOf and parentSplitter.orientation () == Qt.Vertical:
				return QRect (QPoint ( widgetRect.left (), widgetRect.top () - int (delta) ),
										 QSize ( widgetRect.width (), self.rubberBandLineWidth ) )
			elif side == ToolWindowManager.LeftOf and parentSplitter.orientation () == Qt.Horizontal:
				return QRect (QPoint (widgetRect.left () - int (delta), widgetRect.top ()),
										 QSize (self.rubberBandLineWidth, widgetRect.height ()))

		if side == ToolWindowManager.TopOf:
			return QRect (QPoint (widgetRect.left (), widgetRect.top ()),
									 QSize (widgetRect.width (), self.rubberBandLineWidth))
		elif side == ToolWindowManager.LeftOf:
			return QRect (QPoint (widgetRect.left (), widgetRect.top ()),
									 QSize (self.rubberBandLineWidth, widgetRect.height ()))
		elif side == ToolWindowManager.BottomOf:
			return QRect (QPoint (widgetRect.left (), widgetRect.top () + widgetRect.height () - self.rubberBandLineWidth),
									 QSize (widgetRect.width (), self.rubberBandLineWidth))
		elif side == ToolWindowManager.RightOf:
			return QRect (QPoint (widgetRect.left () + widgetRect.width () - self.rubberBandLineWidth, widgetRect.top ()),
									 QSize (self.rubberBandLineWidth, widgetRect.height ()))
		else:
			qWarning ( 'invalid side' )
			return QRect ()

	def updateDragPosition ( self ):
		if not self.dragInProgress (): return
		if not QApplication.mouseButtons () & Qt.LeftButton :
			self.finishDrag ()
			return

		pos = QCursor.pos ()
		self.dragIndicator.move ( pos + QPoint (1, 1) )
		foundWrapper = False
		window = QApplication.topLevelAt ( pos )
		for wrapper in self.wrappers:
			if wrapper.window () == window:
				if wrapper.rect ().contains ( wrapper.mapFromGlobal (pos) ):
					self.findSuggestions ( wrapper )
					if len ( self.suggestions ) > 0:
						#starting or restarting timer
						if self.dropSuggestionSwitchTimer.isActive ():
							self.dropSuggestionSwitchTimer.stop ()
						self.dropSuggestionSwitchTimer.start ()
						foundWrapper = True
				break
		if not foundWrapper:
			self.handleNoSuggestions ()

	def finishDrag ( self ):
		if not self.dragInProgress ():
			qWarning ( 'unexpected finishDrag' )
			return

		# 如果suggestions数组中没有建议位置,则创建一个Wrapper窗口
		# 反之,则移动toolWindow到建议位置
		if len ( self.suggestions ) == 0:
			self.moveToolWindows ( self.draggedToolWindows, ToolWindowManager.NewFloatingArea )
		else:
			if self.dropCurrentSuggestionIndex >= len ( self.suggestions ):
				qWarning ( 'invalid self.dropCurrentSuggestionIndex' )
				return
			suggestion = self.suggestions[ self.dropCurrentSuggestionIndex ]
			self.handleNoSuggestions ()
			self.moveToolWindows ( self.draggedToolWindows, suggestion )

		self.dragIndicator.hide ()
		self.draggedToolWindows = []

	def tabCloseRequested ( self, index ):
		'''
		处理area控件的子tab关闭事件
		'''
		if not isinstance ( self.sender (), ToolWindowManagerArea ):
			qWarning ( 'sender is not a ToolWindowManagerArea' )
			return
		area = self.sender ()
		toolWindow = area.widget ( index )
		if not self.hasToolWindow ( toolWindow ):
			qWarning ( 'unknown tab in tab widget' )
			return
		self.hideToolWindow ( toolWindow )

	def createSplitter ( self ):
		splitter = QSplitter ()
		splitter.setChildrenCollapsible ( False )
		return splitter