예제 #1
0
파일: project.py 프로젝트: mfkiwl/DGP
    def generate_model(self) -> Tuple[QStandardItemModel, QModelIndex]:
        """Generate a Qt Model based on the project structure."""
        model = QStandardItemModel()
        root = model.invisibleRootItem()

        # TODO: Add these icon resources to library or something so they are not loaded every time
        dgs_ico = QIcon('ui/assets/DGSIcon.xpm')
        flt_ico = QIcon('ui/assets/flight_icon.png')

        prj_header = QStandardItem(
            dgs_ico, "{name}: {path}".format(name=self.name,
                                             path=self.projectdir))
        prj_header.setEditable(False)
        fli_header = QStandardItem(flt_ico, "Flights")
        fli_header.setEditable(False)
        # TODO: Add a human readable identifier to flights
        first_flight = None
        for uid, flight in self.flights.items():
            fli_item = QStandardItem(flt_ico, "Flight: {}".format(flight.name))
            if first_flight is None:
                first_flight = fli_item
            fli_item.setToolTip("UUID: {}".format(uid))
            fli_item.setEditable(False)
            fli_item.setData(flight, QtCore.Qt.UserRole)

            gps_path, gps_uid = flight.gps_file
            gps = QStandardItem("GPS: {}".format(gps_uid))
            gps.setToolTip("File Path: {}".format(gps_path))
            gps.setEditable(False)
            gps.setData(gps_uid)  # For future use

            grav_path, grav_uid = flight.gravity_file
            if grav_path is not None:
                _, grav_fname = os.path.split(grav_path)
            else:
                grav_fname = '<None>'
            grav = QStandardItem("Gravity: {}".format(grav_fname))
            grav.setToolTip("File Path: {}".format(grav_path))
            grav.setEditable(False)
            grav.setData(grav_uid)  # For future use

            fli_item.appendRow(gps)
            fli_item.appendRow(grav)

            for line in flight:
                line_item = QStandardItem("Line {}:{}".format(
                    line.start, line.end))
                line_item.setEditable(False)
                fli_item.appendRow(line_item)
            fli_header.appendRow(fli_item)
        prj_header.appendRow(fli_header)

        root.appendRow(prj_header)
        self.log.debug("Tree Model generated")
        first_index = model.indexFromItem(first_flight)
        return model, first_index
예제 #2
0
class ListView(QListView):
    def __init__(self, *args, **kwargs):
        super(ListView, self).__init__(*args, **kwargs)
        # 模型
        self._model = QStandardItemModel(self)
        self.setModel(self._model)

        # 循环生成10个自定义控件
        for i in range(10):
            item = QStandardItem()
            self._model.appendRow(item)  # 添加item

            # 得到索引
            index = self._model.indexFromItem(item)
            widget = CustomWidget(str(i))
            item.setSizeHint(widget.sizeHint())  # 主要是调整item的高度
            # 设置自定义的widget
            self.setIndexWidget(index, widget)
예제 #3
0
 def updateImagesList(self):
     index, selected = self.getSelected()
     model = QStandardItemModel(self.imagesList)
     self.primaryImage.clear()
     selectedIndex = None
     for i in self.project["images"]:
         item = QStandardItem(i)
         item.setEditable(False)
         model.appendRow(item)
         self.primaryImage.addItem(i)
         if i == selected:
             selectedIndex = model.indexFromItem(item)
     self.imagesList.setModel(model)
     if selectedIndex != None:
         self.imagesList.setCurrentIndex(selectedIndex)
     if "primary" in self.project:
         self.primaryImage.setCurrentText(
             self.project["images"][self.project["primary"]])
예제 #4
0
class DataChangeUI(object):

    def __init__(self, window, uaclient, hub_manager):
        self.window = window
        self.uaclient = uaclient

        # FIXME IoT stuff
        self.hub_manager = hub_manager

        self._subhandler = DataChangeHandler(self.hub_manager)
        self._subscribed_nodes = []
        self.model = QStandardItemModel()
        self.window.ui.subView.setModel(self.model)
        self.window.ui.subView.horizontalHeader().setSectionResizeMode(1)

        self.window.ui.actionSubscribeDataChange.triggered.connect(self._subscribe)
        self.window.ui.actionUnsubscribeDataChange.triggered.connect(self._unsubscribe)

        # populate contextual menu
        self.window.ui.treeView.addAction(self.window.ui.actionSubscribeDataChange)
        self.window.ui.treeView.addAction(self.window.ui.actionUnsubscribeDataChange)

        # handle subscriptions
        self._subhandler.data_change_fired.connect(self._update_subscription_model, type=Qt.QueuedConnection)

    def clear(self):
        self._subscribed_nodes = []
        self.model.clear()

    def _subscribe(self):
        node = self.window.get_current_node()
        if node is None:
            return
        if node in self._subscribed_nodes:
            print("allready subscribed to node: ", node)
            return
        self.model.setHorizontalHeaderLabels(["DisplayName", "Value", "Timestamp"])
        row = [QStandardItem(node.display_name), QStandardItem("No Data yet"), QStandardItem("")]
        row[0].setData(node)
        self.model.appendRow(row)
        self._subscribed_nodes.append(node)
        self.window.ui.subDockWidget.raise_()
        try:
            self.uaclient.subscribe_datachange(node, self._subhandler)
        except Exception as ex:
            self.window.show_error(ex)
            idx = self.model.indexFromItem(row[0])
            self.model.takeRow(idx.row())

    def _unsubscribe(self):
        node = self.window.get_current_node()
        if node is None:
            return
        self.uaclient.unsubscribe_datachange(node)
        self._subscribed_nodes.remove(node)
        i = 0
        while self.model.item(i):
            item = self.model.item(i)
            if item.data() == node:
                self.model.removeRow(i)
            i += 1

    def _update_subscription_model(self, node, value, timestamp):
        i = 0
        while self.model.item(i):
            item = self.model.item(i)
            if item.data() == node:
                it = self.model.item(i, 1)
                it.setText(value)
                it_ts = self.model.item(i, 2)
                it_ts.setText(timestamp)
            i += 1
예제 #5
0
class DataChangeUI(object):

    def __init__(self, window, opc_ua_client, view):
        self.window = window
        self.opc_ua_client = opc_ua_client
        self._subhandler = DataChangeHandler()
        self.subscribed_nodes = []

        self.view = view
        self.model = QStandardItemModel()
        self.model.setHorizontalHeaderLabels(["ObjectName", "VariableName", "Value", "Timestamp"])
        self.view.setModel(self.model)

        # handle subscriptions
        self._subhandler.data_change_fired.connect(self._update_subscription_model, type=Qt.QueuedConnection)

        # accept drops
        self.model.canDropMimeData = self.canDropMimeData
        self.model.dropMimeData = self.dropMimeData

        # Context menu
        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.view.customContextMenuRequested.connect(self.showContextMenu)
        actionDeleteMonitoredItem = QAction("Delete Monitored Item", self.model)
        actionDeleteMonitoredItem.triggered.connect(self._delete_monitored_item_context)
        self._contextMenu = QMenu()
        self._contextMenu.addAction(actionDeleteMonitoredItem)

        self.view.selectionModel().selectionChanged.connect(self.highlight_node)

    def highlight_node(self, selection):
        if isinstance(selection, QItemSelection):
            if not selection.indexes():  # no selection
                return
        idx = self.view.currentIndex()
        idx = idx.siblingAtColumn(0)
        it = self.model.itemFromIndex(idx)
        if not it:
            return
        node = it.data()
        self.window.tree_ui.expand_to_node(node)

    def canDropMimeData(self, mdata, action, row, column, parent):
        node = self.opc_ua_client.client.get_node(mdata.text())
        if node.get_node_class() == ua.NodeClass.Variable:
            return True
        return False

    def dropMimeData(self, mdata, action, row, column, parent):
        node = self.opc_ua_client.client.get_node(mdata.text())
        self.window.add_monitored_item(node)
        return True

    def showContextMenu(self, position):
        item = self.get_current_item()
        if item:
            self._contextMenu.exec_(self.view.viewport().mapToGlobal(position))

    def get_current_item(self, col_idx=0):
        idx = self.view.currentIndex()
        idx = idx.siblingAtColumn(col_idx)
        return self.model.itemFromIndex(idx)

    def _delete_monitored_item_context(self):
        it = self.get_current_item()
        if it:
            index = self.window.ui.tabWidget.currentIndex()
            self.delete_monitored_item(index, it.data())

    def clear(self):
        self.subscribed_nodes = []
        # remove all rows but not header
        self.model.removeRows(0, self.model.rowCount())

    def show_error(self, *args):
        self.window.show_error(*args)

    def create_subscription(self):
        self.opc_ua_client.create_subscription(self._subhandler)

    def delete_subscription(self, index):
        self.opc_ua_client.delete_subscription(index)

    @trycatchslot
    def add_monitored_item(self, index, node):
        variable_name = node.get_display_name().Text
        descriptions = node.get_references(ua.ObjectIds.Aggregates, ua.BrowseDirection.Inverse, ua.NodeClass.Object, True)
        parent_node = node
        while not descriptions:
            parent_node = parent_node.get_parent()
            descriptions = parent_node.get_references(ua.ObjectIds.Aggregates, ua.BrowseDirection.Inverse, ua.NodeClass.Object, True)
        parent_nodeid = descriptions[0].NodeId
        if parent_nodeid in self.opc_ua_client.custom_objects:
            custom_type = self.opc_ua_client.custom_objects[parent_nodeid]
            icon = get_icon(custom_type)
        else:
            icon = "icons/object.svg"
        row = [QStandardItem(QIcon(icon), descriptions[0].DisplayName.Text), QStandardItem(variable_name), QStandardItem("No Data yet"), QStandardItem("")]
        row[0].setData(node)
        self.model.appendRow(row)
        self.subscribed_nodes.append(node)
        try:
            self.opc_ua_client.create_monitored_items(node, index)
            row[0].setData(self.window.get_monitored_item_tooltip(), Qt.ToolTipRole)
            self.model.sort(0, Qt.AscendingOrder)
            self._color_rows()
        except Exception as ex:
            self.window.show_error(ex)
            idx = self.model.indexFromItem(row[0])
            self.model.takeRow(idx.row())
            self.subscribed_nodes.remove(node)
            raise

    def _color_rows(self):
        n_rows = self.model.rowCount()
        if n_rows > 1:
            change_color = False
            for row in range(1, n_rows):
                prev_object_name = self.model.data(self.model.index(row - 1, 0))
                curr_object_name = self.model.data(self.model.index(row, 0))
                if curr_object_name != prev_object_name:
                    change_color = not change_color
                if change_color:
                    color = QColor(240, 240, 240)  # grey
                else:
                    color = QColor(255, 255, 255)  # white
                for col in range(self.model.columnCount()):
                    item = self.model.item(row, col)
                    item.setData(QBrush(color), Qt.BackgroundRole)

    @trycatchslot
    def delete_monitored_item(self, index, node=None):
        if not isinstance(node, Node):
            node = self.window.get_current_node()
        if node is None:
            return
        self.opc_ua_client.remove_monitored_item(node, index)
        self.subscribed_nodes.remove(node)
        i = 0
        while self.model.item(i):
            item = self.model.item(i)
            if item.data() == node:
                self.model.removeRow(i)
            i += 1

    def _update_subscription_model(self, node, value, status_code, timestamp):
        i = 0
        while self.model.item(i):
            item = self.model.item(i)
            if item.data() == node:
                it = self.model.item(i, 2)
                it.setText(value)
                if status_code == ua.StatusCodes.Good:
                    it.setForeground(QBrush(QColor("green")))
                elif status_code == ua.StatusCodes.Uncertain:
                    it.setForeground(QBrush(QColor("yellow")))
                else:  # StatusCode = Bad:
                    it.setForeground(QBrush(QColor("red")))
                it_ts = self.model.item(i, 3)
                it_ts.setText(timestamp)
            i += 1
예제 #6
0
class IsosViewer(QMainWindow, mageiaSyncUI.Ui_mainWindow):
    #   Display the main window
    def __init__(self, parent=None):
        super(IsosViewer, self).__init__(parent)
        self.setupUi(self)
        self.connectActions()
        self.IprogressBar.setMinimum(0)
        self.IprogressBar.setMaximum(100)
        self.IprogressBar.setValue(0)
        self.IprogressBar.setEnabled(False)
        self.selectAllState=True
        self.stop.setEnabled(False)
        self.destination=''
        self.rsyncThread = mageiaSyncExt.syncThread(self)    # create a thread to launch rsync
        self.rsyncThread.progressSignal.connect(self.setProgress)
        self.rsyncThread.speedSignal.connect(self.setSpeed)
        self.rsyncThread.sizeSignal.connect(self.setSize)
        self.rsyncThread.remainSignal.connect(self.setRemain)
        self.rsyncThread.endSignal.connect(self.syncEnd)
        self.rsyncThread.lvM.connect(self.lvMessage)
        self.rsyncThread.checkSignal.connect(self.checks)
        self.checkThreads=[]    #   A list of thread for each iso

 #      Model for list view in a table
        self.model = QStandardItemModel(0, 6, self)
        headers=[self.tr("Directory"),self.tr("Name"),self.tr("Size"),self.tr("Date"),"SHA1","MD5"]
        i=0
        for label in headers:
            self.model.setHeaderData(i, QtCore.Qt.Horizontal,label )
            i+=1

#       settings for the list view
        self.localList.setModel(self.model)
        self.localList.setColumnWidth(0,220)
        self.localList.setColumnWidth(1,220)
        self.localList.horizontalHeader().setStretchLastSection(True)
        # settings for local iso names management
        self.localListNames=[]

    def multiSelect(self):
    #   allows to select multiple lines in remote ISOs list
        self.listIsos.setSelectionMode(2)

    def add(self, iso):
    #   Add an remote ISO in list
         self.listIsos.addItem(iso)

    def localAdd(self, path,iso,isoSize):
        #   Add an entry in local ISOs list, with indications about checking
        itemPath=QStandardItem(path)
        itemIso=QStandardItem(iso)
        if isoSize==0:
            itemSize=QStandardItem('--')
        else:
            formatedSize='{:n}'.format(isoSize).replace(","," ")
            itemSize=QStandardItem(formatedSize)
        itemSize.setTextAlignment(QtCore.Qt.AlignVCenter|QtCore.Qt.AlignHCenter)
        itemDate=QStandardItem("--/--/--")
        itemDate.setTextAlignment(QtCore.Qt.AlignVCenter|QtCore.Qt.AlignHCenter)
        itemCheck1=QStandardItem("--")
        itemCheck1.setTextAlignment(QtCore.Qt.AlignVCenter|QtCore.Qt.AlignHCenter)
        itemCheck5=QStandardItem("--")
        itemCheck5.setTextAlignment(QtCore.Qt.AlignVCenter|QtCore.Qt.AlignHCenter)
        self.model.appendRow([itemPath,itemIso,itemSize,itemDate, itemCheck1, itemCheck5,])
        self.localListNames.append([path,iso])

    def setProgress(self, value):
        #   Update the progress bar
        self.IprogressBar.setValue(value)

    def setSpeed(self, value):
        #   Update the speed field
        self.speedLCD.display(value)

    def setSize(self, size):
        #   Update the size field
        self.Lsize.setText(size+self.tr(" bytes"))

    def setRemain(self,remainTime):
        content=QtCore.QTime.fromString(remainTime,"h:mm:ss")
        self.timeRemaining.setTime(content)

    def manualChecks(self):
        for iso in self.listIsos.selectedItems():
            path,name=iso.text().split('/')
            try:
                #   Look for ISO in local list
                item=self.model.findItems(name,QtCore.Qt.MatchExactly,1)[0]
            except:
                #   Remote ISO is not yet in local directory. We add it in localList and create the directory
                self.localAdd(path,name,0)
                basedir=QtCore.QDir(self.destination)
                basedir.mkdir(path)
                item=self.model.findItems(name,QtCore.Qt.MatchExactly,1)[0]
            row=self.model.indexFromItem(item).row()
            self.checks(row)

    def checks(self,isoIndex):
        #   processes a checking for each iso
        #   launches a thread for each iso
        newThread=mageiaSyncExt.checkThread(self)
        self.checkThreads.append(newThread)
        self.checkThreads[-1].setup(self.destination,
            self.model.data(self.model.index(isoIndex,0)) ,
            self.model.data(self.model.index(isoIndex,1)),
            isoIndex)
        self.checkThreads[-1].md5Signal.connect(self.md5Check)
        self.checkThreads[-1].sha1Signal.connect(self.sha1Check)
        self.checkThreads[-1].dateSignal.connect(self.dateCheck)
        self.checkThreads[-1].sizeFinalSignal.connect(self.sizeUpdate)
        self.checkThreads[-1].checkStartSignal.connect(self.checkStart)
        self.checkThreads[-1].start()

    def checkStart(self,isoIndex):
        #   the function indicates that checking is in progress
        #   the hundred contains index of the value to check, the minor value contains the row
        col=(int)(isoIndex/100)
        row=isoIndex-col*100
        self.model.setData(self.model.index(row, col, QtCore.QModelIndex()), self.tr("Checking"))

    def md5Check(self,check):
        if check>=128:
            val=self.tr("OK")
            row=check-128
        else:
            val=self.tr("Failed")
            row=check
        self.model.setData(self.model.index(row, 5, QtCore.QModelIndex()), val)

    def sha1Check(self,check):
        if check>=128:
            val=self.tr("OK")
            row=check-128
        else:
            val=self.tr("Failed")
            row=check
        self.model.setData(self.model.index(row, 4, QtCore.QModelIndex()), val)

    def dateCheck(self,check):
        if check>=128:
            val=self.tr("OK")
            row=check-128
        else:
            val=self.tr("Failed")
            row=check
        self.model.setData(self.model.index(row, 3, QtCore.QModelIndex()), val)

    def sizeUpdate(self,signal,isoSize):
        col=(int)(signal/100)
        row=signal-col*100
        self.model.setData(self.model.index(row, col, QtCore.QModelIndex()), isoSize)

    def syncEnd(self, rc):
        if rc==1:
            self.lvMessage(self.tr("Command rsync not found"))
        elif rc==2:
            self.lvMessage(self.tr("Error in rsync parameters"))
        elif rc==3:
            self.lvMessage(self.tr("Unknown error in rsync"))
        self.IprogressBar.setEnabled(False)
        self.syncGo.setEnabled(True)
        self.listIsos.setEnabled(True)
        self.selectAll.setEnabled(True)
        self.stop.setEnabled(False)

    def prefsInit(self):
    #   Load the parameters at first
        params=QtCore.QSettings("Mageia","mageiaSync")
        paramRelease=""
        try:
            paramRelease=params.value("release", type="QString")
            #   the parameters already initialised?
        except:
            pass
        if paramRelease =="":
            # Values are not yet set
            self.pd0=prefsDialog0()
            self.pd0.user.setFocus()
            answer=self.pd0.exec_()
            if answer:
                #   Update params
                self.user=self.pd0.user.text()
                self.password=self.pd0.password.text()
                self.location=self.pd0.location.text()
                params=QtCore.QSettings("Mageia","mageiaSync")
                params.setValue("user",self.user)
                params.setValue("password",self.password)
                params.setValue("location",self.location)
            else:
                pass
#                answer=QDialogButtonBox(QDialogButtonBox.Ok)
                # the user must set values or default values
            self.pd0.close()
            self.pd=prefsDialog()
            if self.password !="":
                code,list=mageiaSyncExt.findRelease('rsync://'+self.user+'@bcd.mageia.org/isos/',self.password)
                if code==0:
                    for item in list:
                        self.pd.release.addItem(item)
            self.pd.password.setText(self.password)
            self.pd.user.setText(self.user)
            self.pd.location.setText(self.location)
            self.pd.selectDest.setText(QtCore.QDir.currentPath())
            self.pd.release.setFocus()
            answer=self.pd.exec_()
            if answer:
                #   Update params
                self.user=self.pd.user.text()
                self.password=self.pd.password.text()
                self.location=self.pd.location.text()
                params=QtCore.QSettings("Mageia","mageiaSync")
                self.release= self.pd.release.currentText()
                self.destination=self.pd.selectDest.text()
                self.bwl=self.pd.bwl.value()
                params.setValue("release", self.release)
                params.setValue("user",self.user)
                params.setValue("password",self.password)
                params.setValue("location",self.location)
                params.setValue("destination",self.destination)
                params.setValue("bwl",str(self.bwl))
            else:
                pass
#                answer=QDialogButtonBox(QDialogButtonBox.Ok)
                print(self.tr("the user must set values or default values"))
            self.pd.close()
        else:
            self.release=params.value("release", type="QString")
            self.user=params.value("user", type="QString")
            self.location=params.value("location", type="QString")
            self.password=params.value("password", type="QString")
            self.destination=params.value("destination", type="QString")
            self.bwl=params.value("bwl",type=int)
        self.localDirLabel.setText(self.tr("Local directory: ")+self.destination)
        if self.location !="":
            self.remoteDirLabel.setText(self.tr("Remote directory: ")+self.location)

    def selectDestination(self):
        #   dialog box to select the destination (local directory)
        directory = QFileDialog.getExistingDirectory(self, self.tr('Select a directory'),'~/')
        isosSync.destination = directory
        self.pd.selectDest.setText(isosSync.destination)


    def selectAllIsos(self):
        #   Select or unselect the ISOs in remote list
        if self.selectAllState :
            for i in range(self.listIsos.count()):
                self.listIsos.item(i).setSelected(True)
            self.selectAll.setText(self.tr("Unselect &All"))
        else:
            for i in range(self.listIsos.count()):
                self.listIsos.item(i).setSelected(False)
            self.selectAll.setText(self.tr("Select &All"))
        self.selectAllState=not self.selectAllState

    def connectActions(self):
        self.actionQuit.triggered.connect(app.quit)
        self.quit.clicked.connect(app.quit)
        self.actionRename.triggered.connect(self.rename)
        self.actionUpdate.triggered.connect(self.updateList)
        self.actionCheck.triggered.connect(self.manualChecks)
        self.actionPreferences.triggered.connect(self.prefs)
        self.syncGo.clicked.connect(self.launchSync)
        self.selectAll.clicked.connect(self.selectAllIsos)

    def updateList(self):
        # From the menu entry
        self.lw = LogWindow()
        self.lw.show()
        self.listIsos.clear()
        self.model.removeRows(0,self.model.rowCount())
        if self.location  == "" :
            self.nameWithPath='rsync://'+self.user+'@bcd.mageia.org/isos/'+self.release+'/'
#            print self.nameWithPath
        else:
            self.nameWithPath=self.location+'/'
        self.lvMessage(self.tr("Source: ")+self.nameWithPath)
        self.fillList = mageiaSyncExt.findIsos()
        self.fillList.setup(self.nameWithPath, self.password,self.destination)
        self.fillList.endSignal.connect(self.closeFill)
        self.fillList.start()
        # Reset the button
        self.selectAll.setText(self.tr("Select &All"))
        self.selectAllState=True

    def lvMessage( self,message):
        #   Add a line in the logview
        self.lvText.append(message)

    def renameDir(self):
        #   Choose the directory where isos are stored
        directory = QFileDialog.getExistingDirectory(self, self.tr('Select a directory'),self.destination)
        self.rd.chooseDir.setText(directory)

    def rename(self):
        #   rename old isos and directories to a new release
        self.rd=renameDialog()
        loc=[]
        loc=self.location.split('/')
        self.rd.oldRelease.setText(loc[-1])
        self.rd.chooseDir.setText(self.destination)
        answer=self.rd.exec_()
        if answer:
            returnMsg=mageiaSyncExt.rename(self.rd.chooseDir.text(),self.rd.oldRelease.text(),str(self.rd.newRelease.text()))
            self.lvMessage(returnMsg)
        self.rd.close()

    def prefs(self):
        # From the menu entry
        self.pd=prefsDialog()
        self.pd.release.addItem(self.release)
        self.pd.password.setText(self.password)
        self.pd.user.setText(self.user)
        self.pd.location.setText(self.location)
        self.pd.selectDest.setText(self.destination)
        self.pd.bwl.setValue(self.bwl)
        params=QtCore.QSettings("Mageia","mageiaSync")
        answer=self.pd.exec_()
        if answer:
            params.setValue("release", self.pd.release.currentText())
            params.setValue("user",self.pd.user.text())
            params.setValue("password",self.pd.password.text())
            params.setValue("location",self.pd.location.text())
            params.setValue("destination",self.pd.selectDest.text())
            params.setValue("bwl",str(self.pd.bwl.value()))
        self.prefsInit()
        self.pd.close()


    def launchSync(self):
        self.IprogressBar.setEnabled(True)
        self.stop.setEnabled(True)
        self.syncGo.setEnabled(False)
        self.listIsos.setEnabled(False)
        self.selectAll.setEnabled(False)
        # Connect the button Stop
        self.stop.clicked.connect(self.stopSync)
        self.rsyncThread.params(self.password, self.bwl)
        for iso in self.listIsos.selectedItems():
            path,name=iso.text().split('/')
            try:
                #   Look for ISO in local list
                item=self.model.findItems(name,QtCore.Qt.MatchExactly,1)[0]
            except:
                #   Remote ISO is not yet in local directory. We add it in localList and create the directory
                self.localAdd(path,name,0)
                basedir=QtCore.QDir(self.destination)
                basedir.mkdir(path)
                item=self.model.findItems(name,QtCore.Qt.MatchExactly,1)[0]
            row=self.model.indexFromItem(item).row()
            if self.location  == "" :
                self.nameWithPath='rsync://'+self.user+'@bcd.mageia.org/isos/'+self.release+'/'+path
            else:
                self.nameWithPath=self.location+path
            if (not str(path).endswith('/')):
                    self.nameWithPath+='/'
            self.rsyncThread.setup(self.nameWithPath, self.destination+'/'+path+'/',row)
        self.rsyncThread.start()             # start the thread
        # Pour les tests uniquement
            #rsync://[email protected]/isos/$release/
        #self.nameWithPath='rsync://ftp5.gwdg.de/pub/linux/mageia/iso/4.1/Mageia-4.1-LiveCD-GNOME-en-i586-CD/'

    def closeFill(self,code):
        if code==0: #   list returned
            list=self.fillList.getList()
            for iso in list:
                self.add(iso)
        elif code==1:
            self.lvMessage(self.tr("Command rsync not found"))
        elif code==2:
            self.lvMessage(self.tr("Error in rsync parameters"))
        elif code==3:
            self.lvMessage(self.tr("Unknown error in rsync"))
        list=self.fillList.getList()

        list=self.fillList.getLocal()
        for path,iso,isoSize in list:
            self.localAdd(path,iso, isoSize)
        self.fillList.quit()
        self.lw.hide()

    def stopSync(self):
        self.rsyncThread.stop()
        self.IprogressBar.setEnabled(False)
        self.stop.setEnabled(False)
        self.syncGo.setEnabled(True)
        self.listIsos.setEnabled(True)
        self.selectAll.setEnabled(True)


    def main(self):
        self.show()
        #   Load or look for intitial parameters
        self.prefsInit()
        # look for Isos list and add it to the isoSync list. Update preferences
        self.updateList()
        self.multiSelect()

    def close(self):
        self.rsyncThread.stop()
        exit(0)
