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))
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
 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)
Ejemplo n.º 4
0
 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
Ejemplo n.º 5
0
 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);
Ejemplo n.º 6
0
    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)
Ejemplo n.º 7
0
	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)
Ejemplo n.º 8
0
    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)
Ejemplo n.º 9
0
    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()
Ejemplo n.º 10
0
    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()
Ejemplo n.º 11
0
 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)
Ejemplo n.º 12
0
    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)
Ejemplo n.º 13
0
    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)
Ejemplo n.º 14
0
Archivo: main.py Proyecto: lordmauve/mu
 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
Ejemplo n.º 15
0
    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
Ejemplo n.º 16
0
    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)
Ejemplo n.º 17
0
    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)
Ejemplo n.º 18
0
Archivo: app.py Proyecto: ipapi/ipap
    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)
Ejemplo n.º 19
0
    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)
Ejemplo n.º 20
0
 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        
Ejemplo n.º 21
0
    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)
Ejemplo n.º 22
0
 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)
Ejemplo n.º 23
0
    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
Ejemplo n.º 24
0
Archivo: main.py Proyecto: lordmauve/mu
 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()
Ejemplo n.º 25
0
 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)
Ejemplo n.º 26
0
Archivo: main.py Proyecto: lordmauve/mu
 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()
Ejemplo n.º 27
0
Archivo: app.py Proyecto: ipapi/ipap
    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)
Ejemplo n.º 28
0
    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
Ejemplo n.º 29
0
 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)
Ejemplo n.º 30
0
 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)
Ejemplo n.º 31
0
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)
Ejemplo n.º 32
0
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)
Ejemplo n.º 33
0
Archivo: main.py Proyecto: lordmauve/mu
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))
Ejemplo n.º 34
0
    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()
Ejemplo n.º 35
0
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)
Ejemplo n.º 36
0
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)
Ejemplo n.º 37
0
    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))
Ejemplo n.º 38
0
    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; }'
                    )
Ejemplo n.º 39
0
 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)
Ejemplo n.º 40
0
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)
Ejemplo n.º 41
0
    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)
Ejemplo n.º 42
0
 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)
Ejemplo n.º 43
0
 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)
Ejemplo n.º 44
0
    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))
Ejemplo n.º 45
0
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()
Ejemplo n.º 46
0
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)
Ejemplo n.º 47
0
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()
Ejemplo n.º 48
0
    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)
Ejemplo n.º 49
0
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")
Ejemplo n.º 50
0
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()
Ejemplo n.º 51
0
    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
Ejemplo n.º 52
0
Archivo: app.py Proyecto: knobse/Dwarf
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))
Ejemplo n.º 53
0
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__
Ejemplo n.º 54
0
Archivo: app.py Proyecto: knobse/Dwarf
    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 }'
                    )
Ejemplo n.º 55
0
    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)
Ejemplo n.º 57
0
    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()
Ejemplo n.º 58
0
 def setWidget(self, widget):
     self.mainWidget = DockMainWidgetWrapper(self)
     self.mainWidget.setWidget(widget)
     QDockWidget.setWidget(self, self.mainWidget)
Ejemplo n.º 59
0
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)
Ejemplo n.º 60
0
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/')