// Copyright (c) IPython Development Team. // Distributed under the terms of the Modified BSD License. /** * * * @module keyboardmanager * @namespace keyboardmanager * @class KeyboardManager */ define([ 'base/js/namespace', 'jquery', 'base/js/utils', 'base/js/keyboard', ], function(IPython, $, utils, keyboard) { "use strict"; // Main keyboard manager for the notebook var keycodes = keyboard.keycodes; var KeyboardManager = function (options) { /** * A class to deal with keyboard event and shortcut * * @class KeyboardManager * @constructor * @param options {dict} Dictionary of keyword arguments : * @param options.events {$(Events)} instance * @param options.pager: {Pager} pager instance */ this.mode = 'command'; this.enabled = true; this.pager = options.pager; this.quick_help = undefined; this.notebook = undefined; this.last_mode = undefined; this.bind_events(); this.env = {pager:this.pager}; this.actions = options.actions; this.command_shortcuts = new keyboard.ShortcutManager(undefined, options.events, this.actions, this.env ); this.command_shortcuts.add_shortcuts(this.get_default_common_shortcuts()); this.command_shortcuts.add_shortcuts(this.get_default_command_shortcuts()); this.edit_shortcuts = new keyboard.ShortcutManager(undefined, options.events, this.actions, this.env); this.edit_shortcuts.add_shortcuts(this.get_default_common_shortcuts()); this.edit_shortcuts.add_shortcuts(this.get_default_edit_shortcuts()); Object.seal(this); }; /** * Return a dict of common shortcut * @method get_default_common_shortcuts * * @example Example of returned shortcut * ``` * 'shortcut-key': 'action-name' * // a string representing the shortcut as dash separated value. * // e.g. 'shift' , 'shift-enter', 'cmd-t' *``` */ KeyboardManager.prototype.get_default_common_shortcuts = function() { return { 'shift' : 'ipython.ignore', 'shift-enter' : 'ipython.run-select-next', 'ctrl-enter' : 'ipython.execute-in-place', 'alt-enter' : 'ipython.execute-and-insert-after', // cmd on mac, ctrl otherwise 'cmdtrl-s' : 'ipython.save-notebook', }; }; KeyboardManager.prototype.get_default_edit_shortcuts = function() { return { 'esc' : 'ipython.go-to-command-mode', 'ctrl-m' : 'ipython.go-to-command-mode', 'up' : 'ipython.move-cursor-up-or-previous-cell', 'down' : 'ipython.move-cursor-down-or-next-cell', 'ctrl-shift--' : 'ipython.split-cell-at-cursor', 'ctrl-shift-subtract' : 'ipython.split-cell-at-cursor' }; }; KeyboardManager.prototype.get_default_command_shortcuts = function() { return { 'shift-space': 'ipython.scroll-up', 'shift-v' : 'ipython.paste-cell-before', 'shift-m' : 'ipython.merge-selected-cell-with-cell-after', 'shift-o' : 'ipython.toggle-output-scrolling-selected-cell', 'ctrl-j' : 'ipython.move-selected-cell-down', 'ctrl-k' : 'ipython.move-selected-cell-up', 'enter' : 'ipython.enter-edit-mode', 'space' : 'ipython.scroll-down', 'down' : 'ipython.select-next-cell', 'i,i' : 'ipython.interrupt-kernel', '0,0' : 'ipython.restart-kernel', 'd,d' : 'ipython.delete-cell', 'up' : 'ipython.select-previous-cell', 'k' : 'ipython.select-previous-cell', 'j' : 'ipython.select-next-cell', 'x' : 'ipython.cut-selected-cell', 'c' : 'ipython.copy-selected-cell', 'v' : 'ipython.paste-cell-after', 'a' : 'ipython.insert-cell-before', 'b' : 'ipython.insert-cell-after', 'y' : 'ipython.change-selected-cell-to-code-cell', 'm' : 'ipython.change-selected-cell-to-markdown-cell', 'r' : 'ipython.change-selected-cell-to-raw-cell', '1' : 'ipython.change-selected-cell-to-heading-1', '2' : 'ipython.change-selected-cell-to-heading-2', '3' : 'ipython.change-selected-cell-to-heading-3', '4' : 'ipython.change-selected-cell-to-heading-4', '5' : 'ipython.change-selected-cell-to-heading-5', '6' : 'ipython.change-selected-cell-to-heading-6', 'o' : 'ipython.toggle-output-visibility-selected-cell', 's' : 'ipython.save-notebook', 'l' : 'ipython.toggle-line-number-selected-cell', 'h' : 'ipython.show-keyboard-shortcut-help-dialog', 'z' : 'ipython.undo-last-cell-deletion', 'q' : 'ipython.close-pager', 'i,e,e,e,e,e' : function(){console.log('[[===>>> 5E <<<===]]');}, 'i,d,d,q,d' : function(){console.log('[[===>>> Trigger god mode <<<===]]');}, 'i,d,d' : function(){console.log('[[===>>> should warn at registration <<<===]]');}, 'i,d,k' : function(){console.log('[[===>>> Trigger shadow mode <<<===]]');}, 'i,d,k,r,q' : function(){console.log('[[===>>> Trigger invisibility mode <<<===]]');}, ';,up,down,up,down,left,right,left,right,b,a' : function(){console.log('[[===>>> Konami <<<===]]');}, 'ctrl-x,meta-c,meta-b,u,t,t,e,r,f,l,y' : function(){ console.log('[[Are you a real Programmer ?]]'); window.open('http://xkcd.com/378/','_blank'); }, }; }; KeyboardManager.prototype.bind_events = function () { var that = this; $(document).keydown(function (event) { if(event._ipkmIgnore===true||(event.originalEvent||{})._ipkmIgnore===true){ return false; } return that.handle_keydown(event); }); }; KeyboardManager.prototype.set_notebook = function (notebook) { this.notebook = notebook; this.actions.extend_env({notebook:notebook}); }; KeyboardManager.prototype.set_quickhelp = function (notebook) { this.actions.extend_env({quick_help:notebook}); }; KeyboardManager.prototype.handle_keydown = function (event) { /** * returning false from this will stop event propagation **/ if (event.which === keycodes.esc) { // Intercept escape at highest level to avoid closing // websocket connection with firefox event.preventDefault(); } if (!this.enabled) { if (event.which === keycodes.esc) { this.notebook.command_mode(); return false; } return true; } if (this.mode === 'edit') { return this.edit_shortcuts.call_handler(event); } else if (this.mode === 'command') { return this.command_shortcuts.call_handler(event); } return true; }; KeyboardManager.prototype.edit_mode = function () { this.last_mode = this.mode; this.mode = 'edit'; }; KeyboardManager.prototype.command_mode = function () { this.last_mode = this.mode; this.mode = 'command'; }; KeyboardManager.prototype.enable = function () { this.enabled = true; }; KeyboardManager.prototype.disable = function () { this.enabled = false; }; KeyboardManager.prototype.register_events = function (e) { var that = this; var handle_focus = function () { that.disable(); }; var handle_blur = function () { that.enable(); }; e.on('focusin', handle_focus); e.on('focusout', handle_blur); // TODO: Very strange. The focusout event does not seem fire for the // bootstrap textboxes on FF25&26... This works around that by // registering focus and blur events recursively on all inputs within // registered element. e.find('input').blur(handle_blur); e.on('DOMNodeInserted', function (event) { var target = $(event.target); if (target.is('input')) { target.blur(handle_blur); } else { target.find('input').blur(handle_blur); } }); // There are times (raw_input) where we remove the element from the DOM before // focusout is called. In this case we bind to the remove event of jQueryUI, // which gets triggered upon removal, iff it is focused at the time. // is_focused must be used to check for the case where an element within // the element being removed is focused. e.on('remove', function () { if (utils.is_focused(e[0])) { that.enable(); } }); }; // For backwards compatibility. IPython.KeyboardManager = KeyboardManager; return {'KeyboardManager': KeyboardManager}; });