##// END OF EJS Templates
Merge pull request #4929 from ellisonbg/modal-fixes...
Min RK -
r14980:9b8c058d merge
parent child Browse files
Show More
@@ -1,494 +1,502 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Cell
9 // Cell
10 //============================================================================
10 //============================================================================
11 /**
11 /**
12 * An extendable module that provide base functionnality to create cell for notebook.
12 * An extendable module that provide base functionnality to create cell for notebook.
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule Cell
15 * @submodule Cell
16 */
16 */
17
17
18 var IPython = (function (IPython) {
18 var IPython = (function (IPython) {
19 "use strict";
19 "use strict";
20
20
21 var utils = IPython.utils;
21 var utils = IPython.utils;
22
22
23 /**
23 /**
24 * The Base `Cell` class from which to inherit
24 * The Base `Cell` class from which to inherit
25 * @class Cell
25 * @class Cell
26 **/
26 **/
27
27
28 /*
28 /*
29 * @constructor
29 * @constructor
30 *
30 *
31 * @param {object|undefined} [options]
31 * @param {object|undefined} [options]
32 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
32 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
33 */
33 */
34 var Cell = function (options) {
34 var Cell = function (options) {
35
35
36 options = this.mergeopt(Cell, options);
36 options = this.mergeopt(Cell, options);
37 // superclass default overwrite our default
37 // superclass default overwrite our default
38
38
39 this.placeholder = options.placeholder || '';
39 this.placeholder = options.placeholder || '';
40 this.read_only = options.cm_config.readOnly;
40 this.read_only = options.cm_config.readOnly;
41 this.selected = false;
41 this.selected = false;
42 this.rendered = false;
42 this.rendered = false;
43 this.mode = 'command';
43 this.mode = 'command';
44 this.metadata = {};
44 this.metadata = {};
45 // load this from metadata later ?
45 // load this from metadata later ?
46 this.user_highlight = 'auto';
46 this.user_highlight = 'auto';
47 this.cm_config = options.cm_config;
47 this.cm_config = options.cm_config;
48 this.cell_id = utils.uuid();
48 this.cell_id = utils.uuid();
49 this._options = options;
49 this._options = options;
50
50
51 // For JS VM engines optimisation, attributes should be all set (even
51 // For JS VM engines optimisation, attributes should be all set (even
52 // to null) in the constructor, and if possible, if different subclass
52 // to null) in the constructor, and if possible, if different subclass
53 // have new attributes with same name, they should be created in the
53 // have new attributes with same name, they should be created in the
54 // same order. Easiest is to create and set to null in parent class.
54 // same order. Easiest is to create and set to null in parent class.
55
55
56 this.element = null;
56 this.element = null;
57 this.cell_type = this.cell_type || null;
57 this.cell_type = this.cell_type || null;
58 this.code_mirror = null;
58 this.code_mirror = null;
59
59
60
60
61 this.create_element();
61 this.create_element();
62 if (this.element !== null) {
62 if (this.element !== null) {
63 this.element.data("cell", this);
63 this.element.data("cell", this);
64 this.bind_events();
64 this.bind_events();
65 this.init_classes();
65 this.init_classes();
66 }
66 }
67 };
67 };
68
68
69 Cell.options_default = {
69 Cell.options_default = {
70 cm_config : {
70 cm_config : {
71 indentUnit : 4,
71 indentUnit : 4,
72 readOnly: false,
72 readOnly: false,
73 theme: "default"
73 theme: "default"
74 }
74 }
75 };
75 };
76
76
77 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
77 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
78 // by disabling drag/drop altogether on Safari
78 // by disabling drag/drop altogether on Safari
79 // https://github.com/marijnh/CodeMirror/issues/332
79 // https://github.com/marijnh/CodeMirror/issues/332
80
80
81 if (utils.browser[0] == "Safari") {
81 if (utils.browser[0] == "Safari") {
82 Cell.options_default.cm_config.dragDrop = false;
82 Cell.options_default.cm_config.dragDrop = false;
83 }
83 }
84
84
85 Cell.prototype.mergeopt = function(_class, options, overwrite){
85 Cell.prototype.mergeopt = function(_class, options, overwrite){
86 options = options || {};
86 options = options || {};
87 overwrite = overwrite || {};
87 overwrite = overwrite || {};
88 return $.extend(true, {}, _class.options_default, options, overwrite)
88 return $.extend(true, {}, _class.options_default, options, overwrite)
89
89
90 }
90 }
91
91
92
92
93
93
94 /**
94 /**
95 * Empty. Subclasses must implement create_element.
95 * Empty. Subclasses must implement create_element.
96 * This should contain all the code to create the DOM element in notebook
96 * This should contain all the code to create the DOM element in notebook
97 * and will be called by Base Class constructor.
97 * and will be called by Base Class constructor.
98 * @method create_element
98 * @method create_element
99 */
99 */
100 Cell.prototype.create_element = function () {
100 Cell.prototype.create_element = function () {
101 };
101 };
102
102
103 Cell.prototype.init_classes = function () {
103 Cell.prototype.init_classes = function () {
104 // Call after this.element exists to initialize the css classes
104 // Call after this.element exists to initialize the css classes
105 // related to selected, rendered and mode.
105 // related to selected, rendered and mode.
106 if (this.selected) {
106 if (this.selected) {
107 this.element.addClass('selected');
107 this.element.addClass('selected');
108 } else {
108 } else {
109 this.element.addClass('unselected');
109 this.element.addClass('unselected');
110 }
110 }
111 if (this.rendered) {
111 if (this.rendered) {
112 this.element.addClass('rendered');
112 this.element.addClass('rendered');
113 } else {
113 } else {
114 this.element.addClass('unrendered');
114 this.element.addClass('unrendered');
115 }
115 }
116 if (this.mode === 'edit') {
116 if (this.mode === 'edit') {
117 this.element.addClass('edit_mode');
117 this.element.addClass('edit_mode');
118 } else {
118 } else {
119 this.element.addClass('command_mode');
119 this.element.addClass('command_mode');
120 }
120 }
121 }
121 }
122
122
123
123
124 /**
124 /**
125 * Subclasses can implement override bind_events.
125 * Subclasses can implement override bind_events.
126 * Be carefull to call the parent method when overwriting as it fires event.
126 * Be carefull to call the parent method when overwriting as it fires event.
127 * this will be triggerd after create_element in constructor.
127 * this will be triggerd after create_element in constructor.
128 * @method bind_events
128 * @method bind_events
129 */
129 */
130 Cell.prototype.bind_events = function () {
130 Cell.prototype.bind_events = function () {
131 var that = this;
131 var that = this;
132 // We trigger events so that Cell doesn't have to depend on Notebook.
132 // We trigger events so that Cell doesn't have to depend on Notebook.
133 that.element.click(function (event) {
133 that.element.click(function (event) {
134 if (!that.selected) {
134 if (!that.selected) {
135 $([IPython.events]).trigger('select.Cell', {'cell':that});
135 $([IPython.events]).trigger('select.Cell', {'cell':that});
136 };
136 };
137 });
137 });
138 that.element.focusin(function (event) {
138 that.element.focusin(function (event) {
139 if (!that.selected) {
139 if (!that.selected) {
140 $([IPython.events]).trigger('select.Cell', {'cell':that});
140 $([IPython.events]).trigger('select.Cell', {'cell':that});
141 };
141 };
142 });
142 });
143 if (this.code_mirror) {
143 if (this.code_mirror) {
144 this.code_mirror.on("change", function(cm, change) {
144 this.code_mirror.on("change", function(cm, change) {
145 $([IPython.events]).trigger("set_dirty.Notebook", {value: true});
145 $([IPython.events]).trigger("set_dirty.Notebook", {value: true});
146 });
146 });
147 }
147 }
148 if (this.code_mirror) {
148 if (this.code_mirror) {
149 this.code_mirror.on('focus', function(cm, change) {
149 this.code_mirror.on('focus', function(cm, change) {
150 $([IPython.events]).trigger('edit_mode.Cell', {cell: that});
150 $([IPython.events]).trigger('edit_mode.Cell', {cell: that});
151 });
151 });
152 }
152 }
153 if (this.code_mirror) {
153 if (this.code_mirror) {
154 this.code_mirror.on('blur', function(cm, change) {
154 this.code_mirror.on('blur', function(cm, change) {
155 if (that.mode === 'edit') {
155 if (that.mode === 'edit') {
156 setTimeout(function () {
156 setTimeout(function () {
157 var isf = IPython.utils.is_focused;
157 var isf = IPython.utils.is_focused;
158 var trigger = true;
158 var trigger = true;
159 if (isf('div#tooltip') || isf('div.completions')) {
159 if (isf('div#tooltip') || isf('div.completions')) {
160 trigger = false;
160 trigger = false;
161 }
161 }
162 if (trigger) {
162 if (trigger) {
163 $([IPython.events]).trigger('command_mode.Cell', {cell: that});
163 $([IPython.events]).trigger('command_mode.Cell', {cell: that});
164 }
164 }
165 }, 1);
165 }, 1);
166 }
166 }
167 });
167 });
168 }
168 }
169 };
169 };
170
170
171 /**
171 /**
172 * Triger typsetting of math by mathjax on current cell element
172 * Triger typsetting of math by mathjax on current cell element
173 * @method typeset
173 * @method typeset
174 */
174 */
175 Cell.prototype.typeset = function () {
175 Cell.prototype.typeset = function () {
176 if (window.MathJax) {
176 if (window.MathJax) {
177 var cell_math = this.element.get(0);
177 var cell_math = this.element.get(0);
178 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
178 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
179 }
179 }
180 };
180 };
181
181
182 /**
182 /**
183 * handle cell level logic when a cell is selected
183 * handle cell level logic when a cell is selected
184 * @method select
184 * @method select
185 * @return is the action being taken
185 * @return is the action being taken
186 */
186 */
187 Cell.prototype.select = function () {
187 Cell.prototype.select = function () {
188 if (!this.selected) {
188 if (!this.selected) {
189 this.element.addClass('selected');
189 this.element.addClass('selected');
190 this.element.removeClass('unselected');
190 this.element.removeClass('unselected');
191 this.selected = true;
191 this.selected = true;
192 return true;
192 return true;
193 } else {
193 } else {
194 return false;
194 return false;
195 }
195 }
196 };
196 };
197
197
198 /**
198 /**
199 * handle cell level logic when a cell is unselected
199 * handle cell level logic when a cell is unselected
200 * @method unselect
200 * @method unselect
201 * @return is the action being taken
201 * @return is the action being taken
202 */
202 */
203 Cell.prototype.unselect = function () {
203 Cell.prototype.unselect = function () {
204 if (this.selected) {
204 if (this.selected) {
205 this.element.addClass('unselected');
205 this.element.addClass('unselected');
206 this.element.removeClass('selected');
206 this.element.removeClass('selected');
207 this.selected = false;
207 this.selected = false;
208 return true;
208 return true;
209 } else {
209 } else {
210 return false;
210 return false;
211 }
211 }
212 };
212 };
213
213
214 /**
214 /**
215 * handle cell level logic when a cell is rendered
215 * handle cell level logic when a cell is rendered
216 * @method render
216 * @method render
217 * @return is the action being taken
217 * @return is the action being taken
218 */
218 */
219 Cell.prototype.render = function () {
219 Cell.prototype.render = function () {
220 if (!this.rendered) {
220 if (!this.rendered) {
221 this.element.addClass('rendered');
221 this.element.addClass('rendered');
222 this.element.removeClass('unrendered');
222 this.element.removeClass('unrendered');
223 this.rendered = true;
223 this.rendered = true;
224 return true;
224 return true;
225 } else {
225 } else {
226 return false;
226 return false;
227 }
227 }
228 };
228 };
229
229
230 /**
230 /**
231 * handle cell level logic when a cell is unrendered
231 * handle cell level logic when a cell is unrendered
232 * @method unrender
232 * @method unrender
233 * @return is the action being taken
233 * @return is the action being taken
234 */
234 */
235 Cell.prototype.unrender = function () {
235 Cell.prototype.unrender = function () {
236 if (this.rendered) {
236 if (this.rendered) {
237 this.element.addClass('unrendered');
237 this.element.addClass('unrendered');
238 this.element.removeClass('rendered');
238 this.element.removeClass('rendered');
239 this.rendered = false;
239 this.rendered = false;
240 return true;
240 return true;
241 } else {
241 } else {
242 return false;
242 return false;
243 }
243 }
244 };
244 };
245
245
246 /**
246 /**
247 * enter the command mode for the cell
247 * enter the command mode for the cell
248 * @method command_mode
248 * @method command_mode
249 * @return is the action being taken
249 * @return is the action being taken
250 */
250 */
251 Cell.prototype.command_mode = function () {
251 Cell.prototype.command_mode = function () {
252 if (this.mode !== 'command') {
252 if (this.mode !== 'command') {
253 this.element.addClass('command_mode');
253 this.element.addClass('command_mode');
254 this.element.removeClass('edit_mode');
254 this.element.removeClass('edit_mode');
255 this.mode = 'command';
255 this.mode = 'command';
256 return true;
256 return true;
257 } else {
257 } else {
258 return false;
258 return false;
259 }
259 }
260 };
260 };
261
261
262 /**
262 /**
263 * enter the edit mode for the cell
263 * enter the edit mode for the cell
264 * @method command_mode
264 * @method command_mode
265 * @return is the action being taken
265 * @return is the action being taken
266 */
266 */
267 Cell.prototype.edit_mode = function () {
267 Cell.prototype.edit_mode = function () {
268 if (this.mode !== 'edit') {
268 if (this.mode !== 'edit') {
269 this.element.addClass('edit_mode');
269 this.element.addClass('edit_mode');
270 this.element.removeClass('command_mode');
270 this.element.removeClass('command_mode');
271 this.mode = 'edit';
271 this.mode = 'edit';
272 return true;
272 return true;
273 } else {
273 } else {
274 return false;
274 return false;
275 }
275 }
276 }
276 }
277
277
278 /**
278 /**
279 * Focus the cell in the DOM sense
279 * Focus the cell in the DOM sense
280 * @method focus_cell
280 * @method focus_cell
281 */
281 */
282 Cell.prototype.focus_cell = function () {
282 Cell.prototype.focus_cell = function () {
283 this.element.focus();
283 this.element.focus();
284 }
284 }
285
285
286 /**
286 /**
287 * Focus the editor area so a user can type
287 * Focus the editor area so a user can type
288 * @method focus_editor
288 * @method focus_editor
289 */
289 */
290 Cell.prototype.focus_editor = function () {
290 Cell.prototype.focus_editor = function () {
291 var that = this;
291 this.refresh();
292 this.refresh();
292 this.code_mirror.focus();
293 // Only focus the CM editor if it is not focused already. This prevents jumps
294 // related to the previous prompt position.
295 setTimeout(function () {
296 var isf = IPython.utils.is_focused;
297 if (!isf(that.element.find('div.CodeMirror'))) {
298 that.code_mirror.focus();
299 }
300 }, 1);
293 }
301 }
294
302
295 /**
303 /**
296 * Refresh codemirror instance
304 * Refresh codemirror instance
297 * @method refresh
305 * @method refresh
298 */
306 */
299 Cell.prototype.refresh = function () {
307 Cell.prototype.refresh = function () {
300 this.code_mirror.refresh();
308 this.code_mirror.refresh();
301 };
309 };
302
310
303 /**
311 /**
304 * should be overritten by subclass
312 * should be overritten by subclass
305 * @method get_text
313 * @method get_text
306 */
314 */
307 Cell.prototype.get_text = function () {
315 Cell.prototype.get_text = function () {
308 };
316 };
309
317
310 /**
318 /**
311 * should be overritten by subclass
319 * should be overritten by subclass
312 * @method set_text
320 * @method set_text
313 * @param {string} text
321 * @param {string} text
314 */
322 */
315 Cell.prototype.set_text = function (text) {
323 Cell.prototype.set_text = function (text) {
316 };
324 };
317
325
318 /**
326 /**
319 * should be overritten by subclass
327 * should be overritten by subclass
320 * serialise cell to json.
328 * serialise cell to json.
321 * @method toJSON
329 * @method toJSON
322 **/
330 **/
323 Cell.prototype.toJSON = function () {
331 Cell.prototype.toJSON = function () {
324 var data = {};
332 var data = {};
325 data.metadata = this.metadata;
333 data.metadata = this.metadata;
326 data.cell_type = this.cell_type;
334 data.cell_type = this.cell_type;
327 return data;
335 return data;
328 };
336 };
329
337
330
338
331 /**
339 /**
332 * should be overritten by subclass
340 * should be overritten by subclass
333 * @method fromJSON
341 * @method fromJSON
334 **/
342 **/
335 Cell.prototype.fromJSON = function (data) {
343 Cell.prototype.fromJSON = function (data) {
336 if (data.metadata !== undefined) {
344 if (data.metadata !== undefined) {
337 this.metadata = data.metadata;
345 this.metadata = data.metadata;
338 }
346 }
339 this.celltoolbar.rebuild();
347 this.celltoolbar.rebuild();
340 };
348 };
341
349
342
350
343 /**
351 /**
344 * can the cell be split into two cells
352 * can the cell be split into two cells
345 * @method is_splittable
353 * @method is_splittable
346 **/
354 **/
347 Cell.prototype.is_splittable = function () {
355 Cell.prototype.is_splittable = function () {
348 return true;
356 return true;
349 };
357 };
350
358
351
359
352 /**
360 /**
353 * can the cell be merged with other cells
361 * can the cell be merged with other cells
354 * @method is_mergeable
362 * @method is_mergeable
355 **/
363 **/
356 Cell.prototype.is_mergeable = function () {
364 Cell.prototype.is_mergeable = function () {
357 return true;
365 return true;
358 };
366 };
359
367
360
368
361 /**
369 /**
362 * @return {String} - the text before the cursor
370 * @return {String} - the text before the cursor
363 * @method get_pre_cursor
371 * @method get_pre_cursor
364 **/
372 **/
365 Cell.prototype.get_pre_cursor = function () {
373 Cell.prototype.get_pre_cursor = function () {
366 var cursor = this.code_mirror.getCursor();
374 var cursor = this.code_mirror.getCursor();
367 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
375 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
368 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
376 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
369 return text;
377 return text;
370 }
378 }
371
379
372
380
373 /**
381 /**
374 * @return {String} - the text after the cursor
382 * @return {String} - the text after the cursor
375 * @method get_post_cursor
383 * @method get_post_cursor
376 **/
384 **/
377 Cell.prototype.get_post_cursor = function () {
385 Cell.prototype.get_post_cursor = function () {
378 var cursor = this.code_mirror.getCursor();
386 var cursor = this.code_mirror.getCursor();
379 var last_line_num = this.code_mirror.lineCount()-1;
387 var last_line_num = this.code_mirror.lineCount()-1;
380 var last_line_len = this.code_mirror.getLine(last_line_num).length;
388 var last_line_len = this.code_mirror.getLine(last_line_num).length;
381 var end = {line:last_line_num, ch:last_line_len}
389 var end = {line:last_line_num, ch:last_line_len}
382 var text = this.code_mirror.getRange(cursor, end);
390 var text = this.code_mirror.getRange(cursor, end);
383 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
391 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
384 return text;
392 return text;
385 };
393 };
386
394
387 /**
395 /**
388 * Show/Hide CodeMirror LineNumber
396 * Show/Hide CodeMirror LineNumber
389 * @method show_line_numbers
397 * @method show_line_numbers
390 *
398 *
391 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
399 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
392 **/
400 **/
393 Cell.prototype.show_line_numbers = function (value) {
401 Cell.prototype.show_line_numbers = function (value) {
394 this.code_mirror.setOption('lineNumbers', value);
402 this.code_mirror.setOption('lineNumbers', value);
395 this.code_mirror.refresh();
403 this.code_mirror.refresh();
396 };
404 };
397
405
398 /**
406 /**
399 * Toggle CodeMirror LineNumber
407 * Toggle CodeMirror LineNumber
400 * @method toggle_line_numbers
408 * @method toggle_line_numbers
401 **/
409 **/
402 Cell.prototype.toggle_line_numbers = function () {
410 Cell.prototype.toggle_line_numbers = function () {
403 var val = this.code_mirror.getOption('lineNumbers');
411 var val = this.code_mirror.getOption('lineNumbers');
404 this.show_line_numbers(!val);
412 this.show_line_numbers(!val);
405 };
413 };
406
414
407 /**
415 /**
408 * Force codemirror highlight mode
416 * Force codemirror highlight mode
409 * @method force_highlight
417 * @method force_highlight
410 * @param {object} - CodeMirror mode
418 * @param {object} - CodeMirror mode
411 **/
419 **/
412 Cell.prototype.force_highlight = function(mode) {
420 Cell.prototype.force_highlight = function(mode) {
413 this.user_highlight = mode;
421 this.user_highlight = mode;
414 this.auto_highlight();
422 this.auto_highlight();
415 };
423 };
416
424
417 /**
425 /**
418 * Try to autodetect cell highlight mode, or use selected mode
426 * Try to autodetect cell highlight mode, or use selected mode
419 * @methods _auto_highlight
427 * @methods _auto_highlight
420 * @private
428 * @private
421 * @param {String|object|undefined} - CodeMirror mode | 'auto'
429 * @param {String|object|undefined} - CodeMirror mode | 'auto'
422 **/
430 **/
423 Cell.prototype._auto_highlight = function (modes) {
431 Cell.prototype._auto_highlight = function (modes) {
424 //Here we handle manually selected modes
432 //Here we handle manually selected modes
425 if( this.user_highlight != undefined && this.user_highlight != 'auto' )
433 if( this.user_highlight != undefined && this.user_highlight != 'auto' )
426 {
434 {
427 var mode = this.user_highlight;
435 var mode = this.user_highlight;
428 CodeMirror.autoLoadMode(this.code_mirror, mode);
436 CodeMirror.autoLoadMode(this.code_mirror, mode);
429 this.code_mirror.setOption('mode', mode);
437 this.code_mirror.setOption('mode', mode);
430 return;
438 return;
431 }
439 }
432 var current_mode = this.code_mirror.getOption('mode', mode);
440 var current_mode = this.code_mirror.getOption('mode', mode);
433 var first_line = this.code_mirror.getLine(0);
441 var first_line = this.code_mirror.getLine(0);
434 // loop on every pairs
442 // loop on every pairs
435 for( var mode in modes) {
443 for( var mode in modes) {
436 var regs = modes[mode]['reg'];
444 var regs = modes[mode]['reg'];
437 // only one key every time but regexp can't be keys...
445 // only one key every time but regexp can't be keys...
438 for(var i=0; i<regs.length; i++) {
446 for(var i=0; i<regs.length; i++) {
439 // here we handle non magic_modes
447 // here we handle non magic_modes
440 if(first_line.match(regs[i]) != null) {
448 if(first_line.match(regs[i]) != null) {
441 if(current_mode == mode){
449 if(current_mode == mode){
442 return;
450 return;
443 }
451 }
444 if (mode.search('magic_') != 0) {
452 if (mode.search('magic_') != 0) {
445 this.code_mirror.setOption('mode', mode);
453 this.code_mirror.setOption('mode', mode);
446 CodeMirror.autoLoadMode(this.code_mirror, mode);
454 CodeMirror.autoLoadMode(this.code_mirror, mode);
447 return;
455 return;
448 }
456 }
449 var open = modes[mode]['open']|| "%%";
457 var open = modes[mode]['open']|| "%%";
450 var close = modes[mode]['close']|| "%%end";
458 var close = modes[mode]['close']|| "%%end";
451 var mmode = mode;
459 var mmode = mode;
452 mode = mmode.substr(6);
460 mode = mmode.substr(6);
453 if(current_mode == mode){
461 if(current_mode == mode){
454 return;
462 return;
455 }
463 }
456 CodeMirror.autoLoadMode(this.code_mirror, mode);
464 CodeMirror.autoLoadMode(this.code_mirror, mode);
457 // create on the fly a mode that swhitch between
465 // create on the fly a mode that swhitch between
458 // plain/text and smth else otherwise `%%` is
466 // plain/text and smth else otherwise `%%` is
459 // source of some highlight issues.
467 // source of some highlight issues.
460 // we use patchedGetMode to circumvent a bug in CM
468 // we use patchedGetMode to circumvent a bug in CM
461 CodeMirror.defineMode(mmode , function(config) {
469 CodeMirror.defineMode(mmode , function(config) {
462 return CodeMirror.multiplexingMode(
470 return CodeMirror.multiplexingMode(
463 CodeMirror.patchedGetMode(config, 'text/plain'),
471 CodeMirror.patchedGetMode(config, 'text/plain'),
464 // always set someting on close
472 // always set someting on close
465 {open: open, close: close,
473 {open: open, close: close,
466 mode: CodeMirror.patchedGetMode(config, mode),
474 mode: CodeMirror.patchedGetMode(config, mode),
467 delimStyle: "delimit"
475 delimStyle: "delimit"
468 }
476 }
469 );
477 );
470 });
478 });
471 this.code_mirror.setOption('mode', mmode);
479 this.code_mirror.setOption('mode', mmode);
472 return;
480 return;
473 }
481 }
474 }
482 }
475 }
483 }
476 // fallback on default
484 // fallback on default
477 var default_mode
485 var default_mode
478 try {
486 try {
479 default_mode = this._options.cm_config.mode;
487 default_mode = this._options.cm_config.mode;
480 } catch(e) {
488 } catch(e) {
481 default_mode = 'text/plain';
489 default_mode = 'text/plain';
482 }
490 }
483 if( current_mode === default_mode){
491 if( current_mode === default_mode){
484 return
492 return
485 }
493 }
486 this.code_mirror.setOption('mode', default_mode);
494 this.code_mirror.setOption('mode', default_mode);
487 };
495 };
488
496
489 IPython.Cell = Cell;
497 IPython.Cell = Cell;
490
498
491 return IPython;
499 return IPython;
492
500
493 }(IPython));
501 }(IPython));
494
502
@@ -1,770 +1,767 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 // Keyboard management
9 // Keyboard management
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 // Setup global keycodes and inverse keycodes.
15 // Setup global keycodes and inverse keycodes.
16
16
17 // See http://unixpapa.com/js/key.html for a complete description. The short of
17 // See http://unixpapa.com/js/key.html for a complete description. The short of
18 // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
18 // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
19 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
19 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
20 // but have minor differences.
20 // but have minor differences.
21
21
22 // These apply to Firefox, (Webkit and IE)
22 // These apply to Firefox, (Webkit and IE)
23 var _keycodes = {
23 var _keycodes = {
24 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
24 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
25 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
25 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
26 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
26 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
27 '1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
27 '1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
28 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
28 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
29 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
29 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
30 '\\ |': 220, '\' "': 222,
30 '\\ |': 220, '\' "': 222,
31 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
31 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
32 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
32 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
33 'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
33 'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
34 'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
34 'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
35 'f8': 119, 'f9': 120, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
35 'f8': 119, 'f9': 120, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
36 'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
36 'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
37 'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
37 'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
38 'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
38 'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
39 'insert': 45, 'delete': 46, 'numlock': 144,
39 'insert': 45, 'delete': 46, 'numlock': 144,
40 };
40 };
41
41
42 // These apply to Firefox and Opera
42 // These apply to Firefox and Opera
43 var _mozilla_keycodes = {
43 var _mozilla_keycodes = {
44 '; :': 59, '= +': 61, '- _': 173, 'meta': 224
44 '; :': 59, '= +': 61, '- _': 173, 'meta': 224
45 }
45 }
46
46
47 // This apply to Webkit and IE
47 // This apply to Webkit and IE
48 var _ie_keycodes = {
48 var _ie_keycodes = {
49 '; :': 186, '= +': 187, '- _': 189,
49 '; :': 186, '= +': 187, '- _': 189,
50 }
50 }
51
51
52 var browser = IPython.utils.browser[0];
52 var browser = IPython.utils.browser[0];
53 var platform = IPython.utils.platform;
53 var platform = IPython.utils.platform;
54
54
55 if (browser === 'Firefox' || browser === 'Opera') {
55 if (browser === 'Firefox' || browser === 'Opera') {
56 $.extend(_keycodes, _mozilla_keycodes);
56 $.extend(_keycodes, _mozilla_keycodes);
57 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
57 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
58 $.extend(_keycodes, _ie_keycodes);
58 $.extend(_keycodes, _ie_keycodes);
59 }
59 }
60
60
61 var keycodes = {};
61 var keycodes = {};
62 var inv_keycodes = {};
62 var inv_keycodes = {};
63 for (var name in _keycodes) {
63 for (var name in _keycodes) {
64 var names = name.split(' ');
64 var names = name.split(' ');
65 if (names.length === 1) {
65 if (names.length === 1) {
66 var n = names[0]
66 var n = names[0]
67 keycodes[n] = _keycodes[n]
67 keycodes[n] = _keycodes[n]
68 inv_keycodes[_keycodes[n]] = n
68 inv_keycodes[_keycodes[n]] = n
69 } else {
69 } else {
70 var primary = names[0];
70 var primary = names[0];
71 var secondary = names[1];
71 var secondary = names[1];
72 keycodes[primary] = _keycodes[name]
72 keycodes[primary] = _keycodes[name]
73 keycodes[secondary] = _keycodes[name]
73 keycodes[secondary] = _keycodes[name]
74 inv_keycodes[_keycodes[name]] = primary
74 inv_keycodes[_keycodes[name]] = primary
75 }
75 }
76 }
76 }
77
77
78
78
79 // Default keyboard shortcuts
79 // Default keyboard shortcuts
80
80
81 var default_common_shortcuts = {
81 var default_common_shortcuts = {
82 'shift' : {
82 'shift' : {
83 help : '',
83 help : '',
84 help_index : '',
84 help_index : '',
85 handler : function (event) {
85 handler : function (event) {
86 // ignore shift keydown
86 // ignore shift keydown
87 return true;
87 return true;
88 }
88 }
89 },
89 },
90 'shift+enter' : {
90 'shift+enter' : {
91 help : 'run cell, select below',
91 help : 'run cell, select below',
92 help_index : 'ba',
92 help_index : 'ba',
93 handler : function (event) {
93 handler : function (event) {
94 IPython.notebook.execute_cell_and_select_below();
94 IPython.notebook.execute_cell_and_select_below();
95 return false;
95 return false;
96 }
96 }
97 },
97 },
98 'ctrl+enter' : {
98 'ctrl+enter' : {
99 help : 'run cell',
99 help : 'run cell',
100 help_index : 'bb',
100 help_index : 'bb',
101 handler : function (event) {
101 handler : function (event) {
102 IPython.notebook.execute_cell();
102 IPython.notebook.execute_cell();
103 return false;
103 return false;
104 }
104 }
105 },
105 },
106 'alt+enter' : {
106 'alt+enter' : {
107 help : 'run cell, insert below',
107 help : 'run cell, insert below',
108 help_index : 'bc',
108 help_index : 'bc',
109 handler : function (event) {
109 handler : function (event) {
110 IPython.notebook.execute_cell_and_insert_below();
110 IPython.notebook.execute_cell_and_insert_below();
111 return false;
111 return false;
112 }
112 }
113 }
113 }
114 }
114 }
115
115
116 if (platform === 'MacOS') {
116 if (platform === 'MacOS') {
117 default_common_shortcuts['cmd+s'] =
117 default_common_shortcuts['cmd+s'] =
118 {
118 {
119 help : 'save notebook',
119 help : 'save notebook',
120 help_index : 'fb',
120 help_index : 'fb',
121 handler : function (event) {
121 handler : function (event) {
122 IPython.notebook.save_checkpoint();
122 IPython.notebook.save_checkpoint();
123 event.preventDefault();
123 event.preventDefault();
124 return false;
124 return false;
125 }
125 }
126 };
126 };
127 } else {
127 } else {
128 default_common_shortcuts['ctrl+s'] =
128 default_common_shortcuts['ctrl+s'] =
129 {
129 {
130 help : 'save notebook',
130 help : 'save notebook',
131 help_index : 'fb',
131 help_index : 'fb',
132 handler : function (event) {
132 handler : function (event) {
133 IPython.notebook.save_checkpoint();
133 IPython.notebook.save_checkpoint();
134 event.preventDefault();
134 event.preventDefault();
135 return false;
135 return false;
136 }
136 }
137 };
137 };
138 }
138 }
139
139
140 // Edit mode defaults
140 // Edit mode defaults
141
141
142 var default_edit_shortcuts = {
142 var default_edit_shortcuts = {
143 'esc' : {
143 'esc' : {
144 help : 'command mode',
144 help : 'command mode',
145 help_index : 'aa',
145 help_index : 'aa',
146 handler : function (event) {
146 handler : function (event) {
147 IPython.notebook.command_mode();
147 IPython.notebook.command_mode();
148 IPython.notebook.focus_cell();
148 IPython.notebook.focus_cell();
149 return false;
149 return false;
150 }
150 }
151 },
151 },
152 'ctrl+m' : {
152 'ctrl+m' : {
153 help : 'command mode',
153 help : 'command mode',
154 help_index : 'ab',
154 help_index : 'ab',
155 handler : function (event) {
155 handler : function (event) {
156 IPython.notebook.command_mode();
156 IPython.notebook.command_mode();
157 IPython.notebook.focus_cell();
157 IPython.notebook.focus_cell();
158 return false;
158 return false;
159 }
159 }
160 },
160 },
161 'up' : {
161 'up' : {
162 help : '',
162 help : '',
163 help_index : '',
163 help_index : '',
164 handler : function (event) {
164 handler : function (event) {
165 var cell = IPython.notebook.get_selected_cell();
165 var cell = IPython.notebook.get_selected_cell();
166 if (cell && cell.at_top()) {
166 if (cell && cell.at_top()) {
167 event.preventDefault();
167 event.preventDefault();
168 IPython.notebook.command_mode()
168 IPython.notebook.command_mode()
169 IPython.notebook.select_prev();
169 IPython.notebook.select_prev();
170 IPython.notebook.edit_mode();
170 IPython.notebook.edit_mode();
171 return false;
171 return false;
172 };
172 };
173 }
173 }
174 },
174 },
175 'down' : {
175 'down' : {
176 help : '',
176 help : '',
177 help_index : '',
177 help_index : '',
178 handler : function (event) {
178 handler : function (event) {
179 var cell = IPython.notebook.get_selected_cell();
179 var cell = IPython.notebook.get_selected_cell();
180 if (cell && cell.at_bottom()) {
180 if (cell && cell.at_bottom()) {
181 event.preventDefault();
181 event.preventDefault();
182 IPython.notebook.command_mode()
182 IPython.notebook.command_mode()
183 IPython.notebook.select_next();
183 IPython.notebook.select_next();
184 IPython.notebook.edit_mode();
184 IPython.notebook.edit_mode();
185 return false;
185 return false;
186 };
186 };
187 }
187 }
188 },
188 },
189 'alt+-' : {
189 'alt+-' : {
190 help : 'split cell',
190 help : 'split cell',
191 help_index : 'ea',
191 help_index : 'ea',
192 handler : function (event) {
192 handler : function (event) {
193 IPython.notebook.split_cell();
193 IPython.notebook.split_cell();
194 return false;
194 return false;
195 }
195 }
196 },
196 },
197 'alt+subtract' : {
197 'alt+subtract' : {
198 help : '',
198 help : '',
199 help_index : 'eb',
199 help_index : 'eb',
200 handler : function (event) {
200 handler : function (event) {
201 IPython.notebook.split_cell();
201 IPython.notebook.split_cell();
202 return false;
202 return false;
203 }
203 }
204 },
204 },
205 'tab' : {
205 'tab' : {
206 help : 'indent or complete',
206 help : 'indent or complete',
207 help_index : 'ec',
207 help_index : 'ec',
208 },
208 },
209 'shift+tab' : {
209 'shift+tab' : {
210 help : 'tooltip',
210 help : 'tooltip',
211 help_index : 'ed',
211 help_index : 'ed',
212 },
212 },
213 }
213 }
214
214
215 if (platform === 'MacOS') {
215 if (platform === 'MacOS') {
216 default_edit_shortcuts['cmd+/'] =
216 default_edit_shortcuts['cmd+/'] =
217 {
217 {
218 help : 'toggle comment',
218 help : 'toggle comment',
219 help_index : 'ee'
219 help_index : 'ee'
220 };
220 };
221 default_edit_shortcuts['cmd+]'] =
221 default_edit_shortcuts['cmd+]'] =
222 {
222 {
223 help : 'indent',
223 help : 'indent',
224 help_index : 'ef'
224 help_index : 'ef'
225 };
225 };
226 default_edit_shortcuts['cmd+['] =
226 default_edit_shortcuts['cmd+['] =
227 {
227 {
228 help : 'dedent',
228 help : 'dedent',
229 help_index : 'eg'
229 help_index : 'eg'
230 };
230 };
231 } else {
231 } else {
232 default_edit_shortcuts['ctrl+/'] =
232 default_edit_shortcuts['ctrl+/'] =
233 {
233 {
234 help : 'toggle comment',
234 help : 'toggle comment',
235 help_index : 'ee'
235 help_index : 'ee'
236 };
236 };
237 default_edit_shortcuts['ctrl+]'] =
237 default_edit_shortcuts['ctrl+]'] =
238 {
238 {
239 help : 'indent',
239 help : 'indent',
240 help_index : 'ef'
240 help_index : 'ef'
241 };
241 };
242 default_edit_shortcuts['ctrl+['] =
242 default_edit_shortcuts['ctrl+['] =
243 {
243 {
244 help : 'dedent',
244 help : 'dedent',
245 help_index : 'eg'
245 help_index : 'eg'
246 };
246 };
247 }
247 }
248
248
249 // Command mode defaults
249 // Command mode defaults
250
250
251 var default_command_shortcuts = {
251 var default_command_shortcuts = {
252 'enter' : {
252 'enter' : {
253 help : 'edit mode',
253 help : 'edit mode',
254 help_index : 'aa',
254 help_index : 'aa',
255 handler : function (event) {
255 handler : function (event) {
256 IPython.notebook.edit_mode();
256 IPython.notebook.edit_mode();
257 return false;
257 return false;
258 }
258 }
259 },
259 },
260 'up' : {
260 'up' : {
261 help : 'select previous cell',
261 help : 'select previous cell',
262 help_index : 'da',
262 help_index : 'da',
263 handler : function (event) {
263 handler : function (event) {
264 var index = IPython.notebook.get_selected_index();
264 var index = IPython.notebook.get_selected_index();
265 if (index !== 0 && index !== null) {
265 if (index !== 0 && index !== null) {
266 IPython.notebook.select_prev();
266 IPython.notebook.select_prev();
267 var cell = IPython.notebook.get_selected_cell();
267 var cell = IPython.notebook.get_selected_cell();
268 cell.focus_cell();
268 cell.focus_cell();
269 };
269 };
270 return false;
270 return false;
271 }
271 }
272 },
272 },
273 'down' : {
273 'down' : {
274 help : 'select next cell',
274 help : 'select next cell',
275 help_index : 'db',
275 help_index : 'db',
276 handler : function (event) {
276 handler : function (event) {
277 var index = IPython.notebook.get_selected_index();
277 var index = IPython.notebook.get_selected_index();
278 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
278 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
279 IPython.notebook.select_next();
279 IPython.notebook.select_next();
280 var cell = IPython.notebook.get_selected_cell();
280 var cell = IPython.notebook.get_selected_cell();
281 cell.focus_cell();
281 cell.focus_cell();
282 };
282 };
283 return false;
283 return false;
284 }
284 }
285 },
285 },
286 'k' : {
286 'k' : {
287 help : 'select previous cell',
287 help : 'select previous cell',
288 help_index : 'dc',
288 help_index : 'dc',
289 handler : function (event) {
289 handler : function (event) {
290 var index = IPython.notebook.get_selected_index();
290 var index = IPython.notebook.get_selected_index();
291 if (index !== 0 && index !== null) {
291 if (index !== 0 && index !== null) {
292 IPython.notebook.select_prev();
292 IPython.notebook.select_prev();
293 var cell = IPython.notebook.get_selected_cell();
293 var cell = IPython.notebook.get_selected_cell();
294 cell.focus_cell();
294 cell.focus_cell();
295 };
295 };
296 return false;
296 return false;
297 }
297 }
298 },
298 },
299 'j' : {
299 'j' : {
300 help : 'select next cell',
300 help : 'select next cell',
301 help_index : 'dd',
301 help_index : 'dd',
302 handler : function (event) {
302 handler : function (event) {
303 var index = IPython.notebook.get_selected_index();
303 var index = IPython.notebook.get_selected_index();
304 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
304 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
305 IPython.notebook.select_next();
305 IPython.notebook.select_next();
306 var cell = IPython.notebook.get_selected_cell();
306 var cell = IPython.notebook.get_selected_cell();
307 cell.focus_cell();
307 cell.focus_cell();
308 };
308 };
309 return false;
309 return false;
310 }
310 }
311 },
311 },
312 'x' : {
312 'x' : {
313 help : 'cut cell',
313 help : 'cut cell',
314 help_index : 'ee',
314 help_index : 'ee',
315 handler : function (event) {
315 handler : function (event) {
316 IPython.notebook.cut_cell();
316 IPython.notebook.cut_cell();
317 return false;
317 return false;
318 }
318 }
319 },
319 },
320 'c' : {
320 'c' : {
321 help : 'copy cell',
321 help : 'copy cell',
322 help_index : 'ef',
322 help_index : 'ef',
323 handler : function (event) {
323 handler : function (event) {
324 IPython.notebook.copy_cell();
324 IPython.notebook.copy_cell();
325 return false;
325 return false;
326 }
326 }
327 },
327 },
328 'shift+v' : {
328 'shift+v' : {
329 help : 'paste cell above',
329 help : 'paste cell above',
330 help_index : 'eg',
330 help_index : 'eg',
331 handler : function (event) {
331 handler : function (event) {
332 IPython.notebook.paste_cell_above();
332 IPython.notebook.paste_cell_above();
333 return false;
333 return false;
334 }
334 }
335 },
335 },
336 'v' : {
336 'v' : {
337 help : 'paste cell below',
337 help : 'paste cell below',
338 help_index : 'eh',
338 help_index : 'eh',
339 handler : function (event) {
339 handler : function (event) {
340 IPython.notebook.paste_cell_below();
340 IPython.notebook.paste_cell_below();
341 return false;
341 return false;
342 }
342 }
343 },
343 },
344 'd' : {
344 'd' : {
345 help : 'delete cell (press twice)',
345 help : 'delete cell (press twice)',
346 help_index : 'ej',
346 help_index : 'ej',
347 count: 2,
347 count: 2,
348 handler : function (event) {
348 handler : function (event) {
349 IPython.notebook.delete_cell();
349 IPython.notebook.delete_cell();
350 return false;
350 return false;
351 }
351 }
352 },
352 },
353 'a' : {
353 'a' : {
354 help : 'insert cell above',
354 help : 'insert cell above',
355 help_index : 'ec',
355 help_index : 'ec',
356 handler : function (event) {
356 handler : function (event) {
357 IPython.notebook.insert_cell_above('code');
357 IPython.notebook.insert_cell_above('code');
358 IPython.notebook.select_prev();
358 IPython.notebook.select_prev();
359 IPython.notebook.focus_cell();
359 IPython.notebook.focus_cell();
360 return false;
360 return false;
361 }
361 }
362 },
362 },
363 'b' : {
363 'b' : {
364 help : 'insert cell below',
364 help : 'insert cell below',
365 help_index : 'ed',
365 help_index : 'ed',
366 handler : function (event) {
366 handler : function (event) {
367 IPython.notebook.insert_cell_below('code');
367 IPython.notebook.insert_cell_below('code');
368 IPython.notebook.select_next();
368 IPython.notebook.select_next();
369 IPython.notebook.focus_cell();
369 IPython.notebook.focus_cell();
370 return false;
370 return false;
371 }
371 }
372 },
372 },
373 'y' : {
373 'y' : {
374 help : 'to code',
374 help : 'to code',
375 help_index : 'ca',
375 help_index : 'ca',
376 handler : function (event) {
376 handler : function (event) {
377 IPython.notebook.to_code();
377 IPython.notebook.to_code();
378 return false;
378 return false;
379 }
379 }
380 },
380 },
381 'm' : {
381 'm' : {
382 help : 'to markdown',
382 help : 'to markdown',
383 help_index : 'cb',
383 help_index : 'cb',
384 handler : function (event) {
384 handler : function (event) {
385 IPython.notebook.to_markdown();
385 IPython.notebook.to_markdown();
386 return false;
386 return false;
387 }
387 }
388 },
388 },
389 'r' : {
389 'r' : {
390 help : 'to raw',
390 help : 'to raw',
391 help_index : 'cc',
391 help_index : 'cc',
392 handler : function (event) {
392 handler : function (event) {
393 IPython.notebook.to_raw();
393 IPython.notebook.to_raw();
394 return false;
394 return false;
395 }
395 }
396 },
396 },
397 '1' : {
397 '1' : {
398 help : 'to heading 1',
398 help : 'to heading 1',
399 help_index : 'cd',
399 help_index : 'cd',
400 handler : function (event) {
400 handler : function (event) {
401 IPython.notebook.to_heading(undefined, 1);
401 IPython.notebook.to_heading(undefined, 1);
402 return false;
402 return false;
403 }
403 }
404 },
404 },
405 '2' : {
405 '2' : {
406 help : 'to heading 2',
406 help : 'to heading 2',
407 help_index : 'ce',
407 help_index : 'ce',
408 handler : function (event) {
408 handler : function (event) {
409 IPython.notebook.to_heading(undefined, 2);
409 IPython.notebook.to_heading(undefined, 2);
410 return false;
410 return false;
411 }
411 }
412 },
412 },
413 '3' : {
413 '3' : {
414 help : 'to heading 3',
414 help : 'to heading 3',
415 help_index : 'cf',
415 help_index : 'cf',
416 handler : function (event) {
416 handler : function (event) {
417 IPython.notebook.to_heading(undefined, 3);
417 IPython.notebook.to_heading(undefined, 3);
418 return false;
418 return false;
419 }
419 }
420 },
420 },
421 '4' : {
421 '4' : {
422 help : 'to heading 4',
422 help : 'to heading 4',
423 help_index : 'cg',
423 help_index : 'cg',
424 handler : function (event) {
424 handler : function (event) {
425 IPython.notebook.to_heading(undefined, 4);
425 IPython.notebook.to_heading(undefined, 4);
426 return false;
426 return false;
427 }
427 }
428 },
428 },
429 '5' : {
429 '5' : {
430 help : 'to heading 5',
430 help : 'to heading 5',
431 help_index : 'ch',
431 help_index : 'ch',
432 handler : function (event) {
432 handler : function (event) {
433 IPython.notebook.to_heading(undefined, 5);
433 IPython.notebook.to_heading(undefined, 5);
434 return false;
434 return false;
435 }
435 }
436 },
436 },
437 '6' : {
437 '6' : {
438 help : 'to heading 6',
438 help : 'to heading 6',
439 help_index : 'ci',
439 help_index : 'ci',
440 handler : function (event) {
440 handler : function (event) {
441 IPython.notebook.to_heading(undefined, 6);
441 IPython.notebook.to_heading(undefined, 6);
442 return false;
442 return false;
443 }
443 }
444 },
444 },
445 'o' : {
445 'o' : {
446 help : 'toggle output',
446 help : 'toggle output',
447 help_index : 'gb',
447 help_index : 'gb',
448 handler : function (event) {
448 handler : function (event) {
449 IPython.notebook.toggle_output();
449 IPython.notebook.toggle_output();
450 return false;
450 return false;
451 }
451 }
452 },
452 },
453 'shift+o' : {
453 'shift+o' : {
454 help : 'toggle output scrolling',
454 help : 'toggle output scrolling',
455 help_index : 'gc',
455 help_index : 'gc',
456 handler : function (event) {
456 handler : function (event) {
457 IPython.notebook.toggle_output_scroll();
457 IPython.notebook.toggle_output_scroll();
458 return false;
458 return false;
459 }
459 }
460 },
460 },
461 's' : {
461 's' : {
462 help : 'save notebook',
462 help : 'save notebook',
463 help_index : 'fa',
463 help_index : 'fa',
464 handler : function (event) {
464 handler : function (event) {
465 IPython.notebook.save_checkpoint();
465 IPython.notebook.save_checkpoint();
466 return false;
466 return false;
467 }
467 }
468 },
468 },
469 'ctrl+j' : {
469 'ctrl+j' : {
470 help : 'move cell down',
470 help : 'move cell down',
471 help_index : 'eb',
471 help_index : 'eb',
472 handler : function (event) {
472 handler : function (event) {
473 IPython.notebook.move_cell_down();
473 IPython.notebook.move_cell_down();
474 return false;
474 return false;
475 }
475 }
476 },
476 },
477 'ctrl+k' : {
477 'ctrl+k' : {
478 help : 'move cell up',
478 help : 'move cell up',
479 help_index : 'ea',
479 help_index : 'ea',
480 handler : function (event) {
480 handler : function (event) {
481 IPython.notebook.move_cell_up();
481 IPython.notebook.move_cell_up();
482 return false;
482 return false;
483 }
483 }
484 },
484 },
485 'l' : {
485 'l' : {
486 help : 'toggle line numbers',
486 help : 'toggle line numbers',
487 help_index : 'ga',
487 help_index : 'ga',
488 handler : function (event) {
488 handler : function (event) {
489 IPython.notebook.cell_toggle_line_numbers();
489 IPython.notebook.cell_toggle_line_numbers();
490 return false;
490 return false;
491 }
491 }
492 },
492 },
493 'i' : {
493 'i' : {
494 help : 'interrupt kernel (press twice)',
494 help : 'interrupt kernel (press twice)',
495 help_index : 'ha',
495 help_index : 'ha',
496 count: 2,
496 count: 2,
497 handler : function (event) {
497 handler : function (event) {
498 IPython.notebook.kernel.interrupt();
498 IPython.notebook.kernel.interrupt();
499 return false;
499 return false;
500 }
500 }
501 },
501 },
502 '0' : {
502 '0' : {
503 help : 'restart kernel (press twice)',
503 help : 'restart kernel (press twice)',
504 help_index : 'hb',
504 help_index : 'hb',
505 count: 2,
505 count: 2,
506 handler : function (event) {
506 handler : function (event) {
507 IPython.notebook.restart_kernel();
507 IPython.notebook.restart_kernel();
508 return false;
508 return false;
509 }
509 }
510 },
510 },
511 'h' : {
511 'h' : {
512 help : 'keyboard shortcuts',
512 help : 'keyboard shortcuts',
513 help_index : 'gd',
513 help_index : 'gd',
514 handler : function (event) {
514 handler : function (event) {
515 IPython.quick_help.show_keyboard_shortcuts();
515 IPython.quick_help.show_keyboard_shortcuts();
516 return false;
516 return false;
517 }
517 }
518 },
518 },
519 'z' : {
519 'z' : {
520 help : 'undo last delete',
520 help : 'undo last delete',
521 help_index : 'ei',
521 help_index : 'ei',
522 handler : function (event) {
522 handler : function (event) {
523 IPython.notebook.undelete_cell();
523 IPython.notebook.undelete_cell();
524 return false;
524 return false;
525 }
525 }
526 },
526 },
527 'shift+=' : {
527 'shift+=' : {
528 help : 'merge cell below',
528 help : 'merge cell below',
529 help_index : 'ek',
529 help_index : 'ek',
530 handler : function (event) {
530 handler : function (event) {
531 IPython.notebook.merge_cell_below();
531 IPython.notebook.merge_cell_below();
532 return false;
532 return false;
533 }
533 }
534 },
534 },
535 'shift+m' : {
535 'shift+m' : {
536 help : 'merge cell below',
536 help : 'merge cell below',
537 help_index : 'ek',
537 help_index : 'ek',
538 handler : function (event) {
538 handler : function (event) {
539 IPython.notebook.merge_cell_below();
539 IPython.notebook.merge_cell_below();
540 return false;
540 return false;
541 }
541 }
542 },
542 },
543 }
543 }
544
544
545
545
546 // Shortcut manager class
546 // Shortcut manager class
547
547
548 var ShortcutManager = function (delay) {
548 var ShortcutManager = function (delay) {
549 this._shortcuts = {}
549 this._shortcuts = {}
550 this._counts = {}
550 this._counts = {}
551 this.delay = delay || 800; // delay in milliseconds
551 this.delay = delay || 800; // delay in milliseconds
552 }
552 }
553
553
554 ShortcutManager.prototype.help = function () {
554 ShortcutManager.prototype.help = function () {
555 var help = [];
555 var help = [];
556 for (var shortcut in this._shortcuts) {
556 for (var shortcut in this._shortcuts) {
557 var help_string = this._shortcuts[shortcut]['help'];
557 var help_string = this._shortcuts[shortcut]['help'];
558 var help_index = this._shortcuts[shortcut]['help_index'];
558 var help_index = this._shortcuts[shortcut]['help_index'];
559 if (help_string) {
559 if (help_string) {
560 if (platform === 'MacOS') {
560 if (platform === 'MacOS') {
561 shortcut = shortcut.replace('meta', 'cmd');
561 shortcut = shortcut.replace('meta', 'cmd');
562 }
562 }
563 help.push({
563 help.push({
564 shortcut: shortcut,
564 shortcut: shortcut,
565 help: help_string,
565 help: help_string,
566 help_index: help_index}
566 help_index: help_index}
567 );
567 );
568 }
568 }
569 }
569 }
570 help.sort(function (a, b) {
570 help.sort(function (a, b) {
571 if (a.help_index > b.help_index)
571 if (a.help_index > b.help_index)
572 return 1;
572 return 1;
573 if (a.help_index < b.help_index)
573 if (a.help_index < b.help_index)
574 return -1;
574 return -1;
575 return 0;
575 return 0;
576 });
576 });
577 return help;
577 return help;
578 }
578 }
579
579
580 ShortcutManager.prototype.normalize_key = function (key) {
580 ShortcutManager.prototype.normalize_key = function (key) {
581 return inv_keycodes[keycodes[key]];
581 return inv_keycodes[keycodes[key]];
582 }
582 }
583
583
584 ShortcutManager.prototype.normalize_shortcut = function (shortcut) {
584 ShortcutManager.prototype.normalize_shortcut = function (shortcut) {
585 // Sort a sequence of + separated modifiers into the order alt+ctrl+meta+shift
585 // Sort a sequence of + separated modifiers into the order alt+ctrl+meta+shift
586 shortcut = shortcut.replace('cmd', 'meta').toLowerCase();
586 shortcut = shortcut.replace('cmd', 'meta').toLowerCase();
587 var values = shortcut.split("+");
587 var values = shortcut.split("+");
588 if (values.length === 1) {
588 if (values.length === 1) {
589 return this.normalize_key(values[0])
589 return this.normalize_key(values[0])
590 } else {
590 } else {
591 var modifiers = values.slice(0,-1);
591 var modifiers = values.slice(0,-1);
592 var key = this.normalize_key(values[values.length-1]);
592 var key = this.normalize_key(values[values.length-1]);
593 modifiers.sort();
593 modifiers.sort();
594 return modifiers.join('+') + '+' + key;
594 return modifiers.join('+') + '+' + key;
595 }
595 }
596 }
596 }
597
597
598 ShortcutManager.prototype.event_to_shortcut = function (event) {
598 ShortcutManager.prototype.event_to_shortcut = function (event) {
599 // Convert a jQuery keyboard event to a strong based keyboard shortcut
599 // Convert a jQuery keyboard event to a strong based keyboard shortcut
600 var shortcut = '';
600 var shortcut = '';
601 var key = inv_keycodes[event.which]
601 var key = inv_keycodes[event.which]
602 if (event.altKey && key !== 'alt') {shortcut += 'alt+';}
602 if (event.altKey && key !== 'alt') {shortcut += 'alt+';}
603 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl+';}
603 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl+';}
604 if (event.metaKey && key !== 'meta') {shortcut += 'meta+';}
604 if (event.metaKey && key !== 'meta') {shortcut += 'meta+';}
605 if (event.shiftKey && key !== 'shift') {shortcut += 'shift+';}
605 if (event.shiftKey && key !== 'shift') {shortcut += 'shift+';}
606 shortcut += key;
606 shortcut += key;
607 return shortcut
607 return shortcut
608 }
608 }
609
609
610 ShortcutManager.prototype.clear_shortcuts = function () {
610 ShortcutManager.prototype.clear_shortcuts = function () {
611 this._shortcuts = {};
611 this._shortcuts = {};
612 }
612 }
613
613
614 ShortcutManager.prototype.add_shortcut = function (shortcut, data) {
614 ShortcutManager.prototype.add_shortcut = function (shortcut, data) {
615 if (typeof(data) === 'function') {
615 if (typeof(data) === 'function') {
616 data = {help: '', help_index: '', handler: data}
616 data = {help: '', help_index: '', handler: data}
617 }
617 }
618 data.help_index = data.help_index || '';
618 data.help_index = data.help_index || '';
619 data.help = data.help || '';
619 data.help = data.help || '';
620 data.count = data.count || 1;
620 data.count = data.count || 1;
621 if (data.help_index === '') {
621 if (data.help_index === '') {
622 data.help_index = 'zz';
622 data.help_index = 'zz';
623 }
623 }
624 shortcut = this.normalize_shortcut(shortcut);
624 shortcut = this.normalize_shortcut(shortcut);
625 this._counts[shortcut] = 0;
625 this._counts[shortcut] = 0;
626 this._shortcuts[shortcut] = data;
626 this._shortcuts[shortcut] = data;
627 }
627 }
628
628
629 ShortcutManager.prototype.add_shortcuts = function (data) {
629 ShortcutManager.prototype.add_shortcuts = function (data) {
630 for (var shortcut in data) {
630 for (var shortcut in data) {
631 this.add_shortcut(shortcut, data[shortcut]);
631 this.add_shortcut(shortcut, data[shortcut]);
632 }
632 }
633 }
633 }
634
634
635 ShortcutManager.prototype.remove_shortcut = function (shortcut) {
635 ShortcutManager.prototype.remove_shortcut = function (shortcut) {
636 shortcut = this.normalize_shortcut(shortcut);
636 shortcut = this.normalize_shortcut(shortcut);
637 delete this._counts[shortcut];
637 delete this._counts[shortcut];
638 delete this._shortcuts[shortcut];
638 delete this._shortcuts[shortcut];
639 }
639 }
640
640
641 ShortcutManager.prototype.count_handler = function (shortcut, event, data) {
641 ShortcutManager.prototype.count_handler = function (shortcut, event, data) {
642 var that = this;
642 var that = this;
643 var c = this._counts;
643 var c = this._counts;
644 if (c[shortcut] === data.count-1) {
644 if (c[shortcut] === data.count-1) {
645 c[shortcut] = 0;
645 c[shortcut] = 0;
646 return data.handler(event);
646 return data.handler(event);
647 } else {
647 } else {
648 c[shortcut] = c[shortcut] + 1;
648 c[shortcut] = c[shortcut] + 1;
649 setTimeout(function () {
649 setTimeout(function () {
650 c[shortcut] = 0;
650 c[shortcut] = 0;
651 }, that.delay);
651 }, that.delay);
652 }
652 }
653 return false;
653 return false;
654
654
655 }
655 }
656
656
657 ShortcutManager.prototype.call_handler = function (event) {
657 ShortcutManager.prototype.call_handler = function (event) {
658 var shortcut = this.event_to_shortcut(event);
658 var shortcut = this.event_to_shortcut(event);
659 var data = this._shortcuts[shortcut];
659 var data = this._shortcuts[shortcut];
660 if (data) {
660 if (data) {
661 var handler = data['handler'];
661 var handler = data['handler'];
662 if (handler) {
662 if (handler) {
663 if (data.count === 1) {
663 if (data.count === 1) {
664 return handler(event);
664 return handler(event);
665 } else if (data.count > 1) {
665 } else if (data.count > 1) {
666 return this.count_handler(shortcut, event, data);
666 return this.count_handler(shortcut, event, data);
667 }
667 }
668 }
668 }
669 }
669 }
670 return true;
670 return true;
671 }
671 }
672
672
673
673
674
674
675 // Main keyboard manager for the notebook
675 // Main keyboard manager for the notebook
676
676
677 var KeyboardManager = function () {
677 var KeyboardManager = function () {
678 this.mode = 'command';
678 this.mode = 'command';
679 this.enabled = true;
679 this.enabled = true;
680 this.bind_events();
680 this.bind_events();
681 this.command_shortcuts = new ShortcutManager();
681 this.command_shortcuts = new ShortcutManager();
682 this.command_shortcuts.add_shortcuts(default_common_shortcuts);
682 this.command_shortcuts.add_shortcuts(default_common_shortcuts);
683 this.command_shortcuts.add_shortcuts(default_command_shortcuts);
683 this.command_shortcuts.add_shortcuts(default_command_shortcuts);
684 this.edit_shortcuts = new ShortcutManager();
684 this.edit_shortcuts = new ShortcutManager();
685 this.edit_shortcuts.add_shortcuts(default_common_shortcuts);
685 this.edit_shortcuts.add_shortcuts(default_common_shortcuts);
686 this.edit_shortcuts.add_shortcuts(default_edit_shortcuts);
686 this.edit_shortcuts.add_shortcuts(default_edit_shortcuts);
687 };
687 };
688
688
689 KeyboardManager.prototype.bind_events = function () {
689 KeyboardManager.prototype.bind_events = function () {
690 var that = this;
690 var that = this;
691 $(document).keydown(function (event) {
691 $(document).keydown(function (event) {
692 return that.handle_keydown(event);
692 return that.handle_keydown(event);
693 });
693 });
694 };
694 };
695
695
696 KeyboardManager.prototype.handle_keydown = function (event) {
696 KeyboardManager.prototype.handle_keydown = function (event) {
697 var notebook = IPython.notebook;
697 var notebook = IPython.notebook;
698
698
699 if (event.which === keycodes['esc']) {
699 if (event.which === keycodes['esc']) {
700 // Intercept escape at highest level to avoid closing
700 // Intercept escape at highest level to avoid closing
701 // websocket connection with firefox
701 // websocket connection with firefox
702 event.preventDefault();
702 event.preventDefault();
703 }
703 }
704
704
705 if (!this.enabled) {
705 if (!this.enabled) {
706 if (event.which === keycodes['esc']) {
706 if (event.which === keycodes['esc']) {
707 // ESC
707 // ESC
708 notebook.command_mode();
708 notebook.command_mode();
709 return false;
709 return false;
710 }
710 }
711 return true;
711 return true;
712 }
712 }
713
713
714 if (this.mode === 'edit') {
714 if (this.mode === 'edit') {
715 return this.edit_shortcuts.call_handler(event);
715 return this.edit_shortcuts.call_handler(event);
716 } else if (this.mode === 'command') {
716 } else if (this.mode === 'command') {
717 return this.command_shortcuts.call_handler(event);
717 return this.command_shortcuts.call_handler(event);
718 }
718 }
719 return true;
719 return true;
720 }
720 }
721
721
722 KeyboardManager.prototype.edit_mode = function () {
722 KeyboardManager.prototype.edit_mode = function () {
723 this.last_mode = this.mode;
723 this.last_mode = this.mode;
724 this.mode = 'edit';
724 this.mode = 'edit';
725 }
725 }
726
726
727 KeyboardManager.prototype.command_mode = function () {
727 KeyboardManager.prototype.command_mode = function () {
728 this.last_mode = this.mode;
728 this.last_mode = this.mode;
729 this.mode = 'command';
729 this.mode = 'command';
730 }
730 }
731
731
732 KeyboardManager.prototype.enable = function () {
732 KeyboardManager.prototype.enable = function () {
733 this.enabled = true;
733 this.enabled = true;
734 }
734 }
735
735
736 KeyboardManager.prototype.disable = function () {
736 KeyboardManager.prototype.disable = function () {
737 this.enabled = false;
737 this.enabled = false;
738 }
738 }
739
739
740 KeyboardManager.prototype.register_events = function (e) {
740 KeyboardManager.prototype.register_events = function (e) {
741 var that = this;
741 var that = this;
742 e.on('focusin', function () {
742 e.on('focusin', function () {
743 that.command_mode();
744 that.disable();
743 that.disable();
745 });
744 });
746 e.on('focusout', function () {
745 e.on('focusout', function () {
747 that.command_mode();
748 that.enable();
746 that.enable();
749 });
747 });
750 // There are times (raw_input) where we remove the element from the DOM before
748 // There are times (raw_input) where we remove the element from the DOM before
751 // focusout is called. In this case we bind to the remove event of jQueryUI,
749 // focusout is called. In this case we bind to the remove event of jQueryUI,
752 // which gets triggered upon removal.
750 // which gets triggered upon removal.
753 e.on('remove', function () {
751 e.on('remove', function () {
754 that.command_mode();
755 that.enable();
752 that.enable();
756 });
753 });
757 }
754 }
758
755
759
756
760 IPython.keycodes = keycodes;
757 IPython.keycodes = keycodes;
761 IPython.inv_keycodes = inv_keycodes;
758 IPython.inv_keycodes = inv_keycodes;
762 IPython.default_common_shortcuts = default_common_shortcuts;
759 IPython.default_common_shortcuts = default_common_shortcuts;
763 IPython.default_edit_shortcuts = default_edit_shortcuts;
760 IPython.default_edit_shortcuts = default_edit_shortcuts;
764 IPython.default_command_shortcuts = default_command_shortcuts;
761 IPython.default_command_shortcuts = default_command_shortcuts;
765 IPython.ShortcutManager = ShortcutManager;
762 IPython.ShortcutManager = ShortcutManager;
766 IPython.KeyboardManager = KeyboardManager;
763 IPython.KeyboardManager = KeyboardManager;
767
764
768 return IPython;
765 return IPython;
769
766
770 }(IPython));
767 }(IPython));
@@ -1,210 +1,204 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // WidgetModel, WidgetView, and WidgetManager
9 // WidgetModel, WidgetView, and WidgetManager
10 //============================================================================
10 //============================================================================
11 /**
11 /**
12 * Base Widget classes
12 * Base Widget classes
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule widget
15 * @submodule widget
16 */
16 */
17
17
18 (function () {
18 (function () {
19 "use strict";
19 "use strict";
20
20
21 // Use require.js 'define' method so that require.js is intelligent enough to
21 // Use require.js 'define' method so that require.js is intelligent enough to
22 // syncronously load everything within this file when it is being 'required'
22 // syncronously load everything within this file when it is being 'required'
23 // elsewhere.
23 // elsewhere.
24 define(["underscore",
24 define(["underscore",
25 "backbone",
25 "backbone",
26 ], function (Underscore, Backbone) {
26 ], function (Underscore, Backbone) {
27
27
28 //--------------------------------------------------------------------
28 //--------------------------------------------------------------------
29 // WidgetManager class
29 // WidgetManager class
30 //--------------------------------------------------------------------
30 //--------------------------------------------------------------------
31 var WidgetManager = function (comm_manager) {
31 var WidgetManager = function (comm_manager) {
32 // Public constructor
32 // Public constructor
33 WidgetManager._managers.push(this);
33 WidgetManager._managers.push(this);
34
34
35 // Attach a comm manager to the
35 // Attach a comm manager to the
36 this.comm_manager = comm_manager;
36 this.comm_manager = comm_manager;
37 this._models = {}; /* Dictionary of model ids and model instances */
37 this._models = {}; /* Dictionary of model ids and model instances */
38
38
39 // Register already-registered widget model types with the comm manager.
39 // Register already-registered widget model types with the comm manager.
40 var that = this;
40 var that = this;
41 _.each(WidgetManager._model_types, function(model_type, model_name) {
41 _.each(WidgetManager._model_types, function(model_type, model_name) {
42 that.comm_manager.register_target(model_name, $.proxy(that._handle_comm_open, that));
42 that.comm_manager.register_target(model_name, $.proxy(that._handle_comm_open, that));
43 });
43 });
44 };
44 };
45
45
46 //--------------------------------------------------------------------
46 //--------------------------------------------------------------------
47 // Class level
47 // Class level
48 //--------------------------------------------------------------------
48 //--------------------------------------------------------------------
49 WidgetManager._model_types = {}; /* Dictionary of model type names (target_name) and model types. */
49 WidgetManager._model_types = {}; /* Dictionary of model type names (target_name) and model types. */
50 WidgetManager._view_types = {}; /* Dictionary of view names and view types. */
50 WidgetManager._view_types = {}; /* Dictionary of view names and view types. */
51 WidgetManager._managers = []; /* List of widget managers */
51 WidgetManager._managers = []; /* List of widget managers */
52
52
53 WidgetManager.register_widget_model = function (model_name, model_type) {
53 WidgetManager.register_widget_model = function (model_name, model_type) {
54 // Registers a widget model by name.
54 // Registers a widget model by name.
55 WidgetManager._model_types[model_name] = model_type;
55 WidgetManager._model_types[model_name] = model_type;
56
56
57 // Register the widget with the comm manager. Make sure to pass this object's context
57 // Register the widget with the comm manager. Make sure to pass this object's context
58 // in so `this` works in the call back.
58 // in so `this` works in the call back.
59 _.each(WidgetManager._managers, function(instance, i) {
59 _.each(WidgetManager._managers, function(instance, i) {
60 if (instance.comm_manager !== null) {
60 if (instance.comm_manager !== null) {
61 instance.comm_manager.register_target(model_name, $.proxy(instance._handle_comm_open, instance));
61 instance.comm_manager.register_target(model_name, $.proxy(instance._handle_comm_open, instance));
62 }
62 }
63 });
63 });
64 };
64 };
65
65
66 WidgetManager.register_widget_view = function (view_name, view_type) {
66 WidgetManager.register_widget_view = function (view_name, view_type) {
67 // Registers a widget view by name.
67 // Registers a widget view by name.
68 WidgetManager._view_types[view_name] = view_type;
68 WidgetManager._view_types[view_name] = view_type;
69 };
69 };
70
70
71 //--------------------------------------------------------------------
71 //--------------------------------------------------------------------
72 // Instance level
72 // Instance level
73 //--------------------------------------------------------------------
73 //--------------------------------------------------------------------
74 WidgetManager.prototype.display_view = function(msg, model) {
74 WidgetManager.prototype.display_view = function(msg, model) {
75 // Displays a view for a particular model.
75 // Displays a view for a particular model.
76 var cell = this.get_msg_cell(msg.parent_header.msg_id);
76 var cell = this.get_msg_cell(msg.parent_header.msg_id);
77 if (cell === null) {
77 if (cell === null) {
78 console.log("Could not determine where the display" +
78 console.log("Could not determine where the display" +
79 " message was from. Widget will not be displayed");
79 " message was from. Widget will not be displayed");
80 } else {
80 } else {
81 var view = this.create_view(model, {cell: cell});
81 var view = this.create_view(model, {cell: cell});
82 if (view === null) {
82 if (view === null) {
83 console.error("View creation failed", model);
83 console.error("View creation failed", model);
84 }
84 }
85 if (cell.widget_subarea) {
85 if (cell.widget_subarea) {
86
87 cell.widget_area.show();
86 cell.widget_area.show();
87 this._handle_display_view(view);
88 cell.widget_subarea.append(view.$el);
88 cell.widget_subarea.append(view.$el);
89 }
89 }
90 }
90 }
91 };
91 };
92
92
93 WidgetManager.prototype._handle_display_view = function (view) {
94 // Have the IPython keyboard manager disable its event
95 // handling so the widget can capture keyboard input.
96 // Note, this is only done on the outer most widget.
97 IPython.keyboard_manager.register_events(view.$el);
98 };
99
93 WidgetManager.prototype.create_view = function(model, options, view) {
100 WidgetManager.prototype.create_view = function(model, options, view) {
94 // Creates a view for a particular model.
101 // Creates a view for a particular model.
95 var view_name = model.get('_view_name');
102 var view_name = model.get('_view_name');
96 var ViewType = WidgetManager._view_types[view_name];
103 var ViewType = WidgetManager._view_types[view_name];
97 if (ViewType) {
104 if (ViewType) {
98
105
99 // If a view is passed into the method, use that view's cell as
106 // If a view is passed into the method, use that view's cell as
100 // the cell for the view that is created.
107 // the cell for the view that is created.
101 options = options || {};
108 options = options || {};
102 if (view !== undefined) {
109 if (view !== undefined) {
103 options.cell = view.options.cell;
110 options.cell = view.options.cell;
104 }
111 }
105
112
106 // Create and render the view...
113 // Create and render the view...
107 var parameters = {model: model, options: options};
114 var parameters = {model: model, options: options};
108 view = new ViewType(parameters);
115 view = new ViewType(parameters);
109 view.render();
116 view.render();
110 model.views.push(view);
117 model.views.push(view);
111 model.on('destroy', view.remove, view);
118 model.on('destroy', view.remove, view);
112
113 this._handle_new_view(view);
114 return view;
119 return view;
115 }
120 }
116 return null;
121 return null;
117 };
122 };
118
123
119 WidgetManager.prototype._handle_new_view = function (view) {
120 // Called when a view has been created and rendered.
121
122 // If the view has a well defined element, inform the keyboard
123 // manager about the view's element, so as the element can
124 // escape the dreaded command mode.
125 if (view.$el) {
126 IPython.keyboard_manager.register_events(view.$el);
127 }
128 };
129
130 WidgetManager.prototype.get_msg_cell = function (msg_id) {
124 WidgetManager.prototype.get_msg_cell = function (msg_id) {
131 var cell = null;
125 var cell = null;
132 // First, check to see if the msg was triggered by cell execution.
126 // First, check to see if the msg was triggered by cell execution.
133 if (IPython.notebook) {
127 if (IPython.notebook) {
134 cell = IPython.notebook.get_msg_cell(msg_id);
128 cell = IPython.notebook.get_msg_cell(msg_id);
135 }
129 }
136 if (cell !== null) {
130 if (cell !== null) {
137 return cell;
131 return cell;
138 }
132 }
139 // Second, check to see if a get_cell callback was defined
133 // Second, check to see if a get_cell callback was defined
140 // for the message. get_cell callbacks are registered for
134 // for the message. get_cell callbacks are registered for
141 // widget messages, so this block is actually checking to see if the
135 // widget messages, so this block is actually checking to see if the
142 // message was triggered by a widget.
136 // message was triggered by a widget.
143 var kernel = this.comm_manager.kernel;
137 var kernel = this.comm_manager.kernel;
144 if (kernel) {
138 if (kernel) {
145 var callbacks = kernel.get_callbacks_for_msg(msg_id);
139 var callbacks = kernel.get_callbacks_for_msg(msg_id);
146 if (callbacks && callbacks.iopub &&
140 if (callbacks && callbacks.iopub &&
147 callbacks.iopub.get_cell !== undefined) {
141 callbacks.iopub.get_cell !== undefined) {
148 return callbacks.iopub.get_cell();
142 return callbacks.iopub.get_cell();
149 }
143 }
150 }
144 }
151
145
152 // Not triggered by a cell or widget (no get_cell callback
146 // Not triggered by a cell or widget (no get_cell callback
153 // exists).
147 // exists).
154 return null;
148 return null;
155 };
149 };
156
150
157 WidgetManager.prototype.callbacks = function (view) {
151 WidgetManager.prototype.callbacks = function (view) {
158 // callback handlers specific a view
152 // callback handlers specific a view
159 var callbacks = {};
153 var callbacks = {};
160 if (view && view.options.cell) {
154 if (view && view.options.cell) {
161
155
162 // Try to get output handlers
156 // Try to get output handlers
163 var cell = view.options.cell;
157 var cell = view.options.cell;
164 var handle_output = null;
158 var handle_output = null;
165 var handle_clear_output = null;
159 var handle_clear_output = null;
166 if (cell.output_area) {
160 if (cell.output_area) {
167 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
161 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
168 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
162 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
169 }
163 }
170
164
171 // Create callback dict using what is known
165 // Create callback dict using what is known
172 var that = this;
166 var that = this;
173 callbacks = {
167 callbacks = {
174 iopub : {
168 iopub : {
175 output : handle_output,
169 output : handle_output,
176 clear_output : handle_clear_output,
170 clear_output : handle_clear_output,
177
171
178 // Special function only registered by widget messages.
172 // Special function only registered by widget messages.
179 // Allows us to get the cell for a message so we know
173 // Allows us to get the cell for a message so we know
180 // where to add widgets if the code requires it.
174 // where to add widgets if the code requires it.
181 get_cell : function () {
175 get_cell : function () {
182 return cell;
176 return cell;
183 },
177 },
184 },
178 },
185 };
179 };
186 }
180 }
187 return callbacks;
181 return callbacks;
188 };
182 };
189
183
190 WidgetManager.prototype.get_model = function (model_id) {
184 WidgetManager.prototype.get_model = function (model_id) {
191 // Look-up a model instance by its id.
185 // Look-up a model instance by its id.
192 var model = this._models[model_id];
186 var model = this._models[model_id];
193 if (model !== undefined && model.id == model_id) {
187 if (model !== undefined && model.id == model_id) {
194 return model;
188 return model;
195 }
189 }
196 return null;
190 return null;
197 };
191 };
198
192
199 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
193 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
200 // Handle when a comm is opened.
194 // Handle when a comm is opened.
201 var model_id = comm.comm_id;
195 var model_id = comm.comm_id;
202 var widget_type_name = msg.content.target_name;
196 var widget_type_name = msg.content.target_name;
203 var widget_model = new WidgetManager._model_types[widget_type_name](this, model_id, comm);
197 var widget_model = new WidgetManager._model_types[widget_type_name](this, model_id, comm);
204 this._models[model_id] = widget_model;
198 this._models[model_id] = widget_model;
205 };
199 };
206
200
207 IPython.WidgetManager = WidgetManager;
201 IPython.WidgetManager = WidgetManager;
208 return IPython.WidgetManager;
202 return IPython.WidgetManager;
209 });
203 });
210 }());
204 }());
General Comments 0
You need to be logged in to leave comments. Login now