예제 #7
0
class ApiListPresenter:
    model: QStandardItemModel
    view: QListView
    is_drop_operation: bool = False
    dropped_row: int = -1

    def __init__(self, parent_view):
        self.view = parent_view.lst_http_requests
        self.parent_view = parent_view
        self.assertion_result_presenter = AssertionResultPresenter(
            self.parent_view)

        self.app_state_interactor = AppStateInteractor()

        self.model = QStandardItemModel()
        self.view.setModel(self.model)
        self.view.setItemDelegate(ApiCallItemDelegate())

        self.view.selectionModel().currentChanged.connect(
            self.on_list_item_selected)
        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.view.customContextMenuRequested.connect(self.show_context_menu)
        self.view.drop_event_signal.connect(self.on_drop_event)
        self.view.model().rowsRemoved.connect(self.onRowsRemoved)

        http_exchange_signals.request_started.connect(self.on_request_started)

        # Context Menu setup
        duplicate_action = QAction("Duplicate", self.view)
        duplicate_action.triggered.connect(self._duplicate_request)

        remove_action = QAction("Delete", self.view)
        remove_action.triggered.connect(self.on_remove_selected_item)

        toggle_action = QAction("Toggle Enable/Disable", self.view)
        toggle_action.triggered.connect(self.on_toggle_request_status)

        self.menu = QMenu()
        self.menu.addAction(duplicate_action)
        self.menu.addAction(remove_action)
        self.menu.addAction(toggle_action)

        self.separator_menu = QMenu()
        self.separator_menu.addAction(remove_action)

        app_settings.app_data_writer.signals.api_call_added.connect(
            self.add_request_widget)
        app_settings.app_data_writer.signals.api_call_updated.connect(
            self.refresh_selected_item)
        app_settings.app_data_writer.signals.multiple_api_calls_added.connect(
            self.refresh_multiple_items)
        app_settings.app_data_writer.signals.selected_tag_changed.connect(
            self.refresh)

    def on_toggle_request_status(self):
        selected_model_index: QModelIndex = self.index_at_selected_row()
        if not selected_model_index:
            return
        api_call_id = selected_model_index.data(API_ID_ROLE)
        api_call: ApiCall = app_settings.app_data_cache.get_api_call(
            api_call_id)
        api_call.enabled = not api_call.enabled

        api_call_interactor.update_api_call(api_call_id, api_call)

    def on_drop_event(self, model_index: QModelIndex):
        self.is_drop_operation = True
        self.dropped_row = model_index.row()

    def show_context_menu(self, position):
        index: QModelIndex = self.view.indexAt(position)
        if not index.isValid():
            return

        selected_api_call: ApiCall = index.data(API_CALL_ROLE)
        global_position = self.view.viewport().mapToGlobal(position)
        if selected_api_call.is_separator:
            self.separator_menu.exec_(global_position)
        else:
            self.menu.exec_(global_position)

    def selectPreviousApiCall(self):
        selected_index = self.index_at_selected_row()
        if not selected_index:
            return

        selected_row = selected_index.row()
        previous_row = selected_row - 1

        if previous_row >= 0:
            previous_item: QStandardItem = self.model.item(previous_row)
            self.view.setCurrentIndex(previous_item.index())

    def selectNextApiCall(self):
        selected_index = self.index_at_selected_row()
        if not selected_index:
            return

        selected_row = selected_index.row()
        next_row = selected_row + 1
        total_rows = self.model.rowCount()

        if next_row < total_rows:
            next_item: QStandardItem = self.model.item(next_row)
            self.view.setCurrentIndex(next_item.index())

    def onRowsRemoved(self):
        if self.is_drop_operation:
            self.is_drop_operation = False
            if self.dropped_row < 0 or self.dropped_row >= self.model.rowCount(
            ):
                self.dropped_row = self.model.rowCount() - 1

            current_item = self.model.item(self.dropped_row)
            current_index = self.model.indexFromItem(current_item)
            api_call = current_item.data(API_CALL_ROLE)
            before = self.dropped_row - 1
            prev_sequence_number = 0
            if before >= 0:
                prev_api_call = self.model.item(before).data(API_CALL_ROLE)
                prev_sequence_number = prev_api_call.sequence_number
            total_rows = self.model.rowCount()
            after = self.dropped_row + 1
            if after >= total_rows:
                next_sequence_number = self.app_state_interactor.update_sequence_number(
                )
            else:
                next_api_call = self.model.item(after).data(API_CALL_ROLE)
                next_sequence_number = next_api_call.sequence_number
            new_sequence_number = prev_sequence_number + (
                next_sequence_number - prev_sequence_number) / 2
            api_call.sequence_number = new_sequence_number
            logging.info(
                f"API Call: {api_call.id} - "
                f"New Sequence {new_sequence_number} - "
                f"Moved between {prev_sequence_number} and {next_sequence_number}"
            )

            api_call_interactor.update_api_call(api_call.id, api_call)

            self.view.setCurrentIndex(current_index)

    def add_request_widget(self,
                           api_call_id,
                           api_call: ApiCall,
                           select_item=True):
        logging.info(
            f"Adding new item with id {api_call_id} to requests list - {api_call}"
        )
        item = QStandardItem(api_call.title)
        item.setData(api_call, API_CALL_ROLE)
        item.setData(QVariant(api_call.id), API_ID_ROLE)
        item.setToolTip(api_call.description)
        item.setDragEnabled(True)
        item.setDropEnabled(False)
        self.model.appendRow(item)
        if select_item:
            index = self.model.indexFromItem(item)
            self.view.setCurrentIndex(index)

    def run_all_api_calls(self):
        total_rows = self.model.rowCount()
        logging.debug("** Running multiple API calls: {}".format(total_rows))
        for n in range(total_rows):
            api_call = self.model.item(n).data(API_CALL_ROLE)
            logging.debug("** Multiple APIs: API Call {}".format(api_call.id))
            if api_call.enabled:
                rest_api_interactor.make_http_call(api_call)

    def on_remove_selected_item(self):
        selected_model_index: QModelIndex = self.index_at_selected_row()
        if not selected_model_index:
            return
        row_to_remove = selected_model_index.row()
        api_call: ApiCall = selected_model_index.data(API_CALL_ROLE)
        # Migration
        api_call_interactor.remove_api_call([api_call.id])
        previous_row = row_to_remove - 1
        if previous_row >= 0:
            previous_item: QStandardItem = self.model.item(previous_row)
            self.view.setCurrentIndex(previous_item.index())

    def on_list_item_selected(self, current: QModelIndex):
        if not current:
            return

        selected_api_call: ApiCall = current.data(API_CALL_ROLE)
        logging.info(f"List Item Selected: {selected_api_call.id}")
        if not selected_api_call.is_separator:
            api_call_interactor.update_selected_api_call(selected_api_call.id)

    def on_request_started(self, _, api_call_id):
        logging.info(f"Request started for {api_call_id}")
        api_call_row = self.__row_for_api_call(api_call_id)
        item_running = self.model.item(api_call_row)
        index_running = self.model.indexFromItem(item_running)
        self.view.setCurrentIndex(index_running)

    def refresh_selected_item(self, api_call_id):
        api_call_row = self.__row_for_api_call(api_call_id)
        if api_call_row != -1:
            api_call = app_settings.app_data_cache.get_api_call(api_call_id)
            logging.info(f"Refreshing row {api_call_row} -> {api_call.id}")
            self.model.item(api_call_row).setData(api_call, API_CALL_ROLE)

    def refresh_multiple_items(self, doc_ids: List[str],
                               api_calls: List[ApiCall]):
        assert len(doc_ids) == len(api_calls)
        for doc_id, api_call in zip(doc_ids, api_calls):
            self.add_request_widget(doc_id, api_call, select_item=False)

    def on_search_query(self, search_query):
        app_settings.app_data_cache.update_search_query(search_query)
        self.refresh()

    def refresh(self):
        """Re-renders this list view and selects the first item"""
        self.model.clear()
        app_state = app_settings.app_data_cache.get_app_state()
        api_calls = app_settings.app_data_cache.filter_api_calls(
            search_query=app_state.selected_search,
            search_tag=app_state.selected_tag)
        for api in api_calls:
            self.add_request_widget(api.id, api, select_item=False)

        if len(api_calls) > 0:
            first_item: QStandardItem = self.model.item(0)
            self.view.setCurrentIndex(first_item.index())

    def index_at_selected_row(self):
        selected_model: QItemSelectionModel = self.view.selectionModel()
        if not selected_model.hasSelection():
            return None
        return selected_model.currentIndex()

    def _duplicate_request(self):
        selected_index = self.index_at_selected_row()
        if not selected_index:
            return
        api_call = selected_index.data(API_CALL_ROLE)
        duplicate_api_call = copy.deepcopy(api_call)
        duplicate_api_call.title = f"{duplicate_api_call.title} Duplicate"
        duplicate_api_call.last_response_code = 0
        duplicate_api_call.sequence_number = self.app_state_interactor.update_sequence_number(
        )

        # Migration
        api_call_interactor.add_api_call(duplicate_api_call)

    def __row_for_api_call(self, api_call_id):
        api_call_row = next(
            (n for n in range(self.model.rowCount())
             if self.model.item(n).data(API_ID_ROLE) == api_call_id), -1)
        return api_call_row
예제 #8
0
파일: Tree.py 프로젝트: sysdeep/DNote
class Tree(QTreeView):
	def __init__(self, parent=None):
		super(Tree, self).__init__(parent)

		self.tmodel = QStandardItemModel()
		self.tmodel.setHorizontalHeaderLabels(['name'])
		self.setModel(self.tmodel)
		self.setUniformRowHeights(True)
		self.setHeaderHidden(True)
		self.setFixedWidth(300)

		# self.storage = get_storage()
		# self.storage = smanager.get_storage()
		# self.tree = self.storage.get_tree()
		self.select_cb = None


		self.current_uuid 	= None			# текущий uuid элемента
		self.current_index 	= None			# элемент с флагом current = True - для автоматического выбора(modelIndex)
		self.expand_indexes = []			# список элементов, которые необходимо раскрыть



		#--- dnd
		self.setDragEnabled(True)
		# self.setDragDropMode(QAbstractItemView.DragOnly)
		# self.setDragDropMode(QAbstractItemView.DragDrop)
		# self.setDragDropMode(QAbstractItemView.InternalMove)
		self.viewport().setAcceptDrops(True)
		# self.setDropIndicatorShown(True)


		self.modal_icons = None
		self.copy_node_uuid = None



		#--- menu
		self.__make_cmenu()




		# sevents.eon("storage_opened", self.__remake_tree)
		sevents.eon("project_updated", self.__update_tree)

		# dbus.eon(dbus.STORAGE_OPENED, self.__on_storage_opened)
		storage.s_opened.connect(self.__on_storage_opened)

		#--- signals
		self.pressed.connect(self.__on_select)
		self.expanded.connect(self.__on_expanded)
		self.collapsed.connect(self.__on_collapsed)


		# self.__remake_tree()


	def __on_storage_opened(self):
		log.debug("__on_storage_opened")
		self.__remake_tree()



	def dropEvent(self, ev):
		index = self.indexAt(ev.pos())
		uuid = index.data(Qt.UserRole + 1)

		ev.accept()

		self.storage.pmanager.move_node(self.current_uuid, uuid)
		self.storage.update_project_file()





	def __remake_tree(self):
		log.debug("remake tree")

		self.tree = storage.get_tree()

		# print(self.storage)

		self.current_uuid 	= None			# текущий uuid элемента
		self.current_index 	= None			# элемент с флагом current = True - для автоматического выбора(modelIndex)
		self.expand_indexes = []			# список элементов, которые необходимо раскрыть


		# print(self.storage.project_path)
		self.__update_tree()




	def __update_tree(self):
		log.debug("__update_tree")
		self.current_index = None
		self.tmodel.clear()
		self.__make_tree()


	def update_tree(self):
		self.__update_tree()


	def __make_cmenu(self):
		"""контекстное меню"""
		self.setContextMenuPolicy(Qt.ActionsContextMenu)
		create_new_root 	= QAction("Новая корневая запись", self)
		create_new_parent 	= QAction("Новая запись для данного элемента", self)
		create_new_level 	= QAction("Новая запись такого же уровня", self)
		
		edit_name 			= QAction("Изменить название", self)
		edit_icon 			= QAction("Изменить иконки", self)
		show_info 			= QAction("Информация", self)
		# act_copy 			= QAction("Копировать", self)
		# act_paste 			= QAction("Вставить", self)

		move_up				= QAction("Выше", self)
		move_down			= QAction("Ниже", self)

		remove_item 		= QAction("Удалить запись(ветку)", self)

		separator1 = QAction(self)
		separator1.setSeparator(True)

		separator2 = QAction(self)
		separator2.setSeparator(True)

		separator3 = QAction(self)
		separator3.setSeparator(True)
		
		self.addAction(create_new_root)
		self.addAction(create_new_parent)
		self.addAction(create_new_level)
		self.addAction(separator1)
		self.addAction(edit_name)
		self.addAction(edit_icon)
		self.addAction(show_info)
		# self.addAction(act_copy)
		# self.addAction(act_paste)

		self.addAction(separator2)
		
		self.addAction(move_up)
		self.addAction(move_down)

		self.addAction(separator3)

		self.addAction(remove_item)
		# self.addSeparetor()


		create_new_root.setIcon(qicon("filesystems", "folder_blue.png"))
		create_new_parent.setIcon(qicon("filesystems", "folder_green.png"))
		create_new_level.setIcon(qicon("filesystems", "folder_orange.png"))
		edit_name.setIcon(qicon("actions", "edit.png"))
		edit_icon.setIcon(qicon("actions", "frame_image.png"))
		show_info.setIcon(qicon("actions", "kdeprint_printer_infos.png"))
		# act_copy.setIcon(qicon("actions", "editcopy.png"))
		remove_item.setIcon(qicon("actions", "remove.png"))

		move_up.setIcon(qicon("actions", "arrow_up.png"))
		move_down.setIcon(qicon("actions", "arrow_down.png"))

		create_new_root.triggered.connect(self.__act_create_new_root)
		create_new_parent.triggered.connect(self.__act_create_new_parent)
		create_new_level.triggered.connect(self.__act_create_new_level)
		edit_name.triggered.connect(self.__act_edit_name)
		edit_icon.triggered.connect(self.__act_edit_icon)
		show_info.triggered.connect(self.__act_show_info)
		# act_copy.triggered.connect(self.__act_copy)
		# act_paste.triggered.connect(self.__act_paste)

		move_up.triggered.connect(self.__act_move_up)
		move_down.triggered.connect(self.__act_move_down)

		remove_item.triggered.connect(self.__act_remove_item)




	def __make_tree(self):
		"""строим дерево"""
		log.debug("__make_tree")
		self.blockSignals(True)

		#--- все элементы корня
		root_items = self.tree.get_nodes_level(1)
		root_items.sort(key=lambda node: node.tree_lk)


		#--- запуск обхода дерева(рекурсия)
		for item in root_items:
			self.__wnode(item, self.tmodel)



		#--- выбираем элемент у которого флаг current
		if self.current_index:
			self.setCurrentIndex(self.current_index)
			self.__on_select(self.current_index)
		#--- если текущего нет - первый
		else:
			index = self.tmodel.index(0, 0)
			self.setCurrentIndex(index)
			self.__on_select(index)



		#--- разворачиваем элементы, у которых стоит флаг expanded
		for i in self.expand_indexes:
			self.expand(i)
		self.expand_indexes = []

		self.blockSignals(False)





	def __wnode(self, node, parent):
			"""
				рекурсивный обход элементов и добавление их на форму
			"""
			# if node.ntype == "f":
			# 	# icon = QIcon(get_icon_path("document-properties.png"))
			# 	icon = qicon("empty.png")
			# elif node.ntype == "d":
			# 	icon = qicon("folder.png")
			# 	# icon = QIcon(get_icon_path("document-open.png"))
			# 	# icon = QIcon(get_icon_path("document-open.png"))
			# else:
			# 	icon = QIcon(get_icon_path("list-remove.png"))


			if node.ipack and node.icon:
				icon = QIcon(get_icon_path(node.ipack, node.icon))
			else:
				icon = QIcon(get_icon_path("empty.svg"))



			row = QStandardItem(node.name)				# элемент строки
			row.setIcon(icon)				# icon
			row.setEditable(False)							# editable - false

			row.setData(node.uuid, Qt.UserRole+1)


			


			
			parent.appendRow(row)							# добавляем

			if node.current:
				self.current_index = self.tmodel.indexFromItem(row)
				self.current_uuid = node.uuid


			if node.expanded:
				index = self.tmodel.indexFromItem(row)
				self.expand_indexes.append(index)
			# print(index)

			#--- ищем всех деток на уровень ниже(не дальше)
			# child_items = [node for node in simple_obj_list
			# 			   if (node["tree_lk"] > node_lk) and (node["tree_rk"] < node_rk) and(node["tree_level"] == node_level+1)]

			# child_items = self.tree.get_childrens(node)
			child_items = self.tree.find_childrens(node.uuid)
			# child_items = node.childrens


			#--- для каждого из деток вызываем рекурсию
			for node in child_items:
				self.__wnode(node, row)



			









	#--- user actions ---------------------------------------------------------
	def __on_select(self, index):
		"""
			событие от дерева о выбранном элементе
			прилетает сразу при построении дерева
			причём current_uuid == uuid
		"""
		log.debug("__on_select: {}".format(index))
		#--- get selected uuid
		uuid = index.data(Qt.UserRole + 1)


		storage.select_node(uuid)

		# #--- get node from storage
		# node = smanager.storage.get_node(uuid)
		#
		# #--- set current node + emit
		# smanager.storage.set_current_node(node)
		#
		#
		# if uuid == self.current_uuid:
		# 	return False
		#
		self.current_uuid = uuid
		#
		# #--- update tree node(in project.json)
		# smanager.storage.project.set_current_flag(node.uuid)






	def __on_expanded(self, model_index):
		"""раскрытие узла"""
		uuid = model_index.data(Qt.UserRole + 1)
		storage.pmanager.set_node_expanded(uuid, True)

	def __on_collapsed(self, model_index):
		"""закрытие узла"""
		uuid = model_index.data(Qt.UserRole + 1)
		storage.pmanager.set_node_expanded(uuid, False)






	def __act_create_new_root(self):
		"""запрос на создание ноды от корня"""
		actions.show_modal_create_node(None)
		

	def __act_create_new_parent(self):
		"""выбранная нода - является родительской"""
		actions.show_modal_create_node(self.current_uuid)
		

	def __act_create_new_level(self):
		"""выбранная нода - находится у родителя - пока отключено!!!!!"""
		parent_pnode = storage.pmanager.find_parent_node(self.current_uuid)


		#--- если родитель - корень, то вызываем модал как и у __act_create_new_root
		parent_uuid = parent_pnode.uuid if parent_pnode.tree_lk > 0 else None

		actions.show_modal_create_node(parent_uuid)
		





	def __act_remove_item(self):
		"""удаление ноды"""
		actions.show_modal_remove_node()


	def __act_edit_name(self):
		"""редактирование названия"""
		actions.show_modal_edit_name()

	def __act_edit_icon(self):
		"""редактирование иконки"""

		# events.show_edit_icon(self.current_uuid)
		self.modal_icons = ModalIcons(self.__on_icon_selected, parent=self)
		self.modal_icons.show()


	def __act_show_info(self):
		"""информация о ноде"""
		actions.show_modal_node_info()







	# def __act_copy(self):
	# 	"""копирование ветки"""
	#
	# 	log.debug("copy node: " + self.current_uuid)
	# 	self.copy_node_uuid = self.current_uuid
	#
	#
	# def __act_paste(self):
	# 	log.debug(self.current_uuid)
	# 	log.debug(self.copy_node_uuid)
	#
	# 	self.storage.copy_node(self.copy_node_uuid, self.current_uuid)






	def __act_move_up(self):
		result = storage.pmanager.move_node_up(self.current_uuid)
		if result:
			storage.update_project_file()


	def __act_move_down(self):
		result = storage.pmanager.move_node_down(self.current_uuid)
		if result:
			storage.update_project_file()
	#--- user actions ---------------------------------------------------------




	def __on_icon_selected(self, ipack, icon):

		node = storage.pnode
		node.ipack = ipack
		node.icon = icon

		self.modal_icons.set_close()
		self.modal_icons = None

		storage.update_project_file()
예제 #9
0
class Intercept_Queue:

    def __init__(self, size=100):
        self.size = size
        self.packet_list_model = QStandardItemModel()
        self.field_list_model = QStandardItemModel()
        self.packet_list = []
        self.lock = Lock()

    # Populates the gui with packet information.
    def populate_packet_area(self):
        print('Populating to GUI...', flush=True)

        """ Populate Parent Node """
        layer_dict = self.packet_list[-1].layer_dict
        
        parent = QStandardItem(QIcon(arrow), 
                "Frame {}: {}".format(id_generator(size=3), ', '.join(self.packet_list[-1].layers)
                ))
        parent.setEditable(False)
        self.packet_list_model.appendRow(parent)

        """ Populate Children of Parent """
        for layer in self.packet_list[-1].layers:
            display_layer = layer
            if layer in layer_dict:
                display_layer = layer_dict[layer]
            if "src" in self.packet_list[-1].fields[layer] and "dst" in self.packet_list[-1].fields[layer]:
                src = self.packet_list[-1].fields[layer]["src"]
                dst = self.packet_list[-1].fields[layer]["dst"]
                child = QStandardItem(QIcon(circle),
                    "{}, Src: {}, Dst: {}".format(display_layer, src, dst))
            elif "sport" in self.packet_list[-1].fields[layer] and "dport" in self.packet_list[-1].fields[layer]:
                src = self.packet_list[-1].fields[layer]["sport"]
                dst = self.packet_list[-1].fields[layer]["dport"]
                child = QStandardItem(QIcon(circle),
                    "{}, Src Port: {}, Dst Port: {}".format(display_layer, src, dst))
            else:
                child = QStandardItem(QIcon(circle),
                    "{}".format(display_layer))
            child.setEditable(False)
            self.packet_list_model.itemFromIndex(self.packet_list_model.indexFromItem(parent)).appendRow(child)

     # Populates the field area with fields from the selected layer.
    def populate_field_area(self, packet_idx, layer_idx):
        
        self.field_list_model.removeRows(0,self.field_list_model.rowCount())
        layer = self.packet_list[packet_idx].get_layer(layer_idx)
        fields = self.packet_list[packet_idx].fields[layer]
        for k,v in fields.items():
            self.field_list_model.appendRow(QStandardItem("".join("{}:{}".format(k,v))
            ))
            
    # Insert a packet into the list of packets.  
    def put(self, packet):
        with self.lock:
            if self.size > len(self.packet_list):
                self.packet_list.append(packet)
                self.populate_packet_area()

    # Get a packet from the list of packets.                
    def get(self):
        with self.lock:
            packet = self.packet_list[0]
            del self.packet_list[0]
            self.packet_list_model.removeRow(0)
            return packet
예제 #10
0
class TurnoverDialog(QDialog, Ui_TurnoverDialog):
    """ Dialog used for the display of metabolite turnover

    This dialog shows the turnover of a specific metabolite
    from a given solution. The turnover is displayed in two
    forms:
    1)  A map showing the active reactions
    2)  A table listing the individual contributions of the
        reactions

    """
    def __init__(self, parent=None):
        super(TurnoverDialog, self).__init__(parent)
        self.setupUi(self)
        self.setWindowFlags(QtCore.Qt.Window)

        # Store input
        self.metabolite = None
        self.solution = None
        self.temp_file = os.path.join(tempfile.gettempdir(),
                                      "{0!s}.html".format(uuid.uuid4()))

        # Setup data structures
        self.datatable = QStandardItemModel(self)
        self.datatable.setHorizontalHeaderLabels(("%", "Rate") +
                                                 ReactionBaseTable.header)
        self.dataView.setModel(self.datatable)
        self.dataView.setEditTriggers(QAbstractItemView.NoEditTriggers)

        self.mapView.settings().setAttribute(
            QWebEngineSettings.LocalContentCanAccessFileUrls, True)

        # Connect signals/slots
        self.finished.connect(self.save_settings)
        self.finished.connect(self.delete_temp_file)

        # Restore settings
        self._restore_settings()

    def set_solution(self, solution, metabolite):
        self.metabolite = metabolite
        self.solution = solution

        if metabolite and solution:
            rates = get_rates(solution.fluxes, metabolite)
            self._refresh_map(rates)
            self._populate_tree(rates)
            self.setWindowTitle("{0!s} turnover".format(metabolite.id))

    def _refresh_map(self, rates):
        """ Refresh the map being displayed

        Generate a map from the set solution
        and metabolite. Only display those
        reactions that are active in the set
        solution if hide_active is set.

        Returns
        -------
        None
        """

        # Generate escher turnover map
        builder = escher.Builder(map_json=setup_turnover_map(
            self.metabolite, rates),
                                 reaction_data=self.solution.fluxes.to_dict())
        html = builder._get_html(**ESCHER_OPTIONS_LOCAL)

        # As Qt does not allow the loading of local files
        # from html set via the setHtml method, write map
        # to file and read it back to webview
        with open(self.temp_file, "w") as ofile:
            ofile.write(replace_css_paths(html))
        self.mapView.load(
            QtCore.QUrl("file:///" + self.temp_file.replace("\\", "/")))

    def _populate_tree(self, rates):
        """ Populate the datamodel from reactions

        Show the reactions participating in the
        metabolism of the specific metabolite.

        Returns
        -------
        None
        """

        # Delete old entries
        self.datatable.setRowCount(0)

        # Setup items
        consuming, producing, inactive = QStandardItem("Consuming"), QStandardItem("Producing"), \
                                         QStandardItem("Inactive")

        turnover = sum(v for v in rates.values() if v > 0.)

        for reaction, rate in sorted(rates.items(),
                                     key=lambda x: abs(x[1]),
                                     reverse=True):

            row = []
            try:
                row.append(QStandardItem('{:.2%}'.format(abs(rate /
                                                             turnover))))
            except ZeroDivisionError:
                row.append(QStandardItem("0"))
            finally:
                row.append(QStandardItem(str(rate)))
                row.extend(ReactionBaseTable.row_from_item(reaction))

            if rate > 0:
                producing.appendRow(row)
            elif rate < 0:
                consuming.appendRow(row)
            else:
                inactive.appendRow(row)

        # Add nodes
        root = self.datatable.invisibleRootItem()
        for i, item in enumerate((producing, consuming, inactive)):
            item.setText(item.text() + " ({0!s})".format(item.rowCount()))
            root.setChild(i, item)

        # Expand consuming/producing node
        for item in (consuming, producing):
            self.dataView.expand(self.datatable.indexFromItem(item))

    def _restore_settings(self):
        """ Restore dialog geometry from setting

        Returns
        -------

        """
        with Settings(group=self.__class__.__name__) as settings:
            restore_state(self.dataView.header(),
                          settings.value("TableHeader"))
            restore_geometry(self, settings.value("DialogGeometry"))
            restore_state(self.splitter, settings.value("SplitterState"))

    def save_settings(self):
        """ Store dialog geometry in settings

        Returns
        -------
        None
        """
        with Settings(group=self.__class__.__name__) as settings:
            settings.setValue("SplitterState", self.splitter.saveState())
            settings.setValue("TableHeader",
                              self.dataView.header().saveState())
            settings.setValue("DialogGeometry", self.saveGeometry())

    def delete_temp_file(self):
        """ Delete temporary file created when displaying map

        Returns
        -------

        """
        try:
            os.remove(self.temp_file)
        except:
            pass
예제 #11
0
class DataChangeUI(object):

    def __init__(self, window, uaclient):
        self.window = window
        self.uaclient = uaclient
        self._subhandler = DataChangeHandler()
        self._subscribed_nodes = []
        self.model = QStandardItemModel()
        self.window.ui.subView.setModel(self.model)
        self.window.ui.subView.horizontalHeader().setSectionResizeMode(1)

        self.window.ui.actionSubscribeDataChange.triggered.connect(self._subscribe)
        self.window.ui.actionUnsubscribeDataChange.triggered.connect(self._unsubscribe)

        # populate contextual menu
        self.window.ui.treeView.addAction(self.window.ui.actionSubscribeDataChange)
        self.window.ui.treeView.addAction(self.window.ui.actionUnsubscribeDataChange)

        # handle subscriptions
        self._subhandler.data_change_fired.connect(self._update_subscription_model, type=Qt.QueuedConnection)
        
        # accept drops
        self.model.canDropMimeData = self.canDropMimeData
        self.model.dropMimeData = self.dropMimeData

    def canDropMimeData(self, mdata, action, row, column, parent):
        return True

    def dropMimeData(self, mdata, action, row, column, parent):
        node = self.uaclient.client.get_node(mdata.text())
        self._subscribe(node)
        return True

    def clear(self):
        self._subscribed_nodes = []
        self.model.clear()

    def show_error(self, *args):
        self.window.show_error(*args)

    @trycatchslot
    def _subscribe(self, node=None):
        if not isinstance(node, Node):
            node = self.window.get_current_node()
            if node is None:
                return
        if node in self._subscribed_nodes:
            logger.warning("allready subscribed to node: %s ", node)
            return
        self.model.setHorizontalHeaderLabels(["DisplayName", "Value", "Timestamp"])
        text = str(node.get_display_name().Text)
        row = [QStandardItem(text), QStandardItem("No Data yet"), QStandardItem("")]
        row[0].setData(node)
        self.model.appendRow(row)
        self._subscribed_nodes.append(node)
        self.window.ui.subDockWidget.raise_()
        try:
            self.uaclient.subscribe_datachange(node, self._subhandler)
        except Exception as ex:
            self.window.show_error(ex)
            idx = self.model.indexFromItem(row[0])
            self.model.takeRow(idx.row())
            raise

    @trycatchslot
    def _unsubscribe(self):
        node = self.window.get_current_node()
        if node is None:
            return
        self.uaclient.unsubscribe_datachange(node)
        self._subscribed_nodes.remove(node)
        i = 0
        while self.model.item(i):
            item = self.model.item(i)
            if item.data() == node:
                self.model.removeRow(i)
            i += 1

    def _update_subscription_model(self, node, value, timestamp):
        i = 0
        while self.model.item(i):
            item = self.model.item(i)
            if item.data() == node:
                it = self.model.item(i, 1)
                it.setText(value)
                it_ts = self.model.item(i, 2)
                it_ts.setText(timestamp)
            i += 1
