Esempio n. 1
0
class MainWidget(QWidget):
    def __init__(self, parent=None):
        super(MainWidget, self).__init__(parent)

        # define tree view
        self.treeView = QTreeView(self)

        # insert line edit
        self.lineEdit1 = QLineEdit(self)
        self.lineEdit2 = QLineEdit(self)

        # insert a button group widget
        self.buttonGroupWidget1 = ButtonGroupWidget(self)
        self.buttonGroupWidget1.setTitle("Select option")
        self.buttonGroupWidget1.addRadioButton("Option 1", 1)
        self.buttonGroupWidget1.addRadioButton("Option 2", 2)
        self.buttonGroupWidget1.addRadioButton("Option 3", 3)

        layoutMain = QVBoxLayout(self)
        layoutMain.addWidget(self.treeView)
        layoutMain.addWidget(self.lineEdit1)
        layoutMain.addWidget(self.lineEdit2)
        layoutMain.addWidget(self.buttonGroupWidget1)

        # Create the data model and map the model to the widgets.
        self._model = DataModel()
        self.treeView.setModel(self._model)

        self._dataMapper = QDataWidgetMapper()
        self._dataMapper.setModel(self._model)
        # the mapping works fine for line edits and combo boxes
        self._dataMapper.addMapping(self.lineEdit1, 0)
        self._dataMapper.addMapping(self.lineEdit2, 1)
        # mapping to custom property
        self._dataMapper.addMapping(self.buttonGroupWidget1, 1,
                                    "selectedOption")

        self.treeView.selectionModel().currentChanged.connect(
            self.setSelection)

    def setSelection(self, current):
        parent = current.parent()
        # self._dataMapper.setRootIndex(parent)
        self._dataMapper.setCurrentModelIndex(current)
Esempio n. 2
0
class Snippets(QDialog):

    def __init__(self, parent=None):
        super(Snippets, self).__init__(parent)
        # Create widgets
        self.setWindowModality(Qt.NonModal)
        self.title = QLabel(self.tr("Snippet Editor"))
        self.saveButton = QPushButton(self.tr("Save"))
        self.revertButton = QPushButton(self.tr("Revert"))
        self.clearHotkeyButton = QPushButton(self.tr("Clear Hotkey"))
        self.setWindowTitle(self.title.text())
        self.newFolderButton = QPushButton("New Folder")
        self.deleteSnippetButton = QPushButton("Delete")
        self.newSnippetButton = QPushButton("New Snippet")
        self.edit = QPlainTextEdit()
        self.resetting = False
        self.columns = 3

        self.keySequenceEdit = QKeySequenceEdit(self)
        self.currentHotkey = QKeySequence()
        self.currentHotkeyLabel = QLabel("")
        self.currentFileLabel = QLabel()
        self.currentFile = ""
        self.snippetDescription = QLineEdit()
        self.snippetEditsPending = False

        self.clearSelection()

        #Set Editbox Size
        font = getMonospaceFont(self)
        self.edit.setFont(font)
        font = QFontMetrics(font)
        self.edit.setTabStopWidth(4 * font.width(' ')); #TODO, replace with settings API

        #Files
        self.files = QFileSystemModel()
        self.files.setRootPath(snippetPath)
        self.files.setNameFilters(["*.py"])

        #Tree
        self.tree = QTreeView()
        self.tree.setModel(self.files)
        self.tree.setSortingEnabled(True)
        self.tree.hideColumn(2)
        self.tree.sortByColumn(0, Qt.AscendingOrder)
        self.tree.setRootIndex(self.files.index(snippetPath))
        for x in range(self.columns):
            #self.tree.resizeColumnToContents(x)
            self.tree.header().setSectionResizeMode(x, QHeaderView.ResizeToContents) 
        treeLayout = QVBoxLayout()
        treeLayout.addWidget(self.tree)
        treeButtons = QHBoxLayout()
        treeButtons.addWidget(self.newFolderButton)
        treeButtons.addWidget(self.newSnippetButton)
        treeButtons.addWidget(self.deleteSnippetButton)
        treeLayout.addLayout(treeButtons)
        treeWidget = QWidget()
        treeWidget.setLayout(treeLayout)

        # Create layout and add widgets
        buttons = QHBoxLayout()
        buttons.addWidget(self.clearHotkeyButton)
        buttons.addWidget(self.keySequenceEdit)
        buttons.addWidget(self.currentHotkeyLabel)
        buttons.addWidget(self.revertButton)
        buttons.addWidget(self.saveButton)

        description = QHBoxLayout()
        description.addWidget(QLabel(self.tr("Description: ")))
        description.addWidget(self.snippetDescription)

        vlayoutWidget = QWidget()
        vlayout = QVBoxLayout()
        vlayout.addWidget(self.currentFileLabel)
        vlayout.addWidget(self.edit)
        vlayout.addLayout(description)
        vlayout.addLayout(buttons)
        vlayoutWidget.setLayout(vlayout)

        hsplitter = QSplitter()
        hsplitter.addWidget(treeWidget)
        hsplitter.addWidget(vlayoutWidget)

        hlayout = QHBoxLayout()
        hlayout.addWidget(hsplitter)

        self.showNormal() #Fixes bug that maximized windows are "stuck"
        self.settings = QSettings("Vector 35", "Snippet Editor")
        if self.settings.contains("ui/snippeteditor/geometry"):
            self.restoreGeometry(self.settings.value("ui/snippeteditor/geometry"))
        else:
            self.edit.setMinimumWidth(80 * font.averageCharWidth())
            self.edit.setMinimumHeight(30 * font.lineSpacing())

        # Set dialog layout
        self.setLayout(hlayout)

        # Add signals
        self.saveButton.clicked.connect(self.save)
        self.revertButton.clicked.connect(self.loadSnippet)
        self.clearHotkeyButton.clicked.connect(self.clearHotkey)
        self.tree.selectionModel().selectionChanged.connect(self.selectFile)
        self.newSnippetButton.clicked.connect(self.newFileDialog)
        self.deleteSnippetButton.clicked.connect(self.deleteSnippet)
        self.newFolderButton.clicked.connect(self.newFolder)

    def registerAllSnippets(self):
        for action in list(filter(lambda x: x.startswith("Snippet\\"), UIAction.getAllRegisteredActions())):
            UIActionHandler.globalActions().unbindAction(action)
            UIAction.unregisterAction(action)

        for snippet in includeWalk(snippetPath, ".py"):
            (snippetDescription, snippetKey, snippetCode) = loadSnippetFromFile(snippet)
            if not snippetDescription:
                actionText = "Snippet\\" + snippet
            else:
                actionText = "Snippet\\" + snippetDescription
            UIAction.registerAction(actionText, snippetKey)
            UIActionHandler.globalActions().bindAction(actionText, UIAction(makeSnippetFunction(snippetCode)))

    def clearSelection(self):
        self.keySequenceEdit.clear()
        self.currentHotkey = QKeySequence()
        self.currentHotkeyLabel.setText("")
        self.currentFileLabel.setText("")
        self.snippetDescription.setText("")
        self.edit.setPlainText("")

    def reject(self):
        self.settings.setValue("ui/snippeteditor/geometry", self.saveGeometry())

        if self.snippetChanged():
            question = QMessageBox.question(self, self.tr("Discard"), self.tr("You have unsaved changes, quit anyway?"))
            if question != QMessageBox.StandardButton.Yes:
                return
        self.accept()

    def newFolder(self):
        (folderName, ok) = QInputDialog.getText(self, self.tr("Folder Name"), self.tr("Folder Name: "))
        if ok and folderName:
            index = self.tree.selectionModel().currentIndex()
            selection = self.files.filePath(index)
            if QFileInfo(selection).isDir():
                QDir(selection).mkdir(folderName)
            else:
                QDir(snippetPath).mkdir(folderName)    

    def selectFile(self, new, old):
        if (self.resetting):
            self.resetting = False
            return
        newSelection = self.files.filePath(new.indexes()[0])
        if QFileInfo(newSelection).isDir():
            self.clearSelection()
            return

        if old.length() > 0:
            oldSelection = self.files.filePath(old.indexes()[0])
            if not QFileInfo(oldSelection).isDir() and self.snippetChanged():
                question = QMessageBox.question(self, self.tr("Discard"), self.tr("Snippet changed. Discard changes?"))
                if question != QMessageBox.StandardButton.Yes:
                    self.resetting = True
                    self.tree.selectionModel().select(old, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows)
                    return False

        self.currentFile = newSelection
        self.loadSnippet()

    def loadSnippet(self):
        self.currentFileLabel.setText(QFileInfo(self.currentFile).baseName())
        log_debug("Loading %s as a snippet." % self.currentFile)
        (snippetDescription, snippetKey, snippetCode) = loadSnippetFromFile(self.currentFile)
        self.snippetDescription.setText(snippetDescription) if snippetDescription else self.snippetDescription.setText("")
        self.keySequenceEdit.setKeySequence(snippetKey[0]) if len(snippetKey) != 0 else self.keySequenceEdit.setKeySequence(QKeySequence(""))
        self.edit.setPlainText(snippetCode) if snippetCode else self.edit.setPlainText("")

    def newFileDialog(self):
        (snippetName, ok) = QInputDialog.getText(self, self.tr("Snippet Name"), self.tr("Snippet Name: "))
        if ok and snippetName:
            if not snippetName.endswith(".py"):
                snippetName += ".py"
            index = self.tree.selectionModel().currentIndex()
            selection = self.files.filePath(index)
            if QFileInfo(selection).isDir():
                open(os.path.join(selection, snippetName), "w").close()
            else:
                open(os.path.join(snippetPath, snippetName), "w").close()
            log_debug("Snippet %s created." % snippetName)

    def deleteSnippet(self):
        selection = self.tree.selectedIndexes()[::self.columns][0] #treeview returns each selected element in the row
        snippetName = self.files.fileName(selection)
        question = QMessageBox.question(self, self.tr("Confirm"), self.tr("Confirm deletion: ") + snippetName)
        if (question == QMessageBox.StandardButton.Yes):
            log_debug("Deleting snippet %s." % snippetName)
            self.clearSelection()
            self.files.remove(selection)

    def snippetChanged(self):
        if (self.currentFile == "" or QFileInfo(self.currentFile).isDir()):
            return False
        (snippetDescription, snippetKey, snippetCode) = loadSnippetFromFile(self.currentFile)
        if (not snippetCode):
            return False
        if len(snippetKey) == 0 and not self.keySequenceEdit.keySequence().isEmpty():
            return True
        if len(snippetKey) != 0 and snippetKey[0] != self.keySequenceEdit.keySequence():
            return True
        return self.edit.toPlainText() != snippetCode or \
               self.snippetDescription.text() != snippetDescription

    def save(self):
        log_debug("Saving snippet %s" % self.currentFile)
        outputSnippet = open(self.currentFile, "w")
        outputSnippet.write("#" + self.snippetDescription.text() + "\n")
        outputSnippet.write("#" + self.keySequenceEdit.keySequence().toString() + "\n")
        outputSnippet.write(self.edit.toPlainText())
        outputSnippet.close()
        self.registerAllSnippets()

    def clearHotkey(self):
        self.keySequenceEdit.clear()
