From 9c1c4f9f0a10646ce4a87cc30f34101cd3deac42 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Mon, 15 Sep 2014 22:24:49 -0700 Subject: [PATCH 1/4] Fix bug in bounded int/float logic. --- IPython/html/widgets/widget_int.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/IPython/html/widgets/widget_int.py b/IPython/html/widgets/widget_int.py index 577728791..fa96676da 100644 --- a/IPython/html/widgets/widget_int.py +++ b/IPython/html/widgets/widget_int.py @@ -37,13 +37,23 @@ class _BoundedInt(_Int): def __init__(self, *pargs, **kwargs): """Constructor""" DOMWidget.__init__(self, *pargs, **kwargs) - self.on_trait_change(self._validate, ['value', 'min', 'max']) + self.on_trait_change(self._validate_value, ['value']) + self.on_trait_change(self._handle_max_changed, ['max']) + self.on_trait_change(self._handle_min_changed, ['min']) - def _validate(self, name, old, new): - """Validate value, max, min.""" + def _validate_value(self, name, old, new): + """Validate value.""" if self.min > new or new > self.max: self.value = min(max(new, self.min), self.max) + def _handle_max_changed(self, name, old, new): + """Make sure the min is always <= the max.""" + self.min = min(self.min, new) + + def _handle_min_changed(self, name, old, new): + """Make sure the max is always >= the min.""" + self.max = max(self.max, new) + class IntText(_Int): """Textbox widget that represents a int.""" From aa04d40a8d895b76ab3b13fff96d2dcbc478516e Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Tue, 23 Sep 2014 08:40:43 -0700 Subject: [PATCH 2/4] Added test --- IPython/html/tests/widgets/widget_int.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/IPython/html/tests/widgets/widget_int.js b/IPython/html/tests/widgets/widget_int.js index a9bc43d90..9f06d6dff 100644 --- a/IPython/html/tests/widgets/widget_int.js +++ b/IPython/html/tests/widgets/widget_int.js @@ -154,4 +154,24 @@ casper.notebook_test(function () { this.test.assertEquals(this.get_output_cell(index).text, '50\n', 'Invalid int textbox characters ignored'); }); + + index = this.append_cell( + 'a = widgets.IntSlider()\n' + + 'display(a)\n' + + 'a.max = -1\n' + + 'print("Success")\n'); + this.execute_cell_then(index, function(index){ + this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', + 'Invalid int range max bound does not cause crash.'); + }); + + index = this.append_cell( + 'a = widgets.IntSlider()\n' + + 'display(a)\n' + + 'a.min = 101\n' + + 'print("Success")\n'); + this.execute_cell_then(index, function(index){ + this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', + 'Invalid int range min bound does not cause crash.'); + }); }); \ No newline at end of file From aec576a5f2f5641ad57038a61de8a05c2a61c2b5 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Tue, 23 Sep 2014 12:25:02 -0700 Subject: [PATCH 3/4] Fix infinite loop typo --- IPython/html/static/widgets/js/widget_int.js | 10 +++++++++- IPython/html/tests/widgets/widget_int.js | 6 ++---- IPython/html/widgets/widget.py | 21 +++++++++++++------- IPython/html/widgets/widget_int.py | 9 ++++----- 4 files changed, 29 insertions(+), 17 deletions(-) diff --git a/IPython/html/static/widgets/js/widget_int.js b/IPython/html/static/widgets/js/widget_int.js index c8a736b86..24103cc85 100644 --- a/IPython/html/static/widgets/js/widget_int.js +++ b/IPython/html/static/widgets/js/widget_int.js @@ -43,7 +43,7 @@ define([ if (options === undefined || options.updated_view != this) { // JQuery slider option keys. These keys happen to have a // one-to-one mapping with the corrosponding keys of the model. - var jquery_slider_keys = ['step', 'max', 'min', 'disabled']; + var jquery_slider_keys = ['step', 'disabled']; var that = this; that.$slider.slider({}); _.each(jquery_slider_keys, function(key, i) { @@ -52,6 +52,14 @@ define([ that.$slider.slider("option", key, model_value); } }); + + var max = this.model.get('max'); + var min = this.model.get('min'); + if (min <= max) { + if (max !== undefined) this.$slider.slider('option', 'max', max); + if (min !== undefined) this.$slider.slider('option', 'min', min); + } + var range_value = this.model.get("_range"); if (range_value !== undefined) { this.$slider.slider("option", "range", range_value); diff --git a/IPython/html/tests/widgets/widget_int.js b/IPython/html/tests/widgets/widget_int.js index 9f06d6dff..b5170bab1 100644 --- a/IPython/html/tests/widgets/widget_int.js +++ b/IPython/html/tests/widgets/widget_int.js @@ -161,8 +161,7 @@ casper.notebook_test(function () { 'a.max = -1\n' + 'print("Success")\n'); this.execute_cell_then(index, function(index){ - this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', - 'Invalid int range max bound does not cause crash.'); + this.test.assertEquals(0, 0, 'Invalid int range max bound does not cause crash.'); }); index = this.append_cell( @@ -171,7 +170,6 @@ casper.notebook_test(function () { 'a.min = 101\n' + 'print("Success")\n'); this.execute_cell_then(index, function(index){ - this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', - 'Invalid int range min bound does not cause crash.'); + this.test.assertEquals(0, 0, 'Invalid int range min bound does not cause crash.'); }); }); \ No newline at end of file diff --git a/IPython/html/widgets/widget.py b/IPython/html/widgets/widget.py index 70a3e4f90..1739bd04a 100644 --- a/IPython/html/widgets/widget.py +++ b/IPython/html/widgets/widget.py @@ -124,7 +124,6 @@ class Widget(LoggingConfigurable): self._model_id = kwargs.pop('model_id', None) super(Widget, self).__init__(**kwargs) - self.on_trait_change(self._handle_property_changed, self.keys) Widget._call_widget_constructed(self) self.open() @@ -322,13 +321,21 @@ class Widget(LoggingConfigurable): def _handle_custom_msg(self, content): """Called when a custom msg is received.""" self._msg_callbacks(self, content) - - def _handle_property_changed(self, name, old, new): + + def _notify_trait(self, name, old_value, new_value): """Called when a property has been changed.""" - # Make sure this isn't information that the front-end just sent us. - if self._should_send_property(name, new): - # Send new state to front-end - self.send_state(key=name) + # Trigger default traitlet callback machinery. This allows any user + # registered validation to be processed prior to allowing the widget + # machinery to handle the state. + super(Widget, self)._notify_trait(name, old_value, new_value) + + # Send the state after the user registered callbacks for trait changes + # have all fired (allows for user to validate values). + if name in self.keys: + # Make sure this isn't information that the front-end just sent us. + if self._should_send_property(name, new_value): + # Send new state to front-end + self.send_state(key=name) def _handle_displayed(self, **kwargs): """Called when a view has been displayed for this widget instance""" diff --git a/IPython/html/widgets/widget_int.py b/IPython/html/widgets/widget_int.py index fa96676da..e371e0869 100644 --- a/IPython/html/widgets/widget_int.py +++ b/IPython/html/widgets/widget_int.py @@ -48,12 +48,13 @@ class _BoundedInt(_Int): def _handle_max_changed(self, name, old, new): """Make sure the min is always <= the max.""" - self.min = min(self.min, new) + if new < self.min: + raise ValueError("setting max < min") def _handle_min_changed(self, name, old, new): """Make sure the max is always >= the min.""" - self.max = max(self.max, new) - + if new > self.max: + raise ValueError("setting min > max") class IntText(_Int): """Textbox widget that represents a int.""" @@ -137,11 +138,9 @@ class _BoundedIntRange(_IntRange): if name == "min": if new > self.max: raise ValueError("setting min > max") - self.min = new elif name == "max": if new < self.min: raise ValueError("setting max < min") - self.max = new low, high = self.value if name == "value": From 341527779be2b4fd34eb38b3aec705487ee60ce0 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Thu, 25 Sep 2014 14:51:38 -0700 Subject: [PATCH 4/4] Fix notify_trait getting called too early. --- IPython/html/widgets/widget.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/html/widgets/widget.py b/IPython/html/widgets/widget.py index 1739bd04a..fd0288387 100644 --- a/IPython/html/widgets/widget.py +++ b/IPython/html/widgets/widget.py @@ -327,11 +327,11 @@ class Widget(LoggingConfigurable): # Trigger default traitlet callback machinery. This allows any user # registered validation to be processed prior to allowing the widget # machinery to handle the state. - super(Widget, self)._notify_trait(name, old_value, new_value) + LoggingConfigurable._notify_trait(self, name, old_value, new_value) # Send the state after the user registered callbacks for trait changes # have all fired (allows for user to validate values). - if name in self.keys: + if self.comm is not None and name in self.keys: # Make sure this isn't information that the front-end just sent us. if self._should_send_property(name, new_value): # Send new state to front-end