class Window(QMainWindow): def __init__(self): super(Window, self).__init__() # Set size and centre window self.setGeometry(50, 50, 500, 400) qtRectangle = self.frameGeometry() centerPoint = QDesktopWidget().availableGeometry().center() qtRectangle.moveCenter(centerPoint) self.move(qtRectangle.topLeft()) self.setWindowTitle("Rthenticator") self.setWindowIcon(QIcon('icon.ico')) self.setStyleSheet("background-color: #2F3031") self.home() def home(self): # Init QSystemTrayIcon self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(QIcon('icon.ico')) show_action = QAction("Show", self) quit_action = QAction("Exit", self) hide_action = QAction("Hide", self) show_action.triggered.connect(self.show) hide_action.triggered.connect(self.hide) quit_action.triggered.connect(qApp.quit) tray_menu = QMenu() tray_menu.addAction(show_action) tray_menu.addAction(hide_action) tray_menu.addAction(quit_action) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.activated.connect(self.systemIcon) self.tray_icon.show() # Button Setup self.btnImport = QPushButton("Import", self) self.btnImport.move(50, 320) self.btnImport.setStyleSheet( "background-color: #737C7D; color: #E9E6E4") self.btnImport.clicked.connect(self.btnImportClicked) # Listbox Setup self.Listbox = QListWidget(self) self.Listbox.setAlternatingRowColors(True) self.Listbox.setFixedSize(220, 300) self.Listbox.move(10, 10) self.Listbox.setStyleSheet( "alternate-background-color: #3F4041; color: #E9E6E4;") self.Listbox.itemClicked.connect(self.listboxClicked) for key in sorted(secrets): self.Listbox.addItem(key) self.Listbox.setCurrentRow(0) self.Listbox.currentItem().setSelected(True) # Listview context menu self.Listbox.setContextMenuPolicy(Qt.CustomContextMenu) self.Listbox.customContextMenuRequested.connect(self.showMenu) self.Listbox.itemChanged.connect(self.listboxChanged) self.old_name = "" # Frame Setup self.Frame = QFrame(self) self.Frame.setFixedSize(220, 300) self.Frame.move(266, 10) self.Frame.setFrameShape(QFrame.Shape.Panel) self.Frame.setFrameShadow(QFrame.Shadow.Plain) self.Frame.setStyleSheet("color: #828790") # Progress Bar Setup self.progress = QProgressBar(self) self.progress.setGeometry(266, 325, 200, 20) self.progress.setTextVisible(False) self.progress.setStyleSheet( "QProgressBar::chunk { background: #6187CB; }") self.progress.setRange(1, 29) # Progress Bar Timer Setup self.timer = QTimer() self.timer.timeout.connect(self.progressTimer) self.timer.start(1000) # Label Setup self.label = QLabel(self) self.label.setGeometry(310, 220, 150, 40) self.label.setText("") self.label.setFont(QFont("Arial", 30, QFont.Bold)) self.label.setStyleSheet("color: #E9E6E4") self.label.setTextInteractionFlags(Qt.TextSelectableByMouse) self.image = QLabel(self) self.image.setGeometry(300, 40, 150, 150) self.Listbox.setFocus(True) self.listboxClicked() self.show() # Restore view when tray icon doubleclicked def systemIcon(self, reason): if reason == QSystemTrayIcon.DoubleClick: self.show() self.copy_auth_code() # Override closeEvent, to intercept the window closing event def closeEvent(self, event): event.ignore() self.hide() self.tray_icon.showMessage("Tray Program", "Application was minimized to Tray", QSystemTrayIcon.Information, 2000) def copy_auth_code(self): """ Copies Authentication code to the clipboard """ answer = self.Listbox.currentItem().text() totp = pyotp.TOTP(secrets[answer][0]) self.label.setText(str(totp.now())) pyperclip.copy(totp.now()) def progressTimer(self): """ Updates progress timer Copies authentication code to clipboard once timer has reached 0 and main window is not in system tray """ current_time = int(time.time() % 30) self.progress.setValue(current_time) if current_time == 0: if self.isVisible(): self.copy_auth_code() def setImage(self): """ Reads from the images directory to see if there is a matching logo and must be the same name Splits the text on a `:` png files only """ item = self.Listbox.currentItem().text().split(":")[0] fname = f"images/{item}.png" if os.path.isfile(fname): pixmap = QPixmap(fname).scaled(150, 150) self.image.setPixmap(pixmap) else: self.image.setPixmap(QPixmap()) def listboxClicked(self): """ Listbox has been clicked """ self.setImage() self.copy_auth_code() def btnImportClicked(self): """ Imports a QR-code png file and add its to secrets.json """ fileName, _ = QFileDialog.getOpenFileName( self, "QFileDialog.getOpenFileName()", "", "All Files (*)") if fileName: test = unquote( decode(Image.open(fileName))[0].data.decode("utf-8")) query = urlsplit(test).query params = parse_qs(query) start = "/totp/" end = "\\?" test = re.search(f'{start}(.*){end}', test).group(1) secrets[test] = [params['secret'][0]] self.Listbox.addItem(test) with open('secrets.json', 'w') as fh: json.dump(secrets, fh, sort_keys=True, indent=4) def showMenu(self, pos): """ Displays right click context menu, with 2 options - Rename - Allows us to rename an entry - Delete - Aloows us to remove and entry """ menu = QMenu() renameAction = menu.addAction("Rename") deleteAction = menu.addAction("Delete") action = menu.exec_(self.Listbox.viewport().mapToGlobal(pos)) if action == renameAction: this_item = self.Listbox.currentItem() self.Listbox.blockSignals( True ) # Block signals so we dont trigger the listboxChanged function this_item.setFlags( this_item.flags() | Qt.ItemIsEditable) # Allows us to edit the item self.Listbox.blockSignals(False) # Re-enables signals self.old_name = this_item.text() self.Listbox.edit(self.Listbox.currentIndex()) if action == deleteAction: self.showMessageBox() def showMessageBox(self): """ Creates and displays a message box for delete confirmation """ box = QMessageBox() box.setIcon(QMessageBox.Question) box.setWindowTitle('Warning!') box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) box.setStyleSheet("background-color: #2F3031;") box.setText( "<FONT COLOR='#E9E6E4'>Do you really wish to delete this?</FONT>") btnYes = box.button(QMessageBox.Yes) btnYes.setStyleSheet("background-color: #737C7D; color: #E9E6E4") btnYes.setText('Yes') btnNo = box.button(QMessageBox.No) btnNo.setStyleSheet("background-color: #737C7D; color: #E9E6E4") btnNo.setText('No') box.exec_() if box.clickedButton() == btnYes: items = self.Listbox.selectedItems() for item in items: new_name = item.text() self.Listbox.takeItem(self.Listbox.row(item)) secrets.pop(new_name) with open('secrets.json', 'w') as fh: json.dump(secrets, fh, sort_keys=True, indent=4) def listboxChanged(self): """ Called when we have changed text of an item in the listbox """ new_name = self.Listbox.currentItem().text() self.Listbox.blockSignals( True) # Block signals so we dont trigger ourselves this_item = self.Listbox.currentItem() this_item.setFlags(this_item.flags() & ~Qt.ItemIsEditable) # Turn off the Editable flag self.Listbox.blockSignals(False) # Re-enables signals to be processed secrets[new_name] = secrets.pop(self.old_name) with open('secrets.json', 'w') as fh: json.dump(secrets, fh, sort_keys=True, indent=4)
class CreateQuizWindow(QWidget): def __init__(self): super().__init__() self.DEFAULT_BLOCK = (10, 100, [('Вопрос №1', ['Ответ №1', 'Ответ №2'], 0)]) self.blocks = [self.DEFAULT_BLOCK] self.questions = self.blocks[0][2] self.cur_block = 0 self.cur_question = -1 self.initUI() def initUI(self): self.move(*COORDS) self.setWindowTitle('pyQuiz') self.title_edit = QLineEdit() self.title_edit.setPlaceholderText('Название викторины') self.title_edit.setAlignment(Qt.AlignCenter | Qt.AlignHCenter) self.prev_block_button = QPushButton() self.prev_block_button.setIcon(QIcon('assets/back.png')) self.prev_block_button.setFixedWidth(30) self.prev_block_button.setEnabled(False) self.prev_block_button.clicked.connect(self.prevBlock) self.next_block_button = QPushButton() self.next_block_button.setIcon(QIcon('assets/plus.png')) self.next_block_button.setFixedWidth(30) self.next_block_button.clicked.connect(self.nextBlock) self.delete_block_button = QPushButton() self.delete_block_button.setIcon(QIcon('assets/cross.png')) self.delete_block_button.setFixedWidth(30) self.delete_block_button.setEnabled(False) self.delete_block_button.clicked.connect(self.delBlock) self.block_name_label = QLabel('Блок №1') self.block_name_label.setAlignment(Qt.AlignCenter | Qt.AlignHCenter) self.block_time_limit = QLineEdit() self.block_time_limit.setPlaceholderText('Время на вопрос') self.block_points = QLineEdit() self.block_points.setPlaceholderText('Очки за вопрос') self.add_question_button = QPushButton() self.add_question_button.setIcon(QIcon('assets/plus.png')) self.add_question_button.clicked.connect(self.addQuestion) self.delete_question_button = QPushButton() self.delete_question_button.setIcon(QIcon('assets/cross.png')) self.delete_question_button.clicked.connect(self.deleteQuestion) self.questions_list = QListWidget() self.questions_list.setFixedWidth(150) self.questions_list.itemSelectionChanged.connect(self.switchQuestion) self.question_text_edit = QLineEdit() self.question_text_edit.setPlaceholderText('Текст вопроса') self.options_list = QListWidget() self.exit_button = QPushButton() self.exit_button.clicked.connect(self.exit) self.exit_button.setText('Назад') self.exit_button.setStyleSheet( 'QPushButton{font-size:30px;text-align:center;background-color:#3C3F41;color:#BBBBBB;}' ) self.save_button = QPushButton() self.save_button.setText('Сохранить') self.save_button.setStyleSheet( 'QPushButton{font-size:30px;text-align:center;background-color:#3C3F41;color:#BBBBBB;}' ) self.save_button.clicked.connect(self.save) self.add_option_button = QPushButton() self.add_option_button.setIcon(QIcon('assets/plus.png')) self.add_option_button.clicked.connect(self.addOption) self.delete_option_button = QPushButton() self.delete_option_button.setIcon(QIcon('assets/cross.png')) self.delete_option_button.clicked.connect(self.delOption) vbox = QVBoxLayout() menu_layout = QHBoxLayout() menu_layout.addWidget(self.prev_block_button) menu_layout.addWidget(self.block_name_label) menu_layout.addWidget(self.delete_block_button) menu_layout.addWidget(self.next_block_button) block_settings = QHBoxLayout() block_settings.addWidget(self.block_time_limit) block_settings.addWidget(self.block_points) main_layout = QHBoxLayout() block_table_layout = QVBoxLayout() block_table_layout.addWidget(self.questions_list) change_block_layout = QHBoxLayout() change_block_layout.addWidget(self.add_question_button) change_block_layout.addWidget(self.delete_question_button) block_table_layout.addLayout(change_block_layout) main_layout.addLayout(block_table_layout) question_layout = QVBoxLayout() question_layout.addWidget(self.question_text_edit) question_layout.addWidget(self.options_list) change_question_layout = QHBoxLayout() change_question_layout.addWidget(self.add_option_button) change_question_layout.addWidget(self.delete_option_button) question_layout.addLayout(change_question_layout) main_layout.addLayout(question_layout) footer_menu = QHBoxLayout() footer_menu.addWidget(self.exit_button) footer_menu.addWidget(self.save_button) vbox.addWidget(self.title_edit) vbox.addLayout(menu_layout) vbox.addLayout(block_settings) vbox.addLayout(main_layout) vbox.addLayout(footer_menu) self.restoreBlock() self.setLayout(vbox) self.setStyleSheet(QUIZ_STYLE_SHEET) self.setFixedSize(500, 500) def blockNavUpdate(self): if self.cur_block == len(self.blocks) - 1: self.next_block_button.setIcon(QIcon('assets/plus.png')) else: self.next_block_button.setIcon(QIcon('assets/next.png')) self.prev_block_button.setEnabled(self.cur_block > 0) self.next_block_button.setEnabled( self.cur_block < QUIZ_BLOCKS_NUM_RANGE.stop - 1) self.delete_block_button.setEnabled(len(self.blocks) > 1) self.block_name_label.setText(f'Блок № {self.cur_block + 1}') def showMsg(self, s, type=QMessageBox.Critical): msg = QMessageBox() msg.setIcon(type) msg.setText(s) msg.setWindowTitle('pyQuiz') msg.exec_() def saveBlock(self): if self.cur_question != -1: if not self.saveQuestion(): return False time = self.block_time_limit.text() points = self.block_points.text() if not time.isnumeric(): self.showMsg('Время на вопрос должно быть целым числом!') return False time = int(time) if time not in QUIZ_BLOCK_TIME_RANGE: self.showMsg( f'Время на вопрос должно быть не меньше {QUIZ_BLOCK_TIME_RANGE.start}, ' f'но меньше {QUIZ_BLOCK_TIME_RANGE.stop}') return False if not points.isnumeric(): self.showMsg('Кол-во очков за вопрос должно быть целым числом!') return False points = int(points) if points not in QUIZ_BLOCK_POINTS_RANGE: self.showMsg( f'Кол-во очков за вопрос должно быть не меньше {QUIZ_BLOCK_POINTS_RANGE.start}, ' f'но меньше {QUIZ_BLOCK_POINTS_RANGE.stop}') return False if len(self.questions) not in QUIZ_QUESTIONS_NUM_RANGE: self.showMsg( f'Кол-во вопросов в блоке должно быть не меньше {QUIZ_QUESTIONS_NUM_RANGE.start}, ' f'но меньше {QUIZ_QUESTIONS_NUM_RANGE.stop}') return False self.blocks[self.cur_block] = (time, points, self.questions) return True def restoreBlock(self): block = self.blocks[self.cur_block] self.block_time_limit.setText(str(block[0])) self.block_points.setText(str(block[1])) self.questions_list.clear() for question_text, options, right_option in block[2]: self.questions_list.addItem(question_text) self.questions = block[2][:] self.cur_question = -1 self.questions_list.clearSelection() self.question_text_edit.setText('') self.options_list.clear() self.cur_question = -1 def nextBlock(self): if not self.saveBlock(): return self.cur_block += 1 if self.cur_block == len(self.blocks): self.blocks.append(self.DEFAULT_BLOCK) self.blockNavUpdate() self.restoreBlock() def prevBlock(self): if not self.saveBlock(): return self.cur_block -= 1 self.blockNavUpdate() self.restoreBlock() def delBlock(self): self.blocks.pop(self.cur_block) if self.cur_block == len(self.blocks): self.cur_block -= 1 self.blockNavUpdate() self.restoreBlock() def switchQuestion(self): if len(self.questions_list.selectedIndexes()) == 0: return if self.cur_question != -1: if not self.saveQuestion(): self.questions_list.blockSignals(True) self.questions_list.clearSelection() self.questions_list.item(self.cur_question).setSelected(True) self.questions_list.blockSignals(False) return self.cur_question = self.questions_list.selectedIndexes()[0].row() self.restoreQuestion() def addQuestion(self): question_text, okBtnPressed = QInputDialog.getText( self, 'Создание вопроса', 'Введите текст вопроса') if not okBtnPressed: return if len(question_text) not in QUIZ_QUESTION_TEXT_LEN_RANGE: self.showMsg( f'Длина вопроса должна быть не меньше {QUIZ_QUESTION_TEXT_LEN_RANGE.start}, ' f'но меньше {QUIZ_QUESTION_TEXT_LEN_RANGE.stop}', QMessageBox.Critical) return i = -1 if len(self.questions_list.selectedIndexes()) > 0: i = self.questions_list.selectedIndexes()[0].row() i += 1 self.questions.insert(i, (question_text, ['Ответ №1', 'Ответ №2'], 0)) self.questions_list.insertItem(i, question_text) self.delete_question_button.setEnabled(True) def deleteQuestion(self): if len(self.questions_list.selectedIndexes()) == 0: return i = self.questions_list.selectedIndexes()[0].row() self.questions_list.takeItem(i) self.questions.pop() self.cur_question = -1 def saveQuestion(self): question_text = self.question_text_edit.text() if len(question_text) not in QUIZ_QUESTION_TEXT_LEN_RANGE: self.showMsg( f'Длина текста вопроса должна быть не меньше {QUIZ_QUESTION_TEXT_LEN_RANGE.start}, ' f'но меньше {QUIZ_QUESTION_TEXT_LEN_RANGE.stop}', QMessageBox.Critical) return False self.questions_list.item(self.cur_question).setText(question_text) options = [] for i in range(len(self.options_list)): options.append(self.options_list.item(i).text()) if len(options) not in QUIZ_OPTIONS_NUM_RANGE: self.showMsg( f'Кол-во ответов на вопрос должно быть не меньше {QUIZ_OPTIONS_NUM_RANGE.start}, ' f'но меньше {QUIZ_OPTIONS_NUM_RANGE.stop}', QMessageBox.Critical) return False if len(self.options_list.selectedIndexes()) != 1: self.showMsg(f'Не выбран правильный ответ!', QMessageBox.Critical) return False right_answer = self.options_list.selectedIndexes()[0].row() self.questions[self.cur_question] = (question_text, options, right_answer) return True def restoreQuestion(self): question = self.questions[self.cur_question] self.question_text_edit.setText(question[0]) self.options_list.clear() for option in question[1]: self.options_list.addItem(option) self.options_list.item(question[2]).setSelected(True) def addOption(self): option_text, okBtnPressed = QInputDialog.getText( self, 'Создание ответа', 'Введите текст ответа') if not okBtnPressed: return if len(option_text) not in QUIZ_OPTION_TEXT_LEN_RANGE: self.showMsg( f'Длина ответа должна быть не меньше {QUIZ_OPTION_TEXT_LEN_RANGE.start}, ' f'но меньше {QUIZ_OPTION_TEXT_LEN_RANGE.stop}', QMessageBox.Critical) return self.options_list.addItem(option_text) def delOption(self): for index in self.options_list.selectedIndexes(): self.options_list.takeItem(index.row()) def save(self): if not self.saveBlock(): return quiz_name = self.title_edit.text() if len(quiz_name) not in QUIZ_NAME_LEN_RANGE: self.showMsg( f'Длина названия викторины должна быть не меньше {QUIZ_NAME_LEN_RANGE.start}, ' f'но меньше {QUIZ_NAME_LEN_RANGE.stop}') return quiz_json = {'name': quiz_name} cur = 0 for time, pts, questions in self.blocks: for text, options, right_answer in questions: quiz_json[str(cur)] = { 'time': time, 'score': pts, 'question': text, 'answers': list(options), 'true': right_answer } cur += 1 # QUIZ_SAVE_DIR file_name_preffix = secrets.token_hex(5) while os.path.isfile(os.path.join(QUIZ_SAVE_DIR, file_name_preffix)): file_name_preffix = secrets.token_hex(5) # https://stackoverflow.com/questions/18337407/saving-utf-8-texts-in-json-dumps-as-utf8-not-as-u-escape-sequence file_path = os.path.join(QUIZ_SAVE_DIR, quiz_name + '_' + file_name_preffix + '.json') with open(file_path, 'w', encoding='utf8') as json_file: json.dump(quiz_json, json_file, ensure_ascii=False, sort_keys=True, indent=4) self.exit() def exit(self): global COORDS COORDS = [self.x(), self.y()] self.create_game_window = QuizSelectionWindow() self.create_game_window.show() self.close()
class SongList(QWidget): def __init__(self, parent=None): super(SongList, self).__init__(parent) os.chdir(os.path.dirname(os.path.abspath(__file__))) resourcesPath = os.getcwd() resourcesPath = os.path.join(resourcesPath, "resources") self.PLAY_ICON = QIcon(QPixmap(os.path.join(resourcesPath, "play.png"))) self.PAUSE_ICON = QIcon(QPixmap(os.path.join(resourcesPath, "pause.png"))) self.STOP_ICON = QIcon(QPixmap(os.path.join(resourcesPath, "stop.png"))) self.DELETE_ICON = QIcon(QPixmap(os.path.join(resourcesPath, "delete.png"))) self.setupMediaPlayer() self.setupUi() def setupMediaPlayer(self): self.mediaPlayer = QMediaPlayer() self.mediaPlayer.setNotifyInterval(1) self.mediaPlayer.stateChanged.connect(self.mediaStateChanged) self.mediaPlayer.positionChanged.connect(self.positionChanged) self.mediaPlayer.durationChanged.connect(self.durationChanged) def setupUi(self): self.setWindowTitle("List of songs") mainLayout = QHBoxLayout(self) verticalListLayout = QVBoxLayout() self.songsListWidget = QListWidget() self.songsListWidget.setContextMenuPolicy(Qt.CustomContextMenu) self.songsListWidget.customContextMenuRequested.connect(self.listWidgetRightClick) verticalListLayout.addWidget(self.songsListWidget) miniHorizontalLayout = QHBoxLayout() locatorLine = QLineEdit() locatorLine.setPlaceholderText("Locator") locatorBox = QComboBox() items = ["Title", "Status", "Description", "Style", "All"] locatorBox.addItems(items) locatorBox.setCurrentIndex(len(items)-1) miniHorizontalLayout.addWidget(locatorLine) miniHorizontalLayout.addWidget(locatorBox) locatorLine.textChanged.connect(lambda:self.populateList(locatorLine.text(), locatorBox.currentText())) verticalListLayout.addLayout(miniHorizontalLayout) self.mainForm = QGroupBox() self.mainForm.setTitle("Details") mainLayout.addLayout(verticalListLayout) mainLayout.addWidget(self.mainForm) self.populateList() self.mainFormSetupUi() #self.show() self.songsListWidget.currentRowChanged.connect(self.changePage) def mainFormSetupUi(self): """title, status style, duration, descriptin, location, project, variation_another_song, timestamp""" mainLayout = QVBoxLayout(self.mainForm) #Horizontal Layout 1 horizontalLayout1 = QHBoxLayout() titleLabel = QLabel("Song name:") self.titleEdit = QLineEdit() self.titleEdit.editingFinished.connect(self.checkSong) self.titleEdit.textChanged.connect(self.validateSong) horizontalLayout1.addWidget(titleLabel) horizontalLayout1.addWidget(self.titleEdit) #Horizontal Layout 2 horizontalLayout2 = QHBoxLayout() statusLabel = QLabel("Status:") self.statusBox = QComboBox() dateLabel = QLabel("Date:") self.dateEdit = QDateTimeEdit() self.dateEdit.setCalendarPopup(True) horizontalLayout2.addWidget(statusLabel) horizontalLayout2.addWidget(self.statusBox) horizontalLayout2.addStretch(1) horizontalLayout2.addWidget(dateLabel) horizontalLayout2.addWidget(self.dateEdit) #Style Groupbox, widgets added automatically self.styleGroupBox = QGroupBox() self.styleGroupBox.setTitle("Style:") self.styleLayout = QGridLayout(self.styleGroupBox) horizontalLayout3 = QHBoxLayout() durationLabel = QLabel("Duration:") self.durationLine = QTimeEdit() self.durationLine.setDisplayFormat("mm:ss") projectLabel = QLabel("Project") self.projectComboBox = QComboBox() self.projectComboBox.setEditable(True) horizontalLayout3.addWidget(durationLabel) horizontalLayout3.addWidget(self.durationLine) horizontalLayout3.addWidget(projectLabel) horizontalLayout3.addWidget(self.projectComboBox) horizontalLayout4 = QHBoxLayout() descriptionLabel = QLabel("Description:") variationLabel = QLabel("Variation from another song: ") self.variationLine = QLineEdit() horizontalLayout4.addWidget(descriptionLabel) horizontalLayout4.addStretch(1) horizontalLayout4.addWidget(variationLabel) horizontalLayout4.addWidget(self.variationLine) self.descriptionTextEdit = QTextEdit() horizontalLayout5 = QHBoxLayout() locationLabel = QLabel("Location:") self.locationLine = QLineEdit() self.locationButton = QPushButton("...") self.locationButton.clicked.connect(self.locateFile) horizontalLayout5.addWidget(locationLabel) horizontalLayout5.addWidget(self.locationLine) horizontalLayout5.addWidget(self.locationButton) horizontalLayout6 = QHBoxLayout() self.slider = QSlider(Qt.Horizontal) self.slider.sliderReleased.connect(self.playSlider) self.slider.setStyleSheet("QSlider::handle:horizontal { border: 1px solid #777; background:#b55858;}") horizontalLayout6.addWidget(self.slider) horizontalLayout7 = QHBoxLayout() self.playButton = QPushButton() self.stopButton = QPushButton() self.playButton.setIcon(self.PLAY_ICON) self.stopButton.setIcon(self.STOP_ICON) self.playButton.clicked.connect(self.playSong) self.stopButton.clicked.connect(self.stopSong) horizontalLayout7.addStretch(1) horizontalLayout7.addWidget(self.playButton) horizontalLayout7.addWidget(self.stopButton) horizontalLayout7.addStretch(1) horizontalLayout8 = QHBoxLayout() self.saveButton = QPushButton() self.saveButton.setText("Save") self.saveButton.clicked.connect(self.saveSong) horizontalLayout8.addStretch(1) horizontalLayout8.addWidget(self.saveButton) mainLayout.addLayout(horizontalLayout1) mainLayout.addLayout(horizontalLayout2) mainLayout.addWidget(self.styleGroupBox) mainLayout.addLayout(horizontalLayout3) mainLayout.addLayout(horizontalLayout4) mainLayout.addWidget(self.descriptionTextEdit) mainLayout.addLayout(horizontalLayout5) mainLayout.addLayout(horizontalLayout6) mainLayout.addLayout(horizontalLayout7) mainLayout.addLayout(horizontalLayout8) def clearForm(self): self.titleEdit.clear() self.statusBox.clear() for widget in self.styleGroupBox.children(): if not isinstance(widget, QGridLayout): widget.deleteLater() self.durationLine.clear() self.projectComboBox.clear() self.variationLine.clear() self.descriptionTextEdit.clear() self.locationLine.clear() def changePage(self, index): title = self.songsListWidget.item(index).data(Qt.UserRole) self.clearForm() self.populateForm(title) self.slider.setValue(0) def populateForm(self, title): #title is the primary key listArray = queries("""SELECT title, status, style, duration, description, location, project, variation_another_song, timestamp from songs WHERE title = ?""", (title,)) print(listArray) if len(listArray) != 0: title = listArray[0][0] status = listArray[0][1] styles = [] styleArray = listArray[0][2] if styleArray != None: if "," in styleArray: styles = styleArray.split(",") else: styles.append(styleArray) duration = listArray[0][3] description = listArray[0][4] location = listArray[0][5] project = listArray[0][6] variation_another_song = listArray[0][7] timestamp = listArray[0][8] else: title = None status = None styles = None duration = None description = None location = None project = None variation_another_song = None timestamp = None if title != None: self.titleEdit.setText(title) self.statusBox.addItems(["Select...", "Demo", "WIP", "Idea", "Unfinished song", "EQ", "Master", "Finished"]) if status != None: self.statusBox.setCurrentText(status) if timestamp != None: self.dateEdit.setDateTime(datetime.strptime(timestamp, '%d/%m/%Y %H:%M')) else: self.dateEdit.setDateTime(datetime.now())#default styleArray = queries("select style from songs where style is not null") """ print(styleArray) if styleArray != None: styleArray = styleArray[0][0] if "," in styleArray: styles = styleArray.split(",") else: styles.append(styleArray)""" stylesArray = [] query = queries("select style from songs where style is not null") if len(query) != 0: for style in query: stylesMiniArray = style[0].split(",") stylesMiniArray = list(filter(None, stylesMiniArray)) for item in stylesMiniArray: if item not in stylesArray: if item != '': stylesArray.append(item) self.x = 0 self.y = 0 if len(stylesArray) != 0: for style in stylesArray: print("style", style) checkBox = QCheckBox(style) self.styleLayout.addWidget(checkBox, self.x, self.y) self.checkBoxPositionAsignment() self.addStyle() if styles!= None: if len(styles) != 0: for style in styles: for checkbox in self.styleGroupBox.children(): if isinstance(checkbox, QCheckBox): if checkbox.text() == style: checkbox.setChecked(True) if duration != None: time = QTime(0,0,0) self.durationLine.setTime(time.addSecs(duration)) projectsArray = ["Select..."] projectsArrayQuery = queries("SELECT project from songs") if len(projectsArrayQuery) != 0: for project in projectsArrayQuery[0]: if project not in projectsArray: projectsArray.append(project) if project != None: self.projectComboBox.setCurrentText(project) if variation_another_song != None: self.variationLine.setText(variation_another_song) if description != None: self.descriptionTextEdit.setText(description) available = False if location != None: self.locationLine.setText(location) if len(self.locationLine.text()) != 0: try: self.playlist = QMediaPlaylist() self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile(location))) self.mediaPlayer.setPlaylist(self.playlist) except: pass available = True#I know this is stupid but just in case self.slider.setVisible(available) self.playButton.setVisible(available) self.stopButton.setVisible(available) def populateList(self, locatorItem=None, locatorColumn=None): print(locatorItem, locatorColumn) self.songsListWidget.blockSignals(True) self.songsListWidget.clear() if locatorItem == None or locatorItem == "": listArray = queries("""SELECT title, status, timestamp from songs """) print(listArray) else: if locatorColumn != "All": #No strings concatenation, no security holes if locatorColumn == "Title": sql = """SELECT title, status, timestamp from songs where title LIKE ?""" elif locatorColumn == "Status": sql = """SELECT title, status, timestamp from songs where status LIKE ?""" elif locatorColumn == "Description": sql = """SELECT title, status, timestamp from songs where description LIKE ?""" elif locatorColumn == "Style": sql = """SELECT title, status, timestamp from songs where style LIKE ?""" locatorItem = "%" + locatorItem + "%" listArray = queries(sql, (locatorItem,)) else: locatorItem = "%" + locatorItem + "%" variables = [locatorItem, locatorItem, locatorItem, locatorItem, locatorItem] listArray = queries("""SELECT title, status, timestamp from songs where title LIKE ? OR type LIKE ? OR original_song LIKE ? OR link LIKE ? OR description LIKE ?""", variables) for item in listArray: title = item[0] status = item[1] timestamp = item[2] try: timestamp = datetime.strptime(timestamp, "%d/%m/%Y %H:%M") timestamp = timestamp.strftime("%d/%m/%Y") except: timestamp = "" text = "%s %s %s" % (title, status, timestamp) qItem = QListWidgetItem(text) qItem.setData(Qt.UserRole, title) self.songsListWidget.addItem(qItem) #new idea qItem = QListWidgetItem("New song...") qItem.setData(Qt.UserRole, "New song...") #otherwise that would be an error self.songsListWidget.addItem(qItem) self.songsListWidget.blockSignals(False) def listWidgetRightClick(self, position): widgetItem = self.songsListWidget.itemAt(position) if widgetItem != None: #quick lazy text fix if widgetItem.text() != "New song...": print(widgetItem.text()) menu = QMenu() deleteAction = QAction(self.DELETE_ICON, "Delete song") menu.addAction(deleteAction) action = menu.exec(self.mapToGlobal(position)) if action == deleteAction: msg = QMessageBox.question(None, "Delete?", "Are you sure you want to delete this entry?") if msg == QMessageBox.Yes: title = widgetItem.data(Qt.UserRole) queries("DELETE from songs where title = ?", (title,)) self.populateList() self.songsListWidget.setCurrentRow(0) def songVariations(self): sql = "SELECT title from songs" songArray = [] for song in queries(sql)[0]: songArray.append(song) return songArray def checkBoxPositionAsignment(self): self.y += 1 if self.y == 4: self.y = 0 self.x += 1 def addStyle(self, text=""): "text = "" if comes from outside" self.styleEdit = QLineEdit() self.styleEdit.setPlaceholderText("Style") self.styleEdit.textChanged.connect(self.validateStyle) self.styleEdit.returnPressed.connect(lambda: self.addStyle(self.styleEdit.text())) if text != "": self.styleLayout.takeAt(self.styleLayout.count()-1).widget().deleteLater() styleCheckBox = QCheckBox() styleCheckBox.setText(text) print(text) self.styleLayout.addWidget(styleCheckBox, self.x, self.y) self.checkBoxPositionAsignment() print(self.durationLine.text()) self.styleLayout.addWidget(self.styleEdit) def checkSong(self): text = self.titleEdit.text() sql = "SELECT title from songs where title = ?" if len(queries(sql, (text,))) != 0: self.titleEdit.setText("") def validateSong(self): pass #VALIDATE REG EXP def validateStyle(self, text): if "," in text: self.styleEdit.undo() def playSong(self): if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.mediaPlayer.pause() else: self.mediaPlayer.play() def mediaStateChanged(self): if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.playButton.setIcon(self.PAUSE_ICON) else: self.playButton.setIcon(self.PLAY_ICON) def positionChanged(self, position): if position != self.mediaPlayer.duration(): self.slider.setValue(position) def durationChanged(self, duration): if duration != self.mediaPlayer.position(): print("duration chagned") self.slider.setRange(0, duration) def playSlider(self): self.mediaPlayer.setPosition(self.slider.value()) def stopSong(self): self.mediaPlayer.stop() def locateFile(self): self.fileSystem = QFileDialog(filter="Sound files (*.wav *.mp3 *.flac)") self.fileSystem.show() self.fileSystem.fileSelected.connect(self.fileLoaded) def fileLoaded(self, path): self.locationLine.setText(path) try: self.playlist = QMediaPlaylist() self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile(path))) self.mediaPlayer.setPlaylist(self.playlist) except: print("fail") self.slider.setVisible(True) self.playButton.setVisible(True) self.stopButton.setVisible(True) def saveSong(self): title = self.titleEdit.text() status = self.statusBox.currentText() date = self.dateEdit.text() style = "" print(status, style) x = 0 for checkBox in self.styleGroupBox.children(): if isinstance(checkBox, QCheckBox): if checkBox.isChecked(): style += (checkBox.text()) + "," x+=1 if x != 0: style = style.rstrip(",") else: style = None duration = self.durationLine.time() duration = QTime(0, 0).secsTo(duration) project = self.projectComboBox.currentText() variation = self.variationLine.text() description = self.descriptionTextEdit.toPlainText() location = self.locationLine.text() variables = [title, status, description, location, project,\ variation, date, style, duration] print("---------", variables) sql = """INSERT OR REPLACE into songs (title, status, description, location, project, variation_another_song, timestamp, style, duration) values (?, ?, ?, ?, ?, ?, ?, ?, ?)""" queries(sql, variables) self.populateList()
class ComputingGroup(QGroupBoxCollapsible): """ This class is a subclass of class QGroupBox. """ send_refresh_filenames = pyqtSignal(name='send_refresh_filenames') def __init__(self, path_prj, name_prj, send_log, title): super().__init__() self.path_prj = path_prj self.name_prj = name_prj self.send_log = send_log self.path_last_file_loaded = self.path_prj self.project_properties = load_project_properties(self.path_prj) self.setTitle(title) self.init_ui() self.msg2 = QMessageBox() self.mesh_manager_file = self.read_attribute_xml("mesh_manager_file") self.read_mesh_manager_file(self.mesh_manager_file) # process_manager self.process_manager = MyProcessManager("mesh_manager") def init_ui(self): # file_selection file_selection_label = QLabel(self.tr("Select a 2D mesh file :")) self.file_selection_listwidget = QListWidget() self.file_selection_listwidget.setSelectionMode( QAbstractItemView.ExtendedSelection) self.file_selection_listwidget.itemSelectionChanged.connect( self.names_hdf5_change) self.file_selection_listwidget.setVerticalScrollBarPolicy( Qt.ScrollBarAlwaysOff) self.file_selection_listwidget.verticalScrollBar().setEnabled(True) self.file_selection_listwidget.verticalScrollBar( ).valueChanged.connect(self.change_scroll_position) self.scrollbar = self.file_selection_listwidget.verticalScrollBar() mesh_manager_file = QLabel(self.tr("Mesh manager file (.txt)")) self.mesh_manager_filename_label = QLabel("") self.mesh_manager_file_select_pushbutton = QPushButton("...") self.mesh_manager_file_select_pushbutton.clicked.connect( self.select_mesh_manager_file_dialog) # progress_layout self.progress_layout = ProcessProgLayout( self.compute, send_log=self.send_log, process_type="mesh_manager", send_refresh_filenames=self.send_refresh_filenames) file_selection_layout = QGridLayout() file_selection_layout.addWidget(file_selection_label, 0, 0) file_selection_layout.addWidget(self.file_selection_listwidget, 1, 0) file_selection_layout.addWidget(self.scrollbar, 1, 1) file_selection_layout.setColumnStretch(0, 30) file_selection_layout.setColumnStretch(1, 1) grid_layout = QGridLayout() grid_layout.addWidget(mesh_manager_file, 0, 0, Qt.AlignLeft) grid_layout.addWidget(self.mesh_manager_filename_label, 0, 1, Qt.AlignLeft) grid_layout.addWidget(self.mesh_manager_file_select_pushbutton, 0, 2, Qt.AlignLeft) grid_layout.addLayout(self.progress_layout, 1, 0, 1, 3) general_layout = QVBoxLayout() general_layout.addLayout(file_selection_layout) general_layout.addLayout(grid_layout) self.setLayout(general_layout) def update_gui(self): selected_file_names = [ selection_el.text() for selection_el in self.file_selection_listwidget.selectedItems() ] # computing_group hyd_names = get_filename_by_type_physic( "hydraulic", os.path.join(self.path_prj, "hdf5")) hab_names = get_filename_by_type_physic( "habitat", os.path.join(self.path_prj, "hdf5")) names = hyd_names + hab_names self.file_selection_listwidget.blockSignals(True) self.file_selection_listwidget.clear() if names: for name in names: # check try: hdf5 = Hdf5Management(self.path_prj, name, new=False, edit=False) hdf5.get_hdf5_attributes(close_file=True) item_name = QListWidgetItem() item_name.setText(name) self.file_selection_listwidget.addItem(item_name) if name in selected_file_names: item_name.setSelected(True) if True: #TODO : sort files (hdf5 attributes available for HRR) .hyd, one whole profile for all units, ... pass else: pass except: self.send_log.emit( self. tr("Error: " + name + " file seems to be corrupted. Delete it with HABBY or manually." )) self.file_selection_listwidget.blockSignals(False) # preselection if one if self.file_selection_listwidget.count() == 1: self.file_selection_listwidget.selectAll() def change_scroll_position(self, index): self.file_selection_listwidget.verticalScrollBar().setValue(index) def names_hdf5_change(self): selection = self.file_selection_listwidget.selectedItems() self.progress_layout.progress_bar.setValue(0.0) self.progress_layout.progress_label.setText("{0:.0f}/{1:.0f}".format( 0.0, len(selection))) if selection: self.progress_layout.run_stop_button.setEnabled(True) else: self.progress_layout.run_stop_button.setEnabled(False) def read_mesh_manager_file(self, mesh_manager_file): if os.path.exists(mesh_manager_file): self.mesh_manager_description, warnings_list = mesh_manager_from_file( mesh_manager_file) if warnings_list: for warning in warnings_list: self.send_log.emit(warning) if self.mesh_manager_description["mesh_manager_data"] is None: self.send_log.emit( self.tr("Error: Mesh manager file : ") + os.path.basename(mesh_manager_file) + self.tr(" is not valid.")) self.progress_layout.run_stop_button.setEnabled(False) else: self.mesh_manager_filename_label.setText( os.path.basename(mesh_manager_file)) if self.file_selection_listwidget.selectedItems(): self.progress_layout.run_stop_button.setEnabled(True) else: self.progress_layout.run_stop_button.setEnabled(False) self.progress_layout.progress_bar.setValue(0.0) self.progress_layout.progress_label.setText("{0:.0f}/{1:.0f}".format( 0.0, len(self.file_selection_listwidget.selectedItems()))) def select_mesh_manager_file_dialog(self): self.mesh_manager_file = self.read_attribute_xml("mesh_manager_file") # get last path if self.mesh_manager_file != self.path_prj and self.mesh_manager_file != "": model_path = self.mesh_manager_file # path spe elif self.read_attribute_xml( "path_last_file_loaded" ) != self.path_prj and self.read_attribute_xml( "path_last_file_loaded") != "": model_path = self.read_attribute_xml( "path_last_file_loaded") # path last else: model_path = self.path_prj # path proj filename, _ = QFileDialog.getOpenFileName( self, self.tr("Select a mesh manager file"), model_path, self.tr("Text files") + " (*.txt)") if filename: self.pathfile = filename # source file path self.save_xml("mesh_manager_file") self.read_mesh_manager_file(filename) self.mesh_manager_file = self.read_attribute_xml( "mesh_manager_file") def read_attribute_xml(self, att_here): """ A function to read the text of an attribute in the xml project file. :param att_here: the attribute name (string). """ data = '' filename_path_pro = os.path.join(self.path_prj, self.name_prj + '.habby') if os.path.isfile(filename_path_pro): if att_here in {"path_last_file_loaded"}: data = load_project_properties(self.path_prj)[att_here] else: try: data = load_project_properties(self.path_prj)[att_here] except KeyError: self.save_xml("mesh_manager_file") data = load_project_properties(self.path_prj)[att_here] else: pass return data def save_xml(self, attr): """ A function to save the loaded data in the xml file. This function adds the name and the path of the newly chosen hydrological data to the xml project file. First, it open the xml project file (and send an error if the project is not saved, or if it cannot find the project file). Then, it opens the xml file and add the path and name of the file to this xml file. If the model data was already loaded, it adds the new name without erasing the old name IF the switch append_name is True. Otherwise, it erase the old name and replace it by a new name. The variable “i” has the same role than in select_file_and_show_informations_dialog. :param i: a int for the case where there is more than one file to load :param append_name: A boolean. If True, the name found will be append to the existing name in the xml file, instead of remplacing the old name by the new name. """ filename_path_pro = os.path.join(self.path_prj, self.name_prj + '.habby') # save the name and the path in the xml .prj file if not os.path.isfile(filename_path_pro): self.end_log.emit( 'Error: The project is not saved. ' 'Save the project in the General tab before saving hydrological data. \n' ) else: # change path_last_file_loaded, model_type (path) self.project_properties = load_project_properties( self.path_prj) # load_project_properties self.project_properties[ "path_last_file_loaded"] = self.pathfile # change value self.project_properties[attr] = self.pathfile # change value save_project_properties( self.path_prj, self.project_properties) # save_project_properties def compute(self): if len(self.file_selection_listwidget.selectedItems()) > 0: mesh_manager_description = self.mesh_manager_description mesh_manager_description["hdf5_name_list"] = [ selection_el.text() for selection_el in self.file_selection_listwidget.selectedItems() ] # for hdf5_file in mesh_manager_description["hdf5_name_list"]: # hdf5_1 = Hdf5Management(self.path_prj, hdf5_file, new=False, edit=False) # hdf5_1.load_hdf5(whole_profil=False) # if hdf5_1.data_2d.hvum.hdf5_and_computable_list.habs(): # self.msg2.setIcon(QMessageBox.Warning) # self.msg2.setWindowTitle(self.tr("HSI data in ") + hdf5_1.filename[:-4] + "_MM" + hdf5_1.extension + ".") # self.msg2.setText(self.tr("If computing, existing HSI data will be removed in ") + hdf5_1.filename[:-4] + "_MM" + hdf5_1.extension + ".\n"+ # self.tr("Do you really want to continue computing ?")) # self.msg2.setStandardButtons(QMessageBox.Yes | QMessageBox.No) # res = self.msg2.exec_() # # # cancel # if res == QMessageBox.No: # return # if res == QMessageBox.Yes: # break self.progress_layout.process_manager.set_mesh_manager( self.path_prj, mesh_manager_description, self.project_properties) # start thread self.progress_layout.start_process() def stop_compute(self): # stop_by_user self.process_manager.stop_by_user()
class VisualGroup(QGroupBoxCollapsible): """ This class is a subclass of class QGroupBox. """ def __init__(self, path_prj, name_prj, send_log, title): super().__init__() self.path_prj = path_prj self.name_prj = name_prj self.send_log = send_log self.path_last_file_loaded = self.path_prj self.process_manager = MyProcessManager("hs_plot") self.axe_mod_choosen = 1 self.setTitle(title) self.init_ui() self.process_prog_show_input = ProcessProgShow(send_log=self.send_log, run_function=self.plot_hs_class, computation_pushbutton=self.input_class_plot_button) self.process_prog_show_area = ProcessProgShow(send_log=self.send_log, run_function=self.plot_hs_area, computation_pushbutton=self.result_plot_button_area) self.process_prog_show_volume = ProcessProgShow(send_log=self.send_log, run_function=self.plot_hs_volume, computation_pushbutton=self.result_plot_button_volume) def init_ui(self): # file_selection file_selection_label = QLabel(self.tr("HS files :")) self.file_selection_listwidget = QListWidget() self.file_selection_listwidget.itemSelectionChanged.connect(self.names_hdf5_change) file_selection_layout = QVBoxLayout() file_selection_layout.addWidget(file_selection_label) file_selection_layout.addWidget(self.file_selection_listwidget) # reach reach_label = QLabel(self.tr('reach(s)')) self.reach_QListWidget = QListWidget() self.reach_QListWidget.setSelectionMode(QAbstractItemView.ExtendedSelection) self.reach_QListWidget.itemSelectionChanged.connect(self.reach_hdf5_change) reach_layout = QVBoxLayout() reach_layout.addWidget(reach_label) reach_layout.addWidget(self.reach_QListWidget) # units units_label = QLabel(self.tr('unit(s)')) self.units_QListWidget = QListWidget() self.units_QListWidget.setSelectionMode(QAbstractItemView.ExtendedSelection) self.units_QListWidget.itemSelectionChanged.connect(self.unit_hdf5_change) units_layout = QVBoxLayout() units_layout.addWidget(units_label) units_layout.addWidget(self.units_QListWidget) # axe self.axe_mod_choosen = load_specific_properties(self.path_prj, ["hs_axe_mod"])[0] axe_label = QLabel(self.tr("Axe orientation :")) self.axe_mod_1_radio = QRadioButton() if self.axe_mod_choosen == 1: self.axe_mod_1_radio.setChecked(True) self.axe_mod_1_radio.setIcon(QIcon(r"file_dep/axe_mod_1.png")) self.axe_mod_1_radio.setIconSize(QSize(75, 75)) self.axe_mod_1_radio.clicked.connect(self.change_axe_mod) self.axe_mod_2_radio = QRadioButton() if self.axe_mod_choosen == 2: self.axe_mod_2_radio.setChecked(True) self.axe_mod_2_radio.setIcon(QIcon(r"file_dep/axe_mod_2.png")) self.axe_mod_2_radio.setIconSize(QSize(75, 75)) self.axe_mod_2_radio.clicked.connect(self.change_axe_mod) self.axe_mod_3_radio = QRadioButton() if self.axe_mod_choosen == 3: self.axe_mod_3_radio.setChecked(True) self.axe_mod_3_radio.setIcon(QIcon(r"file_dep/axe_mod_3.png")) self.axe_mod_3_radio.setIconSize(QSize(75, 75)) self.axe_mod_3_radio.clicked.connect(self.change_axe_mod) axe_mod_layout = QHBoxLayout() axe_mod_layout.addWidget(self.axe_mod_1_radio) axe_mod_layout.addWidget(self.axe_mod_2_radio) axe_mod_layout.addWidget(self.axe_mod_3_radio) axe_layout = QVBoxLayout() axe_layout.addWidget(axe_label) axe_layout.addLayout(axe_mod_layout) axe_layout.addStretch() selection_layout = QHBoxLayout() selection_layout.addLayout(file_selection_layout) selection_layout.addLayout(reach_layout) selection_layout.addLayout(units_layout) selection_layout.addLayout(axe_layout) # input_class input_class_label = QLabel(self.tr("Input class :")) input_class_h_label = QLabel(self.tr("h (m)")) self.input_class_h_lineedit = QLineEdit("") input_class_v_label = QLabel(self.tr("v (m)")) self.input_class_v_lineedit = QLineEdit("") self.input_class_plot_button = QPushButton(self.tr("Show input")) self.input_class_plot_button.clicked.connect(self.plot_hs_class) change_button_color(self.input_class_plot_button, "#47B5E6") self.input_class_plot_button.setEnabled(False) input_class_layout = QGridLayout() input_class_layout.addWidget(input_class_label, 0, 0, 1, 2) input_class_layout.addWidget(input_class_h_label, 1, 0) input_class_layout.addWidget(input_class_v_label, 2, 0) input_class_layout.addWidget(self.input_class_h_lineedit, 1, 1) input_class_layout.addWidget(self.input_class_v_lineedit, 2, 1) input_class_layout.addWidget(self.input_class_plot_button, 1, 2, 2, 1) # from row, from column, nb row, nb column # result result_label = QLabel(self.tr("Result :")) self.result_tableview = QTableView() self.result_tableview.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.result_tableview.verticalHeader().setVisible(False) self.result_tableview.horizontalHeader().setVisible(False) self.result_plot_button_area = QPushButton(self.tr("Show area")) self.result_plot_button_area.clicked.connect(self.plot_hs_area) self.result_plot_button_area.setEnabled(False) change_button_color(self.result_plot_button_area, "#47B5E6") self.result_plot_button_volume = QPushButton(self.tr("Show volume")) self.result_plot_button_volume.clicked.connect(self.plot_hs_volume) self.result_plot_button_volume.setEnabled(False) change_button_color(self.result_plot_button_volume, "#47B5E6") pushbutton_layout = QVBoxLayout() pushbutton_layout.addWidget(self.result_plot_button_area) pushbutton_layout.addWidget(self.result_plot_button_volume) result_layout = QGridLayout() result_layout.addWidget(result_label, 0, 0) result_layout.addWidget(self.result_tableview, 1, 0, 2, 1) result_layout.addLayout(pushbutton_layout, 1, 1) self.input_result_group = QGroupBox() input_result_layout = QVBoxLayout() input_result_layout.addLayout(input_class_layout) input_result_layout.addLayout(result_layout) self.input_result_group.setLayout(input_result_layout) self.input_result_group.hide() general_layout = QVBoxLayout() general_layout.addLayout(selection_layout) general_layout.addWidget(self.input_result_group) self.setLayout(general_layout) def update_gui(self): hs_names = get_filename_hs(os.path.join(self.path_prj, "hdf5")) self.file_selection_listwidget.blockSignals(True) self.file_selection_listwidget.clear() if hs_names: self.file_selection_listwidget.addItems(hs_names) self.file_selection_listwidget.blockSignals(False) def change_axe_mod(self): if self.axe_mod_1_radio.isChecked(): self.axe_mod_choosen = 1 elif self.axe_mod_2_radio.isChecked(): self.axe_mod_choosen = 2 elif self.axe_mod_3_radio.isChecked(): self.axe_mod_choosen = 3 change_specific_properties(self.path_prj, ["hs_axe_mod"], [self.axe_mod_choosen]) def names_hdf5_change(self): self.reach_QListWidget.clear() self.units_QListWidget.clear() selection = self.file_selection_listwidget.selectedItems() if selection: # read hdf5name = selection[0].text() hdf5 = Hdf5Management(self.path_prj, hdf5name, new=False, edit=False) hdf5.get_hdf5_attributes(close_file=True) # check reach self.reach_QListWidget.addItems(hdf5.data_2d.reach_list) self.input_class_h_lineedit.setText(", ".join(list(map(str, hdf5.hs_input_class[0])))) self.input_class_v_lineedit.setText(", ".join(list(map(str, hdf5.hs_input_class[1])))) self.input_class_plot_button.setEnabled(True) self.toggle_group(False) self.input_result_group.show() self.toggle_group(True) else: self.input_result_group.hide() def reach_hdf5_change(self): selection_file = self.file_selection_listwidget.selectedItems() selection_reach = self.reach_QListWidget.selectedItems() self.units_QListWidget.clear() # one file selected if len(selection_reach) == 1: hdf5name = selection_file[0].text() # create hdf5 class hdf5 = Hdf5Management(self.path_prj, hdf5name, new=False, edit=False) hdf5.get_hdf5_attributes(close_file=True) # add units for item_text in hdf5.data_2d.unit_list[self.reach_QListWidget.currentRow()]: item = QListWidgetItem(item_text) item.setTextAlignment(Qt.AlignRight) self.units_QListWidget.addItem(item) if len(selection_reach) > 1: # add units item = QListWidgetItem("all units") item.setTextAlignment(Qt.AlignRight) self.units_QListWidget.addItem(item) self.units_QListWidget.selectAll() def unit_hdf5_change(self): selection_unit = self.units_QListWidget.selectedItems() # one file selected if len(selection_unit) > 0: hdf5name = self.file_selection_listwidget.selectedItems()[0].text() # create hdf5 class hdf5 = Hdf5Management(self.path_prj, hdf5name, new=False, edit=False) hdf5.load_hydrosignature() hdf5.close_file() if len(selection_unit) == 1 and selection_unit[0].text() == "all units": # get hs data hdf5.data_2d.get_hs_summary_data([element.row() for element in self.reach_QListWidget.selectedIndexes()], list(range(hdf5.nb_unit))) else: # get hs data hdf5.data_2d.get_hs_summary_data([element.row() for element in self.reach_QListWidget.selectedIndexes()], [element.row() for element in self.units_QListWidget.selectedIndexes()]) # table mytablemodel = MyTableModel(hdf5.data_2d.hs_summary_data) self.result_tableview.setModel(mytablemodel) # set model self.result_plot_button_area.setEnabled(True) self.result_plot_button_volume.setEnabled(True) else: mytablemodel = MyTableModel(["", ""]) self.result_tableview.setModel(mytablemodel) # set model self.result_plot_button_area.setEnabled(False) self.result_plot_button_volume.setEnabled(False) def plot_hs_class(self): plot_attr = lambda: None plot_attr.nb_plot = 1 plot_attr.axe_mod_choosen = self.axe_mod_choosen plot_attr.hs_plot_type = "input_class" # process_manager self.process_manager.set_plot_hdf5_mode(self.path_prj, [self.file_selection_listwidget.selectedItems()[0].text()], plot_attr, load_project_properties(self.path_prj)) # process_prog_show self.process_prog_show_input.start_show_prog(self.process_manager) def plot_hs_area(self): plot_attr = lambda: None plot_attr.axe_mod_choosen = self.axe_mod_choosen plot_attr.hs_plot_type = "area" plot_attr.reach = [element.row() for element in self.reach_QListWidget.selectedIndexes()] plot_attr.units = [element.row() for element in self.units_QListWidget.selectedIndexes()] plot_attr.nb_plot = len(plot_attr.units) # process_manager self.process_manager.set_plot_hdf5_mode(self.path_prj, [self.file_selection_listwidget.selectedItems()[0].text()], plot_attr, load_project_properties(self.path_prj)) # process_prog_show self.process_prog_show_area.start_show_prog(self.process_manager) def plot_hs_volume(self): plot_attr = lambda: None plot_attr.axe_mod_choosen = self.axe_mod_choosen plot_attr.hs_plot_type = "volume" plot_attr.reach = [element.row() for element in self.reach_QListWidget.selectedIndexes()] plot_attr.units = [element.row() for element in self.units_QListWidget.selectedIndexes()] plot_attr.nb_plot = len(plot_attr.units) # process_manager self.process_manager.set_plot_hdf5_mode(self.path_prj, [self.file_selection_listwidget.selectedItems()[0].text()], plot_attr, load_project_properties(self.path_prj)) # process_prog_show self.process_prog_show_volume.start_show_prog(self.process_manager)
class ComputingGroup(QGroupBoxCollapsible): """ This class is a subclass of class QGroupBox. """ send_refresh_filenames = pyqtSignal(name='send_refresh_filenames') def __init__(self, path_prj, name_prj, send_log, title): super().__init__() self.path_prj = path_prj self.name_prj = name_prj self.send_log = send_log self.path_last_file_loaded = self.path_prj self.classhv = None self.project_properties = load_project_properties(self.path_prj) self.setTitle(title) self.init_ui() self.input_class_file_info = self.read_attribute_xml("HS_input_class") self.read_input_class(os.path.join(self.input_class_file_info["path"], self.input_class_file_info["file"])) # process_manager self.process_manager = MyProcessManager("hs") def init_ui(self): # file_selection file_selection_label = QLabel(self.tr("Select a 2D mesh file :")) self.file_selection_listwidget = QListWidget() self.file_selection_listwidget.setSelectionMode(QAbstractItemView.ExtendedSelection) self.file_selection_listwidget.itemSelectionChanged.connect(self.names_hdf5_change) self.file_selection_listwidget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.file_selection_listwidget.verticalScrollBar().setEnabled(True) self.file_selection_listwidget.verticalScrollBar().valueChanged.connect(self.change_scroll_position) self.scrollbar = self.file_selection_listwidget.verticalScrollBar() file_computed_label = QLabel(self.tr("Computed ?")) self.hs_computed_listwidget = QListWidget() self.hs_computed_listwidget.setEnabled(False) self.hs_computed_listwidget.setFlow(QListView.TopToBottom) self.hs_computed_listwidget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.hs_computed_listwidget.verticalScrollBar().setEnabled(True) self.hs_computed_listwidget.verticalScrollBar().valueChanged.connect(self.change_scroll_position) file_selection_layout = QGridLayout() file_selection_layout.addWidget(file_selection_label, 0, 0) file_selection_layout.addWidget(self.file_selection_listwidget, 1, 0) file_selection_layout.addWidget(file_computed_label, 0, 1) file_selection_layout.addWidget(self.hs_computed_listwidget, 1, 1) file_selection_layout.addWidget(self.scrollbar, 1, 2) file_selection_layout.setColumnStretch(0, 30) file_selection_layout.setColumnStretch(1, 1) input_class_label = QLabel(self.tr("Input class (.txt)")) self.input_class_filename = QLabel("") self.input_class_pushbutton = QPushButton("...") self.input_class_pushbutton.clicked.connect(self.select_input_class_dialog) hs_export_txt_label = QLabel(self.tr("Export results (.txt)")) self.hs_export_txt_checkbox = QCheckBox() self.hs_export_txt_checkbox.setChecked(True) hs_export_mesh_label = QLabel(self.tr("Export mesh results (.hyd or .hab)")) self.hs_export_mesh_checkbox = QCheckBox() """ progress layout """ # progress_layout self.progress_layout = ProcessProgLayout(self.compute, send_log=self.send_log, process_type="hs", send_refresh_filenames=self.send_refresh_filenames) grid_layout = QGridLayout() grid_layout.addWidget(input_class_label, 2, 0, Qt.AlignLeft) grid_layout.addWidget(self.input_class_filename, 2, 1, Qt.AlignLeft) grid_layout.addWidget(self.input_class_pushbutton, 2, 2, Qt.AlignLeft) grid_layout.addWidget(hs_export_txt_label, 3, 0, Qt.AlignLeft) grid_layout.addWidget(self.hs_export_txt_checkbox, 3, 1, Qt.AlignLeft) grid_layout.addWidget(hs_export_mesh_label, 4, 0, Qt.AlignLeft) grid_layout.addWidget(self.hs_export_mesh_checkbox, 4, 1, Qt.AlignLeft) grid_layout.addLayout(self.progress_layout, 5, 0, 1, 3) grid_layout.setColumnStretch(0, 2) grid_layout.setColumnStretch(1, 1) grid_layout.setColumnStretch(2, 1) grid_layout.setAlignment(Qt.AlignRight) general_layout = QVBoxLayout() general_layout.addLayout(file_selection_layout) general_layout.addLayout(grid_layout) self.setLayout(general_layout) def update_gui(self): selected_file_names = [selection_el.text() for selection_el in self.file_selection_listwidget.selectedItems()] # computing_group hyd_names = get_filename_by_type_physic("hydraulic", os.path.join(self.path_prj, "hdf5")) hab_names = get_filename_by_type_physic("habitat", os.path.join(self.path_prj, "hdf5")) names = hyd_names + hab_names self.file_selection_listwidget.blockSignals(True) self.file_selection_listwidget.clear() self.hs_computed_listwidget.blockSignals(True) self.hs_computed_listwidget.clear() if names: for name in names: # filename item_name = QListWidgetItem() item_name.setText(name) self.file_selection_listwidget.addItem(item_name) if name in selected_file_names: item_name.setSelected(True) # check item = QListWidgetItem() item.setText("") item.setFlags(item.flags() | Qt.ItemIsUserCheckable) try: hdf5 = Hdf5Management(self.path_prj, name, new=False, edit=False) hdf5.get_hdf5_attributes(close_file=True) if hdf5.hs_calculated: item.setCheckState(Qt.Checked) else: item.setCheckState(Qt.Unchecked) except: self.send_log.emit(self.tr("Error: " + name + " file seems to be corrupted. Delete it with HABBY or manually.")) self.hs_computed_listwidget.addItem(item) item.setTextAlignment(Qt.AlignCenter) self.file_selection_listwidget.blockSignals(False) self.hs_computed_listwidget.blockSignals(False) # preselection if one if self.file_selection_listwidget.count() == 1: self.file_selection_listwidget.selectAll() def change_scroll_position(self, index): self.file_selection_listwidget.verticalScrollBar().setValue(index) self.hs_computed_listwidget.verticalScrollBar().setValue(index) def read_input_class(self, input_class_file): if os.path.exists(input_class_file): self.classhv, warnings_list = hydrosignature_mod.hydraulic_class_from_file(input_class_file) if warnings_list: for warning in warnings_list: self.send_log.emit(warning) if self.classhv is None: self.send_log.emit(self.tr("Error: Input class file : ") + os.path.basename(input_class_file) + self.tr(" is not valid.")) self.progress_layout.run_stop_button.setEnabled(False) else: self.input_class_filename.setText(os.path.basename(input_class_file)) if self.file_selection_listwidget.selectedItems(): self.progress_layout.run_stop_button.setEnabled(True) else: self.progress_layout.run_stop_button.setEnabled(False) self.progress_layout.progress_bar.setValue(0.0) self.progress_layout.progress_label.setText( "{0:.0f}/{1:.0f}".format(0.0, len(self.file_selection_listwidget.selectedItems()))) def names_hdf5_change(self): selection = self.file_selection_listwidget.selectedItems() self.progress_layout.progress_bar.setValue(0.0) self.progress_layout.progress_label.setText( "{0:.0f}/{1:.0f}".format(0.0, len(selection))) if selection: # enable run button if self.input_class_filename.text(): self.progress_layout.run_stop_button.setEnabled(True) else: self.progress_layout.run_stop_button.setEnabled(False) else: self.progress_layout.run_stop_button.setEnabled(False) def select_input_class_dialog(self): self.input_class_file_info = self.read_attribute_xml("HS_input_class") # get last path if self.input_class_file_info["path"] != self.path_prj and self.input_class_file_info["path"] != "": model_path = self.input_class_file_info["path"] # path spe elif self.read_attribute_xml("path_last_file_loaded") != self.path_prj and self.read_attribute_xml("path_last_file_loaded") != "": model_path = self.read_attribute_xml("path_last_file_loaded") # path last else: model_path = self.path_prj # path proj filename, _ = QFileDialog.getOpenFileName(self, self.tr("Select hydraulic class file"), model_path, self.tr("Text files") + " (*.txt)") if filename: self.pathfile = os.path.dirname(filename) # source file path self.namefile = os.path.basename(filename) # source file name self.save_xml("HS_input_class") self.read_input_class(filename) self.input_class_file_info = self.read_attribute_xml("HS_input_class") def read_attribute_xml(self, att_here): """ A function to read the text of an attribute in the xml project file. :param att_here: the attribute name (string). """ data = '' filename_path_pro = os.path.join(self.path_prj, self.name_prj + '.habby') if os.path.isfile(filename_path_pro): if att_here in {"path_last_file_loaded", "HS_input_class"}: data = load_project_properties(self.path_prj)[att_here] else: data = load_project_properties(self.path_prj)[att_here]["path"] else: pass return data def save_xml(self, attr): """ A function to save the loaded data in the xml file. This function adds the name and the path of the newly chosen hydrological data to the xml project file. First, it open the xml project file (and send an error if the project is not saved, or if it cannot find the project file). Then, it opens the xml file and add the path and name of the file to this xml file. If the model data was already loaded, it adds the new name without erasing the old name IF the switch append_name is True. Otherwise, it erase the old name and replace it by a new name. The variable “i” has the same role than in select_file_and_show_informations_dialog. :param i: a int for the case where there is more than one file to load :param append_name: A boolean. If True, the name found will be append to the existing name in the xml file, instead of remplacing the old name by the new name. """ filename_path_pro = os.path.join(self.path_prj, self.name_prj + '.habby') # save the name and the path in the xml .prj file if not os.path.isfile(filename_path_pro): self.end_log.emit('Error: The project is not saved. ' 'Save the project in the General tab before saving hydrological data. \n') else: # change path_last_file_loaded, model_type (path) self.project_properties = load_project_properties(self.path_prj) # load_project_properties self.project_properties["path_last_file_loaded"] = self.pathfile # change value self.project_properties[attr]["file"] = self.namefile # change value self.project_properties[attr]["path"] = self.pathfile # change value save_project_properties(self.path_prj, self.project_properties) # save_project_properties def compute(self): if len(self.file_selection_listwidget.selectedItems()) > 0: hydrosignature_description = dict(hs_export_mesh=self.hs_export_mesh_checkbox.isChecked(), hdf5_name_list=[selection_el.text() for selection_el in self.file_selection_listwidget.selectedItems()], hs_export_txt=self.hs_export_txt_checkbox.isChecked(), classhv_input_class_file_info=self.input_class_file_info, classhv=self.classhv) self.progress_layout.process_manager.set_hs_hdf5_mode(self.path_prj, hydrosignature_description, self.project_properties) # start thread self.progress_layout.start_process() def stop_compute(self): # stop_by_user self.process_manager.stop_by_user()
class PopupDataset(QWidget): def __init__(self, parent, top: Category): super().__init__(parent) self.setWindowFlags(Qt.Window | Qt.WindowStaysOnTopHint) self.setWindowTitle("Datasets") datasets = {} top.get_datasets(datasets) self.__datasets = datasets self.__cur_data = None _content = QGridLayout(self) self._list = QListWidget(self) self._list.addItems(datasets) self._list.blockSignals(True) self._list.currentItemChanged.connect(self.__changed_dataset) self._list.blockSignals(False) _content.addWidget(self._list, 0, 0, 4, 2) self._add = QPushButton("Add", self) self._add.setToolTip("") _content.addWidget(self._add, 4, 0) self._new = QPushButton("New", self) self._new.setToolTip("If the dataset if private or public") _content.addWidget(self._new, 4, 1) self._status = GDropbox("status", self, Status) self._status.setToolTip("") self._status.setFixedWidth(120) _content.addWidget(self._status, 0, 2) self._name = GField("name", self, str) self._name.setToolTip("Name of the dataset") _content.addWidget(self._name, 0, 3) self._ctype = GField("ctype", self, str) self._ctype.setToolTip("") _content.addWidget(self._ctype, 0, 4, 1, 2) self._dist = GDropbox("distribution", self, Distribution) self._dist.setToolTip("How the values are distributed in the dataset") self._dist.setFixedWidth(120) _content.addWidget(self._dist, 1, 2) self._min = GField("minimum", self, float) self._min.setToolTip("Minimum value in the dataset") _content.addWidget(self._min, 1, 3) self._max = GField("maximum", self, float) self._max.setToolTip("Maximum value in the dataset") _content.addWidget(self._max, 1, 4) self._dec = GField("decimals", self, int) self._dec.setToolTip("Number of decimals used in the dataset items") _content.addWidget(self._dec, 1, 5) self._items = GList("items", self) self._items.setToolTip("Dataset items") self._items.setFixedWidth(110) _content.addWidget(self._items, 2, 2, 3, 1) self._classes = QListWidget(self) self._classes.setToolTip("Instances that uses the current dataset") _content.addWidget(self._classes, 2, 3, 2, 3) self._key = QLineEdit(self) self._key.setToolTip("Item that will be updated (key value)") _content.addWidget(self._key, 4, 3) self._value = QLineEdit(self) self._value.setToolTip("Value to be used in the dataset's item update") _content.addWidget(self._value, 4, 4) self._update = QPushButton("Update", self) self._update.setToolTip("Update one item in the dataset using the " "values provided in the field on the left") self._update.clicked.connect(self.__update_items) _content.addWidget(self._update, 4, 5) self.setGeometry(100, 100, 600, 400) def __changed_dataset(self, current, _): self.__cur_data, classes = self.__datasets[current.text()] self._status.from_obj(self.__cur_data) self._name.from_obj(self.__cur_data) self._ctype.from_obj(self.__cur_data) self._dec.from_obj(self.__cur_data) self._dist.from_obj(self.__cur_data) self._min .from_obj(self.__cur_data) self._max.from_obj(self.__cur_data) self._items.from_obj(self.__cur_data) self._classes.clear() for _cls in classes: self._classes.addItem(str(_cls)) @action_handler def __update_items(self, _): key = int(self._key.text()) value = float(self._value.text()) self.__cur_data.items[key] = value self._items.from_obj(self.__cur_data) def closeEvent(self, _): self.parent().is_open_dataset = False
class _HistoryDialog: record_label = "Save..." execute_label = "Execute" def __init__(self, controller, typed_only): # make dialog hidden initially self.controller = controller self.typed_only = typed_only self.window = controller.tool_window.create_child_window( "Command History", close_destroys=False) self.window.fill_context_menu = self.fill_context_menu parent = self.window.ui_area from PyQt5.QtWidgets import QListWidget, QVBoxLayout, QFrame, QHBoxLayout, QPushButton, QLabel self.listbox = QListWidget(parent) self.listbox.setSelectionMode(QListWidget.ExtendedSelection) self.listbox.itemSelectionChanged.connect(self.select) main_layout = QVBoxLayout(parent) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.addWidget(self.listbox) num_cmd_frame = QFrame(parent) main_layout.addWidget(num_cmd_frame) num_cmd_layout = QHBoxLayout(num_cmd_frame) num_cmd_layout.setContentsMargins(0, 0, 0, 0) remem_label = QLabel("Remember") from PyQt5.QtCore import Qt remem_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) num_cmd_layout.addWidget(remem_label, 1) from PyQt5.QtWidgets import QSpinBox, QSizePolicy class ShorterQSpinBox(QSpinBox): max_val = 1000000 def textFromValue(self, val): # kludge to make the damn entry field shorter if val == self.max_val: return "1 mil" return str(val) spin_box = ShorterQSpinBox() spin_box.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) spin_box.setRange(100, spin_box.max_val) spin_box.setSingleStep(100) spin_box.setValue(controller.settings.num_remembered) spin_box.valueChanged.connect(self._num_remembered_changed) num_cmd_layout.addWidget(spin_box, 0) num_cmd_layout.addWidget(QLabel("commands"), 1) num_cmd_frame.setLayout(num_cmd_layout) button_frame = QFrame(parent) main_layout.addWidget(button_frame) button_layout = QHBoxLayout(button_frame) button_layout.setContentsMargins(0, 0, 0, 0) for but_name in [ self.record_label, self.execute_label, "Delete", "Copy", "Help" ]: but = QPushButton(but_name, button_frame) but.setAutoDefault(False) but.clicked.connect( lambda arg, txt=but_name: self.button_clicked(txt)) button_layout.addWidget(but) button_frame.setLayout(button_layout) self.window.manage(placement=None, initially_hidden=True) from chimerax.core.history import FIFOHistory self._history = FIFOHistory(controller.settings.num_remembered, controller.session, "commands") self._record_dialog = None self._search_cache = (False, None) def add(self, item, *, typed=False): if len(self._history) >= self.controller.settings.num_remembered: if not self.typed_only or self._history[0][1]: self.listbox.takeItem(0) if typed or not self.typed_only: self.listbox.addItem(item) self._history.enqueue((item, typed)) # 'if typed:' to avoid clearing any partially entered command text if typed: self.listbox.clearSelection() self.listbox.setCurrentRow(len(self.history()) - 1) self.update_list() def button_clicked(self, label): session = self.controller.session if label == self.record_label: from chimerax.ui.open_save import SaveDialog if self._record_dialog is None: fmt = session.data_formats["ChimeraX commands"] self._record_dialog = dlg = SaveDialog(session, self.window.ui_area, "Save Commands", data_formats=[fmt]) from PyQt5.QtWidgets import QFrame, QLabel, QHBoxLayout, QVBoxLayout, QComboBox from PyQt5.QtWidgets import QCheckBox from PyQt5.QtCore import Qt options_frame = dlg.custom_area options_layout = QVBoxLayout(options_frame) options_frame.setLayout(options_layout) amount_frame = QFrame(options_frame) options_layout.addWidget(amount_frame, Qt.AlignCenter) amount_layout = QHBoxLayout(amount_frame) amount_layout.addWidget(QLabel("Save", amount_frame)) self.save_amount_widget = saw = QComboBox(amount_frame) saw.addItems(["all", "selected"]) amount_layout.addWidget(saw) amount_layout.addWidget(QLabel("commands", amount_frame)) amount_frame.setLayout(amount_layout) self.append_checkbox = QCheckBox("Append to file", options_frame) self.append_checkbox.stateChanged.connect(self.append_changed) options_layout.addWidget(self.append_checkbox, Qt.AlignCenter) self.overwrite_disclaimer = disclaimer = QLabel( "<small><i>(ignore overwrite warning)</i></small>", options_frame) options_layout.addWidget(disclaimer, Qt.AlignCenter) disclaimer.hide() else: dlg = self._record_dialog if not dlg.exec(): return path = dlg.selectedFiles()[0] if not path: from chimerax.core.errors import UserError raise UserError("No file specified for saving command history") if self.save_amount_widget.currentText() == "all": cmds = [cmd for cmd in self.history()] else: # listbox.selectedItems() may not be in order, so... items = [ self.listbox.item(i) for i in range(self.listbox.count()) if self.listbox.item(i).isSelected() ] cmds = [item.text() for item in items] from chimerax.io import open_output f = open_output(path, encoding='utf-8', append=self.append_checkbox.isChecked()) for cmd in cmds: print(cmd, file=f) f.close() return if label == self.execute_label: for item in self.listbox.selectedItems(): self.controller.cmd_replace(item.text()) self.controller.execute() return if label == "Delete": retain = [] listbox_index = 0 for h_item in self._history: if self.typed_only and not h_item[1]: retain.append(h_item) continue if not self.listbox.item(listbox_index).isSelected(): # not selected for deletion retain.append(h_item) listbox_index += 1 self._history.replace(retain) self.populate() return if label == "Copy": clipboard = session.ui.clipboard() clipboard.setText("\n".join( [item.text() for item in self.listbox.selectedItems()])) return if label == "Help": from chimerax.core.commands import run run(session, 'help help:user/tools/cli.html#history') return def down(self, shifted): sels = self.listbox.selectedIndexes() if len(sels) != 1: self._search_cache = (False, None) return sel = sels[0].row() orig_text = self.controller.text.currentText() match_against = None if shifted: was_searching, prev_search = self._search_cache if was_searching: match_against = prev_search else: words = orig_text.strip().split() if words: match_against = words[0] self._search_cache = (True, match_against) else: self._search_cache = (False, None) else: self._search_cache = (False, None) if match_against: last = self.listbox.count() - 1 while sel < last: if self.listbox.item(sel + 1).text().startswith(match_against): break sel += 1 if sel == self.listbox.count() - 1: return self.listbox.clearSelection() self.listbox.setCurrentRow(sel + 1) new_text = self.listbox.item(sel + 1).text() self.controller.cmd_replace(new_text) if orig_text == new_text: self.down(shifted) def fill_context_menu(self, menu, x, y): # avoid having actions destroyed when this routine returns # by stowing a reference in the menu itself from PyQt5.QtWidgets import QAction filter_action = QAction("Typed commands only", menu) filter_action.setCheckable(True) filter_action.setChecked(self.controller.settings.typed_only) filter_action.toggled.connect( lambda arg, f=self.controller._set_typed_only: f(arg)) menu.addAction(filter_action) def on_append_change(self, event): self.overwrite_disclaimer.Show(self.save_append_CheckBox.Value) def append_changed(self, append): if append: self.overwrite_disclaimer.show() else: self.overwrite_disclaimer.hide() def on_listbox(self, event): self.select() def populate(self): self.listbox.clear() history = self.history() self.listbox.addItems([cmd for cmd in history]) self.listbox.setCurrentRow(len(history) - 1) self.update_list() self.select() self.controller.text.lineEdit().setFocus() self.controller.text.lineEdit().selectAll() cursels = self.listbox.scrollToBottom() def search_reset(self): searching, target = self._search_cache if searching: self._search_cache = (False, None) self.listbox.blockSignals(True) self.listbox.clearSelection() self.listbox.setCurrentRow(self.listbox.count() - 1) self.listbox.blockSignals(False) def select(self): sels = self.listbox.selectedItems() if len(sels) != 1: return self.controller.cmd_replace(sels[0].text()) def up(self, shifted): sels = self.listbox.selectedIndexes() if len(sels) != 1: self._search_cache = (False, None) return sel = sels[0].row() orig_text = self.controller.text.currentText() match_against = None if shifted: was_searching, prev_search = self._search_cache if was_searching: match_against = prev_search else: words = orig_text.strip().split() if words: match_against = words[0] self._search_cache = (True, match_against) else: self._search_cache = (False, None) else: self._search_cache = (False, None) if match_against: while sel > 0: if self.listbox.item(sel - 1).text().startswith(match_against): break sel -= 1 if sel == 0: return self.listbox.clearSelection() self.listbox.setCurrentRow(sel - 1) new_text = self.listbox.item(sel - 1).text() self.controller.cmd_replace(new_text) if orig_text == new_text: self.up(shifted) def update_list(self): c = self.controller last8 = list(reversed(self.history()[-8:])) # without blocking signals, if the command list is empty then # "Command History" (the first entry) will execute... c.text.blockSignals(True) c.text.clear() c.text.addItems(last8 + [c.show_history_label, c.compact_label]) if not last8: c.text.lineEdit().setText("") c.text.blockSignals(False) def history(self): if self.typed_only: return [h[0] for h in self._history if h[1]] return [h[0] for h in self._history] def set_typed_only(self, typed_only): self.typed_only = typed_only self.populate() def _num_remembered_changed(self, new_hist_len): if len(self._history) > new_hist_len: self._history.replace(self._history[-new_hist_len:]) self.populate() self.controller.settings.num_remembered = new_hist_len
class ComputingGroup(QGroupBoxCollapsible): """ This class is a subclass of class QGroupBox. """ send_refresh_filenames = pyqtSignal(name='send_refresh_filenames') def __init__(self, path_prj, name_prj, send_log, title): super().__init__() self.path_prj = path_prj self.name_prj = name_prj self.send_log = send_log self.path_last_file_loaded = self.path_prj self.project_properties = load_project_properties(self.path_prj) self.setTitle(title) self.init_ui() # process_manager self.process_manager = MyProcessManager("hrr") def init_ui(self): # file_selection file_selection_label = QLabel(self.tr("Select a 2D mesh file :")) self.file_selection_listwidget = QListWidget() self.file_selection_listwidget.setSelectionMode( QAbstractItemView.ExtendedSelection) self.file_selection_listwidget.itemSelectionChanged.connect( self.names_hdf5_change) self.file_selection_listwidget.setVerticalScrollBarPolicy( Qt.ScrollBarAlwaysOff) self.file_selection_listwidget.verticalScrollBar().setEnabled(True) self.file_selection_listwidget.verticalScrollBar( ).valueChanged.connect(self.change_scroll_position) self.scrollbar = self.file_selection_listwidget.verticalScrollBar() file_selection_layout = QGridLayout() file_selection_layout.addWidget(file_selection_label, 0, 0) file_selection_layout.addWidget(self.file_selection_listwidget, 1, 0) file_selection_layout.addWidget(self.scrollbar, 1, 2) file_selection_layout.setColumnStretch(0, 30) file_selection_layout.setColumnStretch(1, 1) """ progress layout """ # progress_layout self.progress_layout = ProcessProgLayout( self.compute, send_log=self.send_log, process_type="hrr", send_refresh_filenames=self.send_refresh_filenames) grid_layout = QGridLayout() grid_layout.addLayout(self.progress_layout, 5, 0, 1, 3) general_layout = QVBoxLayout() general_layout.addLayout(file_selection_layout) general_layout.addLayout(grid_layout) self.setLayout(general_layout) def update_gui(self): selected_file_names = [ selection_el.text() for selection_el in self.file_selection_listwidget.selectedItems() ] # computing_group hyd_names = get_filename_by_type_physic( "hydraulic", os.path.join(self.path_prj, "hdf5")) names = hyd_names self.file_selection_listwidget.blockSignals(True) self.file_selection_listwidget.clear() if names: for name in names: # check try: hdf5 = Hdf5Management(self.path_prj, name, new=False, edit=False) hdf5.get_hdf5_attributes(close_file=True) item_name = QListWidgetItem() item_name.setText(name) self.file_selection_listwidget.addItem(item_name) if True: #TODO : sort files (hdf5 attributes available for HRR) .hyd, one whole profile for all units, ... pass else: pass except: self.send_log.emit( self. tr("Error: " + name + " file seems to be corrupted. Delete it with HABBY or manually." )) self.file_selection_listwidget.blockSignals(False) # preselection if one if self.file_selection_listwidget.count() == 1: self.file_selection_listwidget.selectAll() def change_scroll_position(self, index): self.file_selection_listwidget.verticalScrollBar().setValue(index) def names_hdf5_change(self): selection = self.file_selection_listwidget.selectedItems() self.progress_layout.progress_bar.setValue(0.0) self.progress_layout.progress_label.setText("{0:.0f}/{1:.0f}".format( 0.0, len(selection))) if selection: self.progress_layout.run_stop_button.setEnabled(True) else: self.progress_layout.run_stop_button.setEnabled(False) def compute(self): if len(self.file_selection_listwidget.selectedItems()) > 0: hrr_description = dict( deltatlist=[], hdf5_name_list=[ selection_el.text() for selection_el in self.file_selection_listwidget.selectedItems() ]) self.progress_layout.process_manager.set_hrr_hdf5_mode( self.path_prj, hrr_description, self.project_properties) # start thread self.progress_layout.start_process() def stop_compute(self): # stop_by_user self.process_manager.stop_by_user()