Fixes bug in forecast thread (close app while downloading). Also makes some style adjustments

This commit is contained in:
alessandro90
2019-05-01 19:50:03 +02:00
parent 9c77ac8278
commit 3826681685
13 changed files with 670 additions and 438 deletions

View File

@@ -6,6 +6,7 @@ import sys
from time import sleep
from pandas import read_csv
from PyQt5.QtWidgets import (QMainWindow,
QApplication,
qApp,
@@ -54,6 +55,7 @@ class Artemis(QMainWindow, Ui_MainWindow):
super().__init__()
self.setupUi(self)
self.set_initial_size()
self.closing = False
self.download_window = DownloadWindow()
self.download_window.complete.connect(self.show_downloaded_signals)
self.actionExit.triggered.connect(qApp.quit)
@@ -63,35 +65,44 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.current_signal_name = ''
self.signal_names = []
self.total_signals = 0
self.switchable_r_labels = SwitchableLabelsIterable(self.r0_now_lbl,
self.switchable_r_labels = SwitchableLabelsIterable(
self.r0_now_lbl,
self.r1_now_lbl,
self.r2_now_lbl,
self.r3_now_lbl,
self.r4_now_lbl,
self.r5_now_lbl,)
self.r5_now_lbl
)
self.switchable_s_labels = SwitchableLabelsIterable(self.s0_now_lbl,
self.switchable_s_labels = SwitchableLabelsIterable(
self.s0_now_lbl,
self.s1_now_lbl,
self.s2_now_lbl,
self.s3_now_lbl,
self.s4_now_lbl,
self.s5_now_lbl,)
self.s5_now_lbl
)
self.switchable_g_now_labels = SwitchableLabelsIterable(self.g0_now_lbl,
self.switchable_g_now_labels = SwitchableLabelsIterable(
self.g0_now_lbl,
self.g1_now_lbl,
self.g2_now_lbl,
self.g3_now_lbl,
self.g4_now_lbl,
self.g5_now_lbl)
self.g5_now_lbl
)
self.switchable_g_today_labels = SwitchableLabelsIterable(self.g0_today_lbl,
self.switchable_g_today_labels = SwitchableLabelsIterable(
self.g0_today_lbl,
self.g1_today_lbl,
self.g2_today_lbl,
self.g3_today_lbl,
self.g4_today_lbl,
self.g5_today_lbl)
self.g5_today_lbl
)
self.k_storm_labels = SwitchableLabelsIterable(self.k_ex_sev_storm_lbl,
self.k_storm_labels = SwitchableLabelsIterable(
self.k_ex_sev_storm_lbl,
self.k_very_sev_storm_lbl,
self.k_sev_storm_lbl,
self.k_maj_storm_lbl,
@@ -100,16 +111,20 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.k_unsettled_lbl,
self.k_quiet_lbl,
self.k_very_quiet_lbl,
self.k_inactive_lbl)
self.k_inactive_lbl
)
self.a_storm_labels = SwitchableLabelsIterable(self.a_sev_storm_lbl,
self.a_storm_labels = SwitchableLabelsIterable(
self.a_sev_storm_lbl,
self.a_maj_storm_lbl,
self.a_min_storm_lbl,
self.a_active_lbl,
self.a_unsettled_lbl,
self.a_quiet_lbl)
self.a_quiet_lbl
)
self.forecast_labels = (self.forecast_lbl_0,
self.forecast_labels = (
self.forecast_lbl_0,
self.forecast_lbl_1,
self.forecast_lbl_2,
self.forecast_lbl_3,
@@ -117,7 +132,8 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.forecast_lbl_5,
self.forecast_lbl_6,
self.forecast_lbl_7,
self.forecast_lbl_8)
self.forecast_lbl_8
)
for lab in self.forecast_labels:
lab.set_default_stylesheet()
@@ -192,27 +208,29 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.apply_remove_freq_filter_btn.set_texts(Constants.APPLY, Constants.REMOVE)
self.apply_remove_freq_filter_btn.set_slave_filters(
[
simple_ones=[
*self.frequency_filters_btns,
self.include_undef_freqs,
self.activate_low_freq_filter_btn,
self.activate_up_freq_filter_btn,
self.activate_up_freq_filter_btn
],
self.activate_low_freq_filter_btn,
[
radio_1=self.activate_low_freq_filter_btn,
ruled_by_radio_1=[
self.lower_freq_spinbox,
self.lower_freq_filter_unit,
self.lower_freq_confidence,
self.lower_freq_confidence
],
self.activate_up_freq_filter_btn,
[
radio_2=self.activate_up_freq_filter_btn,
ruled_by_radio_2=[
self.upper_freq_spinbox,
self.upper_freq_filter_unit,
self.upper_freq_confidence,
],
self.upper_freq_confidence
]
)
self.apply_remove_freq_filter_btn.clicked.connect(self.display_signals)
self.reset_frequency_filters_btn.clicked.connect(partial(self.reset_fb_filters, Ftype.FREQ))
self.reset_frequency_filters_btn.clicked.connect(
partial(self.reset_fb_filters, Ftype.FREQ)
)
# Manage bandwidth filters.
@@ -266,33 +284,37 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.upper_band_confidence)
)
self.apply_remove_band_filter_btn.set_texts(Constants.APPLY, Constants.REMOVE)
self.apply_remove_band_filter_btn.set_texts(Constants.APPLY,
Constants.REMOVE)
self.apply_remove_band_filter_btn.set_slave_filters(
[
simple_ones=[
self.include_undef_bands,
self.activate_low_band_filter_btn,
self.activate_up_band_filter_btn,
self.activate_up_band_filter_btn
],
self.activate_low_band_filter_btn,
[
radio_1=self.activate_low_band_filter_btn,
ruled_by_radio_1=[
self.lower_band_spinbox,
self.lower_band_filter_unit,
self.lower_band_confidence,
self.lower_band_confidence
],
self.activate_up_band_filter_btn,
[
radio_2=self.activate_up_band_filter_btn,
ruled_by_radio_2=[
self.upper_band_spinbox,
self.upper_band_filter_unit,
self.upper_band_confidence,
],
self.upper_band_confidence
]
)
self.apply_remove_band_filter_btn.clicked.connect(self.display_signals)
self.reset_band_filters_btn.clicked.connect(partial(self.reset_fb_filters, Ftype.BAND))
self.reset_band_filters_btn.clicked.connect(
partial(self.reset_fb_filters, Ftype.BAND)
)
# Manage category filters
# Order matters!
self.cat_filter_btns = [self.military_btn,
self.cat_filter_btns = [
self.military_btn,
self.radar_btn,
self.active_btn,
self.inactive_btn,
@@ -308,12 +330,17 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.navigation_btn,
self.interfering_btn,
self.number_stations_btn,
self.time_signal_btn,]
self.time_signal_btn
]
self.apply_remove_cat_filter_btn.set_texts(Constants.APPLY, Constants.REMOVE)
self.apply_remove_cat_filter_btn.set_slave_filters([*self.cat_filter_btns,
self.apply_remove_cat_filter_btn.set_slave_filters(
simple_ones=[
*self.cat_filter_btns,
self.cat_at_least_one,
self.cat_all])
self.cat_all
]
)
self.apply_remove_cat_filter_btn.clicked.connect(self.display_signals)
self.reset_cat_filters_btn.clicked.connect(self.reset_cat_filters)
@@ -323,7 +350,8 @@ class Artemis(QMainWindow, Ui_MainWindow):
UrlColors = namedtuple("UrlColors", ["inactive", "active", "clicked"])
self.url_button.colors = UrlColors("#9f9f9f", "#4c75ff", "#942ccc")
self.category_labels = [self.cat_mil,
self.category_labels = [
self.cat_mil,
self.cat_rad,
self.cat_active,
self.cat_inactive,
@@ -339,15 +367,18 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.cat_navi,
self.cat_interf,
self.cat_num_stat,
self.cat_time_sig,]
self.cat_time_sig
]
self.property_labels = [self.freq_lab,
self.property_labels = [
self.freq_lab,
self.band_lab,
self.mode_lab,
self.modul_lab,
self.loc_lab,
self.acf_lab,
self.description_text,]
self.description_text
]
self.url_button.clicked.connect(self.go_to_web_page_signal)
@@ -357,8 +388,12 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.mode_tree_widget.itemSelectionChanged.connect(self.manage_mode_selections)
self.reset_mode_filters_btn.clicked.connect(self.reset_mode_filters)
self.apply_remove_mode_filter_btn.set_texts(Constants.APPLY, Constants.REMOVE)
self.apply_remove_mode_filter_btn.set_slave_filters([self.mode_tree_widget,
self.include_unknown_modes_btn])
self.apply_remove_mode_filter_btn.set_slave_filters(
simple_ones=[
self.mode_tree_widget,
self.include_unknown_modes_btn
]
)
self.apply_remove_mode_filter_btn.clicked.connect(self.display_signals)
# Set modulation filter screen.
@@ -366,8 +401,12 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.modulation_list.addItems(Constants.MODULATIONS)
self.search_bar_modulation.textEdited.connect(self.show_matching_modulations)
self.apply_remove_modulation_filter_btn.set_texts(Constants.APPLY, Constants.REMOVE)
self.apply_remove_modulation_filter_btn.set_slave_filters([self.search_bar_modulation,
self.modulation_list])
self.apply_remove_modulation_filter_btn.set_slave_filters(
simple_ones=[
self.search_bar_modulation,
self.modulation_list
]
)
self.apply_remove_modulation_filter_btn.clicked.connect(self.display_signals)
self.reset_modulation_filters_btn.clicked.connect(self.reset_modulation_filters)
self.modulation_list.itemClicked.connect(self.remove_if_unselected_modulation)
@@ -377,21 +416,32 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.locations_list.addItems(Constants.LOCATIONS)
self.search_bar_location.textEdited.connect(self.show_matching_locations)
self.apply_remove_location_filter_btn.set_texts(Constants.APPLY, Constants.REMOVE)
self.apply_remove_location_filter_btn.set_slave_filters([self.search_bar_location,
self.locations_list])
self.apply_remove_location_filter_btn.set_slave_filters(
simple_ones=[
self.search_bar_location,
self.locations_list
]
)
self.apply_remove_location_filter_btn.clicked.connect(self.display_signals)
self.reset_location_filters_btn.clicked.connect(self.reset_location_filters)
self.locations_list.itemClicked.connect(self.remove_if_unselected_location)
# Set ACF filter screen.
self.apply_remove_acf_filter_btn.set_texts(Constants.APPLY, Constants.REMOVE)
self.apply_remove_acf_filter_btn.set_slave_filters([self.include_undef_acf, self.acf_spinbox, self.acf_confidence])
self.apply_remove_acf_filter_btn.set_slave_filters(
simple_ones=[
self.include_undef_acf,
self.acf_spinbox,
self.acf_confidence
]
)
self.apply_remove_acf_filter_btn.clicked.connect(self.display_signals)
self.reset_acf_filters_btn.clicked.connect(self.reset_acf_filters)
self.acf_info_btn.clicked.connect(lambda : webbrowser.open(Constants.ACF_DOCS))
connect_events_to_func(
events_to_connect = [self.acf_spinbox.valueChanged, self.acf_confidence.valueChanged],
events_to_connect=[self.acf_spinbox.valueChanged,
self.acf_confidence.valueChanged],
fun_to_connect=self.set_acf_interval_label,
fun_args=None
)
@@ -406,14 +456,18 @@ class Artemis(QMainWindow, Ui_MainWindow):
# Left list widget and search bar.
self.search_bar.textChanged.connect(self.display_signals)
self.result_list.currentItemChanged.connect(self.display_specs)
self.result_list.itemDoubleClicked.connect(lambda: self.main_tab.setCurrentWidget(self.signal_properties_tab))
self.audio_widget = AudioPlayer(self.play,
self.result_list.itemDoubleClicked.connect(
lambda: self.main_tab.setCurrentWidget(self.signal_properties_tab)
)
self.audio_widget = AudioPlayer(
self.play,
self.pause,
self.stop,
self.volume,
self.audio_progress,
self.active_color,
self.inactive_color)
self.inactive_color
)
BandLabel = namedtuple("BandLabel", ["left", "center", "right"])
self.band_labels = [
@@ -431,7 +485,9 @@ class Artemis(QMainWindow, Ui_MainWindow):
]
# Space weather
self.info_now_btn.clicked.connect(lambda : webbrowser.open(Constants.FORECAST_INFO))
self.info_now_btn.clicked.connect(
lambda : webbrowser.open(Constants.FORECAST_INFO)
)
self.update_now_bar.clicked.connect(self.start_update_space_weather)
self.update_now_bar.set_idle()
self.space_weather_data = SpaceWeatherData()
@@ -592,7 +648,7 @@ class Artemis(QMainWindow, Ui_MainWindow):
label.pixmap = pixmap
label.make_transparent()
label.apply_pixmap()
else:
elif not self.closing:
pop_up(self, title=Messages.BAD_DOWNLOAD,
text=Messages.BAD_DOWNLOAD_MSG).show()
self.space_weather_data.remove_data()
@@ -601,7 +657,9 @@ class Artemis(QMainWindow, Ui_MainWindow):
def go_to_gfd(self, by):
query = "/?q="
if by is GfdType.FREQ:
value_in_mhz = self.freq_gfd.value() * Constants.CONVERSION_FACTORS[self.unit_freq_gfd.currentText()] / Constants.CONVERSION_FACTORS["MHz"]
value_in_mhz = self.freq_gfd.value() \
* Constants.CONVERSION_FACTORS[self.unit_freq_gfd.currentText()] \
/ Constants.CONVERSION_FACTORS["MHz"]
query += str(value_in_mhz)
elif by is GfdType.LOC:
query += self.gfd_line_edit.text()
@@ -655,8 +713,8 @@ class Artemis(QMainWindow, Ui_MainWindow):
item.child(i).setSelected(True)
def set_initial_size(self):
"""Function to handle high resolution screens. The function sets bigger sizes
for all the relevant fixed-size widgets."""
"""Function to handle high resolution screens. The function sets bigger
sizes for all the relevant fixed-size widgets."""
d = QDesktopWidget().availableGeometry()
w = d.width()
h = d.height()
@@ -781,7 +839,7 @@ class Artemis(QMainWindow, Ui_MainWindow):
header=None,
index_col=0,
dtype={name: str for name in Database.STRINGS},
names = names,)
names=names)
except FileNotFoundError:
self.search_bar.setDisabled(True)
answer = pop_up(self, title=Messages.NO_DB,
@@ -825,11 +883,13 @@ class Artemis(QMainWindow, Ui_MainWindow):
upper_combo_box.disconnect()
upper_combo_box.setCurrentText(new_unit)
upper_combo_box.currentTextChanged.connect(
partial(self.set_min_value_upper_limit,
partial(
self.set_min_value_upper_limit,
lower_combo_box,
lower_spin_box,
upper_combo_box,
upper_spin_box)
upper_spin_box
)
)
@pyqtSlot()
@@ -854,7 +914,8 @@ class Artemis(QMainWindow, Ui_MainWindow):
min_value = lower_spinbox.value()
if lower_confidence.value() != 0:
min_value -= lower_spinbox.value() * lower_confidence.value() / 100
to_display += str(round(min_value, Constants.MAX_DIGITS)) + ' ' + lower_unit.currentText()
to_display += str(round(min_value, Constants.MAX_DIGITS)) \
+ ' ' + lower_unit.currentText()
else:
to_display += 'DC'
to_display += Constants.RANGE_SEPARATOR
@@ -864,7 +925,8 @@ class Artemis(QMainWindow, Ui_MainWindow):
color = self.active_color
if upper_confidence.value() != 0:
max_value += upper_spinbox.value() * upper_confidence.value() / 100
to_display += str(round(max_value, Constants.MAX_DIGITS)) + ' ' + upper_unit.currentText()
to_display += str(round(max_value, Constants.MAX_DIGITS)) + ' ' \
+ upper_unit.currentText()
else:
to_display += 'INF'
if activate_low and activate_high:
@@ -884,8 +946,9 @@ class Artemis(QMainWindow, Ui_MainWindow):
def set_acf_interval_label(self):
tolerance = self.acf_spinbox.value() * self.acf_confidence.value() / 100
if tolerance > 0:
to_display = f"Selected range:\n\n{round(self.acf_spinbox.value() - tolerance, Constants.MAX_DIGITS)}" +\
Constants.RANGE_SEPARATOR + f"{round(self.acf_spinbox.value() + tolerance, Constants.MAX_DIGITS)} ms"
val = round(self.acf_spinbox.value() - tolerance, Constants.MAX_DIGITS)
to_display = f"Selected range:\n\n{val}" + Constants.RANGE_SEPARATOR \
+ f"{round(self.acf_spinbox.value() + tolerance, Constants.MAX_DIGITS)} ms"
else:
to_display = f"Selected value:\n\n{self.acf_spinbox.value()} ms"
self.acf_range_lbl.setText(to_display)
@@ -923,12 +986,15 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.statusbar.setStyleSheet(f'color: {self.active_color}')
else:
self.statusbar.setStyleSheet(f'color: {self.inactive_color}')
self.statusbar.showMessage(f"{available_signals} out of {self.total_signals} signals displayed.")
self.statusbar.showMessage(
f"{available_signals} out of {self.total_signals} signals displayed."
)
@pyqtSlot()
def reset_fb_filters(self, ftype):
if ftype != Ftype.FREQ and ftype != Ftype.BAND:
raise ValueError("Wrong ftype in function 'reset_fb_filters'")
apply_remove_btn = getattr(self, 'apply_remove_' + ftype + '_filter_btn')
include_undef_btn = getattr(self, 'include_undef_' + ftype + 's')
activate_low = getattr(self, 'activate_low_' + ftype + '_filter_btn')
@@ -939,6 +1005,7 @@ class Artemis(QMainWindow, Ui_MainWindow):
upper_spinbox = getattr(self, 'upper_' + ftype + '_spinbox')
lower_confidence = getattr(self, 'lower_' + ftype + '_confidence')
upper_confidence = getattr(self, 'lower_' + ftype + '_confidence')
default_val = 1 if ftype == Ftype.FREQ else 5000
if ftype == Ftype.FREQ:
for f in self.frequency_filters_btns:
@@ -1014,8 +1081,10 @@ class Artemis(QMainWindow, Ui_MainWindow):
else:
return False
signal_freqs = (int(self.db.at[signal_name, Signal.INF_FREQ]),
int(self.db.at[signal_name, Signal.SUP_FREQ]))
signal_freqs = (
int(self.db.at[signal_name, Signal.INF_FREQ]),
int(self.db.at[signal_name, Signal.SUP_FREQ])
)
band_filter_ok = False
any_checked = False
@@ -1051,8 +1120,10 @@ class Artemis(QMainWindow, Ui_MainWindow):
else:
return False
signal_bands = (int(self.db.at[signal_name, Signal.INF_BAND]),
int(self.db.at[signal_name, Signal.SUP_BAND]))
signal_bands = (
int(self.db.at[signal_name, Signal.INF_BAND]),
int(self.db.at[signal_name, Signal.SUP_BAND])
)
lower_limit_ok = True
upper_limit_ok = True
@@ -1151,21 +1222,31 @@ class Artemis(QMainWindow, Ui_MainWindow):
current_signal = self.db.loc[self.current_signal_name]
self.url_button.setEnabled(True)
if not current_signal.at[Signal.WIKI_CLICKED]:
self.url_button.setStyleSheet(f"color: {self.url_button.colors.active};")
self.url_button.setStyleSheet(
f"color: {self.url_button.colors.active};"
)
else:
self.url_button.setStyleSheet(f"color: {self.url_button.colors.clicked};")
self.url_button.setStyleSheet(
f"color: {self.url_button.colors.clicked};"
)
category_code = current_signal.at[Signal.CATEGORY_CODE]
undef_freq = is_undef_freq(current_signal)
undef_band = is_undef_band(current_signal)
if not undef_freq:
self.freq_lab.setText(format_numbers(current_signal.at[Signal.INF_FREQ],
current_signal.at[Signal.SUP_FREQ])
self.freq_lab.setText(
format_numbers(
current_signal.at[Signal.INF_FREQ],
current_signal.at[Signal.SUP_FREQ]
)
)
else:
self.freq_lab.setText("Undefined")
if not undef_band:
self.band_lab.setText(format_numbers(current_signal.at[Signal.INF_BAND],
current_signal.at[Signal.SUP_BAND])
self.band_lab.setText(
format_numbers(
current_signal.at[Signal.INF_BAND],
current_signal.at[Signal.SUP_BAND]
)
)
else:
self.band_lab.setText("Undefined")
@@ -1184,7 +1265,9 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.audio_widget.set_audio_player(self.current_signal_name)
else:
self.url_button.setEnabled(False)
self.url_button.setStyleSheet(f"color: {self.url_button.colors.inactive};")
self.url_button.setStyleSheet(
f"color: {self.url_button.colors.inactive};"
)
self.current_signal_name = ''
self.name_lab.setText("No Signal")
self.name_lab.setAlignment(Qt.AlignHCenter)
@@ -1196,15 +1279,23 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.audio_widget.set_audio_player()
def display_spectrogram(self):
default_pic = os.path.join(self.default_images_folder, Constants.NOT_SELECTED)
default_pic = os.path.join(
self.default_images_folder,
Constants.NOT_SELECTED
)
item = self.result_list.currentItem()
if item:
spectrogram_name = item.text()
path_spectr = os.path.join(Constants.DATA_FOLDER,
path_spectr = os.path.join(
Constants.DATA_FOLDER,
Constants.SPECTRA_FOLDER,
spectrogram_name + Constants.SPECTRA_EXT)
spectrogram_name + Constants.SPECTRA_EXT
)
if not QFileInfo(path_spectr).exists():
path_spectr = os.path.join(self.default_images_folder, Constants.NOT_AVAILABLE)
path_spectr = os.path.join(
self.default_images_folder,
Constants.NOT_AVAILABLE
)
else:
path_spectr = default_pic
self.spectrogram.setPixmap(QPixmap(path_spectr))
@@ -1248,13 +1339,18 @@ class Artemis(QMainWindow, Ui_MainWindow):
@pyqtSlot()
def go_to_web_page_signal(self):
if self.current_signal_name:
self.url_button.setStyleSheet(f"color: {self.url_button.colors.clicked}")
self.url_button.setStyleSheet(
f"color: {self.url_button.colors.clicked}"
)
webbrowser.open(self.db.at[self.current_signal_name, Signal.URL])
self.db.at[self.current_signal_name, Signal.WIKI_CLICKED] = True
def closeEvent(self, event):
self.closing = True
if self.download_window.isVisible():
self.download_window.close()
if self.space_weather_data.is_updating:
self.space_weather_data.shutdown_thread()
super().closeEvent(event)

View File

@@ -1,5 +1,4 @@
import os
import sys
from pydub import AudioSegment
from pygame import mixer
from PyQt5.QtCore import QTimer, pyqtSlot, QObject
@@ -9,11 +8,9 @@ import qtawesome as qta
class AudioPlayer(QObject): # Maybe useless inheriting from QObject
"""
This is the audio player widget. The only public methods are the __init__
method, set_audio_player, which loads the current file and refresh_btns_colors. Everything else
is managed internally.
"""
"""This is the audio player widget. The only public methods are the __init__
method, set_audio_player, which loads the current file and refresh_btns_colors.
Everything else is managed internally."""
__time_step = 500 # Milliseconds.
@@ -52,7 +49,9 @@ class AudioPlayer(QObject): # Maybe useless inheriting from QObject
@pyqtSlot()
def __set_volume(self):
if mixer.get_init():
mixer.music.set_volume(self.__volume.value() / self.__volume.maximum())
mixer.music.set_volume(
self.__volume.value() / self.__volume.maximum()
)
def __reset_audio_widget(self):
if mixer.get_init():
@@ -82,7 +81,11 @@ class AudioPlayer(QObject): # Maybe useless inheriting from QObject
def set_audio_player(self, fname = ""):
self.__first_call = True
self.__reset_audio_widget()
full_name = os.path.join(Constants.DATA_FOLDER, Constants.AUDIO_FOLDER, fname + '.ogg')
full_name = os.path.join(
Constants.DATA_FOLDER,
Constants.AUDIO_FOLDER,
fname + '.ogg'
)
if os.path.exists(full_name):
self.__play.setEnabled(True)
self.__audio_file = full_name
@@ -92,7 +95,9 @@ class AudioPlayer(QObject): # Maybe useless inheriting from QObject
if not self.__paused:
if self.__first_call:
self.__first_call = False
mixer.init(frequency = AudioSegment.from_ogg(self.__audio_file).frame_rate,
mixer.init(frequency=AudioSegment.from_ogg(
self.__audio_file
).frame_rate,
buffer=2048)
mixer.music.load(self.__audio_file)
self.__set_volume()

View File

@@ -2,6 +2,7 @@ from PyQt5.QtWidgets import QProgressBar
from PyQt5.QtCore import Qt, pyqtSignal
from constants import Constants
class ClickableProgressBar(QProgressBar):
clicked = pyqtSignal()

View File

@@ -1,6 +1,7 @@
from PyQt5.QtWidgets import QLabel
from PyQt5.QtCore import Qt
class FixedAspectRatioLabel(QLabel):
def __init__(self, parent = None):
super().__init__(parent)
@@ -20,7 +21,9 @@ class FixedAspectRatioLabel(QLabel):
if self.pixmap:
self.setPixmap(
self.pixmap.scaled(
self.size(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation))
self.size(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation
)
)
def rescale(self, size):
self.resize(size)

View File

@@ -1,6 +1,7 @@
from PyQt5.QtWidgets import QWidget
from PyQt5.QtCore import QSize
class FixedAspectRatioWidget(QWidget):
space = 10
def __init__(self, parent=None):

View File

@@ -1,13 +1,18 @@
aiohttp==3.5.4
altgraph==0.16.1
asn1crypto==0.24.0
async-timeout==3.0.1
attrs==19.1.0
certifi==2019.3.9
cffi==1.11.5
chardet==3.0.4
cryptography==2.3.1
Cython==0.29.6
future==0.16.0
idna==2.7
intel-openmp==2019.0
macholib==1.11
multidict==4.5.2
numpy==1.15.2
pandas==0.23.4
pefile==2018.8.8
@@ -28,3 +33,4 @@ six==1.11.0
urllib3==1.24
win-inet-pton==1.0.1
wincertstore==0.2
yarl==1.3.0

View File

@@ -2,6 +2,7 @@ from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject
from threads import UpadteSpaceWeatherThread, ThreadStatus
class SpaceWeatherData(QObject):
update_complete = pyqtSignal(bool)
@@ -12,7 +13,7 @@ class SpaceWeatherData(QObject):
self.ak_index = ''
self.sgas = ''
self.geo_storm = ''
self.images = [QPixmap(),
self.images = [
QPixmap(),
QPixmap(),
QPixmap(),
@@ -20,7 +21,9 @@ class SpaceWeatherData(QObject):
QPixmap(),
QPixmap(),
QPixmap(),
QPixmap()]
QPixmap(),
QPixmap()
]
self.__update_thread = UpadteSpaceWeatherThread(self)
self.__update_thread.finished.connect(self.__parse_and_emit_signal)
@@ -46,7 +49,7 @@ class SpaceWeatherData(QObject):
self.ak_index = ''
self.sgas = ''
self.geo_storm = ''
self.images = [QPixmap(),
self.images = [
QPixmap(),
QPixmap(),
QPixmap(),
@@ -54,13 +57,18 @@ class SpaceWeatherData(QObject):
QPixmap(),
QPixmap(),
QPixmap(),
QPixmap()]
QPixmap(),
QPixmap()
]
@pyqtSlot()
def __parse_and_emit_signal(self):
if self.__update_thread.status is not ThreadStatus.OK:
status_ok = False
else:
if self.__update_thread.status is ThreadStatus.OK:
status_ok = True
self.__parse_data()
self.update_complete.emit(status_ok)
def shutdown_thread(self):
self.__update_thread.terminate()
self.__update_thread.wait()

View File

@@ -1,5 +1,6 @@
from PyQt5.QtWidgets import QLabel
class SwitchableLabel(QLabel):
def __init__(self, parent=None):
super().__init__(parent)

215
themes.py
View File

@@ -9,6 +9,7 @@ from constants import Constants
from switchable_label import SwitchableLabelsIterable
from utilities import pop_up
class ThemeConstants(object):
FOLDER = "themes"
EXTENSION = ".qss"
@@ -34,27 +35,41 @@ class Theme(object):
self.__theme_path = ""
self.__current_theme = ""
self.__parent.default_images_folder = os.path.join(ThemeConstants.FOLDER,
self.__parent.default_images_folder = os.path.join(
ThemeConstants.FOLDER,
ThemeConstants.DEFAULT,
ThemeConstants.ICONS_FOLDER)
ThemeConstants.ICONS_FOLDER
)
self.__forecast_labels = SwitchableLabelsIterable(*list(chain(self.__parent.switchable_r_labels,
self.__forecast_labels = SwitchableLabelsIterable(
*list(
chain(
self.__parent.switchable_r_labels,
self.__parent.switchable_s_labels,
self.__parent.switchable_g_now_labels,
self.__parent.switchable_g_today_labels,
self.__parent.k_storm_labels,
self.__parent.a_storm_labels,
[self.__parent.expected_noise_lbl])))
[self.__parent.expected_noise_lbl]
)
)
)
self.__forecast_labels.set("switch_on_colors", ThemeConstants.DEFAULT_ON_COLORS)
self.__forecast_labels.set("switch_off_colors", ThemeConstants.DEFAULT_OFF_COLORS)
self.__forecast_labels.set(
"switch_on_colors",
ThemeConstants.DEFAULT_ON_COLORS
)
self.__forecast_labels.set(
"switch_off_colors", ThemeConstants.DEFAULT_OFF_COLORS
)
self.__theme_names = {}
self.__detect_themes()
def __refresh_range_labels(self):
self.__parent.set_acf_interval_label()
self.__parent.set_band_filter_label(self.__parent.activate_low_band_filter_btn,
self.__parent.set_band_filter_label(
self.__parent.activate_low_band_filter_btn,
self.__parent.lower_band_spinbox,
self.__parent.lower_band_filter_unit,
self.__parent.lower_band_confidence,
@@ -62,9 +77,11 @@ class Theme(object):
self.__parent.upper_band_spinbox,
self.__parent.upper_band_filter_unit,
self.__parent.upper_band_confidence,
self.__parent.band_range_lbl)
self.__parent.band_range_lbl
)
self.__parent.set_band_filter_label(self.__parent.activate_low_freq_filter_btn,
self.__parent.set_band_filter_label(
self.__parent.activate_low_freq_filter_btn,
self.__parent.lower_freq_spinbox,
self.__parent.lower_freq_filter_unit,
self.__parent.lower_freq_confidence,
@@ -72,16 +89,23 @@ class Theme(object):
self.__parent.upper_freq_spinbox,
self.__parent.upper_freq_filter_unit,
self.__parent.upper_freq_confidence,
self.__parent.freq_range_lbl)
self.__parent.freq_range_lbl
)
@pyqtSlot()
def __apply(self, theme_path):
self.__theme_path = theme_path
if self.__theme_path != self.__current_theme:
self.__change()
self.__parent.display_specs(self.__parent.result_list.currentItem(), None)
self.__parent.display_specs(
item=self.__parent.result_list.currentItem(),
previous_item=None
)
self.__refresh_range_labels()
self.__parent.audio_widget.refresh_btns_colors(self.__parent.active_color, self.__parent.inactive_color)
self.__parent.audio_widget.refresh_btns_colors(
self.__parent.active_color,
self.__parent.inactive_color
)
self.__forecast_labels.refresh()
def __pretty_name(self, bad_name):
@@ -101,16 +125,33 @@ class Theme(object):
themes.append(relative_folder)
for theme_path in themes:
theme_name = '&' + self.__pretty_name(os.path.basename(theme_path))
new_theme = ag.addAction(QAction(theme_name, self.__parent, checkable = True))
new_theme = ag.addAction(
QAction(
theme_name,
self.__parent, checkable=True
)
)
self.__parent.menu_themes.addAction(new_theme)
self.__theme_names[theme_name.lstrip('&')] = new_theme
new_theme.triggered.connect(partial(self.__apply, theme_path))
def __is_valid_html_color(self, colors):
pattern = "#([a-zA-Z0-9]){6}"
match_ok = lambda col: bool(re.match(pattern, col))
if isinstance(colors, list):
if len(colors) > 1:
return all(match_ok(c) for c in colors)
else:
return match_ok(colors[0])
else:
return match_ok(colors)
def __change(self):
theme_name = os.path.basename(self.__theme_path).split('-')[1] + ThemeConstants.EXTENSION
try:
with open(os.path.join(
self.__theme_path,
os.path.basename(self.__theme_path).split('-')[1] + ThemeConstants.EXTENSION), "r") as stylesheet:
with open(
os.path.join(self.__theme_path, theme_name), "r"
) as stylesheet:
style = stylesheet.read()
self.__parent.setStyleSheet(style)
self.__parent.download_window.setStyleSheet(style)
@@ -119,9 +160,11 @@ class Theme(object):
text=ThemeConstants.MISSING_THEME).show()
else:
icons_path = os.path.join(self.__theme_path, ThemeConstants.ICONS_FOLDER)
default_icons_path = os.path.join(ThemeConstants.FOLDER,
default_icons_path = os.path.join(
ThemeConstants.FOLDER,
ThemeConstants.DEFAULT,
ThemeConstants.ICONS_FOLDER)
ThemeConstants.ICONS_FOLDER
)
if os.path.exists(os.path.join(icons_path, Constants.NOT_SELECTED)) and \
os.path.exists(os.path.join(icons_path, Constants.NOT_AVAILABLE)):
@@ -129,33 +172,65 @@ class Theme(object):
else:
self.__parent.default_images_folder = default_icons_path
path_to_search_label = os.path.join(icons_path, Constants.SEARCH_LABEL_IMG)
default_search_label = os.path.join(default_icons_path, Constants.SEARCH_LABEL_IMG)
path_to_search_label = os.path.join(
icons_path,
Constants.SEARCH_LABEL_IMG
)
default_search_label = os.path.join(
default_icons_path,
Constants.SEARCH_LABEL_IMG
)
if os.path.exists(path_to_search_label):
self.__parent.search_label.setPixmap(QPixmap(path_to_search_label))
self.__parent.modulation_search_label.setPixmap(QPixmap(path_to_search_label))
self.__parent.location_search_label.setPixmap(QPixmap(path_to_search_label))
self.__parent.search_label.setPixmap(
QPixmap(path_to_search_label)
)
self.__parent.modulation_search_label.setPixmap(
QPixmap(path_to_search_label)
)
self.__parent.location_search_label.setPixmap(
QPixmap(path_to_search_label)
)
else:
self.__parent.search_label.setPixmap(QPixmap(default_search_label))
self.__parent.modulation_search_label.setPixmap(QPixmap(default_search_label))
self.__parent.location_search_label.setPixmap(QPixmap(default_search_label))
self.__parent.search_label.setPixmap(
QPixmap(default_search_label)
)
self.__parent.modulation_search_label.setPixmap(
QPixmap(default_search_label)
)
self.__parent.location_search_label.setPixmap(
QPixmap(default_search_label)
)
self.__parent.search_label.setScaledContents(True)
self.__parent.modulation_search_label.setScaledContents(True)
self.__parent.location_search_label.setScaledContents(True)
path_to_volume_label = os.path.join(icons_path, Constants.VOLUME_LABEL_IMG)
default_volume_label = os.path.join(default_icons_path, Constants.VOLUME_LABEL_IMG)
path_to_volume_label = os.path.join(
icons_path,
Constants.VOLUME_LABEL_IMG
)
default_volume_label = os.path.join(
default_icons_path,
Constants.VOLUME_LABEL_IMG
)
if os.path.exists(path_to_volume_label):
self.__parent.volume_label.setPixmap(QPixmap(path_to_volume_label))
self.__parent.volume_label.setPixmap(
QPixmap(path_to_volume_label)
)
else:
self.__parent.volume_label.setPixmap(QPixmap(default_volume_label))
self.__parent.volume_label.setPixmap(
QPixmap(default_volume_label)
)
self.__parent.volume_label.setScaledContents(True)
path_to_colors = os.path.join(self.__theme_path, ThemeConstants.COLORS)
path_to_colors = os.path.join(
self.__theme_path,
ThemeConstants.COLORS
)
active_color_ok = False
inactive_color_ok = False
switch_on_color_ok = False
@@ -163,57 +238,78 @@ class Theme(object):
text_color_ok = False
if os.path.exists(path_to_colors):
is_valid_html_color = lambda colors : all([bool(re.match("#([a-zA-Z0-9]){6}", color)) for color in colors])
with open(path_to_colors, "r") as colors_file:
for line in colors_file:
if ThemeConstants.COLOR_SEPARATOR in line:
quality, color = line.split(ThemeConstants.COLOR_SEPARATOR)
color = color.rstrip()
color_len = 1
if ',' in color:
color = [c.rstrip().lstrip() for c in color.split(',')]
else:
color = [color]
if len(color) > 2:
break
if is_valid_html_color(color):
color = [c.strip() for c in color.split(',')]
color_len = len(color)
if self.__is_valid_html_color(color):
if color_len == 1:
if quality.lower() == Constants.ACTIVE:
self.__parent.active_color = color[0]
self.__parent.active_color = color
active_color_ok = True
if quality.lower() == Constants.INACTIVE:
self.__parent.inactive_color = color[0]
self.__parent.inactive_color = color
inactive_color_ok = True
if len(color) == 2:
if quality.lower() == Constants.LABEL_ON_COLOR:
switch_on_color_ok = True
self.__forecast_labels.set("switch_on_colors", color)
if quality.lower() == Constants.LABEL_OFF_COLOR:
switch_off_color_ok = True
self.__forecast_labels.set("switch_off_colors", color)
if quality.lower() == Constants.TEXT_COLOR:
text_color_ok = True
self.__forecast_labels.set("text_color", color[0])
self.__forecast_labels.set(
"text_color",
color
)
if color_len == 2:
if quality.lower() == Constants.LABEL_ON_COLOR:
switch_on_color_ok = True
self.__forecast_labels.set(
"switch_on_colors",
color
)
if quality.lower() == Constants.LABEL_OFF_COLOR:
switch_off_color_ok = True
self.__forecast_labels.set(
"switch_off_colors",
color
)
if not (active_color_ok and inactive_color_ok):
self.__parent.active_color = ThemeConstants.DEFAULT_ACTIVE_COLOR
self.__parent.inactive_color = ThemeConstants.DEFAULT_INACTIVE_COLOR
if not (switch_on_color_ok and switch_off_color_ok):
self.__forecast_labels.set("switch_on_colors", ThemeConstants.DEFAULT_ON_COLORS)
self.__forecast_labels.set("switch_off_colors", ThemeConstants.DEFAULT_OFF_COLORS)
self.__forecast_labels.set(
"switch_on_colors",
ThemeConstants.DEFAULT_ON_COLORS
)
self.__forecast_labels.set(
"switch_off_colors",
ThemeConstants.DEFAULT_OFF_COLORS
)
if not text_color_ok:
self.__forecast_labels.set("text_color", ThemeConstants.DEFAULT_TEXT_COLOR)
self.__forecast_labels.set(
"text_color",
ThemeConstants.DEFAULT_TEXT_COLOR
)
self.__current_theme = self.__theme_path
try:
with open(os.path.join(ThemeConstants.FOLDER,
ThemeConstants.CURRENT), "w") as current_theme:
with open(os.path.join(
ThemeConstants.FOLDER,
ThemeConstants.CURRENT
), "w") as current_theme:
current_theme.write(self.__theme_path)
except Exception:
pass
def initialize(self):
current_theme_file = os.path.join(ThemeConstants.FOLDER, ThemeConstants.CURRENT)
current_theme_file = os.path.join(
ThemeConstants.FOLDER,
ThemeConstants.CURRENT
)
if os.path.exists(current_theme_file):
with open(current_theme_file, "r") as current_theme_path:
theme_path = current_theme_path.read()
@@ -221,5 +317,12 @@ class Theme(object):
self.__theme_names[theme_name].setChecked(True)
self.__apply(theme_path)
else:
self.__theme_names[self.__pretty_name(ThemeConstants.DEFAULT)].setChecked(True)
self.__apply(os.path.join(ThemeConstants.FOLDER, ThemeConstants.DEFAULT))
self.__theme_names[
self.__pretty_name(ThemeConstants.DEFAULT)
].setChecked(True)
self.__apply(
os.path.join(
ThemeConstants.FOLDER,
ThemeConstants.DEFAULT
)
)

View File

@@ -3,12 +3,12 @@ from enum import Enum, auto
from io import BytesIO
import os.path
from shutil import rmtree
import urllib3
from zipfile import ZipFile
import aiohttp
import urllib3
from PyQt5.QtCore import QThread
from constants import Constants, Database, ChecksumWhat
from utilities import checksum_ok
import aiohttp
class ThreadStatus(Enum):
@@ -19,38 +19,41 @@ class ThreadStatus(Enum):
UNDEFINED = auto()
class DownloadThread(QThread):
def __init__(self):
super().__init__()
self.__status = ThreadStatus.UNDEFINED
self.reason = 0
@property
def status(self):
return self.__status
class _BaseDownloadThread(QThread):
def __init__(self, parent=None):
super().__init__(parent)
self.status = ThreadStatus.UNDEFINED
def __del__(self):
self.terminate()
self.wait()
super().__del__()
class DownloadThread(_BaseDownloadThread):
def __init__(self):
super().__init__()
self.reason = 0
def run(self):
self.status = ThreadStatus.UNDEFINED
try:
db = urllib3.PoolManager().request('GET', Database.LINK_LOC)
except urllib3.exceptions.MaxRetryError: # No internet connection.
self.__status = ThreadStatus.NO_CONNECTION_ERR
self.status = ThreadStatus.NO_CONNECTION_ERR
return
if db.status != 200:
self.reason = db.reason
self.__status = ThreadStatus.BAD_DOWNLOAD_ERR
self.status = ThreadStatus.BAD_DOWNLOAD_ERR
return
try:
is_checksum_ok = checksum_ok(db.data, ChecksumWhat.FOLDER)
except Exception:
self.__status = ThreadStatus.NO_CONNECTION_ERR
self.status = ThreadStatus.NO_CONNECTION_ERR
return
else:
if not is_checksum_ok:
self.__status = ThreadStatus.BAD_DOWNLOAD_ERR
self.status = ThreadStatus.BAD_DOWNLOAD_ERR
return
if os.path.exists(Constants.DATA_FOLDER):
rmtree(Constants.DATA_FOLDER)
@@ -58,25 +61,19 @@ class DownloadThread(QThread):
with ZipFile(BytesIO(db.data)) as zipped:
zipped.extractall()
except Exception:
self.__status = ThreadStatus.UNKNOWN_ERR
self.status = ThreadStatus.UNKNOWN_ERR
else:
self.__status = ThreadStatus.OK
self.status = ThreadStatus.OK
class UpadteSpaceWeatherThread(QThread):
class UpadteSpaceWeatherThread(_BaseDownloadThread):
__properties = ("xray", "prot_el", "ak_index", "sgas", "geo_storm")
def __init__(self, space_weather_data):
super().__init__()
self.__status = ThreadStatus.UNDEFINED
self.__space_weather_data = space_weather_data
@property
def status(self):
return self.__status
def __del__(self):
self.terminate()
self.wait()
async def __download_resource(self, session, link):
resp = await session.get(link)
return await resp.read()
@@ -92,18 +89,27 @@ class UpadteSpaceWeatherThread(QThread):
async def __download_resources(self, *links):
session = aiohttp.ClientSession()
properties = ("xray", "prot_el", "ak_index", "sgas", "geo_storm")
try:
t = [asyncio.create_task(self.__download_property(session, p)) for p in properties]
t = []
for p in self.__properties:
t.append(
asyncio.create_task(self.__download_property(session, p))
)
tot_images = range(len(Constants.FORECAST_IMGS))
t1 = [asyncio.create_task(self.__download_image(session, im_number)) for im_number in tot_images]
t1 = []
for im_number in tot_images:
t1.append(
asyncio.create_task(self.__download_image(session, im_number))
)
await asyncio.gather(*t, *t1)
except Exception:
self.__status = ThreadStatus.UNKNOWN_ERR
self.status = ThreadStatus.UNKNOWN_ERR
else:
self.__status = ThreadStatus.OK
self.status = ThreadStatus.OK
finally:
await session.close()
def run(self):
self.status = ThreadStatus.UNDEFINED
asyncio.run(self.__download_resources())

View File

@@ -48,14 +48,16 @@ def checksum_ok(data, what):
else:
raise ValueError("Wrong entry name.")
try:
reference = read_csv(Database.LINK_REF,
delimiter = Database.DELIMITER).iat[-1, n]
reference = read_csv(
Database.LINK_REF,
delimiter=Database.DELIMITER
).iat[-1, n]
except Exception:
raise
return code.hexdigest() == reference
def connect_events_to_func(events_to_connect, fun_to_connect, fun_args):
if fun_args:
if fun_args is not None:
for event in events_to_connect:
event.connect(partial(fun_to_connect, *fun_args))
else: