Repository: napari/napari

Issue:
Fix circular referencing
## ❓ Questions and Help

Right now we have a lot of circular referencing of our main objects, i.e. you can type `viewer.control_bars.viewer` or `viewer.layers.viewer`. This was raised at our last meeting and was seen as undesirable as right now you can basically get anywhere from anywhere and read / write as you wish. Unfortunately it was not clear how to fix it. Note that this question doesn't directly connect to any of the qt / UI / non-UI stuff we were talking about, but is really just about how different parts of our "model" or "state" reference each other.

I think we'd like to move to a more tree / DAG like structure, but a problem then becomes - how do you share information across different parts of the graph. Maybe our struggles indicate we have not found the right abstractions and modularity.

As a concrete example I'd like to focus on the `ControlBars` class (but similar arguments would apply to `Dimensions` and `LayersList` too). On construction `viewer` gets passed to the object and appended as a property. When the clims slider values change we go inside `viewer` and inside `layers` to find the layer whose clim values need updating. Similarly when the layer selection changes we go inside `viewer` and inside `layers` to find the layer whose clim values now need to be sent to the slider. Notice how the communication is bidirectional.

I could imagine a world where we passed the `viewer` to `ControlBars` on construction, connected up some events but then did not append it as a property. This might make it more clear how different parts of our model interact with each other -  it's just that I can't really see how to make this work (without say also requiring that the LayersList object digs into the `ControlBars` object too). It could mean that we need to pull more of this functionality that involved both the `LayersList` and the `ControlBars` back into the viewer which can see both - but then maybe some of the modularity and motivation for having the separation is lost too.

Curious about any thoughts?
@kne42, @AhmetCanSolak, @jni, @royerloic 


Discussion:
I feel like for read events it's ok - just have the `viewer` pass the `ControlBars` the information it needs when it needs it (could be all of the `LayersList` could just be some computed values).

The trickier bits for me to imagine are the write events - i.e. something is happening inside `ControlBars` and the `viewer` or `LayersList` needs to be updated. I see two potential solutions - either these objects are there as properties which can be directly written on, or have update methods directly called - OR - the `ControlBars` emits update events which the `viewer` and `LayersList` now need to listen too. Is this second option what we want? Will it make things cleaner / more modular or just more obfuscated and confusing?

Resolving patch:
diff --git a/napari/components/_layers/model.py b/napari/components/_layers/model.py
--- a/napari/components/_layers/model.py
+++ b/napari/components/_layers/model.py
@@ -20,7 +20,7 @@ def _remove(event):
     layers = event.source
     layer = event.item
     layer._order = 0
-    layer.viewer = None
+    layer._parent = None
 
 
 def _reorder(event):
@@ -45,11 +45,6 @@ def __init__(self):
         super().__init__(basetype=Layer,
                          lookup={str: lambda q, e: q == e.name})
 
-        # Connect the add events before setting the viewer so that the
-        # addition will cause the first layer dims to update before any
-        # of the layer properties get set. Note that callbacks get called in
-        # the reverse order that they are made in (i.e. last made called first)
-
         self.events.added.connect(_add)
         self.events.removed.connect(_remove)
         self.events.reordered.connect(_reorder)
diff --git a/napari/components/_viewer/model.py b/napari/components/_viewer/model.py
--- a/napari/components/_viewer/model.py
+++ b/napari/components/_viewer/model.py
@@ -284,7 +284,6 @@ def add_layer(self, layer):
 
         self.layers.append(layer)
         layer.indices = self.dims.indices
-        layer.viewer = self
         layer._parent = self._view.scene
 
         if self.theme is not None and has_clims(layer):
diff --git a/napari/layers/_base_layer/model.py b/napari/layers/_base_layer/model.py
--- a/napari/layers/_base_layer/model.py
+++ b/napari/layers/_base_layer/model.py
@@ -39,7 +39,6 @@ class Layer(VisualWrapper, ABC):
     ndim
     shape
     selected
-    viewer
     indices
 
     Methods
@@ -50,7 +49,6 @@ class Layer(VisualWrapper, ABC):
     def __init__(self, central_node, name=None):
         super().__init__(central_node)
         self._selected = False
-        self._viewer = None
         self._qt_properties = None
         self._qt_controls = None
         self._freeze = False
@@ -185,25 +183,6 @@ def selected(self, selected):
         else:
             self.events.deselect()
 
-    @property
-    def viewer(self):
-        """Viewer: Parent viewer widget.
-        """
-        if self._viewer is not None:
-            return self._viewer()
-
-    @viewer.setter
-    def viewer(self, viewer):
-        prev = self.viewer
-        if viewer == prev:
-            return
-
-        if viewer is None:
-            self._viewer = None
-            parent = None
-        else:
-            self._viewer = weakref.ref(viewer)
-
     @property
     def status(self):
         """string: Status string


Test patch: