def load_pass_file(self, pass_file_object): """ populates the window with pass_file data :param pass_file_object: a PassFile :return: None """ self.clear() self.pass_file = pass_file_object password_field_label = QLabel('password') password_field = QLineEdit() password_field.setEchoMode(QLineEdit.EchoMode(2)) password_field.setDisabled(True) copy_password_button = QPushButton('copy') copy_password_button.clicked.connect(self.copy_password) self.layout().addWidget(password_field_label, 0, 0) self.layout().addWidget(password_field, 0, 1) self.layout().addWidget(copy_password_button, 0, 2) password_field.setText(self.pass_file.password) for key, value in self.pass_file.attributes: current_grid_view_row = self.layout().rowCount() additional_field_label = QLabel(key) additional_field = QLineEdit() self.layout().addWidget(additional_field_label, current_grid_view_row + 1, 0) self.layout().addWidget(additional_field, current_grid_view_row + 1, 1) additional_field.setText(value) comment_browser_label = QLabel('comments') comment_browser = QTextBrowser() self.layout().addWidget(comment_browser_label, self.layout().rowCount() + 1, 0) self.layout().addWidget(comment_browser, self.layout().rowCount(), 1) comment_browser.setPlainText(self.pass_file.comments)
def createSourceSelect(self, init_text): filePath = QLineEdit() filePath.setPlaceholderText("Choose " + init_text + " folder") filePath.setDisabled(True) btn = QPushButton("Browse", self) if init_text == "source": btn.clicked.connect(lambda: self.getMultipleSelected(init_text)) else: btn.clicked.connect(lambda: self.getSeletedFile(init_text)) return filePath, btn
class Window(QWidget): def __init__(self): super().__init__() self.threadpool = QThreadPool() self.sourcefile = QLineEdit() self.sourcefile.setDisabled( True) # must use the file finder to select a valid file. self.file_select = QPushButton("Select CSV...") self.file_select.pressed.connect(self.choose_csv_file) self.generate_btn = QPushButton("Generate PDF") self.generate_btn.pressed.connect(self.generate) layout = QFormLayout() layout.addRow(self.sourcefile, self.file_select) layout.addRow(self.generate_btn) self.setLayout(layout) def choose_csv_file(self): filename, _ = QFileDialog.getOpenFileName(self, "Select a file", filter="CSV files (*.csv)") if filename: self.sourcefile.setText(filename) def generate(self): if not self.sourcefile.text(): return # If the field is empty, ignore. self.generate_btn.setDisabled(True) data = { 'sourcefile': self.sourcefile.text(), } g = Generator(data) g.signals.finished.connect(self.generated) g.signals.error.connect(print) # Print errors to console. self.threadpool.start(g) def generated(self): self.generate_btn.setDisabled(False) QMessageBox.information(self, "Finished", "PDFs have been generated")
class ClientRegister(QDialog): def __init__(self): super(ClientRegister, self).__init__() self.setFixedSize(300, 150) self.setWindowIcon(QIcon('icons/auth.png')) self.setWindowTitle('客户端注册') client = DbHelper.query_client() lbl_app_id = QLabel('APP ID', self) lbl_app_id.setGeometry(10, 20, 50, 26) lbl_app_id.setAlignment(Qt.AlignCenter) self.le_app_id = QLineEdit(self) self.le_app_id.setText(client[0] if client is not None else '') self.le_app_id.setGeometry(70, 20, 200, 26) self.le_app_id.setDisabled(True if client is not None else False) lbl_security = QLabel('密钥', self) lbl_security.setGeometry(10, 66, 50, 26) lbl_security.setAlignment(Qt.AlignCenter) self.le_security = QLineEdit(self) self.le_security.setEchoMode(QLineEdit.Password) self.le_security.setText(client[1] if client is not None else '') self.le_security.setGeometry(70, 66, 200, 26) self.le_security.setDisabled(True if client is not None else False) self.btn_save = QPushButton(self) self.btn_save.setText('已注册' if client is not None else '注册') self.btn_save.setDisabled(True if client is not None else False) self.btn_save.setGeometry(10, 110, 280, 30) self.btn_save.clicked.connect(self.register) def register(self): app_id = self.le_app_id.text().strip(' ') if app_id == '': self.btn_save.setText('请输入APP ID') self.le_app_id.setFocus() return security = self.le_security.text().strip(' ') if security == '': self.btn_save.setText('请输入密钥') self.le_security.setFocus() return self.btn_save.setDisabled(True) result, data = Tool.client_register(app_id, Tool.get_md5(security)) if result: DbHelper.insert_client(data, app_id, security) self.le_app_id.setDisabled(True) self.le_security.setDisabled(True) self.btn_save.setText("注册成功") elif data is not None: self.btn_save.setDisabled(False) self.btn_save.setText(data) else: self.btn_save.setDisabled(False) self.btn_save.setText("注册失败")
class FolderPickerView(QWidget): """Provides a view which facilitates selecting the source folder where renamable files are found. Attributes: layout (QHBoxLayout): Main layout for view. label (QLabel): Label for file picker widget. base_dir (QLineEdit): LineEdit which displays location of where renamable files live. file_browser_btn (QPushButton): Button which, when pressed, presents user with a file browsers. refresh_btn (QPushButton): Button which, when pressed, Refreshes files in selected directory. """ def __init__(self): super(FolderPickerView, self).__init__() self.layout = QHBoxLayout() self.label = QLabel(prefs.FILE_LOCATION) self.base_dir = QLineEdit(prefs.SELECT_DIR) self.file_browser_btn = QPushButton(prefs.OPEN_DIR) self.refresh_btn = QPushButton(prefs.REFRESH) self._configure() def _configure(self) -> None: """Configuration of FolderPickerView.""" self.base_dir.setMinimumHeight(prefs.MIN_HEIGHT) self.base_dir.setDisabled(True) self.layout.addWidget(self.label) self.layout.addWidget(self.base_dir) self.layout.addWidget(self.file_browser_btn) self.layout.addWidget(self.refresh_btn) self.setLayout(self.layout) def get_base_dir(self) -> str: """Return path to basedir.""" return str(self.base_dir.text()) def get_base_dir_set(self) -> bool: """Return if the user has selected a base dir.""" dir_path = self.get_base_dir() return dir_path and dir_path != prefs.SELECT_DIR def set_base_dir(self, dir_path: str) -> None: """Set the value of base dir. Args: dir_path : Value to add to the base dir field. """ self.base_dir.setText(dir_path)
class LabelTextBox(QWidget): def __init__(self, label, orientation="V", read_only=True, line=False, *args, **kwargs): super(LabelTextBox, self).__init__(*args, **kwargs) self.layout = QVBoxLayout() if orientation == "V" else QHBoxLayout() self.label = QLabel(label) self.text_box = QLineEdit() if line else QTextEdit() self._read_only = read_only self._line_edit = line self._text = "" self._setup() def _setup(self): self.text_box.setReadOnly(self._read_only) if not self._line_edit: self.text_box.setFixedSize(400, 200) self.layout.addWidget(self.label) self.layout.addWidget(self.text_box) self.setLayout(self.layout) def text(self): return self.text_box.text() def set_text(self, text): self.text_box.setText(text) def init_update(self, data): print("Just joined? Let's set up all the data we know about") pass def update(self, data): print(f"Updating {self} with data - {data}") def setDisabled(self, disabled): self.text_box.setDisabled(disabled) def isEnabled(self): return self.text_box.isEnabled()
class QTagWidget(QWidget): def __init__(self, parent, items): super(QTagWidget, self).__init__() self.parent = parent self.items = items self.tags = [] self.mainFrame = QFrame() self.mainFrame.setStyleSheet( 'border:1px solid #76797C; border-radius: 1px;') self.mainLayout = QVBoxLayout() self.mainLayout.setContentsMargins(0, 0, 0, 0) self.setLayout(self.mainLayout) self.mainLayout.addWidget(self.mainFrame) self.hLayout = QHBoxLayout() self.hLayout.setSpacing(4) self.lineEdit = QLineEdit() self.lineEdit.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum) completer = QPartialMatchCompleter(self.lineEdit) completer.setCompletionMode(QCompleter.PopupCompletion) self.lineEdit.setCompleter(completer) model = QStringListModel() completer.setModel(model) model.setStringList(self.items) self.mainFrame.setLayout(self.hLayout) self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.hLayout.setContentsMargins(2, 2, 2, 2) self.refresh() self.setup_ui() def show(self): self.show() def setup_ui(self): self.lineEdit.returnPressed.connect(self.create_tags) def create_tags(self): new_tags = self.lineEdit.text().split(', ') self.lineEdit.setText('') self.tags.extend(new_tags) self.tags = list(set(self.tags)) self.tags.sort(key=lambda x: x.lower()) self.refresh() def refresh(self): for i in reversed(range(self.hLayout.count())): self.hLayout.itemAt(i).widget().setParent(None) for tag in self.tags: self.add_tag_to_bar(tag) self.hLayout.addWidget(self.lineEdit) self.lineEdit.setFocus() # Accept to add only 5 tags if len(self.tags) >= 5: self.lineEdit.setDisabled(True) return def add_tag_to_bar(self, text): tag = QFrame() tag.setStyleSheet( 'border:1px solid rgb(192, 192, 192); border-radius: 4px;') tag.setContentsMargins(2, 2, 2, 2) tag.setFixedHeight(28) hbox = QHBoxLayout() hbox.setContentsMargins(4, 4, 4, 4) hbox.setSpacing(10) tag.setLayout(hbox) label = QLabel(text) label.setStyleSheet('border:0px') label.setFixedHeight(16) hbox.addWidget(label) x_button = QPushButton('x') x_button.setFixedSize(20, 20) x_button.setStyleSheet('border:0px; font-weight:bold') x_button.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) x_button.clicked.connect(partial(self.delete_tag, text)) hbox.addWidget(x_button) tag.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred) self.hLayout.addWidget(tag) def delete_tag(self, tag_name): self.tags.remove(tag_name) # Make input available if tags count is less than 5 if len(self.tags) < 5: self.lineEdit.setDisabled(False) self.refresh()
class NetworkSettingsDialog(QDialog): def __init__(self, parent=None): super(NetworkSettingsDialog, self).__init__(parent) self.parent = parent self.setModal(True) self.setWindowTitle("Hálózati beállítások") self.resize(400, 300) self.layout = QVBoxLayout() self.setLayout(self.layout) adatok = QGridLayout() self.layout.addLayout(adatok) self.ip = QLineEdit() self.token = QLineEdit() self.station = QLineEdit() self.valtozott = False self.uj_station = False self.is_exist_config() self.check_config() adatok.addWidget(QLabel("IP cím: "), 0, 0) self.ip.setDisabled(True) adatok.addWidget(self.ip, 0, 1) adatok.addWidget(QLabel("Token: "), 1, 0) self.token.setDisabled(True) adatok.addWidget(self.token, 1, 1) adatok.addWidget(QLabel("Állomás azonosító: "), 2, 0) self.station.setDisabled(True) self.station.editingFinished.connect(self.change_name) adatok.addWidget(self.station, 2, 1) self.space = QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding) self.layout.addItem(self.space) self.buttonbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonbox.addButton("Módosít", QDialogButtonBox.ActionRole) self.buttonbox.button(QDialogButtonBox.Ok).setText('Mentés') self.buttonbox.button(QDialogButtonBox.Ok).setDisabled(True) self.buttonbox.button(QDialogButtonBox.Cancel).setText('Mégsem') self.buttonbox.clicked.connect(self.buttonbox_click) self.layout.addWidget(self.buttonbox) def is_exist_config(self): ip_fizikai = get_ip_address() if os.path.exists('config.ini'): # Van config.ini, ki kell értékelni config.read('config.ini') station_id = config['DEFAULT'].get('station id') if (station_id is None) or len(station_id) < 4: self.valtozott = True msg = QMessageBox() msg.setStyleSheet("fonz-size: 20px") msg.setWindowTitle("Hibás állomás név!") msg.setText( '<html style="font-size: 14px; color: red">Nem megfelelő állomás-azonosító!<br></html>' + '<html style="font-size: 16px">Kérem módosítsa a beállításokat!</html>' ) msg.exec_() station = secrets.token_hex(8) config.set('DEFAULT', 'station id', station) self.station.setText(station) ip_config = config['DEFAULT'].get('station ip') if ip_fizikai != ip_config: self.valtozott = True msg = QMessageBox() msg.setStyleSheet("fonz-size: 20px") msg.setWindowTitle("Hibás beállítás!") msg.setText( '<html style="font-size: 14px; color: red">A fizikai IP cím eltér a konfigurációtól!<br></html>' + '<html style="font-size: 16px">Kérem módosítsa a beállításokat!</html>' ) msg.exec_() self.ip.setText(ip_fizikai) config.set('DEFAULT', 'station ip', ip_fizikai) secret = config['DEFAULT'].get('secret key') if (secret is None) or len(secret) != 32: self.valtozott = True newsecret = secrets.token_hex(16) config.set('DEFAULT', 'secret key', newsecret) self.token.setText(newsecret) with open('config.ini', 'w') as configfile: config.write(configfile) else: # Nincs config.ini, alapértékekkel inicializálni self.valtozott = True msg = QMessageBox() msg.setStyleSheet("fonz-size: 20px") msg.setWindowTitle("Hiányzó beállítás file!") msg.setText( '<html style="font-size: 14px; color: red">Nem tudtam beolvasni a konfigurációt!<br></html>' + '<html style="font-size: 16px">Kérem módosítsa a beállításokat!</html>' ) msg.exec_() kulcs = secrets.token_hex(16) station = secrets.token_hex(8) self.token.setText(kulcs) self.ip.setText(ip_fizikai) self.station.setText(station) config.set('DEFAULT', 'secret key', kulcs) config.set('DEFAULT', 'station ip', ip_fizikai) config.set('DEFAULT', 'station id', secrets.token_hex(8)) with open('config.ini', 'w') as configfile: config.write(configfile) def check_config(self): ip_config = config['DEFAULT'].get('station ip') station_id = config['DEFAULT'].get('station id') secret = config['DEFAULT'].get('secret key') self.ip.setText(ip_config) self.station.setText(station_id) self.token.setText(secret) # todo Ellenőrizni, hogy van-e, és mi van a db-ben model2 = QSqlQueryModel() query = QSqlQuery( f"SELECT * FROM reged_station where secret_key = '{secret}'", db=db) model2.setQuery(query) if model2.record(0).value(0): if self.station.text() != model2.record(0).value( 1) or self.ip.text() != model2.record(0).value( 2) or self.token.text() != model2.record(0).value(3): self.valtozott = True self.rec_id = model2.record(0).value(0) else: self.uj_station = True def change_name(self): self.valtozott = True def buttonbox_click(self, b): if b.text() == "Mentés": self.accept() elif b.text() == "Módosít": self.modify() else: self.reject() def modify(self): self.buttonbox.button(QDialogButtonBox.Ok).setDisabled(False) self.station.setDisabled(False) def accept(self): if self.uj_station: now = QDateTime.currentDateTime() network = QSqlTableModel() network.setTable("reged_station") rec_net = network.record() rec_net.remove(0) rec_net.setValue(0, self.station.text()) rec_net.setValue(1, self.ip.text()) rec_net.setValue(2, self.token.text()) rec_net.setValue(3, now) if network.insertRecord(-1, rec_net): network.submitAll() else: db.rollback() self.uj_station = False if self.valtozott: # Módosítani a db-t és a config.ini-t # config.ini update config.set('DEFAULT', 'secret key', self.token.text()) config.set('DEFAULT', 'station ip', self.ip.text()) config.set('DEFAULT', 'station id', self.station.text()) with open('config.ini', 'w') as configfile: config.write(configfile) # db update now = QDateTime.currentDateTime().toString( "yyyy-MM-ddThh:mm:ss.sss") model3 = QSqlQueryModel() query3 = QSqlQuery( f"update reged_station set station_id='{self.station.text()}', station_ip='{self.ip.text()}', timestamp='{now}' where secret_key='{self.token.text()}'", db=db) model3.setQuery(query3) self.valtozott = False super().accept() def reject(self): # Ha valami db művelet kell if self.valtozott or self.uj_station: msg = QMessageBox() msg.setStyleSheet("fonz-size: 20px") msg.setWindowTitle("Regisztrációs eltérés!") msg.setText( '<html style="font-size: 14px; color: red">Kérem módosítás után mentse a beállításokat!<br></html>' ) msg.exec_() else: super().reject() # # if __name__ == '__main__': # app = QApplication([]) # win = NetworkSettingsDialog() # win.show() # app.exec_()
class UpdatePrompt(QDialog): def __init__(self): super().__init__() self.makeView() return def makeView(self): layout = QVBoxLayout() btnLayout = QHBoxLayout() self.centStack = QStackedWidget() self.updateButton = QPushButton('Update') self.cancelButton = QPushButton('Cancel') notifyLabel = QLabel('There are upgrades scheduled') self.inputBox = QLineEdit() self.outputBox = QTextBrowser() #refreshIcon = QIcon.fromTheme('process-working') self.refreshIcon = QMovie('assets/spin3.gif') refreshAnimation = QLabel() layout.addWidget(notifyLabel) layout.addWidget(self.centStack) layout.addWidget(self.inputBox) layout.addLayout(btnLayout) btnLayout.addWidget(self.cancelButton) btnLayout.addWidget(self.updateButton) self.centStack.addWidget(refreshAnimation) self.centStack.addWidget(self.outputBox) refreshAnimation.setMovie(self.refreshIcon) refreshAnimation.setAlignment(Qt.AlignCenter) self.refreshIcon.start() self.inputBox.setEchoMode(QLineEdit.Password) self.inputBox.setFocus() self.inputBox.returnPressed.connect(self.pkgUpdates) self.updateButton.clicked.connect(self.pkgUpdates) self.cancelButton.clicked.connect(self.cancelUpdates) self.updateButton.setDefault(True) self.centStack.setCurrentIndex(1) notifyLabel.setAlignment(Qt.AlignTop) self.outputBox.setReadOnly(True) #self.outputBox.setAlignment(Qt.AlignTop) self.setWindowTitle('Package Upgrades') self.setLayout(layout) self.resize(450, 250) return async def asetup(self, password): async with trio.open_nursery() as nursery: finishedState = trio.Event() nursery.start_soon(self.upProc, password, 'update', finishedState) #nursery.start_soon(self.KEAlive, finishedState) return async def upProc(self, password, cmd, finishedState): proc = await trio.open_process(['sudo', '-S', 'apt-get', cmd, '-y'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) await proc.stdin.send_all((password + '\n').encode()) while (proc.poll() == None): QCoreApplication.processEvents() await trio.sleep(0.1) result = '' result = await self.pullOutput(proc) self.appendToOutput(result) proc.terminate() if (cmd == 'update'): await self.upProc(password, 'upgrade', finishedState) finishedState.set() return async def pullOutput(self, proc): x = await proc.stdout.receive_some() x = x.decode() result = '' while (x != ''): QCoreApplication.processEvents() result = result + x x = await proc.stdout.receive_some() x = x.decode() return result async def KEAlive(self, finishedState): while finishedState.is_set(): QCoreApplication.processEvents() trio.sleep(0.1) return return def appendToOutput(self, add): currentText = self.outputBox.toPlainText() self.outputBox.setText(currentText + 'Running updates\n' + add + '\n') print(add) return def pkgUpdates(self): self.centStack.setCurrentIndex(0) self.refreshIcon.start() QCoreApplication.processEvents() password = self.inputBox.text() if (password == ''): self.passError('The password field cannot be empty') return self.inputBox.clear() self.inputBox.setDisabled(True) self.updateButton.setDisabled(True) trio.run(self.asetup, password) self.centStack.setCurrentIndex(1) self.refreshIcon.stop() self.updateButton.setDisabled(False) self.inputBox.setDisabled(False) return def passError(self, s): passError = QDialog(self) msg = QLabel(s) layout = QVBoxLayout() layout.addWidget(msg) passError.setLayout(layout) okBtn = QPushButton('OK') okBtn.clicked.connect(passError.reject) layout.addWidget(okBtn) passError.exec_() return def cancelUpdates(self): #Needs way of closing subprocess during async run self.reject() return
class LimitOptionsView(QWidget): """View which displays options for limiting they type of files to work with. Attributes: layout (QVBoxLayout): Main layout for view. frame (QFrame): Frame around limit options. frame_layout (QHBoxLayout): Layout associated with frame. title (QLabel): Title associated with view. limit_type_cb (QCheckBox): CheckBox which controls if view should be limited by file type. limit_type (QLineEdit): File type to limit to. search_cb (QCheckBox): CheckBox which controls if the view should limit by a search word. search (QLineEdit): Word to search for when renaming. """ def __init__(self): super(LimitOptionsView, self).__init__() self.layout = QVBoxLayout() self.title = QLabel(prefs.TITLE) self.frame_layout = QHBoxLayout() self.frame = QFrame() self.limit_type_cb = QCheckBox(prefs.LIMIT) self.limit_type = QLineEdit(prefs.LIMIT_DEFAULT) self.search_cb = QCheckBox(prefs.SEARCH) self.search = QLineEdit(prefs.SEARCH_DEFAULT) self._configure() def _configure(self) -> None: """Configure LimitOptionsView.""" self.frame.setLayout(self.frame_layout) self.layout.setAlignment(Qt.AlignLeft) self.limit_type_cb.setToolTip(prefs.LIMIT_TOOLTIP) self.search_cb.setToolTip(prefs.SEARCH_TOOLTIP) self.frame_layout.addWidget(self.limit_type_cb) self.frame_layout.addWidget(self.limit_type) self.frame_layout.addSpacerItem(QSpacerItem(*prefs.ITEM_SPACING)) self.frame_layout.addWidget(self.search_cb) self.frame_layout.addWidget(self.search) self.layout.addWidget(self.title) self.layout.addWidget(self.frame) self.setLayout(self.layout) def disable_limit_type(self) -> None: """Disable limit type functionality.""" self.limit_type.setDisabled(True) def disable_search(self) -> None: """Disable search functionality.""" self.search.setDisabled(True) def enable_limit_type(self) -> None: """Enable limit type functionality.""" self.limit_type.setEnabled(True) def enable_search(self) -> None: """Enable limit type functionality.""" self.limit_type.setEnabled(True) def get_limit_type(self) -> str: """Return the limit type.""" return str(self.limit_type.text()) def get_do_limit_type(self) -> bool: """Return if end user wants to limit the file type.""" return self.limit_type_cb.isChecked() def get_search(self) -> str: """Return the search value.""" return str(self.search.text()) def get_do_search(self) -> bool: """Return if the end user wants to limit files by a search.""" return self.search_cb.isChecked() def set_disabled(self) -> None: """Disable View.""" self.setDisabled(True) def set_enable(self) -> None: """Enable View.""" self.setEnabled(True) def set_limit_type(self, value: str) -> None: """Set the value in the limit type.""" self.limit_type.setText(value) def set_limit_type_style(self, style: str) -> None: """Set the style applied to limit type.""" self.limit_type.setStyleSheet(style) def set_search(self, value: str) -> None: """Set the value in the search.""" self.search.setText(value) def set_search_style(self, style: str) -> None: """Set the style applied to search.""" self.search.setStyleSheet(style)
class DataProcessingDialog(QDialog): ''' Signals ''' newDataAdded = Signal(str, str, str) dataChanged = Signal(str, str, str) def __init__(self, dataManager, parent=None, inputList=None): self.operations = { 'Filtering': [FilterGroup, 'Filter settings', -1], 'Average': [AverageGroup, 'Window settings', -1], 'Energy': [EnergyGroup, 'Window settings', -1], 'Power': [PowerGroup, 'Window settings', -1], 'Peak-To-Peak': [Peak2PeakGroup, 'Window settings', -1], 'Variance': [VarianceGroup, 'Window settings', -1], 'Entropy': [EntropyGroup, 'Window settings', -1], 'Skewness': [SkewnessGroup, 'Window settings', -1], 'Thresholding': [ThresholdGroup, 'Settings', -1], 'Detrend': [DetrendGroup, 'Settings', -1], 'STFT': [STFTGroup, 'Spectrum settings', -1], 'CWT': [CWTGroup, 'CWT settings', -1] } QDialog.__init__(self, parent) self.dataManager = dataManager self.setWindowTitle('Data Processing') self.inputList = inputList #Setup Layouts self.mainLayout = QGridLayout(self) self.setupInLayout() self.setupProcessLayout() self.setupOutLayout() self.setupBtnBoxLayout() ''' Input Layout ''' def setupInLayout(self): inGroup = QGroupBox('Input to process') inLayout = QFormLayout(inGroup) self.inTree = DataSelector(self.dataManager, inputList=self.inputList) self.operChooser = QComboBox() [self.operChooser.addItem(i) for i in self.operations] self.operChooser.currentTextChanged.connect(self.setOperation) inLayout.addRow(QLabel('Select Input')) inLayout.addRow(self.inTree) inLayout.addRow(QLabel('Operation'), self.operChooser) self.mainLayout.addWidget(inGroup, 0, 0) def setOperation(self): print('Set Operation') index = self.operations[self.operChooser.currentText()][2] self.processLayout.setCurrentIndex(index) ''' Signal Processing Settings Layout ''' def setupProcessLayout(self): processGroup = QGroupBox('Processing settings') self.processLayout = QStackedLayout() self.mainLayout.addLayout(self.processLayout, 1, 0) # Setup Processing Sublayouts for op in self.operations: index = self.createGroup(self.operations[op][0], op, self.operations[op][1]) self.operations[op][2] = index ''' Create Groups ''' def createGroup(self, GroupClass, name, title): newGroup = GroupClass(title, fs=self.dataManager.getFs()) newGroup.progress.connect(self.updateProgress) index = self.processLayout.addWidget(newGroup) return index ''' Output Layout ''' def setupOutLayout(self): outGroup = QGroupBox('Output') outLayout = QFormLayout(outGroup) self.outNameEdit = QLineEdit() inAsOutCheck = QCheckBox('Replace input') inAsOutCheck.toggled.connect(self.setInputAsOutput) outLayout.addWidget(inAsOutCheck) outLayout.addRow('Output name', self.outNameEdit) self.mainLayout.addWidget(outGroup, 2, 0) def setInputAsOutput(self, isOn): if isOn: inStruct = self.inTree.getSelectedStruct() wName = list(inStruct.keys())[0] gName = list(inStruct[wName].keys())[0] self.outNameEdit.setText(gName) self.outNameEdit.setDisabled(True) else: self.outNameEdit.setEnabled(True) ''' Button Box Layout ''' def setupBtnBoxLayout(self): bottomLayout = QHBoxLayout() self.progBar = QProgressBar() self.progBar.setVisible(False) bottomLayout.addWidget(self.progBar) buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Close) buttonBox.accepted.connect(self.okBtnBox) buttonBox.rejected.connect(self.close) bottomLayout.addWidget(buttonBox) self.mainLayout.addLayout(bottomLayout, 3, 0) def okBtnBox(self): inStruct = self.inTree.getSelectedStruct() data = self.dataManager.getData(inStruct, inv=True) wName = list(data.keys())[0] gName = list(data[wName].keys())[0] outName = self.outNameEdit.text() if outName in self.dataManager[wName].getColumnNames(): msgBox = QMessageBox() msgBox.setText('Signal already exists') msgBox.setInformativeText("Do you want to replace it?") msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Ok) ret = msgBox.exec() if ret == QMessageBox.Ok: self.dataManager.removeSignal(wName, outName) print('removed') else: return self.progBar.setVisible(True) outData = self.processLayout.currentWidget().process( data[wName][gName]) self.dataManager.appendSignal(wName, outName, inStruct[wName][gName], outData) self.newDataAdded.emit(wName, gName, self.outNameEdit.text()) def updateProgress(self, prog): self.progBar.setValue(prog)
class ClientLogin(QDialog): def __init__(self): super(ClientLogin, self).__init__() self.setFixedSize(300, 180) self.setWindowIcon(QIcon('icons/auth.png')) self.setWindowTitle('服务连接') client = DbHelper.query_client() lbl_app_id = QLabel('APP ID', self) lbl_app_id.setGeometry(10, 20, 50, 26) lbl_app_id.setAlignment(Qt.AlignCenter) self.le_app_id = QLineEdit(self) self.le_app_id.setText(client[1] if client is not None else '') self.le_app_id.setGeometry(70, 20, 200, 26) self.le_app_id.setDisabled(True if client is not None else False) lbl_security = QLabel('密钥', self) lbl_security.setGeometry(10, 66, 50, 26) lbl_security.setAlignment(Qt.AlignCenter) self.le_security = QLineEdit(self) self.le_security.setEchoMode(QLineEdit.Password) self.le_security.setText(client[2] if client is not None else '') self.le_security.setGeometry(70, 66, 200, 26) self.le_security.setDisabled(True if client is not None else False) self.btn_login = QPushButton(self) self.btn_login.setText('已连接' if client is not None else '连接') self.btn_login.setDisabled(True if client is not None else False) self.btn_login.setGeometry(10, 110, 280, 30) self.btn_login.clicked.connect(self.login) self.btn_logout = QPushButton(self) self.btn_logout.setText('注销' if client is not None else '未连接') self.btn_logout.setDisabled(True if client is None else False) self.btn_logout.setGeometry(10, 140, 280, 30) self.btn_logout.clicked.connect(self.logout) def login(self): app_id = self.le_app_id.text().strip(' ') if app_id == '': self.btn_login.setText('请输入APP ID') self.le_app_id.setFocus() return security = self.le_security.text().strip(' ') if security == '': self.btn_login.setText('请输入密钥') self.le_security.setFocus() return self.btn_login.setDisabled(True) result, data = Tool.client_login(app_id, Tool.get_md5(security)) if result: DbHelper.insert_client(data, app_id, security) self.btn_login.setText("已连接") self.le_app_id.setDisabled(True) self.le_security.setDisabled(True) self.btn_logout.setDisabled(False) self.btn_logout.setText("注销") self.btn_logout.setFocus() else: self.btn_login.setDisabled(False) self.btn_login.setText("APP ID或者密钥不正确") def logout(self): self.btn_logout.setDisabled(True) self.btn_logout.setText("未连接") self.btn_login.setDisabled(False) self.btn_login.setText("连接") self.le_app_id.setDisabled(False) self.le_security.setDisabled(False) DbHelper.delete_client()
class DownloadWindow(QDialog): def __init__(self, parent: Optional[QWidget] = None, url: str = '') -> None: super().__init__(parent, ) if parent: self.setWindowTitle('Download Mod') else: self.setWindowTitle(getTitleString('Download Mod')) self.setAttribute(Qt.WA_DeleteOnClose) mainLayout = QVBoxLayout(self) self.signals = DownloadWindowEvents(self) # URL input gbUrl = QGroupBox('Mod URL') gbUrlLayout = QVBoxLayout() gbUrl.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.url = QLineEdit() self.url.setPlaceholderText( 'https://www.nexusmods.com/witcher3/mods/...') self.url.setText(url) self.url.textChanged.connect(lambda: self.validateUrl(self.url.text())) gbUrlLayout.addWidget(self.url) self.urlInfo = QLabel('🌐') self.urlInfo.setContentsMargins(4, 4, 4, 4) self.urlInfo.setMinimumHeight(36) self.urlInfo.setWordWrap(True) gbUrlLayout.addWidget(self.urlInfo) gbUrl.setLayout(gbUrlLayout) mainLayout.addWidget(gbUrl) # File selection gbFiles = QGroupBox('Mod Files') gbFilesLayout = QVBoxLayout() gbFiles.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.files = QTableWidget(0, 4) self.files.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) self.files.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) self.files.setContextMenuPolicy(Qt.CustomContextMenu) self.files.setSelectionMode(QAbstractItemView.ExtendedSelection) self.files.setSelectionBehavior(QAbstractItemView.SelectRows) self.files.setWordWrap(False) self.files.setSortingEnabled(True) self.files.setFocusPolicy(Qt.StrongFocus) self.files.verticalHeader().hide() self.files.setSortingEnabled(True) self.files.sortByColumn(2, Qt.DescendingOrder) self.files.verticalHeader().setVisible(False) self.files.verticalHeader().setDefaultSectionSize(25) self.files.horizontalHeader().setHighlightSections(False) self.files.horizontalHeader().setStretchLastSection(True) self.files.setHorizontalHeaderLabels( ['File Name', 'Version', 'Upload Date', 'Description']) self.files.setEditTriggers(QAbstractItemView.NoEditTriggers) self.files.verticalScrollBar().valueChanged.connect( lambda: self.files.clearFocus()) self.files.itemSelectionChanged.connect(lambda: self.validateFiles()) self.files.setDisabled(True) self.files.setStyleSheet(''' QTableView { gridline-color: rgba(255,255,255,1); } QTableView::item { padding: 5px; margin: 1px 0; } QTableView::item:!selected:hover { background-color: rgb(217, 235, 249); padding: 0; } ''') gbFilesLayout.addWidget(self.files) _mouseMoveEvent = self.files.mouseMoveEvent self.files.hoverIndexRow = -1 def mouseMoveEvent(event: QMouseEvent) -> None: self.files.hoverIndexRow = self.files.indexAt(event.pos()).row() _mouseMoveEvent(event) self.files.mouseMoveEvent = mouseMoveEvent # type: ignore self.files.setItemDelegate(ModListItemDelegate(self.files)) self.files.setMouseTracking(True) gbFiles.setLayout(gbFilesLayout) mainLayout.addWidget(gbFiles) # Actions actionsLayout = QHBoxLayout() actionsLayout.setAlignment(Qt.AlignRight) self.download = QPushButton('Download', self) self.download.clicked.connect(lambda: self.downloadEvent()) self.download.setAutoDefault(True) self.download.setDefault(True) self.download.setDisabled(True) actionsLayout.addWidget(self.download) cancel = QPushButton('Cancel', self) cancel.clicked.connect(self.cancelEvent) actionsLayout.addWidget(cancel) mainLayout.addLayout(actionsLayout) # Setup self.setMinimumSize(QSize(420, 420)) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.resize(QSize(720, 420)) self.finished.connect( lambda: self.validateUrl.cancel()) # type: ignore self.finished.connect( lambda: self.downloadEvent.cancel()) # type: ignore self.modId = 0 self.validateUrl(self.url.text()) def cancelEvent(self) -> None: self.close() @debounce(200, cancel_running=True) async def validateUrl(self, url: str) -> bool: self.download.setDisabled(True) self.files.setDisabled(True) self.files.clearSelection() self.files.clearFocus() self.files.clearContents() self.files.setRowCount(0) self.files.setSortingEnabled(False) self.url.setStyleSheet('') self.modId = 0 if not url: self.urlInfo.setText(''' <font color="#888">Please enter a valid mod url.</font> ''') return False modId = getModId(url) if not modId: self.files.setDisabled(True) self.url.setStyleSheet(''' *{ border: 1px solid #B22222; padding: 1px 0px; } ''') self.urlInfo.setText(''' <font color="#888">Please enter a valid mod url.</font> ''') return False self.urlInfo.setText('🌐') try: filesResponse = await getModFiles(modId) except (RequestError, ResponseError, Exception) as e: self.url.setStyleSheet(''' *{ border: 1px solid #B22222; padding: 1px 0px; } ''') self.urlInfo.setText(f''' <font color="#888">Could not get mod files: {e}.</font> ''') return False try: files = filesResponse['files'] if not len(files): self.urlInfo.setText(f''' <font color="#888">Mod "{modId}" has no files!</font> ''') return False self.files.setRowCount(len(files)) for i in range(len(files)): file = files[i] fileid = int(file['file_id']) name = str(file['name']) version = str(file['version']) _uploadtime = dateparser.parse(file['uploaded_time']) uploadtime = _uploadtime.astimezone(tz=None).strftime( '%Y-%m-%d %H:%M:%S') if _uploadtime else '?' description = html.unescape(str(file['description'])) nameItem = QTableWidgetItem(name) nameItem.setToolTip(name) nameItem.setData(Qt.UserRole, fileid) self.files.setItem(i, 0, nameItem) versionItem = QTableWidgetItem(version) versionItem.setToolTip(version) self.files.setItem(i, 1, versionItem) uploadtimeItem = QTableWidgetItem(uploadtime) uploadtimeItem.setToolTip(uploadtime) self.files.setItem(i, 2, uploadtimeItem) descriptionItem = QTableWidgetItem(description) descriptionItem.setToolTip(description) self.files.setItem(i, 3, descriptionItem) except KeyError as e: logger.exception( f'Could not find key "{str(e)}" in mod files response') self.urlInfo.setText(f''' <font color="#888">Could not find key "{str(e)}" in mod files response.</font> ''') return False self.urlInfo.setText(f''' <font color="#888">Found {len(files)} available files.</font> ''') self.files.resizeColumnsToContents() self.files.setDisabled(False) self.files.setSortingEnabled(True) self.modId = modId return True def validateFiles(self) -> bool: selection = self.files.selectionModel().selectedRows() if len(selection) > 0: self.download.setText(f'Download {len(selection)} mods') self.download.setDisabled(False) return True return False @debounce(25, cancel_running=True) async def downloadEvent(self) -> None: self.download.setDisabled(True) self.url.setDisabled(True) selection = self.files.selectionModel().selectedRows() files = [ self.files.item(index.row(), 0).data(Qt.UserRole) for index in selection ] self.files.setDisabled(True) try: urls = await asyncio.gather( *[getModFileUrls(self.modId, file) for file in files], loop=asyncio.get_running_loop()) except (RequestError, ResponseError, Exception) as e: self.url.setStyleSheet(''' *{ border: 1px solid #B22222; padding: 1px 0px; } ''') self.urlInfo.setText(f''' <font color="#888">Could not download mod files: {e}.</font> ''') return try: self.signals.download.emit([url[0]['URI'] for url in urls]) except KeyError as e: logger.exception( f'Could not find key "{str(e)}" in file download response') self.urlInfo.setText(f''' <font color="#888">Could not find key "{str(e)}" in file download response.</font> ''') return self.close()
class ContinuousCriteriaPage(EnableNextOnBackMixin, QWizardPage): def __init__(self, parent): super().__init__(parent) self.parent_wizard = weakref.proxy(parent) self.setTitle('Continuous criteria') # Radio button, if yes then ask for inputs self.yes = QRadioButton( '&Yes, there are criteria that needs to be calculated') no = QRadioButton( 'N&o, I will manually give a rating for every choice and criteria') no.setChecked(True) self.registerField('yes', self.yes) self.yes.toggled.connect(self.toggled) group = QButtonGroup(self) group.addButton(self.yes) group.addButton(no) # Duplicated from AbstractMultiInputPage self.line_edit = QLineEdit() self.list_widget = QListWidget() self.add_button = QPushButton('&Add criterion') self.delete_button = QPushButton('&Delete') self.line_edit.setDisabled(True) self.list_widget.setDisabled(True) self.add_button.setDisabled(True) self.delete_button.setDisabled(True) self.line_edit.returnPressed.connect(self.add_item) self.add_button.clicked.connect(self.add_item) self.delete_button.clicked.connect(self.delete_item) grid = QGridLayout(self) grid.addWidget(self.yes, 0, 0) grid.addWidget(no, 1, 0) grid.addWidget(self.line_edit, 2, 0) grid.addWidget(self.add_button, 2, 1) grid.addWidget(self.list_widget, 3, 0) grid.addWidget(self.delete_button, 3, 1, Qt.AlignTop) self.setLayout(grid) def initializePage(self): for criterion in self.parent_wizard.main_parent.matrix.continuous_criteria: self.list_widget.addItem(QListWidgetItem(criterion)) if self.list_widget.count() != 0: self.yes.setChecked(True) self.parent_wizard.next_button.setEnabled(True) def toggled(self, checked: bool): if checked: self.line_edit.setEnabled(True) self.list_widget.setEnabled(True) self.add_button.setEnabled(True) self.parent_wizard.next_button.setDisabled(True) else: self.line_edit.setDisabled(True) self.list_widget.setDisabled(True) self.parent_wizard.next_button.setEnabled(True) def add_item(self): # Duplicated if not (name := self.line_edit.text()): return item = QListWidgetItem(name) self.list_widget.addItem(item) self.line_edit.clear() self.line_edit.setFocus() self.parent_wizard.next_button.setEnabled(True) self.delete_button.setEnabled(True) self.parent_wizard.main_parent.line_edit_cc_tab.setText(name) self.parent_wizard.main_parent.matrix.add_continuous_criterion( name, weight=float('nan')) self.parent_wizard.main_parent.add_continuous_criteria()
class MainWindow(QMainWindow): def __init__(self): super().__init__(flags=Qt.WindowContextHelpButtonHint | Qt.WindowCloseButtonHint | Qt.CustomizeWindowHint) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.setUpMainWindow() def setUpMainWindow(self): self.setWindowTitle( "Lab Tools 1.0 (2021 Edition) - Applications of ML in Mechatronics" ) # set icon self.setUpIcon() #set up the info and start processing button self.inputLineEdit = QLineEdit() self.outputFolderLineEdit = QLineEdit() self.modelLineEdit = QLineEdit() self.browseOutputButton = QPushButton() self.browseInputButton = QPushButton() self.browseModelButton = QPushButton() self.startStopButton = QPushButton() self.useDefaultModelCheckbox = QCheckBox() self.inputLineEdit = self.findChild(QLineEdit, "inputLineEdit") self.modelLineEdit = self.findChild(QLineEdit, "modelLineEdit") self.outputFolderLineEdit = self.findChild(QLineEdit, "outputFolderLineEdit") self.browseOutputButton = self.findChild(QPushButton, "browseOutputButton") self.browseInputButton = self.findChild(QPushButton, "browseInputButton") self.browseModelButton = self.findChild(QPushButton, "browseModelButton") self.startStopButton = self.findChild(QPushButton, "startStopButton") self.useDefaultModelCheckbox = self.findChild( QCheckBox, "useDefaultModelCheckbox") self.chooseModelLabel = self.findChild(QLabel, "chooseModelLabel") #disabling model transfer capability if needed self._modelRPiPath = False if DISABLE_MODEL_TRASFER: self.useDefaultModelCheckbox.hide() self.modelLineEdit.setEnabled(1) self.browseModelButton.hide() self.chooseModelLabel.setText(self.chooseModelLabel.text() + " Name") # self.gridLayout = self.findChild(QGridLayout, "gridLayout") # self.gridLayout.removeWidget(self.browseModelButton) # self.gridLayout.removeWidget(self.useDefaultModelCheckbox) self._modelRPiPath = True self.startStopButton.clicked.connect(self.handleStartStopButtonClicked) self.browseOutputButton.clicked.connect(self.handleBrowseOutputButton) self.browseInputButton.clicked.connect(self.handleBrowseInputButton) self.browseModelButton.clicked.connect(self.handleBrowseModelButton) self.useDefaultModelCheckbox.stateChanged.connect( self.handleUseDefaultModelCheckboxStateChanged) self.useDefaultModelCheckbox.setChecked(True) #set up the log and progress bar self.logTextBrowser = QTextBrowser() self.lastLogTextLabel = QLabel() self.logTextBrowser = self.findChild(QTextBrowser, "logTextBrowser") self.progressBar = self.findChild(QProgressBar, "progressBar") self.clearLogButton = self.findChild(QPushButton, "clearLogButton") self.saveLogButton = self.findChild(QPushButton, "saveLogButton") self.lastLogTextLabel = self.findChild(QLabel, "lastLogTextLabel") self.clearLogButton.clicked.connect(self.handleClearLogButton) self.saveLogButton.clicked.connect(self.handleSaveLogButton) #set up menu bar self.actionHelp = self.findChild(QAction, "actionHelp") self.actionAbout = self.findChild(QAction, "actionAbout") self.actionHelp.triggered.connect(self.handleActionHelpClicked) self.actionAbout.triggered.connect(self.handleActionAboutClicked) # Add additional menu actions self.utilitiesMenu = self.menuBar().addMenu("Utilities") self.actionGetRPiIP = QAction("Get RPi IP") self.utilitiesMenu.addAction(self.actionGetRPiIP) self.actionGetRPiIP.triggered.connect(self.handleActionGetRPiIPClicked) self.actionUpdateRPiScript = QAction("Updare RPi Script") self.utilitiesMenu.addAction(self.actionUpdateRPiScript) self.actionUpdateRPiScript.triggered.connect( self.handleActionUpdateRPiScript) #create objects from the other classes self.logger = Logger(self.logTextBrowser, self.lastLogTextLabel) #initialize member variables self._b_processRunning = False # set up lab names self.setUpLabNames() #set up serial comms self.setupSerial() self.refreshSerialPorts() self.logger.log("The application is ready!", type="INFO") def setUpIcon(self): self.appIcon = QIcon("images/favicon.png") self.setWindowIcon(self.appIcon) def setUpLabNames(self): self.labNameComboBox = QComboBox() self.labNameComboBox = self.findChild(QComboBox, "labNameComboBox") self.labNameComboBox.currentIndexChanged.connect( self.handleLabNameComboboxCurrentIndexChanged) for code, name in utils.lab_names.items(): self.labNameComboBox.addItem(code + ": " + name) self.labNameComboBox.setCurrentIndex(1) def setupSerial(self): self.refreshSerialPortsButton = QPushButton() self.connectDisconnectSerialButton = QPushButton() self.serialPortComboBox = QComboBox() self.refreshSerialPortsButton = self.findChild( QPushButton, "refreshSerialPortsButton") self.connectDisconnectSerialButton = self.findChild( QPushButton, "connectDisconnectSerialButton") self.serialPortComboBox = self.findChild(QComboBox, "serialPortComboBox") self.refreshSerialPortsButton.clicked.connect(self.refreshSerialPorts) self.connectDisconnectSerialButton.clicked.connect( self.handleSerialConnectDisconnect) self._b_serialConnected = False def refreshSerialPorts(self): availablePorts = utils.find_serial_ports() self.serialPortComboBox.clear() for portName in availablePorts: self.serialPortComboBox.addItem(portName) def handleSerialConnectDisconnect(self): if not self.b_serialConnected: try: currentPortName = self.serialPortComboBox.currentText() self.port = serial.Serial(currentPortName, 115200, timeout=1, write_timeout=240, bytesize=8, parity='N', stopbits=1) self.port.set_buffer_size(rx_size=10**3, tx_size=10**8) self.serialPortComboBox.setItemText( self.serialPortComboBox.currentIndex(), currentPortName + " (CONNECTED)") self.connectDisconnectSerialButton.setText("Disconnect") self.b_serialConnected = True self.refreshSerialPortsButton.setDisabled(1) except (OSError, serial.SerialException): print("Problem with Serial Connection!") self.logger.log( "Problem with Serial Connection, Make sure you chose the right port", type="ERROR") else: try: self.port.close() self.refreshSerialPorts() self.connectDisconnectSerialButton.setText("Connect") self.b_serialConnected = False self.refreshSerialPortsButton.setEnabled(1) except (OSError, serial.SerialException): print("Problem with Serial Connection!") self.logger.log("Problem with Serial Connection", type="ERROR") def _startButtonClicked(self): self.logger.log("Attempting to start the processing", type="INFO") if not self.b_serialConnected: self.logger.log( "Serial is not connected, Please connect serial first", type="ERROR") return if self.inputLineEdit.text()[-4:].lower() != ".csv": self.logger.log("Please select a valid input csv file", type="ERROR") return if self.outputFolderLineEdit.text() == "": self.logger.log("Please select an output directory", type="ERROR") return self.executer = Executer(serialObj=self.port, loggerObj=self.logger) if self.modelLineEdit.text() != "": modelPath = self.modelLineEdit.text() else: modelPath = None if self._modelRPiPath: self.logger.log( "Please select a valid model that is already available in the folder saved_models on the RPi", type="ERROR") return #Read the Input File try: inputDataFrame = pd.read_csv(self.inputLineEdit.text()) except: self.logger.log("CSV File Reading Failed, select a valid csv file", type="ERROR") possibleInputs = list(inputDataFrame.columns) #Display a dialog to ask the user to choose what inputs they want dialog = QDialog(self) dialog.setWindowTitle("Select the Input Fields") dialogButtons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) dialogButtons.button(QDialogButtonBox.Ok).setDisabled(0) dialogButtons.accepted.connect(dialog.accept) dialogButtons.rejected.connect(dialog.reject) mainLayout = QVBoxLayout(dialog) scroll = QScrollArea(dialog) scroll.setWidgetResizable(True) layoutWidget = QWidget() layout = QVBoxLayout(layoutWidget) scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll.setWidget(layoutWidget) chosenInputs = [] checkboxes = [] def handleCheckboxClicked(): dialogButtons.button(QDialogButtonBox.Ok).setDisabled(1) for checkbox in checkboxes: if checkbox.isChecked(): dialogButtons.button(QDialogButtonBox.Ok).setDisabled(0) for input in possibleInputs: checkbox = QCheckBox(text=input) checkbox.clicked.connect(handleCheckboxClicked) checkbox.setChecked(True) checkboxes.append(checkbox) layout.addWidget(checkbox) mainLayout.addWidget( QLabel(text="Please select the input fields from the following:")) mainLayout.addWidget(scroll) mainLayout.addWidget(dialogButtons) dialog.setLayout(mainLayout) # dialog.setFixedHeight(400) if dialog.exec_() == QDialog.Accepted: for checkbox in checkboxes: if checkbox.isChecked(): chosenInputs.append(checkbox.text()) self.logger.log("The chosen input fields are: " + ', '.join(chosenInputs), type="INFO") else: return self.startStopButton.setText("Stop Processing") self.b_processRunning = True executionResult = self.executer.execute(self.labNameComboBox.currentText().split(":")[0], inputDataFrame, \ self.outputFolderLineEdit.text(), inputFields=chosenInputs, progressBar=self.progressBar, \ model=modelPath if not self._modelRPiPath else "RPI:"+modelPath) if executionResult == ExecutionResult.COMPLETED: self._stopButtonClicked(finishedProcessing=True) elif executionResult == ExecutionResult.INTERRUPTED or executionResult == ExecutionResult.FAILED: self._stopButtonClicked() if self.executer.reset() == ExecutionResult.FAILED: self.logger.log( "Resetting the serial state of RPi Failed, please power cycle the RPi", type="ERROR") else: self.logger.log("The serial state of RPi has been reset", type="INFO") def _stopButtonClicked(self, finishedProcessing=False): self.startStopButton.setText("Start Processing") if finishedProcessing: self.logger.log("", special="ProcessingCompleted") else: self.logger.log("", special="ProcessingStopped") self.b_processRunning = False #TODO: Complete Implementing this def handleStartStopButtonClicked(self): if (self.b_processRunning): self.executer.requestStop() else: self._startButtonClicked() def handleActionHelpClicked(self): helpBox = QMessageBox() helpBox.setIcon(QMessageBox.Information) helpBox.setStandardButtons(QMessageBox.Ok) helpBox.setWindowTitle("Need Help?") helpBox.setText( "For Help, please reach out to your Instructor or TA or read the lab manual" ) helpBox.setTextFormat(Qt.RichText) helpBox.setInformativeText( f"You can access the project <a href=\"{utils.docs_link}\">Manual</a> and source in the <a href=\"{utils.repo_link}\">Github Repo!</a>" ) helpBox.setWindowIcon(self.appIcon) helpBox.exec_() def handleActionAboutClicked(self): aboutBox = QMessageBox() aboutBox.setIcon(QMessageBox.Information) aboutBox.setStandardButtons(QMessageBox.Ok) aboutBox.setWindowTitle("About the Software") aboutBox.setText(utils.license_text) aboutBox.setWindowIcon(self.appIcon) aboutBox.exec_() def handleBrowseInputButton(self): dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.ExistingFile) dialog.setNameFilter("*.csv") if dialog.exec_(): filePath = dialog.selectedFiles() if len(filePath) == 1: self.inputLineEdit.setText(filePath[0]) def handleBrowseModelButton(self): dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.ExistingFile) dialog.setNameFilter(utils.lab_model_extensions[ self.labNameComboBox.currentText().split(":")[0]]) if dialog.exec_(): filePath = dialog.selectedFiles() if len(filePath) == 1: self.modelLineEdit.setText(filePath[0]) def handleBrowseOutputButton(self): dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.DirectoryOnly) dialog.setOption(QFileDialog.ShowDirsOnly) if dialog.exec_(): folderPath = dialog.selectedFiles() if len(folderPath) == 1: self.outputFolderLineEdit.setText(folderPath[0]) def handleSaveLogButton(self): dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.AnyFile) dialog.AcceptMode(QFileDialog.AcceptSave) dialog.setNameFilter("*.txt") if dialog.exec_(): filePath = dialog.selectedFiles() if len(filePath) == 1: if self.logger.saveLog(filePath[0]) != -1: self.logger.log( "The log has been saved, feel free to clear the log now", type="SUCCESS") return self.logger.log("Failed to save the log, please select a valid file", type="ERROR") def handleClearLogButton(self): self.logger.clearLog() def handleUseDefaultModelCheckboxStateChanged(self): if self._modelRPiPath: return if not self.useDefaultModelCheckbox.checkState(): self.modelLineEdit.setDisabled(0) self.browseModelButton.setDisabled(0) else: self.modelLineEdit.clear() self.modelLineEdit.setDisabled(1) self.browseModelButton.setDisabled(1) def handleLabNameComboboxCurrentIndexChanged(self): if self._modelRPiPath: self.modelLineEdit.setText(utils.lab_default_models[ self.labNameComboBox.currentText().split(":")[0]]) # Disable the model options when the lab selected is Communication Test if self.labNameComboBox.currentIndex() == 0: self.modelLineEdit.setDisabled(1) self.browseModelButton.setDisabled(1) self.modelLineEdit.setText("Model is not required") else: self.modelLineEdit.setDisabled(0) self.browseModelButton.setDisabled(0) def handleActionGetRPiIPClicked(self): self.logger.log("Attempting to Get Raspberry Pi IP Address", type="INFO") if not self.b_serialConnected: replyMessage = "Serial is not connected, Please connect serial first" else: self.executer = Executer(serialObj=self.port, loggerObj=self.logger) ipaddr = self.executer.executeOther("GET_IP") replyMessage = ( "Raspberry Pi IP is " + ipaddr ) if ipaddr != ExecutionResult.FAILED else "Failed to obtain the IP Address" self.logger.log(replyMessage) ipAddrMessage = QMessageBox() ipAddrMessage.setIcon(QMessageBox.Information) ipAddrMessage.setStandardButtons(QMessageBox.Ok) ipAddrMessage.setWindowTitle("Raspberry Pi IP Address") ipAddrMessage.setText(replyMessage) ipAddrMessage.setTextFormat(Qt.RichText) ipAddrMessage.setWindowIcon(self.appIcon) ipAddrMessage.exec_() def handleActionUpdateRPiScript(self): self.logger.log( 'Attempting to "git pull" for the Raspberry Pi Python Script', type="INFO") if not self.b_serialConnected: replyMessage = "Serial is not connected, Please connect serial first" else: self.executer = Executer(serialObj=self.port, loggerObj=self.logger) result = self.executer.executeOther("UPDATE_SCRIPT") if result != ExecutionResult.FAILED and "FAIL" not in result: replyMessage = "Raspberry Pi Script has been updated. You still need to reboot or power cycle the Raspberry Pi for the updated script to run" \ "\nReceived: " + result else: replyMessage = "Failed to update the RPi Script" if "FAIL" in result: replyMessage = replyMessage + "\nReceived: " + result self.logger.log(replyMessage) ipAddrMessage = QMessageBox() ipAddrMessage.setIcon(QMessageBox.Information) ipAddrMessage.setStandardButtons(QMessageBox.Ok) ipAddrMessage.setWindowTitle("Raspberry Pi Script Updating Status") ipAddrMessage.setText(replyMessage) ipAddrMessage.setTextFormat(Qt.RichText) ipAddrMessage.setWindowIcon(self.appIcon) ipAddrMessage.exec_() @property def b_processRunning(self): return self._b_processRunning @property def b_serialConnected(self): return self._b_serialConnected @b_serialConnected.setter def b_serialConnected(self, newValue): self._b_serialConnected = newValue if newValue is True: self.logger.log("", special="SerialConnected") else: self.logger.log("", special="SerialDisconnected") @b_processRunning.setter def b_processRunning(self, newValue): self._b_processRunning = newValue if newValue is True: self.logger.log("", special="ProcessingStarted") def __del__(self): if self.b_serialConnected: self.port.close()
class RenameOptionsView(QWidget): """View responsible for holding renaming options. Attributes: layout (QVBoxLayout): Main layout of view. frame_layout (QVBoxLayout: Layout of frame which holds options. frame (QFrame): Frame surrounding options. prefix_h_layout (QHBoxLayout): Layout holding prefix options. complete_rename_h_layout (QHBoxLayout): Layout holding complete rename options. search_and_replace_h_layout (QHBoxLayout): Layout holding search and replace options. renumber_h_layout (QHBoxLayout): Layout holding renumber options. remove_ext_h_layout (QHBoxLayout): Layout holding remove options. change_ext_h_layout (QHBoxLayout): Layout holding change extension options. create_backup_h_layout (QHBoxLayout): Layout holding backup options. preview_h_layout (QHBoxLayout): Layout holding preview options. start_lbl (QLabel): Label for renumbering start. padding_lbl (QLabel): Label for renumbering padding. add_prefix_cb (QCheckBox): Used to signify the user wants to add a prefix to the renaming. prefix (QLineEdit): prefix to add. complete_rename_cb (QCheckBox): Used to signify the user wants to completely rename the file. new_name (QLineEdit): New name used when renaming. search_and_replace_cb (QCheckBox): Used to signify the user wants to partially rename files. find (QLineEdit): When searching and replacing this is what the user wants to search for. replace (QLineEdit): When searching and replacing this is what the user wants to replace with. renumber_cb (QCheckBox): Used to signify the user wants to renumber while renaming. start_num (QSpinBox): Number to start with when renumbering files. padding (QComboBox): Padding to apply to renaming when renumbering files. dot_cb (QCheckBox): When checked a dot will be used to separate the renumber from the name. remove_ext_cb (QCheckBox): Used to signify the user wants to remove extensions when renaming. backup_files_cb (QCheckBox): Used to signify the user wants to backup old files before renaming. change_ext_cb (QCheckBox): Used to signify the user wants to change the extension while renaming. change_ext (QLineEdit): New extension to add to the renamed file. preview_cb (QCheckBox): Used to signify the user wants to preview the rename before renaming. """ def __init__(self): super(RenameOptionsView, self).__init__() self.layout = QVBoxLayout() self.frame_layout = QVBoxLayout() self.options_lbl = QLabel(prefs.OPTIONS) self.frame = QFrame() self.prefix_h_layout = QHBoxLayout() self.complete_rename_h_layout = QHBoxLayout() self.search_and_replace_h_layout = QHBoxLayout() self.renumber_h_layout = QHBoxLayout() self.remove_ext_h_layout = QHBoxLayout() self.change_ext_h_layout = QHBoxLayout() self.create_backup_h_layout = QHBoxLayout() self.preview_h_layout = QHBoxLayout() self.start_lbl = QLabel(prefs.START_NUM) self.padding_lbl = QLabel(prefs.PADDING) self.add_prefix_cb = QCheckBox(prefs.PREFIX) self.prefix = QLineEdit(prefs.PREFIX_DEFAULT) self.complete_rename_cb = QCheckBox(prefs.COMPLETE_RENAME) self.new_name = QLineEdit(prefs.COMPLETE_RENAME_DEFAULT) self.search_and_replace_cb = QCheckBox(prefs.SEARCH_AND_REPLACE) self.find = QLineEdit(prefs.SEARCH_AND_REPLACE_DEFAULT) self.replace = QLineEdit(prefs.REPLACE_WITH_DEFAULT) self.renumber_cb = QCheckBox(prefs.RENUMBER) self.start_num = QSpinBox() self.padding = QComboBox() self.dot_cb = QCheckBox(prefs.USE_DOT) self.remove_ext_cb = QCheckBox(prefs.REMOVE_EXT) self.backup_files_cb = QCheckBox(prefs.BACKUP) self.change_ext_cb = QCheckBox(prefs.CHANGE_EXT) self.change_ext = QLineEdit(prefs.CHANGE_EXT_DEFAULT) self.preview_cb = QCheckBox(prefs.PREVIEW) self._configure() def _configure(self) -> None: """Configure the RenameOptionsView.""" self.frame.setLayout(self.frame_layout) self.frame.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.layout.addWidget(self.options_lbl) self.layout.addWidget(self.frame) self.add_prefix_cb.setToolTip(prefs.PREFIX_TOOLTIP) self.prefix.setDisabled(True) self.prefix.setMaximumWidth(prefs.PREFIX_WIDTH) self.prefix.setMinimumWidth(prefs.PREFIX_WIDTH) self.complete_rename_cb.setToolTip(prefs.COMPLETE_RENAME_TOOLTIP) self.new_name.setDisabled(True) self.new_name.setMaximumWidth(prefs.NEW_NAME_WIDTH) self.new_name.setMinimumWidth(prefs.NEW_NAME_WIDTH) self.search_and_replace_cb.setToolTip(prefs.SEARCH_AND_REPLACE_TOOLTIP) self.find.setDisabled(True) self.find.setMinimumWidth(prefs.FIND_WIDTH) self.find.setMaximumWidth(prefs.FIND_WIDTH) self.replace.setDisabled(True) self.replace.setMaximumWidth(prefs.REPLACE_WIDTH) self.replace.setMinimumWidth(prefs.REPLACE_WIDTH) self.renumber_cb.setToolTip(prefs.RENUMBER_TOOLTIP) self.start_num.setToolTip(prefs.START_NUM_TOOLTIP) self.start_num.setDisabled(True) self.start_num.setValue(prefs.START_NUM_DEFAULT) self.start_num.setMinimumWidth(prefs.START_NUM_MIN_WIDTH) self.padding.setToolTip(prefs.PADDING_TOOLTIP) self.padding.setDisabled(True) self.padding.addItems([str(x) for x in range(10)]) self.padding.setCurrentIndex(4) self.padding.setMinimumWidth(prefs.PADDING_MIN_WIDTH) self.dot_cb.setToolTip(prefs.USE_DOT_TOOLTIP) self.dot_cb.setDisabled(True) self.dot_cb.setChecked(True) self.dot_cb.setMinimumWidth(prefs.DOT_WIDTH) self.dot_cb.setMaximumWidth(prefs.DOT_WIDTH) self.remove_ext_cb.setToolTip(prefs.REMOVE_EXT_TOOLTIP) self.change_ext.setToolTip(prefs.CHANGE_EXT_TOOLTIP) self.change_ext.setDisabled(True) self.backup_files_cb.setToolTip(prefs.BACKUP_TOOLTIP) self.backup_files_cb.setChecked(True) self.preview_cb.setToolTip(prefs.PREVIEW_TOOLTIP) self.preview_cb.setChecked(True) self.prefix_h_layout.addWidget(self.add_prefix_cb) self.prefix_h_layout.addWidget(self.prefix) self.frame_layout.addLayout(self.prefix_h_layout) self.complete_rename_h_layout.addWidget(self.complete_rename_cb) self.complete_rename_h_layout.addWidget(self.new_name) self.frame_layout.addLayout(self.complete_rename_h_layout) self.search_and_replace_h_layout.addWidget(self.search_and_replace_cb) self.search_and_replace_h_layout.addWidget(self.find) self.search_and_replace_h_layout.addWidget(self.replace) self.frame_layout.addLayout(self.search_and_replace_h_layout) self.renumber_h_layout.addWidget(self.renumber_cb) self.renumber_h_layout.addStretch(1) self.renumber_h_layout.addWidget(self.start_lbl) self.renumber_h_layout.addWidget(self.start_num) self.renumber_h_layout.addSpacerItem(QSpacerItem(*prefs.SPACER_SIZE)) self.renumber_h_layout.addWidget(self.padding_lbl) self.renumber_h_layout.addWidget(self.padding) self.renumber_h_layout.addSpacerItem(QSpacerItem(*prefs.SPACER_SIZE)) self.renumber_h_layout.addWidget(self.dot_cb) self.frame_layout.addLayout(self.renumber_h_layout) self.change_ext_h_layout.addWidget(self.change_ext_cb) self.change_ext_h_layout.addWidget(self.change_ext) self.frame_layout.addLayout(self.change_ext_h_layout) self.remove_ext_h_layout.addWidget(self.remove_ext_cb) self.frame_layout.addLayout(self.remove_ext_h_layout) self.create_backup_h_layout.addWidget(self.backup_files_cb) self.frame_layout.addLayout(self.create_backup_h_layout) self.preview_h_layout.addWidget(self.preview_cb) self.frame_layout.addLayout(self.preview_h_layout) self.frame_layout.addSpacerItem(QSpacerItem(*prefs.SPACER_SIZE)) self.setLayout(self.layout) def disable_change_ext(self) -> None: """Disable change extension.""" self.change_ext.setDisabled(True) def disable_dot(self) -> None: """Disable dot checkbox.""" self.dot_cb.setDisabled(True) def disable_find(self) -> None: """Disable find.""" self.find.setDisabled(True) def disable_new_name(self) -> None: """Disable new name.""" print("disable new name") self.new_name.setDisabled(True) def disable_padding(self) -> None: """Disable padding.""" self.padding.setDisabled(True) def disable_prefix(self) -> None: """Disable prefix.""" self.prefix.setDisabled(True) def disable_start_num(self) -> None: """Disable start num.""" self.start_num.setDisabled(True) def disable_replace(self) -> None: """Disable replace.""" self.replace.setDisabled(True) def enable_change_ext(self) -> None: """Disable change extension.""" self.change_ext.setDisabled(False) def enable_dot(self) -> None: """Enable dot checkbox.""" self.dot_cb.setEnabled(True) def enable_find(self) -> None: """Enable find.""" self.find.setEnabled(True) def enable_new_name(self) -> None: """Enable new name.""" print("enable new name.") self.new_name.setEnabled(True) def enable_padding(self) -> None: """Enable padding.""" self.padding.setEnabled(True) def enable_prefix(self) -> None: """Enable prefix.""" self.prefix.setEnabled(True) def enable_replace(self) -> None: """Enable replace.""" self.replace.setEnabled(True) def enable_start_num(self) -> None: """Enable start num.""" self.start_num.setEnabled(True) def get_add_prefix(self) -> bool: """Return if end user wants to add a prefix and it is not the default value.""" result = self.get_prefix_checked() if result and self.get_prefix() == prefs.PREFIX_DEFAULT: result = False return result def get_do_backup(self) -> bool: """Return if end user wants to backup files.""" return self.backup_files_cb.isChecked() def get_change_ext(self) -> bool: """Return if the change extension checkbox is checked.""" return self.change_ext_cb.isChecked() def get_do_complete_rename(self) -> bool: """Get if end user wants to completely rename.""" return self.complete_rename_cb.isChecked() def get_dot(self) -> str: """Return dot string based on end users configuration. Note: If the end user has not enable using dot separators an empty string will be returned. """ return "." if self.get_do_dot() else "" def get_do_dot(self) -> bool: """Return if the end user wants to use dot separators when renaming.""" return self.dot_cb.isChecked() def get_do_change_ext(self) -> bool: """Return if the end user wants to change the extension.""" result = self.change_ext_cb.isChecked() if self.get_new_ext() == prefs.CHANGE_EXT_DEFAULT: return False return result def get_do_padding(self) -> bool: """Return if the end user wants to add padding.""" return False if self.get_padding() == 0 else True def get_do_preview(self) -> bool: """Return if the end user wants to preview changes.""" return self.preview_cb.isChecked() def get_do_rename(self) -> bool: """Return if end user wants to rename the full item and it is not the default value.""" result = self.complete_rename_cb.isChecked() if result and self.get_new_name() == prefs.COMPLETE_RENAME_DEFAULT: result = False return result def get_do_renumber(self) -> bool: """Return if the end user wants to renumber.""" return self.renumber_cb.isChecked() def get_do_search(self) -> bool: """Return if end user wants to perform a search and replace AND it is not the default values respectfully. Note: If you only want to know if search and replace is checked use get_search_and_replace. """ result = self.search_and_replace_cb.isChecked() if result and (self.get_find() == prefs.SEARCH_AND_REPLACE_DEFAULT or self.get_replace() == prefs.REPLACE_WITH_DEFAULT): result = False return result def get_do_search_and_replace(self) -> bool: """Return if end user wants to perform a search and replace.""" return self.search_and_replace_cb.isChecked() def get_find(self) -> str: """Return find value.""" return str(self.find.text()) def get_new_ext(self) -> str: """Return new ext.""" return str(self.change_ext.text()) def get_new_name(self) -> str: """Return new_name value.""" return str(self.new_name.text()) def get_padding(self) -> int: """Return the current padding value.""" return int(self.padding.currentText()) def get_prefix_checked(self) -> bool: """Return if the prefix checkbox is checked.""" return self.add_prefix_cb.isChecked() def get_prefix(self) -> str: """Return the current prefix value end user has entered.""" return str(self.prefix.text()) def get_remove_ext(self) -> bool: """Return if end user has checked the remove extension checkbox.""" return self.remove_ext_cb.isChecked() def get_replace(self) -> str: """Return the current replace value end user has entered.""" return str(self.replace.text()) def get_start_num(self) -> int: """Return start number from view.""" return int(self.start_num.value()) def set_change_ext_style(self, style: str) -> None: """Set style of change extension. Args: style: Style sheet applied to change extension. """ self.change_ext.setStyleSheet(style) def set_disabled(self) -> None: """Disable View.""" self.setDisabled(True) def set_enable(self) -> None: """Enable View.""" self.setEnabled(True) def set_find(self, value: str) -> None: """Set the value of find. Args: value: Value applied to find """ self.find.setText(value) def set_find_style(self, style: str) -> None: """Set style of find. Args: style: Style sheet applied to find. """ self.find.setStyleSheet(style) def set_new_name(self, value: str) -> None: """Set the value of new name. Args: value: Value applied to new_name """ self.new_name.setText(value) def set_new_name_style(self, style: str) -> None: """Set style of new_name. Args: style: Style sheet applied to new_name. """ self.new_name.setStyleSheet(style) def set_prefix(self, value: str) -> None: """Set the value of prefix. Args: value: Value applied to prefix """ self.prefix.setText(value) def set_prefix_style(self, style: str) -> None: """Set style of prefix. Args: style: Style sheet applied to prefix. """ self.prefix.setStyleSheet(style) def set_remove_ext(self, state: bool) -> None: """Set the remove_ext checkbox as checked or unchecked. Args: state: Check state of remove_ext. """ self.remove_ext_cb.setCheckState(Qt.Checked if state else Qt.Unchecked) def set_replace(self, value: str) -> None: """Set the value of replace. Args: value: Value applied to replace """ self.replace.setText(value) def set_replace_style(self, style: str) -> None: """Set style of replace. Args: style: Style sheet applied to replace. """ self.replace.setStyleSheet(style)
class AddUser(QDialog): def __init__(self): super(AddUser, self).__init__() self.setFixedSize(300, 190) self.setWindowIcon(QIcon('icons/auth.png')) self.setWindowTitle('添加用户') self.client = DbHelper.query_client() lbl_name = QLabel('姓名', self) lbl_name.setGeometry(10, 20, 50, 26) lbl_name.setAlignment(Qt.AlignCenter) self.le_name = QLineEdit(self) self.le_name.setGeometry(70, 20, 200, 26) self.le_name.setDisabled(True if self.client is None else False) lbl_color = QLabel('颜色值', self) lbl_color.setGeometry(10, 100, 50, 26) lbl_color.setAlignment(Qt.AlignCenter) self.le_color = QLineEdit(self) self.le_color.setGeometry(70, 100, 200, 26) self.le_color.setDisabled(True if self.client is None else False) self.btn_save = QPushButton(self) self.btn_save.setText('添加' if self.client is not None else '客户端未连接') self.btn_save.setGeometry(10, 140, 280, 30) self.btn_save.clicked.connect(self.save) self.btn_save.setDisabled(True if self.client is None else False) def save(self): name = self.le_name.text().strip(' ') if name == '': self.btn_save.setText('请输入姓名') self.le_name.setFocus() return if not name.replace(' ', '').encode("utf-8").isalpha(): self.btn_save.setText('只支持英文字母和空格~') self.le_name.setFocus() return color = self.le_color.text().strip(' ') if color == '': self.btn_save.setText('请输入颜色值') self.le_color.setFocus() return colors = color.split(',') if len(colors) != 3: self.btn_save.setText('颜色值不正确') self.le_color.setFocus() return for c in colors: if not c.isdigit() or int(c) > 255 or int(c) < 0: self.btn_save.setText('颜色值不正确') self.le_color.setFocus() return self.btn_save.setDisabled(True) result, data = Tool.user_add(self.client[0], name, color) if result: DbHelper.insert_user(data, name, color, self.client[0]) self.close() elif data is not None: self.btn_save.setDisabled(False) self.btn_save.setText(data) else: self.btn_save.setDisabled(False) self.btn_save.setText("添加失败")
class StatisticsOptions(QWidget): polymorphic_states = { 'Regression': {'dtext': 'Tipo de Regressão:', 'xtext': 'Selecione a coluna dos valores correspondentes à variável independente:', 'ytext': 'Selecione a coluna dos valores correspondentes à variável dependente:', 'dropdown': ['Linear', 'Exponencial', 'Logarítmica', 'Potencial', 'Polinomial'], 'ctext':''}, 'Statistics': {'dtext': 'Tipo de análise estatística:', 'xtext': 'Selecione a coluna dos valores para realizar a análise:', 'ytext': '', 'dropdown': ['Média aritmética', 'Mediana', 'Moda', 'Variância Pop.', 'Variância Am.', 'Desvio Padrão Pop.', 'Desvio Padrão Am.', 'Máximo', 'Mínimo', 'Amplitude', 'Quartil', 'Percentil'], 'ctext':''}, 'Histogram': {'dtext': '', 'xtext': 'Selecione a coluna com os valores:', 'ytext': '', 'dropdown': [], 'ctext':''}, 'Boxplot': {'dtext': 'Orientação do gráfico:', 'ctext': 'Selecione as colunas onde se encontram as séries para o Boxplot (i.e. B, B:C, A:C:F:G):', 'ytext': '', 'dropdown': ['Vertical', 'Horizontal'], 'xtext':''}, } ready2calculate = Signal(bool) def __init__(self, _mode='Regression'): super().__init__() self.mode = _mode self.subtitle = QLabel(str(_mode)) self.dropdown_text = QLabel(self.polymorphic_states[self.mode]['dtext']) self.dropdown_text.setWordWrap(True) self.dropdown = Dropdown(self.polymorphic_states[self.mode]['dropdown']) self.x_column_text = QLabel(self.polymorphic_states[self.mode]['xtext']) self.x_column_text.setWordWrap(True) self.x_column = QLineEdit() self.y_column_text = QLabel(self.polymorphic_states[self.mode]['ytext']) self.y_column_text.setWordWrap(True) self.y_column = QLineEdit() self.column_range_text = QLabel(self.polymorphic_states[self.mode]['ctext']) self.column_range_text.setWordWrap(True) self.column_range = QLineEdit() self.output_text = QLabel('Select Output:') self.output_text.setWordWrap(True) self.table_button = QRadioButton('Tabela (Planilha)') self.plot_button = QRadioButton('Gráfico (Plot)') self.output_destination_text = QLabel('Selecione a coluna onde deve-se armazenar os resultados:') self.output_destination_text.setWordWrap(True) self.output_destination = QLineEdit() self.run_button = QPushButton('Começar Análise') self.selected_output = '' self.degree_l = QLabel('Ordem:') self.degree = QSpinBox() self.degree.setRange(1, 6) self.quartile_l = QLabel('Quartil:') self.quartile = QSpinBox() self.quartile.setRange(1, 4) self.percentile_l = QLabel('Percentil:') self.percentile = QSpinBox() self.percentile.setRange(1, 100) self.default_degree_stylesheet = self.degree.styleSheet() self.default_quartile_stylesheet = self.quartile.styleSheet() self.default_percentile_stylesheet = self.percentile.styleSheet() self.degree.setDisabled(True) self.results_l = QLabel('Resultados da análise: ') self.results = QTextEdit() self.payload = [] self.buildLayout() self.connections() self.initialState() self.setStyleSheet('color:white; font-family: Bahnschrift SemiLight Condensed; font-size: 14px;') self.x_column.setStyleSheet('background-color: white; color: black') self.y_column.setStyleSheet('background-color: white; color: black') self.column_range.setStyleSheet('background-color: white; color: black') self.results.setStyleSheet('background-color: white; color: black') def buildLayout(self): layout = QFormLayout() if len(self.dropdown_text.text()) > 0: layout.addWidget(self.dropdown_text) layout.addWidget(self.dropdown) if len(self.x_column_text.text()) > 0: layout.addWidget(self.x_column_text) layout.addWidget(self.x_column) if len(self.y_column_text.text()) > 0: layout.addWidget(self.y_column_text) layout.addWidget(self.y_column) if len(self.column_range_text.text()) > 0: layout.addWidget(self.column_range_text) layout.addWidget(self.column_range) if self.mode == 'Statistics': layout.addWidget(self.quartile_l) layout.addWidget(self.quartile) layout.addWidget(self.percentile_l) layout.addWidget(self.percentile) if self.mode == 'Regression': layout.addWidget(self.degree_l) layout.addWidget(self.degree) layout.addWidget(self.run_button) if self.mode != 'Boxplot' and self.mode != "Histogram": layout.addWidget(self.results_l) layout.addWidget(self.results) self.setLayout(layout) def initialState(self): self.output_destination.setDisabled(True) def connections(self): self.plot_button.clicked.connect(self.plotSelected) self.table_button.clicked.connect(self.tableSelected) self.run_button.clicked.connect(self.collectPayload) self.dropdown.currentTextChanged.connect(self.enableDegreeBox) def columnRangeDecomposition(self, text): try: return text.split(sep=':') except Exception: print('A problem happened in decomposing the column ranges. Probable bad user input.') def plotSelected(self): self.output_destination.setDisabled(True) self.selected_output = 'Plot' def enableDegreeBox(self, text): if text == 'Polinomial': self.degree.setStyleSheet('background-color: white; color: black') self.degree.setEnabled(True) else: self.degree.setStyleSheet(self.default_degree_stylesheet) self.degree.setDisabled(True) if text == 'Quartil': self.quartile.setStyleSheet('background-color: white; color: black') self.quartile.setEnabled(True) else: self.quartile.setStyleSheet(self.default_degree_stylesheet) self.quartile.setDisabled(True) if text == 'Percentil': self.percentile.setStyleSheet('background-color: white; color: black') self.percentile.setEnabled(True) else: self.percentile.setStyleSheet(self.default_degree_stylesheet) self.percentile.setDisabled(True) def tableSelected(self): self.output_destination.setDisabled(False) self.selected_output = 'Table' def collectPayload(self): if len(self.column_range_text.text()) > 0: a = self.columnRangeDecomposition(self.column_range.text()) b = 'V' else: a = None b = self.y_column.text() payload = { 'calculate': self.dropdown.currentText(), 'x_column': self.x_column.text(), 'y_column': b, 'z_column': None, 'column_range': a, 'output_selection': self.selected_output, 'output_column': self.output_destination.text() } self.payload = payload self.ready2calculate.emit(True)
class MyWidget(QWidget): def __init__(self, parent): super().__init__(parent) self.buttons_id_value = { 1: ('comma', ','), 2: ('space', '\b'), 3: ('tab', '\t'), 4: ('semicolon', ';') } self.separator = QButtonGroup() lab = QLabel() lab.setText('Choose a separator:') for bid, value in self.buttons_id_value.items(): self.separator.addButton(QRadioButton(value[0]), id=bid) self.separator.setExclusive(True) self.default_button = self.separator.button(1) button_layout = QHBoxLayout() for button in self.separator.buttons(): button_layout.addWidget(button) self.default_button.click() openFileChooser = QPushButton('Choose') fileChooser = QFileDialog(self, 'Open csv', str(os.getcwd()), 'Csv (*.csv *.tsv *.dat)') fileChooser.setFileMode(QFileDialog.ExistingFile) self.filePath = QLineEdit() openFileChooser.released.connect(fileChooser.show) fileChooser.fileSelected.connect(self.filePath.setText) self.filePath.textChanged.connect(self.checkFileExists) nameLabel = QLabel('Select a name:', self) self.nameField = QLineEdit(self) self.nameErrorLabel = QLabel(self) self.file_layout = QVBoxLayout() fileChooserLayout = QHBoxLayout() nameRowLayout = QHBoxLayout() fileChooserLayout.addWidget(openFileChooser) fileChooserLayout.addWidget(self.filePath) nameRowLayout.addWidget(nameLabel) nameRowLayout.addWidget(self.nameField) self.fileErrorLabel = QLabel(self) self.file_layout.addLayout(fileChooserLayout) self.file_layout.addWidget(self.fileErrorLabel) self.file_layout.addLayout(nameRowLayout) self.file_layout.addWidget(self.nameErrorLabel) self.fileErrorLabel.hide() self.nameErrorLabel.hide() self.tablePreview = SearchableAttributeTableWidget(self, True) self.tableSpinner = QtWaitingSpinner( self.tablePreview, centerOnParent=True, disableParentWhenSpinning=True) self.nameField.textEdited.connect(self.nameErrorLabel.hide) # Split file by row splitRowLayout = QHBoxLayout() self.checkSplit = QCheckBox('Split file by rows', self) self.numberRowsChunk = QLineEdit(self) self.numberRowsChunk.setPlaceholderText( 'Number of rows per chunk') self.numberRowsChunk.setValidator(QIntValidator(self)) splitRowLayout.addWidget(self.checkSplit) splitRowLayout.addWidget(self.numberRowsChunk) self.checkSplit.stateChanged.connect(self.toggleSplitRows) layout = QVBoxLayout() layout.addLayout(self.file_layout) layout.addWidget(lab) layout.addLayout(button_layout) layout.addLayout(splitRowLayout) layout.addWidget(QLabel('Preview')) layout.addWidget(self.tablePreview) self.setLayout(layout) self.filePath.textChanged.connect(self.loadPreview) self.separator.buttonClicked.connect(self.loadPreview) @Slot(object) def loadPreview(self) -> None: if not os.path.isfile(self.filePath.text()): return class WorkerThread(QThread): resultReady = Signal(Frame) def __init__(self, path: str, separ: str, parent=None): super().__init__(parent) self.__path = path self.__sep = separ def run(self): header = pd.read_csv(self.__path, sep=self.__sep, index_col=False, nrows=0) self.resultReady.emit(Frame(header)) sep: int = self.separator.checkedId() sep_s: str = self.buttons_id_value[sep][ 1] if sep != -1 else None assert sep_s is not None # Async call to load header worker = WorkerThread(path=self.filePath.text(), separ=sep_s, parent=self) worker.resultReady.connect(self.onPreviewComputed) worker.finished.connect(worker.deleteLater) self.tableSpinner.start() worker.start() @Slot(Frame) def onPreviewComputed(self, header: Frame): self.tablePreview.setSourceFrameModel(FrameModel(self, header)) self.tablePreview.model().setAllChecked(True) self.tableSpinner.stop() @Slot(str) def checkFileExists(self, path: str) -> None: file_exists = os.path.isfile(path) if not file_exists: self.fileErrorLabel.setText('File does not exists!') self.fileErrorLabel.setStyleSheet('color: red') self.filePath.setToolTip('File does not exists!') self.filePath.setStyleSheet('border: 1px solid red') # self.file_layout.addWidget(self.fileErrorLabel) self.parentWidget().disableOkButton() self.fileErrorLabel.show() else: # self.file_layout.removeWidget(self.fileErrorLabel) self.fileErrorLabel.hide() self.filePath.setStyleSheet('') self.parentWidget().enableOkButton() if not self.nameField.text(): name: str = os.path.splitext(os.path.basename(path))[0] self.nameField.setText(name) @Slot(Qt.CheckState) def toggleSplitRows(self, state: Qt.CheckState) -> None: if state == Qt.Checked: self.numberRowsChunk.setEnabled(True) else: self.numberRowsChunk.setDisabled(True) def showNameError(self, msg: str) -> None: self.nameErrorLabel.setText(msg) self.nameErrorLabel.setStyleSheet('color: red') self.nameErrorLabel.show()
class SizeDetectorGUI(QWidget): EXIT_CODE_REBOOT = -123 PREVIOUS_GEOMETRY = None def __init__(self): QWidget.__init__(self) self.app_initialize = False self.logger = logging.getLogger(LOGGER_NAME) self.struct = SizeDetectorCore().get_config_params() self.cmd_args = SizeDetectorCore.get_cmd_params() self.gs_button = QPushButton(text="Detect Size!") self.ref_button = QPushButton(text="Refresh") self.inf_label = QLabel("Enter directory for sizing:") self.inf_label.setAlignment(Qt.AlignLeft) self.inp_line = QLineEdit("/") self.res_table = QTableWidget() self.res_table.setColumnCount(2) self.res_table.setHorizontalHeaderItem(0, QTableWidgetItem("size")) self.res_table.setHorizontalHeaderItem(1, QTableWidgetItem("file")) header = self.res_table.horizontalHeader() header.setSectionResizeMode(0, QHeaderView.Stretch) header.setSectionResizeMode(1, QHeaderView.Stretch) self.layout = QVBoxLayout() self.layout.addWidget(self.inf_label) self.layout.addWidget(self.inp_line) self.layout.addWidget(self.gs_button) self.layout.addWidget(self.res_table) self.layout.addWidget(self.ref_button) self.setLayout(self.layout) self.gs_button.clicked.connect(self.run_detecting) self.ref_button.clicked.connect(self.__refresh) self.__initialize() def __initialize(self): if self.struct is None: self._disable_form() self._set_info_message("Invalid or missing config file.\n" "For detailed information see log file") return if self.struct['pmanager'] != 'rpm': self._disable_form() self._set_info_message("Unsupported package manager {}" "".format(self.struct['pmanager'])) return shell = 'rpm -qa {}'.format(self.struct['dupackage']) try: proc = Popen(shell.split(), stdout=PIPE, stderr=PIPE) except FileNotFoundError as e: self.logger.debug("Command `{}` failed: {}".format(shell, e.strerror)) self._set_info_message( "Runtime error. For detailed information see log file.") return out, err = proc.communicate() if err: self.logger.debug( "Command `{}` failed: {}".format(shell, err.decode())) self._set_info_message( "Runtime error. For detailed information see log file.") return if not out: self._disable_form() self._set_info_message("No package `{}` found on the system" .format(self.struct['dupackage'])) return self.app_initialize = True def run_detecting(self): self.inf_label.setText("Enter directory for sizing:") dir_ = self.inp_line.text() if dir_[-1] != '/': dir_ += '/' sd = SizeDetector(dir_) status, retval = sd.detect_size() if status is False: self._set_info_message(retval) return self.res_table.setRowCount(len(retval) - 1) count = 0 for line in retval: if line: line = line.split() self.res_table.setItem(count, 0, QTableWidgetItem(line[0])) dir_name = line[1].replace(dir_, '') if len(retval) - 2 == count: dir_name = '* Summary size *' self.res_table.setItem(count, 1, QTableWidgetItem(dir_name)) count += 1 def _set_info_message(self, message): self.inf_label.setText(message) def _disable_form(self): self.inp_line.setDisabled(True) self.gs_button.setDisabled(True) self.ref_button.setFocus() def __refresh(self): SizeDetectorGUI.PREVIOUS_GEOMETRY = self.geometry() QtGui.qApp.exit(SizeDetectorGUI.EXIT_CODE_REBOOT)
class PlotControls(QWidget): erasePlot = Signal(bool) def __init__(self): super().__init__() types_of_axes = ['Cartesiano', 'Logarítmico'] types_of_traces = ['Scatter', "Boxplot"] self.modify_title = QLineEdit() self.modify_x_axis_type = Dropdown(types_of_axes) self.modify_y_axis_type = Dropdown(types_of_axes) self.modify_x_axis_label = QLineEdit() self.modify_y_axis_label = QLineEdit() self.modify_lower_x_range = QLineEdit() self.modify_upper_x_range = QLineEdit() self.modify_lower_y_range = QLineEdit() self.modify_upper_y_range = QLineEdit() self.type_of_trace = Dropdown(types_of_traces) self.add_x_trace = QLineEdit() self.add_y_trace = QLineEdit() self.trace_name = QLineEdit() self.run_button = QPushButton('Confirmar alterações') self.erase_button = QPushButton('Apagar gráfico') self.erase_button.clicked.connect(lambda: self.erasePlot.emit(True)) self.type_of_trace.currentIndexChanged.connect(self.typeChanged) self.buildLayout() self.setStyleSheet('color:white; font-family: Bahnschrift SemiLight Condensed; font-size: 14px;') self.modify_title.setStyleSheet('background-color: white; color: black') self.modify_x_axis_type.setStyleSheet('background-color: white; color: black') self.modify_y_axis_type.setStyleSheet('background-color: white; color: black') self.modify_x_axis_label.setStyleSheet('background-color: white; color: black') self.modify_y_axis_label.setStyleSheet('background-color: white; color: black') self.modify_lower_x_range.setStyleSheet('background-color: white; color: black') self.modify_upper_x_range.setStyleSheet('background-color: white; color: black') self.modify_lower_y_range.setStyleSheet('background-color: white; color: black') self.modify_upper_y_range.setStyleSheet('background-color: white; color: black') self.type_of_trace.setStyleSheet('background-color: white; color: black') self.add_x_trace.setStyleSheet('background-color: white; color: black') self.add_y_trace.setStyleSheet('background-color: white; color: black') self.trace_name.setStyleSheet('background-color: white; color: black') self.run_button.setStyleSheet('background-color: white; color: black') self.erase_button.setStyleSheet('background-color: white; color: black') def buildLayout(self): layout = QFormLayout() layout.addRow('Título', self.modify_title) layout.addRow('Escala do eixo X', self.modify_x_axis_type) layout.addRow('Escala do eixo Y', self.modify_y_axis_type) layout.addRow('Rótulo do eixo X', self.modify_x_axis_label) layout.addRow('Rótulo do eixo Y', self.modify_y_axis_label) layout.addRow('Limite inferior do eixo X', self.modify_lower_x_range) layout.addRow('Limite superior do eixo X', self.modify_upper_x_range) layout.addRow('Limite inferior do eixo Y', self.modify_lower_y_range) layout.addRow('Limite superior do eixo Y', self.modify_upper_y_range) layout.addRow('Tipo de série', self.type_of_trace) layout.addRow('Adicionar série/box no gráfico (eixo x)', self.add_x_trace) layout.addRow('Adicionar série/box no gráfico (eixo y)', self.add_y_trace) layout.addRow('Nome da série', self.trace_name) layout.addWidget(self.run_button) layout.addWidget(self.erase_button) self.setLayout(layout) def fillFromJson(self, _json): # json is the standard json export from the plotly library assert (isinstance(_json, str)) j_obj = j.loads(_json) try: if 'title' in list(j_obj['layout'].keys()): self.modify_title.setText(str(j_obj['layout']['title']['text'])) if 'type' in list(j_obj['layout']['xaxis'].keys()): if j_obj['layout']['xaxis']['type'].lower() == 'log': self.modify_x_axis_type.setCurrentIndex(1) else: self.modify_y_axis_type.setCurrentIndex(0) if 'type' in list(j_obj['layout']['yaxis'].keys()): if j_obj['layout']['yaxis']['type'].lower() == 'log': self.modify_y_axis_type.setCurrentIndex(1) else: self.modify_y_axis_type.setCurrentIndex(0) if 'title' in list(j_obj['layout']['xaxis'].keys()): self.modify_x_axis_label.setText(str(j_obj['layout']['xaxis']['title']['text'])) if 'title' in list(j_obj['layout']['yaxis'].keys()): self.modify_y_axis_label.setText(str(j_obj['layout']['yaxis']['title']['text'])) if 'range' in list(j_obj['layout']['xaxis'].keys()): self.modify_lower_x_range.setText(str(j_obj['layout']['xaxis']['range'][0])) self.modify_upper_x_range.setText(str(j_obj['layout']['xaxis']['range'][1])) if 'range' in list(j_obj['layout']['yaxis'].keys()): self.modify_lower_y_range.setText(str(j_obj['layout']['yaxis']['range'][0])) self.modify_upper_y_range.setText(str(j_obj['layout']['yaxis']['range'][1])) except KeyError: if 'title' in list(j_obj['layout']['template']['layout'].keys()): self.modify_title.setText(str(j_obj['layout']['template']['layout']['title']['text'])) if 'type' in list(j_obj['layout']['template']['layout']['xaxis'].keys()): if j_obj['layout']['template']['layout']['xaxis']['type'].lower() == 'log': self.modify_x_axis_type.setCurrentIndex(1) else: self.modify_y_axis_type.setCurrentIndex(0) if 'type' in list(j_obj['layout']['template']['layout']['yaxis'].keys()): if j_obj['layout']['template']['layout']['yaxis']['type'].lower() == 'log': self.modify_y_axis_type.setCurrentIndex(1) else: self.modify_y_axis_type.setCurrentIndex(0) if 'title' in list(j_obj['layout']['template']['layout']['xaxis'].keys()): self.modify_x_axis_label.setText(str(j_obj['layout']['template']['layout']['xaxis']['title']['text'])) if 'title' in list(j_obj['layout']['template']['layout']['yaxis'].keys()): self.modify_y_axis_label.setText(str(j_obj['layout']['template']['layout']['yaxis']['title']['text'])) if 'range' in list(j_obj['layout']['template']['layout']['xaxis'].keys()): self.modify_lower_x_range.setText(str(j_obj['layout']['template']['layout']['xaxis']['range'][0])) self.modify_upper_x_range.setText(str(j_obj['layout']['template']['layout']['xaxis']['range'][1])) if 'range' in list(j_obj['layout']['template']['layout']['yaxis'].keys()): self.modify_lower_y_range.setText(str(j_obj['layout']['template']['layout']['yaxis']['range'][0])) self.modify_upper_y_range.setText(str(j_obj['layout']['template']['layout']['yaxis']['range'][1])) return def typeChanged(self, ind): currentType = self.type_of_trace.currentText() if currentType == "Boxplot": self.add_y_trace.setDisabled(True) self.add_y_trace.setStyleSheet('background-color: grey; color: black') else: self.add_y_trace.setEnabled(True) self.add_y_trace.setStyleSheet('background-color: white; color: black')
class Snippets(QDialog): def __init__(self, parent=None): super(Snippets, self).__init__(parent) # Create widgets self.setWindowModality(Qt.ApplicationModal) self.title = QLabel(self.tr("Snippet Editor")) self.saveButton = QPushButton(self.tr("Save")) self.closeButton = QPushButton(self.tr("Close")) self.clearHotkeyButton = QPushButton(self.tr("Clear Hotkey")) self.setWindowTitle(self.title.text()) self.newFolderButton = QPushButton("New Folder") self.deleteSnippetButton = QPushButton("Delete") self.newSnippetButton = QPushButton("New Snippet") self.edit = QPlainTextEdit() self.edit.setPlaceholderText("python code") self.resetting = False self.columns = 3 self.keySequenceEdit = QKeySequenceEdit(self) self.currentHotkey = QKeySequence() self.currentHotkeyLabel = QLabel("") self.currentFileLabel = QLabel() self.currentFile = "" self.snippetDescription = QLineEdit() self.snippetDescription.setPlaceholderText("optional description") #Set Editbox Size font = getMonospaceFont(self) self.edit.setFont(font) font = QFontMetrics(font) self.edit.setTabStopWidth(4 * font.width(' ')); #TODO, replace with settings API #Files self.files = QFileSystemModel() self.files.setRootPath(snippetPath) self.files.setNameFilters(["*.py"]) #Tree self.tree = QTreeView() self.tree.setModel(self.files) self.tree.setSortingEnabled(True) self.tree.hideColumn(2) self.tree.sortByColumn(0, Qt.AscendingOrder) self.tree.setRootIndex(self.files.index(snippetPath)) for x in range(self.columns): #self.tree.resizeColumnToContents(x) self.tree.header().setSectionResizeMode(x, QHeaderView.ResizeToContents) treeLayout = QVBoxLayout() treeLayout.addWidget(self.tree) treeButtons = QHBoxLayout() treeButtons.addWidget(self.newFolderButton) treeButtons.addWidget(self.newSnippetButton) treeButtons.addWidget(self.deleteSnippetButton) treeLayout.addLayout(treeButtons) treeWidget = QWidget() treeWidget.setLayout(treeLayout) # Create layout and add widgets buttons = QHBoxLayout() buttons.addWidget(self.clearHotkeyButton) buttons.addWidget(self.keySequenceEdit) buttons.addWidget(self.currentHotkeyLabel) buttons.addWidget(self.closeButton) buttons.addWidget(self.saveButton) description = QHBoxLayout() description.addWidget(QLabel(self.tr("Description: "))) description.addWidget(self.snippetDescription) vlayoutWidget = QWidget() vlayout = QVBoxLayout() vlayout.addLayout(description) vlayout.addWidget(self.edit) vlayout.addLayout(buttons) vlayoutWidget.setLayout(vlayout) hsplitter = QSplitter() hsplitter.addWidget(treeWidget) hsplitter.addWidget(vlayoutWidget) hlayout = QHBoxLayout() hlayout.addWidget(hsplitter) self.showNormal() #Fixes bug that maximized windows are "stuck" self.settings = QSettings("Vector35", "Snippet Editor") if self.settings.contains("ui/snippeteditor/geometry"): self.restoreGeometry(self.settings.value("ui/snippeteditor/geometry")) else: self.edit.setMinimumWidth(80 * font.averageCharWidth()) self.edit.setMinimumHeight(30 * font.lineSpacing()) # Set dialog layout self.setLayout(hlayout) # Add signals self.saveButton.clicked.connect(self.save) self.closeButton.clicked.connect(self.close) self.clearHotkeyButton.clicked.connect(self.clearHotkey) self.tree.selectionModel().selectionChanged.connect(self.selectFile) self.newSnippetButton.clicked.connect(self.newFileDialog) self.deleteSnippetButton.clicked.connect(self.deleteSnippet) self.newFolderButton.clicked.connect(self.newFolder) if self.settings.contains("ui/snippeteditor/selected"): selectedName = self.settings.value("ui/snippeteditor/selected") self.tree.selectionModel().select(self.files.index(selectedName), QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) if self.tree.selectionModel().hasSelection(): self.selectFile(self.tree.selectionModel().selection(), None) self.edit.setFocus() cursor = self.edit.textCursor() cursor.setPosition(self.edit.document().characterCount()-1) self.edit.setTextCursor(cursor) else: self.readOnly(True) else: self.readOnly(True) @staticmethod def registerAllSnippets(): for action in list(filter(lambda x: x.startswith("Snippets\\"), UIAction.getAllRegisteredActions())): if action == "Snippets\\Snippet Editor...": continue UIActionHandler.globalActions().unbindAction(action) Menu.mainMenu("Tools").removeAction(action) UIAction.unregisterAction(action) for snippet in includeWalk(snippetPath, ".py"): snippetKeys = None (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(snippet) if not snippetDescription: actionText = "Snippets\\" + os.path.basename(snippet).rstrip(".py") else: actionText = "Snippets\\" + snippetDescription if snippetCode: if snippetKeys == None: UIAction.registerAction(actionText) else: UIAction.registerAction(actionText, snippetKeys) UIActionHandler.globalActions().bindAction(actionText, UIAction(makeSnippetFunction(snippetCode))) Menu.mainMenu("Tools").addAction(actionText, actionText) def clearSelection(self): self.keySequenceEdit.clear() self.currentHotkey = QKeySequence() self.currentHotkeyLabel.setText("") self.currentFileLabel.setText("") self.snippetDescription.setText("") self.edit.setPlainText("") self.currentFile = "" def reject(self): self.settings.setValue("ui/snippeteditor/geometry", self.saveGeometry()) if self.snippetChanged(): question = QMessageBox.question(self, self.tr("Discard"), self.tr("You have unsaved changes, quit anyway?")) if question != QMessageBox.StandardButton.Yes: return self.accept() def newFolder(self): (folderName, ok) = QInputDialog.getText(self, self.tr("Folder Name"), self.tr("Folder Name: ")) if ok and folderName: index = self.tree.selectionModel().currentIndex() selection = self.files.filePath(index) if QFileInfo(selection).isDir(): QDir(selection).mkdir(folderName) else: QDir(snippetPath).mkdir(folderName) def selectFile(self, new, old): if (self.resetting): self.resetting = False return newSelection = self.files.filePath(new.indexes()[0]) self.settings.setValue("ui/snippeteditor/selected", newSelection) if QFileInfo(newSelection).isDir(): self.readOnly(True) self.tree.clearSelection() self.currentFile = "" return if old and old.length() > 0: oldSelection = self.files.filePath(old.indexes()[0]) if not QFileInfo(oldSelection).isDir() and self.snippetChanged(): question = QMessageBox.question(self, self.tr("Discard"), self.tr("Snippet changed. Discard changes?")) if question != QMessageBox.StandardButton.Yes: self.resetting = True self.tree.selectionModel().select(old, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) return False self.currentFile = newSelection self.loadSnippet() def loadSnippet(self): self.currentFileLabel.setText(QFileInfo(self.currentFile).baseName()) (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(self.currentFile) self.snippetDescription.setText(snippetDescription) if snippetDescription else self.snippetDescription.setText("") self.keySequenceEdit.setKeySequence(snippetKeys) if snippetKeys else self.keySequenceEdit.setKeySequence(QKeySequence("")) self.edit.setPlainText(snippetCode) if snippetCode else self.edit.setPlainText("") self.readOnly(False) def newFileDialog(self): (snippetName, ok) = QInputDialog.getText(self, self.tr("Snippet Name"), self.tr("Snippet Name: ")) if ok and snippetName: if not snippetName.endswith(".py"): snippetName += ".py" index = self.tree.selectionModel().currentIndex() selection = self.files.filePath(index) if QFileInfo(selection).isDir(): path = os.path.join(selection, snippetName) else: path = os.path.join(snippetPath, snippetName) self.readOnly(False) open(path, "w").close() self.tree.setCurrentIndex(self.files.index(path)) log_debug("Snippet %s created." % snippetName) def readOnly(self, flag): self.keySequenceEdit.setEnabled(not flag) self.snippetDescription.setReadOnly(flag) self.edit.setReadOnly(flag) if flag: self.snippetDescription.setDisabled(True) self.edit.setDisabled(True) else: self.snippetDescription.setEnabled(True) self.edit.setEnabled(True) def deleteSnippet(self): selection = self.tree.selectedIndexes()[::self.columns][0] #treeview returns each selected element in the row snippetName = self.files.fileName(selection) question = QMessageBox.question(self, self.tr("Confirm"), self.tr("Confirm deletion: ") + snippetName) if (question == QMessageBox.StandardButton.Yes): log_debug("Deleting snippet %s." % snippetName) self.clearSelection() self.files.remove(selection) self.registerAllSnippets() def snippetChanged(self): if (self.currentFile == "" or QFileInfo(self.currentFile).isDir()): return False (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(self.currentFile) if snippetKeys == None and not self.keySequenceEdit.keySequence().isEmpty(): return True if snippetKeys != None and snippetKeys != self.keySequenceEdit.keySequence().toString(): return True return self.edit.toPlainText() != snippetCode or \ self.snippetDescription.text() != snippetDescription def save(self): log_debug("Saving snippet %s" % self.currentFile) outputSnippet = open(self.currentFile, "w") outputSnippet.write("#" + self.snippetDescription.text() + "\n") outputSnippet.write("#" + self.keySequenceEdit.keySequence().toString() + "\n") outputSnippet.write(self.edit.toPlainText()) outputSnippet.close() self.registerAllSnippets() def clearHotkey(self): self.keySequenceEdit.clear()
class guiMain(QMainWindow): def __init__(self, bk, prefs): super(guiMain, self).__init__() self.taglist = taglist # Edit Plugin container object self.bk = bk # Handy prefs groupings self.gui_prefs = prefs['gui_selections'] self.misc_prefs = prefs['miscellaneous_settings'] self.update_prefs = prefs['update_settings'] self.combobox_values = prefs['combobox_values'] self._ok_to_close = False # Check online github files for newer version self.update, self.newversion = self.check_for_update() self.setup_ui() def setup_ui(self): app = QApplication.instance() p = app.palette() link_color = p.color(p.Active, p.Link).name() DELETE_STR = _t('guiMain', 'Delete') MODIFY_STR = _t('guiMain', 'Modify') self.NO_ATTRIB_STR = _t('guiMain', 'No attributes (naked tag)') self.NO_CHANGE_STR = _t('guiMain', 'No change') self.setWindowTitle(_t('guiMain', 'Tag Mechanic')) configAct = QAction(_t('guiMain', '&Config'), self) configAct.triggered.connect(self.showConfig) menubar = self.menuBar() fileMenu = menubar.addMenu(_t('guiMain', '&Edit')) fileMenu.addAction(configAct) layout = QVBoxLayout() widget = QWidget() widget.setLayout(layout) self.setCentralWidget(widget) if self.update: update_layout = QHBoxLayout() layout.addLayout(update_layout) self.label = QLabel() self.label.setText( _t('guiMain', 'Plugin Update Available') + ' ' + str(self.newversion)) self.label.setStyleSheet( 'QLabel {{color: {};}}'.format(link_color)) update_layout.addWidget(self.label) action_layout = QHBoxLayout() layout.addLayout(action_layout) label = QLabel(_t('guiMain', 'Action type:'), self) action_layout.addWidget(label) self.action_combo = QComboBox() action_layout.addWidget(self.action_combo) self.action_combo.addItems([DELETE_STR, MODIFY_STR]) self.action_combo.setCurrentIndex(self.gui_prefs['action']) self.action_combo.currentIndexChanged.connect(self.update_gui) tag_layout = QHBoxLayout() layout.addLayout(tag_layout) label = QLabel(_t('guiMain', 'Tag name:'), self) tag_layout.addWidget(label) self.tag_combo = QComboBox() tag_layout.addWidget(self.tag_combo) self.tag_combo.addItems(self.taglist) self.tag_combo.setCurrentIndex(self.gui_prefs['tag']) self.tag_combo.currentIndexChanged.connect(self.update_gui) attr_layout = QHBoxLayout() layout.addLayout(attr_layout) label = QLabel(_t('guiMain', 'Having the attribute:'), self) attr_layout.addWidget(label) self.attr_combo = QComboBox() attr_layout.addWidget(self.attr_combo) self.attr_combo.addItems(self.combobox_values['attrs']) self.attr_combo.addItem(self.NO_ATTRIB_STR) self.attr_combo.setCurrentIndex(self.gui_prefs['attrs']) self.attr_combo.currentIndexChanged.connect(self.update_gui) srch_layout = QHBoxLayout() layout.addLayout(srch_layout) label = QLabel(_t('guiMain', 'Whose value is (no quotes):'), self) srch_layout.addWidget(label) self.srch_txt = QLineEdit('', self) srch_layout.addWidget(self.srch_txt) self.srch_method = QCheckBox(_t('guiMain', 'Regex'), self) srch_layout.addWidget(self.srch_method) newtag_layout = QHBoxLayout() layout.addLayout(newtag_layout) label = QLabel(_t('guiMain', 'Change tag to:'), self) newtag_layout.addWidget(label) self.newtag_combo = QComboBox() newtag_layout.addWidget(self.newtag_combo) self.newtag_combo.addItem(self.NO_CHANGE_STR) self.newtag_combo.addItems(self.combobox_values['{}_changes'.format( str(self.tag_combo.currentText()))]) if self.action_combo.currentIndex() == 0: self.newtag_combo.setDisabled(True) newattr_layout = QVBoxLayout() layout.addLayout(newattr_layout) label = QLabel( _t('guiMain', 'New attribute string to insert (entire):'), self) newattr_layout.addWidget(label) self.newattr_txt = QLineEdit('', self) newattr_layout.addWidget(self.newattr_txt) self.copy_attr = QCheckBox( _t('guiMain', 'Copy existing attribute string'), self) self.copy_attr.stateChanged.connect(self.update_txt_box) newattr_layout.addWidget(self.copy_attr) if self.action_combo.currentIndex() == 0: self.copy_attr.setDisabled(True) self.newattr_txt.setDisabled(True) layout.addSpacing(10) self.text_panel = QTextEdit() self.text_panel.setReadOnly(True) layout.addWidget(self.text_panel) layout.addSpacing(10) button_layout = QHBoxLayout() layout.addLayout(button_layout) self.process_button = QPushButton(_t('guiMain', 'Process'), self) self.process_button.setToolTip('<p>{}'.format( _t('guiMain', 'Process selected files with current criteria'))) self.process_button.clicked.connect(self._process_clicked) button_layout.addWidget(self.process_button) self.abort_button = QPushButton(_t('guiMain', 'Abort Changes'), self) self.abort_button.setToolTip('<p>{}'.format( _t('guiMain', 'Make no changes and exit'))) self.abort_button.clicked.connect(self._abort_clicked) self.abort_button.setDisabled(True) button_layout.addWidget(self.abort_button) self.quit_button = QPushButton(_t('guiMain', 'Quit'), self) self.quit_button.setToolTip('<p>{}'.format( _t('guiMain', 'Quit with no changes'))) self.quit_button.clicked.connect(self._quit_clicked) button_layout.addWidget(self.quit_button) if self.misc_prefs['windowGeometry'] is not None: try: self.restoreGeometry( QByteArray.fromHex( self.misc_prefs['windowGeometry'].encode('ascii'))) except Exception: pass self.show() def update_gui(self): if self.attr_combo.currentIndex() == self.attr_combo.count() - 1: self.srch_txt.clear() self.srch_txt.setDisabled(True) self.srch_method.setChecked(False) self.srch_method.setDisabled(True) else: self.srch_txt.setDisabled(False) self.srch_method.setDisabled(False) self.newtag_combo.clear() self.newtag_combo.addItem(self.NO_CHANGE_STR) self.newtag_combo.addItems(self.combobox_values['{}_changes'.format( str(self.tag_combo.currentText()))]) if self.action_combo.currentIndex() == 0: self.newtag_combo.setCurrentIndex(0) self.newtag_combo.setDisabled(True) self.newattr_txt.clear() self.newattr_txt.setDisabled(True) self.copy_attr.setChecked(False) self.copy_attr.setDisabled(True) else: self.newtag_combo.setDisabled(False) self.newattr_txt.setDisabled(False) self.copy_attr.setDisabled(False) self.update_txt_box() def update_txt_box(self): if self.copy_attr.isChecked() or not self.copy_attr.isEnabled(): self.newattr_txt.clear() self.newattr_txt.setDisabled(True) else: self.newattr_txt.setDisabled(False) def refresh_attr_values(self): self.attr_combo.clear() self.attr_combo.addItems(self.combobox_values['attrs']) self.attr_combo.addItem(self.NO_ATTRIB_STR) def _process_clicked(self): criteria = {} global PROCESSED criteria['tag'] = str(self.tag_combo.currentText()) if self.action_combo.currentIndex() == 0: criteria['action'] = 'delete' else: criteria['action'] = 'modify' if self.attr_combo.currentIndex() == self.attr_combo.count() - 1: criteria['attrib'] = None else: criteria['attrib'] = str(self.attr_combo.currentText()) srch_str = str(self.srch_txt.displayText()) if not len(srch_str): srch_str = None if srch_str is None and criteria['attrib'] is not None: title = _t('guiMain', 'Error') msg = '<p>{0}'.format( _t('guiMain', 'Must enter a value for the attribute selected')) return QMessageBox.warning(self, title, msg, QMessageBox.Ok) criteria['srch_str'] = srch_str criteria['srch_method'] = 'normal' if self.srch_method.isChecked(): criteria['srch_method'] = 'regex' if self.newtag_combo.currentIndex() == 0: criteria['new_tag'] = None else: criteria['new_tag'] = str(self.newtag_combo.currentText()) if criteria['action'] == 'modify' and criteria[ 'new_tag'] is None and self.copy_attr.isChecked(): title = _t('guiMain', 'Error') msg = '<p>{0}'.format( _t('guiMain', 'What--exactly--would that achieve?')) return QMessageBox.question(self, title, msg, QMessageBox.Ok) criteria['new_str'] = str(self.newattr_txt.displayText()) criteria['copy'] = False if self.copy_attr.isChecked(): criteria['copy'] = True if not len(criteria['new_str']): criteria['new_str'] = '' # Disable the 'Process' button, disable the context customization menu self.process_button.setDisabled(True) PROCESSED = True totals = 0 self.text_panel.clear() self.text_panel.insertHtml('<h4>{}...</h4><br>'.format( _t('guiMain', 'Starting'))) # Loop through the files selected in Sigil's Book View for (typ, ident) in self.bk.selected_iter(): # Skip the ones that aren't the "Text" mimetype. if self.bk.id_to_mime(ident) != 'application/xhtml+xml': continue href = self.bk.id_to_href(ident) # Param 1 - the contents of the (x)html file. criteria['html'] = self.bk.readfile(ident) if not isinstance(criteria['html'], str): criteria['html'] = str(criteria['html'], 'utf-8') # Hand off the "criteria" parameters dictionary to the parsing engine parser = MarkupParser(criteria) # Retrieve the new markup and the number of occurrences changed try: html, occurrences = parser.processml() except Exception: self.text_panel.insertHtml('<p>{} {}! {}.</p>\n'.format( _t('guiMain', 'Error parsing'), href, _t('guiMain', 'File skipped'))) continue # Report whether or not changes were made (and how many) totals += occurrences if occurrences: # write changed markup back to file self.bk.writefile(ident, html) self.text_panel.insertHtml( '<p>{} {}:   {}</p>\n'.format( _t('guiMain', 'Occurrences found/changed in'), href, int(occurrences))) else: self.text_panel.insertHtml('<p>{} {}</p>\n'.format( _t('guiMain', 'Criteria not found in'), href)) # report totals if totals: self.quit_button.setText(_t('guiMain', 'Commit and Exit')) self.quit_button.setToolTip('<p>{}'.format( _t('guiMain', 'Commit all changes and exit'))) self.abort_button.setDisabled(False) self.text_panel.insertHtml( '<br><h4>{}:   {}</h4>'.format( _t('guiMain', 'Total occurrences found/changed'), int(totals))) else: self.text_panel.insertHtml('<br><h4>{}</h4>'.format( _t('guiMain', 'No changes made to book'))) self.text_panel.insertHtml('<br><h4>{}</h4>'.format( _t('guiMain', 'Finished'))) def _quit_clicked(self): self.misc_prefs['windowGeometry'] = self.saveGeometry().toHex().data( ).decode('ascii') if PROCESSED: self.gui_prefs['action'] = self.action_combo.currentIndex() self.gui_prefs['tag'] = self.tag_combo.currentIndex() self.gui_prefs['attrs'] = self.attr_combo.currentIndex() self._ok_to_close = True self.close() def _abort_clicked(self): global BAIL_OUT BAIL_OUT = True self._ok_to_close = True self.close() def getAbort(self): return BAIL_OUT def showConfig(self): ''' Launch Customization Dialog ''' dlg = ConfigDialog(self, self.combobox_values) if dlg.exec_() == QDialog.Accepted: self.refresh_attr_values() self.update_gui() def check_for_update(self): '''Use updatecheck.py to check for newer versions of the plugin''' last_time_checked = self.update_prefs['last_time_checked'] last_online_version = self.update_prefs['last_online_version'] chk = UpdateChecker(last_time_checked, last_online_version, self.bk._w) update_available, online_version, time = chk.update_info() # update preferences with latest date/time/version self.update_prefs['last_time_checked'] = time if online_version is not None: self.update_prefs['last_online_version'] = online_version if update_available: return (True, online_version) return (False, online_version) def closeEvent(self, event): if self._ok_to_close: event.accept() # let the window close else: self._abort_clicked()