Esempio n. 3
0
class NavigationWidget(QWidget):
    def __init__(self, index_filename):
        QWidget.__init__(self)

        self.data_index = pd.DataFrame()
        self.data_info = aecg.tools.indexer.StudyInfo()
        self.data_index_stats = aecg.tools.indexer.StudyStats(
            self.data_info, self.data_index)

        # Getting the Models
        self.project_loaded = ''
        self.projectmodel = ProjectTreeModel()

        # Creating a QTreeView for displaying the selected project index
        self.project_treeview = QTreeView()
        self.project_treeview.setModel(self.projectmodel)
        self.phorizontal_header = self.project_treeview.header()
        self.phorizontal_header.setSectionResizeMode(
            QHeaderView.ResizeToContents)
        self.phorizontal_header.setStretchLastSection(True)

        self.sp_right = QSplitter(Qt.Orientation.Horizontal)
        self.sp_left = QSplitter(Qt.Orientation.Vertical, self.sp_right)
        # NavigationWidget Layout
        self.main_layout = QVBoxLayout()
        size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)

        # Left side
        # Left side - Top layout
        # Left side - Bottom Layout
        size.setVerticalStretch(4)
        self.project_treeview.setSizePolicy(size)

        self.sp_left.addWidget(self.project_treeview)

        # Right side
        size = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        size.setHorizontalStretch(2)
        size.setHeightForWidth(False)
        self.tabdisplays = TabDisplays(self)
        self.sp_right.addWidget(self.tabdisplays)
        self.tabdisplays.validator_data_ready.connect(
            self.load_projectindex_after_validation)
        self.sp_right.setSizePolicy(size)

        # Set the layout to the QWidget
        self.main_layout.addWidget(self.sp_right)
        self.setLayout(self.main_layout)

        self.tabdisplays.setCurrentWidget(self.tabdisplays.validator)

        # Load study index
        if index_filename and (index_filename != ""):
            if os.path.exists(index_filename):
                self.load_projectindex(os.path.normpath(index_filename))
            else:
                QMessageBox.warning(self, f"Study index file not found",
                                    f"{index_filename} not found")

    projectindex_loaded = Signal()
    projectindexstats_loaded = Signal()

    def load_projectindex_after_validation(self):
        self.load_projectindex(
            os.path.normpath(self.tabdisplays.study_info_file.text()))

    def load_projectindex(self, project_idx_file):
        index_loaded = False
        stats_loaded = False
        if project_idx_file != "":
            if os.path.exists(project_idx_file):
                try:
                    self.parent().status.showMessage(
                        f"Loading {project_idx_file}")
                    progress = QProgressDialog("Loading index file...",
                                               "Cancel", 0, 3, self)
                    progress.setWindowTitle("Loading study index")
                    progress.setWindowModality(Qt.WindowModal)
                    progress.setMinimumWidth(300)
                    progress.setMinimumDuration(0)
                    progress.forceShow()
                    progress.setValue(0)
                    wb = load_workbook(project_idx_file, read_only=True)
                    progress.setValue(1)
                    ws = wb['Index']
                    ws.reset_dimensions()
                    data = ws.values
                    cols = next(data)
                    data = list(data)
                    progress.setValue(2)
                    self.data_index = pd.DataFrame(data,
                                                   columns=cols).fillna("")
                    progress.setValue(3)
                    progress.close()
                    # Parse index to the tree
                    num_ecgs = 0
                    if "EGREFID" in self.data_index.columns:
                        num_ecgs = self.data_index[[
                            "ZIPFILE", "AECGXML", "EGREFID", "WFTYPE"
                        ]].drop_duplicates().shape[0]
                        progress = QProgressDialog("Parsing index ...",
                                                   "Cancel", 0, num_ecgs, self)
                        progress.setWindowTitle("Loading study index")
                        progress.setLabelText("Parsing index...")
                        progress.setWindowModality(Qt.WindowModal)
                        progress.setMinimumWidth(300)
                        progress.setMinimumDuration(0)
                        progress.forceShow()
                        progress.setValue(0)
                        self.projectmodel = ProjectTreeModel(
                            self.data_index, progress_dialog=progress)
                        self.project_treeview.setModel(self.projectmodel)
                        self.project_treeview.selectionModel().\
                            selectionChanged.connect(self.load_data)
                        progress.close()
                    else:
                        QMessageBox.warning(
                            self, "EGREFID missing",
                            f"EGREFID column missing Index sheet of "
                            f"{project_idx_file}")
                    self.project_loaded = project_idx_file

                    # Reset aECG display
                    self.tabdisplays.aecg_display.aecg_data = None
                    self.tabdisplays.aecg_display.plot_aecg()

                    # Populate study information/validator tab
                    self.tabdisplays.load_study_info(project_idx_file)
                    self.data_index_info = self.tabdisplays.studyindex_info

                    index_loaded = True
                    try:
                        progress = QProgressDialog(
                            "Loading study index stats...", "Cancel", 0, 3,
                            self)
                        progress.setWindowTitle("Loading study index stats")
                        progress.setWindowModality(Qt.WindowModal)
                        progress.setMinimumWidth(300)
                        progress.setMinimumDuration(0)
                        progress.forceShow()
                        progress.setValue(0)
                        ws = wb['Stats']
                        ws.reset_dimensions()
                        data = ws.values
                        cols = next(data)
                        data = list(data)
                        progress.setValue(1)
                        progress.forceShow()
                        statsdf = pd.DataFrame(data, columns=cols).fillna("")
                        progress.setValue(2)
                        progress.forceShow()
                        self.data_index_stats = aecg.tools.indexer.StudyStats()
                        self.data_index_stats.__dict__.update(
                            statsdf.set_index("Property").transpose().
                            reset_index(drop=True).to_dict('index')[0])
                        progress.setValue(3)
                        progress.forceShow()
                        progress.close()
                        stats_loaded = True
                    except Exception as ex:
                        QMessageBox.warning(
                            self, f"Error loading study index stats",
                            f"Error loading study index stats from"
                            f"{project_idx_file}\n{str(ex)}")
                except Exception as ex:
                    QMessageBox.warning(
                        self, f"Error loading study index",
                        f"Error loading study index from {project_idx_file}"
                        f"\n{str(ex)}")
            else:
                QMessageBox.warning(self, f"Study index file not found",
                                    f"{project_idx_file} not found")

            if index_loaded:
                self.projectindex_loaded.emit()
                if stats_loaded:
                    self.projectindexstats_loaded.emit()
            self.parentWidget().status.clearMessage()

    def load_data(self, selected, deselected):
        self.tabdisplays.setCurrentWidget(self.tabdisplays.waveforms)
        rhythm = self.projectmodel.itemData(selected.indexes()[2])[0]
        derived = self.projectmodel.itemData(selected.indexes()[3])[0]
        # Get study directory provided in the GUI
        studydir = self.tabdisplays.effective_aecgs_dir(self, silent=True)
        # Calculate effective study dir
        aecg_xml_file = self.projectmodel.itemData(selected.indexes()[5])[0]
        if aecg_xml_file != "":
            zipfile = self.projectmodel.itemData(selected.indexes()[4])[0]
            if zipfile != "":
                zipfile = os.path.join(studydir, zipfile)
            else:
                aecg_xml_file = os.path.join(studydir, aecg_xml_file)
            # Load aECG file
            aecg_data = aecg.io.read_aecg(aecg_xml_file,
                                          zipfile,
                                          include_digits=True,
                                          in_memory_xml=True,
                                          log_validation=False)
            if aecg_data.xmlfound:
                # Plot aECG
                self.tabdisplays.aecg_display.set_aecg(aecg_data)
                self.tabdisplays.aecg_display.plot_aecg(
                    rhythm,
                    derived,
                    ecg_layout=aecg.utils.ECG_plot_layout(
                        self.tabdisplays.cbECGLayout.currentIndex() + 1))

                # Populate XML viewer
                self.tabdisplays.xml_display.setText(aecg_data.xmlstring())
            else:
                QMessageBox.warning(self, "aECG XML file not found",
                                    f"aECG XML {aecg_xml_file} not found")
                self.parentWidget().update_status_bar()
