~ubuntu-branches/ubuntu/utopic/python-traitsui/utopic

« back to all changes in this revision

Viewing changes to traitsui/wx/range_editor.py

  • Committer: Bazaar Package Importer
  • Author(s): Varun Hiremath
  • Date: 2011-07-09 13:57:39 UTC
  • Revision ID: james.westby@ubuntu.com-20110709135739-x5u20q86huissmn1
Tags: upstream-4.0.0
ImportĀ upstreamĀ versionĀ 4.0.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#------------------------------------------------------------------------------
 
2
#
 
3
#  Copyright (c) 2005, Enthought, Inc.
 
4
#  All rights reserved.
 
5
#
 
6
#  This software is provided without warranty under the terms of the BSD
 
7
#  license included in enthought/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
 
 
18
""" Defines the various range editors for the wxPython user interface toolkit.
 
19
"""
 
20
 
 
21
#-------------------------------------------------------------------------------
 
22
#  Imports:
 
23
#-------------------------------------------------------------------------------
 
24
 
 
25
import sys
 
26
import wx
 
27
 
 
28
from math \
 
29
    import log10
 
30
 
 
31
from traits.api \
 
32
     import TraitError, Str, Float, Any, Bool
 
33
 
 
34
# FIXME: ToolkitEditorFactory is a proxy class defined here just for backward
 
35
# compatibility. The class has been moved to the
 
36
# traitsui.editors.range_editor file.
 
37
from traitsui.editors.range_editor \
 
38
    import ToolkitEditorFactory
 
39
 
 
40
from editor_factory \
 
41
    import TextEditor
 
42
 
 
43
from editor \
 
44
    import Editor
 
45
 
 
46
from constants \
 
47
    import OKColor, ErrorColor
 
48
 
 
49
from helper \
 
50
    import TraitsUIPanel, Slider
 
51
 
 
52
#-------------------------------------------------------------------------------
 
53
#  'BaseRangeEditor' class:
 
54
#-------------------------------------------------------------------------------
 
55
 
 
56
class BaseRangeEditor ( Editor ):
 
57
    """ The base class for Range editors. Using an evaluate trait, if specified,
 
58
        when assigning numbers the object trait.
 
59
    """
 
60
 
 
61
    #---------------------------------------------------------------------------
 
62
    #  Trait definitions:
 
63
    #---------------------------------------------------------------------------
 
64
 
 
65
    # Function to evaluate floats/ints
 
66
    evaluate = Any
 
67
 
 
68
    #---------------------------------------------------------------------------
 
69
    #  Sets the associated object trait's value:
 
70
    #---------------------------------------------------------------------------
 
71
 
 
72
    def _set_value ( self, value ):
 
73
        if self.evaluate is not None:
 
74
            value = self.evaluate( value )
 
75
        Editor._set_value( self, value )
 
76
 
 
77
#-------------------------------------------------------------------------------
 
78
#  'SimpleSliderEditor' class:
 
79
#-------------------------------------------------------------------------------
 
80
 
 
81
class SimpleSliderEditor ( BaseRangeEditor ):
 
82
    """ Simple style of range editor that displays a slider and a text field.
 
83
 
 
84
    The user can set a value either by moving the slider or by typing a value
 
85
    in the text field.
 
86
    """
 
87
 
 
88
    #---------------------------------------------------------------------------
 
89
    #  Trait definitions:
 
90
    #---------------------------------------------------------------------------
 
91
 
 
92
    # Low value for the slider range
 
93
    low = Any
 
94
 
 
95
    # High value for the slider range
 
96
    high = Any
 
97
 
 
98
    # Formatting string used to format value and labels
 
99
    format = Str
 
100
 
 
101
    # Flag indicating that the UI is in the process of being updated
 
102
    ui_changing = Bool( False )
 
103
 
 
104
    #---------------------------------------------------------------------------
 
105
    #  Finishes initializing the editor by creating the underlying toolkit
 
106
    #  widget:
 
107
    #---------------------------------------------------------------------------
 
108
 
 
109
    def init ( self, parent ):
 
110
        """ Finishes initializing the editor by creating the underlying toolkit
 
111
            widget.
 
112
        """
 
113
        factory = self.factory
 
114
        if not factory.low_name:
 
115
            self.low = factory.low
 
116
 
 
117
        if not factory.high_name:
 
118
            self.high = factory.high
 
119
 
 
120
        self.format = factory.format
 
121
 
 
122
        self.evaluate = factory.evaluate
 
123
        self.sync_value( factory.evaluate_name, 'evaluate', 'from' )
 
124
 
 
125
        size        = wx.DefaultSize
 
126
        if factory.label_width > 0:
 
127
            size = wx.Size( factory.label_width, 20 )
 
128
 
 
129
        self.sync_value( factory.low_name,  'low',  'from' )
 
130
        self.sync_value( factory.high_name, 'high', 'from' )
 
131
        self.control = panel = TraitsUIPanel( parent, -1 )
 
132
        sizer  = wx.BoxSizer( wx.HORIZONTAL )
 
133
        fvalue = self.value
 
