예제 #1
0
파일: base.py 프로젝트: GrohLab/phy
    def attach(self, gui):
        """Attach the view to the GUI.

        Perform the following:

        - Add the view to the GUI.
        - Update the view's attribute from the GUI state
        - Add the default view actions (auto_update, screenshot)
        - Bind the on_select() method to the select event raised by the supervisor.

        """

        # Add shortcuts only for the first view of any given type.
        shortcuts = self.shortcuts if not gui.list_views(self.__class__) else None

        gui.add_view(self, position=self._default_position)
        self.gui = gui

        # Set the view state.
        self.set_state(gui.state.get_view_state(self))

        self.actions = Actions(
            gui, name=self.name, view=self,
            default_shortcuts=shortcuts, default_snippets=self.default_snippets)

        # Freeze and unfreeze the view when selecting clusters.
        self.actions.add(
            self.toggle_auto_update, checkable=True, checked=self.auto_update, show_shortcut=False)
        self.actions.add(self.screenshot, show_shortcut=False)
        self.actions.add(self.close, show_shortcut=False)
        self.actions.separator()

        on_select = partial(self.on_select_threaded, gui=gui)
        connect(on_select, event='select')

        # Save the view state in the GUI state.
        @connect
        def on_close_view(view_, gui):
            if view_ != self:
                return
            logger.debug("Close view %s.", self.name)
            self._closed = True
            gui.remove_menu(self.name)
            unconnect(on_select)
            gui.state.update_view_state(self, self.state)
            self.canvas.close()
            gc.collect(0)

        @connect(sender=gui)
        def on_close(sender):
            gui.state.update_view_state(self, self.state)

        # HACK: Fix bug on macOS where docked OpenGL widgets were not displayed at startup.
        self._set_floating = AsyncCaller(delay=5)

        @self._set_floating.set
        def _set_floating():
            self.dock.setFloating(False)

        emit('view_attached', self, gui)
예제 #2
0
    def attach(self, gui, name=None):
        """Attach the view to the GUI."""

        # Disable keyboard pan so that we can use arrows as global shortcuts
        # in the GUI.
        self.panzoom.enable_keyboard_pan = False

        gui.add_view(self)
        self.gui = gui

        # Set the view state.
        self.set_state(gui.state.get_view_state(self))

        # Call on_select() asynchronously after a delay, and set a busy
        # cursor.
        self.async_caller = AsyncCaller(delay=self._callback_delay)

        @gui.connect_
        def on_select(cluster_ids, **kwargs):
            # Call this function after a delay unless there is another
            # cluster selection in the meantime.
            @self.async_caller.set
            def update_view():
                with busy_cursor():
                    self.on_select(cluster_ids, **kwargs)

        self.actions = Actions(gui,
                               name=name or self.__class__.__name__,
                               menu=self.__class__.__name__,
                               default_shortcuts=self.shortcuts)

        # Update the GUI status message when the `self.set_status()` method
        # is called, i.e. when the `status` event is raised by the VisPy
        # view.
        @self.connect
        def on_status(e):
            gui.status_message = e.message

        # Save the view state in the GUI state.
        @gui.connect_
        def on_close():
            gui.state.update_view_state(self, self.state)
            # NOTE: create_gui() already saves the state, but the event
            # is registered *before* we add all views.
            gui.state.save()

        self.show()
예제 #3
0
        def on_gui_ready(gui):
            # Called when the GUI is created.
            # We add the matplotlib figure to the GUI.
            gui.add_view(f, name='AmplitudeHistogram')

            # Call on_select() asynchronously after a delay, and set a busy
            # cursor.
            async_caller = AsyncCaller(delay=100)

            @gui.connect_
            def on_select(cluster_ids, **kwargs):
                # Call this function after a delay unless there is another
                # cluster selection in the meantime.
                @async_caller.set
                def update_view():
                    with busy_cursor():
                        _update(cluster_ids)
예제 #4
0
    def attach(self, gui):
        """Attach the view to the GUI.

        Perform the following:

        - Add the view to the GUI.
        - Update the view's attribute from the GUI state
        - Add the default view actions (auto_update, screenshot)
        - Bind the on_select() method to the select event raised by the supervisor.
          This runs on a background thread not to block the GUI thread.

        """

        # Add shortcuts only for the first view of any given type.
        shortcuts = self.shortcuts if not gui.list_views(self.__class__) else None

        gui.add_view(self, position=self._default_position)
        self.gui = gui

        # Set the view state.
        self.set_state(gui.state.get_view_state(self))

        self.actions = Actions(
            gui, name=self.name, menu='&View', submenu=self.name,
            default_shortcuts=shortcuts, default_snippets=self.default_snippets)

        # Freeze and unfreeze the view when selecting clusters.
        self.actions.add(
            self.toggle_auto_update, checkable=True, checked=self.auto_update, show_shortcut=False)
        self.actions.add(self.screenshot, show_shortcut=False)
        self.actions.separator()

        emit('view_actions_created', self)

        @connect
        def on_select(sender, cluster_ids, **kwargs):
            # Decide whether the view should react to the select event or not.
            if not self.auto_update:
                return
            if sender.__class__.__name__ != 'Supervisor':
                return
            assert isinstance(cluster_ids, list)
            if not cluster_ids:
                return

            # The lock is used so that two different background threads do not access the same
            # view simultaneously, which can lead to conflicts, errors in the plotting code,
            # and QTimer thread exceptions that lead to frozen OpenGL views.
            if self._lock:
                return
            self._lock = True

            # The view update occurs in a thread in order not to block the main GUI thread.
            # A complication is that OpenGL updates should only occur in the main GUI thread,
            # whereas the computation of the data buffers to upload to the GPU should happen
            # in a thread. Finally, the select events are throttled (more precisely, debounced)
            # to avoid clogging the GUI when many clusters are successively selected, but this
            # is implemented at the level of the table widget, not here.

            # This function executes in the Qt thread pool.
            def _worker():  # pragma: no cover
                self.on_select(cluster_ids=cluster_ids, **kwargs)

            # We launch this function in the thread pool.
            worker = Worker(_worker)

            # Once the worker has finished in the thread, the finished signal is raised,
            # and the callback function below runs on the main GUI thread.
            # All OpenGL updates triggered in the worker (background thread) where recorded
            # instead of being immediately executed (which would have caused errors because
            # OpenGL updates should not be executed from a background thread).
            # Once these updates have been collected in the right order, we execute all of
            # them here, in the main GUI thread.
            @worker.signals.finished.connect
            def finished():
                # When the task has finished in the thread pool, we recover all program
                # updates of the view, and we execute them on the GPU.
                if isinstance(self.canvas, PlotCanvas):
                    self.canvas.set_lazy(False)
                    # We go through all collected OpenGL updates.
                    for program, name, data in self.canvas.iter_update_queue():
                        # We update data buffers in OpenGL programs.
                        program[name] = data
                # Finally, we update the canvas.
                self.canvas.update()
                emit('is_busy', self, False)
                self._lock = None

            # Start the task on the thread pool, and let the OpenGL canvas know that we're
            # starting to record all OpenGL calls instead of executing them immediately.
            # This is what we call the "lazy" mode.
            emit('is_busy', self, True)
            if getattr(gui, '_enable_threading', True):
                # This is only for OpenGL views.
                self.canvas.set_lazy(True)
                thread_pool().start(worker)
            else:
                # This is for OpenGL views, without threading.
                worker.run()
                self._lock = None

        # Update the GUI status message when the `self.set_status()` method
        # is called, i.e. when the `status` event is raised by the view.
        @connect(sender=self)  # pragma: no cover
        def on_status(sender=None, e=None):
            gui.status_message = e.message

        # Save the view state in the GUI state.
        @connect(sender=gui)
        def on_close_view(sender, view):
            if view != self:
                return
            logger.debug("Close view %s.", self.name)
            gui.remove_menu(self.name)
            unconnect(on_select)
            gui.state.update_view_state(self, self.state)
            self.canvas.close()
            gc.collect(0)

        @connect(sender=gui)
        def on_close(sender):
            gui.state.update_view_state(self, self.state)

        # HACK: Fix bug on macOS where docked OpenGL widgets were not displayed at startup.
        self._set_floating = AsyncCaller(delay=1)
        @self._set_floating.set
        def _set_floating():
            self.dock_widget.setFloating(False)