Example #1
0
class MainWidget(QWidget):
    def __init__(self, parent: QWidget, model: Model) -> None:
        super().__init__(parent)

        logger.add(self.log)

        settings = QSettings()
        self.mainlayout = QVBoxLayout()
        self.mainlayout.setContentsMargins(5, 5, 5, 5)
        self.setLayout(self.mainlayout)

        # summary

        summarylayout = FlowLayout()
        summarylayout.setContentsMargins(0, 0, 0, 0)
        self.summary = QWidget()
        self.summary.setLayout(summarylayout)
        self.mainlayout.addWidget(self.summary)
        self.summary.setVisible(
            settings.value('showSummary', 'True') == 'True')

        detailslayout = QHBoxLayout()
        detailslayout.setContentsMargins(1, 0, 0, 0)
        detailslayout.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
        detailslayout.setSpacing(15)
        details = QWidget()
        details.setLayout(detailslayout)
        summarylayout.addWidget(details)

        self.modstotal = QLabel()
        detailslayout.addWidget(self.modstotal)
        self.modsenabled = QLabel()
        detailslayout.addWidget(self.modsenabled)
        self.overridden = QLabel()
        detailslayout.addWidget(self.overridden)
        self.conflicts = QLabel()
        detailslayout.addWidget(self.conflicts)

        buttonslayout = QHBoxLayout()
        buttonslayout.setContentsMargins(0, 0, 0, 0)
        buttonslayout.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        buttons = QWidget()
        buttons.setLayout(buttonslayout)
        summarylayout.addWidget(buttons)

        self.startscriptmerger = QPushButton('Start Script Merger')
        self.startscriptmerger.setContentsMargins(0, 0, 0, 0)
        self.startscriptmerger.setMinimumWidth(140)
        self.startscriptmerger.setIcon(
            QIcon(str(getRuntimePath('resources/icons/script.ico'))))
        self.startscriptmerger.clicked.connect(lambda: [
            openExecutable(Path(str(settings.value('scriptMergerPath'))), True)
        ])
        self.startscriptmerger.setEnabled(
            verifyScriptMergerPath(
                Path(str(settings.value('scriptMergerPath')))) is not None)
        buttonslayout.addWidget(self.startscriptmerger)

        self.startgame = QPushButton('Start Game')
        self.startgame.setContentsMargins(0, 0, 0, 0)
        self.startgame.setMinimumWidth(100)
        self.startgame.setIcon(
            QIcon(str(getRuntimePath('resources/icons/w3b.ico'))))
        buttonslayout.addWidget(self.startgame)

        # splitter

        self.splitter = QSplitter(Qt.Vertical)

        self.stack = QStackedWidget()
        self.splitter.addWidget(self.stack)

        # mod list widget

        self.modlistwidget = QWidget()
        self.modlistlayout = QVBoxLayout()
        self.modlistlayout.setContentsMargins(0, 0, 0, 0)
        self.modlistwidget.setLayout(self.modlistlayout)
        self.stack.addWidget(self.modlistwidget)

        # search bar

        self.searchbar = QLineEdit()
        self.searchbar.setPlaceholderText('Search...')
        self.modlistlayout.addWidget(self.searchbar)

        # mod list

        self.modlist = ModList(self, model)
        self.modlistlayout.addWidget(self.modlist)

        self.searchbar.textChanged.connect(lambda e: self.modlist.setFilter(e))

        # welcome message

        welcomelayout = QVBoxLayout()
        welcomelayout.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        welcomewidget = QWidget()
        welcomewidget.setLayout(welcomelayout)
        welcomewidget.dragEnterEvent = self.modlist.dragEnterEvent  # type: ignore
        welcomewidget.dragMoveEvent = self.modlist.dragMoveEvent  # type: ignore
        welcomewidget.dragLeaveEvent = self.modlist.dragLeaveEvent  # type: ignore
        welcomewidget.dropEvent = self.modlist.dropEvent  # type: ignore
        welcomewidget.setAcceptDrops(True)

        icon = QIcon(str(getRuntimePath('resources/icons/open-folder.ico')))
        iconpixmap = icon.pixmap(32, 32)
        icon = QLabel()
        icon.setPixmap(iconpixmap)
        icon.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        icon.setContentsMargins(4, 4, 4, 4)
        welcomelayout.addWidget(icon)

        welcome = QLabel('''<p><font>
            No mod installed yet.
            Drag a mod into this area to get started!
            </font></p>''')
        welcome.setAttribute(Qt.WA_TransparentForMouseEvents)
        welcome.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        welcomelayout.addWidget(welcome)

        self.stack.addWidget(welcomewidget)

        # output log

        self.output = QTextEdit(self)
        self.output.setTextInteractionFlags(Qt.NoTextInteraction)
        self.output.setReadOnly(True)
        self.output.setContextMenuPolicy(Qt.NoContextMenu)
        self.output.setPlaceholderText('Program output...')
        self.splitter.addWidget(self.output)

        # TODO: enhancement: show indicator if scripts have to be merged

        self.splitter.setStretchFactor(0, 1)
        self.splitter.setStretchFactor(1, 0)
        self.mainlayout.addWidget(self.splitter)

        # TODO: incomplete: make start game button functional

        if len(model):
            self.stack.setCurrentIndex(0)
            self.splitter.setSizes([self.splitter.size().height(), 50])
        else:
            self.stack.setCurrentIndex(1)
            self.splitter.setSizes([self.splitter.size().height(), 0])
        model.updateCallbacks.append(self.modelUpdateEvent)

        asyncio.create_task(model.loadInstalled())

    def keyPressEvent(self, event: QKeyEvent) -> None:
        if event.key() == Qt.Key_Escape:
            self.modlist.setFocus()
            self.searchbar.setText('')
        elif event.matches(QKeySequence.Find):
            self.searchbar.setFocus()
        elif event.matches(QKeySequence.Paste):
            self.pasteEvent()
        # TODO: enhancement: add start game / start script merger shortcuts
        else:
            super().keyPressEvent(event)

    def pasteEvent(self) -> None:
        clipboard = QApplication.clipboard().text().splitlines()
        if len(clipboard) == 1 and isValidNexusModsUrl(clipboard[0]):
            self.parentWidget().showDownloadModDialog()
        else:
            urls = [
                url for url in QApplication.clipboard().text().splitlines()
                if len(str(url.strip()))
            ]
            if all(
                    isValidModDownloadUrl(url) or isValidFileUrl(url)
                    for url in urls):
                asyncio.create_task(self.modlist.checkInstallFromURLs(urls))

    def modelUpdateEvent(self, model: Model) -> None:
        total = len(model)
        enabled = len([mod for mod in model if model[mod].enabled])
        overridden = sum(
            len(file) for file in model.conflicts.bundled.values())
        conflicts = sum(len(file) for file in model.conflicts.scripts.values())
        self.modstotal.setText(
            f'<font color="#73b500" size="4">{total}</font> \
                <font color="#888" text-align="center">Installed Mod{"" if total == 1 else "s"}</font>'
        )
        self.modsenabled.setText(
            f'<font color="#73b500" size="4">{enabled}</font> \
                <font color="#888">Enabled Mod{"" if enabled == 1 else "s"}</font>'
        )
        self.overridden.setText(
            f'<font color="{"#b08968" if overridden > 0 else "#84C318"}" size="4">{overridden}</font> \
                <font color="#888">Overridden File{"" if overridden == 1 else "s"}</font> '
        )
        self.conflicts.setText(
            f'<font color="{"#E55934" if conflicts > 0 else "#aad576"}" size="4">{conflicts}</font> \
                <font color="#888">Unresolved Conflict{"" if conflicts == 1 else "s"}</font> '
        )

        if len(model) > 0:
            if self.stack.currentIndex() != 0:
                self.stack.setCurrentIndex(0)
                self.repaint()
        else:
            if self.stack.currentIndex() != 1:
                self.stack.setCurrentIndex(1)
                self.repaint()

    def unhideOutput(self) -> None:
        if self.splitter.sizes()[1] < 10:
            self.splitter.setSizes([self.splitter.size().height(), 50])

    def unhideModList(self) -> None:
        if self.splitter.sizes()[0] < 10:
            self.splitter.setSizes([50, self.splitter.size().height()])

    def log(self, message: Any) -> None:
        # format log messages to user readable output
        settings = QSettings()

        record = message.record
        message = record['message']
        extra = record['extra']
        level = record['level'].name.lower()

        name = str(extra['name']
                   ) if 'name' in extra and extra['name'] is not None else ''
        path = str(extra['path']
                   ) if 'path' in extra and extra['path'] is not None else ''
        dots = bool(
            extra['dots']
        ) if 'dots' in extra and extra['dots'] is not None else False
        newline = bool(
            extra['newline']
        ) if 'newline' in extra and extra['newline'] is not None else False
        output = bool(
            extra['output']
        ) if 'output' in extra and extra['output'] is not None else bool(
            message)
        modlist = bool(
            extra['modlist']
        ) if 'modlist' in extra and extra['modlist'] is not None else False

        if level in ['debug'
                     ] and settings.value('debugOutput', 'False') != 'True':
            if newline:
                self.output.append(f'')
            return

        n = '<br>' if newline else ''
        d = '...' if dots else ''
        if len(name) and len(path):
            path = f' ({path})'

        if output:
            message = html.escape(message, quote=True)

            if level in ['success', 'error', 'warning']:
                message = f'<strong>{message}</strong>'
            if level in ['success']:
                message = f'<font color="#04c45e">{message}</font>'
            if level in ['error', 'critical']:
                message = f'<font color="#ee3b3b">{message}</font>'
            if level in ['warning']:
                message = f'<font color="#ff6500">{message}</font>'
            if level in ['debug', 'trace']:
                message = f'<font color="#aaa">{message}</font>'
                path = f'<font color="#aaa">{path}</font>' if path else ''
                d = f'<font color="#aaa">{d}</font>' if d else ''

            time = record['time'].astimezone(
                tz=None).strftime('%Y-%m-%d %H:%M:%S')
            message = f'<font color="#aaa">{time}</font> {message}'
            self.output.append(
                f'{n}{message.strip()}{" " if name or path else ""}{name}{path}{d}'
            )
        else:
            self.output.append(f'')

        self.output.verticalScrollBar().setValue(
            self.output.verticalScrollBar().maximum())
        self.output.repaint()

        if modlist:
            self.unhideModList()
        if settings.value('unhideOutput', 'True') == 'True' and output:
            self.unhideOutput()
