##// END OF EJS Templates
Merge pull request #5605 from AlbertHilb/CellToolbar...
Min RK -
r16746:fc40b4e2 merge
parent child Browse files
Show More
@@ -1,416 +1,422 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 // CellToolbar
9 // CellToolbar
10 //============================================================================
10 //============================================================================
11
11
12
12
13 /**
13 /**
14 * A Module to control the per-cell toolbar.
14 * A Module to control the per-cell toolbar.
15 * @module IPython
15 * @module IPython
16 * @namespace IPython
16 * @namespace IPython
17 * @submodule CellToolbar
17 * @submodule CellToolbar
18 */
18 */
19 var IPython = (function (IPython) {
19 var IPython = (function (IPython) {
20 "use strict";
20 "use strict";
21
21
22 /**
22 /**
23 * @constructor
23 * @constructor
24 * @class CellToolbar
24 * @class CellToolbar
25 * @param {The cell to attach the metadata UI to} cell
25 * @param {The cell to attach the metadata UI to} cell
26 */
26 */
27 var CellToolbar = function (cell) {
27 var CellToolbar = function (cell) {
28 CellToolbar._instances.push(this);
28 CellToolbar._instances.push(this);
29 this.cell = cell;
29 this.cell = cell;
30 this.create_element();
30 this.create_element();
31 this.rebuild();
31 this.rebuild();
32 return this;
32 return this;
33 };
33 };
34
34
35
35
36 CellToolbar.prototype.create_element = function () {
36 CellToolbar.prototype.create_element = function () {
37 this.inner_element = $('<div/>').addClass('celltoolbar')
37 this.inner_element = $('<div/>').addClass('celltoolbar')
38 this.element = $('<div/>').addClass('ctb_hideshow')
38 this.element = $('<div/>').addClass('ctb_hideshow')
39 .append(this.inner_element);
39 .append(this.inner_element);
40 };
40 };
41
41
42
42
43 // The default css style for the outer celltoolbar div
43 // The default css style for the outer celltoolbar div
44 // (ctb_hideshow) is display: none.
44 // (ctb_hideshow) is display: none.
45 // To show the cell toolbar, *both* of the following conditions must be met:
45 // To show the cell toolbar, *both* of the following conditions must be met:
46 // - A parent container has class `ctb_global_show`
46 // - A parent container has class `ctb_global_show`
47 // - The celltoolbar has the class `ctb_show`
47 // - The celltoolbar has the class `ctb_show`
48 // This allows global show/hide, as well as per-cell show/hide.
48 // This allows global show/hide, as well as per-cell show/hide.
49
49
50 CellToolbar.global_hide = function () {
50 CellToolbar.global_hide = function () {
51 $('body').removeClass('ctb_global_show');
51 $('body').removeClass('ctb_global_show');
52 };
52 };
53
53
54
54
55 CellToolbar.global_show = function () {
55 CellToolbar.global_show = function () {
56 $('body').addClass('ctb_global_show');
56 $('body').addClass('ctb_global_show');
57 };
57 };
58
58
59
59
60 CellToolbar.prototype.hide = function () {
60 CellToolbar.prototype.hide = function () {
61 this.element.removeClass('ctb_show');
61 this.element.removeClass('ctb_show');
62 };
62 };
63
63
64
64
65 CellToolbar.prototype.show = function () {
65 CellToolbar.prototype.show = function () {
66 this.element.addClass('ctb_show');
66 this.element.addClass('ctb_show');
67 };
67 };
68
68
69
69
70 /**
70 /**
71 * Class variable that should contain a dict of all available callback
71 * Class variable that should contain a dict of all available callback
72 * we need to think of wether or not we allow nested namespace
72 * we need to think of wether or not we allow nested namespace
73 * @property _callback_dict
73 * @property _callback_dict
74 * @private
74 * @private
75 * @static
75 * @static
76 * @type Dict
76 * @type Dict
77 */
77 */
78 CellToolbar._callback_dict = {};
78 CellToolbar._callback_dict = {};
79
79
80
80
81 /**
81 /**
82 * Class variable that should contain the reverse order list of the button
82 * Class variable that should contain the reverse order list of the button
83 * to add to the toolbar of each cell
83 * to add to the toolbar of each cell
84 * @property _ui_controls_list
84 * @property _ui_controls_list
85 * @private
85 * @private
86 * @static
86 * @static
87 * @type List
87 * @type List
88 */
88 */
89 CellToolbar._ui_controls_list = [];
89 CellToolbar._ui_controls_list = [];
90
90
91
91
92 /**
92 /**
93 * Class variable that should contain the CellToolbar instances for each
93 * Class variable that should contain the CellToolbar instances for each
94 * cell of the notebook
94 * cell of the notebook
95 *
95 *
96 * @private
96 * @private
97 * @property _instances
97 * @property _instances
98 * @static
98 * @static
99 * @type List
99 * @type List
100 */
100 */
101 CellToolbar._instances = [];
101 CellToolbar._instances = [];
102
102
103
103
104 /**
104 /**
105 * keep a list of all the available presets for the toolbar
105 * keep a list of all the available presets for the toolbar
106 * @private
106 * @private
107 * @property _presets
107 * @property _presets
108 * @static
108 * @static
109 * @type Dict
109 * @type Dict
110 */
110 */
111 CellToolbar._presets = {};
111 CellToolbar._presets = {};
112
112
113
113
114 // this is by design not a prototype.
114 // this is by design not a prototype.
115 /**
115 /**
116 * Register a callback to create an UI element in a cell toolbar.
116 * Register a callback to create an UI element in a cell toolbar.
117 * @method register_callback
117 * @method register_callback
118 * @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name
118 * @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name
119 * for easier sorting and avoid collision
119 * for easier sorting and avoid collision
120 * @param callback {function(div, cell)} callback that will be called to generate the ui element
120 * @param callback {function(div, cell)} callback that will be called to generate the ui element
121 * @param [cell_types] {List of String|undefined} optional list of cell types. If present the UI element
121 * @param [cell_types] {List of String|undefined} optional list of cell types. If present the UI element
122 * will be added only to cells of types in the list.
122 * will be added only to cells of types in the list.
123 *
123 *
124 *
124 *
125 * The callback will receive the following element :
125 * The callback will receive the following element :
126 *
126 *
127 * * a div in which to add element.
127 * * a div in which to add element.
128 * * the cell it is responsible from
128 * * the cell it is responsible from
129 *
129 *
130 * @example
130 * @example
131 *
131 *
132 * Example that create callback for a button that toggle between `true` and `false` label,
132 * Example that create callback for a button that toggle between `true` and `false` label,
133 * with the metadata under the key 'foo' to reflect the status of the button.
133 * with the metadata under the key 'foo' to reflect the status of the button.
134 *
134 *
135 * // first param reference to a DOM div
135 * // first param reference to a DOM div
136 * // second param reference to the cell.
136 * // second param reference to the cell.
137 * var toggle = function(div, cell) {
137 * var toggle = function(div, cell) {
138 * var button_container = $(div)
138 * var button_container = $(div)
139 *
139 *
140 * // let's create a button that show the current value of the metadata
140 * // let's create a button that show the current value of the metadata
141 * var button = $('<div/>').button({label:String(cell.metadata.foo)});
141 * var button = $('<div/>').button({label:String(cell.metadata.foo)});
142 *
142 *
143 * // On click, change the metadata value and update the button label
143 * // On click, change the metadata value and update the button label
144 * button.click(function(){
144 * button.click(function(){
145 * var v = cell.metadata.foo;
145 * var v = cell.metadata.foo;
146 * cell.metadata.foo = !v;
146 * cell.metadata.foo = !v;
147 * button.button("option", "label", String(!v));
147 * button.button("option", "label", String(!v));
148 * })
148 * })
149 *
149 *
150 * // add the button to the DOM div.
150 * // add the button to the DOM div.
151 * button_container.append(button);
151 * button_container.append(button);
152 * }
152 * }
153 *
153 *
154 * // now we register the callback under the name `foo` to give the
154 * // now we register the callback under the name `foo` to give the
155 * // user the ability to use it later
155 * // user the ability to use it later
156 * CellToolbar.register_callback('foo', toggle);
156 * CellToolbar.register_callback('foo', toggle);
157 */
157 */
158 CellToolbar.register_callback = function(name, callback, cell_types) {
158 CellToolbar.register_callback = function(name, callback, cell_types) {
159 // Overwrite if it already exists.
159 // Overwrite if it already exists.
160 CellToolbar._callback_dict[name] = cell_types ? {callback: callback, cell_types: cell_types} : callback;
160 CellToolbar._callback_dict[name] = cell_types ? {callback: callback, cell_types: cell_types} : callback;
161 };
161 };
162
162
163
163
164 /**
164 /**
165 * Register a preset of UI element in a cell toolbar.
165 * Register a preset of UI element in a cell toolbar.
166 * Not supported Yet.
166 * Not supported Yet.
167 * @method register_preset
167 * @method register_preset
168 * @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name
168 * @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name
169 * for easier sorting and avoid collision
169 * for easier sorting and avoid collision
170 * @param preset_list {List of String} reverse order of the button in the toolbar. Each String of the list
170 * @param preset_list {List of String} reverse order of the button in the toolbar. Each String of the list
171 * should correspond to a name of a registerd callback.
171 * should correspond to a name of a registerd callback.
172 *
172 *
173 * @private
173 * @private
174 * @example
174 * @example
175 *
175 *
176 * CellToolbar.register_callback('foo.c1', function(div, cell){...});
176 * CellToolbar.register_callback('foo.c1', function(div, cell){...});
177 * CellToolbar.register_callback('foo.c2', function(div, cell){...});
177 * CellToolbar.register_callback('foo.c2', function(div, cell){...});
178 * CellToolbar.register_callback('foo.c3', function(div, cell){...});
178 * CellToolbar.register_callback('foo.c3', function(div, cell){...});
179 * CellToolbar.register_callback('foo.c4', function(div, cell){...});
179 * CellToolbar.register_callback('foo.c4', function(div, cell){...});
180 * CellToolbar.register_callback('foo.c5', function(div, cell){...});
180 * CellToolbar.register_callback('foo.c5', function(div, cell){...});
181 *
181 *
182 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
182 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
183 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
183 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
184 */
184 */
185 CellToolbar.register_preset = function(name, preset_list) {
185 CellToolbar.register_preset = function(name, preset_list) {
186 CellToolbar._presets[name] = preset_list;
186 CellToolbar._presets[name] = preset_list;
187 $([IPython.events]).trigger('preset_added.CellToolbar', {name: name});
187 $([IPython.events]).trigger('preset_added.CellToolbar', {name: name});
188 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
189 // In that case, activate the preset if needed.
190 if (IPython.notebook && IPython.notebook.metadata && IPython.notebook.metadata.celltoolbar === name)
191 this.activate_preset(name);
188 };
192 };
189
193
190
194
191 /**
195 /**
192 * List the names of the presets that are currently registered.
196 * List the names of the presets that are currently registered.
193 *
197 *
194 * @method list_presets
198 * @method list_presets
195 * @static
199 * @static
196 */
200 */
197 CellToolbar.list_presets = function() {
201 CellToolbar.list_presets = function() {
198 var keys = [];
202 var keys = [];
199 for (var k in CellToolbar._presets) {
203 for (var k in CellToolbar._presets) {
200 keys.push(k);
204 keys.push(k);
201 }
205 }
202 return keys;
206 return keys;
203 };
207 };
204
208
205
209
206 /**
210 /**
207 * Activate an UI preset from `register_preset`
211 * Activate an UI preset from `register_preset`
208 *
212 *
209 * This does not update the selection UI.
213 * This does not update the selection UI.
210 *
214 *
211 * @method activate_preset
215 * @method activate_preset
212 * @param preset_name {String} string corresponding to the preset name
216 * @param preset_name {String} string corresponding to the preset name
213 *
217 *
214 * @static
218 * @static
215 * @private
219 * @private
216 * @example
220 * @example
217 *
221 *
218 * CellToolbar.activate_preset('foo.foo_preset1');
222 * CellToolbar.activate_preset('foo.foo_preset1');
219 */
223 */
220 CellToolbar.activate_preset = function(preset_name){
224 CellToolbar.activate_preset = function(preset_name){
221 var preset = CellToolbar._presets[preset_name];
225 var preset = CellToolbar._presets[preset_name];
222
226
223 if(preset !== undefined){
227 if(preset !== undefined){
224 CellToolbar._ui_controls_list = preset;
228 CellToolbar._ui_controls_list = preset;
225 CellToolbar.rebuild_all();
229 CellToolbar.rebuild_all();
226 }
230 }
231
232 $([IPython.events]).trigger('preset_activated.CellToolbar', {name: preset_name});
227 };
233 };
228
234
229
235
230 /**
236 /**
231 * This should be called on the class and not on a instance as it will trigger
237 * This should be called on the class and not on a instance as it will trigger
232 * rebuild of all the instances.
238 * rebuild of all the instances.
233 * @method rebuild_all
239 * @method rebuild_all
234 * @static
240 * @static
235 *
241 *
236 */
242 */
237 CellToolbar.rebuild_all = function(){
243 CellToolbar.rebuild_all = function(){
238 for(var i=0; i < CellToolbar._instances.length; i++){
244 for(var i=0; i < CellToolbar._instances.length; i++){
239 CellToolbar._instances[i].rebuild();
245 CellToolbar._instances[i].rebuild();
240 }
246 }
241 };
247 };
242
248
243 /**
249 /**
244 * Rebuild all the button on the toolbar to update its state.
250 * Rebuild all the button on the toolbar to update its state.
245 * @method rebuild
251 * @method rebuild
246 */
252 */
247 CellToolbar.prototype.rebuild = function(){
253 CellToolbar.prototype.rebuild = function(){
248 // strip evrything from the div
254 // strip evrything from the div
249 // which is probably inner_element
255 // which is probably inner_element
250 // or this.element.
256 // or this.element.
251 this.inner_element.empty();
257 this.inner_element.empty();
252 this.ui_controls_list = [];
258 this.ui_controls_list = [];
253
259
254 var callbacks = CellToolbar._callback_dict;
260 var callbacks = CellToolbar._callback_dict;
255 var preset = CellToolbar._ui_controls_list;
261 var preset = CellToolbar._ui_controls_list;
256 // Yes we iterate on the class variable, not the instance one.
262 // Yes we iterate on the class variable, not the instance one.
257 for (var i=0; i < preset.length; i++) {
263 for (var i=0; i < preset.length; i++) {
258 var key = preset[i];
264 var key = preset[i];
259 var callback = callbacks[key];
265 var callback = callbacks[key];
260 if (!callback) continue;
266 if (!callback) continue;
261
267
262 if (typeof callback === 'object') {
268 if (typeof callback === 'object') {
263 if (callback.cell_types.indexOf(this.cell.cell_type) === -1) continue;
269 if (callback.cell_types.indexOf(this.cell.cell_type) === -1) continue;
264 callback = callback.callback;
270 callback = callback.callback;
265 }
271 }
266
272
267 var local_div = $('<div/>').addClass('button_container');
273 var local_div = $('<div/>').addClass('button_container');
268 try {
274 try {
269 callback(local_div, this.cell, this);
275 callback(local_div, this.cell, this);
270 this.ui_controls_list.push(key);
276 this.ui_controls_list.push(key);
271 } catch (e) {
277 } catch (e) {
272 console.log("Error in cell toolbar callback " + key, e);
278 console.log("Error in cell toolbar callback " + key, e);
273 continue;
279 continue;
274 }
280 }
275 // only append if callback succeeded.
281 // only append if callback succeeded.
276 this.inner_element.append(local_div);
282 this.inner_element.append(local_div);
277 }
283 }
278
284
279 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
285 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
280 if (!this.ui_controls_list.length || (this.cell instanceof IPython.TextCell && this.cell.rendered)) {
286 if (!this.ui_controls_list.length || (this.cell instanceof IPython.TextCell && this.cell.rendered)) {
281 this.hide();
287 this.hide();
282 } else {
288 } else {
283 this.show();
289 this.show();
284 }
290 }
285 };
291 };
286
292
287
293
288 /**
294 /**
289 */
295 */
290 CellToolbar.utils = {};
296 CellToolbar.utils = {};
291
297
292
298
293 /**
299 /**
294 * A utility function to generate bindings between a checkbox and cell/metadata
300 * A utility function to generate bindings between a checkbox and cell/metadata
295 * @method utils.checkbox_ui_generator
301 * @method utils.checkbox_ui_generator
296 * @static
302 * @static
297 *
303 *
298 * @param name {string} Label in front of the checkbox
304 * @param name {string} Label in front of the checkbox
299 * @param setter {function( cell, newValue )}
305 * @param setter {function( cell, newValue )}
300 * A setter method to set the newValue
306 * A setter method to set the newValue
301 * @param getter {function( cell )}
307 * @param getter {function( cell )}
302 * A getter methods which return the current value.
308 * A getter methods which return the current value.
303 *
309 *
304 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
310 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
305 *
311 *
306 * @example
312 * @example
307 *
313 *
308 * An exmple that bind the subkey `slideshow.isSectionStart` to a checkbox with a `New Slide` label
314 * An exmple that bind the subkey `slideshow.isSectionStart` to a checkbox with a `New Slide` label
309 *
315 *
310 * var newSlide = CellToolbar.utils.checkbox_ui_generator('New Slide',
316 * var newSlide = CellToolbar.utils.checkbox_ui_generator('New Slide',
311 * // setter
317 * // setter
312 * function(cell, value){
318 * function(cell, value){
313 * // we check that the slideshow namespace exist and create it if needed
319 * // we check that the slideshow namespace exist and create it if needed
314 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
320 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
315 * // set the value
321 * // set the value
316 * cell.metadata.slideshow.isSectionStart = value
322 * cell.metadata.slideshow.isSectionStart = value
317 * },
323 * },
318 * //geter
324 * //geter
319 * function(cell){ var ns = cell.metadata.slideshow;
325 * function(cell){ var ns = cell.metadata.slideshow;
320 * // if the slideshow namespace does not exist return `undefined`
326 * // if the slideshow namespace does not exist return `undefined`
321 * // (will be interpreted as `false` by checkbox) otherwise
327 * // (will be interpreted as `false` by checkbox) otherwise
322 * // return the value
328 * // return the value
323 * return (ns == undefined)? undefined: ns.isSectionStart
329 * return (ns == undefined)? undefined: ns.isSectionStart
324 * }
330 * }
325 * );
331 * );
326 *
332 *
327 * CellToolbar.register_callback('newSlide', newSlide);
333 * CellToolbar.register_callback('newSlide', newSlide);
328 *
334 *
329 */
335 */
330 CellToolbar.utils.checkbox_ui_generator = function(name, setter, getter){
336 CellToolbar.utils.checkbox_ui_generator = function(name, setter, getter){
331 return function(div, cell, celltoolbar) {
337 return function(div, cell, celltoolbar) {
332 var button_container = $(div);
338 var button_container = $(div);
333
339
334 var chkb = $('<input/>').attr('type', 'checkbox');
340 var chkb = $('<input/>').attr('type', 'checkbox');
335 var lbl = $('<label/>').append($('<span/>').text(name));
341 var lbl = $('<label/>').append($('<span/>').text(name));
336 lbl.append(chkb);
342 lbl.append(chkb);
337 chkb.attr("checked", getter(cell));
343 chkb.attr("checked", getter(cell));
338
344
339 chkb.click(function(){
345 chkb.click(function(){
340 var v = getter(cell);
346 var v = getter(cell);
341 setter(cell, !v);
347 setter(cell, !v);
342 chkb.attr("checked", !v);
348 chkb.attr("checked", !v);
343 });
349 });
344 button_container.append($('<div/>').append(lbl));
350 button_container.append($('<div/>').append(lbl));
345 };
351 };
346 };
352 };
347
353
348
354
349 /**
355 /**
350 * A utility function to generate bindings between a dropdown list cell
356 * A utility function to generate bindings between a dropdown list cell
351 * @method utils.select_ui_generator
357 * @method utils.select_ui_generator
352 * @static
358 * @static
353 *
359 *
354 * @param list_list {list of sublist} List of sublist of metadata value and name in the dropdown list.
360 * @param list_list {list of sublist} List of sublist of metadata value and name in the dropdown list.
355 * subslit shoud contain 2 element each, first a string that woul be displayed in the dropdown list,
361 * subslit shoud contain 2 element each, first a string that woul be displayed in the dropdown list,
356 * and second the corresponding value to be passed to setter/return by getter. the corresponding value
362 * and second the corresponding value to be passed to setter/return by getter. the corresponding value
357 * should not be "undefined" or behavior can be unexpected.
363 * should not be "undefined" or behavior can be unexpected.
358 * @param setter {function( cell, newValue )}
364 * @param setter {function( cell, newValue )}
359 * A setter method to set the newValue
365 * A setter method to set the newValue
360 * @param getter {function( cell )}
366 * @param getter {function( cell )}
361 * A getter methods which return the current value of the metadata.
367 * A getter methods which return the current value of the metadata.
362 * @param [label=""] {String} optionnal label for the dropdown menu
368 * @param [label=""] {String} optionnal label for the dropdown menu
363 *
369 *
364 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
370 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
365 *
371 *
366 * @example
372 * @example
367 *
373 *
368 * var select_type = CellToolbar.utils.select_ui_generator([
374 * var select_type = CellToolbar.utils.select_ui_generator([
369 * ["<None>" , "None" ],
375 * ["<None>" , "None" ],
370 * ["Header Slide" , "header_slide" ],
376 * ["Header Slide" , "header_slide" ],
371 * ["Slide" , "slide" ],
377 * ["Slide" , "slide" ],
372 * ["Fragment" , "fragment" ],
378 * ["Fragment" , "fragment" ],
373 * ["Skip" , "skip" ],
379 * ["Skip" , "skip" ],
374 * ],
380 * ],
375 * // setter
381 * // setter
376 * function(cell, value){
382 * function(cell, value){
377 * // we check that the slideshow namespace exist and create it if needed
383 * // we check that the slideshow namespace exist and create it if needed
378 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
384 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
379 * // set the value
385 * // set the value
380 * cell.metadata.slideshow.slide_type = value
386 * cell.metadata.slideshow.slide_type = value
381 * },
387 * },
382 * //geter
388 * //geter
383 * function(cell){ var ns = cell.metadata.slideshow;
389 * function(cell){ var ns = cell.metadata.slideshow;
384 * // if the slideshow namespace does not exist return `undefined`
390 * // if the slideshow namespace does not exist return `undefined`
385 * // (will be interpreted as `false` by checkbox) otherwise
391 * // (will be interpreted as `false` by checkbox) otherwise
386 * // return the value
392 * // return the value
387 * return (ns == undefined)? undefined: ns.slide_type
393 * return (ns == undefined)? undefined: ns.slide_type
388 * }
394 * }
389 * CellToolbar.register_callback('slideshow.select', select_type);
395 * CellToolbar.register_callback('slideshow.select', select_type);
390 *
396 *
391 */
397 */
392 CellToolbar.utils.select_ui_generator = function(list_list, setter, getter, label) {
398 CellToolbar.utils.select_ui_generator = function(list_list, setter, getter, label) {
393 label = label || "";
399 label = label || "";
394 return function(div, cell, celltoolbar) {
400 return function(div, cell, celltoolbar) {
395 var button_container = $(div);
401 var button_container = $(div);
396 var lbl = $("<label/>").append($('<span/>').text(label));
402 var lbl = $("<label/>").append($('<span/>').text(label));
397 var select = $('<select/>').addClass('ui-widget ui-widget-content');
403 var select = $('<select/>').addClass('ui-widget ui-widget-content');
398 for(var i=0; i < list_list.length; i++){
404 for(var i=0; i < list_list.length; i++){
399 var opt = $('<option/>')
405 var opt = $('<option/>')
400 .attr('value', list_list[i][1])
406 .attr('value', list_list[i][1])
401 .text(list_list[i][0]);
407 .text(list_list[i][0]);
402 select.append(opt);
408 select.append(opt);
403 }
409 }
404 select.val(getter(cell));
410 select.val(getter(cell));
405 select.change(function(){
411 select.change(function(){
406 setter(cell, select.val());
412 setter(cell, select.val());
407 });
413 });
408 button_container.append($('<div/>').append(lbl).append(select));
414 button_container.append($('<div/>').append(lbl).append(select));
409 };
415 };
410 };
416 };
411
417
412
418
413 IPython.CellToolbar = CellToolbar;
419 IPython.CellToolbar = CellToolbar;
414
420
415 return IPython;
421 return IPython;
416 }(IPython));
422 }(IPython));
@@ -1,214 +1,219 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 "use strict";
14
14
15 var MainToolBar = function (selector) {
15 var MainToolBar = function (selector) {
16 IPython.ToolBar.apply(this, arguments);
16 IPython.ToolBar.apply(this, arguments);
17 this.construct();
17 this.construct();
18 this.add_celltype_list();
18 this.add_celltype_list();
19 this.add_celltoolbar_list();
19 this.add_celltoolbar_list();
20 this.bind_events();
20 this.bind_events();
21 };
21 };
22
22
23 MainToolBar.prototype = new IPython.ToolBar();
23 MainToolBar.prototype = new IPython.ToolBar();
24
24
25 MainToolBar.prototype.construct = function () {
25 MainToolBar.prototype.construct = function () {
26 this.add_buttons_group([
26 this.add_buttons_group([
27 {
27 {
28 id : 'save_b',
28 id : 'save_b',
29 label : 'Save and Checkpoint',
29 label : 'Save and Checkpoint',
30 icon : 'icon-save',
30 icon : 'icon-save',
31 callback : function () {
31 callback : function () {
32 IPython.notebook.save_checkpoint();
32 IPython.notebook.save_checkpoint();
33 }
33 }
34 }
34 }
35 ]);
35 ]);
36
36
37 this.add_buttons_group([
37 this.add_buttons_group([
38 {
38 {
39 id : 'insert_below_b',
39 id : 'insert_below_b',
40 label : 'Insert Cell Below',
40 label : 'Insert Cell Below',
41 icon : 'icon-plus-sign',
41 icon : 'icon-plus-sign',
42 callback : function () {
42 callback : function () {
43 IPython.notebook.insert_cell_below('code');
43 IPython.notebook.insert_cell_below('code');
44 IPython.notebook.select_next();
44 IPython.notebook.select_next();
45 IPython.notebook.focus_cell();
45 IPython.notebook.focus_cell();
46 }
46 }
47 }
47 }
48 ],'insert_above_below');
48 ],'insert_above_below');
49
49
50 this.add_buttons_group([
50 this.add_buttons_group([
51 {
51 {
52 id : 'cut_b',
52 id : 'cut_b',
53 label : 'Cut Cell',
53 label : 'Cut Cell',
54 icon : 'icon-cut',
54 icon : 'icon-cut',
55 callback : function () {
55 callback : function () {
56 IPython.notebook.cut_cell();
56 IPython.notebook.cut_cell();
57 }
57 }
58 },
58 },
59 {
59 {
60 id : 'copy_b',
60 id : 'copy_b',
61 label : 'Copy Cell',
61 label : 'Copy Cell',
62 icon : 'icon-copy',
62 icon : 'icon-copy',
63 callback : function () {
63 callback : function () {
64 IPython.notebook.copy_cell();
64 IPython.notebook.copy_cell();
65 }
65 }
66 },
66 },
67 {
67 {
68 id : 'paste_b',
68 id : 'paste_b',
69 label : 'Paste Cell Below',
69 label : 'Paste Cell Below',
70 icon : 'icon-paste',
70 icon : 'icon-paste',
71 callback : function () {
71 callback : function () {
72 IPython.notebook.paste_cell_below();
72 IPython.notebook.paste_cell_below();
73 }
73 }
74 }
74 }
75 ],'cut_copy_paste');
75 ],'cut_copy_paste');
76
76
77 this.add_buttons_group([
77 this.add_buttons_group([
78 {
78 {
79 id : 'move_up_b',
79 id : 'move_up_b',
80 label : 'Move Cell Up',
80 label : 'Move Cell Up',
81 icon : 'icon-arrow-up',
81 icon : 'icon-arrow-up',
82 callback : function () {
82 callback : function () {
83 IPython.notebook.move_cell_up();
83 IPython.notebook.move_cell_up();
84 }
84 }
85 },
85 },
86 {
86 {
87 id : 'move_down_b',
87 id : 'move_down_b',
88 label : 'Move Cell Down',
88 label : 'Move Cell Down',
89 icon : 'icon-arrow-down',
89 icon : 'icon-arrow-down',
90 callback : function () {
90 callback : function () {
91 IPython.notebook.move_cell_down();
91 IPython.notebook.move_cell_down();
92 }
92 }
93 }
93 }
94 ],'move_up_down');
94 ],'move_up_down');
95
95
96
96
97 this.add_buttons_group([
97 this.add_buttons_group([
98 {
98 {
99 id : 'run_b',
99 id : 'run_b',
100 label : 'Run Cell',
100 label : 'Run Cell',
101 icon : 'icon-play',
101 icon : 'icon-play',
102 callback : function () {
102 callback : function () {
103 // emulate default shift-enter behavior
103 // emulate default shift-enter behavior
104 IPython.notebook.execute_cell_and_select_below();
104 IPython.notebook.execute_cell_and_select_below();
105 }
105 }
106 },
106 },
107 {
107 {
108 id : 'interrupt_b',
108 id : 'interrupt_b',
109 label : 'Interrupt',
109 label : 'Interrupt',
110 icon : 'icon-stop',
110 icon : 'icon-stop',
111 callback : function () {
111 callback : function () {
112 IPython.notebook.session.interrupt_kernel();
112 IPython.notebook.session.interrupt_kernel();
113 }
113 }
114 },
114 },
115 {
115 {
116 id : 'repeat_b',
116 id : 'repeat_b',
117 label : 'Restart Kernel',
117 label : 'Restart Kernel',
118 icon : 'icon-repeat',
118 icon : 'icon-repeat',
119 callback : function () {
119 callback : function () {
120 IPython.notebook.restart_kernel();
120 IPython.notebook.restart_kernel();
121 }
121 }
122 }
122 }
123 ],'run_int');
123 ],'run_int');
124 };
124 };
125
125
126 MainToolBar.prototype.add_celltype_list = function () {
126 MainToolBar.prototype.add_celltype_list = function () {
127 this.element
127 this.element
128 .append($('<select/>')
128 .append($('<select/>')
129 .attr('id','cell_type')
129 .attr('id','cell_type')
130 // .addClass('ui-widget-content')
130 // .addClass('ui-widget-content')
131 .append($('<option/>').attr('value','code').text('Code'))
131 .append($('<option/>').attr('value','code').text('Code'))
132 .append($('<option/>').attr('value','markdown').text('Markdown'))
132 .append($('<option/>').attr('value','markdown').text('Markdown'))
133 .append($('<option/>').attr('value','raw').text('Raw NBConvert'))
133 .append($('<option/>').attr('value','raw').text('Raw NBConvert'))
134 .append($('<option/>').attr('value','heading1').text('Heading 1'))
134 .append($('<option/>').attr('value','heading1').text('Heading 1'))
135 .append($('<option/>').attr('value','heading2').text('Heading 2'))
135 .append($('<option/>').attr('value','heading2').text('Heading 2'))
136 .append($('<option/>').attr('value','heading3').text('Heading 3'))
136 .append($('<option/>').attr('value','heading3').text('Heading 3'))
137 .append($('<option/>').attr('value','heading4').text('Heading 4'))
137 .append($('<option/>').attr('value','heading4').text('Heading 4'))
138 .append($('<option/>').attr('value','heading5').text('Heading 5'))
138 .append($('<option/>').attr('value','heading5').text('Heading 5'))
139 .append($('<option/>').attr('value','heading6').text('Heading 6'))
139 .append($('<option/>').attr('value','heading6').text('Heading 6'))
140 );
140 );
141 };
141 };
142
142
143
143
144 MainToolBar.prototype.add_celltoolbar_list = function () {
144 MainToolBar.prototype.add_celltoolbar_list = function () {
145 var label = $('<span/>').addClass("navbar-text").text('Cell Toolbar:');
145 var label = $('<span/>').addClass("navbar-text").text('Cell Toolbar:');
146 var select = $('<select/>')
146 var select = $('<select/>')
147 // .addClass('ui-widget-content')
147 // .addClass('ui-widget-content')
148 .attr('id', 'ctb_select')
148 .attr('id', 'ctb_select')
149 .append($('<option/>').attr('value', '').text('None'));
149 .append($('<option/>').attr('value', '').text('None'));
150 this.element.append(label).append(select);
150 this.element.append(label).append(select);
151 select.change(function() {
151 select.change(function() {
152 var val = $(this).val()
152 var val = $(this).val()
153 if (val =='') {
153 if (val =='') {
154 IPython.CellToolbar.global_hide();
154 IPython.CellToolbar.global_hide();
155 delete IPython.notebook.metadata.celltoolbar;
155 delete IPython.notebook.metadata.celltoolbar;
156 } else {
156 } else {
157 IPython.CellToolbar.global_show();
157 IPython.CellToolbar.global_show();
158 IPython.CellToolbar.activate_preset(val);
158 IPython.CellToolbar.activate_preset(val);
159 IPython.notebook.metadata.celltoolbar = val;
159 IPython.notebook.metadata.celltoolbar = val;
160 }
160 }
161 });
161 });
162 // Setup the currently registered presets.
162 // Setup the currently registered presets.
163 var presets = IPython.CellToolbar.list_presets();
163 var presets = IPython.CellToolbar.list_presets();
164 for (var i=0; i<presets.length; i++) {
164 for (var i=0; i<presets.length; i++) {
165 var name = presets[i];
165 var name = presets[i];
166 select.append($('<option/>').attr('value', name).text(name));
166 select.append($('<option/>').attr('value', name).text(name));
167 }
167 }
168 // Setup future preset registrations.
168 // Setup future preset registrations.
169 $([IPython.events]).on('preset_added.CellToolbar', function (event, data) {
169 $([IPython.events]).on('preset_added.CellToolbar', function (event, data) {
170 var name = data.name;
170 var name = data.name;
171 select.append($('<option/>').attr('value', name).text(name));
171 select.append($('<option/>').attr('value', name).text(name));
172 });
172 });
173 // Update select value when a preset is activated.
174 $([IPython.events]).on('preset_activated.CellToolbar', function (event, data) {
175 if (select.val() !== data.name)
176 select.val(data.name);
177 });
173 };
178 };
174
179
175
180
176 MainToolBar.prototype.bind_events = function () {
181 MainToolBar.prototype.bind_events = function () {
177 var that = this;
182 var that = this;
178
183
179 this.element.find('#cell_type').change(function () {
184 this.element.find('#cell_type').change(function () {
180 var cell_type = $(this).val();
185 var cell_type = $(this).val();
181 if (cell_type === 'code') {
186 if (cell_type === 'code') {
182 IPython.notebook.to_code();
187 IPython.notebook.to_code();
183 } else if (cell_type === 'markdown') {
188 } else if (cell_type === 'markdown') {
184 IPython.notebook.to_markdown();
189 IPython.notebook.to_markdown();
185 } else if (cell_type === 'raw') {
190 } else if (cell_type === 'raw') {
186 IPython.notebook.to_raw();
191 IPython.notebook.to_raw();
187 } else if (cell_type === 'heading1') {
192 } else if (cell_type === 'heading1') {
188 IPython.notebook.to_heading(undefined, 1);
193 IPython.notebook.to_heading(undefined, 1);
189 } else if (cell_type === 'heading2') {
194 } else if (cell_type === 'heading2') {
190 IPython.notebook.to_heading(undefined, 2);
195 IPython.notebook.to_heading(undefined, 2);
191 } else if (cell_type === 'heading3') {
196 } else if (cell_type === 'heading3') {
192 IPython.notebook.to_heading(undefined, 3);
197 IPython.notebook.to_heading(undefined, 3);
193 } else if (cell_type === 'heading4') {
198 } else if (cell_type === 'heading4') {
194 IPython.notebook.to_heading(undefined, 4);
199 IPython.notebook.to_heading(undefined, 4);
195 } else if (cell_type === 'heading5') {
200 } else if (cell_type === 'heading5') {
196 IPython.notebook.to_heading(undefined, 5);
201 IPython.notebook.to_heading(undefined, 5);
197 } else if (cell_type === 'heading6') {
202 } else if (cell_type === 'heading6') {
198 IPython.notebook.to_heading(undefined, 6);
203 IPython.notebook.to_heading(undefined, 6);
199 }
204 }
200 });
205 });
201 $([IPython.events]).on('selected_cell_type_changed.Notebook', function (event, data) {
206 $([IPython.events]).on('selected_cell_type_changed.Notebook', function (event, data) {
202 if (data.cell_type === 'heading') {
207 if (data.cell_type === 'heading') {
203 that.element.find('#cell_type').val(data.cell_type+data.level);
208 that.element.find('#cell_type').val(data.cell_type+data.level);
204 } else {
209 } else {
205 that.element.find('#cell_type').val(data.cell_type);
210 that.element.find('#cell_type').val(data.cell_type);
206 }
211 }
207 });
212 });
208 };
213 };
209
214
210 IPython.MainToolBar = MainToolBar;
215 IPython.MainToolBar = MainToolBar;
211
216
212 return IPython;
217 return IPython;
213
218
214 }(IPython));
219 }(IPython));
@@ -1,2416 +1,2418 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 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 /**
17 /**
18 * A notebook contains and manages cells.
18 * A notebook contains and manages cells.
19 *
19 *
20 * @class Notebook
20 * @class Notebook
21 * @constructor
21 * @constructor
22 * @param {String} selector A jQuery selector for the notebook's DOM element
22 * @param {String} selector A jQuery selector for the notebook's DOM element
23 * @param {Object} [options] A config object
23 * @param {Object} [options] A config object
24 */
24 */
25 var Notebook = function (selector, options) {
25 var Notebook = function (selector, options) {
26 this.options = options = options || {};
26 this.options = options = options || {};
27 this.base_url = options.base_url;
27 this.base_url = options.base_url;
28 this.notebook_path = options.notebook_path;
28 this.notebook_path = options.notebook_path;
29 this.notebook_name = options.notebook_name;
29 this.notebook_name = options.notebook_name;
30 this.element = $(selector);
30 this.element = $(selector);
31 this.element.scroll();
31 this.element.scroll();
32 this.element.data("notebook", this);
32 this.element.data("notebook", this);
33 this.next_prompt_number = 1;
33 this.next_prompt_number = 1;
34 this.session = null;
34 this.session = null;
35 this.kernel = null;
35 this.kernel = null;
36 this.clipboard = null;
36 this.clipboard = null;
37 this.undelete_backup = null;
37 this.undelete_backup = null;
38 this.undelete_index = null;
38 this.undelete_index = null;
39 this.undelete_below = false;
39 this.undelete_below = false;
40 this.paste_enabled = false;
40 this.paste_enabled = false;
41 // It is important to start out in command mode to match the intial mode
41 // It is important to start out in command mode to match the intial mode
42 // of the KeyboardManager.
42 // of the KeyboardManager.
43 this.mode = 'command';
43 this.mode = 'command';
44 this.set_dirty(false);
44 this.set_dirty(false);
45 this.metadata = {};
45 this.metadata = {};
46 this._checkpoint_after_save = false;
46 this._checkpoint_after_save = false;
47 this.last_checkpoint = null;
47 this.last_checkpoint = null;
48 this.checkpoints = [];
48 this.checkpoints = [];
49 this.autosave_interval = 0;
49 this.autosave_interval = 0;
50 this.autosave_timer = null;
50 this.autosave_timer = null;
51 // autosave *at most* every two minutes
51 // autosave *at most* every two minutes
52 this.minimum_autosave_interval = 120000;
52 this.minimum_autosave_interval = 120000;
53 // single worksheet for now
53 // single worksheet for now
54 this.worksheet_metadata = {};
54 this.worksheet_metadata = {};
55 this.notebook_name_blacklist_re = /[\/\\:]/;
55 this.notebook_name_blacklist_re = /[\/\\:]/;
56 this.nbformat = 3; // Increment this when changing the nbformat
56 this.nbformat = 3; // Increment this when changing the nbformat
57 this.nbformat_minor = 0; // Increment this when changing the nbformat
57 this.nbformat_minor = 0; // Increment this when changing the nbformat
58 this.style();
58 this.style();
59 this.create_elements();
59 this.create_elements();
60 this.bind_events();
60 this.bind_events();
61 this.save_notebook = function() { // don't allow save until notebook_loaded
61 this.save_notebook = function() { // don't allow save until notebook_loaded
62 this.save_notebook_error(null, null, "Load failed, save is disabled");
62 this.save_notebook_error(null, null, "Load failed, save is disabled");
63 };
63 };
64 };
64 };
65
65
66 /**
66 /**
67 * Tweak the notebook's CSS style.
67 * Tweak the notebook's CSS style.
68 *
68 *
69 * @method style
69 * @method style
70 */
70 */
71 Notebook.prototype.style = function () {
71 Notebook.prototype.style = function () {
72 $('div#notebook').addClass('border-box-sizing');
72 $('div#notebook').addClass('border-box-sizing');
73 };
73 };
74
74
75 /**
75 /**
76 * Create an HTML and CSS representation of the notebook.
76 * Create an HTML and CSS representation of the notebook.
77 *
77 *
78 * @method create_elements
78 * @method create_elements
79 */
79 */
80 Notebook.prototype.create_elements = function () {
80 Notebook.prototype.create_elements = function () {
81 var that = this;
81 var that = this;
82 this.element.attr('tabindex','-1');
82 this.element.attr('tabindex','-1');
83 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
83 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
84 // We add this end_space div to the end of the notebook div to:
84 // We add this end_space div to the end of the notebook div to:
85 // i) provide a margin between the last cell and the end of the notebook
85 // i) provide a margin between the last cell and the end of the notebook
86 // ii) to prevent the div from scrolling up when the last cell is being
86 // ii) to prevent the div from scrolling up when the last cell is being
87 // edited, but is too low on the page, which browsers will do automatically.
87 // edited, but is too low on the page, which browsers will do automatically.
88 var end_space = $('<div/>').addClass('end_space');
88 var end_space = $('<div/>').addClass('end_space');
89 end_space.dblclick(function (e) {
89 end_space.dblclick(function (e) {
90 var ncells = that.ncells();
90 var ncells = that.ncells();
91 that.insert_cell_below('code',ncells-1);
91 that.insert_cell_below('code',ncells-1);
92 });
92 });
93 this.element.append(this.container);
93 this.element.append(this.container);
94 this.container.append(end_space);
94 this.container.append(end_space);
95 };
95 };
96
96
97 /**
97 /**
98 * Bind JavaScript events: key presses and custom IPython events.
98 * Bind JavaScript events: key presses and custom IPython events.
99 *
99 *
100 * @method bind_events
100 * @method bind_events
101 */
101 */
102 Notebook.prototype.bind_events = function () {
102 Notebook.prototype.bind_events = function () {
103 var that = this;
103 var that = this;
104
104
105 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
105 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
106 var index = that.find_cell_index(data.cell);
106 var index = that.find_cell_index(data.cell);
107 var new_cell = that.insert_cell_below('code',index);
107 var new_cell = that.insert_cell_below('code',index);
108 new_cell.set_text(data.text);
108 new_cell.set_text(data.text);
109 that.dirty = true;
109 that.dirty = true;
110 });
110 });
111
111
112 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
112 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
113 that.dirty = data.value;
113 that.dirty = data.value;
114 });
114 });
115
115
116 $([IPython.events]).on('trust_changed.Notebook', function (event, data) {
116 $([IPython.events]).on('trust_changed.Notebook', function (event, data) {
117 that.trusted = data.value;
117 that.trusted = data.value;
118 });
118 });
119
119
120 $([IPython.events]).on('select.Cell', function (event, data) {
120 $([IPython.events]).on('select.Cell', function (event, data) {
121 var index = that.find_cell_index(data.cell);
121 var index = that.find_cell_index(data.cell);
122 that.select(index);
122 that.select(index);
123 });
123 });
124
124
125 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
125 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
126 that.handle_edit_mode(data.cell);
126 that.handle_edit_mode(data.cell);
127 });
127 });
128
128
129 $([IPython.events]).on('command_mode.Cell', function (event, data) {
129 $([IPython.events]).on('command_mode.Cell', function (event, data) {
130 that.handle_command_mode(data.cell);
130 that.handle_command_mode(data.cell);
131 });
131 });
132
132
133 $([IPython.events]).on('status_autorestarting.Kernel', function () {
133 $([IPython.events]).on('status_autorestarting.Kernel', function () {
134 IPython.dialog.modal({
134 IPython.dialog.modal({
135 title: "Kernel Restarting",
135 title: "Kernel Restarting",
136 body: "The kernel appears to have died. It will restart automatically.",
136 body: "The kernel appears to have died. It will restart automatically.",
137 buttons: {
137 buttons: {
138 OK : {
138 OK : {
139 class : "btn-primary"
139 class : "btn-primary"
140 }
140 }
141 }
141 }
142 });
142 });
143 });
143 });
144
144
145 var collapse_time = function (time) {
145 var collapse_time = function (time) {
146 var app_height = $('#ipython-main-app').height(); // content height
146 var app_height = $('#ipython-main-app').height(); // content height
147 var splitter_height = $('div#pager_splitter').outerHeight(true);
147 var splitter_height = $('div#pager_splitter').outerHeight(true);
148 var new_height = app_height - splitter_height;
148 var new_height = app_height - splitter_height;
149 that.element.animate({height : new_height + 'px'}, time);
149 that.element.animate({height : new_height + 'px'}, time);
150 };
150 };
151
151
152 this.element.bind('collapse_pager', function (event, extrap) {
152 this.element.bind('collapse_pager', function (event, extrap) {
153 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
153 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
154 collapse_time(time);
154 collapse_time(time);
155 });
155 });
156
156
157 var expand_time = function (time) {
157 var expand_time = function (time) {
158 var app_height = $('#ipython-main-app').height(); // content height
158 var app_height = $('#ipython-main-app').height(); // content height
159 var splitter_height = $('div#pager_splitter').outerHeight(true);
159 var splitter_height = $('div#pager_splitter').outerHeight(true);
160 var pager_height = $('div#pager').outerHeight(true);
160 var pager_height = $('div#pager').outerHeight(true);
161 var new_height = app_height - pager_height - splitter_height;
161 var new_height = app_height - pager_height - splitter_height;
162 that.element.animate({height : new_height + 'px'}, time);
162 that.element.animate({height : new_height + 'px'}, time);
163 };
163 };
164
164
165 this.element.bind('expand_pager', function (event, extrap) {
165 this.element.bind('expand_pager', function (event, extrap) {
166 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
166 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
167 expand_time(time);
167 expand_time(time);
168 });
168 });
169
169
170 // Firefox 22 broke $(window).on("beforeunload")
170 // Firefox 22 broke $(window).on("beforeunload")
171 // I'm not sure why or how.
171 // I'm not sure why or how.
172 window.onbeforeunload = function (e) {
172 window.onbeforeunload = function (e) {
173 // TODO: Make killing the kernel configurable.
173 // TODO: Make killing the kernel configurable.
174 var kill_kernel = false;
174 var kill_kernel = false;
175 if (kill_kernel) {
175 if (kill_kernel) {
176 that.session.kill_kernel();
176 that.session.kill_kernel();
177 }
177 }
178 // if we are autosaving, trigger an autosave on nav-away.
178 // if we are autosaving, trigger an autosave on nav-away.
179 // still warn, because if we don't the autosave may fail.
179 // still warn, because if we don't the autosave may fail.
180 if (that.dirty) {
180 if (that.dirty) {
181 if ( that.autosave_interval ) {
181 if ( that.autosave_interval ) {
182 // schedule autosave in a timeout
182 // schedule autosave in a timeout
183 // this gives you a chance to forcefully discard changes
183 // this gives you a chance to forcefully discard changes
184 // by reloading the page if you *really* want to.
184 // by reloading the page if you *really* want to.
185 // the timer doesn't start until you *dismiss* the dialog.
185 // the timer doesn't start until you *dismiss* the dialog.
186 setTimeout(function () {
186 setTimeout(function () {
187 if (that.dirty) {
187 if (that.dirty) {
188 that.save_notebook();
188 that.save_notebook();
189 }
189 }
190 }, 1000);
190 }, 1000);
191 return "Autosave in progress, latest changes may be lost.";
191 return "Autosave in progress, latest changes may be lost.";
192 } else {
192 } else {
193 return "Unsaved changes will be lost.";
193 return "Unsaved changes will be lost.";
194 }
194 }
195 }
195 }
196 // Null is the *only* return value that will make the browser not
196 // Null is the *only* return value that will make the browser not
197 // pop up the "don't leave" dialog.
197 // pop up the "don't leave" dialog.
198 return null;
198 return null;
199 };
199 };
200 };
200 };
201
201
202 /**
202 /**
203 * Set the dirty flag, and trigger the set_dirty.Notebook event
203 * Set the dirty flag, and trigger the set_dirty.Notebook event
204 *
204 *
205 * @method set_dirty
205 * @method set_dirty
206 */
206 */
207 Notebook.prototype.set_dirty = function (value) {
207 Notebook.prototype.set_dirty = function (value) {
208 if (value === undefined) {
208 if (value === undefined) {
209 value = true;
209 value = true;
210 }
210 }
211 if (this.dirty == value) {
211 if (this.dirty == value) {
212 return;
212 return;
213 }
213 }
214 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
214 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
215 };
215 };
216
216
217 /**
217 /**
218 * Scroll the top of the page to a given cell.
218 * Scroll the top of the page to a given cell.
219 *
219 *
220 * @method scroll_to_cell
220 * @method scroll_to_cell
221 * @param {Number} cell_number An index of the cell to view
221 * @param {Number} cell_number An index of the cell to view
222 * @param {Number} time Animation time in milliseconds
222 * @param {Number} time Animation time in milliseconds
223 * @return {Number} Pixel offset from the top of the container
223 * @return {Number} Pixel offset from the top of the container
224 */
224 */
225 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
225 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
226 var cells = this.get_cells();
226 var cells = this.get_cells();
227 time = time || 0;
227 time = time || 0;
228 cell_number = Math.min(cells.length-1,cell_number);
228 cell_number = Math.min(cells.length-1,cell_number);
229 cell_number = Math.max(0 ,cell_number);
229 cell_number = Math.max(0 ,cell_number);
230 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
230 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
231 this.element.animate({scrollTop:scroll_value}, time);
231 this.element.animate({scrollTop:scroll_value}, time);
232 return scroll_value;
232 return scroll_value;
233 };
233 };
234
234
235 /**
235 /**
236 * Scroll to the bottom of the page.
236 * Scroll to the bottom of the page.
237 *
237 *
238 * @method scroll_to_bottom
238 * @method scroll_to_bottom
239 */
239 */
240 Notebook.prototype.scroll_to_bottom = function () {
240 Notebook.prototype.scroll_to_bottom = function () {
241 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
241 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
242 };
242 };
243
243
244 /**
244 /**
245 * Scroll to the top of the page.
245 * Scroll to the top of the page.
246 *
246 *
247 * @method scroll_to_top
247 * @method scroll_to_top
248 */
248 */
249 Notebook.prototype.scroll_to_top = function () {
249 Notebook.prototype.scroll_to_top = function () {
250 this.element.animate({scrollTop:0}, 0);
250 this.element.animate({scrollTop:0}, 0);
251 };
251 };
252
252
253 // Edit Notebook metadata
253 // Edit Notebook metadata
254
254
255 Notebook.prototype.edit_metadata = function () {
255 Notebook.prototype.edit_metadata = function () {
256 var that = this;
256 var that = this;
257 IPython.dialog.edit_metadata(this.metadata, function (md) {
257 IPython.dialog.edit_metadata(this.metadata, function (md) {
258 that.metadata = md;
258 that.metadata = md;
259 }, 'Notebook');
259 }, 'Notebook');
260 };
260 };
261
261
262 // Cell indexing, retrieval, etc.
262 // Cell indexing, retrieval, etc.
263
263
264 /**
264 /**
265 * Get all cell elements in the notebook.
265 * Get all cell elements in the notebook.
266 *
266 *
267 * @method get_cell_elements
267 * @method get_cell_elements
268 * @return {jQuery} A selector of all cell elements
268 * @return {jQuery} A selector of all cell elements
269 */
269 */
270 Notebook.prototype.get_cell_elements = function () {
270 Notebook.prototype.get_cell_elements = function () {
271 return this.container.children("div.cell");
271 return this.container.children("div.cell");
272 };
272 };
273
273
274 /**
274 /**
275 * Get a particular cell element.
275 * Get a particular cell element.
276 *
276 *
277 * @method get_cell_element
277 * @method get_cell_element
278 * @param {Number} index An index of a cell to select
278 * @param {Number} index An index of a cell to select
279 * @return {jQuery} A selector of the given cell.
279 * @return {jQuery} A selector of the given cell.
280 */
280 */
281 Notebook.prototype.get_cell_element = function (index) {
281 Notebook.prototype.get_cell_element = function (index) {
282 var result = null;
282 var result = null;
283 var e = this.get_cell_elements().eq(index);
283 var e = this.get_cell_elements().eq(index);
284 if (e.length !== 0) {
284 if (e.length !== 0) {
285 result = e;
285 result = e;
286 }
286 }
287 return result;
287 return result;
288 };
288 };
289
289
290 /**
290 /**
291 * Try to get a particular cell by msg_id.
291 * Try to get a particular cell by msg_id.
292 *
292 *
293 * @method get_msg_cell
293 * @method get_msg_cell
294 * @param {String} msg_id A message UUID
294 * @param {String} msg_id A message UUID
295 * @return {Cell} Cell or null if no cell was found.
295 * @return {Cell} Cell or null if no cell was found.
296 */
296 */
297 Notebook.prototype.get_msg_cell = function (msg_id) {
297 Notebook.prototype.get_msg_cell = function (msg_id) {
298 return IPython.CodeCell.msg_cells[msg_id] || null;
298 return IPython.CodeCell.msg_cells[msg_id] || null;
299 };
299 };
300
300
301 /**
301 /**
302 * Count the cells in this notebook.
302 * Count the cells in this notebook.
303 *
303 *
304 * @method ncells
304 * @method ncells
305 * @return {Number} The number of cells in this notebook
305 * @return {Number} The number of cells in this notebook
306 */
306 */
307 Notebook.prototype.ncells = function () {
307 Notebook.prototype.ncells = function () {
308 return this.get_cell_elements().length;
308 return this.get_cell_elements().length;
309 };
309 };
310
310
311 /**
311 /**
312 * Get all Cell objects in this notebook.
312 * Get all Cell objects in this notebook.
313 *
313 *
314 * @method get_cells
314 * @method get_cells
315 * @return {Array} This notebook's Cell objects
315 * @return {Array} This notebook's Cell objects
316 */
316 */
317 // TODO: we are often calling cells as cells()[i], which we should optimize
317 // TODO: we are often calling cells as cells()[i], which we should optimize
318 // to cells(i) or a new method.
318 // to cells(i) or a new method.
319 Notebook.prototype.get_cells = function () {
319 Notebook.prototype.get_cells = function () {
320 return this.get_cell_elements().toArray().map(function (e) {
320 return this.get_cell_elements().toArray().map(function (e) {
321 return $(e).data("cell");
321 return $(e).data("cell");
322 });
322 });
323 };
323 };
324
324
325 /**
325 /**
326 * Get a Cell object from this notebook.
326 * Get a Cell object from this notebook.
327 *
327 *
328 * @method get_cell
328 * @method get_cell
329 * @param {Number} index An index of a cell to retrieve
329 * @param {Number} index An index of a cell to retrieve
330 * @return {Cell} A particular cell
330 * @return {Cell} A particular cell
331 */
331 */
332 Notebook.prototype.get_cell = function (index) {
332 Notebook.prototype.get_cell = function (index) {
333 var result = null;
333 var result = null;
334 var ce = this.get_cell_element(index);
334 var ce = this.get_cell_element(index);
335 if (ce !== null) {
335 if (ce !== null) {
336 result = ce.data('cell');
336 result = ce.data('cell');
337 }
337 }
338 return result;
338 return result;
339 };
339 };
340
340
341 /**
341 /**
342 * Get the cell below a given cell.
342 * Get the cell below a given cell.
343 *
343 *
344 * @method get_next_cell
344 * @method get_next_cell
345 * @param {Cell} cell The provided cell
345 * @param {Cell} cell The provided cell
346 * @return {Cell} The next cell
346 * @return {Cell} The next cell
347 */
347 */
348 Notebook.prototype.get_next_cell = function (cell) {
348 Notebook.prototype.get_next_cell = function (cell) {
349 var result = null;
349 var result = null;
350 var index = this.find_cell_index(cell);
350 var index = this.find_cell_index(cell);
351 if (this.is_valid_cell_index(index+1)) {
351 if (this.is_valid_cell_index(index+1)) {
352 result = this.get_cell(index+1);
352 result = this.get_cell(index+1);
353 }
353 }
354 return result;
354 return result;
355 };
355 };
356
356
357 /**
357 /**
358 * Get the cell above a given cell.
358 * Get the cell above a given cell.
359 *
359 *
360 * @method get_prev_cell
360 * @method get_prev_cell
361 * @param {Cell} cell The provided cell
361 * @param {Cell} cell The provided cell
362 * @return {Cell} The previous cell
362 * @return {Cell} The previous cell
363 */
363 */
364 Notebook.prototype.get_prev_cell = function (cell) {
364 Notebook.prototype.get_prev_cell = function (cell) {
365 // TODO: off-by-one
365 // TODO: off-by-one
366 // nb.get_prev_cell(nb.get_cell(1)) is null
366 // nb.get_prev_cell(nb.get_cell(1)) is null
367 var result = null;
367 var result = null;
368 var index = this.find_cell_index(cell);
368 var index = this.find_cell_index(cell);
369 if (index !== null && index > 1) {
369 if (index !== null && index > 1) {
370 result = this.get_cell(index-1);
370 result = this.get_cell(index-1);
371 }
371 }
372 return result;
372 return result;
373 };
373 };
374
374
375 /**
375 /**
376 * Get the numeric index of a given cell.
376 * Get the numeric index of a given cell.
377 *
377 *
378 * @method find_cell_index
378 * @method find_cell_index
379 * @param {Cell} cell The provided cell
379 * @param {Cell} cell The provided cell
380 * @return {Number} The cell's numeric index
380 * @return {Number} The cell's numeric index
381 */
381 */
382 Notebook.prototype.find_cell_index = function (cell) {
382 Notebook.prototype.find_cell_index = function (cell) {
383 var result = null;
383 var result = null;
384 this.get_cell_elements().filter(function (index) {
384 this.get_cell_elements().filter(function (index) {
385 if ($(this).data("cell") === cell) {
385 if ($(this).data("cell") === cell) {
386 result = index;
386 result = index;
387 }
387 }
388 });
388 });
389 return result;
389 return result;
390 };
390 };
391
391
392 /**
392 /**
393 * Get a given index , or the selected index if none is provided.
393 * Get a given index , or the selected index if none is provided.
394 *
394 *
395 * @method index_or_selected
395 * @method index_or_selected
396 * @param {Number} index A cell's index
396 * @param {Number} index A cell's index
397 * @return {Number} The given index, or selected index if none is provided.
397 * @return {Number} The given index, or selected index if none is provided.
398 */
398 */
399 Notebook.prototype.index_or_selected = function (index) {
399 Notebook.prototype.index_or_selected = function (index) {
400 var i;
400 var i;
401 if (index === undefined || index === null) {
401 if (index === undefined || index === null) {
402 i = this.get_selected_index();
402 i = this.get_selected_index();
403 if (i === null) {
403 if (i === null) {
404 i = 0;
404 i = 0;
405 }
405 }
406 } else {
406 } else {
407 i = index;
407 i = index;
408 }
408 }
409 return i;
409 return i;
410 };
410 };
411
411
412 /**
412 /**
413 * Get the currently selected cell.
413 * Get the currently selected cell.
414 * @method get_selected_cell
414 * @method get_selected_cell
415 * @return {Cell} The selected cell
415 * @return {Cell} The selected cell
416 */
416 */
417 Notebook.prototype.get_selected_cell = function () {
417 Notebook.prototype.get_selected_cell = function () {
418 var index = this.get_selected_index();
418 var index = this.get_selected_index();
419 return this.get_cell(index);
419 return this.get_cell(index);
420 };
420 };
421
421
422 /**
422 /**
423 * Check whether a cell index is valid.
423 * Check whether a cell index is valid.
424 *
424 *
425 * @method is_valid_cell_index
425 * @method is_valid_cell_index
426 * @param {Number} index A cell index
426 * @param {Number} index A cell index
427 * @return True if the index is valid, false otherwise
427 * @return True if the index is valid, false otherwise
428 */
428 */
429 Notebook.prototype.is_valid_cell_index = function (index) {
429 Notebook.prototype.is_valid_cell_index = function (index) {
430 if (index !== null && index >= 0 && index < this.ncells()) {
430 if (index !== null && index >= 0 && index < this.ncells()) {
431 return true;
431 return true;
432 } else {
432 } else {
433 return false;
433 return false;
434 }
434 }
435 };
435 };
436
436
437 /**
437 /**
438 * Get the index of the currently selected cell.
438 * Get the index of the currently selected cell.
439
439
440 * @method get_selected_index
440 * @method get_selected_index
441 * @return {Number} The selected cell's numeric index
441 * @return {Number} The selected cell's numeric index
442 */
442 */
443 Notebook.prototype.get_selected_index = function () {
443 Notebook.prototype.get_selected_index = function () {
444 var result = null;
444 var result = null;
445 this.get_cell_elements().filter(function (index) {
445 this.get_cell_elements().filter(function (index) {
446 if ($(this).data("cell").selected === true) {
446 if ($(this).data("cell").selected === true) {
447 result = index;
447 result = index;
448 }
448 }
449 });
449 });
450 return result;
450 return result;
451 };
451 };
452
452
453
453
454 // Cell selection.
454 // Cell selection.
455
455
456 /**
456 /**
457 * Programmatically select a cell.
457 * Programmatically select a cell.
458 *
458 *
459 * @method select
459 * @method select
460 * @param {Number} index A cell's index
460 * @param {Number} index A cell's index
461 * @return {Notebook} This notebook
461 * @return {Notebook} This notebook
462 */
462 */
463 Notebook.prototype.select = function (index) {
463 Notebook.prototype.select = function (index) {
464 if (this.is_valid_cell_index(index)) {
464 if (this.is_valid_cell_index(index)) {
465 var sindex = this.get_selected_index();
465 var sindex = this.get_selected_index();
466 if (sindex !== null && index !== sindex) {
466 if (sindex !== null && index !== sindex) {
467 // If we are about to select a different cell, make sure we are
467 // If we are about to select a different cell, make sure we are
468 // first in command mode.
468 // first in command mode.
469 if (this.mode !== 'command') {
469 if (this.mode !== 'command') {
470 this.command_mode();
470 this.command_mode();
471 }
471 }
472 this.get_cell(sindex).unselect();
472 this.get_cell(sindex).unselect();
473 }
473 }
474 var cell = this.get_cell(index);
474 var cell = this.get_cell(index);
475 cell.select();
475 cell.select();
476 if (cell.cell_type === 'heading') {
476 if (cell.cell_type === 'heading') {
477 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
477 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
478 {'cell_type':cell.cell_type,level:cell.level}
478 {'cell_type':cell.cell_type,level:cell.level}
479 );
479 );
480 } else {
480 } else {
481 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
481 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
482 {'cell_type':cell.cell_type}
482 {'cell_type':cell.cell_type}
483 );
483 );
484 }
484 }
485 }
485 }
486 return this;
486 return this;
487 };
487 };
488
488
489 /**
489 /**
490 * Programmatically select the next cell.
490 * Programmatically select the next cell.
491 *
491 *
492 * @method select_next
492 * @method select_next
493 * @return {Notebook} This notebook
493 * @return {Notebook} This notebook
494 */
494 */
495 Notebook.prototype.select_next = function () {
495 Notebook.prototype.select_next = function () {
496 var index = this.get_selected_index();
496 var index = this.get_selected_index();
497 this.select(index+1);
497 this.select(index+1);
498 return this;
498 return this;
499 };
499 };
500
500
501 /**
501 /**
502 * Programmatically select the previous cell.
502 * Programmatically select the previous cell.
503 *
503 *
504 * @method select_prev
504 * @method select_prev
505 * @return {Notebook} This notebook
505 * @return {Notebook} This notebook
506 */
506 */
507 Notebook.prototype.select_prev = function () {
507 Notebook.prototype.select_prev = function () {
508 var index = this.get_selected_index();
508 var index = this.get_selected_index();
509 this.select(index-1);
509 this.select(index-1);
510 return this;
510 return this;
511 };
511 };
512
512
513
513
514 // Edit/Command mode
514 // Edit/Command mode
515
515
516 /**
516 /**
517 * Gets the index of the cell that is in edit mode.
517 * Gets the index of the cell that is in edit mode.
518 *
518 *
519 * @method get_edit_index
519 * @method get_edit_index
520 *
520 *
521 * @return index {int}
521 * @return index {int}
522 **/
522 **/
523 Notebook.prototype.get_edit_index = function () {
523 Notebook.prototype.get_edit_index = function () {
524 var result = null;
524 var result = null;
525 this.get_cell_elements().filter(function (index) {
525 this.get_cell_elements().filter(function (index) {
526 if ($(this).data("cell").mode === 'edit') {
526 if ($(this).data("cell").mode === 'edit') {
527 result = index;
527 result = index;
528 }
528 }
529 });
529 });
530 return result;
530 return result;
531 };
531 };
532
532
533 /**
533 /**
534 * Handle when a a cell blurs and the notebook should enter command mode.
534 * Handle when a a cell blurs and the notebook should enter command mode.
535 *
535 *
536 * @method handle_command_mode
536 * @method handle_command_mode
537 * @param [cell] {Cell} Cell to enter command mode on.
537 * @param [cell] {Cell} Cell to enter command mode on.
538 **/
538 **/
539 Notebook.prototype.handle_command_mode = function (cell) {
539 Notebook.prototype.handle_command_mode = function (cell) {
540 if (this.mode !== 'command') {
540 if (this.mode !== 'command') {
541 cell.command_mode();
541 cell.command_mode();
542 this.mode = 'command';
542 this.mode = 'command';
543 $([IPython.events]).trigger('command_mode.Notebook');
543 $([IPython.events]).trigger('command_mode.Notebook');
544 IPython.keyboard_manager.command_mode();
544 IPython.keyboard_manager.command_mode();
545 }
545 }
546 };
546 };
547
547
548 /**
548 /**
549 * Make the notebook enter command mode.
549 * Make the notebook enter command mode.
550 *
550 *
551 * @method command_mode
551 * @method command_mode
552 **/
552 **/
553 Notebook.prototype.command_mode = function () {
553 Notebook.prototype.command_mode = function () {
554 var cell = this.get_cell(this.get_edit_index());
554 var cell = this.get_cell(this.get_edit_index());
555 if (cell && this.mode !== 'command') {
555 if (cell && this.mode !== 'command') {
556 // We don't call cell.command_mode, but rather call cell.focus_cell()
556 // We don't call cell.command_mode, but rather call cell.focus_cell()
557 // which will blur and CM editor and trigger the call to
557 // which will blur and CM editor and trigger the call to
558 // handle_command_mode.
558 // handle_command_mode.
559 cell.focus_cell();
559 cell.focus_cell();
560 }
560 }
561 };
561 };
562
562
563 /**
563 /**
564 * Handle when a cell fires it's edit_mode event.
564 * Handle when a cell fires it's edit_mode event.
565 *
565 *
566 * @method handle_edit_mode
566 * @method handle_edit_mode
567 * @param [cell] {Cell} Cell to enter edit mode on.
567 * @param [cell] {Cell} Cell to enter edit mode on.
568 **/
568 **/
569 Notebook.prototype.handle_edit_mode = function (cell) {
569 Notebook.prototype.handle_edit_mode = function (cell) {
570 if (cell && this.mode !== 'edit') {
570 if (cell && this.mode !== 'edit') {
571 cell.edit_mode();
571 cell.edit_mode();
572 this.mode = 'edit';
572 this.mode = 'edit';
573 $([IPython.events]).trigger('edit_mode.Notebook');
573 $([IPython.events]).trigger('edit_mode.Notebook');
574 IPython.keyboard_manager.edit_mode();
574 IPython.keyboard_manager.edit_mode();
575 }
575 }
576 };
576 };
577
577
578 /**
578 /**
579 * Make a cell enter edit mode.
579 * Make a cell enter edit mode.
580 *
580 *
581 * @method edit_mode
581 * @method edit_mode
582 **/
582 **/
583 Notebook.prototype.edit_mode = function () {
583 Notebook.prototype.edit_mode = function () {
584 var cell = this.get_selected_cell();
584 var cell = this.get_selected_cell();
585 if (cell && this.mode !== 'edit') {
585 if (cell && this.mode !== 'edit') {
586 cell.unrender();
586 cell.unrender();
587 cell.focus_editor();
587 cell.focus_editor();
588 }
588 }
589 };
589 };
590
590
591 /**
591 /**
592 * Focus the currently selected cell.
592 * Focus the currently selected cell.
593 *
593 *
594 * @method focus_cell
594 * @method focus_cell
595 **/
595 **/
596 Notebook.prototype.focus_cell = function () {
596 Notebook.prototype.focus_cell = function () {
597 var cell = this.get_selected_cell();
597 var cell = this.get_selected_cell();
598 if (cell === null) {return;} // No cell is selected
598 if (cell === null) {return;} // No cell is selected
599 cell.focus_cell();
599 cell.focus_cell();
600 };
600 };
601
601
602 // Cell movement
602 // Cell movement
603
603
604 /**
604 /**
605 * Move given (or selected) cell up and select it.
605 * Move given (or selected) cell up and select it.
606 *
606 *
607 * @method move_cell_up
607 * @method move_cell_up
608 * @param [index] {integer} cell index
608 * @param [index] {integer} cell index
609 * @return {Notebook} This notebook
609 * @return {Notebook} This notebook
610 **/
610 **/
611 Notebook.prototype.move_cell_up = function (index) {
611 Notebook.prototype.move_cell_up = function (index) {
612 var i = this.index_or_selected(index);
612 var i = this.index_or_selected(index);
613 if (this.is_valid_cell_index(i) && i > 0) {
613 if (this.is_valid_cell_index(i) && i > 0) {
614 var pivot = this.get_cell_element(i-1);
614 var pivot = this.get_cell_element(i-1);
615 var tomove = this.get_cell_element(i);
615 var tomove = this.get_cell_element(i);
616 if (pivot !== null && tomove !== null) {
616 if (pivot !== null && tomove !== null) {
617 tomove.detach();
617 tomove.detach();
618 pivot.before(tomove);
618 pivot.before(tomove);
619 this.select(i-1);
619 this.select(i-1);
620 var cell = this.get_selected_cell();
620 var cell = this.get_selected_cell();
621 cell.focus_cell();
621 cell.focus_cell();
622 }
622 }
623 this.set_dirty(true);
623 this.set_dirty(true);
624 }
624 }
625 return this;
625 return this;
626 };
626 };
627
627
628
628
629 /**
629 /**
630 * Move given (or selected) cell down and select it
630 * Move given (or selected) cell down and select it
631 *
631 *
632 * @method move_cell_down
632 * @method move_cell_down
633 * @param [index] {integer} cell index
633 * @param [index] {integer} cell index
634 * @return {Notebook} This notebook
634 * @return {Notebook} This notebook
635 **/
635 **/
636 Notebook.prototype.move_cell_down = function (index) {
636 Notebook.prototype.move_cell_down = function (index) {
637 var i = this.index_or_selected(index);
637 var i = this.index_or_selected(index);
638 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
638 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
639 var pivot = this.get_cell_element(i+1);
639 var pivot = this.get_cell_element(i+1);
640 var tomove = this.get_cell_element(i);
640 var tomove = this.get_cell_element(i);
641 if (pivot !== null && tomove !== null) {
641 if (pivot !== null && tomove !== null) {
642 tomove.detach();
642 tomove.detach();
643 pivot.after(tomove);
643 pivot.after(tomove);
644 this.select(i+1);
644 this.select(i+1);
645 var cell = this.get_selected_cell();
645 var cell = this.get_selected_cell();
646 cell.focus_cell();
646 cell.focus_cell();
647 }
647 }
648 }
648 }
649 this.set_dirty();
649 this.set_dirty();
650 return this;
650 return this;
651 };
651 };
652
652
653
653
654 // Insertion, deletion.
654 // Insertion, deletion.
655
655
656 /**
656 /**
657 * Delete a cell from the notebook.
657 * Delete a cell from the notebook.
658 *
658 *
659 * @method delete_cell
659 * @method delete_cell
660 * @param [index] A cell's numeric index
660 * @param [index] A cell's numeric index
661 * @return {Notebook} This notebook
661 * @return {Notebook} This notebook
662 */
662 */
663 Notebook.prototype.delete_cell = function (index) {
663 Notebook.prototype.delete_cell = function (index) {
664 var i = this.index_or_selected(index);
664 var i = this.index_or_selected(index);
665 var cell = this.get_selected_cell();
665 var cell = this.get_selected_cell();
666 this.undelete_backup = cell.toJSON();
666 this.undelete_backup = cell.toJSON();
667 $('#undelete_cell').removeClass('disabled');
667 $('#undelete_cell').removeClass('disabled');
668 if (this.is_valid_cell_index(i)) {
668 if (this.is_valid_cell_index(i)) {
669 var old_ncells = this.ncells();
669 var old_ncells = this.ncells();
670 var ce = this.get_cell_element(i);
670 var ce = this.get_cell_element(i);
671 ce.remove();
671 ce.remove();
672 if (i === 0) {
672 if (i === 0) {
673 // Always make sure we have at least one cell.
673 // Always make sure we have at least one cell.
674 if (old_ncells === 1) {
674 if (old_ncells === 1) {
675 this.insert_cell_below('code');
675 this.insert_cell_below('code');
676 }
676 }
677 this.select(0);
677 this.select(0);
678 this.undelete_index = 0;
678 this.undelete_index = 0;
679 this.undelete_below = false;
679 this.undelete_below = false;
680 } else if (i === old_ncells-1 && i !== 0) {
680 } else if (i === old_ncells-1 && i !== 0) {
681 this.select(i-1);
681 this.select(i-1);
682 this.undelete_index = i - 1;
682 this.undelete_index = i - 1;
683 this.undelete_below = true;
683 this.undelete_below = true;
684 } else {
684 } else {
685 this.select(i);
685 this.select(i);
686 this.undelete_index = i;
686 this.undelete_index = i;
687 this.undelete_below = false;
687 this.undelete_below = false;
688 }
688 }
689 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
689 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
690 this.set_dirty(true);
690 this.set_dirty(true);
691 }
691 }
692 return this;
692 return this;
693 };
693 };
694
694
695 /**
695 /**
696 * Restore the most recently deleted cell.
696 * Restore the most recently deleted cell.
697 *
697 *
698 * @method undelete
698 * @method undelete
699 */
699 */
700 Notebook.prototype.undelete_cell = function() {
700 Notebook.prototype.undelete_cell = function() {
701 if (this.undelete_backup !== null && this.undelete_index !== null) {
701 if (this.undelete_backup !== null && this.undelete_index !== null) {
702 var current_index = this.get_selected_index();
702 var current_index = this.get_selected_index();
703 if (this.undelete_index < current_index) {
703 if (this.undelete_index < current_index) {
704 current_index = current_index + 1;
704 current_index = current_index + 1;
705 }
705 }
706 if (this.undelete_index >= this.ncells()) {
706 if (this.undelete_index >= this.ncells()) {
707 this.select(this.ncells() - 1);
707 this.select(this.ncells() - 1);
708 }
708 }
709 else {
709 else {
710 this.select(this.undelete_index);
710 this.select(this.undelete_index);
711 }
711 }
712 var cell_data = this.undelete_backup;
712 var cell_data = this.undelete_backup;
713 var new_cell = null;
713 var new_cell = null;
714 if (this.undelete_below) {
714 if (this.undelete_below) {
715 new_cell = this.insert_cell_below(cell_data.cell_type);
715 new_cell = this.insert_cell_below(cell_data.cell_type);
716 } else {
716 } else {
717 new_cell = this.insert_cell_above(cell_data.cell_type);
717 new_cell = this.insert_cell_above(cell_data.cell_type);
718 }
718 }
719 new_cell.fromJSON(cell_data);
719 new_cell.fromJSON(cell_data);
720 if (this.undelete_below) {
720 if (this.undelete_below) {
721 this.select(current_index+1);
721 this.select(current_index+1);
722 } else {
722 } else {
723 this.select(current_index);
723 this.select(current_index);
724 }
724 }
725 this.undelete_backup = null;
725 this.undelete_backup = null;
726 this.undelete_index = null;
726 this.undelete_index = null;
727 }
727 }
728 $('#undelete_cell').addClass('disabled');
728 $('#undelete_cell').addClass('disabled');
729 };
729 };
730
730
731 /**
731 /**
732 * Insert a cell so that after insertion the cell is at given index.
732 * Insert a cell so that after insertion the cell is at given index.
733 *
733 *
734 * Similar to insert_above, but index parameter is mandatory
734 * Similar to insert_above, but index parameter is mandatory
735 *
735 *
736 * Index will be brought back into the accissible range [0,n]
736 * Index will be brought back into the accissible range [0,n]
737 *
737 *
738 * @method insert_cell_at_index
738 * @method insert_cell_at_index
739 * @param type {string} in ['code','markdown','heading']
739 * @param type {string} in ['code','markdown','heading']
740 * @param [index] {int} a valid index where to inser cell
740 * @param [index] {int} a valid index where to inser cell
741 *
741 *
742 * @return cell {cell|null} created cell or null
742 * @return cell {cell|null} created cell or null
743 **/
743 **/
744 Notebook.prototype.insert_cell_at_index = function(type, index){
744 Notebook.prototype.insert_cell_at_index = function(type, index){
745
745
746 var ncells = this.ncells();
746 var ncells = this.ncells();
747 index = Math.min(index,ncells);
747 index = Math.min(index,ncells);
748 index = Math.max(index,0);
748 index = Math.max(index,0);
749 var cell = null;
749 var cell = null;
750
750
751 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
751 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
752 if (type === 'code') {
752 if (type === 'code') {
753 cell = new IPython.CodeCell(this.kernel);
753 cell = new IPython.CodeCell(this.kernel);
754 cell.set_input_prompt();
754 cell.set_input_prompt();
755 } else if (type === 'markdown') {
755 } else if (type === 'markdown') {
756 cell = new IPython.MarkdownCell();
756 cell = new IPython.MarkdownCell();
757 } else if (type === 'raw') {
757 } else if (type === 'raw') {
758 cell = new IPython.RawCell();
758 cell = new IPython.RawCell();
759 } else if (type === 'heading') {
759 } else if (type === 'heading') {
760 cell = new IPython.HeadingCell();
760 cell = new IPython.HeadingCell();
761 }
761 }
762
762
763 if(this._insert_element_at_index(cell.element,index)) {
763 if(this._insert_element_at_index(cell.element,index)) {
764 cell.render();
764 cell.render();
765 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
765 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
766 cell.refresh();
766 cell.refresh();
767 // We used to select the cell after we refresh it, but there
767 // We used to select the cell after we refresh it, but there
768 // are now cases were this method is called where select is
768 // are now cases were this method is called where select is
769 // not appropriate. The selection logic should be handled by the
769 // not appropriate. The selection logic should be handled by the
770 // caller of the the top level insert_cell methods.
770 // caller of the the top level insert_cell methods.
771 this.set_dirty(true);
771 this.set_dirty(true);
772 }
772 }
773 }
773 }
774 return cell;
774 return cell;
775
775
776 };
776 };
777
777
778 /**
778 /**
779 * Insert an element at given cell index.
779 * Insert an element at given cell index.
780 *
780 *
781 * @method _insert_element_at_index
781 * @method _insert_element_at_index
782 * @param element {dom element} a cell element
782 * @param element {dom element} a cell element
783 * @param [index] {int} a valid index where to inser cell
783 * @param [index] {int} a valid index where to inser cell
784 * @private
784 * @private
785 *
785 *
786 * return true if everything whent fine.
786 * return true if everything whent fine.
787 **/
787 **/
788 Notebook.prototype._insert_element_at_index = function(element, index){
788 Notebook.prototype._insert_element_at_index = function(element, index){
789 if (element === undefined){
789 if (element === undefined){
790 return false;
790 return false;
791 }
791 }
792
792
793 var ncells = this.ncells();
793 var ncells = this.ncells();
794
794
795 if (ncells === 0) {
795 if (ncells === 0) {
796 // special case append if empty
796 // special case append if empty
797 this.element.find('div.end_space').before(element);
797 this.element.find('div.end_space').before(element);
798 } else if ( ncells === index ) {
798 } else if ( ncells === index ) {
799 // special case append it the end, but not empty
799 // special case append it the end, but not empty
800 this.get_cell_element(index-1).after(element);
800 this.get_cell_element(index-1).after(element);
801 } else if (this.is_valid_cell_index(index)) {
801 } else if (this.is_valid_cell_index(index)) {
802 // otherwise always somewhere to append to
802 // otherwise always somewhere to append to
803 this.get_cell_element(index).before(element);
803 this.get_cell_element(index).before(element);
804 } else {
804 } else {
805 return false;
805 return false;
806 }
806 }
807
807
808 if (this.undelete_index !== null && index <= this.undelete_index) {
808 if (this.undelete_index !== null && index <= this.undelete_index) {
809 this.undelete_index = this.undelete_index + 1;
809 this.undelete_index = this.undelete_index + 1;
810 this.set_dirty(true);
810 this.set_dirty(true);
811 }
811 }
812 return true;
812 return true;
813 };
813 };
814
814
815 /**
815 /**
816 * Insert a cell of given type above given index, or at top
816 * Insert a cell of given type above given index, or at top
817 * of notebook if index smaller than 0.
817 * of notebook if index smaller than 0.
818 *
818 *
819 * default index value is the one of currently selected cell
819 * default index value is the one of currently selected cell
820 *
820 *
821 * @method insert_cell_above
821 * @method insert_cell_above
822 * @param type {string} cell type
822 * @param type {string} cell type
823 * @param [index] {integer}
823 * @param [index] {integer}
824 *
824 *
825 * @return handle to created cell or null
825 * @return handle to created cell or null
826 **/
826 **/
827 Notebook.prototype.insert_cell_above = function (type, index) {
827 Notebook.prototype.insert_cell_above = function (type, index) {
828 index = this.index_or_selected(index);
828 index = this.index_or_selected(index);
829 return this.insert_cell_at_index(type, index);
829 return this.insert_cell_at_index(type, index);
830 };
830 };
831
831
832 /**
832 /**
833 * Insert a cell of given type below given index, or at bottom
833 * Insert a cell of given type below given index, or at bottom
834 * of notebook if index greater thatn number of cell
834 * of notebook if index greater thatn number of cell
835 *
835 *
836 * default index value is the one of currently selected cell
836 * default index value is the one of currently selected cell
837 *
837 *
838 * @method insert_cell_below
838 * @method insert_cell_below
839 * @param type {string} cell type
839 * @param type {string} cell type
840 * @param [index] {integer}
840 * @param [index] {integer}
841 *
841 *
842 * @return handle to created cell or null
842 * @return handle to created cell or null
843 *
843 *
844 **/
844 **/
845 Notebook.prototype.insert_cell_below = function (type, index) {
845 Notebook.prototype.insert_cell_below = function (type, index) {
846 index = this.index_or_selected(index);
846 index = this.index_or_selected(index);
847 return this.insert_cell_at_index(type, index+1);
847 return this.insert_cell_at_index(type, index+1);
848 };
848 };
849
849
850
850
851 /**
851 /**
852 * Insert cell at end of notebook
852 * Insert cell at end of notebook
853 *
853 *
854 * @method insert_cell_at_bottom
854 * @method insert_cell_at_bottom
855 * @param {String} type cell type
855 * @param {String} type cell type
856 *
856 *
857 * @return the added cell; or null
857 * @return the added cell; or null
858 **/
858 **/
859 Notebook.prototype.insert_cell_at_bottom = function (type){
859 Notebook.prototype.insert_cell_at_bottom = function (type){
860 var len = this.ncells();
860 var len = this.ncells();
861 return this.insert_cell_below(type,len-1);
861 return this.insert_cell_below(type,len-1);
862 };
862 };
863
863
864 /**
864 /**
865 * Turn a cell into a code cell.
865 * Turn a cell into a code cell.
866 *
866 *
867 * @method to_code
867 * @method to_code
868 * @param {Number} [index] A cell's index
868 * @param {Number} [index] A cell's index
869 */
869 */
870 Notebook.prototype.to_code = function (index) {
870 Notebook.prototype.to_code = function (index) {
871 var i = this.index_or_selected(index);
871 var i = this.index_or_selected(index);
872 if (this.is_valid_cell_index(i)) {
872 if (this.is_valid_cell_index(i)) {
873 var source_element = this.get_cell_element(i);
873 var source_element = this.get_cell_element(i);
874 var source_cell = source_element.data("cell");
874 var source_cell = source_element.data("cell");
875 if (!(source_cell instanceof IPython.CodeCell)) {
875 if (!(source_cell instanceof IPython.CodeCell)) {
876 var target_cell = this.insert_cell_below('code',i);
876 var target_cell = this.insert_cell_below('code',i);
877 var text = source_cell.get_text();
877 var text = source_cell.get_text();
878 if (text === source_cell.placeholder) {
878 if (text === source_cell.placeholder) {
879 text = '';
879 text = '';
880 }
880 }
881 target_cell.set_text(text);
881 target_cell.set_text(text);
882 // make this value the starting point, so that we can only undo
882 // make this value the starting point, so that we can only undo
883 // to this state, instead of a blank cell
883 // to this state, instead of a blank cell
884 target_cell.code_mirror.clearHistory();
884 target_cell.code_mirror.clearHistory();
885 source_element.remove();
885 source_element.remove();
886 this.select(i);
886 this.select(i);
887 this.set_dirty(true);
887 this.set_dirty(true);
888 }
888 }
889 }
889 }
890 };
890 };
891
891
892 /**
892 /**
893 * Turn a cell into a Markdown cell.
893 * Turn a cell into a Markdown cell.
894 *
894 *
895 * @method to_markdown
895 * @method to_markdown
896 * @param {Number} [index] A cell's index
896 * @param {Number} [index] A cell's index
897 */
897 */
898 Notebook.prototype.to_markdown = function (index) {
898 Notebook.prototype.to_markdown = function (index) {
899 var i = this.index_or_selected(index);
899 var i = this.index_or_selected(index);
900 if (this.is_valid_cell_index(i)) {
900 if (this.is_valid_cell_index(i)) {
901 var source_element = this.get_cell_element(i);
901 var source_element = this.get_cell_element(i);
902 var source_cell = source_element.data("cell");
902 var source_cell = source_element.data("cell");
903 if (!(source_cell instanceof IPython.MarkdownCell)) {
903 if (!(source_cell instanceof IPython.MarkdownCell)) {
904 var target_cell = this.insert_cell_below('markdown',i);
904 var target_cell = this.insert_cell_below('markdown',i);
905 var text = source_cell.get_text();
905 var text = source_cell.get_text();
906 if (text === source_cell.placeholder) {
906 if (text === source_cell.placeholder) {
907 text = '';
907 text = '';
908 }
908 }
909 // We must show the editor before setting its contents
909 // We must show the editor before setting its contents
910 target_cell.unrender();
910 target_cell.unrender();
911 target_cell.set_text(text);
911 target_cell.set_text(text);
912 // make this value the starting point, so that we can only undo
912 // make this value the starting point, so that we can only undo
913 // to this state, instead of a blank cell
913 // to this state, instead of a blank cell
914 target_cell.code_mirror.clearHistory();
914 target_cell.code_mirror.clearHistory();
915 source_element.remove();
915 source_element.remove();
916 this.select(i);
916 this.select(i);
917 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
917 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
918 target_cell.render();
918 target_cell.render();
919 }
919 }
920 this.set_dirty(true);
920 this.set_dirty(true);
921 }
921 }
922 }
922 }
923 };
923 };
924
924
925 /**
925 /**
926 * Turn a cell into a raw text cell.
926 * Turn a cell into a raw text cell.
927 *
927 *
928 * @method to_raw
928 * @method to_raw
929 * @param {Number} [index] A cell's index
929 * @param {Number} [index] A cell's index
930 */
930 */
931 Notebook.prototype.to_raw = function (index) {
931 Notebook.prototype.to_raw = function (index) {
932 var i = this.index_or_selected(index);
932 var i = this.index_or_selected(index);
933 if (this.is_valid_cell_index(i)) {
933 if (this.is_valid_cell_index(i)) {
934 var source_element = this.get_cell_element(i);
934 var source_element = this.get_cell_element(i);
935 var source_cell = source_element.data("cell");
935 var source_cell = source_element.data("cell");
936 var target_cell = null;
936 var target_cell = null;
937 if (!(source_cell instanceof IPython.RawCell)) {
937 if (!(source_cell instanceof IPython.RawCell)) {
938 target_cell = this.insert_cell_below('raw',i);
938 target_cell = this.insert_cell_below('raw',i);
939 var text = source_cell.get_text();
939 var text = source_cell.get_text();
940 if (text === source_cell.placeholder) {
940 if (text === source_cell.placeholder) {
941 text = '';
941 text = '';
942 }
942 }
943 // We must show the editor before setting its contents
943 // We must show the editor before setting its contents
944 target_cell.unrender();
944 target_cell.unrender();
945 target_cell.set_text(text);
945 target_cell.set_text(text);
946 // make this value the starting point, so that we can only undo
946 // make this value the starting point, so that we can only undo
947 // to this state, instead of a blank cell
947 // to this state, instead of a blank cell
948 target_cell.code_mirror.clearHistory();
948 target_cell.code_mirror.clearHistory();
949 source_element.remove();
949 source_element.remove();
950 this.select(i);
950 this.select(i);
951 this.set_dirty(true);
951 this.set_dirty(true);
952 }
952 }
953 }
953 }
954 };
954 };
955
955
956 /**
956 /**
957 * Turn a cell into a heading cell.
957 * Turn a cell into a heading cell.
958 *
958 *
959 * @method to_heading
959 * @method to_heading
960 * @param {Number} [index] A cell's index
960 * @param {Number} [index] A cell's index
961 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
961 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
962 */
962 */
963 Notebook.prototype.to_heading = function (index, level) {
963 Notebook.prototype.to_heading = function (index, level) {
964 level = level || 1;
964 level = level || 1;
965 var i = this.index_or_selected(index);
965 var i = this.index_or_selected(index);
966 if (this.is_valid_cell_index(i)) {
966 if (this.is_valid_cell_index(i)) {
967 var source_element = this.get_cell_element(i);
967 var source_element = this.get_cell_element(i);
968 var source_cell = source_element.data("cell");
968 var source_cell = source_element.data("cell");
969 var target_cell = null;
969 var target_cell = null;
970 if (source_cell instanceof IPython.HeadingCell) {
970 if (source_cell instanceof IPython.HeadingCell) {
971 source_cell.set_level(level);
971 source_cell.set_level(level);
972 } else {
972 } else {
973 target_cell = this.insert_cell_below('heading',i);
973 target_cell = this.insert_cell_below('heading',i);
974 var text = source_cell.get_text();
974 var text = source_cell.get_text();
975 if (text === source_cell.placeholder) {
975 if (text === source_cell.placeholder) {
976 text = '';
976 text = '';
977 }
977 }
978 // We must show the editor before setting its contents
978 // We must show the editor before setting its contents
979 target_cell.set_level(level);
979 target_cell.set_level(level);
980 target_cell.unrender();
980 target_cell.unrender();
981 target_cell.set_text(text);
981 target_cell.set_text(text);
982 // make this value the starting point, so that we can only undo
982 // make this value the starting point, so that we can only undo
983 // to this state, instead of a blank cell
983 // to this state, instead of a blank cell
984 target_cell.code_mirror.clearHistory();
984 target_cell.code_mirror.clearHistory();
985 source_element.remove();
985 source_element.remove();
986 this.select(i);
986 this.select(i);
987 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
987 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
988 target_cell.render();
988 target_cell.render();
989 }
989 }
990 }
990 }
991 this.set_dirty(true);
991 this.set_dirty(true);
992 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
992 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
993 {'cell_type':'heading',level:level}
993 {'cell_type':'heading',level:level}
994 );
994 );
995 }
995 }
996 };
996 };
997
997
998
998
999 // Cut/Copy/Paste
999 // Cut/Copy/Paste
1000
1000
1001 /**
1001 /**
1002 * Enable UI elements for pasting cells.
1002 * Enable UI elements for pasting cells.
1003 *
1003 *
1004 * @method enable_paste
1004 * @method enable_paste
1005 */
1005 */
1006 Notebook.prototype.enable_paste = function () {
1006 Notebook.prototype.enable_paste = function () {
1007 var that = this;
1007 var that = this;
1008 if (!this.paste_enabled) {
1008 if (!this.paste_enabled) {
1009 $('#paste_cell_replace').removeClass('disabled')
1009 $('#paste_cell_replace').removeClass('disabled')
1010 .on('click', function () {that.paste_cell_replace();});
1010 .on('click', function () {that.paste_cell_replace();});
1011 $('#paste_cell_above').removeClass('disabled')
1011 $('#paste_cell_above').removeClass('disabled')
1012 .on('click', function () {that.paste_cell_above();});
1012 .on('click', function () {that.paste_cell_above();});
1013 $('#paste_cell_below').removeClass('disabled')
1013 $('#paste_cell_below').removeClass('disabled')
1014 .on('click', function () {that.paste_cell_below();});
1014 .on('click', function () {that.paste_cell_below();});
1015 this.paste_enabled = true;
1015 this.paste_enabled = true;
1016 }
1016 }
1017 };
1017 };
1018
1018
1019 /**
1019 /**
1020 * Disable UI elements for pasting cells.
1020 * Disable UI elements for pasting cells.
1021 *
1021 *
1022 * @method disable_paste
1022 * @method disable_paste
1023 */
1023 */
1024 Notebook.prototype.disable_paste = function () {
1024 Notebook.prototype.disable_paste = function () {
1025 if (this.paste_enabled) {
1025 if (this.paste_enabled) {
1026 $('#paste_cell_replace').addClass('disabled').off('click');
1026 $('#paste_cell_replace').addClass('disabled').off('click');
1027 $('#paste_cell_above').addClass('disabled').off('click');
1027 $('#paste_cell_above').addClass('disabled').off('click');
1028 $('#paste_cell_below').addClass('disabled').off('click');
1028 $('#paste_cell_below').addClass('disabled').off('click');
1029 this.paste_enabled = false;
1029 this.paste_enabled = false;
1030 }
1030 }
1031 };
1031 };
1032
1032
1033 /**
1033 /**
1034 * Cut a cell.
1034 * Cut a cell.
1035 *
1035 *
1036 * @method cut_cell
1036 * @method cut_cell
1037 */
1037 */
1038 Notebook.prototype.cut_cell = function () {
1038 Notebook.prototype.cut_cell = function () {
1039 this.copy_cell();
1039 this.copy_cell();
1040 this.delete_cell();
1040 this.delete_cell();
1041 };
1041 };
1042
1042
1043 /**
1043 /**
1044 * Copy a cell.
1044 * Copy a cell.
1045 *
1045 *
1046 * @method copy_cell
1046 * @method copy_cell
1047 */
1047 */
1048 Notebook.prototype.copy_cell = function () {
1048 Notebook.prototype.copy_cell = function () {
1049 var cell = this.get_selected_cell();
1049 var cell = this.get_selected_cell();
1050 this.clipboard = cell.toJSON();
1050 this.clipboard = cell.toJSON();
1051 this.enable_paste();
1051 this.enable_paste();
1052 };
1052 };
1053
1053
1054 /**
1054 /**
1055 * Replace the selected cell with a cell in the clipboard.
1055 * Replace the selected cell with a cell in the clipboard.
1056 *
1056 *
1057 * @method paste_cell_replace
1057 * @method paste_cell_replace
1058 */
1058 */
1059 Notebook.prototype.paste_cell_replace = function () {
1059 Notebook.prototype.paste_cell_replace = function () {
1060 if (this.clipboard !== null && this.paste_enabled) {
1060 if (this.clipboard !== null && this.paste_enabled) {
1061 var cell_data = this.clipboard;
1061 var cell_data = this.clipboard;
1062 var new_cell = this.insert_cell_above(cell_data.cell_type);
1062 var new_cell = this.insert_cell_above(cell_data.cell_type);
1063 new_cell.fromJSON(cell_data);
1063 new_cell.fromJSON(cell_data);
1064 var old_cell = this.get_next_cell(new_cell);
1064 var old_cell = this.get_next_cell(new_cell);
1065 this.delete_cell(this.find_cell_index(old_cell));
1065 this.delete_cell(this.find_cell_index(old_cell));
1066 this.select(this.find_cell_index(new_cell));
1066 this.select(this.find_cell_index(new_cell));
1067 }
1067 }
1068 };
1068 };
1069
1069
1070 /**
1070 /**
1071 * Paste a cell from the clipboard above the selected cell.
1071 * Paste a cell from the clipboard above the selected cell.
1072 *
1072 *
1073 * @method paste_cell_above
1073 * @method paste_cell_above
1074 */
1074 */
1075 Notebook.prototype.paste_cell_above = function () {
1075 Notebook.prototype.paste_cell_above = function () {
1076 if (this.clipboard !== null && this.paste_enabled) {
1076 if (this.clipboard !== null && this.paste_enabled) {
1077 var cell_data = this.clipboard;
1077 var cell_data = this.clipboard;
1078 var new_cell = this.insert_cell_above(cell_data.cell_type);
1078 var new_cell = this.insert_cell_above(cell_data.cell_type);
1079 new_cell.fromJSON(cell_data);
1079 new_cell.fromJSON(cell_data);
1080 new_cell.focus_cell();
1080 new_cell.focus_cell();
1081 }
1081 }
1082 };
1082 };
1083
1083
1084 /**
1084 /**
1085 * Paste a cell from the clipboard below the selected cell.
1085 * Paste a cell from the clipboard below the selected cell.
1086 *
1086 *
1087 * @method paste_cell_below
1087 * @method paste_cell_below
1088 */
1088 */
1089 Notebook.prototype.paste_cell_below = function () {
1089 Notebook.prototype.paste_cell_below = function () {
1090 if (this.clipboard !== null && this.paste_enabled) {
1090 if (this.clipboard !== null && this.paste_enabled) {
1091 var cell_data = this.clipboard;
1091 var cell_data = this.clipboard;
1092 var new_cell = this.insert_cell_below(cell_data.cell_type);
1092 var new_cell = this.insert_cell_below(cell_data.cell_type);
1093 new_cell.fromJSON(cell_data);
1093 new_cell.fromJSON(cell_data);
1094 new_cell.focus_cell();
1094 new_cell.focus_cell();
1095 }
1095 }
1096 };
1096 };
1097
1097
1098 // Split/merge
1098 // Split/merge
1099
1099
1100 /**
1100 /**
1101 * Split the selected cell into two, at the cursor.
1101 * Split the selected cell into two, at the cursor.
1102 *
1102 *
1103 * @method split_cell
1103 * @method split_cell
1104 */
1104 */
1105 Notebook.prototype.split_cell = function () {
1105 Notebook.prototype.split_cell = function () {
1106 var mdc = IPython.MarkdownCell;
1106 var mdc = IPython.MarkdownCell;
1107 var rc = IPython.RawCell;
1107 var rc = IPython.RawCell;
1108 var cell = this.get_selected_cell();
1108 var cell = this.get_selected_cell();
1109 if (cell.is_splittable()) {
1109 if (cell.is_splittable()) {
1110 var texta = cell.get_pre_cursor();
1110 var texta = cell.get_pre_cursor();
1111 var textb = cell.get_post_cursor();
1111 var textb = cell.get_post_cursor();
1112 if (cell instanceof IPython.CodeCell) {
1112 if (cell instanceof IPython.CodeCell) {
1113 // In this case the operations keep the notebook in its existing mode
1113 // In this case the operations keep the notebook in its existing mode
1114 // so we don't need to do any post-op mode changes.
1114 // so we don't need to do any post-op mode changes.
1115 cell.set_text(textb);
1115 cell.set_text(textb);
1116 var new_cell = this.insert_cell_above('code');
1116 var new_cell = this.insert_cell_above('code');
1117 new_cell.set_text(texta);
1117 new_cell.set_text(texta);
1118 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1118 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1119 // We know cell is !rendered so we can use set_text.
1119 // We know cell is !rendered so we can use set_text.
1120 cell.set_text(textb);
1120 cell.set_text(textb);
1121 var new_cell = this.insert_cell_above(cell.cell_type);
1121 var new_cell = this.insert_cell_above(cell.cell_type);
1122 // Unrender the new cell so we can call set_text.
1122 // Unrender the new cell so we can call set_text.
1123 new_cell.unrender();
1123 new_cell.unrender();
1124 new_cell.set_text(texta);
1124 new_cell.set_text(texta);
1125 }
1125 }
1126 }
1126 }
1127 };
1127 };
1128
1128
1129 /**
1129 /**
1130 * Combine the selected cell into the cell above it.
1130 * Combine the selected cell into the cell above it.
1131 *
1131 *
1132 * @method merge_cell_above
1132 * @method merge_cell_above
1133 */
1133 */
1134 Notebook.prototype.merge_cell_above = function () {
1134 Notebook.prototype.merge_cell_above = function () {
1135 var mdc = IPython.MarkdownCell;
1135 var mdc = IPython.MarkdownCell;
1136 var rc = IPython.RawCell;
1136 var rc = IPython.RawCell;
1137 var index = this.get_selected_index();
1137 var index = this.get_selected_index();
1138 var cell = this.get_cell(index);
1138 var cell = this.get_cell(index);
1139 var render = cell.rendered;
1139 var render = cell.rendered;
1140 if (!cell.is_mergeable()) {
1140 if (!cell.is_mergeable()) {
1141 return;
1141 return;
1142 }
1142 }
1143 if (index > 0) {
1143 if (index > 0) {
1144 var upper_cell = this.get_cell(index-1);
1144 var upper_cell = this.get_cell(index-1);
1145 if (!upper_cell.is_mergeable()) {
1145 if (!upper_cell.is_mergeable()) {
1146 return;
1146 return;
1147 }
1147 }
1148 var upper_text = upper_cell.get_text();
1148 var upper_text = upper_cell.get_text();
1149 var text = cell.get_text();
1149 var text = cell.get_text();
1150 if (cell instanceof IPython.CodeCell) {
1150 if (cell instanceof IPython.CodeCell) {
1151 cell.set_text(upper_text+'\n'+text);
1151 cell.set_text(upper_text+'\n'+text);
1152 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1152 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1153 cell.unrender(); // Must unrender before we set_text.
1153 cell.unrender(); // Must unrender before we set_text.
1154 cell.set_text(upper_text+'\n\n'+text);
1154 cell.set_text(upper_text+'\n\n'+text);
1155 if (render) {
1155 if (render) {
1156 // The rendered state of the final cell should match
1156 // The rendered state of the final cell should match
1157 // that of the original selected cell;
1157 // that of the original selected cell;
1158 cell.render();
1158 cell.render();
1159 }
1159 }
1160 }
1160 }
1161 this.delete_cell(index-1);
1161 this.delete_cell(index-1);
1162 this.select(this.find_cell_index(cell));
1162 this.select(this.find_cell_index(cell));
1163 }
1163 }
1164 };
1164 };
1165
1165
1166 /**
1166 /**
1167 * Combine the selected cell into the cell below it.
1167 * Combine the selected cell into the cell below it.
1168 *
1168 *
1169 * @method merge_cell_below
1169 * @method merge_cell_below
1170 */
1170 */
1171 Notebook.prototype.merge_cell_below = function () {
1171 Notebook.prototype.merge_cell_below = function () {
1172 var mdc = IPython.MarkdownCell;
1172 var mdc = IPython.MarkdownCell;
1173 var rc = IPython.RawCell;
1173 var rc = IPython.RawCell;
1174 var index = this.get_selected_index();
1174 var index = this.get_selected_index();
1175 var cell = this.get_cell(index);
1175 var cell = this.get_cell(index);
1176 var render = cell.rendered;
1176 var render = cell.rendered;
1177 if (!cell.is_mergeable()) {
1177 if (!cell.is_mergeable()) {
1178 return;
1178 return;
1179 }
1179 }
1180 if (index < this.ncells()-1) {
1180 if (index < this.ncells()-1) {
1181 var lower_cell = this.get_cell(index+1);
1181 var lower_cell = this.get_cell(index+1);
1182 if (!lower_cell.is_mergeable()) {
1182 if (!lower_cell.is_mergeable()) {
1183 return;
1183 return;
1184 }
1184 }
1185 var lower_text = lower_cell.get_text();
1185 var lower_text = lower_cell.get_text();
1186 var text = cell.get_text();
1186 var text = cell.get_text();
1187 if (cell instanceof IPython.CodeCell) {
1187 if (cell instanceof IPython.CodeCell) {
1188 cell.set_text(text+'\n'+lower_text);
1188 cell.set_text(text+'\n'+lower_text);
1189 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1189 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1190 cell.unrender(); // Must unrender before we set_text.
1190 cell.unrender(); // Must unrender before we set_text.
1191 cell.set_text(text+'\n\n'+lower_text);
1191 cell.set_text(text+'\n\n'+lower_text);
1192 if (render) {
1192 if (render) {
1193 // The rendered state of the final cell should match
1193 // The rendered state of the final cell should match
1194 // that of the original selected cell;
1194 // that of the original selected cell;
1195 cell.render();
1195 cell.render();
1196 }
1196 }
1197 }
1197 }
1198 this.delete_cell(index+1);
1198 this.delete_cell(index+1);
1199 this.select(this.find_cell_index(cell));
1199 this.select(this.find_cell_index(cell));
1200 }
1200 }
1201 };
1201 };
1202
1202
1203
1203
1204 // Cell collapsing and output clearing
1204 // Cell collapsing and output clearing
1205
1205
1206 /**
1206 /**
1207 * Hide a cell's output.
1207 * Hide a cell's output.
1208 *
1208 *
1209 * @method collapse_output
1209 * @method collapse_output
1210 * @param {Number} index A cell's numeric index
1210 * @param {Number} index A cell's numeric index
1211 */
1211 */
1212 Notebook.prototype.collapse_output = function (index) {
1212 Notebook.prototype.collapse_output = function (index) {
1213 var i = this.index_or_selected(index);
1213 var i = this.index_or_selected(index);
1214 var cell = this.get_cell(i);
1214 var cell = this.get_cell(i);
1215 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1215 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1216 cell.collapse_output();
1216 cell.collapse_output();
1217 this.set_dirty(true);
1217 this.set_dirty(true);
1218 }
1218 }
1219 };
1219 };
1220
1220
1221 /**
1221 /**
1222 * Hide each code cell's output area.
1222 * Hide each code cell's output area.
1223 *
1223 *
1224 * @method collapse_all_output
1224 * @method collapse_all_output
1225 */
1225 */
1226 Notebook.prototype.collapse_all_output = function () {
1226 Notebook.prototype.collapse_all_output = function () {
1227 $.map(this.get_cells(), function (cell, i) {
1227 $.map(this.get_cells(), function (cell, i) {
1228 if (cell instanceof IPython.CodeCell) {
1228 if (cell instanceof IPython.CodeCell) {
1229 cell.collapse_output();
1229 cell.collapse_output();
1230 }
1230 }
1231 });
1231 });
1232 // this should not be set if the `collapse` key is removed from nbformat
1232 // this should not be set if the `collapse` key is removed from nbformat
1233 this.set_dirty(true);
1233 this.set_dirty(true);
1234 };
1234 };
1235
1235
1236 /**
1236 /**
1237 * Show a cell's output.
1237 * Show a cell's output.
1238 *
1238 *
1239 * @method expand_output
1239 * @method expand_output
1240 * @param {Number} index A cell's numeric index
1240 * @param {Number} index A cell's numeric index
1241 */
1241 */
1242 Notebook.prototype.expand_output = function (index) {
1242 Notebook.prototype.expand_output = function (index) {
1243 var i = this.index_or_selected(index);
1243 var i = this.index_or_selected(index);
1244 var cell = this.get_cell(i);
1244 var cell = this.get_cell(i);
1245 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1245 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1246 cell.expand_output();
1246 cell.expand_output();
1247 this.set_dirty(true);
1247 this.set_dirty(true);
1248 }
1248 }
1249 };
1249 };
1250
1250
1251 /**
1251 /**
1252 * Expand each code cell's output area, and remove scrollbars.
1252 * Expand each code cell's output area, and remove scrollbars.
1253 *
1253 *
1254 * @method expand_all_output
1254 * @method expand_all_output
1255 */
1255 */
1256 Notebook.prototype.expand_all_output = function () {
1256 Notebook.prototype.expand_all_output = function () {
1257 $.map(this.get_cells(), function (cell, i) {
1257 $.map(this.get_cells(), function (cell, i) {
1258 if (cell instanceof IPython.CodeCell) {
1258 if (cell instanceof IPython.CodeCell) {
1259 cell.expand_output();
1259 cell.expand_output();
1260 }
1260 }
1261 });
1261 });
1262 // this should not be set if the `collapse` key is removed from nbformat
1262 // this should not be set if the `collapse` key is removed from nbformat
1263 this.set_dirty(true);
1263 this.set_dirty(true);
1264 };
1264 };
1265
1265
1266 /**
1266 /**
1267 * Clear the selected CodeCell's output area.
1267 * Clear the selected CodeCell's output area.
1268 *
1268 *
1269 * @method clear_output
1269 * @method clear_output
1270 * @param {Number} index A cell's numeric index
1270 * @param {Number} index A cell's numeric index
1271 */
1271 */
1272 Notebook.prototype.clear_output = function (index) {
1272 Notebook.prototype.clear_output = function (index) {
1273 var i = this.index_or_selected(index);
1273 var i = this.index_or_selected(index);
1274 var cell = this.get_cell(i);
1274 var cell = this.get_cell(i);
1275 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1275 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1276 cell.clear_output();
1276 cell.clear_output();
1277 this.set_dirty(true);
1277 this.set_dirty(true);
1278 }
1278 }
1279 };
1279 };
1280
1280
1281 /**
1281 /**
1282 * Clear each code cell's output area.
1282 * Clear each code cell's output area.
1283 *
1283 *
1284 * @method clear_all_output
1284 * @method clear_all_output
1285 */
1285 */
1286 Notebook.prototype.clear_all_output = function () {
1286 Notebook.prototype.clear_all_output = function () {
1287 $.map(this.get_cells(), function (cell, i) {
1287 $.map(this.get_cells(), function (cell, i) {
1288 if (cell instanceof IPython.CodeCell) {
1288 if (cell instanceof IPython.CodeCell) {
1289 cell.clear_output();
1289 cell.clear_output();
1290 }
1290 }
1291 });
1291 });
1292 this.set_dirty(true);
1292 this.set_dirty(true);
1293 };
1293 };
1294
1294
1295 /**
1295 /**
1296 * Scroll the selected CodeCell's output area.
1296 * Scroll the selected CodeCell's output area.
1297 *
1297 *
1298 * @method scroll_output
1298 * @method scroll_output
1299 * @param {Number} index A cell's numeric index
1299 * @param {Number} index A cell's numeric index
1300 */
1300 */
1301 Notebook.prototype.scroll_output = function (index) {
1301 Notebook.prototype.scroll_output = function (index) {
1302 var i = this.index_or_selected(index);
1302 var i = this.index_or_selected(index);
1303 var cell = this.get_cell(i);
1303 var cell = this.get_cell(i);
1304 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1304 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1305 cell.scroll_output();
1305 cell.scroll_output();
1306 this.set_dirty(true);
1306 this.set_dirty(true);
1307 }
1307 }
1308 };
1308 };
1309
1309
1310 /**
1310 /**
1311 * Expand each code cell's output area, and add a scrollbar for long output.
1311 * Expand each code cell's output area, and add a scrollbar for long output.
1312 *
1312 *
1313 * @method scroll_all_output
1313 * @method scroll_all_output
1314 */
1314 */
1315 Notebook.prototype.scroll_all_output = function () {
1315 Notebook.prototype.scroll_all_output = function () {
1316 $.map(this.get_cells(), function (cell, i) {
1316 $.map(this.get_cells(), function (cell, i) {
1317 if (cell instanceof IPython.CodeCell) {
1317 if (cell instanceof IPython.CodeCell) {
1318 cell.scroll_output();
1318 cell.scroll_output();
1319 }
1319 }
1320 });
1320 });
1321 // this should not be set if the `collapse` key is removed from nbformat
1321 // this should not be set if the `collapse` key is removed from nbformat
1322 this.set_dirty(true);
1322 this.set_dirty(true);
1323 };
1323 };
1324
1324
1325 /** Toggle whether a cell's output is collapsed or expanded.
1325 /** Toggle whether a cell's output is collapsed or expanded.
1326 *
1326 *
1327 * @method toggle_output
1327 * @method toggle_output
1328 * @param {Number} index A cell's numeric index
1328 * @param {Number} index A cell's numeric index
1329 */
1329 */
1330 Notebook.prototype.toggle_output = function (index) {
1330 Notebook.prototype.toggle_output = function (index) {
1331 var i = this.index_or_selected(index);
1331 var i = this.index_or_selected(index);
1332 var cell = this.get_cell(i);
1332 var cell = this.get_cell(i);
1333 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1333 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1334 cell.toggle_output();
1334 cell.toggle_output();
1335 this.set_dirty(true);
1335 this.set_dirty(true);
1336 }
1336 }
1337 };
1337 };
1338
1338
1339 /**
1339 /**
1340 * Hide/show the output of all cells.
1340 * Hide/show the output of all cells.
1341 *
1341 *
1342 * @method toggle_all_output
1342 * @method toggle_all_output
1343 */
1343 */
1344 Notebook.prototype.toggle_all_output = function () {
1344 Notebook.prototype.toggle_all_output = function () {
1345 $.map(this.get_cells(), function (cell, i) {
1345 $.map(this.get_cells(), function (cell, i) {
1346 if (cell instanceof IPython.CodeCell) {
1346 if (cell instanceof IPython.CodeCell) {
1347 cell.toggle_output();
1347 cell.toggle_output();
1348 }
1348 }
1349 });
1349 });
1350 // this should not be set if the `collapse` key is removed from nbformat
1350 // this should not be set if the `collapse` key is removed from nbformat
1351 this.set_dirty(true);
1351 this.set_dirty(true);
1352 };
1352 };
1353
1353
1354 /**
1354 /**
1355 * Toggle a scrollbar for long cell outputs.
1355 * Toggle a scrollbar for long cell outputs.
1356 *
1356 *
1357 * @method toggle_output_scroll
1357 * @method toggle_output_scroll
1358 * @param {Number} index A cell's numeric index
1358 * @param {Number} index A cell's numeric index
1359 */
1359 */
1360 Notebook.prototype.toggle_output_scroll = function (index) {
1360 Notebook.prototype.toggle_output_scroll = function (index) {
1361 var i = this.index_or_selected(index);
1361 var i = this.index_or_selected(index);
1362 var cell = this.get_cell(i);
1362 var cell = this.get_cell(i);
1363 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1363 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1364 cell.toggle_output_scroll();
1364 cell.toggle_output_scroll();
1365 this.set_dirty(true);
1365 this.set_dirty(true);
1366 }
1366 }
1367 };
1367 };
1368
1368
1369 /**
1369 /**
1370 * Toggle the scrolling of long output on all cells.
1370 * Toggle the scrolling of long output on all cells.
1371 *
1371 *
1372 * @method toggle_all_output_scrolling
1372 * @method toggle_all_output_scrolling
1373 */
1373 */
1374 Notebook.prototype.toggle_all_output_scroll = function () {
1374 Notebook.prototype.toggle_all_output_scroll = function () {
1375 $.map(this.get_cells(), function (cell, i) {
1375 $.map(this.get_cells(), function (cell, i) {
1376 if (cell instanceof IPython.CodeCell) {
1376 if (cell instanceof IPython.CodeCell) {
1377 cell.toggle_output_scroll();
1377 cell.toggle_output_scroll();
1378 }
1378 }
1379 });
1379 });
1380 // this should not be set if the `collapse` key is removed from nbformat
1380 // this should not be set if the `collapse` key is removed from nbformat
1381 this.set_dirty(true);
1381 this.set_dirty(true);
1382 };
1382 };
1383
1383
1384 // Other cell functions: line numbers, ...
1384 // Other cell functions: line numbers, ...
1385
1385
1386 /**
1386 /**
1387 * Toggle line numbers in the selected cell's input area.
1387 * Toggle line numbers in the selected cell's input area.
1388 *
1388 *
1389 * @method cell_toggle_line_numbers
1389 * @method cell_toggle_line_numbers
1390 */
1390 */
1391 Notebook.prototype.cell_toggle_line_numbers = function() {
1391 Notebook.prototype.cell_toggle_line_numbers = function() {
1392 this.get_selected_cell().toggle_line_numbers();
1392 this.get_selected_cell().toggle_line_numbers();
1393 };
1393 };
1394
1394
1395 // Session related things
1395 // Session related things
1396
1396
1397 /**
1397 /**
1398 * Start a new session and set it on each code cell.
1398 * Start a new session and set it on each code cell.
1399 *
1399 *
1400 * @method start_session
1400 * @method start_session
1401 */
1401 */
1402 Notebook.prototype.start_session = function () {
1402 Notebook.prototype.start_session = function () {
1403 this.session = new IPython.Session(this, this.options);
1403 this.session = new IPython.Session(this, this.options);
1404 this.session.start($.proxy(this._session_started, this));
1404 this.session.start($.proxy(this._session_started, this));
1405 };
1405 };
1406
1406
1407
1407
1408 /**
1408 /**
1409 * Once a session is started, link the code cells to the kernel and pass the
1409 * Once a session is started, link the code cells to the kernel and pass the
1410 * comm manager to the widget manager
1410 * comm manager to the widget manager
1411 *
1411 *
1412 */
1412 */
1413 Notebook.prototype._session_started = function(){
1413 Notebook.prototype._session_started = function(){
1414 this.kernel = this.session.kernel;
1414 this.kernel = this.session.kernel;
1415 var ncells = this.ncells();
1415 var ncells = this.ncells();
1416 for (var i=0; i<ncells; i++) {
1416 for (var i=0; i<ncells; i++) {
1417 var cell = this.get_cell(i);
1417 var cell = this.get_cell(i);
1418 if (cell instanceof IPython.CodeCell) {
1418 if (cell instanceof IPython.CodeCell) {
1419 cell.set_kernel(this.session.kernel);
1419 cell.set_kernel(this.session.kernel);
1420 }
1420 }
1421 }
1421 }
1422 };
1422 };
1423
1423
1424 /**
1424 /**
1425 * Prompt the user to restart the IPython kernel.
1425 * Prompt the user to restart the IPython kernel.
1426 *
1426 *
1427 * @method restart_kernel
1427 * @method restart_kernel
1428 */
1428 */
1429 Notebook.prototype.restart_kernel = function () {
1429 Notebook.prototype.restart_kernel = function () {
1430 var that = this;
1430 var that = this;
1431 IPython.dialog.modal({
1431 IPython.dialog.modal({
1432 title : "Restart kernel or continue running?",
1432 title : "Restart kernel or continue running?",
1433 body : $("<p/>").text(
1433 body : $("<p/>").text(
1434 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1434 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1435 ),
1435 ),
1436 buttons : {
1436 buttons : {
1437 "Continue running" : {},
1437 "Continue running" : {},
1438 "Restart" : {
1438 "Restart" : {
1439 "class" : "btn-danger",
1439 "class" : "btn-danger",
1440 "click" : function() {
1440 "click" : function() {
1441 that.session.restart_kernel();
1441 that.session.restart_kernel();
1442 }
1442 }
1443 }
1443 }
1444 }
1444 }
1445 });
1445 });
1446 };
1446 };
1447
1447
1448 /**
1448 /**
1449 * Execute or render cell outputs and go into command mode.
1449 * Execute or render cell outputs and go into command mode.
1450 *
1450 *
1451 * @method execute_cell
1451 * @method execute_cell
1452 */
1452 */
1453 Notebook.prototype.execute_cell = function () {
1453 Notebook.prototype.execute_cell = function () {
1454 // mode = shift, ctrl, alt
1454 // mode = shift, ctrl, alt
1455 var cell = this.get_selected_cell();
1455 var cell = this.get_selected_cell();
1456 var cell_index = this.find_cell_index(cell);
1456 var cell_index = this.find_cell_index(cell);
1457
1457
1458 cell.execute();
1458 cell.execute();
1459 this.command_mode();
1459 this.command_mode();
1460 this.set_dirty(true);
1460 this.set_dirty(true);
1461 };
1461 };
1462
1462
1463 /**
1463 /**
1464 * Execute or render cell outputs and insert a new cell below.
1464 * Execute or render cell outputs and insert a new cell below.
1465 *
1465 *
1466 * @method execute_cell_and_insert_below
1466 * @method execute_cell_and_insert_below
1467 */
1467 */
1468 Notebook.prototype.execute_cell_and_insert_below = function () {
1468 Notebook.prototype.execute_cell_and_insert_below = function () {
1469 var cell = this.get_selected_cell();
1469 var cell = this.get_selected_cell();
1470 var cell_index = this.find_cell_index(cell);
1470 var cell_index = this.find_cell_index(cell);
1471
1471
1472 cell.execute();
1472 cell.execute();
1473
1473
1474 // If we are at the end always insert a new cell and return
1474 // If we are at the end always insert a new cell and return
1475 if (cell_index === (this.ncells()-1)) {
1475 if (cell_index === (this.ncells()-1)) {
1476 this.command_mode();
1476 this.command_mode();
1477 this.insert_cell_below('code');
1477 this.insert_cell_below('code');
1478 this.select(cell_index+1);
1478 this.select(cell_index+1);
1479 this.edit_mode();
1479 this.edit_mode();
1480 this.scroll_to_bottom();
1480 this.scroll_to_bottom();
1481 this.set_dirty(true);
1481 this.set_dirty(true);
1482 return;
1482 return;
1483 }
1483 }
1484
1484
1485 this.command_mode();
1485 this.command_mode();
1486 this.insert_cell_below('code');
1486 this.insert_cell_below('code');
1487 this.select(cell_index+1);
1487 this.select(cell_index+1);
1488 this.edit_mode();
1488 this.edit_mode();
1489 this.set_dirty(true);
1489 this.set_dirty(true);
1490 };
1490 };
1491
1491
1492 /**
1492 /**
1493 * Execute or render cell outputs and select the next cell.
1493 * Execute or render cell outputs and select the next cell.
1494 *
1494 *
1495 * @method execute_cell_and_select_below
1495 * @method execute_cell_and_select_below
1496 */
1496 */
1497 Notebook.prototype.execute_cell_and_select_below = function () {
1497 Notebook.prototype.execute_cell_and_select_below = function () {
1498
1498
1499 var cell = this.get_selected_cell();
1499 var cell = this.get_selected_cell();
1500 var cell_index = this.find_cell_index(cell);
1500 var cell_index = this.find_cell_index(cell);
1501
1501
1502 cell.execute();
1502 cell.execute();
1503
1503
1504 // If we are at the end always insert a new cell and return
1504 // If we are at the end always insert a new cell and return
1505 if (cell_index === (this.ncells()-1)) {
1505 if (cell_index === (this.ncells()-1)) {
1506 this.command_mode();
1506 this.command_mode();
1507 this.insert_cell_below('code');
1507 this.insert_cell_below('code');
1508 this.select(cell_index+1);
1508 this.select(cell_index+1);
1509 this.edit_mode();
1509 this.edit_mode();
1510 this.scroll_to_bottom();
1510 this.scroll_to_bottom();
1511 this.set_dirty(true);
1511 this.set_dirty(true);
1512 return;
1512 return;
1513 }
1513 }
1514
1514
1515 this.command_mode();
1515 this.command_mode();
1516 this.select(cell_index+1);
1516 this.select(cell_index+1);
1517 this.focus_cell();
1517 this.focus_cell();
1518 this.set_dirty(true);
1518 this.set_dirty(true);
1519 };
1519 };
1520
1520
1521 /**
1521 /**
1522 * Execute all cells below the selected cell.
1522 * Execute all cells below the selected cell.
1523 *
1523 *
1524 * @method execute_cells_below
1524 * @method execute_cells_below
1525 */
1525 */
1526 Notebook.prototype.execute_cells_below = function () {
1526 Notebook.prototype.execute_cells_below = function () {
1527 this.execute_cell_range(this.get_selected_index(), this.ncells());
1527 this.execute_cell_range(this.get_selected_index(), this.ncells());
1528 this.scroll_to_bottom();
1528 this.scroll_to_bottom();
1529 };
1529 };
1530
1530
1531 /**
1531 /**
1532 * Execute all cells above the selected cell.
1532 * Execute all cells above the selected cell.
1533 *
1533 *
1534 * @method execute_cells_above
1534 * @method execute_cells_above
1535 */
1535 */
1536 Notebook.prototype.execute_cells_above = function () {
1536 Notebook.prototype.execute_cells_above = function () {
1537 this.execute_cell_range(0, this.get_selected_index());
1537 this.execute_cell_range(0, this.get_selected_index());
1538 };
1538 };
1539
1539
1540 /**
1540 /**
1541 * Execute all cells.
1541 * Execute all cells.
1542 *
1542 *
1543 * @method execute_all_cells
1543 * @method execute_all_cells
1544 */
1544 */
1545 Notebook.prototype.execute_all_cells = function () {
1545 Notebook.prototype.execute_all_cells = function () {
1546 this.execute_cell_range(0, this.ncells());
1546 this.execute_cell_range(0, this.ncells());
1547 this.scroll_to_bottom();
1547 this.scroll_to_bottom();
1548 };
1548 };
1549
1549
1550 /**
1550 /**
1551 * Execute a contiguous range of cells.
1551 * Execute a contiguous range of cells.
1552 *
1552 *
1553 * @method execute_cell_range
1553 * @method execute_cell_range
1554 * @param {Number} start Index of the first cell to execute (inclusive)
1554 * @param {Number} start Index of the first cell to execute (inclusive)
1555 * @param {Number} end Index of the last cell to execute (exclusive)
1555 * @param {Number} end Index of the last cell to execute (exclusive)
1556 */
1556 */
1557 Notebook.prototype.execute_cell_range = function (start, end) {
1557 Notebook.prototype.execute_cell_range = function (start, end) {
1558 this.command_mode();
1558 this.command_mode();
1559 for (var i=start; i<end; i++) {
1559 for (var i=start; i<end; i++) {
1560 this.select(i);
1560 this.select(i);
1561 this.execute_cell();
1561 this.execute_cell();
1562 }
1562 }
1563 };
1563 };
1564
1564
1565 // Persistance and loading
1565 // Persistance and loading
1566
1566
1567 /**
1567 /**
1568 * Getter method for this notebook's name.
1568 * Getter method for this notebook's name.
1569 *
1569 *
1570 * @method get_notebook_name
1570 * @method get_notebook_name
1571 * @return {String} This notebook's name (excluding file extension)
1571 * @return {String} This notebook's name (excluding file extension)
1572 */
1572 */
1573 Notebook.prototype.get_notebook_name = function () {
1573 Notebook.prototype.get_notebook_name = function () {
1574 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1574 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1575 return nbname;
1575 return nbname;
1576 };
1576 };
1577
1577
1578 /**
1578 /**
1579 * Setter method for this notebook's name.
1579 * Setter method for this notebook's name.
1580 *
1580 *
1581 * @method set_notebook_name
1581 * @method set_notebook_name
1582 * @param {String} name A new name for this notebook
1582 * @param {String} name A new name for this notebook
1583 */
1583 */
1584 Notebook.prototype.set_notebook_name = function (name) {
1584 Notebook.prototype.set_notebook_name = function (name) {
1585 this.notebook_name = name;
1585 this.notebook_name = name;
1586 };
1586 };
1587
1587
1588 /**
1588 /**
1589 * Check that a notebook's name is valid.
1589 * Check that a notebook's name is valid.
1590 *
1590 *
1591 * @method test_notebook_name
1591 * @method test_notebook_name
1592 * @param {String} nbname A name for this notebook
1592 * @param {String} nbname A name for this notebook
1593 * @return {Boolean} True if the name is valid, false if invalid
1593 * @return {Boolean} True if the name is valid, false if invalid
1594 */
1594 */
1595 Notebook.prototype.test_notebook_name = function (nbname) {
1595 Notebook.prototype.test_notebook_name = function (nbname) {
1596 nbname = nbname || '';
1596 nbname = nbname || '';
1597 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1597 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1598 return true;
1598 return true;
1599 } else {
1599 } else {
1600 return false;
1600 return false;
1601 }
1601 }
1602 };
1602 };
1603
1603
1604 /**
1604 /**
1605 * Load a notebook from JSON (.ipynb).
1605 * Load a notebook from JSON (.ipynb).
1606 *
1606 *
1607 * This currently handles one worksheet: others are deleted.
1607 * This currently handles one worksheet: others are deleted.
1608 *
1608 *
1609 * @method fromJSON
1609 * @method fromJSON
1610 * @param {Object} data JSON representation of a notebook
1610 * @param {Object} data JSON representation of a notebook
1611 */
1611 */
1612 Notebook.prototype.fromJSON = function (data) {
1612 Notebook.prototype.fromJSON = function (data) {
1613 var content = data.content;
1613 var content = data.content;
1614 var ncells = this.ncells();
1614 var ncells = this.ncells();
1615 var i;
1615 var i;
1616 for (i=0; i<ncells; i++) {
1616 for (i=0; i<ncells; i++) {
1617 // Always delete cell 0 as they get renumbered as they are deleted.
1617 // Always delete cell 0 as they get renumbered as they are deleted.
1618 this.delete_cell(0);
1618 this.delete_cell(0);
1619 }
1619 }
1620 // Save the metadata and name.
1620 // Save the metadata and name.
1621 this.metadata = content.metadata;
1621 this.metadata = content.metadata;
1622 this.notebook_name = data.name;
1622 this.notebook_name = data.name;
1623 var trusted = true;
1623 var trusted = true;
1624 // Only handle 1 worksheet for now.
1624 // Only handle 1 worksheet for now.
1625 var worksheet = content.worksheets[0];
1625 var worksheet = content.worksheets[0];
1626 if (worksheet !== undefined) {
1626 if (worksheet !== undefined) {
1627 if (worksheet.metadata) {
1627 if (worksheet.metadata) {
1628 this.worksheet_metadata = worksheet.metadata;
1628 this.worksheet_metadata = worksheet.metadata;
1629 }
1629 }
1630 var new_cells = worksheet.cells;
1630 var new_cells = worksheet.cells;
1631 ncells = new_cells.length;
1631 ncells = new_cells.length;
1632 var cell_data = null;
1632 var cell_data = null;
1633 var new_cell = null;
1633 var new_cell = null;
1634 for (i=0; i<ncells; i++) {
1634 for (i=0; i<ncells; i++) {
1635 cell_data = new_cells[i];
1635 cell_data = new_cells[i];
1636 // VERSIONHACK: plaintext -> raw
1636 // VERSIONHACK: plaintext -> raw
1637 // handle never-released plaintext name for raw cells
1637 // handle never-released plaintext name for raw cells
1638 if (cell_data.cell_type === 'plaintext'){
1638 if (cell_data.cell_type === 'plaintext'){
1639 cell_data.cell_type = 'raw';
1639 cell_data.cell_type = 'raw';
1640 }
1640 }
1641
1641
1642 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1642 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1643 new_cell.fromJSON(cell_data);
1643 new_cell.fromJSON(cell_data);
1644 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1644 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1645 trusted = false;
1645 trusted = false;
1646 }
1646 }
1647 }
1647 }
1648 }
1648 }
1649 if (trusted != this.trusted) {
1649 if (trusted != this.trusted) {
1650 this.trusted = trusted;
1650 this.trusted = trusted;
1651 $([IPython.events]).trigger("trust_changed.Notebook", trusted);
1651 $([IPython.events]).trigger("trust_changed.Notebook", trusted);
1652 }
1652 }
1653 if (content.worksheets.length > 1) {
1653 if (content.worksheets.length > 1) {
1654 IPython.dialog.modal({
1654 IPython.dialog.modal({
1655 title : "Multiple worksheets",
1655 title : "Multiple worksheets",
1656 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1656 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1657 "but this version of IPython can only handle the first. " +
1657 "but this version of IPython can only handle the first. " +
1658 "If you save this notebook, worksheets after the first will be lost.",
1658 "If you save this notebook, worksheets after the first will be lost.",
1659 buttons : {
1659 buttons : {
1660 OK : {
1660 OK : {
1661 class : "btn-danger"
1661 class : "btn-danger"
1662 }
1662 }
1663 }
1663 }
1664 });
1664 });
1665 }
1665 }
1666 };
1666 };
1667
1667
1668 /**
1668 /**
1669 * Dump this notebook into a JSON-friendly object.
1669 * Dump this notebook into a JSON-friendly object.
1670 *
1670 *
1671 * @method toJSON
1671 * @method toJSON
1672 * @return {Object} A JSON-friendly representation of this notebook.
1672 * @return {Object} A JSON-friendly representation of this notebook.
1673 */
1673 */
1674 Notebook.prototype.toJSON = function () {
1674 Notebook.prototype.toJSON = function () {
1675 var cells = this.get_cells();
1675 var cells = this.get_cells();
1676 var ncells = cells.length;
1676 var ncells = cells.length;
1677 var cell_array = new Array(ncells);
1677 var cell_array = new Array(ncells);
1678 var trusted = true;
1678 var trusted = true;
1679 for (var i=0; i<ncells; i++) {
1679 for (var i=0; i<ncells; i++) {
1680 var cell = cells[i];
1680 var cell = cells[i];
1681 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1681 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1682 trusted = false;
1682 trusted = false;
1683 }
1683 }
1684 cell_array[i] = cell.toJSON();
1684 cell_array[i] = cell.toJSON();
1685 }
1685 }
1686 var data = {
1686 var data = {
1687 // Only handle 1 worksheet for now.
1687 // Only handle 1 worksheet for now.
1688 worksheets : [{
1688 worksheets : [{
1689 cells: cell_array,
1689 cells: cell_array,
1690 metadata: this.worksheet_metadata
1690 metadata: this.worksheet_metadata
1691 }],
1691 }],
1692 metadata : this.metadata
1692 metadata : this.metadata
1693 };
1693 };
1694 if (trusted != this.trusted) {
1694 if (trusted != this.trusted) {
1695 this.trusted = trusted;
1695 this.trusted = trusted;
1696 $([IPython.events]).trigger("trust_changed.Notebook", trusted);
1696 $([IPython.events]).trigger("trust_changed.Notebook", trusted);
1697 }
1697 }
1698 return data;
1698 return data;
1699 };
1699 };
1700
1700
1701 /**
1701 /**
1702 * Start an autosave timer, for periodically saving the notebook.
1702 * Start an autosave timer, for periodically saving the notebook.
1703 *
1703 *
1704 * @method set_autosave_interval
1704 * @method set_autosave_interval
1705 * @param {Integer} interval the autosave interval in milliseconds
1705 * @param {Integer} interval the autosave interval in milliseconds
1706 */
1706 */
1707 Notebook.prototype.set_autosave_interval = function (interval) {
1707 Notebook.prototype.set_autosave_interval = function (interval) {
1708 var that = this;
1708 var that = this;
1709 // clear previous interval, so we don't get simultaneous timers
1709 // clear previous interval, so we don't get simultaneous timers
1710 if (this.autosave_timer) {
1710 if (this.autosave_timer) {
1711 clearInterval(this.autosave_timer);
1711 clearInterval(this.autosave_timer);
1712 }
1712 }
1713
1713
1714 this.autosave_interval = this.minimum_autosave_interval = interval;
1714 this.autosave_interval = this.minimum_autosave_interval = interval;
1715 if (interval) {
1715 if (interval) {
1716 this.autosave_timer = setInterval(function() {
1716 this.autosave_timer = setInterval(function() {
1717 if (that.dirty) {
1717 if (that.dirty) {
1718 that.save_notebook();
1718 that.save_notebook();
1719 }
1719 }
1720 }, interval);
1720 }, interval);
1721 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1721 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1722 } else {
1722 } else {
1723 this.autosave_timer = null;
1723 this.autosave_timer = null;
1724 $([IPython.events]).trigger("autosave_disabled.Notebook");
1724 $([IPython.events]).trigger("autosave_disabled.Notebook");
1725 }
1725 }
1726 };
1726 };
1727
1727
1728 /**
1728 /**
1729 * Save this notebook on the server. This becomes a notebook instance's
1729 * Save this notebook on the server. This becomes a notebook instance's
1730 * .save_notebook method *after* the entire notebook has been loaded.
1730 * .save_notebook method *after* the entire notebook has been loaded.
1731 *
1731 *
1732 * @method save_notebook
1732 * @method save_notebook
1733 */
1733 */
1734 Notebook.prototype.save_notebook = function (extra_settings) {
1734 Notebook.prototype.save_notebook = function (extra_settings) {
1735 // Create a JSON model to be sent to the server.
1735 // Create a JSON model to be sent to the server.
1736 var model = {};
1736 var model = {};
1737 model.name = this.notebook_name;
1737 model.name = this.notebook_name;
1738 model.path = this.notebook_path;
1738 model.path = this.notebook_path;
1739 model.content = this.toJSON();
1739 model.content = this.toJSON();
1740 model.content.nbformat = this.nbformat;
1740 model.content.nbformat = this.nbformat;
1741 model.content.nbformat_minor = this.nbformat_minor;
1741 model.content.nbformat_minor = this.nbformat_minor;
1742 // time the ajax call for autosave tuning purposes.
1742 // time the ajax call for autosave tuning purposes.
1743 var start = new Date().getTime();
1743 var start = new Date().getTime();
1744 // We do the call with settings so we can set cache to false.
1744 // We do the call with settings so we can set cache to false.
1745 var settings = {
1745 var settings = {
1746 processData : false,
1746 processData : false,
1747 cache : false,
1747 cache : false,
1748 type : "PUT",
1748 type : "PUT",
1749 data : JSON.stringify(model),
1749 data : JSON.stringify(model),
1750 headers : {'Content-Type': 'application/json'},
1750 headers : {'Content-Type': 'application/json'},
1751 success : $.proxy(this.save_notebook_success, this, start),
1751 success : $.proxy(this.save_notebook_success, this, start),
1752 error : $.proxy(this.save_notebook_error, this)
1752 error : $.proxy(this.save_notebook_error, this)
1753 };
1753 };
1754 if (extra_settings) {
1754 if (extra_settings) {
1755 for (var key in extra_settings) {
1755 for (var key in extra_settings) {
1756 settings[key] = extra_settings[key];
1756 settings[key] = extra_settings[key];
1757 }
1757 }
1758 }
1758 }
1759 $([IPython.events]).trigger('notebook_saving.Notebook');
1759 $([IPython.events]).trigger('notebook_saving.Notebook');
1760 var url = utils.url_join_encode(
1760 var url = utils.url_join_encode(
1761 this.base_url,
1761 this.base_url,
1762 'api/notebooks',
1762 'api/notebooks',
1763 this.notebook_path,
1763 this.notebook_path,
1764 this.notebook_name
1764 this.notebook_name
1765 );
1765 );
1766 $.ajax(url, settings);
1766 $.ajax(url, settings);
1767 };
1767 };
1768
1768
1769 /**
1769 /**
1770 * Success callback for saving a notebook.
1770 * Success callback for saving a notebook.
1771 *
1771 *
1772 * @method save_notebook_success
1772 * @method save_notebook_success
1773 * @param {Integer} start the time when the save request started
1773 * @param {Integer} start the time when the save request started
1774 * @param {Object} data JSON representation of a notebook
1774 * @param {Object} data JSON representation of a notebook
1775 * @param {String} status Description of response status
1775 * @param {String} status Description of response status
1776 * @param {jqXHR} xhr jQuery Ajax object
1776 * @param {jqXHR} xhr jQuery Ajax object
1777 */
1777 */
1778 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1778 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1779 this.set_dirty(false);
1779 this.set_dirty(false);
1780 $([IPython.events]).trigger('notebook_saved.Notebook');
1780 $([IPython.events]).trigger('notebook_saved.Notebook');
1781 this._update_autosave_interval(start);
1781 this._update_autosave_interval(start);
1782 if (this._checkpoint_after_save) {
1782 if (this._checkpoint_after_save) {
1783 this.create_checkpoint();
1783 this.create_checkpoint();
1784 this._checkpoint_after_save = false;
1784 this._checkpoint_after_save = false;
1785 }
1785 }
1786 };
1786 };
1787
1787
1788 /**
1788 /**
1789 * update the autosave interval based on how long the last save took
1789 * update the autosave interval based on how long the last save took
1790 *
1790 *
1791 * @method _update_autosave_interval
1791 * @method _update_autosave_interval
1792 * @param {Integer} timestamp when the save request started
1792 * @param {Integer} timestamp when the save request started
1793 */
1793 */
1794 Notebook.prototype._update_autosave_interval = function (start) {
1794 Notebook.prototype._update_autosave_interval = function (start) {
1795 var duration = (new Date().getTime() - start);
1795 var duration = (new Date().getTime() - start);
1796 if (this.autosave_interval) {
1796 if (this.autosave_interval) {
1797 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1797 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1798 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1798 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1799 // round to 10 seconds, otherwise we will be setting a new interval too often
1799 // round to 10 seconds, otherwise we will be setting a new interval too often
1800 interval = 10000 * Math.round(interval / 10000);
1800 interval = 10000 * Math.round(interval / 10000);
1801 // set new interval, if it's changed
1801 // set new interval, if it's changed
1802 if (interval != this.autosave_interval) {
1802 if (interval != this.autosave_interval) {
1803 this.set_autosave_interval(interval);
1803 this.set_autosave_interval(interval);
1804 }
1804 }
1805 }
1805 }
1806 };
1806 };
1807
1807
1808 /**
1808 /**
1809 * Failure callback for saving a notebook.
1809 * Failure callback for saving a notebook.
1810 *
1810 *
1811 * @method save_notebook_error
1811 * @method save_notebook_error
1812 * @param {jqXHR} xhr jQuery Ajax object
1812 * @param {jqXHR} xhr jQuery Ajax object
1813 * @param {String} status Description of response status
1813 * @param {String} status Description of response status
1814 * @param {String} error HTTP error message
1814 * @param {String} error HTTP error message
1815 */
1815 */
1816 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1816 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1817 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1817 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1818 };
1818 };
1819
1819
1820 /**
1820 /**
1821 * Explicitly trust the output of this notebook.
1821 * Explicitly trust the output of this notebook.
1822 *
1822 *
1823 * @method trust_notebook
1823 * @method trust_notebook
1824 */
1824 */
1825 Notebook.prototype.trust_notebook = function (extra_settings) {
1825 Notebook.prototype.trust_notebook = function (extra_settings) {
1826 var body = $("<div>").append($("<p>")
1826 var body = $("<div>").append($("<p>")
1827 .text("A trusted IPython notebook may execute hidden malicious code ")
1827 .text("A trusted IPython notebook may execute hidden malicious code ")
1828 .append($("<strong>")
1828 .append($("<strong>")
1829 .append(
1829 .append(
1830 $("<em>").text("when you open it")
1830 $("<em>").text("when you open it")
1831 )
1831 )
1832 ).append(".").append(
1832 ).append(".").append(
1833 " Selecting trust will immediately reload this notebook in a trusted state."
1833 " Selecting trust will immediately reload this notebook in a trusted state."
1834 ).append(
1834 ).append(
1835 " For more information, see the "
1835 " For more information, see the "
1836 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1836 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1837 .text("IPython security documentation")
1837 .text("IPython security documentation")
1838 ).append(".")
1838 ).append(".")
1839 );
1839 );
1840
1840
1841 var nb = this;
1841 var nb = this;
1842 IPython.dialog.modal({
1842 IPython.dialog.modal({
1843 title: "Trust this notebook?",
1843 title: "Trust this notebook?",
1844 body: body,
1844 body: body,
1845
1845
1846 buttons: {
1846 buttons: {
1847 Cancel : {},
1847 Cancel : {},
1848 Trust : {
1848 Trust : {
1849 class : "btn-danger",
1849 class : "btn-danger",
1850 click : function () {
1850 click : function () {
1851 var cells = nb.get_cells();
1851 var cells = nb.get_cells();
1852 for (var i = 0; i < cells.length; i++) {
1852 for (var i = 0; i < cells.length; i++) {
1853 var cell = cells[i];
1853 var cell = cells[i];
1854 if (cell.cell_type == 'code') {
1854 if (cell.cell_type == 'code') {
1855 cell.output_area.trusted = true;
1855 cell.output_area.trusted = true;
1856 }
1856 }
1857 }
1857 }
1858 $([IPython.events]).on('notebook_saved.Notebook', function () {
1858 $([IPython.events]).on('notebook_saved.Notebook', function () {
1859 window.location.reload();
1859 window.location.reload();
1860 });
1860 });
1861 nb.save_notebook();
1861 nb.save_notebook();
1862 }
1862 }
1863 }
1863 }
1864 }
1864 }
1865 });
1865 });
1866 };
1866 };
1867
1867
1868 Notebook.prototype.new_notebook = function(){
1868 Notebook.prototype.new_notebook = function(){
1869 var path = this.notebook_path;
1869 var path = this.notebook_path;
1870 var base_url = this.base_url;
1870 var base_url = this.base_url;
1871 var settings = {
1871 var settings = {
1872 processData : false,
1872 processData : false,
1873 cache : false,
1873 cache : false,
1874 type : "POST",
1874 type : "POST",
1875 dataType : "json",
1875 dataType : "json",
1876 async : false,
1876 async : false,
1877 success : function (data, status, xhr){
1877 success : function (data, status, xhr){
1878 var notebook_name = data.name;
1878 var notebook_name = data.name;
1879 window.open(
1879 window.open(
1880 utils.url_join_encode(
1880 utils.url_join_encode(
1881 base_url,
1881 base_url,
1882 'notebooks',
1882 'notebooks',
1883 path,
1883 path,
1884 notebook_name
1884 notebook_name
1885 ),
1885 ),
1886 '_blank'
1886 '_blank'
1887 );
1887 );
1888 },
1888 },
1889 error : utils.log_ajax_error,
1889 error : utils.log_ajax_error,
1890 };
1890 };
1891 var url = utils.url_join_encode(
1891 var url = utils.url_join_encode(
1892 base_url,
1892 base_url,
1893 'api/notebooks',
1893 'api/notebooks',
1894 path
1894 path
1895 );
1895 );
1896 $.ajax(url,settings);
1896 $.ajax(url,settings);
1897 };
1897 };
1898
1898
1899
1899
1900 Notebook.prototype.copy_notebook = function(){
1900 Notebook.prototype.copy_notebook = function(){
1901 var path = this.notebook_path;
1901 var path = this.notebook_path;
1902 var base_url = this.base_url;
1902 var base_url = this.base_url;
1903 var settings = {
1903 var settings = {
1904 processData : false,
1904 processData : false,
1905 cache : false,
1905 cache : false,
1906 type : "POST",
1906 type : "POST",
1907 dataType : "json",
1907 dataType : "json",
1908 data : JSON.stringify({copy_from : this.notebook_name}),
1908 data : JSON.stringify({copy_from : this.notebook_name}),
1909 async : false,
1909 async : false,
1910 success : function (data, status, xhr) {
1910 success : function (data, status, xhr) {
1911 window.open(utils.url_join_encode(
1911 window.open(utils.url_join_encode(
1912 base_url,
1912 base_url,
1913 'notebooks',
1913 'notebooks',
1914 data.path,
1914 data.path,
1915 data.name
1915 data.name
1916 ), '_blank');
1916 ), '_blank');
1917 },
1917 },
1918 error : utils.log_ajax_error,
1918 error : utils.log_ajax_error,
1919 };
1919 };
1920 var url = utils.url_join_encode(
1920 var url = utils.url_join_encode(
1921 base_url,
1921 base_url,
1922 'api/notebooks',
1922 'api/notebooks',
1923 path
1923 path
1924 );
1924 );
1925 $.ajax(url,settings);
1925 $.ajax(url,settings);
1926 };
1926 };
1927
1927
1928 Notebook.prototype.rename = function (nbname) {
1928 Notebook.prototype.rename = function (nbname) {
1929 var that = this;
1929 var that = this;
1930 if (!nbname.match(/\.ipynb$/)) {
1930 if (!nbname.match(/\.ipynb$/)) {
1931 nbname = nbname + ".ipynb";
1931 nbname = nbname + ".ipynb";
1932 }
1932 }
1933 var data = {name: nbname};
1933 var data = {name: nbname};
1934 var settings = {
1934 var settings = {
1935 processData : false,
1935 processData : false,
1936 cache : false,
1936 cache : false,
1937 type : "PATCH",
1937 type : "PATCH",
1938 data : JSON.stringify(data),
1938 data : JSON.stringify(data),
1939 dataType: "json",
1939 dataType: "json",
1940 headers : {'Content-Type': 'application/json'},
1940 headers : {'Content-Type': 'application/json'},
1941 success : $.proxy(that.rename_success, this),
1941 success : $.proxy(that.rename_success, this),
1942 error : $.proxy(that.rename_error, this)
1942 error : $.proxy(that.rename_error, this)
1943 };
1943 };
1944 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1944 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1945 var url = utils.url_join_encode(
1945 var url = utils.url_join_encode(
1946 this.base_url,
1946 this.base_url,
1947 'api/notebooks',
1947 'api/notebooks',
1948 this.notebook_path,
1948 this.notebook_path,
1949 this.notebook_name
1949 this.notebook_name
1950 );
1950 );
1951 $.ajax(url, settings);
1951 $.ajax(url, settings);
1952 };
1952 };
1953
1953
1954 Notebook.prototype.delete = function () {
1954 Notebook.prototype.delete = function () {
1955 var that = this;
1955 var that = this;
1956 var settings = {
1956 var settings = {
1957 processData : false,
1957 processData : false,
1958 cache : false,
1958 cache : false,
1959 type : "DELETE",
1959 type : "DELETE",
1960 dataType: "json",
1960 dataType: "json",
1961 error : utils.log_ajax_error,
1961 error : utils.log_ajax_error,
1962 };
1962 };
1963 var url = utils.url_join_encode(
1963 var url = utils.url_join_encode(
1964 this.base_url,
1964 this.base_url,
1965 'api/notebooks',
1965 'api/notebooks',
1966 this.notebook_path,
1966 this.notebook_path,
1967 this.notebook_name
1967 this.notebook_name
1968 );
1968 );
1969 $.ajax(url, settings);
1969 $.ajax(url, settings);
1970 };
1970 };
1971
1971
1972
1972
1973 Notebook.prototype.rename_success = function (json, status, xhr) {
1973 Notebook.prototype.rename_success = function (json, status, xhr) {
1974 var name = this.notebook_name = json.name;
1974 var name = this.notebook_name = json.name;
1975 var path = json.path;
1975 var path = json.path;
1976 this.session.rename_notebook(name, path);
1976 this.session.rename_notebook(name, path);
1977 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1977 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1978 };
1978 };
1979
1979
1980 Notebook.prototype.rename_error = function (xhr, status, error) {
1980 Notebook.prototype.rename_error = function (xhr, status, error) {
1981 var that = this;
1981 var that = this;
1982 var dialog = $('<div/>').append(
1982 var dialog = $('<div/>').append(
1983 $("<p/>").addClass("rename-message")
1983 $("<p/>").addClass("rename-message")
1984 .text('This notebook name already exists.')
1984 .text('This notebook name already exists.')
1985 );
1985 );
1986 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1986 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1987 IPython.dialog.modal({
1987 IPython.dialog.modal({
1988 title: "Notebook Rename Error!",
1988 title: "Notebook Rename Error!",
1989 body: dialog,
1989 body: dialog,
1990 buttons : {
1990 buttons : {
1991 "Cancel": {},
1991 "Cancel": {},
1992 "OK": {
1992 "OK": {
1993 class: "btn-primary",
1993 class: "btn-primary",
1994 click: function () {
1994 click: function () {
1995 IPython.save_widget.rename_notebook();
1995 IPython.save_widget.rename_notebook();
1996 }}
1996 }}
1997 },
1997 },
1998 open : function (event, ui) {
1998 open : function (event, ui) {
1999 var that = $(this);
1999 var that = $(this);
2000 // Upon ENTER, click the OK button.
2000 // Upon ENTER, click the OK button.
2001 that.find('input[type="text"]').keydown(function (event, ui) {
2001 that.find('input[type="text"]').keydown(function (event, ui) {
2002 if (event.which === IPython.keyboard.keycodes.enter) {
2002 if (event.which === IPython.keyboard.keycodes.enter) {
2003 that.find('.btn-primary').first().click();
2003 that.find('.btn-primary').first().click();
2004 }
2004 }
2005 });
2005 });
2006 that.find('input[type="text"]').focus();
2006 that.find('input[type="text"]').focus();
2007 }
2007 }
2008 });
2008 });
2009 };
2009 };
2010
2010
2011 /**
2011 /**
2012 * Request a notebook's data from the server.
2012 * Request a notebook's data from the server.
2013 *
2013 *
2014 * @method load_notebook
2014 * @method load_notebook
2015 * @param {String} notebook_name and path A notebook to load
2015 * @param {String} notebook_name and path A notebook to load
2016 */
2016 */
2017 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2017 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2018 var that = this;
2018 var that = this;
2019 this.notebook_name = notebook_name;
2019 this.notebook_name = notebook_name;
2020 this.notebook_path = notebook_path;
2020 this.notebook_path = notebook_path;
2021 // We do the call with settings so we can set cache to false.
2021 // We do the call with settings so we can set cache to false.
2022 var settings = {
2022 var settings = {
2023 processData : false,
2023 processData : false,
2024 cache : false,
2024 cache : false,
2025 type : "GET",
2025 type : "GET",
2026 dataType : "json",
2026 dataType : "json",
2027 success : $.proxy(this.load_notebook_success,this),
2027 success : $.proxy(this.load_notebook_success,this),
2028 error : $.proxy(this.load_notebook_error,this),
2028 error : $.proxy(this.load_notebook_error,this),
2029 };
2029 };
2030 $([IPython.events]).trigger('notebook_loading.Notebook');
2030 $([IPython.events]).trigger('notebook_loading.Notebook');
2031 var url = utils.url_join_encode(
2031 var url = utils.url_join_encode(
2032 this.base_url,
2032 this.base_url,
2033 'api/notebooks',
2033 'api/notebooks',
2034 this.notebook_path,
2034 this.notebook_path,
2035 this.notebook_name
2035 this.notebook_name
2036 );
2036 );
2037 $.ajax(url, settings);
2037 $.ajax(url, settings);
2038 };
2038 };
2039
2039
2040 /**
2040 /**
2041 * Success callback for loading a notebook from the server.
2041 * Success callback for loading a notebook from the server.
2042 *
2042 *
2043 * Load notebook data from the JSON response.
2043 * Load notebook data from the JSON response.
2044 *
2044 *
2045 * @method load_notebook_success
2045 * @method load_notebook_success
2046 * @param {Object} data JSON representation of a notebook
2046 * @param {Object} data JSON representation of a notebook
2047 * @param {String} status Description of response status
2047 * @param {String} status Description of response status
2048 * @param {jqXHR} xhr jQuery Ajax object
2048 * @param {jqXHR} xhr jQuery Ajax object
2049 */
2049 */
2050 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2050 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2051 this.fromJSON(data);
2051 this.fromJSON(data);
2052 if (this.ncells() === 0) {
2052 if (this.ncells() === 0) {
2053 this.insert_cell_below('code');
2053 this.insert_cell_below('code');
2054 this.edit_mode(0);
2054 this.edit_mode(0);
2055 } else {
2055 } else {
2056 this.select(0);
2056 this.select(0);
2057 this.handle_command_mode(this.get_cell(0));
2057 this.handle_command_mode(this.get_cell(0));
2058 }
2058 }
2059 this.set_dirty(false);
2059 this.set_dirty(false);
2060 this.scroll_to_top();
2060 this.scroll_to_top();
2061 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2061 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2062 var msg = "This notebook has been converted from an older " +
2062 var msg = "This notebook has been converted from an older " +
2063 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2063 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2064 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2064 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2065 "newer notebook format will be used and older versions of IPython " +
2065 "newer notebook format will be used and older versions of IPython " +
2066 "may not be able to read it. To keep the older version, close the " +
2066 "may not be able to read it. To keep the older version, close the " +
2067 "notebook without saving it.";
2067 "notebook without saving it.";
2068 IPython.dialog.modal({
2068 IPython.dialog.modal({
2069 title : "Notebook converted",
2069 title : "Notebook converted",
2070 body : msg,
2070 body : msg,
2071 buttons : {
2071 buttons : {
2072 OK : {
2072 OK : {
2073 class : "btn-primary"
2073 class : "btn-primary"
2074 }
2074 }
2075 }
2075 }
2076 });
2076 });
2077 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2077 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2078 var that = this;
2078 var that = this;
2079 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2079 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2080 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2080 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2081 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2081 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2082 this_vs + ". You can still work with this notebook, but some features " +
2082 this_vs + ". You can still work with this notebook, but some features " +
2083 "introduced in later notebook versions may not be available.";
2083 "introduced in later notebook versions may not be available.";
2084
2084
2085 IPython.dialog.modal({
2085 IPython.dialog.modal({
2086 title : "Newer Notebook",
2086 title : "Newer Notebook",
2087 body : msg,
2087 body : msg,
2088 buttons : {
2088 buttons : {
2089 OK : {
2089 OK : {
2090 class : "btn-danger"
2090 class : "btn-danger"
2091 }
2091 }
2092 }
2092 }
2093 });
2093 });
2094
2094
2095 }
2095 }
2096
2096
2097 // Create the session after the notebook is completely loaded to prevent
2097 // Create the session after the notebook is completely loaded to prevent
2098 // code execution upon loading, which is a security risk.
2098 // code execution upon loading, which is a security risk.
2099 if (this.session === null) {
2099 if (this.session === null) {
2100 this.start_session();
2100 this.start_session();
2101 }
2101 }
2102 // load our checkpoint list
2102 // load our checkpoint list
2103 this.list_checkpoints();
2103 this.list_checkpoints();
2104
2104
2105 // load toolbar state
2105 // load toolbar state
2106 if (this.metadata.celltoolbar) {
2106 if (this.metadata.celltoolbar) {
2107 IPython.CellToolbar.global_show();
2107 IPython.CellToolbar.global_show();
2108 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
2108 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
2109 } else {
2110 IPython.CellToolbar.global_hide();
2109 }
2111 }
2110
2112
2111 // now that we're fully loaded, it is safe to restore save functionality
2113 // now that we're fully loaded, it is safe to restore save functionality
2112 delete(this.save_notebook);
2114 delete(this.save_notebook);
2113 $([IPython.events]).trigger('notebook_loaded.Notebook');
2115 $([IPython.events]).trigger('notebook_loaded.Notebook');
2114 };
2116 };
2115
2117
2116 /**
2118 /**
2117 * Failure callback for loading a notebook from the server.
2119 * Failure callback for loading a notebook from the server.
2118 *
2120 *
2119 * @method load_notebook_error
2121 * @method load_notebook_error
2120 * @param {jqXHR} xhr jQuery Ajax object
2122 * @param {jqXHR} xhr jQuery Ajax object
2121 * @param {String} status Description of response status
2123 * @param {String} status Description of response status
2122 * @param {String} error HTTP error message
2124 * @param {String} error HTTP error message
2123 */
2125 */
2124 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2126 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2125 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2127 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2126 var msg;
2128 var msg;
2127 if (xhr.status === 400) {
2129 if (xhr.status === 400) {
2128 msg = error;
2130 msg = error;
2129 } else if (xhr.status === 500) {
2131 } else if (xhr.status === 500) {
2130 msg = "An unknown error occurred while loading this notebook. " +
2132 msg = "An unknown error occurred while loading this notebook. " +
2131 "This version can load notebook formats " +
2133 "This version can load notebook formats " +
2132 "v" + this.nbformat + " or earlier.";
2134 "v" + this.nbformat + " or earlier.";
2133 }
2135 }
2134 IPython.dialog.modal({
2136 IPython.dialog.modal({
2135 title: "Error loading notebook",
2137 title: "Error loading notebook",
2136 body : msg,
2138 body : msg,
2137 buttons : {
2139 buttons : {
2138 "OK": {}
2140 "OK": {}
2139 }
2141 }
2140 });
2142 });
2141 };
2143 };
2142
2144
2143 /********************* checkpoint-related *********************/
2145 /********************* checkpoint-related *********************/
2144
2146
2145 /**
2147 /**
2146 * Save the notebook then immediately create a checkpoint.
2148 * Save the notebook then immediately create a checkpoint.
2147 *
2149 *
2148 * @method save_checkpoint
2150 * @method save_checkpoint
2149 */
2151 */
2150 Notebook.prototype.save_checkpoint = function () {
2152 Notebook.prototype.save_checkpoint = function () {
2151 this._checkpoint_after_save = true;
2153 this._checkpoint_after_save = true;
2152 this.save_notebook();
2154 this.save_notebook();
2153 };
2155 };
2154
2156
2155 /**
2157 /**
2156 * Add a checkpoint for this notebook.
2158 * Add a checkpoint for this notebook.
2157 * for use as a callback from checkpoint creation.
2159 * for use as a callback from checkpoint creation.
2158 *
2160 *
2159 * @method add_checkpoint
2161 * @method add_checkpoint
2160 */
2162 */
2161 Notebook.prototype.add_checkpoint = function (checkpoint) {
2163 Notebook.prototype.add_checkpoint = function (checkpoint) {
2162 var found = false;
2164 var found = false;
2163 for (var i = 0; i < this.checkpoints.length; i++) {
2165 for (var i = 0; i < this.checkpoints.length; i++) {
2164 var existing = this.checkpoints[i];
2166 var existing = this.checkpoints[i];
2165 if (existing.id == checkpoint.id) {
2167 if (existing.id == checkpoint.id) {
2166 found = true;
2168 found = true;
2167 this.checkpoints[i] = checkpoint;
2169 this.checkpoints[i] = checkpoint;
2168 break;
2170 break;
2169 }
2171 }
2170 }
2172 }
2171 if (!found) {
2173 if (!found) {
2172 this.checkpoints.push(checkpoint);
2174 this.checkpoints.push(checkpoint);
2173 }
2175 }
2174 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2176 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2175 };
2177 };
2176
2178
2177 /**
2179 /**
2178 * List checkpoints for this notebook.
2180 * List checkpoints for this notebook.
2179 *
2181 *
2180 * @method list_checkpoints
2182 * @method list_checkpoints
2181 */
2183 */
2182 Notebook.prototype.list_checkpoints = function () {
2184 Notebook.prototype.list_checkpoints = function () {
2183 var url = utils.url_join_encode(
2185 var url = utils.url_join_encode(
2184 this.base_url,
2186 this.base_url,
2185 'api/notebooks',
2187 'api/notebooks',
2186 this.notebook_path,
2188 this.notebook_path,
2187 this.notebook_name,
2189 this.notebook_name,
2188 'checkpoints'
2190 'checkpoints'
2189 );
2191 );
2190 $.get(url).done(
2192 $.get(url).done(
2191 $.proxy(this.list_checkpoints_success, this)
2193 $.proxy(this.list_checkpoints_success, this)
2192 ).fail(
2194 ).fail(
2193 $.proxy(this.list_checkpoints_error, this)
2195 $.proxy(this.list_checkpoints_error, this)
2194 );
2196 );
2195 };
2197 };
2196
2198
2197 /**
2199 /**
2198 * Success callback for listing checkpoints.
2200 * Success callback for listing checkpoints.
2199 *
2201 *
2200 * @method list_checkpoint_success
2202 * @method list_checkpoint_success
2201 * @param {Object} data JSON representation of a checkpoint
2203 * @param {Object} data JSON representation of a checkpoint
2202 * @param {String} status Description of response status
2204 * @param {String} status Description of response status
2203 * @param {jqXHR} xhr jQuery Ajax object
2205 * @param {jqXHR} xhr jQuery Ajax object
2204 */
2206 */
2205 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2207 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2206 data = $.parseJSON(data);
2208 data = $.parseJSON(data);
2207 this.checkpoints = data;
2209 this.checkpoints = data;
2208 if (data.length) {
2210 if (data.length) {
2209 this.last_checkpoint = data[data.length - 1];
2211 this.last_checkpoint = data[data.length - 1];
2210 } else {
2212 } else {
2211 this.last_checkpoint = null;
2213 this.last_checkpoint = null;
2212 }
2214 }
2213 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2215 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2214 };
2216 };
2215
2217
2216 /**
2218 /**
2217 * Failure callback for listing a checkpoint.
2219 * Failure callback for listing a checkpoint.
2218 *
2220 *
2219 * @method list_checkpoint_error
2221 * @method list_checkpoint_error
2220 * @param {jqXHR} xhr jQuery Ajax object
2222 * @param {jqXHR} xhr jQuery Ajax object
2221 * @param {String} status Description of response status
2223 * @param {String} status Description of response status
2222 * @param {String} error_msg HTTP error message
2224 * @param {String} error_msg HTTP error message
2223 */
2225 */
2224 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2226 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2225 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2227 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2226 };
2228 };
2227
2229
2228 /**
2230 /**
2229 * Create a checkpoint of this notebook on the server from the most recent save.
2231 * Create a checkpoint of this notebook on the server from the most recent save.
2230 *
2232 *
2231 * @method create_checkpoint
2233 * @method create_checkpoint
2232 */
2234 */
2233 Notebook.prototype.create_checkpoint = function () {
2235 Notebook.prototype.create_checkpoint = function () {
2234 var url = utils.url_join_encode(
2236 var url = utils.url_join_encode(
2235 this.base_url,
2237 this.base_url,
2236 'api/notebooks',
2238 'api/notebooks',
2237 this.notebook_path,
2239 this.notebook_path,
2238 this.notebook_name,
2240 this.notebook_name,
2239 'checkpoints'
2241 'checkpoints'
2240 );
2242 );
2241 $.post(url).done(
2243 $.post(url).done(
2242 $.proxy(this.create_checkpoint_success, this)
2244 $.proxy(this.create_checkpoint_success, this)
2243 ).fail(
2245 ).fail(
2244 $.proxy(this.create_checkpoint_error, this)
2246 $.proxy(this.create_checkpoint_error, this)
2245 );
2247 );
2246 };
2248 };
2247
2249
2248 /**
2250 /**
2249 * Success callback for creating a checkpoint.
2251 * Success callback for creating a checkpoint.
2250 *
2252 *
2251 * @method create_checkpoint_success
2253 * @method create_checkpoint_success
2252 * @param {Object} data JSON representation of a checkpoint
2254 * @param {Object} data JSON representation of a checkpoint
2253 * @param {String} status Description of response status
2255 * @param {String} status Description of response status
2254 * @param {jqXHR} xhr jQuery Ajax object
2256 * @param {jqXHR} xhr jQuery Ajax object
2255 */
2257 */
2256 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2258 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2257 data = $.parseJSON(data);
2259 data = $.parseJSON(data);
2258 this.add_checkpoint(data);
2260 this.add_checkpoint(data);
2259 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2261 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2260 };
2262 };
2261
2263
2262 /**
2264 /**
2263 * Failure callback for creating a checkpoint.
2265 * Failure callback for creating a checkpoint.
2264 *
2266 *
2265 * @method create_checkpoint_error
2267 * @method create_checkpoint_error
2266 * @param {jqXHR} xhr jQuery Ajax object
2268 * @param {jqXHR} xhr jQuery Ajax object
2267 * @param {String} status Description of response status
2269 * @param {String} status Description of response status
2268 * @param {String} error_msg HTTP error message
2270 * @param {String} error_msg HTTP error message
2269 */
2271 */
2270 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2272 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2271 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2273 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2272 };
2274 };
2273
2275
2274 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2276 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2275 var that = this;
2277 var that = this;
2276 checkpoint = checkpoint || this.last_checkpoint;
2278 checkpoint = checkpoint || this.last_checkpoint;
2277 if ( ! checkpoint ) {
2279 if ( ! checkpoint ) {
2278 console.log("restore dialog, but no checkpoint to restore to!");
2280 console.log("restore dialog, but no checkpoint to restore to!");
2279 return;
2281 return;
2280 }
2282 }
2281 var body = $('<div/>').append(
2283 var body = $('<div/>').append(
2282 $('<p/>').addClass("p-space").text(
2284 $('<p/>').addClass("p-space").text(
2283 "Are you sure you want to revert the notebook to " +
2285 "Are you sure you want to revert the notebook to " +
2284 "the latest checkpoint?"
2286 "the latest checkpoint?"
2285 ).append(
2287 ).append(
2286 $("<strong/>").text(
2288 $("<strong/>").text(
2287 " This cannot be undone."
2289 " This cannot be undone."
2288 )
2290 )
2289 )
2291 )
2290 ).append(
2292 ).append(
2291 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2293 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2292 ).append(
2294 ).append(
2293 $('<p/>').addClass("p-space").text(
2295 $('<p/>').addClass("p-space").text(
2294 Date(checkpoint.last_modified)
2296 Date(checkpoint.last_modified)
2295 ).css("text-align", "center")
2297 ).css("text-align", "center")
2296 );
2298 );
2297
2299
2298 IPython.dialog.modal({
2300 IPython.dialog.modal({
2299 title : "Revert notebook to checkpoint",
2301 title : "Revert notebook to checkpoint",
2300 body : body,
2302 body : body,
2301 buttons : {
2303 buttons : {
2302 Revert : {
2304 Revert : {
2303 class : "btn-danger",
2305 class : "btn-danger",
2304 click : function () {
2306 click : function () {
2305 that.restore_checkpoint(checkpoint.id);
2307 that.restore_checkpoint(checkpoint.id);
2306 }
2308 }
2307 },
2309 },
2308 Cancel : {}
2310 Cancel : {}
2309 }
2311 }
2310 });
2312 });
2311 };
2313 };
2312
2314
2313 /**
2315 /**
2314 * Restore the notebook to a checkpoint state.
2316 * Restore the notebook to a checkpoint state.
2315 *
2317 *
2316 * @method restore_checkpoint
2318 * @method restore_checkpoint
2317 * @param {String} checkpoint ID
2319 * @param {String} checkpoint ID
2318 */
2320 */
2319 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2321 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2320 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2322 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2321 var url = utils.url_join_encode(
2323 var url = utils.url_join_encode(
2322 this.base_url,
2324 this.base_url,
2323 'api/notebooks',
2325 'api/notebooks',
2324 this.notebook_path,
2326 this.notebook_path,
2325 this.notebook_name,
2327 this.notebook_name,
2326 'checkpoints',
2328 'checkpoints',
2327 checkpoint
2329 checkpoint
2328 );
2330 );
2329 $.post(url).done(
2331 $.post(url).done(
2330 $.proxy(this.restore_checkpoint_success, this)
2332 $.proxy(this.restore_checkpoint_success, this)
2331 ).fail(
2333 ).fail(
2332 $.proxy(this.restore_checkpoint_error, this)
2334 $.proxy(this.restore_checkpoint_error, this)
2333 );
2335 );
2334 };
2336 };
2335
2337
2336 /**
2338 /**
2337 * Success callback for restoring a notebook to a checkpoint.
2339 * Success callback for restoring a notebook to a checkpoint.
2338 *
2340 *
2339 * @method restore_checkpoint_success
2341 * @method restore_checkpoint_success
2340 * @param {Object} data (ignored, should be empty)
2342 * @param {Object} data (ignored, should be empty)
2341 * @param {String} status Description of response status
2343 * @param {String} status Description of response status
2342 * @param {jqXHR} xhr jQuery Ajax object
2344 * @param {jqXHR} xhr jQuery Ajax object
2343 */
2345 */
2344 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2346 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2345 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2347 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2346 this.load_notebook(this.notebook_name, this.notebook_path);
2348 this.load_notebook(this.notebook_name, this.notebook_path);
2347 };
2349 };
2348
2350
2349 /**
2351 /**
2350 * Failure callback for restoring a notebook to a checkpoint.
2352 * Failure callback for restoring a notebook to a checkpoint.
2351 *
2353 *
2352 * @method restore_checkpoint_error
2354 * @method restore_checkpoint_error
2353 * @param {jqXHR} xhr jQuery Ajax object
2355 * @param {jqXHR} xhr jQuery Ajax object
2354 * @param {String} status Description of response status
2356 * @param {String} status Description of response status
2355 * @param {String} error_msg HTTP error message
2357 * @param {String} error_msg HTTP error message
2356 */
2358 */
2357 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2359 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2358 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2360 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2359 };
2361 };
2360
2362
2361 /**
2363 /**
2362 * Delete a notebook checkpoint.
2364 * Delete a notebook checkpoint.
2363 *
2365 *
2364 * @method delete_checkpoint
2366 * @method delete_checkpoint
2365 * @param {String} checkpoint ID
2367 * @param {String} checkpoint ID
2366 */
2368 */
2367 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2369 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2368 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2370 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2369 var url = utils.url_join_encode(
2371 var url = utils.url_join_encode(
2370 this.base_url,
2372 this.base_url,
2371 'api/notebooks',
2373 'api/notebooks',
2372 this.notebook_path,
2374 this.notebook_path,
2373 this.notebook_name,
2375 this.notebook_name,
2374 'checkpoints',
2376 'checkpoints',
2375 checkpoint
2377 checkpoint
2376 );
2378 );
2377 $.ajax(url, {
2379 $.ajax(url, {
2378 type: 'DELETE',
2380 type: 'DELETE',
2379 success: $.proxy(this.delete_checkpoint_success, this),
2381 success: $.proxy(this.delete_checkpoint_success, this),
2380 error: $.proxy(this.delete_checkpoint_error, this)
2382 error: $.proxy(this.delete_checkpoint_error, this)
2381 });
2383 });
2382 };
2384 };
2383
2385
2384 /**
2386 /**
2385 * Success callback for deleting a notebook checkpoint
2387 * Success callback for deleting a notebook checkpoint
2386 *
2388 *
2387 * @method delete_checkpoint_success
2389 * @method delete_checkpoint_success
2388 * @param {Object} data (ignored, should be empty)
2390 * @param {Object} data (ignored, should be empty)
2389 * @param {String} status Description of response status
2391 * @param {String} status Description of response status
2390 * @param {jqXHR} xhr jQuery Ajax object
2392 * @param {jqXHR} xhr jQuery Ajax object
2391 */
2393 */
2392 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2394 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2393 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2395 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2394 this.load_notebook(this.notebook_name, this.notebook_path);
2396 this.load_notebook(this.notebook_name, this.notebook_path);
2395 };
2397 };
2396
2398
2397 /**
2399 /**
2398 * Failure callback for deleting a notebook checkpoint.
2400 * Failure callback for deleting a notebook checkpoint.
2399 *
2401 *
2400 * @method delete_checkpoint_error
2402 * @method delete_checkpoint_error
2401 * @param {jqXHR} xhr jQuery Ajax object
2403 * @param {jqXHR} xhr jQuery Ajax object
2402 * @param {String} status Description of response status
2404 * @param {String} status Description of response status
2403 * @param {String} error_msg HTTP error message
2405 * @param {String} error_msg HTTP error message
2404 */
2406 */
2405 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2407 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2406 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2408 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2407 };
2409 };
2408
2410
2409
2411
2410 IPython.Notebook = Notebook;
2412 IPython.Notebook = Notebook;
2411
2413
2412
2414
2413 return IPython;
2415 return IPython;
2414
2416
2415 }(IPython));
2417 }(IPython));
2416
2418
General Comments 0
You need to be logged in to leave comments. Login now