コード例 #1
0
ファイル: main_window.py プロジェクト: 00mjk/randovania
    def _setup_difficulties_menu(self, game: RandovaniaGame,
                                 menu: QtWidgets.QMenu):
        from randovania.game_description import default_database
        game = default_database.game_description_for(game)
        tricks_in_use = used_tricks(game)

        menu.clear()
        for trick in sorted(game.resource_database.trick,
                            key=lambda _trick: _trick.long_name):
            if trick not in tricks_in_use:
                continue

            trick_menu = QtWidgets.QMenu(self)
            trick_menu.setTitle(trick.long_name)
            menu.addAction(trick_menu.menuAction())

            used_difficulties = difficulties_for_trick(game, trick)
            for i, trick_level in enumerate(iterate_enum(LayoutTrickLevel)):
                if trick_level in used_difficulties:
                    difficulty_action = QtWidgets.QAction(self)
                    difficulty_action.setText(trick_level.long_name)
                    trick_menu.addAction(difficulty_action)
                    difficulty_action.triggered.connect(
                        functools.partial(self._open_trick_details_popup, game,
                                          trick, trick_level))
コード例 #2
0
    def buildMenuFromTree(cls, tree: Tree, parentMenu: QtWidgets.QMenu = None):
        """returns qmenu corresponding to tree"""
        parentMenu = parentMenu or QtWidgets.QMenu(title=tree.name)
        parentMenu.clear()
        for i in tree.branches:
            added = None
            if i.branches:
                added = parentMenu.addMenu(TreeMenu.buildMenuFromTree(i))
            elif isinstance(i.value, PartialAction):
                i.value.setParent(parentMenu)
                added = parentMenu.addAction(i.value)
            elif callable(i.value):
                added = parentMenu.addAction(i.name)
                added.triggered.connect(i.value)
            # check for list of multiple actions
            elif isinstance(i.value, (list, tuple)):
                added = parentMenu.addAction(i.name)
                for slot in i.value:
                    added.triggered.connect(slot)

            if added is None:
                #tree.display()
                raise RuntimeError(
                    "no action value found for branch {}".format(i))

            if i.description:
                added.setToolTip(i.description)
        return parentMenu
コード例 #3
0
ファイル: action_clear.py プロジェクト: zwadar/pyside-setup
 def testMenu(self):
     self._actionDestroyed = False
     w = QWidget()
     menu = QMenu(w)
     act = menu.addAction("MENU")
     _ref = weakref.ref(act, self.actionDestroyed)
     act = None
     self.assertFalse(self._actionDestroyed)
     menu.clear()
     self.assertTrue(self._actionDestroyed)
コード例 #4
0
ファイル: action_clear.py プロジェクト: BadSingleton/pyside2
 def testMenu(self):
     self._actionDestroyed = False
     w = QWidget()
     menu = QMenu(w)
     act = menu.addAction("MENU")
     _ref = weakref.ref(act, self.actionDestroyed)
     act = None
     self.assertFalse(self._actionDestroyed)
     menu.clear()
     self.assertTrue(self._actionDestroyed)
コード例 #5
0
class TrayIcon(QSystemTrayIcon):
    on_settings = Signal()

    host: "Host"

    def __init__(self, host: "Host"):
        global _tray_icon
        super().__init__(QIcon(":/runekit/ui/trayicon.png"))

        _tray_icon = self

        self.host = host
        self._setup_menu()
        self.setContextMenu(self.menu)

    def _setup_menu(self):
        if not hasattr(self, "menu"):
            self.menu = QMenu("RuneKit")

        self.menu.clear()

        self._setup_app_menu("", self.menu)

        self.menu.addSeparator()
        self.menu_settings = self.menu.addAction("Settings")
        self.menu_settings.triggered.connect(self.on_settings)
        self.menu.addAction(
            QIcon.fromTheme("application-exit"),
            "Exit",
            lambda: QCoreApplication.instance().quit(),
        )

    def _setup_app_menu(self, path: str, menu: QMenu):
        for app_id, manifest in self.host.app_store.list_app(path):
            if manifest is None:
                # Folder
                submenu = menu.addMenu(QIcon.fromTheme("folder"), app_id)
                self._setup_app_menu(app_id, submenu)
                continue

            app_menu = menu.addAction(manifest["appName"])
            app_menu.triggered.connect(
                lambda _=None, app_id=app_id: self.host.launch_app_id(app_id)
            )

            icon = self.host.app_store.icon(app_id)
            if icon:
                app_menu.setIcon(icon)

    @Slot()
    def update_menu(self):
        self._setup_menu()
コード例 #6
0
ファイル: ControlsWidget.py プロジェクト: joshwatson/debugger
class DebugControlsWidget(QToolBar):
    def __init__(self, parent, name, data, debug_state):
        if not type(data) == binaryninja.binaryview.BinaryView:
            raise Exception('expected widget data to be a BinaryView')

        self.bv = data
        self.debug_state = debug_state

        QToolBar.__init__(self, parent)

        # TODO: Is there a cleaner way to do this?
        self.setStyleSheet("""
		QToolButton{padding: 4px 14px 4px 14px; font-size: 14pt;}
		QToolButton:disabled{color: palette(alternate-base)}
		""")

        self.actionRun = QAction("Run", self)
        self.actionRun.triggered.connect(lambda: self.perform_run())
        self.actionRestart = QAction("Restart", self)
        self.actionRestart.triggered.connect(lambda: self.perform_restart())
        self.actionQuit = QAction("Quit", self)
        self.actionQuit.triggered.connect(lambda: self.perform_quit())
        self.actionAttach = QAction("Attach... (todo)", self)
        self.actionAttach.triggered.connect(lambda: self.perform_attach())
        self.actionDetach = QAction("Detach", self)
        self.actionDetach.triggered.connect(lambda: self.perform_detach())
        self.actionSettings = QAction("Settings...", self)
        self.actionSettings.triggered.connect(lambda: self.perform_settings())
        self.actionPause = QAction("Pause", self)
        self.actionPause.triggered.connect(lambda: self.perform_pause())
        self.actionResume = QAction("Resume", self)
        self.actionResume.triggered.connect(lambda: self.perform_resume())
        self.actionStepInto = QAction("Step Into", self)
        self.actionStepInto.triggered.connect(lambda: self.perform_step_into())
        self.actionStepOver = QAction("Step Over", self)
        self.actionStepOver.triggered.connect(lambda: self.perform_step_over())
        self.actionStepReturn = QAction("Step Return", self)
        self.actionStepReturn.triggered.connect(
            lambda: self.perform_step_return())

        # session control menu
        self.controlMenu = QMenu("Process Control", self)
        self.controlMenu.addAction(self.actionRun)
        self.controlMenu.addAction(self.actionRestart)
        self.controlMenu.addAction(self.actionQuit)
        self.controlMenu.addSeparator()
        # TODO: Attach to running process
        # self.controlMenu.addAction(self.actionAttach)
        self.controlMenu.addAction(self.actionDetach)
        # TODO: Switch adapter/etc (could go in regular settings)
        self.controlMenu.addSeparator()
        self.controlMenu.addAction(self.actionSettings)

        self.btnControl = QToolButton(self)
        self.btnControl.setMenu(self.controlMenu)
        self.btnControl.setPopupMode(QToolButton.MenuButtonPopup)
        self.btnControl.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnControl.setDefaultAction(self.actionRun)
        self.addWidget(self.btnControl)

        # execution control buttons
        self.addAction(self.actionPause)
        self.addAction(self.actionResume)
        self.addAction(self.actionStepInto)
        self.addAction(self.actionStepOver)
        # TODO: Step until returning from current function
        self.addAction(self.actionStepReturn)

        self.threadMenu = QMenu("Threads", self)

        self.btnThreads = QToolButton(self)
        self.btnThreads.setMenu(self.threadMenu)
        self.btnThreads.setPopupMode(QToolButton.InstantPopup)
        self.btnThreads.setToolButtonStyle(Qt.ToolButtonTextOnly)
        self.addWidget(self.btnThreads)

        self.set_thread_list([])

        self.editStatus = QLineEdit('INACTIVE', self)
        self.editStatus.setReadOnly(True)
        self.editStatus.setAlignment(QtCore.Qt.AlignCenter)
        self.addWidget(self.editStatus)

        # disable buttons
        self.set_actions_enabled(Run=True,
                                 Restart=False,
                                 Quit=False,
                                 Attach=True,
                                 Detach=False,
                                 Pause=False,
                                 Resume=False,
                                 StepInto=False,
                                 StepOver=False,
                                 StepReturn=False)
        self.set_resume_pause_action("Pause")

    def __del__(self):
        # TODO: Move this elsewhere
        # This widget is tasked with cleaning up the state after the view is closed
        # binjaplug.delete_state(self.bv)
        pass

    def perform_run(self):
        self.debug_state.run()
        self.state_stopped()
        self.debug_state.ui.context_display()

    def perform_restart(self):
        self.debug_state.restart()
        self.state_stopped()

    def perform_quit(self):
        self.debug_state.quit()
        self.state_inactive()

    def perform_attach(self):
        # TODO: Show dialog to select adapter/address/process
        pass

    def perform_detach(self):
        self.debug_state.detach()
        self.state_inactive()

    def perform_settings(self):
        dialog = AdapterSettingsDialog.AdapterSettingsDialog(self, self.bv)
        dialog.show()

    def perform_pause(self):
        self.debug_state.pause()

    def perform_resume(self):
        def perform_resume_thread():
            (reason, data) = self.debug_state.go()
            execute_on_main_thread_and_wait(
                lambda: self.handle_stop_return(reason, data))
            execute_on_main_thread_and_wait(
                lambda: self.debug_state.ui.context_display())

        self.state_running()
        threading.Thread(target=perform_resume_thread).start()

    def perform_step_into(self):
        def perform_step_into_thread():
            (reason, data) = self.debug_state.step_into()
            execute_on_main_thread_and_wait(
                lambda: self.handle_stop_return(reason, data))
            execute_on_main_thread_and_wait(
                lambda: self.debug_state.ui.context_display())

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_into_thread).start()

    def perform_step_over(self):
        def perform_step_over_thread():
            (reason, data) = self.debug_state.step_over()
            execute_on_main_thread_and_wait(
                lambda: self.handle_stop_return(reason, data))
            execute_on_main_thread_and_wait(
                lambda: self.debug_state.ui.context_display())

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_over_thread).start()

    def perform_step_return(self):
        def perform_step_return_thread():
            (reason, data) = self.debug_state.step_return()
            execute_on_main_thread_and_wait(
                lambda: self.handle_stop_return(reason, data))
            execute_on_main_thread_and_wait(
                lambda: self.debug_state.ui.context_display())

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_return_thread).start()

    def set_actions_enabled(self, **kwargs):
        def enable_starting(e):
            self.actionRun.setEnabled(e)
            self.actionAttach.setEnabled(e)

        def enable_stopping(e):
            self.actionRestart.setEnabled(e)
            self.actionQuit.setEnabled(e)
            self.actionDetach.setEnabled(e)

        def enable_stepping(e):
            self.actionStepInto.setEnabled(e)
            self.actionStepOver.setEnabled(e)
            self.actionStepReturn.setEnabled(e)

        actions = {
            "Run": lambda e: self.actionRun.setEnabled(e),
            "Restart": lambda e: self.actionRestart.setEnabled(e),
            "Quit": lambda e: self.actionQuit.setEnabled(e),
            "Attach": lambda e: self.actionAttach.setEnabled(e),
            "Detach": lambda e: self.actionDetach.setEnabled(e),
            "Pause": lambda e: self.actionPause.setEnabled(e),
            "Resume": lambda e: self.actionResume.setEnabled(e),
            "StepInto": lambda e: self.actionStepInto.setEnabled(e),
            "StepOver": lambda e: self.actionStepOver.setEnabled(e),
            "StepReturn": lambda e: self.actionStepReturn.setEnabled(e),
            "Threads": lambda e: self.btnThreads.setEnabled(e),
            "Starting": enable_starting,
            "Stopping": enable_stopping,
            "Stepping": enable_stepping,
        }
        for (action, enabled) in kwargs.items():
            actions[action](enabled)

    def set_default_process_action(self, action):
        actions = {
            "Run": self.actionRun,
            "Restart": self.actionRestart,
            "Quit": self.actionQuit,
            "Attach": self.actionAttach,
            "Detach": self.actionDetach,
        }
        self.btnControl.setDefaultAction(actions[action])

    def set_resume_pause_action(self, action):
        self.actionResume.setVisible(action == "Resume")
        self.actionPause.setVisible(action == "Pause")

    def set_thread_list(self, threads):
        def select_thread_fn(tid):
            def select_thread(tid):
                stateObj = binjaplug.get_state(self.bv)
                if stateObj.state == 'STOPPED':
                    adapter = stateObj.adapter
                    adapter.thread_select(tid)
                    self.debug_state.ui.context_display()
                else:
                    print('cannot set thread in state %s' % stateObj.state)

            return lambda: select_thread(tid)

        self.threadMenu.clear()
        if len(threads) > 0:
            for thread in threads:
                item_name = "Thread {} at {}".format(thread['tid'],
                                                     hex(thread['rip']))
                action = self.threadMenu.addAction(
                    item_name, select_thread_fn(thread['tid']))
                if thread['selected']:
                    self.btnThreads.setDefaultAction(action)
        else:
            defaultThreadAction = self.threadMenu.addAction("Thread List")
            defaultThreadAction.setEnabled(False)
            self.btnThreads.setDefaultAction(defaultThreadAction)

    def state_inactive(self, msg=None):
        debug_state = binjaplug.get_state(self.bv)

        # clear breakpoints
        debug_state.breakpoint_tag_del()
        debug_state.breakpoints = {}

        debug_state.state = 'INACTIVE'
        self.editStatus.setText(msg or debug_state.state)
        self.set_actions_enabled(Starting=True,
                                 Stopping=False,
                                 Stepping=False,
                                 Pause=False,
                                 Resume=False,
                                 Threads=False)
        self.set_default_process_action("Run")
        self.set_thread_list([])
        self.set_resume_pause_action("Pause")

    def state_stopped(self, msg=None):
        debug_state = binjaplug.get_state(self.bv)
        debug_state.state = 'STOPPED'
        self.editStatus.setText(msg or debug_state.state)
        self.set_actions_enabled(Starting=False,
                                 Stopping=True,
                                 Stepping=True,
                                 Pause=True,
                                 Resume=True,
                                 Threads=True)
        self.set_default_process_action("Quit")
        self.set_resume_pause_action("Resume")

    def state_stopped_extern(self, msg=None):
        debug_state = binjaplug.get_state(self.bv)
        debug_state.state = 'STOPPED (outside view)'
        self.editStatus.setText(msg or debug_state.state)
        self.set_actions_enabled(Starting=False,
                                 Stopping=True,
                                 Stepping=True,
                                 StepReturn=False,
                                 Pause=True,
                                 Resume=True,
                                 Threads=True)
        self.set_default_process_action("Quit")
        self.set_resume_pause_action("Resume")

    def state_running(self, msg=None):
        debug_state = binjaplug.get_state(self.bv)
        debug_state.state = 'RUNNING'
        self.editStatus.setText(msg or debug_state.state)
        self.set_actions_enabled(Starting=False,
                                 Stopping=True,
                                 Stepping=False,
                                 Pause=True,
                                 Resume=False,
                                 Threads=False)
        self.set_default_process_action("Quit")
        self.set_resume_pause_action("Pause")

    def state_busy(self, msg=None):
        debug_state = binjaplug.get_state(self.bv)
        debug_state.state = 'RUNNING'
        self.editStatus.setText(msg or debug_state.state)
        self.set_actions_enabled(Starting=False,
                                 Stopping=True,
                                 Stepping=False,
                                 Pause=True,
                                 Resume=False,
                                 Threads=False)
        self.set_default_process_action("Quit")
        self.set_resume_pause_action("Pause")

    def state_error(self, msg=None):
        debug_state = binjaplug.get_state(self.bv)
        debug_state.state = 'ERROR'
        self.editStatus.setText(msg or debug_state.state)
        self.set_actions_enabled(Run=True,
                                 Restart=True,
                                 Quit=True,
                                 Attach=True,
                                 Detach=True,
                                 Pause=True,
                                 Resume=True,
                                 StepInto=True,
                                 StepOver=True,
                                 StepReturn=True,
                                 Threads=True)
        self.set_default_process_action("Run")
        self.set_thread_list([])
        self.set_resume_pause_action("Resume")

    def handle_stop_return(self, reason, data):
        if reason == DebugAdapter.STOP_REASON.STDOUT_MESSAGE:
            self.state_stopped('stdout: ' + data)
        elif reason == DebugAdapter.STOP_REASON.PROCESS_EXITED:
            self.debug_state.quit()
            self.state_inactive('process exited, return code=%d' % data)
        elif reason == DebugAdapter.STOP_REASON.BACKEND_DISCONNECTED:
            self.debug_state.quit()
            self.state_inactive('backend disconnected (process exited?)')