134
 
 
135
        if not (self.low <= fvalue <= self.high):
 
136
            fvalue_text = ''
 
137
            fvalue = self.low
 
138
        else:
 
139
            try:
 
140
                fvalue_text = self.format % fvalue
 
141
            except (ValueError, TypeError), e:
 
142
                fvalue_text = ''
 
143
 
 
144
        ivalue = self._convert_to_slider(fvalue)
 
145
 
 
146
        self._label_lo = wx.StaticText( panel, -1, '999999', size = size,
 
147
                                style = wx.ALIGN_RIGHT | wx.ST_NO_AUTORESIZE )
 
148
        sizer.Add( self._label_lo, 0, wx.ALIGN_CENTER )
 
149
        panel.slider = slider = Slider( panel, -1, ivalue, 0, 10000,
 
150
                                   size   = wx.Size( 80, 20 ),
 
151
                                   style  = wx.SL_HORIZONTAL | wx.SL_AUTOTICKS )
 
152
        slider.SetTickFreq( 1000, 1 )
 
153
        slider.SetPageSize( 1000 )
 
154
        slider.SetLineSize( 100 )
 
155
        wx.EVT_SCROLL( slider, self.update_object_on_scroll )
 
156
        sizer.Add( slider, 1, wx.EXPAND )
 
157
        self._label_hi = wx.StaticText( panel, -1, '999999', size = size )
 
158
        sizer.Add( self._label_hi, 0, wx.ALIGN_CENTER )
 
159
 
 
160
        panel.text = text = wx.TextCtrl( panel, -1, fvalue_text,
 
161
                                         size  = wx.Size( 56, 20 ),
 
162
                                         style = wx.TE_PROCESS_ENTER )
 
163
        wx.EVT_TEXT_ENTER( panel, text.GetId(), self.update_object_on_enter )
 
164
        wx.EVT_KILL_FOCUS( text, self.update_object_on_enter )
 
165
 
 
166
        sizer.Add( text, 0, wx.LEFT | wx.EXPAND, 4 )
 
167
 
 
168
        low_label = factory.low_label
 
169
        if factory.low_name != '':
 
170
            low_label = self.format % self.low
 
171
 
 
172
        high_label = factory.high_label
 
173
        if factory.high_name != '':
 
174
            high_label = self.format % self.high
 
175
 
 
176
        self._label_lo.SetLabel( low_label )
 
177
        self._label_hi.SetLabel( high_label )
 
178
        self.set_tooltip( slider )
 
179
        self.set_tooltip( self._label_lo )
 
180
        self.set_tooltip( self._label_hi )
 
181
        self.set_tooltip( text )
 
182
 
 
183
        # Set-up the layout:
 
184
        panel.SetSizerAndFit( sizer )
 
185
 
 
186
    #---------------------------------------------------------------------------
 
187
    #  Handles the user changing the current slider value:
 
188
    #---------------------------------------------------------------------------
 
189
 
 
190
    def update_object_on_scroll ( self, event ):
 
191
        """ Handles the user changing the current slider value.
 
192
        """
 
193
        value = self._convert_from_slider(event.GetPosition())
 
194
        event_type = event.GetEventType()
 
195
        if ((event_type == wx.wxEVT_SCROLL_ENDSCROLL) or
 
196
            (self.factory.auto_set and
 
197
             (event_type == wx.wxEVT_SCROLL_THUMBTRACK)) or
 
198
            (self.factory.enter_set and
 
199
             (event_type == wx.wxEVT_SCROLL_THUMBRELEASE))):
 
200
            try:
 
201
                self.ui_changing = True
 
202
                self.control.text.SetValue( self.format % value )
 
203
                self.value = value
 
204
            except TraitError:
 
205
                pass
 
206
            finally:
 
207
                self.ui_changing = False
 
208
 
 
209
    #---------------------------------------------------------------------------
 
210
    #  Handle the user pressing the 'Enter' key in the edit control:
 
211
    #---------------------------------------------------------------------------
 
212
 
 
213
    def update_object_on_enter ( self, event ):
 
214
        """ Handles the user pressing the Enter key in the text field.
 
215
        """
 
216
 
 
217
        # There are cases where this method is called with self.control == None.
 
218
        if self.control is None:
 
219
            return
 
220
 
 
221
        try:
 
222
            try:
 
223
                value = self.control.text.GetValue().strip()
 
224
                if self.factory.is_float:
 
225
                    value = float(value)
 
226
                else:
 
227
                    value = int(value)
 
228
            except Exception, ex:
 
229
                # The user entered something that didn't eval as a number (e.g., 'foo').
 
230
                # Pretend it didn't happen (i.e. do not change self.value).
 
231
                value = self.value
 
232
                self.control.text.SetValue( str( value ) )
 
233
 
 
234
            self.value = value
 
235
            if not self.ui_changing:
 
236
                self.control.slider.SetValue(
 
237
                     self._convert_to_slider(self.value) )
 
238
            self.control.text.SetBackgroundColour(OKColor)
 
239
            self.control.text.Refresh()
 
240
            if self._error is not None:
 
