Skip to content

Commit

Permalink
Improve issues with widget visibility (#130)
Browse files Browse the repository at this point in the history
  • Loading branch information
tlambert03 committed Jan 24, 2021
1 parent 3d1d3b0 commit 7f51aef
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 46 deletions.
16 changes: 8 additions & 8 deletions magicgui/backends/_qtpy/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ def __init__(self, qwidg: QtW.QWidget):
self._event_filter = EventFilter()
self._qwidget.installEventFilter(self._event_filter)

def _mgui_show_widget(self):
self._qwidget.show()
def _mgui_get_visible(self):
return self._qwidget.isVisible()

def _mgui_hide_widget(self):
self._qwidget.hide()
def _mgui_set_visible(self, value: bool):
self._qwidget.setVisible(value)

def _mgui_get_enabled(self) -> bool:
return self._qwidget.isEnabled()
Expand Down Expand Up @@ -338,11 +338,11 @@ def __init__(self, layout="vertical"):
self._main_menu = self._main_window.menuBar()
self._menus: Dict[str, QtW.QMenu] = {}

def _mgui_show_widget(self):
self._main_window.show()
def _mgui_get_visible(self):
return self._main_window.isVisible()

def _mgui_hide_widget(self):
self._main_window.hide()
def _mgui_set_visible(self, value: bool):
self._main_window.setVisible(value)

def _mgui_get_native_widget(self) -> QtW.QMainWindow:
return self._main_window
Expand Down
2 changes: 1 addition & 1 deletion magicgui/widgets/_bases/container_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ def insert(self, key: int, widget: Widget):
widget.changed.connect(lambda x: self.changed(value=self))
_widget = widget

if self.labels and _widget.visible:
if self.labels:
from magicgui.widgets._concrete import _LabeledWidget

# no labels for button widgets (push buttons, checkboxes, have their own)
Expand Down
54 changes: 39 additions & 15 deletions magicgui/widgets/_bases/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def __init__(
annotation: Any = None,
label: str = None,
tooltip: Optional[str] = None,
visible: bool = True,
visible: Optional[bool] = None,
enabled: bool = True,
gui_only=False,
backend_kwargs=dict(),
Expand Down Expand Up @@ -85,16 +85,17 @@ def __init__(
self.enabled = enabled
self.annotation: Any = annotation
self.gui_only = gui_only
self.visible: bool = True
self.parent_changed = EventEmitter(source=self, type="parent_changed")
self.label_changed = EventEmitter(source=self, type="label_changed")
self._widget._mgui_bind_parent_change_callback(self._emit_parent)

# put the magicgui widget on the native object...may cause error on some backend
self.native._magic_widget = self
self._post_init()
if not visible:
self.hide()
self._visible: bool = False
self._explicitly_hidden: bool = False
if visible is not None:
self.visible = visible

@property
def annotation(self):
Expand Down Expand Up @@ -253,14 +254,39 @@ def _labeled_widget(self) -> Optional[_LabeledWidget]:
"""Return _LabeledWidget container, if applicable."""
return self._labeled_widget_ref() if self._labeled_widget_ref else None

def show(self, run=False):
"""Show the widget."""
self._widget._mgui_show_widget()
self.visible = True
@property
def visible(self) -> bool:
"""Return whether widget is visible."""
return self._widget._mgui_get_visible()

@visible.setter
def visible(self, value: bool):
"""Set widget visibility.
``widget.show()`` is an alias for ``widget.visible = True``
``widget.hide()`` is an alias for ``widget.visible = False``
"""
if value is None:
return

self._widget._mgui_set_visible(value)
self._explicitly_hidden = not value

labeled_widget = self._labeled_widget()
if labeled_widget is not None:
labeled_widget.show()
labeled_widget.visible = value

def show(self, run=False):
"""Show widget.
alias for ``widget.visible = True``
Parameters
----------
run : bool, optional
Whether to start the application event loop, by default False
"""
self.visible = True
if run:
self.__magicgui_app__.run()
return self # useful for generating repr in sphinx
Expand All @@ -275,13 +301,11 @@ def shown(self):
self.__magicgui_app__.__exit__()

def hide(self):
"""Hide widget."""
self._widget._mgui_hide_widget()
self.visible = False
"""Hide widget.
labeled_widget = self._labeled_widget()
if labeled_widget is not None:
labeled_widget.hide()
alias for ``widget.visible = False``
"""
self.visible = False

def render(self) -> "np.ndarray":
"""Return an RGBA (MxNx4) numpy array bitmap of the rendered widget."""
Expand Down
4 changes: 3 additions & 1 deletion magicgui/widgets/_concrete.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ def __init__(
self.filter = filter
kwargs["widgets"] = [self.line_edit, self.choose_btn]
kwargs["labels"] = False
kwargs["layout"] = "horizontal"
super().__init__(**kwargs)
self.margins = (0, 0, 0, 0)
self._show_file_dialog = use_app().get_obj("show_file_dialog")
Expand Down Expand Up @@ -531,8 +532,9 @@ def __init__(
kwargs["layout"] = "horizontal" if position in ("left", "right") else "vertical"
self._inner_widget = widget
widget._labeled_widget_ref = ref(self)
_visible = False if widget._explicitly_hidden else None
self._label_widget = Label(value=label or widget.label, tooltip=widget.tooltip)
super().__init__(**kwargs)
super().__init__(**kwargs, visible=_visible)
self.parent_changed.disconnect() # don't need _LabeledWidget to trigger stuff
self.labels = False # important to avoid infinite recursion during insert!
self._inner_widget.label_changed.connect(self._on_label_change)
Expand Down
12 changes: 6 additions & 6 deletions magicgui/widgets/_function_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class FunctionGui(Container, Generic[_R]):
Whether tooltips are shown when hovering over widgets. by default True
app : magicgui.Application or str, optional
A backend to use, by default ``None`` (use the default backend.)
show : bool, optional
visible : bool, optional
Whether to immediately show the widget, by default False
auto_call : bool, optional
If True, changing any parameter in either the GUI or the widget attributes
Expand Down Expand Up @@ -115,13 +115,14 @@ def __init__(
labels: bool = True,
tooltips: bool = True,
app: AppRef = None,
show: bool = False,
visible: bool = False,
auto_call: bool = False,
result_widget: bool = False,
param_options: Optional[Dict[str, dict]] = None,
name: str = None,
**kwargs,
):
print("FG, visible", visible)
if not callable(function):
raise TypeError("'function' argument to FunctionGui must be callable.")

Expand Down Expand Up @@ -155,6 +156,7 @@ def __init__(
super().__init__(
layout=layout,
labels=labels,
visible=visible,
widgets=list(sig.widgets(app).values()),
return_annotation=sig.return_annotation,
name=name or self._callable_name,
Expand Down Expand Up @@ -188,9 +190,6 @@ def __init__(
if auto_call:
self.changed.connect(lambda e: self.__call__())

if show:
self.show()

@property
def call_count(self) -> int:
"""Return the number of times the function has been called."""
Expand Down Expand Up @@ -277,7 +276,7 @@ def __call__(self, *args: Any, **kwargs: Any) -> _R:

def __repr__(self) -> str:
"""Return string representation of instance."""
return f"<FunctionGui {self._callable_name}{self.__signature__}>"
return f"<{type(self).__name__} {self._callable_name}{self.__signature__}>"

@property
def result_name(self) -> str:
Expand Down Expand Up @@ -363,6 +362,7 @@ class MainFunctionGui(FunctionGui[_R], MainWindow):
_widget: MainWindowProtocol

def __init__(self, function: Callable, *args, **kwargs):
print(kwargs)
super().__init__(function, *args, **kwargs)
self.create_menu_item("Help", "Documentation", callback=self._show_docs)
self._help_text_edit: Optional[TextEdit] = None
Expand Down
8 changes: 4 additions & 4 deletions magicgui/widgets/_protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ def __init__(self, **kwargs):
pass

@abstractmethod
def _mgui_show_widget(self) -> None:
"""Show the widget."""
def _mgui_get_visible(self):
"""Get widget visibility."""
raise NotImplementedError()

@abstractmethod
def _mgui_hide_widget(self) -> None:
"""Hide the widget."""
def _mgui_set_visible(self, value: bool):
"""Set widget visibility."""
raise NotImplementedError()

@abstractmethod
Expand Down
1 change: 1 addition & 0 deletions tests/test_magicgui.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ def func(a: str = "string", b: int = 3, c=7.1) -> str:
return "works"

assert hasattr(func, "a")
func.show()
assert not func.a.visible
assert func.b.visible
assert func.c.visible
Expand Down
1 change: 1 addition & 0 deletions tests/test_tqdm.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def f():
pass
assert pbar1.progressbar.visible is True

f.show()
f()

@magicgui
Expand Down
46 changes: 35 additions & 11 deletions tests/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ def test_create_widget(kwargs, expect_type):
class MyBadWidget:
"""INCOMPLETE widget implementation and will error."""

def _mgui_hide_widget(self): ... # noqa
def _mgui_get_visible(self): ... # noqa
def _mgui_set_visible(self): ... # noqa
def _mgui_get_enabled(self): ... # noqa
def _mgui_set_enabled(self, enabled): ... # noqa
def _mgui_get_parent(self): ... # noqa
Expand All @@ -64,13 +65,13 @@ def _mgui_get_value(self): ... # noqa
def _mgui_set_value(self, value): ... # noqa
def _mgui_bind_change_callback(self, callback): ... # noqa
def _mgui_get_tooltip(self, value): ... # noqa
def _mgui_set_tooltip(self, value): ... # noqa
# def _mgui_set_tooltip(self, value): ... # noqa


class MyValueWidget(MyBadWidget):
"""Complete protocol implementation... should work."""

def _mgui_show_widget(self): ... # noqa
def _mgui_set_tooltip(self, value): ... # noqa
# fmt: on


Expand All @@ -88,7 +89,7 @@ def test_custom_widget_fails():
with pytest.raises(TypeError) as err:
widgets.create_widget(1, widget_type=MyBadWidget) # type: ignore
assert "does not implement 'WidgetProtocol'" in str(err)
assert "Missing methods: {'_mgui_show_widget'}" in str(err)
assert "Missing methods: {'_mgui_set_tooltip'}" in str(err)


def test_extra_kwargs_warn():
Expand Down Expand Up @@ -116,9 +117,9 @@ def test_basic_widget_attributes():
widget.enabled = False
assert not widget.enabled

assert widget.visible
widget.visible = False
assert not widget.visible
widget.show()
assert widget.visible

assert widget.parent is None
container.append(widget)
Expand All @@ -128,7 +129,7 @@ def test_basic_widget_attributes():
assert widget.label == "my name"
widget.label = "A different label"
assert widget.label == "A different label"
assert widget.width > 200
assert widget.width < 100
widget.width = 150
assert widget.width == 150

Expand Down Expand Up @@ -215,21 +216,39 @@ def test_labeled_widget_container():

w1 = widgets.Label(value="hi", name="w1")
w2 = widgets.Label(value="hi", name="w2")
_ = widgets.Container(widgets=[w1, w2], layout="vertical")
container = widgets.Container(widgets=[w1, w2], layout="vertical")
assert w1._labeled_widget
lw = w1._labeled_widget()
assert isinstance(lw, _LabeledWidget)
assert not lw.visible
container.show()
assert w1.visible
assert lw.visible
w1.hide()
assert not w1.visible
assert not lw.visible
w1.show()
assert w1.visible
assert lw.visible
w1.label = "another label"
assert lw._label_widget.value == "another label"


def test_visible_in_container():
"""Test that visibility depends on containers."""
w1 = widgets.Label(value="hi", name="w1")
w2 = widgets.Label(value="hi", name="w2")
w3 = widgets.Label(value="hi", name="w2", visible=False)
container = widgets.Container(widgets=[w2, w3])
assert not w1.visible
assert not w2.visible
assert not w3.visible
assert not container.visible
container.show()
assert container.visible
assert w2.visible
assert not w3.visible
w1.show()
assert w1.visible


def test_delete_widget():
"""We can delete widgets from containers."""
a = widgets.Label(name="a")
Expand Down Expand Up @@ -299,6 +318,7 @@ def test_bound_values_visible():
def f(x: int = 5):
return x

f.show()
assert f.x.visible
assert f() == 10
f.x.unbind()
Expand Down Expand Up @@ -432,6 +452,10 @@ def add(num1: int, num2: int) -> int:
Resulting integer
"""

assert not add.visible
add.show()
assert add.visible

assert isinstance(add, widgets.MainFunctionGui)
add._show_docs()
assert isinstance(add._help_text_edit, widgets.TextEdit)
Expand Down

0 comments on commit 7f51aef

Please sign in to comment.