Esempio n. 4
0
class TriageFilePicker(QWidget):
	def __init__(self, context):
		super(TriageFilePicker, self).__init__()
		self.context = context
		self.actionHandler = UIActionHandler()
		self.actionHandler.setupActionHandler(self)
		self.contextMenu = Menu()
		self.contextMenuManager = ContextMenuManager(self)

		layout = QVBoxLayout()
		layout.setContentsMargins(0, 0, 0, 0)

		self.model = QFileSystemModel()
		self.model.setRootPath("")
		self.model.setFilter(QDir.AllEntries | QDir.Hidden | QDir.System)
		self.tree = QTreeView(self)
		self.tree.setModel(self.model)
		self.tree.setSelectionMode(QAbstractItemView.ExtendedSelection)
		self.tree.setColumnWidth(0, 500)
		layout.addWidget(self.tree, 1)

		self.setLayout(layout)

		self.tree.doubleClicked.connect(self.onDoubleClick)

		recentFile = QSettings().value("triage/recentFile", os.path.expanduser("~"))
		while len(recentFile) > 0:
			f = self.model.index(recentFile)
			if f.isValid():
				self.tree.scrollTo(f)
				self.tree.setExpanded(f, True)
				break
			parentDir = os.path.dirname(recentFile)
			if parentDir == recentFile:
				break
			recentFile = parentDir

		self.actionHandler.bindAction("Open Selected Files", UIAction(
			lambda context: self.openSelectedFiles(),
			lambda context: self.areFilesSelected()))
		self.contextMenu.addAction("Open Selected Files", "Open")

	def contextMenuEvent(self, event):
		self.contextMenuManager.show(self.contextMenu, self.actionHandler)

	def onDoubleClick(self, index):
		self.openSelectedFiles()

	def openSelectedFiles(self):
		failedToOpen = []
		files = set()

		for index in self.tree.selectionModel().selectedIndexes():
			if self.model.fileInfo(index).isFile():
				files.add(self.model.fileInfo(index).absoluteFilePath())

		for filename in files:
				QSettings().setValue("triage/recentFile", filename)

				f = FileContext.openFilename(filename)
				if not f:
					failedToOpen.append(filename)
					continue

				f.createBinaryViews()
				for data in f.getAllDataViews():
					Settings().set_string("analysis.mode", Settings().get_string("triage.analysisMode"), data)
					Settings().set_bool("triage.preferSummaryView", True, data)
					if data.view_type != "Raw":
						linearSweepMode = Settings().get_string("triage.linearSweep")
						if linearSweepMode == "none":
							Settings().set_bool("analysis.linearSweep.autorun", False, data)
						elif linearSweepMode == "partial":
							Settings().set_bool("analysis.linearSweep.autorun", True, data)
							Settings().set_bool("analysis.linearSweep.controlFlowGraph", False, data)
						elif linearSweepMode == "full":
							Settings().set_bool("analysis.linearSweep.autorun", True, data)
							Settings().set_bool("analysis.linearSweep.controlFlowGraph", True, data)

				self.context.openFileContext(f)

		if len(failedToOpen) > 0:
			QMessageBox.critical(self, "Error", "Unable to open:\n" + "\n".join(failedToOpen))

	def areFilesSelected(self):
		return self.tree.selectionModel().hasSelection()
Esempio n. 5
0
class MainWindow(QMainWindow):
    def __init__(self, app):
        super(MainWindow, self).__init__()
        self._app = app
        self._selectedIndex = None

        # model
        nodeFactory = NodeFactory()
        rootNode = nodeFactory.create(NodeType.General, 'Root')
        # for i in range(10000):  # for testing
        childNode0 = nodeFactory.create(NodeType.General, 'RightPirateLeg',
                                        rootNode)
        childNode1 = nodeFactory.create(NodeType.General, 'RightPirateLeg_END',
                                        childNode0)
        childNode2 = nodeFactory.create(NodeType.General, 'LeftFemur',
                                        rootNode)
        childNode3 = nodeFactory.create(NodeType.Sphere, 'LeftTibia',
                                        childNode2)
        childNode4 = nodeFactory.create(NodeType.Sphere, 'LeftFoot',
                                        childNode3)
        transform = childNode4.component(ComponentType.Transform)
        qTransform = transform.component()
        translation = qTransform.translation()
        translation.setX(5)
        qTransform.setTranslation(translation)

        # childNode5 = nodeFactory.create(NodeType.Box, 'LeftFoot_END', childNode4)
        self._model = SceneGraphModel(rootNode)

        self._sceneView = SceneView(rootNode.entity())
        self._container = self.createWindowContainer(self._sceneView)

        # scene graph view
        self._treeView = QTreeView()
        self._treeView.setModel(self._model)
        self._treeView.setHeaderHidden(True)
        self._treeView.setAlternatingRowColors(True)

        dockWidget = QDockWidget()
        dockWidget.setWidget(self._treeView)
        dockWidget.setWindowTitle('Scene Graph')
        dockWidget.setObjectName('sceneGraph')
        sceneGraphToggleAction = dockWidget.toggleViewAction()
        self.addDockWidget(Qt.LeftDockWidgetArea, dockWidget)

        # property editor
        propertyEditor = PropertyEditor(self._model, nodeFactory,
                                        FieldFactory())

        dockWidget = QDockWidget()
        dockWidget.setWidget(propertyEditor)
        dockWidget.setWindowTitle('Property Editor')
        dockWidget.setObjectName('propertyEditor')
        propertyEditorToggleAction = dockWidget.toggleViewAction()
        self.addDockWidget(Qt.RightDockWidgetArea, dockWidget)

        # menu
        menuBar = self.menuBar()
        menu = menuBar.addMenu('&File')
        exitAction = menu.addAction('E&xit')
        exitAction.triggered.connect(self.close)
        menu.addAction(exitAction)
        menu = menuBar.addMenu('&Windows')
        menu.addAction(sceneGraphToggleAction)
        menu.addAction(propertyEditorToggleAction)
        menuBar.addMenu(menu)

        # central widget
        #button = QPushButton()
        self.setCentralWidget(self._container)

        # selection change event
        selectionModel = self._treeView.selectionModel()
        selectionModel.currentChanged.connect(propertyEditor.changeSelection)

        # click event
        #button.clicked.connect(self.buttonClicked)

    def selectedNode(self):
        indices = self._treeView.selectedIndexes()
        result = None
        if len(indices) == 1:
            self._selectedIndex = indices[0]
            result = self._selectedIndex.internalPointer()
        return result

    def commitChange(self):
        self._model.dataChanged.emit(self._selectedIndex, self._selectedIndex)

    def closeEvent(self, event):
        self._app.exit()

    # data change example
    def buttonClicked(self):
        node = self.selectedNode()
        if not node:
            return
        general = node.component(ComponentType.General)
        general.setName('CLICKED')
        self.commitChange()
