Beispiel #1
0
 def __init__(self,
              debug=False,
              DbOnly=False,
              doc=False,
              arch='vxWorks-ppc604_long'):
     # This is a default architecture
     self.architecture = arch
     self.simarch = False
     self.epics_base = None
     self.build_root = '.'
     self.iocname = 'example'
     # This the group of undo stacks for each table
     self.stack = QUndoGroup()
     # this is a dict of tables
     self._tables = {}
     self._tableNames = []
     # store the debug state
     self.debug = debug
     self.DbOnly = DbOnly
     self.doc = doc
     self.setLastModified()
     # this is the list of objects provided by this module, for documentation
     self.moduleObjects = []
     # store the arch and tableNames so we know we're clean
     self.setStored()
Beispiel #2
0
    def __init__(self, parent=None):
        super().__init__(parent)

        self.mDocuments = QList()
        self.mTabWidget = MovableTabWidget()
        self.mUndoGroup = QUndoGroup(self)
        self.mSelectedTool = None
        self.mViewWithTool = None
        self.mFileSystemWatcher = FileSystemWatcher(self)

        self.mTabWidget.setDocumentMode(True)
        self.mTabWidget.setTabsClosable(True)
        self.mTabWidget.currentChanged.connect(self.currentIndexChanged)
        self.mTabWidget.tabCloseRequested.connect(self.documentCloseRequested)
        self.mTabWidget.tabMoved.connect(self.documentTabMoved)
        self.mFileSystemWatcher.fileChanged.connect(self.fileChanged)
Beispiel #3
0
    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()
Beispiel #4
0
    def __init__(self, parent = None):
        super().__init__(parent)

        self.mDocuments = QList()
        self.mTabWidget = MovableTabWidget()
        self.mUndoGroup = QUndoGroup(self)
        self.mSelectedTool = None
        self.mViewWithTool = None
        self.mFileSystemWatcher = FileSystemWatcher(self)

        self.mTabWidget.setDocumentMode(True)
        self.mTabWidget.setTabsClosable(True)
        self.mTabWidget.currentChanged.connect(self.currentIndexChanged)
        self.mTabWidget.tabCloseRequested.connect(self.documentCloseRequested)
        self.mTabWidget.tabMoved.connect(self.documentTabMoved)
        self.mFileSystemWatcher.fileChanged.connect(self.fileChanged)
Beispiel #5
0
    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)
Beispiel #6
0
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 &lt;<a href='mailto:[email protected]'>[email protected]</a>&gt;</li>" \
                "<li>Andreas Noack &lt;<a href='mailto:[email protected]'>[email protected]</a>&gt;</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_()
Beispiel #7
0
    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)
Beispiel #8
0
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 &lt;<a href='mailto:[email protected]'>[email protected]</a>&gt;</li>" \
                "<li>Andreas Noack &lt;<a href='mailto:[email protected]'>[email protected]</a>&gt;</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()