예제 #12
0
class TreeView(QTreeView):
    def __init__(self, *args, **kwargs):
        super(TreeView, self).__init__(*args, **kwargs)
        self._initModel()
        self._initSignals()

    def _initModel(self):
        """设置目录树Model"""
        self._dmodel = QStandardItemModel(self)
        self._fmodel = SortFilterModel(self)
        self._fmodel.setSourceModel(self._dmodel)
        self.setModel(self._fmodel)

    def _initSignals(self):
        Signals.itemJumped.connect(self.onItemJumped)
        Signals.filterChanged.connect(self._fmodel.setFilterRegExp)
        self.clicked.connect(self.onClicked)
        self.doubleClicked.connect(self.onDoubleClicked)

    def rootItem(self):
        """得到根节点Item"""
        return self._dmodel.invisibleRootItem()

    def findItems(self, name):
        """根据名字查找item
        :param name:
        """
        return self._dmodel.findItems(name)

    def onItemJumped(self, name):
        items = self.findItems(name)
        if not items:
            return
        index = self._fmodel.mapFromSource(self._dmodel.indexFromItem(
            items[0]))
        self.setCurrentIndex(index)
        self.expand(index)
        #         # 显示readme
        Signals.urlLoaded.emit(name)

    def listSubDir(self, pitem, path):
        """遍历子目录
        :param item:    上级Item
        :param path:    目录
        """
        paths = os.listdir(path)
        files = []
        for name in paths:
            spath = os.path.join(path, name)
            if not os.path.isfile(spath):
                continue
            spath = os.path.splitext(spath)
            if len(spath) == 0:
                continue
            if spath[1] == '.py' and spath[0].endswith('__init__') == False:
                files.append(name)

        # 已经存在的item
        existsItems = [pitem.child(i).text() for i in range(pitem.rowCount())]

        for name in files:
            if name in existsItems:
                continue
            file = os.path.join(path, name).replace('\\', '/')
            item = QStandardItem(name)
            # 添加自定义的数据
            item.setData(False, Constants.RoleRoot)  # 不是根目录
            item.setData(file, Constants.RolePath)
            try:
                item.setData(
                    open(file, 'rb').read().decode(errors='ignore'),
                    Constants.RoleCode)
            except Exception as e:
                AppLog.warn('read file({}) code error: {}'.format(
                    file, str(e)))
            pitem.appendRow(item)

    def initCatalog(self):
        """初始化本地仓库结构树
        """
        AppLog.debug('')
        if not os.path.exists(Constants.DirProjects):
            return
        pitem = self._dmodel.invisibleRootItem()
        # 只遍历根目录
        for name in os.listdir(Constants.DirProjects):
            file = os.path.join(Constants.DirProjects, name).replace('\\', '/')
            if os.path.isfile(file):  # 跳过文件
                continue
            if name.startswith(
                    '.') or name == 'Donate' or name == 'Test':  # 不显示.开头的文件夹
                continue
            items = self.findItems(name)
            if items:
                item = items[0]
            else:
                item = QStandardItem(name)
                # 添加自定义的数据
                # 用于绘制进度条的item标识
                item.setData(True, Constants.RoleRoot)
                # 目录或者文件的绝对路径
                item.setData(
                    os.path.abspath(os.path.join(Constants.DirProjects, name)),
                    Constants.RolePath)
                pitem.appendRow(item)
            # 遍历子目录
            self.listSubDir(item, file)
        # 排序
        self._fmodel.sort(0, Qt.AscendingOrder)

    def onClicked(self, modelIndex):
        """Item单击
        :param modelIndex:        此处是代理模型中的QModelIndex, 并不是真实的
        """
        root = modelIndex.data(Constants.RoleRoot)
        path = modelIndex.data(Constants.RolePath)
        code = modelIndex.data(Constants.RoleCode)
        AppLog.debug('is root: {}'.format(root))
        AppLog.debug('path: {}'.format(path))
        if not root and os.path.isfile(path) and code:
            # 右侧显示代码
            Signals.showCoded.emit(code)
        if root and os.path.isdir(path):
            if self.isExpanded(modelIndex):
                self.collapse(modelIndex)
            else:
                self.expand(modelIndex)
            # 显示readme
            Signals.showReadmed.emit(os.path.join(path, 'README.md'))

    def onDoubleClicked(self, modelIndex):
        """Item双击
        :param modelIndex:        此处是代理模型中的QModelIndex, 并不是真实的
        """
        root = modelIndex.data(Constants.RoleRoot)
        path = modelIndex.data(Constants.RolePath)
        AppLog.debug('is root: {}'.format(root))
        AppLog.debug('path: {}'.format(path))
        if not root and os.path.isfile(path):
            # 运行代码
            Signals.runExampled.emit(path)
#         if root and os.path.isdir(path):
#             # 显示readme
#             Signals.showReadmed.emit(os.path.join(path, 'README.md'))

    def enterEvent(self, event):
        super(TreeView, self).enterEvent(event)
        # 鼠标进入显示滚动条
        self.verticalScrollBar().setVisible(True)

    def leaveEvent(self, event):
        super(TreeView, self).leaveEvent(event)
        # 鼠标离开隐藏滚动条
        self.verticalScrollBar().setVisible(False)
예제 #13
0
class MyWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MyWindow, self).__init__(parent)
        #uic.loadUi('gui_template.ui',self)

        try:
            self.path_home = os.path.expanduser("~\\Desktop\\")
        except Exception:
            self.path_home = ""

        for curren_dir in ["interim_image"]:
            if os.path.exists(curren_dir):
                if os.path.isdir(curren_dir):
                    print(curren_dir + " is here")
                else:
                    try:
                        os.mkdir(curren_dir)
                    except OSError:
                        print("Error generate dir " + curren_dir)
            else:
                try:
                    os.mkdir(curren_dir)
                except OSError:
                    print("Error generate dir " + curren_dir)

        # Load last path for files
        try:
            with open('last_path.json', "r") as f:
                path_dict = {i: j for i, j in json.load(f).items()}
            self.path_image = path_dict["path_image"]
            self.path_dir_images = path_dict["path_dir_images"]
            self.path_dir_dirs = path_dict["path_dir_dirs"]
            self.path_excel = path_dict["path_excel"]
            self.path_excels = path_dict["path_excels"]
            self.path_vez_excel = path_dict["path_vez_excel"]
            self.path_ro_excel = path_dict["path_ro_excel"]
            self.path_ro_word = path_dict["path_ro_word"]
        except Exception:
            self.path_image = self.path_home
            self.path_dir_images = self.path_home
            self.path_dir_dirs = self.path_home
            self.path_excel = self.path_home
            self.path_excels = self.path_home
            self.path_vez_excel = self.path_home
            self.path_ro_excel = self.path_home
            self.path_ro_word = self.path_home

        self.lv_c = 5
        self.t_c = 0.5
        self.imnam = "слои"

        desktop = QApplication.desktop()
        wd = desktop.width()
        hg = desktop.height()
        ww = 1000
        wh = 500
        if ww > wd: ww = int(0.7 * wd)
        if wh > hg: wh = int(0.7 * hg)
        x = (wd - ww) // 2
        y = (hg - wh) // 2
        self.setGeometry(x, y, ww, wh)

        topVBoxLayout = QVBoxLayout(self)
        topVBoxLayout.setContentsMargins(0, 0, 0, 0)

        SPFrame = QFrame()
        SPFrame.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        SPFrame.setMinimumSize(QSize(150, 100))

        self.ImFrame = QFrame()
        self.ImFrame.setSizePolicy(QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)
        #self.ImFrame.setMinimumSize(QSize(300, 100))

        TbFrame = QFrame()
        TbFrame.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        TbFrame.setMinimumSize(QSize(0, 100))

        RFrame = QFrame()
        RFrame.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
        RFrame.setMinimumSize(QSize(0, 100))

        self.listView = CustomList()  #QListView()
        self.listView.clicked.connect(self.OpenPict)
        self.select_able = True
        self.listView.setcloseEditorSignal(self.Rename)
        #self.listView.editingFinished.connect(self.Rename)
        #self.listView.edit.connect(self.Rename)

        BoxLayout1 = QVBoxLayout()
        self.progress_bar = QProgressBar()
        self.progress_bar.setAlignment(Qt.AlignHCenter)
        BoxLayout1.addWidget(self.progress_bar)
        BoxLayout1.addWidget(self.listView)

        SPFrame.setLayout(BoxLayout1)

        self.Table = CustomTable()  #QTableWidget()
        self.Table.setcloseEditorSignal(
            lambda: self.WriteTable(who_edited="table"))
        #self.Table.itemChanged(lambda:print("change2"))
        self.Table.cellClicked[int, int].connect(self.PosChangedCell)
        self.Table.setColumnCount(3)
        self.Table.setHorizontalHeaderLabels(["p", "h", "d"])
        self.Table.setRowCount(30)
        self.Table.setColumnWidth(0, 50)
        self.Table.setColumnWidth(1, 50)
        self.Table.setColumnWidth(2, 50)
        self.Table.setItemDelegate(DownloadDelegate(self))
        BoxLayout2 = QHBoxLayout()
        BoxLayout2.addWidget(self.Table)
        TbFrame.setLayout(BoxLayout2)

        self.pe1 = QLabel("Рэ1=")
        self.pe2 = QLabel("Рэ2=")
        self.pe = QLabel("Рэ=")
        lb1 = QLabel("Длинна вертикального")
        lb2 = QLabel("заземлителя lв, м")
        lb3 = QLabel("Глубина заложения")
        lb4 = QLabel("вертикального заземлителя t, м")
        lb5 = QLabel("Эквивалентное сопротивление")
        lb6 = QLabel("Двухслойная модель")
        lb7 = QLabel("Однослойная модель")
        lb8 = QLabel("Шаблон искомого изображения")
        lb9 = QLabel("Название проекта")
        lb10 = QLabel("Название ВЛ")
        self.vl = QDoubleSpinBox()
        self.vl.setValue(self.lv_c)
        self.vl.valueChanged.connect(self.WriteTable)
        self.t = QDoubleSpinBox()
        self.t.setValue(self.t_c)
        self.t.valueChanged.connect(self.WriteTable)
        self.pe1_le = QLineEdit()
        self.pe2_le = QLineEdit()
        self.pe_le = QLineEdit()
        self.ImageName = QLineEdit()
        self.ImageName.setText(self.imnam)
        self.NPrj = QLineEdit()
        self.NVL = QLineEdit()
        self.ImageName.editingFinished.connect(self.ReadImName)
        spacerItem = QSpacerItem(2, 20, QSizePolicy.Minimum,
                                 QSizePolicy.Expanding)
        BoxLayout3 = QVBoxLayout()
        BoxLayout4 = QHBoxLayout()
        BoxLayout5 = QHBoxLayout()
        BoxLayout6 = QHBoxLayout()
        BoxLayout3.addWidget(lb8)
        BoxLayout3.addWidget(self.ImageName)
        BoxLayout3.addWidget(lb9)
        BoxLayout3.addWidget(self.NPrj)
        BoxLayout3.addWidget(lb10)
        BoxLayout3.addWidget(self.NVL)
        BoxLayout3.addWidget(lb1)
        BoxLayout3.addWidget(lb2)
        BoxLayout3.addWidget(self.vl)
        BoxLayout3.addWidget(lb3)
        BoxLayout3.addWidget(lb4)
        BoxLayout3.addWidget(self.t)
        BoxLayout3.addWidget(lb5)
        BoxLayout3.addWidget(lb6)
        BoxLayout4.addWidget(self.pe1)
        BoxLayout4.addWidget(self.pe1_le)
        BoxLayout3.addLayout(BoxLayout4)
        BoxLayout5.addWidget(self.pe2)
        BoxLayout5.addWidget(self.pe2_le)
        BoxLayout3.addLayout(BoxLayout5)
        BoxLayout3.addWidget(lb7)
        BoxLayout6.addWidget(self.pe)
        BoxLayout6.addWidget(self.pe_le)
        BoxLayout3.addLayout(BoxLayout6)
        BoxLayout3.addItem(spacerItem)
        RFrame.setLayout(BoxLayout3)

        Splitter1 = QSplitter(Qt.Horizontal)
        Splitter1.addWidget(SPFrame)
        Splitter1.addWidget(self.ImFrame)
        Splitter1.addWidget(TbFrame)
        Splitter1.addWidget(RFrame)
        Splitter1.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        Splitter1.setStretchFactor(1, 4)
        Splitter1.setStretchFactor(1, 1)

        topVBoxLayout.addWidget(Splitter1)

        self.central_widget = QWidget()
        self.central_widget.setLayout(topVBoxLayout)
        self.setCentralWidget(self.central_widget)

        #self.resize()
        #self.central_widget.show()
        self.PaintForm = MyFrame(False, parent=self.ImFrame)

        self.statusBar()
        menubar = self.menuBar()

        exitAction = QAction('&Выход', self)  #QIcon('exit.png'),
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Выход и программы')
        exitAction.triggered.connect(lambda: self.closeEvent(QCloseEvent()))

        Open0Action = QAction('&Открыть изображение',
                              self)  #QIcon('exit.png'),
        Open0Action.setShortcut('Ctrl+1')
        Open0Action.setStatusTip(
            'Результаты ВЭЗ предоставлены одиночными изображениями')
        Open0Action.triggered.connect(self.Op0)

        Open1Action = QAction('&Открыть папку изображений',
                              self)  #QIcon('exit.png'),
        Open1Action.setShortcut('Ctrl+2')
        Open1Action.setStatusTip(
            'Результаты ВЭЗ предоставлены одиночными изображениями')
        Open1Action.triggered.connect(lambda: self.Op12(0))

        Open2Action = QAction('&Открыть папку каталогов',
                              self)  #QIcon('exit.png'),
        Open2Action.setShortcut('Ctrl+3')
        Open2Action.setStatusTip('Результаты ВЭЗ предоставлены набором файлов')
        Open2Action.triggered.connect(lambda: self.Op12(1))

        Open3Action = QAction('&Открыть файл Ecxel', self)  #QIcon('exit.png'),
        Open3Action.setShortcut('Ctrl+4')
        Open3Action.setStatusTip('Результаты ВЭЗ файле Ecxel')
        Open3Action.triggered.connect(self.Op3)

        Open4Action = QAction('&Открыть файлы Ecxel',
                              self)  #QIcon('exit.png'),
        Open4Action.setShortcut('Ctrl+5')
        Open4Action.setStatusTip('Результаты ВЭЗ в нескольких файлах Ecxel')
        Open4Action.triggered.connect(self.Op4)

        NewPick = QAction("Добавить точку", self)
        NewPick.setShortcut('Ctrl+A')
        NewPick.setStatusTip('Добавить новую точку')
        NewPick.triggered.connect(self.NewPick)

        DelPick = QAction("Удалить точку", self)
        DelPick.setShortcut('Ctrl+D')
        DelPick.setStatusTip('Удалить точку из списка')
        DelPick.triggered.connect(self.DelPick)

        ReRead = QAction("Прочитать изображение", self)
        ReRead.setShortcut('Ctrl+R')
        ReRead.setStatusTip('Принудительно считывает информацию с изображения')
        ReRead.triggered.connect(self.ReReadImage)

        Select = QAction("Выбрать все точки", self)
        Select.setShortcut('Ctrl+W')
        Select.setStatusTip('Выбираем все точки из списка')
        Select.triggered.connect(self.select)

        ClearTable = QAction("Очистить таблицу", self)
        ClearTable.setShortcut('Ctrl+Shift+D')
        ClearTable.setStatusTip('Очищает текущую таблицу')
        ClearTable.triggered.connect(self.ClearTable)

        SetLvCheck = QAction("Установить lв выбранным точкам", self)
        SetLvCheck.setShortcut('Ctrl+E')
        SetLvCheck.setStatusTip('Изменяет lв выбраных точек на текущее')
        SetLvCheck.triggered.connect(self.SetLvCheck)

        SetTCheck = QAction("Установить t выбранным точкам", self)
        SetTCheck.setShortcut('Ctrl+T')
        SetTCheck.setStatusTip('Изменяет t выбраных точек на текущее')
        SetTCheck.triggered.connect(self.SetTCheck)

        Save1VEZExcel = QAction("Сохранить ВЭЗ в Excel", self)
        Save1VEZExcel.setShortcut('Ctrl+6')
        Save1VEZExcel.setStatusTip('Сохраняет выбраные ВЭЗ в Excel файл')
        Save1VEZExcel.triggered.connect(lambda: self.Save1VEZExcel(1))

        Save2VEZExcel = QAction("Сохранить Pэ в Excel", self)
        Save2VEZExcel.setShortcut('Ctrl+7')
        Save2VEZExcel.setStatusTip('Сохраняет выбраные Рэ в Excel файл')
        Save2VEZExcel.triggered.connect(lambda: self.Save1VEZExcel(2))

        Save3VEZExcel = QAction("Сохранить Pэ в Word", self)
        Save3VEZExcel.setShortcut('Ctrl+8')
        Save3VEZExcel.setStatusTip('Сохраняет выбраные Рэ в Word файл')
        Save3VEZExcel.triggered.connect(lambda: self.Save1VEZExcel(3))

        EditWord = QAction("Редактировать шаблон Word", self)
        EditWord.setShortcut('Ctrl+G')
        EditWord.setStatusTip('Запускает окно для редактирования шаблона Word')
        EditWord.triggered.connect(self.EditWord)

        zoomIn = QAction("Увеличить изображение", self)
        zoomIn.setShortcut('Ctrl++')
        zoomIn.setStatusTip('Увеличивает открытое изображение')
        zoomIn.triggered.connect(self.PaintForm.zoomIn)

        zoomOut = QAction("Уменьшить изображение", self)
        zoomOut.setShortcut('Ctrl+-')
        zoomOut.setStatusTip('Уменьшает открытое изображение')
        zoomOut.triggered.connect(self.PaintForm.zoomOut)

        Rotate1 = QAction("Повернуть изображение по ч.с", self)
        Rotate1.setShortcut('Ctrl+Shift++')
        Rotate1.setStatusTip('Поворачивает изображение по часовой стрелке')
        Rotate1.triggered.connect(lambda: self.PaintForm.Rotate(1))

        Rotate2 = QAction("Повернуть изображение против ч.с", self)
        Rotate2.setShortcut('Ctrl+Shift+-')
        Rotate2.setStatusTip(
            'Поворачивает изображение ппротив часовой стрелке')
        Rotate2.triggered.connect(lambda: self.PaintForm.Rotate(-1))

        NormalSize = QAction("Вернуть исходный размер", self)
        NormalSize.setShortcut('Ctrl+F')
        NormalSize.setStatusTip('Вернуть исходный размер изображения')
        NormalSize.triggered.connect(self.PaintForm.normalSize)

        fileMenu = menubar.addMenu('&Файл')
        fileMenu.addAction(Open0Action)
        fileMenu.addAction(Open1Action)
        fileMenu.addAction(Open2Action)
        fileMenu.addAction(Open3Action)
        fileMenu.addAction(Open4Action)
        fileMenu.addAction(exitAction)

        editMenu = menubar.addMenu('&Правка')
        editMenu.addAction(NewPick)
        editMenu.addAction(DelPick)
        editMenu.addAction(ReRead)
        editMenu.addAction(Select)
        editMenu.addAction(SetLvCheck)
        editMenu.addAction(SetTCheck)
        editMenu.addAction(ClearTable)

        imageMenu = menubar.addMenu('&Изображение')
        imageMenu.addAction(zoomIn)
        imageMenu.addAction(zoomOut)
        imageMenu.addAction(Rotate1)
        imageMenu.addAction(Rotate2)
        imageMenu.addAction(NormalSize)

        reportMenu = menubar.addMenu('&Отчёт')
        reportMenu.addAction(Save1VEZExcel)
        reportMenu.addAction(Save2VEZExcel)
        reportMenu.addAction(Save3VEZExcel)
        reportMenu.addAction(EditWord)

        self.setWindowTitle('VEZRead')
        self.setWindowIcon(QIcon('images\\op1.png'))

        self.d = {}
        self.sp_m = []
        self.file_path = []
        self.file_name = []
        self.arr = []
        self.stst = []
        self.arr_lv = []
        self.arr_t = []
        self.adres = None
        self.ski = QStandardItemModel()
        self.listView.setModel(self.ski)
        self.listView.pressed.connect(self.presseditem)

        self.sel = True
        self.block = True
        self.back_colors = ((255, 255, 255, 255), (255, 0, 0, 50),
                            (255, 255, 0, 50), (0, 255, 0, 50))

        self.pos_changed_cell = (0, 0)

        #self.NewPick()

    def PosChangedCell(self, i, j):
        self.pos_changed_cell = (i, j)

    def Zap(self):
        self.sel = True
        self.adres = None
        self.d = {}
        self.sp_m = []
        self.ski = QStandardItemModel()
        for i in range(len(self.file_name)):
            self.sp_m.append(
                QStandardItem(QIcon('images\\op1.png'), self.file_name[i]))
            check = (Qt.Checked if self.sel else Qt.Unchecked)
            self.sp_m[i].setCheckable(True)
            self.sp_m[i].setCheckState(check)
            self.ski.appendRow(self.sp_m[i])
            self.d[QPersistentModelIndex(self.sp_m[i].index())] = i
        self.listView.setModel(self.ski)

    def Op0(self):
        try:
            p = QFileDialog.getOpenFileNames(self, 'Открыть файлы',
                                             self.path_image,
                                             "*.png *.jpg *.bmp")
            if p == ([], ''): raise Exception("string index out of range")
            self.file_path = []
            self.file_name = []
            for i in p[0]:
                fname, pr = os.path.splitext(os.path.split(i)[1])
                self.file_path.append(i)
                self.file_name.append(fname)
            self.Zap()
            self.arr = [None for j in self.file_name]
            self.stst = [None for j in self.file_name]
            self.arr_lv = [self.lv_c for j in self.file_name]
            self.arr_t = [self.t_c for j in self.file_name]

            self.path_image = os.path.dirname(p[0][0])

            self.Run_Tread()

        except Exception as ex:
            if str(ex) != "string index out of range":
                ems = QErrorMessage(self)
                ems.setWindowTitle('Возникла ошибка')
                ems.showMessage('При открытии возникла ошибка (' + str(ex) +
                                ').')

    def Tread_signal_handler(self, signal):
        if signal[:5] == "Start":
            self.select_able = False
        elif signal[:4] == "Work":
            a = signal.index("iter_")
            b = signal.index("color_")
            i = int(signal[a + 5:b - 1])
            color = int(signal[b + 6:])
            self.progress_bar.setValue(i)
            self.sp_m[i].setBackground(QBrush(
                QColor(*self.back_colors[color])))
        elif signal[:3] == "End":
            self.select_able = True
            self.progress_bar.reset()
        #self.sp_m[i].setData(self.sp_m[i].data())
        #self.listView.dataChanged(self.sp_m[i].index(),self.sp_m[i].index())
        #self.sp_m[i].setText("aaaa")

    def Run_Tread(self):
        self.progress_bar.setRange(0, len(self.file_path))

        self.potokRasch = Calc_Tread(self.arr, self.stst, self.file_path)
        self.potokRasch.mysignal.connect(self.Tread_signal_handler,
                                         Qt.QueuedConnection)

        if not self.potokRasch.isRunning():
            self.potokRasch.start()

    #QFileDialog.getSaveFileName
    def Op12(self, n):
        try:
            p = ""
            p = QFileDialog.getExistingDirectory(
                self, 'Открыть папку',
                self.path_dir_dirs if n == 1 else self.path_dir_images)
            if p == "": raise Exception("string index out of range")
            if n == 1:
                self.file_path, self.file_name = self.Files(
                    p, n, namef=self.ImageName.text())
                self.path_dir_dirs = p
            else:
                self.file_path, self.file_name = self.Files(p, n)
                self.path_dir_images = p
            #print(self.file_path)
            self.Zap()
            self.arr = [None for j in self.file_name]
            self.stst = [None for j in self.file_name]  ##
            self.arr_lv = [self.lv_c for j in self.file_name]
            self.arr_t = [self.t_c for j in self.file_name]

            self.Run_Tread()
            """ def ClosePotok(self):

                self.potokRasch.stop()
                self.potokRasch.mysignal.disconnect()
                del self.potokRasch """

        except Exception as ex:
            if str(ex) != "string index out of range":
                ems = QErrorMessage(self)
                ems.setWindowTitle('Возникла ошибка')
                ems.showMessage('При открытии возникла ошибка (' + str(ex) +
                                ').')

    def Op3(self):
        try:
            p = QFileDialog.getOpenFileNames(self, 'Открыть файлы',
                                             self.path_excel, "*.xlsx *.xls")
            if p == ([], ''): raise Exception("string index out of range")
            self.file_path = []
            self.file_name = []
            self.arr_lv = []
            self.arr_t = []
            self.arr = []

            for i in p[0]:
                N, nam, lv, t = xlsx.OpenFile(i)
                self.arr += N
                self.stst += [imagescan.define_state(a) for a in N]
                self.file_name += nam
                self.file_path += [None for j in nam]
                self.arr_lv += [j if j != None else self.lv_c for j in lv]
                self.arr_t += [j if j != None else self.t_c for j in t]

            self.path_excel = os.path.dirname(p[0][0])

            self.Zap()

            for i in range(len(self.arr)):
                color = imagescan.common_state(self.stst[i])
                self.sp_m[i].setBackground(
                    QBrush(QColor(*self.back_colors[color])))

        except Exception as ex:
            if str(ex) != "string index out of range":
                ems = QErrorMessage(self)
                ems.setWindowTitle('Возникла ошибка')
                ems.showMessage('При открытии возникла ошибка (' + str(ex) +
                                ').')

    def Op4(self):
        try:
            p = ""
            p = QFileDialog.getExistingDirectory(
                self, 'Открыть папку',
                self.path_excels)  # Обрати внимание на последний элемент
            if p == "": raise Exception("string index out of range")
            file_path, n = self.Files(p, 2)
            self.file_path = []
            self.file_name = []
            self.arr_lv = []
            self.arr_t = []
            self.arr = []
            for i in file_path:
                N, nam, lv, t = xlsx.OpenFile(i)
                self.arr += N
                self.stst += [imagescan.define_state(a) for a in N]
                self.file_name += nam
                self.file_path += [None for j in nam]
                self.arr_lv += [j if j != None else self.lv_c for j in lv]
                self.arr_t += [j if j != None else self.t_c for j in t]

            self.path_excels = p

            self.Zap()

            for i in range(len(self.arr)):
                color = imagescan.common_state(self.stst[i])
                self.sp_m[i].setBackground(
                    QBrush(QColor(*self.back_colors[color])))
        except Exception as ex:
            if str(ex) != "string index out of range":
                ems = QErrorMessage(self)
                ems.setWindowTitle('Возникла ошибка')
                ems.showMessage('При открытии возникла ошибка (' + str(ex) +
                                ').')

    #os.path.exists(file_path)
    #os.path.isfile
    def Files(self, pt, on, namef=None):
        p = []
        n = []
        r1 = set([".png", ".jpg", ".bmp"])
        r2 = set([".xls", ".xlsx"])
        r = [r1, r1, r2, r2]
        for root, dirs, files in os.walk(pt + '/'):
            if on == 0 or on == 2:
                for filename in files:
                    fname, pr = os.path.splitext(filename)
                    if pr in r[on]:
                        n.append(fname)
                        p.append(pt + "/" + filename)

            else:
                for Dir in dirs:
                    for root, dirs, files1 in os.walk(pt + '/' + Dir + '/'):
                        for f in files1:
                            fname, pr = os.path.splitext(f)
                            if fname == namef and pr in r[on]:
                                n.append(Dir)
                                p.append(pt + '/' + Dir + '/' + f)
                        break
            break

        return p, n

    def OpenPict(self, modelindex):
        if self.select_able:
            modelindex = QPersistentModelIndex(modelindex)
            ind = self.d[modelindex]
            #print(self.arr[ind])
            self.PaintForm.Update(self.ImFrame.size())
            p = self.file_path[ind]

            if p != None:
                self.PaintForm.open(p)
                if self.arr[ind] == None:
                    try:
                        arr, st_arr = imagescan.Scan(p)
                    except Exception:
                        arr = None
                        st_arr = None
                    self.arr[ind] = arr
                    self.stst[ind] = st_arr
                else:
                    arr = self.arr[ind]
                    stst = self.stst[ind]
            else:
                self.PaintForm.open("images\\image_error_full.png")
                arr = self.arr[ind]
                stst = self.stst[ind]

            ScrollBar = self.PaintForm.scrollArea.horizontalScrollBar()
            ScrollBar.setValue(ScrollBar.maximum())

            self.block = False
            self.vl.setValue(self.arr_lv[ind])
            self.t.setValue(self.arr_t[ind])
            self.block = True
            #print(self.arr[ind])
            #print("-"*10)

            self.WriteArrTable(arr, stst=stst)

            try:
                pe1, pe2, pe = roo.RschRoo(arr, self.vl.value(),
                                           self.t.value())
                self.pe1_le.setText(str(round(pe1, 2)) + " Ом")
                self.pe2_le.setText(str(round(pe2, 2)) + " Ом")
                self.pe_le.setText(str(round(pe, 2)) + " Ом")
            except Exception:
                self.pe1_le.setText("")
                self.pe2_le.setText("")
                self.pe_le.setText("")

    def WriteArrTable(self, arr, stst=None):
        try:
            self.Table.setRowCount(0)
            self.Table.setRowCount(30 if 30 > len(arr) else len(arr))
            #print(arr, stst)
            for i in range(len(arr)):
                for j in range(3):
                    a = QTableWidgetItem(str(arr[i][j]))
                    self.Table.setItem(i, j, a)
            #print("trtrt",stst)
            for i in range(len(stst)):
                for j in range(3):
                    tb_item = self.Table.item(i, j)
                    if stst is not None:
                        if tb_item is None:
                            tb_item = QTableWidgetItem("")
                            self.Table.setItem(i, j, tb_item)
                        tb_item.setBackground(
                            QBrush(QColor(*self.back_colors[stst[i][j][0]])))

        except Exception as ex:
            print(ex)
            self.Table.setRowCount(0)
            self.Table.setRowCount(30)

    def resizeEvent(self, event):
        self.PaintForm.Update(self.ImFrame.size())

    def ReadTable(self):
        b = 0
        c = ("", "", "")
        N = []
        for i in range(30):
            try:
                a1 = self.Table.item(i, 0).text().replace(",", ".")
                a1 = float(a1) if a1 != '' else a1
            except Exception:
                a1 = ""
            try:
                a2 = self.Table.item(i, 1).text().replace(",", ".")
                a2 = float(a2) if a2 != '' else a2
            except Exception:
                a2 = ""
            try:
                a3 = self.Table.item(i, 2).text().replace(",", ".")
                a3 = float(a3) if a3 != '' else a3
            except Exception:
                a3 = ""
            N.append([a1, a2, a3])
            if str(a1) + str(a2) + str(a3) != "":
                b = i
                c = (a1, a2, a3)

        if str(c[0]) != "" and str(c[1]) != "":
            return N[:b + 2]
        else:
            return N[:b + 1]

    def NewPick(self):
        self.sp_m.append(QStandardItem(QIcon('images\\op1.png'), "Точка"))
        i = len(self.sp_m) - 1
        check = (Qt.Checked if True else Qt.Unchecked)
        self.sp_m[i].setCheckable(True)
        self.sp_m[i].setCheckState(check)
        self.ski.appendRow(self.sp_m[i])
        self.d[QPersistentModelIndex(self.sp_m[i].index())] = i

        self.file_name.append("Точка")
        self.file_path.append(None)
        self.arr.append([])
        self.stst.append([])
        self.arr_lv.append(self.lv_c)
        self.arr_t.append(self.t_c)

    def DelPick(self):
        if self.adres in self.d:
            self.ski.removeRow(self.adres.row())
            self.listView.clearSelection()
            del self.d[self.adres]
            self.adres = None
        #print("test")

    def presseditem(self, modelindex):
        self.adres = QPersistentModelIndex(modelindex)

    def Rename(self):
        ind = self.d[self.adres]
        try:
            modelindex = QModelIndex(self.adres)
            self.file_name[ind] = self.ski.itemFromIndex(modelindex).text()
        except Exception as ex:
            print(ex)

    def set_table_color(self, ind):
        for i in range(len(self.stst[ind])):
            for j in range(3):
                #print(self.stst[ind][i][j])
                tb_item = self.Table.item(i, j)
                if tb_item is None:
                    tb_item = QTableWidgetItem("")
                    self.Table.setItem(i, j, tb_item)
                tb_item.setBackground(
                    QBrush(QColor(*self.back_colors[self.stst[ind][i][j][0]])))
        for i in range(len(self.stst[ind]), 30):
            for j in range(3):
                #print(self.stst[ind][i][j])
                tb_item = self.Table.item(i, j)
                if tb_item is not None:
                    tb_item.setBackground(QBrush(QColor(*self.back_colors[0])))

    def WriteTable(self, who_edited=None):
        if self.block:
            arr = self.ReadTable()
            #print(arr)
            stst = imagescan.define_state(arr)

            if self.adres != None:
                ind = self.d[self.adres]
                self.arr[ind] = arr
                new_stst = imagescan.new_state(self.stst[ind], stst,
                                               *self.pos_changed_cell)
                self.stst[ind] = new_stst
                self.arr_lv[ind] = self.vl.value()
                self.arr_t[ind] = self.t.value()

            if who_edited == "table":
                i, j = self.pos_changed_cell

                self.set_table_color(ind)
                color = imagescan.common_state(self.stst[ind])
                self.sp_m[ind].setBackground(
                    QBrush(QColor(*self.back_colors[color])))
                #print("change1", i,j)

            try:
                pe1, pe2, pe = roo.RschRoo(arr, self.vl.value(),
                                           self.t.value())
                self.pe1_le.setText(str(round(pe1, 2)) + " Ом")
                self.pe2_le.setText(str(round(pe2, 2)) + " Ом")
                self.pe_le.setText(str(round(pe, 2)) + " Ом")
            except Exception:
                self.pe1_le.setText("")
                self.pe2_le.setText("")
                self.pe_le.setText("")

    def ReReadImage(self):
        if self.adres != None:
            ind = self.d[self.adres]
            p = self.file_path[ind]
            if p != None:
                try:
                    arr, st_arr = imagescan.Scan(p)
                    self.arr[ind] = arr
                    self.stst[ind] = st_arr
                    self.WriteArrTable(arr, stst=st_arr)
                    self.WriteTable()
                    color = imagescan.common_state(self.stst[ind])
                    self.sp_m[ind].setBackground(
                        QBrush(QColor(*self.back_colors[color])))
                except Exception:
                    1

    def ClearTable(self):
        if self.adres != None:
            ind = self.d[self.adres]
            self.arr[ind] = []

        self.Table.setRowCount(0)
        self.Table.setRowCount(30)

    def select(self):
        i = 0
        self.sel = not self.sel
        while self.ski.item(i):
            item = self.ski.item(i)
            item.setCheckState(Qt.Checked if self.sel else Qt.Unchecked)
            i += 1

    def SetLvCheck(self):
        i = 0
        while self.ski.item(i):
            item = self.ski.item(i)
            if item.checkState():
                modelindex = QPersistentModelIndex(
                    self.ski.indexFromItem(item))
                ind = self.d[modelindex]
                self.arr_lv[ind] = self.vl.value()
            i += 1

    def SetTCheck(self):
        i = 0
        while self.ski.item(i):
            item = self.ski.item(i)
            if item.checkState():
                modelindex = QPersistentModelIndex(
                    self.ski.indexFromItem(item))
                ind = self.d[modelindex]
                self.arr_t[ind] = self.t.value()
            i += 1

    def ReadImName(self):
        self.imnam = self.ImageName.text()

    def Save1VEZExcel(self, trig=1):
        i = 0
        N = []
        nm = []
        lv = []
        t = []
        Pe1 = []
        Pe2 = []
        Pe = []
        current_path = {
            1: self.path_vez_excel,
            2: self.path_ro_excel,
            3: self.path_ro_word
        }[trig]
        try:
            fname = QFileDialog.getSaveFileName(
                self, 'Сохранить файл', current_path,
                '*.xlsx;;*.xls' if trig != 3 else
                '*.docx')[0]  # Обрати внимание на последний элемент
            while self.ski.item(i):
                item = self.ski.item(i)
                if item.checkState():
                    modelindex = QPersistentModelIndex(
                        self.ski.indexFromItem(item))
                    ind = self.d[modelindex]

                    p = self.file_path[ind]
                    if p != None:
                        if self.arr[ind] == None:
                            try:
                                arr, st_arr = imagescan.Scan(p)
                            except Exception:
                                arr = None
                        else:
                            arr = self.arr[ind]
                    else:
                        arr = self.arr[ind]

                    if arr != None and arr != []:
                        if trig == 1:
                            N.append(arr)
                            nm.append(self.file_name[ind])
                            lv.append(self.arr_lv[ind] if
                                      self.arr_lv[ind] != self.lv_c else None)
                            t.append(self.arr_t[ind]
                                     if self.arr_t[ind] != self.t_c else None)
                        elif trig == 2 or trig == 3:
                            try:
                                pe1, pe2, pe = roo.RschRoo(
                                    arr, self.arr_lv[ind], self.arr_t[ind])
                            except Exception:
                                1
                            else:
                                nm.append(self.file_name[ind])
                                lv.append(self.arr_lv[ind])
                                t.append(self.arr_t[ind])
                                Pe1.append(round(pe1, 2))
                                Pe2.append(round(pe2, 2))
                                Pe.append(round(pe, 2))

                i += 1

            if trig == 1:
                if fname != "": self.path_vez_excel = os.path.dirname(fname)
                xlsx.SaveFile1(fname, nm, N, lv, t)
            elif trig == 2:
                if fname != "": self.path_ro_excel = os.path.dirname(fname)
                xlsx.SaveFile2(fname, nm, Pe1, Pe2, Pe, lv, t)
            elif trig == 3:
                if fname != "": self.path_ro_word = os.path.dirname(fname)
                Word.Word(fname, nm, Pe, lv, t, self.NPrj.text(),
                          self.NVL.text())

        except Exception as ex:
            if str(ex) != "string index out of range":
                ems = QErrorMessage(self)
                ems.setWindowTitle('Возникла ошибка')
                ems.showMessage('Не получилось сгенерировать массив точек. ' +
                                str(ex))
        else:
            mes = QMessageBox.information(self,
                                          'Генерация массива точек',
                                          'Операция прошла успешно.',
                                          buttons=QMessageBox.Ok,
                                          defaultButton=QMessageBox.Ok)

    def closeEvent(self, event):
        Message = QMessageBox(QMessageBox.Question,
                              'Выход из программы',
                              "Вы дейстивлеьно хотите выйти?",
                              parent=self)
        Message.addButton('Да', QMessageBox.YesRole)
        Message.addButton('Нет', QMessageBox.NoRole)
        #Message.addButton('Сохранить', QMessageBox.ActionRole)
        reply = Message.exec()
        if reply == 0:
            # Save last path for files
            with open('last_path.json', "w", encoding="utf8") as f:
                json.dump(
                    {
                        "path_image": self.path_image,
                        "path_dir_images": self.path_dir_images,
                        "path_dir_dirs": self.path_dir_dirs,
                        "path_excel": self.path_excel,
                        "path_excels": self.path_excels,
                        "path_vez_excel": self.path_vez_excel,
                        "path_ro_excel": self.path_ro_excel,
                        "path_ro_word": self.path_ro_word
                    },
                    f,
                    indent=4)

            qApp.quit()
        elif reply == 1:
            event.ignore()

    def EditWord(self):
        try:
            win32api.ShellExecute(0, 'open', "Word_template\\Template.docx",
                                  '', '', 1)
        except Exception as ex:
            if str(
                    ex
            ) == "(2, 'ShellExecute', 'Не удается найти указанный файл.')":
                d = Document()
                d.save("Word_template\\Template.docx")
            else:
                print(ex)