コード例 #7
0
class DebugControlsWidget(QToolBar):
    def __init__(self, parent, name, data, debug_state):
        if not type(data) == BinaryView:
            raise Exception('expected widget data to be a BinaryView')

        self.bv = data
        self.debug_state = debug_state

        QToolBar.__init__(self, parent)

        # TODO: Is there a cleaner way to do this?
        self.setStyleSheet("""
		QToolButton{padding: 4px 14px 4px 14px; font-size: 14pt;}
		QToolButton:disabled{color: palette(alternate-base)}
		""")

        self.actionRun = QAction("Run", self)
        self.actionRun.triggered.connect(lambda: self.perform_run())
        self.actionRun.setIcon(load_icon('run.svg'))

        self.actionRestart = QAction("Restart", self)
        self.actionRestart.triggered.connect(lambda: self.perform_restart())
        self.actionRestart.setIcon(load_icon('restart.svg'))

        self.actionQuit = QAction("Quit", self)
        self.actionQuit.triggered.connect(lambda: self.perform_quit())
        self.actionQuit.setIcon(load_icon('cancel.svg'))

        self.actionAttach = QAction("Attach", self)
        self.actionAttach.triggered.connect(lambda: self.perform_attach())
        self.actionAttach.setIcon(load_icon('connect.svg'))

        self.actionDetach = QAction("Detach", self)
        self.actionDetach.triggered.connect(lambda: self.perform_detach())
        self.actionDetach.setIcon(load_icon('disconnect.svg'))

        self.actionSettings = QAction("Settings...", self)
        self.actionSettings.triggered.connect(lambda: self.perform_settings())

        self.actionPause = QAction("Pause", self)
        self.actionPause.triggered.connect(lambda: self.perform_pause())
        self.actionPause.setIcon(load_icon('pause.svg'))

        self.actionResume = QAction("Resume", self)
        self.actionResume.triggered.connect(lambda: self.perform_resume())
        self.actionResume.setIcon(load_icon('resume.svg'))

        self.actionStepIntoAsm = QAction("Step Into (Assembly)", self)
        self.actionStepIntoAsm.triggered.connect(
            lambda: self.perform_step_into_asm())
        self.actionStepIntoAsm.setIcon(load_icon('stepinto.svg'))

        self.actionStepIntoIL = QAction("Step Into", self)
        self.actionStepIntoIL.triggered.connect(
            lambda: self.perform_step_into_il())
        self.actionStepIntoIL.setIcon(load_icon('stepinto.svg'))

        self.actionStepOverAsm = QAction("Step Over (Assembly)", self)
        self.actionStepOverAsm.triggered.connect(
            lambda: self.perform_step_over_asm())
        self.actionStepOverAsm.setIcon(load_icon('stepover.svg'))

        self.actionStepOverIL = QAction("Step Over", self)
        self.actionStepOverIL.triggered.connect(
            lambda: self.perform_step_over_il())
        self.actionStepOverIL.setIcon(load_icon('stepover.svg'))

        self.actionStepReturn = QAction("Step Return", self)
        self.actionStepReturn.triggered.connect(
            lambda: self.perform_step_return())
        self.actionStepReturn.setIcon(load_icon('stepout.svg'))

        # session control menu
        self.controlMenu = QMenu("Process Control", self)
        self.controlMenu.addAction(self.actionRun)
        self.controlMenu.addAction(self.actionRestart)
        self.controlMenu.addAction(self.actionQuit)
        self.controlMenu.addSeparator()
        self.controlMenu.addAction(self.actionAttach)
        self.controlMenu.addAction(self.actionDetach)
        self.controlMenu.addSeparator()
        self.controlMenu.addAction(self.actionSettings)

        self.stepIntoMenu = QMenu("Step Into", self)
        self.stepIntoMenu.addAction(self.actionStepIntoIL)
        self.stepIntoMenu.addAction(self.actionStepIntoAsm)

        self.stepOverMenu = QMenu("Step Over", self)
        self.stepOverMenu.addAction(self.actionStepOverIL)
        self.stepOverMenu.addAction(self.actionStepOverAsm)

        self.btnControl = QToolButton(self)
        self.btnControl.setMenu(self.controlMenu)
        self.btnControl.setPopupMode(QToolButton.MenuButtonPopup)
        self.btnControl.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnControl.setDefaultAction(self.actionRun)
        self.addWidget(self.btnControl)

        # execution control buttons
        self.btnPauseResume = QToolButton(self)
        self.btnPauseResume.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnPauseResume.setDefaultAction(self.actionPause)
        self.addWidget(self.btnPauseResume)

        #self.addAction(self.actionPause)
        #self.addAction(self.actionResume)

        self.btnStepInto = QToolButton(self)
        self.btnStepInto.setMenu(self.stepIntoMenu)
        self.btnStepInto.setPopupMode(QToolButton.MenuButtonPopup)
        self.btnStepInto.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnStepInto.setDefaultAction(self.actionStepIntoIL)
        self.addWidget(self.btnStepInto)

        self.btnStepOver = QToolButton(self)
        self.btnStepOver.setMenu(self.stepOverMenu)
        self.btnStepOver.setPopupMode(QToolButton.MenuButtonPopup)
        self.btnStepOver.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnStepOver.setDefaultAction(self.actionStepOverIL)
        self.addWidget(self.btnStepOver)

        # TODO: Step until returning from current function
        self.btnStepReturn = QToolButton(self)
        self.btnStepReturn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnStepReturn.setDefaultAction(self.actionStepReturn)
        self.addWidget(self.btnStepReturn)

        #self.addAction(self.actionStepReturn)

        self.threadMenu = QMenu("Threads", self)

        self.btnThreads = QToolButton(self)
        self.btnThreads.setMenu(self.threadMenu)
        self.btnThreads.setPopupMode(QToolButton.InstantPopup)
        self.btnThreads.setToolButtonStyle(Qt.ToolButtonTextOnly)
        self.addWidget(self.btnThreads)

        self.set_thread_list([])

        self.editStatus = QLineEdit('INACTIVE', self)
        self.editStatus.setReadOnly(True)
        self.editStatus.setAlignment(QtCore.Qt.AlignCenter)
        self.addWidget(self.editStatus)

        # disable buttons
        self.set_actions_enabled(Run=self.can_exec(),
                                 Restart=False,
                                 Quit=False,
                                 Attach=self.can_connect(),
                                 Detach=False,
                                 Pause=False,
                                 Resume=False,
                                 StepInto=False,
                                 StepOver=False,
                                 StepReturn=False)
        self.set_resume_pause_action("Pause")
        self.set_default_process_action(
            "Attach" if self.can_connect() else "Run")

    def __del__(self):
        # TODO: Move this elsewhere
        # This widget is tasked with cleaning up the state after the view is closed
        # binjaplug.delete_state(self.bv)
        pass

    # -------------------------------------------------------------------------
    # Helpers
    # -------------------------------------------------------------------------
    def can_exec(self):
        return DebugAdapter.ADAPTER_TYPE.use_exec(
            self.debug_state.adapter_type)

    def can_connect(self):
        return DebugAdapter.ADAPTER_TYPE.use_connect(
            self.debug_state.adapter_type)

    def alert_need_install(self, proc):
        message = "Cannot start debugger: {} not found on {}.".format(
            proc,
            "the target machine" if self.can_connect() else "your machine")

        adapter_type = self.debug_state.adapter_type

        # TODO: detect remote os correctly, as gdb/lldb are compatible with both macos and linux
        if adapter_type == DebugAdapter.ADAPTER_TYPE.LOCAL_GDB or adapter_type == DebugAdapter.ADAPTER_TYPE.REMOTE_GDB:
            remote_os = "Linux"
        elif adapter_type == DebugAdapter.ADAPTER_TYPE.LOCAL_LLDB or adapter_type == DebugAdapter.ADAPTER_TYPE.REMOTE_LLDB:
            remote_os = "Darwin"
        elif adapter_type == DebugAdapter.ADAPTER_TYPE.LOCAL_DBGENG or adapter_type == DebugAdapter.ADAPTER_TYPE.REMOTE_DBGENG:
            remote_os = "Windows"
        else:
            # Uncertain
            remote_os = platform.system()

        if remote_os == "Linux":
            message += "\nYou can find this in your package manager or build it from source."
        elif remote_os == "Darwin":
            if proc == "lldb":
                message += "\nYou need to install it by running the following command in Terminal:\nxcode-select --install"
            elif proc == "gdbserver":
                message += "\nYou can find this in your package manager or build it from source."
            else:
                message += "\nYou need to install this manually."
        elif remote_os == "Windows":
            # TODO: dbgeng does not currently throw this
            message += "\nYou need to reinstall the debugger plugin."
        else:
            message += "\nYou need to install this manually."

        show_message_box("Cannot Start Debugger",
                         message,
                         icon=MessageBoxIcon.ErrorIcon)

    # -------------------------------------------------------------------------
    # UIActions
    # -------------------------------------------------------------------------

    def perform_run(self):
        def perform_run_thread():
            while True:
                try:
                    self.debug_state.run()
                    execute_on_main_thread_and_wait(perform_run_after)
                except ConnectionRefusedError:
                    execute_on_main_thread_and_wait(
                        lambda: perform_run_error('ERROR: Connection Refused'))
                except DebugAdapter.ProcessStartError as e:
                    execute_on_main_thread_and_wait(
                        lambda: perform_run_error(str(e)))
                except DebugAdapter.NotExecutableError as e:
                    fpath = e.args[0]
                    if platform.system() != 'Windows':
                        msg = '%s is not executable, would you like to set +x and retry?' % fpath
                        res = show_message_box(
                            'Error', msg, MessageBoxButtonSet.YesNoButtonSet,
                            MessageBoxIcon.ErrorIcon)
                        if res == MessageBoxButtonResult.YesButton:
                            os.chmod(fpath, os.stat(fpath).st_mode | 0o100)
                            continue
                    execute_on_main_thread_and_wait(lambda: perform_run_error(
                        'ERROR: Target Not Executable'))
                except DebugAdapter.NotInstalledError as e:
                    execute_on_main_thread_and_wait(
                        lambda: self.alert_need_install(e.args[0]))
                    execute_on_main_thread_and_wait(lambda: perform_run_error(
                        'ERROR: Debugger Not Installed'))
                except DebugAdapter.PermissionDeniedError as e:
                    execute_on_main_thread_and_wait(
                        lambda: perform_run_error('ERROR: Permission denied'))
                    if platform.system() == 'Darwin':
                        res = show_message_box(
                            'Error',
                            'Developer tools need to be enabled to debug programs. This can be authorized either from here or by starting a debugger in Xcode.',
                            MessageBoxButtonSet.OKButtonSet,
                            MessageBoxIcon.ErrorIcon)
                except Exception as e:
                    execute_on_main_thread_and_wait(lambda: perform_run_error(
                        'ERROR: ' + ' '.join(e.args)))
                    traceback.print_exc(file=sys.stderr)

                break

        def perform_run_after():
            self.state_stopped()
            self.debug_state.ui.on_step()

        def perform_run_error(e):
            self.state_error(e)

        self.state_starting('STARTING')

        threading.Thread(target=perform_run_thread).start()

    def perform_restart(self):
        def perform_restart_thread():
            try:
                self.debug_state.restart()
                execute_on_main_thread_and_wait(perform_restart_after)
            except ConnectionRefusedError:
                execute_on_main_thread_and_wait(
                    lambda: perform_restart_error('ERROR: Connection Refused'))
            except Exception as e:
                execute_on_main_thread_and_wait(lambda: perform_restart_error(
                    'ERROR: ' + ' '.join(e.args)))
                traceback.print_exc(file=sys.stderr)

        def perform_restart_after():
            self.state_stopped()
            self.debug_state.ui.on_step()

        def perform_restart_error(e):
            self.state_error(e)

        self.state_starting('RESTARTING')
        threading.Thread(target=perform_restart_thread).start()

    def perform_quit(self):
        self.debug_state.quit()
        self.state_inactive()
        self.debug_state.ui.on_step()

    def perform_attach(self):
        def perform_attach_thread():
            try:
                self.debug_state.attach()
                execute_on_main_thread_and_wait(perform_attach_after)
            except ConnectionRefusedError:
                execute_on_main_thread_and_wait(
                    lambda: perform_attach_error('ERROR: Connection Refused'))
            except TimeoutError:
                execute_on_main_thread_and_wait(
                    lambda: perform_attach_error('ERROR: Connection Refused'))
            except Exception as e:
                execute_on_main_thread_and_wait(
                    lambda: perform_attach_error('ERROR: ' + ' '.join(e.args)))
                traceback.print_exc(file=sys.stderr)

        def perform_attach_after():
            self.state_stopped()
            self.debug_state.ui.on_step()

        def perform_attach_error(e):
            self.state_error(e)

        self.state_starting('ATTACHING')
        threading.Thread(target=perform_attach_thread).start()

    def perform_detach(self):
        self.debug_state.detach()
        self.state_inactive()
        self.debug_state.ui.on_step()

    def perform_settings(self):
        def settings_finished():
            if self.debug_state.running:
                self.state_running()
            elif self.debug_state.connected:
                local_rip = self.debug_state.local_ip
                if self.debug_state.bv.read(local_rip, 1) and len(
                        self.debug_state.bv.get_functions_containing(
                            local_rip)) > 0:
                    self.state_stopped()
                else:
                    self.state_stopped_extern()
            else:
                self.state_inactive()

        dialog = AdapterSettingsDialog.AdapterSettingsDialog(self, self.bv)
        dialog.show()
        dialog.finished.connect(settings_finished)

    def perform_pause(self):
        self.debug_state.pause()
        # Don't update state here-- one of the other buttons is running in a thread and updating for us

    def perform_resume(self):
        def perform_resume_thread():
            (reason, data) = self.debug_state.go()
            execute_on_main_thread_and_wait(
                lambda: perform_resume_after(reason, data))

        def perform_resume_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_running()
        threading.Thread(target=perform_resume_thread).start()

    def perform_step_into_asm(self):
        def perform_step_into_asm_thread():
            (reason, data) = self.debug_state.step_into()
            execute_on_main_thread_and_wait(
                lambda: perform_step_into_asm_after(reason, data))

        def perform_step_into_asm_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_into_asm_thread).start()

    def perform_step_into_il(self):

        disasm = self.debug_state.ui.debug_view.binary_editor.getDisassembly()
        graph_type = disasm.getGraphType()

        def perform_step_into_il_thread():
            (reason, data) = self.debug_state.step_into(graph_type)
            execute_on_main_thread_and_wait(
                lambda: perform_step_into_il_after(reason, data))

        def perform_step_into_il_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_into_il_thread).start()

    def perform_step_over_asm(self):
        def perform_step_over_asm_thread():
            (reason, data) = self.debug_state.step_over()
            execute_on_main_thread_and_wait(
                lambda: perform_step_over_asm_after(reason, data))

        def perform_step_over_asm_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_over_asm_thread).start()

    def perform_step_over_il(self):

        disasm = self.debug_state.ui.debug_view.binary_editor.getDisassembly()
        graph_type = disasm.getGraphType()

        def perform_step_over_il_thread():
            (reason, data) = self.debug_state.step_over(graph_type)
            execute_on_main_thread_and_wait(
                lambda: perform_step_over_il_after(reason, data))

        def perform_step_over_il_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_over_il_thread).start()

    def perform_step_return(self):
        def perform_step_return_thread():
            (reason, data) = self.debug_state.step_return()
            execute_on_main_thread_and_wait(
                lambda: perform_step_return_after(reason, data))

        def perform_step_return_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_return_thread).start()

    # -------------------------------------------------------------------------
    # Control state setters
    # -------------------------------------------------------------------------

    def set_actions_enabled(self, **kwargs):
        def enable_step_into(e):
            self.actionStepIntoAsm.setEnabled(e)
            self.actionStepIntoIL.setEnabled(e)

        def enable_step_over(e):
            self.actionStepOverAsm.setEnabled(e)
            self.actionStepOverIL.setEnabled(e)

        def enable_starting(e):
            self.actionRun.setEnabled(e and self.can_exec())
            self.actionAttach.setEnabled(e and self.can_connect())

        def enable_stopping(e):
            self.actionRestart.setEnabled(e)
            self.actionQuit.setEnabled(e)
            self.actionDetach.setEnabled(e)

        def enable_stepping(e):
            self.actionStepIntoAsm.setEnabled(e)
            self.actionStepIntoIL.setEnabled(e)
            self.actionStepOverAsm.setEnabled(e)
            self.actionStepOverIL.setEnabled(e)
            self.actionStepReturn.setEnabled(e)

        actions = {
            "Run": lambda e: self.actionRun.setEnabled(e),
            "Restart": lambda e: self.actionRestart.setEnabled(e),
            "Quit": lambda e: self.actionQuit.setEnabled(e),
            "Attach": lambda e: self.actionAttach.setEnabled(e),
            "Detach": lambda e: self.actionDetach.setEnabled(e),
            "Pause": lambda e: self.actionPause.setEnabled(e),
            "Resume": lambda e: self.actionResume.setEnabled(e),
            "StepInto": enable_step_into,
            "StepOver": enable_step_over,
            "StepReturn": lambda e: self.actionStepReturn.setEnabled(e),
            "Threads": lambda e: self.btnThreads.setEnabled(e),
            "Starting": enable_starting,
            "Stopping": enable_stopping,
            "Stepping": enable_stepping,
        }
        for (action, enabled) in kwargs.items():
            actions[action](enabled)

    def set_default_process_action(self, action):
        actions = {
            "Run": self.actionRun,
            "Restart": self.actionRestart,
            "Quit": self.actionQuit,
            "Attach": self.actionAttach,
            "Detach": self.actionDetach,
        }
        self.btnControl.setDefaultAction(actions[action])

    def set_resume_pause_action(self, action):
        lookup = {'Resume': self.actionResume, 'Pause': self.actionPause}
        self.btnPauseResume.setDefaultAction(lookup[action])

    def set_thread_list(self, threads):
        def select_thread_fn(tid):
            def select_thread(tid):
                stateObj = binjaplug.get_state(self.bv)
                if stateObj.connected and not stateObj.running:
                    stateObj.threads.current = tid
                    stateObj.ui.context_display()
                    stateObj.ui.on_step()
                else:
                    print('cannot set thread in current state')

            return lambda: select_thread(tid)

        self.threadMenu.clear()
        if len(threads) > 0:
            for thread in threads:
                item_name = "Thread {} at {}".format(thread['tid'],
                                                     hex(thread['ip']))
                action = self.threadMenu.addAction(
                    item_name, select_thread_fn(thread['tid']))
                if thread['selected']:
                    self.btnThreads.setDefaultAction(action)
        else:
            defaultThreadAction = self.threadMenu.addAction("Thread List")
            defaultThreadAction.setEnabled(False)
            self.btnThreads.setDefaultAction(defaultThreadAction)

    # -------------------------------------------------------------------------
    # State handling
    # -------------------------------------------------------------------------

    def state_starting(self, msg=None):
        self.editStatus.setText(msg or 'INACTIVE')
        self.set_actions_enabled(Starting=False,
                                 Stopping=False,
                                 Stepping=False,
                                 Pause=False,
                                 Resume=False,
                                 Threads=False)
        self.set_default_process_action(
            "Attach" if self.can_connect() else "Run")
        self.set_thread_list([])
        self.set_resume_pause_action("Pause")

    def state_inactive(self, msg=None):
        self.editStatus.setText(msg or 'INACTIVE')
        self.set_actions_enabled(Starting=True,
                                 Stopping=False,
                                 Stepping=False,
                                 Pause=False,
                                 Resume=False,
                                 Threads=False)
        self.set_default_process_action(
            "Attach" if self.can_connect() else "Run")
        self.set_thread_list([])
        self.set_resume_pause_action("Pause")

    def state_stopped(self, msg=None):
        self.editStatus.setText(msg or 'STOPPED')
        self.set_actions_enabled(Starting=False,
                                 Stopping=True,
                                 Stepping=True,
                                 Pause=True,
                                 Resume=True,
                                 Threads=True)
        self.set_default_process_action("Quit")
        self.set_resume_pause_action("Resume")

    def state_stopped_extern(self, msg=None):
        self.editStatus.setText(msg or 'STOPPED')
        self.set_actions_enabled(Starting=False,
                                 Stopping=True,
                                 Stepping=True,
                                 StepReturn=False,
                                 Pause=True,
                                 Resume=True,
                                 Threads=True)
        self.set_default_process_action("Quit")
        self.set_resume_pause_action("Resume")

    def state_running(self, msg=None):
        self.editStatus.setText(msg or 'RUNNING')
        self.set_actions_enabled(Starting=False,
                                 Stopping=True,
                                 Stepping=False,
                                 Pause=True,
                                 Resume=False,
                                 Threads=False)
        self.set_default_process_action("Quit")
        self.set_resume_pause_action("Pause")

    def state_busy(self, msg=None):
        self.editStatus.setText(msg or 'RUNNING')
        self.set_actions_enabled(Starting=False,
                                 Stopping=True,
                                 Stepping=False,
                                 Pause=True,
                                 Resume=False,
                                 Threads=False)
        self.set_default_process_action("Quit")
        self.set_resume_pause_action("Pause")

    def state_error(self, msg=None):
        self.editStatus.setText(msg or 'ERROR')
        self.set_actions_enabled(Starting=True,
                                 Stopping=False,
                                 Pause=False,
                                 Resume=False,
                                 Stepping=False,
                                 Threads=False)
        self.set_default_process_action(
            "Attach" if self.can_connect() else "Run")
        self.set_thread_list([])
        self.set_resume_pause_action("Resume")

    def handle_stop_return(self, reason, data):
        if reason == DebugAdapter.STOP_REASON.STDOUT_MESSAGE:
            self.state_stopped('stdout: ' + data)
        elif reason == DebugAdapter.STOP_REASON.PROCESS_EXITED:
            self.debug_state.quit()
            self.state_inactive('process exited, return code=%d' % data)
        elif reason == DebugAdapter.STOP_REASON.BACKEND_DISCONNECTED:
            self.debug_state.quit()
            self.state_inactive('backend disconnected (process exited?)')
