Switch from filter panel to signal panel with double-click on
signal name. Also make minor style changes
This commit is contained in:
2
.flake8
2
.flake8
@@ -1,2 +1,2 @@
|
|||||||
[flake8]
|
[flake8]
|
||||||
ignore = E221, E501
|
ignore = E221, E501, W605, W504
|
||||||
|
|||||||
17
artemis.py
17
artemis.py
@@ -485,9 +485,8 @@ class Artemis(QMainWindow, Ui_MainWindow):
|
|||||||
# Left list widget and search bar.
|
# Left list widget and search bar.
|
||||||
self.search_bar.textChanged.connect(self.display_signals)
|
self.search_bar.textChanged.connect(self.display_signals)
|
||||||
self.signals_list.currentItemChanged.connect(self.display_specs)
|
self.signals_list.currentItemChanged.connect(self.display_specs)
|
||||||
self.signals_list.itemDoubleClicked.connect(
|
self.signals_list.itemDoubleClicked.connect(self.set_visible_tab)
|
||||||
lambda: self.main_tab.setCurrentWidget(self.signal_properties_tab)
|
|
||||||
)
|
|
||||||
self.audio_widget = AudioPlayer(
|
self.audio_widget = AudioPlayer(
|
||||||
self.play,
|
self.play,
|
||||||
self.pause,
|
self.pause,
|
||||||
@@ -537,6 +536,13 @@ class Artemis(QMainWindow, Ui_MainWindow):
|
|||||||
self.load_db()
|
self.load_db()
|
||||||
self.display_signals()
|
self.display_signals()
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def set_visible_tab(self):
|
||||||
|
if self.main_tab.currentWidget() != self.signal_properties_tab:
|
||||||
|
self.main_tab.setCurrentWidget(self.signal_properties_tab)
|
||||||
|
else:
|
||||||
|
self.main_tab.setCurrentWidget(self.filter_tab)
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def start_update_forecast(self):
|
def start_update_forecast(self):
|
||||||
"""Start the update of the 3-day forecast screen.
|
"""Start the update of the 3-day forecast screen.
|
||||||
@@ -582,7 +588,10 @@ class Artemis(QMainWindow, Ui_MainWindow):
|
|||||||
self.update_now_bar.set_idle()
|
self.update_now_bar.set_idle()
|
||||||
if status_ok:
|
if status_ok:
|
||||||
xray_long = safe_cast(self.space_weather_data.xray[-1][7], float)
|
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}"
|
|
||||||
|
def format_text(letter, power):
|
||||||
|
return letter + f"{xray_long * 10**power:.1f}"
|
||||||
|
|
||||||
if xray_long < 1e-8 and xray_long != -1.00e+05:
|
if xray_long < 1e-8 and xray_long != -1.00e+05:
|
||||||
self.peak_flux_lbl.setText(format_text("<A", 8))
|
self.peak_flux_lbl.setText(format_text("<A", 8))
|
||||||
elif xray_long >= 1e-8 and xray_long < 1e-7:
|
elif xray_long >= 1e-8 and xray_long < 1e-7:
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class AudioPlayer(QObject):
|
|||||||
method, set_audio_player, which loads the current file and refresh_btns_colors.
|
method, set_audio_player, which loads the current file and refresh_btns_colors.
|
||||||
Everything else is managed internally."""
|
Everything else is managed internally."""
|
||||||
|
|
||||||
_time_step = 500 # Milliseconds.
|
_TIME_STEP = 500 # Milliseconds.
|
||||||
|
|
||||||
def __init__(self, play,
|
def __init__(self, play,
|
||||||
pause,
|
pause,
|
||||||
@@ -111,10 +111,10 @@ class AudioPlayer(QObject):
|
|||||||
if not self._paused:
|
if not self._paused:
|
||||||
if self._first_call:
|
if self._first_call:
|
||||||
self._first_call = False
|
self._first_call = False
|
||||||
mixer.init(frequency=AudioSegment.from_ogg(
|
mixer.init(
|
||||||
self._audio_file
|
frequency=AudioSegment.from_ogg(self._audio_file).frame_rate,
|
||||||
).frame_rate,
|
buffer=2048
|
||||||
buffer=2048)
|
)
|
||||||
mixer.music.load(self._audio_file)
|
mixer.music.load(self._audio_file)
|
||||||
self._set_volume()
|
self._set_volume()
|
||||||
self._set_max_progress_bar()
|
self._set_max_progress_bar()
|
||||||
@@ -122,7 +122,7 @@ class AudioPlayer(QObject):
|
|||||||
else:
|
else:
|
||||||
mixer.music.unpause()
|
mixer.music.unpause()
|
||||||
self._paused = False
|
self._paused = False
|
||||||
self._timer.start(self._time_step)
|
self._timer.start(self._TIME_STEP)
|
||||||
self._enable_buttons(False, True, True)
|
self._enable_buttons(False, True, True)
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from PyQt5.QtWidgets import QPushButton
|
from PyQt5.QtWidgets import QPushButton
|
||||||
from PyQt5.QtCore import pyqtSlot
|
from PyQt5.QtCore import pyqtSlot
|
||||||
|
|
||||||
|
|
||||||
class DoubleTextButton(QPushButton):
|
class DoubleTextButton(QPushButton):
|
||||||
"""Subclass QPushButton.
|
"""Subclass QPushButton.
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ class FixedAspectRatioLabel(QLabel):
|
|||||||
"""Set the initial stylesheet of the label."""
|
"""Set the initial stylesheet of the label."""
|
||||||
self.setStyleSheet("""border-width: 1px;
|
self.setStyleSheet("""border-width: 1px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: black;"""
|
border-color: black;""")
|
||||||
)
|
|
||||||
|
|
||||||
def make_transparent(self):
|
def make_transparent(self):
|
||||||
"""Make the label transparent.
|
"""Make the label transparent.
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ from PyQt5.QtCore import QSize
|
|||||||
class FixedAspectRatioWidget(QWidget):
|
class FixedAspectRatioWidget(QWidget):
|
||||||
"""Subclass QWidget. Keep all the internal labels to a fixed aspect ratio."""
|
"""Subclass QWidget. Keep all the internal labels to a fixed aspect ratio."""
|
||||||
|
|
||||||
space = 10
|
SPACE = 10
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
"""Initialize the instance."""
|
"""Initialize the instance."""
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@@ -14,7 +15,7 @@ class FixedAspectRatioWidget(QWidget):
|
|||||||
def resizeEvent(self, event):
|
def resizeEvent(self, event):
|
||||||
"""Override QWidget.resizeEvent. Rescale all the internal widgets."""
|
"""Override QWidget.resizeEvent. Rescale all the internal widgets."""
|
||||||
h, w = self.height(), self.width()
|
h, w = self.height(), self.width()
|
||||||
h_lbl = h / 9 - self.space
|
h_lbl = h / 9 - self.SPACE
|
||||||
w_lbl = 5 * h_lbl
|
w_lbl = 5 * h_lbl
|
||||||
w_pad = w - 10
|
w_pad = w - 10
|
||||||
if w_lbl > w_pad:
|
if w_lbl > w_pad:
|
||||||
|
|||||||
@@ -71,7 +71,9 @@ class _ColorsHandler:
|
|||||||
def _color_is_valid(self):
|
def _color_is_valid(self):
|
||||||
"""Return if the color (or the list of colors) has a valid html format."""
|
"""Return if the color (or the list of colors) has a valid html format."""
|
||||||
pattern = "#([a-zA-Z0-9]){6}"
|
pattern = "#([a-zA-Z0-9]){6}"
|
||||||
def match_ok(col): return bool(re.match(pattern, col)) and len(col) == 7
|
|
||||||
|
def match_ok(col):
|
||||||
|
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:
|
||||||
@@ -81,7 +83,6 @@ class _ColorsHandler:
|
|||||||
else:
|
else:
|
||||||
return match_ok(self.color_str)
|
return match_ok(self.color_str)
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, simple_color_list, double_color_list):
|
def __init__(self, simple_color_list, double_color_list):
|
||||||
"""Initialize the lists of valid _Color objects."""
|
"""Initialize the lists of valid _Color objects."""
|
||||||
self.simple_color_list = simple_color_list
|
self.simple_color_list = simple_color_list
|
||||||
@@ -197,7 +198,8 @@ class ThemeManager:
|
|||||||
def _pretty_name(self, bad_name):
|
def _pretty_name(self, bad_name):
|
||||||
"""Return a well-formatted theme name."""
|
"""Return a well-formatted theme name."""
|
||||||
return ' '.join(
|
return ' '.join(
|
||||||
map(lambda s: s.capitalize(),
|
map(
|
||||||
|
lambda s: s.capitalize(),
|
||||||
bad_name.split('_')
|
bad_name.split('_')
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ class _AsyncDownloader:
|
|||||||
class UpdateSpaceWeatherThread(BaseDownloadThread, _AsyncDownloader):
|
class UpdateSpaceWeatherThread(BaseDownloadThread, _AsyncDownloader):
|
||||||
"""Subclass BaseDownloadThread. Downlaod the space weather data."""
|
"""Subclass BaseDownloadThread. Downlaod the space weather data."""
|
||||||
|
|
||||||
_properties = ("xray", "prot_el", "ak_index", "sgas", "geo_storm")
|
_PROPERTIES = ("xray", "prot_el", "ak_index", "sgas", "geo_storm")
|
||||||
|
|
||||||
def __init__(self, space_weather_data):
|
def __init__(self, space_weather_data):
|
||||||
"""Initialize the a local space_weather_data."""
|
"""Initialize the a local space_weather_data."""
|
||||||
@@ -151,7 +151,7 @@ class UpdateSpaceWeatherThread(BaseDownloadThread, _AsyncDownloader):
|
|||||||
session = aiohttp.ClientSession()
|
session = aiohttp.ClientSession()
|
||||||
try:
|
try:
|
||||||
t = []
|
t = []
|
||||||
for p in self._properties:
|
for p in self._PROPERTIES:
|
||||||
t.append(
|
t.append(
|
||||||
asyncio.create_task(self._download_property(session, p))
|
asyncio.create_task(self._download_property(session, p))
|
||||||
)
|
)
|
||||||
|
|||||||
11
utilities.py
11
utilities.py
@@ -8,6 +8,7 @@ from PyQt5.QtWidgets import QMessageBox
|
|||||||
|
|
||||||
from constants import Constants, Signal, Database, ChecksumWhat
|
from constants import Constants, Signal, Database, ChecksumWhat
|
||||||
|
|
||||||
|
|
||||||
def resource_path(relative_path):
|
def resource_path(relative_path):
|
||||||
"""Get absolute path to resource, works for dev and for PyInstaller."""
|
"""Get absolute path to resource, works for dev and for PyInstaller."""
|
||||||
try:
|
try:
|
||||||
@@ -16,12 +17,14 @@ def resource_path(relative_path):
|
|||||||
base_path = os.path.abspath(".")
|
base_path = os.path.abspath(".")
|
||||||
return os.path.join(base_path, relative_path)
|
return os.path.join(base_path, relative_path)
|
||||||
|
|
||||||
|
|
||||||
def uncheck_and_emit(button):
|
def uncheck_and_emit(button):
|
||||||
"""Set the button to the unchecked state and emit the clicked signal."""
|
"""Set the button to the unchecked state and emit the clicked signal."""
|
||||||
if button.isChecked():
|
if button.isChecked():
|
||||||
button.setChecked(False)
|
button.setChecked(False)
|
||||||
button.clicked.emit()
|
button.clicked.emit()
|
||||||
|
|
||||||
|
|
||||||
def pop_up(cls, title, text,
|
def pop_up(cls, title, text,
|
||||||
informative_text=None,
|
informative_text=None,
|
||||||
connection=None,
|
connection=None,
|
||||||
@@ -47,6 +50,7 @@ def pop_up(cls, title, text,
|
|||||||
msg.adjustSize()
|
msg.adjustSize()
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
def checksum_ok(data, what):
|
def checksum_ok(data, what):
|
||||||
"""Check whether the checksum of the 'data' argument is correct."""
|
"""Check whether the checksum of the 'data' argument is correct."""
|
||||||
code = hashlib.sha256()
|
code = hashlib.sha256()
|
||||||
@@ -66,6 +70,7 @@ def checksum_ok(data, what):
|
|||||||
raise
|
raise
|
||||||
return code.hexdigest() == reference
|
return code.hexdigest() == reference
|
||||||
|
|
||||||
|
|
||||||
def connect_events_to_func(events_to_connect, fun_to_connect, fun_args):
|
def connect_events_to_func(events_to_connect, fun_to_connect, fun_args):
|
||||||
"""Connect all elements of events_to_connect to the callable fun_to_connect.
|
"""Connect all elements of events_to_connect to the callable fun_to_connect.
|
||||||
|
|
||||||
@@ -77,23 +82,27 @@ def connect_events_to_func(events_to_connect, fun_to_connect, fun_args):
|
|||||||
for event in events_to_connect:
|
for event in events_to_connect:
|
||||||
event.connect(fun_to_connect)
|
event.connect(fun_to_connect)
|
||||||
|
|
||||||
|
|
||||||
def filters_limit(spinbox, filter_unit, confidence, sign=1):
|
def filters_limit(spinbox, filter_unit, confidence, sign=1):
|
||||||
"""Return the actual limit of a numerical filter."""
|
"""Return the actual limit of a numerical filter."""
|
||||||
band_filter = spinbox.value() * Constants.CONVERSION_FACTORS[filter_unit.currentText()]
|
band_filter = spinbox.value() * Constants.CONVERSION_FACTORS[filter_unit.currentText()]
|
||||||
return band_filter + sign * (confidence.value() * band_filter) // 100
|
return band_filter + sign * (confidence.value() * band_filter) // 100
|
||||||
|
|
||||||
|
|
||||||
def is_undef_freq(current_signal):
|
def is_undef_freq(current_signal):
|
||||||
"""Return whether the lower or upper frequency of a signal is undefined."""
|
"""Return whether the lower or upper frequency of a signal is undefined."""
|
||||||
lower_freq = current_signal.at[Signal.INF_FREQ]
|
lower_freq = current_signal.at[Signal.INF_FREQ]
|
||||||
upper_freq = current_signal.at[Signal.SUP_FREQ]
|
upper_freq = current_signal.at[Signal.SUP_FREQ]
|
||||||
return lower_freq == Constants.UNKNOWN or upper_freq == Constants.UNKNOWN
|
return lower_freq == Constants.UNKNOWN or upper_freq == Constants.UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
def is_undef_band(current_signal):
|
def is_undef_band(current_signal):
|
||||||
"""Return whether the lower or upper band of a signal is undefined."""
|
"""Return whether the lower or upper band of a signal is undefined."""
|
||||||
lower_band = current_signal.at[Signal.INF_BAND]
|
lower_band = current_signal.at[Signal.INF_BAND]
|
||||||
upper_band = current_signal.at[Signal.SUP_BAND]
|
upper_band = current_signal.at[Signal.SUP_BAND]
|
||||||
return lower_band == Constants.UNKNOWN or upper_band == Constants.UNKNOWN
|
return lower_band == Constants.UNKNOWN or upper_band == Constants.UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
def _change_unit(str_num):
|
def _change_unit(str_num):
|
||||||
"""Return a scale factor given the number of digits of a numeric string."""
|
"""Return a scale factor given the number of digits of a numeric string."""
|
||||||
digits = len(str_num)
|
digits = len(str_num)
|
||||||
@@ -106,6 +115,7 @@ def _change_unit(str_num):
|
|||||||
else:
|
else:
|
||||||
return 10**9
|
return 10**9
|
||||||
|
|
||||||
|
|
||||||
def format_numbers(lower, upper):
|
def format_numbers(lower, upper):
|
||||||
"""Return the string which displays the numeric limits of a filter."""
|
"""Return the string which displays the numeric limits of a filter."""
|
||||||
units = {1: 'Hz', 1000: 'kHz', 10**6: 'MHz', 10**9: 'GHz'}
|
units = {1: 'Hz', 1000: 'kHz', 10**6: 'MHz', 10**9: 'GHz'}
|
||||||
@@ -128,6 +138,7 @@ def format_numbers(lower, upper):
|
|||||||
else:
|
else:
|
||||||
return f"{lower:,} {units[lower_factor]}"
|
return f"{lower:,} {units[lower_factor]}"
|
||||||
|
|
||||||
|
|
||||||
def safe_cast(value, cast_type, default=-1):
|
def safe_cast(value, cast_type, default=-1):
|
||||||
"""Call 'cast_type(value)' and return the result.
|
"""Call 'cast_type(value)' and return the result.
|
||||||
|
|
||||||
|
|||||||
@@ -115,9 +115,15 @@ class SpaceWeatherData(_BaseWeatherData):
|
|||||||
|
|
||||||
def _make_labels_table(forecast, probabilities, rows):
|
def _make_labels_table(forecast, probabilities, rows):
|
||||||
"""Organize all the arguments to feed _get_lbl_value."""
|
"""Organize all the arguments to feed _get_lbl_value."""
|
||||||
def get_first_split(x): return x.split("/")[0]
|
def get_first_split(x):
|
||||||
def get_second_split(x): return x.split("/")[1]
|
return x.split("/")[0]
|
||||||
def get_third_split(x): return x.split("/")[2]
|
|
||||||
|
def get_second_split(x):
|
||||||
|
return x.split("/")[1]
|
||||||
|
|
||||||
|
def get_third_split(x):
|
||||||
|
return x.split("/")[2]
|
||||||
|
|
||||||
solar_row = rows["solar_row"]
|
solar_row = rows["solar_row"]
|
||||||
event_row = rows["event_row"]
|
event_row = rows["event_row"]
|
||||||
rb_now_row = rows["rb_now_row"]
|
rb_now_row = rows["rb_now_row"]
|
||||||
@@ -192,6 +198,7 @@ def _make_labels_table(forecast, probabilities, rows):
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def _get_lbl_value(data, row, col, f=None):
|
def _get_lbl_value(data, row, col, f=None):
|
||||||
"""Return the well-formatted string-value of the label."""
|
"""Return the well-formatted string-value of the label."""
|
||||||
val = data[row][col]
|
val = data[row][col]
|
||||||
@@ -202,6 +209,7 @@ def _get_lbl_value(data, row, col, f=None):
|
|||||||
val = val.lstrip('0')
|
val = val.lstrip('0')
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
|
||||||
class ForecastData(_BaseWeatherData):
|
class ForecastData(_BaseWeatherData):
|
||||||
"""3-day forecast class. Extends _BaseWeatherData."""
|
"""3-day forecast class. Extends _BaseWeatherData."""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user