241
                self._error     = None
 
242
                self.ui.errors -= 1
 
243
        except TraitError:
 
244
            pass
 
245
 
 
246
    #---------------------------------------------------------------------------
 
247
    #  Handles an error that occurs while setting the object's trait value:
 
248
    #---------------------------------------------------------------------------
 
249
 
 
250
    def error ( self, excp ):
 
251
        """ Handles an error that occurs while setting the object's trait value.
 
252
        """
 
253
        if self._error is None:
 
254
            self._error     = True
 
255
            self.ui.errors += 1
 
256
            super(SimpleSliderEditor, self).error(excp)
 
257
        self.set_error_state( True )
 
258
 
 
259
    #---------------------------------------------------------------------------
 
260
    #  Updates the editor when the object trait changes external to the editor:
 
261
    #---------------------------------------------------------------------------
 
262
 
 
263
    def update_editor ( self ):
 
264
        """ Updates the editor when the object trait changes externally to the
 
265
            editor.
 
266
        """
 
267
        value = self.value
 
268
        try:
 
269
            text = self.format % value
 
270
            1 / (self.low <= value <= self.high)
 
271
        except:
 
272
            text  = ''
 
273
            value = self.low
 
274
 
 
275
        ivalue = self._convert_to_slider( value )
 
276
        self.control.text.SetValue( text )
 
277
        self.control.slider.SetValue( ivalue )
 
278
 
 
279
    def _convert_to_slider(self, value):
 
280
        """ Returns the slider setting corresponding to the user-supplied value.
 
281
        """
 
282
        if self.high > self.low:
 
283
            ivalue = int( (float( value - self.low ) /
 
284
                           (self.high - self.low)) * 10000.0 )
 
285
        else:
 
286
            ivalue = self.low
 
287
        return ivalue
 
288
 
 
289
    def _convert_from_slider(self, ivalue):
 
290
        """ Returns the float or integer value corresponding to the slider
 
291
        setting.
 
292
        """
 
293
        value = self.low + ((float( ivalue ) / 10000.0) *
 
294
                            (self.high - self.low))
 
295
        if not self.factory.is_float:
 
296
            value = int(round(value))
 
297
        return value
 
298
 
 
299
    #---------------------------------------------------------------------------
 
300
    #  Returns the editor's control for indicating error status:
 
301
    #---------------------------------------------------------------------------
 
302
 
 
303
    def get_error_control ( self ):
 
304
        """ Returns the editor's control for indicating error status.
 
305
        """
 
306
        return self.control.text
 
307
 
 
308
    #---------------------------------------------------------------------------
 
309
    #  Handles the 'low'/'high' traits being changed:
 
310
    #---------------------------------------------------------------------------
 
311
 
 
312
    def _low_changed ( self, low ):
 
313
        if self.value < low:
 
314
            if self.factory.is_float:
 
315
                self.value = float( low )
 
316
            else:
 
317
                self.value = int( low )
 
318
 
 
319
        if self._label_lo is not None:
 
320
            self._label_lo.SetLabel( self.format % low  )
 
321
            self.update_editor()
 
322
 
 
323
    def _high_changed ( self, high ):
 
324
        if self.value > high:
 
325
            if self.factory.is_float:
 
326
                self.value = float( high )
 
327
            else:
 
328
                self.value = int( high )
 
329
 
 
330
        if self._label_hi is not None:
 
331
            self._label_hi.SetLabel( self.format % high  )
 
332
            self.update_editor()
 
333
 
 
334
 
 
335
#-------------------------------------------------------------------------------
 
336
class LogRangeSliderEditor ( SimpleSliderEditor ):
 
337
#-------------------------------------------------------------------------------
 
338
    """ A slider editor for log-spaced values
 
339
    """
 
340
 
 
341
    def _convert_to_slider(self, value):
 
342
        """ Returns the slider setting corresponding to the user-supplied value.
 
343
        """
 
344
        value = max(value, self.low)
 
345
        ivalue = int( (log10(value) - log10(self.low)) /
 
346
                      (log10(self.high) - log10(self.low)) * 10000.0)
 
347
        return ivalue
 
348
 
 
349
    def _convert_from_slider(self, ivalue):
 
350
        """ Returns the float or integer value corresponding to the slider
 
351
        setting.
 
352
        """
 
353
        value = float( ivalue ) / 10000.0 * (log10(self.high) -log10(self.low))
 
354
        # Do this to handle floating point errors, where fvalue may exceed
 
355
        # self.high.
 
356
        fvalue = min(self.low*10**(value), self.high)
 
357
        if not self.factory.is_float:
 
358
            fvalue = int(round(fvalue))
 
359
        return fvalue
 
360
 
 
361
#-------------------------------------------------------------------------------
 
362
#  'LargeRangeSliderEditor' class:
 
363
#-------------------------------------------------------------------------------
 
364
 
 
365
class LargeRangeSliderEditor ( BaseRangeEditor ):
 
366
    """ A slider editor for large ranges.
 
367
 
 
368
       The editor displays a slider and a text field. A subset of the total
 
369
       range is displayed in the slider; arrow buttons at each end of the
 
370
       slider let the user move the displayed range higher or lower.
 
371
    """
 
