##// END OF EJS Templates
squashing the whitespace changes
Nicholas Bollweg -
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("&nbsp;")
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('value_name');
61 var selected_item_text = this.model.get('selected_label');
61 62 if (selected_item_text.trim().length === 0) {
62 63 this.$droplabel.html("&nbsp;");
63 64 } else {
64 65 this.$droplabel.text(selected_item_text);
65 66 }
66 67
67 var items = this.model.get('_value_names');
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('value_name', $(e.target).text(), {updated_view: this});
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('_value_names');
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('value_name') == item) {
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('value_name', $(e.target).val(), {updated_view: this});
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('_value_names');
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 = "&nbsp;";
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('value_name') == item) {
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('value_name', $(e.target).attr('value'), {updated_view: this});
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('_value_names');
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('value_name', item)
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('value_name'));
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('value_name', $(e.target).text(), {updated_view: this});
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 'values=["' + selection_values + '"[i] for i in range(4)]\n' +
47 'selection = [widgets.Dropdown(values=values),\n' +
48 ' widgets.ToggleButtons(values=values),\n' +
49 ' widgets.RadioButtons(values=values),\n' +
50 ' widgets.Select(values=values)]\n' +
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.values)\n' +
139 ' d = copy(widget.options)\n' +
140 140 ' d.append("z")\n' +
141 ' widget.values = d\n' +
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(values=o)
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(values=[unicode_type(k) for k in o])
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 values=d,
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 values=values
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 values={'a': 1, 'b': 2},
290 options={'a': 1, 'b': 2},
291 291 value=2
292 292 ),
293 293 j=dict(
294 294 cls=widgets.Dropdown,
295 values=['hi', 'there'],
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 values={'a': 1},
313 options={'a': 1},
314 314 value=1,
315 315 ),
316 316 j=dict(
317 317 cls=widgets.Dropdown,
318 values=['hi', 'there'],
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 ``values`` can be specified as a list or dict. If given as a list,
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 value_name = Unicode(help="The name of the selected value", sync=True)
39 values = Any(help="""List of (key, value) tuples or dict of values that the
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 _value_names.
45 The keys of this list are also available as _options_labels.
46 46 """)
47 47
48 _values_dict = Dict()
49 _value_names = Tuple(sync=True)
50 _value_values = Tuple()
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.values_lock = Lock()
58 self.on_trait_change(self._values_readonly_changed, ['_values_dict', '_value_names', '_value_values', '_values'])
59 if 'values' in kwargs:
60 self.values = kwargs.pop('values')
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_values()
62 self._value_in_options()
63 63
64 def _make_values(self, x):
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 _values_changed(self, name, old, new):
82 """Handles when the values tuple has been changed.
81 def _options_changed(self, name, old, new):
82 """Handles when the options tuple has been changed.
83 83
84 Setting values implies setting value names from the keys of the dict.
85 """
86 if self.values_lock.acquire(False):
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.values = new
88 self.options = new
89 89
90 values = self._make_values(new)
91 self._values_dict = {i[0]: i[1] for i in values}
92 self._value_names = [i[0] for i in values]
93 self._value_values = [i[1] for i in values]
94 self._value_in_values()
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.values_lock.release()
96 self.options_lock.release()
97 97
98 def _value_in_values(self):
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._values_dict.items():
113 for k, v in self._options_dict.items():
114 114 if new == v:
115 115 # set the selected value name
116 self.value_name = k
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 _value_name_changed(self, name, old, new):
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._values_dict[new]
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(values=get_export_names(), value='html')\n",
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 four inherit from the same base class. You can specify the **enumeration of selectables 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."
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 " values=['1', '2', '3'],\n",
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 " values={'One': 1, 'Two': 2, 'Three': 3},\n",
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 " values=['pepperoni', 'pineapple', 'anchovies'],\n",
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 " values=['Linux', 'Windows', 'OSX'],\n",
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 " values=['Slow', 'Regular', 'Fast'],\n",
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:', values=['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'])\n",
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:', values=['male', 'female'])\n",
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