import math import time from multiprocessing import Process, Array import numpy as np from PyQt5.QtCore import pyqtSignal, QPoint, Qt, QMimeData, pyqtSlot, QTimer from PyQt5.QtGui import QIcon, QDrag, QPixmap, QRegion, QDropEvent, QTextCursor, QContextMenuEvent, \ QResizeEvent from PyQt5.QtWidgets import QFrame, QMessageBox, QMenu, QWidget, QUndoStack, QCheckBox, QApplication, qApp from urh import settings from urh.controller.dialogs.AdvancedModulationOptionsDialog import AdvancedModulationOptionsDialog from urh.controller.dialogs.FilterDialog import FilterDialog from urh.controller.dialogs.SendDialog import SendDialog from urh.controller.dialogs.SignalDetailsDialog import SignalDetailsDialog from urh.signalprocessing.Filter import Filter, FilterType from urh.signalprocessing.IQArray import IQArray from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer from urh.signalprocessing.Signal import Signal from urh.signalprocessing.Spectrogram import Spectrogram from urh.ui.actions.ChangeSignalParameter import ChangeSignalParameter from urh.ui.actions.EditSignalAction import EditSignalAction, EditAction from urh.ui.painting.SignalSceneManager import SignalSceneManager from urh.ui.ui_signal_frame import Ui_SignalFrame from urh.util import FileOperator, util from urh.util.Errors import Errors from urh.util.Formatter import Formatter from urh.util.Logger import logger def perform_filter(result_array: Array, data, f_low, f_high, filter_bw): result_array = np.frombuffer(result_array.get_obj(), dtype=np.complex64) result_array[:] = Filter.apply_bandpass_filter(data, f_low, f_high, filter_bw=filter_bw) class SignalFrame(QFrame): closed = pyqtSignal(QWidget) signal_created = pyqtSignal(Signal) drag_started = pyqtSignal(QPoint) frame_dropped = pyqtSignal(QPoint) files_dropped = pyqtSignal(list) not_show_again_changed = pyqtSignal() signal_drawing_finished = pyqtSignal() apply_to_all_clicked = pyqtSignal(Signal) sort_action_clicked = pyqtSignal() @property def proto_view(self): return self.ui.txtEdProto.cur_view def __init__(self, proto_analyzer: ProtocolAnalyzer, undo_stack: QUndoStack, project_manager, parent=None): super().__init__(parent) self.undo_stack = undo_stack self.ui = Ui_SignalFrame() self.ui.setupUi(self) util.set_splitter_stylesheet(self.ui.splitter) self.__set_spectrogram_adjust_widgets_visibility() self.ui.gvSignal.init_undo_stack(self.undo_stack) self.ui.txtEdProto.setFont(util.get_monospace_font()) self.ui.txtEdProto.participants = project_manager.participants self.ui.txtEdProto.messages = proto_analyzer.messages self.ui.gvSignal.participants = project_manager.participants self.filter_abort_wanted = False self.setAttribute(Qt.WA_DeleteOnClose) self.project_manager = project_manager self.proto_analyzer = proto_analyzer self.signal = proto_analyzer.signal if self.proto_analyzer is not None else None # type: Signal self.ui.gvSignal.protocol = self.proto_analyzer self.ui.gvSignal.set_signal(self.signal) self.ui.sliderFFTWindowSize.setValue(int(math.log2(Spectrogram.DEFAULT_FFT_WINDOW_SIZE))) self.ui.sliderSpectrogramMin.setValue(self.ui.gvSpectrogram.scene_manager.spectrogram.data_min) self.ui.sliderSpectrogramMax.setValue(self.ui.gvSpectrogram.scene_manager.spectrogram.data_max) self.dsp_filter = Filter([0.1] * 10, FilterType.moving_average) self.set_filter_button_caption() self.filter_dialog = FilterDialog(self.dsp_filter, parent=self) self.proto_selection_timer = QTimer(self) # For Update Proto Selection from ROI self.proto_selection_timer.setSingleShot(True) self.proto_selection_timer.setInterval(1) self.spectrogram_update_timer = QTimer(self) self.spectrogram_update_timer.setSingleShot(True) self.spectrogram_update_timer.setInterval(500) # Disabled because never used (see also set_protocol_visibilty()) self.ui.chkBoxSyncSelection.hide() if self.signal is not None: self.filter_menu = QMenu() self.apply_filter_to_selection_only = self.filter_menu.addAction(self.tr("Apply only to selection")) self.apply_filter_to_selection_only.setCheckable(True) self.apply_filter_to_selection_only.setChecked(False) self.configure_filter_action = self.filter_menu.addAction("Configure filter...") self.configure_filter_action.setIcon(QIcon.fromTheme("configure")) self.configure_filter_action.triggered.connect(self.on_configure_filter_action_triggered) self.ui.btnFilter.setMenu(self.filter_menu) if not self.signal.already_demodulated: self.auto_detect_menu = QMenu() self.detect_noise_action = self.auto_detect_menu.addAction(self.tr("Additionally detect noise")) self.detect_noise_action.setCheckable(True) self.detect_noise_action.setChecked(False) self.detect_modulation_action = self.auto_detect_menu.addAction(self.tr("Additionally detect modulation")) self.detect_modulation_action.setCheckable(True) self.detect_modulation_action.setChecked(False) self.ui.btnAutoDetect.setMenu(self.auto_detect_menu) if self.signal.wav_mode: if self.signal.already_demodulated: self.ui.lSignalTyp.setText("Demodulated (1-channel *.wav)") else: self.ui.lSignalTyp.setText("Signal (*.wav)") else: self.ui.lSignalTyp.setText("Complex Signal") self.ui.lineEditSignalName.setText(self.signal.name) self.ui.lSamplesInView.setText("{0:,}".format(self.signal.num_samples)) self.ui.lSamplesTotal.setText("{0:,}".format(self.signal.num_samples)) self.sync_protocol = self.ui.chkBoxSyncSelection.isChecked() self.ui.chkBoxSyncSelection.hide() self.ui.splitter.setSizes([self.ui.splitter.height(), 0]) self.protocol_selection_is_updateable = True self.scene_manager = SignalSceneManager(self.signal, self) self.ui.gvSignal.scene_manager = self.scene_manager self.scene_manager.scene.setParent(self.ui.gvSignal) self.ui.gvSignal.setScene(self.scene_manager.scene) self.ui.spinBoxCenterSpacing.setValue(self.signal.center_spacing) self.ui.spinBoxBitsPerSymbol.setValue(self.signal.bits_per_symbol) self.jump_sync = True self.on_btn_show_hide_start_end_clicked() self.refresh_signal_information(block=True) self.create_connects() self.set_protocol_visibility() self.ui.chkBoxShowProtocol.setChecked(True) self.ui.btnSaveSignal.hide() self.show_protocol(refresh=False) if self.signal.already_demodulated: self.ui.cbModulationType.hide() self.ui.labelModulation.hide() self.ui.labelNoise.hide() self.ui.spinBoxNoiseTreshold.hide() self.ui.btnAutoDetect.hide() self.ui.cbSignalView.setCurrentIndex(1) self.ui.cbSignalView.hide() self.ui.lSignalViewText.hide() else: self.ui.lSignalTyp.setText("Protocol") self.set_empty_frame_visibilities() self.create_connects() self.set_center_spacing_visibility() @property def spectrogram_is_active(self) -> bool: return self.ui.stackedWidget.currentWidget() == self.ui.pageSpectrogram def create_connects(self): self.ui.btnCloseSignal.clicked.connect(self.on_btn_close_signal_clicked) self.ui.btnReplay.clicked.connect(self.on_btn_replay_clicked) self.ui.btnAutoDetect.clicked.connect(self.on_btn_autodetect_clicked) self.ui.btnInfo.clicked.connect(self.on_info_btn_clicked) self.ui.btnShowHideStartEnd.clicked.connect(self.on_btn_show_hide_start_end_clicked) self.filter_dialog.filter_accepted.connect(self.on_filter_dialog_filter_accepted) self.ui.sliderFFTWindowSize.valueChanged.connect(self.on_slider_fft_window_size_value_changed) self.ui.sliderSpectrogramMin.valueChanged.connect(self.on_slider_spectrogram_min_value_changed) self.ui.sliderSpectrogramMax.valueChanged.connect(self.on_slider_spectrogram_max_value_changed) self.ui.gvSpectrogram.y_scale_changed.connect(self.on_gv_spectrogram_y_scale_changed) self.ui.gvSpectrogram.bandpass_filter_triggered.connect(self.on_bandpass_filter_triggered) self.ui.gvSpectrogram.export_fta_wanted.connect(self.on_export_fta_wanted) self.ui.btnAdvancedModulationSettings.clicked.connect(self.on_btn_advanced_modulation_settings_clicked) if self.signal is not None: self.ui.gvSignal.save_clicked.connect(self.save_signal) self.signal.samples_per_symbol_changed.connect(self.ui.spinBoxSamplesPerSymbol.setValue) self.signal.center_changed.connect(self.on_signal_center_changed) self.signal.noise_threshold_changed.connect(self.on_noise_threshold_changed) self.signal.modulation_type_changed.connect(self.ui.cbModulationType.setCurrentText) self.signal.tolerance_changed.connect(self.ui.spinBoxTolerance.setValue) self.signal.protocol_needs_update.connect(self.refresh_protocol) self.signal.data_edited.connect(self.on_signal_data_edited) # Crop/Delete Mute etc. self.signal.bits_per_symbol_changed.connect(self.ui.spinBoxBitsPerSymbol.setValue) self.signal.center_spacing_changed.connect(self.on_signal_center_spacing_changed) self.signal.sample_rate_changed.connect(self.on_signal_sample_rate_changed) self.signal.saved_status_changed.connect(self.on_signal_data_changed_before_save) self.ui.btnSaveSignal.clicked.connect(self.save_signal) self.signal.name_changed.connect(self.ui.lineEditSignalName.setText) self.ui.gvSignal.selection_width_changed.connect(self.start_proto_selection_timer) self.ui.gvSignal.sel_area_start_end_changed.connect(self.start_proto_selection_timer) self.proto_selection_timer.timeout.connect(self.update_protocol_selection_from_roi) self.spectrogram_update_timer.timeout.connect(self.on_spectrogram_update_timer_timeout) self.ui.lineEditSignalName.editingFinished.connect(self.change_signal_name) self.proto_analyzer.qt_signals.protocol_updated.connect(self.on_protocol_updated) self.ui.btnFilter.clicked.connect(self.on_btn_filter_clicked) self.ui.gvSignal.set_noise_clicked.connect(self.on_set_noise_in_graphic_view_clicked) self.ui.gvSignal.save_as_clicked.connect(self.save_signal_as) self.ui.gvSignal.export_demodulated_clicked.connect(self.export_demodulated) self.ui.gvSignal.create_clicked.connect(self.create_new_signal) self.ui.gvSignal.zoomed.connect(self.on_signal_zoomed) self.ui.gvSpectrogram.zoomed.connect(self.on_spectrum_zoomed) self.ui.gvSignal.sel_area_start_end_changed.connect(self.update_selection_area) self.ui.gvSpectrogram.sel_area_start_end_changed.connect(self.update_selection_area) self.ui.gvSpectrogram.selection_height_changed.connect(self.update_number_selected_samples) self.ui.gvSignal.sep_area_changed.connect(self.set_center) self.ui.sliderYScale.valueChanged.connect(self.on_slider_y_scale_value_changed) self.ui.spinBoxXZoom.valueChanged.connect(self.on_spinbox_x_zoom_value_changed) self.project_manager.project_updated.connect(self.on_participant_changed) self.ui.txtEdProto.participant_changed.connect(self.on_participant_changed) self.ui.gvSignal.participant_changed.connect(self.on_participant_changed) self.proto_selection_timer.timeout.connect(self.update_number_selected_samples) self.ui.cbSignalView.currentIndexChanged.connect(self.on_cb_signal_view_index_changed) self.ui.cbModulationType.currentTextChanged.connect(self.on_combobox_modulation_type_text_changed) self.ui.cbProtoView.currentIndexChanged.connect(self.on_combo_box_proto_view_index_changed) self.ui.chkBoxShowProtocol.stateChanged.connect(self.set_protocol_visibility) self.ui.chkBoxSyncSelection.stateChanged.connect(self.handle_protocol_sync_changed) self.ui.txtEdProto.proto_view_changed.connect(self.show_protocol) self.ui.txtEdProto.show_proto_clicked.connect(self.update_roi_from_protocol_selection) self.ui.txtEdProto.show_proto_clicked.connect(self.zoom_to_roi) self.ui.txtEdProto.selectionChanged.connect(self.update_roi_from_protocol_selection) self.ui.txtEdProto.deletion_wanted.connect(self.ui.gvSignal.on_delete_action_triggered) self.ui.spinBoxSelectionStart.valueChanged.connect(self.on_spinbox_selection_start_value_changed) self.ui.spinBoxSelectionEnd.valueChanged.connect(self.on_spinbox_selection_end_value_changed) self.ui.spinBoxCenterOffset.editingFinished.connect(self.on_spinbox_center_editing_finished) self.ui.spinBoxCenterSpacing.valueChanged.connect(self.on_spinbox_spacing_value_changed) self.ui.spinBoxCenterSpacing.editingFinished.connect(self.on_spinbox_spacing_editing_finished) self.ui.spinBoxTolerance.editingFinished.connect(self.on_spinbox_tolerance_editing_finished) self.ui.spinBoxNoiseTreshold.editingFinished.connect(self.on_spinbox_noise_threshold_editing_finished) self.ui.spinBoxSamplesPerSymbol.editingFinished.connect(self.on_spinbox_samples_per_symbol_editing_finished) self.ui.spinBoxBitsPerSymbol.editingFinished.connect(self.on_spinbox_bits_per_symbol_editing_finished) def refresh_signal_information(self, block=True): self.ui.spinBoxTolerance.blockSignals(block) self.ui.spinBoxCenterOffset.blockSignals(block) self.ui.spinBoxSamplesPerSymbol.blockSignals(block) self.ui.spinBoxNoiseTreshold.blockSignals(block) self.ui.spinBoxBitsPerSymbol.blockSignals(block) self.ui.spinBoxCenterSpacing.blockSignals(block) self.ui.spinBoxTolerance.setValue(self.signal.tolerance) self.ui.spinBoxCenterOffset.setValue(self.signal.center) self.ui.spinBoxSamplesPerSymbol.setValue(self.signal.samples_per_symbol) self.ui.spinBoxNoiseTreshold.setValue(self.signal.noise_threshold_relative) self.ui.cbModulationType.setCurrentText(self.signal.modulation_type) self.ui.btnAdvancedModulationSettings.setVisible(self.ui.cbModulationType.currentText() == "ASK") self.ui.spinBoxCenterSpacing.setValue(self.signal.center_spacing) self.ui.spinBoxBitsPerSymbol.setValue(self.signal.bits_per_symbol) self.ui.spinBoxTolerance.blockSignals(False) self.ui.spinBoxCenterOffset.blockSignals(False) self.ui.spinBoxSamplesPerSymbol.blockSignals(False) self.ui.spinBoxNoiseTreshold.blockSignals(False) self.ui.spinBoxCenterSpacing.blockSignals(False) self.ui.spinBoxBitsPerSymbol.blockSignals(False) self.set_center_spacing_visibility() def set_empty_frame_visibilities(self): for widget in dir(self.ui): w = getattr(self.ui, widget) if hasattr(w, "hide") and w not in (self.ui.lSignalNr, self.ui.lSignalTyp, self.ui.btnCloseSignal, self.ui.lineEditSignalName): w.hide() self.adjustSize() def cancel_filtering(self): self.filter_abort_wanted = True def update_number_selected_samples(self): if self.spectrogram_is_active: self.ui.lNumSelectedSamples.setText(str(abs(int(self.ui.gvSpectrogram.selection_area.length)))) self.__set_selected_bandwidth() return else: self.ui.lNumSelectedSamples.setText(str(abs(int(self.ui.gvSignal.selection_area.length)))) self.__set_duration() try: start, end = int(self.ui.gvSignal.selection_area.start), int(self.ui.gvSignal.selection_area.end) power_str = "-\u221e" # minus infinity if start < end: max_window_size = 10 ** 5 step_size = int(math.ceil((end - start) / max_window_size)) power = np.mean(self.signal.iq_array.subarray(start, end, step_size).magnitudes_normalized) if power > 0: power_str = Formatter.big_value_with_suffix(10 * np.log10(power), 2) self.ui.labelRSSI.setText("{} dBm".format(power_str)) except Exception as e: logger.exception(e) self.ui.labelRSSI.setText("") def change_signal_name(self): if self.signal is not None: self.signal.name = self.ui.lineEditSignalName.text() def __set_spectrogram_adjust_widgets_visibility(self): self.ui.labelFFTWindowSize.setVisible(self.ui.cbSignalView.currentIndex() == 2) self.ui.sliderFFTWindowSize.setVisible(self.ui.cbSignalView.currentIndex() == 2) self.ui.labelSpectrogramMin.setVisible(self.ui.cbSignalView.currentIndex() == 2) self.ui.labelSpectrogramMax.setVisible(self.ui.cbSignalView.currentIndex() == 2) self.ui.sliderSpectrogramMin.setVisible(self.ui.cbSignalView.currentIndex() == 2) self.ui.sliderSpectrogramMax.setVisible(self.ui.cbSignalView.currentIndex() == 2) def __set_selected_bandwidth(self): try: num_samples = int(self.ui.lNumSelectedSamples.text()) except ValueError: return if self.ui.gvSpectrogram.height_spectrogram and self.signal: bw = (num_samples / self.ui.gvSpectrogram.height_spectrogram) * self.signal.sample_rate self.ui.lDuration.setText(Formatter.big_value_with_suffix(bw) + "Hz") def __set_duration(self): # On Signal Sample Rate changed try: num_samples = int(self.ui.lNumSelectedSamples.text()) except ValueError: return if self.signal: t = num_samples / self.signal.sample_rate self.ui.lDuration.setText(Formatter.science_time(t)) def on_slider_y_scale_value_changed(self): try: gv = self.ui.gvSignal if self.ui.stackedWidget.currentIndex() == 0 else self.ui.gvSpectrogram yscale = self.ui.sliderYScale.value() current_factor = gv.sceneRect().height() / gv.view_rect().height() gv.scale(1, yscale / current_factor) x, w = gv.view_rect().x(), gv.view_rect().width() gv.centerOn(x + w / 2, gv.y_center) if gv.scene_type == 1: gv.scene().redraw_legend() except ZeroDivisionError: pass @pyqtSlot() def on_slider_fft_window_size_value_changed(self): self.spectrogram_update_timer.start() @pyqtSlot() def on_slider_spectrogram_min_value_changed(self): self.spectrogram_update_timer.start() @pyqtSlot() def on_slider_spectrogram_max_value_changed(self): self.spectrogram_update_timer.start() def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.drag_started.emit(self.mapToParent(event.pos())) drag = QDrag(self) mimeData = QMimeData() mimeData.setText("Move Signal") pixmap = QPixmap(self.rect().size()) self.render(pixmap, QPoint(), QRegion(self.rect())) drag.setPixmap(pixmap) drag.setMimeData(mimeData) drag.exec_() def set_filter_button_caption(self): self.ui.btnFilter.setText("Filter ({0})".format(self.dsp_filter.filter_type.value)) def dragMoveEvent(self, event): event.accept() def dragEnterEvent(self, event): event.acceptProposedAction() def dropEvent(self, event: QDropEvent): if len(event.mimeData().urls()) == 0: self.frame_dropped.emit(self.mapToParent(event.pos())) else: self.files_dropped.emit(event.mimeData().urls()) def create_new_signal(self, start, end): if start != end: new_signal = self.signal.create_new(start=start, end=end) self.signal_created.emit(new_signal) else: Errors.empty_selection() def my_close(self): not_show = settings.read('not_show_close_dialog', False, type=bool) if not not_show: cb = QCheckBox("Do not show this again.") msgbox = QMessageBox(QMessageBox.Question, "Confirm close", "Are you sure you want to close?") msgbox.addButton(QMessageBox.Yes) msgbox.addButton(QMessageBox.No) msgbox.setDefaultButton(QMessageBox.No) msgbox.setCheckBox(cb) reply = msgbox.exec() not_show_again = bool(cb.isChecked()) settings.write("not_show_close_dialog", not_show_again) self.not_show_again_changed.emit() if reply != QMessageBox.Yes: return self.closed.emit(self) def save_signal(self): if len(self.signal.filename) > 0: self.signal.save() else: self.save_signal_as() def save_signal_as(self): try: FileOperator.save_data_dialog(self.signal.name, self.signal.iq_array, self.signal.sample_rate, self.signal.wav_mode) except Exception as e: Errors.exception(e) def export_demodulated(self): try: initial_name = self.signal.name + "-demodulated.complex" except Exception as e: logger.exception(e) initial_name = "demodulated.complex" filename = FileOperator.get_save_file_name(initial_name) if filename: try: self.setCursor(Qt.WaitCursor) data = self.signal.qad if filename.endswith(".wav"): data = self.signal.qad.astype(np.float32) data /= np.max(np.abs(data)) FileOperator.save_data(IQArray(data, skip_conversion=True), filename, self.signal.sample_rate, num_channels=1) self.unsetCursor() except Exception as e: QMessageBox.critical(self, self.tr("Error exporting demodulated data"), e.args[0]) def draw_signal(self, full_signal=False): self.scene_manager.scene_type = self.ui.cbSignalView.currentIndex() self.scene_manager.init_scene() if full_signal: self.ui.gvSignal.show_full_scene() else: self.ui.gvSignal.redraw_view() self.ui.gvSignal.y_sep = -self.signal.center def restore_protocol_selection(self, sel_start, sel_end, start_message, end_message, old_protoview): if old_protoview == self.proto_view: return self.protocol_selection_is_updateable = False sel_start = int(self.proto_analyzer.convert_index(sel_start, old_protoview, self.proto_view, True)[0]) sel_end = int(math.ceil(self.proto_analyzer.convert_index(sel_end, old_protoview, self.proto_view, True)[1])) c = self.ui.txtEdProto.textCursor() c.setPosition(0) cur_message = 0 i = 0 text = self.ui.txtEdProto.toPlainText() while cur_message < start_message: if text[i] == "\n": cur_message += 1 i += 1 c.movePosition(QTextCursor.Right, QTextCursor.MoveAnchor, i) c.movePosition(QTextCursor.Right, QTextCursor.MoveAnchor, sel_start) text = text[i:] i = 0 while cur_message < end_message: if text[i] == "\n": cur_message += 1 i += 1 c.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, i) c.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, sel_end) self.ui.txtEdProto.setTextCursor(c) self.protocol_selection_is_updateable = True def update_protocol(self): self.ui.txtEdProto.setEnabled(False) self.ui.txtEdProto.setText("Demodulating...") qApp.processEvents() self.proto_analyzer.get_protocol_from_signal() def show_protocol(self, old_view=-1, refresh=False): if not self.proto_analyzer: return if not self.ui.chkBoxShowProtocol.isChecked(): return if old_view == -1: old_view = self.ui.cbProtoView.currentIndex() if self.proto_analyzer.messages is None or refresh: self.update_protocol() else: # Keep things synchronized and restore selection self.ui.txtEdProto.blockSignals(True) self.ui.cbProtoView.blockSignals(True) self.ui.cbProtoView.setCurrentIndex(self.proto_view) self.ui.cbProtoView.blockSignals(False) start_message = 0 sel_start = self.ui.txtEdProto.textCursor().selectionStart() text = self.ui.txtEdProto.toPlainText()[:sel_start] sel_start = 0 read_pause = False for t in text: if t == "\t": read_pause = True if not read_pause: sel_start += 1 if t == "\n": sel_start = 0 start_message += 1 read_pause = False sel_end = self.ui.txtEdProto.textCursor().selectionEnd() text = self.ui.txtEdProto.toPlainText()[self.ui.txtEdProto.textCursor().selectionStart():sel_end] end_message = 0 sel_end = 0 read_pause = False for t in text: if t == "\t": read_pause = True if not read_pause: sel_end += 1 if t == "\n": sel_end = 0 end_message += 1 read_pause = False self.ui.txtEdProto.setHtml(self.proto_analyzer.plain_to_html(self.proto_view)) try: self.restore_protocol_selection(sel_start, sel_end, start_message, end_message, old_view) except TypeError: # Without try/except: segfault (TypeError) when changing sample_rate in info dialog of signal pass self.ui.txtEdProto.blockSignals(False) def draw_spectrogram(self, show_full_scene=False, force_redraw=False): self.setCursor(Qt.WaitCursor) window_size = 2 ** self.ui.sliderFFTWindowSize.value() data_min, data_max = self.ui.sliderSpectrogramMin.value(), self.ui.sliderSpectrogramMax.value() redraw_needed = self.ui.gvSpectrogram.scene_manager.set_parameters(self.signal.iq_array.data, window_size=window_size, data_min=data_min, data_max=data_max) self.ui.gvSpectrogram.scene_manager.update_scene_rect() if show_full_scene: self.ui.gvSpectrogram.show_full_scene() if redraw_needed or force_redraw: self.ui.gvSpectrogram.scene_manager.show_full_scene() self.ui.gvSpectrogram.show_full_scene() self.on_slider_y_scale_value_changed() self.__set_samples_in_view() self.unsetCursor() def eliminate(self): self.proto_selection_timer.stop() self.ui.verticalLayout.removeItem(self.ui.additionalInfos) if self.signal is not None: # Avoid memory leaks self.scene_manager.eliminate() self.signal.eliminate() self.proto_analyzer.eliminate() self.ui.gvSignal.scene_manager.eliminate() self.ui.gvSignal.eliminate() self.ui.gvSpectrogram.eliminate() self.scene_manager = None self.signal = None self.proto_analyzer = None self.ui.layoutWidget.setParent(None) self.ui.layoutWidget.deleteLater() self.setParent(None) self.deleteLater() def __handle_graphic_view_zoomed(self, graphic_view): self.ui.lSamplesInView.setText("{0:n}".format(int(graphic_view.view_rect().width()))) self.ui.spinBoxXZoom.blockSignals(True) self.ui.spinBoxXZoom.setValue(int(graphic_view.sceneRect().width() / graphic_view.view_rect().width() * 100)) self.ui.spinBoxXZoom.blockSignals(False) @pyqtSlot() def on_signal_zoomed(self): self.__handle_graphic_view_zoomed(self.ui.gvSignal) @pyqtSlot() def on_spectrum_zoomed(self): self.__handle_graphic_view_zoomed(self.ui.gvSpectrogram) @pyqtSlot(int) def on_spinbox_x_zoom_value_changed(self, value: int): graphic_view = self.ui.gvSpectrogram if self.spectrogram_is_active else self.ui.gvSignal zoom_factor = value / 100 current_factor = graphic_view.sceneRect().width() / graphic_view.view_rect().width() graphic_view.zoom(zoom_factor / current_factor) @pyqtSlot() def on_btn_close_signal_clicked(self): self.my_close() @pyqtSlot() def on_set_noise_in_graphic_view_clicked(self): self.setCursor(Qt.WaitCursor) start = self.ui.gvSignal.selection_area.x end = start + self.ui.gvSignal.selection_area.width new_thresh = self.signal.calc_relative_noise_threshold_from_range(start, end) self.ui.spinBoxNoiseTreshold.setValue(new_thresh) self.ui.spinBoxNoiseTreshold.editingFinished.emit() self.unsetCursor() @pyqtSlot(float) def on_signal_center_spacing_changed(self, value: float): self.ui.spinBoxCenterSpacing.setValue(value) if self.ui.gvSignal.scene_type == 1: self.ui.gvSignal.scene().redraw_legend() @pyqtSlot() def on_noise_threshold_changed(self): self.ui.spinBoxNoiseTreshold.setValue(self.signal.noise_threshold_relative) minimum = self.signal.noise_min_plot maximum = self.signal.noise_max_plot if self.ui.cbSignalView.currentIndex() == 0: # Draw Noise only in Analog View self.ui.gvSignal.scene().draw_noise_area(minimum, maximum - minimum) @pyqtSlot(int) def on_spinbox_selection_start_value_changed(self, value: int): if self.spectrogram_is_active: self.ui.gvSpectrogram.set_vertical_selection(y=self.ui.gvSpectrogram.sceneRect().height() - value) self.ui.gvSpectrogram.emit_selection_size_changed() self.ui.gvSpectrogram.selection_area.finished = True else: self.ui.gvSignal.set_horizontal_selection(x=value) self.ui.gvSignal.selection_area.finished = True self.ui.gvSignal.emit_selection_size_changed() @pyqtSlot(int) def on_spinbox_selection_end_value_changed(self, value: int): if self.spectrogram_is_active: self.ui.gvSpectrogram.set_vertical_selection(h=self.ui.spinBoxSelectionStart.value() - value) self.ui.gvSpectrogram.emit_selection_size_changed() self.ui.gvSpectrogram.selection_area.finished = True else: self.ui.gvSignal.set_horizontal_selection(w=value - self.ui.spinBoxSelectionStart.value()) self.ui.gvSignal.selection_area.finished = True self.ui.gvSignal.emit_selection_size_changed() @pyqtSlot() def on_protocol_updated(self): self.ui.gvSignal.redraw_view() # Participants may have changed self.ui.txtEdProto.setEnabled(True) self.ui.txtEdProto.setHtml(self.proto_analyzer.plain_to_html(self.proto_view)) @pyqtSlot() def handle_protocol_sync_changed(self): self.sync_protocol = self.ui.chkBoxSyncSelection.isChecked() @pyqtSlot() def set_protocol_visibility(self): checked = self.ui.chkBoxShowProtocol.isChecked() if checked: self.show_protocol() self.ui.cbProtoView.setEnabled(True) # Disabled because never used # self.ui.chkBoxSyncSelection.show() self.ui.txtEdProto.show() else: self.ui.txtEdProto.hide() self.ui.chkBoxSyncSelection.hide() self.ui.cbProtoView.setEnabled(False) @pyqtSlot() def on_cb_signal_view_index_changed(self): self.setCursor(Qt.WaitCursor) self.__set_spectrogram_adjust_widgets_visibility() if self.ui.cbSignalView.currentText().lower() == "spectrogram": self.ui.stackedWidget.setCurrentWidget(self.ui.pageSpectrogram) self.draw_spectrogram(show_full_scene=True) self.__set_selected_bandwidth() self.ui.labelRSSI.hide() else: self.ui.stackedWidget.setCurrentWidget(self.ui.pageSignal) self.ui.gvSignal.scene_type = self.ui.cbSignalView.currentIndex() self.scene_manager.mod_type = self.signal.modulation_type self.ui.gvSignal.redraw_view(reinitialize=True) self.ui.labelRSSI.show() self.ui.gvSignal.auto_fit_view() self.ui.gvSignal.refresh_selection_area() qApp.processEvents() self.on_slider_y_scale_value_changed() # apply YScale to new view self.__set_samples_in_view() self.__set_duration() self.unsetCursor() @pyqtSlot() def on_btn_autodetect_clicked(self): self.ui.btnAutoDetect.setEnabled(False) self.setCursor(Qt.WaitCursor) try: detect_modulation = self.detect_modulation_action.isChecked() except AttributeError: detect_modulation = False try: detect_noise = self.detect_noise_action.isChecked() except AttributeError: detect_noise = False success = self.signal.auto_detect(detect_modulation=detect_modulation, detect_noise=detect_noise) self.ui.btnAutoDetect.setEnabled(True) self.unsetCursor() if not success: Errors.generic_error(self.tr("Autodetection failed"), self.tr("Failed to autodetect parameters for this signal.")) @pyqtSlot() def on_btn_replay_clicked(self): project_manager = self.project_manager try: dialog = SendDialog(project_manager, modulated_data=self.signal.iq_array, parent=self) except OSError as e: logger.error(repr(e)) return if dialog.has_empty_device_list: Errors.no_device() dialog.close() return dialog.device_parameters_changed.connect(project_manager.set_device_parameters) dialog.show() dialog.graphics_view.show_full_scene(reinitialize=True) @pyqtSlot(int, int) def update_selection_area(self, start, end): self.update_number_selected_samples() self.ui.spinBoxSelectionStart.blockSignals(True) self.ui.spinBoxSelectionStart.setValue(start) self.ui.spinBoxSelectionStart.blockSignals(False) self.ui.spinBoxSelectionEnd.blockSignals(True) self.ui.spinBoxSelectionEnd.setValue(end) self.ui.spinBoxSelectionEnd.blockSignals(False) @pyqtSlot() def refresh_protocol(self): self.show_protocol(refresh=True) @pyqtSlot(int) def on_combo_box_proto_view_index_changed(self, index: int): old_view = self.ui.txtEdProto.cur_view self.ui.txtEdProto.cur_view = index self.show_protocol(old_view=old_view) @pyqtSlot(float) def set_center(self, th): self.ui.spinBoxCenterOffset.setValue(th) self.ui.spinBoxCenterOffset.editingFinished.emit() def set_roi_from_protocol_analysis(self, start_message, start_pos, end_message, end_pos, view_type): if not self.proto_analyzer: return if not self.ui.chkBoxShowProtocol.isChecked(): self.ui.chkBoxShowProtocol.setChecked(True) self.set_protocol_visibility() self.ui.cbProtoView.setCurrentIndex(view_type) if view_type == 1: # Hex View start_pos *= 4 end_pos *= 4 elif view_type == 2: # ASCII View start_pos *= 8 end_pos *= 8 sample_pos, num_samples = self.proto_analyzer.get_samplepos_of_bitseq(start_message, start_pos, end_message, end_pos, True) self.protocol_selection_is_updateable = False if sample_pos != -1: if self.jump_sync and self.sync_protocol: self.ui.gvSignal.centerOn(sample_pos, self.ui.gvSignal.y_center) self.ui.gvSignal.set_horizontal_selection(sample_pos, num_samples) self.ui.gvSignal.centerOn(sample_pos + num_samples, self.ui.gvSignal.y_center) else: self.ui.gvSignal.set_horizontal_selection(sample_pos, num_samples) self.ui.gvSignal.zoom_to_selection(sample_pos, sample_pos + num_samples) else: self.ui.gvSignal.clear_horizontal_selection() self.protocol_selection_is_updateable = True self.update_protocol_selection_from_roi() @pyqtSlot() def update_roi_from_protocol_selection(self): text_edit = self.ui.txtEdProto start_pos, end_pos = text_edit.textCursor().selectionStart(), text_edit.textCursor().selectionEnd() if start_pos == end_pos == -1: return forward_selection = text_edit.textCursor().anchor() <= text_edit.textCursor().position() if start_pos > end_pos: start_pos, end_pos = end_pos, start_pos text = text_edit.toPlainText() start_message = text[:start_pos].count("\n") end_message = start_message + text[start_pos:end_pos].count("\n") newline_pos = text[:start_pos].rfind("\n") if newline_pos != -1: start_pos -= (newline_pos + 1) newline_pos = text[:end_pos].rfind("\n") if newline_pos != -1: end_pos -= (newline_pos + 1) factor = 1 if text_edit.cur_view == 0 else 4 if text_edit.cur_view == 1 else 8 start_pos *= factor end_pos *= factor try: include_last_pause = False s = text_edit.textCursor().selectionStart() e = text_edit.textCursor().selectionEnd() if s > e: s, e = e, s selected_text = text[s:e] last_newline = selected_text.rfind("\n") if last_newline == -1: last_newline = 0 if selected_text.endswith(" "): end_pos -= 1 elif selected_text.endswith(" \t"): end_pos -= 2 if "[" in selected_text[last_newline:]: include_last_pause = True sample_pos, num_samples = self.proto_analyzer.get_samplepos_of_bitseq(start_message, start_pos, end_message, end_pos, include_last_pause) except IndexError: return self.ui.gvSignal.blockSignals(True) if sample_pos != -1: if self.jump_sync and self.sync_protocol: self.ui.gvSignal.centerOn(sample_pos, self.ui.gvSignal.y_center) self.ui.gvSignal.set_horizontal_selection(sample_pos, num_samples) if forward_selection: # Forward Selection --> Center ROI to End of Selection self.ui.gvSignal.centerOn(sample_pos + num_samples, self.ui.gvSignal.y_center) else: # Backward Selection --> Center ROI to Start of Selection self.ui.gvSignal.centerOn(sample_pos, self.ui.gvSignal.y_center) else: self.ui.gvSignal.set_horizontal_selection(sample_pos, num_samples) else: self.ui.gvSignal.clear_horizontal_selection() self.ui.gvSignal.blockSignals(False) self.update_number_selected_samples() def zoom_to_roi(self): roi = self.ui.gvSignal.selection_area start, end = roi.x, roi.x + roi.width self.ui.gvSignal.zoom_to_selection(start, end) @pyqtSlot() def start_proto_selection_timer(self): self.proto_selection_timer.start() @pyqtSlot() def update_protocol_selection_from_roi(self): protocol = self.proto_analyzer if protocol is None or protocol.messages is None or not self.ui.chkBoxShowProtocol.isChecked(): return start = self.ui.gvSignal.selection_area.x w = self.ui.gvSignal.selection_area.width if w < 0: start += w w = -w c = self.ui.txtEdProto.textCursor() self.jump_sync = False self.ui.txtEdProto.blockSignals(True) try: start_message, start_index, end_message, end_index = protocol.get_bitseq_from_selection(start, w) except IndexError: c.clearSelection() self.ui.txtEdProto.setTextCursor(c) self.jump_sync = True self.ui.txtEdProto.blockSignals(False) return if start_message == -1 or end_index == -1 or start_index == -1 or end_message == -1: c.clearSelection() self.ui.txtEdProto.setTextCursor(c) self.jump_sync = True self.ui.txtEdProto.blockSignals(False) return start_index = int(protocol.convert_index(start_index, 0, self.proto_view, True)[0]) end_index = int(math.ceil(protocol.convert_index(end_index, 0, self.proto_view, True)[1])) + 1 text = self.ui.txtEdProto.toPlainText() n = 0 message_pos = 0 c.setPosition(0) for i, t in enumerate(text): message_pos += 1 if t == "\n": n += 1 message_pos = 0 if n == start_message and message_pos == start_index: c.setPosition(i + 1, QTextCursor.MoveAnchor) if n == end_message and message_pos == end_index: c.setPosition(i, QTextCursor.KeepAnchor) break self.ui.txtEdProto.setTextCursor(c) self.ui.txtEdProto.blockSignals(False) self.jump_sync = True def __set_samples_in_view(self): if self.spectrogram_is_active: self.ui.lSamplesInView.setText("{0:n}".format(int(self.ui.gvSpectrogram.view_rect().width()))) self.ui.lSamplesTotal.setText("{0:n}".format(self.ui.gvSpectrogram.width_spectrogram)) else: self.ui.lSamplesInView.setText("{0:n}".format(int(self.ui.gvSignal.view_rect().width()))) self.ui.lSamplesTotal.setText("{0:n}".format(self.signal.num_samples)) def refresh_signal(self, draw_full_signal=False): self.draw_signal(draw_full_signal) self.__set_samples_in_view() self.update_number_selected_samples() self.on_slider_y_scale_value_changed() @pyqtSlot(float) def on_signal_center_changed(self, center): self.ui.gvSignal.y_sep = -center if self.ui.cbSignalView.currentIndex() > 0: self.scene_manager.scene.draw_sep_area(-self.signal.center_thresholds) self.ui.spinBoxCenterOffset.blockSignals(False) self.ui.spinBoxCenterOffset.setValue(center) def on_spinbox_noise_threshold_editing_finished(self): if self.signal is not None and self.signal.noise_threshold_relative != self.ui.spinBoxNoiseTreshold.value(): noise_action = ChangeSignalParameter(signal=self.signal, protocol=self.proto_analyzer, parameter_name="noise_threshold_relative", parameter_value=self.ui.spinBoxNoiseTreshold.value()) self.undo_stack.push(noise_action) def contextMenuEvent(self, event: QContextMenuEvent): if self.signal is None: return menu = QMenu() apply_to_all_action = menu.addAction(self.tr("Apply values (BitLen, 0/1-Threshold, Tolerance) to all signals")) menu.addSeparator() auto_detect_action = menu.addAction(self.tr("Auto-Detect signal parameters")) action = menu.exec_(self.mapToGlobal(event.pos())) if action == apply_to_all_action: self.setCursor(Qt.WaitCursor) self.apply_to_all_clicked.emit(self.signal) self.unsetCursor() elif action == auto_detect_action: self.setCursor(Qt.WaitCursor) self.signal.auto_detect(detect_modulation=False, detect_noise=False) self.unsetCursor() def show_modulation_type(self): self.ui.cbModulationType.blockSignals(True) self.ui.cbModulationType.setCurrentText(self.signal.modulation_type) self.ui.cbModulationType.blockSignals(False) def on_participant_changed(self): if hasattr(self, "proto_analyzer") and self.proto_analyzer: self.proto_analyzer.qt_signals.protocol_updated.emit() def resizeEvent(self, event: QResizeEvent): old_width, new_width = max(1, event.oldSize().width()), max(1, event.size().width()) super().resizeEvent(event) self.on_slider_y_scale_value_changed() # Force update of GVS, when size changed e.g. when Project Tree is opened if not self.spectrogram_is_active: self.ui.gvSignal.zoom(new_width / old_width, zoom_to_mouse_cursor=False) def set_center_spacing_visibility(self): visible = self.ui.spinBoxBitsPerSymbol.value() > 1 self.ui.spinBoxCenterSpacing.setVisible(visible) self.ui.lCenterSpacing.setVisible(visible) @pyqtSlot() def on_info_btn_clicked(self): sdc = SignalDetailsDialog(self.signal, self) sdc.show() @pyqtSlot(str) def on_combobox_modulation_type_text_changed(self, txt: str): if txt != self.signal.modulation_type: modulation_action = ChangeSignalParameter(signal=self.signal, protocol=self.proto_analyzer, parameter_name="modulation_type", parameter_value=txt) self.undo_stack.push(modulation_action) self.scene_manager.mod_type = txt if self.ui.cbSignalView.currentIndex() == 1: self.scene_manager.init_scene() self.on_slider_y_scale_value_changed() self.ui.btnAdvancedModulationSettings.setVisible(self.ui.cbModulationType.currentText() == "ASK") @pyqtSlot() def on_signal_data_changed_before_save(self): font = self.ui.lineEditSignalName.font() self.ui.gvSignal.auto_fit_on_resize_is_blocked = True if self.signal.changed: font.setBold(True) self.ui.btnSaveSignal.show() else: font.setBold(False) self.ui.btnSaveSignal.hide() for i in range(self.undo_stack.count()): cmd = self.undo_stack.command(i) if isinstance(cmd, EditSignalAction): # https://github.com/jopohl/urh/issues/570 cmd.signal_was_changed = True qApp.processEvents() self.ui.gvSignal.auto_fit_on_resize_is_blocked = False self.ui.lineEditSignalName.setFont(font) @pyqtSlot() def on_btn_show_hide_start_end_clicked(self): show = self.ui.btnShowHideStartEnd.isChecked() if show: self.ui.btnShowHideStartEnd.setIcon(QIcon.fromTheme("arrow-down-double")) self.ui.verticalLayout.insertItem(2, self.ui.additionalInfos) else: self.ui.btnShowHideStartEnd.setIcon(QIcon.fromTheme("arrow-up-double")) self.ui.verticalLayout.removeItem(self.ui.additionalInfos) for i in range(self.ui.additionalInfos.count()): try: self.ui.additionalInfos.itemAt(i).widget().setVisible(show) except AttributeError: pass @pyqtSlot() def on_spinbox_tolerance_editing_finished(self): if self.signal.tolerance != self.ui.spinBoxTolerance.value(): self.ui.spinBoxTolerance.blockSignals(True) tolerance_action = ChangeSignalParameter(signal=self.signal, protocol=self.proto_analyzer, parameter_name="tolerance", parameter_value=self.ui.spinBoxTolerance.value()) self.undo_stack.push(tolerance_action) self.ui.spinBoxTolerance.blockSignals(False) @pyqtSlot() def on_spinbox_samples_per_symbol_editing_finished(self): if self.signal.samples_per_symbol != self.ui.spinBoxSamplesPerSymbol.value(): self.ui.spinBoxSamplesPerSymbol.blockSignals(True) action = ChangeSignalParameter(signal=self.signal, protocol=self.proto_analyzer, parameter_name="samples_per_symbol", parameter_value=self.ui.spinBoxSamplesPerSymbol.value()) self.undo_stack.push(action) self.ui.spinBoxSamplesPerSymbol.blockSignals(False) @pyqtSlot() def on_spinbox_bits_per_symbol_editing_finished(self): if self.signal.bits_per_symbol != self.ui.spinBoxBitsPerSymbol.value(): self.ui.spinBoxBitsPerSymbol.blockSignals(True) bits_per_symbol_action = ChangeSignalParameter(signal=self.signal, protocol=self.proto_analyzer, parameter_name="bits_per_symbol", parameter_value=self.ui.spinBoxBitsPerSymbol.value()) self.undo_stack.push(bits_per_symbol_action) self.ui.spinBoxBitsPerSymbol.blockSignals(False) if self.ui.gvSignal.scene_type == 1: self.ui.gvSignal.scene().draw_sep_area(-self.signal.center_thresholds) self.set_center_spacing_visibility() @pyqtSlot() def on_spinbox_center_editing_finished(self): if self.signal.center != self.ui.spinBoxCenterOffset.value(): self.ui.spinBoxCenterOffset.blockSignals(True) center_action = ChangeSignalParameter(signal=self.signal, protocol=self.proto_analyzer, parameter_name="center", parameter_value=self.ui.spinBoxCenterOffset.value()) self.undo_stack.push(center_action) self.ui.spinBoxCenterOffset.blockSignals(False) @pyqtSlot() def on_spinbox_spacing_value_changed(self): if self.ui.gvSignal.scene_type == 1: thresholds = self.signal.get_thresholds_for_center(self.signal.center, self.ui.spinBoxCenterSpacing.value()) self.ui.gvSignal.scene().draw_sep_area(-thresholds) @pyqtSlot() def on_spinbox_spacing_editing_finished(self): if self.signal.center_spacing != self.ui.spinBoxCenterSpacing.value(): self.ui.spinBoxCenterSpacing.blockSignals(True) center_spacing_action = ChangeSignalParameter(signal=self.signal, protocol=self.proto_analyzer, parameter_name="center_spacing", parameter_value=self.ui.spinBoxCenterSpacing.value()) self.undo_stack.push(center_spacing_action) self.ui.spinBoxCenterSpacing.blockSignals(False) if self.ui.gvSignal.scene_type == 1: self.ui.gvSignal.scene().draw_sep_area(-self.signal.center_thresholds) @pyqtSlot() def refresh(self, draw_full_signal=False): self.refresh_signal(draw_full_signal=draw_full_signal) self.refresh_signal_information(block=True) self.show_protocol(refresh=True) @pyqtSlot() def on_btn_filter_clicked(self): if self.apply_filter_to_selection_only.isChecked(): start, end = self.ui.gvSignal.selection_area.start, self.ui.gvSignal.selection_area.end else: start, end = 0, self.signal.num_samples filter_action = EditSignalAction(signal=self.signal, mode=EditAction.filter, start=start, end=end, dsp_filter=self.dsp_filter, protocol=self.proto_analyzer) self.undo_stack.push(filter_action) @pyqtSlot() def on_configure_filter_action_triggered(self): self.filter_dialog.set_dsp_filter_status(self.dsp_filter.filter_type) self.filter_dialog.exec() @pyqtSlot(Filter) def on_filter_dialog_filter_accepted(self, dsp_filter: Filter): if dsp_filter is not None: self.dsp_filter = dsp_filter self.set_filter_button_caption() @pyqtSlot() def on_spectrogram_update_timer_timeout(self): self.draw_spectrogram(show_full_scene=True) @pyqtSlot(float) def on_gv_spectrogram_y_scale_changed(self, scale: float): self.ui.sliderYScale.blockSignals(True) self.ui.sliderYScale.setValue(self.ui.sliderYScale.value() * scale) self.ui.sliderYScale.blockSignals(False) @pyqtSlot(float, float) def on_bandpass_filter_triggered(self, f_low: float, f_high: float): self.filter_abort_wanted = False QApplication.instance().setOverrideCursor(Qt.WaitCursor) filter_bw = Filter.read_configured_filter_bw() filtered = Array("f", 2 * self.signal.num_samples) p = Process(target=perform_filter, args=(filtered, self.signal.iq_array.as_complex64(), f_low, f_high, filter_bw)) p.daemon = True p.start() while p.is_alive(): QApplication.instance().processEvents() if self.filter_abort_wanted: p.terminate() p.join() QApplication.instance().restoreOverrideCursor() return time.sleep(0.1) filtered = np.frombuffer(filtered.get_obj(), dtype=np.complex64) signal = self.signal.create_new(new_data=filtered.astype(np.complex64)) signal.name = self.signal.name + " filtered with f_low={0:.4n} f_high={1:.4n} bw={2:.4n}".format(f_low, f_high, filter_bw) self.signal_created.emit(signal) QApplication.instance().restoreOverrideCursor() def on_signal_data_edited(self): self.refresh_signal() self.ui.gvSpectrogram.scene_manager.samples_need_update = True @pyqtSlot() def on_signal_sample_rate_changed(self): if self.spectrogram_is_active: self.__set_selected_bandwidth() else: self.__set_duration() self.show_protocol() # update times @pyqtSlot(int) def on_pause_threshold_edited(self, pause_threshold: int): if self.signal.pause_threshold != pause_threshold: pause_threshold_action = ChangeSignalParameter(signal=self.signal, protocol=self.proto_analyzer, parameter_name="pause_threshold", parameter_value=pause_threshold) self.undo_stack.push(pause_threshold_action) @pyqtSlot(int) def on_message_length_divisor_edited(self, message_length_divisor: int): if self.signal.message_length_divisor != message_length_divisor: message_length_divisor_action = ChangeSignalParameter(signal=self.signal, protocol=self.proto_analyzer, parameter_name="message_length_divisor", parameter_value=message_length_divisor) self.undo_stack.push(message_length_divisor_action) def get_advanced_modulation_settings_dialog(self): dialog = AdvancedModulationOptionsDialog(self.signal.pause_threshold, self.signal.message_length_divisor, parent=self) dialog.pause_threshold_edited.connect(self.on_pause_threshold_edited) dialog.message_length_divisor_edited.connect(self.on_message_length_divisor_edited) return dialog @pyqtSlot() def on_btn_advanced_modulation_settings_clicked(self): dialog = self.get_advanced_modulation_settings_dialog() dialog.exec_() @pyqtSlot() def on_export_fta_wanted(self): try: initial_name = self.signal.name + "-spectrogram.ft" except Exception as e: logger.exception(e) initial_name = "spectrogram.ft" filename = FileOperator.get_save_file_name(initial_name, caption="Export spectrogram") if not filename: return QApplication.setOverrideCursor(Qt.WaitCursor) try: self.ui.gvSpectrogram.scene_manager.spectrogram.export_to_fta(sample_rate=self.signal.sample_rate, filename=filename, include_amplitude=filename.endswith(".fta")) except Exception as e: Errors.exception(e) finally: QApplication.restoreOverrideCursor()