372
 
 
373
    #---------------------------------------------------------------------------
 
374
    #  Trait definitions:
 
375
    #---------------------------------------------------------------------------
 
376
 
 
377
    # Low value for the slider range
 
378
    low = Any( 0 )
 
379
 
 
380
    # High value for the slider range
 
381
    high = Any( 1 )
 
382
 
 
383
    # Low end of displayed range
 
384
    cur_low = Float
 
385
 
 
386
    # High end of displayed range
 
387
    cur_high = Float
 
388
 
 
389
    # Flag indicating that the UI is in the process of being updated
 
390
    ui_changing = Bool( False )
 
391
 
 
392
    #---------------------------------------------------------------------------
 
393
    #  Finishes initializing the editor by creating the underlying toolkit
 
394
    #  widget:
 
395
    #---------------------------------------------------------------------------
 
396
 
 
397
    def init ( self, parent ):
 
398
        """ Finishes initializing the editor by creating the underlying toolkit
 
399
            widget.
 
400
        """
 
401
        factory = self.factory
 
402
 
 
403
        # Initialize using the factory range defaults:
 
404
        self.low = factory.low
 
405
        self.high = factory.high
 
406
        self.evaluate = factory.evaluate
 
407
 
 
408
        # Hook up the traits to listen to the object.
 
409
        self.sync_value( factory.low_name,  'low',  'from' )
 
410
        self.sync_value( factory.high_name, 'high', 'from' )
 
411
        self.sync_value( factory.evaluate_name, 'evaluate', 'from' )
 
412
 
 
413
        self.init_range()
 
414
        low  = self.cur_low
 
415
        high = self.cur_high
 
416
 
 
417
        self._set_format()
 
418
        self.control = panel = TraitsUIPanel( parent, -1 )
 
419
        sizer  = wx.BoxSizer( wx.HORIZONTAL )
 
420
        fvalue = self.value
 
421
        try:
 
422
            fvalue_text = self._format % fvalue
 
423
            1 / (low <= fvalue <= high)
 
424
        except:
 
425
            fvalue_text = ''
 
426
            fvalue      = low
 
427
 
 
428
        if high > low:
 
429
            ivalue = int( (float( fvalue - low ) / (high - low)) * 10000 )
 
430
        else:
 
431
            ivalue = low
 
432
 
 
433
        # Lower limit label:
 
434
        label_lo       = wx.StaticText( panel, -1, '999999' )
 
435
        panel.label_lo = label_lo
 
436
        sizer.Add( label_lo, 2, wx.ALIGN_CENTER )
 
437
 
 
438
        # Lower limit button:
 
439
        bmp       = wx.ArtProvider.GetBitmap( wx.ART_GO_BACK,
 
440
                                              size = ( 15, 15 ) )
 
441
        button_lo = wx.BitmapButton( panel, -1, bitmap = bmp, size = ( -1, 20 ),
 
442
                                     style = wx.BU_EXACTFIT | wx.NO_BORDER )
 
443
        panel.button_lo = button_lo
 
444
        button_lo.Bind( wx.EVT_BUTTON, self.reduce_range, button_lo )
 
445
        sizer.Add( button_lo, 1, wx.ALIGN_CENTER )
 
446
 
 
447
        # Slider:
 
448
        panel.slider = slider = Slider( panel, -1, ivalue, 0, 10000,
 
449
                                   size   = wx.Size( 80, 20 ),
 
450
                                   style  = wx.SL_HORIZONTAL | wx.SL_AUTOTICKS )
 
451
        slider.SetTickFreq( 1000, 1 )
 
452
        slider.SetPageSize( 1000 )
 
453
        slider.SetLineSize( 100 )
 
454
        wx.EVT_SCROLL( slider, self.update_object_on_scroll )
 
455
        sizer.Add( slider, 6, wx.EXPAND )
 
456
 
 
457
        # Upper limit button:
 
458
        bmp       = wx.ArtProvider.GetBitmap( wx.ART_GO_FORWARD,
 
459
                                              size = ( 15, 15 ) )
 
460
        button_hi = wx.BitmapButton( panel, -1, bitmap = bmp, size = ( -1, 20 ),
 
461
                                     style = wx.BU_EXACTFIT | wx.NO_BORDER )
 
462
        panel.button_hi = button_hi
 
463
        button_hi.Bind( wx.EVT_BUTTON, self.increase_range, button_hi )
 
464
        sizer.Add( button_hi, 1, wx.ALIGN_CENTER )
 
465
 
 
466
        # Upper limit label:
 
467
        label_hi = wx.StaticText( panel, -1, '999999' )
 
468
        panel.label_hi = label_hi
 
469
        sizer.Add( label_hi, 2, wx.ALIGN_CENTER )
 
470
 
 
471
        # Text entry:
 
472
        panel.text = text = wx.TextCtrl( panel, -1, fvalue_text,
 
473
                                         size  = wx.Size( 56, 20 ),
 
474
                                         style = wx.TE_PROCESS_ENTER )
 