コード例 #8
0
class PlotImage(FigureCanvas):
    def __init__(self, model, parent, main_window):

        self.figure = Figure(dpi=main_window.logicalDpiX())
        super().__init__(self.figure)

        FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)

        FigureCanvas.updateGeometry(self)
        self.model = model
        self.main_window = main_window
        self.parent = parent

        self.rubber_band = QRubberBand(QRubberBand.Rectangle, self)
        self.band_origin = QtCore.QPoint()
        self.x_plot_origin = None
        self.y_plot_origin = None

        self.colorbar = None
        self.data_indicator = None
        self.tally_data_indicator = None
        self.image = None

        self.menu = QMenu(self)

    def enterEvent(self, event):
        self.setCursor(QtCore.Qt.CrossCursor)
        self.main_window.coord_label.show()

    def leaveEvent(self, event):
        self.main_window.coord_label.hide()
        self.main_window.statusBar().showMessage("")

    def mousePressEvent(self, event):
        self.main_window.coord_label.hide()
        position = event.pos()
        # Set rubber band absolute and relative position
        self.band_origin = position
        self.x_plot_origin, self.y_plot_origin = self.getPlotCoords(position)

        # Create rubber band
        self.rubber_band.setGeometry(
            QtCore.QRect(self.band_origin, QtCore.QSize()))

    def getPlotCoords(self, pos):
        x, y = self.mouseEventCoords(pos)

        # get the normalized axis coordinates from the event display units
        transform = self.ax.transAxes.inverted()
        xPlotCoord, yPlotCoord = transform.transform((x, y))
        # flip the y-axis (its zero is in the upper left)

        # scale axes using the plot extents
        xPlotCoord = self.ax.dataLim.x0 + xPlotCoord * self.ax.dataLim.width
        yPlotCoord = self.ax.dataLim.y0 + yPlotCoord * self.ax.dataLim.height

        # set coordinate label if pointer is in the axes
        if self.parent.underMouse():
            self.main_window.coord_label.show()
            self.main_window.showCoords(xPlotCoord, yPlotCoord)
        else:
            self.main_window.coord_label.hide()

        return (xPlotCoord, yPlotCoord)

    def _resize(self):
        z = self.main_window.zoom / 100.0
        # manage scroll bars
        if z <= 1.0:
            self.parent.verticalScrollBar().hide()
            self.parent.horizontalScrollBar().hide()
            self.parent.cornerWidget().hide()
            self.parent.verticalScrollBar().setEnabled(False)
            self.parent.horizontalScrollBar().setEnabled(False)
        else:
            self.parent.verticalScrollBar().show()
            self.parent.horizontalScrollBar().show()
            self.parent.cornerWidget().show()
            self.parent.verticalScrollBar().setEnabled(True)
            self.parent.horizontalScrollBar().setEnabled(True)

        # resize plot
        self.resize(self.parent.width() * z, self.parent.height() * z)

    def getDataIndices(self, event):
        cv = self.model.currentView

        x, y = self.mouseEventCoords(event.pos())

        # get origin in axes coordinates
        x0, y0 = self.ax.transAxes.transform((0.0, 0.0))

        # get the extents of the axes box in axes coordinates
        bbox = self.ax.get_window_extent().transformed(
            self.figure.dpi_scale_trans.inverted())
        # get dimensions and scale using dpi
        width, height = bbox.width, bbox.height
        width *= self.figure.dpi
        height *= self.figure.dpi

        # use factor to get proper x,y position in pixels
        factor = (width / cv.h_res, height / cv.v_res)
        xPos = int((x - x0 + 0.01) / factor[0])
        # flip y-axis
        yPos = cv.v_res - int((y - y0 + 0.01) / factor[1])

        return xPos, yPos

    def getTallyIndices(self, event):

        xPos, yPos = self.getPlotCoords(event.pos())

        ext = self.model.tally_extents

        x0 = ext[0]
        y0 = ext[2]

        v_res, h_res = self.model.tally_data.shape

        dx = (ext[1] - ext[0]) / h_res
        dy = (ext[3] - ext[2]) / v_res

        i = int((xPos - x0) // dx)
        j = v_res - int((yPos - y0) // dy) - 1

        return i, j

    def getTallyInfo(self, event):
        cv = self.model.currentView

        xPos, yPos = self.getTallyIndices(event)

        if self.model.tally_data is None:
            return -1, None

        if not cv.selectedTally or not cv.tallyDataVisible:
            return -1, None

        # don't look up mesh filter data (for now)
        tally = self.model.statepoint.tallies[cv.selectedTally]

        # check that the position is in the axes view
        v_res, h_res = self.model.tally_data.shape
        if 0 <= yPos < v_res and 0 <= xPos < h_res:
            value = self.model.tally_data[yPos][xPos]
        else:
            value = None

        return cv.selectedTally, value

    def getIDinfo(self, event):

        xPos, yPos = self.getDataIndices(event)

        # check that the position is in the axes view
        if 0 <= yPos < self.model.currentView.v_res \
           and 0 <= xPos and xPos < self.model.currentView.h_res:
            id = self.model.ids[yPos][xPos]
            temp = "{:g}".format(self.model.properties[yPos][xPos][0])
            density = "{:g}".format(self.model.properties[yPos][xPos][1])
        else:
            id = _NOT_FOUND
            density = str(_NOT_FOUND)
            temp = str(_NOT_FOUND)

        if self.model.currentView.colorby == 'cell':
            domain = self.model.activeView.cells
            domain_kind = 'Cell'
        elif self.model.currentView.colorby == 'temperature':
            domain = self.model.activeView.materials
            domain_kind = 'Temperature'
        elif self.model.currentView.colorby == 'density':
            domain = self.model.activeView.materials
            domain_kind = 'Density'
        else:
            domain = self.model.activeView.materials
            domain_kind = 'Material'

        properties = {'density': density, 'temperature': temp}

        return id, properties, domain, domain_kind

    def mouseDoubleClickEvent(self, event):
        xCenter, yCenter = self.getPlotCoords(event.pos())
        self.main_window.editPlotOrigin(xCenter, yCenter, apply=True)

    def mouseMoveEvent(self, event):
        cv = self.model.currentView
        # Show Cursor position relative to plot in status bar
        xPlotPos, yPlotPos = self.getPlotCoords(event.pos())

        # Show Cell/Material ID, Name in status bar
        id, properties, domain, domain_kind = self.getIDinfo(event)

        domainInfo = ""
        tallyInfo = ""

        if self.parent.underMouse():

            if domain_kind.lower() in _MODEL_PROPERTIES:
                line_val = float(properties[domain_kind.lower()])
                line_val = max(line_val, 0.0)
                self.updateDataIndicatorValue(line_val)
                domain_kind = 'Material'

            temperature = properties['temperature']
            density = properties['density']

            if id == _VOID_REGION:
                domainInfo = ("VOID")
            elif id == _OVERLAP:
                domainInfo = ("OVERLAP")
            elif id != _NOT_FOUND and domain[id].name:
                domainInfo = ("{} {}: \"{}\"\t Density: {} g/cc\t"
                              "Temperature: {} K".format(
                                  domain_kind, id, domain[id].name, density,
                                  temperature))
            elif id != _NOT_FOUND:
                domainInfo = ("{} {}\t Density: {} g/cc\t"
                              "Temperature: {} K".format(
                                  domain_kind, id, density, temperature))
            else:
                domainInfo = ""

            if self.model.tally_data is not None:
                tid, value = self.getTallyInfo(event)
                if value is not None and value != np.nan:
                    self.updateTallyDataIndicatorValue(value)
                    tallyInfo = "Tally {} {}: {:.5E}".format(
                        tid, cv.tallyValue, value)
                else:
                    self.updateTallyDataIndicatorValue(0.0)
        else:
            self.updateTallyDataIndicatorValue(0.0)
            self.updateDataIndicatorValue(0.0)

        if domainInfo:
            self.main_window.statusBar().showMessage(" " + domainInfo +
                                                     "      " + tallyInfo)
        else:
            self.main_window.statusBar().showMessage(" " + tallyInfo)

        # Update rubber band and values if mouse button held down
        if event.buttons() == QtCore.Qt.LeftButton:
            self.rubber_band.setGeometry(
                QtCore.QRect(self.band_origin, event.pos()).normalized())

            # Show rubber band if both dimensions > 10 pixels
            if self.rubber_band.width() > 10 and self.rubber_band.height(
            ) > 10:
                self.rubber_band.show()
            else:
                self.rubber_band.hide()

            # Update plot X Origin
            xCenter = (self.x_plot_origin + xPlotPos) / 2
            yCenter = (self.y_plot_origin + yPlotPos) / 2
            self.main_window.editPlotOrigin(xCenter, yCenter)

            modifiers = event.modifiers()

            # Zoom out if Shift held
            if modifiers == QtCore.Qt.ShiftModifier:
                cv = self.model.currentView
                bandwidth = abs(self.band_origin.x() - event.pos().x())
                width = cv.width * (cv.h_res / max(bandwidth, .001))
                bandheight = abs(self.band_origin.y() - event.pos().y())
                height = cv.height * (cv.v_res / max(bandheight, .001))
            # Zoom in
            else:
                width = max(abs(self.x_plot_origin - xPlotPos), 0.1)
                height = max(abs(self.y_plot_origin - yPlotPos), 0.1)

            self.main_window.editWidth(width)
            self.main_window.editHeight(height)

    def mouseReleaseEvent(self, event):

        if self.rubber_band.isVisible():
            self.rubber_band.hide()
            self.main_window.applyChanges()
        else:
            self.main_window.revertDockControls()

    def wheelEvent(self, event):

        if event.delta() and event.modifiers() == QtCore.Qt.ShiftModifier:
            numDegrees = event.delta() / 8

            if 24 < self.main_window.zoom + numDegrees < 5001:
                self.main_window.editZoom(self.main_window.zoom + numDegrees)

    def contextMenuEvent(self, event):

        self.menu.clear()

        self.main_window.undoAction.setText('&Undo ({})'.format(
            len(self.model.previousViews)))
        self.main_window.redoAction.setText('&Redo ({})'.format(
            len(self.model.subsequentViews)))

        id, properties, domain, domain_kind = self.getIDinfo(event)

        cv = self.model.currentView

        # always provide undo option
        self.menu.addSeparator()
        self.menu.addAction(self.main_window.undoAction)
        self.menu.addAction(self.main_window.redoAction)
        self.menu.addSeparator()

        if int(id) not in (_NOT_FOUND, _OVERLAP) and \
           cv.colorby not in _MODEL_PROPERTIES:

            # Domain ID
            if domain[id].name:
                domainID = self.menu.addAction("{} {}: \"{}\"".format(
                    domain_kind, id, domain[id].name))
            else:
                domainID = self.menu.addAction("{} {}".format(domain_kind, id))

            self.menu.addSeparator()

            colorAction = self.menu.addAction(
                'Edit {} Color...'.format(domain_kind))
            colorAction.setDisabled(cv.highlighting)
            colorAction.setToolTip('Edit {} color'.format(domain_kind))
            colorAction.setStatusTip('Edit {} color'.format(domain_kind))
            domain_color_connector = partial(self.main_window.editDomainColor,
                                             domain_kind, id)
            colorAction.triggered.connect(domain_color_connector)

            maskAction = self.menu.addAction('Mask {}'.format(domain_kind))
            maskAction.setCheckable(True)
            maskAction.setChecked(domain[id].masked)
            maskAction.setDisabled(not cv.masking)
            maskAction.setToolTip('Toggle {} mask'.format(domain_kind))
            maskAction.setStatusTip('Toggle {} mask'.format(domain_kind))
            mask_connector = partial(self.main_window.toggleDomainMask,
                                     kind=domain_kind,
                                     id=id)
            maskAction.toggled.connect(mask_connector)

            highlightAction = self.menu.addAction(
                'Highlight {}'.format(domain_kind))
            highlightAction.setCheckable(True)
            highlightAction.setChecked(domain[id].highlight)
            highlightAction.setDisabled(not cv.highlighting)
            highlightAction.setToolTip(
                'Toggle {} highlight'.format(domain_kind))
            highlightAction.setStatusTip(
                'Toggle {} highlight'.format(domain_kind))
            highlight_connector = partial(
                self.main_window.toggleDomainHighlight,
                kind=domain_kind,
                id=id)
            highlightAction.toggled.connect(highlight_connector)

        else:
            self.menu.addAction(self.main_window.undoAction)
            self.menu.addAction(self.main_window.redoAction)

            if cv.colorby not in _MODEL_PROPERTIES:
                self.menu.addSeparator()
                if int(id) == _NOT_FOUND:
                    bgColorAction = self.menu.addAction(
                        'Edit Background Color...')
                    bgColorAction.setToolTip('Edit background color')
                    bgColorAction.setStatusTip('Edit plot background color')
                    connector = partial(self.main_window.editBackgroundColor,
                                        apply=True)
                    bgColorAction.triggered.connect(connector)
                elif int(id) == _OVERLAP:
                    olapColorAction = self.menu.addAction(
                        'Edit Overlap Color...')
                    olapColorAction.setToolTip('Edit overlap color')
                    olapColorAction.setStatusTip('Edit plot overlap color')
                    connector = partial(self.main_window.editOverlapColor,
                                        apply=True)
                    olapColorAction.triggered.connect(connector)

        self.menu.addSeparator()
        self.menu.addAction(self.main_window.saveImageAction)
        self.menu.addAction(self.main_window.saveViewAction)
        self.menu.addAction(self.main_window.openAction)
        self.menu.addSeparator()
        self.menu.addMenu(self.main_window.basisMenu)
        self.menu.addMenu(self.main_window.colorbyMenu)
        self.menu.addSeparator()
        if domain_kind.lower() not in ('density', 'temperature'):
            self.menu.addAction(self.main_window.maskingAction)
            self.menu.addAction(self.main_window.highlightingAct)
            self.menu.addAction(self.main_window.overlapAct)
            self.menu.addSeparator()
        self.menu.addAction(self.main_window.dockAction)

        self.main_window.maskingAction.setChecked(cv.masking)
        self.main_window.highlightingAct.setChecked(cv.highlighting)
        self.main_window.overlapAct.setChecked(cv.color_overlaps)

        if self.main_window.dock.isVisible():
            self.main_window.dockAction.setText('Hide &Dock')
        else:
            self.main_window.dockAction.setText('Show &Dock')

        self.menu.exec_(event.globalPos())

    def generatePixmap(self, update=False):
        self.model.generatePlot()
        if update:
            self.updatePixmap()

    def updatePixmap(self):

        # clear out figure
        self.figure.clear()

        cv = self.model.currentView
        # set figure bg color to match window
        window_bg = self.parent.palette().color(QtGui.QPalette.Background)
        self.figure.patch.set_facecolor(rgb_normalize(window_bg.getRgb()))

        # set data extents for automatic reporting of pointer location
        # in model units
        data_bounds = [
            cv.origin[self.main_window.xBasis] - cv.width / 2.,
            cv.origin[self.main_window.xBasis] + cv.width / 2.,
            cv.origin[self.main_window.yBasis] - cv.height / 2.,
            cv.origin[self.main_window.yBasis] + cv.height / 2.
        ]

        # make sure we have a domain image to load
        if not hasattr(self.model, 'image'):
            self.model.generatePlot()

        ### DRAW DOMAIN IMAGE ###

        # still generate the domain image if the geometric
        # plot isn't visible so mouse-over info can still
        # be shown
        alpha = cv.domainAlpha if cv.domainVisible else 0.0
        if cv.colorby in ('material', 'cell'):
            self.image = self.figure.subplots().imshow(self.model.image,
                                                       extent=data_bounds,
                                                       alpha=alpha)
        else:
            cmap = cv.colormaps[cv.colorby]
            if cv.colorby == 'temperature':
                idx = 0
                cmap_label = "Temperature (K)"
            else:
                idx = 1
                cmap_label = "Density (g/cc)"

            norm = SymLogNorm(1E-10) if cv.color_scale_log[
                cv.colorby] else None

            data = self.model.properties[:, :, idx]
            self.image = self.figure.subplots().imshow(data,
                                                       cmap=cmap,
                                                       norm=norm,
                                                       extent=data_bounds,
                                                       alpha=cv.domainAlpha)

            # add colorbar
            self.colorbar = self.figure.colorbar(self.image, anchor=(1.0, 0.0))
            self.colorbar.set_label(cmap_label, rotation=-90, labelpad=15)
            # draw line on colorbar
            dl = self.colorbar.ax.dataLim.get_points()
            self.data_indicator = mlines.Line2D(dl[:][0], [0.0, 0.0],
                                                linewidth=3.,
                                                color='blue',
                                                clip_on=True)
            self.colorbar.ax.add_line(self.data_indicator)
            self.colorbar.ax.margins(0.0, 0.0)
            self.updateDataIndicatorVisibility()
            self.updateColorMinMax(cv.colorby)

        self.ax = self.figure.axes[0]
        self.ax.margins(0.0, 0.0)

        # set axis labels
        axis_label_str = "{} (cm)"
        self.ax.set_xlabel(axis_label_str.format(cv.basis[0]))
        self.ax.set_ylabel(axis_label_str.format(cv.basis[1]))

        # generate tally image
        image_data, extents, data_min, data_max, units = self.model.create_tally_image(
        )

        ### DRAW TALLY IMAGE ###

        # draw tally image
        if image_data is not None:

            if not cv.tallyDataUserMinMax:
                cv.tallyDataMin = data_min
                cv.tallyDataMax = data_max
            else:
                data_min = cv.tallyDataMin
                data_max = cv.tallyDataMax

            # always mask out negative values
            image_mask = image_data < 0.0

            if cv.clipTallyData:
                image_mask |= image_data < data_min
                image_mask |= image_data > data_max

            if cv.tallyMaskZeroValues:
                image_mask |= image_data == 0.0

            # mask out invalid values
            image_data = np.ma.masked_where(image_mask, image_data)

            if extents is None:
                extents = data_bounds

            self.model.tally_data = image_data
            self.model.tally_extents = extents if extents is not None else data_bounds

            norm = SymLogNorm(1E-30) if cv.tallyDataLogScale else None

            if cv.tallyContours:
                # parse the levels line
                levels = self.parseContoursLine(cv.tallyContourLevels)
                self.tally_image = self.ax.contour(image_data,
                                                   origin='image',
                                                   levels=levels,
                                                   alpha=cv.tallyDataAlpha,
                                                   cmap=cv.tallyDataColormap,
                                                   norm=norm,
                                                   extent=extents)

            else:
                self.tally_image = self.ax.imshow(image_data,
                                                  alpha=cv.tallyDataAlpha,
                                                  cmap=cv.tallyDataColormap,
                                                  norm=norm,
                                                  extent=extents)
            # add colorbar
            self.tally_colorbar = self.figure.colorbar(self.tally_image,
                                                       anchor=(1.0, 0.0))

            if cv.tallyContours:
                fmt = "%.2E"
                self.ax.clabel(self.tally_image,
                               self.tally_image.levels,
                               inline=True,
                               fmt=fmt)

            # draw line on colorbar
            self.tally_data_indicator = mlines.Line2D([0.0, 1.0], [0.0, 0.0],
                                                      linewidth=3.,
                                                      color='blue',
                                                      clip_on=True)
            self.tally_colorbar.ax.add_line(self.tally_data_indicator)
            self.tally_colorbar.ax.margins(0.0, 0.0)

            self.tally_data_indicator.set_visible(cv.tallyDataIndicator)

            self.main_window.updateTallyMinMax()

            self.tally_colorbar.mappable.set_clim(data_min, data_max)
            self.tally_colorbar.set_label(units, rotation=-90, labelpad=15)

        # annotate outlines
        self.add_outlines()

        # always make sure the data bounds are set correctly
        self.ax.set_xbound(data_bounds[0], data_bounds[1])
        self.ax.set_ybound(data_bounds[2], data_bounds[3])
        self.ax.dataLim.x0 = data_bounds[0]
        self.ax.dataLim.x1 = data_bounds[1]
        self.ax.dataLim.y0 = data_bounds[2]
        self.ax.dataLim.y1 = data_bounds[3]

        self.draw()
        return "Done"

    def add_outlines(self):
        cv = self.model.currentView
        # draw outlines as isocontours
        if cv.outlines:
            # set data extents for automatic reporting of pointer location
            data_bounds = [
                cv.origin[self.main_window.xBasis] - cv.width / 2.,
                cv.origin[self.main_window.xBasis] + cv.width / 2.,
                cv.origin[self.main_window.yBasis] - cv.height / 2.,
                cv.origin[self.main_window.yBasis] + cv.height / 2.
            ]
            levels = np.unique(self.model.ids)
            self.contours = self.ax.contour(self.model.ids,
                                            origin='upper',
                                            colors='k',
                                            linestyles='solid',
                                            levels=levels,
                                            extent=data_bounds)

    @staticmethod
    def parseContoursLine(line):
        # if there are any commas in the line, treat as level values
        line = line.strip()
        if ',' in line:
            return [float(val) for val in line.split(",") if val != '']
        else:
            return int(line)

    def updateColorbarScale(self):
        self.updatePixmap()

    def updateTallyDataIndicatorValue(self, y_val):
        cv = self.model.currentView

        if not cv.tallyDataVisible or not cv.tallyDataIndicator:
            return

        if self.tally_data_indicator is not None:
            data = self.tally_data_indicator.get_data()
            # use norm to get axis value if log scale
            if cv.tallyDataLogScale:
                y_val = self.tally_image.norm(y_val)
            self.tally_data_indicator.set_data([data[0], [y_val, y_val]])
            dl_color = invert_rgb(self.tally_image.get_cmap()(y_val), True)
            self.tally_data_indicator.set_c(dl_color)
            self.draw()

    def updateDataIndicatorValue(self, y_val):
        cv = self.model.currentView

        if cv.colorby not in _MODEL_PROPERTIES or \
           not cv.data_indicator_enabled[cv.colorby]:
            return

        if self.data_indicator:
            data = self.data_indicator.get_data()
            # use norm to get axis value if log scale
            if cv.color_scale_log[cv.colorby]:
                y_val = self.image.norm(y_val)
            self.data_indicator.set_data([data[0], [y_val, y_val]])
            dl_color = invert_rgb(self.image.get_cmap()(y_val), True)
            self.data_indicator.set_c(dl_color)
            self.draw()

    def updateDataIndicatorVisibility(self):
        cv = self.model.currentView
        if self.data_indicator and cv.colorby in _MODEL_PROPERTIES:
            val = cv.data_indicator_enabled[cv.colorby]
            self.data_indicator.set_visible(val)
            self.draw()

    def updateColorMap(self, colormap_name, property_type):
        if self.colorbar and property_type == self.model.activeView.colorby:
            self.image.set_cmap(colormap_name)
            self.colorbar.draw_all()
            self.draw()

    def updateColorMinMax(self, property_type):
        av = self.model.activeView
        if self.colorbar and property_type == av.colorby:
            clim = av.getColorLimits(property_type)
            self.colorbar.mappable.set_clim(*clim)
            self.data_indicator.set_data(clim[:2], (0.0, 0.0))
            self.colorbar.draw_all()
            self.draw()
コード例 #9
0
class PlotImage(FigureCanvas):
    def __init__(self, model, parent, main):

        super(FigureCanvas, self).__init__(Figure())

        FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)

        FigureCanvas.updateGeometry(self)

        self.model = model
        self.mw = main
        self.parent = parent

        self.rubber_band = QRubberBand(QRubberBand.Rectangle, self)
        self.band_origin = QtCore.QPoint()
        self.x_plot_origin = None
        self.y_plot_origin = None

        self.menu = QMenu(self)

    def enterEvent(self, event):
        self.setCursor(QtCore.Qt.CrossCursor)
        self.mw.coord_label.show()

    def leaveEvent(self, event):
        self.mw.coord_label.hide()
        self.mw.statusBar().showMessage("")

    def mousePressEvent(self, event):
        self.mw.coord_label.hide()

        # Set rubber band absolute and relative position
        self.band_origin = event.pos()
        self.x_plot_origin, self.y_plot_origin = self.getPlotCoords(
            event.pos())

        # Create rubber band
        self.rubber_band.setGeometry(
            QtCore.QRect(self.band_origin, QtCore.QSize()))

        FigureCanvas.mousePressEvent(self, event)

    def getPlotCoords(self, pos):

        cv = self.model.currentView

        # get the normalized axis coordinates from the event display units
        xPlotCoord, yPlotCoord = self.ax.transAxes.inverted().transform(
            (pos.x(), pos.y()))
        # flip the y-axis (its zero is in the upper left)
        yPlotCoord = 1 - yPlotCoord

        # scale axes using the plot extents
        xPlotCoord = self.ax.dataLim.x0 + xPlotCoord * self.ax.dataLim.width
        yPlotCoord = self.ax.dataLim.y0 + yPlotCoord * self.ax.dataLim.height

        # set coordinate label if pointer is in the axes
        if self.ax.contains_point((pos.x(), pos.y())):
            self.mw.coord_label.show()
            self.mw.showCoords(xPlotCoord, yPlotCoord)
        else:
            self.mw.coord_label.hide()

        return (xPlotCoord, yPlotCoord)

    def getIDinfo(self, event):

        cv = self.model.currentView

        # get origin in axes coordinates
        x0, y0 = self.ax.transAxes.transform((0., 0.))

        # get the extents of the axes box in axes coordinates
        bbox = self.ax.get_window_extent().transformed(
            self.figure.dpi_scale_trans.inverted())
        # get dimensions and scale using dpi
        width, height = bbox.width, bbox.height
        width *= self.figure.dpi
        height *= self.figure.dpi

        # use factor to get proper x,y position in pixels
        factor = (width / cv.h_res, height / cv.v_res)
        xPos = int((event.pos().x() - x0 + 0.05) / factor[0])
        yPos = int((event.pos().y() - y0 + 0.05) / factor[1])

        # check that the position is in the axes view
        if yPos < self.model.currentView.v_res \
            and xPos < self.model.currentView.h_res:
            id = f"{self.model.ids[yPos][xPos]}"
            temp = f"{self.model.props[yPos][xPos][0]:g}"
            density = f"{self.model.props[yPos][xPos][1]:g}"
        else:
            id = '-1'
            density = '-1'
            temp = '-1'

        if self.model.currentView.colorby == 'cell':
            domain = self.model.activeView.cells
            domain_kind = 'Cell'
        else:
            domain = self.model.activeView.materials
            domain_kind = 'Material'

        properties = {'density': density, 'temperature': temp}

        return id, properties, domain, domain_kind

    def mouseDoubleClickEvent(self, event):

        xCenter, yCenter = self.getPlotCoords(event.pos())
        self.mw.editPlotOrigin(xCenter, yCenter, apply=True)

        FigureCanvas.mouseDoubleClickEvent(self, event)

    def mouseMoveEvent(self, event):

        # Show Cursor position relative to plot in status bar
        xPlotPos, yPlotPos = self.getPlotCoords(event.pos())

        # Show Cell/Material ID, Name in status bar
        id, properties, domain, domain_kind = self.getIDinfo(event)
        if self.ax.contains_point((event.pos().x(), event.pos().y())):

            if id != str(_NOT_FOUND_) and domain[id].name:
                domainInfo = (f"{domain_kind} {id}: \"{domain[id].name}\"\t "
                              f"Density: {properties['density']} g/cm3\t"
                              f"Temperature: {properties['temperature']} K")
            elif id != str(_NOT_FOUND_):
                domainInfo = (f"{domain_kind} {id}\t"
                              f"Density: {properties['density']} g/cm3\t"
                              f"Temperature: {properties['temperature']} K")

            else:
                domainInfo = ""
        else:
            domainInfo = ""

        self.mw.statusBar().showMessage(f" {domainInfo}")

        # Update rubber band and values if mouse button held down
        if event.buttons() == QtCore.Qt.LeftButton:
            self.rubber_band.setGeometry(
                QtCore.QRect(self.band_origin, event.pos()).normalized())

            # Show rubber band if both dimensions > 10 pixels
            if self.rubber_band.width() > 10 and self.rubber_band.height(
            ) > 10:
                self.rubber_band.show()
            else:
                self.rubber_band.hide()

            # Update plot X Origin
            xCenter = (self.x_plot_origin + xPlotPos) / 2
            yCenter = (self.y_plot_origin + yPlotPos) / 2
            self.mw.editPlotOrigin(xCenter, yCenter)

            modifiers = event.modifiers()

            # Zoom out if Shift held
            if modifiers == QtCore.Qt.ShiftModifier:
                cv = self.model.currentView
                bandwidth = abs(self.band_origin.x() - event.pos().x())
                width = cv.width * (cv.h_res / max(bandwidth, .001))
                bandheight = abs(self.band_origin.y() - event.pos().y())
                height = cv.height * (cv.v_res / max(bandheight, .001))
            else:  # Zoom in
                width = max(abs(self.x_plot_origin - xPlotPos), 0.1)
                height = max(abs(self.y_plot_origin - yPlotPos), 0.1)

            self.mw.editWidth(width)
            self.mw.editHeight(height)

    def mouseReleaseEvent(self, event):

        if self.rubber_band.isVisible():
            self.rubber_band.hide()
            self.mw.applyChanges()
        else:
            self.mw.revertDockControls()

    def wheelEvent(self, event):

        if event.delta() and event.modifiers() == QtCore.Qt.ShiftModifier:
            numDegrees = event.delta() / 8

            if 24 < self.mw.zoom + numDegrees < 5001:
                self.mw.editZoom(self.mw.zoom + numDegrees)

    def contextMenuEvent(self, event):

        self.menu.clear()

        self.mw.undoAction.setText(f'&Undo ({len(self.model.previousViews)})')
        self.mw.redoAction.setText(
            f'&Redo ({len(self.model.subsequentViews)})')

        id, properties, domain, domain_kind = self.getIDinfo(event)

        if id != '-1':

            # Domain ID
            domainID = self.menu.addAction(f"{domain_kind} {id}")
            domainID.setDisabled(True)

            # Domain Name (if any)
            if domain[id].name:
                domainName = self.menu.addAction(domain[id].name)
                domainName.setDisabled(True)

            self.menu.addSeparator()
            self.menu.addAction(self.mw.undoAction)
            self.menu.addAction(self.mw.redoAction)
            self.menu.addSeparator()

            colorAction = self.menu.addAction(f'Edit {domain_kind} Color...')
            colorAction.setDisabled(self.model.currentView.highlighting)
            colorAction.setToolTip(f'Edit {domain_kind} color')
            colorAction.setStatusTip(f'Edit {domain_kind} color')
            colorAction.triggered.connect(
                lambda: self.mw.editDomainColor(domain_kind, id))

            maskAction = self.menu.addAction(f'Mask {domain_kind}')
            maskAction.setCheckable(True)
            maskAction.setChecked(domain[id].masked)
            maskAction.setDisabled(not self.model.currentView.masking)
            maskAction.setToolTip(f'Toggle {domain_kind} mask')
            maskAction.setStatusTip(f'Toggle {domain_kind} mask')
            maskAction.triggered[bool].connect(
                lambda bool=bool: self.mw.toggleDomainMask(
                    bool, domain_kind, id))

            highlightAction = self.menu.addAction(f'Highlight {domain_kind}')
            highlightAction.setCheckable(True)
            highlightAction.setChecked(domain[id].highlighted)
            highlightAction.setDisabled(
                not self.model.currentView.highlighting)
            highlightAction.setToolTip(f'Toggle {domain_kind} highlight')
            highlightAction.setStatusTip(f'Toggle {domain_kind} highlight')
            highlightAction.triggered[bool].connect(
                lambda bool=bool: self.mw.toggleDomainHighlight(
                    bool, domain_kind, id))

        else:
            self.menu.addAction(self.mw.undoAction)
            self.menu.addAction(self.mw.redoAction)
            self.menu.addSeparator()
            bgColorAction = self.menu.addAction('Edit Background Color...')
            bgColorAction.setToolTip('Edit background color')
            bgColorAction.setStatusTip('Edit plot background color')
            bgColorAction.triggered.connect(
                lambda: self.mw.editBackgroundColor(apply=True))

        self.menu.addSeparator()
        self.menu.addAction(self.mw.saveImageAction)
        self.menu.addAction(self.mw.saveViewAction)
        self.menu.addAction(self.mw.openAction)
        self.menu.addSeparator()
        self.menu.addMenu(self.mw.basisMenu)
        self.menu.addMenu(self.mw.colorbyMenu)
        self.menu.addSeparator()
        self.menu.addAction(self.mw.maskingAction)
        self.menu.addAction(self.mw.highlightingAct)
        self.menu.addSeparator()
        self.menu.addAction(self.mw.dockAction)

        self.mw.maskingAction.setChecked(self.model.currentView.masking)
        self.mw.highlightingAct.setChecked(self.model.currentView.highlighting)

        if self.mw.dock.isVisible():
            self.mw.dockAction.setText('Hide &Dock')
        else:
            self.mw.dockAction.setText('Show &Dock')

        self.menu.exec_(event.globalPos())

    def setPixmap(self, w, h):

        # clear out figure
        self.figure.clear()

        cv = self.model.currentView
        # set figure bg color to match window
        window_background = self.parent.palette().color(
            QtGui.QPalette.Background)
        self.figure.patch.set_facecolor(
            rgb_normalize(window_background.getRgb()))
        # set figure width
        self.figure.set_figwidth(0.99 * w / self.figure.get_dpi())
        self.figure.set_figheight(0.99 * h / self.figure.get_dpi())
        # set data extents for automatic reporting of pointer location
        data_bounds = [
            cv.origin[self.mw.xBasis] - cv.width / 2.,
            cv.origin[self.mw.xBasis] + cv.width / 2.,
            cv.origin[self.mw.yBasis] - cv.height / 2.,
            cv.origin[self.mw.yBasis] + cv.height / 2.
        ]

        # make sure we have an image to load
        if not hasattr(self.model, 'image'):
            self.model.generatePlot()
        c = self.figure.subplots().imshow(self.model.image,
                                          extent=data_bounds,
                                          alpha=cv.plotAlpha)
        self.ax = self.figure.axes[0]
        self.ax.margins(0.0, 0.0)
        self.figure.set_tight_layout({'pad': 1.0})
        # set axis labels
        axis_label_str = "{} (cm)"
        self.ax.set_xlabel(axis_label_str.format(cv.basis[0]))
        self.ax.set_ylabel(axis_label_str.format(cv.basis[1]))
        self.draw()
コード例 #10
0
class TreeTaskList_supervisor(TreeTaskList_coordinator):
    def __init__(self, parent):
        super(TreeTaskList_supervisor, self).__init__(parent)

        self.setColumnCount(3)
        self.setColumnHidden(2, False)

        self.appendSupervisorConntectMenu()

    def appendSupervisorConntectMenu(self):
        self.processMenu = QMenu("Processes", self.menu)
        self.proceesAddSubMenu = QMenu("Add", self.processMenu)
        self.processMenu.addMenu(self.proceesAddSubMenu)
        self.processMenu.addAction("Remove", self.removeProcess)
        self.menu.addMenu(self.processMenu)

    def callContextMenu(self, pos):
        self.proceesAddSubMenu.clear()
        self.createProcessAddMenu(self.proceesAddSubMenu)
        self.menu.exec_(QCursor.pos())

    def createProcessAddMenu(self, processMenu):
        items = self.itemUtils.getSelected_shotItems()
        processesList = self.getProcessList(items)
        sKeysList = [it.data(0, Qt.UserRole) for it in items]

        server = self.taskManagerWdg.userServerCore.server
        server.start()
        for process in processesList:
            processMenu.addAction(
                "{}".format(process),
                lambda x=process: self.addTaskAction(server, sKeysList, x))
        server.finish()

    def getProcessList(self, items):
        def getItemType(sKey):
            idx0 = sKey.find('/')
            idx1 = sKey.find('?')
            return sKey[idx0:idx1]

        def compareItemTypes(code, types):
            idx = code.find('/')
            processType = code[idx:]
            return processType in types

        itemTypes = []
        for item in items:
            sKey = item.data(0, Qt.UserRole)
            itemTypes.append(getItemType(sKey))

        itemTypes = list(set(itemTypes))

        processesData = self.taskManagerWdg.getProcessesData()
        filteredData = list(
            filter(lambda x: compareItemTypes(x['code'], itemTypes),
                   processesData))

        processesList = []
        for data in filteredData:
            processesList += data.get("processes")
        return processesList

    def addTaskAction(self, server, sKeysList, process):
        for sKey in sKeysList:
            taskData = self.taskManagerWdg.userServerCore.taskData
            itemData = tacticDataProcess.getTaskElementBySearchField(
                taskData, "__search_key__", sKey)
            existingProcesses = tacticDataProcess.getActiveProcessesList(
                [itemData])

            if process in existingProcesses:
                continue
            task = tacticPostUtils.createTask(server, sKey, process)
            tacticPostUtils.updateSobject(
                server, task.get('__search_key__'),
                {"status": configUtils.tctStatusElements.get('assignment')})

    def removeProcess(self):
        items = self.itemUtils.getSelected_ProcessItems(getMultiple=True)
        if not isinstance(items, list):
            items = [items]

        server = self.taskManagerWdg.userServerCore.server
        for item in items:
            sKey = item.data(0, Qt.UserRole)
            if sKey.find("task") < 0:
                continue
            print(sKey)
            tacticPostUtils.deleteSObject(server, sKey, True)
コード例 #11
0
class DebugControlsWidget(QToolBar):
    def __init__(self, parent, name, data, debug_state):
        if not type(data) == binaryninja.binaryview.BinaryView:
            raise Exception('expected widget data to be a BinaryView')

        self.bv = data
        self.debug_state = debug_state

        QToolBar.__init__(self, parent)

        # TODO: Is there a cleaner way to do this?
        self.setStyleSheet("""
		QToolButton{padding: 4px 14px 4px 14px; font-size: 14pt;}
		QToolButton:disabled{color: palette(alternate-base)}
		""")

        self.actionRun = QAction("Run", self)
        self.actionRun.triggered.connect(lambda: self.perform_run())
        self.actionRestart = QAction("Restart", self)
        self.actionRestart.triggered.connect(lambda: self.perform_restart())
        self.actionQuit = QAction("Quit", self)
        self.actionQuit.triggered.connect(lambda: self.perform_quit())
        self.actionAttach = QAction("Attach", self)
        self.actionAttach.triggered.connect(lambda: self.perform_attach())
        self.actionDetach = QAction("Detach", self)
        self.actionDetach.triggered.connect(lambda: self.perform_detach())
        self.actionSettings = QAction("Settings...", self)
        self.actionSettings.triggered.connect(lambda: self.perform_settings())
        self.actionPause = QAction("Pause", self)
        self.actionPause.triggered.connect(lambda: self.perform_pause())
        self.actionResume = QAction("Resume", self)
        self.actionResume.triggered.connect(lambda: self.perform_resume())
        self.actionStepIntoAsm = QAction("Step Into (Assembly)", self)
        self.actionStepIntoAsm.triggered.connect(
            lambda: self.perform_step_into_asm())
        self.actionStepIntoIL = QAction("Step Into", self)
        self.actionStepIntoIL.triggered.connect(
            lambda: self.perform_step_into_il())
        self.actionStepOverAsm = QAction("Step Over (Assembly)", self)
        self.actionStepOverAsm.triggered.connect(
            lambda: self.perform_step_over_asm())
        self.actionStepOverIL = QAction("Step Over", self)
        self.actionStepOverIL.triggered.connect(
            lambda: self.perform_step_over_il())
        self.actionStepReturn = QAction("Step Return", self)
        self.actionStepReturn.triggered.connect(
            lambda: self.perform_step_return())

        # session control menu
        self.controlMenu = QMenu("Process Control", self)
        self.controlMenu.addAction(self.actionRun)
        self.controlMenu.addAction(self.actionRestart)
        self.controlMenu.addAction(self.actionQuit)
        self.controlMenu.addSeparator()
        self.controlMenu.addAction(self.actionAttach)
        self.controlMenu.addAction(self.actionDetach)
        self.controlMenu.addSeparator()
        self.controlMenu.addAction(self.actionSettings)

        self.stepIntoMenu = QMenu("Step Into", self)
        self.stepIntoMenu.addAction(self.actionStepIntoIL)
        self.stepIntoMenu.addAction(self.actionStepIntoAsm)

        self.stepOverMenu = QMenu("Step Over", self)
        self.stepOverMenu.addAction(self.actionStepOverIL)
        self.stepOverMenu.addAction(self.actionStepOverAsm)

        self.btnControl = QToolButton(self)
        self.btnControl.setMenu(self.controlMenu)
        self.btnControl.setPopupMode(QToolButton.MenuButtonPopup)
        self.btnControl.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnControl.setDefaultAction(self.actionRun)
        self.addWidget(self.btnControl)

        # execution control buttons
        self.addAction(self.actionPause)
        self.addAction(self.actionResume)

        self.btnStepInto = QToolButton(self)
        self.btnStepInto.setMenu(self.stepIntoMenu)
        self.btnStepInto.setPopupMode(QToolButton.MenuButtonPopup)
        self.btnStepInto.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnStepInto.setDefaultAction(self.actionStepIntoIL)
        self.addWidget(self.btnStepInto)

        self.btnStepOver = QToolButton(self)
        self.btnStepOver.setMenu(self.stepOverMenu)
        self.btnStepOver.setPopupMode(QToolButton.MenuButtonPopup)
        self.btnStepOver.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnStepOver.setDefaultAction(self.actionStepOverIL)
        self.addWidget(self.btnStepOver)

        # TODO: Step until returning from current function
        self.addAction(self.actionStepReturn)

        self.threadMenu = QMenu("Threads", self)

        self.btnThreads = QToolButton(self)
        self.btnThreads.setMenu(self.threadMenu)
        self.btnThreads.setPopupMode(QToolButton.InstantPopup)
        self.btnThreads.setToolButtonStyle(Qt.ToolButtonTextOnly)
        self.addWidget(self.btnThreads)

        self.set_thread_list([])

        self.editStatus = QLineEdit('INACTIVE', self)
        self.editStatus.setReadOnly(True)
        self.editStatus.setAlignment(QtCore.Qt.AlignCenter)
        self.addWidget(self.editStatus)

        # disable buttons
        self.set_actions_enabled(Run=self.can_exec(),
                                 Restart=False,
                                 Quit=False,
                                 Attach=self.can_connect(),
                                 Detach=False,
                                 Pause=False,
                                 Resume=False,
                                 StepInto=False,
                                 StepOver=False,
                                 StepReturn=False)
        self.set_resume_pause_action("Pause")
        self.set_default_process_action(
            "Attach" if self.can_connect() else "Run")

    def __del__(self):
        # TODO: Move this elsewhere
        # This widget is tasked with cleaning up the state after the view is closed
        # binjaplug.delete_state(self.bv)
        pass

    def can_exec(self):
        return DebugAdapter.ADAPTER_TYPE.use_exec(
            self.debug_state.adapter_type)

    def can_connect(self):
        return DebugAdapter.ADAPTER_TYPE.use_connect(
            self.debug_state.adapter_type)

    def perform_run(self):
        def perform_run_thread():
            try:
                self.debug_state.run()
                execute_on_main_thread_and_wait(perform_run_after)
            except ConnectionRefusedError:
                execute_on_main_thread_and_wait(
                    lambda: perform_run_error('ERROR: Connection Refused'))
            except Exception as e:
                execute_on_main_thread_and_wait(
                    lambda: perform_run_error('ERROR: ' + ' '.join(e.args)))
                traceback.print_exc(file=sys.stderr)

        def perform_run_after():
            self.state_stopped()
            self.debug_state.ui.on_step()

        def perform_run_error(e):
            self.state_error(e)

        self.state_starting('STARTING')
        threading.Thread(target=perform_run_thread).start()

    def perform_restart(self):
        def perform_restart_thread():
            try:
                self.debug_state.restart()
                execute_on_main_thread_and_wait(perform_restart_after)
            except ConnectionRefusedError:
                execute_on_main_thread_and_wait(
                    lambda: perform_restart_error('ERROR: Connection Refused'))
            except Exception as e:
                execute_on_main_thread_and_wait(lambda: perform_restart_error(
                    'ERROR: ' + ' '.join(e.args)))
                traceback.print_exc(file=sys.stderr)

        def perform_restart_after():
            self.state_stopped()
            self.debug_state.ui.on_step()

        def perform_restart_error(e):
            self.state_error(e)

        self.state_starting('RESTARTING')
        threading.Thread(target=perform_restart_thread).start()

    def perform_quit(self):
        self.debug_state.quit()
        self.state_inactive()
        self.debug_state.ui.on_step()

    def perform_attach(self):
        def perform_attach_thread():
            try:
                self.debug_state.attach()
                execute_on_main_thread_and_wait(perform_attach_after)
            except ConnectionRefusedError:
                execute_on_main_thread_and_wait(
                    lambda: perform_attach_error('ERROR: Connection Refused'))
            except TimeoutError:
                execute_on_main_thread_and_wait(
                    lambda: perform_attach_error('ERROR: Connection Refused'))
            except Exception as e:
                execute_on_main_thread_and_wait(
                    lambda: perform_attach_error('ERROR: ' + ' '.join(e.args)))
                traceback.print_exc(file=sys.stderr)

        def perform_attach_after():
            self.state_stopped()
            self.debug_state.ui.on_step()

        def perform_attach_error(e):
            self.state_error(e)

        self.state_starting('ATTACHING')
        threading.Thread(target=perform_attach_thread).start()

    def perform_detach(self):
        self.debug_state.detach()
        self.state_inactive()
        self.debug_state.ui.on_step()

    def perform_settings(self):
        def settings_finished():
            if self.debug_state.running:
                self.state_running()
            elif self.debug_state.connected:
                local_rip = self.debug_state.local_ip
                if self.debug_state.bv.read(local_rip, 1) and len(
                        self.debug_state.bv.get_functions_containing(
                            local_rip)) > 0:
                    self.state_stopped()
                else:
                    self.state_stopped_extern()
            else:
                self.state_inactive()

        dialog = AdapterSettingsDialog.AdapterSettingsDialog(self, self.bv)
        dialog.show()
        dialog.finished.connect(settings_finished)

    def perform_pause(self):
        self.debug_state.pause()
        # Don't update state here-- one of the other buttons is running in a thread and updating for us

    def perform_resume(self):
        def perform_resume_thread():
            (reason, data) = self.debug_state.go()
            execute_on_main_thread_and_wait(
                lambda: perform_resume_after(reason, data))

        def perform_resume_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_running()
        threading.Thread(target=perform_resume_thread).start()

    def perform_step_into_asm(self):
        def perform_step_into_asm_thread():
            (reason, data) = self.debug_state.step_into()
            execute_on_main_thread_and_wait(
                lambda: perform_step_into_asm_after(reason, data))

        def perform_step_into_asm_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_into_asm_thread).start()

    def perform_step_into_il(self):

        disasm = self.debug_state.ui.debug_view.binary_editor.getDisassembly()
        graph_type = disasm.getGraphType()

        def perform_step_into_il_thread():
            (reason, data) = self.debug_state.step_into(graph_type)
            execute_on_main_thread_and_wait(
                lambda: perform_step_into_il_after(reason, data))

        def perform_step_into_il_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_into_il_thread).start()

    def perform_step_over_asm(self):
        def perform_step_over_asm_thread():
            (reason, data) = self.debug_state.step_over()
            execute_on_main_thread_and_wait(
                lambda: perform_step_over_asm_after(reason, data))

        def perform_step_over_asm_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_over_asm_thread).start()

    def perform_step_over_il(self):

        disasm = self.debug_state.ui.debug_view.binary_editor.getDisassembly()
        graph_type = disasm.getGraphType()

        def perform_step_over_il_thread():
            (reason, data) = self.debug_state.step_over(graph_type)
            execute_on_main_thread_and_wait(
                lambda: perform_step_over_il_after(reason, data))

        def perform_step_over_il_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_over_il_thread).start()

    def perform_step_return(self):
        def perform_step_return_thread():
            (reason, data) = self.debug_state.step_return()
            execute_on_main_thread_and_wait(
                lambda: perform_step_return_after(reason, data))

        def perform_step_return_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_return_thread).start()

    def set_actions_enabled(self, **kwargs):
        def enable_step_into(e):
            self.actionStepIntoAsm.setEnabled(e)
            self.actionStepIntoIL.setEnabled(e)

        def enable_step_over(e):
            self.actionStepOverAsm.setEnabled(e)
            self.actionStepOverIL.setEnabled(e)

        def enable_starting(e):
            self.actionRun.setEnabled(e and self.can_exec())
            self.actionAttach.setEnabled(e and self.can_connect())

        def enable_stopping(e):
            self.actionRestart.setEnabled(e)
            self.actionQuit.setEnabled(e)
            self.actionDetach.setEnabled(e)

        def enable_stepping(e):
            self.actionStepIntoAsm.setEnabled(e)
            self.actionStepIntoIL.setEnabled(e)
            self.actionStepOverAsm.setEnabled(e)
            self.actionStepOverIL.setEnabled(e)
            self.actionStepReturn.setEnabled(e)

        actions = {
            "Run": lambda e: self.actionRun.setEnabled(e),
            "Restart": lambda e: self.actionRestart.setEnabled(e),
            "Quit": lambda e: self.actionQuit.setEnabled(e),
            "Attach": lambda e: self.actionAttach.setEnabled(e),
            "Detach": lambda e: self.actionDetach.setEnabled(e),
            "Pause": lambda e: self.actionPause.setEnabled(e),
            "Resume": lambda e: self.actionResume.setEnabled(e),
            "StepInto": enable_step_into,
            "StepOver": enable_step_over,
            "StepReturn": lambda e: self.actionStepReturn.setEnabled(e),
            "Threads": lambda e: self.btnThreads.setEnabled(e),
            "Starting": enable_starting,
            "Stopping": enable_stopping,
            "Stepping": enable_stepping,
        }
        for (action, enabled) in kwargs.items():
            actions[action](enabled)

    def set_default_process_action(self, action):
        actions = {
            "Run": self.actionRun,
            "Restart": self.actionRestart,
            "Quit": self.actionQuit,
            "Attach": self.actionAttach,
            "Detach": self.actionDetach,
        }
        self.btnControl.setDefaultAction(actions[action])

    def set_resume_pause_action(self, action):
        self.actionResume.setVisible(action == "Resume")
        self.actionPause.setVisible(action == "Pause")

    def set_thread_list(self, threads):
        def select_thread_fn(tid):
            def select_thread(tid):
                stateObj = binjaplug.get_state(self.bv)
                if stateObj.state == 'STOPPED':
                    adapter = stateObj.adapter
                    adapter.thread_select(tid)
                    self.debug_state.ui.context_display()
                else:
                    print('cannot set thread in state %s' % stateObj.state)

            return lambda: select_thread(tid)

        self.threadMenu.clear()
        if len(threads) > 0:
            for thread in threads:
                item_name = "Thread {} at {}".format(thread['tid'],
                                                     hex(thread['ip']))
                action = self.threadMenu.addAction(
                    item_name, select_thread_fn(thread['tid']))
                if thread['selected']:
                    self.btnThreads.setDefaultAction(action)
        else:
            defaultThreadAction = self.threadMenu.addAction("Thread List")
            defaultThreadAction.setEnabled(False)
            self.btnThreads.setDefaultAction(defaultThreadAction)

    def state_starting(self, msg=None):
        self.editStatus.setText(msg or 'INACTIVE')
        self.set_actions_enabled(Starting=False,
                                 Stopping=False,
                                 Stepping=False,
                                 Pause=False,
                                 Resume=False,
                                 Threads=False)
        self.set_default_process_action(
            "Attach" if self.can_connect() else "Run")
        self.set_thread_list([])
        self.set_resume_pause_action("Pause")

    def state_inactive(self, msg=None):
        self.editStatus.setText(msg or 'INACTIVE')
        self.set_actions_enabled(Starting=True,
                                 Stopping=False,
                                 Stepping=False,
                                 Pause=False,
                                 Resume=False,
                                 Threads=False)
        self.set_default_process_action(
            "Attach" if self.can_connect() else "Run")
        self.set_thread_list([])
        self.set_resume_pause_action("Pause")

    def state_stopped(self, msg=None):
        self.editStatus.setText(msg or 'STOPPED')
        self.set_actions_enabled(Starting=False,
                                 Stopping=True,
                                 Stepping=True,
                                 Pause=True,
                                 Resume=True,
                                 Threads=True)
        self.set_default_process_action("Quit")
        self.set_resume_pause_action("Resume")

    def state_stopped_extern(self, msg=None):
        self.editStatus.setText(msg or 'STOPPED')
        self.set_actions_enabled(Starting=False,
                                 Stopping=True,
                                 Stepping=True,
                                 StepReturn=False,
                                 Pause=True,
                                 Resume=True,
                                 Threads=True)
        self.set_default_process_action("Quit")
        self.set_resume_pause_action("Resume")

    def state_running(self, msg=None):
        self.editStatus.setText(msg or 'RUNNING')
        self.set_actions_enabled(Starting=False,
                                 Stopping=True,
                                 Stepping=False,
                                 Pause=True,
                                 Resume=False,
                                 Threads=False)
        self.set_default_process_action("Quit")
        self.set_resume_pause_action("Pause")

    def state_busy(self, msg=None):
        self.editStatus.setText(msg or 'RUNNING')
        self.set_actions_enabled(Starting=False,
                                 Stopping=True,
                                 Stepping=False,
                                 Pause=True,
                                 Resume=False,
                                 Threads=False)
        self.set_default_process_action("Quit")
        self.set_resume_pause_action("Pause")

    def state_error(self, msg=None):
        self.editStatus.setText(msg or 'ERROR')
        self.set_actions_enabled(Starting=True,
                                 Stopping=False,
                                 Pause=False,
                                 Resume=False,
                                 Stepping=False,
                                 Threads=False)
        self.set_default_process_action(
            "Attach" if self.can_connect() else "Run")
        self.set_thread_list([])
        self.set_resume_pause_action("Resume")

    def handle_stop_return(self, reason, data):
        if reason == DebugAdapter.STOP_REASON.STDOUT_MESSAGE:
            self.state_stopped('stdout: ' + data)
        elif reason == DebugAdapter.STOP_REASON.PROCESS_EXITED:
            self.debug_state.quit()
            self.state_inactive('process exited, return code=%d' % data)
        elif reason == DebugAdapter.STOP_REASON.BACKEND_DISCONNECTED:
            self.debug_state.quit()
            self.state_inactive('backend disconnected (process exited?)')