Esempio n. 6
0
class ProjectWidget(QSplitter):

    treeCurrentItemChanged = Signal(Container)

    def __init__(self, parent, f=...):
        super().__init__(parent, f)
        self.project = Project()
        self.current_tree_item: Optional[Container] = None

        self.main_splitter = self  # inheritance from QSplitter may not be preserved
        self.main_splitter.setOrientation(Qt.Horizontal)
        self.left_splitter = QSplitter(Qt.Vertical)
        self.right_splitter = QSplitter(Qt.Vertical)
        self.main_splitter.addWidget(self.left_splitter)
        self.main_splitter.addWidget(self.right_splitter)

        # self.w_parameters = ParametersWidget(self.project.bundle)
        self.w_parameters_tree = QTreeView()
        self.w_parameters_tree.setModel(self.project.parameters_model)
        delegate = ParametersTableDelegate()
        self.w_parameters_tree.setItemDelegate(delegate)
        self.selection_model_parameters = self.w_parameters_tree.selectionModel(
        )
        self.selection_model_parameters.currentRowChanged.connect(
            self._on_tree_row_changed)

        self.w_curves_tree = QTreeView()
        self.w_curves_tree.setModel(self.project.curves_model)
        delegate = CurvesTableDelegate()
        self.w_curves_tree.setItemDelegate(delegate)
        self.selection_model_curves = self.w_curves_tree.selectionModel()
        self.selection_model_curves.currentRowChanged.connect(
            self._on_tree_row_changed)

        self.w_light_plot = LightPlotWidget(self.project.curves_model)
        self.w_rv_plot = RvPlotWidget(self.project.curves_model)

        self.left_splitter.addWidget(self.w_parameters_tree)
        self.left_splitter.addWidget(self.w_curves_tree)

        self.right_splitter.addWidget(self.w_light_plot)
        self.right_splitter.addWidget(self.w_rv_plot)

    def resizeEvent(self, arg__1: PySide2.QtGui.QResizeEvent):
        super().resizeEvent(arg__1)

    def save_session(self, settings: QSettings):
        settings.beginGroup('project_splitter')
        settings.setValue('horiz', self.main_splitter.saveState())
        settings.setValue('vert_trees', self.left_splitter.saveState())
        settings.setValue('vert_plots', self.right_splitter.saveState())
        settings.endGroup()
        settings.beginGroup('tree_columns')
        settings.setValue('params',
                          self.w_parameters_tree.header().saveState())
        settings.setValue('curves', self.w_curves_tree.header().saveState())
        settings.endGroup()

    def restore_session(self, settings: QSettings):
        settings.beginGroup('project_splitter')
        try:
            self.main_splitter.restoreState(settings.value("horiz"))
            self.left_splitter.restoreState(settings.value("vert_trees"))
            self.right_splitter.restoreState(settings.value("vert_plots"))
        except AttributeError:
            pass
        settings.endGroup()
        settings.beginGroup('tree_columns')
        try:
            self.w_parameters_tree.header().restoreState(
                settings.value("params"))
            self.w_curves_tree.header().restoreState(settings.value("curves"))
        except AttributeError:
            pass
        settings.endGroup()

    @Slot(QModelIndex, QModelIndex)
    def _on_tree_row_changed(self, current: QModelIndex,
                             previous: QModelIndex):
        model = current.model()

        prev_item, prev_column = model.item_and_column(previous)
        curr_item, curr_column = model.item_and_column(current)

        logger().info(f'Selection changed {prev_item} -> {curr_item}')
        self.current_tree_item = curr_item

        self.treeCurrentItemChanged.emit(curr_item)

    @Slot()
    def add_lc(self):
        self.project.add_lc()

    @Slot()
    def add_rv(self):
        self.project.add_rv()

    @Slot()
    def del_curve(self):
        to_del = self.current_tree_item
        while to_del is not None and not isinstance(to_del, CurveContainer):
            to_del = to_del.parent()
        if QMessageBox.question(self, 'Deleting Curve Confirmation', f'Delete the curve "{to_del.objectName()}"?') \
                != QMessageBox.Yes:
            return
        self.project.delete_curve(to_del)
Esempio n. 7
0
class BindiffViewerDialog(QDialog):
	def __init__(self, bv, match_db, role, primary_be, secondary_be):
		super(BindiffViewerDialog, self).__init__()

		self.bv = bv
		self.primary_be = primary_be
		self.secondary_be = secondary_be
		self.role = role

		# UI
		self.match_model = BindiffMatchModel(bv, match_db, role, primary_be, secondary_be)

		self.match_view = QTreeView()
		self.match_view.setModel(self.match_model)

		self.match_view.setSelectionMode(QTreeView.ExtendedSelection)

		self.match_view.setContextMenuPolicy(Qt.CustomContextMenu)
		self.match_view.customContextMenuRequested.connect(self.match_view_context_menu_requested)
		self.match_view.doubleClicked.connect(self.match_view_double_clicked)

		self.match_view.setRootIsDecorated(False)
		self.match_view.setFont(binaryninjaui.getMonospaceFont(self))

		for i in range(len(self.match_model.column_infos)):
			self.match_view.resizeColumnToContents(i)

		self.match_view.setSortingEnabled(True)
		self.match_view.sortByColumn(0, Qt.AscendingOrder)

		layout = QVBoxLayout()
		layout.addWidget(self.match_view)

		self.setLayout(layout)
		self.setWindowTitle("BinDiff Viewer")
		self.resize(1000, 640)
		flags = self.windowFlags()
		flags |= Qt.WindowMaximizeButtonHint
		flags &= ~Qt.WindowContextHelpButtonHint
		self.setWindowFlags(flags)

	def match_view_double_clicked(self, index):
		if not index.isValid():
			assert(False)
			return
		if self.role == None:
			return

		entry = self.match_model.entries[index.row()]
		if self.role == 0:
			address = entry["address1"]
		elif self.role == 1:
			address = entry["address2"]
		else:
			assert(False)

		self.bv.navigate(self.bv.file.view, address)

	def match_view_context_menu_requested(self, pos):
		if self.role == None:
			return

		selected_indices = self.match_view.selectionModel().selectedIndexes()

		# This may return each row multiple times, so we uniquify
		selected = set([i.row() for i in selected_indices])

		def action_port_symbols():
			for i in selected:
				self.port_symbols(i)

		menu = QMenu(self.match_view)
		menu.addAction("Port symbols", action_port_symbols)
		menu.exec_(self.match_view.mapToGlobal(pos))

	def port_symbols(self, i):
		if self.role == None:
			return

		entry = self.match_model.entries[i]
		target_index = self.role
		source_index = 1 if target_index == 0 else 0

		source_name = entry["name{}".format(source_index + 1)]
		target_address = entry["address{}".format(target_index + 1)]

		old_sym = self.bv.get_symbol_at(target_address)

		target_name = None
		if old_sym:
			target_name = old_sym.name
		target_text = target_name if target_name else "<unnamed>"

		if not source_name:
			bn.log_warn("Port symbols: {} @ {:x} has no source name, skipping".format(target_text, target_address))
			return

		if old_sym and not old_sym.auto:
			bn.log_warn("Port symbols: {} @ {:x} is already named, skipping".format(target_text, target_address))
			return

		bn.log_info("Port symbols: {} @ {:x} -> {}".format(target_text, target_address, source_name))
		new_sym = bn.Symbol(bn.SymbolType.FunctionSymbol, target_address, source_name)
		self.bv.define_user_symbol(new_sym)