475
        wx.EVT_TEXT_ENTER( panel, text.GetId(), self.update_object_on_enter )
 
476
        wx.EVT_KILL_FOCUS( text, self.update_object_on_enter )
 
477
 
 
478
        sizer.Add( text, 0, wx.LEFT | wx.EXPAND, 4 )
 
479
 
 
480
        # Set-up the layout:
 
481
        panel.SetSizerAndFit( sizer )
 
482
        label_lo.SetLabel( str(low)  )
 
483
        label_hi.SetLabel( str(high) )
 
484
        self.set_tooltip( slider )
 
485
        self.set_tooltip( label_lo )
 
486
        self.set_tooltip( label_hi )
 
487
        self.set_tooltip( text )
 
488
 
 
489
        # Update the ranges and button just in case.
 
490
        self.update_range_ui()
 
491
 
 
492
    #---------------------------------------------------------------------------
 
493
    #  Handles the user changing the current slider value:
 
494
    #---------------------------------------------------------------------------
 
495
 
 
496
    def update_object_on_scroll ( self, event ):
 
497
        """ Handles the user changing the current slider value.
 
498
        """
 
499
        low   = self.cur_low
 
500
        high  = self.cur_high
 
501
        value = low + ((float( event.GetPosition() ) / 10000.0) *
 
502
                       (high - low))
 
503
        self.control.text.SetValue( self._format % value )
 
504
        event_type = event.GetEventType()
 
505
        try:
 
506
            self.ui_changing = True
 
507
            if ((event_type == wx.wxEVT_SCROLL_ENDSCROLL) or
 
508
                (self.factory.auto_set and
 
509
                 (event_type == wx.wxEVT_SCROLL_THUMBTRACK)) or
 
510
                (self.factory.enter_set and
 
511
                 (event_type == wx.wxEVT_SCROLL_THUMBRELEASE))):
 
512
                if self.factory.is_float:
 
513
                    self.value = value
 
514
                else:
 
515
                    self.value = int( value )
 
516
        finally:
 
517
            self.ui_changing = False
 
518
 
 
519
    #---------------------------------------------------------------------------
 
520
    #  Handle the user pressing the 'Enter' key in the edit control:
 
521
    #---------------------------------------------------------------------------
 
522
 
 
523
    def update_object_on_enter ( self, event ):
 
524
        """ Handles the user pressing the Enter key in the text field.
 
525
        """
 
526
        try:
 
527
            value = self.control.text.GetValue().strip()
 
528
            try:
 
529
                if self.factory.is_float:
 
530
                    value = float(value)
 
531
                else:
 
532
                    value = int(value)
 
533
            except Exception, ex:
 
534
                # The user entered something that didn't eval as a number (e.g., 'foo').
 
535
                # Pretend it didn't happen (i.e. do not change self.value).
 
536
                value = self.value
 
537
                self.control.text.SetValue( str( value ) )
 
538
 
 
539
            self.value = value
 
540
            self.control.text.SetBackgroundColour(OKColor)
 
541
            self.control.text.Refresh()
 
542
            # Update the slider range.
 
543
            # Set ui_changing to True to avoid recursion:
 
544
            # the update_range_ui method will try to set the value in the text
 
545
            # box, which will again fire this method if auto_set is True.
 
546
            if not self.ui_changing:
 
547
                self.ui_changing = True
 
548
                self.init_range()
 
549
                self.update_range_ui()
 
550
                self.ui_changing = False
 
551
            if self._error is not None:
 
552
                self._error     = None
 
553
                self.ui.errors -= 1
 
554
        except TraitError, excp:
 
555
            pass
 
556
 
 
557
    #---------------------------------------------------------------------------
 
558
    #  Handles an error that occurs while setting the object's trait value:
 
559
    #---------------------------------------------------------------------------
 
560
 
 
561
    def error ( self, excp ):
 
562
        """ Handles an error that occurs while setting the object's trait value.
 
563
        """
 
564
        if self._error is None:
 
565
            self._error     = True
 
566
            self.ui.errors += 1
 
567
            super(LargeRangeSliderEditor, self).error(excp)
 
568
        self.set_error_state( True )
 
569
 
 
570
    #---------------------------------------------------------------------------
 
571
    #  Updates the editor when the object trait changes external to the editor:
 
572
    #---------------------------------------------------------------------------
 
573
 
 
574
    def update_editor ( self ):
 
575
        """ Updates the editor when the object trait changes externally to the
 
576
            editor.
 
577
        """
 
578
        value = self.value
 
579
        low   = self.low
 
580
        high  = self.high
 
581
        try:
 
582
            text = self._format % value
 
583
            1 / (low <= value <= high)
 
584
        except:
 
585
            value = low
 
586
        self.value = value
 
587
 
 
588
        if not self.ui_changing:
 
589
            self.init_range()
 
590
            self.update_range_ui()
 
591
 
 
592
    def update_range_ui ( self ):
 
593
        """ Updates the slider range controls.
 
594
        """
 
595
        low, high = self.cur_low, self.cur_high
 
596
        value = self.value
 