예제 #14
0
class StepListView:
    def __init__(self, main_window):
        self.main_window = main_window
        self.lst_steps: CustomStepsListView = self.main_window.lst_steps
        self.controller = StepListController(self, self.main_window.world)

        # menu
        delete_action = QAction("Delete", self.lst_steps)
        delete_action.triggered.connect(self.on_delete_selected_item)

        self.menu = QMenu()
        self.menu.addAction(delete_action)

        # setup model
        self.model = QStandardItemModel()
        self.lst_steps.setModel(self.model)
        self.lst_steps.setItemDelegate(StepItemDelegate())

        # ui events
        self.lst_steps.selectionModel().currentChanged.connect(
            self.on_step_selected)
        self.lst_steps.setContextMenuPolicy(Qt.CustomContextMenu)
        self.lst_steps.customContextMenuRequested.connect(
            self.on_display_context_menu)
        self.lst_steps.dropEventSignal.connect(self.on_drop_event)

    def on_drop_event(self, model_index: QModelIndex):
        selected_model_indexes = self.lst_steps.selectedIndexes()
        self.delete_steps_by_indexes(selected_model_indexes,
                                     delete_from_db=False)

        def step_with_order(order):
            step_entity = self.model.item(order).data(STEP_LIST_OBJECT_ROLE)
            step_entity.order = order
            return step_entity

        steps = [step_with_order(n) for n in range(self.model.rowCount())]
        self.controller.update_multiple_steps(steps)

    def get_step_entity_at_index(self, model_index):
        return model_index.data(STEP_LIST_OBJECT_ROLE)

    def select_step_at_index(self, model_index):
        self.lst_steps.setCurrentIndex(model_index)

    def indexes_for_selected_rows(self):
        return self.lst_steps.selectedIndexes()

    def delete_steps_by_indexes(self, model_indexes, delete_from_db=True):
        for items in reversed(sorted(model_indexes)):
            if delete_from_db:
                step_entity: StepEntity = self.get_step_entity_at_index(items)
                self.controller.delete_step(step_entity)
            self.model.takeRow(items.row())

    def on_delete_selected_item(self):
        selected_model_indexes = self.indexes_for_selected_rows()
        if not selected_model_indexes:
            return

        self.delete_steps_by_indexes(selected_model_indexes)

        before_first_row_to_delete = selected_model_indexes[0].row() - 1
        if before_first_row_to_delete >= 0:
            previous_item: QStandardItem = self.model.item(
                before_first_row_to_delete)
            if previous_item:
                self.select_step_at_index(previous_item.index())

    def on_display_context_menu(self, position):
        index: QModelIndex = self.lst_steps.indexAt(position)
        if not index.isValid():
            return

        global_position = self.lst_steps.viewport().mapToGlobal(position)
        self.menu.exec_(global_position)

    def clear_steps(self):
        self.model.clear()

    def update_steps(self, steps):
        for step in steps:
            self.add_step_widget(step, select_item=False)

    def select_step_at(self, position):
        first_item: QStandardItem = self.model.item(position)
        self.lst_steps.setCurrentIndex(first_item.index())

    def add_step_widget(self, step: StepEntity, select_item=True):
        logging.info("Adding a new widget for {}".format(step))
        step_item = QStandardItem("({}) {}".format(step.step_type.value,
                                                   step.title))
        step_item.setData(step, STEP_LIST_OBJECT_ROLE)
        step_item.setData(QVariant(step.id), STEP_LIST_ID_ROLE)
        step_item.setDragEnabled(True)
        step_item.setDropEnabled(False)
        self.model.appendRow(step_item)

        if select_item:
            index = self.model.indexFromItem(step_item)
            self.lst_steps.setCurrentIndex(index)

    def on_step_selected(self, current: QModelIndex):
        if not current:
            return

        selected_step_id = current.data(STEP_LIST_ID_ROLE)
        self.controller.trigger_step_selected(selected_step_id)
예제 #15
0
class QWTable(QTableView):
    def __init__(self, parent=None):
        QTableView.__init__(self, parent)
        self._name = self.__class__.__name__

        icon.set_icons()

        self.is_connected_item_changed = False

        self.model = QStandardItemModel()
        self.set_selection_mode()
        self.fill_table_model()  # defines self.model
        self.setModel(self.model)

        self.connect_item_selected(self.on_item_selected)
        self.clicked.connect(self.on_click)
        self.doubleClicked.connect(self.on_double_click)
        #self.connect_item_changed(self.on_item_changed)

        self.set_style()

    #def __del__(self):
    #    QTableView.__del__(self) - it does not have __del__

    def set_selection_mode(self, smode=QAbstractItemView.ExtendedSelection):
        logger.debug('Set selection mode: %s' % smode)
        self.setSelectionMode(smode)

    def connect_item_changed(self, recipient):
        self.model.itemChanged.connect(recipient)
        self.is_connected_item_changed = True

    def disconnect_item_changed(self, recipient):
        if self.is_connected_item_changed:
            self.model.itemChanged.disconnect(recipient)
            self.is_connected_item_changed = False

    def connect_item_selected(self, recipient):
        self.selectionModel().currentChanged[QModelIndex,
                                             QModelIndex].connect(recipient)

    def disconnect_item_selected(self, recipient):
        self.selectionModel().currentChanged[QModelIndex,
                                             QModelIndex].disconnect(recipient)

    def set_style(self):
        self.setStyleSheet("QTableView::item:hover{background-color:#00FFAA;}")

    def fill_table_model(self):
        self.clear_model()
        self.model.setHorizontalHeaderLabels(
            ['col0', 'col1', 'col2', 'col3', 'col4'])
        self.model.setVerticalHeaderLabels(['row0', 'row1', 'row2', 'row3'])
        for row in range(0, 4):
            for col in range(0, 6):
                item = QStandardItem("itemA %d %d" % (row, col))
                item.setIcon(icon.icon_table)
                item.setCheckable(True)
                self.model.setItem(row, col, item)
                if col == 2: item.setIcon(icon.icon_folder_closed)
                if col == 3: item.setText('Some text')
                #self.model.appendRow(item)

    def clear_model(self):
        rows, cols = self.model.rowCount(), self.model.columnCount()
        self.model.removeRows(0, rows)
        self.model.removeColumns(0, cols)

    def selected_indexes(self):
        return self.selectedIndexes()

    def selected_items(self):
        indexes = self.selectedIndexes()
        return [self.model.itemFromIndex(i) for i in self.selectedIndexes()]

    def getFullNameFromItem(self, item):
        #item = self.model.itemFromIndex(ind)
        ind = self.model.indexFromItem(item)
        return self.getFullNameFromIndex(ind)

    def getFullNameFromIndex(self, ind):
        item = self.model.itemFromIndex(ind)
        if item is None: return None
        self._full_name = item.text()
        self._getFullName(ind)
        return self._full_name

    def _getFullName(self, ind):
        ind_par = self.model.parent(ind)
        if (ind_par.column() == -1):
            item = self.model.itemFromIndex(ind)
            self.full_name = '/' + self._full_name
            #logger.debug('Item full name:' + self._full_name)
            return self._full_name
        else:
            item_par = self.model.itemFromIndex(ind_par)
            self._full_name = item_par.text() + '/' + self._full_name
            self._getFullName(ind_par)

    def closeEvent(self, event):  # if the x is clicked
        logger.debug('closeEvent')

    def on_click(self, index):
        item = self.model.itemFromIndex(index)
        msg = 'on_click: item in row:%02d text: %s' % (index.row(),
                                                       item.text())
        logger.debug(msg)

    def on_double_click(self, index):
        item = self.model.itemFromIndex(index)
        msg = 'on_double_click: item in row:%02d text: %s' % (index.row(),
                                                              item.text())
        logger.debug(msg)

    def on_item_selected(self, ind_sel, ind_desel):
        item = self.model.itemFromIndex(ind_sel)
        logger.debug('on_item_selected: "%s" is selected' %
                     (item.text() if item is not None else None))

    def on_item_changed(self, item):
        state = ['UNCHECKED', 'TRISTATE', 'CHECKED'][item.checkState()]
        logger.debug('abstract on_item_changed: "%s" at state %s' %
                     (self.getFullNameFromItem(item), state))

    def process_selected_items(self):
        selitems = self.selected_items()
        msg = '%d Selected items:' % len(selitems)
        for i in selitems:
            msg += '\n  %s' % i.text()
        logger.info(msg)

    def key_usage(self):
        return 'Keys:'\
               '\n  ESC - exit'\
               '\n  S - show selected items'\
               '\n'

    def keyPressEvent(self, e):
        logger.info('keyPressEvent, key=', e.key())
        if e.key() == Qt.Key_Escape:
            self.close()

        elif e.key() == Qt.Key_S:
            self.process_selected_items()

        else:
            logger.info(self.key_usage())
