From 0b2c0de13d7d0b02eef2577d7450177c4c9f7197 Mon Sep 17 00:00:00 2001 From: itziakos Date: Sun, 15 Jan 2012 22:54:56 +0000 Subject: [PATCH 01/12] FIX: fix issue with run-away change notification in the CSVListEditor --- traitsui/editors/csv_list_editor.py | 150 +++++++++++++++------------- traitsui/qt4/csv_list_editor.py | 46 +++++++++ traitsui/wx/csv_list_editor.py | 47 +++++++++ 3 files changed, 171 insertions(+), 72 deletions(-) create mode 100644 traitsui/qt4/csv_list_editor.py create mode 100644 traitsui/wx/csv_list_editor.py diff --git a/traitsui/editors/csv_list_editor.py b/traitsui/editors/csv_list_editor.py index d7080a197..c02c44ad5 100644 --- a/traitsui/editors/csv_list_editor.py +++ b/traitsui/editors/csv_list_editor.py @@ -24,7 +24,6 @@ from traits.trait_handlers import RangeTypes from .text_editor import TextEditor -from ..editor_factory import EditorFactory from ..helper import enum_values_changed @@ -153,7 +152,36 @@ def _validate_range_value(range_object, object, name, value): range_object.error(object, name, value) -class CSVListEditor(EditorFactory): +def _prepare_method(cls, parent): + """ Unbound implementation of the prepare editor method to add a + change notification hook in the items of the list before calling + the parent prepere method of the parent class. + + """ + name = cls.extended_name + if name != 'None': + cls.context_object.on_trait_change(cls._update_editor, + name + '[]', + dispatch='ui') + super(cls.__class__, cls).prepare(parent) + +def _dispose_method(cls): + """ Unbound implementation of the dispose editor method to remove + the change notification hook in the items of the list before calling + the parent dispose method of the parent class. + + """ + if cls.ui is None: + return + + name = cls.extended_name + if name != 'None': + cls.context_object.on_trait_change(cls._update_editor, + name + '[]', + remove=True) + super(cls.__class__, cls).dispose() + +class CSVListEditor(TextEditor): """A text editor for a List. This editor provides a single line of input text of comma separated @@ -161,9 +189,9 @@ class CSVListEditor(EditorFactory): changed.) The editor can only be used with List traits whose inner trait is one of Int, Float, Str, Enum, or Range. - The 'simple', 'text' and 'custom' styles are all the same. The - 'readonly' style provides the same formatting in the text field as - the other editors, but the user can not change the value. + The 'simple', 'text', 'custom' and readonly styles are based on + TextEditor. The 'readonly' style provides the same formatting in the + text field as the other editors, but the user cannot change the value. Like other Traits editors, the background of the text field will turn red if the user enters an incorrectly formatted list or if the values @@ -309,73 +337,51 @@ def _funcs(self, object, name): return evaluate, fmt_func - def _make_text_editor(self, ui, object, name, description, parent, - readonly=False): - """Create the actual text editor for this list. - - Parameters - ---------- - ui : instance of traitsui.ui.UI - Passed on to factory functions of the TextEditor instance. - - object : instance of HasTraits - The HasTraits instance with the trait `name`. - - name : str - The name of the trait on `object`. - - description : str - This is a description of the trait. If, for example, the Item - holding the trait defines a tooltip, that string will end up - in `description`. - It is passed on to the factory functions of the TextEditor instance. - parent : object - The parent of the backend-dependent control that will be used - to implement the editor. + #--------------------------------------------------------------------------- + # Methods that generate backend toolkit-specific editors. + #--------------------------------------------------------------------------- - readonly : bool - If True, create a read-only editor. Otherwise, the editor field - will be editable by the user. - - Returns - ------- - ed : object - An editor generated by either TextEditor.simple_editor(...) - or TextEditor.readonly_editor(...), depending on the value - of `readonly`. + def simple_editor ( self, ui, object, name, description, parent ): + """ Generates an editor using the "simple" style. """ - evaluate, fmt_func = self._funcs(object, name) - # Create a TextEditor with the appropriate evaluation and formatting - # functions. - editor_factory = TextEditor(evaluate=evaluate, format_func=fmt_func, - auto_set=self.auto_set, enter_set=self.enter_set) - - # Call the appropriate factory function to create the actual editor. - if readonly: - ed = editor_factory.readonly_editor(ui, object, name, description, - parent) - else: - ed = editor_factory.simple_editor(ui, object, name, description, - parent) - - # Hook up a listener on `object` so that the display is updated if - # the list changes external to the editor. - object.on_trait_change(ed.update_editor, name + '[]') - - return ed - - def simple_editor(self, ui, object, name, description, parent): - e = self._make_text_editor(ui, object, name, description, parent) - return e - - def custom_editor(self, ui, object, name, description, parent): - return self.simple_editor(ui, object, name, description, parent) - - def text_editor(self, ui, object, name, description, parent): - return self.simple_editor(ui, object, name, description, parent) - - def readonly_editor(self, ui, object, name, description, parent): - e = self._make_text_editor(ui, object, name, description, parent, - readonly=True) - return e + self.evaluate, self.format_func = self._funcs(object, name) + return self.simple_editor_class( parent, + factory = self, + ui = ui, + object = object, + name = name, + description = description ) + + def custom_editor ( self, ui, object, name, description, parent ): + """ Generates an editor using the "custom" style. + """ + self.evaluate, self.format_func = self._funcs(object, name) + return self.custom_editor_class( parent, + factory = self, + ui = ui, + object = object, + name = name, + description = description ) + + def text_editor ( self, ui, object, name, description, parent ): + """ Generates an editor using the "text" style. + """ + self.evaluate, self.format_func = self._funcs(object, name) + return self.text_editor_class( parent, + factory = self, + ui = ui, + object = object, + name = name, + description = description ) + + def readonly_editor ( self, ui, object, name, description, parent ): + """ Generates an "editor" that is read-only. + """ + self.evaluate, self.format_func = self._funcs(object, name) + return self.readonly_editor_class( parent, + factory = self, + ui = ui, + object = object, + name = name, + description = description ) diff --git a/traitsui/qt4/csv_list_editor.py b/traitsui/qt4/csv_list_editor.py new file mode 100644 index 000000000..9087bbb52 --- /dev/null +++ b/traitsui/qt4/csv_list_editor.py @@ -0,0 +1,46 @@ +#------------------------------------------------------------------------------ +# +# Copyright (c) 2005, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/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! +# +# Author: Ioannis Tziakos +# Date: 11 Jan 2012 +# +#------------------------------------------------------------------------------ + +""" Defines the various text editors for the Qt user interface toolkit. + The module is mainly a place-folder for TextEditor factories that have + been augmented to also listen to changes in the items of the list object. +""" + +#------------------------------------------------------------------------------- +# Imports: +#------------------------------------------------------------------------------ + +from .text_editor import (SimpleEditor, CustomEditor, + ReadonlyEditor, TextEditor) +from ..editors.csv_list_editor import hook_on_item_change + +class SimpleEditor(WXSimpleEditor): + """ Simple Editor style for CSVListEditor. """ + prepare = _prepare_method + dispose = _dispose_method + +class CustomEditor(WXCustomEditor): + """ Custom Editor style for CSVListEditor. """ + prepare = _prepare_method + dispose = _dispose_method + +class ReadonlyEditor(WXReadonlyEditor): + """ Readonly Editor style for CSVListEditor. """ + prepare = _prepare_method + dispose = _dispose_method + +TextEditor = SimpleEditor diff --git a/traitsui/wx/csv_list_editor.py b/traitsui/wx/csv_list_editor.py new file mode 100644 index 000000000..f95e2a628 --- /dev/null +++ b/traitsui/wx/csv_list_editor.py @@ -0,0 +1,47 @@ +#------------------------------------------------------------------------------ +# +# Copyright (c) 2005, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/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! +# +# Author: Ioannis Tziakos +# Date: 11 Jan 2012 +# +#------------------------------------------------------------------------------ + +""" Defines the various text editors for the wxPython user interface toolkit. + The module is mainly a place-folder for TextEditor factories that have + been augmented to also listen to changes in the items of the list object. + +""" + +#------------------------------------------------------------------------------- +# Imports: +#------------------------------------------------------------------------------ + +from .text_editor import (SimpleEditor, CustomEditor, + ReadonlyEditor, TextEditor) +from ..editors.csv_list_editor import hook_on_item_change + +class SimpleEditor(WXSimpleEditor): + """ Simple Editor style for CSVListEditor. """ + prepare = _prepare_method + dispose = _dispose_method + +class CustomEditor(WXCustomEditor): + """ Custom Editor style for CSVListEditor. """ + prepare = _prepare_method + dispose = _dispose_method + +class ReadonlyEditor(WXReadonlyEditor): + """ Readonly Editor style for CSVListEditor. """ + prepare = _prepare_method + dispose = _dispose_method + +TextEditor = SimpleEditor From a387912fce1ad766fae34ebb571fa62fab437e03 Mon Sep 17 00:00:00 2001 From: itziakos Date: Mon, 16 Jan 2012 00:34:09 +0000 Subject: [PATCH 02/12] Correct import error --- traitsui/qt4/csv_list_editor.py | 2 +- traitsui/wx/csv_list_editor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/traitsui/qt4/csv_list_editor.py b/traitsui/qt4/csv_list_editor.py index 9087bbb52..391d1c05a 100644 --- a/traitsui/qt4/csv_list_editor.py +++ b/traitsui/qt4/csv_list_editor.py @@ -26,7 +26,7 @@ from .text_editor import (SimpleEditor, CustomEditor, ReadonlyEditor, TextEditor) -from ..editors.csv_list_editor import hook_on_item_change +from ..editors.csv_list_editor import _prepare_method, _dispose_method class SimpleEditor(WXSimpleEditor): """ Simple Editor style for CSVListEditor. """ diff --git a/traitsui/wx/csv_list_editor.py b/traitsui/wx/csv_list_editor.py index f95e2a628..356c38d8d 100644 --- a/traitsui/wx/csv_list_editor.py +++ b/traitsui/wx/csv_list_editor.py @@ -27,7 +27,7 @@ from .text_editor import (SimpleEditor, CustomEditor, ReadonlyEditor, TextEditor) -from ..editors.csv_list_editor import hook_on_item_change +from ..editors.csv_list_editor import _prepare_method, _dispose_method class SimpleEditor(WXSimpleEditor): """ Simple Editor style for CSVListEditor. """ From 4cbd21e096ec103cbaa517b3d0e40ec7facc4cd6 Mon Sep 17 00:00:00 2001 From: itziakos Date: Mon, 16 Jan 2012 00:52:14 +0000 Subject: [PATCH 03/12] more corrections in the imports --- traitsui/qt4/csv_list_editor.py | 11 ++++++----- traitsui/wx/csv_list_editor.py | 5 +++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/traitsui/qt4/csv_list_editor.py b/traitsui/qt4/csv_list_editor.py index 391d1c05a..9a9e2d9d4 100644 --- a/traitsui/qt4/csv_list_editor.py +++ b/traitsui/qt4/csv_list_editor.py @@ -24,21 +24,22 @@ # Imports: #------------------------------------------------------------------------------ -from .text_editor import (SimpleEditor, CustomEditor, - ReadonlyEditor, TextEditor) +from .text_editor import SimpleEditor as QtSimpleEditor +from .text_editor import CustomEditor as QtCustomEditor +from .text_editor import ReadonlyEditor as QtReadonlyEditor from ..editors.csv_list_editor import _prepare_method, _dispose_method -class SimpleEditor(WXSimpleEditor): +class SimpleEditor(QtSimpleEditor): """ Simple Editor style for CSVListEditor. """ prepare = _prepare_method dispose = _dispose_method -class CustomEditor(WXCustomEditor): +class CustomEditor(QtCustomEditor): """ Custom Editor style for CSVListEditor. """ prepare = _prepare_method dispose = _dispose_method -class ReadonlyEditor(WXReadonlyEditor): +class ReadonlyEditor(QtReadonlyEditor): """ Readonly Editor style for CSVListEditor. """ prepare = _prepare_method dispose = _dispose_method diff --git a/traitsui/wx/csv_list_editor.py b/traitsui/wx/csv_list_editor.py index 356c38d8d..8da92f8e9 100644 --- a/traitsui/wx/csv_list_editor.py +++ b/traitsui/wx/csv_list_editor.py @@ -25,8 +25,9 @@ # Imports: #------------------------------------------------------------------------------ -from .text_editor import (SimpleEditor, CustomEditor, - ReadonlyEditor, TextEditor) +from .text_editor import SimpleEditor as WXSimpleEditor +from .text_editor import CustomEditor as WXCustomEditor +from .text_editor import ReadonlyEditor as WXReadonlyEditor from ..editors.csv_list_editor import _prepare_method, _dispose_method class SimpleEditor(WXSimpleEditor): From eedbbb6fc36317fd1e2a7fe86e8aafde7a9dcb6f Mon Sep 17 00:00:00 2001 From: Pietro Berkes Date: Mon, 16 Jan 2012 09:10:25 +0000 Subject: [PATCH 04/12] DOC: correct typos --- traitsui/editors/csv_list_editor.py | 2 +- traitsui/qt4/csv_list_editor.py | 2 +- traitsui/wx/csv_list_editor.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/traitsui/editors/csv_list_editor.py b/traitsui/editors/csv_list_editor.py index c02c44ad5..cad8fc653 100644 --- a/traitsui/editors/csv_list_editor.py +++ b/traitsui/editors/csv_list_editor.py @@ -155,7 +155,7 @@ def _validate_range_value(range_object, object, name, value): def _prepare_method(cls, parent): """ Unbound implementation of the prepare editor method to add a change notification hook in the items of the list before calling - the parent prepere method of the parent class. + the parent prepare method of the parent class. """ name = cls.extended_name diff --git a/traitsui/qt4/csv_list_editor.py b/traitsui/qt4/csv_list_editor.py index 9a9e2d9d4..8add9182c 100644 --- a/traitsui/qt4/csv_list_editor.py +++ b/traitsui/qt4/csv_list_editor.py @@ -1,6 +1,6 @@ #------------------------------------------------------------------------------ # -# Copyright (c) 2005, Enthought, Inc. +# Copyright (c) 2012, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD diff --git a/traitsui/wx/csv_list_editor.py b/traitsui/wx/csv_list_editor.py index 8da92f8e9..def2036b4 100644 --- a/traitsui/wx/csv_list_editor.py +++ b/traitsui/wx/csv_list_editor.py @@ -1,6 +1,6 @@ #------------------------------------------------------------------------------ # -# Copyright (c) 2005, Enthought, Inc. +# Copyright (c) 2012, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD From 24f33dc8978a968a440c72fb3dd6906eddce6876 Mon Sep 17 00:00:00 2001 From: Pietro Berkes Date: Mon, 16 Jan 2012 09:37:02 +0000 Subject: [PATCH 05/12] NEW: add press_ok_button utililty to tests --- traitsui/tests/_tools.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/traitsui/tests/_tools.py b/traitsui/tests/_tools.py index 0a83966a7..203750a44 100644 --- a/traitsui/tests/_tools.py +++ b/traitsui/tests/_tools.py @@ -76,6 +76,25 @@ def get_children(node): return node.children() +def press_ok_button(ui): + """Press the OK button in a wx or qt dialog.""" + + if ETSConfig.toolkit == 'wx': + import wx + + ok_button = ui.control.FindWindowByName('button') + click_event = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, + ok_button.GetId()) + ok_button.ProcessEvent(click_event) + + elif ETSConfig.toolkit == 'qt4': + from pyface import qt + + # press the OK button and close the dialog + ok_button = ui.control.findChild(qt.QtGui.QPushButton) + ok_button.click() + + # ######### Debug tools def apply_on_children(func, node, _level=0): From 976e58d507534887008d2803e63b63156c9b1fb3 Mon Sep 17 00:00:00 2001 From: Pietro Berkes Date: Mon, 16 Jan 2012 09:37:45 +0000 Subject: [PATCH 06/12] TST: turn Ioannis' example into test for CSVListEditor --- traitsui/tests/test_csv_editor.py | 52 +++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 traitsui/tests/test_csv_editor.py diff --git a/traitsui/tests/test_csv_editor.py b/traitsui/tests/test_csv_editor.py new file mode 100644 index 000000000..0abff7f38 --- /dev/null +++ b/traitsui/tests/test_csv_editor.py @@ -0,0 +1,52 @@ +from traits.api import HasTraits, List, Float, Instance +from traitsui.api import View, Item, CSVListEditor, ModelView + +from _tools import * + + +class ListOfFloats(HasTraits): + data = List(Float) + + +class ListOfFloatsWithCSVEditor(ModelView): + model = Instance(ListOfFloats) + + traits_view = View( + Item(label="Close the window to append data"), + Item('model.data', editor = CSVListEditor()), + buttons = ['OK'] + ) + + +def test_csv_editor_disposal(): + # Bug: CSVListEditor does not un-hook the traits notifications after its + # disposal, causing errors when the hooked data is accessed after + # the window is closed + + try: + with store_exceptions_on_all_threads(): + list_of_floats = ListOfFloats(data=[1,2,3]) + csv_view = ListOfFloatsWithCSVEditor(model=list_of_floats) + ui = csv_view.edit_traits() + press_ok_button(ui) + + # raise an exception if still hooked + list_of_floats.data.append(2) + + except AttributeError: + # if all went well, we should not be here + assert False, "AttributeError raised" + + +if __name__ == '__main__': + # Executing the file opens the dialog for manual testing + list_of_floats = ListOfFloats(data=[1,2,3]) + csv_view = ListOfFloatsWithCSVEditor(model=list_of_floats) + csv_view.configure_traits() + + # this call will raise an AttributeError in commit + # 4ecb2fa8f0ef385d55a2a4062d821b0415777973 + # This is because the editor does not un-hook the traits notifications + # after its disposal + list_of_floats.data.append(2) + print list_of_floats.data From add59585319570a087dc24c5c8442ffbf4ef8122 Mon Sep 17 00:00:00 2001 From: Pietro Berkes Date: Mon, 16 Jan 2012 11:01:46 +0000 Subject: [PATCH 07/12] FIX: capture exceptions that Traits swallows Fix context manager that captures all exceptions in tests._tools. Traits defines traits.trait_notifier.handle_exception, which logs exceptions to console without re-raising them. --- traitsui/tests/_tools.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/traitsui/tests/_tools.py b/traitsui/tests/_tools.py index 203750a44..9d5b73cd8 100644 --- a/traitsui/tests/_tools.py +++ b/traitsui/tests/_tools.py @@ -6,6 +6,7 @@ import traceback from traits.etsconfig.api import ETSConfig +import traits.trait_notifiers # ######### Testing tools @@ -13,23 +14,38 @@ def store_exceptions_on_all_threads(): """Context manager that captures all exceptions, even those coming from the UI thread. On exit, the first exception is raised (if any). + + It also temporarily overwrites the global function + traits.trait_notifier.handle_exception , which logs exceptions to + console without re-raising them by default. """ exceptions = [] - def excepthook(type, value, tb): - exceptions.append(value) + def _print_uncaught_exception(type, value, tb): message = 'Uncaught exception:\n' message += ''.join(traceback.format_exception(type, value, tb)) print message + def excepthook(type, value, tb): + exceptions.append(value) + _print_uncaught_exception(type, value, tb) + + def handle_exception(object, trait_name, old, new): + type, value, tb = sys.exc_info() + exceptions.append(value) + _print_uncaught_exception(type, value, tb) + + _original_handle_exception = traits.trait_notifiers.handle_exception try: sys.excepthook = excepthook + traits.trait_notifiers.handle_exception = handle_exception yield finally: if len(exceptions) > 0: raise exceptions[0] sys.excepthook = sys.__excepthook__ + traits.trait_notifiers.handle_exception = _original_handle_exception def skip_if_not_backend(test_func, backend_name=''): From 8bdc3f1d184a379300823c4fd79f61d58a22601e Mon Sep 17 00:00:00 2001 From: Pietro Berkes Date: Mon, 16 Jan 2012 11:03:36 +0000 Subject: [PATCH 08/12] ENH: all tests now use _tools.press_ok_button --- traitsui/tests/test_csv_editor.py | 8 ++++++-- traitsui/tests/test_range_editor_spinner.py | 17 +++-------------- traitsui/tests/test_range_editor_text.py | 7 +------ traitsui/tests/test_ui.py | 2 -- 4 files changed, 10 insertions(+), 24 deletions(-) diff --git a/traitsui/tests/test_csv_editor.py b/traitsui/tests/test_csv_editor.py index 0abff7f38..49680e3d3 100644 --- a/traitsui/tests/test_csv_editor.py +++ b/traitsui/tests/test_csv_editor.py @@ -1,5 +1,9 @@ -from traits.api import HasTraits, List, Float, Instance -from traitsui.api import View, Item, CSVListEditor, ModelView +from traits.has_traits import HasTraits +from traits.trait_types import Float, List, Instance +from traitsui.handler import ModelView +from traitsui.view import View +from traitsui.item import Item +from traitsui.editors.csv_list_editor import CSVListEditor from _tools import * diff --git a/traitsui/tests/test_range_editor_spinner.py b/traitsui/tests/test_range_editor_spinner.py index ab767bc80..e712d0793 100644 --- a/traitsui/tests/test_range_editor_spinner.py +++ b/traitsui/tests/test_range_editor_spinner.py @@ -37,8 +37,6 @@ def test_wx_spin_control_editing_should_not_crash(): # Bug: when editing the text part of a spin control box, pressing # the OK button raises an AttributeError on Mac OS X - import wx - try: with store_exceptions_on_all_threads(): num = NumberWithSpinnerEditor() @@ -61,10 +59,7 @@ def test_wx_spin_control_editing_should_not_crash(): spintxt.SetValue('4') # press the OK button and close the dialog - okbutton = ui.control.FindWindowByName('button') - click_event = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, - okbutton.GetId()) - okbutton.ProcessEvent(click_event) + press_ok_button(ui) except AttributeError: # if all went well, we should not be here assert False, "AttributeError raised" @@ -77,8 +72,6 @@ def test_wx_spin_control_editing_does_not_update(): # the OK button does not update the value of the HasTraits class # on Mac OS X - import wx - with store_exceptions_on_all_threads(): num = NumberWithSpinnerEditor() ui = num.edit_traits() @@ -100,10 +93,7 @@ def test_wx_spin_control_editing_does_not_update(): spintxt.SetValue('4') # press the OK button and close the dialog - okbutton = ui.control.FindWindowByName('button') - click_event = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, - okbutton.GetId()) - okbutton.ProcessEvent(click_event) + press_ok_button(ui) # if all went well, the number traits has been updated and its value is 4 assert num.number == 4 @@ -129,8 +119,7 @@ def test_qt_spin_control_editing(): lineedit.setText('4') # press the OK button and close the dialog - okb = ui.control.findChild(qt.QtGui.QPushButton) - okb.click() + press_ok_button(ui) # if all went well, the number traits has been updated and its value is 4 assert num.number == 4 diff --git a/traitsui/tests/test_range_editor_text.py b/traitsui/tests/test_range_editor_text.py index c28810323..99e3348cb 100644 --- a/traitsui/tests/test_range_editor_text.py +++ b/traitsui/tests/test_range_editor_text.py @@ -32,8 +32,6 @@ def test_wx_spin_control_editing(): # the OK button should update the value of the HasTraits class # (tests a bug where this fails with an AttributeError) - import wx - with store_exceptions_on_all_threads(): num = NumberWithTextEditor() ui = num.edit_traits() @@ -45,10 +43,7 @@ def test_wx_spin_control_editing(): textctrl.SetValue('1') # press the OK button and close the dialog - okbutton = ui.control.FindWindowByName('button') - click_event = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, - okbutton.GetId()) - okbutton.ProcessEvent(click_event) + press_ok_button(ui) # the number traits should be between 3 and 8 assert num.number >= 3 and num.number <=8 diff --git a/traitsui/tests/test_ui.py b/traitsui/tests/test_ui.py index d503a1bf6..f6a863289 100644 --- a/traitsui/tests/test_ui.py +++ b/traitsui/tests/test_ui.py @@ -2,8 +2,6 @@ Test cases for the UI object. """ -import nose.tools - from traits.has_traits import HasTraits from traits.trait_types import Str, Int import traitsui From 90af4527de2fbf910548b0c918a78cc6f2293823 Mon Sep 17 00:00:00 2001 From: Pietro Berkes Date: Mon, 16 Jan 2012 11:23:14 +0000 Subject: [PATCH 09/12] DOC: add header to test files --- traitsui/tests/_tools.py | 15 +++++++++++++++ traitsui/tests/test_csv_editor.py | 17 ++++++++++++++++- traitsui/tests/test_range_editor_spinner.py | 18 ++++++++++++++++-- traitsui/tests/test_range_editor_text.py | 17 ++++++++++++++++- 4 files changed, 63 insertions(+), 4 deletions(-) diff --git a/traitsui/tests/_tools.py b/traitsui/tests/_tools.py index 9d5b73cd8..f26f5c21d 100644 --- a/traitsui/tests/_tools.py +++ b/traitsui/tests/_tools.py @@ -1,3 +1,18 @@ +#------------------------------------------------------------------------------ +# +# Copyright (c) 2012, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/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 +# +# Author: Pietro Berkes +# Date: Jan 2012 +# +#------------------------------------------------------------------------------ + from functools import partial from contextlib import contextmanager import nose diff --git a/traitsui/tests/test_csv_editor.py b/traitsui/tests/test_csv_editor.py index 49680e3d3..64bdaf76d 100644 --- a/traitsui/tests/test_csv_editor.py +++ b/traitsui/tests/test_csv_editor.py @@ -1,3 +1,18 @@ +#------------------------------------------------------------------------------ +# +# Copyright (c) 2012, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/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 +# +# Author: Pietro Berkes +# Date: Jan 2012 +# +#------------------------------------------------------------------------------ + from traits.has_traits import HasTraits from traits.trait_types import Float, List, Instance from traitsui.handler import ModelView @@ -25,7 +40,7 @@ class ListOfFloatsWithCSVEditor(ModelView): def test_csv_editor_disposal(): # Bug: CSVListEditor does not un-hook the traits notifications after its # disposal, causing errors when the hooked data is accessed after - # the window is closed + # the window is closed (Issue #48) try: with store_exceptions_on_all_threads(): diff --git a/traitsui/tests/test_range_editor_spinner.py b/traitsui/tests/test_range_editor_spinner.py index e712d0793..961f9ae93 100644 --- a/traitsui/tests/test_range_editor_spinner.py +++ b/traitsui/tests/test_range_editor_spinner.py @@ -1,5 +1,20 @@ +#------------------------------------------------------------------------------ +# +# Copyright (c) 2012, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/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 +# +# Author: Pietro Berkes +# Date: Jan 2012 +# +#------------------------------------------------------------------------------ + """ -Test case for bug (wx, Max OS X) +Test case for bug (wx, Mac OS X) Editing the text part of a spin control box and pressing the OK button without de-focusing raises an AttributeError @@ -65,7 +80,6 @@ def test_wx_spin_control_editing_should_not_crash(): assert False, "AttributeError raised" - @skip_if_not_wx def test_wx_spin_control_editing_does_not_update(): # Bug: when editing the text part of a spin control box, pressing diff --git a/traitsui/tests/test_range_editor_text.py b/traitsui/tests/test_range_editor_text.py index 99e3348cb..8f0b51423 100644 --- a/traitsui/tests/test_range_editor_text.py +++ b/traitsui/tests/test_range_editor_text.py @@ -1,5 +1,20 @@ +#------------------------------------------------------------------------------ +# +# Copyright (c) 2012, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/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 +# +# Author: Pietro Berkes +# Date: Jan 2012 +# +#------------------------------------------------------------------------------ + """ -Test case for bug (wx, Max OS X) +Test case for bug (wx, Mac OS X) A RangeEditor in mode 'text' for an Int allows values out of range. """ From 1cb022809585d439b4bc0274d670092127655a3c Mon Sep 17 00:00:00 2001 From: Pietro Berkes Date: Mon, 16 Jan 2012 11:52:58 +0000 Subject: [PATCH 10/12] TST: add functions to test for presence of backend --- traitsui/tests/_tools.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/traitsui/tests/_tools.py b/traitsui/tests/_tools.py index f26f5c21d..bcda30f5a 100644 --- a/traitsui/tests/_tools.py +++ b/traitsui/tests/_tools.py @@ -63,10 +63,14 @@ def handle_exception(object, trait_name, old, new): traits.trait_notifiers.handle_exception = _original_handle_exception +def _is_current_backend(backend_name=''): + return ETSConfig.toolkit == backend_name + + def skip_if_not_backend(test_func, backend_name=''): """Decorator that skip tests if the backend is not the desired one.""" - if ETSConfig.toolkit != backend_name: + if not _is_current_backend(backend_name): # preserve original name so that it appears in the report orig_name = test_func.__name__ def test_func(): @@ -76,6 +80,12 @@ def test_func(): return test_func +#: Return True if current backend is 'wx' +is_current_backend_wx = partial(_is_current_backend, backend_name='wx') + +#: Return True if current backend is 'qt4' +is_current_backend_qt4 = partial(_is_current_backend, backend_name='qt4') + #: Test decorator: Skip test if backend is not 'wx' skip_if_not_wx = partial(skip_if_not_backend, backend_name='wx') @@ -139,12 +149,22 @@ def apply_on_children(func, node, _level=0): def wx_print_names(node): """Print the name and id of `node` and its children. + + Use as:: + + >>> ui = xxx.edit_traits() + >>> wx_print_names(ui.control) """ apply_on_children(lambda n: (n.GetName(), n.GetId()), node) def qt_print_names(node): """Print the name of `node` and its children. + + Use as:: + + >>> ui = xxx.edit_traits() + >>> wx_print_names(ui.control) """ apply_on_children(lambda n: n.objectName(), node) From 9371cde1df9d6e8800034881728bdb786026388e Mon Sep 17 00:00:00 2001 From: Pietro Berkes Date: Mon, 16 Jan 2012 11:53:45 +0000 Subject: [PATCH 11/12] TST: add test for CSV editor test that the editor correctly handles external changes in the data --- traitsui/tests/test_csv_editor.py | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/traitsui/tests/test_csv_editor.py b/traitsui/tests/test_csv_editor.py index 64bdaf76d..4eaf02d49 100644 --- a/traitsui/tests/test_csv_editor.py +++ b/traitsui/tests/test_csv_editor.py @@ -19,6 +19,7 @@ from traitsui.view import View from traitsui.item import Item from traitsui.editors.csv_list_editor import CSVListEditor +import traitsui.editors.csv_list_editor as csv_list_editor from _tools import * @@ -57,6 +58,41 @@ def test_csv_editor_disposal(): assert False, "AttributeError raised" +def test_csv_editor_external_append(): + # Behavior: CSV editor is notified when an element is appended to the + # list externally + + def _wx_get_text_value(ui): + txt_ctrl = ui.control.FindWindowByName('text') + return txt_ctrl.GetValue() + + def _qt_get_text_value(ui): + from pyface import qt + txt_ctrl = ui.control.findChild(qt.QtGui.QLineEdit) + return txt_ctrl.text() + + with store_exceptions_on_all_threads(): + list_of_floats = ListOfFloats(data=[1.0]) + csv_view = ListOfFloatsWithCSVEditor(model=list_of_floats) + ui = csv_view.edit_traits() + + # add element to list, make sure that editor knows about it + list_of_floats.data.append(3.14) + + # get current value from editor + if is_current_backend_wx(): + value_str = _wx_get_text_value(ui) + elif is_current_backend_qt4(): + value_str = _qt_get_text_value(ui) + else: + raise Exception('Unknown backend', ETSConfig.toolkit) + + expected = csv_list_editor._format_list_str([1.0, 3.14]) + nose.tools.assert_equal(value_str, expected) + + press_ok_button(ui) + + if __name__ == '__main__': # Executing the file opens the dialog for manual testing list_of_floats = ListOfFloats(data=[1,2,3]) From a59ac8d55ff81c6646f9ea2580f429ffbca3b854 Mon Sep 17 00:00:00 2001 From: Pietro Berkes Date: Mon, 16 Jan 2012 11:56:34 +0000 Subject: [PATCH 12/12] TST: small refactor in _tools --- traitsui/tests/_tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/traitsui/tests/_tools.py b/traitsui/tests/_tools.py index bcda30f5a..69743a181 100644 --- a/traitsui/tests/_tools.py +++ b/traitsui/tests/_tools.py @@ -120,7 +120,7 @@ def get_children(node): def press_ok_button(ui): """Press the OK button in a wx or qt dialog.""" - if ETSConfig.toolkit == 'wx': + if is_current_backend_wx(): import wx ok_button = ui.control.FindWindowByName('button') @@ -128,7 +128,7 @@ def press_ok_button(ui): ok_button.GetId()) ok_button.ProcessEvent(click_event) - elif ETSConfig.toolkit == 'qt4': + elif is_current_backend_qt4(): from pyface import qt # press the OK button and close the dialog