Repository: napari/napari

Issue:
Consistent API for layer.data.events in setter.data
## 🧰 Task
With #6117 the `events.data` emitter is fixed. However, the API for this emitter in `setter.data` in `Points` is now different from `setter.data` in `Shapes`. The API should be made consistent.



Discussion:

Resolving patch:
diff --git a/napari/layers/shapes/shapes.py b/napari/layers/shapes/shapes.py
--- a/napari/layers/shapes/shapes.py
+++ b/napari/layers/shapes/shapes.py
@@ -673,17 +673,23 @@ def data(self, data):
         self._data_view.slice_key = np.array(self._slice_indices)[
             self._slice_input.not_displayed
         ]
-        self.add(
+        self._add_shapes(
             data,
             shape_type=shape_type,
             edge_width=edge_widths,
             edge_color=edge_color,
             face_color=face_color,
             z_index=z_indices,
+            n_new_shapes=n_new_shapes,
         )
 
         self._update_dims()
-        self.events.data(value=self.data)
+        self.events.data(
+            value=self.data,
+            action=ActionType.CHANGE.value,
+            data_indices=slice(None),
+            vertex_indices=((),),
+        )
         self._reset_editable()
 
     def _on_selection(self, selected: bool):
@@ -1979,28 +1985,9 @@ def add(
         """
         data, shape_type = extract_shape_type(data, shape_type)
 
-        if edge_width is None:
-            edge_width = self.current_edge_width
-
         n_new_shapes = number_of_shapes(data)
 
-        if edge_color is None:
-            edge_color = self._get_new_shape_color(
-                n_new_shapes, attribute='edge'
-            )
-        if face_color is None:
-            face_color = self._get_new_shape_color(
-                n_new_shapes, attribute='face'
-            )
-        if self._data_view is not None:
-            z_index = z_index or max(self._data_view._z_index, default=-1) + 1
-        else:
-            z_index = z_index or 0
-
         if n_new_shapes > 0:
-            total_shapes = n_new_shapes + self.nshapes
-            self._feature_table.resize(total_shapes)
-            self.text.apply(self.features)
             self._add_shapes(
                 data,
                 shape_type=shape_type,
@@ -2008,6 +1995,7 @@ def add(
                 edge_color=edge_color,
                 face_color=face_color,
                 z_index=z_index,
+                n_new_shapes=n_new_shapes,
             )
             self.events.data(
                 value=self.data,
@@ -2105,7 +2093,7 @@ def _init_shapes(
                 edge_color=edge_color,
                 face_color=face_color,
                 z_index=z_index,
-                z_refresh=False,
+                n_new_shapes=n_shapes,
             )
             self._data_view._update_z_order()
             self.refresh_colors()
@@ -2119,7 +2107,7 @@ def _add_shapes(
         edge_color=None,
         face_color=None,
         z_index=None,
-        z_refresh=True,
+        n_new_shapes=0,
     ):
         """Add shapes to the data view.
 
@@ -2160,13 +2148,24 @@ def _add_shapes(
             same length as the length of `data` and each element will be
             applied to each shape otherwise the same value will be used for all
             shapes.
-        z_refresh : bool
-            If set to true, the mesh elements are reindexed with the new z order.
-            When shape_index is provided, z_refresh will be overwritten to false,
-            as the z indices will not change.
-            When adding a batch of shapes, set to false  and then call
-            ShapesList._update_z_order() once at the end.
+        n_new_shapes: int
+            The number of new shapes to be added to the Shapes layer.
         """
+        if n_new_shapes > 0:
+            total_shapes = n_new_shapes + self.nshapes
+            self._feature_table.resize(total_shapes)
+            if hasattr(self, "text"):
+                self.text.apply(self.features)
+
+        if edge_color is None:
+            edge_color = self._get_new_shape_color(
+                n_new_shapes, attribute='edge'
+            )
+        if face_color is None:
+            face_color = self._get_new_shape_color(
+                n_new_shapes, attribute='face'
+            )
+
         if edge_width is None:
             edge_width = self.current_edge_width
         if edge_color is None:


Test patch:
diff --git a/napari/layers/shapes/_tests/test_shapes.py b/napari/layers/shapes/_tests/test_shapes.py
--- a/napari/layers/shapes/_tests/test_shapes.py
+++ b/napari/layers/shapes/_tests/test_shapes.py
@@ -2239,3 +2239,24 @@ def test_editing_4d():
     viewer.layers['rois'].data = [
         np.around(x) for x in viewer.layers['rois'].data
     ]
+
+
+def test_points_data_setter_emits_event():
+    data = np.random.random((4, 2))
+    emitted_events = Mock()
+    layer = Shapes(data)
+    layer.events.data.connect(emitted_events)
+    layer.data = np.random.random((4, 2))
+    emitted_events.assert_called_once()
+
+
+def test_points_add_delete_only_emit_one_event():
+    data = np.random.random((4, 2))
+    emitted_events = Mock()
+    layer = Shapes(data)
+    layer.events.data.connect(emitted_events)
+    layer.add(np.random.random((4, 2)))
+    assert emitted_events.call_count == 1
+    layer.selected_data = {1}
+    layer.remove_selected()
+    assert emitted_events.call_count == 2