##// END OF EJS Templates
Partial implementation of styles
Jonathan Frederic -
Show More
@@ -1,119 +1,124 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 "widgets/js/widget",
5 "widgets/js/widget",
6 "jquery",
6 "jquery",
7 "bootstrap",
7 "bootstrap",
8 ], function(widget, $){
8 ], function(widget, $){
9
9
10 var CheckboxView = widget.DOMWidgetView.extend({
10 var CheckboxView = widget.DOMWidgetView.extend({
11 render : function(){
11 render : function(){
12 // Called when view is rendered.
12 // Called when view is rendered.
13 this.$el
13 this.$el
14 .addClass('widget-hbox-single');
14 .addClass('widget-hbox-single');
15 this.$label = $('<div />')
15 this.$label = $('<div />')
16 .addClass('widget-hlabel')
16 .addClass('widget-hlabel')
17 .appendTo(this.$el)
17 .appendTo(this.$el)
18 .hide();
18 .hide();
19 this.$checkbox = $('<input />')
19 this.$checkbox = $('<input />')
20 .attr('type', 'checkbox')
20 .attr('type', 'checkbox')
21 .appendTo(this.$el)
21 .appendTo(this.$el)
22 .click($.proxy(this.handle_click, this));
22 .click($.proxy(this.handle_click, this));
23
23
24 this.update(); // Set defaults.
24 this.update(); // Set defaults.
25 },
25 },
26
26
27 update_attr: function(name, value) {
28 // Set a css attr of the widget view.
29 this.$checkbox.css(name, value);
30 },
31
27 handle_click: function() {
32 handle_click: function() {
28 // Handles when the checkbox is clicked.
33 // Handles when the checkbox is clicked.
29
34
30 // Calling model.set will trigger all of the other views of the
35 // Calling model.set will trigger all of the other views of the
31 // model to update.
36 // model to update.
32 var value = this.model.get('value');
37 var value = this.model.get('value');
33 this.model.set('value', ! value, {updated_view: this});
38 this.model.set('value', ! value, {updated_view: this});
34 this.touch();
39 this.touch();
35 },
40 },
36
41
37 update : function(options){
42 update : function(options){
38 // Update the contents of this view
43 // Update the contents of this view
39 //
44 //
40 // Called when the model is changed. The model may have been
45 // Called when the model is changed. The model may have been
41 // changed by another view or by a state update from the back-end.
46 // changed by another view or by a state update from the back-end.
42 this.$checkbox.prop('checked', this.model.get('value'));
47 this.$checkbox.prop('checked', this.model.get('value'));
43
48
44 if (options === undefined || options.updated_view != this) {
49 if (options === undefined || options.updated_view != this) {
45 var disabled = this.model.get('disabled');
50 var disabled = this.model.get('disabled');
46 this.$checkbox.prop('disabled', disabled);
51 this.$checkbox.prop('disabled', disabled);
47
52
48 var description = this.model.get('description');
53 var description = this.model.get('description');
49 if (description.trim().length === 0) {
54 if (description.trim().length === 0) {
50 this.$label.hide();
55 this.$label.hide();
51 } else {
56 } else {
52 this.$label.text(description);
57 this.$label.text(description);
53 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
58 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
54 this.$label.show();
59 this.$label.show();
55 }
60 }
56 }
61 }
57 return CheckboxView.__super__.update.apply(this);
62 return CheckboxView.__super__.update.apply(this);
58 },
63 },
59
64
60 });
65 });
61
66
62
67
63 var ToggleButtonView = widget.DOMWidgetView.extend({
68 var ToggleButtonView = widget.DOMWidgetView.extend({
64 render : function() {
69 render : function() {
65 // Called when view is rendered.
70 // Called when view is rendered.
66 var that = this;
71 var that = this;
67 this.setElement($('<button />')
72 this.setElement($('<button />')
68 .addClass('btn btn-default')
73 .addClass('btn btn-default')
69 .attr('type', 'button')
74 .attr('type', 'button')
70 .on('click', function (e) {
75 .on('click', function (e) {
71 e.preventDefault();
76 e.preventDefault();
72 that.handle_click();
77 that.handle_click();
73 }));
78 }));
74
79
75 this.update(); // Set defaults.
80 this.update(); // Set defaults.
76 },
81 },
77
82
78 update : function(options){
83 update : function(options){
79 // Update the contents of this view
84 // Update the contents of this view
80 //
85 //
81 // Called when the model is changed. The model may have been
86 // Called when the model is changed. The model may have been
82 // changed by another view or by a state update from the back-end.
87 // changed by another view or by a state update from the back-end.
83 if (this.model.get('value')) {
88 if (this.model.get('value')) {
84 this.$el.addClass('active');
89 this.$el.addClass('active');
85 } else {
90 } else {
86 this.$el.removeClass('active');
91 this.$el.removeClass('active');
87 }
92 }
88
93
89 if (options === undefined || options.updated_view != this) {
94 if (options === undefined || options.updated_view != this) {
90
95
91 var disabled = this.model.get('disabled');
96 var disabled = this.model.get('disabled');
92 this.$el.prop('disabled', disabled);
97 this.$el.prop('disabled', disabled);
93
98
94 var description = this.model.get('description');
99 var description = this.model.get('description');
95 if (description.trim().length === 0) {
100 if (description.trim().length === 0) {
96 this.$el.html("&nbsp;"); // Preserve button height
101 this.$el.html("&nbsp;"); // Preserve button height
97 } else {
102 } else {
98 this.$el.text(description);
103 this.$el.text(description);
99 }
104 }
100 }
105 }
101 return ToggleButtonView.__super__.update.apply(this);
106 return ToggleButtonView.__super__.update.apply(this);
102 },
107 },
103
108
104 handle_click: function(e) {
109 handle_click: function(e) {
105 // Handles and validates user input.
110 // Handles and validates user input.
106
111
107 // Calling model.set will trigger all of the other views of the
112 // Calling model.set will trigger all of the other views of the
108 // model to update.
113 // model to update.
109 var value = this.model.get('value');
114 var value = this.model.get('value');
110 this.model.set('value', ! value, {updated_view: this});
115 this.model.set('value', ! value, {updated_view: this});
111 this.touch();
116 this.touch();
112 },
117 },
113 });
118 });
114
119
115 return {
120 return {
116 'CheckboxView': CheckboxView,
121 'CheckboxView': CheckboxView,
117 'ToggleButtonView': ToggleButtonView,
122 'ToggleButtonView': ToggleButtonView,
118 };
123 };
119 });
124 });
@@ -1,330 +1,366 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 "widgets/js/widget",
5 "widgets/js/widget",
6 "jqueryui",
6 "jqueryui",
7 "bootstrap",
7 "bootstrap",
8 ], function(widget, $){
8 ], function(widget, $){
9
9
10 var IntSliderView = widget.DOMWidgetView.extend({
10 var IntSliderView = widget.DOMWidgetView.extend({
11 render : function(){
11 render : function(){
12 // Called when view is rendered.
12 // Called when view is rendered.
13 this.$el
13 this.$el
14 .addClass('widget-hbox-single');
14 .addClass('widget-hbox-single');
15 this.$label = $('<div />')
15 this.$label = $('<div />')
16 .appendTo(this.$el)
16 .appendTo(this.$el)
17 .addClass('widget-hlabel')
17 .addClass('widget-hlabel')
18 .hide();
18 .hide();
19
19
20 this.$slider = $('<div />')
20 this.$slider = $('<div />')
21 .slider({})
21 .slider({})
22 .addClass('slider');
22 .addClass('slider');
23 // Put the slider in a container
23 // Put the slider in a container
24 this.$slider_container = $('<div />')
24 this.$slider_container = $('<div />')
25 .addClass('widget-hslider')
25 .addClass('widget-hslider')
26 .append(this.$slider);
26 .append(this.$slider);
27 this.$el.append(this.$slider_container);
27 this.$el.append(this.$slider_container);
28
28
29 this.$readout = $('<div/>')
29 this.$readout = $('<div/>')
30 .appendTo(this.$el)
30 .appendTo(this.$el)
31 .addClass('widget-hreadout')
31 .addClass('widget-hreadout')
32 .hide();
32 .hide();
33
34 this.model.on('change:slider_color', function(sender, value) {
35 this.$slider.find('a').css('background', value);
36 }, this);
33
37
34 // Set defaults.
38 // Set defaults.
35 this.update();
39 this.update();
36 },
40 },
41
42 update_attr: function(name, value) {
43 // Set a css attr of the widget view.
44 if (name == 'color') {
45 this.$readout.css(name, value);
46 } else if (name.substring(0, 4) == 'font') {
47 this.$readout.css(name, value);
48 } else if (name.substring(0, 6) == 'border') {
49 this.$slider.find('a').css(name, value);
50 this.$slider_container.css(name, value);
51 } else if (name == 'width' || name == 'height' || name == 'background') {
52 this.$slider_container.css(name, value);
53 } else {
54 this.$slider.css(name, value);
55 }
56 },
37
57
38 update : function(options){
58 update : function(options){
39 // Update the contents of this view
59 // Update the contents of this view
40 //
60 //
41 // Called when the model is changed. The model may have been
61 // Called when the model is changed. The model may have been
42 // changed by another view or by a state update from the back-end.
62 // changed by another view or by a state update from the back-end.
43 if (options === undefined || options.updated_view != this) {
63 if (options === undefined || options.updated_view != this) {
44 // JQuery slider option keys. These keys happen to have a
64 // JQuery slider option keys. These keys happen to have a
45 // one-to-one mapping with the corrosponding keys of the model.
65 // one-to-one mapping with the corrosponding keys of the model.
46 var jquery_slider_keys = ['step', 'max', 'min', 'disabled'];
66 var jquery_slider_keys = ['step', 'max', 'min', 'disabled'];
47 var that = this;
67 var that = this;
48 that.$slider.slider({});
68 that.$slider.slider({});
49 _.each(jquery_slider_keys, function(key, i) {
69 _.each(jquery_slider_keys, function(key, i) {
50 var model_value = that.model.get(key);
70 var model_value = that.model.get(key);
51 if (model_value !== undefined) {
71 if (model_value !== undefined) {
52 that.$slider.slider("option", key, model_value);
72 that.$slider.slider("option", key, model_value);
53 }
73 }
54 });
74 });
55 var range_value = this.model.get("_range");
75 var range_value = this.model.get("_range");
56 if (range_value !== undefined) {
76 if (range_value !== undefined) {
57 this.$slider.slider("option", "range", range_value);
77 this.$slider.slider("option", "range", range_value);
58 }
78 }
59
79
60 // WORKAROUND FOR JQUERY SLIDER BUG.
80 // WORKAROUND FOR JQUERY SLIDER BUG.
61 // The horizontal position of the slider handle
81 // The horizontal position of the slider handle
62 // depends on the value of the slider at the time
82 // depends on the value of the slider at the time
63 // of orientation change. Before applying the new
83 // of orientation change. Before applying the new
64 // workaround, we set the value to the minimum to
84 // workaround, we set the value to the minimum to
65 // make sure that the horizontal placement of the
85 // make sure that the horizontal placement of the
66 // handle in the vertical slider is always
86 // handle in the vertical slider is always
67 // consistent.
87 // consistent.
68 var orientation = this.model.get('orientation');
88 var orientation = this.model.get('orientation');
69 var min = this.model.get('min');
89 var min = this.model.get('min');
70 var max = this.model.get('max');
90 var max = this.model.get('max');
71 if (this.model.get('_range')) {
91 if (this.model.get('_range')) {
72 this.$slider.slider('option', 'values', [min, min]);
92 this.$slider.slider('option', 'values', [min, min]);
73 } else {
93 } else {
74 this.$slider.slider('option', 'value', min);
94 this.$slider.slider('option', 'value', min);
75 }
95 }
76 this.$slider.slider('option', 'orientation', orientation);
96 this.$slider.slider('option', 'orientation', orientation);
77 var value = this.model.get('value');
97 var value = this.model.get('value');
78 if (this.model.get('_range')) {
98 if (this.model.get('_range')) {
79 // values for the range case are validated python-side in
99 // values for the range case are validated python-side in
80 // _Bounded{Int,Float}RangeWidget._validate
100 // _Bounded{Int,Float}RangeWidget._validate
81 this.$slider.slider('option', 'values', value);
101 this.$slider.slider('option', 'values', value);
82 this.$readout.text(value.join("-"));
102 this.$readout.text(value.join("-"));
83 } else {
103 } else {
84 if(value > max) {
104 if(value > max) {
85 value = max;
105 value = max;
86 }
106 }
87 else if(value < min){
107 else if(value < min){
88 value = min;
108 value = min;
89 }
109 }
90 this.$slider.slider('option', 'value', value);
110 this.$slider.slider('option', 'value', value);
91 this.$readout.text(value);
111 this.$readout.text(value);
92 }
112 }
93
113
94 if(this.model.get('value')!=value) {
114 if(this.model.get('value')!=value) {
95 this.model.set('value', value, {updated_view: this});
115 this.model.set('value', value, {updated_view: this});
96 this.touch();
116 this.touch();
97 }
117 }
98
118
99 // Use the right CSS classes for vertical & horizontal sliders
119 // Use the right CSS classes for vertical & horizontal sliders
100 if (orientation=='vertical') {
120 if (orientation=='vertical') {
101 this.$slider_container
121 this.$slider_container
102 .removeClass('widget-hslider')
122 .removeClass('widget-hslider')
103 .addClass('widget-vslider');
123 .addClass('widget-vslider');
104 this.$el
124 this.$el
105 .removeClass('widget-hbox-single')
125 .removeClass('widget-hbox-single')
106 .addClass('widget-vbox-single');
126 .addClass('widget-vbox-single');
107 this.$label
127 this.$label
108 .removeClass('widget-hlabel')
128 .removeClass('widget-hlabel')
109 .addClass('widget-vlabel');
129 .addClass('widget-vlabel');
110 this.$readout
130 this.$readout
111 .removeClass('widget-hreadout')
131 .removeClass('widget-hreadout')
112 .addClass('widget-vreadout');
132 .addClass('widget-vreadout');
113
133
114 } else {
134 } else {
115 this.$slider_container
135 this.$slider_container
116 .removeClass('widget-vslider')
136 .removeClass('widget-vslider')
117 .addClass('widget-hslider');
137 .addClass('widget-hslider');
118 this.$el
138 this.$el
119 .removeClass('widget-vbox-single')
139 .removeClass('widget-vbox-single')
120 .addClass('widget-hbox-single');
140 .addClass('widget-hbox-single');
121 this.$label
141 this.$label
122 .removeClass('widget-vlabel')
142 .removeClass('widget-vlabel')
123 .addClass('widget-hlabel');
143 .addClass('widget-hlabel');
124 this.$readout
144 this.$readout
125 .removeClass('widget-vreadout')
145 .removeClass('widget-vreadout')
126 .addClass('widget-hreadout');
146 .addClass('widget-hreadout');
127 }
147 }
128
148
129 var description = this.model.get('description');
149 var description = this.model.get('description');
130 if (description.length === 0) {
150 if (description.length === 0) {
131 this.$label.hide();
151 this.$label.hide();
132 } else {
152 } else {
133 this.$label.text(description);
153 this.$label.text(description);
134 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
154 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
135 this.$label.show();
155 this.$label.show();
136 }
156 }
137
157
138 var readout = this.model.get('readout');
158 var readout = this.model.get('readout');
139 if (readout) {
159 if (readout) {
140 this.$readout.show();
160 this.$readout.show();
141 } else {
161 } else {
142 this.$readout.hide();
162 this.$readout.hide();
143 }
163 }
144 }
164 }
145 return IntSliderView.__super__.update.apply(this);
165 return IntSliderView.__super__.update.apply(this);
146 },
166 },
147
167
148 events: {
168 events: {
149 // Dictionary of events and their handlers.
169 // Dictionary of events and their handlers.
150 "slide" : "handleSliderChange"
170 "slide" : "handleSliderChange"
151 },
171 },
152
172
153 handleSliderChange: function(e, ui) {
173 handleSliderChange: function(e, ui) {
154 // Called when the slider value is changed.
174 // Called when the slider value is changed.
155
175
156 // Calling model.set will trigger all of the other views of the
176 // Calling model.set will trigger all of the other views of the
157 // model to update.
177 // model to update.
158 if (this.model.get("_range")) {
178 if (this.model.get("_range")) {
159 var actual_value = ui.values.map(this._validate_slide_value);
179 var actual_value = ui.values.map(this._validate_slide_value);
160 this.$readout.text(actual_value.join("-"));
180 this.$readout.text(actual_value.join("-"));
161 } else {
181 } else {
162 var actual_value = this._validate_slide_value(ui.value);
182 var actual_value = this._validate_slide_value(ui.value);
163 this.$readout.text(actual_value);
183 this.$readout.text(actual_value);
164 }
184 }
165 this.model.set('value', actual_value, {updated_view: this});
185 this.model.set('value', actual_value, {updated_view: this});
166 this.touch();
186 this.touch();
167 },
187 },
168
188
169 _validate_slide_value: function(x) {
189 _validate_slide_value: function(x) {
170 // Validate the value of the slider before sending it to the back-end
190 // Validate the value of the slider before sending it to the back-end
171 // and applying it to the other views on the page.
191 // and applying it to the other views on the page.
172
192
173 // Double bit-wise not truncates the decimel (int cast).
193 // Double bit-wise not truncates the decimel (int cast).
174 return ~~x;
194 return ~~x;
175 },
195 },
176 });
196 });
177
197
178
198
179 var IntTextView = widget.DOMWidgetView.extend({
199 var IntTextView = widget.DOMWidgetView.extend({
180 render : function(){
200 render : function(){
181 // Called when view is rendered.
201 // Called when view is rendered.
182 this.$el
202 this.$el
183 .addClass('widget-hbox-single');
203 .addClass('widget-hbox-single');
184 this.$label = $('<div />')
204 this.$label = $('<div />')
185 .appendTo(this.$el)
205 .appendTo(this.$el)
186 .addClass('widget-hlabel')
206 .addClass('widget-hlabel')
187 .hide();
207 .hide();
188 this.$textbox = $('<input type="text" />')
208 this.$textbox = $('<input type="text" />')
189 .addClass('form-control')
209 .addClass('form-control')
190 .addClass('widget-numeric-text')
210 .addClass('widget-numeric-text')
191 .appendTo(this.$el);
211 .appendTo(this.$el);
192 this.update(); // Set defaults.
212 this.update(); // Set defaults.
193 },
213 },
194
214
195 update : function(options){
215 update : function(options){
196 // Update the contents of this view
216 // Update the contents of this view
197 //
217 //
198 // Called when the model is changed. The model may have been
218 // Called when the model is changed. The model may have been
199 // changed by another view or by a state update from the back-end.
219 // changed by another view or by a state update from the back-end.
200 if (options === undefined || options.updated_view != this) {
220 if (options === undefined || options.updated_view != this) {
201 var value = this.model.get('value');
221 var value = this.model.get('value');
202 if (this._parse_value(this.$textbox.val()) != value) {
222 if (this._parse_value(this.$textbox.val()) != value) {
203 this.$textbox.val(value);
223 this.$textbox.val(value);
204 }
224 }
205
225
206 if (this.model.get('disabled')) {
226 if (this.model.get('disabled')) {
207 this.$textbox.attr('disabled','disabled');
227 this.$textbox.attr('disabled','disabled');
208 } else {
228 } else {
209 this.$textbox.removeAttr('disabled');
229 this.$textbox.removeAttr('disabled');
210 }
230 }
211
231
212 var description = this.model.get('description');
232 var description = this.model.get('description');
213 if (description.length === 0) {
233 if (description.length === 0) {
214 this.$label.hide();
234 this.$label.hide();
215 } else {
235 } else {
216 this.$label.text(description);
236 this.$label.text(description);
217 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
237 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
218 this.$label.show();
238 this.$label.show();
219 }
239 }
220 }
240 }
221 return IntTextView.__super__.update.apply(this);
241 return IntTextView.__super__.update.apply(this);
222 },
242 },
223
243
244 update_attr: function(name, value) {
245 // Set a css attr of the widget view.
246 this.$textbox.css(name, value);
247 },
248
224 events: {
249 events: {
225 // Dictionary of events and their handlers.
250 // Dictionary of events and their handlers.
226 "keyup input" : "handleChanging",
251 "keyup input" : "handleChanging",
227 "paste input" : "handleChanging",
252 "paste input" : "handleChanging",
228 "cut input" : "handleChanging",
253 "cut input" : "handleChanging",
229
254
230 // Fires only when control is validated or looses focus.
255 // Fires only when control is validated or looses focus.
231 "change input" : "handleChanged"
256 "change input" : "handleChanged"
232 },
257 },
233
258
234 handleChanging: function(e) {
259 handleChanging: function(e) {
235 // Handles and validates user input.
260 // Handles and validates user input.
236
261
237 // Try to parse value as a int.
262 // Try to parse value as a int.
238 var numericalValue = 0;
263 var numericalValue = 0;
239 if (e.target.value !== '') {
264 if (e.target.value !== '') {
240 var trimmed = e.target.value.trim();
265 var trimmed = e.target.value.trim();
241 if (!(['-', '-.', '.', '+.', '+'].indexOf(trimmed) >= 0)) {
266 if (!(['-', '-.', '.', '+.', '+'].indexOf(trimmed) >= 0)) {
242 numericalValue = this._parse_value(e.target.value);
267 numericalValue = this._parse_value(e.target.value);
243 }
268 }
244 }
269 }
245
270
246 // If parse failed, reset value to value stored in model.
271 // If parse failed, reset value to value stored in model.
247 if (isNaN(numericalValue)) {
272 if (isNaN(numericalValue)) {
248 e.target.value = this.model.get('value');
273 e.target.value = this.model.get('value');
249 } else if (!isNaN(numericalValue)) {
274 } else if (!isNaN(numericalValue)) {
250 if (this.model.get('max') !== undefined) {
275 if (this.model.get('max') !== undefined) {
251 numericalValue = Math.min(this.model.get('max'), numericalValue);
276 numericalValue = Math.min(this.model.get('max'), numericalValue);
252 }
277 }
253 if (this.model.get('min') !== undefined) {
278 if (this.model.get('min') !== undefined) {
254 numericalValue = Math.max(this.model.get('min'), numericalValue);
279 numericalValue = Math.max(this.model.get('min'), numericalValue);
255 }
280 }
256
281
257 // Apply the value if it has changed.
282 // Apply the value if it has changed.
258 if (numericalValue != this.model.get('value')) {
283 if (numericalValue != this.model.get('value')) {
259
284
260 // Calling model.set will trigger all of the other views of the
285 // Calling model.set will trigger all of the other views of the
261 // model to update.
286 // model to update.
262 this.model.set('value', numericalValue, {updated_view: this});
287 this.model.set('value', numericalValue, {updated_view: this});
263 this.touch();
288 this.touch();
264 }
289 }
265 }
290 }
266 },
291 },
267
292
268 handleChanged: function(e) {
293 handleChanged: function(e) {
269 // Applies validated input.
294 // Applies validated input.
270 if (this.model.get('value') != e.target.value) {
295 if (this.model.get('value') != e.target.value) {
271 e.target.value = this.model.get('value');
296 e.target.value = this.model.get('value');
272 }
297 }
273 },
298 },
274
299
275 _parse_value: function(value) {
300 _parse_value: function(value) {
276 // Parse the value stored in a string.
301 // Parse the value stored in a string.
277 return parseInt(value);
302 return parseInt(value);
278 },
303 },
279 });
304 });
280
305
281
306
282 var ProgressView = widget.DOMWidgetView.extend({
307 var ProgressView = widget.DOMWidgetView.extend({
283 render : function(){
308 render : function(){
284 // Called when view is rendered.
309 // Called when view is rendered.
285 this.$el
310 this.$el
286 .addClass('widget-hbox-single');
311 .addClass('widget-hbox-single');
287 this.$label = $('<div />')
312 this.$label = $('<div />')
288 .appendTo(this.$el)
313 .appendTo(this.$el)
289 .addClass('widget-hlabel')
314 .addClass('widget-hlabel')
290 .hide();
315 .hide();
291 this.$progress = $('<div />')
316 this.$progress = $('<div />')
292 .addClass('progress')
317 .addClass('progress')
293 .addClass('widget-progress')
318 .addClass('widget-progress')
294 .appendTo(this.$el);
319 .appendTo(this.$el);
295 this.$bar = $('<div />')
320 this.$bar = $('<div />')
296 .addClass('progress-bar')
321 .addClass('progress-bar')
297 .css('width', '50%')
322 .css('width', '50%')
298 .appendTo(this.$progress);
323 .appendTo(this.$progress);
299 this.update(); // Set defaults.
324 this.update(); // Set defaults.
300 },
325 },
301
326
302 update : function(){
327 update : function(){
303 // Update the contents of this view
328 // Update the contents of this view
304 //
329 //
305 // Called when the model is changed. The model may have been
330 // Called when the model is changed. The model may have been
306 // changed by another view or by a state update from the back-end.
331 // changed by another view or by a state update from the back-end.
307 var value = this.model.get('value');
332 var value = this.model.get('value');
308 var max = this.model.get('max');
333 var max = this.model.get('max');
309 var min = this.model.get('min');
334 var min = this.model.get('min');
310 var percent = 100.0 * (value - min) / (max - min);
335 var percent = 100.0 * (value - min) / (max - min);
311 this.$bar.css('width', percent + '%');
336 this.$bar.css('width', percent + '%');
312
337
313 var description = this.model.get('description');
338 var description = this.model.get('description');
314 if (description.length === 0) {
339 if (description.length === 0) {
315 this.$label.hide();
340 this.$label.hide();
316 } else {
341 } else {
317 this.$label.text(description);
342 this.$label.text(description);
318 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
343 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
319 this.$label.show();
344 this.$label.show();
320 }
345 }
321 return ProgressView.__super__.update.apply(this);
346 return ProgressView.__super__.update.apply(this);
322 },
347 },
348
349 update_attr: function(name, value) {
350 // Set a css attr of the widget view.
351 if (name.substring(0, 6) == 'border' || name == 'width' || name == 'height' || name == 'background') {
352 this.$progress.css(name, value);
353 } else if (name == 'color') {
354 this.$bar.css('background', value);
355 } else {
356 this.$bar.css(name, value);
357 }
358 },
323 });
359 });
324
360
325 return {
361 return {
326 'IntSliderView': IntSliderView,
362 'IntSliderView': IntSliderView,
327 'IntTextView': IntTextView,
363 'IntTextView': IntTextView,
328 'ProgressView': ProgressView,
364 'ProgressView': ProgressView,
329 };
365 };
330 });
366 });
@@ -1,376 +1,401 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 "widgets/js/widget",
5 "widgets/js/widget",
6 "base/js/utils",
6 "base/js/utils",
7 "jquery",
7 "jquery",
8 "bootstrap",
8 "bootstrap",
9 ], function(widget, utils, $){
9 ], function(widget, utils, $){
10
10
11 var DropdownView = widget.DOMWidgetView.extend({
11 var DropdownView = widget.DOMWidgetView.extend({
12 render : function(){
12 render : function(){
13 // Called when view is rendered.
13 // Called when view is rendered.
14 this.$el
14 this.$el
15 .addClass('widget-hbox-single');
15 .addClass('widget-hbox-single');
16 this.$label = $('<div />')
16 this.$label = $('<div />')
17 .appendTo(this.$el)
17 .appendTo(this.$el)
18 .addClass('widget-hlabel')
18 .addClass('widget-hlabel')
19 .hide();
19 .hide();
20 this.$buttongroup = $('<div />')
20 this.$buttongroup = $('<div />')
21 .addClass('widget_item')
21 .addClass('widget_item')
22 .addClass('btn-group')
22 .addClass('btn-group')
23 .appendTo(this.$el);
23 .appendTo(this.$el);
24 this.$droplabel = $('<button />')
24 this.$droplabel = $('<button />')
25 .addClass('btn btn-default')
25 .addClass('btn btn-default')
26 .addClass('widget-combo-btn')
26 .addClass('widget-combo-btn')
27 .html("&nbsp;")
27 .html("&nbsp;")
28 .appendTo(this.$buttongroup);
28 .appendTo(this.$buttongroup);
29 this.$dropbutton = $('<button />')
29 this.$dropbutton = $('<button />')
30 .addClass('btn btn-default')
30 .addClass('btn btn-default')
31 .addClass('dropdown-toggle')
31 .addClass('dropdown-toggle')
32 .addClass('widget-combo-carrot-btn')
32 .addClass('widget-combo-carrot-btn')
33 .attr('data-toggle', 'dropdown')
33 .attr('data-toggle', 'dropdown')
34 .append($('<span />').addClass("caret"))
34 .append($('<span />').addClass("caret"))
35 .appendTo(this.$buttongroup);
35 .appendTo(this.$buttongroup);
36 this.$droplist = $('<ul />')
36 this.$droplist = $('<ul />')
37 .addClass('dropdown-menu')
37 .addClass('dropdown-menu')
38 .appendTo(this.$buttongroup);
38 .appendTo(this.$buttongroup);
39
39
40 // Set defaults.
40 // Set defaults.
41 this.update();
41 this.update();
42 },
42 },
43
43
44 update : function(options){
44 update : function(options){
45 // Update the contents of this view
45 // Update the contents of this view
46 //
46 //
47 // Called when the model is changed. The model may have been
47 // Called when the model is changed. The model may have been
48 // changed by another view or by a state update from the back-end.
48 // changed by another view or by a state update from the back-end.
49
49
50 if (options === undefined || options.updated_view != this) {
50 if (options === undefined || options.updated_view != this) {
51 var selected_item_text = this.model.get('value_name');
51 var selected_item_text = this.model.get('value_name');
52 if (selected_item_text.trim().length === 0) {
52 if (selected_item_text.trim().length === 0) {
53 this.$droplabel.html("&nbsp;");
53 this.$droplabel.html("&nbsp;");
54 } else {
54 } else {
55 this.$droplabel.text(selected_item_text);
55 this.$droplabel.text(selected_item_text);
56 }
56 }
57
57
58 var items = this.model.get('value_names');
58 var items = this.model.get('value_names');
59 var $replace_droplist = $('<ul />')
59 var $replace_droplist = $('<ul />')
60 .addClass('dropdown-menu');
60 .addClass('dropdown-menu');
61 // Copy the style
62 $replace_droplist.attr('style', this.$droplist.attr('style'));
61 var that = this;
63 var that = this;
62 _.each(items, function(item, i) {
64 _.each(items, function(item, i) {
63 var item_button = $('<a href="#"/>')
65 var item_button = $('<a href="#"/>')
64 .text(item)
66 .text(item)
65 .on('click', $.proxy(that.handle_click, that));
67 .on('click', $.proxy(that.handle_click, that));
66 $replace_droplist.append($('<li />').append(item_button));
68 $replace_droplist.append($('<li />').append(item_button));
67 });
69 });
68
70
69 this.$droplist.replaceWith($replace_droplist);
71 this.$droplist.replaceWith($replace_droplist);
70 this.$droplist.remove();
72 this.$droplist.remove();
71 this.$droplist = $replace_droplist;
73 this.$droplist = $replace_droplist;
72
74
73 if (this.model.get('disabled')) {
75 if (this.model.get('disabled')) {
74 this.$buttongroup.attr('disabled','disabled');
76 this.$buttongroup.attr('disabled','disabled');
75 this.$droplabel.attr('disabled','disabled');
77 this.$droplabel.attr('disabled','disabled');
76 this.$dropbutton.attr('disabled','disabled');
78 this.$dropbutton.attr('disabled','disabled');
77 this.$droplist.attr('disabled','disabled');
79 this.$droplist.attr('disabled','disabled');
78 } else {
80 } else {
79 this.$buttongroup.removeAttr('disabled');
81 this.$buttongroup.removeAttr('disabled');
80 this.$droplabel.removeAttr('disabled');
82 this.$droplabel.removeAttr('disabled');
81 this.$dropbutton.removeAttr('disabled');
83 this.$dropbutton.removeAttr('disabled');
82 this.$droplist.removeAttr('disabled');
84 this.$droplist.removeAttr('disabled');
83 }
85 }
84
86
85 var description = this.model.get('description');
87 var description = this.model.get('description');
86 if (description.length === 0) {
88 if (description.length === 0) {
87 this.$label.hide();
89 this.$label.hide();
88 } else {
90 } else {
89 this.$label.text(description);
91 this.$label.text(description);
90 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
92 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
91 this.$label.show();
93 this.$label.show();
92 }
94 }
93 }
95 }
94 return DropdownView.__super__.update.apply(this);
96 return DropdownView.__super__.update.apply(this);
95 },
97 },
96
98
99 update_attr: function(name, value) {
100 // Set a css attr of the widget view.
101 if (name.substring(0, 6) == 'border' || name == 'background' || name == 'color') {
102 this.$droplabel.css(name, value);
103 this.$dropbutton.css(name, value);
104 this.$droplist.css(name, value);
105 } if (name.substring(0, 4) == 'font') {
106 this.$droplabel.css(name, value);
107 this.$droplist.css(name, value);
108 } else if (name == 'width') {
109 this.$buttongroup.width(value);
110 var width = value - this.$dropbutton.width();
111 this.$droplist.css(name, width);
112 this.$droplabel.css(name, width);
113 } else if (name == 'height') {
114 this.$droplist.css(name, value);
115 this.$dropbutton.css(name, value);
116 } else {
117 this.$droplabel.css(name, value);
118 this.$droplist.css(name, value);
119 }
120 },
121
97 handle_click: function (e) {
122 handle_click: function (e) {
98 // Handle when a value is clicked.
123 // Handle when a value is clicked.
99
124
100 // Calling model.set will trigger all of the other views of the
125 // Calling model.set will trigger all of the other views of the
101 // model to update.
126 // model to update.
102 this.model.set('value_name', $(e.target).text(), {updated_view: this});
127 this.model.set('value_name', $(e.target).text(), {updated_view: this});
103 this.touch();
128 this.touch();
104 },
129 },
105
130
106 });
131 });
107
132
108
133
109 var RadioButtonsView = widget.DOMWidgetView.extend({
134 var RadioButtonsView = widget.DOMWidgetView.extend({
110 render : function(){
135 render : function(){
111 // Called when view is rendered.
136 // Called when view is rendered.
112 this.$el
137 this.$el
113 .addClass('widget-hbox');
138 .addClass('widget-hbox');
114 this.$label = $('<div />')
139 this.$label = $('<div />')
115 .appendTo(this.$el)
140 .appendTo(this.$el)
116 .addClass('widget-hlabel')
141 .addClass('widget-hlabel')
117 .hide();
142 .hide();
118 this.$container = $('<div />')
143 this.$container = $('<div />')
119 .appendTo(this.$el)
144 .appendTo(this.$el)
120 .addClass('widget-radio-box');
145 .addClass('widget-radio-box');
121 this.update();
146 this.update();
122 },
147 },
123
148
124 update : function(options){
149 update : function(options){
125 // Update the contents of this view
150 // Update the contents of this view
126 //
151 //
127 // Called when the model is changed. The model may have been
152 // Called when the model is changed. The model may have been
128 // changed by another view or by a state update from the back-end.
153 // changed by another view or by a state update from the back-end.
129 if (options === undefined || options.updated_view != this) {
154 if (options === undefined || options.updated_view != this) {
130 // Add missing items to the DOM.
155 // Add missing items to the DOM.
131 var items = this.model.get('value_names');
156 var items = this.model.get('value_names');
132 var disabled = this.model.get('disabled');
157 var disabled = this.model.get('disabled');
133 var that = this;
158 var that = this;
134 _.each(items, function(item, index) {
159 _.each(items, function(item, index) {
135 var item_query = ' :input[value="' + item + '"]';
160 var item_query = ' :input[value="' + item + '"]';
136 if (that.$el.find(item_query).length === 0) {
161 if (that.$el.find(item_query).length === 0) {
137 var $label = $('<label />')
162 var $label = $('<label />')
138 .addClass('radio')
163 .addClass('radio')
139 .text(item)
164 .text(item)
140 .appendTo(that.$container);
165 .appendTo(that.$container);
141
166
142 $('<input />')
167 $('<input />')
143 .attr('type', 'radio')
168 .attr('type', 'radio')
144 .addClass(that.model)
169 .addClass(that.model)
145 .val(item)
170 .val(item)
146 .prependTo($label)
171 .prependTo($label)
147 .on('click', $.proxy(that.handle_click, that));
172 .on('click', $.proxy(that.handle_click, that));
148 }
173 }
149
174
150 var $item_element = that.$container.find(item_query);
175 var $item_element = that.$container.find(item_query);
151 if (that.model.get('value_name') == item) {
176 if (that.model.get('value_name') == item) {
152 $item_element.prop('checked', true);
177 $item_element.prop('checked', true);
153 } else {
178 } else {
154 $item_element.prop('checked', false);
179 $item_element.prop('checked', false);
155 }
180 }
156 $item_element.prop('disabled', disabled);
181 $item_element.prop('disabled', disabled);
157 });
182 });
158
183
159 // Remove items that no longer exist.
184 // Remove items that no longer exist.
160 this.$container.find('input').each(function(i, obj) {
185 this.$container.find('input').each(function(i, obj) {
161 var value = $(obj).val();
186 var value = $(obj).val();
162 var found = false;
187 var found = false;
163 _.each(items, function(item, index) {
188 _.each(items, function(item, index) {
164 if (item == value) {
189 if (item == value) {
165 found = true;
190 found = true;
166 return false;
191 return false;
167 }
192 }
168 });
193 });
169
194
170 if (!found) {
195 if (!found) {
171 $(obj).parent().remove();
196 $(obj).parent().remove();
172 }
197 }
173 });
198 });
174
199
175 var description = this.model.get('description');
200 var description = this.model.get('description');
176 if (description.length === 0) {
201 if (description.length === 0) {
177 this.$label.hide();
202 this.$label.hide();
178 } else {
203 } else {
179 this.$label.text(description);
204 this.$label.text(description);
180 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
205 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
181 this.$label.show();
206 this.$label.show();
182 }
207 }
183 }
208 }
184 return RadioButtonsView.__super__.update.apply(this);
209 return RadioButtonsView.__super__.update.apply(this);
185 },
210 },
186
211
187 handle_click: function (e) {
212 handle_click: function (e) {
188 // Handle when a value is clicked.
213 // Handle when a value is clicked.
189
214
190 // Calling model.set will trigger all of the other views of the
215 // Calling model.set will trigger all of the other views of the
191 // model to update.
216 // model to update.
192 this.model.set('value_name', $(e.target).val(), {updated_view: this});
217 this.model.set('value_name', $(e.target).val(), {updated_view: this});
193 this.touch();
218 this.touch();
194 },
219 },
195 });
220 });
196
221
197
222
198 var ToggleButtonsView = widget.DOMWidgetView.extend({
223 var ToggleButtonsView = widget.DOMWidgetView.extend({
199 render : function(){
224 render : function(){
200 // Called when view is rendered.
225 // Called when view is rendered.
201 this.$el
226 this.$el
202 .addClass('widget-hbox-single');
227 .addClass('widget-hbox-single');
203 this.$label = $('<div />')
228 this.$label = $('<div />')
204 .appendTo(this.$el)
229 .appendTo(this.$el)
205 .addClass('widget-hlabel')
230 .addClass('widget-hlabel')
206 .hide();
231 .hide();
207 this.$buttongroup = $('<div />')
232 this.$buttongroup = $('<div />')
208 .addClass('btn-group')
233 .addClass('btn-group')
209 .attr('data-toggle', 'buttons-radio')
234 .attr('data-toggle', 'buttons-radio')
210 .appendTo(this.$el);
235 .appendTo(this.$el);
211 this.update();
236 this.update();
212 },
237 },
213
238
214 update : function(options){
239 update : function(options){
215 // Update the contents of this view
240 // Update the contents of this view
216 //
241 //
217 // Called when the model is changed. The model may have been
242 // Called when the model is changed. The model may have been
218 // changed by another view or by a state update from the back-end.
243 // changed by another view or by a state update from the back-end.
219 if (options === undefined || options.updated_view != this) {
244 if (options === undefined || options.updated_view != this) {
220 // Add missing items to the DOM.
245 // Add missing items to the DOM.
221 var items = this.model.get('value_names');
246 var items = this.model.get('value_names');
222 var disabled = this.model.get('disabled');
247 var disabled = this.model.get('disabled');
223 var that = this;
248 var that = this;
224 var item_html;
249 var item_html;
225 _.each(items, function(item, index) {
250 _.each(items, function(item, index) {
226 if (item.trim().length == 0) {
251 if (item.trim().length == 0) {
227 item_html = "&nbsp;";
252 item_html = "&nbsp;";
228 } else {
253 } else {
229 item_html = utils.escape_html(item);
254 item_html = utils.escape_html(item);
230 }
255 }
231 var item_query = '[data-value="' + item + '"]';
256 var item_query = '[data-value="' + item + '"]';
232 var $item_element = that.$buttongroup.find(item_query);
257 var $item_element = that.$buttongroup.find(item_query);
233 if (!$item_element.length) {
258 if (!$item_element.length) {
234 $item_element = $('<button/>')
259 $item_element = $('<button/>')
235 .attr('type', 'button')
260 .attr('type', 'button')
236 .addClass('btn btn-default')
261 .addClass('btn btn-default')
237 .html(item_html)
262 .html(item_html)
238 .appendTo(that.$buttongroup)
263 .appendTo(that.$buttongroup)
239 .attr('data-value', item)
264 .attr('data-value', item)
240 .on('click', $.proxy(that.handle_click, that));
265 .on('click', $.proxy(that.handle_click, that));
241 }
266 }
242 if (that.model.get('value_name') == item) {
267 if (that.model.get('value_name') == item) {
243 $item_element.addClass('active');
268 $item_element.addClass('active');
244 } else {
269 } else {
245 $item_element.removeClass('active');
270 $item_element.removeClass('active');
246 }
271 }
247 $item_element.prop('disabled', disabled);
272 $item_element.prop('disabled', disabled);
248 });
273 });
249
274
250 // Remove items that no longer exist.
275 // Remove items that no longer exist.
251 this.$buttongroup.find('button').each(function(i, obj) {
276 this.$buttongroup.find('button').each(function(i, obj) {
252 var value = $(obj).data('value');
277 var value = $(obj).data('value');
253 var found = false;
278 var found = false;
254 _.each(items, function(item, index) {
279 _.each(items, function(item, index) {
255 if (item == value) {
280 if (item == value) {
256 found = true;
281 found = true;
257 return false;
282 return false;
258 }
283 }
259 });
284 });
260
285
261 if (!found) {
286 if (!found) {
262 $(obj).remove();
287 $(obj).remove();
263 }
288 }
264 });
289 });
265
290
266 var description = this.model.get('description');
291 var description = this.model.get('description');
267 if (description.length === 0) {
292 if (description.length === 0) {
268 this.$label.hide();
293 this.$label.hide();
269 } else {
294 } else {
270 this.$label.text(description);
295 this.$label.text(description);
271 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
296 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
272 this.$label.show();
297 this.$label.show();
273 }
298 }
274 }
299 }
275 return ToggleButtonsView.__super__.update.apply(this);
300 return ToggleButtonsView.__super__.update.apply(this);
276 },
301 },
277
302
278 handle_click: function (e) {
303 handle_click: function (e) {
279 // Handle when a value is clicked.
304 // Handle when a value is clicked.
280
305
281 // Calling model.set will trigger all of the other views of the
306 // Calling model.set will trigger all of the other views of the
282 // model to update.
307 // model to update.
283 this.model.set('value_name', $(e.target).data('value'), {updated_view: this});
308 this.model.set('value_name', $(e.target).data('value'), {updated_view: this});
284 this.touch();
309 this.touch();
285 },
310 },
286 });
311 });
287
312
288
313
289 var SelectView = widget.DOMWidgetView.extend({
314 var SelectView = widget.DOMWidgetView.extend({
290 render : function(){
315 render : function(){
291 // Called when view is rendered.
316 // Called when view is rendered.
292 this.$el
317 this.$el
293 .addClass('widget-hbox');
318 .addClass('widget-hbox');
294 this.$label = $('<div />')
319 this.$label = $('<div />')
295 .appendTo(this.$el)
320 .appendTo(this.$el)
296 .addClass('widget-hlabel')
321 .addClass('widget-hlabel')
297 .hide();
322 .hide();
298 this.$listbox = $('<select />')
323 this.$listbox = $('<select />')
299 .addClass('widget-listbox form-control')
324 .addClass('widget-listbox form-control')
300 .attr('size', 6)
325 .attr('size', 6)
301 .appendTo(this.$el);
326 .appendTo(this.$el);
302 this.update();
327 this.update();
303 },
328 },
304
329
305 update : function(options){
330 update : function(options){
306 // Update the contents of this view
331 // Update the contents of this view
307 //
332 //
308 // Called when the model is changed. The model may have been
333 // Called when the model is changed. The model may have been
309 // changed by another view or by a state update from the back-end.
334 // changed by another view or by a state update from the back-end.
310 if (options === undefined || options.updated_view != this) {
335 if (options === undefined || options.updated_view != this) {
311 // Add missing items to the DOM.
336 // Add missing items to the DOM.
312 var items = this.model.get('value_names');
337 var items = this.model.get('value_names');
313 var that = this;
338 var that = this;
314 _.each(items, function(item, index) {
339 _.each(items, function(item, index) {
315 var item_query = ' :contains("' + item + '")';
340 var item_query = ' :contains("' + item + '")';
316 if (that.$listbox.find(item_query).length === 0) {
341 if (that.$listbox.find(item_query).length === 0) {
317 $('<option />')
342 $('<option />')
318 .text(item)
343 .text(item)
319 .attr('value_name', item)
344 .attr('value_name', item)
320 .appendTo(that.$listbox)
345 .appendTo(that.$listbox)
321 .on('click', $.proxy(that.handle_click, that));
346 .on('click', $.proxy(that.handle_click, that));
322 }
347 }
323 });
348 });
324
349
325 // Select the correct element
350 // Select the correct element
326 this.$listbox.val(this.model.get('value_name'));
351 this.$listbox.val(this.model.get('value_name'));
327
352
328 // Disable listbox if needed
353 // Disable listbox if needed
329 var disabled = this.model.get('disabled');
354 var disabled = this.model.get('disabled');
330 this.$listbox.prop('disabled', disabled);
355 this.$listbox.prop('disabled', disabled);
331
356
332 // Remove items that no longer exist.
357 // Remove items that no longer exist.
333 this.$listbox.find('option').each(function(i, obj) {
358 this.$listbox.find('option').each(function(i, obj) {
334 var value = $(obj).text();
359 var value = $(obj).text();
335 var found = false;
360 var found = false;
336 _.each(items, function(item, index) {
361 _.each(items, function(item, index) {
337 if (item == value) {
362 if (item == value) {
338 found = true;
363 found = true;
339 return false;
364 return false;
340 }
365 }
341 });
366 });
342
367
343 if (!found) {
368 if (!found) {
344 $(obj).remove();
369 $(obj).remove();
345 }
370 }
346 });
371 });
347
372
348 var description = this.model.get('description');
373 var description = this.model.get('description');
349 if (description.length === 0) {
374 if (description.length === 0) {
350 this.$label.hide();
375 this.$label.hide();
351 } else {
376 } else {
352 this.$label.text(description);
377 this.$label.text(description);
353 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
378 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
354 this.$label.show();
379 this.$label.show();
355 }
380 }
356 }
381 }
357 return SelectView.__super__.update.apply(this);
382 return SelectView.__super__.update.apply(this);
358 },
383 },
359
384
360 handle_click: function (e) {
385 handle_click: function (e) {
361 // Handle when a value is clicked.
386 // Handle when a value is clicked.
362
387
363 // Calling model.set will trigger all of the other views of the
388 // Calling model.set will trigger all of the other views of the
364 // model to update.
389 // model to update.
365 this.model.set('value_name', $(e.target).text(), {updated_view: this});
390 this.model.set('value_name', $(e.target).text(), {updated_view: this});
366 this.touch();
391 this.touch();
367 },
392 },
368 });
393 });
369
394
370 return {
395 return {
371 'DropdownView': DropdownView,
396 'DropdownView': DropdownView,
372 'RadioButtonsView': RadioButtonsView,
397 'RadioButtonsView': RadioButtonsView,
373 'ToggleButtonsView': ToggleButtonsView,
398 'ToggleButtonsView': ToggleButtonsView,
374 'SelectView': SelectView,
399 'SelectView': SelectView,
375 };
400 };
376 });
401 });
@@ -1,425 +1,425 b''
1 """Base Widget class. Allows user to create widgets in the back-end that render
1 """Base Widget class. Allows user to create widgets in the back-end that render
2 in the IPython notebook front-end.
2 in the IPython notebook front-end.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2013, the IPython Development Team.
5 # Copyright (c) 2013, the IPython Development Team.
6 #
6 #
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8 #
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from contextlib import contextmanager
15 from contextlib import contextmanager
16 import collections
16 import collections
17
17
18 from IPython.core.getipython import get_ipython
18 from IPython.core.getipython import get_ipython
19 from IPython.kernel.comm import Comm
19 from IPython.kernel.comm import Comm
20 from IPython.config import LoggingConfigurable
20 from IPython.config import LoggingConfigurable
21 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List,
21 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, \
22 CaselessStrEnum, Tuple, CTuple, CUnicode, Int, Set
22 CaselessStrEnum, Tuple, CUnicode, Int, Set
23 from IPython.utils.py3compat import string_types
23 from IPython.utils.py3compat import string_types
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Classes
26 # Classes
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 class CallbackDispatcher(LoggingConfigurable):
28 class CallbackDispatcher(LoggingConfigurable):
29 """A structure for registering and running callbacks"""
29 """A structure for registering and running callbacks"""
30 callbacks = List()
30 callbacks = List()
31
31
32 def __call__(self, *args, **kwargs):
32 def __call__(self, *args, **kwargs):
33 """Call all of the registered callbacks."""
33 """Call all of the registered callbacks."""
34 value = None
34 value = None
35 for callback in self.callbacks:
35 for callback in self.callbacks:
36 try:
36 try:
37 local_value = callback(*args, **kwargs)
37 local_value = callback(*args, **kwargs)
38 except Exception as e:
38 except Exception as e:
39 ip = get_ipython()
39 ip = get_ipython()
40 if ip is None:
40 if ip is None:
41 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
41 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
42 else:
42 else:
43 ip.showtraceback()
43 ip.showtraceback()
44 else:
44 else:
45 value = local_value if local_value is not None else value
45 value = local_value if local_value is not None else value
46 return value
46 return value
47
47
48 def register_callback(self, callback, remove=False):
48 def register_callback(self, callback, remove=False):
49 """(Un)Register a callback
49 """(Un)Register a callback
50
50
51 Parameters
51 Parameters
52 ----------
52 ----------
53 callback: method handle
53 callback: method handle
54 Method to be registered or unregistered.
54 Method to be registered or unregistered.
55 remove=False: bool
55 remove=False: bool
56 Whether to unregister the callback."""
56 Whether to unregister the callback."""
57
57
58 # (Un)Register the callback.
58 # (Un)Register the callback.
59 if remove and callback in self.callbacks:
59 if remove and callback in self.callbacks:
60 self.callbacks.remove(callback)
60 self.callbacks.remove(callback)
61 elif not remove and callback not in self.callbacks:
61 elif not remove and callback not in self.callbacks:
62 self.callbacks.append(callback)
62 self.callbacks.append(callback)
63
63
64 def _show_traceback(method):
64 def _show_traceback(method):
65 """decorator for showing tracebacks in IPython"""
65 """decorator for showing tracebacks in IPython"""
66 def m(self, *args, **kwargs):
66 def m(self, *args, **kwargs):
67 try:
67 try:
68 return(method(self, *args, **kwargs))
68 return(method(self, *args, **kwargs))
69 except Exception as e:
69 except Exception as e:
70 ip = get_ipython()
70 ip = get_ipython()
71 if ip is None:
71 if ip is None:
72 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
72 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
73 else:
73 else:
74 ip.showtraceback()
74 ip.showtraceback()
75 return m
75 return m
76
76
77 class Widget(LoggingConfigurable):
77 class Widget(LoggingConfigurable):
78 #-------------------------------------------------------------------------
78 #-------------------------------------------------------------------------
79 # Class attributes
79 # Class attributes
80 #-------------------------------------------------------------------------
80 #-------------------------------------------------------------------------
81 _widget_construction_callback = None
81 _widget_construction_callback = None
82 widgets = {}
82 widgets = {}
83
83
84 @staticmethod
84 @staticmethod
85 def on_widget_constructed(callback):
85 def on_widget_constructed(callback):
86 """Registers a callback to be called when a widget is constructed.
86 """Registers a callback to be called when a widget is constructed.
87
87
88 The callback must have the following signature:
88 The callback must have the following signature:
89 callback(widget)"""
89 callback(widget)"""
90 Widget._widget_construction_callback = callback
90 Widget._widget_construction_callback = callback
91
91
92 @staticmethod
92 @staticmethod
93 def _call_widget_constructed(widget):
93 def _call_widget_constructed(widget):
94 """Static method, called when a widget is constructed."""
94 """Static method, called when a widget is constructed."""
95 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
95 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
96 Widget._widget_construction_callback(widget)
96 Widget._widget_construction_callback(widget)
97
97
98 #-------------------------------------------------------------------------
98 #-------------------------------------------------------------------------
99 # Traits
99 # Traits
100 #-------------------------------------------------------------------------
100 #-------------------------------------------------------------------------
101 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
101 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
102 registered in the front-end to create and sync this widget with.""")
102 registered in the front-end to create and sync this widget with.""")
103 _view_name = Unicode('WidgetView', help="""Default view registered in the front-end
103 _view_name = Unicode('WidgetView', help="""Default view registered in the front-end
104 to use to represent the widget.""", sync=True)
104 to use to represent the widget.""", sync=True)
105 comm = Instance('IPython.kernel.comm.Comm')
105 comm = Instance('IPython.kernel.comm.Comm')
106
106
107 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
107 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
108 front-end can send before receiving an idle msg from the back-end.""")
108 front-end can send before receiving an idle msg from the back-end.""")
109
109
110 keys = List()
110 keys = List()
111 def _keys_default(self):
111 def _keys_default(self):
112 return [name for name in self.traits(sync=True)]
112 return [name for name in self.traits(sync=True)]
113
113
114 _property_lock = Tuple((None, None))
114 _property_lock = Tuple((None, None))
115 _send_state_lock = Int(0)
115 _send_state_lock = Int(0)
116 _states_to_send = Set(allow_none=False)
116 _states_to_send = Set(allow_none=False)
117 _display_callbacks = Instance(CallbackDispatcher, ())
117 _display_callbacks = Instance(CallbackDispatcher, ())
118 _msg_callbacks = Instance(CallbackDispatcher, ())
118 _msg_callbacks = Instance(CallbackDispatcher, ())
119
119
120 #-------------------------------------------------------------------------
120 #-------------------------------------------------------------------------
121 # (Con/de)structor
121 # (Con/de)structor
122 #-------------------------------------------------------------------------
122 #-------------------------------------------------------------------------
123 def __init__(self, **kwargs):
123 def __init__(self, **kwargs):
124 """Public constructor"""
124 """Public constructor"""
125 self._model_id = kwargs.pop('model_id', None)
125 self._model_id = kwargs.pop('model_id', None)
126 super(Widget, self).__init__(**kwargs)
126 super(Widget, self).__init__(**kwargs)
127
127
128 self.on_trait_change(self._handle_property_changed, self.keys)
128 self.on_trait_change(self._handle_property_changed, self.keys)
129 Widget._call_widget_constructed(self)
129 Widget._call_widget_constructed(self)
130 self.open()
130 self.open()
131
131
132 def __del__(self):
132 def __del__(self):
133 """Object disposal"""
133 """Object disposal"""
134 self.close()
134 self.close()
135
135
136 #-------------------------------------------------------------------------
136 #-------------------------------------------------------------------------
137 # Properties
137 # Properties
138 #-------------------------------------------------------------------------
138 #-------------------------------------------------------------------------
139
139
140 def open(self):
140 def open(self):
141 """Open a comm to the frontend if one isn't already open."""
141 """Open a comm to the frontend if one isn't already open."""
142 if self.comm is None:
142 if self.comm is None:
143 if self._model_id is None:
143 if self._model_id is None:
144 self.comm = Comm(target_name=self._model_name)
144 self.comm = Comm(target_name=self._model_name)
145 self._model_id = self.model_id
145 self._model_id = self.model_id
146 else:
146 else:
147 self.comm = Comm(target_name=self._model_name, comm_id=self._model_id)
147 self.comm = Comm(target_name=self._model_name, comm_id=self._model_id)
148 self.comm.on_msg(self._handle_msg)
148 self.comm.on_msg(self._handle_msg)
149 Widget.widgets[self.model_id] = self
149 Widget.widgets[self.model_id] = self
150
150
151 # first update
151 # first update
152 self.send_state()
152 self.send_state()
153
153
154 @property
154 @property
155 def model_id(self):
155 def model_id(self):
156 """Gets the model id of this widget.
156 """Gets the model id of this widget.
157
157
158 If a Comm doesn't exist yet, a Comm will be created automagically."""
158 If a Comm doesn't exist yet, a Comm will be created automagically."""
159 return self.comm.comm_id
159 return self.comm.comm_id
160
160
161 #-------------------------------------------------------------------------
161 #-------------------------------------------------------------------------
162 # Methods
162 # Methods
163 #-------------------------------------------------------------------------
163 #-------------------------------------------------------------------------
164
164
165 def close(self):
165 def close(self):
166 """Close method.
166 """Close method.
167
167
168 Closes the underlying comm.
168 Closes the underlying comm.
169 When the comm is closed, all of the widget views are automatically
169 When the comm is closed, all of the widget views are automatically
170 removed from the front-end."""
170 removed from the front-end."""
171 if self.comm is not None:
171 if self.comm is not None:
172 Widget.widgets.pop(self.model_id, None)
172 Widget.widgets.pop(self.model_id, None)
173 self.comm.close()
173 self.comm.close()
174 self.comm = None
174 self.comm = None
175
175
176 def send_state(self, key=None):
176 def send_state(self, key=None):
177 """Sends the widget state, or a piece of it, to the front-end.
177 """Sends the widget state, or a piece of it, to the front-end.
178
178
179 Parameters
179 Parameters
180 ----------
180 ----------
181 key : unicode, or iterable (optional)
181 key : unicode, or iterable (optional)
182 A single property's name or iterable of property names to sync with the front-end.
182 A single property's name or iterable of property names to sync with the front-end.
183 """
183 """
184 self._send({
184 self._send({
185 "method" : "update",
185 "method" : "update",
186 "state" : self.get_state(key=key)
186 "state" : self.get_state(key=key)
187 })
187 })
188
188
189 def get_state(self, key=None):
189 def get_state(self, key=None):
190 """Gets the widget state, or a piece of it.
190 """Gets the widget state, or a piece of it.
191
191
192 Parameters
192 Parameters
193 ----------
193 ----------
194 key : unicode or iterable (optional)
194 key : unicode or iterable (optional)
195 A single property's name or iterable of property names to get.
195 A single property's name or iterable of property names to get.
196 """
196 """
197 if key is None:
197 if key is None:
198 keys = self.keys
198 keys = self.keys
199 elif isinstance(key, string_types):
199 elif isinstance(key, string_types):
200 keys = [key]
200 keys = [key]
201 elif isinstance(key, collections.Iterable):
201 elif isinstance(key, collections.Iterable):
202 keys = key
202 keys = key
203 else:
203 else:
204 raise ValueError("key must be a string, an iterable of keys, or None")
204 raise ValueError("key must be a string, an iterable of keys, or None")
205 state = {}
205 state = {}
206 for k in keys:
206 for k in keys:
207 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
207 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
208 value = getattr(self, k)
208 value = getattr(self, k)
209 state[k] = f(value)
209 state[k] = f(value)
210 return state
210 return state
211
211
212 def send(self, content):
212 def send(self, content):
213 """Sends a custom msg to the widget model in the front-end.
213 """Sends a custom msg to the widget model in the front-end.
214
214
215 Parameters
215 Parameters
216 ----------
216 ----------
217 content : dict
217 content : dict
218 Content of the message to send.
218 Content of the message to send.
219 """
219 """
220 self._send({"method": "custom", "content": content})
220 self._send({"method": "custom", "content": content})
221
221
222 def on_msg(self, callback, remove=False):
222 def on_msg(self, callback, remove=False):
223 """(Un)Register a custom msg receive callback.
223 """(Un)Register a custom msg receive callback.
224
224
225 Parameters
225 Parameters
226 ----------
226 ----------
227 callback: callable
227 callback: callable
228 callback will be passed two arguments when a message arrives::
228 callback will be passed two arguments when a message arrives::
229
229
230 callback(widget, content)
230 callback(widget, content)
231
231
232 remove: bool
232 remove: bool
233 True if the callback should be unregistered."""
233 True if the callback should be unregistered."""
234 self._msg_callbacks.register_callback(callback, remove=remove)
234 self._msg_callbacks.register_callback(callback, remove=remove)
235
235
236 def on_displayed(self, callback, remove=False):
236 def on_displayed(self, callback, remove=False):
237 """(Un)Register a widget displayed callback.
237 """(Un)Register a widget displayed callback.
238
238
239 Parameters
239 Parameters
240 ----------
240 ----------
241 callback: method handler
241 callback: method handler
242 Must have a signature of::
242 Must have a signature of::
243
243
244 callback(widget, **kwargs)
244 callback(widget, **kwargs)
245
245
246 kwargs from display are passed through without modification.
246 kwargs from display are passed through without modification.
247 remove: bool
247 remove: bool
248 True if the callback should be unregistered."""
248 True if the callback should be unregistered."""
249 self._display_callbacks.register_callback(callback, remove=remove)
249 self._display_callbacks.register_callback(callback, remove=remove)
250
250
251 #-------------------------------------------------------------------------
251 #-------------------------------------------------------------------------
252 # Support methods
252 # Support methods
253 #-------------------------------------------------------------------------
253 #-------------------------------------------------------------------------
254 @contextmanager
254 @contextmanager
255 def _lock_property(self, key, value):
255 def _lock_property(self, key, value):
256 """Lock a property-value pair.
256 """Lock a property-value pair.
257
257
258 The value should be the JSON state of the property.
258 The value should be the JSON state of the property.
259
259
260 NOTE: This, in addition to the single lock for all state changes, is
260 NOTE: This, in addition to the single lock for all state changes, is
261 flawed. In the future we may want to look into buffering state changes
261 flawed. In the future we may want to look into buffering state changes
262 back to the front-end."""
262 back to the front-end."""
263 self._property_lock = (key, value)
263 self._property_lock = (key, value)
264 try:
264 try:
265 yield
265 yield
266 finally:
266 finally:
267 self._property_lock = (None, None)
267 self._property_lock = (None, None)
268
268
269 @contextmanager
269 @contextmanager
270 def hold_sync(self):
270 def hold_sync(self):
271 """Hold syncing any state until the context manager is released"""
271 """Hold syncing any state until the context manager is released"""
272 # We increment a value so that this can be nested. Syncing will happen when
272 # We increment a value so that this can be nested. Syncing will happen when
273 # all levels have been released.
273 # all levels have been released.
274 self._send_state_lock += 1
274 self._send_state_lock += 1
275 try:
275 try:
276 yield
276 yield
277 finally:
277 finally:
278 self._send_state_lock -=1
278 self._send_state_lock -=1
279 if self._send_state_lock == 0:
279 if self._send_state_lock == 0:
280 self.send_state(self._states_to_send)
280 self.send_state(self._states_to_send)
281 self._states_to_send.clear()
281 self._states_to_send.clear()
282
282
283 def _should_send_property(self, key, value):
283 def _should_send_property(self, key, value):
284 """Check the property lock (property_lock)"""
284 """Check the property lock (property_lock)"""
285 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
285 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
286 if (key == self._property_lock[0]
286 if (key == self._property_lock[0]
287 and to_json(value) == self._property_lock[1]):
287 and to_json(value) == self._property_lock[1]):
288 return False
288 return False
289 elif self._send_state_lock > 0:
289 elif self._send_state_lock > 0:
290 self._states_to_send.add(key)
290 self._states_to_send.add(key)
291 return False
291 return False
292 else:
292 else:
293 return True
293 return True
294
294
295 # Event handlers
295 # Event handlers
296 @_show_traceback
296 @_show_traceback
297 def _handle_msg(self, msg):
297 def _handle_msg(self, msg):
298 """Called when a msg is received from the front-end"""
298 """Called when a msg is received from the front-end"""
299 data = msg['content']['data']
299 data = msg['content']['data']
300 method = data['method']
300 method = data['method']
301 if not method in ['backbone', 'custom']:
301 if not method in ['backbone', 'custom']:
302 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
302 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
303
303
304 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
304 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
305 if method == 'backbone' and 'sync_data' in data:
305 if method == 'backbone' and 'sync_data' in data:
306 sync_data = data['sync_data']
306 sync_data = data['sync_data']
307 self._handle_receive_state(sync_data) # handles all methods
307 self._handle_receive_state(sync_data) # handles all methods
308
308
309 # Handle a custom msg from the front-end
309 # Handle a custom msg from the front-end
310 elif method == 'custom':
310 elif method == 'custom':
311 if 'content' in data:
311 if 'content' in data:
312 self._handle_custom_msg(data['content'])
312 self._handle_custom_msg(data['content'])
313
313
314 def _handle_receive_state(self, sync_data):
314 def _handle_receive_state(self, sync_data):
315 """Called when a state is received from the front-end."""
315 """Called when a state is received from the front-end."""
316 for name in self.keys:
316 for name in self.keys:
317 if name in sync_data:
317 if name in sync_data:
318 json_value = sync_data[name]
318 json_value = sync_data[name]
319 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
319 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
320 with self._lock_property(name, json_value):
320 with self._lock_property(name, json_value):
321 setattr(self, name, from_json(json_value))
321 setattr(self, name, from_json(json_value))
322
322
323 def _handle_custom_msg(self, content):
323 def _handle_custom_msg(self, content):
324 """Called when a custom msg is received."""
324 """Called when a custom msg is received."""
325 self._msg_callbacks(self, content)
325 self._msg_callbacks(self, content)
326
326
327 def _handle_property_changed(self, name, old, new):
327 def _handle_property_changed(self, name, old, new):
328 """Called when a property has been changed."""
328 """Called when a property has been changed."""
329 # Make sure this isn't information that the front-end just sent us.
329 # Make sure this isn't information that the front-end just sent us.
330 if self._should_send_property(name, new):
330 if self._should_send_property(name, new):
331 # Send new state to front-end
331 # Send new state to front-end
332 self.send_state(key=name)
332 self.send_state(key=name)
333
333
334 def _handle_displayed(self, **kwargs):
334 def _handle_displayed(self, **kwargs):
335 """Called when a view has been displayed for this widget instance"""
335 """Called when a view has been displayed for this widget instance"""
336 self._display_callbacks(self, **kwargs)
336 self._display_callbacks(self, **kwargs)
337
337
338 def _trait_to_json(self, x):
338 def _trait_to_json(self, x):
339 """Convert a trait value to json
339 """Convert a trait value to json
340
340
341 Traverse lists/tuples and dicts and serialize their values as well.
341 Traverse lists/tuples and dicts and serialize their values as well.
342 Replace any widgets with their model_id
342 Replace any widgets with their model_id
343 """
343 """
344 if isinstance(x, dict):
344 if isinstance(x, dict):
345 return {k: self._trait_to_json(v) for k, v in x.items()}
345 return {k: self._trait_to_json(v) for k, v in x.items()}
346 elif isinstance(x, (list, tuple)):
346 elif isinstance(x, (list, tuple)):
347 return [self._trait_to_json(v) for v in x]
347 return [self._trait_to_json(v) for v in x]
348 elif isinstance(x, Widget):
348 elif isinstance(x, Widget):
349 return "IPY_MODEL_" + x.model_id
349 return "IPY_MODEL_" + x.model_id
350 else:
350 else:
351 return x # Value must be JSON-able
351 return x # Value must be JSON-able
352
352
353 def _trait_from_json(self, x):
353 def _trait_from_json(self, x):
354 """Convert json values to objects
354 """Convert json values to objects
355
355
356 Replace any strings representing valid model id values to Widget references.
356 Replace any strings representing valid model id values to Widget references.
357 """
357 """
358 if isinstance(x, dict):
358 if isinstance(x, dict):
359 return {k: self._trait_from_json(v) for k, v in x.items()}
359 return {k: self._trait_from_json(v) for k, v in x.items()}
360 elif isinstance(x, (list, tuple)):
360 elif isinstance(x, (list, tuple)):
361 return [self._trait_from_json(v) for v in x]
361 return [self._trait_from_json(v) for v in x]
362 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
362 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
363 # we want to support having child widgets at any level in a hierarchy
363 # we want to support having child widgets at any level in a hierarchy
364 # trusting that a widget UUID will not appear out in the wild
364 # trusting that a widget UUID will not appear out in the wild
365 return Widget.widgets[x]
365 return Widget.widgets[x]
366 else:
366 else:
367 return x
367 return x
368
368
369 def _ipython_display_(self, **kwargs):
369 def _ipython_display_(self, **kwargs):
370 """Called when `IPython.display.display` is called on the widget."""
370 """Called when `IPython.display.display` is called on the widget."""
371 # Show view. By sending a display message, the comm is opened and the
371 # Show view. By sending a display message, the comm is opened and the
372 # initial state is sent.
372 # initial state is sent.
373 self._send({"method": "display"})
373 self._send({"method": "display"})
374 self._handle_displayed(**kwargs)
374 self._handle_displayed(**kwargs)
375
375
376 def _send(self, msg):
376 def _send(self, msg):
377 """Sends a message to the model in the front-end."""
377 """Sends a message to the model in the front-end."""
378 self.comm.send(msg)
378 self.comm.send(msg)
379
379
380
380
381 class DOMWidget(Widget):
381 class DOMWidget(Widget):
382 visible = Bool(True, help="Whether the widget is visible.", sync=True)
382 visible = Bool(True, help="Whether the widget is visible.", sync=True)
383 _css = CTuple(sync=True, help="CSS property list: (selector, key, value)")
383 _css = Tuple(sync=True, help="CSS property list: (selector, key, value)")
384 _dom_classes = CTuple(sync=True, help="DOM classes applied to widget.$el.")
384 _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.")
385
385
386 width = CUnicode(sync=True)
386 width = CUnicode(sync=True)
387 height = CUnicode(sync=True)
387 height = CUnicode(sync=True)
388
388
389 fore_color = Unicode(sync=True)
389 fore_color = Unicode(sync=True)
390 back_color = Unicode(sync=True)
390 back_color = Unicode(sync=True)
391 border_color = Unicode(sync=True)
391 border_color = Unicode(sync=True)
392
392
393 border_width = CUnicode(sync=True)
393 border_width = CUnicode(sync=True)
394 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
394 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
395 'none',
395 'none',
396 'hidden',
396 'hidden',
397 'dotted',
397 'dotted',
398 'dashed',
398 'dashed',
399 'solid',
399 'solid',
400 'double',
400 'double',
401 'groove',
401 'groove',
402 'ridge',
402 'ridge',
403 'inset',
403 'inset',
404 'outset',
404 'outset',
405 'initial',
405 'initial',
406 'inherit', ''],
406 'inherit', ''],
407 default_value='', sync=True)
407 default_value='', sync=True)
408
408
409 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
409 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
410 'normal',
410 'normal',
411 'italic',
411 'italic',
412 'oblique',
412 'oblique',
413 'initial',
413 'initial',
414 'inherit', ''],
414 'inherit', ''],
415 default_value='', sync=True)
415 default_value='', sync=True)
416 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
416 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
417 'normal',
417 'normal',
418 'bold',
418 'bold',
419 'bolder',
419 'bolder',
420 'lighter',
420 'lighter',
421 'initial',
421 'initial',
422 'inherit', ''] + [str(100 * (i+1)) for i in range(9)],
422 'inherit', ''] + [str(100 * (i+1)) for i in range(9)],
423 default_value='', sync=True)
423 default_value='', sync=True)
424 font_size = CUnicode(sync=True)
424 font_size = CUnicode(sync=True)
425 font_family = Unicode(sync=True)
425 font_family = Unicode(sync=True)
@@ -1,170 +1,171 b''
1 """Float class.
1 """Float class.
2
2
3 Represents an unbounded float using a widget.
3 Represents an unbounded float using a widget.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
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, Tuple
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 #-----------------------------------------------------------------------------
21 # Classes
21 # Classes
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 class _Float(DOMWidget):
23 class _Float(DOMWidget):
24 value = CFloat(0.0, help="Float value", sync=True)
24 value = CFloat(0.0, help="Float value", sync=True)
25 disabled = Bool(False, help="Enable or disable user changes", sync=True)
25 disabled = Bool(False, help="Enable or disable user changes", sync=True)
26 description = Unicode(help="Description of the value this widget represents", sync=True)
26 description = Unicode(help="Description of the value this widget represents", sync=True)
27
27
28
28
29 class _BoundedFloat(_Float):
29 class _BoundedFloat(_Float):
30 max = CFloat(100.0, help="Max value", sync=True)
30 max = CFloat(100.0, help="Max value", sync=True)
31 min = CFloat(0.0, help="Min value", sync=True)
31 min = CFloat(0.0, help="Min value", sync=True)
32 step = CFloat(0.1, help="Minimum step that the value can take (ignored by some views)", sync=True)
32 step = CFloat(0.1, help="Minimum step that the value can take (ignored by some views)", sync=True)
33
33
34 def __init__(self, *pargs, **kwargs):
34 def __init__(self, *pargs, **kwargs):
35 """Constructor"""
35 """Constructor"""
36 DOMWidget.__init__(self, *pargs, **kwargs)
36 DOMWidget.__init__(self, *pargs, **kwargs)
37 self._validate('value', None, self.value)
37 self._validate('value', None, self.value)
38 self.on_trait_change(self._validate, ['value', 'min', 'max'])
38 self.on_trait_change(self._validate, ['value', 'min', 'max'])
39
39
40 def _validate(self, name, old, new):
40 def _validate(self, name, old, new):
41 """Validate value, max, min."""
41 """Validate value, max, min."""
42 if self.min > new or new > self.max:
42 if self.min > new or new > self.max:
43 self.value = min(max(new, self.min), self.max)
43 self.value = min(max(new, self.min), self.max)
44
44
45
45
46 class FloatText(_Float):
46 class FloatText(_Float):
47 _view_name = Unicode('FloatTextView', sync=True)
47 _view_name = Unicode('FloatTextView', sync=True)
48
48
49
49
50 class BoundedFloatText(_BoundedFloat):
50 class BoundedFloatText(_BoundedFloat):
51 _view_name = Unicode('FloatTextView', sync=True)
51 _view_name = Unicode('FloatTextView', sync=True)
52
52
53
53
54 class FloatSlider(_BoundedFloat):
54 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 _range = Bool(False, help="Display a range selector", sync=True)
59 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)
60 slider_color = Unicode(sync=True)
60
61
61
62
62 class FloatProgress(_BoundedFloat):
63 class FloatProgress(_BoundedFloat):
63 _view_name = Unicode('ProgressView', sync=True)
64 _view_name = Unicode('ProgressView', sync=True)
64
65
65 class _FloatRange(_Float):
66 class _FloatRange(_Float):
66 value = Tuple(CFloat, CFloat, default_value=(0.0, 1.0), help="Tuple of (lower, upper) bounds", sync=True)
67 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 lower = CFloat(0.0, help="Lower bound", sync=False)
68 upper = CFloat(1.0, help="Upper bound", sync=False)
69 upper = CFloat(1.0, help="Upper bound", sync=False)
69
70
70 def __init__(self, *pargs, **kwargs):
71 def __init__(self, *pargs, **kwargs):
71 value_given = 'value' in kwargs
72 value_given = 'value' in kwargs
72 lower_given = 'lower' in kwargs
73 lower_given = 'lower' in kwargs
73 upper_given = 'upper' in kwargs
74 upper_given = 'upper' in kwargs
74 if value_given and (lower_given or upper_given):
75 if value_given and (lower_given or upper_given):
75 raise ValueError("Cannot specify both 'value' and 'lower'/'upper' for range widget")
76 raise ValueError("Cannot specify both 'value' and 'lower'/'upper' for range widget")
76 if lower_given != upper_given:
77 if lower_given != upper_given:
77 raise ValueError("Must specify both 'lower' and 'upper' for range widget")
78 raise ValueError("Must specify both 'lower' and 'upper' for range widget")
78
79
79 DOMWidget.__init__(self, *pargs, **kwargs)
80 DOMWidget.__init__(self, *pargs, **kwargs)
80
81
81 # ensure the traits match, preferring whichever (if any) was given in kwargs
82 # ensure the traits match, preferring whichever (if any) was given in kwargs
82 if value_given:
83 if value_given:
83 self.lower, self.upper = self.value
84 self.lower, self.upper = self.value
84 else:
85 else:
85 self.value = (self.lower, self.upper)
86 self.value = (self.lower, self.upper)
86
87
87 self.on_trait_change(self._validate, ['value', 'upper', 'lower'])
88 self.on_trait_change(self._validate, ['value', 'upper', 'lower'])
88
89
89 def _validate(self, name, old, new):
90 def _validate(self, name, old, new):
90 if name == 'value':
91 if name == 'value':
91 self.lower, self.upper = min(new), max(new)
92 self.lower, self.upper = min(new), max(new)
92 elif name == 'lower':
93 elif name == 'lower':
93 self.value = (new, self.value[1])
94 self.value = (new, self.value[1])
94 elif name == 'upper':
95 elif name == 'upper':
95 self.value = (self.value[0], new)
96 self.value = (self.value[0], new)
96
97
97 class _BoundedFloatRange(_FloatRange):
98 class _BoundedFloatRange(_FloatRange):
98 step = CFloat(1.0, help="Minimum step that the value can take (ignored by some views)", sync=True)
99 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 max = CFloat(100.0, help="Max value", sync=True)
100 min = CFloat(0.0, help="Min value", sync=True)
101 min = CFloat(0.0, help="Min value", sync=True)
101
102
102 def __init__(self, *pargs, **kwargs):
103 def __init__(self, *pargs, **kwargs):
103 any_value_given = 'value' in kwargs or 'upper' in kwargs or 'lower' in kwargs
104 any_value_given = 'value' in kwargs or 'upper' in kwargs or 'lower' in kwargs
104 _FloatRange.__init__(self, *pargs, **kwargs)
105 _FloatRange.__init__(self, *pargs, **kwargs)
105
106
106 # ensure a minimal amount of sanity
107 # ensure a minimal amount of sanity
107 if self.min > self.max:
108 if self.min > self.max:
108 raise ValueError("min must be <= max")
109 raise ValueError("min must be <= max")
109
110
110 if any_value_given:
111 if any_value_given:
111 # if a value was given, clamp it within (min, max)
112 # if a value was given, clamp it within (min, max)
112 self._validate("value", None, self.value)
113 self._validate("value", None, self.value)
113 else:
114 else:
114 # otherwise, set it to 25-75% to avoid the handles overlapping
115 # otherwise, set it to 25-75% to avoid the handles overlapping
115 self.value = (0.75*self.min + 0.25*self.max,
116 self.value = (0.75*self.min + 0.25*self.max,
116 0.25*self.min + 0.75*self.max)
117 0.25*self.min + 0.75*self.max)
117 # callback already set for 'value', 'lower', 'upper'
118 # callback already set for 'value', 'lower', 'upper'
118 self.on_trait_change(self._validate, ['min', 'max'])
119 self.on_trait_change(self._validate, ['min', 'max'])
119
120
120
121
121 def _validate(self, name, old, new):
122 def _validate(self, name, old, new):
122 if name == "min":
123 if name == "min":
123 if new > self.max:
124 if new > self.max:
124 raise ValueError("setting min > max")
125 raise ValueError("setting min > max")
125 self.min = new
126 self.min = new
126 elif name == "max":
127 elif name == "max":
127 if new < self.min:
128 if new < self.min:
128 raise ValueError("setting max < min")
129 raise ValueError("setting max < min")
129 self.max = new
130 self.max = new
130
131
131 low, high = self.value
132 low, high = self.value
132 if name == "value":
133 if name == "value":
133 low, high = min(new), max(new)
134 low, high = min(new), max(new)
134 elif name == "upper":
135 elif name == "upper":
135 if new < self.lower:
136 if new < self.lower:
136 raise ValueError("setting upper < lower")
137 raise ValueError("setting upper < lower")
137 high = new
138 high = new
138 elif name == "lower":
139 elif name == "lower":
139 if new > self.upper:
140 if new > self.upper:
140 raise ValueError("setting lower > upper")
141 raise ValueError("setting lower > upper")
141 low = new
142 low = new
142
143
143 low = max(self.min, min(low, self.max))
144 low = max(self.min, min(low, self.max))
144 high = min(self.max, max(high, self.min))
145 high = min(self.max, max(high, self.min))
145
146
146 # determine the order in which we should update the
147 # determine the order in which we should update the
147 # lower, upper traits to avoid a temporary inverted overlap
148 # lower, upper traits to avoid a temporary inverted overlap
148 lower_first = high < self.lower
149 lower_first = high < self.lower
149
150
150 self.value = (low, high)
151 self.value = (low, high)
151 if lower_first:
152 if lower_first:
152 self.lower = low
153 self.lower = low
153 self.upper = high
154 self.upper = high
154 else:
155 else:
155 self.upper = high
156 self.upper = high
156 self.lower = low
157 self.lower = low
157
158
158
159
159 class FloatRangeSlider(_BoundedFloatRange):
160 class FloatRangeSlider(_BoundedFloatRange):
160 _view_name = Unicode('FloatSliderView', sync=True)
161 _view_name = Unicode('FloatSliderView', sync=True)
161 orientation = Enum([u'horizontal', u'vertical'], u'horizontal',
162 orientation = Enum([u'horizontal', u'vertical'], u'horizontal',
162 help="Vertical or horizontal.", sync=True)
163 help="Vertical or horizontal.", sync=True)
163 _range = Bool(True, help="Display a range selector", sync=True)
164 _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)
165 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
165
166
166 # Remove in IPython 4.0
167 # Remove in IPython 4.0
167 FloatTextWidget = DeprecatedClass(FloatText, 'FloatTextWidget')
168 FloatTextWidget = DeprecatedClass(FloatText, 'FloatTextWidget')
168 BoundedFloatTextWidget = DeprecatedClass(BoundedFloatText, 'BoundedFloatTextWidget')
169 BoundedFloatTextWidget = DeprecatedClass(BoundedFloatText, 'BoundedFloatTextWidget')
169 FloatSliderWidget = DeprecatedClass(FloatSlider, 'FloatSliderWidget')
170 FloatSliderWidget = DeprecatedClass(FloatSlider, 'FloatSliderWidget')
170 FloatProgressWidget = DeprecatedClass(FloatProgress, 'FloatProgressWidget')
171 FloatProgressWidget = DeprecatedClass(FloatProgress, 'FloatProgressWidget')
@@ -1,174 +1,175 b''
1 """Int class.
1 """Int class.
2
2
3 Represents an unbounded int using a widget.
3 Represents an unbounded int using a widget.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
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, Tuple
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 #-----------------------------------------------------------------------------
21 # Classes
21 # Classes
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 class _Int(DOMWidget):
23 class _Int(DOMWidget):
24 """Base class used to create widgets that represent an int."""
24 """Base class used to create widgets that represent an int."""
25 value = CInt(0, help="Int value", sync=True)
25 value = CInt(0, help="Int value", sync=True)
26 disabled = Bool(False, help="Enable or disable user changes", sync=True)
26 disabled = Bool(False, help="Enable or disable user changes", sync=True)
27 description = Unicode(help="Description of the value this widget represents", sync=True)
27 description = Unicode(help="Description of the value this widget represents", sync=True)
28
28
29
29
30 class _BoundedInt(_Int):
30 class _BoundedInt(_Int):
31 """Base class used to create widgets that represent a int that is bounded
31 """Base class used to create widgets that represent a int that is bounded
32 by a minium and maximum."""
32 by a minium and maximum."""
33 step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True)
33 step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True)
34 max = CInt(100, help="Max value", sync=True)
34 max = CInt(100, help="Max value", sync=True)
35 min = CInt(0, help="Min value", sync=True)
35 min = CInt(0, help="Min value", sync=True)
36
36
37 def __init__(self, *pargs, **kwargs):
37 def __init__(self, *pargs, **kwargs):
38 """Constructor"""
38 """Constructor"""
39 DOMWidget.__init__(self, *pargs, **kwargs)
39 DOMWidget.__init__(self, *pargs, **kwargs)
40 self.on_trait_change(self._validate, ['value', 'min', 'max'])
40 self.on_trait_change(self._validate, ['value', 'min', 'max'])
41
41
42 def _validate(self, name, old, new):
42 def _validate(self, name, old, new):
43 """Validate value, max, min."""
43 """Validate value, max, min."""
44 if self.min > new or new > self.max:
44 if self.min > new or new > self.max:
45 self.value = min(max(new, self.min), self.max)
45 self.value = min(max(new, self.min), self.max)
46
46
47
47
48 class IntText(_Int):
48 class IntText(_Int):
49 """Textbox widget that represents a int."""
49 """Textbox widget that represents a int."""
50 _view_name = Unicode('IntTextView', sync=True)
50 _view_name = Unicode('IntTextView', sync=True)
51
51
52
52
53 class BoundedIntText(_BoundedInt):
53 class BoundedIntText(_BoundedInt):
54 """Textbox widget that represents a int bounded by a minimum and maximum value."""
54 """Textbox widget that represents a int bounded by a minimum and maximum value."""
55 _view_name = Unicode('IntTextView', sync=True)
55 _view_name = Unicode('IntTextView', sync=True)
56
56
57
57
58 class IntSlider(_BoundedInt):
58 class IntSlider(_BoundedInt):
59 """Slider widget that represents a int bounded by a minimum and maximum value."""
59 """Slider widget that represents a int bounded by a minimum and maximum value."""
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 _range = Bool(False, help="Display a range selector", sync=True)
64 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)
65 slider_color = Unicode(sync=True)
65
66
66
67
67 class IntProgress(_BoundedInt):
68 class IntProgress(_BoundedInt):
68 """Progress bar that represents a int bounded by a minimum and maximum value."""
69 """Progress bar that represents a int bounded by a minimum and maximum value."""
69 _view_name = Unicode('ProgressView', sync=True)
70 _view_name = Unicode('ProgressView', sync=True)
70
71
71 class _IntRange(_Int):
72 class _IntRange(_Int):
72 value = Tuple(CInt, CInt, default_value=(0, 1), help="Tuple of (lower, upper) bounds", sync=True)
73 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 lower = CInt(0, help="Lower bound", sync=False)
74 upper = CInt(1, help="Upper bound", sync=False)
75 upper = CInt(1, help="Upper bound", sync=False)
75
76
76 def __init__(self, *pargs, **kwargs):
77 def __init__(self, *pargs, **kwargs):
77 value_given = 'value' in kwargs
78 value_given = 'value' in kwargs
78 lower_given = 'lower' in kwargs
79 lower_given = 'lower' in kwargs
79 upper_given = 'upper' in kwargs
80 upper_given = 'upper' in kwargs
80 if value_given and (lower_given or upper_given):
81 if value_given and (lower_given or upper_given):
81 raise ValueError("Cannot specify both 'value' and 'lower'/'upper' for range widget")
82 raise ValueError("Cannot specify both 'value' and 'lower'/'upper' for range widget")
82 if lower_given != upper_given:
83 if lower_given != upper_given:
83 raise ValueError("Must specify both 'lower' and 'upper' for range widget")
84 raise ValueError("Must specify both 'lower' and 'upper' for range widget")
84
85
85 DOMWidget.__init__(self, *pargs, **kwargs)
86 DOMWidget.__init__(self, *pargs, **kwargs)
86
87
87 # ensure the traits match, preferring whichever (if any) was given in kwargs
88 # ensure the traits match, preferring whichever (if any) was given in kwargs
88 if value_given:
89 if value_given:
89 self.lower, self.upper = self.value
90 self.lower, self.upper = self.value
90 else:
91 else:
91 self.value = (self.lower, self.upper)
92 self.value = (self.lower, self.upper)
92
93
93 self.on_trait_change(self._validate, ['value', 'upper', 'lower'])
94 self.on_trait_change(self._validate, ['value', 'upper', 'lower'])
94
95
95 def _validate(self, name, old, new):
96 def _validate(self, name, old, new):
96 if name == 'value':
97 if name == 'value':
97 self.lower, self.upper = min(new), max(new)
98 self.lower, self.upper = min(new), max(new)
98 elif name == 'lower':
99 elif name == 'lower':
99 self.value = (new, self.value[1])
100 self.value = (new, self.value[1])
100 elif name == 'upper':
101 elif name == 'upper':
101 self.value = (self.value[0], new)
102 self.value = (self.value[0], new)
102
103
103 class _BoundedIntRange(_IntRange):
104 class _BoundedIntRange(_IntRange):
104 step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True)
105 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 max = CInt(100, help="Max value", sync=True)
106 min = CInt(0, help="Min value", sync=True)
107 min = CInt(0, help="Min value", sync=True)
107
108
108 def __init__(self, *pargs, **kwargs):
109 def __init__(self, *pargs, **kwargs):
109 any_value_given = 'value' in kwargs or 'upper' in kwargs or 'lower' in kwargs
110 any_value_given = 'value' in kwargs or 'upper' in kwargs or 'lower' in kwargs
110 _IntRange.__init__(self, *pargs, **kwargs)
111 _IntRange.__init__(self, *pargs, **kwargs)
111
112
112 # ensure a minimal amount of sanity
113 # ensure a minimal amount of sanity
113 if self.min > self.max:
114 if self.min > self.max:
114 raise ValueError("min must be <= max")
115 raise ValueError("min must be <= max")
115
116
116 if any_value_given:
117 if any_value_given:
117 # if a value was given, clamp it within (min, max)
118 # if a value was given, clamp it within (min, max)
118 self._validate("value", None, self.value)
119 self._validate("value", None, self.value)
119 else:
120 else:
120 # otherwise, set it to 25-75% to avoid the handles overlapping
121 # otherwise, set it to 25-75% to avoid the handles overlapping
121 self.value = (0.75*self.min + 0.25*self.max,
122 self.value = (0.75*self.min + 0.25*self.max,
122 0.25*self.min + 0.75*self.max)
123 0.25*self.min + 0.75*self.max)
123 # callback already set for 'value', 'lower', 'upper'
124 # callback already set for 'value', 'lower', 'upper'
124 self.on_trait_change(self._validate, ['min', 'max'])
125 self.on_trait_change(self._validate, ['min', 'max'])
125
126
126 def _validate(self, name, old, new):
127 def _validate(self, name, old, new):
127 if name == "min":
128 if name == "min":
128 if new > self.max:
129 if new > self.max:
129 raise ValueError("setting min > max")
130 raise ValueError("setting min > max")
130 self.min = new
131 self.min = new
131 elif name == "max":
132 elif name == "max":
132 if new < self.min:
133 if new < self.min:
133 raise ValueError("setting max < min")
134 raise ValueError("setting max < min")
134 self.max = new
135 self.max = new
135
136
136 low, high = self.value
137 low, high = self.value
137 if name == "value":
138 if name == "value":
138 low, high = min(new), max(new)
139 low, high = min(new), max(new)
139 elif name == "upper":
140 elif name == "upper":
140 if new < self.lower:
141 if new < self.lower:
141 raise ValueError("setting upper < lower")
142 raise ValueError("setting upper < lower")
142 high = new
143 high = new
143 elif name == "lower":
144 elif name == "lower":
144 if new > self.upper:
145 if new > self.upper:
145 raise ValueError("setting lower > upper")
146 raise ValueError("setting lower > upper")
146 low = new
147 low = new
147
148
148 low = max(self.min, min(low, self.max))
149 low = max(self.min, min(low, self.max))
149 high = min(self.max, max(high, self.min))
150 high = min(self.max, max(high, self.min))
150
151
151 # determine the order in which we should update the
152 # determine the order in which we should update the
152 # lower, upper traits to avoid a temporary inverted overlap
153 # lower, upper traits to avoid a temporary inverted overlap
153 lower_first = high < self.lower
154 lower_first = high < self.lower
154
155
155 self.value = (low, high)
156 self.value = (low, high)
156 if lower_first:
157 if lower_first:
157 self.lower = low
158 self.lower = low
158 self.upper = high
159 self.upper = high
159 else:
160 else:
160 self.upper = high
161 self.upper = high
161 self.lower = low
162 self.lower = low
162
163
163 class IntRangeSlider(_BoundedIntRange):
164 class IntRangeSlider(_BoundedIntRange):
164 _view_name = Unicode('IntSliderView', sync=True)
165 _view_name = Unicode('IntSliderView', sync=True)
165 orientation = Enum([u'horizontal', u'vertical'], u'horizontal',
166 orientation = Enum([u'horizontal', u'vertical'], u'horizontal',
166 help="Vertical or horizontal.", sync=True)
167 help="Vertical or horizontal.", sync=True)
167 _range = Bool(True, help="Display a range selector", sync=True)
168 _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)
169 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
169
170
170 # Remove in IPython 4.0
171 # Remove in IPython 4.0
171 IntTextWidget = DeprecatedClass(IntText, 'IntTextWidget')
172 IntTextWidget = DeprecatedClass(IntText, 'IntTextWidget')
172 BoundedIntTextWidget = DeprecatedClass(BoundedIntText, 'BoundedIntTextWidget')
173 BoundedIntTextWidget = DeprecatedClass(BoundedIntText, 'BoundedIntTextWidget')
173 IntSliderWidget = DeprecatedClass(IntSlider, 'IntSliderWidget')
174 IntSliderWidget = DeprecatedClass(IntSlider, 'IntSliderWidget')
174 IntProgressWidget = DeprecatedClass(IntProgress, 'IntProgressWidget')
175 IntProgressWidget = DeprecatedClass(IntProgress, 'IntProgressWidget')
General Comments 0
You need to be logged in to leave comments. Login now