import os, json, platform, uuid
import xml.etree.ElementTree as ET
from PySide6.QtCore import Qt, QSize
from PySide6.QtGui import QAction
from editor.common.pyqtads import CDockManager, CDockWidget, DockWidgetArea
from editor.common.logger import warn, error
from editor.common.icon_cache import getThemeIcon
from editor.common.util import getIde, isParentOfWidget
from editor.widgets.misc.toast import notifyToast
####################### INTERNALS #######################
_dockManager = None
_viewRegistry = {}
_dockAreaTable = {
'left' : DockWidgetArea.LeftDockWidgetArea,
'right' : DockWidgetArea.RightDockWidgetArea,
'top' : DockWidgetArea.TopDockWidgetArea,
'bottom' : DockWidgetArea.BottomDockWidgetArea,
'center' : DockWidgetArea.CenterDockWidgetArea,
}
####################### INTERFACE #######################
[docs]class DockView(CDockWidget):
def __init__(self, parent, **data):
cls = self.__class__.__name__
name = data['name' ] if 'name' in data else cls
icon = data['icon' ] if 'icon' in data else None
title = data['title' ] if 'title' in data else name
tooltip = data['tooltip' ] if 'tooltip' in data else None
keepAlive = data['keepAlive'] if 'keepAlive' in data else False
super().__init__(title, parent)
self.guid = uuid.uuid1().hex
self.setObjectName(f'{name}::{self.guid}')
if icon: self.setIcon(getThemeIcon(icon))
self.setFeature(CDockWidget.DockWidgetForceCloseWithArea, True)
self.setFeature(CDockWidget.DockWidgetDeleteOnClose, not keepAlive)
self.setTabToolTip(tooltip)
self.setupTitleActions()
[docs] def createTitleAction(self, icon, func = None, shortcut = False, checkable = False):
action = QAction()
action.setAutoRepeat(False)
action.setToolTip('foo')
if func: action.triggered.connect(func)
if icon: action.setIcon(getThemeIcon(icon))
if shortcut:
action.setShortcut(shortcut)
action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
if checkable:
action.setCheckable(True)
action.setChecked(True)
self.titleActions.append(action)
[docs] def setupTitleActions(self):
self.titleActions = []
self.createTitleAction('menu_d.png')
self.setTitleBarActions( self.titleActions )
[docs] def showNotification(self, msg):
notifyToast(self, msg, 1500)
[docs] def addIntoEditor(self, area = 'center', anchor = None):
anchor = anchor or focusedDockView()
if anchor:
_dockManager.addDockWidget(_dockAreaTable[area], self, anchor.dockAreaWidget())
else:
_dockManager.addDockWidget(_dockAreaTable[area], self)
[docs] def addIntoEditorAsFloating(self):
_dockManager.addDockWidgetFloating(self)
[docs] def minimumSizeHint(self):
return QSize(150, 100)
[docs] def onLoadLayout(self):
# print('dump data: ', self.serializedData())
pass
####################### REGISTERS #######################
[docs]def registerDockView(cls, name, icon = None, **data):
if name in _viewRegistry: warn(f'editor view \'{name}\' has registered!')
data['icon' ] = icon
data['name' ] = name
data['class'] = cls
_viewRegistry[name] = data
[docs]def dockView(name, icon = None, **data):
def wrapper(cls):
registerDockView(cls, name, icon, **data)
return cls
return wrapper
registerDockView(DockView, 'Dummy')
######################### API ##########################
[docs]def findDockView(name):
dockMap = _dockManager.dockWidgetsMap()
for k in dockMap:
if k.startswith(name + '::'):
return dockMap[k]
[docs]def findDockViewList(name):
dockMap = _dockManager.dockWidgetsMap()
list = []
for k in dockMap:
if k.startswith(name + '::'):
list.append(dockMap[k])
return list
[docs]def createDockView(name):
data = _viewRegistry[name]
cls = data['class']
return cls(_dockManager, **data)
[docs]def affirmDockView(name):
dock = findDockView(name)
if not dock: dock = createDockView(name)
return dock
[docs]def focusedDockView():
return _dockManager.focusedDockWidget()
[docs]def setDockViewFocused(name, focused):
dock = findDockView(name)
_dockManager.setDockWidgetFocused(focused)
dock.setAsCurrentTab()
[docs]def closeFocusedDockView():
focused = _dockManager.focusedDockWidget()
if focused: focused.closeDockWidget()
[docs]def setDockSplitterSizes(dockArea, sizes):
_dockManager.setSplitterSizes(dockArea, sizes)
[docs]def createDockManager(mainWin):
CDockManager.setConfigFlag(CDockManager.RetainTabSizeWhenCloseButtonHidden, True)
CDockManager.setConfigFlag(CDockManager.FloatingContainerHasWidgetIcon, False)
CDockManager.setConfigFlag(CDockManager.FloatingContainerHasWidgetTitle, False)
CDockManager.setConfigFlag(CDockManager.OpaqueSplitterResize, True)
CDockManager.setConfigFlag(CDockManager.TabCloseButtonIsToolButton, False)
CDockManager.setConfigFlag(CDockManager.DockAreaHasUndockButton, False)
CDockManager.setConfigFlag(CDockManager.DockAreaHasCloseButton, False)
CDockManager.setConfigFlag(CDockManager.DockAreaHasTabsMenuButton, False)
CDockManager.setConfigFlag(CDockManager.ActiveTabHasCloseButton, False)
CDockManager.setConfigFlag(CDockManager.XmlAutoFormattingEnabled, True)
CDockManager.setConfigFlag(CDockManager.XmlCompressionEnabled, False)
CDockManager.setConfigFlag(CDockManager.AlwaysShowTabs, True)
CDockManager.setConfigFlag(CDockManager.FocusHighlighting, True)
CDockManager.setConfigFlag(CDockManager.MiddleMouseButtonClosesTab, True)
CDockManager.setConfigFlag(CDockManager.DragPreviewShowsContentPixmap, False)
if platform.system() == 'Darwin':
CDockManager.setConfigFlag(CDockManager.AllMenusHaveCustomStyle, True)
CDockManager.setAutoHideConfigFlag(CDockManager.AutoHideFeatureEnabled, True)
CDockManager.setAutoHideConfigFlag(CDockManager.DockAreaHasAutoHideButton, True)
CDockManager.setAutoHideConfigFlag(CDockManager.AutoHideTitleForceHasCloseBtn, True)
global _dockManager
_dockManager = CDockManager(mainWin)
_dockManager.setStyleSheet(None)
_dockManager.floatingWidgetCreated.connect(lambda f: f.layout().setContentsMargins(0, 1, 0, 0))
def focusedDockWidgetChanged(old, now):
focused = getIde().focusWidget()
if not isParentOfWidget(now, focused): now.setFocus()
if now.dockContainer().isFloating(): now.window().setWindowTitle(now.tabWidget().text())
_dockManager.focusedDockWidgetChanged.connect(focusedDockWidgetChanged)
return _dockManager
[docs]def dockManager():
return _dockManager
######################### LAYOUT #########################
[docs]def listLayouts():
folder = 'data/layouts/'
if not os.path.exists(folder): return
return [ f.name for f in os.scandir(folder) if f.is_dir() ]
[docs]def saveLayout(name):
folder = f'data/layouts/{name}/'
if not os.path.exists(folder): os.makedirs(folder)
statesFile = folder + 'states.xml'
windowFile = folder + 'window.json'
with open(statesFile, 'wb') as file:
file.write(bytes(_dockManager.saveState(1)))
file.close()
# enum Qt.WindowState:
# ------------------------------------
# Qt.WindowNoState 0x00000000
# Qt.WindowMinimized 0x00000001
# Qt.WindowMaximized 0x00000002
# Qt.WindowFullScreen 0x00000004
# Qt.WindowActive 0x00000008
with open(windowFile, 'w') as file:
win = _dockManager.window()
rect = win.geometry()
file.write(json.dumps({
'state' : int(win.windowState()),
'width' : rect.width(),
'height': rect.height(),
}, indent = 4))
file.close()
[docs]def loadLayout(name):
folder = f'data/layouts/{name}/'
if not os.path.exists(folder): return error(f'load layout fail! layout \'{name}\' not found.')
statesFile = folder + 'states.xml'
windowFile = folder + 'window.json'
with open(statesFile, 'rb') as file:
states = bytearray(file.read())
file.close()
layoutTable = {}
adsTree = ET.fromstring(bytes(states).decode())
for dock in adsTree.findall('.//Widget'):
if dock.attrib['Closed'] == '1': continue
names = dock.attrib['Name'].split('::')
view, guid = names[0], names[1]
data = {
'guid': guid,
'name': dock.attrib['Name'],
'data': dock.attrib['Data'] if 'Data' in dock.attrib else None
}
if view in layoutTable:
layoutTable[ view ].append(data)
else:
layoutTable[ view ] = [ data ]
for view in layoutTable:
if view not in _viewRegistry:
error(f'skip restoring view \'{view}\', which is not registered.')
for dock in findDockViewList(view): dock.closeDockWidget()
continue
lviews = layoutTable[view]
eviews = findDockViewList(view)
lviewsLen, eviewsLen = len(lviews), len(eviews)
def insideLayout(name):
for table in lviews:
if table['name'] == name:
return True
return False
if lviewsLen > eviewsLen:
for i in range(eviewsLen, lviewsLen):
new = createDockView(view)
new.addIntoEditor()
eviews.append(new)
elif lviewsLen < eviewsLen:
closeIndexList = []
closeCount = eviewsLen - lviewsLen
for i in range(eviewsLen):
if not insideLayout(eviews[i].objectName()): closeIndexList.insert(0, i)
if len(closeIndexList) == closeCount: break
for i in closeIndexList:
eviews[i].closeDockWidget()
del eviews[i]
stayTable, reuseList = {}, []
for dock in eviews:
name = dock.objectName()
if insideLayout(name):
stayTable[name] = dock
else:
reuseList.append(dock)
for i in range(0, lviewsLen):
table = lviews[i]
dockWidget = None
name = table['name']
if name in stayTable:
dockWidget = stayTable[name]
else:
dockWidget = reuseList.pop()
oldName = dockWidget.objectName()
dockWidget.setObjectName(name)
_dockManager.updateDockWidgetsMapKey(oldName, name)
_dockManager.restoreState(states, 1)
for d in _dockManager.dockWidgetsMap().values(): d.onLoadLayout()
win = _dockManager.window()
with open(windowFile, 'r') as file:
windata = json.loads(file.read())
file.close()
win.setWindowState(Qt.WindowState(windata['state']))
win.resize(windata['width'], windata['height'])
win.activateWindow()
win.raise_()
###################### TEST ######################
from editor.widgets.menubar import menuItem
@menuItem('Tools/test save layout')
[docs]def testSaveLayout(): saveLayout('test')
@menuItem('Tools/test load layout')
[docs]def testLoadLayout(): loadLayout('test')
# print(listLayouts())