class InstallPage(QWidget): """Settings page for the installed plugins""" def __init__(self, parent, repo): """QWidget Dictionary -> Void Consumes the parent and the repository dictionary and sets up the install page in the settings area""" QWidget.__init__(self, parent) self._userPlugins = helper.getPlugins() self._repo = repo # Add a scrollArea that if they are more plugins that fit into the # settings page scrollArea = QScrollArea(self) scrollArea.setWidgetResizable(True) scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) baseLayout = QVBoxLayout() baseLayout.setAlignment(Qt.AlignTop) self.setLayout(baseLayout) baseWidget = QWidget() scrollArea.setWidget(baseWidget) baseLayout.addWidget(scrollArea) self._vbox = QVBoxLayout() baseWidget.setLayout(self._vbox) def update(self, userPlugins): """ListOfUserpluginEntry -> Void Consume a list of UserpluginEntry and repopulates the install page""" for i in reversed(range(self._vbox.count())): try: self._vbox.itemAt(i).widget().setParent(None) except AttributeError as e: qWarning("Can't call setParent of None type") labelText = "<h2>Install Plugins</h2>" if (len(self._repo["plugins"]) < 1): labelText += "<p>It seems we could not load the plugin repository.</p>" labelText += "<p style='color:red'>Make shure your internet connection is working and restart Enki.</p><p></p>" self._vbox.addWidget(QLabel(labelText)) for entry in self._repo["plugins"]: isInstalled = helper.isPluginInstalled(entry["name"], userPlugins) if isInstalled: self._vbox.addWidget(pluginspage.PluginTitlecard(isInstalled)) else: self._vbox.addWidget(InstallableTitlecard(entry, self)) def addPluginToUserPlugins(self, installableTitlecard): """InstallableTitlecard -> Void Consumes an InstallableTitlecard and insert an PluginTitleCard instead of itself""" index = self._vbox.indexOf(installableTitlecard) name = installableTitlecard.modulename() pluginEntry = helper.initPlugin(name) if pluginEntry: self._userPlugins.append(pluginEntry) self._vbox.insertWidget(index, pluginspage.PluginTitlecard(pluginEntry))
def _create_rule_widget(self, layout: QVBoxLayout, rule: Rule) -> RuleWidget: rule_widget = RuleWidget(rule) rule_widget.register_callbacks( lambda: layout.insertWidget( layout.indexOf(rule_widget), self._create_rule_widget(layout, Rule({"type": "app"}))), lambda: rule_widget.remove_from(layout)) return rule_widget
class QScrollableBox(QWidget): def __init__(self, parent): super(QScrollableBox, self).__init__(parent) mainLayout = QVBoxLayout() mainLayout.setContentsMargins(0, 0, 0, 0) self.scrollArea = QScrollArea(self) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) self.scrollArea.setSizePolicy(sizePolicy) self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.scrollArea.setWidgetResizable(True) mainLayout.addWidget(self.scrollArea) self.setLayout(mainLayout) scrollContents = QWidget() self.m_layout = QVBoxLayout() self.m_layout.setContentsMargins(0, 0, 0, 0) self.m_layout.setSizeConstraint(QLayout.SetNoConstraint) scrollContents.setLayout(self.m_layout) self.scrollArea.setWidget(scrollContents) def addWidget(self, w): if not w: return count = self.m_layout.count() if count > 1: self.m_layout.removeItem(self.m_layout.itemAt(count - 1)) self.m_layout.addWidget(w) w.show() self.m_layout.addStretch() self.scrollArea.update() def removeWidget(self, w): self.m_layout.removeWidget(w) self.scrollArea.update() def insertWidget(self, i, w): self.m_layout.insertWidget(i, w) self.scrollArea.update() def clearWidgets(self): item = self.m_layout.takeAt(0) while item != None: item.widget().deleteLater() self.m_layout.removeItem(item) del item self.scrollArea.update() def indexOf(self, w): return self.m_layout.indexOf(w)
class BasePage(QWidget): finished = pyqtSignal() def __init__(self, parent=None): super().__init__(parent=parent) self._pager = None self.nextEnabled = True self.previousEnabled = True self.layout = QVBoxLayout(self) self.layout.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self.setSizePolicy( QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)) self.setStyleSheet("QLabel {font: 20pt}") self.labels = [] self.picture = None @pyqtSlot() def onEnter(self): pass def setPager(self, pager): self._pager = pager def getButtonHeight(self): if self._pager is not None: return self._pager.getButtonHeight() else: return 100 @pyqtSlot() def onExit(self): pass def getPictureSize(self): s = self.size() - QSize(0, self.getButtonHeight()) for l in self.labels: s -= QSize(0, l.size().height()) return QSize(max(400, s.width()), max(400, s.height())) def resizeEvent(self, event): if self.picture is not None: i = self.layout.indexOf(self.picture) self.layout.removeWidget(self.picture) self.picture.setParent(None) self.picture = QLabel(self) self.picture.setPixmap( self.pixMap.scaled(self.getPictureSize(), Qt.KeepAspectRatio)) self.layout.insertWidget(i, self.picture)
class QImageGrids(QWidget): # signals focusChanged = pyqtSignal(QPixmap) def __init__(self): super().__init__() self.VBoxLayout = QVBoxLayout() self.setLayout(self.VBoxLayout) self.VBoxLayout.addStretch() self.setBackgroundRole(QPalette.Light) self._focusItemIndex = 0 def add(self, imgPath, imgBasePath=None): self.insert(self.VBoxLayout.count() - 1, imgPath, imgBasePath) def insert(self, index, imgPath, imgBasePath=None): imgGrid = QImageGrid(imgPath) if imgBasePath is not None: imgGrid.baseImgPath = imgBasePath self.VBoxLayout.insertWidget(index, imgGrid) self.connectGridSignals(imgGrid) def count(self): return self.VBoxLayout.count() - 1 def connectGridSignals(self, grid): for img in grid.children(): if isinstance(img, QImageLabel): img.clicked.connect(self.imageClicked) def removeFocusedGrid(self): # TODO grid = self.getFocusedGrid( ) # .VBoxLayout.itemAt(self._focusItemIndex) if isinstance(grid, QImageGrid): self.VBoxLayout.removeWidget(grid) grid.deleteLater() try: self.moveGridFocusUp() except MoveGridFocusError: pass finally: newGrid = self.getFocusedGrid() if newGrid is not None: newGrid.reFocus() self.emitFocusChanged() def reloadFocusedGrid(self): index = self._focusItemIndex grid = self.getFocusedGrid() row = grid._focusItemRow col = grid._focusItemColumn baseImgPath = grid.baseImgPath self.removeFocusedGrid() self.getFocusedGrid().clearFocusItem() self.insert(index, baseImgPath) self._focusItemIndex = index self.getFocusedGrid().setFocusItem(row, col) self.emitFocusChanged() def getFocusedGrid(self) -> QImageGrid: imgGridItem = self.VBoxLayout.itemAt(self._focusItemIndex) if imgGridItem is None: return None else: return imgGridItem.widget() @pyqtSlot() def imageClicked(self): # clear the currently selected image oldGrid = self.getFocusedGrid() if oldGrid is not None: oldGrid.clearFocusItem() # focus on the new image imgLabel = self.sender() self._focusItemIndex = self.VBoxLayout.indexOf(imgLabel.parent()) self.getFocusedGrid().setFocusWidget(imgLabel) imgLabel.clicked.connect(self.emitFocusChanged) def emitFocusChanged(self): pixmap = self.getFocusedGrid().getFocusWidget().pixmap() self.focusChanged.emit(pixmap) def moveGridFocusDown(self): # only shift if we're not already at the bottom if not self._focusItemIndex == self.VBoxLayout.count() - 2: self._focusItemIndex += 1 else: raise MoveGridFocusError( self._focusItemIndex, self._focusItemIndex + 1, f'Grid {self._focusItemIndex + 1} does not exist') def moveGridFocusUp(self): # only shift if we're not already at the top if not self._focusItemIndex == 0: self._focusItemIndex -= 1 else: raise MoveGridFocusError(self._focusItemIndex, self._focusItemIndex - 1, 'Grid "-1" does not exist') def moveItemFocusDown(self): grid = self.getFocusedGrid() try: grid.moveFocusDown() except MoveGridItemFocusError as e: try: self.moveGridFocusDown() except MoveGridFocusError: pass else: newGrid = self.getFocusedGrid() newGrid.setFocusItem(0, e.column) grid.clearFocusItem() self.emitFocusChanged() else: self.emitFocusChanged() def moveItemFocusUp(self): grid = self.getFocusedGrid() try: grid.moveFocusUp() except MoveGridItemFocusError as e: try: self.moveGridFocusUp() except MoveGridFocusError: pass else: newGrid = self.getFocusedGrid() newGrid.setFocusItem(newGrid.rows - 1, e.column) grid.clearFocusItem() self.emitFocusChanged() else: self.emitFocusChanged() def moveItemFocusLeft(self): grid = self.getFocusedGrid() try: grid.moveFocusLeft() except MoveGridItemFocusError: pass else: self.emitFocusChanged() def moveItemFocusRight(self): grid = self.getFocusedGrid() try: grid.moveFocusRight() except MoveGridItemFocusError: pass else: self.emitFocusChanged() def moveFocusNext(self): grid = self.getFocusedGrid() try: grid.moveFocusNext() except MoveGridItemFocusError: try: self.moveGridFocusDown() except MoveGridFocusError: pass else: newGrid = self.getFocusedGrid() newGrid.setFocusItem(0, 0) grid.clearFocusItem() self.emitFocusChanged() else: self.emitFocusChanged() def moveFocusPrevious(self): grid = self.getFocusedGrid() try: grid.moveFocusPrevious() except MoveGridItemFocusError: try: self.moveGridFocusUp() except MoveGridFocusError: pass else: newGrid = self.getFocusedGrid() if newGrid.rows % 2 == 0: newGrid.setFocusItem(newGrid.rows - 1, 0) else: newGrid.setFocusItem(newGrid.rows - 1, newGrid.cols - 1) grid.clearFocusItem() self.emitFocusChanged() else: self.emitFocusChanged()
class MainWindow(QWidget): def __init__(self): super(QWidget, self).__init__() self.settings = QSettings() geometry = self.settings.value('mainwindowgeometry', '') if geometry: self.restoreGeometry(geometry) self.vlayout = QVBoxLayout() self.layout = QHBoxLayout() self.left = QWidget() self.left.setLayout(self.vlayout) self.right = QWidget() self.layout.addWidget(self.left) self.layout.addWidget(self.right) self.setLayout(self.layout) self.vlayout.addWidget(QLabel("Choose profile to check:")) self.profilewidget = QComboBox() for p in CLI_PROFILES: self.profilewidget.addItem(p) last_used_profile = self.settings.value("last_used_profile", "") if last_used_profile: self.profilewidget.setCurrentText(last_used_profile) self.vlayout.addWidget(self.profilewidget) self.profilewidget.currentIndexChanged.connect(self.profile_changed) self.vlayout.addWidget(QLabel("Choose checks to run:")) self.checkwidget = CheckCombo(self.profilewidget.currentText()) self.vlayout.addWidget(self.checkwidget) self.vlayout.addWidget(QLabel("Choose level of output:")) self.loglevelwidget = QComboBox() for l in log_levels.keys(): self.loglevelwidget.addItem(l) self.loglevelwidget.setCurrentText("INFO") self.vlayout.addWidget(self.loglevelwidget) self.vlayout.addWidget(DragDropArea(self)) self.progress = QProgressBar(self) self.progress.setMinimum(0) self.progress.setMaximum(100) self.progress.setValue(0) self.vlayout.addWidget(self.progress) self.vlayout.addStretch() def run_fontbakery(self, paths): self.progress.setValue(0) # Setup the worker object and the worker_thread. profilename = self.profilewidget.currentText() loglevel = log_levels[self.loglevelwidget.currentText()] self.settings.setValue('last_used_profile', profilename) print("checked_checks", self.checkwidget.checked_checks()) self.worker = FontbakeryRunner(profilename, [loglevel], paths, checks=self.checkwidget.checked_checks()) self.worker_thread = QThread() self.worker.moveToThread(self.worker_thread) self.worker_thread.started.connect(self.worker.start) self.worker.signalStatus.connect(self.show_results) self.worker.progressStatus.connect(self.update_progress) self.worker_thread.start() def update_progress(self, value): self.progress.setValue(int(value)) def show_results(self, html, md): self.worker_thread.quit() self.layout.removeWidget(self.right) self.right.deleteLater() self.right = ResultsWidget(html, md) self.layout.addWidget(self.right) def profile_changed(self): index = self.vlayout.indexOf(self.checkwidget) self.vlayout.removeWidget(self.checkwidget) self.checkwidget.deleteLater() self.checkwidget = CheckCombo(self.profilewidget.currentText()) self.vlayout.insertWidget(index, self.checkwidget) def closeEvent(self, event): geometry = self.saveGeometry() self.settings.setValue('mainwindowgeometry', geometry)
class QtLayersList(QScrollArea): def __init__(self, layers): super().__init__() self.layers = layers self.setWidgetResizable(True) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scrollWidget = QWidget() self.setWidget(scrollWidget) self.vbox_layout = QVBoxLayout(scrollWidget) self.vbox_layout.addWidget(QtDivider()) self.vbox_layout.addStretch(1) self.centers = [] self.setAcceptDrops(True) self.setToolTip('Layer list') self.layers.changed.added.connect(self._add) self.layers.changed.removed.connect(self._remove) self.layers.changed.reordered.connect(self._reorder) def _add(self, event): """Inserts a layer widget at a specific index """ layer = event.item index = event.index total = len(self.layers) if layer._qt_properties is not None: self.vbox_layout.insertWidget(2*(total - index)-1, layer._qt_properties) self.vbox_layout.insertWidget(2*(total - index), QtDivider()) def _remove(self, event): """Removes a layer widget """ layer = event.item if layer._qt_properties is not None: index = self.vbox_layout.indexOf(layer._qt_properties) divider = self.vbox_layout.itemAt(index+1).widget() self.vbox_layout.removeWidget(layer._qt_properties) layer._qt_properties.deleteLater() layer._qt_properties = None self.vbox_layout.removeWidget(divider) divider.deleteLater() divider = None def _reorder(self, event): """Reorders list of layer widgets by looping through all widgets in list sequentially removing them and inserting them into the correct place in final list. """ total = len(self.layers) for i in range(total): layer = self.layers[i] if layer._qt_properties is not None: index = self.vbox_layout.indexOf(layer._qt_properties) divider = self.vbox_layout.itemAt(index+1).widget() self.vbox_layout.removeWidget(layer._qt_properties) self.vbox_layout.removeWidget(divider) self.vbox_layout.insertWidget(2*(total - i)-1, layer._qt_properties) self.vbox_layout.insertWidget(2*(total - i), divider) def mouseReleaseEvent(self, event): """Unselects all layer widgets """ self.layers.unselect_all() def dragLeaveEvent(self, event): """Unselects layer dividers """ event.ignore() for i in range(0, self.vbox_layout.count(), 2): self.vbox_layout.itemAt(i).widget().setSelected(False) def dragEnterEvent(self, event): event.accept() divs = [] for i in range(0, self.vbox_layout.count(), 2): widget = self.vbox_layout.itemAt(i).widget() divs.append(widget.y()+widget.frameGeometry().height()/2) self.centers = [(divs[i+1]+divs[i])/2 for i in range(len(divs)-1)] def dragMoveEvent(self, event): cord = event.pos().y() center_list = (i for i, x in enumerate(self.centers) if x > cord) divider_index = next(center_list, len(self.centers)) layerWidget = event.source() total = self.vbox_layout.count()//2 - 1 index = total - self.vbox_layout.indexOf(layerWidget)//2 - 1 insert = total - divider_index if not (insert == index) and not (insert-1 == index): state = True else: state = False for i in range(0, self.vbox_layout.count(), 2): if i == 2*divider_index: self.vbox_layout.itemAt(i).widget().setSelected(state) else: self.vbox_layout.itemAt(i).widget().setSelected(False) def dropEvent(self, event): for i in range(0, self.vbox_layout.count(), 2): self.vbox_layout.itemAt(i).widget().setSelected(False) cord = event.pos().y() center_list = (i for i, x in enumerate(self.centers) if x > cord) divider_index = next(center_list, len(self.centers)) layerWidget = event.source() total = self.vbox_layout.count()//2 - 1 index = total - self.vbox_layout.indexOf(layerWidget)//2 - 1 insert = total - divider_index if index != insert and index+1 != insert: if not self.layers[index].selected: self.layers.unselect_all() self.layers[index].selected = True self.layers._move_layers(index, insert) event.accept()
class Plugin(Plugin_Base): ''' call sequence: set vars like hintSignal, hintSignal onInit onWidget onUiInitDone send onReceived ''' # vars set by caller send = None # send(data_bytes=None, file_path=None) hintSignal = None # hintSignal.emit(title, msg) configGlobal = {} # other vars connParent = "main" connChilds = [] id = "dbg" name = _("Send Receive") # receiveUpdateSignal = pyqtSignal(str, list, str) # head, content, encoding receiveProgressStop = False receivedData = [] lock = threading.Lock() sendRecord = [] lastColor = None lastBg = None defaultColor = None defaultBg = None help = '''{}<br> <b style="color:#ef5350;"><kbd>F11</kbd></b>: {}<br> <b style="color:#ef5350;"><kbd>Ctrl+Enter</kbd></b>: {}<br> <b style="color:#ef5350;"><kbd>Ctrl+L</kbd></b>: {}<br> <b style="color:#ef5350;"><kbd>Ctrl+K</kbd></b>: {}<br> '''.format( _('Shortcut:'), _('Full screen'), _('Send data'), _('Clear Send Area'), _('Clear Receive Area') ) def onInit(self, config): super().onInit(config) self.keyControlPressed = False self.isScheduledSending = False self.config = config default = { "version": 1, "receiveAscii" : True, "receiveAutoLinefeed" : False, "receiveAutoLindefeedTime" : 200, "sendAscii" : True, "sendScheduled" : False, "sendScheduledTime" : 300, "sendAutoNewline": False, "useCRLF" : True, "showTimestamp" : False, "recordSend" : False, "saveLogPath" : "", "saveLog" : False, "color" : False, "sendEscape" : False, "customSendItems" : [], "sendHistoryList" : [], } for k in default: if not k in self.config: self.config[k] = default[k] def onWidgetMain(self, parent): self.mainWidget = QSplitter(Qt.Vertical) # widgets receive and send area self.receiveArea = QTextEdit() font = QFont('Menlo,Consolas,Bitstream Vera Sans Mono,Courier New,monospace, Microsoft YaHei', 10) self.receiveArea.setFont(font) self.receiveArea.setLineWrapMode(QTextEdit.NoWrap) self.sendArea = QTextEdit() self.sendArea.setLineWrapMode(QTextEdit.NoWrap) self.sendArea.setAcceptRichText(False) self.clearReceiveButtion = QPushButton("") utils_ui.setButtonIcon(self.clearReceiveButtion, "mdi6.broom") self.sendButton = QPushButton("") utils_ui.setButtonIcon(self.sendButton, "fa.send") self.sendHistory = ComboBox() sendWidget = QWidget() sendAreaWidgetsLayout = QHBoxLayout() sendAreaWidgetsLayout.setContentsMargins(0,4,0,0) sendWidget.setLayout(sendAreaWidgetsLayout) buttonLayout = QVBoxLayout() buttonLayout.addWidget(self.clearReceiveButtion) buttonLayout.addStretch(1) buttonLayout.addWidget(self.sendButton) sendAreaWidgetsLayout.addWidget(self.sendArea) sendAreaWidgetsLayout.addLayout(buttonLayout) self.mainWidget.addWidget(self.receiveArea) self.mainWidget.addWidget(sendWidget) self.mainWidget.addWidget(self.sendHistory) self.mainWidget.setStretchFactor(0, 7) self.mainWidget.setStretchFactor(1, 2) self.mainWidget.setStretchFactor(2, 1) # event self.sendButton.clicked.connect(self.onSendData) self.clearReceiveButtion.clicked.connect(self.clearReceiveBuffer) self.receiveUpdateSignal.connect(self.updateReceivedDataDisplay) self.sendHistory.activated.connect(self.onSendHistoryIndexChanged) return self.mainWidget def onWidgetSettings(self, parent): # serial receive settings layout = QVBoxLayout() serialReceiveSettingsLayout = QGridLayout() serialReceiveSettingsGroupBox = QGroupBox(_("Receive Settings")) self.receiveSettingsAscii = QRadioButton(_("ASCII")) self.receiveSettingsAscii.setToolTip(_("Show recived data as visible format, select decode method at top right corner")) self.receiveSettingsHex = QRadioButton(_("HEX")) self.receiveSettingsHex.setToolTip(_("Show recived data as hex format")) self.receiveSettingsAscii.setChecked(True) self.receiveSettingsAutoLinefeed = QCheckBox(_("Auto\nLinefeed\nms")) self.receiveSettingsAutoLinefeed.setToolTip(_("Auto linefeed after interval, unit: ms")) self.receiveSettingsAutoLinefeedTime = QLineEdit("200") self.receiveSettingsAutoLinefeedTime.setProperty("class", "smallInput") self.receiveSettingsAutoLinefeedTime.setToolTip(_("Auto linefeed after interval, unit: ms")) self.receiveSettingsAutoLinefeed.setMaximumWidth(75) self.receiveSettingsAutoLinefeedTime.setMaximumWidth(75) self.receiveSettingsTimestamp = QCheckBox(_("Timestamp")) self.receiveSettingsTimestamp.setToolTip(_("Add timestamp before received data, will automatically enable auto line feed")) self.receiveSettingsColor = QCheckBox(_("Color")) self.receiveSettingsColor.setToolTip(_("Enable unix terminal color support, e.g. \\33[31;43mhello\\33[0m")) serialReceiveSettingsLayout.addWidget(self.receiveSettingsAscii,1,0,1,1) serialReceiveSettingsLayout.addWidget(self.receiveSettingsHex,1,1,1,1) serialReceiveSettingsLayout.addWidget(self.receiveSettingsAutoLinefeed, 2, 0, 1, 1) serialReceiveSettingsLayout.addWidget(self.receiveSettingsAutoLinefeedTime, 2, 1, 1, 1) serialReceiveSettingsLayout.addWidget(self.receiveSettingsTimestamp, 3, 0, 1, 1) serialReceiveSettingsLayout.addWidget(self.receiveSettingsColor, 3, 1, 1, 1) serialReceiveSettingsGroupBox.setLayout(serialReceiveSettingsLayout) serialReceiveSettingsGroupBox.setAlignment(Qt.AlignHCenter) layout.addWidget(serialReceiveSettingsGroupBox) # serial send settings serialSendSettingsLayout = QGridLayout() serialSendSettingsGroupBox = QGroupBox(_("Send Settings")) self.sendSettingsAscii = QRadioButton(_("ASCII")) self.sendSettingsHex = QRadioButton(_("HEX")) self.sendSettingsAscii.setToolTip(_("Get send data as visible format, select encoding method at top right corner")) self.sendSettingsHex.setToolTip(_("Get send data as hex format, e.g. hex '31 32 33' equal to ascii '123'")) self.sendSettingsAscii.setChecked(True) self.sendSettingsScheduledCheckBox = QCheckBox(_("Timed Send\nms")) self.sendSettingsScheduledCheckBox.setToolTip(_("Timed send, unit: ms")) self.sendSettingsScheduled = QLineEdit("300") self.sendSettingsScheduled.setProperty("class", "smallInput") self.sendSettingsScheduled.setToolTip(_("Timed send, unit: ms")) self.sendSettingsScheduledCheckBox.setMaximumWidth(75) self.sendSettingsScheduled.setMaximumWidth(75) self.sendSettingsCRLF = QCheckBox(_("<CRLF>")) self.sendSettingsCRLF.setToolTip(_("Select to send \\r\\n instead of \\n")) self.sendSettingsCRLF.setChecked(False) self.sendSettingsRecord = QCheckBox(_("Record")) self.sendSettingsRecord.setToolTip(_("Record send data")) self.sendSettingsEscape= QCheckBox(_("Escape")) self.sendSettingsEscape.setToolTip(_("Enable escape characters support like \\t \\r \\n \\x01 \\001")) self.sendSettingsAppendNewLine= QCheckBox(_("Newline")) self.sendSettingsAppendNewLine.setToolTip(_("Auto add new line when send")) serialSendSettingsLayout.addWidget(self.sendSettingsAscii,1,0,1,1) serialSendSettingsLayout.addWidget(self.sendSettingsHex,1,1,1,1) serialSendSettingsLayout.addWidget(self.sendSettingsScheduledCheckBox, 2, 0, 1, 1) serialSendSettingsLayout.addWidget(self.sendSettingsScheduled, 2, 1, 1, 1) serialSendSettingsLayout.addWidget(self.sendSettingsCRLF, 3, 0, 1, 1) serialSendSettingsLayout.addWidget(self.sendSettingsAppendNewLine, 3, 1, 1, 1) serialSendSettingsLayout.addWidget(self.sendSettingsEscape, 4, 0, 1, 2) serialSendSettingsLayout.addWidget(self.sendSettingsEscape, 4, 0, 1, 2) serialSendSettingsLayout.addWidget(self.sendSettingsRecord, 4, 1, 1, 1) serialSendSettingsGroupBox.setLayout(serialSendSettingsLayout) layout.addWidget(serialSendSettingsGroupBox) widget = QWidget() widget.setLayout(layout) layout.setContentsMargins(0,0,0,0) # event self.receiveSettingsTimestamp.clicked.connect(self.onTimeStampClicked) self.receiveSettingsAutoLinefeed.clicked.connect(self.onAutoLinefeedClicked) self.receiveSettingsAscii.clicked.connect(lambda : self.bindVar(self.receiveSettingsAscii, self.config, "receiveAscii")) self.receiveSettingsHex.clicked.connect(lambda : self.bindVar(self.receiveSettingsHex, self.config, "receiveAscii", invert = True)) self.sendSettingsHex.clicked.connect(self.onSendSettingsHexClicked) self.sendSettingsAscii.clicked.connect(self.onSendSettingsAsciiClicked) self.sendSettingsRecord.clicked.connect(self.onRecordSendClicked) self.sendSettingsAppendNewLine.clicked.connect(lambda: self.bindVar(self.sendSettingsAppendNewLine, self.config, "sendAutoNewline")) self.sendSettingsEscape.clicked.connect(lambda: self.bindVar(self.sendSettingsEscape, self.config, "sendEscape")) self.sendSettingsCRLF.clicked.connect(lambda: self.bindVar(self.sendSettingsCRLF, self.config, "useCRLF")) self.receiveSettingsColor.clicked.connect(self.onSetColorChanged) self.receiveSettingsAutoLinefeedTime.textChanged.connect(lambda: self.bindVar(self.receiveSettingsAutoLinefeedTime, self.config, "receiveAutoLindefeedTime", vtype=int, vErrorMsg=_("Auto line feed value error, must be integer"), emptyDefault = "200")) self.sendSettingsScheduled.textChanged.connect(lambda: self.bindVar(self.sendSettingsScheduled, self.config, "sendScheduledTime", vtype=int, vErrorMsg=_("Timed send value error, must be integer"), emptyDefault = "300")) self.sendSettingsScheduledCheckBox.clicked.connect(lambda: self.bindVar(self.sendSettingsScheduledCheckBox, self.config, "sendScheduled")) return widget def onWidgetFunctional(self, parent): sendFunctionalLayout = QVBoxLayout() sendFunctionalLayout.setContentsMargins(0,0,0,0) # right functional layout self.filePathWidget = QLineEdit() self.openFileButton = QPushButton(_("Open File")) self.sendFileButton = QPushButton(_("Send File")) self.clearHistoryButton = QPushButton(_("Clear History")) self.addButton = QPushButton("") utils_ui.setButtonIcon(self.addButton, "fa.plus") self.fileSendGroupBox = QGroupBox(_("Sendding File")) fileSendGridLayout = QGridLayout() fileSendGridLayout.addWidget(self.filePathWidget, 0, 0, 1, 1) fileSendGridLayout.addWidget(self.openFileButton, 0, 1, 1, 1) fileSendGridLayout.addWidget(self.sendFileButton, 1, 0, 1, 2) self.fileSendGroupBox.setLayout(fileSendGridLayout) self.logFileGroupBox = QGroupBox(_("Save log")) # cumtom send zone # groupbox customSendGroupBox = QGroupBox(_("Cutom send")) customSendItemsLayout0 = QVBoxLayout() customSendItemsLayout0.setContentsMargins(0,8,0,0) customSendGroupBox.setLayout(customSendItemsLayout0) # scroll self.customSendScroll = QScrollArea() self.customSendScroll.setMinimumHeight(parameters.customSendItemHeight + 20) self.customSendScroll.setWidgetResizable(True) self.customSendScroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) # add scroll to groupbox customSendItemsLayout0.addWidget(self.customSendScroll) # wrapper widget cutomSendItemsWraper = QWidget() customSendItemsLayoutWrapper = QVBoxLayout() customSendItemsLayoutWrapper.setContentsMargins(0,0,0,0) cutomSendItemsWraper.setLayout(customSendItemsLayoutWrapper) # custom items customItems = QWidget() self.customSendItemsLayout = QVBoxLayout() self.customSendItemsLayout.setContentsMargins(0,0,0,0) customItems.setLayout(self.customSendItemsLayout) customSendItemsLayoutWrapper.addWidget(customItems) customSendItemsLayoutWrapper.addWidget(self.addButton) # set wrapper widget self.customSendScroll.setWidget(cutomSendItemsWraper) self.customSendScroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # logFileLayout = QHBoxLayout() self.saveLogCheckbox = QCheckBox() self.logFilePath = QLineEdit() self.logFileBtn = QPushButton(_("Log path")) logFileLayout.addWidget(self.saveLogCheckbox) logFileLayout.addWidget(self.logFilePath) logFileLayout.addWidget(self.logFileBtn) self.logFileGroupBox.setLayout(logFileLayout) sendFunctionalLayout.addWidget(self.logFileGroupBox) sendFunctionalLayout.addWidget(self.fileSendGroupBox) sendFunctionalLayout.addWidget(self.clearHistoryButton) sendFunctionalLayout.addWidget(customSendGroupBox) sendFunctionalLayout.addStretch(1) self.funcWidget = QWidget() self.funcWidget.setLayout(sendFunctionalLayout) # event self.sendFileButton.clicked.connect(self.sendFile) self.saveLogCheckbox.clicked.connect(self.setSaveLog) self.logFileBtn.clicked.connect(self.selectLogFile) self.openFileButton.clicked.connect(self.selectFile) self.addButton.clicked.connect(self.customSendAdd) self.clearHistoryButton.clicked.connect(self.clearHistory) self.funcParent = parent return self.funcWidget def onWidgetStatusBar(self, parent): self.statusBar = statusBar(rxTxCount=True) return self.statusBar def onUiInitDone(self): paramObj = self.config self.receiveSettingsHex.setChecked(not paramObj["receiveAscii"]) self.receiveSettingsAutoLinefeed.setChecked(paramObj["receiveAutoLinefeed"]) try: interval = int(paramObj["receiveAutoLindefeedTime"]) paramObj["receiveAutoLindefeedTime"] = interval except Exception: interval = parameters.Parameters.receiveAutoLindefeedTime self.receiveSettingsAutoLinefeedTime.setText(str(interval) if interval > 0 else str(parameters.Parameters.receiveAutoLindefeedTime)) self.receiveSettingsTimestamp.setChecked(paramObj["showTimestamp"]) self.sendSettingsHex.setChecked(not paramObj["sendAscii"]) self.sendSettingsScheduledCheckBox.setChecked(paramObj["sendScheduled"]) try: interval = int(paramObj["sendScheduledTime"]) paramObj["sendScheduledTime"] = interval except Exception: interval = parameters.Parameters.sendScheduledTime self.sendSettingsScheduled.setText(str(interval) if interval > 0 else str(parameters.Parameters.sendScheduledTime)) self.sendSettingsCRLF.setChecked(paramObj["useCRLF"]) self.sendSettingsAppendNewLine.setChecked(paramObj["sendAutoNewline"]) self.sendSettingsRecord.setChecked(paramObj["recordSend"]) self.sendSettingsEscape.setChecked(paramObj["sendEscape"]) for i in range(0, len(paramObj["sendHistoryList"])): text = paramObj["sendHistoryList"][i] self.sendHistory.addItem(text) self.logFilePath.setText(paramObj["saveLogPath"]) self.logFilePath.setToolTip(paramObj["saveLogPath"]) self.saveLogCheckbox.setChecked(paramObj["saveLog"]) self.receiveSettingsColor.setChecked(paramObj["color"]) # send items for text in paramObj["customSendItems"]: self.insertSendItem(text, load=True) self.receiveProcess = threading.Thread(target=self.receiveDataProcess) self.receiveProcess.setDaemon(True) self.receiveProcess.start() def onSendSettingsHexClicked(self): self.config["sendAscii"] = False data = self.sendArea.toPlainText().replace("\n","\r\n") data = utils.bytes_to_hex_str(data.encode()) self.sendArea.clear() self.sendArea.insertPlainText(data) def onSendSettingsAsciiClicked(self): self.config["sendAscii"] = True try: data = self.sendArea.toPlainText().replace("\n"," ").strip() self.sendArea.clear() if data != "": data = utils.hex_str_to_bytes(data).decode(self.configGlobal["encoding"],'ignore') self.sendArea.insertPlainText(data) except Exception as e: # QMessageBox.information(self,self.strings.strWriteFormatError,self.strings.strWriteFormatError) print("format error") def onAutoLinefeedClicked(self): if (self.config["showTimestamp"] or self.config["recordSend"]) and not self.receiveSettingsAutoLinefeed.isChecked(): self.receiveSettingsAutoLinefeed.setChecked(True) self.hintSignal.emit("warning", _("Warning"), _("linefeed always on if timestamp or record send is on")) self.config["receiveAutoLinefeed"] = self.receiveSettingsAutoLinefeed.isChecked() def onTimeStampClicked(self): self.config["showTimestamp"] = self.receiveSettingsTimestamp.isChecked() if self.config["showTimestamp"]: self.config["receiveAutoLinefeed"] = True self.receiveSettingsAutoLinefeed.setChecked(True) def onRecordSendClicked(self): self.config["recordSend"] = self.sendSettingsRecord.isChecked() if self.config["recordSend"]: self.config["receiveAutoLinefeed"] = True self.receiveSettingsAutoLinefeed.setChecked(True) def onEscapeSendClicked(self): self.config["sendEscape"] = self.sendSettingsEscape.isChecked() def onSetColorChanged(self): self.config["color"] = self.receiveSettingsColor.isChecked() def onSendHistoryIndexChanged(self, idx): self.sendArea.clear() self.sendArea.insertPlainText(self.sendHistory.currentText()) def clearHistory(self): self.config["sendHistoryList"].clear() self.sendHistory.clear() self.hintSignal.emit("info", _("OK"), _("History cleared!")) def onSent(self, ok, msg, length, path): if ok: self.statusBar.addTx(length) else: self.hintSignal.emit("error", _("Error"), _("Send data failed!") + " " + msg) def onSentFile(self, ok, msg, length, path): print("file sent {}, path: {}".format('ok' if ok else 'fail', path)) if ok: self.sendFileButton.setText(_("Send file")) self.sendFileButton.setDisabled(False) self.statusBar.addTx(length) else: self.hintSignal.emit("error", _("Error"), _("Send file failed!") + " " + msg) def setSaveLog(self): if self.saveLogCheckbox.isChecked(): self.config["saveLog"] = True else: self.config["saveLog"] = False def selectFile(self): oldPath = self.filePathWidget.text() if oldPath=="": oldPath = os.getcwd() fileName_choose, filetype = QFileDialog.getOpenFileName(self.mainWidget, _("Select file"), oldPath, _("All Files (*)")) if fileName_choose == "": return self.filePathWidget.setText(fileName_choose) self.filePathWidget.setToolTip(fileName_choose) def selectLogFile(self): oldPath = self.logFilePath.text() if oldPath=="": oldPath = os.getcwd() fileName_choose, filetype = QFileDialog.getSaveFileName(self.mainWidget, _("Select file"), os.path.join(oldPath, "comtool.log"), _("Log file (*.log);;txt file (*.txt);;All Files (*)")) if fileName_choose == "": return self.logFilePath.setText(fileName_choose) self.logFilePath.setToolTip(fileName_choose) self.config["saveLogPath"] = fileName_choose def onLog(self, text): if self.config["saveLogPath"]: with open(self.config["saveLogPath"], "a+", encoding=self.configGlobal["encoding"], newline="\n") as f: f.write(text) def onKeyPressEvent(self, event): if event.key() == Qt.Key_Control: self.keyControlPressed = True elif event.key() == Qt.Key_Return or event.key()==Qt.Key_Enter: if self.keyControlPressed: self.onSendData() elif event.key() == Qt.Key_L: if self.keyControlPressed: self.sendArea.clear() elif event.key() == Qt.Key_K: if self.keyControlPressed: self.receiveArea.clear() def onKeyReleaseEvent(self, event): if event.key() == Qt.Key_Control: self.keyControlPressed = False def insertSendItem(self, text="", load = False): itemsNum = self.customSendItemsLayout.count() + 1 height = parameters.customSendItemHeight * (itemsNum + 1) + 20 topHeight = self.fileSendGroupBox.height() + self.logFileGroupBox.height() + 100 if height + topHeight > self.funcParent.height(): height = self.funcParent.height() - topHeight if height < 0: height = self.funcParent.height() // 3 self.customSendScroll.setMinimumHeight(height) item = QWidget() layout = QHBoxLayout() layout.setContentsMargins(0,0,0,0) item.setLayout(layout) cmd = QLineEdit(text) send = QPushButton("") utils_ui.setButtonIcon(send, "fa.send") cmd.setToolTip(text) send.setToolTip(text) cmd.textChanged.connect(lambda: self.onCustomItemChange(self.customSendItemsLayout.indexOf(item), cmd, send)) send.setProperty("class", "smallBtn") send.clicked.connect(lambda: self.sendCustomItem(self.config["customSendItems"][self.customSendItemsLayout.indexOf(item)])) delete = QPushButton("") utils_ui.setButtonIcon(delete, "fa.close") delete.setProperty("class", "deleteBtn") layout.addWidget(cmd) layout.addWidget(send) layout.addWidget(delete) delete.clicked.connect(lambda: self.deleteSendItem(self.customSendItemsLayout.indexOf(item), item)) self.customSendItemsLayout.addWidget(item) if not load: self.config["customSendItems"].append("") def deleteSendItem(self, idx, item): item.setParent(None) self.config["customSendItems"].pop(idx) itemsNum = self.customSendItemsLayout.count() height = parameters.customSendItemHeight * (itemsNum + 1) + 20 topHeight = self.fileSendGroupBox.height() + self.logFileGroupBox.height() + 100 if height + topHeight > self.funcParent.height(): height = self.funcParent.height() - topHeight self.customSendScroll.setMinimumHeight(height) def onCustomItemChange(self, idx, edit, send): text = edit.text() edit.setToolTip(text) send.setToolTip(text) self.config["customSendItems"][idx] = text def sendCustomItem(self, text): self.onSendData(data = text) def customSendAdd(self): self.insertSendItem() def getSendData(self, data=None) -> bytes: if data is None: data = self.sendArea.toPlainText() return self.parseSendData(data, self.configGlobal["encoding"], self.config["useCRLF"], not self.config["sendAscii"], self.config["sendEscape"]) def sendFile(self): filename = self.filePathWidget.text() if not os.path.exists(filename): self.hintSignal.emit("error", _("Error"), _("File path error\npath") + ":%s" %(filename)) return if not self.isConnected(): self.hintSignal.emit("warning", _("Warning"), _("Connect first please")) else: self.sendFileButton.setDisabled(True) self.sendFileButton.setText(_("Sending file")) self.send(file_path=filename, callback = lambda ok, msg, length, path: self.onSentFile(ok, msg, length, path)) def scheduledSend(self): self.isScheduledSending = True while self.config["sendScheduled"]: self.onSendData() try: time.sleep(self.config["sendScheduledTime"]/1000) except Exception: self.hintSignal.emit("error", _("Error"), _("Time format error")) self.isScheduledSending = False def sendData(self, data_bytes = None): try: if self.isConnected(): if not data_bytes or type(data_bytes) == str: data = self.getSendData(data_bytes) else: data = data_bytes if not data: return if self.config["sendAutoNewline"]: data += b"\r\n" if self.config["useCRLF"] else b"\n" # record send data if self.config["recordSend"]: head = '=> ' if self.config["showTimestamp"]: head += '[{}] '.format(utils.datetime_format_ms(datetime.now())) isHexStr, sendStr, sendStrsColored = self.bytes2String(data, not self.config["receiveAscii"], encoding=self.configGlobal["encoding"]) if isHexStr: sendStr = sendStr.upper() sendStrsColored= sendStr head += "[HEX] " if self.config["useCRLF"]: head = "\r\n" + head else: head = "\n" + head if head.strip() != '=>': head = '{}: '.format(head.rstrip()) self.receiveUpdateSignal.emit(head, [sendStrsColored], self.configGlobal["encoding"]) self.sendRecord.insert(0, head + sendStr) self.send(data_bytes=data, callback = self.onSent) if data_bytes: data = str(data_bytes) else: data = self.sendArea.toPlainText() self.sendHistoryFindDelete(data) self.sendHistory.insertItem(0,data) self.sendHistory.setCurrentIndex(0) try: idx = self.config["sendHistoryList"].index(data) self.config["sendHistoryList"].pop(idx) except Exception: pass self.config["sendHistoryList"].insert(0, data) # scheduled send if self.config["sendScheduled"]: if not self.isScheduledSending: t = threading.Thread(target=self.scheduledSend) t.setDaemon(True) t.start() except Exception as e: import traceback traceback.print_exc() print("[Error] sendData: ", e) self.hintSignal.emit("error", _("Error"), _("Send Error") + str(e)) # print(e) def onSendData(self, call=True, data=None): try: self.sendData(data) except Exception as e: print("[Error] onSendData: ", e) self.hintSignal.emit("error", _("Error"), _("get data error") + ": " + str(e)) def updateReceivedDataDisplay(self, head : str, datas : list, encoding): if datas: curScrollValue = self.receiveArea.verticalScrollBar().value() self.receiveArea.moveCursor(QTextCursor.End) endScrollValue = self.receiveArea.verticalScrollBar().value() cursor = self.receiveArea.textCursor() format = cursor.charFormat() font = QFont('Menlo,Consolas,Bitstream Vera Sans Mono,Courier New,monospace, Microsoft YaHei', 10) format.setFont(font) if not self.defaultColor: self.defaultColor = format.foreground() if not self.defaultBg: self.defaultBg = format.background() if head: format.setForeground(self.defaultColor) cursor.setCharFormat(format) format.setBackground(self.defaultBg) cursor.setCharFormat(format) cursor.insertText(head) for data in datas: if type(data) == str: self.receiveArea.insertPlainText(data) elif type(data) == list: for color, bg, text in data: if color: format.setForeground(QColor(color)) cursor.setCharFormat(format) else: format.setForeground(self.defaultColor) cursor.setCharFormat(format) if bg: format.setBackground(QColor(bg)) cursor.setCharFormat(format) else: format.setBackground(self.defaultBg) cursor.setCharFormat(format) cursor.insertText(text) else: # bytes self.receiveArea.insertPlainText(data.decode(encoding=encoding, errors="ignore")) if curScrollValue < endScrollValue: self.receiveArea.verticalScrollBar().setValue(curScrollValue) else: self.receiveArea.moveCursor(QTextCursor.End) def sendHistoryFindDelete(self,str): self.sendHistory.removeItem(self.sendHistory.findText(str)) def _getColorByfmt(self, fmt:bytes): colors = { b"0": None, b"30": "#000000", b"31": "#f44336", b"32": "#4caf50", b"33": "#ffa000", b"34": "#2196f3", b"35": "#e85aad", b"36": "#26c6da", b"37": "#a1887f", } bgs = { b"0": None, b"40": "#000000", b"41": "#f44336", b"42": "#4caf50", b"43": "#ffa000", b"44": "#2196f3", b"45": "#e85aad", b"46": "#26c6da", b"47": "#a1887f", } fmt = fmt[2:-1].split(b";") color = colors[b'0'] bg = bgs[b'0'] for cmd in fmt: if cmd in colors: color = colors[cmd] if cmd in bgs: bg = bgs[cmd] return color, bg def _texSplitByColor(self, text:bytes): remain = b'' ignoreCodes = [rb'\x1b\[\?.*?h', rb'\x1b\[\?.*?l'] text = text.replace(b"\x1b[K", b"") for code in ignoreCodes: colorFmt = re.findall(code, text) for fmt in colorFmt: text = text.replace(fmt, b"") colorFmt = re.findall(rb'\x1b\[.*?m', text) if text.endswith(b"\x1b"): # ***\x1b text = text[:-1] remain = b'\x1b' elif text.endswith(b"\x1b["): # ***\x1b[ text = text[:-2] remain = b'\x1b[' else: # ****\x1b[****, ****\x1b[****;****m idx = -2 idx_remain = -1 while 1: idx = text.find(b"\x1b[", len(text) - 10 + idx + 2) # \x1b[00;00m] if idx < 0: break remain = text[idx:] idx_remain = idx if len(remain) > 0: match = re.findall(rb'\x1b\[.*?m', remain) # ****\x1b[****;****m*** if len(match) > 0: # have full color format remain = b'' else: text = text[:idx_remain] plaintext = text for fmt in colorFmt: plaintext = plaintext.replace(fmt, b"") colorStrs = [] if colorFmt: p = 0 for fmt in colorFmt: idx = text[p:].index(fmt) if idx != 0: colorStrs.append([self.lastColor, self.lastBg, text[p:p+idx]]) p += idx self.lastColor, self.lastBg = self._getColorByfmt(fmt) p += len(fmt) colorStrs.append([self.lastColor, self.lastBg, text[p:]]) else: colorStrs = [[self.lastColor, self.lastBg, text]] return plaintext, colorStrs, remain def getColoredText(self, data_bytes, decoding=None): plainText, coloredText, remain = self._texSplitByColor(data_bytes) if decoding: plainText = plainText.decode(encoding=decoding, errors="ignore") decodedColoredText = [] for color, bg, text in coloredText: decodedColoredText.append([color, bg, text.decode(encoding=decoding, errors="ignore")]) coloredText = decodedColoredText return plainText, coloredText, remain def bytes2String(self, data : bytes, showAsHex : bool, encoding="utf-8"): isHexString = False dataColored = None if showAsHex: return True, utils.hexlify(data, ' ').decode(encoding=encoding), dataColored try: dataPlain, dataColore, remain = self.getColoredText(data, self.configGlobal["encoding"]) if remain: dataPlain += remain.decode(encoding=self.configGlobal["encoding"], errors="ignore") except Exception: dataPlain = utils.hexlify(data, ' ').decode(encoding=encoding) isHexString = True return isHexString, dataPlain, dataColored def clearReceiveBuffer(self): self.receiveArea.clear() self.statusBar.clear() def onReceived(self, data : bytes): self.receivedData.append(data) self.statusBar.addRx(len(data)) self.lock.release() def receiveDataProcess(self): self.receiveProgressStop = False timeLastReceive = 0 new_line = True logData = None buffer = b'' remain = b'' while(not self.receiveProgressStop): logData = None head = "" self.lock.acquire() new = b"".join(self.receivedData) buffer += new self.receivedData = [] # timeout, add new line if time.time() - timeLastReceive> self.config["receiveAutoLindefeedTime"]: if self.config["showTimestamp"] or self.config["receiveAutoLinefeed"]: if self.config["useCRLF"]: head += "\r\n" else: head += "\n" new_line = True data = "" # have data in buffer if len(buffer) > 0: hexstr = False # show as hex, just show if not self.config["receiveAscii"]: data = utils.bytes_to_hex_str(buffer) colorData = data buffer = b'' hexstr = True # show as string, and don't need to render color elif not self.config["color"]: data = buffer.decode(encoding=self.configGlobal["encoding"], errors="ignore") colorData = data buffer = b'' # show as string, and need to render color, wait for \n or until timeout to ensure color flag in buffer else: if time.time() - timeLastReceive > self.config["receiveAutoLindefeedTime"] or b'\n' in buffer: data, colorData, remain = self.getColoredText(buffer, self.configGlobal["encoding"]) buffer = remain # add time receive head # get data from buffer, now render if data: # add time header, head format(send receive '123' for example): # '123' '[2021-12-20 11:02:08.02.754]: 123' '=> 12' '<= 123' # '=> [2021-12-20 11:02:34.02.291]: 123' '<= [2021-12-20 11:02:40.02.783]: 123' # '<= [2021-12-20 11:03:25.03.320] [HEX]: 31 32 33 ' '=> [2021-12-20 11:03:27.03.319] [HEX]: 31 32 33' if new_line: timeNow = '[{}] '.format(utils.datetime_format_ms(datetime.now())) if self.config["recordSend"]: head += "<= " if self.config["showTimestamp"]: head += timeNow head = '{} '.format(head.rstrip()) if hexstr: head += "[HEX] " if (self.config["recordSend"] or self.config["showTimestamp"]) and not head.endswith("<= "): head = head[:-1] + ": " new_line = False self.receiveUpdateSignal.emit(head, [colorData], self.configGlobal["encoding"]) logData = head + data if len(new) > 0: timeLastReceive = time.time() while len(self.sendRecord) > 0: self.onLog(self.sendRecord.pop()) if logData: self.onLog(logData)
class textEditorWindow(QMainWindow): updateOpen = pyqtSignal(str) removeOpen = pyqtSignal(object) # Constructor def __init__(self): super(textEditorWindow, self).__init__() self.setGeometry(400, 400, 600, 500) self.textBoxList = [] self.openFiles = [] self.openDialog = QDialog(parent=self) self.openDialog.setModal(True) self.connFileMap = [] self.onlineIndex = 0 self.username = None self.fileList = [] self.tabsNextIndex = 0 self.tabs = QTabWidget() self.tabs.resize(300,200) self.tabs.setTabsClosable(True) self.initOpenDialog() self.layout = QVBoxLayout(self) self.tabs.tabBar().setTabsClosable(True) self.tabs.tabBar().tabCloseRequested.connect(self.closeRequestedTab) self.setCentralWidget(self.tabs) self.menu() self.setEditingMenu(False) self.docked = QDockWidget("Online Users", self) self.addDockWidget(Qt.LeftDockWidgetArea, self.docked) self.dockedWidget = QWidget(self) self.docked.setWidget(self.dockedWidget) self.docklayout = QVBoxLayout() self.docklayout.addStretch(1) self.dockedWidget.setLayout(self.docklayout) self.docked.hide() toolbar = QToolBar('Toolbar', self) self.addToolBar(toolbar) toolbar.setFixedHeight fontFamilySelect = QFontComboBox(toolbar) fontFamilySelect.setCurrentFont(QFont(fontName, fontSize)) fontFamilySelect.setWritingSystem(QFontDatabase.WritingSystem.Any) # Monospaced fonts only fontFamilySelect.setFontFilters(QFontComboBox.MonospacedFonts) fontFamilySelect.currentFontChanged.connect(self.updateFontFamily) self.fontSizes = ['8','9','10','11','12','14','16','18','20','22','24','26','28','36','48','72'] fontSizeSelect = QComboBox(toolbar) fontSizeSelect.setSizeAdjustPolicy(QComboBox.AdjustToContents) fontSizeSelect.move(150,0) fontSizeSelect.addItems(self.fontSizes) fontSizeSelect.setCurrentIndex(6) fontSizeSelect.currentIndexChanged.connect(self.updateFontSizeIndex) def closeRequestedTab(self, index): if index==False: # hardcoded to be from menu item index = self.tabs.currentIndex() textBox = self.getCurrentTextbox(index=index) textBox.stopEditingFunction(index) def updateFontFamily(self, qfont): global fontName fontName = qfont.family() if len(self.openFiles) > 0: self.updateFonts() def updateFontSizeIndex(self, qsize): global fontSize fontSize = int(self.fontSizes[qsize]) if len(self.openFiles) > 0: self.updateFonts() def updateFonts(self): text = self.getCurrentTextbox() text.setFont(QFont(fontName, fontSize)) def createNewTab(self): ip, port, fileName, fullName = self.readFromLists() print(ip + "|" + port + "|" + fileName + "|" + fullName) if (ip == ""): return clientSocket = self.createFileSocket(ip, port, fileName) if clientSocket is None: return self.setEditingMenu(True) newTab = QWidget() text = Textbox(clientSocket, fullName, self.tabsNextIndex, self.updateOnline) self.tabsNextIndex += 1 text.stopEditing.connect(self.removeTab) self.tabs.addTab(newTab, fullName) newTab.layout = QVBoxLayout(self) self.tabs.setCurrentIndex(self.tabsNextIndex-1) newTab.layout.addWidget(text) newTab.setLayout(newTab.layout) self.textBoxList.append(text) self.openFiles.append(fullName) fileusers = QGridLayout() ##Temporary i = 0 y = 0 for x in text.names: label = QLabel(x) fileusers.addWidget(label, i, y) i += 1 if i == 2: y += 1 i = 0 onlineBox = QGroupBox() onlineBox.setMaximumHeight(120) onlineBox.setLayout(fileusers) onlineBox.setTitle(fileName) text.onlineBox = onlineBox #self.docklayout.addWidget(onlineBox) self.docklayout.insertWidget(self.onlineIndex, onlineBox) self.onlineIndex += 1 self.openDialog.hide() def initOpenDialog(self): tabNew = self.openDialog # tabNew = QWidget() # self.tabs.addTab(tabNew, "+") lytTab = QVBoxLayout(self) ############################ ## Connection Layout Area ## ############################ lytConnection = QHBoxLayout(self) ## Left Side: Connection List self.qlwConnSelect = QListWidget(tabNew) self.qlwConnSelect.setFixedSize(300,150) self.qlwConnSelect.itemSelectionChanged.connect(self.refreshFileList) ## Middle: +/- buttons lytConnSettings = QVBoxLayout(self) btnAddConnection = QPushButton("+") btnAddConnection.setFixedSize(25,25) btnAddConnection.clicked.connect(self.addConnection) btnRefreshConnection = QPushButton("↻") btnRefreshConnection.setFixedSize(25,25) btnRefreshConnection.clicked.connect(self.refreshFileList) btnRemoveConnection = QPushButton("-") btnRemoveConnection.setFixedSize(25,25) btnRemoveConnection.clicked.connect(self.removeConnection) lytConnSettings.addWidget(btnAddConnection) lytConnSettings.addWidget(btnRefreshConnection) lytConnSettings.addWidget(btnRemoveConnection) ## Right: Host/Port Inputs lblHost = QLabel("Host", self) self.lneHost = QLineEdit(parent=self) self.lneHost.placeholderText = 'Hostname' lblPort = QLabel("Port", self) self.lnePort = QLineEdit(parent=self) self.lnePort.placeholderText = 'Port' lytNewConn = QVBoxLayout(self) lytNewConn.addWidget(lblHost) lytNewConn.addWidget(self.lneHost) lytNewConn.addWidget(lblPort) lytNewConn.addWidget(self.lnePort) ### Adding to Connection Layout lytConnection.addWidget(self.qlwConnSelect) lytConnection.addLayout(lytConnSettings) lytConnection.addStretch(1) lytConnection.addLayout(lytNewConn) lytConnection.addStretch(1) #################### ## File Selection ## #################### lytFileList = QHBoxLayout(self) ## Left Side self.qlwFileSelect = QListWidget(tabNew) self.qlwFileSelect.setFixedSize(300,150) ## Middle lytModifyFile = QVBoxLayout(self) btnCreateFile = QPushButton("Create File") btnRenameFile = QPushButton("Rename File") btnDeleteFile = QPushButton("Delete File") btnCreateFile.setFixedSize(130,50) btnRenameFile.setFixedSize(130,50) btnDeleteFile.setFixedSize(130,50) btnCreateFile.clicked.connect(self.createFile) btnRenameFile.clicked.connect(self.renameFile) btnDeleteFile.clicked.connect(self.deleteFile) lytModifyFile.addWidget(btnCreateFile) lytModifyFile.addWidget(btnRenameFile) lytModifyFile.addWidget(btnDeleteFile) ### Adding to File List Layout lytFileList.addWidget(self.qlwFileSelect) lytFileList.addStretch(2) lytFileList.addLayout(lytModifyFile) lytFileList.addStretch(1) # Connection Button btnConnect = QPushButton("Open File") btnConnect.setFixedSize(150,50) btnConnect.clicked.connect(self.createNewTab) ## Final Layout Settings lytTab.addLayout(lytConnection) lytTab.addLayout(lytFileList) lytTab.addWidget(btnConnect) tabNew.setLayout(lytTab) def addConnection(self): host = self.lneHost.text() port = self.lnePort.text() # print("host = %s, port = %s"% (host, port)) # Check if the port number is valid if not port.isdigit() or host == "": showErrorMessage("Invalid host or port") return # Attempt to connect to the server combinedName = host + ":" + port # If this is already an existing connection if combinedName in self.connFileMap: return if self.refreshFileList(ip=host, port=port) != True: return self.connFileMap.append(combinedName) self.lneHost.setText('') self.lnePort.setText('') qliNewConn = QListWidgetItem() qliNewConn.setText(combinedName) self.qlwConnSelect.addItem(qliNewConn) self.qlwConnSelect.setCurrentItem(qliNewConn) def removeConnection(self): if len(self.qlwConnSelect.selectedItems()) == 0: return self.connFileMap.remove(self.qlwConnSelect.currentItem().text()) self.qlwConnSelect.takeItem(self.qlwConnSelect.currentRow()) self.qlwFileSelect.clear() # Called when a new connection is selected def refreshFileList(self, ip="", port=""): if ip == False: ip = "" print("here1") if ip=="" or port=="": if len(self.qlwConnSelect.selectedItems()) == 0: return ip, port = self.qlwConnSelect.currentItem().text().split(':') combinedName = ip + ":" + port print("here2") # If this is the first refresh clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: clientSocket.connect((ip, int(port))) except socket.error: showErrorMessage("Failed to connect") return print("here3") response = sendMessage(clientSocket, True, "getFiles") if "Err" in response["FilesListResp"]: self.showErrorMessage(response["FilesListResp"]["Err"]) return print("here4") files = sorted(response["FilesListResp"]["Ok"]) print("we made it here!") self.qlwFileSelect.clear() for file in files: self.qlwFileSelect.addItem(file) return True def removeTab(self, index, text): self.tabs.removeTab(index) self.textBoxList.remove(text) tList = [] tList.append(text) self.removeOpen.emit(tList) self.tabsNextIndex += -1 self.openFiles.remove(text.fullName) if len(self.openFiles) == 0: self.setEditingMenu(False) for file in self.openFiles: print(file) def setEditingMenu(self, enabled): for child in self.fileMenu.actions(): if child.text() == '&Save and close': child.setEnabled(enabled) for child in self.editMenu.actions(): child.setEnabled(enabled) def setFileList(self, fileList): self.fileList = fileList def closeEvent(self, event: QCloseEvent): print("Window {} closed".format(self)) self.removeOpen.emit(self.textBoxList) for object in self.textBoxList: sendMessage(object.clientSocket, False, "close") super().closeEvent(event) def createServerSocket(self, ip, port): clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: clientSocket.connect((ip, int(port))) except socket.error: showErrorMessage("Failed to connect") return None return clientSocket def createFileSocket(self, ip, port, fileName): clientSocket = self.createServerSocket(ip, port) fullName = ip + ":" + port + " " + fileName if fullName in self.openFiles: showErrorMessage("File is already open!") else: response = sendMessage(clientSocket, True, "open", fileName, self.username) if "Err" in response["OpenResp"]: showErrorMessage(response["OpenResp"]["Err"]) return None else: return clientSocket def createFile(self): ip, port, fileName, fullName = self.readFromLists(requireFileSelection=False) if ip == "": return clientSocket = self.createServerSocket(ip, port) if clientSocket is None: return newName, okPressed = QInputDialog.getText(self, "Create file", "Enter new file name:", QLineEdit.Normal, "") if not okPressed: return response = sendMessage(clientSocket, True, "create", newName) if "Err" in response["CreateResp"]: showErrorMessage(response["CreateResp"]["Err"]) else: self.refreshFileList() def renameFile(self): ip, port, fileName, fullName = self.readFromLists() if ip == "": return clientSocket = self.createServerSocket(ip, port) if clientSocket is None: return newName, okPressed = QInputDialog.getText(self, "Rename file", "Enter new file name:", QLineEdit.Normal, "") if okPressed: if newName != '': response = sendMessage(clientSocket, True, "rename", fileName, newName) if "Err" in response["RenameResp"]: showErrorMessage(response["RenameResp"]["Err"]) else: newFullName = ip + ":" + port + " " + newName self.refreshFileList() else: showErrorMessage("File name cannot be empty") def deleteFile(self): ip, port, fileName, fullName = self.readFromLists() if ip == "": return clientSocket = self.createServerSocket(ip, port) if clientSocket is None: return if fileName in self.openFiles: showErrorMessage("Target file is open! Please close it first") else: response = sendMessage(clientSocket, True, "delete", fileName) if "Err" in response["DeleteResp"]: showErrorMessage(response["DeleteResp"]["Err"]) else: self.refreshFileList() def readFromLists(self, requireFileSelection=True): if len(self.qlwConnSelect.selectedItems()) == 0: return ("","","","") if requireFileSelection: if len(self.qlwFileSelect.selectedItems()) == 0: return ("","","","") ip, port = self.qlwConnSelect.currentItem().text().split(':') fileName = None if requireFileSelection: fileName = self.qlwFileSelect.currentItem().text() else: fileName = "" fullName = ip+":"+port+" "+fileName if fullName in self.openFiles: showErrorMessage("File is open, please close it first.") return ("","","","") return (ip, port, fileName, fullName) def menu(self): menuBar = self.menuBar() self.fileMenu = menuBar.addMenu('&File') self.editMenu = menuBar.addMenu('&Edit') nameMenu = menuBar.addMenu('&Name') users = menuBar.addMenu('&Users') openAct = QAction('&Open', self) openAct.setShortcut('Ctrl+O') openAct.triggered.connect(self.showOpenDialog) self.fileMenu.addAction(openAct) saveCloseAct = QAction('&Save and close', self) saveCloseAct.setShortcut('Ctrl+W') saveCloseAct.setStatusTip('Save the current tab and close it.') saveCloseAct.triggered.connect(self.closeRequestedTab) self.fileMenu.addAction(saveCloseAct) exitAct = QAction(QIcon('exit.png'), '&Exit', self) exitAct.setShortcut('Ctrl+Q') exitAct.setStatusTip('Exit application') exitAct.triggered.connect(qApp.quit) self.fileMenu.addAction(exitAct) # === Edit === # cutAct = QAction('&Cut', self) cutAct.setStatusTip('Cut the current selection') cutAct.setShortcut('Ctrl+X') cutAct.triggered.connect(self.cutWrapper) self.editMenu.addAction(cutAct) copyAct = QAction('&Copy', self) copyAct.setStatusTip('Copy the current selection') copyAct.setShortcut('Ctrl+C') copyAct.triggered.connect(self.copyWrapper) self.editMenu.addAction(copyAct) pasteAct = QAction('&Paste', self) pasteAct.setStatusTip('Paste the current selection') pasteAct.setShortcut('Ctrl+V') pasteAct.triggered.connect(self.pasteWrapper) self.editMenu.addAction(pasteAct) self.editMenu.addSeparator() undoAct = QAction('&Undo', self) undoAct.setStatusTip('Undo the current selection') undoAct.setShortcut('Ctrl+Z') undoAct.triggered.connect(self.undoWrapper) self.editMenu.addAction(undoAct) redoAct = QAction('&Redo', self) redoAct.setStatusTip('Redo the current selection') redoAct.setShortcut('Ctrl+Y') redoAct.triggered.connect(self.redoWrapper) self.editMenu.addAction(redoAct) nameAction = QAction('Set Username', self) nameAction.triggered.connect(self.setName) nameMenu.addAction(nameAction) ##Will be used to add new connections userAction = QAction('View Online Users', self, checkable=True) userAction.setChecked(False) userAction.triggered.connect(self.toggleOnline) users.addAction(userAction) def cutWrapper(self): text = self.getCurrentTextbox() text.cut() def copyWrapper(self): text = self.getCurrentTextbox() text.copy() def pasteWrapper(self): text = self.getCurrentTextbox() text.paste() def undoWrapper(self): text = self.getCurrentTextbox() text.undo() def redoWrapper(self): text = self.getCurrentTextbox() text.redo() def getCurrentTextbox(self, index=None): if index==None: index = self.tabs.currentIndex() childrenList = self.tabs.widget(index).children() for child in childrenList: if child.metaObject().className() == "Textbox": return child def showOpenDialog(self): self.openDialog.show() def setName(self): if len(self.openFiles) > 0: showErrorMessage("Cant set name when files opened") return username, okPressed = QInputDialog.getText(self, "", "Set Username", QLineEdit.Normal, "") if okPressed and username != "": self.username = username if okPressed and username == "": showErrorMessage("Name cannot be blank") def toggleOnline(self, state): if state: self.docked.show() if not state: self.docked.hide() def updateOnline(self, textbox, userlist): userlist.sort() if userlist == textbox.names: return index = self.docklayout.indexOf(textbox.onlineBox) textbox.onlineBox.close() fileusers = QGridLayout() i = 0 y = 0 count = 0 for x in userlist: if x == self.username and count == 0: count+=1 continue label = QLabel(x) fileusers.addWidget(label, i, y) i += 1 if i == 2: y += 1 i = 0 onlineBox = QGroupBox() onlineBox.setMaximumHeight(120) onlineBox.setLayout(fileusers) onlineBox.setTitle(textbox.fullName) textbox.onlineBox = onlineBox self.docklayout.insertWidget(index, onlineBox) textbox.names = userlist
class MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) centralWidget = QWidget() self.mainLayout = QVBoxLayout() buttonLayout = QHBoxLayout() foldersLayout = QHBoxLayout() backupFolderLayout = QHBoxLayout() self.usbDevicesChooser = QComboBox() usb_util = Utils() self.usbDevices = usb_util.get_storage_device() for device in self.usbDevices: self.usbDevicesChooser.addItem(device.__str__()) self.pathToFolders = QLineEdit() self.pathToFolders.setPlaceholderText("Path to folders you want to backup") self.pathToBackupFolder = QLineEdit() self.pathToBackupFolder.setPlaceholderText("Path to backup destination folder") self.browseFoldersButton = QPushButton("Browse...") self.browseFoldersButton.clicked.connect(self.on_browseFolders_clicked) self.browseBackupFolderButton = QPushButton("Browse...") self.browseBackupFolderButton.clicked.connect(self.on_browseBackupFolder_clicked) foldersLayout.addWidget(self.pathToFolders) foldersLayout.addWidget(self.browseFoldersButton) foldersLayout.setSpacing(5) backupFolderLayout.addWidget(self.pathToBackupFolder) backupFolderLayout.addWidget(self.browseBackupFolderButton) backupFolderLayout.setSpacing(5) self.startBackupButton = QPushButton("Start Backup") self.startBackupButton.clicked.connect(self.on_startBackup_clicked) self.exitButton = QPushButton("Exit") self.exitButton.clicked.connect(self.on_exit_clicked) buttonLayout.addWidget(self.startBackupButton) buttonLayout.addWidget(self.exitButton) buttonLayout.setSpacing(10) self.mainLayout.addWidget(self.usbDevicesChooser) self.mainLayout.addLayout(backupFolderLayout) self.mainLayout.addLayout(foldersLayout) self.mainLayout.addLayout(buttonLayout) centralWidget.setLayout(self.mainLayout) self.setCentralWidget(centralWidget) self.setWindowTitle("Nice Backup") self.setMinimumSize(600, 200) def on_exit_clicked(self, widget): sys.exit() def on_browseFolders_clicked(self, widget): index = self.usbDevicesChooser.currentIndex() file_dialog = QFileDialog() file_dialog.setFileMode(QFileDialog.DirectoryOnly) file_dialog.setOption(QFileDialog.DontUseNativeDialog, True) file_view = file_dialog.findChild(QListView, 'listView') if file_view: file_view.setSelectionMode(QAbstractItemView.MultiSelection) ftree_view = file_dialog.findChild(QTreeView) if ftree_view: ftree_view.setSelectionMode(QAbstractItemView.MultiSelection) file_dialog.setDirectory(self.usbDevices[index].getPath()) if file_dialog.exec(): paths = file_dialog.selectedFiles() self.pathToFolders.setText(";".join(paths)) def on_browseBackupFolder_clicked(self, widget): fname = QFileDialog.getExistingDirectory(self.window(), 'Open file', '/home/{}'.format(getpass.getuser())) self.pathToBackupFolder.setText(fname) def on_startBackup_clicked(self, widget): print("Starting backup") backuper = Backuper.Backuper() sources = self.pathToFolders.text().split(";") destination = self.pathToBackupFolder.text() progress = QProgressBar() progress.setMinimum(0) progress.setMaximum(len(sources)) self.mainLayout.addWidget(progress) for source in sources: backuper.make_backup(source, destination) progress.setValue(sources.index(source)) QApplication.processEvents() doneDialog = QMessageBox() doneDialog.setIcon(QMessageBox.Information) doneDialog.setText("Copying data is completed") doneDialog.setWindowTitle("Copy event") doneDialog.setStandardButtons(QMessageBox.Ok) index = self.mainLayout.indexOf(progress) self.mainLayout.itemAt(index).widget().setParent(None) doneDialog.exec()
class QtFigure(BaseFigure, QWidget): """The VTK render window embedded into a PyQt5 QWidget. This can be embedded into a GUI the same way all other QWidgets are used. :param name: The window title of the figure, only applicable is **parent** is None, defaults to 'qt vtk figure'. :type name: str, optional :param parent: Parent window, defaults to None. :type parent: PyQt5.QtWidgets.QWidget, optional .. note:: If you are new to Qt then this is a rather poor place to start. Whilst many libraries in Python are intuitive enough to be able to just dive straight in, Qt is not one of them. Preferably familiarise yourself with some basic Qt before coming here. This class inherits both from :class:`PyQt5.QtWidgets.QWidget` and a vtkplotlib BaseFigure class. Therefore it can be used exactly the same as you would normally use either a `QWidget` or a :class:`vtkplotlib.figure`. Care must be taken when using Qt to ensure you have **exactly one** QApplication. To make this class quicker to use the qapp is created automatically but is wrapped in a .. code-block:: python if QApplication.instance() is None: self.qapp = QApplication(sys.argv) else: self.qapp = QApplication.instance() This prevents multiple QApplication instances from being created (which causes an instant crash) whilst also preventing a QWidget from being created without a qapp (which also causes a crash). On ``self.show()``, ``self.qapp.exec_()`` is called automatically if ``self.parent() is None`` (unless specified otherwise). If the QFigure is part of a larger window then ``larger_window.show()`` must also explicitly show the figure. It won't begin interactive mode until ``qapp.exec_()`` is called. If the figure is not to be part of a larger window then it behaves exactly like a regular figure. You just need to explicitly create it first. .. code-block:: python import vtkplotlib as vpl # Create the figure. This automatically sets itself as the current # working figure. The qapp is created automatically if one doesn't # already exist. vpl.QtFigure("Exciting Window Title") # Everything from here on should be exactly the same as normal. vpl.quick_test_plot() # Automatically calls ``qapp.exec_()``. If you don't want it to then # use ``vpl.show(False)``. vpl.show() However this isn't particularly helpful. A more realistic example would require the figure be part of a larger window. In this case, treat the figure as you would any other QWidget. You must explicitly call ``figure.show()`` however. (Not sure why.) .. code-block:: python import vtkplotlib as vpl from PyQt5 import QtWidgets import numpy as np import sys # python 2 compatibility from builtins import super class FigureAndButton(QtWidgets.QWidget): def __init__(self): super().__init__() # Go for a vertical stack layout. vbox = QtWidgets.QVBoxLayout() self.setLayout(vbox) # Create the figure self.figure = vpl.QtFigure() # Create a button and attach a callback. self.button = QtWidgets.QPushButton("Make a Ball") self.button.released.connect(self.button_pressed_cb) # QtFigures are QWidgets and are added to layouts with `addWidget` vbox.addWidget(self.figure) vbox.addWidget(self.button) def button_pressed_cb(self): \"""Plot commands can be called in callbacks. The current working figure is still self.figure and will remain so until a new figure is created explicitly. So the ``fig=self.figure`` arguments below aren't necessary but are recommended for larger, more complex scenarios. \""" # Randomly place a ball. vpl.scatter(np.random.uniform(-30, 30, 3), color=np.random.rand(3), fig=self.figure) # Reposition the camera to better fit to the balls. vpl.reset_camera(self.figure) # Without this the figure will not redraw unless you click on it. self.figure.update() def show(self): # The order of these two are interchangeable. super().show() self.figure.show() def closeEvent(self, event): \"""This isn't essential. VTK, OpenGL, Qt and Python's garbage collect all get in the way of each other so that VTK can't clean up properly which causes an annoying VTK error window to pop up. Explicitly calling QtFigure's `closeEvent()` ensures everything gets deleted in the right order. \""" self.figure.closeEvent(event) qapp = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv) window = FigureAndButton() window.show() qapp.exec_() .. note:: QtFigures are not reshow-able if the figure has a parent. .. seealso:: :class:`vtkplotlib.QtFigure2` is an extension of this to provide some standard GUI elements, ready-made. """ def __init__(self, name="qt vtk figure", parent=None): self.qapp = QApplication.instance() or QApplication(sys.argv) QWidget.__init__(self, parent) BaseFigure.__init__(self, name) self.vl = QVBoxLayout() self.setLayout(self.vl) self._vtkWidget = QVTKRenderWindowInteractor(self) self.vl.addWidget(self.vtkWidget) self.renWin self.iren def _re_init(self): debug("re init") name = self.window_name QWidget.__init__(self, self.parent()) self.window_name = name self.setLayout(self.vl) self.setWindowTitle(self.window_name) self.vtkWidget = QVTKRenderWindowInteractor(self) self.vl.insertWidget(self._vtkWidget_replace_index, self.vtkWidget) self.renWin, self.iren @property def vtkWidget(self): if not hasattr(self, "_vtkWidget"): self._re_init() return self._vtkWidget @vtkWidget.setter def vtkWidget(self, widget): self._vtkWidget = widget @vtkWidget.deleter def vtkWidget(self): if hasattr(self, "_vtkWidget"): del self._vtkWidget def _base_show_wrap(QWidget_show_name): """Wrap all the ``QWidget.show()``, ``QWidget.showMaximized()`` etc methods so they can all be used as expected. Just in case Qt has changed and some ``show...()`` methods aren't present, this defaults to just ``show()``. """ QWidget_show = getattr(QWidget, QWidget_show_name, QWidget.show) def show(self, block=None): if not hasattr(self, "vtkWidget"): self._re_init() self._connect_renderer() QWidget_show(self) self.iren.Initialize() self.renWin.Render() self.iren.Start() if block is None: block = self.parent() is None if block: self._flush_stdout() self.qapp.exec_() BaseFigure.show(self, block) show.__name__ = QWidget_show.__name__ try: show.__qualname__ = QWidget_show.__qualname__ except (AttributeError, TypeError): pass return show show = _base_show_wrap("show") showMaximized = _base_show_wrap("showMaximized") showMinimized = _base_show_wrap("showMinimized") showFullScreen = _base_show_wrap("showFullScreen") showNormal = _base_show_wrap("showNormal") @nuts_and_bolts.init_when_called def renWin(self): if not hasattr(self, "vtkWidget"): self._re_init() renWin = self.vtkWidget.GetRenderWindow() return renWin @nuts_and_bolts.init_when_called def iren(self): iren = self.renWin.GetInteractor() iren.SetInteractorStyle(self.style) return iren def update(self): BaseFigure.update(self) QWidget.update(self) self.qapp.processEvents() # def close(self): # BaseFigure.close(self) # QWidget.close(self) # self._clean_up() def on_close(self): debug("cleaning up") if hasattr(self, "_renWin"): # These prevent error dialogs popping up. self._disconnect_renderer() self.renWin.MakeCurrent() self.renWin.Finalize() if hasattr(self, "_vtkWidget"): self._vtkWidget_replace_index = self.vl.indexOf(self.vtkWidget) self.vl.removeWidget(self.vtkWidget) # self.renderer.RemoveAllViewProps() del self.vtkWidget, self.iren, self.renWin def closeEvent(self, event): self.on_close() window_name = property(QWidget.windowTitle, QWidget.setWindowTitle) def __del__(self): try: self.renderer.RemoveAllViewProps() except (AttributeError, TypeError): # In Python2, RemoveAllViewProps is already None pass def _prep_for_screenshot(self, off_screen=False): BaseFigure._prep_for_screenshot(self, off_screen) if off_screen: print("Off screen rendering can't be done using QtFigures.") self.show(block=False) def close(self): QWidget.close(self) # closeEvent seems to be called anyway but call this just to be sure. self.on_close() BaseFigure.close(self)
class QtLayerList(QScrollArea): def __init__(self, layers): super().__init__() self.layers = layers self.setWidgetResizable(True) #self.setFixedWidth(315) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scrollWidget = QWidget() self.setWidget(scrollWidget) self.layersLayout = QVBoxLayout(scrollWidget) self.layersLayout.addWidget(QtDivider()) self.layersLayout.addStretch(1) self.setAcceptDrops(True) self.setToolTip('Layer list') def insert(self, index, total, layer): """Inserts a layer widget at a specific index """ if layer._qt is not None: self.layersLayout.insertWidget(2 * (total - index) - 1, layer._qt) self.layersLayout.insertWidget(2 * (total - index), QtDivider()) self.layers.viewer._update_active_layers() self.layers.viewer.controlBars.climSliderUpdate() def remove(self, layer): """Removes a layer widget """ if layer._qt is not None: index = self.layersLayout.indexOf(layer._qt) divider = self.layersLayout.itemAt(index + 1).widget() self.layersLayout.removeWidget(layer._qt) layer._qt.deleteLater() layer._qt = None self.layersLayout.removeWidget(divider) divider.deleteLater() divider = None self.layers.viewer._update_active_layers() self.layers.viewer.controlBars.climSliderUpdate() def reorder(self): """Reorders list of layer widgets by looping through all widgets in list sequentially removing them and inserting them into the correct place in final list. """ total = len(self.layers) for i in range(total): layer = self.layers[i] if layer._qt is not None: index = self.layersLayout.indexOf(layer._qt) divider = self.layersLayout.itemAt(index + 1).widget() self.layersLayout.removeWidget(layer._qt) self.layersLayout.removeWidget(divider) self.layersLayout.insertWidget(2 * (total - i) - 1, layer._qt) self.layersLayout.insertWidget(2 * (total - i), divider) self.layers.viewer._update_active_layers() self.layers.viewer.controlBars.climSliderUpdate() def mouseReleaseEvent(self, event): """Unselects all layer widgets """ if self.layersLayout.count() > 1: self.layersLayout.itemAt(1).widget().unselectAll() self.layers.viewer._update_active_layers() self.layers.viewer._set_annotation_mode(self.layers.viewer.annotation) self.layers.viewer.controlBars.climSliderUpdate() self.layers.viewer._status = 'Ready' self.layers.viewer.emitStatus() def dragLeaveEvent(self, event): event.ignore() for i in range(0, self.layersLayout.count(), 2): self.layersLayout.itemAt(i).widget().setSelected(False) def dragEnterEvent(self, event): event.accept() dividers = [] for i in range(0, self.layersLayout.count(), 2): widget = self.layersLayout.itemAt(i).widget() dividers.append(widget.y() + widget.frameGeometry().height() / 2) self.centers = [(dividers[i + 1] + dividers[i]) / 2 for i in range(len(dividers) - 1)] def dragMoveEvent(self, event): cord = event.pos().y() divider_index = next( (i for i, x in enumerate(self.centers) if x > cord), len(self.centers)) layerWidget = event.source() layers = layerWidget.layer.viewer.layers index = layers.index(layerWidget.layer) total = len(layers) insert_index = total - divider_index if not (insert_index == index) and not (insert_index - 1 == index): state = True else: state = False for i in range(0, self.layersLayout.count(), 2): if i == 2 * divider_index: self.layersLayout.itemAt(i).widget().setSelected(state) else: self.layersLayout.itemAt(i).widget().setSelected(False) def dropEvent(self, event): for i in range(0, self.layersLayout.count(), 2): self.layersLayout.itemAt(i).widget().setSelected(False) cord = event.pos().y() divider_index = next( (i for i, x in enumerate(self.centers) if x > cord), len(self.centers)) layerWidget = event.source() layers = layerWidget.layer.viewer.layers index = layers.index(layerWidget.layer) total = len(layers) insert_index = total - divider_index indices = [i for i in range(total)] if layerWidget.layer.selected: selected = [] for i in range(total): if layers[i].selected: selected.append(i) else: selected = [index] for i in selected: indices.remove(i) offset = sum([i < insert_index for i in selected]) j = insert_index - offset for i in selected: indices.insert(j, i) j = j + 1 if not indices == [i for i in range(total)]: layers.reorder(indices) event.accept() else: event.ignore() if not layerWidget.layer.selected: layerWidget.unselectAll() layerWidget.setSelected(True)
class Overview(QWidget): ''' Dock Widget This widget shows an overview of all created frames. Supports: Change to a frame with a click. The active frame is heighlighted. ''' def __init__(self,parent=None): super().__init__(parent) self.initUI() def initUI(self): self.scrollArea = QScrollArea(self) self.scrollArea.setWidgetResizable(True) self.scrollAreaWidgetContents = QWidget(self.scrollArea) self.vLayout = QVBoxLayout(self.scrollAreaWidgetContents) self.vLayout.setSpacing(20) self.scrollArea.setWidget(self.scrollAreaWidgetContents) self.scrollArea.setMinimumSize(150,self.parent().parent().frames.size().height()) self.addFrameWidget() self.parent().parent().frames.resized.connect(lambda x:self.adaptFrameViewerSize(x)) def addFrameWidget(self,frameID = 0): for i in reversed(range(self.vLayout.count())): widget = self.vLayout.takeAt(i).widget() if widget is not None: widget = widget.setParent(None) for i,frame in enumerate(self.parent().parent().frames.sceneCollection): label = OverviewLabel(self) view = SingleFrameViewer(label,self.parent().parent().frames) view.setScene(frame) view.scale(0.2,0.2) view.resize(130,130) label.clicked.connect(lambda x: self.labelClicked(x)) self.vLayout.insertWidget(i,label) self.activeFrameChanged(self.parent().parent().frames.activeFrameID) def removeFrameWidget(self, frameID = 0): try: widget = self.vLayout.takeAt(frameID).widget() if widget is not None: widget = widget.setParent(None) except: print("Tried to remove overview frame {}, but it could not be found.".format(frameID)) def activeFrameChanged(self,activeFrameID): for i in reversed(range(self.vLayout.count())): widget = self.vLayout.itemAt(i).widget() if widget is not None: if widget.active: widget.toggleActive() activeFrame = self.vLayout.itemAt(activeFrameID) if activeFrame is not None: activeFrame.widget().toggleActive() def labelClicked(self,widget): self.parent().parent().frames.selectFrameById(self.vLayout.indexOf(widget)) def adaptFrameViewerSize(self,size): self.scrollArea.setMinimumSize(self.scrollArea.minimumSize().width(),size.height()-7)
class Window(QMainWindow, Ui_window): renderRequested = QtCore.pyqtSignal(int, float) loadFileRequested = QtCore.pyqtSignal(str, str) findTextRequested = QtCore.pyqtSignal(str, int, bool) def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.setupUi(self) self.dockSearch.hide() self.dockWidget.hide() self.dockWidget.setMinimumWidth(310) self.findTextEdit.setFocusPolicy(QtCore.Qt.StrongFocus) self.treeView.setAlternatingRowColors(True) self.treeView.clicked.connect(self.onOutlineClick) # resizing pages requires some time to take effect self.resize_page_timer = QtCore.QTimer(self) self.resize_page_timer.setSingleShot(True) self.resize_page_timer.timeout.connect(self.onWindowResize) # Add shortcut actions self.gotoPageAction = QAction(QIcon(":/goto.png"), "GoTo Page", self) self.gotoPageAction.triggered.connect(self.gotoPage) self.copyTextAction = QAction(QIcon(":/copy.png"), "Copy Text", self) self.copyTextAction.setCheckable(True) self.copyTextAction.triggered.connect(self.toggleCopyText) self.findTextAction = QAction(QIcon(":/search.png"), "Find Text", self) self.findTextAction.setShortcut('Ctrl+F') self.findTextAction.triggered.connect(self.dockSearch.show) # connect menu actions signals self.openFileAction.triggered.connect(self.openFile) self.lockUnlockAction.triggered.connect(self.lockUnlock) self.printAction.triggered.connect(self.printFile) self.quitAction.triggered.connect(self.close) self.toPSAction.triggered.connect(self.exportToPS) self.pageToImageAction.triggered.connect(self.exportPageToImage) self.docInfoAction.triggered.connect(self.docInfo) self.zoominAction.triggered.connect(self.zoomIn) self.zoomoutAction.triggered.connect(self.zoomOut) self.undoJumpAction.triggered.connect(self.undoJump) self.prevPageAction.triggered.connect(self.goPrevPage) self.nextPageAction.triggered.connect(self.goNextPage) self.firstPageAction.triggered.connect(self.goFirstPage) self.lastPageAction.triggered.connect(self.goLastPage) # Create widgets for menubar / toolbar self.gotoPageEdit = QLineEdit(self) self.gotoPageEdit.setPlaceholderText("Jump to page...") self.gotoPageEdit.setMaximumWidth(120) self.gotoPageEdit.returnPressed.connect(self.gotoPage) self.gotoPageValidator = QIntValidator(1, 1, self.gotoPageEdit) self.gotoPageEdit.setValidator(self.gotoPageValidator) self.pageNoLabel = QLabel(self) self.pageNoLabel.setFrameShape(QFrame.StyledPanel) spacer = QWidget(self) spacer.setSizePolicy(1 | 2 | 4, 1 | 4) self.zoomLevelCombo = QComboBox(self) self.zoomLevelCombo.addItems([ "Fixed Width", "75%", "90%", "100%", "110%", "121%", "133%", "146%", "175%", "200%" ]) self.zoomLevelCombo.activated.connect(self.setZoom) self.zoom_levels = [0, 75, 90, 100, 110, 121, 133, 146, 175, 200] # Add toolbar actions self.toolBar.addAction(self.openFileAction) self.toolBar.addAction(self.printAction) self.toolBar.addSeparator() self.toolBar.addAction(self.docInfoAction) self.toolBar.addSeparator() self.toolBar.addAction(self.zoomoutAction) self.toolBar.addWidget(self.zoomLevelCombo) self.toolBar.addAction(self.zoominAction) self.toolBar.addSeparator() self.toolBar.addAction(self.firstPageAction) self.toolBar.addAction(self.prevPageAction) self.toolBar.addWidget(self.pageNoLabel) self.toolBar.addAction(self.nextPageAction) self.toolBar.addAction(self.lastPageAction) self.toolBar.addAction(self.undoJumpAction) self.toolBar.addWidget(self.gotoPageEdit) self.toolBar.addAction(self.gotoPageAction) self.toolBar.addSeparator() self.toolBar.addAction(self.copyTextAction) self.toolBar.addAction(self.findTextAction) #self.toolBar.addAction(self.saveUnlockedAction) self.toolBar.addWidget(spacer) self.toolBar.addSeparator() self.toolBar.addAction(self.quitAction) # Add widgets self.statusbar = QLabel(self) self.statusbar.setStyleSheet( "QLabel { font-size: 12px; border-radius: 2px; padding: 2px; background: palette(highlight); color: palette(highlighted-text); }" ) self.statusbar.setMaximumHeight(16) self.statusbar.hide() # Impoort settings desktop = QApplication.desktop() self.settings = QtCore.QSettings("gospel-pdf", "main", self) self.recent_files = self.settings.value("RecentFiles", []) self.history_filenames = self.settings.value("HistoryFileNameList", []) self.history_page_no = self.settings.value("HistoryPageNoList", []) self.offset_x = int(self.settings.value("OffsetX", 4)) self.offset_y = int(self.settings.value("OffsetY", 26)) self.available_area = [ desktop.availableGeometry().width(), desktop.availableGeometry().height() ] self.zoomLevelCombo.setCurrentIndex( int(self.settings.value("ZoomLevel", 2))) # Connect Signals self.scrollArea.verticalScrollBar().valueChanged.connect( self.onMouseScroll) self.scrollArea.verticalScrollBar().sliderReleased.connect( self.onSliderRelease) self.findTextEdit.returnPressed.connect(self.findNext) self.findNextButton.clicked.connect(self.findNext) self.findBackButton.clicked.connect(self.findBack) self.findCloseButton.clicked.connect(self.dockSearch.hide) self.dockSearch.visibilityChanged.connect(self.toggleFindMode) # Create separate thread and move renderer to it self.thread1 = QtCore.QThread(self) self.renderer1 = Renderer(0) self.renderer1.moveToThread( self.thread1) # this must be moved before connecting signals self.renderRequested.connect(self.renderer1.render) self.loadFileRequested.connect(self.renderer1.loadDocument) self.findTextRequested.connect(self.renderer1.findText) self.renderer1.rendered.connect(self.setRenderedImage) self.renderer1.textFound.connect(self.onTextFound) self.thread1.start() self.thread2 = QtCore.QThread(self) self.renderer2 = Renderer(1) self.renderer2.moveToThread(self.thread2) self.renderRequested.connect(self.renderer2.render) self.loadFileRequested.connect(self.renderer2.loadDocument) self.renderer2.rendered.connect(self.setRenderedImage) self.thread2.start() # Initialize Variables self.doc = None self.filename = '' self.passwd = '' self.pages = [] self.jumped_from = None self.max_preload = 1 self.recent_files_actions = [] self.addRecentFiles() # Show Window width = int(self.settings.value("WindowWidth", 1040)) height = int(self.settings.value("WindowHeight", 717)) self.resize(width, height) self.show() def addRecentFiles(self): self.recent_files_actions[:] = [] # pythonic way to clear list self.menuRecentFiles.clear() for each in self.recent_files: name = elideMiddle(os.path.basename(each), 60) action = self.menuRecentFiles.addAction(name, self.openRecentFile) self.recent_files_actions.append(action) self.menuRecentFiles.addSeparator() self.menuRecentFiles.addAction(QIcon(':/edit-clear.png'), 'Clear Recents', self.clearRecents) def openRecentFile(self): action = self.sender() index = self.recent_files_actions.index(action) self.loadPDFfile(self.recent_files[index]) def clearRecents(self): self.recent_files_actions[:] = [] self.menuRecentFiles.clear() self.recent_files[:] = [] def removeOldDoc(self): if not self.doc: return # Save current page number self.saveFileData() # Remove old document for i in range(len(self.pages)): self.verticalLayout.removeWidget(self.pages[-1]) for i in range(len(self.pages)): self.pages.pop().deleteLater() self.frame.deleteLater() self.jumped_from = None self.addRecentFiles() def loadPDFfile(self, filename): """ Loads pdf document in all threads """ filename = os.path.expanduser(filename) doc = Poppler.Document.load(filename) if not doc: return password = '' if doc.isLocked(): password = QInputDialog.getText(self, 'This PDF is locked', 'Enter Password :'******'': if self.doc == None: sys.exit(1) #exif if first document else: return locked = doc.unlock(password.encode(), password.encode()) if locked: return QMessageBox.critical(self, "Failed !", "Incorrect Password") self.passwd = password self.lockUnlockAction.setText("Save Unlocked") else: self.lockUnlockAction.setText("Encrypt PDF") self.removeOldDoc() doc.setRenderHint(Poppler.Document.TextAntialiasing | Poppler.Document.TextHinting | Poppler.Document.Antialiasing | Poppler.Document.ThinLineSolid) self.doc = doc self.filename = filename self.pages_count = self.doc.numPages() self.current_page = 1 self.rendered_pages = [] self.getOutlines(self.doc) # Load Document in other threads self.loadFileRequested.emit(self.filename, password) if collapseUser(self.filename) in self.history_filenames: self.current_page = int( self.history_page_no[self.history_filenames.index( collapseUser(self.filename))]) self.current_page = min(self.current_page, self.pages_count) self.scroll_render_lock = False # Add widgets self.frame = Frame(self.scrollAreaWidgetContents, self.scrollArea) self.verticalLayout = QVBoxLayout(self.frame) self.horizontalLayout_2.addWidget(self.frame) self.scrollArea.verticalScrollBar().setValue(0) self.frame.jumpToRequested.connect(self.jumpToPage) self.frame.copyTextRequested.connect(self.copyText) self.frame.showStatusRequested.connect(self.showStatus) # Render 4 pages, (Preload 3 pages) self.max_preload = min(4, self.pages_count) # Add pages for i in range(self.pages_count): page = PageWidget(i + 1, self.frame) self.verticalLayout.addWidget(page, 0, QtCore.Qt.AlignCenter) self.pages.append(page) self.resizePages() self.pageNoLabel.setText('<b>%i/%i</b>' % (self.current_page, self.pages_count)) self.gotoPageValidator.setTop(self.pages_count) self.setWindowTitle( os.path.basename(self.filename) + " - Gospel PDF " + __version__) if self.current_page != 1: QtCore.QTimer.singleShot(150 + self.pages_count // 3, self.jumpToCurrentPage) def setRenderedImage(self, page_no, image): """ takes a QImage and sets pixmap of the specified page when number of rendered pages exceeds a certain number, old page image is deleted to save memory """ debug("Set Rendered Image :", page_no) self.pages[page_no - 1].setPageData(page_no, QPixmap.fromImage(image), self.doc.page(page_no - 1)) # Request to render next page if self.current_page <= page_no < (self.current_page + self.max_preload - 2): if (page_no + 2 not in self.rendered_pages) and (page_no + 2 <= self.pages_count): self.rendered_pages.append(page_no + 2) self.renderRequested.emit(page_no + 2, self.pages[page_no + 1].dpi) # Replace old rendered pages with blank image if len(self.rendered_pages) > 10: cleared_page_no = self.rendered_pages.pop(0) debug("Clear Page :", cleared_page_no) self.pages[cleared_page_no - 1].clear() debug("Rendered Pages :", self.rendered_pages) def renderCurrentPage(self): """ Requests to render current page. if it is already rendered, then request to render next unrendered page """ requested = 0 for page_no in range(self.current_page, self.current_page + self.max_preload): if (page_no not in self.rendered_pages) and (page_no <= self.pages_count): self.rendered_pages.append(page_no) self.renderRequested.emit(page_no, self.pages[page_no - 1].dpi) requested += 1 debug("Render Requested :", page_no) if requested == 2: return def onMouseScroll(self, pos): """ It is called when vertical scrollbar value is changed. Get the current page number on scrolling, then requests to render""" index = self.verticalLayout.indexOf( self.frame.childAt(self.frame.width() / 2, pos)) if index == -1: return self.pageNoLabel.setText('<b>%i/%i</b>' % (index + 1, self.pages_count)) if self.scrollArea.verticalScrollBar().isSliderDown( ) or self.scroll_render_lock: return self.current_page = index + 1 self.renderCurrentPage() def onSliderRelease(self): self.onMouseScroll(self.scrollArea.verticalScrollBar().value()) def openFile(self): filename, sel_filter = QFileDialog.getOpenFileName( self, "Select Document to Open", "", "Portable Document Format (*.pdf);;All Files (*)") if filename != "": self.loadPDFfile(filename) def lockUnlock(self): if which("qpdf") == None: self.lockUnlockAction.setEnabled(False) QMessageBox.warning( self, "qpdf Required", "qpdf command not found.\nInstall qpdf program.") return if self.lockUnlockAction.text() == "Encrypt PDF": self.encryptPDF() return filename, ext = os.path.splitext(self.filename) new_name = filename + "-unlocked.pdf" proc = Popen([ "qpdf", "--decrypt", "--password="******"File Saved", "Successfully saved as\n" + basename) else: QMessageBox.warning(self, "Failed !", "Failed to save as unlocked") def encryptPDF(self): password, ok = QInputDialog.getText(self, "Lock PDF", "Enter Password :"******"": return filename, ext = os.path.splitext(self.filename) new_name = filename + "-locked.pdf" proc = Popen([ "qpdf", "--encrypt", password, password, '128', '--', self.filename, new_name ]) stdout, stderr = proc.communicate() if proc.returncode == 0: basename = os.path.basename(new_name) QMessageBox.information(self, "File Saved", "Successfully saved as\n" + basename) else: QMessageBox.warning(self, "Failed !", "Failed to save as Encrypted") def printFile(self): if which("quikprint") == None: QMessageBox.warning(self, "QuikPrint Required", "Install QuikPrint program.") return Popen(["quikprint", self.filename]) def exportToPS(self): width = self.doc.page(self.current_page - 1).pageSizeF().width() height = self.doc.page(self.current_page - 1).pageSizeF().height() filename, sel_filter = QFileDialog.getSaveFileName( self, "Select File to Save", os.path.splitext(self.filename)[0] + '.ps', "Adobe Postscript Format (*.ps)") if filename == '': return conv = self.doc.psConverter() conv.setPaperWidth(width) conv.setPaperHeight(height) conv.setOutputFileName(filename) conv.setPageList([i + 1 for i in range(self.pages_count)]) ok = conv.convert() if ok: QMessageBox.information(self, "Successful !", "File has been successfully exported") else: QMessageBox.warning(self, "Failed !", "Failed to export to Postscript") def exportPageToImage(self): dialog = ExportToImageDialog(self.current_page, self.pages_count, self) if dialog.exec_() == QDialog.Accepted: try: dpi = int(dialog.dpiEdit.text()) page_no = dialog.pageNoSpin.value() filename = os.path.splitext( self.filename)[0] + '-' + str(page_no) + '.jpg' page = self.doc.page(page_no - 1) if not page: return img = page.renderToImage(dpi, dpi) img.save(filename) QMessageBox.information(self, "Successful !", "Page has been successfully exported") except: QMessageBox.warning(self, "Failed !", "Failed to export to Image") def docInfo(self): info_keys = list(self.doc.infoKeys()) values = [self.doc.info(key) for key in info_keys] page_size = self.doc.page(self.current_page - 1).pageSizeF() page_size = "%s x %s pts" % (page_size.width(), page_size.height()) info_keys += ['Embedded FIles', 'Page Size'] values += [str(self.doc.hasEmbeddedFiles()), page_size] dialog = DocInfoDialog(self) dialog.setInfo(info_keys, values) dialog.exec_() def jumpToCurrentPage(self): """ this is used as a slot, to connect with a timer""" self.jumpToPage(self.current_page) def jumpToPage(self, page_num, top=0.0): """ scrolls to a particular page and position """ if page_num < 1: page_num = 1 elif page_num > self.pages_count: page_num = self.pages_count if not (0 < top < 1.0): top = 0 self.jumped_from = self.current_page self.current_page = page_num scrollbar_pos = self.pages[page_num - 1].pos().y() scrollbar_pos += top * self.pages[page_num - 1].height() self.scrollArea.verticalScrollBar().setValue(scrollbar_pos) def undoJump(self): if self.jumped_from == None: return self.jumpToPage(self.jumped_from) def goNextPage(self): if self.current_page == self.pages_count: return self.jumpToPage(self.current_page + 1) def goPrevPage(self): if self.current_page == 1: return self.jumpToPage(self.current_page - 1) def goFirstPage(self): self.jumpToPage(1) def goLastPage(self): self.jumpToPage(self.pages_count) def gotoPage(self): text = self.gotoPageEdit.text() if text == "": return self.jumpToPage(int(text)) self.gotoPageEdit.clear() self.gotoPageEdit.clearFocus() ###################### Zoom and Size Management ########################## def availableWidth(self): """ Returns available width for rendering a page """ dock_width = 0 if self.dockWidget.isHidden( ) else self.dockWidget.width() return self.width() - dock_width - 50 def resizePages(self): '''Resize all pages according to zoom level ''' page_dpi = self.zoom_levels[ self.zoomLevelCombo.currentIndex()] * SCREEN_DPI / 100 fixed_width = self.availableWidth() for i in range(self.pages_count): pg_width = self.doc.page(i).pageSizeF().width() # width in points pg_height = self.doc.page(i).pageSizeF().height() if self.zoomLevelCombo.currentIndex() == 0: # if fixed width dpi = 72.0 * fixed_width / pg_width else: dpi = page_dpi self.pages[i].dpi = dpi self.pages[i].setFixedSize(pg_width * dpi / 72.0, pg_height * dpi / 72.0) for page_no in self.rendered_pages: self.pages[page_no - 1].clear() self.rendered_pages = [] self.renderCurrentPage() def setZoom(self, index): """ Gets called when zoom level is changed""" self.scroll_render_lock = True # rendering on scroll is locked as set scroll position self.resizePages() QtCore.QTimer.singleShot(300, self.afterZoom) def zoomIn(self): index = self.zoomLevelCombo.currentIndex() if index == len(self.zoom_levels) - 1: return if index == 0: index = 3 self.zoomLevelCombo.setCurrentIndex(index + 1) self.setZoom(index + 1) def zoomOut(self): index = self.zoomLevelCombo.currentIndex() if index == 1: return if index == 0: index = 4 self.zoomLevelCombo.setCurrentIndex(index - 1) self.setZoom(index - 1) def afterZoom(self): scrolbar_pos = self.pages[self.current_page - 1].pos().y() self.scrollArea.verticalScrollBar().setValue(scrolbar_pos) self.scroll_render_lock = False ######### Search Text ######### def toggleFindMode(self, enable): if enable: self.findTextEdit.setText('') self.findTextEdit.setFocus() self.search_text = '' self.search_result_page = 0 elif self.search_result_page != 0: self.pages[self.search_result_page - 1].highlight_area = None self.pages[self.search_result_page - 1].updateImage() def findNext(self): """ search text in current page and next pages """ text = self.findTextEdit.text() if text == "": return # search from current page when text changed if self.search_text != text or self.search_result_page == 0: search_from_page = self.current_page else: search_from_page = self.search_result_page + 1 self.findTextRequested.emit(text, search_from_page, False) if self.search_result_page != 0: # clear previous highlights self.pages[self.search_result_page - 1].highlight_area = None self.pages[self.search_result_page - 1].updateImage() self.search_result_page = 0 self.search_text = text def findBack(self): """ search text in pages before current page """ text = self.findTextEdit.text() if text == "": return if self.search_text != text or self.search_result_page == 0: search_from_page = self.current_page else: search_from_page = self.search_result_page - 1 self.findTextRequested.emit(text, search_from_page, True) if self.search_result_page != 0: self.pages[self.search_result_page - 1].highlight_area = None self.pages[self.search_result_page - 1].updateImage() self.search_result_page = 0 self.search_text = text def onTextFound(self, page_no, areas): self.pages[page_no - 1].highlight_area = areas self.search_result_page = page_no if self.pages[page_no - 1].pixmap(): self.pages[page_no - 1].updateImage() first_result_pos = areas[0].y() / self.doc.page(page_no - 1).pageSize().height() self.jumpToPage(page_no, first_result_pos) ######### Cpoy Text to Clip Board ######### def toggleCopyText(self, checked): self.frame.enableCopyTextMode(checked) def copyText(self, page_no, top_left, bottom_right): zoom = self.pages[page_no - 1].height() / self.doc.page(page_no - 1).pageSize().height() # Copy text to clipboard text = self.doc.page(page_no - 1).text( QtCore.QRectF(top_left / zoom, bottom_right / zoom)) QApplication.clipboard().setText(text) self.copyTextAction.setChecked(False) self.toggleCopyText(False) ########## Other Functions ########## def getOutlines(self, doc): toc = doc.toc() if not toc: self.dockWidget.hide() return self.dockWidget.show() outline_model = QStandardItemModel(self) parent_item = outline_model.invisibleRootItem() node = toc.firstChild() loadOutline(doc, node, parent_item) self.treeView.setModel(outline_model) if parent_item.rowCount() < 4: self.treeView.expandToDepth(0) self.treeView.setHeaderHidden(True) self.treeView.header().setSectionResizeMode(0, 1) self.treeView.header().setSectionResizeMode(1, 3) self.treeView.header().setStretchLastSection(False) def onOutlineClick(self, m_index): page_num = self.treeView.model().data(m_index, QtCore.Qt.UserRole + 1) top = self.treeView.model().data(m_index, QtCore.Qt.UserRole + 2) if not page_num: return self.jumpToPage(page_num, top) def showStatus(self, url): if url == "": self.statusbar.hide() return self.statusbar.setText(url) self.statusbar.adjustSize() self.statusbar.move(0, self.height() - self.statusbar.height()) self.statusbar.show() def resizeEvent(self, ev): QMainWindow.resizeEvent(self, ev) if self.filename == '': return if self.zoomLevelCombo.currentIndex() == 0: self.resize_page_timer.start(200) def onWindowResize(self): for i in range(self.pages_count): self.pages[ i].annots_listed = False # Clears prev link annotation positions self.resizePages() wait(300) self.jumpToCurrentPage() if not self.isMaximized(): self.settings.setValue("WindowWidth", self.width()) self.settings.setValue("WindowHeight", self.height()) def saveFileData(self): if self.filename != '': filename = collapseUser(self.filename) if filename in self.history_filenames: index = self.history_filenames.index(filename) self.history_page_no[index] = self.current_page else: self.history_filenames.insert(0, filename) self.history_page_no.insert(0, self.current_page) if filename in self.recent_files: self.recent_files.remove(filename) self.recent_files.insert(0, filename) def closeEvent(self, ev): """ Save all settings on window close """ self.saveFileData() self.settings.setValue("OffsetX", self.geometry().x() - self.x()) self.settings.setValue("OffsetY", self.geometry().y() - self.y()) self.settings.setValue("ZoomLevel", self.zoomLevelCombo.currentIndex()) self.settings.setValue("HistoryFileNameList", self.history_filenames[:100]) self.settings.setValue("HistoryPageNoList", self.history_page_no[:100]) self.settings.setValue("RecentFiles", self.recent_files[:10]) return QMainWindow.closeEvent(self, ev) def onAppQuit(self): """ Close running threads """ loop1 = QtCore.QEventLoop() loop2 = QtCore.QEventLoop() self.thread1.finished.connect(loop1.quit) self.thread2.finished.connect(loop2.quit) self.thread1.quit() loop1.exec_() self.thread2.quit() loop2.exec_()
class PadCalc(QWidget): def __init__(self): super().__init__() load_data() card_tags = ['leader','sub1','sub2','sub3','sub4','friend'] self.cards = { t: CardIcon() for t in card_tags } self.vlayout = QVBoxLayout(self) self.vlayout.setSpacing(0) self.setLayout(self.vlayout) self.userbox = QHBoxLayout() userfield = QLineEdit() userbutton = QPushButton('Load') userbutton.clicked.connect(lambda: self.set_user(userfield.text())) self.userbox.addWidget(userfield) self.userbox.addWidget(userbutton) userfield.returnPressed.connect(userbutton.click) self.vlayout.addLayout(self.userbox) maxcheckbox = QCheckBox('Use maxed stats?') maxcheckbox.stateChanged[int].connect(self.setMaxed) self.vlayout.addWidget(maxcheckbox) self.teamchooser = QComboBox(self) self.teamchooser.currentIndexChanged[int].connect(self.set_team) self.vlayout.addWidget(self.teamchooser) teambox = QHBoxLayout() teambox.addStretch(1) for card in card_tags: teambox.addWidget(self.cards[card]) teambox.setSpacing(0) teambox.addStretch(1) teambox.setAlignment(Qt.AlignCenter) self.vlayout.addLayout(teambox) self.board = Board() self.vlayout.addWidget(self.board) self.vlayout.itemAt(self.vlayout.indexOf(self.board)).setAlignment(Qt.AlignCenter) self.orbchooser = QHBoxLayout() b = OrbButton(value = 0) b.clicked.connect(functools.partial(self.setPaintOrb,Orb.Null)) self.orbchooser.addWidget(b) for i in ORBS: b = OrbButton(value=i) #print('Setting click value of button %s to %s' % (id(b),i)) b.clicked.connect(functools.partial(self.setPaintOrb,i)) self.orbchooser.addWidget(b) self.vlayout.addLayout(self.orbchooser) self.damagereadout = QLabel() font = QFont() font.setPointSize(30) self.damagereadout.setAlignment(Qt.AlignCenter) self.damagereadout.setFont(font) self.vlayout.addWidget(self.damagereadout) self.board.valueChanged.connect(self.update_damage) labels = ['atk','combos','leaders','enhance','prongs','rows'] lfont = QFont() lfont.setPointSize(9) vfont = QFont() vfont.setPointSize(12) self.details = {key: QVBoxLayout() for key in labels} for i in labels: label = QLabel(i) label.setFont(lfont) label.setAlignment(Qt.AlignCenter) label.setMargin(0) label.setContentsMargins(0,0,0,0) label.setIndent(0) self.details[i].label = label self.details[i].addWidget(self.details[i].label) value = QLabel('1') value.setFont(vfont) value.setAlignment(Qt.AlignCenter) value.setMargin(0) value.setIndent(0) value.setContentsMargins(0,0,0,0) self.details[i].value = value self.details[i].addWidget(self.details[i].value) self.details[i].setContentsMargins(1,1,1,1) self.detailreadout = QHBoxLayout() for i in labels: self.detailreadout.addLayout(self.details[i]) timeslabel = QLabel('\u00d7') timeslabel.setMargin(0) timeslabel.setIndent(0) timeslabel.setAlignment(Qt.AlignCenter) timeslabel.setContentsMargins(0,0,0,0) self.detailreadout.addWidget(timeslabel) self.detailreadout.takeAt(self.detailreadout.count()-1) self.vlayout.addLayout(self.detailreadout) self.vlayout.addStretch(1000) self.skillbox = QHBoxLayout() self.vlayout.addLayout(self.skillbox) #self.set_user('korora') def setMaxed(self,state): Card.use_max_stats = (state == Qt.Checked) self.drawui() self.update_damage() self.set_team(self.teamchooser.currentIndex()) def setPaintOrb(self,orb): global paintOrb paintOrb = orb def set_user(self,username): newuser = User(username) if hasattr(self,'user'): olduser = self.user.username else: olduser = '' if hasattr(newuser,'teams') and len(newuser.teams) > 0: teamchooser = self.teamchooser self.user = newuser index = teamchooser.currentIndex() try: teamchooser.currentIndexChanged[int].disconnect() except: return teamchooser.clear() for team in self.user.teams: teamchooser.addItem('%s' % (team['name'])) if newuser.username != olduser: self.set_team(0) else: teamchooser.setCurrentIndex(index) self.set_team(index) teamchooser.currentIndexChanged[int].connect(self.set_team) def update_damage(self): (match,enhanced,row) = self.board.match() nmatch = sum(len(v) for v in match.values()) nrow = sum(v for v in row.values()) (dmg, multipliers) = compute_damage((match,enhanced,row),self.team) self.damagereadout.setText('{:,}'.format(round(sum([sum(i) for i in dmg.values()])))) for i in multipliers: if i is not 'atk': self.details[i].value.setText('%.2f' % multipliers[i]) else: self.details[i].value.setText('%d' % multipliers[i]) for card in self.cards.values(): # add a damage label dam = dmg[card.card] card.main_attack = dam[0] card.sub_attack = dam[1] card.repaint() def set_team(self,index): teamdata = self.user.teams[index] team = [] for i in ['leader','sub1','sub2','sub3','sub4']: team += [Card().load_from_id(self.user,teamdata[i])] friend = { 'monster': teamdata['friend_leader'], 'plus_atk': teamdata['friend_atk'], 'plus_hp': teamdata['friend_hp'], 'plus_rcv': teamdata['friend_rcv'], 'current_awakening': teamdata['friend_awakening'], 'lv': teamdata['friend_level'], 'current_skill': teamdata['friend_skill'] } team += [Card().load_from_card(friend)] self.team = Team(team) #print('|'+self.teamchooser.itemText(index)+'|') #print(len(self.teamchooser.itemText(index))) self.teamchooser.setMinimumContentsLength(len(self.teamchooser.itemText(index))-3) for i in range(self.skillbox.count()): w = self.skillbox.takeAt(i) w.widget().deleteLater() #svc = SkillViewController(skill=self.team.lskills[0]) #svc.skillsChanged.connect(self.update_damage) #self.skillbox.addWidget(svc) self.drawui() self.update_damage() def drawui(self): self.cards['leader'].card = (self.team.cards[0]) self.cards['sub1'].card = (self.team.cards[1]) self.cards['sub2'].card = (self.team.cards[2]) self.cards['sub3'].card = (self.team.cards[3]) self.cards['sub4'].card = (self.team.cards[4]) self.cards['friend'].card = (self.team.cards[5]) for card in self.cards.values(): card.load_icon()
class Plugin(Plugin_Base): ''' call sequence: set vars like hintSignal, hintSignal onInit onWidget onUiInitDone send onReceived getConfig ''' # vars set by caller isConnected = lambda: False send = lambda x, y: None # send(data_bytes=None, file_path=None, callback=lambda ok,msg:None) hintSignal = None # hintSignal.emit(type(error, warning, info), title, msg) configGlobal = {} # other vars connParent = "dbg" # parent id connChilds = [] # children ids id = "protocol" name = _("protocol") enabled = False # user enabled this plugin active = False # using this plugin showReceiveDataSignal = pyqtSignal(str) def __init__(self): super().__init__() if not self.id: raise ValueError(f"var id of Plugin {self} should be set") def onInit(self, config): ''' init params, DO NOT take too long time in this func ''' default = { "version": 1, "sendAscii": True, "useCRLF": False, "sendEscape": True, "code": defaultProtocols.copy(), "currCode": "default", "customSendItems": [{ "text": "hello", "remark": "hello", "icon": "fa5.hand-paper" }, { "text": "\\x01\\x03\\x03\\x03\\x03\\x01", "remark": "pre", "icon": "ei.arrow-left", "shortcut": [[16777234, "Left"]] }, { "text": "\\x01\\x04\\x04\\x04\\x04\\x01", "remark": "next", "icon": "ei.arrow-right", "shortcut": [[16777236, "Right"]] }, { "text": "\\x01\\x01\\x01\\x01\\x01\\x01", "remark": "ok", "icon": "fa.circle-o", "shortcut": [[16777220, "Return"]] }, { "text": "\\x01\\x02\\x02\\x02\\x02\\x01", "remark": "ret", "icon": "ei.return-key", "shortcut": [[16777216, "Esc"]] }] } self.config = config for k in default: if not k in self.config: self.config[k] = default[k] self.editingDefaults = False self.codeGlobals = { "unpack": unpack, "pack": pack, "crc": crc, "encoding": self.configGlobal["encoding"], "print": self.print } self.encodeMethod = lambda x: x self.decodeMethod = lambda x: x self.pressedKeys = [] self.keyModeClickTime = 0 def print(self, *args, **kw_args): end = "\n" start = "[MSG]: " if "end" in kw_args: end = kw_args["end"] if "start" in kw_args: start = kw_args["start"] string = start + " ".join(map(str, args)) + end self.showReceiveDataSignal.emit(string) class ModeButton(QPushButton): onFocusIn = pyqtSignal(QFocusEvent) onFocusOut = pyqtSignal(QFocusEvent) def __init__(self, text, eventFilter, parent=None) -> None: super().__init__(text, parent) self.installEventFilter(eventFilter) def focusInEvent(self, event): self.onFocusIn.emit(event) def focusOutEvent(self, event): self.onFocusOut.emit(event) def onWidgetMain(self, parent): self.mainWidget = QSplitter(Qt.Vertical) self.receiveWidget = TextEdit() font = QFont( 'Menlo,Consolas,Bitstream Vera Sans Mono,Courier New,monospace, Microsoft YaHei', 10) self.receiveWidget.setFont(font) self.receiveWidget.setLineWrapMode(TextEdit.NoWrap) self.clearBtn = QPushButton("") self.keyBtneventFilter = self.ModeButtonEventFilter( self.onModeBtnKeyPressEvent, self.onModeBtnKeyReleaseEvent) self.keyModeBtn = self.ModeButton(_("Key mode"), self.keyBtneventFilter) layoutClearMode = QHBoxLayout() layoutClearMode.addWidget(self.clearBtn) layoutClearMode.addWidget(self.keyModeBtn) clearModeWidget = QWidget() clearModeWidget.setLayout(layoutClearMode) utils_ui.setButtonIcon(self.clearBtn, "mdi6.broom") self.addButton = QPushButton("") utils_ui.setButtonIcon(self.addButton, "fa.plus") self.customSendScroll = QScrollArea() self.customSendScroll.setMinimumHeight( parameters.customSendItemHeight + 20) self.customSendScroll.setWidgetResizable(True) self.customSendScroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.customSendScroll.setHorizontalScrollBarPolicy( Qt.ScrollBarAlwaysOff) cutomSendItemsWraper = QWidget() self.customSendScroll.setWidget(cutomSendItemsWraper) # wrapper widget cutomSendItemsWraper0 = QWidget() cutomSendItemsWraper0.setProperty("class", "scrollbar2") layout0 = QVBoxLayout() layout0.setContentsMargins(0, 8, 0, 0) cutomSendItemsWraper0.setLayout(layout0) layout0.addWidget(self.customSendScroll) customSendItemsLayoutWrapper = QVBoxLayout() customSendItemsLayoutWrapper.setContentsMargins(0, 0, 0, 0) cutomSendItemsWraper.setLayout(customSendItemsLayoutWrapper) # items container self.customItems = QWidget() self.customSendItemsLayout = QVBoxLayout() self.customSendItemsLayout.setContentsMargins(0, 0, 0, 0) self.customItems.setLayout(self.customSendItemsLayout) customSendItemsLayoutWrapper.addWidget(self.customItems) customSendItemsLayoutWrapper.addWidget(self.addButton) customSendItemsLayoutWrapper.addStretch(0) self.mainWidget.addWidget(self.receiveWidget) self.mainWidget.addWidget(clearModeWidget) self.mainWidget.addWidget(cutomSendItemsWraper0) self.mainWidget.setStretchFactor(0, 2) self.mainWidget.setStretchFactor(1, 1) self.mainWidget.setStretchFactor(2, 11) # event self.addButton.clicked.connect(lambda: self.insertSendItem()) def clearReceived(): self.receiveWidget.clear() self.statusBar.clear() self.clearBtn.clicked.connect(clearReceived) def keyModeOn(event): self.keyModeBtn.setProperty("class", "deleteBtn") utils_ui.updateStyle(self.mainWidget, self.keyModeBtn) self.keyModeClickTime = time.time() # show all shortcut widgets = self.customItems.findChildren(QPushButton, "editRemark") for i, w in enumerate(widgets): shortcut = "+".join( (name for v, name in self.config["customSendItems"][i] ["shortcut"])) w.setText(shortcut) utils_ui.updateStyle(self.mainWidget, w) def keyModeOff(event): self.keyModeBtn.setProperty("class", "") utils_ui.updateStyle(self.mainWidget, self.keyModeBtn) self.keyModeClickTime = 0 # remove all preesed keys even them not release actually, to avoid window swith by ALT+TAB bug self.pressedKeys = [] # hide all shortcut widgets = self.customItems.findChildren(QPushButton, "editRemark") for w in widgets: w.setText("") def keyModeTuggle(): if self.keyModeBtn.property("class") == "deleteBtn": if time.time() - self.keyModeClickTime < 0.2: return else: self.keyModeBtn.clearFocus() self.keyModeBtn.onFocusIn.connect(keyModeOn) self.keyModeBtn.onFocusOut.connect(keyModeOff) self.keyModeBtn.clicked.connect(keyModeTuggle) return self.mainWidget def onWidgetSettings(self, parent): root = QWidget() rootLayout = QVBoxLayout() rootLayout.setContentsMargins(0, 0, 0, 0) root.setLayout(rootLayout) setingGroup = QGroupBox(_("En-decoding settings")) layout = QGridLayout() setingGroup.setLayout(layout) self.codeItems = ComboBox() self.codeItemCustomStr = _("Custom, input name") self.codeItemLoadDefaultsStr = _("Load defaults") self.codeItems.setEditable(True) self.codeWidget = PlainTextEdit() self.saveCodeBtn = QPushButton(_("Save")) self.saveCodeBtn.setEnabled(False) self.deleteCodeBtn = QPushButton(_("Delete")) btnLayout = QHBoxLayout() btnLayout.addWidget(self.saveCodeBtn) btnLayout.addWidget(self.deleteCodeBtn) layout.addWidget(QLabel(_("Defaults")), 0, 0, 1, 1) layout.addWidget(self.codeItems, 0, 1, 1, 1) layout.addWidget(QLabel(_("Code")), 1, 0, 1, 1) layout.addWidget(self.codeWidget, 1, 1, 1, 1) layout.addLayout(btnLayout, 2, 1, 1, 1) serialSendSettingsLayout = QGridLayout() sendGroup = QGroupBox(_("Send settings")) sendGroup.setLayout(serialSendSettingsLayout) self.sendSettingsAscii = QRadioButton(_("ASCII")) self.sendSettingsHex = QRadioButton(_("HEX")) self.sendSettingsAscii.setToolTip( _("Get send data as visible format, select encoding method at top right corner" )) self.sendSettingsHex.setToolTip( _("Get send data as hex format, e.g. hex '31 32 33' equal to ascii '123'" )) self.sendSettingsAscii.setChecked(True) self.sendSettingsCRLF = QCheckBox(_("<CRLF>")) self.sendSettingsCRLF.setToolTip( _("Select to send \\r\\n instead of \\n")) self.sendSettingsCRLF.setChecked(False) self.sendSettingsEscape = QCheckBox(_("Escape")) self.sendSettingsEscape.setToolTip( _("Enable escape characters support like \\t \\r \\n \\x01 \\001")) serialSendSettingsLayout.addWidget(self.sendSettingsAscii, 0, 0, 1, 1) serialSendSettingsLayout.addWidget(self.sendSettingsHex, 0, 1, 1, 1) serialSendSettingsLayout.addWidget(self.sendSettingsCRLF, 1, 0, 1, 1) serialSendSettingsLayout.addWidget(self.sendSettingsEscape, 1, 1, 1, 1) rootLayout.addWidget(sendGroup) rootLayout.addWidget(setingGroup) # event self.sendSettingsAscii.clicked.connect(lambda: self.bindVar( self.sendSettingsAscii, self.config, "sendAscii", bool)) self.sendSettingsHex.clicked.connect(lambda: self.bindVar( self.sendSettingsHex, self.config, "sendAscii", bool, invert=True)) self.sendSettingsCRLF.clicked.connect(lambda: self.bindVar( self.sendSettingsCRLF, self.config, "useCRLF", bool)) self.sendSettingsEscape.clicked.connect(lambda: self.bindVar( self.sendSettingsEscape, self.config, "sendEscape", bool)) self.saveCodeBtn.clicked.connect(self.saveCode) self.deleteCodeBtn.clicked.connect(self.deleteCode) self.codeWidget.onSave = self.saveCode return root def onWidgetStatusBar(self, parent): self.statusBar = statusBar(rxTxCount=True) return self.statusBar def onUiInitDone(self): ''' UI init done, you can update your widget here this method runs in UI thread, do not block too long ''' newItems = [] for item in self.config["customSendItems"]: item = self.insertSendItem(item, load=True) newItems.append(item) self.config["customSendItems"] = newItems self.sendSettingsAscii.setChecked(self.config["sendAscii"]) self.sendSettingsHex.setChecked(not self.config["sendAscii"]) self.sendSettingsCRLF.setChecked(self.config["useCRLF"]) self.sendSettingsEscape.setChecked(self.config["sendEscape"]) self.showReceiveDataSignal.connect(self.showReceivedData) # init decoder and encoder for k in self.config["code"]: self.codeItems.addItem(k) self.codeItems.addItem(self.codeItemCustomStr) self.codeItems.addItem(self.codeItemLoadDefaultsStr) name = self.config["currCode"] idx = self.codeItems.findText(self.config["currCode"]) if idx < 0: idx = 0 name = "default" self.codeItems.setCurrentIndex(idx) self.selectCode(name) self.codeItems.currentIndexChanged.connect( self.onCodeItemChanged ) # add here to avoid self.selectCode trigger self.codeWidget.textChanged.connect(self.onCodeChanged) class ModeButtonEventFilter(QObject): def __init__(self, keyPressCb, keyReleaseCb) -> None: super().__init__() self.keyPressCb = keyPressCb self.keyReleaseCb = keyReleaseCb def eventFilter(self, obj, evt): if evt.type() == QEvent.KeyPress: # prevent default key events self.keyPressCb(evt) return True elif evt.type() == QEvent.KeyRelease: self.keyReleaseCb(evt) return True return False def onModeBtnKeyPressEvent(self, event): # send by shortcut key = event.key() self.pressedKeys.append(key) for item in self.config["customSendItems"]: if not "shortcut" in item: continue shortcut = item["shortcut"] if len(shortcut) == len(self.pressedKeys): same = True for i in range(len(shortcut)): if shortcut[i][0] != self.pressedKeys[i]: same = False break if same: self.sendCustomItem(item) def onModeBtnKeyReleaseEvent(self, event): key = event.key() if key in self.pressedKeys: self.pressedKeys.remove(key) def onKeyPressEvent(self, event): pass def onKeyReleaseEvent(self, event): pass def insertSendItem(self, item=None, load=False): # itemsNum = self.customSendItemsLayout.count() + 1 # height = parameters.customSendItemHeight * (itemsNum + 1) + 20 # topHeight = self.receiveWidget.height() + 100 # if height + topHeight > self.funcParent.height(): # height = self.funcParent.height() - topHeight # if height < 0: # height = self.funcParent.height() // 3 # self.customSendScroll.setMinimumHeight(height) if not item: item = {"text": "", "remark": None, "icon": None} if type(item) == str: item = {"text": item, "remark": None} text = item["text"] remark = item["remark"] itemWidget = QWidget() layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) itemWidget.setLayout(layout) cmd = QLineEdit(text) if remark: send = QPushButton(remark) else: send = QPushButton("") if (not "icon" in item) or not item["icon"]: item["icon"] = "fa.send" if not "shortcut" in item: item["shortcut"] = [] utils_ui.setButtonIcon(send, item["icon"]) editRemark = QPushButton("") editRemark.setObjectName("editRemark") utils_ui.setButtonIcon(editRemark, "ei.pencil") editRemark.setProperty("class", "remark") cmd.setToolTip(text) send.setToolTip(text) cmd.textChanged.connect(lambda: self.onCustomItemChange( self.customSendItemsLayout.indexOf(itemWidget), cmd, send)) send.setProperty("class", "smallBtn") def sendCustomData(idx): self.sendCustomItem(self.config["customSendItems"][idx]) send.clicked.connect(lambda: sendCustomData( self.customSendItemsLayout.indexOf(itemWidget))) delete = QPushButton("") utils_ui.setButtonIcon(delete, "fa.close") delete.setProperty("class", "deleteBtn") layout.addWidget(cmd) layout.addWidget(send) layout.addWidget(editRemark) layout.addWidget(delete) delete.clicked.connect(lambda: self.deleteSendItem( self.customSendItemsLayout.indexOf(itemWidget), itemWidget, [send, editRemark, delete])) def changeRemark(idx, obj): if not "icon" in self.config["customSendItems"][idx]: self.config["customSendItems"][idx]["icon"] = None shortcut = [] if "shortcut" in self.config["customSendItems"][idx]: shortcut = self.config["customSendItems"][idx]["shortcut"] ok, remark, icon, shortcut = EditRemarDialog( obj.text(), self.config["customSendItems"][idx]["icon"], shortcut).exec() if ok: obj.setText(remark) if icon: utils_ui.setButtonIcon(obj, icon) else: obj.setIcon(QIcon()) self.config["customSendItems"][idx]["remark"] = remark self.config["customSendItems"][idx]["icon"] = icon self.config["customSendItems"][idx]["shortcut"] = shortcut editRemark.clicked.connect(lambda: changeRemark( self.customSendItemsLayout.indexOf(itemWidget), send)) self.customSendItemsLayout.addWidget(itemWidget) if not load: self.config["customSendItems"].append(item) return item def deleteSendItem(self, idx, item, iconItems=[]): for obj in iconItems: utils_ui.clearButtonIcon(obj) item.setParent(None) self.config["customSendItems"].pop(idx) # itemsNum = self.customSendItemsLayout.count() # height = parameters.customSendItemHeight * (itemsNum + 1) + 20 # topHeight = self.receiveWidget.height() + 100 # if height + topHeight > self.funcParent.height(): # height = self.funcParent.height() - topHeight # self.customSendScroll.setMinimumHeight(height) def showReceivedData(self, text: str): curScrollValue = self.receiveWidget.verticalScrollBar().value() self.receiveWidget.moveCursor(QTextCursor.End) endScrollValue = self.receiveWidget.verticalScrollBar().value() self.receiveWidget.insertPlainText(text) if curScrollValue < endScrollValue: self.receiveWidget.verticalScrollBar().setValue(curScrollValue) else: self.receiveWidget.moveCursor(QTextCursor.End) def onReceived(self, data: bytes): self.statusBar.addRx(len(data)) try: data = self.decodeMethod(data) except Exception as e: self.hintSignal.emit("error", _("Error"), _("Run decode error") + " " + str(e)) return if not data: return for plugin in self.connChilds: plugin.onReceived(data) if type(data) != str: data = self.decodeReceivedData(data, self.configGlobal["encoding"], not self.config["sendAscii"], self.config["sendEscape"]) self.showReceiveDataSignal.emit(data + "\n") def sendData(self, data_bytes=None): try: data_bytes = self.encodeMethod(data_bytes) except Exception as e: self.hintSignal.emit("error", _("Error"), _("Run encode error") + " " + str(e)) return if data_bytes: self.send(data_bytes, callback=self.onSent) def onSent(self, ok, msg, length, path): if ok: self.statusBar.addTx(length) else: self.hintSignal.emit("error", _("Error"), _("Send data failed!") + " " + msg) def sendCustomItem(self, item): text = item["text"] dateBytes = self.parseSendData(text, self.configGlobal["encoding"], self.config["useCRLF"], not self.config["sendAscii"], self.config["sendEscape"]) if dateBytes: self.sendData(data_bytes=dateBytes) def onCustomItemChange(self, idx, edit, send): text = edit.text() edit.setToolTip(text) send.setToolTip(text) self.config["customSendItems"][idx].update({ "text": text, "remark": send.text() }) def onCodeItemChanged(self): if self.editingDefaults: return self.editingDefaults = True if self.codeItems.currentText() == self.codeItemCustomStr: self.codeItems.clearEditText() self.editingDefaults = False return if self.codeItems.currentText() == self.codeItemLoadDefaultsStr: for name in defaultProtocols: idx = self.codeItems.findText(name) if idx >= 0: self.codeItems.removeItem(idx) self.codeItems.insertItem(self.codeItems.count() - 2, name) self.config["code"][name] = defaultProtocols[name] self.codeItems.setCurrentIndex(0) self.selectCode(self.codeItems.currentText()) self.editingDefaults = False return # update code from defaults self.selectCode(self.codeItems.currentText()) self.editingDefaults = False def selectCode(self, name): if name in [self.codeItemCustomStr, self.codeItemLoadDefaultsStr ] or not name or not name in self.config["code"]: print(f"name {name} invalid") return self.config["currCode"] = name self.codeWidget.clear() self.codeWidget.insertPlainText(self.config["code"][name]) ok, e, d = self.getEnDecodeMethod(self.codeWidget.toPlainText()) if ok: self.encodeMethod = e self.decodeMethod = d self.saveCodeBtn.setText(_("Save")) self.saveCodeBtn.setEnabled(False) def getEnDecodeMethod(self, code): func = lambda x: x try: exec(code, self.codeGlobals) if (not "decode" in self.codeGlobals) or not "encode" in self.codeGlobals: raise ValueError( _("decode and encode method should be in code")) return True, self.codeGlobals["encode"], self.codeGlobals["decode"] except Exception as e: msg = _("Method error") + "\n" + str(e) self.hintSignal.emit("error", _("Error"), msg) return False, func, func def onCodeChanged(self): changed = True name = self.codeItems.currentText() if name in self.config["code"]: codeSaved = self.config["code"][name] code = self.codeWidget.toPlainText() if code == codeSaved: changed = False if changed: self.saveCodeBtn.setText(_("Save") + " *") self.saveCodeBtn.setEnabled(True) else: self.saveCodeBtn.setText(_("Save")) self.saveCodeBtn.setEnabled(False) def saveCode(self): self.editingDefaults = True name = self.codeItems.currentText() if name in [self.codeItemCustomStr, self.codeItemLoadDefaultsStr ] or not name: self.hintSignal.emit("warning", _("Warning"), _("Please input code profile name first")) self.editingDefaults = False return idx = self.codeItems.findText(name) if idx < 0: self.codeItems.insertItem(self.codeItems.count() - 2, name) self.editingDefaults = False code = self.codeWidget.toPlainText() ok, e, d = self.getEnDecodeMethod(code) if ok: self.encodeMethod = e self.decodeMethod = d self.config["code"][name] = code self.saveCodeBtn.setText(_("Save")) self.saveCodeBtn.setEnabled(False) def deleteCode(self): self.editingDefaults = True name = self.codeItems.currentText() itemsConfig = [self.codeItemCustomStr, self.codeItemLoadDefaultsStr] # QMessageBox.infomation() if name in itemsConfig or not name: self.hintSignal.emit( "warning", _("Warning"), _("Please select a code profile name first to delete")) self.editingDefaults = False return idx = self.codeItems.findText(name) if idx < 0: self.editingDefaults = False return self.codeItems.removeItem(idx) self.config["code"].pop(name) name = list(self.config["code"].keys()) if len(name) > 0: name = name[0] self.codeItems.setCurrentText(name) self.selectCode(name) self.editingDefaults = False