Show More
@@ -1,523 +1,551 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 | "underscore", | |
|
8 | 9 | "bootstrap", |
|
9 | ], function(widget, utils, $){ | |
|
10 | ], function(widget, utils, $, _){ | |
|
10 | 11 | |
|
11 | 12 | var DropdownView = widget.DOMWidgetView.extend({ |
|
12 | 13 | render : function(){ |
|
13 | 14 | /** |
|
14 | 15 | * Called when view is rendered. |
|
15 | 16 | */ |
|
16 | 17 | this.$el |
|
17 | 18 | .addClass('widget-hbox widget-dropdown'); |
|
18 | 19 | this.$label = $('<div />') |
|
19 | 20 | .appendTo(this.$el) |
|
20 | 21 | .addClass('widget-label') |
|
21 | 22 | .hide(); |
|
22 | 23 | this.$buttongroup = $('<div />') |
|
23 | 24 | .addClass('widget_item') |
|
24 | 25 | .addClass('btn-group') |
|
25 | 26 | .appendTo(this.$el); |
|
26 | 27 | this.$droplabel = $('<button />') |
|
27 | 28 | .addClass('btn btn-default') |
|
28 | 29 | .addClass('widget-combo-btn') |
|
29 | 30 | .html(" ") |
|
30 | 31 | .appendTo(this.$buttongroup); |
|
31 | 32 | this.$dropbutton = $('<button />') |
|
32 | 33 | .addClass('btn btn-default') |
|
33 | 34 | .addClass('dropdown-toggle') |
|
34 | 35 | .addClass('widget-combo-carrot-btn') |
|
35 | 36 | .attr('data-toggle', 'dropdown') |
|
36 | 37 | .append($('<span />').addClass("caret")) |
|
37 | 38 | .appendTo(this.$buttongroup); |
|
38 | 39 | this.$droplist = $('<ul />') |
|
39 | 40 | .addClass('dropdown-menu') |
|
40 | 41 | .appendTo(this.$buttongroup); |
|
41 | 42 | |
|
42 | 43 | this.model.on('change:button_style', function(model, value) { |
|
43 | 44 | this.update_button_style(); |
|
44 | 45 | }, this); |
|
45 | 46 | this.update_button_style(''); |
|
46 | 47 | |
|
47 | 48 | // Set defaults. |
|
48 | 49 | this.update(); |
|
49 | 50 | }, |
|
50 | 51 | |
|
51 | 52 | update : function(options){ |
|
52 | 53 | /** |
|
53 | 54 | * Update the contents of this view |
|
54 | 55 | * |
|
55 |
* Called when the model is changed. The model may have been |
|
|
56 | * Called when the model is changed. The model may have been | |
|
56 | 57 | * changed by another view or by a state update from the back-end. |
|
57 | 58 | */ |
|
58 | 59 | |
|
59 | 60 | if (options === undefined || options.updated_view != this) { |
|
60 |
var selected_item_text = this.model.get(' |
|
|
61 | var selected_item_text = this.model.get('selected_label'); | |
|
61 | 62 | if (selected_item_text.trim().length === 0) { |
|
62 | 63 | this.$droplabel.html(" "); |
|
63 | 64 | } else { |
|
64 | 65 | this.$droplabel.text(selected_item_text); |
|
65 | 66 | } |
|
66 | 67 | |
|
67 |
var items = this.model.get('_ |
|
|
68 | var items = this.model.get('_options_labels'); | |
|
68 | 69 | var $replace_droplist = $('<ul />') |
|
69 | 70 | .addClass('dropdown-menu'); |
|
70 | 71 | // Copy the style |
|
71 | 72 | $replace_droplist.attr('style', this.$droplist.attr('style')); |
|
72 | 73 | var that = this; |
|
73 | 74 | _.each(items, function(item, i) { |
|
74 | 75 | var item_button = $('<a href="#"/>') |
|
75 | 76 | .text(item) |
|
76 | 77 | .on('click', $.proxy(that.handle_click, that)); |
|
77 | 78 | $replace_droplist.append($('<li />').append(item_button)); |
|
78 | 79 | }); |
|
79 | 80 | |
|
80 | 81 | this.$droplist.replaceWith($replace_droplist); |
|
81 | 82 | this.$droplist.remove(); |
|
82 | 83 | this.$droplist = $replace_droplist; |
|
83 | 84 | |
|
84 | 85 | if (this.model.get('disabled')) { |
|
85 | 86 | this.$buttongroup.attr('disabled','disabled'); |
|
86 | 87 | this.$droplabel.attr('disabled','disabled'); |
|
87 | 88 | this.$dropbutton.attr('disabled','disabled'); |
|
88 | 89 | this.$droplist.attr('disabled','disabled'); |
|
89 | 90 | } else { |
|
90 | 91 | this.$buttongroup.removeAttr('disabled'); |
|
91 | 92 | this.$droplabel.removeAttr('disabled'); |
|
92 | 93 | this.$dropbutton.removeAttr('disabled'); |
|
93 | 94 | this.$droplist.removeAttr('disabled'); |
|
94 | 95 | } |
|
95 | 96 | |
|
96 | 97 | var description = this.model.get('description'); |
|
97 | 98 | if (description.length === 0) { |
|
98 | 99 | this.$label.hide(); |
|
99 | 100 | } else { |
|
100 | 101 | this.typeset(this.$label, description); |
|
101 | 102 | this.$label.show(); |
|
102 | 103 | } |
|
103 | 104 | } |
|
104 | 105 | return DropdownView.__super__.update.apply(this); |
|
105 | 106 | }, |
|
106 | 107 | |
|
107 | 108 | update_button_style: function(previous_trait_value) { |
|
108 | 109 | var class_map = { |
|
109 | 110 | primary: ['btn-primary'], |
|
110 | 111 | success: ['btn-success'], |
|
111 | 112 | info: ['btn-info'], |
|
112 | 113 | warning: ['btn-warning'], |
|
113 | 114 | danger: ['btn-danger'] |
|
114 | 115 | }; |
|
115 | 116 | this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$droplabel); |
|
116 | 117 | this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$dropbutton); |
|
117 | 118 | }, |
|
118 | 119 | |
|
119 | 120 | update_attr: function(name, value) { |
|
120 | 121 | /** |
|
121 | 122 | * Set a css attr of the widget view. |
|
122 | 123 | */ |
|
123 | 124 | if (name.substring(0, 6) == 'border' || name == 'background' || name == 'color') { |
|
124 | 125 | this.$droplabel.css(name, value); |
|
125 | 126 | this.$dropbutton.css(name, value); |
|
126 | 127 | this.$droplist.css(name, value); |
|
127 | 128 | } else if (name == 'width') { |
|
128 | 129 | this.$droplist.css(name, value); |
|
129 | 130 | this.$droplabel.css(name, value); |
|
130 | 131 | } else if (name == 'padding') { |
|
131 | 132 | this.$droplist.css(name, value); |
|
132 | 133 | this.$buttongroup.css(name, value); |
|
133 | 134 | } else if (name == 'margin') { |
|
134 | 135 | this.$buttongroup.css(name, value); |
|
135 | 136 | } else if (name == 'height') { |
|
136 | 137 | this.$droplabel.css(name, value); |
|
137 | 138 | this.$dropbutton.css(name, value); |
|
138 | 139 | } else if (name == 'padding' || name == 'margin') { |
|
139 | 140 | this.$el.css(name, value); |
|
140 | 141 | } else { |
|
141 | 142 | this.$droplist.css(name, value); |
|
142 | 143 | this.$droplabel.css(name, value); |
|
143 | 144 | } |
|
144 | 145 | }, |
|
145 | 146 | |
|
146 | 147 | handle_click: function (e) { |
|
147 | 148 | /** |
|
148 | 149 | * Handle when a value is clicked. |
|
149 | 150 | * |
|
150 | 151 | * Calling model.set will trigger all of the other views of the |
|
151 | 152 | * model to update. |
|
152 | 153 | */ |
|
153 |
this.model.set(' |
|
|
154 | this.model.set('selected_label', $(e.target).text(), {updated_view: this}); | |
|
154 | 155 | this.touch(); |
|
155 | 156 | |
|
156 | 157 | // Manually hide the droplist. |
|
157 | 158 | e.stopPropagation(); |
|
158 | 159 | e.preventDefault(); |
|
159 | 160 | this.$buttongroup.removeClass('open'); |
|
160 | 161 | }, |
|
161 | 162 | |
|
162 | 163 | }); |
|
163 | 164 | |
|
164 | 165 | |
|
165 | 166 | var RadioButtonsView = widget.DOMWidgetView.extend({ |
|
166 | 167 | render : function(){ |
|
167 | 168 | /** |
|
168 | 169 | * Called when view is rendered. |
|
169 | 170 | */ |
|
170 | 171 | this.$el |
|
171 | 172 | .addClass('widget-hbox widget-radio'); |
|
172 | 173 | this.$label = $('<div />') |
|
173 | 174 | .appendTo(this.$el) |
|
174 | 175 | .addClass('widget-label') |
|
175 | 176 | .hide(); |
|
176 | 177 | this.$container = $('<div />') |
|
177 | 178 | .appendTo(this.$el) |
|
178 | 179 | .addClass('widget-radio-box'); |
|
179 | 180 | this.update(); |
|
180 | 181 | }, |
|
181 | 182 | |
|
182 | 183 | update : function(options){ |
|
183 | 184 | /** |
|
184 | 185 | * Update the contents of this view |
|
185 | 186 | * |
|
186 | 187 | * Called when the model is changed. The model may have been |
|
187 | 188 | * changed by another view or by a state update from the back-end. |
|
188 | 189 | */ |
|
189 | 190 | if (options === undefined || options.updated_view != this) { |
|
190 | 191 | // Add missing items to the DOM. |
|
191 |
var items = this.model.get('_ |
|
|
192 | var items = this.model.get('_options_labels'); | |
|
192 | 193 | var disabled = this.model.get('disabled'); |
|
193 | 194 | var that = this; |
|
194 | 195 | _.each(items, function(item, index) { |
|
195 | 196 | var item_query = ' :input[data-value="' + encodeURIComponent(item) + '"]'; |
|
196 | 197 | if (that.$el.find(item_query).length === 0) { |
|
197 | 198 | var $label = $('<label />') |
|
198 | 199 | .addClass('radio') |
|
199 | 200 | .text(item) |
|
200 | 201 | .appendTo(that.$container); |
|
201 | 202 | |
|
202 | 203 | $('<input />') |
|
203 | 204 | .attr('type', 'radio') |
|
204 | 205 | .addClass(that.model) |
|
205 | 206 | .val(item) |
|
206 | 207 | .attr('data-value', encodeURIComponent(item)) |
|
207 | 208 | .prependTo($label) |
|
208 | 209 | .on('click', $.proxy(that.handle_click, that)); |
|
209 | 210 | } |
|
210 | 211 | |
|
211 | 212 | var $item_element = that.$container.find(item_query); |
|
212 |
if (that.model.get(' |
|
|
213 | if (that.model.get('selected_label') == item) { | |
|
213 | 214 | $item_element.prop('checked', true); |
|
214 | 215 | } else { |
|
215 | 216 | $item_element.prop('checked', false); |
|
216 | 217 | } |
|
217 | 218 | $item_element.prop('disabled', disabled); |
|
218 | 219 | }); |
|
219 | 220 | |
|
220 | 221 | // Remove items that no longer exist. |
|
221 | 222 | this.$container.find('input').each(function(i, obj) { |
|
222 | 223 | var value = $(obj).val(); |
|
223 | 224 | var found = false; |
|
224 | 225 | _.each(items, function(item, index) { |
|
225 | 226 | if (item == value) { |
|
226 | 227 | found = true; |
|
227 | 228 | return false; |
|
228 | 229 | } |
|
229 | 230 | }); |
|
230 | 231 | |
|
231 | 232 | if (!found) { |
|
232 | 233 | $(obj).parent().remove(); |
|
233 | 234 | } |
|
234 | 235 | }); |
|
235 | 236 | |
|
236 | 237 | var description = this.model.get('description'); |
|
237 | 238 | if (description.length === 0) { |
|
238 | 239 | this.$label.hide(); |
|
239 | 240 | } else { |
|
240 | 241 | this.$label.text(description); |
|
241 | 242 | this.typeset(this.$label, description); |
|
242 | 243 | this.$label.show(); |
|
243 | 244 | } |
|
244 | 245 | } |
|
245 | 246 | return RadioButtonsView.__super__.update.apply(this); |
|
246 | 247 | }, |
|
247 | 248 | |
|
248 | 249 | update_attr: function(name, value) { |
|
249 | 250 | /** |
|
250 | 251 | * Set a css attr of the widget view. |
|
251 | 252 | */ |
|
252 | 253 | if (name == 'padding' || name == 'margin') { |
|
253 | 254 | this.$el.css(name, value); |
|
254 | 255 | } else { |
|
255 | 256 | this.$container.css(name, value); |
|
256 | 257 | } |
|
257 | 258 | }, |
|
258 | 259 | |
|
259 | 260 | handle_click: function (e) { |
|
260 | 261 | /** |
|
261 | 262 | * Handle when a value is clicked. |
|
262 | 263 | * |
|
263 | 264 | * Calling model.set will trigger all of the other views of the |
|
264 | 265 | * model to update. |
|
265 | 266 | */ |
|
266 |
this.model.set(' |
|
|
267 | this.model.set('selected_label', $(e.target).val(), {updated_view: this}); | |
|
267 | 268 | this.touch(); |
|
268 | 269 | }, |
|
269 | 270 | }); |
|
270 | 271 | |
|
271 | 272 | |
|
272 | 273 | var ToggleButtonsView = widget.DOMWidgetView.extend({ |
|
273 | 274 | initialize: function() { |
|
274 | 275 | this._css_state = {}; |
|
275 | 276 | ToggleButtonsView.__super__.initialize.apply(this, arguments); |
|
276 | 277 | }, |
|
277 | 278 | |
|
278 | 279 | render: function() { |
|
279 | 280 | /** |
|
280 | 281 | * Called when view is rendered. |
|
281 | 282 | */ |
|
282 | 283 | this.$el |
|
283 | 284 | .addClass('widget-hbox widget-toggle-buttons'); |
|
284 | 285 | this.$label = $('<div />') |
|
285 | 286 | .appendTo(this.$el) |
|
286 | 287 | .addClass('widget-label') |
|
287 | 288 | .hide(); |
|
288 | 289 | this.$buttongroup = $('<div />') |
|
289 | 290 | .addClass('btn-group') |
|
290 | 291 | .appendTo(this.$el); |
|
291 | 292 | |
|
292 | 293 | this.model.on('change:button_style', function(model, value) { |
|
293 | 294 | this.update_button_style(); |
|
294 | 295 | }, this); |
|
295 | 296 | this.update_button_style(''); |
|
296 | 297 | this.update(); |
|
297 | 298 | }, |
|
298 | 299 | |
|
299 | 300 | update : function(options){ |
|
300 | 301 | /** |
|
301 | 302 | * Update the contents of this view |
|
302 | 303 | * |
|
303 | 304 | * Called when the model is changed. The model may have been |
|
304 | 305 | * changed by another view or by a state update from the back-end. |
|
305 | 306 | */ |
|
306 | 307 | if (options === undefined || options.updated_view != this) { |
|
307 | 308 | // Add missing items to the DOM. |
|
308 |
var items = this.model.get('_ |
|
|
309 | var items = this.model.get('_options_labels'); | |
|
309 | 310 | var disabled = this.model.get('disabled'); |
|
310 | 311 | var that = this; |
|
311 | 312 | var item_html; |
|
312 | 313 | _.each(items, function(item, index) { |
|
313 | 314 | if (item.trim().length === 0) { |
|
314 | 315 | item_html = " "; |
|
315 | 316 | } else { |
|
316 | 317 | item_html = utils.escape_html(item); |
|
317 | 318 | } |
|
318 | 319 | var item_query = '[data-value="' + encodeURIComponent(item) + '"]'; |
|
319 | 320 | var $item_element = that.$buttongroup.find(item_query); |
|
320 | 321 | if (!$item_element.length) { |
|
321 | 322 | $item_element = $('<button/>') |
|
322 | 323 | .attr('type', 'button') |
|
323 | 324 | .addClass('btn btn-default') |
|
324 | 325 | .html(item_html) |
|
325 | 326 | .appendTo(that.$buttongroup) |
|
326 | 327 | .attr('data-value', encodeURIComponent(item)) |
|
327 | 328 | .attr('value', item) |
|
328 | 329 | .on('click', $.proxy(that.handle_click, that)); |
|
329 | 330 | that.update_style_traits($item_element); |
|
330 | 331 | } |
|
331 |
if (that.model.get(' |
|
|
332 | if (that.model.get('selected_label') == item) { | |
|
332 | 333 | $item_element.addClass('active'); |
|
333 | 334 | } else { |
|
334 | 335 | $item_element.removeClass('active'); |
|
335 | 336 | } |
|
336 | 337 | $item_element.prop('disabled', disabled); |
|
337 | 338 | }); |
|
338 | 339 | |
|
339 | 340 | // Remove items that no longer exist. |
|
340 | 341 | this.$buttongroup.find('button').each(function(i, obj) { |
|
341 | 342 | var value = $(obj).attr('value'); |
|
342 | 343 | var found = false; |
|
343 | 344 | _.each(items, function(item, index) { |
|
344 | 345 | if (item == value) { |
|
345 | 346 | found = true; |
|
346 | 347 | return false; |
|
347 | 348 | } |
|
348 | 349 | }); |
|
349 | 350 | |
|
350 | 351 | if (!found) { |
|
351 | 352 | $(obj).remove(); |
|
352 | 353 | } |
|
353 | 354 | }); |
|
354 | 355 | |
|
355 | 356 | var description = this.model.get('description'); |
|
356 | 357 | if (description.length === 0) { |
|
357 | 358 | this.$label.hide(); |
|
358 | 359 | } else { |
|
359 | 360 | this.$label.text(); |
|
360 | 361 | this.typeset(this.$label, description); |
|
361 | 362 | this.$label.show(); |
|
362 | 363 | } |
|
363 | 364 | } |
|
364 | 365 | return ToggleButtonsView.__super__.update.apply(this); |
|
365 | 366 | }, |
|
366 | 367 | |
|
367 | 368 | update_attr: function(name, value) { |
|
368 | 369 | /** |
|
369 | 370 | * Set a css attr of the widget view. |
|
370 | 371 | */ |
|
371 | 372 | if (name == 'padding' || name == 'margin') { |
|
372 | 373 | this.$el.css(name, value); |
|
373 | 374 | } else { |
|
374 | 375 | this._css_state[name] = value; |
|
375 | 376 | this.update_style_traits(); |
|
376 | 377 | } |
|
377 | 378 | }, |
|
378 | 379 | |
|
379 | 380 | update_style_traits: function(button) { |
|
380 | 381 | for (var name in this._css_state) { |
|
381 | 382 | if (this._css_state.hasOwnProperty(name)) { |
|
382 | 383 | if (name == 'margin') { |
|
383 | 384 | this.$buttongroup.css(name, this._css_state[name]); |
|
384 | 385 | } else if (name != 'width') { |
|
385 | 386 | if (button) { |
|
386 | 387 | button.css(name, this._css_state[name]); |
|
387 | 388 | } else { |
|
388 | 389 | this.$buttongroup.find('button').css(name, this._css_state[name]); |
|
389 | 390 | } |
|
390 | 391 | } |
|
391 | 392 | } |
|
392 | 393 | } |
|
393 | 394 | }, |
|
394 | 395 | |
|
395 | 396 | update_button_style: function(previous_trait_value) { |
|
396 | 397 | var class_map = { |
|
397 | 398 | primary: ['btn-primary'], |
|
398 | 399 | success: ['btn-success'], |
|
399 | 400 | info: ['btn-info'], |
|
400 | 401 | warning: ['btn-warning'], |
|
401 | 402 | danger: ['btn-danger'] |
|
402 | 403 | }; |
|
403 | 404 | this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$buttongroup.find('button')); |
|
404 | 405 | }, |
|
405 | 406 | |
|
406 | 407 | handle_click: function (e) { |
|
407 | 408 | /** |
|
408 | 409 | * Handle when a value is clicked. |
|
409 | 410 | * |
|
410 | 411 | * Calling model.set will trigger all of the other views of the |
|
411 | 412 | * model to update. |
|
412 | 413 | */ |
|
413 |
this.model.set(' |
|
|
414 | this.model.set('selected_label', $(e.target).attr('value'), {updated_view: this}); | |
|
414 | 415 | this.touch(); |
|
415 | 416 | }, |
|
416 | 417 | }); |
|
417 | 418 | |
|
418 | 419 | |
|
419 | 420 | var SelectView = widget.DOMWidgetView.extend({ |
|
420 | 421 | render : function(){ |
|
421 | 422 | /** |
|
422 | 423 | * Called when view is rendered. |
|
423 | 424 | */ |
|
424 | 425 | this.$el |
|
425 | 426 | .addClass('widget-hbox widget-select'); |
|
426 | 427 | this.$label = $('<div />') |
|
427 | 428 | .appendTo(this.$el) |
|
428 | 429 | .addClass('widget-label') |
|
429 | 430 | .hide(); |
|
430 | 431 | this.$listbox = $('<select />') |
|
431 | 432 | .addClass('widget-listbox form-control') |
|
432 | 433 | .attr('size', 6) |
|
433 | 434 | .appendTo(this.$el); |
|
434 | 435 | this.update(); |
|
435 | 436 | }, |
|
436 | 437 | |
|
437 | 438 | update : function(options){ |
|
438 | 439 | /** |
|
439 | 440 | * Update the contents of this view |
|
440 | 441 | * |
|
441 | 442 | * Called when the model is changed. The model may have been |
|
442 | 443 | * changed by another view or by a state update from the back-end. |
|
443 | 444 | */ |
|
444 | 445 | if (options === undefined || options.updated_view != this) { |
|
445 | 446 | // Add missing items to the DOM. |
|
446 |
var items = this.model.get('_ |
|
|
447 | var items = this.model.get('_options_labels'); | |
|
447 | 448 | var that = this; |
|
448 | 449 | _.each(items, function(item, index) { |
|
449 | 450 | var item_query = 'option[data-value="' + encodeURIComponent(item) + '"]'; |
|
450 | 451 | if (that.$listbox.find(item_query).length === 0) { |
|
451 | 452 | $('<option />') |
|
452 | 453 | .text(item) |
|
453 | 454 | .attr('data-value', encodeURIComponent(item)) |
|
454 |
.attr(' |
|
|
455 | .attr('selected_label', item) | |
|
455 | 456 | .appendTo(that.$listbox) |
|
456 | 457 | .on('click', $.proxy(that.handle_click, that)); |
|
457 | 458 | } |
|
458 | 459 | }); |
|
459 | 460 | |
|
460 | 461 | // Select the correct element |
|
461 |
this.$listbox.val(this.model.get(' |
|
|
462 | this.$listbox.val(this.model.get('selected_label')); | |
|
462 | 463 | |
|
463 | 464 | // Disable listbox if needed |
|
464 | 465 | var disabled = this.model.get('disabled'); |
|
465 | 466 | this.$listbox.prop('disabled', disabled); |
|
466 | 467 | |
|
467 | 468 | // Remove items that no longer exist. |
|
468 | 469 | this.$listbox.find('option').each(function(i, obj) { |
|
469 | 470 | var value = $(obj).text(); |
|
470 | 471 | var found = false; |
|
471 | 472 | _.each(items, function(item, index) { |
|
472 | 473 | if (item == value) { |
|
473 | 474 | found = true; |
|
474 | 475 | return false; |
|
475 | 476 | } |
|
476 | 477 | }); |
|
477 | 478 | |
|
478 | 479 | if (!found) { |
|
479 | 480 | $(obj).remove(); |
|
480 | 481 | } |
|
481 | 482 | }); |
|
482 | 483 | |
|
483 | 484 | var description = this.model.get('description'); |
|
484 | 485 | if (description.length === 0) { |
|
485 | 486 | this.$label.hide(); |
|
486 | 487 | } else { |
|
487 | 488 | this.typeset(this.$label, description); |
|
488 | 489 | this.$label.show(); |
|
489 | 490 | } |
|
490 | 491 | } |
|
491 | 492 | return SelectView.__super__.update.apply(this); |
|
492 | 493 | }, |
|
493 | 494 | |
|
494 | 495 | update_attr: function(name, value) { |
|
495 | 496 | /** |
|
496 | 497 | * Set a css attr of the widget view. |
|
497 | 498 | */ |
|
498 | 499 | if (name == 'padding' || name == 'margin') { |
|
499 | 500 | this.$el.css(name, value); |
|
500 | 501 | } else { |
|
501 | 502 | this.$listbox.css(name, value); |
|
502 | 503 | } |
|
503 | 504 | }, |
|
504 | 505 | |
|
505 | 506 | handle_click: function (e) { |
|
506 | 507 | /** |
|
507 | 508 | * Handle when a value is clicked. |
|
508 | 509 | * |
|
509 | 510 | * Calling model.set will trigger all of the other views of the |
|
510 | 511 | * model to update. |
|
511 | 512 | */ |
|
512 |
this.model.set(' |
|
|
513 | this.model.set('selected_label', $(e.target).text(), {updated_view: this}); | |
|
513 | 514 | this.touch(); |
|
514 |
}, |
|
|
515 | }, | |
|
516 | }); | |
|
517 | var SelectMultipleView = SelectView.extend({ | |
|
518 | render: function(){ | |
|
519 | SelectMultipleView.__super__.render.apply(this); | |
|
520 | this.$el.removeClass('widget-select') | |
|
521 | .addClass('widget-select-multiple'); | |
|
522 | this.$listbox.attr('multiple', true) | |
|
523 | .on('input', $.proxy(this.handle_click, this)); | |
|
524 | return this; | |
|
525 | }, | |
|
526 | ||
|
527 | update: function(){ | |
|
528 | SelectMultipleView.__super__.update.apply(this, arguments); | |
|
529 | this.$listbox.val(this.model.get('selected_labels')); | |
|
530 | }, | |
|
531 | ||
|
532 | handle_click: function (e) { | |
|
533 | // Handle when a value is clicked. | |
|
534 | ||
|
535 | // Calling model.set will trigger all of the other views of the | |
|
536 | // model to update. | |
|
537 | this.model.set('selected_labels', | |
|
538 | (this.$listbox.val() || []).slice(), | |
|
539 | {updated_view: this}); | |
|
540 | this.touch(); | |
|
541 | }, | |
|
515 | 542 | }); |
|
516 | 543 | |
|
517 | 544 | return { |
|
518 | 545 | 'DropdownView': DropdownView, |
|
519 | 546 | 'RadioButtonsView': RadioButtonsView, |
|
520 | 547 | 'ToggleButtonsView': ToggleButtonsView, |
|
521 | 548 | 'SelectView': SelectView, |
|
549 | 'SelectMultipleView': SelectMultipleView, | |
|
522 | 550 | }; |
|
523 | 551 | }); |
@@ -1,148 +1,148 b'' | |||
|
1 | 1 | // Test selection class |
|
2 | 2 | casper.notebook_test(function () { |
|
3 | 3 | index = this.append_cell( |
|
4 | 4 | 'from IPython.html import widgets\n' + |
|
5 | 5 | 'from IPython.display import display, clear_output\n' + |
|
6 | 6 | 'print("Success")'); |
|
7 | 7 | this.execute_cell_then(index); |
|
8 | 8 | |
|
9 | 9 | var combo_selector = '.widget-area .widget-subarea .widget-hbox .btn-group .widget-combo-btn'; |
|
10 | 10 | var multibtn_selector = '.widget-area .widget-subarea .widget-hbox.widget-toggle-buttons .btn-group'; |
|
11 | 11 | var radio_selector = '.widget-area .widget-subarea .widget-hbox .widget-radio-box'; |
|
12 | 12 | var list_selector = '.widget-area .widget-subarea .widget-hbox .widget-listbox'; |
|
13 | 13 | |
|
14 | 14 | var selection_index; |
|
15 | 15 | var selection_values = 'abcd'; |
|
16 | 16 | var check_state = function(context, index, state){ |
|
17 | 17 | if (0 <= index && index < selection_values.length) { |
|
18 | 18 | var multibtn_state = context.cell_element_function(selection_index, multibtn_selector + ' .btn:nth-child(' + (index + 1) + ')', 'hasClass', ['active']); |
|
19 | 19 | var radio_state = context.cell_element_function(selection_index, radio_selector + ' .radio:nth-child(' + (index + 1) + ') input', 'prop', ['checked']); |
|
20 | 20 | var list_val = context.cell_element_function(selection_index, list_selector, 'val'); |
|
21 | 21 | var combo_val = context.cell_element_function(selection_index, combo_selector, 'html'); |
|
22 | 22 | |
|
23 | 23 | var val = selection_values.charAt(index); |
|
24 | 24 | var list_state = (val == list_val); |
|
25 | 25 | var combo_state = (val == combo_val); |
|
26 | 26 | |
|
27 | 27 | return multibtn_state == state && |
|
28 | 28 | radio_state == state && |
|
29 | 29 | list_state == state && |
|
30 | 30 | combo_state == state; |
|
31 | 31 | } |
|
32 | 32 | return true; |
|
33 | 33 | }; |
|
34 | 34 | |
|
35 | 35 | var verify_selection = function(context, index){ |
|
36 | 36 | for (var i = 0; i < selection_values.length; i++) { |
|
37 | 37 | if (!check_state(context, i, i==index)) { |
|
38 | 38 | return false; |
|
39 | 39 | } |
|
40 | 40 | } |
|
41 | 41 | return true; |
|
42 | 42 | }; |
|
43 | 43 | |
|
44 | 44 | //values=["' + selection_values + '"[i] for i in range(4)] |
|
45 | 45 | selection_index = this.append_cell( |
|
46 |
' |
|
|
47 |
'selection = [widgets.Dropdown( |
|
|
48 |
' widgets.ToggleButtons( |
|
|
49 |
' widgets.RadioButtons( |
|
|
50 |
' widgets.Select( |
|
|
46 | 'options=["' + selection_values + '"[i] for i in range(4)]\n' + | |
|
47 | 'selection = [widgets.Dropdown(options=options),\n' + | |
|
48 | ' widgets.ToggleButtons(options=options),\n' + | |
|
49 | ' widgets.RadioButtons(options=options),\n' + | |
|
50 | ' widgets.Select(options=options)]\n' + | |
|
51 | 51 | '[display(selection[i]) for i in range(4)]\n' + |
|
52 | 52 | 'for widget in selection:\n' + |
|
53 | 53 | ' def handle_change(name,old,new):\n' + |
|
54 | 54 | ' for other_widget in selection:\n' + |
|
55 | 55 | ' other_widget.value = new\n' + |
|
56 | 56 | ' widget.on_trait_change(handle_change, "value")\n' + |
|
57 | 57 | 'print("Success")\n'); |
|
58 | 58 | this.execute_cell_then(selection_index, function(index){ |
|
59 | 59 | this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', |
|
60 | 60 | 'Create selection cell executed with correct output.'); |
|
61 | 61 | }); |
|
62 | 62 | |
|
63 | 63 | // Wait for the widgets to actually display. |
|
64 | 64 | this.wait_for_element(selection_index, combo_selector); |
|
65 | 65 | this.wait_for_element(selection_index, multibtn_selector); |
|
66 | 66 | this.wait_for_element(selection_index, radio_selector); |
|
67 | 67 | this.wait_for_element(selection_index, list_selector); |
|
68 | 68 | |
|
69 | 69 | // Continue with the tests. |
|
70 | 70 | this.then(function() { |
|
71 | 71 | this.test.assert(this.cell_element_exists(selection_index, |
|
72 | 72 | '.widget-area .widget-subarea'), |
|
73 | 73 | 'Widget subarea exists.'); |
|
74 | 74 | |
|
75 | 75 | this.test.assert(this.cell_element_exists(selection_index, combo_selector), |
|
76 | 76 | 'Widget combobox exists.'); |
|
77 | 77 | |
|
78 | 78 | this.test.assert(this.cell_element_exists(selection_index, multibtn_selector), |
|
79 | 79 | 'Widget multibutton exists.'); |
|
80 | 80 | |
|
81 | 81 | this.test.assert(this.cell_element_exists(selection_index, radio_selector), |
|
82 | 82 | 'Widget radio buttons exists.'); |
|
83 | 83 | |
|
84 | 84 | this.test.assert(this.cell_element_exists(selection_index, list_selector), |
|
85 | 85 | 'Widget list exists.'); |
|
86 | 86 | |
|
87 | 87 | // Verify that no items are selected. |
|
88 | 88 | this.test.assert(verify_selection(this, 0), 'Default first item selected.'); |
|
89 | 89 | }); |
|
90 | 90 | |
|
91 | 91 | index = this.append_cell( |
|
92 | 92 | 'for widget in selection:\n' + |
|
93 | 93 | ' widget.value = "a"\n' + |
|
94 | 94 | 'print("Success")\n'); |
|
95 | 95 | this.execute_cell_then(index, function(index){ |
|
96 | 96 | this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', |
|
97 | 97 | 'Python select item executed with correct output.'); |
|
98 | 98 | |
|
99 | 99 | // Verify that the first item is selected. |
|
100 | 100 | this.test.assert(verify_selection(this, 0), 'Python selected'); |
|
101 | 101 | |
|
102 | 102 | // Verify that selecting a radio button updates all of the others. |
|
103 | 103 | this.cell_element_function(selection_index, radio_selector + ' .radio:nth-child(2) input', 'click'); |
|
104 | 104 | }); |
|
105 | 105 | this.wait_for_idle(); |
|
106 | 106 | this.then(function () { |
|
107 | 107 | this.test.assert(verify_selection(this, 1), 'Radio button selection updated view states correctly.'); |
|
108 | 108 | |
|
109 | 109 | // Verify that selecting a list option updates all of the others. |
|
110 | 110 | this.cell_element_function(selection_index, list_selector + ' option:nth-child(3)', 'click'); |
|
111 | 111 | }); |
|
112 | 112 | this.wait_for_idle(); |
|
113 | 113 | this.then(function () { |
|
114 | 114 | this.test.assert(verify_selection(this, 2), 'List selection updated view states correctly.'); |
|
115 | 115 | |
|
116 | 116 | // Verify that selecting a multibutton option updates all of the others. |
|
117 | 117 | // Bootstrap3 has changed the toggle button group behavior. Two clicks |
|
118 | 118 | // are required to actually select an item. |
|
119 | 119 | this.cell_element_function(selection_index, multibtn_selector + ' .btn:nth-child(4)', 'click'); |
|
120 | 120 | this.cell_element_function(selection_index, multibtn_selector + ' .btn:nth-child(4)', 'click'); |
|
121 | 121 | }); |
|
122 | 122 | this.wait_for_idle(); |
|
123 | 123 | this.then(function () { |
|
124 | 124 | this.test.assert(verify_selection(this, 3), 'Multibutton selection updated view states correctly.'); |
|
125 | 125 | |
|
126 | 126 | // Verify that selecting a combobox option updates all of the others. |
|
127 | 127 | this.cell_element_function(selection_index, '.widget-area .widget-subarea .widget-hbox .btn-group ul.dropdown-menu li:nth-child(3) a', 'click'); |
|
128 | 128 | }); |
|
129 | 129 | this.wait_for_idle(); |
|
130 | 130 | this.then(function () { |
|
131 | 131 | this.test.assert(verify_selection(this, 2), 'Combobox selection updated view states correctly.'); |
|
132 | 132 | }); |
|
133 | 133 | |
|
134 | 134 | this.wait_for_idle(); |
|
135 | 135 | |
|
136 | 136 | index = this.append_cell( |
|
137 | 137 | 'from copy import copy\n' + |
|
138 | 138 | 'for widget in selection:\n' + |
|
139 |
' d = copy(widget. |
|
|
139 | ' d = copy(widget.options)\n' + | |
|
140 | 140 | ' d.append("z")\n' + |
|
141 |
' widget. |
|
|
141 | ' widget.options = d\n' + | |
|
142 | 142 | 'selection[0].value = "z"'); |
|
143 | 143 | this.execute_cell_then(index, function(index){ |
|
144 | 144 | |
|
145 | 145 | // Verify that selecting a combobox option updates all of the others. |
|
146 | 146 | this.test.assert(verify_selection(this, 4), 'Item added to selection widget.'); |
|
147 | 147 | }); |
|
148 | 148 | }); No newline at end of file |
@@ -1,38 +1,38 b'' | |||
|
1 | 1 | from .widget import Widget, DOMWidget, CallbackDispatcher, register |
|
2 | 2 | |
|
3 | 3 | from .widget_bool import Checkbox, ToggleButton |
|
4 | 4 | from .widget_button import Button |
|
5 | 5 | from .widget_box import Box, FlexBox, HBox, VBox |
|
6 | 6 | from .widget_float import FloatText, BoundedFloatText, FloatSlider, FloatProgress, FloatRangeSlider |
|
7 | 7 | from .widget_image import Image |
|
8 | 8 | from .widget_int import IntText, BoundedIntText, IntSlider, IntProgress, IntRangeSlider |
|
9 | 9 | from .widget_output import Output |
|
10 | from .widget_selection import RadioButtons, ToggleButtons, Dropdown, Select | |
|
10 | from .widget_selection import RadioButtons, ToggleButtons, Dropdown, Select, SelectMultiple | |
|
11 | 11 | from .widget_selectioncontainer import Tab, Accordion |
|
12 | 12 | from .widget_string import HTML, Latex, Text, Textarea |
|
13 | 13 | from .interaction import interact, interactive, fixed, interact_manual |
|
14 | 14 | from .widget_link import jslink, jsdlink |
|
15 | 15 | |
|
16 | 16 | # Deprecated classes |
|
17 | 17 | from .widget_bool import CheckboxWidget, ToggleButtonWidget |
|
18 | 18 | from .widget_button import ButtonWidget |
|
19 | 19 | from .widget_box import ContainerWidget |
|
20 | 20 | from .widget_float import FloatTextWidget, BoundedFloatTextWidget, FloatSliderWidget, FloatProgressWidget |
|
21 | 21 | from .widget_image import ImageWidget |
|
22 | 22 | from .widget_int import IntTextWidget, BoundedIntTextWidget, IntSliderWidget, IntProgressWidget |
|
23 | 23 | from .widget_selection import RadioButtonsWidget, ToggleButtonsWidget, DropdownWidget, SelectWidget |
|
24 | 24 | from .widget_selectioncontainer import TabWidget, AccordionWidget |
|
25 | 25 | from .widget_string import HTMLWidget, LatexWidget, TextWidget, TextareaWidget |
|
26 | 26 | |
|
27 | 27 | # We use warn_explicit so we have very brief messages without file or line numbers. |
|
28 | 28 | # The concern is that file or line numbers will confuse the interactive user. |
|
29 | 29 | # To ignore this warning, do: |
|
30 | 30 | # |
|
31 | 31 | # from warnings import filterwarnings |
|
32 | 32 | # filterwarnings('ignore', module='IPython.html.widgets') |
|
33 | 33 | |
|
34 | 34 | from warnings import warn_explicit |
|
35 | 35 | __warningregistry__ = {} |
|
36 | 36 | warn_explicit("IPython widgets are experimental and may change in the future.", |
|
37 | 37 | FutureWarning, '', 0, module = 'IPython.html.widgets', |
|
38 | 38 | registry = __warningregistry__, module_globals = globals) |
@@ -1,349 +1,349 b'' | |||
|
1 | 1 | """Interact with functions using widgets.""" |
|
2 | 2 | |
|
3 | 3 | # Copyright (c) IPython Development Team. |
|
4 | 4 | # Distributed under the terms of the Modified BSD License. |
|
5 | 5 | |
|
6 | 6 | from __future__ import print_function |
|
7 | 7 | |
|
8 | 8 | try: # Python >= 3.3 |
|
9 | 9 | from inspect import signature, Parameter |
|
10 | 10 | except ImportError: |
|
11 | 11 | from IPython.utils.signatures import signature, Parameter |
|
12 | 12 | from inspect import getcallargs |
|
13 | 13 | |
|
14 | 14 | from IPython.core.getipython import get_ipython |
|
15 | 15 | from IPython.html.widgets import (Widget, Text, |
|
16 | 16 | FloatSlider, IntSlider, Checkbox, Dropdown, |
|
17 | 17 | Box, Button, DOMWidget, Output) |
|
18 | 18 | from IPython.display import display, clear_output |
|
19 | 19 | from IPython.utils.py3compat import string_types, unicode_type |
|
20 | 20 | from IPython.utils.traitlets import HasTraits, Any, Unicode |
|
21 | 21 | |
|
22 | 22 | empty = Parameter.empty |
|
23 | 23 | |
|
24 | 24 | |
|
25 | 25 | def _matches(o, pattern): |
|
26 | 26 | """Match a pattern of types in a sequence.""" |
|
27 | 27 | if not len(o) == len(pattern): |
|
28 | 28 | return False |
|
29 | 29 | comps = zip(o,pattern) |
|
30 | 30 | return all(isinstance(obj,kind) for obj,kind in comps) |
|
31 | 31 | |
|
32 | 32 | |
|
33 | 33 | def _get_min_max_value(min, max, value=None, step=None): |
|
34 | 34 | """Return min, max, value given input values with possible None.""" |
|
35 | 35 | if value is None: |
|
36 | 36 | if not max > min: |
|
37 | 37 | raise ValueError('max must be greater than min: (min={0}, max={1})'.format(min, max)) |
|
38 | 38 | value = min + abs(min-max)/2 |
|
39 | 39 | value = type(min)(value) |
|
40 | 40 | elif min is None and max is None: |
|
41 | 41 | if value == 0.0: |
|
42 | 42 | min, max, value = 0.0, 1.0, 0.5 |
|
43 | 43 | elif value == 0: |
|
44 | 44 | min, max, value = 0, 1, 0 |
|
45 | 45 | elif isinstance(value, (int, float)): |
|
46 | 46 | min, max = (-value, 3*value) if value > 0 else (3*value, -value) |
|
47 | 47 | else: |
|
48 | 48 | raise TypeError('expected a number, got: %r' % value) |
|
49 | 49 | else: |
|
50 | 50 | raise ValueError('unable to infer range, value from: ({0}, {1}, {2})'.format(min, max, value)) |
|
51 | 51 | if step is not None: |
|
52 | 52 | # ensure value is on a step |
|
53 | 53 | r = (value - min) % step |
|
54 | 54 | value = value - r |
|
55 | 55 | return min, max, value |
|
56 | 56 | |
|
57 | 57 | def _widget_abbrev_single_value(o): |
|
58 | 58 | """Make widgets from single values, which can be used as parameter defaults.""" |
|
59 | 59 | if isinstance(o, string_types): |
|
60 | 60 | return Text(value=unicode_type(o)) |
|
61 | 61 | elif isinstance(o, dict): |
|
62 |
return Dropdown( |
|
|
62 | return Dropdown(options=o) | |
|
63 | 63 | elif isinstance(o, bool): |
|
64 | 64 | return Checkbox(value=o) |
|
65 | 65 | elif isinstance(o, float): |
|
66 | 66 | min, max, value = _get_min_max_value(None, None, o) |
|
67 | 67 | return FloatSlider(value=o, min=min, max=max) |
|
68 | 68 | elif isinstance(o, int): |
|
69 | 69 | min, max, value = _get_min_max_value(None, None, o) |
|
70 | 70 | return IntSlider(value=o, min=min, max=max) |
|
71 | 71 | else: |
|
72 | 72 | return None |
|
73 | 73 | |
|
74 | 74 | def _widget_abbrev(o): |
|
75 | 75 | """Make widgets from abbreviations: single values, lists or tuples.""" |
|
76 | 76 | float_or_int = (float, int) |
|
77 | 77 | if isinstance(o, (list, tuple)): |
|
78 | 78 | if o and all(isinstance(x, string_types) for x in o): |
|
79 |
return Dropdown( |
|
|
79 | return Dropdown(options=[unicode_type(k) for k in o]) | |
|
80 | 80 | elif _matches(o, (float_or_int, float_or_int)): |
|
81 | 81 | min, max, value = _get_min_max_value(o[0], o[1]) |
|
82 | 82 | if all(isinstance(_, int) for _ in o): |
|
83 | 83 | cls = IntSlider |
|
84 | 84 | else: |
|
85 | 85 | cls = FloatSlider |
|
86 | 86 | return cls(value=value, min=min, max=max) |
|
87 | 87 | elif _matches(o, (float_or_int, float_or_int, float_or_int)): |
|
88 | 88 | step = o[2] |
|
89 | 89 | if step <= 0: |
|
90 | 90 | raise ValueError("step must be >= 0, not %r" % step) |
|
91 | 91 | min, max, value = _get_min_max_value(o[0], o[1], step=step) |
|
92 | 92 | if all(isinstance(_, int) for _ in o): |
|
93 | 93 | cls = IntSlider |
|
94 | 94 | else: |
|
95 | 95 | cls = FloatSlider |
|
96 | 96 | return cls(value=value, min=min, max=max, step=step) |
|
97 | 97 | else: |
|
98 | 98 | return _widget_abbrev_single_value(o) |
|
99 | 99 | |
|
100 | 100 | def _widget_from_abbrev(abbrev, default=empty): |
|
101 | 101 | """Build a Widget instance given an abbreviation or Widget.""" |
|
102 | 102 | if isinstance(abbrev, Widget) or isinstance(abbrev, fixed): |
|
103 | 103 | return abbrev |
|
104 | 104 | |
|
105 | 105 | widget = _widget_abbrev(abbrev) |
|
106 | 106 | if default is not empty and isinstance(abbrev, (list, tuple, dict)): |
|
107 | 107 | # if it's not a single-value abbreviation, |
|
108 | 108 | # set the initial value from the default |
|
109 | 109 | try: |
|
110 | 110 | widget.value = default |
|
111 | 111 | except Exception: |
|
112 | 112 | # ignore failure to set default |
|
113 | 113 | pass |
|
114 | 114 | if widget is None: |
|
115 | 115 | raise ValueError("%r cannot be transformed to a Widget" % (abbrev,)) |
|
116 | 116 | return widget |
|
117 | 117 | |
|
118 | 118 | def _yield_abbreviations_for_parameter(param, kwargs): |
|
119 | 119 | """Get an abbreviation for a function parameter.""" |
|
120 | 120 | name = param.name |
|
121 | 121 | kind = param.kind |
|
122 | 122 | ann = param.annotation |
|
123 | 123 | default = param.default |
|
124 | 124 | not_found = (name, empty, empty) |
|
125 | 125 | if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY): |
|
126 | 126 | if name in kwargs: |
|
127 | 127 | value = kwargs.pop(name) |
|
128 | 128 | elif ann is not empty: |
|
129 | 129 | value = ann |
|
130 | 130 | elif default is not empty: |
|
131 | 131 | value = default |
|
132 | 132 | else: |
|
133 | 133 | yield not_found |
|
134 | 134 | yield (name, value, default) |
|
135 | 135 | elif kind == Parameter.VAR_KEYWORD: |
|
136 | 136 | # In this case name=kwargs and we yield the items in kwargs with their keys. |
|
137 | 137 | for k, v in kwargs.copy().items(): |
|
138 | 138 | kwargs.pop(k) |
|
139 | 139 | yield k, v, empty |
|
140 | 140 | |
|
141 | 141 | def _find_abbreviations(f, kwargs): |
|
142 | 142 | """Find the abbreviations for a function and kwargs passed to interact.""" |
|
143 | 143 | new_kwargs = [] |
|
144 | 144 | for param in signature(f).parameters.values(): |
|
145 | 145 | for name, value, default in _yield_abbreviations_for_parameter(param, kwargs): |
|
146 | 146 | if value is empty: |
|
147 | 147 | raise ValueError('cannot find widget or abbreviation for argument: {!r}'.format(name)) |
|
148 | 148 | new_kwargs.append((name, value, default)) |
|
149 | 149 | return new_kwargs |
|
150 | 150 | |
|
151 | 151 | def _widgets_from_abbreviations(seq): |
|
152 | 152 | """Given a sequence of (name, abbrev) tuples, return a sequence of Widgets.""" |
|
153 | 153 | result = [] |
|
154 | 154 | for name, abbrev, default in seq: |
|
155 | 155 | widget = _widget_from_abbrev(abbrev, default) |
|
156 | 156 | if not widget.description: |
|
157 | 157 | widget.description = name |
|
158 | 158 | widget._kwarg = name |
|
159 | 159 | result.append(widget) |
|
160 | 160 | return result |
|
161 | 161 | |
|
162 | 162 | def interactive(__interact_f, **kwargs): |
|
163 | 163 | """ |
|
164 | 164 | Builds a group of interactive widgets tied to a function and places the |
|
165 | 165 | group into a Box container. |
|
166 | 166 | |
|
167 | 167 | Returns |
|
168 | 168 | ------- |
|
169 | 169 | container : a Box instance containing multiple widgets |
|
170 | 170 | |
|
171 | 171 | Parameters |
|
172 | 172 | ---------- |
|
173 | 173 | __interact_f : function |
|
174 | 174 | The function to which the interactive widgets are tied. The **kwargs |
|
175 | 175 | should match the function signature. |
|
176 | 176 | **kwargs : various, optional |
|
177 | 177 | An interactive widget is created for each keyword argument that is a |
|
178 | 178 | valid widget abbreviation. |
|
179 | 179 | """ |
|
180 | 180 | f = __interact_f |
|
181 | 181 | co = kwargs.pop('clear_output', True) |
|
182 | 182 | manual = kwargs.pop('__manual', False) |
|
183 | 183 | kwargs_widgets = [] |
|
184 | 184 | container = Box() |
|
185 | 185 | container.result = None |
|
186 | 186 | container.args = [] |
|
187 | 187 | container.kwargs = dict() |
|
188 | 188 | kwargs = kwargs.copy() |
|
189 | 189 | |
|
190 | 190 | new_kwargs = _find_abbreviations(f, kwargs) |
|
191 | 191 | # Before we proceed, let's make sure that the user has passed a set of args+kwargs |
|
192 | 192 | # that will lead to a valid call of the function. This protects against unspecified |
|
193 | 193 | # and doubly-specified arguments. |
|
194 | 194 | getcallargs(f, **{n:v for n,v,_ in new_kwargs}) |
|
195 | 195 | # Now build the widgets from the abbreviations. |
|
196 | 196 | kwargs_widgets.extend(_widgets_from_abbreviations(new_kwargs)) |
|
197 | 197 | |
|
198 | 198 | # This has to be done as an assignment, not using container.children.append, |
|
199 | 199 | # so that traitlets notices the update. We skip any objects (such as fixed) that |
|
200 | 200 | # are not DOMWidgets. |
|
201 | 201 | c = [w for w in kwargs_widgets if isinstance(w, DOMWidget)] |
|
202 | 202 | |
|
203 | 203 | # If we are only to run the function on demand, add a button to request this |
|
204 | 204 | if manual: |
|
205 | 205 | manual_button = Button(description="Run %s" % f.__name__) |
|
206 | 206 | c.append(manual_button) |
|
207 | 207 | |
|
208 | 208 | # Use an output widget to capture the output of interact. |
|
209 | 209 | output = Output() |
|
210 | 210 | c.append(output) |
|
211 | 211 | container.children = c |
|
212 | 212 | |
|
213 | 213 | # Build the callback |
|
214 | 214 | def call_f(name=None, old=None, new=None): |
|
215 | 215 | with output: |
|
216 | 216 | container.kwargs = {} |
|
217 | 217 | for widget in kwargs_widgets: |
|
218 | 218 | value = widget.value |
|
219 | 219 | container.kwargs[widget._kwarg] = value |
|
220 | 220 | if co: |
|
221 | 221 | clear_output(wait=True) |
|
222 | 222 | if manual: |
|
223 | 223 | manual_button.disabled = True |
|
224 | 224 | try: |
|
225 | 225 | container.result = f(**container.kwargs) |
|
226 | 226 | except Exception as e: |
|
227 | 227 | ip = get_ipython() |
|
228 | 228 | if ip is None: |
|
229 | 229 | container.log.warn("Exception in interact callback: %s", e, exc_info=True) |
|
230 | 230 | else: |
|
231 | 231 | ip.showtraceback() |
|
232 | 232 | finally: |
|
233 | 233 | if manual: |
|
234 | 234 | manual_button.disabled = False |
|
235 | 235 | |
|
236 | 236 | # Wire up the widgets |
|
237 | 237 | # If we are doing manual running, the callback is only triggered by the button |
|
238 | 238 | # Otherwise, it is triggered for every trait change received |
|
239 | 239 | # On-demand running also suppresses running the function with the initial parameters |
|
240 | 240 | if manual: |
|
241 | 241 | manual_button.on_click(call_f) |
|
242 | 242 | else: |
|
243 | 243 | for widget in kwargs_widgets: |
|
244 | 244 | widget.on_trait_change(call_f, 'value') |
|
245 | 245 | |
|
246 | 246 | container.on_displayed(lambda _: call_f(None, None, None)) |
|
247 | 247 | |
|
248 | 248 | return container |
|
249 | 249 | |
|
250 | 250 | def interact(__interact_f=None, **kwargs): |
|
251 | 251 | """ |
|
252 | 252 | Displays interactive widgets which are tied to a function. |
|
253 | 253 | Expects the first argument to be a function. Parameters to this function are |
|
254 | 254 | widget abbreviations passed in as keyword arguments (**kwargs). Can be used |
|
255 | 255 | as a decorator (see examples). |
|
256 | 256 | |
|
257 | 257 | Returns |
|
258 | 258 | ------- |
|
259 | 259 | f : __interact_f with interactive widget attached to it. |
|
260 | 260 | |
|
261 | 261 | Parameters |
|
262 | 262 | ---------- |
|
263 | 263 | __interact_f : function |
|
264 | 264 | The function to which the interactive widgets are tied. The **kwargs |
|
265 | 265 | should match the function signature. Passed to :func:`interactive()` |
|
266 | 266 | **kwargs : various, optional |
|
267 | 267 | An interactive widget is created for each keyword argument that is a |
|
268 | 268 | valid widget abbreviation. Passed to :func:`interactive()` |
|
269 | 269 | |
|
270 | 270 | Examples |
|
271 | 271 | -------- |
|
272 | 272 | Renders an interactive text field that shows the greeting with the passed in |
|
273 | 273 | text. |
|
274 | 274 | |
|
275 | 275 | 1. Invocation of interact as a function |
|
276 | 276 | def greeting(text="World"): |
|
277 | 277 | print "Hello {}".format(text) |
|
278 | 278 | interact(greeting, text="IPython Widgets") |
|
279 | 279 | |
|
280 | 280 | 2. Invocation of interact as a decorator |
|
281 | 281 | @interact |
|
282 | 282 | def greeting(text="World"): |
|
283 | 283 | print "Hello {}".format(text) |
|
284 | 284 | |
|
285 | 285 | 3. Invocation of interact as a decorator with named parameters |
|
286 | 286 | @interact(text="IPython Widgets") |
|
287 | 287 | def greeting(text="World"): |
|
288 | 288 | print "Hello {}".format(text) |
|
289 | 289 | |
|
290 | 290 | Renders an interactive slider widget and prints square of number. |
|
291 | 291 | |
|
292 | 292 | 1. Invocation of interact as a function |
|
293 | 293 | def square(num=1): |
|
294 | 294 | print "{} squared is {}".format(num, num*num) |
|
295 | 295 | interact(square, num=5) |
|
296 | 296 | |
|
297 | 297 | 2. Invocation of interact as a decorator |
|
298 | 298 | @interact |
|
299 | 299 | def square(num=2): |
|
300 | 300 | print "{} squared is {}".format(num, num*num) |
|
301 | 301 | |
|
302 | 302 | 3. Invocation of interact as a decorator with named parameters |
|
303 | 303 | @interact(num=5) |
|
304 | 304 | def square(num=2): |
|
305 | 305 | print "{} squared is {}".format(num, num*num) |
|
306 | 306 | """ |
|
307 | 307 | # positional arg support in: https://gist.github.com/8851331 |
|
308 | 308 | if __interact_f is not None: |
|
309 | 309 | # This branch handles the cases 1 and 2 |
|
310 | 310 | # 1. interact(f, **kwargs) |
|
311 | 311 | # 2. @interact |
|
312 | 312 | # def f(*args, **kwargs): |
|
313 | 313 | # ... |
|
314 | 314 | f = __interact_f |
|
315 | 315 | w = interactive(f, **kwargs) |
|
316 | 316 | try: |
|
317 | 317 | f.widget = w |
|
318 | 318 | except AttributeError: |
|
319 | 319 | # some things (instancemethods) can't have attributes attached, |
|
320 | 320 | # so wrap in a lambda |
|
321 | 321 | f = lambda *args, **kwargs: __interact_f(*args, **kwargs) |
|
322 | 322 | f.widget = w |
|
323 | 323 | display(w) |
|
324 | 324 | return f |
|
325 | 325 | else: |
|
326 | 326 | # This branch handles the case 3 |
|
327 | 327 | # @interact(a=30, b=40) |
|
328 | 328 | # def f(*args, **kwargs): |
|
329 | 329 | # ... |
|
330 | 330 | def dec(f): |
|
331 | 331 | return interact(f, **kwargs) |
|
332 | 332 | return dec |
|
333 | 333 | |
|
334 | 334 | def interact_manual(__interact_f=None, **kwargs): |
|
335 | 335 | """interact_manual(f, **kwargs) |
|
336 | 336 | |
|
337 | 337 | As `interact()`, generates widgets for each argument, but rather than running |
|
338 | 338 | the function after each widget change, adds a "Run" button and waits for it |
|
339 | 339 | to be clicked. Useful if the function is long-running and has several |
|
340 | 340 | parameters to change. |
|
341 | 341 | """ |
|
342 | 342 | return interact(__interact_f, __manual=True, **kwargs) |
|
343 | 343 | |
|
344 | 344 | class fixed(HasTraits): |
|
345 | 345 | """A pseudo-widget whose value is fixed and never synced to the client.""" |
|
346 | 346 | value = Any(help="Any Python object") |
|
347 | 347 | description = Unicode('', help="Any Python object") |
|
348 | 348 | def __init__(self, value, **kwargs): |
|
349 | 349 | super(fixed, self).__init__(value=value, **kwargs) |
@@ -1,636 +1,692 b'' | |||
|
1 | 1 | """Test interact and interactive.""" |
|
2 | 2 | |
|
3 | 3 | # Copyright (c) IPython Development Team. |
|
4 | 4 | # Distributed under the terms of the Modified BSD License. |
|
5 | 5 | |
|
6 | 6 | from __future__ import print_function |
|
7 | 7 | |
|
8 | 8 | from collections import OrderedDict |
|
9 | 9 | |
|
10 | 10 | import nose.tools as nt |
|
11 | 11 | import IPython.testing.tools as tt |
|
12 | 12 | |
|
13 | 13 | from IPython.kernel.comm import Comm |
|
14 | 14 | from IPython.html import widgets |
|
15 | 15 | from IPython.html.widgets import interact, interactive, Widget, interaction |
|
16 | 16 | from IPython.utils.py3compat import annotate |
|
17 | 17 | |
|
18 | 18 | #----------------------------------------------------------------------------- |
|
19 | 19 | # Utility stuff |
|
20 | 20 | #----------------------------------------------------------------------------- |
|
21 | 21 | |
|
22 | 22 | class DummyComm(Comm): |
|
23 | 23 | comm_id = 'a-b-c-d' |
|
24 | 24 | |
|
25 | 25 | def open(self, *args, **kwargs): |
|
26 | 26 | pass |
|
27 | 27 | |
|
28 | 28 | def send(self, *args, **kwargs): |
|
29 | 29 | pass |
|
30 | 30 | |
|
31 | 31 | def close(self, *args, **kwargs): |
|
32 | 32 | pass |
|
33 | 33 | |
|
34 | 34 | _widget_attrs = {} |
|
35 | 35 | displayed = [] |
|
36 | 36 | undefined = object() |
|
37 | 37 | |
|
38 | 38 | def setup(): |
|
39 | 39 | _widget_attrs['_comm_default'] = getattr(Widget, '_comm_default', undefined) |
|
40 | 40 | Widget._comm_default = lambda self: DummyComm() |
|
41 | 41 | _widget_attrs['_ipython_display_'] = Widget._ipython_display_ |
|
42 | 42 | def raise_not_implemented(*args, **kwargs): |
|
43 | 43 | raise NotImplementedError() |
|
44 | 44 | Widget._ipython_display_ = raise_not_implemented |
|
45 | 45 | |
|
46 | 46 | def teardown(): |
|
47 | 47 | for attr, value in _widget_attrs.items(): |
|
48 | 48 | if value is undefined: |
|
49 | 49 | delattr(Widget, attr) |
|
50 | 50 | else: |
|
51 | 51 | setattr(Widget, attr, value) |
|
52 | 52 | |
|
53 | 53 | def f(**kwargs): |
|
54 | 54 | pass |
|
55 | 55 | |
|
56 | 56 | def clear_display(): |
|
57 | 57 | global displayed |
|
58 | 58 | displayed = [] |
|
59 | 59 | |
|
60 | 60 | def record_display(*args): |
|
61 | 61 | displayed.extend(args) |
|
62 | 62 | |
|
63 | 63 | #----------------------------------------------------------------------------- |
|
64 | 64 | # Actual tests |
|
65 | 65 | #----------------------------------------------------------------------------- |
|
66 | 66 | |
|
67 | 67 | def check_widget(w, **d): |
|
68 | 68 | """Check a single widget against a dict""" |
|
69 | 69 | for attr, expected in d.items(): |
|
70 | 70 | if attr == 'cls': |
|
71 | 71 | nt.assert_is(w.__class__, expected) |
|
72 | 72 | else: |
|
73 | 73 | value = getattr(w, attr) |
|
74 | 74 | nt.assert_equal(value, expected, |
|
75 | 75 | "%s.%s = %r != %r" % (w.__class__.__name__, attr, value, expected) |
|
76 | 76 | ) |
|
77 | 77 | |
|
78 | 78 | def check_widgets(container, **to_check): |
|
79 | 79 | """Check that widgets are created as expected""" |
|
80 | 80 | # build a widget dictionary, so it matches |
|
81 | 81 | widgets = {} |
|
82 | 82 | for w in container.children: |
|
83 | 83 | if hasattr(w, 'description'): |
|
84 | 84 | widgets[w.description] = w |
|
85 | 85 | |
|
86 | 86 | for key, d in to_check.items(): |
|
87 | 87 | nt.assert_in(key, widgets) |
|
88 | 88 | check_widget(widgets[key], **d) |
|
89 | 89 | |
|
90 | 90 | |
|
91 | 91 | def test_single_value_string(): |
|
92 | 92 | a = u'hello' |
|
93 | 93 | c = interactive(f, a=a) |
|
94 | 94 | w = c.children[0] |
|
95 | 95 | check_widget(w, |
|
96 | 96 | cls=widgets.Text, |
|
97 | 97 | description='a', |
|
98 | 98 | value=a, |
|
99 | 99 | ) |
|
100 | 100 | |
|
101 | 101 | def test_single_value_bool(): |
|
102 | 102 | for a in (True, False): |
|
103 | 103 | c = interactive(f, a=a) |
|
104 | 104 | w = c.children[0] |
|
105 | 105 | check_widget(w, |
|
106 | 106 | cls=widgets.Checkbox, |
|
107 | 107 | description='a', |
|
108 | 108 | value=a, |
|
109 | 109 | ) |
|
110 | 110 | |
|
111 | 111 | def test_single_value_dict(): |
|
112 | 112 | for d in [ |
|
113 | 113 | dict(a=5), |
|
114 | 114 | dict(a=5, b='b', c=dict), |
|
115 | 115 | ]: |
|
116 | 116 | c = interactive(f, d=d) |
|
117 | 117 | w = c.children[0] |
|
118 | 118 | check_widget(w, |
|
119 | 119 | cls=widgets.Dropdown, |
|
120 | 120 | description='d', |
|
121 |
|
|
|
121 | options=d, | |
|
122 | 122 | value=next(iter(d.values())), |
|
123 | 123 | ) |
|
124 | 124 | |
|
125 | 125 | def test_single_value_float(): |
|
126 | 126 | for a in (2.25, 1.0, -3.5): |
|
127 | 127 | c = interactive(f, a=a) |
|
128 | 128 | w = c.children[0] |
|
129 | 129 | check_widget(w, |
|
130 | 130 | cls=widgets.FloatSlider, |
|
131 | 131 | description='a', |
|
132 | 132 | value=a, |
|
133 | 133 | min= -a if a > 0 else 3*a, |
|
134 | 134 | max= 3*a if a > 0 else -a, |
|
135 | 135 | step=0.1, |
|
136 | 136 | readout=True, |
|
137 | 137 | ) |
|
138 | 138 | |
|
139 | 139 | def test_single_value_int(): |
|
140 | 140 | for a in (1, 5, -3): |
|
141 | 141 | c = interactive(f, a=a) |
|
142 | 142 | nt.assert_equal(len(c.children), 2) |
|
143 | 143 | w = c.children[0] |
|
144 | 144 | check_widget(w, |
|
145 | 145 | cls=widgets.IntSlider, |
|
146 | 146 | description='a', |
|
147 | 147 | value=a, |
|
148 | 148 | min= -a if a > 0 else 3*a, |
|
149 | 149 | max= 3*a if a > 0 else -a, |
|
150 | 150 | step=1, |
|
151 | 151 | readout=True, |
|
152 | 152 | ) |
|
153 | 153 | |
|
154 | 154 | def test_list_tuple_2_int(): |
|
155 | 155 | with nt.assert_raises(ValueError): |
|
156 | 156 | c = interactive(f, tup=(1,1)) |
|
157 | 157 | with nt.assert_raises(ValueError): |
|
158 | 158 | c = interactive(f, tup=(1,-1)) |
|
159 | 159 | for min, max in [ (0,1), (1,10), (1,2), (-5,5), (-20,-19) ]: |
|
160 | 160 | c = interactive(f, tup=(min, max), lis=[min, max]) |
|
161 | 161 | nt.assert_equal(len(c.children), 3) |
|
162 | 162 | d = dict( |
|
163 | 163 | cls=widgets.IntSlider, |
|
164 | 164 | min=min, |
|
165 | 165 | max=max, |
|
166 | 166 | step=1, |
|
167 | 167 | readout=True, |
|
168 | 168 | ) |
|
169 | 169 | check_widgets(c, tup=d, lis=d) |
|
170 | 170 | |
|
171 | 171 | def test_list_tuple_3_int(): |
|
172 | 172 | with nt.assert_raises(ValueError): |
|
173 | 173 | c = interactive(f, tup=(1,2,0)) |
|
174 | 174 | with nt.assert_raises(ValueError): |
|
175 | 175 | c = interactive(f, tup=(1,2,-1)) |
|
176 | 176 | for min, max, step in [ (0,2,1), (1,10,2), (1,100,2), (-5,5,4), (-100,-20,4) ]: |
|
177 | 177 | c = interactive(f, tup=(min, max, step), lis=[min, max, step]) |
|
178 | 178 | nt.assert_equal(len(c.children), 3) |
|
179 | 179 | d = dict( |
|
180 | 180 | cls=widgets.IntSlider, |
|
181 | 181 | min=min, |
|
182 | 182 | max=max, |
|
183 | 183 | step=step, |
|
184 | 184 | readout=True, |
|
185 | 185 | ) |
|
186 | 186 | check_widgets(c, tup=d, lis=d) |
|
187 | 187 | |
|
188 | 188 | def test_list_tuple_2_float(): |
|
189 | 189 | with nt.assert_raises(ValueError): |
|
190 | 190 | c = interactive(f, tup=(1.0,1.0)) |
|
191 | 191 | with nt.assert_raises(ValueError): |
|
192 | 192 | c = interactive(f, tup=(0.5,-0.5)) |
|
193 | 193 | for min, max in [ (0.5, 1.5), (1.1,10.2), (1,2.2), (-5.,5), (-20,-19.) ]: |
|
194 | 194 | c = interactive(f, tup=(min, max), lis=[min, max]) |
|
195 | 195 | nt.assert_equal(len(c.children), 3) |
|
196 | 196 | d = dict( |
|
197 | 197 | cls=widgets.FloatSlider, |
|
198 | 198 | min=min, |
|
199 | 199 | max=max, |
|
200 | 200 | step=.1, |
|
201 | 201 | readout=True, |
|
202 | 202 | ) |
|
203 | 203 | check_widgets(c, tup=d, lis=d) |
|
204 | 204 | |
|
205 | 205 | def test_list_tuple_3_float(): |
|
206 | 206 | with nt.assert_raises(ValueError): |
|
207 | 207 | c = interactive(f, tup=(1,2,0.0)) |
|
208 | 208 | with nt.assert_raises(ValueError): |
|
209 | 209 | c = interactive(f, tup=(-1,-2,1.)) |
|
210 | 210 | with nt.assert_raises(ValueError): |
|
211 | 211 | c = interactive(f, tup=(1,2.,-1.)) |
|
212 | 212 | for min, max, step in [ (0.,2,1), (1,10.,2), (1,100,2.), (-5.,5.,4), (-100,-20.,4.) ]: |
|
213 | 213 | c = interactive(f, tup=(min, max, step), lis=[min, max, step]) |
|
214 | 214 | nt.assert_equal(len(c.children), 3) |
|
215 | 215 | d = dict( |
|
216 | 216 | cls=widgets.FloatSlider, |
|
217 | 217 | min=min, |
|
218 | 218 | max=max, |
|
219 | 219 | step=step, |
|
220 | 220 | readout=True, |
|
221 | 221 | ) |
|
222 | 222 | check_widgets(c, tup=d, lis=d) |
|
223 | 223 | |
|
224 | 224 | def test_list_tuple_str(): |
|
225 | 225 | values = ['hello', 'there', 'guy'] |
|
226 | 226 | first = values[0] |
|
227 | 227 | c = interactive(f, tup=tuple(values), lis=list(values)) |
|
228 | 228 | nt.assert_equal(len(c.children), 3) |
|
229 | 229 | d = dict( |
|
230 | 230 | cls=widgets.Dropdown, |
|
231 | 231 | value=first, |
|
232 |
|
|
|
232 | options=values | |
|
233 | 233 | ) |
|
234 | 234 | check_widgets(c, tup=d, lis=d) |
|
235 | 235 | |
|
236 | 236 | def test_list_tuple_invalid(): |
|
237 | 237 | for bad in [ |
|
238 | 238 | (), |
|
239 | 239 | (5, 'hi'), |
|
240 | 240 | ('hi', 5), |
|
241 | 241 | ({},), |
|
242 | 242 | (None,), |
|
243 | 243 | ]: |
|
244 | 244 | with nt.assert_raises(ValueError): |
|
245 | 245 | print(bad) # because there is no custom message in assert_raises |
|
246 | 246 | c = interactive(f, tup=bad) |
|
247 | 247 | |
|
248 | 248 | def test_defaults(): |
|
249 | 249 | @annotate(n=10) |
|
250 | 250 | def f(n, f=4.5, g=1): |
|
251 | 251 | pass |
|
252 | 252 | |
|
253 | 253 | c = interactive(f) |
|
254 | 254 | check_widgets(c, |
|
255 | 255 | n=dict( |
|
256 | 256 | cls=widgets.IntSlider, |
|
257 | 257 | value=10, |
|
258 | 258 | ), |
|
259 | 259 | f=dict( |
|
260 | 260 | cls=widgets.FloatSlider, |
|
261 | 261 | value=4.5, |
|
262 | 262 | ), |
|
263 | 263 | g=dict( |
|
264 | 264 | cls=widgets.IntSlider, |
|
265 | 265 | value=1, |
|
266 | 266 | ), |
|
267 | 267 | ) |
|
268 | 268 | |
|
269 | 269 | def test_default_values(): |
|
270 | 270 | @annotate(n=10, f=(0, 10.), g=5, h={'a': 1, 'b': 2}, j=['hi', 'there']) |
|
271 | 271 | def f(n, f=4.5, g=1, h=2, j='there'): |
|
272 | 272 | pass |
|
273 | 273 | |
|
274 | 274 | c = interactive(f) |
|
275 | 275 | check_widgets(c, |
|
276 | 276 | n=dict( |
|
277 | 277 | cls=widgets.IntSlider, |
|
278 | 278 | value=10, |
|
279 | 279 | ), |
|
280 | 280 | f=dict( |
|
281 | 281 | cls=widgets.FloatSlider, |
|
282 | 282 | value=4.5, |
|
283 | 283 | ), |
|
284 | 284 | g=dict( |
|
285 | 285 | cls=widgets.IntSlider, |
|
286 | 286 | value=5, |
|
287 | 287 | ), |
|
288 | 288 | h=dict( |
|
289 | 289 | cls=widgets.Dropdown, |
|
290 |
|
|
|
290 | options={'a': 1, 'b': 2}, | |
|
291 | 291 | value=2 |
|
292 | 292 | ), |
|
293 | 293 | j=dict( |
|
294 | 294 | cls=widgets.Dropdown, |
|
295 |
|
|
|
295 | options=['hi', 'there'], | |
|
296 | 296 | value='there' |
|
297 | 297 | ), |
|
298 | 298 | ) |
|
299 | 299 | |
|
300 | 300 | def test_default_out_of_bounds(): |
|
301 | 301 | @annotate(f=(0, 10.), h={'a': 1}, j=['hi', 'there']) |
|
302 | 302 | def f(f='hi', h=5, j='other'): |
|
303 | 303 | pass |
|
304 | 304 | |
|
305 | 305 | c = interactive(f) |
|
306 | 306 | check_widgets(c, |
|
307 | 307 | f=dict( |
|
308 | 308 | cls=widgets.FloatSlider, |
|
309 | 309 | value=5., |
|
310 | 310 | ), |
|
311 | 311 | h=dict( |
|
312 | 312 | cls=widgets.Dropdown, |
|
313 |
|
|
|
313 | options={'a': 1}, | |
|
314 | 314 | value=1, |
|
315 | 315 | ), |
|
316 | 316 | j=dict( |
|
317 | 317 | cls=widgets.Dropdown, |
|
318 |
|
|
|
318 | options=['hi', 'there'], | |
|
319 | 319 | value='hi', |
|
320 | 320 | ), |
|
321 | 321 | ) |
|
322 | 322 | |
|
323 | 323 | def test_annotations(): |
|
324 | 324 | @annotate(n=10, f=widgets.FloatText()) |
|
325 | 325 | def f(n, f): |
|
326 | 326 | pass |
|
327 | 327 | |
|
328 | 328 | c = interactive(f) |
|
329 | 329 | check_widgets(c, |
|
330 | 330 | n=dict( |
|
331 | 331 | cls=widgets.IntSlider, |
|
332 | 332 | value=10, |
|
333 | 333 | ), |
|
334 | 334 | f=dict( |
|
335 | 335 | cls=widgets.FloatText, |
|
336 | 336 | ), |
|
337 | 337 | ) |
|
338 | 338 | |
|
339 | 339 | def test_priority(): |
|
340 | 340 | @annotate(annotate='annotate', kwarg='annotate') |
|
341 | 341 | def f(kwarg='default', annotate='default', default='default'): |
|
342 | 342 | pass |
|
343 | 343 | |
|
344 | 344 | c = interactive(f, kwarg='kwarg') |
|
345 | 345 | check_widgets(c, |
|
346 | 346 | kwarg=dict( |
|
347 | 347 | cls=widgets.Text, |
|
348 | 348 | value='kwarg', |
|
349 | 349 | ), |
|
350 | 350 | annotate=dict( |
|
351 | 351 | cls=widgets.Text, |
|
352 | 352 | value='annotate', |
|
353 | 353 | ), |
|
354 | 354 | ) |
|
355 | 355 | |
|
356 | 356 | @nt.with_setup(clear_display) |
|
357 | 357 | def test_decorator_kwarg(): |
|
358 | 358 | with tt.monkeypatch(interaction, 'display', record_display): |
|
359 | 359 | @interact(a=5) |
|
360 | 360 | def foo(a): |
|
361 | 361 | pass |
|
362 | 362 | nt.assert_equal(len(displayed), 1) |
|
363 | 363 | w = displayed[0].children[0] |
|
364 | 364 | check_widget(w, |
|
365 | 365 | cls=widgets.IntSlider, |
|
366 | 366 | value=5, |
|
367 | 367 | ) |
|
368 | 368 | |
|
369 | 369 | @nt.with_setup(clear_display) |
|
370 | 370 | def test_interact_instancemethod(): |
|
371 | 371 | class Foo(object): |
|
372 | 372 | def show(self, x): |
|
373 | 373 | print(x) |
|
374 | 374 | |
|
375 | 375 | f = Foo() |
|
376 | 376 | |
|
377 | 377 | with tt.monkeypatch(interaction, 'display', record_display): |
|
378 | 378 | g = interact(f.show, x=(1,10)) |
|
379 | 379 | nt.assert_equal(len(displayed), 1) |
|
380 | 380 | w = displayed[0].children[0] |
|
381 | 381 | check_widget(w, |
|
382 | 382 | cls=widgets.IntSlider, |
|
383 | 383 | value=5, |
|
384 | 384 | ) |
|
385 | 385 | |
|
386 | 386 | @nt.with_setup(clear_display) |
|
387 | 387 | def test_decorator_no_call(): |
|
388 | 388 | with tt.monkeypatch(interaction, 'display', record_display): |
|
389 | 389 | @interact |
|
390 | 390 | def foo(a='default'): |
|
391 | 391 | pass |
|
392 | 392 | nt.assert_equal(len(displayed), 1) |
|
393 | 393 | w = displayed[0].children[0] |
|
394 | 394 | check_widget(w, |
|
395 | 395 | cls=widgets.Text, |
|
396 | 396 | value='default', |
|
397 | 397 | ) |
|
398 | 398 | |
|
399 | 399 | @nt.with_setup(clear_display) |
|
400 | 400 | def test_call_interact(): |
|
401 | 401 | def foo(a='default'): |
|
402 | 402 | pass |
|
403 | 403 | with tt.monkeypatch(interaction, 'display', record_display): |
|
404 | 404 | ifoo = interact(foo) |
|
405 | 405 | nt.assert_equal(len(displayed), 1) |
|
406 | 406 | w = displayed[0].children[0] |
|
407 | 407 | check_widget(w, |
|
408 | 408 | cls=widgets.Text, |
|
409 | 409 | value='default', |
|
410 | 410 | ) |
|
411 | 411 | |
|
412 | 412 | @nt.with_setup(clear_display) |
|
413 | 413 | def test_call_interact_kwargs(): |
|
414 | 414 | def foo(a='default'): |
|
415 | 415 | pass |
|
416 | 416 | with tt.monkeypatch(interaction, 'display', record_display): |
|
417 | 417 | ifoo = interact(foo, a=10) |
|
418 | 418 | nt.assert_equal(len(displayed), 1) |
|
419 | 419 | w = displayed[0].children[0] |
|
420 | 420 | check_widget(w, |
|
421 | 421 | cls=widgets.IntSlider, |
|
422 | 422 | value=10, |
|
423 | 423 | ) |
|
424 | 424 | |
|
425 | 425 | @nt.with_setup(clear_display) |
|
426 | 426 | def test_call_decorated_on_trait_change(): |
|
427 | 427 | """test calling @interact decorated functions""" |
|
428 | 428 | d = {} |
|
429 | 429 | with tt.monkeypatch(interaction, 'display', record_display): |
|
430 | 430 | @interact |
|
431 | 431 | def foo(a='default'): |
|
432 | 432 | d['a'] = a |
|
433 | 433 | return a |
|
434 | 434 | nt.assert_equal(len(displayed), 1) |
|
435 | 435 | w = displayed[0].children[0] |
|
436 | 436 | check_widget(w, |
|
437 | 437 | cls=widgets.Text, |
|
438 | 438 | value='default', |
|
439 | 439 | ) |
|
440 | 440 | # test calling the function directly |
|
441 | 441 | a = foo('hello') |
|
442 | 442 | nt.assert_equal(a, 'hello') |
|
443 | 443 | nt.assert_equal(d['a'], 'hello') |
|
444 | 444 | |
|
445 | 445 | # test that setting trait values calls the function |
|
446 | 446 | w.value = 'called' |
|
447 | 447 | nt.assert_equal(d['a'], 'called') |
|
448 | 448 | |
|
449 | 449 | @nt.with_setup(clear_display) |
|
450 | 450 | def test_call_decorated_kwargs_on_trait_change(): |
|
451 | 451 | """test calling @interact(foo=bar) decorated functions""" |
|
452 | 452 | d = {} |
|
453 | 453 | with tt.monkeypatch(interaction, 'display', record_display): |
|
454 | 454 | @interact(a='kwarg') |
|
455 | 455 | def foo(a='default'): |
|
456 | 456 | d['a'] = a |
|
457 | 457 | return a |
|
458 | 458 | nt.assert_equal(len(displayed), 1) |
|
459 | 459 | w = displayed[0].children[0] |
|
460 | 460 | check_widget(w, |
|
461 | 461 | cls=widgets.Text, |
|
462 | 462 | value='kwarg', |
|
463 | 463 | ) |
|
464 | 464 | # test calling the function directly |
|
465 | 465 | a = foo('hello') |
|
466 | 466 | nt.assert_equal(a, 'hello') |
|
467 | 467 | nt.assert_equal(d['a'], 'hello') |
|
468 | 468 | |
|
469 | 469 | # test that setting trait values calls the function |
|
470 | 470 | w.value = 'called' |
|
471 | 471 | nt.assert_equal(d['a'], 'called') |
|
472 | 472 | |
|
473 | 473 | def test_fixed(): |
|
474 | 474 | c = interactive(f, a=widgets.fixed(5), b='text') |
|
475 | 475 | nt.assert_equal(len(c.children), 2) |
|
476 | 476 | w = c.children[0] |
|
477 | 477 | check_widget(w, |
|
478 | 478 | cls=widgets.Text, |
|
479 | 479 | value='text', |
|
480 | 480 | description='b', |
|
481 | 481 | ) |
|
482 | 482 | |
|
483 | 483 | def test_default_description(): |
|
484 | 484 | c = interactive(f, b='text') |
|
485 | 485 | w = c.children[0] |
|
486 | 486 | check_widget(w, |
|
487 | 487 | cls=widgets.Text, |
|
488 | 488 | value='text', |
|
489 | 489 | description='b', |
|
490 | 490 | ) |
|
491 | 491 | |
|
492 | 492 | def test_custom_description(): |
|
493 | 493 | d = {} |
|
494 | 494 | def record_kwargs(**kwargs): |
|
495 | 495 | d.clear() |
|
496 | 496 | d.update(kwargs) |
|
497 | 497 | |
|
498 | 498 | c = interactive(record_kwargs, b=widgets.Text(value='text', description='foo')) |
|
499 | 499 | w = c.children[0] |
|
500 | 500 | check_widget(w, |
|
501 | 501 | cls=widgets.Text, |
|
502 | 502 | value='text', |
|
503 | 503 | description='foo', |
|
504 | 504 | ) |
|
505 | 505 | w.value = 'different text' |
|
506 | 506 | nt.assert_equal(d, {'b': 'different text'}) |
|
507 | 507 | |
|
508 | 508 | def test_interact_manual_button(): |
|
509 | 509 | c = interactive(f, __manual=True) |
|
510 | 510 | w = c.children[0] |
|
511 | 511 | check_widget(w, cls=widgets.Button) |
|
512 | 512 | |
|
513 | 513 | def test_interact_manual_nocall(): |
|
514 | 514 | callcount = 0 |
|
515 | 515 | def calltest(testarg): |
|
516 | 516 | callcount += 1 |
|
517 | 517 | c = interactive(calltest, testarg=5, __manual=True) |
|
518 | 518 | c.children[0].value = 10 |
|
519 | 519 | nt.assert_equal(callcount, 0) |
|
520 | 520 | |
|
521 | 521 | def test_int_range_logic(): |
|
522 | 522 | irsw = widgets.IntRangeSlider |
|
523 | 523 | w = irsw(value=(2, 4), min=0, max=6) |
|
524 | 524 | check_widget(w, cls=irsw, value=(2, 4), min=0, max=6) |
|
525 | 525 | w.value = (4, 2) |
|
526 | 526 | check_widget(w, cls=irsw, value=(2, 4), min=0, max=6) |
|
527 | 527 | w.value = (-1, 7) |
|
528 | 528 | check_widget(w, cls=irsw, value=(0, 6), min=0, max=6) |
|
529 | 529 | w.min = 3 |
|
530 | 530 | check_widget(w, cls=irsw, value=(3, 6), min=3, max=6) |
|
531 | 531 | w.max = 3 |
|
532 | 532 | check_widget(w, cls=irsw, value=(3, 3), min=3, max=3) |
|
533 | 533 | |
|
534 | 534 | w.min = 0 |
|
535 | 535 | w.max = 6 |
|
536 | 536 | w.lower = 2 |
|
537 | 537 | w.upper = 4 |
|
538 | 538 | check_widget(w, cls=irsw, value=(2, 4), min=0, max=6) |
|
539 | 539 | w.value = (0, 1) #lower non-overlapping range |
|
540 | 540 | check_widget(w, cls=irsw, value=(0, 1), min=0, max=6) |
|
541 | 541 | w.value = (5, 6) #upper non-overlapping range |
|
542 | 542 | check_widget(w, cls=irsw, value=(5, 6), min=0, max=6) |
|
543 | 543 | w.value = (-1, 4) #semi out-of-range |
|
544 | 544 | check_widget(w, cls=irsw, value=(0, 4), min=0, max=6) |
|
545 | 545 | w.lower = 2 |
|
546 | 546 | check_widget(w, cls=irsw, value=(2, 4), min=0, max=6) |
|
547 | 547 | w.value = (-2, -1) #wholly out of range |
|
548 | 548 | check_widget(w, cls=irsw, value=(0, 0), min=0, max=6) |
|
549 | 549 | w.value = (7, 8) |
|
550 | 550 | check_widget(w, cls=irsw, value=(6, 6), min=0, max=6) |
|
551 | 551 | |
|
552 | 552 | with nt.assert_raises(ValueError): |
|
553 | 553 | w.min = 7 |
|
554 | 554 | with nt.assert_raises(ValueError): |
|
555 | 555 | w.max = -1 |
|
556 | 556 | with nt.assert_raises(ValueError): |
|
557 | 557 | w.lower = 5 |
|
558 | 558 | with nt.assert_raises(ValueError): |
|
559 | 559 | w.upper = 1 |
|
560 | 560 | |
|
561 | 561 | w = irsw(min=2, max=3) |
|
562 | 562 | check_widget(w, min=2, max=3) |
|
563 | 563 | w = irsw(min=100, max=200) |
|
564 | 564 | check_widget(w, lower=125, upper=175, value=(125, 175)) |
|
565 | 565 | |
|
566 | 566 | with nt.assert_raises(ValueError): |
|
567 | 567 | irsw(value=(2, 4), lower=3) |
|
568 | 568 | with nt.assert_raises(ValueError): |
|
569 | 569 | irsw(value=(2, 4), upper=3) |
|
570 | 570 | with nt.assert_raises(ValueError): |
|
571 | 571 | irsw(value=(2, 4), lower=3, upper=3) |
|
572 | 572 | with nt.assert_raises(ValueError): |
|
573 | 573 | irsw(min=2, max=1) |
|
574 | 574 | with nt.assert_raises(ValueError): |
|
575 | 575 | irsw(lower=5) |
|
576 | 576 | with nt.assert_raises(ValueError): |
|
577 | 577 | irsw(upper=5) |
|
578 | 578 | |
|
579 | 579 | |
|
580 | 580 | def test_float_range_logic(): |
|
581 | 581 | frsw = widgets.FloatRangeSlider |
|
582 | 582 | w = frsw(value=(.2, .4), min=0., max=.6) |
|
583 | 583 | check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6) |
|
584 | 584 | w.value = (.4, .2) |
|
585 | 585 | check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6) |
|
586 | 586 | w.value = (-.1, .7) |
|
587 | 587 | check_widget(w, cls=frsw, value=(0., .6), min=0., max=.6) |
|
588 | 588 | w.min = .3 |
|
589 | 589 | check_widget(w, cls=frsw, value=(.3, .6), min=.3, max=.6) |
|
590 | 590 | w.max = .3 |
|
591 | 591 | check_widget(w, cls=frsw, value=(.3, .3), min=.3, max=.3) |
|
592 | 592 | |
|
593 | 593 | w.min = 0. |
|
594 | 594 | w.max = .6 |
|
595 | 595 | w.lower = .2 |
|
596 | 596 | w.upper = .4 |
|
597 | 597 | check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6) |
|
598 | 598 | w.value = (0., .1) #lower non-overlapping range |
|
599 | 599 | check_widget(w, cls=frsw, value=(0., .1), min=0., max=.6) |
|
600 | 600 | w.value = (.5, .6) #upper non-overlapping range |
|
601 | 601 | check_widget(w, cls=frsw, value=(.5, .6), min=0., max=.6) |
|
602 | 602 | w.value = (-.1, .4) #semi out-of-range |
|
603 | 603 | check_widget(w, cls=frsw, value=(0., .4), min=0., max=.6) |
|
604 | 604 | w.lower = .2 |
|
605 | 605 | check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6) |
|
606 | 606 | w.value = (-.2, -.1) #wholly out of range |
|
607 | 607 | check_widget(w, cls=frsw, value=(0., 0.), min=0., max=.6) |
|
608 | 608 | w.value = (.7, .8) |
|
609 | 609 | check_widget(w, cls=frsw, value=(.6, .6), min=.0, max=.6) |
|
610 | 610 | |
|
611 | 611 | with nt.assert_raises(ValueError): |
|
612 | 612 | w.min = .7 |
|
613 | 613 | with nt.assert_raises(ValueError): |
|
614 | 614 | w.max = -.1 |
|
615 | 615 | with nt.assert_raises(ValueError): |
|
616 | 616 | w.lower = .5 |
|
617 | 617 | with nt.assert_raises(ValueError): |
|
618 | 618 | w.upper = .1 |
|
619 | 619 | |
|
620 | 620 | w = frsw(min=2, max=3) |
|
621 | 621 | check_widget(w, min=2, max=3) |
|
622 | 622 | w = frsw(min=1., max=2.) |
|
623 | 623 | check_widget(w, lower=1.25, upper=1.75, value=(1.25, 1.75)) |
|
624 | 624 | |
|
625 | 625 | with nt.assert_raises(ValueError): |
|
626 | 626 | frsw(value=(2, 4), lower=3) |
|
627 | 627 | with nt.assert_raises(ValueError): |
|
628 | 628 | frsw(value=(2, 4), upper=3) |
|
629 | 629 | with nt.assert_raises(ValueError): |
|
630 | 630 | frsw(value=(2, 4), lower=3, upper=3) |
|
631 | 631 | with nt.assert_raises(ValueError): |
|
632 | 632 | frsw(min=.2, max=.1) |
|
633 | 633 | with nt.assert_raises(ValueError): |
|
634 | 634 | frsw(lower=5) |
|
635 | 635 | with nt.assert_raises(ValueError): |
|
636 | 636 | frsw(upper=5) |
|
637 | ||
|
638 | ||
|
639 | def test_multiple_selection(): | |
|
640 | smw = widgets.SelectMultiple | |
|
641 | ||
|
642 | # degenerate multiple select | |
|
643 | w = smw() | |
|
644 | check_widget(w, value=tuple(), options=None, selected_labels=tuple()) | |
|
645 | ||
|
646 | # don't accept random other value when no options | |
|
647 | with nt.assert_raises(KeyError): | |
|
648 | w.value = (2,) | |
|
649 | check_widget(w, value=tuple(), selected_labels=tuple()) | |
|
650 | ||
|
651 | # basic multiple select | |
|
652 | w = smw(options=[(1, 1)], value=[1]) | |
|
653 | check_widget(w, cls=smw, value=(1,), options=[(1, 1)]) | |
|
654 | ||
|
655 | # don't accept random other value | |
|
656 | with nt.assert_raises(KeyError): | |
|
657 | w.value = w.value + (2,) | |
|
658 | check_widget(w, value=(1,), selected_labels=(1,)) | |
|
659 | ||
|
660 | # change options | |
|
661 | w.options = w.options + [(2, 2)] | |
|
662 | check_widget(w, options=[(1, 1), (2,2)]) | |
|
663 | ||
|
664 | # change value | |
|
665 | w.value = w.value + (2,) | |
|
666 | check_widget(w, value=(1, 2), selected_labels=(1, 2)) | |
|
667 | ||
|
668 | # change value name | |
|
669 | w.selected_labels = (1,) | |
|
670 | check_widget(w, value=(1,)) | |
|
671 | ||
|
672 | # don't accept random other names when no options | |
|
673 | with nt.assert_raises(KeyError): | |
|
674 | w.selected_labels = (3,) | |
|
675 | check_widget(w, value=(1,)) | |
|
676 | ||
|
677 | # don't accept selected_label (from superclass) | |
|
678 | with nt.assert_raises(AttributeError): | |
|
679 | w.selected_label = 3 | |
|
680 | ||
|
681 | # don't return selected_label (from superclass) | |
|
682 | with nt.assert_raises(AttributeError): | |
|
683 | print(w.selected_label) | |
|
684 | ||
|
685 | # dict style | |
|
686 | w.options = {1: 1} | |
|
687 | check_widget(w, options={1: 1}) | |
|
688 | ||
|
689 | # updating | |
|
690 | with nt.assert_raises(KeyError): | |
|
691 | w.value = (2,) | |
|
692 | check_widget(w, options={1: 1}) |
@@ -1,172 +1,238 b'' | |||
|
1 | 1 | """Selection classes. |
|
2 | 2 | |
|
3 | 3 | Represents an enumeration 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 | |
|
17 | 17 | from collections import OrderedDict |
|
18 | 18 | from threading import Lock |
|
19 | 19 | |
|
20 | 20 | from .widget import DOMWidget, register |
|
21 | 21 | from IPython.utils.traitlets import ( |
|
22 | 22 | Unicode, Bool, Any, Dict, TraitError, CaselessStrEnum, Tuple |
|
23 | 23 | ) |
|
24 | 24 | from IPython.utils.py3compat import unicode_type |
|
25 | 25 | from IPython.utils.warn import DeprecatedClass |
|
26 | 26 | |
|
27 | 27 | #----------------------------------------------------------------------------- |
|
28 | 28 | # SelectionWidget |
|
29 | 29 | #----------------------------------------------------------------------------- |
|
30 | 30 | class _Selection(DOMWidget): |
|
31 | 31 | """Base class for Selection widgets |
|
32 | 32 | |
|
33 |
`` |
|
|
33 | ``options`` can be specified as a list or dict. If given as a list, | |
|
34 | 34 | it will be transformed to a dict of the form ``{str(value):value}``. |
|
35 | 35 | """ |
|
36 | 36 | |
|
37 | 37 | value = Any(help="Selected value") |
|
38 |
|
|
|
39 |
|
|
|
38 | selected_label = Unicode(help="The label of the selected value", sync=True) | |
|
39 | options = Any(help="""List of (key, value) tuples or dict of values that the | |
|
40 | 40 | user can select. |
|
41 | 41 | |
|
42 | 42 | The keys of this list are the strings that will be displayed in the UI, |
|
43 | 43 | representing the actual Python choices. |
|
44 | 44 | |
|
45 |
The keys of this list are also available as _ |
|
|
45 | The keys of this list are also available as _options_labels. | |
|
46 | 46 | """) |
|
47 | 47 | |
|
48 |
_ |
|
|
49 |
_ |
|
|
50 |
_ |
|
|
48 | _options_dict = Dict() | |
|
49 | _options_labels = Tuple(sync=True) | |
|
50 | _options_values = Tuple() | |
|
51 | 51 | |
|
52 | 52 | disabled = Bool(False, help="Enable or disable user changes", sync=True) |
|
53 | 53 | description = Unicode(help="Description of the value this widget represents", sync=True) |
|
54 | 54 | |
|
55 | 55 | def __init__(self, *args, **kwargs): |
|
56 | 56 | self.value_lock = Lock() |
|
57 |
self. |
|
|
58 |
self.on_trait_change(self._ |
|
|
59 |
if ' |
|
|
60 |
self. |
|
|
57 | self.options_lock = Lock() | |
|
58 | self.on_trait_change(self._options_readonly_changed, ['_options_dict', '_options_labels', '_options_values', '_options']) | |
|
59 | if 'options' in kwargs: | |
|
60 | self.options = kwargs.pop('options') | |
|
61 | 61 | DOMWidget.__init__(self, *args, **kwargs) |
|
62 |
self._value_in_ |
|
|
62 | self._value_in_options() | |
|
63 | 63 | |
|
64 |
def _make_ |
|
|
64 | def _make_options(self, x): | |
|
65 | 65 | # If x is a dict, convert it to list format. |
|
66 | 66 | if isinstance(x, (OrderedDict, dict)): |
|
67 | 67 | return [(k, v) for k, v in x.items()] |
|
68 | 68 | |
|
69 | 69 | # Make sure x is a list or tuple. |
|
70 | 70 | if not isinstance(x, (list, tuple)): |
|
71 | 71 | raise ValueError('x') |
|
72 | 72 | |
|
73 | # If x is an ordinary list, use the values as names. | |
|
73 | # If x is an ordinary list, use the option values as names. | |
|
74 | 74 | for y in x: |
|
75 | 75 | if not isinstance(y, (list, tuple)) or len(y) < 2: |
|
76 | 76 | return [(i, i) for i in x] |
|
77 | 77 | |
|
78 | 78 | # Value is already in the correct format. |
|
79 | 79 | return x |
|
80 | 80 | |
|
81 |
def _ |
|
|
82 |
"""Handles when the |
|
|
81 | def _options_changed(self, name, old, new): | |
|
82 | """Handles when the options tuple has been changed. | |
|
83 | 83 | |
|
84 |
Setting |
|
|
85 |
""" |
|
|
86 |
if self. |
|
|
84 | Setting options implies setting option labels from the keys of the dict. | |
|
85 | """ | |
|
86 | if self.options_lock.acquire(False): | |
|
87 | 87 | try: |
|
88 |
self. |
|
|
88 | self.options = new | |
|
89 | 89 | |
|
90 |
|
|
|
91 |
self._ |
|
|
92 |
self._ |
|
|
93 |
self._ |
|
|
94 |
self._value_in_ |
|
|
90 | options = self._make_options(new) | |
|
91 | self._options_dict = {i[0]: i[1] for i in options} | |
|
92 | self._options_labels = [i[0] for i in options] | |
|
93 | self._options_values = [i[1] for i in options] | |
|
94 | self._value_in_options() | |
|
95 | 95 | finally: |
|
96 |
self. |
|
|
96 | self.options_lock.release() | |
|
97 | 97 | |
|
98 |
def _value_in_ |
|
|
98 | def _value_in_options(self): | |
|
99 | 99 | # ensure that the chosen value is one of the choices |
|
100 | if self._value_values: | |
|
101 | if self.value not in self._value_values: | |
|
102 | self.value = next(iter(self._value_values)) | |
|
103 | 100 | |
|
104 | def _values_readonly_changed(self, name, old, new): | |
|
105 | if not self.values_lock.locked(): | |
|
106 | raise TraitError("`.%s` is a read-only trait. Use the `.values` tuple instead." % name) | |
|
101 | if self._options_values: | |
|
102 | if self.value not in self._options_values: | |
|
103 | self.value = next(iter(self._options_values)) | |
|
107 | 104 | |
|
105 | def _options_readonly_changed(self, name, old, new): | |
|
106 | if not self.options_lock.locked(): | |
|
107 | raise TraitError("`.%s` is a read-only trait. Use the `.options` tuple instead." % name) | |
|
108 | 108 | def _value_changed(self, name, old, new): |
|
109 | 109 | """Called when value has been changed""" |
|
110 | 110 | if self.value_lock.acquire(False): |
|
111 | 111 | try: |
|
112 | 112 | # Reverse dictionary lookup for the value name |
|
113 |
for k,v in self._ |
|
|
113 | for k, v in self._options_dict.items(): | |
|
114 | 114 | if new == v: |
|
115 | 115 | # set the selected value name |
|
116 |
self. |
|
|
116 | self.selected_label = k | |
|
117 | 117 | return |
|
118 | 118 | # undo the change, and raise KeyError |
|
119 | 119 | self.value = old |
|
120 | 120 | raise KeyError(new) |
|
121 | 121 | finally: |
|
122 | 122 | self.value_lock.release() |
|
123 | 123 | |
|
124 |
def _ |
|
|
124 | def _selected_label_changed(self, name, old, new): | |
|
125 | 125 | """Called when the value name has been changed (typically by the frontend).""" |
|
126 | 126 | if self.value_lock.acquire(False): |
|
127 | 127 | try: |
|
128 |
self.value = self._ |
|
|
128 | self.value = self._options_dict[new] | |
|
129 | finally: | |
|
130 | self.value_lock.release() | |
|
131 | ||
|
132 | ||
|
133 | class _MultipleSelection(_Selection): | |
|
134 | """Base class for MultipleSelection widgets. | |
|
135 | ||
|
136 | As with ``_Selection``, ``options`` can be specified as a list or dict. If | |
|
137 | given as a list, it will be transformed to a dict of the form | |
|
138 | ``{str(value): value}``. | |
|
139 | ||
|
140 | Despite their names, ``value`` (and ``selected_label``) will be tuples, even | |
|
141 | if only a single option is selected. | |
|
142 | """ | |
|
143 | ||
|
144 | value = Tuple(help="Selected values") | |
|
145 | selected_labels = Tuple(help="The labels of the selected options", | |
|
146 | sync=True) | |
|
147 | ||
|
148 | @property | |
|
149 | def selected_label(self): | |
|
150 | raise AttributeError( | |
|
151 | "Does not support selected_label, use selected_labels") | |
|
152 | ||
|
153 | def _value_in_options(self): | |
|
154 | # ensure that the chosen value is one of the choices | |
|
155 | if self.options: | |
|
156 | old_value = self.value or [] | |
|
157 | new_value = [] | |
|
158 | for value in old_value: | |
|
159 | if value in self._options_dict.values(): | |
|
160 | new_value.append(value) | |
|
161 | if new_value: | |
|
162 | self.value = new_value | |
|
163 | else: | |
|
164 | self.value = [next(iter(self._options_dict.values()))] | |
|
165 | ||
|
166 | def _value_changed(self, name, old, new): | |
|
167 | """Called when value has been changed""" | |
|
168 | if self.value_lock.acquire(False): | |
|
169 | try: | |
|
170 | self.selected_labels = [ | |
|
171 | self._options_labels[self._options_values.index(v)] | |
|
172 | for v in new | |
|
173 | ] | |
|
174 | except: | |
|
175 | self.value = old | |
|
176 | raise KeyError(new) | |
|
177 | finally: | |
|
178 | self.value_lock.release() | |
|
179 | ||
|
180 | def _selected_labels_changed(self, name, old, new): | |
|
181 | """Called when the selected label has been changed (typically by the | |
|
182 | frontend).""" | |
|
183 | if self.value_lock.acquire(False): | |
|
184 | try: | |
|
185 | self.value = [self._options_dict[name] for name in new] | |
|
129 | 186 | finally: |
|
130 | 187 | self.value_lock.release() |
|
131 | 188 | |
|
132 | 189 | |
|
133 | 190 | @register('IPython.ToggleButtons') |
|
134 | 191 | class ToggleButtons(_Selection): |
|
135 | 192 | """Group of toggle buttons that represent an enumeration. Only one toggle |
|
136 | 193 | button can be toggled at any point in time.""" |
|
137 | 194 | _view_name = Unicode('ToggleButtonsView', sync=True) |
|
138 | 195 | |
|
139 | 196 | button_style = CaselessStrEnum( |
|
140 | 197 | values=['primary', 'success', 'info', 'warning', 'danger', ''], |
|
141 | 198 | default_value='', allow_none=True, sync=True, help="""Use a |
|
142 | 199 | predefined styling for the buttons.""") |
|
143 | 200 | |
|
144 | 201 | @register('IPython.Dropdown') |
|
145 | 202 | class Dropdown(_Selection): |
|
146 | 203 | """Allows you to select a single item from a dropdown.""" |
|
147 | 204 | _view_name = Unicode('DropdownView', sync=True) |
|
148 | 205 | |
|
149 | 206 | button_style = CaselessStrEnum( |
|
150 | 207 | values=['primary', 'success', 'info', 'warning', 'danger', ''], |
|
151 | 208 | default_value='', allow_none=True, sync=True, help="""Use a |
|
152 | 209 | predefined styling for the buttons.""") |
|
153 | 210 | |
|
154 | 211 | @register('IPython.RadioButtons') |
|
155 | 212 | class RadioButtons(_Selection): |
|
156 | 213 | """Group of radio buttons that represent an enumeration. Only one radio |
|
157 | 214 | button can be toggled at any point in time.""" |
|
158 | 215 | _view_name = Unicode('RadioButtonsView', sync=True) |
|
159 | 216 | |
|
160 | 217 | |
|
161 | 218 | |
|
162 | 219 | @register('IPython.Select') |
|
163 | 220 | class Select(_Selection): |
|
164 | 221 | """Listbox that only allows one item to be selected at any given time.""" |
|
165 | 222 | _view_name = Unicode('SelectView', sync=True) |
|
166 | 223 | |
|
167 | 224 | |
|
225 | @register('IPython.SelectMultiple') | |
|
226 | class SelectMultiple(_MultipleSelection): | |
|
227 | """Listbox that allows many items to be selected at any given time. | |
|
228 | Despite their names, inherited from ``_Selection``, the currently chosen | |
|
229 | option values, ``value``, or their labels, ``selected_labels`` must both be | |
|
230 | updated with a list-like object.""" | |
|
231 | _view_name = Unicode('SelectMultipleView', sync=True) | |
|
232 | ||
|
233 | ||
|
168 | 234 | # Remove in IPython 4.0 |
|
169 | 235 | ToggleButtonsWidget = DeprecatedClass(ToggleButtons, 'ToggleButtonsWidget') |
|
170 | 236 | DropdownWidget = DeprecatedClass(Dropdown, 'DropdownWidget') |
|
171 | 237 | RadioButtonsWidget = DeprecatedClass(RadioButtons, 'RadioButtonsWidget') |
|
172 | 238 | SelectWidget = DeprecatedClass(Select, 'SelectWidget') |
@@ -1,176 +1,176 b'' | |||
|
1 | 1 | { |
|
2 | 2 | "cells": [ |
|
3 | 3 | { |
|
4 | 4 | "cell_type": "code", |
|
5 | 5 | "execution_count": null, |
|
6 | 6 | "metadata": { |
|
7 | 7 | "collapsed": false |
|
8 | 8 | }, |
|
9 | 9 | "outputs": [], |
|
10 | 10 | "source": [ |
|
11 | 11 | "# Widget related imports\n", |
|
12 | 12 | "from IPython.html import widgets\n", |
|
13 | 13 | "from IPython.display import display, clear_output, Javascript\n", |
|
14 | 14 | "from IPython.utils.traitlets import Unicode\n", |
|
15 | 15 | "\n", |
|
16 | 16 | "# nbconvert related imports\n", |
|
17 | 17 | "from IPython.nbconvert import get_export_names, export_by_name\n", |
|
18 | 18 | "from IPython.nbconvert.writers import FilesWriter\n", |
|
19 | 19 | "from IPython.nbformat import current\n", |
|
20 | 20 | "from IPython.nbconvert.utils.exceptions import ConversionException" |
|
21 | 21 | ] |
|
22 | 22 | }, |
|
23 | 23 | { |
|
24 | 24 | "cell_type": "markdown", |
|
25 | 25 | "metadata": {}, |
|
26 | 26 | "source": [ |
|
27 | 27 | "Create a text Widget without displaying it. The widget will be used to store the notebook's name which is otherwise only available in the front-end." |
|
28 | 28 | ] |
|
29 | 29 | }, |
|
30 | 30 | { |
|
31 | 31 | "cell_type": "code", |
|
32 | 32 | "execution_count": null, |
|
33 | 33 | "metadata": { |
|
34 | 34 | "collapsed": false |
|
35 | 35 | }, |
|
36 | 36 | "outputs": [], |
|
37 | 37 | "source": [ |
|
38 | 38 | "notebook_name = widgets.Text()" |
|
39 | 39 | ] |
|
40 | 40 | }, |
|
41 | 41 | { |
|
42 | 42 | "cell_type": "markdown", |
|
43 | 43 | "metadata": {}, |
|
44 | 44 | "source": [ |
|
45 | 45 | "Get the current notebook's name by pushing JavaScript to the browser that sets the notebook name in a string widget." |
|
46 | 46 | ] |
|
47 | 47 | }, |
|
48 | 48 | { |
|
49 | 49 | "cell_type": "code", |
|
50 | 50 | "execution_count": null, |
|
51 | 51 | "metadata": { |
|
52 | 52 | "collapsed": false |
|
53 | 53 | }, |
|
54 | 54 | "outputs": [], |
|
55 | 55 | "source": [ |
|
56 | 56 | "js = \"\"\"var model = IPython.notebook.kernel.widget_manager.get_model('{model_id}');\n", |
|
57 | 57 | "model.set('value', IPython.notebook.notebook_name);\n", |
|
58 | 58 | "model.save();\"\"\".format(model_id=notebook_name.model_id)\n", |
|
59 | 59 | "display(Javascript(data=js))" |
|
60 | 60 | ] |
|
61 | 61 | }, |
|
62 | 62 | { |
|
63 | 63 | "cell_type": "code", |
|
64 | 64 | "execution_count": null, |
|
65 | 65 | "metadata": { |
|
66 | 66 | "collapsed": false |
|
67 | 67 | }, |
|
68 | 68 | "outputs": [], |
|
69 | 69 | "source": [ |
|
70 | 70 | "filename = notebook_name.value\n", |
|
71 | 71 | "filename" |
|
72 | 72 | ] |
|
73 | 73 | }, |
|
74 | 74 | { |
|
75 | 75 | "cell_type": "markdown", |
|
76 | 76 | "metadata": {}, |
|
77 | 77 | "source": [ |
|
78 | 78 | "Create the widget that will allow the user to Export the current notebook." |
|
79 | 79 | ] |
|
80 | 80 | }, |
|
81 | 81 | { |
|
82 | 82 | "cell_type": "code", |
|
83 | 83 | "execution_count": null, |
|
84 | 84 | "metadata": { |
|
85 | 85 | "collapsed": false |
|
86 | 86 | }, |
|
87 | 87 | "outputs": [], |
|
88 | 88 | "source": [ |
|
89 |
"exporter_names = widgets.Dropdown( |
|
|
89 | "exporter_names = widgets.Dropdown(options=get_export_names(), value='html')\n", | |
|
90 | 90 | "export_button = widgets.Button(description=\"Export\")\n", |
|
91 | 91 | "download_link = widgets.HTML(visible=False)" |
|
92 | 92 | ] |
|
93 | 93 | }, |
|
94 | 94 | { |
|
95 | 95 | "cell_type": "markdown", |
|
96 | 96 | "metadata": {}, |
|
97 | 97 | "source": [ |
|
98 | 98 | "Export the notebook when the export button is clicked." |
|
99 | 99 | ] |
|
100 | 100 | }, |
|
101 | 101 | { |
|
102 | 102 | "cell_type": "code", |
|
103 | 103 | "execution_count": null, |
|
104 | 104 | "metadata": { |
|
105 | 105 | "collapsed": false |
|
106 | 106 | }, |
|
107 | 107 | "outputs": [], |
|
108 | 108 | "source": [ |
|
109 | 109 | "file_writer = FilesWriter()\n", |
|
110 | 110 | "\n", |
|
111 | 111 | "def export(name, nb):\n", |
|
112 | 112 | " \n", |
|
113 | 113 | " # Get a unique key for the notebook and set it in the resources object.\n", |
|
114 | 114 | " notebook_name = name[:name.rfind('.')]\n", |
|
115 | 115 | " resources = {}\n", |
|
116 | 116 | " resources['unique_key'] = notebook_name\n", |
|
117 | 117 | " resources['output_files_dir'] = '%s_files' % notebook_name\n", |
|
118 | 118 | "\n", |
|
119 | 119 | " # Try to export\n", |
|
120 | 120 | " try:\n", |
|
121 | 121 | " output, resources = export_by_name(exporter_names.value, nb)\n", |
|
122 | 122 | " except ConversionException as e:\n", |
|
123 | 123 | " download_link.value = \"<br>Could not export notebook!\"\n", |
|
124 | 124 | " else:\n", |
|
125 | 125 | " write_results = file_writer.write(output, resources, notebook_name=notebook_name)\n", |
|
126 | 126 | " \n", |
|
127 | 127 | " download_link.value = \"<br>Results: <a href='files/{filename}'><i>\\\"{filename}\\\"</i></a>\".format(filename=write_results)\n", |
|
128 | 128 | " download_link.visible = True\n", |
|
129 | 129 | " \n", |
|
130 | 130 | "def handle_export(widget):\n", |
|
131 | 131 | " with open(filename, 'r') as f:\n", |
|
132 | 132 | " export(filename, current.read(f, 'json'))\n", |
|
133 | 133 | "export_button.on_click(handle_export)" |
|
134 | 134 | ] |
|
135 | 135 | }, |
|
136 | 136 | { |
|
137 | 137 | "cell_type": "markdown", |
|
138 | 138 | "metadata": {}, |
|
139 | 139 | "source": [ |
|
140 | 140 | "Display the controls." |
|
141 | 141 | ] |
|
142 | 142 | }, |
|
143 | 143 | { |
|
144 | 144 | "cell_type": "code", |
|
145 | 145 | "execution_count": null, |
|
146 | 146 | "metadata": { |
|
147 | 147 | "collapsed": false |
|
148 | 148 | }, |
|
149 | 149 | "outputs": [], |
|
150 | 150 | "source": [ |
|
151 | 151 | "display(exporter_names, export_button, download_link)" |
|
152 | 152 | ] |
|
153 | 153 | } |
|
154 | 154 | ], |
|
155 | 155 | "metadata": { |
|
156 | 156 | "kernelspec": { |
|
157 | 157 | "display_name": "Python 3", |
|
158 | 158 | "language": "python", |
|
159 | 159 | "name": "python3" |
|
160 | 160 | }, |
|
161 | 161 | "language_info": { |
|
162 | 162 | "codemirror_mode": { |
|
163 | 163 | "name": "ipython", |
|
164 | 164 | "version": 3 |
|
165 | 165 | }, |
|
166 | 166 | "file_extension": ".py", |
|
167 | 167 | "mimetype": "text/x-python", |
|
168 | 168 | "name": "python", |
|
169 | 169 | "nbconvert_exporter": "python", |
|
170 | 170 | "pygments_lexer": "ipython3", |
|
171 | 171 | "version": "3.4.2" |
|
172 | 172 | } |
|
173 | 173 | }, |
|
174 | 174 | "nbformat": 4, |
|
175 | 175 | "nbformat_minor": 0 |
|
176 | 176 | } |
@@ -1,587 +1,621 b'' | |||
|
1 | 1 | { |
|
2 | 2 | "cells": [ |
|
3 | 3 | { |
|
4 | 4 | "cell_type": "markdown", |
|
5 | 5 | "metadata": {}, |
|
6 | 6 | "source": [ |
|
7 | 7 | "[Index](Index.ipynb) - [Back](Widget Basics.ipynb) - [Next](Widget Events.ipynb)" |
|
8 | 8 | ] |
|
9 | 9 | }, |
|
10 | 10 | { |
|
11 | 11 | "cell_type": "markdown", |
|
12 | 12 | "metadata": {}, |
|
13 | 13 | "source": [ |
|
14 | 14 | "# Widget List" |
|
15 | 15 | ] |
|
16 | 16 | }, |
|
17 | 17 | { |
|
18 | 18 | "cell_type": "markdown", |
|
19 | 19 | "metadata": {}, |
|
20 | 20 | "source": [ |
|
21 | 21 | "## Complete list" |
|
22 | 22 | ] |
|
23 | 23 | }, |
|
24 | 24 | { |
|
25 | 25 | "cell_type": "markdown", |
|
26 | 26 | "metadata": { |
|
27 | 27 | "slideshow": { |
|
28 | 28 | "slide_type": "slide" |
|
29 | 29 | } |
|
30 | 30 | }, |
|
31 | 31 | "source": [ |
|
32 | 32 | "For a complete list of the widgets available to you, you can list the classes in the widget namespace (as seen below). `Widget` and `DOMWidget`, not listed below, are base classes." |
|
33 | 33 | ] |
|
34 | 34 | }, |
|
35 | 35 | { |
|
36 | 36 | "cell_type": "code", |
|
37 | 37 | "execution_count": null, |
|
38 | 38 | "metadata": { |
|
39 | 39 | "collapsed": false |
|
40 | 40 | }, |
|
41 | 41 | "outputs": [], |
|
42 | 42 | "source": [ |
|
43 | 43 | "from IPython.html import widgets\n", |
|
44 | 44 | "[n for n in dir(widgets) if not n.endswith('Widget') and n[0] == n[0].upper() and not n[0] == '_']" |
|
45 | 45 | ] |
|
46 | 46 | }, |
|
47 | 47 | { |
|
48 | 48 | "cell_type": "markdown", |
|
49 | 49 | "metadata": { |
|
50 | 50 | "slideshow": { |
|
51 | 51 | "slide_type": "slide" |
|
52 | 52 | } |
|
53 | 53 | }, |
|
54 | 54 | "source": [ |
|
55 | 55 | "## Numeric widgets" |
|
56 | 56 | ] |
|
57 | 57 | }, |
|
58 | 58 | { |
|
59 | 59 | "cell_type": "markdown", |
|
60 | 60 | "metadata": {}, |
|
61 | 61 | "source": [ |
|
62 | 62 | "There are 8 widgets distributed with IPython that are designed to display numeric values. Widgets exist for displaying integers and floats, both bounded and unbounded. The integer widgets share a similar naming scheme to their floating point counterparts. By replacing `Float` with `Int` in the widget name, you can find the Integer equivalent." |
|
63 | 63 | ] |
|
64 | 64 | }, |
|
65 | 65 | { |
|
66 | 66 | "cell_type": "markdown", |
|
67 | 67 | "metadata": { |
|
68 | 68 | "slideshow": { |
|
69 | 69 | "slide_type": "slide" |
|
70 | 70 | } |
|
71 | 71 | }, |
|
72 | 72 | "source": [ |
|
73 | 73 | "### FloatSlider" |
|
74 | 74 | ] |
|
75 | 75 | }, |
|
76 | 76 | { |
|
77 | 77 | "cell_type": "code", |
|
78 | 78 | "execution_count": null, |
|
79 | 79 | "metadata": { |
|
80 | 80 | "collapsed": false |
|
81 | 81 | }, |
|
82 | 82 | "outputs": [], |
|
83 | 83 | "source": [ |
|
84 | 84 | "widgets.FloatSlider(\n", |
|
85 | 85 | " value=7.5,\n", |
|
86 | 86 | " min=5.0,\n", |
|
87 | 87 | " max=10.0,\n", |
|
88 | 88 | " step=0.1,\n", |
|
89 | 89 | " description='Test:',\n", |
|
90 | 90 | ")" |
|
91 | 91 | ] |
|
92 | 92 | }, |
|
93 | 93 | { |
|
94 | 94 | "cell_type": "markdown", |
|
95 | 95 | "metadata": {}, |
|
96 | 96 | "source": [ |
|
97 | 97 | "Sliders can also be **displayed vertically**." |
|
98 | 98 | ] |
|
99 | 99 | }, |
|
100 | 100 | { |
|
101 | 101 | "cell_type": "code", |
|
102 | 102 | "execution_count": null, |
|
103 | 103 | "metadata": { |
|
104 | 104 | "collapsed": false |
|
105 | 105 | }, |
|
106 | 106 | "outputs": [], |
|
107 | 107 | "source": [ |
|
108 | 108 | "widgets.FloatSlider(\n", |
|
109 | 109 | " value=7.5,\n", |
|
110 | 110 | " min=5.0,\n", |
|
111 | 111 | " max=10.0,\n", |
|
112 | 112 | " step=0.1,\n", |
|
113 | 113 | " description='Test',\n", |
|
114 | 114 | " orientation='vertical',\n", |
|
115 | 115 | ")" |
|
116 | 116 | ] |
|
117 | 117 | }, |
|
118 | 118 | { |
|
119 | 119 | "cell_type": "markdown", |
|
120 | 120 | "metadata": { |
|
121 | 121 | "slideshow": { |
|
122 | 122 | "slide_type": "slide" |
|
123 | 123 | } |
|
124 | 124 | }, |
|
125 | 125 | "source": [ |
|
126 | 126 | "### FloatProgress" |
|
127 | 127 | ] |
|
128 | 128 | }, |
|
129 | 129 | { |
|
130 | 130 | "cell_type": "code", |
|
131 | 131 | "execution_count": null, |
|
132 | 132 | "metadata": { |
|
133 | 133 | "collapsed": false |
|
134 | 134 | }, |
|
135 | 135 | "outputs": [], |
|
136 | 136 | "source": [ |
|
137 | 137 | "widgets.FloatProgress(\n", |
|
138 | 138 | " value=7.5,\n", |
|
139 | 139 | " min=5.0,\n", |
|
140 | 140 | " max=10.0,\n", |
|
141 | 141 | " step=0.1,\n", |
|
142 | 142 | " description='Loading:',\n", |
|
143 | 143 | ")" |
|
144 | 144 | ] |
|
145 | 145 | }, |
|
146 | 146 | { |
|
147 | 147 | "cell_type": "markdown", |
|
148 | 148 | "metadata": { |
|
149 | 149 | "slideshow": { |
|
150 | 150 | "slide_type": "slide" |
|
151 | 151 | } |
|
152 | 152 | }, |
|
153 | 153 | "source": [ |
|
154 | 154 | "### BoundedFloatText" |
|
155 | 155 | ] |
|
156 | 156 | }, |
|
157 | 157 | { |
|
158 | 158 | "cell_type": "code", |
|
159 | 159 | "execution_count": null, |
|
160 | 160 | "metadata": { |
|
161 | 161 | "collapsed": false |
|
162 | 162 | }, |
|
163 | 163 | "outputs": [], |
|
164 | 164 | "source": [ |
|
165 | 165 | "widgets.BoundedFloatText(\n", |
|
166 | 166 | " value=7.5,\n", |
|
167 | 167 | " min=5.0,\n", |
|
168 | 168 | " max=10.0,\n", |
|
169 | 169 | " description='Text:',\n", |
|
170 | 170 | ")" |
|
171 | 171 | ] |
|
172 | 172 | }, |
|
173 | 173 | { |
|
174 | 174 | "cell_type": "markdown", |
|
175 | 175 | "metadata": { |
|
176 | 176 | "slideshow": { |
|
177 | 177 | "slide_type": "slide" |
|
178 | 178 | } |
|
179 | 179 | }, |
|
180 | 180 | "source": [ |
|
181 | 181 | "### FloatText" |
|
182 | 182 | ] |
|
183 | 183 | }, |
|
184 | 184 | { |
|
185 | 185 | "cell_type": "code", |
|
186 | 186 | "execution_count": null, |
|
187 | 187 | "metadata": { |
|
188 | 188 | "collapsed": false |
|
189 | 189 | }, |
|
190 | 190 | "outputs": [], |
|
191 | 191 | "source": [ |
|
192 | 192 | "widgets.FloatText(\n", |
|
193 | 193 | " value=7.5,\n", |
|
194 | 194 | " description='Any:',\n", |
|
195 | 195 | ")" |
|
196 | 196 | ] |
|
197 | 197 | }, |
|
198 | 198 | { |
|
199 | 199 | "cell_type": "markdown", |
|
200 | 200 | "metadata": { |
|
201 | 201 | "slideshow": { |
|
202 | 202 | "slide_type": "slide" |
|
203 | 203 | } |
|
204 | 204 | }, |
|
205 | 205 | "source": [ |
|
206 | 206 | "## Boolean widgets" |
|
207 | 207 | ] |
|
208 | 208 | }, |
|
209 | 209 | { |
|
210 | 210 | "cell_type": "markdown", |
|
211 | 211 | "metadata": {}, |
|
212 | 212 | "source": [ |
|
213 | 213 | "There are two widgets that are designed to display a boolean value." |
|
214 | 214 | ] |
|
215 | 215 | }, |
|
216 | 216 | { |
|
217 | 217 | "cell_type": "markdown", |
|
218 | 218 | "metadata": {}, |
|
219 | 219 | "source": [ |
|
220 | 220 | "### ToggleButton" |
|
221 | 221 | ] |
|
222 | 222 | }, |
|
223 | 223 | { |
|
224 | 224 | "cell_type": "code", |
|
225 | 225 | "execution_count": null, |
|
226 | 226 | "metadata": { |
|
227 | 227 | "collapsed": false |
|
228 | 228 | }, |
|
229 | 229 | "outputs": [], |
|
230 | 230 | "source": [ |
|
231 | 231 | "widgets.ToggleButton(\n", |
|
232 | 232 | " description='Click me',\n", |
|
233 | 233 | " value=False,\n", |
|
234 | 234 | ")" |
|
235 | 235 | ] |
|
236 | 236 | }, |
|
237 | 237 | { |
|
238 | 238 | "cell_type": "markdown", |
|
239 | 239 | "metadata": { |
|
240 | 240 | "slideshow": { |
|
241 | 241 | "slide_type": "slide" |
|
242 | 242 | } |
|
243 | 243 | }, |
|
244 | 244 | "source": [ |
|
245 | 245 | "### Checkbox" |
|
246 | 246 | ] |
|
247 | 247 | }, |
|
248 | 248 | { |
|
249 | 249 | "cell_type": "code", |
|
250 | 250 | "execution_count": null, |
|
251 | 251 | "metadata": { |
|
252 | 252 | "collapsed": false |
|
253 | 253 | }, |
|
254 | 254 | "outputs": [], |
|
255 | 255 | "source": [ |
|
256 | 256 | "widgets.Checkbox(\n", |
|
257 | 257 | " description='Check me',\n", |
|
258 | 258 | " value=True,\n", |
|
259 | 259 | ")" |
|
260 | 260 | ] |
|
261 | 261 | }, |
|
262 | 262 | { |
|
263 | 263 | "cell_type": "markdown", |
|
264 | 264 | "metadata": { |
|
265 | 265 | "slideshow": { |
|
266 | 266 | "slide_type": "slide" |
|
267 | 267 | } |
|
268 | 268 | }, |
|
269 | 269 | "source": [ |
|
270 | 270 | "## Selection widgets" |
|
271 | 271 | ] |
|
272 | 272 | }, |
|
273 | 273 | { |
|
274 | 274 | "cell_type": "markdown", |
|
275 | 275 | "metadata": {}, |
|
276 | 276 | "source": [ |
|
277 |
"There are four widgets that can be used to display single selection lists. All |
|
|
277 | "There are four widgets that can be used to display single selection lists, and one that can be used to display multiple selection lists. All inherit from the same base class. You can specify the **enumeration of selectable options by passing a list**. You can **also specify the enumeration as a dictionary**, in which case the **keys will be used as the item displayed** in the list and the corresponding **value will be returned** when an item is selected." | |
|
278 | 278 | ] |
|
279 | 279 | }, |
|
280 | 280 | { |
|
281 | 281 | "cell_type": "markdown", |
|
282 | 282 | "metadata": { |
|
283 | 283 | "slideshow": { |
|
284 | 284 | "slide_type": "slide" |
|
285 | 285 | } |
|
286 | 286 | }, |
|
287 | 287 | "source": [ |
|
288 | 288 | "### Dropdown" |
|
289 | 289 | ] |
|
290 | 290 | }, |
|
291 | 291 | { |
|
292 | 292 | "cell_type": "code", |
|
293 | 293 | "execution_count": null, |
|
294 | 294 | "metadata": { |
|
295 | 295 | "collapsed": false |
|
296 | 296 | }, |
|
297 | 297 | "outputs": [], |
|
298 | 298 | "source": [ |
|
299 | 299 | "from IPython.display import display\n", |
|
300 | 300 | "w = widgets.Dropdown(\n", |
|
301 |
" |
|
|
301 | " options=['1', '2', '3'],\n", | |
|
302 | 302 | " value='2',\n", |
|
303 | 303 | " description='Number:',\n", |
|
304 | 304 | ")\n", |
|
305 | 305 | "display(w)" |
|
306 | 306 | ] |
|
307 | 307 | }, |
|
308 | 308 | { |
|
309 | 309 | "cell_type": "code", |
|
310 | 310 | "execution_count": null, |
|
311 | 311 | "metadata": { |
|
312 | 312 | "collapsed": false |
|
313 | 313 | }, |
|
314 | 314 | "outputs": [], |
|
315 | 315 | "source": [ |
|
316 | 316 | "w.value" |
|
317 | 317 | ] |
|
318 | 318 | }, |
|
319 | 319 | { |
|
320 | 320 | "cell_type": "markdown", |
|
321 | 321 | "metadata": {}, |
|
322 | 322 | "source": [ |
|
323 | 323 | "The following is also valid:" |
|
324 | 324 | ] |
|
325 | 325 | }, |
|
326 | 326 | { |
|
327 | 327 | "cell_type": "code", |
|
328 | 328 | "execution_count": null, |
|
329 | 329 | "metadata": { |
|
330 | 330 | "collapsed": false |
|
331 | 331 | }, |
|
332 | 332 | "outputs": [], |
|
333 | 333 | "source": [ |
|
334 | 334 | "w = widgets.Dropdown(\n", |
|
335 |
" |
|
|
335 | " options={'One': 1, 'Two': 2, 'Three': 3},\n", | |
|
336 | 336 | " value=2,\n", |
|
337 | 337 | " description='Number:',\n", |
|
338 | 338 | ")\n", |
|
339 | 339 | "display(w)" |
|
340 | 340 | ] |
|
341 | 341 | }, |
|
342 | 342 | { |
|
343 | 343 | "cell_type": "code", |
|
344 | 344 | "execution_count": null, |
|
345 | 345 | "metadata": { |
|
346 | 346 | "collapsed": false |
|
347 | 347 | }, |
|
348 | 348 | "outputs": [], |
|
349 | 349 | "source": [ |
|
350 | 350 | "w.value" |
|
351 | 351 | ] |
|
352 | 352 | }, |
|
353 | 353 | { |
|
354 | 354 | "cell_type": "markdown", |
|
355 | 355 | "metadata": { |
|
356 | 356 | "slideshow": { |
|
357 | 357 | "slide_type": "slide" |
|
358 | 358 | } |
|
359 | 359 | }, |
|
360 | 360 | "source": [ |
|
361 | 361 | "### RadioButtons" |
|
362 | 362 | ] |
|
363 | 363 | }, |
|
364 | 364 | { |
|
365 | 365 | "cell_type": "code", |
|
366 | 366 | "execution_count": null, |
|
367 | 367 | "metadata": { |
|
368 | 368 | "collapsed": false |
|
369 | 369 | }, |
|
370 | 370 | "outputs": [], |
|
371 | 371 | "source": [ |
|
372 | 372 | "widgets.RadioButtons(\n", |
|
373 | 373 | " description='Pizza topping:',\n", |
|
374 |
" |
|
|
374 | " options=['pepperoni', 'pineapple', 'anchovies'],\n", | |
|
375 | 375 | ")" |
|
376 | 376 | ] |
|
377 | 377 | }, |
|
378 | 378 | { |
|
379 | 379 | "cell_type": "markdown", |
|
380 | 380 | "metadata": { |
|
381 | 381 | "slideshow": { |
|
382 | 382 | "slide_type": "slide" |
|
383 | 383 | } |
|
384 | 384 | }, |
|
385 | 385 | "source": [ |
|
386 | 386 | "### Select" |
|
387 | 387 | ] |
|
388 | 388 | }, |
|
389 | 389 | { |
|
390 | 390 | "cell_type": "code", |
|
391 | 391 | "execution_count": null, |
|
392 | 392 | "metadata": { |
|
393 | 393 | "collapsed": false |
|
394 | 394 | }, |
|
395 | 395 | "outputs": [], |
|
396 | 396 | "source": [ |
|
397 | 397 | "widgets.Select(\n", |
|
398 | 398 | " description='OS:',\n", |
|
399 |
" |
|
|
399 | " options=['Linux', 'Windows', 'OSX'],\n", | |
|
400 | 400 | ")" |
|
401 | 401 | ] |
|
402 | 402 | }, |
|
403 | 403 | { |
|
404 | 404 | "cell_type": "markdown", |
|
405 | 405 | "metadata": { |
|
406 | 406 | "slideshow": { |
|
407 | 407 | "slide_type": "slide" |
|
408 | 408 | } |
|
409 | 409 | }, |
|
410 | 410 | "source": [ |
|
411 | 411 | "### ToggleButtons" |
|
412 | 412 | ] |
|
413 | 413 | }, |
|
414 | 414 | { |
|
415 | 415 | "cell_type": "code", |
|
416 | 416 | "execution_count": null, |
|
417 | 417 | "metadata": { |
|
418 | 418 | "collapsed": false |
|
419 | 419 | }, |
|
420 | 420 | "outputs": [], |
|
421 | 421 | "source": [ |
|
422 | 422 | "widgets.ToggleButtons(\n", |
|
423 | 423 | " description='Speed:',\n", |
|
424 |
" |
|
|
424 | " options=['Slow', 'Regular', 'Fast'],\n", | |
|
425 | 425 | ")" |
|
426 | 426 | ] |
|
427 | 427 | }, |
|
428 | 428 | { |
|
429 | 429 | "cell_type": "markdown", |
|
430 | "metadata": {}, | |
|
431 | "source": [ | |
|
432 | "### SelectMultiple\n", | |
|
433 | "Multiple values can be selected with <kbd>shift</kbd> and <kbd>ctrl</kbd> pressed and mouse clicks or arrow keys." | |
|
434 | ] | |
|
435 | }, | |
|
436 | { | |
|
437 | "cell_type": "code", | |
|
438 | "execution_count": null, | |
|
439 | "metadata": { | |
|
440 | "collapsed": true | |
|
441 | }, | |
|
442 | "outputs": [], | |
|
443 | "source": [ | |
|
444 | "w = widgets.SelectMultiple(\n", | |
|
445 | " description=\"Fruits\",\n", | |
|
446 | " options=['Apples', 'Oranges', 'Pears']\n", | |
|
447 | ")\n", | |
|
448 | "display(w)" | |
|
449 | ] | |
|
450 | }, | |
|
451 | { | |
|
452 | "cell_type": "code", | |
|
453 | "execution_count": null, | |
|
454 | "metadata": { | |
|
455 | "collapsed": false | |
|
456 | }, | |
|
457 | "outputs": [], | |
|
458 | "source": [ | |
|
459 | "w.value" | |
|
460 | ] | |
|
461 | }, | |
|
462 | { | |
|
463 | "cell_type": "markdown", | |
|
430 | 464 | "metadata": { |
|
431 | 465 | "slideshow": { |
|
432 | 466 | "slide_type": "slide" |
|
433 | 467 | } |
|
434 | 468 | }, |
|
435 | 469 | "source": [ |
|
436 | 470 | "## String widgets" |
|
437 | 471 | ] |
|
438 | 472 | }, |
|
439 | 473 | { |
|
440 | 474 | "cell_type": "markdown", |
|
441 | 475 | "metadata": {}, |
|
442 | 476 | "source": [ |
|
443 | 477 | "There are 4 widgets that can be used to display a string value. Of those, the **`Text` and `Textarea` widgets accept input**. The **`Latex` and `HTML` widgets display the string** as either Latex or HTML respectively, but **do not accept input**." |
|
444 | 478 | ] |
|
445 | 479 | }, |
|
446 | 480 | { |
|
447 | 481 | "cell_type": "markdown", |
|
448 | 482 | "metadata": { |
|
449 | 483 | "slideshow": { |
|
450 | 484 | "slide_type": "slide" |
|
451 | 485 | } |
|
452 | 486 | }, |
|
453 | 487 | "source": [ |
|
454 | 488 | "### Text" |
|
455 | 489 | ] |
|
456 | 490 | }, |
|
457 | 491 | { |
|
458 | 492 | "cell_type": "code", |
|
459 | 493 | "execution_count": null, |
|
460 | 494 | "metadata": { |
|
461 | 495 | "collapsed": false |
|
462 | 496 | }, |
|
463 | 497 | "outputs": [], |
|
464 | 498 | "source": [ |
|
465 | 499 | "widgets.Text(\n", |
|
466 | 500 | " description='String:',\n", |
|
467 | 501 | " value='Hello World',\n", |
|
468 | 502 | ")" |
|
469 | 503 | ] |
|
470 | 504 | }, |
|
471 | 505 | { |
|
472 | 506 | "cell_type": "markdown", |
|
473 | 507 | "metadata": {}, |
|
474 | 508 | "source": [ |
|
475 | 509 | "### Textarea" |
|
476 | 510 | ] |
|
477 | 511 | }, |
|
478 | 512 | { |
|
479 | 513 | "cell_type": "code", |
|
480 | 514 | "execution_count": null, |
|
481 | 515 | "metadata": { |
|
482 | 516 | "collapsed": false |
|
483 | 517 | }, |
|
484 | 518 | "outputs": [], |
|
485 | 519 | "source": [ |
|
486 | 520 | "widgets.Textarea(\n", |
|
487 | 521 | " description='String:',\n", |
|
488 | 522 | " value='Hello World',\n", |
|
489 | 523 | ")" |
|
490 | 524 | ] |
|
491 | 525 | }, |
|
492 | 526 | { |
|
493 | 527 | "cell_type": "markdown", |
|
494 | 528 | "metadata": { |
|
495 | 529 | "slideshow": { |
|
496 | 530 | "slide_type": "slide" |
|
497 | 531 | } |
|
498 | 532 | }, |
|
499 | 533 | "source": [ |
|
500 | 534 | "### Latex" |
|
501 | 535 | ] |
|
502 | 536 | }, |
|
503 | 537 | { |
|
504 | 538 | "cell_type": "code", |
|
505 | 539 | "execution_count": null, |
|
506 | 540 | "metadata": { |
|
507 | 541 | "collapsed": false |
|
508 | 542 | }, |
|
509 | 543 | "outputs": [], |
|
510 | 544 | "source": [ |
|
511 | 545 | "widgets.Latex(\n", |
|
512 | 546 | " value=\"$$\\\\frac{n!}{k!(n-k)!} = \\\\binom{n}{k}$$\",\n", |
|
513 | 547 | ")" |
|
514 | 548 | ] |
|
515 | 549 | }, |
|
516 | 550 | { |
|
517 | 551 | "cell_type": "markdown", |
|
518 | 552 | "metadata": {}, |
|
519 | 553 | "source": [ |
|
520 | 554 | "### HTML" |
|
521 | 555 | ] |
|
522 | 556 | }, |
|
523 | 557 | { |
|
524 | 558 | "cell_type": "code", |
|
525 | 559 | "execution_count": null, |
|
526 | 560 | "metadata": { |
|
527 | 561 | "collapsed": false |
|
528 | 562 | }, |
|
529 | 563 | "outputs": [], |
|
530 | 564 | "source": [ |
|
531 | 565 | "widgets.HTML(\n", |
|
532 | 566 | " value=\"Hello <b>World</b>\"\n", |
|
533 | 567 | ")" |
|
534 | 568 | ] |
|
535 | 569 | }, |
|
536 | 570 | { |
|
537 | 571 | "cell_type": "markdown", |
|
538 | 572 | "metadata": { |
|
539 | 573 | "slideshow": { |
|
540 | 574 | "slide_type": "slide" |
|
541 | 575 | } |
|
542 | 576 | }, |
|
543 | 577 | "source": [ |
|
544 | 578 | "## Button" |
|
545 | 579 | ] |
|
546 | 580 | }, |
|
547 | 581 | { |
|
548 | 582 | "cell_type": "code", |
|
549 | 583 | "execution_count": null, |
|
550 | 584 | "metadata": { |
|
551 | 585 | "collapsed": false |
|
552 | 586 | }, |
|
553 | 587 | "outputs": [], |
|
554 | 588 | "source": [ |
|
555 | 589 | "widgets.Button(description='Click me')" |
|
556 | 590 | ] |
|
557 | 591 | }, |
|
558 | 592 | { |
|
559 | 593 | "cell_type": "markdown", |
|
560 | 594 | "metadata": {}, |
|
561 | 595 | "source": [ |
|
562 | 596 | "[Index](Index.ipynb) - [Back](Widget Basics.ipynb) - [Next](Widget Events.ipynb)" |
|
563 | 597 | ] |
|
564 | 598 | } |
|
565 | 599 | ], |
|
566 | 600 | "metadata": { |
|
567 | 601 | "kernelspec": { |
|
568 | 602 | "display_name": "Python 3", |
|
569 | 603 | "language": "python", |
|
570 | 604 | "name": "python3" |
|
571 | 605 | }, |
|
572 | 606 | "language_info": { |
|
573 | 607 | "codemirror_mode": { |
|
574 | 608 | "name": "ipython", |
|
575 | 609 | "version": 3 |
|
576 | 610 | }, |
|
577 | 611 | "file_extension": ".py", |
|
578 | 612 | "mimetype": "text/x-python", |
|
579 | 613 | "name": "python", |
|
580 | 614 | "nbconvert_exporter": "python", |
|
581 | 615 | "pygments_lexer": "ipython3", |
|
582 | 616 | "version": "3.4.2" |
|
583 | 617 | } |
|
584 | 618 | }, |
|
585 | 619 | "nbformat": 4, |
|
586 | 620 | "nbformat_minor": 0 |
|
587 | 621 | } |
@@ -1,585 +1,585 b'' | |||
|
1 | 1 | { |
|
2 | 2 | "cells": [ |
|
3 | 3 | { |
|
4 | 4 | "cell_type": "markdown", |
|
5 | 5 | "metadata": {}, |
|
6 | 6 | "source": [ |
|
7 | 7 | "[Index](Index.ipynb) - [Back](Widget Events.ipynb) - [Next](Custom Widget - Hello World.ipynb)" |
|
8 | 8 | ] |
|
9 | 9 | }, |
|
10 | 10 | { |
|
11 | 11 | "cell_type": "code", |
|
12 | 12 | "execution_count": null, |
|
13 | 13 | "metadata": { |
|
14 | 14 | "collapsed": false |
|
15 | 15 | }, |
|
16 | 16 | "outputs": [], |
|
17 | 17 | "source": [ |
|
18 | 18 | "%%html\n", |
|
19 | 19 | "<style>\n", |
|
20 | 20 | ".example-container { background: #999999; padding: 2px; min-height: 100px; }\n", |
|
21 | 21 | ".example-container.sm { min-height: 50px; }\n", |
|
22 | 22 | ".example-box { background: #9999FF; width: 50px; height: 50px; text-align: center; vertical-align: middle; color: white; font-weight: bold; margin: 2px;}\n", |
|
23 | 23 | ".example-box.med { width: 65px; height: 65px; } \n", |
|
24 | 24 | ".example-box.lrg { width: 80px; height: 80px; } \n", |
|
25 | 25 | "</style>" |
|
26 | 26 | ] |
|
27 | 27 | }, |
|
28 | 28 | { |
|
29 | 29 | "cell_type": "code", |
|
30 | 30 | "execution_count": null, |
|
31 | 31 | "metadata": { |
|
32 | 32 | "collapsed": false |
|
33 | 33 | }, |
|
34 | 34 | "outputs": [], |
|
35 | 35 | "source": [ |
|
36 | 36 | "from IPython.html import widgets\n", |
|
37 | 37 | "from IPython.display import display" |
|
38 | 38 | ] |
|
39 | 39 | }, |
|
40 | 40 | { |
|
41 | 41 | "cell_type": "markdown", |
|
42 | 42 | "metadata": { |
|
43 | 43 | "slideshow": { |
|
44 | 44 | "slide_type": "slide" |
|
45 | 45 | } |
|
46 | 46 | }, |
|
47 | 47 | "source": [ |
|
48 | 48 | "# Widget Styling" |
|
49 | 49 | ] |
|
50 | 50 | }, |
|
51 | 51 | { |
|
52 | 52 | "cell_type": "markdown", |
|
53 | 53 | "metadata": {}, |
|
54 | 54 | "source": [ |
|
55 | 55 | "## Basic styling" |
|
56 | 56 | ] |
|
57 | 57 | }, |
|
58 | 58 | { |
|
59 | 59 | "cell_type": "markdown", |
|
60 | 60 | "metadata": {}, |
|
61 | 61 | "source": [ |
|
62 | 62 | "The widgets distributed with IPython can be styled by setting the following traits:\n", |
|
63 | 63 | "\n", |
|
64 | 64 | "- width \n", |
|
65 | 65 | "- height \n", |
|
66 | 66 | "- fore_color \n", |
|
67 | 67 | "- back_color \n", |
|
68 | 68 | "- border_color \n", |
|
69 | 69 | "- border_width \n", |
|
70 | 70 | "- border_style \n", |
|
71 | 71 | "- font_style \n", |
|
72 | 72 | "- font_weight \n", |
|
73 | 73 | "- font_size \n", |
|
74 | 74 | "- font_family \n", |
|
75 | 75 | "\n", |
|
76 | 76 | "The example below shows how a `Button` widget can be styled:" |
|
77 | 77 | ] |
|
78 | 78 | }, |
|
79 | 79 | { |
|
80 | 80 | "cell_type": "code", |
|
81 | 81 | "execution_count": null, |
|
82 | 82 | "metadata": { |
|
83 | 83 | "collapsed": false |
|
84 | 84 | }, |
|
85 | 85 | "outputs": [], |
|
86 | 86 | "source": [ |
|
87 | 87 | "button = widgets.Button(\n", |
|
88 | 88 | " description='Hello World!',\n", |
|
89 | 89 | " width=100, # Integers are interpreted as pixel measurements.\n", |
|
90 | 90 | " height='2em', # em is valid HTML unit of measurement.\n", |
|
91 | 91 | " color='lime', # Colors can be set by name,\n", |
|
92 | 92 | " background_color='#0022FF', # and also by color code.\n", |
|
93 | 93 | " border_color='red')\n", |
|
94 | 94 | "display(button)" |
|
95 | 95 | ] |
|
96 | 96 | }, |
|
97 | 97 | { |
|
98 | 98 | "cell_type": "markdown", |
|
99 | 99 | "metadata": { |
|
100 | 100 | "slideshow": { |
|
101 | 101 | "slide_type": "slide" |
|
102 | 102 | } |
|
103 | 103 | }, |
|
104 | 104 | "source": [ |
|
105 | 105 | "## Parent/child relationships" |
|
106 | 106 | ] |
|
107 | 107 | }, |
|
108 | 108 | { |
|
109 | 109 | "cell_type": "markdown", |
|
110 | 110 | "metadata": {}, |
|
111 | 111 | "source": [ |
|
112 | 112 | "To display widget A inside widget B, widget A must be a child of widget B. Widgets that can contain other widgets have a **`children` attribute**. This attribute can be **set via a keyword argument** in the widget's constructor **or after construction**. Calling display on an **object with children automatically displays those children**, too." |
|
113 | 113 | ] |
|
114 | 114 | }, |
|
115 | 115 | { |
|
116 | 116 | "cell_type": "code", |
|
117 | 117 | "execution_count": null, |
|
118 | 118 | "metadata": { |
|
119 | 119 | "collapsed": false |
|
120 | 120 | }, |
|
121 | 121 | "outputs": [], |
|
122 | 122 | "source": [ |
|
123 | 123 | "from IPython.display import display\n", |
|
124 | 124 | "\n", |
|
125 | 125 | "float_range = widgets.FloatSlider()\n", |
|
126 | 126 | "string = widgets.Text(value='hi')\n", |
|
127 | 127 | "container = widgets.Box(children=[float_range, string])\n", |
|
128 | 128 | "\n", |
|
129 | 129 | "container.border_color = 'red'\n", |
|
130 | 130 | "container.border_style = 'dotted'\n", |
|
131 | 131 | "container.border_width = 3\n", |
|
132 | 132 | "display(container) # Displays the `container` and all of it's children." |
|
133 | 133 | ] |
|
134 | 134 | }, |
|
135 | 135 | { |
|
136 | 136 | "cell_type": "markdown", |
|
137 | 137 | "metadata": {}, |
|
138 | 138 | "source": [ |
|
139 | 139 | "### After the parent is displayed" |
|
140 | 140 | ] |
|
141 | 141 | }, |
|
142 | 142 | { |
|
143 | 143 | "cell_type": "markdown", |
|
144 | 144 | "metadata": { |
|
145 | 145 | "slideshow": { |
|
146 | 146 | "slide_type": "slide" |
|
147 | 147 | } |
|
148 | 148 | }, |
|
149 | 149 | "source": [ |
|
150 | 150 | "Children **can be added to parents** after the parent has been displayed. The **parent is responsible for rendering its children**." |
|
151 | 151 | ] |
|
152 | 152 | }, |
|
153 | 153 | { |
|
154 | 154 | "cell_type": "code", |
|
155 | 155 | "execution_count": null, |
|
156 | 156 | "metadata": { |
|
157 | 157 | "collapsed": false |
|
158 | 158 | }, |
|
159 | 159 | "outputs": [], |
|
160 | 160 | "source": [ |
|
161 | 161 | "container = widgets.Box()\n", |
|
162 | 162 | "container.border_color = 'red'\n", |
|
163 | 163 | "container.border_style = 'dotted'\n", |
|
164 | 164 | "container.border_width = 3\n", |
|
165 | 165 | "display(container)\n", |
|
166 | 166 | "\n", |
|
167 | 167 | "int_range = widgets.IntSlider()\n", |
|
168 | 168 | "container.children=[int_range]" |
|
169 | 169 | ] |
|
170 | 170 | }, |
|
171 | 171 | { |
|
172 | 172 | "cell_type": "markdown", |
|
173 | 173 | "metadata": { |
|
174 | 174 | "slideshow": { |
|
175 | 175 | "slide_type": "slide" |
|
176 | 176 | } |
|
177 | 177 | }, |
|
178 | 178 | "source": [ |
|
179 | 179 | "## Fancy boxes" |
|
180 | 180 | ] |
|
181 | 181 | }, |
|
182 | 182 | { |
|
183 | 183 | "cell_type": "markdown", |
|
184 | 184 | "metadata": {}, |
|
185 | 185 | "source": [ |
|
186 | 186 | "If you need to display a more complicated set of widgets, there are **specialized containers** that you can use. To display **multiple sets of widgets**, you can use an **`Accordion` or a `Tab` in combination with one `Box` per set of widgets** (as seen below). The \"pages\" of these widgets are their children. To set the titles of the pages, one can **call `set_title`**." |
|
187 | 187 | ] |
|
188 | 188 | }, |
|
189 | 189 | { |
|
190 | 190 | "cell_type": "markdown", |
|
191 | 191 | "metadata": {}, |
|
192 | 192 | "source": [ |
|
193 | 193 | "### Accordion" |
|
194 | 194 | ] |
|
195 | 195 | }, |
|
196 | 196 | { |
|
197 | 197 | "cell_type": "code", |
|
198 | 198 | "execution_count": null, |
|
199 | 199 | "metadata": { |
|
200 | 200 | "collapsed": false |
|
201 | 201 | }, |
|
202 | 202 | "outputs": [], |
|
203 | 203 | "source": [ |
|
204 | 204 | "name1 = widgets.Text(description='Location:')\n", |
|
205 | 205 | "zip1 = widgets.BoundedIntText(description='Zip:', min=0, max=99999)\n", |
|
206 | 206 | "page1 = widgets.Box(children=[name1, zip1])\n", |
|
207 | 207 | "\n", |
|
208 | 208 | "name2 = widgets.Text(description='Location:')\n", |
|
209 | 209 | "zip2 = widgets.BoundedIntText(description='Zip:', min=0, max=99999)\n", |
|
210 | 210 | "page2 = widgets.Box(children=[name2, zip2])\n", |
|
211 | 211 | "\n", |
|
212 | 212 | "accord = widgets.Accordion(children=[page1, page2])\n", |
|
213 | 213 | "display(accord)\n", |
|
214 | 214 | "\n", |
|
215 | 215 | "accord.set_title(0, 'From')\n", |
|
216 | 216 | "accord.set_title(1, 'To')" |
|
217 | 217 | ] |
|
218 | 218 | }, |
|
219 | 219 | { |
|
220 | 220 | "cell_type": "markdown", |
|
221 | 221 | "metadata": { |
|
222 | 222 | "slideshow": { |
|
223 | 223 | "slide_type": "slide" |
|
224 | 224 | } |
|
225 | 225 | }, |
|
226 | 226 | "source": [ |
|
227 | 227 | "### TabWidget" |
|
228 | 228 | ] |
|
229 | 229 | }, |
|
230 | 230 | { |
|
231 | 231 | "cell_type": "code", |
|
232 | 232 | "execution_count": null, |
|
233 | 233 | "metadata": { |
|
234 | 234 | "collapsed": false |
|
235 | 235 | }, |
|
236 | 236 | "outputs": [], |
|
237 | 237 | "source": [ |
|
238 | 238 | "name = widgets.Text(description='Name:')\n", |
|
239 |
"color = widgets.Dropdown(description='Color:', |
|
|
239 | "color = widgets.Dropdown(description='Color:', options=['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'])\n", | |
|
240 | 240 | "page1 = widgets.Box(children=[name, color])\n", |
|
241 | 241 | "\n", |
|
242 | 242 | "age = widgets.IntSlider(description='Age:', min=0, max=120, value=50)\n", |
|
243 |
"gender = widgets.RadioButtons(description='Gender:', |
|
|
243 | "gender = widgets.RadioButtons(description='Gender:', options=['male', 'female'])\n", | |
|
244 | 244 | "page2 = widgets.Box(children=[age, gender])\n", |
|
245 | 245 | "\n", |
|
246 | 246 | "tabs = widgets.Tab(children=[page1, page2])\n", |
|
247 | 247 | "display(tabs)\n", |
|
248 | 248 | "\n", |
|
249 | 249 | "tabs.set_title(0, 'Name')\n", |
|
250 | 250 | "tabs.set_title(1, 'Details')" |
|
251 | 251 | ] |
|
252 | 252 | }, |
|
253 | 253 | { |
|
254 | 254 | "cell_type": "markdown", |
|
255 | 255 | "metadata": { |
|
256 | 256 | "slideshow": { |
|
257 | 257 | "slide_type": "slide" |
|
258 | 258 | } |
|
259 | 259 | }, |
|
260 | 260 | "source": [ |
|
261 | 261 | "# Alignment" |
|
262 | 262 | ] |
|
263 | 263 | }, |
|
264 | 264 | { |
|
265 | 265 | "cell_type": "markdown", |
|
266 | 266 | "metadata": {}, |
|
267 | 267 | "source": [ |
|
268 | 268 | "Most widgets have a **`description` attribute**, which allows a label for the widget to be defined.\n", |
|
269 | 269 | "The label of the widget **has a fixed minimum width**.\n", |
|
270 | 270 | "The text of the label is **always right aligned and the widget is left aligned**:" |
|
271 | 271 | ] |
|
272 | 272 | }, |
|
273 | 273 | { |
|
274 | 274 | "cell_type": "code", |
|
275 | 275 | "execution_count": null, |
|
276 | 276 | "metadata": { |
|
277 | 277 | "collapsed": false |
|
278 | 278 | }, |
|
279 | 279 | "outputs": [], |
|
280 | 280 | "source": [ |
|
281 | 281 | "display(widgets.Text(description=\"a:\"))\n", |
|
282 | 282 | "display(widgets.Text(description=\"aa:\"))\n", |
|
283 | 283 | "display(widgets.Text(description=\"aaa:\"))" |
|
284 | 284 | ] |
|
285 | 285 | }, |
|
286 | 286 | { |
|
287 | 287 | "cell_type": "markdown", |
|
288 | 288 | "metadata": { |
|
289 | 289 | "slideshow": { |
|
290 | 290 | "slide_type": "slide" |
|
291 | 291 | } |
|
292 | 292 | }, |
|
293 | 293 | "source": [ |
|
294 | 294 | "If a **label is longer** than the minimum width, the **widget is shifted to the right**:" |
|
295 | 295 | ] |
|
296 | 296 | }, |
|
297 | 297 | { |
|
298 | 298 | "cell_type": "code", |
|
299 | 299 | "execution_count": null, |
|
300 | 300 | "metadata": { |
|
301 | 301 | "collapsed": false |
|
302 | 302 | }, |
|
303 | 303 | "outputs": [], |
|
304 | 304 | "source": [ |
|
305 | 305 | "display(widgets.Text(description=\"a:\"))\n", |
|
306 | 306 | "display(widgets.Text(description=\"aa:\"))\n", |
|
307 | 307 | "display(widgets.Text(description=\"aaa:\"))\n", |
|
308 | 308 | "display(widgets.Text(description=\"aaaaaaaaaaaaaaaaaa:\"))" |
|
309 | 309 | ] |
|
310 | 310 | }, |
|
311 | 311 | { |
|
312 | 312 | "cell_type": "markdown", |
|
313 | 313 | "metadata": { |
|
314 | 314 | "slideshow": { |
|
315 | 315 | "slide_type": "slide" |
|
316 | 316 | } |
|
317 | 317 | }, |
|
318 | 318 | "source": [ |
|
319 | 319 | "If a `description` is **not set** for the widget, the **label is not displayed**:" |
|
320 | 320 | ] |
|
321 | 321 | }, |
|
322 | 322 | { |
|
323 | 323 | "cell_type": "code", |
|
324 | 324 | "execution_count": null, |
|
325 | 325 | "metadata": { |
|
326 | 326 | "collapsed": false |
|
327 | 327 | }, |
|
328 | 328 | "outputs": [], |
|
329 | 329 | "source": [ |
|
330 | 330 | "display(widgets.Text(description=\"a:\"))\n", |
|
331 | 331 | "display(widgets.Text(description=\"aa:\"))\n", |
|
332 | 332 | "display(widgets.Text(description=\"aaa:\"))\n", |
|
333 | 333 | "display(widgets.Text())" |
|
334 | 334 | ] |
|
335 | 335 | }, |
|
336 | 336 | { |
|
337 | 337 | "cell_type": "markdown", |
|
338 | 338 | "metadata": { |
|
339 | 339 | "slideshow": { |
|
340 | 340 | "slide_type": "slide" |
|
341 | 341 | } |
|
342 | 342 | }, |
|
343 | 343 | "source": [ |
|
344 | 344 | "## Flex boxes" |
|
345 | 345 | ] |
|
346 | 346 | }, |
|
347 | 347 | { |
|
348 | 348 | "cell_type": "markdown", |
|
349 | 349 | "metadata": {}, |
|
350 | 350 | "source": [ |
|
351 | 351 | "Widgets can be aligned using the `FlexBox`, `HBox`, and `VBox` widgets." |
|
352 | 352 | ] |
|
353 | 353 | }, |
|
354 | 354 | { |
|
355 | 355 | "cell_type": "markdown", |
|
356 | 356 | "metadata": { |
|
357 | 357 | "slideshow": { |
|
358 | 358 | "slide_type": "slide" |
|
359 | 359 | } |
|
360 | 360 | }, |
|
361 | 361 | "source": [ |
|
362 | 362 | "### Application to widgets" |
|
363 | 363 | ] |
|
364 | 364 | }, |
|
365 | 365 | { |
|
366 | 366 | "cell_type": "markdown", |
|
367 | 367 | "metadata": {}, |
|
368 | 368 | "source": [ |
|
369 | 369 | "Widgets display vertically by default:" |
|
370 | 370 | ] |
|
371 | 371 | }, |
|
372 | 372 | { |
|
373 | 373 | "cell_type": "code", |
|
374 | 374 | "execution_count": null, |
|
375 | 375 | "metadata": { |
|
376 | 376 | "collapsed": false |
|
377 | 377 | }, |
|
378 | 378 | "outputs": [], |
|
379 | 379 | "source": [ |
|
380 | 380 | "buttons = [widgets.Button(description=str(i)) for i in range(3)]\n", |
|
381 | 381 | "display(*buttons)" |
|
382 | 382 | ] |
|
383 | 383 | }, |
|
384 | 384 | { |
|
385 | 385 | "cell_type": "markdown", |
|
386 | 386 | "metadata": { |
|
387 | 387 | "slideshow": { |
|
388 | 388 | "slide_type": "slide" |
|
389 | 389 | } |
|
390 | 390 | }, |
|
391 | 391 | "source": [ |
|
392 | 392 | "### Using hbox" |
|
393 | 393 | ] |
|
394 | 394 | }, |
|
395 | 395 | { |
|
396 | 396 | "cell_type": "markdown", |
|
397 | 397 | "metadata": {}, |
|
398 | 398 | "source": [ |
|
399 | 399 | "To make widgets display horizontally, you need to **child them to a `HBox` widget**." |
|
400 | 400 | ] |
|
401 | 401 | }, |
|
402 | 402 | { |
|
403 | 403 | "cell_type": "code", |
|
404 | 404 | "execution_count": null, |
|
405 | 405 | "metadata": { |
|
406 | 406 | "collapsed": false |
|
407 | 407 | }, |
|
408 | 408 | "outputs": [], |
|
409 | 409 | "source": [ |
|
410 | 410 | "container = widgets.HBox(children=buttons)\n", |
|
411 | 411 | "display(container)" |
|
412 | 412 | ] |
|
413 | 413 | }, |
|
414 | 414 | { |
|
415 | 415 | "cell_type": "markdown", |
|
416 | 416 | "metadata": {}, |
|
417 | 417 | "source": [ |
|
418 | 418 | "By setting the width of the container to 100% and its `pack` to `center`, you can center the buttons." |
|
419 | 419 | ] |
|
420 | 420 | }, |
|
421 | 421 | { |
|
422 | 422 | "cell_type": "code", |
|
423 | 423 | "execution_count": null, |
|
424 | 424 | "metadata": { |
|
425 | 425 | "collapsed": false |
|
426 | 426 | }, |
|
427 | 427 | "outputs": [], |
|
428 | 428 | "source": [ |
|
429 | 429 | "container.width = '100%'\n", |
|
430 | 430 | "container.pack = 'center'" |
|
431 | 431 | ] |
|
432 | 432 | }, |
|
433 | 433 | { |
|
434 | 434 | "cell_type": "markdown", |
|
435 | 435 | "metadata": { |
|
436 | 436 | "slideshow": { |
|
437 | 437 | "slide_type": "slide" |
|
438 | 438 | } |
|
439 | 439 | }, |
|
440 | 440 | "source": [ |
|
441 | 441 | "## Visibility" |
|
442 | 442 | ] |
|
443 | 443 | }, |
|
444 | 444 | { |
|
445 | 445 | "cell_type": "markdown", |
|
446 | 446 | "metadata": {}, |
|
447 | 447 | "source": [ |
|
448 | 448 | "Sometimes it is necessary to **hide or show widgets** in place, **without having to re-display** the widget.\n", |
|
449 | 449 | "The `visible` property of widgets can be used to hide or show **widgets that have already been displayed** (as seen below). The `visible` property can be:\n", |
|
450 | 450 | "* `True` - the widget is displayed\n", |
|
451 | 451 | "* `False` - the widget is hidden, and the empty space where the widget would be is collapsed\n", |
|
452 | 452 | "* `None` - the widget is hidden, and the empty space where the widget would be is shown" |
|
453 | 453 | ] |
|
454 | 454 | }, |
|
455 | 455 | { |
|
456 | 456 | "cell_type": "code", |
|
457 | 457 | "execution_count": null, |
|
458 | 458 | "metadata": { |
|
459 | 459 | "collapsed": false |
|
460 | 460 | }, |
|
461 | 461 | "outputs": [], |
|
462 | 462 | "source": [ |
|
463 | 463 | "w1 = widgets.Latex(value=\"First line\")\n", |
|
464 | 464 | "w2 = widgets.Latex(value=\"Second line\")\n", |
|
465 | 465 | "w3 = widgets.Latex(value=\"Third line\")\n", |
|
466 | 466 | "display(w1, w2, w3)" |
|
467 | 467 | ] |
|
468 | 468 | }, |
|
469 | 469 | { |
|
470 | 470 | "cell_type": "code", |
|
471 | 471 | "execution_count": null, |
|
472 | 472 | "metadata": { |
|
473 | 473 | "collapsed": true |
|
474 | 474 | }, |
|
475 | 475 | "outputs": [], |
|
476 | 476 | "source": [ |
|
477 | 477 | "w2.visible=None" |
|
478 | 478 | ] |
|
479 | 479 | }, |
|
480 | 480 | { |
|
481 | 481 | "cell_type": "code", |
|
482 | 482 | "execution_count": null, |
|
483 | 483 | "metadata": { |
|
484 | 484 | "collapsed": false |
|
485 | 485 | }, |
|
486 | 486 | "outputs": [], |
|
487 | 487 | "source": [ |
|
488 | 488 | "w2.visible=False" |
|
489 | 489 | ] |
|
490 | 490 | }, |
|
491 | 491 | { |
|
492 | 492 | "cell_type": "code", |
|
493 | 493 | "execution_count": null, |
|
494 | 494 | "metadata": { |
|
495 | 495 | "collapsed": false |
|
496 | 496 | }, |
|
497 | 497 | "outputs": [], |
|
498 | 498 | "source": [ |
|
499 | 499 | "w2.visible=True" |
|
500 | 500 | ] |
|
501 | 501 | }, |
|
502 | 502 | { |
|
503 | 503 | "cell_type": "markdown", |
|
504 | 504 | "metadata": { |
|
505 | 505 | "slideshow": { |
|
506 | 506 | "slide_type": "slide" |
|
507 | 507 | } |
|
508 | 508 | }, |
|
509 | 509 | "source": [ |
|
510 | 510 | "### Another example" |
|
511 | 511 | ] |
|
512 | 512 | }, |
|
513 | 513 | { |
|
514 | 514 | "cell_type": "markdown", |
|
515 | 515 | "metadata": {}, |
|
516 | 516 | "source": [ |
|
517 | 517 | "In the example below, a form is rendered, which conditionally displays widgets depending on the state of other widgets. Try toggling the student checkbox." |
|
518 | 518 | ] |
|
519 | 519 | }, |
|
520 | 520 | { |
|
521 | 521 | "cell_type": "code", |
|
522 | 522 | "execution_count": null, |
|
523 | 523 | "metadata": { |
|
524 | 524 | "collapsed": false |
|
525 | 525 | }, |
|
526 | 526 | "outputs": [], |
|
527 | 527 | "source": [ |
|
528 | 528 | "form = widgets.VBox()\n", |
|
529 | 529 | "first = widgets.Text(description=\"First Name:\")\n", |
|
530 | 530 | "last = widgets.Text(description=\"Last Name:\")\n", |
|
531 | 531 | "\n", |
|
532 | 532 | "student = widgets.Checkbox(description=\"Student:\", value=False)\n", |
|
533 | 533 | "school_info = widgets.VBox(visible=False, children=[\n", |
|
534 | 534 | " widgets.Text(description=\"School:\"),\n", |
|
535 | 535 | " widgets.IntText(description=\"Grade:\", min=0, max=12)\n", |
|
536 | 536 | " ])\n", |
|
537 | 537 | "\n", |
|
538 | 538 | "pet = widgets.Text(description=\"Pet's Name:\")\n", |
|
539 | 539 | "form.children = [first, last, student, school_info, pet]\n", |
|
540 | 540 | "display(form)\n", |
|
541 | 541 | "\n", |
|
542 | 542 | "def on_student_toggle(name, value):\n", |
|
543 | 543 | " if value:\n", |
|
544 | 544 | " school_info.visible = True\n", |
|
545 | 545 | " else:\n", |
|
546 | 546 | " school_info.visible = False\n", |
|
547 | 547 | "student.on_trait_change(on_student_toggle, 'value')\n" |
|
548 | 548 | ] |
|
549 | 549 | }, |
|
550 | 550 | { |
|
551 | 551 | "cell_type": "markdown", |
|
552 | 552 | "metadata": {}, |
|
553 | 553 | "source": [ |
|
554 | 554 | "[Index](Index.ipynb) - [Back](Widget Events.ipynb) - [Next](Custom Widget - Hello World.ipynb)" |
|
555 | 555 | ] |
|
556 | 556 | } |
|
557 | 557 | ], |
|
558 | 558 | "metadata": { |
|
559 | 559 | "cell_tags": [ |
|
560 | 560 | [ |
|
561 | 561 | "<None>", |
|
562 | 562 | null |
|
563 | 563 | ] |
|
564 | 564 | ], |
|
565 | 565 | "kernelspec": { |
|
566 | 566 | "display_name": "Python 3", |
|
567 | 567 | "language": "python", |
|
568 | 568 | "name": "python3" |
|
569 | 569 | }, |
|
570 | 570 | "language_info": { |
|
571 | 571 | "codemirror_mode": { |
|
572 | 572 | "name": "ipython", |
|
573 | 573 | "version": 3 |
|
574 | 574 | }, |
|
575 | 575 | "file_extension": ".py", |
|
576 | 576 | "mimetype": "text/x-python", |
|
577 | 577 | "name": "python", |
|
578 | 578 | "nbconvert_exporter": "python", |
|
579 | 579 | "pygments_lexer": "ipython3", |
|
580 | 580 | "version": "3.4.2" |
|
581 | 581 | } |
|
582 | 582 | }, |
|
583 | 583 | "nbformat": 4, |
|
584 | 584 | "nbformat_minor": 0 |
|
585 | 585 | } |
General Comments 0
You need to be logged in to leave comments.
Login now