From f956869ec06bad5ea6ab16072ed5eb0e4c902dcf Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 28 Jun 2021 11:35:21 -0500 Subject: [PATCH 01/20] add support for disabling tabs (previously content would be disabled but you could still click on the tab) --- traitsui/qt4/ui_panel.py | 99 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 91 insertions(+), 8 deletions(-) diff --git a/traitsui/qt4/ui_panel.py b/traitsui/qt4/ui_panel.py index f49203979..ab1dc73b9 100644 --- a/traitsui/qt4/ui_panel.py +++ b/traitsui/qt4/ui_panel.py @@ -28,7 +28,7 @@ from pyface.qt import QtCore, QtGui -from traits.api import Any, HasPrivateTraits, Instance, Undefined +from traits.api import Any, HasPrivateTraits, Instance, Undefined, List, Property from traits.observation.api import match from traitsui.api import Group @@ -293,7 +293,7 @@ def panel(ui): return panel -def _fill_panel(panel, content, ui, item_handler=None): +def _fill_panel(panel, content, ui, item_handler=None, _visible_when_groups=None, _enabled_when_groups=None): """Fill a page based container panel with content. """ active = 0 @@ -306,7 +306,6 @@ def _fill_panel(panel, content, ui, item_handler=None): if isinstance(item, Group): if item.selected: active = index - gp = _GroupPanel(item, ui, suppress_label=True) page = gp.control sub_page = gp.sub_control @@ -337,9 +336,14 @@ def _fill_panel(panel, content, ui, item_handler=None): # Add the content. if isinstance(panel, QtGui.QTabWidget): - panel.addTab(new, page_name) + idx = panel.addTab(new, page_name) else: - panel.addItem(new, page_name) + idx = panel.addItem(new, page_name) + + if item.visible_when and (_visible_when_groups is not None): + _visible_when_groups.append((item.visible_when, new, idx)) + if item.enabled_when and (_enabled_when_groups is not None): + _enabled_when_groups.append((item.enabled_when, new, idx)) panel.setCurrentIndex(active) @@ -582,8 +586,16 @@ def __init__(self, group, ui, suppress_label=False): policy.setHorizontalStretch(50) policy.setVerticalStretch(50) sub.setSizePolicy(policy) - - _fill_panel(sub, content, self.ui, self._add_page_item) + _visible_when_groups = [] + _enabled_when_groups = [] + _fill_panel( + sub, + content, + self.ui, + self._add_page_item, + _visible_when_groups, + _enabled_when_groups + ) if outer is None: outer = sub @@ -591,7 +603,13 @@ def __init__(self, group, ui, suppress_label=False): inner.addWidget(sub) # Create an editor. - editor = TabbedFoldGroupEditor(container=sub, control=outer, ui=ui) + editor = TabbedFoldGroupEditor( + container=sub, + control=outer, + ui=ui, + _visible_when_groups=_visible_when_groups, + _enabled_when_groups=_enabled_when_groups + ) self._setup_editor(group, editor) else: @@ -1300,6 +1318,71 @@ class TabbedFoldGroupEditor(GroupEditor): #: The QTabWidget or QToolBox for the group container = Any() + + _visible_when_groups = List() + _enabled_when_groups = List() + #silly_list = List() + #nicer_list = Property(List, observe="silly_list") + + # def _get_nicer_list(self): + # return [ + # #getattr(self.ui.info, gid[0]) for gid in self.silly_list + # (*gid, getattr(self.ui.info, gid[0])) for gid in self.silly_list + # ] + + def __init__(self, **traits): + """ Initialise the object. + """ + super().__init__(**traits) + num_enabled_or_visible_whens = len(self._visible_when_groups) \ + + len(self._enabled_when_groups) + if num_enabled_or_visible_whens > 0: + for object in self.ui.context.values(): + object.on_trait_change( + lambda: self._when(), dispatch="ui" + ) + self._when() + + def _when(self): + """Set the visible and enabled states of all tabs in the editor as + controlled by a 'visible_when' or 'enabled_when' expression. + """ + self._evaluate_enabled_condition(self._enabled_when_groups) + #self._evaluate_visible_condition(self._visible_when_groups, "visible") + + def _evaluate_enabled_condition(self, conditions): + """Evaluates a list of (eval, widget) pairs and calls the + appropriate method on the widget to toggle whether it is + enabled as needed. + """ + print("_evaluate_enabled_condition") + context = self.ui._get_context(self.ui.context) + + if isinstance(self.container, QtGui.QTabWidget): + method_to_call_name = "setTabEnabled" + elif isinstance(self.container, QtGui.QToolBox): + method_to_call_name = "setItemEnabled" + else: + raise + + for when, widget, idx in conditions: + method_to_call = getattr(self.container, method_to_call_name) + try: + cond_value = eval(when, globals(), context) + method_to_call(idx, cond_value) + except Exception: + # catch errors in the validate_when expression + from traitsui.api import raise_to_debug + + raise_to_debug() + + def _evaluate_visible_condition(self, conditions, kind): + """Evaluates a list of (eval, widget) pairs and calls the + appropriate method on the tab widget to toggle whether it is + visible as needed. + """ + pass + # -- UI preference save/restore interface --------------------------------- From 4ff2f311a3b8f3e8a9f0f2a07dc44d744b97fb56 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 28 Jun 2021 12:01:16 -0500 Subject: [PATCH 02/20] get visible_when working as well --- traitsui/qt4/ui_panel.py | 44 ++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/traitsui/qt4/ui_panel.py b/traitsui/qt4/ui_panel.py index ab1dc73b9..5df0bc277 100644 --- a/traitsui/qt4/ui_panel.py +++ b/traitsui/qt4/ui_panel.py @@ -341,9 +341,9 @@ def _fill_panel(panel, content, ui, item_handler=None, _visible_when_groups=None idx = panel.addItem(new, page_name) if item.visible_when and (_visible_when_groups is not None): - _visible_when_groups.append((item.visible_when, new, idx)) + _visible_when_groups.append((item.visible_when, idx, new, page_name)) if item.enabled_when and (_enabled_when_groups is not None): - _enabled_when_groups.append((item.enabled_when, new, idx)) + _enabled_when_groups.append((item.enabled_when, idx, new, page_name)) panel.setCurrentIndex(active) @@ -1348,14 +1348,13 @@ def _when(self): controlled by a 'visible_when' or 'enabled_when' expression. """ self._evaluate_enabled_condition(self._enabled_when_groups) - #self._evaluate_visible_condition(self._visible_when_groups, "visible") + self._evaluate_visible_condition(self._visible_when_groups) def _evaluate_enabled_condition(self, conditions): """Evaluates a list of (eval, widget) pairs and calls the appropriate method on the widget to toggle whether it is enabled as needed. """ - print("_evaluate_enabled_condition") context = self.ui._get_context(self.ui.context) if isinstance(self.container, QtGui.QTabWidget): @@ -1376,12 +1375,45 @@ def _evaluate_enabled_condition(self, conditions): raise_to_debug() - def _evaluate_visible_condition(self, conditions, kind): + def _evaluate_visible_condition(self, conditions): """Evaluates a list of (eval, widget) pairs and calls the appropriate method on the tab widget to toggle whether it is visible as needed. """ - pass + context = self.ui._get_context(self.ui.context) + + if isinstance(self.container, QtGui.QTabWidget): + tab_or_item = "Tab" + elif isinstance(self.container, QtGui.QToolBox): + tab_or_item = "Item" + else: + raise + + for when, idx, widget, label in conditions: + + try: + cond_value = eval(when, globals(), context) + if cond_value: + method_to_call_name = "insert" + tab_or_item + method_to_call = getattr( + self.container, method_to_call_name + ) + # check that the tab for this widget is not already showing + if self.container.indexOf(widget) == -1: + method_to_call(idx, widget, label) + else: + method_to_call_name = "remove" + tab_or_item + method_to_call = getattr( + self.container, method_to_call_name + ) + # check that the tab for this widget is already showing + if self.container.indexOf(widget) != -1: + method_to_call(idx) + except Exception: + # catch errors in the validate_when expression + from traitsui.api import raise_to_debug + + raise_to_debug() # -- UI preference save/restore interface --------------------------------- From b15347cb21d11ad96463eba30528b2b8eeea71a9 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 28 Jun 2021 12:56:04 -0500 Subject: [PATCH 03/20] clean up --- traitsui/qt4/ui_panel.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/traitsui/qt4/ui_panel.py b/traitsui/qt4/ui_panel.py index 5df0bc277..c5fe31904 100644 --- a/traitsui/qt4/ui_panel.py +++ b/traitsui/qt4/ui_panel.py @@ -28,7 +28,7 @@ from pyface.qt import QtCore, QtGui -from traits.api import Any, HasPrivateTraits, Instance, Undefined, List, Property +from traits.api import Any, HasPrivateTraits, Instance, Undefined, List from traits.observation.api import match from traitsui.api import Group @@ -293,7 +293,14 @@ def panel(ui): return panel -def _fill_panel(panel, content, ui, item_handler=None, _visible_when_groups=None, _enabled_when_groups=None): +def _fill_panel( + panel, + content, + ui, + item_handler=None, + _visible_when_groups=None, + _enabled_when_groups=None +): """Fill a page based container panel with content. """ active = 0 @@ -1321,14 +1328,6 @@ class TabbedFoldGroupEditor(GroupEditor): _visible_when_groups = List() _enabled_when_groups = List() - #silly_list = List() - #nicer_list = Property(List, observe="silly_list") - - # def _get_nicer_list(self): - # return [ - # #getattr(self.ui.info, gid[0]) for gid in self.silly_list - # (*gid, getattr(self.ui.info, gid[0])) for gid in self.silly_list - # ] def __init__(self, **traits): """ Initialise the object. From 2465e944e418c61d4e9d228d93a666fd3ec46071 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 28 Jun 2021 12:57:40 -0500 Subject: [PATCH 04/20] remove unneeded imports --- traitsui/qt4/ui_panel.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/traitsui/qt4/ui_panel.py b/traitsui/qt4/ui_panel.py index c5fe31904..53442daf6 100644 --- a/traitsui/qt4/ui_panel.py +++ b/traitsui/qt4/ui_panel.py @@ -28,8 +28,7 @@ from pyface.qt import QtCore, QtGui -from traits.api import Any, HasPrivateTraits, Instance, Undefined, List -from traits.observation.api import match +from traits.api import Any, HasPrivateTraits, Instance, List, Undefined from traitsui.api import Group From ac39d865faece791f80dfc4ef53dc8b43d136cd7 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 28 Jun 2021 13:32:25 -0500 Subject: [PATCH 05/20] raise a TypeError with message --- traitsui/qt4/ui_panel.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/traitsui/qt4/ui_panel.py b/traitsui/qt4/ui_panel.py index 53442daf6..b7ce6a11f 100644 --- a/traitsui/qt4/ui_panel.py +++ b/traitsui/qt4/ui_panel.py @@ -1360,7 +1360,10 @@ def _evaluate_enabled_condition(self, conditions): elif isinstance(self.container, QtGui.QToolBox): method_to_call_name = "setItemEnabled" else: - raise + raise TypeError( + "container of a TabbedFoldGroupEditor must be either a " + "QTabWidget or a QToolBox" + ) for when, widget, idx in conditions: method_to_call = getattr(self.container, method_to_call_name) @@ -1385,7 +1388,10 @@ def _evaluate_visible_condition(self, conditions): elif isinstance(self.container, QtGui.QToolBox): tab_or_item = "Item" else: - raise + raise TypeError( + "container of a TabbedFoldGroupEditor must be either a " + "QTabWidget or a QToolBox" + ) for when, idx, widget, label in conditions: From 82c9a63940e93713e8f595c4c5d370054950d381 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 28 Jun 2021 13:33:54 -0500 Subject: [PATCH 06/20] update comment --- traitsui/qt4/ui_panel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/traitsui/qt4/ui_panel.py b/traitsui/qt4/ui_panel.py index b7ce6a11f..e4896a3b8 100644 --- a/traitsui/qt4/ui_panel.py +++ b/traitsui/qt4/ui_panel.py @@ -1342,7 +1342,7 @@ def __init__(self, **traits): self._when() def _when(self): - """Set the visible and enabled states of all tabs in the editor as + """Set all tabs in the editor to be enabled/visible as controlled by a 'visible_when' or 'enabled_when' expression. """ self._evaluate_enabled_condition(self._enabled_when_groups) From 291aaa1d79cddbc3bee5221d1ee62e44f8ed0e04 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 28 Jun 2021 13:56:51 -0500 Subject: [PATCH 07/20] add a regression test for Tabbed visible_when --- traitsui/tests/test_group.py | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 traitsui/tests/test_group.py diff --git a/traitsui/tests/test_group.py b/traitsui/tests/test_group.py new file mode 100644 index 000000000..21afb48bd --- /dev/null +++ b/traitsui/tests/test_group.py @@ -0,0 +1,66 @@ +# (C) Copyright 2004-2021 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + +import unittest + +from traits.api import Float, HasTraits, Int + +from traitsui.api import HGroup, Item, Tabbed, VGroup, View +from traitsui.testing.api import KeyClick, UITester + + +class Foo(HasTraits): + + a = Int + b = Float + + view = View( + Tabbed( + VGroup( + HGroup( + Item('a') + ), + label='Tab #1', + visible_when='object.b != 0.0', + id="first_tab" + ), + VGroup( + HGroup( + Item('b') + ), + label='Tab #2', + visible_when='True', + id="second_tab" + ), + id="tabbed_group" + ) + ) + + +class TestTabbed(unittest.TestCase): + + def test_visible_when(self): + foo = Foo() + tester = UITester() + + with tester.create_ui(foo) as ui: + tabbed_fold_group_editor = tester.find_by_id( + ui, "tabbed_group" + )._target + q_tab_widget = tabbed_fold_group_editor.container + # only Tab#2 is available at first + self.assertEqual(q_tab_widget.count(), 1) + + # change b to != 0.0 so Tab #1 is visible + b_field = tester.find_by_name(ui, 'b') + b_field.perform(KeyClick("1")) + b_field.perform(KeyClick("Enter")) + + self.assertEqual(q_tab_widget.count(), 2) From 98f22b7b59912827dce733c65e9ae0723e217a85 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 28 Jun 2021 14:01:31 -0500 Subject: [PATCH 08/20] fix newly introduced bug with unpacking in for loop --- traitsui/qt4/ui_panel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/traitsui/qt4/ui_panel.py b/traitsui/qt4/ui_panel.py index e4896a3b8..66cd5e660 100644 --- a/traitsui/qt4/ui_panel.py +++ b/traitsui/qt4/ui_panel.py @@ -1365,7 +1365,7 @@ def _evaluate_enabled_condition(self, conditions): "QTabWidget or a QToolBox" ) - for when, widget, idx in conditions: + for when, idx, widget, label in conditions: method_to_call = getattr(self.container, method_to_call_name) try: cond_value = eval(when, globals(), context) From eec5f97b138b31a06b59872f7194fe6c18f75205 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 28 Jun 2021 14:01:56 -0500 Subject: [PATCH 09/20] add Tabbed enabled_when regression test --- traitsui/tests/test_group.py | 50 +++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/traitsui/tests/test_group.py b/traitsui/tests/test_group.py index 21afb48bd..410e82e1f 100644 --- a/traitsui/tests/test_group.py +++ b/traitsui/tests/test_group.py @@ -17,7 +17,6 @@ class Foo(HasTraits): - a = Int b = Float @@ -44,6 +43,33 @@ class Foo(HasTraits): ) +class Bar(HasTraits): + a = Int + b = Float + + view = View( + Tabbed( + VGroup( + HGroup( + Item('a') + ), + label='Tab #1', + enabled_when='object.b != 0.0', + id="first_tab" + ), + VGroup( + HGroup( + Item('b') + ), + label='Tab #2', + enabled_when='True', + id="second_tab" + ), + id="tabbed_group" + ) + ) + + class TestTabbed(unittest.TestCase): def test_visible_when(self): @@ -64,3 +90,25 @@ def test_visible_when(self): b_field.perform(KeyClick("Enter")) self.assertEqual(q_tab_widget.count(), 2) + + def test_enabled_when(self): + bar = Bar() + tester = UITester() + + with tester.create_ui(bar) as ui: + tabbed_fold_group_editor = tester.find_by_id( + ui, "tabbed_group" + )._target + q_tab_widget = tabbed_fold_group_editor.container + # both tabs exist + self.assertEqual(q_tab_widget.count(), 2) + # but first is disabled + self.assertFalse(q_tab_widget.isTabEnabled(0)) + + # change b to != 0.0 so Tab #1 is enabled + b_field = tester.find_by_name(ui, 'b') + b_field.perform(KeyClick("1")) + b_field.perform(KeyClick("Enter")) + + self.assertEqual(q_tab_widget.count(), 2) + self.assertTrue(q_tab_widget.isTabEnabled(0)) From 3acd7a155d421cd7700a237c9187a73bc4cca479 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 28 Jun 2021 14:09:33 -0500 Subject: [PATCH 10/20] add same tests for VFold --- traitsui/tests/test_group.py | 112 ++++++++++++++++++++++++++++++++--- 1 file changed, 105 insertions(+), 7 deletions(-) diff --git a/traitsui/tests/test_group.py b/traitsui/tests/test_group.py index 410e82e1f..e2387f023 100644 --- a/traitsui/tests/test_group.py +++ b/traitsui/tests/test_group.py @@ -12,11 +12,11 @@ from traits.api import Float, HasTraits, Int -from traitsui.api import HGroup, Item, Tabbed, VGroup, View +from traitsui.api import HGroup, Item, Tabbed, VFold, VGroup, View from traitsui.testing.api import KeyClick, UITester -class Foo(HasTraits): +class TabbedVisible(HasTraits): a = Int b = Float @@ -43,7 +43,7 @@ class Foo(HasTraits): ) -class Bar(HasTraits): +class TabbedEnabled(HasTraits): a = Int b = Float @@ -70,13 +70,68 @@ class Bar(HasTraits): ) +class FoldVisible(HasTraits): + a = Int + b = Float + + view = View( + VFold( + VGroup( + HGroup( + Item('a') + ), + label='Fold #1', + visible_when='object.b != 0.0', + id="first_fold" + ), + VGroup( + HGroup( + Item('b') + ), + label='Fold #2', + visible_when='True', + id="second_fold" + ), + id="folded_group" + ) + ) + + +class FoldEnabled(HasTraits): + a = Int + b = Float + + view = View( + VFold( + VGroup( + HGroup( + Item('a') + ), + label='Fold #1', + enabled_when='object.b != 0.0', + id="first_fold" + ), + VGroup( + HGroup( + Item('b') + ), + label='Fold #2', + enabled_when='True', + id="second_fold" + ), + id="folded_group" + ) + ) + + + class TestTabbed(unittest.TestCase): def test_visible_when(self): - foo = Foo() + tabbed_visible = TabbedVisible() tester = UITester() - with tester.create_ui(foo) as ui: + with tester.create_ui(tabbed_visible) as ui: tabbed_fold_group_editor = tester.find_by_id( ui, "tabbed_group" )._target @@ -92,10 +147,10 @@ def test_visible_when(self): self.assertEqual(q_tab_widget.count(), 2) def test_enabled_when(self): - bar = Bar() + tabbed_enabled = TabbedEnabled() tester = UITester() - with tester.create_ui(bar) as ui: + with tester.create_ui(tabbed_enabled) as ui: tabbed_fold_group_editor = tester.find_by_id( ui, "tabbed_group" )._target @@ -112,3 +167,46 @@ def test_enabled_when(self): self.assertEqual(q_tab_widget.count(), 2) self.assertTrue(q_tab_widget.isTabEnabled(0)) + +class TestFold(unittest.TestCase): + + def test_visible_when(self): + fold_visible = FoldVisible() + tester = UITester() + + with tester.create_ui(fold_visible) as ui: + tabbed_fold_group_editor = tester.find_by_id( + ui, "folded_group" + )._target + q_tab_widget = tabbed_fold_group_editor.container + # only Tab#2 is available at first + self.assertEqual(q_tab_widget.count(), 1) + + # change b to != 0.0 so Fold #1 is visible + b_field = tester.find_by_name(ui, 'b') + b_field.perform(KeyClick("1")) + b_field.perform(KeyClick("Enter")) + + self.assertEqual(q_tab_widget.count(), 2) + + def test_enabled_when(self): + fold_enabled = FoldEnabled() + tester = UITester() + + with tester.create_ui(fold_enabled) as ui: + tabbed_fold_group_editor = tester.find_by_id( + ui, "folded_group" + )._target + q_tab_widget = tabbed_fold_group_editor.container + # both tabs exist + self.assertEqual(q_tab_widget.count(), 2) + # but first is disabled + self.assertFalse(q_tab_widget.isItemEnabled(0)) + + # change b to != 0.0 so fold #1 is enabled + b_field = tester.find_by_name(ui, 'b') + b_field.perform(KeyClick("1")) + b_field.perform(KeyClick("Enter")) + + self.assertEqual(q_tab_widget.count(), 2) + self.assertTrue(q_tab_widget.isItemEnabled(0)) \ No newline at end of file From e77e12b4bb3451b6025d8302858ba1c565e8ae28 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 28 Jun 2021 14:11:02 -0500 Subject: [PATCH 11/20] comments / naming --- traitsui/tests/test_group.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/traitsui/tests/test_group.py b/traitsui/tests/test_group.py index e2387f023..de8e823ed 100644 --- a/traitsui/tests/test_group.py +++ b/traitsui/tests/test_group.py @@ -179,7 +179,7 @@ def test_visible_when(self): ui, "folded_group" )._target q_tab_widget = tabbed_fold_group_editor.container - # only Tab#2 is available at first + # only Fold #2 is available at first self.assertEqual(q_tab_widget.count(), 1) # change b to != 0.0 so Fold #1 is visible @@ -198,15 +198,15 @@ def test_enabled_when(self): ui, "folded_group" )._target q_tab_widget = tabbed_fold_group_editor.container - # both tabs exist + # both folds exist self.assertEqual(q_tab_widget.count(), 2) # but first is disabled self.assertFalse(q_tab_widget.isItemEnabled(0)) - # change b to != 0.0 so fold #1 is enabled + # change b to != 0.0 so Fold #1 is enabled b_field = tester.find_by_name(ui, 'b') b_field.perform(KeyClick("1")) b_field.perform(KeyClick("Enter")) self.assertEqual(q_tab_widget.count(), 2) - self.assertTrue(q_tab_widget.isItemEnabled(0)) \ No newline at end of file + self.assertTrue(q_tab_widget.isItemEnabled(0)) From 3bfe51239aed42d4f56a9efca8f4a144c67f8273 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 28 Jun 2021 14:17:05 -0500 Subject: [PATCH 12/20] refactor tests a bit --- traitsui/tests/test_group.py | 186 +++++++++++++---------------------- 1 file changed, 66 insertions(+), 120 deletions(-) diff --git a/traitsui/tests/test_group.py b/traitsui/tests/test_group.py index de8e823ed..90ec5df7b 100644 --- a/traitsui/tests/test_group.py +++ b/traitsui/tests/test_group.py @@ -16,124 +16,67 @@ from traitsui.testing.api import KeyClick, UITester -class TabbedVisible(HasTraits): - a = Int - b = Float - - view = View( - Tabbed( - VGroup( - HGroup( - Item('a') +class Foo(HasTraits): + a = Int() + b = Float() + + +def get_view(group_type, enabled_visible): + if enabled_visible == "enabled": + return View( + group_type( + VGroup( + HGroup( + Item('a') + ), + label='Fold #1', + enabled_when='object.b != 0.0', + id="first_fold" ), - label='Tab #1', - visible_when='object.b != 0.0', - id="first_tab" - ), - VGroup( - HGroup( - Item('b') + VGroup( + HGroup( + Item('b') + ), + label='Fold #2', + id="second_fold" ), - label='Tab #2', - visible_when='True', - id="second_tab" - ), - id="tabbed_group" + id="interesting_group" + ) ) - ) - - -class TabbedEnabled(HasTraits): - a = Int - b = Float - - view = View( - Tabbed( - VGroup( - HGroup( - Item('a') + else: + return View( + group_type( + VGroup( + HGroup( + Item('a') + ), + label='Fold #1', + visible_when='object.b != 0.0', + id="first_fold" ), - label='Tab #1', - enabled_when='object.b != 0.0', - id="first_tab" - ), - VGroup( - HGroup( - Item('b') + VGroup( + HGroup( + Item('b') + ), + label='Fold #2', + id="second_fold" ), - label='Tab #2', - enabled_when='True', - id="second_tab" - ), - id="tabbed_group" + id="interesting_group" + ) ) - ) - - -class FoldVisible(HasTraits): - a = Int - b = Float - - view = View( - VFold( - VGroup( - HGroup( - Item('a') - ), - label='Fold #1', - visible_when='object.b != 0.0', - id="first_fold" - ), - VGroup( - HGroup( - Item('b') - ), - label='Fold #2', - visible_when='True', - id="second_fold" - ), - id="folded_group" - ) - ) - - -class FoldEnabled(HasTraits): - a = Int - b = Float - - view = View( - VFold( - VGroup( - HGroup( - Item('a') - ), - label='Fold #1', - enabled_when='object.b != 0.0', - id="first_fold" - ), - VGroup( - HGroup( - Item('b') - ), - label='Fold #2', - enabled_when='True', - id="second_fold" - ), - id="folded_group" - ) - ) class TestTabbed(unittest.TestCase): def test_visible_when(self): - tabbed_visible = TabbedVisible() + tabbed_visible = Foo() + view = get_view(Tabbed, "visible") tester = UITester() - with tester.create_ui(tabbed_visible) as ui: + with tester.create_ui(tabbed_visible, dict(view=view)) as ui: tabbed_fold_group_editor = tester.find_by_id( - ui, "tabbed_group" + ui, "interesting_group" )._target q_tab_widget = tabbed_fold_group_editor.container # only Tab#2 is available at first @@ -147,12 +90,13 @@ def test_visible_when(self): self.assertEqual(q_tab_widget.count(), 2) def test_enabled_when(self): - tabbed_enabled = TabbedEnabled() + tabbed_enabled = Foo() + view = get_view(Tabbed, "enabled") tester = UITester() - with tester.create_ui(tabbed_enabled) as ui: + with tester.create_ui(tabbed_enabled, dict(view=view)) as ui: tabbed_fold_group_editor = tester.find_by_id( - ui, "tabbed_group" + ui, "interesting_group" )._target q_tab_widget = tabbed_fold_group_editor.container # both tabs exist @@ -171,42 +115,44 @@ def test_enabled_when(self): class TestFold(unittest.TestCase): def test_visible_when(self): - fold_visible = FoldVisible() + fold_visible = Foo() + view = get_view(VFold, "visible") tester = UITester() - with tester.create_ui(fold_visible) as ui: + with tester.create_ui(fold_visible, dict(view=view)) as ui: tabbed_fold_group_editor = tester.find_by_id( - ui, "folded_group" + ui, "interesting_group" )._target - q_tab_widget = tabbed_fold_group_editor.container + q_tool_box = tabbed_fold_group_editor.container # only Fold #2 is available at first - self.assertEqual(q_tab_widget.count(), 1) + self.assertEqual(q_tool_box.count(), 1) # change b to != 0.0 so Fold #1 is visible b_field = tester.find_by_name(ui, 'b') b_field.perform(KeyClick("1")) b_field.perform(KeyClick("Enter")) - self.assertEqual(q_tab_widget.count(), 2) + self.assertEqual(q_tool_box.count(), 2) def test_enabled_when(self): - fold_enabled = FoldEnabled() + fold_enabled = Foo() + view = get_view(VFold, "enabled") tester = UITester() - with tester.create_ui(fold_enabled) as ui: + with tester.create_ui(fold_enabled, dict(view=view)) as ui: tabbed_fold_group_editor = tester.find_by_id( - ui, "folded_group" + ui, "interesting_group" )._target - q_tab_widget = tabbed_fold_group_editor.container + q_tool_box = tabbed_fold_group_editor.container # both folds exist - self.assertEqual(q_tab_widget.count(), 2) + self.assertEqual(q_tool_box.count(), 2) # but first is disabled - self.assertFalse(q_tab_widget.isItemEnabled(0)) + self.assertFalse(q_tool_box.isItemEnabled(0)) # change b to != 0.0 so Fold #1 is enabled b_field = tester.find_by_name(ui, 'b') b_field.perform(KeyClick("1")) b_field.perform(KeyClick("Enter")) - self.assertEqual(q_tab_widget.count(), 2) - self.assertTrue(q_tab_widget.isItemEnabled(0)) + self.assertEqual(q_tool_box.count(), 2) + self.assertTrue(q_tool_box.isItemEnabled(0)) From 0e70767c94127da3dddc6e4e93907aeb38a8c0a8 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 28 Jun 2021 14:18:56 -0500 Subject: [PATCH 13/20] remove unneeded HGroups --- traitsui/tests/test_group.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/traitsui/tests/test_group.py b/traitsui/tests/test_group.py index 90ec5df7b..060e9c750 100644 --- a/traitsui/tests/test_group.py +++ b/traitsui/tests/test_group.py @@ -12,7 +12,7 @@ from traits.api import Float, HasTraits, Int -from traitsui.api import HGroup, Item, Tabbed, VFold, VGroup, View +from traitsui.api import Item, Tabbed, VFold, VGroup, View from traitsui.testing.api import KeyClick, UITester @@ -26,17 +26,13 @@ def get_view(group_type, enabled_visible): return View( group_type( VGroup( - HGroup( - Item('a') - ), + Item('a'), label='Fold #1', enabled_when='object.b != 0.0', id="first_fold" ), VGroup( - HGroup( - Item('b') - ), + Item('b'), label='Fold #2', id="second_fold" ), @@ -47,17 +43,13 @@ def get_view(group_type, enabled_visible): return View( group_type( VGroup( - HGroup( - Item('a') - ), + Item('a'), label='Fold #1', visible_when='object.b != 0.0', id="first_fold" ), VGroup( - HGroup( - Item('b') - ), + Item('b'), label='Fold #2', id="second_fold" ), From 146b24146c1c1c08ea919113e8e83a01819afb3d Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 28 Jun 2021 14:19:45 -0500 Subject: [PATCH 14/20] rename test case as TestVFold --- traitsui/tests/test_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/traitsui/tests/test_group.py b/traitsui/tests/test_group.py index 060e9c750..1b5fdbe3b 100644 --- a/traitsui/tests/test_group.py +++ b/traitsui/tests/test_group.py @@ -104,7 +104,7 @@ def test_enabled_when(self): self.assertEqual(q_tab_widget.count(), 2) self.assertTrue(q_tab_widget.isTabEnabled(0)) -class TestFold(unittest.TestCase): +class TestVFold(unittest.TestCase): def test_visible_when(self): fold_visible = Foo() From 3637bcdb40fdf0ecfd20dd4d25ea67b1a6d5887c Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 28 Jun 2021 14:22:38 -0500 Subject: [PATCH 15/20] add comments to regression tests refering to the issue --- traitsui/tests/test_group.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/traitsui/tests/test_group.py b/traitsui/tests/test_group.py index 1b5fdbe3b..eb05abe24 100644 --- a/traitsui/tests/test_group.py +++ b/traitsui/tests/test_group.py @@ -61,6 +61,7 @@ def get_view(group_type, enabled_visible): class TestTabbed(unittest.TestCase): + # regression test for enthought/tratsui#758 def test_visible_when(self): tabbed_visible = Foo() view = get_view(Tabbed, "visible") @@ -81,6 +82,7 @@ def test_visible_when(self): self.assertEqual(q_tab_widget.count(), 2) + # regression test for enthought/tratsui#758 def test_enabled_when(self): tabbed_enabled = Foo() view = get_view(Tabbed, "enabled") @@ -106,6 +108,7 @@ def test_enabled_when(self): class TestVFold(unittest.TestCase): + # regression test for enthought/tratsui#758 def test_visible_when(self): fold_visible = Foo() view = get_view(VFold, "visible") @@ -126,6 +129,7 @@ def test_visible_when(self): self.assertEqual(q_tool_box.count(), 2) + # regression test for enthought/tratsui#758 def test_enabled_when(self): fold_enabled = Foo() view = get_view(VFold, "enabled") From 8c405bbfbbeaa352b3c713b688ce72fa225d7d63 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 28 Jun 2021 14:27:33 -0500 Subject: [PATCH 16/20] tests are qt specific --- traitsui/tests/test_group.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/traitsui/tests/test_group.py b/traitsui/tests/test_group.py index eb05abe24..68c606d7a 100644 --- a/traitsui/tests/test_group.py +++ b/traitsui/tests/test_group.py @@ -14,6 +14,7 @@ from traitsui.api import Item, Tabbed, VFold, VGroup, View from traitsui.testing.api import KeyClick, UITester +from traitsui.tests._tools import requires_toolkit, ToolkitName class Foo(HasTraits): @@ -62,6 +63,7 @@ def get_view(group_type, enabled_visible): class TestTabbed(unittest.TestCase): # regression test for enthought/tratsui#758 + @requires_toolkit([ToolkitName.qt]) def test_visible_when(self): tabbed_visible = Foo() view = get_view(Tabbed, "visible") @@ -83,6 +85,7 @@ def test_visible_when(self): self.assertEqual(q_tab_widget.count(), 2) # regression test for enthought/tratsui#758 + @requires_toolkit([ToolkitName.qt]) def test_enabled_when(self): tabbed_enabled = Foo() view = get_view(Tabbed, "enabled") @@ -109,6 +112,7 @@ def test_enabled_when(self): class TestVFold(unittest.TestCase): # regression test for enthought/tratsui#758 + @requires_toolkit([ToolkitName.qt]) def test_visible_when(self): fold_visible = Foo() view = get_view(VFold, "visible") @@ -130,6 +134,7 @@ def test_visible_when(self): self.assertEqual(q_tool_box.count(), 2) # regression test for enthought/tratsui#758 + @requires_toolkit([ToolkitName.qt]) def test_enabled_when(self): fold_enabled = Foo() view = get_view(VFold, "enabled") From c73854ab36177099059948f280907f88ed948b83 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 28 Jun 2021 14:30:39 -0500 Subject: [PATCH 17/20] flake8 --- traitsui/tests/test_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/traitsui/tests/test_group.py b/traitsui/tests/test_group.py index 68c606d7a..496063729 100644 --- a/traitsui/tests/test_group.py +++ b/traitsui/tests/test_group.py @@ -59,7 +59,6 @@ def get_view(group_type, enabled_visible): ) - class TestTabbed(unittest.TestCase): # regression test for enthought/tratsui#758 @@ -109,6 +108,7 @@ def test_enabled_when(self): self.assertEqual(q_tab_widget.count(), 2) self.assertTrue(q_tab_widget.isTabEnabled(0)) + class TestVFold(unittest.TestCase): # regression test for enthought/tratsui#758 From a908d3bf96bbb02b5b3324c54779148a794a9e16 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 28 Jun 2021 15:16:13 -0500 Subject: [PATCH 18/20] add news fragment --- docs/releases/upcoming/1705.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/releases/upcoming/1705.bugfix.rst diff --git a/docs/releases/upcoming/1705.bugfix.rst b/docs/releases/upcoming/1705.bugfix.rst new file mode 100644 index 000000000..35b02242e --- /dev/null +++ b/docs/releases/upcoming/1705.bugfix.rst @@ -0,0 +1 @@ +Add support for {enabled/visible}_when on Tabbed and VFold groups (#1705) \ No newline at end of file From c050ebf7aebce6d0dd3880383217bf0cd7dd8326 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 28 Jun 2021 15:19:33 -0500 Subject: [PATCH 19/20] remove irrelevant changes --- traitsui/qt4/ui_panel.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/traitsui/qt4/ui_panel.py b/traitsui/qt4/ui_panel.py index 66cd5e660..824567aea 100644 --- a/traitsui/qt4/ui_panel.py +++ b/traitsui/qt4/ui_panel.py @@ -29,6 +29,7 @@ from pyface.qt import QtCore, QtGui from traits.api import Any, HasPrivateTraits, Instance, List, Undefined +from traits.observation.api import match from traitsui.api import Group @@ -312,6 +313,7 @@ def _fill_panel( if isinstance(item, Group): if item.selected: active = index + gp = _GroupPanel(item, ui, suppress_label=True) page = gp.control sub_page = gp.sub_control From 74dfc68b6dee051cb53b637d39a54caefda20daf Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Wed, 22 Dec 2021 08:33:24 -0700 Subject: [PATCH 20/20] flake8 --- traitsui/qt4/ui_panel.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/traitsui/qt4/ui_panel.py b/traitsui/qt4/ui_panel.py index dbf446ade..ca8658928 100644 --- a/traitsui/qt4/ui_panel.py +++ b/traitsui/qt4/ui_panel.py @@ -1313,7 +1313,7 @@ class TabbedFoldGroupEditor(GroupEditor): #: The QTabWidget or QToolBox for the group container = Any() - + _visible_when_groups = List() _enabled_when_groups = List() @@ -1321,8 +1321,9 @@ def __init__(self, **traits): """ Initialise the object. """ super().__init__(**traits) - num_enabled_or_visible_whens = len(self._visible_when_groups) \ - + len(self._enabled_when_groups) + num_enabled_or_visible_whens = ( + len(self._visible_when_groups) + len(self._enabled_when_groups) + ) if num_enabled_or_visible_whens > 0: for object in self.ui.context.values(): object.on_trait_change( @@ -1408,7 +1409,6 @@ def _evaluate_visible_condition(self, conditions): raise_to_debug() - # -- UI preference save/restore interface --------------------------------- def restore_prefs(self, prefs):