Beispiel #9
0
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)
Beispiel #10
0
class Store(object):
    def __init__(self,
                 debug=False,
                 DbOnly=False,
                 doc=False,
                 arch='vxWorks-ppc604_long'):
        # This is a default architecture
        self.architecture = arch
        self.simarch = False
        self.epics_base = None
        self.build_root = '.'
        self.iocname = 'example'
        # This the group of undo stacks for each table
        self.stack = QUndoGroup()
        # this is a dict of tables
        self._tables = {}
        self._tableNames = []
        # store the debug state
        self.debug = debug
        self.DbOnly = DbOnly
        self.doc = doc
        self.setLastModified()
        # this is the list of objects provided by this module, for documentation
        self.moduleObjects = []
        # store the arch and tableNames so we know we're clean
        self.setStored()

    def setStored(self):
        self._storedarchitecture = self.architecture
        self._stored_tableNames = self._tableNames[:]
        for table in list(self._tables.values()):
            table.stack.setClean()

    def isClean(self):
        if self._storedarchitecture != self.architecture:
            return False
        if len(self._stored_tableNames) != len(self._tableNames):
            return False
        for x, y in zip(self._tableNames, self._stored_tableNames):
            if x != y:
                return False
        return True

    def lastModified(self):
        return self._lastModified

    def setLastModified(self, index1=None, index2=None):
        self._lastModified = time.time()

    def New(self, filename=""):
        '''Create a new table list by setting up ModuleVersion calls according
        to paths in release'''
        # First clear up the undo stack
        for stack in self.stack.stacks():
            self.stack.removeStack(stack)
        # then clear the table list
        self._tables.clear()
        # then clear the display list
        self._tableNames = []
        self._stored_tableNames = []
        xml_config = XmlConfig(debug=self.debug,
                               DbOnly=self.DbOnly,
                               doc=self.doc,
                               arch=self.architecture,
                               simarch=self.simarch,
                               filename=filename)
        # create our dict of classes
        classes = xml_config.iocbuilder.includeXml.createClassLookup()
        # now we can make our tables
        for name, o in list(classes.items()):
            # make a table object
            table = Table(o, self)
            # add it to our internal dict of tables
            self._tables[name] = table
            # add the undo stack
            self.stack.addStack(table.stack)
            # connect its modified signal to store a timestamp
            table.dataChanged.connect(self.setLastModified)
        self.setStored()
        # failure if there were no callables
        self.setLastModified()

    def Open(self, filename, sim=None):
        if self.debug:
            print('--- Parsing %s ---' % filename)
        # read the tables
        xml_root = xml.dom.minidom.parse(filename)
        # find the root node
        components = self._elements(xml_root)[0]
        if sim is not None:
            self.architecture = sim
            self.simarch = self.architecture
        else:
            self.architecture = str(components.attributes['arch'].value)
            self.simarch = None
        self.New(filename)
        # proccess each component in turn
        problems = []
        warnings = []
        commentText = ""
        for node in components.childNodes:
            # try to find the class of each component
            if node.nodeType == node.COMMENT_NODE:
                # If it's a comment, then mark as a comment and try to process
                # its content
                commented = True
                text = '<junk>' + node.toxml()[4:-3] + '</junk>'
                root = self._elements(
                    xml.dom.minidom.parseString(
                        text.replace("&dashdash;", "--")))[0]
                nodes = self._elements(root)
                if len(nodes) == 0:
                    # treat this as a comment on the next node
                    commentText += str(node.nodeValue) + "\n"
            elif node.nodeType == node.ELEMENT_NODE:
                # If it's an element node, then just add this node
                commented = False
                nodes = [node]
            else:
                # Ignore whitespace
                continue
            for node in nodes:
                # find the correct table
                obname = str(node.nodeName)
                if obname in self._tables:
                    pass
                elif obname.replace("auto_", "") in self._tables:
                    node.nodeName = obname.replace("auto_", "")
                    obname = str(node.nodeName)
                else:
                    problems.append(obname)
                    continue
                table = self.getTable(obname)
                if obname not in self._tableNames:
                    self._tableNames.append(obname)
                # make a new row
                warnings += table.addNode(node, commented, commentText)
                commentText = ""
        self.setStored()
        self.setLastModified()
        return self._unique(problems, warnings)

    def _unique(self, *ls):
        # make each list in args unique, then return a tuple of them
        ret = []
        for l in ls:
            newl = []
            for x in l:
                if x not in newl:
                    newl.append(x)
            ret.append(newl)
        return tuple(ret)

    def _elements(self, xml):
        return [n for n in xml.childNodes if n.nodeType == n.ELEMENT_NODE]

    def Save(self, filename):
        # write the tables to disk
        impl = xml.dom.minidom.getDOMImplementation()
        doc = impl.createDocument(None, 'components', None)
        # look at each table that is displayed
        for name in self._tableNames:
            table = self._tables[name]
            # make the xml elements and add them to doc
            table.createElements(doc, name)
        doc.documentElement.setAttribute('arch', self.architecture)
        text = doc.toprettyxml()
        open(filename, 'w').write(text)
        self.setStored()

    def getTable(self, name):
        # return the table
        table = self._tables[name]
        self.stack.setActiveStack(table.stack)
        return table

    def allTableNames(self):
        return sorted(self._tables.keys())

    def setTableNames(self, names):
        self._tableNames = names

    def getTableNames(self):
        return self._tableNames

    def getArch(self):
        return self.architecture

    def setArch(self, arch):
        self.architecture = arch
