##// END OF EJS Templates
Merge pull request #3969 from Carreau/usestrict...
Matthias Bussonnier -
r12232:d5724045 merge
parent child Browse files
Show More
@@ -1,83 +1,84 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Utility for modal dialogs with bootstrap
9 // Utility for modal dialogs with bootstrap
10 //============================================================================
10 //============================================================================
11
11
12 IPython.namespace('IPython.dialog');
12 IPython.namespace('IPython.dialog');
13
13
14 IPython.dialog = (function (IPython) {
14 IPython.dialog = (function (IPython) {
15 "use strict";
15
16
16 var modal = function (options) {
17 var modal = function (options) {
17 var dialog = $("<div/>").addClass("modal").attr("role", "dialog");
18 var dialog = $("<div/>").addClass("modal").attr("role", "dialog");
18 dialog.append(
19 dialog.append(
19 $("<div/>")
20 $("<div/>")
20 .addClass("modal-header")
21 .addClass("modal-header")
21 .append($("<button>")
22 .append($("<button>")
22 .addClass("close")
23 .addClass("close")
23 .attr("data-dismiss", "modal")
24 .attr("data-dismiss", "modal")
24 .html("&times;")
25 .html("&times;")
25 ).append(
26 ).append(
26 $("<h3/>").text(options.title || "")
27 $("<h3/>").text(options.title || "")
27 )
28 )
28 ).append(
29 ).append(
29 $("<div/>").addClass("modal-body").append(
30 $("<div/>").addClass("modal-body").append(
30 options.body || $("<p/>")
31 options.body || $("<p/>")
31 )
32 )
32 );
33 );
33
34
34 var footer = $("<div/>").addClass("modal-footer");
35 var footer = $("<div/>").addClass("modal-footer");
35
36
36 for (var label in options.buttons) {
37 for (var label in options.buttons) {
37 var btn_opts = options.buttons[label];
38 var btn_opts = options.buttons[label];
38 var button = $("<button/>")
39 var button = $("<button/>")
39 .addClass("btn")
40 .addClass("btn")
40 .attr("data-dismiss", "modal")
41 .attr("data-dismiss", "modal")
41 .text(label);
42 .text(label);
42 if (btn_opts.click) {
43 if (btn_opts.click) {
43 button.click($.proxy(btn_opts.click, dialog));
44 button.click($.proxy(btn_opts.click, dialog));
44 }
45 }
45 if (btn_opts.class) {
46 if (btn_opts.class) {
46 button.addClass(btn_opts.class);
47 button.addClass(btn_opts.class);
47 }
48 }
48 footer.append(button);
49 footer.append(button);
49 }
50 }
50 dialog.append(footer);
51 dialog.append(footer);
51 // hook up on-open event
52 // hook up on-open event
52 dialog.on("shown", function() {
53 dialog.on("shown", function() {
53 setTimeout(function() {
54 setTimeout(function() {
54 footer.find("button").last().focus();
55 footer.find("button").last().focus();
55 if (options.open) {
56 if (options.open) {
56 $.proxy(options.open, dialog)();
57 $.proxy(options.open, dialog)();
57 }
58 }
58 }, 0);
59 }, 0);
59 });
60 });
60
61
61 // destroy dialog on hide, unless explicitly asked not to
62 // destroy dialog on hide, unless explicitly asked not to
62 if (options.destroy == undefined || options.destroy) {
63 if (options.destroy == undefined || options.destroy) {
63 dialog.on("hidden", function () {
64 dialog.on("hidden", function () {
64 dialog.remove();
65 dialog.remove();
65 });
66 });
66 }
67 }
67 if (options.reselect_cell !== false) {
68 if (options.reselect_cell !== false) {
68 dialog.on("hidden", function () {
69 dialog.on("hidden", function () {
69 if (IPython.notebook) {
70 if (IPython.notebook) {
70 cell = IPython.notebook.get_selected_cell();
71 cell = IPython.notebook.get_selected_cell();
71 if (cell) cell.select();
72 if (cell) cell.select();
72 }
73 }
73 });
74 });
74 }
75 }
75
76
76 return dialog.modal(options);
77 return dialog.modal(options);
77 }
78 }
78
79
79 return {
80 return {
80 modal : modal,
81 modal : modal,
81 };
82 };
82
83
83 }(IPython));
84 }(IPython));
@@ -1,31 +1,32 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Events
9 // Events
10 //============================================================================
10 //============================================================================
11
11
12 // Give us an object to bind all events to. This object should be created
12 // Give us an object to bind all events to. This object should be created
13 // before all other objects so it exists when others register event handlers.
13 // before all other objects so it exists when others register event handlers.
14 // To trigger an event handler:
14 // To trigger an event handler:
15 // $([IPython.events]).trigger('event.Namespace');
15 // $([IPython.events]).trigger('event.Namespace');
16 // To handle it:
16 // To handle it:
17 // $([IPython.events]).on('event.Namespace',function () {});
17 // $([IPython.events]).on('event.Namespace',function () {});
18
18
19 var IPython = (function (IPython) {
19 var IPython = (function (IPython) {
20 "use strict";
20
21
21 var utils = IPython.utils;
22 var utils = IPython.utils;
22
23
23 var Events = function () {};
24 var Events = function () {};
24
25
25 IPython.Events = Events;
26 IPython.Events = Events;
26 IPython.events = new Events();
27 IPython.events = new Events();
27
28
28 return IPython;
29 return IPython;
29
30
30 }(IPython));
31 }(IPython));
31
32
@@ -1,30 +1,32 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 var IPython = IPython || {};
8 var IPython = IPython || {};
9
9
10 IPython.namespace = function (ns_string) {
10 IPython.namespace = function (ns_string) {
11 "use strict";
12
11 var parts = ns_string.split('.'),
13 var parts = ns_string.split('.'),
12 parent = IPython,
14 parent = IPython,
13 i;
15 i;
14
16
15 // String redundant leading global
17 // String redundant leading global
16 if (parts[0] === "IPython") {
18 if (parts[0] === "IPython") {
17 parts = parts.slice(1);
19 parts = parts.slice(1);
18 }
20 }
19
21
20 for (i=0; i<parts.length; i+=1) {
22 for (i=0; i<parts.length; i+=1) {
21 // Create property if it doesn't exist
23 // Create property if it doesn't exist
22 if (typeof parent[parts[i]] === "undefined") {
24 if (typeof parent[parts[i]] === "undefined") {
23 parent[parts[i]] = {};
25 parent[parts[i]] = {};
24 }
26 }
25 }
27 }
26 return parent;
28 return parent;
27 };
29 };
28
30
29
31
30
32
@@ -1,55 +1,56 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Global header/site setup.
9 // Global header/site setup.
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13
14
14 var Page = function () {
15 var Page = function () {
15 this.style();
16 this.style();
16 this.bind_events();
17 this.bind_events();
17 };
18 };
18
19
19 Page.prototype.style = function () {
20 Page.prototype.style = function () {
20 $('div#header').addClass('border-box-sizing');
21 $('div#header').addClass('border-box-sizing');
21 $('div#site').addClass('border-box-sizing');
22 $('div#site').addClass('border-box-sizing');
22 };
23 };
23
24
24
25
25 Page.prototype.bind_events = function () {
26 Page.prototype.bind_events = function () {
26 };
27 };
27
28
28
29
29 Page.prototype.show = function () {
30 Page.prototype.show = function () {
30 // The header and site divs start out hidden to prevent FLOUC.
31 // The header and site divs start out hidden to prevent FLOUC.
31 // Main scripts should call this method after styling everything.
32 // Main scripts should call this method after styling everything.
32 this.show_header();
33 this.show_header();
33 this.show_site();
34 this.show_site();
34 };
35 };
35
36
36
37
37 Page.prototype.show_header = function () {
38 Page.prototype.show_header = function () {
38 // The header and site divs start out hidden to prevent FLOUC.
39 // The header and site divs start out hidden to prevent FLOUC.
39 // Main scripts should call this method after styling everything.
40 // Main scripts should call this method after styling everything.
40 $('div#header').css('display','block');
41 $('div#header').css('display','block');
41 };
42 };
42
43
43
44
44 Page.prototype.show_site = function () {
45 Page.prototype.show_site = function () {
45 // The header and site divs start out hidden to prevent FLOUC.
46 // The header and site divs start out hidden to prevent FLOUC.
46 // Main scripts should call this method after styling everything.
47 // Main scripts should call this method after styling everything.
47 $('div#site').css('display','block');
48 $('div#site').css('display','block');
48 };
49 };
49
50
50
51
51 IPython.Page = Page;
52 IPython.Page = Page;
52
53
53 return IPython;
54 return IPython;
54
55
55 }(IPython));
56 }(IPython));
@@ -1,19 +1,20 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // On document ready
9 // On document ready
10 //============================================================================
10 //============================================================================
11
11
12
12
13 $(document).ready(function () {
13 $(document).ready(function () {
14 "use strict";
14
15
15 IPython.page = new IPython.Page();
16 IPython.page = new IPython.Page();
16 IPython.page.show();
17 IPython.page.show();
17
18
18 });
19 });
19
20
@@ -1,390 +1,391 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2012 The IPython Development Team
2 // Copyright (C) 2008-2012 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Utilities
9 // Utilities
10 //============================================================================
10 //============================================================================
11 IPython.namespace('IPython.utils');
11 IPython.namespace('IPython.utils');
12
12
13 IPython.utils = (function (IPython) {
13 IPython.utils = (function (IPython) {
14 "use strict";
14
15
15 //============================================================================
16 //============================================================================
16 // Cross-browser RegEx Split
17 // Cross-browser RegEx Split
17 //============================================================================
18 //============================================================================
18
19
19 // This code has been MODIFIED from the code licensed below to not replace the
20 // This code has been MODIFIED from the code licensed below to not replace the
20 // default browser split. The license is reproduced here.
21 // default browser split. The license is reproduced here.
21
22
22 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
23 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
23 /*!
24 /*!
24 * Cross-Browser Split 1.1.1
25 * Cross-Browser Split 1.1.1
25 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
26 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
26 * Available under the MIT License
27 * Available under the MIT License
27 * ECMAScript compliant, uniform cross-browser split method
28 * ECMAScript compliant, uniform cross-browser split method
28 */
29 */
29
30
30 /**
31 /**
31 * Splits a string into an array of strings using a regex or string
32 * Splits a string into an array of strings using a regex or string
32 * separator. Matches of the separator are not included in the result array.
33 * separator. Matches of the separator are not included in the result array.
33 * However, if `separator` is a regex that contains capturing groups,
34 * However, if `separator` is a regex that contains capturing groups,
34 * backreferences are spliced into the result each time `separator` is
35 * backreferences are spliced into the result each time `separator` is
35 * matched. Fixes browser bugs compared to the native
36 * matched. Fixes browser bugs compared to the native
36 * `String.prototype.split` and can be used reliably cross-browser.
37 * `String.prototype.split` and can be used reliably cross-browser.
37 * @param {String} str String to split.
38 * @param {String} str String to split.
38 * @param {RegExp|String} separator Regex or string to use for separating
39 * @param {RegExp|String} separator Regex or string to use for separating
39 * the string.
40 * the string.
40 * @param {Number} [limit] Maximum number of items to include in the result
41 * @param {Number} [limit] Maximum number of items to include in the result
41 * array.
42 * array.
42 * @returns {Array} Array of substrings.
43 * @returns {Array} Array of substrings.
43 * @example
44 * @example
44 *
45 *
45 * // Basic use
46 * // Basic use
46 * regex_split('a b c d', ' ');
47 * regex_split('a b c d', ' ');
47 * // -> ['a', 'b', 'c', 'd']
48 * // -> ['a', 'b', 'c', 'd']
48 *
49 *
49 * // With limit
50 * // With limit
50 * regex_split('a b c d', ' ', 2);
51 * regex_split('a b c d', ' ', 2);
51 * // -> ['a', 'b']
52 * // -> ['a', 'b']
52 *
53 *
53 * // Backreferences in result array
54 * // Backreferences in result array
54 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
55 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
55 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
56 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
56 */
57 */
57 var regex_split = function (str, separator, limit) {
58 var regex_split = function (str, separator, limit) {
58 // If `separator` is not a regex, use `split`
59 // If `separator` is not a regex, use `split`
59 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
60 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
60 return split.call(str, separator, limit);
61 return split.call(str, separator, limit);
61 }
62 }
62 var output = [],
63 var output = [],
63 flags = (separator.ignoreCase ? "i" : "") +
64 flags = (separator.ignoreCase ? "i" : "") +
64 (separator.multiline ? "m" : "") +
65 (separator.multiline ? "m" : "") +
65 (separator.extended ? "x" : "") + // Proposed for ES6
66 (separator.extended ? "x" : "") + // Proposed for ES6
66 (separator.sticky ? "y" : ""), // Firefox 3+
67 (separator.sticky ? "y" : ""), // Firefox 3+
67 lastLastIndex = 0,
68 lastLastIndex = 0,
68 // Make `global` and avoid `lastIndex` issues by working with a copy
69 // Make `global` and avoid `lastIndex` issues by working with a copy
69 separator = new RegExp(separator.source, flags + "g"),
70 separator = new RegExp(separator.source, flags + "g"),
70 separator2, match, lastIndex, lastLength;
71 separator2, match, lastIndex, lastLength;
71 str += ""; // Type-convert
72 str += ""; // Type-convert
72
73
73 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
74 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
74 if (!compliantExecNpcg) {
75 if (!compliantExecNpcg) {
75 // Doesn't need flags gy, but they don't hurt
76 // Doesn't need flags gy, but they don't hurt
76 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
77 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
77 }
78 }
78 /* Values for `limit`, per the spec:
79 /* Values for `limit`, per the spec:
79 * If undefined: 4294967295 // Math.pow(2, 32) - 1
80 * If undefined: 4294967295 // Math.pow(2, 32) - 1
80 * If 0, Infinity, or NaN: 0
81 * If 0, Infinity, or NaN: 0
81 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
82 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
82 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
83 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
83 * If other: Type-convert, then use the above rules
84 * If other: Type-convert, then use the above rules
84 */
85 */
85 limit = typeof(limit) === "undefined" ?
86 limit = typeof(limit) === "undefined" ?
86 -1 >>> 0 : // Math.pow(2, 32) - 1
87 -1 >>> 0 : // Math.pow(2, 32) - 1
87 limit >>> 0; // ToUint32(limit)
88 limit >>> 0; // ToUint32(limit)
88 while (match = separator.exec(str)) {
89 while (match = separator.exec(str)) {
89 // `separator.lastIndex` is not reliable cross-browser
90 // `separator.lastIndex` is not reliable cross-browser
90 lastIndex = match.index + match[0].length;
91 lastIndex = match.index + match[0].length;
91 if (lastIndex > lastLastIndex) {
92 if (lastIndex > lastLastIndex) {
92 output.push(str.slice(lastLastIndex, match.index));
93 output.push(str.slice(lastLastIndex, match.index));
93 // Fix browsers whose `exec` methods don't consistently return `undefined` for
94 // Fix browsers whose `exec` methods don't consistently return `undefined` for
94 // nonparticipating capturing groups
95 // nonparticipating capturing groups
95 if (!compliantExecNpcg && match.length > 1) {
96 if (!compliantExecNpcg && match.length > 1) {
96 match[0].replace(separator2, function () {
97 match[0].replace(separator2, function () {
97 for (var i = 1; i < arguments.length - 2; i++) {
98 for (var i = 1; i < arguments.length - 2; i++) {
98 if (typeof(arguments[i]) === "undefined") {
99 if (typeof(arguments[i]) === "undefined") {
99 match[i] = undefined;
100 match[i] = undefined;
100 }
101 }
101 }
102 }
102 });
103 });
103 }
104 }
104 if (match.length > 1 && match.index < str.length) {
105 if (match.length > 1 && match.index < str.length) {
105 Array.prototype.push.apply(output, match.slice(1));
106 Array.prototype.push.apply(output, match.slice(1));
106 }
107 }
107 lastLength = match[0].length;
108 lastLength = match[0].length;
108 lastLastIndex = lastIndex;
109 lastLastIndex = lastIndex;
109 if (output.length >= limit) {
110 if (output.length >= limit) {
110 break;
111 break;
111 }
112 }
112 }
113 }
113 if (separator.lastIndex === match.index) {
114 if (separator.lastIndex === match.index) {
114 separator.lastIndex++; // Avoid an infinite loop
115 separator.lastIndex++; // Avoid an infinite loop
115 }
116 }
116 }
117 }
117 if (lastLastIndex === str.length) {
118 if (lastLastIndex === str.length) {
118 if (lastLength || !separator.test("")) {
119 if (lastLength || !separator.test("")) {
119 output.push("");
120 output.push("");
120 }
121 }
121 } else {
122 } else {
122 output.push(str.slice(lastLastIndex));
123 output.push(str.slice(lastLastIndex));
123 }
124 }
124 return output.length > limit ? output.slice(0, limit) : output;
125 return output.length > limit ? output.slice(0, limit) : output;
125 };
126 };
126
127
127 //============================================================================
128 //============================================================================
128 // End contributed Cross-browser RegEx Split
129 // End contributed Cross-browser RegEx Split
129 //============================================================================
130 //============================================================================
130
131
131
132
132 var uuid = function () {
133 var uuid = function () {
133 // http://www.ietf.org/rfc/rfc4122.txt
134 // http://www.ietf.org/rfc/rfc4122.txt
134 var s = [];
135 var s = [];
135 var hexDigits = "0123456789ABCDEF";
136 var hexDigits = "0123456789ABCDEF";
136 for (var i = 0; i < 32; i++) {
137 for (var i = 0; i < 32; i++) {
137 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
138 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
138 }
139 }
139 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
140 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
140 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
141 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
141
142
142 var uuid = s.join("");
143 var uuid = s.join("");
143 return uuid;
144 return uuid;
144 };
145 };
145
146
146
147
147 //Fix raw text to parse correctly in crazy XML
148 //Fix raw text to parse correctly in crazy XML
148 function xmlencode(string) {
149 function xmlencode(string) {
149 return string.replace(/\&/g,'&'+'amp;')
150 return string.replace(/\&/g,'&'+'amp;')
150 .replace(/</g,'&'+'lt;')
151 .replace(/</g,'&'+'lt;')
151 .replace(/>/g,'&'+'gt;')
152 .replace(/>/g,'&'+'gt;')
152 .replace(/\'/g,'&'+'apos;')
153 .replace(/\'/g,'&'+'apos;')
153 .replace(/\"/g,'&'+'quot;')
154 .replace(/\"/g,'&'+'quot;')
154 .replace(/`/g,'&'+'#96;');
155 .replace(/`/g,'&'+'#96;');
155 }
156 }
156
157
157
158
158 //Map from terminal commands to CSS classes
159 //Map from terminal commands to CSS classes
159 var ansi_colormap = {
160 var ansi_colormap = {
160 "01":"ansibold",
161 "01":"ansibold",
161
162
162 "30":"ansiblack",
163 "30":"ansiblack",
163 "31":"ansired",
164 "31":"ansired",
164 "32":"ansigreen",
165 "32":"ansigreen",
165 "33":"ansiyellow",
166 "33":"ansiyellow",
166 "34":"ansiblue",
167 "34":"ansiblue",
167 "35":"ansipurple",
168 "35":"ansipurple",
168 "36":"ansicyan",
169 "36":"ansicyan",
169 "37":"ansigray",
170 "37":"ansigray",
170
171
171 "40":"ansibgblack",
172 "40":"ansibgblack",
172 "41":"ansibgred",
173 "41":"ansibgred",
173 "42":"ansibggreen",
174 "42":"ansibggreen",
174 "43":"ansibgyellow",
175 "43":"ansibgyellow",
175 "44":"ansibgblue",
176 "44":"ansibgblue",
176 "45":"ansibgpurple",
177 "45":"ansibgpurple",
177 "46":"ansibgcyan",
178 "46":"ansibgcyan",
178 "47":"ansibggray"
179 "47":"ansibggray"
179 };
180 };
180
181
181 function _process_numbers(attrs, numbers) {
182 function _process_numbers(attrs, numbers) {
182 // process ansi escapes
183 // process ansi escapes
183 var n = numbers.shift();
184 var n = numbers.shift();
184 if (ansi_colormap[n]) {
185 if (ansi_colormap[n]) {
185 if ( ! attrs["class"] ) {
186 if ( ! attrs["class"] ) {
186 attrs["class"] = ansi_colormap[n];
187 attrs["class"] = ansi_colormap[n];
187 } else {
188 } else {
188 attrs["class"] += " " + ansi_colormap[n];
189 attrs["class"] += " " + ansi_colormap[n];
189 }
190 }
190 } else if (n == "38" || n == "48") {
191 } else if (n == "38" || n == "48") {
191 // VT100 256 color or 24 bit RGB
192 // VT100 256 color or 24 bit RGB
192 if (numbers.length < 2) {
193 if (numbers.length < 2) {
193 console.log("Not enough fields for VT100 color", numbers);
194 console.log("Not enough fields for VT100 color", numbers);
194 return;
195 return;
195 }
196 }
196
197
197 var index_or_rgb = numbers.shift();
198 var index_or_rgb = numbers.shift();
198 var r,g,b;
199 var r,g,b;
199 if (index_or_rgb == "5") {
200 if (index_or_rgb == "5") {
200 // 256 color
201 // 256 color
201 var idx = parseInt(numbers.shift());
202 var idx = parseInt(numbers.shift());
202 if (idx < 16) {
203 if (idx < 16) {
203 // indexed ANSI
204 // indexed ANSI
204 // ignore bright / non-bright distinction
205 // ignore bright / non-bright distinction
205 idx = idx % 8;
206 idx = idx % 8;
206 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
207 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
207 if ( ! attrs["class"] ) {
208 if ( ! attrs["class"] ) {
208 attrs["class"] = ansiclass;
209 attrs["class"] = ansiclass;
209 } else {
210 } else {
210 attrs["class"] += " " + ansiclass;
211 attrs["class"] += " " + ansiclass;
211 }
212 }
212 return;
213 return;
213 } else if (idx < 232) {
214 } else if (idx < 232) {
214 // 216 color 6x6x6 RGB
215 // 216 color 6x6x6 RGB
215 idx = idx - 16;
216 idx = idx - 16;
216 b = idx % 6;
217 b = idx % 6;
217 g = Math.floor(idx / 6) % 6;
218 g = Math.floor(idx / 6) % 6;
218 r = Math.floor(idx / 36) % 6;
219 r = Math.floor(idx / 36) % 6;
219 // convert to rgb
220 // convert to rgb
220 r = (r * 51);
221 r = (r * 51);
221 g = (g * 51);
222 g = (g * 51);
222 b = (b * 51);
223 b = (b * 51);
223 } else {
224 } else {
224 // grayscale
225 // grayscale
225 idx = idx - 231;
226 idx = idx - 231;
226 // it's 1-24 and should *not* include black or white,
227 // it's 1-24 and should *not* include black or white,
227 // so a 26 point scale
228 // so a 26 point scale
228 r = g = b = Math.floor(idx * 256 / 26);
229 r = g = b = Math.floor(idx * 256 / 26);
229 }
230 }
230 } else if (index_or_rgb == "2") {
231 } else if (index_or_rgb == "2") {
231 // Simple 24 bit RGB
232 // Simple 24 bit RGB
232 if (numbers.length > 3) {
233 if (numbers.length > 3) {
233 console.log("Not enough fields for RGB", numbers);
234 console.log("Not enough fields for RGB", numbers);
234 return;
235 return;
235 }
236 }
236 r = numbers.shift();
237 r = numbers.shift();
237 g = numbers.shift();
238 g = numbers.shift();
238 b = numbers.shift();
239 b = numbers.shift();
239 } else {
240 } else {
240 console.log("unrecognized control", numbers);
241 console.log("unrecognized control", numbers);
241 return;
242 return;
242 }
243 }
243 if (r !== undefined) {
244 if (r !== undefined) {
244 // apply the rgb color
245 // apply the rgb color
245 var line;
246 var line;
246 if (n == "38") {
247 if (n == "38") {
247 line = "color: ";
248 line = "color: ";
248 } else {
249 } else {
249 line = "background-color: ";
250 line = "background-color: ";
250 }
251 }
251 line = line + "rgb(" + r + "," + g + "," + b + ");"
252 line = line + "rgb(" + r + "," + g + "," + b + ");"
252 if ( !attrs["style"] ) {
253 if ( !attrs["style"] ) {
253 attrs["style"] = line;
254 attrs["style"] = line;
254 } else {
255 } else {
255 attrs["style"] += " " + line;
256 attrs["style"] += " " + line;
256 }
257 }
257 }
258 }
258 }
259 }
259 }
260 }
260
261
261 function ansispan(str) {
262 function ansispan(str) {
262 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
263 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
263 // regular ansi escapes (using the table above)
264 // regular ansi escapes (using the table above)
264 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
265 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
265 if (!pattern) {
266 if (!pattern) {
266 // [(01|22|39|)m close spans
267 // [(01|22|39|)m close spans
267 return "</span>";
268 return "</span>";
268 }
269 }
269 // consume sequence of color escapes
270 // consume sequence of color escapes
270 var numbers = pattern.match(/\d+/g);
271 var numbers = pattern.match(/\d+/g);
271 var attrs = {};
272 var attrs = {};
272 while (numbers.length > 0) {
273 while (numbers.length > 0) {
273 _process_numbers(attrs, numbers);
274 _process_numbers(attrs, numbers);
274 }
275 }
275
276
276 var span = "<span ";
277 var span = "<span ";
277 for (var attr in attrs) {
278 for (var attr in attrs) {
278 var value = attrs[attr];
279 var value = attrs[attr];
279 span = span + " " + attr + '="' + attrs[attr] + '"';
280 span = span + " " + attr + '="' + attrs[attr] + '"';
280 }
281 }
281 return span + ">";
282 return span + ">";
282 });
283 });
283 };
284 };
284
285
285 // Transform ANSI color escape codes into HTML <span> tags with css
286 // Transform ANSI color escape codes into HTML <span> tags with css
286 // classes listed in the above ansi_colormap object. The actual color used
287 // classes listed in the above ansi_colormap object. The actual color used
287 // are set in the css file.
288 // are set in the css file.
288 function fixConsole(txt) {
289 function fixConsole(txt) {
289 txt = xmlencode(txt);
290 txt = xmlencode(txt);
290 var re = /\033\[([\dA-Fa-f;]*?)m/;
291 var re = /\033\[([\dA-Fa-f;]*?)m/;
291 var opened = false;
292 var opened = false;
292 var cmds = [];
293 var cmds = [];
293 var opener = "";
294 var opener = "";
294 var closer = "";
295 var closer = "";
295
296
296 // Strip all ANSI codes that are not color related. Matches
297 // Strip all ANSI codes that are not color related. Matches
297 // all ANSI codes that do not end with "m".
298 // all ANSI codes that do not end with "m".
298 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
299 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
299 txt = txt.replace(ignored_re, "");
300 txt = txt.replace(ignored_re, "");
300
301
301 // color ansi codes
302 // color ansi codes
302 txt = ansispan(txt);
303 txt = ansispan(txt);
303 return txt;
304 return txt;
304 }
305 }
305
306
306 // Remove chunks that should be overridden by the effect of
307 // Remove chunks that should be overridden by the effect of
307 // carriage return characters
308 // carriage return characters
308 function fixCarriageReturn(txt) {
309 function fixCarriageReturn(txt) {
309 var tmp = txt;
310 var tmp = txt;
310 do {
311 do {
311 txt = tmp;
312 txt = tmp;
312 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
313 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
313 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
314 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
314 } while (tmp.length < txt.length);
315 } while (tmp.length < txt.length);
315 return txt;
316 return txt;
316 }
317 }
317
318
318 // Locate any URLs and convert them to a anchor tag
319 // Locate any URLs and convert them to a anchor tag
319 function autoLinkUrls(txt) {
320 function autoLinkUrls(txt) {
320 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
321 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
321 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
322 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
322 }
323 }
323
324
324 // some keycodes that seem to be platform/browser independent
325 // some keycodes that seem to be platform/browser independent
325 var keycodes = {
326 var keycodes = {
326 BACKSPACE: 8,
327 BACKSPACE: 8,
327 TAB : 9,
328 TAB : 9,
328 ENTER : 13,
329 ENTER : 13,
329 SHIFT : 16,
330 SHIFT : 16,
330 CTRL : 17,
331 CTRL : 17,
331 CONTROL : 17,
332 CONTROL : 17,
332 ALT : 18,
333 ALT : 18,
333 CAPS_LOCK: 20,
334 CAPS_LOCK: 20,
334 ESC : 27,
335 ESC : 27,
335 SPACE : 32,
336 SPACE : 32,
336 PGUP : 33,
337 PGUP : 33,
337 PGDOWN : 34,
338 PGDOWN : 34,
338 END : 35,
339 END : 35,
339 HOME : 36,
340 HOME : 36,
340 LEFT_ARROW: 37,
341 LEFT_ARROW: 37,
341 LEFTARROW: 37,
342 LEFTARROW: 37,
342 LEFT : 37,
343 LEFT : 37,
343 UP_ARROW : 38,
344 UP_ARROW : 38,
344 UPARROW : 38,
345 UPARROW : 38,
345 UP : 38,
346 UP : 38,
346 RIGHT_ARROW:39,
347 RIGHT_ARROW:39,
347 RIGHTARROW:39,
348 RIGHTARROW:39,
348 RIGHT : 39,
349 RIGHT : 39,
349 DOWN_ARROW: 40,
350 DOWN_ARROW: 40,
350 DOWNARROW: 40,
351 DOWNARROW: 40,
351 DOWN : 40,
352 DOWN : 40,
352 // all three of these keys may be COMMAND on OS X:
353 // all three of these keys may be COMMAND on OS X:
353 LEFT_SUPER : 91,
354 LEFT_SUPER : 91,
354 RIGHT_SUPER : 92,
355 RIGHT_SUPER : 92,
355 COMMAND : 93,
356 COMMAND : 93,
356 };
357 };
357
358
358
359
359 var points_to_pixels = function (points) {
360 var points_to_pixels = function (points) {
360 // A reasonably good way of converting between points and pixels.
361 // A reasonably good way of converting between points and pixels.
361 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
362 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
362 $(body).append(test);
363 $(body).append(test);
363 var pixel_per_point = test.width()/10000;
364 var pixel_per_point = test.width()/10000;
364 test.remove();
365 test.remove();
365 return Math.floor(points*pixel_per_point);
366 return Math.floor(points*pixel_per_point);
366 };
367 };
367
368
368 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
369 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
369 browser = (function() {
370 var browser = (function() {
370 var N= navigator.appName, ua= navigator.userAgent, tem;
371 var N= navigator.appName, ua= navigator.userAgent, tem;
371 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
372 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
372 if (M && (tem= ua.match(/version\/([\.\d]+)/i))!= null) M[2]= tem[1];
373 if (M && (tem= ua.match(/version\/([\.\d]+)/i))!= null) M[2]= tem[1];
373 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
374 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
374 return M;
375 return M;
375 })();
376 })();
376
377
377
378
378 return {
379 return {
379 regex_split : regex_split,
380 regex_split : regex_split,
380 uuid : uuid,
381 uuid : uuid,
381 fixConsole : fixConsole,
382 fixConsole : fixConsole,
382 keycodes : keycodes,
383 keycodes : keycodes,
383 fixCarriageReturn : fixCarriageReturn,
384 fixCarriageReturn : fixCarriageReturn,
384 autoLinkUrls : autoLinkUrls,
385 autoLinkUrls : autoLinkUrls,
385 points_to_pixels : points_to_pixels,
386 points_to_pixels : points_to_pixels,
386 browser : browser
387 browser : browser
387 };
388 };
388
389
389 }(IPython));
390 }(IPython));
390
391
@@ -1,335 +1,336 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Cell
9 // Cell
10 //============================================================================
10 //============================================================================
11 /**
11 /**
12 * An extendable module that provide base functionnality to create cell for notebook.
12 * An extendable module that provide base functionnality to create cell for notebook.
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule Cell
15 * @submodule Cell
16 */
16 */
17
17
18 var IPython = (function (IPython) {
18 var IPython = (function (IPython) {
19 "use strict";
19
20
20 var utils = IPython.utils;
21 var utils = IPython.utils;
21
22
22 /**
23 /**
23 * The Base `Cell` class from which to inherit
24 * The Base `Cell` class from which to inherit
24 * @class Cell
25 * @class Cell
25 **/
26 **/
26
27
27 /*
28 /*
28 * @constructor
29 * @constructor
29 *
30 *
30 * @param {object|undefined} [options]
31 * @param {object|undefined} [options]
31 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
32 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
32 */
33 */
33 var Cell = function (options) {
34 var Cell = function (options) {
34
35
35 options = this.mergeopt(Cell, options)
36 options = this.mergeopt(Cell, options)
36 // superclass default overwrite our default
37 // superclass default overwrite our default
37
38
38 this.placeholder = options.placeholder || '';
39 this.placeholder = options.placeholder || '';
39 this.read_only = options.cm_config.readOnly;
40 this.read_only = options.cm_config.readOnly;
40 this.selected = false;
41 this.selected = false;
41 this.element = null;
42 this.element = null;
42 this.metadata = {};
43 this.metadata = {};
43 // load this from metadata later ?
44 // load this from metadata later ?
44 this.user_highlight = 'auto';
45 this.user_highlight = 'auto';
45 this.cm_config = options.cm_config;
46 this.cm_config = options.cm_config;
46 this.create_element();
47 this.create_element();
47 if (this.element !== null) {
48 if (this.element !== null) {
48 this.element.data("cell", this);
49 this.element.data("cell", this);
49 this.bind_events();
50 this.bind_events();
50 }
51 }
51 this.cell_id = utils.uuid();
52 this.cell_id = utils.uuid();
52 this._options = options;
53 this._options = options;
53 };
54 };
54
55
55 Cell.options_default = {
56 Cell.options_default = {
56 cm_config : {
57 cm_config : {
57 indentUnit : 4,
58 indentUnit : 4,
58 readOnly: false,
59 readOnly: false,
59 theme: "default"
60 theme: "default"
60 }
61 }
61 };
62 };
62
63
63 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
64 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
64 // by disabling drag/drop altogether on Safari
65 // by disabling drag/drop altogether on Safari
65 // https://github.com/marijnh/CodeMirror/issues/332
66 // https://github.com/marijnh/CodeMirror/issues/332
66
67
67 if (utils.browser[0] == "Safari") {
68 if (utils.browser[0] == "Safari") {
68 Cell.options_default.cm_config.dragDrop = false;
69 Cell.options_default.cm_config.dragDrop = false;
69 }
70 }
70
71
71 Cell.prototype.mergeopt = function(_class, options, overwrite){
72 Cell.prototype.mergeopt = function(_class, options, overwrite){
72 overwrite = overwrite || {};
73 overwrite = overwrite || {};
73 return $.extend(true, {}, _class.options_default, options, overwrite)
74 return $.extend(true, {}, _class.options_default, options, overwrite)
74
75
75 }
76 }
76
77
77
78
78
79
79 /**
80 /**
80 * Empty. Subclasses must implement create_element.
81 * Empty. Subclasses must implement create_element.
81 * This should contain all the code to create the DOM element in notebook
82 * This should contain all the code to create the DOM element in notebook
82 * and will be called by Base Class constructor.
83 * and will be called by Base Class constructor.
83 * @method create_element
84 * @method create_element
84 */
85 */
85 Cell.prototype.create_element = function () {
86 Cell.prototype.create_element = function () {
86 };
87 };
87
88
88
89
89 /**
90 /**
90 * Subclasses can implement override bind_events.
91 * Subclasses can implement override bind_events.
91 * Be carefull to call the parent method when overwriting as it fires event.
92 * Be carefull to call the parent method when overwriting as it fires event.
92 * this will be triggerd after create_element in constructor.
93 * this will be triggerd after create_element in constructor.
93 * @method bind_events
94 * @method bind_events
94 */
95 */
95 Cell.prototype.bind_events = function () {
96 Cell.prototype.bind_events = function () {
96 var that = this;
97 var that = this;
97 // We trigger events so that Cell doesn't have to depend on Notebook.
98 // We trigger events so that Cell doesn't have to depend on Notebook.
98 that.element.click(function (event) {
99 that.element.click(function (event) {
99 if (that.selected === false) {
100 if (that.selected === false) {
100 $([IPython.events]).trigger('select.Cell', {'cell':that});
101 $([IPython.events]).trigger('select.Cell', {'cell':that});
101 }
102 }
102 });
103 });
103 that.element.focusin(function (event) {
104 that.element.focusin(function (event) {
104 if (that.selected === false) {
105 if (that.selected === false) {
105 $([IPython.events]).trigger('select.Cell', {'cell':that});
106 $([IPython.events]).trigger('select.Cell', {'cell':that});
106 }
107 }
107 });
108 });
108 if (this.code_mirror) {
109 if (this.code_mirror) {
109 this.code_mirror.on("change", function(cm, change) {
110 this.code_mirror.on("change", function(cm, change) {
110 $([IPython.events]).trigger("set_dirty.Notebook", {value: true});
111 $([IPython.events]).trigger("set_dirty.Notebook", {value: true});
111 });
112 });
112 }
113 }
113 };
114 };
114
115
115 /**
116 /**
116 * Triger typsetting of math by mathjax on current cell element
117 * Triger typsetting of math by mathjax on current cell element
117 * @method typeset
118 * @method typeset
118 */
119 */
119 Cell.prototype.typeset = function () {
120 Cell.prototype.typeset = function () {
120 if (window.MathJax){
121 if (window.MathJax){
121 var cell_math = this.element.get(0);
122 var cell_math = this.element.get(0);
122 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
123 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
123 }
124 }
124 };
125 };
125
126
126 /**
127 /**
127 * should be triggerd when cell is selected
128 * should be triggerd when cell is selected
128 * @method select
129 * @method select
129 */
130 */
130 Cell.prototype.select = function () {
131 Cell.prototype.select = function () {
131 this.element.addClass('selected');
132 this.element.addClass('selected');
132 this.selected = true;
133 this.selected = true;
133 };
134 };
134
135
135
136
136 /**
137 /**
137 * should be triggerd when cell is unselected
138 * should be triggerd when cell is unselected
138 * @method unselect
139 * @method unselect
139 */
140 */
140 Cell.prototype.unselect = function () {
141 Cell.prototype.unselect = function () {
141 this.element.removeClass('selected');
142 this.element.removeClass('selected');
142 this.selected = false;
143 this.selected = false;
143 };
144 };
144
145
145 /**
146 /**
146 * should be overritten by subclass
147 * should be overritten by subclass
147 * @method get_text
148 * @method get_text
148 */
149 */
149 Cell.prototype.get_text = function () {
150 Cell.prototype.get_text = function () {
150 };
151 };
151
152
152 /**
153 /**
153 * should be overritten by subclass
154 * should be overritten by subclass
154 * @method set_text
155 * @method set_text
155 * @param {string} text
156 * @param {string} text
156 */
157 */
157 Cell.prototype.set_text = function (text) {
158 Cell.prototype.set_text = function (text) {
158 };
159 };
159
160
160 /**
161 /**
161 * Refresh codemirror instance
162 * Refresh codemirror instance
162 * @method refresh
163 * @method refresh
163 */
164 */
164 Cell.prototype.refresh = function () {
165 Cell.prototype.refresh = function () {
165 this.code_mirror.refresh();
166 this.code_mirror.refresh();
166 };
167 };
167
168
168
169
169 /**
170 /**
170 * should be overritten by subclass
171 * should be overritten by subclass
171 * @method edit
172 * @method edit
172 **/
173 **/
173 Cell.prototype.edit = function () {
174 Cell.prototype.edit = function () {
174 };
175 };
175
176
176
177
177 /**
178 /**
178 * should be overritten by subclass
179 * should be overritten by subclass
179 * @method render
180 * @method render
180 **/
181 **/
181 Cell.prototype.render = function () {
182 Cell.prototype.render = function () {
182 };
183 };
183
184
184 /**
185 /**
185 * should be overritten by subclass
186 * should be overritten by subclass
186 * serialise cell to json.
187 * serialise cell to json.
187 * @method toJSON
188 * @method toJSON
188 **/
189 **/
189 Cell.prototype.toJSON = function () {
190 Cell.prototype.toJSON = function () {
190 var data = {};
191 var data = {};
191 data.metadata = this.metadata;
192 data.metadata = this.metadata;
192 return data;
193 return data;
193 };
194 };
194
195
195
196
196 /**
197 /**
197 * should be overritten by subclass
198 * should be overritten by subclass
198 * @method fromJSON
199 * @method fromJSON
199 **/
200 **/
200 Cell.prototype.fromJSON = function (data) {
201 Cell.prototype.fromJSON = function (data) {
201 if (data.metadata !== undefined) {
202 if (data.metadata !== undefined) {
202 this.metadata = data.metadata;
203 this.metadata = data.metadata;
203 }
204 }
204 this.celltoolbar.rebuild();
205 this.celltoolbar.rebuild();
205 };
206 };
206
207
207
208
208 /**
209 /**
209 * can the cell be splitted in 2 cells.
210 * can the cell be splitted in 2 cells.
210 * @method is_splittable
211 * @method is_splittable
211 **/
212 **/
212 Cell.prototype.is_splittable = function () {
213 Cell.prototype.is_splittable = function () {
213 return true;
214 return true;
214 };
215 };
215
216
216
217
217 /**
218 /**
218 * @return {String} - the text before the cursor
219 * @return {String} - the text before the cursor
219 * @method get_pre_cursor
220 * @method get_pre_cursor
220 **/
221 **/
221 Cell.prototype.get_pre_cursor = function () {
222 Cell.prototype.get_pre_cursor = function () {
222 var cursor = this.code_mirror.getCursor();
223 var cursor = this.code_mirror.getCursor();
223 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
224 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
224 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
225 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
225 return text;
226 return text;
226 }
227 }
227
228
228
229
229 /**
230 /**
230 * @return {String} - the text after the cursor
231 * @return {String} - the text after the cursor
231 * @method get_post_cursor
232 * @method get_post_cursor
232 **/
233 **/
233 Cell.prototype.get_post_cursor = function () {
234 Cell.prototype.get_post_cursor = function () {
234 var cursor = this.code_mirror.getCursor();
235 var cursor = this.code_mirror.getCursor();
235 var last_line_num = this.code_mirror.lineCount()-1;
236 var last_line_num = this.code_mirror.lineCount()-1;
236 var last_line_len = this.code_mirror.getLine(last_line_num).length;
237 var last_line_len = this.code_mirror.getLine(last_line_num).length;
237 var end = {line:last_line_num, ch:last_line_len}
238 var end = {line:last_line_num, ch:last_line_len}
238 var text = this.code_mirror.getRange(cursor, end);
239 var text = this.code_mirror.getRange(cursor, end);
239 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
240 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
240 return text;
241 return text;
241 };
242 };
242
243
243 /**
244 /**
244 * Show/Hide CodeMirror LineNumber
245 * Show/Hide CodeMirror LineNumber
245 * @method show_line_numbers
246 * @method show_line_numbers
246 *
247 *
247 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
248 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
248 **/
249 **/
249 Cell.prototype.show_line_numbers = function (value) {
250 Cell.prototype.show_line_numbers = function (value) {
250 this.code_mirror.setOption('lineNumbers', value);
251 this.code_mirror.setOption('lineNumbers', value);
251 this.code_mirror.refresh();
252 this.code_mirror.refresh();
252 };
253 };
253
254
254 /**
255 /**
255 * Toggle CodeMirror LineNumber
256 * Toggle CodeMirror LineNumber
256 * @method toggle_line_numbers
257 * @method toggle_line_numbers
257 **/
258 **/
258 Cell.prototype.toggle_line_numbers = function () {
259 Cell.prototype.toggle_line_numbers = function () {
259 var val = this.code_mirror.getOption('lineNumbers');
260 var val = this.code_mirror.getOption('lineNumbers');
260 this.show_line_numbers(!val);
261 this.show_line_numbers(!val);
261 };
262 };
262
263
263 /**
264 /**
264 * Force codemirror highlight mode
265 * Force codemirror highlight mode
265 * @method force_highlight
266 * @method force_highlight
266 * @param {object} - CodeMirror mode
267 * @param {object} - CodeMirror mode
267 **/
268 **/
268 Cell.prototype.force_highlight = function(mode) {
269 Cell.prototype.force_highlight = function(mode) {
269 this.user_highlight = mode;
270 this.user_highlight = mode;
270 this.auto_highlight();
271 this.auto_highlight();
271 };
272 };
272
273
273 /**
274 /**
274 * Try to autodetect cell highlight mode, or use selected mode
275 * Try to autodetect cell highlight mode, or use selected mode
275 * @methods _auto_highlight
276 * @methods _auto_highlight
276 * @private
277 * @private
277 * @param {String|object|undefined} - CodeMirror mode | 'auto'
278 * @param {String|object|undefined} - CodeMirror mode | 'auto'
278 **/
279 **/
279 Cell.prototype._auto_highlight = function (modes) {
280 Cell.prototype._auto_highlight = function (modes) {
280 //Here we handle manually selected modes
281 //Here we handle manually selected modes
281 if( this.user_highlight != undefined && this.user_highlight != 'auto' )
282 if( this.user_highlight != undefined && this.user_highlight != 'auto' )
282 {
283 {
283 var mode = this.user_highlight;
284 var mode = this.user_highlight;
284 CodeMirror.autoLoadMode(this.code_mirror, mode);
285 CodeMirror.autoLoadMode(this.code_mirror, mode);
285 this.code_mirror.setOption('mode', mode);
286 this.code_mirror.setOption('mode', mode);
286 return;
287 return;
287 }
288 }
288 var first_line = this.code_mirror.getLine(0);
289 var first_line = this.code_mirror.getLine(0);
289 // loop on every pairs
290 // loop on every pairs
290 for( var mode in modes) {
291 for( var mode in modes) {
291 var regs = modes[mode]['reg'];
292 var regs = modes[mode]['reg'];
292 // only one key every time but regexp can't be keys...
293 // only one key every time but regexp can't be keys...
293 for(var reg in regs ) {
294 for(var reg in regs ) {
294 // here we handle non magic_modes
295 // here we handle non magic_modes
295 if(first_line.match(regs[reg]) != null) {
296 if(first_line.match(regs[reg]) != null) {
296 if (mode.search('magic_') != 0) {
297 if (mode.search('magic_') != 0) {
297 this.code_mirror.setOption('mode', mode);
298 this.code_mirror.setOption('mode', mode);
298 CodeMirror.autoLoadMode(this.code_mirror, mode);
299 CodeMirror.autoLoadMode(this.code_mirror, mode);
299 return;
300 return;
300 }
301 }
301 var open = modes[mode]['open']|| "%%";
302 var open = modes[mode]['open']|| "%%";
302 var close = modes[mode]['close']|| "%%end";
303 var close = modes[mode]['close']|| "%%end";
303 var mmode = mode;
304 var mmode = mode;
304 mode = mmode.substr(6);
305 mode = mmode.substr(6);
305 CodeMirror.autoLoadMode(this.code_mirror, mode);
306 CodeMirror.autoLoadMode(this.code_mirror, mode);
306 // create on the fly a mode that swhitch between
307 // create on the fly a mode that swhitch between
307 // plain/text and smth else otherwise `%%` is
308 // plain/text and smth else otherwise `%%` is
308 // source of some highlight issues.
309 // source of some highlight issues.
309 // we use patchedGetMode to circumvent a bug in CM
310 // we use patchedGetMode to circumvent a bug in CM
310 CodeMirror.defineMode(mmode , function(config) {
311 CodeMirror.defineMode(mmode , function(config) {
311 return CodeMirror.multiplexingMode(
312 return CodeMirror.multiplexingMode(
312 CodeMirror.patchedGetMode(config, 'text/plain'),
313 CodeMirror.patchedGetMode(config, 'text/plain'),
313 // always set someting on close
314 // always set someting on close
314 {open: open, close: close,
315 {open: open, close: close,
315 mode: CodeMirror.patchedGetMode(config, mode),
316 mode: CodeMirror.patchedGetMode(config, mode),
316 delimStyle: "delimit"
317 delimStyle: "delimit"
317 }
318 }
318 );
319 );
319 });
320 });
320 this.code_mirror.setOption('mode', mmode);
321 this.code_mirror.setOption('mode', mmode);
321 return;
322 return;
322 }
323 }
323 }
324 }
324 }
325 }
325 // fallback on default (python)
326 // fallback on default (python)
326 var default_mode = this.default_mode || 'text/plain';
327 var default_mode = this.default_mode || 'text/plain';
327 this.code_mirror.setOption('mode', default_mode);
328 this.code_mirror.setOption('mode', default_mode);
328 };
329 };
329
330
330 IPython.Cell = Cell;
331 IPython.Cell = Cell;
331
332
332 return IPython;
333 return IPython;
333
334
334 }(IPython));
335 }(IPython));
335
336
@@ -1,16 +1,17 b''
1 // IPython mode is just a slightly altered Python Mode with `?` beeing a extra
1 // IPython mode is just a slightly altered Python Mode with `?` beeing a extra
2 // single operator. Here we define `ipython` mode in the require `python`
2 // single operator. Here we define `ipython` mode in the require `python`
3 // callback to auto-load python mode, which is more likely not the best things
3 // callback to auto-load python mode, which is more likely not the best things
4 // to do, but at least the simple one for now.
4 // to do, but at least the simple one for now.
5
5
6 CodeMirror.requireMode('python',function(){
6 CodeMirror.requireMode('python',function(){
7 "use strict";
7
8
8 CodeMirror.defineMode("ipython", function(conf, parserConf) {
9 CodeMirror.defineMode("ipython", function(conf, parserConf) {
9
10
10 parserConf.singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]");
11 parserConf.singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]");
11 parserConf.name = 'python'
12 parserConf.name = 'python'
12 return CodeMirror.getMode(conf, parserConf);
13 return CodeMirror.getMode(conf, parserConf);
13 }, 'python');
14 }, 'python');
14
15
15 CodeMirror.defineMIME("text/x-ipython", "ipython");
16 CodeMirror.defineMIME("text/x-ipython", "ipython");
16 })
17 })
@@ -1,78 +1,79 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2012 The IPython Development Team
2 // Copyright (C) 2012 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 **/
15 **/
16
16
17 var IPython = (function (IPython) {
17 var IPython = (function (IPython) {
18 "use strict";
18 /**
19 /**
19 * A place where some stuff can be confugured.
20 * A place where some stuff can be confugured.
20 *
21 *
21 * @class config
22 * @class config
22 * @static
23 * @static
23 *
24 *
24 **/
25 **/
25 var default_config = {
26 var default_config = {
26 /**
27 /**
27 * Dictionary of object to autodetect highlight mode for code cell.
28 * Dictionary of object to autodetect highlight mode for code cell.
28 * Item of the dictionnary should take the form :
29 * Item of the dictionnary should take the form :
29 *
30 *
30 * key : {'reg':[list_of_regexp]}
31 * key : {'reg':[list_of_regexp]}
31 *
32 *
32 * where `key` will be the code mirror mode name
33 * where `key` will be the code mirror mode name
33 * and `list_of_regexp` should be a list of regext that should match
34 * and `list_of_regexp` should be a list of regext that should match
34 * the first line of the cell to trigger this mode.
35 * the first line of the cell to trigger this mode.
35 *
36 *
36 * if `key` is prefixed by the `magic_` prefix the codemirror `mode`
37 * if `key` is prefixed by the `magic_` prefix the codemirror `mode`
37 * will be applied only at the end of the first line
38 * will be applied only at the end of the first line
38 *
39 *
39 * @attribute cell_magic_highlight
40 * @attribute cell_magic_highlight
40 * @example
41 * @example
41 * This would trigger javascript mode
42 * This would trigger javascript mode
42 * from the second line if first line start with `%%javascript` or `%%jsmagic`
43 * from the second line if first line start with `%%javascript` or `%%jsmagic`
43 *
44 *
44 * cell_magic_highlight['magic_javascript'] = {'reg':[/^%%javascript/,/^%%jsmagic/]}
45 * cell_magic_highlight['magic_javascript'] = {'reg':[/^%%javascript/,/^%%jsmagic/]}
45 * @example
46 * @example
46 * This would trigger javascript mode
47 * This would trigger javascript mode
47 * from the second line if first line start with `var`
48 * from the second line if first line start with `var`
48 *
49 *
49 * cell_magic_highlight['javascript'] = {'reg':[/^var/]}
50 * cell_magic_highlight['javascript'] = {'reg':[/^var/]}
50 */
51 */
51 cell_magic_highlight : {
52 cell_magic_highlight : {
52 'magic_javascript':{'reg':[/^%%javascript/]}
53 'magic_javascript':{'reg':[/^%%javascript/]}
53 ,'magic_perl' :{'reg':[/^%%perl/]}
54 ,'magic_perl' :{'reg':[/^%%perl/]}
54 ,'magic_ruby' :{'reg':[/^%%ruby/]}
55 ,'magic_ruby' :{'reg':[/^%%ruby/]}
55 ,'magic_python' :{'reg':[/^%%python3?/]}
56 ,'magic_python' :{'reg':[/^%%python3?/]}
56 ,'magic_shell' :{'reg':[/^%%bash/]}
57 ,'magic_shell' :{'reg':[/^%%bash/]}
57 ,'magic_r' :{'reg':[/^%%R/]}
58 ,'magic_r' :{'reg':[/^%%R/]}
58 },
59 },
59
60
60 /**
61 /**
61 * same as `cell_magic_highlight` but for raw cells
62 * same as `cell_magic_highlight` but for raw cells
62 * @attribute raw_cell_highlight
63 * @attribute raw_cell_highlight
63 */
64 */
64 raw_cell_highlight : {
65 raw_cell_highlight : {
65 'diff' :{'reg':[/^diff/]}
66 'diff' :{'reg':[/^diff/]}
66 },
67 },
67
68
68 tooltip_on_tab : true,
69 tooltip_on_tab : true,
69 };
70 };
70
71
71 // use the same method to merge user configuration
72 // use the same method to merge user configuration
72 IPython.config = {};
73 IPython.config = {};
73 $.extend(IPython.config, default_config);
74 $.extend(IPython.config, default_config);
74
75
75 return IPython;
76 return IPython;
76
77
77 }(IPython));
78 }(IPython));
78
79
@@ -1,62 +1,63 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Layout
9 // Layout
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13
14
14 var LayoutManager = function () {
15 var LayoutManager = function () {
15 this.bind_events();
16 this.bind_events();
16 };
17 };
17
18
18
19
19 LayoutManager.prototype.bind_events = function () {
20 LayoutManager.prototype.bind_events = function () {
20 $(window).resize($.proxy(this.do_resize,this));
21 $(window).resize($.proxy(this.do_resize,this));
21 };
22 };
22
23
23 LayoutManager.prototype.app_height = function() {
24 LayoutManager.prototype.app_height = function() {
24 var win = $(window);
25 var win = $(window);
25 var w = win.width();
26 var w = win.width();
26 var h = win.height();
27 var h = win.height();
27 var header_height;
28 var header_height;
28 if ($('div#header').css('display') === 'none') {
29 if ($('div#header').css('display') === 'none') {
29 header_height = 0;
30 header_height = 0;
30 } else {
31 } else {
31 header_height = $('div#header').outerHeight(true);
32 header_height = $('div#header').outerHeight(true);
32 }
33 }
33 var menubar_height = $('div#menubar').outerHeight(true);
34 var menubar_height = $('div#menubar').outerHeight(true);
34 var toolbar_height;
35 var toolbar_height;
35 if ($('div#maintoolbar').css('display') === 'none') {
36 if ($('div#maintoolbar').css('display') === 'none') {
36 toolbar_height = 0;
37 toolbar_height = 0;
37 } else {
38 } else {
38 toolbar_height = $('div#maintoolbar').outerHeight(true);
39 toolbar_height = $('div#maintoolbar').outerHeight(true);
39 }
40 }
40 return h-header_height-menubar_height-toolbar_height; // content height
41 return h-header_height-menubar_height-toolbar_height; // content height
41 }
42 }
42
43
43 LayoutManager.prototype.do_resize = function () {
44 LayoutManager.prototype.do_resize = function () {
44 var app_height = this.app_height() // content height
45 var app_height = this.app_height() // content height
45
46
46 $('#ipython-main-app').height(app_height); // content+padding+border height
47 $('#ipython-main-app').height(app_height); // content+padding+border height
47
48
48 var pager_height = IPython.pager.percentage_height*app_height;
49 var pager_height = IPython.pager.percentage_height*app_height;
49 var pager_splitter_height = $('div#pager_splitter').outerHeight(true);
50 var pager_splitter_height = $('div#pager_splitter').outerHeight(true);
50 $('div#pager').outerHeight(pager_height);
51 $('div#pager').outerHeight(pager_height);
51 if (IPython.pager.expanded) {
52 if (IPython.pager.expanded) {
52 $('div#notebook').outerHeight(app_height-pager_height-pager_splitter_height);
53 $('div#notebook').outerHeight(app_height-pager_height-pager_splitter_height);
53 } else {
54 } else {
54 $('div#notebook').outerHeight(app_height-pager_splitter_height);
55 $('div#notebook').outerHeight(app_height-pager_splitter_height);
55 }
56 }
56 };
57 };
57
58
58 IPython.LayoutManager = LayoutManager;
59 IPython.LayoutManager = LayoutManager;
59
60
60 return IPython;
61 return IPython;
61
62
62 }(IPython));
63 }(IPython));
@@ -1,206 +1,207 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // ToolBar
9 // ToolBar
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13
14
14 var MainToolBar = function (selector) {
15 var MainToolBar = function (selector) {
15 IPython.ToolBar.apply(this, arguments);
16 IPython.ToolBar.apply(this, arguments);
16 this.construct();
17 this.construct();
17 this.add_celltype_list();
18 this.add_celltype_list();
18 this.add_celltoolbar_list();
19 this.add_celltoolbar_list();
19 this.bind_events();
20 this.bind_events();
20 };
21 };
21
22
22 MainToolBar.prototype = new IPython.ToolBar();
23 MainToolBar.prototype = new IPython.ToolBar();
23
24
24 MainToolBar.prototype.construct = function () {
25 MainToolBar.prototype.construct = function () {
25 this.add_buttons_group([
26 this.add_buttons_group([
26 {
27 {
27 id : 'save_b',
28 id : 'save_b',
28 label : 'Save and Checkpoint',
29 label : 'Save and Checkpoint',
29 icon : 'icon-save',
30 icon : 'icon-save',
30 callback : function () {
31 callback : function () {
31 IPython.notebook.save_checkpoint();
32 IPython.notebook.save_checkpoint();
32 }
33 }
33 }
34 }
34 ]);
35 ]);
35 this.add_buttons_group([
36 this.add_buttons_group([
36 {
37 {
37 id : 'cut_b',
38 id : 'cut_b',
38 label : 'Cut Cell',
39 label : 'Cut Cell',
39 icon : 'icon-cut',
40 icon : 'icon-cut',
40 callback : function () {
41 callback : function () {
41 IPython.notebook.cut_cell();
42 IPython.notebook.cut_cell();
42 }
43 }
43 },
44 },
44 {
45 {
45 id : 'copy_b',
46 id : 'copy_b',
46 label : 'Copy Cell',
47 label : 'Copy Cell',
47 icon : 'icon-copy',
48 icon : 'icon-copy',
48 callback : function () {
49 callback : function () {
49 IPython.notebook.copy_cell();
50 IPython.notebook.copy_cell();
50 }
51 }
51 },
52 },
52 {
53 {
53 id : 'paste_b',
54 id : 'paste_b',
54 label : 'Paste Cell Below',
55 label : 'Paste Cell Below',
55 icon : 'icon-paste',
56 icon : 'icon-paste',
56 callback : function () {
57 callback : function () {
57 IPython.notebook.paste_cell_below();
58 IPython.notebook.paste_cell_below();
58 }
59 }
59 }
60 }
60 ],'cut_copy_paste');
61 ],'cut_copy_paste');
61
62
62 this.add_buttons_group([
63 this.add_buttons_group([
63 {
64 {
64 id : 'move_up_b',
65 id : 'move_up_b',
65 label : 'Move Cell Up',
66 label : 'Move Cell Up',
66 icon : 'icon-arrow-up',
67 icon : 'icon-arrow-up',
67 callback : function () {
68 callback : function () {
68 IPython.notebook.move_cell_up();
69 IPython.notebook.move_cell_up();
69 }
70 }
70 },
71 },
71 {
72 {
72 id : 'move_down_b',
73 id : 'move_down_b',
73 label : 'Move Cell Down',
74 label : 'Move Cell Down',
74 icon : 'icon-arrow-down',
75 icon : 'icon-arrow-down',
75 callback : function () {
76 callback : function () {
76 IPython.notebook.move_cell_down();
77 IPython.notebook.move_cell_down();
77 }
78 }
78 }
79 }
79 ],'move_up_down');
80 ],'move_up_down');
80
81
81 this.add_buttons_group([
82 this.add_buttons_group([
82 {
83 {
83 id : 'insert_above_b',
84 id : 'insert_above_b',
84 label : 'Insert Cell Above',
85 label : 'Insert Cell Above',
85 icon : 'icon-circle-arrow-up',
86 icon : 'icon-circle-arrow-up',
86 callback : function () {
87 callback : function () {
87 IPython.notebook.insert_cell_above('code');
88 IPython.notebook.insert_cell_above('code');
88 }
89 }
89 },
90 },
90 {
91 {
91 id : 'insert_below_b',
92 id : 'insert_below_b',
92 label : 'Insert Cell Below',
93 label : 'Insert Cell Below',
93 icon : 'icon-circle-arrow-down',
94 icon : 'icon-circle-arrow-down',
94 callback : function () {
95 callback : function () {
95 IPython.notebook.insert_cell_below('code');
96 IPython.notebook.insert_cell_below('code');
96 }
97 }
97 }
98 }
98 ],'insert_above_below');
99 ],'insert_above_below');
99
100
100 this.add_buttons_group([
101 this.add_buttons_group([
101 {
102 {
102 id : 'run_b',
103 id : 'run_b',
103 label : 'Run Cell',
104 label : 'Run Cell',
104 icon : 'icon-play',
105 icon : 'icon-play',
105 callback : function () {
106 callback : function () {
106 IPython.notebook.execute_selected_cell();
107 IPython.notebook.execute_selected_cell();
107 }
108 }
108 },
109 },
109 {
110 {
110 id : 'interrupt_b',
111 id : 'interrupt_b',
111 label : 'Interrupt',
112 label : 'Interrupt',
112 icon : 'icon-stop',
113 icon : 'icon-stop',
113 callback : function () {
114 callback : function () {
114 IPython.notebook.kernel.interrupt();
115 IPython.notebook.kernel.interrupt();
115 }
116 }
116 }
117 }
117 ],'run_int');
118 ],'run_int');
118 };
119 };
119
120
120 MainToolBar.prototype.add_celltype_list = function () {
121 MainToolBar.prototype.add_celltype_list = function () {
121 this.element
122 this.element
122 .append($('<select/>')
123 .append($('<select/>')
123 .attr('id','cell_type')
124 .attr('id','cell_type')
124 // .addClass('ui-widget-content')
125 // .addClass('ui-widget-content')
125 .append($('<option/>').attr('value','code').text('Code'))
126 .append($('<option/>').attr('value','code').text('Code'))
126 .append($('<option/>').attr('value','markdown').text('Markdown'))
127 .append($('<option/>').attr('value','markdown').text('Markdown'))
127 .append($('<option/>').attr('value','raw').text('Raw Text'))
128 .append($('<option/>').attr('value','raw').text('Raw Text'))
128 .append($('<option/>').attr('value','heading1').text('Heading 1'))
129 .append($('<option/>').attr('value','heading1').text('Heading 1'))
129 .append($('<option/>').attr('value','heading2').text('Heading 2'))
130 .append($('<option/>').attr('value','heading2').text('Heading 2'))
130 .append($('<option/>').attr('value','heading3').text('Heading 3'))
131 .append($('<option/>').attr('value','heading3').text('Heading 3'))
131 .append($('<option/>').attr('value','heading4').text('Heading 4'))
132 .append($('<option/>').attr('value','heading4').text('Heading 4'))
132 .append($('<option/>').attr('value','heading5').text('Heading 5'))
133 .append($('<option/>').attr('value','heading5').text('Heading 5'))
133 .append($('<option/>').attr('value','heading6').text('Heading 6'))
134 .append($('<option/>').attr('value','heading6').text('Heading 6'))
134 );
135 );
135 };
136 };
136
137
137
138
138 MainToolBar.prototype.add_celltoolbar_list = function () {
139 MainToolBar.prototype.add_celltoolbar_list = function () {
139 var label = $('<span/>').addClass("navbar-text").text('Cell Toolbar:');
140 var label = $('<span/>').addClass("navbar-text").text('Cell Toolbar:');
140 var select = $('<select/>')
141 var select = $('<select/>')
141 // .addClass('ui-widget-content')
142 // .addClass('ui-widget-content')
142 .attr('id', 'ctb_select')
143 .attr('id', 'ctb_select')
143 .append($('<option/>').attr('value', '').text('None'));
144 .append($('<option/>').attr('value', '').text('None'));
144 this.element.append(label).append(select);
145 this.element.append(label).append(select);
145 select.change(function() {
146 select.change(function() {
146 var val = $(this).val()
147 var val = $(this).val()
147 if (val =='') {
148 if (val =='') {
148 IPython.CellToolbar.global_hide();
149 IPython.CellToolbar.global_hide();
149 } else {
150 } else {
150 IPython.CellToolbar.global_show();
151 IPython.CellToolbar.global_show();
151 IPython.CellToolbar.activate_preset(val);
152 IPython.CellToolbar.activate_preset(val);
152 }
153 }
153 });
154 });
154 // Setup the currently registered presets.
155 // Setup the currently registered presets.
155 var presets = IPython.CellToolbar.list_presets();
156 var presets = IPython.CellToolbar.list_presets();
156 for (var i=0; i<presets.length; i++) {
157 for (var i=0; i<presets.length; i++) {
157 var name = presets[i];
158 var name = presets[i];
158 select.append($('<option/>').attr('value', name).text(name));
159 select.append($('<option/>').attr('value', name).text(name));
159 }
160 }
160 // Setup future preset registrations.
161 // Setup future preset registrations.
161 $([IPython.events]).on('preset_added.CellToolbar', function (event, data) {
162 $([IPython.events]).on('preset_added.CellToolbar', function (event, data) {
162 var name = data.name;
163 var name = data.name;
163 select.append($('<option/>').attr('value', name).text(name));
164 select.append($('<option/>').attr('value', name).text(name));
164 });
165 });
165 };
166 };
166
167
167
168
168 MainToolBar.prototype.bind_events = function () {
169 MainToolBar.prototype.bind_events = function () {
169 var that = this;
170 var that = this;
170
171
171 this.element.find('#cell_type').change(function () {
172 this.element.find('#cell_type').change(function () {
172 var cell_type = $(this).val();
173 var cell_type = $(this).val();
173 if (cell_type === 'code') {
174 if (cell_type === 'code') {
174 IPython.notebook.to_code();
175 IPython.notebook.to_code();
175 } else if (cell_type === 'markdown') {
176 } else if (cell_type === 'markdown') {
176 IPython.notebook.to_markdown();
177 IPython.notebook.to_markdown();
177 } else if (cell_type === 'raw') {
178 } else if (cell_type === 'raw') {
178 IPython.notebook.to_raw();
179 IPython.notebook.to_raw();
179 } else if (cell_type === 'heading1') {
180 } else if (cell_type === 'heading1') {
180 IPython.notebook.to_heading(undefined, 1);
181 IPython.notebook.to_heading(undefined, 1);
181 } else if (cell_type === 'heading2') {
182 } else if (cell_type === 'heading2') {
182 IPython.notebook.to_heading(undefined, 2);
183 IPython.notebook.to_heading(undefined, 2);
183 } else if (cell_type === 'heading3') {
184 } else if (cell_type === 'heading3') {
184 IPython.notebook.to_heading(undefined, 3);
185 IPython.notebook.to_heading(undefined, 3);
185 } else if (cell_type === 'heading4') {
186 } else if (cell_type === 'heading4') {
186 IPython.notebook.to_heading(undefined, 4);
187 IPython.notebook.to_heading(undefined, 4);
187 } else if (cell_type === 'heading5') {
188 } else if (cell_type === 'heading5') {
188 IPython.notebook.to_heading(undefined, 5);
189 IPython.notebook.to_heading(undefined, 5);
189 } else if (cell_type === 'heading6') {
190 } else if (cell_type === 'heading6') {
190 IPython.notebook.to_heading(undefined, 6);
191 IPython.notebook.to_heading(undefined, 6);
191 }
192 }
192 });
193 });
193 $([IPython.events]).on('selected_cell_type_changed.Notebook', function (event, data) {
194 $([IPython.events]).on('selected_cell_type_changed.Notebook', function (event, data) {
194 if (data.cell_type === 'heading') {
195 if (data.cell_type === 'heading') {
195 that.element.find('#cell_type').val(data.cell_type+data.level);
196 that.element.find('#cell_type').val(data.cell_type+data.level);
196 } else {
197 } else {
197 that.element.find('#cell_type').val(data.cell_type);
198 that.element.find('#cell_type').val(data.cell_type);
198 }
199 }
199 });
200 });
200 };
201 };
201
202
202 IPython.MainToolBar = MainToolBar;
203 IPython.MainToolBar = MainToolBar;
203
204
204 return IPython;
205 return IPython;
205
206
206 }(IPython));
207 }(IPython));
@@ -1,269 +1,270 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // MenuBar
9 // MenuBar
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule MenuBar
15 * @submodule MenuBar
16 */
16 */
17
17
18
18
19 var IPython = (function (IPython) {
19 var IPython = (function (IPython) {
20 "use strict";
20
21
21 /**
22 /**
22 * A MenuBar Class to generate the menubar of IPython noteboko
23 * A MenuBar Class to generate the menubar of IPython noteboko
23 * @Class MenuBar
24 * @Class MenuBar
24 *
25 *
25 * @constructor
26 * @constructor
26 *
27 *
27 *
28 *
28 * @param selector {string} selector for the menubar element in DOM
29 * @param selector {string} selector for the menubar element in DOM
29 * @param {object} [options]
30 * @param {object} [options]
30 * @param [options.baseProjectUrl] {String} String to use for the
31 * @param [options.baseProjectUrl] {String} String to use for the
31 * Base Project url, default would be to inspect
32 * Base Project url, default would be to inspect
32 * $('body').data('baseProjectUrl');
33 * $('body').data('baseProjectUrl');
33 * does not support change for now is set through this option
34 * does not support change for now is set through this option
34 */
35 */
35 var MenuBar = function (selector, options) {
36 var MenuBar = function (selector, options) {
36 var options = options || {};
37 var options = options || {};
37 if(options.baseProjectUrl!= undefined){
38 if(options.baseProjectUrl!= undefined){
38 this._baseProjectUrl = options.baseProjectUrl;
39 this._baseProjectUrl = options.baseProjectUrl;
39 }
40 }
40 this.selector = selector;
41 this.selector = selector;
41 if (this.selector !== undefined) {
42 if (this.selector !== undefined) {
42 this.element = $(selector);
43 this.element = $(selector);
43 this.style();
44 this.style();
44 this.bind_events();
45 this.bind_events();
45 }
46 }
46 };
47 };
47
48
48 MenuBar.prototype.baseProjectUrl = function(){
49 MenuBar.prototype.baseProjectUrl = function(){
49 return this._baseProjectUrl || $('body').data('baseProjectUrl');
50 return this._baseProjectUrl || $('body').data('baseProjectUrl');
50 };
51 };
51
52
52
53
53 MenuBar.prototype.style = function () {
54 MenuBar.prototype.style = function () {
54 this.element.addClass('border-box-sizing');
55 this.element.addClass('border-box-sizing');
55 this.element.find("li").click(function (event, ui) {
56 this.element.find("li").click(function (event, ui) {
56 // The selected cell loses focus when the menu is entered, so we
57 // The selected cell loses focus when the menu is entered, so we
57 // re-select it upon selection.
58 // re-select it upon selection.
58 var i = IPython.notebook.get_selected_index();
59 var i = IPython.notebook.get_selected_index();
59 IPython.notebook.select(i);
60 IPython.notebook.select(i);
60 }
61 }
61 );
62 );
62 };
63 };
63
64
64
65
65 MenuBar.prototype.bind_events = function () {
66 MenuBar.prototype.bind_events = function () {
66 // File
67 // File
67 var that = this;
68 var that = this;
68 this.element.find('#new_notebook').click(function () {
69 this.element.find('#new_notebook').click(function () {
69 window.open(that.baseProjectUrl()+'new');
70 window.open(that.baseProjectUrl()+'new');
70 });
71 });
71 this.element.find('#open_notebook').click(function () {
72 this.element.find('#open_notebook').click(function () {
72 window.open(that.baseProjectUrl());
73 window.open(that.baseProjectUrl());
73 });
74 });
74 this.element.find('#rename_notebook').click(function () {
75 this.element.find('#rename_notebook').click(function () {
75 IPython.save_widget.rename_notebook();
76 IPython.save_widget.rename_notebook();
76 });
77 });
77 this.element.find('#copy_notebook').click(function () {
78 this.element.find('#copy_notebook').click(function () {
78 var notebook_id = IPython.notebook.get_notebook_id();
79 var notebook_id = IPython.notebook.get_notebook_id();
79 var url = that.baseProjectUrl() + notebook_id + '/copy';
80 var url = that.baseProjectUrl() + notebook_id + '/copy';
80 window.open(url,'_blank');
81 window.open(url,'_blank');
81 return false;
82 return false;
82 });
83 });
83 this.element.find('#save_checkpoint').click(function () {
84 this.element.find('#save_checkpoint').click(function () {
84 IPython.notebook.save_checkpoint();
85 IPython.notebook.save_checkpoint();
85 });
86 });
86 this.element.find('#restore_checkpoint').click(function () {
87 this.element.find('#restore_checkpoint').click(function () {
87 });
88 });
88 this.element.find('#download_ipynb').click(function () {
89 this.element.find('#download_ipynb').click(function () {
89 var notebook_id = IPython.notebook.get_notebook_id();
90 var notebook_id = IPython.notebook.get_notebook_id();
90 var url = that.baseProjectUrl() + 'notebooks/' +
91 var url = that.baseProjectUrl() + 'notebooks/' +
91 notebook_id + '?format=json';
92 notebook_id + '?format=json';
92 window.location.assign(url);
93 window.location.assign(url);
93 });
94 });
94 this.element.find('#download_py').click(function () {
95 this.element.find('#download_py').click(function () {
95 var notebook_id = IPython.notebook.get_notebook_id();
96 var notebook_id = IPython.notebook.get_notebook_id();
96 var url = that.baseProjectUrl() + 'notebooks/' +
97 var url = that.baseProjectUrl() + 'notebooks/' +
97 notebook_id + '?format=py';
98 notebook_id + '?format=py';
98 window.location.assign(url);
99 window.location.assign(url);
99 });
100 });
100 this.element.find('#kill_and_exit').click(function () {
101 this.element.find('#kill_and_exit').click(function () {
101 IPython.notebook.kernel.kill();
102 IPython.notebook.kernel.kill();
102 setTimeout(function(){window.close();}, 200);
103 setTimeout(function(){window.close();}, 200);
103 });
104 });
104 // Edit
105 // Edit
105 this.element.find('#cut_cell').click(function () {
106 this.element.find('#cut_cell').click(function () {
106 IPython.notebook.cut_cell();
107 IPython.notebook.cut_cell();
107 });
108 });
108 this.element.find('#copy_cell').click(function () {
109 this.element.find('#copy_cell').click(function () {
109 IPython.notebook.copy_cell();
110 IPython.notebook.copy_cell();
110 });
111 });
111 this.element.find('#delete_cell').click(function () {
112 this.element.find('#delete_cell').click(function () {
112 IPython.notebook.delete_cell();
113 IPython.notebook.delete_cell();
113 });
114 });
114 this.element.find('#undelete_cell').click(function () {
115 this.element.find('#undelete_cell').click(function () {
115 IPython.notebook.undelete();
116 IPython.notebook.undelete();
116 });
117 });
117 this.element.find('#split_cell').click(function () {
118 this.element.find('#split_cell').click(function () {
118 IPython.notebook.split_cell();
119 IPython.notebook.split_cell();
119 });
120 });
120 this.element.find('#merge_cell_above').click(function () {
121 this.element.find('#merge_cell_above').click(function () {
121 IPython.notebook.merge_cell_above();
122 IPython.notebook.merge_cell_above();
122 });
123 });
123 this.element.find('#merge_cell_below').click(function () {
124 this.element.find('#merge_cell_below').click(function () {
124 IPython.notebook.merge_cell_below();
125 IPython.notebook.merge_cell_below();
125 });
126 });
126 this.element.find('#move_cell_up').click(function () {
127 this.element.find('#move_cell_up').click(function () {
127 IPython.notebook.move_cell_up();
128 IPython.notebook.move_cell_up();
128 });
129 });
129 this.element.find('#move_cell_down').click(function () {
130 this.element.find('#move_cell_down').click(function () {
130 IPython.notebook.move_cell_down();
131 IPython.notebook.move_cell_down();
131 });
132 });
132 this.element.find('#select_previous').click(function () {
133 this.element.find('#select_previous').click(function () {
133 IPython.notebook.select_prev();
134 IPython.notebook.select_prev();
134 });
135 });
135 this.element.find('#select_next').click(function () {
136 this.element.find('#select_next').click(function () {
136 IPython.notebook.select_next();
137 IPython.notebook.select_next();
137 });
138 });
138 // View
139 // View
139 this.element.find('#toggle_header').click(function () {
140 this.element.find('#toggle_header').click(function () {
140 $('div#header').toggle();
141 $('div#header').toggle();
141 IPython.layout_manager.do_resize();
142 IPython.layout_manager.do_resize();
142 });
143 });
143 this.element.find('#toggle_toolbar').click(function () {
144 this.element.find('#toggle_toolbar').click(function () {
144 $('div#maintoolbar').toggle();
145 $('div#maintoolbar').toggle();
145 IPython.layout_manager.do_resize();
146 IPython.layout_manager.do_resize();
146 });
147 });
147 // Insert
148 // Insert
148 this.element.find('#insert_cell_above').click(function () {
149 this.element.find('#insert_cell_above').click(function () {
149 IPython.notebook.insert_cell_above('code');
150 IPython.notebook.insert_cell_above('code');
150 });
151 });
151 this.element.find('#insert_cell_below').click(function () {
152 this.element.find('#insert_cell_below').click(function () {
152 IPython.notebook.insert_cell_below('code');
153 IPython.notebook.insert_cell_below('code');
153 });
154 });
154 // Cell
155 // Cell
155 this.element.find('#run_cell').click(function () {
156 this.element.find('#run_cell').click(function () {
156 IPython.notebook.execute_selected_cell();
157 IPython.notebook.execute_selected_cell();
157 });
158 });
158 this.element.find('#run_cell_in_place').click(function () {
159 this.element.find('#run_cell_in_place').click(function () {
159 IPython.notebook.execute_selected_cell({terminal:true});
160 IPython.notebook.execute_selected_cell({terminal:true});
160 });
161 });
161 this.element.find('#run_all_cells').click(function () {
162 this.element.find('#run_all_cells').click(function () {
162 IPython.notebook.execute_all_cells();
163 IPython.notebook.execute_all_cells();
163 }).attr('title', 'Run all cells in the notebook');
164 }).attr('title', 'Run all cells in the notebook');
164 this.element.find('#run_all_cells_above').click(function () {
165 this.element.find('#run_all_cells_above').click(function () {
165 IPython.notebook.execute_cells_above();
166 IPython.notebook.execute_cells_above();
166 }).attr('title', 'Run all cells above (but not including) this cell');
167 }).attr('title', 'Run all cells above (but not including) this cell');
167 this.element.find('#run_all_cells_below').click(function () {
168 this.element.find('#run_all_cells_below').click(function () {
168 IPython.notebook.execute_cells_below();
169 IPython.notebook.execute_cells_below();
169 }).attr('title', 'Run this cell and all cells below it');
170 }).attr('title', 'Run this cell and all cells below it');
170 this.element.find('#to_code').click(function () {
171 this.element.find('#to_code').click(function () {
171 IPython.notebook.to_code();
172 IPython.notebook.to_code();
172 });
173 });
173 this.element.find('#to_markdown').click(function () {
174 this.element.find('#to_markdown').click(function () {
174 IPython.notebook.to_markdown();
175 IPython.notebook.to_markdown();
175 });
176 });
176 this.element.find('#to_raw').click(function () {
177 this.element.find('#to_raw').click(function () {
177 IPython.notebook.to_raw();
178 IPython.notebook.to_raw();
178 });
179 });
179 this.element.find('#to_heading1').click(function () {
180 this.element.find('#to_heading1').click(function () {
180 IPython.notebook.to_heading(undefined, 1);
181 IPython.notebook.to_heading(undefined, 1);
181 });
182 });
182 this.element.find('#to_heading2').click(function () {
183 this.element.find('#to_heading2').click(function () {
183 IPython.notebook.to_heading(undefined, 2);
184 IPython.notebook.to_heading(undefined, 2);
184 });
185 });
185 this.element.find('#to_heading3').click(function () {
186 this.element.find('#to_heading3').click(function () {
186 IPython.notebook.to_heading(undefined, 3);
187 IPython.notebook.to_heading(undefined, 3);
187 });
188 });
188 this.element.find('#to_heading4').click(function () {
189 this.element.find('#to_heading4').click(function () {
189 IPython.notebook.to_heading(undefined, 4);
190 IPython.notebook.to_heading(undefined, 4);
190 });
191 });
191 this.element.find('#to_heading5').click(function () {
192 this.element.find('#to_heading5').click(function () {
192 IPython.notebook.to_heading(undefined, 5);
193 IPython.notebook.to_heading(undefined, 5);
193 });
194 });
194 this.element.find('#to_heading6').click(function () {
195 this.element.find('#to_heading6').click(function () {
195 IPython.notebook.to_heading(undefined, 6);
196 IPython.notebook.to_heading(undefined, 6);
196 });
197 });
197 this.element.find('#toggle_output').click(function () {
198 this.element.find('#toggle_output').click(function () {
198 IPython.notebook.toggle_output();
199 IPython.notebook.toggle_output();
199 });
200 });
200 this.element.find('#collapse_all_output').click(function () {
201 this.element.find('#collapse_all_output').click(function () {
201 IPython.notebook.collapse_all_output();
202 IPython.notebook.collapse_all_output();
202 });
203 });
203 this.element.find('#scroll_all_output').click(function () {
204 this.element.find('#scroll_all_output').click(function () {
204 IPython.notebook.scroll_all_output();
205 IPython.notebook.scroll_all_output();
205 });
206 });
206 this.element.find('#expand_all_output').click(function () {
207 this.element.find('#expand_all_output').click(function () {
207 IPython.notebook.expand_all_output();
208 IPython.notebook.expand_all_output();
208 });
209 });
209 this.element.find('#clear_all_output').click(function () {
210 this.element.find('#clear_all_output').click(function () {
210 IPython.notebook.clear_all_output();
211 IPython.notebook.clear_all_output();
211 });
212 });
212 // Kernel
213 // Kernel
213 this.element.find('#int_kernel').click(function () {
214 this.element.find('#int_kernel').click(function () {
214 IPython.notebook.kernel.interrupt();
215 IPython.notebook.kernel.interrupt();
215 });
216 });
216 this.element.find('#restart_kernel').click(function () {
217 this.element.find('#restart_kernel').click(function () {
217 IPython.notebook.restart_kernel();
218 IPython.notebook.restart_kernel();
218 });
219 });
219 // Help
220 // Help
220 this.element.find('#keyboard_shortcuts').click(function () {
221 this.element.find('#keyboard_shortcuts').click(function () {
221 IPython.quick_help.show_keyboard_shortcuts();
222 IPython.quick_help.show_keyboard_shortcuts();
222 });
223 });
223
224
224 this.update_restore_checkpoint(null);
225 this.update_restore_checkpoint(null);
225
226
226 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
227 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
227 that.update_restore_checkpoint(IPython.notebook.checkpoints);
228 that.update_restore_checkpoint(IPython.notebook.checkpoints);
228 });
229 });
229
230
230 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
231 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
231 that.update_restore_checkpoint(IPython.notebook.checkpoints);
232 that.update_restore_checkpoint(IPython.notebook.checkpoints);
232 });
233 });
233 };
234 };
234
235
235 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
236 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
236 var ul = this.element.find("#restore_checkpoint").find("ul");
237 var ul = this.element.find("#restore_checkpoint").find("ul");
237 ul.empty();
238 ul.empty();
238 if (! checkpoints || checkpoints.length == 0) {
239 if (! checkpoints || checkpoints.length == 0) {
239 ul.append(
240 ul.append(
240 $("<li/>")
241 $("<li/>")
241 .addClass("disabled")
242 .addClass("disabled")
242 .append(
243 .append(
243 $("<a/>")
244 $("<a/>")
244 .text("No checkpoints")
245 .text("No checkpoints")
245 )
246 )
246 );
247 );
247 return;
248 return;
248 };
249 };
249
250
250 checkpoints.map(function (checkpoint) {
251 checkpoints.map(function (checkpoint) {
251 var d = new Date(checkpoint.last_modified);
252 var d = new Date(checkpoint.last_modified);
252 ul.append(
253 ul.append(
253 $("<li/>").append(
254 $("<li/>").append(
254 $("<a/>")
255 $("<a/>")
255 .attr("href", "#")
256 .attr("href", "#")
256 .text(d.format("mmm dd HH:MM:ss"))
257 .text(d.format("mmm dd HH:MM:ss"))
257 .click(function () {
258 .click(function () {
258 IPython.notebook.restore_checkpoint_dialog(checkpoint);
259 IPython.notebook.restore_checkpoint_dialog(checkpoint);
259 })
260 })
260 )
261 )
261 );
262 );
262 });
263 });
263 };
264 };
264
265
265 IPython.MenuBar = MenuBar;
266 IPython.MenuBar = MenuBar;
266
267
267 return IPython;
268 return IPython;
268
269
269 }(IPython));
270 }(IPython));
@@ -1,2064 +1,2065 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13
14
14 var utils = IPython.utils;
15 var utils = IPython.utils;
15 var key = IPython.utils.keycodes;
16 var key = IPython.utils.keycodes;
16
17
17 /**
18 /**
18 * A notebook contains and manages cells.
19 * A notebook contains and manages cells.
19 *
20 *
20 * @class Notebook
21 * @class Notebook
21 * @constructor
22 * @constructor
22 * @param {String} selector A jQuery selector for the notebook's DOM element
23 * @param {String} selector A jQuery selector for the notebook's DOM element
23 * @param {Object} [options] A config object
24 * @param {Object} [options] A config object
24 */
25 */
25 var Notebook = function (selector, options) {
26 var Notebook = function (selector, options) {
26 var options = options || {};
27 var options = options || {};
27 this._baseProjectUrl = options.baseProjectUrl;
28 this._baseProjectUrl = options.baseProjectUrl;
28
29
29 this.element = $(selector);
30 this.element = $(selector);
30 this.element.scroll();
31 this.element.scroll();
31 this.element.data("notebook", this);
32 this.element.data("notebook", this);
32 this.next_prompt_number = 1;
33 this.next_prompt_number = 1;
33 this.kernel = null;
34 this.kernel = null;
34 this.clipboard = null;
35 this.clipboard = null;
35 this.undelete_backup = null;
36 this.undelete_backup = null;
36 this.undelete_index = null;
37 this.undelete_index = null;
37 this.undelete_below = false;
38 this.undelete_below = false;
38 this.paste_enabled = false;
39 this.paste_enabled = false;
39 this.set_dirty(false);
40 this.set_dirty(false);
40 this.metadata = {};
41 this.metadata = {};
41 this._checkpoint_after_save = false;
42 this._checkpoint_after_save = false;
42 this.last_checkpoint = null;
43 this.last_checkpoint = null;
43 this.checkpoints = [];
44 this.checkpoints = [];
44 this.autosave_interval = 0;
45 this.autosave_interval = 0;
45 this.autosave_timer = null;
46 this.autosave_timer = null;
46 // autosave *at most* every two minutes
47 // autosave *at most* every two minutes
47 this.minimum_autosave_interval = 120000;
48 this.minimum_autosave_interval = 120000;
48 // single worksheet for now
49 // single worksheet for now
49 this.worksheet_metadata = {};
50 this.worksheet_metadata = {};
50 this.control_key_active = false;
51 this.control_key_active = false;
51 this.notebook_id = null;
52 this.notebook_id = null;
52 this.notebook_name = null;
53 this.notebook_name = null;
53 this.notebook_name_blacklist_re = /[\/\\:]/;
54 this.notebook_name_blacklist_re = /[\/\\:]/;
54 this.nbformat = 3 // Increment this when changing the nbformat
55 this.nbformat = 3 // Increment this when changing the nbformat
55 this.nbformat_minor = 0 // Increment this when changing the nbformat
56 this.nbformat_minor = 0 // Increment this when changing the nbformat
56 this.style();
57 this.style();
57 this.create_elements();
58 this.create_elements();
58 this.bind_events();
59 this.bind_events();
59 };
60 };
60
61
61 /**
62 /**
62 * Tweak the notebook's CSS style.
63 * Tweak the notebook's CSS style.
63 *
64 *
64 * @method style
65 * @method style
65 */
66 */
66 Notebook.prototype.style = function () {
67 Notebook.prototype.style = function () {
67 $('div#notebook').addClass('border-box-sizing');
68 $('div#notebook').addClass('border-box-sizing');
68 };
69 };
69
70
70 /**
71 /**
71 * Get the root URL of the notebook server.
72 * Get the root URL of the notebook server.
72 *
73 *
73 * @method baseProjectUrl
74 * @method baseProjectUrl
74 * @return {String} The base project URL
75 * @return {String} The base project URL
75 */
76 */
76 Notebook.prototype.baseProjectUrl = function(){
77 Notebook.prototype.baseProjectUrl = function(){
77 return this._baseProjectUrl || $('body').data('baseProjectUrl');
78 return this._baseProjectUrl || $('body').data('baseProjectUrl');
78 };
79 };
79
80
80 /**
81 /**
81 * Create an HTML and CSS representation of the notebook.
82 * Create an HTML and CSS representation of the notebook.
82 *
83 *
83 * @method create_elements
84 * @method create_elements
84 */
85 */
85 Notebook.prototype.create_elements = function () {
86 Notebook.prototype.create_elements = function () {
86 // We add this end_space div to the end of the notebook div to:
87 // We add this end_space div to the end of the notebook div to:
87 // i) provide a margin between the last cell and the end of the notebook
88 // i) provide a margin between the last cell and the end of the notebook
88 // ii) to prevent the div from scrolling up when the last cell is being
89 // ii) to prevent the div from scrolling up when the last cell is being
89 // edited, but is too low on the page, which browsers will do automatically.
90 // edited, but is too low on the page, which browsers will do automatically.
90 var that = this;
91 var that = this;
91 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
92 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
92 var end_space = $('<div/>').addClass('end_space');
93 var end_space = $('<div/>').addClass('end_space');
93 end_space.dblclick(function (e) {
94 end_space.dblclick(function (e) {
94 var ncells = that.ncells();
95 var ncells = that.ncells();
95 that.insert_cell_below('code',ncells-1);
96 that.insert_cell_below('code',ncells-1);
96 });
97 });
97 this.element.append(this.container);
98 this.element.append(this.container);
98 this.container.append(end_space);
99 this.container.append(end_space);
99 $('div#notebook').addClass('border-box-sizing');
100 $('div#notebook').addClass('border-box-sizing');
100 };
101 };
101
102
102 /**
103 /**
103 * Bind JavaScript events: key presses and custom IPython events.
104 * Bind JavaScript events: key presses and custom IPython events.
104 *
105 *
105 * @method bind_events
106 * @method bind_events
106 */
107 */
107 Notebook.prototype.bind_events = function () {
108 Notebook.prototype.bind_events = function () {
108 var that = this;
109 var that = this;
109
110
110 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
111 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
111 var index = that.find_cell_index(data.cell);
112 var index = that.find_cell_index(data.cell);
112 var new_cell = that.insert_cell_below('code',index);
113 var new_cell = that.insert_cell_below('code',index);
113 new_cell.set_text(data.text);
114 new_cell.set_text(data.text);
114 that.dirty = true;
115 that.dirty = true;
115 });
116 });
116
117
117 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
118 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
118 that.dirty = data.value;
119 that.dirty = data.value;
119 });
120 });
120
121
121 $([IPython.events]).on('select.Cell', function (event, data) {
122 $([IPython.events]).on('select.Cell', function (event, data) {
122 var index = that.find_cell_index(data.cell);
123 var index = that.find_cell_index(data.cell);
123 that.select(index);
124 that.select(index);
124 });
125 });
125
126
126 $([IPython.events]).on('status_autorestarting.Kernel', function () {
127 $([IPython.events]).on('status_autorestarting.Kernel', function () {
127 IPython.dialog.modal({
128 IPython.dialog.modal({
128 title: "Kernel Restarting",
129 title: "Kernel Restarting",
129 body: "The kernel appears to have died. It will restart automatically.",
130 body: "The kernel appears to have died. It will restart automatically.",
130 buttons: {
131 buttons: {
131 OK : {
132 OK : {
132 class : "btn-primary"
133 class : "btn-primary"
133 }
134 }
134 }
135 }
135 });
136 });
136 });
137 });
137
138
138
139
139 $(document).keydown(function (event) {
140 $(document).keydown(function (event) {
140
141
141 // Save (CTRL+S) or (AppleKey+S)
142 // Save (CTRL+S) or (AppleKey+S)
142 //metaKey = applekey on mac
143 //metaKey = applekey on mac
143 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
144 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
144 that.save_checkpoint();
145 that.save_checkpoint();
145 event.preventDefault();
146 event.preventDefault();
146 return false;
147 return false;
147 } else if (event.which === key.ESC) {
148 } else if (event.which === key.ESC) {
148 // Intercept escape at highest level to avoid closing
149 // Intercept escape at highest level to avoid closing
149 // websocket connection with firefox
150 // websocket connection with firefox
150 IPython.pager.collapse();
151 IPython.pager.collapse();
151 event.preventDefault();
152 event.preventDefault();
152 } else if (event.which === key.SHIFT) {
153 } else if (event.which === key.SHIFT) {
153 // ignore shift keydown
154 // ignore shift keydown
154 return true;
155 return true;
155 }
156 }
156 if (event.which === key.UPARROW && !event.shiftKey) {
157 if (event.which === key.UPARROW && !event.shiftKey) {
157 var cell = that.get_selected_cell();
158 var cell = that.get_selected_cell();
158 if (cell && cell.at_top()) {
159 if (cell && cell.at_top()) {
159 event.preventDefault();
160 event.preventDefault();
160 that.select_prev();
161 that.select_prev();
161 };
162 };
162 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
163 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
163 var cell = that.get_selected_cell();
164 var cell = that.get_selected_cell();
164 if (cell && cell.at_bottom()) {
165 if (cell && cell.at_bottom()) {
165 event.preventDefault();
166 event.preventDefault();
166 that.select_next();
167 that.select_next();
167 };
168 };
168 } else if (event.which === key.ENTER && event.shiftKey) {
169 } else if (event.which === key.ENTER && event.shiftKey) {
169 that.execute_selected_cell();
170 that.execute_selected_cell();
170 return false;
171 return false;
171 } else if (event.which === key.ENTER && event.altKey) {
172 } else if (event.which === key.ENTER && event.altKey) {
172 // Execute code cell, and insert new in place
173 // Execute code cell, and insert new in place
173 that.execute_selected_cell();
174 that.execute_selected_cell();
174 // Only insert a new cell, if we ended up in an already populated cell
175 // Only insert a new cell, if we ended up in an already populated cell
175 if (/\S/.test(that.get_selected_cell().get_text()) == true) {
176 if (/\S/.test(that.get_selected_cell().get_text()) == true) {
176 that.insert_cell_above('code');
177 that.insert_cell_above('code');
177 }
178 }
178 return false;
179 return false;
179 } else if (event.which === key.ENTER && event.ctrlKey) {
180 } else if (event.which === key.ENTER && event.ctrlKey) {
180 that.execute_selected_cell({terminal:true});
181 that.execute_selected_cell({terminal:true});
181 return false;
182 return false;
182 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
183 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
183 that.control_key_active = true;
184 that.control_key_active = true;
184 return false;
185 return false;
185 } else if (event.which === 88 && that.control_key_active) {
186 } else if (event.which === 88 && that.control_key_active) {
186 // Cut selected cell = x
187 // Cut selected cell = x
187 that.cut_cell();
188 that.cut_cell();
188 that.control_key_active = false;
189 that.control_key_active = false;
189 return false;
190 return false;
190 } else if (event.which === 67 && that.control_key_active) {
191 } else if (event.which === 67 && that.control_key_active) {
191 // Copy selected cell = c
192 // Copy selected cell = c
192 that.copy_cell();
193 that.copy_cell();
193 that.control_key_active = false;
194 that.control_key_active = false;
194 return false;
195 return false;
195 } else if (event.which === 86 && that.control_key_active) {
196 } else if (event.which === 86 && that.control_key_active) {
196 // Paste below selected cell = v
197 // Paste below selected cell = v
197 that.paste_cell_below();
198 that.paste_cell_below();
198 that.control_key_active = false;
199 that.control_key_active = false;
199 return false;
200 return false;
200 } else if (event.which === 68 && that.control_key_active) {
201 } else if (event.which === 68 && that.control_key_active) {
201 // Delete selected cell = d
202 // Delete selected cell = d
202 that.delete_cell();
203 that.delete_cell();
203 that.control_key_active = false;
204 that.control_key_active = false;
204 return false;
205 return false;
205 } else if (event.which === 65 && that.control_key_active) {
206 } else if (event.which === 65 && that.control_key_active) {
206 // Insert code cell above selected = a
207 // Insert code cell above selected = a
207 that.insert_cell_above('code');
208 that.insert_cell_above('code');
208 that.control_key_active = false;
209 that.control_key_active = false;
209 return false;
210 return false;
210 } else if (event.which === 66 && that.control_key_active) {
211 } else if (event.which === 66 && that.control_key_active) {
211 // Insert code cell below selected = b
212 // Insert code cell below selected = b
212 that.insert_cell_below('code');
213 that.insert_cell_below('code');
213 that.control_key_active = false;
214 that.control_key_active = false;
214 return false;
215 return false;
215 } else if (event.which === 89 && that.control_key_active) {
216 } else if (event.which === 89 && that.control_key_active) {
216 // To code = y
217 // To code = y
217 that.to_code();
218 that.to_code();
218 that.control_key_active = false;
219 that.control_key_active = false;
219 return false;
220 return false;
220 } else if (event.which === 77 && that.control_key_active) {
221 } else if (event.which === 77 && that.control_key_active) {
221 // To markdown = m
222 // To markdown = m
222 that.to_markdown();
223 that.to_markdown();
223 that.control_key_active = false;
224 that.control_key_active = false;
224 return false;
225 return false;
225 } else if (event.which === 84 && that.control_key_active) {
226 } else if (event.which === 84 && that.control_key_active) {
226 // To Raw = t
227 // To Raw = t
227 that.to_raw();
228 that.to_raw();
228 that.control_key_active = false;
229 that.control_key_active = false;
229 return false;
230 return false;
230 } else if (event.which === 49 && that.control_key_active) {
231 } else if (event.which === 49 && that.control_key_active) {
231 // To Heading 1 = 1
232 // To Heading 1 = 1
232 that.to_heading(undefined, 1);
233 that.to_heading(undefined, 1);
233 that.control_key_active = false;
234 that.control_key_active = false;
234 return false;
235 return false;
235 } else if (event.which === 50 && that.control_key_active) {
236 } else if (event.which === 50 && that.control_key_active) {
236 // To Heading 2 = 2
237 // To Heading 2 = 2
237 that.to_heading(undefined, 2);
238 that.to_heading(undefined, 2);
238 that.control_key_active = false;
239 that.control_key_active = false;
239 return false;
240 return false;
240 } else if (event.which === 51 && that.control_key_active) {
241 } else if (event.which === 51 && that.control_key_active) {
241 // To Heading 3 = 3
242 // To Heading 3 = 3
242 that.to_heading(undefined, 3);
243 that.to_heading(undefined, 3);
243 that.control_key_active = false;
244 that.control_key_active = false;
244 return false;
245 return false;
245 } else if (event.which === 52 && that.control_key_active) {
246 } else if (event.which === 52 && that.control_key_active) {
246 // To Heading 4 = 4
247 // To Heading 4 = 4
247 that.to_heading(undefined, 4);
248 that.to_heading(undefined, 4);
248 that.control_key_active = false;
249 that.control_key_active = false;
249 return false;
250 return false;
250 } else if (event.which === 53 && that.control_key_active) {
251 } else if (event.which === 53 && that.control_key_active) {
251 // To Heading 5 = 5
252 // To Heading 5 = 5
252 that.to_heading(undefined, 5);
253 that.to_heading(undefined, 5);
253 that.control_key_active = false;
254 that.control_key_active = false;
254 return false;
255 return false;
255 } else if (event.which === 54 && that.control_key_active) {
256 } else if (event.which === 54 && that.control_key_active) {
256 // To Heading 6 = 6
257 // To Heading 6 = 6
257 that.to_heading(undefined, 6);
258 that.to_heading(undefined, 6);
258 that.control_key_active = false;
259 that.control_key_active = false;
259 return false;
260 return false;
260 } else if (event.which === 79 && that.control_key_active) {
261 } else if (event.which === 79 && that.control_key_active) {
261 // Toggle output = o
262 // Toggle output = o
262 if (event.shiftKey){
263 if (event.shiftKey){
263 that.toggle_output_scroll();
264 that.toggle_output_scroll();
264 } else {
265 } else {
265 that.toggle_output();
266 that.toggle_output();
266 }
267 }
267 that.control_key_active = false;
268 that.control_key_active = false;
268 return false;
269 return false;
269 } else if (event.which === 83 && that.control_key_active) {
270 } else if (event.which === 83 && that.control_key_active) {
270 // Save notebook = s
271 // Save notebook = s
271 that.save_checkpoint();
272 that.save_checkpoint();
272 that.control_key_active = false;
273 that.control_key_active = false;
273 return false;
274 return false;
274 } else if (event.which === 74 && that.control_key_active) {
275 } else if (event.which === 74 && that.control_key_active) {
275 // Move cell down = j
276 // Move cell down = j
276 that.move_cell_down();
277 that.move_cell_down();
277 that.control_key_active = false;
278 that.control_key_active = false;
278 return false;
279 return false;
279 } else if (event.which === 75 && that.control_key_active) {
280 } else if (event.which === 75 && that.control_key_active) {
280 // Move cell up = k
281 // Move cell up = k
281 that.move_cell_up();
282 that.move_cell_up();
282 that.control_key_active = false;
283 that.control_key_active = false;
283 return false;
284 return false;
284 } else if (event.which === 80 && that.control_key_active) {
285 } else if (event.which === 80 && that.control_key_active) {
285 // Select previous = p
286 // Select previous = p
286 that.select_prev();
287 that.select_prev();
287 that.control_key_active = false;
288 that.control_key_active = false;
288 return false;
289 return false;
289 } else if (event.which === 78 && that.control_key_active) {
290 } else if (event.which === 78 && that.control_key_active) {
290 // Select next = n
291 // Select next = n
291 that.select_next();
292 that.select_next();
292 that.control_key_active = false;
293 that.control_key_active = false;
293 return false;
294 return false;
294 } else if (event.which === 76 && that.control_key_active) {
295 } else if (event.which === 76 && that.control_key_active) {
295 // Toggle line numbers = l
296 // Toggle line numbers = l
296 that.cell_toggle_line_numbers();
297 that.cell_toggle_line_numbers();
297 that.control_key_active = false;
298 that.control_key_active = false;
298 return false;
299 return false;
299 } else if (event.which === 73 && that.control_key_active) {
300 } else if (event.which === 73 && that.control_key_active) {
300 // Interrupt kernel = i
301 // Interrupt kernel = i
301 that.kernel.interrupt();
302 that.kernel.interrupt();
302 that.control_key_active = false;
303 that.control_key_active = false;
303 return false;
304 return false;
304 } else if (event.which === 190 && that.control_key_active) {
305 } else if (event.which === 190 && that.control_key_active) {
305 // Restart kernel = . # matches qt console
306 // Restart kernel = . # matches qt console
306 that.restart_kernel();
307 that.restart_kernel();
307 that.control_key_active = false;
308 that.control_key_active = false;
308 return false;
309 return false;
309 } else if (event.which === 72 && that.control_key_active) {
310 } else if (event.which === 72 && that.control_key_active) {
310 // Show keyboard shortcuts = h
311 // Show keyboard shortcuts = h
311 IPython.quick_help.show_keyboard_shortcuts();
312 IPython.quick_help.show_keyboard_shortcuts();
312 that.control_key_active = false;
313 that.control_key_active = false;
313 return false;
314 return false;
314 } else if (event.which === 90 && that.control_key_active) {
315 } else if (event.which === 90 && that.control_key_active) {
315 // Undo last cell delete = z
316 // Undo last cell delete = z
316 that.undelete();
317 that.undelete();
317 that.control_key_active = false;
318 that.control_key_active = false;
318 return false;
319 return false;
319 } else if (event.which === 189 && that.control_key_active) {
320 } else if (event.which === 189 && that.control_key_active) {
320 // Split cell = -
321 // Split cell = -
321 that.split_cell();
322 that.split_cell();
322 that.control_key_active = false;
323 that.control_key_active = false;
323 return false;
324 return false;
324 } else if (that.control_key_active) {
325 } else if (that.control_key_active) {
325 that.control_key_active = false;
326 that.control_key_active = false;
326 return true;
327 return true;
327 }
328 }
328 return true;
329 return true;
329 });
330 });
330
331
331 var collapse_time = function(time){
332 var collapse_time = function(time){
332 var app_height = $('#ipython-main-app').height(); // content height
333 var app_height = $('#ipython-main-app').height(); // content height
333 var splitter_height = $('div#pager_splitter').outerHeight(true);
334 var splitter_height = $('div#pager_splitter').outerHeight(true);
334 var new_height = app_height - splitter_height;
335 var new_height = app_height - splitter_height;
335 that.element.animate({height : new_height + 'px'}, time);
336 that.element.animate({height : new_height + 'px'}, time);
336 }
337 }
337
338
338 this.element.bind('collapse_pager', function (event,extrap) {
339 this.element.bind('collapse_pager', function (event,extrap) {
339 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
340 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
340 collapse_time(time);
341 collapse_time(time);
341 });
342 });
342
343
343 var expand_time = function(time) {
344 var expand_time = function(time) {
344 var app_height = $('#ipython-main-app').height(); // content height
345 var app_height = $('#ipython-main-app').height(); // content height
345 var splitter_height = $('div#pager_splitter').outerHeight(true);
346 var splitter_height = $('div#pager_splitter').outerHeight(true);
346 var pager_height = $('div#pager').outerHeight(true);
347 var pager_height = $('div#pager').outerHeight(true);
347 var new_height = app_height - pager_height - splitter_height;
348 var new_height = app_height - pager_height - splitter_height;
348 that.element.animate({height : new_height + 'px'}, time);
349 that.element.animate({height : new_height + 'px'}, time);
349 }
350 }
350
351
351 this.element.bind('expand_pager', function (event, extrap) {
352 this.element.bind('expand_pager', function (event, extrap) {
352 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
353 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
353 expand_time(time);
354 expand_time(time);
354 });
355 });
355
356
356 // Firefox 22 broke $(window).on("beforeunload")
357 // Firefox 22 broke $(window).on("beforeunload")
357 // I'm not sure why or how.
358 // I'm not sure why or how.
358 window.onbeforeunload = function (e) {
359 window.onbeforeunload = function (e) {
359 // TODO: Make killing the kernel configurable.
360 // TODO: Make killing the kernel configurable.
360 var kill_kernel = false;
361 var kill_kernel = false;
361 if (kill_kernel) {
362 if (kill_kernel) {
362 that.kernel.kill();
363 that.kernel.kill();
363 }
364 }
364 // if we are autosaving, trigger an autosave on nav-away.
365 // if we are autosaving, trigger an autosave on nav-away.
365 // still warn, because if we don't the autosave may fail.
366 // still warn, because if we don't the autosave may fail.
366 if (that.dirty) {
367 if (that.dirty) {
367 if ( that.autosave_interval ) {
368 if ( that.autosave_interval ) {
368 // schedule autosave in a timeout
369 // schedule autosave in a timeout
369 // this gives you a chance to forcefully discard changes
370 // this gives you a chance to forcefully discard changes
370 // by reloading the page if you *really* want to.
371 // by reloading the page if you *really* want to.
371 // the timer doesn't start until you *dismiss* the dialog.
372 // the timer doesn't start until you *dismiss* the dialog.
372 setTimeout(function () {
373 setTimeout(function () {
373 if (that.dirty) {
374 if (that.dirty) {
374 that.save_notebook();
375 that.save_notebook();
375 }
376 }
376 }, 1000);
377 }, 1000);
377 return "Autosave in progress, latest changes may be lost.";
378 return "Autosave in progress, latest changes may be lost.";
378 } else {
379 } else {
379 return "Unsaved changes will be lost.";
380 return "Unsaved changes will be lost.";
380 }
381 }
381 };
382 };
382 // Null is the *only* return value that will make the browser not
383 // Null is the *only* return value that will make the browser not
383 // pop up the "don't leave" dialog.
384 // pop up the "don't leave" dialog.
384 return null;
385 return null;
385 };
386 };
386 };
387 };
387
388
388 /**
389 /**
389 * Set the dirty flag, and trigger the set_dirty.Notebook event
390 * Set the dirty flag, and trigger the set_dirty.Notebook event
390 *
391 *
391 * @method set_dirty
392 * @method set_dirty
392 */
393 */
393 Notebook.prototype.set_dirty = function (value) {
394 Notebook.prototype.set_dirty = function (value) {
394 if (value === undefined) {
395 if (value === undefined) {
395 value = true;
396 value = true;
396 }
397 }
397 if (this.dirty == value) {
398 if (this.dirty == value) {
398 return;
399 return;
399 }
400 }
400 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
401 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
401 };
402 };
402
403
403 /**
404 /**
404 * Scroll the top of the page to a given cell.
405 * Scroll the top of the page to a given cell.
405 *
406 *
406 * @method scroll_to_cell
407 * @method scroll_to_cell
407 * @param {Number} cell_number An index of the cell to view
408 * @param {Number} cell_number An index of the cell to view
408 * @param {Number} time Animation time in milliseconds
409 * @param {Number} time Animation time in milliseconds
409 * @return {Number} Pixel offset from the top of the container
410 * @return {Number} Pixel offset from the top of the container
410 */
411 */
411 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
412 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
412 var cells = this.get_cells();
413 var cells = this.get_cells();
413 var time = time || 0;
414 var time = time || 0;
414 cell_number = Math.min(cells.length-1,cell_number);
415 cell_number = Math.min(cells.length-1,cell_number);
415 cell_number = Math.max(0 ,cell_number);
416 cell_number = Math.max(0 ,cell_number);
416 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
417 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
417 this.element.animate({scrollTop:scroll_value}, time);
418 this.element.animate({scrollTop:scroll_value}, time);
418 return scroll_value;
419 return scroll_value;
419 };
420 };
420
421
421 /**
422 /**
422 * Scroll to the bottom of the page.
423 * Scroll to the bottom of the page.
423 *
424 *
424 * @method scroll_to_bottom
425 * @method scroll_to_bottom
425 */
426 */
426 Notebook.prototype.scroll_to_bottom = function () {
427 Notebook.prototype.scroll_to_bottom = function () {
427 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
428 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
428 };
429 };
429
430
430 /**
431 /**
431 * Scroll to the top of the page.
432 * Scroll to the top of the page.
432 *
433 *
433 * @method scroll_to_top
434 * @method scroll_to_top
434 */
435 */
435 Notebook.prototype.scroll_to_top = function () {
436 Notebook.prototype.scroll_to_top = function () {
436 this.element.animate({scrollTop:0}, 0);
437 this.element.animate({scrollTop:0}, 0);
437 };
438 };
438
439
439
440
440 // Cell indexing, retrieval, etc.
441 // Cell indexing, retrieval, etc.
441
442
442 /**
443 /**
443 * Get all cell elements in the notebook.
444 * Get all cell elements in the notebook.
444 *
445 *
445 * @method get_cell_elements
446 * @method get_cell_elements
446 * @return {jQuery} A selector of all cell elements
447 * @return {jQuery} A selector of all cell elements
447 */
448 */
448 Notebook.prototype.get_cell_elements = function () {
449 Notebook.prototype.get_cell_elements = function () {
449 return this.container.children("div.cell");
450 return this.container.children("div.cell");
450 };
451 };
451
452
452 /**
453 /**
453 * Get a particular cell element.
454 * Get a particular cell element.
454 *
455 *
455 * @method get_cell_element
456 * @method get_cell_element
456 * @param {Number} index An index of a cell to select
457 * @param {Number} index An index of a cell to select
457 * @return {jQuery} A selector of the given cell.
458 * @return {jQuery} A selector of the given cell.
458 */
459 */
459 Notebook.prototype.get_cell_element = function (index) {
460 Notebook.prototype.get_cell_element = function (index) {
460 var result = null;
461 var result = null;
461 var e = this.get_cell_elements().eq(index);
462 var e = this.get_cell_elements().eq(index);
462 if (e.length !== 0) {
463 if (e.length !== 0) {
463 result = e;
464 result = e;
464 }
465 }
465 return result;
466 return result;
466 };
467 };
467
468
468 /**
469 /**
469 * Count the cells in this notebook.
470 * Count the cells in this notebook.
470 *
471 *
471 * @method ncells
472 * @method ncells
472 * @return {Number} The number of cells in this notebook
473 * @return {Number} The number of cells in this notebook
473 */
474 */
474 Notebook.prototype.ncells = function () {
475 Notebook.prototype.ncells = function () {
475 return this.get_cell_elements().length;
476 return this.get_cell_elements().length;
476 };
477 };
477
478
478 /**
479 /**
479 * Get all Cell objects in this notebook.
480 * Get all Cell objects in this notebook.
480 *
481 *
481 * @method get_cells
482 * @method get_cells
482 * @return {Array} This notebook's Cell objects
483 * @return {Array} This notebook's Cell objects
483 */
484 */
484 // TODO: we are often calling cells as cells()[i], which we should optimize
485 // TODO: we are often calling cells as cells()[i], which we should optimize
485 // to cells(i) or a new method.
486 // to cells(i) or a new method.
486 Notebook.prototype.get_cells = function () {
487 Notebook.prototype.get_cells = function () {
487 return this.get_cell_elements().toArray().map(function (e) {
488 return this.get_cell_elements().toArray().map(function (e) {
488 return $(e).data("cell");
489 return $(e).data("cell");
489 });
490 });
490 };
491 };
491
492
492 /**
493 /**
493 * Get a Cell object from this notebook.
494 * Get a Cell object from this notebook.
494 *
495 *
495 * @method get_cell
496 * @method get_cell
496 * @param {Number} index An index of a cell to retrieve
497 * @param {Number} index An index of a cell to retrieve
497 * @return {Cell} A particular cell
498 * @return {Cell} A particular cell
498 */
499 */
499 Notebook.prototype.get_cell = function (index) {
500 Notebook.prototype.get_cell = function (index) {
500 var result = null;
501 var result = null;
501 var ce = this.get_cell_element(index);
502 var ce = this.get_cell_element(index);
502 if (ce !== null) {
503 if (ce !== null) {
503 result = ce.data('cell');
504 result = ce.data('cell');
504 }
505 }
505 return result;
506 return result;
506 }
507 }
507
508
508 /**
509 /**
509 * Get the cell below a given cell.
510 * Get the cell below a given cell.
510 *
511 *
511 * @method get_next_cell
512 * @method get_next_cell
512 * @param {Cell} cell The provided cell
513 * @param {Cell} cell The provided cell
513 * @return {Cell} The next cell
514 * @return {Cell} The next cell
514 */
515 */
515 Notebook.prototype.get_next_cell = function (cell) {
516 Notebook.prototype.get_next_cell = function (cell) {
516 var result = null;
517 var result = null;
517 var index = this.find_cell_index(cell);
518 var index = this.find_cell_index(cell);
518 if (this.is_valid_cell_index(index+1)) {
519 if (this.is_valid_cell_index(index+1)) {
519 result = this.get_cell(index+1);
520 result = this.get_cell(index+1);
520 }
521 }
521 return result;
522 return result;
522 }
523 }
523
524
524 /**
525 /**
525 * Get the cell above a given cell.
526 * Get the cell above a given cell.
526 *
527 *
527 * @method get_prev_cell
528 * @method get_prev_cell
528 * @param {Cell} cell The provided cell
529 * @param {Cell} cell The provided cell
529 * @return {Cell} The previous cell
530 * @return {Cell} The previous cell
530 */
531 */
531 Notebook.prototype.get_prev_cell = function (cell) {
532 Notebook.prototype.get_prev_cell = function (cell) {
532 // TODO: off-by-one
533 // TODO: off-by-one
533 // nb.get_prev_cell(nb.get_cell(1)) is null
534 // nb.get_prev_cell(nb.get_cell(1)) is null
534 var result = null;
535 var result = null;
535 var index = this.find_cell_index(cell);
536 var index = this.find_cell_index(cell);
536 if (index !== null && index > 1) {
537 if (index !== null && index > 1) {
537 result = this.get_cell(index-1);
538 result = this.get_cell(index-1);
538 }
539 }
539 return result;
540 return result;
540 }
541 }
541
542
542 /**
543 /**
543 * Get the numeric index of a given cell.
544 * Get the numeric index of a given cell.
544 *
545 *
545 * @method find_cell_index
546 * @method find_cell_index
546 * @param {Cell} cell The provided cell
547 * @param {Cell} cell The provided cell
547 * @return {Number} The cell's numeric index
548 * @return {Number} The cell's numeric index
548 */
549 */
549 Notebook.prototype.find_cell_index = function (cell) {
550 Notebook.prototype.find_cell_index = function (cell) {
550 var result = null;
551 var result = null;
551 this.get_cell_elements().filter(function (index) {
552 this.get_cell_elements().filter(function (index) {
552 if ($(this).data("cell") === cell) {
553 if ($(this).data("cell") === cell) {
553 result = index;
554 result = index;
554 };
555 };
555 });
556 });
556 return result;
557 return result;
557 };
558 };
558
559
559 /**
560 /**
560 * Get a given index , or the selected index if none is provided.
561 * Get a given index , or the selected index if none is provided.
561 *
562 *
562 * @method index_or_selected
563 * @method index_or_selected
563 * @param {Number} index A cell's index
564 * @param {Number} index A cell's index
564 * @return {Number} The given index, or selected index if none is provided.
565 * @return {Number} The given index, or selected index if none is provided.
565 */
566 */
566 Notebook.prototype.index_or_selected = function (index) {
567 Notebook.prototype.index_or_selected = function (index) {
567 var i;
568 var i;
568 if (index === undefined || index === null) {
569 if (index === undefined || index === null) {
569 i = this.get_selected_index();
570 i = this.get_selected_index();
570 if (i === null) {
571 if (i === null) {
571 i = 0;
572 i = 0;
572 }
573 }
573 } else {
574 } else {
574 i = index;
575 i = index;
575 }
576 }
576 return i;
577 return i;
577 };
578 };
578
579
579 /**
580 /**
580 * Get the currently selected cell.
581 * Get the currently selected cell.
581 * @method get_selected_cell
582 * @method get_selected_cell
582 * @return {Cell} The selected cell
583 * @return {Cell} The selected cell
583 */
584 */
584 Notebook.prototype.get_selected_cell = function () {
585 Notebook.prototype.get_selected_cell = function () {
585 var index = this.get_selected_index();
586 var index = this.get_selected_index();
586 return this.get_cell(index);
587 return this.get_cell(index);
587 };
588 };
588
589
589 /**
590 /**
590 * Check whether a cell index is valid.
591 * Check whether a cell index is valid.
591 *
592 *
592 * @method is_valid_cell_index
593 * @method is_valid_cell_index
593 * @param {Number} index A cell index
594 * @param {Number} index A cell index
594 * @return True if the index is valid, false otherwise
595 * @return True if the index is valid, false otherwise
595 */
596 */
596 Notebook.prototype.is_valid_cell_index = function (index) {
597 Notebook.prototype.is_valid_cell_index = function (index) {
597 if (index !== null && index >= 0 && index < this.ncells()) {
598 if (index !== null && index >= 0 && index < this.ncells()) {
598 return true;
599 return true;
599 } else {
600 } else {
600 return false;
601 return false;
601 };
602 };
602 }
603 }
603
604
604 /**
605 /**
605 * Get the index of the currently selected cell.
606 * Get the index of the currently selected cell.
606
607
607 * @method get_selected_index
608 * @method get_selected_index
608 * @return {Number} The selected cell's numeric index
609 * @return {Number} The selected cell's numeric index
609 */
610 */
610 Notebook.prototype.get_selected_index = function () {
611 Notebook.prototype.get_selected_index = function () {
611 var result = null;
612 var result = null;
612 this.get_cell_elements().filter(function (index) {
613 this.get_cell_elements().filter(function (index) {
613 if ($(this).data("cell").selected === true) {
614 if ($(this).data("cell").selected === true) {
614 result = index;
615 result = index;
615 };
616 };
616 });
617 });
617 return result;
618 return result;
618 };
619 };
619
620
620
621
621 // Cell selection.
622 // Cell selection.
622
623
623 /**
624 /**
624 * Programmatically select a cell.
625 * Programmatically select a cell.
625 *
626 *
626 * @method select
627 * @method select
627 * @param {Number} index A cell's index
628 * @param {Number} index A cell's index
628 * @return {Notebook} This notebook
629 * @return {Notebook} This notebook
629 */
630 */
630 Notebook.prototype.select = function (index) {
631 Notebook.prototype.select = function (index) {
631 if (this.is_valid_cell_index(index)) {
632 if (this.is_valid_cell_index(index)) {
632 var sindex = this.get_selected_index()
633 var sindex = this.get_selected_index()
633 if (sindex !== null && index !== sindex) {
634 if (sindex !== null && index !== sindex) {
634 this.get_cell(sindex).unselect();
635 this.get_cell(sindex).unselect();
635 };
636 };
636 var cell = this.get_cell(index);
637 var cell = this.get_cell(index);
637 cell.select();
638 cell.select();
638 if (cell.cell_type === 'heading') {
639 if (cell.cell_type === 'heading') {
639 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
640 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
640 {'cell_type':cell.cell_type,level:cell.level}
641 {'cell_type':cell.cell_type,level:cell.level}
641 );
642 );
642 } else {
643 } else {
643 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
644 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
644 {'cell_type':cell.cell_type}
645 {'cell_type':cell.cell_type}
645 );
646 );
646 };
647 };
647 };
648 };
648 return this;
649 return this;
649 };
650 };
650
651
651 /**
652 /**
652 * Programmatically select the next cell.
653 * Programmatically select the next cell.
653 *
654 *
654 * @method select_next
655 * @method select_next
655 * @return {Notebook} This notebook
656 * @return {Notebook} This notebook
656 */
657 */
657 Notebook.prototype.select_next = function () {
658 Notebook.prototype.select_next = function () {
658 var index = this.get_selected_index();
659 var index = this.get_selected_index();
659 this.select(index+1);
660 this.select(index+1);
660 return this;
661 return this;
661 };
662 };
662
663
663 /**
664 /**
664 * Programmatically select the previous cell.
665 * Programmatically select the previous cell.
665 *
666 *
666 * @method select_prev
667 * @method select_prev
667 * @return {Notebook} This notebook
668 * @return {Notebook} This notebook
668 */
669 */
669 Notebook.prototype.select_prev = function () {
670 Notebook.prototype.select_prev = function () {
670 var index = this.get_selected_index();
671 var index = this.get_selected_index();
671 this.select(index-1);
672 this.select(index-1);
672 return this;
673 return this;
673 };
674 };
674
675
675
676
676 // Cell movement
677 // Cell movement
677
678
678 /**
679 /**
679 * Move given (or selected) cell up and select it.
680 * Move given (or selected) cell up and select it.
680 *
681 *
681 * @method move_cell_up
682 * @method move_cell_up
682 * @param [index] {integer} cell index
683 * @param [index] {integer} cell index
683 * @return {Notebook} This notebook
684 * @return {Notebook} This notebook
684 **/
685 **/
685 Notebook.prototype.move_cell_up = function (index) {
686 Notebook.prototype.move_cell_up = function (index) {
686 var i = this.index_or_selected(index);
687 var i = this.index_or_selected(index);
687 if (this.is_valid_cell_index(i) && i > 0) {
688 if (this.is_valid_cell_index(i) && i > 0) {
688 var pivot = this.get_cell_element(i-1);
689 var pivot = this.get_cell_element(i-1);
689 var tomove = this.get_cell_element(i);
690 var tomove = this.get_cell_element(i);
690 if (pivot !== null && tomove !== null) {
691 if (pivot !== null && tomove !== null) {
691 tomove.detach();
692 tomove.detach();
692 pivot.before(tomove);
693 pivot.before(tomove);
693 this.select(i-1);
694 this.select(i-1);
694 };
695 };
695 this.set_dirty(true);
696 this.set_dirty(true);
696 };
697 };
697 return this;
698 return this;
698 };
699 };
699
700
700
701
701 /**
702 /**
702 * Move given (or selected) cell down and select it
703 * Move given (or selected) cell down and select it
703 *
704 *
704 * @method move_cell_down
705 * @method move_cell_down
705 * @param [index] {integer} cell index
706 * @param [index] {integer} cell index
706 * @return {Notebook} This notebook
707 * @return {Notebook} This notebook
707 **/
708 **/
708 Notebook.prototype.move_cell_down = function (index) {
709 Notebook.prototype.move_cell_down = function (index) {
709 var i = this.index_or_selected(index);
710 var i = this.index_or_selected(index);
710 if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
711 if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
711 var pivot = this.get_cell_element(i+1);
712 var pivot = this.get_cell_element(i+1);
712 var tomove = this.get_cell_element(i);
713 var tomove = this.get_cell_element(i);
713 if (pivot !== null && tomove !== null) {
714 if (pivot !== null && tomove !== null) {
714 tomove.detach();
715 tomove.detach();
715 pivot.after(tomove);
716 pivot.after(tomove);
716 this.select(i+1);
717 this.select(i+1);
717 };
718 };
718 };
719 };
719 this.set_dirty();
720 this.set_dirty();
720 return this;
721 return this;
721 };
722 };
722
723
723
724
724 // Insertion, deletion.
725 // Insertion, deletion.
725
726
726 /**
727 /**
727 * Delete a cell from the notebook.
728 * Delete a cell from the notebook.
728 *
729 *
729 * @method delete_cell
730 * @method delete_cell
730 * @param [index] A cell's numeric index
731 * @param [index] A cell's numeric index
731 * @return {Notebook} This notebook
732 * @return {Notebook} This notebook
732 */
733 */
733 Notebook.prototype.delete_cell = function (index) {
734 Notebook.prototype.delete_cell = function (index) {
734 var i = this.index_or_selected(index);
735 var i = this.index_or_selected(index);
735 var cell = this.get_selected_cell();
736 var cell = this.get_selected_cell();
736 this.undelete_backup = cell.toJSON();
737 this.undelete_backup = cell.toJSON();
737 $('#undelete_cell').removeClass('disabled');
738 $('#undelete_cell').removeClass('disabled');
738 if (this.is_valid_cell_index(i)) {
739 if (this.is_valid_cell_index(i)) {
739 var ce = this.get_cell_element(i);
740 var ce = this.get_cell_element(i);
740 ce.remove();
741 ce.remove();
741 if (i === (this.ncells())) {
742 if (i === (this.ncells())) {
742 this.select(i-1);
743 this.select(i-1);
743 this.undelete_index = i - 1;
744 this.undelete_index = i - 1;
744 this.undelete_below = true;
745 this.undelete_below = true;
745 } else {
746 } else {
746 this.select(i);
747 this.select(i);
747 this.undelete_index = i;
748 this.undelete_index = i;
748 this.undelete_below = false;
749 this.undelete_below = false;
749 };
750 };
750 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
751 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
751 this.set_dirty(true);
752 this.set_dirty(true);
752 };
753 };
753 return this;
754 return this;
754 };
755 };
755
756
756 /**
757 /**
757 * Insert a cell so that after insertion the cell is at given index.
758 * Insert a cell so that after insertion the cell is at given index.
758 *
759 *
759 * Similar to insert_above, but index parameter is mandatory
760 * Similar to insert_above, but index parameter is mandatory
760 *
761 *
761 * Index will be brought back into the accissible range [0,n]
762 * Index will be brought back into the accissible range [0,n]
762 *
763 *
763 * @method insert_cell_at_index
764 * @method insert_cell_at_index
764 * @param type {string} in ['code','markdown','heading']
765 * @param type {string} in ['code','markdown','heading']
765 * @param [index] {int} a valid index where to inser cell
766 * @param [index] {int} a valid index where to inser cell
766 *
767 *
767 * @return cell {cell|null} created cell or null
768 * @return cell {cell|null} created cell or null
768 **/
769 **/
769 Notebook.prototype.insert_cell_at_index = function(type, index){
770 Notebook.prototype.insert_cell_at_index = function(type, index){
770
771
771 var ncells = this.ncells();
772 var ncells = this.ncells();
772 var index = Math.min(index,ncells);
773 var index = Math.min(index,ncells);
773 index = Math.max(index,0);
774 index = Math.max(index,0);
774 var cell = null;
775 var cell = null;
775
776
776 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
777 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
777 if (type === 'code') {
778 if (type === 'code') {
778 cell = new IPython.CodeCell(this.kernel);
779 cell = new IPython.CodeCell(this.kernel);
779 cell.set_input_prompt();
780 cell.set_input_prompt();
780 } else if (type === 'markdown') {
781 } else if (type === 'markdown') {
781 cell = new IPython.MarkdownCell();
782 cell = new IPython.MarkdownCell();
782 } else if (type === 'raw') {
783 } else if (type === 'raw') {
783 cell = new IPython.RawCell();
784 cell = new IPython.RawCell();
784 } else if (type === 'heading') {
785 } else if (type === 'heading') {
785 cell = new IPython.HeadingCell();
786 cell = new IPython.HeadingCell();
786 }
787 }
787
788
788 if(this._insert_element_at_index(cell.element,index)){
789 if(this._insert_element_at_index(cell.element,index)){
789 cell.render();
790 cell.render();
790 this.select(this.find_cell_index(cell));
791 this.select(this.find_cell_index(cell));
791 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
792 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
792 this.set_dirty(true);
793 this.set_dirty(true);
793 }
794 }
794 }
795 }
795 return cell;
796 return cell;
796
797
797 };
798 };
798
799
799 /**
800 /**
800 * Insert an element at given cell index.
801 * Insert an element at given cell index.
801 *
802 *
802 * @method _insert_element_at_index
803 * @method _insert_element_at_index
803 * @param element {dom element} a cell element
804 * @param element {dom element} a cell element
804 * @param [index] {int} a valid index where to inser cell
805 * @param [index] {int} a valid index where to inser cell
805 * @private
806 * @private
806 *
807 *
807 * return true if everything whent fine.
808 * return true if everything whent fine.
808 **/
809 **/
809 Notebook.prototype._insert_element_at_index = function(element, index){
810 Notebook.prototype._insert_element_at_index = function(element, index){
810 if (element === undefined){
811 if (element === undefined){
811 return false;
812 return false;
812 }
813 }
813
814
814 var ncells = this.ncells();
815 var ncells = this.ncells();
815
816
816 if (ncells === 0) {
817 if (ncells === 0) {
817 // special case append if empty
818 // special case append if empty
818 this.element.find('div.end_space').before(element);
819 this.element.find('div.end_space').before(element);
819 } else if ( ncells === index ) {
820 } else if ( ncells === index ) {
820 // special case append it the end, but not empty
821 // special case append it the end, but not empty
821 this.get_cell_element(index-1).after(element);
822 this.get_cell_element(index-1).after(element);
822 } else if (this.is_valid_cell_index(index)) {
823 } else if (this.is_valid_cell_index(index)) {
823 // otherwise always somewhere to append to
824 // otherwise always somewhere to append to
824 this.get_cell_element(index).before(element);
825 this.get_cell_element(index).before(element);
825 } else {
826 } else {
826 return false;
827 return false;
827 }
828 }
828
829
829 if (this.undelete_index !== null && index <= this.undelete_index) {
830 if (this.undelete_index !== null && index <= this.undelete_index) {
830 this.undelete_index = this.undelete_index + 1;
831 this.undelete_index = this.undelete_index + 1;
831 this.set_dirty(true);
832 this.set_dirty(true);
832 }
833 }
833 return true;
834 return true;
834 };
835 };
835
836
836 /**
837 /**
837 * Insert a cell of given type above given index, or at top
838 * Insert a cell of given type above given index, or at top
838 * of notebook if index smaller than 0.
839 * of notebook if index smaller than 0.
839 *
840 *
840 * default index value is the one of currently selected cell
841 * default index value is the one of currently selected cell
841 *
842 *
842 * @method insert_cell_above
843 * @method insert_cell_above
843 * @param type {string} cell type
844 * @param type {string} cell type
844 * @param [index] {integer}
845 * @param [index] {integer}
845 *
846 *
846 * @return handle to created cell or null
847 * @return handle to created cell or null
847 **/
848 **/
848 Notebook.prototype.insert_cell_above = function (type, index) {
849 Notebook.prototype.insert_cell_above = function (type, index) {
849 index = this.index_or_selected(index);
850 index = this.index_or_selected(index);
850 return this.insert_cell_at_index(type, index);
851 return this.insert_cell_at_index(type, index);
851 };
852 };
852
853
853 /**
854 /**
854 * Insert a cell of given type below given index, or at bottom
855 * Insert a cell of given type below given index, or at bottom
855 * of notebook if index greater thatn number of cell
856 * of notebook if index greater thatn number of cell
856 *
857 *
857 * default index value is the one of currently selected cell
858 * default index value is the one of currently selected cell
858 *
859 *
859 * @method insert_cell_below
860 * @method insert_cell_below
860 * @param type {string} cell type
861 * @param type {string} cell type
861 * @param [index] {integer}
862 * @param [index] {integer}
862 *
863 *
863 * @return handle to created cell or null
864 * @return handle to created cell or null
864 *
865 *
865 **/
866 **/
866 Notebook.prototype.insert_cell_below = function (type, index) {
867 Notebook.prototype.insert_cell_below = function (type, index) {
867 index = this.index_or_selected(index);
868 index = this.index_or_selected(index);
868 return this.insert_cell_at_index(type, index+1);
869 return this.insert_cell_at_index(type, index+1);
869 };
870 };
870
871
871
872
872 /**
873 /**
873 * Insert cell at end of notebook
874 * Insert cell at end of notebook
874 *
875 *
875 * @method insert_cell_at_bottom
876 * @method insert_cell_at_bottom
876 * @param {String} type cell type
877 * @param {String} type cell type
877 *
878 *
878 * @return the added cell; or null
879 * @return the added cell; or null
879 **/
880 **/
880 Notebook.prototype.insert_cell_at_bottom = function (type){
881 Notebook.prototype.insert_cell_at_bottom = function (type){
881 var len = this.ncells();
882 var len = this.ncells();
882 return this.insert_cell_below(type,len-1);
883 return this.insert_cell_below(type,len-1);
883 };
884 };
884
885
885 /**
886 /**
886 * Turn a cell into a code cell.
887 * Turn a cell into a code cell.
887 *
888 *
888 * @method to_code
889 * @method to_code
889 * @param {Number} [index] A cell's index
890 * @param {Number} [index] A cell's index
890 */
891 */
891 Notebook.prototype.to_code = function (index) {
892 Notebook.prototype.to_code = function (index) {
892 var i = this.index_or_selected(index);
893 var i = this.index_or_selected(index);
893 if (this.is_valid_cell_index(i)) {
894 if (this.is_valid_cell_index(i)) {
894 var source_element = this.get_cell_element(i);
895 var source_element = this.get_cell_element(i);
895 var source_cell = source_element.data("cell");
896 var source_cell = source_element.data("cell");
896 if (!(source_cell instanceof IPython.CodeCell)) {
897 if (!(source_cell instanceof IPython.CodeCell)) {
897 var target_cell = this.insert_cell_below('code',i);
898 var target_cell = this.insert_cell_below('code',i);
898 var text = source_cell.get_text();
899 var text = source_cell.get_text();
899 if (text === source_cell.placeholder) {
900 if (text === source_cell.placeholder) {
900 text = '';
901 text = '';
901 }
902 }
902 target_cell.set_text(text);
903 target_cell.set_text(text);
903 // make this value the starting point, so that we can only undo
904 // make this value the starting point, so that we can only undo
904 // to this state, instead of a blank cell
905 // to this state, instead of a blank cell
905 target_cell.code_mirror.clearHistory();
906 target_cell.code_mirror.clearHistory();
906 source_element.remove();
907 source_element.remove();
907 this.set_dirty(true);
908 this.set_dirty(true);
908 };
909 };
909 };
910 };
910 };
911 };
911
912
912 /**
913 /**
913 * Turn a cell into a Markdown cell.
914 * Turn a cell into a Markdown cell.
914 *
915 *
915 * @method to_markdown
916 * @method to_markdown
916 * @param {Number} [index] A cell's index
917 * @param {Number} [index] A cell's index
917 */
918 */
918 Notebook.prototype.to_markdown = function (index) {
919 Notebook.prototype.to_markdown = function (index) {
919 var i = this.index_or_selected(index);
920 var i = this.index_or_selected(index);
920 if (this.is_valid_cell_index(i)) {
921 if (this.is_valid_cell_index(i)) {
921 var source_element = this.get_cell_element(i);
922 var source_element = this.get_cell_element(i);
922 var source_cell = source_element.data("cell");
923 var source_cell = source_element.data("cell");
923 if (!(source_cell instanceof IPython.MarkdownCell)) {
924 if (!(source_cell instanceof IPython.MarkdownCell)) {
924 var target_cell = this.insert_cell_below('markdown',i);
925 var target_cell = this.insert_cell_below('markdown',i);
925 var text = source_cell.get_text();
926 var text = source_cell.get_text();
926 if (text === source_cell.placeholder) {
927 if (text === source_cell.placeholder) {
927 text = '';
928 text = '';
928 };
929 };
929 // The edit must come before the set_text.
930 // The edit must come before the set_text.
930 target_cell.edit();
931 target_cell.edit();
931 target_cell.set_text(text);
932 target_cell.set_text(text);
932 // make this value the starting point, so that we can only undo
933 // make this value the starting point, so that we can only undo
933 // to this state, instead of a blank cell
934 // to this state, instead of a blank cell
934 target_cell.code_mirror.clearHistory();
935 target_cell.code_mirror.clearHistory();
935 source_element.remove();
936 source_element.remove();
936 this.set_dirty(true);
937 this.set_dirty(true);
937 };
938 };
938 };
939 };
939 };
940 };
940
941
941 /**
942 /**
942 * Turn a cell into a raw text cell.
943 * Turn a cell into a raw text cell.
943 *
944 *
944 * @method to_raw
945 * @method to_raw
945 * @param {Number} [index] A cell's index
946 * @param {Number} [index] A cell's index
946 */
947 */
947 Notebook.prototype.to_raw = function (index) {
948 Notebook.prototype.to_raw = function (index) {
948 var i = this.index_or_selected(index);
949 var i = this.index_or_selected(index);
949 if (this.is_valid_cell_index(i)) {
950 if (this.is_valid_cell_index(i)) {
950 var source_element = this.get_cell_element(i);
951 var source_element = this.get_cell_element(i);
951 var source_cell = source_element.data("cell");
952 var source_cell = source_element.data("cell");
952 var target_cell = null;
953 var target_cell = null;
953 if (!(source_cell instanceof IPython.RawCell)) {
954 if (!(source_cell instanceof IPython.RawCell)) {
954 target_cell = this.insert_cell_below('raw',i);
955 target_cell = this.insert_cell_below('raw',i);
955 var text = source_cell.get_text();
956 var text = source_cell.get_text();
956 if (text === source_cell.placeholder) {
957 if (text === source_cell.placeholder) {
957 text = '';
958 text = '';
958 };
959 };
959 // The edit must come before the set_text.
960 // The edit must come before the set_text.
960 target_cell.edit();
961 target_cell.edit();
961 target_cell.set_text(text);
962 target_cell.set_text(text);
962 // make this value the starting point, so that we can only undo
963 // make this value the starting point, so that we can only undo
963 // to this state, instead of a blank cell
964 // to this state, instead of a blank cell
964 target_cell.code_mirror.clearHistory();
965 target_cell.code_mirror.clearHistory();
965 source_element.remove();
966 source_element.remove();
966 this.set_dirty(true);
967 this.set_dirty(true);
967 };
968 };
968 };
969 };
969 };
970 };
970
971
971 /**
972 /**
972 * Turn a cell into a heading cell.
973 * Turn a cell into a heading cell.
973 *
974 *
974 * @method to_heading
975 * @method to_heading
975 * @param {Number} [index] A cell's index
976 * @param {Number} [index] A cell's index
976 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
977 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
977 */
978 */
978 Notebook.prototype.to_heading = function (index, level) {
979 Notebook.prototype.to_heading = function (index, level) {
979 level = level || 1;
980 level = level || 1;
980 var i = this.index_or_selected(index);
981 var i = this.index_or_selected(index);
981 if (this.is_valid_cell_index(i)) {
982 if (this.is_valid_cell_index(i)) {
982 var source_element = this.get_cell_element(i);
983 var source_element = this.get_cell_element(i);
983 var source_cell = source_element.data("cell");
984 var source_cell = source_element.data("cell");
984 var target_cell = null;
985 var target_cell = null;
985 if (source_cell instanceof IPython.HeadingCell) {
986 if (source_cell instanceof IPython.HeadingCell) {
986 source_cell.set_level(level);
987 source_cell.set_level(level);
987 } else {
988 } else {
988 target_cell = this.insert_cell_below('heading',i);
989 target_cell = this.insert_cell_below('heading',i);
989 var text = source_cell.get_text();
990 var text = source_cell.get_text();
990 if (text === source_cell.placeholder) {
991 if (text === source_cell.placeholder) {
991 text = '';
992 text = '';
992 };
993 };
993 // The edit must come before the set_text.
994 // The edit must come before the set_text.
994 target_cell.set_level(level);
995 target_cell.set_level(level);
995 target_cell.edit();
996 target_cell.edit();
996 target_cell.set_text(text);
997 target_cell.set_text(text);
997 // make this value the starting point, so that we can only undo
998 // make this value the starting point, so that we can only undo
998 // to this state, instead of a blank cell
999 // to this state, instead of a blank cell
999 target_cell.code_mirror.clearHistory();
1000 target_cell.code_mirror.clearHistory();
1000 source_element.remove();
1001 source_element.remove();
1001 this.set_dirty(true);
1002 this.set_dirty(true);
1002 };
1003 };
1003 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
1004 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
1004 {'cell_type':'heading',level:level}
1005 {'cell_type':'heading',level:level}
1005 );
1006 );
1006 };
1007 };
1007 };
1008 };
1008
1009
1009
1010
1010 // Cut/Copy/Paste
1011 // Cut/Copy/Paste
1011
1012
1012 /**
1013 /**
1013 * Enable UI elements for pasting cells.
1014 * Enable UI elements for pasting cells.
1014 *
1015 *
1015 * @method enable_paste
1016 * @method enable_paste
1016 */
1017 */
1017 Notebook.prototype.enable_paste = function () {
1018 Notebook.prototype.enable_paste = function () {
1018 var that = this;
1019 var that = this;
1019 if (!this.paste_enabled) {
1020 if (!this.paste_enabled) {
1020 $('#paste_cell_replace').removeClass('disabled')
1021 $('#paste_cell_replace').removeClass('disabled')
1021 .on('click', function () {that.paste_cell_replace();});
1022 .on('click', function () {that.paste_cell_replace();});
1022 $('#paste_cell_above').removeClass('disabled')
1023 $('#paste_cell_above').removeClass('disabled')
1023 .on('click', function () {that.paste_cell_above();});
1024 .on('click', function () {that.paste_cell_above();});
1024 $('#paste_cell_below').removeClass('disabled')
1025 $('#paste_cell_below').removeClass('disabled')
1025 .on('click', function () {that.paste_cell_below();});
1026 .on('click', function () {that.paste_cell_below();});
1026 this.paste_enabled = true;
1027 this.paste_enabled = true;
1027 };
1028 };
1028 };
1029 };
1029
1030
1030 /**
1031 /**
1031 * Disable UI elements for pasting cells.
1032 * Disable UI elements for pasting cells.
1032 *
1033 *
1033 * @method disable_paste
1034 * @method disable_paste
1034 */
1035 */
1035 Notebook.prototype.disable_paste = function () {
1036 Notebook.prototype.disable_paste = function () {
1036 if (this.paste_enabled) {
1037 if (this.paste_enabled) {
1037 $('#paste_cell_replace').addClass('disabled').off('click');
1038 $('#paste_cell_replace').addClass('disabled').off('click');
1038 $('#paste_cell_above').addClass('disabled').off('click');
1039 $('#paste_cell_above').addClass('disabled').off('click');
1039 $('#paste_cell_below').addClass('disabled').off('click');
1040 $('#paste_cell_below').addClass('disabled').off('click');
1040 this.paste_enabled = false;
1041 this.paste_enabled = false;
1041 };
1042 };
1042 };
1043 };
1043
1044
1044 /**
1045 /**
1045 * Cut a cell.
1046 * Cut a cell.
1046 *
1047 *
1047 * @method cut_cell
1048 * @method cut_cell
1048 */
1049 */
1049 Notebook.prototype.cut_cell = function () {
1050 Notebook.prototype.cut_cell = function () {
1050 this.copy_cell();
1051 this.copy_cell();
1051 this.delete_cell();
1052 this.delete_cell();
1052 }
1053 }
1053
1054
1054 /**
1055 /**
1055 * Copy a cell.
1056 * Copy a cell.
1056 *
1057 *
1057 * @method copy_cell
1058 * @method copy_cell
1058 */
1059 */
1059 Notebook.prototype.copy_cell = function () {
1060 Notebook.prototype.copy_cell = function () {
1060 var cell = this.get_selected_cell();
1061 var cell = this.get_selected_cell();
1061 this.clipboard = cell.toJSON();
1062 this.clipboard = cell.toJSON();
1062 this.enable_paste();
1063 this.enable_paste();
1063 };
1064 };
1064
1065
1065 /**
1066 /**
1066 * Replace the selected cell with a cell in the clipboard.
1067 * Replace the selected cell with a cell in the clipboard.
1067 *
1068 *
1068 * @method paste_cell_replace
1069 * @method paste_cell_replace
1069 */
1070 */
1070 Notebook.prototype.paste_cell_replace = function () {
1071 Notebook.prototype.paste_cell_replace = function () {
1071 if (this.clipboard !== null && this.paste_enabled) {
1072 if (this.clipboard !== null && this.paste_enabled) {
1072 var cell_data = this.clipboard;
1073 var cell_data = this.clipboard;
1073 var new_cell = this.insert_cell_above(cell_data.cell_type);
1074 var new_cell = this.insert_cell_above(cell_data.cell_type);
1074 new_cell.fromJSON(cell_data);
1075 new_cell.fromJSON(cell_data);
1075 var old_cell = this.get_next_cell(new_cell);
1076 var old_cell = this.get_next_cell(new_cell);
1076 this.delete_cell(this.find_cell_index(old_cell));
1077 this.delete_cell(this.find_cell_index(old_cell));
1077 this.select(this.find_cell_index(new_cell));
1078 this.select(this.find_cell_index(new_cell));
1078 };
1079 };
1079 };
1080 };
1080
1081
1081 /**
1082 /**
1082 * Paste a cell from the clipboard above the selected cell.
1083 * Paste a cell from the clipboard above the selected cell.
1083 *
1084 *
1084 * @method paste_cell_above
1085 * @method paste_cell_above
1085 */
1086 */
1086 Notebook.prototype.paste_cell_above = function () {
1087 Notebook.prototype.paste_cell_above = function () {
1087 if (this.clipboard !== null && this.paste_enabled) {
1088 if (this.clipboard !== null && this.paste_enabled) {
1088 var cell_data = this.clipboard;
1089 var cell_data = this.clipboard;
1089 var new_cell = this.insert_cell_above(cell_data.cell_type);
1090 var new_cell = this.insert_cell_above(cell_data.cell_type);
1090 new_cell.fromJSON(cell_data);
1091 new_cell.fromJSON(cell_data);
1091 };
1092 };
1092 };
1093 };
1093
1094
1094 /**
1095 /**
1095 * Paste a cell from the clipboard below the selected cell.
1096 * Paste a cell from the clipboard below the selected cell.
1096 *
1097 *
1097 * @method paste_cell_below
1098 * @method paste_cell_below
1098 */
1099 */
1099 Notebook.prototype.paste_cell_below = function () {
1100 Notebook.prototype.paste_cell_below = function () {
1100 if (this.clipboard !== null && this.paste_enabled) {
1101 if (this.clipboard !== null && this.paste_enabled) {
1101 var cell_data = this.clipboard;
1102 var cell_data = this.clipboard;
1102 var new_cell = this.insert_cell_below(cell_data.cell_type);
1103 var new_cell = this.insert_cell_below(cell_data.cell_type);
1103 new_cell.fromJSON(cell_data);
1104 new_cell.fromJSON(cell_data);
1104 };
1105 };
1105 };
1106 };
1106
1107
1107 // Cell undelete
1108 // Cell undelete
1108
1109
1109 /**
1110 /**
1110 * Restore the most recently deleted cell.
1111 * Restore the most recently deleted cell.
1111 *
1112 *
1112 * @method undelete
1113 * @method undelete
1113 */
1114 */
1114 Notebook.prototype.undelete = function() {
1115 Notebook.prototype.undelete = function() {
1115 if (this.undelete_backup !== null && this.undelete_index !== null) {
1116 if (this.undelete_backup !== null && this.undelete_index !== null) {
1116 var current_index = this.get_selected_index();
1117 var current_index = this.get_selected_index();
1117 if (this.undelete_index < current_index) {
1118 if (this.undelete_index < current_index) {
1118 current_index = current_index + 1;
1119 current_index = current_index + 1;
1119 }
1120 }
1120 if (this.undelete_index >= this.ncells()) {
1121 if (this.undelete_index >= this.ncells()) {
1121 this.select(this.ncells() - 1);
1122 this.select(this.ncells() - 1);
1122 }
1123 }
1123 else {
1124 else {
1124 this.select(this.undelete_index);
1125 this.select(this.undelete_index);
1125 }
1126 }
1126 var cell_data = this.undelete_backup;
1127 var cell_data = this.undelete_backup;
1127 var new_cell = null;
1128 var new_cell = null;
1128 if (this.undelete_below) {
1129 if (this.undelete_below) {
1129 new_cell = this.insert_cell_below(cell_data.cell_type);
1130 new_cell = this.insert_cell_below(cell_data.cell_type);
1130 } else {
1131 } else {
1131 new_cell = this.insert_cell_above(cell_data.cell_type);
1132 new_cell = this.insert_cell_above(cell_data.cell_type);
1132 }
1133 }
1133 new_cell.fromJSON(cell_data);
1134 new_cell.fromJSON(cell_data);
1134 this.select(current_index);
1135 this.select(current_index);
1135 this.undelete_backup = null;
1136 this.undelete_backup = null;
1136 this.undelete_index = null;
1137 this.undelete_index = null;
1137 }
1138 }
1138 $('#undelete_cell').addClass('disabled');
1139 $('#undelete_cell').addClass('disabled');
1139 }
1140 }
1140
1141
1141 // Split/merge
1142 // Split/merge
1142
1143
1143 /**
1144 /**
1144 * Split the selected cell into two, at the cursor.
1145 * Split the selected cell into two, at the cursor.
1145 *
1146 *
1146 * @method split_cell
1147 * @method split_cell
1147 */
1148 */
1148 Notebook.prototype.split_cell = function () {
1149 Notebook.prototype.split_cell = function () {
1149 // Todo: implement spliting for other cell types.
1150 // Todo: implement spliting for other cell types.
1150 var cell = this.get_selected_cell();
1151 var cell = this.get_selected_cell();
1151 if (cell.is_splittable()) {
1152 if (cell.is_splittable()) {
1152 var texta = cell.get_pre_cursor();
1153 var texta = cell.get_pre_cursor();
1153 var textb = cell.get_post_cursor();
1154 var textb = cell.get_post_cursor();
1154 if (cell instanceof IPython.CodeCell) {
1155 if (cell instanceof IPython.CodeCell) {
1155 cell.set_text(texta);
1156 cell.set_text(texta);
1156 var new_cell = this.insert_cell_below('code');
1157 var new_cell = this.insert_cell_below('code');
1157 new_cell.set_text(textb);
1158 new_cell.set_text(textb);
1158 } else if (cell instanceof IPython.MarkdownCell) {
1159 } else if (cell instanceof IPython.MarkdownCell) {
1159 cell.set_text(texta);
1160 cell.set_text(texta);
1160 cell.render();
1161 cell.render();
1161 var new_cell = this.insert_cell_below('markdown');
1162 var new_cell = this.insert_cell_below('markdown');
1162 new_cell.edit(); // editor must be visible to call set_text
1163 new_cell.edit(); // editor must be visible to call set_text
1163 new_cell.set_text(textb);
1164 new_cell.set_text(textb);
1164 new_cell.render();
1165 new_cell.render();
1165 }
1166 }
1166 };
1167 };
1167 };
1168 };
1168
1169
1169 /**
1170 /**
1170 * Combine the selected cell into the cell above it.
1171 * Combine the selected cell into the cell above it.
1171 *
1172 *
1172 * @method merge_cell_above
1173 * @method merge_cell_above
1173 */
1174 */
1174 Notebook.prototype.merge_cell_above = function () {
1175 Notebook.prototype.merge_cell_above = function () {
1175 var index = this.get_selected_index();
1176 var index = this.get_selected_index();
1176 var cell = this.get_cell(index);
1177 var cell = this.get_cell(index);
1177 if (index > 0) {
1178 if (index > 0) {
1178 var upper_cell = this.get_cell(index-1);
1179 var upper_cell = this.get_cell(index-1);
1179 var upper_text = upper_cell.get_text();
1180 var upper_text = upper_cell.get_text();
1180 var text = cell.get_text();
1181 var text = cell.get_text();
1181 if (cell instanceof IPython.CodeCell) {
1182 if (cell instanceof IPython.CodeCell) {
1182 cell.set_text(upper_text+'\n'+text);
1183 cell.set_text(upper_text+'\n'+text);
1183 } else if (cell instanceof IPython.MarkdownCell) {
1184 } else if (cell instanceof IPython.MarkdownCell) {
1184 cell.edit();
1185 cell.edit();
1185 cell.set_text(upper_text+'\n'+text);
1186 cell.set_text(upper_text+'\n'+text);
1186 cell.render();
1187 cell.render();
1187 };
1188 };
1188 this.delete_cell(index-1);
1189 this.delete_cell(index-1);
1189 this.select(this.find_cell_index(cell));
1190 this.select(this.find_cell_index(cell));
1190 };
1191 };
1191 };
1192 };
1192
1193
1193 /**
1194 /**
1194 * Combine the selected cell into the cell below it.
1195 * Combine the selected cell into the cell below it.
1195 *
1196 *
1196 * @method merge_cell_below
1197 * @method merge_cell_below
1197 */
1198 */
1198 Notebook.prototype.merge_cell_below = function () {
1199 Notebook.prototype.merge_cell_below = function () {
1199 var index = this.get_selected_index();
1200 var index = this.get_selected_index();
1200 var cell = this.get_cell(index);
1201 var cell = this.get_cell(index);
1201 if (index < this.ncells()-1) {
1202 if (index < this.ncells()-1) {
1202 var lower_cell = this.get_cell(index+1);
1203 var lower_cell = this.get_cell(index+1);
1203 var lower_text = lower_cell.get_text();
1204 var lower_text = lower_cell.get_text();
1204 var text = cell.get_text();
1205 var text = cell.get_text();
1205 if (cell instanceof IPython.CodeCell) {
1206 if (cell instanceof IPython.CodeCell) {
1206 cell.set_text(text+'\n'+lower_text);
1207 cell.set_text(text+'\n'+lower_text);
1207 } else if (cell instanceof IPython.MarkdownCell) {
1208 } else if (cell instanceof IPython.MarkdownCell) {
1208 cell.edit();
1209 cell.edit();
1209 cell.set_text(text+'\n'+lower_text);
1210 cell.set_text(text+'\n'+lower_text);
1210 cell.render();
1211 cell.render();
1211 };
1212 };
1212 this.delete_cell(index+1);
1213 this.delete_cell(index+1);
1213 this.select(this.find_cell_index(cell));
1214 this.select(this.find_cell_index(cell));
1214 };
1215 };
1215 };
1216 };
1216
1217
1217
1218
1218 // Cell collapsing and output clearing
1219 // Cell collapsing and output clearing
1219
1220
1220 /**
1221 /**
1221 * Hide a cell's output.
1222 * Hide a cell's output.
1222 *
1223 *
1223 * @method collapse
1224 * @method collapse
1224 * @param {Number} index A cell's numeric index
1225 * @param {Number} index A cell's numeric index
1225 */
1226 */
1226 Notebook.prototype.collapse = function (index) {
1227 Notebook.prototype.collapse = function (index) {
1227 var i = this.index_or_selected(index);
1228 var i = this.index_or_selected(index);
1228 this.get_cell(i).collapse();
1229 this.get_cell(i).collapse();
1229 this.set_dirty(true);
1230 this.set_dirty(true);
1230 };
1231 };
1231
1232
1232 /**
1233 /**
1233 * Show a cell's output.
1234 * Show a cell's output.
1234 *
1235 *
1235 * @method expand
1236 * @method expand
1236 * @param {Number} index A cell's numeric index
1237 * @param {Number} index A cell's numeric index
1237 */
1238 */
1238 Notebook.prototype.expand = function (index) {
1239 Notebook.prototype.expand = function (index) {
1239 var i = this.index_or_selected(index);
1240 var i = this.index_or_selected(index);
1240 this.get_cell(i).expand();
1241 this.get_cell(i).expand();
1241 this.set_dirty(true);
1242 this.set_dirty(true);
1242 };
1243 };
1243
1244
1244 /** Toggle whether a cell's output is collapsed or expanded.
1245 /** Toggle whether a cell's output is collapsed or expanded.
1245 *
1246 *
1246 * @method toggle_output
1247 * @method toggle_output
1247 * @param {Number} index A cell's numeric index
1248 * @param {Number} index A cell's numeric index
1248 */
1249 */
1249 Notebook.prototype.toggle_output = function (index) {
1250 Notebook.prototype.toggle_output = function (index) {
1250 var i = this.index_or_selected(index);
1251 var i = this.index_or_selected(index);
1251 this.get_cell(i).toggle_output();
1252 this.get_cell(i).toggle_output();
1252 this.set_dirty(true);
1253 this.set_dirty(true);
1253 };
1254 };
1254
1255
1255 /**
1256 /**
1256 * Toggle a scrollbar for long cell outputs.
1257 * Toggle a scrollbar for long cell outputs.
1257 *
1258 *
1258 * @method toggle_output_scroll
1259 * @method toggle_output_scroll
1259 * @param {Number} index A cell's numeric index
1260 * @param {Number} index A cell's numeric index
1260 */
1261 */
1261 Notebook.prototype.toggle_output_scroll = function (index) {
1262 Notebook.prototype.toggle_output_scroll = function (index) {
1262 var i = this.index_or_selected(index);
1263 var i = this.index_or_selected(index);
1263 this.get_cell(i).toggle_output_scroll();
1264 this.get_cell(i).toggle_output_scroll();
1264 };
1265 };
1265
1266
1266 /**
1267 /**
1267 * Hide each code cell's output area.
1268 * Hide each code cell's output area.
1268 *
1269 *
1269 * @method collapse_all_output
1270 * @method collapse_all_output
1270 */
1271 */
1271 Notebook.prototype.collapse_all_output = function () {
1272 Notebook.prototype.collapse_all_output = function () {
1272 var ncells = this.ncells();
1273 var ncells = this.ncells();
1273 var cells = this.get_cells();
1274 var cells = this.get_cells();
1274 for (var i=0; i<ncells; i++) {
1275 for (var i=0; i<ncells; i++) {
1275 if (cells[i] instanceof IPython.CodeCell) {
1276 if (cells[i] instanceof IPython.CodeCell) {
1276 cells[i].output_area.collapse();
1277 cells[i].output_area.collapse();
1277 }
1278 }
1278 };
1279 };
1279 // this should not be set if the `collapse` key is removed from nbformat
1280 // this should not be set if the `collapse` key is removed from nbformat
1280 this.set_dirty(true);
1281 this.set_dirty(true);
1281 };
1282 };
1282
1283
1283 /**
1284 /**
1284 * Expand each code cell's output area, and add a scrollbar for long output.
1285 * Expand each code cell's output area, and add a scrollbar for long output.
1285 *
1286 *
1286 * @method scroll_all_output
1287 * @method scroll_all_output
1287 */
1288 */
1288 Notebook.prototype.scroll_all_output = function () {
1289 Notebook.prototype.scroll_all_output = function () {
1289 var ncells = this.ncells();
1290 var ncells = this.ncells();
1290 var cells = this.get_cells();
1291 var cells = this.get_cells();
1291 for (var i=0; i<ncells; i++) {
1292 for (var i=0; i<ncells; i++) {
1292 if (cells[i] instanceof IPython.CodeCell) {
1293 if (cells[i] instanceof IPython.CodeCell) {
1293 cells[i].output_area.expand();
1294 cells[i].output_area.expand();
1294 cells[i].output_area.scroll_if_long();
1295 cells[i].output_area.scroll_if_long();
1295 }
1296 }
1296 };
1297 };
1297 // this should not be set if the `collapse` key is removed from nbformat
1298 // this should not be set if the `collapse` key is removed from nbformat
1298 this.set_dirty(true);
1299 this.set_dirty(true);
1299 };
1300 };
1300
1301
1301 /**
1302 /**
1302 * Expand each code cell's output area, and remove scrollbars.
1303 * Expand each code cell's output area, and remove scrollbars.
1303 *
1304 *
1304 * @method expand_all_output
1305 * @method expand_all_output
1305 */
1306 */
1306 Notebook.prototype.expand_all_output = function () {
1307 Notebook.prototype.expand_all_output = function () {
1307 var ncells = this.ncells();
1308 var ncells = this.ncells();
1308 var cells = this.get_cells();
1309 var cells = this.get_cells();
1309 for (var i=0; i<ncells; i++) {
1310 for (var i=0; i<ncells; i++) {
1310 if (cells[i] instanceof IPython.CodeCell) {
1311 if (cells[i] instanceof IPython.CodeCell) {
1311 cells[i].output_area.expand();
1312 cells[i].output_area.expand();
1312 cells[i].output_area.unscroll_area();
1313 cells[i].output_area.unscroll_area();
1313 }
1314 }
1314 };
1315 };
1315 // this should not be set if the `collapse` key is removed from nbformat
1316 // this should not be set if the `collapse` key is removed from nbformat
1316 this.set_dirty(true);
1317 this.set_dirty(true);
1317 };
1318 };
1318
1319
1319 /**
1320 /**
1320 * Clear each code cell's output area.
1321 * Clear each code cell's output area.
1321 *
1322 *
1322 * @method clear_all_output
1323 * @method clear_all_output
1323 */
1324 */
1324 Notebook.prototype.clear_all_output = function () {
1325 Notebook.prototype.clear_all_output = function () {
1325 var ncells = this.ncells();
1326 var ncells = this.ncells();
1326 var cells = this.get_cells();
1327 var cells = this.get_cells();
1327 for (var i=0; i<ncells; i++) {
1328 for (var i=0; i<ncells; i++) {
1328 if (cells[i] instanceof IPython.CodeCell) {
1329 if (cells[i] instanceof IPython.CodeCell) {
1329 cells[i].clear_output(true,true,true);
1330 cells[i].clear_output(true,true,true);
1330 // Make all In[] prompts blank, as well
1331 // Make all In[] prompts blank, as well
1331 // TODO: make this configurable (via checkbox?)
1332 // TODO: make this configurable (via checkbox?)
1332 cells[i].set_input_prompt();
1333 cells[i].set_input_prompt();
1333 }
1334 }
1334 };
1335 };
1335 this.set_dirty(true);
1336 this.set_dirty(true);
1336 };
1337 };
1337
1338
1338
1339
1339 // Other cell functions: line numbers, ...
1340 // Other cell functions: line numbers, ...
1340
1341
1341 /**
1342 /**
1342 * Toggle line numbers in the selected cell's input area.
1343 * Toggle line numbers in the selected cell's input area.
1343 *
1344 *
1344 * @method cell_toggle_line_numbers
1345 * @method cell_toggle_line_numbers
1345 */
1346 */
1346 Notebook.prototype.cell_toggle_line_numbers = function() {
1347 Notebook.prototype.cell_toggle_line_numbers = function() {
1347 this.get_selected_cell().toggle_line_numbers();
1348 this.get_selected_cell().toggle_line_numbers();
1348 };
1349 };
1349
1350
1350 // Kernel related things
1351 // Kernel related things
1351
1352
1352 /**
1353 /**
1353 * Start a new kernel and set it on each code cell.
1354 * Start a new kernel and set it on each code cell.
1354 *
1355 *
1355 * @method start_kernel
1356 * @method start_kernel
1356 */
1357 */
1357 Notebook.prototype.start_kernel = function () {
1358 Notebook.prototype.start_kernel = function () {
1358 var base_url = $('body').data('baseKernelUrl') + "kernels";
1359 var base_url = $('body').data('baseKernelUrl') + "kernels";
1359 this.kernel = new IPython.Kernel(base_url);
1360 this.kernel = new IPython.Kernel(base_url);
1360 this.kernel.start(this.notebook_id);
1361 this.kernel.start(this.notebook_id);
1361 // Now that the kernel has been created, tell the CodeCells about it.
1362 // Now that the kernel has been created, tell the CodeCells about it.
1362 var ncells = this.ncells();
1363 var ncells = this.ncells();
1363 for (var i=0; i<ncells; i++) {
1364 for (var i=0; i<ncells; i++) {
1364 var cell = this.get_cell(i);
1365 var cell = this.get_cell(i);
1365 if (cell instanceof IPython.CodeCell) {
1366 if (cell instanceof IPython.CodeCell) {
1366 cell.set_kernel(this.kernel)
1367 cell.set_kernel(this.kernel)
1367 };
1368 };
1368 };
1369 };
1369 };
1370 };
1370
1371
1371 /**
1372 /**
1372 * Prompt the user to restart the IPython kernel.
1373 * Prompt the user to restart the IPython kernel.
1373 *
1374 *
1374 * @method restart_kernel
1375 * @method restart_kernel
1375 */
1376 */
1376 Notebook.prototype.restart_kernel = function () {
1377 Notebook.prototype.restart_kernel = function () {
1377 var that = this;
1378 var that = this;
1378 IPython.dialog.modal({
1379 IPython.dialog.modal({
1379 title : "Restart kernel or continue running?",
1380 title : "Restart kernel or continue running?",
1380 body : $("<p/>").html(
1381 body : $("<p/>").html(
1381 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1382 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1382 ),
1383 ),
1383 buttons : {
1384 buttons : {
1384 "Continue running" : {},
1385 "Continue running" : {},
1385 "Restart" : {
1386 "Restart" : {
1386 "class" : "btn-danger",
1387 "class" : "btn-danger",
1387 "click" : function() {
1388 "click" : function() {
1388 that.kernel.restart();
1389 that.kernel.restart();
1389 }
1390 }
1390 }
1391 }
1391 }
1392 }
1392 });
1393 });
1393 };
1394 };
1394
1395
1395 /**
1396 /**
1396 * Run the selected cell.
1397 * Run the selected cell.
1397 *
1398 *
1398 * Execute or render cell outputs.
1399 * Execute or render cell outputs.
1399 *
1400 *
1400 * @method execute_selected_cell
1401 * @method execute_selected_cell
1401 * @param {Object} options Customize post-execution behavior
1402 * @param {Object} options Customize post-execution behavior
1402 */
1403 */
1403 Notebook.prototype.execute_selected_cell = function (options) {
1404 Notebook.prototype.execute_selected_cell = function (options) {
1404 // add_new: should a new cell be added if we are at the end of the nb
1405 // add_new: should a new cell be added if we are at the end of the nb
1405 // terminal: execute in terminal mode, which stays in the current cell
1406 // terminal: execute in terminal mode, which stays in the current cell
1406 var default_options = {terminal: false, add_new: true};
1407 var default_options = {terminal: false, add_new: true};
1407 $.extend(default_options, options);
1408 $.extend(default_options, options);
1408 var that = this;
1409 var that = this;
1409 var cell = that.get_selected_cell();
1410 var cell = that.get_selected_cell();
1410 var cell_index = that.find_cell_index(cell);
1411 var cell_index = that.find_cell_index(cell);
1411 if (cell instanceof IPython.CodeCell) {
1412 if (cell instanceof IPython.CodeCell) {
1412 cell.execute();
1413 cell.execute();
1413 }
1414 }
1414 if (default_options.terminal) {
1415 if (default_options.terminal) {
1415 cell.select_all();
1416 cell.select_all();
1416 } else {
1417 } else {
1417 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1418 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1418 that.insert_cell_below('code');
1419 that.insert_cell_below('code');
1419 // If we are adding a new cell at the end, scroll down to show it.
1420 // If we are adding a new cell at the end, scroll down to show it.
1420 that.scroll_to_bottom();
1421 that.scroll_to_bottom();
1421 } else {
1422 } else {
1422 that.select(cell_index+1);
1423 that.select(cell_index+1);
1423 };
1424 };
1424 };
1425 };
1425 this.set_dirty(true);
1426 this.set_dirty(true);
1426 };
1427 };
1427
1428
1428 /**
1429 /**
1429 * Execute all cells below the selected cell.
1430 * Execute all cells below the selected cell.
1430 *
1431 *
1431 * @method execute_cells_below
1432 * @method execute_cells_below
1432 */
1433 */
1433 Notebook.prototype.execute_cells_below = function () {
1434 Notebook.prototype.execute_cells_below = function () {
1434 this.execute_cell_range(this.get_selected_index(), this.ncells());
1435 this.execute_cell_range(this.get_selected_index(), this.ncells());
1435 this.scroll_to_bottom();
1436 this.scroll_to_bottom();
1436 };
1437 };
1437
1438
1438 /**
1439 /**
1439 * Execute all cells above the selected cell.
1440 * Execute all cells above the selected cell.
1440 *
1441 *
1441 * @method execute_cells_above
1442 * @method execute_cells_above
1442 */
1443 */
1443 Notebook.prototype.execute_cells_above = function () {
1444 Notebook.prototype.execute_cells_above = function () {
1444 this.execute_cell_range(0, this.get_selected_index());
1445 this.execute_cell_range(0, this.get_selected_index());
1445 };
1446 };
1446
1447
1447 /**
1448 /**
1448 * Execute all cells.
1449 * Execute all cells.
1449 *
1450 *
1450 * @method execute_all_cells
1451 * @method execute_all_cells
1451 */
1452 */
1452 Notebook.prototype.execute_all_cells = function () {
1453 Notebook.prototype.execute_all_cells = function () {
1453 this.execute_cell_range(0, this.ncells());
1454 this.execute_cell_range(0, this.ncells());
1454 this.scroll_to_bottom();
1455 this.scroll_to_bottom();
1455 };
1456 };
1456
1457
1457 /**
1458 /**
1458 * Execute a contiguous range of cells.
1459 * Execute a contiguous range of cells.
1459 *
1460 *
1460 * @method execute_cell_range
1461 * @method execute_cell_range
1461 * @param {Number} start Index of the first cell to execute (inclusive)
1462 * @param {Number} start Index of the first cell to execute (inclusive)
1462 * @param {Number} end Index of the last cell to execute (exclusive)
1463 * @param {Number} end Index of the last cell to execute (exclusive)
1463 */
1464 */
1464 Notebook.prototype.execute_cell_range = function (start, end) {
1465 Notebook.prototype.execute_cell_range = function (start, end) {
1465 for (var i=start; i<end; i++) {
1466 for (var i=start; i<end; i++) {
1466 this.select(i);
1467 this.select(i);
1467 this.execute_selected_cell({add_new:false});
1468 this.execute_selected_cell({add_new:false});
1468 };
1469 };
1469 };
1470 };
1470
1471
1471 // Persistance and loading
1472 // Persistance and loading
1472
1473
1473 /**
1474 /**
1474 * Getter method for this notebook's ID.
1475 * Getter method for this notebook's ID.
1475 *
1476 *
1476 * @method get_notebook_id
1477 * @method get_notebook_id
1477 * @return {String} This notebook's ID
1478 * @return {String} This notebook's ID
1478 */
1479 */
1479 Notebook.prototype.get_notebook_id = function () {
1480 Notebook.prototype.get_notebook_id = function () {
1480 return this.notebook_id;
1481 return this.notebook_id;
1481 };
1482 };
1482
1483
1483 /**
1484 /**
1484 * Getter method for this notebook's name.
1485 * Getter method for this notebook's name.
1485 *
1486 *
1486 * @method get_notebook_name
1487 * @method get_notebook_name
1487 * @return {String} This notebook's name
1488 * @return {String} This notebook's name
1488 */
1489 */
1489 Notebook.prototype.get_notebook_name = function () {
1490 Notebook.prototype.get_notebook_name = function () {
1490 return this.notebook_name;
1491 return this.notebook_name;
1491 };
1492 };
1492
1493
1493 /**
1494 /**
1494 * Setter method for this notebook's name.
1495 * Setter method for this notebook's name.
1495 *
1496 *
1496 * @method set_notebook_name
1497 * @method set_notebook_name
1497 * @param {String} name A new name for this notebook
1498 * @param {String} name A new name for this notebook
1498 */
1499 */
1499 Notebook.prototype.set_notebook_name = function (name) {
1500 Notebook.prototype.set_notebook_name = function (name) {
1500 this.notebook_name = name;
1501 this.notebook_name = name;
1501 };
1502 };
1502
1503
1503 /**
1504 /**
1504 * Check that a notebook's name is valid.
1505 * Check that a notebook's name is valid.
1505 *
1506 *
1506 * @method test_notebook_name
1507 * @method test_notebook_name
1507 * @param {String} nbname A name for this notebook
1508 * @param {String} nbname A name for this notebook
1508 * @return {Boolean} True if the name is valid, false if invalid
1509 * @return {Boolean} True if the name is valid, false if invalid
1509 */
1510 */
1510 Notebook.prototype.test_notebook_name = function (nbname) {
1511 Notebook.prototype.test_notebook_name = function (nbname) {
1511 nbname = nbname || '';
1512 nbname = nbname || '';
1512 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1513 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1513 return true;
1514 return true;
1514 } else {
1515 } else {
1515 return false;
1516 return false;
1516 };
1517 };
1517 };
1518 };
1518
1519
1519 /**
1520 /**
1520 * Load a notebook from JSON (.ipynb).
1521 * Load a notebook from JSON (.ipynb).
1521 *
1522 *
1522 * This currently handles one worksheet: others are deleted.
1523 * This currently handles one worksheet: others are deleted.
1523 *
1524 *
1524 * @method fromJSON
1525 * @method fromJSON
1525 * @param {Object} data JSON representation of a notebook
1526 * @param {Object} data JSON representation of a notebook
1526 */
1527 */
1527 Notebook.prototype.fromJSON = function (data) {
1528 Notebook.prototype.fromJSON = function (data) {
1528 var ncells = this.ncells();
1529 var ncells = this.ncells();
1529 var i;
1530 var i;
1530 for (i=0; i<ncells; i++) {
1531 for (i=0; i<ncells; i++) {
1531 // Always delete cell 0 as they get renumbered as they are deleted.
1532 // Always delete cell 0 as they get renumbered as they are deleted.
1532 this.delete_cell(0);
1533 this.delete_cell(0);
1533 };
1534 };
1534 // Save the metadata and name.
1535 // Save the metadata and name.
1535 this.metadata = data.metadata;
1536 this.metadata = data.metadata;
1536 this.notebook_name = data.metadata.name;
1537 this.notebook_name = data.metadata.name;
1537 // Only handle 1 worksheet for now.
1538 // Only handle 1 worksheet for now.
1538 var worksheet = data.worksheets[0];
1539 var worksheet = data.worksheets[0];
1539 if (worksheet !== undefined) {
1540 if (worksheet !== undefined) {
1540 if (worksheet.metadata) {
1541 if (worksheet.metadata) {
1541 this.worksheet_metadata = worksheet.metadata;
1542 this.worksheet_metadata = worksheet.metadata;
1542 }
1543 }
1543 var new_cells = worksheet.cells;
1544 var new_cells = worksheet.cells;
1544 ncells = new_cells.length;
1545 ncells = new_cells.length;
1545 var cell_data = null;
1546 var cell_data = null;
1546 var new_cell = null;
1547 var new_cell = null;
1547 for (i=0; i<ncells; i++) {
1548 for (i=0; i<ncells; i++) {
1548 cell_data = new_cells[i];
1549 cell_data = new_cells[i];
1549 // VERSIONHACK: plaintext -> raw
1550 // VERSIONHACK: plaintext -> raw
1550 // handle never-released plaintext name for raw cells
1551 // handle never-released plaintext name for raw cells
1551 if (cell_data.cell_type === 'plaintext'){
1552 if (cell_data.cell_type === 'plaintext'){
1552 cell_data.cell_type = 'raw';
1553 cell_data.cell_type = 'raw';
1553 }
1554 }
1554
1555
1555 new_cell = this.insert_cell_below(cell_data.cell_type);
1556 new_cell = this.insert_cell_below(cell_data.cell_type);
1556 new_cell.fromJSON(cell_data);
1557 new_cell.fromJSON(cell_data);
1557 };
1558 };
1558 };
1559 };
1559 if (data.worksheets.length > 1) {
1560 if (data.worksheets.length > 1) {
1560 IPython.dialog.modal({
1561 IPython.dialog.modal({
1561 title : "Multiple worksheets",
1562 title : "Multiple worksheets",
1562 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1563 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1563 "but this version of IPython can only handle the first. " +
1564 "but this version of IPython can only handle the first. " +
1564 "If you save this notebook, worksheets after the first will be lost.",
1565 "If you save this notebook, worksheets after the first will be lost.",
1565 buttons : {
1566 buttons : {
1566 OK : {
1567 OK : {
1567 class : "btn-danger"
1568 class : "btn-danger"
1568 }
1569 }
1569 }
1570 }
1570 });
1571 });
1571 }
1572 }
1572 };
1573 };
1573
1574
1574 /**
1575 /**
1575 * Dump this notebook into a JSON-friendly object.
1576 * Dump this notebook into a JSON-friendly object.
1576 *
1577 *
1577 * @method toJSON
1578 * @method toJSON
1578 * @return {Object} A JSON-friendly representation of this notebook.
1579 * @return {Object} A JSON-friendly representation of this notebook.
1579 */
1580 */
1580 Notebook.prototype.toJSON = function () {
1581 Notebook.prototype.toJSON = function () {
1581 var cells = this.get_cells();
1582 var cells = this.get_cells();
1582 var ncells = cells.length;
1583 var ncells = cells.length;
1583 var cell_array = new Array(ncells);
1584 var cell_array = new Array(ncells);
1584 for (var i=0; i<ncells; i++) {
1585 for (var i=0; i<ncells; i++) {
1585 cell_array[i] = cells[i].toJSON();
1586 cell_array[i] = cells[i].toJSON();
1586 };
1587 };
1587 var data = {
1588 var data = {
1588 // Only handle 1 worksheet for now.
1589 // Only handle 1 worksheet for now.
1589 worksheets : [{
1590 worksheets : [{
1590 cells: cell_array,
1591 cells: cell_array,
1591 metadata: this.worksheet_metadata
1592 metadata: this.worksheet_metadata
1592 }],
1593 }],
1593 metadata : this.metadata
1594 metadata : this.metadata
1594 };
1595 };
1595 return data;
1596 return data;
1596 };
1597 };
1597
1598
1598 /**
1599 /**
1599 * Start an autosave timer, for periodically saving the notebook.
1600 * Start an autosave timer, for periodically saving the notebook.
1600 *
1601 *
1601 * @method set_autosave_interval
1602 * @method set_autosave_interval
1602 * @param {Integer} interval the autosave interval in milliseconds
1603 * @param {Integer} interval the autosave interval in milliseconds
1603 */
1604 */
1604 Notebook.prototype.set_autosave_interval = function (interval) {
1605 Notebook.prototype.set_autosave_interval = function (interval) {
1605 var that = this;
1606 var that = this;
1606 // clear previous interval, so we don't get simultaneous timers
1607 // clear previous interval, so we don't get simultaneous timers
1607 if (this.autosave_timer) {
1608 if (this.autosave_timer) {
1608 clearInterval(this.autosave_timer);
1609 clearInterval(this.autosave_timer);
1609 }
1610 }
1610
1611
1611 this.autosave_interval = this.minimum_autosave_interval = interval;
1612 this.autosave_interval = this.minimum_autosave_interval = interval;
1612 if (interval) {
1613 if (interval) {
1613 this.autosave_timer = setInterval(function() {
1614 this.autosave_timer = setInterval(function() {
1614 if (that.dirty) {
1615 if (that.dirty) {
1615 that.save_notebook();
1616 that.save_notebook();
1616 }
1617 }
1617 }, interval);
1618 }, interval);
1618 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1619 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1619 } else {
1620 } else {
1620 this.autosave_timer = null;
1621 this.autosave_timer = null;
1621 $([IPython.events]).trigger("autosave_disabled.Notebook");
1622 $([IPython.events]).trigger("autosave_disabled.Notebook");
1622 };
1623 };
1623 };
1624 };
1624
1625
1625 /**
1626 /**
1626 * Save this notebook on the server.
1627 * Save this notebook on the server.
1627 *
1628 *
1628 * @method save_notebook
1629 * @method save_notebook
1629 */
1630 */
1630 Notebook.prototype.save_notebook = function () {
1631 Notebook.prototype.save_notebook = function () {
1631 // We may want to move the name/id/nbformat logic inside toJSON?
1632 // We may want to move the name/id/nbformat logic inside toJSON?
1632 var data = this.toJSON();
1633 var data = this.toJSON();
1633 data.metadata.name = this.notebook_name;
1634 data.metadata.name = this.notebook_name;
1634 data.nbformat = this.nbformat;
1635 data.nbformat = this.nbformat;
1635 data.nbformat_minor = this.nbformat_minor;
1636 data.nbformat_minor = this.nbformat_minor;
1636
1637
1637 // time the ajax call for autosave tuning purposes.
1638 // time the ajax call for autosave tuning purposes.
1638 var start = new Date().getTime();
1639 var start = new Date().getTime();
1639
1640
1640 // We do the call with settings so we can set cache to false.
1641 // We do the call with settings so we can set cache to false.
1641 var settings = {
1642 var settings = {
1642 processData : false,
1643 processData : false,
1643 cache : false,
1644 cache : false,
1644 type : "PUT",
1645 type : "PUT",
1645 data : JSON.stringify(data),
1646 data : JSON.stringify(data),
1646 headers : {'Content-Type': 'application/json'},
1647 headers : {'Content-Type': 'application/json'},
1647 success : $.proxy(this.save_notebook_success, this, start),
1648 success : $.proxy(this.save_notebook_success, this, start),
1648 error : $.proxy(this.save_notebook_error, this)
1649 error : $.proxy(this.save_notebook_error, this)
1649 };
1650 };
1650 $([IPython.events]).trigger('notebook_saving.Notebook');
1651 $([IPython.events]).trigger('notebook_saving.Notebook');
1651 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1652 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1652 $.ajax(url, settings);
1653 $.ajax(url, settings);
1653 };
1654 };
1654
1655
1655 /**
1656 /**
1656 * Success callback for saving a notebook.
1657 * Success callback for saving a notebook.
1657 *
1658 *
1658 * @method save_notebook_success
1659 * @method save_notebook_success
1659 * @param {Integer} start the time when the save request started
1660 * @param {Integer} start the time when the save request started
1660 * @param {Object} data JSON representation of a notebook
1661 * @param {Object} data JSON representation of a notebook
1661 * @param {String} status Description of response status
1662 * @param {String} status Description of response status
1662 * @param {jqXHR} xhr jQuery Ajax object
1663 * @param {jqXHR} xhr jQuery Ajax object
1663 */
1664 */
1664 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1665 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1665 this.set_dirty(false);
1666 this.set_dirty(false);
1666 $([IPython.events]).trigger('notebook_saved.Notebook');
1667 $([IPython.events]).trigger('notebook_saved.Notebook');
1667 this._update_autosave_interval(start);
1668 this._update_autosave_interval(start);
1668 if (this._checkpoint_after_save) {
1669 if (this._checkpoint_after_save) {
1669 this.create_checkpoint();
1670 this.create_checkpoint();
1670 this._checkpoint_after_save = false;
1671 this._checkpoint_after_save = false;
1671 };
1672 };
1672 };
1673 };
1673
1674
1674 /**
1675 /**
1675 * update the autosave interval based on how long the last save took
1676 * update the autosave interval based on how long the last save took
1676 *
1677 *
1677 * @method _update_autosave_interval
1678 * @method _update_autosave_interval
1678 * @param {Integer} timestamp when the save request started
1679 * @param {Integer} timestamp when the save request started
1679 */
1680 */
1680 Notebook.prototype._update_autosave_interval = function (start) {
1681 Notebook.prototype._update_autosave_interval = function (start) {
1681 var duration = (new Date().getTime() - start);
1682 var duration = (new Date().getTime() - start);
1682 if (this.autosave_interval) {
1683 if (this.autosave_interval) {
1683 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1684 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1684 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1685 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1685 // round to 10 seconds, otherwise we will be setting a new interval too often
1686 // round to 10 seconds, otherwise we will be setting a new interval too often
1686 interval = 10000 * Math.round(interval / 10000);
1687 interval = 10000 * Math.round(interval / 10000);
1687 // set new interval, if it's changed
1688 // set new interval, if it's changed
1688 if (interval != this.autosave_interval) {
1689 if (interval != this.autosave_interval) {
1689 this.set_autosave_interval(interval);
1690 this.set_autosave_interval(interval);
1690 }
1691 }
1691 }
1692 }
1692 };
1693 };
1693
1694
1694 /**
1695 /**
1695 * Failure callback for saving a notebook.
1696 * Failure callback for saving a notebook.
1696 *
1697 *
1697 * @method save_notebook_error
1698 * @method save_notebook_error
1698 * @param {jqXHR} xhr jQuery Ajax object
1699 * @param {jqXHR} xhr jQuery Ajax object
1699 * @param {String} status Description of response status
1700 * @param {String} status Description of response status
1700 * @param {String} error_msg HTTP error message
1701 * @param {String} error_msg HTTP error message
1701 */
1702 */
1702 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1703 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1703 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1704 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1704 };
1705 };
1705
1706
1706 /**
1707 /**
1707 * Request a notebook's data from the server.
1708 * Request a notebook's data from the server.
1708 *
1709 *
1709 * @method load_notebook
1710 * @method load_notebook
1710 * @param {String} notebook_id A notebook to load
1711 * @param {String} notebook_id A notebook to load
1711 */
1712 */
1712 Notebook.prototype.load_notebook = function (notebook_id) {
1713 Notebook.prototype.load_notebook = function (notebook_id) {
1713 var that = this;
1714 var that = this;
1714 this.notebook_id = notebook_id;
1715 this.notebook_id = notebook_id;
1715 // We do the call with settings so we can set cache to false.
1716 // We do the call with settings so we can set cache to false.
1716 var settings = {
1717 var settings = {
1717 processData : false,
1718 processData : false,
1718 cache : false,
1719 cache : false,
1719 type : "GET",
1720 type : "GET",
1720 dataType : "json",
1721 dataType : "json",
1721 success : $.proxy(this.load_notebook_success,this),
1722 success : $.proxy(this.load_notebook_success,this),
1722 error : $.proxy(this.load_notebook_error,this),
1723 error : $.proxy(this.load_notebook_error,this),
1723 };
1724 };
1724 $([IPython.events]).trigger('notebook_loading.Notebook');
1725 $([IPython.events]).trigger('notebook_loading.Notebook');
1725 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1726 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1726 $.ajax(url, settings);
1727 $.ajax(url, settings);
1727 };
1728 };
1728
1729
1729 /**
1730 /**
1730 * Success callback for loading a notebook from the server.
1731 * Success callback for loading a notebook from the server.
1731 *
1732 *
1732 * Load notebook data from the JSON response.
1733 * Load notebook data from the JSON response.
1733 *
1734 *
1734 * @method load_notebook_success
1735 * @method load_notebook_success
1735 * @param {Object} data JSON representation of a notebook
1736 * @param {Object} data JSON representation of a notebook
1736 * @param {String} status Description of response status
1737 * @param {String} status Description of response status
1737 * @param {jqXHR} xhr jQuery Ajax object
1738 * @param {jqXHR} xhr jQuery Ajax object
1738 */
1739 */
1739 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1740 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1740 this.fromJSON(data);
1741 this.fromJSON(data);
1741 if (this.ncells() === 0) {
1742 if (this.ncells() === 0) {
1742 this.insert_cell_below('code');
1743 this.insert_cell_below('code');
1743 };
1744 };
1744 this.set_dirty(false);
1745 this.set_dirty(false);
1745 this.select(0);
1746 this.select(0);
1746 this.scroll_to_top();
1747 this.scroll_to_top();
1747 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1748 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1748 var msg = "This notebook has been converted from an older " +
1749 var msg = "This notebook has been converted from an older " +
1749 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1750 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1750 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1751 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1751 "newer notebook format will be used and older versions of IPython " +
1752 "newer notebook format will be used and older versions of IPython " +
1752 "may not be able to read it. To keep the older version, close the " +
1753 "may not be able to read it. To keep the older version, close the " +
1753 "notebook without saving it.";
1754 "notebook without saving it.";
1754 IPython.dialog.modal({
1755 IPython.dialog.modal({
1755 title : "Notebook converted",
1756 title : "Notebook converted",
1756 body : msg,
1757 body : msg,
1757 buttons : {
1758 buttons : {
1758 OK : {
1759 OK : {
1759 class : "btn-primary"
1760 class : "btn-primary"
1760 }
1761 }
1761 }
1762 }
1762 });
1763 });
1763 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1764 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1764 var that = this;
1765 var that = this;
1765 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1766 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1766 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1767 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1767 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1768 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1768 this_vs + ". You can still work with this notebook, but some features " +
1769 this_vs + ". You can still work with this notebook, but some features " +
1769 "introduced in later notebook versions may not be available."
1770 "introduced in later notebook versions may not be available."
1770
1771
1771 IPython.dialog.modal({
1772 IPython.dialog.modal({
1772 title : "Newer Notebook",
1773 title : "Newer Notebook",
1773 body : msg,
1774 body : msg,
1774 buttons : {
1775 buttons : {
1775 OK : {
1776 OK : {
1776 class : "btn-danger"
1777 class : "btn-danger"
1777 }
1778 }
1778 }
1779 }
1779 });
1780 });
1780
1781
1781 }
1782 }
1782
1783
1783 // Create the kernel after the notebook is completely loaded to prevent
1784 // Create the kernel after the notebook is completely loaded to prevent
1784 // code execution upon loading, which is a security risk.
1785 // code execution upon loading, which is a security risk.
1785 this.start_kernel();
1786 this.start_kernel();
1786 // load our checkpoint list
1787 // load our checkpoint list
1787 IPython.notebook.list_checkpoints();
1788 IPython.notebook.list_checkpoints();
1788
1789
1789 $([IPython.events]).trigger('notebook_loaded.Notebook');
1790 $([IPython.events]).trigger('notebook_loaded.Notebook');
1790 };
1791 };
1791
1792
1792 /**
1793 /**
1793 * Failure callback for loading a notebook from the server.
1794 * Failure callback for loading a notebook from the server.
1794 *
1795 *
1795 * @method load_notebook_error
1796 * @method load_notebook_error
1796 * @param {jqXHR} xhr jQuery Ajax object
1797 * @param {jqXHR} xhr jQuery Ajax object
1797 * @param {String} textStatus Description of response status
1798 * @param {String} textStatus Description of response status
1798 * @param {String} errorThrow HTTP error message
1799 * @param {String} errorThrow HTTP error message
1799 */
1800 */
1800 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1801 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1801 if (xhr.status === 400) {
1802 if (xhr.status === 400) {
1802 var msg = errorThrow;
1803 var msg = errorThrow;
1803 } else if (xhr.status === 500) {
1804 } else if (xhr.status === 500) {
1804 var msg = "An unknown error occurred while loading this notebook. " +
1805 var msg = "An unknown error occurred while loading this notebook. " +
1805 "This version can load notebook formats " +
1806 "This version can load notebook formats " +
1806 "v" + this.nbformat + " or earlier.";
1807 "v" + this.nbformat + " or earlier.";
1807 }
1808 }
1808 IPython.dialog.modal({
1809 IPython.dialog.modal({
1809 title: "Error loading notebook",
1810 title: "Error loading notebook",
1810 body : msg,
1811 body : msg,
1811 buttons : {
1812 buttons : {
1812 "OK": {}
1813 "OK": {}
1813 }
1814 }
1814 });
1815 });
1815 }
1816 }
1816
1817
1817 /********************* checkpoint-related *********************/
1818 /********************* checkpoint-related *********************/
1818
1819
1819 /**
1820 /**
1820 * Save the notebook then immediately create a checkpoint.
1821 * Save the notebook then immediately create a checkpoint.
1821 *
1822 *
1822 * @method save_checkpoint
1823 * @method save_checkpoint
1823 */
1824 */
1824 Notebook.prototype.save_checkpoint = function () {
1825 Notebook.prototype.save_checkpoint = function () {
1825 this._checkpoint_after_save = true;
1826 this._checkpoint_after_save = true;
1826 this.save_notebook();
1827 this.save_notebook();
1827 };
1828 };
1828
1829
1829 /**
1830 /**
1830 * Add a checkpoint for this notebook.
1831 * Add a checkpoint for this notebook.
1831 * for use as a callback from checkpoint creation.
1832 * for use as a callback from checkpoint creation.
1832 *
1833 *
1833 * @method add_checkpoint
1834 * @method add_checkpoint
1834 */
1835 */
1835 Notebook.prototype.add_checkpoint = function (checkpoint) {
1836 Notebook.prototype.add_checkpoint = function (checkpoint) {
1836 var found = false;
1837 var found = false;
1837 for (var i = 0; i < this.checkpoints.length; i++) {
1838 for (var i = 0; i < this.checkpoints.length; i++) {
1838 var existing = this.checkpoints[i];
1839 var existing = this.checkpoints[i];
1839 if (existing.checkpoint_id == checkpoint.checkpoint_id) {
1840 if (existing.checkpoint_id == checkpoint.checkpoint_id) {
1840 found = true;
1841 found = true;
1841 this.checkpoints[i] = checkpoint;
1842 this.checkpoints[i] = checkpoint;
1842 break;
1843 break;
1843 }
1844 }
1844 }
1845 }
1845 if (!found) {
1846 if (!found) {
1846 this.checkpoints.push(checkpoint);
1847 this.checkpoints.push(checkpoint);
1847 }
1848 }
1848 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
1849 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
1849 };
1850 };
1850
1851
1851 /**
1852 /**
1852 * List checkpoints for this notebook.
1853 * List checkpoints for this notebook.
1853 *
1854 *
1854 * @method list_checkpoints
1855 * @method list_checkpoints
1855 */
1856 */
1856 Notebook.prototype.list_checkpoints = function () {
1857 Notebook.prototype.list_checkpoints = function () {
1857 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1858 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1858 $.get(url).done(
1859 $.get(url).done(
1859 $.proxy(this.list_checkpoints_success, this)
1860 $.proxy(this.list_checkpoints_success, this)
1860 ).fail(
1861 ).fail(
1861 $.proxy(this.list_checkpoints_error, this)
1862 $.proxy(this.list_checkpoints_error, this)
1862 );
1863 );
1863 };
1864 };
1864
1865
1865 /**
1866 /**
1866 * Success callback for listing checkpoints.
1867 * Success callback for listing checkpoints.
1867 *
1868 *
1868 * @method list_checkpoint_success
1869 * @method list_checkpoint_success
1869 * @param {Object} data JSON representation of a checkpoint
1870 * @param {Object} data JSON representation of a checkpoint
1870 * @param {String} status Description of response status
1871 * @param {String} status Description of response status
1871 * @param {jqXHR} xhr jQuery Ajax object
1872 * @param {jqXHR} xhr jQuery Ajax object
1872 */
1873 */
1873 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1874 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1874 var data = $.parseJSON(data);
1875 var data = $.parseJSON(data);
1875 this.checkpoints = data;
1876 this.checkpoints = data;
1876 if (data.length) {
1877 if (data.length) {
1877 this.last_checkpoint = data[data.length - 1];
1878 this.last_checkpoint = data[data.length - 1];
1878 } else {
1879 } else {
1879 this.last_checkpoint = null;
1880 this.last_checkpoint = null;
1880 }
1881 }
1881 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1882 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1882 };
1883 };
1883
1884
1884 /**
1885 /**
1885 * Failure callback for listing a checkpoint.
1886 * Failure callback for listing a checkpoint.
1886 *
1887 *
1887 * @method list_checkpoint_error
1888 * @method list_checkpoint_error
1888 * @param {jqXHR} xhr jQuery Ajax object
1889 * @param {jqXHR} xhr jQuery Ajax object
1889 * @param {String} status Description of response status
1890 * @param {String} status Description of response status
1890 * @param {String} error_msg HTTP error message
1891 * @param {String} error_msg HTTP error message
1891 */
1892 */
1892 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1893 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1893 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1894 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1894 };
1895 };
1895
1896
1896 /**
1897 /**
1897 * Create a checkpoint of this notebook on the server from the most recent save.
1898 * Create a checkpoint of this notebook on the server from the most recent save.
1898 *
1899 *
1899 * @method create_checkpoint
1900 * @method create_checkpoint
1900 */
1901 */
1901 Notebook.prototype.create_checkpoint = function () {
1902 Notebook.prototype.create_checkpoint = function () {
1902 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1903 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1903 $.post(url).done(
1904 $.post(url).done(
1904 $.proxy(this.create_checkpoint_success, this)
1905 $.proxy(this.create_checkpoint_success, this)
1905 ).fail(
1906 ).fail(
1906 $.proxy(this.create_checkpoint_error, this)
1907 $.proxy(this.create_checkpoint_error, this)
1907 );
1908 );
1908 };
1909 };
1909
1910
1910 /**
1911 /**
1911 * Success callback for creating a checkpoint.
1912 * Success callback for creating a checkpoint.
1912 *
1913 *
1913 * @method create_checkpoint_success
1914 * @method create_checkpoint_success
1914 * @param {Object} data JSON representation of a checkpoint
1915 * @param {Object} data JSON representation of a checkpoint
1915 * @param {String} status Description of response status
1916 * @param {String} status Description of response status
1916 * @param {jqXHR} xhr jQuery Ajax object
1917 * @param {jqXHR} xhr jQuery Ajax object
1917 */
1918 */
1918 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
1919 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
1919 var data = $.parseJSON(data);
1920 var data = $.parseJSON(data);
1920 this.add_checkpoint(data);
1921 this.add_checkpoint(data);
1921 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
1922 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
1922 };
1923 };
1923
1924
1924 /**
1925 /**
1925 * Failure callback for creating a checkpoint.
1926 * Failure callback for creating a checkpoint.
1926 *
1927 *
1927 * @method create_checkpoint_error
1928 * @method create_checkpoint_error
1928 * @param {jqXHR} xhr jQuery Ajax object
1929 * @param {jqXHR} xhr jQuery Ajax object
1929 * @param {String} status Description of response status
1930 * @param {String} status Description of response status
1930 * @param {String} error_msg HTTP error message
1931 * @param {String} error_msg HTTP error message
1931 */
1932 */
1932 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
1933 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
1933 $([IPython.events]).trigger('checkpoint_failed.Notebook');
1934 $([IPython.events]).trigger('checkpoint_failed.Notebook');
1934 };
1935 };
1935
1936
1936 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
1937 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
1937 var that = this;
1938 var that = this;
1938 var checkpoint = checkpoint || this.last_checkpoint;
1939 var checkpoint = checkpoint || this.last_checkpoint;
1939 if ( ! checkpoint ) {
1940 if ( ! checkpoint ) {
1940 console.log("restore dialog, but no checkpoint to restore to!");
1941 console.log("restore dialog, but no checkpoint to restore to!");
1941 return;
1942 return;
1942 }
1943 }
1943 var body = $('<div/>').append(
1944 var body = $('<div/>').append(
1944 $('<p/>').addClass("p-space").text(
1945 $('<p/>').addClass("p-space").text(
1945 "Are you sure you want to revert the notebook to " +
1946 "Are you sure you want to revert the notebook to " +
1946 "the latest checkpoint?"
1947 "the latest checkpoint?"
1947 ).append(
1948 ).append(
1948 $("<strong/>").text(
1949 $("<strong/>").text(
1949 " This cannot be undone."
1950 " This cannot be undone."
1950 )
1951 )
1951 )
1952 )
1952 ).append(
1953 ).append(
1953 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
1954 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
1954 ).append(
1955 ).append(
1955 $('<p/>').addClass("p-space").text(
1956 $('<p/>').addClass("p-space").text(
1956 Date(checkpoint.last_modified)
1957 Date(checkpoint.last_modified)
1957 ).css("text-align", "center")
1958 ).css("text-align", "center")
1958 );
1959 );
1959
1960
1960 IPython.dialog.modal({
1961 IPython.dialog.modal({
1961 title : "Revert notebook to checkpoint",
1962 title : "Revert notebook to checkpoint",
1962 body : body,
1963 body : body,
1963 buttons : {
1964 buttons : {
1964 Revert : {
1965 Revert : {
1965 class : "btn-danger",
1966 class : "btn-danger",
1966 click : function () {
1967 click : function () {
1967 that.restore_checkpoint(checkpoint.checkpoint_id);
1968 that.restore_checkpoint(checkpoint.checkpoint_id);
1968 }
1969 }
1969 },
1970 },
1970 Cancel : {}
1971 Cancel : {}
1971 }
1972 }
1972 });
1973 });
1973 }
1974 }
1974
1975
1975 /**
1976 /**
1976 * Restore the notebook to a checkpoint state.
1977 * Restore the notebook to a checkpoint state.
1977 *
1978 *
1978 * @method restore_checkpoint
1979 * @method restore_checkpoint
1979 * @param {String} checkpoint ID
1980 * @param {String} checkpoint ID
1980 */
1981 */
1981 Notebook.prototype.restore_checkpoint = function (checkpoint) {
1982 Notebook.prototype.restore_checkpoint = function (checkpoint) {
1982 $([IPython.events]).trigger('checkpoint_restoring.Notebook', checkpoint);
1983 $([IPython.events]).trigger('checkpoint_restoring.Notebook', checkpoint);
1983 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
1984 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
1984 $.post(url).done(
1985 $.post(url).done(
1985 $.proxy(this.restore_checkpoint_success, this)
1986 $.proxy(this.restore_checkpoint_success, this)
1986 ).fail(
1987 ).fail(
1987 $.proxy(this.restore_checkpoint_error, this)
1988 $.proxy(this.restore_checkpoint_error, this)
1988 );
1989 );
1989 };
1990 };
1990
1991
1991 /**
1992 /**
1992 * Success callback for restoring a notebook to a checkpoint.
1993 * Success callback for restoring a notebook to a checkpoint.
1993 *
1994 *
1994 * @method restore_checkpoint_success
1995 * @method restore_checkpoint_success
1995 * @param {Object} data (ignored, should be empty)
1996 * @param {Object} data (ignored, should be empty)
1996 * @param {String} status Description of response status
1997 * @param {String} status Description of response status
1997 * @param {jqXHR} xhr jQuery Ajax object
1998 * @param {jqXHR} xhr jQuery Ajax object
1998 */
1999 */
1999 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2000 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2000 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2001 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2001 this.load_notebook(this.notebook_id);
2002 this.load_notebook(this.notebook_id);
2002 };
2003 };
2003
2004
2004 /**
2005 /**
2005 * Failure callback for restoring a notebook to a checkpoint.
2006 * Failure callback for restoring a notebook to a checkpoint.
2006 *
2007 *
2007 * @method restore_checkpoint_error
2008 * @method restore_checkpoint_error
2008 * @param {jqXHR} xhr jQuery Ajax object
2009 * @param {jqXHR} xhr jQuery Ajax object
2009 * @param {String} status Description of response status
2010 * @param {String} status Description of response status
2010 * @param {String} error_msg HTTP error message
2011 * @param {String} error_msg HTTP error message
2011 */
2012 */
2012 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2013 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2013 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2014 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2014 };
2015 };
2015
2016
2016 /**
2017 /**
2017 * Delete a notebook checkpoint.
2018 * Delete a notebook checkpoint.
2018 *
2019 *
2019 * @method delete_checkpoint
2020 * @method delete_checkpoint
2020 * @param {String} checkpoint ID
2021 * @param {String} checkpoint ID
2021 */
2022 */
2022 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2023 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2023 $([IPython.events]).trigger('checkpoint_deleting.Notebook', checkpoint);
2024 $([IPython.events]).trigger('checkpoint_deleting.Notebook', checkpoint);
2024 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
2025 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
2025 $.ajax(url, {
2026 $.ajax(url, {
2026 type: 'DELETE',
2027 type: 'DELETE',
2027 success: $.proxy(this.delete_checkpoint_success, this),
2028 success: $.proxy(this.delete_checkpoint_success, this),
2028 error: $.proxy(this.delete_notebook_error,this)
2029 error: $.proxy(this.delete_notebook_error,this)
2029 });
2030 });
2030 };
2031 };
2031
2032
2032 /**
2033 /**
2033 * Success callback for deleting a notebook checkpoint
2034 * Success callback for deleting a notebook checkpoint
2034 *
2035 *
2035 * @method delete_checkpoint_success
2036 * @method delete_checkpoint_success
2036 * @param {Object} data (ignored, should be empty)
2037 * @param {Object} data (ignored, should be empty)
2037 * @param {String} status Description of response status
2038 * @param {String} status Description of response status
2038 * @param {jqXHR} xhr jQuery Ajax object
2039 * @param {jqXHR} xhr jQuery Ajax object
2039 */
2040 */
2040 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2041 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2041 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2042 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2042 this.load_notebook(this.notebook_id);
2043 this.load_notebook(this.notebook_id);
2043 };
2044 };
2044
2045
2045 /**
2046 /**
2046 * Failure callback for deleting a notebook checkpoint.
2047 * Failure callback for deleting a notebook checkpoint.
2047 *
2048 *
2048 * @method delete_checkpoint_error
2049 * @method delete_checkpoint_error
2049 * @param {jqXHR} xhr jQuery Ajax object
2050 * @param {jqXHR} xhr jQuery Ajax object
2050 * @param {String} status Description of response status
2051 * @param {String} status Description of response status
2051 * @param {String} error_msg HTTP error message
2052 * @param {String} error_msg HTTP error message
2052 */
2053 */
2053 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2054 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2054 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2055 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2055 };
2056 };
2056
2057
2057
2058
2058 IPython.Notebook = Notebook;
2059 IPython.Notebook = Notebook;
2059
2060
2060
2061
2061 return IPython;
2062 return IPython;
2062
2063
2063 }(IPython));
2064 }(IPython));
2064
2065
@@ -1,175 +1,176 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Pager
9 // Pager
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13
14
14 var utils = IPython.utils;
15 var utils = IPython.utils;
15
16
16 var Pager = function (pager_selector, pager_splitter_selector) {
17 var Pager = function (pager_selector, pager_splitter_selector) {
17 this.pager_element = $(pager_selector);
18 this.pager_element = $(pager_selector);
18 this.pager_button_area = $('#pager_button_area');
19 this.pager_button_area = $('#pager_button_area');
19 var that = this;
20 var that = this;
20 this.percentage_height = 0.40;
21 this.percentage_height = 0.40;
21 this.pager_splitter_element = $(pager_splitter_selector)
22 this.pager_splitter_element = $(pager_splitter_selector)
22 .draggable({
23 .draggable({
23 containment: 'window',
24 containment: 'window',
24 axis:'y',
25 axis:'y',
25 helper: null ,
26 helper: null ,
26 drag: function(event, ui) {
27 drag: function(event, ui) {
27 // recalculate the amount of space the pager should take
28 // recalculate the amount of space the pager should take
28 var pheight = ($(document.body).height()-event.clientY-4);
29 var pheight = ($(document.body).height()-event.clientY-4);
29 var downprct = pheight/IPython.layout_manager.app_height();
30 var downprct = pheight/IPython.layout_manager.app_height();
30 downprct = Math.min(0.9, downprct);
31 downprct = Math.min(0.9, downprct);
31 if (downprct < 0.1) {
32 if (downprct < 0.1) {
32 that.percentage_height = 0.1;
33 that.percentage_height = 0.1;
33 that.collapse({'duration':0});
34 that.collapse({'duration':0});
34 } else if (downprct > 0.2) {
35 } else if (downprct > 0.2) {
35 that.percentage_height = downprct;
36 that.percentage_height = downprct;
36 that.expand({'duration':0});
37 that.expand({'duration':0});
37 }
38 }
38 IPython.layout_manager.do_resize();
39 IPython.layout_manager.do_resize();
39 }
40 }
40 });
41 });
41 this.expanded = false;
42 this.expanded = false;
42 this.style();
43 this.style();
43 this.create_button_area();
44 this.create_button_area();
44 this.bind_events();
45 this.bind_events();
45 };
46 };
46
47
47 Pager.prototype.create_button_area = function(){
48 Pager.prototype.create_button_area = function(){
48 var that = this;
49 var that = this;
49 this.pager_button_area.append(
50 this.pager_button_area.append(
50 $('<a>').attr('role', "button")
51 $('<a>').attr('role', "button")
51 .attr('title',"Open the pager in an external window")
52 .attr('title',"Open the pager in an external window")
52 .addClass('ui-button')
53 .addClass('ui-button')
53 .click(function(){that.detach()})
54 .click(function(){that.detach()})
54 .attr('style','position: absolute; right: 20px;')
55 .attr('style','position: absolute; right: 20px;')
55 .append(
56 .append(
56 $('<span>').addClass("ui-icon ui-icon-extlink")
57 $('<span>').addClass("ui-icon ui-icon-extlink")
57 )
58 )
58 )
59 )
59 this.pager_button_area.append(
60 this.pager_button_area.append(
60 $('<a>').attr('role', "button")
61 $('<a>').attr('role', "button")
61 .attr('title',"Close the pager")
62 .attr('title',"Close the pager")
62 .addClass('ui-button')
63 .addClass('ui-button')
63 .click(function(){that.collapse()})
64 .click(function(){that.collapse()})
64 .attr('style','position: absolute; right: 5px;')
65 .attr('style','position: absolute; right: 5px;')
65 .append(
66 .append(
66 $('<span>').addClass("ui-icon ui-icon-close")
67 $('<span>').addClass("ui-icon ui-icon-close")
67 )
68 )
68 )
69 )
69 };
70 };
70
71
71 Pager.prototype.style = function () {
72 Pager.prototype.style = function () {
72 this.pager_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
73 this.pager_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
73 this.pager_element.addClass('border-box-sizing');
74 this.pager_element.addClass('border-box-sizing');
74 this.pager_element.find(".container").addClass('border-box-sizing');
75 this.pager_element.find(".container").addClass('border-box-sizing');
75 this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
76 this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
76 };
77 };
77
78
78
79
79 Pager.prototype.bind_events = function () {
80 Pager.prototype.bind_events = function () {
80 var that = this;
81 var that = this;
81
82
82 this.pager_element.bind('collapse_pager', function (event, extrap) {
83 this.pager_element.bind('collapse_pager', function (event, extrap) {
83 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
84 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
84 that.pager_element.hide(time);
85 that.pager_element.hide(time);
85 });
86 });
86
87
87 this.pager_element.bind('expand_pager', function (event, extrap) {
88 this.pager_element.bind('expand_pager', function (event, extrap) {
88 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
89 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
89 that.pager_element.show(time);
90 that.pager_element.show(time);
90 });
91 });
91
92
92 this.pager_splitter_element.hover(
93 this.pager_splitter_element.hover(
93 function () {
94 function () {
94 that.pager_splitter_element.addClass('ui-state-hover');
95 that.pager_splitter_element.addClass('ui-state-hover');
95 },
96 },
96 function () {
97 function () {
97 that.pager_splitter_element.removeClass('ui-state-hover');
98 that.pager_splitter_element.removeClass('ui-state-hover');
98 }
99 }
99 );
100 );
100
101
101 this.pager_splitter_element.click(function () {
102 this.pager_splitter_element.click(function () {
102 that.toggle();
103 that.toggle();
103 });
104 });
104
105
105 $([IPython.events]).on('open_with_text.Pager', function (event, data) {
106 $([IPython.events]).on('open_with_text.Pager', function (event, data) {
106 if (data.text.trim() !== '') {
107 if (data.text.trim() !== '') {
107 that.clear();
108 that.clear();
108 that.expand();
109 that.expand();
109 that.append_text(data.text);
110 that.append_text(data.text);
110 };
111 };
111 });
112 });
112 };
113 };
113
114
114
115
115 Pager.prototype.collapse = function (extrap) {
116 Pager.prototype.collapse = function (extrap) {
116 if (this.expanded === true) {
117 if (this.expanded === true) {
117 this.expanded = false;
118 this.expanded = false;
118 this.pager_element.add($('div#notebook')).trigger('collapse_pager', extrap);
119 this.pager_element.add($('div#notebook')).trigger('collapse_pager', extrap);
119 };
120 };
120 };
121 };
121
122
122
123
123 Pager.prototype.expand = function (extrap) {
124 Pager.prototype.expand = function (extrap) {
124 if (this.expanded !== true) {
125 if (this.expanded !== true) {
125 this.expanded = true;
126 this.expanded = true;
126 this.pager_element.add($('div#notebook')).trigger('expand_pager', extrap);
127 this.pager_element.add($('div#notebook')).trigger('expand_pager', extrap);
127 };
128 };
128 };
129 };
129
130
130
131
131 Pager.prototype.toggle = function () {
132 Pager.prototype.toggle = function () {
132 if (this.expanded === true) {
133 if (this.expanded === true) {
133 this.collapse();
134 this.collapse();
134 } else {
135 } else {
135 this.expand();
136 this.expand();
136 };
137 };
137 };
138 };
138
139
139
140
140 Pager.prototype.clear = function (text) {
141 Pager.prototype.clear = function (text) {
141 this.pager_element.find(".container").empty();
142 this.pager_element.find(".container").empty();
142 };
143 };
143
144
144 Pager.prototype.detach = function(){
145 Pager.prototype.detach = function(){
145 var w = window.open("","_blank");
146 var w = window.open("","_blank");
146 $(w.document.head)
147 $(w.document.head)
147 .append(
148 .append(
148 $('<link>')
149 $('<link>')
149 .attr('rel',"stylesheet")
150 .attr('rel',"stylesheet")
150 .attr('href',"/static/css/notebook.css")
151 .attr('href',"/static/css/notebook.css")
151 .attr('type',"text/css")
152 .attr('type',"text/css")
152 )
153 )
153 .append(
154 .append(
154 $('<title>').text("IPython Pager")
155 $('<title>').text("IPython Pager")
155 );
156 );
156 var pager_body = $(w.document.body);
157 var pager_body = $(w.document.body);
157 pager_body.css('overflow','scroll');
158 pager_body.css('overflow','scroll');
158
159
159 pager_body.append(this.pager_element.clone().children());
160 pager_body.append(this.pager_element.clone().children());
160 w.document.close();
161 w.document.close();
161 this.collapse();
162 this.collapse();
162
163
163 }
164 }
164
165
165 Pager.prototype.append_text = function (text) {
166 Pager.prototype.append_text = function (text) {
166 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
167 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
167 };
168 };
168
169
169
170
170 IPython.Pager = Pager;
171 IPython.Pager = Pager;
171
172
172 return IPython;
173 return IPython;
173
174
174 }(IPython));
175 }(IPython));
175
176
@@ -1,75 +1,76 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // QuickHelp button
9 // QuickHelp button
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13
14
14 var QuickHelp = function (selector) {
15 var QuickHelp = function (selector) {
15 };
16 };
16
17
17 QuickHelp.prototype.show_keyboard_shortcuts = function () {
18 QuickHelp.prototype.show_keyboard_shortcuts = function () {
18 // toggles display of keyboard shortcut dialog
19 // toggles display of keyboard shortcut dialog
19 var that = this;
20 var that = this;
20 if ( this.shortcut_dialog ){
21 if ( this.shortcut_dialog ){
21 // if dialog is already shown, close it
22 // if dialog is already shown, close it
22 $(this.shortcut_dialog).modal("toggle");
23 $(this.shortcut_dialog).modal("toggle");
23 return;
24 return;
24 }
25 }
25 var body = $('<div/>');
26 var body = $('<div/>');
26 var shortcuts = [
27 var shortcuts = [
27 {key: 'Shift-Enter', help: 'run cell'},
28 {key: 'Shift-Enter', help: 'run cell'},
28 {key: 'Ctrl-Enter', help: 'run cell in-place'},
29 {key: 'Ctrl-Enter', help: 'run cell in-place'},
29 {key: 'Alt-Enter', help: 'run cell, insert below'},
30 {key: 'Alt-Enter', help: 'run cell, insert below'},
30 {key: 'Ctrl-m x', help: 'cut cell'},
31 {key: 'Ctrl-m x', help: 'cut cell'},
31 {key: 'Ctrl-m c', help: 'copy cell'},
32 {key: 'Ctrl-m c', help: 'copy cell'},
32 {key: 'Ctrl-m v', help: 'paste cell'},
33 {key: 'Ctrl-m v', help: 'paste cell'},
33 {key: 'Ctrl-m d', help: 'delete cell'},
34 {key: 'Ctrl-m d', help: 'delete cell'},
34 {key: 'Ctrl-m z', help: 'undo last cell deletion'},
35 {key: 'Ctrl-m z', help: 'undo last cell deletion'},
35 {key: 'Ctrl-m -', help: 'split cell'},
36 {key: 'Ctrl-m -', help: 'split cell'},
36 {key: 'Ctrl-m a', help: 'insert cell above'},
37 {key: 'Ctrl-m a', help: 'insert cell above'},
37 {key: 'Ctrl-m b', help: 'insert cell below'},
38 {key: 'Ctrl-m b', help: 'insert cell below'},
38 {key: 'Ctrl-m o', help: 'toggle output'},
39 {key: 'Ctrl-m o', help: 'toggle output'},
39 {key: 'Ctrl-m O', help: 'toggle output scroll'},
40 {key: 'Ctrl-m O', help: 'toggle output scroll'},
40 {key: 'Ctrl-m l', help: 'toggle line numbers'},
41 {key: 'Ctrl-m l', help: 'toggle line numbers'},
41 {key: 'Ctrl-m s', help: 'save notebook'},
42 {key: 'Ctrl-m s', help: 'save notebook'},
42 {key: 'Ctrl-m j', help: 'move cell down'},
43 {key: 'Ctrl-m j', help: 'move cell down'},
43 {key: 'Ctrl-m k', help: 'move cell up'},
44 {key: 'Ctrl-m k', help: 'move cell up'},
44 {key: 'Ctrl-m y', help: 'code cell'},
45 {key: 'Ctrl-m y', help: 'code cell'},
45 {key: 'Ctrl-m m', help: 'markdown cell'},
46 {key: 'Ctrl-m m', help: 'markdown cell'},
46 {key: 'Ctrl-m t', help: 'raw cell'},
47 {key: 'Ctrl-m t', help: 'raw cell'},
47 {key: 'Ctrl-m 1-6', help: 'heading 1-6 cell'},
48 {key: 'Ctrl-m 1-6', help: 'heading 1-6 cell'},
48 {key: 'Ctrl-m p', help: 'select previous'},
49 {key: 'Ctrl-m p', help: 'select previous'},
49 {key: 'Ctrl-m n', help: 'select next'},
50 {key: 'Ctrl-m n', help: 'select next'},
50 {key: 'Ctrl-m i', help: 'interrupt kernel'},
51 {key: 'Ctrl-m i', help: 'interrupt kernel'},
51 {key: 'Ctrl-m .', help: 'restart kernel'},
52 {key: 'Ctrl-m .', help: 'restart kernel'},
52 {key: 'Ctrl-m h', help: 'show keyboard shortcuts'}
53 {key: 'Ctrl-m h', help: 'show keyboard shortcuts'}
53 ];
54 ];
54 for (var i=0; i<shortcuts.length; i++) {
55 for (var i=0; i<shortcuts.length; i++) {
55 body.append($('<div>').addClass('quickhelp').
56 body.append($('<div>').addClass('quickhelp').
56 append($('<span/>').addClass('shortcut_key').html(shortcuts[i].key)).
57 append($('<span/>').addClass('shortcut_key').html(shortcuts[i].key)).
57 append($('<span/>').addClass('shortcut_descr').html(' : ' + shortcuts[i].help))
58 append($('<span/>').addClass('shortcut_descr').html(' : ' + shortcuts[i].help))
58 );
59 );
59 };
60 };
60 this.shortcut_dialog = IPython.dialog.modal({
61 this.shortcut_dialog = IPython.dialog.modal({
61 title : "Keyboard shortcuts",
62 title : "Keyboard shortcuts",
62 body : body,
63 body : body,
63 destroy : false,
64 destroy : false,
64 buttons : {
65 buttons : {
65 Close : {}
66 Close : {}
66 }
67 }
67 });
68 });
68 };
69 };
69
70
70 // Set module variables
71 // Set module variables
71 IPython.QuickHelp = QuickHelp;
72 IPython.QuickHelp = QuickHelp;
72
73
73 return IPython;
74 return IPython;
74
75
75 }(IPython));
76 }(IPython));
@@ -1,157 +1,158 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // SaveWidget
9 // SaveWidget
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13
14
14 var utils = IPython.utils;
15 var utils = IPython.utils;
15
16
16 var SaveWidget = function (selector) {
17 var SaveWidget = function (selector) {
17 this.selector = selector;
18 this.selector = selector;
18 if (this.selector !== undefined) {
19 if (this.selector !== undefined) {
19 this.element = $(selector);
20 this.element = $(selector);
20 this.style();
21 this.style();
21 this.bind_events();
22 this.bind_events();
22 }
23 }
23 };
24 };
24
25
25
26
26 SaveWidget.prototype.style = function () {
27 SaveWidget.prototype.style = function () {
27 };
28 };
28
29
29
30
30 SaveWidget.prototype.bind_events = function () {
31 SaveWidget.prototype.bind_events = function () {
31 var that = this;
32 var that = this;
32 this.element.find('span#notebook_name').click(function () {
33 this.element.find('span#notebook_name').click(function () {
33 that.rename_notebook();
34 that.rename_notebook();
34 });
35 });
35 this.element.find('span#notebook_name').hover(function () {
36 this.element.find('span#notebook_name').hover(function () {
36 $(this).addClass("ui-state-hover");
37 $(this).addClass("ui-state-hover");
37 }, function () {
38 }, function () {
38 $(this).removeClass("ui-state-hover");
39 $(this).removeClass("ui-state-hover");
39 });
40 });
40 $([IPython.events]).on('notebook_loaded.Notebook', function () {
41 $([IPython.events]).on('notebook_loaded.Notebook', function () {
41 that.update_notebook_name();
42 that.update_notebook_name();
42 that.update_document_title();
43 that.update_document_title();
43 });
44 });
44 $([IPython.events]).on('notebook_saved.Notebook', function () {
45 $([IPython.events]).on('notebook_saved.Notebook', function () {
45 that.update_notebook_name();
46 that.update_notebook_name();
46 that.update_document_title();
47 that.update_document_title();
47 });
48 });
48 $([IPython.events]).on('notebook_save_failed.Notebook', function () {
49 $([IPython.events]).on('notebook_save_failed.Notebook', function () {
49 that.set_save_status('Autosave Failed!');
50 that.set_save_status('Autosave Failed!');
50 });
51 });
51 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
52 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
52 that.set_last_checkpoint(data[0]);
53 that.set_last_checkpoint(data[0]);
53 });
54 });
54
55
55 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
56 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
56 that.set_last_checkpoint(data);
57 that.set_last_checkpoint(data);
57 });
58 });
58 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
59 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
59 that.set_autosaved(data.value);
60 that.set_autosaved(data.value);
60 });
61 });
61 };
62 };
62
63
63
64
64 SaveWidget.prototype.rename_notebook = function () {
65 SaveWidget.prototype.rename_notebook = function () {
65 var that = this;
66 var that = this;
66 var dialog = $('<div/>').append(
67 var dialog = $('<div/>').append(
67 $("<p/>").addClass("rename-message")
68 $("<p/>").addClass("rename-message")
68 .html('Enter a new notebook name:')
69 .html('Enter a new notebook name:')
69 ).append(
70 ).append(
70 $("<br/>")
71 $("<br/>")
71 ).append(
72 ).append(
72 $('<input/>').attr('type','text').attr('size','25')
73 $('<input/>').attr('type','text').attr('size','25')
73 .val(IPython.notebook.get_notebook_name())
74 .val(IPython.notebook.get_notebook_name())
74 );
75 );
75 IPython.dialog.modal({
76 IPython.dialog.modal({
76 title: "Rename Notebook",
77 title: "Rename Notebook",
77 body: dialog,
78 body: dialog,
78 buttons : {
79 buttons : {
79 "Cancel": {},
80 "Cancel": {},
80 "OK": {
81 "OK": {
81 class: "btn-primary",
82 class: "btn-primary",
82 click: function () {
83 click: function () {
83 var new_name = $(this).find('input').val();
84 var new_name = $(this).find('input').val();
84 if (!IPython.notebook.test_notebook_name(new_name)) {
85 if (!IPython.notebook.test_notebook_name(new_name)) {
85 $(this).find('.rename-message').html(
86 $(this).find('.rename-message').html(
86 "Invalid notebook name. Notebook names must "+
87 "Invalid notebook name. Notebook names must "+
87 "have 1 or more characters and can contain any characters " +
88 "have 1 or more characters and can contain any characters " +
88 "except :/\\. Please enter a new notebook name:"
89 "except :/\\. Please enter a new notebook name:"
89 );
90 );
90 return false;
91 return false;
91 } else {
92 } else {
92 IPython.notebook.set_notebook_name(new_name);
93 IPython.notebook.set_notebook_name(new_name);
93 IPython.notebook.save_notebook();
94 IPython.notebook.save_notebook();
94 }
95 }
95 }}
96 }}
96 },
97 },
97 open : function (event, ui) {
98 open : function (event, ui) {
98 var that = $(this);
99 var that = $(this);
99 // Upon ENTER, click the OK button.
100 // Upon ENTER, click the OK button.
100 that.find('input[type="text"]').keydown(function (event, ui) {
101 that.find('input[type="text"]').keydown(function (event, ui) {
101 if (event.which === utils.keycodes.ENTER) {
102 if (event.which === utils.keycodes.ENTER) {
102 that.find('.btn-primary').first().click();
103 that.find('.btn-primary').first().click();
103 return false;
104 return false;
104 }
105 }
105 });
106 });
106 that.find('input[type="text"]').focus();
107 that.find('input[type="text"]').focus();
107 }
108 }
108 });
109 });
109 }
110 }
110
111
111
112
112 SaveWidget.prototype.update_notebook_name = function () {
113 SaveWidget.prototype.update_notebook_name = function () {
113 var nbname = IPython.notebook.get_notebook_name();
114 var nbname = IPython.notebook.get_notebook_name();
114 this.element.find('span#notebook_name').html(nbname);
115 this.element.find('span#notebook_name').html(nbname);
115 };
116 };
116
117
117
118
118 SaveWidget.prototype.update_document_title = function () {
119 SaveWidget.prototype.update_document_title = function () {
119 var nbname = IPython.notebook.get_notebook_name();
120 var nbname = IPython.notebook.get_notebook_name();
120 document.title = nbname;
121 document.title = nbname;
121 };
122 };
122
123
123
124
124 SaveWidget.prototype.set_save_status = function (msg) {
125 SaveWidget.prototype.set_save_status = function (msg) {
125 this.element.find('span#autosave_status').html(msg);
126 this.element.find('span#autosave_status').html(msg);
126 }
127 }
127
128
128 SaveWidget.prototype.set_checkpoint_status = function (msg) {
129 SaveWidget.prototype.set_checkpoint_status = function (msg) {
129 this.element.find('span#checkpoint_status').html(msg);
130 this.element.find('span#checkpoint_status').html(msg);
130 }
131 }
131
132
132 SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
133 SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
133 if (!checkpoint) {
134 if (!checkpoint) {
134 this.set_checkpoint_status("");
135 this.set_checkpoint_status("");
135 return;
136 return;
136 }
137 }
137 var d = new Date(checkpoint.last_modified);
138 var d = new Date(checkpoint.last_modified);
138 this.set_checkpoint_status(
139 this.set_checkpoint_status(
139 "Last Checkpoint: " + d.format('mmm dd HH:MM')
140 "Last Checkpoint: " + d.format('mmm dd HH:MM')
140 );
141 );
141 }
142 }
142
143
143 SaveWidget.prototype.set_autosaved = function (dirty) {
144 SaveWidget.prototype.set_autosaved = function (dirty) {
144 if (dirty) {
145 if (dirty) {
145 this.set_save_status("(unsaved changes)");
146 this.set_save_status("(unsaved changes)");
146 } else {
147 } else {
147 this.set_save_status("(autosaved)");
148 this.set_save_status("(autosaved)");
148 }
149 }
149 };
150 };
150
151
151
152
152 IPython.SaveWidget = SaveWidget;
153 IPython.SaveWidget = SaveWidget;
153
154
154 return IPython;
155 return IPython;
155
156
156 }(IPython));
157 }(IPython));
157
158
@@ -1,108 +1,109 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008 The IPython Development Team
2 // Copyright (C) 2008 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // ToolBar
9 // ToolBar
10 //============================================================================
10 //============================================================================
11 /**
11 /**
12 * @module IPython
12 * @module IPython
13 * @namespace IPython
13 * @namespace IPython
14 * @submodule ToolBar
14 * @submodule ToolBar
15 */
15 */
16
16
17 var IPython = (function (IPython) {
17 var IPython = (function (IPython) {
18 "use strict";
18
19
19 /**
20 /**
20 * A generic toolbar on which one can add button
21 * A generic toolbar on which one can add button
21 * @class ToolBar
22 * @class ToolBar
22 * @constructor
23 * @constructor
23 * @param {Dom object} selector
24 * @param {Dom object} selector
24 */
25 */
25 var ToolBar = function (selector) {
26 var ToolBar = function (selector) {
26 this.selector = selector;
27 this.selector = selector;
27 if (this.selector !== undefined) {
28 if (this.selector !== undefined) {
28 this.element = $(selector);
29 this.element = $(selector);
29 this.style();
30 this.style();
30 }
31 }
31 };
32 };
32
33
33 /**
34 /**
34 * add a group of button into the current toolbar.
35 * add a group of button into the current toolbar.
35 *
36 *
36 *
37 *
37 * @example
38 * @example
38 *
39 *
39 * IPython.toolbar.add_buttons_group([
40 * IPython.toolbar.add_buttons_group([
40 * {
41 * {
41 * label:'my button',
42 * label:'my button',
42 * icon:'icon-hdd',
43 * icon:'icon-hdd',
43 * callback:function(){alert('hoho')},
44 * callback:function(){alert('hoho')},
44 * id : 'my_button_id', // this is optional
45 * id : 'my_button_id', // this is optional
45 * },
46 * },
46 * {
47 * {
47 * label:'my second button',
48 * label:'my second button',
48 * icon:'icon-play',
49 * icon:'icon-play',
49 * callback:function(){alert('be carefull I cut')}
50 * callback:function(){alert('be carefull I cut')}
50 * }
51 * }
51 * ],
52 * ],
52 * "my_button_group_id"
53 * "my_button_group_id"
53 * )
54 * )
54 *
55 *
55 * @method add_buttons_group
56 * @method add_buttons_group
56 * @param list {List}
57 * @param list {List}
57 * List of button of the group, with the following paramter for each :
58 * List of button of the group, with the following paramter for each :
58 * @param list.label {string} text to show on button hover
59 * @param list.label {string} text to show on button hover
59 * @param list.icon {string} icon to choose from [Font Awesome](http://fortawesome.github.io/Font-Awesome)
60 * @param list.icon {string} icon to choose from [Font Awesome](http://fortawesome.github.io/Font-Awesome)
60 * @param list.callback {function} function to be called on button click
61 * @param list.callback {function} function to be called on button click
61 * @param [list.id] {String} id to give to the button
62 * @param [list.id] {String} id to give to the button
62 * @param [group_id] {String} optionnal id to give to the group
63 * @param [group_id] {String} optionnal id to give to the group
63 *
64 *
64 */
65 */
65 ToolBar.prototype.add_buttons_group = function (list, group_id) {
66 ToolBar.prototype.add_buttons_group = function (list, group_id) {
66 var btn_group = $('<div/>').addClass("btn-group");
67 var btn_group = $('<div/>').addClass("btn-group");
67 if( group_id != undefined ) {
68 if( group_id != undefined ) {
68 btn_group.attr('id',group_id);
69 btn_group.attr('id',group_id);
69 }
70 }
70 for(var el in list) {
71 for(var el in list) {
71 var button = $('<button/>')
72 var button = $('<button/>')
72 .addClass('btn')
73 .addClass('btn')
73 .attr("title", list[el].label)
74 .attr("title", list[el].label)
74 .append(
75 .append(
75 $("<i/>").addClass(list[el].icon)
76 $("<i/>").addClass(list[el].icon)
76 );
77 );
77 var id = list[el].id;
78 var id = list[el].id;
78 if( id != undefined )
79 if( id != undefined )
79 button.attr('id',id);
80 button.attr('id',id);
80 var fun = list[el].callback;
81 var fun = list[el].callback;
81 button.click(fun);
82 button.click(fun);
82 btn_group.append(button);
83 btn_group.append(button);
83 }
84 }
84 $(this.selector).append(btn_group);
85 $(this.selector).append(btn_group);
85 };
86 };
86
87
87 ToolBar.prototype.style = function () {
88 ToolBar.prototype.style = function () {
88 this.element.addClass('border-box-sizing')
89 this.element.addClass('border-box-sizing')
89 .addClass('toolbar');
90 .addClass('toolbar');
90 };
91 };
91
92
92 /**
93 /**
93 * Show and hide toolbar
94 * Show and hide toolbar
94 * @method toggle
95 * @method toggle
95 */
96 */
96 ToolBar.prototype.toggle = function () {
97 ToolBar.prototype.toggle = function () {
97 this.element.toggle();
98 this.element.toggle();
98 if (IPython.layout_manager != undefined) {
99 if (IPython.layout_manager != undefined) {
99 IPython.layout_manager.do_resize();
100 IPython.layout_manager.do_resize();
100 }
101 }
101 };
102 };
102
103
103
104
104 IPython.ToolBar = ToolBar;
105 IPython.ToolBar = ToolBar;
105
106
106 return IPython;
107 return IPython;
107
108
108 }(IPython));
109 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now