예제 #16
0
class Window(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        #fix stuff imposible to do in qtdesigner
        #remove dock titlebar for addressbar
        w = QWidget()
        self.ui.addrDockWidget.setTitleBarWidget(w)
        #tabify some docks
        self.tabifyDockWidget(self.ui.evDockWidget, self.ui.subDockWidget)
        self.tabifyDockWidget(self.ui.subDockWidget, self.ui.refDockWidget)

        # init widgets
        self.ui.statusBar.hide()
        self.ui.addrComboBox.insertItem(-1, "opc.tcp://localhost:4841/")
        self.ui.addrComboBox.insertItem(
            1, "opc.tcp://localhost:53530/OPCUA/SimulationServer/")
        self.ui.addrComboBox.insertItem(1, "opc.tcp://10.0.5.15:49320/")

        self.attr_model = QStandardItemModel()
        self.refs_model = QStandardItemModel()
        self.sub_model = QStandardItemModel()
        self.ui.attrView.setModel(self.attr_model)
        self.ui.refView.setModel(self.refs_model)
        self.ui.subView.setModel(self.sub_model)

        self.model = MyModel(self)
        self.model.clear()
        self.model.error.connect(self.show_error)
        self.ui.treeView.setModel(self.model)
        self.ui.treeView.setUniformRowHeights(True)
        self.ui.treeView.setSelectionBehavior(QAbstractItemView.SelectRows)

        self.uaclient = UaClient()
        self.ui.connectButton.clicked.connect(self._connect)
        self.ui.disconnectButton.clicked.connect(self._disconnect)
        self.ui.treeView.activated.connect(self._show_attrs_and_refs)
        self.ui.treeView.clicked.connect(self._show_attrs_and_refs)
        self.ui.treeView.expanded.connect(self._fit)

        self.ui.actionSubscribeDataChange.triggered.connect(self._subscribe)
        self.ui.actionSubscribeEvent.triggered.connect(self._subscribeEvent)
        self.ui.actionConnect.triggered.connect(self._connect)
        self.ui.actionDisconnect.triggered.connect(self._disconnect)

        self.ui.attrRefreshButton.clicked.connect(self.show_attrs)

        # context menu
        self.ui.treeView.addAction(self.ui.actionSubscribeDataChange)
        self.ui.treeView.addAction(self.ui.actionSubscribeEvent)
        self.ui.treeView.addAction(self.ui.actionUnsubscribe)

        # handle subscriptions
        self._subhandler = SubHandler(self.sub_model)

    def show_error(self, msg, level=1):
        print("showing error: ", msg, level)
        self.ui.statusBar.show()
        self.ui.statusBar.setStyleSheet(
            "QStatusBar { background-color : red; color : black; }")
        #self.ui.statusBar.clear()
        self.ui.statusBar.showMessage(str(msg))
        QTimer.singleShot(1500, self.ui.statusBar.hide)

    def _fit(self, idx):
        self.ui.treeView.resizeColumnToContents(0)

    def _subscribeEvent(self):
        self.showError("Not Implemented")

    def _subscribe(self):
        idx = self.ui.treeView.currentIndex()
        it = self.model.itemFromIndex(idx)
        if not id:
            self.show_error("No item currently selected")
        node = it.data()
        attrs = self.uaclient.get_node_attrs(node)
        self.sub_model.setHorizontalHeaderLabels(
            ["DisplayName", "Browse Name", 'NodeId', "Value"])
        item = QStandardItem(attrs[1])
        self.sub_model.appendRow(
            [item, QStandardItem(attrs[2]),
             QStandardItem(attrs[3])])
        try:
            # FIXME use handle to unsubscribe!!!
            handle = self.uaclient.subscribe(node, self._subhandler)
        except Exception as ex:
            self.show_error(ex)
            idx = self.sub_model.indexFromItem(item)
            self.sub_model.takeRow(idx.row())

    def unsubscribe(self):
        idx = self.ui.treeView.currentIndex()
        it = self.model.itemFromIndex(idx)
        if not id:
            print("No item currently selected")
        node = it.data()
        self.uaclient.unsubscribe(node)

    def _show_attrs_and_refs(self, idx):
        node = self.get_current_node(idx)
        if node:
            self._show_attrs(node)
            self._show_refs(node)

    def get_current_node(self, idx=None):
        if idx is None:
            idx = self.ui.treeView.currentIndex()
        it = self.model.itemFromIndex(idx)
        if not it:
            return None
        node = it.data()
        if not node:
            print("No node for item:", it, it.text())
            return None
        return node

    def show_attrs(self):
        node = self.get_current_node()
        if node:
            self._show_attrs(node)
        else:
            self.attr_model.clear()

    def _show_refs(self, node):
        self.refs_model.clear()
        self.refs_model.setHorizontalHeaderLabels(
            ['ReferenceType', 'NodeId', "BrowseName", "TypeDefinition"])
        try:
            refs = self.uaclient.get_all_refs(node)
        except Exception as ex:
            self.show_error(ex)
            raise
        for ref in refs:
            self.refs_model.appendRow([
                QStandardItem(str(ref.ReferenceTypeId)),
                QStandardItem(str(ref.NodeId)),
                QStandardItem(str(ref.BrowseName)),
                QStandardItem(str(ref.TypeDefinition))
            ])
        self.ui.refView.resizeColumnToContents(0)
        self.ui.refView.resizeColumnToContents(1)
        self.ui.refView.resizeColumnToContents(2)
        self.ui.refView.resizeColumnToContents(3)

    def _show_attrs(self, node):
        try:
            attrs = self.uaclient.get_all_attrs(node)
        except Exception as ex:
            self.show_error(ex)
            raise
        self.attr_model.clear()
        self.attr_model.setHorizontalHeaderLabels(['Attribute', 'Value'])
        for k, v in attrs.items():
            self.attr_model.appendRow(
                [QStandardItem(k), QStandardItem(str(v))])
        self.ui.attrView.resizeColumnToContents(0)
        self.ui.attrView.resizeColumnToContents(1)

    def _connect(self):
        uri = self.ui.addrComboBox.currentText()
        try:
            self.uaclient.connect(uri)
        except Exception as ex:
            self.show_error(ex)
            raise

        self.model.client = self.uaclient
        self.model.clear()
        self.model.add_item(self.uaclient.get_root_attrs())
        self.ui.treeView.resizeColumnToContents(0)
        self.ui.treeView.resizeColumnToContents(1)
        self.ui.treeView.resizeColumnToContents(2)

    def _disconnect(self):
        try:
            self.uaclient.disconnect()
        except Exception as ex:
            self.show_error(ex)
            raise
        finally:
            self.model.clear()
            self.sub_model.clear()
            self.model.client = None

    def closeEvent(self, event):
        self._disconnect()
        event.accept()
예제 #17
0
class MainWindow(QMainWindow, Ui_MainWindow):
	"""
	Main window for the application with groups and password lists
	"""

	KEY_IDX = 0  # column where key is shown in password table
	PASSWORD_IDX = 1  # column where password is shown in password table
	COMMENTS_IDX = 2  # column where comments is shown in password table
	NO_OF_PASSWDTABLE_COLUMNS = 3  # 3 columns: key + value/passwd/secret + comments
	CACHE_IDX = 0  # column of QWidgetItem in whose data we cache decrypted passwords+comments

	def __init__(self, pwMap, settings, dbFilename):
		"""
		@param pwMap: a PasswordMap instance with encrypted passwords
		@param dbFilename: file name for saving pwMap
		"""
		super(MainWindow, self).__init__()
		self.setupUi(self)

		self.logger = settings.logger
		self.settings = settings
		self.pwMap = pwMap
		self.selectedGroup = None
		self.modified = False  # modified flag for "Save?" question on exit
		self.dbFilename = dbFilename

		self.groupsModel = QStandardItemModel(parent=self)
		self.groupsModel.setHorizontalHeaderLabels([u"Password group"])
		self.groupsFilter = QSortFilterProxyModel(parent=self)
		self.groupsFilter.setSourceModel(self.groupsModel)

		self.groupsTree.setModel(self.groupsFilter)
		self.groupsTree.setContextMenuPolicy(Qt.CustomContextMenu)
		self.groupsTree.customContextMenuRequested.connect(self.showGroupsContextMenu)
		# Dont use e following line, it would cause loadPasswordsBySelection
		# to be called twice on mouse-click.
		# self.groupsTree.clicked.connect(self.loadPasswordsBySelection)
		self.groupsTree.selectionModel().selectionChanged.connect(self.loadPasswordsBySelection)
		self.groupsTree.setSortingEnabled(True)

		self.passwordTable.setContextMenuPolicy(Qt.CustomContextMenu)
		self.passwordTable.customContextMenuRequested.connect(self.showPasswdContextMenu)
		self.passwordTable.setSelectionBehavior(QAbstractItemView.SelectRows)
		self.passwordTable.setSelectionMode(QAbstractItemView.SingleSelection)

		shortcut = QShortcut(QKeySequence(u"Ctrl+C"), self.passwordTable, self.copyPasswordFromSelection)
		shortcut.setContext(Qt.WidgetShortcut)

		self.actionQuit.triggered.connect(self.close)
		self.actionQuit.setShortcut(QKeySequence(u"Ctrl+Q"))
		self.actionExport.triggered.connect(self.exportCsv)
		self.actionImport.triggered.connect(self.importCsv)
		self.actionBackup.triggered.connect(self.saveBackup)
		self.actionAbout.triggered.connect(self.printAbout)
		self.actionSave.triggered.connect(self.saveDatabase)
		self.actionSave.setShortcut(QKeySequence(u"Ctrl+S"))

		# headerKey = QTableWidgetItem(u"Key")
		# headerValue = QTableWidgetItem(u"Password/Value")
		# headerComments = QTableWidgetItem(u"Comments")
		# self.passwordTable.setColumnCount(self.NO_OF_PASSWDTABLE_COLUMNS)
		# self.passwordTable.setHorizontalHeaderItem(self.KEY_IDX, headerKey)
		# self.passwordTable.setHorizontalHeaderItem(self.PASSWORD_IDX, headerValue)
		# self.passwordTable.setHorizontalHeaderItem(self.COMMENTS_IDX, headerComments)
		#
		# self.passwordTable.resizeRowsToContents()
		# self.passwordTable.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
		# self.passwordTable.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeToContents)
		# self.passwordTable.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeToContents)

		self.searchEdit.textChanged.connect(self.filterGroups)

		if pwMap is not None:
			self.setPwMap(pwMap)

		self.clipboard = QApplication.clipboard()

		self.timer = QTimer(parent=self)
		self.timer.timeout.connect(self.clearClipboard)

	def setPwMap(self, pwMap):
		""" if not done in __init__ pwMap can be supplied later """
		self.pwMap = pwMap
		groupNames = self.pwMap.groups.keys()
		for groupName in groupNames:
			item = QStandardItem(groupName)
			self.groupsModel.appendRow(item)
		self.groupsTree.sortByColumn(0, Qt.AscendingOrder)
		self.settings.mlogger.log("pwMap was initialized.",
			logging.DEBUG, "GUI IO")

	def setModified(self, modified):
		"""
		Sets the modified flag so that user is notified when exiting
		with unsaved changes.
		"""
		self.modified = modified
		self.setWindowTitle("TrezorPass" + "*" * int(self.modified))

	def showGroupsContextMenu(self, point):
		"""
		Show context menu for group management.

		@param point: point in self.groupsTree where click occured
		"""
		self.addGroupMenu = QMenu(self)
		newGroupAction = QAction('Add group', self)
		editGroupAction = QAction('Rename group', self)
		deleteGroupAction = QAction('Delete group', self)
		self.addGroupMenu.addAction(newGroupAction)
		self.addGroupMenu.addAction(editGroupAction)
		self.addGroupMenu.addAction(deleteGroupAction)

		# disable deleting if no point is clicked on
		proxyIdx = self.groupsTree.indexAt(point)
		itemIdx = self.groupsFilter.mapToSource(proxyIdx)
		item = self.groupsModel.itemFromIndex(itemIdx)
		if item is None:
			deleteGroupAction.setEnabled(False)

		action = self.addGroupMenu.exec_(self.groupsTree.mapToGlobal(point))

		if action == newGroupAction:
			self.createGroupWithCheck()
		elif action == editGroupAction:
			self.editGroupWithCheck(item)
		elif action == deleteGroupAction:
			self.deleteGroupWithCheck(item)

	def showPasswdContextMenu(self, point):
		"""
		Show context menu for password management

		@param point: point in self.passwordTable where click occured
		"""
		self.passwdMenu = QMenu(self)
		showPasswordAction = QAction('Show password', self)
		copyPasswordAction = QAction('Copy password', self)
		copyPasswordAction.setShortcut(QKeySequence("Ctrl+C"))
		showCommentsAction = QAction('Show comments', self)
		copyCommentsAction = QAction('Copy comments', self)
		showAllAction = QAction('Show all of group', self)
		newItemAction = QAction('New item', self)
		deleteItemAction = QAction('Delete item', self)
		editItemAction = QAction('Edit item', self)
		self.passwdMenu.addAction(showPasswordAction)
		self.passwdMenu.addAction(copyPasswordAction)
		self.passwdMenu.addSeparator()
		self.passwdMenu.addAction(showCommentsAction)
		self.passwdMenu.addAction(copyCommentsAction)
		self.passwdMenu.addSeparator()
		self.passwdMenu.addAction(showAllAction)
		self.passwdMenu.addSeparator()
		self.passwdMenu.addAction(newItemAction)
		self.passwdMenu.addAction(deleteItemAction)
		self.passwdMenu.addAction(editItemAction)

		# disable creating if no group is selected
		if self.selectedGroup is None:
			newItemAction.setEnabled(False)
			showAllAction.setEnabled(False)

		# disable deleting if no point is clicked on
		item = self.passwordTable.itemAt(point.x(), point.y())
		if item is None:
			deleteItemAction.setEnabled(False)
			showPasswordAction.setEnabled(False)
			copyPasswordAction.setEnabled(False)
			showCommentsAction.setEnabled(False)
			copyCommentsAction.setEnabled(False)
			editItemAction.setEnabled(False)

		action = self.passwdMenu.exec_(self.passwordTable.mapToGlobal(point))
		if action == newItemAction:
			self.createPassword()
		elif action == deleteItemAction:
			self.deletePassword(item)
		elif action == editItemAction:
			self.editPassword(item)
		elif action == copyPasswordAction:
			self.copyPasswordFromItem(item)
		elif action == showPasswordAction:
			self.showPassword(item)
		elif action == copyCommentsAction:
			self.copyCommentsFromItem(item)
		elif action == showCommentsAction:
			self.showComments(item)
		elif action == showAllAction:
			self.showAll()

	def createGroup(self, groupName, group=None):
		"""
		Slot to create a password group.
		"""
		newItem = QStandardItem(groupName)
		self.groupsModel.appendRow(newItem)
		self.pwMap.addGroup(groupName)
		if group is not None:
			self.pwMap.replaceGroup(groupName, group)

		# make new item selected to save a few clicks
		itemIdx = self.groupsModel.indexFromItem(newItem)
		proxyIdx = self.groupsFilter.mapFromSource(itemIdx)
		self.groupsTree.selectionModel().select(proxyIdx,
			QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows)
		self.groupsTree.sortByColumn(0, Qt.AscendingOrder)

		# Make item's passwords loaded so new key-value entries can be created
		# right away - better from UX perspective.
		self.loadPasswords(newItem)

		self.setModified(True)
		self.settings.mlogger.log("Group '%s' was created." % (groupName),
			logging.DEBUG, "GUI IO")

	def createGroupWithCheck(self):
		"""
		Slot to create a password group.
		"""
		dialog = AddGroupDialog(self.pwMap.groups, self.settings)
		if not dialog.exec_():
			return
		groupName = dialog.newGroupName()
		self.createGroup(groupName)

	def createRenamedGroupWithCheck(self, groupNameOld, groupNameNew):
		"""
		Creates a copy of a group by name as utf-8 encoded string
		with a new group name.
		A more appropriate name for the method would be:
		createRenamedGroup().
		Since the entries inside the group are encrypted
		with the groupName, we cannot simply make a copy.
		We must decrypt with old name and afterwards encrypt
		with new name.
		If the group has many entries, each entry would require a 'Confirm'
		press on Trezor. So, to mkae it faster and more userfriendly
		we use the backup key to decrypt. This requires a single
		Trezor 'Confirm' press independent of how many entries there are
		in the group.
		@param groupNameOld: name of group to copy and rename
		@type groupNameOld: string
		@param groupNameNew: name of group to be created
		@type groupNameNew: string
		"""
		if groupNameOld not in self.pwMap.groups:
			raise KeyError("Password group does not exist")
		# with less than 3 rows dont bother the user with a pop-up
		rowCount = len(self.pwMap.groups[groupNameOld].entries)
		if rowCount < 3:
			self.pwMap.createRenamedGroupSecure(groupNameOld, groupNameNew)
			return

		msgBox = QMessageBox(parent=self)
		msgBox.setText("Do you want to use the more secure way?")
		msgBox.setIcon(QMessageBox.Question)
		msgBox.setWindowTitle("How to decrypt?")
		msgBox.setDetailedText("The more secure way requires pressing 'Confirm' "
			"on Trezor once for each entry in the group. %d presses in this "
			"case. This is recommended. "
			"Select 'Yes'.\n\n"
			"The less secure way requires only a single 'Confirm' click on the "
			"Trezor. This is not recommended. "
			"Select 'No'." % (rowCount))
		msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
		msgBox.setDefaultButton(QMessageBox.Yes)
		res = msgBox.exec_()

		if res == QMessageBox.Yes:
			moreSecure = True
		else:
			moreSecure = False
		groupNew = self.pwMap.createRenamedGroup(groupNameOld, groupNameNew, moreSecure)
		self.settings.mlogger.log("Copy of group '%s' with new name '%s' "
			"was created the %s way." %
			(groupNameOld, groupNameNew, 'secure' if moreSecure else 'fast'),
			logging.DEBUG, "GUI IO")
		return(groupNew)

	def editGroup(self, item, groupNameOld, groupNameNew):
		"""
		Slot to edit name a password group.
		"""
		groupNew = self.createRenamedGroupWithCheck(groupNameOld, groupNameNew)
		self.deleteGroup(item)
		self.createGroup(groupNameNew, groupNew)
		self.settings.mlogger.log("Group '%s' was renamed to '%s'." % (groupNameOld, groupNameNew),
			logging.DEBUG, "GUI IO")

	def editGroupWithCheck(self, item):
		"""
		Slot to edit name a password group.
		"""
		groupNameOld = encoding.normalize_nfc(item.text())
		dialog = AddGroupDialog(self.pwMap.groups, self.settings)
		dialog.setWindowTitle("Edit group name")
		dialog.groupNameLabel.setText("New name for group")
		dialog.setNewGroupName(groupNameOld)
		if not dialog.exec_():
			return

		groupNameNew = dialog.newGroupName()
		self.editGroup(item, groupNameOld, groupNameNew)

	def deleteGroup(self, item):  # without checking user
		groupName = encoding.normalize_nfc(item.text())
		self.selectedGroup = None
		del self.pwMap.groups[groupName]

		itemIdx = self.groupsModel.indexFromItem(item)
		self.groupsModel.takeRow(itemIdx.row())
		self.passwordTable.setRowCount(0)
		self.groupsTree.clearSelection()

		self.setModified(True)
		self.settings.mlogger.log("Group '%s' was deleted." % (groupName),
			logging.DEBUG, "GUI IO")

	def deleteGroupWithCheck(self, item):
		msgBox = QMessageBox(text="Are you sure about delete?", parent=self)
		msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
		res = msgBox.exec_()

		if res != QMessageBox.Yes:
			return

		self.deleteGroup(item)

	def deletePassword(self, item):
		msgBox = QMessageBox(text="Are you sure about delete?", parent=self)
		msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
		res = msgBox.exec_()

		if res != QMessageBox.Yes:
			return

		row = self.passwordTable.row(item)
		self.passwordTable.removeRow(row)
		group = self.pwMap.groups[self.selectedGroup]
		group.removeEntry(row)

		self.passwordTable.resizeRowsToContents()
		self.passwordTable.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
		self.passwordTable.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeToContents)
		self.passwordTable.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeToContents)
		self.setModified(True)
		self.settings.mlogger.log("Row '%d' was deleted." % (row),
			logging.DEBUG, "GUI IO")

	def logCache(self, row):
		item = self.passwordTable.item(row, self.CACHE_IDX)
		cachedTuple = item.data(Qt.UserRole)
		if cachedTuple is None:
			cachedPassword, cachedComments = (None, None)
		else:
			cachedPassword, cachedComments = cachedTuple
		if cachedPassword is not None:
			cachedPassword = u'***'
		if cachedComments is not None:
			cachedComments = cachedComments[0:3] + u'...'
		self.settings.mlogger.log("Cache holds '%s' and '%s'." %
			(cachedPassword, cachedComments), logging.DEBUG, "Cache")

	def cachePasswordComments(self, row, password, comments):
		item = self.passwordTable.item(row, self.CACHE_IDX)
		item.setData(Qt.UserRole, QVariant((password, comments)))

	def cachedPassword(self, row):
		"""
		Retrieve cached password for given row of currently selected group.
		Returns password as string or None if no password cached.
		"""
		item = self.passwordTable.item(row, self.CACHE_IDX)
		cachedTuple = item.data(Qt.UserRole)
		if cachedTuple is None:
			cachedPassword, cachedComments = (None, None)
		else:
			cachedPassword, cachedComments = cachedTuple
		return cachedPassword

	def cachedComments(self, row):
		"""
		Retrieve cached comments for given row of currently selected group.
		Returns comments as string or None if no coments cached.
		"""
		item = self.passwordTable.item(row, self.CACHE_IDX)
		cachedTuple = item.data(Qt.UserRole)
		if cachedTuple is None:
			cachedPassword, cachedComments = (None, None)
		else:
			cachedPassword, cachedComments = cachedTuple
		return cachedComments

	def cachedOrDecryptPassword(self, row):
		"""
		Try retrieving cached password for item in given row, otherwise
		decrypt with Trezor.
		"""
		cached = self.cachedPassword(row)

		if cached is not None:
			return cached
		else:  # decrypt with Trezor
			group = self.pwMap.groups[self.selectedGroup]
			pwEntry = group.entry(row)
			encPwComments = pwEntry[1]

			decryptedPwComments = self.pwMap.decryptPassword(encPwComments, self.selectedGroup)
			lngth = int(decryptedPwComments[0:4])
			decryptedPassword = decryptedPwComments[4:4+lngth]
			decryptedComments = decryptedPwComments[4+lngth:]
			# while we are at it, cache the comments too
			self.cachePasswordComments(row, decryptedPassword, decryptedComments)
			self.settings.mlogger.log("Decrypted password and comments "
				"for '%s', row '%d'." % (pwEntry[0], row),
				logging.DEBUG, "GUI IO")
		return decryptedPassword

	def cachedOrDecryptComments(self, row):
		"""
		Try retrieving cached comments for item in given row, otherwise
		decrypt with Trezor.
		"""
		cached = self.cachedComments(row)

		if cached is not None:
			return cached
		else:  # decrypt with Trezor
			group = self.pwMap.groups[self.selectedGroup]
			pwEntry = group.entry(row)
			encPwComments = pwEntry[1]

			decryptedPwComments = self.pwMap.decryptPassword(encPwComments, self.selectedGroup)
			lngth = int(decryptedPwComments[0:4])
			decryptedPassword = decryptedPwComments[4:4+lngth]
			decryptedComments = decryptedPwComments[4+lngth:]
			self.cachePasswordComments(row, decryptedPassword, decryptedComments)
			self.settings.mlogger.log("Decrypted password and comments "
				"for '%s', row '%d'." % (pwEntry[0], row),
				logging.DEBUG, "GUI IO")
		return decryptedComments

	def showPassword(self, item):
		# check if this password has been decrypted, use cached version
		row = self.passwordTable.row(item)
		self.logCache(row)
		try:
			decryptedPassword = self.cachedOrDecryptPassword(row)
		except CallException:
			return
		item = QTableWidgetItem(decryptedPassword)

		self.passwordTable.setItem(row, self.PASSWORD_IDX, item)

	def showComments(self, item):
		# check if these comments has been decrypted, use cached version
		row = self.passwordTable.row(item)
		try:
			decryptedComments = self.cachedOrDecryptComments(row)
		except CallException:
			return
		item = QTableWidgetItem(decryptedComments)

		self.passwordTable.setItem(row, self.COMMENTS_IDX, item)

	def showAllSecure(self):
		rowCount = self.passwordTable.rowCount()
		for row in range(rowCount):
			try:
				decryptedPassword = self.cachedOrDecryptPassword(row)
			except CallException:
				return
			item = QTableWidgetItem(decryptedPassword)
			self.passwordTable.setItem(row, self.PASSWORD_IDX, item)
			try:
				decryptedComments = self.cachedOrDecryptComments(row)
			except CallException:
				return
			item = QTableWidgetItem(decryptedComments)
			self.passwordTable.setItem(row, self.COMMENTS_IDX, item)

		self.settings.mlogger.log("Showed all entries for group '%s' the secure way." %
			(self.selectedGroup), logging.DEBUG, "GUI IO")

	def showAllFast(self):
		try:
			privateKey = self.pwMap.backupKey.unwrapPrivateKey()
		except CallException:
			return
		group = self.pwMap.groups[self.selectedGroup]

		row = 0
		for key, _, bkupPw in group.entries:
			decryptedPwComments = self.pwMap.backupKey.decryptPassword(bkupPw, privateKey)
			lngth = int(decryptedPwComments[0:4])
			password = decryptedPwComments[4:4+lngth]
			comments = decryptedPwComments[4+lngth:]
			item = QTableWidgetItem(key)
			pwItem = QTableWidgetItem(password)
			commentsItem = QTableWidgetItem(comments)
			self.passwordTable.setItem(row, self.KEY_IDX, item)
			self.passwordTable.setItem(row, self.PASSWORD_IDX, pwItem)
			self.passwordTable.setItem(row, self.COMMENTS_IDX, commentsItem)
			self.cachePasswordComments(row, password, comments)
			row = row+1

		self.passwordTable.resizeRowsToContents()
		self.passwordTable.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
		self.passwordTable.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeToContents)
		self.passwordTable.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeToContents)

		self.settings.mlogger.log("Showed all entries for group '%s' the fast way." %
			(self.selectedGroup), logging.DEBUG, "GUI IO")

	def showAll(self):
		"""
		show all passwords and comments in plaintext in GUI
		can be called without any password selectedGroup
		a group must be selected
		"""
		# with less than 3 rows dont bother the user with a pop-up
		if self.passwordTable.rowCount() < 3:
			self.showAllSecure()
			return

		msgBox = QMessageBox(parent=self)
		msgBox.setText("Do you want to use the more secure way?")
		msgBox.setIcon(QMessageBox.Question)
		msgBox.setWindowTitle("How to decrypt?")
		msgBox.setDetailedText("The more secure way requires pressing 'Confirm' "
			"on Trezor once for each entry in the group. %d presses in this "
			"case. This is recommended. "
			"Select 'Yes'.\n\n"
			"The less secure way requires only a single 'Confirm' click on the "
			"Trezor. This is not recommended. "
			"Select 'No'." % (self.passwordTable.rowCount()))
		msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
		msgBox.setDefaultButton(QMessageBox.Yes)
		res = msgBox.exec_()

		if res == QMessageBox.Yes:
			self.showAllSecure()
		else:
			self.showAllFast()

	def createPassword(self):
		"""
		Slot to create key-value password entry.
		"""
		if self.selectedGroup is None:
			return
		group = self.pwMap.groups[self.selectedGroup]
		dialog = AddPasswordDialog(self.pwMap.trezor, self.settings)
		if not dialog.exec_():
			return

		plainPw = dialog.pw1()
		plainComments = dialog.comments()
		if len(plainPw) + len(plainComments) > basics.MAX_SIZE_OF_PASSWDANDCOMMENTS:
			self.settings.mlogger.log("Password and/or comments too long. "
				"Combined they must not be larger than %d." %
				basics.MAX_SIZE_OF_PASSWDANDCOMMENTS,
				logging.CRITICAL, "User IO")
			return

		row = self.passwordTable.rowCount()
		self.passwordTable.setRowCount(row+1)
		item = QTableWidgetItem(dialog.key())
		pwItem = QTableWidgetItem("*****")
		commentsItem = QTableWidgetItem("*****")
		self.passwordTable.setItem(row, self.KEY_IDX, item)
		self.passwordTable.setItem(row, self.PASSWORD_IDX, pwItem)
		self.passwordTable.setItem(row, self.COMMENTS_IDX, commentsItem)

		plainPwComments = ("%4d" % len(plainPw)) + plainPw + plainComments
		encPw = self.pwMap.encryptPassword(plainPwComments, self.selectedGroup)
		bkupPw = self.pwMap.backupKey.encryptPassword(plainPwComments)
		group.addEntry(dialog.key(), encPw, bkupPw)

		self.cachePasswordComments(row, plainPw, plainComments)

		self.passwordTable.resizeRowsToContents()
		self.passwordTable.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
		self.passwordTable.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeToContents)
		self.passwordTable.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeToContents)
		self.setModified(True)
		self.settings.mlogger.log("Password and comments entry "
			"for '%s', row '%d' was created." % (dialog.key(), row),
			logging.DEBUG, "GUI IO")

	def editPassword(self, item):
		row = self.passwordTable.row(item)
		group = self.pwMap.groups[self.selectedGroup]
		try:
			decrypted = self.cachedOrDecryptPassword(row)
			decryptedComments = self.cachedOrDecryptComments(row)
		except CallException:
			return

		dialog = AddPasswordDialog(self.pwMap.trezor, self.settings)
		entry = group.entry(row)
		dialog.keyEdit.setText(encoding.normalize_nfc(entry[0]))
		dialog.pwEdit1.setText(encoding.normalize_nfc(decrypted))
		dialog.pwEdit2.setText(encoding.normalize_nfc(decrypted))
		doc = QTextDocument(encoding.normalize_nfc(decryptedComments), parent=self)
		dialog.commentsEdit.setDocument(doc)

		if not dialog.exec_():
			return

		item = QTableWidgetItem(dialog.key())
		pwItem = QTableWidgetItem("*****")
		commentsItem = QTableWidgetItem("*****")
		self.passwordTable.setItem(row, self.KEY_IDX, item)
		self.passwordTable.setItem(row, self.PASSWORD_IDX, pwItem)
		self.passwordTable.setItem(row, self.COMMENTS_IDX, commentsItem)

		plainPw = dialog.pw1()
		plainComments = dialog.comments()
		if len(plainPw) + len(plainComments) > basics.MAX_SIZE_OF_PASSWDANDCOMMENTS:
			self.settings.mlogger.log("Password and/or comments too long. "
				"Combined they must not be larger than %d." %
				basics.MAX_SIZE_OF_PASSWDANDCOMMENTS,
				logging.CRITICAL, "User IO")
			return

		plainPwComments = ("%4d" % len(plainPw)) + plainPw + plainComments

		encPw = self.pwMap.encryptPassword(plainPwComments, self.selectedGroup)
		bkupPw = self.pwMap.backupKey.encryptPassword(plainPwComments)
		group.updateEntry(row, dialog.key(), encPw, bkupPw)

		self.cachePasswordComments(row, plainPw, plainComments)

		self.setModified(True)
		self.settings.mlogger.log("Password and comments entry "
			"for '%s', row '%d' was edited." % (dialog.key(), row),
			logging.DEBUG, "GUI IO")

	def copyPasswordFromSelection(self):
		"""
		Copy selected password to clipboard. Password is decrypted if
		necessary.
		"""
		indexes = self.passwordTable.selectedIndexes()
		if not indexes:
			return

		# there will be more indexes as the selection is on a row
		row = indexes[0].row()
		item = self.passwordTable.item(row, self.PASSWORD_IDX)
		self.copyPasswordFromItem(item)

	def copyPasswordFromItem(self, item):
		row = self.passwordTable.row(item)
		try:
			decryptedPassword = self.cachedOrDecryptPassword(row)
		except CallException:
			return

		self.clipboard.setText(decryptedPassword)
		# Do not log contents of clipboard, contains secrets!
		self.settings.mlogger.log("Copied text to clipboard.", logging.DEBUG,
			"Clipboard")
		if basics.CLIPBOARD_TIMEOUT_IN_SEC > 0:
			self.timer.start(basics.CLIPBOARD_TIMEOUT_IN_SEC*1000)  # cancels previous timer

	def copyCommentsFromItem(self, item):
		row = self.passwordTable.row(item)
		try:
			decryptedComments = self.cachedOrDecryptComments(row)
		except CallException:
			return

		self.clipboard.setText(decryptedComments)
		# Do not log contents of clipboard, contains secrets!
		self.settings.mlogger.log("Copied text to clipboard.", logging.DEBUG,
			"Clipboard")
		if basics.CLIPBOARD_TIMEOUT_IN_SEC > 0:
			self.timer.start(basics.CLIPBOARD_TIMEOUT_IN_SEC*1000)  # cancels previous timer

	def clearClipboard(self):
		self.clipboard.clear()
		self.timer.stop()  # cancels previous timer
		self.settings.mlogger.log("Clipboard cleared.", logging.DEBUG,
			"Clipboard")

	def loadPasswords(self, item):
		"""
		Slot that should load items for group that has been clicked on.
		"""
		self.passwordTable.clear()  # clears cahce, but also clears the header, the 3 titles
		headerKey = QTableWidgetItem(u"Key")
		headerValue = QTableWidgetItem(u"Password/Value")
		headerComments = QTableWidgetItem(u"Comments")
		self.passwordTable.setColumnCount(self.NO_OF_PASSWDTABLE_COLUMNS)
		self.passwordTable.setHorizontalHeaderItem(self.KEY_IDX, headerKey)
		self.passwordTable.setHorizontalHeaderItem(self.PASSWORD_IDX, headerValue)
		self.passwordTable.setHorizontalHeaderItem(self.COMMENTS_IDX, headerComments)

		groupName = encoding.normalize_nfc(item.text())
		self.selectedGroup = groupName
		group = self.pwMap.groups[groupName]
		self.passwordTable.setRowCount(len(group.entries))

		i = 0
		for key, encValue, bkupValue in group.entries:
			item = QTableWidgetItem(key)
			pwItem = QTableWidgetItem("*****")
			commentsItem = QTableWidgetItem("*****")
			self.passwordTable.setItem(i, self.KEY_IDX, item)
			self.passwordTable.setItem(i, self.PASSWORD_IDX, pwItem)
			self.passwordTable.setItem(i, self.COMMENTS_IDX, commentsItem)
			i = i+1

		self.passwordTable.resizeRowsToContents()
		self.passwordTable.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
		self.passwordTable.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeToContents)
		self.passwordTable.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeToContents)
		self.settings.mlogger.log("Loaded password group '%s'." % (groupName),
			logging.DEBUG, "GUI IO")

	def loadPasswordsBySelection(self):
		proxyIdx = self.groupsTree.currentIndex()
		itemIdx = self.groupsFilter.mapToSource(proxyIdx)
		selectedItem = self.groupsModel.itemFromIndex(itemIdx)

		if not selectedItem:
			return

		self.loadPasswords(selectedItem)

	def filterGroups(self, substring):
		"""
		Filter groupsTree view to have items containing given substring.
		"""
		self.groupsFilter.setFilterFixedString(substring)
		self.groupsTree.sortByColumn(0, Qt.AscendingOrder)

	def printAbout(self):
		"""
		Show window with about and version information.
		"""
		msgBox = QMessageBox(QMessageBox.Information, "About",
			"About <b>TrezorPass</b>: <br><br>TrezorPass is a safe " +
			"Password Manager application for people owning a Trezor who prefer to " +
			"keep their passwords local and not on the cloud. All passwords are " +
			"stored locally in a single file.<br><br>" +
			"<b>" + basics.NAME + " Version: </b>" + basics.VERSION_STR +
			" from " + basics.VERSION_DATE_STR +
			"<br><br><b>Python Version: </b>" + sys.version.replace(" \n", "; ") +
			"<br><br><b>Qt Version: </b>" + QT_VERSION_STR +
			"<br><br><b>PyQt Version: </b>" + PYQT_VERSION_STR, parent=self)
		msgBox.setIconPixmap(QPixmap("icons/TrezorPass.svg"))
		msgBox.exec_()

	def saveBackup(self):
		"""
		First it saves any pending changes to the pwdb database file. Then it uses an operating system call
		to copy the file appending a timestamp at the end of the file name.
		"""
		if self.modified:
			self.saveDatabase()
		backupFilename = self.settings.dbFilename + u"." + time.strftime('%Y%m%d%H%M%S')
		copyfile(self.settings.dbFilename, backupFilename)
		self.settings.mlogger.log("Backup of the encrypted database file has been created "
			"and placed into file \"%s\" (%d bytes)." % (backupFilename, os.path.getsize(backupFilename)),
			logging.INFO, "User IO")

	def importCsv(self):
		"""
		Read a properly formated CSV file from disk
		and add its contents to the current entries.

		Import format in CSV should be : group, key, password, comments

		There is no error checking, so be extra careful.

		Make a backup first.

		Entries from CSV will be *added* to existing pwdb. If this is not desired
		create an empty pwdb file first.

		GroupNames are unique, so if a groupname exists then
		key-password-comments tuples are added to the already existing group.
		If a group name does not exist, a new group is created and the
		key-password-comments tuples are added to the newly created group.

		Keys are not unique. So key-password-comments are always added.
		If a key with a given name existed before and the CSV file contains a
		key with the same name, then the key-password-comments is added and
		after the import the given group has 2 keys with the same name.
		Both keys exist then, the old from before the import, and the new one from the import.

		Examples of valid CSV file format: Some example lines
		First Bank account,login,myloginname,	# no comment
		[email protected],2-factor-authentication key,abcdef12345678,seed to regenerate 2FA codes	# with comment
		[email protected],recovery phrase,"passwd with 2 commas , ,",	# with comma
		[email protected],large multi-line comments,,"first line, some comma,
		second line"
		"""
		if self.modified:
			self.saveDatabase()
		copyfile(self.settings.dbFilename, self.settings.dbFilename + ".beforeCsvImport.backup")
		self.settings.mlogger.log("WARNING: You are about to import entries from a "
			"CSV file into your current password-database file. For safety "
			"reasons please make a backup copy now.\nFurthermore, this"
			"operation can be slow, so please be patient.", logging.NOTSET,
			"CSV import")
		dialog = QFileDialog(self, "Select CSV file to import",
			"", "CSV files (*.csv)")
		dialog.setAcceptMode(QFileDialog.AcceptOpen)

		res = dialog.exec_()
		if not res:
			return

		fname = encoding.normalize_nfc(dialog.selectedFiles()[0])
		listOfAddedGroupNames = self.pwMap.importCsv(fname)
		for groupNameNew in listOfAddedGroupNames:
			item = QStandardItem(groupNameNew)
			self.groupsModel.appendRow(item)
		self.groupsTree.sortByColumn(0, Qt.AscendingOrder)
		self.setModified(True)

	def exportCsv(self):
		"""
		Uses backup key encrypted by Trezor to decrypt all passwords
		at once and export them into a single paintext CSV file.

		Export format is CSV: group, key, password, comments
		"""
		self.settings.mlogger.log("WARNING: During backup/export all passwords will be "
			"written in plaintext to disk. If possible you should consider performing this "
			"operation on an offline or air-gapped computer. Be aware of the risks.",
			logging.NOTSET,	"CSV export")
		dialog = QFileDialog(self, "Select backup export file",
			"", "CSV files (*.csv)")
		dialog.setAcceptMode(QFileDialog.AcceptSave)

		res = dialog.exec_()
		if not res:
			return

		fname = encoding.normalize_nfc(dialog.selectedFiles()[0])
		self.pwMap.exportCsv(fname)

	def saveDatabase(self):
		"""
		Save main database file.
		"""
		self.pwMap.save(self.dbFilename)
		self.setModified(False)
		self.settings.mlogger.log("TrezorPass password database file was "
			"saved to '%s'." % (self.dbFilename), logging.DEBUG, "GUI IO")

	def closeEvent(self, event):
		if self.modified:
			msgBox = QMessageBox(text="Password database is modified. Save on exit?", parent=self)
			msgBox.setStandardButtons(QMessageBox.Yes |
				QMessageBox.No | QMessageBox.Cancel)
			reply = msgBox.exec_()

			if not reply or reply == QMessageBox.Cancel:
				event.ignore()
				return
			elif reply == QMessageBox.Yes:
				self.saveDatabase()

		event.accept()