コード例 #12
0
class PlotImage(QLabel):
    def __init__(self, model, FM, parent=None):
        super(PlotImage, self).__init__(parent)

        self.model = model
        self.FM = FM
        self.mw = parent

        self.setAlignment(QtCore.Qt.AlignCenter)
        self.setMouseTracking(True)

        self.rubberBand = QRubberBand(QRubberBand.Rectangle, self)
        self.bandOrigin = QtCore.QPoint()
        self.xPlotOrigin = None
        self.yPlotOrigin = None

        self.menu = QMenu(self)

    def enterEvent(self, event):
        self.setCursor(QtCore.Qt.CrossCursor)
        self.mw.coordLabel.show()

    def leaveEvent(self, event):
        self.mw.coordLabel.hide()
        self.mw.statusBar().showMessage('')

    def mousePressEvent(self, event):

        # Set rubber band absolute and relative position
        self.bandOrigin = event.pos()
        self.xPlotOrigin, self.yPlotOrigin = self.getPlotCoords(event.pos())

        # Create rubber band
        self.rubberBand.setGeometry(
            QtCore.QRect(self.bandOrigin, QtCore.QSize()))

        QLabel.mousePressEvent(self, event)

    def mouseDoubleClickEvent(self, event):

        xCenter, yCenter = self.getPlotCoords(event.pos())
        self.mw.editPlotOrigin(xCenter, yCenter, apply=True)

        QLabel.mouseDoubleClickEvent(self, event)

    def mouseMoveEvent(self, event):

        # Show Cursor position relative to plot in status bar
        xPlotPos, yPlotPos = self.getPlotCoords(event.pos())

        # Show Cell/Material ID, Name in status bar
        id, domain, domain_kind = self.getIDinfo(event)
        if id != '-1' and domain[id].name:
            domainInfo = f"{domain_kind} {id}: {domain[id].name}"
        elif id != '-1':
            domainInfo = f"{domain_kind} {id}"
        else:
            domainInfo = ""
        self.mw.statusBar().showMessage(f" {domainInfo}")

        # Update rubber band and values if mouse button held down
        if event.buttons() == QtCore.Qt.LeftButton:
            self.rubberBand.setGeometry(
                QtCore.QRect(self.bandOrigin, event.pos()).normalized())

            # Show rubber band if both dimensions > 10 pixels
            if self.rubberBand.width() > 10 and self.rubberBand.height() > 10:
                self.rubberBand.show()
            else:
                self.rubberBand.hide()

            # Update plot X Origin
            xCenter = (self.xPlotOrigin + xPlotPos) / 2
            yCenter = (self.yPlotOrigin + yPlotPos) / 2
            self.mw.editPlotOrigin(xCenter, yCenter)

            modifiers = event.modifiers()

            # Zoom out if Shift held
            if modifiers == QtCore.Qt.ShiftModifier:
                cv = self.model.currentView
                bandwidth = abs(self.bandOrigin.x() - event.pos().x())
                width = cv.width * (cv.hRes / max(bandwidth, .001))
                bandheight = abs(self.bandOrigin.y() - event.pos().y())
                height = cv.height * (cv.vRes / max(bandheight, .001))
            else:  # Zoom in
                width = max(abs(self.xPlotOrigin - xPlotPos), 0.1)
                height = max(abs(self.yPlotOrigin - yPlotPos), 0.1)

            self.mw.editWidth(width)
            self.mw.editHeight(height)

    def mouseReleaseEvent(self, event):

        if self.rubberBand.isVisible():
            self.rubberBand.hide()
            self.mw.applyChanges()
        else:
            self.mw.revertDockControls()

    def wheelEvent(self, event):

        if event.delta() and event.modifiers() == QtCore.Qt.ShiftModifier:
            numDegrees = event.delta() / 8

            if 24 < self.mw.zoom + numDegrees < 5001:
                self.mw.editZoom(self.mw.zoom + numDegrees)

    def contextMenuEvent(self, event):

        self.menu.clear()

        self.mw.undoAction.setText(f'&Undo ({len(self.model.previousViews)})')
        self.mw.redoAction.setText(
            f'&Redo ({len(self.model.subsequentViews)})')

        id, domain, domain_kind = self.getIDinfo(event)

        if id != '-1':

            # Domain ID
            domainID = self.menu.addAction(f"{domain_kind} {id}")
            domainID.setDisabled(True)

            # Domain Name (if any)
            if domain[id].name:
                domainName = self.menu.addAction(domain[id].name)
                domainName.setDisabled(True)

            self.menu.addSeparator()
            self.menu.addAction(self.mw.undoAction)
            self.menu.addAction(self.mw.redoAction)
            self.menu.addSeparator()

            colorAction = self.menu.addAction(f'Edit {domain_kind} Color...')
            colorAction.setDisabled(self.model.currentView.highlighting)
            colorAction.setToolTip(f'Edit {domain_kind} color')
            colorAction.setStatusTip(f'Edit {domain_kind} color')
            colorAction.triggered.connect(
                lambda: self.mw.editDomainColor(domain_kind, id))

            maskAction = self.menu.addAction(f'Mask {domain_kind}')
            maskAction.setCheckable(True)
            maskAction.setChecked(domain[id].masked)
            maskAction.setDisabled(not self.model.currentView.masking)
            maskAction.setToolTip(f'Toggle {domain_kind} mask')
            maskAction.setStatusTip(f'Toggle {domain_kind} mask')
            maskAction.triggered[bool].connect(
                lambda bool=bool: self.mw.toggleDomainMask(
                    bool, domain_kind, id))

            highlightAction = self.menu.addAction(f'Highlight {domain_kind}')
            highlightAction.setCheckable(True)
            highlightAction.setChecked(domain[id].highlighted)
            highlightAction.setDisabled(
                not self.model.currentView.highlighting)
            highlightAction.setToolTip(f'Toggle {domain_kind} highlight')
            highlightAction.setStatusTip(f'Toggle {domain_kind} highlight')
            highlightAction.triggered[bool].connect(
                lambda bool=bool: self.mw.toggleDomainHighlight(
                    bool, domain_kind, id))

        else:
            self.menu.addAction(self.mw.undoAction)
            self.menu.addAction(self.mw.redoAction)
            self.menu.addSeparator()
            bgColorAction = self.menu.addAction('Edit Background Color...')
            bgColorAction.setToolTip('Edit background color')
            bgColorAction.setStatusTip('Edit plot background color')
            bgColorAction.triggered.connect(
                lambda: self.mw.editBackgroundColor(apply=True))

        self.menu.addSeparator()
        self.menu.addAction(self.mw.saveImageAction)
        self.menu.addAction(self.mw.saveViewAction)
        self.menu.addAction(self.mw.openAction)
        self.menu.addSeparator()
        self.menu.addMenu(self.mw.basisMenu)
        self.menu.addMenu(self.mw.colorbyMenu)
        self.menu.addSeparator()
        self.menu.addAction(self.mw.maskingAction)
        self.menu.addAction(self.mw.highlightingAct)
        self.menu.addSeparator()
        self.menu.addAction(self.mw.dockAction)

        self.mw.maskingAction.setChecked(self.model.currentView.masking)
        self.mw.highlightingAct.setChecked(self.model.currentView.highlighting)

        if self.mw.dock.isVisible():
            self.mw.dockAction.setText('Hide &Dock')
        else:
            self.mw.dockAction.setText('Show &Dock')

        self.menu.exec_(event.globalPos())

    def getPlotCoords(self, pos):

        cv = self.model.currentView

        factor = (self.width() / cv.hRes, self.height() / cv.vRes)

        # Cursor position in pixels relative to center of plot image
        xPos = (pos.x() + 0.5) / factor[0] - cv.hRes / 2
        yPos = (-pos.y() - 0.5) / factor[1] + cv.vRes / 2

        # Curson position in plot coordinates
        xPlotCoord = (xPos / self.mw.scale[0]) + cv.origin[self.mw.xBasis]
        yPlotCoord = (yPos / self.mw.scale[1]) + cv.origin[self.mw.yBasis]

        self.mw.showCoords(xPlotCoord, yPlotCoord)

        return (xPlotCoord, yPlotCoord)

    def getIDinfo(self, event):

        cv = self.model.currentView
        factor = (self.width() / cv.hRes, self.height() / cv.vRes)
        xPos = int((event.pos().x() + .05) / factor[0])
        yPos = int((event.pos().y() + .05) / factor[1])

        if yPos < self.model.currentView.vRes \
            and xPos < self.model.currentView.hRes:

            id = f"{self.model.ids[yPos][xPos]}"
        else:
            id = '-1'

        if self.model.currentView.colorby == 'cell':
            domain = self.model.activeView.cells
            domain_kind = 'Cell'
        else:
            domain = self.model.activeView.materials
            domain_kind = 'Material'

        return id, domain, domain_kind