Beispiel #11
0
    def init(
        cls,
        application: QApplication,
        compiled: bool = True,
        debug: bool = False,
        db_type: DbType = DbType.DEFAULT,
        echo_sql: bool = False,
        no_ssl_verify: bool = False,
    ) -> None:
        cls.debug = debug
        cls.no_ssl_verify = no_ssl_verify
        cls.compiled = compiled

        cls.settings = QtCore.QSettings('lost-world', 'Embargo Edit')

        if db_type == DbType.DEFAULT:
            if cls.settings.value('sql_db', False, bool):
                SqlContext.init(cls.settings, echo = echo_sql)
                cls.db = SqlLoader(SqlContext.engine, SqlContext.scoped_session).load()
            else:
                cls.db = PickleLoader().load()
        elif db_type == DbType.SQL:
            SqlContext.init(cls.settings, echo = echo_sql)
            cls.db = SqlLoader(SqlContext.engine, SqlContext.scoped_session).load()
        else:
            cls.db = PickleLoader().load()

        cls.application = application

        cls.clipboard = application.clipboard()

        cls.cube_api_client = AsyncNativeApiClient(host = 'prohunterdogkeeper.dk', db = cls.db, verify_ssl = not cls.no_ssl_verify)

        # # https://github.com/syrusakbary/promise/issues/57
        # promise.async_instance.disable_trampoline()

        use_disk_with_remote = cls.settings.value('allow_disk_with_local_images', False, bool)

        cls.pixmap_loader = PixmapLoader(
            image_loader = ImageClient(
                cls.settings.value('remote_image_url', 'prohunterdogkeeper.dk', str),
                executor = LIFOExecutor(max_workers = 16),
                imageables_executor = LIFOExecutor(max_workers = 8),
                use_scryfall_when_available = True,
                image_cache_size = None,
                allow_save_to_disk = use_disk_with_remote,
                allow_load_from_disk = use_disk_with_remote,
                allow_local_fallback = cls.settings.value('allow_local_image_fallback', True, bool),
            ) if cls.settings.value('remote_images', False, bool) else
            ImageLoader(
                printing_executor = LIFOExecutor(max_workers = 16),
                imageable_executor = LIFOExecutor(max_workers = 8),
                image_cache_size = None,
            ),
            image_cache_size = cls.settings.value('image_cache_size', 64, int),
        )

        cls.cardboard_names = sorted(cls.db.cardboards.keys())

        cls.search_pattern_parser = SearchParser(cls.db)
        cls.undo_group = QUndoGroup()

        cls.sort_map = CustomSortMap.empty()