예제 #18
0
class PinStatusWidget(GalacteekTab):
    COL_TS = 0
    COL_QUEUE = 1
    COL_PATH = 2
    COL_STATUS = 3
    COL_PROGRESS = 4
    COL_CTRL = 5

    def __init__(self, gWindow, **kw):
        super(PinStatusWidget, self).__init__(gWindow, **kw)

        self.tree = QTreeView()
        self.tree.setObjectName('pinStatusWidget')
        self.boxLayout = QVBoxLayout()
        self.boxLayout.addWidget(self.tree)

        self.ctrlLayout = QHBoxLayout()
        self.btnPin = QPushButton(iPin())
        self.pathLabel = QLabel(iCidOrPath())
        self.pathEdit = QLineEdit()
        self.ctrlLayout.addWidget(self.pathLabel)
        self.ctrlLayout.addWidget(self.pathEdit)
        self.ctrlLayout.addWidget(self.btnPin)
        self.vLayout.addLayout(self.ctrlLayout)
        self.vLayout.addLayout(self.boxLayout)

        self.app.ipfsCtx.pinItemStatusChanged.connect(self.onPinStatusChanged)
        self.app.ipfsCtx.pinFinished.connect(self.onPinFinished)
        self.app.ipfsCtx.pinItemRemoved.connect(self.onItemRemoved)
        self.pathEdit.returnPressed.connect(self.onPathEntered)
        self.btnPin.clicked.connect(self.onPathEntered)

        self.model = QStandardItemModel()
        self.model.setHorizontalHeaderLabels(
            ['TS', iQueue(),
             iPath(),
             iStatus(),
             iNodesProcessed(), ''])

        self.tree.setSortingEnabled(True)
        self.tree.setModel(self.model)
        self.tree.sortByColumn(self.COL_TS, Qt.DescendingOrder)

        for col in [self.COL_QUEUE, self.COL_PATH, self.COL_PROGRESS]:
            self.tree.header().setSectionResizeMode(
                col, QHeaderView.ResizeToContents)

        self.tree.hideColumn(self.COL_TS)

    def resort(self):
        self.model.sort(self.COL_TS, Qt.DescendingOrder)

    def onPathEntered(self):
        text = self.pathEdit.text()
        self.pathEdit.clear()

        path = IPFSPath(text)
        if path.valid:
            ensure(self.app.ipfsCtx.pinner.queue(path.objPath, True, None))
        else:
            messageBox(iInvalidInput())

    def removeItem(self, path):
        modelSearch(self.model,
                    search=path,
                    columns=[self.COL_PATH],
                    delete=True)

    def onItemRemoved(self, qname, path):
        self.removeItem(path)

    def getIndexFromPath(self, path):
        idxList = self.model.match(self.model.index(0, self.COL_PATH),
                                   PinObjectPathRole, path, 1,
                                   Qt.MatchFixedString | Qt.MatchWrap)
        if len(idxList) > 0:
            return idxList.pop()

    def findPinItems(self, path):
        idx = self.getIndexFromPath(path)

        if not idx:
            return None

        itemP = self.model.itemFromIndex(idx)

        if not itemP:
            return None

        idxQueue = self.model.index(itemP.row(), self.COL_QUEUE,
                                    itemP.index().parent())
        idxProgress = self.model.index(itemP.row(), self.COL_PROGRESS,
                                       itemP.index().parent())
        idxStatus = self.model.index(itemP.row(), self.COL_STATUS,
                                     itemP.index().parent())
        idxC = self.model.index(itemP.row(), self.COL_CTRL,
                                itemP.index().parent())
        cancelButton = self.tree.indexWidget(idxC)

        return {
            'itemPath': itemP,
            'itemQname': self.model.itemFromIndex(idxQueue),
            'itemProgress': self.model.itemFromIndex(idxProgress),
            'itemStatus': self.model.itemFromIndex(idxStatus),
            'cancelButton': cancelButton
        }

    def updatePinStatus(self, path, status, progress):
        idx = self.getIndexFromPath(path)
        if not idx:
            return

        try:
            itemPath = self.model.itemFromIndex(idx)
            if itemPath and time.time() - itemPath.lastProgressUpdate < 5:
                return

            itemProgress = self.model.itemFromIndex(
                self.model.index(idx.row(), self.COL_PROGRESS, idx.parent()))

            itemStatus = self.model.itemFromIndex(
                self.model.index(idx.row(), self.COL_STATUS, idx.parent()))

            itemStatus.setText(status)
            itemProgress.setText(progress)
            itemPath.lastProgressUpdate = time.time()
        except:
            pass

    def onPinFinished(self, path):
        items = self.findPinItems(path)

        if items:
            items['itemStatus'].setText(iPinned())
            items['itemProgress'].setText('OK')

            color1 = QBrush(QColor('#4a9ea1'))
            color2 = QBrush(QColor('#66a56e'))

            for item in [
                    items['itemQname'], items['itemPath'], items['itemStatus'],
                    items['itemProgress']
            ]:
                item.setBackground(color1)

            for item in [items['itemStatus'], items['itemProgress']]:
                item.setBackground(color2)

            if items['cancelButton']:
                items['cancelButton'].setEnabled(False)

        self.resort()
        self.purgeFinishedItems()

    def purgeFinishedItems(self):
        maxFinished = 16
        ret = modelSearch(self.model,
                          search=iPinned(),
                          columns=[self.COL_STATUS])

        if len(ret) > maxFinished:
            rows = []
            for idx in ret:
                item = self.model.itemFromIndex(idx)
                if not item:
                    continue
                rows.append(item.row())

            try:
                for row in list(sorted(rows))[int(maxFinished / 2):]:
                    self.model.removeRow(row)
            except:
                pass

    async def onCancel(self, qname, path, *a):
        self.removeItem(path)
        await self.app.ipfsCtx.pinner.cancel(qname, path)

    def onPinStatusChanged(self, qname, path, statusInfo):
        nodesProcessed = statusInfo['status'].get('Progress', iUnknown())

        idx = self.getIndexFromPath(path)

        if not idx:
            # Register it
            btnCancel = QToolButton()
            btnCancel.setIcon(getIcon('cancel.png'))
            btnCancel.setText(iCancel())
            btnCancel.clicked.connect(partialEnsure(self.onCancel, qname,
                                                    path))
            btnCancel.setFixedWidth(140)

            displayPath = path
            if len(displayPath) > 64:
                displayPath = displayPath[0:64] + ' ..'

            itemTs = UneditableItem(str(statusInfo['ts_queued']))
            itemQ = UneditableItem(qname)
            itemP = UneditableItem(displayPath)
            itemP.setData(path, PinObjectPathRole)
            itemP.setToolTip(path)
            itemP.lastProgressUpdate = time.time()

            itemStatus = UneditableItem(iPinning())
            itemProgress = UneditableItem(str(nodesProcessed))

            itemC = UneditableItem('')

            self.model.invisibleRootItem().appendRow(
                [itemTs, itemQ, itemP, itemStatus, itemProgress, itemC])
            idx = self.model.indexFromItem(itemC)
            self.tree.setIndexWidget(idx, btnCancel)
            self.resort()
        else:
            self.updatePinStatus(path, iPinning(), str(nodesProcessed))
예제 #19
0
class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi(self)
        self.theModel = QStandardItemModel()
        theItemDelegate = RixJsonItemDelegate()

        self.theModel.setHorizontalHeaderItem(0, QStandardItem('Key'))
        self.theModel.setHorizontalHeaderItem(1, QStandardItem('Value'))
        self.theModel.setHorizontalHeaderItem(2, QStandardItem('Type'))

        # 设置TREE VIEW使用的模型为StandarItemModel
        self.treeView.setModel(theModel)
        # 设置Tree View使用的委托为自定义的委托
        self.treeView.setItemDelegate(theItemDelegate)

        self.theModel.itemChanged.connect(self.treeDataChanged)

        self.openButton.clicked.connect(self.openFile)
        self.saveButton.clicked.connect(self.saveFile)
        self.saveAnotherFile.clicked.connect(self.saveAnotherFile)

        self.menuEdit.triggered.connect(self.onMenuActionTrigger)
        self.menuFile.triggered.connect(self.onMenuActionTrigger)

        self.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.treeView.CustomContextMenuRequested.connect(self.showTreeViewMenu)

        self.menuEdit.aboutToShow.connect(self.aboutToShowEditMenu)

    def treeDataChanged(self, item: 'QStandardItem'):
        s = QStack()
        if item.column() == 0:
            i = self.theModel.indexFromItem(item)
        else:
            i = self.theModel.indexFromItem(item).siblingAtColumn(0)
        while i.isValid():
            o = DataManager.get_instance().getCurrentJsonObject()
            p = None
        while not s.isEmpty():
            index = s.pop()
            p = o
            o = DataManager.get_instance().getChild(index.row())

    def openFile(self):
        pass

    def saveFile(self):
        pass

    def saveAnotherFile(self):
        pass

    def onMenuActionTrigger(self, action: 'QAction'):
        pass

    def showTreeViewMenu(self, point: 'QPoint'):
        self.menuEdit.exec(QCursor.pos())

    def aboutToShowEditMenu(self):
        index = self.treeView.selectionModel.currentIndex()
        self.actionAddChild.setVisible(
            index.isValid() & (index.siblingAtColumn(2).data(Qt.DisplayRole)))

    def updateTreeModel(self):
        pass

    def addRixJsonItem(self,
                       asChild=False,
                       key="key",
                       value="value",
                       type=str):
        index = self.treeView.selectionModel().currentIndex()

        if not index.isValid():
            o = DataManager.get_instance().getCurrentJsonObject()
            o.setType(type)
            o.addChild(new_o)  # ????

            self.updateTreeModel()
            self.textBrowser.setText(
                DataManager.get_instance().getCurrentJsonObject())
            DataManager.get_instance().setDirty(True)
            self.setWindowTitle("RixJsonEditor | " +
                                DataManager.get_instance().getFileName() + "*")
            return

        item = self.theModel.itemFromIndex(index)
        s = QStack()
        if item.column() == 0:
            i = self.theModel.indexFromItem(item)
        else:
            i = self.theModel.indexFromItem(item).siblingAtColumn(0)
        while i.isValid:
            s.push(i)
            i = i.parent()
        o = DataManager.get_instance().getCurrentJsonObject()
        p = None
        while not s.isEmpty():
            index = s.pop()
            p = o
            o = DataManager.get_instance().getChild(index.row())

        if asChild:
            o.addChild(new_o)
        else:
            if p != None:
                os = p.getChildren()
                pos = index.row()
                os.insert(os.begin() + pos + 1, new_o)  # ???
        self.updateTreeModel()
        self.textBrowser.setText(
            DataManager.get_instance().getCurrentJsonObject())
        DataManager.get_instance().setDirty(True)
        self.setWindowTitle("RixJsonEditor | " +
                            DataManager.get_instance().getFileName() + "*")

    def deleteRixJsonItem(self):
        index = self.treeView.selectionModel().currentIndex()
        if not index.isValid():
            return
        item = self.theModel.itemFromIndex(index)
        s = QStack()
        if item.column() == 0:
            i = self.theModel.indexFromItem(item)
        else:
            i = self.theModel.indexFromItem(item).siblingAtColumn(0)
        while i.isValid():
            s.push(i)
            i = i.parent()
        o = DataManager.get_instance().getCurrentJsonObject()
        p = None
        while not s.isEmpty():
            index = s.pop()
            p = o
            o = DataManager.get_instance().getChild(index.row())
        if p != None:
            os = p.getChildren()
            os.erase(os.begin() + index.row())

            self.updateTreeModel()
            self.textBrowser.setText(
                DataManager.get_instance().getCurrentJsonObject())
            DataManager.get_instance().setDirty(True)
            self.setWindowTitle("RixJsonEditor | " +
                                DataManager.get_instance().getFileName() + "*")

    def expandAll(self):
        self.treeView.expandAll

    def collapseAll(self):
        self.treeView.collapseAll()
예제 #20
0
class NamespaceWidget(QObject):

    error = pyqtSignal(Exception)

    def __init__(self, view):
        QObject.__init__(self, view)
        self.view = view
        self.model = QStandardItemModel()
        self.view.setModel(self.model)
        delegate = MyDelegate(self.view, self)
        delegate.error.connect(self.error.emit)
        self.view.setItemDelegate(delegate)
        self.node = None
        self.view.header().setSectionResizeMode(1)
        
        self.addNamespaceAction = QAction("Add Namespace", self.model)
        self.addNamespaceAction.triggered.connect(self.add_namespace)
        self.removeNamespaceAction = QAction("Remove Namespace", self.model)
        self.removeNamespaceAction.triggered.connect(self.remove_namespace)

        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.view.customContextMenuRequested.connect(self.showContextMenu)
        self._contextMenu = QMenu()
        self._contextMenu.addAction(self.addNamespaceAction)
        self._contextMenu.addAction(self.removeNamespaceAction)

    @trycatchslot
    def add_namespace(self):
        uries = self.node.get_value()
        newidx = len(uries)
        it = self.model.item(0, 0)
        uri_it = QStandardItem("")
        it.appendRow([QStandardItem(), QStandardItem(str(newidx)), uri_it])
        idx = self.model.indexFromItem(uri_it)
        self.view.edit(idx)

    @trycatchslot
    def remove_namespace(self):
        idx = self.view.currentIndex()
        if not idx.isValid() or idx == self.model.item(0, 0):
            logger.warning("No valid item selected to remove")
        idx = idx.sibling(idx.row(), 2)
        item = self.model.itemFromIndex(idx)
        uri = item.text()
        uries = self.node.get_value()
        uries.remove(uri)
        logger.info("Writting namespace array: %s", uries)
        self.node.set_value(uries)
        self.reload()

    def set_node(self, node):
        self.model.clear()
        self.node = node
        self.show_array()

    def reload(self):
        self.set_node(self.node)

    def show_array(self):
        self.model.setHorizontalHeaderLabels(['Browse Name', 'Index', 'Value'])

        name_item = QStandardItem(self.node.get_browse_name().Name)
        self.model.appendRow([name_item, QStandardItem(""), QStandardItem()])
        it = self.model.item(0, 0)
        val = self.node.get_value()
        for idx, url in enumerate(val):
            it.appendRow([QStandardItem(), QStandardItem(str(idx)), QStandardItem(url)])
        self.view.expandAll()

    def clear(self):
        self.model.clear()

    def showContextMenu(self, position):
        self.removeNamespaceAction.setEnabled(False)
        idx = self.view.currentIndex()
        if not idx.isValid():
            return
        if idx.parent().isValid() and idx.row() >= 1:
            self.removeNamespaceAction.setEnabled(True)
        self._contextMenu.exec_(self.view.viewport().mapToGlobal(position))
예제 #21
0
class ClientGui(QWidget):
    def __init__(self):
        self.service_msg_deq = []  # очередь сервисных сообщений, очищать!
        self.icon_user = QIcon("user.svg")
        self.icon_new_msg = QIcon("message.svg")
        super().__init__()

        self.get_login_dialog()

    def initUI(self):

        # Кнопки: добавить/удалить контакты в контакт лист
        self.button_add_contact = QPushButton('add', self)
        self.button_add_contact.clicked.connect(self.add_contact)
        self.button_del_contact = QPushButton('del', self)
        self.button_del_contact.clicked.connect(self.del_contact)
        self.button_settings = QPushButton('men', self)
        self.button_settings.setEnabled(False)  # не работает
        self.button_connect = QPushButton('conn', self)
        self.button_connect.setEnabled(False)  # не работает

        self.box_button = QHBoxLayout()
        self.box_button.addWidget(self.button_add_contact)
        self.box_button.addWidget(self.button_del_contact)
        self.box_button.addWidget(self.button_settings)
        self.box_button.addWidget(self.button_connect)

        # создаю модель для листа контактов, подключаю отображение
        cl = bd_client_app.BDContacts().get_contacts()
        self.model_cl = QStandardItemModel()
        for user in cl:
            row = QStandardItem(self.icon_user, user)
            self.model_cl.appendRow(row)

        self.contact_list = QListView()
        self.contact_list.setModel(self.model_cl)
        self.contact_list.setSelectionMode(QListView.SingleSelection)
        self.contact_list.setEditTriggers(QListView.NoEditTriggers)
        self.contact_list.clicked.connect(self.select_conlist)

        # строка и кнопка отправки сообщений
        qButton = QPushButton('>>', self)
        qButton.clicked.connect(self.send_click)
        self.sendBox = QLineEdit(self)
        self.sendBox.returnPressed.connect(self.send_click)

        self.messageBox = QStackedWidget()
        # два словаря, в первом: логин ключ виджет значение, второй наоборот
        self.messageBox_dict_ctw = {}
        self.messageBox_dict_wtc = {}
        for user in cl:
            self.messageBox_dict_ctw[user] = QListWidget()
            self.messageBox_dict_wtc[self.messageBox_dict_ctw[user]] = user
            self.messageBox.addWidget(self.messageBox_dict_ctw[user])

        grid = QGridLayout()
        # строка, столбец, растянуть на строк, растянуть на столбцов
        grid.addWidget(self.contact_list, 0, 0, 2, 3)
        grid.addLayout(self.box_button, 2, 0)
        grid.addWidget(self.messageBox, 0, 3, 2, 3)
        grid.addWidget(self.sendBox, 2, 3, 1, 2)
        grid.addWidget(qButton, 2, 5)

        grid.setSpacing(5)
        grid.setColumnMinimumWidth(3, 200)
        grid.setColumnStretch(3, 10)
        self.setLayout(grid)

        self.resize(800, 300)
        self.center()
        self.setWindowTitle('Avocado')
        self.setWindowIcon(QIcon('icon.svg'))
        self.show()

    def initThreads(self):
        self.print_thread = ClientThreads(self.client, self)
        self.print_thread.print_signal.connect(self.add_message)
        self.print_thread.start()

    def get_login_dialog(self):

        text, ok = QInputDialog.getText(self, 'Login', 'Connect with login:')
        self.login_name = str(text)

        if ok:
            self.service_msg_deq.clear()  # жду свой ответ

            self.init_client()
            self.initThreads()

            # while not self.service_msg_deq:
            #     print(self.service_msg_deq)
            #     pass  # жду ответ
            # if self.service_msg_deq[0] is True:
            time.sleep(1)
            self.initUI()
            # else:
            #     self.exit()
        else:
            self.exit()

    def init_client(self):
        self.client = client.Client(self.login_name, "localhost", 7777)
        self.client.start_th_gui_client()

    def center(self):
        # центрирую окно
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    @pyqtSlot()
    def send_click(self):
        text_to_send = self.sendBox.text()
        if text_to_send.rstrip():
            self.messageBox.currentWidget().addItem("<< " + text_to_send)
            self.client.inp_queue.put(text_to_send)
        self.sendBox.clear()

    @pyqtSlot(QModelIndex)
    def select_conlist(self, curr):
        self.messageBox.setCurrentIndex(curr.row())
        self.model_cl.itemFromIndex(curr).setIcon(self.icon_user)
        self.client.to_user = self.messageBox_dict_wtc[
            self.messageBox.currentWidget()]

    @pyqtSlot(tuple)
    def add_message(self, message):
        msg = message[0]
        from_u = message[1]

        try:
            client_widget = self.messageBox_dict_ctw[from_u]
        except KeyError:
            mesg_con_log.error("Message from user from not in contact list:")
            mesg_con_log.error("%s, %s" % (from_u, msg))
        else:
            client_widget.addItem(">> " + msg)
            message_from = self.model_cl.findItems(from_u)[0]
            if self.contact_list.currentIndex() != self.model_cl.indexFromItem(
                    message_from):
                message_from.setIcon(self.icon_new_msg)

    @pyqtSlot()
    def del_contact(self):
        user = self.client.to_user
        self.client.inp_queue.put("del_contact " + user)
        self.messageBox.removeWidget(self.messageBox_dict_ctw[user])
        self.model_cl.takeRow(
            self.model_cl.indexFromItem(
                self.model_cl.findItems(user)[0]).row())

    @pyqtSlot()
    def add_contact(self):
        user = self.sendBox.text()
        self.service_msg_deq.clear()  # жду свой ответ
        self.client.inp_queue.put("add_contact " + user)

        while not self.service_msg_deq:
            pass  # жду ответ

        if self.service_msg_deq[0] is True:
            row = QStandardItem(self.icon_user, user)
            self.model_cl.appendRow(row)

            self.messageBox_dict_ctw[user] = QListWidget()
            self.messageBox_dict_wtc[self.messageBox_dict_ctw[user]] = user
            self.messageBox.addWidget(self.messageBox_dict_ctw[user])
        else:
            pass

        self.sendBox.clear()
예제 #22
0
class ConfigManagerDialog(Ui_MainWindow, QMainWindow):
    def __init__(self, roamapp, parent=None):
        super(ConfigManagerDialog, self).__init__(parent)
        self.setupUi(self)
        self.setWindowTitle("Roam Config Manager")
        self.bar = roam.messagebaritems.MessageBar(self)

        self.roamapp = roamapp
        self.reloadingProject = False
        self.loadedProject = None
        self.projectpath = None

        # Nope!
        self.projectwidget.roamapp = roamapp
        self.projectwidget.bar = self.bar

        self.treemodel = QStandardItemModel()
        self.projectList.setModel(self.treemodel)
        self.projectList.setHeaderHidden(True)

        self.projectList.selectionModel().currentChanged.connect(
            self.nodeselected)

        self.projectwidget.adjustSize()
        self.setWindowFlags(Qt.Window)

        self.projectwidget.projectupdated.connect(self.projectupdated)
        self.projectwidget.projectloaded.connect(self.projectLoaded)

        self.projectwidget.setaboutinfo()
        self.projectwidget.projects_page.projectlocationchanged.connect(
            self.loadprojects)
        self.setuprootitems()

        ConfigEvents.deleteForm.connect(self.delete_form)

    def closeEvent(self, closeevent):
        self.save_page_config()
        closeevent.accept()

    def raiseerror(self, *exinfo):
        self.bar.pushError(*exinfo)

    def setuprootitems(self):
        rootitem = self.treemodel.invisibleRootItem()
        self.roamnode = RoamNode()
        rootitem.appendRow(self.roamnode)

        self.datanode = DataNode(folder=None)
        rootitem.appendRow(self.datanode)
        self.projectsnode = ProjectsNode(folder=None)
        rootitem.appendRow(self.projectsnode)
        self.publishnode = PublishNode(folder=None)
        rootitem.appendRow(self.publishnode)

        self.pluginsnode = PluginsNode()
        pluginpath = os.path.join(self.roamapp.apppath, "plugins")
        rootitem.appendRow(self.pluginsnode)
        self.pluginsnode.add_plugin_paths([pluginpath])

    def delete_form(self):
        index = self.projectList.currentIndex()
        node = index.data(Qt.UserRole)
        if not node.type() == Treenode.FormNode:
            return

        title, removemessage = node.removemessage
        delete = node.canremove
        if node.canremove and removemessage:
            button = QMessageBox.warning(self, title, removemessage,
                                         QMessageBox.Yes | QMessageBox.No)
            delete = button == QMessageBox.Yes

        if delete:
            parentindex = index.parent()
            newindex = self.treemodel.index(index.row(), 0, parentindex)
            if parentindex.isValid():
                parent = parentindex.data(Qt.UserRole)
                parent.delete(index.row())

            print(parentindex)
            self.projectList.setCurrentIndex(parentindex)

    def delete_project(self):
        index = self.projectList.currentIndex()
        node = index.data(Qt.UserRole)
        if node.type() == Treenode.ProjectNode:
            self.projectwidget._closeqgisproject()

        title, removemessage = node.removemessage
        delete = node.canremove
        if node.canremove and removemessage:
            button = QMessageBox.warning(self, title, removemessage,
                                         QMessageBox.Yes | QMessageBox.No)
            delete = button == QMessageBox.Yes

        if delete:
            parentindex = index.parent()
            newindex = self.treemodel.index(index.row(), 0, parentindex)
            if parentindex.isValid():
                parent = parentindex.data(Qt.UserRole)
                parent.delete(index.row())

            self.projectList.setCurrentIndex(newindex)

    def addprojectfolders(self, folders):
        self.projectwidget.projects_page.setprojectfolders(folders)

    @property
    def active_project_folder(self):
        return self.projectpath

    @active_project_folder.setter
    def active_project_folder(self, value):
        self.projectpath = value
        pass

    def loadprojects(self, projectpath):
        self.active_project_folder = projectpath

        projects = roam.project.getProjects([projectpath])
        self.projectsnode.loadprojects(projects, projectsbase=projectpath)

        index = self.treemodel.indexFromItem(self.projectsnode)
        self.projectList.setCurrentIndex(index)
        self.projectList.expand(index)

    def projectLoaded(self, project):
        roam.utils.info("Project loaded: {}".format(project.name))
        node = self.projectsnode.find_by_name(project.name)
        node.create_children()
        self.projectwidget.setpage(node.page, node, refreshingProject=True)
        self.reloadingProject = False
        self.loadedProject = project
        self.projectList.setExpanded(node.index(), True)

    def nodeselected(self, index, last, reloadProject=False):
        node = index.data(Qt.UserRole)
        if node is None:
            return

        project = node.project

        if project:
            if not project.requires_upgrade:
                validateresults = list(project.validate())
                if validateresults:
                    text = "Here are some reasons we found: \n\n"
                    for message in validateresults:
                        text += "- {} \n".format(message)

                    self.projectwidget.reasons_label.setText(text)
                    return

        if node.nodetype == Treenode.ProjectNode and reloadProject:
            self.reloadingProject = True
            self.projectwidget.setproject(project)
            return

        if project and self.loadedProject != project:
            # Only load the project if it's different the current one.
            if self.loadedProject:
                lastnode = self.projectsnode.find_by_name(
                    self.loadedProject.name)
                self.projectList.setExpanded(lastnode.index(), False)

            # We are closing the open project at this point. Watch for null ref after this.
            self.projectwidget.setproject(project)
            self.projectwidget.setpage(node.page, node)
            return
        else:
            self.projectwidget.setpage(node.page, node)

        if node.nodetype == Treenode.AddNew:
            try:
                item = node.additem()
            except ValueError:
                return
            newindex = self.treemodel.indexFromItem(item)
            self.projectList.setCurrentIndex(newindex)
            return

    def save_page_config(self):
        """
        Save the current page config
        """
        self.projectwidget.savePage(closing=True)

    def projectupdated(self, project):
        node = self.projectsnode.find_by_name(project.name)
        self.projectList.selectionModel().select(
            node.index(), QItemSelectionModel.ClearAndSelect)
        self.nodeselected(node.index(), None, reloadProject=True)