Esempio n. 8
0
class Snippets(QDialog):

    def __init__(self, context, parent=None):
        super(Snippets, self).__init__(parent)
        # Create widgets
        self.setWindowModality(Qt.ApplicationModal)
        self.title = QLabel(self.tr("Snippet Editor"))
        self.saveButton = QPushButton(self.tr("&Save"))
        self.saveButton.setShortcut(QKeySequence(self.tr("Ctrl+S")))
        self.runButton = QPushButton(self.tr("&Run"))
        self.runButton.setShortcut(QKeySequence(self.tr("Ctrl+R")))
        self.closeButton = QPushButton(self.tr("Close"))
        self.clearHotkeyButton = QPushButton(self.tr("Clear Hotkey"))
        self.setWindowTitle(self.title.text())
        #self.newFolderButton = QPushButton("New Folder")
        self.browseButton = QPushButton("Browse Snippets")
        self.browseButton.setIcon(QIcon.fromTheme("edit-undo"))
        self.deleteSnippetButton = QPushButton("Delete")
        self.newSnippetButton = QPushButton("New Snippet")
        self.edit = QCodeEditor(HIGHLIGHT_CURRENT_LINE=False, SyntaxHighlighter=PythonHighlighter)
        self.edit.setPlaceholderText("python code")
        self.resetting = False
        self.columns = 3
        self.context = context

        self.keySequenceEdit = QKeySequenceEdit(self)
        self.currentHotkey = QKeySequence()
        self.currentHotkeyLabel = QLabel("")
        self.currentFileLabel = QLabel()
        self.currentFile = ""
        self.snippetDescription = QLineEdit()
        self.snippetDescription.setPlaceholderText("optional description")

        #Set Editbox Size
        font = getMonospaceFont(self)
        self.edit.setFont(font)
        font = QFontMetrics(font)
        self.edit.setTabStopWidth(4 * font.width(' ')); #TODO, replace with settings API

        #Files
        self.files = QFileSystemModel()
        self.files.setRootPath(snippetPath)
        self.files.setNameFilters(["*.py"])

        #Tree
        self.tree = QTreeView()
        self.tree.setModel(self.files)
        self.tree.setSortingEnabled(True)
        self.tree.hideColumn(2)
        self.tree.sortByColumn(0, Qt.AscendingOrder)
        self.tree.setRootIndex(self.files.index(snippetPath))
        for x in range(self.columns):
            #self.tree.resizeColumnToContents(x)
            self.tree.header().setSectionResizeMode(x, QHeaderView.ResizeToContents)
        treeLayout = QVBoxLayout()
        treeLayout.addWidget(self.tree)
        treeButtons = QHBoxLayout()
        #treeButtons.addWidget(self.newFolderButton)
        treeButtons.addWidget(self.browseButton)
        treeButtons.addWidget(self.newSnippetButton)
        treeButtons.addWidget(self.deleteSnippetButton)
        treeLayout.addLayout(treeButtons)
        treeWidget = QWidget()
        treeWidget.setLayout(treeLayout)

        # Create layout and add widgets
        buttons = QHBoxLayout()
        buttons.addWidget(self.clearHotkeyButton)
        buttons.addWidget(self.keySequenceEdit)
        buttons.addWidget(self.currentHotkeyLabel)
        buttons.addWidget(self.closeButton)
        buttons.addWidget(self.runButton)
        buttons.addWidget(self.saveButton)

        description = QHBoxLayout()
        description.addWidget(QLabel(self.tr("Description: ")))
        description.addWidget(self.snippetDescription)

        vlayoutWidget = QWidget()
        vlayout = QVBoxLayout()
        vlayout.addLayout(description)
        vlayout.addWidget(self.edit)
        vlayout.addLayout(buttons)
        vlayoutWidget.setLayout(vlayout)

        hsplitter = QSplitter()
        hsplitter.addWidget(treeWidget)
        hsplitter.addWidget(vlayoutWidget)

        hlayout = QHBoxLayout()
        hlayout.addWidget(hsplitter)

        self.showNormal() #Fixes bug that maximized windows are "stuck"
        #Because you can't trust QT to do the right thing here
        if (sys.platform == "darwin"):
            self.settings = QSettings("Vector35", "Snippet Editor")
        else:
            self.settings = QSettings("Vector 35", "Snippet Editor")
        if self.settings.contains("ui/snippeteditor/geometry"):
            self.restoreGeometry(self.settings.value("ui/snippeteditor/geometry"))
        else:
            self.edit.setMinimumWidth(80 * font.averageCharWidth())
            self.edit.setMinimumHeight(30 * font.lineSpacing())

        # Set dialog layout
        self.setLayout(hlayout)

        # Add signals
        self.saveButton.clicked.connect(self.save)
        self.closeButton.clicked.connect(self.close)
        self.runButton.clicked.connect(self.run)
        self.clearHotkeyButton.clicked.connect(self.clearHotkey)
        self.tree.selectionModel().selectionChanged.connect(self.selectFile)
        self.newSnippetButton.clicked.connect(self.newFileDialog)
        self.deleteSnippetButton.clicked.connect(self.deleteSnippet)
        #self.newFolderButton.clicked.connect(self.newFolder)
        self.browseButton.clicked.connect(self.browseSnippets)

        if self.settings.contains("ui/snippeteditor/selected"):
            selectedName = self.settings.value("ui/snippeteditor/selected")
            self.tree.selectionModel().select(self.files.index(selectedName), QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows)
            if self.tree.selectionModel().hasSelection():
                self.selectFile(self.tree.selectionModel().selection(), None)
                self.edit.setFocus()
                cursor = self.edit.textCursor()
                cursor.setPosition(self.edit.document().characterCount()-1)
                self.edit.setTextCursor(cursor)
            else:
                self.readOnly(True)
        else:
            self.readOnly(True)


    @staticmethod
    def registerAllSnippets():
        for action in list(filter(lambda x: x.startswith("Snippets\\"), UIAction.getAllRegisteredActions())):
            if action == "Snippets\\Snippet Editor...":
                continue
            UIActionHandler.globalActions().unbindAction(action)
            Menu.mainMenu("Tools").removeAction(action)
            UIAction.unregisterAction(action)

        for snippet in includeWalk(snippetPath, ".py"):
            snippetKeys = None
            (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(snippet)
            actionText = actionFromSnippet(snippet, snippetDescription)
            if snippetCode:
                if snippetKeys == None:
                    UIAction.registerAction(actionText)
                else:
                    UIAction.registerAction(actionText, snippetKeys)
                UIActionHandler.globalActions().bindAction(actionText, UIAction(makeSnippetFunction(snippetCode)))
                Menu.mainMenu("Tools").addAction(actionText, "Snippets")

    def clearSelection(self):
        self.keySequenceEdit.clear()
        self.currentHotkey = QKeySequence()
        self.currentHotkeyLabel.setText("")
        self.currentFileLabel.setText("")
        self.snippetDescription.setText("")
        self.edit.clear()
        self.tree.clearSelection()
        self.currentFile = ""

    def reject(self):
        self.settings.setValue("ui/snippeteditor/geometry", self.saveGeometry())

        if self.snippetChanged():
            question = QMessageBox.question(self, self.tr("Discard"), self.tr("You have unsaved changes, quit anyway?"))
            if question != QMessageBox.StandardButton.Yes:
                return
        self.accept()

    def browseSnippets(self):
        url = QUrl.fromLocalFile(snippetPath)
        QDesktopServices.openUrl(url);

    def newFolder(self):
        (folderName, ok) = QInputDialog.getText(self, self.tr("Folder Name"), self.tr("Folder Name: "))
        if ok and folderName:
            index = self.tree.selectionModel().currentIndex()
            selection = self.files.filePath(index)
            if QFileInfo(selection).isDir():
                QDir(selection).mkdir(folderName)
            else:
                QDir(snippetPath).mkdir(folderName)

    def selectFile(self, new, old):
        if (self.resetting):
            self.resetting = False
            return
        if len(new.indexes()) == 0:
            self.clearSelection()
            self.currentFile = ""
            self.readOnly(True)
            return
        newSelection = self.files.filePath(new.indexes()[0])
        self.settings.setValue("ui/snippeteditor/selected", newSelection)
        if QFileInfo(newSelection).isDir():
            self.readOnly(True)
            self.clearSelection()
            self.currentFile = ""
            return

        if old and old.length() > 0:
            oldSelection = self.files.filePath(old.indexes()[0])
            if not QFileInfo(oldSelection).isDir() and self.snippetChanged():
                question = QMessageBox.question(self, self.tr("Discard"), self.tr("Snippet changed. Discard changes?"))
                if question != QMessageBox.StandardButton.Yes:
                    self.resetting = True
                    self.tree.selectionModel().select(old, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows)
                    return False

        self.currentFile = newSelection
        self.loadSnippet()

    def loadSnippet(self):
        self.currentFileLabel.setText(QFileInfo(self.currentFile).baseName())
        (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(self.currentFile)
        self.snippetDescription.setText(snippetDescription) if snippetDescription else self.snippetDescription.setText("")
        self.keySequenceEdit.setKeySequence(snippetKeys) if snippetKeys else self.keySequenceEdit.setKeySequence(QKeySequence(""))
        self.edit.setPlainText(snippetCode) if snippetCode else self.edit.setPlainText("")
        self.readOnly(False)

    def newFileDialog(self):
        (snippetName, ok) = QInputDialog.getText(self, self.tr("Snippet Name"), self.tr("Snippet Name: "))
        if ok and snippetName:
            if not snippetName.endswith(".py"):
                snippetName += ".py"
            index = self.tree.selectionModel().currentIndex()
            selection = self.files.filePath(index)
            if QFileInfo(selection).isDir():
                path = os.path.join(selection, snippetName)
            else:
                path = os.path.join(snippetPath, snippetName)
                self.readOnly(False)
            open(path, "w").close()
            self.tree.setCurrentIndex(self.files.index(path))
            log_debug("Snippet %s created." % snippetName)

    def readOnly(self, flag):
        self.keySequenceEdit.setEnabled(not flag)
        self.snippetDescription.setReadOnly(flag)
        self.edit.setReadOnly(flag)
        if flag:
            self.snippetDescription.setDisabled(True)
            self.edit.setDisabled(True)
        else:
            self.snippetDescription.setEnabled(True)
            self.edit.setEnabled(True)

    def deleteSnippet(self):
        selection = self.tree.selectedIndexes()[::self.columns][0] #treeview returns each selected element in the row
        snippetName = self.files.fileName(selection)
        question = QMessageBox.question(self, self.tr("Confirm"), self.tr("Confirm deletion: ") + snippetName)
        if (question == QMessageBox.StandardButton.Yes):
            log_debug("Deleting snippet %s." % snippetName)
            self.clearSelection()
            self.files.remove(selection)
            self.registerAllSnippets()

    def snippetChanged(self):
        if (self.currentFile == "" or QFileInfo(self.currentFile).isDir()):
            return False
        (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(self.currentFile)
        if snippetKeys == None and not self.keySequenceEdit.keySequence().isEmpty():
            return True
        if snippetKeys != None and snippetKeys != self.keySequenceEdit.keySequence().toString():
            return True
        return self.edit.toPlainText() != snippetCode or \
               self.snippetDescription.text() != snippetDescription

    def save(self):
        log_debug("Saving snippet %s" % self.currentFile)
        outputSnippet = codecs.open(self.currentFile, "w", "utf-8")
        outputSnippet.write("#" + self.snippetDescription.text() + "\n")
        outputSnippet.write("#" + self.keySequenceEdit.keySequence().toString() + "\n")
        outputSnippet.write(self.edit.toPlainText())
        outputSnippet.close()
        self.registerAllSnippets()

    def run(self):
        if self.context == None:
            log_warn("Cannot run snippets outside of the UI at this time.")
            return
        if self.snippetChanged():
            question = QMessageBox.question(self, self.tr("Confirm"), self.tr("You have unsaved changes, must save first. Save?"))
            if (question == QMessageBox.StandardButton.No):
                return
            else:
                self.save()
        actionText = actionFromSnippet(self.currentFile, self.snippetDescription.text())
        UIActionHandler.globalActions().executeAction(actionText, self.context)

        log_debug("Saving snippet %s" % self.currentFile)
        outputSnippet = codecs.open(self.currentFile, "w", "utf-8")
        outputSnippet.write("#" + self.snippetDescription.text() + "\n")
        outputSnippet.write("#" + self.keySequenceEdit.keySequence().toString() + "\n")
        outputSnippet.write(self.edit.toPlainText())
        outputSnippet.close()
        self.registerAllSnippets()

    def clearHotkey(self):
        self.keySequenceEdit.clear()
Esempio n. 9
0
class WTreeEdit(QWidget):
    """TreeEdit widget is to show and edit all of the pyleecan objects data."""

    # Signals
    dataChanged = Signal()

    def __init__(self, obj, *args, **kwargs):
        QWidget.__init__(self, *args, **kwargs)

        self.class_dict = ClassInfo().get_dict()
        self.treeDict = None  # helper to track changes
        self.obj = obj  # the object
        self.is_save_needed = False

        self.model = TreeEditModel(obj)

        self.setupUi()

        # === Signals ===
        self.selectionModel.selectionChanged.connect(self.onSelectionChanged)
        self.treeView.collapsed.connect(self.onItemCollapse)
        self.treeView.expanded.connect(self.onItemExpand)
        self.treeView.customContextMenuRequested.connect(self.openContextMenu)
        self.model.dataChanged.connect(self.onDataChanged)
        self.dataChanged.connect(self.setSaveNeeded)

        # === Finalize ===
        # set 'root' the selected item and resize columns
        self.treeView.setCurrentIndex(self.treeView.model().index(0, 0))
        self.treeView.resizeColumnToContents(0)

    def setupUi(self):
        """Setup the UI"""
        # === Widgets ===
        # TreeView
        self.treeView = QTreeView()
        # self.treeView.rootNode = model.invisibleRootItem()
        self.treeView.setModel(self.model)
        self.treeView.setAlternatingRowColors(False)

        # self.treeView.setColumnWidth(0, 150)
        self.treeView.setMinimumWidth(100)

        self.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.selectionModel = self.treeView.selectionModel()

        self.statusBar = QStatusBar()
        self.statusBar.setSizeGripEnabled(False)
        self.statusBar.setSizePolicy(QSizePolicy.Expanding,
                                     QSizePolicy.Maximum)
        self.statusBar.setStyleSheet(
            "QStatusBar {border: 1px solid rgb(200, 200, 200)}")
        self.saveLabel = QLabel("unsaved")
        self.saveLabel.setVisible(False)
        self.statusBar.addPermanentWidget(self.saveLabel)

        # Splitters
        self.leftSplitter = QSplitter()
        self.leftSplitter.setStretchFactor(0, 0)
        self.leftSplitter.setStretchFactor(1, 1)

        # === Layout ===
        # Horizontal Div.
        self.hLayout = QVBoxLayout()
        self.hLayout.setContentsMargins(0, 0, 0, 0)
        self.hLayout.setSpacing(0)

        # add widgets to layout
        self.hLayout.addWidget(self.leftSplitter)
        self.hLayout.addWidget(self.statusBar)

        # add widgets
        self.leftSplitter.addWidget(self.treeView)

        self.setLayout(self.hLayout)

    def update(self, obj):
        """Check if object has changed and update tree in case."""
        if not obj is self.obj:
            self.obj = obj
            self.model = TreeEditModel(obj)
            self.treeView.setModel(self.model)
            self.model.dataChanged.connect(self.onDataChanged)
            self.selectionModel = self.treeView.selectionModel()
            self.selectionModel.selectionChanged.connect(
                self.onSelectionChanged)
            self.treeView.setCurrentIndex(self.treeView.model().index(0, 0))
            self.setSaveNeeded(True)

    def setSaveNeeded(self, state=True):
        self.is_save_needed = state
        self.saveLabel.setVisible(state)

    def openContextMenu(self, point):
        """Generate and open context the menu at the given point position."""
        index = self.treeView.indexAt(point)
        pos = QtGui.QCursor.pos()

        if not index.isValid():
            return

        # get the data
        item = self.model.item(index)
        obj_info = self.model.get_obj_info(item)

        # init the menu
        menu = TreeEditContextMenu(obj_dict=obj_info, parent=self)
        menu.exec_(pos)

        self.onSelectionChanged(self.selectionModel.selection())

    def onItemCollapse(self, index):
        """Slot for item collapsed"""
        # dynamic resize
        for ii in range(3):
            self.treeView.resizeColumnToContents(ii)

    def onItemExpand(self, index):
        """Slot for item expand"""
        # dynamic resize
        for ii in range(3):
            self.treeView.resizeColumnToContents(ii)

    def onDataChanged(self, first=None, last=None):
        """Slot for changed data"""
        self.dataChanged.emit()
        self.onSelectionChanged(self.selectionModel.selection())

    def onSelectionChanged(self, itemSelection):
        """Slot for changed item selection"""
        # get the index
        if itemSelection.indexes():
            index = itemSelection.indexes()[0]
        else:
            index = self.treeView.model().index(0, 0)
            self.treeView.setCurrentIndex(index)
            return

        # get the data
        item = self.model.item(index)
        obj = item.object()
        typ = type(obj).__name__
        obj_info = self.model.get_obj_info(item)
        ref_typ = obj_info["ref_typ"] if obj_info else None

        # set statusbar information on class typ
        msg = f"{typ} (Ref: {ref_typ})" if ref_typ else f"{typ}"
        self.statusBar.showMessage(msg)

        # --- choose the respective widget by class type ---
        # numpy array -> table editor
        if typ == "ndarray":
            widget = WTableData(obj, editable=True)
            widget.dataChanged.connect(self.dataChanged.emit)

        elif typ == "MeshSolution":
            widget = WMeshSolution(obj)  # only a view (not editable)

        # list (no pyleecan type, non empty) -> table editor
        # TODO add another widget for lists of non 'primitive' types (e.g. DataND)
        elif isinstance(obj, list) and not self.isListType(ref_typ) and obj:
            widget = WTableData(obj, editable=True)
            widget.dataChanged.connect(self.dataChanged.emit)

        # generic editor
        else:
            # widget = SimpleInputWidget().generate(obj)
            widget = WTableParameterEdit(obj)
            widget.dataChanged.connect(self.dataChanged.emit)

        # show the widget
        if self.leftSplitter.widget(1) is None:
            self.leftSplitter.addWidget(widget)
        else:
            self.leftSplitter.replaceWidget(1, widget)
            widget.setParent(
                self.leftSplitter)  # workaround for PySide2 replace bug
            widget.show()
        pass

    def isListType(self, typ):
        if not typ:
            return False
        return typ[0] == "[" and typ[-1] == "]" and typ[1:-1] in self.class_dict

    def isDictType(self, typ):
        if not typ:
            return False
        return typ[0] == "{" and typ[-1] == "}" and typ[1:-1] in self.class_dict
    def import_accounts_dialog(self):
        self.import_accounts_window.setWindowTitle(_("Import accounts"))
        self.import_accounts_window.setWindowIcon(self.switcher_logo)
        self.import_accounts_window.setMinimumWidth(400)

        layout = QVBoxLayout()
        self.import_accounts_window.setLayout(layout)

        text_label = QLabel(_("Select accounts to import"))
        import_accounts_list = QTreeView()
        import_button = QPushButton()

        model = QStandardItemModel()
        model.setHorizontalHeaderLabels(
            [_('Login name'), _('Steam name'),
             _('Steam UID')])
        import_accounts_list.setModel(model)
        import_accounts_list.setUniformRowHeights(True)
        import_accounts_list.setEditTriggers(QAbstractItemView.NoEditTriggers)
        import_accounts_list.setSelectionMode(QTreeView.MultiSelection)

        layout.addWidget(text_label)
        layout.addWidget(import_accounts_list)
        layout.addWidget(import_button)

        installed_accounts = self.switcher.settings.get("users").keys()
        disabled = []
        for uid, steam_user in self.switcher.load_loginusers().items():
            account_row = [
                QStandardItem(steam_user.get("AccountName")),
                QStandardItem(steam_user.get("PersonaName")),
                QStandardItem(uid)
            ]
            # account_row[0].setCheckable(True)
            account_row[2].setEnabled(False)

            if steam_user.get("AccountName") in installed_accounts:
                # account_row = [ x.setEnabled(False) for x in account_row]
                disabled.append(account_row)
            else:
                model.appendRow(account_row)

        # model.appendRows(disabled) #Existing accounts grayed out
        import_accounts_list.resizeColumnToContents(0)

        def import_accounts():
            selected_accounts = import_accounts_list.selectionModel(
            ).selectedRows()
            for account in selected_accounts:
                self.switcher.add_account(account.data(0))
            self.steamapi_refresh()
            self.import_accounts_window.hide()

        def button_enabled():
            num_selected = len(
                import_accounts_list.selectionModel().selectedRows())
            import_button.setText(
                _("Import {0} accounts").format(num_selected))
            if num_selected:
                import_button.setEnabled(True)
            else:
                import_button.setEnabled(False)

        button_enabled()

        import_accounts_list.selectionModel().selectionChanged.connect(
            button_enabled)
        import_button.clicked.connect(import_accounts)

        self.import_accounts_window.show()
Esempio n. 11
0
class FE14ChapterSpawnsTab(QWidget):
    def __init__(self):
        super().__init__()
        self.chapter_data = None
        self.dispos_model = None
        self.dispos = None
        self.terrain = None
        self.tiles_model = None
        self.terrain_mode = False
        self.initialized_selection_signal = False
        self.selected_faction = None

        left_panel_container = QWidget()
        left_panel_layout = QVBoxLayout()
        self.toggle_editor_type_checkbox = QCheckBox()
        self.toggle_editor_type_checkbox.setText("Spawns/Terrain")
        self.toggle_editor_type_checkbox.setChecked(True)
        self.toggle_editor_type_checkbox.stateChanged.connect(
            self._on_mode_change_requested)
        self.toggle_coordinate_type_checkbox = QCheckBox()
        self.toggle_coordinate_type_checkbox.setText(
            "Coordinate (1)/Coordinate (2)")
        self.toggle_coordinate_type_checkbox.setChecked(True)
        self.toggle_coordinate_type_checkbox.stateChanged.connect(
            self._on_coordinate_change_requested)
        self.tree_view = QTreeView()
        left_panel_layout.addWidget(self.toggle_editor_type_checkbox)
        left_panel_layout.addWidget(self.toggle_coordinate_type_checkbox)
        left_panel_layout.addWidget(self.tree_view)
        left_panel_container.setLayout(left_panel_layout)

        self.grid = FE14MapGrid()
        self.dispos_scroll, self.dispos_form = PropertyForm.create_with_scroll(
            dispo.SPAWN_TEMPLATE)
        self.terrain_form, self.terrain_persistent_editors, self.tile_form = _create_terrain_form(
        )

        self.organizer = QSplitter()
        self.organizer.addWidget(left_panel_container)
        self.organizer.addWidget(self.grid)
        self.organizer.addWidget(self.dispos_scroll)

        main_layout = QVBoxLayout(self)
        main_layout.addWidget(self.organizer)
        self.setLayout(main_layout)

        self.add_faction_shortcut = QShortcut(QKeySequence("Ctrl+F"), self)
        self.add_item_shortcut = QShortcut(QKeySequence("Ctrl+N"), self)

        self.grid.focused_spawn_changed.connect(self._on_focused_spawn_changed)
        self.add_faction_shortcut.activated.connect(
            self._on_add_faction_requested)
        self.add_item_shortcut.activated.connect(self._on_add_item_requested)
        self.dispos_form.editors["PID"].editingFinished.connect(
            self._on_pid_field_changed)
        self.dispos_form.editors["Team"].currentIndexChanged.connect(
            self._on_team_field_changed)
        self.dispos_form.editors["Coordinate (1)"].textChanged.connect(
            self._on_coordinate_1_field_changed)
        self.dispos_form.editors["Coordinate (2)"].textChanged.connect(
            self._on_coordinate_2_field_changed)

    def update_chapter_data(self, chapter_data):
        self.chapter_data = chapter_data
        if self.chapter_data and self.chapter_data.dispos and self.chapter_data.terrain:
            self.setEnabled(True)
            self.dispos = self.chapter_data.dispos
            self.dispos_model = DisposModel(self.dispos)
            self.terrain = self.chapter_data.terrain
            self.tiles_model = TilesModel(self.terrain.tiles)
            if self.terrain_mode:
                self.tree_view.setModel(self.tiles_model)
            else:
                self.tree_view.setModel(self.dispos_model)
            self.tree_view.selectionModel().currentChanged.connect(
                self._on_tree_selection_changed)
            self.grid.set_chapter_data(chapter_data)
            self._update_forms(self.terrain, None, None)
        else:
            self.setEnabled(False)
            self.grid.clear()

    def _update_forms(self, terrain_target, tile_target, dispos_target):
        self.dispos_form.update_target(dispos_target)
        self.tile_form.update_target(tile_target)
        for editor in self.terrain_persistent_editors:
            editor.update_target(terrain_target)

    def _on_focused_spawn_changed(self, spawn):
        self.dispos_form.update_target(spawn)

    def _on_mode_change_requested(self, state):
        self.organizer.widget(2).setParent(None)
        self.terrain_mode = state != QtGui.Qt.Checked
        if not self.terrain_mode:
            self.grid.transition_to_dispos_mode()
            self.organizer.addWidget(self.dispos_scroll)
            self.tree_view.setModel(self.dispos_model)
        else:
            self.grid.transition_to_terrain_mode()
            self.organizer.addWidget(self.terrain_form)
            self.tree_view.setModel(self.tiles_model)
        self.tree_view.selectionModel().currentChanged.connect(
            self._on_tree_selection_changed)

    def _on_coordinate_change_requested(self, _state):
        self.grid.toggle_coordinate_key()

    def _on_tree_selection_changed(self, index: QModelIndex, _previous):
        data = index.data(QtCore.Qt.UserRole)
        if self.terrain_mode:
            self.grid.selected_tile = data
            self.tile_form.update_target(data)
        else:
            if type(data) == PropertyContainer:
                self.grid.select_spawn(data)
                self.selected_faction = None
            else:
                self.selected_faction = data

    def _on_add_faction_requested(self):
        if self.dispos_model:
            (faction_name, ok) = QInputDialog.getText(self,
                                                      "Enter a faction name.",
                                                      "Name:")
            if ok:
                self.dispos_model.add_faction(faction_name)

    def _on_add_item_requested(self):
        if not self.chapter_data or (not self.terrain_mode
                                     and not self.selected_faction):
            return
        if self.terrain_mode:
            self.tiles_model.add_tile()
        else:
            self.dispos_model.add_spawn_to_faction(self.selected_faction)
            spawn = self.selected_faction.spawns[-1]
            self.grid.add_spawn_to_map(spawn)

    def _on_coordinate_1_field_changed(self, _text):
        new_position = self._parse_coordinate_field(
            self.dispos_form.editors["Coordinate (1)"])
        self.grid.update_focused_spawn_position(new_position, "Coordinate (1)")

    def _on_coordinate_2_field_changed(self, _text):
        new_position = self._parse_coordinate_field(
            self.dispos_form.editors["Coordinate (2)"])
        self.grid.update_focused_spawn_position(new_position, "Coordinate (2)")

    @staticmethod
    def _parse_coordinate_field(field):
        split_text = field.displayText().split()
        result = []
        for entry in split_text:
            result.append(int(entry, 16))
        return result

    def _on_team_field_changed(self):
        if self.chapter_data and self.chapter_data.dispos:
            self.grid.update_team_for_focused_spawn()

    def _on_pid_field_changed(self):
        self.dispos_model.refresh_spawn(self.grid.selected_spawns[-1])
Esempio n. 12
0
class SVDBrowser(QDialog):
    def __init__(self, context, parent=None):
        super(SVDBrowser, self).__init__(parent)
        QWebEngineProfile.defaultProfile().downloadRequested.connect(
            self.on_downloadRequested)

        # Create widgets
        #self.setWindowModality(Qt.ApplicationModal)
        self.title = QLabel(self.tr("SVD Browser"))
        self.closeButton = QPushButton(self.tr("Close"))
        self.setWindowTitle(self.title.text())
        self.browseButton = QPushButton("Browse SVD Folder")
        self.deleteSvdButton = QPushButton("Delete")
        self.applySvdButton = QPushButton("Apply SVD")
        self.view = QWebEngineView()
        url = "https://developer.arm.com/tools-and-software/embedded/cmsis/cmsis-search"
        self.view.load(QUrl(url))
        self.columns = 3
        self.context = context

        self.currentFileLabel = QLabel()
        self.currentFile = ""

        #Files
        self.files = QFileSystemModel()
        self.files.setRootPath(svdPath)
        self.files.setNameFilters(["*.svd", "*.patched"])

        #Tree
        self.tree = QTreeView()
        self.tree.setModel(self.files)
        self.tree.setSortingEnabled(True)
        self.tree.hideColumn(2)
        self.tree.sortByColumn(0, Qt.AscendingOrder)
        self.tree.setRootIndex(self.files.index(svdPath))
        for x in range(self.columns):
            #self.tree.resizeColumnToContents(x)
            self.tree.header().setSectionResizeMode(
                x, QHeaderView.ResizeToContents)
        treeLayout = QVBoxLayout()
        treeLayout.addWidget(self.tree)
        treeButtons = QHBoxLayout()
        #treeButtons.addWidget(self.newFolderButton)
        treeButtons.addWidget(self.browseButton)
        treeButtons.addWidget(self.applySvdButton)
        treeButtons.addWidget(self.deleteSvdButton)
        treeLayout.addLayout(treeButtons)
        treeWidget = QWidget()
        treeWidget.setLayout(treeLayout)

        # Create layout and add widgets
        buttons = QHBoxLayout()
        buttons.addWidget(self.closeButton)

        vlayoutWidget = QWidget()
        vlayout = QVBoxLayout()
        vlayout.addWidget(self.view)
        vlayout.addLayout(buttons)
        vlayoutWidget.setLayout(vlayout)

        hsplitter = QSplitter()
        hsplitter.addWidget(treeWidget)
        hsplitter.addWidget(vlayoutWidget)

        hlayout = QHBoxLayout()
        hlayout.addWidget(hsplitter)

        self.showMaximized()  #Fixes bug that maximized windows are "stuck"
        #Because you can't trust QT to do the right thing here

        # Set dialog layout
        self.setLayout(hlayout)

        # Add signals
        self.closeButton.clicked.connect(self.close)
        self.tree.selectionModel().selectionChanged.connect(self.selectFile)
        self.applySvdButton.clicked.connect(self.applySvd)
        self.deleteSvdButton.clicked.connect(self.deleteSvd)
        self.browseButton.clicked.connect(self.browseSvd)

    def browseSvd(self):
        url = QUrl.fromLocalFile(svdPath)
        QDesktopServices.openUrl(url)

    def selectFile(self, new, old):
        if len(new.indexes()) == 0:
            self.tree.clearSelection()
            self.currentFile = ""
            return
        newSelection = self.files.filePath(new.indexes()[0])
        if QFileInfo(newSelection).isDir():
            self.tree.clearSelection()
            self.currentFile = ""
            return
        self.currentFile = newSelection

    def applySvd(self):
        selection = self.tree.selectedIndexes()[::self.columns][
            0]  #treeview returns each selected element in the row
        svdName = self.files.fileName(selection)
        if (svdName != ""):
            question = QMessageBox.question(
                self, self.tr("Confirm"),
                self.
                tr(f"Confirm applying {svdName} to {os.path.basename(self.context.file.filename)} : "
                   ))
            if (question == QMessageBox.StandardButton.Yes):
                log_debug("SVD Browser: Applying SVD %s." % svdName)
                load_svd(self.context, self.currentFile)
                self.close()

    def deleteSvd(self):
        selection = self.tree.selectedIndexes()[::self.columns][
            0]  #treeview returns each selected element in the row
        svdName = self.files.fileName(selection)
        question = QMessageBox.question(
            self, self.tr("Confirm"),
            self.tr("Confirm deletion: ") + svdName)
        if (question == QMessageBox.StandardButton.Yes):
            log_debug("SVD Browser: Deleting SVD %s." % svdName)
            self.files.remove(selection)
            self.tree.clearSelection()

    def on_downloadRequested(self, download):
        old_path = download.url().path()  # download.path()
        suffix = QFileInfo(old_path).suffix()
        if (suffix.lower() in ["zip", "svd", "pack", "patched"]):
            log_debug(f"SVD Browser: Downloading {str(download.url())}")
            if suffix.lower() == "svd" or suffix.lower() == "patched":
                download.setDownloadDirectory(svdPath)
                download.accept()
            else:
                with TemporaryDirectory() as tempfolder:
                    log_debug(
                        f"SVD Browser: Downloading pack/zip to {tempfolder}")
                    fname = download.url().fileName()
                    r = requests.get(download.url().toString(),
                                     allow_redirects=True)
                    dlfile = os.path.join(tempfolder, fname)
                    open(dlfile, "wb").write(r.content)
                    '''
                    # TODO: See if the original QT Downloader can be fixed since it would
                    # help with situations where the user entered credentials in the browser.
                    download.setDownloadDirectory(tempfolder)
                    download.accept()
                    while not download.finished:
                        import time
                        time.sleep(100)
                    '''
                    if fname.endswith(".zip") or fname.endswith(".pack"):
                        destFolder = os.path.join(svdPath,
                                                  os.path.splitext(fname)[0])
                        log_debug(f"SVD Browser: Creating {destFolder}")
                        if not os.path.exists(destFolder):
                            os.mkdir(destFolder)
                        with ZipFile(dlfile, 'r') as zipp:
                            for ifname in zipp.namelist():
                                if ifname.endswith(".svd"):
                                    info = zipp.getinfo(ifname)
                                    info.filename = os.path.basename(
                                        info.filename)
                                    log_debug(
                                        f"SVD Browser: Extracting {info.filename} from {ifname}"
                                    )
                                    zipp.extract(info, path=destFolder)
                    else:
                        #Move file into place
                        shutil.move(dlfile, svdPath)
        else:
            show_message_box(
                "Invalid file",
                "That download does not appear to be a valid SVD/ZIP/PACK file."
            )
        download.cancel()