コード例 #13
0
class FileMenu(QMenu):
    """
    A custom menu designed to fit the menubar
    """
    def __init__(self, mainWindow, parent=None):
        super(FileMenu, self).__init__(parent)

        self.mainWindow_ = mainWindow

        self.setTitle("File")
        self.addAction("New", self.newFunc)

        self.openMenu = QMenu()
        self.openMenu.setTitle("Open...")
        self.refreshOpenMenu()
        self.addMenu(self.openMenu)

        self.addAction("Import...", self.importFunc)
        self.addAction("Save", self.saveFunc)
        self.addAction("Save all", self.saveAllFunc)
        self.addAction("Export...", self.exportFunc)
        self.addSeparator()
        self.addAction("Quit", self.quitFunc)

    def newFunc(self):
        """
        Creates a new table, it opens a popup for the user to 
        chose the type and name
        """
        items = ["Members", "Finances"]
        tableType = QInputDialog.getItem(self, "New table", "table type",
                                         items, 0, False)
        if tableType[1]:
            tableName = QInputDialog.getText(self, "New table", "table name")
            if tableName[1]:
                if tableType == "Members":
                    table = Group(tableName[1])
                else:
                    table = Table(tableName[0])
                dataTable = DataTable(name=tableName[0],
                                      table=table,
                                      tableType=tableType[0])
                self.mainWindow_.contentTab.addTable(dataTable)

    def openFunc(self, fileName):
        """
        Open the file at the specified path
        The file has to be JSON (for ot it's CSV though)
        """
        print(fileName)
        #TODO temporary, to be change by json call
        try:
            table, name = csvParser(fileName)
        except Exception as inst:
            message = ErrorMessage(str(inst))
            message.exec_()
            return
        if isinstance(table, Group):
            dataTable = DataTable(name, table, "Members")
        elif isinstance(table, Table):
            dataTable = DataTable(name, table, "Tresury")
        else:
            return
        self.mainWindow_.contentTab.addTable(dataTable)

    def refreshOpenMenu(self):
        """
        Refresh the "Open" menu so that new file can be seen
        To be changed when csv is dropped for internal saves
        """
        path = dirname(dirname(dirname((abspath(__file__)))))
        path += "/csv_files"
        #TODO implement json
        files = csvList(path)
        print(files)
        self.openMenu.clear()
        for elem in files:
            self.openMenu.addAction(elem,
                                    lambda: self.openFunc(path + "/" + elem))
        self.openMenu.addSeparator()
        self.openMenu.addAction("Refresh", self.refreshOpenMenu)

    def importFunc(self):
        """
        Allow the user to choose in the filesystem a csv file and open it
        in MLE
        """
        fileName = QFileDialog.getOpenFileName(self, "Import table",
                                               "~/../../", "CSV files (*.csv)")
        print(fileName[0])
        if fileName[0] == "":
            return

        try:
            table, name = csvParser(fileName[0])
        except Exception as inst:
            message = ErrorMessage(str(inst))
            message.exec_()
            return

        if isinstance(table, Group):
            dataTable = DataTable(name, table, "Members")
        elif isinstance(table, Table):
            dataTable = DataTable(name, table, "Tresury")
        else:
            return
        self.mainWindow_.contentTab.addTable(dataTable)

    def saveFunc(self):
        """
        Saves the specified Table in internal storage
        """
        self.mainWindow_.contentTab.saveCurrent()

    def saveAllFunc(self):
        """
        Saves all tables in internal storage
        """
        self.maiWindow_.contentTab.saveAll()

    def exportFunc(self):
        """
        Allow the user to choose in the filesystem a path to save a table as a csv
        """
        fileName = QFileDialog.getSaveFileName(self, "Export table",
                                               "~/../../", "CSV files (*.csv)")

        if fileName[0] == "":
            return

        self.mainWindow_.contentTab.saveCurrent(fileName[0], False)

    def quitFunc(self):
        """
        Quit the application after checking if all tables have been saved
        """
        if self.mainWindow_.contentTab.checkTables():
            self.mainWindow_.quit()
