Show More
@@ -1365,6 +1365,10 b' div.cell.text_cell.rendered {' | |||||
1365 | height: 28px !important; |
|
1365 | height: 28px !important; | |
1366 | margin-top: -8px !important; |
|
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 | .widget-vslider { |
|
1372 | .widget-vslider { | |
1369 | /* Vertical jQuery Slider */ |
|
1373 | /* Vertical jQuery Slider */ | |
1370 | /* Fix the padding of the slide track so the ui-slider is sized |
|
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 | height: 14px !important; |
|
1446 | height: 14px !important; | |
1443 | margin-left: -9px; |
|
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 | .widget-text { |
|
1453 | .widget-text { | |
1446 | /* String Textbox - used for TextBoxView and TextAreaView */ |
|
1454 | /* String Textbox - used for TextBoxView and TextAreaView */ | |
1447 | width: 350px; |
|
1455 | width: 350px; |
@@ -9137,6 +9137,10 b' div.cell.text_cell.rendered {' | |||||
9137 | height: 28px !important; |
|
9137 | height: 28px !important; | |
9138 | margin-top: -8px !important; |
|
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 | .widget-vslider { |
|
9144 | .widget-vslider { | |
9141 | /* Vertical jQuery Slider */ |
|
9145 | /* Vertical jQuery Slider */ | |
9142 | /* Fix the padding of the slide track so the ui-slider is sized |
|
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 | height: 14px !important; |
|
9218 | height: 14px !important; | |
9215 | margin-left: -9px; |
|
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 | .widget-text { |
|
9225 | .widget-text { | |
9218 | /* String Textbox - used for TextBoxView and TextAreaView */ |
|
9226 | /* String Textbox - used for TextBoxView and TextAreaView */ | |
9219 | width: 350px; |
|
9227 | width: 350px; |
@@ -52,6 +52,10 b' define([' | |||||
52 | that.$slider.slider("option", key, model_value); |
|
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 | // WORKAROUND FOR JQUERY SLIDER BUG. |
|
60 | // WORKAROUND FOR JQUERY SLIDER BUG. | |
57 | // The horizontal position of the slider handle |
|
61 | // The horizontal position of the slider handle | |
@@ -64,17 +68,28 b' define([' | |||||
64 | var orientation = this.model.get('orientation'); |
|
68 | var orientation = this.model.get('orientation'); | |
65 | var min = this.model.get('min'); |
|
69 | var min = this.model.get('min'); | |
66 | var max = this.model.get('max'); |
|
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 | this.$slider.slider('option', 'orientation', orientation); |
|
76 | this.$slider.slider('option', 'orientation', orientation); | |
69 | var value = this.model.get('value'); |
|
77 | var value = this.model.get('value'); | |
70 | if(value > max) { |
|
78 | if (this.model.get('_range')) { | |
71 | value = max; |
|
79 | // values for the range case are validated python-side in | |
72 | } |
|
80 | // _Bounded{Int,Float}RangeWidget._validate | |
73 | else if(value < min){ |
|
81 | this.$slider.slider('option', 'values', value); | |
74 |
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 | if(this.model.get('value')!=value) { |
|
94 | if(this.model.get('value')!=value) { | |
80 | this.model.set('value', value, {updated_view: this}); |
|
95 | this.model.set('value', value, {updated_view: this}); | |
@@ -140,9 +155,14 b' define([' | |||||
140 |
|
155 | |||
141 | // Calling model.set will trigger all of the other views of the |
|
156 | // Calling model.set will trigger all of the other views of the | |
142 | // model to update. |
|
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 | this.model.set('value', actual_value, {updated_view: this}); |
|
165 | this.model.set('value', actual_value, {updated_view: this}); | |
145 | this.$readout.text(actual_value); |
|
|||
146 | this.touch(); |
|
166 | this.touch(); | |
147 | }, |
|
167 | }, | |
148 |
|
168 |
@@ -122,6 +122,11 b'' | |||||
122 | height : 28px !important; |
|
122 | height : 28px !important; | |
123 | margin-top : -8px !important; |
|
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 | height : 14px !important; |
|
165 | height : 14px !important; | |
161 | margin-left : -9px; |
|
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 | from .widget_bool import Checkbox, ToggleButton |
|
3 | from .widget_bool import Checkbox, ToggleButton | |
4 | from .widget_button import Button |
|
4 | from .widget_button import Button | |
5 | from .widget_box import Box, Popup, FlexBox, HBox, VBox |
|
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 | from .widget_image import Image |
|
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 | from .widget_selection import RadioButtons, ToggleButtons, Dropdown, Select |
|
9 | from .widget_selection import RadioButtons, ToggleButtons, Dropdown, Select | |
10 | from .widget_selectioncontainer import Tab, Accordion |
|
10 | from .widget_selectioncontainer import Tab, Accordion | |
11 | from .widget_string import HTML, Latex, Text, Textarea |
|
11 | from .widget_string import HTML, Latex, Text, Textarea |
@@ -480,3 +480,120 b' def test_custom_description():' | |||||
480 | value='text', |
|
480 | value='text', | |
481 | description='foo', |
|
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 | # Imports |
|
14 | # Imports | |
15 | #----------------------------------------------------------------------------- |
|
15 | #----------------------------------------------------------------------------- | |
16 | from .widget import DOMWidget |
|
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 | from IPython.utils.warn import DeprecatedClass |
|
18 | from IPython.utils.warn import DeprecatedClass | |
19 |
|
19 | |||
20 | #----------------------------------------------------------------------------- |
|
20 | #----------------------------------------------------------------------------- | |
@@ -55,12 +55,113 b' class FloatSlider(_BoundedFloat):' | |||||
55 | _view_name = Unicode('FloatSliderView', sync=True) |
|
55 | _view_name = Unicode('FloatSliderView', sync=True) | |
56 | orientation = Enum([u'horizontal', u'vertical'], u'horizontal', |
|
56 | orientation = Enum([u'horizontal', u'vertical'], u'horizontal', | |
57 | help="Vertical or horizontal.", sync=True) |
|
57 | help="Vertical or horizontal.", sync=True) | |
|
58 | _range = Bool(False, help="Display a range selector", sync=True) | |||
58 | readout = Bool(True, help="Display the current value of the slider next to it.", sync=True) |
|
59 | readout = Bool(True, help="Display the current value of the slider next to it.", sync=True) | |
59 |
|
60 | |||
60 |
|
61 | |||
61 | class FloatProgress(_BoundedFloat): |
|
62 | class FloatProgress(_BoundedFloat): | |
62 | _view_name = Unicode('ProgressView', sync=True) |
|
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 | # Remove in IPython 4.0 |
|
166 | # Remove in IPython 4.0 | |
66 | FloatTextWidget = DeprecatedClass(FloatText, 'FloatTextWidget') |
|
167 | FloatTextWidget = DeprecatedClass(FloatText, 'FloatTextWidget') |
@@ -14,7 +14,7 b' Represents an unbounded int using a widget.' | |||||
14 | # Imports |
|
14 | # Imports | |
15 | #----------------------------------------------------------------------------- |
|
15 | #----------------------------------------------------------------------------- | |
16 | from .widget import DOMWidget |
|
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 | from IPython.utils.warn import DeprecatedClass |
|
18 | from IPython.utils.warn import DeprecatedClass | |
19 |
|
19 | |||
20 | #----------------------------------------------------------------------------- |
|
20 | #----------------------------------------------------------------------------- | |
@@ -60,6 +60,7 b' class IntSlider(_BoundedInt):' | |||||
60 | _view_name = Unicode('IntSliderView', sync=True) |
|
60 | _view_name = Unicode('IntSliderView', sync=True) | |
61 | orientation = Enum([u'horizontal', u'vertical'], u'horizontal', |
|
61 | orientation = Enum([u'horizontal', u'vertical'], u'horizontal', | |
62 | help="Vertical or horizontal.", sync=True) |
|
62 | help="Vertical or horizontal.", sync=True) | |
|
63 | _range = Bool(False, help="Display a range selector", sync=True) | |||
63 | readout = Bool(True, help="Display the current value of the slider next to it.", sync=True) |
|
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 | """Progress bar that represents a int bounded by a minimum and maximum value.""" |
|
68 | """Progress bar that represents a int bounded by a minimum and maximum value.""" | |
68 | _view_name = Unicode('ProgressView', sync=True) |
|
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 | # Remove in IPython 4.0 |
|
170 | # Remove in IPython 4.0 | |
72 | IntTextWidget = DeprecatedClass(IntText, 'IntTextWidget') |
|
171 | IntTextWidget = DeprecatedClass(IntText, 'IntTextWidget') |
General Comments 0
You need to be logged in to leave comments.
Login now