Skip to content

Commit

Permalink
Refactored the RangeEditor backend for Qt and Wx so that their behavi…
Browse files Browse the repository at this point in the history
…or becomes more consistent. (Closes issue #1771)

Updated the key_click_text_entry function in wx/_interaction_helpers.py to respond to "Right" or "Left" interaction. (Closes issue #1772)

Replaced the use of possibly insecure eval function with the safer ast.literal_eval in RangeEditor. (See issue #1773)

Fixed a bug on wx where the text style RangeEditor did raise an error whenever the textbox was empty, even if enter was not pressed.

Changed the default low and high attribute of RangeEditor from 0, 1 to Undefined, so that
the limits are from -infinite to +infinite by default. (Closes issue #1775)

Updated SimpleSpinEditor (for both Wx and PyQt) to allow floats and be able to specify the step of the spin control as well as allow low and/or high to be Undefined. Also added the possibility to increase the step value for the spinner by the use of the modifier keys "Shift", "Ctrl" or "Alt". They will increase the step value for the spinner by a factor of 2, 10 and 100, respectively. If a combination of "Shift", "Ctrl", "Alt" -keys are pressed the step increment will be the product of their factors. (Closes issue #1776)
Registered the interaction helper for SimpleSpinEditor for both Qt and Wx: (traitsui/testing/tester/_ui_tester_registry/qt4/_traitsui/range_editor.py,
traitsui/testing/tester/_ui_tester_registry/wx/_traitsui/range_editor.py)

Made the behavior of LargeRangeSliderEditor more userfriendly. It now allows low and/or high to be Undefined. Its increase/decrease method also increase/decrease the trait value of a factor 10 so that the value remains the same if the increase range button is pushed once followed by a push on the decrease range button (given that the value did not exceed the boundaries low or high). (Closes issue #1777)

Added a show_error_dialog attribute to the EditorFactory class in editor_factory.py, which will disable the error popup dialog when set to False.
Set the show_error_dialog attribute to False in RangeEditorDemo.py and test_range_editor_text.py in order to avoid that the popup error dialog blocks and eventually makes the tests hang indefinitely. (Closes issue #1778)

Changed the default behavior of mode attribute in the RangeEditor. Now the the 'View' conforms to the view explicitly set by the mode attribute unless it is set to 'auto'. Eg. if the mode is set to 'spinner' you will always get a SimpleSpinEditor as view. (Closes issue #1779)

Updated the mouse_click function in wx/_interaction_helpers.py to set focus to current control. (Closes issue #1780)
  • Loading branch information
pbrod committed Nov 23, 2021
1 parent 87dceb2 commit 6e83a0e
Show file tree
Hide file tree
Showing 15 changed files with 1,203 additions and 1,309 deletions.
4 changes: 3 additions & 1 deletion traitsui/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@

from .item import Item

UNITTESTING = False
# Reference to an EditorFactory object
factory_trait = Instance(EditorFactory)

Expand Down Expand Up @@ -164,7 +165,8 @@ def error(self, excp):
excp : Exception
The exception which occurred.
"""
pass
if UNITTESTING:
raise excp

def set_focus(self):
"""Assigns focus to the editor's underlying toolkit widget.
Expand Down
3 changes: 3 additions & 0 deletions traitsui/editor_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ class EditorFactory(HasPrivateTraits):
#: The editor class to use for 'readonly' style views.
readonly_editor_class = Property()

#: Show the error dialog when an error occur.
show_error_dialog = Bool(True)

def __init__(self, *args, **traits):
"""Initializes the factory object."""
HasPrivateTraits.__init__(self, **traits)
Expand Down
23 changes: 9 additions & 14 deletions traitsui/editors/range_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

""" Defines the range editor factory for all traits user interface toolkits.
"""
import ast
import warnings

from types import CodeType
Expand Down Expand Up @@ -130,12 +131,6 @@ def init(self, handler=None):
self.high = eval(handler._high)
else:
self.high = handler._high
else:
if (self.low is None) and (self.low_name == ""):
self.low = 0.0

if (self.high is None) and (self.high_name == ""):
self.high = 1.0

def _get_low(self):
return self._low
Expand Down Expand Up @@ -217,25 +212,25 @@ def _get_simple_editor_class(self):
The type of editor depends on the type and extent of the range being
edited:
* One end of range is unspecified: RangeTextEditor
* **mode** is specified and not 'auto': editor corresponding to
**mode**
* One end of range is unspecified: RangeTextEditor
* Floating point range with extent > 100: LargeRangeSliderEditor
* Integer range or floating point range with extent <= 100:
SimpleSliderEditor
* All other cases: SimpleSpinEditor
"""
low, high, is_float = self._low_value, self._high_value, self.is_float

if self.mode != "auto":
return toolkit_object("range_editor:SimpleEditorMap")[self.mode]

if (low is None) or (high is None):
return toolkit_object("range_editor:RangeTextEditor")

if (not is_float) and (abs(high - low) > 1000000000):
return toolkit_object("range_editor:RangeTextEditor")

if self.mode != "auto":
return toolkit_object("range_editor:SimpleEditorMap")[self.mode]

if is_float and (abs(high - low) > 100):
return toolkit_object("range_editor:LargeRangeSliderEditor")

Expand All @@ -250,21 +245,21 @@ def _get_custom_editor_class(self):
The type of editor depends on the type and extent of the range being
edited:
* One end of range is unspecified: RangeTextEditor
* **mode** is specified and not 'auto': editor corresponding to
**mode**
* One end of range is unspecified: RangeTextEditor
* Floating point range: Same as "simple" style
* Integer range with extent > 15: Same as "simple" style
* Integer range with extent <= 15: CustomEnumEditor
"""
low, high, is_float = self._low_value, self._high_value, self.is_float
if (low is None) or (high is None):
return toolkit_object("range_editor:RangeTextEditor")

if self.mode != "auto":
return toolkit_object("range_editor:CustomEditorMap")[self.mode]

if (low is None) or (high is None):
return toolkit_object("range_editor:RangeTextEditor")

if is_float or (abs(high - low) > 15):
return self.simple_editor_class

Expand Down
40 changes: 35 additions & 5 deletions traitsui/examples/demo/Standard_Editors/RangeEditor_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,48 @@
.. _RangeEditor API docs: https://docs.enthought.com/traitsui/api/traitsui.editors.range_editor.html#traitsui.editors.range_editor.RangeEditor
"""

from traits.api import HasTraits, Range
from traits.api import HasTraits, Range as _Range
from traitsui.api import Item, Group, View


# TODO: Update traits.api.Range with the following in order to avoid the popup-error-dialog.
# TODO: Remove redefinition of Range here once the traits.api.Range is updated.
class Range(_Range):
def create_editor(self):
""" Returns the default UI editor for the trait.
"""
# fixme: Needs to support a dynamic range editor.

auto_set = self.auto_set
if auto_set is None:
auto_set = True
show_error_dialog = self.show_error_dialog
if show_error_dialog is None:
show_error_dialog = True

from traitsui.api import RangeEditor
return RangeEditor(
self,
mode=self.mode or "auto",
cols=self.cols or 3,
auto_set=auto_set,
enter_set=self.enter_set or False,
low_label=self.low or "",
high_label=self.high or "",
low_name=self._low_name,
high_name=self._high_name,
show_error_dialog=show_error_dialog
)


class RangeEditorDemo(HasTraits):
"""Defines the RangeEditor demo class."""

# Define a trait for each of four range variants:
small_int_range = Range(1, 16)
medium_int_range = Range(1, 25)
large_int_range = Range(1, 150)
float_range = Range(0.0, 150.0)
small_int_range = Range(1, 16, show_error_dialog=False)
medium_int_range = Range(1, 25, show_error_dialog=False)
large_int_range = Range(1, 150, show_error_dialog=False)
float_range = Range(0.0, 150.0, show_error_dialog=False)

# RangeEditor display for narrow integer Range traits (< 17 wide):
int_range_group1 = Group(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,18 +129,19 @@ def test_run_demo(self):
simple_float_slider.perform(KeyClick("Page Up"))
self.assertEqual(demo.float_range, 1.000)
simple_float_text = simple_float.locate(Textbox())
for _ in range(3):
displayed = simple_float_text.inspect(DisplayedText())
for _ in range(len(displayed) - 2):
simple_float_text.perform(KeyClick("Backspace"))
simple_float_text.perform(KeyClick("5"))
simple_float_text.perform(KeyClick("Enter"))
self.assertEqual(demo.float_range, 1.5)

custom_float_slider = custom_float.locate(Slider())
# after the trait is set to 1.5 above, the active range shown by
# the LargeRangeSliderEditor for the custom style is [0,11.500]
# so a page down is now a decrement of 1.15
# the LargeRangeSliderEditor for the custom style is [0,10.00]
# so a page down is now a decrement of 1.0
custom_float_slider.perform(KeyClick("Page Down"))
self.assertEqual(round(demo.float_range, 2), 0.35)
self.assertEqual(round(demo.float_range, 2), 0.5)
custom_float_text = custom_float.locate(Textbox())
for _ in range(5):
custom_float_text.perform(KeyClick("Backspace"))
Expand Down
34 changes: 18 additions & 16 deletions traitsui/qt4/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,22 +67,24 @@ def update_editor(self):

def error(self, excp):
"""Handles an error that occurs while setting the object's trait value."""
# Make sure the control is a widget rather than a layout.
if isinstance(self.control, QtGui.QLayout):
control = self.control.parentWidget()
else:
control = self.control

message_box = QtGui.QMessageBox(
QtGui.QMessageBox.Information,
self.description + " value error",
str(excp),
buttons=QtGui.QMessageBox.Ok,
parent=control,
)
message_box.setTextFormat(QtCore.Qt.PlainText)
message_box.setEscapeButton(QtGui.QMessageBox.Ok)
message_box.exec_()
super().error(excp)
if self.factory.show_error_dialog:
# Make sure the control is a widget rather than a layout.
if isinstance(self.control, QtGui.QLayout):
control = self.control.parentWidget()
else:
control = self.control

message_box = QtGui.QMessageBox(
QtGui.QMessageBox.Information,
self.description + " value error",
str(excp),
buttons=QtGui.QMessageBox.Ok,
parent=control,
)
message_box.setTextFormat(QtCore.Qt.PlainText)
message_box.setEscapeButton(QtGui.QMessageBox.Ok)
message_box.exec_()

def set_tooltip_text(self, control, text):
"""Sets the tooltip for a specified control."""
Expand Down
Loading

0 comments on commit 6e83a0e

Please sign in to comment.