コード例 #14
0
ファイル: menu_file.py プロジェクト: tappi287/RenderKnecht2
class FileMenu(QObject):
    recent_files_changed = Signal()
    new_document_count = 0
    current_progress_obj = ShowTreeViewProgressMessage(None)
    load_save_mgr = None
    supported_file_types = ('.xml', '.rksession', '.xlsx')

    viewer_app = Path(get_current_modules_dir()) / KNECHT_VIEWER_BIN
    schnuffi_app = Path(get_current_modules_dir()) / POS_SCHNUFFI_BIN

    def __init__(self, ui, menu: QMenu = None):
        """ The File menu

        :param modules.gui.main_ui.KnechtWindow ui:
        :param menu: Menu created setup in ui file
        """
        super(FileMenu, self).__init__(parent=ui)
        self.ui = ui
        self.view_mgr: UiViewManager = None

        self.menu = menu or ui.menuDatei
        self.menu.setEnabled(False)
        self.recent_menu = QMenu(_('Zuletzt geöffnet'), self.menu)
        self.import_menu = ImportMenu(self.ui)
        self.import_menu.new_model_ready.connect(self.model_loaded)

        self.xml_message_box = XmlFailedMsgBox(self.ui)

        self.setup_file_menu()
        QTimer.singleShot(1, self.delayed_setup)

    @Slot()
    def delayed_setup(self):
        """ Setup attributes that require a fully initialized ui"""
        self.view_mgr: UiViewManager = self.ui.view_mgr
        self.menu.setEnabled(True)

        self.load_save_mgr = SaveLoadController(self)
        self.load_save_mgr.model_loaded.connect(self.model_loaded)
        self.load_save_mgr.load_aborted.connect(self._load_aborted)

    @Slot(Path)
    def guess_open_file(self, local_file_path: Path) -> bool:
        if local_file_path.suffix.casefold() == '.xml':
            self.open_xml(local_file_path.as_posix())
            return True
        elif local_file_path.suffix.casefold() == '.rksession':
            self.import_menu.open_wizard(local_file_path)
            return True
        elif local_file_path.suffix.casefold() == '.xlsx':
            self.import_menu.open_xlsx(local_file_path)
            return True

        return False

    def setup_file_menu(self):
        insert_before = 0
        if self.ui.actionBeenden in self.menu.actions():
            insert_before = self.ui.actionBeenden
            self.ui.actionBeenden.setIcon(IconRsc.get_icon('sad'))
            self.ui.actionBeenden.setShortcut(QKeySequence('Ctrl+Q'))

        # ---- New file ----
        new_action = QAction(IconRsc.get_icon('document'), _('Neu\tStrg+N'),
                             self.menu)
        new_action.setShortcut(QKeySequence('Ctrl+N'))
        new_action.triggered.connect(self.new_document)
        self.menu.insertAction(insert_before, new_action)

        # ---- Open ----
        open_xml_action = QAction(_('Öffnen\tStrg+O'), self.menu)
        open_xml_action.setShortcut(QKeySequence('Ctrl+O'))
        open_xml_action.triggered.connect(self.open_xml)
        open_xml_action.setIcon(IconRsc.get_icon('folder'))
        self.menu.insertAction(insert_before, open_xml_action)

        # ---- Import Menu ----
        self.menu.insertMenu(insert_before, self.import_menu)

        # ---- Save ----
        save_xml_action = QAction(_('Speichern\tStrg+S'), self.menu)
        save_xml_action.setShortcut(QKeySequence('Ctrl+S'))
        save_xml_action.triggered.connect(self.save_xml)
        save_xml_action.setIcon(IconRsc.get_icon('disk'))
        self.menu.insertAction(insert_before, save_xml_action)

        save_as_action = QAction(_('Speichern unter ...\tStrg+Shift+S'),
                                 self.menu)
        save_as_action.setShortcut(QKeySequence('Ctrl+Shift+S'))
        save_as_action.triggered.connect(self.save_as_xml)
        save_as_action.setIcon(IconRsc.get_icon('save_alt'))
        self.menu.insertAction(insert_before, save_as_action)

        self.menu.insertSeparator(insert_before)

        # ---- Apps ----
        start_knecht_viewer = QAction(_('KnechtViewer starten'), self.menu)
        start_knecht_viewer.triggered.connect(self.start_knecht_viewer)
        start_knecht_viewer.setIcon(IconRsc.get_icon('img'))
        self.menu.insertAction(insert_before, start_knecht_viewer)
        if not path_exists(self.viewer_app):
            LOGGER.info('KnechtViewer executable could not be found: %s',
                        self.viewer_app.as_posix())
            start_knecht_viewer.setEnabled(False)

        start_schnuffi_app = QAction(_('POS Schnuffi starten'), self.menu)
        start_schnuffi_app.triggered.connect(self.start_schnuffi_app)
        start_schnuffi_app.setIcon(IconRsc.get_icon('dog'))
        self.menu.insertAction(insert_before, start_schnuffi_app)
        if not path_exists(self.schnuffi_app):
            LOGGER.info('KnechtViewer executable could not be found: %s',
                        self.schnuffi_app.as_posix())
            start_schnuffi_app.setEnabled(False)

        img_conv = QAction(_('Bilddaten konvertieren ...'))
        img_conv.triggered.connect(self.convert_image_directory)
        img_conv.setIcon(IconRsc.get_icon('render'))
        self.menu.insertAction(insert_before, img_conv)

        material_merger = QAction('AViT Material Merger')
        material_merger.triggered.connect(self.start_material_merger)
        material_merger.setIcon(IconRsc.get_icon('options'))
        self.menu.insertAction(insert_before, material_merger)

        self.menu.insertSeparator(insert_before)

        # ---- Recent files menu ----
        self.recent_menu.aboutToShow.connect(self.update_recent_files_menu)
        self.menu.insertMenu(insert_before, self.recent_menu)

        self.menu.insertSeparator(insert_before)

    def new_document(self):
        new_file = Path(
            _('Neues_Dokument_{:02d}.xml').format(self.new_document_count))
        self.view_mgr.create_view(None, new_file)

        self.new_document_count += 1

    def start_knecht_viewer(self):
        start_app(self.viewer_app)

    def start_schnuffi_app(self):
        start_app(self.schnuffi_app)

    def start_material_merger(self):
        material_merger = MaterialMerger(self.ui)
        GenericTabWidget(self.ui, material_merger)

    def save_xml(self):
        if not self.view_mgr.current_tab_is_document_tab():
            return

        self.enable_menus(False)

        file = self.view_mgr.current_file()

        if not file or not path_exists(file):
            if self._ask_save_as_file(file):
                # User agreed to set new save file
                self.save_as_xml()
                return
            # User aborted
            self.enable_menus(True)
            return

        self.save_as_xml(file)

    def save_as_xml(self, file: Path = None):
        if not self.view_mgr.current_tab_is_document_tab():
            return

        self.enable_menus(False)

        if not file:
            current_dir = Path(KnechtSettings.app['current_path'])
            file, file_type = FileDialog.save(self.ui,
                                              current_dir,
                                              file_key='xml')

        if not file:
            LOGGER.info('Save Xml File dialog canceled.')
            self.enable_menus(True)
            return

        file = Path(file)
        view = self.view_mgr.current_view()

        result, error = self.load_save_mgr.save(file, view)

        if result:
            LOGGER.debug('File saved: %s', file.as_posix())
            self.view_mgr.tab_view_saved(file)
            self.ui.msg(
                _('Datei gespeichert:{0}{1:.3}s').format(
                    f'\n{file.name}\n', self.load_save_mgr.last_progress_time))
        else:
            self._save_aborted(error, file)

        self.enable_menus(True)

    def open_xml(self, file: str = None) -> None:
        self.enable_menus(False)

        if not file:
            file = FileDialog.open(self.ui, None, 'xml')

        if not file:
            LOGGER.info('Open Xml File dialog canceled.')
            self.enable_menus(True)
            return

        # Check if the file is already opened
        file = Path(file)
        if self.view_mgr.file_mgr.already_open(file):
            LOGGER.info('File already open.')
            self.enable_menus(True)
            return

        # Update treeview progress
        view = self.view_mgr.current_view()
        view.progress_msg.msg(_('Daten werden gelesen'))
        view.progress_msg.show_progress()

        self.load_save_mgr.open(file)
        self.enable_menus(True)

    @Slot(KnechtModel, Path)
    @Slot(KnechtModel, Path, bool)
    def model_loaded(self,
                     model: KnechtModel,
                     file: Path,
                     reset_clean: bool = False):
        # Update progress
        view = self.view_mgr.current_view()
        view.progress_msg.hide_progress()

        # Create a new view inside a new tab or load into current view if view model is empty
        new_view = self.view_mgr.create_view(model, file)

        # Refresh model data
        if reset_clean:
            new_view.undo_stack.resetClean()

        self.ui.statusBar().showMessage(
            _('{0} in {1:.3}s geladen.').format(
                file.name, self.load_save_mgr.last_progress_time))

    @Slot(str, Path)
    def _load_aborted(self, error_msg: str, file: Path):
        # Update progress
        view = self.view_mgr.current_view()
        view.progress_msg.hide_progress()

        self.xml_message_box.set_error_msg(error_msg, Path(file))

        self.ui.play_warning_sound()
        self.xml_message_box.exec_()

    def _save_aborted(self, error_msg: str, file: Path):
        self.xml_message_box.set_error_msg(error_msg, Path(file))

        self.ui.play_warning_sound()
        self.xml_message_box.exec_()

    def enable_menus(self, enabled: bool = True):
        for a in self.menu.actions():
            a.setEnabled(enabled)

        self.recent_menu.setEnabled(enabled)

    def _open_recent_xml_file(self):
        recent_action = self.sender()
        self.open_xml(recent_action.file)

    def _open_recent_xlsx_file(self):
        recent_action = self.sender()
        self.import_menu.open_xlsx(recent_action.file)

    def _open_recent_rksession(self):
        recent_action = self.sender()
        self.import_menu.open_wizard(recent_action.file)

    def _ask_save_as_file(self, file: Path):
        """ User hits save but file to save does not exist yet """
        msg_box = AskToContinue(self.ui)

        if not msg_box.ask(
                _('Zieldatei zum Speichern festlegen?'),
                _('Die Datei: <i>{}</i><br>'
                  'Pfad: <i>{}</i><br>'
                  'wurde entfernt oder existiert nicht mehr.<br><br>'
                  'Neue Zieldatei zum Speichern festlegen?'
                  '').format(file.name, file.parent.as_posix()),
                _('Speichern unter..')):
            # User wants to abort save as
            return False
        return True

    def convert_image_directory(self):
        img_dir = FileDialog.open_dir(self.ui, None)

        if not img_dir or not path_exists(img_dir):
            self.ui.msg(
                _('Zu konvertierendes Verzeichnis ist nicht erreichbar.'),
                8000)
            return

        img_dir = Path(img_dir)
        out_dir = img_dir / 'converted'

        try:
            out_dir.mkdir(exist_ok=True)
        except Exception as e:
            self.ui.msg(
                _('Konnte Bild Konvertierung Ausgabeverzeichnis nicht erstellen.'
                  ), 8000)
            LOGGER.warning(e)

        img_converter = KnechtImage(self)
        img_converter.conversion_result.connect(self._conversion_result)

        if img_converter.convert_directory(img_dir, out_dir):
            self.ui.msg(
                _('Bildkonvertierung gestartet.<br /><i>{}</i>').format(
                    img_dir), 5000)
        else:
            self.ui.msg(
                _('Bildkonvertierung konnte nicht gestartet werden. Keine konvertierbaren Dateien gefunden.'
                  ), 10000)

    def _conversion_result(self, result: str):
        result = result.replace('\n', '<br />')
        title = _("Ergebnis der Bildkonvertierung:")
        self.ui.overlay.display_confirm(f'<b>{title}</b><br />{result}',
                                        (('[X]', None), ))

    def update_recent_files_menu(self):
        self.recent_menu.clear()

        if not len(KnechtSettings.app['recent_files']):
            no_entries_dummy = QAction(_("Keine Einträge vorhanden"),
                                       self.recent_menu)
            no_entries_dummy.setEnabled(False)
            self.recent_menu.addAction(no_entries_dummy)

        for idx, entry in enumerate(KnechtSettings.app['recent_files']):
            if idx >= 20:
                break

            file, file_type = entry
            file_name = Path(file).stem

            if not path_exists(file):
                # Skip and remove non existing files
                KnechtSettings.app['recent_files'].pop(idx)
                continue

            recent_action = QAction(f'{file_name} - {file_type}',
                                    self.recent_menu)
            recent_action.file = Path(file)

            if file_type == 'xml':
                recent_action.setText(f'{file_name} - Xml Presets')
                recent_action.setIcon(IconRsc.get_icon('document'))
                recent_action.triggered.connect(self._open_recent_xml_file)
            elif file_type == 'xlsx':
                recent_action.setText(f'{file_name} - Excel Import')
                recent_action.setIcon(IconRsc.get_icon('excel'))
                recent_action.triggered.connect(self._open_recent_xlsx_file)
            elif file_type == 'rksession':
                recent_action.setText(f'{file_name} - Preset Wizard Session')
                recent_action.setIcon(IconRsc.get_icon('qub_button'))
                recent_action.triggered.connect(self._open_recent_rksession)

            self.recent_menu.addAction(recent_action)

        self.recent_files_changed.emit()
