##// END OF EJS Templates
a few todo
Matthias BUSSONNIER -
Show More
@@ -1,563 +1,564 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 ], function(IPython, $, utils) {
8 ], function(IPython, $, utils) {
9 // TODO: remove IPython dependency here
9 "use strict";
10 "use strict";
10
11
11 // monkey patch CM to be able to syntax highlight cell magics
12 // monkey patch CM to be able to syntax highlight cell magics
12 // bug reported upstream,
13 // bug reported upstream,
13 // see https://github.com/marijnh/CodeMirror2/issues/670
14 // see https://github.com/marijnh/CodeMirror2/issues/670
14 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
15 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
15 CodeMirror.modes.null = function() {
16 CodeMirror.modes.null = function() {
16 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
17 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
17 };
18 };
18 }
19 }
19
20
20 CodeMirror.patchedGetMode = function(config, mode){
21 CodeMirror.patchedGetMode = function(config, mode){
21 var cmmode = CodeMirror.getMode(config, mode);
22 var cmmode = CodeMirror.getMode(config, mode);
22 if(cmmode.indent === null) {
23 if(cmmode.indent === null) {
23 console.log('patch mode "' , mode, '" on the fly');
24 console.log('patch mode "' , mode, '" on the fly');
24 cmmode.indent = function(){return 0;};
25 cmmode.indent = function(){return 0;};
25 }
26 }
26 return cmmode;
27 return cmmode;
27 };
28 };
28 // end monkey patching CodeMirror
29 // end monkey patching CodeMirror
29
30
30 var Cell = function (options) {
31 var Cell = function (options) {
31 // Constructor
32 // Constructor
32 //
33 //
33 // The Base `Cell` class from which to inherit.
34 // The Base `Cell` class from which to inherit.
34 //
35 //
35 // Parameters:
36 // Parameters:
36 // options: dictionary
37 // options: dictionary
37 // Dictionary of keyword arguments.
38 // Dictionary of keyword arguments.
38 // events: $(Events) instance
39 // events: $(Events) instance
39 // config: dictionary
40 // config: dictionary
40 // keyboard_manager: KeyboardManager instance
41 // keyboard_manager: KeyboardManager instance
41 options = options || {};
42 options = options || {};
42 this.keyboard_manager = options.keyboard_manager;
43 this.keyboard_manager = options.keyboard_manager;
43 this.events = options.events;
44 this.events = options.events;
44 var config = this.mergeopt(Cell, options.config);
45 var config = this.mergeopt(Cell, options.config);
45 // superclass default overwrite our default
46 // superclass default overwrite our default
46
47
47 this.placeholder = config.placeholder || '';
48 this.placeholder = config.placeholder || '';
48 this.read_only = config.cm_config.readOnly;
49 this.read_only = config.cm_config.readOnly;
49 this.selected = false;
50 this.selected = false;
50 this.rendered = false;
51 this.rendered = false;
51 this.mode = 'command';
52 this.mode = 'command';
52 this.metadata = {};
53 this.metadata = {};
53 // load this from metadata later ?
54 // load this from metadata later ?
54 this.user_highlight = 'auto';
55 this.user_highlight = 'auto';
55 this.cm_config = config.cm_config;
56 this.cm_config = config.cm_config;
56 this.cell_id = utils.uuid();
57 this.cell_id = utils.uuid();
57 this._options = config;
58 this._options = config;
58
59
59 // For JS VM engines optimization, attributes should be all set (even
60 // For JS VM engines optimization, attributes should be all set (even
60 // to null) in the constructor, and if possible, if different subclass
61 // to null) in the constructor, and if possible, if different subclass
61 // have new attributes with same name, they should be created in the
62 // have new attributes with same name, they should be created in the
62 // same order. Easiest is to create and set to null in parent class.
63 // same order. Easiest is to create and set to null in parent class.
63
64
64 this.element = null;
65 this.element = null;
65 this.cell_type = this.cell_type || null;
66 this.cell_type = this.cell_type || null;
66 this.code_mirror = null;
67 this.code_mirror = null;
67
68
68 this.create_element();
69 this.create_element();
69 if (this.element !== null) {
70 if (this.element !== null) {
70 this.element.data("cell", this);
71 this.element.data("cell", this);
71 this.bind_events();
72 this.bind_events();
72 this.init_classes();
73 this.init_classes();
73 }
74 }
74 };
75 };
75
76
76 Cell.options_default = {
77 Cell.options_default = {
77 cm_config : {
78 cm_config : {
78 indentUnit : 4,
79 indentUnit : 4,
79 readOnly: false,
80 readOnly: false,
80 theme: "default",
81 theme: "default",
81 extraKeys: {
82 extraKeys: {
82 "Cmd-Right":"goLineRight",
83 "Cmd-Right":"goLineRight",
83 "End":"goLineRight",
84 "End":"goLineRight",
84 "Cmd-Left":"goLineLeft"
85 "Cmd-Left":"goLineLeft"
85 }
86 }
86 }
87 }
87 };
88 };
88
89
89 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
90 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
90 // by disabling drag/drop altogether on Safari
91 // by disabling drag/drop altogether on Safari
91 // https://github.com/marijnh/CodeMirror/issues/332
92 // https://github.com/marijnh/CodeMirror/issues/332
92 if (utils.browser[0] == "Safari") {
93 if (utils.browser[0] == "Safari") {
93 Cell.options_default.cm_config.dragDrop = false;
94 Cell.options_default.cm_config.dragDrop = false;
94 }
95 }
95
96
96 Cell.prototype.mergeopt = function(_class, options, overwrite){
97 Cell.prototype.mergeopt = function(_class, options, overwrite){
97 options = options || {};
98 options = options || {};
98 overwrite = overwrite || {};
99 overwrite = overwrite || {};
99 return $.extend(true, {}, _class.options_default, options, overwrite);
100 return $.extend(true, {}, _class.options_default, options, overwrite);
100 };
101 };
101
102
102 /**
103 /**
103 * Empty. Subclasses must implement create_element.
104 * Empty. Subclasses must implement create_element.
104 * This should contain all the code to create the DOM element in notebook
105 * This should contain all the code to create the DOM element in notebook
105 * and will be called by Base Class constructor.
106 * and will be called by Base Class constructor.
106 * @method create_element
107 * @method create_element
107 */
108 */
108 Cell.prototype.create_element = function () {
109 Cell.prototype.create_element = function () {
109 };
110 };
110
111
111 Cell.prototype.init_classes = function () {
112 Cell.prototype.init_classes = function () {
112 // Call after this.element exists to initialize the css classes
113 // Call after this.element exists to initialize the css classes
113 // related to selected, rendered and mode.
114 // related to selected, rendered and mode.
114 if (this.selected) {
115 if (this.selected) {
115 this.element.addClass('selected');
116 this.element.addClass('selected');
116 } else {
117 } else {
117 this.element.addClass('unselected');
118 this.element.addClass('unselected');
118 }
119 }
119 if (this.rendered) {
120 if (this.rendered) {
120 this.element.addClass('rendered');
121 this.element.addClass('rendered');
121 } else {
122 } else {
122 this.element.addClass('unrendered');
123 this.element.addClass('unrendered');
123 }
124 }
124 if (this.mode === 'edit') {
125 if (this.mode === 'edit') {
125 this.element.addClass('edit_mode');
126 this.element.addClass('edit_mode');
126 } else {
127 } else {
127 this.element.addClass('command_mode');
128 this.element.addClass('command_mode');
128 }
129 }
129 };
130 };
130
131
131 /**
132 /**
132 * Subclasses can implement override bind_events.
133 * Subclasses can implement override bind_events.
133 * Be carefull to call the parent method when overwriting as it fires event.
134 * Be carefull to call the parent method when overwriting as it fires event.
134 * this will be triggerd after create_element in constructor.
135 * this will be triggerd after create_element in constructor.
135 * @method bind_events
136 * @method bind_events
136 */
137 */
137 Cell.prototype.bind_events = function () {
138 Cell.prototype.bind_events = function () {
138 var that = this;
139 var that = this;
139 // We trigger events so that Cell doesn't have to depend on Notebook.
140 // We trigger events so that Cell doesn't have to depend on Notebook.
140 that.element.click(function (event) {
141 that.element.click(function (event) {
141 if (!that.selected) {
142 if (!that.selected) {
142 that.events.trigger('select.Cell', {'cell':that});
143 that.events.trigger('select.Cell', {'cell':that});
143 }
144 }
144 });
145 });
145 that.element.focusin(function (event) {
146 that.element.focusin(function (event) {
146 if (!that.selected) {
147 if (!that.selected) {
147 that.events.trigger('select.Cell', {'cell':that});
148 that.events.trigger('select.Cell', {'cell':that});
148 }
149 }
149 });
150 });
150 if (this.code_mirror) {
151 if (this.code_mirror) {
151 this.code_mirror.on("change", function(cm, change) {
152 this.code_mirror.on("change", function(cm, change) {
152 that.events.trigger("set_dirty.Notebook", {value: true});
153 that.events.trigger("set_dirty.Notebook", {value: true});
153 });
154 });
154 }
155 }
155 if (this.code_mirror) {
156 if (this.code_mirror) {
156 this.code_mirror.on('focus', function(cm, change) {
157 this.code_mirror.on('focus', function(cm, change) {
157 that.events.trigger('edit_mode.Cell', {cell: that});
158 that.events.trigger('edit_mode.Cell', {cell: that});
158 });
159 });
159 }
160 }
160 if (this.code_mirror) {
161 if (this.code_mirror) {
161 this.code_mirror.on('blur', function(cm, change) {
162 this.code_mirror.on('blur', function(cm, change) {
162 that.events.trigger('command_mode.Cell', {cell: that});
163 that.events.trigger('command_mode.Cell', {cell: that});
163 });
164 });
164 }
165 }
165 };
166 };
166
167
167 /**
168 /**
168 * This method gets called in CodeMirror's onKeyDown/onKeyPress
169 * This method gets called in CodeMirror's onKeyDown/onKeyPress
169 * handlers and is used to provide custom key handling.
170 * handlers and is used to provide custom key handling.
170 *
171 *
171 * To have custom handling, subclasses should override this method, but still call it
172 * To have custom handling, subclasses should override this method, but still call it
172 * in order to process the Edit mode keyboard shortcuts.
173 * in order to process the Edit mode keyboard shortcuts.
173 *
174 *
174 * @method handle_codemirror_keyevent
175 * @method handle_codemirror_keyevent
175 * @param {CodeMirror} editor - The codemirror instance bound to the cell
176 * @param {CodeMirror} editor - The codemirror instance bound to the cell
176 * @param {event} event - key press event which either should or should not be handled by CodeMirror
177 * @param {event} event - key press event which either should or should not be handled by CodeMirror
177 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
178 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
178 */
179 */
179 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
180 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
180 var that = this;
181 var that = this;
181 var shortcuts = this.keyboard_manager.edit_shortcuts;
182 var shortcuts = this.keyboard_manager.edit_shortcuts;
182
183
183 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
184 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
184 // manager will handle it
185 // manager will handle it
185 if (shortcuts.handles(event)) { return true; }
186 if (shortcuts.handles(event)) { return true; }
186
187
187 return false;
188 return false;
188 };
189 };
189
190
190
191
191 /**
192 /**
192 * Triger typsetting of math by mathjax on current cell element
193 * Triger typsetting of math by mathjax on current cell element
193 * @method typeset
194 * @method typeset
194 */
195 */
195 Cell.prototype.typeset = function () {
196 Cell.prototype.typeset = function () {
196 if (window.MathJax) {
197 if (window.MathJax) {
197 var cell_math = this.element.get(0);
198 var cell_math = this.element.get(0);
198 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
199 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
199 }
200 }
200 };
201 };
201
202
202 /**
203 /**
203 * handle cell level logic when a cell is selected
204 * handle cell level logic when a cell is selected
204 * @method select
205 * @method select
205 * @return is the action being taken
206 * @return is the action being taken
206 */
207 */
207 Cell.prototype.select = function () {
208 Cell.prototype.select = function () {
208 if (!this.selected) {
209 if (!this.selected) {
209 this.element.addClass('selected');
210 this.element.addClass('selected');
210 this.element.removeClass('unselected');
211 this.element.removeClass('unselected');
211 this.selected = true;
212 this.selected = true;
212 return true;
213 return true;
213 } else {
214 } else {
214 return false;
215 return false;
215 }
216 }
216 };
217 };
217
218
218 /**
219 /**
219 * handle cell level logic when a cell is unselected
220 * handle cell level logic when a cell is unselected
220 * @method unselect
221 * @method unselect
221 * @return is the action being taken
222 * @return is the action being taken
222 */
223 */
223 Cell.prototype.unselect = function () {
224 Cell.prototype.unselect = function () {
224 if (this.selected) {
225 if (this.selected) {
225 this.element.addClass('unselected');
226 this.element.addClass('unselected');
226 this.element.removeClass('selected');
227 this.element.removeClass('selected');
227 this.selected = false;
228 this.selected = false;
228 return true;
229 return true;
229 } else {
230 } else {
230 return false;
231 return false;
231 }
232 }
232 };
233 };
233
234
234 /**
235 /**
235 * handle cell level logic when a cell is rendered
236 * handle cell level logic when a cell is rendered
236 * @method render
237 * @method render
237 * @return is the action being taken
238 * @return is the action being taken
238 */
239 */
239 Cell.prototype.render = function () {
240 Cell.prototype.render = function () {
240 if (!this.rendered) {
241 if (!this.rendered) {
241 this.element.addClass('rendered');
242 this.element.addClass('rendered');
242 this.element.removeClass('unrendered');
243 this.element.removeClass('unrendered');
243 this.rendered = true;
244 this.rendered = true;
244 return true;
245 return true;
245 } else {
246 } else {
246 return false;
247 return false;
247 }
248 }
248 };
249 };
249
250
250 /**
251 /**
251 * handle cell level logic when a cell is unrendered
252 * handle cell level logic when a cell is unrendered
252 * @method unrender
253 * @method unrender
253 * @return is the action being taken
254 * @return is the action being taken
254 */
255 */
255 Cell.prototype.unrender = function () {
256 Cell.prototype.unrender = function () {
256 if (this.rendered) {
257 if (this.rendered) {
257 this.element.addClass('unrendered');
258 this.element.addClass('unrendered');
258 this.element.removeClass('rendered');
259 this.element.removeClass('rendered');
259 this.rendered = false;
260 this.rendered = false;
260 return true;
261 return true;
261 } else {
262 } else {
262 return false;
263 return false;
263 }
264 }
264 };
265 };
265
266
266 /**
267 /**
267 * Delegates keyboard shortcut handling to either IPython keyboard
268 * Delegates keyboard shortcut handling to either IPython keyboard
268 * manager when in command mode, or CodeMirror when in edit mode
269 * manager when in command mode, or CodeMirror when in edit mode
269 *
270 *
270 * @method handle_keyevent
271 * @method handle_keyevent
271 * @param {CodeMirror} editor - The codemirror instance bound to the cell
272 * @param {CodeMirror} editor - The codemirror instance bound to the cell
272 * @param {event} - key event to be handled
273 * @param {event} - key event to be handled
273 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
274 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
274 */
275 */
275 Cell.prototype.handle_keyevent = function (editor, event) {
276 Cell.prototype.handle_keyevent = function (editor, event) {
276
277
277 // console.log('CM', this.mode, event.which, event.type)
278 // console.log('CM', this.mode, event.which, event.type)
278
279
279 if (this.mode === 'command') {
280 if (this.mode === 'command') {
280 return true;
281 return true;
281 } else if (this.mode === 'edit') {
282 } else if (this.mode === 'edit') {
282 return this.handle_codemirror_keyevent(editor, event);
283 return this.handle_codemirror_keyevent(editor, event);
283 }
284 }
284 };
285 };
285
286
286 /**
287 /**
287 * @method at_top
288 * @method at_top
288 * @return {Boolean}
289 * @return {Boolean}
289 */
290 */
290 Cell.prototype.at_top = function () {
291 Cell.prototype.at_top = function () {
291 var cm = this.code_mirror;
292 var cm = this.code_mirror;
292 var cursor = cm.getCursor();
293 var cursor = cm.getCursor();
293 if (cursor.line === 0 && cursor.ch === 0) {
294 if (cursor.line === 0 && cursor.ch === 0) {
294 return true;
295 return true;
295 }
296 }
296 return false;
297 return false;
297 };
298 };
298
299
299 /**
300 /**
300 * @method at_bottom
301 * @method at_bottom
301 * @return {Boolean}
302 * @return {Boolean}
302 * */
303 * */
303 Cell.prototype.at_bottom = function () {
304 Cell.prototype.at_bottom = function () {
304 var cm = this.code_mirror;
305 var cm = this.code_mirror;
305 var cursor = cm.getCursor();
306 var cursor = cm.getCursor();
306 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
307 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
307 return true;
308 return true;
308 }
309 }
309 return false;
310 return false;
310 };
311 };
311
312
312 /**
313 /**
313 * enter the command mode for the cell
314 * enter the command mode for the cell
314 * @method command_mode
315 * @method command_mode
315 * @return is the action being taken
316 * @return is the action being taken
316 */
317 */
317 Cell.prototype.command_mode = function () {
318 Cell.prototype.command_mode = function () {
318 if (this.mode !== 'command') {
319 if (this.mode !== 'command') {
319 this.element.addClass('command_mode');
320 this.element.addClass('command_mode');
320 this.element.removeClass('edit_mode');
321 this.element.removeClass('edit_mode');
321 this.mode = 'command';
322 this.mode = 'command';
322 return true;
323 return true;
323 } else {
324 } else {
324 return false;
325 return false;
325 }
326 }
326 };
327 };
327
328
328 /**
329 /**
329 * enter the edit mode for the cell
330 * enter the edit mode for the cell
330 * @method command_mode
331 * @method command_mode
331 * @return is the action being taken
332 * @return is the action being taken
332 */
333 */
333 Cell.prototype.edit_mode = function () {
334 Cell.prototype.edit_mode = function () {
334 if (this.mode !== 'edit') {
335 if (this.mode !== 'edit') {
335 this.element.addClass('edit_mode');
336 this.element.addClass('edit_mode');
336 this.element.removeClass('command_mode');
337 this.element.removeClass('command_mode');
337 this.mode = 'edit';
338 this.mode = 'edit';
338 return true;
339 return true;
339 } else {
340 } else {
340 return false;
341 return false;
341 }
342 }
342 };
343 };
343
344
344 /**
345 /**
345 * Focus the cell in the DOM sense
346 * Focus the cell in the DOM sense
346 * @method focus_cell
347 * @method focus_cell
347 */
348 */
348 Cell.prototype.focus_cell = function () {
349 Cell.prototype.focus_cell = function () {
349 this.element.focus();
350 this.element.focus();
350 };
351 };
351
352
352 /**
353 /**
353 * Focus the editor area so a user can type
354 * Focus the editor area so a user can type
354 *
355 *
355 * NOTE: If codemirror is focused via a mouse click event, you don't want to
356 * NOTE: If codemirror is focused via a mouse click event, you don't want to
356 * call this because it will cause a page jump.
357 * call this because it will cause a page jump.
357 * @method focus_editor
358 * @method focus_editor
358 */
359 */
359 Cell.prototype.focus_editor = function () {
360 Cell.prototype.focus_editor = function () {
360 this.refresh();
361 this.refresh();
361 this.code_mirror.focus();
362 this.code_mirror.focus();
362 };
363 };
363
364
364 /**
365 /**
365 * Refresh codemirror instance
366 * Refresh codemirror instance
366 * @method refresh
367 * @method refresh
367 */
368 */
368 Cell.prototype.refresh = function () {
369 Cell.prototype.refresh = function () {
369 this.code_mirror.refresh();
370 this.code_mirror.refresh();
370 };
371 };
371
372
372 /**
373 /**
373 * should be overritten by subclass
374 * should be overritten by subclass
374 * @method get_text
375 * @method get_text
375 */
376 */
376 Cell.prototype.get_text = function () {
377 Cell.prototype.get_text = function () {
377 };
378 };
378
379
379 /**
380 /**
380 * should be overritten by subclass
381 * should be overritten by subclass
381 * @method set_text
382 * @method set_text
382 * @param {string} text
383 * @param {string} text
383 */
384 */
384 Cell.prototype.set_text = function (text) {
385 Cell.prototype.set_text = function (text) {
385 };
386 };
386
387
387 /**
388 /**
388 * should be overritten by subclass
389 * should be overritten by subclass
389 * serialise cell to json.
390 * serialise cell to json.
390 * @method toJSON
391 * @method toJSON
391 **/
392 **/
392 Cell.prototype.toJSON = function () {
393 Cell.prototype.toJSON = function () {
393 var data = {};
394 var data = {};
394 data.metadata = this.metadata;
395 data.metadata = this.metadata;
395 data.cell_type = this.cell_type;
396 data.cell_type = this.cell_type;
396 return data;
397 return data;
397 };
398 };
398
399
399
400
400 /**
401 /**
401 * should be overritten by subclass
402 * should be overritten by subclass
402 * @method fromJSON
403 * @method fromJSON
403 **/
404 **/
404 Cell.prototype.fromJSON = function (data) {
405 Cell.prototype.fromJSON = function (data) {
405 if (data.metadata !== undefined) {
406 if (data.metadata !== undefined) {
406 this.metadata = data.metadata;
407 this.metadata = data.metadata;
407 }
408 }
408 this.celltoolbar.rebuild();
409 this.celltoolbar.rebuild();
409 };
410 };
410
411
411
412
412 /**
413 /**
413 * can the cell be split into two cells
414 * can the cell be split into two cells
414 * @method is_splittable
415 * @method is_splittable
415 **/
416 **/
416 Cell.prototype.is_splittable = function () {
417 Cell.prototype.is_splittable = function () {
417 return true;
418 return true;
418 };
419 };
419
420
420
421
421 /**
422 /**
422 * can the cell be merged with other cells
423 * can the cell be merged with other cells
423 * @method is_mergeable
424 * @method is_mergeable
424 **/
425 **/
425 Cell.prototype.is_mergeable = function () {
426 Cell.prototype.is_mergeable = function () {
426 return true;
427 return true;
427 };
428 };
428
429
429
430
430 /**
431 /**
431 * @return {String} - the text before the cursor
432 * @return {String} - the text before the cursor
432 * @method get_pre_cursor
433 * @method get_pre_cursor
433 **/
434 **/
434 Cell.prototype.get_pre_cursor = function () {
435 Cell.prototype.get_pre_cursor = function () {
435 var cursor = this.code_mirror.getCursor();
436 var cursor = this.code_mirror.getCursor();
436 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
437 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
437 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
438 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
438 return text;
439 return text;
439 };
440 };
440
441
441
442
442 /**
443 /**
443 * @return {String} - the text after the cursor
444 * @return {String} - the text after the cursor
444 * @method get_post_cursor
445 * @method get_post_cursor
445 **/
446 **/
446 Cell.prototype.get_post_cursor = function () {
447 Cell.prototype.get_post_cursor = function () {
447 var cursor = this.code_mirror.getCursor();
448 var cursor = this.code_mirror.getCursor();
448 var last_line_num = this.code_mirror.lineCount()-1;
449 var last_line_num = this.code_mirror.lineCount()-1;
449 var last_line_len = this.code_mirror.getLine(last_line_num).length;
450 var last_line_len = this.code_mirror.getLine(last_line_num).length;
450 var end = {line:last_line_num, ch:last_line_len};
451 var end = {line:last_line_num, ch:last_line_len};
451 var text = this.code_mirror.getRange(cursor, end);
452 var text = this.code_mirror.getRange(cursor, end);
452 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
453 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
453 return text;
454 return text;
454 };
455 };
455
456
456 /**
457 /**
457 * Show/Hide CodeMirror LineNumber
458 * Show/Hide CodeMirror LineNumber
458 * @method show_line_numbers
459 * @method show_line_numbers
459 *
460 *
460 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
461 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
461 **/
462 **/
462 Cell.prototype.show_line_numbers = function (value) {
463 Cell.prototype.show_line_numbers = function (value) {
463 this.code_mirror.setOption('lineNumbers', value);
464 this.code_mirror.setOption('lineNumbers', value);
464 this.code_mirror.refresh();
465 this.code_mirror.refresh();
465 };
466 };
466
467
467 /**
468 /**
468 * Toggle CodeMirror LineNumber
469 * Toggle CodeMirror LineNumber
469 * @method toggle_line_numbers
470 * @method toggle_line_numbers
470 **/
471 **/
471 Cell.prototype.toggle_line_numbers = function () {
472 Cell.prototype.toggle_line_numbers = function () {
472 var val = this.code_mirror.getOption('lineNumbers');
473 var val = this.code_mirror.getOption('lineNumbers');
473 this.show_line_numbers(!val);
474 this.show_line_numbers(!val);
474 };
475 };
475
476
476 /**
477 /**
477 * Force codemirror highlight mode
478 * Force codemirror highlight mode
478 * @method force_highlight
479 * @method force_highlight
479 * @param {object} - CodeMirror mode
480 * @param {object} - CodeMirror mode
480 **/
481 **/
481 Cell.prototype.force_highlight = function(mode) {
482 Cell.prototype.force_highlight = function(mode) {
482 this.user_highlight = mode;
483 this.user_highlight = mode;
483 this.auto_highlight();
484 this.auto_highlight();
484 };
485 };
485
486
486 /**
487 /**
487 * Try to autodetect cell highlight mode, or use selected mode
488 * Try to autodetect cell highlight mode, or use selected mode
488 * @methods _auto_highlight
489 * @methods _auto_highlight
489 * @private
490 * @private
490 * @param {String|object|undefined} - CodeMirror mode | 'auto'
491 * @param {String|object|undefined} - CodeMirror mode | 'auto'
491 **/
492 **/
492 Cell.prototype._auto_highlight = function (modes) {
493 Cell.prototype._auto_highlight = function (modes) {
493 //Here we handle manually selected modes
494 //Here we handle manually selected modes
494 var mode;
495 var mode;
495 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
496 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
496 {
497 {
497 mode = this.user_highlight;
498 mode = this.user_highlight;
498 CodeMirror.autoLoadMode(this.code_mirror, mode);
499 CodeMirror.autoLoadMode(this.code_mirror, mode);
499 this.code_mirror.setOption('mode', mode);
500 this.code_mirror.setOption('mode', mode);
500 return;
501 return;
501 }
502 }
502 var current_mode = this.code_mirror.getOption('mode', mode);
503 var current_mode = this.code_mirror.getOption('mode', mode);
503 var first_line = this.code_mirror.getLine(0);
504 var first_line = this.code_mirror.getLine(0);
504 // loop on every pairs
505 // loop on every pairs
505 for(mode in modes) {
506 for(mode in modes) {
506 var regs = modes[mode].reg;
507 var regs = modes[mode].reg;
507 // only one key every time but regexp can't be keys...
508 // only one key every time but regexp can't be keys...
508 for(var i=0; i<regs.length; i++) {
509 for(var i=0; i<regs.length; i++) {
509 // here we handle non magic_modes
510 // here we handle non magic_modes
510 if(first_line.match(regs[i]) !== null) {
511 if(first_line.match(regs[i]) !== null) {
511 if(current_mode == mode){
512 if(current_mode == mode){
512 return;
513 return;
513 }
514 }
514 if (mode.search('magic_') !== 0) {
515 if (mode.search('magic_') !== 0) {
515 this.code_mirror.setOption('mode', mode);
516 this.code_mirror.setOption('mode', mode);
516 CodeMirror.autoLoadMode(this.code_mirror, mode);
517 CodeMirror.autoLoadMode(this.code_mirror, mode);
517 return;
518 return;
518 }
519 }
519 var open = modes[mode].open || "%%";
520 var open = modes[mode].open || "%%";
520 var close = modes[mode].close || "%%end";
521 var close = modes[mode].close || "%%end";
521 var mmode = mode;
522 var mmode = mode;
522 mode = mmode.substr(6);
523 mode = mmode.substr(6);
523 if(current_mode == mode){
524 if(current_mode == mode){
524 return;
525 return;
525 }
526 }
526 CodeMirror.autoLoadMode(this.code_mirror, mode);
527 CodeMirror.autoLoadMode(this.code_mirror, mode);
527 // create on the fly a mode that swhitch between
528 // create on the fly a mode that swhitch between
528 // plain/text and smth else otherwise `%%` is
529 // plain/text and smth else otherwise `%%` is
529 // source of some highlight issues.
530 // source of some highlight issues.
530 // we use patchedGetMode to circumvent a bug in CM
531 // we use patchedGetMode to circumvent a bug in CM
531 CodeMirror.defineMode(mmode , function(config) {
532 CodeMirror.defineMode(mmode , function(config) {
532 return CodeMirror.multiplexingMode(
533 return CodeMirror.multiplexingMode(
533 CodeMirror.patchedGetMode(config, 'text/plain'),
534 CodeMirror.patchedGetMode(config, 'text/plain'),
534 // always set someting on close
535 // always set someting on close
535 {open: open, close: close,
536 {open: open, close: close,
536 mode: CodeMirror.patchedGetMode(config, mode),
537 mode: CodeMirror.patchedGetMode(config, mode),
537 delimStyle: "delimit"
538 delimStyle: "delimit"
538 }
539 }
539 );
540 );
540 });
541 });
541 this.code_mirror.setOption('mode', mmode);
542 this.code_mirror.setOption('mode', mmode);
542 return;
543 return;
543 }
544 }
544 }
545 }
545 }
546 }
546 // fallback on default
547 // fallback on default
547 var default_mode;
548 var default_mode;
548 try {
549 try {
549 default_mode = this._options.cm_config.mode;
550 default_mode = this._options.cm_config.mode;
550 } catch(e) {
551 } catch(e) {
551 default_mode = 'text/plain';
552 default_mode = 'text/plain';
552 }
553 }
553 if( current_mode === default_mode){
554 if( current_mode === default_mode){
554 return;
555 return;
555 }
556 }
556 this.code_mirror.setOption('mode', default_mode);
557 this.code_mirror.setOption('mode', default_mode);
557 };
558 };
558
559
559 // Backwards compatability.
560 // Backwards compatibility.
560 IPython.Cell = Cell;
561 IPython.Cell = Cell;
561
562
562 return {'Cell': Cell};
563 return {'Cell': Cell};
563 });
564 });
@@ -1,417 +1,420 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 ], function(IPython, $) {
7 ], function(IPython, $) {
8 "use strict";
8 "use strict";
9
9
10 var CellToolbar = function (options) {
10 var CellToolbar = function (options) {
11 // Constructor
11 // Constructor
12 //
12 //
13 // Parameters:
13 // Parameters:
14 // options: dictionary
14 // options: dictionary
15 // Dictionary of keyword arguments.
15 // Dictionary of keyword arguments.
16 // events: $(Events) instance
16 // events: $(Events) instance
17 // cell: Cell instance
17 // cell: Cell instance
18 // notebook: Notebook instance
18 // notebook: Notebook instance
19 //
20 // TODO: This leaks, when cell are deleted
21 // There is still a reference to each celltoolbars.
19 CellToolbar._instances.push(this);
22 CellToolbar._instances.push(this);
20 this.notebook = options.notebook;
23 this.notebook = options.notebook;
21 this.events = options.events;
24 this.events = options.events;
22 this.cell = options.cell;
25 this.cell = options.cell;
23 this.create_element();
26 this.create_element();
24 this.rebuild();
27 this.rebuild();
25 return this;
28 return this;
26 };
29 };
27
30
28
31
29 CellToolbar.prototype.create_element = function () {
32 CellToolbar.prototype.create_element = function () {
30 this.inner_element = $('<div/>').addClass('celltoolbar');
33 this.inner_element = $('<div/>').addClass('celltoolbar');
31 this.element = $('<div/>').addClass('ctb_hideshow')
34 this.element = $('<div/>').addClass('ctb_hideshow')
32 .append(this.inner_element);
35 .append(this.inner_element);
33 };
36 };
34
37
35
38
36 // The default css style for the outer celltoolbar div
39 // The default css style for the outer celltoolbar div
37 // (ctb_hideshow) is display: none.
40 // (ctb_hideshow) is display: none.
38 // To show the cell toolbar, *both* of the following conditions must be met:
41 // To show the cell toolbar, *both* of the following conditions must be met:
39 // - A parent container has class `ctb_global_show`
42 // - A parent container has class `ctb_global_show`
40 // - The celltoolbar has the class `ctb_show`
43 // - The celltoolbar has the class `ctb_show`
41 // This allows global show/hide, as well as per-cell show/hide.
44 // This allows global show/hide, as well as per-cell show/hide.
42
45
43 CellToolbar.global_hide = function () {
46 CellToolbar.global_hide = function () {
44 $('body').removeClass('ctb_global_show');
47 $('body').removeClass('ctb_global_show');
45 };
48 };
46
49
47
50
48 CellToolbar.global_show = function () {
51 CellToolbar.global_show = function () {
49 $('body').addClass('ctb_global_show');
52 $('body').addClass('ctb_global_show');
50 };
53 };
51
54
52
55
53 CellToolbar.prototype.hide = function () {
56 CellToolbar.prototype.hide = function () {
54 this.element.removeClass('ctb_show');
57 this.element.removeClass('ctb_show');
55 };
58 };
56
59
57
60
58 CellToolbar.prototype.show = function () {
61 CellToolbar.prototype.show = function () {
59 this.element.addClass('ctb_show');
62 this.element.addClass('ctb_show');
60 };
63 };
61
64
62
65
63 /**
66 /**
64 * Class variable that should contain a dict of all available callback
67 * Class variable that should contain a dict of all available callback
65 * we need to think of wether or not we allow nested namespace
68 * we need to think of wether or not we allow nested namespace
66 * @property _callback_dict
69 * @property _callback_dict
67 * @private
70 * @private
68 * @static
71 * @static
69 * @type Dict
72 * @type Dict
70 */
73 */
71 CellToolbar._callback_dict = {};
74 CellToolbar._callback_dict = {};
72
75
73
76
74 /**
77 /**
75 * Class variable that should contain the reverse order list of the button
78 * Class variable that should contain the reverse order list of the button
76 * to add to the toolbar of each cell
79 * to add to the toolbar of each cell
77 * @property _ui_controls_list
80 * @property _ui_controls_list
78 * @private
81 * @private
79 * @static
82 * @static
80 * @type List
83 * @type List
81 */
84 */
82 CellToolbar._ui_controls_list = [];
85 CellToolbar._ui_controls_list = [];
83
86
84
87
85 /**
88 /**
86 * Class variable that should contain the CellToolbar instances for each
89 * Class variable that should contain the CellToolbar instances for each
87 * cell of the notebook
90 * cell of the notebook
88 *
91 *
89 * @private
92 * @private
90 * @property _instances
93 * @property _instances
91 * @static
94 * @static
92 * @type List
95 * @type List
93 */
96 */
94 CellToolbar._instances = [];
97 CellToolbar._instances = [];
95
98
96
99
97 /**
100 /**
98 * keep a list of all the available presets for the toolbar
101 * keep a list of all the available presets for the toolbar
99 * @private
102 * @private
100 * @property _presets
103 * @property _presets
101 * @static
104 * @static
102 * @type Dict
105 * @type Dict
103 */
106 */
104 CellToolbar._presets = {};
107 CellToolbar._presets = {};
105
108
106
109
107 // this is by design not a prototype.
110 // this is by design not a prototype.
108 /**
111 /**
109 * Register a callback to create an UI element in a cell toolbar.
112 * Register a callback to create an UI element in a cell toolbar.
110 * @method register_callback
113 * @method register_callback
111 * @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name
114 * @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name
112 * for easier sorting and avoid collision
115 * for easier sorting and avoid collision
113 * @param callback {function(div, cell)} callback that will be called to generate the ui element
116 * @param callback {function(div, cell)} callback that will be called to generate the ui element
114 * @param [cell_types] {List of String|undefined} optional list of cell types. If present the UI element
117 * @param [cell_types] {List of String|undefined} optional list of cell types. If present the UI element
115 * will be added only to cells of types in the list.
118 * will be added only to cells of types in the list.
116 *
119 *
117 *
120 *
118 * The callback will receive the following element :
121 * The callback will receive the following element :
119 *
122 *
120 * * a div in which to add element.
123 * * a div in which to add element.
121 * * the cell it is responsible from
124 * * the cell it is responsible from
122 *
125 *
123 * @example
126 * @example
124 *
127 *
125 * Example that create callback for a button that toggle between `true` and `false` label,
128 * Example that create callback for a button that toggle between `true` and `false` label,
126 * with the metadata under the key 'foo' to reflect the status of the button.
129 * with the metadata under the key 'foo' to reflect the status of the button.
127 *
130 *
128 * // first param reference to a DOM div
131 * // first param reference to a DOM div
129 * // second param reference to the cell.
132 * // second param reference to the cell.
130 * var toggle = function(div, cell) {
133 * var toggle = function(div, cell) {
131 * var button_container = $(div)
134 * var button_container = $(div)
132 *
135 *
133 * // let's create a button that show the current value of the metadata
136 * // let's create a button that show the current value of the metadata
134 * var button = $('<div/>').button({label:String(cell.metadata.foo)});
137 * var button = $('<div/>').button({label:String(cell.metadata.foo)});
135 *
138 *
136 * // On click, change the metadata value and update the button label
139 * // On click, change the metadata value and update the button label
137 * button.click(function(){
140 * button.click(function(){
138 * var v = cell.metadata.foo;
141 * var v = cell.metadata.foo;
139 * cell.metadata.foo = !v;
142 * cell.metadata.foo = !v;
140 * button.button("option", "label", String(!v));
143 * button.button("option", "label", String(!v));
141 * })
144 * })
142 *
145 *
143 * // add the button to the DOM div.
146 * // add the button to the DOM div.
144 * button_container.append(button);
147 * button_container.append(button);
145 * }
148 * }
146 *
149 *
147 * // now we register the callback under the name `foo` to give the
150 * // now we register the callback under the name `foo` to give the
148 * // user the ability to use it later
151 * // user the ability to use it later
149 * CellToolbar.register_callback('foo', toggle);
152 * CellToolbar.register_callback('foo', toggle);
150 */
153 */
151 CellToolbar.register_callback = function(name, callback, cell_types) {
154 CellToolbar.register_callback = function(name, callback, cell_types) {
152 // Overwrite if it already exists.
155 // Overwrite if it already exists.
153 CellToolbar._callback_dict[name] = cell_types ? {callback: callback, cell_types: cell_types} : callback;
156 CellToolbar._callback_dict[name] = cell_types ? {callback: callback, cell_types: cell_types} : callback;
154 };
157 };
155
158
156
159
157 /**
160 /**
158 * Register a preset of UI element in a cell toolbar.
161 * Register a preset of UI element in a cell toolbar.
159 * Not supported Yet.
162 * Not supported Yet.
160 * @method register_preset
163 * @method register_preset
161 * @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name
164 * @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name
162 * for easier sorting and avoid collision
165 * for easier sorting and avoid collision
163 * @param preset_list {List of String} reverse order of the button in the toolbar. Each String of the list
166 * @param preset_list {List of String} reverse order of the button in the toolbar. Each String of the list
164 * should correspond to a name of a registerd callback.
167 * should correspond to a name of a registerd callback.
165 *
168 *
166 * @private
169 * @private
167 * @example
170 * @example
168 *
171 *
169 * CellToolbar.register_callback('foo.c1', function(div, cell){...});
172 * CellToolbar.register_callback('foo.c1', function(div, cell){...});
170 * CellToolbar.register_callback('foo.c2', function(div, cell){...});
173 * CellToolbar.register_callback('foo.c2', function(div, cell){...});
171 * CellToolbar.register_callback('foo.c3', function(div, cell){...});
174 * CellToolbar.register_callback('foo.c3', function(div, cell){...});
172 * CellToolbar.register_callback('foo.c4', function(div, cell){...});
175 * CellToolbar.register_callback('foo.c4', function(div, cell){...});
173 * CellToolbar.register_callback('foo.c5', function(div, cell){...});
176 * CellToolbar.register_callback('foo.c5', function(div, cell){...});
174 *
177 *
175 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
178 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
176 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
179 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
177 */
180 */
178 CellToolbar.register_preset = function(name, preset_list, notebook, events) {
181 CellToolbar.register_preset = function(name, preset_list, notebook, events) {
179 CellToolbar._presets[name] = preset_list;
182 CellToolbar._presets[name] = preset_list;
180 events.trigger('preset_added.CellToolbar', {name: name});
183 events.trigger('preset_added.CellToolbar', {name: name});
181 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
184 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
182 // In that case, activate the preset if needed.
185 // In that case, activate the preset if needed.
183 if (notebook && notebook.metadata && notebook.metadata.celltoolbar === name)
186 if (notebook && notebook.metadata && notebook.metadata.celltoolbar === name)
184 CellToolbar.activate_preset(name, events);
187 CellToolbar.activate_preset(name, events);
185 };
188 };
186
189
187
190
188 /**
191 /**
189 * List the names of the presets that are currently registered.
192 * List the names of the presets that are currently registered.
190 *
193 *
191 * @method list_presets
194 * @method list_presets
192 * @static
195 * @static
193 */
196 */
194 CellToolbar.list_presets = function() {
197 CellToolbar.list_presets = function() {
195 var keys = [];
198 var keys = [];
196 for (var k in CellToolbar._presets) {
199 for (var k in CellToolbar._presets) {
197 keys.push(k);
200 keys.push(k);
198 }
201 }
199 return keys;
202 return keys;
200 };
203 };
201
204
202
205
203 /**
206 /**
204 * Activate an UI preset from `register_preset`
207 * Activate an UI preset from `register_preset`
205 *
208 *
206 * This does not update the selection UI.
209 * This does not update the selection UI.
207 *
210 *
208 * @method activate_preset
211 * @method activate_preset
209 * @param preset_name {String} string corresponding to the preset name
212 * @param preset_name {String} string corresponding to the preset name
210 *
213 *
211 * @static
214 * @static
212 * @private
215 * @private
213 * @example
216 * @example
214 *
217 *
215 * CellToolbar.activate_preset('foo.foo_preset1');
218 * CellToolbar.activate_preset('foo.foo_preset1');
216 */
219 */
217 CellToolbar.activate_preset = function(preset_name, events){
220 CellToolbar.activate_preset = function(preset_name, events){
218 var preset = CellToolbar._presets[preset_name];
221 var preset = CellToolbar._presets[preset_name];
219
222
220 if(preset !== undefined){
223 if(preset !== undefined){
221 CellToolbar._ui_controls_list = preset;
224 CellToolbar._ui_controls_list = preset;
222 CellToolbar.rebuild_all();
225 CellToolbar.rebuild_all();
223 }
226 }
224
227
225 if (events) {
228 if (events) {
226 events.trigger('preset_activated.CellToolbar', {name: preset_name});
229 events.trigger('preset_activated.CellToolbar', {name: preset_name});
227 }
230 }
228 };
231 };
229
232
230
233
231 /**
234 /**
232 * This should be called on the class and not on a instance as it will trigger
235 * This should be called on the class and not on a instance as it will trigger
233 * rebuild of all the instances.
236 * rebuild of all the instances.
234 * @method rebuild_all
237 * @method rebuild_all
235 * @static
238 * @static
236 *
239 *
237 */
240 */
238 CellToolbar.rebuild_all = function(){
241 CellToolbar.rebuild_all = function(){
239 for(var i=0; i < CellToolbar._instances.length; i++){
242 for(var i=0; i < CellToolbar._instances.length; i++){
240 CellToolbar._instances[i].rebuild();
243 CellToolbar._instances[i].rebuild();
241 }
244 }
242 };
245 };
243
246
244 /**
247 /**
245 * Rebuild all the button on the toolbar to update its state.
248 * Rebuild all the button on the toolbar to update its state.
246 * @method rebuild
249 * @method rebuild
247 */
250 */
248 CellToolbar.prototype.rebuild = function(){
251 CellToolbar.prototype.rebuild = function(){
249 // strip evrything from the div
252 // strip evrything from the div
250 // which is probably inner_element
253 // which is probably inner_element
251 // or this.element.
254 // or this.element.
252 this.inner_element.empty();
255 this.inner_element.empty();
253 this.ui_controls_list = [];
256 this.ui_controls_list = [];
254
257
255 var callbacks = CellToolbar._callback_dict;
258 var callbacks = CellToolbar._callback_dict;
256 var preset = CellToolbar._ui_controls_list;
259 var preset = CellToolbar._ui_controls_list;
257 // Yes we iterate on the class variable, not the instance one.
260 // Yes we iterate on the class variable, not the instance one.
258 for (var i=0; i < preset.length; i++) {
261 for (var i=0; i < preset.length; i++) {
259 var key = preset[i];
262 var key = preset[i];
260 var callback = callbacks[key];
263 var callback = callbacks[key];
261 if (!callback) continue;
264 if (!callback) continue;
262
265
263 if (typeof callback === 'object') {
266 if (typeof callback === 'object') {
264 if (callback.cell_types.indexOf(this.cell.cell_type) === -1) continue;
267 if (callback.cell_types.indexOf(this.cell.cell_type) === -1) continue;
265 callback = callback.callback;
268 callback = callback.callback;
266 }
269 }
267
270
268 var local_div = $('<div/>').addClass('button_container');
271 var local_div = $('<div/>').addClass('button_container');
269 try {
272 try {
270 callback(local_div, this.cell, this);
273 callback(local_div, this.cell, this);
271 this.ui_controls_list.push(key);
274 this.ui_controls_list.push(key);
272 } catch (e) {
275 } catch (e) {
273 console.log("Error in cell toolbar callback " + key, e);
276 console.log("Error in cell toolbar callback " + key, e);
274 continue;
277 continue;
275 }
278 }
276 // only append if callback succeeded.
279 // only append if callback succeeded.
277 this.inner_element.append(local_div);
280 this.inner_element.append(local_div);
278 }
281 }
279
282
280 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
283 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
281 if (!this.ui_controls_list.length || (this.cell.cell_type != 'code' && this.cell.rendered)) {
284 if (!this.ui_controls_list.length || (this.cell.cell_type != 'code' && this.cell.rendered)) {
282 this.hide();
285 this.hide();
283 } else {
286 } else {
284 this.show();
287 this.show();
285 }
288 }
286 };
289 };
287
290
288
291
289 /**
292 /**
290 */
293 */
291 CellToolbar.utils = {};
294 CellToolbar.utils = {};
292
295
293
296
294 /**
297 /**
295 * A utility function to generate bindings between a checkbox and cell/metadata
298 * A utility function to generate bindings between a checkbox and cell/metadata
296 * @method utils.checkbox_ui_generator
299 * @method utils.checkbox_ui_generator
297 * @static
300 * @static
298 *
301 *
299 * @param name {string} Label in front of the checkbox
302 * @param name {string} Label in front of the checkbox
300 * @param setter {function( cell, newValue )}
303 * @param setter {function( cell, newValue )}
301 * A setter method to set the newValue
304 * A setter method to set the newValue
302 * @param getter {function( cell )}
305 * @param getter {function( cell )}
303 * A getter methods which return the current value.
306 * A getter methods which return the current value.
304 *
307 *
305 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
308 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
306 *
309 *
307 * @example
310 * @example
308 *
311 *
309 * An exmple that bind the subkey `slideshow.isSectionStart` to a checkbox with a `New Slide` label
312 * An exmple that bind the subkey `slideshow.isSectionStart` to a checkbox with a `New Slide` label
310 *
313 *
311 * var newSlide = CellToolbar.utils.checkbox_ui_generator('New Slide',
314 * var newSlide = CellToolbar.utils.checkbox_ui_generator('New Slide',
312 * // setter
315 * // setter
313 * function(cell, value){
316 * function(cell, value){
314 * // we check that the slideshow namespace exist and create it if needed
317 * // we check that the slideshow namespace exist and create it if needed
315 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
318 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
316 * // set the value
319 * // set the value
317 * cell.metadata.slideshow.isSectionStart = value
320 * cell.metadata.slideshow.isSectionStart = value
318 * },
321 * },
319 * //geter
322 * //geter
320 * function(cell){ var ns = cell.metadata.slideshow;
323 * function(cell){ var ns = cell.metadata.slideshow;
321 * // if the slideshow namespace does not exist return `undefined`
324 * // if the slideshow namespace does not exist return `undefined`
322 * // (will be interpreted as `false` by checkbox) otherwise
325 * // (will be interpreted as `false` by checkbox) otherwise
323 * // return the value
326 * // return the value
324 * return (ns == undefined)? undefined: ns.isSectionStart
327 * return (ns == undefined)? undefined: ns.isSectionStart
325 * }
328 * }
326 * );
329 * );
327 *
330 *
328 * CellToolbar.register_callback('newSlide', newSlide);
331 * CellToolbar.register_callback('newSlide', newSlide);
329 *
332 *
330 */
333 */
331 CellToolbar.utils.checkbox_ui_generator = function(name, setter, getter){
334 CellToolbar.utils.checkbox_ui_generator = function(name, setter, getter){
332 return function(div, cell, celltoolbar) {
335 return function(div, cell, celltoolbar) {
333 var button_container = $(div);
336 var button_container = $(div);
334
337
335 var chkb = $('<input/>').attr('type', 'checkbox');
338 var chkb = $('<input/>').attr('type', 'checkbox');
336 var lbl = $('<label/>').append($('<span/>').text(name));
339 var lbl = $('<label/>').append($('<span/>').text(name));
337 lbl.append(chkb);
340 lbl.append(chkb);
338 chkb.attr("checked", getter(cell));
341 chkb.attr("checked", getter(cell));
339
342
340 chkb.click(function(){
343 chkb.click(function(){
341 var v = getter(cell);
344 var v = getter(cell);
342 setter(cell, !v);
345 setter(cell, !v);
343 chkb.attr("checked", !v);
346 chkb.attr("checked", !v);
344 });
347 });
345 button_container.append($('<span/>').append(lbl));
348 button_container.append($('<span/>').append(lbl));
346 };
349 };
347 };
350 };
348
351
349
352
350 /**
353 /**
351 * A utility function to generate bindings between a dropdown list cell
354 * A utility function to generate bindings between a dropdown list cell
352 * @method utils.select_ui_generator
355 * @method utils.select_ui_generator
353 * @static
356 * @static
354 *
357 *
355 * @param list_list {list of sublist} List of sublist of metadata value and name in the dropdown list.
358 * @param list_list {list of sublist} List of sublist of metadata value and name in the dropdown list.
356 * subslit shoud contain 2 element each, first a string that woul be displayed in the dropdown list,
359 * subslit shoud contain 2 element each, first a string that woul be displayed in the dropdown list,
357 * and second the corresponding value to be passed to setter/return by getter. the corresponding value
360 * and second the corresponding value to be passed to setter/return by getter. the corresponding value
358 * should not be "undefined" or behavior can be unexpected.
361 * should not be "undefined" or behavior can be unexpected.
359 * @param setter {function( cell, newValue )}
362 * @param setter {function( cell, newValue )}
360 * A setter method to set the newValue
363 * A setter method to set the newValue
361 * @param getter {function( cell )}
364 * @param getter {function( cell )}
362 * A getter methods which return the current value of the metadata.
365 * A getter methods which return the current value of the metadata.
363 * @param [label=""] {String} optionnal label for the dropdown menu
366 * @param [label=""] {String} optionnal label for the dropdown menu
364 *
367 *
365 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
368 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
366 *
369 *
367 * @example
370 * @example
368 *
371 *
369 * var select_type = CellToolbar.utils.select_ui_generator([
372 * var select_type = CellToolbar.utils.select_ui_generator([
370 * ["<None>" , "None" ],
373 * ["<None>" , "None" ],
371 * ["Header Slide" , "header_slide" ],
374 * ["Header Slide" , "header_slide" ],
372 * ["Slide" , "slide" ],
375 * ["Slide" , "slide" ],
373 * ["Fragment" , "fragment" ],
376 * ["Fragment" , "fragment" ],
374 * ["Skip" , "skip" ],
377 * ["Skip" , "skip" ],
375 * ],
378 * ],
376 * // setter
379 * // setter
377 * function(cell, value){
380 * function(cell, value){
378 * // we check that the slideshow namespace exist and create it if needed
381 * // we check that the slideshow namespace exist and create it if needed
379 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
382 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
380 * // set the value
383 * // set the value
381 * cell.metadata.slideshow.slide_type = value
384 * cell.metadata.slideshow.slide_type = value
382 * },
385 * },
383 * //geter
386 * //geter
384 * function(cell){ var ns = cell.metadata.slideshow;
387 * function(cell){ var ns = cell.metadata.slideshow;
385 * // if the slideshow namespace does not exist return `undefined`
388 * // if the slideshow namespace does not exist return `undefined`
386 * // (will be interpreted as `false` by checkbox) otherwise
389 * // (will be interpreted as `false` by checkbox) otherwise
387 * // return the value
390 * // return the value
388 * return (ns == undefined)? undefined: ns.slide_type
391 * return (ns == undefined)? undefined: ns.slide_type
389 * }
392 * }
390 * CellToolbar.register_callback('slideshow.select', select_type);
393 * CellToolbar.register_callback('slideshow.select', select_type);
391 *
394 *
392 */
395 */
393 CellToolbar.utils.select_ui_generator = function(list_list, setter, getter, label) {
396 CellToolbar.utils.select_ui_generator = function(list_list, setter, getter, label) {
394 label = label || "";
397 label = label || "";
395 return function(div, cell, celltoolbar) {
398 return function(div, cell, celltoolbar) {
396 var button_container = $(div);
399 var button_container = $(div);
397 var lbl = $("<label/>").append($('<span/>').text(label));
400 var lbl = $("<label/>").append($('<span/>').text(label));
398 var select = $('<select/>').addClass('ui-widget ui-widget-content');
401 var select = $('<select/>').addClass('ui-widget ui-widget-content');
399 for(var i=0; i < list_list.length; i++){
402 for(var i=0; i < list_list.length; i++){
400 var opt = $('<option/>')
403 var opt = $('<option/>')
401 .attr('value', list_list[i][1])
404 .attr('value', list_list[i][1])
402 .text(list_list[i][0]);
405 .text(list_list[i][0]);
403 select.append(opt);
406 select.append(opt);
404 }
407 }
405 select.val(getter(cell));
408 select.val(getter(cell));
406 select.change(function(){
409 select.change(function(){
407 setter(cell, select.val());
410 setter(cell, select.val());
408 });
411 });
409 button_container.append($('<span/>').append(lbl).append(select));
412 button_container.append($('<span/>').append(lbl).append(select));
410 };
413 };
411 };
414 };
412
415
413 // Backwards compatability.
416 // Backwards compatability.
414 IPython.CellToolbar = CellToolbar;
417 IPython.CellToolbar = CellToolbar;
415
418
416 return {'CellToolbar': CellToolbar};
419 return {'CellToolbar': CellToolbar};
417 });
420 });
General Comments 0
You need to be logged in to leave comments. Login now