class MainController(QMainWindow): def __init__(self, *args): super().__init__(*args) self.ui = Ui_MainWindow() self.ui.setupUi(self) OptionsController.write_default_options() self.project_save_timer = QTimer() self.project_manager = ProjectManager(self) self.plugin_manager = PluginManager() self.signal_tab_controller = SignalTabController( self.project_manager, parent=self.ui.tab_interpretation) self.ui.tab_interpretation.layout().addWidget( self.signal_tab_controller) self.compare_frame_controller = CompareFrameController( parent=self.ui.tab_protocol, plugin_manager=self.plugin_manager, project_manager=self.project_manager) self.ui.tab_protocol.layout().addWidget(self.compare_frame_controller) self.generator_tab_controller = GeneratorTabController( self.compare_frame_controller, self.project_manager, parent=self.ui.tab_generator) self.undo_group = QUndoGroup() self.undo_group.addStack(self.signal_tab_controller.signal_undo_stack) self.undo_group.addStack( self.compare_frame_controller.protocol_undo_stack) self.undo_group.addStack( self.generator_tab_controller.generator_undo_stack) self.undo_group.setActiveStack( self.signal_tab_controller.signal_undo_stack) self.ui.progressBar.hide() self.participant_legend_model = ParticipantLegendListModel( self.project_manager.participants) self.ui.listViewParticipants.setModel(self.participant_legend_model) gtc = self.generator_tab_controller gtc.ui.splitter.setSizes([gtc.width() / 0.7, gtc.width() / 0.3]) self.ui.tab_generator.layout().addWidget(self.generator_tab_controller) self.signal_protocol_dict = {} """:type: dict[SignalFrameController,ProtocolAnalyzer]""" self.signal_tab_controller.ui.lLoadingFile.setText("") self.ui.lnEdtTreeFilter.setClearButtonEnabled(True) group = QActionGroup(self) self.ui.actionFSK.setActionGroup(group) self.ui.actionOOK.setActionGroup(group) self.ui.actionNone.setActionGroup(group) self.ui.actionPSK.setActionGroup(group) self.signal_tab_controller.ui.lShiftStatus.clear() self.recentFileActionList = [] self.create_connects() self.update_recent_action_list() self.filemodel = FileSystemModel(self) path = QDir.homePath() self.filemodel.setIconProvider(FileIconProvider()) self.filemodel.setRootPath(path) self.file_proxy_model = FileFilterProxyModel(self) self.file_proxy_model.setSourceModel(self.filemodel) self.ui.fileTree.setModel(self.file_proxy_model) self.ui.fileTree.setRootIndex( self.file_proxy_model.mapFromSource(self.filemodel.index(path))) self.ui.fileTree.setToolTip(path) self.ui.fileTree.header().setSectionResizeMode(0, QHeaderView.Stretch) self.ui.fileTree.header().setSectionResizeMode(1, QHeaderView.Interactive) self.ui.fileTree.setFocus() self.generator_tab_controller.table_model.cfc = self.compare_frame_controller self.ui.actionConvert_Folder_to_Project.setEnabled(False) undo_action = self.undo_group.createUndoAction(self) undo_action.setIcon(QIcon.fromTheme("edit-undo")) undo_action.setShortcut(QKeySequence.Undo) self.ui.menuEdit.insertAction(self.ui.actionMinimize_all, undo_action) redo_action = self.undo_group.createRedoAction(self) redo_action.setIcon(QIcon.fromTheme("edit-redo")) redo_action.setShortcut(QKeySequence.Redo) self.ui.splitter.setSizes([0, 1]) self.ui.menuEdit.insertAction(self.ui.actionMinimize_all, redo_action) self.refresh_main_menu() self.apply_default_view() self.project_save_timer.start( ProjectManager.AUTOSAVE_INTERVAL_MINUTES * 60 * 1000) self.ui.actionProject_settings.setVisible(False) self.ui.actionSave_project.setVisible(False) # Disabled because never used self.ui.actionMinimize_all.setVisible(False) self.ui.actionMaximize_all.setVisible(False) def create_connects(self): self.ui.actionFullscreen_mode.setShortcut(QKeySequence.FullScreen) self.ui.actionOpen.setShortcut(QKeySequence(QKeySequence.Open)) self.ui.actionOpen_directory.setShortcut(QKeySequence("Ctrl+Shift+O")) self.ui.actionMinimize_all.setShortcut("F10") self.ui.actionMaximize_all.setShortcut("F11") self.ui.actionNew_Project.triggered.connect( self.on_new_project_action_triggered) self.ui.actionProject_settings.triggered.connect( self.on_project_settings_action_triggered) self.ui.actionSave_project.triggered.connect( self.project_manager.saveProject) self.ui.actionAbout_AutomaticHacker.triggered.connect( self.on_show_about_clicked) self.ui.actionRecord.triggered.connect( self.on_show_record_dialog_action_triggered) self.ui.actionFullscreen_mode.triggered.connect( self.on_fullscreen_action_triggered) self.ui.actionSaveAllSignals.triggered.connect( self.signal_tab_controller.save_all) self.ui.actionClose_all.triggered.connect( self.on_close_all_action_triggered) self.ui.actionOpen.triggered.connect( self.on_open_file_action_triggered) self.ui.actionOpen_directory.triggered.connect( self.on_open_directory_action_triggered) self.ui.actionDecoding.triggered.connect( self.on_show_decoding_dialog_triggered) self.ui.actionSpectrum_Analyzer.triggered.connect( self.on_show_spectrum_dialog_action_triggered) self.ui.actionOptions.triggered.connect( self.show_options_dialog_action_triggered) self.ui.actionSniff_protocol.triggered.connect( self.show_proto_sniff_dialog) self.ui.actionAbout_Qt.triggered.connect(QApplication.aboutQt) self.ui.actionMinimize_all.triggered.connect( self.signal_tab_controller.minimize_all) self.ui.actionMaximize_all.triggered.connect( self.signal_tab_controller.maximize_all) self.signal_tab_controller.frame_closed.connect( self.close_signal_frame) self.signal_tab_controller.signal_created.connect(self.add_signal) self.signal_tab_controller.ui.scrollArea.files_dropped.connect( self.on_files_dropped) self.signal_tab_controller.files_dropped.connect(self.on_files_dropped) self.signal_tab_controller.frame_was_dropped.connect( self.set_frame_numbers) self.compare_frame_controller.show_interpretation_clicked.connect( self.show_protocol_selection_in_interpretation) self.compare_frame_controller.files_dropped.connect( self.on_files_dropped) self.compare_frame_controller.show_decoding_clicked.connect( self.on_show_decoding_dialog_triggered) self.compare_frame_controller.ui.treeViewProtocols.files_dropped_on_group.connect( self.on_files_dropped_on_group) self.compare_frame_controller.participant_changed.connect( self.signal_tab_controller.on_participant_changed) self.compare_frame_controller.ui.treeViewProtocols.close_wanted.connect( self.on_cfc_close_wanted) self.compare_frame_controller.show_config_field_types_triggered.connect( self.on_show_field_types_config_action_triggered) self.ui.lnEdtTreeFilter.textChanged.connect( self.on_file_tree_filter_text_changed) self.ui.tabWidget.currentChanged.connect(self.on_selected_tab_changed) self.project_save_timer.timeout.connect( self.project_manager.saveProject) self.ui.actionConvert_Folder_to_Project.triggered.connect( self.project_manager.convert_folder_to_project) self.project_manager.project_loaded_status_changed.connect( self.ui.actionProject_settings.setVisible) self.project_manager.project_loaded_status_changed.connect( self.ui.actionSave_project.setVisible) self.project_manager.project_loaded_status_changed.connect( self.ui.actionConvert_Folder_to_Project.setDisabled) self.project_manager.project_updated.connect(self.on_project_updated) self.ui.textEditProjectDescription.textChanged.connect( self.on_text_edit_project_description_text_changed) self.ui.tabWidget_Project.tabBarDoubleClicked.connect( self.on_project_tab_bar_double_clicked) self.ui.listViewParticipants.doubleClicked.connect( self.on_project_settings_action_triggered) self.ui.menuFile.addSeparator() for i in range(constants.MAX_RECENT_FILE_NR): recentFileAction = QAction(self) recentFileAction.setVisible(False) recentFileAction.triggered.connect( self.on_open_recent_action_triggered) self.recentFileActionList.append(recentFileAction) self.ui.menuFile.addAction(self.recentFileActionList[i]) def add_protocol_file(self, filename): proto = self.compare_frame_controller.add_protocol_from_file(filename) if proto: sf = self.signal_tab_controller.add_empty_frame(filename, proto) self.signal_protocol_dict[sf] = proto self.set_frame_numbers() self.file_proxy_model.open_files.add(filename) def add_fuzz_profile(self, filename): self.ui.tabWidget.setCurrentIndex(2) self.generator_tab_controller.load_from_file(filename) def add_signalfile(self, filename: str, group_id=0): if not os.path.exists(filename): QMessageBox.critical( self, self.tr("File not Found"), self.tr( "The file {0} could not be found. Was it moved or renamed?" ).format(filename)) return alrdy_qad_demod = False if filename.endswith(".wav"): accept, alrdy_qad_demod = WavFileDialog.dialog(self) if not accept: return sig_name = os.path.splitext(os.path.basename(filename))[0] # Use default sample rate for signal # Sample rate will be overriden in case of a project later signal = Signal( filename, sig_name, wav_is_qad_demod=alrdy_qad_demod, sample_rate=self.project_manager.device_conf["sample_rate"]) if self.project_manager.project_file is None: self.adjust_for_current_file(signal.filename) self.file_proxy_model.open_files.add(filename) self.add_signal(signal, group_id) def add_signal(self, signal, group_id=0): self.ui.progressBar.setMaximum(100) self.ui.progressBar.show() pa = ProtocolAnalyzer(signal) sframe = self.signal_tab_controller.add_signal_frame(pa) self.ui.progressBar.setValue(10) QApplication.processEvents() pa = self.compare_frame_controller.add_protocol(pa, group_id) self.ui.progressBar.setValue(20) QApplication.processEvents() signal.blockSignals(True) has_entry = self.project_manager.read_project_file_for_signal(signal) if not has_entry: signal.auto_detect() signal.blockSignals(False) self.ui.progressBar.setValue(50) QApplication.processEvents() self.ui.progressBar.setValue(70) QApplication.processEvents() self.signal_protocol_dict[sframe] = pa self.ui.progressBar.setValue(80) QApplication.processEvents() sframe.refresh( draw_full_signal=True) # Hier wird das Protokoll ausgelesen if self.project_manager.read_participants_for_signal( signal, pa.messages): sframe.ui.gvSignal.redraw_view() sframe.ui.gvSignal.auto_fit_view() self.set_frame_numbers() self.ui.progressBar.setValue(99) QApplication.processEvents() self.compare_frame_controller.filter_search_results() self.refresh_main_menu() self.ui.progressBar.hide() def close_signal_frame(self, signal_frame: SignalFrameController): try: self.project_manager.write_signal_information_to_project_file( signal_frame.signal, signal_frame.proto_analyzer.messages) try: proto = self.signal_protocol_dict[signal_frame] except KeyError: proto = None if proto is not None: self.compare_frame_controller.remove_protocol(proto) # Needs to be removed in generator also, otherwise program crashes, # if item from tree in generator is selected and corresponding signal is closed self.generator_tab_controller.tree_model.remove_protocol(proto) proto.destroy() del self.signal_protocol_dict[signal_frame] if self.signal_tab_controller.ui.scrlAreaSignals.minimumHeight( ) > signal_frame.height(): self.signal_tab_controller.ui.scrlAreaSignals.setMinimumHeight( self.signal_tab_controller.ui.scrlAreaSignals. minimumHeight() - signal_frame.height()) if signal_frame.signal is not None: # Non-Empty Frame (when a signal and not a protocol is opended) self.file_proxy_model.open_files.discard( signal_frame.signal.filename) signal_frame.scene_manager.deleteLater() signal_frame.signal.destroy() signal_frame.signal.deleteLater() signal_frame.proto_analyzer.destroy() signal_frame.proto_analyzer = None signal_frame.close() QApplication.processEvents() signal_frame.destroy() QApplication.processEvents() self.compare_frame_controller.ui.treeViewProtocols.expandAll() self.set_frame_numbers() self.refresh_main_menu() except Exception as e: Errors.generic_error(self.tr("Failed to close"), str(e), traceback.format_exc()) self.ui.progressBar.hide() self.unsetCursor() def update_recent_action_list(self): recent_file_paths = constants.SETTINGS.value("recentFiles") recent_file_paths = [ p for p in recent_file_paths if os.path.exists(p) ] if recent_file_paths else [] it_end = len(recent_file_paths) if len( recent_file_paths ) < constants.MAX_RECENT_FILE_NR else constants.MAX_RECENT_FILE_NR for i in range(it_end): suffix = " (Directory)" if os.path.isdir( recent_file_paths[i]) else "" stripped_name = QFileInfo(recent_file_paths[i]).fileName() + suffix self.recentFileActionList[i].setText(stripped_name) self.recentFileActionList[i].setData(recent_file_paths[i]) self.recentFileActionList[i].setVisible(True) for i in range(it_end, constants.MAX_RECENT_FILE_NR): self.recentFileActionList[i].setVisible(False) constants.SETTINGS.setValue("recentFiles", recent_file_paths) def add_files(self, filepaths, group_id=0): num_files = len(filepaths) if num_files == 0: return for i, file in enumerate(filepaths): if not os.path.exists(file): continue if os.path.isdir(file): for f in self.signal_tab_controller.signal_frames: self.close_signal_frame(f) FileOperator.RECENT_PATH = file self.project_manager.set_project_folder(file) return _, file_extension = os.path.splitext(file) FileOperator.RECENT_PATH = os.path.split(file)[0] self.signal_tab_controller.ui.lLoadingFile.setText( self.tr("Loading File {0:d}/{1:d}".format(i + 1, num_files))) QApplication.processEvents() QApplication.setOverrideCursor(Qt.WaitCursor) if file_extension == ".complex": self.add_signalfile(file, group_id) elif file_extension == ".coco": self.add_signalfile(file, group_id) elif file_extension == ".proto": self.add_protocol_file(file) elif file_extension == ".wav": self.add_signalfile(file, group_id) elif file_extension == ".fuzz": self.add_fuzz_profile(file) else: self.add_signalfile(file, group_id) QApplication.restoreOverrideCursor() self.signal_tab_controller.ui.lLoadingFile.setText("") def set_frame_numbers(self): self.signal_tab_controller.set_frame_numbers() def closeEvent(self, event: QCloseEvent): self.project_manager.saveProject() event.accept() def close_all(self): self.filemodel.setRootPath(QDir.homePath()) self.ui.fileTree.setRootIndex( self.file_proxy_model.mapFromSource( self.filemodel.index(QDir.homePath()))) self.project_manager.saveProject() self.signal_tab_controller.close_all() self.compare_frame_controller.reset() self.generator_tab_controller.close_all() self.project_manager.project_path = "" self.project_manager.project_file = None self.signal_tab_controller.signal_undo_stack.clear() self.compare_frame_controller.protocol_undo_stack.clear() self.generator_tab_controller.generator_undo_stack.clear() def show_options_dialog_specific_tab(self, tab_index: int): op = OptionsController(self.plugin_manager.installed_plugins, parent=self) op.values_changed.connect(self.on_options_changed) op.ui.tabWidget.setCurrentIndex(tab_index) op.show() def refresh_main_menu(self): enable = len(self.signal_protocol_dict) > 0 self.ui.actionSaveAllSignals.setEnabled(enable) self.ui.actionClose_all.setEnabled(enable) def apply_default_view(self): view_index = constants.SETTINGS.value('default_view', type=int) self.compare_frame_controller.ui.cbProtoView.setCurrentIndex( view_index) self.generator_tab_controller.ui.cbViewType.setCurrentIndex(view_index) for sig_frame in self.signal_tab_controller.signal_frames: sig_frame.ui.cbProtoView.setCurrentIndex(view_index) def show_project_settings(self): pdc = ProjectDialogController(new_project=False, project_manager=self.project_manager, parent=self) pdc.finished.connect(self.on_project_dialog_finished) pdc.show() def collapse_project_tab_bar(self): self.ui.tabParticipants.hide() self.ui.tabDescription.hide() self.ui.tabWidget_Project.setMaximumHeight( self.ui.tabWidget_Project.tabBar().height()) def expand_project_tab_bar(self): self.ui.tabDescription.show() self.ui.tabParticipants.show() self.ui.tabWidget_Project.setMaximumHeight(9000) @pyqtSlot() def on_project_tab_bar_double_clicked(self): if self.ui.tabParticipants.isVisible(): self.collapse_project_tab_bar() else: self.expand_project_tab_bar() @pyqtSlot() def on_project_updated(self): self.participant_legend_model.participants = self.project_manager.participants self.participant_legend_model.update() self.compare_frame_controller.refresh() self.ui.textEditProjectDescription.setText( self.project_manager.description) @pyqtSlot() def on_fullscreen_action_triggered(self): if self.ui.actionFullscreen_mode.isChecked(): self.showFullScreen() else: self.showMaximized() @pyqtSlot(str) def adjust_for_current_file(self, file_path): if file_path in FileOperator.archives.keys(): file_path = copy.copy(FileOperator.archives[file_path]) settings = constants.SETTINGS recent_file_paths = settings.value("recentFiles", []) recent_file_paths = [p for p in recent_file_paths if p != file_path] recent_file_paths.insert(0, file_path) while len(recent_file_paths) > constants.MAX_RECENT_FILE_NR: recent_file_paths.pop() settings.setValue("recentFiles", recent_file_paths) self.update_recent_action_list() @pyqtSlot() def on_show_field_types_config_action_triggered(self): self.show_options_dialog_specific_tab(tab_index=3) @pyqtSlot() def on_open_recent_action_triggered(self): action = self.sender() try: if os.path.isdir(action.data()): self.project_manager.set_project_folder(action.data()) elif os.path.isfile(action.data()): self.add_files( FileOperator.uncompress_archives([action.data()], QDir.tempPath())) except Exception as e: Errors.generic_error(self.tr("Failed to open"), str(e), traceback.format_exc()) self.ui.progressBar.hide() self.unsetCursor() @pyqtSlot() def on_show_about_clicked(self): descr = "<b><h2>Universal Radio Hacker</h2></b>Version: {0}<br />" \ "GitHub: <a href='https://github.com/jopohl/urh'>https://github.com/jopohl/urh</a><br /><br />" \ "Contributors:<i><ul><li>" \ "Johannes Pohl <<a href='mailto:[email protected]'>[email protected]</a>></li>" \ "<li>Andreas Noack <<a href='mailto:[email protected]'>[email protected]</a>></li>" \ "</ul></i>".format(version.VERSION) QMessageBox.about(self, self.tr("About"), self.tr(descr)) @pyqtSlot(int, int, int, int) def show_protocol_selection_in_interpretation(self, start_message, start, end_message, end): cfc = self.compare_frame_controller msg_total = 0 last_sig_frame = None for protocol in cfc.protocol_list: if not protocol.show: continue n = protocol.num_messages view_type = cfc.ui.cbProtoView.currentIndex() messages = [ i - msg_total for i in range(msg_total, msg_total + n) if start_message <= i <= end_message ] if len(messages) > 0: try: signal_frame = next( (sf for sf, pf in self.signal_protocol_dict.items() if pf == protocol)) except StopIteration: QMessageBox.critical( self, self.tr("Error"), self.tr("Could not find corresponding signal frame.")) return signal_frame.set_roi_from_protocol_analysis( min(messages), start, max(messages), end + 1, view_type) last_sig_frame = signal_frame msg_total += n focus_frame = last_sig_frame if last_sig_frame is not None: self.signal_tab_controller.ui.scrollArea.ensureWidgetVisible( last_sig_frame, 0, 0) QApplication.processEvents() self.ui.tabWidget.setCurrentIndex(0) if focus_frame is not None: focus_frame.ui.txtEdProto.setFocus() @pyqtSlot(str) def on_file_tree_filter_text_changed(self, text: str): if len(text) > 0: self.filemodel.setNameFilters(["*" + text + "*"]) else: self.filemodel.setNameFilters(["*"]) @pyqtSlot() def on_show_decoding_dialog_triggered(self): signals = [ sf.signal for sf in self.signal_tab_controller.signal_frames ] decoding_controller = DecoderWidgetController( self.compare_frame_controller.decodings, signals, self.project_manager, parent=self) decoding_controller.finished.connect(self.update_decodings) decoding_controller.show() decoding_controller.decoder_update() @pyqtSlot() def update_decodings(self): self.compare_frame_controller.load_decodings() self.compare_frame_controller.fill_decoding_combobox() self.compare_frame_controller.refresh_existing_encodings() self.generator_tab_controller.refresh_existing_encodings( self.compare_frame_controller.decodings) @pyqtSlot(int) def on_selected_tab_changed(self, index: int): if index == 0: self.undo_group.setActiveStack( self.signal_tab_controller.signal_undo_stack) elif index == 1: self.undo_group.setActiveStack( self.compare_frame_controller.protocol_undo_stack) self.compare_frame_controller.ui.tblViewProtocol.resize_columns() self.compare_frame_controller.ui.tblViewProtocol.resize_vertical_header( ) elif index == 2: self.undo_group.setActiveStack( self.generator_tab_controller.generator_undo_stack) @pyqtSlot() def on_show_record_dialog_action_triggered(self): pm = self.project_manager try: r = ReceiveDialogController(pm, parent=self) except OSError as e: logger.error(repr(e)) return if r.has_empty_device_list: Errors.no_device() r.close() return r.recording_parameters.connect(pm.set_recording_parameters) r.files_recorded.connect(self.on_signals_recorded) r.show() @pyqtSlot() def show_proto_sniff_dialog(self): pm = self.project_manager signal = None for proto in self.compare_frame_controller.protocol_list: signal = proto.signal if signal: break if signal: bit_len = signal.bit_len mod_type = signal.modulation_type tolerance = signal.tolerance noise = signal.noise_threshold center = signal.qad_center else: bit_len = 100 mod_type = 1 tolerance = 5 noise = 0.001 center = 0.02 psd = ProtocolSniffDialogController(pm, noise, center, bit_len, tolerance, mod_type, parent=self) if psd.has_empty_device_list: Errors.no_device() psd.close() else: psd.protocol_accepted.connect( self.compare_frame_controller.add_sniffed_protocol_messages) psd.show() @pyqtSlot() def on_show_spectrum_dialog_action_triggered(self): pm = self.project_manager r = SpectrumDialogController(pm, parent=self) if r.has_empty_device_list: Errors.no_device() r.close() return r.recording_parameters.connect(pm.set_recording_parameters) r.show() @pyqtSlot(list) def on_signals_recorded(self, file_names: list): QApplication.setOverrideCursor(Qt.WaitCursor) for filename in file_names: self.add_signalfile(filename) QApplication.restoreOverrideCursor() @pyqtSlot() def show_options_dialog_action_triggered(self): self.show_options_dialog_specific_tab(tab_index=0) @pyqtSlot() def on_new_project_action_triggered(self): pdc = ProjectDialogController(parent=self) pdc.finished.connect(self.on_project_dialog_finished) pdc.show() @pyqtSlot() def on_project_settings_action_triggered(self): self.show_project_settings() @pyqtSlot() def on_project_dialog_finished(self): if self.sender().committed: if self.sender().new_project: for f in self.signal_tab_controller.signal_frames: self.close_signal_frame(f) self.project_manager.from_dialog(self.sender()) @pyqtSlot() def on_open_file_action_triggered(self): self.show_open_dialog(directory=False) @pyqtSlot() def on_open_directory_action_triggered(self): self.show_open_dialog(directory=True) def show_open_dialog(self, directory=False): fip = FileIconProvider() self.dialog = QFileDialog(self) self.dialog.setIconProvider(fip) self.dialog.setDirectory(FileOperator.RECENT_PATH) self.dialog.setWindowTitle("Open Folder") if directory: self.dialog.setFileMode(QFileDialog.Directory) else: self.dialog.setFileMode(QFileDialog.ExistingFiles) self.dialog.setNameFilter( "All files (*);;Complex (*.complex);;Complex16 unsigned (*.complex16u);;Complex16 signed (*.complex16s);;Wave (*.wav);;Protocols (*.proto);;" "Fuzzprofiles (*.fuzz);;Tar Archives (*.tar *.tar.gz *.tar.bz2);;Zip Archives (*.zip)" ) self.dialog.setOptions(QFileDialog.DontResolveSymlinks) self.dialog.setViewMode(QFileDialog.Detail) if self.dialog.exec_(): try: file_names = self.dialog.selectedFiles() folders = [ folder for folder in file_names if os.path.isdir(folder) ] if len(folders) > 0: folder = folders[0] for f in self.signal_tab_controller.signal_frames: self.close_signal_frame(f) self.project_manager.set_project_folder(folder) else: file_names = FileOperator.uncompress_archives( file_names, QDir.tempPath()) self.add_files(file_names) except Exception as e: Errors.generic_error(self.tr("Failed to open"), str(e), traceback.format_exc()) self.ui.progressBar.hide() QApplication.restoreOverrideCursor() @pyqtSlot() def on_close_all_action_triggered(self): self.close_all() @pyqtSlot(list) def on_files_dropped(self, files): """ :type files: list of QtCore.QUrl """ self.__add_urls_to_group(files, group_id=0) @pyqtSlot(list, int) def on_files_dropped_on_group(self, files, group_id: int): """ :param group_id: :type files: list of QtCore.QUrl """ self.__add_urls_to_group(files, group_id=group_id) def __add_urls_to_group(self, file_urls, group_id=0): local_files = [ file_url.toLocalFile() for file_url in file_urls if file_url.isLocalFile() ] if len(local_files) > 0: self.add_files(FileOperator.uncompress_archives( local_files, QDir.tempPath()), group_id=group_id) @pyqtSlot(list) def on_cfc_close_wanted(self, protocols: list): frames = [ sframe for sframe, protocol in self.signal_protocol_dict.items() if protocol in protocols ] if len(frames) != len(protocols): logger.error("failed to close {} protocols".format( len(protocols) - len(frames))) for frame in frames: self.close_signal_frame(frame) @pyqtSlot(dict) def on_options_changed(self, changed_options: dict): refresh_protocol_needed = False for key in changed_options.keys(): if key == "rel_symbol_length": st = changed_options[key] constants.SETTINGS.setValue('rel_symbol_length', st) refresh_protocol_needed = True elif key == "show_pause_as_time": refresh_protocol_needed = True if refresh_protocol_needed: for sf in self.signal_tab_controller.signal_frames: sf.refresh_protocol() self.compare_frame_controller.refresh_field_types_for_labels() self.compare_frame_controller.set_shown_protocols() self.generator_tab_controller.set_network_sdr_send_button_visibility() if "default_view" in changed_options.keys(): self.apply_default_view() @pyqtSlot() def on_text_edit_project_description_text_changed(self): self.project_manager.description = self.ui.textEditProjectDescription.toPlainText( )
class MainController(QMainWindow): def __init__(self, *args): super().__init__(*args) self.ui = Ui_MainWindow() self.ui.setupUi(self) OptionsController.write_default_options() self.project_save_timer = QTimer() self.project_manager = ProjectManager(self) self.plugin_manager = PluginManager() self.signal_tab_controller = SignalTabController( self.project_manager, parent=self.ui.tab_interpretation) self.ui.tab_interpretation.layout().addWidget( self.signal_tab_controller) self.compare_frame_controller = CompareFrameController( parent=self.ui.tab_protocol, plugin_manager=self.plugin_manager, project_manager=self.project_manager) self.compare_frame_controller.ui.splitter.setSizes([1, 1000000]) self.ui.tab_protocol.layout().addWidget(self.compare_frame_controller) self.generator_tab_controller = GeneratorTabController( self.compare_frame_controller, self.project_manager, parent=self.ui.tab_generator) self.undo_group = QUndoGroup() self.undo_group.addStack(self.signal_tab_controller.signal_undo_stack) self.undo_group.addStack( self.compare_frame_controller.protocol_undo_stack) self.undo_group.addStack( self.generator_tab_controller.generator_undo_stack) self.undo_group.setActiveStack( self.signal_tab_controller.signal_undo_stack) self.cancel_action = QAction(self.tr("Cancel"), self) self.cancel_action.setShortcut( QKeySequence.Cancel if hasattr(QKeySequence, "Cancel") else "Esc") self.cancel_action.triggered.connect(self.on_cancel_triggered) self.cancel_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.cancel_action.setIcon(QIcon.fromTheme("dialog-cancel")) self.addAction(self.cancel_action) self.participant_legend_model = ParticipantLegendListModel( self.project_manager.participants) self.ui.listViewParticipants.setModel(self.participant_legend_model) gtc = self.generator_tab_controller gtc.ui.splitter.setSizes([gtc.width() / 0.7, gtc.width() / 0.3]) self.ui.tab_generator.layout().addWidget(self.generator_tab_controller) self.signal_protocol_dict = { } # type: dict[SignalFrameController, ProtocolAnalyzer] self.ui.lnEdtTreeFilter.setClearButtonEnabled(True) group = QActionGroup(self) self.ui.actionFSK.setActionGroup(group) self.ui.actionOOK.setActionGroup(group) self.ui.actionNone.setActionGroup(group) self.ui.actionPSK.setActionGroup(group) self.recentFileActionList = [] self.create_connects() self.init_recent_file_action_list( constants.SETTINGS.value("recentFiles", [])) self.filemodel = FileSystemModel(self) path = QDir.homePath() self.filemodel.setIconProvider(FileIconProvider()) self.filemodel.setRootPath(path) self.file_proxy_model = FileFilterProxyModel(self) self.file_proxy_model.setSourceModel(self.filemodel) self.ui.fileTree.setModel(self.file_proxy_model) self.ui.fileTree.setRootIndex( self.file_proxy_model.mapFromSource(self.filemodel.index(path))) self.ui.fileTree.setToolTip(path) self.ui.fileTree.header().setSectionResizeMode( 0, QHeaderView.ResizeToContents) self.ui.fileTree.header().setSectionResizeMode(1, QHeaderView.Stretch) self.ui.fileTree.setFocus() self.generator_tab_controller.table_model.cfc = self.compare_frame_controller self.ui.actionConvert_Folder_to_Project.setEnabled(False) undo_action = self.undo_group.createUndoAction(self) undo_action.setIcon(QIcon.fromTheme("edit-undo")) undo_action.setShortcut(QKeySequence.Undo) self.ui.menuEdit.insertAction(self.ui.actionDecoding, undo_action) redo_action = self.undo_group.createRedoAction(self) redo_action.setIcon(QIcon.fromTheme("edit-redo")) redo_action.setShortcut(QKeySequence.Redo) self.ui.menuEdit.insertAction(self.ui.actionDecoding, redo_action) self.ui.menuEdit.insertSeparator(self.ui.actionDecoding) self.ui.actionAbout_Qt.setIcon( QIcon(":/qt-project.org/qmessagebox/images/qtlogo-64.png")) self.ui.splitter.setSizes([0, 1]) self.refresh_main_menu() self.apply_default_view( constants.SETTINGS.value('default_view', type=int)) self.project_save_timer.start( ProjectManager.AUTOSAVE_INTERVAL_MINUTES * 60 * 1000) self.ui.actionProject_settings.setVisible(False) self.ui.actionSave_project.setVisible(False) def create_connects(self): self.ui.actionFullscreen_mode.setShortcut(QKeySequence.FullScreen) self.ui.actionOpen.setShortcut(QKeySequence(QKeySequence.Open)) self.ui.actionOpen_directory.setShortcut(QKeySequence("Ctrl+Shift+O")) self.ui.menuEdit.aboutToShow.connect(self.on_edit_menu_about_to_show) self.ui.actionNew_Project.triggered.connect( self.on_new_project_action_triggered) self.ui.actionNew_Project.setShortcut(QKeySequence.New) self.ui.actionProject_settings.triggered.connect( self.on_project_settings_action_triggered) self.ui.actionSave_project.triggered.connect( self.project_manager.save_project) self.ui.actionAbout_AutomaticHacker.triggered.connect( self.on_show_about_clicked) self.ui.actionRecord.triggered.connect( self.on_show_record_dialog_action_triggered) self.ui.actionFullscreen_mode.triggered.connect( self.on_fullscreen_action_triggered) self.ui.actionSaveAllSignals.triggered.connect( self.signal_tab_controller.save_all) self.ui.actionClose_all.triggered.connect( self.on_close_all_action_triggered) self.ui.actionOpen.triggered.connect( self.on_open_file_action_triggered) self.ui.actionOpen_directory.triggered.connect( self.on_open_directory_action_triggered) self.ui.actionDecoding.triggered.connect( self.on_show_decoding_dialog_triggered) self.ui.actionSpectrum_Analyzer.triggered.connect( self.on_show_spectrum_dialog_action_triggered) self.ui.actionOptions.triggered.connect( self.show_options_dialog_action_triggered) self.ui.actionSniff_protocol.triggered.connect( self.show_proto_sniff_dialog) self.ui.actionAbout_Qt.triggered.connect( QApplication.instance().aboutQt) self.ui.actionSamples_from_csv.triggered.connect( self.on_import_samples_from_csv_action_triggered) self.ui.btnFileTreeGoUp.clicked.connect( self.on_btn_file_tree_go_up_clicked) self.ui.fileTree.directory_open_wanted.connect( self.project_manager.set_project_folder) self.signal_tab_controller.frame_closed.connect( self.close_signal_frame) self.signal_tab_controller.signal_created.connect( self.on_signal_created) self.signal_tab_controller.ui.scrollArea.files_dropped.connect( self.on_files_dropped) self.signal_tab_controller.files_dropped.connect(self.on_files_dropped) self.signal_tab_controller.frame_was_dropped.connect( self.set_frame_numbers) self.compare_frame_controller.show_interpretation_clicked.connect( self.show_protocol_selection_in_interpretation) self.compare_frame_controller.files_dropped.connect( self.on_files_dropped) self.compare_frame_controller.show_decoding_clicked.connect( self.on_show_decoding_dialog_triggered) self.compare_frame_controller.ui.treeViewProtocols.files_dropped_on_group.connect( self.on_files_dropped_on_group) self.compare_frame_controller.participant_changed.connect( self.signal_tab_controller.on_participant_changed) self.compare_frame_controller.ui.treeViewProtocols.close_wanted.connect( self.on_cfc_close_wanted) self.compare_frame_controller.show_config_field_types_triggered.connect( self.on_show_field_types_config_action_triggered) self.ui.lnEdtTreeFilter.textChanged.connect( self.on_file_tree_filter_text_changed) self.ui.tabWidget.currentChanged.connect(self.on_selected_tab_changed) self.project_save_timer.timeout.connect( self.project_manager.save_project) self.ui.actionConvert_Folder_to_Project.triggered.connect( self.project_manager.convert_folder_to_project) self.project_manager.project_loaded_status_changed.connect( self.ui.actionProject_settings.setVisible) self.project_manager.project_loaded_status_changed.connect( self.ui.actionSave_project.setVisible) self.project_manager.project_loaded_status_changed.connect( self.ui.actionConvert_Folder_to_Project.setDisabled) self.project_manager.project_updated.connect(self.on_project_updated) self.ui.textEditProjectDescription.textChanged.connect( self.on_text_edit_project_description_text_changed) self.ui.tabWidget_Project.tabBarDoubleClicked.connect( self.on_project_tab_bar_double_clicked) self.ui.listViewParticipants.doubleClicked.connect( self.on_project_settings_action_triggered) self.ui.actionShowFileTree.triggered.connect( self.on_action_show_filetree_triggered) self.ui.actionShowFileTree.setShortcut(QKeySequence("F10")) self.ui.menuFile.addSeparator() for i in range(constants.MAX_RECENT_FILE_NR): recent_file_action = QAction(self) recent_file_action.setVisible(False) recent_file_action.triggered.connect( self.on_open_recent_action_triggered) self.recentFileActionList.append(recent_file_action) self.ui.menuFile.addAction(self.recentFileActionList[i]) def add_plain_bits_from_txt(self, filename: str): protocol = ProtocolAnalyzer(None) protocol.filename = filename with open(filename) as f: for line in f: protocol.messages.append( Message.from_plain_bits_str(line.strip())) self.compare_frame_controller.add_protocol(protocol) self.compare_frame_controller.refresh() self.__add_empty_frame_for_filename(protocol, filename) def __add_empty_frame_for_filename(self, protocol: ProtocolAnalyzer, filename: str): sf = self.signal_tab_controller.add_empty_frame(filename, protocol) self.signal_protocol_dict[sf] = protocol self.set_frame_numbers() self.file_proxy_model.open_files.add(filename) def add_protocol_file(self, filename): proto = self.compare_frame_controller.add_protocol_from_file(filename) if proto: self.__add_empty_frame_for_filename(proto, filename) def add_fuzz_profile(self, filename): self.ui.tabWidget.setCurrentIndex(2) self.generator_tab_controller.load_from_file(filename) def add_signalfile(self, filename: str, group_id=0, enforce_sample_rate=None): if not os.path.exists(filename): QMessageBox.critical( self, self.tr("File not Found"), self.tr( "The file {0} could not be found. Was it moved or renamed?" ).format(filename)) return sig_name = os.path.splitext(os.path.basename(filename))[0] # Use default sample rate for signal # Sample rate will be overriden in case of a project later if enforce_sample_rate is not None: sample_rate = enforce_sample_rate else: sample_rate = self.project_manager.device_conf["sample_rate"] signal = Signal(filename, sig_name, sample_rate=sample_rate) self.file_proxy_model.open_files.add(filename) self.add_signal(signal, group_id) def add_signal(self, signal, group_id=0, index=-1): self.setCursor(Qt.WaitCursor) pa = ProtocolAnalyzer(signal) sig_frame = self.signal_tab_controller.add_signal_frame(pa, index=index) pa = self.compare_frame_controller.add_protocol(pa, group_id) signal.blockSignals(True) has_entry = self.project_manager.read_project_file_for_signal(signal) if not has_entry and not signal.changed: signal.auto_detect() signal.blockSignals(False) self.signal_protocol_dict[sig_frame] = pa sig_frame.refresh(draw_full_signal=True) # protocol is derived here if self.project_manager.read_participants_for_signal( signal, pa.messages): sig_frame.ui.gvSignal.redraw_view() sig_frame.ui.gvSignal.auto_fit_view() self.set_frame_numbers() self.compare_frame_controller.filter_search_results() self.refresh_main_menu() self.unsetCursor() def close_protocol(self, protocol): self.compare_frame_controller.remove_protocol(protocol) # Needs to be removed in generator also, otherwise program crashes, # if item from tree in generator is selected and corresponding signal is closed self.generator_tab_controller.tree_model.remove_protocol(protocol) protocol.eliminate() def close_signal_frame(self, signal_frame: SignalFrameController): try: self.project_manager.write_signal_information_to_project_file( signal_frame.signal) try: proto = self.signal_protocol_dict[signal_frame] except KeyError: proto = None if proto is not None: self.close_protocol(proto) del self.signal_protocol_dict[signal_frame] if self.signal_tab_controller.ui.scrlAreaSignals.minimumHeight( ) > signal_frame.height(): self.signal_tab_controller.ui.scrlAreaSignals.setMinimumHeight( self.signal_tab_controller.ui.scrlAreaSignals. minimumHeight() - signal_frame.height()) if signal_frame.signal is not None: # Non-Empty Frame (when a signal and not a protocol is opened) self.file_proxy_model.open_files.discard( signal_frame.signal.filename) signal_frame.eliminate() self.compare_frame_controller.ui.treeViewProtocols.expandAll() self.set_frame_numbers() self.refresh_main_menu() except Exception as e: Errors.generic_error(self.tr("Failed to close"), str(e), traceback.format_exc()) self.unsetCursor() def add_files(self, filepaths, group_id=0, enforce_sample_rate=None): num_files = len(filepaths) if num_files == 0: return for i, filename in enumerate(filepaths): if not os.path.exists(filename): continue if os.path.isdir(filename): for f in self.signal_tab_controller.signal_frames: self.close_signal_frame(f) FileOperator.RECENT_PATH = filename self.project_manager.set_project_folder(filename) return FileOperator.RECENT_PATH = os.path.split(filename)[0] if filename.endswith(".complex"): self.add_signalfile(filename, group_id, enforce_sample_rate=enforce_sample_rate) elif filename.endswith(".coco"): self.add_signalfile(filename, group_id, enforce_sample_rate=enforce_sample_rate) elif filename.endswith(".proto") or filename.endswith( ".proto.xml"): self.add_protocol_file(filename) elif filename.endswith(".wav"): try: import wave w = wave.open(filename) w.close() except wave.Error as e: Errors.generic_error( "Unsupported WAV type", "Only uncompressed WAVs (PCM) are supported.", str(e)) continue self.add_signalfile(filename, group_id, enforce_sample_rate=enforce_sample_rate) elif filename.endswith(".fuzz") or filename.endswith(".fuzz.xml"): self.add_fuzz_profile(filename) elif filename.endswith(".txt"): self.add_plain_bits_from_txt(filename) elif filename.endswith(".csv"): self.__import_csv(filename, group_id) continue elif os.path.basename(filename) == constants.PROJECT_FILE: self.project_manager.set_project_folder( os.path.split(filename)[0]) else: self.add_signalfile(filename, group_id, enforce_sample_rate=enforce_sample_rate) if self.project_manager.project_file is None: self.adjust_for_current_file(filename) def set_frame_numbers(self): self.signal_tab_controller.set_frame_numbers() def closeEvent(self, event: QCloseEvent): self.project_manager.save_project() super().closeEvent(event) def close_all(self): self.filemodel.setRootPath(QDir.homePath()) self.ui.fileTree.setRootIndex( self.file_proxy_model.mapFromSource( self.filemodel.index(QDir.homePath()))) self.project_manager.save_project() self.signal_tab_controller.close_all() self.compare_frame_controller.reset() self.generator_tab_controller.table_model.protocol.clear() self.generator_tab_controller.refresh_tree() self.generator_tab_controller.refresh_table() self.generator_tab_controller.refresh_label_list() self.project_manager.project_path = "" self.project_manager.project_file = None self.signal_tab_controller.signal_undo_stack.clear() self.compare_frame_controller.protocol_undo_stack.clear() self.generator_tab_controller.generator_undo_stack.clear() def show_options_dialog_specific_tab(self, tab_index: int): op = OptionsController(self.plugin_manager.installed_plugins, parent=self) op.values_changed.connect(self.on_options_changed) op.ui.tabWidget.setCurrentIndex(tab_index) op.show() def refresh_main_menu(self): enable = len(self.signal_protocol_dict) > 0 self.ui.actionSaveAllSignals.setEnabled(enable) self.ui.actionClose_all.setEnabled(enable) def apply_default_view(self, view_index: int): self.compare_frame_controller.ui.cbProtoView.setCurrentIndex( view_index) self.generator_tab_controller.ui.cbViewType.setCurrentIndex(view_index) for sig_frame in self.signal_tab_controller.signal_frames: sig_frame.ui.cbProtoView.setCurrentIndex(view_index) def show_project_settings(self): pdc = ProjectDialogController(new_project=False, project_manager=self.project_manager, parent=self) pdc.finished.connect(self.on_project_dialog_finished) pdc.show() def collapse_project_tab_bar(self): self.ui.tabParticipants.hide() self.ui.tabDescription.hide() self.ui.tabWidget_Project.setMaximumHeight( self.ui.tabWidget_Project.tabBar().height()) def expand_project_tab_bar(self): self.ui.tabDescription.show() self.ui.tabParticipants.show() self.ui.tabWidget_Project.setMaximumHeight(9000) @pyqtSlot() def on_project_tab_bar_double_clicked(self): if self.ui.tabParticipants.isVisible(): self.collapse_project_tab_bar() else: self.expand_project_tab_bar() @pyqtSlot() def on_project_updated(self): self.participant_legend_model.participants = self.project_manager.participants self.participant_legend_model.update() self.compare_frame_controller.refresh() self.ui.textEditProjectDescription.setText( self.project_manager.description) @pyqtSlot() def on_fullscreen_action_triggered(self): if self.ui.actionFullscreen_mode.isChecked(): self.showFullScreen() else: self.showMaximized() def adjust_for_current_file(self, file_path): if file_path is None: return if file_path in FileOperator.archives.keys(): file_path = copy.copy(FileOperator.archives[file_path]) settings = constants.SETTINGS recent_file_paths = settings.value("recentFiles", []) recent_file_paths = [] if recent_file_paths is None else recent_file_paths # check None for OSX recent_file_paths = [ p for p in recent_file_paths if p != file_path and p is not None and os.path.exists(p) ] recent_file_paths.insert(0, file_path) recent_file_paths = recent_file_paths[:constants.MAX_RECENT_FILE_NR] self.init_recent_file_action_list(recent_file_paths) settings.setValue("recentFiles", recent_file_paths) def init_recent_file_action_list(self, recent_file_paths: list): for i in range(len(self.recentFileActionList)): self.recentFileActionList[i].setVisible(False) if recent_file_paths is None: return for i, file_path in enumerate(recent_file_paths): if os.path.isfile(file_path): display_text = os.path.basename(file_path) self.recentFileActionList[i].setIcon(QIcon()) elif os.path.isdir(file_path): head, tail = os.path.split(file_path) display_text = tail head, tail = os.path.split(head) if tail: display_text = tail + "/" + display_text self.recentFileActionList[i].setIcon(QIcon.fromTheme("folder")) else: continue self.recentFileActionList[i].setText(display_text) self.recentFileActionList[i].setData(file_path) self.recentFileActionList[i].setVisible(True) @pyqtSlot() def on_show_field_types_config_action_triggered(self): self.show_options_dialog_specific_tab(tab_index=2) @pyqtSlot() def on_open_recent_action_triggered(self): action = self.sender() try: if os.path.isdir(action.data()): self.project_manager.set_project_folder(action.data()) elif os.path.isfile(action.data()): self.setCursor(Qt.WaitCursor) self.add_files( FileOperator.uncompress_archives([action.data()], QDir.tempPath())) self.unsetCursor() except Exception as e: Errors.generic_error(self.tr("Failed to open"), str(e), traceback.format_exc()) self.unsetCursor() @pyqtSlot() def on_show_about_clicked(self): descr = "<b><h2>Universal Radio Hacker</h2></b>Version: {0}<br />" \ "GitHub: <a href='https://github.com/jopohl/urh'>https://github.com/jopohl/urh</a><br /><br />" \ "Creators:<i><ul><li>" \ "Johannes Pohl <<a href='mailto:[email protected]'>[email protected]</a>></li>" \ "<li>Andreas Noack <<a href='mailto:[email protected]'>[email protected]</a>></li>" \ "</ul></i>".format(version.VERSION) QMessageBox.about(self, self.tr("About"), self.tr(descr)) @pyqtSlot(int, int, int, int) def show_protocol_selection_in_interpretation(self, start_message, start, end_message, end): cfc = self.compare_frame_controller msg_total = 0 last_sig_frame = None for protocol in cfc.protocol_list: if not protocol.show: continue n = protocol.num_messages view_type = cfc.ui.cbProtoView.currentIndex() messages = [ i - msg_total for i in range(msg_total, msg_total + n) if start_message <= i <= end_message ] if len(messages) > 0: try: signal_frame = next( (sf for sf, pf in self.signal_protocol_dict.items() if pf == protocol)) except StopIteration: QMessageBox.critical( self, self.tr("Error"), self.tr("Could not find corresponding signal frame.")) return signal_frame.set_roi_from_protocol_analysis( min(messages), start, max(messages), end + 1, view_type) last_sig_frame = signal_frame msg_total += n focus_frame = last_sig_frame if last_sig_frame is not None: self.signal_tab_controller.ui.scrollArea.ensureWidgetVisible( last_sig_frame, 0, 0) QApplication.instance().processEvents() self.ui.tabWidget.setCurrentIndex(0) if focus_frame is not None: focus_frame.ui.txtEdProto.setFocus() @pyqtSlot(str) def on_file_tree_filter_text_changed(self, text: str): if len(text) > 0: self.filemodel.setNameFilters(["*" + text + "*"]) else: self.filemodel.setNameFilters(["*"]) @pyqtSlot() def on_show_decoding_dialog_triggered(self): signals = [ sf.signal for sf in self.signal_tab_controller.signal_frames ] decoding_controller = DecoderWidgetController( self.compare_frame_controller.decodings, signals, self.project_manager, parent=self) decoding_controller.finished.connect(self.update_decodings) decoding_controller.show() decoding_controller.decoder_update() @pyqtSlot() def update_decodings(self): self.compare_frame_controller.load_decodings() self.compare_frame_controller.fill_decoding_combobox() self.compare_frame_controller.refresh_existing_encodings() self.generator_tab_controller.refresh_existing_encodings( self.compare_frame_controller.decodings) @pyqtSlot(int) def on_selected_tab_changed(self, index: int): if index == 0: self.undo_group.setActiveStack( self.signal_tab_controller.signal_undo_stack) elif index == 1: self.undo_group.setActiveStack( self.compare_frame_controller.protocol_undo_stack) self.compare_frame_controller.ui.tblViewProtocol.resize_columns() self.compare_frame_controller.ui.tblViewProtocol.resize_vertical_header( ) h = max(self.compare_frame_controller.ui.btnSaveProto.height(), self.generator_tab_controller.ui.btnSave.height()) self.compare_frame_controller.ui.btnSaveProto.setMinimumHeight(h) th = self.compare_frame_controller.ui.tabWidget.tabBar().height() for i in range(self.compare_frame_controller.ui.tabWidget.count()): self.compare_frame_controller.ui.tabWidget.widget( i).layout().setContentsMargins(0, 7 + h - th, 0, 0) elif index == 2: self.undo_group.setActiveStack( self.generator_tab_controller.generator_undo_stack) h = max(self.compare_frame_controller.ui.btnSaveProto.height(), self.generator_tab_controller.ui.btnSave.height()) self.generator_tab_controller.ui.btnSave.setMinimumHeight(h) th = self.generator_tab_controller.ui.tabWidget.tabBar().height() for i in range(self.generator_tab_controller.ui.tabWidget.count()): self.generator_tab_controller.ui.tabWidget.widget( i).layout().setContentsMargins(0, 7 + h - th, 0, 0) @pyqtSlot() def on_show_record_dialog_action_triggered(self): pm = self.project_manager try: r = ReceiveDialogController(pm, parent=self) except OSError as e: logger.error(repr(e)) return if r.has_empty_device_list: Errors.no_device() r.close() return r.recording_parameters.connect(pm.set_recording_parameters) r.files_recorded.connect(self.on_signals_recorded) r.show() @pyqtSlot() def show_proto_sniff_dialog(self): pm = self.project_manager signal = next( (proto.signal for proto in self.compare_frame_controller.protocol_list), None) bit_len = signal.bit_len if signal else 100 mod_type = signal.modulation_type if signal else 1 tolerance = signal.tolerance if signal else 5 noise = signal.noise_threshold if signal else 0.001 center = signal.qad_center if signal else 0.02 psd = ProtocolSniffDialogController( pm, noise, center, bit_len, tolerance, mod_type, self.compare_frame_controller.decodings, encoding_index=self.compare_frame_controller.ui.cbDecoding. currentIndex(), parent=self) if psd.has_empty_device_list: Errors.no_device() psd.close() else: psd.recording_parameters.connect(pm.set_recording_parameters) psd.protocol_accepted.connect( self.compare_frame_controller.add_sniffed_protocol_messages) psd.show() @pyqtSlot() def on_show_spectrum_dialog_action_triggered(self): pm = self.project_manager r = SpectrumDialogController(pm, parent=self) if r.has_empty_device_list: Errors.no_device() r.close() return r.recording_parameters.connect(pm.set_recording_parameters) r.show() @pyqtSlot(list, float) def on_signals_recorded(self, file_names: list, sample_rate: float): QApplication.instance().setOverrideCursor(Qt.WaitCursor) for filename in file_names: self.add_signalfile(filename, enforce_sample_rate=sample_rate) QApplication.instance().restoreOverrideCursor() @pyqtSlot() def show_options_dialog_action_triggered(self): self.show_options_dialog_specific_tab(tab_index=0) @pyqtSlot() def on_new_project_action_triggered(self): pdc = ProjectDialogController(parent=self) pdc.finished.connect(self.on_project_dialog_finished) pdc.show() @pyqtSlot() def on_project_settings_action_triggered(self): self.show_project_settings() @pyqtSlot() def on_edit_menu_about_to_show(self): self.ui.actionShowFileTree.setChecked(self.ui.splitter.sizes()[0] > 0) @pyqtSlot() def on_action_show_filetree_triggered(self): if self.ui.splitter.sizes()[0] > 0: self.ui.splitter.setSizes([0, 1]) else: self.ui.splitter.setSizes([1, 1]) @pyqtSlot() def on_project_dialog_finished(self): if self.sender().committed: if self.sender().new_project: for f in self.signal_tab_controller.signal_frames: self.close_signal_frame(f) self.project_manager.from_dialog(self.sender()) @pyqtSlot() def on_open_file_action_triggered(self): self.show_open_dialog(directory=False) @pyqtSlot() def on_open_directory_action_triggered(self): self.show_open_dialog(directory=True) def show_open_dialog(self, directory=False): fip = FileIconProvider() self.dialog = QFileDialog(self) self.dialog.setIconProvider(fip) self.dialog.setDirectory(FileOperator.RECENT_PATH) self.dialog.setWindowTitle("Open Folder") if directory: self.dialog.setFileMode(QFileDialog.Directory) else: self.dialog.setFileMode(QFileDialog.ExistingFiles) self.dialog.setNameFilter( "All files (*);;Complex (*.complex);;Complex16 unsigned (*.complex16u);;Complex16 signed (*.complex16s);;Wave (*.wav);;Protocols (*.proto.xml *.proto);;" "Fuzzprofiles (*.fuzz.xml *.fuzz);;Plain bits (*.txt);;Tar Archives (*.tar *.tar.gz *.tar.bz2);;Zip Archives (*.zip)" ) self.dialog.setOptions(QFileDialog.DontResolveSymlinks) self.dialog.setViewMode(QFileDialog.Detail) if self.dialog.exec_(): try: file_names = self.dialog.selectedFiles() folders = [ folder for folder in file_names if os.path.isdir(folder) ] if len(folders) > 0: folder = folders[0] for f in self.signal_tab_controller.signal_frames: self.close_signal_frame(f) self.project_manager.set_project_folder(folder) else: self.setCursor(Qt.WaitCursor) file_names = FileOperator.uncompress_archives( file_names, QDir.tempPath()) self.add_files(file_names) self.unsetCursor() except Exception as e: Errors.generic_error(self.tr("Failed to open"), str(e), traceback.format_exc()) self.unsetCursor() @pyqtSlot() def on_close_all_action_triggered(self): self.close_all() @pyqtSlot(list) def on_files_dropped(self, files): """ :type files: list of QtCore.QUrl """ self.__add_urls_to_group(files, group_id=0) @pyqtSlot(list, int) def on_files_dropped_on_group(self, files, group_id: int): """ :param group_id: :type files: list of QtCore.QUrl """ self.__add_urls_to_group(files, group_id=group_id) def __add_urls_to_group(self, file_urls, group_id=0): local_files = [ file_url.toLocalFile() for file_url in file_urls if file_url.isLocalFile() ] if len(local_files) > 0: self.setCursor(Qt.WaitCursor) self.add_files(FileOperator.uncompress_archives( local_files, QDir.tempPath()), group_id=group_id) self.unsetCursor() @pyqtSlot(list) def on_cfc_close_wanted(self, protocols: list): frame_protos = { sframe: protocol for sframe, protocol in self.signal_protocol_dict.items() if protocol in protocols } for frame in frame_protos: self.close_signal_frame(frame) for proto in (proto for proto in protocols if proto not in frame_protos.values()): # close protocols without associated signal frame self.close_protocol(proto) @pyqtSlot(dict) def on_options_changed(self, changed_options: dict): refresh_protocol_needed = "show_pause_as_time" in changed_options if refresh_protocol_needed: for sf in self.signal_tab_controller.signal_frames: sf.refresh_protocol() self.compare_frame_controller.refresh_field_types_for_labels() self.compare_frame_controller.set_shown_protocols() self.generator_tab_controller.set_network_sdr_send_button_visibility() self.generator_tab_controller.init_rfcat_plugin() if "default_view" in changed_options: self.apply_default_view(int(changed_options["default_view"])) if "spectrogram_colormap" in changed_options: self.signal_tab_controller.redraw_spectrograms() @pyqtSlot() def on_text_edit_project_description_text_changed(self): self.project_manager.description = self.ui.textEditProjectDescription.toPlainText( ) @pyqtSlot() def on_btn_file_tree_go_up_clicked(self): cur_dir = self.filemodel.rootDirectory() if cur_dir.cdUp(): path = cur_dir.path() self.filemodel.setRootPath(path) self.ui.fileTree.setRootIndex( self.file_proxy_model.mapFromSource( self.filemodel.index(path))) @pyqtSlot(int, Signal) def on_signal_created(self, index: int, signal: Signal): self.add_signal(signal, index=index) @pyqtSlot() def on_cancel_triggered(self): for signal_frame in self.signal_tab_controller.signal_frames: signal_frame.cancel_filtering() @pyqtSlot() def on_import_samples_from_csv_action_triggered(self): self.__import_csv(file_name="") def __import_csv(self, file_name, group_id=0): def on_data_imported(complex_file, sample_rate): sample_rate = None if sample_rate == 0 else sample_rate self.add_files([complex_file], group_id=group_id, enforce_sample_rate=sample_rate) dialog = CSVImportDialogController(file_name, parent=self) dialog.data_imported.connect(on_data_imported) dialog.exec_()
class MainController(QMainWindow): def __init__(self, *args): super().__init__(*args) self.ui = Ui_MainWindow() self.ui.setupUi(self) util.set_splitter_stylesheet(self.ui.splitter) OptionsDialog.write_default_options() self.project_save_timer = QTimer() self.project_manager = ProjectManager(self) self.plugin_manager = PluginManager() self.signal_tab_controller = SignalTabController(self.project_manager, parent=self.ui.tab_interpretation) self.ui.tab_interpretation.layout().addWidget(self.signal_tab_controller) self.compare_frame_controller = CompareFrameController(parent=self.ui.tab_protocol, plugin_manager=self.plugin_manager, project_manager=self.project_manager) self.compare_frame_controller.ui.splitter.setSizes([1, 1000000]) self.ui.tab_protocol.layout().addWidget(self.compare_frame_controller) self.generator_tab_controller = GeneratorTabController(self.compare_frame_controller, self.project_manager, parent=self.ui.tab_generator) self.simulator_tab_controller = SimulatorTabController(parent=self.ui.tab_simulator, compare_frame_controller=self.compare_frame_controller, generator_tab_controller=self.generator_tab_controller, project_manager=self.project_manager) self.ui.tab_simulator.layout().addWidget(self.simulator_tab_controller) self.undo_group = QUndoGroup() self.undo_group.addStack(self.signal_tab_controller.signal_undo_stack) self.undo_group.addStack(self.compare_frame_controller.protocol_undo_stack) self.undo_group.addStack(self.generator_tab_controller.generator_undo_stack) self.undo_group.setActiveStack(self.signal_tab_controller.signal_undo_stack) self.cancel_action = QAction(self.tr("Cancel"), self) self.cancel_action.setShortcut(QKeySequence.Cancel if hasattr(QKeySequence, "Cancel") else "Esc") self.cancel_action.triggered.connect(self.on_cancel_triggered) self.cancel_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.cancel_action.setIcon(QIcon.fromTheme("dialog-cancel")) self.addAction(self.cancel_action) self.ui.actionAuto_detect_new_signals.setChecked(constants.SETTINGS.value("auto_detect_new_signals", True, bool)) self.participant_legend_model = ParticipantLegendListModel(self.project_manager.participants) self.ui.listViewParticipants.setModel(self.participant_legend_model) gtc = self.generator_tab_controller gtc.ui.splitter.setSizes([gtc.width() / 0.7, gtc.width() / 0.3]) self.ui.tab_generator.layout().addWidget(self.generator_tab_controller) self.signal_protocol_dict = {} # type: dict[SignalFrame, ProtocolAnalyzer] self.ui.lnEdtTreeFilter.setClearButtonEnabled(True) group = QActionGroup(self) self.ui.actionFSK.setActionGroup(group) self.ui.actionOOK.setActionGroup(group) self.ui.actionNone.setActionGroup(group) self.ui.actionPSK.setActionGroup(group) self.recentFileActionList = [] self.create_connects() self.init_recent_file_action_list(constants.SETTINGS.value("recentFiles", [])) self.filemodel = FileSystemModel(self) path = QDir.homePath() self.filemodel.setIconProvider(FileIconProvider()) self.filemodel.setRootPath(path) self.file_proxy_model = FileFilterProxyModel(self) self.file_proxy_model.setSourceModel(self.filemodel) self.ui.fileTree.setModel(self.file_proxy_model) self.ui.fileTree.setRootIndex(self.file_proxy_model.mapFromSource(self.filemodel.index(path))) self.ui.fileTree.setToolTip(path) self.ui.fileTree.header().setSectionResizeMode(0, QHeaderView.ResizeToContents) self.ui.fileTree.header().setSectionResizeMode(1, QHeaderView.Stretch) self.ui.fileTree.setFocus() self.generator_tab_controller.table_model.cfc = self.compare_frame_controller self.ui.actionConvert_Folder_to_Project.setEnabled(False) undo_action = self.undo_group.createUndoAction(self) undo_action.setIcon(QIcon.fromTheme("edit-undo")) undo_action.setShortcut(QKeySequence.Undo) self.ui.menuEdit.insertAction(self.ui.actionDecoding, undo_action) redo_action = self.undo_group.createRedoAction(self) redo_action.setIcon(QIcon.fromTheme("edit-redo")) redo_action.setShortcut(QKeySequence.Redo) self.ui.menuEdit.insertAction(self.ui.actionDecoding, redo_action) self.ui.menuEdit.insertSeparator(self.ui.actionDecoding) self.ui.actionAbout_Qt.setIcon(QIcon(":/qt-project.org/qmessagebox/images/qtlogo-64.png")) self.__set_non_project_warning_visibility() self.ui.splitter.setSizes([0, 1]) self.refresh_main_menu() self.apply_default_view(constants.SETTINGS.value('default_view', type=int)) self.project_save_timer.start(ProjectManager.AUTOSAVE_INTERVAL_MINUTES * 60 * 1000) self.ui.actionProject_settings.setVisible(False) self.ui.actionSave_project.setVisible(False) self.ui.actionClose_project.setVisible(False) def __set_non_project_warning_visibility(self): show = constants.SETTINGS.value("show_non_project_warning", True, bool) and not self.project_manager.project_loaded self.ui.labelNonProjectMode.setVisible(show) def create_connects(self): self.ui.actionFullscreen_mode.setShortcut(QKeySequence.FullScreen) self.ui.actionOpen.setShortcut(QKeySequence(QKeySequence.Open)) self.ui.actionOpen_directory.setShortcut(QKeySequence("Ctrl+Shift+O")) self.ui.menuEdit.aboutToShow.connect(self.on_edit_menu_about_to_show) self.ui.actionNew_Project.triggered.connect(self.on_new_project_action_triggered) self.ui.actionNew_Project.setShortcut(QKeySequence.New) self.ui.actionProject_settings.triggered.connect(self.on_project_settings_action_triggered) self.ui.actionSave_project.triggered.connect(self.save_project) self.ui.actionClose_project.triggered.connect(self.close_project) self.ui.actionAbout_AutomaticHacker.triggered.connect(self.on_show_about_clicked) self.ui.actionRecord.triggered.connect(self.on_show_record_dialog_action_triggered) self.ui.actionFullscreen_mode.triggered.connect(self.on_fullscreen_action_triggered) self.ui.actionSaveAllSignals.triggered.connect(self.signal_tab_controller.save_all) self.ui.actionCloseAllFiles.triggered.connect(self.on_close_all_files_action_triggered) self.ui.actionOpen.triggered.connect(self.on_open_file_action_triggered) self.ui.actionOpen_directory.triggered.connect(self.on_open_directory_action_triggered) self.ui.actionDecoding.triggered.connect(self.on_show_decoding_dialog_triggered) self.ui.actionSpectrum_Analyzer.triggered.connect(self.on_show_spectrum_dialog_action_triggered) self.ui.actionOptions.triggered.connect(self.show_options_dialog_action_triggered) self.ui.actionSniff_protocol.triggered.connect(self.show_proto_sniff_dialog) self.ui.actionAbout_Qt.triggered.connect(QApplication.instance().aboutQt) self.ui.actionSamples_from_csv.triggered.connect(self.on_import_samples_from_csv_action_triggered) self.ui.actionAuto_detect_new_signals.triggered.connect(self.on_auto_detect_new_signals_action_triggered) self.ui.btnFileTreeGoUp.clicked.connect(self.on_btn_file_tree_go_up_clicked) self.ui.fileTree.directory_open_wanted.connect(self.project_manager.set_project_folder) self.signal_tab_controller.frame_closed.connect(self.close_signal_frame) self.signal_tab_controller.signal_created.connect(self.on_signal_created) self.signal_tab_controller.ui.scrollArea.files_dropped.connect(self.on_files_dropped) self.signal_tab_controller.files_dropped.connect(self.on_files_dropped) self.signal_tab_controller.frame_was_dropped.connect(self.set_frame_numbers) self.simulator_tab_controller.open_in_analysis_requested.connect(self.on_simulator_open_in_analysis_requested) self.simulator_tab_controller.rx_file_saved.connect(self.adjust_for_current_file) self.compare_frame_controller.show_interpretation_clicked.connect( self.show_protocol_selection_in_interpretation) self.compare_frame_controller.files_dropped.connect(self.on_files_dropped) self.compare_frame_controller.show_decoding_clicked.connect(self.on_show_decoding_dialog_triggered) self.compare_frame_controller.ui.treeViewProtocols.files_dropped_on_group.connect( self.on_files_dropped_on_group) self.compare_frame_controller.participant_changed.connect(self.signal_tab_controller.on_participant_changed) self.compare_frame_controller.ui.treeViewProtocols.close_wanted.connect(self.on_cfc_close_wanted) self.compare_frame_controller.show_config_field_types_triggered.connect( self.on_show_field_types_config_action_triggered) self.compare_frame_controller.load_protocol_clicked.connect(self.on_compare_frame_controller_load_protocol_clicked) self.ui.lnEdtTreeFilter.textChanged.connect(self.on_file_tree_filter_text_changed) self.ui.tabWidget.currentChanged.connect(self.on_selected_tab_changed) self.project_save_timer.timeout.connect(self.save_project) self.ui.actionConvert_Folder_to_Project.triggered.connect(self.project_manager.convert_folder_to_project) self.project_manager.project_loaded_status_changed.connect(self.on_project_loaded_status_changed) self.project_manager.project_updated.connect(self.on_project_updated) self.ui.textEditProjectDescription.textChanged.connect(self.on_text_edit_project_description_text_changed) self.ui.tabWidget_Project.tabBarDoubleClicked.connect(self.on_project_tab_bar_double_clicked) self.ui.listViewParticipants.doubleClicked.connect(self.on_project_settings_action_triggered) self.ui.actionShowFileTree.triggered.connect(self.on_action_show_filetree_triggered) self.ui.actionShowFileTree.setShortcut(QKeySequence("F10")) self.ui.labelNonProjectMode.linkActivated.connect(self.on_label_non_project_mode_link_activated) self.ui.menuFile.addSeparator() for i in range(constants.MAX_RECENT_FILE_NR): recent_file_action = QAction(self) recent_file_action.setVisible(False) recent_file_action.triggered.connect(self.on_open_recent_action_triggered) self.recentFileActionList.append(recent_file_action) self.ui.menuFile.addAction(self.recentFileActionList[i]) def add_plain_bits_from_txt(self, filename: str): with open(filename) as f: protocol = ProtocolAnalyzer.get_protocol_from_string(f.readlines()) protocol.filename = filename protocol.name = util.get_name_from_filename(filename) self.compare_frame_controller.add_protocol(protocol) self.compare_frame_controller.refresh() self.__add_empty_frame_for_filename(protocol, filename) def __add_empty_frame_for_filename(self, protocol: ProtocolAnalyzer, filename: str): sf = self.signal_tab_controller.add_empty_frame(filename, protocol) self.signal_protocol_dict[sf] = protocol self.set_frame_numbers() self.file_proxy_model.open_files.add(filename) def add_protocol_file(self, filename): proto = self.compare_frame_controller.add_protocol_from_file(filename) if proto: self.__add_empty_frame_for_filename(proto, filename) def add_fuzz_profile(self, filename): self.ui.tabWidget.setCurrentIndex(2) self.generator_tab_controller.load_from_file(filename) def add_simulator_profile(self, filename): self.ui.tabWidget.setCurrentIndex(3) self.simulator_tab_controller.load_simulator_file(filename) def add_signalfile(self, filename: str, group_id=0, enforce_sample_rate=None): if not os.path.exists(filename): QMessageBox.critical(self, self.tr("File not Found"), self.tr("The file {0} could not be found. Was it moved or renamed?").format( filename)) return sig_name = os.path.splitext(os.path.basename(filename))[0] # Use default sample rate for signal # Sample rate will be overriden in case of a project later if enforce_sample_rate is not None: sample_rate = enforce_sample_rate else: sample_rate = self.project_manager.device_conf["sample_rate"] signal = Signal(filename, sig_name, sample_rate=sample_rate) self.file_proxy_model.open_files.add(filename) self.add_signal(signal, group_id) def add_signal(self, signal, group_id=0, index=-1): self.setCursor(Qt.WaitCursor) pa = ProtocolAnalyzer(signal) sig_frame = self.signal_tab_controller.add_signal_frame(pa, index=index) pa = self.compare_frame_controller.add_protocol(pa, group_id) signal.blockSignals(True) has_entry = self.project_manager.read_project_file_for_signal(signal) if self.ui.actionAuto_detect_new_signals.isChecked() and not has_entry and not signal.changed: sig_frame.ui.stackedWidget.setCurrentWidget(sig_frame.ui.pageLoading) qApp.processEvents() signal.auto_detect(detect_modulation=True, detect_noise=False) sig_frame.ui.stackedWidget.setCurrentWidget(sig_frame.ui.pageSignal) signal.blockSignals(False) self.signal_protocol_dict[sig_frame] = pa sig_frame.refresh(draw_full_signal=True) # protocol is derived here if self.project_manager.read_participants_for_signal(signal, pa.messages): sig_frame.ui.gvSignal.redraw_view() sig_frame.ui.gvSignal.auto_fit_view() self.set_frame_numbers() self.compare_frame_controller.filter_search_results() self.refresh_main_menu() self.unsetCursor() def close_protocol(self, protocol): self.compare_frame_controller.remove_protocol(protocol) # Needs to be removed in generator also, otherwise program crashes, # if item from tree in generator is selected and corresponding signal is closed self.generator_tab_controller.tree_model.remove_protocol(protocol) protocol.eliminate() def close_signal_frame(self, signal_frame: SignalFrame): try: self.project_manager.write_signal_information_to_project_file(signal_frame.signal) try: proto = self.signal_protocol_dict[signal_frame] except KeyError: proto = None if proto is not None: self.close_protocol(proto) del self.signal_protocol_dict[signal_frame] if self.signal_tab_controller.ui.scrlAreaSignals.minimumHeight() > signal_frame.height(): self.signal_tab_controller.ui.scrlAreaSignals.setMinimumHeight( self.signal_tab_controller.ui.scrlAreaSignals.minimumHeight() - signal_frame.height()) if signal_frame.signal is not None: # Non-Empty Frame (when a signal and not a protocol is opened) self.file_proxy_model.open_files.discard(signal_frame.signal.filename) signal_frame.eliminate() self.compare_frame_controller.ui.treeViewProtocols.expandAll() self.set_frame_numbers() self.refresh_main_menu() except Exception as e: Errors.generic_error(self.tr("Failed to close"), str(e), traceback.format_exc()) self.unsetCursor() def add_files(self, filepaths, group_id=0, enforce_sample_rate=None): num_files = len(filepaths) if num_files == 0: return for i, filename in enumerate(filepaths): if not os.path.exists(filename): continue if os.path.isdir(filename): for f in self.signal_tab_controller.signal_frames: self.close_signal_frame(f) FileOperator.RECENT_PATH = filename self.project_manager.set_project_folder(filename) return FileOperator.RECENT_PATH = os.path.split(filename)[0] if filename.endswith(".complex"): self.add_signalfile(filename, group_id, enforce_sample_rate=enforce_sample_rate) elif filename.endswith(".coco"): self.add_signalfile(filename, group_id, enforce_sample_rate=enforce_sample_rate) elif filename.endswith(".proto") or filename.endswith(".proto.xml") or filename.endswith(".bin"): self.add_protocol_file(filename) elif filename.endswith(".wav"): try: import wave w = wave.open(filename) w.close() except wave.Error as e: Errors.generic_error("Unsupported WAV type", "Only uncompressed WAVs (PCM) are supported.", str(e)) continue self.add_signalfile(filename, group_id, enforce_sample_rate=enforce_sample_rate) elif filename.endswith(".fuzz") or filename.endswith(".fuzz.xml"): self.add_fuzz_profile(filename) elif filename.endswith(".sim") or filename.endswith(".sim.xml"): self.add_simulator_profile(filename) elif filename.endswith(".txt"): self.add_plain_bits_from_txt(filename) elif filename.endswith(".csv"): self.__import_csv(filename, group_id) continue elif os.path.basename(filename) == constants.PROJECT_FILE: self.project_manager.set_project_folder(os.path.split(filename)[0]) else: self.add_signalfile(filename, group_id, enforce_sample_rate=enforce_sample_rate) if self.project_manager.project_file is None: self.adjust_for_current_file(filename) self.refresh_main_menu() def set_frame_numbers(self): self.signal_tab_controller.set_frame_numbers() def closeEvent(self, event: QCloseEvent): self.save_project() super().closeEvent(event) def close_all_files(self): self.signal_tab_controller.close_all() self.compare_frame_controller.reset() self.generator_tab_controller.table_model.protocol.clear() self.generator_tab_controller.refresh_tree() self.generator_tab_controller.refresh_table() self.generator_tab_controller.refresh_label_list() self.signal_tab_controller.signal_undo_stack.clear() self.compare_frame_controller.protocol_undo_stack.clear() self.generator_tab_controller.generator_undo_stack.clear() self.simulator_tab_controller.close_all() def show_options_dialog_specific_tab(self, tab_index: int): op = OptionsDialog(self.plugin_manager.installed_plugins, parent=self) op.values_changed.connect(self.on_options_changed) op.ui.tabWidget.setCurrentIndex(tab_index) op.show() def refresh_main_menu(self): enable = len(self.signal_protocol_dict) > 0 self.ui.actionSaveAllSignals.setEnabled(enable) self.ui.actionCloseAllFiles.setEnabled(enable) def apply_default_view(self, view_index: int): self.compare_frame_controller.ui.cbProtoView.setCurrentIndex(view_index) self.generator_tab_controller.ui.cbViewType.setCurrentIndex(view_index) self.simulator_tab_controller.ui.cbViewType.setCurrentIndex(view_index) for sig_frame in self.signal_tab_controller.signal_frames: sig_frame.ui.cbProtoView.setCurrentIndex(view_index) def show_project_settings(self): pdc = ProjectDialog(new_project=False, project_manager=self.project_manager, parent=self) pdc.finished.connect(self.on_project_dialog_finished) pdc.show() def collapse_project_tab_bar(self): self.ui.tabParticipants.hide() self.ui.tabDescription.hide() self.ui.tabWidget_Project.setMaximumHeight(self.ui.tabWidget_Project.tabBar().height()) def expand_project_tab_bar(self): self.ui.tabDescription.show() self.ui.tabParticipants.show() self.ui.tabWidget_Project.setMaximumHeight(9000) def save_project(self): self.project_manager.save_project(simulator_config=self.simulator_tab_controller.simulator_config) def close_project(self): self.save_project() self.close_all_files() self.compare_frame_controller.proto_analyzer.message_types.clear() self.compare_frame_controller.active_message_type.clear() self.compare_frame_controller.updateUI() self.project_manager.participants.clear() self.participant_legend_model.update() self.filemodel.setRootPath(QDir.homePath()) self.ui.fileTree.setRootIndex(self.file_proxy_model.mapFromSource(self.filemodel.index(QDir.homePath()))) self.hide_file_tree() self.project_manager.project_path = "" self.project_manager.project_file = None @pyqtSlot() def on_project_tab_bar_double_clicked(self): if self.ui.tabParticipants.isVisible(): self.collapse_project_tab_bar() else: self.expand_project_tab_bar() @pyqtSlot() def on_project_updated(self): self.participant_legend_model.update() self.compare_frame_controller.refresh() self.ui.textEditProjectDescription.setText(self.project_manager.description) @pyqtSlot() def on_fullscreen_action_triggered(self): if self.ui.actionFullscreen_mode.isChecked(): self.showFullScreen() else: self.showMaximized() def adjust_for_current_file(self, file_path): if file_path is None: return if file_path in FileOperator.archives.keys(): file_path = copy.copy(FileOperator.archives[file_path]) settings = constants.SETTINGS recent_file_paths = settings.value("recentFiles", []) recent_file_paths = [] if recent_file_paths is None else recent_file_paths # check None for OSX recent_file_paths = [p for p in recent_file_paths if p != file_path and p is not None and os.path.exists(p)] recent_file_paths.insert(0, file_path) recent_file_paths = recent_file_paths[:constants.MAX_RECENT_FILE_NR] self.init_recent_file_action_list(recent_file_paths) settings.setValue("recentFiles", recent_file_paths) def init_recent_file_action_list(self, recent_file_paths: list): for i in range(len(self.recentFileActionList)): self.recentFileActionList[i].setVisible(False) if recent_file_paths is None: return for i, file_path in enumerate(recent_file_paths): if os.path.isfile(file_path): display_text = os.path.basename(file_path) self.recentFileActionList[i].setIcon(QIcon()) elif os.path.isdir(file_path): head, tail = os.path.split(file_path) display_text = tail head, tail = os.path.split(head) if tail: display_text = tail + "/" + display_text self.recentFileActionList[i].setIcon(QIcon.fromTheme("folder")) else: continue self.recentFileActionList[i].setText(display_text) self.recentFileActionList[i].setData(file_path) self.recentFileActionList[i].setVisible(True) @pyqtSlot() def on_show_field_types_config_action_triggered(self): self.show_options_dialog_specific_tab(tab_index=2) @pyqtSlot() def on_open_recent_action_triggered(self): action = self.sender() try: if os.path.isdir(action.data()): self.project_manager.set_project_folder(action.data()) elif os.path.isfile(action.data()): self.setCursor(Qt.WaitCursor) self.add_files(FileOperator.uncompress_archives([action.data()], QDir.tempPath())) self.unsetCursor() except Exception as e: Errors.generic_error(self.tr("Failed to open"), str(e), traceback.format_exc()) self.unsetCursor() @pyqtSlot() def on_show_about_clicked(self): descr = "<b><h2>Universal Radio Hacker</h2></b>Version: {0}<br />" \ "GitHub: <a href='https://github.com/jopohl/urh'>https://github.com/jopohl/urh</a><br /><br />" \ "Creators:<i><ul><li>" \ "Johannes Pohl <<a href='mailto:[email protected]'>[email protected]</a>></li>" \ "<li>Andreas Noack <<a href='mailto:[email protected]'>[email protected]</a>></li>" \ "</ul></i>".format(version.VERSION) QMessageBox.about(self, self.tr("About"), self.tr(descr)) @pyqtSlot(int, int, int, int) def show_protocol_selection_in_interpretation(self, start_message, start, end_message, end): try: cfc = self.compare_frame_controller msg_total = 0 last_sig_frame = None for protocol in cfc.protocol_list: if not protocol.show: continue n = protocol.num_messages view_type = cfc.ui.cbProtoView.currentIndex() messages = [i - msg_total for i in range(msg_total, msg_total + n) if start_message <= i <= end_message] if len(messages) > 0: try: signal_frame = next((sf for sf, pf in self.signal_protocol_dict.items() if pf == protocol)) except StopIteration: QMessageBox.critical(self, self.tr("Error"), self.tr("Could not find corresponding signal frame.")) return signal_frame.set_roi_from_protocol_analysis(min(messages), start, max(messages), end + 1, view_type) last_sig_frame = signal_frame msg_total += n focus_frame = last_sig_frame if last_sig_frame is not None: self.signal_tab_controller.ui.scrollArea.ensureWidgetVisible(last_sig_frame, 0, 0) QApplication.instance().processEvents() self.ui.tabWidget.setCurrentIndex(0) if focus_frame is not None: focus_frame.ui.txtEdProto.setFocus() except Exception as e: logger.exception(e) @pyqtSlot(str) def on_file_tree_filter_text_changed(self, text: str): if len(text) > 0: self.filemodel.setNameFilters(["*" + text + "*"]) else: self.filemodel.setNameFilters(["*"]) @pyqtSlot() def on_show_decoding_dialog_triggered(self): signals = [sf.signal for sf in self.signal_tab_controller.signal_frames] decoding_controller = DecoderDialog( self.compare_frame_controller.decodings, signals, self.project_manager, parent=self) decoding_controller.finished.connect(self.update_decodings) decoding_controller.show() decoding_controller.decoder_update() @pyqtSlot() def update_decodings(self): self.project_manager.load_decodings() self.compare_frame_controller.fill_decoding_combobox() self.compare_frame_controller.refresh_existing_encodings() self.generator_tab_controller.refresh_existing_encodings(self.compare_frame_controller.decodings) @pyqtSlot(int) def on_selected_tab_changed(self, index: int): if index == 0: self.undo_group.setActiveStack(self.signal_tab_controller.signal_undo_stack) elif index == 1: self.undo_group.setActiveStack(self.compare_frame_controller.protocol_undo_stack) self.compare_frame_controller.ui.tblViewProtocol.resize_columns() self.compare_frame_controller.ui.tblViewProtocol.resize_vertical_header() h = max(self.compare_frame_controller.ui.btnSaveProto.height(), self.generator_tab_controller.ui.btnSave.height()) self.compare_frame_controller.ui.btnSaveProto.setMinimumHeight(h) th = self.compare_frame_controller.ui.tabWidget.tabBar().height() for i in range(self.compare_frame_controller.ui.tabWidget.count()): self.compare_frame_controller.ui.tabWidget.widget(i).layout().setContentsMargins(0, 7 + h - th, 0, 0) elif index == 2: self.undo_group.setActiveStack(self.generator_tab_controller.generator_undo_stack) h = max(self.compare_frame_controller.ui.btnSaveProto.height(), self.generator_tab_controller.ui.btnSave.height()) self.generator_tab_controller.ui.btnSave.setMinimumHeight(h) th = self.generator_tab_controller.ui.tabWidget.tabBar().height() for i in range(self.generator_tab_controller.ui.tabWidget.count()): self.generator_tab_controller.ui.tabWidget.widget(i).layout().setContentsMargins(0, 7 + h - th, 0, 0) # Modulators may got changed from Simulator Dialog self.generator_tab_controller.refresh_modulators() # Signals may got reordered in analysis self.generator_tab_controller.tree_model.update() self.generator_tab_controller.ui.treeProtocols.expandAll() @pyqtSlot() def on_show_record_dialog_action_triggered(self): pm = self.project_manager try: r = ReceiveDialog(pm, parent=self) except OSError as e: logger.error(repr(e)) return if r.has_empty_device_list: Errors.no_device() r.close() return r.device_parameters_changed.connect(pm.set_device_parameters) r.files_recorded.connect(self.on_signals_recorded) r.show() def create_protocol_sniff_dialog(self, testing_mode=False): pm = self.project_manager signal = next((proto.signal for proto in self.compare_frame_controller.protocol_list), None) signals = [f.signal for f in self.signal_tab_controller.signal_frames if f.signal] psd = ProtocolSniffDialog(project_manager=pm, signal=signal, signals=signals, testing_mode=testing_mode, parent=self) if psd.has_empty_device_list: Errors.no_device() psd.close() return None else: psd.device_parameters_changed.connect(pm.set_device_parameters) psd.protocol_accepted.connect(self.compare_frame_controller.add_sniffed_protocol_messages) return psd @pyqtSlot() def show_proto_sniff_dialog(self): psd = self.create_protocol_sniff_dialog() if psd: psd.show() @pyqtSlot() def on_show_spectrum_dialog_action_triggered(self): pm = self.project_manager r = SpectrumDialogController(pm, parent=self) if r.has_empty_device_list: Errors.no_device() r.close() return r.device_parameters_changed.connect(pm.set_device_parameters) r.show() @pyqtSlot(list, float) def on_signals_recorded(self, file_names: list, sample_rate: float): QApplication.instance().setOverrideCursor(Qt.WaitCursor) for filename in file_names: self.add_signalfile(filename, enforce_sample_rate=sample_rate) QApplication.instance().restoreOverrideCursor() @pyqtSlot() def show_options_dialog_action_triggered(self): self.show_options_dialog_specific_tab(tab_index=4) @pyqtSlot() def on_new_project_action_triggered(self): self.close_project() pdc = ProjectDialog(parent=self) pdc.finished.connect(self.on_project_dialog_finished) pdc.show() @pyqtSlot() def on_project_settings_action_triggered(self): self.show_project_settings() @pyqtSlot() def on_edit_menu_about_to_show(self): self.ui.actionShowFileTree.setChecked(self.ui.splitter.sizes()[0] > 0) def hide_file_tree(self): self.ui.splitter.setSizes([0, 1]) @pyqtSlot() def on_action_show_filetree_triggered(self): if self.ui.splitter.sizes()[0] > 0: self.hide_file_tree() else: self.ui.splitter.setSizes([1, 1]) @pyqtSlot() def on_project_dialog_finished(self): if self.sender().committed: self.project_manager.from_dialog(self.sender()) @pyqtSlot() def on_open_file_action_triggered(self): self.show_open_dialog(directory=False) @pyqtSlot() def on_open_directory_action_triggered(self): self.show_open_dialog(directory=True) def show_open_dialog(self, directory=False): dialog = FileOperator.get_open_dialog(directory_mode=directory, parent=self, name_filter="full") if dialog.exec_(): try: file_names = dialog.selectedFiles() folders = [folder for folder in file_names if os.path.isdir(folder)] if len(folders) > 0: folder = folders[0] for f in self.signal_tab_controller.signal_frames: self.close_signal_frame(f) self.project_manager.set_project_folder(folder) else: self.setCursor(Qt.WaitCursor) file_names = FileOperator.uncompress_archives(file_names, QDir.tempPath()) self.add_files(file_names) self.unsetCursor() except Exception as e: Errors.generic_error(self.tr("Failed to open"), str(e), traceback.format_exc()) self.unsetCursor() @pyqtSlot() def on_close_all_files_action_triggered(self): self.close_all_files() @pyqtSlot(list) def on_files_dropped(self, files): """ :type files: list of QtCore.QUrl """ self.__add_urls_to_group(files, group_id=0) @pyqtSlot(list, int) def on_files_dropped_on_group(self, files, group_id: int): """ :param group_id: :type files: list of QtCore.QUrl """ self.__add_urls_to_group(files, group_id=group_id) def __add_urls_to_group(self, file_urls, group_id=0): local_files = [file_url.toLocalFile() for file_url in file_urls if file_url.isLocalFile()] if len(local_files) > 0: self.setCursor(Qt.WaitCursor) self.add_files(FileOperator.uncompress_archives(local_files, QDir.tempPath()), group_id=group_id) self.unsetCursor() @pyqtSlot(list) def on_cfc_close_wanted(self, protocols: list): frame_protos = {sframe: protocol for sframe, protocol in self.signal_protocol_dict.items() if protocol in protocols} for frame in frame_protos: self.close_signal_frame(frame) for proto in (proto for proto in protocols if proto not in frame_protos.values()): # close protocols without associated signal frame self.close_protocol(proto) @pyqtSlot(dict) def on_options_changed(self, changed_options: dict): refresh_protocol_needed = "show_pause_as_time" in changed_options if refresh_protocol_needed: for sf in self.signal_tab_controller.signal_frames: sf.refresh_protocol() self.project_manager.reload_field_types() self.compare_frame_controller.refresh_field_types_for_labels() self.compare_frame_controller.set_shown_protocols() self.generator_tab_controller.set_network_sdr_send_button_visibility() self.generator_tab_controller.init_rfcat_plugin() self.generator_tab_controller.set_modulation_profile_status() self.simulator_tab_controller.refresh_field_types_for_labels() if "num_sending_repeats" in changed_options: self.project_manager.device_conf["num_sending_repeats"] = changed_options["num_sending_repeats"] if "default_view" in changed_options: self.apply_default_view(int(changed_options["default_view"])) if "spectrogram_colormap" in changed_options: self.signal_tab_controller.redraw_spectrograms() @pyqtSlot() def on_text_edit_project_description_text_changed(self): self.project_manager.description = self.ui.textEditProjectDescription.toPlainText() @pyqtSlot() def on_btn_file_tree_go_up_clicked(self): cur_dir = self.filemodel.rootDirectory() if cur_dir.cdUp(): path = cur_dir.path() self.filemodel.setRootPath(path) self.ui.fileTree.setRootIndex(self.file_proxy_model.mapFromSource(self.filemodel.index(path))) @pyqtSlot(int, Signal) def on_signal_created(self, index: int, signal: Signal): self.add_signal(signal, index=index) @pyqtSlot() def on_cancel_triggered(self): for signal_frame in self.signal_tab_controller.signal_frames: signal_frame.cancel_filtering() @pyqtSlot() def on_import_samples_from_csv_action_triggered(self): self.__import_csv(file_name="") @pyqtSlot(bool) def on_auto_detect_new_signals_action_triggered(self, checked: bool): constants.SETTINGS.setValue("auto_detect_new_signals", bool(checked)) def __import_csv(self, file_name, group_id=0): def on_data_imported(complex_file, sample_rate): sample_rate = None if sample_rate == 0 else sample_rate self.add_files([complex_file], group_id=group_id, enforce_sample_rate=sample_rate) dialog = CSVImportDialog(file_name, parent=self) dialog.data_imported.connect(on_data_imported) dialog.exec_() @pyqtSlot(str) def on_label_non_project_mode_link_activated(self, link: str): if link == "dont_show_non_project_again": self.ui.labelNonProjectMode.hide() constants.SETTINGS.setValue("show_non_project_warning", False) elif link == "open_new_project_dialog": self.on_new_project_action_triggered() @pyqtSlot(bool) def on_project_loaded_status_changed(self, project_loaded: bool): self.ui.actionProject_settings.setVisible(project_loaded) self.ui.actionSave_project.setVisible(project_loaded) self.ui.actionClose_project.setVisible(project_loaded) self.ui.actionConvert_Folder_to_Project.setDisabled(project_loaded) self.__set_non_project_warning_visibility() @pyqtSlot() def on_compare_frame_controller_load_protocol_clicked(self): dialog = FileOperator.get_open_dialog(directory_mode=False, parent=self, name_filter="proto") if dialog.exec_(): for filename in dialog.selectedFiles(): self.add_protocol_file(filename) @pyqtSlot(str) def on_simulator_open_in_analysis_requested(self, text: str): protocol = ProtocolAnalyzer.get_protocol_from_string(text.split("\n")) protocol.name = "Transcript" self.ui.tabWidget.setCurrentIndex(1) self.compare_frame_controller.add_protocol(protocol) self.compare_frame_controller.refresh()
class MainWindow(QMainWindow, MainWindowUI): toolbar_gadget_changed_signal = pyqtSignal(ToolbarState) def __init__(self, parent=None): super(MainWindow, self).__init__(parent) super(MainWindow, self)._init_ui(self) self.setWindowIcon(QIcon(":/app-icon.png")) self._action_manager = ActionManager.instance(self) self._undo_group = QUndoGroup(self) self.cmp_browser_dock = None self._preferences_dialog = None self._undo_widget.set_group(self._undo_group) # undo redo action 要重新create self.undo_action = self._undo_group.createUndoAction(self) self.redo_action = self._undo_group.createRedoAction(self) self.undo_action.setText("撤销") self.undo_action.setShortcut("Ctrl+Z") self.redo_action.setText("重做") self.redo_action.setShortcut("Ctrl+Shift+Z") self.edit_menu.insertAction(self.edit_menu.actions()[0], self.undo_action) self.edit_menu.insertAction(self.undo_action, self.redo_action) self.edit_menu.insertSeparator(self.edit_menu.actions()[2]) self.__register_actions() self._has_editor = False self._property_dock = PropertyBrowserDock(parent=self) self._property_dock.setAllowedAreas(Qt.RightDockWidgetArea | Qt.LeftDockWidgetArea) self.addDockWidget(Qt.LeftDockWidgetArea, self._property_dock) self.project_dock_widget.current_item_changed_signal.connect(self._property_dock.set_browser) self._main_toolbar = MainToolBar(self) self.addToolBar(self._main_toolbar) self._tools_toolbar = ToolsToolBar(self) self._selection_toolbar = self._tools_toolbar.selection_toolbar() self._eraser_toolbar = self._tools_toolbar.eraser_toolbar() self.toolbar_gadget = ToolbarState.NONE_SELECTED self.window_state_data = WindowStateData() self._connect_notify() self._load_initial_file() self._restore_data() self.update_actions() self._show_no_editor_widget() def _init_style(self): file = QFile(os.path.join(os.getcwd(), "other/qss/lightblue.qss")) if file.open(QFile.ReadOnly): qss = file.readAll() palette_color = qss.mid(23, 7) self.setPalette(QPalette(QColor(str(palette_color)))) self.setStyleSheet(str(qss)) file.close() def _show_no_editor_widget(self): self.project_dock_widget.setHidden(True) self._undo_widget.setHidden(True) self._property_dock.setHidden(True) self._main_toolbar.setHidden(True) self._tools_toolbar.setHidden(True) # self._selection_toolbar.setHidden(True) # self.center_tab_widget.setHidden(True) self.menubar.clear() self.menubar.addMenu(self.file_menu) self.menubar.addMenu(self.edit_menu) # self.menubar.addMenu(self.view_menu) self.menubar.addMenu(self.help_menu) self.save_project_action.setEnabled(False) self.save_all_action.setEnabled(False) self.save_project_as_action.setEnabled(False) self.export_action.setEnabled(False) self.import_action.setEnabled(False) self.project_info_action.setEnabled(False) self.undo_action.setEnabled(False) self.redo_action.setEnabled(False) self.find_replace_menu.setEnabled(False) self._no_editor_widget = NoEditorWidget(self) self.setCentralWidget(self._no_editor_widget) self._has_editor = False def _show_document_widget(self): self.project_dock_widget.setHidden(False) self._undo_widget.setHidden(False) self._property_dock.setHidden(False) self._no_editor_widget.setHidden(True) self._main_toolbar.setHidden(False) self._tools_toolbar.setHidden(False) # self._selection_toolbar.setHidden(False) # self.center_tab_widget.setHidden(False) self.menubar.clear() self.menubar.addMenu(self.file_menu) self.menubar.addMenu(self.edit_menu) self.menubar.addMenu(self.view_menu) self.menubar.addMenu(self.project_menu) self.menubar.addMenu(self.graph_menu) self.menubar.addMenu(self.mark_menu) self.menubar.addMenu(self.count_menu) self.menubar.addMenu(self.cmp_menu) self.menubar.addMenu(self.help_menu) self.save_project_action.setEnabled(True) self.save_all_action.setEnabled(True) self.save_project_as_action.setEnabled(True) self.export_action.setEnabled(True) self.import_action.setEnabled(True) self.project_info_action.setEnabled(True) self.find_replace_menu.setEnabled(True) # 窗体的中心 使用 QTabWidget # 中心部件 self.center_tab_widget = QTabWidget(self) self.center_tab_widget.setMovable(True) self.center_tab_widget.setTabsClosable(True) self.center_tab_widget.setContextMenuPolicy(Qt.ActionsContextMenu) self.setCentralWidget(self.center_tab_widget) # 设置此label为窗口的 self._property_dock.setBaseSize(200, 600) self.center_tab_widget.tabCloseRequested.connect(self.close_file) self.center_tab_widget.currentChanged.connect(self.current_document_changed) self._has_editor = True def update_actions(self, index=-1): """TODO""" def __register_actions(self): ActionManager.register_menu(self.file_menu, Id("File")) ActionManager.register_menu(self.edit_menu, Id("Edit")) ActionManager.register_menu(self.project_menu, Id("Project")) ActionManager.register_menu(self.graph_menu, Id("Graph")) ActionManager.register_menu(self.mark_menu, Id("Mark")) ActionManager.register_menu(self.cmp_menu, Id("Comp")) ActionManager.register_menu(self.help_menu, Id("Help")) ActionManager.register_menu(self.view_menu, Id("View")) ActionManager.register_menu(self.outline_correction_menu, Id("OutlineCorrection")) ActionManager.register_action(self.new_project_action, Id("NewProject")) ActionManager.register_action(self.open_project_action, Id("OpenProject")) ActionManager.register_action(self.open_original_image_action, Id("OpenOriginalImage")) ActionManager.register_action(self.open_project_as_action, Id("OpenProjectAs")) ActionManager.register_action(self.save_project_action, Id("SaveProject")) ActionManager.register_action(self.save_project_as_action, Id("SaveProjectAs")) ActionManager.register_action(self.save_all_action, Id("SaveAll")) ActionManager.register_action(self.close_project_action, Id("CloseProject")) ActionManager.register_action(self.close_action, Id("Close")) ActionManager.register_action(self.close_all_action, Id("CloseALl")) ActionManager.register_action(self.import_action, Id("Import")) ActionManager.register_action(self.export_action, Id("Export")) ActionManager.register_action(self.project_info_action, Id("ProjectInfo")) ActionManager.register_action(self.quit_action, Id("Quit")) ActionManager.register_action(self.undo_action, Id("Undo")) ActionManager.register_action(self.redo_action, Id("Redo")) ActionManager.register_action(self.reference_action, Id("Reference")) ActionManager.register_action(self.quick_find_action, Id("QuickFind")) ActionManager.register_action(self.quick_replace_action, Id("QuickReplace")) ActionManager.register_action(self.quick_find_in_file_action, Id("QuickFindInFile")) ActionManager.register_action(self.quick_replace_in_file_action, Id("QuickReplaceInFile")) ActionManager.register_action(self.new_mark_action, Id("NewMark")) ActionManager.register_action(self.new_mark_from_action, Id("NewMarkFrom")) ActionManager.register_action(self.size_action, Id("Size")) ActionManager.register_action(self.bmp_action, Id("Bmp")) ActionManager.register_action(self.gray_action, Id("Gray")) ActionManager.register_action(self.rgb_action, Id("RGB")) ActionManager.register_action(self.bright_action, Id("Bright")) ActionManager.register_action(self.rotate_180_action, Id("Rotate180")) ActionManager.register_action(self.rotate_clockwise90_action, Id("RotateClockwise90")) ActionManager.register_action(self.rotate_counterclockwise90_action, Id("RotateCounterClockwise90")) ActionManager.register_action(self.rotate_any_action, Id("RotateAny")) ActionManager.register_action(self.rectangle_action, Id("Rectangle")) ActionManager.register_action(self.polygon_action, Id("Polygon")) ActionManager.register_action(self.origin_outline_action, Id("OriginOutline")) ActionManager.register_action(self.convex_outline_action, Id("ConvexOutline")) ActionManager.register_action(self.polygon_outline_action, Id("PolygonOutline")) ActionManager.register_action(self.woodland_outline_action, Id("AiOutline")) ActionManager.register_action(self.as_outline_action, Id("AsOutline")) ActionManager.register_action(self.add_outline_correction, Id("AddOutlineCorrection")) ActionManager.register_action(self.remove_outline_correction, Id("RemoveOutlineCorrection")) ActionManager.register_action(self.erosion_area_action, Id("ErosionArea")) ActionManager.register_action(self.girth_area_action, Id("GirthArea")) ActionManager.register_action(self.cmp_history_action, Id("CmpHistory")) ActionManager.register_action(self.help_about_action, Id("HelpAbout")) ActionManager.register_action(self.help_help_action, Id("HelpHelp")) ActionManager.register_action(self.delete_mark_item_action, Id("DeleteMarkItem")) ActionManager.register_action(self.project_dock_action, Id("ProjectDock")) ActionManager.register_action(self.property_dock_action, Id("PropertyDock")) ActionManager.register_action(self.history_dock_action, Id("HistoryDock")) def _restore_data(self): settings = QSettings() self.window_state_data.recent_files = settings.value("recent_files", []) self.window_state_data.recent_projects = settings.value("project_files", []) if not self.window_state_data.recent_files: self.window_state_data.recent_files = [] if not self.window_state_data.recent_projects: self.window_state_data.recent_projects = [] # if window_state is not None: # self.restoreState(window_state) QTimer.singleShot(0, self._load_initial_file) def _load_initial_file(self): pass def _connect_notify(self): """ 统一进行窗口组件事件信号的和槽函数连接 """ self.file_menu.aboutToShow.connect(self.update_file_menu) self.new_project_action.triggered.connect(self.create_new_project) self.open_original_image_action.triggered.connect(self.open_file) self.open_project_as_action.triggered.connect(self.open_project_as) self.save_project_action.triggered.connect(self.save_project) self.open_project_action.triggered.connect(self.open_project) self.close_all_action.triggered.connect(self.close_all_file) self.quit_action.triggered.connect(self.close) self.export_action.triggered.connect(self.export_result) self.cmp_history_action.triggered.connect(self.cmp_history) self.polygon_outline_action.triggered.connect(self.polygon_outline_detect) self.convex_outline_action.triggered.connect(self.polygon_outline_detect) self.origin_outline_action.triggered.connect(self.polygon_outline_detect) self.woodland_outline_action.triggered.connect(self.polygon_outline_detect) self.as_outline_action.triggered.connect(self.polygon_outline_detect) self.add_outline_correction.triggered.connect(self.outline_correction) self.remove_outline_correction.triggered.connect(self.outline_correction) self.project_dock_widget.double_click_mark_item.connect(self.select_pro_or_item) self.project_dock_widget.delete_mark_item_signal.connect(self.delete_mark_item) self.erosion_area_action.triggered.connect(self.show_marked_erosion) self.girth_area_action.triggered.connect(self.show_marked_girth) self.project_dock_action.toggled.connect(lambda is_on: self.project_dock_widget.setHidden(not is_on)) self.property_dock_action.toggled.connect(lambda is_on: self._property_dock.setHidden(not is_on)) self.history_dock_action.toggled.connect(lambda is_on: self._undo_widget.setHidden(not is_on)) self.project_dock_widget.close_event_signal.connect(self.project_dock_action.setChecked) self._property_dock.close_event_signal.connect(self.property_dock_action.setChecked) self._undo_widget.close_event_signal.connect(self.history_dock_action.setChecked) self.reference_action.triggered.connect(self.open_preferences_dialog) def show_marked_erosion(self): current_doc = self.center_tab_widget.currentWidget() if isinstance(current_doc, Document): project = current_doc.project() project_data = count_project_data_area(project) pie_widget = PieChart("面积饼图", project_data, self) pie_widget.show() def show_marked_girth(self): current_doc = self.center_tab_widget.currentWidget() if isinstance(current_doc, Document): project = current_doc.project() project_data = count_project_data_perimeter(project) pie_widget = PieChart("周长饼图", project_data, self) pie_widget.show() def select_pro_or_item(self, project: Project, mark_item: MarkItem): count = self.center_tab_widget.count() for index in range(count): document = self.center_tab_widget.widget(index) if document.project() == project: document.set_current_mark_item(mark_item) if document != self.center_tab_widget.currentWidget(): self.center_tab_widget.setCurrentWidget(document) def delete_mark_item(self, project: Project, mark_item: MarkItem): count = self.center_tab_widget.count() for index in range(count): document = self.center_tab_widget.widget(index) if document.project() == project: document.delete_mark_item(mark_item) def save_project(self): current_document = self.center_tab_widget.currentWidget() if current_document and isinstance(current_document, Document): try: current_document.save_project() except Exception as e: print(e) def open_project(self): dir_ = os.path.dirname(self.window_state_data.recent_files[0]) \ if self.window_state_data.recent_files else os.path.dirname(".") file_format = "IProject files " + Stream.formats() file_name = QFileDialog.getOpenFileName(self, "选择标注项目", dir_, file_format)[0] if file_name: is_open, index = self.is_open_this(file_name) if not is_open: message = self.create_document_by_project_path(file_name) else: message = "项目 %s 已经打开" % file_name self.center_tab_widget.setCurrentIndex(index) self.statusBar().showMessage(message, 5000) def open_project_as(self): image_last_dir = os.path.dirname(self.window_state_data.recent_files[0]) \ if self.window_state_data.recent_files else os.path.dirname(".") message = " " open_project_as_dialog = OpenProjectAsDialog(img_last_dir=image_last_dir, parent=self) if open_project_as_dialog.exec_(): project_path, image_path, is_changed_img_path = open_project_as_dialog.get_project_info() is_open, index = self.is_open_this(project_path) if not is_open: message = self.create_document_by_other_img_path(project_path, image_path, is_changed_img_path) else: message = "项目 %s 已经打开" % project_path self.center_tab_widget.setCurrentIndex(index) self.statusBar().showMessage(message, 5000) def closeEvent(self, event): if self._has_editor: if self.ok_to_continue(): self.save_window_state() else: event.ignore() else: self.save_window_state() def save_window_state(self): settings = QSettings() # 最近打开文件 recently_files = QVariant(self.window_state_data.recent_files) \ if self.window_state_data.recent_files else QVariant() recently_projects = QVariant(self.window_state_data.recent_projects) \ if self.window_state_data.recent_projects else QVariant() settings.setValue("recent_files", recently_files) settings.setValue("project_files", recently_projects) # 主窗口的其他状态 settings.setValue("main_window/state", QVariant(self.saveState())) # 用户关闭窗口前提问 def ok_to_continue(self): tab_num = self.center_tab_widget.count() for _ in range(tab_num): if not self.close_file(index=0): return False return True def is_save_question(self, file_name): tip_text = "要在退出前保存对 图片\"" + os.path.basename(file_name) + "\"的更改吗?" reply = QMessageBox.question( self, "遥感地图标注 - 未保存提示", tip_text, QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) if reply == QMessageBox.Cancel: return False elif reply == QMessageBox.Yes: # 保存文件 待写 """TODO""" return True else: return True def update_file_menu(self): self.file_menu.clear() self.file_menu.addAction(self.file_menu_actions[0]) self.file_menu.addAction(self.file_menu_actions[1]) self.file_menu.addAction(self.file_menu_actions[2]) self.file_menu.addSeparator() recent_files_menu = self.file_menu.addMenu("打开最近的文件(T)...") if self.window_state_data.recent_files: for i, project_name in enumerate(self.window_state_data.recent_files): dir_file_name = os.path.basename(project_name) action = create_action( parent=recent_files_menu, text=str(i + 1) + " " + dir_file_name, slot=self._open_file_from_recent) action.setData(QVariant(project_name)) recent_files_menu.addAction(action) recent_files_menu.addSeparator() remove_action = create_action( parent=recent_files_menu, text="清空最近打开的文件列表", slot=self.remove_recent_files) recent_files_menu.addAction(remove_action) recent_project_menu = self.file_menu.addMenu("打开最近的项目(P)...") if self.window_state_data.recent_projects: for i, project_name in enumerate(self.window_state_data.recent_projects): # dir_file_name = os.path.basename(project_name) action = create_action( parent=recent_project_menu, text=str(i + 1) + " " + project_name, slot=self._open_project_from_recent) action.setData(QVariant(project_name)) recent_project_menu.addAction(action) recent_project_menu.addSeparator() remove_action = create_action( parent=recent_files_menu, text="清空最近打开的项目列表", slot=self.remove_recent_project) recent_project_menu.addAction(remove_action) self.file_menu.addSeparator() add_actions(self.file_menu, self.file_menu_actions[3:]) def remove_recent_files(self): self.window_state_data.recent_files.clear() def add_recent_file(self, file_name): if file_name: if file_name in self.window_state_data.recent_files: self.window_state_data.recent_files.remove(file_name) self.window_state_data.recent_files.insert(0, file_name) while len(self.window_state_data.recent_files) > 9: print(self.window_state_data.recent_files.pop()) def remove_recent_project(self): self.window_state_data.recent_projects.clear() def add_recent_file_project(self, project_name): if project_name: if project_name in self.window_state_data.recent_projects: self.window_state_data.recent_projects.remove(project_name) self.window_state_data.recent_projects.insert(0, project_name) while len(self.window_state_data.recent_projects) > 9: print(self.window_state_data.recent_projects.pop()) def open_file(self, file_name=None): if file_name is None or isinstance(file_name, bool): dir_ = os.path.dirname(self.window_state_data.recent_files[0]) \ if self.window_state_data.recent_files else os.path.dirname(".") # 打开一个 文件选择对口框 file_format = "Image files " + Stream.support_image_formats() file_name = QFileDialog.getOpenFileName(self, "选择遥感图片", dir_, file_format)[0] if file_name: file_names = [] for index in range(self.center_tab_widget.count()): file_names.append(self.center_tab_widget.widget(index).get_file_name()) if file_name not in file_names: """""" # message = self.create_new_tab(file_name) else: message = "文件 %s 已经打开" % file_name self.center_tab_widget.setCurrentIndex(file_names.index(file_name)) self.statusBar().showMessage(message, 5000) def create_new_project(self): image_last_dir = os.path.dirname(self.window_state_data.recent_files[0]) \ if self.window_state_data.recent_files else os.path.dirname(".") new_project_dialog = NewProjectDialog(img_last_dir=image_last_dir, parent=self) if new_project_dialog.exec_(): self.setCursor(Qt.BusyCursor) project_name, project_dir, image_file, person = new_project_dialog.new_project_info() self.create_document(project_name, project_dir, image_file, person) self.setCursor(Qt.ArrowCursor) def _open_file_from_recent(self): sender = self.sender() if isinstance(sender, QAction): file_name = sender.data() self.open_file(file_name) def _open_project_from_recent(self): sender = self.sender() if isinstance(sender, QAction): file_name = sender.data() if not os.path.exists(file_name): QMessageBox.critical(self, "打开最近文件", "找不到项目:" + file_name) return is_open, index = self.is_open_this(file_name) if not is_open: self.create_document_by_project_path(file_name) else: self.center_tab_widget.setCurrentIndex(index) def close_file(self, index=None): if index is not None and isinstance(index, int): tab_widget = self.center_tab_widget.widget(index) if tab_widget.modifier(): if not self.is_save_question(tab_widget.get_file_name()): return False self.project_dock_widget.close_project(tab_widget.project()) self.center_tab_widget.removeTab(index) self._undo_group.removeStack(tab_widget.undo_stack()) if tab_widget.project().contain_browser(self._property_dock.get_browser()): self._property_dock.set_browser(None) tab_widget.disconnect() del tab_widget if index and isinstance(index, bool) and self.center_tab_widget.count() != 0: current_index = self.center_tab_widget.currentIndex() current_widget = self.center_tab_widget.currentWidget() if current_widget.is_dirty(): if self.is_save_question(current_index): self.center_tab_widget.removeTab(current_index) del current_widget self.update_close_button_enabled() if self.center_tab_widget.count() == 0: self._show_no_editor_widget() return True def close_all_file(self): self.ok_to_continue() def open_history_project(self): dir_ = os.path.dirname(self.window_state_data.recent_files[0]) \ if self.window_state_data.recent_files else os.path.dirname(".") file_format = "Project files (*.mfb)" file_names = QFileDialog.getOpenFileNames(self, "选择遥感图片", dir_, file_format)[0] if file_names: project_format = ProjectFormat() project_documents = [] for file in file_names: if not os.path.exists(file): continue if file == self.center_tab_widget.currentWidget().get_file_name(): continue project = ProjectDocument(project_format.read_project(file)) if not project: continue project_documents.append(project) return project_documents else: return None def cmp_history(self): try: current_doc = self.center_tab_widget.currentWidget() if current_doc.had_cmp(): current_doc.about_to_cmp() else: project_documents = self.open_history_project() if not project_documents: return current_doc.about_to_cmp(project_documents) except Exception as e: QMessageBox.critical(self, "打开历史项目", "打开历史记录失败:" + e.__str__()) def count_area(self, outline_array: tuple, ratio: int) -> float: """ :param outline_array: 轮廓的像素的矩阵 :param ratio: 图像的分辨率 以米为单位 :return: float类型的面积 """ pass def polygon_outline_detect(self): current_tab = self.center_tab_widget.currentWidget() outline_type = self.sender().data() if current_tab and isinstance(current_tab, Document): try: current_tab.detect_outline(outline_type) except Exception as e: print(e) def outline_correction(self): current_tab = self.center_tab_widget.currentWidget() correction_option = self.sender().data() if current_tab and isinstance(current_tab, Document): current_tab.correction_outline(correction_option) def adjust_image_size(self): current_tab = self.center_tab_widget.currentWidget() option_type = self.sender().data() if current_tab and isinstance(current_tab, Document): current_tab.adjust_size(option_type) def gadget_changed(self, gadget: GadgetDockWidgetState): if gadget in (GadgetDockWidgetState.RECTANGLE_SELECT_TOOL, GadgetDockWidgetState.ELLIPSE_SELECT_TOOL): self.quick_select_toolbar.setHidden(False) self.quick_select_toolbar.setEnabled(True) self.zoom_toolbar.setHidden(True) elif gadget == GadgetDockWidgetState.ZOOM_TOOL: self.zoom_toolbar.setHidden(False) self.zoom_toolbar.setEnabled(True) self.quick_select_toolbar.setHidden(True) else: self.zoom_toolbar.setEnabled(False) self.quick_select_toolbar.setEnabled(False) def toolbar_gadget_changed(self): self.toolbar_gadget = self.sender().data() self.toolbar_gadget_changed_signal.emit(self.toolbar_gadget) def current_document_changed(self, index: int): current_doc = self.center_tab_widget.currentWidget() if current_doc: self._undo_widget.set_stack(current_doc.undo_stack()) self._undo_group.setActiveStack(current_doc.undo_stack()) self.update_actions(index) def export_result(self): current_doc = self.center_tab_widget.currentWidget() if current_doc and isinstance(current_doc, Document): path, file_name = QFileDialog.getSaveFileName( self, "导出tif", current_doc.get_project_name(), "Images (*.tif)", self.window_state_data.recent_projects[0] ) print("---------- dir name: ", os.path.dirname(path)) print("---------- base name: ", os.path.basename(path)) if os.path.exists(os.path.dirname(path)) and os.path.basename(path): _progress = Progress() _progress.setHidden(True) current_doc.export_result(path, _progress) def open_preferences_dialog(self): self._preferences_dialog = PreferencesDialog(self) self._preferences_dialog.setAttribute(Qt.WA_DeleteOnClose) self._preferences_dialog.show() self._preferences_dialog.activateWindow() self._preferences_dialog.raise_() # #################################################################################################### def is_open_this(self, file_name): if self._has_editor: file_names = [] for index in range(self.center_tab_widget.count()): file_names.append(self.center_tab_widget.widget(index).get_file_name()) if file_name in file_names: return True, file_names.index(file_name) else: return False, -1 else: return False, -1 def update_close_button_enabled(self): is_tab_empty = self.center_tab_widget.count() != 0 self.file_menu_actions[5].setEnabled(is_tab_empty) self.file_menu_actions[6].setEnabled(is_tab_empty) self.file_menu_actions[7].setEnabled(is_tab_empty) def create_document_by_project_path(self, project_path): reader_format = ProjectFormat() project = reader_format.read_project(project_path) if not project: return "" if not os.path.exists(project.image_path): QMessageBox.critical(self, "打开最近文件", "找不到原始图片:" + project_path) return "" new_doc = Document( image_path=project.image_path, gadget=self._tools_toolbar.current_tools(), toolbar_gadget=self._selection_toolbar.current_selection_option(), eraser_size=self._eraser_toolbar.current_eraser_size(), ) new_doc.reader_format = reader_format return self.open_document(project, new_doc) def create_document_by_other_img_path(self, project_path, image_path, is_change_img_path): reader_format = ProjectFormat() project = reader_format.read_project(project_path) if not project: return " " if is_change_img_path: project.image_path = image_path reader_format.save_project(project) new_doc = Document( image_path=image_path, gadget=self._tools_toolbar.current_tools(), toolbar_gadget=self._selection_toolbar.current_selection_option(), eraser_size=self._eraser_toolbar.current_eraser_size(), ) new_doc.reader_format = reader_format return self.open_document(project, new_doc) def open_document(self, project:Project, new_doc: Document): if not self._has_editor: self._show_document_widget() self.project_dock_widget.create_project(project) new_doc.added_mark_item.connect(self.project_dock_widget.add_mark_item) new_doc.selected_mark_item_changed.connect(self.project_dock_widget.selected_mark_changed) new_doc.set_project(project) self._tools_toolbar.tools_changed.connect(new_doc.change_gadget) self._selection_toolbar.selection_option_changed.connect(new_doc.change_toolbar_gadget) self._eraser_toolbar.eraser_size_changed.connect(new_doc.eraser_size_changed) self._eraser_toolbar.selection_option_changed.connect(new_doc.change_eraser_option) self.add_document(new_doc) return " " def create_document(self, project_name, file_mame, image_path, person): try: new_doc = Document( gadget=self._tools_toolbar.current_tools(), toolbar_gadget=self._selection_toolbar.current_selection_option(), eraser_size=self._eraser_toolbar.current_eraser_size(), file_name=file_mame, project_name=project_name, image_path=image_path, person_name=person ) # type: Document project = new_doc.project() self.project_dock_widget.create_project(project) new_doc.added_mark_item.connect(self.project_dock_widget.add_mark_item) new_doc.selected_mark_item_changed.connect(self.project_dock_widget.selected_mark_changed) if not self._has_editor: self._show_document_widget() self._tools_toolbar.tools_changed.connect(new_doc.change_gadget) self._eraser_toolbar.eraser_size_changed.connect(new_doc.eraser_size_changed) self._selection_toolbar.selection_option_changed.connect(new_doc.change_toolbar_gadget) self._eraser_toolbar.selection_option_changed.connect(new_doc.change_eraser_option) self.add_document(new_doc) return "打开文件\"" + file_mame + "\"成功" except FileOpenFailException as e: print(e.message) return e.message except AttributeError as e: QMessageBox.critical(self, "创建项目", "创建项目失败1:" + e.__str__()) return " " except Exception as e: print(e) QMessageBox.critical(self, "创建项目", "创建项目失败2:" + e.__str__()) return " " def add_document(self, document: Document): self._undo_group.addStack(document.undo_stack()) index = self.center_tab_widget.addTab(document, document.project_name()) self.center_tab_widget.setTabToolTip(index, document.project().project_full_path()) document.undo_stack().indexChanged.connect(self.update_actions) document.undo_stack().cleanChanged.connect(self.update_actions) self.add_recent_file_project(document.project().project_full_path()) self.set_current_document(document) self._undo_widget.set_stack(document.undo_stack()) self._undo_group.setActiveStack(document.undo_stack()) self._property_dock.set_browser(document.project().browser) def set_current_document(self, document: Document): self.center_tab_widget.setCurrentWidget(document)
class MainController(QMainWindow): def __init__(self, *args): super().__init__(*args) self.ui = Ui_MainWindow() self.ui.setupUi(self) OptionsController.write_default_options() self.project_save_timer = QTimer() self.project_manager = ProjectManager(self) self.plugin_manager = PluginManager() self.signal_tab_controller = SignalTabController(self.project_manager, parent=self.ui.tab_interpretation) self.ui.tab_interpretation.layout().addWidget(self.signal_tab_controller) self.compare_frame_controller = CompareFrameController(parent=self.ui.tab_protocol, plugin_manager=self.plugin_manager, project_manager=self.project_manager) self.compare_frame_controller.ui.splitter.setSizes([1, 1000000]) self.ui.tab_protocol.layout().addWidget(self.compare_frame_controller) self.generator_tab_controller = GeneratorTabController(self.compare_frame_controller, self.project_manager, parent=self.ui.tab_generator) self.undo_group = QUndoGroup() self.undo_group.addStack(self.signal_tab_controller.signal_undo_stack) self.undo_group.addStack(self.compare_frame_controller.protocol_undo_stack) self.undo_group.addStack(self.generator_tab_controller.generator_undo_stack) self.undo_group.setActiveStack(self.signal_tab_controller.signal_undo_stack) self.participant_legend_model = ParticipantLegendListModel(self.project_manager.participants) self.ui.listViewParticipants.setModel(self.participant_legend_model) gtc = self.generator_tab_controller gtc.ui.splitter.setSizes([gtc.width() / 0.7, gtc.width() / 0.3]) self.ui.tab_generator.layout().addWidget(self.generator_tab_controller) self.signal_protocol_dict = {} # type: dict[SignalFrameController, ProtocolAnalyzer] self.ui.lnEdtTreeFilter.setClearButtonEnabled(True) group = QActionGroup(self) self.ui.actionFSK.setActionGroup(group) self.ui.actionOOK.setActionGroup(group) self.ui.actionNone.setActionGroup(group) self.ui.actionPSK.setActionGroup(group) self.recentFileActionList = [] self.create_connects() self.init_recent_file_action_list(constants.SETTINGS.value("recentFiles", [])) self.filemodel = FileSystemModel(self) path = QDir.homePath() self.filemodel.setIconProvider(FileIconProvider()) self.filemodel.setRootPath(path) self.file_proxy_model = FileFilterProxyModel(self) self.file_proxy_model.setSourceModel(self.filemodel) self.ui.fileTree.setModel(self.file_proxy_model) self.ui.fileTree.setRootIndex(self.file_proxy_model.mapFromSource(self.filemodel.index(path))) self.ui.fileTree.setToolTip(path) self.ui.fileTree.header().setSectionResizeMode(0, QHeaderView.ResizeToContents) self.ui.fileTree.header().setSectionResizeMode(1, QHeaderView.Stretch) self.ui.fileTree.setFocus() self.generator_tab_controller.table_model.cfc = self.compare_frame_controller self.ui.actionConvert_Folder_to_Project.setEnabled(False) undo_action = self.undo_group.createUndoAction(self) undo_action.setIcon(QIcon.fromTheme("edit-undo")) undo_action.setShortcut(QKeySequence.Undo) self.ui.menuEdit.insertAction(self.ui.actionDecoding, undo_action) redo_action = self.undo_group.createRedoAction(self) redo_action.setIcon(QIcon.fromTheme("edit-redo")) redo_action.setShortcut(QKeySequence.Redo) self.ui.menuEdit.insertAction(self.ui.actionDecoding, redo_action) self.ui.menuEdit.insertSeparator(self.ui.actionDecoding) self.ui.actionAbout_Qt.setIcon(QIcon(":/qt-project.org/qmessagebox/images/qtlogo-64.png")) self.ui.splitter.setSizes([0, 1]) self.refresh_main_menu() self.apply_default_view(constants.SETTINGS.value('default_view', type=int)) self.project_save_timer.start(ProjectManager.AUTOSAVE_INTERVAL_MINUTES * 60 * 1000) self.ui.actionProject_settings.setVisible(False) self.ui.actionSave_project.setVisible(False) def create_connects(self): self.ui.actionFullscreen_mode.setShortcut(QKeySequence.FullScreen) self.ui.actionOpen.setShortcut(QKeySequence(QKeySequence.Open)) self.ui.actionOpen_directory.setShortcut(QKeySequence("Ctrl+Shift+O")) self.ui.menuEdit.aboutToShow.connect(self.on_edit_menu_about_to_show) self.ui.actionNew_Project.triggered.connect(self.on_new_project_action_triggered) self.ui.actionNew_Project.setShortcut(QKeySequence.New) self.ui.actionProject_settings.triggered.connect(self.on_project_settings_action_triggered) self.ui.actionSave_project.triggered.connect(self.project_manager.saveProject) self.ui.actionAbout_AutomaticHacker.triggered.connect(self.on_show_about_clicked) self.ui.actionRecord.triggered.connect(self.on_show_record_dialog_action_triggered) self.ui.actionFullscreen_mode.triggered.connect(self.on_fullscreen_action_triggered) self.ui.actionSaveAllSignals.triggered.connect(self.signal_tab_controller.save_all) self.ui.actionClose_all.triggered.connect(self.on_close_all_action_triggered) self.ui.actionOpen.triggered.connect(self.on_open_file_action_triggered) self.ui.actionOpen_directory.triggered.connect(self.on_open_directory_action_triggered) self.ui.actionDecoding.triggered.connect(self.on_show_decoding_dialog_triggered) self.ui.actionSpectrum_Analyzer.triggered.connect(self.on_show_spectrum_dialog_action_triggered) self.ui.actionOptions.triggered.connect(self.show_options_dialog_action_triggered) self.ui.actionSniff_protocol.triggered.connect(self.show_proto_sniff_dialog) self.ui.actionAbout_Qt.triggered.connect(QApplication.instance().aboutQt) self.ui.btnFileTreeGoUp.clicked.connect(self.on_btn_file_tree_go_up_clicked) self.ui.fileTree.directory_open_wanted.connect(self.project_manager.set_project_folder) self.signal_tab_controller.frame_closed.connect(self.close_signal_frame) self.signal_tab_controller.signal_created.connect(self.add_signal) self.signal_tab_controller.ui.scrollArea.files_dropped.connect(self.on_files_dropped) self.signal_tab_controller.files_dropped.connect(self.on_files_dropped) self.signal_tab_controller.frame_was_dropped.connect(self.set_frame_numbers) self.compare_frame_controller.show_interpretation_clicked.connect( self.show_protocol_selection_in_interpretation) self.compare_frame_controller.files_dropped.connect(self.on_files_dropped) self.compare_frame_controller.show_decoding_clicked.connect(self.on_show_decoding_dialog_triggered) self.compare_frame_controller.ui.treeViewProtocols.files_dropped_on_group.connect( self.on_files_dropped_on_group) self.compare_frame_controller.participant_changed.connect(self.signal_tab_controller.on_participant_changed) self.compare_frame_controller.ui.treeViewProtocols.close_wanted.connect(self.on_cfc_close_wanted) self.compare_frame_controller.show_config_field_types_triggered.connect( self.on_show_field_types_config_action_triggered) self.ui.lnEdtTreeFilter.textChanged.connect(self.on_file_tree_filter_text_changed) self.ui.tabWidget.currentChanged.connect(self.on_selected_tab_changed) self.project_save_timer.timeout.connect(self.project_manager.saveProject) self.ui.actionConvert_Folder_to_Project.triggered.connect(self.project_manager.convert_folder_to_project) self.project_manager.project_loaded_status_changed.connect(self.ui.actionProject_settings.setVisible) self.project_manager.project_loaded_status_changed.connect(self.ui.actionSave_project.setVisible) self.project_manager.project_loaded_status_changed.connect(self.ui.actionConvert_Folder_to_Project.setDisabled) self.project_manager.project_updated.connect(self.on_project_updated) self.ui.textEditProjectDescription.textChanged.connect(self.on_text_edit_project_description_text_changed) self.ui.tabWidget_Project.tabBarDoubleClicked.connect(self.on_project_tab_bar_double_clicked) self.ui.listViewParticipants.doubleClicked.connect(self.on_project_settings_action_triggered) self.ui.actionShowFileTree.triggered.connect(self.on_action_show_filetree_triggered) self.ui.actionShowFileTree.setShortcut(QKeySequence("F10")) self.ui.menuFile.addSeparator() for i in range(constants.MAX_RECENT_FILE_NR): recent_file_action = QAction(self) recent_file_action.setVisible(False) recent_file_action.triggered.connect(self.on_open_recent_action_triggered) self.recentFileActionList.append(recent_file_action) self.ui.menuFile.addAction(self.recentFileActionList[i]) def add_plain_bits_from_txt(self, filename: str): protocol = ProtocolAnalyzer(None) protocol.filename = filename with open(filename) as f: for line in f: protocol.messages.append(Message.from_plain_bits_str(line.strip())) self.compare_frame_controller.add_protocol(protocol) self.compare_frame_controller.refresh() self.__add_empty_frame_for_filename(protocol, filename) def __add_empty_frame_for_filename(self, protocol: ProtocolAnalyzer, filename: str): sf = self.signal_tab_controller.add_empty_frame(filename, protocol) self.signal_protocol_dict[sf] = protocol self.set_frame_numbers() self.file_proxy_model.open_files.add(filename) def add_protocol_file(self, filename): proto = self.compare_frame_controller.add_protocol_from_file(filename) if proto: self.__add_empty_frame_for_filename(proto, filename) def add_fuzz_profile(self, filename): self.ui.tabWidget.setCurrentIndex(2) self.generator_tab_controller.load_from_file(filename) def add_signalfile(self, filename: str, group_id=0): if not os.path.exists(filename): QMessageBox.critical(self, self.tr("File not Found"), self.tr("The file {0} could not be found. Was it moved or renamed?").format( filename)) return already_qad_demodulated = False if filename.endswith(".wav"): cb = QCheckBox("Signal in file is already quadrature demodulated") msg = self.tr("You selected a .wav file as signal.\n" "Universal Radio Hacker (URH) will interpret it as real part of the signal.\n" "Protocol results may be bad due to missing imaginary part.\n\n" "Load a complex file if you experience problems.\n" "You have been warned.") msg_box = QMessageBox(QMessageBox.Information, "WAV file selected", msg) msg_box.addButton(QMessageBox.Ok) msg_box.addButton(QMessageBox.Abort) msg_box.setCheckBox(cb) reply = msg_box.exec() if reply != QMessageBox.Ok: return already_qad_demodulated = cb.isChecked() sig_name = os.path.splitext(os.path.basename(filename))[0] # Use default sample rate for signal # Sample rate will be overriden in case of a project later signal = Signal(filename, sig_name, wav_is_qad_demod=already_qad_demodulated, sample_rate=self.project_manager.device_conf["sample_rate"]) if self.project_manager.project_file is None: self.adjust_for_current_file(signal.filename) self.file_proxy_model.open_files.add(filename) self.add_signal(signal, group_id) def add_signal(self, signal, group_id=0): self.setCursor(Qt.WaitCursor) pa = ProtocolAnalyzer(signal) sig_frame = self.signal_tab_controller.add_signal_frame(pa) pa = self.compare_frame_controller.add_protocol(pa, group_id) signal.blockSignals(True) has_entry = self.project_manager.read_project_file_for_signal(signal) if not has_entry: signal.auto_detect() signal.blockSignals(False) self.signal_protocol_dict[sig_frame] = pa sig_frame.refresh(draw_full_signal=True) # Hier wird das Protokoll ausgelesen if self.project_manager.read_participants_for_signal(signal, pa.messages): sig_frame.ui.gvSignal.redraw_view() sig_frame.ui.gvSignal.auto_fit_view() self.set_frame_numbers() self.compare_frame_controller.filter_search_results() self.refresh_main_menu() self.unsetCursor() def close_protocol(self, protocol): self.compare_frame_controller.remove_protocol(protocol) # Needs to be removed in generator also, otherwise program crashes, # if item from tree in generator is selected and corresponding signal is closed self.generator_tab_controller.tree_model.remove_protocol(protocol) protocol.eliminate() def close_signal_frame(self, signal_frame: SignalFrameController): try: self.project_manager.write_signal_information_to_project_file(signal_frame.signal) try: proto = self.signal_protocol_dict[signal_frame] except KeyError: proto = None if proto is not None: self.close_protocol(proto) del self.signal_protocol_dict[signal_frame] if self.signal_tab_controller.ui.scrlAreaSignals.minimumHeight() > signal_frame.height(): self.signal_tab_controller.ui.scrlAreaSignals.setMinimumHeight( self.signal_tab_controller.ui.scrlAreaSignals.minimumHeight() - signal_frame.height()) if signal_frame.signal is not None: # Non-Empty Frame (when a signal and not a protocol is opened) self.file_proxy_model.open_files.discard(signal_frame.signal.filename) signal_frame.eliminate() self.compare_frame_controller.ui.treeViewProtocols.expandAll() self.set_frame_numbers() self.refresh_main_menu() except Exception as e: Errors.generic_error(self.tr("Failed to close"), str(e), traceback.format_exc()) self.unsetCursor() def add_files(self, filepaths, group_id=0): num_files = len(filepaths) if num_files == 0: return for i, file in enumerate(filepaths): if not os.path.exists(file): continue if os.path.isdir(file): for f in self.signal_tab_controller.signal_frames: self.close_signal_frame(f) FileOperator.RECENT_PATH = file self.project_manager.set_project_folder(file) return _, file_extension = os.path.splitext(file) FileOperator.RECENT_PATH = os.path.split(file)[0] if file_extension == ".complex": self.add_signalfile(file, group_id) elif file_extension == ".coco": self.add_signalfile(file, group_id) elif file_extension == ".proto": self.add_protocol_file(file) elif file_extension == ".wav": self.add_signalfile(file, group_id) elif file_extension == ".fuzz": self.add_fuzz_profile(file) elif file_extension == ".txt": self.add_plain_bits_from_txt(file) else: self.add_signalfile(file, group_id) def set_frame_numbers(self): self.signal_tab_controller.set_frame_numbers() def closeEvent(self, event: QCloseEvent): self.project_manager.saveProject() super().closeEvent(event) def close_all(self): self.filemodel.setRootPath(QDir.homePath()) self.ui.fileTree.setRootIndex(self.file_proxy_model.mapFromSource(self.filemodel.index(QDir.homePath()))) self.project_manager.saveProject() self.signal_tab_controller.close_all() self.compare_frame_controller.reset() self.generator_tab_controller.table_model.protocol.clear() self.generator_tab_controller.refresh_tree() self.generator_tab_controller.refresh_table() self.generator_tab_controller.refresh_label_list() self.project_manager.project_path = "" self.project_manager.project_file = None self.signal_tab_controller.signal_undo_stack.clear() self.compare_frame_controller.protocol_undo_stack.clear() self.generator_tab_controller.generator_undo_stack.clear() def show_options_dialog_specific_tab(self, tab_index: int): op = OptionsController(self.plugin_manager.installed_plugins, parent=self) op.values_changed.connect(self.on_options_changed) op.ui.tabWidget.setCurrentIndex(tab_index) op.show() def refresh_main_menu(self): enable = len(self.signal_protocol_dict) > 0 self.ui.actionSaveAllSignals.setEnabled(enable) self.ui.actionClose_all.setEnabled(enable) def apply_default_view(self, view_index: int): self.compare_frame_controller.ui.cbProtoView.setCurrentIndex(view_index) self.generator_tab_controller.ui.cbViewType.setCurrentIndex(view_index) for sig_frame in self.signal_tab_controller.signal_frames: sig_frame.ui.cbProtoView.setCurrentIndex(view_index) def show_project_settings(self): pdc = ProjectDialogController(new_project=False, project_manager=self.project_manager, parent=self) pdc.finished.connect(self.on_project_dialog_finished) pdc.show() def collapse_project_tab_bar(self): self.ui.tabParticipants.hide() self.ui.tabDescription.hide() self.ui.tabWidget_Project.setMaximumHeight(self.ui.tabWidget_Project.tabBar().height()) def expand_project_tab_bar(self): self.ui.tabDescription.show() self.ui.tabParticipants.show() self.ui.tabWidget_Project.setMaximumHeight(9000) @pyqtSlot() def on_project_tab_bar_double_clicked(self): if self.ui.tabParticipants.isVisible(): self.collapse_project_tab_bar() else: self.expand_project_tab_bar() @pyqtSlot() def on_project_updated(self): self.participant_legend_model.participants = self.project_manager.participants self.participant_legend_model.update() self.compare_frame_controller.refresh() self.ui.textEditProjectDescription.setText(self.project_manager.description) @pyqtSlot() def on_fullscreen_action_triggered(self): if self.ui.actionFullscreen_mode.isChecked(): self.showFullScreen() else: self.showMaximized() @pyqtSlot(str) def adjust_for_current_file(self, file_path): if file_path is None: return if file_path in FileOperator.archives.keys(): file_path = copy.copy(FileOperator.archives[file_path]) settings = constants.SETTINGS recent_file_paths = settings.value("recentFiles", []) recent_file_paths = [] if recent_file_paths is None else recent_file_paths # check None for OSX recent_file_paths = [p for p in recent_file_paths if p != file_path and p is not None and os.path.exists(p)] recent_file_paths.insert(0, file_path) recent_file_paths = recent_file_paths[:constants.MAX_RECENT_FILE_NR] self.init_recent_file_action_list(recent_file_paths) settings.setValue("recentFiles", recent_file_paths) def init_recent_file_action_list(self, recent_file_paths: list): for i in range(len(self.recentFileActionList)): self.recentFileActionList[i].setVisible(False) if recent_file_paths is None: return for i, file_path in enumerate(recent_file_paths): if os.path.isfile(file_path): display_text = os.path.basename(file_path) self.recentFileActionList[i].setIcon(QIcon()) elif os.path.isdir(file_path): head, tail = os.path.split(file_path) display_text = tail head, tail = os.path.split(head) if tail: display_text = tail + "/" + display_text self.recentFileActionList[i].setIcon(QIcon.fromTheme("folder")) else: continue self.recentFileActionList[i].setText(display_text) self.recentFileActionList[i].setData(file_path) self.recentFileActionList[i].setVisible(True) @pyqtSlot() def on_show_field_types_config_action_triggered(self): self.show_options_dialog_specific_tab(tab_index=2) @pyqtSlot() def on_open_recent_action_triggered(self): action = self.sender() try: if os.path.isdir(action.data()): self.project_manager.set_project_folder(action.data()) elif os.path.isfile(action.data()): self.setCursor(Qt.WaitCursor) self.add_files(FileOperator.uncompress_archives([action.data()], QDir.tempPath())) self.unsetCursor() except Exception as e: Errors.generic_error(self.tr("Failed to open"), str(e), traceback.format_exc()) self.unsetCursor() @pyqtSlot() def on_show_about_clicked(self): descr = "<b><h2>Universal Radio Hacker</h2></b>Version: {0}<br />" \ "GitHub: <a href='https://github.com/jopohl/urh'>https://github.com/jopohl/urh</a><br /><br />" \ "Creators:<i><ul><li>" \ "Johannes Pohl <<a href='mailto:[email protected]'>[email protected]</a>></li>" \ "<li>Andreas Noack <<a href='mailto:[email protected]'>[email protected]</a>></li>" \ "</ul></i>".format(version.VERSION) QMessageBox.about(self, self.tr("About"), self.tr(descr)) @pyqtSlot(int, int, int, int) def show_protocol_selection_in_interpretation(self, start_message, start, end_message, end): cfc = self.compare_frame_controller msg_total = 0 last_sig_frame = None for protocol in cfc.protocol_list: if not protocol.show: continue n = protocol.num_messages view_type = cfc.ui.cbProtoView.currentIndex() messages = [i - msg_total for i in range(msg_total, msg_total + n) if start_message <= i <= end_message] if len(messages) > 0: try: signal_frame = next((sf for sf, pf in self.signal_protocol_dict.items() if pf == protocol)) except StopIteration: QMessageBox.critical(self, self.tr("Error"), self.tr("Could not find corresponding signal frame.")) return signal_frame.set_roi_from_protocol_analysis(min(messages), start, max(messages), end + 1, view_type) last_sig_frame = signal_frame msg_total += n focus_frame = last_sig_frame if last_sig_frame is not None: self.signal_tab_controller.ui.scrollArea.ensureWidgetVisible(last_sig_frame, 0, 0) QApplication.instance().processEvents() self.ui.tabWidget.setCurrentIndex(0) if focus_frame is not None: focus_frame.ui.txtEdProto.setFocus() @pyqtSlot(str) def on_file_tree_filter_text_changed(self, text: str): if len(text) > 0: self.filemodel.setNameFilters(["*" + text + "*"]) else: self.filemodel.setNameFilters(["*"]) @pyqtSlot() def on_show_decoding_dialog_triggered(self): signals = [sf.signal for sf in self.signal_tab_controller.signal_frames] decoding_controller = DecoderWidgetController( self.compare_frame_controller.decodings, signals, self.project_manager, parent=self) decoding_controller.finished.connect(self.update_decodings) decoding_controller.show() decoding_controller.decoder_update() @pyqtSlot() def update_decodings(self): self.compare_frame_controller.load_decodings() self.compare_frame_controller.fill_decoding_combobox() self.compare_frame_controller.refresh_existing_encodings() self.generator_tab_controller.refresh_existing_encodings(self.compare_frame_controller.decodings) @pyqtSlot(int) def on_selected_tab_changed(self, index: int): if index == 0: self.undo_group.setActiveStack(self.signal_tab_controller.signal_undo_stack) elif index == 1: self.undo_group.setActiveStack(self.compare_frame_controller.protocol_undo_stack) self.compare_frame_controller.ui.tblViewProtocol.resize_columns() self.compare_frame_controller.ui.tblViewProtocol.resize_vertical_header() h = max(self.compare_frame_controller.ui.btnSaveProto.height(), self.generator_tab_controller.ui.btnSave.height()) self.compare_frame_controller.ui.btnSaveProto.setMinimumHeight(h) th = self.compare_frame_controller.ui.tabWidget.tabBar().height() for i in range(self.compare_frame_controller.ui.tabWidget.count()): self.compare_frame_controller.ui.tabWidget.widget(i).layout().setContentsMargins(0, 7 + h - th, 0, 0) elif index == 2: self.undo_group.setActiveStack(self.generator_tab_controller.generator_undo_stack) h = max(self.compare_frame_controller.ui.btnSaveProto.height(), self.generator_tab_controller.ui.btnSave.height()) self.generator_tab_controller.ui.btnSave.setMinimumHeight(h) th = self.generator_tab_controller.ui.tabWidget.tabBar().height() for i in range(self.generator_tab_controller.ui.tabWidget.count()): self.generator_tab_controller.ui.tabWidget.widget(i).layout().setContentsMargins(0, 7 + h - th, 0, 0) @pyqtSlot() def on_show_record_dialog_action_triggered(self): pm = self.project_manager try: r = ReceiveDialogController(pm, parent=self) except OSError as e: logger.error(repr(e)) return if r.has_empty_device_list: Errors.no_device() r.close() return r.recording_parameters.connect(pm.set_recording_parameters) r.files_recorded.connect(self.on_signals_recorded) r.show() @pyqtSlot() def show_proto_sniff_dialog(self): pm = self.project_manager signal = next((proto.signal for proto in self.compare_frame_controller.protocol_list), None) bit_len = signal.bit_len if signal else 100 mod_type = signal.modulation_type if signal else 1 tolerance = signal.tolerance if signal else 5 noise = signal.noise_threshold if signal else 0.001 center = signal.qad_center if signal else 0.02 psd = ProtocolSniffDialogController(pm, noise, center, bit_len, tolerance, mod_type, self.compare_frame_controller.decodings, encoding_index=self.compare_frame_controller.ui.cbDecoding.currentIndex(), parent=self) if psd.has_empty_device_list: Errors.no_device() psd.close() else: psd.recording_parameters.connect(pm.set_recording_parameters) psd.protocol_accepted.connect(self.compare_frame_controller.add_sniffed_protocol_messages) psd.show() @pyqtSlot() def on_show_spectrum_dialog_action_triggered(self): pm = self.project_manager r = SpectrumDialogController(pm, parent=self) if r.has_empty_device_list: Errors.no_device() r.close() return r.recording_parameters.connect(pm.set_recording_parameters) r.show() @pyqtSlot(list) def on_signals_recorded(self, file_names: list): QApplication.instance().setOverrideCursor(Qt.WaitCursor) for filename in file_names: self.add_signalfile(filename) QApplication.instance().restoreOverrideCursor() @pyqtSlot() def show_options_dialog_action_triggered(self): self.show_options_dialog_specific_tab(tab_index=0) @pyqtSlot() def on_new_project_action_triggered(self): pdc = ProjectDialogController(parent=self) pdc.finished.connect(self.on_project_dialog_finished) pdc.show() @pyqtSlot() def on_project_settings_action_triggered(self): self.show_project_settings() @pyqtSlot() def on_edit_menu_about_to_show(self): self.ui.actionShowFileTree.setChecked(self.ui.splitter.sizes()[0] > 0) @pyqtSlot() def on_action_show_filetree_triggered(self): if self.ui.splitter.sizes()[0] > 0: self.ui.splitter.setSizes([0, 1]) else: self.ui.splitter.setSizes([1, 1]) @pyqtSlot() def on_project_dialog_finished(self): if self.sender().committed: if self.sender().new_project: for f in self.signal_tab_controller.signal_frames: self.close_signal_frame(f) self.project_manager.from_dialog(self.sender()) @pyqtSlot() def on_open_file_action_triggered(self): self.show_open_dialog(directory=False) @pyqtSlot() def on_open_directory_action_triggered(self): self.show_open_dialog(directory=True) def show_open_dialog(self, directory=False): fip = FileIconProvider() self.dialog = QFileDialog(self) self.dialog.setIconProvider(fip) self.dialog.setDirectory(FileOperator.RECENT_PATH) self.dialog.setWindowTitle("Open Folder") if directory: self.dialog.setFileMode(QFileDialog.Directory) else: self.dialog.setFileMode(QFileDialog.ExistingFiles) self.dialog.setNameFilter( "All files (*);;Complex (*.complex);;Complex16 unsigned (*.complex16u);;Complex16 signed (*.complex16s);;Wave (*.wav);;Protocols (*.proto);;" "Fuzzprofiles (*.fuzz);;Tar Archives (*.tar *.tar.gz *.tar.bz2);;Zip Archives (*.zip)") self.dialog.setOptions(QFileDialog.DontResolveSymlinks) self.dialog.setViewMode(QFileDialog.Detail) if self.dialog.exec_(): try: file_names = self.dialog.selectedFiles() folders = [folder for folder in file_names if os.path.isdir(folder)] if len(folders) > 0: folder = folders[0] for f in self.signal_tab_controller.signal_frames: self.close_signal_frame(f) self.project_manager.set_project_folder(folder) else: self.setCursor(Qt.WaitCursor) file_names = FileOperator.uncompress_archives(file_names, QDir.tempPath()) self.add_files(file_names) self.unsetCursor() except Exception as e: Errors.generic_error(self.tr("Failed to open"), str(e), traceback.format_exc()) QApplication.instance().restoreOverrideCursor() @pyqtSlot() def on_close_all_action_triggered(self): self.close_all() @pyqtSlot(list) def on_files_dropped(self, files): """ :type files: list of QtCore.QUrl """ self.__add_urls_to_group(files, group_id=0) @pyqtSlot(list, int) def on_files_dropped_on_group(self, files, group_id: int): """ :param group_id: :type files: list of QtCore.QUrl """ self.__add_urls_to_group(files, group_id=group_id) def __add_urls_to_group(self, file_urls, group_id=0): local_files = [file_url.toLocalFile() for file_url in file_urls if file_url.isLocalFile()] if len(local_files) > 0: self.setCursor(Qt.WaitCursor) self.add_files(FileOperator.uncompress_archives(local_files, QDir.tempPath()), group_id=group_id) self.unsetCursor() @pyqtSlot(list) def on_cfc_close_wanted(self, protocols: list): frame_protos = {sframe: protocol for sframe, protocol in self.signal_protocol_dict.items() if protocol in protocols} for frame in frame_protos: self.close_signal_frame(frame) for proto in (proto for proto in protocols if proto not in frame_protos.values()): # close protocols without associated signal frame self.close_protocol(proto) @pyqtSlot(dict) def on_options_changed(self, changed_options: dict): refresh_protocol_needed = "show_pause_as_time" in changed_options if refresh_protocol_needed: for sf in self.signal_tab_controller.signal_frames: sf.refresh_protocol() self.compare_frame_controller.refresh_field_types_for_labels() self.compare_frame_controller.set_shown_protocols() self.generator_tab_controller.set_network_sdr_send_button_visibility() self.generator_tab_controller.init_rfcat_plugin() if "default_view" in changed_options: self.apply_default_view(int(changed_options["default_view"])) @pyqtSlot() def on_text_edit_project_description_text_changed(self): self.project_manager.description = self.ui.textEditProjectDescription.toPlainText() @pyqtSlot() def on_btn_file_tree_go_up_clicked(self): cur_dir = self.filemodel.rootDirectory() if cur_dir.cdUp(): path = cur_dir.path() self.filemodel.setRootPath(path) self.ui.fileTree.setRootIndex(self.file_proxy_model.mapFromSource(self.filemodel.index(path)))
class MainController(QMainWindow): resized = pyqtSignal() def __init__(self, *args): super().__init__(*args) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.project_save_timer = QTimer() self.project_manager = ProjectManager(self) self.plugin_manager = PluginManager() self.signal_tab_controller = SignalTabController(self.project_manager, parent=self.ui.tab_interpretation) self.ui.tab_interpretation.layout().addWidget(self.signal_tab_controller) self.compare_frame_controller = CompareFrameController(parent=self.ui.tab_protocol, plugin_manager=self.plugin_manager, project_manager=self.project_manager) self.ui.tab_protocol.layout().addWidget(self.compare_frame_controller) self.generator_tab_controller = GeneratorTabController(self.compare_frame_controller, self.project_manager, self.compare_frame_controller.decodings, parent=self.ui.tab_generator) self.simulator_tab_controller = SimulatorTabController(parent=self.ui.tab_simulator, compare_frame_controller=self.compare_frame_controller, generator_tab_controller=self.generator_tab_controller, project_manager=self.project_manager) self.ui.tab_simulator.layout().addWidget(self.simulator_tab_controller) self.undo_group = QUndoGroup() self.undo_group.addStack(self.signal_tab_controller.signal_undo_stack) self.undo_group.addStack(self.compare_frame_controller.protocol_undo_stack) self.undo_group.addStack(self.generator_tab_controller.generator_undo_stack) self.undo_group.setActiveStack(self.signal_tab_controller.signal_undo_stack) self.ui.progressBar.hide() gtc = self.generator_tab_controller gtc.ui.splitter.setSizes([gtc.width() / 0.7, gtc.width() / 0.3]) self.ui.tab_generator.layout().addWidget(self.generator_tab_controller) self.signal_protocol_dict = {} """:type: dict[SignalFrameController,ProtocolAnalyzer]""" self.signal_tab_controller.ui.lLoadingFile.setText("") self.ui.lnEdtTreeFilter.setClearButtonEnabled(True) group = QActionGroup(self) self.ui.actionFSK.setActionGroup(group) self.ui.actionOOK.setActionGroup(group) self.ui.actionNone.setActionGroup(group) self.ui.actionPSK.setActionGroup(group) self.update_confirm_dialogs_checkbox() self.set_hold_shift_checkbox() self.signal_tab_controller.ui.lCtrlStatus.clear() self.signal_tab_controller.ui.lShiftStatus.clear() self.recentFileActionList = [] self.create_connects() self.updateRecentActionList() OptionsController.write_default_options() self.filemodel = FileSystemModel(self) path = QDir.homePath() self.filemodel.setIconProvider(FileIconProvider()) self.filemodel.setRootPath(path) self.file_proxy_model = FileFilterProxyModel(self) self.file_proxy_model.setSourceModel(self.filemodel) self.ui.fileTree.setModel(self.file_proxy_model) self.ui.fileTree.setRootIndex(self.file_proxy_model.mapFromSource(self.filemodel.index(path))) self.ui.fileTree.setToolTip(path) self.ui.fileTree.header().setSectionResizeMode(0, QHeaderView.Stretch) self.ui.fileTree.header().setSectionResizeMode(1, QHeaderView.Interactive) self.ui.fileTree.setFocus() self.generator_tab_controller.table_model.cfc = self.compare_frame_controller self.ui.actionConvert_Folder_to_Project.setEnabled(False) undo_action = self.undo_group.createUndoAction(self) undo_action.setIcon(QIcon.fromTheme("edit-undo")) undo_action.setShortcut(QKeySequence.Undo) self.ui.menuEdit.insertAction(self.ui.actionMinimize_all, undo_action) redo_action = self.undo_group.createRedoAction(self) redo_action.setIcon(QIcon.fromTheme("edit-redo")) redo_action.setShortcut(QKeySequence.Redo) self.ui.splitter.setSizes([0, 1]) self.ui.menuEdit.insertAction(self.ui.actionMinimize_all, redo_action) self.refresh_main_menu() self.apply_default_view() self.project_save_timer.start(ProjectManager.AUTOSAVE_INTERVAL_MINUTES * 60 * 1000) def create_connects(self): self.ui.menuView.aboutToShow.connect(self.on_menu_view_clicked) self.ui.actionNew_Project.triggered.connect(self.on_new_project_clicked) self.ui.actionShow_file_tree.triggered.connect(self.on_show_file_tree_clicked) self.ui.actionCommon_Zoom.setShortcut(QKeySequence(Qt.SHIFT + Qt.Key_Z)) self.ui.actionShow_Confirm_Close_Dialog.triggered.connect(self.update_confirm_dialogs_settings) self.ui.actionHold_Shift_to_Drag.triggered.connect(self.set_hold_shift) self.ui.actionAbout_AutomaticHacker.triggered.connect(self.show_about) self.ui.actionRecord.triggered.connect(self.show_record_dialog) self.signal_tab_controller.frame_closed.connect(self.close_signal_frame) self.signal_tab_controller.not_show_again_changed.connect(self.update_confirm_dialogs_checkbox) self.signal_tab_controller.signal_created.connect(self.add_signal) self.compare_frame_controller.show_interpretation_clicked.connect( self.show_protocol_selection_in_interpretation) self.signal_tab_controller.ui.scrollArea.files_dropped.connect(self.handle_files_dropped) self.signal_tab_controller.files_dropped.connect(self.handle_files_dropped) self.compare_frame_controller.files_dropped.connect(self.handle_files_dropped) self.signal_tab_controller.frame_was_dropped.connect(self.set_frame_numbers) self.ui.actionMinimize_all.triggered.connect(self.minimize_all) self.ui.actionMaximize_all.triggered.connect(self.maximize_all) self.ui.actionSaveAllSignals.triggered.connect(self.signal_tab_controller.save_all) self.ui.actionClose_all.triggered.connect(self.close_all) self.ui.actionSeperate_Protocols_in_Compare_Frame.triggered.connect(self.handle_compare_frame_seperation_changed) self.ui.actionOpen.triggered.connect(self.open) self.ui.actionOpen.setShortcut(QKeySequence(QKeySequence.Open)) self.ui.actionMinimize_all.setShortcut("F10") self.ui.actionMaximize_all.setShortcut("F11") self.ui.lnEdtTreeFilter.textChanged.connect(self.handle_filtetree_filter_text_changed) self.ui.actionSort_Frames_by_Name.triggered.connect(self.sort_frames_by_name) self.ui.actionConvert_Folder_to_Project.triggered.connect(self.project_manager.convert_folder_to_project) self.ui.actionDecoding.triggered.connect(self.show_decoding_dialog) self.compare_frame_controller.show_decoding_clicked.connect(self.show_decoding_dialog) self.ui.tabWidget.currentChanged.connect(self.on_selected_tab_changed) self.ui.actionSpectrum_Analyzer.triggered.connect(self.show_spectrum_dialog) self.ui.actionOptions.triggered.connect(self.show_options_dialog) self.project_save_timer.timeout.connect(self.project_manager.saveProject) self.ui.actionSniff_protocol.triggered.connect( self.compare_frame_controller.show_proto_sniff_dialog) self.compare_frame_controller.ui.treeViewProtocols.files_dropped_on_group.connect( self.handle_files_dropped) self.ui.menuFile.addSeparator() for i in range(constants.MAX_RECENT_FILE_NR): recentFileAction = QAction(self) recentFileAction.setVisible(False) recentFileAction.triggered.connect(self.openRecent) self.recentFileActionList.append(recentFileAction) self.ui.menuFile.addAction(self.recentFileActionList[i]) @pyqtSlot() def update_confirm_dialogs_checkbox(self): settings = constants.SETTINGS try: not_show = settings.value('not_show_close_dialog', type=bool) except TypeError: not_show = False self.ui.actionShow_Confirm_Close_Dialog.setChecked(not not_show) @pyqtSlot() def update_confirm_dialogs_settings(self): settings = constants.SETTINGS settings.setValue('not_show_close_dialog', not self.ui.actionShow_Confirm_Close_Dialog.isChecked()) @pyqtSlot() def on_menu_view_clicked(self): file_tree_shown = self.ui.splitter.sizes()[0] > 0 self.ui.actionShow_file_tree.setChecked(file_tree_shown) @pyqtSlot() def on_show_file_tree_clicked(self): show = self.ui.actionShow_file_tree.isChecked() if show: self.ui.splitter.setSizes([1, 1]) else: self.ui.splitter.setSizes([0, 1]) def set_hold_shift_checkbox(self): settings = constants.SETTINGS if 'hold_shift_to_drag' in settings.allKeys(): hold_shift = settings.value('hold_shift_to_drag', type=bool) else: hold_shift = True settings.setValue('hold_shift_to_drag', hold_shift) self.ui.actionHold_Shift_to_Drag.setChecked(hold_shift) @pyqtSlot() def set_hold_shift(self): hs = self.ui.actionHold_Shift_to_Drag.isChecked() constants.SETTINGS.setValue('hold_shift_to_drag', hs) @pyqtSlot() def open(self): fip = FileIconProvider() self.dialog = QFileDialog(self) #tree = self.dialog.findChild(QTreeView) #""":type: QTreeView """ #tree.setRootIsDecorated(True) #tree.setItemsExpandable(True) # Damit SignalClick Behavior auch geht. Nachteil: Man muss immer den Open Button drücken #tree.blockSignals(True) self.dialog.setIconProvider(fip) self.dialog.setDirectory(FileOperator.RECENT_PATH) self.dialog.setWindowTitle("Open Folder") self.dialog.setFileMode(QFileDialog.ExistingFiles) self.dialog.setOptions(QFileDialog.DontUseNativeDialog | QFileDialog.DontResolveSymlinks) self.dialog.setViewMode(QFileDialog.Detail) self.dialog.setNameFilter( "All files (*);;Complex Files *.complex (*.complex);;Wav Files *.wav (*.wav);;Protocols *.txt (*.txt);;" "Tar Archives (*.tar *.tar.gz *.tar.bz2);;Zip Archives (*.zip)") self.dialog.currentChanged.connect(self.handle_dialog_selection_changed) if self.dialog.exec_(): fileNames = self.dialog.selectedFiles() folders = [folder for folder in fileNames if os.path.isdir(folder)] if len(folders) > 0: folder = folders[0] for f in self.signal_tab_controller.signal_frames: self.close_signal_frame(f) self.project_manager.set_project_folder(folder) else: fileNames = FileOperator.uncompress_archives(fileNames, QDir.tempPath()) self.add_files(fileNames) @pyqtSlot(str) def handle_dialog_selection_changed(self, path: str): if os.path.isdir(path): self.dialog.setFileMode(QFileDialog.Directory) self.dialog.setNameFilter( "All files (*);;Complex Files *.complex (*.complex);;Wav Files *.wav (*.wav);;Protocols *.txt (*.txt);;" "Tar Archives (*.tar *.tar.gz *.tar.bz2);;Zip Archives (*.zip)") else: self.dialog.setFileMode(QFileDialog.ExistingFiles) self.dialog.setNameFilter( "All files (*);;Complex Files *.complex (*.complex);;Wav Files *.wav (*.wav);;Protocols *.txt (*.txt);;" "Tar Archives (*.tar *.tar.gz *.tar.bz2);;Zip Archives (*.zip)") def add_protocol_file(self, filename): protolist = self.compare_frame_controller.add_protocol_from_file(filename) protolist = protolist if protolist is not None else [] for pa in protolist: sf = self.signal_tab_controller.add_empty_frame(filename, pa.decoded_proto_bits_str) self.signal_protocol_dict[sf] = pa self.set_frame_numbers() self.file_proxy_model.open_files.add(filename) def add_signalfile(self, filename: str, group_id=0): if not os.path.exists(filename): QMessageBox.critical(self, self.tr("File not Found"), self.tr("The file {0} could not be found. Was it moved or renamed?").format( filename)) return alrdy_qad_demod = False if filename.endswith(".wav"): accept, alrdy_qad_demod = WavFileDialog.dialog(self) if not accept: return sig_name = os.path.splitext(os.path.basename(filename))[0] # Use default sample rate for signal # Sample rate will be overriden in case of a project later signal = Signal(filename, sig_name, wav_is_qad_demod=alrdy_qad_demod, sample_rate=self.project_manager.sample_rate) if self.project_manager.project_file is None: self.adjustForCurrentFile(signal.filename) self.file_proxy_model.open_files.add(filename) self.add_signal(signal, group_id) def add_signal(self, signal, group_id=0): self.ui.progressBar.setMaximum(100) self.ui.progressBar.show() pa = ProtocolAnalyzer(signal) sframe = self.signal_tab_controller.add_signal_frame(pa) self.ui.progressBar.setValue(10) QApplication.processEvents() pa = self.compare_frame_controller.add_protocol(pa, group_id) self.ui.progressBar.setValue(20) QApplication.processEvents() signal.blockSignals(True) has_entry = self.project_manager.read_project_file_for_signal(signal)