from collections import namedtuple from functools import partial from glob import glob import webbrowser import os import sys from pandas import read_csv from PyQt5.QtWidgets import (QMainWindow, QApplication, QAction, qApp, QDesktopWidget, QListWidgetItem, QTreeView, QTreeWidgetItem,) from PyQt5.QtGui import QPixmap from PyQt5 import uic from PyQt5.QtCore import (QFileInfo, QSize, Qt, pyqtSlot,) from audio_player import AudioPlayer from double_text_button import DoubleTextButton from download_window import DownloadWindow import constants from utilities import (uncheck_and_emit, throwable_message, is_valid_html_color, connect_to, filters_ok, is_undef_freq, is_undef_band, change_unit, format_numbers) qt_creator_file = "main_window.ui" Ui_MainWindow, _ = uic.loadUiType(qt_creator_file) class MyApp(QMainWindow, Ui_MainWindow): def __init__(self): super().__init__() self.setupUi(self) self.set_initial_size() self.download_window = DownloadWindow() self.actionExit.triggered.connect(qApp.quit) self.action_update_database.triggered.connect(self.download_db) self.db = None self.current_signal_name = '' self.signal_names = [] self.total_signals = 0 self.active_color = constants.Theme.DEFAULT_ACTIVE_COLOR self.inactive_color = constants.Theme.DEFAULT_INACTIVE_COLOR # 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_to( 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_to( 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( [ *self.frequency_filters_btns, self.include_undef_freqs, self.activate_low_freq_filter_btn, self.activate_up_freq_filter_btn, ], 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.apply_remove_freq_filter_btn.clicked.connect(self.display_signals) self.reset_frequency_filters_btn.clicked.connect(partial(self.reset_fb_filters, constants.Ftype.FREQ)) # Manage bandwidth filters. connect_to( 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_to( 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( [ self.include_undef_bands, self.activate_low_band_filter_btn, self.activate_up_band_filter_btn, ], 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.apply_remove_band_filter_btn.clicked.connect(self.display_signals) self.reset_band_filters_btn.clicked.connect(partial(self.reset_fb_filters, constants.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([*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.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 = [self.cat_mil, self.cat_rad, self.cat_active, self.cat_inactive, self.cat_ham, self.cat_comm, self.cat_avi, self.cat_mar, self.cat_ana, self.cat_dig, self.cat_trunked, self.cat_utility, self.cat_sat, self.cat_navi, self.cat_interf, self.cat_num_stat, self.cat_time_sig,] self.property_labels = [self.freq_lab, self.band_lab, self.mode_lab, self.modul_lab, self.loc_lab, self.acf_lab, self.description_text,] self.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([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.modulation_list.addItems(constants.MODULATIONS) self.search_bar_modulation.textEdited.connect(self.show_matching_modulations) self.apply_remove_modulation_filter_btn.set_texts(constants.APPLY, constants.REMOVE) self.apply_remove_modulation_filter_btn.set_slave_filters([self.search_bar_modulation, self.modulation_list]) self.apply_remove_modulation_filter_btn.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.locations_list.addItems(constants.LOCATIONS) self.search_bar_location.textEdited.connect(self.show_matching_locations) self.apply_remove_location_filter_btn.set_texts(constants.APPLY, constants.REMOVE) self.apply_remove_location_filter_btn.set_slave_filters([self.search_bar_location, self.locations_list]) self.apply_remove_location_filter_btn.clicked.connect(self.display_signals) self.reset_location_filters_btn.clicked.connect(self.reset_location_filters) self.locations_list.itemClicked.connect(self.remove_if_unselected_location) # Set ACF filter screen. self.apply_remove_acf_filter_btn.set_texts(constants.APPLY, constants.REMOVE) self.apply_remove_acf_filter_btn.set_slave_filters([self.include_undef_acf, self.acf_spinbox, self.acf_confidence]) self.apply_remove_acf_filter_btn.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_to( events_to_connect = [self.acf_spinbox.valueChanged, self.acf_confidence.valueChanged], fun_to_connect = self.set_acf_interval_label, fun_args = None ) # Find available themes. self.default_images_folder = os.path.join(constants.Theme.FOLDER, constants.Theme.DEFAULT, constants.Theme.ICONS_FOLDER) # ########################################################################################## self.load_db() # Left list widget and search bar. self.search_bar.textChanged.connect(self.display_signals) self.result_list.addItems(self.signal_names) self.result_list.currentItemChanged.connect(self.display_specs) self.result_list.itemDoubleClicked.connect(lambda: self.main_tab.setCurrentWidget(self.signal_properties_tab)) self.display_signals() self.audio_widget = AudioPlayer(self.play, self.pause, self.stop, self.volume, self.audio_progress, self.active_color, self.inactive_color) BandLabel = namedtuple("BandLabel", ["left", "center", "right"]) self.band_labels = [ BandLabel(self.elf_left, self.elf, self.elf_right), 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.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), BandLabel(self.ehf_left, self.ehf, self.ehf_right), ] self.find_themes() self.set_theme() self.show() def refresh_range_labels(self): self.set_acf_interval_label() self.set_band_filter_label(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.set_band_filter_label(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) @pyqtSlot() def show_theme(self, theme): self.change_theme(theme) self.display_specs(self.result_list.currentItem(), None) self.refresh_range_labels() self.audio_widget.refresh_btns_colors(self.active_color, self.inactive_color) def find_themes(self): themes = [] for theme_folder in os.listdir(constants.Theme.FOLDER): relative_folder = os.path.join(constants.Theme.FOLDER, theme_folder) if os.path.isdir(os.path.abspath(relative_folder)): relative_folder = os.path.join(constants.Theme.FOLDER, theme_folder) themes.append(relative_folder) for theme in themes: theme_name = '&' + ' '.join( map(lambda s: s.capitalize(), os.path.basename(theme).split('-')[1].split('_') ) ) new_theme = QAction(theme_name, self) self.menu_themes.addAction(new_theme) new_theme.triggered.connect(partial(self.show_theme, theme)) @pyqtSlot() def change_theme(self, theme_path): try: with open(os.path.join( theme_path, os.path.basename(theme_path).split('-')[1] + constants.Theme.EXTENSION) ) as stylesheet: style = stylesheet.read() self.setStyleSheet(style) self.download_window.setStyleSheet(style) except FileNotFoundError: throwable_message(self, title = constants.Messages.THEME_NOT_FOUND, text = constants.Messages.MISSING_THEME).show() else: icons_path = os.path.join(theme_path, constants.Theme.ICONS_FOLDER) default_icons_path = os.path.join(constants.Theme.FOLDER, constants.Theme.DEFAULT, constants.Theme.ICONS_FOLDER) if os.path.exists(os.path.join(icons_path, constants.NOT_SELECTED)) and \ os.path.exists(os.path.join(icons_path, constants.NOT_AVAILABLE)): self.default_images_folder = icons_path else: self.default_images_folder = default_icons_path path_to_search_label = os.path.join(icons_path, constants.SEARCH_LABEL_IMG) default_search_label = os.path.join(default_icons_path, constants.SEARCH_LABEL_IMG) if os.path.exists(path_to_search_label): self.search_label.setPixmap(QPixmap(path_to_search_label)) self.modulation_search_label.setPixmap(QPixmap(path_to_search_label)) self.location_search_label.setPixmap(QPixmap(path_to_search_label)) else: self.search_label.setPixmap(QPixmap(default_search_label)) self.modulation_search_label.setPixmap(QPixmap(default_search_label)) self.location_search_label.setPixmap(QPixmap(default_search_label)) self.search_label.setScaledContents(True) self.modulation_search_label.setScaledContents(True) self.location_search_label.setScaledContents(True) path_to_volume_label = os.path.join(icons_path, constants.VOLUME_LABEL_IMG) default_volume_label = os.path.join(default_icons_path, constants.VOLUME_LABEL_IMG) if os.path.exists(path_to_volume_label): self.volume_label.setPixmap(QPixmap(path_to_volume_label)) else: self.volume_label.setPixmap(QPixmap(default_volume_label)) self.volume_label.setScaledContents(True) path_to_colors = os.path.join(theme_path, constants.Theme.COLORS) active_color_ok = False inactive_color_ok = False valid_format = False valid_file = False if os.path.exists(path_to_colors): valid_file = True with open(path_to_colors, "r") as colors_file: for line in colors_file: if constants.Theme.COLOR_SEPARATOR in line: valid_format = True quality, color = line.split(constants.Theme.COLOR_SEPARATOR) color = color.rstrip() if quality.lower() == constants.ACTIVE and is_valid_html_color(color): self.active_color = color active_color_ok = True if quality.lower() == constants.INACTIVE and is_valid_html_color(color): self.inactive_color = color inactive_color_ok = True if not all([valid_file, valid_format, active_color_ok, inactive_color_ok]): self.active_color = constants.Theme.DEFAULT_ACTIVE_COLOR self.inactive_color = constants.Theme.DEFAULT_INACTIVE_COLOR try: with open(os.path.join(constants.Theme.FOLDER, constants.Theme.CURRENT), "w") as current_theme: current_theme.write(theme_path) except: pass def set_theme(self): current_theme_file = os.path.join(constants.Theme.FOLDER, constants.Theme.CURRENT) if os.path.exists(current_theme_file): with open(current_theme_file) as current_theme: theme = current_theme.read() if theme != constants.Theme.DEFAULT: self.show_theme(theme) @pyqtSlot(QListWidgetItem) def remove_if_unselected_modulation(self, item): if not item.isSelected(): self.show_matching_modulations(self.search_bar_modulation.text()) @pyqtSlot(QListWidgetItem) def remove_if_unselected_location(self, item): if not item.isSelected(): self.show_matching_locations(self.search_bar_location.text()) @pyqtSlot(str) def show_matching_modulations(self, text): self.show_matching_strings(self.modulation_list, text) @pyqtSlot(str) def show_matching_locations(self, text): self.show_matching_strings(self.locations_list, text) def show_matching_strings(self, list_elements, text): for index in range(list_elements.count()): item = list_elements.item(index) if text.upper() in item.text() or item.isSelected(): item.setHidden(False) else: item.setHidden(True) def set_mode_tree_widget(self): 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): 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): """ Function to handle high resolution screens. The function sets bigger sizes for all the relevant fixed-size widgets. """ d = QDesktopWidget().availableGeometry() w = d.width() h = d.height() self.setGeometry(50, 50, (3 * w) // 4, (3 * h) // 4) if w > 3000 or h > 2000: self.fixed_audio_and_image.setFixedSize(540, 1150) self.fixed_audio_and_image.setMaximumSize(540, 1150) self.play.setFixedSize(140, 140) self.pause.setFixedSize(140, 140) self.stop.setFixedSize(140, 140) self.lower_freq_spinbox.setFixedWidth(200) self.upper_freq_spinbox.setFixedWidth(200) self.lower_freq_filter_unit.setFixedWidth(120) self.upper_freq_filter_unit.setFixedWidth(120) self.lower_freq_confidence.setFixedWidth(120) self.upper_freq_confidence.setFixedWidth(120) self.lower_band_spinbox.setFixedWidth(200) self.upper_band_spinbox.setFixedWidth(200) self.lower_band_filter_unit.setFixedWidth(120) self.upper_band_filter_unit.setFixedWidth(120) self.lower_band_confidence.setFixedWidth(120) self.upper_band_confidence.setFixedWidth(120) self.audio_progress.setFixedHeight(20) self.volume.setStyleSheet(""" QSlider::groove:horizontal { height: 12px; background: #7a7a7a; margin: 0 10px; 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); border: 1px solid #5c5c5c; width: 28px; margin: -8px -8px; border-radius: 14px; } """) @pyqtSlot() def download_db(self): self.download_window.download_thread.finished.connect(self.show_downloaded_signals) self.download_window.download_thread.start() self.download_window.show() @pyqtSlot() def show_downloaded_signals(self): if self.download_window.everything_ok: self.search_bar.setEnabled(True) self.load_db() self.display_signals() def load_db(self): names = constants.Database.NAMES try: self.db = read_csv(os.path.join(constants.DATA_FOLDER, constants.Database.NAME), sep = constants.Database.DELIMITER, header = None, index_col = 0, dtype = {name : str for name in constants.Database.STRINGS}, names = names,) except FileNotFoundError: self.search_bar.setDisabled(True) throwable_message(self, title = constants.Messages.NO_DB, text = constants.Messages.NO_DB_AVAIL).show() else: self.signal_names = self.db.index self.total_signals = len(self.signal_names) self.db.fillna(constants.UNKNOWN, inplace = True) self.db[constants.Signal.WIKI_CLICKED] = False self.update_status_tip(self.total_signals) @pyqtSlot() def set_min_value_upper_limit(self, lower_combo_box, lower_spin_box, upper_combo_box, upper_spin_box): 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() upper_value = upper_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): 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): tolerance = self.acf_spinbox.value() * self.acf_confidence.value() / 100 if tolerance > 0: to_display = f"Selected range:\n\n{round(self.acf_spinbox.value() - tolerance, constants.MAX_DIGITS)}" +\ constants.RANGE_SEPARATOR + f"{round(self.acf_spinbox.value() + tolerance, constants.MAX_DIGITS)} ms" 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): toggled = True if radio_btn.isChecked() else False for w in widgets[:-1]: # Neglect the bool coming from the emitted signal. w.setEnabled(toggled) @pyqtSlot() def display_signals(self): 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)]): self.result_list.item(index).setHidden(False) available_signals += 1 else: self.result_list.item(index).setHidden(True) self.update_status_tip(available_signals) def update_status_tip(self, available_signals): if available_signals < self.total_signals: self.statusbar.setStyleSheet(f'color: {self.active_color}') else: self.statusbar.setStyleSheet(f'color: {self.inactive_color}') self.statusbar.showMessage(f"{available_signals} out of {self.total_signals} signals displayed.") @pyqtSlot() def reset_fb_filters(self, ftype): if ftype != constants.Ftype.FREQ and ftype != constants.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 == constants.Ftype.FREQ else 5000 if ftype == constants.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): 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): uncheck_and_emit(self.apply_remove_mode_filter_btn) for item in self.mode_tree_widget.selectedItems(): item.setSelected(False) if self.include_unknown_modes_btn.isChecked(): self.include_unknown_modes_btn.setChecked(False) @pyqtSlot() def reset_modulation_filters(self): uncheck_and_emit(self.apply_remove_modulation_filter_btn) self.search_bar_modulation.setText('') 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): uncheck_and_emit(self.apply_remove_location_filter_btn) self.search_bar_location.setText('') 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): 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): 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 = (int(self.db.at[signal_name, constants.Signal.INF_FREQ]), int(self.db.at[signal_name, constants.Signal.SUP_FREQ])) 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_ok(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_ok(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): 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 = (int(self.db.at[signal_name, constants.Signal.INF_BAND]), int(self.db.at[signal_name, constants.Signal.SUP_BAND])) lower_limit_ok = True upper_limit_ok = True if self.activate_low_band_filter_btn.isChecked(): if not signal_bands[1] >= filters_ok(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_ok(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): if not self.apply_remove_cat_filter_btn.isChecked(): return True cat_code = self.db.at[signal_name, constants.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): if not self.apply_remove_mode_filter_btn.isChecked(): return True signal_mode = self.db.at[signal_name, constants.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()] children = [item for item in selected_items_text if item not in parents] 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 modulation_filters_ok(self, signal_name): if not self.apply_remove_modulation_filter_btn.isChecked(): return True signal_modulation = self.db.at[signal_name, constants.Signal.MODULATION] for item in self.modulation_list.selectedItems(): if item.text() == signal_modulation: return True return False def location_filters_ok(self, signal_name): if not self.apply_remove_location_filter_btn.isChecked(): return True signal_location = self.db.at[signal_name, constants.Signal.LOCATION] for item in self.locations_list.selectedItems(): if item.text() == signal_location: return True return False def acf_filters_ok(self, signal_name): if not self.apply_remove_acf_filter_btn.isChecked(): return True signal_acf = self.db.at[signal_name, constants.Signal.ACF] if signal_acf == constants.UNKNOWN: if self.include_undef_acf.isChecked(): return True else: return False else: signal_acf = float(signal_acf.rstrip("ms")) 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): self.display_spectrogram() if item: self.current_signal_name = item.text() self.name_lab.setText(self.current_signal_name) self.name_lab.setAlignment(Qt.AlignHCenter) current_signal = self.db.loc[self.current_signal_name] self.url_button.setEnabled(True) if not current_signal.at[constants.Signal.WIKI_CLICKED]: self.url_button.setStyleSheet(f"color: {self.url_button.colors.active};") else: self.url_button.setStyleSheet(f"color: {self.url_button.colors.clicked};") category_code = current_signal.at[constants.Signal.CATEGORY_CODE] undef_freq = is_undef_freq(current_signal) undef_band = is_undef_band(current_signal) if not undef_freq: self.freq_lab.setText(format_numbers(current_signal.at[constants.Signal.INF_FREQ], current_signal.at[constants.Signal.SUP_FREQ]) ) else: self.freq_lab.setText("Undefined") if not undef_band: self.band_lab.setText(format_numbers(current_signal.at[constants.Signal.INF_BAND], current_signal.at[constants.Signal.SUP_BAND]) ) else: self.band_lab.setText("Undefined") self.mode_lab.setText(current_signal.at[constants.Signal.MODE]) self.modul_lab.setText(current_signal.at[constants.Signal.MODULATION]) self.loc_lab.setText(current_signal.at[constants.Signal.LOCATION]) self.acf_lab.setText(current_signal.at[constants.Signal.ACF]) self.description_text.setText(current_signal.at[constants.Signal.DESCRIPTION]) for cat, cat_lab in zip(category_code, self.category_labels): if cat == '0': cat_lab.setStyleSheet(f"color: {self.inactive_color};") elif cat == '1': cat_lab.setStyleSheet(f"color: {self.active_color};") self.set_band_range(current_signal) self.audio_widget.set_audio_player(self.current_signal_name) else: self.url_button.setEnabled(False) self.url_button.setStyleSheet(f"color: {self.url_button.colors.inactive};") self.current_signal_name = '' self.name_lab.setText("No Signal") self.name_lab.setAlignment(Qt.AlignHCenter) for lab in self.property_labels: lab.setText(constants.UNKNOWN) for lab in self.category_labels: lab.setStyleSheet(f"color: {self.inactive_color};") self.set_band_range() self.audio_widget.set_audio_player() def display_spectrogram(self): default_pic = os.path.join(self.default_images_folder, constants.NOT_SELECTED) item = self.result_list.currentItem() if item: spectrogram_name = item.text() path_spectr = os.path.join(constants.DATA_FOLDER, constants.SPECTRA_FOLDER, spectrogram_name + constants.SPECTRA_EXT) if not QFileInfo(path_spectr).exists(): path_spectr = os.path.join(self.default_images_folder, constants.NOT_AVAILABLE) else: path_spectr = default_pic self.spectrogram.setPixmap(QPixmap(path_spectr)) def activate_band_category(self, band_label, activate = True): color = self.active_color if activate else self.inactive_color for label in band_label: label.setStyleSheet(f"color: {color};") def set_band_range(self, current_signal = None): if current_signal is not None and not is_undef_freq(current_signal): lower_freq = int(current_signal.at[constants.Signal.INF_FREQ]) upper_freq = int(current_signal.at[constants.Signal.SUP_FREQ]) zipped = list(zip(constants.BANDS, self.band_labels)) for i, w in enumerate(zipped): band, band_label = w if lower_freq >= band.lower and lower_freq < band.upper: self.activate_band_category(band_label) for uband, uband_label in zipped[i + 1:]: if upper_freq > uband.lower: self.activate_band_category(uband_label) else: self.activate_band_category(uband_label, False) break else: self.activate_band_category(band_label, False) else: for band_label in self.band_labels: self.activate_band_category(band_label, False) @pyqtSlot() def reset_all_filters(self): 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): if self.current_signal_name: self.url_button.setStyleSheet(f"color: {self.url_button.colors.clicked}") webbrowser.open(self.db.at[self.current_signal_name, constants.Signal.URL]) self.db.at[self.current_signal_name, constants.Signal.WIKI_CLICKED] = True def closeEvent(self, event): if self.download_window.isVisible(): self.download_window.close() super().closeEvent(event) if __name__ == '__main__': my_app = QApplication(sys.argv) w = MyApp() sys.exit(my_app.exec_())