Example #2
0
    def __init__(self, model):
        """Initialize MNELAB main window.

        Parameters
        ----------
        model : mnelab.model.Model instance
            The main window needs to connect to a model containing all data sets. This
            decouples the GUI from the data (model/view).
        """
        super().__init__()
        self.model = model  # data model
        self.setWindowTitle("MNELAB")

        # restore settings
        settings = read_settings()
        self.recent = settings["recent"]  # list of recent files
        self.resize(settings["size"])
        self.move(settings["pos"])

        # remove None entries from self.recent
        self.recent = [recent for recent in self.recent if recent is not None]

        # trigger theme setting
        QIcon.setThemeSearchPaths([str(Path(__file__).parent / "icons")])
        self.event(QEvent(QEvent.PaletteChange))

        self.actions = {}  # contains all actions

        # initialize menus
        file_menu = self.menuBar().addMenu("&File")
        icon = QIcon.fromTheme("open-file")
        self.actions["open_file"] = file_menu.addAction(
            icon, "&Open...", self.open_data, QKeySequence.Open)
        self.recent_menu = file_menu.addMenu("Open recent")
        self.recent_menu.aboutToShow.connect(self._update_recent_menu)
        self.recent_menu.triggered.connect(self._load_recent)
        if not self.recent:
            self.recent_menu.setEnabled(False)
        self.actions["close_file"] = file_menu.addAction(
            "&Close", self.model.remove_data, QKeySequence.Close)
        self.actions["close_all"] = file_menu.addAction(
            "Close all", self.close_all)
        file_menu.addSeparator()
        icon = QIcon.fromTheme("meta-info")
        self.actions["meta_info"] = file_menu.addAction(
            icon, "Show information...", self.meta_info)
        file_menu.addSeparator()
        self.actions["import_bads"] = file_menu.addAction(
            "Import bad channels...", lambda: self.import_file(
                model.import_bads, "Import bad channels", "*.csv"))
        self.actions["import_events"] = file_menu.addAction(
            "Import events...", lambda: self.import_file(
                model.import_events, "Import events", "*.csv"))
        self.actions["import_annotations"] = file_menu.addAction(
            "Import annotations...", lambda: self.import_file(
                model.import_annotations, "Import annotations", "*.csv"))
        self.actions["import_ica"] = file_menu.addAction(
            "Import &ICA...", lambda: self.open_file(
                model.import_ica, "Import ICA", "*.fif *.fif.gz"))
        file_menu.addSeparator()
        self.export_menu = file_menu.addMenu("Export data")
        for ext, description in writers.items():
            action = "export_data" + ext.replace(".", "_")
            self.actions[action] = self.export_menu.addAction(
                f"{ext[1:].upper()} ({description[1]})...",
                partial(self.export_file, model.export_data, "Export data",
                        "*" + ext))
        self.actions["export_bads"] = file_menu.addAction(
            "Export &bad channels...", lambda: self.export_file(
                model.export_bads, "Export bad channels", "*.csv"))
        self.actions["export_events"] = file_menu.addAction(
            "Export &events...", lambda: self.export_file(
                model.export_events, "Export events", "*.csv"))
        self.actions["export_annotations"] = file_menu.addAction(
            "Export &annotations...", lambda: self.export_file(
                model.export_annotations, "Export annotations", "*.csv"))
        self.actions["export_ica"] = file_menu.addAction(
            "Export ICA...", lambda: self.export_file(
                model.export_ica, "Export ICA", "*.fif *.fif.gz"))
        file_menu.addSeparator()
        self.actions["xdf_chunks"] = file_menu.addAction(
            "Show XDF chunks...", self.xdf_chunks)
        file_menu.addSeparator()
        self.actions["quit"] = file_menu.addAction("&Quit", self.close,
                                                   QKeySequence.Quit)

        edit_menu = self.menuBar().addMenu("&Edit")
        self.actions["pick_chans"] = edit_menu.addAction(
            "P&ick channels...", self.pick_channels)
        icon = QIcon.fromTheme("chan-props")
        self.actions["chan_props"] = edit_menu.addAction(
            icon, "Channel &properties...", self.channel_properties)
        self.actions["set_montage"] = edit_menu.addAction(
            "Set &montage...", self.set_montage)
        edit_menu.addSeparator()
        self.actions["set_ref"] = edit_menu.addAction("Set &reference...",
                                                      self.set_reference)
        edit_menu.addSeparator()
        self.actions["annotations"] = edit_menu.addAction(
            "&Annotations...", self.edit_annotations)
        self.actions["events"] = edit_menu.addAction("&Events...",
                                                     self.edit_events)

        edit_menu.addSeparator()
        self.actions["crop"] = edit_menu.addAction("&Crop data...", self.crop)
        self.actions["append_data"] = edit_menu.addAction(
            "Appen&d data...", self.append_data)

        plot_menu = self.menuBar().addMenu("&Plot")
        icon = QIcon.fromTheme("plot-data")
        self.actions["plot_data"] = plot_menu.addAction(
            icon, "&Data", self.plot_data)
        icon = QIcon.fromTheme("plot-psd")
        self.actions["plot_psd"] = plot_menu.addAction(
            icon, "&Power spectral density", self.plot_psd)
        icon = QIcon.fromTheme("plot-locations")
        self.actions["plot_locations"] = plot_menu.addAction(
            icon, "&Channel locations", self.plot_locations)
        self.actions["plot_erds"] = plot_menu.addAction(
            "&ERDS maps...", self.plot_erds)
        plot_menu.addSeparator()
        self.actions["plot_ica_components"] = plot_menu.addAction(
            "ICA &components...", self.plot_ica_components)
        self.actions["plot_ica_sources"] = plot_menu.addAction(
            "ICA &sources...", self.plot_ica_sources)

        tools_menu = self.menuBar().addMenu("&Tools")
        icon = QIcon.fromTheme("filter-data")
        self.actions["filter"] = tools_menu.addAction(icon, "&Filter data...",
                                                      self.filter_data)
        icon = QIcon.fromTheme("find-events")
        self.actions["find_events"] = tools_menu.addAction(
            icon, "Find &events...", self.find_events)
        self.actions["events_from_annotations"] = tools_menu.addAction(
            "Create events from annotations", self.events_from_annotations)
        self.actions["annotations_from_events"] = tools_menu.addAction(
            "Create annotations from events", self.annotations_from_events)
        tools_menu.addSeparator()
        nirs_menu = tools_menu.addMenu("NIRS")
        self.actions["convert_od"] = nirs_menu.addAction(
            "Convert to &optical density", self.convert_od)
        self.actions["convert_bl"] = nirs_menu.addAction(
            "Convert to &haemoglobin", self.convert_bl)

        tools_menu.addSeparator()
        icon = QIcon.fromTheme("run-ica")
        self.actions["run_ica"] = tools_menu.addAction(icon, "Run &ICA...",
                                                       self.run_ica)
        self.actions["apply_ica"] = tools_menu.addAction(
            "Apply &ICA", self.apply_ica)
        tools_menu.addSeparator()
        self.actions["interpolate_bads"] = tools_menu.addAction(
            "Interpolate bad channels...", self.interpolate_bads)
        tools_menu.addSeparator()
        icon = QIcon.fromTheme("epoch-data")
        self.actions["epoch_data"] = tools_menu.addAction(
            icon, "Create epochs...", self.epoch_data)

        view_menu = self.menuBar().addMenu("&View")
        self.actions["history"] = view_menu.addAction("&History...",
                                                      self.show_history)
        self.actions["toolbar"] = view_menu.addAction("&Toolbar",
                                                      self._toggle_toolbar)
        self.actions["toolbar"].setCheckable(True)
        self.actions["statusbar"] = view_menu.addAction(
            "&Statusbar", self._toggle_statusbar)
        self.actions["statusbar"].setCheckable(True)

        help_menu = self.menuBar().addMenu("&Help")
        self.actions["about"] = help_menu.addAction("&About", self.show_about)
        self.actions["about_qt"] = help_menu.addAction("About &Qt",
                                                       self.show_about_qt)

        # actions that are always enabled
        self.always_enabled = [
            "open_file", "about", "about_qt", "quit", "xdf_chunks", "toolbar",
            "statusbar"
        ]

        # set up toolbar
        self.toolbar = self.addToolBar("toolbar")
        self.toolbar.setObjectName("toolbar")
        self.toolbar.addAction(self.actions["open_file"])
        self.toolbar.addAction(self.actions["meta_info"])
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.actions["chan_props"])
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.actions["plot_data"])
        self.toolbar.addAction(self.actions["plot_psd"])
        self.toolbar.addAction(self.actions["plot_locations"])
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.actions["filter"])
        self.toolbar.addAction(self.actions["find_events"])
        self.toolbar.addAction(self.actions["epoch_data"])
        self.toolbar.addAction(self.actions["run_ica"])
        self.toolbar.setMovable(False)
        self.setUnifiedTitleAndToolBarOnMac(True)
        if settings["toolbar"]:
            self.toolbar.show()
            self.actions["toolbar"].setChecked(True)
        else:
            self.toolbar.hide()
            self.actions["toolbar"].setChecked(False)

        # set up data model for sidebar (list of open files)
        self.names = QStringListModel()
        self.names.dataChanged.connect(self._update_names)
        splitter = QSplitter()
        self.sidebar = QListView()
        self.sidebar.setFrameStyle(QFrame.NoFrame)
        self.sidebar.setFocusPolicy(Qt.NoFocus)
        self.sidebar.setModel(self.names)
        self.sidebar.clicked.connect(self._update_data)
        splitter.addWidget(self.sidebar)
        self.infowidget = InfoWidget()
        splitter.addWidget(self.infowidget)
        width = splitter.size().width()
        splitter.setSizes((int(width * 0.3), int(width * 0.7)))
        self.setCentralWidget(splitter)

        self.status_label = QLabel()
        self.statusBar().addPermanentWidget(self.status_label)
        if settings["statusbar"]:
            self.statusBar().show()
            self.actions["statusbar"].setChecked(True)
        else:
            self.statusBar().hide()
            self.actions["statusbar"].setChecked(False)

        self.setAcceptDrops(True)
        self.data_changed()
