Repository: hanjinliu/magic-class (language: python)

Issue:
Trouble creating magic-class with two functions, each decorated with @magicgui
We are updating some older magic-class code we used to control a microscope with napari. In the process, we discovered that our GUI is no longer being rendered correctly in napari. That led to some testing and questions on my part.

My main question is how do I create two functions in a magic-class, each decorator with magicgui options?

For example, I would expect this code,

```python
from magicclass import magicclass
from magicgui import magicgui

# Test magicgui options      
@magicclass(labels=False,name='Test two magicgui')
class TestTwoMagicgui:

    # initialize
    def __init__(self):
        self.value_1 = 25
        self.value_2 = 400
        self.value_3 = 0

    @magicgui(layout='horizontal',
              value_1={"widget_type": "FloatSpinBox", "min": 5, "max": 225, "label": 'Value 1'},
              call_button = 'Set value 1')
    def set_value_1(self, value_1 = 25.0):
        """
        Set value 1

        :param value_1: float

        :return None:
        """

        self.value_1=value_1

    @magicgui(layout='horizontal',
              value_2 = {"widget_type": "SpinBox",  "min": 0, "max": 100, "label": 'Value 2'},
              value_3 = {"widget_type": "FloatSpinBox", "min": 0.0, "max": 200.0, "label": 'Value 3'},
              call_button = "Set values 2 and 3"
             )
    def set_values_2_3(self, value_2= 50, value_3 = 10.0):
        """
        Set values 2 and 3

        :param value_2: int
        :param value_3: float

        :return None:
        """

        self.value_2 = value_2
        self.value_3 = value_3

if __name__ == "__main__":
    ui = TestTwoMagicgui()
    ui.show()
```

To show a GUI with two widgets. The first widget should have one entry and the second widget have two entries. Instead, I get a blank GUI that I have to expand to see the title of the window (screenshot attached).

