"""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 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.include_variable_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) if self._owner.include_variable_acf.isChecked(): self._owner.include_variable_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_list = self._owner.db.at[signal_name, Signal.ACF] if signal_acf_list[0].unknown: # Unknown acf are the only acf of the signal. if self._owner.include_undef_acf.isChecked(): return True else: return False else: 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 for v in signal_acf_list: if v.is_numeric: if lower_limit <= v.numeric_value <= upper_limit: return True elif self._owner.include_variable_acf.isChecked(): return True 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 dictionary 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.""" def __init__(self, owner): super().__init__() self.filters = { "freq_filter": FreqFilter(owner), "band_filter": BandFilter(owner), "cat_filter": CatFilter(owner), "mode_filter": ModeFilter(owner), "modulation_filter": ModulationFilter(owner), "location_filter": LocFilter(owner), "acf_filter": 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._values: f.apply_remove_btn.clicked.connect(self._display_signals) f.reset_btn.clicked.connect(f.reset) @property def _values(self): return self.filters.values() @pyqtSlot() def _display_signals(self): self._owner.display_signals() @pyqtSlot() def _reset(self): """Reset all the filters.""" for f in self._values: f.reset() def ok(self, signal_name): """Check whether all the filters are passed.""" return all(f._ok(signal_name) for f in self._values) def refresh(self): """Refresh the relevant widgets when changing theme.""" for f in self._values: f.refresh()