class ActionSet(QtCore.QObject, PluginMixin): __plugin__ = 'workbench' sig_actionAdded = QtCore.pyqtSignal(object) sig_actionRemoved = QtCore.pyqtSignal(object) def __init__(self, parent=None): QtCore.QObject.__init__(self, parent) self._actions = ExtendList() self.loadExtensions() def loadExtensions(self): def load_action(ext): factory, meta = ext action = factory(self) action.name = '@'.join( (meta.name, meta._provider) ) action.domain = meta.domain action.location = meta.location action.position = int(meta.position) if hasattr(meta, 'Icon'): action.setIcon(QtGui.QIcon(meta.Icon)) if hasattr(meta, 'Text'): action.setText(meta.Text) self.addAction(action) self.extensions['action'].cook(load_action) def addAction(self, action): exist = self._actions.find(lambda a: a.domain==action.domain and a.name==action.name) if not exist: self._actions.append(action) self.sig_actionAdded.emit(action) def removeAction(self, action): if action in self._actions: self._action.remove(action) self.sig_actionRemoved.emit(action) def walk(self, root, func): children = self._actions.screen(lambda a: a.domain==root.domain and a.location==root.name) func(root, children) for child in children: self.walk(child, func) def populate(self, root, context): def expand(parent, children): if not hasattr(parent, 'clear'): return children = children.sorted(lambda a:a.position).screen(lambda a:a.assignContext(context)) if children: cat = children[0].position//100 for child in children: if child.position//100 > cat: parent.addSeparator() cat = child.position//100 parent.addAction(child) for holder in children.screen(lambda a: hasattr(a, 'clear')): holder.clear() self.walk(root, expand)
class WorkbenchWindow(QtGui.QMainWindow, IWorkbenchPart, PluginMixin): __plugin__ = 'workbench' sig_pageChanged = QtCore.pyqtSignal(object) ## methods used by app def __init__(self, parent=None): QtGui.QMainWindow.__init__(self, parent) self.wb_actionset = None self.wb_views = ExtendList() self.wb_pages = ExtendList() def init(self, actionset): self.wb_actionset = actionset self.wbInitWindowUI() self.wbInitExtensions() self.wpInitWorkbench(self) ## init the workbench after actions are loaded self.wbLoadState() ## check if the workbench is now able to be closed def wbClose(self): self.wbSaveState() self.wb_pages.cook(lambda p: self.wbClosePage(p)) self.close() ## override IWorkbenchPart method def wpInitWorkbench(self, workbench): self.setObjectName('window@workbench') self.toolKit.domain = self.objectName() self.toolKit.name = 'menubar' self.pageToolKit.domain = None self.pageToolKit.name = 'toolbar' IWorkbenchPart.wpInitWorkbench(self, workbench) def wpResetActions(self): self.toolKit.clear() self.wb_actionset.populate(self.toolKit, self) def wpOnActionChanged(self, action): if action.domain == self.objectName(): self.toolKit.clear() self.wb_actionset.populate(self.toolKit, self) else: page = self.pageStack.currentWidget() if page and action.domain==page.objectName(): self.pageToolKit.domain = page.objectName() self.wb_actionset.populate(self.pageToolKit, page) ## initialize window UI def wbInitWindowUI(self): self.setWindowTitle("Workbench") self.setWindowIcon(QtGui.QIcon(":/workbench/moon.png")) self.viewBar = QtGui.QToolBar(self) self.viewBar.restore_state = None self.viewBar.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) self.viewBar.setObjectName("View ToolBar") self.viewBar.setIconSize(QtCore.QSize(24,24)) self.viewBar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon) self.viewBar.layout().setMargin(0) self.addToolBar(QtCore.Qt.LeftToolBarArea, self.viewBar) self.viewMenu = QtGui.QMenu(self) self.viewMenuAction = QtGui.QAction(QtGui.QIcon(':/workbench/stack.png'), '', self) self.viewMenuAction.setMenu(self.viewMenu) self.viewBar.addAction(self.viewMenuAction) self.viewBar.addSeparator() def remove_view_action(action): if action is not self.viewMenuAction: self.viewBar.removeAction(action) self.viewBar.actionTriggered.connect(remove_view_action) self.toolKit = QtGui.QMenuBar(self) self.toolKit.setObjectName("Workbench Menu") self.toolKit.setWindowIcon(QtGui.QIcon('')) self.toolKit.setWindowTitle("Workbench Menu") self.setMenuBar(self.toolKit) addToggleAction(self.toolKit) central = QtGui.QFrame(self) central.setObjectName("Page Stack") central.setWindowIcon(QtGui.QIcon('')) central.setWindowTitle("Page Stack") addToggleAction(central) self.tabBar = TabBar(central) self.tabBar.setMovable(True) # self.tabBar.setElideMode(QtCore.Qt.ElideMiddle) self.tabBar.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.tabActions = QtGui.QActionGroup(self.tabBar) self.tabActions.addAction('Close') self.tabActions.addAction('Close Others') self.tabActions.addAction('Close All') self.tabBar.addActions(self.tabActions.actions()) self.tabActions.triggered.connect(self.wbOnTabAction) self.pageToolKit = ToolBar(central) self.pageToolKit.setObjectName("Page ToolBar") self.pageToolKit.setWindowTitle('Page ToolBar') self.pageStack = QtGui.QStackedWidget(central) hbox = QtGui.QHBoxLayout() hbox.addWidget(self.tabBar) hbox.addStretch() # hbox.addSpacing(2) vbox = QtGui.QVBoxLayout() vbox.setSpacing(0) vbox.setMargin(0) vbox.addLayout(hbox) vbox.addWidget(self.pageToolKit) vbox.addWidget(self.pageStack) central.setLayout(vbox) self.setCentralWidget(central) self.tabBar.mouseDoubleClickEvent = lambda evt: self.wbChangeViewState('togglemax', central) central.mouseDoubleClickEvent = lambda evt: self.wbChangeViewState('togglemax', central) def page_child_evt(evt): if evt.removed()and QtCore.QObject.isWidgetType(evt.child()): self.wbCheckPageCoherence() QtGui.QStackedWidget.childEvent(self.pageStack, evt) self.pageStack.childEvent = page_child_evt self.currentPage = self.pageStack.currentWidget self.tabBar.currentChanged.connect(self.wbOnTabChanged) self.tabBar.tabCloseRequested.connect(self.wbOnTabToClose) self.pageStack.currentChanged.connect(self.wbOnPageChanged) ## initialize extensions def wbInitExtensions(self): def assign_ext_name(ext): meta = ext[1] meta._ext_name = '@'.join((meta.name, meta._provider)) self.extensions['opener'].cook(assign_ext_name) self.extensions['view'].cook(assign_ext_name) ## manipulate views def wbShowView(self, extname): view = self.wb_views.find(lambda v:v.objectName()==extname) if view is None: ext = self.extensions['view'].find(lambda e:e[1]._ext_name ==extname) if ext is not None: factory, meta = ext view = factory() view.wpInitWorkbench(self) if hasattr(meta, 'Icon'): view.setWindowIcon(QtGui.QIcon(meta.Icon)) view.setWindowTitle(meta.name) view.setObjectName(meta._ext_name ) self.addDockWidget(8, view) self.wb_views.append(view) view.sig_requestViewStateChange.connect(self.wbChangeViewState) if view: view.show() view.raise_() self.viewBar.removeAction(view.toggleViewAction()) else: QtGui.QMessageBox.information(self, "Error", "View {} is not found.".format(extname)) def wbChangeViewState(self, cmd, view): def restore(): self.viewBar.show() self.centralWidget().show() if self.viewBar.restore_state: self.restoreState(self.viewBar.restore_state) self.viewBar.restore_state = None self.wb_views.cook(lambda v:v.winBar.setRestoreState(False)) def backup(): self.viewBar.restore_state = self.saveState() if view in self.wb_views: if cmd == 'close': restore() view.hide() elif cmd == 'min': restore() view.hide() self.viewBar.addAction(view.toggleViewAction()) elif cmd == 'max': backup() self.viewBar.hide() self.centralWidget().hide() self.wb_views.screen(lambda v: v is not view).cook(lambda v:v.hide()) elif cmd == 'restore': restore() elif view is self.centralWidget(): if cmd == 'togglemax': if self.viewBar.restore_state: restore() else: backup() self.viewBar.hide() self.wb_views.cook(lambda v:v.hide()) ## manipulate pages def wbShowPage(self, page): self.tabBar.setCurrentIndexByData(page._pageid) def wbAddPage(self, page): if page not in self.wb_pages: self.pageStack.addWidget(page) self.wb_pages.append(page) page._pageid = self.tabBar.addTab(page.pgTabTitle(), page.windowIcon()) def wbClosePage(self, page): if page in self.wb_pages: if page._dirty: decision = QtGui.QMessageBox.question(self, 'Save or not?', '{} has been changed. Do you want to save it befor close?'.format(page.pgWinTitle()), QtGui.QMessageBox.Save | QtGui.QMessageBox.Discard) if decision == QtGui.QMessageBox.Save: page.pgSave() self.tabBar.removeTabByData(page._pageid) self.pageStack.removeWidget(page) self.wb_pages.remove(page) page.close() def wbOnTabChanged(self, tabind): page = self.wb_pages.find(lambda p: p._pageid==self.tabBar.tabData(tabind)) if page: self.pageStack.setCurrentWidget(page) def wbOnTabToClose(self, tabind): page = self.wb_pages.find(lambda p: p._pageid==self.tabBar.tabData(tabind)) self.wbClosePage(page) def wbOnPageChanged(self, pageind): page = self.pageStack.widget(pageind) self.pageToolKit.clear() if page: self.setWindowTitle(page.pgWinTitle()) self.pageToolKit.domain = page.objectName() self.wb_actionset.populate(self.pageToolKit, page) else: self.setWindowTitle('Workbench') self.sig_pageChanged.emit(page) def wbOnTabAction(self, action): txt = action.text() if txt == 'Close': page = self.pageStack.currentWidget() if page: self.wbClosePage(page) elif txt == 'Close Others': page = self.pageStack.currentWidget() self.wb_pages.screen(lambda p: p is not page).cook(lambda p: self.wbClosePage(p)) elif txt == 'Close All': self.wb_pages.cook(lambda p: self.wbClosePage(p)) def wbOnPageDirtyChanged(self, page): txt = ('*' if page._dirty else '') + page.pgTabTitle() self.tabBar.setTextByData(page._pageid, txt) def wbCheckPageCoherence(self): for page in self.wb_pages.screen(lambda p: not isAlive(p)): self.wb_pages.remove(page) self.tabBar.removeTabByData(page._pageid) ## "open" service def wbMatchOpener(self, scheme): opener = (None, None) settingid = 'default.opener.'+scheme defaultopener = self.setting.get(settingid, None) if defaultopener: opener = self.extensions['opener'].find(lambda o: defaultopener==o[1]._ext_name ) if opener[0] is None: openers = self.extensions['opener'].screen(lambda o: scheme == o[1].scheme) if len(openers) == 1: opener = openers[0] elif len(openers) > 1: ind, bedefault = ChooseDlg.choose("Select a opener for scheme: "+scheme, [o[1]._ext_name for o in openers], self, "Choose Opener", None) if ind is not None: opener = openers[ind] if bedefault: self.setting.set(settingid, opener[1]._ext_name ) return opener def wbOpen(self, scheme, uri): opener, meta = self.wbMatchOpener(scheme) if opener: self.wbOpenBy(uri, opener, meta) else: QtGui.QMessageBox.information(self, "Information", "No proper opener is found for: "+scheme) def wbOpenBy(self, uri, opener, meta): page = opener.openPage(uri) if page: if page not in self.wb_pages: page.setObjectName(meta._ext_name ) page.wpInitWorkbench(self) self.wbAddPage(page) self.wbShowPage(page) def wbSaveState(self): self.setting['state.close.qstate']._bytearray = self.viewBar.restore_state or self.saveState() self.setting['state.close.pages']._val = SettingTuple(['@@'.join([p.pgRepr(), p.objectName()]) for p in self.wb_pages if p.objectName() and p.pgRepr() is not None]) self.setting['state.close.views']._val = SettingTuple([v.objectName() for v in self.wb_views if v.isVisible()]) self.setting['state.close.size']._int = SettingTuple([self.size().width(), self.size().height()]) def wbLoadState(self): ## size if self.setting.has('state.close.size'): self.resize(QtCore.QSize(*self.setting['state.close.size']._int)) ## views for v in self.setting.get('state.close.views', ()): self.wbShowView(v) for v in self.setting.get('workbench.onstart.views', ()): self.wbShowView(v) ## load style sheet # if self.setting.has('workbench.css'): # with plat.workspace.file(self.setting('workbench.css')._val).open('r') as fp: # self.setStyleSheet(fp.read()) ## state if self.setting.has('state.close.qstate'): self.restoreState(self.setting['state.close.qstate']._bytearray) ## pages for pdata in self.setting.get('state.close.pages', ()): uri, name = pdata.rsplit('@@', 1) opener = self.extensions['opener'].find(lambda o: o[1]._ext_name ==name) if opener: self.wbOpenBy(uri, *opener) ## view bar self.loadViewMenu() ## context menu def loadViewMenu(self): self.viewMenu.addActions([self.toolKit.toggleViewAction(), self.centralWidget().toggleViewAction()]) self.viewMenu.addSeparator() def add_view_action(view): _, meta = view a = QtGui.QAction(self.viewMenu) a.setText(meta.name) a.setIcon(QtGui.QIcon(getattr(meta, 'Icon', ''))) a.setProperty('viewname', meta._ext_name ) self.viewMenu.addAction(a) self.extensions['view'].cook(add_view_action) def show_view(action): vn = action.property('viewname') if vn: self.wbShowView(vn) self.viewMenu.triggered.connect(show_view)