class VideoFinderAddLink(AddLinkWindow): running_thread = None threadPool = {} def __init__(self, parent, receiver_slot, settings, video_dict={}): super().__init__(parent, receiver_slot, settings, video_dict) self.setWindowTitle( QCoreApplication.translate("ytaddlink_src_ui_tr", 'Video Finder')) self.size_label.hide() # empty lists for no_audio and no_video and video_audio files self.no_audio_list = [] self.no_video_list = [] self.video_audio_list = [] self.media_title = '' # add support for other languages locale = str(self.persepolis_setting.value('settings/locale')) QLocale.setDefault(QLocale(locale)) self.translator = QTranslator() if self.translator.load(':/translations/locales/ui_' + locale, 'ts'): QCoreApplication.installTranslator(self.translator) # extension_label self.extension_label = QLabel(self.link_frame) self.change_name_horizontalLayout.addWidget(self.extension_label) # Fetch Button self.url_submit_pushButtontton = QPushButton(self.link_frame) self.link_horizontalLayout.addWidget(self.url_submit_pushButtontton) # Status Box self.status_box_textEdit = QTextEdit(self.link_frame) self.status_box_textEdit.setMaximumHeight(150) self.link_verticalLayout.addWidget(self.status_box_textEdit) # Select format horizontal layout select_format_horizontalLayout = QHBoxLayout() # Selection Label select_format_label = QLabel(self.link_frame) select_format_horizontalLayout.addWidget(select_format_label) # Selection combobox self.media_comboBox = QComboBox(self.link_frame) self.media_comboBox.setMinimumWidth(200) select_format_horizontalLayout.addWidget(self.media_comboBox) # Duration label self.duration_label = QLabel(self.link_frame) select_format_horizontalLayout.addWidget(self.duration_label) self.format_selection_frame = QFrame(self) self.format_selection_frame.setLayout(select_format_horizontalLayout) self.link_verticalLayout.addWidget(self.format_selection_frame) # advanced_format_selection_checkBox self.advanced_format_selection_checkBox = QCheckBox(self) self.link_verticalLayout.addWidget( self.advanced_format_selection_checkBox) # advanced_format_selection_frame self.advanced_format_selection_frame = QFrame(self) self.link_verticalLayout.addWidget( self.advanced_format_selection_frame) advanced_format_selection_horizontalLayout = QHBoxLayout( self.advanced_format_selection_frame) # video_format_selection self.video_format_selection_label = QLabel( self.advanced_format_selection_frame) self.video_format_selection_comboBox = QComboBox( self.advanced_format_selection_frame) # audio_format_selection self.audio_format_selection_label = QLabel( self.advanced_format_selection_frame) self.audio_format_selection_comboBox = QComboBox( self.advanced_format_selection_frame) for widget in [ self.video_format_selection_label, self.video_format_selection_comboBox, self.audio_format_selection_label, self.audio_format_selection_comboBox ]: advanced_format_selection_horizontalLayout.addWidget(widget) # Set Texts self.url_submit_pushButtontton.setText( QCoreApplication.translate("ytaddlink_src_ui_tr", 'Fetch Media List')) select_format_label.setText( QCoreApplication.translate("ytaddlink_src_ui_tr", 'Select a format')) self.video_format_selection_label.setText( QCoreApplication.translate("ytaddlink_src_ui_tr", 'Video format:')) self.audio_format_selection_label.setText( QCoreApplication.translate("ytaddlink_src_ui_tr", 'Audio format:')) self.advanced_format_selection_checkBox.setText( QCoreApplication.translate("ytaddlink_src_ui_tr", 'Advanced options')) # Add Slot Connections self.url_submit_pushButtontton.setEnabled(False) self.change_name_lineEdit.setEnabled(False) self.ok_pushButton.setEnabled(False) self.download_later_pushButton.setEnabled(False) self.format_selection_frame.setEnabled(True) self.advanced_format_selection_frame.setEnabled(False) self.advanced_format_selection_checkBox.toggled.connect( self.advancedFormatFrame) self.url_submit_pushButtontton.clicked.connect(self.submitClicked) self.media_comboBox.activated.connect( partial(self.mediaSelectionChanged, 'video_audio')) self.video_format_selection_comboBox.activated.connect( partial(self.mediaSelectionChanged, 'video')) self.audio_format_selection_comboBox.activated.connect( partial(self.mediaSelectionChanged, 'audio')) self.link_lineEdit.textChanged.disconnect( super().linkLineChanged) # Should be disconnected. self.link_lineEdit.textChanged.connect(self.linkLineChangedHere) self.setMinimumSize(650, 480) self.status_box_textEdit.hide() self.format_selection_frame.hide() self.advanced_format_selection_frame.hide() self.advanced_format_selection_checkBox.hide() if 'link' in video_dict.keys() and video_dict['link']: self.link_lineEdit.setText(video_dict['link']) self.url_submit_pushButtontton.setEnabled(True) else: # check clipboard clipboard = QApplication.clipboard() text = clipboard.text() if (("tp:/" in text[2:6]) or ("tps:/" in text[2:7])): self.link_lineEdit.setText(str(text)) self.url_submit_pushButtontton.setEnabled(True) def advancedFormatFrame(self, button): if self.advanced_format_selection_checkBox.isChecked(): self.advanced_format_selection_frame.setEnabled(True) self.format_selection_frame.setEnabled(False) self.mediaSelectionChanged( 'video', int(self.video_format_selection_comboBox.currentIndex())) else: self.advanced_format_selection_frame.setEnabled(False) self.format_selection_frame.setEnabled(True) self.mediaSelectionChanged('video_audio', int(self.media_comboBox.currentIndex())) def getReadableSize(self, size): try: return '{:1.2f} MB'.format(int(size) / 1048576) except: return str(size) def getReadableDuration(self, seconds): try: seconds = int(seconds) hours = seconds // 3600 seconds = seconds % 3600 minutes = seconds // 60 seconds = seconds % 60 return '{:02d}:{:02d}:{:02d}'.format(hours, minutes, seconds) except: return str(seconds) # Define native slots def urlChanged(self, value): if ' ' in value or value == '': self.url_submit_pushButtontton.setEnabled(False) self.url_submit_pushButtontton.setToolTip( QCoreApplication.translate("ytaddlink_src_ui_tr", 'Please enter a valid video link')) else: self.url_submit_pushButtontton.setEnabled(True) self.url_submit_pushButtontton.setToolTip('') def submitClicked(self, button=None): # Clear media list self.media_comboBox.clear() self.format_selection_frame.hide() self.advanced_format_selection_checkBox.hide() self.advanced_format_selection_frame.hide() self.video_format_selection_comboBox.clear() self.audio_format_selection_comboBox.clear() self.change_name_lineEdit.clear() self.threadPool.clear() self.change_name_checkBox.setChecked(False) self.video_audio_list.clear() self.no_video_list.clear() self.no_audio_list.clear() self.url_submit_pushButtontton.setEnabled(False) self.status_box_textEdit.setText( QCoreApplication.translate("ytaddlink_src_ui_tr", 'Fetching Media Info...')) self.status_box_textEdit.show() self.ok_pushButton.setEnabled(False) self.download_later_pushButton.setEnabled(False) dictionary_to_send = deepcopy(self.plugin_add_link_dictionary) # More options more_options = self.collectMoreOptions() for k in more_options.keys(): dictionary_to_send[k] = more_options[k] dictionary_to_send['link'] = self.link_lineEdit.text() fetcher_thread = MediaListFetcherThread(self.fetchedResult, dictionary_to_send, self) self.parent.threadPool.append(fetcher_thread) self.parent.threadPool[len(self.parent.threadPool) - 1].start() def fileNameChanged(self, value): if value.strip() == '': self.ok_pushButton.setEnabled(False) def mediaSelectionChanged(self, combobox, index): try: if combobox == 'video_audio': if self.media_comboBox.currentText() == 'Best quality': self.change_name_lineEdit.setText(self.media_title) self.extension_label.setText('.' + self.no_audio_list[-1]['ext']) else: self.change_name_lineEdit.setText(self.media_title) self.extension_label.setText( '.' + self.video_audio_list[index]['ext']) self.change_name_checkBox.setChecked(True) elif combobox == 'video': if self.video_format_selection_comboBox.currentText( ) != 'No video': self.change_name_lineEdit.setText(self.media_title) self.extension_label.setText('.' + self.no_audio_list[index - 1]['ext']) self.change_name_checkBox.setChecked(True) else: if self.audio_format_selection_comboBox.currentText( ) != 'No audio': self.change_name_lineEdit.setText(self.media_title) self.extension_label.setText('.' + self.no_video_list[ int(self.audio_format_selection_comboBox. currentIndex()) - 1]['ext']) self.change_name_checkBox.setChecked(True) else: self.change_name_lineEdit.setChecked(False) elif combobox == 'audio': if self.audio_format_selection_comboBox.currentText( ) != 'No audio' and self.video_format_selection_comboBox.currentText( ) == 'No video': self.change_name_lineEdit.setText(self.media_title) self.extension_label.setText('.' + self.no_video_list[index - 1]['ext']) self.change_name_checkBox.setChecked(True) elif (self.audio_format_selection_comboBox.currentText() == 'No audio' and self.video_format_selection_comboBox.currentText() != 'No video') or ( self.audio_format_selection_comboBox.currentText() != 'No audio' and self.video_format_selection_comboBox.currentText() != 'No video'): self.change_name_lineEdit.setText(self.media_title) self.extension_label.setText('.' + self.no_audio_list[ int(self.video_format_selection_comboBox.currentIndex( )) - 1]['ext']) self.change_name_checkBox.setChecked(True) elif self.audio_format_selection_comboBox.currentText( ) == 'No audio' and self.video_format_selection_comboBox.currentText( ) == 'No video': self.change_name_checkBox.setChecked(False) except Exception as ex: logger.sendToLog(ex, "ERROR") def fetchedResult(self, media_dict): self.url_submit_pushButtontton.setEnabled(True) if 'error' in media_dict.keys(): self.status_box_textEdit.setText('<font color="#f11">' + str(media_dict['error']) + '</font>') self.status_box_textEdit.show() else: # Show the media list # add no audio and no video options to the comboboxes self.video_format_selection_comboBox.addItem('No video') self.audio_format_selection_comboBox.addItem('No audio') self.media_title = media_dict['title'] if 'formats' not in media_dict.keys( ) and 'entries' in media_dict.keys(): formats = media_dict['entries'] formats = formats[0] media_dict['formats'] = formats['formats'] elif 'formats' not in media_dict.keys( ) and 'format' in media_dict.keys(): media_dict['formats'] = [media_dict.copy()] try: i = 0 for f in media_dict['formats']: no_audio = False no_video = False text = '' if 'acodec' in f.keys(): # only video, no audio if f['acodec'] == 'none': no_audio = True # resolution if 'height' in f.keys(): text = text + ' ' + '{}p'.format(f['height']) if 'vcodec' in f.keys(): # if f['vcodec'] == 'none' and f['acodec'] != 'none': # continue # No video, show audio bit rate if f['vcodec'] == 'none': text = text + '{}kbps'.format(f['abr']) no_video = True if 'ext' in f.keys(): text = text + ' ' + '.{}'.format(f['ext']) if 'filesize' in f.keys() and f['filesize']: # Youtube api does not supply file size for some formats, so check it. text = text + ' ' + '{}'.format( self.getReadableSize(f['filesize'])) else: # Start spider to find file size input_dict = deepcopy(self.plugin_add_link_dictionary) input_dict['link'] = f['url'] more_options = self.collectMoreOptions() for key in more_options.keys(): input_dict[key] = more_options[key] size_fetcher = FileSizeFetcherThread(input_dict, i) self.threadPool[str(i)] = { 'thread': size_fetcher, 'item_id': i } self.parent.threadPool.append(size_fetcher) self.parent.threadPool[len(self.parent.threadPool) - 1].start() self.parent.threadPool[len(self.parent.threadPool) - 1].FOUND.connect( self.findFileSize) # Add current format to the related comboboxes if no_audio: self.no_audio_list.append(f) self.video_format_selection_comboBox.addItem(text) elif no_video: self.no_video_list.append(f) self.audio_format_selection_comboBox.addItem(text) else: self.video_audio_list.append(f) self.media_comboBox.addItem(text) i = i + 1 self.status_box_textEdit.hide() if 'duration' in media_dict.keys(): self.duration_label.setText( 'Duration ' + self.getReadableDuration(media_dict['duration'])) self.format_selection_frame.show() self.advanced_format_selection_checkBox.show() self.advanced_format_selection_frame.show() self.ok_pushButton.setEnabled(True) self.download_later_pushButton.setEnabled(True) # if we have no options for seperate audio and video, then hide advanced_format_selection... if len(self.no_audio_list) == 0 and len( self.no_video_list) == 0: self.advanced_format_selection_checkBox.hide() self.advanced_format_selection_frame.hide() # set index of comboboxes on best available quality. if len(self.no_audio_list) != 0 and len( self.no_video_list) != 0: self.media_comboBox.addItem('Best quality') self.media_comboBox.setCurrentIndex( len(self.video_audio_list)) elif len(self.video_audio_list) != 0: self.media_comboBox.setCurrentIndex( len(self.video_audio_list) - 1) if len(self.no_audio_list) != 0: self.video_format_selection_comboBox.setCurrentIndex( len(self.no_audio_list)) if len(self.no_video_list) != 0: self.audio_format_selection_comboBox.setCurrentIndex( len(self.no_video_list)) self.mediaSelectionChanged( 'video_audio', int(self.media_comboBox.currentIndex())) except Exception as ex: logger.sendToLog(ex, "ERROR") def findFileSize(self, result): try: item_id = self.threadPool[str(result['thread_key'])]['item_id'] if result['file_size'] and result['file_size'] != '0': text = self.media_comboBox.itemText(item_id) self.media_comboBox.setItemText( item_id, '{} - {}'.format(text, result['file_size'])) except Exception as ex: logger.sendToLog(ex, "ERROR") def linkLineChangedHere(self, lineEdit): if str(lineEdit) == '': self.url_submit_pushButtontton.setEnabled(False) else: self.url_submit_pushButtontton.setEnabled(True) # This method collects additional information like proxy ip, user, password etc. def collectMoreOptions(self): options = { 'ip': None, 'port': None, 'proxy_user': None, 'proxy_passwd': None, 'download_user': None, 'download_passwd': None } if self.proxy_checkBox.isChecked(): options['ip'] = self.ip_lineEdit.text() options['port'] = self.port_spinBox.value() options['proxy_user'] = self.proxy_user_lineEdit.text() options['proxy_passwd'] = self.proxy_pass_lineEdit.text() if self.download_checkBox.isChecked(): options['download_user'] = self.download_user_lineEdit.text() options['download_passwd'] = self.download_pass_lineEdit.text() # These info (keys) are required for spider to find file size, because spider() does not check if key exists. additional_info = [ 'header', 'load_cookies', 'user_agent', 'referer', 'out' ] for i in additional_info: if i not in self.plugin_add_link_dictionary.keys(): options[i] = None return options # user commited information by pressing ok_pushButton, so get information # from VideoFinderAddLink window and return them to the mainwindow with callback! def okButtonPressed(self, button, download_later): link_list = [] # seperate audio format and video format is selected. if self.advanced_format_selection_checkBox.isChecked(): if self.video_format_selection_comboBox.currentText( ) == 'No video' and self.audio_format_selection_comboBox.currentText( ) != 'No audio': # only audio link must be added to the link_list audio_link = self.no_video_list[ self.audio_format_selection_comboBox.currentIndex() - 1]['url'] link_list.append(audio_link) elif self.video_format_selection_comboBox.currentText( ) != 'No video' and self.audio_format_selection_comboBox.currentText( ) == 'No audio': # only video link must be added to the link_list video_link = self.no_audio_list[ self.video_format_selection_comboBox.currentIndex() - 1]['url'] link_list.append(video_link) elif self.video_format_selection_comboBox.currentText( ) != 'No video' and self.audio_format_selection_comboBox.currentText( ) != 'No audio': # video and audio links must be added to the link_list audio_link = self.no_video_list[ self.audio_format_selection_comboBox.currentIndex() - 1]['url'] video_link = self.no_audio_list[ self.video_format_selection_comboBox.currentIndex() - 1]['url'] link_list = [video_link, audio_link] elif self.video_format_selection_comboBox.currentText( ) == 'No video' and self.audio_format_selection_comboBox.currentText( ) == 'No audio': # no video and no video selected! REALY?!. user is DRUNK! close the window! :)) self.close() else: if self.media_comboBox.currentText() == 'Best quality': # the last item in no_video_list and no_audio_list are the best. audio_link = self.no_video_list[-1]['url'] video_link = self.no_audio_list[-1]['url'] link_list = [video_link, audio_link] else: audio_and_video_link = self.video_audio_list[ self.media_comboBox.currentIndex()]['url'] link_list.append(audio_and_video_link) # write user's new inputs in persepolis_setting for next time :) self.persepolis_setting.setValue('add_link_initialization/ip', self.ip_lineEdit.text()) self.persepolis_setting.setValue('add_link_initialization/port', self.port_spinBox.value()) self.persepolis_setting.setValue('add_link_initialization/proxy_user', self.proxy_user_lineEdit.text()) self.persepolis_setting.setValue( 'add_link_initialization/download_user', self.download_user_lineEdit.text()) # get proxy information if not (self.proxy_checkBox.isChecked()): ip = None port = None proxy_user = None proxy_passwd = None else: ip = self.ip_lineEdit.text() if not (ip): ip = None port = self.port_spinBox.value() if not (port): port = None proxy_user = self.proxy_user_lineEdit.text() if not (proxy_user): proxy_user = None proxy_passwd = self.proxy_pass_lineEdit.text() if not (proxy_passwd): proxy_passwd = None # get download username and password information if not (self.download_checkBox.isChecked()): download_user = None download_passwd = None else: download_user = self.download_user_lineEdit.text() if not (download_user): download_user = None download_passwd = self.download_pass_lineEdit.text() if not (download_passwd): download_passwd = None # check that if user limits download speed. if not (self.limit_checkBox.isChecked()): limit = 0 else: if self.limit_comboBox.currentText() == "KiB/s": limit = str(self.limit_spinBox.value()) + str("K") else: limit = str(self.limit_spinBox.value()) + str("M") # get start time for download if user set that. if not (self.start_checkBox.isChecked()): start_time = None else: start_time = self.start_time_qDataTimeEdit.text() # get end time for download if user set that. if not (self.end_checkBox.isChecked()): end_time = None else: end_time = self.end_time_qDateTimeEdit.text() # set name for file(s) if self.change_name_checkBox.isChecked(): name = str(self.change_name_lineEdit.text()) if name == '': name = 'video_finder_file' else: name = 'video_finder_file' # video finder always finds extension # but if it can't find file extension # use mp4 for extension. if str(self.extension_label.text()) == '': extension = '.mp4' else: extension = str(self.extension_label.text()) # did user select seperate audio and video? if len(link_list) == 2: video_name = name + extension audio_name = name + '.' + str(self.no_video_list[ self.audio_format_selection_comboBox.currentIndex() - 1]['ext']) name_list = [video_name, audio_name] else: name_list = [name + extension] # get number of connections connections = self.connections_spinBox.value() # get download_path download_path = self.download_folder_lineEdit.text() # referer if self.referer_lineEdit.text() != '': referer = self.referer_lineEdit.text() else: referer = None # header if self.header_lineEdit.text() != '': header = self.header_lineEdit.text() else: header = None # user_agent if self.user_agent_lineEdit.text() != '': user_agent = self.user_agent_lineEdit.text() else: user_agent = None # load_cookies if self.load_cookies_lineEdit.text() != '': load_cookies = self.load_cookies_lineEdit.text() else: load_cookies = None add_link_dictionary_list = [] if len(link_list) == 1: # save information in a dictionary(add_link_dictionary). add_link_dictionary = { 'referer': referer, 'header': header, 'user_agent': user_agent, 'load_cookies': load_cookies, 'out': name_list[0], 'start_time': start_time, 'end_time': end_time, 'link': link_list[0], 'ip': ip, 'port': port, 'proxy_user': proxy_user, 'proxy_passwd': proxy_passwd, 'download_user': download_user, 'download_passwd': download_passwd, 'connections': connections, 'limit_value': limit, 'download_path': download_path } add_link_dictionary_list.append(add_link_dictionary) else: video_add_link_dictionary = { 'referer': referer, 'header': header, 'user_agent': user_agent, 'load_cookies': load_cookies, 'out': name_list[0], 'start_time': start_time, 'end_time': end_time, 'link': link_list[0], 'ip': ip, 'port': port, 'proxy_user': proxy_user, 'proxy_passwd': proxy_passwd, 'download_user': download_user, 'download_passwd': download_passwd, 'connections': connections, 'limit_value': limit, 'download_path': download_path } audio_add_link_dictionary = { 'referer': referer, 'header': header, 'user_agent': user_agent, 'load_cookies': load_cookies, 'out': name_list[1], 'start_time': None, 'end_time': end_time, 'link': link_list[1], 'ip': ip, 'port': port, 'proxy_user': proxy_user, 'proxy_passwd': proxy_passwd, 'download_user': download_user, 'download_passwd': download_passwd, 'connections': connections, 'limit_value': limit, 'download_path': download_path } add_link_dictionary_list = [ video_add_link_dictionary, audio_add_link_dictionary ] # get category of download category = str(self.add_queue_comboBox.currentText()) del self.plugin_add_link_dictionary # return information to mainwindow self.callback(add_link_dictionary_list, download_later, category) # close window self.close()
def __init__(self, parent=None): QWidget.__init__(self, parent) self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowSystemMenuHint) self.setWindowModality(QtCore.Qt.WindowModal) global par par = parent global int_lng int_lng = par.interface_lng_val # ------------------------Функции связанные с формой----------------------------- # .....Функция, запускаемая при нажатии радио-кнопки "создать новый проект"...... def on_np_clicked(): if np_radio.isChecked(): title_label.setEnabled(True) project_frame.setEnabled(True) project_frame.setStyleSheet("border-color: dimgray;") project_name.setEnabled(True) project_name.setText("") project_path_name.setText("") path_button.setEnabled(True) # .....Функция, запускаемая при нажатии радио-кнопки "открыть имеющийся проект"...... def on_cp_clicked(): if cp_radio.isChecked(): choice_button.setEnabled(True) title_label.setEnabled(False) project_frame.setEnabled(False) project_frame.setStyleSheet("border-color: darkgray;") else: choice_button.setEnabled(False) # .....Функция, запускаемая при нажатии кнопки "выбрать имеющийся проект"...... def on_chbtn_clicked(): global new_dir folder_dir = QFileDialog.getExistingDirectory( directory=QtCore.QDir.currentPath()) new_dir, project_name_dir = os.path.split(folder_dir) path_button.setEnabled(False) title_label.setEnabled(True) project_frame.setEnabled(True) project_frame.setStyleSheet("border-color: dimgray;") project_name.setEnabled(True) project_name.setStyleSheet("border-color: silver;") project_name.setText(project_name_dir) project_path_name.setText(new_dir) project_path_name.setEnabled(True) project_path_name.setStyleSheet("border-color: silver;") # --------------------------Функции связанные c выводом----------------------------- # .....Функция, запускаемая при нажатии кнопки выбора директории сохранения нового проекта"...... def on_path_choose(): global new_dir new_dir = QFileDialog.getExistingDirectory( directory=QtCore.QDir.currentPath()) dir_reg = re.compile(r"\S*(?<=[\/])run(?![\/])") dir_mas = dir_reg.findall(new_dir) project_path_name.setText(new_dir) # .....Функция, запускаемая при завершении редактирования названия проекта и его директории"...... def handleEditingFinished(): if project_name.text() and project_path_name.text(): save_button.setEnabled(True) # ....................Функция, запускаемая при нажатии кнопки "сохранить".................... def on_save_clicked(): par.addDockWidget(QtCore.Qt.LeftDockWidgetArea, par.fsw) par.addDockWidget(QtCore.Qt.BottomDockWidgetArea, par.serv_mes) prj_name = project_name.text() full_dir = new_dir + "/" + prj_name par.full_dir = full_dir par.prj_name = prj_name # ---constant--- dir_constant_path = full_dir + '/constant' if dir_constant_path: dir_constant_name = os.path.basename(full_dir + '/constant') files_constant = ['mechanicalProperties', 'thermalProperties'] item_constant = QtGui.QStandardItem(dir_constant_name) par.treeview.model.insertRow(0, item_constant) j = 0 index_constant = par.treeview.model.index(0, 0) par.treeview.expand(index_constant) for el_constant in files_constant: child_item_constant = QtGui.QStandardItem(el_constant) child_item_constant.setForeground(QtGui.QColor('navy')) item_constant.setChild(j, 0, child_item_constant) j = j + 1 dir_system_path = full_dir + '/system' # ---system--- dir_system_name = os.path.basename(full_dir + '/system') if dir_system_name: files_system = ['controlDict', 'fvSchemes', 'fvSolution'] item_system = QtGui.QStandardItem(dir_system_name) par.treeview.model.insertRow(1, item_system) j = 0 index_system = par.treeview.model.index(1, 0) par.treeview.expand(index_system) for el_system in files_system: child_item_system = QtGui.QStandardItem(el_system) child_item_system.setForeground(QtGui.QColor('navy')) item_system.setChild(j, 0, child_item_system) j = j + 1 dir_0_path = full_dir + '/0' # ---0--- if dir_0_path: dir_0_name = os.path.basename(full_dir + '/0') item_0 = QtGui.QStandardItem(dir_0_name) par.treeview.model.insertRow(2, item_0) files_0 = ['D', 'T'] j = 0 index = par.treeview.model.index(2, 0) par.treeview.expand(index) for el_0 in files_0: child_item_0 = QtGui.QStandardItem(el_0) child_item_0.setForeground(QtGui.QColor('navy')) item_0.setChild(j, 0, child_item_0) j = j + 1 prj_lbl = QLabel() if int_lng == 'Russian': prj_lbl.setText('Путь до директории проекта:') elif int_lng == 'English': prj_lbl.setText('Path to mesh file:') prj_lbl.setStyleSheet("border-style: none;" "font-size: 10pt;") prj_path_lbl = QLineEdit() prj_path_lbl.setStyleSheet("background-color: white;" "font-size: 10pt;" "color: green;") prj_path_lbl.setFixedSize(500, 25) prj_path_lbl.setText(full_dir) prj_path_lbl.setEnabled(False) par.tdw_grid.addWidget(prj_lbl, 0, 0, alignment=QtCore.Qt.AlignCenter) par.tdw_grid.addWidget(prj_path_lbl, 0, 1, alignment=QtCore.Qt.AlignCenter) # Создаем все таблицы, которые нужны if not os.path.exists(full_dir + '/db_data'): os.mkdir(full_dir + '/db_data') db_path = full_dir + '/db_data/db.sqlite' con = QtSql.QSqlDatabase.addDatabase('QSQLITE') con.setDatabaseName(db_path) con.open() par.con = con parent.msh_open.setEnabled(True) self.close() # .....................Функция, запускаемая при нажатии кнопки "отмена"...................... def on_cancel_clicked(): self.close() self.clear_label = QLabel() parent.ffw.setTitleBarWidget(self.clear_label) # ------------------------------------Первый блок формы-------------------------------------- choice_label = QLabel("Создайте новый проект или откройте имеющийся") cl_hbox = QHBoxLayout() cl_hbox.addWidget(choice_label) np_radio = QRadioButton("Создать новый проект") np_radio.toggled.connect(on_np_clicked) cp_radio = QRadioButton("Открыть имеющийся проект") cp_radio.toggled.connect(on_cp_clicked) icon = self.style().standardIcon(QStyle.SP_DirOpenIcon) choice_button = QPushButton() choice_button.setFixedSize(30, 30) choice_button.setIcon(icon) choice_button.setEnabled(False) choice_button.clicked.connect(on_chbtn_clicked) ch_grid = QGridLayout() ch_grid.addWidget(np_radio, 0, 0) ch_grid.addWidget(cp_radio, 0, 1) ch_grid.addWidget(choice_button, 0, 2) ch_frame = QFrame() ch_frame.setFrameShape(QFrame.Panel) ch_frame.setFrameShadow(QFrame.Sunken) ch_frame.setLayout(ch_grid) ch_hbox = QHBoxLayout() ch_hbox.addWidget(ch_frame) # -------------------------------------Второй блок формы------------------------------------ title_label = QLabel("Введите название задачи") title_label.setEnabled(False) tl_hbox = QHBoxLayout() tl_hbox.addWidget(title_label) project_label = QLabel("Название проекта:") project_name = QLineEdit() project_name.textChanged.connect(handleEditingFinished) project_name.setFixedSize(180, 25) valid = QtGui.QRegExpValidator(QtCore.QRegExp("\S*"), self) project_name.setValidator(valid) project_path_label = QLabel("Путь:") project_path_name = QLineEdit() project_path_name.setEnabled(False) project_path_name.textChanged.connect(handleEditingFinished) project_path_name.setFixedSize(180, 25) path_button = QPushButton("...") path_button.clicked.connect(on_path_choose) path_button.setFixedSize(25, 25) project_grid = QGridLayout() project_grid.addWidget(project_label, 0, 0) project_grid.addWidget(project_name, 0, 1, alignment=QtCore.Qt.AlignRight) project_grid.addWidget(project_path_label, 1, 0) project_grid.addWidget(project_path_name, 1, 1) project_grid.addWidget(path_button, 1, 2) project_frame = QFrame() project_frame.setEnabled(False) project_frame.setStyleSheet("border-color: darkgray;") project_frame.setFrameShape(QFrame.Panel) project_frame.setFrameShadow(QFrame.Sunken) project_frame.setLayout(project_grid) project_grid_vbox = QVBoxLayout() project_grid_vbox.addWidget(project_frame) # ---------------------Кнопки сохранения и отмены и их блок------------------------- save_button = QPushButton("Сохранить") save_button.setFixedSize(80, 25) save_button.clicked.connect(on_save_clicked) save_button.setEnabled(False) cancel_button = QPushButton("Отмена") cancel_button.setFixedSize(80, 25) cancel_button.clicked.connect(on_cancel_clicked) buttons_hbox = QHBoxLayout() buttons_hbox.addWidget(save_button) buttons_hbox.addWidget(cancel_button) # -------------------------Фрейм формы--------------------------- bound_grid = QGridLayout() bound_grid.addLayout(cl_hbox, 0, 0, alignment=QtCore.Qt.AlignCenter) bound_grid.addLayout(ch_hbox, 1, 0, alignment=QtCore.Qt.AlignCenter) bound_grid.addLayout(tl_hbox, 2, 0, alignment=QtCore.Qt.AlignCenter) bound_grid.addLayout(project_grid_vbox, 3, 0, alignment=QtCore.Qt.AlignCenter) bound_grid.addLayout(buttons_hbox, 4, 0, alignment=QtCore.Qt.AlignCenter) bound_frame = QFrame() bound_frame.setStyleSheet( open("./styles/properties_form_style.qss", "r").read()) bound_frame.setLayout(bound_grid) bound_vbox = QVBoxLayout() bound_vbox.addWidget(bound_frame) # --------------------Размещение на форме всех компонентов--------- form_1 = QFormLayout() form_1.addRow(bound_vbox) self.setLayout(form_1)
class msh_window_class(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowSystemMenuHint) self.setWindowModality(QtCore.Qt.WindowModal) global par par = parent global int_lng int_lng = par.interface_lng_val global full_dir full_dir = parent.full_dir self.t1 = MyThread(full_dir) # ------------------------------------Первый блок формы--------------------------------------# self.mesh_choose_lbl = QLabel() self.mesh_choose_lbl_hbox = QHBoxLayout() self.mesh_choose_lbl_hbox.addWidget(self.mesh_choose_lbl) self.radio_1 = QRadioButton() self.radio_1.toggled.connect(self.on_radio_1_clicked) self.radio_2 = QRadioButton() self.radio_2.toggled.connect(self.on_radio_2_clicked) self.mesh_choose_grid = QGridLayout() self.mesh_choose_grid.addWidget(self.radio_1, 0, 0) self.mesh_choose_grid.addWidget(self.radio_2, 0, 1) self.mesh_choose_frame = QFrame() self.mesh_choose_frame.setLayout(self.mesh_choose_grid) self.mesh_choose_hbox = QHBoxLayout() self.mesh_choose_hbox.addWidget(self.mesh_choose_frame) # ------------------------------------Второй блок формы--------------------------------------# self.fmtf_radio = QRadioButton("Импорт 2D-сетки") self.f3Dmtf_radio = QRadioButton("Импорт 3D-сетки") self.import_hbox = QHBoxLayout() self.import_hbox.addWidget(self.fmtf_radio) self.import_hbox.addWidget(self.f3Dmtf_radio) self.mesh_label = QLabel("Путь: ") self.mesh_edit = QLineEdit() self.mesh_edit.setEnabled(False) self.mesh_edit.setFixedSize(290, 25) self.path_button = QPushButton("...") self.path_button.setFixedSize(25, 25) self.import_prs_hbox = QHBoxLayout() self.import_prs_hbox.addWidget(self.mesh_label) self.import_prs_hbox.addWidget(self.mesh_edit) self.import_prs_hbox.addWidget(self.path_button) self.path_button.clicked.connect(self.on_path_choose) self.prs_grid = QGridLayout() self.prs_grid.addLayout(self.import_hbox, 0, 0) self.prs_grid.addLayout(self.import_prs_hbox, 1, 0) self.prs_frame = QFrame() self.prs_frame.setStyleSheet( open("./styles/properties_form_style.qss", "r").read()) self.prs_frame.setLayout(self.prs_grid) self.prs_frame.setEnabled(False) self.prs_frame.setStyleSheet("border-color: darkgray;") self.prs_hbox = QHBoxLayout() self.prs_hbox.addWidget(self.prs_frame) # ------------------------------------Третий блок формы--------------------------------------# self.chc_label = QLabel() self.chc_label.setEnabled(False) self.chc_lbl_hbox = QHBoxLayout() self.chc_lbl_hbox.addWidget(self.chc_label) self.nf_radio = QRadioButton() self.nf_radio.toggled.connect(self.on_nf_clicked) self.cf_radio = QRadioButton() self.cf_radio.toggled.connect(self.on_cf_clicked) self.icon = self.style().standardIcon(QStyle.SP_DirOpenIcon) self.chc_button = QPushButton() self.chc_button.setFixedSize(30, 30) self.chc_button.setIcon(self.icon) self.chc_button.setEnabled(False) self.chc_button.clicked.connect(self.on_chc_clicked) self.chc_grid = QGridLayout() self.chc_grid.addWidget(self.nf_radio, 0, 0) self.chc_grid.addWidget(self.cf_radio, 0, 1) self.chc_grid.addWidget(self.chc_button, 0, 2) self.chc_frame = QFrame() self.chc_frame.setFixedWidth(400) self.chc_frame.setEnabled(False) self.chc_frame.setStyleSheet("border-color: darkgray;") self.chc_frame.setLayout(self.chc_grid) self.chc_hbox = QHBoxLayout() self.chc_hbox.addWidget(self.chc_frame) # ------------------------------------Четвертый блок формы--------------------------------------# self.mesh_type_label = QLabel('Выберите тип сетки:') self.bm = QRadioButton("blockMesh") self.bm.setChecked(True) self.shm = QRadioButton("snappyHexMesh") self.mesh_type_vbox = QVBoxLayout() self.mesh_type_vbox.addWidget(self.bm) self.mesh_type_vbox.addWidget(self.shm) self.mesh_label = QLabel() self.mesh_name = QLineEdit() self.mesh_name.setFixedSize(214, 25) regexp = QtCore.QRegExp('[А-яА-Яa-zA-Z0-9\_]+') validator = QtGui.QRegExpValidator(regexp) self.mesh_name.setValidator(validator) self.prj_path_label = QLabel() self.prj_path_name = QLineEdit() self.prj_path_name.setEnabled(False) self.prj_path_name.setFixedSize(214, 25) self.prj_grid = QGridLayout() self.prj_grid.addWidget(self.mesh_type_label, 0, 0, alignment=QtCore.Qt.AlignCenter) self.prj_grid.addLayout(self.mesh_type_vbox, 0, 1, alignment=QtCore.Qt.AlignCenter) self.prj_grid.addWidget(self.mesh_label, 1, 0, alignment=QtCore.Qt.AlignCenter) self.prj_grid.addWidget(self.mesh_name, 1, 1, alignment=QtCore.Qt.AlignCenter) self.prj_grid.addWidget(self.prj_path_label, 2, 0, alignment=QtCore.Qt.AlignCenter) self.prj_grid.addWidget(self.prj_path_name, 2, 1, alignment=QtCore.Qt.AlignCenter) self.prj_frame = QFrame() self.prj_frame.setFixedWidth(400) self.prj_frame.setEnabled(False) self.prj_frame.setStyleSheet("border-color: darkgray;") self.prj_frame.setFrameShape(QFrame.Panel) self.prj_frame.setFrameShadow(QFrame.Sunken) self.prj_frame.setLayout(self.prj_grid) self.prj_grid_vbox = QVBoxLayout() self.prj_grid_vbox.addWidget(self.prj_frame) # ---------------------Кнопки сохранения и отмены и их блок-------------------------# self.save_button = QPushButton() self.save_button.setFixedSize(80, 25) self.save_button.clicked.connect(self.on_save_clicked) self.save_button.setEnabled(False) self.cancel_button = QPushButton() self.cancel_button.setFixedSize(80, 25) self.cancel_button.clicked.connect(self.on_cancel_clicked) self.buttons_hbox = QHBoxLayout() self.buttons_hbox.addWidget(self.save_button) self.buttons_hbox.addWidget(self.cancel_button) # -------------------------Фрейм формы---------------------------# self.form_grid = QGridLayout() self.form_grid.addLayout(self.mesh_choose_hbox, 0, 0, alignment=QtCore.Qt.AlignCenter) self.form_grid.addLayout(self.prs_hbox, 1, 0, alignment=QtCore.Qt.AlignCenter) self.form_grid.addLayout(self.chc_lbl_hbox, 2, 0, alignment=QtCore.Qt.AlignCenter) self.form_grid.addLayout(self.chc_hbox, 3, 0, alignment=QtCore.Qt.AlignCenter) self.form_grid.addLayout(self.prj_grid_vbox, 4, 0, alignment=QtCore.Qt.AlignCenter) self.form_grid.addLayout(self.buttons_hbox, 5, 0, alignment=QtCore.Qt.AlignCenter) self.form_frame = QFrame() self.form_frame.setStyleSheet( open("./styles/properties_form_style.qss", "r").read()) self.form_frame.setLayout(self.form_grid) self.form_vbox = QVBoxLayout() self.form_vbox.addWidget(self.form_frame) # --------------------Размещение на форме всех компонентов---------# self.form = QFormLayout() self.form.addRow(self.form_vbox) self.setLayout(self.form) # --------------------Определяем параметры интерфейса окна---------# if int_lng == 'Russian': self.radio_1.setText("Внешняя сетка") self.radio_2.setText("OpenFOAM-сетка") self.fmtf_radio.setText("Импорт 2D-сетки") self.f3Dmtf_radio.setText("Импорт 3D-сетки") self.chc_label.setText( "Создайте новую сетку или откройте существующую") self.nf_radio.setText("Создать новую") self.cf_radio.setText("Открыть существующую") self.mesh_type_label.setText("Выберите тип сетки:") self.mesh_label.setText("Название сетки:") self.prj_path_label.setText("Путь:") self.save_button.setText("Сохранить") self.cancel_button.setText("Отмена") elif int_lng == 'English': self.radio_1.setText("External mesh") self.radio_2.setText("OpenFOAM-mesh") self.fmtf_radio.setText("2D-mesh import") self.f3Dmtf_radio.setText("3D-mesh import") self.chc_label.setText( "Create a new mesh or open an existing mesh") self.nf_radio.setText("Create new mesh") self.cf_radio.setText("Open existing mesh") self.mesh_type_label.setText("Select mesh type:") self.mesh_label.setText("Mesh name:") self.prj_path_label.setText("Path:") self.save_button.setText("Save") self.cancel_button.setText("Cancel") # ------------------------Функции связанные с формой-----------------------------# # .....Функция, запускаемая при нажатии радио-кнопки "Внешняя сетка"......# def on_radio_1_clicked(self): self.prs_frame.setEnabled(True) self.chc_frame.setEnabled(False) self.prj_frame.setEnabled(False) self.chc_label.setEnabled(False) self.prs_frame.setStyleSheet("border-color: dimgray;") self.chc_frame.setStyleSheet("border-color: darkgray;") self.prj_frame.setStyleSheet("border-color: darkgray;") self.prj_path_name.setText("") # .....Функция, запускаемая при нажатии радио-кнопки "OpenFOAM сетка"......# def on_radio_2_clicked(self): self.prs_frame.setEnabled(False) self.chc_frame.setEnabled(True) self.chc_label.setEnabled(True) self.chc_frame.setStyleSheet("border-color: dimgray;") self.prs_frame.setStyleSheet("border-color: darkgray;") self.prj_path_name.setText(full_dir + '/system') self.save_button.setEnabled(True) # .....Функция определения пути до внешней сетки......# def on_path_choose(self): global mesh_dir user = getpass.getuser() mesh_dir = QFileDialog.getOpenFileName(directory="/home/" + user) mesh_reg = re.compile(r"\S*(?<=[\/])\S*msh") mesh_mas = mesh_reg.findall(mesh_dir) if mesh_mas != []: self.mesh_edit.setText(mesh_dir) else: dialog = QMessageBox(QMessageBox.Critical, "Внимание!", "Это не файл сетки. Выберите другой файл", buttons=QMessageBox.Ok) result = dialog.exec_() self.save_button.setEnabled(True) #......Функция по завершению генерации внешней сетки......# def on_finished(self): global mas if proc.returncode == 0: file = open(full_dir + "/constant/polyMesh/boundary", 'r') data = file.read() file.close() struct_reg = re.compile(r"\S*\n\s*(?=[{])") struct_mas = struct_reg.findall(data) i = 1 mas = [] for elem in range(len(struct_mas) - 1): div = struct_mas[i].split("\n") i = i + 1 mas.append(div[0]) file_U = open(full_dir + "/0/U", 'a') file_U.write("\n{\n") for el in range(len(mas)): file_U.write( " " + mas[el] + "\n {\n type empty;\n }\n") file_U.write("}") file_U.close() file_T = open(full_dir + "/0/T", 'a') file_T.write("\n{\n") for el in range(len(mas)): file_T.write( " " + mas[el] + "\n {\n type empty;\n }\n") file_T.write("}") file_T.close() file_p = open(full_dir + "/0/p", 'a') file_p.write("\n{\n") for el in range(len(mas)): file_p.write( " " + mas[el] + "\n {\n type empty;\n }\n") file_p.write("}") file_p.close() par.listWidget.clear() par.item = QListWidgetItem("Расчетная сетка успешно сгенерирована", par.listWidget) par.color = QtGui.QColor("green") par.item.setTextColor(par.color) par.listWidget.addItem(par.item) par.task_open.setEnabled(True) self.close() else: par.item = QListWidgetItem("Расчетная сетка не сгенерирована", par.listWidget) par.color = QtGui.QColor("red") par.item.setTextColor(par.color) par.listWidget.addItem(par.item) # .....Функция, запускаемая при нажатии радио-кнопки "создать новую сетку OpenFOAM"......# def on_nf_clicked(self): self.prj_path_label.setEnabled(True) self.prj_frame.setEnabled(True) self.prj_frame.setStyleSheet("border-color: dimgray;") self.chc_button.setEnabled(False) # .....Функция, запускаемая при нажатии радио-кнопки "открыть имеющуюся сетку OpenFOAM"......# def on_cf_clicked(self): self.prj_path_label.setEnabled(False) self.prj_frame.setEnabled(False) self.prj_frame.setStyleSheet("border-color: darkgray;") self.chc_button.setEnabled(True) self.prj_path_name.setText('') # .....Функция, запускаемая при нажатии кнопки "выбрать существующую"......# def on_chc_clicked(self): global prj_path_cur global pickles_dir global pd_2 prj_dir = QFileDialog.getExistingDirectory(self, directory=full_dir + '/system/') prj_path_cur, pickles_dir = os.path.split(prj_dir) pd_1, pd_2 = pickles_dir.split("_") initial_path = prj_dir + '/' + 'initial.pkl' if os.path.exists(initial_path) == True: self.prj_path_name.setText(prj_dir) self.save_button.setEnabled(True) self.mesh_name.setText(pd_1) if pd_2 == 'blockMesh': self.bm.setChecked(True) par.on_mesh_type_get(pd_2) elif pd_2 == 'snappyHexMesh': self.shm.setChecked(True) par.on_mesh_type_get(pd_2) self.prj_frame.setEnabled(True) self.prj_path_name.setEnabled(False) else: if int_lng == 'Russian': dialog = QMessageBox( QMessageBox.Critical, "Внимание!", "Это не директория сетки или в ней отсутствуют все необходимые файлы", buttons=QMessageBox.Ok) elif int_lng == 'English': dialog = QMessageBox( QMessageBox.Critical, "Attention!", "This is not a grid directory, or all necessary files are missing in it", buttons=QMessageBox.Ok) result = dialog.exec_() # ....................Функция, запускаемая при нажатии кнопки "сохранить"....................# def on_save_clicked(self): global pckls_path msh_lbl_widget = par.tdw_grid.itemAtPosition(0, 2) msh_path_lbl_widget = par.tdw_grid.itemAtPosition(0, 3) if msh_lbl_widget != None: par.tdw_grid.removeWidget(msh_lbl_widget, 0, 2) if msh_path_lbl_widget != None: par.tdw_grid.removeWidget(msh_path_lbl_widget, 0, 3) full_dir = par.full_dir if self.radio_1.isChecked(): f = open(full_dir + '/MESH_BASH', 'w') if self.fmtf_radio.isChecked(): f.write('#!/bin/sh' + '\n' + '. /opt/openfoam4/etc/bashrc' + '\n' + 'fluentMeshToFoam ' + mesh_dir + '\n' + 'exit') f.close() elif self.f3Dmtf_radio.isChecked(): f.write('#!/bin/sh' + '\n' + '. /opt/openfoam4/etc/bashrc' + '\n' + 'fluent3DMeshToFoam ' + mesh_dir + '\n' + 'exit') f.close() self.t1.start() shutil.copytree("./matches/0", full_dir + "/0") par.msh_visual.setEnabled(True) elif self.radio_2.isChecked(): global mesh_name_txt easy_lbl = QLabel() par.cdw.setTitleBarWidget(easy_lbl) par.cdw.setWidget(easy_lbl) mesh_name_txt = self.mesh_name.text() msh_lbl = QLabel() if int_lng == 'Russian': msh_lbl.setText('Путь до расчетной сетки:') elif int_lng == 'English': msh_lbl.setText('Path to mesh file:') if self.bm.isChecked() == True: pd_2 = 'blockMesh' elif self.shm.isChecked() == True: pd_2 = 'snappyHexMesh' msh_lbl.setStyleSheet("border-style: none;" "font-size: 10pt;") msh_path_lbl = QLineEdit() msh_path_lbl.setStyleSheet("background-color: white;" "font-size: 10pt;" "color: green;") msh_path_lbl.setFixedSize(500, 25) msh_path_lbl.setText(par.full_dir + '/system/' + mesh_name_txt + "_" + pd_2) msh_path_lbl.setEnabled(False) par.tdw_grid.addWidget(msh_lbl, 0, 2, alignment=QtCore.Qt.AlignCenter) par.tdw_grid.addWidget(msh_path_lbl, 0, 3, alignment=QtCore.Qt.AlignCenter) self.clear_label = QLabel() if self.nf_radio.isChecked() == True: if self.mesh_name.text() == '': if int_lng == 'Russian': dialog = QMessageBox(QMessageBox.Critical, "Внимание!", "Укажите название сетки", buttons=QMessageBox.Ok) elif int_lng == 'English': dialog = QMessageBox(QMessageBox.Critical, "Attention!", "Specify name mesh", buttons=QMessageBox.Ok) result = dialog.exec_() else: if self.bm.isChecked() == True: par.addDockWidget(QtCore.Qt.BottomDockWidgetArea, par.serv_mes) pckls_path = full_dir + '/system/' pd_2_cur = 'blockMesh' bmd_form = bmd_window_class(self, par, pckls_path, mesh_name_txt, pd_2_cur) par.ffw.setWidget(bmd_form) self.close() par.cdw.setWidget(self.clear_label) par.cdw.setTitleBarWidget(self.clear_label) par.setCentralWidget(par.ffw) ffw_label = QLabel() par.ffw.setTitleBarWidget(par.ffw_frame) par.ffw_label.setText( "Форма подготовки расчетной сетки: " + "<font color='peru'>" + 'blockMesh' + "</font>") par.ffw_label.setStyleSheet("border-style: none;" "font-size: 9pt;") dir_system_name = os.path.basename(full_dir + '/system') if dir_system_name: item_system = QtGui.QStandardItem(dir_system_name) el_system = 'blockMeshDict' child_item_system = QtGui.QStandardItem(el_system) child_item_system.setForeground( QtGui.QColor('navy')) item_system.setChild(3, 0, child_item_system) if os.path.basename(full_dir + '/system/blockMeshDict'): dir_0_name = os.path.basename(full_dir + '/0') item_0 = QtGui.QStandardItem(dir_0_name) files_0 = ['D', 'T'] j = 0 index = par.treeview.model.index(2, 0) par.treeview.expand(index) for el_0 in files_0: child_item_0 = QtGui.QStandardItem(el_0) child_item_0.setForeground( QtGui.QColor('navy')) item_0.setChild(j, 0, child_item_0) j = j + 1 elif self.shm.isChecked() == True: par.addDockWidget(QtCore.Qt.BottomDockWidgetArea, par.serv_mes) pckls_path = full_dir + '/system/' pd_2_cur = 'snappyHexMesh' shmd_form = shmd_window_class(self, par, pckls_path, mesh_name_txt, pd_2_cur) par.ffw.setWidget(shmd_form) self.close() par.cdw.setWidget(self.clear_label) par.cdw.setTitleBarWidget(self.clear_label) par.setCentralWidget(par.ffw) ffw_label = QLabel() par.ffw.setTitleBarWidget(par.ffw_frame) par.ffw_label.setText( "Форма подготовки расчетной сетки: " + "<font color='peru'>" + 'snappyHexMesh' + "</font>") par.ffw_label.setStyleSheet("border-style: none;" "font-size: 9pt;") dir_system_name = os.path.basename( full_dir + '/system/snappyHexMeshDict') if dir_system_name: item_system = QtGui.QStandardItem(dir_system_name) el_system = 'snappyHexMeshDict' child_item_system = QtGui.QStandardItem(el_system) child_item_system.setForeground( QtGui.QColor('navy')) item_system.setChild(3, 0, child_item_system) if os.path.basename(full_dir + '/system/snappyHexMeshDict'): dir_0_name = os.path.basename(full_dir + '/0') item_0 = QtGui.QStandardItem(dir_0_name) files_0 = ['D', 'T'] j = 0 index = par.treeview.model.index(2, 0) par.treeview.expand(index) for el_0 in files_0: child_item_0 = QtGui.QStandardItem(el_0) child_item_0.setForeground( QtGui.QColor('navy')) item_0.setChild(j, 0, child_item_0) j = j + 1 if os.path.exists(pckls_path + self.mesh_name.text()) == True: msh_msg_box = QMessageBox() if int_lng == 'Russian': msh_msg_box.setText( "Расчетная сетка с таким именем существует") msh_msg_box.setInformativeText( "Заменить существующую сетку?") elif int_lng == 'English': msh_msg_box.setText( "A calculated mesh with this name exists") msh_msg_box.setInformativeText( "Replace an existing mesh?") msh_msg_box.setStandardButtons(QMessageBox.Save | QMessageBox.Discard) msh_msg_box.setDefaultButton(QMessageBox.Save) ret = msh_msg_box.exec_() if ret == QMessageBox.Save: # Save was clicked shutil.rmtree(pckls_path) self.close() elif ret == QMessageBox.Discard: # Don't save was clicked self.close() elif self.cf_radio.isChecked() == True: if pd_2 == 'blockMesh': par.addDockWidget(QtCore.Qt.BottomDockWidgetArea, par.serv_mes) pckls_path = full_dir + '/system/' pd_2_cur = 'blockMesh' bmd_form = bmd_window_class(self, par, pckls_path, mesh_name_txt, pd_2_cur) par.ffw.setWidget(bmd_form) self.close() par.cdw.setWidget(self.clear_label) par.cdw.setTitleBarWidget(self.clear_label) par.setCentralWidget(par.ffw) ffw_label = QLabel() par.ffw.setTitleBarWidget(par.ffw_frame) par.ffw_label.setText( "Форма подготовки расчетной сетки: " + "<font color='peru'>" + 'blockMesh' + "</font>") par.ffw_label.setStyleSheet("border-style: none;" "font-size: 9pt;") dir_system_name = os.path.basename(full_dir + '/system') if dir_system_name: item_system = QtGui.QStandardItem(dir_system_name) el_system = 'blockMeshDict' child_item_system = QtGui.QStandardItem(el_system) child_item_system.setForeground(QtGui.QColor('navy')) item_system.setChild(3, 0, child_item_system) if os.path.basename(full_dir + '/system/blockMeshDict'): dir_0_name = os.path.basename(full_dir + '/0') item_0 = QtGui.QStandardItem(dir_0_name) files_0 = ['D', 'T'] j = 0 index = par.treeview.model.index(2, 0) par.treeview.expand(index) for el_0 in files_0: child_item_0 = QtGui.QStandardItem(el_0) child_item_0.setForeground(QtGui.QColor('navy')) item_0.setChild(j, 0, child_item_0) j = j + 1 el_system = 'blockMeshDict' item_system = par.treeview.model.item(1, 0) child_item_system = QtGui.QStandardItem(el_system) child_item_system.setForeground(QtGui.QColor('navy')) item_system.setChild(3, 0, child_item_system) elif pd_2 == 'snappyHexMesh': par.addDockWidget(QtCore.Qt.BottomDockWidgetArea, par.serv_mes) pckls_path = full_dir + '/system/' pd_2_cur = 'snappyHexMesh' shmd_form = shmd_window_class(self, par, pckls_path, mesh_name_txt, pd_2_cur) par.ffw.setWidget(shmd_form) if os.path.exists(pckls_path + '/snappyHexMeshDict'): outf = open(pckls_path + '/snappyHexMeshDict') data = outf.read() if int_lng == 'Russian': par.outf_lbl.setText("Файл: " + "<font color='peru'>" + 'snappyHexMeshDict' + "</font>") elif int_lng == 'English': par.outf_lbl.setText("<font color='peru'>" + 'snappyHexMeshDict' + "</font>" + " file") par.outf_edit.setText(data) par.cdw.setWidget(par.outf_scroll) par.cdw.setTitleBarWidget(par.cdw_frame) else: empty_lbl = QLabel() par.cdw.setWidget(empty_lbl) par.cdw.setTitleBarWidget(empty_lbl) el_system = 'snappyHexMeshDict' item_system = par.treeview.model.item(1, 0) child_item_system = QtGui.QStandardItem(el_system) child_item_system.setForeground(QtGui.QColor('navy')) item_system.setChild(3, 0, child_item_system) if int_lng == 'Russian': msg_lbl = QLabel( '<span style="color:blue">' + 'Загружены параметры сетки ' + self.mesh_name.text() + '. Установите ее в качестве текущей, выполнив генерацию сетки' + '</span>') elif int_lng == 'English': msg_lbl = QLabel( '<span style="color:blue">' + 'Loaded parameters of mesh ' + self.mesh_name.text() + '. Set it as current, making mesh generation' + '</span>') par.listWidget.clear() par.item = QListWidgetItem() par.listWidget.addItem(par.item) par.listWidget.setItemWidget(par.item, msg_lbl) self.close() par.msh_run.setEnabled(True) par.msh_visual.setEnabled(True) par.str_an_run.setEnabled(True) par.str_an_vis_run.setEnabled(True) par.on_prj_path_get(prj_path_cur, mesh_name_txt) def int_lng_path_return(self): return (int_lng) # .....................Функция, запускаемая при нажатии кнопки "отмена"......................# def on_cancel_clicked(self): self.close()
def __init__(self, parent=None): QWidget.__init__(self, parent) self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowSystemMenuHint) self.setWindowModality(QtCore.Qt.WindowModal) global par par = parent global int_lng int_lng = par.interface_lng_val # ------------------------Функции связанные с формой----------------------------- # .....Функция, запускаемая при нажатии радио-кнопки "создать новый проект"...... def on_np_clicked(): if np_radio.isChecked(): title_label.setEnabled(True) project_frame.setEnabled(True) project_frame.setStyleSheet("border-color: dimgray;") project_name.setEnabled(True) project_name.setText("") project_path_name.setText("") path_button.setEnabled(True) # .....Функция, запускаемая при нажатии радио-кнопки "открыть имеющийся проект"...... def on_cp_clicked(): if cp_radio.isChecked(): choice_button.setEnabled(True) title_label.setEnabled(False) project_frame.setEnabled(False) project_frame.setStyleSheet("border-color: darkgray;") else: choice_button.setEnabled(False) # .....Функция, запускаемая при нажатии кнопки "выбрать имеющийся проект"...... def on_chbtn_clicked(): folder_dir = QFileDialog.getExistingDirectory( self, directory=QtCore.QDir.currentPath()) self.new_dir, project_name_dir = os.path.split(folder_dir) path_button.setEnabled(False) title_label.setEnabled(True) project_frame.setEnabled(True) project_frame.setStyleSheet("border-color: dimgray;") project_name.setEnabled(True) project_name.setStyleSheet("border-color: silver;") project_name.setText(project_name_dir) project_path_name.setText(self.new_dir) project_path_name.setEnabled(True) project_path_name.setStyleSheet("border-color: silver;") # --------------------------Функции связанные c выводом----------------------------- # .....Функция, запускаемая при нажатии кнопки выбора директории сохранения нового проекта"...... def on_path_choose(): self.new_dir = QFileDialog.getExistingDirectory( self, directory=QtCore.QDir.currentPath()) dir_reg = re.compile(r"\S*(?<=[\/])run(?![\/])") dir_mas = dir_reg.findall(self.new_dir) project_path_name.setText(self.new_dir) # .....Функция, запускаемая при завершении редактирования названия проекта и его директории"...... def handleEditingFinished(): if project_name.text() and project_path_name.text(): save_button.setEnabled(True) # ....................Функция, запускаемая при нажатии кнопки "сохранить".................... def on_save_clicked(): par.treeview.model.clear() par.fsw.setWidget(par.treeview) par.fsw.setTitleBarWidget(par.fsw_frame) par.serv_mes.setWidget(par.listWidget) if int_lng == 'Russian': par.fsw_grid.itemAtPosition( 0, 0).widget().setText("<font color='SeaGreen'>" + "Файловая Cтруктура Проекта" + "</font>") serv_mes_default = QLabel("Служебные сообщения") elif int_lng == 'English': par.fsw_grid.itemAtPosition( 0, 0).widget().setText("<font color='SeaGreen'>" + "File Structure of the Project" + "</font>") serv_mes_default = QLabel("Service messages") par.serv_mes.setTitleBarWidget(serv_mes_default) par.addDockWidget(QtCore.Qt.LeftDockWidgetArea, par.fsw) par.addDockWidget(QtCore.Qt.BottomDockWidgetArea, par.serv_mes) prj_name = project_name.text() full_dir = self.new_dir + "/" + prj_name par.full_dir = full_dir par.prj_name = prj_name if np_radio.isChecked(): os.mkdir(full_dir) os.mkdir(full_dir + "/system") os.mkdir(full_dir + "/constant") os.mkdir(full_dir + "/0") shutil.copy("./matches/Shablon/system/fvSchemes", full_dir + '/system') shutil.copy("./matches/Shablon/system/setFieldsDict", full_dir + '/system') shutil.copy("./matches/Shablon/system/controlDict", full_dir + '/system') shutil.copy("./matches/Shablon/system/fvSolution", full_dir + '/system') shutil.copy("./matches/Shablon/system/decomposeParDict", full_dir + '/system') # ---constant--- dir_constant_path = full_dir + '/constant' if dir_constant_path: dir_constant_name = os.path.basename(full_dir + '/constant') files_constant = [ f for f in os.listdir(full_dir + '/constant/') if os.path.isfile(os.path.join(full_dir + '/constant/', f)) ] item_constant = QtGui.QStandardItem(dir_constant_name) par.treeview.model.insertRow(0, item_constant) j = 0 index_constant = par.treeview.model.index(0, 0) par.treeview.expand(index_constant) for el_constant in files_constant: child_item_constant = QtGui.QStandardItem(el_constant) child_item_constant.setForeground(QtGui.QColor('navy')) item_constant.setChild(j, 0, child_item_constant) j = j + 1 dir_system_path = full_dir + '/system' # ---system--- dir_system_name = os.path.basename(full_dir + '/system') if dir_system_name: files_system = ['controlDict', 'decomposeParDict', 'fvSchemes'] item_system = QtGui.QStandardItem(dir_system_name) par.treeview.model.insertRow(0, item_system) j = 0 index_system = par.treeview.model.index(0, 0) par.treeview.expand(index_system) for el_system in files_system: child_item_system = QtGui.QStandardItem(el_system) child_item_system.setForeground(QtGui.QColor('navy')) item_system.setChild(j, 0, child_item_system) j = j + 1 bMD_name = os.path.exists(full_dir + '/system/blockMeshDict') if bMD_name: bMD_system = par.treeview.model.item(0, 0) bMD_system = 'blockMeshDict' child_item_system = QtGui.QStandardItem(bMD_system) child_item_system.setForeground(QtGui.QColor('navy')) item_system.setChild(3, 0, child_item_system) par.file_open.setEnabled(True) sHMD_name = os.path.exists(full_dir + '/system/snappyHexMeshDict') if sHMD_name: sHMD_system = par.treeview.model.item(0, 0) sHMD_system = 'snappyHexMeshDict' child_item_system = QtGui.QStandardItem(sHMD_system) child_item_system.setForeground(QtGui.QColor('navy')) item_system.setChild(3, 0, child_item_system) par.file_open.setEnabled(True) # ---0--- dir_0_name = os.path.basename(full_dir + '/0') if dir_system_name: item_0 = QtGui.QStandardItem(dir_0_name) par.treeview.model.insertRow(2, item_0) if cp_radio.isChecked(): if os.path.exists(full_dir + '/0') and os.listdir(full_dir + '/0'): files_0 = [ f for f in os.listdir(full_dir + '/0/') if os.path.isfile(os.path.join( full_dir + '/0/', f)) ] j = 0 index = par.treeview.model.index(2, 0) par.treeview.expand(index) for el_0 in files_0: child_item_0 = QtGui.QStandardItem(el_0) child_item_0.setForeground(QtGui.QColor('navy')) item_0.setChild(j, 0, child_item_0) j = j + 1 prj_lbl = QLabel() if int_lng == 'Russian': prj_lbl.setText('Путь до директории проекта:') elif int_lng == 'English': prj_lbl.setText('Path to mesh file:') prj_lbl.setStyleSheet("border-style: none;" "font-size: 10pt;") prj_path_lbl = QLineEdit() prj_path_lbl.setStyleSheet("background-color: white;" "font-size: 10pt;" "color: green;") prj_path_lbl.setFixedSize(500, 25) prj_path_lbl.setText(full_dir) prj_path_lbl.setEnabled(False) par.tdw_grid.addWidget(prj_lbl, 0, 0, alignment=QtCore.Qt.AlignCenter) par.tdw_grid.addWidget(prj_path_lbl, 0, 1, alignment=QtCore.Qt.AlignCenter) # Создаем все таблицы, которые нужны if not os.path.exists(full_dir + '/db_data'): os.mkdir(full_dir + '/db_data') db_path = full_dir + '/db_data/db.sqlite' if par.con == '': con = QtSql.QSqlDatabase.addDatabase('QSQLITE') con.setDatabaseName(db_path) con.open() par.con = con parent.msh_open.setEnabled(True) self.close() contDict_file = os.path.exists(par.full_dir + '/system/controlDict') if os.path.exists(contDict_file) == True: query = QtSql.QSqlQuery() query.exec("SELECT * FROM controlDict") if query.isActive(): query.first() value_list = [] while query.isValid(): value_res = query.value('value') value_list.append(value_res) query.next() parent.application = value_list[0] for el in os.listdir(par.full_dir): if 'processor' in el: shutil.rmtree(par.full_dir + '/' + el) # .....................Функция, запускаемая при нажатии кнопки "отмена"...................... def on_cancel_clicked(): self.close() self.clear_label = QLabel() parent.ffw.setTitleBarWidget(self.clear_label) # ------------------------------------Первый блок формы-------------------------------------- if int_lng == 'Russian': choice_label = QLabel( "Создайте новый проект или откройте имеющийся") np_radio = QRadioButton("Создать новый проект") cp_radio = QRadioButton("Открыть имеющийся проект") elif int_lng == 'English': choice_label = QLabel( "Create a new project or open an existing one") np_radio = QRadioButton("Create a new project") cp_radio = QRadioButton("Open existing project") cl_hbox = QHBoxLayout() cl_hbox.addWidget(choice_label) np_radio.toggled.connect(on_np_clicked) cp_radio.toggled.connect(on_cp_clicked) icon = self.style().standardIcon(QStyle.SP_DirOpenIcon) choice_button = QPushButton() choice_button.setFixedSize(30, 30) choice_button.setIcon(icon) choice_button.setEnabled(False) choice_button.clicked.connect(on_chbtn_clicked) ch_grid = QGridLayout() ch_grid.addWidget(np_radio, 0, 0) ch_grid.addWidget(cp_radio, 0, 1) ch_grid.addWidget(choice_button, 0, 2) ch_frame = QFrame() ch_frame.setFrameShape(QFrame.Panel) ch_frame.setFrameShadow(QFrame.Sunken) ch_frame.setLayout(ch_grid) ch_hbox = QHBoxLayout() ch_hbox.addWidget(ch_frame) # -------------------------------------Второй блок формы------------------------------------ if int_lng == 'Russian': title_label = QLabel("Введите название задачи") project_label = QLabel("Название проекта:") elif int_lng == 'English': title_label = QLabel("Enter project name") project_label = QLabel("Project name:") title_label.setEnabled(False) tl_hbox = QHBoxLayout() tl_hbox.addWidget(title_label) project_name = QLineEdit() project_name.textChanged.connect(handleEditingFinished) project_name.setFixedSize(180, 25) valid = QtGui.QRegExpValidator(QtCore.QRegExp("\S*"), self) project_name.setValidator(valid) if int_lng == 'Russian': project_path_label = QLabel("Путь:") elif int_lng == 'English': project_path_label = QLabel("Path:") project_path_name = QLineEdit() project_path_name.setEnabled(False) project_path_name.textChanged.connect(handleEditingFinished) project_path_name.setFixedSize(180, 25) path_button = QPushButton("...") path_button.clicked.connect(on_path_choose) path_button.setFixedSize(25, 25) project_grid = QGridLayout() project_grid.addWidget(project_label, 0, 0) project_grid.addWidget(project_name, 0, 1, alignment=QtCore.Qt.AlignRight) project_grid.addWidget(project_path_label, 1, 0) project_grid.addWidget(project_path_name, 1, 1) project_grid.addWidget(path_button, 1, 2) project_frame = QFrame() project_frame.setEnabled(False) project_frame.setStyleSheet("border-color: darkgray;") project_frame.setFrameShape(QFrame.Panel) project_frame.setFrameShadow(QFrame.Sunken) project_frame.setLayout(project_grid) project_grid_vbox = QVBoxLayout() project_grid_vbox.addWidget(project_frame) # ---------------------Кнопки сохранения и отмены и их блок------------------------- if int_lng == 'Russian': save_button = QPushButton("Сохранить") cancel_button = QPushButton("Отмена") elif int_lng == 'English': save_button = QPushButton("Save") cancel_button = QPushButton("Cancel") save_button.setFixedSize(80, 25) save_button.clicked.connect(on_save_clicked) save_button.setEnabled(False) cancel_button.setFixedSize(80, 25) cancel_button.clicked.connect(on_cancel_clicked) buttons_hbox = QHBoxLayout() buttons_hbox.addWidget(save_button) buttons_hbox.addWidget(cancel_button) # -------------------------Фрейм формы--------------------------- bound_grid = QGridLayout() bound_grid.addLayout(cl_hbox, 0, 0, alignment=QtCore.Qt.AlignCenter) bound_grid.addLayout(ch_hbox, 1, 0, alignment=QtCore.Qt.AlignCenter) bound_grid.addLayout(tl_hbox, 2, 0, alignment=QtCore.Qt.AlignCenter) bound_grid.addLayout(project_grid_vbox, 3, 0, alignment=QtCore.Qt.AlignCenter) bound_grid.addLayout(buttons_hbox, 4, 0, alignment=QtCore.Qt.AlignCenter) bound_frame = QFrame() bound_frame.setStyleSheet( open("./styles/properties_form_style.qss", "r").read()) bound_frame.setLayout(bound_grid) bound_vbox = QVBoxLayout() bound_vbox.addWidget(bound_frame) # --------------------Размещение на форме всех компонентов--------- form_1 = QFormLayout() form_1.addRow(bound_vbox) self.setLayout(form_1)
def setup_ui(self, frame: QtWidgets.QFrame) -> None: """Set up the user input window and initializes all the variables to create the user interface of the window.""" frame.setObjectName("frame") frame.setEnabled(True) frame.resize(777, 553) frame.setFrameShape(QtWidgets.QFrame.NoFrame) self.tab = QtWidgets.QLabel(frame) self.tab.setGeometry(QtCore.QRect(150, 30, 501, 61)) self.tab.setSizeIncrement(QtCore.QSize(0, 0)) font = QtGui.QFont() font.setPointSize(10) font.setBold(True) font.setWeight(75) self.tab.setFont(font) self.tab.setAutoFillBackground(False) self.tab.setFrameShape(QtWidgets.QFrame.NoFrame) self.tab.setAlignment(QtCore.Qt.AlignCenter) self.tab.setWordWrap(True) self.tab.setObjectName("tab") self.sbox9 = QtWidgets.QSpinBox(frame) self.sbox9.setGeometry(QtCore.QRect(680, 300, 42, 22)) self.sbox9.setMaximum(10) self.sbox9.setObjectName("sbox9") self.cbox5 = QtWidgets.QComboBox(frame) self.cbox5.setGeometry(QtCore.QRect(60, 350, 231, 21)) self.cbox5.setEditable(True) self.cbox5.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength) self.cbox5.setObjectName("cbox5") self.cbox5.addItem("") self.cbox6 = QtWidgets.QComboBox(frame) self.cbox6.setGeometry(QtCore.QRect(430, 150, 231, 21)) self.cbox6.setEditable(True) self.cbox6.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength) self.cbox6.setObjectName("cbox6") self.cbox6.addItem("") self.cbox9 = QtWidgets.QComboBox(frame) self.cbox9.setGeometry(QtCore.QRect(430, 300, 231, 21)) self.cbox9.setEditable(True) self.cbox9.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength) self.cbox9.setObjectName("cbox9") self.cbox9.addItem("") self.sbox5 = QtWidgets.QSpinBox(frame) self.sbox5.setGeometry(QtCore.QRect(310, 350, 42, 22)) self.sbox5.setMaximum(10) self.sbox5.setObjectName("sbox5") self.sbox8 = QtWidgets.QSpinBox(frame) self.sbox8.setGeometry(QtCore.QRect(680, 250, 42, 22)) self.sbox8.setMaximum(10) self.sbox8.setObjectName("sbox8") self.cbox8 = QtWidgets.QComboBox(frame) self.cbox8.setGeometry(QtCore.QRect(430, 250, 231, 21)) self.cbox8.setEditable(True) self.cbox8.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength) self.cbox8.setObjectName("cbox8") self.cbox8.addItem("") self.cbox3 = QtWidgets.QComboBox(frame) self.cbox3.setGeometry(QtCore.QRect(60, 250, 231, 21)) self.cbox3.setEditable(True) self.cbox3.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength) self.cbox3.setObjectName("cbox3") self.cbox3.addItem("") self.spin_box = QtWidgets.QSpinBox(frame) self.spin_box.setGeometry(QtCore.QRect(310, 150, 42, 22)) self.spin_box.setMaximum(10) self.spin_box.setObjectName("spin_box") self.sbox10 = QtWidgets.QSpinBox(frame) self.sbox10.setGeometry(QtCore.QRect(680, 350, 42, 22)) self.sbox10.setMaximum(10) self.sbox10.setObjectName("sbox10") self.cbox7 = QtWidgets.QComboBox(frame) self.cbox7.setGeometry(QtCore.QRect(430, 200, 231, 21)) self.cbox7.setEditable(True) self.cbox7.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength) self.cbox7.setObjectName("cbox7") self.cbox7.addItem("") self.cbox10 = QtWidgets.QComboBox(frame) self.cbox10.setGeometry(QtCore.QRect(430, 350, 231, 21)) self.cbox10.setEditable(True) self.cbox10.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength) self.cbox10.setObjectName("cbox10") self.cbox10.addItem("") self.sbox3 = QtWidgets.QSpinBox(frame) self.sbox3.setGeometry(QtCore.QRect(310, 250, 42, 22)) self.sbox3.setMaximum(10) self.sbox3.setObjectName("sbox3") self.cbox4 = QtWidgets.QComboBox(frame) self.cbox4.setGeometry(QtCore.QRect(60, 300, 231, 21)) self.cbox4.setEditable(True) self.cbox4.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength) self.cbox4.setObjectName("cbox4") self.cbox4.addItem("") self.sbox4 = QtWidgets.QSpinBox(frame) self.sbox4.setGeometry(QtCore.QRect(310, 300, 42, 22)) self.sbox4.setMaximum(10) self.sbox4.setObjectName("sbox4") self.sbox2 = QtWidgets.QSpinBox(frame) self.sbox2.setGeometry(QtCore.QRect(310, 200, 42, 22)) self.sbox2.setMaximum(10) self.sbox2.setObjectName("sbox2") self.sbox6 = QtWidgets.QSpinBox(frame) self.sbox6.setGeometry(QtCore.QRect(680, 150, 42, 22)) self.sbox6.setMaximum(10) self.sbox6.setObjectName("sbox6") self.cbox2 = QtWidgets.QComboBox(frame) self.cbox2.setGeometry(QtCore.QRect(60, 200, 231, 21)) self.cbox2.setEditable(True) self.cbox2.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength) self.cbox2.setObjectName("cbox2") self.cbox2.addItem("") self.sbox7 = QtWidgets.QSpinBox(frame) self.sbox7.setGeometry(QtCore.QRect(680, 200, 42, 22)) self.sbox7.setMaximum(10) self.sbox7.setObjectName("sbox7") self.cbox1 = QtWidgets.QComboBox(frame) self.cbox1.setGeometry(QtCore.QRect(60, 150, 231, 21)) self.cbox1.setEditable(True) self.cbox1.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength) self.cbox1.setObjectName("cbox1") self.cbox1.addItem("") self.button2 = QtWidgets.QPushButton(frame) self.button2.setGeometry(QtCore.QRect(350, 410, 93, 28)) self.button2.setObjectName("button2") self.tab4 = QtWidgets.QLabel(frame) self.tab4.setGeometry(QtCore.QRect(60, 120, 55, 16)) size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) size_policy.setHorizontalStretch(0) size_policy.setVerticalStretch(0) size_policy.setHeightForWidth(self.tab4.sizePolicy().hasHeightForWidth()) self.tab4.setSizePolicy(size_policy) font = QtGui.QFont() font.setBold(True) font.setWeight(75) self.tab4.setFont(font) self.tab4.setObjectName("tab4") self.tab2 = QtWidgets.QLabel(frame) self.tab2.setGeometry(QtCore.QRect(430, 120, 55, 16)) size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) size_policy.setHorizontalStretch(0) size_policy.setVerticalStretch(0) size_policy.setHeightForWidth(self.tab2.sizePolicy().hasHeightForWidth()) self.tab2.setSizePolicy(size_policy) font = QtGui.QFont() font.setBold(True) font.setWeight(75) self.tab2.setFont(font) self.tab2.setObjectName("tab2") self.tab3 = QtWidgets.QLabel(frame) self.tab3.setGeometry(QtCore.QRect(310, 120, 55, 16)) size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) size_policy.setHorizontalStretch(0) size_policy.setVerticalStretch(0) size_policy.setHeightForWidth(self.tab3.sizePolicy().hasHeightForWidth()) self.tab3.setSizePolicy(size_policy) font = QtGui.QFont() font.setBold(True) font.setWeight(75) self.tab3.setFont(font) self.tab3.setObjectName("tab3") self.tab5 = QtWidgets.QLabel(frame) self.tab5.setGeometry(QtCore.QRect(680, 120, 55, 16)) size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) size_policy.setHorizontalStretch(0) size_policy.setVerticalStretch(0) size_policy.setHeightForWidth(self.tab5.sizePolicy().hasHeightForWidth()) self.tab5.setSizePolicy(size_policy) font = QtGui.QFont() font.setBold(True) font.setWeight(75) self.tab5.setFont(font) self.tab5.setObjectName("tab5") self.retranslate_ui(frame) QtCore.QMetaObject.connectSlotsByName(frame) ui_functions.add_options(self.cbox1) ui_functions.add_options(self.cbox2) ui_functions.add_options(self.cbox3) ui_functions.add_options(self.cbox4) ui_functions.add_options(self.cbox5) ui_functions.add_options(self.cbox6) ui_functions.add_options(self.cbox7) ui_functions.add_options(self.cbox8) ui_functions.add_options(self.cbox9) ui_functions.add_options(self.cbox10) self.button2.clicked.connect(self.get_values)
class installAir(confStack): def __init_stack__(self): self.dbg = False self._debug("installer load") self.description = (_("Air Apps Installer")) self.menu_description = (_("Install air apps")) self.icon = ('air-installer') self.tooltip = (_( "From here you can manage the air apps installed on your system")) self.index = 2 self.enabled = True self.level = 'system' # self.hideControlButtons() self.airinstaller = installer.AirManager() self.setStyleSheet(self._setCss()) #def __init__ def _load_screen(self): def _fileChooser(): fdia = QFileDialog() fdia.setNameFilter("air apps(*.air)") if (fdia.exec_()): fchoosed = fdia.selectedFiles()[0] self.inp_file.setText(fchoosed) self.updateScreen() box = QGridLayout() box.addWidget(QLabel(_("Air file")), 0, 0, 1, 1, Qt.AlignBottom) self.inp_file = QLineEdit() self.inp_file.setPlaceholderText(_("Choose file for install")) box.addWidget(self.inp_file, 1, 0, 1, 1, Qt.AlignTop) btn_file = QPushButton("...") btn_file.setObjectName("fileButton") btn_file.clicked.connect(_fileChooser) box.addWidget(btn_file, 1, 1, 1, 1, Qt.AlignLeft | Qt.AlignTop) self.frame = QFrame() box.addWidget(self.frame, 2, 0, 1, 1, Qt.AlignTop) framebox = QGridLayout() self.frame.setLayout(framebox) framebox.addWidget(QLabel(_("App name")), 0, 0, 1, 1, Qt.AlignBottom) self.btn_icon = QPushButton() self.btn_icon.setToolTip(_("Push for icon change")) framebox.addWidget(self.btn_icon, 0, 1, 2, 1, Qt.AlignLeft) self.inp_name = QLineEdit() self.inp_name.setObjectName("fileInput") self.inp_name.setPlaceholderText(_("Application name")) framebox.addWidget(self.inp_name, 1, 0, 1, 1, Qt.AlignTop) framebox.addWidget(QLabel(_("App description")), 2, 0, 1, 1, Qt.AlignBottom) self.inp_desc = QLineEdit() self.inp_desc.setPlaceholderText(_("Application description")) framebox.addWidget(self.inp_desc, 3, 0, 1, 2, Qt.AlignTop) self.setLayout(box) self.updateScreen() return (self) #def _load_screen def _loadAppData(self, air=""): if air: air_info = installer.AirManager().get_air_info(air) pb = air_info.get('pb', '') if pb: #Convert GDK pixbuf to QPixmap img = QtGui.QImage(GdkPixbuf.Pixbuf.get_pixels(pb), GdkPixbuf.Pixbuf.get_width(pb), GdkPixbuf.Pixbuf.get_height(pb), QtGui.QImage.Format_ARGB32 ) #,GdkPixbuf.Pixbuf.get_rowstride(pb)) icon = QtGui.QIcon(QtGui.QPixmap(img)) self.btn_icon.setIcon(icon) self.btn_icon.setIconSize(QSize(64, 64)) name = air_info.get('name', os.path.basename(self.inp_file.text())) self.inp_name.setText(name) else: self.inp_name.setText("") self.inp_desc.setText("") icon = QtGui.QIcon.fromTheme("application-x-air-installer") self.btn_icon.setIcon(icon) self.btn_icon.setIconSize(QSize(64, 64)) self.frame.setEnabled(False) #def _loadAppData def updateScreen(self): self.frame.setEnabled(True) air = self.inp_file.text() self._loadAppData(air) return True #def _udpate_screen def writeConfig(self): tmp_icon = tempfile.mkstemp()[1] self.btn_icon.icon().pixmap(QSize(64, 64)).save(tmp_icon, "PNG") subprocess.check_call(['/usr/bin/xhost', '+']) air = self.inp_file.text() try: ins = subprocess.check_call([ 'pkexec', '/usr/bin/air-helper-installer.py', 'install', air, tmp_icon ]) self.install_err = False except Exception as e: self._debug(e) subprocess.check_output([ "xdg-mime", "install", "/usr/share/mime/packages/x-air-installer.xml" ]) subprocess.check_output([ "xdg-mime", "default", "/usr/share/applications/air-installer.desktop", "/usr/share/mime/packages/x-air-installer.xml" ], input=b"") subprocess.check_call(['/usr/bin/xhost', '-']) self.showMsg(_("App %s installed succesfully" % os.path.basename(air))) #def writeConfig def _setCss(self): css = """ #fileButton{ margin:0px; padding:1px; } #fileInput{ margin:0px; } #imgButton{ margin:0px; padding:0px; }""" return (css)
class WeatherDataGapfiller(QMainWindow): ConsoleSignal = QSignal(str) def __init__(self, parent=None): super().__init__(parent) self._workdir = None self._corrcoeff_update_inprogress = False self._pending_corrcoeff_update = None self._loading_data_inprogress = False self.__initUI__() # Setup the DataGapfillManager. self.gapfill_manager = DataGapfillManager() self.gapfill_manager.sig_task_progress.connect( self.progressbar.setValue) self.gapfill_manager.sig_status_message.connect( self.set_statusbar_text) def __initUI__(self): self.setWindowIcon(get_icon('master')) # Setup the toolbar at the bottom. self.btn_fill = QPushButton('Gapfill Data') self.btn_fill.setIcon(get_icon('fill_data')) self.btn_fill.setIconSize(get_iconsize('small')) self.btn_fill.setToolTip( "Fill the gaps in the daily weather data of the selected " "weather station.") self.btn_fill.clicked.connect(self._handle_gapfill_btn_clicked) widget_toolbar = QFrame() grid_toolbar = QGridLayout(widget_toolbar) grid_toolbar.addWidget(self.btn_fill, 0, 0) grid_toolbar.setContentsMargins(0, 0, 0, 0) grid_toolbar.setColumnStretch(0, 100) # ---- Target Station groupbox self.target_station = QComboBox() self.target_station.currentIndexChanged.connect( self._handle_target_station_changed) self.target_station_info = QTextEdit() self.target_station_info.setReadOnly(True) self.target_station_info.setMaximumHeight(110) self.btn_refresh_staList = QToolButton() self.btn_refresh_staList.setIcon(get_icon('refresh')) self.btn_refresh_staList.setToolTip( 'Force the reloading of the weather data files') self.btn_refresh_staList.setIconSize(get_iconsize('small')) self.btn_refresh_staList.setAutoRaise(True) self.btn_refresh_staList.clicked.connect( lambda: self.load_data_dir_content(force_reload=True)) self.btn_delete_data = QToolButton() self.btn_delete_data.setIcon(get_icon('delete_data')) self.btn_delete_data.setEnabled(False) self.btn_delete_data.setAutoRaise(True) self.btn_delete_data.setToolTip( 'Remove the currently selected dataset and delete the input ' 'datafile. However, raw datafiles will be kept.') self.btn_delete_data.clicked.connect(self.delete_current_dataset) # Generate the layout for the target station group widget. self.target_widget = QWidget() target_station_layout = QGridLayout(self.target_widget) target_station_layout.setHorizontalSpacing(1) target_station_layout.setColumnStretch(0, 1) target_station_layout.setContentsMargins(0, 0, 0, 0) widgets = [self.target_station, self.btn_refresh_staList, self.btn_delete_data] target_station_layout.addWidget(self.target_station, 1, 0) for col, widget in enumerate(widgets): target_station_layout.addWidget(widget, 1, col) # Setup the gapfill dates. label_From = QLabel('From : ') self.date_start_widget = QDateEdit() self.date_start_widget.setDisplayFormat('dd / MM / yyyy') self.date_start_widget.setEnabled(False) self.date_start_widget.dateChanged.connect( self._update_corrcoeff_table) label_To = QLabel('To : ') self.date_end_widget = QDateEdit() self.date_end_widget.setEnabled(False) self.date_end_widget.setDisplayFormat('dd / MM / yyyy') self.date_end_widget.dateChanged.connect( self._update_corrcoeff_table) self.fillDates_widg = QWidget() gapfilldates_layout = QGridLayout(self.fillDates_widg) gapfilldates_layout.addWidget(label_From, 0, 0) gapfilldates_layout.addWidget(self.date_start_widget, 0, 1) gapfilldates_layout.addWidget(label_To, 1, 0) gapfilldates_layout.addWidget(self.date_end_widget, 1, 1) gapfilldates_layout.setColumnStretch(2, 1) gapfilldates_layout.setContentsMargins(0, 0, 0, 0) # Create the gapfill target station groupbox. target_groupbox = QGroupBox("Fill data for weather station") target_layout = QGridLayout(target_groupbox) target_layout.addWidget(self.target_widget, 0, 0) target_layout.addWidget(self.target_station_info, 1, 0) target_layout.addWidget(self.fillDates_widg, 2, 0) # Setup the left panel. self._regression_model_groupbox = ( self._create_regression_model_settings()) self._station_selection_groupbox = ( self._create_station_selection_criteria()) self.left_panel = QFrame() left_panel_layout = QGridLayout(self.left_panel) left_panel_layout.addWidget(target_groupbox, 0, 0) left_panel_layout.addWidget(self._station_selection_groupbox, 3, 0) left_panel_layout.addWidget(self._regression_model_groupbox, 4, 0) left_panel_layout.addWidget(widget_toolbar, 5, 0) left_panel_layout.setRowStretch(6, 1) left_panel_layout.setContentsMargins(0, 0, 0, 0) # Setup the right panel. self.corrcoeff_textedit = QTextEdit() self.corrcoeff_textedit.setReadOnly(True) self.corrcoeff_textedit.setMinimumWidth(700) self.corrcoeff_textedit.setFrameStyle(0) self.corrcoeff_textedit.document().setDocumentMargin(10) self.sta_display_summary = QTextEdit() self.sta_display_summary.setReadOnly(True) self.sta_display_summary.setFrameStyle(0) self.sta_display_summary.document().setDocumentMargin(10) self.right_panel = QTabWidget() self.right_panel.addTab( self.corrcoeff_textedit, 'Correlation Coefficients') self.right_panel.addTab( self.sta_display_summary, 'Data Overview') # Setup the progressbar. self.progressbar = QProgressBar() self.progressbar.setValue(0) self.progressbar.hide() self.statustext = QLabel() self.statustext.setStyleSheet( "QLabel {background-color: transparent; padding: 0 0 0 3px;}") self.statustext.setMinimumHeight(self.progressbar.minimumHeight()) # Setup the main widget. main_widget = QWidget() main_grid = QGridLayout(main_widget) main_grid.addWidget(self.left_panel, 0, 0) main_grid.addWidget(self.right_panel, 0, 1) main_grid.addWidget(self.progressbar, 1, 0, 1, 2) main_grid.addWidget(self.statustext, 1, 0, 1, 2) main_grid.setColumnStretch(1, 500) main_grid.setRowStretch(0, 500) self.setCentralWidget(main_widget) def _create_station_selection_criteria(self): Nmax_label = QLabel('Nbr. of stations :') self.Nmax = QSpinBox() self.Nmax.setRange(0, 99) self.Nmax.setMinimum(1) self.Nmax.setValue(CONF.get('gapfill_data', 'nbr_of_station', 4)) self.Nmax.setAlignment(Qt.AlignCenter) ttip = ('<p>Distance limit beyond which neighboring stations' ' are excluded from the gapfilling procedure.</p>' '<p>This condition is ignored if set to -1.</p>') distlimit_label = QLabel('Max. Distance :') distlimit_label.setToolTip(ttip) self.distlimit = QSpinBox() self.distlimit.setRange(-1, 9999) self.distlimit.setSingleStep(1) self.distlimit.setValue( CONF.get('gapfill_data', 'max_horiz_dist', 100)) self.distlimit.setToolTip(ttip) self.distlimit.setSuffix(' km') self.distlimit.setAlignment(Qt.AlignCenter) self.distlimit.valueChanged.connect(self._update_corrcoeff_table) ttip = ('<p>Altitude difference limit over which neighboring ' ' stations are excluded from the gapfilling procedure.</p>' '<p>This condition is ignored if set to -1.</p>') altlimit_label = QLabel('Max. Elevation Diff. :') altlimit_label.setToolTip(ttip) self.altlimit = QSpinBox() self.altlimit.setRange(-1, 9999) self.altlimit.setSingleStep(1) self.altlimit.setValue( CONF.get('gapfill_data', 'max_vert_dist', 350)) self.altlimit.setToolTip(ttip) self.altlimit.setSuffix(' m') self.altlimit.setAlignment(Qt.AlignCenter) self.altlimit.valueChanged.connect(self._update_corrcoeff_table) # Setup the main widget. widget = QGroupBox('Stations Selection Criteria') layout = QGridLayout(widget) layout.addWidget(Nmax_label, 0, 0) layout.addWidget(self.Nmax, 0, 1) layout.addWidget(distlimit_label, 1, 0) layout.addWidget(self.distlimit, 1, 1) layout.addWidget(altlimit_label, 2, 0) layout.addWidget(self.altlimit, 2, 1) layout.setColumnStretch(0, 1) return widget def _create_advanced_settings(self): self.full_error_analysis = QCheckBox('Full Error Analysis.') self.full_error_analysis.setChecked(True) fig_opt_layout = QGridLayout() fig_opt_layout.addWidget(QLabel("Figure output format : "), 0, 0) fig_opt_layout.addWidget(self.fig_format, 0, 2) fig_opt_layout.addWidget(QLabel("Figure labels language : "), 1, 0) fig_opt_layout.addWidget(self.fig_language, 1, 2) fig_opt_layout.setContentsMargins(0, 0, 0, 0) fig_opt_layout.setColumnStretch(1, 100) # Setup the main layout. widget = QFrame() layout = QGridLayout(widget) layout.addWidget(self.full_error_analysis, 0, 0) layout.addLayout(fig_opt_layout, 2, 0) layout.setRowStretch(layout.rowCount(), 100) layout.setContentsMargins(10, 0, 10, 0) return widget def _create_regression_model_settings(self): self.RMSE_regression = QRadioButton('Ordinary Least Squares') self.RMSE_regression.setChecked( CONF.get('gapfill_data', 'regression_model', 'OLS') == 'OLS') self.ABS_regression = QRadioButton('Least Absolute Deviations') self.ABS_regression.setChecked( CONF.get('gapfill_data', 'regression_model', 'OLS') == 'LAD') widget = QGroupBox('Regression Model') layout = QGridLayout(widget) layout.addWidget(self.RMSE_regression, 0, 0) layout.addWidget(self.ABS_regression, 1, 0) return widget def set_statusbar_text(self, text): self.statustext.setText(text) @property def workdir(self): return self._workdir def set_workdir(self, dirname): """ Set the working directory to dirname. """ self._workdir = dirname self.gapfill_manager.set_workdir(dirname) self.load_data_dir_content() def delete_current_dataset(self): """ Delete the current dataset source file and force a reload of the input daily weather datafiles. """ current_index = self.target_station.currentIndex() if current_index != -1: basename = self.gapfill_manager.worker().wxdatasets.fnames[ current_index] filename = os.path.join(self.workdir, basename) delete_file(filename) self.load_data_dir_content() def _handle_target_station_changed(self): """Handle when the target station is changed by the user.""" self.btn_delete_data.setEnabled( self.target_station.currentIndex() != -1) self.update_corrcoeff() def get_dataset_names(self): """ Return a list of the names of the dataset that are loaded in memory and listed in the target station dropdown menu. """ return [self.target_station.itemText(i) for i in range(self.target_station.count())] # ---- Correlation coefficients def update_corrcoeff(self): """ Calculate the correlation coefficients and display the results in the GUI. """ if self.target_station.currentIndex() != -1: station_id = self.target_station.currentData() if self._corrcoeff_update_inprogress is True: self._pending_corrcoeff_update = station_id else: self._corrcoeff_update_inprogress = True self.corrcoeff_textedit.setText('') self.gapfill_manager.set_target_station( station_id, callback=self._handle_corrcoeff_updated) def _handle_corrcoeff_updated(self): self._corrcoeff_update_inprogress = False if self._pending_corrcoeff_update is None: self._update_corrcoeff_table() else: self._pending_corrcoeff_update = None self.update_corrcoeff() def _update_corrcoeff_table(self): """ This method plot the correlation coefficient table in the display area. It is separated from the method "update_corrcoeff" because red numbers and statistics regarding missing data for the selected time period can be updated in the table when the user changes the values without having to recalculate the correlation coefficient each time. """ if self.target_station.currentIndex() != -1: table, target_info = ( self.gapfill_manager.worker().generate_correlation_html_table( self.get_gapfill_parameters())) self.corrcoeff_textedit.setText(table) self.target_station_info.setText(target_info) # ---- Load Data def load_data_dir_content(self, force_reload=False): """ Load weater data from valid files contained in the working directory. """ self._pending_corrcoeff_update = None self._loading_data_inprogress = True self.left_panel.setEnabled(False) self.right_panel.setEnabled(False) self.corrcoeff_textedit.setText('') self.target_station_info.setText('') self.target_station.clear() self.gapfill_manager.load_data( force_reload=force_reload, callback=self._handle_data_dir_content_loaded) def _handle_data_dir_content_loaded(self): """ Handle when data finished loaded from valid files contained in the working directory. """ self.left_panel.setEnabled(True) self.right_panel.setEnabled(True) self.target_station.blockSignals(True) station_names = self.gapfill_manager.get_station_names() station_ids = self.gapfill_manager.get_station_ids() for station_name, station_id in zip(station_names, station_ids): self.target_station.addItem( '{} ({})'.format(station_name, station_id), userData=station_id) self.target_station.blockSignals(False) self.sta_display_summary.setHtml( self.gapfill_manager.worker().generate_html_summary_table()) if len(station_names) > 0: self._setup_fill_and_save_dates() self.target_station.blockSignals(True) self.target_station.setCurrentIndex(0) self.target_station.blockSignals(False) self._handle_target_station_changed() self._loading_data_inprogress = False def _setup_fill_and_save_dates(self): """ Set first and last dates of the 'Fill data for weather station'. """ if self.gapfill_manager.count(): self.date_start_widget.setEnabled(True) self.date_end_widget.setEnabled(True) mindate = ( self.gapfill_manager.worker() .wxdatasets.metadata['first_date'].min()) maxdate = ( self.gapfill_manager.worker() .wxdatasets.metadata['last_date'].max()) qdatemin = QDate(mindate.year, mindate.month, mindate.day) qdatemax = QDate(maxdate.year, maxdate.month, maxdate.day) self.date_start_widget.blockSignals(True) self.date_start_widget.setDate(qdatemin) self.date_start_widget.setMinimumDate(qdatemin) self.date_start_widget.setMaximumDate(qdatemax) self.date_start_widget.blockSignals(False) self.date_end_widget.blockSignals(True) self.date_end_widget.setDate(qdatemax) self.date_end_widget.setMinimumDate(qdatemin) self.date_end_widget.setMaximumDate(qdatemax) self.date_end_widget.blockSignals(False) # ---- Gapfill Data def get_gapfill_parameters(self): """ Return a dictionary containing the parameters that are set in the GUI for gapfilling weather data. """ return { 'limitDist': self.distlimit.value(), 'limitAlt': self.altlimit.value(), 'date_start': self.date_start_widget.date().toString('dd/MM/yyyy'), 'date_end': self.date_end_widget.date().toString('dd/MM/yyyy') } def _handle_gapfill_btn_clicked(self): """ Handle when the user clicked on the gapfill button. """ if self.gapfill_manager.count() == 0: QMessageBox.warning( self, 'Warning', "There is no data to fill.", QMessageBox.Ok) return # Check for dates errors. datetime_start = datetime_from_qdatedit(self.date_start_widget) datetime_end = datetime_from_qdatedit(self.date_end_widget) if datetime_start > datetime_end: QMessageBox.warning( self, 'Warning', ("<i>From</i> date is set to a later time than " "the <i>To</i> date."), QMessageBox.Ok) return if self.target_station.currentIndex() == -1: QMessageBox.warning( self, 'Warning', "No weather station is currently selected", QMessageBox.Ok) return self.start_gapfill_target() def _handle_gapfill_target_finished(self): """ Method initiated from an automatic return from the gapfilling process in batch mode. Iterate over the station list and continue process normally. """ self.btn_fill.setIcon(get_icon('fill_data')) self.btn_fill.setEnabled(True) self.target_widget.setEnabled(True) self.fillDates_widg.setEnabled(True) self._regression_model_groupbox.setEnabled(True) self._station_selection_groupbox.setEnabled(True) self.progressbar.setValue(0) QApplication.processEvents() self.progressbar.hide() def start_gapfill_target(self): # Update the gui. self.btn_fill.setEnabled(False) self.fillDates_widg.setEnabled(False) self.target_widget.setEnabled(False) self._regression_model_groupbox.setEnabled(False) self._station_selection_groupbox.setEnabled(False) self.progressbar.show() # Start the gapfill thread. self.gapfill_manager.gapfill_data( time_start=datetime_from_qdatedit(self.date_start_widget), time_end=datetime_from_qdatedit(self.date_end_widget), max_neighbors=self.Nmax.value(), hdist_limit=self.distlimit.value(), vdist_limit=self.altlimit.value(), regression_mode=self.RMSE_regression.isChecked(), callback=self._handle_gapfill_target_finished ) def close(self): CONF.set('gapfill_data', 'nbr_of_station', self.Nmax.value()) CONF.set('gapfill_data', 'max_horiz_dist', self.distlimit.value()) CONF.set('gapfill_data', 'max_vert_dist', self.altlimit.value()) CONF.set('gapfill_data', 'regression_model', 'OLS' if self.RMSE_regression.isChecked() else 'LAD') super().close()
class RearrangementViewer(QDialog): def __init__( self, parent, testNumber, current_pages, page_data, need_to_confirm=False ): super().__init__() self.parent = parent self.testNumber = testNumber self.need_to_confirm = need_to_confirm self._setupUI() page_data = self.dedupe_by_md5sum(page_data) self.pageData = page_data self.nameToIrefNFile = {} if current_pages: self.populateListWithCurrent(current_pages) else: self.populateListOriginal() def _setupUI(self): """ Sets up thee UI for the rearrangement Viewer. Notes: helper method for __init__ Returns: None """ self.scrollA = QScrollArea() self.listA = SourceList(self) self.listA.itemSelectionChanged.connect(self.show_relevant_tools) self.scrollA.setWidget(self.listA) self.scrollA.setWidgetResizable(True) self.scrollB = QScrollArea() self.listB = SinkList(self) self.listB.itemSelectionChanged.connect(self.show_relevant_tools) self.scrollB.setWidget(self.listB) self.scrollB.setWidgetResizable(True) self.appendB = QToolButton() # TODO: move &A here and use alt-Enter to Accept dialog? self.appendB.setText("Add &Page(s)") self.appendB.setArrowType(Qt.DownArrow) self.appendB.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.removeB = QToolButton() self.removeB.setArrowType(Qt.UpArrow) self.removeB.setText("&Remove Page(s)") self.removeB.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.sLeftB = QToolButton() self.sLeftB.setArrowType(Qt.LeftArrow) self.sLeftB.setText("Shift Left") self.sLeftB.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.sRightB = QToolButton() self.sRightB.setArrowType(Qt.RightArrow) self.sRightB.setText("Shift Right") self.sRightB.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.reverseB = QPushButton("Reverse Order") self.revertB = QPushButton("Revert to original state") self.revertB.clicked.connect(self.populateListOriginal) self.rotateB_cw = QPushButton("\N{Clockwise Open Circle Arrow} Rotate CW") self.rotateB_ccw = QPushButton("\N{Anticlockwise Open Circle Arrow} Rotate CCW") self.closeB = QPushButton("&Cancel") self.acceptB = QPushButton("&Accept") self.permute = [False] def GrippyMcGrab(): """Grippy bars to spice-up QSplitterHandles.""" width = 64 pad = 20 hb = QHBoxLayout() hb.addItem( QSpacerItem(pad, 1, QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) ) vb = QVBoxLayout() hb.addLayout(vb) hb.addItem( QSpacerItem(pad, 1, QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) ) vb.setContentsMargins(0, 1, 0, 1) vb.setSpacing(2) vb.addItem( QSpacerItem( width, 3, QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding ) ) for i in range(3): f = QFrame() f.setFrameShape(QFrame.HLine) f.setFrameShadow(QFrame.Sunken) vb.addWidget(f) vb.addItem( QSpacerItem( width, 3, QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding ) ) return hb hb3 = QHBoxLayout() self.tools = QFrame() hb = QHBoxLayout() self.tools.setLayout(hb) hb.setContentsMargins(0, 0, 0, 0) hb.addWidget(self.rotateB_ccw) hb.addWidget(self.rotateB_cw) hb.addItem(QSpacerItem(16, 20, QSizePolicy.Minimum, QSizePolicy.Minimum)) hb.addWidget(self.sLeftB) hb.addWidget(self.sRightB) hb.addItem(QSpacerItem(16, 20, QSizePolicy.Minimum, QSizePolicy.Minimum)) hb3.addWidget(self.tools) hb3.addWidget(self.reverseB) hb3.addItem( QSpacerItem(16, 20, QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) ) hb3.addWidget(self.acceptB) hb3.addWidget(self.closeB) allPages = QLabel("Other Pages in Exam") thisQuestion = QLabel("Pages for this Question") # center add/remove buttons on label row hb1 = QHBoxLayout() hb1.addWidget(thisQuestion) hb1.addLayout(GrippyMcGrab()) hb = QHBoxLayout() hb.addWidget(self.appendB) hb.addItem(QSpacerItem(64, 20, QSizePolicy.Minimum, QSizePolicy.Minimum)) hb.addWidget(self.removeB) hb1.addLayout(hb) hb1.addLayout(GrippyMcGrab()) hb1.addWidget(self.revertB) vb0 = QVBoxLayout() s = QSplitter() s.setOrientation(Qt.Vertical) # s.setOpaqueResize(False) s.setChildrenCollapsible(False) s.setHandleWidth(50) # TODO: better not to hardcode, take from children? vb0.addWidget(s) f = QFrame() s.addWidget(f) vb = QVBoxLayout() vb.setContentsMargins(0, 0, 0, 0) f.setLayout(vb) vb.addWidget(allPages) vb.addWidget(self.scrollA) f = QFrame() s.addWidget(f) vb = QVBoxLayout() vb.setContentsMargins(0, 0, 0, 0) f.setLayout(vb) vb.addWidget(self.scrollB) vb.addLayout(hb3) handle = s.handle(1) vb = QVBoxLayout() vb.setContentsMargins(0, 0, 0, 0) vb.setSpacing(0) handle.setLayout(hb1) hb1.setContentsMargins(0, 0, 0, 0) # TODO: Buttons inside the splitter bar, disable drag and custom cursor for b in (self.removeB, self.appendB, self.revertB): b.mouseMoveEvent = lambda *args: None b.setCursor(Qt.ArrowCursor) self.setLayout(vb0) self.resize(QSize(self.parent.width() * 7 / 8, self.parent.height() * 9 / 10)) self.closeB.clicked.connect(self.close) self.sLeftB.clicked.connect(self.shuffleLeft) self.sRightB.clicked.connect(self.shuffleRight) self.reverseB.clicked.connect(self.reverseOrder) self.rotateB_cw.clicked.connect(lambda: self.rotateImages(90)) self.rotateB_ccw.clicked.connect(lambda: self.rotateImages(-90)) self.appendB.clicked.connect(self.sourceToSink) self.removeB.clicked.connect(self.sinkToSource) self.acceptB.clicked.connect(self.doShuffle) allPageWidgets = [self.listA, self.listB] self.listA.selectionModel().selectionChanged.connect( lambda sel, unsel: self.singleSelect(self.listA, allPageWidgets) ) self.listB.selectionModel().selectionChanged.connect( lambda sel, unsel: self.singleSelect(self.listB, allPageWidgets) ) def dedupe_by_md5sum(self, pageData): """Collapse entries in the pagedata with duplicated md5sums. In the future [1], pages will be shared between questions but we only want to show one copy of each such duplicated page in the "Adjust pages" dialog. [1] https://gitlab.com/plom/plom/-/merge_requests/698 The data looks like the following. We want to compress rows that have duplicated md5sums: ``` ['h1.1', 'e224c22eda93456143fbac94beb0ffbd', True, 1, 40, '/tmp/plom_zq/tmpnqq.image] ['h1.2', '97521f4122df24ca012a12930391195a', True, 2, 41, '/tmp/plom_zq/tmp_om.image] ['h2.1', 'e224c22eda93456143fbac94beb0ffbd', False, 1, 40, '/tmp/plom_zq/tmpx0s.image] ['h2.2', '97521f4122df24ca012a12930391195a', False, 2, 41, '/tmp/plom_zq/tmpd5g.image] ['h2.3', 'abcd1234abcd12314717621412412444', False, 3, 42, '/tmp/plom_zq/tmp012.image] ['h3.1', 'abcd1234abcd12314717621412412444', False, 1, 42, '/tmp/plom_zq/tmp012.image] ``` (Possibly filenames are repeated for repeat md5: not required by this code.) From this we want something like: ``` ['h1.1 (& h2.1)', 'e224c22eda93456143fbac94beb0ffbd', True, 1, 40, '/tmp/plom_zq/tmpnqq.image] ['h1.2 (& h2.2)', '97521f4122df24ca012a12930391195a', True, 2, 41, '/tmp/plom_zq/tmp_om.image] ['h2.3 (& h3.1)', 'abcd1234abcd12314717621412412444', False, 3, 42, '/tmp/plom_zq/tmp012.image] ``` where the names of duplicates are shown in parentheses. It seems we need to keep the order as much as possible in this file, which complicates this. May not be completely well-posed. Probably better to refactor before this. E.g., factor out a dict of md5sum to filenames before we get here. "Included" (column 3): include these in the question or maybe server originally had these in the question (TODO: maybe not, True/False generated on API call). TODO: if order does not have `h1,1` first, should we move it first? that is, before the parenthetical? Probably by re-ordering the list. """ # List of lists, preserving original order within each list tmp_data = [] for x in pageData: md5 = x[1] md5s_so_far = [y[0][1] for y in tmp_data] if md5 in md5s_so_far: i = md5s_so_far.index(md5) tmp_data[i].append(x.copy()) else: tmp_data.append([x.copy()]) # Compress each list down to a single item, packing the names new_pageData = [] # warn/log if True not in first? for y in tmp_data: z = y[0].copy() other_names = [_[0] for _ in y[1:]] if other_names: z[0] = z[0] + " (& {})".format(", ".join(other_names)) # If any entry had True for "included", include this row # TODO: or should we reorder the list, moving True to front? # TODO: depends what is done with the other data z[2] = any([_[2] for _ in y]) new_pageData.append(z) return new_pageData def show_relevant_tools(self): """Hide/show tools based on current selections.""" if self.listB.selectionModel().hasSelection(): self.removeB.setEnabled(True) self.tools.setEnabled(True) else: self.removeB.setEnabled(False) self.tools.setEnabled(False) if self.listA.selectionModel().hasSelection(): self.appendB.setEnabled(True) else: self.appendB.setEnabled(False) def populateListOriginal(self): """ Populates the QListWidgets with exam pages, using original server view. Returns: None: but changes the state of self. """ self.nameToIrefNFile = {} self.listA.clear() self.listB.clear() move_order = {} for row in self.pageData: self.nameToIrefNFile[row[0]] = [row[1], row[5]] # add every page image to list A self.listA.addImageItem(row[0], row[5], row[2]) # add the potential for every page to listB self.listB.addPotentialItem(row[0], row[5], row[2]) # if position in current annot is non-null then add to list of pages to move between lists. if row[2] and row[3]: move_order[row[3]] = row[0] for k in sorted(move_order.keys()): self.listB.appendItem(self.listA.hideItemByName(name=move_order[k])) def populateListWithCurrent(self, current): """ Populates the QListWidgets with pages, with current state highlighted. Args: current (list): dicts with 'md5' and 'orientation' keys. Returns: None: but changes the state of self. """ self.nameToIrefNFile = {} self.listA.clear() self.listB.clear() for row in self.pageData: self.nameToIrefNFile[row[0]] = [row[1], row[5]] # add every page image to list A self.listA.addImageItem(row[0], row[5], row[2]) # add the potential for every page to listB self.listB.addPotentialItem(row[0], row[5], row[2]) for kv in current: match = [row[0] for row in self.pageData if row[1] == kv["md5"]] assert len(match) == 1, "Oops, expected unique md5s in filtered pagedata" (match,) = match self.listB.appendItem(self.listA.hideItemByName(match)) if kv["orientation"] != 0: log.info("Applying orientation of %s", kv["orientation"]) # always display unrotated in source ListA # TODO: should reflect server static info (currently always orientation = 0 but...) self.listB.rotateItemTo(match, kv["orientation"]) def sourceToSink(self): """ Adds the currently selected page to the list for the current question. Notes: If currently selected page is in current question, does nothing. Returns: None """ if self.listA.selectionModel().hasSelection(): self.listB.appendItems(self.listA.hideSelectedItems()) else: pass def sinkToSource(self): """ Removes the currently selected page from the list for the current question. Notes: If currently selected page isn't in current question, does nothing. Returns: None """ if self.listB.selectionModel().hasSelection(): self.listA.unhideNamedItems(self.listB.removeSelectedItems()) else: pass def shuffleLeft(self): """ Shuffles currently selected page to the left one position. Notes: If currently selected page isn't in current question, does nothing. Returns: None """ if self.listB.selectionModel().hasSelection(): self.listB.shuffleLeft() else: pass def shuffleRight(self): """ Shuffles currently selected page to the left one position. Notes: If currently selected page isn't in current question, does nothing. Returns: None """ if self.listB.selectionModel().hasSelection(): self.listB.shuffleRight() else: pass def reverseOrder(self): """ reverses the order of the pages in current question. """ self.listB.reverseOrder() def rotateImages(self, angle=90): """ Rotates the currently selected page by 90 degrees.""" self.listB.rotateSelectedImages(angle) def viewImage(self, fname): """ Shows a larger view of the currently selected page.""" ShowExamPage(self, fname) def doShuffle(self): """ Reorders and saves pages according to user's selections. Returns: """ if self.listB.count() == 0: msg = ErrorMessage("You must have at least one page in the bottom list.") msg.exec() return if self.need_to_confirm: msg = SimpleMessage( "Are you sure you want to save this page order? This will erase " "all your annotations." ) if msg.exec() == QMessageBox.No: return self.permute = [] for n in self.listB.getNameList(): tmp = self.nameToIrefNFile[n] self.permute.append((*tmp, self.listB.item_orientation[n])) # return triples of [iref, file, angle] self.accept() def singleSelect(self, currentList, allPages): """ If item selected by user isnt in currentList, deselects currentList. Args: currentList (QListWidget): the list being checked. allPages (List[QListWidget]): all lists in selection Notes: from https://stackoverflow.com/questions/45509496/qt-multiple-qlistwidgets-and-only-a-single-entry-selected Returns: None """ for lstViewI in allPages: if lstViewI == currentList: continue # the check is necessary to prevent recursions... if lstViewI.selectionModel().hasSelection(): # ...as this causes emission of selectionChanged() signal as well: lstViewI.selectionModel().clearSelection()
class WeatherStationDownloader(QMainWindow): """ Widget that allows the user to browse and select ECCC climate stations. """ sig_download_process_ended = QSignal() ConsoleSignal = QSignal(str) staListSignal = QSignal(list) PROV_NAME = [x[0].title() for x in PROV_NAME_ABB] PROV_ABB = [x[1] for x in PROV_NAME_ABB] def __init__(self, parent=None, workdir=None): super().__init__(parent) self.workdir = workdir or get_home_dir() self.stn_finder_worker = WeatherStationFinder() self.stn_finder_worker.sig_load_database_finished.connect( self.receive_load_database) self.stn_finder_thread = QThread() self.stn_finder_worker.moveToThread(self.stn_finder_thread) self._database_isloading = False self.station_table = WeatherSationView() self.waitspinnerbar = WaitSpinnerBar() self.stn_finder_worker.sig_progress_msg.connect( self.waitspinnerbar.set_label) self.__initUI__() self._restore_window_geometry() # Setup the raw data downloader. self._dwnld_inprogress = False self._dwnld_stations_list = [] self.dwnld_thread = QThread() self.dwnld_worker = RawDataDownloader() self.dwnld_worker.moveToThread(self.dwnld_thread) self.dwnld_worker.sig_download_finished.connect( self.process_station_data) self.dwnld_worker.sig_update_pbar.connect(self.progressbar.setValue) self.start_load_database() def __initUI__(self): self.setWindowTitle('Download Weather Data') self.setWindowIcon(get_icon('master')) self.setWindowFlags(Qt.Window) now = datetime.now() # Setup the proximity filter group. self.lat_spinBox = QDoubleSpinBox() self.lat_spinBox.setAlignment(Qt.AlignCenter) self.lat_spinBox.setSingleStep(0.1) self.lat_spinBox.setDecimals(3) self.lat_spinBox.setValue(CONF.get('download_data', 'latitude', 0)) self.lat_spinBox.setMinimum(0) self.lat_spinBox.setMaximum(180) self.lat_spinBox.setSuffix(u' °') self.lat_spinBox.valueChanged.connect(self.proximity_grpbox_toggled) self.lon_spinBox = QDoubleSpinBox() self.lon_spinBox.setAlignment(Qt.AlignCenter) self.lon_spinBox.setSingleStep(0.1) self.lon_spinBox.setDecimals(3) self.lon_spinBox.setValue(CONF.get('download_data', 'longitude', 0)) self.lon_spinBox.setMinimum(0) self.lon_spinBox.setMaximum(180) self.lon_spinBox.setSuffix(u' °') self.lon_spinBox.valueChanged.connect(self.proximity_grpbox_toggled) self.radius_SpinBox = QComboBox() self.radius_SpinBox.addItems(['25 km', '50 km', '100 km', '200 km']) self.radius_SpinBox.setCurrentIndex( CONF.get('download_data', 'radius_index', 0)) self.radius_SpinBox.currentIndexChanged.connect( self.search_filters_changed) self.prox_grpbox = QGroupBox("Proximity Filter") self.prox_grpbox.setCheckable(True) self.prox_grpbox.setChecked( CONF.get('download_data', 'proximity_filter', False)) self.prox_grpbox.toggled.connect(self.proximity_grpbox_toggled) prox_search_grid = QGridLayout(self.prox_grpbox) prox_search_grid.addWidget(QLabel('Latitude:'), 0, 0) prox_search_grid.addWidget(self.lat_spinBox, 0, 1) prox_search_grid.addWidget(QLabel('North'), 0, 2) prox_search_grid.addWidget(QLabel('Longitude:'), 1, 0) prox_search_grid.addWidget(self.lon_spinBox, 1, 1) prox_search_grid.addWidget(QLabel('West'), 1, 2) prox_search_grid.addWidget(QLabel('Search Radius:'), 2, 0) prox_search_grid.addWidget(self.radius_SpinBox, 2, 1) prox_search_grid.setColumnStretch(0, 100) prox_search_grid.setRowStretch(3, 100) # ---- Province filter self.prov_widg = QComboBox() self.prov_widg.addItems(['All'] + self.PROV_NAME) self.prov_widg.setCurrentIndex( CONF.get('download_data', 'province_index', 0)) self.prov_widg.currentIndexChanged.connect(self.search_filters_changed) prov_grpbox = QGroupBox() prov_layout = QGridLayout(prov_grpbox) prov_layout.addWidget(QLabel('Province:'), 0, 0) prov_layout.addWidget(self.prov_widg, 0, 1) prov_layout.setColumnStretch(0, 1) prov_layout.setRowStretch(1, 1) # ---- Data availability filter # Number of years with data self.nbrYear = QSpinBox() self.nbrYear.setAlignment(Qt.AlignCenter) self.nbrYear.setSingleStep(1) self.nbrYear.setMinimum(0) self.nbrYear.setValue(CONF.get('download_data', 'min_nbr_of_years', 3)) self.nbrYear.valueChanged.connect(self.search_filters_changed) subgrid1 = QGridLayout() subgrid1.setContentsMargins(0, 0, 0, 0) subgrid1.addWidget(QLabel('for at least'), 0, 0) subgrid1.addWidget(self.nbrYear, 0, 1) subgrid1.addWidget(QLabel('year(s)'), 0, 2) subgrid1.setColumnStretch(3, 100) subgrid1.setHorizontalSpacing(5) # Year range self.minYear = QSpinBox() self.minYear.setAlignment(Qt.AlignCenter) self.minYear.setSingleStep(1) self.minYear.setMinimum(1840) self.minYear.setMaximum(now.year) self.minYear.setValue( CONF.get('download_data', 'year_range_left_bound', 1840)) self.minYear.valueChanged.connect(self.minYear_changed) label_and = QLabel('and') label_and.setAlignment(Qt.AlignCenter) self.maxYear = QSpinBox() self.maxYear.setAlignment(Qt.AlignCenter) self.maxYear.setSingleStep(1) self.maxYear.setMinimum(1840) self.maxYear.setMaximum(now.year) self.maxYear.setValue( CONF.get('download_data', 'year_range_right_bound', now.year)) self.maxYear.valueChanged.connect(self.maxYear_changed) subgrid2 = QGridLayout() subgrid2.addWidget(QLabel('between'), 0, 0) subgrid2.addWidget(self.minYear, 0, 1) subgrid2.addWidget(label_and, 0, 2) subgrid2.addWidget(self.maxYear, 0, 3) subgrid2.setContentsMargins(0, 0, 0, 0) subgrid2.setColumnStretch(4, 100) subgrid2.setHorizontalSpacing(5) # Subgridgrid assembly self.year_widg = QGroupBox("Data Availability filter") self.year_widg.setCheckable(True) self.year_widg.setChecked( CONF.get('download_data', 'data_availability_filter', False)) self.year_widg.toggled.connect(self.search_filters_changed) grid = QGridLayout(self.year_widg) grid.setRowMinimumHeight(0, 10) grid.addWidget(QLabel('Search for stations with data available'), 1, 0) grid.addLayout(subgrid1, 2, 0) grid.addLayout(subgrid2, 3, 0) grid.setRowStretch(4, 100) grid.setVerticalSpacing(8) # Setup the toolbar. self.btn_addSta = QPushButton('Add') self.btn_addSta.setIcon(get_icon('add2list')) self.btn_addSta.setIconSize(get_iconsize('small')) self.btn_addSta.setToolTip( 'Add selected stations to the current list of weather stations.') self.btn_addSta.clicked.connect(self.btn_addSta_isClicked) self.btn_addSta.hide() btn_save = QPushButton('Save') btn_save.setToolTip('Save the list of selected stations to a file.') btn_save.clicked.connect(self.btn_save_isClicked) btn_save.hide() self.btn_download = QPushButton('Download') self.btn_download.clicked.connect(self.start_download_process) btn_close = QPushButton('Close') btn_close.clicked.connect(self.close) self.btn_fetch = btn_fetch = QPushButton('Refresh') btn_fetch.setToolTip( "Update the list of climate stations by fetching it from " "the ECCC remote location.") btn_fetch.clicked.connect(self.btn_fetch_isClicked) toolbar_widg = QWidget() toolbar_grid = QGridLayout(toolbar_widg) toolbar_grid.addWidget(self.btn_addSta, 1, 1) toolbar_grid.addWidget(btn_save, 1, 2) toolbar_grid.addWidget(btn_fetch, 1, 3) toolbar_grid.addWidget(self.btn_download, 1, 4) toolbar_grid.addWidget(btn_close, 1, 5) toolbar_grid.setColumnStretch(0, 100) toolbar_grid.setContentsMargins(0, 10, 0, 0) # Setup the left panel. self.left_panel = QFrame() left_panel_grid = QGridLayout(self.left_panel) left_panel_grid.setContentsMargins(0, 0, 0, 0) left_panel_grid.addWidget(QLabel('Search Criteria'), 0, 0) left_panel_grid.addWidget(prov_grpbox, 1, 0) left_panel_grid.addWidget(self.prox_grpbox, 2, 0) left_panel_grid.addWidget(self.year_widg, 3, 0) left_panel_grid.setRowStretch(3, 100) # Setup the progress bar. self.progressbar = QProgressBar() self.progressbar.setValue(0) self.progressbar.hide() # Setup the central widget. main_widget = QWidget() main_layout = QGridLayout(main_widget) main_layout.addWidget(self.left_panel, 0, 0) main_layout.addWidget(self.station_table, 0, 1) main_layout.addWidget(self.waitspinnerbar, 0, 1) main_layout.addWidget(toolbar_widg, 1, 0, 1, 2) main_layout.addWidget(self.progressbar, 2, 0, 1, 2) main_layout.setColumnStretch(1, 100) self.setCentralWidget(main_widget) @property def stationlist(self): return self.station_table.get_stationlist() @property def search_by(self): return ['proximity', 'province'][self.tab_widg.currentIndex()] @property def prov(self): if self.prov_widg.currentIndex() == 0: return self.PROV_NAME else: return [self.PROV_NAME[self.prov_widg.currentIndex() - 1]] @property def lat(self): return self.lat_spinBox.value() def set_lat(self, x, silent=True): if silent: self.lat_spinBox.blockSignals(True) self.lat_spinBox.setValue(x) self.lat_spinBox.blockSignals(False) self.proximity_grpbox_toggled() @property def lon(self): return self.lon_spinBox.value() def set_lon(self, x, silent=True): if silent: self.lon_spinBox.blockSignals(True) self.lon_spinBox.setValue(x) self.lon_spinBox.blockSignals(False) self.proximity_grpbox_toggled() @property def rad(self): return int(self.radius_SpinBox.currentText()[:-3]) @property def prox(self): if self.prox_grpbox.isChecked(): return (self.lat, -self.lon, self.rad) else: return None @property def year_min(self): return int(self.minYear.value()) def set_yearmin(self, x, silent=True): if silent: self.minYear.blockSignals(True) self.minYear.setValue(x) self.minYear.blockSignals(False) @property def year_max(self): return int(self.maxYear.value()) def set_yearmax(self, x, silent=True): if silent: self.maxYear.blockSignals(True) self.maxYear.setValue(x) self.maxYear.blockSignals(False) @property def nbr_of_years(self): return int(self.nbrYear.value()) def set_yearnbr(self, x, silent=True): if silent: self.nbrYear.blockSignals(True) self.nbrYear.setValue(x) self.nbrYear.blockSignals(False) # ---- Load Station Database def start_load_database(self, force_fetch=False): """Start the process of loading the climate station database.""" if self._database_isloading is False: self._database_isloading = True self.station_table.clear() self.waitspinnerbar.show() # Start the downloading process. if force_fetch: self.stn_finder_thread.started.connect( self.stn_finder_worker.fetch_database) else: self.stn_finder_thread.started.connect( self.stn_finder_worker.load_database) self.stn_finder_thread.start() @QSlot() def receive_load_database(self): """Handles when loading the database is finished.""" # Disconnect the thread. self.stn_finder_thread.started.disconnect() # Quit the thread. self.stn_finder_thread.quit() waittime = 0 while self.stn_finder_thread.isRunning(): sleep(0.1) waittime += 0.1 if waittime > 15: print("Unable to quit the thread.") break # Force an update of the GUI. self.proximity_grpbox_toggled() if self.stn_finder_worker.data is None: self.waitspinnerbar.show_warning_icon() else: self.waitspinnerbar.hide() self._database_isloading = False # ---- GUI handlers def minYear_changed(self): min_yr = min_yr = max(self.minYear.value(), 1840) now = datetime.now() max_yr = now.year self.maxYear.setRange(min_yr, max_yr) self.search_filters_changed() def maxYear_changed(self): min_yr = 1840 now = datetime.now() max_yr = min(self.maxYear.value(), now.year) self.minYear.setRange(min_yr, max_yr) self.search_filters_changed() # ---- Toolbar Buttons Handlers def btn_save_isClicked(self): ddir = os.path.join(os.getcwd(), 'weather_station_list.csv') filename, ftype = QFileDialog().getSaveFileName( self, 'Save normals', ddir, '*.csv;;*.xlsx;;*.xls') self.station_table.save_stationlist(filename) def btn_addSta_isClicked(self): rows = self.station_table.get_checked_rows() if len(rows) > 0: staList = self.station_table.get_content4rows(rows) self.staListSignal.emit(staList) print('Selected stations sent to list') else: print('No station currently selected') def btn_fetch_isClicked(self): """Handles when the button fetch is clicked.""" self.start_load_database(force_fetch=True) # ---- Search Filters Handlers def proximity_grpbox_toggled(self): """ Set the values for the reference geo coordinates that are used in the WeatherSationView to calculate the proximity values and forces a refresh of the content of the table. """ if self.prox_grpbox.isChecked(): self.station_table.set_geocoord((self.lat, -self.lon)) else: self.station_table.set_geocoord(None) self.search_filters_changed() def search_filters_changed(self): """ Search for weather stations with the current filter values and forces an update of the station table content. """ if self.stn_finder_worker.data is not None: stnlist = self.stn_finder_worker.get_stationlist( prov=self.prov, prox=self.prox, yrange=((self.year_min, self.year_max, self.nbr_of_years) if self.year_widg.isChecked() else None)) self.station_table.populate_table(stnlist) # ---- Download weather data def start_download_process(self): """Start the downloading process of raw weather data files.""" if self._dwnld_inprogress is True: self.stop_download_process() return # Grab the info of the weather stations that are selected. rows = self.station_table.get_checked_rows() self._dwnld_stations_list = self.station_table.get_content4rows(rows) if len(self._dwnld_stations_list) == 0: QMessageBox.warning(self, 'Warning', "No weather station currently selected.", QMessageBox.Ok) return # Update the UI. self.progressbar.show() self.btn_download.setText("Cancel") self.left_panel.setEnabled(False) self.station_table.setEnabled(False) self.btn_fetch.setEnabled(False) # Set thread working directory. self.dwnld_worker.dirname = self.workdir # Start downloading data. self._dwnld_inprogress = True self.download_next_station() def stop_download_process(self): """Stop the downloading process.""" print('Stopping the download process...') self.dwnld_worker.stop_download() self._dwnld_stations_list = [] self.btn_download.setEnabled(False) self.wait_for_thread_to_quit() self.btn_download.setEnabled(True) self.btn_download.setText("Download") self.left_panel.setEnabled(True) self.station_table.setEnabled(True) self.btn_fetch.setEnabled(True) self._dwnld_inprogress = False self.sig_download_process_ended.emit() print('Download process stopped.') def download_next_station(self): self.wait_for_thread_to_quit() try: dwnld_station = self._dwnld_stations_list.pop(0) except IndexError: # There is no more data to download. print('Raw weather data downloaded for all selected stations.') self.btn_download.setText("Download") self.progressbar.hide() self.left_panel.setEnabled(True) self.station_table.setEnabled(True) self.btn_fetch.setEnabled(True) self._dwnld_inprogress = False self.sig_download_process_ended.emit() return # Set worker attributes. self.dwnld_worker.StaName = dwnld_station[0] self.dwnld_worker.stationID = dwnld_station[1] self.dwnld_worker.yr_start = dwnld_station[2] self.dwnld_worker.yr_end = dwnld_station[3] self.dwnld_worker.climateID = dwnld_station[5] # Highlight the row of the next station to download data from. self.station_table.selectRow( self.station_table.get_row_from_climateid(dwnld_station[5])) # Start the downloading process. try: self.dwnld_thread.started.disconnect( self.dwnld_worker.download_data) except TypeError: # The method self.dwnld_worker.download_data is not connected. pass finally: self.dwnld_thread.started.connect(self.dwnld_worker.download_data) self.dwnld_thread.start() def wait_for_thread_to_quit(self): self.dwnld_thread.quit() waittime = 0 while self.dwnld_thread.isRunning(): print('Waiting for the downloading thread to close') sleep(0.1) waittime += 0.1 if waittime > 15: print("Unable to close the weather data dowloader thread.") return def process_station_data(self, climateid, file_list=None): """ Read, concatenate, and save to csv the raw weather data that were just downloaded for the station corresponding to the specified climate ID. """ if file_list: station_metadata = self.station_table.get_content4rows( [self.station_table.get_row_from_climateid(climateid)])[0] station_data = read_raw_datafiles(file_list) print( 'Formating and concatenating raw data for station {}.'.format( station_metadata[0])) # Define the concatenated filename. station_name = (station_metadata[0].replace('\\', '_').replace('/', '_')) min_year = min(station_data.index).year max_year = max(station_data.index).year filename = osp.join("%s (%s)_%s-%s.csv" % (station_name, climateid, min_year, max_year)) # Save the concatenated data to csv. data_headers = [ 'Year', 'Month', 'Day', 'Max Temp (°C)', 'Min Temp (°C)', 'Mean Temp (°C)', 'Total Precip (mm)' ] fcontent = [['Station Name', station_metadata[0]], ['Province', station_metadata[4]], ['Latitude (dd)', station_metadata[6]], ['Longitude (dd)', station_metadata[7]], ['Elevation (m)', station_metadata[8]], ['Climate Identifier', station_metadata[5]], [], data_headers] fcontent = fcontent + station_data[data_headers].values.tolist() # Save the data to csv. filepath = osp.join(self.dwnld_worker.dirname, filename) with open(filepath, 'w', encoding='utf-8') as f: writer = csv.writer(f, delimiter=',', lineterminator='\n') writer.writerows(fcontent) self.download_next_station() # ---- Main window settings def _restore_window_geometry(self): """ Restore the geometry of this mainwindow from the value saved in the config. """ hexstate = CONF.get('download_data', 'window/geometry', None) if hexstate: hexstate = hexstate_to_qbytearray(hexstate) self.restoreGeometry(hexstate) else: self.resize(1000, 450) def _save_window_geometry(self): """ Save the geometry of this mainwindow to the config. """ hexstate = qbytearray_to_hexstate(self.saveGeometry()) CONF.set('download_data', 'window/geometry', hexstate) # ---- Qt overrides def closeEvent(self, event): self._save_window_geometry() # Proximity Filter Options. CONF.set('download_data', 'proximity_filter', self.prox_grpbox.isChecked()) CONF.set('download_data', 'latitude', self.lat) CONF.set('download_data', 'longitude', self.lon) CONF.set('download_data', 'radius_index', self.radius_SpinBox.currentIndex()) CONF.set('download_data', 'province_index', self.prov_widg.currentIndex()) # Data Availability Filter Options. CONF.set('download_data', 'data_availability_filter', self.year_widg.isChecked()) CONF.set('download_data', 'min_nbr_of_years', self.nbrYear.value()) CONF.set('download_data', 'year_range_left_bound', self.minYear.value()) CONF.set('download_data', 'year_range_right_bound', self.maxYear.value()) event.accept()
class Main(CenterWidget): ''' Class of first windon that appears. ''' def __init__(self, app, backend): super().__init__() self.app = app self.backend = backend self.refresher = None # Creates GUI of main Window self.setGeometry(300, 300, 550, 450) # X, Y, width , height self.setWindowTitle('DeepAmazom') self.create_GuiButtonGrid() self.create_GuiTop() self.create_GuiMid() self.create_GuiBottom() # Creates the Actions that connect the backend with # the frontend, and binds them to GUI elements self.connect_Actions() self.setEnabledStates(False) self.show() # ======== Below the four functions that ======== # ======== create the GUI are defined ======== def create_GuiButtonGrid(self): ''' Sets distances of elements that appear in GuiTop and GuiMid ''' # vertical distance from top for: self.v_lab = 15 # label self.v_btn = 35 # buttons self.v_com = 31 # Comboboxes # hor. distance from left beginning for: self.h_btn1 = 15 # 1st button self.h_com1 = self.h_btn1 + 90 # 1st combobox self.h_btn2 = self.h_com1 + 150 # 2nd button self.h_com2 = self.h_btn2 + 80 # 2nd combobox self.h_btn3 = self.h_com2 + 150 # 3rd button def create_GuiTop(self): ''' Creates top container that contains the buttons and options necessary to log in to AWS account with AWS credentials. It also creates the corresponding buttons, labels, etc... ''' # Container on top self.GuiTop = QFrame(self) self.GuiTop.resize(550, 75) self.GuiTop.setStyleSheet(""" QFrame{ background-color: rgb(219, 219, 219); border: 0px solid rgb(100, 100, 100); border-bottom-width: 0.5px; }""") # Connect button and label self.GuiConnectLabel = LabelOfButton('Connect', self.GuiTop) self.GuiConnectLabel.move(self.h_btn1, self.v_lab) self.GuiConnect = ButtonIcon('aws-connect.png', self.GuiTop, [55, 18, 50, 13]) self.GuiConnect.move(self.h_btn1, self.v_btn) self.GuiConnect.setToolTip('Connect with AWS Credentials') # Access Key, label, drop down menu, and actions button self.GuiAccessKeyLabel = LabelOfButton('Access Keys', self.GuiTop) self.GuiAccessKeyLabel.move(self.h_com1 + 15, self.v_lab) self.GuiAccessKey = ComboBoxWithUpdate(self.GuiTop, self.backend.user_data.profile) self.GuiAccessKey.move(self.h_com1, self.v_com) self.GuiAccessKey.resize(130, 30) self.GuiAccessKey.setToolTip('Access Key used to connect with AWS') self.GuiAccessKeyBtn = ButtonIconMenu('gear.png', self.GuiTop) self.GuiAccessKeyBtn.setBtnIconSize(40, 20, 35, 16) self.GuiAccessKeyBtn.move(self.h_btn2, self.v_btn) self.GuiAccessKeyBtn.setToolTip('Add or delete Access Key Pairs.') # Region drop down menu and label self.GuiRegionsLabel = LabelOfButton('Regions', self.GuiTop) self.GuiRegionsLabel.move(self.h_com2 + 15, self.v_lab) self.GuiRegions = ComboBoxWithUpdate( self.GuiTop, self.backend.user_data.ec2_regions, self.backend.user_data.default_region) self.GuiRegions.move(self.h_com2, self.v_com) self.GuiRegions.resize(130, 30) self.GuiRegions.setToolTip('Region with which the connection opens.') # Help button and label self.GuiHelp = ButtonIcon('questionmark.png', self.GuiTop, [35, 20, 30, 15]) self.GuiHelp.move(self.h_btn3, self.v_btn) self.GuiHelp.setToolTip('Help Menu') def create_GuiMid(self): ''' Creates container with information on existing key pairs and security groups. ''' self.GuiMid = QFrame(self) self.GuiMid.move(0, 75) self.GuiMid.resize(550, 75) self.GuiMid.setStyleSheet(""" QFrame{ background-color: rgba(235, 240, 243, 1); border: 0px solid rgb(100, 100, 100); }""") # Disconnect button and label self.GuiDisconnectLabel = LabelOfButton('Disconnect', self.GuiMid) self.GuiDisconnectLabel.move(self.h_btn1, self.v_lab) self.GuiDisconnect = ButtonIcon('aws-connect.png', self.GuiMid, [55, 18, 50, 13]) self.GuiDisconnect.move(self.h_btn1, self.v_btn) self.GuiDisconnect.setToolTip('Disconnect from session.') # Key Pairs drop down menu and label self.GuiKeyPairLabel = LabelOfButton('Key Pairs', self.GuiMid) self.GuiKeyPairLabel.move(self.h_com1 + 15, self.v_lab) self.GuiKeyPair = ComboBoxWithUpdate(self.GuiMid, self.backend.key_pairs) self.GuiKeyPair.move(self.h_com1, self.v_com) self.GuiKeyPair.resize(130, 30) self.GuiKeyPair.setToolTip('Key Pairs used with AWS instances') self.GuiKeyPairBtn = ButtonIconMenu('gear.png', self.GuiMid) self.GuiKeyPairBtn.setBtnIconSize(40, 20, 35, 16) self.GuiKeyPairBtn.move(self.h_btn2, self.v_btn) self.GuiKeyPairBtn.setToolTip( 'Create or delete a Key Pair to connect with an instance') # Security Groups drop down menu and label self.GuiSecurityLabel = LabelOfButton('Security Groups', self.GuiMid) self.GuiSecurityLabel.move(self.h_com2 + 15, self.v_lab) self.GuiSecurity = ComboBoxWithUpdate(self.GuiMid, self.backend.security_groups) self.GuiSecurity.move(self.h_com2, self.v_com) self.GuiSecurity.resize(130, 30) self.GuiSecurity.setToolTip('Access Key used to connect with AWS') self.GuiSecurityBtn = ButtonIconMenu('gear.png', self.GuiMid) self.GuiSecurityBtn.setBtnIconSize(40, 20, 35, 16) self.GuiSecurityBtn.move(self.h_btn3, self.v_btn) self.GuiSecurityBtn.setToolTip('Create or delete a Security Group') def create_GuiBottom(self): ''' Creates container with options to launch, stop instance, .., list of instances running, and information on each instances ''' self.GuiBottom = QFrame(self) self.GuiBottom.move(0, 150) self.GuiBottom.resize(550, 280) self.GuiBottom.setStyleSheet(""" QFrame{ background-color: rgba(235, 240, 243, 1); border: 0.5px solid rgb(100, 100, 100); }""") v_dist_btn = 25 # vert dist of btns from top of frame # Launch Instance button self.GuiInstanceLaunch = RectButton('Launch Instance', self.GuiBottom) self.GuiInstanceLaunch.move(15, v_dist_btn) self.GuiInstanceLaunch.resize(110, 30) self.GuiInstanceLaunch.setToolTip('Launch new instance') # Stop, terminate button self.GuiInstanceActions = ButtonMenu('Actions', self.GuiBottom) self.GuiInstanceActions.move(145, v_dist_btn) self.GuiInstanceActions.resize(110, 30) self.GuiInstanceActions.setToolTip( 'Start, stop, or terminate an instance') # Drop down menu which can be used to choose what info # appear for the running instances. self.GuiInstanceView = ButtonMenu('View Options', self.GuiBottom) self.GuiInstanceView.move(275, v_dist_btn) self.GuiInstanceView.resize(110, 30) self.GuiInstanceView.setToolTip( 'Choose columns that appear on the table of the running instances') # Connection Report Label with icon self.GuiStateReport = StateReport(self.GuiBottom) self.GuiStateReport.move(430, v_dist_btn - 17) # Instances Table labels = list(self.backend.user_data.InstanceView.keys()) self.GuiInstances = InstanceTable(self.GuiBottom, labels) self.GuiInstances.setStyleSheet(""" QTableWidget{ background-color: rgb(250, 250, 250); }""") self.GuiInstances.move(0, 80) self.GuiInstances.resize(550, 200) # ======== Below Actions are defined and binded ======== # ======== to the GUI objects of main window ======== def connect_Actions(self): ''' Here the action that are defined in the next def's are connected to the GUI. ''' # Actions of GuiTop: # GuiConnect Button self.GuiConnect.clicked.connect(self.ActConnect) # GuiAccessKey Button self.ActAccessKeyBtnAdd = QAction('Add', self) self.ActAccessKeyBtnAdd.triggered.connect(self.ActAccessKeyWinAdd) self.ActAccessKeyBtnDelete = QAction('Delete', self) self.ActAccessKeyBtnDelete.triggered.connect( self.ActAccessKeyWinDelete) self.GuiAccessKeyBtn.bindMenu( [self.ActAccessKeyBtnAdd, self.ActAccessKeyBtnDelete]) # GuiHelp Button self.GuiHelp.clicked.connect(self.ActHelp) # Actions of GuiMid: # GuiDisconnect Button self.GuiDisconnect.clicked.connect(self.ActDisconnect) # GuiKeyPair Button self.ActKeyPairBtnAdd = QAction('Add', self) self.ActKeyPairBtnAdd.triggered.connect(self.ActKeyPairCreate) self.ActKeyPairBtnDelete = QAction('Delete', self) self.ActKeyPairBtnDelete.triggered.connect(self.ActKeyPairDelete) self.GuiKeyPairBtn.bindMenu( [self.ActKeyPairBtnAdd, self.ActKeyPairBtnDelete]) # GuiSecurity Button self.ActSecurityBtnCreate = QAction('Create', self) self.ActSecurityBtnCreate.triggered.connect(self.ActSecurityCreate) self.ActSecurityBtnDelete = QAction('Delete', self) self.ActSecurityBtnDelete.triggered.connect(self.ActSecurityDelete) self.GuiSecurityBtn.bindMenu( [self.ActSecurityBtnCreate, self.ActSecurityBtnDelete]) # Actions of GuiBottom: # GuiInstanceView Button self.ActInstanceViewCreateMenu() self.GuiInstanceView.bindMenu(self.ActInstanceView) # GuiInstanceActions Button self.ActInstanceActionsCreateMenu() self.GuiInstanceActions.bindMenu(self.ActInstanceActions) # GuiInstanceLaunch Button self.GuiInstanceLaunch.clicked.connect(self.ActInstanceLaunch) # Defines Refresher that periodically updates GuiInstances # and the corresponding backend object. self.refresher = MainRefresher(1, self) # ==================== GuiTop Actions ==================== # ======================================================== def ActConnect(self): ''' Triggered by: GuiConnect 1) Checks using the backend if connection is possible 2) Reports error, and resets enabled states of buttons ''' error = self.backend.connect(self.GuiAccessKey.currentText(), self.GuiRegions.currentText()) if error == 'NoAccessKey': error_msg = 'Access Key has not been provided.' QMessageBox.information(self, 'Error', error_msg, QMessageBox.Ok) elif error == 'WrongCredentials': error_msg = 'Could not match name with credentials. Possible bug.' QMessageBox.information(self, 'Error', error_msg, QMessageBox.Ok) elif error == 'ConnectionError': error_msg = 'Not able to establish connection.' QMessageBox.information(self, 'Error', error_msg, QMessageBox.Ok) elif error == 'DescribeError': msg1 = 'A connection was successfully established, but' msg2 = ' there was an error while requesting data from AWS.' msg3 = '\n\nPossible bug because of an update on' msg4 = ' a describe_someitem() method.' error_msg = msg1 + msg2 + msg3 + msg4 QMessageBox.information(self, 'Error', error_msg, QMessageBox.Ok) else: self.backend.user_data.default_region = self.GuiRegions.currentText( ) self.setEnabledStates(True) self.GuiKeyPair.updateList(self.backend.key_pairs) self.GuiSecurity.updateList(self.backend.security_groups) self.backend.user_data.get_AmiNames(self.backend.client) self.refresher.on() def ActHelp(self): ''' Triggered by: GuiHelp 1) Popups a window with some basic instructions on how to use the Main window. ''' a1 = 'This application provides a Graphic Interface in order to' a2 = ' use some common features of boto3. Some knowledge of' a3 = ' boto3 is necessary in order to understand what each' a4 = ' of the GUI is doing.' A = a1 + a2 + a3 + a4 + '\n\n' b1 = 'Access Keys: Those can be obtained from AWS once you logged' b2 = ' in. When adding a Key you are also requested to select a' b3 = ' name for it.' B = b1 + b2 + b3 + '\n\n' C = '...To be written.' text = A + B + C QMessageBox.information(self, 'Help', text, QMessageBox.Ok) def ActAccessKeyWinAdd(self): ''' Triggered by: GuiAccessKey --> Add 1) Opens Window that allows user to Add new Access Key. 2) Using Backend it ensures that this is valid. ''' x = self.mapToGlobal(QPoint(0, 0)).x() y = self.mapToGlobal(QPoint(0, 0)).y() GuiAccessKeyWinAdd = AccessKeyAddWindow(x, y, self.backend) if GuiAccessKeyWinAdd.Added: self.GuiAccessKey.updateList(self.backend.user_data.profile) del GuiAccessKeyWinAdd def ActAccessKeyWinDelete(self): ''' Triggered by: GuiAccessKey --> Delete 1) Opens message box to confirm delete of Access Key selected on GuiAccessKey (QCombobox). 2) Deletes it from both GuiAccessKey and Backend. ''' if len(self.backend.user_data.profile) == 0: error_msg = 'There is no Access Key to Delete.' QMessageBox.information(self, 'Error', error_msg, QMessageBox.Ok) else: msg = 'Are you sure you want to delete the selected Access Key?' buttonReply = QMessageBox.question( self, 'Warning', msg, QMessageBox.Yes | QMessageBox.No) if buttonReply == QMessageBox.Yes: self.backend.user_data.delete_profile( self.GuiAccessKey.currentText()) self.GuiAccessKey.updateList(self.backend.user_data.profile) # ==================== GuiMid Actions ==================== # ======================================================== def ActDisconnect(self): ''' Triggered by: GuiDisconnect 1) Pops up window for the user to confirm she wants to log out. 1) In the backend it deletes the session used to communicate with AWS. 2) In the frontend it disable and empties GuiMid and GuiBottom. 3) ATTENTION: It leaves any AWS instances running as they are. ''' msg1 = 'This will log you out, but it will not' msg2 = ' affect any AWS instances currently running.' msg3 = '\n\nAre you sure you want to log out?' msg = msg1 + msg2 + msg3 buttonReply = QMessageBox.question(self, 'Warning', msg, QMessageBox.Yes | QMessageBox.No) if buttonReply == QMessageBox.Yes: self.refresher.off() self.backend.disconnect() self.GuiKeyPair.clear() self.GuiSecurity.clear() self.GuiInstances.totalClear() self.setEnabledStates(False) def ActKeyPairCreate(self): ''' Triggered by: GuiKeyPair --> Create 1) Popups window to take name of new key pair 2) If no repetition, creates it, stores it in corresponding directory defined in Backend, and updates GuiKeyPair. ''' name, okPressed = QInputDialog.getText( self, '', 'Key Pair Name (without .pem):', QLineEdit.Normal, '') if okPressed: response = self.backend.create_key_pair(name) if response == 'NoName': error_msg = 'Provide name for the new Key Pair.' QMessageBox.information(self, 'Error', error_msg, QMessageBox.Ok) elif response == 'NameDuplicate': error_msg = 'A Key Pair with the same name already exists.' QMessageBox.information(self, 'Error', error_msg, QMessageBox.Ok) elif response == 'Error': error_msg = 'Unexpected Error. Possible bug.' QMessageBox.information(self, 'Error', error_msg, QMessageBox.Ok) else: self.GuiKeyPair.updateList(self.backend.key_pairs) def ActKeyPairDelete(self): ''' Triggered by: GuiKeyPair --> Delete 1) Opens message box to confirm delete of Access Key selected on GuiKeyPair (QCombobox). 2) Deletes it from both GuiAccessKey and Backend. ''' if len(self.backend.key_pairs) == 0: error_msg = 'There is no Key Pair to Delete.' QMessageBox.information(self, 'Error', error_msg, QMessageBox.Ok) else: msg1 = 'This will delete the Key Pair from both your computer' msg2 = ' and the AWS server.' msg3 = 'Are you sure you want to proceed?' msg = msg1 + msg2 + msg3 buttonReply = QMessageBox.question( self, 'Warning', msg, QMessageBox.Yes | QMessageBox.No) if buttonReply == QMessageBox.Yes: self.backend.delete_key_pair(self.GuiKeyPair.currentText()) self.GuiKeyPair.updateList(self.backend.key_pairs) def ActSecurityCreate(self): ''' Triggered by: GuiSecurity --> Create 1) Popups window to take Description, Name, and VPC of new security group. 2) If inputs are valid creates it, and updates both GuiSecurity and the Backend. ''' x = self.mapToGlobal(QPoint(0, 0)).x() y = self.mapToGlobal(QPoint(0, 0)).y() GuiSecurityGroupCreate = SecurityGroupCreateWindow(x, y, self.backend) if GuiSecurityGroupCreate.Created: self.GuiSecurity.updateList(self.backend.security_groups) del GuiSecurityGroupCreate def ActSecurityDelete(self): ''' Triggered by: GuiSecurity --> Delete 1) Opens message box to confirm delete of Security Group selected on GuiSecurity (QCombobox). 2) Deletes it from both GuiSecurity and Backend. ''' if len(self.backend.security_groups) == 0: error_msg = 'There is no Security Group to Delete.' QMessageBox.information(self, 'Error', error_msg, QMessageBox.Ok) elif self.GuiSecurity.currentText() == 'default': error_msg = 'Default Group cannot be deleted.' QMessageBox.information(self, 'Error', error_msg, QMessageBox.Ok) elif self.GuiSecurity.currentText() == 'Recommended': error_msg = 'Recommended option cannot be deleted.' QMessageBox.information(self, 'Error', error_msg, QMessageBox.Ok) else: msg1 = 'This will delete the Security Group from' msg2 = ' the AWS server. Are you sure you want to proceed?' msg = msg1 + msg2 buttonReply = QMessageBox.question( self, 'Warning', msg, QMessageBox.Yes | QMessageBox.No) if buttonReply == QMessageBox.Yes: self.backend.delete_security_group( self.GuiSecurity.currentText()) self.GuiSecurity.updateList(self.backend.security_groups) # ================= GuiBottom Actions ==================== # ======================================================== def ActInstanceViewCreateMenu(self): ''' Used by: GuiInstanceView, GuiInstance 1) Creates a Menu with the actions that will go into GuiInstanceView 2) 'SelectAll' and 'DeselectAll' are used to perform the two actions on both the frontend Menu (defined here), and the corresponding Beckend list. 3) All other actions of the menu correspond to an element of Backend.user_data.InstanceView. 4) Those (un)check the element of the frontend menu, update backend list, and call ActInstanceViewUpdate to update GuiInstance (QTableWidget) ''' backendDict = self.backend.user_data.InstanceView Keys = list(backendDict.keys()) self.ActInstanceView = {} # Adds special buttons to select or deselect all self.ActInstanceView['SelectAll'] = QAction('(Select All)', self, checkable=False) self.ActInstanceView['SelectAll'].triggered.connect( self.ActInstanceViewSelectAll) self.ActInstanceView['DeselectAll'] = QAction('(Deselect All)', self, checkable=False) self.ActInstanceView['DeselectAll'].triggered.connect( self.ActInstanceViewDeselectAll) for i in Keys: # Defines the action self.ActInstanceView[i] = QAction(i, self, checkable=True) # Sets initial check of the action using backend.user_data self.ActInstanceView[i].setChecked( self.backend.user_data.InstanceView[i]) # Connects the action so that when clicked it updates # backend.user_data.InstanceView and the collumns shown in # GuiInstaces self.ActInstanceView[i].triggered.connect( self.ActInstanceViewUpdate) def ActInstanceViewUpdate(self): ''' Triggered by: GuiInstanceView --> AttributeAction 1) Updates the Backend Attribute to be on the same state (True, False) with that of the fronend menu. 2) Refreshes GuiInstance (QTableWidget). ''' backendDict = self.backend.user_data.InstanceView Keys = list(backendDict.keys()) for i in Keys: self.backend.user_data.InstanceView[i] \ = self.ActInstanceView[i].isChecked() self.backend.user_data.save() self.GuiInstances.refresh(self.backend.user_data.InstanceView, self.backend.instancesData) def ActInstanceViewSelectAll(self): ''' Triggered by: GuiInstanceView --> SelectAll 1) Selects all Action in the menu of GuiInstanceView end updates the corresponding Backend list. ''' backendDict = self.backend.user_data.InstanceView Keys = list(backendDict.keys()) for i in Keys: self.backend.user_data.InstanceView[i] = True self.ActInstanceView[i].setChecked(True) self.backend.user_data.save() self.GuiInstances.refresh(self.backend.user_data.InstanceView, self.backend.instancesData) def ActInstanceViewDeselectAll(self): ''' Triggered by: GuiInstanceView --> DeselectAll 1) Deselects all Action in the menu of GuiInstanceView end updates the corresponding Backend list. ''' backendDict = self.backend.user_data.InstanceView Keys = list(backendDict.keys()) for i in Keys: self.backend.user_data.InstanceView[i] = False self.ActInstanceView[i].setChecked(False) self.backend.user_data.save() self.GuiInstances.refresh(self.backend.user_data.InstanceView, self.backend.instancesData) def ActInstanceActionsCreateMenu(self): ''' Used by: GuiInstanceActions 1) Creates a Menu with the actions that will go into GuiInstanceView. 2) Those, stop, reboot, and terminate an instances if it is selected from GuiInstances (QTableWidget). ''' self.ActInstanceActions = {} self.ActInstanceActions['Start'] = QAction('Start', self, checkable=False) self.ActInstanceActions['Start'].triggered.connect( self.ActInstanceStart) self.ActInstanceActions['Stop'] = QAction('Stop', self, checkable=False) self.ActInstanceActions['Stop'].triggered.connect(self.ActInstanceStop) self.ActInstanceActions['Reboot'] = QAction('Reboot', self, checkable=False) self.ActInstanceActions['Reboot'].triggered.connect( self.ActInstanceReboot) self.ActInstanceActions['Terminate'] = QAction('Terminate', self, checkable=False) self.ActInstanceActions['Terminate'].triggered.connect( self.ActInstanceTerminate) self.ActInstanceActions['More'] = QAction('More', self, checkable=False) self.ActInstanceActions['More'].triggered.connect(self.ActInstanceMore) def ActInstanceMore(self): ''' Triggered by: GuiInstanceActions --> More 1) If a row with a running instance is selected on GuiInstances (QTableWidget), then it pops up a box with information on how to connect to this instance through the terminal, or other. 2) Otherwise, provides appropriate messages to user. ''' # Obtains indexes of the subset of selected rows that are running sr = self.GuiInstances.selectedRows() srd = self.GuiInstances.RowsFilter('InstanceState', 'running', sr) # First checks if instance is selected. if srd == []: msg = 'No running AWS Instance selected.' QMessageBox.information(self, 'Error', msg, QMessageBox.Ok) # Then if more than one running instance has been selected elif len(srd) > 1: msg = 'Select a single AWS Instance.' QMessageBox.information(self, 'Error', msg, QMessageBox.Ok) else: msg = self.backend.get_more_info(srd[0]) QMessageBox.information(self, 'More Information', msg, QMessageBox.Ok) def ActInstanceStart(self): ''' Triggered by: GuiInstanceActions --> Start 1) If a row with a stopped instance is selected on GuiInstances (QTableWidget), then it requests from AWS to start it. 2) If successful, updates backend list and GuiInstances. 3) Otherwise, provides appropriate messages to user. ''' # Obtains indexes of selected rows and the subset of those # that are stopped. sr = self.GuiInstances.selectedRows() srd = self.GuiInstances.RowsFilter('InstanceState', 'stopped', sr) # First checks if instance is selected. if sr == []: msg = 'No AWS Instance selected.' QMessageBox.information(self, 'Error', msg, QMessageBox.Ok) # Then if the two groups are the same elif sr != srd: if len(sr) == 1: msg = 'Only a stopped AWS Instance can be started.' else: msg1 = 'Your selection includes AWS Instances that are not' msg2 = ' stopped.\n\nOnly a stopped AWS Instance can be started.' msg = msg1 + msg2 QMessageBox.information(self, 'Error', msg, QMessageBox.Ok) else: error = self.backend.act_instances(sr, 'start') if error == 'IndexDoesNotExist': msg1 = 'Could not match frontend and backend indexes.' msg2 = ' Potential bug.' msg = msg1 + msg2 elif error == 'ConnectionError': self.get_instances() self.GuiInstances.refresh(self.backend.user_data.InstanceView) msg1 = 'Error while communicating with AWS. An attempt' msg2 = ' to update Table with AWS Instances was made.' msg3 = ' To be on the safe side also check using the' msg4 = ' AWS website.' msg = msg1 + msg2 + msg3 + msg4 QMessageBox.information(self, 'Error', msg, QMessageBox.Ok) else: self.backend.get_instances() self.GuiInstances.refresh(self.backend.user_data.InstanceView, self.backend.instancesData) def ActInstanceStop(self): ''' Triggered by: GuiInstanceActions --> Stop 1) If a row with a running instance is selected on GuiInstances (QTableWidget), then it requests from AWS to stop it. 2) If successful, updates backend list and GuiInstances. 3) Otherwise, provides appropriate messages to user. ''' # Obtains indexes of selected rows and the subset of those # that are running. sr = self.GuiInstances.selectedRows() srd = self.GuiInstances.RowsFilter('InstanceState', 'running', sr) # First checks if instance is selected. if sr == []: msg = 'No AWS Instance selected.' QMessageBox.information(self, 'Error', msg, QMessageBox.Ok) # Then if the two groups are the same elif sr != srd: if len(sr) == 1: msg = 'Only a running AWS Instance can be stopped.' else: msg1 = 'Your selection includes AWS Instances that are not' msg2 = ' running.\n\nOnly a running AWS Instance can be stopped.' msg = msg1 + msg2 QMessageBox.information(self, 'Error', msg, QMessageBox.Ok) else: error = self.backend.act_instances(sr, 'stop') if error == 'IndexDoesNotExist': msg1 = 'Could not match frontend and backend indexes.' msg2 = ' Potential bug.' msg = msg1 + msg2 elif error == 'ConnectionError': self.get_instances() self.GuiInstances.refresh(self.backend.user_data.InstanceView) msg1 = 'Error while communicating with AWS. An attempt' msg2 = ' to update Table with AWS Instances was made.' msg3 = ' To be on the safe side also check using the' msg4 = ' AWS website.' msg = msg1 + msg2 + msg3 + msg4 QMessageBox.information(self, 'Error', msg, QMessageBox.Ok) else: self.backend.get_instances() self.GuiInstances.refresh(self.backend.user_data.InstanceView, self.backend.instancesData) def ActInstanceReboot(self): ''' Triggered by: GuiInstanceActions --> Reboot 1) If a row with a running instance is selected on GuiInstances (QTableWidget), then it requests from AWS to reboot it. 2) If successful, updates backend list and GuiInstances. 3) Otherwise, provides appropriate messages to user. ''' # Obtains indexes of selected rows and the subset of those # that are stopped. sr = self.GuiInstances.selectedRows() srd = self.GuiInstances.RowsFilter('InstanceState', 'running', sr) # First checks if instance is selected. if sr == []: msg = 'No AWS Instance selected.' QMessageBox.information(self, 'Error', msg, QMessageBox.Ok) # Then if the two groups are the same elif sr != srd: if len(sr) == 1: msg = 'Only a running AWS Instance can be rebooted.' else: msg1 = 'Your selection includes AWS Instances that are not' msg2 = ' running.\n\nOnly a running AWS Instance can be rebooted.' msg = msg1 + msg2 QMessageBox.information(self, 'Error', msg, QMessageBox.Ok) else: error = self.backend.act_instances(sr, 'reboot') if error == 'IndexDoesNotExist': msg1 = 'Could not match frontend and backend indexes.' msg2 = ' Potential bug.' msg = msg1 + msg2 elif error == 'ConnectionError': self.get_instances() self.GuiInstances.refresh(self.backend.user_data.InstanceView) msg1 = 'Error while communicating with AWS. An attempt' msg2 = ' to update Table with AWS Instances was made.' msg3 = ' To be on the safe side also check using the' msg4 = ' AWS website.' msg = msg1 + msg2 + msg3 + msg4 QMessageBox.information(self, 'Error', msg, QMessageBox.Ok) else: self.backend.get_instances() self.GuiInstances.refresh(self.backend.user_data.InstanceView, self.backend.instancesData) def ActInstanceTerminate(self): ''' Triggered by: GuiInstanceActions --> Terminate 1) If a row with a running or stopped instance is selected on GuiInstances (QTableWidget), then it requests from AWS to terminate it. 2) If successful, updates backend list and GuiInstances. 3) Otherwise, provides appropriate messages to user. ''' # Obtains indexes of selected rows and the subset of those # that are stopped or running. sr = self.GuiInstances.selectedRows() srd = self.GuiInstances.RowsFilter('InstanceState', ['running', 'stopped'], sr) # First checks if instance is selected. if sr == []: msg = 'No AWS Instance selected.' QMessageBox.information(self, 'Error', msg, QMessageBox.Ok) # Then if the two groups are the same elif sr != srd: if len(sr) == 1: msg = 'Only a running or stopped AWS Instance can be terminated.' else: msg1 = 'Your selection includes AWS Instances that are either' msg2 = ' not running, or are not stopped.' msg3 = '\n\nOnly a running or stopped AWS Instance can be' msg4 = ' terminated.' msg = msg1 + msg2 + msg3 + msg4 QMessageBox.information(self, 'Error', msg, QMessageBox.Ok) else: error = self.backend.act_instances(sr, 'terminate') if error == 'IndexDoesNotExist': msg1 = 'Could not match frontend and backend indexes.' msg2 = ' Potential bug.' msg = msg1 + msg2 elif error == 'ConnectionError': self.get_instances() self.GuiInstances.refresh(self.backend.user_data.InstanceView) msg1 = 'Error while communicating with AWS. An attempt' msg2 = ' to update Table with AWS Instances was made.' msg3 = ' To be on the safe side also check using the' msg4 = ' AWS website.' msg = msg1 + msg2 + msg3 + msg4 QMessageBox.information(self, 'Error', msg, QMessageBox.Ok) else: self.backend.get_instances() self.GuiInstances.refresh(self.backend.user_data.InstanceView, self.backend.instancesData) def ActInstanceLaunch(self): ''' Triggered by: GuiInstanceLaunch 1) Pops up window for the user to provide details on the AWS instance she want to start. 2) Launches the AWS instance and updates Instances (QTableWidget). ''' GuiInstanceLaunchWindow = InstanceLaunchWindow(self) def setEnabledStates(self, logged_in): ''' Part of ActConnect: triggered by: GuiConnect 1) Takes logged_in (True, False) 2) If False allows only the buttons of GuiTop to be manipulated. Otherwise, it disable those and enables the remaining ones. ''' # GuiTop self.GuiConnect.setEnabled(not logged_in) self.GuiAccessKey.setEnabled(not logged_in) self.GuiAccessKeyBtn.setEnabled(not logged_in) self.GuiRegions.setEnabled(not logged_in) # GuiMid and GuiBottom self.GuiMid.setEnabled(logged_in) self.GuiBottom.setEnabled(logged_in) # ======================================================== # ======================================================== def closeEvent(self, event): ''' triggered by: 'x button on titlebar' 1) Asks user to confirm she want to close the app and if yes, then it closes it. ''' msg1 = 'This will not stop any AWS instances currently running.' msg2 = '\n\nDo you still want to close the application?' msg = msg1 + msg2 reply = QMessageBox.question(self, 'Warning', msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: self.refresher.off() event.accept() else: event.ignore()