597
        self._set_format()
 
598
        self.control.label_lo.SetLabel( self._format % low )
 
599
        self.control.label_hi.SetLabel( self._format % high )
 
600
        if high > low:
 
601
            ivalue = int( (float( value - low ) / (high - low)) * 10000.0 )
 
602
        else:
 
603
            ivalue = low
 
604
        self.control.slider.SetValue( ivalue )
 
605
        text = self._format % self.value
 
606
        self.control.text.SetValue( text )
 
607
        factory = self.factory
 
608
        f_low, f_high = self.low, self.high
 
609
 
 
610
        if low == f_low:
 
611
            self.control.button_lo.Disable()
 
612
        else:
 
613
            self.control.button_lo.Enable()
 
614
 
 
615
        if high == f_high:
 
616
            self.control.button_hi.Disable()
 
617
        else:
 
618
            self.control.button_hi.Enable()
 
619
 
 
620
    def init_range ( self ):
 
621
        """ Initializes the slider range controls.
 
622
        """
 
623
        value     = self.value
 
624
        factory   = self.factory
 
625
        low, high = self.low, self.high
 
626
        if (high is None) and (low is not None):
 
627
            high = -low
 
628
 
 
629
        mag = abs( value )
 
630
        if mag <= 10.0:
 
631
            cur_low  = max( value - 10, low )
 
632
            cur_high = min( value + 10, high )
 
633
        else:
 
634
            d        = 0.5 * (10**int( log10( mag ) + 1 ))
 
635
            cur_low  = max( low,  value - d )
 
636
            cur_high = min( high, value + d )
 
637
 
 
638
        self.cur_low, self.cur_high = cur_low, cur_high
 
639
 
 
640
    def reduce_range ( self, event ):
 
641
        """ Reduces the extent of the displayed range.
 
642
        """
 
643
        factory   = self.factory
 
644
        low, high = self.low, self.high
 
645
        if abs( self.cur_low ) < 10:
 
646
            self.cur_low  = max( -10, low )
 
647
            self.cur_high = min( 10, high )
 
648
        elif self.cur_low > 0:
 
649
            self.cur_high = self.cur_low
 
650
            self.cur_low  = max( low, self.cur_low / 10 )
 
651
        else:
 
652
            self.cur_high = self.cur_low
 
653
            self.cur_low  = max( low, self.cur_low * 10 )
 
654
 
 
655
        self.ui_changing = True
 
656
        self.value       = min( max( self.value, self.cur_low ), self.cur_high )
 
657
        self.ui_changing = False
 
658
        self.update_range_ui()
 
659
 
 
660
    def increase_range ( self, event ):
 
661
        """ Increased the extent of the displayed range.
 
662
        """
 
663
        factory   = self.factory
 
664
        low, high = self.low, self.high
 
665
        if abs( self.cur_high ) < 10:
 
666
            self.cur_low  = max( -10, low )
 
667
            self.cur_high = min(  10, high )
 
668
        elif self.cur_high > 0:
 
669
            self.cur_low  = self.cur_high
 
670
            self.cur_high = min( high, self.cur_high * 10 )
 
671
        else:
 
672
            self.cur_low  = self.cur_high
 
673
            self.cur_high = min( high, self.cur_high / 10 )
 
674
 
 
675
        self.ui_changing = True
 
676
        self.value       = min( max( self.value, self.cur_low ), self.cur_high )
 
677
        self.ui_changing = False
 
678
        self.update_range_ui()
 
679
 
 
680
    def _set_format ( self ):
 
681
        self._format = '%d'
 
682
        factory      = self.factory
 
683
        low, high    = self.cur_low, self.cur_high
 
684
        diff         = high - low
 
685
        if factory.is_float:
 
686
            if diff > 99999:
 
687
                self._format = '%.2g'
 
688
            elif diff > 1:
 
689
                self._format = '%%.%df' % max( 0, 4 -
 
690
                                                  int( log10( high - low ) ) )
 
691
            else:
 
692
                self._format = '%.3f'
 
693
 
 
694
    #---------------------------------------------------------------------------
 
695
    #  Returns the editor's control for indicating error status:
 
696
    #---------------------------------------------------------------------------
 
697
 
 
698
    def get_error_control ( self ):
 
699
        """ Returns the editor's control for indicating error status.
 
700
        """
 
701
        return self.control.text
 
702
 
 
703
    #---------------------------------------------------------------------------
 
704
    #  Handles the 'low'/'high' traits being changed:
 
705
    #---------------------------------------------------------------------------
 
706
 
 
707
    def _low_changed ( self, low ):
 
708
        if self.control is not None:
 
709
            if self.value < low:
 
710
                if self.factory.is_float:
 
711
                    self.value = float( low )
 
712
                else:
 
713
                    self.value = int( low )
 
714
 
 
715
            self.update_editor()
 
716
 
 
717
    def _high_changed ( self, high ):
 
718
        if self.control is not None:
 
719
            if self.value > high:
 
720
                if self.factory.is_float:
 
721
                    self.value = float( high )
 
722
                else:
 
723
                    self.value = int( high )
 
