From 8cb81dd2256e72a418dd3a790a6748600eefddab Mon Sep 17 00:00:00 2001 From: alessandro90 Date: Sun, 2 Jun 2019 14:10:06 +0200 Subject: [PATCH] Switch from filter panel to signal panel with double-click on signal name. Also make minor style changes --- .flake8 | 2 +- artemis.py | 61 +++++++++++++++++++++--------------- audio_player.py | 12 +++---- double_text_button.py | 1 + download_window.py | 12 +++---- fixed_aspect_ratio_label.py | 5 ++- fixed_aspect_ratio_widget.py | 5 +-- themesmanager.py | 14 +++++---- threads.py | 6 ++-- utilities.py | 11 +++++++ weatherdata.py | 14 +++++++-- 11 files changed, 87 insertions(+), 56 deletions(-) diff --git a/.flake8 b/.flake8 index 5269d12..d7cbbca 100644 --- a/.flake8 +++ b/.flake8 @@ -1,2 +1,2 @@ [flake8] -ignore = E221, E501 +ignore = E221, E501, W605, W504 diff --git a/artemis.py b/artemis.py index 5c972a5..fe5239e 100644 --- a/artemis.py +++ b/artemis.py @@ -227,7 +227,7 @@ class Artemis(QMainWindow, Ui_MainWindow): 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, @@ -235,7 +235,7 @@ class Artemis(QMainWindow, Ui_MainWindow): 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( @@ -271,7 +271,7 @@ class Artemis(QMainWindow, Ui_MainWindow): 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_to_connect=self.set_min_value_upper_limit, fun_args=[self.lower_band_filter_unit, self.lower_band_spinbox, self.upper_band_filter_unit, @@ -305,7 +305,7 @@ class Artemis(QMainWindow, Ui_MainWindow): 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, @@ -313,7 +313,7 @@ class Artemis(QMainWindow, Ui_MainWindow): 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) @@ -485,9 +485,8 @@ class Artemis(QMainWindow, Ui_MainWindow): # Left list widget and search bar. self.search_bar.textChanged.connect(self.display_signals) self.signals_list.currentItemChanged.connect(self.display_specs) - self.signals_list.itemDoubleClicked.connect( - lambda: self.main_tab.setCurrentWidget(self.signal_properties_tab) - ) + self.signals_list.itemDoubleClicked.connect(self.set_visible_tab) + self.audio_widget = AudioPlayer( self.play, self.pause, @@ -504,9 +503,9 @@ class Artemis(QMainWindow, Ui_MainWindow): BandLabel(self.slf_left, self.slf, self.slf_right), BandLabel(self.ulf_left, self.ulf, self.ulf_right), BandLabel(self.vlf_left, self.vlf, self.vlf_right), - BandLabel(self.lf_left, self.lf, self.lf_right), - BandLabel(self.mf_left, self.mf, self.mf_right), - BandLabel(self.hf_left, self.hf, self.hf_right), + BandLabel(self.lf_left, self.lf, self.lf_right), + BandLabel(self.mf_left, self.mf, self.mf_right), + BandLabel(self.hf_left, self.hf, self.hf_right), BandLabel(self.vhf_left, self.vhf, self.vhf_right), BandLabel(self.uhf_left, self.uhf, self.uhf_right), BandLabel(self.shf_left, self.shf, self.shf_right), @@ -537,6 +536,13 @@ class Artemis(QMainWindow, Ui_MainWindow): self.load_db() 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() def start_update_forecast(self): """Start the update of the 3-day forecast screen. @@ -582,7 +588,10 @@ class Artemis(QMainWindow, Ui_MainWindow): 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}" + + 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: @@ -866,7 +875,7 @@ class Artemis(QMainWindow, Ui_MainWindow): height: 12px; background: #7a7a7a; margin: 0 10px; - border-radius: 6px + border-radius: 6px } QSlider::handle:horizontal { background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 gray, stop:0.5 white, stop:1.0 gray); @@ -875,7 +884,7 @@ class Artemis(QMainWindow, Ui_MainWindow): margin: -8px -8px; border-radius: 14px; } - """) + """) @pyqtSlot() def download_db(self): @@ -1021,7 +1030,7 @@ class Artemis(QMainWindow, Ui_MainWindow): values = list( set([ x.strip() for value in values[values != Constants.UNKNOWN] - for x in value.split(separator) + for x in value.split(separator) ]) ) values.sort() @@ -1037,7 +1046,7 @@ class Artemis(QMainWindow, Ui_MainWindow): Used for frequency and bandwidth screens.""" if lower_spin_box.isEnabled(): - unit_conversion = {'Hz' : ['kHz', 'MHz', 'GHz'], + unit_conversion = {'Hz': ['kHz', 'MHz', 'GHz'], 'kHz': ['MHz', 'GHz'], 'MHz': ['GHz']} lower_units = lower_combo_box.currentText() @@ -1138,7 +1147,7 @@ class Artemis(QMainWindow, Ui_MainWindow): Do nothing otherwise. """ toggled = radio_btn.isChecked() - for w in widgets[:-1]: # Neglect the bool coming from the emitted signal. + for w in widgets[:-1]: # Neglect the bool coming from the emitted signal. w.setEnabled(toggled) @pyqtSlot() @@ -1147,13 +1156,13 @@ 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) , + 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)]): self.signals_list.item(index).setHidden(False) available_signals += 1 @@ -1372,7 +1381,7 @@ class Artemis(QMainWindow, Ui_MainWindow): 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() + if item in Constants.MODES.keys() ] ok = [] for item in selected_items: @@ -1597,7 +1606,7 @@ if __name__ == '__main__': img = QPixmap(ARTEMIS_ICON) splash = QSplashScreen(img) splash.show() - start= time() + start = time() while time() - start < 1.5: sleep(0.001) my_app.processEvents() diff --git a/audio_player.py b/audio_player.py index ffbfe4e..3706bc8 100644 --- a/audio_player.py +++ b/audio_player.py @@ -14,7 +14,7 @@ class AudioPlayer(QObject): method, set_audio_player, which loads the current file and refresh_btns_colors. Everything else is managed internally.""" - _time_step = 500 # Milliseconds. + _TIME_STEP = 500 # Milliseconds. def __init__(self, play, pause, @@ -111,10 +111,10 @@ class AudioPlayer(QObject): if not self._paused: if self._first_call: self._first_call = False - mixer.init(frequency=AudioSegment.from_ogg( - self._audio_file - ).frame_rate, - buffer=2048) + mixer.init( + frequency=AudioSegment.from_ogg(self._audio_file).frame_rate, + buffer=2048 + ) mixer.music.load(self._audio_file) self._set_volume() self._set_max_progress_bar() @@ -122,7 +122,7 @@ class AudioPlayer(QObject): else: mixer.music.unpause() self._paused = False - self._timer.start(self._time_step) + self._timer.start(self._TIME_STEP) self._enable_buttons(False, True, True) @pyqtSlot() diff --git a/double_text_button.py b/double_text_button.py index fe10308..d1d9b41 100644 --- a/double_text_button.py +++ b/double_text_button.py @@ -1,6 +1,7 @@ from PyQt5.QtWidgets import QPushButton from PyQt5.QtCore import pyqtSlot + class DoubleTextButton(QPushButton): """Subclass QPushButton. diff --git a/download_window.py b/download_window.py index 15538bf..9c75aa3 100644 --- a/download_window.py +++ b/download_window.py @@ -21,20 +21,20 @@ class DownloadWindow(QWidget, Ui_Download_window): super().__init__() self.setupUi(self) self.setWindowFlags( - #Qt.Window | + # Qt.Window | Qt.CustomizeWindowHint | Qt.WindowTitleHint | - Qt.WindowCloseButtonHint #| + Qt.WindowCloseButtonHint # | # Qt.WindowStaysOnTopHint ) self._no_internet_msg = pop_up(self, title=Messages.NO_CONNECTION, - text=Messages.NO_CONNECTION_MSG, - connection=self.close) + text=Messages.NO_CONNECTION_MSG, + connection=self.close) self._bad_db_download_msg = pop_up(self, title=Messages.BAD_DOWNLOAD, - text=Messages.BAD_DOWNLOAD_MSG, - connection=self.close) + text=Messages.BAD_DOWNLOAD_MSG, + connection=self.close) self._download_thread = DownloadThread() self._download_thread.finished.connect(self._wait_close) diff --git a/fixed_aspect_ratio_label.py b/fixed_aspect_ratio_label.py index a8e90d4..d72b323 100644 --- a/fixed_aspect_ratio_label.py +++ b/fixed_aspect_ratio_label.py @@ -5,7 +5,7 @@ from PyQt5.QtCore import Qt class FixedAspectRatioLabel(QLabel): """Subclass QLabel. A resizable label class.""" - def __init__(self, parent = None): + def __init__(self, parent=None): """Initialize the instance. Set the pixmap to None.""" super().__init__(parent) self.pixmap = None @@ -14,8 +14,7 @@ class FixedAspectRatioLabel(QLabel): """Set the initial stylesheet of the label.""" self.setStyleSheet("""border-width: 1px; border-style: solid; - border-color: black;""" - ) + border-color: black;""") def make_transparent(self): """Make the label transparent. diff --git a/fixed_aspect_ratio_widget.py b/fixed_aspect_ratio_widget.py index c09dda3..654fed4 100644 --- a/fixed_aspect_ratio_widget.py +++ b/fixed_aspect_ratio_widget.py @@ -5,7 +5,8 @@ from PyQt5.QtCore import QSize class FixedAspectRatioWidget(QWidget): """Subclass QWidget. Keep all the internal labels to a fixed aspect ratio.""" - space = 10 + SPACE = 10 + def __init__(self, parent=None): """Initialize the instance.""" super().__init__(parent) @@ -14,7 +15,7 @@ class FixedAspectRatioWidget(QWidget): def resizeEvent(self, event): """Override QWidget.resizeEvent. Rescale all the internal widgets.""" h, w = self.height(), self.width() - h_lbl = h / 9 - self.space + h_lbl = h / 9 - self.SPACE w_lbl = 5 * h_lbl w_pad = w - 10 if w_lbl > w_pad: diff --git a/themesmanager.py b/themesmanager.py index bd7896a..2b42454 100644 --- a/themesmanager.py +++ b/themesmanager.py @@ -71,7 +71,9 @@ class _ColorsHandler: def _color_is_valid(self): """Return if the color (or the list of colors) has a valid html format.""" 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 len(self.color_list) <= self.MAX_COLORS: @@ -81,7 +83,6 @@ class _ColorsHandler: else: return match_ok(self.color_str) - def __init__(self, simple_color_list, double_color_list): """Initialize the lists of valid _Color objects.""" self.simple_color_list = simple_color_list @@ -197,7 +198,8 @@ class ThemeManager: def _pretty_name(self, bad_name): """Return a well-formatted theme name.""" return ' '.join( - map(lambda s: s.capitalize(), + map( + lambda s: s.capitalize(), bad_name.split('_') ) ) @@ -248,7 +250,7 @@ class ThemeManager: else: icons_path = os.path.join(self._theme_path, ThemeConstants.ICONS_FOLDER) - path_to_search_label = os.path.join(icons_path,Constants.SEARCH_LABEL_IMG) + path_to_search_label = os.path.join(icons_path, Constants.SEARCH_LABEL_IMG) if os.path.exists(path_to_search_label): path = path_to_search_label @@ -263,7 +265,7 @@ class ThemeManager: self._owner.modulation_search_label.setScaledContents(True) self._owner.location_search_label.setScaledContents(True) - path_to_volume_label = os.path.join(icons_path,Constants.VOLUME_LABEL_IMG) + path_to_volume_label = os.path.join(icons_path, Constants.VOLUME_LABEL_IMG) if os.path.exists(path_to_volume_label): path = path_to_volume_label @@ -273,7 +275,7 @@ class ThemeManager: self._owner.volume_label.setPixmap(QPixmap(path)) self._owner.volume_label.setScaledContents(True) - path_to_colors = os.path.join(self._theme_path,ThemeConstants.COLORS) + path_to_colors = os.path.join(self._theme_path, ThemeConstants.COLORS) active_color_ok = False inactive_color_ok = False diff --git a/threads.py b/threads.py index 0b33dd1..e72bb37 100644 --- a/threads.py +++ b/threads.py @@ -86,7 +86,7 @@ class DownloadThread(BaseDownloadThread): self._get_download_speed(data, delta) ) db.release_conn() - except Exception: # No internet connection. + except Exception: # No internet connection. db.release_conn() self.status = ThreadStatus.NO_CONNECTION_ERR return @@ -126,7 +126,7 @@ class _AsyncDownloader: class UpdateSpaceWeatherThread(BaseDownloadThread, _AsyncDownloader): """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): """Initialize the a local space_weather_data.""" @@ -151,7 +151,7 @@ class UpdateSpaceWeatherThread(BaseDownloadThread, _AsyncDownloader): session = aiohttp.ClientSession() try: t = [] - for p in self._properties: + for p in self._PROPERTIES: t.append( asyncio.create_task(self._download_property(session, p)) ) diff --git a/utilities.py b/utilities.py index 99412c5..b679c5c 100644 --- a/utilities.py +++ b/utilities.py @@ -8,6 +8,7 @@ from PyQt5.QtWidgets import QMessageBox from constants import Constants, Signal, Database, ChecksumWhat + def resource_path(relative_path): """Get absolute path to resource, works for dev and for PyInstaller.""" try: @@ -16,12 +17,14 @@ def resource_path(relative_path): base_path = os.path.abspath(".") return os.path.join(base_path, relative_path) + def uncheck_and_emit(button): """Set the button to the unchecked state and emit the clicked signal.""" if button.isChecked(): button.setChecked(False) button.clicked.emit() + def pop_up(cls, title, text, informative_text=None, connection=None, @@ -47,6 +50,7 @@ def pop_up(cls, title, text, msg.adjustSize() return msg + def checksum_ok(data, what): """Check whether the checksum of the 'data' argument is correct.""" code = hashlib.sha256() @@ -66,6 +70,7 @@ def checksum_ok(data, what): raise return code.hexdigest() == reference + 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. @@ -77,23 +82,27 @@ def connect_events_to_func(events_to_connect, fun_to_connect, fun_args): for event in events_to_connect: event.connect(fun_to_connect) + def filters_limit(spinbox, filter_unit, confidence, sign=1): """Return the actual limit of a numerical filter.""" band_filter = spinbox.value() * Constants.CONVERSION_FACTORS[filter_unit.currentText()] return band_filter + sign * (confidence.value() * band_filter) // 100 + def is_undef_freq(current_signal): """Return whether the lower or upper frequency of a signal is undefined.""" lower_freq = current_signal.at[Signal.INF_FREQ] upper_freq = current_signal.at[Signal.SUP_FREQ] return lower_freq == Constants.UNKNOWN or upper_freq == Constants.UNKNOWN + def is_undef_band(current_signal): """Return whether the lower or upper band of a signal is undefined.""" lower_band = current_signal.at[Signal.INF_BAND] upper_band = current_signal.at[Signal.SUP_BAND] return lower_band == Constants.UNKNOWN or upper_band == Constants.UNKNOWN + def _change_unit(str_num): """Return a scale factor given the number of digits of a numeric string.""" digits = len(str_num) @@ -106,6 +115,7 @@ def _change_unit(str_num): else: return 10**9 + def format_numbers(lower, upper): """Return the string which displays the numeric limits of a filter.""" units = {1: 'Hz', 1000: 'kHz', 10**6: 'MHz', 10**9: 'GHz'} @@ -128,6 +138,7 @@ def format_numbers(lower, upper): else: return f"{lower:,} {units[lower_factor]}" + def safe_cast(value, cast_type, default=-1): """Call 'cast_type(value)' and return the result. diff --git a/weatherdata.py b/weatherdata.py index 8bc8713..46a0c89 100644 --- a/weatherdata.py +++ b/weatherdata.py @@ -115,9 +115,15 @@ class SpaceWeatherData(_BaseWeatherData): def _make_labels_table(forecast, probabilities, rows): """Organize all the arguments to feed _get_lbl_value.""" - def get_first_split(x): return x.split("/")[0] - def get_second_split(x): return x.split("/")[1] - def get_third_split(x): return x.split("/")[2] + def get_first_split(x): + return x.split("/")[0] + + def get_second_split(x): + return x.split("/")[1] + + def get_third_split(x): + return x.split("/")[2] + solar_row = rows["solar_row"] event_row = rows["event_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): """Return the well-formatted string-value of the label.""" val = data[row][col] @@ -202,6 +209,7 @@ def _get_lbl_value(data, row, col, f=None): val = val.lstrip('0') return val + class ForecastData(_BaseWeatherData): """3-day forecast class. Extends _BaseWeatherData."""