Exemple #1
0
    def on_mouse_click(self, e):
        """Select a cluster by clicking on a spike."""
        if 'Control' in e.modifiers:
            # Get mouse position in NDC.
            box_id, _ = self.canvas.stacked.box_map(e.pos)
            channel_id = np.nonzero(self.channel_y_ranks == box_id)[0]
            # Find the spike and cluster closest to the mouse.
            db = self.data_bounds
            # Get the information about the displayed spikes.
            wt = [(t, s, c, ch) for t, s, c, ch in self._waveform_times if channel_id in ch]
            if not wt:
                return
            # Get the time coordinate of the mouse position.
            mouse_pos = self.canvas.panzoom.window_to_ndc(e.pos)
            mouse_time = Range(NDC, db).apply(mouse_pos)[0][0]
            # Get the closest spike id.
            times, spike_ids, spike_clusters, channel_ids = zip(*wt)
            i = np.argmin(np.abs(np.array(times) - mouse_time))
            # Raise the select_spike event.
            spike_id = spike_ids[i]
            cluster_id = spike_clusters[i]
            emit('select_spike', self, channel_id=channel_id,
                 spike_id=spike_id, cluster_id=cluster_id)

        if 'Shift' in e.modifiers:
            # Get mouse position in NDC.
            box_id, _ = self.canvas.stacked.box_map(e.pos)
            channel_id = int(np.nonzero(self.channel_y_ranks == box_id)[0][0])
            emit('select_channel', self, channel_id=channel_id, button=e.button)
