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
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 ()
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