1# ------------------------------------------------------------------------------
2#
3#  Copyright (c) 2008, Enthought, Inc.
4#  All rights reserved.
5#
6#  This software is provided without warranty under the terms of the BSD
7#  license included in LICENSE.txt and may be redistributed only
8#  under the conditions described in the aforementioned license.  The license
9#  is also available online at http://www.enthought.com/licenses/BSD.txt
10#
11#  Thanks for using Enthought open source!
12#
13#  Author: David C. Morrill
14#  Date:   10/21/2004
15#
16# ------------------------------------------------------------------------------
17""" Defines the range editor factory for all traits user interface toolkits.
18"""
19
20
21
22from types import CodeType
23
24
25
26from traits.api import (
27    CTrait,
28    Property,
29    Range,
30    Enum,
31    Str,
32    Int,
33    Any,
34    Str,
35    Bool,
36    Undefined,
37)
38
39# CIRCULAR IMPORT FIXME: Importing from the source rather than traits.ui.api
40# to avoid circular imports, as this EditorFactory will be part of
41# traits.ui.api as well.
42from ..view import View
43
44from ..editor_factory import EditorFactory
45
46from ..toolkit import toolkit_object
47
48# -------------------------------------------------------------------------
49#  'ToolkitEditorFactory' class:
50# -------------------------------------------------------------------------
51
52
53class ToolkitEditorFactory(EditorFactory):
54    """ Editor factory for range editors.
55    """
56
57    # -------------------------------------------------------------------------
58    #  Trait definitions:
59    # -------------------------------------------------------------------------
60
61    #: Number of columns when displayed as an enumeration
62    cols = Range(1, 20)
63
64    #: Is user input set on every keystroke?
65    auto_set = Bool(True)
66
67    #: Is user input set when the Enter key is pressed?
68    enter_set = Bool(False)
69
70    #: Label for the low end of the range
71    low_label = Str()
72
73    #: Label for the high end of the range
74    high_label = Str()
75
76    #: FIXME: This is supported only in the wx backend so far.
77    #: The width of the low and high labels
78    label_width = Int()
79
80    #: The name of an [object.]trait that defines the low value for the range
81    low_name = Str()
82
83    #: The name of an [object.]trait that defines the high value for the range
84    high_name = Str()
85
86    #: Formatting string used to format value and labels
87    format = Str("%s")
88
89    #: Is the range for floating pointer numbers (vs. integers)?
90    is_float = Bool(Undefined)
91
92    #: Function to evaluate floats/ints when they are assigned to an object
93    #: trait
94    evaluate = Any()
95
96    #: The object trait containing the function used to evaluate floats/ints
97    evaluate_name = Str()
98
99    #: Low end of range
100    low = Property()
101
102    #: High end of range
103    high = Property()
104
105    #: Display mode to use
106    mode = Enum(
107        "auto", "slider", "xslider", "spinner", "enum", "text", "logslider"
108    )
109
110    # -------------------------------------------------------------------------
111    #  Traits view definition:
112    # -------------------------------------------------------------------------
113
114    traits_view = View(
115        [
116            ["low", "high", "|[Range]"],
117            ["low_label{Low}", "high_label{High}", "|[Range Labels]"],
118            [
119                "auto_set{Set automatically}",
120                "enter_set{Set on enter key pressed}",
121                "is_float{Is floating point range}",
122                "-[Options]>",
123            ],
124            ["cols", "|[Number of columns for integer custom style]<>"],
125        ]
126    )
127
128    def init(self, handler=None):
129        """ Performs any initialization needed after all constructor traits
130            have been set.
131        """
132        if handler is not None:
133            if isinstance(handler, CTrait):
134                handler = handler.handler
135
136            if self.low_name == "":
137                if isinstance(handler._low, CodeType):
138                    self.low = eval(handler._low)
139                else:
140                    self.low = handler._low
141
142            if self.high_name == "":
143                if isinstance(handler._low, CodeType):
144                    self.high = eval(handler._high)
145                else:
146                    self.high = handler._high
147        else:
148            if (self.low is None) and (self.low_name == ""):
149                self.low = 0.0
150
151            if (self.high is None) and (self.high_name == ""):
152                self.high = 1.0
153
154    def _get_low(self):
155        return self._low
156
157    def _set_low(self, low):
158        old_low = self._low
159        self._low = low = self._cast(low)
160        if self.is_float is Undefined:
161            self.is_float = isinstance(low, float)
162
163        if (self.low_label == "") or (
164            self.low_label == str(old_low)
165        ):
166            self.low_label = str(low)
167
168    def _get_high(self):
169        return self._high
170
171    def _set_high(self, high):
172        old_high = self._high
173        self._high = high = self._cast(high)
174        if self.is_float is Undefined:
175            self.is_float = isinstance(high, float)
176
177        if (self.high_label == "") or (
178            self.high_label == str(old_high)
179        ):
180            self.high_label = str(high)
181
182    def _cast(self, value):
183        if not isinstance(value, str):
184            return value
185
186        try:
187            return int(value)
188        except ValueError:
189            return float(value)
190
191    # -- Private Methods ------------------------------------------------------
192
193    def _get_low_high(self, ui):
194        """ Returns the low and high values used to determine the initial range.
195        """
196        low, high = self.low, self.high
197
198        if (low is None) and (self.low_name != ""):
199            low = self.named_value(self.low_name, ui)
200            if self.is_float is Undefined:
201                self.is_float = isinstance(low, float)
202
203        if (high is None) and (self.high_name != ""):
204            high = self.named_value(self.high_name, ui)
205            if self.is_float is Undefined:
206                self.is_float = isinstance(high, float)
207
208        if self.is_float is Undefined:
209            self.is_float = True
210
211        return (low, high, self.is_float)
212
213    # -------------------------------------------------------------------------
214    #  Property getters.
215    # -------------------------------------------------------------------------
216    def _get_simple_editor_class(self):
217        """ Returns the editor class to use for a simple style.
218
219        The type of editor depends on the type and extent of the range being
220        edited:
221
222        * One end of range is unspecified: RangeTextEditor
223        * **mode** is specified and not 'auto': editor corresponding to **mode**
224        * Floating point range with extent > 100: LargeRangeSliderEditor
225        * Integer range or floating point range with extent <= 100:
226          SimpleSliderEditor
227        * All other cases: SimpleSpinEditor
228        """
229        low, high, is_float = self._low_value, self._high_value, self.is_float
230
231        if (low is None) or (high is None):
232            return toolkit_object("range_editor:RangeTextEditor")
233
234        if (not is_float) and (abs(high - low) > 1000000000):
235            return toolkit_object("range_editor:RangeTextEditor")
236
237        if self.mode != "auto":
238            return toolkit_object("range_editor:SimpleEditorMap")[self.mode]
239
240        if is_float and (abs(high - low) > 100):
241            return toolkit_object("range_editor:LargeRangeSliderEditor")
242
243        if is_float or (abs(high - low) <= 100):
244            return toolkit_object("range_editor:SimpleSliderEditor")
245
246        return toolkit_object("range_editor:SimpleSpinEditor")
247
248    def _get_custom_editor_class(self):
249        """ Creates a custom style of range editor
250
251        The type of editor depends on the type and extent of the range being
252        edited:
253
254        * One end of range is unspecified: RangeTextEditor
255        * **mode** is specified and not 'auto': editor corresponding to **mode**
256        * Floating point range: Same as "simple" style
257        * Integer range with extent > 15: Same as "simple" style
258        * Integer range with extent <= 15: CustomEnumEditor
259
260        """
261        low, high, is_float = self._low_value, self._high_value, self.is_float
262        if (low is None) or (high is None):
263            return toolkit_object("range_editor:RangeTextEditor")
264
265        if self.mode != "auto":
266            return toolkit_object("range_editor:CustomEditorMap")[self.mode]
267
268        if is_float or (abs(high - low) > 15):
269            return self.simple_editor_class
270
271        return toolkit_object("range_editor:CustomEnumEditor")
272
273    def _get_text_editor_class(self):
274        """Returns the editor class to use for a text style.
275        """
276        return toolkit_object("range_editor:RangeTextEditor")
277
278    # -------------------------------------------------------------------------
279    #  'Editor' factory methods:
280    # -------------------------------------------------------------------------
281
282    def simple_editor(self, ui, object, name, description, parent):
283        """ Generates an editor using the "simple" style.
284        Overridden to set the values of the _low_value, _high_value and
285        is_float traits.
286
287        """
288        self._low_value, self._high_value, self.is_float = self._get_low_high(
289            ui
290        )
291        return super(RangeEditor, self).simple_editor(
292            ui, object, name, description, parent
293        )
294
295    def custom_editor(self, ui, object, name, description, parent):
296        """ Generates an editor using the "custom" style.
297        Overridden to set the values of the _low_value, _high_value and
298        is_float traits.
299
300        """
301        self._low_value, self._high_value, self.is_float = self._get_low_high(
302            ui
303        )
304        return super(RangeEditor, self).custom_editor(
305            ui, object, name, description, parent
306        )
307
308
309# Define the RangeEditor class
310RangeEditor = ToolkitEditorFactory
311