724
 
 
725
            self.update_editor()
 
726
 
 
727
#-------------------------------------------------------------------------------
 
728
#  'SimpleSpinEditor' class:
 
729
#-------------------------------------------------------------------------------
 
730
 
 
731
class SimpleSpinEditor ( BaseRangeEditor ):
 
732
    """ A simple style of range editor that displays a spin box control.
 
733
    """
 
734
 
 
735
    #---------------------------------------------------------------------------
 
736
    #  Trait definitions:
 
737
    #---------------------------------------------------------------------------
 
738
 
 
739
    # Low value for the slider range
 
740
    low = Any
 
741
 
 
742
    # High value for the slider range
 
743
    high = Any
 
744
 
 
745
    #---------------------------------------------------------------------------
 
746
    #  Finishes initializing the editor by creating the underlying toolkit
 
747
    #  widget:
 
748
    #---------------------------------------------------------------------------
 
749
 
 
750
    def init ( self, parent ):
 
751
        """ Finishes initializing the editor by creating the underlying toolkit
 
752
            widget.
 
753
        """
 
754
        factory = self.factory
 
755
        if not factory.low_name:
 
756
            self.low = factory.low
 
757
 
 
758
        if not factory.high_name:
 
759
            self.high = factory.high
 
760
 
 
761
        self.sync_value( factory.low_name,  'low',  'from' )
 
762
        self.sync_value( factory.high_name, 'high', 'from' )
 
763
        low  = self.low
 
764
        high = self.high
 
765
        self.control = wx.SpinCtrl( parent, -1, self.str_value,
 
766
                                    min     = low,
 
767
                                    max     = high,
 
768
                                    initial = self.value )
 
769
        wx.EVT_SPINCTRL( parent, self.control.GetId(), self.update_object )
 
770
        if sys.platform.startswith( 'win' ):
 
771
            wx.EVT_TEXT( parent, self.control.GetId(), self.update_object )
 
772
        self.set_tooltip()
 
773
 
 
774
    #---------------------------------------------------------------------------
 
775
    #  Handle the user selecting a new value from the spin control:
 
776
    #---------------------------------------------------------------------------
 
777
 
 
778
    def update_object ( self, event ):
 
779
        """ Handles the user selecting a new value in the spin box.
 
780
        """
 
781
        self._locked = True
 
782
        try:
 
783
            self.value = self.control.GetValue()
 
784
        finally:
 
785
            self._locked = False
 
786
 
 
787
    #---------------------------------------------------------------------------
 
788
    #  Updates the editor when the object trait changes external to the editor:
 
789
    #---------------------------------------------------------------------------
 
790
 
 
791
    def update_editor ( self ):
 
792
        """ Updates the editor when the object trait changes externally to the
 
793
            editor.
 
794
        """
 
795
        if not self._locked:
 
796
            try:
 
797
                self.control.SetValue( int( self.value ) )
 
798
            except:
 
799
                pass
 
800
 
 
801
    #---------------------------------------------------------------------------
 
802
    #  Handles the 'low'/'high' traits being changed:
 
803
    #---------------------------------------------------------------------------
 
804
 
 
805
    def _low_changed ( self, low ):
 
806
        if self.value < low:
 
807
            if self.factory.is_float:
 
808
                self.value = float( low )
 
809
            else:
 
810
                self.value = int( low )
 
811
        if self.control:
 
812
            self.control.SetRange( self.low, self.high )
 
813
            self.control.SetValue( int( self.value ) )
 
814
 
 
815
    def _high_changed ( self, high ):
 
816
        if self.value > high:
 
817
            if self.factory.is_float:
 
818
                self.value = float( high )
 
819
            else:
 
820
                self.value = int( high )
 
821
        if self.control:
 
822
            self.control.SetRange( self.low, self.high )
 
823
            self.control.SetValue( int( self.value ) )
 
824
 
 
825
#-------------------------------------------------------------------------------
 
826
#  'RangeTextEditor' class:
 
827
#-------------------------------------------------------------------------------
 
828
 
 
829
class RangeTextEditor ( TextEditor ):
 
830
    """ Editor for ranges that displays a text field. If the user enters a
 
831
        value that is outside the allowed range, the background of the field
 
832
        changes color to indicate an error.
 
833
    """
 
834
 
 
835
    #---------------------------------------------------------------------------
 
836
    #  Trait definitions:
 
837
    #---------------------------------------------------------------------------
 
838
 
 
839
    # Function to evaluate floats/ints
 
840
    evaluate = Any
 
841
 
 
842
    #---------------------------------------------------------------------------
 
843
    #  Finishes initializing the editor by creating the underlying toolkit
 
844
    #  widget:
 
845
    #---------------------------------------------------------------------------
 
846
 
 
847
    def init ( self, parent ):
 
848
        """ Finishes initializing the editor by creating the underlying toolkit
 
849
            widget.
 
850
        """
 
851
 
 
852
        if self.factory.enter_set:
 
853
            control = wx.TextCtrl( parent, -1, self.str_value,
 
854
                                   style = wx.TE_PROCESS_ENTER )
 
