def __init__(self, parent_widget, ui, menu_name: str = _('Baum')): """ :param modules.gui.main_ui.KnechtWindow ui: The main ui class :param str menu_name: Edit Menu display name """ super(TreeMenu, self).__init__(menu_name, parent_widget) self.parent_widget = parent_widget self.ui = ui self.view: KnechtTreeView = None sort_view = QAction(IconRsc.get_icon('sort'), _('Breite der Kopfspalten an Bauminhalt anpassen'), self) sort_view.triggered.connect(self.sort_current_tree) quick_view = QAction(IconRsc.get_icon('eye'), _('Schnellansicht ein-/ausschalten'), self) quick_view.triggered.connect(self.ui.pushButton_Dest_show.animateClick) self.clear_view = QAction(IconRsc.get_icon('delete_list'), _('Bauminhalt vollständig verwerfen'), self) self.clear_view.triggered.connect(self.clear_current_tree) self.addActions([sort_view, quick_view, self.clear_view]) self.addSeparator() reset_filter = QAction(IconRsc.get_icon('reset'), _('Filter zurücksetzen\tEsc'), self) reset_filter.triggered.connect(self.clear_view_filter) collapse_all = QAction(IconRsc.get_icon('options'), _('Bauminhalte vollständig einklappen\t2x Esc'), self) collapse_all.triggered.connect(self.collapse_tree) self.addActions([reset_filter, collapse_all]) self.addSeparator() self.move_grp = QActionGroup(self) self.m_up = QAction( IconRsc.get_icon('arrow_up'), _('Selektierte Elemente aufwärts verschieben\tPfeil auf'), self.move_grp) self.m_dn = QAction( IconRsc.get_icon('arrow'), _('Selektierte Elemente abwärts verschieben\tPfeil ab'), self.move_grp) self.j_up = QAction( IconRsc.get_icon('arrow_up'), _('Selektierte Elemente 10 Schritte aufwärts verschieben\tBild auf' ), self.move_grp) self.j_dn = QAction( IconRsc.get_icon('arrow'), _('Selektierte Elemente 10 Schritte abwärts verschieben\tBild ab'), self.move_grp) self.move_grp.triggered.connect(self.move) self.addActions([self.m_up, self.m_dn, self.j_up, self.j_dn]) self.aboutToShow.connect(self.update_current_view)
def _display_age_column_header_context_menu(self, point: QPoint): menu = QMenu(self) format_menu = menu.addMenu('Format') format_action_group = QActionGroup(self) for age_format in self._age_formats: format_action = format_menu.addAction(age_format.NAME) format_action.age_format = age_format format_action.setCheckable(True) if self._age_format == age_format: format_action.setChecked(True) format_action_group.addAction(format_action) triggered_action = menu.exec_( self.horizontalHeader().viewport().mapToGlobal(point)) if triggered_action: self.age_format = triggered_action.age_format
def __init__(self, parent=None): QPlainTextEdit.__init__(self, parent) self.setMaximumBlockCount(1000) self.group = QActionGroup(self) self.actions = [ QAction(log_lvl, self.group) for log_lvl in ["Debug", "Info"] ] for action in self.actions: action.setCheckable(True) action.triggered.connect(self.on_log_filter) self.actions[1].setChecked(True) self.customContextMenuRequested.connect( self.on_custom_context_menu_requested) self.log_lvl = log_levels["INFO"]
def __init__(self, ui, menu_name: str = _('Baum Kontextmenü')): """ Context menu of document tabs :param modules.gui.main_ui.KnechtWindow ui: main window ui class :param str menu_name: name of the menu """ super(TabContextMenu, self).__init__(menu_name, ui) self.ui, self.status_bar = ui, ui.statusBar() self.context_tab_index = -1 grp = QActionGroup(self) self.copy_action = QAction( IconRsc.get_icon('options'), _('Dokumenten Pfad in Zwischenablage kopieren'), grp) self.open_action = QAction(IconRsc.get_icon('folder'), _('Dokumenten Pfad öffnen'), grp) grp.triggered.connect(self.doc_action) self.addActions([self.copy_action, self.open_action]) self.tab_bar = self.ui.srcTabWidget.tabBar() self.tab_bar.installEventFilter(self)
def _show_menu(self, colleague, pos): if not self._is_owner and not colleague.is_you or colleague.is_deleting: return menu = QMenu(self._ui.colleagues_list) menu.setStyleSheet("background-color: #EFEFF4; ") if colleague.is_you: if colleague.is_owner: action = menu.addAction(tr("Quit collaboration")) action.triggered.connect(self._on_quit_collaboration) else: action = menu.addAction(tr("Leave collaboration")) action.triggered.connect(self._on_leave_collaboration) else: rights_group = QActionGroup(menu) rights_group.setExclusive(True) menu.addSection(tr("Access rights")) action = menu.addAction(tr("Can view")) action.setCheckable(True) rights_action = rights_group.addAction(action) rights_action.setData(False) rights_action.setChecked(not colleague.can_edit) action = menu.addAction(tr("Can edit")) action.setCheckable(True) rights_action = rights_group.addAction(action) rights_action.setChecked(colleague.can_edit) rights_action.setData(True) rights_group.triggered.connect( lambda a: self._on_grant_edit(colleague, a)) menu.addSeparator() action = menu.addAction(tr("Remove user")) action.triggered.connect(lambda: self._on_remove_user(colleague)) pos_to_show = QPoint(pos.x(), pos.y() + 10) menu.exec_(pos_to_show)
def __init__(self, mainWindow): self.mainWindow = mainWindow #Basic actions self.basicActions = QActionGroup(self.mainWindow) self.actionOpen = self.basicActions.addAction(QIcon(":/icons/save.png"), "Open Database") self.actionOpen.triggered.connect(self.openDB) self.actionNew = self.basicActions.addAction(QIcon(":/icons/new.png"), "New Database") self.actionNew.triggered.connect(self.makeDB) #Database actions self.databaseActions = QActionGroup(self.mainWindow) self.actionExport = self.databaseActions.addAction(QIcon(":/icons/export.png"), "Export Data") self.actionExport.setToolTip("Export selected node(s) and their children to a .csv file. \n If no or all node(s) are selected inside the data-view, a complete export of all data in the DB is performed") self.actionExport.triggered.connect(self.exportNodes) self.actionAdd = self.databaseActions.addAction(QIcon(":/icons/add.png"), "Add Nodes") self.actionAdd.setToolTip("Add new node(s) as a starting point for further data collection") self.actionAdd.triggered.connect(self.addNodes) self.actionDelete = self.databaseActions.addAction(QIcon(":/icons/delete.png"), "Delete Nodes") self.actionDelete.setToolTip("Delete nodes(s) and their children") self.actionDelete.triggered.connect(self.deleteNodes) #Data actions self.dataActions = QActionGroup(self.mainWindow) self.actionQuery = self.dataActions.addAction(QIcon(":/icons/fetch.png"), "Query") self.actionQuery.triggered.connect(self.querySelectedNodes) self.actionTimer = self.dataActions.addAction(QIcon(":/icons/fetch.png"), "Time") self.actionTimer.setToolTip("Time your data collection with a timer. Fetches the data for the selected node(s) in user-defined intervalls") self.actionTimer.triggered.connect(self.setupTimer) self.actionHelp = self.dataActions.addAction(QIcon(":/icons/help.png"), "Help") self.actionHelp.triggered.connect(self.help) self.actionLoadPreset = self.dataActions.addAction(QIcon(":/icons/presets.png"), "Presets") self.actionLoadPreset.triggered.connect(self.loadPreset) self.actionLoadAPIs = self.dataActions.addAction(QIcon(":/icons/apis.png"), "APIs") self.actionLoadAPIs.triggered.connect(self.loadAPIs) self.actionShowColumns = self.dataActions.addAction("Show Columns") self.actionShowColumns.triggered.connect(self.showColumns) self.actionClearColumns = self.dataActions.addAction("Clear Columns") self.actionClearColumns.triggered.connect(self.clearColumns) #Detail actions self.detailActions = QActionGroup(self.mainWindow) self.actionAddColumn = self.detailActions.addAction(QIcon(":/icons/addcolumn.png"),"Add Column") self.actionAddColumn.setToolTip("Add the current JSON-key as a column in the data view") self.actionAddColumn.triggered.connect(self.addColumn) self.actionAddAllolumns = self.detailActions.addAction(QIcon(":/icons/addcolumn.png"),"Add All Columns") self.actionAddAllolumns.setToolTip("Analyzes all selected nodes in the data view and adds all found keys as columns") self.actionAddAllolumns.triggered.connect(self.addAllColumns) self.actionUnpack = self.detailActions.addAction(QIcon(":/icons/unpack.png"),"Unpack List") self.actionUnpack.setToolTip("Unpacks a list in the JSON-data and creates a new node containing the list content") self.actionUnpack.triggered.connect(self.unpackList) self.actionJsonCopy = self.detailActions.addAction(QIcon(":/icons/toclip.png"),"Copy JSON to Clipboard") self.actionJsonCopy.setToolTip("Copy the selected JSON-data to the clipboard") self.actionJsonCopy.triggered.connect(self.jsonCopy) self.actionFieldDoc = self.detailActions.addAction(QIcon(":/icons/help.png"),"") self.actionFieldDoc.setToolTip("Open the documentation for the selected item if available.") self.actionFieldDoc.triggered.connect(self.showFieldDoc) #Tree actions self.treeActions = QActionGroup(self.mainWindow) self.actionExpandAll = self.treeActions.addAction(QIcon(":/icons/expand.png"), "Expand nodes") self.actionExpandAll.triggered.connect(self.expandAll) self.actionCollapseAll = self.treeActions.addAction(QIcon(":/icons/collapse.png"), "Collapse nodes") self.actionCollapseAll.triggered.connect(self.collapseAll) #self.actionSelectNodes=self.treeActions.addAction(QIcon(":/icons/collapse.png"),"Select nodes") #self.actionSelectNodes.triggered.connect(self.selectNodes) self.actionClipboard = self.treeActions.addAction(QIcon(":/icons/toclip.png"), "Copy Node(s) to Clipboard") self.actionClipboard.setToolTip("Copy the selected nodes(s) to the clipboard") self.actionClipboard.triggered.connect(self.clipboardNodes)
class Actions(object): def __init__(self, mainWindow): self.mainWindow = mainWindow #Basic actions self.basicActions = QActionGroup(self.mainWindow) self.actionOpen = self.basicActions.addAction(QIcon(":/icons/save.png"), "Open Database") self.actionOpen.triggered.connect(self.openDB) self.actionNew = self.basicActions.addAction(QIcon(":/icons/new.png"), "New Database") self.actionNew.triggered.connect(self.makeDB) #Database actions self.databaseActions = QActionGroup(self.mainWindow) self.actionExport = self.databaseActions.addAction(QIcon(":/icons/export.png"), "Export Data") self.actionExport.setToolTip("Export selected node(s) and their children to a .csv file. \n If no or all node(s) are selected inside the data-view, a complete export of all data in the DB is performed") self.actionExport.triggered.connect(self.exportNodes) self.actionAdd = self.databaseActions.addAction(QIcon(":/icons/add.png"), "Add Nodes") self.actionAdd.setToolTip("Add new node(s) as a starting point for further data collection") self.actionAdd.triggered.connect(self.addNodes) self.actionDelete = self.databaseActions.addAction(QIcon(":/icons/delete.png"), "Delete Nodes") self.actionDelete.setToolTip("Delete nodes(s) and their children") self.actionDelete.triggered.connect(self.deleteNodes) #Data actions self.dataActions = QActionGroup(self.mainWindow) self.actionQuery = self.dataActions.addAction(QIcon(":/icons/fetch.png"), "Query") self.actionQuery.triggered.connect(self.querySelectedNodes) self.actionTimer = self.dataActions.addAction(QIcon(":/icons/fetch.png"), "Time") self.actionTimer.setToolTip("Time your data collection with a timer. Fetches the data for the selected node(s) in user-defined intervalls") self.actionTimer.triggered.connect(self.setupTimer) self.actionHelp = self.dataActions.addAction(QIcon(":/icons/help.png"), "Help") self.actionHelp.triggered.connect(self.help) self.actionLoadPreset = self.dataActions.addAction(QIcon(":/icons/presets.png"), "Presets") self.actionLoadPreset.triggered.connect(self.loadPreset) self.actionLoadAPIs = self.dataActions.addAction(QIcon(":/icons/apis.png"), "APIs") self.actionLoadAPIs.triggered.connect(self.loadAPIs) self.actionShowColumns = self.dataActions.addAction("Show Columns") self.actionShowColumns.triggered.connect(self.showColumns) self.actionClearColumns = self.dataActions.addAction("Clear Columns") self.actionClearColumns.triggered.connect(self.clearColumns) #Detail actions self.detailActions = QActionGroup(self.mainWindow) self.actionAddColumn = self.detailActions.addAction(QIcon(":/icons/addcolumn.png"),"Add Column") self.actionAddColumn.setToolTip("Add the current JSON-key as a column in the data view") self.actionAddColumn.triggered.connect(self.addColumn) self.actionAddAllolumns = self.detailActions.addAction(QIcon(":/icons/addcolumn.png"),"Add All Columns") self.actionAddAllolumns.setToolTip("Analyzes all selected nodes in the data view and adds all found keys as columns") self.actionAddAllolumns.triggered.connect(self.addAllColumns) self.actionUnpack = self.detailActions.addAction(QIcon(":/icons/unpack.png"),"Unpack List") self.actionUnpack.setToolTip("Unpacks a list in the JSON-data and creates a new node containing the list content") self.actionUnpack.triggered.connect(self.unpackList) self.actionJsonCopy = self.detailActions.addAction(QIcon(":/icons/toclip.png"),"Copy JSON to Clipboard") self.actionJsonCopy.setToolTip("Copy the selected JSON-data to the clipboard") self.actionJsonCopy.triggered.connect(self.jsonCopy) self.actionFieldDoc = self.detailActions.addAction(QIcon(":/icons/help.png"),"") self.actionFieldDoc.setToolTip("Open the documentation for the selected item if available.") self.actionFieldDoc.triggered.connect(self.showFieldDoc) #Tree actions self.treeActions = QActionGroup(self.mainWindow) self.actionExpandAll = self.treeActions.addAction(QIcon(":/icons/expand.png"), "Expand nodes") self.actionExpandAll.triggered.connect(self.expandAll) self.actionCollapseAll = self.treeActions.addAction(QIcon(":/icons/collapse.png"), "Collapse nodes") self.actionCollapseAll.triggered.connect(self.collapseAll) #self.actionSelectNodes=self.treeActions.addAction(QIcon(":/icons/collapse.png"),"Select nodes") #self.actionSelectNodes.triggered.connect(self.selectNodes) self.actionClipboard = self.treeActions.addAction(QIcon(":/icons/toclip.png"), "Copy Node(s) to Clipboard") self.actionClipboard.setToolTip("Copy the selected nodes(s) to the clipboard") self.actionClipboard.triggered.connect(self.clipboardNodes) @Slot() def help(self): self.mainWindow.helpwindow.show() @Slot() def openDB(self): #open a file dialog with a .db filter datadir = self.mainWindow.settings.value("lastpath", os.path.expanduser("~")) datadir = datadir if os.path.exists(datadir) else os.path.expanduser("~") fldg = QFileDialog(caption="Open DB File", directory=datadir, filter="DB files (*.db)") fldg.setFileMode(QFileDialog.ExistingFile) if fldg.exec_(): self.mainWindow.timerWindow.cancelTimer() self.mainWindow.tree.treemodel.clear() self.mainWindow.database.connect(fldg.selectedFiles()[0]) self.mainWindow.settings.setValue("lastpath", fldg.selectedFiles()[0]) self.mainWindow.updateUI() self.mainWindow.tree.loadData(self.mainWindow.database) self.mainWindow.actions.actionShowColumns.trigger() @Slot() def openDBFolder(self): path = self.mainWindow.settings.value("lastpath",None) if (path is not None) and (os.path.exists(path)): if platform.system() == "Windows": os.startfile(path) elif platform.system() == "Darwin": subprocess.Popen(["open", path]) else: subprocess.Popen(["xdg-open", path]) @Slot() def makeDB(self): #same as openDB-Slot, but now for creating a new one on the file system datadir = self.mainWindow.settings.value("lastpath", os.path.expanduser("~")) datadir = datadir if os.path.exists(datadir) else os.path.expanduser("~") fldg = QFileDialog(caption="Save DB File", directory=datadir, filter="DB files (*.db)") fldg.setAcceptMode(QFileDialog.AcceptSave) fldg.setDefaultSuffix("db") if fldg.exec_(): self.mainWindow.timerWindow.cancelTimer() self.mainWindow.tree.treemodel.clear() self.mainWindow.database.createconnect(fldg.selectedFiles()[0]) self.mainWindow.settings.setValue("lastpath", fldg.selectedFiles()[0]) self.mainWindow.updateUI() @Slot() def deleteNodes(self): reply = QMessageBox.question(self.mainWindow, 'Delete Nodes', "Are you sure to delete all selected nodes?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply != QMessageBox.Yes: return progress = ProgressBar("Deleting data...", self.mainWindow) try: todo = self.mainWindow.tree.selectedIndexesAndChildren(True) progress.setMaximum(len(todo)) for index in todo: progress.step() self.mainWindow.tree.treemodel.deleteNode(index, delaycommit=True) if progress.wasCanceled: break finally: # commit the operation on the db-layer afterwards (delaycommit is True) self.mainWindow.tree.treemodel.commitNewNodes() progress.close() @Slot() def clipboardNodes(self): progress = ProgressBar("Copy to clipboard", self.mainWindow) indexes = self.mainWindow.tree.selectionModel().selectedRows() progress.setMaximum(len(indexes)) output = io.StringIO() try: writer = csv.writer(output, delimiter='\t', quotechar='"', quoting=csv.QUOTE_ALL, doublequote=True, lineterminator='\r\n') #headers row = [str(val) for val in self.mainWindow.tree.treemodel.getRowHeader()] writer.writerow(row) #rows for no in range(len(indexes)): if progress.wasCanceled: break row = [str(val) for val in self.mainWindow.tree.treemodel.getRowData(indexes[no])] writer.writerow(row) progress.step() clipboard = QApplication.clipboard() clipboard.setText(output.getvalue()) finally: output.close() progress.close() @Slot() def exportNodes(self): fldg = ExportFileDialog(self.mainWindow, filter ="CSV Files (*.csv)") @Slot() def addNodes(self): if not self.mainWindow.database.connected: return False # makes the user add a new facebook object into the db dialog = QDialog(self.mainWindow) dialog.setWindowTitle("Add Nodes") layout = QVBoxLayout() label = QLabel("<b>Object IDs (one ID per line):</b>") layout.addWidget(label) input = QPlainTextEdit() input.setMinimumWidth(500) input.LineWrapMode = QPlainTextEdit.NoWrap #input.acceptRichText=False input.setFocus() layout.addWidget(input) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) layout.addWidget(buttons) dialog.setLayout(layout) def createNodes(): newnodes = [node.strip() for node in input.toPlainText().splitlines()] self.mainWindow.tree.treemodel.addNodes(newnodes) self.mainWindow.tree.selectLastRow() dialog.close() def close(): dialog.close() #connect the nested functions above to the dialog-buttons buttons.accepted.connect(createNodes) buttons.rejected.connect(close) dialog.exec_() @Slot() def showColumns(self): self.mainWindow.tree.treemodel.setCustomColumns(self.mainWindow.fieldList.toPlainText().splitlines()) @Slot() def clearColumns(self): self.mainWindow.fieldList.clear() self.mainWindow.tree.treemodel.setCustomColumns([]) @Slot() def addColumn(self): key = self.mainWindow.detailTree.selectedKey() if key != '': self.mainWindow.fieldList.append(key) self.mainWindow.tree.treemodel.setCustomColumns(self.mainWindow.fieldList.toPlainText().splitlines()) @Slot() def addAllColumns(self): progress = ProgressBar("Analyzing data...", self.mainWindow) columns = self.mainWindow.fieldList.toPlainText().splitlines() try: indexes = self.mainWindow.tree.selectedIndexesAndChildren() progress.setMaximum(len(indexes)) for no in range(len(indexes)): progress.step() item = indexes[no].internalPointer() columns.extend([key for key in recursiveIterKeys(item.data['response']) if not key in columns]) if progress.wasCanceled: break finally: self.mainWindow.fieldList.setPlainText("\n".join(columns)) self.mainWindow.tree.treemodel.setCustomColumns(columns) progress.close() @Slot() def loadPreset(self): self.mainWindow.presetWindow.showPresets() @Slot() def loadAPIs(self): self.mainWindow.apiWindow.showWindow() @Slot() def jsonCopy(self): self.mainWindow.detailTree.copyToClipboard() @Slot() def unpackList(self): try: key = self.mainWindow.detailTree.selectedKey() if key == '': return False selected = self.mainWindow.tree.selectionModel().selectedRows() for item in selected: if not item.isValid(): continue treenode = item.internalPointer() treenode.unpackList(key) except Exception as e: self.mainWindow.logmessage(e) @Slot() def showFieldDoc(self): tree = self.mainWindow.detailTree key = tree.selectedKey() if key == '': return False key = tree.treemodel.fieldprefix + key if tree.treemodel.itemtype is not None: self.mainWindow.apiWindow.showDoc(tree.treemodel.module, tree.treemodel.basepath, tree.treemodel.path, key) @Slot() def expandAll(self): self.mainWindow.tree.expandAll() @Slot() def collapseAll(self): self.mainWindow.tree.collapseAll() @Slot() def selectNodes(self): self.mainWindow.selectNodesWindow.show() def queryNodes(self, indexes=None, apimodule=False, options=False): if not self.actionQuery.isEnabled() or not ((self.mainWindow.tree.selectedCount() > 0) or (indexes is not None)): return (False) #Show progress window progress = ProgressBar("Fetching Data", parent=self.mainWindow) try: #Get global options globaloptions = {} globaloptions['threads'] = self.mainWindow.threadsEdit.value() globaloptions['speed'] = self.mainWindow.speedEdit.value() globaloptions['errors'] = self.mainWindow.errorEdit.value() globaloptions['expand'] = self.mainWindow.autoexpandCheckbox.isChecked() globaloptions['logrequests'] = self.mainWindow.logCheckbox.isChecked() globaloptions['saveheaders'] = self.mainWindow.headersCheckbox.isChecked() objecttypes = self.mainWindow.typesEdit.text().replace(' ','').split(',') level = self.mainWindow.levelEdit.value() - 1 #Get selected nodes if indexes is None: indexes = self.mainWindow.tree.selectedIndexesAndChildren(False, {'level': level, 'objecttype':objecttypes}) if (len(indexes) == 0): return (False) #Update progress window self.mainWindow.logmessage("Start fetching data for {} node(s).".format(len(indexes))) progress.setMaximum(len(indexes)) self.mainWindow.tree.treemodel.nodecounter = 0 #Init status messages statuscount = {} errorcount = 0 ratelimitcount = 0 allowedstatus = ['fetched (200)','downloaded (200)','fetched (202)','stream'] #,'error (400)' if apimodule == False: apimodule = self.mainWindow.RequestTabs.currentWidget() if options == False: options = apimodule.getOptions() options.update(globaloptions) try: #Spawn Threadpool threadpool = ApiThreadPool(apimodule) threadpool.spawnThreads(options.get("threads", 1)) #Init input Queue indexes = deque(indexes) #Process Logging/Input/Output Queue while True: try: #Logging (sync logs in threads with main thread) msg = threadpool.getLogMessage() if msg is not None: self.mainWindow.logmessage(msg) #Jobs in if (len(indexes) > 0): index = indexes.popleft() if index.isValid(): treenode = index.internalPointer() job = {'nodeindex': index, 'nodedata': deepcopy(treenode.data), 'options': deepcopy(options)} threadpool.addJob(job) if len(indexes) == 0: threadpool.applyJobs() progress.setRemaining(threadpool.getJobCount()) progress.resetRate() #Jobs out job = threadpool.getJob() #-Finished all nodes (sentinel)... if job is None: break #-Finished one node... elif 'progress' in job: progresskey = 'nodeprogress' + str(job.get('threadnumber', '')) # Update single progress if 'current' in job: percent = int((job.get('current',0) * 100.0 / job.get('total',1))) progress.showInfo(progresskey, "{}% of current node processed.".format(percent)) elif 'page' in job: if job.get('page', 0) > 1: progress.showInfo(progresskey, "{} page(s) of current node processed.".format(job.get('page',0))) # Update total progress else: progress.removeInfo(progresskey) if not threadpool.suspended: progress.step() #-Add data... elif 'data' in job and (not progress.wasCanceled): if not job['nodeindex'].isValid(): continue # Add data treeindex = job['nodeindex'] treenode = treeindex.internalPointer() treenode.appendNodes(job['data'], job['options'], job['headers'], True) if options.get('expand',False): self.mainWindow.tree.setExpanded(treeindex,True) # Count status status = job['options'].get('querystatus', 'empty') if not status in statuscount: statuscount[status] = 1 else: statuscount[status] = statuscount[status]+1 # Collect errors for automatic retry if not status in allowedstatus: threadpool.addError(job) errorcount += 1 # Detect rate limit ratelimit = job['options'].get('ratelimit', False) if ratelimit: ratelimitcount += 1 # Clear errors if not threadpool.suspended and (status in allowedstatus) and not ratelimit: threadpool.clearRetry() errorcount = 0 ratelimitcount = 0 # Suspend on error elif (errorcount > (globaloptions['errors']-1)) or (ratelimitcount > 0): threadpool.suspendJobs() if ratelimit: msg = "You reached the rate limit of the API." else: msg = "{} consecutive errors occurred.\nPlease check your settings.".format(errorcount) timeout = 60 * 5 #5 minutes # Adjust progress progress.setRemaining(threadpool.getJobCount() + threadpool.getRetryCount()) progress.showError(msg, timeout, ratelimitcount > 0) self.mainWindow.tree.treemodel.commitNewNodes() # Show info progress.showInfo(status,"{} response(s) with status: {}".format(statuscount[status],status)) progress.showInfo('newnodes',"{} new node(s) created".format(self.mainWindow.tree.treemodel.nodecounter)) progress.showInfo('threads',"{} active thread(s)".format(threadpool.getThreadCount())) progress.setRemaining(threadpool.getJobCount()) # Custom info from modules info = job['options'].get('info', {}) for name, value in info.items(): progress.showInfo(name, value) # Abort elif progress.wasCanceled: progress.showInfo('cancel', "Disconnecting from stream, may take some time.") threadpool.stopJobs() # Retry elif progress.wasResumed: if progress.wasRetried: threadpool.retryJobs() else: threadpool.clearRetry() threadpool.resumeJobs() progress.setRemaining(threadpool.getJobCount()) progress.hideError() # Continue elif not threadpool.suspended: threadpool.resumeJobs() # Finished if not threadpool.hasJobs(): progress.showInfo('cancel', "Work finished, shutting down threads.") threadpool.stopJobs() #-Waiting... progress.computeRate() time.sleep(1.0 / 1000.0) finally: QApplication.processEvents() finally: request_summary = [str(val)+" x "+key for key,val in statuscount.items()] request_summary = ", ".join(request_summary) request_end = "Fetching completed" if not progress.wasCanceled else 'Fetching cancelled by user' self.mainWindow.logmessage("{}, {} new node(s) created. Summary of responses: {}.".format(request_end, self.mainWindow.tree.treemodel.nodecounter,request_summary)) self.mainWindow.tree.treemodel.commitNewNodes() finally: progress.close() @Slot() def querySelectedNodes(self): self.queryNodes() @Slot() def setupTimer(self): #Get data level = self.mainWindow.levelEdit.value() - 1 indexes = self.mainWindow.tree.selectedIndexesAndChildren(True, {'level': level, 'objecttype': ['seed', 'data', 'unpacked']}) module = self.mainWindow.RequestTabs.currentWidget() options = module.getOptions() #show timer window self.mainWindow.timerWindow.setupTimer( {'indexes': indexes, 'nodecount': len(indexes), 'module': module, 'options': options}) @Slot() def timerStarted(self, time): self.mainWindow.timerStatus.setStyleSheet("QLabel {color:red;}") self.mainWindow.timerStatus.setText("Timer will be fired at " + time.toString("d MMM yyyy - hh:mm") + " ") @Slot() def timerStopped(self): self.mainWindow.timerStatus.setStyleSheet("QLabel {color:black;}") self.mainWindow.timerStatus.setText("Timer stopped ") @Slot() def timerCountdown(self, countdown): self.mainWindow.timerStatus.setStyleSheet("QLabel {color:red;}") self.mainWindow.timerStatus.setText("Timer will be fired in " + str(countdown) + " seconds ") @Slot() def timerFired(self, data): self.mainWindow.timerStatus.setText("Timer fired ") self.mainWindow.timerStatus.setStyleSheet("QLabel {color:red;}") self.queryNodes(data.get('indexes', []), data.get('module', None), data.get('options', {}).copy()) @Slot() def treeNodeSelected(self, current, selected): #show details self.mainWindow.detailTree.clear() if current.isValid(): item = current.internalPointer() self.mainWindow.detailTree.showDict(item.data['response'],item.data['querytype'], item.data['queryparams']) #select level level = 0 c = current while c.isValid(): level += 1 c = c.parent() self.mainWindow.levelEdit.setValue(level) #show node count self.mainWindow.selectionStatus.setText(str(len(selected)) + ' node(s) selected ') self.actionQuery.setDisabled(len(selected) == 0)
def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self._csvFilePath = "" self.serialport = serial.Serial() self.receiver_thread = readerThread(self) self.receiver_thread.setPort(self.serialport) self._localEcho = None self.setupUi(self) self.setCorner(Qt.TopLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea) font = QtGui.QFont() font.setFamily(EDITOR_FONT) font.setPointSize(10) self.txtEdtOutput.setFont(font) self.txtEdtInput.setFont(font) self.quickSendTable.setFont(font) if UI_FONT is not None: font = QtGui.QFont() font.setFamily(UI_FONT) font.setPointSize(9) self.dockWidget_PortConfig.setFont(font) self.dockWidget_SendHex.setFont(font) self.dockWidget_QuickSend.setFont(font) self.setupFlatUi() self.onEnumPorts() icon = QtGui.QIcon(":/icon.ico") self.setWindowIcon(icon) self.actionAbout.setIcon(icon) icon = QtGui.QIcon(":/qt_logo_16.ico") self.actionAbout_Qt.setIcon(icon) self._viewGroup = QActionGroup(self) self._viewGroup.addAction(self.actionAscii) self._viewGroup.addAction(self.actionHex_lowercase) self._viewGroup.addAction(self.actionHEX_UPPERCASE) self._viewGroup.setExclusive(True) # bind events self.actionOpen_Cmd_File.triggered.connect(self.openCSV) self.actionSave_Log.triggered.connect(self.onSaveLog) self.actionExit.triggered.connect(self.onExit) self.actionOpen.triggered.connect(self.openPort) self.actionClose.triggered.connect(self.closePort) self.actionPort_Config_Panel.triggered.connect(self.onTogglePrtCfgPnl) self.actionQuick_Send_Panel.triggered.connect(self.onToggleQckSndPnl) self.actionSend_Hex_Panel.triggered.connect(self.onToggleHexPnl) self.dockWidget_PortConfig.visibilityChanged.connect(self.onVisiblePrtCfgPnl) self.dockWidget_QuickSend.visibilityChanged.connect(self.onVisibleQckSndPnl) self.dockWidget_SendHex.visibilityChanged.connect(self.onVisibleHexPnl) self.actionLocal_Echo.triggered.connect(self.onLocalEcho) self.actionAlways_On_Top.triggered.connect(self.onAlwaysOnTop) self.actionAscii.triggered.connect(self.onViewChanged) self.actionHex_lowercase.triggered.connect(self.onViewChanged) self.actionHEX_UPPERCASE.triggered.connect(self.onViewChanged) self.actionAbout.triggered.connect(self.onAbout) self.actionAbout_Qt.triggered.connect(self.onAboutQt) self.btnOpen.clicked.connect(self.onOpen) self.btnClear.clicked.connect(self.onClear) self.btnSaveLog.clicked.connect(self.onSaveLog) self.btnEnumPorts.clicked.connect(self.onEnumPorts) self.btnSendHex.clicked.connect(self.sendHex) self.receiver_thread.read.connect(self.receive) self.receiver_thread.exception.connect(self.readerExcept) self._signalMap = QSignalMapper(self) self._signalMap.mapped[int].connect(self.tableClick) # initial action self.actionHEX_UPPERCASE.setChecked(True) self.receiver_thread.setViewMode(VIEWMODE_HEX_UPPERCASE) self.initQuickSend() self.restoreLayout() self.moveScreenCenter() self.syncMenu() if self.isMaximized(): self.setMaximizeButton("restore") else: self.setMaximizeButton("maximize") self.LoadSettings()
class MainWindow(QMainWindow, Ui_MainWindow): """docstring for MainWindow.""" def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self._csvFilePath = "" self.serialport = serial.Serial() self.receiver_thread = readerThread(self) self.receiver_thread.setPort(self.serialport) self._localEcho = None self.setupUi(self) self.setCorner(Qt.TopLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea) font = QtGui.QFont() font.setFamily(EDITOR_FONT) font.setPointSize(10) self.txtEdtOutput.setFont(font) self.txtEdtInput.setFont(font) self.quickSendTable.setFont(font) if UI_FONT is not None: font = QtGui.QFont() font.setFamily(UI_FONT) font.setPointSize(9) self.dockWidget_PortConfig.setFont(font) self.dockWidget_SendHex.setFont(font) self.dockWidget_QuickSend.setFont(font) self.setupFlatUi() self.onEnumPorts() icon = QtGui.QIcon(":/icon.ico") self.setWindowIcon(icon) self.actionAbout.setIcon(icon) icon = QtGui.QIcon(":/qt_logo_16.ico") self.actionAbout_Qt.setIcon(icon) self._viewGroup = QActionGroup(self) self._viewGroup.addAction(self.actionAscii) self._viewGroup.addAction(self.actionHex_lowercase) self._viewGroup.addAction(self.actionHEX_UPPERCASE) self._viewGroup.setExclusive(True) # bind events self.actionOpen_Cmd_File.triggered.connect(self.openCSV) self.actionSave_Log.triggered.connect(self.onSaveLog) self.actionExit.triggered.connect(self.onExit) self.actionOpen.triggered.connect(self.openPort) self.actionClose.triggered.connect(self.closePort) self.actionPort_Config_Panel.triggered.connect(self.onTogglePrtCfgPnl) self.actionQuick_Send_Panel.triggered.connect(self.onToggleQckSndPnl) self.actionSend_Hex_Panel.triggered.connect(self.onToggleHexPnl) self.dockWidget_PortConfig.visibilityChanged.connect(self.onVisiblePrtCfgPnl) self.dockWidget_QuickSend.visibilityChanged.connect(self.onVisibleQckSndPnl) self.dockWidget_SendHex.visibilityChanged.connect(self.onVisibleHexPnl) self.actionLocal_Echo.triggered.connect(self.onLocalEcho) self.actionAlways_On_Top.triggered.connect(self.onAlwaysOnTop) self.actionAscii.triggered.connect(self.onViewChanged) self.actionHex_lowercase.triggered.connect(self.onViewChanged) self.actionHEX_UPPERCASE.triggered.connect(self.onViewChanged) self.actionAbout.triggered.connect(self.onAbout) self.actionAbout_Qt.triggered.connect(self.onAboutQt) self.btnOpen.clicked.connect(self.onOpen) self.btnClear.clicked.connect(self.onClear) self.btnSaveLog.clicked.connect(self.onSaveLog) self.btnEnumPorts.clicked.connect(self.onEnumPorts) self.btnSendHex.clicked.connect(self.sendHex) self.receiver_thread.read.connect(self.receive) self.receiver_thread.exception.connect(self.readerExcept) self._signalMap = QSignalMapper(self) self._signalMap.mapped[int].connect(self.tableClick) # initial action self.actionHEX_UPPERCASE.setChecked(True) self.receiver_thread.setViewMode(VIEWMODE_HEX_UPPERCASE) self.initQuickSend() self.restoreLayout() self.moveScreenCenter() self.syncMenu() if self.isMaximized(): self.setMaximizeButton("restore") else: self.setMaximizeButton("maximize") self.LoadSettings() def setupFlatUi(self): self._dragPos = self.pos() self._isDragging = False self.setMouseTracking(True) self.setWindowFlags(Qt.FramelessWindowHint) self.setStyleSheet(""" QWidget { background-color:#99d9ea; /*background-image: url(:/background.png);*/ outline: 1px solid #0057ff; } QLabel { color:#202020; font-size:13px; font-family:Century; } QComboBox { color:#202020; font-size:13px; font-family:Century Schoolbook; } QComboBox { border: none; padding: 1px 18px 1px 3px; } QComboBox:editable { background: white; } QComboBox:!editable, QComboBox::drop-down:editable { background: #62c7e0; } QComboBox:!editable:hover, QComboBox::drop-down:editable:hover { background: #c7eaf3; } QComboBox:!editable:pressed, QComboBox::drop-down:editable:pressed { background: #35b6d7; } QComboBox:on { padding-top: 3px; padding-left: 4px; } QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 16px; border: none; } QComboBox::down-arrow { image: url(:/downarrow.png); } QComboBox::down-arrow:on { image: url(:/uparrow.png); } QGroupBox { color:#202020; font-size:12px; font-family:Century Schoolbook; border: 1px solid gray; margin-top: 15px; } QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; left:5px; top:3px; } QCheckBox { color:#202020; spacing: 5px; font-size:12px; font-family:Century Schoolbook; } QScrollBar:horizontal { background-color:#99d9ea; border: none; height: 15px; margin: 0px 20px 0 20px; } QScrollBar::handle:horizontal { background: #61b9e1; min-width: 20px; } QScrollBar::add-line:horizontal { image: url(:/rightarrow.png); border: none; background: #7ecfe4; width: 20px; subcontrol-position: right; subcontrol-origin: margin; } QScrollBar::sub-line:horizontal { image: url(:/leftarrow.png); border: none; background: #7ecfe4; width: 20px; subcontrol-position: left; subcontrol-origin: margin; } QScrollBar:vertical { background-color:#99d9ea; border: none; width: 15px; margin: 20px 0px 20px 0px; } QScrollBar::handle::vertical { background: #61b9e1; min-height: 20px; } QScrollBar::add-line::vertical { image: url(:/downarrow.png); border: none; background: #7ecfe4; height: 20px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar::sub-line::vertical { image: url(:/uparrow.png); border: none; background: #7ecfe4; height: 20px; subcontrol-position: top; subcontrol-origin: margin; } QTableView { background-color: white; /*selection-background-color: #FF92BB;*/ border: 1px solid #eeeeee; color: #2f2f2f; } QTableView::focus { /*border: 1px solid #2a7fff;*/ } QTableView QTableCornerButton::section { border: none; border-right: 1px solid #eeeeee; border-bottom: 1px solid #eeeeee; background-color: #8ae6d2; } QTableView QWidget { background-color: white; } QTableView::item:focus { border: 1px red; background-color: transparent; color: #2f2f2f; } QHeaderView::section { border: none; border-right: 1px solid #eeeeee; border-bottom: 1px solid #eeeeee; padding-left: 2px; padding-right: 2px; color: #444444; background-color: #8ae6d2; } QTextEdit { background-color:white; color:#2f2f2f; border: 1px solid white; } QTextEdit::focus { border: 1px solid #2a7fff; } QPushButton { background-color:#30a7b8; border:none; color:#ffffff; font-size:14px; font-family:Century Schoolbook; } QPushButton:hover { background-color:#51c0d1; } QPushButton:pressed { background-color:#3a9ecc; } QMenuBar { color: #2f2f2f; } QMenuBar::item { background-color: transparent; margin: 8px 0px 0px 0px; padding: 1px 8px 1px 8px; height: 15px; } QMenuBar::item:selected { background: #51c0d1; } QMenuBar::item:pressed { } QMenu { color: #2f2f2f; } QMenu { margin: 2px; } QMenu::item { padding: 2px 25px 2px 21px; border: 1px solid transparent; } QMenu::item:selected { background: #51c0d1; } QMenu::icon { background: transparent; border: 2px inset transparent; } QDockWidget { font-size:13px; font-family:Century; color: #202020; titlebar-close-icon: none; titlebar-normal-icon: none; } QDockWidget::title { margin: 0; padding: 2px; subcontrol-origin: content; subcontrol-position: right top; text-align: left; background: #67baed; } QDockWidget::float-button { max-width: 12px; max-height: 12px; background-color:transparent; border:none; image: url(:/restore_inactive.png); } QDockWidget::float-button:hover { background-color:#227582; image: url(:/restore_active.png); } QDockWidget::float-button:pressed { padding: 0; background-color:#14464e; image: url(:/restore_active.png); } QDockWidget::close-button { max-width: 12px; max-height: 12px; background-color:transparent; border:none; image: url(:/close_inactive.png); } QDockWidget::close-button:hover { background-color:#ea5e00; image: url(:/close_active.png); } QDockWidget::close-button:pressed { background-color:#994005; image: url(:/close_active.png); padding: 0; } """) self.dockWidgetContents.setStyleSheet(""" QPushButton { min-height:23px; } """) self.dockWidget_QuickSend.setStyleSheet(""" QPushButton { background-color:#27b798; font-family:Consolas; font-size:12px; min-width:46px; } QPushButton:hover { background-color:#3bd5b4; } QPushButton:pressed { background-color:#1d8770; } """) self.dockWidgetContents_2.setStyleSheet(""" QPushButton { min-height:23px; min-width:50px; } """) w = self.frameGeometry().width() self._minBtn = QPushButton(self) self._minBtn.setGeometry(w-103,0,28,24) self._minBtn.clicked.connect(self.onMinimize) self._minBtn.setStyleSheet(""" QPushButton { background-color:transparent; border:none; outline: none; image: url(:/minimize_inactive.png); } QPushButton:hover { background-color:#227582; image: url(:/minimize_active.png); } QPushButton:pressed { background-color:#14464e; image: url(:/minimize_active.png); } """) self._maxBtn = QPushButton(self) self._maxBtn.setGeometry(w-74,0,28,24) self._maxBtn.clicked.connect(self.onMaximize) self.setMaximizeButton("maximize") self._closeBtn = QPushButton(self) self._closeBtn.setGeometry(w-45,0,36,24) self._closeBtn.clicked.connect(self.onExit) self._closeBtn.setStyleSheet(""" QPushButton { background-color:transparent; border:none; outline: none; image: url(:/close_inactive.png); } QPushButton:hover { background-color:#ea5e00; image: url(:/close_active.png); } QPushButton:pressed { background-color:#994005; image: url(:/close_active.png); } """) def resizeEvent(self, event): w = event.size().width() self._minBtn.move(w-103,0) self._maxBtn.move(w-74,0) self._closeBtn.move(w-45,0) def onMinimize(self): self.showMinimized() def isMaximized(self): return ((self.windowState() == Qt.WindowMaximized)) def onMaximize(self): if self.isMaximized(): self.showNormal() self.setMaximizeButton("maximize") else: self.showMaximized() self.setMaximizeButton("restore") def setMaximizeButton(self, style): if "maximize" == style: self._maxBtn.setStyleSheet(""" QPushButton { background-color:transparent; border:none; outline: none; image: url(:/maximize_inactive.png); } QPushButton:hover { background-color:#227582; image: url(:/maximize_active.png); } QPushButton:pressed { background-color:#14464e; image: url(:/maximize_active.png); } """) elif "restore" == style: self._maxBtn.setStyleSheet(""" QPushButton { background-color:transparent; border:none; outline: none; image: url(:/restore_inactive.png); } QPushButton:hover { background-color:#227582; image: url(:/restore_active.png); } QPushButton:pressed { background-color:#14464e; image: url(:/restore_active.png); } """) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self._isDragging = True self._dragPos = event.globalPos() - self.pos() event.accept() def mouseMoveEvent(self, event): if event.buttons() == Qt.LeftButton and self._isDragging and not self.isMaximized(): self.move(event.globalPos() - self._dragPos) event.accept() def mouseReleaseEvent(self, event): self._isDragging = False event.accept() def SaveSettings(self): root = ET.Element("MyTerm") GUISettings = ET.SubElement(root, "GUISettings") PortCfg = ET.SubElement(GUISettings, "PortConfig") ET.SubElement(PortCfg, "port").text = self.cmbPort.currentText() ET.SubElement(PortCfg, "baudrate").text = self.cmbBaudRate.currentText() ET.SubElement(PortCfg, "databits").text = self.cmbDataBits.currentText() ET.SubElement(PortCfg, "parity").text = self.cmbParity.currentText() ET.SubElement(PortCfg, "stopbits").text = self.cmbStopBits.currentText() ET.SubElement(PortCfg, "rtscts").text = self.chkRTSCTS.isChecked() and "on" or "off" ET.SubElement(PortCfg, "xonxoff").text = self.chkXonXoff.isChecked() and "on" or "off" View = ET.SubElement(GUISettings, "View") ET.SubElement(View, "LocalEcho").text = self.actionLocal_Echo.isChecked() and "on" or "off" ET.SubElement(View, "ReceiveView").text = self._viewGroup.checkedAction().text() with open(get_config_path('settings.xml'), 'w') as f: f.write('<?xml version="1.0" encoding="UTF-8"?>\n') f.write(ET.tostring(root, encoding='utf-8', pretty_print=True).decode("utf-8")) def LoadSettings(self): if os.path.isfile(get_config_path("settings.xml")): with open(get_config_path("settings.xml"), 'r') as f: tree = safeET.parse(f) port = tree.findtext('GUISettings/PortConfig/port', default='') if port != '': self.cmbPort.setCurrentText(port) baudrate = tree.findtext('GUISettings/PortConfig/baudrate', default='38400') if baudrate != '': self.cmbBaudRate.setCurrentText(baudrate) databits = tree.findtext('GUISettings/PortConfig/databits', default='8') id = self.cmbDataBits.findText(databits) if id >= 0: self.cmbDataBits.setCurrentIndex(id) parity = tree.findtext('GUISettings/PortConfig/parity', default='None') id = self.cmbParity.findText(parity) if id >= 0: self.cmbParity.setCurrentIndex(id) stopbits = tree.findtext('GUISettings/PortConfig/stopbits', default='1') id = self.cmbStopBits.findText(stopbits) if id >= 0: self.cmbStopBits.setCurrentIndex(id) rtscts = tree.findtext('GUISettings/PortConfig/rtscts', default='off') if 'on' == rtscts: self.chkRTSCTS.setChecked(True) else: self.chkRTSCTS.setChecked(False) xonxoff = tree.findtext('GUISettings/PortConfig/xonxoff', default='off') if 'on' == xonxoff: self.chkXonXoff.setChecked(True) else: self.chkXonXoff.setChecked(False) LocalEcho = tree.findtext('GUISettings/View/LocalEcho', default='off') if 'on' == LocalEcho: self.actionLocal_Echo.setChecked(True) self._localEcho = True else: self.actionLocal_Echo.setChecked(False) self._localEcho = False ReceiveView = tree.findtext('GUISettings/View/ReceiveView', default='HEX(UPPERCASE)') if 'Ascii' in ReceiveView: self.actionAscii.setChecked(True) elif 'lowercase' in ReceiveView: self.actionHex_lowercase.setChecked(True) elif 'UPPERCASE' in ReceiveView: self.actionHEX_UPPERCASE.setChecked(True) def closeEvent(self, event): self.saveLayout() self.saveCSV() self.SaveSettings() event.accept() def tableClick(self, row): self.sendTableRow(row) def initQuickSend(self): #self.quickSendTable.horizontalHeader().setDefaultSectionSize(40) #self.quickSendTable.horizontalHeader().setMinimumSectionSize(25) self.quickSendTable.setRowCount(50) self.quickSendTable.setColumnCount(20) for row in range(50): item = QPushButton(str("Send")) item.clicked.connect(self._signalMap.map) self._signalMap.setMapping(item, row) self.quickSendTable.setCellWidget(row, 0, item) self.quickSendTable.setRowHeight(row, 20) if os.path.isfile(get_config_path('QckSndBckup.csv')): self.loadCSV(get_config_path('QckSndBckup.csv')) self.quickSendTable.resizeColumnsToContents() def openCSV(self): fileName = QFileDialog.getOpenFileName(self, "Select a file", os.getcwd(), "CSV Files (*.csv)")[0] if fileName: self.loadCSV(fileName, notifyExcept = True) def saveCSV(self): # scan table rows = self.quickSendTable.rowCount() cols = self.quickSendTable.columnCount() tmp_data = [[self.quickSendTable.item(row, col) is not None and self.quickSendTable.item(row, col).text() or '' for col in range(1, cols)] for row in range(rows)] data = [] # delete trailing blanks for row in tmp_data: for idx, d in enumerate(row[::-1]): if '' != d: break new_row = row[:len(row) - idx] data.append(new_row) #import pprint #pprint.pprint(data, width=120, compact=True) # write to file with open(get_config_path('QckSndBckup.csv'), 'w') as csvfile: csvwriter = csv.writer(csvfile, delimiter=',', lineterminator='\n') csvwriter.writerows(data) def loadCSV(self, path, notifyExcept = False): data = [] set_rows = 0 set_cols = 0 try: with open(path) as csvfile: csvData = csv.reader(csvfile) for row in csvData: data.append(row) set_rows = set_rows + 1 if len(row) > set_cols: set_cols = len(row) except IOError as e: print("({})".format(e)) if notifyExcept: QMessageBox.critical(self, "Open failed", str(e), QMessageBox.Close) return rows = self.quickSendTable.rowCount() cols = self.quickSendTable.columnCount() # clear table for col in range(cols): for row in range(rows): self.quickSendTable.setItem(row, col, QTableWidgetItem("")) self._csvFilePath = path if (cols - 1) < set_cols: # first colume is used by the "send" buttons. cols = set_cols + 10 self.quickSendTable.setColumnCount(cols) if rows < set_rows: rows = set_rows + 20 self.quickSendTable.setRowCount(rows) for row, rowdat in enumerate(data): if len(rowdat) > 0: for col, cell in enumerate(rowdat, 1): self.quickSendTable.setItem(row, col, QTableWidgetItem(str(cell))) self.quickSendTable.resizeColumnsToContents() #self.quickSendTable.resizeRowsToContents() def sendTableRow(self, row): cols = self.quickSendTable.columnCount() try: data = ['0' + self.quickSendTable.item(row, col).text() for col in range(1, cols) if self.quickSendTable.item(row, col) is not None and self.quickSendTable.item(row, col).text() is not ''] except: print("Exception in get table data(row = %d)" % (row + 1)) else: tmp = [d[-2] + d[-1] for d in data if len(d) >= 2] for t in tmp: if not is_hex(t): QMessageBox.critical(self, "Error", "'%s' is not hexadecimal." % (t), QMessageBox.Close) return h = [int(t, 16) for t in tmp] self.transmitHex(h) def sendHex(self): hexStr = self.txtEdtInput.toPlainText() hexStr = ''.join(hexStr.split(" ")) hexarray = [] for i in range(0, len(hexStr), 2): hexarray.append(int(hexStr[i:i+2], 16)) self.transmitHex(hexarray) def readerExcept(self, e): self.closePort() QMessageBox.critical(self, "Read failed", str(e), QMessageBox.Close) def timestamp(self): return datetime.datetime.now().time().isoformat()[:-3] def receive(self, data): self.appendOutputText("\n%s R<-:%s" % (self.timestamp(), data)) def appendOutputText(self, data, color=Qt.black): # the qEditText's "append" methon will add a unnecessary newline. # self.txtEdtOutput.append(data.decode('utf-8')) tc=self.txtEdtOutput.textColor() self.txtEdtOutput.moveCursor(QtGui.QTextCursor.End) self.txtEdtOutput.setTextColor(QtGui.QColor(color)) self.txtEdtOutput.insertPlainText(data) self.txtEdtOutput.moveCursor(QtGui.QTextCursor.End) self.txtEdtOutput.setTextColor(tc) def transmitHex(self, hexarray): if len(hexarray) > 0: byteArray = bytearray(hexarray) if self.serialport.isOpen(): try: self.serialport.write(byteArray) except serial.SerialException as e: print("Exception in transmitHex(%s)" % repr(hexarray)) QMessageBox.critical(self, "Exception in transmitHex", str(e), QMessageBox.Close) else: # self.txCount += len( b ) # self.frame.statusbar.SetStatusText('Tx:%d' % self.txCount, 2) text = ''.join(['%02X ' % i for i in hexarray]) self.appendOutputText("\n%s T->:%s" % (self.timestamp(), text), Qt.blue) def GetPort(self): return self.cmbPort.currentText() def GetDataBits(self): s = self.cmbDataBits.currentText() if s == '5': return serial.FIVEBITS elif s == '6': return serial.SIXBITS elif s == '7': return serial.SEVENBITS elif s == '8': return serial.EIGHTBITS def GetParity(self): s = self.cmbParity.currentText() if s == 'None': return serial.PARITY_NONE elif s == 'Even': return serial.PARITY_EVEN elif s == 'Odd': return serial.PARITY_ODD elif s == 'Mark': return serial.PARITY_MARK elif s == 'Space': return serial.PARITY_SPACE def GetStopBits(self): s = self.cmbStopBits.currentText() if s == '1': return serial.STOPBITS_ONE elif s == '1.5': return serial.STOPBITS_ONE_POINT_FIVE elif s == '2': return serial.STOPBITS_TWO def openPort(self): if self.serialport.isOpen(): return _port = self.GetPort() if '' == _port: QMessageBox.information(self, "Invalid parameters", "Port is empty.") return _baudrate = self.cmbBaudRate.currentText() if '' == _baudrate: QMessageBox.information(self, "Invalid parameters", "Baudrate is empty.") return self.serialport.port = _port self.serialport.baudrate = _baudrate self.serialport.bytesize = self.GetDataBits() self.serialport.stopbits = self.GetStopBits() self.serialport.parity = self.GetParity() self.serialport.rtscts = self.chkRTSCTS.isChecked() self.serialport.xonxoff = self.chkXonXoff.isChecked() # self.serialport.timeout = THREAD_TIMEOUT # self.serialport.writeTimeout = SERIAL_WRITE_TIMEOUT try: self.serialport.open() except serial.SerialException as e: QMessageBox.critical(self, "Could not open serial port", str(e), QMessageBox.Close) else: self._start_reader() self.setWindowTitle("%s on %s [%s, %s%s%s%s%s]" % ( appInfo.title, self.serialport.portstr, self.serialport.baudrate, self.serialport.bytesize, self.serialport.parity, self.serialport.stopbits, self.serialport.rtscts and ' RTS/CTS' or '', self.serialport.xonxoff and ' Xon/Xoff' or '', ) ) pal = self.btnOpen.palette() pal.setColor(QtGui.QPalette.Button, QtGui.QColor(0,0xff,0x7f)) self.btnOpen.setAutoFillBackground(True) self.btnOpen.setPalette(pal) self.btnOpen.setText('Close') self.btnOpen.update() def closePort(self): if self.serialport.isOpen(): self._stop_reader() self.serialport.close() self.setWindowTitle(appInfo.title) pal = self.btnOpen.style().standardPalette() self.btnOpen.setAutoFillBackground(True) self.btnOpen.setPalette(pal) self.btnOpen.setText('Open') self.btnOpen.update() def _start_reader(self): """Start reader thread""" self.receiver_thread.start() def _stop_reader(self): """Stop reader thread only, wait for clean exit of thread""" self.receiver_thread.join() def onTogglePrtCfgPnl(self): if self.actionPort_Config_Panel.isChecked(): self.dockWidget_PortConfig.show() else: self.dockWidget_PortConfig.hide() def onToggleQckSndPnl(self): if self.actionQuick_Send_Panel.isChecked(): self.dockWidget_QuickSend.show() else: self.dockWidget_QuickSend.hide() def onToggleHexPnl(self): if self.actionSend_Hex_Panel.isChecked(): self.dockWidget_SendHex.show() else: self.dockWidget_SendHex.hide() def onVisiblePrtCfgPnl(self, visible): self.actionPort_Config_Panel.setChecked(visible) def onVisibleQckSndPnl(self, visible): self.actionQuick_Send_Panel.setChecked(visible) def onVisibleHexPnl(self, visible): self.actionSend_Hex_Panel.setChecked(visible) def onLocalEcho(self): self._localEcho = self.actionLocal_Echo.isChecked() def onAlwaysOnTop(self): if self.actionAlways_On_Top.isChecked(): style = self.windowFlags() self.setWindowFlags(style|Qt.WindowStaysOnTopHint) self.show() else: style = self.windowFlags() self.setWindowFlags(style & ~Qt.WindowStaysOnTopHint) self.show() def onOpen(self): if self.serialport.isOpen(): self.closePort() else: self.openPort() def onClear(self): self.txtEdtOutput.clear() def onSaveLog(self): fileName = QFileDialog.getSaveFileName(self, "Save as", os.getcwd(), "Log files (*.log);;Text files (*.txt);;All files (*.*)")[0] if fileName: import codecs f = codecs.open(fileName, 'w', 'utf-8') f.write(self.txtEdtOutput.toPlainText()) f.close() def moveScreenCenter(self): w = self.frameGeometry().width() h = self.frameGeometry().height() desktop = QDesktopWidget() screenW = desktop.screen().width() screenH = desktop.screen().height() self.setGeometry((screenW-w)/2, (screenH-h)/2, w, h) def onEnumPorts(self): for p in enum_ports(): self.cmbPort.addItem(p) # self.cmbPort.update() def onAbout(self): q = QWidget() icon = QtGui.QIcon(":/icon.ico") q.setWindowIcon(icon) QMessageBox.about(q, "About MyTerm", appInfo.aboutme) def onAboutQt(self): QMessageBox.aboutQt(None) def onExit(self): if self.serialport.isOpen(): self.closePort() self.close() def restoreLayout(self): if os.path.isfile(get_config_path("layout.dat")): try: f=open(get_config_path("layout.dat"), 'rb') geometry, state=pickle.load(f) self.restoreGeometry(geometry) self.restoreState(state) except Exception as e: print("Exception on restoreLayout, {}".format(e)) else: try: f=QFile(':/default_layout.dat') f.open(QIODevice.ReadOnly) geometry, state=pickle.loads(f.readAll()) self.restoreGeometry(geometry) self.restoreState(state) except Exception as e: print("Exception on restoreLayout, {}".format(e)) def saveLayout(self): with open(get_config_path("layout.dat"), 'wb') as f: pickle.dump((self.saveGeometry(), self.saveState()), f) def syncMenu(self): self.actionPort_Config_Panel.setChecked(not self.dockWidget_PortConfig.isHidden()) self.actionQuick_Send_Panel.setChecked(not self.dockWidget_QuickSend.isHidden()) self.actionSend_Hex_Panel.setChecked(not self.dockWidget_SendHex.isHidden()) def onViewChanged(self): checked = self._viewGroup.checkedAction() if checked is None: self.actionHEX_UPPERCASE.setChecked(True) self.receiver_thread.setViewMode(VIEWMODE_HEX_UPPERCASE) else: if 'Ascii' in checked.text(): self.receiver_thread.setViewMode(VIEWMODE_ASCII) elif 'lowercase' in checked.text(): self.receiver_thread.setViewMode(VIEWMODE_HEX_LOWERCASE) elif 'UPPERCASE' in checked.text(): self.receiver_thread.setViewMode(VIEWMODE_HEX_UPPERCASE)