コード例 #15
0
class MainWindow(QMainWindow):

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

        self.setWindowIcon(QIcon(":/icons/apps/16/tabulator.svg"))

        self._recentDocuments = []
        self._actionRecentDocuments = []
        self._keyboardShortcutsDialog = None

        self._preferences = Preferences()
        self._preferences.loadSettings()

        self._createActions()
        self._createMenus()
        self._createToolBars()

        self._loadSettings()

        self._updateActions()
        self._updateActionFullScreen()
        self._updateMenuOpenRecent()

        # Central widget
        self._documentArea = QMdiArea()
        self._documentArea.setViewMode(QMdiArea.TabbedView)
        self._documentArea.setTabsMovable(True)
        self._documentArea.setTabsClosable(True)
        self.setCentralWidget(self._documentArea)
        self._documentArea.subWindowActivated.connect(self._onDocumentWindowActivated)


    def closeEvent(self, event):

        if True:
            # Store application properties and preferences
            self._saveSettings()
            self._preferences.saveSettings()

            event.accept()
        else:
            event.ignore()


    def _loadSettings(self):

        settings = QSettings()

        # Recent documents
        size = settings.beginReadArray("RecentDocuments")
        for idx in range(size-1, -1, -1):
            settings.setArrayIndex(idx)
            canonicalName = QFileInfo(settings.value("Document")).canonicalFilePath()
            self._updateRecentDocuments(canonicalName)
        settings.endArray()

        # Application properties: Geometry
        geometry = settings.value("Application/Geometry", QByteArray()) if self._preferences.restoreApplicationGeometry() else QByteArray()
        if not geometry.isEmpty():
            self.restoreGeometry(geometry)
        else:
            availableGeometry = self.screen().availableGeometry()
            self.resize(availableGeometry.width() * 2/3, availableGeometry.height() * 2/3)
            self.move((availableGeometry.width() - self.width()) / 2, (availableGeometry.height() - self.height()) / 2)

        # Application properties: State
        state = settings.value("Application/State", QByteArray()) if self._preferences.restoreApplicationState() else QByteArray()
        if not state.isEmpty():
            self.restoreState(state)
        else:
            self._toolbarApplication.setVisible(True)
            self._toolbarDocument.setVisible(True)
            self._toolbarEdit.setVisible(True)
            self._toolbarTools.setVisible(True)
            self._toolbarView.setVisible(False)
            self._toolbarHelp.setVisible(False)


    def _saveSettings(self):

        settings = QSettings()

        # Recent documents
        if not self._preferences.restoreRecentDocuments():
            self._recentDocuments.clear()
        settings.remove("RecentDocuments")
        settings.beginWriteArray("RecentDocuments")
        for idx in range(len(self._recentDocuments)):
            settings.setArrayIndex(idx)
            settings.setValue("Document", self._recentDocuments[idx])
        settings.endArray()

        # Application properties: Geometry
        geometry = self.saveGeometry() if self._preferences.restoreApplicationGeometry() else QByteArray()
        settings.setValue("Application/Geometry", geometry)

        # Application properties: State
        state = self.saveState() if self._preferences.restoreApplicationState() else QByteArray()
        settings.setValue("Application/State", state)


    def _createActions(self):

        #
        # Actions: Application

        self._actionAbout = QAction(self.tr("About {0}").format(QApplication.applicationName()), self)
        self._actionAbout.setObjectName("actionAbout")
        self._actionAbout.setIcon(QIcon(":/icons/apps/16/tabulator.svg"))
        self._actionAbout.setIconText(self.tr("About"))
        self._actionAbout.setToolTip(self.tr("Brief description of the application"))
        self._actionAbout.triggered.connect(self._onActionAboutTriggered)

        self._actionColophon = QAction(self.tr("Colophon"), self)
        self._actionColophon.setObjectName("actionColophon")
        self._actionColophon.setToolTip(self.tr("Lengthy description of the application"))
        self._actionColophon.triggered.connect(self._onActionColophonTriggered)

        self._actionPreferences = QAction(self.tr("Preferences…"), self)
        self._actionPreferences.setObjectName("actionPreferences")
        self._actionPreferences.setIcon(QIcon.fromTheme("configure", QIcon(":/icons/actions/16/application-configure.svg")))
        self._actionPreferences.setToolTip(self.tr("Customize the appearance and behavior of the application"))
        self._actionPreferences.triggered.connect(self._onActionPreferencesTriggered)

        self._actionQuit = QAction(self.tr("Quit"), self)
        self._actionQuit.setObjectName("actionQuit")
        self._actionQuit.setIcon(QIcon.fromTheme("application-exit", QIcon(":/icons/actions/16/application-exit.svg")))
        self._actionQuit.setShortcut(QKeySequence.Quit)
        self._actionQuit.setToolTip(self.tr("Quit the application"))
        self._actionQuit.triggered.connect(self.close)

        #
        # Actions: Document

        self._actionNew = QAction(self.tr("New"), self)
        self._actionNew.setObjectName("actionNew")
        self._actionNew.setIcon(QIcon.fromTheme("document-new", QIcon(":/icons/actions/16/document-new.svg")))
        self._actionNew.setShortcut(QKeySequence.New)
        self._actionNew.setToolTip(self.tr("Create new document"))
        self._actionNew.triggered.connect(self._onActionNewTriggered)

        self._actionOpen = QAction(self.tr("Open…"), self)
        self._actionOpen.setObjectName("actionOpen")
        self._actionOpen.setIcon(QIcon.fromTheme("document-open", QIcon(":/icons/actions/16/document-open.svg")))
        self._actionOpen.setShortcut(QKeySequence.Open)
        self._actionOpen.setToolTip(self.tr("Open an existing document"))
        self._actionOpen.triggered.connect(self._onActionOpenTriggered)

        self._actionOpenRecentClear = QAction(self.tr("Clear List"), self)
        self._actionOpenRecentClear.setObjectName("actionOpenRecentClear")
        self._actionOpenRecentClear.setToolTip(self.tr("Clear document list"))
        self._actionOpenRecentClear.triggered.connect(self._onActionOpenRecentClearTriggered)

        self._actionSave = QAction(self.tr("Save"), self)
        self._actionSave.setObjectName("actionSave")
        self._actionSave.setIcon(QIcon.fromTheme("document-save", QIcon(":/icons/actions/16/document-save.svg")))
        self._actionSave.setShortcut(QKeySequence.Save)
        self._actionSave.setToolTip(self.tr("Save document"))
        self._actionSave.triggered.connect(self._onActionSaveTriggered)

        self._actionSaveAs = QAction(self.tr("Save As…"), self)
        self._actionSaveAs.setObjectName("actionSaveAs")
        self._actionSaveAs.setIcon(QIcon.fromTheme("document-save-as", QIcon(":/icons/actions/16/document-save-as.svg")))
        self._actionSaveAs.setShortcut(QKeySequence.SaveAs)
        self._actionSaveAs.setToolTip(self.tr("Save document under a new name"))
        self._actionSaveAs.triggered.connect(self._onActionSaveAsTriggered)

        self._actionSaveAsDelimiterColon = QAction(self.tr("Colon"), self)
        self._actionSaveAsDelimiterColon.setObjectName("actionSaveAsDelimiterColon")
        self._actionSaveAsDelimiterColon.setCheckable(True)
        self._actionSaveAsDelimiterColon.setToolTip(self.tr("Save document with colon as delimiter under a new name"))
        self._actionSaveAsDelimiterColon.setData("colon")
        self._actionSaveAsDelimiterColon.triggered.connect(lambda: self._onActionSaveAsDelimiterTriggered("colon") )

        self._actionSaveAsDelimiterComma = QAction(self.tr("Comma"), self)
        self._actionSaveAsDelimiterComma.setObjectName("actionSaveAsDelimiterComma")
        self._actionSaveAsDelimiterComma.setCheckable(True)
        self._actionSaveAsDelimiterComma.setToolTip(self.tr("Save document with comma as delimiter under a new name"))
        self._actionSaveAsDelimiterComma.setData("comma")
        self._actionSaveAsDelimiterComma.triggered.connect(lambda: self._onActionSaveAsDelimiterTriggered("comma") )

        self._actionSaveAsDelimiterSemicolon = QAction(self.tr("Semicolon"), self)
        self._actionSaveAsDelimiterSemicolon.setObjectName("actionSaveAsDelimiterSemicolon")
        self._actionSaveAsDelimiterSemicolon.setCheckable(True)
        self._actionSaveAsDelimiterSemicolon.setToolTip(self.tr("Save document with semicolon as delimiter under a new name"))
        self._actionSaveAsDelimiterSemicolon.setData("semicolon")
        self._actionSaveAsDelimiterSemicolon.triggered.connect(lambda: self._onActionSaveAsDelimiterTriggered("semicolon") )

        self._actionSaveAsDelimiterTab = QAction(self.tr("Tab"), self)
        self._actionSaveAsDelimiterTab.setObjectName("actionSaveAsDelimiterTab")
        self._actionSaveAsDelimiterTab.setCheckable(True)
        self._actionSaveAsDelimiterTab.setToolTip(self.tr("Save document with tab as delimiter under a new name"))
        self._actionSaveAsDelimiterTab.setData("tab")
        self._actionSaveAsDelimiterTab.triggered.connect(lambda: self._onActionSaveAsDelimiterTriggered("tab") )

        self._actionSaveAsDelimiter = QActionGroup(self)
        self._actionSaveAsDelimiter.setObjectName("actionSaveAsDelimiter")
        self._actionSaveAsDelimiter.addAction(self._actionSaveAsDelimiterColon)
        self._actionSaveAsDelimiter.addAction(self._actionSaveAsDelimiterComma)
        self._actionSaveAsDelimiter.addAction(self._actionSaveAsDelimiterSemicolon)
        self._actionSaveAsDelimiter.addAction(self._actionSaveAsDelimiterTab)

        self._actionSaveCopyAs = QAction(self.tr("Save Copy As…"), self)
        self._actionSaveCopyAs.setObjectName("actionSaveCopyAs")
        self._actionSaveCopyAs.setIcon(QIcon.fromTheme("document-save-as", QIcon(":/icons/actions/16/document-save-as.svg")))
        self._actionSaveCopyAs.setToolTip(self.tr("Save copy of document under a new name"))
        self._actionSaveCopyAs.triggered.connect(self._onActionSaveCopyAsTriggered)

        self._actionSaveAll = QAction(self.tr("Save All"), self)
        self._actionSaveAll.setObjectName("actionSaveAll")
        self._actionSaveAll.setIcon(QIcon.fromTheme("document-save-all", QIcon(":/icons/actions/16/document-save-all.svg")))
        self._actionSaveAll.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_L))
        self._actionSaveAll.setToolTip(self.tr("Save all documents"))
        self._actionSaveAll.triggered.connect(self._onActionSaveAllTriggered)

        self._actionClose = QAction(self.tr("Close"), self)
        self._actionClose.setObjectName("actionClose")
        self._actionClose.setIcon(QIcon.fromTheme("document-close", QIcon(":/icons/actions/16/document-close.svg")))
        self._actionClose.setShortcut(QKeySequence.Close)
        self._actionClose.setToolTip(self.tr("Close document"))
        self._actionClose.triggered.connect(self._onActionCloseTriggered)

        self._actionCloseOther = QAction(self.tr("Close Other"), self)
        self._actionCloseOther.setObjectName("actionCloseOther")
        self._actionCloseOther.setToolTip(self.tr("Close all other documents"))
        self._actionCloseOther.triggered.connect(self._onActionCloseOtherTriggered)

        self._actionCloseAll = QAction(self.tr("Close All"), self)
        self._actionCloseAll.setObjectName("actionCloseAll")
        self._actionCloseAll.setShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_W))
        self._actionCloseAll.setToolTip(self.tr("Close all documents"))
        self._actionCloseAll.triggered.connect(self._onActionCloseAllTriggered)

        #
        # Actions: View

        self._actionFullScreen = QAction(self)
        self._actionFullScreen.setObjectName("actionFullScreen")
        self._actionFullScreen.setIconText(self.tr("Full Screen"))
        self._actionFullScreen.setCheckable(True)
        self._actionFullScreen.setShortcuts([QKeySequence(Qt.Key_F11), QKeySequence.FullScreen])
        self._actionFullScreen.triggered.connect(self._onActionFullScreenTriggered)

        self._actionTitlebarFullPath = QAction(self.tr("Show Path in Titlebar"), self)
        self._actionTitlebarFullPath.setObjectName("actionTitlebarFullPath")
        self._actionTitlebarFullPath.setCheckable(True)
        self._actionTitlebarFullPath.setChecked(True)
        self._actionTitlebarFullPath.setToolTip(self.tr("Display the full path of the document in the titlebar"))
        self._actionTitlebarFullPath.triggered.connect(self._onActionTitlebarFullPathTriggered)

        self._actionToolbarApplication = QAction(self.tr("Show Application Toolbar"), self)
        self._actionToolbarApplication.setObjectName("actionToolbarApplication")
        self._actionToolbarApplication.setCheckable(True)
        self._actionToolbarApplication.setToolTip(self.tr("Display the Application toolbar"))
        self._actionToolbarApplication.toggled.connect(lambda checked: self._toolbarApplication.setVisible(checked))

        self._actionToolbarDocument = QAction(self.tr("Show Document Toolbar"), self)
        self._actionToolbarDocument.setObjectName("actionToolbarDocument")
        self._actionToolbarDocument.setCheckable(True)
        self._actionToolbarDocument.setToolTip(self.tr("Display the Document toolbar"))
        self._actionToolbarDocument.toggled.connect(lambda checked: self._toolbarDocument.setVisible(checked))

        self._actionToolbarEdit = QAction(self.tr("Show Edit Toolbar"), self)
        self._actionToolbarEdit.setObjectName("actionToolbarEdit")
        self._actionToolbarEdit.setCheckable(True)
        self._actionToolbarEdit.setToolTip(self.tr("Display the Edit toolbar"))
        self._actionToolbarEdit.toggled.connect(lambda checked: self._toolbarEdit.setVisible(checked))

        self._actionToolbarTools = QAction(self.tr("Show Tools Toolbar"), self)
        self._actionToolbarTools.setObjectName("actionToolbarTools")
        self._actionToolbarTools.setCheckable(True)
        self._actionToolbarTools.setToolTip(self.tr("Display the Tools toolbar"))
        self._actionToolbarTools.toggled.connect(lambda checked: self._toolbarTools.setVisible(checked))

        self._actionToolbarView = QAction(self.tr("Show View Toolbar"), self)
        self._actionToolbarView.setObjectName("actionToolbarView")
        self._actionToolbarView.setCheckable(True)
        self._actionToolbarView.setToolTip(self.tr("Display the View toolbar"))
        self._actionToolbarView.toggled.connect(lambda checked: self._toolbarView.setVisible(checked))

        self._actionToolbarHelp = QAction(self.tr("Show Help Toolbar"), self)
        self._actionToolbarHelp.setObjectName("actionToolbarHelp")
        self._actionToolbarHelp.setCheckable(True)
        self._actionToolbarHelp.setToolTip(self.tr("Display the Help toolbar"))
        self._actionToolbarHelp.toggled.connect(lambda checked: self._toolbarHelp.setVisible(checked))

        #
        # Actions: Help

        self._actionKeyboardShortcuts = QAction(self.tr("Keyboard Shortcuts"), self)
        self._actionKeyboardShortcuts.setObjectName("actionKeyboardShortcuts")
        self._actionKeyboardShortcuts.setIcon(QIcon.fromTheme("help-keyboard-shortcuts", QIcon(":/icons/actions/16/help-keyboard-shortcuts.svg")))
        self._actionKeyboardShortcuts.setIconText(self.tr("Shortcuts"))
        self._actionKeyboardShortcuts.setToolTip(self.tr("List of all keyboard shortcuts"))
        self._actionKeyboardShortcuts.triggered.connect(self._onActionKeyboardShortcutsTriggered)


    def _createMenus(self):

        # Menu: Application
        menuApplication = self.menuBar().addMenu(self.tr("Application"))
        menuApplication.setObjectName("menuApplication")
        menuApplication.addAction(self._actionAbout)
        menuApplication.addAction(self._actionColophon)
        menuApplication.addSeparator()
        menuApplication.addAction(self._actionPreferences)
        menuApplication.addSeparator()
        menuApplication.addAction(self._actionQuit)

        #
        # Menu: Document

        self._menuOpenRecent = QMenu(self.tr("Open Recent"), self)
        self._menuOpenRecent.setObjectName("menuOpenRecent")
        self._menuOpenRecent.setIcon(QIcon.fromTheme("document-open-recent", QIcon(":/icons/actions/16/document-open-recent.svg")))
        self._menuOpenRecent.setToolTip(self.tr("Open a document which was recently opened"))

        self._menuSaveAsDelimiter = QMenu(self.tr("Save As with Delimiter…"), self)
        self._menuSaveAsDelimiter.setObjectName("menuSaveAsDelimiter")
        self._menuSaveAsDelimiter.setIcon(QIcon.fromTheme("document-save-as", QIcon(":/icons/actions/16/document-save-as.svg")))
        self._menuSaveAsDelimiter.setToolTip(self.tr("Save document with specific delimiter under a new name"))
        self._menuSaveAsDelimiter.addActions(self._actionSaveAsDelimiter.actions())

        menuDocument = self.menuBar().addMenu(self.tr("Document"))
        menuDocument.setObjectName("menuDocument")
        menuDocument.addAction(self._actionNew)
        menuDocument.addSeparator()
        menuDocument.addAction(self._actionOpen)
        menuDocument.addMenu(self._menuOpenRecent)
        menuDocument.addSeparator()
        menuDocument.addAction(self._actionSave)
        menuDocument.addAction(self._actionSaveAs)
        menuDocument.addMenu(self._menuSaveAsDelimiter)
        menuDocument.addAction(self._actionSaveCopyAs)
        menuDocument.addAction(self._actionSaveAll)
        menuDocument.addSeparator()
        menuDocument.addAction(self._actionClose)
        menuDocument.addAction(self._actionCloseOther)
        menuDocument.addAction(self._actionCloseAll)

        # Menu: Edit
        menuEdit = self.menuBar().addMenu(self.tr("Edit"))
        menuEdit.setObjectName("menuEdit")

        # Menu: Tools
        menuTools = self.menuBar().addMenu(self.tr("Tools"))
        menuTools.setObjectName("menuTools")

        # Menu: View
        menuView = self.menuBar().addMenu(self.tr("View"))
        menuView.setObjectName("menuView")
        menuView.addAction(self._actionFullScreen)
        menuView.addSeparator()
        menuView.addAction(self._actionTitlebarFullPath)
        menuView.addSeparator()
        menuView.addAction(self._actionToolbarApplication)
        menuView.addAction(self._actionToolbarDocument)
        menuView.addAction(self._actionToolbarEdit)
        menuView.addAction(self._actionToolbarTools)
        menuView.addAction(self._actionToolbarView)
        menuView.addAction(self._actionToolbarHelp)

        # Menu: Help
        menuHelp = self.menuBar().addMenu(self.tr("Help"))
        menuHelp.setObjectName("menuHelp")
        menuHelp.addAction(self._actionKeyboardShortcuts)


    def _createToolBars(self):

        # Toolbar: Application
        self._toolbarApplication = self.addToolBar(self.tr("Application Toolbar"))
        self._toolbarApplication.setObjectName("toolbarApplication")
        self._toolbarApplication.addAction(self._actionAbout)
        self._toolbarApplication.addAction(self._actionPreferences)
        self._toolbarApplication.addSeparator()
        self._toolbarApplication.addAction(self._actionQuit)
        self._toolbarApplication.visibilityChanged.connect(lambda visible: self._actionToolbarApplication.setChecked(visible))

        # Toolbar: Document
        self._toolbarDocument = self.addToolBar(self.tr("Document Toolbar"))
        self._toolbarDocument.setObjectName("toolbarDocument")
        self._toolbarDocument.addAction(self._actionNew)
        self._toolbarDocument.addAction(self._actionOpen)
        self._toolbarDocument.addSeparator()
        self._toolbarDocument.addAction(self._actionSave)
        self._toolbarDocument.addAction(self._actionSaveAs)
        self._toolbarDocument.addSeparator()
        self._toolbarDocument.addAction(self._actionClose)
        self._toolbarDocument.visibilityChanged.connect(lambda visible: self._actionToolbarDocument.setChecked(visible))

        # Toolbar: Edit
        self._toolbarEdit = self.addToolBar(self.tr("Edit Toolbar"))
        self._toolbarEdit.setObjectName("toolbarEdit")
        self._toolbarEdit.visibilityChanged.connect(lambda visible: self._actionToolbarEdit.setChecked(visible))

        # Toolbar: Tools
        self._toolbarTools = self.addToolBar(self.tr("Tools Toolbar"))
        self._toolbarTools.setObjectName("toolbarTools")
        self._toolbarTools.visibilityChanged.connect(lambda visible: self._actionToolbarTools.setChecked(visible))

        # Toolbar: View
        self._toolbarView = self.addToolBar(self.tr("View Toolbar"))
        self._toolbarView.setObjectName("toolbarView")
        self._toolbarView.addAction(self._actionFullScreen)
        self._toolbarView.visibilityChanged.connect(lambda visible: self._actionToolbarView.setChecked(visible))

        # Toolbar: Help
        self._toolbarHelp = self.addToolBar(self.tr("Help Toolbar"))
        self._toolbarHelp.setObjectName("toolbarHelp")
        self._toolbarHelp.addAction(self._actionKeyboardShortcuts)
        self._toolbarHelp.visibilityChanged.connect(lambda visible: self._actionToolbarHelp.setChecked(visible))


    def _updateActions(self, subWindowCount=0):

        hasDocument = subWindowCount >= 1
        hasDocuments = subWindowCount >= 2

        # Actions: Document
        self._actionSave.setEnabled(hasDocument)
        self._actionSaveAs.setEnabled(hasDocument)
        self._menuSaveAsDelimiter.setEnabled(hasDocument)
        self._actionSaveCopyAs.setEnabled(hasDocument)
        self._actionSaveAll.setEnabled(hasDocument)
        self._actionClose.setEnabled(hasDocument)
        self._actionCloseOther.setEnabled(hasDocuments)
        self._actionCloseAll.setEnabled(hasDocument)


    def _updateActionFullScreen(self):

        if not self.isFullScreen():
            self._actionFullScreen.setText(self.tr("Full Screen Mode"))
            self._actionFullScreen.setIcon(QIcon.fromTheme("view-fullscreen", QIcon(":/icons/actions/16/view-fullscreen.svg")))
            self._actionFullScreen.setChecked(False)
            self._actionFullScreen.setToolTip(self.tr("Display the window in full screen"))
        else:
            self._actionFullScreen.setText(self.tr("Exit Full Screen Mode"))
            self._actionFullScreen.setIcon(QIcon.fromTheme("view-restore", QIcon(":/icons/actions/16/view-restore.svg")))
            self._actionFullScreen.setChecked(True)
            self._actionFullScreen.setToolTip(self.tr("Exit the full screen mode"))


    def _updateActionRecentDocuments(self):

        # Add items to the list, if necessary
        for idx in range(len(self._actionRecentDocuments)+1, self._preferences.maximumRecentDocuments()+1):

            actionRecentDocument = QAction(self)
            actionRecentDocument.setObjectName(f"actionRecentDocument_{idx}")
            actionRecentDocument.triggered.connect(lambda data=actionRecentDocument.data(): self._onActionOpenRecentDocumentTriggered(data))

            self._actionRecentDocuments.append(actionRecentDocument)

        # Remove items from the list, if necessary
        while len(self._actionRecentDocuments) > self._preferences.maximumRecentDocuments():
            self._actionRecentDocuments.pop()

        # Update items
        for idx in range(len(self._actionRecentDocuments)):
            text = None
            data = None
            show = False

            if idx < len(self._recentDocuments):
                text = self.tr("{0} [{1}]").format(QFileInfo(self._recentDocuments[idx]).fileName(), self._recentDocuments[idx])
                data = self._recentDocuments[idx]
                show = True

            self._actionRecentDocuments[idx].setText(text)
            self._actionRecentDocuments[idx].setData(data)
            self._actionRecentDocuments[idx].setVisible(show)


    def _updateMenuOpenRecent(self):

        self._menuOpenRecent.clear()

        if self._preferences.maximumRecentDocuments() > 0:
            # Document list wanted; show the menu
            self._menuOpenRecent.menuAction().setVisible(True)

            if len(self._recentDocuments) > 0:
                # Document list has items; enable the menu
                self._menuOpenRecent.setEnabled(True)

                self._menuOpenRecent.addActions(self._actionRecentDocuments)
                self._menuOpenRecent.addSeparator()
                self._menuOpenRecent.addAction(self._actionOpenRecentClear)
            else:
                # Document list is empty; disable the menu
                self._menuOpenRecent.setEnabled(False)
        else:
            # No document list wanted; hide the menu
            self._menuOpenRecent.menuAction().setVisible(False)


    def _updateTitleBar(self):

        title = None

        document = self._activeDocument()
        if document:
            title = document.canonicalName() if self._actionTitlebarFullPath.isChecked() and document.canonicalName() else document.documentTitle()

        self.setWindowTitle(title)


    def _onActionAboutTriggered(self):

        dialog = AboutDialog(self)
        dialog.exec_()


    def _onActionColophonTriggered(self):

        dialog = ColophonDialog(self)
        dialog.exec_()


    def _onActionPreferencesTriggered(self):

        dialog = PreferencesDialog(self)
        dialog.setPreferences(self._preferences)
        dialog.exec_()

        self._preferences = dialog.preferences()

        self._updateRecentDocuments(None)
        self._updateMenuOpenRecent()


    def _onActionNewTriggered(self):

        self._loadDocument("")


    def _onActionOpenTriggered(self):

        fileNames = QFileDialog.getOpenFileNames(self, self.tr("Open Document"),
                        QStandardPaths.writableLocation(QStandardPaths.HomeLocation),
                        self.tr("CSV Files (*.csv);;All Files (*.*)"))[0]

        for fileName in fileNames:
            self._openDocument(fileName)


    def _onActionOpenRecentDocumentTriggered(self, canonicalName):
        pass