예제 #23
0
class BlockView(QWidget):

    selectionChanged = pyqtSignal(int, int, str)
    addAnalyzerTrigger = pyqtSignal(str)

    def __init__(self, parent, cols: dict, blocks: [Block],
                 undoStack: QUndoStack):
        super(BlockView, self).__init__(parent=parent)
        self.undoStack = undoStack
        self.buffer = None
        self.cols = cols
        self.blocks = blocks
        self.tree = TreeView(self)
        self.model = QStandardItemModel()
        self.tree.setModel(self.model)
        self.entry = QLineEdit(self)
        self.entry.setPlaceholderText('Search for ...')
        self.entry.setClearButtonEnabled(True)

        vbox = QVBoxLayout()
        vbox.addWidget(self.entry)
        vbox.addWidget(self.tree)
        self.setLayout(vbox)
        self.tree.selectionModel().selectionChanged.connect(
            self.selectionChangedEvent)
        self.tree.doubleClicked.connect(self.moduleModify)
        self.entry.textChanged.connect(self.searchTree)
        self.hiddenRows = {}

        self.create_ui()

    def clearHidden(self):
        if self.entry.text().strip() == '':
            for key, rows in self.hiddenRows.items():
                for row in rows:
                    self.tree.setRowHidden(row, key, False)
            self.hiddenRows = {}

    def searchTree(self):
        text = self.entry.text().strip()
        if text == '':
            self.clearHidden()
            return
        pattern = re.compile(text, re.IGNORECASE)
        field_pattern = re.compile(text, re.IGNORECASE)
        for row in range(self.model.rowCount()):
            item = self.model.item(row, 0)
            index = self.model.indexFromItem(item)
            block = self.blocks[index.row()]
            if item.hasChildren():
                for itemRow in range(item.rowCount()):
                    fields = block.get_register_fields(itemRow)
                    for itemCol in range(item.columnCount()):
                        child = item.child(itemRow, itemCol)
                        if pattern.search(child.text()):
                            self.tree.setRowHidden(itemRow, index, False)
                            break
                        if itemCol == item.columnCount() - 1:
                            if self.search_fields(fields, field_pattern):
                                self.tree.setRowHidden(itemRow, index, False)
                                break
                    else:
                        self.hiddenRows.setdefault(index, [])
                        self.hiddenRows[index].append(itemRow)
                        self.tree.setRowHidden(itemRow, index, True)

    def search_fields(self, fields, pattern):
        for field in fields:
            for value in field.values():
                # if type(value) is int:
                #     value = str(value)
                if pattern.findall(value):
                    return True
        return False

    def dataBeforeChangedEvent(self, index: QModelIndex, new: str, old: str):
        if not index.isValid():
            return
        row = index.row()
        col = index.column()
        cmd = DataChanged(widget=self.model,
                          newtext=new,
                          oldtext=old,
                          index=index,
                          description=f'Table Data changed at ({row}, {col})')
        self.undoStack.push(cmd)

    def create_ui(self):
        self.create_actions()
        self.create_cols()
        self.create_rows()
        self.tree.setCurrentIndex(self.model.index(0, 0))

    def create_rows(self, blocks: [dict] = None):
        if blocks is not None:
            self.blocks = blocks
            self.model.removeRows(0, self.model.rowCount())
        for block in self.blocks:
            block.setDisplayItem()
            root = block.getDisplayItem()
            for register in block:
                root[0].appendRow([
                    QStandardItem(register.get(col))
                    for col in self.cols.keys()
                ])
            self.model.appendRow(root)

    def create_cols(self):
        self.model.setHorizontalHeaderLabels(self.cols.keys())

        for col, config in enumerate(self.cols.values()):
            widget = config.get('widget', None)
            width = config.get('width', None)
            if width:
                self.tree.setColumnWidth(col, width)
            if widget == "list":
                items = config.get('items', [])
                delegate = ListViewDelegate(self.tree, items)
            elif widget == 'textEdit':
                delegate = TextEditDelegate(self.tree)
            else:
                validator = config.get('type', None)
                items = config.get('items', None)
                delegate = LineEditDelegate(self.tree,
                                            items,
                                            validator,
                                            minValue=config.get('minValue', 0),
                                            maxValue=config.get(
                                                'maxValue', 31))

            self.tree.setItemDelegateForColumn(col, delegate)
            delegate.dataBeforeChanged.connect(self.dataBeforeChangedEvent)

    def selectionChangedEvent(self):
        # self.blockSignals(True)
        index = self.tree.currentIndex()
        if not index.isValid():
            # print(index.row(), index.column(), 'is not exist')
            return
        try:
            if not index.model().hasChildren(index):
                block_index = index.parent()
                if not block_index.isValid():
                    return
                block_index = block_index.row()
                block = self.blocks[block_index]
                index = index.row()
                register = block.get_register(index)
                if register is None:
                    return
                self.selectionChanged.emit(
                    block_index, index,
                    caption_formatter.format(Module=block, Register=register))
        except Exception:
            traceback.print_exc(file=sys.stdout)

    def getRowValues(self, items=None):
        if items is None:
            items = self.tree.selectedIndexes()
        values = {}
        for item, col in zip(items, self.cols.keys()):
            values[col] = item.data()
        return values

    def iterItemPerSelectedRow(self) -> (int, QModelIndex):
        indexes = self.tree.selectedIndexes()
        col_length = len(self.cols.keys())

        for i in range(0, len(indexes), col_length):
            index = indexes[i]
            yield index.row(), index
            # yield indexes[i].row()

    def contextMenuEvent(self, event: QContextMenuEvent):
        menu = QMenu(self.tree)
        actions = {}

        for each_menu in register_contextmenu:
            label = each_menu.get('label')
            icon = each_menu.get('icon')
            buffer = each_menu.get('buffer', False)
            action = menu.addAction(label)

            if buffer and self.buffer is None:
                action.setEnabled(False)
            sc = each_menu.get('shortcut', None)
            if sc:
                action.setShortcut(sc)
            if icon:
                action.setIcon(qta.icon(icon))
            actions[action] = getattr(self, each_menu.get('action'))

        action = menu.exec_(self.mapToGlobal(event.pos()))
        func = actions.get(action)
        if callable(func):
            func()

    def create_actions(self):
        for config in register_contextmenu:
            sc = config.get('shortcut', None)
            if sc is None:
                continue
            label = config.get('label')
            qaction = QAction(label, self.tree)
            # func = actions.pop()
            func = getattr(self, config.get('action'))
            qaction.setShortcut(sc)
            qaction.setShortcutContext(Qt.WidgetWithChildrenShortcut)
            qaction.triggered.connect(func)
            self.addAction(qaction)

    def append_new(self):
        row = self.tree.currentRow
        new = Register(deepcopy(new_reg))
        index = self.tree.currentIndex()
        if not index.isValid():
            return
        if index.data(Qt.UserRole) == 'block':
            return
        if index.column() != 0:
            index = index.sibling(index.row(), 0)
        try:
            offset = int(index.data(), 16) + 4
            new['Offset'] = "0x{0:04X}".format(offset)
            self.insert_row(
                row + 1,
                [new],
            )
        except ValueError:
            MessageBox.showError(
                self, "The Offset value of this row in not valid\n", GUI_NAME)

    def prepend_new(self):
        index = self.tree.currentIndex()
        if not index.isValid():
            return
        if index.data(Qt.UserRole) == 'block':
            return
        if index.column() != 0:
            index = index.sibling(index.row(), 0)
        row = self.tree.currentRow
        new = Register(deepcopy(new_reg))
        try:
            offset = int(index.data(), 16) - 4
            if offset < 0:
                offset = 0
            new['Offset'] = "0x{0:04X}".format(offset)
            self.insert_row(
                row,
                [new],
            )
        except ValueError:
            MessageBox.showError(
                self, "The Offset value of this row in not valid\n", GUI_NAME)

    def append_copy(self):
        if self.buffer is None:
            return
        index = self.tree.currentIndex()
        if index.data(Qt.UserRole) == 'block':
            items = self.buffer.get('blocks')
        else:
            items = self.buffer.get('registers')
        if not items:
            return
        news = []
        for item in items:
            news.append(item.copy())
        row = self.tree.currentRow

        self.insert_row(
            row + 1,
            news,
        )

    def prepend_copy(self):
        if self.buffer is None:
            return
        row = self.tree.currentRow
        news = []
        for register in self.buffer:
            news.append(register.copy())
        self.insert_row(
            row,
            news,
        )

    def insert_row(self, row, items):
        root = self.model.itemFromIndex(self.tree.currentIndex()).parent()
        if root is None:
            root = self.model
            block = self.blocks
            is_root = True
        else:
            block = self.blocks[root.row()].registers
            is_root = False
        for item in reversed(items):
            cmd = TreeInsertCommand(widget=root,
                                    block=block,
                                    row=row,
                                    items=item,
                                    cols=self.cols,
                                    description='tree insertion',
                                    is_root=is_root)
            self.undoStack.push(cmd)

    def copy(self):
        try:
            values = {'blocks': [], 'registers': []}
            for row, index in self.iterItemPerSelectedRow():
                if index.data(Qt.UserRole) == 'block':
                    values['blocks'].append(self.blocks[row].copy())
                else:
                    register = self.blocks[index.parent().row()].get_register(
                        row)
                    values['registers'].append(register.copy())
            if not values:
                self.buffer = None
            else:
                self.buffer = values
        except Exception as e:
            print(traceback.format_exc())

    def moduleModify(self, index: QModelIndex):
        if not index.isValid():
            return
        item = self.model.itemFromIndex(index)
        if item.data(Qt.UserRole) == 'block':
            block = self.blocks[index.row()]
            dialog = InputDialog(parent=self,
                                 title=GUI_NAME,
                                 inputs=block_columns,
                                 values=block)
            info, save = dialog.get()
            if save:
                block.update(info)
                block.viewUpdate()

    def cut(self):
        self.copy()
        self.remove()

    def remove(self):
        if not self.blocks:
            return
        items = {}
        for row, index in self.iterItemPerSelectedRow():
            item = self.model.itemFromIndex(index)
            if not index.isValid():
                continue
            if item.data(Qt.UserRole) == 'block':
                if row >= len(self.blocks):
                    continue
                items[row] = [self.model, self.blocks]
            else:
                index = index.parent()
                items[row] = [
                    self.model.itemFromIndex(index),
                    self.blocks[index.row()].registers
                ]
        if not items:
            return
        for row in sorted(items.keys(), reverse=True):
            cmd = TreeRemoveCommand(widget=items[row][0],
                                    row=row,
                                    description='remove',
                                    block=items[row][1])
            self.undoStack.push(cmd)

    def setFocus(self):
        self.tree.setFocus()

    def saveChanges(self):
        for row in range(self.model.rowCount()):
            item = self.model.item(row, 0)
            block = self.blocks[row]
            if item.hasChildren():
                for itemRow in range(item.rowCount()):
                    register = block.get_register(itemRow)
                    for col, header in enumerate(self.cols.keys()):
                        child = item.child(itemRow, col)
                        register[header] = child.text()

    def clearSelection(self):
        self.tree.clearSelection()

    def addAnalyzer(self):
        index = self.tree.currentIndex()
        if not index.isValid():
            return
        if index.data(Qt.UserRole) == 'block':
            return

        block_index = index.parent().row()
        address, _ = self.blocks[block_index].get_address_space(index.row())
        self.addAnalyzerTrigger.emit(address)
예제 #24
0
class NamespaceWidget(QObject):

    error = pyqtSignal(Exception)

    def __init__(self, view):
        QObject.__init__(self, view)
        self.view = view
        self.model = QStandardItemModel()
        self.view.setModel(self.model)
        delegate = MyDelegate(self.view, self)
        delegate.error.connect(self.error.emit)
        self.view.setItemDelegate(delegate)
        self.node = None
        self.view.header().setSectionResizeMode(1)

        self.addNamespaceAction = QAction("Add Namespace", self.model)
        self.addNamespaceAction.triggered.connect(self.add_namespace)
        self.removeNamespaceAction = QAction("Remove Namespace", self.model)
        self.removeNamespaceAction.triggered.connect(self.remove_namespace)

        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.view.customContextMenuRequested.connect(self.showContextMenu)
        self._contextMenu = QMenu()
        self._contextMenu.addAction(self.addNamespaceAction)
        self._contextMenu.addAction(self.removeNamespaceAction)

    @trycatchslot
    def add_namespace(self):
        uries = self.node.read_value()
        newidx = len(uries)
        it = self.model.item(0, 0)
        uri_it = QStandardItem("")
        it.appendRow([QStandardItem(), QStandardItem(str(newidx)), uri_it])
        idx = self.model.indexFromItem(uri_it)
        self.view.edit(idx)

    @trycatchslot
    def remove_namespace(self):
        idx = self.view.currentIndex()
        if not idx.isValid() or idx == self.model.item(0, 0):
            logger.warning("No valid item selected to remove")
        idx = idx.sibling(idx.row(), 2)
        item = self.model.itemFromIndex(idx)
        uri = item.text()
        uries = self.node.read_value()
        uries.remove(uri)
        logger.info("Writting namespace array: %s", uries)
        self.node.write_value(uries)
        self.reload()

    def set_node(self, node):
        self.model.clear()
        self.node = node
        self.show_array()

    def reload(self):
        self.set_node(self.node)

    def show_array(self):
        self.model.setHorizontalHeaderLabels(['Browse Name', 'Index', 'Value'])

        name_item = QStandardItem(self.node.read_browse_name().Name)
        self.model.appendRow([name_item, QStandardItem(""), QStandardItem()])
        it = self.model.item(0, 0)
        val = self.node.read_value()
        for idx, url in enumerate(val):
            it.appendRow([QStandardItem(), QStandardItem(str(idx)), QStandardItem(url)])
        self.view.expandAll()

    def clear(self):
        self.model.clear()

    def showContextMenu(self, position):
        self.removeNamespaceAction.setEnabled(False)
        idx = self.view.currentIndex()
        if not idx.isValid():
            return
        if idx.parent().isValid() and idx.row() >= 1:
            self.removeNamespaceAction.setEnabled(True)
        self._contextMenu.exec_(self.view.viewport().mapToGlobal(position))
예제 #25
0
class FileListDisplay(QtWidgets.QListView):
    """

    """
    NO_COMPATIBLE_IMAGE_STR = 'No Compatible Images'

    # settings for text display of selected and unselected lines
    UNSELECTED_FONT = QFont("Helvetica", 12)
    UNSELECTED_FONT.setBold(False)
    font = QFont()
    SELECTED_FONT = QFont("Helvetica", 14)
    SELECTED_FONT.setBold(True)

    ####################################################################################################################
    def __init__(self,
                 ui: ipview_ui.IPViewWindow):
        """
        """
        self.ui = ui

        super(FileListDisplay, self).__init__()
        self.model = QStandardItemModel()
        self.ui.text_list_display.setModel(self.model)

        # list of QStandardItem, each element represents one text line
        self.q_item_list = []
        self.displayed_item_idx = -1

        self.stream_display = StreamDisplay.StreamDisplay(ui=self.ui)

    ####################################################################################################################
    def load_directory_button_pushed(self) -> None:
        """
        Slot method for loading directory upon load push button.
        """
        self.clear_list_display()
        self.stream_display.clear_text()

        directory = self.ui.directory_display.toPlainText()
        try:
            files_for_display = self.ui.app_data.load_directory(directory=directory)
        except FileNotFoundError as e:
            print(e)
            return

        if len(files_for_display) == 0:
            item = QStandardItem(FileListDisplay.NO_COMPATIBLE_IMAGE_STR)
            self.model.appendRow(item)

        else:
            for f in files_for_display:  # initialize to UNSELECTED
                item = QStandardItem(f)
                item.setFont(FileListDisplay.UNSELECTED_FONT)
                self.q_item_list.append(item)
                self.model.appendRow(item)  # display to scene

        return

    ####################################################################################################################
    def display_next_item(self) -> None:
        """
        Method to display next item. In effect, will change the settings of the old item back to UNCHECKED and will
        change the settings of the next item to CHECKED.
        """
        if self.displayed_item_idx < len(self.q_item_list) - 1:
            self.q_item_list[self.displayed_item_idx].setFont(FileListDisplay.UNSELECTED_FONT)
            self.displayed_item_idx += 1
            self.q_item_list[self.displayed_item_idx].setFont(FileListDisplay.SELECTED_FONT)

        self.__keep_current_item_in_view()

        return

    ####################################################################################################################
    def display_previous_item(self) -> None:
        """
        Method to display next item. In effect, will change the settings of the old item back to UNCHECKED and will
        change the settings of the previous item to CHECKED.
        """
        if self.displayed_item_idx > 0:
            self.q_item_list[self.displayed_item_idx].setFont(FileListDisplay.UNSELECTED_FONT)
            self.displayed_item_idx -= 1
            self.q_item_list[self.displayed_item_idx].setFont(FileListDisplay.SELECTED_FONT)

        self.__keep_current_item_in_view()

        return

    ####################################################################################################################
    def clear_list_display(self) -> None:
        """
        Method to clear text display.
        """
        self.model.clear()
        self.q_item_list = []
        self.displayed_item_idx = -1

        return

    ####################################################################################################################
    def __keep_current_item_in_view(self) -> None:
        """
        Method to auto-scroll the display box to keep the current item in view.
        """
        try:
            index = self.model.indexFromItem(self.q_item_list[self.displayed_item_idx])
        except IndexError:
            return

        self.ui.text_list_display.scrollTo(index, QtWidgets.QAbstractItemView.EnsureVisible)

        return
class DataChangeUI(object):
    def __init__(self, window, sub_window):
        self.window = window
        self.sub_window = sub_window
        self._subhandler = DataChangeHandler()
        self._subscribed_nodes = []
        #self._datachange_sub = None
        self._datachange_sub = {
        }  #key: subscriptionId, value: subscription class
        self._subs_dc = {}  #key: nodeId, value: (handler,subscriptionId)
        '''self.model = QStandardItemModel()
        self.monitored_item_model = QStandardItemModel()
        self.window.ui.subView.setModel(self.model)
        self.window.ui.subView.horizontalHeader().setSectionResizeMode(1)
        self.window.ui.monItemView.setModel(self.monitored_item_model)
        self.window.ui.monItemView.horizontalHeader().setSectionResizeMode(1)'''

        self.model = QStandardItemModel()
        self.monitored_item_model = QStandardItemModel()
        self.sub_window.ui.subView.setModel(self.model)
        self.sub_window.ui.subView.horizontalHeader().setSectionResizeMode(1)
        self.sub_window.ui.monItemView.setModel(self.monitored_item_model)
        self.sub_window.ui.monItemView.horizontalHeader().setSectionResizeMode(
            1)

        self.pub_interval = 500
        self.lifetime_count = 10000
        self.max_keep_alive_count = 3000
        self.max_notifications_per_publish = 10000
        self.priority = 0

        self.subscription_id = None
        self.sampling_interval = 500
        self.monitoring_mode = ua.MonitoringMode.Reporting
        self.queue_size = 1
        self.deadband_value = 0
        self.discard_oldest = True
        self.deadband_type = None

        self.window.ui.actionCreate_subscription.triggered.connect(
            lambda: self.show_sub_dialog())
        self.window.ui.actionCreate_monitored_item.triggered.connect(
            lambda: self.show_monitored_item_dialog())
        self.window.ui.actionDelete_monitered_item.triggered.connect(
            lambda: self._unsubscribe())
        self.window.ui.actionDelete_subscription.triggered.connect(
            lambda: self.show_delete_sub_dialog())

        # handle subscriptions
        self._subhandler.data_change_fired.connect(
            self._update_subscription_model, type=Qt.QueuedConnection)

        self.numeric_types = [
            ua.uatypes.VariantType.Int16, ua.uatypes.VariantType.UInt16,
            ua.uatypes.VariantType.Int32, ua.uatypes.VariantType.UInt32,
            ua.uatypes.VariantType.Int64, ua.uatypes.VariantType.UInt64,
            ua.uatypes.VariantType.Float, ua.uatypes.VariantType.Double
        ]

    def show_sub_dialog(self):
        dia = SubscriptionDialog(self)
        dia.exec_()

    def show_monitored_item_dialog(self):
        dia = MonitoredItemDialog(self)
        dia.exec_()

    def show_delete_sub_dialog(self):
        dia = DeleteSubDialog(self)
        dia.exec_()

    def clear(self):
        self._subscribed_nodes = []
        self.model.clear()
        self.monitored_item_model.clear()

    def create_subscription(self):
        try:
            #if not self._datachange_sub:
            params = ua.CreateSubscriptionParameters()
            params.RequestedPublishingInterval = self.pub_interval
            params.RequestedLifetimeCount = self.lifetime_count
            params.RequestedMaxKeepAliveCount = self.max_keep_alive_count
            params.MaxNotificationsPerPublish = self.max_notifications_per_publish
            params.PublishingEnabled = True
            params.Priority = self.priority
            sub = Subscription(self.window.client.uaclient, params,
                               self._subhandler)

            self._datachange_sub[sub.subscription_id] = sub
            self.model.setHorizontalHeaderLabels(
                ["Subscription Id", "Publishing Interval"])
            row = [
                QStandardItem(str(sub.subscription_id)),
                QStandardItem(str(sub.parameters.RequestedPublishingInterval))
            ]
            row[0].setData(sub)
            self.model.appendRow(row)
        except Exception as ex:
            self.window.log_window.ui.logTextEdit.append(str(ex))
            raise
            #modelidx = self.model.indexFromItem(row[0])
            #self.model.takeRow(idx.row())

    def _subscribe(self, node=None):
        if not isinstance(node, Node):
            node = self.window.get_current_node()
            if node is None:
                return
        if node.get_node_class() != ua.NodeClass.Variable:
            self.window.log_window.ui.logTextEdit.append(
                "Select a variable node")
            return
        if node in self._subscribed_nodes:
            self.window.log_window.ui.logTextEdit.append(
                "already subscribed to node: %s " % node)
            return
        self.monitored_item_model.setHorizontalHeaderLabels(
            ["DisplayName", "Value", "Timestamp", "Subscription Id"])
        text = str(node.get_display_name().Text)
        row = [
            QStandardItem(text),
            QStandardItem("No Data yet"),
            QStandardItem(""),
            QStandardItem(str(self.subscription_id))
        ]
        row[0].setData(node)
        self.monitored_item_model.appendRow(row)
        self._subscribed_nodes.append(node)
        try:
            mir = self._datachange_sub[
                self.subscription_id]._make_monitored_item_request(
                    node, ua.AttributeIds.Value, None,
                    self.queue_size)  #mfilter, queue size
            mir.RequestedParameters.DiscardOldest = self.discard_oldest
            mir.RequestedParameters.SamplingInterval = self.sampling_interval
            mir.MonitoringMode = self.monitoring_mode
            print(mir.MonitoringMode)
            mod_filter = ua.DataChangeFilter()
            mod_filter.Trigger = ua.DataChangeTrigger(
                1)  # send notification when status or value change
            if self.deadband_type != None:
                if self.deadband_type == 1:
                    if node.get_data_type_as_variant_type(
                    ) in self.numeric_types:
                        mod_filter.DeadbandType = self.deadband_type  #1 assoluta , 2 percentage
                        mod_filter.DeadbandValue = self.deadband_value
                    else:
                        self.window.log_window.ui.logTextEdit.append(
                            "filter must be used for numeric data type")
                elif self.deadband_type == 2:
                    properties = node.get_properties()
                    EURange = False
                    for p in properties:
                        browse_name = p.get_browse_name().Name
                        if browse_name == "EURange":
                            EURange = True
                    if node.get_type_definition(
                    ).Identifier == ua.object_ids.ObjectIds.AnalogItemType and EURange == True:
                        if node.get_data_type_as_variant_type(
                        ) in self.numeric_types:
                            mod_filter.DeadbandType = self.deadband_type  #1 assoluta , 2 percentage
                            mod_filter.DeadbandValue = self.deadband_value
                        else:
                            self.window.log_window.ui.logTextEdit.append(
                                "filter must be used for numeric data type")
                    else:
                        self.window.log_window.ui.logTextEdit.append(
                            "percentage deadband must be applied to AnalagoItemType with EUrange"
                        )

            mir.RequestedParameters.Filter = mod_filter
            handle = self._datachange_sub[
                self.subscription_id].create_monitored_items([mir])
            self._subs_dc[node.nodeid] = (handle[0], self.subscription_id)
        except Exception as ex:
            self.window.log_window.ui.logTextEdit.append(str(ex))
            idx = self.monitored_item_model.indexFromItem(row[0])
            self.monitored_item_model.takeRow(idx.row())

    def _unsubscribe(self, node=None):
        try:
            if node is None:
                node = self.window.get_current_node()
            if node is None:
                return
            sub_id = self._subs_dc[node.nodeid][1]
            handle = self._subs_dc[node.nodeid][0]
            self._datachange_sub[sub_id].unsubscribe(handle)
            self._subscribed_nodes.remove(node)
            i = 0
            while self.monitored_item_model.item(i):
                item = self.monitored_item_model.item(i)
                if item.data() == node:
                    self.monitored_item_model.removeRow(i)
                i += 1
        except Exception as ex:
            self.window.log_window.ui.logTextEdit.append(str(ex))

    def _update_subscription_model(self, node, value, timestamp):
        i = 0
        while self.monitored_item_model.item(i):
            item = self.monitored_item_model.item(i)
            if item.data() == node:
                it = self.monitored_item_model.item(i, 1)
                it.setText(value)
                it_ts = self.monitored_item_model.item(i, 2)
                it_ts.setText(timestamp)
            i += 1

    def delete_subscription(self, subscription_id):
        try:
            for k, v in self._subs_dc.items():
                if v[1] == subscription_id:
                    node = self.get_node(k)
                    if node is not None:
                        self._unsubscribe(node=node)

            sub = self._datachange_sub[subscription_id].delete()
            self._datachange_sub.pop(subscription_id)
            i = 0
            while self.model.item(i):
                item = self.model.item(i)
                if item.data().subscription_id == subscription_id:
                    self.model.removeRow(i)
                i += 1
        except Exception as ex:
            self.window.log_window.ui.logTextEdit.append(str(ex))

    def get_node(self, node_id):
        for node in self._subscribed_nodes:
            if node != None:
                if node.nodeid == node_id:
                    return node
        return None
예제 #27
0
class WadTreeView(Base, Form):
    def __init__(self, root, controller, parent=None):
        super(self.__class__, self).__init__(parent)

        self.setupUi(self)
        add_widget(root, self, 'WAD_TREE')

        self.wads = controller.models.wads
        self.categories = controller.models.categories
        self.controller = controller
        self.loaded_wads = {}
        self.loaded_categories = {}
        self.pending_children = {}
        self.finished_loading = {'categories': False, 'wads': False}
        self.id_item_mapping = {}

        self.wadtree = self.findChild(QTreeView, 'wadtree')
        self.wadtree.dropEvent = self.dropEvent
        self.wadtree.setDragEnabled(True)
        self.wadtree.viewport().setAcceptDrops(True)
        self.wadtree.setDropIndicatorShown(True)
        self.wadtree.setDragDropMode(QAbstractItemView.DragDrop)
        self.wadtree.setDefaultDropAction(Qt.MoveAction)
        self.wadtree.setContextMenuPolicy(Qt.CustomContextMenu)
        self.wadtree.customContextMenuRequested.connect(self.open_menu)

        self.wadtree_model = QStandardItemModel()
        self.wadtree.setModel(self.wadtree_model)

        self.wadtree.selectionModel().selectionChanged.connect(
            self.select_tree_index)
        self.wadtree_model.itemChanged.connect(self.change_category_text)

        self.root = self.wadtree_model.invisibleRootItem()

    def dropEvent(self, e):
        from_index = e.source().currentIndex()
        from_item = self.wadtree_model.itemFromIndex(from_index)
        from_parent = from_item.parent() or self.root
        from_row = from_item.row()

        to_index = self.wadtree.indexAt(e.pos())
        to_item = self.wadtree_model.itemFromIndex(to_index) or self.root
        to_parent = to_item.parent() or self.root
        to_row = to_item.row()

        take_from = from_parent.takeRow(from_row)
        sharing_parents = from_parent == to_parent
        adjust = {
            QAbstractItemView.AboveItem:
            (-1) if sharing_parents and from_row < to_row else 0,
            QAbstractItemView.BelowItem:
            (0) if sharing_parents and from_row < to_row else 1
        }
        self.categories.remove_child(from_parent.data(ID_ROLE),
                                     from_item.data(ID_ROLE))

        indicator = self.wadtree.dropIndicatorPosition()
        if indicator == QAbstractItemView.OnItem:
            self.categories.add_child(to_item.data(ID_ROLE),
                                      from_item.data(ID_ROLE))
            to_item.appendRow(take_from)
        elif indicator == QAbstractItemView.AboveItem:
            self.categories.insert_child(to_parent.data(ID_ROLE),
                                         from_item.data(ID_ROLE),
                                         to_row + adjust[indicator])
            to_parent.insertRow(to_row + adjust[indicator], take_from)
        elif indicator == QAbstractItemView.BelowItem:
            self.categories.insert_child(to_parent.data(ID_ROLE),
                                         from_item.data(ID_ROLE),
                                         to_row + adjust[indicator])
            to_parent.insertRow(to_row + adjust[indicator], take_from)
        elif indicator == QAbstractItemView.OnViewport:
            self.categories.add_child(to_parent.data(ID_ROLE),
                                      from_item.data(ID_ROLE))
            to_parent.appendRow(take_from)

        self.categories.save()

    def change_category_text(self, item):
        if all(self.finished_loading.values()):
            item_text = item.text()
            if item.data(TYPE_ROLE) == 'categories' and self.categories.find(
                    item.data(ID_ROLE)).name != item_text:
                self.controller.edit_category(item.data(ID_ROLE),
                                              name=item_text)

    def select_tree_index(self, selection):
        if len(selection.indexes()) == 0:
            return
        index = selection.indexes()[0]
        item = self.wadtree_model.itemFromIndex(index)
        if item.data(TYPE_ROLE) == 'wads':
            self.wads.select_wad(item.data(ID_ROLE))

    def remove_wad(self, item):
        self.wads.remove(item.data(ID_ROLE))

    def wad_removed(self, data):
        item = self.id_item_mapping[data.id]
        parent = item.parent() or self.root
        self.categories.remove_child(parent.data(ID_ROLE), item.data(ID_ROLE))
        parent.removeRow(item.row())
        self.categories.save()

    def import_wad(self, id, parent_name):
        data = self.wads.find(id)
        item = self.make_tree_item(data)
        parent = self.categories.find_by(name=parent_name)
        parent_item = None
        if not parent:
            parent_id = self.categories.create(name=parent_name, children=[])
            parent_item = self.make_tree_item(self.categories.find(parent_id))
            self.root.appendRow(parent_item)
            self.categories.add_child(self.root.data(ID_ROLE),
                                      parent_item.data(ID_ROLE))
        else:
            parent_item = self.id_item_mapping[parent.id]
        index = self.wadtree_model.indexFromItem(parent_item)
        self.wadtree.expand(index)
        self.categories.add_child(parent_item.data(ID_ROLE), data.id)
        parent_item.appendRow(item)
        self.categories.save()

    def new_category(self, index):
        item = self.wadtree_model.itemFromIndex(index) or self.root
        if item.data(TYPE_ROLE) == 'wads':
            item = self.wadtree_model.itemFromIndex(
                index.parent()) or self.root
        self.categories.new(item.data(ID_ROLE))

    def category_added(self, parent_id, data):
        item = self.make_tree_item(data)
        self.id_item_mapping[parent_id].appendRow(item)

    def remove_category(self, id):
        parent = self.id_item_mapping[id].parent() or self.root
        self.categories.remove(id, parent.data(ID_ROLE))

    def category_removed(self, parent_id, id):
        parent = self.id_item_mapping[parent_id]
        item = self.id_item_mapping.pop(id)
        item_row = item.row()
        for row in reversed(range(item.rowCount())):
            child = item.takeChild(row)
            parent.insertRow(item_row + 1, child)
        parent.removeRow(item_row)

    def open_menu(self, pos):
        index = self.wadtree.indexAt(pos)
        entries = [('Add Category', lambda *_: self.new_category(index))]

        if index.isValid():
            item = self.wadtree_model.itemFromIndex(index)
            model = getattr(self, item.data(TYPE_ROLE).lower())
            data = model.find(item.data(ID_ROLE))

            def remove_wad():
                self.remove_wad(item)

            def remove_category():
                self.remove_category(data.id)

            entries_by_model = {
                'categories':
                ('Remove category ({})'.format(data.name), remove_category),
                'wads': ('Remove ({})'.format(data.display_name), remove_wad)
            }
            entries.append(entries_by_model[item.data(TYPE_ROLE).lower()])

        execute_menu = make_context_menu(self, entries)
        execute_menu(pos)

    def appendWad(self, data):
        if data.id in self.pending_children:
            from_item = self.pending_children.pop(data.id)
            item = self.make_tree_item(data, from_item=from_item)
        else:
            self.loaded_wads[data.id] = data

    def appendCategory(self, data):
        is_root = data.is_root
        item = None
        if is_root:
            item = self.make_tree_item(data, self.root)
            self.root = item
        elif data.id in self.pending_children:
            from_item = self.pending_children.pop(data.id)
            item = self.make_tree_item(data, from_item=from_item)
        else:
            item = self.make_tree_item(data)
            self.loaded_categories[data.id] = item

        for child_id in data.children:
            child_item = None
            if child_id in self.loaded_wads:
                wad = self.loaded_wads.pop(child_id)
                child_item = self.make_tree_item(wad)
                child_item.setFlags(TREE_WAD_FLAGS)
            elif child_id in self.loaded_categories:
                child_item = self.loaded_categories.pop(child_id)
            else:
                child_item = self.make_tree_item(Pending(id=child_id))
                self.pending_children[child_id] = child_item
            item.appendRow(child_item)

        if data.id not in self.loaded_categories:
            index = self.wadtree_model.indexFromItem(item)
            self.wadtree.expand(index)

    def finish_loading(self, model_type):
        self.finished_loading[model_type] = True
        if not all(self.finished_loading.values()): return
        wads_missing_categories = False
        root_id = self.root.data(ID_ROLE)
        root_category = self.categories.find(root_id)
        for wad in self.loaded_wads.values():
            if wad.id in self.pending_children:
                item = self.pending_children.pop(wad.id)
                self.make_tree_item(wad, from_item=item)
            else:
                item = self.make_tree_item(wad)
                root_category.add_child(wad.id)
                self.root.appendRow(item)
                wads_missing_categories = True
        for category in self.loaded_categories.values():
            root_category.add_child(category.id)
            self.root.appendRow(category)
            wads_missing_categories = True
        if wads_missing_categories:
            self.categories.save()

        for pending_child in self.pending_children.values():
            parent = (pending_child.parent() or self.root)
            parent_category = self.categories.find(parent.data(ID_ROLE))
            parent_category.remove_child(pending_child.data(ID_ROLE))
            parent.removeRow(pending_child.row())
            self.categories.save()

    def make_tree_item(self, data, from_item=None):
        item_type = data.model_type
        make_item = {
            'categories': make_category_item,
            'wads':
            lambda data, item: make_wad_item(data, TREE_WAD_FLAGS, item),
            'pending': make_pending_item
        }
        item = make_item[item_type](data, from_item)
        self.id_item_mapping[data.id] = item

        return item
