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