##// END OF EJS Templates
Use .load() to resize
jon -
Show More
@@ -1,882 +1,901
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // OutputArea
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule OutputArea
16 16 */
17 17 var IPython = (function (IPython) {
18 18 "use strict";
19 19
20 20 var utils = IPython.utils;
21 21
22 22 /**
23 23 * @class OutputArea
24 24 *
25 25 * @constructor
26 26 */
27 27
28 28 var OutputArea = function (selector, prompt_area) {
29 29 this.selector = selector;
30 30 this.wrapper = $(selector);
31 31 this.outputs = [];
32 32 this.collapsed = false;
33 33 this.scrolled = false;
34 34 this.trusted = true;
35 35 this.clear_queued = null;
36 36 if (prompt_area === undefined) {
37 37 this.prompt_area = true;
38 38 } else {
39 39 this.prompt_area = prompt_area;
40 40 }
41 41 this.create_elements();
42 42 this.style();
43 43 this.bind_events();
44 44 };
45 45
46 46
47 47 /**
48 48 * Class prototypes
49 49 **/
50 50
51 51 OutputArea.prototype.create_elements = function () {
52 52 this.element = $("<div/>");
53 53 this.collapse_button = $("<div/>");
54 54 this.prompt_overlay = $("<div/>");
55 55 this.wrapper.append(this.prompt_overlay);
56 56 this.wrapper.append(this.element);
57 57 this.wrapper.append(this.collapse_button);
58 58 };
59 59
60 60
61 61 OutputArea.prototype.style = function () {
62 62 this.collapse_button.hide();
63 63 this.prompt_overlay.hide();
64 64
65 65 this.wrapper.addClass('output_wrapper');
66 66 this.element.addClass('output');
67 67
68 68 this.collapse_button.addClass("btn output_collapsed");
69 69 this.collapse_button.attr('title', 'click to expand output');
70 70 this.collapse_button.text('. . .');
71 71
72 72 this.prompt_overlay.addClass('out_prompt_overlay prompt');
73 73 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
74 74
75 75 this.collapse();
76 76 };
77 77
78 78 /**
79 79 * Should the OutputArea scroll?
80 80 * Returns whether the height (in lines) exceeds a threshold.
81 81 *
82 82 * @private
83 83 * @method _should_scroll
84 84 * @param [lines=100]{Integer}
85 85 * @return {Bool}
86 86 *
87 87 */
88 88 OutputArea.prototype._should_scroll = function (lines) {
89 89 if (lines <=0 ){ return }
90 90 if (!lines) {
91 91 lines = 100;
92 92 }
93 93 // line-height from http://stackoverflow.com/questions/1185151
94 94 var fontSize = this.element.css('font-size');
95 95 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
96 96
97 97 return (this.element.height() > lines * lineHeight);
98 98 };
99 99
100 100
101 101 OutputArea.prototype.bind_events = function () {
102 102 var that = this;
103 103 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
104 104 this.prompt_overlay.click(function () { that.toggle_scroll(); });
105 105
106 106 this.element.resize(function () {
107 107 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
108 108 if ( IPython.utils.browser[0] === "Firefox" ) {
109 109 return;
110 110 }
111 111 // maybe scroll output,
112 112 // if it's grown large enough and hasn't already been scrolled.
113 113 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
114 114 that.scroll_area();
115 115 }
116 116 });
117 117 this.collapse_button.click(function () {
118 118 that.expand();
119 119 });
120 120 };
121 121
122 122
123 123 OutputArea.prototype.collapse = function () {
124 124 if (!this.collapsed) {
125 125 this.element.hide();
126 126 this.prompt_overlay.hide();
127 127 if (this.element.html()){
128 128 this.collapse_button.show();
129 129 }
130 130 this.collapsed = true;
131 131 }
132 132 };
133 133
134 134
135 135 OutputArea.prototype.expand = function () {
136 136 if (this.collapsed) {
137 137 this.collapse_button.hide();
138 138 this.element.show();
139 139 this.prompt_overlay.show();
140 140 this.collapsed = false;
141 141 }
142 142 };
143 143
144 144
145 145 OutputArea.prototype.toggle_output = function () {
146 146 if (this.collapsed) {
147 147 this.expand();
148 148 } else {
149 149 this.collapse();
150 150 }
151 151 };
152 152
153 153
154 154 OutputArea.prototype.scroll_area = function () {
155 155 this.element.addClass('output_scroll');
156 156 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
157 157 this.scrolled = true;
158 158 };
159 159
160 160
161 161 OutputArea.prototype.unscroll_area = function () {
162 162 this.element.removeClass('output_scroll');
163 163 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
164 164 this.scrolled = false;
165 165 };
166 166
167 167 /**
168 168 *
169 169 * Scroll OutputArea if height supperior than a threshold (in lines).
170 170 *
171 171 * Threshold is a maximum number of lines. If unspecified, defaults to
172 172 * OutputArea.minimum_scroll_threshold.
173 173 *
174 174 * Negative threshold will prevent the OutputArea from ever scrolling.
175 175 *
176 176 * @method scroll_if_long
177 177 *
178 178 * @param [lines=20]{Number} Default to 20 if not set,
179 179 * behavior undefined for value of `0`.
180 180 *
181 181 **/
182 182 OutputArea.prototype.scroll_if_long = function (lines) {
183 183 var n = lines | OutputArea.minimum_scroll_threshold;
184 184 if(n <= 0){
185 185 return
186 186 }
187 187
188 188 if (this._should_scroll(n)) {
189 189 // only allow scrolling long-enough output
190 190 this.scroll_area();
191 191 }
192 192 };
193 193
194 194
195 195 OutputArea.prototype.toggle_scroll = function () {
196 196 if (this.scrolled) {
197 197 this.unscroll_area();
198 198 } else {
199 199 // only allow scrolling long-enough output
200 200 this.scroll_if_long();
201 201 }
202 202 };
203 203
204 204
205 205 // typeset with MathJax if MathJax is available
206 206 OutputArea.prototype.typeset = function () {
207 207 if (window.MathJax){
208 208 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
209 209 }
210 210 };
211 211
212 212
213 213 OutputArea.prototype.handle_output = function (msg) {
214 214 var json = {};
215 215 var msg_type = json.output_type = msg.header.msg_type;
216 216 var content = msg.content;
217 217 if (msg_type === "stream") {
218 218 json.text = content.data;
219 219 json.stream = content.name;
220 220 } else if (msg_type === "display_data") {
221 221 json = content.data;
222 222 json.output_type = msg_type;
223 223 json.metadata = content.metadata;
224 224 } else if (msg_type === "pyout") {
225 225 json = content.data;
226 226 json.output_type = msg_type;
227 227 json.metadata = content.metadata;
228 228 json.prompt_number = content.execution_count;
229 229 } else if (msg_type === "pyerr") {
230 230 json.ename = content.ename;
231 231 json.evalue = content.evalue;
232 232 json.traceback = content.traceback;
233 233 }
234 234 this.append_output(json);
235 235 };
236 236
237 237
238 238 OutputArea.prototype.rename_keys = function (data, key_map) {
239 239 var remapped = {};
240 240 for (var key in data) {
241 241 var new_key = key_map[key] || key;
242 242 remapped[new_key] = data[key];
243 243 }
244 244 return remapped;
245 245 };
246 246
247 247
248 248 OutputArea.output_types = [
249 249 'application/javascript',
250 250 'text/html',
251 251 'text/latex',
252 252 'image/svg+xml',
253 253 'image/png',
254 254 'image/jpeg',
255 255 'application/pdf',
256 256 'text/plain'
257 257 ];
258 258
259 259 OutputArea.prototype.validate_output = function (json) {
260 260 // scrub invalid outputs
261 261 // TODO: right now everything is a string, but JSON really shouldn't be.
262 262 // nbformat 4 will fix that.
263 263 $.map(OutputArea.output_types, function(key){
264 264 if (json[key] !== undefined && typeof json[key] !== 'string') {
265 265 console.log("Invalid type for " + key, json[key]);
266 266 delete json[key];
267 267 }
268 268 });
269 269 return json;
270 270 };
271 271
272 272 OutputArea.prototype.append_output = function (json) {
273 273 this.expand();
274 274 // Clear the output if clear is queued.
275 275 var needs_height_reset = false;
276 276 if (this.clear_queued) {
277 277 this.clear_output(false);
278 278 needs_height_reset = true;
279 279 }
280 280
281 // We must release the animation fixed height in a callback since Gecko
282 // (FireFox) doesn't render the image immediately as the data is
283 // available.
284 var that = this;
285 var handle_appended = function () {
286 // Only reset the height to automatic if the height is currently
287 // fixed (done by wait=True flag on clear_output).
288 console.log('appended');
289 if (needs_height_reset) {
290 that.element.height('');
291 }
292 that.element.trigger('resize');
293 };
294
281 295 // validate output data types
282 296 json = this.validate_output(json);
283 297
284 298 if (json.output_type === 'pyout') {
285 299 this.append_pyout(json);
286 300 } else if (json.output_type === 'pyerr') {
287 301 this.append_pyerr(json);
288 } else if (json.output_type === 'display_data') {
289 this.append_display_data(json);
290 302 } else if (json.output_type === 'stream') {
291 303 this.append_stream(json);
292 304 }
293 305
306 if (json.output_type === 'display_data') {
307 this.append_display_data(json, handle_appended);
308 } else {
309 handle_appended();
310 }
311
294 312 this.outputs.push(json);
295 313
296 // We must release the animation fixed height in a timeout since Gecko
297 // (FireFox) doesn't render the image immediately as the data is
298 // available.
299 var that = this;
300 setTimeout(function(){
301 // Only reset the height to automatic if the height is currently
302 // fixed (done by wait=True flag on clear_output).
303 if (needs_height_reset) {
304 that.element.height('');
305 }
306 that.element.trigger('resize');
307 }, 250);
308 314 };
309 315
310 316
311 317 OutputArea.prototype.create_output_area = function () {
312 318 var oa = $("<div/>").addClass("output_area");
313 319 if (this.prompt_area) {
314 320 oa.append($('<div/>').addClass('prompt'));
315 321 }
316 322 return oa;
317 323 };
318 324
319 325
320 326 function _get_metadata_key(metadata, key, mime) {
321 327 var mime_md = metadata[mime];
322 328 // mime-specific higher priority
323 329 if (mime_md && mime_md[key] !== undefined) {
324 330 return mime_md[key];
325 331 }
326 332 // fallback on global
327 333 return metadata[key];
328 334 }
329 335
330 336 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
331 337 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
332 338 if (_get_metadata_key(md, 'isolated', mime)) {
333 339 // Create an iframe to isolate the subarea from the rest of the
334 340 // document
335 341 var iframe = $('<iframe/>').addClass('box-flex1');
336 342 iframe.css({'height':1, 'width':'100%', 'display':'block'});
337 343 iframe.attr('frameborder', 0);
338 344 iframe.attr('scrolling', 'auto');
339 345
340 346 // Once the iframe is loaded, the subarea is dynamically inserted
341 347 iframe.on('load', function() {
342 348 // Workaround needed by Firefox, to properly render svg inside
343 349 // iframes, see http://stackoverflow.com/questions/10177190/
344 350 // svg-dynamically-added-to-iframe-does-not-render-correctly
345 351 this.contentDocument.open();
346 352
347 353 // Insert the subarea into the iframe
348 354 // We must directly write the html. When using Jquery's append
349 355 // method, javascript is evaluated in the parent document and
350 356 // not in the iframe document. At this point, subarea doesn't
351 357 // contain any user content.
352 358 this.contentDocument.write(subarea.html());
353 359
354 360 this.contentDocument.close();
355 361
356 362 var body = this.contentDocument.body;
357 363 // Adjust the iframe height automatically
358 364 iframe.height(body.scrollHeight + 'px');
359 365 });
360 366
361 367 // Elements should be appended to the inner subarea and not to the
362 368 // iframe
363 369 iframe.append = function(that) {
364 370 subarea.append(that);
365 371 };
366 372
367 373 return iframe;
368 374 } else {
369 375 return subarea;
370 376 }
371 377 }
372 378
373 379
374 380 OutputArea.prototype._append_javascript_error = function (err, element) {
375 381 // display a message when a javascript error occurs in display output
376 382 var msg = "Javascript error adding output!"
377 383 if ( element === undefined ) return;
378 384 element
379 385 .append($('<div/>').text(msg).addClass('js-error'))
380 386 .append($('<div/>').text(err.toString()).addClass('js-error'))
381 387 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
382 388 };
383 389
384 390 OutputArea.prototype._safe_append = function (toinsert) {
385 391 // safely append an item to the document
386 392 // this is an object created by user code,
387 393 // and may have errors, which should not be raised
388 394 // under any circumstances.
389 395 try {
390 396 this.element.append(toinsert);
391 397 } catch(err) {
392 398 console.log(err);
393 399 // Create an actual output_area and output_subarea, which creates
394 400 // the prompt area and the proper indentation.
395 401 var toinsert = this.create_output_area();
396 402 var subarea = $('<div/>').addClass('output_subarea');
397 403 toinsert.append(subarea);
398 404 this._append_javascript_error(err, subarea);
399 405 this.element.append(toinsert);
400 406 }
401 407 };
402 408
403 409
404 410 OutputArea.prototype.append_pyout = function (json) {
405 411 var n = json.prompt_number || ' ';
406 412 var toinsert = this.create_output_area();
407 413 if (this.prompt_area) {
408 414 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
409 415 }
410 416 var inserted = this.append_mime_type(json, toinsert);
411 417 if (inserted) {
412 418 inserted.addClass('output_pyout');
413 419 }
414 420 this._safe_append(toinsert);
415 421 // If we just output latex, typeset it.
416 422 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
417 423 this.typeset();
418 424 }
419 425 };
420 426
421 427
422 428 OutputArea.prototype.append_pyerr = function (json) {
423 429 var tb = json.traceback;
424 430 if (tb !== undefined && tb.length > 0) {
425 431 var s = '';
426 432 var len = tb.length;
427 433 for (var i=0; i<len; i++) {
428 434 s = s + tb[i] + '\n';
429 435 }
430 436 s = s + '\n';
431 437 var toinsert = this.create_output_area();
432 438 var append_text = OutputArea.append_map['text/plain'];
433 439 if (append_text) {
434 440 append_text.apply(this, [s, {}, toinsert]).addClass('output_pyerr');
435 441 }
436 442 this._safe_append(toinsert);
437 443 }
438 444 };
439 445
440 446
441 447 OutputArea.prototype.append_stream = function (json) {
442 448 // temporary fix: if stream undefined (json file written prior to this patch),
443 449 // default to most likely stdout:
444 450 if (json.stream === undefined){
445 451 json.stream = 'stdout';
446 452 }
447 453 var text = json.text;
448 454 var subclass = "output_"+json.stream;
449 455 if (this.outputs.length > 0){
450 456 // have at least one output to consider
451 457 var last = this.outputs[this.outputs.length-1];
452 458 if (last.output_type == 'stream' && json.stream == last.stream){
453 459 // latest output was in the same stream,
454 460 // so append directly into its pre tag
455 461 // escape ANSI & HTML specials:
456 462 var pre = this.element.find('div.'+subclass).last().find('pre');
457 463 var html = utils.fixCarriageReturn(
458 464 pre.html() + utils.fixConsole(text));
459 465 // The only user content injected with this HTML call is
460 466 // escaped by the fixConsole() method.
461 467 pre.html(html);
462 468 return;
463 469 }
464 470 }
465 471
466 472 if (!text.replace("\r", "")) {
467 473 // text is nothing (empty string, \r, etc.)
468 474 // so don't append any elements, which might add undesirable space
469 475 return;
470 476 }
471 477
472 478 // If we got here, attach a new div
473 479 var toinsert = this.create_output_area();
474 480 var append_text = OutputArea.append_map['text/plain'];
475 481 if (append_text) {
476 482 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
477 483 }
478 484 this._safe_append(toinsert);
479 485 };
480 486
481 487
482 OutputArea.prototype.append_display_data = function (json) {
488 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
483 489 var toinsert = this.create_output_area();
484 if (this.append_mime_type(json, toinsert)) {
490 if (this.append_mime_type(json, toinsert, handle_inserted)) {
485 491 this._safe_append(toinsert);
486 492 // If we just output latex, typeset it.
487 493 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
488 494 this.typeset();
489 495 }
490 496 }
491 497 };
492 498
493 499
494 500 OutputArea.safe_outputs = {
495 501 'text/plain' : true,
496 502 'text/latex' : true,
497 503 'image/png' : true,
498 504 'image/jpeg' : true
499 505 };
500 506
501 OutputArea.prototype.append_mime_type = function (json, element) {
507 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
502 508 for (var type_i in OutputArea.display_order) {
503 509 var type = OutputArea.display_order[type_i];
504 510 var append = OutputArea.append_map[type];
505 511 if ((json[type] !== undefined) && append) {
506 512 var value = json[type];
507 513 if (!this.trusted && !OutputArea.safe_outputs[type]) {
508 514 // not trusted, sanitize HTML
509 515 if (type==='text/html' || type==='text/svg') {
510 516 value = IPython.security.sanitize_html(value);
511 517 } else {
512 518 // don't display if we don't know how to sanitize it
513 519 console.log("Ignoring untrusted " + type + " output.");
514 520 continue;
515 521 }
516 522 }
517 523 var md = json.metadata || {};
518 var toinsert = append.apply(this, [value, md, element]);
524 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
525 // Since only the png and jpeg mime types call the inserted
526 // callback, if the mim type is something other we must call the
527 // inserted callback only when the element is actually inserted
528 // into the DOM. Use a timeout of 0 to do this.
529 if (['image/png', 'image/jpeg'].indexOf() < 0 && handle_inserted !== undefined) {
530 setTimeout(handle_inserted, 0);
531 }
519 532 $([IPython.events]).trigger('output_appended.OutputArea', [type, value, md, toinsert]);
520 533 return toinsert;
521 534 }
522 535 }
523 536 return null;
524 537 };
525 538
526 539
527 540 var append_html = function (html, md, element) {
528 541 var type = 'text/html';
529 542 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
530 543 IPython.keyboard_manager.register_events(toinsert);
531 544 toinsert.append(html);
532 545 element.append(toinsert);
533 546 return toinsert;
534 547 };
535 548
536 549
537 550 var append_javascript = function (js, md, element) {
538 551 // We just eval the JS code, element appears in the local scope.
539 552 var type = 'application/javascript';
540 553 var toinsert = this.create_output_subarea(md, "output_javascript", type);
541 554 IPython.keyboard_manager.register_events(toinsert);
542 555 element.append(toinsert);
543 556 // FIXME TODO : remove `container element for 3.0`
544 557 //backward compat, js should be eval'ed in a context where `container` is defined.
545 558 var container = element;
546 559 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
547 560 // end backward compat
548 561
549 562 // Fix for ipython/issues/5293, make sure `element` is the area which
550 563 // output can be inserted into at the time of JS execution.
551 564 element = toinsert;
552 565 try {
553 566 eval(js);
554 567 } catch(err) {
555 568 console.log(err);
556 569 this._append_javascript_error(err, toinsert);
557 570 }
558 571 return toinsert;
559 572 };
560 573
561 574
562 575 var append_text = function (data, md, element) {
563 576 var type = 'text/plain';
564 577 var toinsert = this.create_output_subarea(md, "output_text", type);
565 578 // escape ANSI & HTML specials in plaintext:
566 579 data = utils.fixConsole(data);
567 580 data = utils.fixCarriageReturn(data);
568 581 data = utils.autoLinkUrls(data);
569 582 // The only user content injected with this HTML call is
570 583 // escaped by the fixConsole() method.
571 584 toinsert.append($("<pre/>").html(data));
572 585 element.append(toinsert);
573 586 return toinsert;
574 587 };
575 588
576 589
577 590 var append_svg = function (svg, md, element) {
578 591 var type = 'image/svg+xml';
579 592 var toinsert = this.create_output_subarea(md, "output_svg", type);
580 593 toinsert.append(svg);
581 594 element.append(toinsert);
582 595 return toinsert;
583 596 };
584 597
585 598
586 599 OutputArea.prototype._dblclick_to_reset_size = function (img) {
587 600 // wrap image after it's loaded on the page,
588 601 // otherwise the measured initial size will be incorrect
589 602 img.on("load", function (){
590 603 var h0 = img.height();
591 604 var w0 = img.width();
592 605 if (!(h0 && w0)) {
593 606 // zero size, don't make it resizable
594 607 return;
595 608 }
596 609 img.resizable({
597 610 aspectRatio: true,
598 611 autoHide: true
599 612 });
600 613 img.dblclick(function () {
601 614 // resize wrapper & image together for some reason:
602 615 img.parent().height(h0);
603 616 img.height(h0);
604 617 img.parent().width(w0);
605 618 img.width(w0);
606 619 });
607 620 });
608 621 };
609 622
610 623 var set_width_height = function (img, md, mime) {
611 624 // set width and height of an img element from metadata
612 625 var height = _get_metadata_key(md, 'height', mime);
613 626 if (height !== undefined) img.attr('height', height);
614 627 var width = _get_metadata_key(md, 'width', mime);
615 628 if (width !== undefined) img.attr('width', width);
616 629 };
617 630
618 var append_png = function (png, md, element) {
631 var append_png = function (png, md, element, handle_inserted) {
619 632 var type = 'image/png';
620 633 var toinsert = this.create_output_subarea(md, "output_png", type);
621 634 var img = $("<img/>").attr('src','data:image/png;base64,'+png);
635 if (handle_inserted !== undefined) {
636 img.load(handle_inserted);
637 }
622 638 set_width_height(img, md, 'image/png');
623 639 this._dblclick_to_reset_size(img);
624 640 toinsert.append(img);
625 641 element.append(toinsert);
626 642 return toinsert;
627 643 };
628 644
629 645
630 var append_jpeg = function (jpeg, md, element) {
646 var append_jpeg = function (jpeg, md, element, handle_inserted) {
631 647 var type = 'image/jpeg';
632 648 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
633 649 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
650 if (handle_inserted !== undefined) {
651 img.load(handle_inserted);
652 }
634 653 set_width_height(img, md, 'image/jpeg');
635 654 this._dblclick_to_reset_size(img);
636 655 toinsert.append(img);
637 656 element.append(toinsert);
638 657 return toinsert;
639 658 };
640 659
641 660
642 661 var append_pdf = function (pdf, md, element) {
643 662 var type = 'application/pdf';
644 663 var toinsert = this.create_output_subarea(md, "output_pdf", type);
645 664 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
646 665 a.attr('target', '_blank');
647 666 a.text('View PDF')
648 667 toinsert.append(a);
649 668 element.append(toinsert);
650 669 return toinsert;
651 670 }
652 671
653 672 var append_latex = function (latex, md, element) {
654 673 // This method cannot do the typesetting because the latex first has to
655 674 // be on the page.
656 675 var type = 'text/latex';
657 676 var toinsert = this.create_output_subarea(md, "output_latex", type);
658 677 toinsert.append(latex);
659 678 element.append(toinsert);
660 679 return toinsert;
661 680 };
662 681
663 682
664 683 OutputArea.prototype.append_raw_input = function (msg) {
665 684 var that = this;
666 685 this.expand();
667 686 var content = msg.content;
668 687 var area = this.create_output_area();
669 688
670 689 // disable any other raw_inputs, if they are left around
671 690 $("div.output_subarea.raw_input_container").remove();
672 691
673 692 area.append(
674 693 $("<div/>")
675 694 .addClass("box-flex1 output_subarea raw_input_container")
676 695 .append(
677 696 $("<span/>")
678 697 .addClass("raw_input_prompt")
679 698 .text(content.prompt)
680 699 )
681 700 .append(
682 701 $("<input/>")
683 702 .addClass("raw_input")
684 703 .attr('type', 'text')
685 704 .attr("size", 47)
686 705 .keydown(function (event, ui) {
687 706 // make sure we submit on enter,
688 707 // and don't re-execute the *cell* on shift-enter
689 708 if (event.which === IPython.keyboard.keycodes.enter) {
690 709 that._submit_raw_input();
691 710 return false;
692 711 }
693 712 })
694 713 )
695 714 );
696 715
697 716 this.element.append(area);
698 717 var raw_input = area.find('input.raw_input');
699 718 // Register events that enable/disable the keyboard manager while raw
700 719 // input is focused.
701 720 IPython.keyboard_manager.register_events(raw_input);
702 721 // Note, the following line used to read raw_input.focus().focus().
703 722 // This seemed to be needed otherwise only the cell would be focused.
704 723 // But with the modal UI, this seems to work fine with one call to focus().
705 724 raw_input.focus();
706 725 }
707 726
708 727 OutputArea.prototype._submit_raw_input = function (evt) {
709 728 var container = this.element.find("div.raw_input_container");
710 729 var theprompt = container.find("span.raw_input_prompt");
711 730 var theinput = container.find("input.raw_input");
712 731 var value = theinput.val();
713 732 var content = {
714 733 output_type : 'stream',
715 734 name : 'stdout',
716 735 text : theprompt.text() + value + '\n'
717 736 }
718 737 // remove form container
719 738 container.parent().remove();
720 739 // replace with plaintext version in stdout
721 740 this.append_output(content, false);
722 741 $([IPython.events]).trigger('send_input_reply.Kernel', value);
723 742 }
724 743
725 744
726 745 OutputArea.prototype.handle_clear_output = function (msg) {
727 746 // msg spec v4 had stdout, stderr, display keys
728 747 // v4.1 replaced these with just wait
729 748 // The default behavior is the same (stdout=stderr=display=True, wait=False),
730 749 // so v4 messages will still be properly handled,
731 750 // except for the rarely used clearing less than all output.
732 751 this.clear_output(msg.content.wait || false);
733 752 };
734 753
735 754
736 755 OutputArea.prototype.clear_output = function(wait) {
737 756 if (wait) {
738 757
739 758 // If a clear is queued, clear before adding another to the queue.
740 759 if (this.clear_queued) {
741 760 this.clear_output(false);
742 761 };
743 762
744 763 this.clear_queued = true;
745 764 } else {
746 765
747 766 // Fix the output div's height if the clear_output is waiting for
748 767 // new output (it is being used in an animation).
749 768 if (this.clear_queued) {
750 769 var height = this.element.height();
751 770 this.element.height(height);
752 771 this.clear_queued = false;
753 772 }
754 773
755 774 // clear all, no need for logic
756 775 this.element.html("");
757 776 this.outputs = [];
758 777 this.trusted = true;
759 778 this.unscroll_area();
760 779 return;
761 780 };
762 781 };
763 782
764 783
765 784 // JSON serialization
766 785
767 786 OutputArea.prototype.fromJSON = function (outputs) {
768 787 var len = outputs.length;
769 788 var data;
770 789
771 790 for (var i=0; i<len; i++) {
772 791 data = outputs[i];
773 792 var msg_type = data.output_type;
774 793 if (msg_type === "display_data" || msg_type === "pyout") {
775 794 // convert short keys to mime keys
776 795 // TODO: remove mapping of short keys when we update to nbformat 4
777 796 data = this.rename_keys(data, OutputArea.mime_map_r);
778 797 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
779 798 }
780 799
781 800 this.append_output(data);
782 801 }
783 802 };
784 803
785 804
786 805 OutputArea.prototype.toJSON = function () {
787 806 var outputs = [];
788 807 var len = this.outputs.length;
789 808 var data;
790 809 for (var i=0; i<len; i++) {
791 810 data = this.outputs[i];
792 811 var msg_type = data.output_type;
793 812 if (msg_type === "display_data" || msg_type === "pyout") {
794 813 // convert mime keys to short keys
795 814 data = this.rename_keys(data, OutputArea.mime_map);
796 815 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
797 816 }
798 817 outputs[i] = data;
799 818 }
800 819 return outputs;
801 820 };
802 821
803 822 /**
804 823 * Class properties
805 824 **/
806 825
807 826 /**
808 827 * Threshold to trigger autoscroll when the OutputArea is resized,
809 828 * typically when new outputs are added.
810 829 *
811 830 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
812 831 * unless it is < 0, in which case autoscroll will never be triggered
813 832 *
814 833 * @property auto_scroll_threshold
815 834 * @type Number
816 835 * @default 100
817 836 *
818 837 **/
819 838 OutputArea.auto_scroll_threshold = 100;
820 839
821 840 /**
822 841 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
823 842 * shorter than this are never scrolled.
824 843 *
825 844 * @property minimum_scroll_threshold
826 845 * @type Number
827 846 * @default 20
828 847 *
829 848 **/
830 849 OutputArea.minimum_scroll_threshold = 20;
831 850
832 851
833 852
834 853 OutputArea.mime_map = {
835 854 "text/plain" : "text",
836 855 "text/html" : "html",
837 856 "image/svg+xml" : "svg",
838 857 "image/png" : "png",
839 858 "image/jpeg" : "jpeg",
840 859 "text/latex" : "latex",
841 860 "application/json" : "json",
842 861 "application/javascript" : "javascript",
843 862 };
844 863
845 864 OutputArea.mime_map_r = {
846 865 "text" : "text/plain",
847 866 "html" : "text/html",
848 867 "svg" : "image/svg+xml",
849 868 "png" : "image/png",
850 869 "jpeg" : "image/jpeg",
851 870 "latex" : "text/latex",
852 871 "json" : "application/json",
853 872 "javascript" : "application/javascript",
854 873 };
855 874
856 875 OutputArea.display_order = [
857 876 'application/javascript',
858 877 'text/html',
859 878 'text/latex',
860 879 'image/svg+xml',
861 880 'image/png',
862 881 'image/jpeg',
863 882 'application/pdf',
864 883 'text/plain'
865 884 ];
866 885
867 886 OutputArea.append_map = {
868 887 "text/plain" : append_text,
869 888 "text/html" : append_html,
870 889 "image/svg+xml" : append_svg,
871 890 "image/png" : append_png,
872 891 "image/jpeg" : append_jpeg,
873 892 "text/latex" : append_latex,
874 893 "application/javascript" : append_javascript,
875 894 "application/pdf" : append_pdf
876 895 };
877 896
878 897 IPython.OutputArea = OutputArea;
879 898
880 899 return IPython;
881 900
882 901 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now