#        self.openDocument(canonicalName)


    def _onActionOpenRecentClearTriggered(self):

        self._recentDocuments.clear()

        self._updateRecentDocuments(None)
        self._updateMenuOpenRecent()


    def _onActionSaveTriggered(self):
        pass


    def _onActionSaveAsTriggered(self):
        pass


    def _onActionSaveAsDelimiterTriggered(self, delimiter):
        pass


    def _onActionSaveCopyAsTriggered(self):
        pass


    def _onActionSaveAllTriggered(self):
        pass


    def _onActionCloseTriggered(self):

        self._documentArea.closeActiveSubWindow()


    def _onActionCloseOtherTriggered(self):

        for subWindow in self._documentArea.subWindowList():
            if subWindow != self._documentArea.activeSubWindow():
                subWindow.close()


    def _onActionCloseAllTriggered(self):

        self._documentArea.closeAllSubWindows()


    def _onActionFullScreenTriggered(self):

        if not self.isFullScreen():
            self.setWindowState(self.windowState() | Qt.WindowFullScreen)
        else:
            self.setWindowState(self.windowState() & ~Qt.WindowFullScreen)

        self._updateActionFullScreen()


    def _onActionTitlebarFullPathTriggered(self):

        self._updateTitleBar()


    def _onActionKeyboardShortcutsTriggered(self):

        if not self._keyboardShortcutsDialog:
            self._keyboardShortcutsDialog = KeyboardShortcutsDialog(self)

        self._keyboardShortcutsDialog.show()
        self._keyboardShortcutsDialog.raise_()
        self._keyboardShortcutsDialog.activateWindow()


    def _onDocumentWindowActivated(self, subWindow):

        # Update the application window
        self._updateActions(len(self._documentArea.subWindowList()))
        self._updateTitleBar()

        if not subWindow:
            return


    def _onDocumentAboutToClose(self, canonicalName):

        # Workaround to show subwindows always maximized
        for subWindow in self._documentArea.subWindowList():
            if not subWindow.isMaximized():
                subWindow.showMaximized()

        # Update menu items without the emitter
        self._updateActions(len(self._documentArea.subWindowList()) - 1)


    def _createDocument(self):

        document = Document()
        document.setPreferences(self._preferences)
        document.aboutToClose.connect(self._onDocumentAboutToClose)

        subWindow = self._documentArea.addSubWindow(document)
        subWindow.setWindowIcon(QIcon())
        subWindow.showMaximized()

        return document


    def _createDocumentIndex(self, canonicalName):

        fileName = QFileInfo(canonicalName).fileName()
        canonicalIndex = 0

        for subWindow in self._documentArea.subWindowList():
            if QFileInfo(subWindow.widget().canonicalName()).fileName() == fileName:
                if subWindow.widget().canonicalIndex() > canonicalIndex:
                    canonicalIndex = subWindow.widget().canonicalIndex()

        return canonicalIndex + 1


    def _findDocumentWindow(self, canonicalName):

        for subWindow in self._documentArea.subWindowList():
            if subWindow.widget().canonicalName() == canonicalName:
                return subWindow

        return None


    def _activeDocument(self):

        subWindow = self._documentArea.activeSubWindow()

        return subWindow.widget() if subWindow else None


    def _openDocument(self, fileName):

        canonicalName = QFileInfo(fileName).canonicalFilePath()

        subWindow = self._findDocumentWindow(canonicalName)
        if subWindow:
            # Given document is already loaded; activate the subwindow
            self._documentArea.setActiveSubWindow(subWindow)

            # Update list of recent documents
            self._updateRecentDocuments(canonicalName)
            self._updateMenuOpenRecent()
            return True

        return self._loadDocument(canonicalName);


    def _loadDocument(self, canonicalName):

        document = self._createDocument()

        succeeded = document.load(canonicalName)
        if succeeded:
            document.setCanonicalIndex(self._createDocumentIndex(canonicalName))
            document.updateDocumentTitle()
            document.show()

            # Update list of recent documents
            self._updateRecentDocuments(canonicalName)
            self._updateMenuOpenRecent()

            # Update the application window
            self._updateActions(len(self._documentArea.subWindowList()))
            self._updateTitleBar()
        else:
            document.close()

        return succeeded


    def _updateRecentDocuments(self, canonicalName):

        if canonicalName:
            while canonicalName in self._recentDocuments:
                self._recentDocuments.remove(canonicalName)
            self._recentDocuments.insert(0, canonicalName)

        # Remove items from the list, if necessary
        while len(self._recentDocuments) > self._preferences.maximumRecentDocuments():
            self._recentDocuments.pop()

        self._updateActionRecentDocuments()
コード例 #16
0
class Mixin:
    def make_file_actions(self):
        self.file_new_action = make_action(
            self, get_icon(DOCUMENT_NEW_SVG), '&New...', self.file_new,
            QKeySequence.New, f'Create a new SQLite or {APPNAME} database')
        self.file_open_action = make_action(
            self, get_icon(DOCUMENT_OPEN_SVG), '&Open...', self.file_open,
            QKeySequence.Open,
            f'Open an existing SQLite or {APPNAME} database')
        self.file_open_recent_action = make_action(self,
                                                   get_icon(DOCUMENT_OPEN_SVG),
                                                   'Open &Recent')
        self.file_open_recent_menu = QMenu(self)
        self.file_open_recent_action.setMenu(self.file_open_recent_menu)
        self.file_save_action = make_action(
            self, get_icon(FILESAVE_SVG), '&Save', self.file_save,
            QKeySequence.Save, 'Save any unsaved changes to the database')
        self.file_saveas_action = make_action(
            self, get_icon(FILESAVEAS_SVG), 'Save &As...', self.file_saveas,
            QKeySequence.SaveAs, 'Save the database under a new name and '
            'open the database with the new name')
        self.file_backup_action = make_action(
            self,
            get_icon(FILESAVEAS_SVG),
            '&Backup...',
            self.file_backup,
            tip='Save a copy of the database')
        self.file_import_action = make_action(
            self,
            get_icon(IMPORT_SVG),
            '&Import...',
            self.file_import,
            tip='Import an external data file (e.g., CSV) as a new '
            'table in the current database')
        self.file_export_action = make_action(
            self,
            get_icon(EXPORT_SVG),
            '&Export...',
            self.file_export,
            tip="Export a table, view or SELECT query's data as an "
            'external data file (e.g., CSV)')
        self.file_quit_action = make_action(
            self, get_icon(SHUTDOWN_SVG), '&Quit', self.close,
            QKeySequence(Qt.CTRL + Qt.Key_Q),
            'Save any unsaved changes and quit')

    @property
    def file_actions_for_menu(self):
        return (self.file_new_action, self.file_open_action,
                self.file_open_recent_action, self.file_save_action,
                self.file_saveas_action, self.file_backup_action, None,
                self.file_import_action, self.file_export_action, None,
                self.file_quit_action)

    @property
    def file_actions_for_toolbar(self):
        return (self.file_new_action, self.file_open_action,
                self.file_save_action, None, self.file_import_action,
                self.file_export_action)

    def file_update_ui(self):
        enable = bool(self.db)
        for action in (self.file_save_action, self.file_saveas_action,
                       self.file_backup_action, self.file_export_action):
            action.setEnabled(enable)
        QTimer.singleShot(0, self.file_populate_open_recent_menu)

    def file_populate_open_recent_menu(self):
        self.file_open_recent_menu.clear()
        filenames = [str(filename) for filename in list(self.recent_files)]
        self.file_open_recent_menu.setEnabled(bool(filenames))
        icon = get_icon(DOCUMENT_OPEN_SVG)
        for i, filename in enumerate(filenames, 1):
            action = make_action(
                self,
                icon,
                '&{} {}'.format(i, filename),
                lambda *_, filename=filename: self.file_load(filename),
                tip=f'Open {filename}')
            self.file_open_recent_menu.addAction(action)
        if filenames:
            self.file_open_recent_menu.addSeparator()
            self.file_open_recent_menu.addAction(
                make_action(self,
                            get_icon(EDIT_CLEAR_SVG),
                            '&Clear',
                            self.file_clear_recent_files,
                            tip='Clear the list of recently opened databases'))

    def file_clear_recent_files(self):
        self.recent_files.clear()
        self.file_update_ui()

    def file_new(self):
        if filename := self._file_new_or_open('New',
                                              QFileDialog.getSaveFileName):
            if filename.exists():
                QMessageBox.warning(
                    self, f'Database exists — {APPNAME}',
                    f'Will not overwrite an existing database '
                    '({filename}) with a new one')
            else:
                self.file_load(filename, new=True)