예제 #28
0
class Explorer(QWidget):
    """
    This class implements the diagram predicate node explorer.
    """
    def __init__(self, mainwindow):
        """
        Initialize the Explorer.
        :type mainwindow: MainWindow
        """
        super().__init__(mainwindow)
        self.expanded = {}
        self.searched = {}
        self.scrolled = {}
        self.mainview = None
        self.iconA = QIcon(':/icons/treeview-icon-attribute')
        self.iconC = QIcon(':/icons/treeview-icon-concept')
        self.iconD = QIcon(':/icons/treeview-icon-datarange')
        self.iconI = QIcon(':/icons/treeview-icon-instance')
        self.iconR = QIcon(':/icons/treeview-icon-role')
        self.iconV = QIcon(':/icons/treeview-icon-value')
        self.search = StringField(self)
        self.search.setAcceptDrops(False)
        self.search.setClearButtonEnabled(True)
        self.search.setPlaceholderText('Search...')
        self.search.setFixedHeight(30)
        self.model = QStandardItemModel(self)
        self.proxy = QSortFilterProxyModel(self)
        self.proxy.setDynamicSortFilter(False)
        self.proxy.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.proxy.setSortCaseSensitivity(Qt.CaseSensitive)
        self.proxy.setSourceModel(self.model)
        self.view = ExplorerView(mainwindow, self)
        self.view.setModel(self.proxy)
        self.mainLayout = QVBoxLayout(self)
        self.mainLayout.setContentsMargins(0, 0, 0, 0)
        self.mainLayout.addWidget(self.search)
        self.mainLayout.addWidget(self.view)
        self.setContentsMargins(0, 0, 0, 0)
        self.setMinimumWidth(216)
        self.setMinimumHeight(160)

        connect(self.view.doubleClicked, self.itemDoubleClicked)
        connect(self.view.pressed, self.itemPressed)
        connect(self.view.collapsed, self.itemCollapsed)
        connect(self.view.expanded, self.itemExpanded)
        connect(self.search.textChanged, self.filterItem)

    ####################################################################################################################
    #                                                                                                                  #
    #   EVENTS                                                                                                         #
    #                                                                                                                  #
    ####################################################################################################################

    def paintEvent(self, paintEvent):
        """
        This is needed for the widget to pick the stylesheet.
        :type paintEvent: QPaintEvent
        """
        option = QStyleOption()
        option.initFrom(self)
        painter = QPainter(self)
        style = self.style()
        style.drawPrimitive(QStyle.PE_Widget, option, painter, self)

    ####################################################################################################################
    #                                                                                                                  #
    #   SLOTS                                                                                                          #
    #                                                                                                                  #
    ####################################################################################################################

    @pyqtSlot('QGraphicsItem')
    def add(self, item):
        """
        Add a node in the tree view.
        :type item: AbstractItem
        """
        if item.node and item.predicate:
            parent = self.parentFor(item)
            if not parent:
                parent = ParentItem(item)
                parent.setIcon(self.iconFor(item))
                self.model.appendRow(parent)
                self.proxy.sort(0, Qt.AscendingOrder)
            child = ChildItem(item)
            child.setData(item)
            parent.appendRow(child)
            self.proxy.sort(0, Qt.AscendingOrder)

    @pyqtSlot(str)
    def filterItem(self, key):
        """
        Executed when the search box is filled with data.
        :type key: str
        """
        if self.mainview:
            self.proxy.setFilterFixedString(key)
            self.proxy.sort(Qt.AscendingOrder)
            self.searched[self.mainview] = key

    @pyqtSlot('QModelIndex')
    def itemCollapsed(self, index):
        """
        Executed when an item in the tree view is collapsed.
        :type index: QModelIndex
        """
        if self.mainview:
            if self.mainview in self.expanded:
                item = self.model.itemFromIndex(self.proxy.mapToSource(index))
                expanded = self.expanded[self.mainview]
                expanded.remove(item.text())

    @pyqtSlot('QModelIndex')
    def itemDoubleClicked(self, index):
        """
        Executed when an item in the tree view is double clicked.
        :type index: QModelIndex
        """
        item = self.model.itemFromIndex(self.proxy.mapToSource(index))
        node = item.data()
        if node:
            self.selectNode(node)
            self.focusNode(node)

    @pyqtSlot('QModelIndex')
    def itemExpanded(self, index):
        """
        Executed when an item in the tree view is expanded.
        :type index: QModelIndex
        """
        if self.mainview:
            item = self.model.itemFromIndex(self.proxy.mapToSource(index))
            if self.mainview not in self.expanded:
                self.expanded[self.mainview] = set()
            expanded = self.expanded[self.mainview]
            expanded.add(item.text())

    @pyqtSlot('QModelIndex')
    def itemPressed(self, index):
        """
        Executed when an item in the tree view is clicked.
        :type index: QModelIndex
        """
        item = self.model.itemFromIndex(self.proxy.mapToSource(index))
        node = item.data()
        if node:
            self.selectNode(node)

    @pyqtSlot('QGraphicsItem')
    def remove(self, item):
        """
        Remove a node from the tree view.
        :type item: AbstractItem
        """
        if item.node and item.predicate:
            parent = self.parentFor(item)
            if parent:
                child = self.childFor(parent, item)
                if child:
                    parent.removeRow(child.index().row())
                if not parent.rowCount():
                    self.model.removeRow(parent.index().row())

    ####################################################################################################################
    #                                                                                                                  #
    #   AUXILIARY METHODS                                                                                              #
    #                                                                                                                  #
    ####################################################################################################################

    @staticmethod
    def childFor(parent, node):
        """
        Search the item representing this node among parent children.
        :type parent: QStandardItem
        :type node: AbstractNode
        """
        key = ChildItem.key(node)
        for i in range(parent.rowCount()):
            child = parent.child(i)
            if child.text() == key:
                return child
        return None

    def parentFor(self, node):
        """
        Search the parent element of the given node.
        :type node: AbstractNode
        :rtype: QStandardItem
        """
        key = ParentItem.key(node)
        for i in self.model.findItems(key, Qt.MatchExactly):
            n = i.child(0).data()
            if node.item is n.item:
                return i
        return None

    ####################################################################################################################
    #                                                                                                                  #
    #   INTERFACE                                                                                                      #
    #                                                                                                                  #
    ####################################################################################################################

    def browse(self, view):
        """
        Set the widget to inspect the given view.
        :type view: MainView
        """
        self.reset()
        self.mainview = view

        if self.mainview:

            scene = self.mainview.scene()
            connect(scene.index.sgnItemAdded, self.add)
            connect(scene.index.sgnItemRemoved, self.remove)

            for item in scene.index.nodes():
                self.add(item)

            if self.mainview in self.expanded:
                expanded = self.expanded[self.mainview]
                for i in range(self.model.rowCount()):
                    item = self.model.item(i)
                    index = self.proxy.mapFromSource(
                        self.model.indexFromItem(item))
                    self.view.setExpanded(index, item.text() in expanded)

            key = ''
            if self.mainview in self.searched:
                key = self.searched[self.mainview]
            self.search.setText(key)

            if self.mainview in self.scrolled:
                rect = self.rect()
                item = first(self.model.findItems(
                    self.scrolled[self.mainview]))
                for i in range(self.model.rowCount()):
                    self.view.scrollTo(
                        self.proxy.mapFromSource(
                            self.model.indexFromItem(self.model.item(i))))
                    index = self.proxy.mapToSource(
                        self.view.indexAt(rect.topLeft()))
                    if self.model.itemFromIndex(index) is item:
                        break

    def reset(self):
        """
        Clear the widget from inspecting the current view.
        """
        if self.mainview:

            rect = self.rect()
            item = self.model.itemFromIndex(
                self.proxy.mapToSource(self.view.indexAt(rect.topLeft())))
            if item:
                node = item.data()
                key = ParentItem.key(node) if node else item.text()
                self.scrolled[self.mainview] = key
            else:
                self.scrolled.pop(self.mainview, None)

            try:
                scene = self.mainview.scene()
                disconnect(scene.index.sgnItemAdded, self.add)
                disconnect(scene.index.sgnItemRemoved, self.remove)
            except RuntimeError:
                pass
            finally:
                self.mainview = None

        self.model.clear()

    def flush(self, view):
        """
        Flush the cache of the given mainview.
        :type view: MainView
        """
        self.expanded.pop(view, None)
        self.searched.pop(view, None)
        self.scrolled.pop(view, None)

    def iconFor(self, node):
        """
        Returns the icon for the given node.
        :type node:
        """
        if node.item is Item.AttributeNode:
            return self.iconA
        if node.item is Item.ConceptNode:
            return self.iconC
        if node.item is Item.ValueDomainNode:
            return self.iconD
        if node.item is Item.ValueRestrictionNode:
            return self.iconD
        if node.item is Item.IndividualNode:
            if node.identity is Identity.Instance:
                return self.iconI
            if node.identity is Identity.Value:
                return self.iconV
        if node.item is Item.RoleNode:
            return self.iconR

    def focusNode(self, node):
        """
        Focus the given node in the main view.
        :type node: AbstractNode
        """
        if self.mainview:
            self.mainview.centerOn(node)

    def selectNode(self, node):
        """
        Select the given node in the main view.
        :type node: AbstractNode
        """
        if self.mainview:
            scene = self.mainview.scene()
            scene.clearSelection()
            node.setSelected(True)
예제 #29
0
    def __init__(self, app: QApplication):
        super().__init__()

        Settings.load()

        self.setWindowTitle(APP_NAME)

        self.app = app
        app.setApplicationName(APP_NAME)
        app.setApplicationDisplayName(APP_NAME)

        self.preview_image = QLabel('No items to preview')
        self.preview_image.setFixedHeight(USABLE_HEIGHT)

        self.props_empty = False

        self.save_image_button = QPushButton('Save image')
        self.save_image_button.setDisabled(True)

        self.tree_view = ItemView()
        self.items = self.create_model()
        self.item_data = []
        self.tree_view.setRootIsDecorated(False)
        # self.tree_view.setAlternatingRowColors(True)
        self.tree_view.setModel(self.items)
        self.tree_view.clicked.connect(self.tree_view_clicked)
        self.tree_view.rowMoved.connect(self.tree_view_reorder)
        self.tree_view.setDragEnabled(True)
        self.tree_view.setDragDropMode(QTreeView.InternalMove)
        self.tree_view.setItemsExpandable(False)
        self.tree_view.setDragDropOverwriteMode(False)

        root = QVBoxLayout()

        items_layout = QHBoxLayout()

        group = QGroupBox('Source items:')
        layout = QVBoxLayout()
        layout.addWidget(self.tree_view)
        buttons = QHBoxLayout()

        add_menu = QMenu()
        add_menu.addAction(Text.get_add_add_action(self))
        add_menu.addAction(Image.get_add_add_action(self))
        add_menu.addAction(Barcode.get_add_add_action(self))
        add_menu.addAction(QrCode.get_add_add_action(self))

        add_menu.addSeparator()
        add_menu.addAction(Spacing.get_add_add_action(self))

        add_button = QPushButton('Add')
        add_button.setMenu(add_menu)
        buttons.addWidget(add_button)

        #buttons.addWidget(add_text_button)
        #buttons.addWidget(add_barcode_button)
        buttons.addStretch()
        buttons.setSpacing(1)

        b_down = QPushButton('⬇︎')
        b_down.clicked.connect(lambda _: self.move_item(1))
        b_up = QPushButton('⬆︎')
        b_up.clicked.connect(lambda _: self.move_item(-1))
        b_delete = QPushButton('Delete')
        b_delete.clicked.connect(self.delete_item)

        b_clone = QPushButton('Copy')
        b_clone.clicked.connect(self.on_clone)

        buttons.addWidget(b_clone)
        buttons.addSpacing(10)
        buttons.addWidget(b_up)
        buttons.addWidget(b_down)
        buttons.addSpacing(10)
        buttons.addWidget(b_delete)

        layout.addLayout(buttons)
        group.setLayout(layout)
        items_layout.addWidget(group)

        self.property_group = QGroupBox('Item properties:')
        self.props_layout = QVBoxLayout()

        self.property_group.setLayout(self.props_layout)

        self.props_current = QLabel()
        self.props_layout.addWidget(self.props_current)
        self.save_printable_button = QPushButton('Save Changes')
        self.save_printable_button.clicked.connect(self.save_props)
        self.props_layout.addWidget(self.save_printable_button)
        self.update_props()

        items_layout.addWidget(self.property_group)

        root.addLayout(items_layout)

        group = QGroupBox('Preview:')
        layout = QVBoxLayout()

        preview_wrapper = QScrollArea(self)

        #prev_layout = QHBoxLayout()
        preview_wrapper.setWidget(self.preview_image)
        preview_wrapper.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        preview_wrapper.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        preview_wrapper.setWidgetResizable(True)
        preview_wrapper.setFixedHeight(USABLE_HEIGHT)
        layout.addWidget(preview_wrapper)

        # layout.addWidget(self.save_image_button)
        group.setLayout(layout)
        group.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))

        root.addWidget(group)

        self.printer_select = QComboBox(self)
        fs_model = QFileSystemModel(self)
        #model_proxy = QSortFilterProxyModel(self)
        #model_proxy.setSourceModel(fs_model)
        # fs_model.setNameFilters(['tty.PT-P3*'])

        potential_printer = None
        printers = QStandardItemModel()

        #for p in QDir('/dev').entryList(['tty*'], QDir.System, QDir.Name):
        #    if p.startswith('tty.'):
        for p in LabelMaker.list_serial_ports():
            # pprint(p.__dict__)
            item = [QStandardItem(p.name), QStandardItem(p.device)]
            print(p.name)
            printers.appendRow(item)
            '''
            item = QStandardItem('/dev/' + p)
            printers.appendRow(item)
            if p.startswith('tty.PT-P3'):
                potential_printer = item
            '''

        #print(printers.entryList())

        #model_proxy.setRecursiveFilteringEnabled(True)
        #model_proxy.setFilterKeyColumn(0)

        fs_model.setRootPath('/dev/')  #/Users/nilsmasen')
        fs_model.setFilter(QDir.System)

        dev_index = fs_model.index('/dev')
        #proxy_dev = model_proxy.mapFromSource(dev_index)

        self.printer_select.setModel(printers)

        if potential_printer is not None:
            index = printers.indexFromItem(potential_printer)
            self.printer_select.setCurrentIndex(index.row())
        #printer_select.setRootModelIndex(dev_index)
        #printer_select.setRootIndex(dev_index)
        #printer_select.setExpanded(dev_index, True)
        #model_proxy.setFilterWildcard('tty*')

        bottom_box = QGroupBox('Print label: ')
        bottom_bar = QHBoxLayout()
        bottom_bar.addWidget(QLabel('Print device:'))
        bottom_bar.addWidget(self.printer_select)
        bottom_bar.addStretch()
        # /dev/tty.PT-P300BT0607-Serial
        print_button = QPushButton('Print')
        print_button.setFixedWidth(100)
        bottom_bar.addWidget(print_button)
        print_button.clicked.connect(self.print_clicked)

        bottom_box.setLayout(bottom_bar)
        root.addWidget(bottom_box)

        root_widget = QWidget()
        root_widget.setFixedWidth(800)
        root_widget.setLayout(root)
        self.setCentralWidget(root_widget)
        menu = QMenuBar()
        self.setMenuBar(menu)

        menu.setWindowTitle(APP_NAME)
        tools_menu = menu.addMenu('Python')
        prefs = QAction('&Preferences', self)
        prefs.triggered.connect(self.on_prefs)
        tools_menu.addAction(prefs)
        about = QAction('About ' + APP_NAME, self)
        about.triggered.connect(self.on_prefs)
        about.setMenuRole(QAction.AboutRole)
        tools_menu.addAction(about)

        file_menu = menu.addMenu('Label')
        act_new = QAction('&New', self)
        act_new.triggered.connect(self.on_new)
        file_menu.addAction(act_new)
        file_menu.addSeparator()
        act_open = QAction('&Open', self)
        act_open.triggered.connect(self.on_open)
        act_open.setShortcut(QKeySequence('Ctrl+O'))
        file_menu.addAction(act_open)
        file_menu.addSeparator()
        act_save = QAction('&Save', self)
        act_save.triggered.connect(self.on_save)
        act_save.setShortcut(QKeySequence("Ctrl+S"))
        file_menu.addAction(act_save)
        act_save_as = QAction('Save &as...', self)
        act_save_as.triggered.connect(self.on_save_as)
        act_save_as.setShortcut(QKeySequence("Ctrl+Shift+S"))
        file_menu.addAction(act_save_as)
        file_menu.addSeparator()
        file_menu.addAction(QAction('&Export image', self))
예제 #30
0
class DataChangeUI(object):
    def __init__(self, window, uaclient, hub_manager):
        self.window = window
        self.uaclient = uaclient

        # FIXME IoT stuff
        self.hub_manager = hub_manager

        self._subhandler = DataChangeHandler(self.hub_manager)
        self._subscribed_nodes = []
        self.model = QStandardItemModel()
        self.window.ui.subView.setModel(self.model)
        self.window.ui.subView.horizontalHeader().setSectionResizeMode(1)

        self.window.ui.actionSubscribeDataChange.triggered.connect(
            self._subscribe)
        self.window.ui.actionUnsubscribeDataChange.triggered.connect(
            self._unsubscribe)

        # populate contextual menu
        self.window.ui.treeView.addAction(
            self.window.ui.actionSubscribeDataChange)
        self.window.ui.treeView.addAction(
            self.window.ui.actionUnsubscribeDataChange)

        # handle subscriptions
        self._subhandler.data_change_fired.connect(
            self._update_subscription_model, type=Qt.QueuedConnection)

    def clear(self):
        self._subscribed_nodes = []
        self.model.clear()

    def _subscribe(self):
        node = self.window.get_current_node()
        if node is None:
            return
        if node in self._subscribed_nodes:
            print("allready subscribed to node: ", node)
            return
        self.model.setHorizontalHeaderLabels(
            ["DisplayName", "Value", "Timestamp"])
        row = [
            QStandardItem(node.display_name),
            QStandardItem("No Data yet"),
            QStandardItem("")
        ]
        row[0].setData(node)
        self.model.appendRow(row)
        self._subscribed_nodes.append(node)
        self.window.ui.subDockWidget.raise_()
        try:
            self.uaclient.subscribe_datachange(node, self._subhandler)
        except Exception as ex:
            self.window.show_error(ex)
            idx = self.model.indexFromItem(row[0])
            self.model.takeRow(idx.row())

    def _unsubscribe(self):
        node = self.window.get_current_node()
        if node is None:
            return
        self.uaclient.unsubscribe_datachange(node)
        self._subscribed_nodes.remove(node)
        i = 0
        while self.model.item(i):
            item = self.model.item(i)
            if item.data() == node:
                self.model.removeRow(i)
            i += 1

    def _update_subscription_model(self, node, value, timestamp):
        i = 0
        while self.model.item(i):
            item = self.model.item(i)
            if item.data() == node:
                it = self.model.item(i, 1)
                it.setText(value)
                it_ts = self.model.item(i, 2)
                it_ts.setText(timestamp)
            i += 1
예제 #31
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)
        # Ensure there's a .py extension if none is provided by the user.
        # See issue #1571.
        name, ext = os.path.splitext(os.path.basename(path))
        if (not name.startswith(".")) and (not ext):
            # The file is not a . (dot) file and there's no extension, so add
            # .py as default.
            path += ".py"
        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)

        new_tab.context_menu.connect(self.on_context_menu)

        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_context_menu(self):
        """
        Called when a user right-clicks on an editor pane.

        If the REPL is active AND there is selected text in the current editor
        pane, modify the default context menu to include a paste to REPL
        option. Otherwise, just display the default context menu.
        """
        # Create a standard context menu.
        menu = self.current_tab.createStandardContextMenu()
        # Flag to indicate if there is a section of text highlighted.
        has_selection = any([x > -1 for x in self.current_tab.getSelection()])
        # Flag to indicate if the REPL pane is active.
        has_repl = hasattr(self, "repl") and self.repl
        # Flag to indicate if the Python process runner pane is active and in
        # interactive Python3 mode (pasting into other "standard" Python modes
        # doesn't make sense).
        has_runner = (hasattr(self, "process_runner") and self.process_runner
                      and self.process_runner.is_interactive)
        if has_selection and (has_repl or has_runner):
            # Text selected with REPL/process context, so add the bespoke
            # "copy to repl" item to the standard context menu to handle this
            # situation.
            actions = menu.actions()
            copy_to_repl = QAction(_("Copy selected text to REPL"), menu)
            copy_to_repl.triggered.connect(self.copy_to_repl)
            menu.insertAction(actions[0], copy_to_repl)
            menu.insertSeparator(actions[0])
        # Display menu.
        menu.exec_(QCursor.pos())

    def copy_to_repl(self):
        """
        Copies currently selected text in the editor, into the active REPL
        widget and sets focus to the REPL widget. The final line pasted into
        the REPL waits for RETURN to be pressed by the user (this appears to be
        the default behaviour for pasting into the REPL widget).
        """
        line_from, pos_from, line_to, pos_to = self.current_tab.getSelection()
        lines = self.current_tab.text().split("\n")
        if line_from == line_to:
            # Paste a fragment from an individual line.
            to_paste = lines[line_from][pos_from:pos_to]
        else:
            # Multi-line paste (paste all lines selected).
            selected = lines[line_from:line_to + 1]
            # Ensure the correct indentation.
            # If the first line starts with whitespace, work out the number of
            # spaces and deduct said number of whitespace from start of all
            # other lines. That's perhaps the best we can do.
            if selected and selected[0].startswith(" "):
                indent = 0
                for char in selected[0]:
                    if char.isspace():
                        indent += 1
                    else:
                        break
                selected = [line[indent:] for line in selected]
            to_paste = "\n".join(selected)
        logger.info("Pasting to REPL")
        logger.info("\n" + to_paste)
        clipboard = QApplication.clipboard()
        clipboard.setText(to_paste)
        if hasattr(self, "repl_pane") and self.repl_pane:
            self.repl_pane.paste()
            self.repl_pane.setFocus()
        elif hasattr(self, "process_runner") and self.process_runner:
            self.process_runner.paste()
            self.process_runner.setFocus()

    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,
                                               False,
                                               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, False, 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,
                False,  # 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)
예제 #32
0
class DataChangeUI(object):
    def __init__(self, window, uaclient):
        self.window = window
        self.uaclient = uaclient
        self._subhandler = DataChangeHandler()
        self._subscribed_nodes = []
        self.model = QStandardItemModel()
        self.window.ui.subView.setModel(self.model)
        self.window.ui.subView.horizontalHeader().setSectionResizeMode(1)

        self.window.ui.actionSubscribeDataChange.triggered.connect(
            self._subscribe)
        self.window.ui.actionUnsubscribeDataChange.triggered.connect(
            self._unsubscribe)

        # populate contextual menu
        self.window.addAction(self.window.ui.actionSubscribeDataChange)
        self.window.addAction(self.window.ui.actionUnsubscribeDataChange)

        # handle subscriptions
        self._subhandler.data_change_fired.connect(
            self._update_subscription_model, type=Qt.QueuedConnection)

        # accept drops
        self.model.canDropMimeData = self.canDropMimeData
        self.model.dropMimeData = self.dropMimeData

    def canDropMimeData(self, mdata, action, row, column, parent):
        return True

    def dropMimeData(self, mdata, action, row, column, parent):
        node = self.uaclient.client.get_node(mdata.text())
        self._subscribe(node)
        return True

    def clear(self):
        self._subscribed_nodes = []
        self.model.clear()

    def show_error(self, *args):
        self.window.show_error(*args)

    @trycatchslot
    def _subscribe(self, node=None):
        if not isinstance(node, Node):
            node = self.window.get_current_node()
            if node is None:
                return
        if node in self._subscribed_nodes:
            logger.warning("allready subscribed to node: %s ", node)
            return
        self.model.setHorizontalHeaderLabels(
            ["DisplayName", "Value", "Timestamp"])
        text = str(node.get_display_name().Text)
        row = [
            QStandardItem(text),
            QStandardItem("No Data yet"),
            QStandardItem("")
        ]
        row[0].setData(node)
        self.model.appendRow(row)
        self._subscribed_nodes.append(node)
        self.window.ui.subDockWidget.raise_()
        try:
            self.uaclient.subscribe_datachange(node, self._subhandler)
        except Exception as ex:
            self.window.show_error(ex)
            idx = self.model.indexFromItem(row[0])
            self.model.takeRow(idx.row())
            raise

    @trycatchslot
    def _unsubscribe(self):
        node = self.window.get_current_node()
        if node is None:
            return
        self.uaclient.unsubscribe_datachange(node)
        self._subscribed_nodes.remove(node)
        i = 0
        while self.model.item(i):
            item = self.model.item(i)
            if item.data() == node:
                self.model.removeRow(i)
            i += 1

    def _update_subscription_model(self, node, value, timestamp):
        i = 0
        while self.model.item(i):
            item = self.model.item(i)
            if item.data() == node:
                it = self.model.item(i, 1)
                it.setText(value)
                it_ts = self.model.item(i, 2)
                it_ts.setText(timestamp)
            i += 1
예제 #33
0
class CommonListView(QListView):
    dblAddress = pyqtSignal(int)

    def __init__(self):
        super(CommonListView, self).__init__()
        self.model = QStandardItemModel(self)
        self.setItemDelegate(HTMLDelegate(self))
        self.setModel(self.model)
        self.addressMap = {}
        self.resetList = []
        self.lockRelease = True
        self.clickedX = 0
        self.lastClickIndex = -1
        self.setEditTriggers(QListView.NoEditTriggers)
        self.setStyleSheet("QListView::item{margin-bottom: 5px; padding:0px}")

    def getItem(self, row):
        return self.model.item(row, 0)

    def getItemFormIndex(self, index):
        return self.model.item(index.row(), 0)

    def setSize(self):
        width_view = 0
        for i in range(self.model.rowCount()):
            item = self.getItem(i)
            _, end = item.componentRanges[-1]
            if width_view < end * self.fontMetrics().averageCharWidth():
                width_view = end * self.fontMetrics().averageCharWidth()
        self.setFixedWidth(width_view +
                           self.fontMetrics().averageCharWidth() * 3)
        self.setFixedHeight(
            self.sizeHintForRow(1) * self.model.rowCount() +
            2 * self.frameWidth() + 10)

    def highlighRelation(self, text, start, func=None):
        texts = [text]
        for regs in relate_registers:
            if text in regs:
                texts = regs
                break
        if (func is not None) and (func.address in self.addressMap):
            startItem = self.addressMap[func.address]
            index = self.model.indexFromItem(startItem)
            row = index.row()
            item = self.getItem(row)
            while hasattr(item, 'func') and item.func == func:
                if isinstance(item, LocLine):
                    change = item.highlight(texts, 0)
                else:
                    change = item.highlight(texts, start)
                if change:
                    self.resetList.append(item)
                row += 1
                item = self.getItem(row)
                if hasattr(item, 'address'):
                    if item.address > func.maxBound:
                        break
        else:
            for i in range(self.model.rowCount()):
                item = self.getItem(i)
                if item.isSelectable():
                    if isinstance(item, LocLine):
                        change = item.highlight(texts, 0)
                    else:
                        if hasattr(item, 'highlight'):
                            change = item.highlight(texts, start)
                    if change:
                        self.resetList.append(item)

    def mousePressEvent(self, event) -> None:
        self.lockRelease = False
        self.clickedX = event.pos().x()
        for item in self.resetList:
            if hasattr(item, 'normal'):
                item.setText(item.normal)
        self.resetList.clear()
        super(CommonListView, self).mousePressEvent(event)

    def mouseReleaseEvent(self, event) -> None:
        if not self.lockRelease:
            indexes = self.selectedIndexes()
            if len(indexes) == 1:
                index = self.selectedIndexes()[0]
                item = self.getItemFormIndex(index)
                self.getClickedIndex(item)
        super(CommonListView, self).mouseReleaseEvent(event)

    def getClickedIndex(self, item, highlight=True):
        import platform
        if platform.system() == 'Windows':
            widthChar = self.fontMetrics().averageCharWidth() + 4
            self.clickedX -= 5
        else:
            widthChar = self.fontMetrics().averageCharWidth()
        pos = self.clickedX // widthChar
        if hasattr(item, 'getIndexByPos'):
            index = item.getIndexByPos(pos)
            if index != -1:
                self.lastClickIndex = index
                if item.components[index].text != ',':
                    start = 0
                    if index > 1:
                        start = 2
                    if highlight:
                        func = None
                        if hasattr(item, 'func'):
                            func = item.func
                        self.highlighRelation(item.components[index].text,
                                              start, func)
                    item.selectTextAt(index)
                    self.resetList.append(item)

    def mouseDoubleClickEvent(self, event) -> None:
        self.lockRelease = True
        index = self.selectedIndexes()[0]
        item = self.getItemFormIndex(index)
        self.dblAddress.emit(item.address)

    def focusAddress(self, address, focus=True):
        if address in self.addressMap:
            if focus:
                self.clearAllEffect()
            item = self.addressMap[address]
            index = self.model.indexFromItem(item)
            self.selectionModel().select(index, QItemSelectionModel.Select)
            if focus:
                self.scrollTo(index, QAbstractItemView.PositionAtCenter)
            self.setFocus()

    def focusItem(self, index):
        self.clearAllEffect()
        self.selectionModel().select(index, QItemSelectionModel.Select)
        self.scrollTo(index, QAbstractItemView.PositionAtCenter)
        self.setFocus()

    def clearAllEffect(self):
        self.clearFocus()
        self.clearSelection()
        for item in self.resetList:
            if hasattr(item, 'normal'):
                try:
                    item.setText(item.normal)
                except:
                    pass
        self.resetList.clear()