##// END OF EJS Templates
Fixed rebase bugs and other bugs.
Jonathan Frederic -
Show More
@@ -1,576 +1,576
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define(["widgets/js/manager",
5 5 "underscore",
6 6 "backbone",
7 7 "jquery",
8 8 "base/js/namespace",
9 9 ], function(widgetmanager, _, Backbone, $, IPython){
10 10
11 11 var WidgetModel = Backbone.Model.extend({
12 12 constructor: function (widget_manager, model_id, comm) {
13 13 // Constructor
14 14 //
15 15 // Creates a WidgetModel instance.
16 16 //
17 17 // Parameters
18 18 // ----------
19 19 // widget_manager : WidgetManager instance
20 20 // model_id : string
21 21 // An ID unique to this model.
22 22 // comm : Comm instance (optional)
23 23 this.widget_manager = widget_manager;
24 24 this._buffered_state_diff = {};
25 25 this.pending_msgs = 0;
26 26 this.msg_buffer = null;
27 27 this.key_value_lock = null;
28 28 this.id = model_id;
29 29 this.views = [];
30 30
31 31 if (comm !== undefined) {
32 32 // Remember comm associated with the model.
33 33 this.comm = comm;
34 34 comm.model = this;
35 35
36 36 // Hook comm messages up to model.
37 37 comm.on_close($.proxy(this._handle_comm_closed, this));
38 38 comm.on_msg($.proxy(this._handle_comm_msg, this));
39 39 }
40 40 return Backbone.Model.apply(this);
41 41 },
42 42
43 43 send: function (content, callbacks) {
44 44 // Send a custom msg over the comm.
45 45 if (this.comm !== undefined) {
46 46 var data = {method: 'custom', content: content};
47 47 this.comm.send(data, callbacks);
48 48 this.pending_msgs++;
49 49 }
50 50 },
51 51
52 52 _handle_comm_closed: function (msg) {
53 53 // Handle when a widget is closed.
54 54 this.trigger('comm:close');
55 55 delete this.comm.model; // Delete ref so GC will collect widget model.
56 56 delete this.comm;
57 57 delete this.model_id; // Delete id from model so widget manager cleans up.
58 58 _.each(this.views, function(view, i) {
59 59 view.remove();
60 60 });
61 61 },
62 62
63 63 _handle_comm_msg: function (msg) {
64 64 // Handle incoming comm msg.
65 65 var method = msg.content.data.method;
66 66 switch (method) {
67 67 case 'update':
68 68 this.apply_update(msg.content.data.state);
69 69 break;
70 70 case 'custom':
71 71 this.trigger('msg:custom', msg.content.data.content);
72 72 break;
73 73 case 'display':
74 74 this.widget_manager.display_view(msg, this);
75 75 break;
76 76 }
77 77 },
78 78
79 79 apply_update: function (state) {
80 80 // Handle when a widget is updated via the python side.
81 81 var that = this;
82 82 _.each(state, function(value, key) {
83 83 that.key_value_lock = [key, value];
84 84 try {
85 85 WidgetModel.__super__.set.apply(that, [key, that._unpack_models(value)]);
86 86 } finally {
87 87 that.key_value_lock = null;
88 88 }
89 89 });
90 90 },
91 91
92 92 _handle_status: function (msg, callbacks) {
93 93 // Handle status msgs.
94 94
95 95 // execution_state : ('busy', 'idle', 'starting')
96 96 if (this.comm !== undefined) {
97 97 if (msg.content.execution_state ==='idle') {
98 98 // Send buffer if this message caused another message to be
99 99 // throttled.
100 100 if (this.msg_buffer !== null &&
101 101 (this.get('msg_throttle') || 3) === this.pending_msgs) {
102 102 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
103 103 this.comm.send(data, callbacks);
104 104 this.msg_buffer = null;
105 105 } else {
106 106 --this.pending_msgs;
107 107 }
108 108 }
109 109 }
110 110 },
111 111
112 112 callbacks: function(view) {
113 113 // Create msg callbacks for a comm msg.
114 114 var callbacks = this.widget_manager.callbacks(view);
115 115
116 116 if (callbacks.iopub === undefined) {
117 117 callbacks.iopub = {};
118 118 }
119 119
120 120 var that = this;
121 121 callbacks.iopub.status = function (msg) {
122 122 that._handle_status(msg, callbacks);
123 123 };
124 124 return callbacks;
125 125 },
126 126
127 127 set: function(key, val, options) {
128 128 // Set a value.
129 129 var return_value = WidgetModel.__super__.set.apply(this, arguments);
130 130
131 131 // Backbone only remembers the diff of the most recent set()
132 132 // operation. Calling set multiple times in a row results in a
133 133 // loss of diff information. Here we keep our own running diff.
134 134 this._buffered_state_diff = $.extend(this._buffered_state_diff, this.changedAttributes() || {});
135 135 return return_value;
136 136 },
137 137
138 138 sync: function (method, model, options) {
139 139 // Handle sync to the back-end. Called when a model.save() is called.
140 140
141 141 // Make sure a comm exists.
142 142 var error = options.error || function() {
143 143 console.error('Backbone sync error:', arguments);
144 144 };
145 145 if (this.comm === undefined) {
146 146 error();
147 147 return false;
148 148 }
149 149
150 150 // Delete any key value pairs that the back-end already knows about.
151 151 var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
152 152 if (this.key_value_lock !== null) {
153 153 var key = this.key_value_lock[0];
154 154 var value = this.key_value_lock[1];
155 155 if (attrs[key] === value) {
156 156 delete attrs[key];
157 157 }
158 158 }
159 159
160 160 // Only sync if there are attributes to send to the back-end.
161 161 attrs = this._pack_models(attrs);
162 162 if (_.size(attrs) > 0) {
163 163
164 164 // If this message was sent via backbone itself, it will not
165 165 // have any callbacks. It's important that we create callbacks
166 166 // so we can listen for status messages, etc...
167 167 var callbacks = options.callbacks || this.callbacks();
168 168
169 169 // Check throttle.
170 170 if (this.pending_msgs >= (this.get('msg_throttle') || 3)) {
171 171 // The throttle has been exceeded, buffer the current msg so
172 172 // it can be sent once the kernel has finished processing
173 173 // some of the existing messages.
174 174
175 175 // Combine updates if it is a 'patch' sync, otherwise replace updates
176 176 switch (method) {
177 177 case 'patch':
178 178 this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
179 179 break;
180 180 case 'update':
181 181 case 'create':
182 182 this.msg_buffer = attrs;
183 183 break;
184 184 default:
185 185 error();
186 186 return false;
187 187 }
188 188 this.msg_buffer_callbacks = callbacks;
189 189
190 190 } else {
191 191 // We haven't exceeded the throttle, send the message like
192 192 // normal.
193 193 var data = {method: 'backbone', sync_data: attrs};
194 194 this.comm.send(data, callbacks);
195 195 this.pending_msgs++;
196 196 }
197 197 }
198 198 // Since the comm is a one-way communication, assume the message
199 199 // arrived. Don't call success since we don't have a model back from the server
200 200 // this means we miss out on the 'sync' event.
201 201 this._buffered_state_diff = {};
202 202 },
203 203
204 204 save_changes: function(callbacks) {
205 205 // Push this model's state to the back-end
206 206 //
207 207 // This invokes a Backbone.Sync.
208 208 this.save(this._buffered_state_diff, {patch: true, callbacks: callbacks});
209 209 },
210 210
211 211 _pack_models: function(value) {
212 212 // Replace models with model ids recursively.
213 213 var that = this;
214 214 var packed;
215 215 if (value instanceof Backbone.Model) {
216 216 return "IPY_MODEL_" + value.id;
217 217
218 218 } else if ($.isArray(value)) {
219 219 packed = [];
220 220 _.each(value, function(sub_value, key) {
221 221 packed.push(that._pack_models(sub_value));
222 222 });
223 223 return packed;
224 224
225 225 } else if (value instanceof Object) {
226 226 packed = {};
227 227 _.each(value, function(sub_value, key) {
228 228 packed[key] = that._pack_models(sub_value);
229 229 });
230 230 return packed;
231 231
232 232 } else {
233 233 return value;
234 234 }
235 235 },
236 236
237 237 _unpack_models: function(value) {
238 238 // Replace model ids with models recursively.
239 239 var that = this;
240 240 var unpacked;
241 241 if ($.isArray(value)) {
242 242 unpacked = [];
243 243 _.each(value, function(sub_value, key) {
244 244 unpacked.push(that._unpack_models(sub_value));
245 245 });
246 246 return unpacked;
247 247
248 248 } else if (value instanceof Object) {
249 249 unpacked = {};
250 250 _.each(value, function(sub_value, key) {
251 251 unpacked[key] = that._unpack_models(sub_value);
252 252 });
253 253 return unpacked;
254 254
255 255 } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") {
256 256 var model = this.widget_manager.get_model(value.slice(10, value.length));
257 257 if (model) {
258 258 return model;
259 259 } else {
260 260 return value;
261 261 }
262 262 } else {
263 263 return value;
264 264 }
265 265 },
266 266
267 267 });
268 268 widgetmanager.WidgetManager.register_widget_model('WidgetModel', WidgetModel);
269 269
270 270
271 271 var WidgetView = Backbone.View.extend({
272 272 initialize: function(parameters) {
273 273 // Public constructor.
274 274 this.model.on('change',this.update,this);
275 275 this.options = parameters.options;
276 276 this.child_model_views = {};
277 277 this.child_views = {};
278 278 this.model.views.push(this);
279 279 this.id = this.id || IPython.utils.uuid();
280 280 this.on('displayed', function() {
281 281 this.is_displayed = true;
282 282 }, this);
283 283 },
284 284
285 285 update: function(){
286 286 // Triggered on model change.
287 287 //
288 288 // Update view to be consistent with this.model
289 289 },
290 290
291 291 create_child_view: function(child_model, options) {
292 292 // Create and return a child view.
293 293 //
294 294 // -given a model and (optionally) a view name if the view name is
295 295 // not given, it defaults to the model's default view attribute.
296 296
297 297 // TODO: this is hacky, and makes the view depend on this cell attribute and widget manager behavior
298 298 // it would be great to have the widget manager add the cell metadata
299 299 // to the subview without having to add it here.
300 300 options = $.extend({ parent: this }, options || {});
301 301 var child_view = this.model.widget_manager.create_view(child_model, options, this);
302 302
303 303 // Associate the view id with the model id.
304 304 if (this.child_model_views[child_model.id] === undefined) {
305 305 this.child_model_views[child_model.id] = [];
306 306 }
307 307 this.child_model_views[child_model.id].push(child_view.id);
308 308
309 309 // Remember the view by id.
310 310 this.child_views[child_view.id] = child_view;
311 311 return child_view;
312 312 },
313 313
314 314 pop_child_view: function(child_model) {
315 315 // Delete a child view that was previously created using create_child_view.
316 316 var view_ids = this.child_model_views[child_model.id];
317 317 if (view_ids !== undefined) {
318 318
319 319 // Only delete the first view in the list.
320 320 var view_id = view_ids[0];
321 321 var view = this.child_views[view_id];
322 322 delete this.child_views[view_id];
323 323 view_ids.splice(0,1);
324 324 child_model.views.pop(view);
325 325
326 326 // Remove the view list specific to this model if it is empty.
327 327 if (view_ids.length === 0) {
328 328 delete this.child_model_views[child_model.id];
329 329 }
330 330 return view;
331 331 }
332 332 return null;
333 333 },
334 334
335 335 do_diff: function(old_list, new_list, removed_callback, added_callback) {
336 336 // Difference a changed list and call remove and add callbacks for
337 337 // each removed and added item in the new list.
338 338 //
339 339 // Parameters
340 340 // ----------
341 341 // old_list : array
342 342 // new_list : array
343 343 // removed_callback : Callback(item)
344 344 // Callback that is called for each item removed.
345 345 // added_callback : Callback(item)
346 346 // Callback that is called for each item added.
347 347
348 348 // Walk the lists until an unequal entry is found.
349 349 var i;
350 350 for (i = 0; i < new_list.length; i++) {
351 351 if (i >= old_list.length || new_list[i] !== old_list[i]) {
352 352 break;
353 353 }
354 354 }
355 355
356 356 // Remove the non-matching items from the old list.
357 357 for (var j = i; j < old_list.length; j++) {
358 358 removed_callback(old_list[j]);
359 359 }
360 360
361 361 // Add the rest of the new list items.
362 362 for (; i < new_list.length; i++) {
363 363 added_callback(new_list[i]);
364 364 }
365 365 },
366 366
367 367 callbacks: function(){
368 368 // Create msg callbacks for a comm msg.
369 369 return this.model.callbacks(this);
370 370 },
371 371
372 372 render: function(){
373 373 // Render the view.
374 374 //
375 375 // By default, this is only called the first time the view is created
376 376 },
377 377
378 378 show: function(){
379 379 // Show the widget-area
380 380 if (this.options && this.options.cell &&
381 381 this.options.cell.widget_area !== undefined) {
382 382 this.options.cell.widget_area.show();
383 383 }
384 384 },
385 385
386 386 send: function (content) {
387 387 // Send a custom msg associated with this view.
388 388 this.model.send(content, this.callbacks());
389 389 },
390 390
391 391 touch: function () {
392 392 this.model.save_changes(this.callbacks());
393 393 },
394 394
395 395 after_displayed: function (callback, context) {
396 396 // Calls the callback right away is the view is already displayed
397 397 // otherwise, register the callback to the 'displayed' event.
398 398 if (this.is_displayed) {
399 399 callback.apply(context);
400 400 } else {
401 401 this.on('displayed', callback, context);
402 402 }
403 403 },
404 404 });
405 405
406 406
407 407 var DOMWidgetView = WidgetView.extend({
408 408 initialize: function (parameters) {
409 409 // Public constructor
410 410 DOMWidgetView.__super__.initialize.apply(this, [parameters]);
411 411 this.on('displayed', this.show, this);
412 412 this.model.on('change:visible', this.update_visible, this);
413 413 this.model.on('change:_css', this.update_css, this);
414 414
415 415 this.model.on('change:_dom_classes', function(model, new_classes) {
416 var old_classes = model.previous('children');
416 var old_classes = model.previous('_dom_classes');
417 417 this.update_classes(old_classes, new_classes);
418 418 }, this);
419 419
420 420 this.model.on('change:fore_color', function (model, value) {
421 421 this.update_attr('color', value); }, this);
422 422
423 423 this.model.on('change:back_color', function (model, value) {
424 424 this.update_attr('background', value); }, this);
425 425
426 426 this.model.on('change:width', function (model, value) {
427 427 this.update_attr('width', value); }, this);
428 428
429 429 this.model.on('change:height', function (model, value) {
430 430 this.update_attr('height', value); }, this);
431 431
432 432 this.model.on('change:border_color', function (model, value) {
433 433 this.update_attr('border-color', value); }, this);
434 434
435 435 this.model.on('change:border_width', function (model, value) {
436 436 this.update_attr('border-width', value); }, this);
437 437
438 438 this.model.on('change:border_style', function (model, value) {
439 439 this.update_attr('border-style', value); }, this);
440 440
441 441 this.model.on('change:font_style', function (model, value) {
442 442 this.update_attr('font-style', value); }, this);
443 443
444 444 this.model.on('change:font_weight', function (model, value) {
445 445 this.update_attr('font-weight', value); }, this);
446 446
447 447 this.model.on('change:font_size', function (model, value) {
448 448 this.update_attr('font-size', value); }, this);
449 449
450 450 this.model.on('change:font_family', function (model, value) {
451 451 this.update_attr('font-family', value); }, this);
452 452
453 453 this.model.on('change:padding', function (model, value) {
454 454 this.update_attr('padding', value); }, this);
455 455
456 456 this.model.on('change:margin', function (model, value) {
457 457 this.update_attr('margin', value); }, this);
458 458
459 459 this.after_displayed(function() {
460 460 this.update_visible(this.model, this.model.get("visible"));
461 461 this.update_css(this.model, this.model.get("_css"));
462 462
463 463 this.update_classes([], this.model.get('_dom_classes'));
464 464 this.update_attr('color', this.model.get('fore_color'));
465 465 this.update_attr('background', this.model.get('back_color'));
466 466 this.update_attr('width', this.model.get('width'));
467 467 this.update_attr('height', this.model.get('height'));
468 468 this.update_attr('border-color', this.model.get('border_color'));
469 469 this.update_attr('border-width', this.model.get('border_width'));
470 470 this.update_attr('border-style', this.model.get('border_style'));
471 471 this.update_attr('font-style', this.model.get('font_style'));
472 472 this.update_attr('font-weight', this.model.get('font_weight'));
473 473 this.update_attr('font-size', this.model.get('font_size'));
474 474 this.update_attr('font-family', this.model.get('font_family'));
475 475 this.update_attr('padding', this.model.get('padding'));
476 476 this.update_attr('margin', this.model.get('margin'));
477 477 }, this);
478 478 },
479 479
480 480 update_attr: function(name, value) {
481 481 // Set a css attr of the widget view.
482 482 this.$el.css(name, value);
483 483 },
484 484
485 485 update_visible: function(model, value) {
486 486 // Update visibility
487 487 this.$el.toggle(value);
488 488 },
489 489
490 490 update_css: function (model, css) {
491 491 // Update the css styling of this view.
492 492 var e = this.$el;
493 493 if (css === undefined) {return;}
494 494 for (var i = 0; i < css.length; i++) {
495 495 // Apply the css traits to all elements that match the selector.
496 496 var selector = css[i][0];
497 497 var elements = this._get_selector_element(selector);
498 498 if (elements.length > 0) {
499 499 var trait_key = css[i][1];
500 500 var trait_value = css[i][2];
501 501 elements.css(trait_key ,trait_value);
502 502 }
503 503 }
504 504 },
505 505
506 506 update_classes: function (old_classes, new_classes, $el) {
507 507 // Update the DOM classes applied to an element, default to this.$el.
508 508 if ($el===undefined) {
509 509 $el = this.$el;
510 510 }
511 511 this.do_diff(old_classes, new_classes, function(removed) {
512 512 $el.removeClass(removed);
513 513 }, function(added) {
514 514 $el.addClass(added);
515 515 });
516 516 },
517 517
518 518 update_mapped_classes: function(class_map, trait_name, previous_trait_value, $el) {
519 519 // Update the DOM classes applied to the widget based on a single
520 520 // trait's value.
521 521 //
522 522 // Given a trait value classes map, this function automatically
523 523 // handles applying the appropriate classes to the widget element
524 524 // and removing classes that are no longer valid.
525 525 //
526 526 // Parameters
527 527 // ----------
528 528 // class_map: dictionary
529 529 // Dictionary of trait values to class lists.
530 530 // Example:
531 531 // {
532 532 // success: ['alert', 'alert-success'],
533 533 // info: ['alert', 'alert-info'],
534 534 // warning: ['alert', 'alert-warning'],
535 535 // danger: ['alert', 'alert-danger']
536 536 // };
537 537 // trait_name: string
538 538 // Name of the trait to check the value of.
539 539 // previous_trait_value: optional string, default ''
540 540 // Last trait value
541 541 // $el: optional jQuery element handle, defaults to this.$el
542 542 // Element that the classes are applied to.
543 543 var key = previous_trait_value;
544 544 if (key === undefined) {
545 545 key = this.model.previous(trait_name);
546 546 }
547 547 var old_classes = class_map[key] ? class_map[key] : [];
548 548 key = this.model.get(trait_name);
549 549 var new_classes = class_map[key] ? class_map[key] : [];
550 550
551 551 this.update_classes(old_classes, new_classes, $el || this.$el);
552 552 },
553 553
554 554 _get_selector_element: function (selector) {
555 555 // Get the elements via the css selector.
556 556 var elements;
557 557 if (!selector) {
558 558 elements = this.$el;
559 559 } else {
560 560 elements = this.$el.find(selector).addBack(selector);
561 561 }
562 562 return elements;
563 563 },
564 564 });
565 565
566 566 var widget = {
567 567 'WidgetModel': WidgetModel,
568 568 'WidgetView': WidgetView,
569 569 'DOMWidgetView': DOMWidgetView,
570 570 };
571 571
572 572 // For backwards compatability.
573 573 $.extend(IPython, widget);
574 574
575 575 return widget;
576 576 });
@@ -1,80 +1,80
1 1 // Test container 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 container_index = this.append_cell(
10 10 'container = widgets.Box()\n' +
11 11 'button = widgets.Button()\n'+
12 12 'container.children = [button]\n'+
13 13 'display(container)\n'+
14 14 'container._dom_classes = ["my-test-class"]\n'+
15 15 'print("Success")\n');
16 16 this.execute_cell_then(container_index, function(index){
17 17
18 18 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
19 19 'Create container cell executed with correct output.');
20 20
21 21 this.test.assert(this.cell_element_exists(index,
22 22 '.widget-area .widget-subarea'),
23 23 'Widget subarea exists.');
24 24
25 25 this.test.assert(this.cell_element_exists(index,
26 26 '.widget-area .widget-subarea .widget-box'),
27 27 'Widget container exists.');
28 28
29 29 this.test.assert(this.cell_element_exists(index,
30 30 '.widget-area .widget-subarea .my-test-class'),
31 31 '_dom_classes works.');
32 32
33 33 this.test.assert(this.cell_element_exists(index,
34 34 '.widget-area .widget-subarea .my-test-class button'),
35 35 'Container parent/child relationship works.');
36 36 });
37 37
38 38 index = this.append_cell(
39 'container.set_css("float", "right")\n'+
39 'container.box_style = "success"\n'+
40 40 'print("Success")\n');
41 41 this.execute_cell_then(index, function(index){
42 42
43 43 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
44 'Set container class CSS cell executed with correct output.');
45
46 this.test.assert(this.cell_element_function(container_index,
47 '.widget-area .widget-subarea .my-test-class', 'css', ['float'])=='right',
48 'set_css works.');
44 'Set box_style cell executed with correct output.');
45
46 this.test.assert(this.cell_element_exists(container_index,
47 '.widget-box.alert-success'),
48 'Set box_style works.');
49 49 });
50 50
51 51 index = this.append_cell(
52 52 'container._dom_classes = []\n'+
53 53 'print("Success")\n');
54 54 this.execute_cell_then(index, function(index){
55 55
56 56 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
57 57 'Remove container class cell executed with correct output.');
58 58
59 59 this.test.assert(! this.cell_element_exists(container_index,
60 60 '.widget-area .widget-subarea .my-test-class'),
61 61 '_dom_classes can be used to remove a class.');
62 62 });
63 63
64 64 index = this.append_cell(
65 65 'display(button)\n'+
66 66 'print("Success")\n');
67 67 this.execute_cell_then(index, function(index){
68 68
69 69 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
70 70 'Display container child executed with correct output.');
71 71
72 72 this.test.assert(! this.cell_element_exists(index,
73 73 '.widget-area .widget-subarea .widget-box'),
74 74 'Parent container not displayed.');
75 75
76 76 this.test.assert(this.cell_element_exists(index,
77 77 '.widget-area .widget-subarea button'),
78 78 'Child displayed.');
79 79 });
80 80 }); No newline at end of file
@@ -1,100 +1,100
1 1 // Test widget float 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 float_text = {};
10 float_text.query = '.widget-area .widget-subarea .widget-hbox-single .my-second-float-text input';
10 float_text.query = '.widget-area .widget-subarea .my-second-float-text input';
11 11 float_text.index = this.append_cell(
12 12 'float_widget = widgets.FloatText()\n' +
13 13 'display(float_widget)\n' +
14 14 'float_widget._dom_classes = ["my-second-float-text"]\n' +
15 15 'print(float_widget.model_id)\n');
16 16 this.execute_cell_then(float_text.index, function(index){
17 17 float_text.model_id = this.get_output_cell(index).text.trim();
18 18
19 19 this.test.assert(this.cell_element_exists(index,
20 20 '.widget-area .widget-subarea'),
21 21 'Widget subarea exists.');
22 22
23 23 this.test.assert(this.cell_element_exists(index, float_text.query),
24 24 'Widget float textbox exists.');
25 25
26 26 this.cell_element_function(float_text.index, float_text.query, 'val', ['']);
27 27 this.sendKeys(float_text.query, '1.05');
28 28 });
29 29
30 30 this.wait_for_widget(float_text);
31 31
32 32 index = this.append_cell('print(float_widget.value)\n');
33 33 this.execute_cell_then(index, function(index){
34 34 this.test.assertEquals(this.get_output_cell(index).text, '1.05\n',
35 35 'Float textbox value set.');
36 36 this.cell_element_function(float_text.index, float_text.query, 'val', ['']);
37 37 this.sendKeys(float_text.query, '123456789.0');
38 38 });
39 39
40 40 this.wait_for_widget(float_text);
41 41
42 42 index = this.append_cell('print(float_widget.value)\n');
43 43 this.execute_cell_then(index, function(index){
44 44 this.test.assertEquals(this.get_output_cell(index).text, '123456789.0\n',
45 45 'Long float textbox value set (probably triggers throttling).');
46 46 this.cell_element_function(float_text.index, float_text.query, 'val', ['']);
47 47 this.sendKeys(float_text.query, '12hello');
48 48 });
49 49
50 50 this.wait_for_widget(float_text);
51 51
52 52 index = this.append_cell('print(float_widget.value)\n');
53 53 this.execute_cell_then(index, function(index){
54 54 this.test.assertEquals(this.get_output_cell(index).text, '12.0\n',
55 55 'Invald float textbox value caught and filtered.');
56 56 });
57 57
58 var float_text_query = '.widget-area .widget-subarea .widget-hbox-single .widget-numeric-text';
58 var float_text_query = '.widget-area .widget-subarea .widget-numeric-text';
59 59 var slider = {};
60 slider.query = '.widget-area .widget-subarea .widget-hbox-single .slider';
60 slider.query = '.widget-area .widget-subarea .slider';
61 61 slider.index = this.append_cell(
62 62 'floatrange = [widgets.BoundedFloatText(), \n' +
63 63 ' widgets.FloatSlider()]\n' +
64 64 '[display(floatrange[i]) for i in range(2)]\n' +
65 65 'print("Success")\n');
66 66 this.execute_cell_then(slider.index, function(index){
67 67
68 68 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
69 69 'Create float range cell executed with correct output.');
70 70
71 71 this.test.assert(this.cell_element_exists(index,
72 72 '.widget-area .widget-subarea'),
73 73 'Widget subarea exists.');
74 74
75 75 this.test.assert(this.cell_element_exists(index, slider.query),
76 76 'Widget slider exists.');
77 77
78 78 this.test.assert(this.cell_element_exists(index, float_text_query),
79 79 'Widget float textbox exists.');
80 80 });
81 81
82 82 index = this.append_cell(
83 83 'for widget in floatrange:\n' +
84 84 ' widget.max = 50.0\n' +
85 85 ' widget.min = -50.0\n' +
86 86 ' widget.value = 25.0\n' +
87 87 'print("Success")\n');
88 88 this.execute_cell_then(index, function(index){
89 89
90 90 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
91 91 'Float range properties cell executed with correct output.');
92 92
93 93 this.test.assert(this.cell_element_exists(slider.index, slider.query),
94 94 'Widget slider exists.');
95 95
96 96 this.test.assert(this.cell_element_function(slider.index, slider.query,
97 97 'slider', ['value']) == 25.0,
98 98 'Slider set to Python value.');
99 99 });
100 100 }); No newline at end of file
@@ -1,47 +1,44
1 1 // Test image 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 // Get the temporary directory that the test server is running in.
10 10 var cwd = '';
11 11 index = this.append_cell('!echo $(pwd)');
12 12 this.execute_cell_then(index, function(index){
13 13 cwd = this.get_output_cell(index).text.trim();
14 14 });
15 15
16 16 var test_jpg = '/9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wgARCAABAAEDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAAA//EABUBAQEAAAAAAAAAAAAAAAAAAAME/9oADAMBAAIQAxAAAAECv//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAQUCf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Bf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Bf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEABj8Cf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAT8hf//aAAwDAQACAAMAAAAQn//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Qf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Qf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAT8Qf//Z';
17 17
18 18 var image_index = this.append_cell(
19 19 'import base64\n' +
20 20 'data = base64.b64decode("' + test_jpg + '")\n' +
21 21 'image = widgets.Image()\n' +
22 22 'image.format = "jpeg"\n' +
23 23 'image.value = data\n' +
24 24 'image.width = "50px"\n' +
25 25 'image.height = "50px"\n' +
26 // Set css that will make the image render within the PhantomJS visible
27 // window. If we don't do this, the captured image will be black.
28 'image.set_css({"background": "blue", "z-index": "9999", "position": "fixed", "top": "0px", "left": "0px"})\n' +
29 26 'display(image)\n' +
30 27 'print("Success")\n');
31 28 this.execute_cell_then(image_index, function(index){
32 29
33 30 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
34 31 'Create image executed with correct output.');
35 32
36 33 this.test.assert(this.cell_element_exists(index,
37 34 '.widget-area .widget-subarea'),
38 35 'Widget subarea exists.');
39 36
40 37 var img_sel = '.widget-area .widget-subarea img';
41 38 this.test.assert(this.cell_element_exists(index, img_sel), 'Image exists.');
42 39
43 40 // Verify that the image's base64 data has made it into the DOM.
44 41 var img_src = this.cell_element_function(image_index, img_sel, 'attr', ['src']);
45 42 this.test.assert(img_src.indexOf(test_jpg) > -1, 'Image src data exists.');
46 43 });
47 44 }); No newline at end of file
@@ -1,157 +1,157
1 1 // Test widget int 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 int_text = {};
10 int_text.query = '.widget-area .widget-subarea .widget-hbox-single .my-second-int-text input';
10 int_text.query = '.widget-area .widget-subarea .my-second-int-text input';
11 11 int_text.index = this.append_cell(
12 12 'int_widget = widgets.IntText()\n' +
13 13 'display(int_widget)\n' +
14 14 'int_widget._dom_classes = ["my-second-int-text"]\n' +
15 15 'print(int_widget.model_id)\n');
16 16 this.execute_cell_then(int_text.index, function(index){
17 17 int_text.model_id = this.get_output_cell(index).text.trim();
18 18
19 19 this.test.assert(this.cell_element_exists(index,
20 20 '.widget-area .widget-subarea'),
21 21 'Widget subarea exists.');
22 22
23 23 this.test.assert(this.cell_element_exists(index, int_text.query),
24 24 'Widget int textbox exists.');
25 25
26 26 this.cell_element_function(int_text.index, int_text.query, 'val', ['']);
27 27 this.sendKeys(int_text.query, '1.05');
28 28 });
29 29
30 30 this.wait_for_widget(int_text);
31 31
32 32 index = this.append_cell('print(int_widget.value)\n');
33 33 this.execute_cell_then(index, function(index){
34 34 this.test.assertEquals(this.get_output_cell(index).text, '1\n',
35 35 'Int textbox value set.');
36 36 this.cell_element_function(int_text.index, int_text.query, 'val', ['']);
37 37 this.sendKeys(int_text.query, '123456789');
38 38 });
39 39
40 40 this.wait_for_widget(int_text);
41 41
42 42 index = this.append_cell('print(int_widget.value)\n');
43 43 this.execute_cell_then(index, function(index){
44 44 this.test.assertEquals(this.get_output_cell(index).text, '123456789\n',
45 45 'Long int textbox value set (probably triggers throttling).');
46 46 this.cell_element_function(int_text.index, int_text.query, 'val', ['']);
47 47 this.sendKeys(int_text.query, '12hello');
48 48 });
49 49
50 50 this.wait_for_widget(int_text);
51 51
52 52 index = this.append_cell('print(int_widget.value)\n');
53 53 this.execute_cell_then(index, function(index){
54 54 this.test.assertEquals(this.get_output_cell(index).text, '12\n',
55 55 'Invald int textbox value caught and filtered.');
56 56 });
57 57
58 58 index = this.append_cell(
59 59 'from IPython.html import widgets\n' +
60 60 'from IPython.display import display, clear_output\n' +
61 61 'print("Success")');
62 62 this.execute_cell_then(index);
63 63
64 64
65 var slider_query = '.widget-area .widget-subarea .widget-hbox-single .slider';
65 var slider_query = '.widget-area .widget-subarea .slider';
66 66 var int_text2 = {};
67 int_text2.query = '.widget-area .widget-subarea .widget-hbox-single .my-second-num-test-text input';
67 int_text2.query = '.widget-area .widget-subarea .my-second-num-test-text input';
68 68 int_text2.index = this.append_cell(
69 69 'intrange = [widgets.BoundedIntTextWidget(),\n' +
70 70 ' widgets.IntSliderWidget()]\n' +
71 71 '[display(intrange[i]) for i in range(2)]\n' +
72 72 'intrange[0]._dom_classes = ["my-second-num-test-text"]\n' +
73 73 'print(intrange[0].model_id)\n');
74 74 this.execute_cell_then(int_text2.index, function(index){
75 75 int_text2.model_id = this.get_output_cell(index).text.trim();
76 76
77 77 this.test.assert(this.cell_element_exists(index,
78 78 '.widget-area .widget-subarea'),
79 79 'Widget subarea exists.');
80 80
81 81 this.test.assert(this.cell_element_exists(index, slider_query),
82 82 'Widget slider exists.');
83 83
84 84 this.test.assert(this.cell_element_exists(index, int_text2.query),
85 85 'Widget int textbox exists.');
86 86 });
87 87
88 88 index = this.append_cell(
89 89 'for widget in intrange:\n' +
90 90 ' widget.max = 50\n' +
91 91 ' widget.min = -50\n' +
92 92 ' widget.value = 25\n' +
93 93 'print("Success")\n');
94 94 this.execute_cell_then(index, function(index){
95 95
96 96 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
97 97 'Int range properties cell executed with correct output.');
98 98
99 99 this.test.assert(this.cell_element_exists(int_text2.index, slider_query),
100 100 'Widget slider exists.');
101 101
102 102 this.test.assert(this.cell_element_function(int_text2.index, slider_query,
103 103 'slider', ['value']) == 25,
104 104 'Slider set to Python value.');
105 105
106 106 this.test.assert(this.cell_element_function(int_text2.index, int_text2.query,
107 107 'val') == 25, 'Int textbox set to Python value.');
108 108
109 109 // Clear the int textbox value and then set it to 1 by emulating
110 110 // keyboard presses.
111 111 this.evaluate(function(q){
112 112 var textbox = IPython.notebook.element.find(q);
113 113 textbox.val('1');
114 114 textbox.trigger('keyup');
115 115 }, {q: int_text2.query});
116 116 });
117 117
118 118 this.wait_for_widget(int_text2);
119 119
120 120 index = this.append_cell('print(intrange[0].value)\n');
121 121 this.execute_cell_then(index, function(index){
122 122 this.test.assertEquals(this.get_output_cell(index).text, '1\n',
123 123 'Int textbox set int range value');
124 124
125 125 // Clear the int textbox value and then set it to 120 by emulating
126 126 // keyboard presses.
127 127 this.evaluate(function(q){
128 128 var textbox = IPython.notebook.element.find(q);
129 129 textbox.val('120');
130 130 textbox.trigger('keyup');
131 131 }, {q: int_text2.query});
132 132 });
133 133
134 134 this.wait_for_widget(int_text2);
135 135
136 136 index = this.append_cell('print(intrange[0].value)\n');
137 137 this.execute_cell_then(index, function(index){
138 138 this.test.assertEquals(this.get_output_cell(index).text, '50\n',
139 139 'Int textbox value bound');
140 140
141 141 // Clear the int textbox value and then set it to 'hello world' by
142 142 // emulating keyboard presses. 'hello world' should get filtered...
143 143 this.evaluate(function(q){
144 144 var textbox = IPython.notebook.element.find(q);
145 145 textbox.val('hello world');
146 146 textbox.trigger('keyup');
147 147 }, {q: int_text2.query});
148 148 });
149 149
150 150 this.wait_for_widget(int_text2);
151 151
152 152 index = this.append_cell('print(intrange[0].value)\n');
153 153 this.execute_cell_then(index, function(index){
154 154 this.test.assertEquals(this.get_output_cell(index).text, '50\n',
155 155 'Invalid int textbox characters ignored');
156 156 });
157 157 }); No newline at end of file
@@ -1,177 +1,178
1 1 """Float class.
2 2
3 3 Represents an unbounded float using a widget.
4 4 """
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (c) 2013, the IPython Development Team.
7 7 #
8 8 # Distributed under the terms of the Modified BSD License.
9 9 #
10 10 # The full license is in the file COPYING.txt, distributed with this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from .widget import DOMWidget
17 17 from IPython.utils.traitlets import Unicode, CFloat, Bool, CaselessStrEnum, Tuple
18 18 from IPython.utils.warn import DeprecatedClass
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Classes
22 22 #-----------------------------------------------------------------------------
23 23 class _Float(DOMWidget):
24 24 value = CFloat(0.0, help="Float value", sync=True)
25 25 disabled = Bool(False, help="Enable or disable user changes", sync=True)
26 26 description = Unicode(help="Description of the value this widget represents", sync=True)
27 27
28 28
29 29 class _BoundedFloat(_Float):
30 30 max = CFloat(100.0, help="Max value", sync=True)
31 31 min = CFloat(0.0, help="Min value", sync=True)
32 32 step = CFloat(0.1, help="Minimum step that the value can take (ignored by some views)", sync=True)
33 33
34 34 def __init__(self, *pargs, **kwargs):
35 35 """Constructor"""
36 36 DOMWidget.__init__(self, *pargs, **kwargs)
37 37 self._validate('value', None, self.value)
38 38 self.on_trait_change(self._validate, ['value', 'min', 'max'])
39 39
40 40 def _validate(self, name, old, new):
41 41 """Validate value, max, min."""
42 42 if self.min > new or new > self.max:
43 43 self.value = min(max(new, self.min), self.max)
44 44
45 45
46 46 class FloatText(_Float):
47 47 _view_name = Unicode('FloatTextView', sync=True)
48 48
49 49
50 50 class BoundedFloatText(_BoundedFloat):
51 51 _view_name = Unicode('FloatTextView', sync=True)
52 52
53 53
54 54 class FloatSlider(_BoundedFloat):
55 55 _view_name = Unicode('FloatSliderView', sync=True)
56 56 orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
57 57 default_value='horizontal',
58 58 help="Vertical or horizontal.", allow_none=False, sync=True)
59 59 _range = Bool(False, help="Display a range selector", sync=True)
60 60 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
61 61 slider_color = Unicode(sync=True)
62 62
63 63
64 64 class FloatProgress(_BoundedFloat):
65 65 _view_name = Unicode('ProgressView', sync=True)
66 66
67 67 bar_style = CaselessStrEnum(
68 68 values=['success', 'info', 'warning', 'danger', ''],
69 69 default_value='', allow_none=True, sync=True, help="""Use a
70 70 predefined styling for the progess bar.""")
71 71
72 72 class _FloatRange(_Float):
73 73 value = Tuple(CFloat, CFloat, default_value=(0.0, 1.0), help="Tuple of (lower, upper) bounds", sync=True)
74 74 lower = CFloat(0.0, help="Lower bound", sync=False)
75 75 upper = CFloat(1.0, help="Upper bound", sync=False)
76 76
77 77 def __init__(self, *pargs, **kwargs):
78 78 value_given = 'value' in kwargs
79 79 lower_given = 'lower' in kwargs
80 80 upper_given = 'upper' in kwargs
81 81 if value_given and (lower_given or upper_given):
82 82 raise ValueError("Cannot specify both 'value' and 'lower'/'upper' for range widget")
83 83 if lower_given != upper_given:
84 84 raise ValueError("Must specify both 'lower' and 'upper' for range widget")
85 85
86 86 DOMWidget.__init__(self, *pargs, **kwargs)
87 87
88 88 # ensure the traits match, preferring whichever (if any) was given in kwargs
89 89 if value_given:
90 90 self.lower, self.upper = self.value
91 91 else:
92 92 self.value = (self.lower, self.upper)
93 93
94 94 self.on_trait_change(self._validate, ['value', 'upper', 'lower'])
95 95
96 96 def _validate(self, name, old, new):
97 97 if name == 'value':
98 98 self.lower, self.upper = min(new), max(new)
99 99 elif name == 'lower':
100 100 self.value = (new, self.value[1])
101 101 elif name == 'upper':
102 102 self.value = (self.value[0], new)
103 103
104 104 class _BoundedFloatRange(_FloatRange):
105 105 step = CFloat(1.0, help="Minimum step that the value can take (ignored by some views)", sync=True)
106 106 max = CFloat(100.0, help="Max value", sync=True)
107 107 min = CFloat(0.0, help="Min value", sync=True)
108 108
109 109 def __init__(self, *pargs, **kwargs):
110 110 any_value_given = 'value' in kwargs or 'upper' in kwargs or 'lower' in kwargs
111 111 _FloatRange.__init__(self, *pargs, **kwargs)
112 112
113 113 # ensure a minimal amount of sanity
114 114 if self.min > self.max:
115 115 raise ValueError("min must be <= max")
116 116
117 117 if any_value_given:
118 118 # if a value was given, clamp it within (min, max)
119 119 self._validate("value", None, self.value)
120 120 else:
121 121 # otherwise, set it to 25-75% to avoid the handles overlapping
122 122 self.value = (0.75*self.min + 0.25*self.max,
123 123 0.25*self.min + 0.75*self.max)
124 124 # callback already set for 'value', 'lower', 'upper'
125 125 self.on_trait_change(self._validate, ['min', 'max'])
126 126
127 127
128 128 def _validate(self, name, old, new):
129 129 if name == "min":
130 130 if new > self.max:
131 131 raise ValueError("setting min > max")
132 132 self.min = new
133 133 elif name == "max":
134 134 if new < self.min:
135 135 raise ValueError("setting max < min")
136 136 self.max = new
137 137
138 138 low, high = self.value
139 139 if name == "value":
140 140 low, high = min(new), max(new)
141 141 elif name == "upper":
142 142 if new < self.lower:
143 143 raise ValueError("setting upper < lower")
144 144 high = new
145 145 elif name == "lower":
146 146 if new > self.upper:
147 147 raise ValueError("setting lower > upper")
148 148 low = new
149 149
150 150 low = max(self.min, min(low, self.max))
151 151 high = min(self.max, max(high, self.min))
152 152
153 153 # determine the order in which we should update the
154 154 # lower, upper traits to avoid a temporary inverted overlap
155 155 lower_first = high < self.lower
156 156
157 157 self.value = (low, high)
158 158 if lower_first:
159 159 self.lower = low
160 160 self.upper = high
161 161 else:
162 162 self.upper = high
163 163 self.lower = low
164 164
165 165
166 166 class FloatRangeSlider(_BoundedFloatRange):
167 167 _view_name = Unicode('FloatSliderView', sync=True)
168 orientation = Enum([u'horizontal', u'vertical'], u'horizontal',
168 orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
169 default_value='horizontal', allow_none=False,
169 170 help="Vertical or horizontal.", sync=True)
170 171 _range = Bool(True, help="Display a range selector", sync=True)
171 172 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
172 173
173 174 # Remove in IPython 4.0
174 175 FloatTextWidget = DeprecatedClass(FloatText, 'FloatTextWidget')
175 176 BoundedFloatTextWidget = DeprecatedClass(BoundedFloatText, 'BoundedFloatTextWidget')
176 177 FloatSliderWidget = DeprecatedClass(FloatSlider, 'FloatSliderWidget')
177 178 FloatProgressWidget = DeprecatedClass(FloatProgress, 'FloatProgressWidget')
@@ -1,181 +1,182
1 1 """Int class.
2 2
3 3 Represents an unbounded int using a widget.
4 4 """
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (c) 2013, the IPython Development Team.
7 7 #
8 8 # Distributed under the terms of the Modified BSD License.
9 9 #
10 10 # The full license is in the file COPYING.txt, distributed with this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from .widget import DOMWidget
17 17 from IPython.utils.traitlets import Unicode, CInt, Bool, CaselessStrEnum, Tuple
18 18 from IPython.utils.warn import DeprecatedClass
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Classes
22 22 #-----------------------------------------------------------------------------
23 23 class _Int(DOMWidget):
24 24 """Base class used to create widgets that represent an int."""
25 25 value = CInt(0, help="Int value", sync=True)
26 26 disabled = Bool(False, help="Enable or disable user changes", sync=True)
27 27 description = Unicode(help="Description of the value this widget represents", sync=True)
28 28
29 29
30 30 class _BoundedInt(_Int):
31 31 """Base class used to create widgets that represent a int that is bounded
32 32 by a minium and maximum."""
33 33 step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True)
34 34 max = CInt(100, help="Max value", sync=True)
35 35 min = CInt(0, help="Min value", sync=True)
36 36
37 37 def __init__(self, *pargs, **kwargs):
38 38 """Constructor"""
39 39 DOMWidget.__init__(self, *pargs, **kwargs)
40 40 self.on_trait_change(self._validate, ['value', 'min', 'max'])
41 41
42 42 def _validate(self, name, old, new):
43 43 """Validate value, max, min."""
44 44 if self.min > new or new > self.max:
45 45 self.value = min(max(new, self.min), self.max)
46 46
47 47
48 48 class IntText(_Int):
49 49 """Textbox widget that represents a int."""
50 50 _view_name = Unicode('IntTextView', sync=True)
51 51
52 52
53 53 class BoundedIntText(_BoundedInt):
54 54 """Textbox widget that represents a int bounded by a minimum and maximum value."""
55 55 _view_name = Unicode('IntTextView', sync=True)
56 56
57 57
58 58 class IntSlider(_BoundedInt):
59 59 """Slider widget that represents a int bounded by a minimum and maximum value."""
60 60 _view_name = Unicode('IntSliderView', sync=True)
61 61 orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
62 62 default_value='horizontal', allow_none=False,
63 63 help="Vertical or horizontal.", sync=True)
64 64 _range = Bool(False, help="Display a range selector", sync=True)
65 65 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
66 66 slider_color = Unicode(sync=True)
67 67
68 68
69 69 class IntProgress(_BoundedInt):
70 70 """Progress bar that represents a int bounded by a minimum and maximum value."""
71 71 _view_name = Unicode('ProgressView', sync=True)
72 72
73 73 bar_style = CaselessStrEnum(
74 74 values=['success', 'info', 'warning', 'danger', ''],
75 75 default_value='', allow_none=True, sync=True, help="""Use a
76 76 predefined styling for the progess bar.""")
77 77
78 78 class _IntRange(_Int):
79 79 value = Tuple(CInt, CInt, default_value=(0, 1), help="Tuple of (lower, upper) bounds", sync=True)
80 80 lower = CInt(0, help="Lower bound", sync=False)
81 81 upper = CInt(1, help="Upper bound", sync=False)
82 82
83 83 def __init__(self, *pargs, **kwargs):
84 84 value_given = 'value' in kwargs
85 85 lower_given = 'lower' in kwargs
86 86 upper_given = 'upper' in kwargs
87 87 if value_given and (lower_given or upper_given):
88 88 raise ValueError("Cannot specify both 'value' and 'lower'/'upper' for range widget")
89 89 if lower_given != upper_given:
90 90 raise ValueError("Must specify both 'lower' and 'upper' for range widget")
91 91
92 92 DOMWidget.__init__(self, *pargs, **kwargs)
93 93
94 94 # ensure the traits match, preferring whichever (if any) was given in kwargs
95 95 if value_given:
96 96 self.lower, self.upper = self.value
97 97 else:
98 98 self.value = (self.lower, self.upper)
99 99
100 100 self.on_trait_change(self._validate, ['value', 'upper', 'lower'])
101 101
102 102 def _validate(self, name, old, new):
103 103 if name == 'value':
104 104 self.lower, self.upper = min(new), max(new)
105 105 elif name == 'lower':
106 106 self.value = (new, self.value[1])
107 107 elif name == 'upper':
108 108 self.value = (self.value[0], new)
109 109
110 110 class _BoundedIntRange(_IntRange):
111 111 step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True)
112 112 max = CInt(100, help="Max value", sync=True)
113 113 min = CInt(0, help="Min value", sync=True)
114 114
115 115 def __init__(self, *pargs, **kwargs):
116 116 any_value_given = 'value' in kwargs or 'upper' in kwargs or 'lower' in kwargs
117 117 _IntRange.__init__(self, *pargs, **kwargs)
118 118
119 119 # ensure a minimal amount of sanity
120 120 if self.min > self.max:
121 121 raise ValueError("min must be <= max")
122 122
123 123 if any_value_given:
124 124 # if a value was given, clamp it within (min, max)
125 125 self._validate("value", None, self.value)
126 126 else:
127 127 # otherwise, set it to 25-75% to avoid the handles overlapping
128 128 self.value = (0.75*self.min + 0.25*self.max,
129 129 0.25*self.min + 0.75*self.max)
130 130 # callback already set for 'value', 'lower', 'upper'
131 131 self.on_trait_change(self._validate, ['min', 'max'])
132 132
133 133 def _validate(self, name, old, new):
134 134 if name == "min":
135 135 if new > self.max:
136 136 raise ValueError("setting min > max")
137 137 self.min = new
138 138 elif name == "max":
139 139 if new < self.min:
140 140 raise ValueError("setting max < min")
141 141 self.max = new
142 142
143 143 low, high = self.value
144 144 if name == "value":
145 145 low, high = min(new), max(new)
146 146 elif name == "upper":
147 147 if new < self.lower:
148 148 raise ValueError("setting upper < lower")
149 149 high = new
150 150 elif name == "lower":
151 151 if new > self.upper:
152 152 raise ValueError("setting lower > upper")
153 153 low = new
154 154
155 155 low = max(self.min, min(low, self.max))
156 156 high = min(self.max, max(high, self.min))
157 157
158 158 # determine the order in which we should update the
159 159 # lower, upper traits to avoid a temporary inverted overlap
160 160 lower_first = high < self.lower
161 161
162 162 self.value = (low, high)
163 163 if lower_first:
164 164 self.lower = low
165 165 self.upper = high
166 166 else:
167 167 self.upper = high
168 168 self.lower = low
169 169
170 170 class IntRangeSlider(_BoundedIntRange):
171 171 _view_name = Unicode('IntSliderView', sync=True)
172 orientation = Enum([u'horizontal', u'vertical'], u'horizontal',
172 orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
173 default_value='horizontal', allow_none=False,
173 174 help="Vertical or horizontal.", sync=True)
174 175 _range = Bool(True, help="Display a range selector", sync=True)
175 176 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
176 177
177 178 # Remove in IPython 4.0
178 179 IntTextWidget = DeprecatedClass(IntText, 'IntTextWidget')
179 180 BoundedIntTextWidget = DeprecatedClass(BoundedIntText, 'BoundedIntTextWidget')
180 181 IntSliderWidget = DeprecatedClass(IntSlider, 'IntSliderWidget')
181 182 IntProgressWidget = DeprecatedClass(IntProgress, 'IntProgressWidget')
General Comments 0
You need to be logged in to leave comments. Login now