Exemple #2
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.

        """

        # 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)
Exemple #3
0
def test_probe_view(qtbot, gui):

    n = 50
    positions = staggered_positions(n)
    positions = positions.astype(np.int32)
    best_channels = lambda cluster_id: range(1, 9, 2)

    v = ProbeView(positions=positions, best_channels=best_channels, dead_channels=(3, 7, 12))
    v.do_show_labels = False
    v.show()
    qtbot.waitForWindowShown(v.canvas)
    v.attach(gui)

    class Supervisor(object):
        pass

    v.toggle_show_labels(True)
    v.on_select(cluster_ids=[])
    v.on_select(cluster_ids=[0])
    v.on_select(cluster_ids=[0, 2, 3])
    emit('select', Supervisor(), cluster_ids=[0, 2])

    v.toggle_show_labels(False)

    _stop_and_close(qtbot, v)
Exemple #4
0
 def on_mouse_click(self, e):
     """Add a polygon point with ctrl+click."""
     if 'Control' in e.modifiers:
         if e.button == 'Left':
             layout = getattr(self.canvas, 'layout', None)
             if hasattr(layout, 'box_map'):
                 box, pos = layout.box_map(e.pos)
                 # Only update the box for the first click, so that the box containing
                 # the lasso is determined by the first click only.
                 if self.box is None:
                     self.box = box
                 # Avoid clicks outside the active box (box of the first click).
                 if box != self.box:
                     return
             else:  # pragma: no cover
                 pos = self.canvas.window_to_ndc(e.pos)
             # Force the active box to be the box of the first click, not the box of the
             # current click.
             if layout:
                 layout.active_box = self.box
             self.add(pos)  # call update_lasso_visual
             emit("lasso_updated", self.canvas, self.polygon)
         else:
             self.clear()
             self.box = None
Exemple #5
0
 def on_mouse_click(self, e):
     """Select a feature dimension by clicking on a box in the feature view."""
     b = e.button
     if 'Alt' in e.modifiers:
         # Get mouse position in NDC.
         (i, j), _ = self.canvas.grid.box_map(e.pos)
         dim = self.grid_dim[i][j]
         dim_x, dim_y = dim.split(',')
         dim = dim_x if b == 'Left' else dim_y
         other_dim = dim_y if b == 'Left' else dim_x
         if dim not in self.attributes:
             # When a regular (channel, PC) dimension is selected.
             channel_pc = self._get_channel_and_pc(dim)
             if channel_pc is None:
                 return
             channel_id, pc = channel_pc
             logger.debug("Click on feature dim %s, channel id %s, PC %s.", dim, channel_id, pc)
         else:
             # When the selected dimension is an attribute, e.g. "time".
             pc = None
             # Take the channel id in the other dimension.
             channel_pc = self._get_channel_and_pc(other_dim)
             channel_id = channel_pc[0] if channel_pc is not None else None
             logger.debug("Click on feature dim %s.", dim)
         emit('select_feature', self, dim=dim, channel_id=channel_id, pc=pc)
Exemple #6
0
 def _clusters_selected(self, sender, obj, **kwargs):
     """When clusters are selected in the cluster view, register the action in the history
     stack, update the similarity view, and emit the global supervisor.select event unless
     update_views is False."""
     if sender != self.cluster_view:
         return
     cluster_ids = obj['selected']
     next_cluster = obj['next']
     kwargs = obj.get('kwargs', {})
     logger.debug("Clusters selected: %s (%s)", cluster_ids, next_cluster)
     self.task_logger.log(self.cluster_view,
                          'select',
                          cluster_ids,
                          output=obj)
     # Update the similarity view when the cluster view selection changes.
     self.similarity_view.reset(cluster_ids)
     self.similarity_view.set_selected_index_offset(
         len(self.selected_clusters))
     # Emit supervisor.select event unless update_views is False. This happens after
     # a merge event, where the views should not be updated after the first cluster_view.select
     # event, but instead after the second similarity_view.select event.
     if kwargs.pop('update_views', True):
         emit('select', self, self.selected, **kwargs)
     if cluster_ids:
         self.cluster_view.scroll_to(cluster_ids[-1])
     self.cluster_view.dock.set_status('clusters: %s' %
                                       ', '.join(map(str, cluster_ids)))
Exemple #7
0
 def test_trace_view(self):
     self.trace_view.actions.go_to_next_spike()
     self.trace_view.actions.go_to_previous_spike()
     self.trace_view.actions.toggle_highlighted_spikes(True)
     mouse_click(self.qtbot, self.trace_view.canvas, (100, 100), modifiers=('Control',))
     mouse_click(self.qtbot, self.trace_view.canvas, (150, 100), modifiers=('Shift',))
     emit('select_time', self, 0)
     self.trace_view.actions.next_color_scheme()
Exemple #8
0
 def on_cluster(sender, up):
     # NOTE: update the cluster meta of new clusters, depending on the values of the
     # ancestor clusters. In case of a conflict between the values of the old clusters,
     # the largest cluster wins and its value is set to its descendants.
     if up.added:
         self.cluster_meta.set_from_descendants(
             up.descendants, largest_old_cluster=up.largest_old_cluster)
     emit('cluster', self, up)
Exemple #9
0
 def on_lasso_updated(sender, polygon):
     if len(polygon) < 3:
         return
     pos = range_transform([self.data_bounds], [NDC],
                           self.marker_positions)
     ind = self.canvas.lasso.in_polygon(pos)
     cluster_ids = self.all_cluster_ids[ind]
     emit("request_select", self, list(cluster_ids))
Exemple #10
0
    def _init_table(self, columns=None, value_names=None, data=None, sort=None):
        """Build the table."""

        columns = columns or ['id']
        value_names = value_names or columns
        data = data or []

        b = self.builder
        b.set_body_src('index.html')

        if is_high_dpi():  # pragma: no cover
            b.add_style('''

                /* This is for high-dpi displays. */
                body {
                    transform: scale(2);
                    transform-origin: 0 0;
                    overflow-y: scroll;
                    /*overflow-x: hidden;*/
                }

                input.filter {
                    width: 50% !important;
                }

            ''')

        b.add_style(_color_styles())

        self.data = data
        self.columns = columns
        self.value_names = value_names

        emit('pre_build', self)

        data_json = dumps(self.data)
        columns_json = dumps(self.columns)
        value_names_json = dumps(self.value_names)
        sort_json = dumps(sort)

        b.body += '''
        <script>
            var data = %s;

            var options = {
              valueNames: %s,
              columns: %s,
              sort: %s,
            };

            var table = new Table('table', options, data);

        </script>
        ''' % (data_json, value_names_json, columns_json, sort_json)
        self.build(lambda html: emit('ready', self))

        connect(event='select', sender=self, func=lambda *args: self.update(), last=True)
        connect(event='ready', sender=self, func=lambda *args: self._set_ready())
Exemple #11
0
    def add_visual(self, visual, **kwargs):
        """Add a visual to the canvas and build its OpenGL program using the attached interacts.

        We can't build the visual's program before, because we need the canvas' transforms first.

        Parameters
        ----------

        visual : Visual
        clearable : True
            Whether the visual should be deleted when calling `canvas.clear()`.
        exclude_origins : list-like
            List of interact instances that should not apply to that visual. For example, use to
            add a visual outside of the subplots, or with no support for pan and zoom.
        key : str
            An optional key to identify a visual

        """
        if self.has_visual(visual):
            logger.log(5, "This visual has already been added.")
            return
        visual.canvas = self
        # This is the list of origins (mostly, interacts and layouts) that should be ignored
        # when adding this visual. For example, an AxesVisual would keep the PanZoom interact,
        # but not the Grid layout.
        exclude_origins = kwargs.pop('exclude_origins', ())

        # Retrieve the visual's GLSL inserter.
        v_inserter = visual.inserter

        # Add the canvas' GPU transforms.
        v_inserter.add_gpu_transforms(self.gpu_transforms)
        # Also, add the canvas' inserter. The snippets that should be ignored will be excluded
        # in insert_into_shaders() below.
        v_inserter += self.inserter

        # Now, we insert the transforms GLSL into the shaders.
        vs, fs = visual.vertex_shader, visual.fragment_shader
        vs, fs = v_inserter.insert_into_shaders(
            vs, fs, exclude_origins=exclude_origins)

        # Geometry shader, if there is one.
        gs = getattr(visual, 'geometry_shader', None)
        if gs:
            gs = gloo.GeometryShader(gs, visual.geometry_count,
                                     visual.geometry_in, visual.geometry_out)

        # Finally, we create the visual's program.
        visual.program = LazyProgram(vs, fs, gs)
        logger.log(5, "Vertex shader: %s", vs)
        logger.log(5, "Fragment shader: %s", fs)

        # Initialize the size.
        visual.on_resize(self.size().width(), self.size().height())
        # Register the visual in the list of visuals in the canvas.
        self.visuals.append(Bunch(visual=visual, **kwargs))
        emit('visual_added', self, visual)
        return visual
Exemple #12
0
 def on_mouse_click(self, e):
     """Select a cluster by clicking in the raster plot."""
     b = e.button
     if 'Control' in e.modifiers:
         # Get mouse position in NDC.
         cluster_idx, _ = self.canvas.stacked.box_map(e.pos)
         cluster_id = self.all_cluster_ids[cluster_idx]
         logger.debug("Click on cluster %d with button %s.", cluster_id, b)
         emit('cluster_click', self, cluster_id, button=b)
Exemple #13
0
 def emitJS(self, name, arg_json):
     logger.log(5, "Emit from Python %s %s.", name, arg_json)
     args = str(name), self._parent, json.loads(str(arg_json))
     # NOTE: debounce some events but not other events coming from JS.
     # This is typically used for select events of table widgets.
     if name in self._debounce_events:
         self._debouncer.submit(emit, *args)
     else:
         emit(*args)
Exemple #14
0
 def on_mouse_click(self, e):
     """Select a cluster by clicking on its template waveform."""
     b = e.button
     if 'Control' in e.modifiers or 'Shift' in e.modifiers:
         # Get mouse position in NDC.
         (channel_idx, cluster_rel), _ = self.canvas.grid.box_map(e.pos)
         cluster_id = self.all_cluster_ids[cluster_rel]
         logger.debug("Click on cluster %d with button %s.", cluster_id, b)
         emit('cluster_click', self, cluster_id, button=b, modifiers=e.modifiers)
Exemple #15
0
    def attach(self, gui):
        """Attach to the GUI."""

        # Make sure the selected field in cluster and similarity views are saved in the local
        # supervisor state, as this information is dataset-dependent.
        gui.state.add_local_keys(['ClusterView.selected'])

        # Create the cluster view and similarity view.
        self._create_views(gui=gui,
                           sort=gui.state.get('ClusterView',
                                              {}).get('current_sort', None))

        # Create the TaskLogger.
        self.task_logger = TaskLogger(
            cluster_view=self.cluster_view,
            similarity_view=self.similarity_view,
            supervisor=self,
        )

        connect(self._save_gui_state, event='close', sender=gui)
        gui.add_view(self.cluster_view, position='left', closable=False)
        gui.add_view(self.similarity_view, position='left', closable=False)

        # Create all supervisor actions (edit and view menu).
        self.action_creator.attach(gui)
        self.actions = self.action_creator.edit_actions  # clustering actions
        self.select_actions = self.action_creator.select_actions
        self.view_actions = gui.view_actions
        emit('attach_gui', self)

        # Call supervisor.save() when the save/ctrl+s action is triggered in the GUI.
        @connect(sender=gui)
        def on_request_save(sender):
            self.save()

        # Set the debouncer.
        self._busy = {}
        self._is_busy = False
        # Collect all busy events from the views, and sets the GUI as busy
        # if at least one view is busy.

        @connect
        def on_is_busy(sender, is_busy):
            self._busy[sender] = is_busy
            self._set_busy(any(self._busy.values()))

        @connect(sender=gui)
        def on_close(e):
            unconnect(on_is_busy, self)

        @connect(sender=self.cluster_view)
        def on_ready(sender):
            """Select the clusters from the cluster view state."""
            selected = gui.state.get('ClusterView', {}).get('selected', [])
            if selected:  # pragma: no cover
                self.cluster_view.select(selected)
Exemple #16
0
    def pan(self, value):
        """Pan translation."""
        assert len(value) == 2
        old = tuple(self.pan)
        self._pan[:] = value
        self._constrain_pan()

        new = tuple(self.pan)
        if new != old:
            emit('pan', self, new)
        self.update()
Exemple #17
0
    def add_view(self,
                 view,
                 position=None,
                 closable=True,
                 floatable=True,
                 floating=None):
        """Add a dock widget to the main window.

        Parameters
        ----------

        view : View
        position : str
            Relative position where to add the view (left, right, top, bottom).
        closable : boolean
            Whether the view can be closed by the user.
        floatable : boolean
            Whether the view can be detached from the main GUI.
        floating : boolean
            Whether the view should be added in floating mode or not.

        """

        logger.debug("Add view %s to GUI.", view.__class__.__name__)

        name = self._set_view_name(view)
        self._views.append(view)
        self._view_class_indices[view.__class__] += 1

        # Get the Qt canvas for matplotlib/OpenGL views.
        widget = _try_get_matplotlib_canvas(view)
        widget = _try_get_opengl_canvas(widget)

        dock_widget = _create_dock_widget(widget,
                                          name,
                                          closable=closable,
                                          floatable=floatable)
        self.addDockWidget(_get_dock_position(position), dock_widget,
                           Qt.Horizontal)
        if floating is not None:
            dock_widget.setFloating(floating)
        dock_widget.view = view
        view.dock_widget = dock_widget

        # Emit the close_view event when the dock widget is closed.
        @connect(sender=dock_widget)
        def on_close_dock_widget(sender):
            self._views.remove(view)
            emit('close_view', self, view)

        dock_widget.show()
        emit('add_view', self, view)
        logger.log(5, "Add %s to GUI.", name)
        return dock_widget
Exemple #18
0
 def on_mouse_click(self, e):
     """Select a cluster by clicking in the raster plot."""
     if 'Control' not in e.modifiers:
         return
     b = e.button
     # Get mouse position in NDC.
     cluster_idx, _ = self.canvas.stacked.box_map(e.pos)
     cluster_id = self.all_cluster_ids[cluster_idx]
     logger.debug("Click on cluster %d with button %s.", cluster_id, b)
     if 'Shift' in e.modifiers:
         emit('select_more', self, [cluster_id])
     else:
         emit('request_select', self, [cluster_id])
Exemple #19
0
 def on_mouse_click(self, e):
     """Select a cluster by clicking on its template waveform."""
     if 'Control' not in e.modifiers:
         return
     b = e.button
     # Get mouse position in NDC.
     (channel_idx, cluster_rel), _ = self.canvas.grid.box_map(e.pos)
     cluster_id = self.all_cluster_ids[cluster_rel]
     logger.debug("Click on cluster %d with button %s.", cluster_id, b)
     if 'Shift' in e.modifiers:
         emit('select_more', self, [cluster_id])
     else:
         emit('request_select', self, [cluster_id])
Exemple #20
0
 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
Exemple #21
0
 def _similar_selected(self, sender, obj):
     """When clusters are selected in the similarity view, register the action in the history
     stack, and emit the global supervisor.select event."""
     if sender != self.similarity_view:
         return
     similar = obj['selected']
     next_similar = obj['next']
     kwargs = obj.get('kwargs', {})
     logger.debug("Similar clusters selected: %s (%s)", similar, next_similar)
     self.task_logger.log(self.similarity_view, 'select', similar, output=obj)
     emit('select', self, self.selected, **kwargs)
     if similar:
         self.similarity_view.scroll_to(similar[-1])
Exemple #22
0
 def on_mouse_click(self, e):
     """Select a cluster by clicking on its template waveform."""
     if 'Control' in e.modifiers:
         return
     b = e.button
     pos = self.canvas.window_to_ndc(e.pos)
     marker_pos = range_transform([self.data_bounds], [NDC],
                                  self.marker_positions)
     cluster_rel = np.argmin(((marker_pos - pos)**2).sum(axis=1))
     cluster_id = self.all_cluster_ids[cluster_rel]
     logger.debug("Click on cluster %d with button %s.", cluster_id, b)
     if 'Shift' in e.modifiers:
         emit('select_more', self, [cluster_id])
     else:
         emit('request_select', self, [cluster_id])
Exemple #23
0
 def on_mouse_click(self, e):
     """Select a channel by clicking on a box in the waveform view."""
     b = e.button
     nums = tuple('%d' % i for i in range(10))
     if 'Control' in e.modifiers or e.key in nums:
         key = int(e.key) if e.key in nums else None
         # Get mouse position in NDC.
         channel_idx, _ = self.canvas.boxed.box_map(e.pos)
         channel_id = self.channel_ids[channel_idx]
         logger.debug("Click on channel_id %d with key %s and button %s.",
                      channel_id, key, b)
         emit('select_channel',
              self,
              channel_id=channel_id,
              key=key,
              button=b)
Exemple #24
0
def test_manual_clustering_view_2(qtbot, gui):
    v = MyView()
    v.canvas.show()
    # qtbot.addWidget(v.canvas)
    v.attach(gui)

    class Supervisor(object):
        pass

    emit('select', Supervisor(), cluster_ids=[0, 1])

    qtbot.wait(200)
    # qtbot.stop()
    v.canvas.close()
    v.dock_widget.close()
    qtbot.wait(100)
Exemple #25
0
    def zoom(self, value):
        """Zoom level."""
        if isinstance(value, (int, float)):
            value = (value, value)
        assert len(value) == 2
        old = tuple(self.zoom)
        self._zoom = np.clip(value, self._zmin, self._zmax)

        # Constrain bounding box.
        self._constrain_pan()
        self._constrain_zoom()

        new = tuple(self.zoom)
        if new != old:
            emit('zoom', self, new)
        self.update()
Exemple #26
0
    def set(self, field, clusters, value, add_to_stack=True):
        """Set the value of one of several clusters.

        Parameters
        ----------

        field : str
            The field to set.
        clusters : list
            The list of cluster ids to change.
        value : str
            The new metadata value for the given clusters.
        add_to_stack : boolean
            Whether this metadata change should be recorded in the undo stack.

        Returns
        -------

        up : UpdateInfo instance

        """
        # Add the field if it doesn't exist.
        if field not in self._fields:
            self.add_field(field)
        assert field in self._fields

        clusters = _as_list(clusters)
        for cluster in clusters:
            if cluster not in self._data:
                self._data[cluster] = {}
            self._data[cluster][field] = value

        up = UpdateInfo(
            description='metadata_' + field,
            metadata_changed=clusters,
            metadata_value=value,
        )
        undo_state = emit('request_undo_state', self, up)

        if add_to_stack:
            self._undo_stack.add((clusters, field, value, up, undo_state))
            emit('cluster', self, up)

        return up
Exemple #27
0
    def save(self):
        """Save the manual clustering back to disk.

        This method emits the `save_clustering(spike_clusters, groups, *labels)` event.
        It is up to the caller to react to this event and save the data to disk.

        """
        spike_clusters = self.clustering.spike_clusters
        groups = {
            c: self.cluster_meta.get('group', c) or 'unsorted'
            for c in self.clustering.cluster_ids}
        # List of tuples (field_name, dictionary).
        labels = [
            (field, self.get_labels(field)) for field in self.cluster_meta.fields
            if field not in ('next_cluster')]
        emit('save_clustering', self, spike_clusters, groups, *labels)
        # Cache the spikes_per_cluster array.
        self._save_spikes_per_cluster()
        self._is_dirty = False
Exemple #28
0
    def _reset_table(self, data=None, columns=(), sort=None):
        """Recreate the table with specified columns, data, and sort."""
        emit(self._view_name + '_init', self)
        # Ensure 'id' is the first column.
        if 'id' in columns:
            columns.remove('id')
        columns = ['id'] + list(columns)
        # Add required columns if needed.
        for col in self._required_columns:
            if col not in columns:
                columns += [col]
            assert col in columns
        assert columns[0] == 'id'

        # Allow to have <tr data_group="good"> etc. which allows for CSS styling.
        value_names = columns + [{'data': ['group']}]
        # Default sort.
        sort = sort or ('n_spikes', 'desc')
        self._init_table(columns=columns, value_names=value_names, data=data, sort=sort)
Exemple #29
0
def test_gui_dock_widget_1(qtbot, gui):
    gui.show()

    v = _create_canvas()
    gui.add_view(v)

    def callback(checked):
        pass

    # Add 2 buttons.
    v.dock.add_button(name='b1', text='hello world', callback=callback)

    @v.dock.add_button(name='b2',
                       checkable=True,
                       checked=True,
                       icon='f15c',
                       event='button_clicked')
    def callback_1(checked):
        pass

    # Add a checkbox.
    @v.dock.add_checkbox(name='c1', text='checkbox', checked=True)
    def callback_2(checked):
        pass

    # Make sure the second button reacts to events.
    b2 = v.dock.get_widget('b2')
    assert b2.isChecked()
    emit('button_clicked', v, False)
    assert not b2.isChecked()

    # Set and check the title bar status text.
    v.dock.set_status("this is a status")
    assert v.dock.status == 'this is a status'

    # Set and check the title bar status text.
    v.dock.set_status("---very long---" + "------" * 10)
    assert len(v.dock.status) <= v.dock.max_status_length + 5

    b2.click()
    v.dock.get_widget('b1').click()
    v.dock.get_widget('c1').click()
Exemple #30
0
    def redo(self):
        """Redo the next metadata change.

        Returns
        -------

        up : UpdateInfo instance

        """
        args = self._undo_stack.forward()
        if args is None:
            return
        clusters, field, value, up, undo_state = args
        self.set(field, clusters, value, add_to_stack=False)

        # Return the UpdateInfo instance of the redo action.
        up.history = 'redo'

        emit('cluster', self, up)
        return up