Show More
@@ -1365,6 +1365,10 b' div.cell.text_cell.rendered {' | |||
|
1365 | 1365 | height: 28px !important; |
|
1366 | 1366 | margin-top: -8px !important; |
|
1367 | 1367 | } |
|
1368 | .widget-hslider .ui-slider .ui-slider-range { | |
|
1369 | height: 12px !important; | |
|
1370 | margin-top: -4px !important; | |
|
1371 | } | |
|
1368 | 1372 | .widget-vslider { |
|
1369 | 1373 | /* Vertical jQuery Slider */ |
|
1370 | 1374 | /* Fix the padding of the slide track so the ui-slider is sized |
@@ -1442,6 +1446,10 b' div.cell.text_cell.rendered {' | |||
|
1442 | 1446 | height: 14px !important; |
|
1443 | 1447 | margin-left: -9px; |
|
1444 | 1448 | } |
|
1449 | .widget-vslider .ui-slider .ui-slider-range { | |
|
1450 | width: 12px !important; | |
|
1451 | margin-left: -1px !important; | |
|
1452 | } | |
|
1445 | 1453 | .widget-text { |
|
1446 | 1454 | /* String Textbox - used for TextBoxView and TextAreaView */ |
|
1447 | 1455 | width: 350px; |
@@ -9137,6 +9137,10 b' div.cell.text_cell.rendered {' | |||
|
9137 | 9137 | height: 28px !important; |
|
9138 | 9138 | margin-top: -8px !important; |
|
9139 | 9139 | } |
|
9140 | .widget-hslider .ui-slider .ui-slider-range { | |
|
9141 | height: 12px !important; | |
|
9142 | margin-top: -4px !important; | |
|
9143 | } | |
|
9140 | 9144 | .widget-vslider { |
|
9141 | 9145 | /* Vertical jQuery Slider */ |
|
9142 | 9146 | /* Fix the padding of the slide track so the ui-slider is sized |
@@ -9214,6 +9218,10 b' div.cell.text_cell.rendered {' | |||
|
9214 | 9218 | height: 14px !important; |
|
9215 | 9219 | margin-left: -9px; |
|
9216 | 9220 | } |
|
9221 | .widget-vslider .ui-slider .ui-slider-range { | |
|
9222 | width: 12px !important; | |
|
9223 | margin-left: -1px !important; | |
|
9224 | } | |
|
9217 | 9225 | .widget-text { |
|
9218 | 9226 | /* String Textbox - used for TextBoxView and TextAreaView */ |
|
9219 | 9227 | width: 350px; |
@@ -52,6 +52,10 b' define([' | |||
|
52 | 52 | that.$slider.slider("option", key, model_value); |
|
53 | 53 | } |
|
54 | 54 | }); |
|
55 | var range_value = this.model.get("_range"); | |
|
56 | if (range_value !== undefined) { | |
|
57 | this.$slider.slider("option", "range", range_value); | |
|
58 | } | |
|
55 | 59 | |
|
56 | 60 | // WORKAROUND FOR JQUERY SLIDER BUG. |
|
57 | 61 | // The horizontal position of the slider handle |
@@ -64,17 +68,28 b' define([' | |||
|
64 | 68 | var orientation = this.model.get('orientation'); |
|
65 | 69 | var min = this.model.get('min'); |
|
66 | 70 | var max = this.model.get('max'); |
|
67 | this.$slider.slider('option', 'value', min); | |
|
71 | if (this.model.get('_range')) { | |
|
72 | this.$slider.slider('option', 'values', [min, min]); | |
|
73 | } else { | |
|
74 | this.$slider.slider('option', 'value', min); | |
|
75 | } | |
|
68 | 76 | this.$slider.slider('option', 'orientation', orientation); |
|
69 | 77 | var value = this.model.get('value'); |
|
70 | if(value > max) { | |
|
71 | value = max; | |
|
72 | } | |
|
73 | else if(value < min){ | |
|
74 |
value |
|
|
78 | if (this.model.get('_range')) { | |
|
79 | // values for the range case are validated python-side in | |
|
80 | // _Bounded{Int,Float}RangeWidget._validate | |
|
81 | this.$slider.slider('option', 'values', value); | |
|
82 | this.$readout.text(value.join("-")); | |
|
83 | } else { | |
|
84 | if(value > max) { | |
|
85 | value = max; | |
|
86 | } | |
|
87 | else if(value < min){ | |
|
88 | value = min; | |
|
89 | } | |
|
90 | this.$slider.slider('option', 'value', value); | |
|
91 | this.$readout.text(value); | |
|
75 | 92 | } |
|
76 | this.$slider.slider('option', 'value', value); | |
|
77 | this.$readout.text(value); | |
|
78 | 93 | |
|
79 | 94 | if(this.model.get('value')!=value) { |
|
80 | 95 | this.model.set('value', value, {updated_view: this}); |
@@ -140,9 +155,14 b' define([' | |||
|
140 | 155 | |
|
141 | 156 | // Calling model.set will trigger all of the other views of the |
|
142 | 157 | // model to update. |
|
143 | var actual_value = this._validate_slide_value(ui.value); | |
|
158 | if (this.model.get("_range")) { | |
|
159 | var actual_value = ui.values.map(this._validate_slide_value); | |
|
160 | this.$readout.text(actual_value.join("-")); | |
|
161 | } else { | |
|
162 | var actual_value = this._validate_slide_value(ui.value); | |
|
163 | this.$readout.text(actual_value); | |
|
164 | } | |
|
144 | 165 | this.model.set('value', actual_value, {updated_view: this}); |
|
145 | this.$readout.text(actual_value); | |
|
146 | 166 | this.touch(); |
|
147 | 167 | }, |
|
148 | 168 |
@@ -122,6 +122,11 b'' | |||
|
122 | 122 | height : 28px !important; |
|
123 | 123 | margin-top : -8px !important; |
|
124 | 124 | } |
|
125 | ||
|
126 | .ui-slider-range { | |
|
127 | height : 12px !important; | |
|
128 | margin-top : -4px !important; | |
|
129 | } | |
|
125 | 130 | } |
|
126 | 131 | } |
|
127 | 132 | |
@@ -160,6 +165,11 b'' | |||
|
160 | 165 | height : 14px !important; |
|
161 | 166 | margin-left : -9px; |
|
162 | 167 | } |
|
168 | ||
|
169 | .ui-slider-range { | |
|
170 | width : 12px !important; | |
|
171 | margin-left : -1px !important; | |
|
172 | } | |
|
163 | 173 | } |
|
164 | 174 | } |
|
165 | 175 |
@@ -3,9 +3,9 b' from .widget import Widget, DOMWidget, CallbackDispatcher' | |||
|
3 | 3 | from .widget_bool import Checkbox, ToggleButton |
|
4 | 4 | from .widget_button import Button |
|
5 | 5 | from .widget_box import Box, Popup, FlexBox, HBox, VBox |
|
6 | from .widget_float import FloatText, BoundedFloatText, FloatSlider, FloatProgress | |
|
6 | from .widget_float import FloatText, BoundedFloatText, FloatSlider, FloatProgress, FloatRangeSlider | |
|
7 | 7 | from .widget_image import Image |
|
8 | from .widget_int import IntText, BoundedIntText, IntSlider, IntProgress | |
|
8 | from .widget_int import IntText, BoundedIntText, IntSlider, IntProgress, IntRangeSlider | |
|
9 | 9 | from .widget_selection import RadioButtons, ToggleButtons, Dropdown, Select |
|
10 | 10 | from .widget_selectioncontainer import Tab, Accordion |
|
11 | 11 | from .widget_string import HTML, Latex, Text, Textarea |
@@ -480,3 +480,120 b' def test_custom_description():' | |||
|
480 | 480 | value='text', |
|
481 | 481 | description='foo', |
|
482 | 482 | ) |
|
483 | ||
|
484 | def test_int_range_logic(): | |
|
485 | irsw = widgets.IntRangeSlider | |
|
486 | w = irsw(value=(2, 4), min=0, max=6) | |
|
487 | check_widget(w, cls=irsw, value=(2, 4), min=0, max=6) | |
|
488 | w.value = (4, 2) | |
|
489 | check_widget(w, cls=irsw, value=(2, 4), min=0, max=6) | |
|
490 | w.value = (-1, 7) | |
|
491 | check_widget(w, cls=irsw, value=(0, 6), min=0, max=6) | |
|
492 | w.min = 3 | |
|
493 | check_widget(w, cls=irsw, value=(3, 6), min=3, max=6) | |
|
494 | w.max = 3 | |
|
495 | check_widget(w, cls=irsw, value=(3, 3), min=3, max=3) | |
|
496 | ||
|
497 | w.min = 0 | |
|
498 | w.max = 6 | |
|
499 | w.lower = 2 | |
|
500 | w.upper = 4 | |
|
501 | check_widget(w, cls=irsw, value=(2, 4), min=0, max=6) | |
|
502 | w.value = (0, 1) #lower non-overlapping range | |
|
503 | check_widget(w, cls=irsw, value=(0, 1), min=0, max=6) | |
|
504 | w.value = (5, 6) #upper non-overlapping range | |
|
505 | check_widget(w, cls=irsw, value=(5, 6), min=0, max=6) | |
|
506 | w.value = (-1, 4) #semi out-of-range | |
|
507 | check_widget(w, cls=irsw, value=(0, 4), min=0, max=6) | |
|
508 | w.lower = 2 | |
|
509 | check_widget(w, cls=irsw, value=(2, 4), min=0, max=6) | |
|
510 | w.value = (-2, -1) #wholly out of range | |
|
511 | check_widget(w, cls=irsw, value=(0, 0), min=0, max=6) | |
|
512 | w.value = (7, 8) | |
|
513 | check_widget(w, cls=irsw, value=(6, 6), min=0, max=6) | |
|
514 | ||
|
515 | with nt.assert_raises(ValueError): | |
|
516 | w.min = 7 | |
|
517 | with nt.assert_raises(ValueError): | |
|
518 | w.max = -1 | |
|
519 | with nt.assert_raises(ValueError): | |
|
520 | w.lower = 5 | |
|
521 | with nt.assert_raises(ValueError): | |
|
522 | w.upper = 1 | |
|
523 | ||
|
524 | w = irsw(min=2, max=3) | |
|
525 | check_widget(w, min=2, max=3) | |
|
526 | w = irsw(min=100, max=200) | |
|
527 | check_widget(w, lower=125, upper=175, value=(125, 175)) | |
|
528 | ||
|
529 | with nt.assert_raises(ValueError): | |
|
530 | irsw(value=(2, 4), lower=3) | |
|
531 | with nt.assert_raises(ValueError): | |
|
532 | irsw(value=(2, 4), upper=3) | |
|
533 | with nt.assert_raises(ValueError): | |
|
534 | irsw(value=(2, 4), lower=3, upper=3) | |
|
535 | with nt.assert_raises(ValueError): | |
|
536 | irsw(min=2, max=1) | |
|
537 | with nt.assert_raises(ValueError): | |
|
538 | irsw(lower=5) | |
|
539 | with nt.assert_raises(ValueError): | |
|
540 | irsw(upper=5) | |
|
541 | ||
|
542 | ||
|
543 | def test_float_range_logic(): | |
|
544 | frsw = widgets.FloatRangeSlider | |
|
545 | w = frsw(value=(.2, .4), min=0., max=.6) | |
|
546 | check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6) | |
|
547 | w.value = (.4, .2) | |
|
548 | check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6) | |
|
549 | w.value = (-.1, .7) | |
|
550 | check_widget(w, cls=frsw, value=(0., .6), min=0., max=.6) | |
|
551 | w.min = .3 | |
|
552 | check_widget(w, cls=frsw, value=(.3, .6), min=.3, max=.6) | |
|
553 | w.max = .3 | |
|
554 | check_widget(w, cls=frsw, value=(.3, .3), min=.3, max=.3) | |
|
555 | ||
|
556 | w.min = 0. | |
|
557 | w.max = .6 | |
|
558 | w.lower = .2 | |
|
559 | w.upper = .4 | |
|
560 | check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6) | |
|
561 | w.value = (0., .1) #lower non-overlapping range | |
|
562 | check_widget(w, cls=frsw, value=(0., .1), min=0., max=.6) | |
|
563 | w.value = (.5, .6) #upper non-overlapping range | |
|
564 | check_widget(w, cls=frsw, value=(.5, .6), min=0., max=.6) | |
|
565 | w.value = (-.1, .4) #semi out-of-range | |
|
566 | check_widget(w, cls=frsw, value=(0., .4), min=0., max=.6) | |
|
567 | w.lower = .2 | |
|
568 | check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6) | |
|
569 | w.value = (-.2, -.1) #wholly out of range | |
|
570 | check_widget(w, cls=frsw, value=(0., 0.), min=0., max=.6) | |
|
571 | w.value = (.7, .8) | |
|
572 | check_widget(w, cls=frsw, value=(.6, .6), min=.0, max=.6) | |
|
573 | ||
|
574 | with nt.assert_raises(ValueError): | |
|
575 | w.min = .7 | |
|
576 | with nt.assert_raises(ValueError): | |
|
577 | w.max = -.1 | |
|
578 | with nt.assert_raises(ValueError): | |
|
579 | w.lower = .5 | |
|
580 | with nt.assert_raises(ValueError): | |
|
581 | w.upper = .1 | |
|
582 | ||
|
583 | w = frsw(min=2, max=3) | |
|
584 | check_widget(w, min=2, max=3) | |
|
585 | w = frsw(min=1., max=2.) | |
|
586 | check_widget(w, lower=1.25, upper=1.75, value=(1.25, 1.75)) | |
|
587 | ||
|
588 | with nt.assert_raises(ValueError): | |
|
589 | frsw(value=(2, 4), lower=3) | |
|
590 | with nt.assert_raises(ValueError): | |
|
591 | frsw(value=(2, 4), upper=3) | |
|
592 | with nt.assert_raises(ValueError): | |
|
593 | frsw(value=(2, 4), lower=3, upper=3) | |
|
594 | with nt.assert_raises(ValueError): | |
|
595 | frsw(min=.2, max=.1) | |
|
596 | with nt.assert_raises(ValueError): | |
|
597 | frsw(lower=5) | |
|
598 | with nt.assert_raises(ValueError): | |
|
599 | frsw(upper=5) |
@@ -14,7 +14,7 b' Represents an unbounded float using a widget.' | |||
|
14 | 14 | # Imports |
|
15 | 15 | #----------------------------------------------------------------------------- |
|
16 | 16 | from .widget import DOMWidget |
|
17 | from IPython.utils.traitlets import Unicode, CFloat, Bool, Enum | |
|
17 | from IPython.utils.traitlets import Unicode, CFloat, Bool, Enum, Tuple | |
|
18 | 18 | from IPython.utils.warn import DeprecatedClass |
|
19 | 19 | |
|
20 | 20 | #----------------------------------------------------------------------------- |
@@ -55,12 +55,113 b' class FloatSlider(_BoundedFloat):' | |||
|
55 | 55 | _view_name = Unicode('FloatSliderView', sync=True) |
|
56 | 56 | orientation = Enum([u'horizontal', u'vertical'], u'horizontal', |
|
57 | 57 | help="Vertical or horizontal.", sync=True) |
|
58 | _range = Bool(False, help="Display a range selector", sync=True) | |
|
58 | 59 | readout = Bool(True, help="Display the current value of the slider next to it.", sync=True) |
|
59 | 60 | |
|
60 | 61 | |
|
61 | 62 | class FloatProgress(_BoundedFloat): |
|
62 | 63 | _view_name = Unicode('ProgressView', sync=True) |
|
63 | 64 | |
|
65 | class _FloatRange(_Float): | |
|
66 | value = Tuple(CFloat, CFloat, default_value=(0.0, 1.0), help="Tuple of (lower, upper) bounds", sync=True) | |
|
67 | lower = CFloat(0.0, help="Lower bound", sync=False) | |
|
68 | upper = CFloat(1.0, help="Upper bound", sync=False) | |
|
69 | ||
|
70 | def __init__(self, *pargs, **kwargs): | |
|
71 | value_given = 'value' in kwargs | |
|
72 | lower_given = 'lower' in kwargs | |
|
73 | upper_given = 'upper' in kwargs | |
|
74 | if value_given and (lower_given or upper_given): | |
|
75 | raise ValueError("Cannot specify both 'value' and 'lower'/'upper' for range widget") | |
|
76 | if lower_given != upper_given: | |
|
77 | raise ValueError("Must specify both 'lower' and 'upper' for range widget") | |
|
78 | ||
|
79 | DOMWidget.__init__(self, *pargs, **kwargs) | |
|
80 | ||
|
81 | # ensure the traits match, preferring whichever (if any) was given in kwargs | |
|
82 | if value_given: | |
|
83 | self.lower, self.upper = self.value | |
|
84 | else: | |
|
85 | self.value = (self.lower, self.upper) | |
|
86 | ||
|
87 | self.on_trait_change(self._validate, ['value', 'upper', 'lower']) | |
|
88 | ||
|
89 | def _validate(self, name, old, new): | |
|
90 | if name == 'value': | |
|
91 | self.lower, self.upper = min(new), max(new) | |
|
92 | elif name == 'lower': | |
|
93 | self.value = (new, self.value[1]) | |
|
94 | elif name == 'upper': | |
|
95 | self.value = (self.value[0], new) | |
|
96 | ||
|
97 | class _BoundedFloatRange(_FloatRange): | |
|
98 | step = CFloat(1.0, help="Minimum step that the value can take (ignored by some views)", sync=True) | |
|
99 | max = CFloat(100.0, help="Max value", sync=True) | |
|
100 | min = CFloat(0.0, help="Min value", sync=True) | |
|
101 | ||
|
102 | def __init__(self, *pargs, **kwargs): | |
|
103 | any_value_given = 'value' in kwargs or 'upper' in kwargs or 'lower' in kwargs | |
|
104 | _FloatRange.__init__(self, *pargs, **kwargs) | |
|
105 | ||
|
106 | # ensure a minimal amount of sanity | |
|
107 | if self.min > self.max: | |
|
108 | raise ValueError("min must be <= max") | |
|
109 | ||
|
110 | if any_value_given: | |
|
111 | # if a value was given, clamp it within (min, max) | |
|
112 | self._validate("value", None, self.value) | |
|
113 | else: | |
|
114 | # otherwise, set it to 25-75% to avoid the handles overlapping | |
|
115 | self.value = (0.75*self.min + 0.25*self.max, | |
|
116 | 0.25*self.min + 0.75*self.max) | |
|
117 | # callback already set for 'value', 'lower', 'upper' | |
|
118 | self.on_trait_change(self._validate, ['min', 'max']) | |
|
119 | ||
|
120 | ||
|
121 | def _validate(self, name, old, new): | |
|
122 | if name == "min": | |
|
123 | if new > self.max: | |
|
124 | raise ValueError("setting min > max") | |
|
125 | self.min = new | |
|
126 | elif name == "max": | |
|
127 | if new < self.min: | |
|
128 | raise ValueError("setting max < min") | |
|
129 | self.max = new | |
|
130 | ||
|
131 | low, high = self.value | |
|
132 | if name == "value": | |
|
133 | low, high = min(new), max(new) | |
|
134 | elif name == "upper": | |
|
135 | if new < self.lower: | |
|
136 | raise ValueError("setting upper < lower") | |
|
137 | high = new | |
|
138 | elif name == "lower": | |
|
139 | if new > self.upper: | |
|
140 | raise ValueError("setting lower > upper") | |
|
141 | low = new | |
|
142 | ||
|
143 | low = max(self.min, min(low, self.max)) | |
|
144 | high = min(self.max, max(high, self.min)) | |
|
145 | ||
|
146 | # determine the order in which we should update the | |
|
147 | # lower, upper traits to avoid a temporary inverted overlap | |
|
148 | lower_first = high < self.lower | |
|
149 | ||
|
150 | self.value = (low, high) | |
|
151 | if lower_first: | |
|
152 | self.lower = low | |
|
153 | self.upper = high | |
|
154 | else: | |
|
155 | self.upper = high | |
|
156 | self.lower = low | |
|
157 | ||
|
158 | ||
|
159 | class FloatRangeSlider(_BoundedFloatRange): | |
|
160 | _view_name = Unicode('FloatSliderView', sync=True) | |
|
161 | orientation = Enum([u'horizontal', u'vertical'], u'horizontal', | |
|
162 | help="Vertical or horizontal.", sync=True) | |
|
163 | _range = Bool(True, help="Display a range selector", sync=True) | |
|
164 | readout = Bool(True, help="Display the current value of the slider next to it.", sync=True) | |
|
64 | 165 | |
|
65 | 166 | # Remove in IPython 4.0 |
|
66 | 167 | FloatTextWidget = DeprecatedClass(FloatText, 'FloatTextWidget') |
@@ -14,7 +14,7 b' Represents an unbounded int using a widget.' | |||
|
14 | 14 | # Imports |
|
15 | 15 | #----------------------------------------------------------------------------- |
|
16 | 16 | from .widget import DOMWidget |
|
17 | from IPython.utils.traitlets import Unicode, CInt, Bool, Enum | |
|
17 | from IPython.utils.traitlets import Unicode, CInt, Bool, Enum, Tuple | |
|
18 | 18 | from IPython.utils.warn import DeprecatedClass |
|
19 | 19 | |
|
20 | 20 | #----------------------------------------------------------------------------- |
@@ -60,6 +60,7 b' class IntSlider(_BoundedInt):' | |||
|
60 | 60 | _view_name = Unicode('IntSliderView', sync=True) |
|
61 | 61 | orientation = Enum([u'horizontal', u'vertical'], u'horizontal', |
|
62 | 62 | help="Vertical or horizontal.", sync=True) |
|
63 | _range = Bool(False, help="Display a range selector", sync=True) | |
|
63 | 64 | readout = Bool(True, help="Display the current value of the slider next to it.", sync=True) |
|
64 | 65 | |
|
65 | 66 | |
@@ -67,6 +68,104 b' class IntProgress(_BoundedInt):' | |||
|
67 | 68 | """Progress bar that represents a int bounded by a minimum and maximum value.""" |
|
68 | 69 | _view_name = Unicode('ProgressView', sync=True) |
|
69 | 70 | |
|
71 | class _IntRange(_Int): | |
|
72 | value = Tuple(CInt, CInt, default_value=(0, 1), help="Tuple of (lower, upper) bounds", sync=True) | |
|
73 | lower = CInt(0, help="Lower bound", sync=False) | |
|
74 | upper = CInt(1, help="Upper bound", sync=False) | |
|
75 | ||
|
76 | def __init__(self, *pargs, **kwargs): | |
|
77 | value_given = 'value' in kwargs | |
|
78 | lower_given = 'lower' in kwargs | |
|
79 | upper_given = 'upper' in kwargs | |
|
80 | if value_given and (lower_given or upper_given): | |
|
81 | raise ValueError("Cannot specify both 'value' and 'lower'/'upper' for range widget") | |
|
82 | if lower_given != upper_given: | |
|
83 | raise ValueError("Must specify both 'lower' and 'upper' for range widget") | |
|
84 | ||
|
85 | DOMWidget.__init__(self, *pargs, **kwargs) | |
|
86 | ||
|
87 | # ensure the traits match, preferring whichever (if any) was given in kwargs | |
|
88 | if value_given: | |
|
89 | self.lower, self.upper = self.value | |
|
90 | else: | |
|
91 | self.value = (self.lower, self.upper) | |
|
92 | ||
|
93 | self.on_trait_change(self._validate, ['value', 'upper', 'lower']) | |
|
94 | ||
|
95 | def _validate(self, name, old, new): | |
|
96 | if name == 'value': | |
|
97 | self.lower, self.upper = min(new), max(new) | |
|
98 | elif name == 'lower': | |
|
99 | self.value = (new, self.value[1]) | |
|
100 | elif name == 'upper': | |
|
101 | self.value = (self.value[0], new) | |
|
102 | ||
|
103 | class _BoundedIntRange(_IntRange): | |
|
104 | step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True) | |
|
105 | max = CInt(100, help="Max value", sync=True) | |
|
106 | min = CInt(0, help="Min value", sync=True) | |
|
107 | ||
|
108 | def __init__(self, *pargs, **kwargs): | |
|
109 | any_value_given = 'value' in kwargs or 'upper' in kwargs or 'lower' in kwargs | |
|
110 | _IntRange.__init__(self, *pargs, **kwargs) | |
|
111 | ||
|
112 | # ensure a minimal amount of sanity | |
|
113 | if self.min > self.max: | |
|
114 | raise ValueError("min must be <= max") | |
|
115 | ||
|
116 | if any_value_given: | |
|
117 | # if a value was given, clamp it within (min, max) | |
|
118 | self._validate("value", None, self.value) | |
|
119 | else: | |
|
120 | # otherwise, set it to 25-75% to avoid the handles overlapping | |
|
121 | self.value = (0.75*self.min + 0.25*self.max, | |
|
122 | 0.25*self.min + 0.75*self.max) | |
|
123 | # callback already set for 'value', 'lower', 'upper' | |
|
124 | self.on_trait_change(self._validate, ['min', 'max']) | |
|
125 | ||
|
126 | def _validate(self, name, old, new): | |
|
127 | if name == "min": | |
|
128 | if new > self.max: | |
|
129 | raise ValueError("setting min > max") | |
|
130 | self.min = new | |
|
131 | elif name == "max": | |
|
132 | if new < self.min: | |
|
133 | raise ValueError("setting max < min") | |
|
134 | self.max = new | |
|
135 | ||
|
136 | low, high = self.value | |
|
137 | if name == "value": | |
|
138 | low, high = min(new), max(new) | |
|
139 | elif name == "upper": | |
|
140 | if new < self.lower: | |
|
141 | raise ValueError("setting upper < lower") | |
|
142 | high = new | |
|
143 | elif name == "lower": | |
|
144 | if new > self.upper: | |
|
145 | raise ValueError("setting lower > upper") | |
|
146 | low = new | |
|
147 | ||
|
148 | low = max(self.min, min(low, self.max)) | |
|
149 | high = min(self.max, max(high, self.min)) | |
|
150 | ||
|
151 | # determine the order in which we should update the | |
|
152 | # lower, upper traits to avoid a temporary inverted overlap | |
|
153 | lower_first = high < self.lower | |
|
154 | ||
|
155 | self.value = (low, high) | |
|
156 | if lower_first: | |
|
157 | self.lower = low | |
|
158 | self.upper = high | |
|
159 | else: | |
|
160 | self.upper = high | |
|
161 | self.lower = low | |
|
162 | ||
|
163 | class IntRangeSlider(_BoundedIntRange): | |
|
164 | _view_name = Unicode('IntSliderView', sync=True) | |
|
165 | orientation = Enum([u'horizontal', u'vertical'], u'horizontal', | |
|
166 | help="Vertical or horizontal.", sync=True) | |
|
167 | _range = Bool(True, help="Display a range selector", sync=True) | |
|
168 | readout = Bool(True, help="Display the current value of the slider next to it.", sync=True) | |
|
70 | 169 | |
|
71 | 170 | # Remove in IPython 4.0 |
|
72 | 171 | IntTextWidget = DeprecatedClass(IntText, 'IntTextWidget') |
General Comments 0
You need to be logged in to leave comments.
Login now