Beispiel #12
0
class DocumentManager(QObject):
    ##
    # Emitted when the current displayed map document changed.
    ##
    currentDocumentChanged = pyqtSignal(list)
    ##
    # Emitted when the user requested the document at \a index to be closed.
    ##
    documentCloseRequested = pyqtSignal(int)
    ##
    # Emitted when a document is about to be closed.
    ##
    documentAboutToClose = pyqtSignal(MapDocument)
    ##
    # Emitted when an error occurred while reloading the map.
    ##
    reloadError = pyqtSignal(str)

    mInstance = None

    def __init__(self, parent=None):
        super().__init__(parent)

        self.mDocuments = QList()
        self.mTabWidget = MovableTabWidget()
        self.mUndoGroup = QUndoGroup(self)
        self.mSelectedTool = None
        self.mViewWithTool = None
        self.mFileSystemWatcher = FileSystemWatcher(self)

        self.mTabWidget.setDocumentMode(True)
        self.mTabWidget.setTabsClosable(True)
        self.mTabWidget.currentChanged.connect(self.currentIndexChanged)
        self.mTabWidget.tabCloseRequested.connect(self.documentCloseRequested)
        self.mTabWidget.tabMoved.connect(self.documentTabMoved)
        self.mFileSystemWatcher.fileChanged.connect(self.fileChanged)

    def __del__(self):
        # All documents should be closed gracefully beforehand
        del self.mTabWidget

    def instance():
        if not DocumentManager.mInstance:
            DocumentManager.mInstance = DocumentManager()
        return DocumentManager.mInstance

    def deleteInstance():
        del DocumentManager.mInstance
        DocumentManager.mInstance = None

    ##
    # Returns the document manager widget. It contains the different map views
    # and a tab bar to switch between them.
    ##
    def widget(self):
        return self.mTabWidget

    ##
    # Returns the undo group that combines the undo stacks of all opened map
    # documents.
    #
    # @see MapDocument.undoStack()
    ##
    def undoGroup(self):
        return self.mUndoGroup

    ##
    # Returns the current map document, or 0 when there is none.
    ##
    def currentDocument(self):
        index = self.mTabWidget.currentIndex()
        if (index == -1):
            return None
        return self.mDocuments.at(index)

    ##
    # Returns the map view of the current document, or 0 when there is none.
    ##
    def currentMapView(self):
        widget = self.mTabWidget.currentWidget()
        if widget:
            return widget.mapView()
        return None

    ##
    # Returns the map scene of the current document, or 0 when there is none.
    ##
    def currentMapScene(self):
        mapView = self.currentMapView()
        if mapView:
            return mapView.mapScene()
        return None

    ##
    # Returns the map view that displays the given document, or 0 when there
    # is none.
    ##
    def viewForDocument(self, mapDocument):
        index = self.mDocuments.indexOf(mapDocument)
        if (index == -1):
            return None
        return self.mTabWidget.widget(index).mapView()

    ##
    # Returns the number of map documents.
    ##
    def documentCount(self):
        return self.mDocuments.size()

    ##
    # Searches for a document with the given \a fileName and returns its
    # index. Returns -1 when the document isn't open.
    ##
    def findDocument(self, fileName):
        canonicalFilePath = QFileInfo(fileName).canonicalFilePath()
        if (canonicalFilePath == ''):  # file doesn't exist
            return -1
        for i in range(self.mDocuments.size()):
            fileInfo = QFileInfo(self.mDocuments.at(i).fileName())
            if (fileInfo.canonicalFilePath() == canonicalFilePath):
                return i

        return -1

    ##
    # Switches to the map document at the given \a index.
    ##
    def switchToDocument(self, arg):
        tp = type(arg)
        if tp == int:
            index = arg
            self.mTabWidget.setCurrentIndex(index)
        elif tp == MapDocument:
            mapDocument = arg
            index = self.mDocuments.indexOf(mapDocument)
            if (index != -1):
                self.switchToDocument(index)

    ##
    # Adds the new or opened \a mapDocument to the document manager.
    ##
    def addDocument(self, mapDocument):
        self.mDocuments.append(mapDocument)
        self.mUndoGroup.addStack(mapDocument.undoStack())
        if (mapDocument.fileName() != ''):
            self.mFileSystemWatcher.addPath(mapDocument.fileName())
        view = MapView()
        scene = MapScene(view)  # scene is owned by the view
        container = MapViewContainer(view, self.mTabWidget)
        scene.setMapDocument(mapDocument)
        view.setScene(scene)
        documentIndex = self.mDocuments.size() - 1
        self.mTabWidget.addTab(container, mapDocument.displayName())
        self.mTabWidget.setTabToolTip(documentIndex, mapDocument.fileName())
        mapDocument.fileNameChanged.connect(self.fileNameChanged)
        mapDocument.modifiedChanged.connect(self.updateDocumentTab)
        mapDocument.saved.connect(self.documentSaved)
        container.reload.connect(self.reloadRequested)
        self.switchToDocument(documentIndex)
        self.centerViewOn(0, 0)

    ##
    # Closes the current map document. Will not ask the user whether to save
    # any changes!
    ##
    def closeCurrentDocument(self):
        index = self.mTabWidget.currentIndex()
        if (index == -1):
            return
        self.closeDocumentAt(index)

    ##
    # Closes the document at the given \a index. Will not ask the user whether
    # to save any changes!
    ##
    def closeDocumentAt(self, index):
        mapDocument = self.mDocuments.at(index)
        self.documentAboutToClose.emit(mapDocument)

        self.mDocuments.removeAt(index)
        self.mTabWidget.removeTab(index)

        if (mapDocument.fileName() != ''):
            self.mFileSystemWatcher.removePath(mapDocument.fileName())

        self.mUndoGroup.removeStack(mapDocument.undoStack())

    ##
    # Reloads the current document. Will not ask the user whether to save any
    # changes!
    #
    # \sa reloadDocumentAt()
    ##
    def reloadCurrentDocument(self):
        index = self.mTabWidget.currentIndex()
        if (index == -1):
            return False
        return self.reloadDocumentAt(index)

    ##
    # Reloads the document at the given \a index. It will lose any undo
    # history and current selections. Will not ask the user whether to save
    # any changes!
    #
    # Returns whether the map loaded successfully.
    ##
    def reloadDocumentAt(self, index):
        oldDocument = self.mDocuments.at(index)

        newDocument, error = MapDocument.load(oldDocument.fileName(),
                                              oldDocument.readerFormat())
        if (not newDocument):
            self.reloadError.emit(
                self.tr("%s:\n\n%s" % (oldDocument.fileName(), error)))
            return False

        # Remember current view state
        mapView = self.viewForDocument(oldDocument)
        layerIndex = oldDocument.currentLayerIndex()
        scale = mapView.zoomable().scale()
        horizontalPosition = mapView.horizontalScrollBar().sliderPosition()
        verticalPosition = mapView.verticalScrollBar().sliderPosition()
        # Replace old tab
        self.addDocument(newDocument)
        self.closeDocumentAt(index)
        self.mTabWidget.moveTab(self.mDocuments.size() - 1, index)
        # Restore previous view state
        mapView = self.currentMapView()
        mapView.zoomable().setScale(scale)
        mapView.horizontalScrollBar().setSliderPosition(horizontalPosition)
        mapView.verticalScrollBar().setSliderPosition(verticalPosition)
        if (layerIndex > 0 and layerIndex < newDocument.map().layerCount()):
            newDocument.setCurrentLayerIndex(layerIndex)
        return True

    ##
    # Close all documents. Will not ask the user whether to save any changes!
    ##
    def closeAllDocuments(self):
        while (not self.mDocuments.isEmpty()):
            self.closeCurrentDocument()

    ##
    # Returns all open map documents.
    ##
    def documents(self):
        return self.mDocuments

    ##
    # Centers the current map on the tile coordinates \a x, \a y.
    ##
    def centerViewOn(self, *args):
        l = len(args)
        if l == 2:
            x, y = args
            view = self.currentMapView()
            if (not view):
                return
            view.centerOn(
                self.currentDocument().renderer().pixelToScreenCoords(x, y))
        elif l == 1:
            pos = args[0]
            self.centerViewOn(pos.x(), pos.y())

    def switchToLeftDocument(self):
        tabCount = self.mTabWidget.count()
        if (tabCount < 2):
            return
        currentIndex = self.mTabWidget.currentIndex()
        if currentIndex > 0:
            x = currentIndex
        else:
            x = tabCount
        self.switchToDocument(x - 1)

    def switchToRightDocument(self):
        tabCount = self.mTabWidget.count()
        if (tabCount < 2):
            return
        currentIndex = self.mTabWidget.currentIndex()
        self.switchToDocument((currentIndex + 1) % tabCount)

    def setSelectedTool(self, tool):
        if type(tool) == list:
            tool = tool[0]
        if (self.mSelectedTool == tool):
            return

        if self.mSelectedTool:
            self.mSelectedTool.cursorChanged.disconnect(self.cursorChanged)

        self.mSelectedTool = tool

        if self.mViewWithTool:
            mapScene = self.mViewWithTool.mapScene()
            mapScene.disableSelectedTool()

            if tool:
                mapScene.setSelectedTool(tool)
                mapScene.enableSelectedTool()

            if tool:
                self.mViewWithTool.viewport().setCursor(tool.cursor)
            else:
                self.mViewWithTool.viewport().unsetCursor()

        if tool:
            tool.cursorChanged.connect(self.cursorChanged)

    def currentIndexChanged(self):
        if self.mViewWithTool:
            mapScene = self.mViewWithTool.mapScene()
            mapScene.disableSelectedTool()
            self.mViewWithTool = None

        mapDocument = self.currentDocument()
        if (mapDocument):
            self.mUndoGroup.setActiveStack(mapDocument.undoStack())

        self.currentDocumentChanged.emit([mapDocument])

        mapView = self.currentMapView()
        if mapView:
            mapScene = mapView.mapScene()
            mapScene.setSelectedTool(self.mSelectedTool)
            mapScene.enableSelectedTool()
            if self.mSelectedTool:
                mapView.viewport().setCursor(self.mSelectedTool.cursor)
            else:
                mapView.viewport().unsetCursor()
            self.mViewWithTool = mapView

    def fileNameChanged(self, fileName, oldFileName):
        if fileName != '':
            self.mFileSystemWatcher.addPath(fileName)
        if oldFileName != '':
            self.mFileSystemWatcher.removePath(oldFileName)
        self.updateDocumentTab()

    def updateDocumentTab(self):
        mapDocument = self.sender()
        index = self.mDocuments.indexOf(mapDocument)
        tabText = mapDocument.displayName()
        if (mapDocument.isModified()):
            tabText = '*' + tabText
        self.mTabWidget.setTabText(index, tabText)
        self.mTabWidget.setTabToolTip(index, mapDocument.fileName())

    def documentSaved(self):
        document = self.sender()
        index = self.mDocuments.indexOf(document)
        widget = self.mTabWidget.widget(index)
        container = widget
        container.setFileChangedWarningVisible(False)

    def documentTabMoved(self, _from, to):
        self.mDocuments.move(_from, to)

    def fileChanged(self, fileName):
        index = self.findDocument(fileName)
        # Most likely the file was removed
        if (index == -1):
            return
        document = self.mDocuments.at(index)
        # Ignore change event when it seems to be our own save
        if (QFileInfo(fileName).lastModified() == document.lastSaved()):
            return
        # Automatically reload when there are no unsaved changes
        if (not document.isModified()):
            self.reloadDocumentAt(index)
            return

        widget = self.mTabWidget.widget(index)
        container = widget
        container.setFileChangedWarningVisible(True)

    def cursorChanged(self, cursor):
        if self.mViewWithTool:
            self.mViewWithTool.viewport().setCursor(cursor)

    def reloadRequested(self):
        index = self.mTabWidget.indexOf(self.sender())
        self.reloadDocumentAt(index)
