Major refactor of filters and spaceweather

This commit is contained in:
Alessandro
2019-08-31 19:28:22 +02:00
parent 6870774577
commit 4a54ef54cb
9 changed files with 1210 additions and 1062 deletions

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@ src/themes/.current_theme
designer.bat designer.bat
launch.bat launch.bat
.vscode/ .vscode/
.code-workspace

View File

@@ -0,0 +1,7 @@
{
"folders": [
{
"path": "."
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,7 @@ class AudioPlayer(QObject):
"""Subclass QObject. Audio player widget for the audio samples. """Subclass QObject. Audio player widget for the audio samples.
The only public methods are the __init__ 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.""" Everything else is managed internally."""
_TIME_STEP = 500 # Milliseconds. _TIME_STEP = 500 # Milliseconds.
@@ -47,7 +47,7 @@ class AudioPlayer(QObject):
self._pause.setIconSize(self._pause.size()) self._pause.setIconSize(self._pause.size())
self._stop.setIconSize(self._stop.size()) self._stop.setIconSize(self._stop.size())
self._loop.setIconSize(self._loop.size()) self._loop.setIconSize(self._loop.size())
self.refresh_btns_colors(active_color, inactive_color) self.refresh(active_color, inactive_color)
@pyqtSlot() @pyqtSlot()
def _set_loop_icon(self): def _set_loop_icon(self):
@@ -67,7 +67,7 @@ class AudioPlayer(QObject):
) )
self._loop.setIcon(loop_icon) 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.""" """Repaint the buttons of the widgetd after the theme has changed."""
self._active_color = active_color self._active_color = active_color
self._inactive_color = inactive_color self._inactive_color = inactive_color

View File

@@ -41,7 +41,7 @@ class Messages:
NO_CONNECTION = "No connection" NO_CONNECTION = "No connection"
NO_CONNECTION_MSG = "Unable to establish an internet connection." NO_CONNECTION_MSG = "Unable to establish an internet connection."
BAD_DOWNLOAD = "Something went wrong" 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 = "Slow internet connection"
SLOW_CONN_MSG = "Your internet connection is unstable or too slow." SLOW_CONN_MSG = "Your internet connection is unstable or too slow."

850
src/filters.py Normal file
View File

@@ -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()

297
src/spaceweathermanager.py Normal file
View File

@@ -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("<A", 8))
elif xray_long >= 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()

View File

@@ -1,12 +1,10 @@
from functools import partial from functools import partial
from itertools import chain
import os import os
import re import re
from PyQt5.QtWidgets import QAction, QActionGroup from PyQt5.QtWidgets import QAction, QActionGroup
from PyQt5.QtCore import pyqtSlot from PyQt5.QtCore import pyqtSlot
from PyQt5.QtGui import QPixmap from PyQt5.QtGui import QPixmap
from constants import Constants from constants import Constants
from switchable_label import SwitchableLabelsIterable
from utilities import pop_up from utilities import pop_up
@@ -46,7 +44,7 @@ class _ColorsHandler:
Can handle strings representing multiple colors.""" Can handle strings representing multiple colors."""
MAX_COLORS = 2 _MAX_COLORS = 2
def __init__(self, line): def __init__(self, line):
"""Define the color from the string 'line'. """Define the color from the string 'line'.
@@ -76,7 +74,7 @@ class _ColorsHandler:
return bool(re.match(pattern, col)) and len(col) == 7 return bool(re.match(pattern, col)) and len(col) == 7
if not self.is_simple_string: 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) return all(match_ok(c) for c in self.color_list)
else: else:
return False return False
@@ -120,57 +118,16 @@ class ThemeManager:
self._theme_path = "" self._theme_path = ""
self._current_theme = "" self._current_theme = ""
self._space_weather_labels = SwitchableLabelsIterable( self._owner.spaceweather_screen.refreshable_labels.set(
*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(
"switch_on_colors", "switch_on_colors",
ThemeConstants.DEFAULT_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 "switch_off_colors", ThemeConstants.DEFAULT_OFF_COLORS
) )
self._theme_names = {} 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() @pyqtSlot()
def _apply(self, theme_path): def _apply(self, theme_path):
"""Apply the selected theme. """Apply the selected theme.
@@ -185,12 +142,12 @@ class ThemeManager:
item=self._owner.signals_list.currentItem(), item=self._owner.signals_list.currentItem(),
previous_item=None previous_item=None
) )
self._refresh_range_labels() self._owner.filters.refresh()
self._owner.audio_widget.refresh_btns_colors( self._owner.audio_widget.refresh(
self._owner.active_color, self._owner.active_color,
self._owner.inactive_color self._owner.inactive_color
) )
self._space_weather_labels.refresh() self._owner.spaceweather_screen.refreshable_labels.refresh()
else: else:
pop_up(self._owner, title=ThemeConstants.THEME_NOT_FOUND, pop_up(self._owner, title=ThemeConstants.THEME_NOT_FOUND,
text=ThemeConstants.MISSING_THEME).show() text=ThemeConstants.MISSING_THEME).show()
@@ -297,20 +254,20 @@ class ThemeManager:
inactive_color_ok = True inactive_color_ok = True
if color.quality == Constants.TEXT_COLOR: if color.quality == Constants.TEXT_COLOR:
text_color_ok = True text_color_ok = True
self._space_weather_labels.set( self._owner.spaceweather_screen.refreshable_labels.set(
"text_color", "text_color",
color.color_str color.color_str
) )
for color in color_handler.double_color_list: for color in color_handler.double_color_list:
if color.quality == Constants.LABEL_ON_COLOR: if color.quality == Constants.LABEL_ON_COLOR:
switch_on_color_ok = True switch_on_color_ok = True
self._space_weather_labels.set( self._owner.spaceweather_screen.refreshable_labels.set(
"switch_on_colors", "switch_on_colors",
color.color_list color.color_list
) )
if color.quality == Constants.LABEL_OFF_COLOR: if color.quality == Constants.LABEL_OFF_COLOR:
switch_off_color_ok = True switch_off_color_ok = True
self._space_weather_labels.set( self._owner.spaceweather_screen.refreshable_labels.set(
"switch_off_colors", "switch_off_colors",
color.color_list color.color_list
) )
@@ -320,17 +277,17 @@ class ThemeManager:
self._owner.inactive_color = ThemeConstants.DEFAULT_INACTIVE_COLOR self._owner.inactive_color = ThemeConstants.DEFAULT_INACTIVE_COLOR
if not (switch_on_color_ok and switch_off_color_ok): 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", "switch_on_colors",
ThemeConstants.DEFAULT_ON_COLORS ThemeConstants.DEFAULT_ON_COLORS
) )
self._space_weather_labels.set( self._owner.spaceweather_screen.refreshable_labels.set(
"switch_off_colors", "switch_off_colors",
ThemeConstants.DEFAULT_OFF_COLORS ThemeConstants.DEFAULT_OFF_COLORS
) )
if not text_color_ok: if not text_color_ok:
self._space_weather_labels.set( self._owner.spaceweather_screen.refreshable_labels.set(
"text_color", "text_color",
ThemeConstants.DEFAULT_TEXT_COLOR ThemeConstants.DEFAULT_TEXT_COLOR
) )

View File

@@ -23,6 +23,29 @@ def uncheck_and_emit(button):
button.clicked.emit() 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, def pop_up(cls, title, text,
informative_text=None, informative_text=None,
connection=None, connection=None,