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