Example #3
0
class DebugView(QWidget, View):
	class DebugViewHistoryEntry(HistoryEntry):
		def __init__(self, memory_addr, address, is_raw):
			HistoryEntry.__init__(self)

			self.memory_addr = memory_addr
			self.address = address
			self.is_raw = is_raw

		def __repr__(self):
			if self.is_raw:
				return "<raw history: {}+{:0x} (memory: {:0x})>".format(self.address['module'], self.address['offset'], self.memory_addr)
			return "<code history: {:0x} (memory: {:0x})>".format(self.address, self.memory_addr)

	def __init__(self, parent, data):
		if not type(data) == BinaryView:
			raise Exception('expected widget data to be a BinaryView')

		self.bv = data

		self.debug_state = binjaplug.get_state(data)
		memory_view = self.debug_state.memory_view
		self.debug_state.ui.debug_view = self

		QWidget.__init__(self, parent)
		self.controls = ControlsWidget.DebugControlsWidget(self, "Controls", data, self.debug_state)
		View.__init__(self)

		self.setupView(self)

		self.current_offset = 0

		self.splitter = QSplitter(Qt.Orientation.Horizontal, self)

		frame = ViewFrame.viewFrameForWidget(self)
		self.memory_editor = LinearView(memory_view, frame)
		self.binary_editor = DisassemblyContainer(frame, data, frame)

		self.binary_text = TokenizedTextView(self, memory_view)
		self.is_raw_disassembly = False
		self.raw_address = 0

		self.is_navigating_history = False
		self.memory_history_addr = 0

		# TODO: Handle these and change views accordingly
		# Currently they are just disabled as the DisassemblyContainer gets confused
		# about where to go and just shows a bad view
		self.binary_editor.getDisassembly().actionHandler().bindAction("View in Hex Editor", UIAction())
		self.binary_editor.getDisassembly().actionHandler().bindAction("View in Linear Disassembly", UIAction())
		self.binary_editor.getDisassembly().actionHandler().bindAction("View in Types View", UIAction())

		self.memory_editor.actionHandler().bindAction("View in Hex Editor", UIAction())
		self.memory_editor.actionHandler().bindAction("View in Disassembly Graph", UIAction())
		self.memory_editor.actionHandler().bindAction("View in Types View", UIAction())

		small_font = QApplication.font()
		small_font.setPointSize(11)

		bv_layout = QVBoxLayout()
		bv_layout.setSpacing(0)
		bv_layout.setContentsMargins(0, 0, 0, 0)

		bv_label = QLabel("Loaded File")
		bv_label.setFont(small_font)
		bv_layout.addWidget(bv_label)
		bv_layout.addWidget(self.binary_editor)

		self.bv_widget = QWidget()
		self.bv_widget.setLayout(bv_layout)

		disasm_layout = QVBoxLayout()
		disasm_layout.setSpacing(0)
		disasm_layout.setContentsMargins(0, 0, 0, 0)

		disasm_label = QLabel("Raw Disassembly at PC")
		disasm_label.setFont(small_font)
		disasm_layout.addWidget(disasm_label)
		disasm_layout.addWidget(self.binary_text)

		self.disasm_widget = QWidget()
		self.disasm_widget.setLayout(disasm_layout)

		memory_layout = QVBoxLayout()
		memory_layout.setSpacing(0)
		memory_layout.setContentsMargins(0, 0, 0, 0)

		memory_label = QLabel("Debugged Process")
		memory_label.setFont(small_font)
		memory_layout.addWidget(memory_label)
		memory_layout.addWidget(self.memory_editor)

		self.memory_widget = QWidget()
		self.memory_widget.setLayout(memory_layout)

		self.splitter.addWidget(self.bv_widget)
		self.splitter.addWidget(self.memory_widget)

		# Equally sized
		self.splitter.setSizes([0x7fffffff, 0x7fffffff])

		layout = QVBoxLayout()
		layout.setContentsMargins(0, 0, 0, 0)
		layout.setSpacing(0)
		layout.addWidget(self.controls)
		layout.addWidget(self.splitter, 100)
		self.setLayout(layout)

		self.needs_update = True
		self.update_timer = QTimer(self)
		self.update_timer.setInterval(200)
		self.update_timer.setSingleShot(False)
		self.update_timer.timeout.connect(lambda: self.updateTimerEvent())

		self.add_scripting_ref()

		# set initial breakpoint when view is switched
		if self.debug_state.bv and self.debug_state.bv.entry_point:
			local_entry_offset = self.debug_state.bv.entry_point - self.debug_state.bv.start
			if not self.debug_state.breakpoints.contains_offset(self.debug_state.bv.file.original_filename, local_entry_offset):
				self.debug_state.breakpoints.add_offset(self.debug_state.bv.file.original_filename, local_entry_offset)
				if self.debug_state.ui is not None:
					self.debug_state.ui.breakpoint_tag_add(self.debug_state.bv.entry_point)
					self.debug_state.ui.update_highlights()
					self.debug_state.ui.update_breakpoints()

	def add_scripting_ref(self):
		# Hack: The interpreter is just a thread, so look through all threads
		# and assign our state to the interpreter's locals
		for thread in threading.enumerate():
			if type(thread) == PythonScriptingInstance.InterpreterThread:
				thread.locals["dbg"] = self.debug_state

	def getData(self):
		return self.bv

	def getFont(self):
		return binaryninjaui.getMonospaceFont(self)

	def getCurrentOffset(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentOffset()
		return self.raw_address

	def getSelectionOffsets(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getSelectionOffsets()
		return (self.raw_address, self.raw_address)

	def getCurrentFunction(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentFunction()
		return None

	def getCurrentBasicBlock(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentBasicBlock()
		return None

	def getCurrentArchitecture(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentArchitecture()
		return None

	def getCurrentLowLevelILFunction(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentLowLevelILFunction()
		return None

	def getCurrentMediumLevelILFunction(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentMediumLevelILFunction()
		return None

	def getHistoryEntry(self):
		if self.is_navigating_history:
			return None
		memory_addr = self.memory_editor.getCurrentOffset()
		if memory_addr != self.memory_history_addr:
			self.memory_history_addr = memory_addr
		if self.is_raw_disassembly and self.debug_state.connected:
			rel_addr = self.debug_state.modules.absolute_addr_to_relative(self.raw_address)
			return DebugView.DebugViewHistoryEntry(memory_addr, rel_addr, True)
		else:
			address = self.binary_editor.getDisassembly().getCurrentOffset()
			return DebugView.DebugViewHistoryEntry(memory_addr, address, False)

	def navigateToFunction(self, func, offset):
		return self.navigate(offset)

	def navigateToHistoryEntry(self, entry):
		self.is_navigating_history = True
		if hasattr(entry, 'is_raw'):
			self.memory_editor.navigate(entry.memory_addr)
			if entry.is_raw:
				if self.debug_state.connected:
					address = self.debug_state.modules.relative_addr_to_absolute(entry.address)
					self.navigate_raw(address)
			else:
				self.navigate_live(entry.address)

		View.navigateToHistoryEntry(self, entry)
		self.is_navigating_history = False

	def navigate(self, addr):
		# If we're not connected we cannot even check if the address is remote
		if not self.debug_state.connected:
			return self.navigate_live(addr)

		if self.debug_state.memory_view.is_local_addr(addr):
			local_addr = self.debug_state.memory_view.remote_addr_to_local(addr)
			if self.debug_state.bv.read(local_addr, 1) and len(self.debug_state.bv.get_functions_containing(local_addr)) > 0:
				return self.navigate_live(local_addr)

		# This runs into conflicts if some other address space is mapped over
		# where the local BV is currently loaded, but this is was less likely
		# than the user navigating to a function from the UI
		if self.debug_state.bv.read(addr, 1) and len(self.debug_state.bv.get_functions_containing(addr)) > 0:
			return self.navigate_live(addr)

		return self.navigate_raw(addr)

	def navigate_live(self, addr):
		self.show_raw_disassembly(False)
		return self.binary_editor.getDisassembly().navigate(addr)

	def navigate_raw(self, addr):
		if not self.debug_state.connected:
			# Can't navigate to remote addr when disconnected
			return False
		self.raw_address = addr
		self.show_raw_disassembly(True)
		self.load_raw_disassembly(addr)
		return True

	def notifyMemoryChanged(self):
		self.needs_update = True

	def updateTimerEvent(self):
		if self.needs_update:
			self.needs_update = False

			# Refresh the editor
			if not self.debug_state.connected:
				self.memory_editor.navigate(0)
				return

			# self.memory_editor.navigate(self.debug_state.stack_pointer)

	def showEvent(self, event):
		if not event.spontaneous():
			self.update_timer.start()
			self.add_scripting_ref()

	def hideEvent(self, event):
		if not event.spontaneous():
			self.update_timer.stop()

	def shouldBeVisible(self, view_frame):
		if view_frame is None:
			return False
		else:
			return True

	def load_raw_disassembly(self, start_ip):
		# Read a few instructions from rip and disassemble them
		inst_count = 50

		arch_dis = self.debug_state.remote_arch
		rip = self.debug_state.ip

		# Assume the worst, just in case
		read_length = arch_dis.max_instr_length * inst_count
		data = self.debug_state.memory_view.read(start_ip, read_length)

		lines = []

		# Append header line
		tokens = [InstructionTextToken(InstructionTextTokenType.TextToken, "(Code not backed by loaded file, showing only raw disassembly)")]
		contents = DisassemblyTextLine(tokens, start_ip)
		line = LinearDisassemblyLine(LinearDisassemblyLineType.BasicLineType, None, None, contents)
		lines.append(line)

		total_read = 0
		for i in range(inst_count):
			line_addr = start_ip + total_read
			(insn_tokens, length) = arch_dis.get_instruction_text(data[total_read:], line_addr)

			if insn_tokens is None:
				insn_tokens = [InstructionTextToken(InstructionTextTokenType.TextToken, "??")]
				length = arch_dis.instr_alignment
				if length == 0:
					length = 1

			# terrible libshiboken workaround, see #101
			for tok in insn_tokens:
				if tok.value.bit_length() == 64:
					tok.value ^= 0x8000000000000000

			tokens = []
			color = HighlightStandardColor.NoHighlightColor
			if line_addr == rip:
				if self.debug_state.breakpoints.contains_absolute(start_ip + total_read):
					# Breakpoint & pc
					tokens.append(InstructionTextToken(InstructionTextTokenType.TagToken, self.debug_state.ui.get_breakpoint_tag_type().icon + ">", width=5))
					color = HighlightStandardColor.RedHighlightColor
				else:
					# PC
					tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, " ==> "))
					color = HighlightStandardColor.BlueHighlightColor
			else:
				if self.debug_state.breakpoints.contains_absolute(start_ip + total_read):
					# Breakpoint
					tokens.append(InstructionTextToken(InstructionTextTokenType.TagToken, self.debug_state.ui.get_breakpoint_tag_type().icon, width=5))
					color = HighlightStandardColor.RedHighlightColor
				else:
					# Regular line
					tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, "     "))
			# Address
			tokens.append(InstructionTextToken(InstructionTextTokenType.AddressDisplayToken, hex(line_addr)[2:], line_addr))
			tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, "  "))
			tokens.extend(insn_tokens)

			# Convert to linear disassembly line
			contents = DisassemblyTextLine(tokens, line_addr, color=color)
			line = LinearDisassemblyLine(LinearDisassemblyLineType.CodeDisassemblyLineType, None, None, contents)
			lines.append(line)

			total_read += length

		self.binary_text.setLines(lines)

	def show_raw_disassembly(self, raw):
		if raw != self.is_raw_disassembly:
			self.splitter.replaceWidget(0, self.disasm_widget if raw else self.bv_widget)
			self.is_raw_disassembly = raw

	def refresh_raw_disassembly(self):
		if not self.debug_state.connected:
			# Can't navigate to remote addr when disconnected
			return

		if self.is_raw_disassembly:
			self.load_raw_disassembly(self.getCurrentOffset())