![image](https://user-images.githubusercontent.com/23435837/198751425-9557cde0-02d6-47ca-a03b-3468817038db.png)

Do you have any suggestions about how to properly setup the above magic-class?

Thanks!


Discussion / hints:

Fix patch:
diff --git a/magicclass/_gui/_base.py b/magicclass/_gui/_base.py
index 6734fc59..5f0cfd3d 100644
--- a/magicclass/_gui/_base.py
+++ b/magicclass/_gui/_base.py
@@ -546,6 +546,7 @@ def _unwrap_method(
                     else:
                         del child_instance[index - 1]
                         child_instance._fast_insert(index, widget)
+                    widget.visible = True
 
                 else:
                     widget.visible = copy
diff --git a/magicclass/_gui/class_gui.py b/magicclass/_gui/class_gui.py
index e920a591..608d8d62 100644
--- a/magicclass/_gui/class_gui.py
+++ b/magicclass/_gui/class_gui.py
@@ -262,6 +262,10 @@ def _convert_attributes_into_widgets(self):
                         self._fast_insert(n_insert, widget)
                         n_insert += 1
 
+                        # NOTE: Function GUI is invisible by some reason...
+                        # See https://github.com/hanjinliu/magic-class/issues/53
+                        widget.visible = True
+
                     _hist.append((name, str(type(attr)), type(widget).__name__))
 
             except Exception as e:
@@ -338,6 +342,11 @@ def _fast_insert(self, key: int, obj: Widget | Callable) -> None:
 
 
 def find_window_ancestor(widget: Widget) -> SubWindowsClassGui:
+    """
+    Try to find a window ancestor of the given widget.
+
+    This function is used only for subwindows.
+    """
     parent_self = widget
     while (parent := getattr(parent_self, "__magicclass_parent__", None)) is not None:
         parent_self = parent
diff --git a/magicclass/_gui/menu_gui.py b/magicclass/_gui/menu_gui.py
index eb241124..6b44420c 100644
--- a/magicclass/_gui/menu_gui.py
+++ b/magicclass/_gui/menu_gui.py
@@ -98,7 +98,8 @@ def _convert_attributes_into_widgets(self):
                     widget = getattr(self, name, None)
 
                 if isinstance(widget, FunctionGui):
-                    widget[0].bind(self)  # set self to the first argument
+                    widget = attr.copy()
+                    widget[0].bind(self)  # bind self to the first argument
 
                 elif isinstance(widget, BaseGui):
                     connect_magicclasses(self, widget, name)
diff --git a/magicclass/_gui/toolbar.py b/magicclass/_gui/toolbar.py
index e04fad40..12822674 100644
--- a/magicclass/_gui/toolbar.py
+++ b/magicclass/_gui/toolbar.py
@@ -136,7 +136,8 @@ def _convert_attributes_into_widgets(self):
                     widget = getattr(self, name, None)
 
                 if isinstance(widget, FunctionGui):
-                    widget[0].bind(self)  # set self to the first argument
+                    widget = attr.copy()
+                    widget[0].bind(self)  # bind self to the first argument
 
                 elif isinstance(widget, BaseGui):
                     connect_magicclasses(self, widget, name)
diff --git a/magicclass/_gui/utils.py b/magicclass/_gui/utils.py
index c74e11e2..61c14a4b 100644
--- a/magicclass/_gui/utils.py
+++ b/magicclass/_gui/utils.py
@@ -8,9 +8,13 @@
 from magicgui.type_map import get_widget_class
 from magicgui.signature import magic_signature, MagicParameter
 
-if _magicgui_version >= "0.6.0":
+MAGICGUI_BEFORE_0_6 = _magicgui_version < "0.6"
+
+if not MAGICGUI_BEFORE_0_6:
     from functools import partial
 
+    # since magicgui 0.6, raise_on_unknown is newly implemented, which caused
+    # inconsistency. See https://github.com/napari/magicgui/pull/476
     get_widget_class = partial(get_widget_class, raise_on_unknown=False)
 
 from macrokit import Symbol


Test patch:
diff --git a/tests/test_magicgui.py b/tests/test_magicgui.py
index b71a1ad9..13b16858 100644
--- a/tests/test_magicgui.py
+++ b/tests/test_magicgui.py
@@ -9,9 +9,11 @@ def f(self, a: int, b: str = "x"):
             self.a = a
 
     ui = A()
+    ui.show(run=False)
     assert type(ui[0]) is widgets.FunctionGui
     assert ui[0].a.value == 0
     assert ui[0].b.value == "x"
+    assert ui[0].visible
 
     # macro should be recorded after called
     ui[0].a.value = 10
@@ -25,7 +27,7 @@ def f(self, a: int, b: str = "x"):
     assert len(ui.macro) == 3
     assert str(ui.macro[-1]) == "ui.f(a=20, b='x')"
     assert str(ui.macro[-2]) == "ui.f(a=10, b='x')"
-
+    ui.close()
 
 def test_autocall_macro():
     """Auto-called macro should be recorded once."""
@@ -56,10 +58,12 @@ def f2(self, a: int, b: str = "x"):
             self.a = a
 
     ui = A()
+    ui.show(run=False)
     assert len(ui.B) == 3
     assert type(ui.B[1]) is widgets.FunctionGui
     assert ui.B[1].a.value == 0
     assert ui.B[1].b.value == "x"
+    assert ui.B[1].visible
 
     # macro should be recorded after called
     ui.B[1].a.value = 10
@@ -73,23 +77,33 @@ def f2(self, a: int, b: str = "x"):
     assert len(ui.macro) == 3
     assert str(ui.macro[-1]) == "ui.f2(a=20, b='x')"
     assert str(ui.macro[-2]) == "ui.f2(a=10, b='x')"
+    ui.close()
 
-# NOTE: need fix on magicgui side
-# def test_multi_gui():
-#     @magicclass
-#     class A:
-#         @magicgui
-#         def f(self, x: int):
-#             return id(self)
+def test_multi_gui():
+    @magicclass
+    class A:
+        @magicgui
+        def f(self, x: int):
+            return id(self)
 
-#         @magicgui
-#         def g(self, x: float):
-#             return -id(self)
+        @magicgui
+        def g(self, x: float):
+            return -id(self)
 
-#     ui0 = A()
-#     ui1 = A()
-#     assert ui0["f"] is not ui1["f"]
-#     assert ui0["g"] is not ui1["g"]
-#     assert ui0["f"](0) == -ui0["g"](0)
-#     assert ui1["f"](0) == -ui1["g"](0)
-#     assert ui0["f"](0) != ui1["f"](0)
+    ui0 = A()
+    ui1 = A()
+    ui0.show(run=False)
+    ui1.show(run=False)
+    assert ui0["f"] is not ui1["f"]
+    assert ui0["g"] is not ui1["g"]
+    assert ui0["f"] is not ui0["g"]
+    assert ui0["f"](x=0) == -ui0["g"](x=0)
+    assert ui1["f"](x=0) == -ui1["g"](x=0)
+    assert ui0["f"](x=0) == id(ui0)
+    assert ui1["f"](x=0) == id(ui1)
+    assert ui0["f"].visible
+    assert ui0["g"].visible
+    assert ui1["f"].visible
+    assert ui1["g"].visible
+    ui0.close()
+    ui1.close()