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)
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()
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)
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)