class DebuggerWidget(QWidget): def __init__(self, scripter, parent=None): super(DebuggerWidget, self).__init__(parent) self.scripter = scripter self.setObjectName('Debugger') self.layout = QVBoxLayout() self.stopAction = stopaction.StopAction(self.scripter, self) self.toolbar = QToolBar() self.stepAction = stepaction.StepAction(self.scripter, self) self.toolbar.addAction(self.stopAction) self.toolbar.addAction(self.stepAction) self.disableToolbar(True) self.table = debuggertable.DebuggerTable() self.layout.addWidget(self.toolbar) self.layout.addWidget(self.table) self.setLayout(self.layout) def startDebugger(self): self.disableToolbar(False) def disableToolbar(self, status): for action in self.toolbar.actions(): action.setDisabled(status) def updateWidget(self): data = self.scripter.debugcontroller.debuggerData self.table.updateTable(data)
def _init_ui(self): color = QColor(255, 255, 255) palette = QPalette(color) main_box = QHBoxLayout(self) settings_tlbr = QToolBar(self) settings_tlbr.setOrientation(Qt.Vertical) tblr_font = QFont("Times", 14) settings_tlbr.setFont(tblr_font) main_box.addWidget(settings_tlbr) fuselage_act = QAction("Fuselage", self) fuselage_act.setObjectName("fuselage_act") fuselage_act.setCheckable(True) fuselage_act.setChecked(True) fuselage_act.triggered.connect(self._show_widget) settings_tlbr.addAction(fuselage_act) fuselage_tab = FuselageWidget(self) fuselage_tab.setObjectName("fuselage_tab") fuselage_tab.setPalette(palette) fuselage_tab.setAutoFillBackground(True) fuselage_tab.copter_changed.connect(self._copter_changed) self.current_action = fuselage_act self.main_widget = fuselage_tab main_box.addWidget(fuselage_tab) if self.copter.equal_engines: engine_act = QAction("Engines", self) engine_act.setObjectName("engine_act") engine_act.setCheckable(True) engine_act.setChecked(False) engine_act.triggered.connect(self._show_widget) settings_tlbr.addAction(engine_act) engine_tab = EngineWidget(self) engine_tab.setObjectName("engine_tab") engine_tab.setPalette(palette) engine_tab.setAutoFillBackground(True) engine_tab.copter_changed.connect(self._copter_changed) main_box.addWidget(engine_tab) engine_tab.hide() else: for i in range(self.copter.num_of_engines): engine_act = QAction("Engine {}".format(i + 1), self) engine_act.setObjectName("engine_act_{}".format(i)) engine_act.setCheckable(True) engine_act.setChecked(False) engine_act.triggered.connect(self._show_widget) settings_tlbr.addAction(engine_act) engine_tab = EngineWidget(self, i) engine_tab.setObjectName("engine_tab_{}".format(i)) engine_tab.setPalette(palette) engine_tab.setAutoFillBackground(True) engine_tab.copter_changed.connect(self._copter_changed) main_box.addWidget(engine_tab) engine_tab.hide() for action in settings_tlbr.actions(): widget = settings_tlbr.widgetForAction(action) widget.setFixedWidth(115) self.setLayout(main_box) return
def _init_ui(self): color = QColor(255, 255, 255) palette = QPalette(color) main_box = QHBoxLayout(self) settings_tlbr = QToolBar(self) settings_tlbr.setOrientation(Qt.Vertical) tblr_font = QFont("Times", 14) settings_tlbr.setFont(tblr_font) simulation_act = QAction("Simulation settings", self) simulation_act.setObjectName("simulation_act") simulation_act.setCheckable(True) simulation_act.setChecked(True) simulation_act.triggered.connect(self._show_widget) settings_tlbr.addAction(simulation_act) simulation_tab = SimSettingsWidget(self) simulation_tab.setObjectName("simulation_tab") simulation_tab.setPalette(palette) simulation_tab.setAutoFillBackground(True) simulation_tab.settings_changed.connect(self._settings_changed) self.current_action = simulation_act self.main_widget = simulation_tab copter_act = QAction("Start settings", self) copter_act.setObjectName("start_act") copter_act.setCheckable(True) copter_act.setChecked(False) copter_act.triggered.connect(self._show_widget) settings_tlbr.addAction(copter_act) copter_tab = SimSettingsStartWidget(self) copter_tab.setObjectName("start_tab") copter_tab.setPalette(palette) copter_tab.setAutoFillBackground(True) copter_tab.settings_changed.connect(self._settings_changed) copter_tab.hide() save_act = QAction("Save settings", self) save_act.setObjectName("save_act") save_act.setEnabled(False) save_act.triggered.connect(self._save_settings) settings_tlbr.addAction(save_act) main_box.addWidget(settings_tlbr) main_box.addWidget(simulation_tab) main_box.addWidget(copter_tab) for action in settings_tlbr.actions(): widget = settings_tlbr.widgetForAction(action) widget.setFixedWidth(200) self.setLayout(main_box) return
def __init__(self, toolbar: QToolBar): """ :param toolbar: Toolbar that the menu is managing """ super().__init__() self.toolbar = toolbar for action in toolbar.actions(): if action.isSeparator(): self.addSeparator() else: self.addAction(self._get_toggle_action(action)) self.update_checked_states()
class MainGlyphWindow(QMainWindow): def __init__(self, glyph, parent=None): super().__init__(parent) menuBar = self.menuBar() fileMenu = QMenu("&File", self) fileMenu.addAction("E&xit", self.close, QKeySequence.Quit) menuBar.addMenu(fileMenu) editMenu = QMenu("&Edit", self) self._undoAction = editMenu.addAction( "&Undo", self.undo, QKeySequence.Undo) self._redoAction = editMenu.addAction( "&Redo", self.redo, QKeySequence.Redo) editMenu.addSeparator() # XXX action = editMenu.addAction("C&ut", self.cutOutlines, QKeySequence.Cut) action.setEnabled(False) self._copyAction = editMenu.addAction( "&Copy", self.copyOutlines, QKeySequence.Copy) editMenu.addAction("&Paste", self.pasteOutlines, QKeySequence.Paste) editMenu.addAction( "Select &All", self.selectAll, QKeySequence.SelectAll) editMenu.addAction("&Deselect", self.deselect, "Ctrl+D") menuBar.addMenu(editMenu) glyphMenu = QMenu("&Glyph", self) glyphMenu.addAction("&Next Glyph", lambda: self.glyphOffset(1), "End") glyphMenu.addAction( "&Previous Glyph", lambda: self.glyphOffset(-1), "Home") glyphMenu.addAction("&Go To…", self.changeGlyph, "G") glyphMenu.addSeparator() self._layerAction = glyphMenu.addAction( "&Layer Actions…", self.layerActions, "L") menuBar.addMenu(glyphMenu) # create tools and buttons toolBars self._tools = [] self._toolsToolBar = QToolBar("Tools", self) self._toolsToolBar.setMovable(False) self._buttons = [] self._buttonsToolBar = QToolBar("Buttons", self) self._buttonsToolBar.setMovable(False) self.addToolBar(self._toolsToolBar) self.addToolBar(self._buttonsToolBar) # http://www.setnode.com/blog/right-aligning-a-button-in-a-qtoolbar/ self._layersToolBar = QToolBar("Layers", self) spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self._currentLayerBox = QComboBox(self) self._currentLayerBox.currentIndexChanged.connect( self._layerChanged) self._layersToolBar.addWidget(spacer) self._layersToolBar.addWidget(self._currentLayerBox) self._layersToolBar.setContentsMargins(0, 0, 2, 0) self._layersToolBar.setMovable(False) self.addToolBar(self._layersToolBar) viewMenu = self.createPopupMenu() viewMenu.setTitle("View") viewMenu.addSeparator() action = viewMenu.addAction("Lock Toolbars", self.lockToolBars) action.setCheckable(True) action.setChecked(True) menuBar.addMenu(viewMenu) self.view = GlyphView(self) self.setGlyph(glyph) selectionTool = self.installTool(SelectionTool) selectionTool.trigger() self.installTool(PenTool) self.installTool(RulerTool) self.installTool(KnifeTool) self.installButton(RemoveOverlapButton) self.setCentralWidget(self.view.scrollArea()) self.resize(900, 700) self.view.setFocus(True) # ---------- # Menu items # ---------- def glyphOffset(self, offset): currentGlyph = self.view.glyph() font = currentGlyph.font glyphOrder = font.glyphOrder # should be enforced in fontView already if not (glyphOrder and len(glyphOrder)): return index = glyphOrder.index(currentGlyph.name) newIndex = (index + offset) % len(glyphOrder) glyph = font[glyphOrder[newIndex]] self.setGlyph(glyph) def changeGlyph(self): glyph = self.view.glyph() newGlyph, ok = GotoDialog.getNewGlyph(self, glyph) if ok and newGlyph is not None: self.setGlyph(newGlyph) def layerActions(self): glyph = self.view.glyph() newLayer, action, ok = LayerActionsDialog.getLayerAndAction( self, glyph) if ok and newLayer is not None: # TODO: whole glyph for now, but consider selection too if not glyph.name in newLayer: newLayer.newGlyph(glyph.name) otherGlyph = newLayer[glyph.name] otherGlyph.disableNotifications() if action == "Swap": tempGlyph = TGlyph() otherGlyph.drawPoints(tempGlyph.getPointPen()) tempGlyph.width = otherGlyph.width otherGlyph.clearContours() glyph.drawPoints(otherGlyph.getPointPen()) otherGlyph.width = glyph.width if action != "Copy": glyph.disableNotifications() glyph.clearContours() if action == "Swap": tempGlyph.drawPoints(glyph.getPointPen()) glyph.width = tempGlyph.width glyph.enableNotifications() otherGlyph.enableNotifications() def undo(self): glyph = self.view.glyph() glyph.undo() def redo(self): glyph = self.view.glyph() glyph.redo() def cutOutlines(self): pass def copyOutlines(self): glyph = self.view.glyph() clipboard = QApplication.clipboard() mimeData = QMimeData() copyGlyph = glyph.getRepresentation("defconQt.FilterSelection") mimeData.setData("application/x-defconQt-glyph-data", pickle.dumps([copyGlyph.serialize( blacklist=("name", "unicode") )])) clipboard.setMimeData(mimeData) def pasteOutlines(self): glyph = self.view.glyph() clipboard = QApplication.clipboard() mimeData = clipboard.mimeData() if mimeData.hasFormat("application/x-defconQt-glyph-data"): data = pickle.loads(mimeData.data( "application/x-defconQt-glyph-data")) if len(data) == 1: pen = glyph.getPointPen() pasteGlyph = TGlyph() pasteGlyph.deserialize(data[0]) # TODO: if we serialize selected state, we don't need to do # this pasteGlyph.selected = True if len(pasteGlyph) or len(pasteGlyph.components) or \ len(pasteGlyph.anchors): glyph.prepareUndo() pasteGlyph.drawPoints(pen) def selectAll(self): glyph = self.view.glyph() glyph.selected = True if not len(glyph): for component in glyph.components: component.selected = True def deselect(self): glyph = self.view.glyph() for anchor in glyph.anchors: anchor.selected = False for component in glyph.components: component.selected = False glyph.selected = False def lockToolBars(self): action = self.sender() movable = not action.isChecked() for toolBar in ( self._toolsToolBar, self._buttonsToolBar, self._layersToolBar): toolBar.setMovable(movable) # -------------------------- # Tools & buttons management # -------------------------- def installTool(self, tool): action = self._toolsToolBar.addAction( QIcon(tool.iconPath), tool.name, self._setViewTool) action.setCheckable(True) num = len(self._tools) action.setData(num) action.setShortcut(QKeySequence(str(num + 1))) self._tools.append(tool(parent=self.view)) return action def uninstallTool(self, tool): pass # XXX def _setViewTool(self): action = self.sender() index = action.data() newTool = self._tools[index] if newTool == self.view.currentTool(): action.setChecked(True) return ok = self.view.setCurrentTool(newTool) # if view did change tool, disable them all and enable the one we want # otherwise, just disable the tool that was clicked. # previously we used QActionGroup to have exclusive buttons, but doing # it manually allows us to NAK a button change. if ok: for act in self._toolsToolBar.actions(): act.setChecked(False) action.setChecked(ok) def installButton(self, button): action = self._buttonsToolBar.addAction( QIcon(button.iconPath), button.name, self._buttonAction) action.setData(len(self._buttons)) self._buttons.append(button(parent=self.view)) return action def uninstallButton(self, button): pass # XXX def _buttonAction(self): action = self.sender() index = action.data() button = self._buttons[index] button.clicked() # -------------------- # Notification support # -------------------- # glyph def _subscribeToGlyph(self, glyph): if glyph is not None: glyph.addObserver(self, "_glyphChanged", "Glyph.Changed") glyph.addObserver(self, "_glyphNameChanged", "Glyph.NameChanged") glyph.addObserver( self, "_glyphSelectionChanged", "Glyph.SelectionChanged") undoManager = glyph.undoManager undoManager.canUndoChanged.connect(self._undoAction.setEnabled) undoManager.canRedoChanged.connect(self._redoAction.setEnabled) self._subscribeToFontAndLayerSet(glyph.font) def _unsubscribeFromGlyph(self, glyph): if glyph is not None: glyph.removeObserver(self, "Glyph.Changed") glyph.removeObserver(self, "Glyph.NameChanged") glyph.removeObserver(self, "Glyph.SelectionChanged") undoManager = glyph.undoManager undoManager.canUndoChanged.disconnect(self._undoAction.setEnabled) undoManager.canRedoChanged.disconnect(self._redoAction.setEnabled) self._unsubscribeFromFontAndLayerSet(glyph.font) def _glyphChanged(self, notification): self.view.glyphChanged() def _glyphNameChanged(self, notification): glyph = self.view.glyph() self.setWindowTitle(glyph.name, glyph.font) def _glyphSelectionChanged(self, notification): self._updateSelection() self.view.glyphChanged() def _fontInfoChanged(self, notification): self.view.fontInfoChanged() glyph = self.view.glyph() self.setWindowTitle(glyph.name, glyph.font) # layers & font def _subscribeToFontAndLayerSet(self, font): """Note: called by _subscribeToGlyph.""" if font is None: return font.info.addObserver(self, "_fontInfoChanged", "Info.Changed") layerSet = font.layers if layerSet is None: return layerSet.addObserver(self, '_layerSetLayerDeleted', 'LayerSet.LayerDeleted') for event in ('LayerSet.LayerAdded', 'LayerSet.LayerChanged', 'LayerSet.LayerOrderChanged'): layerSet.addObserver(self, '_layerSetEvents', event) def _unsubscribeFromFontAndLayerSet(self, font): """Note: called by _unsubscribeFromGlyph.""" if font is None: return font.info.removeObserver(self, "Info.Changed") layerSet = font.layers if layerSet is None: return for event in ('LayerSet.LayerAdded', 'LayerSet.LayerChanged', 'LayerSet.LayerOrderChanged', 'LayerSet.LayerDeleted'): layerSet.removeObserver(self, event) def _layerSetEvents(self, notification): self._updateLayerControls() def _layerSetLayerDeleted(self, notification): self._layerSetEvents(notification) self._currentLayerBox.setCurrentIndex(0) # other updaters def _updateUndoRedo(self): glyph = self.view.glyph() self._undoAction.setEnabled(glyph.canUndo()) self._redoAction.setEnabled(glyph.canRedo()) def _updateSelection(self): def hasSelection(): glyph = self.view.glyph() for contour in glyph: if len(contour.selection): return True for anchor in glyph.anchors: if anchor.selected: return True for component in glyph.components: if component.selected: return True return False self._copyAction.setEnabled(hasSelection()) # -------------- # Public Methods # -------------- def setGlyph(self, glyph): currentGlyph = self.view.glyph() self._unsubscribeFromGlyph(currentGlyph) self._subscribeToGlyph(glyph) self.view.setGlyph(glyph) self._updateLayerControls() self._updateUndoRedo() self._updateSelection() self.setWindowTitle(glyph.name, glyph.font) def setDrawingAttribute(self, attr, value, layerName=None): self.view.setDrawingAttribute(attr, value, layerName) def drawingAttribute(self, attr, layerName=None): return self.view.drawingAttribute(attr, layerName) # ----------------- # Layers management # ----------------- def _layerChanged(self, newLayerIndex): glyph = self.view.glyph() layer = self._currentLayerBox.itemData(newLayerIndex) if layer is None: layer = self._makeLayer() if layer is None: # restore comboBox to active index layerSet = glyph.layerSet index = layerSet.layerOrder.index(glyph.layer.name) self._setLayerBoxIndex(index) return if glyph.name in layer: newGlyph = layer[glyph.name] else: # TODO: make sure we mimic defcon ufo3 APIs for that newGlyph = self._makeLayerGlyph(layer, glyph) self.setGlyph(newGlyph) # setting the layer-glyph here app = QApplication.instance() app.setCurrentGlyph(newGlyph) def _makeLayer(self): # TODO: what with duplicate names? glyph = self.view.glyph() newLayerName, ok = AddLayerDialog.getNewLayerName(self) if ok: layerSet = glyph.layerSet # TODO: this should return the layer layerSet.newLayer(newLayerName) return layerSet[newLayerName] else: return None def _makeLayerGlyph(self, layer, currentGlyph): glyph = layer.newGlyph(currentGlyph.name) glyph.width = currentGlyph.width glyph.template = True return glyph def _updateLayerControls(self): comboBox = self._currentLayerBox glyph = self.view.glyph() comboBox.blockSignals(True) comboBox.clear() for layer in glyph.layerSet: comboBox.addItem(layer.name, layer) comboBox.setCurrentText(glyph.layer.name) comboBox.addItem("New layer…", None) comboBox.blockSignals(False) self._layerAction.setEnabled(len(glyph.layerSet) > 1) def _setLayerBoxIndex(self, index): comboBox = self._currentLayerBox comboBox.blockSignals(True) comboBox.setCurrentIndex(index) comboBox.blockSignals(False) # --------------------- # QMainWindow functions # --------------------- def event(self, event): if event.type() == QEvent.WindowActivate: app = QApplication.instance() app.setCurrentGlyph(self.view.glyph()) return super().event(event) def closeEvent(self, event): glyph = self.view.glyph() self._unsubscribeFromGlyph(glyph) event.accept() def setWindowTitle(self, title, font=None): if font is not None: title = "%s – %s %s" % ( title, font.info.familyName, font.info.styleName) super().setWindowTitle(title)
class GlyphWindow(BaseMainWindow): def __init__(self, glyph, parent=None): super().__init__(parent) self.setAttribute(Qt.WA_DeleteOnClose, False) self.setUnifiedTitleAndToolBarOnMac(True) self.view = GlyphCanvasView(self) # create tools and buttons toolBars self._tools = [] self._toolsToolBar = QToolBar(self.tr("Tools"), self) self._toolsToolBar.setMovable(False) self._buttons = [] self._buttonsToolBar = QToolBar(self.tr("Buttons"), self) self._buttonsToolBar.setMovable(False) self.addToolBar(self._toolsToolBar) self.addToolBar(self._buttonsToolBar) # http://www.setnode.com/blog/right-aligning-a-button-in-a-qtoolbar/ self._layersToolBar = QToolBar(self.tr("Layers"), self) spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self._currentLayerBox = QComboBox(self) self._currentLayerBox.currentIndexChanged.connect( self._layerChanged) self._layersToolBar.addWidget(spacer) self._layersToolBar.addWidget(self._currentLayerBox) self._layersToolBar.setContentsMargins(0, 0, 2, 0) self._layersToolBar.setMovable(False) self.addToolBar(self._layersToolBar) self.setGlyph(glyph) app = QApplication.instance() tools = app.drawingTools() for index, tool in enumerate(tools): action = self.installTool(tool) if not index: action.trigger() app.dispatcher.addObserver( self, "_drawingToolRegistered", "drawingToolRegistered") # TODO: drawingToolUnregistered self.installButton(RemoveOverlapButton) self.setCentralWidget(self.view) self.view.setFocus(True) self.readSettings() def readSettings(self): geometry = settings.glyphWindowGeometry() if geometry: self.restoreGeometry(geometry) def writeSettings(self): # TODO: save current tool? settings.setGlyphWindowGeometry(self.saveGeometry()) def setupMenu(self, menuBar): fileMenu = menuBar.fetchMenu(Entries.File) fontWindow = self.parent() if fontWindow is not None: fileMenu.fetchAction(Entries.File_Save, fontWindow.saveFile) fileMenu.fetchAction(Entries.File_Save_As, fontWindow.saveFileAs) fileMenu.fetchAction(Entries.File_Close, self.close) if fontWindow is not None: fileMenu.fetchAction(Entries.File_Reload, fontWindow.reloadFile) editMenu = menuBar.fetchMenu(Entries.Edit) self._undoAction = editMenu.fetchAction(Entries.Edit_Undo, self.undo) self._redoAction = editMenu.fetchAction(Entries.Edit_Redo, self.redo) editMenu.addSeparator() cutAction = editMenu.fetchAction(Entries.Edit_Cut, self.cutOutlines) copyAction = editMenu.fetchAction(Entries.Edit_Copy, self.copyOutlines) self._selectActions = (cutAction, copyAction) editMenu.fetchAction(Entries.Edit_Paste, self.pasteOutlines) editMenu.fetchAction(Entries.Edit_Select_All, self.selectAll) editMenu.fetchAction(Entries.Edit_Find, self.changeGlyph) # TODO: sort this out # editMenu.fetchAction(self.tr("&Deselect"), self.deselect, "Ctrl+D") # glyphMenu = menuBar.fetchMenu(self.tr("&Glyph")) # self._layerAction = glyphMenu.fetchAction( # self.tr("&Layer Actions…"), self.layerActions, "L") viewMenu = menuBar.fetchMenu(Entries.View) viewMenu.fetchAction(Entries.View_Zoom_In, lambda: self.view.zoom(1)) viewMenu.fetchAction(Entries.View_Zoom_Out, lambda: self.view.zoom(-1)) viewMenu.fetchAction(Entries.View_Reset_Zoom, self.view.fitScaleBBox) viewMenu.addSeparator() viewMenu.fetchAction( Entries.View_Next_Glyph, lambda: self.glyphOffset(1)) viewMenu.fetchAction( Entries.View_Previous_Glyph, lambda: self.glyphOffset(-1)) self._updateUndoRedo() # ---------- # Menu items # ---------- def saveFile(self): glyph = self.view.glyph() font = glyph.font if None not in (font, font.path): font.save() def glyphOffset(self, offset): currentGlyph = self.view.glyph() font = currentGlyph.font glyphOrder = font.glyphOrder # should be enforced in fontView already if not (glyphOrder and len(glyphOrder)): return index = glyphOrder.index(currentGlyph.name) newIndex = (index + offset) % len(glyphOrder) glyph = font[glyphOrder[newIndex]] self.setGlyph(glyph) def changeGlyph(self): glyph = self.view.glyph() newGlyph, ok = FindDialog.getNewGlyph(self, glyph) if ok and newGlyph is not None: self.setGlyph(newGlyph) def layerActions(self): glyph = self.view.glyph() newLayer, action, ok = LayerActionsDialog.getLayerAndAction( self, glyph) if ok and newLayer is not None: # TODO: whole glyph for now, but consider selection too if glyph.name not in newLayer: newLayer.newGlyph(glyph.name) otherGlyph = newLayer[glyph.name] otherGlyph.holdNotifications() if action == "Swap": tempGlyph = glyph.__class__() otherGlyph.drawPoints(tempGlyph.getPointPen()) tempGlyph.width = otherGlyph.width otherGlyph.clearContours() glyph.drawPoints(otherGlyph.getPointPen()) otherGlyph.width = glyph.width if action != "Copy": glyph.holdNotifications() glyph.clearContours() if action == "Swap": tempGlyph.drawPoints(glyph.getPointPen()) glyph.width = tempGlyph.width glyph.releaseHeldNotifications() otherGlyph.releaseHeldNotifications() def undo(self): glyph = self.view.glyph() glyph.undo() def redo(self): glyph = self.view.glyph() glyph.redo() def cutOutlines(self): glyph = self.view.glyph() self.copyOutlines() deleteUISelection(glyph) def copyOutlines(self): glyph = self.view.glyph() clipboard = QApplication.clipboard() mimeData = QMimeData() copyGlyph = glyph.getRepresentation("TruFont.FilterSelection") mimeData.setData("application/x-trufont-glyph-data", pickle.dumps([copyGlyph.serialize( blacklist=("name", "unicode") )])) clipboard.setMimeData(mimeData) def pasteOutlines(self): glyph = self.view.glyph() clipboard = QApplication.clipboard() mimeData = clipboard.mimeData() if mimeData.hasFormat("application/x-trufont-glyph-data"): data = pickle.loads(mimeData.data( "application/x-trufont-glyph-data")) if len(data) == 1: pen = glyph.getPointPen() pasteGlyph = glyph.__class__() pasteGlyph.deserialize(data[0]) # TODO: if we serialize selected state, we don't need to do # this pasteGlyph.selected = True if len(pasteGlyph) or len(pasteGlyph.components) or \ len(pasteGlyph.anchors): glyph.prepareUndo() pasteGlyph.drawPoints(pen) def selectAll(self): glyph = self.view.glyph() if glyph.selected: for anchor in glyph.anchors: anchor.selected = True for component in glyph.components: component.selected = True else: glyph.selected = True def deselect(self): glyph = self.view.glyph() for anchor in glyph.anchors: anchor.selected = False for component in glyph.components: component.selected = False glyph.selected = False def lockToolBars(self): action = self.sender() movable = not action.isChecked() for toolBar in ( self._toolsToolBar, self._buttonsToolBar, self._layersToolBar): toolBar.setMovable(movable) # -------------------------- # Tools & buttons management # -------------------------- def installTool(self, tool): action = self._toolsToolBar.addAction( QIcon(tool.iconPath), tool.name, self._setViewTool) action.setCheckable(True) num = len(self._tools) action.setData(num) action.setShortcut(QKeySequence(str(num + 1))) self._tools.append(tool(parent=self.view.widget())) return action def uninstallTool(self, tool): pass # XXX def _setViewTool(self): action = self.sender() index = action.data() newTool = self._tools[index] if newTool == self.view.currentTool(): action.setChecked(True) return ok = self.view.setCurrentTool(newTool) # if view did change tool, disable them all and enable the one we want # otherwise, just disable the tool that was clicked. # previously we used QActionGroup to have exclusive buttons, but doing # it manually allows us to NAK a button change. if ok: for act in self._toolsToolBar.actions(): act.setChecked(False) action.setChecked(ok) def installButton(self, button): action = self._buttonsToolBar.addAction( QIcon(button.iconPath), button.name, self._buttonAction) action.setData(len(self._buttons)) self._buttons.append(button(parent=self.view)) return action def uninstallButton(self, button): pass # XXX def _buttonAction(self): action = self.sender() index = action.data() button = self._buttons[index] button.clicked() # ------------- # Notifications # ------------- # app def _drawingToolRegistered(self, notification): tool = notification.data["tool"] self.installTool(tool) # glyph def _subscribeToGlyph(self, glyph): if glyph is not None: glyph.addObserver(self, "_glyphNameChanged", "Glyph.NameChanged") glyph.addObserver( self, "_glyphSelectionChanged", "Glyph.SelectionChanged") undoManager = glyph.undoManager undoManager.canUndoChanged.connect(self._setUndoEnabled) undoManager.canRedoChanged.connect(self._setRedoEnabled) self._subscribeToFontAndLayerSet(glyph.font) def _unsubscribeFromGlyph(self, glyph): if glyph is not None: glyph.removeObserver(self, "Glyph.NameChanged") glyph.removeObserver(self, "Glyph.SelectionChanged") undoManager = glyph.undoManager undoManager.canUndoChanged.disconnect(self._setUndoEnabled) undoManager.canRedoChanged.disconnect(self._setRedoEnabled) self._unsubscribeFromFontAndLayerSet(glyph.font) def _glyphNameChanged(self, notification): glyph = self.view.glyph() self.setWindowTitle(glyph.name, glyph.font) def _glyphSelectionChanged(self, notification): self._updateSelection() # layers & font def _subscribeToFontAndLayerSet(self, font): """Note: called by _subscribeToGlyph.""" if font is None: return font.info.addObserver(self, "_fontInfoChanged", "Info.Changed") layerSet = font.layers if layerSet is None: return layerSet.addObserver( self, '_layerSetLayerDeleted', 'LayerSet.LayerDeleted') for event in ('LayerSet.LayerAdded', 'LayerSet.LayerChanged', 'LayerSet.LayerOrderChanged'): layerSet.addObserver(self, '_layerSetEvents', event) def _unsubscribeFromFontAndLayerSet(self, font): """Note: called by _unsubscribeFromGlyph.""" if font is None: return font.info.removeObserver(self, "Info.Changed") layerSet = font.layers if layerSet is None: return for event in ('LayerSet.LayerAdded', 'LayerSet.LayerChanged', 'LayerSet.LayerOrderChanged', 'LayerSet.LayerDeleted'): layerSet.removeObserver(self, event) def _fontInfoChanged(self, notification): glyph = self.view.glyph() self.setWindowTitle(glyph.name, glyph.font) def _layerSetEvents(self, notification): self._updateLayerControls() def _layerSetLayerDeleted(self, notification): self._layerSetEvents(notification) self._currentLayerBox.setCurrentIndex(0) # other updaters def _updateSelection(self): def hasSelection(): glyph = self.view.glyph() for contour in glyph: if len(contour.selection): return True for anchor in glyph.anchors: if anchor.selected: return True for component in glyph.components: if component.selected: return True return False if not hasattr(self, "_selectActions"): return hasSelection = hasSelection() for action in self._selectActions: action.setEnabled(hasSelection) def _updateUndoRedo(self): glyph = self.view.glyph() self._setUndoEnabled(glyph.canUndo()) self._setRedoEnabled(glyph.canRedo()) def _setUndoEnabled(self, value): if not hasattr(self, "_undoAction"): return self._undoAction.setEnabled(value) def _setRedoEnabled(self, value): if not hasattr(self, "_redoAction"): return self._redoAction.setEnabled(value) # -------------- # Public Methods # -------------- def setGlyph(self, glyph): currentGlyph = self.view.glyph() self._unsubscribeFromGlyph(currentGlyph) self.view.setGlyph(glyph) self._subscribeToGlyph(glyph) self._updateLayerControls() self._updateUndoRedo() self._updateSelection() self.setWindowTitle(glyph.name, glyph.font) # setting the layer-glyph here app = QApplication.instance() app.setCurrentGlyph(glyph) # ----------------- # Layers management # ----------------- def _layerChanged(self, newLayerIndex): glyph = self.view.glyph() layer = self._currentLayerBox.itemData(newLayerIndex) if layer is None: layer = self._makeLayer() if layer is None: # restore comboBox to active index layerSet = glyph.layerSet index = layerSet.layerOrder.index(glyph.layer.name) self._setLayerBoxIndex(index) return if glyph.name in layer: newGlyph = layer[glyph.name] else: # TODO: make sure we mimic defcon ufo3 APIs for that newGlyph = self._makeLayerGlyph(layer, glyph) self.setGlyph(newGlyph) def _makeLayer(self): # TODO: what with duplicate names? glyph = self.view.glyph() newLayerName, color, ok = AddLayerDialog.getNewLayerNameAndColor(self) if ok: layerSet = glyph.layerSet layer = layerSet.newLayer(newLayerName) layer.color = color return layer return None def _makeLayerGlyph(self, layer, currentGlyph): glyph = layer.newGlyph(currentGlyph.name) glyph.width = currentGlyph.width glyph.template = True return glyph def _updateLayerControls(self): comboBox = self._currentLayerBox glyph = self.view.glyph() comboBox.blockSignals(True) comboBox.clear() for layer in glyph.layerSet: comboBox.addItem(layer.name, layer) comboBox.setCurrentText(glyph.layer.name) comboBox.addItem(self.tr("New layer…"), None) comboBox.blockSignals(False) if not hasattr(self, "_layerAction"): return self._layerAction.setEnabled(len(glyph.layerSet) > 1) def _setLayerBoxIndex(self, index): comboBox = self._currentLayerBox comboBox.blockSignals(True) comboBox.setCurrentIndex(index) comboBox.blockSignals(False) # --------------------- # QMainWindow functions # --------------------- def sizeHint(self): return QSize(1100, 750) def moveEvent(self, event): self.writeSettings() resizeEvent = moveEvent def event(self, event): if event.type() == QEvent.WindowActivate: app = QApplication.instance() app.setCurrentGlyph(self.view.glyph()) return super().event(event) def showEvent(self, event): app = QApplication.instance() data = dict(window=self) app.postNotification("glyphWindowWillOpen", data) super().showEvent(event) app.postNotification("glyphWindowOpened", data) def closeEvent(self, event): super().closeEvent(event) if event.isAccepted(): app = QApplication.instance() app.dispatcher.removeObserver(self, "drawingToolRegistered") data = dict(window=self) app.postNotification("glyphWindowWillClose", data) glyph = self.view.glyph() self._unsubscribeFromGlyph(glyph) self.view.closeEvent(event) def setWindowTitle(self, title, font=None): if font is not None: title = "%s – %s %s" % ( title, font.info.familyName, font.info.styleName) super().setWindowTitle(title)
class ToolbarController: def __init__(self, parent_window, app): self.parent = parent_window self.toolbar = QToolBar() self.app = app self.populating_tools = True def __get_combo_box(self, action_name) -> QComboBox: toolbar_actions = self.toolbar.actions() tags_list_action = next(act for act in toolbar_actions if act.text() == action_name) return tags_list_action.defaultWidget() def init(self): tools_combo = self.__get_combo_box(DEVTOOLS_COMBO_NAME) tools_combo.clear() for ek, ev in tool_plugins.items(): tools_combo.addItem(ev.tool.name, ek) # To avoid creating any view while we are populating tools in the combo box self.populating_tools = False selected_tool = self.app.data.get_selected_tool() found = tools_combo.findData(selected_tool) tools_combo.setCurrentIndex(found if found > 0 else 0) # Manually triggering to render the view as the index is not changing if found <= 0: self.on_toolbar_tool_changed(selected_tool) def on_toolbar_tool_changed(self, _): if self.populating_tools: return tools_combo = self.__get_combo_box("DevTools") current_tool = tools_combo.currentData() self.app.data.update_selected_tool(current_tool) def focus_on_devtools_combo_box(self): tools_combo = self.__get_combo_box("DevTools") tools_combo.setFocus(True) tools_combo.showPopup() def init_items(self): self.toolbar.setObjectName("maintoolbar") self.parent.addToolBar(Qt.TopToolBarArea, self.toolbar) self.toolbar.setMovable(False) self.toolbar.addSeparator() toolbar_ctx_list = QComboBox(self.parent) toolbar_ctx_list.setDuplicatesEnabled(False) toolbar_ctx_list.setEditable(True) toolbar_ctx_list.currentIndexChanged[str].connect( lambda new_tool: self.on_toolbar_tool_changed(new_tool)) toolbar_ctx_list_action = QWidgetAction(self.parent) toolbar_ctx_list_action.setText(DEVTOOLS_COMBO_NAME) toolbar_ctx_list_action.setDefaultWidget(toolbar_ctx_list) self.toolbar.addAction(toolbar_ctx_list_action) spacer = QWidget(self.parent) spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.toolbar.addWidget(spacer) toolbar_configure_action = QAction(QIcon(":/images/configure-48.png"), "Settings", self.parent) toolbar_configure_action.triggered.connect( self.parent.config_controller.show_dialog) self.toolbar.addAction(toolbar_configure_action)
class MainWindow(QMainWindow, Ui_MainWindow): releases_page: QUrl = QUrl( 'https://github.com/namuan/anchor-app-osx/releases/latest') def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.setupUi(self) self.resize(1024, 768) self.setWindowTitle('Anchor App - Improving development workflow') self.setWindowIcon(QIcon(":/images/anchor.png")) # Add Components on Main Window self.updater = Updater(self) self.menu_bar = self.menuBar() self.tool_bar = QToolBar() self.status_bar = self.statusBar() self.status_bar.showMessage('Ready', 5000) # Custom Dialogs self.configuration_dialog = ConfigurationDialog(self) self.progress_dialog = ProgressDialog(self) self.feature_branch_dialog = FeatureBranchDialog(self) # Initialise Components file_menu(self) tool_bar_items(self) # Initialise Presenters self.presenter = MainPresenter(self) self.tickets_list_presenter = TicketsListPresenter( self.tickets_list_widget, self) self.ticket_content_presenter = TicketContentPresenter(self) self.git_stats_presenter = GitStatsPresenter(self) self.apps_presenter = AppsPresenter(self) self.ticket_state_presenter = TicketStatePresenter(self) # Initialise Sub-Systems sys.excepthook = MainWindow.log_uncaught_exceptions @staticmethod def log_uncaught_exceptions(cls, exc, tb) -> None: logging.critical(''.join(traceback.format_tb(tb))) logging.critical('{0}: {1}'.format(cls, exc)) # Main Window events def resizeEvent(self, event): self.presenter.after_load() def closeEvent(self, event: QCloseEvent): logging.info("Received close event") event.accept() self.presenter.shutdown() try: qApp.exit(0) except: pass # End Main Window events def check_updates(self): self.updater.check() def update_available(self, latest, current): update_available = True if latest > current else False logging.info( f"Update Available ({latest} > {current}) ? ({update_available}) Enable Toolbar Icon" ) if update_available: toolbar_actions = self.tool_bar.actions() updates_action = next(act for act in toolbar_actions if act.text() == 'Update Available') if updates_action: updates_action.setIcon(QIcon(":/images/download-48.png")) updates_action.setEnabled(True) def open_releases_page(self) -> None: QDesktopServices.openUrl(self.releases_page) def refresh_all_tickets(self): self.presenter.refresh_all_tickets() def show_progress_dialog(self, message): self.progress_dialog.show_dialog(message) def hide_progress_dialog(self): self.progress_dialog.hide_dialog() def update_progress_dialog(self, percent_completed, message): self.progress_dialog.update_status(percent_completed, message) def show_jira_configuration_dialog(self): self.configuration_dialog.show_dialog() def show_branch_setup_dialog(self, selected_ticket): self.feature_branch_dialog.show_dialog(selected_ticket) def open_directory(self, dialog_title, dialog_location, flags): return QFileDialog.getExistingDirectory(self, dialog_title, dialog_location, flags) def open_file(self, dialog_title, dialog_location): return QFileDialog.getOpenFileName(self, dialog_title, dialog_location)
class MainGlyphWindow(QMainWindow): def __init__(self, glyph, parent=None): super().__init__(parent) menuBar = self.menuBar() fileMenu = QMenu(self.tr("&File"), self) fileMenu.addAction(self.tr("&Close"), self.close, QKeySequence.Quit) menuBar.addMenu(fileMenu) editMenu = QMenu(self.tr("&Edit"), self) self._undoAction = editMenu.addAction(self.tr("&Undo"), self.undo, QKeySequence.Undo) self._redoAction = editMenu.addAction(self.tr("&Redo"), self.redo, QKeySequence.Redo) editMenu.addSeparator() # XXX action = editMenu.addAction(self.tr("C&ut"), self.cutOutlines, QKeySequence.Cut) action.setEnabled(False) self._copyAction = editMenu.addAction(self.tr("&Copy"), self.copyOutlines, QKeySequence.Copy) editMenu.addAction(self.tr("&Paste"), self.pasteOutlines, QKeySequence.Paste) editMenu.addAction(self.tr("Select &All"), self.selectAll, QKeySequence.SelectAll) editMenu.addAction(self.tr("&Deselect"), self.deselect, "Ctrl+D") menuBar.addMenu(editMenu) glyphMenu = QMenu(self.tr("&Glyph"), self) glyphMenu.addAction(self.tr("&Next Glyph"), lambda: self.glyphOffset(1), "End") glyphMenu.addAction(self.tr("&Previous Glyph"), lambda: self.glyphOffset(-1), "Home") glyphMenu.addAction(self.tr("&Go To…"), self.changeGlyph, "G") glyphMenu.addSeparator() self._layerAction = glyphMenu.addAction(self.tr("&Layer Actions…"), self.layerActions, "L") menuBar.addMenu(glyphMenu) # create tools and buttons toolBars self._tools = [] self._toolsToolBar = QToolBar(self.tr("Tools"), self) self._toolsToolBar.setMovable(False) self._buttons = [] self._buttonsToolBar = QToolBar(self.tr("Buttons"), self) self._buttonsToolBar.setMovable(False) self.addToolBar(self._toolsToolBar) self.addToolBar(self._buttonsToolBar) # http://www.setnode.com/blog/right-aligning-a-button-in-a-qtoolbar/ self._layersToolBar = QToolBar(self.tr("Layers"), self) spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self._currentLayerBox = QComboBox(self) self._currentLayerBox.currentIndexChanged.connect(self._layerChanged) self._layersToolBar.addWidget(spacer) self._layersToolBar.addWidget(self._currentLayerBox) self._layersToolBar.setContentsMargins(0, 0, 2, 0) self._layersToolBar.setMovable(False) self.addToolBar(self._layersToolBar) viewMenu = self.createPopupMenu() viewMenu.setTitle(self.tr("View")) viewMenu.addSeparator() action = viewMenu.addAction(self.tr("Lock Toolbars"), self.lockToolBars) action.setCheckable(True) action.setChecked(True) menuBar.addMenu(viewMenu) self.view = GlyphView(self) self.setGlyph(glyph) selectionTool = self.installTool(SelectionTool) selectionTool.trigger() self.installTool(PenTool) self.installTool(RulerTool) self.installTool(KnifeTool) self.installButton(RemoveOverlapButton) self.setCentralWidget(self.view.scrollArea()) self.resize(900, 700) self.view.setFocus(True) # ---------- # Menu items # ---------- def glyphOffset(self, offset): currentGlyph = self.view.glyph() font = currentGlyph.font glyphOrder = font.glyphOrder # should be enforced in fontView already if not (glyphOrder and len(glyphOrder)): return index = glyphOrder.index(currentGlyph.name) newIndex = (index + offset) % len(glyphOrder) glyph = font[glyphOrder[newIndex]] self.setGlyph(glyph) def changeGlyph(self): glyph = self.view.glyph() newGlyph, ok = GotoDialog.getNewGlyph(self, glyph) if ok and newGlyph is not None: self.setGlyph(newGlyph) def layerActions(self): glyph = self.view.glyph() newLayer, action, ok = LayerActionsDialog.getLayerAndAction( self, glyph) if ok and newLayer is not None: # TODO: whole glyph for now, but consider selection too if not glyph.name in newLayer: newLayer.newGlyph(glyph.name) otherGlyph = newLayer[glyph.name] otherGlyph.disableNotifications() if action == "Swap": tempGlyph = TGlyph() otherGlyph.drawPoints(tempGlyph.getPointPen()) tempGlyph.width = otherGlyph.width otherGlyph.clearContours() glyph.drawPoints(otherGlyph.getPointPen()) otherGlyph.width = glyph.width if action != "Copy": glyph.disableNotifications() glyph.clearContours() if action == "Swap": tempGlyph.drawPoints(glyph.getPointPen()) glyph.width = tempGlyph.width glyph.enableNotifications() otherGlyph.enableNotifications() def undo(self): glyph = self.view.glyph() glyph.undo() def redo(self): glyph = self.view.glyph() glyph.redo() def cutOutlines(self): pass def copyOutlines(self): glyph = self.view.glyph() clipboard = QApplication.clipboard() mimeData = QMimeData() copyGlyph = glyph.getRepresentation("defconQt.FilterSelection") mimeData.setData( "application/x-defconQt-glyph-data", pickle.dumps([copyGlyph.serialize(blacklist=("name", "unicode"))])) clipboard.setMimeData(mimeData) def pasteOutlines(self): glyph = self.view.glyph() clipboard = QApplication.clipboard() mimeData = clipboard.mimeData() if mimeData.hasFormat("application/x-defconQt-glyph-data"): data = pickle.loads( mimeData.data("application/x-defconQt-glyph-data")) if len(data) == 1: pen = glyph.getPointPen() pasteGlyph = TGlyph() pasteGlyph.deserialize(data[0]) # TODO: if we serialize selected state, we don't need to do # this pasteGlyph.selected = True if len(pasteGlyph) or len(pasteGlyph.components) or \ len(pasteGlyph.anchors): glyph.prepareUndo() pasteGlyph.drawPoints(pen) def selectAll(self): glyph = self.view.glyph() glyph.selected = True if not len(glyph): for component in glyph.components: component.selected = True def deselect(self): glyph = self.view.glyph() for anchor in glyph.anchors: anchor.selected = False for component in glyph.components: component.selected = False glyph.selected = False def lockToolBars(self): action = self.sender() movable = not action.isChecked() for toolBar in (self._toolsToolBar, self._buttonsToolBar, self._layersToolBar): toolBar.setMovable(movable) # -------------------------- # Tools & buttons management # -------------------------- def installTool(self, tool): action = self._toolsToolBar.addAction(QIcon(tool.iconPath), tool.name, self._setViewTool) action.setCheckable(True) num = len(self._tools) action.setData(num) action.setShortcut(QKeySequence(str(num + 1))) self._tools.append(tool(parent=self.view)) return action def uninstallTool(self, tool): pass # XXX def _setViewTool(self): action = self.sender() index = action.data() newTool = self._tools[index] if newTool == self.view.currentTool(): action.setChecked(True) return ok = self.view.setCurrentTool(newTool) # if view did change tool, disable them all and enable the one we want # otherwise, just disable the tool that was clicked. # previously we used QActionGroup to have exclusive buttons, but doing # it manually allows us to NAK a button change. if ok: for act in self._toolsToolBar.actions(): act.setChecked(False) action.setChecked(ok) def installButton(self, button): action = self._buttonsToolBar.addAction(QIcon(button.iconPath), button.name, self._buttonAction) action.setData(len(self._buttons)) self._buttons.append(button(parent=self.view)) return action def uninstallButton(self, button): pass # XXX def _buttonAction(self): action = self.sender() index = action.data() button = self._buttons[index] button.clicked() # -------------------- # Notification support # -------------------- # glyph def _subscribeToGlyph(self, glyph): if glyph is not None: glyph.addObserver(self, "_glyphChanged", "Glyph.Changed") glyph.addObserver(self, "_glyphNameChanged", "Glyph.NameChanged") glyph.addObserver(self, "_glyphSelectionChanged", "Glyph.SelectionChanged") undoManager = glyph.undoManager undoManager.canUndoChanged.connect(self._undoAction.setEnabled) undoManager.canRedoChanged.connect(self._redoAction.setEnabled) self._subscribeToFontAndLayerSet(glyph.font) def _unsubscribeFromGlyph(self, glyph): if glyph is not None: glyph.removeObserver(self, "Glyph.Changed") glyph.removeObserver(self, "Glyph.NameChanged") glyph.removeObserver(self, "Glyph.SelectionChanged") undoManager = glyph.undoManager undoManager.canUndoChanged.disconnect(self._undoAction.setEnabled) undoManager.canRedoChanged.disconnect(self._redoAction.setEnabled) self._unsubscribeFromFontAndLayerSet(glyph.font) def _glyphChanged(self, notification): self.view.glyphChanged() def _glyphNameChanged(self, notification): glyph = self.view.glyph() self.setWindowTitle(glyph.name, glyph.font) def _glyphSelectionChanged(self, notification): self._updateSelection() self.view.glyphChanged() def _fontInfoChanged(self, notification): self.view.fontInfoChanged() glyph = self.view.glyph() self.setWindowTitle(glyph.name, glyph.font) # layers & font def _subscribeToFontAndLayerSet(self, font): """Note: called by _subscribeToGlyph.""" if font is None: return font.info.addObserver(self, "_fontInfoChanged", "Info.Changed") layerSet = font.layers if layerSet is None: return layerSet.addObserver(self, '_layerSetLayerDeleted', 'LayerSet.LayerDeleted') for event in ('LayerSet.LayerAdded', 'LayerSet.LayerChanged', 'LayerSet.LayerOrderChanged'): layerSet.addObserver(self, '_layerSetEvents', event) def _unsubscribeFromFontAndLayerSet(self, font): """Note: called by _unsubscribeFromGlyph.""" if font is None: return font.info.removeObserver(self, "Info.Changed") layerSet = font.layers if layerSet is None: return for event in ('LayerSet.LayerAdded', 'LayerSet.LayerChanged', 'LayerSet.LayerOrderChanged', 'LayerSet.LayerDeleted'): layerSet.removeObserver(self, event) def _layerSetEvents(self, notification): self._updateLayerControls() def _layerSetLayerDeleted(self, notification): self._layerSetEvents(notification) self._currentLayerBox.setCurrentIndex(0) # other updaters def _updateUndoRedo(self): glyph = self.view.glyph() self._undoAction.setEnabled(glyph.canUndo()) self._redoAction.setEnabled(glyph.canRedo()) def _updateSelection(self): def hasSelection(): glyph = self.view.glyph() for contour in glyph: if len(contour.selection): return True for anchor in glyph.anchors: if anchor.selected: return True for component in glyph.components: if component.selected: return True return False self._copyAction.setEnabled(hasSelection()) # -------------- # Public Methods # -------------- def setGlyph(self, glyph): currentGlyph = self.view.glyph() self._unsubscribeFromGlyph(currentGlyph) self._subscribeToGlyph(glyph) self.view.setGlyph(glyph) self._updateLayerControls() self._updateUndoRedo() self._updateSelection() self.setWindowTitle(glyph.name, glyph.font) def setDrawingAttribute(self, attr, value, layerName=None): self.view.setDrawingAttribute(attr, value, layerName) def drawingAttribute(self, attr, layerName=None): return self.view.drawingAttribute(attr, layerName) # ----------------- # Layers management # ----------------- def _layerChanged(self, newLayerIndex): glyph = self.view.glyph() layer = self._currentLayerBox.itemData(newLayerIndex) if layer is None: layer = self._makeLayer() if layer is None: # restore comboBox to active index layerSet = glyph.layerSet index = layerSet.layerOrder.index(glyph.layer.name) self._setLayerBoxIndex(index) return if glyph.name in layer: newGlyph = layer[glyph.name] else: # TODO: make sure we mimic defcon ufo3 APIs for that newGlyph = self._makeLayerGlyph(layer, glyph) self.setGlyph(newGlyph) # setting the layer-glyph here app = QApplication.instance() app.setCurrentGlyph(newGlyph) def _makeLayer(self): # TODO: what with duplicate names? glyph = self.view.glyph() newLayerName, ok = AddLayerDialog.getNewLayerName(self) if ok: layerSet = glyph.layerSet # TODO: this should return the layer layerSet.newLayer(newLayerName) return layerSet[newLayerName] else: return None def _makeLayerGlyph(self, layer, currentGlyph): glyph = layer.newGlyph(currentGlyph.name) glyph.width = currentGlyph.width glyph.template = True return glyph def _updateLayerControls(self): comboBox = self._currentLayerBox glyph = self.view.glyph() comboBox.blockSignals(True) comboBox.clear() for layer in glyph.layerSet: comboBox.addItem(layer.name, layer) comboBox.setCurrentText(glyph.layer.name) comboBox.addItem(self.tr("New layer…"), None) comboBox.blockSignals(False) self._layerAction.setEnabled(len(glyph.layerSet) > 1) def _setLayerBoxIndex(self, index): comboBox = self._currentLayerBox comboBox.blockSignals(True) comboBox.setCurrentIndex(index) comboBox.blockSignals(False) # --------------------- # QMainWindow functions # --------------------- def event(self, event): if event.type() == QEvent.WindowActivate: app = QApplication.instance() app.setCurrentGlyph(self.view.glyph()) return super().event(event) def closeEvent(self, event): glyph = self.view.glyph() self._unsubscribeFromGlyph(glyph) event.accept() def setWindowTitle(self, title, font=None): if font is not None: title = "%s – %s %s" % (title, font.info.familyName, font.info.styleName) super().setWindowTitle(title)
class UserSettingsDialog(QMainWindow): """ A User Settings/Defaults dialog. """ MAC_UNIFIED = True def __init__(self, parent=None, **kwargs): QMainWindow.__init__(self, parent, **kwargs) self.setWindowFlags(Qt.Dialog) self.setWindowModality(Qt.ApplicationModal) self.layout().setSizeConstraint(QVBoxLayout.SetFixedSize) self.__macUnified = sys.platform == "darwin" and self.MAC_UNIFIED self._manager = BindingManager(self, submitPolicy=BindingManager.AutoSubmit) self.__loop = None self.__settings = config.settings() self.__setupUi() def __setupUi(self): """Set up the UI. """ if self.__macUnified: self.tab = QToolBar() self.addToolBar(Qt.TopToolBarArea, self.tab) self.setUnifiedTitleAndToolBarOnMac(True) # This does not seem to work self.setWindowFlags(self.windowFlags() & \ ~Qt.MacWindowToolBarButtonHint) self.tab.actionTriggered[QAction].connect( self.__macOnToolBarAction) central = QStackedWidget() central.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) else: self.tab = central = QTabWidget(self) # Add a close button to the bottom of the dialog # (to satisfy GNOME 3 which shows the dialog without a title bar). container = container_widget_helper() container.layout().addWidget(central) buttonbox = QDialogButtonBox(QDialogButtonBox.Close) buttonbox.rejected.connect(self.close) container.layout().addWidget(buttonbox) self.setCentralWidget(container) self.stack = central # General Tab tab = QWidget() self.addTab(tab, self.tr("General"), toolTip=self.tr("General Options")) form = QFormLayout() tab.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) nodes = QWidget(self, objectName="nodes") nodes.setLayout(QVBoxLayout()) nodes.layout().setContentsMargins(0, 0, 0, 0) cb_anim = QCheckBox(self.tr("Enable node animations"), objectName="enable-node-animations", toolTip=self.tr( "Enable shadow and ping animations for nodes " "in the workflow.")) self.bind(cb_anim, "checked", "schemeedit/enable-node-animations") nodes.layout().addWidget(cb_anim) form.addRow(self.tr("Nodes"), nodes) links = QWidget(self, objectName="links") links.setLayout(QVBoxLayout()) links.layout().setContentsMargins(0, 0, 0, 0) cb_show = QCheckBox(self.tr("Show channel names between widgets"), objectName="show-channel-names", toolTip=self.tr( "Show source and sink channel names " "over the links.")) self.bind(cb_show, "checked", "schemeedit/show-channel-names") links.layout().addWidget(cb_show) form.addRow(self.tr("Links"), links) quickmenu = QWidget(self, objectName="quickmenu-options") quickmenu.setLayout(QVBoxLayout()) quickmenu.layout().setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox(self.tr("On double click"), toolTip=self.tr("Open quick menu on a double click " "on an empty spot in the canvas")) cb2 = QCheckBox(self.tr("On right click"), toolTip=self.tr("Open quick menu on a right click " "on an empty spot in the canvas")) cb3 = QCheckBox(self.tr("On space key press"), toolTip=self.tr("On Space key press while the mouse" "is hovering over the canvas.")) cb4 = QCheckBox(self.tr("On any key press"), toolTip=self.tr("On any key press while the mouse" "is hovering over the canvas.")) self.bind(cb1, "checked", "quickmenu/trigger-on-double-click") self.bind(cb2, "checked", "quickmenu/trigger-on-right-click") self.bind(cb3, "checked", "quickmenu/trigger-on-space-key") self.bind(cb4, "checked", "quickmenu/trigger-on-any-key") quickmenu.layout().addWidget(cb1) quickmenu.layout().addWidget(cb2) quickmenu.layout().addWidget(cb3) quickmenu.layout().addWidget(cb4) form.addRow(self.tr("Open quick menu on"), quickmenu) startup = QWidget(self, objectName="startup-group") startup.setLayout(QVBoxLayout()) startup.layout().setContentsMargins(0, 0, 0, 0) cb_splash = QCheckBox(self.tr("Show splash screen"), self, objectName="show-splash-screen") cb_welcome = QCheckBox(self.tr("Show welcome screen"), self, objectName="show-welcome-screen") self.bind(cb_splash, "checked", "startup/show-splash-screen") self.bind(cb_welcome, "checked", "startup/show-welcome-screen") startup.layout().addWidget(cb_splash) startup.layout().addWidget(cb_welcome) form.addRow(self.tr("On startup"), startup) toolbox = QWidget(self, objectName="toolbox-group") toolbox.setLayout(QVBoxLayout()) toolbox.layout().setContentsMargins(0, 0, 0, 0) exclusive = QCheckBox(self.tr("Only one tab can be open at a time")) self.bind(exclusive, "checked", "mainwindow/toolbox-dock-exclusive") toolbox.layout().addWidget(exclusive) form.addRow(self.tr("Tool box"), toolbox) tab.setLayout(form) # Output Tab tab = QWidget() self.addTab(tab, self.tr("Output"), toolTip="Output Redirection") form = QFormLayout() box = QWidget(self, objectName="streams") layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox(self.tr("Standard output")) cb2 = QCheckBox(self.tr("Standard error")) self.bind(cb1, "checked", "output/redirect-stdout") self.bind(cb2, "checked", "output/redirect-stderr") layout.addWidget(cb1) layout.addWidget(cb2) box.setLayout(layout) form.addRow(self.tr("Redirect output"), box) box = QWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) combo = QComboBox() combo.addItems([ self.tr("Critical"), self.tr("Error"), self.tr("Warn"), self.tr("Info"), self.tr("Debug") ]) cb = QCheckBox(self.tr("Show output on 'Error'"), objectName="focus-on-error") self.bind(combo, "currentIndex", "logging/level") self.bind(cb, "checked", "output/show-on-error") layout.addWidget(combo) layout.addWidget(cb) box.setLayout(layout) form.addRow(self.tr("Logging"), box) box = QWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox(self.tr("Stay on top"), objectName="stay-on-top") cb2 = QCheckBox(self.tr("Dockable"), objectName="output-dockable") self.bind(cb1, "checked", "output/stay-on-top") self.bind(cb2, "checked", "output/dockable") layout.addWidget(cb1) layout.addWidget(cb2) box.setLayout(layout) form.addRow(self.tr("Output window"), box) box = QWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox(self.tr("Open in external browser"), objectName="open-in-external-browser") cb2 = QCheckBox(self.tr("Stay on top"), objectName="help-stay-on-top") cb3 = QCheckBox(self.tr("Dockable"), objectName="help-dockable") self.bind(cb1, "checked", "help/open-in-external-browser") self.bind(cb2, "checked", "help/stay-on-top") self.bind(cb3, "checked", "help/dockable") layout.addWidget(cb1) layout.addWidget(cb2) layout.addWidget(cb3) box.setLayout(layout) form.addRow(self.tr("Help window"), box) tab.setLayout(form) # Categories Tab tab = QWidget() layout = QVBoxLayout() view = QListView() from .. import registry reg = registry.global_registry() model = QStandardItemModel() settings = QSettings() for cat in reg.categories(): item = QStandardItem() item.setText(cat.name) item.setCheckable(True) visible, _ = category_state(cat, settings) item.setCheckState(Qt.Checked if visible else Qt.Unchecked) model.appendRow([item]) view.setModel(model) layout.addWidget(view) tab.setLayout(layout) model.itemChanged.connect(lambda item: save_category_state( reg.category(str(item.text())), _State(item.checkState() == Qt.Checked, -1), settings)) self.addTab(tab, "Categories") if self.__macUnified: # Need some sensible size otherwise mac unified toolbar 'takes' # the space that should be used for layout of the contents self.adjustSize() def addTab(self, widget, text, toolTip=None, icon=None): if self.__macUnified: action = QAction(text, self) if toolTip: action.setToolTip(toolTip) if icon: action.setIcon(toolTip) action.setData(len(self.tab.actions())) self.tab.addAction(action) self.stack.addWidget(widget) else: i = self.tab.addTab(widget, text) if toolTip: self.tab.setTabToolTip(i, toolTip) if icon: self.tab.setTabIcon(i, icon) def widget(self, index): if self.__macUnified: return self.stack.widget(index) else: return self.tab.widget(index) def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.hide() self.deleteLater() def bind(self, source, source_property, key, transformer=None): target = UserDefaultsPropertyBinding(self.__settings, key) source = PropertyBinding(source, source_property) source.set(target.get()) self._manager.bind(target, source) def commit(self): self._manager.commit() def revert(self): self._manager.revert() def reset(self): for target, source in self._manager.bindings(): try: source.reset() except NotImplementedError: # Cannot reset. pass except Exception: log.error("Error reseting %r", source.propertyName, exc_info=True) def exec_(self): self.__loop = QEventLoop() self.show() status = self.__loop.exec_() self.__loop = None return status def hideEvent(self, event): QMainWindow.hideEvent(self, event) if self.__loop is not None: self.__loop.exit(0) self.__loop = None def __macOnToolBarAction(self, action): index = qunwrap(action.data()) self.stack.setCurrentIndex(index)
class TabbedWorkspace(BaseWorkspace): def __init__(self, stack, name, description=None, section='default', icon=None, acceptsDrops=False): super(TabbedWorkspace, self).__init__( stack, name, description=description, section=section, icon=icon, acceptsDrops=acceptsDrops ) self.wsTagRules = [] self.wsActions = {} self.toolBarCtrl = QToolBar() self.toolBarActions = QToolBar() self.toolBarActions.setObjectName('wsActionsToolBar') def wsToolTip(self): return self.wsDescription def wsTabs(self): for tidx in range(self.tabWidget.count()): yield tidx, self.tabWidget.widget(tidx) def wsFindTabWithName(self, name): for idx in range(0, self.tabWidget.count()): tName = self.tabWidget.tabText(idx) if tName.strip() == name.strip(): return self.tabWidget.widget(idx) def wsFindTabWithId(self, id): for tidx, tab in self.wsTabs(): if tab and tab.ctx.tabIdent == id: return tab def empty(self): return self.tabWidget.count() == 0 def workspaceIdx(self): return self.stack.indexOf(self) def setupWorkspace(self): # Workspace's tab widget and toolbars self.tabWidget = MainTabWidget(self) self.wLayout.addWidget(self.tabWidget) self.tabWidget.setElideMode(Qt.ElideMiddle) self.tabWidget.setUsesScrollButtons(True) self.tabWidget.onTabRemoved.connect( partialEnsure(self.wsTabRemoved)) self.tabWidget.currentChanged.connect( partialEnsure(self.wsTabChanged)) self.tabWidget.tabCloseRequested.connect( partialEnsure(self.onTabCloseRequest)) self.tabWidget.tabBarDoubleClicked.connect( partialEnsure(self.onTabDoubleClicked)) if self.app.system != 'Darwin': self.tabWidget.setDocumentMode(True) # Set the corner widgets # Workspace actions on the left, the right toolbar is unused for now self.setCornerRight(self.toolBarCtrl) self.setCornerLeft(self.toolBarActions) async def workspaceShutDown(self): pass def setCornerLeft(self, pButton): self.tabWidget.setCornerWidget(pButton, Qt.TopLeftCorner) def setCornerRight(self, nButton): self.tabWidget.setCornerWidget(nButton, Qt.TopRightCorner) def previousWorkspace(self): return self.stack.previousWorkspace(self) def nextWorkspace(self): return self.stack.nextWorkspace(self) def wsRegisterTab(self, tab, name, icon=None, current=False, tooltip=None, position='append'): if position == 'append': atIdx = self.tabWidget.count() elif position == 'nextcurrent': atIdx = self.tabWidget.currentIndex() + 1 if icon: idx = self.tabWidget.insertTab(atIdx, tab, icon, name) else: idx = self.tabWidget.insertTab(atIdx, tab, name) tab.workspaceAttach(self) if current is True: self.tabWidget.setCurrentWidget(tab) tab.setFocus(Qt.OtherFocusReason) if tooltip and idx: self.tabWidget.setTabToolTip(idx, tooltip) async def workspaceSwitched(self): await self.triggerDefaultActionIfEmpty() async def wsTabChanged(self, tabidx): tab = self.tabWidget.widget(tabidx) if tab: await tab.onTabChanged() async def wsTabRemoved(self, tabidx): await self.triggerDefaultActionIfEmpty() async def triggerDefaultActionIfEmpty(self): if self.empty() and self.defaultAction: self.defaultAction.trigger() def wsAddCustomAction(self, actionName: str, icon, name, func, default=False): action = self.toolBarActions.addAction( icon, name, func ) self.wsActions[actionName] = action if default is True and not self.defaultAction: self.defaultAction = action return action def wsAddAction(self, action: QAction, default=False): self.toolBarActions.addAction(action) if default is True and not self.defaultAction: self.defaultAction = action if len(list(self.toolBarActions.actions())) == 1 and \ not self.defaultAction: # Only one action yet, make it the default self.defaultAction = action def wsAddWidget(self, widget): self.toolBarActions.addWidget(widget) async def onTabDoubleClicked(self, idx): tab = self.tabWidget.widget(idx) if tab: await tab.onTabDoubleClicked() async def onTabCloseRequest(self, idx): tab = self.tabWidget.widget(idx) if tab is None: # TODO return if tab.sticky is True: # Sticky tab return if await tab.onClose() is True: self.tabWidget.removeTab(idx) tab.deleteLater() def wsSwitch(self, soundNotify=False): self.stack.setCurrentIndex(self.workspaceIdx()) if soundNotify and 0: playSound('wsswitch.wav') def wsTagRulesMatchesHashmark(self, hashmark): tags = [ { 'tag': tag.name } for tag in hashmark.iptags ] for rule in self.wsTagRules: res = list(rule.filter(tags)) if len(res) > 0: return True return False
class KubeRiderMainWindow(QMainWindow, Ui_MainWindow): def __init__(self, parent=None): super(KubeRiderMainWindow, self).__init__(parent) self.setupUi(self) # Add Components on Main Window self.updater = Updater(self) self.menu_bar = self.menuBar() self.toolbar = QToolBar() self.status_bar = self.statusBar() self.status_bar.showMessage('Ready', 5000) # Initialise Presenters self.presenter = KubeRiderMainPresenter(self) self.file_menu_presenter = FileMenuPresenter(self) self.toolbar_presenter = ToolbarPresenter(self.toolbar) self.pod_list_presenter = PodListPresenter(self.lst_pods) self.console_presenter = ConsolePresenter(self.console_text_edit) self.watch_presenter = WatchPresenter(self) self.pod_containers_presenter = PodContainersPresenter( self.lst_pod_containers) self.pod_events_presenter = PodEventsPresenter(self.txt_pod_events) self.pods_filter_presenter = PodsFilterPresenter(self) self.pod_logs_presenter = PodLogsPresenter(self) self.container_exec_presenter = ContainerExecPresenter(self) self.kube_resource_presenter = KubeResourcePresenter(self) # Custom Dialogs self.progress_dialog = ProgressDialog(self) self.configuration_dialog = ConfigurationDialog(self) # Initialise Components menu_items(self) toolbar_items(self) shortcut_items(self) # Initialise Sub-Systems sys.excepthook = KubeRiderMainWindow.log_uncaught_exceptions @staticmethod def log_uncaught_exceptions(cls, exc, tb) -> None: logging.critical(''.join(traceback.format_tb(tb))) logging.critical('{0}: {1}'.format(cls, exc)) # Main Window events def resizeEvent(self, event): self.presenter.after_window_loaded() def closeEvent(self, event: QCloseEvent): logging.info("Received close event") event.accept() self.presenter.shutdown() try: qApp.exit(0) except: pass # End Main Window events def check_updates(self): self.updater.check() def update_available(self, latest, current): update_available = True if latest > current else False logging.info( f"Update Available ({latest} > {current}) ? ({update_available}) Enable Toolbar Icon" ) if update_available: toolbar_actions = self.toolbar.actions() updates_action = next(act for act in toolbar_actions if act.text() == 'Update Available') if updates_action: updates_action.setIcon(QIcon(":/images/download-48.png")) updates_action.setEnabled(True) def open_releases_page(self) -> None: QDesktopServices.openUrl(self.releases_page)