855
            wx.EVT_TEXT_ENTER( parent, control.GetId(), self.update_object )
 
856
        else:
 
857
            control = wx.TextCtrl( parent, -1, self.str_value )
 
858
 
 
859
        wx.EVT_KILL_FOCUS( control, self.update_object )
 
860
 
 
861
        if self.factory.auto_set:
 
862
            wx.EVT_TEXT( parent, control.GetId(), self.update_object )
 
863
 
 
864
        self.evaluate = self.factory.evaluate
 
865
        self.sync_value( self.factory.evaluate_name, 'evaluate', 'from' )
 
866
 
 
867
        self.control = control
 
868
        self.set_tooltip()
 
869
 
 
870
    #---------------------------------------------------------------------------
 
871
    #  Handles the user entering input data in the edit control:
 
872
    #---------------------------------------------------------------------------
 
873
 
 
874
    def update_object ( self, event ):
 
875
        """ Handles the user entering input data in the edit control.
 
876
        """
 
877
 
 
878
        # There are cases where this method is called with self.control == None.
 
879
        if self.control is None:
 
880
            return
 
881
 
 
882
        value = self.control.GetValue()
 
883
 
 
884
        # Try to convert the string value entered by the user to a numerical value.
 
885
        try:
 
886
            if self.evaluate is not None:
 
887
                value = self.evaluate(value)
 
888
            else:
 
889
                if self.factory.is_float:
 
890
                    value = float(value)
 
891
                else:
 
892
                    value = int(value)
 
893
        except Exception, excp:
 
894
            # The conversion failed.
 
895
            self.error(excp)
 
896
            return
 
897
 
 
898
        # Try to assign the numerical value to the trait.
 
899
        # This may fail because of constraints on the trait.
 
900
        try:
 
901
            self.value = value
 
902
            self.control.SetBackgroundColour(OKColor)
 
903
            self.control.Refresh()
 
904
            if self._error is not None:
 
905
                self._error     = None
 
906
                self.ui.errors -= 1
 
907
        except TraitError, excp:
 
908
            pass
 
909
 
 
910
    #---------------------------------------------------------------------------
 
911
    #  Handles an error that occurs while setting the object's trait value:
 
912
    #---------------------------------------------------------------------------
 
913
 
 
914
    def error ( self, excp ):
 
915
        """ Handles an error that occurs while setting the object's trait value.
 
916
        """
 
917
        if self._error is None:
 
918
            self._error     = True
 
919
            self.ui.errors += 1
 
920
            super(RangeTextEditor, self).error(excp)
 
921
        self.set_error_state( True )
 
922
 
 
923
#-------------------------------------------------------------------------------
 
924
#  'SimpleEnumEditor' factory adaptor:
 
925
#-------------------------------------------------------------------------------
 
926
 
 
927
def SimpleEnumEditor ( parent, factory, ui, object, name, description ):
 
928
    return CustomEnumEditor( parent, factory, ui, object, name, description,
 
929
                             'simple' )
 
930
 
 
931
#-------------------------------------------------------------------------------
 
932
#  'CustomEnumEditor' factory adaptor:
 
933
#-------------------------------------------------------------------------------
 
934
 
 
935
def CustomEnumEditor ( parent, factory, ui, object, name, description,
 
936
                       style = 'custom' ):
 
937
    """ Factory adapter that returns a enumeration editor of the specified
 
938
        style.
 
939
    """
 
940
    if factory._enum is None:
 
941
        import traitsui.editors.enum_editor as enum_editor
 
942
        factory._enum = enum_editor.ToolkitEditorFactory(
 
943
                            values = range( factory.low, factory.high + 1 ),
 
944
                            cols   = factory.cols )
 
945
 
 
946
    if style == 'simple':
 
947
        return factory._enum.simple_editor( ui, object, name, description,
 
948
                                            parent )
 
949
 
 
950
    return factory._enum.custom_editor( ui, object, name, description, parent )
 
951
 
 
952
#-------------------------------------------------------------------------------
 
953
#  Defines the mapping between editor factory 'mode's and Editor classes:
 
954
#-------------------------------------------------------------------------------
 
955
 
 
956
# Mapping between editor factory modes and simple editor classes
 
957
SimpleEditorMap = {
 
958
    'slider':  SimpleSliderEditor,
 
959
    'xslider': LargeRangeSliderEditor,
 
960
    'spinner': SimpleSpinEditor,
 
961
    'enum':    SimpleEnumEditor,
 
962
    'text':    RangeTextEditor,
 
963
    'logslider':     LogRangeSliderEditor
 
964
}
 
965
# Mapping between editor factory modes and custom editor classes
 
966
CustomEditorMap = {
 
967
    'slider':  SimpleSliderEditor,
 
968
    'xslider': LargeRangeSliderEditor,
 
969
    'spinner': SimpleSpinEditor,
 
970
    'enum':    CustomEnumEditor,
 
971
    'text':    RangeTextEditor,
 
972
    'logslider':     LogRangeSliderEditor
 
973
}
 
974
 
 
975
### EOF #######################################################################
 
976