def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.propertyToId = QMap() self.idToProperty = QMap() self.idToExpanded = QMap() editMenu = self.menuBar().addMenu(self.tr("Edit")) newObjectMenu = editMenu.addMenu(self.tr("New Object")) newRectangleAction = QAction(self.tr("Rectangle"), self) newRectangleAction.triggered.connect(self.newRectangle) newObjectMenu.addAction(newRectangleAction) newLineAction = QAction(self.tr("Line"), self) newLineAction.triggered.connect(self.newLine) newObjectMenu.addAction(newLineAction) newEllipseAction = QAction(self.tr("Ellipse"), self) newEllipseAction.triggered.connect(self.newEllipse) newObjectMenu.addAction(newEllipseAction) newTextAction = QAction(self.tr("Text"), self) newTextAction.triggered.connect(self.newText) newObjectMenu.addAction(newTextAction) self.deleteAction = QAction(self.tr("Delete Object"), self) self.deleteAction.triggered.connect(self.deleteObject) editMenu.addAction(self.deleteAction) clearAction = QAction(self.tr("Clear All"), self) clearAction.triggered.connect(self.clearAll) editMenu.addAction(clearAction) fillAction = QAction(self.tr("Fill View"), self) fillAction.triggered.connect(self.fillView) editMenu.addAction(fillAction) self.variantManager = QtVariantPropertyManager(self) self.variantManager.valueChangedSignal.connect(self.valueChanged) variantFactory = QtVariantEditorFactory(self) self.canvas = QtCanvas(800, 600) self.canvasView = CanvasView(self.canvas, self) self.setCentralWidget(self.canvasView) dock = QDockWidget(self) self.addDockWidget(Qt.RightDockWidgetArea, dock) self.propertyEditor = QtTreePropertyBrowser(dock) self.propertyEditor.setFactoryForManager(self.variantManager, variantFactory) dock.setWidget(self.propertyEditor) self.currentItem = QtCanvasItem(None) self.canvasView.itemClickedSignal.connect(self.itemClicked) self.canvasView.itemMovedSignal.connect(self.itemMoved) self.fillView() self.itemClicked(QtCanvasItem(None))
def __init__(self, parentObject, windowTitle, windowIcon=QIcon(), shortcut=None): QDockWidget.__init__(self, parentObject) self._showAction = None self.setObjectName(str(self.__class__)) self.setWindowTitle(windowTitle) self.setFeatures(self.features() & (~QDockWidget.DockWidgetFloatable)) if not windowIcon.isNull(): self.setWindowIcon(windowIcon) if shortcut is not None: self.showAction().setShortcut(shortcut) self._titleBar = _TitleBar(self) self.setTitleBarWidget(self._titleBar) if shortcut is not None: toolTip = "Move focus with <b>%s</b>,<br/>close with <b>Esc</b>" % shortcut else: toolTip = "Close with <b>Esc</b>" self._titleBar.setToolTip(toolTip) self._closeShortcut = QShortcut(QKeySequence("Esc"), self) self._closeShortcut.setContext(Qt.WidgetWithChildrenShortcut) self._closeShortcut.activated.connect(self._close)
def keyPressEvent(self, event): """Catch Esc. Not using QShortcut, because dock shall be closed, only if child widgets haven't catched Esc event """ if event.key() == Qt.Key_Escape and \ event.modifiers() == Qt.NoModifier: self._hide() else: QDockWidget.keyPressEvent(self, event)
def add_view(self, name, widget,parent,first): dock = QDockWidget(name, self) dock.setAllowedAreas(Qt.AllDockWidgetAreas) dock.setWidget(widget) widget.show() if first : self.addDockWidget(Qt.TopDockWidgetArea, dock) else : self.tabifyDockWidget(parent,dock) return dock
def __init__(self): super(AssetWindow, self).__init__() self.prevDockWidget = None aw = AssetWidget(self) qd = QDockWidget() qd.setWidget(aw) self.setCentralWidget(qd) self.setMinimumSize(480,360) self.setMaximumSize(1920,1080) self.setDockOptions(QMainWindow.AnimatedDocks | QMainWindow.ForceTabbedDocks);
def createTreeView(self): dockWidget = QDockWidget() dockWidget.setAllowedAreas(Qt.LeftDockWidgetArea) dockWidget.setFeatures(QDockWidget.NoDockWidgetFeatures) dockWidget.setTitleBarWidget(QWidget()) self.treeView = QTreeView() self.treeView.clicked.connect(self.treeItemClicked) self.treeModel = TreeModel() self.treeView.setModel(self.treeModel) self.logo = QLabel() logoPixmap = QPixmap(CMAKE_INSTALL_PREFIX + '/share/jderobot/resources/jderobot.png') self.logo.setPixmap(logoPixmap) self.upButton = QPushButton() self.upButton.setText('Up') self.upButton.clicked.connect(self.upButtonClicked) leftContainer = QWidget() leftLayout = QVBoxLayout() leftLayout.addWidget(self.treeView) leftLayout.addWidget(self.upButton) leftLayout.addWidget(self.logo) leftContainer.setLayout(leftLayout) dockWidget.setWidget(leftContainer) self.addDockWidget(Qt.LeftDockWidgetArea, dockWidget)
def createWidgets(self): """Cette fonction permet la création de tous les widgets de la mainWindow""" #Création toolbar toolBar = self.addToolBar("Tools") #Création bar recherche self.lineEditSearch = QLineEdit() self.lineEditSearch.setPlaceholderText("Recherche") self.lineEditSearch.setStyleSheet("background-color:white") toolBar.addWidget(self.lineEditSearch) self.lineEditSearch.setMaximumWidth(300) #Création séparateur toolBar.addSeparator() #Création icon add contact self.actionAdd = QAction("Ajouter (Ctrl+P)",self) toolBar.addAction(self.actionAdd) self.actionAdd.setShortcut("Ctrl+P") self.actionAdd.setIcon(QIcon("Pictures/sign.png")) #Création icon delete contact self.actionDelete = QAction("supprimer (Ctrl+D)",self) toolBar.addAction(self.actionDelete) self.actionDelete.setShortcut("Ctrl+D") self.actionDelete.setIcon(QIcon("Pictures/contacts.png")) #Création icon quit self.actionQuitter = QAction("Quitter (Ctrl+Q)",self) toolBar.addAction(self.actionQuitter) self.actionQuitter.setShortcut("Ctrl+Q") self.actionQuitter.setIcon(QIcon("Pictures/arrows.png")) #Création widget central self.centralWidget = QWidget() self.centralWidget.setStyleSheet("background-color:white") self.setCentralWidget(self.centralWidget) #Création dockWidget left dockDisplay = QDockWidget("Répertoire") dockDisplay.setStyleSheet("background-color:white") dockDisplay.setFeatures(QDockWidget.DockWidgetFloatable) dockDisplay.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.addDockWidget(Qt.LeftDockWidgetArea,dockDisplay) containDock = QWidget(dockDisplay) dockDisplay.setWidget(containDock) dockLayout = QVBoxLayout() displayWidget = QScrollArea() displayWidget.setWidgetResizable(1) dockLayout.addWidget(displayWidget) containDock.setLayout(dockLayout) #Ajouter la list au dockwidget self.listContact = QListWidget() displayWidget.setWidget(self.listContact)
def createDockWindows(self): dock = QDockWidget("Folders", self) dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) #Code to Create FileView Colums and FolderTree self.FileView = QtWidgets.QColumnView() self.FileView.setGeometry(QtCore.QRect(240, 10, 291, 281)) self.FolderTree = QtWidgets.QTreeView() self.FolderTree.setGeometry(QtCore.QRect(10, 10, 221, 281)) FolderTree = self.FolderTree #FolderTree.hidecolumn(1),... ?? to show only name column #include FolderTree to a Dock at the left side dock.setWidget(FolderTree) self.addDockWidget(Qt.LeftDockWidgetArea, dock) #set the model and rootpath for filling the FolderTree from self.ui dirmodel = QFileSystemModel() #set filter to show only folders dirmodel.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs) dirmodel.setRootPath(rootpath) #filemodel and filter for only files on right side filemodel = QFileSystemModel() filemodel.setFilter(QDir.NoDotAndDotDot | QDir.Files) filemodel.setRootPath(rootpath) FolderView = self.FolderTree FolderView.setModel(dirmodel) FolderView.setRootIndex(dirmodel.index(rootpath)) FileView = self.FileView FileView.setModel(filemodel) dock = QDockWidget("Files", self) dock.setWidget(FileView) self.addDockWidget(Qt.RightDockWidgetArea, dock) #important lines for the connection, which does not work self.FolderTree.clicked['QModelIndex'].connect(self.setpathonclick)
def createDockWindows(self): dock = QDockWidget("Available Garments Types", self) #dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.availableItems = QListWidget(dock) self.availableItems.setMinimumWidth(350) self.availableItems.setMaximumWidth(350) #self.availableItems.addItems(("stuff")) self.availableItems.itemClicked.connect(self.itemClicked_Click) dock.setWidget(self.availableItems) self.addDockWidget(Qt.RightDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) dock.hide() self.dock = QDockWidget("Available Garment Sizes", self) self.orderItem = QTreeWidget(dock) #self.orderItem.setMinimumWidth(350) #self.orderItem.setMaximumWidth(350) #self.orderItem.insertText(("more stuff")) self.dock.setWidget(self.orderItem) self.addDockWidget(Qt.RightDockWidgetArea, self.dock) self.viewMenu.addAction(self.dock.toggleViewAction()) self.dock.hide() #Create a tree widget for use when the t-shirt is clicked. self.treeDock = QDockWidget("Order Items", self) self.garmentTree = QTreeWidget(self.treeDock) self.garmentTree.setObjectName('garmentTree') self.garmentTree.itemClicked.connect(CSRWidgets.sumQuantity) self.garmentTree.itemClicked.connect(lambda: CSRWidgets.updateNameDesign(self)) self.garmentTree.setMaximumWidth(480) self.garmentTree.setMinimumWidth(480) self.treeDock.hide()
def __init__(self, parent, plugin): self.plugin = plugin QDockWidget.__init__(self, parent) self.setupUi(self) self.btnApply.setIcon(QIcon(":plugins/nominatim/arrow_green.png")) self.btnMask.setIcon(QIcon(":plugins/nominatim/add_mask.png")) self.btnLayer.setIcon(QIcon(":plugins/nominatim/add_layer.png")) self.tableResult.installEventFilter(self) # cf. eventFilter method self.tableResult.cellDoubleClicked.connect(self.onChoose) self.tableResult.cellEntered.connect(self.cellEntered) self.editSearch.returnPressed.connect(self.onReturnPressed) self.btnSearch.clicked.connect(self.onReturnPressed) self.btnApply.clicked.connect(self.onApply) self.btnHelp.clicked.connect(self.plugin.do_help) self.btnLocalize.clicked.connect(self.doLocalize) self.btnMask.clicked.connect(self.onMask) self.btnLayer.clicked.connect(self.onLayer) self.MultiPolygonLayerId = None self.LineLayerId = None self.PointLayerId = None try: self.cbExtent.setChecked(self.plugin.limitSearchToExtent) except: self.cbExtent.setChecked(self.plugin.limitSearchToExtent) self.currentExtent = self.plugin.canvas.extent() self.tableResult.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) try: self.editSearch.setText(self.plugin.lastSearch) except: pass try: if self.plugin.localiseOnStartup: self.doLocalize() except Exception as e: for m in e.args: QgsMessageLog.logMessage(m, 'Extensions') pass self.nominatim_networkAccessManager = QgsNetworkAccessManager.instance()
def _createElectronicsControl(self): """Creates a tabbed widget of voltage clamp and current clamp controls""" self._electronicsTab = QTabWidget(self) self._electronicsTab.addTab(self._getIClampCtrlBox(), 'Current clamp') self._electronicsTab.addTab(self._getVClampCtrlBox(), 'Voltage clamp') self._electronicsDock = QDockWidget(self) self._electronicsDock.setWidget(self._electronicsTab)
def titleBarWidget(self): """QToolBar on the title. You may add own actions to this tool bar """ # method was added only for documenting return QDockWidget.titleBarWidget(self)
def _initMaya(self): """ Initialize Maya-related state. Delete Maya nodes if there is an old document left over from the same session. Set up the Maya window. """ # There will only be one document if (app().active_document and app().active_document.win and not app().active_document.win.close()): return del app().active_document app().active_document = self import maya.OpenMayaUI as OpenMayaUI import sip ptr = OpenMayaUI.MQtUtil.mainWindow() mayaWin = sip.wrapinstance(int(ptr), QMainWindow) self.windock = QDockWidget("cadnano") self.windock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) self.windock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.windock.setWidget(self.win) mayaWin.addDockWidget(Qt.DockWidgetArea(Qt.LeftDockWidgetArea), self.windock) self.windock.setVisible(True)
def add_filesystem(self, home, file_manager): """ Adds the file system pane to the application. """ self.fs_pane = FileSystemPane(home) self.fs = QDockWidget(_('Filesystem on micro:bit')) self.fs.setWidget(self.fs_pane) self.fs.setFeatures(QDockWidget.DockWidgetMovable) self.fs.setAllowedAreas(Qt.BottomDockWidgetArea) self.addDockWidget(Qt.BottomDockWidgetArea, self.fs) self.fs_pane.setFocus() file_manager.on_list_files.connect(self.fs_pane.on_ls) self.fs_pane.list_files.connect(file_manager.ls) self.fs_pane.microbit_fs.put.connect(file_manager.put) self.fs_pane.microbit_fs.delete.connect(file_manager.delete) self.fs_pane.microbit_fs.list_files.connect(file_manager.ls) self.fs_pane.local_fs.get.connect(file_manager.get) self.fs_pane.local_fs.list_files.connect(file_manager.ls) file_manager.on_put_file.connect(self.fs_pane.microbit_fs.on_put) file_manager.on_delete_file.connect(self.fs_pane.microbit_fs.on_delete) file_manager.on_get_file.connect(self.fs_pane.local_fs.on_get) file_manager.on_list_fail.connect(self.fs_pane.on_ls_fail) file_manager.on_put_fail.connect(self.fs_pane.on_put_fail) file_manager.on_delete_fail.connect(self.fs_pane.on_delete_fail) file_manager.on_get_fail.connect(self.fs_pane.on_get_fail) self.connect_zoom(self.fs_pane) return self.fs_pane
def new_docked(self, widget, name, title, dock_area, hiden=False): dock = QDockWidget() dock.setWindowTitle(title) dock.setWidget(widget) if hiden: dock.hide() else: dock.show() self.main_window.addDockWidget(dock_area, dock) self.modules_dock[name] = dock return dock
def showDocumentation(self): if self._documentation is None: doc = QWebView(self) doc.load(QUrl("doc/html/index.html")) self._documentation = QDockWidget("Documentation", self) self._documentation.setWidget(doc) self._documentation.closeEvent = lambda _: self.hide_documentation() self.addDockWidget(Qt.LeftDockWidgetArea, self._documentation)
def showConsole(self): if not self.dockConsole: self.dockConsole = QDockWidget() self.dockConsole.setWindowTitle("Consola") self.addDockWidget(Qt.BottomDockWidgetArea, self.dockConsole) self.teo_ = OutputWindow() self.dockConsole.setWidget(self.teo_) self.dockConsole.setVisible(True)
def initoptionspanel(self): label = QLabel('Filter') filtertype = QComboBox() filtertype.addItem('None') filtertype.addItem('Lowpass') filtertype.addItem('Highpass') filtertype.addItem('Bandreject') filtertype.addItem('Bandpass') filtertype.currentIndexChanged.connect(self.filtertypelistener) self.filterfunction = QComboBox() self.filterfunction.addItem('Ideal') self.filterfunction.addItem('Butterworth') self.filterfunction.addItem('Gaussian') self.filterfunction.currentIndexChanged.connect(self.filterfunctionlistener) self.filterfunction.setEnabled(False) self.filtercutoff = QDoubleSpinBox() self.filtercutoff.setValue(0.0) self.filtercutoff.setRange(0.0, 10000.0) self.filtercutoff.valueChanged.connect(self.filtercutofflistener) self.filtercutoff.setEnabled(False) self.filterbandwidth = QDoubleSpinBox() self.filterbandwidth.setValue(1.0) self.filterbandwidth.setRange(0.0, 10000.0) self.filterbandwidth.valueChanged.connect(self.filterbandwidthlistener) self.filterbandwidth.setEnabled(False) self.filterorder = QDoubleSpinBox() self.filterorder.setValue(1.0) self.filterorder.setRange(0.0, 10000.0) self.filterorder.valueChanged.connect(self.filterorderlistener) self.filterorder.setEnabled(False) loader = QMovie('loader.gif') loader.start() self.loadercontainer = QLabel() self.loadercontainer.setMovie(loader) self.loadercontainer.setVisible(False) formlayout = QFormLayout() formlayout.addRow('Type', filtertype) formlayout.addRow('Function', self.filterfunction) formlayout.addRow('Cut off', self.filtercutoff) formlayout.addRow('Bandwidth', self.filterbandwidth) formlayout.addRow('Order', self.filterorder) formlayout.addRow('', self.loadercontainer) filterbox = QGroupBox('Filter') filterbox.setLayout(formlayout) options = QDockWidget('Options') options.setFeatures(QDockWidget.DockWidgetFloatable) options.setFeatures(QDockWidget.DockWidgetMovable) options.setWidget(filterbox) self.addDockWidget(Qt.RightDockWidgetArea, options)
def createDockWindows(self): dock = QDockWidget("Customers", self) dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.customerList = QListWidget(dock) self.customerList.addItems(( "John Doe, Harmony Enterprises, 12 Lakeside, Ambleton", "Jane Doe, Memorabilia, 23 Watersedge, Beaton", "Tammy Shea, Tiblanka, 38 Sea Views, Carlton", "Tim Sheen, Caraba Gifts, 48 Ocean Way, Deal", "Sol Harvey, Chicos Coffee, 53 New Springs, Eccleston", "Sally Hobart, Tiroli Tea, 67 Long River, Fedula")) dock.setWidget(self.customerList) self.addDockWidget(Qt.RightDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) dock = QDockWidget("Paragraphs", self) self.paragraphsList = QListWidget(dock) self.paragraphsList.addItems(( "Thank you for your payment which we have received today.", "Your order has been dispatched and should be with you within " "28 days.", "We have dispatched those items that were in stock. The rest of " "your order will be dispatched once all the remaining items " "have arrived at our warehouse. No additional shipping " "charges will be made.", "You made a small overpayment (less than $5) which we will keep " "on account for you, or return at your request.", "You made a small underpayment (less than $1), but we have sent " "your order anyway. We'll add this underpayment to your next " "bill.", "Unfortunately you did not send enough money. Please remit an " "additional $. Your order will be dispatched as soon as the " "complete amount has been received.", "You made an overpayment (more than $5). Do you wish to buy more " "items, or should we return the excess to you?")) dock.setWidget(self.paragraphsList) self.addDockWidget(Qt.RightDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) self.customerList.currentTextChanged.connect(self.insertCustomer) self.paragraphsList.currentTextChanged.connect(self.addParagraph)
def _createChannelControl(self): self._channelControlDock = QDockWidget('Channels', self) self._channelCtrlBox = QGroupBox(self) self._naConductanceToggle = QCheckBox('Block Na+ channel', self._channelCtrlBox) self._naConductanceToggle.setToolTip('<html>%s</html>' % (tooltip_NaChan)) self._kConductanceToggle = QCheckBox('Block K+ channel', self._channelCtrlBox) self._kConductanceToggle.setToolTip('<html>%s</html>' % (tooltip_KChan)) self._kOutLabel = QLabel('[K+]out (mM)', self._channelCtrlBox) self._kOutEdit = QLineEdit('%g' % (self.squid_setup.squid_axon.K_out), self._channelCtrlBox) self._kOutLabel.setToolTip('<html>%s</html>' % (tooltip_Nernst)) self._kOutEdit.setToolTip('<html>%s</html>' % (tooltip_Nernst)) set_default_line_edit_size(self._kOutEdit) self._naOutLabel = QLabel('[Na+]out (mM)', self._channelCtrlBox) self._naOutEdit = QLineEdit('%g' % (self.squid_setup.squid_axon.Na_out), self._channelCtrlBox) self._naOutLabel.setToolTip('<html>%s</html>' % (tooltip_Nernst)) self._naOutEdit.setToolTip('<html>%s</html>' % (tooltip_Nernst)) set_default_line_edit_size(self._naOutEdit) self._kInLabel = QLabel('[K+]in (mM)', self._channelCtrlBox) self._kInEdit = QLineEdit('%g' % (self.squid_setup.squid_axon.K_in), self._channelCtrlBox) self._kInEdit.setToolTip(tooltip_Nernst) self._naInLabel = QLabel('[Na+]in (mM)', self._channelCtrlBox) self._naInEdit = QLineEdit('%g' % (self.squid_setup.squid_axon.Na_in), self._channelCtrlBox) self._naInEdit.setToolTip('<html>%s</html>' % (tooltip_Nernst)) self._temperatureLabel = QLabel('Temperature (C)', self._channelCtrlBox) self._temperatureEdit = QLineEdit('%g' % (self.defaults['temperature'] - CELSIUS_TO_KELVIN), self._channelCtrlBox) self._temperatureEdit.setToolTip('<html>%s</html>' % (tooltip_Nernst)) set_default_line_edit_size(self._temperatureEdit) for child in self._channelCtrlBox.children(): if isinstance(child, QLineEdit): set_default_line_edit_size(child) layout = QGridLayout(self._channelCtrlBox) layout.addWidget(self._naConductanceToggle, 0, 0) layout.addWidget(self._kConductanceToggle, 1, 0) layout.addWidget(self._naOutLabel, 2, 0) layout.addWidget(self._naOutEdit, 2, 1) layout.addWidget(self._naInLabel, 3, 0) layout.addWidget(self._naInEdit, 3, 1) layout.addWidget(self._kOutLabel, 4, 0) layout.addWidget(self._kOutEdit, 4, 1) layout.addWidget(self._kInLabel, 5, 0) layout.addWidget(self._kInEdit, 5, 1) layout.addWidget(self._temperatureLabel, 6, 0) layout.addWidget(self._temperatureEdit, 6, 1) layout.setRowStretch(7, 1.0) self._channelCtrlBox.setLayout(layout) self._channelControlDock.setWidget(self._channelCtrlBox) return self._channelCtrlBox
def initUI(self): self.setStyleSheet("QCheckBox { background: palette(window); border-radius: 4px; padding: 2px; margin-right: 2px; }") self.toolBar = QToolBar(self) self.toolBar.setStyleSheet(stylesheet % (create_gradient("pastel"),)) self.toolBar.setMovable(False) self.toolBar.setContextMenuPolicy(Qt.CustomContextMenu) self.addToolBar(self.toolBar) self.reverseBox = QCheckBox("&Reverse", self) self.reverseBox.clicked.connect(lambda: self.updateGradient()) self.toolBar.addWidget(self.reverseBox) self.byWordBox = QCheckBox("By &word", self) self.toolBar.addWidget(self.byWordBox) self.bounceBox = QCheckBox("&Bounce", self) self.bounceBox.clicked.connect(lambda: self.updateGradient()) self.toolBar.addWidget(self.bounceBox) self.sizeList = QComboBox(self) self.sizeList.addItem("None") for num in range(1, 8): self.sizeList.addItem(str(num)) self.toolBar.addWidget(self.sizeList) self.cycleList = QComboBox(self) self.toolBar.addWidget(self.cycleList) self.cycleList.currentIndexChanged.connect(self.updateGradient) self.loadCycles() self.convertButton = QPushButton("&Convert", self) self.convertButton.clicked.connect(self.convert) self.toolBar.addWidget(self.convertButton) self.reloadButton = QPushButton("Reload", self) self.reloadButton.setShortcut("Alt+Shift+R") self.reloadButton.clicked.connect(self.loadCycles) self.toolBar.addWidget(self.reloadButton) self.inputDock = QDockWidget("Input", self) self.inputDock.setFeatures(QDockWidget.NoDockWidgetFeatures) self.inputDock.setContextMenuPolicy(Qt.CustomContextMenu) self.addDockWidget(Qt.LeftDockWidgetArea, self.inputDock) self.inputField = QTextEdit(self) self.inputField.setAcceptRichText(False) self.inputDock.setWidget(self.inputField) self.outputField = QTextEdit(self) self.outputField.setReadOnly(True) self.setCentralWidget(self.outputField)
def __init__(self,iface): QDockWidget.__init__(self) self.iface = iface GPSLogger.iface = iface self.canvas = self.iface.mapCanvas() self.setupUi(self) self.setupCustomUi() self.connection = GPSConnection(self.infoList, self.tvInfoList.model(), QgsCoordinateReferenceSystem(4326, QgsCoordinateReferenceSystem.EpsgCrsId)) self.marker = GPSMarker(self.canvas, path.join(self.pluginPath, 'markers/krzyz w okregu.svg'), self) self.path = GPSPath(self.canvas, self) self.dataWriter = GPSDataWriter(self) self.cmbLayers.setFilters( QgsMapLayerProxyModel.HasGeometry ) self.cmbLayers.layerChanged.connect(self.dataWriter.changeLayer) self.getLeftPoint = GPSGetCanvasPoint(self.canvas, self.btnA) self.getLeftPoint.pointSide = 'left' self.getRightPoint = GPSGetCanvasPoint(self.canvas, self.btnB) self.getRightPoint.pointSide = 'right' self.resection = GPSResection(self) self.selectedMarker = GPSSelectedMarker(self.canvas) self.logger = GPSLogger(self) self.lastGpsPoint = None self.measureType = None self.doIntervalMeasure = False self.setMenus() self.setProjectCrs() self.loadSettings() self.setupSignals() self.lastPointElevation = None self.groups_points = None self.groupBox_3.setVisible(False) self.pointListLogger = GPSMeasureSave(self.logger, QSettings().value('gpsTracker/measureSaveInterval', 1, type=int), QSettings().value('gpsTracker/measureSave', True, type=bool)) points = self.pointListLogger.loadMeasure() if points: groups = list(set([ p['group_id'] for p in points ])) self.addEnclaves(len(groups)-1) self.tvPointList.model().insertRows(points)
def _createDock(self, widgetClass, widgetName, widgetArea, *param): """创建停靠组件""" widget = widgetClass(*param) dock = QDockWidget(widgetName, self) dock.setWidget(widget) dock.setObjectName(widgetName) dock.setFeatures(QDockWidget.NoDockWidgetFeatures) self.addDockWidget(widgetArea, dock) return widget, dock
def add_plotter(self, plotter_pane, name): """ Adds the referenced plotter pane to the application. """ self.plotter_pane = plotter_pane self.plotter = QDockWidget(_('{} Plotter').format(name)) self.plotter.setWidget(plotter_pane) self.plotter.setFeatures(QDockWidget.DockWidgetMovable) self.plotter.setAllowedAreas(Qt.BottomDockWidgetArea | Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.addDockWidget(Qt.BottomDockWidgetArea, self.plotter) self.plotter_pane.set_theme(self.theme) self.plotter_pane.setFocus()
def add_debug_inspector(self): """ Display a debug inspector to view the call stack. """ self.debug_inspector = DebugInspector() self.debug_model = QStandardItemModel() self.debug_inspector.setModel(self.debug_model) self.inspector = QDockWidget(_('Debug Inspector')) self.inspector.setWidget(self.debug_inspector) self.inspector.setFeatures(QDockWidget.DockWidgetMovable) self.inspector.setAllowedAreas(Qt.BottomDockWidgetArea | Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.addDockWidget(Qt.RightDockWidgetArea, self.inspector) self.connect_zoom(self.debug_inspector)
def add_repl(self, repl_pane, name): """ Adds the referenced REPL pane to the application. """ self.repl_pane = repl_pane self.repl = QDockWidget(_('{} REPL').format(name)) self.repl.setWidget(repl_pane) self.repl.setFeatures(QDockWidget.DockWidgetMovable) self.repl.setAllowedAreas(Qt.BottomDockWidgetArea | Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.addDockWidget(Qt.BottomDockWidgetArea, self.repl) self.connect_zoom(self.repl_pane) self.repl_pane.set_theme(self.theme) self.repl_pane.setFocus()
def initinformationpanel(self): self.mselabel = QLabel('No image selected') infoform = QFormLayout() infoform.addRow('MSE:', self.mselabel) imagebox = QGroupBox('Image') imagebox.setLayout(infoform) information = QDockWidget('Information') information.setFeatures(QDockWidget.DockWidgetFloatable) information.setFeatures(QDockWidget.DockWidgetMovable) information.setWidget(imagebox) self.addDockWidget(Qt.RightDockWidgetArea, information)
def add_python3_runner(self, script_name, working_directory, interactive=False, debugger=False, command_args=None, runner=None, envars=None, python_args=None): """ Display console output for the referenced Python script. The script will be run within the workspace_path directory. If interactive is True (default is False) the Python process will run in interactive mode (dropping the user into the REPL when the script completes). If debugger is True (default is False) the script will be run within a debug runner session. The debugger overrides the interactive flag (you cannot run the debugger in interactive mode). If there is a list of command_args (the default is None) then these will be passed as further arguments into the command run in the new process. If runner is given, this is used as the command to start the Python process. If envars is given, these will become part of the environment context of the new chlid process. If python_args is given, these will be passed as arguments to the Python runtime used to launch the child process. """ self.process_runner = PythonProcessPane(self) self.runner = QDockWidget(_("Running: {}").format( os.path.basename(script_name))) self.runner.setWidget(self.process_runner) self.runner.setFeatures(QDockWidget.DockWidgetMovable) self.runner.setAllowedAreas(Qt.BottomDockWidgetArea | Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.addDockWidget(Qt.BottomDockWidgetArea, self.runner) self.process_runner.start_process(script_name, working_directory, interactive, debugger, command_args, envars, runner, python_args) self.process_runner.setFocus() self.process_runner.on_append_text.connect(self.on_stdout_write) self.connect_zoom(self.process_runner) return self.process_runner
def _createRunControl(self): self._runControlBox = QGroupBox(self) self._runControlBox.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self._runTimeLabel = QLabel("Run time (ms)", self._runControlBox) self._simTimeStepLabel = QLabel("Simulation time step (ms)", self._runControlBox) self._runTimeEdit = QLineEdit('%g' % (SquidGui.defaults['runtime']), self._runControlBox) set_default_line_edit_size(self._runTimeEdit) self._simTimeStepEdit = QLineEdit('%g' % (SquidGui.defaults['simdt']), self._runControlBox) set_default_line_edit_size(self._simTimeStepEdit) layout = QGridLayout() layout.addWidget(self._runTimeLabel, 0, 0) layout.addWidget(self._runTimeEdit, 0, 1) layout.addWidget(self._simTimeStepLabel, 1, 0) layout.addWidget(self._simTimeStepEdit, 1, 1) layout.setColumnStretch(2, 1.0) layout.setRowStretch(2, 1.0) self._runControlBox.setLayout(layout) self._runControlDock = QDockWidget('Simulation', self) self._runControlDock.setWidget(self._runControlBox)
def leaveEvent(self, event): self.entered = False if not self.shot and not self.isPinned() and not self.isFloating(): self.shot = True QTimer.singleShot(1000, self.autohide) return QDockWidget.leaveEvent(self, event)
class MainWindow(QMainWindow): """Pyspread main window""" gui_update = pyqtSignal(dict) def __init__(self, application): super().__init__() self._loading = True self.application = application self.settings = Settings(self) self.workflows = Workflows(self) self.undo_stack = QUndoStack(self) self.refresh_timer = QTimer() self._init_widgets() self.main_window_actions = MainWindowActions(self) self._init_window() self._init_toolbars() self.settings.restore() if self.settings.signature_key is None: self.settings.signature_key = genkey() self.show() self._update_action_toggles() # Update the GUI so that everything matches the model cell_attributes = self.grid.model.code_array.cell_attributes attributes = cell_attributes[self.grid.current] self.on_gui_update(attributes) self._loading = False self._previous_window_state = self.windowState() def _init_window(self): """Initialize main window components""" self.setWindowTitle(APP_NAME) self.setWindowIcon(Icon.pyspread) self.safe_mode_widget = QSvgWidget(str(IconPath.warning), self) msg = "%s is in safe mode.\nExpressions are not evaluated." % APP_NAME self.safe_mode_widget.setToolTip(msg) self.statusBar().addPermanentWidget(self.safe_mode_widget) self.safe_mode_widget.hide() self.setMenuBar(MenuBar(self)) def resizeEvent(self, event): super(MainWindow, self).resizeEvent(event) if self._loading: return def closeEvent(self, event=None): """Overloaded close event, allows saving changes or canceling close""" self.workflows.file_quit() # has @handle_changed_since_save decorator self.settings.save() if event: event.ignore() # Maybe a warn of closing sys.exit() def _init_widgets(self): """Initialize widgets""" self.widgets = Widgets(self) self.entry_line = Entryline(self) self.grid = Grid(self) self.macro_panel = MacroPanel(self, self.grid.model.code_array) self.main_splitter = QSplitter(Qt.Vertical, self) self.setCentralWidget(self.main_splitter) self.main_splitter.addWidget(self.entry_line) self.main_splitter.addWidget(self.grid) self.main_splitter.addWidget(self.grid.table_choice) self.main_splitter.setSizes([self.entry_line.minimumHeight(), 9999, 20]) self.macro_dock = QDockWidget("Macros", self) self.macro_dock.setObjectName("Macro Panel") self.macro_dock.setWidget(self.macro_panel) self.addDockWidget(Qt.RightDockWidgetArea, self.macro_dock) self.macro_dock.installEventFilter(self) self.gui_update.connect(self.on_gui_update) self.refresh_timer.timeout.connect(self.on_refresh_timer) def eventFilter(self, source, event): """Event filter for handling QDockWidget close events Updates the menu if the macro panel is closed. """ if event.type() == QEvent.Close \ and isinstance(source, QDockWidget) \ and source.windowTitle() == "Macros": self.main_window_actions.toggle_macro_panel.setChecked(False) return super().eventFilter(source, event) def _init_toolbars(self): """Initialize the main window toolbars""" self.main_toolbar = MainToolBar(self) self.find_toolbar = FindToolbar(self) self.format_toolbar = FormatToolbar(self) self.macro_toolbar = MacroToolbar(self) self.widget_toolbar = WidgetToolbar(self) self.addToolBar(self.main_toolbar) self.addToolBar(self.find_toolbar) self.addToolBarBreak() self.addToolBar(self.format_toolbar) self.addToolBar(self.macro_toolbar) self.addToolBar(self.widget_toolbar) def _update_action_toggles(self): """Updates the toggle menu check states""" self.main_window_actions.toggle_main_toolbar.setChecked( self.main_toolbar.isVisible()) self.main_window_actions.toggle_macro_toolbar.setChecked( self.macro_toolbar.isVisible()) self.main_window_actions.toggle_widget_toolbar.setChecked( self.widget_toolbar.isVisible()) self.main_window_actions.toggle_format_toolbar.setChecked( self.format_toolbar.isVisible()) self.main_window_actions.toggle_find_toolbar.setChecked( self.find_toolbar.isVisible()) self.main_window_actions.toggle_entry_line.setChecked( self.entry_line.isVisible()) self.main_window_actions.toggle_macro_panel.setChecked( self.macro_dock.isVisible()) @property def safe_mode(self): """Returns safe_mode state. In safe_mode cells are not evaluated.""" return self.grid.model.code_array.safe_mode @safe_mode.setter def safe_mode(self, value): """Sets safe mode. This triggers the safe_mode icon in the statusbar. If safe_mode changes from True to False then caches are cleared and macros are executed. """ if self.grid.model.code_array.safe_mode == bool(value): return self.grid.model.code_array.safe_mode = bool(value) if value: # Safe mode entered self.safe_mode_widget.show() else: # Safe_mode disabled self.safe_mode_widget.hide() # Clear result cache self.grid.model.code_array.result_cache.clear() # Execute macros self.grid.model.code_array.execute_macros() def on_nothing(self): """Dummy action that does nothing""" sender = self.sender() print("on_nothing > ", sender.text(), sender) def on_fullscreen(self): """Fullscreen toggle event handler""" if self.windowState() == Qt.WindowFullScreen: self.setWindowState(self._previous_window_state) else: self._previous_window_state = self.windowState() self.setWindowState(Qt.WindowFullScreen) def on_approve(self): """Approve event handler""" if ApproveWarningDialog(self).choice: self.safe_mode = False def on_preferences(self): """Preferences event handler (:class:`dialogs.PreferencesDialog`) """ data = PreferencesDialog(self).data if data is not None: # Dialog has not been approved --> Store data to settings for key in data: if key == "signature_key" and not data[key]: data[key] = genkey() self.settings.__setattr__(key, data[key]) def on_undo(self): """Undo event handler""" self.undo_stack.undo() def on_redo(self): """Undo event handler""" self.undo_stack.redo() def on_toggle_refresh_timer(self, toggled): """Toggles periodic timer for frozen cells""" if toggled: self.refresh_timer.start(self.settings.refresh_timeout) else: self.refresh_timer.stop() def on_refresh_timer(self): """Event handler for self.refresh_timer.timeout Called for periodic updates of frozen cells. Does nothing if either the entry_line or a cell editor is active. """ if not self.entry_line.hasFocus() \ and self.grid.state() != self.grid.EditingState: self.grid.refresh_frozen_cells() def _toggle_widget(self, widget, action_name, toggled): """Toggles widget visibility and updates toggle actions""" if toggled: widget.show() else: widget.hide() self.main_window_actions[action_name].setChecked(widget.isVisible()) def on_toggle_main_toolbar(self, toggled): """Main toolbar toggle event handler""" self._toggle_widget(self.main_toolbar, "toggle_main_toolbar", toggled) def on_toggle_macro_toolbar(self, toggled): """Macro toolbar toggle event handler""" self._toggle_widget(self.macro_toolbar, "toggle_macro_toolbar", toggled) def on_toggle_widget_toolbar(self, toggled): """Widget toolbar toggle event handler""" self._toggle_widget(self.widget_toolbar, "toggle_widget_toolbar", toggled) def on_toggle_format_toolbar(self, toggled): """Format toolbar toggle event handler""" self._toggle_widget(self.format_toolbar, "toggle_format_toolbar", toggled) def on_toggle_find_toolbar(self, toggled): """Find toolbar toggle event handler""" self._toggle_widget(self.find_toolbar, "toggle_find_toolbar", toggled) def on_toggle_entry_line(self, toggled): """Entryline toggle event handler""" self._toggle_widget(self.entry_line, "toggle_entry_line", toggled) def on_toggle_macro_panel(self, toggled): """Macro panel toggle event handler""" self._toggle_widget(self.macro_dock, "toggle_macro_panel", toggled) def on_about(self): """Show about message box""" about_msg_template = "<p>".join(( "<b>%s</b>" % APP_NAME, "A non-traditional Python spreadsheet application", "Version {version}", "Created by:<br>{devs}", "Documented by:<br>{doc_devs}", "Copyright:<br>Martin Manns", "License:<br>{license}", '<a href="https://pyspread.gitlab.io">pyspread.gitlab.io</a>', )) devs = "Martin Manns, Jason Sexauer<br>Vova Kolobok, mgunyho, " \ "Pete Morgan" doc_devs = "Martin Manns, Bosko Markovic, Pete Morgan" about_msg = about_msg_template.format( version=VERSION, license=LICENSE, devs=devs, doc_devs=doc_devs) QMessageBox.about(self, "About %s" % APP_NAME, about_msg) def on_gui_update(self, attributes): """GUI update event handler. Emitted on cell change. Attributes contains current cell_attributes. """ widgets = self.widgets is_bold = attributes["fontweight"] == QFont.Bold self.main_window_actions.bold.setChecked(is_bold) is_italic = attributes["fontstyle"] == QFont.StyleItalic self.main_window_actions.italics.setChecked(is_italic) underline_action = self.main_window_actions.underline underline_action.setChecked(attributes["underline"]) strikethrough_action = self.main_window_actions.strikethrough strikethrough_action.setChecked(attributes["strikethrough"]) renderer = attributes["renderer"] widgets.renderer_button.set_current_action(renderer) widgets.renderer_button.set_menu_checked(renderer) freeze_action = self.main_window_actions.freeze_cell freeze_action.setChecked(attributes["frozen"]) lock_action = self.main_window_actions.lock_cell lock_action.setChecked(attributes["locked"]) self.entry_line.setReadOnly(attributes["locked"]) rotation = "rotate_{angle}".format(angle=int(attributes["angle"])) widgets.rotate_button.set_current_action(rotation) widgets.rotate_button.set_menu_checked(rotation) widgets.justify_button.set_current_action(attributes["justification"]) widgets.justify_button.set_menu_checked(attributes["justification"]) widgets.align_button.set_current_action(attributes["vertical_align"]) widgets.align_button.set_menu_checked(attributes["vertical_align"]) border_action = self.main_window_actions.border_group.checkedAction() if border_action is not None: icon = border_action.icon() self.menuBar().border_submenu.setIcon(icon) self.format_toolbar.border_menu_button.setIcon(icon) border_width_action = \ self.main_window_actions.border_width_group.checkedAction() if border_width_action is not None: icon = border_width_action.icon() self.menuBar().line_width_submenu.setIcon(icon) self.format_toolbar.line_width_button.setIcon(icon) if attributes["textcolor"] is None: text_color = self.grid.palette().color(QPalette.Text) else: text_color = QColor(*attributes["textcolor"]) widgets.text_color_button.color = text_color if attributes["bgcolor"] is None: bgcolor = self.grid.palette().color(QPalette.Base) else: bgcolor = QColor(*attributes["bgcolor"]) widgets.background_color_button.color = bgcolor if attributes["textfont"] is None: widgets.font_combo.font = QFont().family() else: widgets.font_combo.font = attributes["textfont"] widgets.font_size_combo.size = attributes["pointsize"] merge_cells_action = self.main_window_actions.merge_cells merge_cells_action.setChecked(attributes["merge_area"] is not None)
class PhotoEditor(QMainWindow): def __init__(self): super().__init__() self.initializeUI() def initializeUI(self): """ Initialize the window and display its contents to the screen """ self.setFixedSize(650, 650) self.setWindowTitle('5.2 - Photo Editor GUI') self.centerMainWindow() self.createToolsDockWidget() self.createMenu() self.createToolBar() self.photoEditorWidgets() self.show() def createMenu(self): """ Create menu for photo editor GUI """ # Create actions for file menu self.open_act = QAction(QIcon('images/open_file.png'), "Open", self) self.open_act.setShortcut('Ctrl+O') self.open_act.setStatusTip('Open a new image') self.open_act.triggered.connect(self.openImage) self.save_act = QAction(QIcon('images/save_file.png'), "Save", self) self.save_act.setShortcut('Ctrl+S') self.save_act.setStatusTip('Save image') self.save_act.triggered.connect(self.saveImage) self.print_act = QAction(QIcon('images/print.png'), "Print", self) self.print_act.setShortcut('Ctrl+P') self.print_act.setStatusTip('Print image') self.print_act.triggered.connect(self.printImage) self.print_act.setEnabled(False) self.exit_act = QAction(QIcon('images/exit.png'), 'Exit', self) self.exit_act.setShortcut('Ctrl+Q') self.exit_act.setStatusTip('Quit program') self.exit_act.triggered.connect(self.close) # Create actions for edit menu self.rotate90_act = QAction("Rotate 90°", self) self.rotate90_act.setStatusTip('Rotate image 90° clockwise') self.rotate90_act.triggered.connect(self.rotateImage90) self.rotate180_act = QAction("Rotate 180°", self) self.rotate180_act.setStatusTip('Rotate image 180° clockwise') self.rotate180_act.triggered.connect(self.rotateImage180) self.flip_hor_act = QAction("Flip Horizontal", self) self.flip_hor_act.setStatusTip('Flip image across horizontal axis') self.flip_hor_act.triggered.connect(self.flipImageHorizontal) self.flip_ver_act = QAction("Flip Vertical", self) self.flip_ver_act.setStatusTip('Flip image across vertical axis') self.flip_ver_act.triggered.connect(self.flipImageVertical) self.resize_act = QAction("Resize Half", self) self.resize_act.setStatusTip('Resize image to half the original size') self.resize_act.triggered.connect(self.resizeImageHalf) self.clear_act = QAction(QIcon('images/clear.png'), "Clear Image", self) self.clear_act.setShortcut("Ctrl+D") self.clear_act.setStatusTip('Clear the current image') self.clear_act.triggered.connect(self.clearImage) # Create menubar menu_bar = self.menuBar() menu_bar.setNativeMenuBar(False) # Create file menu and add actions file_menu = menu_bar.addMenu('File') file_menu.addAction(self.open_act) file_menu.addAction(self.save_act) file_menu.addSeparator() file_menu.addAction(self.print_act) file_menu.addSeparator() file_menu.addAction(self.exit_act) # Create edit menu and add actions edit_menu = menu_bar.addMenu('Edit') edit_menu.addAction(self.rotate90_act) edit_menu.addAction(self.rotate180_act) edit_menu.addSeparator() edit_menu.addAction(self.flip_hor_act) edit_menu.addAction(self.flip_ver_act) edit_menu.addSeparator() edit_menu.addAction(self.resize_act) edit_menu.addSeparator() edit_menu.addAction(self.clear_act) # Create view menu and add actions view_menu = menu_bar.addMenu('View') view_menu.addAction(self.toggle_dock_tools_act) # Display info about tools, menu, and view in the status bar self.setStatusBar(QStatusBar(self)) def createToolBar(self): """ Create toolbar for photo editor GUI """ tool_bar = QToolBar("Photo Editor Toolbar") tool_bar.setIconSize(QSize(24, 24)) self.addToolBar(tool_bar) # Add actions to toolbar tool_bar.addAction(self.open_act) tool_bar.addAction(self.save_act) tool_bar.addAction(self.print_act) tool_bar.addAction(self.clear_act) tool_bar.addSeparator() tool_bar.addAction(self.exit_act) def createToolsDockWidget(self): """ Use View -> Edit Image Tools menu and click the dock widget on or off. Tools dock can be placed on the left or right of the main window. """ # Set up QDockWidget self.dock_tools_view = QDockWidget() self.dock_tools_view.setWindowTitle("Edit Image Tools") self.dock_tools_view.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) # Create container QWidget to hold all widgets inside dock widget self.tools_contents = QWidget() # Create tool push buttons self.rotate90 = QPushButton("Rotate 90°") self.rotate90.setMinimumSize(QSize(130, 40)) self.rotate90.setStatusTip('Rotate image 90° clockwise') self.rotate90.clicked.connect(self.rotateImage90) self.rotate180 = QPushButton("Rotate 180°") self.rotate180.setMinimumSize(QSize(130, 40)) self.rotate180.setStatusTip('Rotate image 180° clockwise') self.rotate180.clicked.connect(self.rotateImage180) self.flip_horizontal = QPushButton("Flip Horizontal") self.flip_horizontal.setMinimumSize(QSize(130, 40)) self.flip_horizontal.setStatusTip('Flip image across horizontal axis') self.flip_horizontal.clicked.connect(self.flipImageHorizontal) self.flip_vertical = QPushButton("Flip Vertical") self.flip_vertical.setMinimumSize(QSize(130, 40)) self.flip_vertical.setStatusTip('Flip image across vertical axis') self.flip_vertical.clicked.connect(self.flipImageVertical) self.resize_half = QPushButton("Resize Half") self.resize_half.setMinimumSize(QSize(130, 40)) self.resize_half.setStatusTip('Resize image to half the original size') self.resize_half.clicked.connect(self.resizeImageHalf) # Set up vertical layout to contain all the push buttons dock_v_box = QVBoxLayout() dock_v_box.addWidget(self.rotate90) dock_v_box.addWidget(self.rotate180) dock_v_box.addStretch(1) dock_v_box.addWidget(self.flip_horizontal) dock_v_box.addWidget(self.flip_vertical) dock_v_box.addStretch(1) dock_v_box.addWidget(self.resize_half) dock_v_box.addStretch(6) # Set the main layout for the QWidget, tools_contents, # then set the main widget of the dock widget self.tools_contents.setLayout(dock_v_box) self.dock_tools_view.setWidget(self.tools_contents) # Set initial location of dock widget self.addDockWidget(Qt.RightDockWidgetArea, self.dock_tools_view) # Handles the visibility of the dock widget self.toggle_dock_tools_act = self.dock_tools_view.toggleViewAction() def photoEditorWidgets(self): """ Set up instances of widgets for photo editor GUI """ self.image = QPixmap() self.image_label = QLabel() self.image_label.setAlignment(Qt.AlignCenter) # Use setSizePolicy to specify how the widget can be resized, # horizontally and vertically. Here, the image will stretch # horizontally, but not vertically. self.image_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored) self.setCentralWidget(self.image_label) def openImage(self): """ Open an image file and display its contents in label widget. Display error message if image can't be opened. """ image_file, _ = QFileDialog.getOpenFileName( self, "Open Image", "", "JPG Files (*.jpeg *.jpg );;PNG Files (*.png);;Bitmap Files(*.bmp);;\GIF Files (*.gif)" ) if image_file: self.image = QPixmap(image_file) self.image_label.setPixmap( self.image.scaled(self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) else: QMessageBox.information(self, "Error", "Unable to open image.", QMessageBox.Ok) self.print_act.setEnabled(True) def saveImage(self): """ Save the image. Display error message if image can't be saved. """ image_file, _ = QFileDialog.getSaveFileName( self, "Save Image", "", "JPG Files (*.jpeg *.jpg );;PNG Files (*.png);;Bitmap Files(*.bmp);;\GIF Files (*.gif)" ) if image_file and self.image.isNull() == False: self.image.save(image_file) else: QMessageBox.information(self, "Error", "Unable to save image.", QMessageBox.Ok) def printImage(self): """ Print image. """ # Create printer object and print output defined by the platform # the program is being run on. # QPrinter.NativeFormat is the default printer = QPrinter() printer.setOutputFormat(QPrinter.NativeFormat) # Create printer dialog to configure printer print_dialog = QPrintDialog(printer) # If the dialog is accepted by the user, begin printing if (print_dialog.exec_() == QPrintDialog.Accepted): # Use QPainter to output a PDF file painter = QPainter() # Begin painting device painter.begin(printer) # Set QRect to hold painter's current viewport, which # is the image_label rect = QRect(painter.viewport()) # Get the size of image_label and use it to set the size # of the viewport size = QSize(self.image_label.pixmap().size()) size.scale(rect.size(), Qt.KeepAspectRatio) painter.setViewport(rect.x(), rect.y(), size.width(), size.height()) painter.setWindow(self.image_label.pixmap().rect()) # Scale the image_label to fit the rect source (0, 0) painter.drawPixmap(0, 0, self.image_label.pixmap()) # End painting painter.end() def clearImage(self): """ Clears current image in QLabel widget """ self.image_label.clear() self.image = QPixmap() # reset pixmap so that isNull() = True def rotateImage90(self): """ Rotate image 90° clockwise """ if self.image.isNull() == False: transform90 = QTransform().rotate(90) pixmap = QPixmap(self.image) rotated = pixmap.transformed(transform90, mode=Qt.SmoothTransformation) self.image_label.setPixmap( rotated.scaled(self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) self.image = QPixmap(rotated) self.image_label.repaint() # repaint the child widget else: # No image to rotate pass def rotateImage180(self): """ Rotate image 180° clockwise """ if self.image.isNull() == False: transform180 = QTransform().rotate(180) pixmap = QPixmap(self.image) rotated = pixmap.transformed(transform180, mode=Qt.SmoothTransformation) self.image_label.setPixmap( rotated.scaled(self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) # In order to keep being allowed to rotate the image, set the rotated image as self.image self.image = QPixmap(rotated) self.image_label.repaint() # repaint the child widget else: # No image to rotate pass def flipImageHorizontal(self): """ Mirror the image across the horizontal axis """ if self.image.isNull() == False: flip_h = QTransform().scale(-1, 1) pixmap = QPixmap(self.image) flipped = pixmap.transformed(flip_h) self.image_label.setPixmap( flipped.scaled(self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) self.image = QPixmap(flipped) self.image_label.repaint() else: # No image to flip pass def flipImageVertical(self): """ Mirror the image across the vertical axis """ if self.image.isNull() == False: flip_v = QTransform().scale(1, -1) pixmap = QPixmap(self.image) flipped = pixmap.transformed(flip_v) self.image_label.setPixmap( flipped.scaled(self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) self.image = QPixmap(flipped) self.image_label.repaint() else: # No image to flip pass def resizeImageHalf(self): """ Resize the image to half its current size. """ if self.image.isNull() == False: resize = QTransform().scale(0.5, 0.5) pixmap = QPixmap(self.image) resized = pixmap.transformed(resize) self.image_label.setPixmap( resized.scaled(self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) self.image = QPixmap(resized) self.image_label.repaint() else: # No image to resize pass def centerMainWindow(self): """ Use QDesktopWidget class to access information about your screen and use it to center the application window. """ desktop = QDesktopWidget().screenGeometry() screen_width = desktop.width() screen_height = desktop.height() self.move((screen_width - self.width()) / 2, (screen_height - self.height()) / 2)
class Window(QMainWindow): """ Defines the look and characteristics of the application's main window. """ title = _("Mu {}").format(__version__) icon = "icon" timer = None usb_checker = None serial = None repl = None plotter = None _zoom_in = pyqtSignal(int) _zoom_out = pyqtSignal(int) close_serial = pyqtSignal() write_to_serial = pyqtSignal(bytes) data_received = pyqtSignal(bytes) def zoom_in(self): """ Handles zooming in. """ self._zoom_in.emit(2) def zoom_out(self): """ Handles zooming out. """ self._zoom_out.emit(2) def connect_zoom(self, widget): """ Connects a referenced widget to the zoom related signals. """ self._zoom_in.connect(widget.zoomIn) self._zoom_out.connect(widget.zoomOut) @property def current_tab(self): """ Returns the currently focussed tab. """ return self.tabs.currentWidget() def set_read_only(self, is_readonly): """ Set all tabs read-only. """ self.read_only_tabs = is_readonly for tab in self.widgets: tab.setReadOnly(is_readonly) def get_load_path(self, folder): """ Displays a dialog for selecting a file to load. Returns the selected path. Defaults to start in the referenced folder. """ path, _ = QFileDialog.getOpenFileName(self.widget, 'Open file', folder, '*.py *.PY *.hex') logger.debug('Getting load path: {}'.format(path)) return path def get_save_path(self, folder): """ Displays a dialog for selecting a file to save. Returns the selected path. Defaults to start in the referenced folder. """ path, _ = QFileDialog.getSaveFileName(self.widget, 'Save file', folder) logger.debug('Getting save path: {}'.format(path)) return path def get_microbit_path(self, folder): """ Displays a dialog for locating the location of the BBC micro:bit in the host computer's filesystem. Returns the selected path. Defaults to start in the referenced folder. """ path = QFileDialog.getExistingDirectory(self.widget, 'Locate BBC micro:bit', folder, QFileDialog.ShowDirsOnly) logger.debug('Getting micro:bit path: {}'.format(path)) return path def add_tab(self, path, text, api): """ Adds a tab with the referenced path and text to the editor. """ new_tab = EditorPane(path, text) new_tab.connect_margin(self.breakpoint_toggle) new_tab_index = self.tabs.addTab(new_tab, new_tab.label) new_tab.set_api(api) @new_tab.modificationChanged.connect def on_modified(): modified_tab_index = self.tabs.currentIndex() self.tabs.setTabText(modified_tab_index, new_tab.label) self.update_title(new_tab.label) self.tabs.setCurrentIndex(new_tab_index) self.connect_zoom(new_tab) self.set_theme(self.theme) new_tab.setFocus() if self.read_only_tabs: new_tab.setReadOnly(self.read_only_tabs) def focus_tab(self, tab): index = self.tabs.indexOf(tab) self.tabs.setCurrentIndex(index) tab.setFocus() @property def tab_count(self): """ Returns the number of active tabs. """ return self.tabs.count() @property def widgets(self): """ Returns a list of references to the widgets representing tabs in the editor. """ return [self.tabs.widget(i) for i in range(self.tab_count)] @property def modified(self): """ Returns a boolean indication if there are any modified tabs in the editor. """ for widget in self.widgets: if widget.isModified(): return True return False def on_serial_read(self): """ Called when the connected device is ready to send data via the serial connection. It reads all the available data, emits the data_received signal with the received bytes and, if appropriate, emits the tuple_received signal with the tuple created from the bytes received. """ data = bytes(self.serial.readAll()) # get all the available bytes. self.data_received.emit(data) def open_serial_link(self, port): """ Creates a new serial link instance. """ self.input_buffer = [] self.serial = QSerialPort() self.serial.setPortName(port) if self.serial.open(QIODevice.ReadWrite): self.serial.dataTerminalReady = True if not self.serial.isDataTerminalReady(): # Using pyserial as a 'hack' to open the port and set DTR # as QtSerial does not seem to work on some Windows :( # See issues #281 and #302 for details. self.serial.close() pyser = serial.Serial(port) # open serial port w/pyserial pyser.dtr = True pyser.close() self.serial.open(QIODevice.ReadWrite) self.serial.setBaudRate(115200) self.serial.readyRead.connect(self.on_serial_read) else: raise IOError("Cannot connect to device on port {}".format(port)) def close_serial_link(self): """ Close and clean up the currently open serial link. """ self.serial.close() self.serial = None def add_filesystem(self, home, file_manager): """ Adds the file system pane to the application. """ self.fs_pane = FileSystemPane(home) self.fs = QDockWidget(_('Filesystem on micro:bit')) self.fs.setWidget(self.fs_pane) self.fs.setFeatures(QDockWidget.DockWidgetMovable) self.fs.setAllowedAreas(Qt.BottomDockWidgetArea) self.addDockWidget(Qt.BottomDockWidgetArea, self.fs) self.fs_pane.setFocus() file_manager.on_list_files.connect(self.fs_pane.on_ls) self.fs_pane.list_files.connect(file_manager.ls) self.fs_pane.microbit_fs.put.connect(file_manager.put) self.fs_pane.microbit_fs.delete.connect(file_manager.delete) self.fs_pane.microbit_fs.list_files.connect(file_manager.ls) self.fs_pane.local_fs.get.connect(file_manager.get) self.fs_pane.local_fs.list_files.connect(file_manager.ls) file_manager.on_put_file.connect(self.fs_pane.microbit_fs.on_put) file_manager.on_delete_file.connect(self.fs_pane.microbit_fs.on_delete) file_manager.on_get_file.connect(self.fs_pane.local_fs.on_get) file_manager.on_list_fail.connect(self.fs_pane.on_ls_fail) file_manager.on_put_fail.connect(self.fs_pane.on_put_fail) file_manager.on_delete_fail.connect(self.fs_pane.on_delete_fail) file_manager.on_get_fail.connect(self.fs_pane.on_get_fail) self.connect_zoom(self.fs_pane) return self.fs_pane def add_micropython_repl(self, port, name): """ Adds a MicroPython based REPL pane to the application. """ if not self.serial: self.open_serial_link(port) # Send a Control-C / keyboard interrupt. self.serial.write(b'\x03') repl_pane = MicroPythonREPLPane(serial=self.serial, theme=self.theme) self.data_received.connect(repl_pane.process_bytes) self.add_repl(repl_pane, name) def add_micropython_plotter(self, port, name): """ Adds a plotter that reads data from a serial connection. """ if not self.serial: self.open_serial_link(port) plotter_pane = PlotterPane(theme=self.theme) self.data_received.connect(plotter_pane.process_bytes) self.add_plotter(plotter_pane, name) def add_jupyter_repl(self, kernel_manager, kernel_client): """ Adds a Jupyter based REPL pane to the application. """ kernel_manager.kernel.gui = 'qt4' kernel_client.start_channels() ipython_widget = JupyterREPLPane(theme=self.theme) ipython_widget.kernel_manager = kernel_manager ipython_widget.kernel_client = kernel_client self.add_repl(ipython_widget, _('Python3 (Jupyter)')) def add_repl(self, repl_pane, name): """ Adds the referenced REPL pane to the application. """ self.repl_pane = repl_pane self.repl = QDockWidget(_('{} REPL').format(name)) self.repl.setWidget(repl_pane) self.repl.setFeatures(QDockWidget.DockWidgetMovable) self.repl.setAllowedAreas(Qt.BottomDockWidgetArea | Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.addDockWidget(Qt.BottomDockWidgetArea, self.repl) self.connect_zoom(self.repl_pane) self.repl_pane.set_theme(self.theme) self.repl_pane.setFocus() def add_plotter(self, plotter_pane, name): """ Adds the referenced plotter pane to the application. """ self.plotter_pane = plotter_pane self.plotter = QDockWidget(_('{} Plotter').format(name)) self.plotter.setWidget(plotter_pane) self.plotter.setFeatures(QDockWidget.DockWidgetMovable) self.plotter.setAllowedAreas(Qt.BottomDockWidgetArea | Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.addDockWidget(Qt.BottomDockWidgetArea, self.plotter) self.plotter_pane.set_theme(self.theme) self.plotter_pane.setFocus() def add_python3_runner(self, script_name, working_directory, interactive=False, debugger=False, command_args=None, runner=None): """ Display console output for the referenced Python script. The script will be run within the workspace_path directory. If interactive is True (default is False) the Python process will run in interactive mode (dropping the user into the REPL when the script completes). If debugger is True (default is False) the script will be run within a debug runner session. The debugger overrides the interactive flag (you cannot run the debugger in interactive mode). If there is a list of command_args (the default is None) then these will be passed as further arguments into the command run in the new process. If runner is give, this is used as the command to start the Python process. """ self.process_runner = PythonProcessPane(self) self.runner = QDockWidget(_("Running: {}").format( os.path.basename(script_name))) self.runner.setWidget(self.process_runner) self.runner.setFeatures(QDockWidget.DockWidgetMovable) self.runner.setAllowedAreas(Qt.BottomDockWidgetArea | Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.addDockWidget(Qt.BottomDockWidgetArea, self.runner) self.process_runner.start_process(script_name, working_directory, interactive, debugger, command_args, runner) self.process_runner.setFocus() self.connect_zoom(self.process_runner) return self.process_runner def add_debug_inspector(self): """ Display a debug inspector to view the call stack. """ self.debug_inspector = DebugInspector() self.debug_model = QStandardItemModel() self.debug_inspector.setModel(self.debug_model) self.debug_inspector.setUniformRowHeights(True) self.inspector = QDockWidget(_('Debug Inspector')) self.inspector.setWidget(self.debug_inspector) self.inspector.setFeatures(QDockWidget.DockWidgetMovable) self.inspector.setAllowedAreas(Qt.BottomDockWidgetArea | Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.addDockWidget(Qt.RightDockWidgetArea, self.inspector) self.connect_zoom(self.debug_inspector) def update_debug_inspector(self, locals_dict): """ Given the contents of a dict representation of the locals in the current stack frame, update the debug inspector with the new values. """ excluded_names = ['__builtins__', '__debug_code__', '__debug_script__', ] names = sorted([x for x in locals_dict if x not in excluded_names]) self.debug_model.clear() self.debug_model.setHorizontalHeaderLabels([_('Name'), _('Value'), ]) for name in names: try: # DANGER! val = eval(locals_dict[name]) except Exception: val = None if isinstance(val, list): # Show a list consisting of rows of position/value list_item = QStandardItem(name) for i, i_val in enumerate(val): list_item.appendRow([ QStandardItem(str(i)), QStandardItem(repr(i_val)) ]) self.debug_model.appendRow([ list_item, QStandardItem(_('(A list of {} items.)').format(len(val))) ]) elif isinstance(val, dict): # Show a dict consisting of rows of key/value pairs. dict_item = QStandardItem(name) for k, k_val in val.items(): dict_item.appendRow([ QStandardItem(repr(k)), QStandardItem(repr(k_val)) ]) self.debug_model.appendRow([ dict_item, QStandardItem(_('(A dict of {} items.)').format(len(val))) ]) else: self.debug_model.appendRow([ QStandardItem(name), QStandardItem(locals_dict[name]), ]) def remove_filesystem(self): """ Removes the file system pane from the application. """ if hasattr(self, 'fs') and self.fs: self.fs_pane = None self.fs.setParent(None) self.fs.deleteLater() self.fs = None def remove_repl(self): """ Removes the REPL pane from the application. """ if self.repl: self.repl_pane = None self.repl.setParent(None) self.repl.deleteLater() self.repl = None if not self.plotter: self.serial = None def remove_plotter(self): """ Removes the plotter pane from the application. """ if self.plotter: self.plotter_pane = None self.plotter.setParent(None) self.plotter.deleteLater() self.plotter = None if not self.repl: self.serial = None def remove_python_runner(self): """ Removes the runner pane from the application. """ if hasattr(self, 'runner') and self.runner: self.process_runner = None self.runner.setParent(None) self.runner.deleteLater() self.runner = None def remove_debug_inspector(self): """ Removes the debug inspector pane from the application. """ if hasattr(self, 'inspector') and self.inspector: self.debug_inspector = None self.debug_model = None self.inspector.setParent(None) self.inspector.deleteLater() self.inspector = None def set_theme(self, theme): """ Sets the theme for the REPL and editor tabs. """ self.theme = theme if theme == 'contrast': self.setStyleSheet(CONTRAST_STYLE) new_theme = ContrastTheme new_icon = 'theme_day' elif theme == 'night': new_theme = NightTheme new_icon = 'theme_contrast' self.setStyleSheet(NIGHT_STYLE) else: self.setStyleSheet(DAY_STYLE) new_theme = DayTheme new_icon = 'theme' for widget in self.widgets: widget.set_theme(new_theme) self.button_bar.slots['theme'].setIcon(load_icon(new_icon)) if hasattr(self, 'repl') and self.repl: self.repl_pane.set_theme(theme) if hasattr(self, 'plotter') and self.plotter: self.plotter_pane.set_theme(theme) def show_logs(self, log, theme): """ Display the referenced content of the log. """ log_box = LogDisplay() log_box.setup(log, theme) log_box.exec() def show_message(self, message, information=None, icon=None): """ Displays a modal message to the user. If information is passed in this will be set as the additional informative text in the modal dialog. Since this mechanism will be used mainly for warning users that something is awry the default icon is set to "Warning". It's possible to override the icon to one of the following settings: NoIcon, Question, Information, Warning or Critical. """ message_box = QMessageBox(self) message_box.setText(message) message_box.setWindowTitle('Mu') if information: message_box.setInformativeText(information) if icon and hasattr(message_box, icon): message_box.setIcon(getattr(message_box, icon)) else: message_box.setIcon(message_box.Warning) logger.debug(message) logger.debug(information) message_box.exec() def show_confirmation(self, message, information=None, icon=None): """ Displays a modal message to the user to which they need to confirm or cancel. If information is passed in this will be set as the additional informative text in the modal dialog. Since this mechanism will be used mainly for warning users that something is awry the default icon is set to "Warning". It's possible to override the icon to one of the following settings: NoIcon, Question, Information, Warning or Critical. """ message_box = QMessageBox() message_box.setText(message) message_box.setWindowTitle(_('Mu')) if information: message_box.setInformativeText(information) if icon and hasattr(message_box, icon): message_box.setIcon(getattr(message_box, icon)) else: message_box.setIcon(message_box.Warning) message_box.setStandardButtons(message_box.Cancel | message_box.Ok) message_box.setDefaultButton(message_box.Cancel) logger.debug(message) logger.debug(information) return message_box.exec() def update_title(self, filename=None): """ Updates the title bar of the application. If a filename (representing the name of the file currently the focus of the editor) is supplied, append it to the end of the title. """ title = self.title if filename: title += ' - ' + filename self.setWindowTitle(title) def autosize_window(self): """ Makes the editor 80% of the width*height of the screen and centres it. """ screen = QDesktopWidget().screenGeometry() w = int(screen.width() * 0.8) h = int(screen.height() * 0.8) self.resize(w, h) size = self.geometry() self.move((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2) def reset_annotations(self): """ Resets the state of annotations on the current tab. """ self.current_tab.reset_annotations() def annotate_code(self, feedback, annotation_type): """ Given a list of annotations about the code in the current tab, add the annotations to the editor window so the user can make appropriate changes. """ self.current_tab.annotate_code(feedback, annotation_type) def show_annotations(self): """ Show the annotations added to the current tab. """ self.current_tab.show_annotations() def setup(self, breakpoint_toggle, theme): """ Sets up the window. Defines the various attributes of the window and defines how the user interface is laid out. """ self.theme = theme self.breakpoint_toggle = breakpoint_toggle # Give the window a default icon, title and minimum size. self.setWindowIcon(load_icon(self.icon)) self.update_title() self.read_only_tabs = False self.setMinimumSize(800, 400) self.widget = QWidget() widget_layout = QVBoxLayout() self.widget.setLayout(widget_layout) self.button_bar = ButtonBar(self.widget) self.tabs = FileTabs() self.tabs.setMovable(True) self.setCentralWidget(self.tabs) self.status_bar = StatusBar(parent=self) self.setStatusBar(self.status_bar) self.addToolBar(self.button_bar) self.show() self.autosize_window() def resizeEvent(self, resizeEvent): """ Respond to window getting too small for the button bar to fit well. """ size = resizeEvent.size() self.button_bar.set_responsive_mode(size.width(), size.height()) def select_mode(self, modes, current_mode, theme): """ Display the mode selector dialog and return the result. """ mode_select = ModeSelector() mode_select.setup(modes, current_mode, theme) mode_select.exec() try: return mode_select.get_mode() except Exception as ex: return None def change_mode(self, mode): """ Given a an object representing a mode, recreates the button bar with the expected functionality. """ self.button_bar.change_mode(mode) # Update the autocomplete / tooltip APIs for each tab to the new mode. api = mode.api() for widget in self.widgets: widget.set_api(api) def set_usb_checker(self, duration, callback): """ Sets up a timer that polls for USB changes via the "callback" every "duration" seconds. """ self.usb_checker = QTimer() self.usb_checker.timeout.connect(callback) self.usb_checker.start(duration * 1000) def set_timer(self, duration, callback): """ Set a repeating timer to call "callback" every "duration" seconds. """ self.timer = QTimer() self.timer.timeout.connect(callback) self.timer.start(duration * 1000) # Measured in milliseconds. def stop_timer(self): """ Stop the repeating timer. """ if self.timer: self.timer.stop() self.timer = None def connect_tab_rename(self, handler, shortcut): """ Connect the double-click event on a tab and the keyboard shortcut to the referenced handler (causing the Save As dialog). """ self.tabs.shortcut = QShortcut(QKeySequence(shortcut), self) self.tabs.shortcut.activated.connect(handler) self.tabs.tabBarDoubleClicked.connect(handler) def open_directory_from_os(self, path): """ Given the path to a directoy, open the OS's built in filesystem explorer for that path. Works with Windows, OSX and Linux. """ if sys.platform == 'win32': # Windows os.startfile(path) elif sys.platform == 'darwin': # OSX os.system('open "{}"'.format(path)) else: # Assume freedesktop.org on unix-y. os.system('xdg-open "{}"'.format(path))
def __init__(self): super(SynpoWindow, self).__init__() self.ais = None self.h5file = None self.data = None self.setWindowTitle("AICluster") self.pix = None Global.imageWindow = self self.canny2 = 200 self.canny1 = 100 self.synpochannel = 0 self.aisthreshold = 0.5 self.synpothreshold = 0.5 import matplotlib.pyplot as plt self.fig, self.nanas = plt.subplots(3, 2) self.axes = self.nanas.flatten() self.canvas = FigureCanvas(self.fig) self.clustermask = None self.roidock = QDockWidget("Settings", self) self.span = SpanSelector(self.axes[2], self.onselect, 'horizontal', useblit=True, rectprops=dict(alpha=0.5, facecolor='red')) self.lasso = LassoSelector(self.axes[5], self.onlassoselectcluster) self.lasso2 = LassoSelector(self.axes[1], self.onlassoselectais) self.setCentralWidget(self.canvas) self.settings = QWidget() self.settingsLayout = QVBoxLayout() self.settingBox = QGroupBox("Roi-Meta") self.settingLayout = QGridLayout() self.indexLabel = QLabel("Roi") self.indexEdit = QLineEdit() self.indexEdit.setDisabled(True) self.settingLayout.addWidget(self.indexLabel, 1, 0) self.settingLayout.addWidget(self.indexEdit, 1, 1) self.fileLabel = QLabel("File") self.fileEdit = QLineEdit() self.fileEdit.setDisabled(True) self.settingLayout.addWidget(self.fileLabel, 0, 0) self.settingLayout.addWidget(self.fileEdit, 0, 1) self.volumeLabel = QLabel("Volume") self.volumeEdit = QLineEdit() self.volumeEdit.setDisabled(True) self.settingLayout.addWidget(self.volumeLabel, 2, 0) self.settingLayout.addWidget(self.volumeEdit, 2, 1) self.diameterLabel = QLabel("Diameter") self.diameterEdit = QLineEdit() self.diameterEdit.setDisabled(True) self.settingLayout.addWidget(self.diameterLabel, 3, 0) self.settingLayout.addWidget(self.diameterEdit, 3, 1) self.diameterLabel = QLabel("SynpoVolume") self.diameterEdit = QLineEdit() self.diameterEdit.setDisabled(True) self.settingLayout.addWidget(self.diameterLabel, 4, 0) self.settingLayout.addWidget(self.diameterEdit, 4, 1) self.settingBox.setLayout(self.settingLayout) self.settingsLayout.addWidget(self.settingBox) self.fileBox = QGroupBox("Files") self.filesLayout = QGridLayout() self.saveButton = QPushButton("Save") self.saveButton.clicked.connect(self.saveROI) self.filesLayout.addWidget(self.saveButton, 0, 0) self.calculateButton = QPushButton("Calculate") self.calculateButton.clicked.connect(self.calculate) self.filesLayout.addWidget(self.calculateButton, 0, 0) self.changefilebutton = QPushButton('Change File') self.changefilebutton.clicked.connect(self.changeFile) self.filesLayout.addWidget(self.changefilebutton, 0, 1) self.calculateandsavebutton = QPushButton('Calculate and Save') self.calculateandsavebutton.clicked.connect(self.calculateandSave) self.filesLayout.addWidget(self.calculateandsavebutton, 1, 0) # set button to call somefunction() when clicked self.cannyselector1 = ValueSelector("Canny1", 0, 100, self.cannyselector1changed, ismin=True) self.cannyselector2 = ValueSelector("Canny1", 0, 200, self.cannyselector2changed, ismin=True) self.aisthresholdselector = ValueSelector( "AISThreshold", 0, 100, self.aisthresholdselectorchanged, ismin=True) self.synpothresholdselector = ValueSelector( "SynpoThreshold", 0, 100, self.synpothresholdselectorchanged, ismin=True) self.synpochannelselector = SynpoChannelSelector( "Synpochannel", self.synpochannelchanged) self.flagsBox = QGroupBox("Flags") self.flagsLayout = QVBoxLayout() self.flagsText = QLineEdit() self.flagsLayout.addWidget(self.flagsText) self.flagsLayout.addWidget(self.cannyselector1) self.flagsLayout.addWidget(self.cannyselector2) self.flagsLayout.addWidget(self.synpochannelselector) self.flagsLayout.addWidget(self.aisthresholdselector) self.flagsLayout.addWidget(self.synpothresholdselector) self.flagsBox.setLayout(self.flagsLayout) self.settingsLayout.addWidget(self.flagsBox) self.fileBox.setLayout(self.filesLayout) self.settingsLayout.addWidget(self.fileBox) self.settingsLayout.addStretch() self.filebutton = QPushButton("Choose Directory", self) self.filebutton.clicked.connect(self.openDir) self.settingsLayout.addWidget(self.filebutton) self.settings.setLayout(self.settingsLayout) self.roidock.setWidget(self.settings) self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.roidock) self.show()
class Window(QMainWindow): def __init__(self): super().__init__() self.setGeometry(300, 100, 1200, 800) self.setWindowTitle('SIG Tool App') self.sig_widgets = SigWidgets() self.data = Data('CarrotMod') self.items = self.data.load_items() self.initUi() def initUi(self): self.create_menu_bar() self.json_editor() # self.setCentralWidget(QTextEdit()) self.set_dock_inspector() self.set_dock_items() self.setDockOptions(QMainWindow.AnimatedDocks | QMainWindow.AllowNestedDocks) self.setTabPosition(Qt.DockWidgetArea.AllDockWidgetAreas, QTabWidget.TabPosition.North) def create_menu_bar(self): menu_bar = Menu(self, self.set_dock_inspector, self.set_dock_items, self.json_editor) self.setMenuBar(menu_bar) def json_editor(self): if (hasattr(self, 'json_editor_dock') and self.json_editor_dock.isVisible()): return self.json_editor_dock = QDockWidget('Json Editor', self) self.json_editor_dock.setFloating(False) self.json_editor = JSONCodeEdit(self) self.json_editor.syntax_highlighter.color_scheme = 'monokai' self.json_editor_dock.setWidget(self.json_editor) self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.json_editor_dock) def set_dock_inspector(self): self.inspectorDock = QDockWidget('Inspector', self) inspector_layout = QGroupBox() inspector_form = self.sig_widgets.get_widgets(type_data) inspector_layout.setLayout(inspector_form) self.inspectorDock.setWidget(inspector_layout) self.inspectorDock.setFloating(False) self.addDockWidget(Qt.RightDockWidgetArea, self.inspectorDock) def set_dock_items(self): self.items_view = ItemsView('Items', self, self.items) self.items_view.set_on_item_selected(self.__select_item) self.addDockWidget(Qt.LeftDockWidgetArea, self.items_view) def __edit_item_json(self): if (self.current_item != None): self.data.set_item(self.current_item['path'], self.json_editor.toPlainText()) def __select_item(self, item): if (item != None): self.current_item = item self.json_editor.setDisabled(False) self.json_editor.setPlainText(item['raw_text']) else: self.json_editor.setPlainText('') self.json_editor.setDisabled(True)
class Window(QMainWindow): """ Defines the look and characteristics of the application's main window. """ #title = _("Mu {}").format(__version__) title = _("mPython2_{} ( base on Mu )").format(__version__) icon = "icon" timer = None usb_checker = None serial = None repl = None plotter = None _zoom_in = pyqtSignal(int) _zoom_out = pyqtSignal(int) close_serial = pyqtSignal() write_to_serial = pyqtSignal(bytes) data_received = pyqtSignal(bytes) open_file = pyqtSignal(str) load_theme = pyqtSignal(str) previous_folder = None def zoom_in(self): """ Handles zooming in. """ self._zoom_in.emit(2) def zoom_out(self): """ Handles zooming out. """ self._zoom_out.emit(2) def connect_zoom(self, widget): """ Connects a referenced widget to the zoom related signals. """ self._zoom_in.connect(widget.zoomIn) self._zoom_out.connect(widget.zoomOut) @property def current_tab(self): """ Returns the currently focussed tab. """ return self.tabs.currentWidget() def set_read_only(self, is_readonly): """ Set all tabs read-only. """ self.read_only_tabs = is_readonly for tab in self.widgets: tab.setReadOnly(is_readonly) def get_load_path(self, folder, extensions='*'): """ Displays a dialog for selecting a file to load. Returns the selected path. Defaults to start in the referenced folder. """ path, _ = QFileDialog.getOpenFileName( self.widget, 'Open file', folder if self.previous_folder is None else self.previous_folder, extensions) self.previous_folder = os.path.dirname(path) logger.debug('Getting load path: {}'.format(path)) return path def get_save_path(self, folder): """ Displays a dialog for selecting a file to save. Returns the selected path. Defaults to start in the referenced folder. """ path, _ = QFileDialog.getSaveFileName( self.widget, 'Save file', folder if self.previous_folder is None else self.previous_folder) self.previous_folder = os.path.dirname(path) logger.debug('Getting save path: {}'.format(path)) return path def get_microbit_path(self, folder): """ Displays a dialog for locating the location of the BBC micro:bit in the host computer's filesystem. Returns the selected path. Defaults to start in the referenced folder. """ path = QFileDialog.getExistingDirectory( self.widget, 'Locate BBC micro:bit', folder if self.previous_folder is None else self.previous_folder, QFileDialog.ShowDirsOnly) self.previous_folder = os.path.dirname(path) logger.debug('Getting micro:bit path: {}'.format(path)) return path def add_tab(self, path, text, api, newline): """ Adds a tab with the referenced path and text to the editor. """ new_tab = EditorPane(path, text, newline) new_tab.connect_margin(self.breakpoint_toggle) new_tab_index = self.tabs.addTab(new_tab, new_tab.label) new_tab.set_api(api) @new_tab.modificationChanged.connect def on_modified(): modified_tab_index = self.tabs.currentIndex() self.tabs.setTabText(modified_tab_index, new_tab.label) self.update_title(new_tab.label) @new_tab.open_file.connect def on_open_file(file): # Bubble the signal up self.open_file.emit(file) self.tabs.setCurrentIndex(new_tab_index) self.connect_zoom(new_tab) self.set_theme(self.theme) new_tab.setFocus() if self.read_only_tabs: new_tab.setReadOnly(self.read_only_tabs) return new_tab def focus_tab(self, tab): index = self.tabs.indexOf(tab) self.tabs.setCurrentIndex(index) tab.setFocus() @property def tab_count(self): """ Returns the number of active tabs. """ return self.tabs.count() @property def widgets(self): """ Returns a list of references to the widgets representing tabs in the editor. """ return [self.tabs.widget(i) for i in range(self.tab_count)] @property def modified(self): """ Returns a boolean indication if there are any modified tabs in the editor. """ for widget in self.widgets: if widget.isModified(): return True return False def on_serial_read(self): """ Called when the connected device is ready to send data via the serial connection. It reads all the available data, emits the data_received signal with the received bytes and, if appropriate, emits the tuple_received signal with the tuple created from the bytes received. """ data = bytes(self.serial.readAll()) # get all the available bytes. self.data_received.emit(data) def on_stdout_write(self, data): """ Called when either a running script or the REPL write to STDOUT. """ self.data_received.emit(data) def open_serial_link(self, port): """ Creates a new serial link instance. """ self.input_buffer = [] self.serial = QSerialPort() self.serial.setPortName(port) if self.serial.open(QIODevice.ReadWrite): self.serial.dataTerminalReady = True if not self.serial.isDataTerminalReady(): # Using pyserial as a 'hack' to open the port and set DTR # as QtSerial does not seem to work on some Windows :( # See issues #281 and #302 for details. self.serial.close() pyser = serial.Serial(port) # open serial port w/pyserial pyser.dtr = True pyser.close() self.serial.open(QIODevice.ReadWrite) self.serial.setBaudRate(115200) self.serial.readyRead.connect(self.on_serial_read) else: msg = _("Cannot connect to device on port {}").format(port) raise IOError(msg) def close_serial_link(self): """ Close and clean up the currently open serial link. """ if self.serial: self.serial.close() self.serial = None def add_filesystem(self, home, file_manager): """ Adds the file system pane to the application. """ self.fs_pane = FileSystemPane(home) @self.fs_pane.open_file.connect def on_open_file(file): # Bubble the signal up self.open_file.emit(file) self.fs = QDockWidget(_('Filesystem on micro:bit')) self.fs.setWidget(self.fs_pane) self.fs.setFeatures(QDockWidget.DockWidgetMovable) self.fs.setAllowedAreas(Qt.BottomDockWidgetArea) self.addDockWidget(Qt.BottomDockWidgetArea, self.fs) self.fs_pane.setFocus() file_manager.on_list_files.connect(self.fs_pane.on_ls) self.fs_pane.list_files.connect(file_manager.ls) self.fs_pane.microbit_fs.put.connect(file_manager.put) self.fs_pane.microbit_fs.delete.connect(file_manager.delete) self.fs_pane.microbit_fs.list_files.connect(file_manager.ls) self.fs_pane.local_fs.get.connect(file_manager.get) self.fs_pane.local_fs.list_files.connect(file_manager.ls) file_manager.on_put_file.connect(self.fs_pane.microbit_fs.on_put) file_manager.on_delete_file.connect(self.fs_pane.microbit_fs.on_delete) file_manager.on_get_file.connect(self.fs_pane.local_fs.on_get) file_manager.on_list_fail.connect(self.fs_pane.on_ls_fail) file_manager.on_put_fail.connect(self.fs_pane.on_put_fail) file_manager.on_delete_fail.connect(self.fs_pane.on_delete_fail) file_manager.on_get_fail.connect(self.fs_pane.on_get_fail) self.connect_zoom(self.fs_pane) return self.fs_pane def add_filesystem_esp(self, home, file_manager): """ Adds the file system pane to the application. """ self.fs_pane = EspFileSystemPane(home) @self.fs_pane.open_file.connect def on_open_file(file): # Bubble the signal up self.open_file.emit(file) @self.fs_pane.local_fs.delete.connect def on_delete_file(file): send2trash.send2trash(file) self.fs = QDockWidget(_('Filesystem on mPython board')) self.fs.setWidget(self.fs_pane) self.fs.setFeatures(QDockWidget.DockWidgetMovable) self.fs.setAllowedAreas(Qt.BottomDockWidgetArea) self.addDockWidget(Qt.BottomDockWidgetArea, self.fs) self.fs_pane.setFocus() file_manager.on_list_files.connect(self.fs_pane.on_ls) self.fs_pane.list_files.connect(file_manager.ls) self.fs_pane.esp_fs.put.connect(file_manager.put) self.fs_pane.esp_fs.load_py.connect(file_manager.load_py) self.fs_pane.esp_fs.stop_run_py.connect(file_manager.stop_run_py) self.fs_pane.esp_fs.run_py.connect(file_manager.run_py) self.fs_pane.esp_fs.write_lib.connect(file_manager.write_lib) self.fs_pane.esp_fs.set_default.connect(file_manager.set_default) self.fs_pane.esp_fs.rename.connect(file_manager.rename) self.fs_pane.esp_fs.delete.connect(file_manager.delete) self.fs_pane.esp_fs.list_files.connect(file_manager.ls) self.fs_pane.local_fs.get.connect(file_manager.get) self.fs_pane.local_fs.list_files.connect(file_manager.ls) file_manager.on_put_file.connect(self.fs_pane.esp_fs.on_put) file_manager.on_put_run_file.connect(self.fs_pane.esp_fs.on_put_run) file_manager.on_load_file.connect(self.fs_pane.esp_fs.on_load) file_manager.on_run_file.connect(self.fs_pane.esp_fs.on_run) file_manager.on_delete_file.connect(self.fs_pane.esp_fs.on_delete) file_manager.on_get_file.connect(self.fs_pane.local_fs.on_get) file_manager.on_load_py.connect(self.fs_pane.local_fs.on_load_py) file_manager.on_list_fail.connect(self.fs_pane.on_ls_fail) file_manager.on_put_fail.connect(self.fs_pane.on_put_fail) file_manager.on_load_start.connect(self.fs_pane.on_load_start) file_manager.on_load_fail.connect(self.fs_pane.on_load_fail) file_manager.on_run_fail.connect(self.fs_pane.on_run_fail) file_manager.on_delete_fail.connect(self.fs_pane.on_delete_fail) file_manager.on_get_fail.connect(self.fs_pane.on_get_fail) file_manager.on_set_default.connect(self.fs_pane.esp_fs.on_set_default) file_manager.on_set_default_fail.connect(self.fs_pane.on_set_default_fail) file_manager.on_write_lib_start.connect(self.fs_pane.on_write_lib_start) file_manager.on_write_lib.connect(self.fs_pane.esp_fs.on_write_lib) file_manager.on_write_lib_fail.connect(self.fs_pane.on_write_lib_fail) file_manager.on_rename_start.connect(self.fs_pane.on_rename_start) file_manager.on_rename.connect(self.fs_pane.esp_fs.on_rename) file_manager.on_rename_fail.connect(self.fs_pane.on_rename_fail) self.connect_zoom(self.fs_pane) return self.fs_pane def add_micropython_repl(self, port, name, force_interrupt=True): """ Adds a MicroPython based REPL pane to the application. """ if not self.serial: self.open_serial_link(port) if force_interrupt: # Send a Control-B / exit raw mode. self.serial.write(b'\x02') # Send a Control-C / keyboard interrupt. self.serial.write(b'\x03') repl_pane = MicroPythonREPLPane(serial=self.serial) self.data_received.connect(repl_pane.process_bytes) self.add_repl(repl_pane, name) def add_micropython_plotter(self, port, name, mode): """ Adds a plotter that reads data from a serial connection. """ if not self.serial: self.open_serial_link(port) plotter_pane = PlotterPane() self.data_received.connect(plotter_pane.process_bytes) plotter_pane.data_flood.connect(mode.on_data_flood) self.add_plotter(plotter_pane, name) def add_python3_plotter(self, mode): """ Add a plotter that reads from either the REPL or a running script. Since this function will only be called when either the REPL or a running script are running (but not at the same time), it'll just grab data emitted by the REPL or script via data_received. """ plotter_pane = PlotterPane() self.data_received.connect(plotter_pane.process_bytes) plotter_pane.data_flood.connect(mode.on_data_flood) self.add_plotter(plotter_pane, _('Python3 data tuple')) def add_jupyter_repl(self, kernel_manager, kernel_client): """ Adds a Jupyter based REPL pane to the application. """ kernel_manager.kernel.gui = 'qt4' kernel_client.start_channels() ipython_widget = JupyterREPLPane() ipython_widget.kernel_manager = kernel_manager ipython_widget.kernel_client = kernel_client ipython_widget.on_append_text.connect(self.on_stdout_write) self.add_repl(ipython_widget, _('Python3 (Jupyter)')) def add_repl(self, repl_pane, name): """ Adds the referenced REPL pane to the application. """ self.repl_pane = repl_pane self.repl = QDockWidget(_('{} REPL').format(name)) self.repl.setWidget(repl_pane) self.repl.setFeatures(QDockWidget.DockWidgetMovable) self.repl.setAllowedAreas(Qt.BottomDockWidgetArea | Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.addDockWidget(Qt.BottomDockWidgetArea, self.repl) self.connect_zoom(self.repl_pane) self.repl_pane.set_theme(self.theme) self.repl_pane.setFocus() def add_plotter(self, plotter_pane, name): """ Adds the referenced plotter pane to the application. """ self.plotter_pane = plotter_pane self.plotter = QDockWidget(_('{} Plotter').format(name)) self.plotter.setWidget(plotter_pane) self.plotter.setFeatures(QDockWidget.DockWidgetMovable) self.plotter.setAllowedAreas(Qt.BottomDockWidgetArea | Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.addDockWidget(Qt.BottomDockWidgetArea, self.plotter) self.plotter_pane.set_theme(self.theme) self.plotter_pane.setFocus() def add_python3_runner(self, script_name, working_directory, interactive=False, debugger=False, command_args=None, runner=None, envars=None, python_args=None): """ Display console output for the referenced Python script. The script will be run within the workspace_path directory. If interactive is True (default is False) the Python process will run in interactive mode (dropping the user into the REPL when the script completes). If debugger is True (default is False) the script will be run within a debug runner session. The debugger overrides the interactive flag (you cannot run the debugger in interactive mode). If there is a list of command_args (the default is None) then these will be passed as further arguments into the command run in the new process. If runner is given, this is used as the command to start the Python process. If envars is given, these will become part of the environment context of the new chlid process. If python_args is given, these will be passed as arguments to the Python runtime used to launch the child process. """ self.process_runner = PythonProcessPane(self) self.runner = QDockWidget(_("Running: {}").format( os.path.basename(script_name))) self.runner.setWidget(self.process_runner) self.runner.setFeatures(QDockWidget.DockWidgetMovable) self.runner.setAllowedAreas(Qt.BottomDockWidgetArea | Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.addDockWidget(Qt.BottomDockWidgetArea, self.runner) self.process_runner.start_process(script_name, working_directory, interactive, debugger, command_args, envars, runner, python_args) self.process_runner.setFocus() self.process_runner.on_append_text.connect(self.on_stdout_write) self.connect_zoom(self.process_runner) return self.process_runner def add_debug_inspector(self): """ Display a debug inspector to view the call stack. """ self.debug_inspector = DebugInspector() self.debug_model = QStandardItemModel() self.debug_inspector.setModel(self.debug_model) self.inspector = QDockWidget(_('Debug Inspector')) self.inspector.setWidget(self.debug_inspector) self.inspector.setFeatures(QDockWidget.DockWidgetMovable) self.inspector.setAllowedAreas(Qt.BottomDockWidgetArea | Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.addDockWidget(Qt.RightDockWidgetArea, self.inspector) self.connect_zoom(self.debug_inspector) def update_debug_inspector(self, locals_dict): """ Given the contents of a dict representation of the locals in the current stack frame, update the debug inspector with the new values. """ excluded_names = ['__builtins__', '__debug_code__', '__debug_script__', ] names = sorted([x for x in locals_dict if x not in excluded_names]) self.debug_model.clear() self.debug_model.setHorizontalHeaderLabels([_('Name'), _('Value'), ]) for name in names: try: # DANGER! val = eval(locals_dict[name]) except Exception: val = None if isinstance(val, list): # Show a list consisting of rows of position/value list_item = DebugInspectorItem(name) for i, i_val in enumerate(val): list_item.appendRow([ DebugInspectorItem(str(i)), DebugInspectorItem(repr(i_val)) ]) self.debug_model.appendRow([ list_item, DebugInspectorItem(_('(A list of {} items.)') .format(len(val))) ]) elif isinstance(val, dict): # Show a dict consisting of rows of key/value pairs. dict_item = DebugInspectorItem(name) for k, k_val in val.items(): dict_item.appendRow([ DebugInspectorItem(repr(k)), DebugInspectorItem(repr(k_val)) ]) self.debug_model.appendRow([ dict_item, DebugInspectorItem(_('(A dict of {} items.)') .format(len(val))) ]) else: self.debug_model.appendRow([ DebugInspectorItem(name), DebugInspectorItem(locals_dict[name]), ]) def remove_filesystem(self): """ Removes the file system pane from the application. """ if hasattr(self, 'fs') and self.fs: self.fs_pane = None self.fs.setParent(None) self.fs.deleteLater() self.fs = None def remove_repl(self): """ Removes the REPL pane from the application. """ if self.repl: self.repl_pane = None self.repl.setParent(None) self.repl.deleteLater() self.repl = None if not self.plotter: self.close_serial_link() def remove_plotter(self): """ Removes the plotter pane from the application. """ if self.plotter: self.plotter_pane = None self.plotter.setParent(None) self.plotter.deleteLater() self.plotter = None if not self.repl: self.close_serial_link() def remove_python_runner(self): """ Removes the runner pane from the application. """ if hasattr(self, 'runner') and self.runner: self.process_runner = None self.runner.setParent(None) self.runner.deleteLater() self.runner = None def remove_debug_inspector(self): """ Removes the debug inspector pane from the application. """ if hasattr(self, 'inspector') and self.inspector: self.debug_inspector = None self.debug_model = None self.inspector.setParent(None) self.inspector.deleteLater() self.inspector = None def set_theme(self, theme): """ Sets the theme for the REPL and editor tabs. """ self.theme = theme self.load_theme.emit(theme) if theme == 'contrast': new_theme = ContrastTheme new_icon = 'theme_day' elif theme == 'night': new_theme = NightTheme new_icon = 'theme_contrast' else: new_theme = DayTheme new_icon = 'theme' for widget in self.widgets: widget.set_theme(new_theme) self.button_bar.slots['theme'].setIcon(load_icon(new_icon)) if hasattr(self, 'repl') and self.repl: self.repl_pane.set_theme(theme) if hasattr(self, 'plotter') and self.plotter: self.plotter_pane.set_theme(theme) def show_admin(self, log, settings): """ Display the administrative dialog with referenced content of the log and settings. Return a dictionary of the settings that may have been changed by the admin dialog. """ admin_box = AdminDialog(self) admin_box.setup(log, settings) admin_box.exec() return admin_box.settings() def show_message(self, message, information=None, icon=None): """ Displays a modal message to the user. If information is passed in this will be set as the additional informative text in the modal dialog. Since this mechanism will be used mainly for warning users that something is awry the default icon is set to "Warning". It's possible to override the icon to one of the following settings: NoIcon, Question, Information, Warning or Critical. """ message_box = QMessageBox(self) message_box.setText(message) #message_box.setWindowTitle('Mu') message_box.setWindowTitle(_('mPython2')) if information: message_box.setInformativeText(information) if icon and hasattr(message_box, icon): message_box.setIcon(getattr(message_box, icon)) else: message_box.setIcon(message_box.Warning) logger.debug(message) logger.debug(information) message_box.exec() def show_confirmation(self, message, information=None, icon=None): """ Displays a modal message to the user to which they need to confirm or cancel. If information is passed in this will be set as the additional informative text in the modal dialog. Since this mechanism will be used mainly for warning users that something is awry the default icon is set to "Warning". It's possible to override the icon to one of the following settings: NoIcon, Question, Information, Warning or Critical. """ message_box = QMessageBox(self) message_box.setText(message) #message_box.setWindowTitle(_('Mu')) message_box.setWindowTitle(_('mPython2')) if information: message_box.setInformativeText(information) if icon and hasattr(message_box, icon): message_box.setIcon(getattr(message_box, icon)) else: message_box.setIcon(message_box.Warning) message_box.setStandardButtons(message_box.Cancel | message_box.Ok) message_box.setDefaultButton(message_box.Cancel) logger.debug(message) logger.debug(information) return message_box.exec() def update_title(self, filename=None): """ Updates the title bar of the application. If a filename (representing the name of the file currently the focus of the editor) is supplied, append it to the end of the title. """ title = self.title if filename: title += ' - ' + filename self.setWindowTitle(title) def autosize_window(self): """ Makes the editor 80% of the width*height of the screen and centres it. """ screen = QDesktopWidget().screenGeometry() w = int(screen.width() * 0.8) h = int(screen.height() * 0.8) self.resize(w, h) size = self.geometry() self.move((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2) def reset_annotations(self): """ Resets the state of annotations on the current tab. """ self.current_tab.reset_annotations() def annotate_code(self, feedback, annotation_type): """ Given a list of annotations about the code in the current tab, add the annotations to the editor window so the user can make appropriate changes. """ self.current_tab.annotate_code(feedback, annotation_type) def show_annotations(self): """ Show the annotations added to the current tab. """ self.current_tab.show_annotations() def setup(self, breakpoint_toggle, theme): """ Sets up the window. Defines the various attributes of the window and defines how the user interface is laid out. """ self.theme = theme self.breakpoint_toggle = breakpoint_toggle # Give the window a default icon, title and minimum size. self.setWindowIcon(load_icon(self.icon)) self.update_title() self.read_only_tabs = False self.setMinimumSize(820, 400) self.setTabPosition(Qt.AllDockWidgetAreas, QTabWidget.North) self.widget = QWidget() widget_layout = QVBoxLayout() self.widget.setLayout(widget_layout) self.button_bar = ButtonBar(self.widget) self.tabs = FileTabs() self.tabs.setMovable(True) self.setCentralWidget(self.tabs) self.status_bar = StatusBar(parent=self) self.setStatusBar(self.status_bar) self.addToolBar(self.button_bar) self.show() self.autosize_window() def resizeEvent(self, resizeEvent): """ Respond to window getting too small for the button bar to fit well. """ size = resizeEvent.size() self.button_bar.set_responsive_mode(size.width(), size.height()) def select_mode(self, modes, current_mode): """ Display the mode selector dialog and return the result. """ mode_select = ModeSelector(self) mode_select.setup(modes, current_mode) mode_select.exec() try: return mode_select.get_mode() except Exception: return None def change_mode(self, mode): """ Given a an object representing a mode, recreates the button bar with the expected functionality. """ self.button_bar.change_mode(mode) # Update the autocomplete / tooltip APIs for each tab to the new mode. api = mode.api() for widget in self.widgets: widget.set_api(api) def set_usb_checker(self, duration, callback): """ Sets up a timer that polls for USB changes via the "callback" every "duration" seconds. """ self.usb_checker = QTimer() self.usb_checker.timeout.connect(callback) self.usb_checker.start(duration * 1000) def set_timer(self, duration, callback): """ Set a repeating timer to call "callback" every "duration" seconds. """ self.timer = QTimer() self.timer.timeout.connect(callback) self.timer.start(duration * 1000) # Measured in milliseconds. def stop_timer(self): """ Stop the repeating timer. """ if self.timer: self.timer.stop() self.timer = None def connect_tab_rename(self, handler, shortcut): """ Connect the double-click event on a tab and the keyboard shortcut to the referenced handler (causing the Save As dialog). """ self.tabs.shortcut = QShortcut(QKeySequence(shortcut), self) self.tabs.shortcut.activated.connect(handler) self.tabs.tabBarDoubleClicked.connect(handler) def open_directory_from_os(self, path): """ Given the path to a directory, open the OS's built in filesystem explorer for that path. Works with Windows, OSX and Linux. """ if sys.platform == 'win32': # Windows os.startfile(path) elif sys.platform == 'darwin': # OSX os.system('open "{}"'.format(path)) else: # Assume freedesktop.org on unix-y. os.system('xdg-open "{}"'.format(path)) def connect_find_replace(self, handler, shortcut): """ Create a keyboard shortcut and associate it with a handler for doing a find and replace. """ self.find_replace_shortcut = QShortcut(QKeySequence(shortcut), self) self.find_replace_shortcut.activated.connect(handler) def show_find_replace(self, find, replace, global_replace): """ Display the find/replace dialog. If the dialog's OK button was clicked return a tuple containing the find term, replace term and global replace flag. """ finder = FindReplaceDialog(self) finder.setup(find, replace, global_replace) if finder.exec(): return (finder.find(), finder.replace(), finder.replace_flag()) def replace_text(self, target_text, replace, global_replace): """ Given target_text, replace the first instance after the cursor with "replace". If global_replace is true, replace all instances of "target". Returns the number of times replacement has occurred. """ if not self.current_tab: return 0 if global_replace: counter = 0 found = self.current_tab.findFirst(target_text, True, True, False, False, line=0, index=0) if found: counter += 1 self.current_tab.replace(replace) while self.current_tab.findNext(): self.current_tab.replace(replace) counter += 1 return counter else: found = self.current_tab.findFirst(target_text, True, True, False, True) if found: self.current_tab.replace(replace) return 1 else: return 0 def highlight_text(self, target_text): """ Highlight the first match from the current position of the cursor in the current tab for the target_text. Returns True if there's a match. """ if self.current_tab: return self.current_tab.findFirst(target_text, True, True, False, True) else: return False def connect_toggle_comments(self, handler, shortcut): """ Create a keyboard shortcut and associate it with a handler for toggling comments on highlighted lines. """ self.toggle_comments_shortcut = QShortcut(QKeySequence(shortcut), self) self.toggle_comments_shortcut.activated.connect(handler) def toggle_comments(self): """ Toggle comments on/off for all selected line in the currently active tab. """ if self.current_tab: self.current_tab.toggle_comments() def download_url(self, _version, _url): result = self.show_confirmation(_("\nFound a new version '{}' of the" " software, download it now ?").format(_version), icon='Question') if result == QMessageBox.Ok: webbrowser.open_new(_url)
def setup_ui(self, main_window): super().setup_ui(main_window) # col: 0,key; 1,value; 2,type; 3,image_path self.tree_widget_left.setColumnCount(4) self.tree_widget_left.setColumnWidth(0, 200) self.tree_widget_left.setColumnHidden(1, True) self.tree_widget_left.setColumnHidden(2, True) self.tree_widget_left.setColumnHidden(3, True) self.tree_widget_left.setContextMenuPolicy(Qt.CustomContextMenu) self.tree_widget_left.headerItem().setText(0, "") self.tree_widget_left.headerItem().setText(1, "") self.tree_widget_left.headerItem().setText(2, "") self.tree_widget_left.headerItem().setText(3, "") # canvas replace TextEdit self.graph_view.setWidget(QTextEdit()) # self.graph_view.setWidget(self.canvas) self.graph_view.setWidgetResizable(True) self.video_button.hide() self.video_slider.hide() self.video_label.hide() self.text_edit.setMaximumSize(QSize(167700, 167)) # col: 0,key; 1,value; 2,type; 3,image_path self.tree_widget_right.setColumnCount(4) self.tree_widget_right.setColumnWidth(0, 200) self.tree_widget_right.headerItem().setText(0, "Attribute") self.tree_widget_right.setContextMenuPolicy(Qt.CustomContextMenu) self.tree_widget_right.headerItem().setText(1, "") self.tree_widget_right.headerItem().setText(2, "") self.tree_widget_right.headerItem().setText(3, "") self.tree_widget_right.setMinimumSize(QSize(600, 16777215)) self.tree_widget_right.setColumnHidden(2, True) self.tree_widget_right.setColumnHidden(3, True) self.labelCoordinates = QLabel('') self.status_bar.addPermanentWidget(self.labelCoordinates) dock_tree_widget_left = QDockWidget('', main_window) dock_tree_widget_left.setObjectName(u'treeWidgetR') dock_tree_widget_left.setWidget(self.tree_widget_left) dock_tree_widget_left.setFeatures(QDockWidget.DockWidgetMovable) main_window.addDockWidget(Qt.LeftDockWidgetArea, dock_tree_widget_left) dock_tree_widget_right = QDockWidget('', main_window) dock_tree_widget_right.setObjectName(u'treeWidgetL') dock_tree_widget_right.setWidget(self.tree_widget_right) dock_tree_widget_right.setFeatures(QDockWidget.DockWidgetMovable) main_window.addDockWidget(Qt.RightDockWidgetArea, dock_tree_widget_right) self.retranslate_ui(main_window) QMetaObject.connectSlotsByName(main_window) # 日志框设置为不可编辑 # 日志框可以拖动,未实现 self.text_edit.setReadOnly(True) dock = QDockWidget('', main_window) dock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) dock.setWidget(self.textEdit) main_window.addDockWidget(Qt.TopDockWidgetArea, dock) self.tree_widget_left.setItemDelegateForColumn( 0, EmptyDelegate(self.tree_widget_left)) self.tree_widget_right.setItemDelegateForColumn( 0, EmptyDelegate(self.tree_widget_right))
def _create_ui_elem(self, elem): elem = elem.lower() if not isinstance(elem, str): return if elem not in self._ui_elems: self._ui_elems.append(elem) elem_wiget = None if elem == 'watchpoints': from dwarf_debugger.ui.session_widgets.watchpoints import WatchpointsWidget self.watchpoints_dwidget = QDockWidget('Watchpoints', self) self.watchpoints_panel = WatchpointsWidget(self) # dont respond to dblclick mem cant be shown # self.watchpoints_panel.onItemDoubleClicked.connect( # self._on_watchpoint_clicked) self.watchpoints_panel.onItemRemoved.connect( self._on_watchpoint_removeditem) self.watchpoints_panel.onItemAdded.connect(self._on_watchpoint_added) self.watchpoints_dwidget.setWidget(self.watchpoints_panel) self.watchpoints_dwidget.setObjectName('WatchpointsWidget') self.addDockWidget(Qt.LeftDockWidgetArea, self.watchpoints_dwidget) self.view_menu.addAction(self.watchpoints_dwidget.toggleViewAction()) elem_wiget = self.watchpoints_panel elif elem == 'breakpoints': from dwarf_debugger.ui.session_widgets.breakpoints import BreakpointsWidget self.breakpoint_dwiget = QDockWidget('breakpoint', self) self.breakpoints_panel = BreakpointsWidget(self) self.breakpoints_panel.onBreakpointRemoved.connect(self._on_breakpoint_removed) self.breakpoint_dwiget.setWidget(self.breakpoints_panel) self.breakpoint_dwiget.setObjectName('breakpointWidget') self.addDockWidget(Qt.LeftDockWidgetArea, self.breakpoint_dwiget) self.view_menu.addAction(self.breakpoint_dwiget.toggleViewAction()) elem_wiget = self.breakpoints_panel elif elem == 'bookmarks': from dwarf_debugger.ui.session_widgets.bookmarks import BookmarksWidget self.bookmarks_dwiget = QDockWidget('Boomarks', self) self.bookmarks_panel = BookmarksWidget(self) self.bookmarks_dwiget.setWidget(self.bookmarks_panel) self.bookmarks_dwiget.setObjectName('BookmarksWidget') self.addDockWidget(Qt.LeftDockWidgetArea, self.bookmarks_dwiget) self.view_menu.addAction(self.bookmarks_dwiget.toggleViewAction()) elem_wiget = self.bookmarks_panel elif elem == 'registers': from dwarf_debugger.ui.session_widgets.context import ContextWidget self.registers_dock = QDockWidget('Context', self) self.context_panel = ContextWidget(self) self.registers_dock.setWidget(self.context_panel) self.registers_dock.setObjectName('ContextWidget') self.addDockWidget(Qt.RightDockWidgetArea, self.registers_dock) self.view_menu.addAction(self.registers_dock.toggleViewAction()) elem_wiget = self.context_panel elif elem == 'debug': from dwarf_debugger.ui.panels.panel_debug import QDebugPanel self.debug_panel = QDebugPanel(self) self.main_tabs.addTab(self.debug_panel, 'Debug') elem_wiget = self.debug_panel elif elem == 'jvm-debugger': from dwarf_debugger.ui.panels.panel_java_explorer import JavaExplorerPanel self.java_explorer_panel = JavaExplorerPanel(self) self.main_tabs.addTab(self.java_explorer_panel, 'JVM debugger') self.main_tabs.tabBar().moveTab( self.main_tabs.indexOf(self.java_explorer_panel), 1) elem_wiget = self.java_explorer_panel elif elem == 'jvm-inspector': from dwarf_debugger.ui.panels.panel_java_inspector import JavaInspector self.java_inspector_panel = JavaInspector(self) self.main_tabs.addTab(self.java_inspector_panel, 'JVM inspector') elem_wiget = self.java_inspector_panel elif elem == 'objc-inspector': from dwarf_debugger.ui.panels.panel_objc_inspector import ObjCInspector self.objc_inspector_panel = ObjCInspector(self) self.main_tabs.addTab(self.objc_inspector_panel, 'ObjC inspector') elem_wiget = self.objc_inspector_panel elif elem == 'console': from dwarf_debugger.ui.session_widgets.console import ConsoleWidget self.console_dock = QDockWidget('Console', self) self.console_panel = ConsoleWidget(self) if self.dwarf_args.script and len(self.dwarf_args.script) > 0 and os.path.exists(self.dwarf_args.script): with open(self.dwarf_args.script, 'r') as f: self.console_panel.get_js_console().script_file = self.dwarf_args.script self.console_panel.get_js_console().function_content = f.read() self.dwarf.onLogToConsole.connect(self._log_js_output) self.dwarf.onLogEvent.connect(self._log_event) self.console_dock.setWidget(self.console_panel) self.console_dock.setObjectName('ConsoleWidget') self.addDockWidget(Qt.BottomDockWidgetArea, self.console_dock) self.view_menu.addAction(self.console_dock.toggleViewAction()) elem_wiget = self.console_panel elif elem == 'backtrace': from dwarf_debugger.ui.session_widgets.backtrace import BacktraceWidget self.backtrace_dock = QDockWidget('Backtrace', self) self.backtrace_panel = BacktraceWidget(self) self.backtrace_dock.setWidget(self.backtrace_panel) self.backtrace_dock.setObjectName('BacktraceWidget') self.backtrace_panel.onShowMemoryRequest.connect(self._on_showmemory_request) self.addDockWidget(Qt.RightDockWidgetArea, self.backtrace_dock) self.view_menu.addAction(self.backtrace_dock.toggleViewAction()) elem_wiget = self.backtrace_panel elif elem == 'threads': from dwarf_debugger.ui.session_widgets.threads import ThreadsWidget self.threads_dock = QDockWidget('Threads', self) self.contexts_list_panel = ThreadsWidget(self) self.dwarf.onThreadResumed.connect( self.contexts_list_panel.resume_tid) self.contexts_list_panel.onItemDoubleClicked.connect( self._manually_apply_context) self.threads_dock.setWidget(self.contexts_list_panel) self.threads_dock.setObjectName('ThreadPanel') self.addDockWidget(Qt.RightDockWidgetArea, self.threads_dock) self.view_menu.addAction(self.threads_dock.toggleViewAction()) elem_wiget = self.contexts_list_panel elif elem == 'modules': from dwarf_debugger.ui.panels.panel_modules import ModulesPanel self.modules_panel = ModulesPanel(self) self.modules_panel.onModuleSelected.connect( self._on_module_dblclicked) self.modules_panel.onModuleFuncSelected.connect( self._on_modulefunc_dblclicked) self.modules_panel.onAddBreakpoint.connect(self._on_addmodule_breakpoint) self.modules_panel.onDumpBinary.connect(self._on_dump_module) self.main_tabs.addTab(self.modules_panel, 'Modules') elem_wiget = self.modules_panel elif elem == 'ranges': from dwarf_debugger.ui.panels.panel_ranges import RangesPanel self.ranges_panel = RangesPanel(self) self.ranges_panel.onItemDoubleClicked.connect( self._range_dblclicked) self.ranges_panel.onDumpBinary.connect(self._on_dump_module) # connect to watchpointpanel func self.ranges_panel.onAddWatchpoint.connect( self.watchpoints_panel.do_addwatchpoint_dlg) self.main_tabs.addTab(self.ranges_panel, 'Ranges') elem_wiget = self.ranges_panel elif elem == 'search': from dwarf_debugger.ui.panels.panel_search import SearchPanel self.search_panel = SearchPanel(self) self.main_tabs.addTab(self.search_panel, 'Search') elem_wiget = self.search_panel elif elem == 'data': from dwarf_debugger.ui.panels.panel_data import DataPanel self.data_panel = DataPanel(self) self.main_tabs.addTab(self.data_panel, 'Data') elem_wiget = self.data_panel elif elem == 'jvm-tracer': from dwarf_debugger.ui.panels.panel_java_trace import JavaTracePanel self.java_trace_panel = JavaTracePanel(self) self.main_tabs.addTab(self.java_trace_panel, 'JVM tracer') elem_wiget = self.java_trace_panel elif elem == 'smali': from dwarf_debugger.ui.panels.panel_smali import SmaliPanel self.smali_panel = SmaliPanel() self.main_tabs.addTab(self.smali_panel, 'Smali') elem_wiget = self.smali_panel else: print('no handler for elem: ' + elem) if elem_wiget is not None: self.onSystemUIElementCreated.emit(elem, elem_wiget) # TODO: remove add @2x for item in self.findChildren(QDockWidget): if item: if 'darwin' in sys.platform: item.setStyleSheet( 'QDockWidget::title { padding-left:-30px; }' )
def __init__(self, title="", parent=None, flags=Qt.WindowFlags(), bind_widget=None, close_slot=None, toggle_slot=None): QDockWidget.__init__(self, title, parent, flags) self.installEventFilter(self) self.main_win = parent # default stlyesheets for title bars self.title_stylesheet = "QWidget {background: rgb(68,68,68);}" self.button_style = "QPushButton:hover:!pressed {background: grey;}" from TigGUI.Images.ControlDialog import ImageControlDialog from TigGUI.Plot.SkyModelPlot import ToolDialog from TigGUI.Plot.SkyModelPlot import LiveImageZoom if bind_widget is not None: self.bind_widget = bind_widget if bind_widget is not None: if isinstance(bind_widget, ToolDialog): self.tdock_style = "ToolDialog {border: 1.5px solid rgb(68,68,68);}" elif isinstance(bind_widget, ImageControlDialog): self.tdock_style = "ImageControlDialog {border: 1.5px solid rgb(68,68,68);}" # set default sizes for QDockWidgets self.btn_w = 28 self.btn_h = 28 self.icon_size = QSize(20, 20) self.font_size = 8 # setup custom title bar for profiles dockable self.dock_title_bar = QWidget() self.dock_title_bar.setContentsMargins(0, 0, 0, 0) self.dock_title_bar.setStyleSheet(self.title_stylesheet) self.dock_title_bar.setBaseSize(0, 0) self.dock_title_layout = QHBoxLayout() self.dock_title_layout.setContentsMargins(0, 0, 0, 0) self.dock_title_layout.setSpacing(0) self.dock_title_bar.setLayout(self.dock_title_layout) # custom close button self.close_button = QPushButton() self.close_button.setStyleSheet(self.button_style) self.close_button.setMaximumWidth(self.btn_w) self.close_button.setMaximumHeight(self.btn_h) self.close_button.setContentsMargins(0, 0, 0, 0) self.close_button.setBaseSize(0, 0) self.close_icon = self.dock_title_bar.style().standardIcon(QStyle.SP_TitleBarCloseButton) self.close_button.setIcon(self.close_icon) self.close_button.setToolTip("Close") # custom toggle button self.toggle_button = QPushButton() self.toggle_button.setStyleSheet(self.button_style) self.toggle_button.setMaximumWidth(self.btn_w) self.toggle_button.setMaximumHeight(self.btn_h) self.toggle_button.setContentsMargins(0, 0, 0, 0) self.toggle_button.setBaseSize(0, 0) self.toggle_icon = self.dock_title_bar.style().standardIcon(QStyle.SP_TitleBarShadeButton) self.toggle_button.setIcon(self.toggle_icon) self.toggle_button.setToolTip("Dock/float widget") # tigger logo self.image0 = pixmaps.tigger_logo.pm() self.title_icon = QLabel() self.title_icon.setContentsMargins(0, 0, 0, 0) self.title_icon.setBaseSize(0, 0) self.title_icon.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.title_icon.setScaledContents(True) self.title_icon.setPixmap(self.image0) self.title_icon.setAlignment(Qt.AlignCenter) self.title_icon.setMaximumSize(self.icon_size) # set dock widget title self.title_font = QFont() self.title_font.setBold(True) self.title_font.setPointSize(self.font_size) if bind_widget is not None: if isinstance(bind_widget, ImageControlDialog): self.dock_title = QLabel(f"{title}: Control Dialog") else: self.dock_title = QLabel(title) self.dock_title.setFont(self.title_font) self.dock_title.setAlignment(Qt.AlignCenter) self.dock_title.setContentsMargins(0, 0, 0, 0) self.dock_title.setBaseSize(0, 0) self.dock_title.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) # add dock widget title items to layout self.dock_title_layout.addWidget(self.title_icon) self.dock_title_layout.addWidget(self.dock_title) self.dock_title_layout.addWidget(self.toggle_button) self.dock_title_layout.addWidget(self.close_button) # set up profiles as dockable self.setStyleSheet(self.tdock_style) self.setWidget(bind_widget) self.setFeatures(QDockWidget.AllDockWidgetFeatures) if bind_widget is not None: if isinstance(bind_widget, ToolDialog): self.setAllowedAreas(Qt.AllDockWidgetAreas) elif isinstance(bind_widget, ImageControlDialog): self.setAllowedAreas(Qt.RightDockWidgetArea | Qt.LeftDockWidgetArea) self.setTitleBarWidget(self.dock_title_bar) self.setFloating(False) # get current sizeHints() if bind_widget is not None: self.setBaseSize(bind_widget.sizeHint()) if isinstance(bind_widget, LiveImageZoom): bind_widget.livezoom_resize_signal.connect(self._resizeDockWidget) if close_slot is not None: self.close_button.clicked.connect(close_slot) if toggle_slot is not None: self.toggle_button.clicked.connect(toggle_slot)
class AppWindow(QMainWindow): onRestart = pyqtSignal(name='onRestart') onSystemUIElementCreated = pyqtSignal(str, QWidget, name='onSystemUIElementCreated') onSystemUIElementRemoved = pyqtSignal(str, name='onSystemUIElementRemoved') def __init__(self, dwarf_args, flags=None): super(AppWindow, self).__init__(flags) self.dwarf_args = dwarf_args self.session_manager = SessionManager(self) self.session_manager.sessionCreated.connect(self.session_created) self.session_manager.sessionStopped.connect(self.session_stopped) self.session_manager.sessionClosed.connect(self.session_closed) self._tab_order = [ 'debug', 'modules', 'ranges', 'jvm-inspector', 'jvm-debugger' ] self._is_newer_dwarf = False self.q_settings = QSettings(utils.home_path() + "dwarf_window_pos.ini", QSettings.IniFormat) self.menu = self.menuBar() self.view_menu = None self._initialize_ui_elements() self.setWindowTitle( 'Dwarf - A debugger for reverse engineers, crackers and security analyst' ) # load external assets _app = QApplication.instance() # themes self.prefs = Prefs() utils.set_theme(self.prefs.get('dwarf_ui_theme', 'dark'), self.prefs) # load font if os.path.exists(utils.resource_path('assets/Anton.ttf')): QFontDatabase.addApplicationFont( utils.resource_path('assets/Anton.ttf')) else: QFontDatabase.addApplicationFont(':/assets/Anton.ttf') if os.path.exists(utils.resource_path('assets/OpenSans-Regular.ttf')): QFontDatabase.addApplicationFont( utils.resource_path('assets/OpenSans-Regular.ttf')) else: QFontDatabase.addApplicationFont(':/assets/OpenSans-Regular.ttf') if os.path.exists(utils.resource_path('assets/OpenSans-Bold.ttf')): QFontDatabase.addApplicationFont( utils.resource_path('assets/OpenSans-Bold.ttf')) else: QFontDatabase.addApplicationFont(':/assets/OpenSans-Bold.ttf') font = QFont("OpenSans", 9, QFont.Normal) # TODO: add settingsdlg font_size = self.prefs.get('dwarf_ui_font_size', 12) font.setPixelSize(font_size) _app.setFont(font) # mainwindow statusbar self.progressbar = QProgressBar() self.progressbar.setRange(0, 0) self.progressbar.setVisible(False) self.progressbar.setFixedHeight(15) self.progressbar.setFixedWidth(100) self.progressbar.setTextVisible(False) self.progressbar.setValue(30) self.statusbar = QStatusBar(self) self.statusbar.setAutoFillBackground(False) self.statusbar.addPermanentWidget(self.progressbar) self.statusbar.setObjectName("statusbar") self.setStatusBar(self.statusbar) self.main_tabs = QTabWidget(self) self.main_tabs.setMovable(False) self.main_tabs.setTabsClosable(True) self.main_tabs.setAutoFillBackground(True) self.main_tabs.tabCloseRequested.connect(self._on_close_tab) self.setCentralWidget(self.main_tabs) # pluginmanager self.plugin_manager = PluginManager(self) self.plugin_manager.reload_plugins() self.welcome_window = None if dwarf_args.any == '': self.welcome_window = WelcomeDialog(self) self.welcome_window.setModal(True) self.welcome_window.onIsNewerVersion.connect( self._enable_update_menu) self.welcome_window.onUpdateComplete.connect( self._on_dwarf_updated) self.welcome_window.setWindowTitle( 'Welcome to Dwarf - A debugger for reverse engineers, crackers and security analyst' ) self.welcome_window.onSessionSelected.connect(self._start_session) # wait for welcome screen self.hide() self.welcome_window.show() else: print('* Starting new Session') self._start_session(dwarf_args.target) def _initialize_ui_elements(self): # dockwidgets self.watchpoints_dwidget = None self.breakpoint_dwiget = None self.bookmarks_dwiget = None self.registers_dock = None self.console_dock = None self.backtrace_dock = None self.threads_dock = None # panels self.asm_panel = None self.backtrace_panel = None self.bookmarks_panel = None self.console_panel = None self.context_panel = None self.debug_panel = None self.contexts_list_panel = None self.data_panel = None self.ftrace_panel = None self.breakpoints_panel = None self.objc_inspector_panel = None self.java_inspector_panel = None self.java_explorer_panel = None self.java_trace_panel = None self.modules_panel = None self.ranges_panel = None self.search_panel = None self.smali_panel = None self.watchpoints_panel = None self._ui_elems = [] def _setup_main_menu(self): self.menu = self.menuBar() dwarf_menu = QMenu('Dwarf', self) theme = QMenu('Theme', dwarf_menu) theme.addAction('Black') theme.addAction('Dark') theme.addAction('Light') theme.triggered.connect(self._set_theme) dwarf_menu.addMenu(theme) dwarf_menu.addSeparator() if self._is_newer_dwarf: dwarf_menu.addAction('Update', self._update_dwarf) dwarf_menu.addAction('Close', self.close) self.menu.addMenu(dwarf_menu) session = self.session_manager.session if session is not None: session_menu = session.main_menu if isinstance(session_menu, list): for menu in session_menu: self.menu.addMenu(menu) else: self.menu.addMenu(session_menu) # plugins if self.plugin_manager.plugins: self.plugin_menu = QMenu('Plugins', self) for plugin in self.plugin_manager.plugins: plugin_instance = self.plugin_manager.plugins[plugin] plugin_sub_menu = self.plugin_menu.addMenu(plugin_instance.name) try: actions = plugin_instance.__get_top_menu_actions__() for action in actions: plugin_sub_menu.addAction(action) except: pass if not plugin_sub_menu.isEmpty(): plugin_sub_menu.addSeparator() about = plugin_sub_menu.addAction('About') about.triggered.connect( lambda x, item=plugin: self._show_plugin_about(item)) if not self.plugin_menu.isEmpty(): self.menu.addMenu(self.plugin_menu) self.view_menu = QMenu('View', self) self.panels_menu = QMenu('Panels', self.view_menu) self.panels_menu.addAction( 'Search', lambda: self.show_main_tab('search'), shortcut=QKeySequence(Qt.CTRL + Qt.Key_F3)) self.panels_menu.addAction( 'Modules', lambda: self.show_main_tab('modules') ) self.panels_menu.addAction( 'Ranges', lambda: self.show_main_tab('ranges') ) self.view_menu.addMenu(self.panels_menu) self.debug_view_menu = self.view_menu.addMenu('Debug') self.view_menu.addSeparator() self.view_menu.addAction('Hide all', self._hide_all_widgets, shortcut=QKeySequence(Qt.CTRL + Qt.Key_F1)) self.view_menu.addAction('Show all', self._show_all_widgets, shortcut=QKeySequence(Qt.CTRL + Qt.Key_F2)) self.view_menu.addSeparator() self.menu.addMenu(self.view_menu) if self.dwarf_args.debug_script: debug_menu = QMenu('Debug', self) debug_menu.addAction('Reload core', self._menu_reload_core) debug_menu.addAction('Debug dwarf js core', self._menu_debug_dwarf_js) self.menu.addMenu(debug_menu) # tools _tools = self.prefs.get('tools') if _tools: tools_menu = QMenu('Tools', self) for _tool in _tools: if _tool and _tool['name']: if _tool['name'] == 'sep': tools_menu.addSeparator() continue _cmd = _tool['cmd'] tools_menu.addAction(_tool['name']) if not tools_menu.isEmpty(): tools_menu.triggered.connect(self._execute_tool) self.menu.addMenu(tools_menu) about_menu = QMenu('About', self) about_menu.addAction('Dwarf on GitHub', self._menu_github) about_menu.addAction('Documention', self._menu_documentation) about_menu.addAction('Api', self._menu_api) about_menu.addAction('Slack', self._menu_slack) about_menu.addSeparator() about_menu.addAction('Info', self._show_about_dlg) self.menu.addMenu(about_menu) def _show_plugin_about(self, plugin): plugin = self.plugin_manager.plugins[plugin] if plugin: info = plugin.__get_plugin_info__() version = utils.safe_read_map(info, 'version', '') description = utils.safe_read_map(info, 'description', '') author = utils.safe_read_map(info, 'author', '') homepage = utils.safe_read_map(info, 'homepage', '') license_ = utils.safe_read_map(info, 'license', '') utils.show_message_box( 'Name: {0}\nVersion: {1}\nDescription: {2}\nAuthor: {3}\nHomepage: {4}\nLicense: {5}'. format(plugin.name, version, description, author, homepage, license_)) def _enable_update_menu(self): self._is_newer_dwarf = True def _update_dwarf(self): if self.welcome_window: self.welcome_window._update_dwarf() def _on_close_tab(self, index): tab_text = self.main_tabs.tabText(index) if tab_text: tab_text = tab_text.lower().replace(' ', '-') try: self._ui_elems.remove(tab_text) except ValueError: # recheck ValueError: list.remove(x): x not in list pass self.main_tabs.removeTab(index) self.onSystemUIElementRemoved.emit(tab_text) def _on_dwarf_updated(self): self.onRestart.emit() def _execute_tool(self, qaction): if qaction: _tools = self.prefs.get('tools') if _tools: for _tool in _tools: if _tool and _tool['name'] and _tool['name'] != 'sep': if qaction.text() == _tool['name']: try: import subprocess subprocess.Popen(_tool['cmd'], creationflags=subprocess.CREATE_NEW_CONSOLE) except: pass break def _set_theme(self, qaction): if qaction: utils.set_theme(qaction.text(), self.prefs) def _hide_all_widgets(self): self.watchpoints_dwidget.hide() self.breakpoint_dwiget.hide() self.bookmarks_dwiget.hide() self.registers_dock.hide() self.console_dock.hide() self.backtrace_dock.hide() self.threads_dock.hide() def _show_all_widgets(self): self.watchpoints_dwidget.show() self.breakpoint_dwiget.show() self.bookmarks_dwiget.show() self.registers_dock.show() self.console_dock.show() self.backtrace_dock.show() self.threads_dock.show() def _menu_reload_core(self): self.dwarf.script.exports.reload() def _menu_debug_dwarf_js(self): you_know_what_to_do = json.loads( self.dwarf.script.exports.debugdwarfjs()) return you_know_what_to_do def show_main_tab(self, name): name = name.lower() # elem doesnt exists? create it if name not in self._ui_elems: self._create_ui_elem(name) index = 0 name = name.join(name.split()).lower() if name == 'ranges': index = self.main_tabs.indexOf(self.ranges_panel) elif name == 'search': index = self.main_tabs.indexOf(self.search_panel) elif name == 'modules': index = self.main_tabs.indexOf(self.modules_panel) elif name == 'data': index = self.main_tabs.indexOf(self.data_panel) elif name == 'jvm-tracer': index = self.main_tabs.indexOf(self.java_trace_panel) elif name == 'jvm-inspector': index = self.main_tabs.indexOf(self.java_inspector_panel) elif name == 'jvm-debugger': index = self.main_tabs.indexOf(self.java_explorer_panel) elif name == 'objc-inspector': index = self.main_tabs.indexOf(self.objc_inspector_panel) elif name == 'smali': index = self.main_tabs.indexOf(self.smali_panel) self.main_tabs.setCurrentIndex(index) def jump_to_address(self, ptr, view=0, show_panel=True): if show_panel: self.show_main_tab('debug') self.debug_panel.jump_to_address(ptr, view=view) @pyqtSlot(name='mainMenuGitHub') def _menu_github(self): QDesktopServices.openUrl(QUrl('https://github.com/iGio90/Dwarf')) @pyqtSlot(name='mainMenuApi') def _menu_api(self): QDesktopServices.openUrl(QUrl('https://igio90.github.io/Dwarf/')) @pyqtSlot(name='mainMenuDocumentation') def _menu_documentation(self): QDesktopServices.openUrl(QUrl('http://www.giovanni-rocca.com/dwarf/')) @pyqtSlot(name='mainMenuSlack') def _menu_slack(self): QDesktopServices.openUrl( QUrl('https://join.slack.com/t/resecret/shared_invite' '/enQtMzc1NTg4MzE3NjA1LTlkNzYxNTIwYTc2ZTYyOWY1MT' 'Q1NzBiN2ZhYjQwYmY0ZmRhODQ0NDE3NmRmZjFiMmE1MDYwN' 'WJlNDVjZDcwNGE')) def _show_about_dlg(self): about_dlg = AboutDialog(self) about_dlg.show() def _create_ui_elem(self, elem): elem = elem.lower() if not isinstance(elem, str): return if elem not in self._ui_elems: self._ui_elems.append(elem) elem_wiget = None if elem == 'watchpoints': from dwarf_debugger.ui.session_widgets.watchpoints import WatchpointsWidget self.watchpoints_dwidget = QDockWidget('Watchpoints', self) self.watchpoints_panel = WatchpointsWidget(self) # dont respond to dblclick mem cant be shown # self.watchpoints_panel.onItemDoubleClicked.connect( # self._on_watchpoint_clicked) self.watchpoints_panel.onItemRemoved.connect( self._on_watchpoint_removeditem) self.watchpoints_panel.onItemAdded.connect(self._on_watchpoint_added) self.watchpoints_dwidget.setWidget(self.watchpoints_panel) self.watchpoints_dwidget.setObjectName('WatchpointsWidget') self.addDockWidget(Qt.LeftDockWidgetArea, self.watchpoints_dwidget) self.view_menu.addAction(self.watchpoints_dwidget.toggleViewAction()) elem_wiget = self.watchpoints_panel elif elem == 'breakpoints': from dwarf_debugger.ui.session_widgets.breakpoints import BreakpointsWidget self.breakpoint_dwiget = QDockWidget('breakpoint', self) self.breakpoints_panel = BreakpointsWidget(self) self.breakpoints_panel.onBreakpointRemoved.connect(self._on_breakpoint_removed) self.breakpoint_dwiget.setWidget(self.breakpoints_panel) self.breakpoint_dwiget.setObjectName('breakpointWidget') self.addDockWidget(Qt.LeftDockWidgetArea, self.breakpoint_dwiget) self.view_menu.addAction(self.breakpoint_dwiget.toggleViewAction()) elem_wiget = self.breakpoints_panel elif elem == 'bookmarks': from dwarf_debugger.ui.session_widgets.bookmarks import BookmarksWidget self.bookmarks_dwiget = QDockWidget('Boomarks', self) self.bookmarks_panel = BookmarksWidget(self) self.bookmarks_dwiget.setWidget(self.bookmarks_panel) self.bookmarks_dwiget.setObjectName('BookmarksWidget') self.addDockWidget(Qt.LeftDockWidgetArea, self.bookmarks_dwiget) self.view_menu.addAction(self.bookmarks_dwiget.toggleViewAction()) elem_wiget = self.bookmarks_panel elif elem == 'registers': from dwarf_debugger.ui.session_widgets.context import ContextWidget self.registers_dock = QDockWidget('Context', self) self.context_panel = ContextWidget(self) self.registers_dock.setWidget(self.context_panel) self.registers_dock.setObjectName('ContextWidget') self.addDockWidget(Qt.RightDockWidgetArea, self.registers_dock) self.view_menu.addAction(self.registers_dock.toggleViewAction()) elem_wiget = self.context_panel elif elem == 'debug': from dwarf_debugger.ui.panels.panel_debug import QDebugPanel self.debug_panel = QDebugPanel(self) self.main_tabs.addTab(self.debug_panel, 'Debug') elem_wiget = self.debug_panel elif elem == 'jvm-debugger': from dwarf_debugger.ui.panels.panel_java_explorer import JavaExplorerPanel self.java_explorer_panel = JavaExplorerPanel(self) self.main_tabs.addTab(self.java_explorer_panel, 'JVM debugger') self.main_tabs.tabBar().moveTab( self.main_tabs.indexOf(self.java_explorer_panel), 1) elem_wiget = self.java_explorer_panel elif elem == 'jvm-inspector': from dwarf_debugger.ui.panels.panel_java_inspector import JavaInspector self.java_inspector_panel = JavaInspector(self) self.main_tabs.addTab(self.java_inspector_panel, 'JVM inspector') elem_wiget = self.java_inspector_panel elif elem == 'objc-inspector': from dwarf_debugger.ui.panels.panel_objc_inspector import ObjCInspector self.objc_inspector_panel = ObjCInspector(self) self.main_tabs.addTab(self.objc_inspector_panel, 'ObjC inspector') elem_wiget = self.objc_inspector_panel elif elem == 'console': from dwarf_debugger.ui.session_widgets.console import ConsoleWidget self.console_dock = QDockWidget('Console', self) self.console_panel = ConsoleWidget(self) if self.dwarf_args.script and len(self.dwarf_args.script) > 0 and os.path.exists(self.dwarf_args.script): with open(self.dwarf_args.script, 'r') as f: self.console_panel.get_js_console().script_file = self.dwarf_args.script self.console_panel.get_js_console().function_content = f.read() self.dwarf.onLogToConsole.connect(self._log_js_output) self.dwarf.onLogEvent.connect(self._log_event) self.console_dock.setWidget(self.console_panel) self.console_dock.setObjectName('ConsoleWidget') self.addDockWidget(Qt.BottomDockWidgetArea, self.console_dock) self.view_menu.addAction(self.console_dock.toggleViewAction()) elem_wiget = self.console_panel elif elem == 'backtrace': from dwarf_debugger.ui.session_widgets.backtrace import BacktraceWidget self.backtrace_dock = QDockWidget('Backtrace', self) self.backtrace_panel = BacktraceWidget(self) self.backtrace_dock.setWidget(self.backtrace_panel) self.backtrace_dock.setObjectName('BacktraceWidget') self.backtrace_panel.onShowMemoryRequest.connect(self._on_showmemory_request) self.addDockWidget(Qt.RightDockWidgetArea, self.backtrace_dock) self.view_menu.addAction(self.backtrace_dock.toggleViewAction()) elem_wiget = self.backtrace_panel elif elem == 'threads': from dwarf_debugger.ui.session_widgets.threads import ThreadsWidget self.threads_dock = QDockWidget('Threads', self) self.contexts_list_panel = ThreadsWidget(self) self.dwarf.onThreadResumed.connect( self.contexts_list_panel.resume_tid) self.contexts_list_panel.onItemDoubleClicked.connect( self._manually_apply_context) self.threads_dock.setWidget(self.contexts_list_panel) self.threads_dock.setObjectName('ThreadPanel') self.addDockWidget(Qt.RightDockWidgetArea, self.threads_dock) self.view_menu.addAction(self.threads_dock.toggleViewAction()) elem_wiget = self.contexts_list_panel elif elem == 'modules': from dwarf_debugger.ui.panels.panel_modules import ModulesPanel self.modules_panel = ModulesPanel(self) self.modules_panel.onModuleSelected.connect( self._on_module_dblclicked) self.modules_panel.onModuleFuncSelected.connect( self._on_modulefunc_dblclicked) self.modules_panel.onAddBreakpoint.connect(self._on_addmodule_breakpoint) self.modules_panel.onDumpBinary.connect(self._on_dump_module) self.main_tabs.addTab(self.modules_panel, 'Modules') elem_wiget = self.modules_panel elif elem == 'ranges': from dwarf_debugger.ui.panels.panel_ranges import RangesPanel self.ranges_panel = RangesPanel(self) self.ranges_panel.onItemDoubleClicked.connect( self._range_dblclicked) self.ranges_panel.onDumpBinary.connect(self._on_dump_module) # connect to watchpointpanel func self.ranges_panel.onAddWatchpoint.connect( self.watchpoints_panel.do_addwatchpoint_dlg) self.main_tabs.addTab(self.ranges_panel, 'Ranges') elem_wiget = self.ranges_panel elif elem == 'search': from dwarf_debugger.ui.panels.panel_search import SearchPanel self.search_panel = SearchPanel(self) self.main_tabs.addTab(self.search_panel, 'Search') elem_wiget = self.search_panel elif elem == 'data': from dwarf_debugger.ui.panels.panel_data import DataPanel self.data_panel = DataPanel(self) self.main_tabs.addTab(self.data_panel, 'Data') elem_wiget = self.data_panel elif elem == 'jvm-tracer': from dwarf_debugger.ui.panels.panel_java_trace import JavaTracePanel self.java_trace_panel = JavaTracePanel(self) self.main_tabs.addTab(self.java_trace_panel, 'JVM tracer') elem_wiget = self.java_trace_panel elif elem == 'smali': from dwarf_debugger.ui.panels.panel_smali import SmaliPanel self.smali_panel = SmaliPanel() self.main_tabs.addTab(self.smali_panel, 'Smali') elem_wiget = self.smali_panel else: print('no handler for elem: ' + elem) if elem_wiget is not None: self.onSystemUIElementCreated.emit(elem, elem_wiget) # TODO: remove add @2x for item in self.findChildren(QDockWidget): if item: if 'darwin' in sys.platform: item.setStyleSheet( 'QDockWidget::title { padding-left:-30px; }' ) def set_status_text(self, txt): self.statusbar.showMessage(txt) # ************************************************************************ # **************************** Properties ******************************** # ************************************************************************ @property def disassembly(self): return self.asm_panel @property def backtrace(self): return self.backtrace_panel @property def console(self): return self.console_panel @property def context(self): return self.context_panel @property def threads(self): return self.contexts_list_panel @property def ftrace(self): return self.ftrace_panel @property def breakpoint(self): return self.breakpoints_panel @property def java_inspector(self): return self.java_inspector_panel @property def objc_inspector(self): return self.objc_inspector_panel @property def java_explorer(self): return self.java_explorer_panel @property def modules(self): return self.modules_panel @property def ranges(self): return self.ranges_panel @property def watchpoints(self): return self.watchpoints_panel @property def dwarf(self): if self.session_manager.session is not None: return self.session_manager.session.dwarf else: return None @property def ui_elements(self): return self._ui_elems # ************************************************************************ # **************************** Handlers ********************************** # ************************************************************************ # session handlers def _start_session(self, session_type, session_data=None): if self.welcome_window is not None: self.welcome_window.close() try: self.session_manager.create_session( session_type, session_data=session_data) except Exception as e: if self.welcome_window: utils.show_message_box(str(e)) def _restore_session(self, session_data): if 'session' in session_data: session_type = session_data['session'] self.dwarf_args.any = session_data['package'] self._start_session(session_type, session_data=session_data) def session_created(self): # session init done create ui for it session = self.session_manager.session self._setup_main_menu() for ui_elem in session.session_ui_sections: ui_elem = ui_elem.join(ui_elem.split()).lower() self._create_ui_elem(ui_elem) self.dwarf.onProcessAttached.connect(self._on_attached) self.dwarf.onProcessDetached.connect(self._on_detached) self.dwarf.onScriptLoaded.connect(self._on_script_loaded) self.dwarf.onSetRanges.connect(self._on_setranges) self.dwarf.onSetModules.connect(self._on_setmodules) self.dwarf.onAddNativeBreakpoint.connect(self._on_add_breakpoint) self.dwarf.onApplyContext.connect(self._apply_context) self.dwarf.onThreadResumed.connect(self.on_tid_resumed) self.dwarf.onHitModuleInitializationBreakpoint.connect(self._on_hit_module_initialization_breakpoint) self.dwarf.onSetData.connect(self._on_set_data) self.session_manager.start_session(self.dwarf_args) ui_state = self.q_settings.value('dwarf_ui_state') if ui_state: self.restoreGeometry(ui_state) window_state = self.q_settings.value('dwarf_ui_window', self.saveState()) if window_state: self.restoreState(window_state) self.showMaximized() def session_stopped(self): self.menu.clear() self.main_tabs.clear() # actually we need to kill this. needs a refactor if self.java_trace_panel is not None: self.java_trace_panel = None for elem in self._ui_elems: if elem == 'watchpoints': self.watchpoints_panel.clear_list() self.watchpoints_panel.close() self.watchpoints_panel = None self.removeDockWidget(self.watchpoints_dwidget) self.watchpoints_dwidget = None elif elem == 'breakpoints': self.breakpoints_panel.close() self.breakpoints_panel = None self.removeDockWidget(self.breakpoint_dwiget) self.breakpoint_dwiget = None elif elem == 'registers': self.context_panel.close() self.context_panel = None self.removeDockWidget(self.registers_dock) self.registers_dock = None elif elem == 'debug': self.debug_panel.close() self.debug_panel = None self.main_tabs.removeTab(0) # self.main_tabs elif elem == 'jvm-debugger': self.java_explorer_panel.close() self.java_explorer_panel = None self.removeDockWidget(self.watchpoints_dwidget) elif elem == 'console': self.console_panel.close() self.console_panel = None self.removeDockWidget(self.console_dock) self.console_dock = None elif elem == 'backtrace': self.backtrace_panel.close() self.backtrace_panel = None self.removeDockWidget(self.backtrace_dock) elif elem == 'threads': self.contexts_list_panel.close() self.contexts_list_panel = None self.removeDockWidget(self.threads_dock) self.threads_dock = None elif elem == 'bookmarks': self.bookmarks_panel.close() self.bookmarks_panel = None self.removeDockWidget(self.bookmarks_dwiget) self.bookmarks_dwiget = None self._initialize_ui_elements() def session_closed(self): self._initialize_ui_elements() self.hide() if self.welcome_window: self.welcome_window.exec() else: if self.dwarf_args.any != '': self.close() # ui handler def closeEvent(self, event): """ Window closed save stuff or whatever at exit detaches dwarf """ if self.session_manager.session: self.session_manager.session.stop() # save windowstuff self.q_settings.setValue('dwarf_ui_state', self.saveGeometry()) self.q_settings.setValue('dwarf_ui_window', self.saveState()) if self.dwarf: try: self.dwarf.detach() except: pass super().closeEvent(event) def _on_watchpoint_clicked(self, ptr): """ Address in Watchpoint/Breakpointpanel was clicked show Memory """ if '.' in ptr: # java_breakpoint file_path = ptr.replace('.', os.path.sep) else: self.jump_to_address(ptr) def _on_watchpoint_added(self, ptr): """ Watchpoint Entry was added """ try: # set highlight self.debug_panel.memory_panel.add_highlight( HighLight('watchpoint', ptr, self.dwarf.pointer_size)) except HighlightExistsError: pass def _on_watchpoint_removeditem(self, ptr): """ Watchpoint Entry was removed remove highlight too """ self.debug_panel.memory_panel.remove_highlight(ptr) def _on_module_dblclicked(self, data): """ Module in ModulePanel was doubleclicked """ addr, size = data addr = utils.parse_ptr(addr) self.jump_to_address(addr) def _on_modulefunc_dblclicked(self, ptr): """ Function in ModulePanel was doubleclicked """ ptr = utils.parse_ptr(ptr) self.jump_to_address(ptr) def _on_dump_module(self, data): """ DumpBinary MenuItem in ModulePanel was selected """ ptr, size = data ptr = utils.parse_ptr(ptr) size = int(size, 10) self.dwarf.dump_memory(ptr=ptr, length=size) def _range_dblclicked(self, ptr): """ Range in RangesPanel was doubleclicked """ ptr = utils.parse_ptr(ptr) self.jump_to_address(ptr) # dwarf handlers def _log_js_output(self, output): if self.console_panel is not None: time_prefix = True if len(output.split('\n')) > 1 or len(output.split('<br />')) > 1: time_prefix = False self.console_panel.get_js_console().log(output, time_prefix=time_prefix) def _log_event(self, output): if self.console_panel is not None: self.console_panel.get_events_console().log(output) def _on_setranges(self, ranges): """ Dwarf wants to set Ranges only breakpointed up to switch tab or create ui its connected in panel after creation """ if self.ranges_panel is None: self.show_main_tab('ranges') # forward only now to panel it connects after creation self.ranges_panel.set_ranges(ranges) else: self.show_main_tab('ranges') def _on_setmodules(self, modules): """ Dwarf wants to set Modules only breakpointed up to switch tab or create ui its connected in panel after creation """ if self.modules_panel is None: self._create_ui_elem('modules') self.modules_panel.set_modules(modules) else: self.show_main_tab('modules') def _manually_apply_context(self, context): """ perform additional operation if the context has been manually applied from the context list """ self._apply_context(context, manual=True) def _on_hit_module_initialization_breakpoint(self, data): if self.debug_panel.memory_panel.number_of_lines() == 0: data = data[1] module_base = int(data['moduleBase'], 16) self.jump_to_address(module_base) def _apply_context(self, context, manual=False): # update current context tid # this should be on top as any further api from js needs to be executed on that thread reason = context['reason'] is_initial_setup = reason == -1 if manual or (self.dwarf.context_tid and not is_initial_setup): self.dwarf.context_tid = context['tid'] if is_initial_setup: self.debug_panel.on_context_setup() if 'context' in context: if not manual: self.threads.add_context(context) is_java = context['is_java'] if is_java: if self.java_explorer_panel is None: self._create_ui_elem('jvm-debugger') self.context_panel.set_context(context['ptr'], 1, context['context']) self.java_explorer_panel.init() self.show_main_tab('jvm-debugger') else: self.context_panel.set_context(context['ptr'], 0, context['context']) if reason == 0: if 'pc' in context['context']: if self.debug_panel.disassembly_panel.number_of_lines() == 0 or manual: self.jump_to_address(context['context']['pc']['value'], view=1) elif reason == 3: # step # we make the frontend believe we are in the real step pc instead of the frida space context['context']['pc'] = context['ptr'] if 'rip' in context['context']: context['context']['rip'] = context['ptr'] self.jump_to_address(context['ptr'], view=1) if 'backtrace' in context: self.backtrace_panel.set_backtrace(context['backtrace']) def _on_add_breakpoint(self, breakpoint): try: # set highlight ptr = breakpoint.get_target() ptr = utils.parse_ptr(ptr) self.debug_panel.memory_panel.add_highlight( HighLight('breakpoint', ptr, self.dwarf.pointer_size)) except HighlightExistsError: pass def _on_breakpoint_removed(self, ptr): ptr = utils.parse_ptr(ptr) self.debug_panel.memory_panel.remove_highlight(ptr) def _on_addmodule_breakpoint(self, data): ptr, name = data self.dwarf.breakpoint_native(input_=ptr) def on_tid_resumed(self, tid): if self.dwarf: if self.dwarf.context_tid == tid: # clear backtrace if 'backtrace' in self._ui_elems: if self.backtrace_panel is not None: self.backtrace_panel.clear() # remove thread if 'threads' in self._ui_elems: if self.contexts_list_panel is not None: self.contexts_list_panel.resume_tid(tid) # clear registers if 'registers' in self._ui_elems: if self.context_panel is not None: self.context_panel.clear() # clear jvm explorer if 'jvm-debugger' in self._ui_elems: if self.java_explorer_panel is not None: self.java_explorer_panel.clear_panel() # invalidate dwarf context tid self.dwarf.context_tid = 0 def _on_set_data(self, data): if not isinstance(data, list): return if self.data_panel is None: self.show_main_tab('data') if self.data_panel is not None: self.data_panel.append_data(data[0], data[1], data[2]) def show_progress(self, text): self.progressbar.setVisible(True) self.set_status_text(text) def hide_progress(self): self.progressbar.setVisible(False) self.set_status_text('') def _on_attached(self, data): self.setWindowTitle('Dwarf - Attached to %s (%s)' % (data[1], data[0])) def _on_detached(self, data): reason = data[1] if reason == 'application-requested': if self.session_manager.session: self.session_manager.session.stop() return 0 if self.dwarf is not None: ret = QDialogDetached.show_dialog(self.dwarf, data[0], data[1], data[2]) if ret == 0: self.dwarf.restart_proc() elif ret == 1: self.session_manager.session.stop() return 0 def _on_script_loaded(self): # restore the loaded session if any self.session_manager.restore_session() def on_add_bookmark(self, ptr): """ provide ptr as int """ if self.bookmarks_panel is not None: self.bookmarks_panel._create_bookmark(ptr=hex(ptr)) def _on_showmemory_request(self, ptr): # its simple ptr show in memorypanel if isinstance(ptr, str): ptr = utils.parse_ptr(ptr) self.jump_to_address(ptr, 0) elif isinstance(ptr, list): # TODO: extend caller, ptr = ptr ptr = utils.parse_ptr(ptr) if caller == 'backtrace' or caller == 'bt': # jumpto in disasm self.jump_to_address(ptr, 1)
def createUi(self): self.content = Expander("Content", ":/images/parts.svg") self.images = Expander("Images", ":/images/images.svg") self.settings = Expander("Settings", ":/images/settings.svg") self.setWindowTitle(QCoreApplication.applicationName() + " " + QCoreApplication.applicationVersion()) vbox = QVBoxLayout() vbox.addWidget(self.content) vbox.addWidget(self.images) vbox.addWidget(self.settings) vbox.addStretch() self.content_list = QListWidget() self.content_list.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) content_box = QVBoxLayout() content_box.addWidget(self.content_list) self.item_edit = QLineEdit() self.item_edit.setMaximumHeight(0) self.item_edit.editingFinished.connect(self.editItemFinished) self.item_anim = QPropertyAnimation(self.item_edit, "maximumHeight".encode("utf-8")) content_box.addWidget(self.item_edit) button_layout = QHBoxLayout() plus_button = FlatButton(":/images/plus.svg") self.edit_button = FlatButton(":/images/edit.svg") self.trash_button = FlatButton(":/images/trash.svg") self.up_button = FlatButton(":/images/up.svg") self.down_button = FlatButton(":/images/down.svg") self.trash_button.enabled = False self.up_button.enabled = False self.down_button.enabled = False button_layout.addWidget(plus_button) button_layout.addWidget(self.up_button) button_layout.addWidget(self.down_button) button_layout.addWidget(self.edit_button) button_layout.addWidget(self.trash_button) content_box.addLayout(button_layout) self.content.addLayout(content_box) plus_button.clicked.connect(self.addPart) self.trash_button.clicked.connect(self.dropPart) self.up_button.clicked.connect(self.partUp) self.down_button.clicked.connect(self.partDown) self.edit_button.clicked.connect(self.editPart) self.image_list = QListWidget() self.image_list.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) image_box = QVBoxLayout() image_box.addWidget(self.image_list) image_button_layout = QHBoxLayout() image_plus_button = FlatButton(":/images/plus.svg") self.image_trash_button = FlatButton(":/images/trash.svg") self.image_trash_button.enabled = False image_button_layout.addWidget(image_plus_button) image_button_layout.addWidget(self.image_trash_button) image_box.addLayout(image_button_layout) self.images.addLayout(image_box) image_plus_button.clicked.connect(self.addImage) self.image_trash_button.clicked.connect(self.dropImage) scroll_content = QWidget() scroll_content.setLayout(vbox) scroll = QScrollArea() scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll.setWidget(scroll_content) scroll.setWidgetResizable(True) scroll.setMaximumWidth(200) scroll.setMinimumWidth(200) self.navigationdock = QDockWidget("Navigation", self) self.navigationdock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.navigationdock.setWidget(scroll) self.navigationdock.setObjectName("Navigation") self.addDockWidget(Qt.LeftDockWidgetArea, self.navigationdock) self.splitter = QSplitter() self.text_edit = MarkdownEdit() self.text_edit.setFont(QFont("Courier", 11)) self.preview = QWebEngineView() self.preview.setMinimumWidth(300) self.setWindowTitle(QCoreApplication.applicationName()) self.splitter.addWidget(self.text_edit) self.splitter.addWidget(self.preview) self.setCentralWidget(self.splitter) self.content.expanded.connect(self.contentExpanded) self.images.expanded.connect(self.imagesExpanded) self.settings.expanded.connect(self.settingsExpanded) self.settings.clicked.connect(self.openSettings) self.content_list.currentItemChanged.connect(self.partSelectionChanged) self.image_list.currentItemChanged.connect(self.imageSelectionChanged) self.image_list.itemDoubleClicked.connect(self.insertImage) self.text_edit.undoAvailable.connect(self.undoAvailable) self.text_edit.redoAvailable.connect(self.redoAvailable) self.text_edit.copyAvailable.connect(self.copyAvailable) QApplication.clipboard().dataChanged.connect(self.clipboardDataChanged)
def eventFilter(self, source, event): ''' Event Filter ''' if (event.type() == QEvent.MouseButtonPress and source is self.VManager.viewport() and self.VManager.itemAt(event.pos()) is None): self.VManager.clearSelection() return QDockWidget.eventFilter(self, source, event)
def enterEvent(self, event): self.entered = True if not self.shot and not self.isPinned() and not self.isFloating(): self.shot = True QTimer.singleShot(500, self.autoshow) return QDockWidget.enterEvent(self, event)
def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.propertyToId = QMap() self.idToProperty = QMap() self.idToExpanded = QMap() editMenu = self.menuBar().addMenu(self.tr("Edit")) newObjectMenu = editMenu.addMenu(self.tr("New Object")) newRectangleAction = QAction(self.tr("Rectangle"), self) newRectangleAction.triggered.connect(self.newRectangle) newObjectMenu.addAction(newRectangleAction) newLineAction = QAction(self.tr("Line"), self) newLineAction.triggered.connect(self.newLine) newObjectMenu.addAction(newLineAction) newEllipseAction = QAction(self.tr("Ellipse"), self) newEllipseAction.triggered.connect(self.newEllipse) newObjectMenu.addAction(newEllipseAction) newTextAction = QAction(self.tr("Text"), self) newTextAction.triggered.connect(self.newText) newObjectMenu.addAction(newTextAction) self.deleteAction = QAction(self.tr("Delete Object"), self) self.deleteAction.triggered.connect(self.deleteObject) editMenu.addAction(self.deleteAction) clearAction = QAction(self.tr("Clear All"), self) clearAction.triggered.connect(self.clearAll) editMenu.addAction(clearAction) fillAction = QAction(self.tr("Fill View"), self) fillAction.triggered.connect(self.fillView) editMenu.addAction(fillAction) self.doubleManager = QtDoublePropertyManager(self) self.stringManager = QtStringPropertyManager(self) self.colorManager = QtColorPropertyManager(self) self.fontManager = QtFontPropertyManager(self) self.pointManager = QtPointPropertyManager(self) self.sizeManager = QtSizePropertyManager(self) self.doubleManager.valueChangedSignal.connect(self.valueChanged) self.stringManager.valueChangedSignal.connect(self.valueChanged) self.colorManager.valueChangedSignal.connect(self.valueChanged) self.fontManager.valueChangedSignal.connect(self.valueChanged) self.pointManager.valueChangedSignal.connect(self.valueChanged) self.sizeManager.valueChangedSignal.connect(self.valueChanged) doubleSpinBoxFactory = QtDoubleSpinBoxFactory(self) checkBoxFactory = QtCheckBoxFactory(self) spinBoxFactory = QtSpinBoxFactory(self) lineEditFactory = QtLineEditFactory(self) comboBoxFactory = QtEnumEditorFactory(self) self.canvas = QtCanvas(800, 600) self.canvasView = CanvasView(self.canvas, self) self.setCentralWidget(self.canvasView) dock = QDockWidget(self) self.addDockWidget(Qt.RightDockWidgetArea, dock) self.propertyEditor = QtTreePropertyBrowser(dock) self.propertyEditor.setFactoryForManager(self.doubleManager, doubleSpinBoxFactory) self.propertyEditor.setFactoryForManager(self.stringManager, lineEditFactory) self.propertyEditor.setFactoryForManager( self.colorManager.subIntPropertyManager(), spinBoxFactory) self.propertyEditor.setFactoryForManager( self.fontManager.subIntPropertyManager(), spinBoxFactory) self.propertyEditor.setFactoryForManager( self.fontManager.subBoolPropertyManager(), checkBoxFactory) self.propertyEditor.setFactoryForManager( self.fontManager.subEnumPropertyManager(), comboBoxFactory) self.propertyEditor.setFactoryForManager( self.pointManager.subIntPropertyManager(), spinBoxFactory) self.propertyEditor.setFactoryForManager( self.sizeManager.subIntPropertyManager(), spinBoxFactory) dock.setWidget(self.propertyEditor) self.currentItem = QtCanvasItem(None) self.canvasView.itemClickedSignal.connect(self.itemClicked) self.canvasView.itemMovedSignal.connect(self.itemMoved) self.fillView() self.itemClicked(QtCanvasItem(None))
class USBLModule(QObject): signal_create_dock = pyqtSignal(int, QDockWidget) signal_enable_boat_pose_action = pyqtSignal() def __init__(self, canvas, config, vehicle_info, mission_sts, action_boat_pose, menubar, view_menu_toolbar): super(QObject, self).__init__() self.usblwidget = None self.boat_pose_action = action_boat_pose self.canvas = canvas self.config = config self.vehicle_info = vehicle_info self.mission_sts = mission_sts # Actions for Vehicle (usbl) self.usbl_pose_action = QAction( QIcon(":/resources/" + vehicle_info.get_vehicle_type() + "/mActionAUVPoseUSBL.svg"), "Monitor AUV Pose", self) self.abort_and_surface_action = QAction( QIcon(":/resources/" + vehicle_info.get_vehicle_type() + "/mActionAbortAndSurfaceAcoustic.svg"), "Send Abort and Surface", self) self.emergency_surface_action = QAction( QIcon(":/resources/" + vehicle_info.get_vehicle_type() + "/mActionEmergencySurfaceAcoustic.svg"), "Send Emergency Surface", self) self.usbl_start_mission_action = QAction( QIcon(":/resources/mActionExecuteMissionAcoustic.svg"), "Start Mission", self) self.usbl_abort_mission_action = QAction( QIcon(":/resources/mActionStopMissionAcoustic.svg"), "Stop Mission", self) self.usbl_enable_update_action = QAction( QIcon(":/resources/mActionUSBLUpdatesOff.svg"), "Enable Updates", self) self.usbl_pose_action.setCheckable(True) self.usbl_start_mission_action.setCheckable(True) self.usbl_abort_mission_action.setCheckable(True) self.abort_and_surface_action.setCheckable(True) self.emergency_surface_action.setCheckable(True) self.usbl_enable_update_action.setCheckable(True) self.usbl_menu = menubar.addMenu("USBL") self.usbl_menu.addActions([ self.usbl_pose_action, self.usbl_start_mission_action, self.usbl_abort_mission_action, self.usbl_enable_update_action, self.abort_and_surface_action, self.emergency_surface_action ]) # Toolbar for Vehicle (usbl) self.usbl_toolbar = QToolBar("USBL Tools") self.usbl_toolbar.setObjectName("USBL Tools") self.usbl_toolbar.addAction(self.usbl_pose_action) # self.usbl_toolbar.addAction(self.usbl_start_mission_action) # self.usbl_toolbar.addAction(self.usbl_abort_mission_action) self.usbl_toolbar.addAction(self.usbl_enable_update_action) self.usbl_toolbar.addAction(self.abort_and_surface_action) self.usbl_toolbar.addAction(self.emergency_surface_action) self.usbl_toolbar_action = QAction("USBL", self) self.usbl_toolbar_action.setCheckable(True) self.usbl_toolbar_action.setChecked(True) self.usbl_toolbar_action.triggered.connect( lambda: self.change_toolbar_visibility(self.usbl_toolbar)) self.usbl_pose_action.triggered.connect(self.show_usbl_pose) self.usbl_start_mission_action.toggled.connect(self.send_start_mission) self.usbl_abort_mission_action.toggled.connect(self.send_abort_mission) self.usbl_enable_update_action.toggled.connect(self.enable_update) self.abort_and_surface_action.toggled.connect( self.send_abort_and_surface) self.emergency_surface_action.toggled.connect( self.send_emergency_surface) self.usbl_is_connected(False) view_menu_toolbar.addAction(self.usbl_toolbar_action) def get_usbl_toolbar(self): return self.usbl_toolbar def get_usbl_widget(self): return self.usblwidget def show_usbl_pose(self): if self.usbl_pose_action.isChecked(): # Disconnect gps from boat, will be visible from USBL self.boat_pose_action.setChecked(False) self.boat_pose_action.setEnabled(False) self.create_usbl_dock_widget() self.usblwidget = usblwidget.USBLWidget(self.canvas, self.config, self.vehicle_info, self.mission_sts) self.usblwidget.setAttribute(Qt.WA_DeleteOnClose) self.usblwd.setWidget(self.usblwidget) self.usblwd.show() self.usblwidget.usbl_connected.connect(self.usbl_is_connected) self.usblwidget.connect() self.usbl_pose_action.setToolTip("Hide AUV USBL Monitoring") else: self.usblwd.close() self.disconnect_usbl_dw() self.usbl_pose_action.setToolTip("Show AUV USBL Monitoring") def send_start_mission(self): if self.usbl_start_mission_action.isChecked(): self.usblwidget.send_start_mission() # self.usblwidget.mission_stopped.connect(self.mission_stopped_acoustically) self.usbl_start_mission_action.setToolTip("Stop Mission") else: self.usblwidget.send_informative() self.usbl_start_mission_action.setToolTip("Start Mission") # self.usblwidget.mission_stopped.disconnect() def send_abort_mission(self): if self.usbl_abort_mission_action.isChecked(): self.usblwidget.send_stop_mission() else: self.usblwidget.send_informative() def enable_update(self): if self.usbl_enable_update_action.isChecked(): icon = QIcon(":resources/mActionUSBLUpdatesOn.svg") self.usbl_enable_update_action.setToolTip("Disable Updates") self.usblwidget.set_enable_update(True) else: icon = QIcon(":resources/mActionUSBLUpdatesOff.svg") self.usbl_enable_update_action.setToolTip("Enable Updates") self.usblwidget.set_enable_update(False) self.usbl_enable_update_action.setIcon(icon) def send_abort_and_surface(self): if self.abort_and_surface_action.isChecked(): confirmation_msg = "Are you sure you want to abort the mission?" reply = QMessageBox.question(self.parent(), 'Abort Confirmation', confirmation_msg, QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: self.usblwidget.send_abort_and_surface() else: self.usblwidget.send_informative() def send_emergency_surface(self): if self.emergency_surface_action.isChecked(): confirmation_msg = "Are you sure you want to abort the mission and send an emergency surface? " \ "An emergency surface will require you to restart the architecture afterwards. " reply = QMessageBox.question(self.parent(), 'Emergency Surface Confirmation', confirmation_msg, QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: self.usblwidget.send_emergency_surface() else: self.usblwidget.send_informative() def mission_started_acoustically(self): icon = QIcon(":resources/mActionStopMissionAcoustic.svg") self.usbl_start_mission_action.setIcon(icon) # self.actionUSBLStartMission.setChecked(True) def mission_stopped_acoustically(self): icon = QIcon(":resources/mActionExecuteMissionAcoustic.svg") self.usbl_start_mission_action.setIcon(icon) self.usbl_start_mission_action.setChecked(False) def usbl_is_connected(self, connected): self.usbl_start_mission_action.setEnabled(connected) self.usbl_abort_mission_action.setEnabled(connected) self.abort_and_surface_action.setEnabled(connected) self.emergency_surface_action.setEnabled(connected) self.usbl_enable_update_action.setEnabled(connected) if not connected: self.usbl_start_mission_action.setChecked(False) self.usbl_abort_mission_action.setChecked(False) self.abort_and_surface_action.setChecked(False) self.emergency_surface_action.setChecked(False) self.usbl_enable_update_action.setChecked(False) def create_usbl_dock_widget(self): # Dock for USBL self.usblwd = QDockWidget() self.usblwd.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.usblwd.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.signal_create_dock.emit(Qt.LeftDockWidgetArea, self.usblwd) self.usblwd.setWindowTitle("AUV monitoring (USBL)") self.usblwd.setStyleSheet("QDockWidget { font: bold; }") self.usblwd.setAttribute(Qt.WA_DeleteOnClose) self.usblwd.destroyed.connect(self.disconnect_usbl_dw) def change_toolbar_visibility(self, toolbar): """ Change toolbar visibility :param toolbar: toolbar """ sender = self.sender() if sender.isChecked(): toolbar.setVisible(True) else: toolbar.setVisible(False) def disconnect_usbl_dw(self): if self.usblwidget is not None: if self.usblwidget.is_connected(): self.usblwidget.disconnect() self.usblwidget.deleteLater() self.usblwidget = None self.usbl_pose_action.setChecked(False) self.signal_enable_boat_pose_action.emit()
class Window(QMainWindow): """ Defines the look and characteristics of the application's main window. """ title = _("Mu {}").format(__version__) icon = "icon" timer = None usb_checker = None repl = None plotter = None zooms = ("xs", "s", "m", "l", "xl", "xxl", "xxxl") # levels of zoom. zoom_position = 2 # current level of zoom (as position in zooms tuple). _zoom_in = pyqtSignal(str) _zoom_out = pyqtSignal(str) data_received = pyqtSignal(bytes) open_file = pyqtSignal(str) load_theme = pyqtSignal(str) previous_folder = None debug_widths = None def __init__(self, parent=None): super().__init__(parent) # Record pane area to allow reopening where user put it in a session self._debugger_area = 0 self._inspector_area = 0 self._plotter_area = 0 self._repl_area = 0 self._runner_area = 0 def wheelEvent(self, event): """ Trap a CTRL-scroll event so the user is able to zoom in and out. """ modifiers = QApplication.keyboardModifiers() if modifiers == Qt.ControlModifier: zoom = event.angleDelta().y() > 0 if zoom: self.zoom_in() else: self.zoom_out() event.ignore() def set_zoom(self): """ Sets the zoom to current zoom_position level. """ self._zoom_in.emit(self.zooms[self.zoom_position]) def zoom_in(self): """ Handles zooming in. """ self.zoom_position = min(self.zoom_position + 1, len(self.zooms) - 1) self._zoom_in.emit(self.zooms[self.zoom_position]) def zoom_out(self): """ Handles zooming out. """ self.zoom_position = max(self.zoom_position - 1, 0) self._zoom_out.emit(self.zooms[self.zoom_position]) def connect_zoom(self, widget): """ Connects a referenced widget to the zoom related signals and sets the zoom of the widget to the current zoom level. """ self._zoom_in.connect(widget.set_zoom) self._zoom_out.connect(widget.set_zoom) widget.set_zoom(self.zooms[self.zoom_position]) @property def current_tab(self): """ Returns the currently focussed tab. """ return self.tabs.currentWidget() def set_read_only(self, is_readonly): """ Set all tabs read-only. """ self.read_only_tabs = is_readonly for tab in self.widgets: tab.setReadOnly(is_readonly) def get_load_path(self, folder, extensions="*", allow_previous=True): """ Displays a dialog for selecting a file to load. Returns the selected path. Defaults to start in the referenced folder unless a previous folder has been used and the allow_previous flag is True (the default behaviour) """ if allow_previous: open_in = ( folder if self.previous_folder is None else self.previous_folder ) else: open_in = folder path, _ = QFileDialog.getOpenFileName( self.widget, "Open file", open_in, extensions ) logger.debug("Getting load path: {}".format(path)) if allow_previous: self.previous_folder = os.path.dirname(path) return path def get_save_path(self, folder): """ Displays a dialog for selecting a file to save. Returns the selected path. Defaults to start in the referenced folder. """ path, _ = QFileDialog.getSaveFileName( self.widget, "Save file", folder if self.previous_folder is None else self.previous_folder, "Python (*.py);;Other (*.*)", "Python (*.py)", ) self.previous_folder = os.path.dirname(path) logger.debug("Getting save path: {}".format(path)) return path def get_microbit_path(self, folder): """ Displays a dialog for locating the location of the BBC micro:bit in the host computer's filesystem. Returns the selected path. Defaults to start in the referenced folder. """ path = QFileDialog.getExistingDirectory( self.widget, "Locate BBC micro:bit", folder if self.previous_folder is None else self.previous_folder, QFileDialog.ShowDirsOnly, ) self.previous_folder = os.path.dirname(path) logger.debug("Getting micro:bit path: {}".format(path)) return path def add_tab(self, path, text, api, newline): """ Adds a tab with the referenced path and text to the editor. """ new_tab = EditorPane(path, text, newline) new_tab.connect_margin(self.breakpoint_toggle) new_tab_index = self.tabs.addTab(new_tab, new_tab.label) new_tab.set_api(api) @new_tab.modificationChanged.connect def on_modified(): modified_tab_index = self.tabs.indexOf(new_tab) # Update tab label & window title # Tab dirty indicator is managed in FileTabs.addTab self.tabs.setTabText(modified_tab_index, new_tab.label) self.update_title(new_tab.title) @new_tab.open_file.connect def on_open_file(file): # Bubble the signal up self.open_file.emit(file) self.tabs.setCurrentIndex(new_tab_index) self.connect_zoom(new_tab) self.set_theme(self.theme) new_tab.setFocus() if self.read_only_tabs: new_tab.setReadOnly(self.read_only_tabs) return new_tab def focus_tab(self, tab): """ Force focus on the referenced tab. """ index = self.tabs.indexOf(tab) self.tabs.setCurrentIndex(index) tab.setFocus() @property def tab_count(self): """ Returns the number of active tabs. """ return self.tabs.count() @property def widgets(self): """ Returns a list of references to the widgets representing tabs in the editor. """ return [self.tabs.widget(i) for i in range(self.tab_count)] @property def modified(self): """ Returns a boolean indication if there are any modified tabs in the editor. """ for widget in self.widgets: if widget.isModified(): return True return False def on_stdout_write(self, data): """ Called when either a running script or the REPL write to STDOUT. """ self.data_received.emit(data) def add_filesystem(self, home, file_manager, board_name="board"): """ Adds the file system pane to the application. """ self.fs_pane = FileSystemPane(home) @self.fs_pane.open_file.connect def on_open_file(file): # Bubble the signal up self.open_file.emit(file) self.fs = QDockWidget(_("Filesystem on ") + board_name) self.fs.setWidget(self.fs_pane) self.fs.setFeatures(QDockWidget.DockWidgetMovable) self.fs.setAllowedAreas(Qt.BottomDockWidgetArea) self.addDockWidget(Qt.BottomDockWidgetArea, self.fs) self.fs_pane.setFocus() file_manager.on_list_files.connect(self.fs_pane.on_ls) self.fs_pane.list_files.connect(file_manager.ls) self.fs_pane.microbit_fs.put.connect(file_manager.put) self.fs_pane.microbit_fs.delete.connect(file_manager.delete) self.fs_pane.microbit_fs.list_files.connect(file_manager.ls) self.fs_pane.local_fs.get.connect(file_manager.get) self.fs_pane.local_fs.put.connect(file_manager.put) self.fs_pane.local_fs.list_files.connect(file_manager.ls) file_manager.on_put_file.connect(self.fs_pane.microbit_fs.on_put) file_manager.on_delete_file.connect(self.fs_pane.microbit_fs.on_delete) file_manager.on_get_file.connect(self.fs_pane.local_fs.on_get) file_manager.on_list_fail.connect(self.fs_pane.on_ls_fail) file_manager.on_put_fail.connect(self.fs_pane.on_put_fail) file_manager.on_delete_fail.connect(self.fs_pane.on_delete_fail) file_manager.on_get_fail.connect(self.fs_pane.on_get_fail) self.connect_zoom(self.fs_pane) return self.fs_pane def add_micropython_repl(self, name, connection): """ Adds a MicroPython based REPL pane to the application. """ repl_pane = MicroPythonREPLPane(connection) connection.data_received.connect(repl_pane.process_tty_data) self.add_repl(repl_pane, name) def add_micropython_plotter(self, name, connection, data_flood_handler): """ Adds a plotter that reads data from a serial connection. """ plotter_pane = PlotterPane() connection.data_received.connect(plotter_pane.process_tty_data) plotter_pane.data_flood.connect(data_flood_handler) self.add_plotter(plotter_pane, name) def add_python3_plotter(self, mode): """ Add a plotter that reads from either the REPL or a running script. Since this function will only be called when either the REPL or a running script are running (but not at the same time), it'll just grab data emitted by the REPL or script via data_received. """ plotter_pane = PlotterPane() self.data_received.connect(plotter_pane.process_tty_data) plotter_pane.data_flood.connect(mode.on_data_flood) self.add_plotter(plotter_pane, _("Python3 data tuple")) def add_jupyter_repl(self, kernel_manager, kernel_client): """ Adds a Jupyter based REPL pane to the application. """ kernel_manager.kernel.gui = "qt4" kernel_client.start_channels() ipython_widget = JupyterREPLPane() ipython_widget.kernel_manager = kernel_manager ipython_widget.kernel_client = kernel_client ipython_widget.on_append_text.connect(self.on_stdout_write) self.add_repl(ipython_widget, _("Python3 (Jupyter)")) def add_repl(self, repl_pane, name): """ Adds the referenced REPL pane to the application. """ self.repl_pane = repl_pane self.repl = QDockWidget(_("{} REPL").format(name)) self.repl.setWidget(repl_pane) self.repl.setFeatures(QDockWidget.DockWidgetMovable) self.repl.setAllowedAreas( Qt.BottomDockWidgetArea | Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea ) area = self._repl_area or Qt.BottomDockWidgetArea self.addDockWidget(area, self.repl) self.connect_zoom(self.repl_pane) self.repl_pane.set_theme(self.theme) self.repl_pane.setFocus() def add_plotter(self, plotter_pane, name): """ Adds the referenced plotter pane to the application. """ self.plotter_pane = plotter_pane self.plotter = QDockWidget(_("{} Plotter").format(name)) self.plotter.setWidget(plotter_pane) self.plotter.setFeatures(QDockWidget.DockWidgetMovable) self.plotter.setAllowedAreas( Qt.BottomDockWidgetArea | Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea ) area = self._plotter_area or Qt.BottomDockWidgetArea self.addDockWidget(area, self.plotter) self.plotter_pane.set_theme(self.theme) self.plotter_pane.setFocus() def add_python3_runner( self, interpreter, script_name, working_directory, interactive=False, debugger=False, command_args=None, envars=None, python_args=None, ): """ Display console output for the interpreter with the referenced pythonpath running the referenced script. The script will be run within the workspace_path directory. If interactive is True (default is False) the Python process will run in interactive mode (dropping the user into the REPL when the script completes). If debugger is True (default is False) the script will be run within a debug runner session. The debugger overrides the interactive flag (you cannot run the debugger in interactive mode). If there is a list of command_args (the default is None) then these will be passed as further arguments into the command run in the new process. If envars is given, these will become part of the environment context of the new chlid process. If python_args is given, these will be passed as arguments to the Python runtime used to launch the child process. """ self.process_runner = PythonProcessPane(self) self.runner = QDockWidget( _("Running: {}").format(os.path.basename(script_name)) ) self.runner.setWidget(self.process_runner) self.runner.setFeatures(QDockWidget.DockWidgetMovable) self.runner.setAllowedAreas( Qt.BottomDockWidgetArea | Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea ) self.process_runner.debugger = debugger if debugger: area = self._debugger_area or Qt.BottomDockWidgetArea else: area = self._runner_area or Qt.BottomDockWidgetArea self.addDockWidget(area, self.runner) logger.info( "About to start_process: %r, %r, %r, %r, %r, %r, %r, %r", interpreter, script_name, working_directory, interactive, debugger, command_args, envars, python_args, ) self.process_runner.start_process( interpreter, script_name, working_directory, interactive, debugger, command_args, envars, python_args, ) self.process_runner.setFocus() self.process_runner.on_append_text.connect(self.on_stdout_write) self.connect_zoom(self.process_runner) return self.process_runner def add_debug_inspector(self): """ Display a debug inspector to view the call stack. """ self.debug_inspector = DebugInspector() self.debug_model = QStandardItemModel() self.debug_inspector.setModel(self.debug_model) self.inspector = QDockWidget(_("Debug Inspector")) self.inspector.setWidget(self.debug_inspector) self.inspector.setFeatures(QDockWidget.DockWidgetMovable) self.inspector.setAllowedAreas( Qt.BottomDockWidgetArea | Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea ) area = self._inspector_area or Qt.RightDockWidgetArea self.addDockWidget(area, self.inspector) self.connect_zoom(self.debug_inspector) # Setup the inspector headers and restore column widths self.debug_model.setHorizontalHeaderLabels([_("Name"), _("Value")]) if self.debug_widths: for col, width in enumerate(self.debug_widths): self.debug_inspector.setColumnWidth(col, width) def update_debug_inspector(self, locals_dict): """ Given the contents of a dict representation of the locals in the current stack frame, update the debug inspector with the new values. """ excluded_names = ["__builtins__", "__debug_code__", "__debug_script__"] names = sorted([x for x in locals_dict if x not in excluded_names]) # Remove rows so we keep the same column layouts if manually set while self.debug_model.rowCount() > 0: self.debug_model.removeRow(0) for name in names: item_to_expand = None try: # DANGER! val = eval(locals_dict[name]) except Exception: val = None if isinstance(val, list): # Show a list consisting of rows of position/value list_item = DebugInspectorItem(name) item_to_expand = list_item for i, i_val in enumerate(val): list_item.appendRow( [ DebugInspectorItem(str(i)), DebugInspectorItem(repr(i_val)), ] ) self.debug_model.appendRow( [ list_item, DebugInspectorItem( _("(A list of {} items.)").format(len(val)) ), ] ) elif isinstance(val, dict): # Show a dict consisting of rows of key/value pairs. dict_item = DebugInspectorItem(name) item_to_expand = dict_item for k, k_val in val.items(): dict_item.appendRow( [ DebugInspectorItem(repr(k)), DebugInspectorItem(repr(k_val)), ] ) self.debug_model.appendRow( [ dict_item, DebugInspectorItem( _("(A dict of {} items.)").format(len(val)) ), ] ) else: self.debug_model.appendRow( [ DebugInspectorItem(name), DebugInspectorItem(locals_dict[name]), ] ) # Expand dicts/list with names matching old expanded entries if ( hasattr(self, "debug_inspector") and name in self.debug_inspector.expanded_dicts and item_to_expand is not None ): self.debug_inspector.expand( self.debug_model.indexFromItem(item_to_expand) ) def remove_filesystem(self): """ Removes the file system pane from the application. """ if hasattr(self, "fs") and self.fs: self.fs_pane = None self.fs.setParent(None) self.fs.deleteLater() self.fs = None def remove_repl(self): """ Removes the REPL pane from the application. """ if self.repl: self._repl_area = self.dockWidgetArea(self.repl) self.repl_pane = None self.repl.setParent(None) self.repl.deleteLater() self.repl = None def remove_plotter(self): """ Removes the plotter pane from the application. """ if self.plotter: self._plotter_area = self.dockWidgetArea(self.plotter) self.plotter_pane = None self.plotter.setParent(None) self.plotter.deleteLater() self.plotter = None def remove_python_runner(self): """ Removes the runner pane from the application. """ if hasattr(self, "runner") and self.runner: if self.process_runner.debugger: self._debugger_area = self.dockWidgetArea(self.runner) else: self._runner_area = self.dockWidgetArea(self.runner) self.process_runner = None self.runner.setParent(None) self.runner.deleteLater() self.runner = None def remove_debug_inspector(self): """ Removes the debug inspector pane from the application. """ if hasattr(self, "inspector") and self.inspector: width = self.debug_inspector.columnWidth self.debug_widths = width(0), width(1) self._inspector_area = self.dockWidgetArea(self.inspector) self.debug_inspector = None self.debug_model = None self.inspector.setParent(None) self.inspector.deleteLater() self.inspector = None def set_theme(self, theme): """ Sets the theme for the REPL and editor tabs. """ self.theme = theme self.load_theme.emit(theme) if theme == "contrast": new_theme = ContrastTheme new_icon = "theme_day" elif theme == "night": new_theme = NightTheme new_icon = "theme_contrast" else: new_theme = DayTheme new_icon = "theme" for widget in self.widgets: widget.set_theme(new_theme) self.button_bar.slots["theme"].setIcon(load_icon(new_icon)) if hasattr(self, "repl") and self.repl: self.repl_pane.set_theme(theme) if hasattr(self, "plotter") and self.plotter: self.plotter_pane.set_theme(theme) def set_checker_icon(self, icon): """ Set the status icon to use on the check button """ self.button_bar.slots["check"].setIcon(load_icon(icon)) timer = QTimer() @timer.timeout.connect def reset(): self.button_bar.slots["check"].setIcon(load_icon("check.png")) timer.stop() timer.start(500) def show_admin(self, log, settings, packages, mode, device_list): """ Display the administrative dialog with referenced content of the log and settings. Return a dictionary of the settings that may have been changed by the admin dialog. """ admin_box = AdminDialog(self) admin_box.setup(log, settings, packages, mode, device_list) result = admin_box.exec() if result: return admin_box.settings() else: return {} def sync_packages(self, to_remove, to_add): """ Display a modal dialog that indicates the status of the add/remove package management operation. """ package_box = PackageDialog(self) package_box.setup(to_remove, to_add) package_box.exec() def show_message(self, message, information=None, icon=None): """ Displays a modal message to the user. If information is passed in this will be set as the additional informative text in the modal dialog. Since this mechanism will be used mainly for warning users that something is awry the default icon is set to "Warning". It's possible to override the icon to one of the following settings: NoIcon, Question, Information, Warning or Critical. """ message_box = QMessageBox(self) message_box.setText(message) message_box.setWindowTitle("Mu") if information: message_box.setInformativeText(information) if icon and hasattr(message_box, icon): message_box.setIcon(getattr(message_box, icon)) else: message_box.setIcon(message_box.Warning) logger.debug(message) logger.debug(information) message_box.exec() def show_confirmation(self, message, information=None, icon=None): """ Displays a modal message to the user to which they need to confirm or cancel. If information is passed in this will be set as the additional informative text in the modal dialog. Since this mechanism will be used mainly for warning users that something is awry the default icon is set to "Warning". It's possible to override the icon to one of the following settings: NoIcon, Question, Information, Warning or Critical. """ message_box = QMessageBox(self) message_box.setText(message) message_box.setWindowTitle(_("Mu")) if information: message_box.setInformativeText(information) if icon and hasattr(message_box, icon): message_box.setIcon(getattr(message_box, icon)) else: message_box.setIcon(message_box.Warning) message_box.setStandardButtons(message_box.Cancel | message_box.Ok) message_box.setDefaultButton(message_box.Cancel) logger.debug(message) logger.debug(information) return message_box.exec() def update_title(self, filename=None): """ Updates the title bar of the application. If a filename (representing the name of the file currently the focus of the editor) is supplied, append it to the end of the title. """ title = self.title if filename: title += " - " + filename self.setWindowTitle(title) def screen_size(self): """ Returns an (width, height) tuple with the screen geometry. """ screen = QDesktopWidget().screenGeometry() return screen.width(), screen.height() def size_window(self, x=None, y=None, w=None, h=None): """ Makes the editor 80% of the width*height of the screen and centres it when none of x, y, w and h is passed in; otherwise uses the passed in values to position and size the editor window. """ screen_width, screen_height = self.screen_size() w = int(screen_width * 0.8) if w is None else w h = int(screen_height * 0.8) if h is None else h self.resize(w, h) size = self.geometry() x = (screen_width - size.width()) / 2 if x is None else x y = (screen_height - size.height()) / 2 if y is None else y self.move(x, y) def reset_annotations(self): """ Resets the state of annotations on the current tab. """ self.current_tab.reset_annotations() def annotate_code(self, feedback, annotation_type): """ Given a list of annotations about the code in the current tab, add the annotations to the editor window so the user can make appropriate changes. """ self.current_tab.annotate_code(feedback, annotation_type) def show_annotations(self): """ Show the annotations added to the current tab. """ self.current_tab.show_annotations() def setup(self, breakpoint_toggle, theme): """ Sets up the window. Defines the various attributes of the window and defines how the user interface is laid out. """ self.theme = theme self.breakpoint_toggle = breakpoint_toggle # Give the window a default icon, title and minimum size. self.setWindowIcon(load_icon(self.icon)) self.update_title() self.read_only_tabs = False screen_width, screen_height = self.screen_size() self.setMinimumSize(screen_width // 2, screen_height // 2) self.setTabPosition(Qt.AllDockWidgetAreas, QTabWidget.North) self.widget = QWidget() widget_layout = QVBoxLayout() self.widget.setLayout(widget_layout) self.button_bar = ButtonBar(self.widget) self.tabs = FileTabs() self.setCentralWidget(self.tabs) self.status_bar = StatusBar(parent=self) self.setStatusBar(self.status_bar) self.addToolBar(self.button_bar) self.show() def resizeEvent(self, resizeEvent): """ Respond to window getting too small for the button bar to fit well. """ size = resizeEvent.size() self.button_bar.set_responsive_mode(size.width(), size.height()) def select_mode(self, modes, current_mode): """ Display the mode selector dialog and return the result. """ mode_select = ModeSelector(self) mode_select.setup(modes, current_mode) mode_select.exec() try: return mode_select.get_mode() except Exception: return None def change_mode(self, mode): """ Given a an object representing a mode, recreates the button bar with the expected functionality. """ self.button_bar.change_mode(mode) # Update the autocomplete / tooltip APIs for each tab to the new mode. api = mode.api() for widget in self.widgets: widget.set_api(api) def set_usb_checker(self, duration, callback): """ Sets up a timer that polls for USB changes via the "callback" every "duration" seconds. """ self.usb_checker = QTimer() self.usb_checker.timeout.connect(callback) self.usb_checker.start(duration * 1000) def set_timer(self, duration, callback): """ Set a repeating timer to call "callback" every "duration" seconds. """ self.timer = QTimer() self.timer.timeout.connect(callback) self.timer.start(duration * 1000) # Measured in milliseconds. def stop_timer(self): """ Stop the repeating timer. """ if self.timer: self.timer.stop() self.timer = None def connect_tab_rename(self, handler, shortcut): """ Connect the double-click event on a tab and the keyboard shortcut to the referenced handler (causing the Save As dialog). """ self.tabs.shortcut = QShortcut(QKeySequence(shortcut), self) self.tabs.shortcut.activated.connect(handler) self.tabs.tabBarDoubleClicked.connect(handler) def open_directory_from_os(self, path): """ Given the path to a directory, open the OS's built in filesystem explorer for that path. Works with Windows, OSX and Linux. """ if sys.platform == "win32": # Windows os.startfile(path) elif sys.platform == "darwin": # OSX os.system('open "{}"'.format(path)) else: # Assume freedesktop.org on unix-y. os.system('xdg-open "{}"'.format(path)) def connect_find_replace(self, handler, shortcut): """ Create a keyboard shortcut and associate it with a handler for doing a find and replace. """ self.find_replace_shortcut = QShortcut(QKeySequence(shortcut), self) self.find_replace_shortcut.activated.connect(handler) def connect_find_again(self, handlers, shortcut): """ Create keyboard shortcuts and associate them with handlers for doing a find again in forward or backward direction. Any given shortcut will be used for forward find again, while Shift+shortcut will find again backwards. """ forward, backward = handlers self.find_again_shortcut = QShortcut(QKeySequence(shortcut), self) self.find_again_shortcut.activated.connect(forward) backward_shortcut = QKeySequence("Shift+" + shortcut) self.find_again_backward_shortcut = QShortcut(backward_shortcut, self) self.find_again_backward_shortcut.activated.connect(backward) def show_find_replace(self, find, replace, global_replace): """ Display the find/replace dialog. If the dialog's OK button was clicked return a tuple containing the find term, replace term and global replace flag. """ finder = FindReplaceDialog(self) finder.setup(find, replace, global_replace) if finder.exec(): return (finder.find(), finder.replace(), finder.replace_flag()) def replace_text(self, target_text, replace, global_replace): """ Given target_text, replace the first instance after the cursor with "replace". If global_replace is true, replace all instances of "target". Returns the number of times replacement has occurred. """ if not self.current_tab: return 0 if global_replace: counter = 0 found = self.current_tab.findFirst( target_text, True, True, False, False, line=0, index=0 ) if found: counter += 1 self.current_tab.replace(replace) while self.current_tab.findNext(): self.current_tab.replace(replace) counter += 1 return counter else: found = self.current_tab.findFirst( target_text, True, True, False, True ) if found: self.current_tab.replace(replace) return 1 else: return 0 def highlight_text(self, target_text, forward=True): """ Highlight the first match from the current position of the cursor in the current tab for the target_text. Returns True if there's a match. """ if self.current_tab: line = -1 index = -1 if not forward: # Workaround for `findFirst(forward=False)` not advancing # backwards: pass explicit line and index values. line, index, _el, _ei = self.current_tab.getSelection() return self.current_tab.findFirst( target_text, # Text to find, True, # Treat as regular expression True, # Case sensitive search False, # Whole word matches only True, # Wrap search forward=forward, # Forward search line=line, # -1 starts at current position index=index, # -1 starts at current position ) else: return False def connect_toggle_comments(self, handler, shortcut): """ Create a keyboard shortcut and associate it with a handler for toggling comments on highlighted lines. """ self.toggle_comments_shortcut = QShortcut(QKeySequence(shortcut), self) self.toggle_comments_shortcut.activated.connect(handler) def toggle_comments(self): """ Toggle comments on/off for all selected line in the currently active tab. """ if self.current_tab: self.current_tab.toggle_comments() def show_device_selector(self): """ Reveals the device selector in the status bar """ self.status_bar.device_selector.setHidden(False) def hide_device_selector(self): """ Hides the device selector in the status bar """ self.status_bar.device_selector.setHidden(True)
class MainWindow(QMainWindow): """Main GUI window containing an assy tree view and a 3D display view The User controls whether parts displayed in the 3D display view are drawn or hidden through the use of check boxes on the tree view display. The list of the uid's of all the items currently hidden is held in self.hide_list. When tree view items are checked or unchecked, a list of unchecked items is compared to self.hide_list. That comparison results in two new lists: a list of items to be erased and a list of items to be drawn. The items to be erased are erased and the items to be drawn are drawn, and the hide_list is then updated. When a part is newly created or loaded (step), the doc model is changed and this results in the regeneration of the tree view. As the new tree view items are generated, they are shown checked except for the ones that are contained in the hide_list. """ def __init__(self, *args): super().__init__() self.canvas = qtDisplay.qtViewer3d(self) # Renaming self.canvas._display (like below) doesn't work. # self.display = self.canvas._display self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.contextMenu) self.popMenu = QMenu(self) title = f"KodaCAD {APP_VERSION} " title += f"(Using: PythonOCC version {VERSION} with PyQt5 backend)" self.setWindowTitle(title) self.resize(960, 720) self.setCentralWidget(self.canvas) self.createDockWidget() self.wcToolBar = QToolBar("2D") # Construction toolbar self.addToolBar(Qt.RightToolBarArea, self.wcToolBar) self.wcToolBar.setMovable(True) self.wgToolBar = QToolBar("2D") # Geom Profile toolbar self.addToolBar(Qt.RightToolBarArea, self.wgToolBar) self.wgToolBar.setMovable(True) self.menu_bar = self.menuBar() self._menus = {} self._menu_methods = {} self.centerOnScreen() self.calculator = None self.assy_root, self.wp_root = self.create_root_items() self.itemClicked = None # TreeView item that has been mouse clicked # Internally, everything is always in mm # scale user input and output values # (user input values) * unitscale = value in mm # (output values) / unitscale = value in user's units self._unitDict = {"mm": 1.0, "in": 25.4, "ft": 304.8} self.units = "mm" self.unitscale = self._unitDict[self.units] self.unitsLabel = QLabel() self.unitsLabel.setText("Units: %s " % self.units) self.unitsLabel.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.endOpButton = QToolButton() self.endOpButton.setText("End Operation") self.endOpButton.clicked.connect(self.clearCallback) self.currOpLabel = QLabel() self.registeredCallback = None self.currOpLabel.setText("Current Operation: %s " % self.registeredCallback) self.lineEdit = QLineEdit() self.lineEdit.returnPressed.connect(self.appendToStack) status = self.statusBar() status.setSizeGripEnabled(False) status.addPermanentWidget(self.lineEdit) status.addPermanentWidget(self.currOpLabel) status.addPermanentWidget(self.endOpButton) status.addPermanentWidget(self.unitsLabel) status.showMessage("Ready", 5000) self.hide_list = [] # list of part uid's to be hidden (not displayed) self.floatStack = [] # storage stack for floating point values self.xyPtStack = [] # storage stack for 2d points (x, y) self.ptStack = [] # storage stack for gp_Pnts self.edgeStack = [] # storage stack for edge picks self.faceStack = [] # storage stack for face picks self.shapeStack = [] # storage stack for shape picks self.lineEditStack = [] # list of user inputs self.activePart = None # <TopoDS_Shape> object self.activePartUID = 0 self.transparency_dict = {} # {uid: part display transparency} self.ancestor_dict = defaultdict( list) # {uid: [list of ancestor shapes]} self.ais_shape_dict = {} # {uid: <AIS_Shape> object} self.activeWp = None # WorkPlane object self.activeWpUID = 0 self.wp_dict = {} # k = uid, v = wpObject self._wpNmbr = 1 self.activeAsyUID = 0 self.assy_list = [] # list of assy uid's self.showItemActive(0) self.setActiveAsy(self.activeAsyUID) def createDockWidget(self): self.treeDockWidget = QDockWidget("Assy/Part Structure", self) self.treeDockWidget.setObjectName("treeDockWidget") self.treeDockWidget.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.treeView = TreeView() # Assy/Part structure (display) self.treeView.itemClicked.connect(self.treeViewItemClicked) self.treeDockWidget.setWidget(self.treeView) self.addDockWidget(Qt.LeftDockWidgetArea, self.treeDockWidget) def centerOnScreen(self): """Centers the window on the screen.""" resolution = QDesktopWidget().screenGeometry() self.move( (resolution.width() / 2) - (self.frameSize().width() / 2), (resolution.height() / 2) - (self.frameSize().height() / 2), ) def contextMenu(self, point): self.menu = QMenu() self.popMenu.exec_(self.mapToGlobal(point)) def add_menu(self, menu_name): _menu = self.menu_bar.addMenu("&" + menu_name) self._menus[menu_name] = _menu def add_function_to_menu(self, menu_name, text, _callable): assert callable(_callable), "the function supplied is not callable" try: _action = QAction(text, self) # if not, the "exit" action is now shown... # Qt is trying so hard to be native cocoa'ish that its a nuisance _action.setMenuRole(QAction.NoRole) _action.triggered.connect(_callable) self._menus[menu_name].addAction(_action) except KeyError: raise ValueError("the menu item %s does not exist" % (menu_name)) def closeEvent(self, event): # things that need to happen on exit try: self.calculator.close() except AttributeError: pass event.accept() ############################################# # # treeView (QTreeWidget) building methods: # ############################################# def build_tree(self): """Build new tree view from doc.label_dict. This method is called whenever doc.doc is modified in a way that would result in a change in the tree view. The tree view represents the hierarchical structure of the top assembly and its components.""" self.clearTree() self.assy_list = [] parent_item_dict = {} # {uid: tree view item} for uid, dic in doc.label_dict.items(): # dic: {keys: 'entry', 'name', 'parent_uid', 'ref_entry'} entry = dic["entry"] name = dic["name"] parent_uid = dic["parent_uid"] if parent_uid not in parent_item_dict: parent_item = self.assy_root else: parent_item = parent_item_dict[parent_uid] # create node in tree view item_name = [name, uid] item = QTreeWidgetItem(parent_item, item_name) item.setFlags(item.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable) if uid in self.hide_list: item.setCheckState(0, Qt.Unchecked) else: item.setCheckState(0, Qt.Checked) self.treeView.expandItem(item) parent_item_dict[uid] = item # build assy_list if dic["is_assy"]: self.assy_list.append(uid) self.sync_treeview_to_active() # self.syncCheckedToDrawList() def clearTree(self): """Remove all tree view widget items and replace root item""" self.treeView.clear() self.assy_root, self.wp_root = self.create_root_items() self.repopulate_2D_tree_view() def create_root_items(self): """Create '2D' & '3D' root items in treeView.""" root_item = ["/", "0"] # [name, uid] tree_view_root = QTreeWidgetItem(self.treeView, root_item) self.treeView.expandItem(tree_view_root) wp_root = QTreeWidgetItem(tree_view_root, ["WP", "wp0"]) self.treeView.expandItem(wp_root) ay_root = QTreeWidgetItem(tree_view_root, ["3D", "0:1:1.0"]) self.treeView.expandItem(ay_root) return (ay_root, wp_root) def repopulate_2D_tree_view(self): """Add all workplanes to 2D section of tree view.""" # add items to treeView for uid in self.wp_dict: itemName = [uid, uid] item = QTreeWidgetItem(self.wp_root, itemName) item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState(0, Qt.Checked) ############################################# # # treeView item action methods: # ############################################# def treeViewItemClicked(self, item): """Called when treeView item is clicked""" self.itemClicked = item # store item if not self.inSync(): # click may have been on checkmark. self.adjust_draw_hide() def inSync(self): """Return True if unchecked items are in sync with hide_list.""" return set(self.uncheckedToList()) == set(self.hide_list) def uncheckedToList(self): """Return list of uid's of unchecked (part & wp) items in treeView.""" dl = [] for item in self.treeView.findItems( "", Qt.MatchContains | Qt.MatchRecursive): if item.checkState(0) == Qt.Unchecked: uid = item.text(1) if (uid in doc.part_dict) or (uid in self.wp_dict): dl.append(uid) return dl def adjust_draw_hide(self): """Erase from 3D display any item that gets unchecked, draw when checked. An item is a treeView widget item. It may be a part, assy or workplane. For our purpose here, we only care if it is a part or wp because those are the only types that are displayed in the 3D view window. For parts, the display is adjusted incrementally. A newly checked part is drawn and a newly unchecked part is erased. However, because workplanes have a great many ais_shapes, ais lines, ais_circles and topoDS_shapes (edges & border) as well, it isn't practical to keep track of them all just so they can removed incrementally. Also, when a new workplane is created, it is set active, so the old active workplane needs to be redrawn with a duller border color. Therefore, if there is a change in the hide_list involving a workplane, it is best to just clear the display and redraw all the workplanes that are not in the hide_list. """ unchecked = self.uncheckedToList() unchecked_set = set(unchecked) hide_list = list(self.hide_list) hide_set = set(hide_list) newly_unchecked = unchecked_set - hide_set newly_checked = hide_set - unchecked_set for uid in newly_unchecked: # If a workplane is newly unchecked, redraw is needed if uid in self.wp_dict: self.hide_list.append(uid) self.redraw() # Otherwise, we can do an incremental change in the display elif uid in doc.part_dict: self.erase_shape(uid) # Erase the shape for uid in newly_checked: if uid in doc.part_dict: self.draw_shape(uid) # Draw the shape elif uid in self.wp_dict: self.draw_wp(uid) # Draw the workplane self.hide_list = unchecked def syncUncheckedToHideList(self): """Use this method after building a new treeView to make sure items that were previously hidden are still unchecked in new treeView.""" for item in self.treeView.findItems( "", Qt.MatchContains | Qt.MatchRecursive): uid = item.text(1) if (uid in doc.part_dict) or (uid in self.wp_dict): if uid in self.hide_list: item.setCheckState(0, Qt.Unchecked) else: item.setCheckState(0, Qt.Checked) def sortViewItems(self): """Return dicts of tree view items sorted by type: (prt, ay, wp)""" # Traverse all treeView widget items iterator = QTreeWidgetItemIterator(self.treeView) pdict = {} # part-types {uid: item} adict = {} # asy-types {uid: item} wdict = {} # wp-types {uid: item} while iterator.value(): item = iterator.value() name = item.text(0) uid = item.text(1) if uid in doc.part_dict: pdict[uid] = item elif uid in self.assy_list: adict[uid] = item elif uid in self.wp_dict: wdict[uid] = item iterator += 1 return (pdict, adict, wdict) def showClickedInfo(self): """Show info for item clicked in treeView.""" item = self.itemClicked if item: self.showItemInfo(item) def showItemInfo(self, item): """Show info for item clicked in treeView.""" if item: name = item.text(0) uid = item.text(1) if name in ["/", "WP", "3D"]: print(f"Root ({name}) tree view item") elif uid.startswith("wp"): print(f"Workplane: uid: {uid}; name: {name}") else: entry = doc.label_dict[uid]["entry"] ref_ent = doc.label_dict[uid]["ref_entry"] is_assy = doc.label_dict[uid]["is_assy"] if is_assy: print( f"Assembly: uid: {uid}; name: {name}; entry: {entry}; ref_entry: {ref_ent}" ) else: print( f"Part: uid: {uid}; name: {name}; entry: {entry}; ref_entry: {ref_ent}" ) def setClickedActive(self): """Set item clicked in treeView Active.""" item = self.itemClicked if item: self.setItemActive(item) self.treeView.clearSelection() self.itemClicked = None def setItemActive(self, item): """Set (part, wp or assy) represented by treeView item to be active.""" if item: name = item.text(0) uid = item.text(1) print(f"Part selected: {name}, UID: {uid}") pd, ad, wd = self.sortViewItems() if uid in pd: self.setActivePart(uid) sbText = f"{name} [uid={uid}] is now the active part" elif uid in wd: self.setActiveWp(uid) sbText = f"{name} [uid={uid}] is now the active workplane" self.redraw() # update color of new active wp elif uid in ad: self.setActiveAsy(uid) sbText = f"{name} [uid={uid}] is now the active assembly" else: sbText = f"{name} [uid={uid}] Unable to set active." self.statusBar().showMessage(sbText, 5000) def showItemActive(self, uid): """Update tree view to show active status of (uid).""" pd, ad, wd = self.sortViewItems() if uid in pd: # Clear BG color of all part items for itm in pd.values(): itm.setBackground(0, QBrush(QColor(255, 255, 255, 0))) # Set BG color of new active part pd[uid].setBackground(0, QBrush(QColor("gold"))) elif uid in wd: # Clear BG color of all wp items for itm in wd.values(): itm.setBackground(0, QBrush(QColor(255, 255, 255, 0))) # Set BG color of new active wp wd[uid].setBackground(0, QBrush(QColor("lightgreen"))) elif uid in ad: # Clear BG color of all asy items for itm in ad.values(): itm.setBackground(0, QBrush(QColor(255, 255, 255, 0))) # Set BG color of new active asy ad[uid].setBackground(0, QBrush(QColor("lightblue"))) def sync_treeview_to_active(self): for uid in (self.activePartUID, self.activeAsyUID, self.activeWpUID): if uid: self.showItemActive(uid) def setTransparent(self): """Set treeView item clicked transparent""" item = self.itemClicked if item: uid = item.text(1) if uid in doc.part_dict: self.transparency_dict[uid] = 0.6 self.erase_shape(uid) self.draw_shape(uid) self.itemClicked = None def setOpaque(self): """Set treeView item clicked opaque""" item = self.itemClicked if item: uid = item.text(1) if uid in doc.part_dict: self.transparency_dict.pop(uid) self.erase_shape(uid) self.draw_shape(uid) self.itemClicked = None def editName(self): """Edit name of treeView item clicked""" item = self.itemClicked if item: name = item.text(0) uid = item.text(1) prompt = "Enter new name for part %s" % name newName, OK = QInputDialog.getText(self, "Input Dialog", prompt, text=name) if OK: item.setText(0, newName) print(f"UID= {uid}, name = {newName}") self.treeView.clearSelection() self.itemClicked = None doc.change_label_name(uid, newName) self.build_tree() ############################################# # # Administrative and data management methods: # ############################################# def get_wp_uid(self, wp_objct): """ Assign (and return) a new uid to a new workplane. Add item to treeview (2D) Make wp active Add to self.wp_dict. """ uid = "wp%i" % self._wpNmbr self._wpNmbr += 1 self.wp_dict[uid] = wp_objct # Add treeView item itemName = [uid, uid] item = QTreeWidgetItem(self.wp_root, itemName) item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState(0, Qt.Checked) # Make new workplane active self.setActiveWp(uid) return uid def appendToStack(self): """Called when <ret> is pressed on line edit""" self.lineEditStack.append(self.lineEdit.text()) self.lineEdit.clear() cb = self.registeredCallback if cb: cb([]) # call self.registeredCallback with arg=empty_list else: self.lineEditStack.pop() def setActivePart(self, uid): """Change active part status in a coordinated manner.""" # modify status in self self.activePartUID = uid if uid: self.activePart = doc.part_dict[uid]["shape"] # show as active in treeView self.showItemActive(uid) else: self.activePart = None def setActiveWp(self, uid): """Change active workplane status in coordinated manner.""" # modify status in self self.activeWpUID = uid self.activeWp = self.wp_dict[uid] # show as active in treeView self.showItemActive(uid) def setActiveAsy(self, uid): """Change active assembly status in coordinated manner.""" # modify status in self self.activeAsyUID = uid if uid: # show as active in treeView self.showItemActive(uid) def valueFromCalc(self, value): """Receive value from calculator.""" cb = self.registeredCallback if cb: self.lineEditStack.append(str(value)) cb([]) # call self.registeredCallback with arg=empty_list else: print(value) def clearLEStack(self): """Clear lineEditStack""" self.lineEditStack = [] def clearAllStacks(self): self.lineEditStack = [] self.floatStack = [] self.xyPtStack = [] self.edgeStack = [] self.faceStack = [] self.ptStack = [] def registerCallback(self, callback): currCallback = self.registeredCallback if currCallback: # Make sure a callback isn't already registered self.clearCallback() self.canvas._display.register_select_callback(callback) self.registeredCallback = callback self.currOpLabel.setText("Current Operation: %s " % callback.__name__[:-1]) def clearCallback(self): if self.registeredCallback: self.canvas._display.unregister_callback(self.registeredCallback) self.registeredCallback = None self.clearAllStacks() self.currOpLabel.setText("Current Operation: None ") self.statusBar().showMessage("") self.canvas._display.SetSelectionModeNeutral() ############################################# # # 3D Display Draw/Hide methods: # ############################################# def fitAll(self): """Fit all displayed parts and wp's to the screen""" self.canvas._display.FitAll() def redraw(self): """Erase & redraw all parts & workplanes except those in hide_list.""" context = self.canvas._display.Context if not self.registeredCallback: self.canvas._display.SetSelectionModeNeutral() context.SetAutoActivateSelection(True) context.RemoveAll(True) # Redraw all parts except those hidden for uid in doc.part_dict: if uid not in self.hide_list: self.draw_shape(uid) # Redraw workplanes except those hidden self.redraw_workplanes() def redraw_workplanes(self): """Redraw all workplanes except those in self.hide_list""" for uid in self.wp_dict: if uid not in self.hide_list: self.draw_wp(uid) def draw_wp(self, uid): """Draw the workplane with uid.""" context = self.canvas._display.Context if uid: wp = self.wp_dict[uid] border = wp.border if uid == self.activeWpUID: borderColor = Quantity_Color(Quantity_NOC_DARKGREEN) else: borderColor = Quantity_Color(Quantity_NOC_GRAY) aisBorder = AIS_Shape(border) context.Display(aisBorder, True) context.SetColor(aisBorder, borderColor, True) transp = 0.8 # 0.0 <= transparency <= 1.0 context.SetTransparency(aisBorder, transp, True) drawer = aisBorder.DynamicHilightAttributes() context.HilightWithColor(aisBorder, drawer, True) clClr = Quantity_Color(Quantity_NOC_MAGENTA1) for cline in wp.clines: geomline = wp.geomLineBldr(cline) aisline = AIS_Line(geomline) aisline.SetOwner(geomline) drawer = aisline.Attributes() # asp parameters: (color, type, width) asp = Prs3d_LineAspect(clClr, 2, 1.0) drawer.SetLineAspect(asp) aisline.SetAttributes(drawer) context.Display(aisline, False) # (see comment below) # 'False' above enables 'context' mode display & selection pntlist = wp.intersectPts() # type <gp_Pnt> for point in pntlist: self.canvas._display.DisplayShape(point) for ccirc in wp.ccircs: aiscirc = AIS_Circle(wp.convert_circ_to_geomCirc(ccirc)) drawer = aisline.Attributes() # asp parameters: (color, type, width) asp = Prs3d_LineAspect(clClr, 2, 1.0) drawer.SetLineAspect(asp) aiscirc.SetAttributes(drawer) context.Display(aiscirc, False) # (see comment below) # 'False' above enables 'context' mode display & selection for edge in wp.edgeList: self.canvas._display.DisplayShape(edge, color="WHITE") self.canvas._display.Repaint() def draw_shape(self, uid): """Draw the part (shape) with uid.""" context = self.canvas._display.Context if uid: if uid in self.transparency_dict: transp = self.transparency_dict[uid] else: transp = 0.0 part_data = doc.part_dict[uid] shape = part_data["shape"] color = part_data["color"] try: aisShape = AIS_Shape(shape) self.ais_shape_dict[uid] = aisShape context.Display(aisShape, True) context.SetColor(aisShape, color, True) # Set shape transparency, a float from 0.0 to 1.0 context.SetTransparency(aisShape, transp, True) drawer = aisShape.DynamicHilightAttributes() context.HilightWithColor(aisShape, drawer, True) except AttributeError as e: print(e) def erase_shape(self, uid): """Erase the part (shape) with uid.""" if uid in self.ais_shape_dict: context = self.canvas._display.Context aisShape = self.ais_shape_dict[uid] context.Remove(aisShape, True) ############################################# # # 3D Measure functons... # ############################################# def launchCalc(self): """Launch Calculator""" if not self.calculator: self.calculator = rpnCalculator.Calculator(self) self.calculator.show() def setUnits(self, units): """Set units of linear distance (Default is 'mm')""" if units in self._unitDict.keys(): self.units = units self.unitscale = self._unitDict[self.units] self.unitsLabel.setText("Units: %s " % self.units) def distPtPt(self): """Measure distance between 2 selectable points on model or workplane""" if len(self.ptStack) == 2: p2 = self.ptStack.pop() p1 = self.ptStack.pop() vec = gp_Vec(p1, p2) dist = vec.Magnitude() dist = dist / self.unitscale self.calculator.putx(dist) self.distPtPt() else: self.registerCallback(self.distPtPtC) # How to enable selecting intersection points on WP? self.canvas._display.SetSelectionModeVertex() statusText = "Select 2 points to measure distance." self.statusBar().showMessage(statusText) def distPtPtC(self, shapeList, *args): """Callback (collector) for distPtPt""" logger.debug("Edges selected: %s", shapeList) logger.debug("args: %s", args) # args = x, y mouse coords for shape in shapeList: vrtx = topods_Vertex(shape) gpPt = BRep_Tool.Pnt(vrtx) # convert vertex to gp_Pnt self.ptStack.append(gpPt) if len(self.ptStack) == 2: self.distPtPt() def edgeLen(self): """Measure length of a part edge or geometry profile line""" if self.edgeStack: edge = self.edgeStack.pop() edgelen = CPnts_AbscissaPoint_Length(BRepAdaptor_Curve(edge)) edgelen = edgelen / self.unitscale self.calculator.putx(edgelen) self.edgeLen() else: self.registerCallback(self.edgeLenC) self.canvas._display.SetSelectionModeEdge() statusText = "Pick an edge to measure." self.statusBar().showMessage(statusText) def edgeLenC(self, shapeList, *args): """Callback (collector) for edgeLen""" logger.debug("Edges selected: %s", shapeList) logger.debug("args: %s", args) # args = x, y mouse coords for shape in shapeList: edge = topods_Edge(shape) self.edgeStack.append(edge) if self.edgeStack: self.edgeLen()
def initGui(self): self.installEventFilter(self) self.dashboard = Expander("Dashboard", ":/images/dashboard_normal.png", ":/images/dashboard_hover.png", ":/images/dashboard_selected.png") self.content = Expander("Content", ":/images/pages_normal.png", ":/images/pages_hover.png", ":/images/pages_selected.png") self.appearance = Expander("Appearance", ":/images/appearance_normal.png", ":/images/appearance_hover.png", ":/images/appearance_selected.png") self.settings = Expander("Settings", ":/images/settings_normal.png", ":/images/settings_hover.png", ":/images/settings_selected.png") self.setWindowTitle(QCoreApplication.applicationName() + " " + QCoreApplication.applicationVersion()) vbox = QVBoxLayout() vbox.addWidget(self.dashboard) vbox.addWidget(self.content) vbox.addWidget(self.appearance) vbox.addWidget(self.settings) vbox.addStretch() content_box = QVBoxLayout() pages_button = HyperLink("Pages") posts_button = HyperLink("Posts") content_box.addWidget(pages_button) content_box.addWidget(posts_button) self.content.addLayout(content_box) app_box = QVBoxLayout() themes_button = HyperLink("Themes") menus_button = HyperLink("Menus") self.theme_settings_button = HyperLink("Theme Settings") self.theme_settings_button.setVisible(False) app_box.addWidget(menus_button) app_box.addWidget(themes_button) app_box.addWidget(self.theme_settings_button) self.appearance.addLayout(app_box) scroll_content = QWidget() scroll_content.setLayout(vbox) scroll = QScrollArea() scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll.setWidget(scroll_content) scroll.setWidgetResizable(True) scroll.setMaximumWidth(200) scroll.setMinimumWidth(200) self.navigationdock = QDockWidget("Navigation", self) self.navigationdock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.navigationdock.setWidget(scroll) self.navigationdock.setObjectName("Navigation") self.addDockWidget(Qt.LeftDockWidgetArea, self.navigationdock) self.showDock = FlatButton(":/images/edit_normal.png", ":/images/edit_hover.png") self.showDock.setToolTip("Show Navigation") self.statusBar().addPermanentWidget(self.showDock) self.dashboard.expanded.connect(self.dashboardExpanded) self.dashboard.clicked.connect(self.showDashboard) self.content.expanded.connect(self.contentExpanded) self.content.clicked.connect(self.showPages) self.appearance.expanded.connect(self.appearanceExpanded) self.appearance.clicked.connect(self.showMenus) self.settings.expanded.connect(self.settingsExpanded) self.settings.clicked.connect(self.showSettings) menus_button.clicked.connect(self.showMenus) pages_button.clicked.connect(self.showPages) posts_button.clicked.connect(self.showPosts) themes_button.clicked.connect(self.showThemes) self.theme_settings_button.clicked.connect(self.showThemesSettings) self.showDock.clicked.connect(self.showMenu) self.navigationdock.visibilityChanged.connect( self.dockVisibilityChanged)
class MainAppWindow(QMainWindow, QObject): """Main window of the Mooring Simulator application The MainWindows class inherits from QMainWindow which is a prefabricated widget providing many standard window features that are used in the application, including toolbars, menus, a status bar and more, menus, status bar, dockable widgets and more """ # defined a signal named trigger as class attribute, used to display info on status bar # experimental... trigger = pyqtSignal() def __init__(self, library_file_name='', file_name=''): """In the class initializer .__init__(), you first call the parent class QMainWindow initializer using super(). Then you set the title of the window using .setWindowTitle() and resize the window using .resize() """ super(MainAppWindow, self).__init__() self.setWindowTitle(f"{NAME} v{VERSION}") # we use same name for directory and toml configuration file self.cfg = ConfigWindow(NAME, APPNAME, VERSION) self.resize(self.cfg['global']['screenWidth'], self.cfg['global']['screenHeight']) self.fileName = file_name self.libraryFileName = library_file_name # The window’s central widget is a QLabel object that you’ll use to show # messages in response to certain user actions. These messages will display # at the center of the window. To do this, you call .setAlignment() on the # QLabel object with a couple of alignment flags. self.centralWidget = QLabel(f"Hello, welcome inside {NAME}, enjoy!") self.centralWidget.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self.setCentralWidget(self.centralWidget) # Note that you call ._createActions() before you call ._createMenuBar() and # ._createToolBars() because you’ll be using these actions on your menus and # toolbars. self._createActions() self._createMenuBar() self._createToolBars() # Uncomment the call to ._createContextMenu() below to create a context # menu using menu policies. To test this out, you also need to # comment .contextMenuEvent() and uncomment ._createContextMenu() # self._createContextMenu() self._connectActions() self._createStatusBar() # need to be modify because the path of the library is defined in the config file now # if library = args.lib not empty, load library directly if self.libraryFileName: self.loadLibrary() def _createMenuBar(self): """In a PyQt main window–style application, QMainWindow provides an empty QMenuBar object by default. To get access to this menu bar, you need to call .menuBar() on your QMainWindow object. This method will return an empty menu bar. The parent for this menu bar will be your main window object.""" # This is the preferred way of creating a menu bar in PyQt. Here, the menuBar # variable will hold an empty menu bar, which will be your main window’s menu bar. menuBar = self.menuBar() # File menu # If you use the first option, then you need to create your custom QMenu objects # first. To do that, you can use one of the following constructors: # QMenu(parent) # QMenu(title, parent) # In both cases, parent is the QWidget that will hold the ownership of the QMenu # object. You’ll typically set parent to the window in which you’ll use the menu. # In the second constructor, title will hold a string with a text that describes # the menu option. # Creating menus using a QMenu object fileMenu = QMenu("&File", self) menuBar.addMenu(fileMenu) fileMenu.addAction(self.newAction) fileMenu.addAction(self.openAction) # Creating menus using a title self.openRecentMenu = fileMenu.addMenu("Open Recent") # You can add actions to a QMenu object using .addAction(). This method has # several variations. Most of them are thought to create actions on the fly. # We use a variation of .addAction() that QMenu inherits from QWidget: # QWidget.addAction(action) # The argument action represents the QAction object that you want to add to # a given QWidget object. With this variation of .addAction(), you can create # your actions beforehand and then add them to your menus as needed. fileMenu.addAction(self.saveAction) # Separator fileMenu.addSeparator() fileMenu.addAction(self.exitAction) # Edit menu editMenu = menuBar.addMenu("&Edit") editMenu.addAction(self.copyAction) editMenu.addAction(self.pasteAction) editMenu.addAction(self.cutAction) editMenu.setDisabled(True) # Edit separator editMenu.addSeparator() editMenu.addAction(self.zoomInAction) editMenu.addAction(self.zoomOutAction) # Library menu libraryMenu = menuBar.addMenu("&Library") libraryMenu.addAction(self.showLibraryAction) libraryMenu.addAction(self.loadLibraryAction) libraryMenu.addAction(self.refreshLibraryAction) libraryMenu.addAction(self.openExcelLibraryAction) # Configuration menu configurationMenu = menuBar.addMenu("&Configuration") configurationMenu.addAction(self.globalConfigurationAction) configurationMenu.addAction(self.setenvConfigurationAction) # Simulate menu simulateMenu = menuBar.addMenu("&Simulate") simulateMenu.addAction(self.startSimulateAction) simulateMenu.addAction(self.generateReportAction) simulateMenu.setDisabled(True) # Help menu helpMenu = menuBar.addMenu("&Help") helpMenu.addAction(self.helpContentAction) helpMenu.addAction(self.aboutAction) def _createToolBars(self): # A toolbar is a movable panel that holds buttons and other widgets to provide # fast access to the most common options of a GUI application. Toolbar buttons # can display icons, text, or both to represent the task that they perform. # The base class for toolbars in PyQt is QToolBar. This class will allow you to # create custom toolbars for your GUI applications. # Create File toolbar using a title fileToolBar = self.addToolBar("File") fileToolBar.setMovable(False) fileToolBar.addAction(self.newAction) fileToolBar.addAction(self.openAction) fileToolBar.addAction(self.saveAction) # Create Edit toolbar using a QToolBar object self.editToolBar = QToolBar("Edit", self) # inserts a QToolBar object (toolbar) into the specified toolbar area (area). # self.addToolBar(Qt.LeftToolBarArea, editToolBar) self.addToolBar(self.editToolBar) self.editToolBar.addAction(self.copyAction) self.editToolBar.addAction(self.pasteAction) self.editToolBar.addAction(self.cutAction) self.editToolBar.addAction(self.zoomInAction) self.editToolBar.addAction(self.zoomOutAction) self.editToolBar.setDisabled(True) # Library toolbar libraryToolBar = QToolBar("Edit", self) self.addToolBar(libraryToolBar) libraryToolBar.addAction(self.showLibraryAction) libraryToolBar.addAction(self.loadLibraryAction) libraryToolBar.addAction(self.refreshLibraryAction) libraryToolBar.addAction(self.openExcelLibraryAction) # Adding Widgets to a Toolbar # First import the spin box class. Then create a QSpinBox object, set its # focusPolicy to Qt.NoFocus, and finally add it to the library toolbar. # self.fontSizeSpinBox = QSpinBox() # self.fontSizeSpinBox.setFocusPolicy(Qt.NoFocus) # libraryToolBar.addWidget(self.fontSizeSpinBox) def _createStatusBar(self): self.statusbar = self.statusBar() # Temporary message self.statusbar.showMessage("Ready", 3000) # Permanent widget self.wcLabel = QLabel(f"{self.getWordCount()} Words") self.statusbar.addPermanentWidget(self.wcLabel) def _createActions(self): # File actions self.newAction = QAction(self) self.newAction.setText("&New mooring") self.newAction.setIcon( QIcon(self.style().standardIcon( getattr(QStyle, "SP_FileDialogNewFolder")))) self.openAction = QAction( QIcon(self.style().standardIcon( getattr(QStyle, "SP_DialogOpenButton"))), "&Open mooring", self) self.saveAction = QAction( QIcon(self.style().standardIcon( getattr(QStyle, "SP_DialogSaveButton"))), "&Save mooring", self) self.exitAction = QAction("&Exit", self) # String-based key sequences self.newAction.setShortcut("Ctrl+N") self.openAction.setShortcut("Ctrl+O") self.saveAction.setShortcut("Ctrl+S") # Help tips newTip = "Design a new mooring" self.newAction.setStatusTip(newTip) self.newAction.setToolTip(newTip) self.newAction.setWhatsThis("Create a new and empty mooring") # Edit actions self.copyAction = QAction(QIcon(":edit-copy.png"), "&Copy", self) self.pasteAction = QAction(QIcon(":edit-paste.png"), "&Paste", self) self.cutAction = QAction(QIcon(":edit-cut.png"), "C&ut", self) self.zoomInAction = QAction(QIcon(":zoom-in.png"), "Zoom in", self) self.zoomOutAction = QAction(QIcon(":zoom-out.png"), "Zoom out", self) # Standard key sequence self.copyAction.setShortcut(QKeySequence.Copy) self.pasteAction.setShortcut(QKeySequence.Paste) self.cutAction.setShortcut(QKeySequence.Cut) # Library actions self.showLibraryAction = QAction(QIcon(":library-show.png"), "&Show library", self) self.loadLibraryAction = QAction(QIcon(":library-load-2.png"), "&Load new library", self) self.refreshLibraryAction = QAction(QIcon(":refresh.png"), "&Refresh library", self) self.openExcelLibraryAction = QAction(QIcon(":spreadsheet.png"), "&Open Excel library", self) # Standard key sequence # self.showLibraryAction.setShortcut(QKeySequence.Copy) # self.loadLibraryAction.setShortcut(QKeySequence.Paste) # Configuration actions self.globalConfigurationAction = QAction("Set global configuration", self) self.setenvConfigurationAction = QAction( "Set environnemental conditions", self, checkable=True) # Simulate actions self.startSimulateAction = QAction(QIcon(":play.png"), "Start simulation", self) self.generateReportAction = QAction("Generate report", self) # Help actions self.helpContentAction = QAction("&Help Content...", self) self.aboutAction = QAction("&About...", self) # Status bar actions # not well implemented # self.centralWidgetAction = QAction(self.centralWidget) # self.centralWidget.addAction(self.centralWidgetAction) self.trigger.connect(self.handle_trigger) # Uncomment this method to create a context menu using menu policies # def _createContextMenu(self): # # Setting contextMenuPolicy # self.centralWidget.setContextMenuPolicy(Qt.ActionsContextMenu) # # Populating the widget with actions # self.centralWidget.addAction(self.newAction) # self.centralWidget.addAction(self.openAction) # self.centralWidget.addAction(self.saveAction) # self.centralWidget.addAction(self.copyAction) # self.centralWidget.addAction(self.pasteAction) # self.centralWidget.addAction(self.cutAction) def contextMenuEvent(self, event): # Context menu menu = QMenu(self.centralWidget) # Populating the menu with actions menu.addAction(self.newAction) menu.addAction(self.openAction) menu.addAction(self.saveAction) # Separator separator = QAction(self) separator.setSeparator(True) menu.addAction(separator) menu.addAction(self.showLibrary) menu.addAction(self.pickLibrary) # Launching the menu menu.exec(event.globalPos()) def _connectActions(self): """Connecting Signals and Slots in Menus and Toolbars In PyQt, you use signals and slots to provide functionality to your GUI applications. PyQt widgets emit signals every time an event such as a mouse click, a keypress, or a window resizing, occurs on them. A slot is a Python callable that you can connect to a widget’s signal to perform some actions in response to user events. If a signal and a slot are connected, then the slot will be called automatically every time the signal is emitted. Slot is a Python callable. In other words, slot can be a function, a method, a class, or an instance of a class that implements .__call__().""" # Connect File actions self.newAction.triggered.connect(self.newFile) self.openAction.triggered.connect(self.openFile) self.saveAction.triggered.connect(self.saveFile) # In the case of exitAction, you connect its triggered() signal with the built-in # slot QMainWindow.close(). # This way, if you select File → Exit, then your application will close. self.exitAction.triggered.connect(self.close) # Connect Edit actions self.copyAction.triggered.connect(self.copyContent) self.pasteAction.triggered.connect(self.pasteContent) self.cutAction.triggered.connect(self.cutContent) self.zoomInAction.triggered.connect(self.zoomIn) self.zoomOutAction.triggered.connect(self.zoomOut) # Connect Library actions self.showLibraryAction.triggered.connect(self.showLibrary) self.loadLibraryAction.triggered.connect(self.pickLibrary) self.refreshLibraryAction.triggered.connect(self.refreshLibrary) self.openExcelLibraryAction.triggered.connect(self.openExcelLibrary) # Connect Configuration actions self.globalConfigurationAction.triggered.connect( self.globalConfiguration) self.setenvConfigurationAction.triggered.connect( self.setenvConfiguration) # Connect Simulate actions self.startSimulateAction.triggered.connect(self.startSimulate) self.generateReportAction.triggered.connect(self.generateReport) # Connect Help actions self.helpContentAction.triggered.connect(self.helpContent) # Connect About actions self.aboutAction.triggered.connect(self.about) # Connect Open Recent to dynamically populate it self.openRecentMenu.aboutToShow.connect(self.populateOpenRecent) # Slots def newFile(self): # Logic for creating a new file goes here... self.centralWidget.setText("<b>File > New mooring</b> clicked") def openFile(self): # Logic for opening an existing file goes here... self.centralWidget.setText("<b>File > Open a mooring...</b> clicked") def saveFile(self): # Logic for saving a file goes here... self.centralWidget.setText("<b>File > Save mooring</b> clicked") def copyContent(self): # Logic for copying content goes here... self.centralWidget.setText("<b>Edit > Copy</b> clicked") def pasteContent(self): # Logic for pasting content goes here... self.centralWidget.setText("<b>Edit > Paste</b> clicked") def cutContent(self): # Logic for cutting content goes here... self.centralWidget.setText("<b>Edit > Cut</b> clicked") def zoomIn(self): # Logic for saving a file goes here... self.centralWidget.setText("<b>File > Zoom in mooring</b> clicked") def zoomOut(self): # Logic for copying content goes here... self.centralWidget.setText("<b>Edit > Zoom out mooring</b> clicked") def showLibrary(self): # Logic for pasting content goes here... self.centralWidget.setText("<b>Library > Show </b> clicked") def pickLibrary(self): # Logic for pasting content goes here... # self.libraryDockWidget.hide() (self.libraryFileName, _) = QFileDialog.getOpenFileName(self, ("Open File"), "Library", ("Spreadsheet (*.xls)")) self.loadLibrary() def loadLibrary(self): self.editToolBar.setDisabled(False) self.library = LibraryWidget(self.libraryFileName) self.library.setMinimumWidth( floor(self.cfg['global']['screenWidth'] / 2)) self.library.setMinimumHeight(200) self.libraryDockWidget = QDockWidget() self.libraryDockWidget.setWidget(self.library) #Ajoute la bibliotheque dans le DockWidget en position haute# self.addDockWidget(Qt.TopDockWidgetArea, self.libraryDockWidget) self.libraryDockWidget.setWindowTitle('Library') # send a signal to statusbar for testing only self.trigger.emit() def refreshLibrary(self): """ insert doc here""" if self.editToolBar.isEnabled(): self.library.read() self.library.libraryLayout.removeWidget(self.library.libraryArea) self.library.libraryArea.close() self.library.display() self.library.libraryLayout.addWidget(self.library.libraryArea) else: self.loadLibrary() self.library.display() def openExcelLibrary(self): print(sys.platform) if sys.platform == "win32": os.startfile(self.libraryFileName) else: opener = "open" if sys.platform == "darwin" else "xdg-open" subprocess.call([opener, self.libraryFileName]) def globalConfiguration(self): self.cfg.displayGlobalConfig() self.cfg.show() def setenvConfiguration(self): # Logic for pasting content goes here... self.centralWidget.setText("<b>setenv Configuration</b> clicked") def startSimulate(self): # Logic for pasting content goes here... self.centralWidget.setText( "<b>Simulate > Start simulation</b> clicked") def generateReport(self, action=None): # Logic for pasting content goes here... self.centralWidget.setText("<b>Simulate > Generate report</b> clicked") def helpContent(self): # Logic for launching help goes here... self.centralWidget.setText("<b>Simulate > Help Content...</b> clicked") def about(self): # Logic for showing an about dialog content goes here... self.centralWidget.setText(f"<b>{NAME}:</b> {VERSION}") def populateOpenRecent(self): # Step 1. Remove the old options from the menu self.openRecentMenu.clear() # Step 2. Dynamically create the actions actions = [] filenames = [f"File-{n}" for n in range(5)] for filename in filenames: action = QAction(filename, self) action.triggered.connect(partial(self.openRecentFile, filename)) actions.append(action) # Step 3. Add the actions to the menu self.openRecentMenu.addActions(actions) def openRecentFile(self, filename): # Logic for opening a recent file goes here... self.centralWidget.setText(f"<b>{filename}</b> opened") def getWordCount(self): # Logic for computing the word count goes here... return len(self.centralWidget.text()) def handle_trigger(self): self.wcLabel.setText(f"{self.getWordCount()} Words")
class SynpoWindow(QMainWindow): def __init__(self): super(SynpoWindow, self).__init__() self.ais = None self.h5file = None self.data = None self.setWindowTitle("AICluster") self.pix = None Global.imageWindow = self self.canny2 = 200 self.canny1 = 100 self.synpochannel = 0 self.aisthreshold = 0.5 self.synpothreshold = 0.5 import matplotlib.pyplot as plt self.fig, self.nanas = plt.subplots(3, 2) self.axes = self.nanas.flatten() self.canvas = FigureCanvas(self.fig) self.clustermask = None self.roidock = QDockWidget("Settings", self) self.span = SpanSelector(self.axes[2], self.onselect, 'horizontal', useblit=True, rectprops=dict(alpha=0.5, facecolor='red')) self.lasso = LassoSelector(self.axes[5], self.onlassoselectcluster) self.lasso2 = LassoSelector(self.axes[1], self.onlassoselectais) self.setCentralWidget(self.canvas) self.settings = QWidget() self.settingsLayout = QVBoxLayout() self.settingBox = QGroupBox("Roi-Meta") self.settingLayout = QGridLayout() self.indexLabel = QLabel("Roi") self.indexEdit = QLineEdit() self.indexEdit.setDisabled(True) self.settingLayout.addWidget(self.indexLabel, 1, 0) self.settingLayout.addWidget(self.indexEdit, 1, 1) self.fileLabel = QLabel("File") self.fileEdit = QLineEdit() self.fileEdit.setDisabled(True) self.settingLayout.addWidget(self.fileLabel, 0, 0) self.settingLayout.addWidget(self.fileEdit, 0, 1) self.volumeLabel = QLabel("Volume") self.volumeEdit = QLineEdit() self.volumeEdit.setDisabled(True) self.settingLayout.addWidget(self.volumeLabel, 2, 0) self.settingLayout.addWidget(self.volumeEdit, 2, 1) self.diameterLabel = QLabel("Diameter") self.diameterEdit = QLineEdit() self.diameterEdit.setDisabled(True) self.settingLayout.addWidget(self.diameterLabel, 3, 0) self.settingLayout.addWidget(self.diameterEdit, 3, 1) self.diameterLabel = QLabel("SynpoVolume") self.diameterEdit = QLineEdit() self.diameterEdit.setDisabled(True) self.settingLayout.addWidget(self.diameterLabel, 4, 0) self.settingLayout.addWidget(self.diameterEdit, 4, 1) self.settingBox.setLayout(self.settingLayout) self.settingsLayout.addWidget(self.settingBox) self.fileBox = QGroupBox("Files") self.filesLayout = QGridLayout() self.saveButton = QPushButton("Save") self.saveButton.clicked.connect(self.saveROI) self.filesLayout.addWidget(self.saveButton, 0, 0) self.calculateButton = QPushButton("Calculate") self.calculateButton.clicked.connect(self.calculate) self.filesLayout.addWidget(self.calculateButton, 0, 0) self.changefilebutton = QPushButton('Change File') self.changefilebutton.clicked.connect(self.changeFile) self.filesLayout.addWidget(self.changefilebutton, 0, 1) self.calculateandsavebutton = QPushButton('Calculate and Save') self.calculateandsavebutton.clicked.connect(self.calculateandSave) self.filesLayout.addWidget(self.calculateandsavebutton, 1, 0) # set button to call somefunction() when clicked self.cannyselector1 = ValueSelector("Canny1", 0, 100, self.cannyselector1changed, ismin=True) self.cannyselector2 = ValueSelector("Canny1", 0, 200, self.cannyselector2changed, ismin=True) self.aisthresholdselector = ValueSelector( "AISThreshold", 0, 100, self.aisthresholdselectorchanged, ismin=True) self.synpothresholdselector = ValueSelector( "SynpoThreshold", 0, 100, self.synpothresholdselectorchanged, ismin=True) self.synpochannelselector = SynpoChannelSelector( "Synpochannel", self.synpochannelchanged) self.flagsBox = QGroupBox("Flags") self.flagsLayout = QVBoxLayout() self.flagsText = QLineEdit() self.flagsLayout.addWidget(self.flagsText) self.flagsLayout.addWidget(self.cannyselector1) self.flagsLayout.addWidget(self.cannyselector2) self.flagsLayout.addWidget(self.synpochannelselector) self.flagsLayout.addWidget(self.aisthresholdselector) self.flagsLayout.addWidget(self.synpothresholdselector) self.flagsBox.setLayout(self.flagsLayout) self.settingsLayout.addWidget(self.flagsBox) self.fileBox.setLayout(self.filesLayout) self.settingsLayout.addWidget(self.fileBox) self.settingsLayout.addStretch() self.filebutton = QPushButton("Choose Directory", self) self.filebutton.clicked.connect(self.openDir) self.settingsLayout.addWidget(self.filebutton) self.settings.setLayout(self.settingsLayout) self.roidock.setWidget(self.settings) self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.roidock) self.show() def cannyselector1changed(self, value, ismax, ismin): if ismin is True: logging.info("Setting Canny1 to {0}".format(value)) self.canny1 = value self.updateUI() if ismax is True: print("EROOR") logging.info("Setting Endstage to {0}".format(value)) self.canny2 = value def cannyselector2changed(self, value, ismax, ismin): if ismin is True: logging.info("Setting Canny2 to {0}".format(value)) self.canny2 = value self.updateUI() if ismax is True: print("EROOR") logging.info("Setting Endstage to {0}".format(value)) self.canny2 = value def aisthresholdselectorchanged(self, value, ismax, ismin): if ismin is True: logging.info("Setting Threshold to {0}".format(value)) try: self.aisthreshold = float(value) / 100 except: self.aisthreshold = 0 print(self.aisthreshold) self.updateUI() if ismax is True: print("EROOR") logging.info("Setting Endstage to {0}".format(value)) self.aisthreshold = value def synpothresholdselectorchanged(self, value, ismax, ismin): if ismin is True: logging.info("Setting Threshold to {0}".format(value)) try: self.synpothreshold = float(value) / 100 except: self.synpothreshold = 0 print(self.synpothreshold) self.updateUI() if ismax is True: print("EROOR") logging.info("Setting Endstage to {0}".format(value)) self.synpothreshold = value def synpochannelchanged(self, value): self.synpochannel = value print(self.synpochannel) self.updateUI() def updateUI(self): self.setWindowTitle(Global.filepath) self.flagsText.setText(self.ais.flags) self.fileEdit.setText(Global.filepath.split("/")[-1]) self.indexEdit.setText(str(self.ais.index)) self.sobelx = logic.getSobel(self.ais.image, self.ais.b4channel, self.canny1, self.canny2) self.discardedsobel = self.sobelx - self.aismask * 255 self.discardedsobel[self.discardedsobel < 0] = 0 self.overlappedimage = logic.overlap(self.ais.image, self.ais.b4channel, self.synpochannel, self.aisthreshold, self.synpothreshold) self.maskedoverlapped = self.overlappedimage - self.clustermask * 255 self.maskedoverlapped[self.maskedoverlapped < 0] = 0 self.sobelsynpoc = logic.getSobel(self.maskedoverlapped, 0, self.canny1, self.canny2) self.linelist = logic.getLineListSobel(self.discardedsobel) self.linelistsynpo = logic.getLineListSobel(self.sobelsynpoc) self.axes[0].set_ylim(0, self.sobelx.shape[0]) self.axes[0].set_xlim(0, self.sobelx.shape[1]) self.axes[0].imshow(self.ais.image) self.axes[1].set_ylim(0, self.sobelx.shape[0]) self.axes[1].set_xlim(0, self.sobelx.shape[1]) self.axes[1].imshow(self.discardedsobel) self.axes[2].clear() self.axes[2].set_ylim(0, self.sobelx.shape[0]) self.axes[2].set_xlim(0, self.sobelx.shape[1]) self.axes[2].imshow(self.ais.image) self.axes[3].set_ylim(0, self.sobelx.shape[0]) self.axes[3].set_xlim(0, self.sobelx.shape[1]) self.axes[3].imshow(self.sobelsynpoc) self.axes[4].clear() self.axes[4].set_ylim(0, self.sobelx.shape[0]) self.axes[4].set_xlim(0, self.sobelx.shape[1]) self.axes[4].imshow(self.ais.image) #self.axes[6].clear() #self.axes[6].set_ylim(0, self.sobelx.shape[0]) #self.axes[6].set_xlim(0, self.sobelx.shape[1]) #if self.clustermask is not None: self.axes[6].imshow(self.clustermask) self.axes[5].set_ylim(0, self.sobelx.shape[0]) self.axes[5].set_xlim(0, self.sobelx.shape[1]) self.axes[5].imshow(self.maskedoverlapped) for index, element in enumerate(self.linelist): line = lines.Line2D([element[0][0], element[1][0]], [element[0][1], element[1][1]], color='pink', alpha=0.6) self.axes[2].add_line(line) for index, element in enumerate(self.linelistsynpo): line = lines.Line2D([element[0][0], element[1][0]], [element[0][1], element[1][1]], color='yellow', alpha=0.6) self.axes[4].add_line(line) self.canvas.draw() self.show() def setRoi(self): self.ais.linelist = self.linelist self.ais.sobel = self.sobelx def instantiateFiles(self): self.aish5list = logic.getAIS(Global.filepath) random.shuffle(self.aish5list) self.newinstance() def newinstance(self): if len(self.aish5list) >= 1: self.ais, self.h5file = self.aish5list.pop() print("HFile", self.h5file, " ROI: ", self.ais.index) print("Resetting the pix matrix") x, y = np.meshgrid(np.arange(self.ais.image.shape[1]), np.arange(self.ais.image.shape[0])) self.pix = np.vstack((x.flatten(), y.flatten())).T self.clustermask = np.zeros_like(self.ais.image[:, :, 0]) self.aismask = np.zeros_like(self.ais.image[:, :, 0]) self.updateUI() else: QMessageBox.about( self, "All Done", "Every single file is already evaluated. YEAAHHHH") self.openDir() def saveROI(self): with h5py.File(self.h5file, "a") as f: nana = f["Data/" + self.ais.key].attrs["Diameter"] = self.ais.diameter soso = f["Data/" + self.ais.key].attrs["Volume"] = self.ais.volume return 0 def quit(self): pass def openDir(self): # this is called when button1 is clicked # put directory specific tasks here # examples: ddir = QFileDialog.getExistingDirectory(self, "Get Dir Path") print(ddir) # ddir is a QString containing the path to the directory you selected if ddir != "": Global.filepath = ddir self.instantiateFiles() # lets get a list of files from the directory: def calculate(self): self.startParsing() def calculateandSave(self): self.calculate() self.saveROI() def startParsing(self): self.setRoi() self.ais.diameter, self.ais.volume = logic.calculateDiameterAndVolume( self.ais) self.synpodiameter, self.synpovolume = logic.calculateSynpoVolume( self.ais) self.volumeEdit.setText(str(self.ais.volume) + " µm") self.diameterEdit.setText(str(self.ais.diameter) + " µm") def changeFile(self, **kwargs): self.linelist = [] self.newinstance() def onselect(self, xmin, xmax): print(xmin, xmax) self.ais.sections.append([xmin, xmax]) def updateArray(self, array, indices): lin = np.arange(array.size) newArray = array.flatten() newArray[lin[indices]] = 1 return newArray.reshape(array.shape) def onlassoselectcluster(self, verts): p = path.Path(verts) ind = p.contains_points(self.pix, radius=1) self.clustermask.flat[ind] = np.ones_like(self.ais.image[:, :, 0]).flat[ind] self.updateUI() def onlassoselectais(self, verts): p = path.Path(verts) ind = p.contains_points(self.pix, radius=1) self.aismask.flat[ind] = np.ones_like(self.ais.image[:, :, 0]).flat[ind] self.updateUI()
def add_python3_runner( self, interpreter, script_name, working_directory, interactive=False, debugger=False, command_args=None, envars=None, python_args=None, ): """ Display console output for the interpreter with the referenced pythonpath running the referenced script. The script will be run within the workspace_path directory. If interactive is True (default is False) the Python process will run in interactive mode (dropping the user into the REPL when the script completes). If debugger is True (default is False) the script will be run within a debug runner session. The debugger overrides the interactive flag (you cannot run the debugger in interactive mode). If there is a list of command_args (the default is None) then these will be passed as further arguments into the command run in the new process. If envars is given, these will become part of the environment context of the new chlid process. If python_args is given, these will be passed as arguments to the Python runtime used to launch the child process. """ self.process_runner = PythonProcessPane(self) self.runner = QDockWidget( _("Running: {}").format(os.path.basename(script_name)) ) self.runner.setWidget(self.process_runner) self.runner.setFeatures(QDockWidget.DockWidgetMovable) self.runner.setAllowedAreas( Qt.BottomDockWidgetArea | Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea ) self.process_runner.debugger = debugger if debugger: area = self._debugger_area or Qt.BottomDockWidgetArea else: area = self._runner_area or Qt.BottomDockWidgetArea self.addDockWidget(area, self.runner) logger.info( "About to start_process: %r, %r, %r, %r, %r, %r, %r, %r", interpreter, script_name, working_directory, interactive, debugger, command_args, envars, python_args, ) self.process_runner.start_process( interpreter, script_name, working_directory, interactive, debugger, command_args, envars, python_args, ) self.process_runner.setFocus() self.process_runner.on_append_text.connect(self.on_stdout_write) self.connect_zoom(self.process_runner) return self.process_runner
class AppWindow(QMainWindow): onRestart = pyqtSignal(name='onRestart') def __init__(self, dwarf_args, flags=None): super(AppWindow, self).__init__(flags) self.dwarf_args = dwarf_args self.session_manager = SessionManager(self) self.session_manager.sessionCreated.connect(self.session_created) self.session_manager.sessionStopped.connect(self.session_stopped) self.session_manager.sessionClosed.connect(self.session_closed) self.menu = self.menuBar() self._is_newer_dwarf = False self.view_menu = None self.asm_panel = None self.console_panel = None self.context_panel = None self.backtrace_panel = None self.contexts_list_panel = None self.data_panel = None self.emulator_panel = None self.ftrace_panel = None self.hooks_panel = None self.bookmarks_panel = None self.smali_panel = None self.java_inspector_panel = None self.java_explorer_panel = None self.java_trace_panel = None self.memory_panel = None self.modules_panel = None self.ranges_panel = None self.search_panel = None self.trace_panel = None self.watchers_panel = None self.welcome_window = None self._ui_elems = [] self.setWindowTitle( 'Dwarf - A debugger for reverse engineers, crackers and security analyst' ) # load external assets _app = QApplication.instance() self.remove_tmp_dir() # themes self.prefs = Prefs() self.set_theme(self.prefs.get('dwarf_ui_theme', 'black')) # load font if os.path.exists(utils.resource_path('assets/Anton.ttf')): QFontDatabase.addApplicationFont( utils.resource_path('assets/Anton.ttf')) if os.path.exists(utils.resource_path('assets/OpenSans-Regular.ttf')): QFontDatabase.addApplicationFont( utils.resource_path('assets/OpenSans-Regular.ttf')) _app.setFont(QFont("OpenSans", 9, QFont.Normal)) if os.path.exists(utils.resource_path('assets/OpenSans-Bold.ttf')): QFontDatabase.addApplicationFont( utils.resource_path('assets/OpenSans-Bold.ttf')) # mainwindow statusbar self.progressbar = QProgressBar() self.progressbar.setRange(0, 0) self.progressbar.setVisible(False) self.progressbar.setFixedHeight(15) self.progressbar.setFixedWidth(100) self.progressbar.setTextVisible(False) self.progressbar.setValue(30) self.statusbar = QStatusBar(self) self.statusbar.setAutoFillBackground(False) self.statusbar.addPermanentWidget(self.progressbar) self.statusbar.setObjectName("statusbar") self.setStatusBar(self.statusbar) self.main_tabs = QTabWidget(self) self.main_tabs.setMovable(True) self.main_tabs.setAutoFillBackground(True) self.setCentralWidget(self.main_tabs) if self.dwarf_args.package is None: self.welcome_window = WelcomeDialog(self) self.welcome_window.setModal(True) self.welcome_window.onIsNewerVersion.connect( self._enable_update_menu) self.welcome_window.onUpdateComplete.connect( self._on_dwarf_updated) self.welcome_window.setWindowTitle( 'Welcome to Dwarf - A debugger for reverse engineers, crackers and security analyst' ) self.welcome_window.onSessionSelected.connect(self._start_session) self.welcome_window.onSessionRestore.connect(self._restore_session) # wait for welcome screen self.hide() self.welcome_window.show() else: if dwarf_args.package is not None: if dwarf_args.type is None: # no device given check if package is local path if os.path.exists(dwarf_args.package): print('* Starting new LocalSession') self._start_session('local') else: print('use -t to set sessiontype') exit(0) else: print('* Starting new Session') self._start_session(dwarf_args.type) def _setup_main_menu(self): self.menu = self.menuBar() dwarf_menu = QMenu('Dwarf', self) if self._is_newer_dwarf: dwarf_menu.addAction('Update', self._update_dwarf) dwarf_menu.addAction('Close', self.session_manager.session.stop) self.menu.addMenu(dwarf_menu) session = self.session_manager.session if session is not None: session_menu = session.main_menu if isinstance(session_menu, list): for menu in session_menu: self.menu.addMenu(menu) else: self.menu.addMenu(session_menu) self.view_menu = QMenu('View', self) theme = QMenu('Theme', self.view_menu) theme.addAction('Black') theme.addAction('Dark') theme.addAction('Light') theme.triggered.connect(self._set_theme) self.view_menu.addMenu(theme) self.view_menu.addSeparator() self.menu.addMenu(self.view_menu) if self.dwarf_args.debug_script: debug_menu = QMenu('Debug', self) debug_menu.addAction('Reload core', self._menu_reload_core) self.menu.addMenu(debug_menu) about_menu = QMenu('About', self) about_menu.addAction('Dwarf on GitHub', self._menu_github) about_menu.addAction('Documention', self._menu_documentation) about_menu.addAction('Api', self._menu_api) about_menu.addAction('Slack', self._menu_slack) about_menu.addSeparator() about_menu.addAction('Info', self._show_about_dlg) self.menu.addMenu(about_menu) def _enable_update_menu(self): self._is_newer_dwarf = True def _update_dwarf(self): if self.welcome_window: self.welcome_window._update_dwarf() def _on_dwarf_updated(self): self.onRestart.emit() def remove_tmp_dir(self): if os.path.exists('.tmp'): shutil.rmtree('.tmp', ignore_errors=True) def _set_theme(self, qaction): if qaction: self.set_theme(qaction.text()) def _menu_reload_core(self): self.dwarf.load_script() def show_main_tab(self, name): # elem doesnt exists? create it if name not in self._ui_elems: self._create_ui_elem(name) index = 0 name = name.join(name.split()).lower() if name == 'memory': index = self.main_tabs.indexOf(self.memory_panel) elif name == 'ranges': index = self.main_tabs.indexOf(self.ranges_panel) elif name == 'search': index = self.main_tabs.indexOf(self.search_panel) elif name == 'modules': index = self.main_tabs.indexOf(self.modules_panel) elif name == 'disassembly': index = self.main_tabs.indexOf(self.asm_panel) elif name == 'trace': index = self.main_tabs.indexOf(self.trace_panel) elif name == 'data': index = self.main_tabs.indexOf(self.data_panel) elif name == 'emulator': index = self.main_tabs.indexOf(self.emulator_panel) elif name == 'java-trace': index = self.main_tabs.indexOf(self.java_trace_panel) elif name == 'jvm-inspector': index = self.main_tabs.indexOf(self.java_inspector_panel) elif name == 'jvm-explorer': index = self.main_tabs.indexOf(self.java_explorer_panel) elif name == 'smali': index = self.main_tabs.indexOf(self.smali_panel) self.main_tabs.setCurrentIndex(index) def jump_to_address(self, ptr, show_panel=True): if self.memory_panel is not None: if show_panel: self.show_main_tab('memory') self.memory_panel.read_memory(ptr) @pyqtSlot(name='mainMenuGitHub') def _menu_github(self): QDesktopServices.openUrl(QUrl('https://github.com/iGio90/Dwarf')) @pyqtSlot(name='mainMenuDocumentation') def _menu_api(self): QDesktopServices.openUrl(QUrl('https://igio90.github.io/Dwarf/')) @pyqtSlot(name='mainMenuApi') def _menu_documentation(self): QDesktopServices.openUrl(QUrl('https://igio90.github.io/Dwarf/api')) @pyqtSlot(name='mainMenuSlack') def _menu_slack(self): QDesktopServices.openUrl( QUrl('https://join.slack.com/t/resecret/shared_invite' '/enQtMzc1NTg4MzE3NjA1LTlkNzYxNTIwYTc2ZTYyOWY1MT' 'Q1NzBiN2ZhYjQwYmY0ZmRhODQ0NDE3NmRmZjFiMmE1MDYwN' 'WJlNDVjZDcwNGE')) def _show_about_dlg(self): about_dlg = AboutDialog(self) about_dlg.show() def _create_ui_elem(self, elem): if not isinstance(elem, str): return if elem not in self._ui_elems: self._ui_elems.append(elem) if elem == 'watchers': from ui.panel_watchers import WatchersPanel self.watchers_dwidget = QDockWidget('Watchers', self) self.watchers_panel = WatchersPanel(self) # dont respond to dblclick mem cant be shown # self.watchers_panel.onItemDoubleClicked.connect( # self._on_watcher_clicked) self.watchers_panel.onItemRemoved.connect( self._on_watcher_removeditem) self.watchers_panel.onItemAdded.connect(self._on_watcher_added) self.watchers_dwidget.setWidget(self.watchers_panel) self.watchers_dwidget.setObjectName('WatchersPanel') self.addDockWidget(Qt.LeftDockWidgetArea, self.watchers_dwidget) self.view_menu.addAction(self.watchers_dwidget.toggleViewAction()) elif elem == 'hooks': from ui.panel_hooks import HooksPanel self.hooks_dwiget = QDockWidget('Hooks', self) self.hooks_panel = HooksPanel(self) self.hooks_panel.onShowMemoryRequest.connect( self._on_watcher_clicked) self.hooks_panel.onHookRemoved.connect(self._on_hook_removed) self.hooks_dwiget.setWidget(self.hooks_panel) self.hooks_dwiget.setObjectName('HooksPanel') self.addDockWidget(Qt.LeftDockWidgetArea, self.hooks_dwiget) self.view_menu.addAction(self.hooks_dwiget.toggleViewAction()) elif elem == 'bookmarks': from ui.panel_bookmarks import BookmarksPanel self.bookmarks_dwiget = QDockWidget('Boomarks', self) self.bookmarks_panel = BookmarksPanel(self) self.bookmarks_dwiget.setWidget(self.bookmarks_panel) self.bookmarks_dwiget.setObjectName('BookmarksPanel') self.addDockWidget(Qt.LeftDockWidgetArea, self.bookmarks_dwiget) self.view_menu.addAction(self.bookmarks_dwiget.toggleViewAction()) elif elem == 'registers': from ui.panel_context import ContextPanel self.registers_dock = QDockWidget('Context', self) self.context_panel = ContextPanel(self) self.registers_dock.setWidget(self.context_panel) self.registers_dock.setObjectName('ContextsPanel') self.addDockWidget(Qt.RightDockWidgetArea, self.registers_dock) self.view_menu.addAction(self.registers_dock.toggleViewAction()) elif elem == 'memory': from ui.panel_memory import MemoryPanel self.memory_panel = MemoryPanel(self) self.memory_panel.onShowDisassembly.connect( self._disassemble_range) self.memory_panel.dataChanged.connect(self._on_memory_modified) self.memory_panel.statusChanged.connect(self.set_status_text) self.main_tabs.addTab(self.memory_panel, 'Memory') elif elem == 'jvm-explorer': from ui.panel_java_explorer import JavaExplorerPanel self.java_explorer_panel = JavaExplorerPanel(self) self.main_tabs.addTab(self.java_explorer_panel, 'JVM debugger') self.main_tabs.tabBar().moveTab( self.main_tabs.indexOf(self.java_explorer_panel), 1) elif elem == 'jvm-inspector': from ui.panel_java_inspector import JavaInspector self.java_inspector_panel = JavaInspector(self) self.main_tabs.addTab(self.java_inspector_panel, 'JVM inspector') elif elem == 'console': from ui.panel_console import ConsolePanel self.console_dock = QDockWidget('Console', self) self.console_panel = ConsolePanel(self) self.dwarf.onLogToConsole.connect(self._log_js_output) self.console_dock.setWidget(self.console_panel) self.console_dock.setObjectName('ConsolePanel') self.addDockWidget(Qt.BottomDockWidgetArea, self.console_dock) self.view_menu.addAction(self.console_dock.toggleViewAction()) elif elem == 'backtrace': from ui.panel_backtrace import BacktracePanel self.backtrace_dock = QDockWidget('Backtrace', self) self.backtrace_panel = BacktracePanel(self) self.backtrace_dock.setWidget(self.backtrace_panel) self.backtrace_dock.setObjectName('BacktracePanel') self.backtrace_panel.onShowMemoryRequest.connect( self._on_watcher_clicked) self.addDockWidget(Qt.RightDockWidgetArea, self.backtrace_dock) self.view_menu.addAction(self.backtrace_dock.toggleViewAction()) elif elem == 'threads': from ui.panel_contexts_list import ContextsListPanel self.threads_dock = QDockWidget('Threads', self) self.contexts_list_panel = ContextsListPanel(self) self.dwarf.onThreadResumed.connect( self.contexts_list_panel.resume_tid) self.contexts_list_panel.onItemDoubleClicked.connect( self._manually_apply_context) self.threads_dock.setWidget(self.contexts_list_panel) self.threads_dock.setObjectName('ThreadPanel') self.addDockWidget(Qt.RightDockWidgetArea, self.threads_dock) self.view_menu.addAction(self.threads_dock.toggleViewAction()) elif elem == 'modules': from ui.panel_modules import ModulesPanel self.modules_panel = ModulesPanel(self) self.modules_panel.onModuleSelected.connect( self._on_module_dblclicked) self.modules_panel.onModuleFuncSelected.connect( self._on_modulefunc_dblclicked) self.modules_panel.onAddHook.connect(self._on_addmodule_hook) self.modules_panel.onDumpBinary.connect(self._on_dumpmodule) self.main_tabs.addTab(self.modules_panel, 'Modules') elif elem == 'ranges': from ui.panel_ranges import RangesPanel self.ranges_panel = RangesPanel(self) self.ranges_panel.onItemDoubleClicked.connect( self._range_dblclicked) self.ranges_panel.onDumpBinary.connect(self._on_dumpmodule) # connect to watcherpanel func self.ranges_panel.onAddWatcher.connect( self.watchers_panel.do_addwatcher_dlg) self.main_tabs.addTab(self.ranges_panel, 'Ranges') elif elem == 'search': from ui.panel_search import SearchPanel self.search_panel = SearchPanel(self) self.search_panel.onShowMemoryRequest.connect( self._on_watcher_clicked) self.main_tabs.addTab(self.search_panel, 'Search') elif elem == 'data': from ui.panel_data import DataPanel self.data_panel = DataPanel(self) self.main_tabs.addTab(self.data_panel, 'Data') elif elem == 'trace': from ui.panel_trace import TracePanel self.trace_panel = TracePanel(self) self.main_tabs.addTab(self.trace_panel, 'Trace') elif elem == 'disassembly': from ui.disasm_view import DisassemblyView self.asm_panel = DisassemblyView(self) self.asm_panel.onShowMemoryRequest.connect(self._on_disasm_showmem) self.main_tabs.addTab(self.asm_panel, 'Disassembly') self.main_tabs.tabBar().moveTab( self.main_tabs.indexOf(self.asm_panel), 1) elif elem == 'emulator': from ui.panel_emulator import EmulatorPanel self.emulator_panel = EmulatorPanel(self) self.main_tabs.addTab(self.emulator_panel, 'Emulator') elif elem == 'java-trace': from ui.panel_java_trace import JavaTracePanel self.java_trace_panel = JavaTracePanel(self) self.main_tabs.addTab(self.java_trace_panel, 'JVM tracer') elif elem == 'smali': from ui.panel_smali import SmaliPanel self.smali_panel = SmaliPanel() self.main_tabs.addTab(self.smali_panel, 'Smali') else: print('no handler for elem: ' + elem) # TODO: remove add @2x for item in self.findChildren(QDockWidget): if item: if 'darwin' in sys.platform: item.setStyleSheet( 'QDockWidget::title { padding-left:-30px; } QDockWidget::close-button, QDockWidget::float-button { width: 10px; height:10px }' ) def set_theme(self, theme): if theme: theme = theme.replace(os.pardir, '').replace('.', '') theme = theme.join(theme.split()).lower() theme_style = 'assets/' + theme + '_style.qss' if not os.path.exists(utils.resource_path(theme_style)): return self.prefs.put('dwarf_ui_theme', theme) try: _app = QApplication.instance() with open(theme_style) as stylesheet: _app.setStyleSheet(_app.styleSheet() + '\n' + stylesheet.read()) except Exception as e: pass # err = self.dwarf.spawn(dwarf_args.package, dwarf_args.script) def set_status_text(self, txt): self.statusbar.showMessage(txt) # ************************************************************************ # **************************** Properties ******************************** # ************************************************************************ @property def disassembly(self): return self.asm_panel @property def backtrace(self): return self.backtrace_panel @property def console(self): return self.console_panel @property def context(self): return self.context_panel @property def threads(self): return self.contexts_list_panel @property def emulator(self): return self.emulator_panel @property def ftrace(self): return self.ftrace_panel @property def hooks(self): return self.hooks_panel @property def java_inspector(self): return self.java_inspector_panel @property def java_explorer(self): return self.java_explorer_panel @property def memory(self): return self.memory_panel @property def modules(self): return self.memory_panel @property def ranges(self): return self.ranges_panel @property def trace(self): return self.trace_panel @property def watchers(self): return self.watchers_panel @property def dwarf(self): if self.session_manager.session is not None: return self.session_manager.session.dwarf else: return None # ************************************************************************ # **************************** Handlers ********************************** # ************************************************************************ # session handlers def _start_session(self, session_type, session_data=None): if self.welcome_window is not None: self.welcome_window.close() self.session_manager.create_session(session_type, session_data=session_data) def _restore_session(self, session_data): if 'session' in session_data: session_type = session_data['session'] self._start_session(session_type, session_data=session_data) def session_created(self): # session init done create ui for it session = self.session_manager.session self._setup_main_menu() for ui_elem in session.session_ui_sections: ui_elem = ui_elem.join(ui_elem.split()).lower() self._create_ui_elem(ui_elem) self.dwarf.onAttached.connect(self._on_attached) self.dwarf.onScriptLoaded.connect(self._on_script_loaded) # hookup self.dwarf.onSetRanges.connect(self._on_setranges) self.dwarf.onSetModules.connect(self._on_setmodules) self.dwarf.onAddNativeHook.connect(self._on_add_hook) self.dwarf.onApplyContext.connect(self._apply_context) self.dwarf.onThreadResumed.connect(self.on_tid_resumed) self.dwarf.onTraceData.connect(self._on_tracer_data) self.dwarf.onSetData.connect(self._on_set_data) self.session_manager.start_session(self.dwarf_args) q_settings = QSettings("dwarf_window_pos.ini", QSettings.IniFormat) ui_state = q_settings.value('dwarf_ui_state') if ui_state: self.restoreGeometry(ui_state) window_state = q_settings.value('dwarf_ui_window', self.saveState()) if window_state: self.restoreState(window_state) self.showMaximized() def session_stopped(self): self.remove_tmp_dir() self.menu.clear() self.main_tabs.clear() # actually we need to kill this. needs a refactor if self.java_trace_panel is not None: self.java_trace_panel = None for elem in self._ui_elems: if elem == 'watchers': self.watchers_panel.clear_list() self.watchers_panel.close() self.watchers_panel = None self.removeDockWidget(self.watchers_dwidget) self.watchers_dwidget = None elif elem == 'hooks': self.hooks_panel.close() self.hooks_panel = None self.removeDockWidget(self.hooks_dwiget) self.hooks_dwiget = None elif elem == 'registers': self.context_panel.close() self.context_panel = None self.removeDockWidget(self.registers_dock) self.registers_dock = None elif elem == 'memory': self.memory_panel.close() self.memory_panel = None self.main_tabs.removeTab(0) # self.main_tabs elif elem == 'jvm-explorer': self.java_explorer_panel.close() self.java_explorer_panel = None self.removeDockWidget(self.watchers_dwidget) elif elem == 'console': self.console_panel.close() self.console_panel = None self.removeDockWidget(self.console_dock) self.console_dock = None elif elem == 'backtrace': self.backtrace_panel.close() self.backtrace_panel = None self.removeDockWidget(self.backtrace_dock) elif elem == 'threads': self.contexts_list_panel.close() self.contexts_list_panel = None self.removeDockWidget(self.threads_dock) self.threads_dock = None elif elem == 'bookmarks': self.bookmarks_panel.close() self.bookmarks_panel = None self.removeDockWidget(self.bookmarks_dwiget) self.bookmarks_dwiget = None def session_closed(self): self._ui_elems = [] self.hide() if self.welcome_window is not None: self.welcome_window.exec() # close if it was a commandline session if self.welcome_window is None: if self.dwarf_args.package: self.close() # ui handler def closeEvent(self, event): """ Window closed save stuff or whatever at exit detaches dwarf """ # save windowstuff q_settings = QSettings("dwarf_window_pos.ini", QSettings.IniFormat) q_settings.setValue('dwarf_ui_state', self.saveGeometry()) q_settings.setValue('dwarf_ui_window', self.saveState()) if self.dwarf: self.dwarf.detach() super().closeEvent(event) def _on_watcher_clicked(self, ptr): """ Address in Watcher/Hookpanel was clicked show Memory """ if '.' in ptr: # java_hook file_path = ptr.replace('.', os.path.sep) if os.path.exists('.tmp/smali/' + file_path + '.smali'): if self.smali_panel is None: self._create_ui_elem('smali') self.smali_panel.set_file('.tmp/smali/' + file_path + '.smali') self.show_main_tab('smali') else: self.memory_panel.read_memory(ptr=ptr) self.show_main_tab('memory') def _on_disasm_showmem(self, ptr, length): """ Address in Disasm was clicked adds temphighlight for bytes from current instruction """ self.memory_panel.read_memory(ptr) self.memory_panel.add_highlight( HighLight('attention', utils.parse_ptr(ptr), length)) self.show_main_tab('memory') def _on_watcher_added(self, ptr): """ Watcher Entry was added """ try: # set highlight self.memory_panel.add_highlight( HighLight('watcher', ptr, self.dwarf.pointer_size)) except HighlightExistsError: pass def _on_watcher_removeditem(self, ptr): """ Watcher Entry was removed remove highlight too """ self.memory_panel.remove_highlight(ptr) def _on_module_dblclicked(self, data): """ Module in ModulePanel was doubleclicked """ addr, size = data addr = utils.parse_ptr(addr) size = int(size, 10) self.memory_panel.read_memory(ptr=addr, length=size) self.show_main_tab('Memory') def _on_modulefunc_dblclicked(self, ptr): """ Function in ModulePanel was doubleclicked """ ptr = utils.parse_ptr(ptr) self.memory_panel.read_memory(ptr=ptr) self.show_main_tab('Memory') def _on_dumpmodule(self, data): """ DumpBinary MenuItem in ModulePanel was selected """ ptr, size = data ptr = utils.parse_ptr(ptr) size = int(size, 10) self.dwarf.dump_memory(ptr=ptr, length=size) def _disassemble_range(self, mem_range): """ Disassemble MenuItem in Hexview was selected """ if mem_range: if self.asm_panel is None: self._create_ui_elem('disassembly') if mem_range: self.asm_panel.disassemble(mem_range) self.show_main_tab('disassembly') def _range_dblclicked(self, ptr): """ Range in RangesPanel was doubleclicked """ ptr = utils.parse_ptr(ptr) self.memory_panel.read_memory(ptr=ptr) self.show_main_tab('Memory') # dwarf handlers def _log_js_output(self, output): if self.console_panel is not None: self.console_panel.get_js_console().log(output) def _on_setranges(self, ranges): """ Dwarf wants to set Ranges only hooked up to switch tab or create ui its connected in panel after creation """ if self.ranges_panel is None: self._create_ui_elem('ranges') # forward only now to panel it connects after creation self.ranges_panel.set_ranges(ranges) # once we got ranges in place from our target we can create the search panel as well if self.search_panel is None: self._create_ui_elem('search') self.search_panel.set_ranges(ranges) if self.ranges_panel is not None: self.show_main_tab('ranges') def _on_setmodules(self, modules): """ Dwarf wants to set Modules only hooked up to switch tab or create ui its connected in panel after creation """ if self.modules_panel is None: self._create_ui_elem('modules') self.modules_panel.set_modules(modules) if self.modules_panel is not None: self.show_main_tab('modules') def _manually_apply_context(self, context): """ perform additional operation if the context has been manually applied from the context list """ self._apply_context(context, manual=True) def _apply_context(self, context, manual=False): # update current context tid # this should be on top as any further api from js needs to be executed on that thread if manual or self.dwarf.context_tid == 0: self.dwarf.context_tid = context['tid'] if 'context' in context: if not manual: self.threads.add_context(context) is_java = context['is_java'] if is_java: if self.java_explorer_panel is None: self._create_ui_elem('jvm-explorer') self.context_panel.set_context(context['ptr'], 1, context['context']) self.java_explorer_panel.set_handle_arg(-1) self.show_main_tab('jvm-explorer') else: self.context_panel.set_context(context['ptr'], 0, context['context']) if 'pc' in context['context']: should_disasm = self.asm_panel is not None and self.asm_panel._range is not None \ and not self.asm_panel._running_disasm if should_disasm: if self.asm_panel._range is not None: off = int(context['context']['pc']['value'], 16) if self.asm_panel._range.start_address == off: should_disasm = False # we set this to false as well to prevent disasm. be careful to use 'manual' later manual = False if should_disasm or manual: self.jump_to_address(int( context['context']['pc']['value'], 16), show_panel=False) self._disassemble_range(self.memory_panel.range) self.show_main_tab('disassembly') if 'backtrace' in context: self.backtrace_panel.set_backtrace(context['backtrace']) def _on_add_hook(self, hook): try: # set highlight ptr = hook.get_ptr() ptr = utils.parse_ptr(ptr) self.memory_panel.add_highlight( HighLight('hook', ptr, self.dwarf.pointer_size)) except HighlightExistsError: pass def _on_hook_removed(self, ptr): ptr = utils.parse_ptr(ptr) self.memory_panel.remove_highlight(ptr) def _on_addmodule_hook(self, data): ptr, name = data self.dwarf.hook_native(ptr, own_input=name) def on_tid_resumed(self, tid): if self.dwarf: if self.dwarf.context_tid == tid: # clear backtrace if 'backtrace' in self._ui_elems: if self.backtrace_panel is not None: self.backtrace_panel.clear() # remove thread if 'threads' in self._ui_elems: if self.contexts_list_panel is not None: self.contexts_list_panel.resume_tid(tid) # clear registers if 'registers' in self._ui_elems: if self.context_panel is not None: self.context_panel.clear() # clear jvm explorer if 'jvm-explorer' in self._ui_elems: if self.java_explorer_panel is not None: self.java_explorer_panel.clear_panel() # invalidate dwarf context tid self.dwarf.context_tid = 0 def _on_tracer_data(self, data): if not data: return if self.trace_panel is None: self._create_ui_elem('trace') if self.trace_panel is not None: self.show_main_tab('Trace') self.trace_panel.start() trace_events_parts = data[1].split(',') while trace_events_parts: trace_event = TraceEvent(trace_events_parts.pop(0), trace_events_parts.pop(0), trace_events_parts.pop(0), trace_events_parts.pop(0)) self.trace_panel.event_queue.append(trace_event) def _on_set_data(self, data): if not isinstance(data, list): return if self.data_panel is None: self._create_ui_elem('data') if self.data_panel is not None: self.show_main_tab('Data') self.data_panel.append_data(data[0], data[1], data[2]) def show_progress(self, text): self.progressbar.setVisible(True) self.set_status_text(text) def hide_progress(self): self.progressbar.setVisible(False) self.set_status_text('') def _on_attached(self, data): self.setWindowTitle('Dwarf - Attached to %s (%s)' % (data[1], data[0])) def _on_script_loaded(self): # restore the loaded session if any self.session_manager.restore_session() def _on_memory_modified(self, pos, length): data_pos = self.memory_panel.base + pos data = self.memory_panel.data[pos:pos + length] data = [data[0]] # todo: strange js part if self.dwarf.dwarf_api('writeBytes', [data_pos, data]): pass else: utils.show_message_box('Failed to write Memory') def on_add_bookmark(self, ptr): """ provide ptr as int """ if self.bookmarks_panel is not None: self.bookmarks_panel._create_bookmark(ptr=hex(ptr))
class MainWindow(QMainWindow): siteLoaded = pyqtSignal(object) def __init__(self): QMainWindow.__init__(self) self.site = None self.editor = "" self.install_directory = os.getcwd() self.content_after_animation = "" self.default_path = "" self.method_after_animation = "" Generator.install_directory = self.install_directory self.initUndoRedo() self.initGui() self.readSettings() self.loadPlugins() if self.default_path: if self.loadProject(self.default_path + "/Site.qml"): # if site has never been generated (after install) # then generate the site site = QDir(Generator.sitesPath() + "/" + self.site.title) if site.exists(): gen = Generator() gen.generateSite(self, self.site) self.dashboard.setExpanded(True) self.showDashboard() self.statusBar().showMessage("Ready") def actualThemeChanged(self, themename): self.theme_settings_button.setVisible(False) for name in Plugins.themePluginNames(): tei = Plugins.getThemePlugin(name) if tei: if tei.theme_name == themename: self.theme_settings_button.setVisible(True) break def loadProject(self, filename): if self.reloadProject(filename): # create temp dir for undo redo tempPath = self.site.source_path[self.site.source_path.rfind("/") + 1:] temp = QDir(QDir.tempPath() + "/FlatSiteBuilder") temp.mkdir(tempPath) temp.cd(tempPath) temp.mkdir("pages") temp.mkdir("posts") return True else: return False def initUndoRedo(self): self.undoStack = QUndoStack() temp = QDir(QDir.tempPath() + "/FlatSiteBuilder") if temp.exists(): temp.removeRecursively() temp.setPath(QDir.tempPath()) temp.mkdir("FlatSiteBuilder") def initGui(self): self.installEventFilter(self) self.dashboard = Expander("Dashboard", ":/images/dashboard_normal.png", ":/images/dashboard_hover.png", ":/images/dashboard_selected.png") self.content = Expander("Content", ":/images/pages_normal.png", ":/images/pages_hover.png", ":/images/pages_selected.png") self.appearance = Expander("Appearance", ":/images/appearance_normal.png", ":/images/appearance_hover.png", ":/images/appearance_selected.png") self.settings = Expander("Settings", ":/images/settings_normal.png", ":/images/settings_hover.png", ":/images/settings_selected.png") self.setWindowTitle(QCoreApplication.applicationName() + " " + QCoreApplication.applicationVersion()) vbox = QVBoxLayout() vbox.addWidget(self.dashboard) vbox.addWidget(self.content) vbox.addWidget(self.appearance) vbox.addWidget(self.settings) vbox.addStretch() content_box = QVBoxLayout() pages_button = HyperLink("Pages") posts_button = HyperLink("Posts") content_box.addWidget(pages_button) content_box.addWidget(posts_button) self.content.addLayout(content_box) app_box = QVBoxLayout() themes_button = HyperLink("Themes") menus_button = HyperLink("Menus") self.theme_settings_button = HyperLink("Theme Settings") self.theme_settings_button.setVisible(False) app_box.addWidget(menus_button) app_box.addWidget(themes_button) app_box.addWidget(self.theme_settings_button) self.appearance.addLayout(app_box) scroll_content = QWidget() scroll_content.setLayout(vbox) scroll = QScrollArea() scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll.setWidget(scroll_content) scroll.setWidgetResizable(True) scroll.setMaximumWidth(200) scroll.setMinimumWidth(200) self.navigationdock = QDockWidget("Navigation", self) self.navigationdock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.navigationdock.setWidget(scroll) self.navigationdock.setObjectName("Navigation") self.addDockWidget(Qt.LeftDockWidgetArea, self.navigationdock) self.showDock = FlatButton(":/images/edit_normal.png", ":/images/edit_hover.png") self.showDock.setToolTip("Show Navigation") self.statusBar().addPermanentWidget(self.showDock) self.dashboard.expanded.connect(self.dashboardExpanded) self.dashboard.clicked.connect(self.showDashboard) self.content.expanded.connect(self.contentExpanded) self.content.clicked.connect(self.showPages) self.appearance.expanded.connect(self.appearanceExpanded) self.appearance.clicked.connect(self.showMenus) self.settings.expanded.connect(self.settingsExpanded) self.settings.clicked.connect(self.showSettings) menus_button.clicked.connect(self.showMenus) pages_button.clicked.connect(self.showPages) posts_button.clicked.connect(self.showPosts) themes_button.clicked.connect(self.showThemes) self.theme_settings_button.clicked.connect(self.showThemesSettings) self.showDock.clicked.connect(self.showMenu) self.navigationdock.visibilityChanged.connect( self.dockVisibilityChanged) def showDashboard(self): if self.editor: self.method_after_animation = "showDashboard" self.editor.closeEditor() return db = Dashboard(self.site, self.default_path) db.loadSite.connect(self.loadProject) db.previewSite.connect(self.previewSite) db.publishSite.connect(self.publishSite) db.createSite.connect(self.createSite) db.buildSite.connect(self.buildSite) self.siteLoaded.connect(db.siteLoaded) self.setCentralWidget(db) def setCentralWidget(self, widget): # do not delete plugin editors old_widget = self.takeCentralWidget() if not isinstance(old_widget, PublisherInterface) and not isinstance( old_widget, ThemeEditorInterface): del old_widget super().setCentralWidget(widget) widget.show() def closeEvent(self, event): self.writeSettings() event.accept() def writeSettings(self): settings = QSettings(QSettings.IniFormat, QSettings.UserScope, QCoreApplication.organizationName(), QCoreApplication.applicationName()) settings.setValue("geometry", self.saveGeometry()) settings.setValue("state", self.saveState()) if self.site: settings.setValue("lastSite", self.site.source_path) def readSettings(self): settings = QSettings(QSettings.IniFormat, QSettings.UserScope, QCoreApplication.organizationName(), QCoreApplication.applicationName()) geometry = settings.value("geometry", QByteArray()) if geometry.isEmpty(): availableGeometry = QApplication.desktop().availableGeometry(self) self.resize(availableGeometry.width() / 3, availableGeometry.height() / 2) self.move(int(((availableGeometry.width() - self.width()) / 2)), int((availableGeometry.height() - self.height()) / 2)) else: self.restoreGeometry(geometry) self.restoreState(settings.value("state")) self.default_path = settings.value("lastSite") def reloadProject(self, filename): engine = QQmlEngine() component = QQmlComponent(engine) component.loadUrl(QUrl(filename)) self.site = component.create() if self.site is not None: self.site.setFilename(filename) self.site.setWindow(self) else: for error in component.errors(): print(error.toString()) return False self.site.loadMenus() self.site.loadPages() self.site.loadPosts() self.theme_settings_button.setVisible(False) Plugins.setActualThemeEditorPlugin("") for key in Plugins.themePluginNames(): tei = Plugins.getThemePlugin(key) if tei: if tei.theme_name == self.site.theme: Plugins.setActualThemeEditorPlugin(tei.class_name) self.theme_settings_button.setVisible(True) break #if not self.site.publisher: # if len(Plugins.publishPluginNames()) > 0: # self.site.publisher = Plugins.publishPluginNames[0] Plugins.setActualPublishPlugin(self.site.publisher) self.siteLoaded.emit(self.site) return True def dashboardExpanded(self, value): if value: self.content.setExpanded(False) self.appearance.setExpanded(False) self.settings.setExpanded(False) def contentExpanded(self, value): if value: self.dashboard.setExpanded(False) self.appearance.setExpanded(False) self.settings.setExpanded(False) def appearanceExpanded(self, value): if value: self.dashboard.setExpanded(False) self.content.setExpanded(False) self.settings.setExpanded(False) def settingsExpanded(self, value): if value: self.dashboard.setExpanded(False) self.content.setExpanded(False) self.appearance.setExpanded(False) def showMenus(self): if self.editor: self.method_after_animation = "showMenus" self.editor.closeEditor() return edit = MenuList(self, self.site) edit.editContent.connect(self.editMenu) self.setCentralWidget(edit) def showPages(self): if self.editor: self.method_after_animation = "showPages" self.editor.closeEditor() return list = ContentList(self.site, ContentType.PAGE) list.editContent.connect(self.editContent) self.setCentralWidget(list) def showPosts(self): if self.editor: self.method_after_animation = "showPosts" self.editor.closeEditor() return list = ContentList(self.site, ContentType.POST) list.editContent.connect(self.editContent) self.setCentralWidget(list) def showThemes(self): if self.editor: self.method_after_animation = "showThemes" self.editor.closeEditor() return tc = ThemeChooser(self, self.site) self.setCentralWidget(tc) def showThemesSettings(self): tei = Plugins.getThemePlugin(Plugins.actualThemeEditorPlugin()) if tei: if self.editor: self.method_after_animation = "showThemesSettings" self.editor.closeEditor() return path = self.site.source_path tei.setWindow(self) tei.setSourcePath(path) self.setCentralWidget(tei) else: self.statusBar().showMessage("Unable to load plugin " + Plugins.actualThemeEditorPlugin()) def showSettings(self): if self.editor: self.method_after_animation = "showSettings" self.editor.closeEditor() return sse = SiteSettingsEditor(self, self.site) self.setCentralWidget(sse) def showMenu(self): self.navigationdock.setVisible(True) def dockVisibilityChanged(self, visible): self.showDock.setVisible(not visible) def previewSite(self, content=None): if self.editor and content: self.content_after_animation = content self.editor.closeEditor() return dir = os.path.join(self.install_directory, "sites") path = os.path.join(dir, self.site.title) if not content: if len(self.site.pages) > 0: content = self.site.pages[0] for c in self.site.pages: if c.url() == "index.html": content = c break elif len(self.site.posts) > 0: content = self.site.posts()[0] if content: file = content.url() self.webView = QWebEngineView() self.webView.loadFinished.connect(self.webViewLoadFinished) url = pathlib.Path(os.path.join(path, file)).as_uri() self.webView.setUrl(QUrl(url)) self.setCursor(Qt.WaitCursor) else: self.statusBar().showMessage( "Site has no pages or posts to preview.") def webViewLoadFinished(self, success): if success: self.setCentralWidget(self.webView) self.webView.loadFinished.disconnect(self.webViewLoadFinished) else: QMessageBox.warning(self, "FlatSiteBuilder", "Unable to open webpage.") self.setCursor(Qt.ArrowCursor) def publishSite(self): pluginName = Plugins.actualPublishPlugin() pi = Plugins.getPublishPlugin(pluginName) if pi: self.setCentralWidget(pi) pi.setSitePath(self.site.deploy_path) def createSite(self): wiz = SiteWizard(self.install_directory, parent=self) wiz.loadSite.connect(self.loadProject) wiz.buildSite.connect(self.buildSite) wiz.show() def buildSite(self): self.site.loadMenus() if len(self.site.pages) == 0 and len(self.site.posts) == 0: self.statusBar().showMessage( "Site has no pages or posts to build.") else: gen = Generator() gen.generateSite(self, self.site) self.statusBar().showMessage(self.site.title + " has been generated") def editMenu(self, item): menu = item.data(Qt.UserRole) me = MenuEditor(self, menu, self.site) self.editor = me list = self.centralWidget() if list: list.registerMenuEditor(me) list.editedItemChanged.connect(self.editedItemChanged) self.editor.closes.connect(self.editorClosed) self.editor.contentChanged.connect(self.menuChanged) self.animate(item) def editContent(self, item): content = item.data(Qt.UserRole) self.editor = ContentEditor(self, self.site, content) self.siteLoaded.connect(self.editor.siteLoaded) self.editor.closes.connect(self.editorClosed) self.editor.preview.connect(self.previewSite) self.animate(item) def animate(self, item): panel = self.centralWidget() self.list = item.tableWidget() self.row = item.row() # create a cell widget to get the right position in the table self.cellWidget = QWidget() self.cellWidget.setMaximumHeight(0) self.list.setCellWidget(self.row, 1, self.cellWidget) pos = self.cellWidget.mapTo(panel, QPoint(0, 0)) self.editor.setParent(panel) self.editor.move(pos) self.editor.resize(self.cellWidget.size()) self.editor.show() self.animation = QPropertyAnimation(self.editor, "geometry".encode("utf-8")) self.animation.setDuration(300) self.animation.setStartValue( QRect(pos.x(), pos.y(), self.cellWidget.size().width(), self.cellWidget.size().height())) self.animation.setEndValue( QRect(0, 0, panel.size().width(), panel.size().height())) self.animation.start() def eventFilter(self, watched, event): if watched == self and event.type() == QEvent.Resize and self.editor: w = self.centralWidget() if w: self.editor.resize(w.size()) return False def editorClosed(self): pos = self.cellWidget.mapTo(self.centralWidget(), QPoint(0, 0)) # correct end values in case of resizing the window self.animation.setStartValue( QRect(pos.x(), pos.y(), self.cellWidget.size().width(), self.cellWidget.size().height())) self.animation.finished.connect(self.animationFineshedZoomOut) self.animation.setDirection(QAbstractAnimation.Backward) self.animation.start() def animationFineshedZoomOut(self): self.list.removeCellWidget(self.row, 1) del self.animation # in the case self.editor was a MenuEditor, we have to unregister it in the MenuList # should be refactored some day :-) list = self.centralWidget() if list is MenuEditor: list.unregisterMenuEditor() del self.editor self.editor = None if self.method_after_animation == "showDashboard": self.showDashboard() self.method_after_animation = "" elif self.method_after_animation == "showSettings": self.showSettings() elif self.method_after_animation == "showThemesSettings": self.showThemesSettings() elif self.method_after_animation == "showThemes": self.showThemes() elif self.method_after_animation == "showMenus": self.showMenus() elif self.method_after_animation == "showPages": self.showPages() elif self.method_after_animation == "showPosts": self.showPosts() if self.content_after_animation: self.previewSite(self.content_after_animation) self.content_after_animation = None def contentChanged(self, content): self.list.item(self.row, 1).setText(content.title()) self.list.item(self.row, 2).setText(content.source()) self.list.item(self.row, 3).setText(content.layout()) self.list.item(self.row, 4).setText(content.author()) self.list.item(self.row, 5).setText(content.date().toString("dd.MM.yyyy")) def menuChanged(self, menu): self.list.item(self.row, 1).setText(menu.name()) def editedItemChanged(self, item): # this will happen, if the MenuList.reloadMenu() has been called by the undo.command self.list = item.tableWidget() self.row = item.row() self.cellWidget = QWidget() self.list.setCellWidget(self.row, 1, self.cellWidget) def loadPlugins(self): # check if we are running in a frozen environment (pyinstaller --onefile) if getattr(sys, "frozen", False): bundle_dir = sys._MEIPASS # if we are running in a onefile environment, then copy all plugin to /tmp/... if bundle_dir != os.getcwd(): os.mkdir(os.path.join(bundle_dir, "plugins")) for root, dirs, files in os.walk( os.path.join(os.getcwd(), "plugins")): for file in files: shutil.copy(os.path.join(root, file), os.path.join(bundle_dir, "plugins")) print("copy", file) break # do not copy __pycache__ else: bundle_dir = os.getcwd( ) # os.path.dirname(os.path.abspath(__file__)) plugins_dir = os.path.join(bundle_dir, "plugins") for root, dirs, files in os.walk(plugins_dir): for file in files: modulename, ext = os.path.splitext(file) if ext == ".py": module = import_module("plugins." + modulename) for name, klass in inspect.getmembers( module, inspect.isclass): if klass.__module__ == "plugins." + modulename: instance = klass() if isinstance(instance, ElementEditorInterface): Plugins.addElementPlugin(name, instance) instance.registerContenType() elif isinstance(instance, ThemeEditorInterface): Plugins.addThemePlugin(name, instance) elif isinstance(instance, PublisherInterface): Plugins.addPublishPlugin(name, instance) break # not to list __pycache__
def _create_ui_elem(self, elem): if not isinstance(elem, str): return if elem not in self._ui_elems: self._ui_elems.append(elem) if elem == 'watchers': from ui.panel_watchers import WatchersPanel self.watchers_dwidget = QDockWidget('Watchers', self) self.watchers_panel = WatchersPanel(self) # dont respond to dblclick mem cant be shown # self.watchers_panel.onItemDoubleClicked.connect( # self._on_watcher_clicked) self.watchers_panel.onItemRemoved.connect( self._on_watcher_removeditem) self.watchers_panel.onItemAdded.connect(self._on_watcher_added) self.watchers_dwidget.setWidget(self.watchers_panel) self.watchers_dwidget.setObjectName('WatchersPanel') self.addDockWidget(Qt.LeftDockWidgetArea, self.watchers_dwidget) self.view_menu.addAction(self.watchers_dwidget.toggleViewAction()) elif elem == 'hooks': from ui.panel_hooks import HooksPanel self.hooks_dwiget = QDockWidget('Hooks', self) self.hooks_panel = HooksPanel(self) self.hooks_panel.onShowMemoryRequest.connect( self._on_watcher_clicked) self.hooks_panel.onHookRemoved.connect(self._on_hook_removed) self.hooks_dwiget.setWidget(self.hooks_panel) self.hooks_dwiget.setObjectName('HooksPanel') self.addDockWidget(Qt.LeftDockWidgetArea, self.hooks_dwiget) self.view_menu.addAction(self.hooks_dwiget.toggleViewAction()) elif elem == 'bookmarks': from ui.panel_bookmarks import BookmarksPanel self.bookmarks_dwiget = QDockWidget('Boomarks', self) self.bookmarks_panel = BookmarksPanel(self) self.bookmarks_dwiget.setWidget(self.bookmarks_panel) self.bookmarks_dwiget.setObjectName('BookmarksPanel') self.addDockWidget(Qt.LeftDockWidgetArea, self.bookmarks_dwiget) self.view_menu.addAction(self.bookmarks_dwiget.toggleViewAction()) elif elem == 'registers': from ui.panel_context import ContextPanel self.registers_dock = QDockWidget('Context', self) self.context_panel = ContextPanel(self) self.registers_dock.setWidget(self.context_panel) self.registers_dock.setObjectName('ContextsPanel') self.addDockWidget(Qt.RightDockWidgetArea, self.registers_dock) self.view_menu.addAction(self.registers_dock.toggleViewAction()) elif elem == 'memory': from ui.panel_memory import MemoryPanel self.memory_panel = MemoryPanel(self) self.memory_panel.onShowDisassembly.connect( self._disassemble_range) self.memory_panel.dataChanged.connect(self._on_memory_modified) self.memory_panel.statusChanged.connect(self.set_status_text) self.main_tabs.addTab(self.memory_panel, 'Memory') elif elem == 'jvm-explorer': from ui.panel_java_explorer import JavaExplorerPanel self.java_explorer_panel = JavaExplorerPanel(self) self.main_tabs.addTab(self.java_explorer_panel, 'JVM debugger') self.main_tabs.tabBar().moveTab( self.main_tabs.indexOf(self.java_explorer_panel), 1) elif elem == 'jvm-inspector': from ui.panel_java_inspector import JavaInspector self.java_inspector_panel = JavaInspector(self) self.main_tabs.addTab(self.java_inspector_panel, 'JVM inspector') elif elem == 'console': from ui.panel_console import ConsolePanel self.console_dock = QDockWidget('Console', self) self.console_panel = ConsolePanel(self) self.dwarf.onLogToConsole.connect(self._log_js_output) self.console_dock.setWidget(self.console_panel) self.console_dock.setObjectName('ConsolePanel') self.addDockWidget(Qt.BottomDockWidgetArea, self.console_dock) self.view_menu.addAction(self.console_dock.toggleViewAction()) elif elem == 'backtrace': from ui.panel_backtrace import BacktracePanel self.backtrace_dock = QDockWidget('Backtrace', self) self.backtrace_panel = BacktracePanel(self) self.backtrace_dock.setWidget(self.backtrace_panel) self.backtrace_dock.setObjectName('BacktracePanel') self.backtrace_panel.onShowMemoryRequest.connect( self._on_watcher_clicked) self.addDockWidget(Qt.RightDockWidgetArea, self.backtrace_dock) self.view_menu.addAction(self.backtrace_dock.toggleViewAction()) elif elem == 'threads': from ui.panel_contexts_list import ContextsListPanel self.threads_dock = QDockWidget('Threads', self) self.contexts_list_panel = ContextsListPanel(self) self.dwarf.onThreadResumed.connect( self.contexts_list_panel.resume_tid) self.contexts_list_panel.onItemDoubleClicked.connect( self._manually_apply_context) self.threads_dock.setWidget(self.contexts_list_panel) self.threads_dock.setObjectName('ThreadPanel') self.addDockWidget(Qt.RightDockWidgetArea, self.threads_dock) self.view_menu.addAction(self.threads_dock.toggleViewAction()) elif elem == 'modules': from ui.panel_modules import ModulesPanel self.modules_panel = ModulesPanel(self) self.modules_panel.onModuleSelected.connect( self._on_module_dblclicked) self.modules_panel.onModuleFuncSelected.connect( self._on_modulefunc_dblclicked) self.modules_panel.onAddHook.connect(self._on_addmodule_hook) self.modules_panel.onDumpBinary.connect(self._on_dumpmodule) self.main_tabs.addTab(self.modules_panel, 'Modules') elif elem == 'ranges': from ui.panel_ranges import RangesPanel self.ranges_panel = RangesPanel(self) self.ranges_panel.onItemDoubleClicked.connect( self._range_dblclicked) self.ranges_panel.onDumpBinary.connect(self._on_dumpmodule) # connect to watcherpanel func self.ranges_panel.onAddWatcher.connect( self.watchers_panel.do_addwatcher_dlg) self.main_tabs.addTab(self.ranges_panel, 'Ranges') elif elem == 'search': from ui.panel_search import SearchPanel self.search_panel = SearchPanel(self) self.search_panel.onShowMemoryRequest.connect( self._on_watcher_clicked) self.main_tabs.addTab(self.search_panel, 'Search') elif elem == 'data': from ui.panel_data import DataPanel self.data_panel = DataPanel(self) self.main_tabs.addTab(self.data_panel, 'Data') elif elem == 'trace': from ui.panel_trace import TracePanel self.trace_panel = TracePanel(self) self.main_tabs.addTab(self.trace_panel, 'Trace') elif elem == 'disassembly': from ui.disasm_view import DisassemblyView self.asm_panel = DisassemblyView(self) self.asm_panel.onShowMemoryRequest.connect(self._on_disasm_showmem) self.main_tabs.addTab(self.asm_panel, 'Disassembly') self.main_tabs.tabBar().moveTab( self.main_tabs.indexOf(self.asm_panel), 1) elif elem == 'emulator': from ui.panel_emulator import EmulatorPanel self.emulator_panel = EmulatorPanel(self) self.main_tabs.addTab(self.emulator_panel, 'Emulator') elif elem == 'java-trace': from ui.panel_java_trace import JavaTracePanel self.java_trace_panel = JavaTracePanel(self) self.main_tabs.addTab(self.java_trace_panel, 'JVM tracer') elif elem == 'smali': from ui.panel_smali import SmaliPanel self.smali_panel = SmaliPanel() self.main_tabs.addTab(self.smali_panel, 'Smali') else: print('no handler for elem: ' + elem) # TODO: remove add @2x for item in self.findChildren(QDockWidget): if item: if 'darwin' in sys.platform: item.setStyleSheet( 'QDockWidget::title { padding-left:-30px; } QDockWidget::close-button, QDockWidget::float-button { width: 10px; height:10px }' )
def setup_dock_widgets(self): dock1 = QDockWidget() dock1.setMinimumWidth(200) dock1.setMinimumHeight(100) dock1.setWindowTitle("Recent Trade") recent_trade_view = RecentTradeTableView(self, ws=self.ws) recent_trade_model = RecentTradeTableModel(self, view=recent_trade_view) recent_trade_view.setModel(recent_trade_model) dock1.setWidget(recent_trade_view) self.addDockWidget(Qt.RightDockWidgetArea, dock1) dock2 = QDockWidget() dock2.setMinimumWidth(200) dock2.setMinimumHeight(100) dock2.setWindowTitle("Left dock") self.addDockWidget(Qt.LeftDockWidgetArea, dock2)
def __init__(self, parentWindow): QDockWidget.__init__(self, parentWindow) self.setTitleBarWidget(QWidget()) self.setAllowedAreas(QtCore.Qt.BottomDockWidgetArea) self.setFeatures(QDockWidget.NoDockWidgetFeatures) self.setVisible(False) Console._instance = self banner = """Simple Spectra Manipulator console based on IPython. Numpy package was imported as np. Three variables are setup: pw - Plot Widget sw - SVD widget - factor anaylsis fw - Fit widget f - fitter Enjoy. """ # Add console window self.console_widget = ConsoleWidget(banner) self.setWidget(self.console_widget) startup_comands = """ import numpy as np import matplotlib import matplotlib.pyplot as plt from user_namespace import * import warnings warnings.filterwarnings('ignore') # #from IPython.display import display, Math, Latex from augmentedmatrix import AugmentedMatrix # # _cdict = {'red': ((0.0, 0.0, 0.0), # (2/5, 0.0, 0.0), # (1/2, 1.0, 1.0), # (3/5, 1.0, 1.0), # (4/5, 1.0, 1.0), # (1.0, 0.3, 0.3)), # # 'green': ((0.0, 0, 0), # (2/5, 0.0, 0.0), # (1/2, 1.0, 1.0), # (3/5, 1.0, 1.0), # (4/5, 0.0, 0.0), # (1.0, 0.0, 0.0)), # # 'blue': ((0.0, 0.3, 0.3), # (2/5, 1.0, 1.0), # (1/2, 1.0, 1.0), # (3/5, 0.0, 0.0), # (4/5, 0.0, 0.0), # (1.0, 0.0, 0.0)) # } # _ = matplotlib.colors.LinearSegmentedColormap('div', _cdict) # matplotlib.cm.register_cmap('div', _) np.seterr(divide='ignore') %matplotlib # setup default backend """ # execute first commands self.console_widget.execute_command(startup_comands)
def createToolsDockWidget(self): """ Use View -> Edit Image Tools menu and click the dock widget on or off. Tools dock can be placed on the left or right of the main window. """ # set up QDockWidget self.dock_tools_view = QDockWidget() self.dock_tools_view.setWindowTitle("Edit Image Tools") self.dock_tools_view.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) # create container QWidget to hold all widgets inside dock widget self.tools_contents = QWidget() # create tool push buttons self.rotate90 = QPushButton("Rotate 90º") self.rotate90.setMinimumSize(QSize(130, 40)) self.rotate90.setStatusTip('Rotate image 90º clockwise') self.rotate90.clicked.connect(self.rotateImage90) self.rotate180 = QPushButton("Rotate 180º") self.rotate180.setMinimumSize(QSize(130, 40)) self.rotate180.setStatusTip('Rotate image 180º clockwise') self.rotate180.clicked.connect(self.rotateImage180) self.flip_horizontal = QPushButton("Flip Horizontal") self.flip_horizontal.setMinimumSize(QSize(130, 40)) self.flip_horizontal.setStatusTip('Flip image across horizontal axis') self.flip_horizontal.clicked.connect(self.flipImageHorizontal) self.flip_vertical = QPushButton("Flip Vertical") self.flip_vertical.setMinimumSize(QSize(130, 40)) self.flip_vertical.setStatusTip('Flip image across vertical axis') self.flip_vertical.clicked.connect(self.flipImageVertical) self.resize_half = QPushButton("Resize Half") self.resize_half.setMinimumSize(QSize(130, 40)) self.resize_half.setStatusTip('Resize image to half the original size') self.resize_half.clicked.connect(self.resizeImageHalf) # set up vertical layout to contain all the push buttons dock_v_box = QVBoxLayout() dock_v_box.addWidget(self.rotate90) dock_v_box.addWidget(self.rotate180) dock_v_box.addStretch(1) dock_v_box.addWidget(self.flip_horizontal) dock_v_box.addWidget(self.flip_vertical) dock_v_box.addStretch(1) dock_v_box.addWidget(self.resize_half) dock_v_box.addStretch(6) # set the main layout for the QWidget, tools_contents, # then set the main widget of the dock widget self.tools_contents.setLayout(dock_v_box) self.dock_tools_view.setWidget(self.tools_contents) # set initial location of dock widget self.addDockWidget(Qt.RightDockWidgetArea, self.dock_tools_view) # handles the visibility of the dock widget self.toggle_dock_tools_act = self.dock_tools_view.toggleViewAction()
def setWidget(self, widget): self.mainWidget = DockMainWidgetWrapper(self) self.mainWidget.setWidget(widget) QDockWidget.setWidget(self, self.mainWidget)
class MainWindow(QMainWindow): htmlReady = pyqtSignal(str) def __init__(self, app): QMainWindow.__init__(self) self.install_directory = os.getcwd() self.app = app self.book = None self.last_book = "" self.filename = "" self._part_is_new = False self.tread_running = False self.initTheme() self.createUi() self.createMenus() self.createStatusBar() self.readSettings() self.text_edit.textChanged.connect(self.textChanged) def initTheme(self): settings = QSettings(QSettings.IniFormat, QSettings.UserScope, QCoreApplication.organizationName(), QCoreApplication.applicationName()) self.theme = settings.value("theme", "DarkFusion") hilite_color = settings.value("hiliteColor", self.palette().highlight().color().name()) self.changeStyle(self.theme, hilite_color) def showEvent(self, event): if self.last_book: self.loadBook(self.last_book) def changeStyle(self, theme, hilite_color): self.theme = theme if theme == "DarkFusion": QApplication.setStyle(DarkFusion(hilite_color)) else: QApplication.setStyle(QStyleFactory.create(theme)) pal = self.app.palette() pal.setColor(QPalette.Highlight, QColor(hilite_color)) self.app.setPalette(pal) def createUi(self): self.content = Expander("Content", ":/images/parts.svg") self.images = Expander("Images", ":/images/images.svg") self.settings = Expander("Settings", ":/images/settings.svg") self.setWindowTitle(QCoreApplication.applicationName() + " " + QCoreApplication.applicationVersion()) vbox = QVBoxLayout() vbox.addWidget(self.content) vbox.addWidget(self.images) vbox.addWidget(self.settings) vbox.addStretch() self.content_list = QListWidget() self.content_list.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) content_box = QVBoxLayout() content_box.addWidget(self.content_list) self.item_edit = QLineEdit() self.item_edit.setMaximumHeight(0) self.item_edit.editingFinished.connect(self.editItemFinished) self.item_anim = QPropertyAnimation(self.item_edit, "maximumHeight".encode("utf-8")) content_box.addWidget(self.item_edit) button_layout = QHBoxLayout() plus_button = FlatButton(":/images/plus.svg") self.edit_button = FlatButton(":/images/edit.svg") self.trash_button = FlatButton(":/images/trash.svg") self.up_button = FlatButton(":/images/up.svg") self.down_button = FlatButton(":/images/down.svg") self.trash_button.enabled = False self.up_button.enabled = False self.down_button.enabled = False button_layout.addWidget(plus_button) button_layout.addWidget(self.up_button) button_layout.addWidget(self.down_button) button_layout.addWidget(self.edit_button) button_layout.addWidget(self.trash_button) content_box.addLayout(button_layout) self.content.addLayout(content_box) plus_button.clicked.connect(self.addPart) self.trash_button.clicked.connect(self.dropPart) self.up_button.clicked.connect(self.partUp) self.down_button.clicked.connect(self.partDown) self.edit_button.clicked.connect(self.editPart) self.image_list = QListWidget() self.image_list.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) image_box = QVBoxLayout() image_box.addWidget(self.image_list) image_button_layout = QHBoxLayout() image_plus_button = FlatButton(":/images/plus.svg") self.image_trash_button = FlatButton(":/images/trash.svg") self.image_trash_button.enabled = False image_button_layout.addWidget(image_plus_button) image_button_layout.addWidget(self.image_trash_button) image_box.addLayout(image_button_layout) self.images.addLayout(image_box) image_plus_button.clicked.connect(self.addImage) self.image_trash_button.clicked.connect(self.dropImage) scroll_content = QWidget() scroll_content.setLayout(vbox) scroll = QScrollArea() scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll.setWidget(scroll_content) scroll.setWidgetResizable(True) scroll.setMaximumWidth(200) scroll.setMinimumWidth(200) self.navigationdock = QDockWidget("Navigation", self) self.navigationdock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.navigationdock.setWidget(scroll) self.navigationdock.setObjectName("Navigation") self.addDockWidget(Qt.LeftDockWidgetArea, self.navigationdock) self.splitter = QSplitter() self.text_edit = MarkdownEdit() self.text_edit.setFont(QFont("Courier", 11)) self.preview = QWebEngineView() self.preview.setMinimumWidth(300) self.setWindowTitle(QCoreApplication.applicationName()) self.splitter.addWidget(self.text_edit) self.splitter.addWidget(self.preview) self.setCentralWidget(self.splitter) self.content.expanded.connect(self.contentExpanded) self.images.expanded.connect(self.imagesExpanded) self.settings.expanded.connect(self.settingsExpanded) self.settings.clicked.connect(self.openSettings) self.content_list.currentItemChanged.connect(self.partSelectionChanged) self.image_list.currentItemChanged.connect(self.imageSelectionChanged) self.image_list.itemDoubleClicked.connect(self.insertImage) self.text_edit.undoAvailable.connect(self.undoAvailable) self.text_edit.redoAvailable.connect(self.redoAvailable) self.text_edit.copyAvailable.connect(self.copyAvailable) QApplication.clipboard().dataChanged.connect(self.clipboardDataChanged) def undoAvailable(self, value): self.undo_act.setEnabled(value) def redoAvailable(self, value): self.redo_act.setEnabled(value) def copyAvailable(self, value): self.copy_act.setEnabled(value) self.cut_act.setEnabled(value) def clipboardDataChanged(self): md = QApplication.clipboard().mimeData() self.paste_act.setEnabled(md.hasText()) def openSettings(self): dlg = Settings(self.book, self.install_directory) dlg.exec() if dlg.saved: self.setWindowTitle(QCoreApplication.applicationName() + " - " + self.book.name) def addPart(self): self.item_edit.setText("") self.item_edit.setFocus() self.item_anim.setStartValue(0) self.item_anim.setEndValue(23) self.item_anim.start() self._part_is_new = True def addItem(self): text = self.item_edit.text() if text: if not self.book.getPart(text): self.book.addPart(text) self.loadBook(self.last_book) def updateItem(self): text = self.item_edit.text() if text: if not self.book.getPart(text): self.book.updatePart(self.content_list.currentItem().data(1).name, text) self.loadBook(self.last_book) def editItemFinished(self): if self._part_is_new: self.addItem() else: self.updateItem() self.item_anim.setStartValue(23) self.item_anim.setEndValue(0) self.item_anim.start() def editPart(self): item = self.content_list.currentItem().data(1).name self.item_edit.setText(item) self.item_edit.setFocus() self.item_anim.setStartValue(0) self.item_anim.setEndValue(23) self.item_anim.start() self._part_is_new = False def dropPart(self): item = self.content_list.currentItem().data(1).name msgBox = QMessageBox() msgBox.setText("You are about to delete the part <i>" + item + "</i>") msgBox.setInformativeText("Do you really want to delete the item?") msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Cancel) ret = msgBox.exec() if ret == QMessageBox.Yes: self.book.dropPart(item) self.loadBook(self.last_book) def addImage(self): fileName = "" dialog = QFileDialog() dialog.setFileMode(QFileDialog.AnyFile) dialog.setNameFilter("Image Files(*.png *.jpg *.bmp *.gif);;All (*)") dialog.setWindowTitle("Load Image") dialog.setOption(QFileDialog.DontUseNativeDialog, True) dialog.setAcceptMode(QFileDialog.AcceptOpen) if dialog.exec_(): fileName = dialog.selectedFiles()[0] del dialog if not fileName: return base = os.path.basename(fileName) if not os.path.exists(os.path.join(self.book.source_path, "images", base)): copy(fileName, os.path.join(self.book.source_path, "images")) item = QListWidgetItem() item.setText(Path(fileName).name) item.setData(1, os.path.join(self.book.source_path, "images", Path(fileName).name)) self.image_list.addItem(item) def dropImage(self): item = self.image_list.currentItem() image = item.data(1) filename = os.path.join(self.book.source_path, "parts", image) os.remove(filename) self.loadImages() def loadImages(self): self.image_list.clear() for root, dir, files in os.walk(os.path.join(self.book.source_path, "images")): for file in files: filename = os.path.join(self.book.source_path, "images", Path(file).name) item = QListWidgetItem() item.setToolTip("Doubleclick image to insert into text") item.setText(Path(file).name) item.setData(1, filename) self.image_list.addItem(item) def partUp(self): pos = self.content_list.currentRow() item = self.content_list.takeItem(pos) self.content_list.insertItem(pos - 1, item) self.content_list.setCurrentRow(pos - 1) self.book.partUp(item.data(1).name) def partDown(self): pos = self.content_list.currentRow() item = self.content_list.takeItem(pos) self.content_list.insertItem(pos + 1, item) self.content_list.setCurrentRow(pos + 1) self.book.partDown(item.data(1).name) def partSelectionChanged(self, item): if item: part = item.data(1) self.filename = os.path.join(self.book.source_path, "parts", part.src) with open(self.filename, "r") as f: self.text_edit.setText(f.read()) self.trash_button.enabled = True self.up_button.enabled = self.content_list.currentRow() > 0 self.down_button.enabled = self.content_list.currentRow() < self.content_list.count() - 1 self.edit_button.enabled = True else: self.text_edit.setText("") self.trash_button.enabled = False self.up_button.enabled = False self.down_button.enabled = False self.edit_button.enabled = False def imageSelectionChanged(self, item): if item: self.image_trash_button.enabled = True else: self.image_trash_button.enabled = False def contentExpanded(self, value): if value: self.images.setExpanded(False) self.settings.setExpanded(False) def imagesExpanded(self, value): if value: self.content.setExpanded(False) self.settings.setExpanded(False) def appearanceExpanded(self, value): if value: self.content.setExpanded(False) self.images.setExpanded(False) self.settings.setExpanded(False) def settingsExpanded(self, value): if value: self.content.setExpanded(False) self.images.setExpanded(False) def closeEvent(self, event): self.writeSettings() event.accept() def createMenus(self): new_icon = QIcon(QPixmap(":/images/new.svg")) open_icon = QIcon(QPixmap(":/images/open.svg")) book_icon = QIcon(QPixmap(":/images/book.svg")) bold_icon = QIcon(QPixmap(":/images/bold.svg")) italic_icon = QIcon(QPixmap(":/images/italic.svg")) image_icon = QIcon(QPixmap(":/images/image.svg")) table_icon = QIcon(QPixmap(":/images/table.svg")) á_icon = QIcon(QPixmap(":/images/á.svg")) ã_icon = QIcon(QPixmap(":/images/ã.svg")) é_icon = QIcon(QPixmap(":/images/é.svg")) ê_icon = QIcon(QPixmap(":/images/ê.svg")) ó_icon = QIcon(QPixmap(":/images/ó.svg")) new_act = QAction(new_icon, "&New", self) new_act.setShortcuts(QKeySequence.New) new_act.setStatusTip("Create a new ebook project") new_act.triggered.connect(self.newFile) new_act.setToolTip("Create new ebook project") open_act = QAction(open_icon, "&Open", self) open_act.setShortcuts(QKeySequence.Open) open_act.setStatusTip("Open an existing ebook project") open_act.triggered.connect(self.open) open_act.setToolTip("Open an existing ebook project") book_act = QAction(book_icon, "&Create Book", self) book_act.setShortcuts(QKeySequence.SaveAs) book_act.setStatusTip("Create an ebook") book_act.triggered.connect(self.create) book_act.setToolTip("Create an ebook") pdf_act = QAction("Create &PDF", self) pdf_act.setStatusTip("Create PDF") pdf_act.setToolTip("Create PDF") pdf_act.triggered.connect(self.pdfExport) settings_act = QAction("&Settings", self) settings_act.setStatusTip("Open settings dialog") settings_act.triggered.connect(self.settingsDialog) settings_act.setToolTip("Open settings dialog") exit_act = QAction("E&xit", self) exit_act.setShortcuts(QKeySequence.Quit) exit_act.setStatusTip("Exit the application") exit_act.triggered.connect(self.close) self.undo_act = QAction("Undo", self) self.undo_act.setShortcut(QKeySequence.Undo) self.undo_act.setEnabled(False) self.undo_act.triggered.connect(self.doUndo) self.redo_act = QAction("Redo", self) self.redo_act.setShortcut(QKeySequence.Redo) self.redo_act.setEnabled(False) self.undo_act.triggered.connect(self.doRedo) self.cut_act = QAction("Cu&t", self) self.cut_act.setShortcut(QKeySequence.Cut) self.cut_act.triggered.connect(self.doCut) self.cut_act.setEnabled(False) self.copy_act = QAction("&Copy", self) self.copy_act.setShortcut(QKeySequence.Copy) self.copy_act.triggered.connect(self.doCopy) self.copy_act.setEnabled(False) self.paste_act = QAction("&Paste", self) self.paste_act.setShortcut(QKeySequence.Paste) self.paste_act.triggered.connect(self.doPaste) self.paste_act.setEnabled(False) bold_act = QAction(bold_icon, "Bold", self) bold_act.setShortcut(Qt.CTRL + Qt.Key_B) bold_act.triggered.connect(self.bold) italic_act = QAction(italic_icon, "Italic", self) italic_act.setShortcut(Qt.CTRL + Qt.Key_I) italic_act.triggered.connect(self.italic) image_act = QAction(image_icon, "Image", self) image_act.setShortcut(Qt.CTRL + Qt.Key_G) image_act.triggered.connect(self.insertImage) image_act.setToolTip("Insert an image") table_act = QAction(table_icon, "Table", self) table_act.setShortcut(Qt.CTRL + Qt.Key_T) table_act.triggered.connect(self.insertTable) table_act.setToolTip("Insert a table") á_act = QAction(á_icon, "á", self) á_act.triggered.connect(self.insertLetterA1) á_act.setToolTip("Insert letter á") ã_act = QAction(ã_icon, "ã", self) ã_act.triggered.connect(self.insertLetterA2) ã_act.setToolTip("Insert letter ã") é_act = QAction(é_icon, "é", self) é_act.triggered.connect(self.insertLetterE1) é_act.setToolTip("Insert letter é") ê_act = QAction(ê_icon, "ê", self) ê_act.triggered.connect(self.insertLetterE2) ê_act.setToolTip("Insert letter ê") ó_act = QAction(ó_icon, "ó", self) ó_act.triggered.connect(self.insertLetterO1) ó_act.setToolTip("Insert letter ó") about_act = QAction("&About", self) about_act.triggered.connect(self.about) about_act.setStatusTip("Show the application's About box") spell_act = QAction("&Spellcheck", self) spell_act.setShortcut(Qt.CTRL + Qt.Key_P) spell_act.triggered.connect(self.spellCheck) spell_act.setStatusTip("Spellcheck") file_menu = self.menuBar().addMenu("&File") file_menu.addAction(new_act) file_menu.addAction(open_act) file_menu.addAction(book_act) file_menu.addAction(pdf_act) file_menu.addSeparator() file_menu.addAction(settings_act) file_menu.addSeparator() file_menu.addAction(exit_act) edit_menu = self.menuBar().addMenu("&Edit") edit_menu.addAction(self.undo_act) edit_menu.addAction(self.redo_act) edit_menu.addSeparator() edit_menu.addAction(self.cut_act) edit_menu.addAction(self.copy_act) edit_menu.addAction(self.paste_act) format_menu = self.menuBar().addMenu("&Format") format_menu.addAction(bold_act) format_menu.addAction(italic_act) insert_menu = self.menuBar().addMenu("&Insert") insert_menu.addAction(image_act) insert_menu.addAction(table_act) help_menu = self.menuBar().addMenu("&Help") help_menu.addAction(about_act) help_menu.addAction(spell_act) file_tool_bar = self.addToolBar("File") file_tool_bar.addAction(new_act) file_tool_bar.addAction(open_act) file_tool_bar.addAction(book_act) format_tool_bar = self.addToolBar("Format") format_tool_bar.addAction(bold_act) format_tool_bar.addAction(italic_act) insert_toolbar = self.addToolBar("Insert") insert_toolbar.addAction(image_act) insert_toolbar.addAction(table_act) insert_toolbar.addAction(á_act) insert_toolbar.addAction(ã_act) insert_toolbar.addAction(é_act) insert_toolbar.addAction(ê_act) insert_toolbar.addAction(ó_act) def doUndo(self): self.text_edit.undo() def doRedo(self): self.text_edit.redo() def doCut(self): self.text_edit.cut() def doCopy(self): self.text_edit.copy() def doPaste(self): self.text_edit.paste() def insertImage(self): if not self.book: QMessageBox.warning(self, QCoreApplication.applicationName(), "You have to load or create a book first!") return if not self.filename: QMessageBox.warning(self, QCoreApplication.applicationName(), "You have to select part from the book content first!") return if self.image_list.count() == 0: QMessageBox.warning(self, QCoreApplication.applicationName(), "You have to add an image to the image list first!") return if not self.image_list.currentItem(): QMessageBox.warning(self, QCoreApplication.applicationName(), "You have to select an image from the image list first!") return item = self.image_list.currentItem() filename = item.text() cursor = self.text_edit.textCursor() pos = cursor.position() base = filename.split(".")[0].replace("_", "-") cursor.insertText("![" + base + "](../images/" + filename + " \"" + base + "\")") cursor.setPosition(pos) self.text_edit.setTextCursor(cursor) def insertTable(self): cursor = self.text_edit.textCursor() pos = cursor.position() cursor.insertText("| alignLeft | alignCenter | unAligned | alignRight |\n" "| :--- | :---: | --- | ---: |\n" "| cell a | cell b | cell c | cell d |\n" "| cell e | cell f | cell g | cell h |\n") cursor.setPosition(pos) self.text_edit.setTextCursor(cursor) def insertLetterA1(self): cursor = self.text_edit.textCursor() pos = cursor.position() cursor.insertText("á") cursor.setPosition(pos + 1) self.text_edit.setTextCursor(cursor) def insertLetterA2(self): cursor = self.text_edit.textCursor() pos = cursor.position() cursor.insertText("ã") cursor.setPosition(pos + 1) self.text_edit.setTextCursor(cursor) def insertLetterE1(self): cursor = self.text_edit.textCursor() pos = cursor.position() cursor.insertText("é") cursor.setPosition(pos + 1) self.text_edit.setTextCursor(cursor) def insertLetterE2(self): cursor = self.text_edit.textCursor() pos = cursor.position() cursor.insertText("ê") cursor.setPosition(pos + 1) self.text_edit.setTextCursor(cursor) def insertLetterO1(self): cursor = self.text_edit.textCursor() pos = cursor.position() cursor.insertText("ó") cursor.setPosition(pos + 1) self.text_edit.setTextCursor(cursor) def createStatusBar(self): self.statusBar().showMessage("Ready") def about(self): QMessageBox.about(self, "About " + QCoreApplication.applicationName(), "EbookCreator\nVersion: " + QCoreApplication.applicationVersion() + "\n(C) Copyright 2019 Olaf Japp. All rights reserved.\n\nThis program is provided AS IS with NO\nWARRANTY OF ANY KIND, INCLUDING THE\nWARRANTY OF DESIGN, MERCHANTABILITY AND\nFITNESS FOR A PATICULAR PURPOSE.") def newFile(self): dlg = ProjectWizard(self.install_directory, parent = self) dlg.loadBook.connect(self.loadBook) dlg.show() def open(self): fileName = "" dialog = QFileDialog() dialog.setFileMode(QFileDialog.AnyFile) dialog.setNameFilter("EbookCreator (book.qml);;All (*)") dialog.setWindowTitle("Load Ebook") dialog.setOption(QFileDialog.DontUseNativeDialog, True) dialog.setAcceptMode(QFileDialog.AcceptOpen) dialog.setDirectory(os.path.join(self.install_directory, "sources")) if dialog.exec_(): fileName = dialog.selectedFiles()[0] del dialog if not fileName: return self.loadBook(fileName) def writeSettings(self): settings = QSettings(QSettings.IniFormat, QSettings.UserScope, QCoreApplication.organizationName(), QCoreApplication.applicationName()) settings.setValue("geometry", self.saveGeometry()) settings.setValue("lastBook", self.last_book) def readSettings(self): settings = QSettings(QSettings.IniFormat, QSettings.UserScope, QCoreApplication.organizationName(), QCoreApplication.applicationName()) geometry = settings.value("geometry", QByteArray()) self.last_book = settings.value("lastBook") if not geometry: availableGeometry = QApplication.desktop().availableGeometry(self) self.resize(availableGeometry.width() / 3, availableGeometry.height() / 2) self.move((availableGeometry.width() - self.width()) / 2, (availableGeometry.height() - self.height()) / 2) else: self.restoreGeometry(geometry) def bold(self): if not self.filename: QMessageBox.warning(self, QCoreApplication.applicationName(), "You have to select part from the book content first!") return cursor = self.text_edit.textCursor() pos = cursor.position() if not cursor.hasSelection(): cursor.select(QTextCursor.WordUnderCursor) cursor.insertText("**" + cursor.selectedText() + "**") cursor.setPosition(pos + 2) self.text_edit.setTextCursor(cursor) def italic(self): if not self.filename: QMessageBox.warning(self, QCoreApplication.applicationName(), "You have to select part from the book content first!") return cursor = self.text_edit.textCursor() pos = cursor.position() if not cursor.hasSelection(): cursor.select(QTextCursor.WordUnderCursor) cursor.insertText("*" + cursor.selectedText() + "*") cursor.setPosition(pos + 1) self.text_edit.setTextCursor(cursor) def create(self): filename = "" dialog = QFileDialog() dialog.setFileMode(QFileDialog.AnyFile) dialog.setNameFilter("ePub3 (*.epub);;All (*)") dialog.setWindowTitle("Create Ebook") dialog.setOption(QFileDialog.DontUseNativeDialog, True) dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setDirectory(self.book.source_path) dialog.setDefaultSuffix("epub") if dialog.exec_(): filename = dialog.selectedFiles()[0] del dialog if not filename: return QApplication.setOverrideCursor(Qt.WaitCursor) createEpub(filename, self.book, self) QApplication.restoreOverrideCursor() def loadStatusChanged(self, status): if status == 1: self.book = self.component.create() if self.book is not None: self.book.setFilename(self.last_book) self.book.setWindow(self) else: for error in self.component.errors(): print(error.toString()) return self.content_list.clear() for part in self.book.parts: item = QListWidgetItem() item.setText(part.name) item.setData(1, part) self.content_list.addItem(item) self.loadImages() self.setWindowTitle(QCoreApplication.applicationName() + " - " + self.book.name) self.content.setExpanded(True) self.content_list.setCurrentRow(0) elif status == 3: for error in self.component.errors(): print(error.toString()) return def loadBook(self, filename): self.last_book = filename self.filename = "" engine = QQmlEngine() self.component = QQmlComponent(engine) self.component.statusChanged.connect(self.loadStatusChanged) self.component.loadUrl(QUrl.fromLocalFile(filename)) def settingsDialog(self): dlg = SettingsDialog(self.theme, self.palette().highlight().color().name(), parent=self) dlg.exec() if dlg.theme != self.theme or dlg.hilite_color != self.palette().highlight().color().name(): settings = QSettings(QSettings.IniFormat, QSettings.UserScope, QCoreApplication.organizationName(), QCoreApplication.applicationName()) settings.setValue("theme", dlg.theme) settings.setValue("hiliteColor", dlg.hilite_color) msgBox = QMessageBox() msgBox.setText("Please restart the app to change the theme!") msgBox.exec() def textChanged(self): text = self.text_edit.toPlainText() if self.filename: with open(self.filename, "w") as f: f.write(text) self.lock = Lock() with self.lock: if not self.tread_running: self.tread_running = True self.htmlReady.connect(self.previewReady) thread = Thread(target=self.createHtml, args=(text,)) thread.daemon = True thread.start() def previewReady(self, html): self.preview.setHtml(html, baseUrl=QUrl(Path(os.path.join(self.book.source_path, "parts", "index.html")).as_uri())) self.htmlReady.disconnect() with self.lock: self.tread_running = False def createHtml(self, text): html = "<html>\n<head>\n" html += "<link href=\"../css/pastie.css\" rel=\"stylesheet\" type=\"text/css\"/>\n" html += "<link href=\"../css/stylesheet.css\" rel=\"stylesheet\" type=\"text/css\"/>\n" html += "</head>\n<body>\n" html += markdown(text, html4tags=False, extras=["fenced-code-blocks", "wiki-tables", "tables", "header-ids"]) html += "\n</body>\n</html>" html = addLineNumbers(html) self.htmlReady.emit(html) def pdfExport(self): p = PdfExport(self.book, self.statusBar()) def spellCheck(self): if not self.filename: QMessageBox.warning(self, QCoreApplication.applicationName(), "You have to select part from the book content first!") return cursor = self.text_edit.textCursor() pos = cursor.position() if not cursor.hasSelection(): cursor.select(QTextCursor.WordUnderCursor) spell = Speller(lang='en') changed = spell(cursor.selectedText()) if changed != cursor.selectedText(): cursor.insertText(changed) self.text_edit.setTextCursor(cursor)
class InvoiceX(QMainWindow): def __init__(self): super().__init__() self.mainWindowLeft = 300 self.mainWindowTop = 300 self.mainWindowWidth = 680 self.mainWindowHeight = 480 self.fileLoaded = False self.dialog = None self.initUI() def initUI(self): # StatusBar self.statusBar() self.setStatusTip('Select a PDF to get started') self.set_menu_bar() self.set_dockview_fields() self.set_center_widget() self.set_toolbar() self.setGeometry(self.mainWindowLeft, self.mainWindowTop, self.mainWindowWidth, self.mainWindowHeight) self.setWindowTitle('Invoice-X') self.setWindowIcon( QIcon(os.path.join(os.path.dirname(__file__), 'icons/logo.ico'))) self.show() if not spawn.find_executable('convert'): QMessageBox.critical(self, 'Import Error', "Imagemagick is not installed", QMessageBox.Ok) self.close() if sys.platform[:3] == 'win': if not spawn.find_executable('magick'): QMessageBox.critical( self, 'Import Error', "Imagemagick and GhostScript are not installed", QMessageBox.Ok) self.close() def set_toolbar(self): toolbar = self.addToolBar('File') toolbar.addAction(self.openFile) toolbar.addAction(self.saveFile) toolbar.addAction(self.validateMetadata) toolbar.addAction(self.editFields) def set_center_widget(self): self.square = QLabel(self) self.square.setAlignment(Qt.AlignCenter) self.setCentralWidget(self.square) def set_dockview_fields(self): self.fields = QDockWidget("Fields", self) self.fields.installEventFilter(self) self.fieldsQWidget = QWidget() self.fieldsScrollArea = QScrollArea() self.fieldsScrollArea.setWidgetResizable(True) self.fieldsScrollArea.setWidget(self.fieldsQWidget) self.layout = QGridLayout() self.fieldsQWidget.setLayout(self.layout) self.fields.setWidget(self.fieldsScrollArea) self.fields.setFloating(False) self.fields.setMinimumWidth(360) self.fields.setStyleSheet("QWidget { background-color: #AAB2BD}") self.addDockWidget(Qt.RightDockWidgetArea, self.fields) def set_menu_bar(self): self.exitAct = QAction( QIcon(os.path.join(os.path.dirname(__file__), 'icons/exit.png')), 'Exit', self) self.exitAct.setShortcut('Ctrl+Q') self.exitAct.setStatusTip('Exit application') self.exitAct.triggered.connect(self.close) self.openFile = QAction( QIcon(os.path.join(os.path.dirname(__file__), 'icons/pdf.png')), 'Open', self) self.openFile.setShortcut('Ctrl+O') self.openFile.setStatusTip('Open new File') self.openFile.triggered.connect(self.show_file_dialog) self.saveFile = QAction( QIcon(os.path.join(os.path.dirname(__file__), 'icons/save.png')), 'Save', self) self.saveFile.setShortcut('Ctrl+S') self.saveFile.setStatusTip('Save File') self.saveFile.triggered.connect(self.save_file_dialog) self.saveAsFile = QAction('Save As', self) self.saveAsFile.setStatusTip('Save File as a new File') self.saveAsFile.triggered.connect(self.show_save_as_dialog) self.viewDock = QAction('View Fields', self, checkable=True) self.viewDock.setStatusTip('View Fields') self.viewDock.setChecked(True) self.viewDock.triggered.connect(self.view_dock_field_toggle) extractFields = QAction('Extract Fields', self) extractFields.setStatusTip('Extract Fields from PDF and add to XML') extractFields.triggered.connect(self.extract_fields_from_pdf) jsonFormat = QAction('JSON', self) jsonFormat.setStatusTip('Export file to JSON') jsonFormat.triggered.connect(lambda: self.export_fields('json')) xmlFormat = QAction('XML', self) xmlFormat.setStatusTip('Export file to XML') xmlFormat.triggered.connect(lambda: self.export_fields('xml')) ymlFormat = QAction('YML', self) ymlFormat.setStatusTip('Export file to YML') ymlFormat.triggered.connect(lambda: self.export_fields('yml')) self.validateMetadata = QAction( QIcon(os.path.join(os.path.dirname(__file__), 'icons/validate.png')), 'Validate', self) self.validateMetadata.setStatusTip('Validate XML') self.validateMetadata.triggered.connect(self.validate_xml) addMetadata = QAction('Add Metadata', self) addMetadata.setStatusTip('Add metadata to PDF') self.editFields = QAction( QIcon(os.path.join(os.path.dirname(__file__), 'icons/edit.png')), 'Edit Metadata', self) self.editFields.setStatusTip('Edit Metadata in XML') self.editFields.triggered.connect(self.edit_fields_dialog) documentation = QAction('Documentation', self) documentation.setStatusTip('Open Documentation for Invoice-X') documentation.triggered.connect(self.documentation_menubar) aboutApp = QAction('About', self) aboutApp.setStatusTip('Know about Invoice-X') aboutApp.triggered.connect(self.about_app_menubar) menubar = self.menuBar() fileMenu = menubar.addMenu('&File') fileMenu.addAction(self.openFile) fileMenu.addAction(self.saveFile) fileMenu.addAction(self.saveAsFile) fileMenu.addAction(self.viewDock) fileMenu.addAction(self.exitAct) commandMenu = menubar.addMenu('&Command') exportMetadata = commandMenu.addMenu('&Export Metadata') exportMetadata.addAction(jsonFormat) exportMetadata.addAction(xmlFormat) exportMetadata.addAction(ymlFormat) commandMenu.addAction(self.validateMetadata) commandMenu.addAction(self.editFields) commandMenu.addAction(addMetadata) commandMenu.addAction(extractFields) helpMenu = menubar.addMenu('&Help') helpMenu.addAction(documentation) helpMenu.addAction(aboutApp) def view_dock_field_toggle(self, state): if state: self.fields.show() else: self.fields.hide() def validate_xml(self): try: if self.factx.is_valid(): QMessageBox.information(self, 'Valid XML', "The XML is Valid", QMessageBox.Ok) else: QMessageBox.critical(self, 'Invalid XML', "The XML is invalid", QMessageBox.Ok) except AttributeError: QMessageBox.critical(self, 'File Not Found', "Load a PDF first", QMessageBox.Ok) def set_pdf_preview(self): # print(str(fileName[0])) if not os.path.exists('.load'): os.mkdir('.load') if sys.platform[:3] == 'win': convert = [ 'magick', self.fileName[0], '-flatten', '.load/preview.jpg' ] else: convert = [ 'convert', '-verbose', '-density', '150', '-trim', self.fileName[0], '-quality', '100', '-flatten', '-sharpen', '0x1.0', '.load/preview.jpg' ] subprocess.call(convert) self.pdfPreviewImage = '.load/preview.jpg' self.fileLoaded = True self.square.setPixmap( QPixmap(self.pdfPreviewImage).scaled(self.square.size().width(), self.square.size().height(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) def edit_fields_dialog(self): try: self.dialog = EditFieldsClass(self, self.factx, self.fieldsDict, self.metadata_field) self.dialog.installEventFilter(self) # self.dialog.show() except AttributeError: QMessageBox.critical(self, 'File Not Found', "Load a PDF first", QMessageBox.Ok) def update_dock_fields(self): self.factx.write_json('.load/output.json') with open('.load/output.json') as jsonFile: self.fieldsDict = json.load(jsonFile) os.remove('.load/output.json') # print(self.fieldsDict) i = 0 self.metadata_field = { 'amount_tax': 'Amount Tax', 'amount_total': 'Amount Total', 'amount_untaxed': 'Amount Untaxed', 'buyer': 'Buyer', 'currency': 'Currency', 'date': 'Date', 'date_due': 'Date Due', 'invoice_number': 'Invoice Number', 'name': 'Name', 'notes': 'Notes', 'seller': 'Seller', 'type': 'Type', 'version': 'Version' } for key in sorted(self.fieldsDict): i += 1 try: self.factx[key] except IndexError: self.fieldsDict[key] = "Field Not Specified" except TypeError: pass fieldKey = QLabel(self.metadata_field[key] + ": ") if self.fieldsDict[key] is None: fieldValue = QLabel("NA") else: if key[:4] == "date" and \ self.fieldsDict[key] != "Field Not Specified": self.fieldsDict[key] = self.fieldsDict[key][:4] \ + "/" + self.fieldsDict[key][4:6] \ + "/" + self.fieldsDict[key][6:8] if self.fieldsDict[key] == "Field Not Specified": fieldValue = QLabel(self.fieldsDict[key]) fieldValue.setStyleSheet("QLabel { color: #666666}") else: fieldValue = QLabel(self.fieldsDict[key]) # fieldValue.setFrameShape(QFrame.Panel) # fieldValue.setFrameShadow(QFrame.Plain) # fieldValue.setLineWidth(3) self.layout.addWidget(fieldKey, i, 0) self.layout.addWidget(fieldValue, i, 1) def show_file_dialog(self): self.fileName = QFileDialog.getOpenFileName(self, 'Open file', os.path.expanduser("~"), "pdf (*.pdf)") self.load_pdf_file() def load_pdf_file(self): if self.fileName[0]: if self.check_xml_for_pdf() is None: self.standard = None self.level = None self.choose_standard_level() if self.standard is not None: self.factx = FacturX(self.fileName[0], self.standard, self.level) else: self.factx = FacturX(self.fileName[0]) if hasattr(self, 'factx'): self.set_pdf_preview() self.update_dock_fields() self.setStatusTip("PDF is Ready") def choose_standard_level(self): self.chooseStandardDialog = QDialog() layout = QGridLayout() noXMLLabel = QLabel("No XML found", self) layout.addWidget(noXMLLabel, 0, 0) chooseStandardLabel = QLabel("Standard", self) chooseStandardCombo = QComboBox(self) chooseStandardCombo.addItem("Factur-X") chooseStandardCombo.addItem("Zugferd") chooseStandardCombo.addItem("UBL") chooseStandardCombo.model().item(2).setEnabled(False) chooseStandardCombo.activated[str].connect(self.on_select_level) chooseLevelLabel = QLabel("Level", self) self.chooseLevelCombo = QComboBox(self) self.chooseLevelCombo.addItem("Minimum") self.chooseLevelCombo.addItem("Basic WL") self.chooseLevelCombo.addItem("Basic") self.chooseLevelCombo.addItem("EN16931") self.chooseLevelCombo.activated[str].connect(self.set_level) applyStandard = QPushButton("Apply") applyStandard.clicked.connect(self.set_standard_level) discardStandard = QPushButton("Cancel") discardStandard.clicked.connect(self.discard_standard_level) layout.addWidget(chooseStandardLabel, 1, 0) layout.addWidget(chooseStandardCombo, 1, 1) layout.addWidget(chooseLevelLabel, 2, 0) layout.addWidget(self.chooseLevelCombo, 2, 1) layout.addWidget(discardStandard, 3, 0) layout.addWidget(applyStandard, 3, 1) self.chooseStandardDialog.setLayout(layout) self.chooseStandardDialog.setWindowTitle("Choose Standard") self.chooseStandardDialog.setWindowModality(Qt.ApplicationModal) self.chooseStandardDialog.exec_() def set_standard_level(self): try: self.standard = self.standard_temp self.level = self.level_temp except AttributeError: self.standard = 'factur-x' self.level = 'minimum' self.chooseStandardDialog.close() def discard_standard_level(self): self.chooseStandardDialog.close() def on_select_level(self, text): if text == "Factur-X": self.chooseLevelCombo.clear() self.chooseLevelCombo.addItem("Minimum") self.chooseLevelCombo.addItem("Basic WL") self.chooseLevelCombo.addItem("Basic") self.chooseLevelCombo.addItem("EN16931") elif text == "Zugferd": self.chooseLevelCombo.clear() self.chooseLevelCombo.addItem("Basic") self.chooseLevelCombo.addItem("Comfort") elif text == "UBL": self.chooseLevelCombo.clear() self.chooseLevelCombo.addItem("UBL 2.0") self.chooseLevelCombo.addItem("UBL 2.1") standard_dict = { 'Factur-X': ['factur-x', 'minimum'], 'Zugferd': ['zugferd', 'basic'], } self.standard_temp = standard_dict[text][0] self.level_temp = standard_dict[text][1] def set_level(self, text): level_dict = { 'Minimum': 'minimum', 'Basic WL': 'basicwl', 'Basic': 'basic', 'EN16931': 'en16931', 'Comfort': 'comfort' } self.level_temp = level_dict[text] def check_xml_for_pdf(self): pdf = PdfFileReader(self.fileName[0]) pdf_root = pdf.trailer['/Root'] if '/Names' not in pdf_root or '/EmbeddedFiles' not in \ pdf_root['/Names']: return None for file in pdf_root['/Names']['/EmbeddedFiles']['/Names']: if isinstance(file, IndirectObject): obj = file.getObject() if obj['/F'] in xml_flavor.valid_xmp_filenames(): xml_root = etree.fromstring(obj['/EF']['/F'].getData()) xml_content = xml_root return xml_content def save_file_dialog(self): if self.fileLoaded: if self.confirm_save_dialog(): try: self.factx.write_pdf(self.fileName[0]) except TypeError: QMessageBox.critical(self, 'Type Error', "Some field value(s) are invalid", QMessageBox.Ok) else: QMessageBox.critical(self, 'File Not Found', "Load a PDF first", QMessageBox.Ok) def confirm_save_dialog(self): reply = QMessageBox.question( self, 'Message', "Do you want to save? This cannot be undone", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: return True else: return False def show_save_as_dialog(self): if self.fileLoaded: try: self.saveFileName = QFileDialog.getSaveFileName( self, 'Save file', os.path.expanduser("~"), "pdf (*.pdf)") if self.saveFileName[0]: if self.saveFileName[0].endswith('.pdf'): fileName = self.saveFileName[0] else: fileName = self.saveFileName[0] + '.pdf' self.factx.write_pdf(fileName) except TypeError: QMessageBox.critical(self, 'Type Error', "Some field value(s) are not valid", QMessageBox.Ok) else: QMessageBox.critical(self, 'File Not Found', "Load a PDF first", QMessageBox.Ok) def extract_fields_from_pdf(self): if self.fileLoaded: self.populate = PopulateFieldClass(self, self.factx, self.fieldsDict, self.metadata_field) else: QMessageBox.critical(self, 'File Not Found', "Load a PDF first", QMessageBox.Ok) def documentation_menubar(self): pass def about_app_menubar(self): pass def export_fields(self, outputformat): if self.fileLoaded: self.exportFileName = QFileDialog.getSaveFileName( self, 'Export file', os.path.expanduser("~") + '/output.%s' % outputformat, "%s (*.%s)" % (outputformat, outputformat)) if self.exportFileName[0]: if outputformat is "json": self.pdf_write_json(self.exportFileName[0]) elif outputformat is "xml": self.pdf_write_xml(self.exportFileName[0]) elif outputformat is "yml": self.pdf_write_yml(self.exportFileName[0]) else: QMessageBox.critical(self, 'File Not Found', "Load a PDF first", QMessageBox.Ok) def pdf_write_json(self, fileName): self.factx.write_json(fileName) def pdf_write_xml(self, fileName): self.factx.write_xml(fileName) def pdf_write_yml(self, fileName): self.factx.write_yml(fileName) def resizeEvent(self, event): if self.fileLoaded: self.square.setPixmap( QPixmap(self.pdfPreviewImage).scaled( self.square.size().width(), self.square.size().height(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) self.square.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Ignored) QMainWindow.resizeEvent(self, event) def eventFilter(self, source, event): if event.type() == QEvent.Close and source is self.fields: self.viewDock.setChecked(False) return QMainWindow.eventFilter(self, source, event) def closeEvent(self, event): if os.path.isdir('.load'): shutil.rmtree('.load/')