diff --git a/.gitignore b/.gitignore
index 9a6b398..b969a23 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ src/themes/.current_theme
designer.bat
launch.bat
.vscode/
+.code-workspace
diff --git a/artemis-workspace.code-workspace b/artemis-workspace.code-workspace
new file mode 100644
index 0000000..362d7c2
--- /dev/null
+++ b/artemis-workspace.code-workspace
@@ -0,0 +1,7 @@
+{
+ "folders": [
+ {
+ "path": "."
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/artemis.py b/src/artemis.py
index 397c185..ab67473 100644
--- a/src/artemis.py
+++ b/src/artemis.py
@@ -13,8 +13,7 @@ from PyQt5.QtWidgets import (QMainWindow,
QDesktopWidget,
QListWidgetItem,
QMessageBox,
- QSplashScreen,
- QTreeWidgetItem,)
+ QSplashScreen,)
from PyQt5.QtGui import QPixmap
from PyQt5 import uic
from PyQt5.QtCore import (QFileInfo,
@@ -22,22 +21,19 @@ from PyQt5.QtCore import (QFileInfo,
pyqtSlot,)
from audio_player import AudioPlayer
-from weatherdata import SpaceWeatherData, ForecastData
+from weatherdata import ForecastData
from download_window import DownloadWindow
-from switchable_label import SwitchableLabelsIterable
+from spaceweathermanager import SpaceWeatherManager
from constants import (Constants,
- Ftype,
GfdType,
Database,
ChecksumWhat,
Messages,
Signal,)
from themesmanager import ThemeManager
+from filters import Filters
from utilities import (checksum_ok,
- uncheck_and_emit,
pop_up,
- connect_events_to_func,
- filters_limit,
is_undef_freq,
is_undef_band,
format_numbers,
@@ -87,300 +83,24 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.signal_names = []
self.total_signals = 0
- 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
+ # Forecast
+ self.forecast_info_btn.clicked.connect(
+ lambda: webbrowser.open(Constants.SPACE_WEATHER_INFO)
)
+ self.forecast_data = ForecastData(self)
+ self.update_forecast_bar.clicked.connect(self.start_update_forecast)
+ self.update_forecast_bar.set_idle()
+ self.forecast_data.update_complete.connect(self.update_forecast)
- 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
- )
+ # Spaceweather manager
+ self.spaceweather_screen = SpaceWeatherManager(self)
- 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.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.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,
- self.k_min_storm_lbl,
- self.k_active_lbl,
- self.k_unsettled_lbl,
- self.k_quiet_lbl,
- self.k_very_quiet_lbl,
- self.k_inactive_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.space_weather_labels = (
- self.space_weather_lbl_0,
- self.space_weather_lbl_1,
- self.space_weather_lbl_2,
- self.space_weather_lbl_3,
- self.space_weather_lbl_4,
- self.space_weather_lbl_5,
- self.space_weather_lbl_6,
- self.space_weather_lbl_7,
- self.space_weather_lbl_8
- )
-
- for lab in self.space_weather_labels:
- lab.set_default_stylesheet()
-
- self.space_weather_label_container.labels = self.space_weather_labels
- self.space_weather_label_name_container.labels = [
- self.eme_lbl,
- self.ms_lbl,
- self.muf_lbl,
- self.hi_lbl,
- self.eu50_lbl,
- self.eu70_lbl,
- self.eu144_lbl,
- self.na_lbl,
- self.aurora_lbl
- ]
self.theme_manager = ThemeManager(self)
- # Manage frequency filters.
- self.frequency_filters_btns = (
- self.elf_filter_btn,
- self.slf_filter_btn,
- self.ulf_filter_btn,
- self.vlf_filter_btn,
- self.lf_filter_btn,
- self.mf_filter_btn,
- self.hf_filter_btn,
- self.vhf_filter_btn,
- self.uhf_filter_btn,
- self.shf_filter_btn,
- self.ehf_filter_btn,
- )
-
- connect_events_to_func(
- events_to_connect=[self.lower_freq_spinbox.valueChanged,
- self.upper_freq_spinbox.valueChanged,
- self.lower_freq_filter_unit.currentTextChanged,
- self.upper_freq_filter_unit.currentTextChanged,
- self.activate_low_freq_filter_btn.toggled],
- fun_to_connect=self.set_min_value_upper_limit,
- fun_args=[self.lower_freq_filter_unit,
- self.lower_freq_spinbox,
- self.upper_freq_filter_unit,
- self.upper_freq_spinbox]
- )
-
- connect_events_to_func(
- events_to_connect=[self.lower_freq_spinbox.valueChanged,
- self.upper_freq_spinbox.valueChanged,
- self.lower_freq_filter_unit.currentTextChanged,
- self.upper_freq_filter_unit.currentTextChanged,
- self.activate_low_freq_filter_btn.clicked,
- self.activate_up_freq_filter_btn.clicked,
- self.lower_freq_confidence.valueChanged,
- self.upper_freq_confidence.valueChanged],
- fun_to_connect=self.set_band_filter_label,
- fun_args=[self.activate_low_freq_filter_btn,
- self.lower_freq_spinbox,
- self.lower_freq_filter_unit,
- self.lower_freq_confidence,
- self.activate_up_freq_filter_btn,
- self.upper_freq_spinbox,
- self.upper_freq_filter_unit,
- self.upper_freq_confidence,
- self.freq_range_lbl]
- )
-
- self.activate_low_freq_filter_btn.toggled.connect(
- partial(self.activate_if_toggled,
- self.activate_low_freq_filter_btn,
- self.lower_freq_spinbox,
- self.lower_freq_filter_unit,
- self.lower_freq_confidence)
- )
-
- self.activate_up_freq_filter_btn.toggled.connect(
- partial(self.activate_if_toggled,
- self.activate_up_freq_filter_btn,
- self.upper_freq_spinbox,
- self.upper_freq_filter_unit,
- self.upper_freq_confidence)
- )
-
- 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
- ],
- 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
- ],
- 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.apply_remove_freq_filter_btn.clicked.connect(self.display_signals)
- self.reset_frequency_filters_btn.clicked.connect(
- partial(self.reset_fb_filters, Ftype.FREQ)
- )
-
- # Manage bandwidth filters.
-
- connect_events_to_func(
- events_to_connect=[self.lower_band_spinbox.valueChanged,
- self.upper_band_spinbox.valueChanged,
- self.lower_band_filter_unit.currentTextChanged,
- self.upper_band_filter_unit.currentTextChanged,
- self.activate_low_band_filter_btn.toggled],
- fun_to_connect=self.set_min_value_upper_limit,
- fun_args=[self.lower_band_filter_unit,
- self.lower_band_spinbox,
- self.upper_band_filter_unit,
- self.upper_band_spinbox]
- )
-
- connect_events_to_func(
- events_to_connect=[self.lower_band_spinbox.valueChanged,
- self.upper_band_spinbox.valueChanged,
- self.lower_band_filter_unit.currentTextChanged,
- self.upper_band_filter_unit.currentTextChanged,
- self.activate_low_band_filter_btn.clicked,
- self.activate_up_band_filter_btn.clicked,
- self.lower_band_confidence.valueChanged,
- self.upper_band_confidence.valueChanged],
- fun_to_connect=self.set_band_filter_label,
- fun_args=[self.activate_low_band_filter_btn,
- self.lower_band_spinbox,
- self.lower_band_filter_unit,
- self.lower_band_confidence,
- self.activate_up_band_filter_btn,
- self.upper_band_spinbox,
- self.upper_band_filter_unit,
- self.upper_band_confidence,
- self.band_range_lbl]
- )
-
- self.activate_low_band_filter_btn.toggled.connect(
- partial(self.activate_if_toggled,
- self.activate_low_band_filter_btn,
- self.lower_band_spinbox,
- self.lower_band_filter_unit,
- self.lower_band_confidence)
- )
-
- self.activate_up_band_filter_btn.toggled.connect(
- partial(self.activate_if_toggled,
- self.activate_up_band_filter_btn,
- self.upper_band_spinbox,
- self.upper_band_filter_unit,
- self.upper_band_confidence)
- )
-
- 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
- ],
- 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
- ],
- 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.apply_remove_band_filter_btn.clicked.connect(self.display_signals)
- 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.radar_btn,
- self.active_btn,
- self.inactive_btn,
- self.ham_btn,
- self.commercial_btn,
- self.aviation_btn,
- self.marine_btn,
- self.analogue_btn,
- self.digital_btn,
- self.trunked_btn,
- self.utility_btn,
- self.sat_btn,
- self.navigation_btn,
- self.interfering_btn,
- self.number_stations_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(
- simple_ones=[
- *self.cat_filter_btns,
- self.cat_at_least_one,
- 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)
+ self.filters = Filters(self)
# #######################################################################################
- self.reset_filters_btn.clicked.connect(self.reset_all_filters)
-
UrlColors = namedtuple("UrlColors", ["inactive", "active", "clicked"])
self.url_button.colors = UrlColors("#9f9f9f", "#4c75ff", "#942ccc")
self.category_labels = [
@@ -415,68 +135,6 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.url_button.clicked.connect(self.go_to_web_page_signal)
- # Set mode TreeView
-
- self.set_mode_tree_widget()
- 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(
- 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.
-
- 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(
- 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)
-
- # Set location filter screen.
-
- 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(
- 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(
- 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],
- fun_to_connect=self.set_acf_interval_label,
- fun_args=None
- )
-
# GFD
self.freq_search_gfd_btn.clicked.connect(partial(self.go_to_gfd, GfdType.FREQ))
self.location_search_gfd_btn.clicked.connect(partial(self.go_to_gfd, GfdType.LOC))
@@ -515,24 +173,6 @@ class Artemis(QMainWindow, Ui_MainWindow):
BandLabel(self.ehf_left, self.ehf, self.ehf_right),
]
- # Space weather
- self.info_now_btn.clicked.connect(
- lambda: webbrowser.open(Constants.SPACE_WEATHER_INFO)
- )
- self.update_now_bar.clicked.connect(self.start_update_space_weather)
- self.update_now_bar.set_idle()
- self.space_weather_data = SpaceWeatherData()
- self.space_weather_data.update_complete.connect(self.update_space_weather)
-
- # Forecast
- self.forecast_info_btn.clicked.connect(
- lambda: webbrowser.open(Constants.SPACE_WEATHER_INFO)
- )
- self.forecast_data = ForecastData(self)
- self.update_forecast_bar.clicked.connect(self.start_update_forecast)
- self.update_forecast_bar.set_idle()
- self.forecast_data.update_complete.connect(self.update_forecast)
-
self.main_tab.currentChanged.connect(self.hide_show_right_widget)
# Final operations.
@@ -565,16 +205,6 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.update_forecast_bar.set_updating()
self.forecast_data.update()
- @pyqtSlot()
- def start_update_space_weather(self):
- """Start the update of the space weather screen.
-
- Start the corresponding thread.
- """
- if not self.space_weather_data.is_updating:
- self.update_now_bar.set_updating()
- self.space_weather_data.update()
-
@pyqtSlot(bool)
def update_forecast(self, status_ok):
"""Update the 3-day forecast screen after a successful download.
@@ -590,175 +220,6 @@ class Artemis(QMainWindow, Ui_MainWindow):
text=Messages.BAD_DOWNLOAD_MSG).show()
self.forecast_data.remove_data()
- @pyqtSlot(bool)
- def update_space_weather(self, status_ok):
- """Update the space weather screen after a successful download.
-
- If the download was not successful throw a warning. In any case remove
- the downloaded data.
- """
- self.update_now_bar.set_idle()
- if status_ok:
- xray_long = safe_cast(self.space_weather_data.xray[-1][7], float)
-
- def format_text(letter, power):
- return letter + f"{xray_long * 10**power:.1f}"
-
- if xray_long < 1e-8 and xray_long != -1.00e+05:
- self.peak_flux_lbl.setText(format_text("= 1e-8 and xray_long < 1e-7:
- self.peak_flux_lbl.setText(format_text("A", 8))
- elif xray_long >= 1e-7 and xray_long < 1e-6:
- self.peak_flux_lbl.setText(format_text("B", 7))
- elif xray_long >= 1e-6 and xray_long < 1e-5:
- self.peak_flux_lbl.setText(format_text("C", 6))
- elif xray_long >= 1e-5 and xray_long < 1e-4:
- self.peak_flux_lbl.setText(format_text("M", 5))
- elif xray_long >= 1e-4:
- self.peak_flux_lbl.setText(format_text("X", 4))
- elif xray_long == -1.00e+05:
- self.peak_flux_lbl.setText("No Data")
-
- if xray_long < 1e-5 and xray_long != -1.00e+05:
- self.switchable_r_labels.switch_on(self.r0_now_lbl)
- elif xray_long >= 1e-5 and xray_long < 5e-5:
- self.switchable_r_labels.switch_on(self.r1_now_lbl)
- elif xray_long >= 5e-5 and xray_long < 1e-4:
- self.switchable_r_labels.switch_on(self.r2_now_lbl)
- elif xray_long >= 1e-4 and xray_long < 1e-3:
- self.switchable_r_labels.switch_on(self.r3_now_lbl)
- elif xray_long >= 1e-3 and xray_long < 2e-3:
- self.switchable_r_labels.switch_on(self.r4_now_lbl)
- elif xray_long >= 2e-3:
- self.switchable_r_labels.switch_on(self.r5_now_lbl)
- elif xray_long == -1.00e+05:
- self.switchable_r_labels.switch_off_all()
-
- pro10 = safe_cast(self.space_weather_data.prot_el[-1][8], float)
- if pro10 < 10 and pro10 != -1.00e+05:
- self.switchable_s_labels.switch_on(self.s0_now_lbl)
- elif pro10 >= 10 and pro10 < 100:
- self.switchable_s_labels.switch_on(self.s1_now_lbl)
- elif pro10 >= 100 and pro10 < 1000:
- self.switchable_s_labels.switch_on(self.s2_now_lbl)
- elif pro10 >= 1000 and pro10 < 10000:
- self.switchable_s_labels.switch_on(self.s3_now_lbl)
- elif pro10 >= 10000 and pro10 < 100000:
- self.switchable_s_labels.switch_on(self.s4_now_lbl)
- elif pro10 >= 100000:
- self.switchable_s_labels.switch_on(self.s5_now_lbl)
- elif pro10 == -1.00e+05:
- self.switchable_s_labels.switch_off_all()
-
- k_index = safe_cast(
- self.space_weather_data.ak_index[8][11].replace('.', ''), int
- )
- self.k_index_lbl.setText(str(k_index))
- a_index = safe_cast(
- self.space_weather_data.ak_index[7][7].replace('.', ''), int
- )
- self.a_index_lbl.setText(str(a_index))
-
- if k_index == 0:
- self.switchable_g_now_labels.switch_on(self.g0_now_lbl)
- self.k_storm_labels.switch_on(self.k_inactive_lbl)
- self.expected_noise_lbl.setText(" S0 - S1 (<-120 dBm) ")
- elif k_index == 1:
- self.switchable_g_now_labels.switch_on(self.g0_now_lbl)
- self.k_storm_labels.switch_on(self.k_very_quiet_lbl)
- self.expected_noise_lbl.setText(" S0 - S1 (<-120 dBm) ")
- elif k_index == 2:
- self.switchable_g_now_labels.switch_on(self.g0_now_lbl)
- self.k_storm_labels.switch_on(self.k_quiet_lbl)
- self.expected_noise_lbl.setText(" S1 - S2 (-115 dBm) ")
- elif k_index == 3:
- self.switchable_g_now_labels.switch_on(self.g0_now_lbl)
- self.k_storm_labels.switch_on(self.k_unsettled_lbl)
- self.expected_noise_lbl.setText(" S2 - S3 (-110 dBm) ")
- elif k_index == 4:
- self.switchable_g_now_labels.switch_on(self.g0_now_lbl)
- self.k_storm_labels.switch_on(self.k_active_lbl)
- self.expected_noise_lbl.setText(" S3 - S4 (-100 dBm) ")
- elif k_index == 5:
- self.switchable_g_now_labels.switch_on(self.g1_now_lbl)
- self.k_storm_labels.switch_on(self.k_min_storm_lbl)
- self.expected_noise_lbl.setText(" S4 - S6 (-90 dBm) ")
- elif k_index == 6:
- self.switchable_g_now_labels.switch_on(self.g2_now_lbl)
- self.k_storm_labels.switch_on(self.k_maj_storm_lbl)
- self.expected_noise_lbl.setText(" S6 - S9 (-80 dBm) ")
- elif k_index == 7:
- self.switchable_g_now_labels.switch_on(self.g3_now_lbl)
- self.k_storm_labels.switch_on(self.k_sev_storm_lbl)
- self.expected_noise_lbl.setText(" S9 - S20 (>-60 dBm) ")
- elif k_index == 8:
- self.switchable_g_now_labels.switch_on(self.g4_now_lbl)
- self.k_storm_labels.switch_on(self.k_very_sev_storm_lbl)
- self.expected_noise_lbl.setText(" S20 - S30 (>-60 dBm) ")
- elif k_index == 9:
- self.switchable_g_now_labels.switch_on(self.g5_now_lbl)
- self.k_storm_labels.switch_on(self.k_ex_sev_storm_lbl)
- self.expected_noise_lbl.setText(" S30+ (>>-60 dBm) ")
- self.expected_noise_lbl.switch_on()
-
- if a_index >= 0 and a_index < 8:
- self.a_storm_labels.switch_on(self.a_quiet_lbl)
- elif a_index >= 8 and a_index < 16:
- self.a_storm_labels.switch_on(self.a_unsettled_lbl)
- elif a_index >= 16 and a_index < 30:
- self.a_storm_labels.switch_on(self.a_active_lbl)
- elif a_index >= 30 and a_index < 50:
- self.a_storm_labels.switch_on(self.a_min_storm_lbl)
- elif a_index >= 50 and a_index < 100:
- self.a_storm_labels.switch_on(self.a_maj_storm_lbl)
- elif a_index >= 100 and a_index < 400:
- self.a_storm_labels.switch_on(self.a_sev_storm_lbl)
-
- index = self.space_weather_data.geo_storm[6].index("was") + 1
- k_index_24_hmax = safe_cast(
- self.space_weather_data.geo_storm[6][index], int
- )
- if k_index_24_hmax == 0:
- self.switchable_g_today_labels.switch_on(self.g0_today_lbl)
- elif k_index_24_hmax == 1:
- self.switchable_g_today_labels.switch_on(self.g0_today_lbl)
- elif k_index_24_hmax == 2:
- self.switchable_g_today_labels.switch_on(self.g0_today_lbl)
- elif k_index_24_hmax == 3:
- self.switchable_g_today_labels.switch_on(self.g0_today_lbl)
- elif k_index_24_hmax == 4:
- self.switchable_g_today_labels.switch_on(self.g0_today_lbl)
- elif k_index_24_hmax == 5:
- self.switchable_g_today_labels.switch_on(self.g1_today_lbl)
- elif k_index_24_hmax == 6:
- self.switchable_g_today_labels.switch_on(self.g2_today_lbl)
- elif k_index_24_hmax == 7:
- self.switchable_g_today_labels.switch_on(self.g3_today_lbl)
- elif k_index_24_hmax == 8:
- self.switchable_g_today_labels.switch_on(self.g4_today_lbl)
- elif k_index_24_hmax == 9:
- self.switchable_g_today_labels.switch_on(self.g5_today_lbl)
-
- val = safe_cast(
- self.space_weather_data.ak_index[7][2].replace('.', ''), int
- )
- self.sfi_lbl.setText(f"{val}")
- val = safe_cast(
- [x[4] for x in self.space_weather_data.sgas
- if "SSN" in x][0], int
- )
- self.sn_lbl.setText(f"{val:d}")
-
- for label, pixmap in zip(self.space_weather_labels,
- self.space_weather_data.images):
- label.pixmap = pixmap
- label.make_transparent()
- label.apply_pixmap()
- elif not self.closing:
- pop_up(self, title=Messages.BAD_DOWNLOAD,
- text=Messages.BAD_DOWNLOAD_MSG).show()
- self.space_weather_data.remove_data()
-
@pyqtSlot()
def go_to_gfd(self, by):
"""Open a browser tab with the GFD site.
@@ -780,69 +241,6 @@ class Artemis(QMainWindow, Ui_MainWindow):
except Exception:
pass
- @pyqtSlot(QListWidgetItem)
- def remove_if_unselected_modulation(self, item):
- """If an item is unselected from the modulations list, hide the item."""
- if not item.isSelected():
- self.show_matching_modulations(self.search_bar_modulation.text())
-
- @pyqtSlot(QListWidgetItem)
- def remove_if_unselected_location(self, item):
- """If an item is unselected from the locations list, hide the item."""
- if not item.isSelected():
- self.show_matching_locations(self.search_bar_location.text())
-
- @pyqtSlot(str)
- def show_matching_modulations(self, text):
- """Show the modulations which matches 'text'.
-
- The match criterion is defined in 'show_matching_strings'."""
- self.show_matching_strings(self.modulation_list, text)
-
- @pyqtSlot(str)
- def show_matching_locations(self, text):
- """Show the locations which matches 'text'.
-
- The match criterion is defined in 'show_matching_strings'."""
- self.show_matching_strings(self.locations_list, text)
-
- def show_matching_strings(self, list_elements, text):
- """Show all elements of QListWidget that matches (even partially) a target text.
-
- Arguments:
- list_elements -- the QListWidget
- text -- the target text.
- """
- for index in range(list_elements.count()):
- item = list_elements.item(index)
- if text.lower() in item.text().lower() or item.isSelected():
- item.setHidden(False)
- else:
- item.setHidden(True)
-
- def set_mode_tree_widget(self):
- """Construct the QTreeWidget for the 'Mode' screen."""
- for parent, children in Constants.MODES.items():
- iparent = QTreeWidgetItem([parent])
- self.mode_tree_widget.addTopLevelItem(iparent)
- for child in children:
- ichild = QTreeWidgetItem([child])
- iparent.addChild(ichild)
- self.mode_tree_widget.expandAll()
-
- def manage_mode_selections(self):
- """Rules the selection of childs items of the 'Mode' QTreeWidget.
-
- If a parent is selected all its children will be selected as well.
- """
- selected_items = self.mode_tree_widget.selectedItems()
- parents = Constants.MODES.keys()
- for parent in parents:
- for item in selected_items:
- if parent == item.text(0):
- for i in range(len(Constants.MODES[parent])):
- item.child(i).setSelected(True)
-
def set_initial_size(self):
"""Handle high resolution screens.
@@ -1050,109 +448,6 @@ class Artemis(QMainWindow, Ui_MainWindow):
values.insert(0, Constants.UNKNOWN)
return values
- @pyqtSlot()
- def set_min_value_upper_limit(self, lower_combo_box,
- lower_spin_box,
- upper_combo_box,
- upper_spin_box):
- """Forbid to a lower limit to be greater than the corresponding upper one.
-
- Used for frequency and bandwidth screens."""
- if lower_spin_box.isEnabled():
- unit_conversion = {'Hz': ['kHz', 'MHz', 'GHz'],
- 'kHz': ['MHz', 'GHz'],
- 'MHz': ['GHz']}
- lower_units = lower_combo_box.currentText()
- upper_units = upper_combo_box.currentText()
- lower_value = lower_spin_box.value()
- inf_limit = (lower_value * Constants.CONVERSION_FACTORS[lower_units]) \
- // Constants.CONVERSION_FACTORS[upper_units]
- counter = 0
- while inf_limit > upper_spin_box.maximum():
- counter += 1
- inf_limit //= 1000
- if upper_spin_box.minimum() != inf_limit:
- upper_spin_box.setMinimum(inf_limit)
- if counter > 0:
- new_unit = unit_conversion[upper_units][counter - 1]
- upper_combo_box.disconnect()
- upper_combo_box.setCurrentText(new_unit)
- upper_combo_box.currentTextChanged.connect(
- partial(
- self.set_min_value_upper_limit,
- lower_combo_box,
- lower_spin_box,
- upper_combo_box,
- upper_spin_box
- )
- )
-
- @pyqtSlot()
- def set_band_filter_label(self,
- activate_low_btn,
- lower_spinbox,
- lower_unit,
- lower_confidence,
- activate_up_btn,
- upper_spinbox,
- upper_unit,
- upper_confidence,
- range_lbl):
- """Display the actual range applied for the signal's property search.
-
- Used for frequency and bandwidth screens."""
- activate_low = False
- activate_high = False
- color = self.inactive_color
- title = ''
- to_display = ''
- if activate_low_btn.isChecked():
- activate_low = True
- color = self.active_color
- 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()
- else:
- to_display += 'DC'
- to_display += Constants.RANGE_SEPARATOR
- if activate_up_btn.isChecked():
- max_value = upper_spinbox.value()
- activate_high = True
- 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()
- else:
- to_display += 'INF'
- if activate_low and activate_high:
- title = 'Band-pass\n\n'
- elif activate_low and not activate_high:
- title = 'Low-pass\n\n'
- elif not activate_low and activate_high:
- title = 'High-pass\n\n'
- else:
- title = "Selected range:\n\n"
- to_display = "Inactive"
- to_display = title + to_display
- range_lbl.setText(to_display)
- range_lbl.setStyleSheet(f'color: {color};')
-
- @pyqtSlot()
- def set_acf_interval_label(self):
- """Display the actual acf interval for the search."""
- tolerance = self.acf_spinbox.value() * self.acf_confidence.value() / 100
- if tolerance > 0:
- 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)
- self.acf_range_lbl.setStyleSheet(f"color: {self.active_color}")
-
@pyqtSlot()
def activate_if_toggled(self, radio_btn, *widgets):
"""If radio_btn is toggled, activate all *widgets.
@@ -1169,14 +464,7 @@ class Artemis(QMainWindow, Ui_MainWindow):
text = self.search_bar.text()
available_signals = 0
for index, signal_name in enumerate(self.signal_names):
- if all([text.lower() in signal_name.lower(),
- self.frequency_filters_ok(signal_name),
- self.band_filters_ok(signal_name),
- self.category_filters_ok(signal_name),
- self.mode_filters_ok(signal_name),
- self.modulation_filters_ok(signal_name),
- self.location_filters_ok(signal_name),
- self.acf_filters_ok(signal_name)]):
+ if text.lower() in signal_name.lower() and self.filters.ok(signal_name):
self.signals_list.item(index).setHidden(False)
available_signals += 1
else:
@@ -1195,267 +483,6 @@ class Artemis(QMainWindow, Ui_MainWindow):
f"{available_signals} out of {self.total_signals} signals displayed."
)
- @pyqtSlot()
- def reset_fb_filters(self, ftype):
- """Reset the Frequency or Bandwidth depending on 'ftype'.
-
- ftype can be either Ftype.FREQ or Ftype.BAND.
- """
- 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')
- activate_up = getattr(self, 'activate_up_' + ftype + '_filter_btn')
- lower_unit = getattr(self, 'lower_' + ftype + '_filter_unit')
- upper_unit = getattr(self, 'upper_' + ftype + '_filter_unit')
- lower_spinbox = getattr(self, 'lower_' + ftype + '_spinbox')
- 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:
- if f.isChecked():
- f.setChecked(False)
- uncheck_and_emit(apply_remove_btn)
- if include_undef_btn.isChecked():
- include_undef_btn.setChecked(False)
- uncheck_and_emit(activate_low)
- uncheck_and_emit(activate_up)
- lower_unit.setCurrentText("MHz")
- upper_unit.setCurrentText("MHz")
- lower_spinbox.setValue(default_val)
- upper_spinbox.setMinimum(1)
- upper_spinbox.setValue(default_val)
- lower_confidence.setValue(0)
- upper_confidence.setValue(0)
-
- @pyqtSlot()
- def reset_cat_filters(self):
- """Reset the category filter screen."""
- uncheck_and_emit(self.apply_remove_cat_filter_btn)
- for f in self.cat_filter_btns:
- if f.isChecked():
- f.setChecked(False)
- self.cat_at_least_one.setChecked(True)
-
- @pyqtSlot()
- def reset_mode_filters(self):
- """Reset the mode filter screen."""
- uncheck_and_emit(self.apply_remove_mode_filter_btn)
- parents = Constants.MODES.keys()
- selected_children = []
- for item in self.mode_tree_widget.selectedItems():
- if item.text(0) in parents:
- item.setSelected(False)
- else:
- selected_children.append(item)
- for children in selected_children:
- children.setSelected(False)
- if self.include_unknown_modes_btn.isChecked():
- self.include_unknown_modes_btn.setChecked(False)
-
- @pyqtSlot()
- def reset_modulation_filters(self):
- """Reset the modulation filter screen."""
- uncheck_and_emit(self.apply_remove_modulation_filter_btn)
- self.search_bar_modulation.setText('')
- self.show_matching_strings(
- self.modulation_list,
- self.search_bar_modulation.text()
- )
- for i in range(self.modulation_list.count()):
- if self.modulation_list.item(i).isSelected():
- self.modulation_list.item(i).setSelected(False)
-
- @pyqtSlot()
- def reset_location_filters(self):
- """Reset the location filter screen."""
- uncheck_and_emit(self.apply_remove_location_filter_btn)
- self.search_bar_location.setText('')
- self.show_matching_strings(
- self.locations_list,
- self.search_bar_location.text()
- )
- for i in range(self.locations_list.count()):
- if self.locations_list.item(i).isSelected():
- self.locations_list.item(i).setSelected(False)
-
- @pyqtSlot()
- def reset_acf_filters(self):
- """Reset the acf filter screen."""
- uncheck_and_emit(self.apply_remove_acf_filter_btn)
- if self.include_undef_acf.isChecked():
- self.include_undef_acf.setChecked(False)
- self.acf_spinbox.setValue(50)
- self.acf_confidence.setValue(0)
-
- def frequency_filters_ok(self, signal_name):
- """Evalaute if the signal matches the frequency filters."""
- if not self.apply_remove_freq_filter_btn.isChecked():
- return True
- undef_freq = is_undef_freq(self.db.loc[signal_name])
- if undef_freq:
- if self.include_undef_freqs.isChecked():
- return True
- else:
- return False
-
- signal_freqs = (
- safe_cast(self.db.at[signal_name, Signal.INF_FREQ], int),
- safe_cast(self.db.at[signal_name, Signal.SUP_FREQ], int)
- )
-
- band_filter_ok = False
- any_checked = False
- for btn, band_limits in zip(self.frequency_filters_btns, Constants.BANDS):
- if btn.isChecked():
- any_checked = True
- if signal_freqs[0] < band_limits.upper and signal_freqs[1] >= band_limits.lower:
- band_filter_ok = True
- lower_limit_ok = True
- upper_limit_ok = True
- if self.activate_low_freq_filter_btn.isChecked():
- if not signal_freqs[1] >= filters_limit(self.lower_freq_spinbox,
- self.lower_freq_filter_unit,
- self.lower_freq_confidence, -1):
- lower_limit_ok = False
- if self.activate_up_freq_filter_btn.isChecked():
- if not signal_freqs[0] < filters_limit(self.upper_freq_spinbox,
- self.upper_freq_filter_unit,
- self.upper_freq_confidence):
- upper_limit_ok = False
- if any_checked:
- return band_filter_ok and lower_limit_ok and upper_limit_ok
- else:
- return lower_limit_ok and upper_limit_ok
-
- def band_filters_ok(self, signal_name):
- """Evalaute if the signal matches the band filters."""
- if not self.apply_remove_band_filter_btn.isChecked():
- return True
- undef_band = is_undef_band(self.db.loc[signal_name])
- if undef_band:
- if self.include_undef_bands.isChecked():
- return True
- else:
- return False
-
- signal_bands = (
- safe_cast(self.db.at[signal_name, Signal.INF_BAND], int),
- safe_cast(self.db.at[signal_name, Signal.SUP_BAND], int)
- )
-
- lower_limit_ok = True
- upper_limit_ok = True
- if self.activate_low_band_filter_btn.isChecked():
- if not signal_bands[1] >= filters_limit(self.lower_band_spinbox,
- self.lower_band_filter_unit,
- self.lower_band_confidence, -1):
- lower_limit_ok = False
- if self.activate_up_band_filter_btn.isChecked():
- if not signal_bands[0] < filters_limit(self.upper_band_spinbox,
- self.upper_band_filter_unit,
- self.upper_band_confidence):
- upper_limit_ok = False
- return lower_limit_ok and upper_limit_ok
-
- def category_filters_ok(self, signal_name):
- """Evalaute if the signal matches the category filters."""
- if not self.apply_remove_cat_filter_btn.isChecked():
- return True
- cat_code = self.db.at[signal_name, Signal.CATEGORY_CODE]
- cat_checked = 0
- positive_cases = 0
- for index, cat in enumerate(self.cat_filter_btns):
- if cat.isChecked():
- cat_checked += 1
- if cat_code[index] == '1':
- positive_cases += 1
- if self.cat_at_least_one.isChecked():
- return positive_cases > 0
- else:
- return cat_checked == positive_cases and cat_checked > 0
-
- def mode_filters_ok(self, signal_name):
- """Evalaute if the signal matches the mode filters."""
- if not self.apply_remove_mode_filter_btn.isChecked():
- return True
- signal_mode = self.db.at[signal_name, Signal.MODE]
- if signal_mode == Constants.UNKNOWN:
- if self.include_unknown_modes_btn.isChecked():
- return True
- else:
- return False
- selected_items = [item for item in self.mode_tree_widget.selectedItems()]
- selected_items_text = [i.text(0) for i in selected_items]
- parents = [
- item for item in selected_items_text
- if item in Constants.MODES.keys()
- ]
- ok = []
- for item in selected_items:
- if item.text(0) in parents:
- ok.append(item.text(0) in signal_mode)
- elif not item.parent().isSelected():
- ok.append(item.text(0) == signal_mode)
- return any(ok)
-
- def get_field_entries(self, signal_name, field, separator=Constants.FIELD_SEPARATOR):
- """Take a signal name, a column label and optionally a separator string.
-
- Return a list obtained by splitting the signal field with separator."""
- return [
- x.strip() for x in self.db.at[signal_name, field].split(separator)
- ]
-
- def modulation_filters_ok(self, signal_name):
- """Evalaute if the signal matches the modulation filters."""
- if not self.apply_remove_modulation_filter_btn.isChecked():
- return True
- signal_modulation = self.get_field_entries(
- signal_name, Signal.MODULATION
- )
- for item in self.modulation_list.selectedItems():
- if item.text() in signal_modulation:
- return True
- return False
-
- def location_filters_ok(self, signal_name):
- """Evalaute if the signal matches the location filters."""
- if not self.apply_remove_location_filter_btn.isChecked():
- return True
- signal_locations = self.get_field_entries(
- signal_name, Signal.LOCATION
- )
- for item in self.locations_list.selectedItems():
- if item.text() in signal_locations:
- return True
- return False
-
- def acf_filters_ok(self, signal_name):
- """Evalaute if the signal matches the acf filters."""
- if not self.apply_remove_acf_filter_btn.isChecked():
- return True
- signal_acf = self.db.at[signal_name, Signal.ACF]
- if signal_acf == Constants.UNKNOWN:
- if self.include_undef_acf.isChecked():
- return True
- else:
- return False
- else:
- signal_acf = safe_cast(signal_acf.rstrip("ms"), float)
- tolerance = self.acf_spinbox.value() * self.acf_confidence.value() / 100
- upper_limit = self.acf_spinbox.value() + tolerance
- lower_limit = self.acf_spinbox.value() - tolerance
- if signal_acf <= upper_limit and signal_acf >= lower_limit:
- return True
- else:
- return False
-
@pyqtSlot(QListWidgetItem, QListWidgetItem)
def display_specs(self, item, previous_item):
"""Display the signal properties.
@@ -1582,20 +609,6 @@ class Artemis(QMainWindow, Ui_MainWindow):
for band_label in self.band_labels:
self.activate_band_category(band_label, False)
- @pyqtSlot()
- def reset_all_filters(self):
- """Reset all filter screens.
-
- Show all available signals.
- """
- self.reset_frequency_filters_btn.clicked.emit()
- self.reset_band_filters_btn.clicked.emit()
- self.reset_cat_filters_btn.clicked.emit()
- self.reset_mode_filters_btn.clicked.emit()
- self.reset_modulation_filters_btn.clicked.emit()
- self.reset_location_filters_btn.clicked.emit()
- self.reset_acf_filters_btn.clicked.emit()
-
@pyqtSlot()
def go_to_web_page_signal(self):
"""Go the web page of the signal's wiki.
diff --git a/src/audio_player.py b/src/audio_player.py
index e1b8a90..1ee1452 100644
--- a/src/audio_player.py
+++ b/src/audio_player.py
@@ -10,7 +10,7 @@ class AudioPlayer(QObject):
"""Subclass QObject. Audio player widget for the audio samples.
The only public methods are the __init__
- method, set_audio_player, which loads the current file and refresh_btns_colors.
+ method, set_audio_player, which loads the current file and refresh.
Everything else is managed internally."""
_TIME_STEP = 500 # Milliseconds.
@@ -47,7 +47,7 @@ class AudioPlayer(QObject):
self._pause.setIconSize(self._pause.size())
self._stop.setIconSize(self._stop.size())
self._loop.setIconSize(self._loop.size())
- self.refresh_btns_colors(active_color, inactive_color)
+ self.refresh(active_color, inactive_color)
@pyqtSlot()
def _set_loop_icon(self):
@@ -67,7 +67,7 @@ class AudioPlayer(QObject):
)
self._loop.setIcon(loop_icon)
- def refresh_btns_colors(self, active_color, inactive_color):
+ def refresh(self, active_color, inactive_color):
"""Repaint the buttons of the widgetd after the theme has changed."""
self._active_color = active_color
self._inactive_color = inactive_color
diff --git a/src/constants.py b/src/constants.py
index dbe2c34..e875826 100644
--- a/src/constants.py
+++ b/src/constants.py
@@ -41,7 +41,7 @@ class Messages:
NO_CONNECTION = "No connection"
NO_CONNECTION_MSG = "Unable to establish an internet connection."
BAD_DOWNLOAD = "Something went wrong"
- BAD_DOWNLOAD_MSG = "Something went wrong with the downaload.\nCheck your internet connection and try again."
+ BAD_DOWNLOAD_MSG = "Something went wrong with the download.\nCheck your internet connection and try again."
SLOW_CONN = "Slow internet connection"
SLOW_CONN_MSG = "Your internet connection is unstable or too slow."
diff --git a/src/filters.py b/src/filters.py
new file mode 100644
index 0000000..fff8f0f
--- /dev/null
+++ b/src/filters.py
@@ -0,0 +1,850 @@
+"""This module contains all the filter-related classes and functions.
+
+The only class exposed is Filters which provides the following methods:
+- ok(signal_name): to check if all the filters are passed;
+- reset(): to reset all the applied filters;
+- refresh(): used when the theme is changed."""
+
+from collections import namedtuple
+from functools import partial
+import webbrowser
+
+from PyQt5.QtWidgets import QListWidgetItem, QTreeWidgetItem
+from PyQt5.QtCore import pyqtSlot, QObject
+
+from constants import (Constants,
+ Ftype,
+ Signal,)
+from utilities import (uncheck_and_emit,
+ connect_events_to_func,
+ filters_limit,
+ is_undef_freq,
+ is_undef_band,
+ safe_cast,
+ show_matching_strings,
+ get_field_entries,)
+
+
+class _BaseFilter(QObject):
+ """Base class for all filters."""
+
+ def __init__(self, owner):
+ """Positional argument:
+ owner - the object containing the filter screen."""
+ super().__init__()
+ self._owner = owner
+
+ def refresh(self):
+ """Refresh the screen."""
+ pass
+
+
+class _FreqBandMixIn:
+ """Mixin class for the frequency and band filters.
+
+ Provides some functions used in both classes."""
+
+ @pyqtSlot()
+ def _set_min_value_upper_limit(self, lower_combo_box,
+ lower_spin_box,
+ upper_combo_box,
+ upper_spin_box):
+ """Forbid to a lower limit to be greater than the corresponding upper one.
+
+ Used for frequency and bandwidth screens."""
+ if lower_spin_box.isEnabled():
+ unit_conversion = {
+ 'Hz': ['kHz', 'MHz', 'GHz'],
+ 'kHz': ['MHz', 'GHz'],
+ 'MHz': ['GHz']
+ }
+ lower_units = lower_combo_box.currentText()
+ upper_units = upper_combo_box.currentText()
+ lower_value = lower_spin_box.value()
+ inf_limit = (lower_value * Constants.CONVERSION_FACTORS[lower_units]) \
+ // Constants.CONVERSION_FACTORS[upper_units]
+ counter = 0
+ while inf_limit > upper_spin_box.maximum():
+ counter += 1
+ inf_limit //= 1000
+ if upper_spin_box.minimum() != inf_limit:
+ upper_spin_box.setMinimum(inf_limit)
+ if counter > 0:
+ new_unit = unit_conversion[upper_units][counter - 1]
+ upper_combo_box.disconnect()
+ upper_combo_box.setCurrentText(new_unit)
+ upper_combo_box.currentTextChanged.connect(
+ partial(
+ self._set_min_value_upper_limit,
+ lower_combo_box,
+ lower_spin_box,
+ upper_combo_box,
+ upper_spin_box
+ )
+ )
+
+ @pyqtSlot()
+ def _reset_fb_filters(self, ftype):
+ """Reset the Frequency or Bandwidth depending on 'ftype'.
+
+ ftype can be either Ftype.FREQ or Ftype.BAND.
+ """
+ if ftype != Ftype.FREQ and ftype != Ftype.BAND:
+ raise ValueError("Wrong ftype in function '_reset_fb_filters'")
+
+ apply_remove_btn = getattr(self._owner, 'apply_remove_' + ftype + '_filter_btn')
+ include_undef_btn = getattr(self._owner, 'include_undef_' + ftype + 's')
+ activate_low = getattr(self._owner, 'activate_low_' + ftype + '_filter_btn')
+ activate_up = getattr(self._owner, 'activate_up_' + ftype + '_filter_btn')
+ lower_unit = getattr(self._owner, 'lower_' + ftype + '_filter_unit')
+ upper_unit = getattr(self._owner, 'upper_' + ftype + '_filter_unit')
+ lower_spinbox = getattr(self._owner, 'lower_' + ftype + '_spinbox')
+ upper_spinbox = getattr(self._owner, 'upper_' + ftype + '_spinbox')
+ lower_confidence = getattr(self._owner, 'lower_' + ftype + '_confidence')
+ upper_confidence = getattr(self._owner, 'lower_' + ftype + '_confidence')
+
+ default_val = 1 if ftype == Ftype.FREQ else 5000
+ if ftype == Ftype.FREQ:
+ for f in self._frequency_filters_btns:
+ if f.isChecked():
+ f.setChecked(False)
+ uncheck_and_emit(apply_remove_btn)
+ if include_undef_btn.isChecked():
+ include_undef_btn.setChecked(False)
+ uncheck_and_emit(activate_low)
+ uncheck_and_emit(activate_up)
+ lower_unit.setCurrentText("MHz")
+ upper_unit.setCurrentText("MHz")
+ lower_spinbox.setValue(default_val)
+ upper_spinbox.setMinimum(1)
+ upper_spinbox.setValue(default_val)
+ lower_confidence.setValue(0)
+ upper_confidence.setValue(0)
+
+ @pyqtSlot()
+ def _set_band_filter_label(self,
+ activate_low_btn,
+ lower_spinbox,
+ lower_unit,
+ lower_confidence,
+ activate_up_btn,
+ upper_spinbox,
+ upper_unit,
+ upper_confidence,
+ range_lbl):
+ """Display the actual range applied for the signal's property search.
+
+ Used for frequency and bandwidth screens."""
+ activate_low = False
+ activate_high = False
+ color = self._owner.inactive_color
+ title = ''
+ to_display = ''
+ if activate_low_btn.isChecked():
+ activate_low = True
+ color = self._owner.active_color
+ 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()
+ else:
+ to_display += 'DC'
+ to_display += Constants.RANGE_SEPARATOR
+ if activate_up_btn.isChecked():
+ max_value = upper_spinbox.value()
+ activate_high = True
+ color = self._owner.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()
+ else:
+ to_display += 'INF'
+ if activate_low and activate_high:
+ title = 'Band-pass\n\n'
+ elif activate_low and not activate_high:
+ title = 'Low-pass\n\n'
+ elif not activate_low and activate_high:
+ title = 'High-pass\n\n'
+ else:
+ title = "Selected range:\n\n"
+ to_display = "Inactive"
+ to_display = title + to_display
+ range_lbl.setText(to_display)
+ range_lbl.setStyleSheet(f'color: {color};')
+
+
+class FreqFilter(_BaseFilter, _FreqBandMixIn):
+ """Frequency filter class."""
+
+ def __init__(self, owner):
+ super().__init__(owner)
+ self.apply_remove_btn = self._owner.apply_remove_freq_filter_btn
+ self.reset_btn = self._owner.reset_frequency_filters_btn
+ self._frequency_filters_btns = (
+ self._owner.elf_filter_btn,
+ self._owner.slf_filter_btn,
+ self._owner.ulf_filter_btn,
+ self._owner.vlf_filter_btn,
+ self._owner.lf_filter_btn,
+ self._owner.mf_filter_btn,
+ self._owner.hf_filter_btn,
+ self._owner.vhf_filter_btn,
+ self._owner.uhf_filter_btn,
+ self._owner.shf_filter_btn,
+ self._owner.ehf_filter_btn,
+ )
+
+ self.apply_remove_btn.set_texts(Constants.APPLY, Constants.REMOVE)
+ self.apply_remove_btn.set_slave_filters(
+ simple_ones=[
+ *self._frequency_filters_btns,
+ self._owner.include_undef_freqs,
+ self._owner.activate_low_freq_filter_btn,
+ self._owner.activate_up_freq_filter_btn
+ ],
+ radio_1=self._owner.activate_low_freq_filter_btn,
+ ruled_by_radio_1=[
+ self._owner.lower_freq_spinbox,
+ self._owner.lower_freq_filter_unit,
+ self._owner.lower_freq_confidence
+ ],
+ radio_2=self._owner.activate_up_freq_filter_btn,
+ ruled_by_radio_2=[
+ self._owner.upper_freq_spinbox,
+ self._owner.upper_freq_filter_unit,
+ self._owner.upper_freq_confidence
+ ]
+ )
+
+ connect_events_to_func(
+ events_to_connect=[self._owner.lower_freq_spinbox.valueChanged,
+ self._owner.upper_freq_spinbox.valueChanged,
+ self._owner.lower_freq_filter_unit.currentTextChanged,
+ self._owner.upper_freq_filter_unit.currentTextChanged,
+ self._owner.activate_low_freq_filter_btn.toggled],
+ fun_to_connect=self._set_min_value_upper_limit,
+ fun_args=[self._owner.lower_freq_filter_unit,
+ self._owner.lower_freq_spinbox,
+ self._owner.upper_freq_filter_unit,
+ self._owner.upper_freq_spinbox]
+ )
+
+ connect_events_to_func(
+ events_to_connect=[self._owner.lower_freq_spinbox.valueChanged,
+ self._owner.upper_freq_spinbox.valueChanged,
+ self._owner.lower_freq_filter_unit.currentTextChanged,
+ self._owner.upper_freq_filter_unit.currentTextChanged,
+ self._owner.activate_low_freq_filter_btn.clicked,
+ self._owner.activate_up_freq_filter_btn.clicked,
+ self._owner.lower_freq_confidence.valueChanged,
+ self._owner.upper_freq_confidence.valueChanged],
+ fun_to_connect=self._set_band_filter_label,
+ fun_args=[self._owner.activate_low_freq_filter_btn,
+ self._owner.lower_freq_spinbox,
+ self._owner.lower_freq_filter_unit,
+ self._owner.lower_freq_confidence,
+ self._owner.activate_up_freq_filter_btn,
+ self._owner.upper_freq_spinbox,
+ self._owner.upper_freq_filter_unit,
+ self._owner.upper_freq_confidence,
+ self._owner.freq_range_lbl]
+ )
+
+ self._owner.activate_low_freq_filter_btn.toggled.connect(
+ partial(self._owner.activate_if_toggled,
+ self._owner.activate_low_freq_filter_btn,
+ self._owner.lower_freq_spinbox,
+ self._owner.lower_freq_filter_unit,
+ self._owner.lower_freq_confidence)
+ )
+
+ self._owner.activate_up_freq_filter_btn.toggled.connect(
+ partial(self._owner.activate_if_toggled,
+ self._owner.activate_up_freq_filter_btn,
+ self._owner.upper_freq_spinbox,
+ self._owner.upper_freq_filter_unit,
+ self._owner.upper_freq_confidence)
+ )
+
+ @pyqtSlot()
+ def reset(self):
+ """Reset the filter screen."""
+ self._reset_fb_filters(Ftype.FREQ)
+
+ def _ok(self, signal_name):
+ """Evalaute if the signal matches the frequency filters."""
+ if not self.apply_remove_btn.isChecked():
+ return True
+ undef_freq = is_undef_freq(self._owner.db.loc[signal_name])
+ if undef_freq:
+ if self._owner.include_undef_freqs.isChecked():
+ return True
+ else:
+ return False
+
+ signal_freqs = (
+ safe_cast(self._owner.db.at[signal_name, Signal.INF_FREQ], int),
+ safe_cast(self._owner.db.at[signal_name, Signal.SUP_FREQ], int)
+ )
+
+ band_filter_ok = False
+ any_checked = False
+ for btn, band_limits in zip(self._frequency_filters_btns, Constants.BANDS):
+ if btn.isChecked():
+ any_checked = True
+ if signal_freqs[0] < band_limits.upper and signal_freqs[1] >= band_limits.lower:
+ band_filter_ok = True
+ lower_limit_ok = True
+ upper_limit_ok = True
+ if self._owner.activate_low_freq_filter_btn.isChecked():
+ if not signal_freqs[1] >= filters_limit(self._owner.lower_freq_spinbox,
+ self._owner.lower_freq_filter_unit,
+ self._owner.lower_freq_confidence, -1):
+ lower_limit_ok = False
+ if self._owner.activate_up_freq_filter_btn.isChecked():
+ if not signal_freqs[0] < filters_limit(self._owner.upper_freq_spinbox,
+ self._owner.upper_freq_filter_unit,
+ self._owner.upper_freq_confidence):
+ upper_limit_ok = False
+ if any_checked:
+ return band_filter_ok and lower_limit_ok and upper_limit_ok
+ else:
+ return lower_limit_ok and upper_limit_ok
+
+ def refresh(self):
+ """Extend _BaseFilter.refresh."""
+ super().refresh()
+ self._set_band_filter_label(
+ self._owner.activate_low_band_filter_btn,
+ self._owner.lower_band_spinbox,
+ self._owner.lower_band_filter_unit,
+ self._owner.lower_band_confidence,
+ self._owner.activate_up_band_filter_btn,
+ self._owner.upper_band_spinbox,
+ self._owner.upper_band_filter_unit,
+ self._owner.upper_band_confidence,
+ self._owner.band_range_lbl
+ )
+
+
+class BandFilter(_BaseFilter, _FreqBandMixIn):
+ """Band filter class."""
+ def __init__(self, owner):
+ super().__init__(owner)
+ self.apply_remove_btn = self._owner.apply_remove_band_filter_btn
+ self.reset_btn = self._owner.reset_band_filters_btn
+ connect_events_to_func(
+ events_to_connect=[self._owner.lower_band_spinbox.valueChanged,
+ self._owner.upper_band_spinbox.valueChanged,
+ self._owner.lower_band_filter_unit.currentTextChanged,
+ self._owner.upper_band_filter_unit.currentTextChanged,
+ self._owner.activate_low_band_filter_btn.toggled],
+ fun_to_connect=self._set_min_value_upper_limit,
+ fun_args=[self._owner.lower_band_filter_unit,
+ self._owner.lower_band_spinbox,
+ self._owner.upper_band_filter_unit,
+ self._owner.upper_band_spinbox]
+ )
+
+ connect_events_to_func(
+ events_to_connect=[self._owner.lower_band_spinbox.valueChanged,
+ self._owner.upper_band_spinbox.valueChanged,
+ self._owner.lower_band_filter_unit.currentTextChanged,
+ self._owner.upper_band_filter_unit.currentTextChanged,
+ self._owner.activate_low_band_filter_btn.clicked,
+ self._owner.activate_up_band_filter_btn.clicked,
+ self._owner.lower_band_confidence.valueChanged,
+ self._owner.upper_band_confidence.valueChanged],
+ fun_to_connect=self._set_band_filter_label,
+ fun_args=[self._owner.activate_low_band_filter_btn,
+ self._owner.lower_band_spinbox,
+ self._owner.lower_band_filter_unit,
+ self._owner.lower_band_confidence,
+ self._owner.activate_up_band_filter_btn,
+ self._owner.upper_band_spinbox,
+ self._owner.upper_band_filter_unit,
+ self._owner.upper_band_confidence,
+ self._owner.band_range_lbl]
+ )
+
+ self._owner.activate_low_band_filter_btn.toggled.connect(
+ partial(self._owner.activate_if_toggled,
+ self._owner.activate_low_band_filter_btn,
+ self._owner.lower_band_spinbox,
+ self._owner.lower_band_filter_unit,
+ self._owner.lower_band_confidence)
+ )
+
+ self._owner.activate_up_band_filter_btn.toggled.connect(
+ partial(self._owner.activate_if_toggled,
+ self._owner.activate_up_band_filter_btn,
+ self._owner.upper_band_spinbox,
+ self._owner.upper_band_filter_unit,
+ self._owner.upper_band_confidence)
+ )
+
+ self.apply_remove_btn.set_texts(Constants.APPLY, Constants.REMOVE)
+ self.apply_remove_btn.set_slave_filters(
+ simple_ones=[
+ self._owner.include_undef_bands,
+ self._owner.activate_low_band_filter_btn,
+ self._owner.activate_up_band_filter_btn
+ ],
+ radio_1=self._owner.activate_low_band_filter_btn,
+ ruled_by_radio_1=[
+ self._owner.lower_band_spinbox,
+ self._owner.lower_band_filter_unit,
+ self._owner.lower_band_confidence
+ ],
+ radio_2=self._owner.activate_up_band_filter_btn,
+ ruled_by_radio_2=[
+ self._owner.upper_band_spinbox,
+ self._owner.upper_band_filter_unit,
+ self._owner.upper_band_confidence
+ ]
+ )
+
+ @pyqtSlot()
+ def reset(self):
+ """Reset the filter screen."""
+ self._reset_fb_filters(Ftype.BAND)
+
+ def _ok(self, signal_name):
+ """Evalaute if the signal matches the band filters."""
+ if not self.apply_remove_btn.isChecked():
+ return True
+ undef_band = is_undef_band(self._owner.db.loc[signal_name])
+ if undef_band:
+ if self._owner.include_undef_bands.isChecked():
+ return True
+ else:
+ return False
+
+ signal_bands = (
+ safe_cast(self._owner.db.at[signal_name, Signal.INF_BAND], int),
+ safe_cast(self._owner.db.at[signal_name, Signal.SUP_BAND], int)
+ )
+
+ lower_limit_ok = True
+ upper_limit_ok = True
+ if self._owner.activate_low_band_filter_btn.isChecked():
+ if not signal_bands[1] >= filters_limit(self._owner.lower_band_spinbox,
+ self._owner.lower_band_filter_unit,
+ self._owner.lower_band_confidence, -1):
+ lower_limit_ok = False
+ if self._owner.activate_up_band_filter_btn.isChecked():
+ if not signal_bands[0] < filters_limit(self._owner.upper_band_spinbox,
+ self._owner.upper_band_filter_unit,
+ self._owner.upper_band_confidence):
+ upper_limit_ok = False
+ return lower_limit_ok and upper_limit_ok
+
+ def refresh(self):
+ """Extend _BaseFilter.refresh."""
+ super().refresh()
+ self._set_band_filter_label(
+ self._owner.activate_low_freq_filter_btn,
+ self._owner.lower_freq_spinbox,
+ self._owner.lower_freq_filter_unit,
+ self._owner.lower_freq_confidence,
+ self._owner.activate_up_freq_filter_btn,
+ self._owner.upper_freq_spinbox,
+ self._owner.upper_freq_filter_unit,
+ self._owner.upper_freq_confidence,
+ self._owner.freq_range_lbl
+ )
+
+
+class CatFilter(_BaseFilter):
+ """Category filter class."""
+
+ def __init__(self, owner):
+ super().__init__(owner)
+ self.apply_remove_btn = self._owner.apply_remove_cat_filter_btn
+ self.reset_btn = self._owner.reset_cat_filters_btn
+ # Order matters!
+ self._cat_filter_btns = [
+ self._owner.military_btn,
+ self._owner.radar_btn,
+ self._owner.active_btn,
+ self._owner.inactive_btn,
+ self._owner.ham_btn,
+ self._owner.commercial_btn,
+ self._owner.aviation_btn,
+ self._owner.marine_btn,
+ self._owner.analogue_btn,
+ self._owner.digital_btn,
+ self._owner.trunked_btn,
+ self._owner.utility_btn,
+ self._owner.sat_btn,
+ self._owner.navigation_btn,
+ self._owner.interfering_btn,
+ self._owner.number_stations_btn,
+ self._owner.time_signal_btn
+ ]
+
+ self.apply_remove_btn.set_texts(Constants.APPLY, Constants.REMOVE)
+ self.apply_remove_btn.set_slave_filters(
+ simple_ones=[
+ *self._cat_filter_btns,
+ self._owner.cat_at_least_one,
+ self._owner.cat_all
+ ]
+ )
+
+ @pyqtSlot()
+ def reset(self):
+ """Reset the category filter screen."""
+ uncheck_and_emit(self.apply_remove_btn)
+ for f in self._cat_filter_btns:
+ if f.isChecked():
+ f.setChecked(False)
+ self._owner.cat_at_least_one.setChecked(True)
+
+ def _ok(self, signal_name):
+ """Evalaute if the signal matches the category filters."""
+ if not self.apply_remove_btn.isChecked():
+ return True
+ cat_code = self._owner.db.at[signal_name, Signal.CATEGORY_CODE]
+ cat_checked = 0
+ positive_cases = 0
+ for index, cat in enumerate(self._cat_filter_btns):
+ if cat.isChecked():
+ cat_checked += 1
+ if cat_code[index] == '1':
+ positive_cases += 1
+ if self._owner.cat_at_least_one.isChecked():
+ return positive_cases > 0
+ else:
+ return cat_checked == positive_cases and cat_checked > 0
+
+
+class ModeFilter(_BaseFilter):
+ """Mode filter class."""
+
+ def __init__(self, owner):
+ super().__init__(owner)
+ self.apply_remove_btn = self._owner.apply_remove_mode_filter_btn
+ self.reset_btn = self._owner.reset_mode_filters_btn
+ self._set_mode_tree_widget()
+ self._owner.mode_tree_widget.itemSelectionChanged.connect(
+ self._manage_mode_selections
+ )
+ self.apply_remove_btn.set_texts(Constants.APPLY, Constants.REMOVE)
+ self.apply_remove_btn.set_slave_filters(
+ simple_ones=[
+ self._owner.mode_tree_widget,
+ self._owner.include_unknown_modes_btn
+ ]
+ )
+
+ def _manage_mode_selections(self):
+ """Rules the selection of childs items of the 'Mode' QTreeWidget.
+
+ If a parent is selected all its children will be selected as well.
+ """
+ selected_items = self._owner.mode_tree_widget.selectedItems()
+ parents = Constants.MODES.keys()
+ for parent in parents:
+ for item in selected_items:
+ if parent == item.text(0):
+ for i in range(len(Constants.MODES[parent])):
+ item.child(i).setSelected(True)
+
+ def _set_mode_tree_widget(self):
+ """Construct the QTreeWidget for the 'Mode' screen."""
+ for parent, children in Constants.MODES.items():
+ iparent = QTreeWidgetItem([parent])
+ self._owner.mode_tree_widget.addTopLevelItem(iparent)
+ for child in children:
+ ichild = QTreeWidgetItem([child])
+ iparent.addChild(ichild)
+ self._owner.mode_tree_widget.expandAll()
+
+ @pyqtSlot()
+ def reset(self):
+ """Reset the mode filter screen."""
+ uncheck_and_emit(self.apply_remove_btn)
+ parents = Constants.MODES.keys()
+ selected_children = []
+ for item in self._owner.mode_tree_widget.selectedItems():
+ if item.text(0) in parents:
+ item.setSelected(False)
+ else:
+ selected_children.append(item)
+ for children in selected_children:
+ children.setSelected(False)
+ if self._owner.include_unknown_modes_btn.isChecked():
+ self._owner.include_unknown_modes_btn.setChecked(False)
+
+ def _ok(self, signal_name):
+ """Evalaute if the signal matches the mode filters."""
+ if not self.apply_remove_btn.isChecked():
+ return True
+ signal_mode = self._owner.db.at[signal_name, Signal.MODE]
+ if signal_mode == Constants.UNKNOWN:
+ if self._owner.include_unknown_modes_btn.isChecked():
+ return True
+ else:
+ return False
+ selected_items = [item for item in self._owner.mode_tree_widget.selectedItems()]
+ selected_items_text = [i.text(0) for i in selected_items]
+ parents = [
+ item for item in selected_items_text
+ if item in Constants.MODES.keys()
+ ]
+ ok = []
+ for item in selected_items:
+ if item.text(0) in parents:
+ ok.append(item.text(0) in signal_mode)
+ elif not item.parent().isSelected():
+ ok.append(item.text(0) == signal_mode)
+ return any(ok)
+
+
+class ModulationFilter(_BaseFilter):
+ """Modulation filter class."""
+
+ def __init__(self, owner):
+ super().__init__(owner)
+ self.apply_remove_btn = self._owner.apply_remove_modulation_filter_btn
+ self.reset_btn = self._owner.reset_modulation_filters_btn
+ self._owner.search_bar_modulation.textEdited.connect(self._show_matching_modulations)
+ self.apply_remove_btn.set_texts(Constants.APPLY, Constants.REMOVE)
+ self.apply_remove_btn.set_slave_filters(
+ simple_ones=[
+ self._owner.search_bar_modulation,
+ self._owner.modulation_list
+ ]
+ )
+ self._owner.modulation_list.itemClicked.connect(self._remove_if_unselected_modulation)
+
+ @pyqtSlot(QListWidgetItem)
+ def _remove_if_unselected_modulation(self, item):
+ """If an item is unselected from the modulations list, hide the item."""
+ if not item.isSelected():
+ self._show_matching_modulations(self.search_bar_modulation.text())
+
+ @pyqtSlot(str)
+ def _show_matching_modulations(self, text):
+ """Show the modulations which matches 'text'.
+
+ The match criterion is defined in 'show_matching_strings'."""
+ show_matching_strings(self._owner.modulation_list, text)
+
+ @pyqtSlot()
+ def reset(self):
+ """Reset the modulation filter screen."""
+ uncheck_and_emit(self.apply_remove_btn)
+ self._owner.search_bar_modulation.setText('')
+ show_matching_strings(
+ self._owner.modulation_list,
+ self._owner.search_bar_modulation.text()
+ )
+ for i in range(self._owner.modulation_list.count()):
+ if self._owner.modulation_list.item(i).isSelected():
+ self._owner.modulation_list.item(i).setSelected(False)
+
+ def _ok(self, signal_name):
+ """Evalaute if the signal matches the modulation filters."""
+ if not self.apply_remove_btn.isChecked():
+ return True
+ signal_modulation = get_field_entries(
+ self._owner.db.at[signal_name, Signal.MODULATION]
+ )
+ for item in self._owner.modulation_list.selectedItems():
+ if item.text() in signal_modulation:
+ return True
+ return False
+
+
+class LocFilter(_BaseFilter):
+ """Location filter class."""
+
+ def __init__(self, owner):
+ super().__init__(owner)
+ self.apply_remove_btn = self._owner.apply_remove_location_filter_btn
+ self.reset_btn = self._owner.reset_location_filters_btn
+ self._owner.search_bar_location.textEdited.connect(
+ self._show_matching_locations
+ )
+ self.apply_remove_btn.set_texts(Constants.APPLY, Constants.REMOVE)
+ self.apply_remove_btn.set_slave_filters(
+ simple_ones=[
+ self._owner.search_bar_location,
+ self._owner.locations_list
+ ]
+ )
+ self._owner.locations_list.itemClicked.connect(self._remove_if_unselected_location)
+
+ @pyqtSlot(str)
+ def _show_matching_locations(self, text):
+ """Show the locations which matches 'text'.
+
+ The match criterion is defined in 'show_matching_strings'."""
+ show_matching_strings(self._owner.locations_list, text)
+
+ @pyqtSlot(QListWidgetItem)
+ def _remove_if_unselected_location(self, item):
+ """If an item is unselected from the locations list, hide the item."""
+ if not item.isSelected():
+ self._show_matching_locations(self._owner.search_bar_location.text())
+
+ @pyqtSlot()
+ def reset(self):
+ """Reset the location filter screen."""
+ uncheck_and_emit(self.apply_remove_btn)
+ self._owner.search_bar_location.setText('')
+ show_matching_strings(
+ self._owner.locations_list,
+ self._owner.search_bar_location.text()
+ )
+ for i in range(self._owner.locations_list.count()):
+ if self._owner.locations_list.item(i).isSelected():
+ self._owner.locations_list.item(i).setSelected(False)
+
+ def _ok(self, signal_name):
+ """Evalaute if the signal matches the location filters."""
+ if not self.apply_remove_btn.isChecked():
+ return True
+ signal_locations = get_field_entries(
+ self._owner.db.at[signal_name, Signal.LOCATION]
+ )
+ for item in self._owner.locations_list.selectedItems():
+ if item.text() in signal_locations:
+ return True
+ return False
+
+
+class ACFFilter(_BaseFilter):
+ """Autocorrelation function filter class."""
+
+ def __init__(self, owner):
+ super().__init__(owner)
+ self.apply_remove_btn = self._owner.apply_remove_acf_filter_btn
+ self.reset_btn = self._owner.reset_acf_filters_btn
+ self.apply_remove_btn.set_texts(Constants.APPLY, Constants.REMOVE)
+ self.apply_remove_btn.set_slave_filters(
+ simple_ones=[
+ self._owner.include_undef_acf,
+ self._owner.acf_spinbox,
+ self._owner.acf_confidence
+ ]
+ )
+ self._owner.acf_info_btn.clicked.connect(lambda: webbrowser.open(Constants.ACF_DOCS))
+
+ connect_events_to_func(
+ events_to_connect=[self._owner.acf_spinbox.valueChanged,
+ self._owner.acf_confidence.valueChanged],
+ fun_to_connect=self._set_acf_interval_label,
+ fun_args=None
+ )
+
+ @pyqtSlot()
+ def _set_acf_interval_label(self):
+ """Display the actual acf interval for the search."""
+ tolerance = self._owner.acf_spinbox.value() * self._owner.acf_confidence.value() / 100
+ if tolerance > 0:
+ val = round(self._owner.acf_spinbox.value() - tolerance, Constants.MAX_DIGITS)
+ to_display = f"Selected range:\n\n{val}" + Constants.RANGE_SEPARATOR \
+ + f"{round(self._owner.acf_spinbox.value() + tolerance, Constants.MAX_DIGITS)} ms"
+ else:
+ to_display = f"Selected value:\n\n{self._owner.acf_spinbox.value()} ms"
+ self._owner.acf_range_lbl.setText(to_display)
+ self._owner.acf_range_lbl.setStyleSheet(f"color: {self._owner.active_color}")
+
+ @pyqtSlot()
+ def reset(self):
+ """Reset the acf filter screen."""
+ uncheck_and_emit(self.apply_remove_btn)
+ if self._owner.include_undef_acf.isChecked():
+ self._owner.include_undef_acf.setChecked(False)
+ self._owner.acf_spinbox.setValue(50)
+ self._owner.acf_confidence.setValue(0)
+
+ def _ok(self, signal_name):
+ """Evalaute if the signal matches the acf filters."""
+ if not self.apply_remove_btn.isChecked():
+ return True
+ signal_acf = self._owner.db.at[signal_name, Signal.ACF]
+ if signal_acf == Constants.UNKNOWN:
+ if self._owner.include_undef_acf.isChecked():
+ return True
+ else:
+ return False
+ else:
+ signal_acf = safe_cast(signal_acf.rstrip("ms"), float)
+ tolerance = self._owner.acf_spinbox.value() * self._owner.acf_confidence.value() / 100
+ upper_limit = self._owner.acf_spinbox.value() + tolerance
+ lower_limit = self._owner.acf_spinbox.value() - tolerance
+ if signal_acf <= upper_limit and signal_acf >= lower_limit:
+ return True
+ else:
+ return False
+
+ def refresh(self):
+ """Extend _BaseFilter.refresh."""
+ super().refresh()
+ self._set_acf_interval_label()
+
+
+class Filters(QObject):
+ """Global filter class.
+
+ Provides the information about all the filters. Its only public attribute
+ is filters, which is a namedtuple containing instances of all the filters.
+ The only exposed methods are reset(), ok(signal_name) and refresh().
+ The class also connects the apply and reset buttons to the relevant functions."""
+
+ _FiltersTuple = namedtuple(
+ "_FiltersTuple",
+ [
+ "freq_filter",
+ "band_filter",
+ "cat_filter",
+ "mode_filter",
+ "modulation_filter",
+ "location_filter",
+ "acf_filter",
+ ]
+ )
+
+ def __init__(self, owner):
+ super().__init__()
+ self.filters = self._FiltersTuple(
+ FreqFilter(owner),
+ BandFilter(owner),
+ CatFilter(owner),
+ ModeFilter(owner),
+ ModulationFilter(owner),
+ LocFilter(owner),
+ ACFFilter(owner),
+ )
+ self._owner = owner
+ self._owner.reset_filters_btn.clicked.connect(self.reset)
+
+ # Connect Apply and Reset buttons clicks to functions.
+ for f in self.filters:
+ f.apply_remove_btn.clicked.connect(self._display_signals)
+ f.reset_btn.clicked.connect(f.reset)
+
+ @pyqtSlot()
+ def _display_signals(self):
+ self._owner.display_signals()
+
+ @pyqtSlot()
+ def reset(self):
+ """Reset all the filters."""
+ for f in self.filters:
+ f.reset()
+
+ def ok(self, signal_name):
+ """Check whether all the filters are passed."""
+ return all(f._ok(signal_name) for f in self.filters)
+
+ def refresh(self):
+ """Refresh the relevant widgets when changing theme."""
+ for f in self.filters:
+ f.refresh()
diff --git a/src/spaceweathermanager.py b/src/spaceweathermanager.py
new file mode 100644
index 0000000..22c3756
--- /dev/null
+++ b/src/spaceweathermanager.py
@@ -0,0 +1,297 @@
+import webbrowser
+from PyQt5.QtCore import QObject, pyqtSlot
+from constants import Constants, Messages
+from switchable_label import SwitchableLabelsIterable
+from weatherdata import SpaceWeatherData
+from utilities import safe_cast, pop_up
+
+
+class SpaceWeatherManager(QObject):
+ """Class to manage the spaceweather screen."""
+
+ def __init__(self, owner):
+ super().__init__()
+ self._owner = owner
+ self._owner.info_now_btn.clicked.connect(
+ lambda: webbrowser.open(Constants.SPACE_WEATHER_INFO)
+ )
+ self._owner.update_now_bar.clicked.connect(self._start_update_space_weather)
+ self._owner.update_now_bar.set_idle()
+ self._owner.space_weather_data = SpaceWeatherData()
+ self._owner.space_weather_data.update_complete.connect(self._update_space_weather)
+
+ self.space_weather_labels = (
+ self._owner.space_weather_lbl_0,
+ self._owner.space_weather_lbl_1,
+ self._owner.space_weather_lbl_2,
+ self._owner.space_weather_lbl_3,
+ self._owner.space_weather_lbl_4,
+ self._owner.space_weather_lbl_5,
+ self._owner.space_weather_lbl_6,
+ self._owner.space_weather_lbl_7,
+ self._owner.space_weather_lbl_8
+ )
+
+ for lab in self.space_weather_labels:
+ lab.set_default_stylesheet()
+
+ self._owner.space_weather_label_container.labels = self.space_weather_labels
+ self._owner.space_weather_label_name_container.labels = [
+ self._owner.eme_lbl,
+ self._owner.ms_lbl,
+ self._owner.muf_lbl,
+ self._owner.hi_lbl,
+ self._owner.eu50_lbl,
+ self._owner.eu70_lbl,
+ self._owner.eu144_lbl,
+ self._owner.na_lbl,
+ self._owner.aurora_lbl
+ ]
+
+ self._switchable_r_labels = SwitchableLabelsIterable(
+ self._owner.r0_now_lbl,
+ self._owner.r1_now_lbl,
+ self._owner.r2_now_lbl,
+ self._owner.r3_now_lbl,
+ self._owner.r4_now_lbl,
+ self._owner.r5_now_lbl
+ )
+
+ self._switchable_s_labels = SwitchableLabelsIterable(
+ self._owner.s0_now_lbl,
+ self._owner.s1_now_lbl,
+ self._owner.s2_now_lbl,
+ self._owner.s3_now_lbl,
+ self._owner.s4_now_lbl,
+ self._owner.s5_now_lbl
+ )
+
+ self._switchable_g_now_labels = SwitchableLabelsIterable(
+ self._owner.g0_now_lbl,
+ self._owner.g1_now_lbl,
+ self._owner.g2_now_lbl,
+ self._owner.g3_now_lbl,
+ self._owner.g4_now_lbl,
+ self._owner.g5_now_lbl
+ )
+
+ self._switchable_g_today_labels = SwitchableLabelsIterable(
+ self._owner.g0_today_lbl,
+ self._owner.g1_today_lbl,
+ self._owner.g2_today_lbl,
+ self._owner.g3_today_lbl,
+ self._owner.g4_today_lbl,
+ self._owner.g5_today_lbl
+ )
+
+ self._k_storm_labels = SwitchableLabelsIterable(
+ self._owner.k_ex_sev_storm_lbl,
+ self._owner.k_very_sev_storm_lbl,
+ self._owner.k_sev_storm_lbl,
+ self._owner.k_maj_storm_lbl,
+ self._owner.k_min_storm_lbl,
+ self._owner.k_active_lbl,
+ self._owner.k_unsettled_lbl,
+ self._owner.k_quiet_lbl,
+ self._owner.k_very_quiet_lbl,
+ self._owner.k_inactive_lbl
+ )
+
+ self._a_storm_labels = SwitchableLabelsIterable(
+ self._owner.a_sev_storm_lbl,
+ self._owner.a_maj_storm_lbl,
+ self._owner.a_min_storm_lbl,
+ self._owner.a_active_lbl,
+ self._owner.a_unsettled_lbl,
+ self._owner.a_quiet_lbl
+ )
+
+ # Used by ThemeManager.
+ self.refreshable_labels = SwitchableLabelsIterable(
+ *self._switchable_r_labels,
+ *self._switchable_s_labels,
+ *self._switchable_g_now_labels,
+ *self._switchable_g_today_labels,
+ *self._k_storm_labels,
+ *self._a_storm_labels,
+ self._owner.expected_noise_lbl
+ )
+
+ @pyqtSlot()
+ def _start_update_space_weather(self):
+ """Start the update of the space weather screen.
+
+ Start the corresponding thread.
+ """
+ if not self._owner.space_weather_data.is_updating:
+ self._owner.update_now_bar.set_updating()
+ self._owner.space_weather_data.update()
+
+ @pyqtSlot(bool)
+ def _update_space_weather(self, status_ok):
+ """Update the space weather screen after a successful download.
+
+ If the download was not successful throw a warning. In any case remove
+ the downloaded data.
+ """
+ self._owner.update_now_bar.set_idle()
+ if status_ok:
+ xray_long = safe_cast(self._owner.space_weather_data.xray[-1][7], float)
+
+ def format_text(letter, power):
+ return letter + f"{xray_long * 10**power:.1f}"
+
+ if xray_long < 1e-8 and xray_long != -1.00e+05:
+ self._owner.peak_flux_lbl.setText(format_text("= 1e-8 and xray_long < 1e-7:
+ self._owner.peak_flux_lbl.setText(format_text("A", 8))
+ elif xray_long >= 1e-7 and xray_long < 1e-6:
+ self._owner.peak_flux_lbl.setText(format_text("B", 7))
+ elif xray_long >= 1e-6 and xray_long < 1e-5:
+ self._owner.peak_flux_lbl.setText(format_text("C", 6))
+ elif xray_long >= 1e-5 and xray_long < 1e-4:
+ self._owner.peak_flux_lbl.setText(format_text("M", 5))
+ elif xray_long >= 1e-4:
+ self._owner.peak_flux_lbl.setText(format_text("X", 4))
+ elif xray_long == -1.00e+05:
+ self._owner.peak_flux_lbl.setText("No Data")
+
+ if xray_long < 1e-5 and xray_long != -1.00e+05:
+ self._switchable_r_labels.switch_on(self._owner.r0_now_lbl)
+ elif xray_long >= 1e-5 and xray_long < 5e-5:
+ self._switchable_r_labels.switch_on(self._owner.r1_now_lbl)
+ elif xray_long >= 5e-5 and xray_long < 1e-4:
+ self._switchable_r_labels.switch_on(self._owner.r2_now_lbl)
+ elif xray_long >= 1e-4 and xray_long < 1e-3:
+ self._switchable_r_labels.switch_on(self._owner.r3_now_lbl)
+ elif xray_long >= 1e-3 and xray_long < 2e-3:
+ self._switchable_r_labels.switch_on(self._owner.r4_now_lbl)
+ elif xray_long >= 2e-3:
+ self._switchable_r_labels.switch_on(self._owner.r5_now_lbl)
+ elif xray_long == -1.00e+05:
+ self._switchable_r_labels.switch_off_all()
+
+ pro10 = safe_cast(self._owner.space_weather_data.prot_el[-1][8], float)
+ if pro10 < 10 and pro10 != -1.00e+05:
+ self._switchable_s_labels.switch_on(self._owner.s0_now_lbl)
+ elif pro10 >= 10 and pro10 < 100:
+ self._switchable_s_labels.switch_on(self._owner.s1_now_lbl)
+ elif pro10 >= 100 and pro10 < 1000:
+ self._switchable_s_labels.switch_on(self._owner.s2_now_lbl)
+ elif pro10 >= 1000 and pro10 < 10000:
+ self._switchable_s_labels.switch_on(self._owner.s3_now_lbl)
+ elif pro10 >= 10000 and pro10 < 100000:
+ self._switchable_s_labels.switch_on(self._owner.s4_now_lbl)
+ elif pro10 >= 100000:
+ self._switchable_s_labels.switch_on(self._owner.s5_now_lbl)
+ elif pro10 == -1.00e+05:
+ self._switchable_s_labels.switch_off_all()
+
+ k_index = safe_cast(
+ self._owner.space_weather_data.ak_index[8][11].replace('.', ''), int
+ )
+ self._owner.k_index_lbl.setText(str(k_index))
+ a_index = safe_cast(
+ self._owner.space_weather_data.ak_index[7][7].replace('.', ''), int
+ )
+ self._owner.a_index_lbl.setText(str(a_index))
+
+ if k_index == 0:
+ self._switchable_g_now_labels.switch_on(self._owner.g0_now_lbl)
+ self._k_storm_labels.switch_on(self.k_inactive_lbl)
+ self._owner.expected_noise_lbl.setText(" S0 - S1 (<-120 dBm) ")
+ elif k_index == 1:
+ self._switchable_g_now_labels.switch_on(self._owner.g0_now_lbl)
+ self._k_storm_labels.switch_on(self._owner.k_very_quiet_lbl)
+ self._owner.expected_noise_lbl.setText(" S0 - S1 (<-120 dBm) ")
+ elif k_index == 2:
+ self._switchable_g_now_labels.switch_on(self._owner.g0_now_lbl)
+ self._k_storm_labels.switch_on(self._owner.k_quiet_lbl)
+ self._owner.expected_noise_lbl.setText(" S1 - S2 (-115 dBm) ")
+ elif k_index == 3:
+ self._switchable_g_now_labels.switch_on(self._owner.g0_now_lbl)
+ self._k_storm_labels.switch_on(self._owner.k_unsettled_lbl)
+ self._owner.expected_noise_lbl.setText(" S2 - S3 (-110 dBm) ")
+ elif k_index == 4:
+ self._switchable_g_now_labels.switch_on(self._owner.g0_now_lbl)
+ self._k_storm_labels.switch_on(self._owner.k_active_lbl)
+ self._owner.expected_noise_lbl.setText(" S3 - S4 (-100 dBm) ")
+ elif k_index == 5:
+ self._switchable_g_now_labels.switch_on(self._owner.g1_now_lbl)
+ self._k_storm_labels.switch_on(self._owner.k_min_storm_lbl)
+ self._owner.expected_noise_lbl.setText(" S4 - S6 (-90 dBm) ")
+ elif k_index == 6:
+ self._switchable_g_now_labels.switch_on(self._owner.g2_now_lbl)
+ self._k_storm_labels.switch_on(self._owner.k_maj_storm_lbl)
+ self._owner.expected_noise_lbl.setText(" S6 - S9 (-80 dBm) ")
+ elif k_index == 7:
+ self._switchable_g_now_labels.switch_on(self._owner.g3_now_lbl)
+ self._k_storm_labels.switch_on(self._owner.k_sev_storm_lbl)
+ self._owner.expected_noise_lbl.setText(" S9 - S20 (>-60 dBm) ")
+ elif k_index == 8:
+ self._switchable_g_now_labels.switch_on(self._owner.g4_now_lbl)
+ self._k_storm_labels.switch_on(self._owner.k_very_sev_storm_lbl)
+ self._owner.expected_noise_lbl.setText(" S20 - S30 (>-60 dBm) ")
+ elif k_index == 9:
+ self._switchable_g_now_labels.switch_on(self._owner.g5_now_lbl)
+ self._k_storm_labels.switch_on(self._owner.k_ex_sev_storm_lbl)
+ self._owner.expected_noise_lbl.setText(" S30+ (>>-60 dBm) ")
+ self._owner.expected_noise_lbl.switch_on()
+
+ if a_index >= 0 and a_index < 8:
+ self._a_storm_labels.switch_on(self._owner.a_quiet_lbl)
+ elif a_index >= 8 and a_index < 16:
+ self._a_storm_labels.switch_on(self._owner.a_unsettled_lbl)
+ elif a_index >= 16 and a_index < 30:
+ self._a_storm_labels.switch_on(self._owner.a_active_lbl)
+ elif a_index >= 30 and a_index < 50:
+ self._a_storm_labels.switch_on(self._owner.a_min_storm_lbl)
+ elif a_index >= 50 and a_index < 100:
+ self._a_storm_labels.switch_on(self._owner.a_maj_storm_lbl)
+ elif a_index >= 100 and a_index < 400:
+ self._a_storm_labels.switch_on(self._owner.a_sev_storm_lbl)
+
+ index = self._owner.space_weather_data.geo_storm[6].index("was") + 1
+ k_index_24_hmax = safe_cast(
+ self._owner.space_weather_data.geo_storm[6][index], int
+ )
+ if k_index_24_hmax == 0:
+ self._switchable_g_today_labels.switch_on(self._owner.g0_today_lbl)
+ elif k_index_24_hmax == 1:
+ self._switchable_g_today_labels.switch_on(self._owner.g0_today_lbl)
+ elif k_index_24_hmax == 2:
+ self._switchable_g_today_labels.switch_on(self._owner.g0_today_lbl)
+ elif k_index_24_hmax == 3:
+ self._switchable_g_today_labels.switch_on(self._owner.g0_today_lbl)
+ elif k_index_24_hmax == 4:
+ self._switchable_g_today_labels.switch_on(self._owner.g0_today_lbl)
+ elif k_index_24_hmax == 5:
+ self._switchable_g_today_labels.switch_on(self._owner.g1_today_lbl)
+ elif k_index_24_hmax == 6:
+ self._switchable_g_today_labels.switch_on(self._owner.g2_today_lbl)
+ elif k_index_24_hmax == 7:
+ self._switchable_g_today_labels.switch_on(self._owner.g3_today_lbl)
+ elif k_index_24_hmax == 8:
+ self._switchable_g_today_labels.switch_on(self._owner.g4_today_lbl)
+ elif k_index_24_hmax == 9:
+ self._switchable_g_today_labels.switch_on(self._owner.g5_today_lbl)
+
+ val = safe_cast(
+ self._owner.space_weather_data.ak_index[7][2].replace('.', ''), int
+ )
+ self._owner.sfi_lbl.setText(f"{val}")
+ val = safe_cast(
+ [x[4] for x in self._owner.space_weather_data.sgas
+ if "SSN" in x][0], int
+ )
+ self._owner.sn_lbl.setText(f"{val:d}")
+
+ for label, pixmap in zip(self.space_weather_labels,
+ self._owner.space_weather_data.images):
+ label.pixmap = pixmap
+ label.make_transparent()
+ label.apply_pixmap()
+ elif not self._owner.closing:
+ pop_up(self._owner, title=Messages.BAD_DOWNLOAD,
+ text=Messages.BAD_DOWNLOAD_MSG).show()
+ self._owner.space_weather_data.remove_data()
diff --git a/src/themesmanager.py b/src/themesmanager.py
index 2b42454..b76cea9 100644
--- a/src/themesmanager.py
+++ b/src/themesmanager.py
@@ -1,12 +1,10 @@
from functools import partial
-from itertools import chain
import os
import re
from PyQt5.QtWidgets import QAction, QActionGroup
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtGui import QPixmap
from constants import Constants
-from switchable_label import SwitchableLabelsIterable
from utilities import pop_up
@@ -46,7 +44,7 @@ class _ColorsHandler:
Can handle strings representing multiple colors."""
- MAX_COLORS = 2
+ _MAX_COLORS = 2
def __init__(self, line):
"""Define the color from the string 'line'.
@@ -76,7 +74,7 @@ class _ColorsHandler:
return bool(re.match(pattern, col)) and len(col) == 7
if not self.is_simple_string:
- if len(self.color_list) <= self.MAX_COLORS:
+ if len(self.color_list) <= self._MAX_COLORS:
return all(match_ok(c) for c in self.color_list)
else:
return False
@@ -120,57 +118,16 @@ class ThemeManager:
self._theme_path = ""
self._current_theme = ""
- self._space_weather_labels = SwitchableLabelsIterable(
- *list(
- chain(
- self._owner.switchable_r_labels,
- self._owner.switchable_s_labels,
- self._owner.switchable_g_now_labels,
- self._owner.switchable_g_today_labels,
- self._owner.k_storm_labels,
- self._owner.a_storm_labels,
- [self._owner.expected_noise_lbl]
- )
- )
- )
-
- self._space_weather_labels.set(
+ self._owner.spaceweather_screen.refreshable_labels.set(
"switch_on_colors",
ThemeConstants.DEFAULT_ON_COLORS
)
- self._space_weather_labels.set(
+ self._owner.spaceweather_screen.refreshable_labels.set(
"switch_off_colors", ThemeConstants.DEFAULT_OFF_COLORS
)
self._theme_names = {}
- def _refresh_range_labels(self):
- """Refresh the range-labels."""
- self._owner.set_acf_interval_label()
- self._owner.set_band_filter_label(
- self._owner.activate_low_band_filter_btn,
- self._owner.lower_band_spinbox,
- self._owner.lower_band_filter_unit,
- self._owner.lower_band_confidence,
- self._owner.activate_up_band_filter_btn,
- self._owner.upper_band_spinbox,
- self._owner.upper_band_filter_unit,
- self._owner.upper_band_confidence,
- self._owner.band_range_lbl
- )
-
- self._owner.set_band_filter_label(
- self._owner.activate_low_freq_filter_btn,
- self._owner.lower_freq_spinbox,
- self._owner.lower_freq_filter_unit,
- self._owner.lower_freq_confidence,
- self._owner.activate_up_freq_filter_btn,
- self._owner.upper_freq_spinbox,
- self._owner.upper_freq_filter_unit,
- self._owner.upper_freq_confidence,
- self._owner.freq_range_lbl
- )
-
@pyqtSlot()
def _apply(self, theme_path):
"""Apply the selected theme.
@@ -185,12 +142,12 @@ class ThemeManager:
item=self._owner.signals_list.currentItem(),
previous_item=None
)
- self._refresh_range_labels()
- self._owner.audio_widget.refresh_btns_colors(
+ self._owner.filters.refresh()
+ self._owner.audio_widget.refresh(
self._owner.active_color,
self._owner.inactive_color
)
- self._space_weather_labels.refresh()
+ self._owner.spaceweather_screen.refreshable_labels.refresh()
else:
pop_up(self._owner, title=ThemeConstants.THEME_NOT_FOUND,
text=ThemeConstants.MISSING_THEME).show()
@@ -297,20 +254,20 @@ class ThemeManager:
inactive_color_ok = True
if color.quality == Constants.TEXT_COLOR:
text_color_ok = True
- self._space_weather_labels.set(
+ self._owner.spaceweather_screen.refreshable_labels.set(
"text_color",
color.color_str
)
for color in color_handler.double_color_list:
if color.quality == Constants.LABEL_ON_COLOR:
switch_on_color_ok = True
- self._space_weather_labels.set(
+ self._owner.spaceweather_screen.refreshable_labels.set(
"switch_on_colors",
color.color_list
)
if color.quality == Constants.LABEL_OFF_COLOR:
switch_off_color_ok = True
- self._space_weather_labels.set(
+ self._owner.spaceweather_screen.refreshable_labels.set(
"switch_off_colors",
color.color_list
)
@@ -320,17 +277,17 @@ class ThemeManager:
self._owner.inactive_color = ThemeConstants.DEFAULT_INACTIVE_COLOR
if not (switch_on_color_ok and switch_off_color_ok):
- self._space_weather_labels.set(
+ self._owner.spaceweather_screen.refreshable_labels.set(
"switch_on_colors",
ThemeConstants.DEFAULT_ON_COLORS
)
- self._space_weather_labels.set(
+ self._owner.spaceweather_screen.refreshable_labels.set(
"switch_off_colors",
ThemeConstants.DEFAULT_OFF_COLORS
)
if not text_color_ok:
- self._space_weather_labels.set(
+ self._owner.spaceweather_screen.refreshable_labels.set(
"text_color",
ThemeConstants.DEFAULT_TEXT_COLOR
)
diff --git a/src/utilities.py b/src/utilities.py
index 375ec88..00e7de2 100644
--- a/src/utilities.py
+++ b/src/utilities.py
@@ -23,6 +23,29 @@ def uncheck_and_emit(button):
button.clicked.emit()
+def show_matching_strings(list_elements, text):
+ """Show all elements of QListWidget that matches (even partially) a target text.
+
+ Arguments:
+ list_elements -- the QListWidget
+ text -- the target text."""
+ for index in range(list_elements.count()):
+ item = list_elements.item(index)
+ if text.lower() in item.text().lower() or item.isSelected():
+ item.setHidden(False)
+ else:
+ item.setHidden(True)
+
+
+def get_field_entries(db_entry, separator=Constants.FIELD_SEPARATOR):
+ """Take a database entry and optionally a separator string.
+
+ Return a list obtained by splitting the signal field with separator."""
+ return [
+ x.strip() for x in db_entry.split(separator)
+ ]
+
+
def pop_up(cls, title, text,
informative_text=None,
connection=None,