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()
Esempio n. 2
0
    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)
Esempio n. 3
0
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)
Esempio n. 5
0
    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)
Esempio n. 7
0
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()
Esempio n. 8
0
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()
Esempio n. 9
0
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()
Esempio n. 10
0
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()