Beispiel #13
0
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)
Beispiel #14
0
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 &lt;<a href='mailto:[email protected]'>[email protected]</a>&gt;</li>" \
                "<li>Andreas Noack &lt;<a href='mailto:[email protected]'>[email protected]</a>&gt;</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(
        )
Beispiel #15
0
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 &lt;<a href='mailto:[email protected]'>[email protected]</a>&gt;</li>" \
                "<li>Andreas Noack &lt;<a href='mailto:[email protected]'>[email protected]</a>&gt;</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)))
Beispiel #16
0
    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)
Beispiel #17
0
class DocumentManager(QObject):
    ##
    # Emitted when the current displayed map document changed.
    ##
    currentDocumentChanged = pyqtSignal(list)
    ##
    # Emitted when the user requested the document at \a index to be closed.
    ##
    documentCloseRequested = pyqtSignal(int)
    ##
    # Emitted when a document is about to be closed.
    ##
    documentAboutToClose = pyqtSignal(MapDocument)
    ##
    # Emitted when an error occurred while reloading the map.
    ##
    reloadError = pyqtSignal(str)

    mInstance = None

    def __init__(self, parent = None):
        super().__init__(parent)

        self.mDocuments = QList()
        self.mTabWidget = MovableTabWidget()
        self.mUndoGroup = QUndoGroup(self)
        self.mSelectedTool = None
        self.mViewWithTool = None
        self.mFileSystemWatcher = FileSystemWatcher(self)

        self.mTabWidget.setDocumentMode(True)
        self.mTabWidget.setTabsClosable(True)
        self.mTabWidget.currentChanged.connect(self.currentIndexChanged)
        self.mTabWidget.tabCloseRequested.connect(self.documentCloseRequested)
        self.mTabWidget.tabMoved.connect(self.documentTabMoved)
        self.mFileSystemWatcher.fileChanged.connect(self.fileChanged)

    def __del__(self):
        # All documents should be closed gracefully beforehand
        del self.mTabWidget

    def instance():
        if not DocumentManager.mInstance:
            DocumentManager.mInstance = DocumentManager()
        return DocumentManager.mInstance

    def deleteInstance():
        del DocumentManager.mInstance
        DocumentManager.mInstance = None

    ##
    # Returns the document manager widget. It contains the different map views
    # and a tab bar to switch between them.
    ##
    def widget(self):
        return self.mTabWidget

    ##
    # Returns the undo group that combines the undo stacks of all opened map
    # documents.
    #
    # @see MapDocument.undoStack()
    ##
    def undoGroup(self):
        return self.mUndoGroup

    ##
    # Returns the current map document, or 0 when there is none.
    ##
    def currentDocument(self):
        index = self.mTabWidget.currentIndex()
        if (index == -1):
            return None
        return self.mDocuments.at(index)

    ##
    # Returns the map view of the current document, or 0 when there is none.
    ##
    def currentMapView(self):
        widget = self.mTabWidget.currentWidget()
        if widget:
            return widget.mapView()
        return None

    ##
    # Returns the map scene of the current document, or 0 when there is none.
    ##
    def currentMapScene(self):
        mapView = self.currentMapView()
        if mapView:
            return mapView.mapScene()
        return None

    ##
    # Returns the map view that displays the given document, or 0 when there
    # is none.
    ##
    def viewForDocument(self, mapDocument):
        index = self.mDocuments.indexOf(mapDocument)
        if (index == -1):
            return None
        return self.mTabWidget.widget(index).mapView()

    ##
    # Returns the number of map documents.
    ##
    def documentCount(self):
        return self.mDocuments.size()

    ##
    # Searches for a document with the given \a fileName and returns its
    # index. Returns -1 when the document isn't open.
    ##
    def findDocument(self, fileName):
        canonicalFilePath = QFileInfo(fileName).canonicalFilePath()
        if (canonicalFilePath==''): # file doesn't exist
            return -1
        for i in range(self.mDocuments.size()):
            fileInfo = QFileInfo(self.mDocuments.at(i).fileName())
            if (fileInfo.canonicalFilePath() == canonicalFilePath):
                return i

        return -1

    ##
    # Switches to the map document at the given \a index.
    ##
    def switchToDocument(self, arg):
        tp = type(arg)
        if tp==int:
            index = arg
            self.mTabWidget.setCurrentIndex(index)
        elif tp==MapDocument:
            mapDocument = arg
            index = self.mDocuments.indexOf(mapDocument)
            if (index != -1):
                self.switchToDocument(index)

    ##
    # Adds the new or opened \a mapDocument to the document manager.
    ##
    def addDocument(self, mapDocument):
        self.mDocuments.append(mapDocument)
        self.mUndoGroup.addStack(mapDocument.undoStack())
        if (mapDocument.fileName()!=''):
            self.mFileSystemWatcher.addPath(mapDocument.fileName())
        view = MapView()
        scene = MapScene(view) # scene is owned by the view
        container = MapViewContainer(view, self.mTabWidget)
        scene.setMapDocument(mapDocument)
        view.setScene(scene)
        documentIndex = self.mDocuments.size() - 1
        self.mTabWidget.addTab(container, mapDocument.displayName())
        self.mTabWidget.setTabToolTip(documentIndex, mapDocument.fileName())
        mapDocument.fileNameChanged.connect(self.fileNameChanged)
        mapDocument.modifiedChanged.connect(self.updateDocumentTab)
        mapDocument.saved.connect(self.documentSaved)
        container.reload.connect(self.reloadRequested)
        self.switchToDocument(documentIndex)
        self.centerViewOn(0, 0)

    ##
    # Closes the current map document. Will not ask the user whether to save
    # any changes!
    ##
    def closeCurrentDocument(self):
        index = self.mTabWidget.currentIndex()
        if (index == -1):
            return
        self.closeDocumentAt(index)

    ##
    # Closes the document at the given \a index. Will not ask the user whether
    # to save any changes!
    ##
    def closeDocumentAt(self, index):
        mapDocument = self.mDocuments.at(index)
        self.documentAboutToClose.emit(mapDocument)

        self.mDocuments.removeAt(index)
        self.mTabWidget.removeTab(index)
        
        if (mapDocument.fileName() != ''):
            self.mFileSystemWatcher.removePath(mapDocument.fileName())

        self.mUndoGroup.removeStack(mapDocument.undoStack())
        
    ##
    # Reloads the current document. Will not ask the user whether to save any
    # changes!
    #
    # \sa reloadDocumentAt()
    ##
    def reloadCurrentDocument(self):
        index = self.mTabWidget.currentIndex()
        if (index == -1):
            return False
        return self.reloadDocumentAt(index)

    ##
    # Reloads the document at the given \a index. It will lose any undo
    # history and current selections. Will not ask the user whether to save
    # any changes!
    #
    # Returns whether the map loaded successfully.
    ##
    def reloadDocumentAt(self, index):
        oldDocument = self.mDocuments.at(index)
        
        newDocument, error = MapDocument.load(oldDocument.fileName(), oldDocument.readerFormat())
        if (not newDocument):
            self.reloadError.emit(self.tr("%s:\n\n%s"%(oldDocument.fileName(), error)))
            return False

        # Remember current view state
        mapView = self.viewForDocument(oldDocument)
        layerIndex = oldDocument.currentLayerIndex()
        scale = mapView.zoomable().scale()
        horizontalPosition = mapView.horizontalScrollBar().sliderPosition()
        verticalPosition = mapView.verticalScrollBar().sliderPosition()
        # Replace old tab
        self.addDocument(newDocument)
        self.closeDocumentAt(index)
        self.mTabWidget.moveTab(self.mDocuments.size() - 1, index)
        # Restore previous view state
        mapView = self.currentMapView()
        mapView.zoomable().setScale(scale)
        mapView.horizontalScrollBar().setSliderPosition(horizontalPosition)
        mapView.verticalScrollBar().setSliderPosition(verticalPosition)
        if (layerIndex > 0 and layerIndex < newDocument.map().layerCount()):
            newDocument.setCurrentLayerIndex(layerIndex)
        return True

    ##
    # Close all documents. Will not ask the user whether to save any changes!
    ##
    def closeAllDocuments(self):
        while (not self.mDocuments.isEmpty()):
            self.closeCurrentDocument()

    ##
    # Returns all open map documents.
    ##
    def documents(self):
        return self.mDocuments

    ##
    # Centers the current map on the tile coordinates \a x, \a y.
    ##
    def centerViewOn(self, *args):
        l = len(args)
        if l==2:
            x, y = args
            view = self.currentMapView()
            if (not view):
                return
            view.centerOn(self.currentDocument().renderer().pixelToScreenCoords(x, y))
        elif l==1:
            pos = args[0]
            self.centerViewOn(pos.x(), pos.y())

    def switchToLeftDocument(self):
        tabCount = self.mTabWidget.count()
        if (tabCount < 2):
            return
        currentIndex = self.mTabWidget.currentIndex()
        if currentIndex > 0:
            x = currentIndex
        else:
            x = tabCount
        self.switchToDocument(x - 1)

    def switchToRightDocument(self):
        tabCount = self.mTabWidget.count()
        if (tabCount < 2):
            return
        currentIndex = self.mTabWidget.currentIndex()
        self.switchToDocument((currentIndex + 1) % tabCount)

    def setSelectedTool(self, tool):
        if type(tool)==list:
            tool = tool[0]
        if (self.mSelectedTool == tool):
            return
        
        if self.mSelectedTool:
            self.mSelectedTool.cursorChanged.disconnect(self.cursorChanged)

        self.mSelectedTool = tool
        
        if self.mViewWithTool:
            mapScene = self.mViewWithTool.mapScene()
            mapScene.disableSelectedTool()

            if tool:
                mapScene.setSelectedTool(tool)
                mapScene.enableSelectedTool()

            if tool:
                self.mViewWithTool.viewport().setCursor(tool.cursor)
            else:
                self.mViewWithTool.viewport().unsetCursor()

        if tool:
            tool.cursorChanged.connect(self.cursorChanged)

    def currentIndexChanged(self):
        if self.mViewWithTool:
            mapScene = self.mViewWithTool.mapScene()
            mapScene.disableSelectedTool()
            self.mViewWithTool = None
        
        mapDocument = self.currentDocument()
        if (mapDocument):
            self.mUndoGroup.setActiveStack(mapDocument.undoStack())

        self.currentDocumentChanged.emit([mapDocument])
        
        mapView = self.currentMapView()
        if mapView:
            mapScene = mapView.mapScene()
            mapScene.setSelectedTool(self.mSelectedTool)
            mapScene.enableSelectedTool()
            if self.mSelectedTool:
                mapView.viewport().setCursor(self.mSelectedTool.cursor)
            else:
                mapView.viewport().unsetCursor()
            self.mViewWithTool = mapView

    def fileNameChanged(self, fileName, oldFileName):
        if fileName != '':
            self.mFileSystemWatcher.addPath(fileName)
        if oldFileName != '':
            self.mFileSystemWatcher.removePath(oldFileName)
        self.updateDocumentTab()

    def updateDocumentTab(self):
        mapDocument = self.sender()
        index = self.mDocuments.indexOf(mapDocument)
        tabText = mapDocument.displayName()
        if (mapDocument.isModified()):
            tabText = '*' + tabText
        self.mTabWidget.setTabText(index, tabText)
        self.mTabWidget.setTabToolTip(index, mapDocument.fileName())

    def documentSaved(self):
        document = self.sender()
        index = self.mDocuments.indexOf(document)
        widget = self.mTabWidget.widget(index)
        container = widget
        container.setFileChangedWarningVisible(False)

    def documentTabMoved(self, _from, to):
        self.mDocuments.move(_from, to)

    def fileChanged(self, fileName):
        index = self.findDocument(fileName)
        # Most likely the file was removed
        if (index == -1):
            return
        document = self.mDocuments.at(index)
        # Ignore change event when it seems to be our own save
        if (QFileInfo(fileName).lastModified() == document.lastSaved()):
            return
        # Automatically reload when there are no unsaved changes
        if (not document.isModified()):
            self.reloadDocumentAt(index)
            return

        widget = self.mTabWidget.widget(index)
        container = widget
        container.setFileChangedWarningVisible(True)

    def cursorChanged(self, cursor):
        if self.mViewWithTool:
            self.mViewWithTool.viewport().setCursor(cursor)
        
    def reloadRequested(self):
        index = self.mTabWidget.indexOf(self.sender())
        self.reloadDocumentAt(index)