diff --git a/admin.admin.inc b/admin.admin.inc index 4dad2baaf23ccd114028cf42cf8a5e03b9c1c270..7138a7d1cfda724ff59d502041895b316d9bbc0e 100644 --- a/admin.admin.inc +++ b/admin.admin.inc @@ -41,6 +41,19 @@ function admin_settings_form($form, &$form_state) { '#default_value' => admin_get_settings('behavior'), ); + $form['admin_toolbar']['keyboard'] = array( + '#type' => 'checkbox', + '#title' => t('Enable Keyboard Control'), + '#description' => t('Allow the admin toolbar to be activated and controlled with the keyboard.'), + '#default_value' => admin_get_settings('keyboard'), + ); + $form['admin_toolbar']['hotkeys'] = array( + '#type' => 'textfield', + '#title' => t('Activation Hotkeys'), + '#description' => t('The key combination to activate the admin toolbar and turn on keyboard control. Seperate multiple combinations with a space.'), + '#default_value' => admin_get_settings('hotkeys'), + ); + $block_info = array(); foreach (module_implements('block_info') as $module) { $module_blocks = module_invoke($module, 'block_info'); diff --git a/admin.module b/admin.module index d662165cf1e1283c0b4f22e5e94b467f76499fc6..bf7fdd1b8ac9b09595daf53e82abbecb813f0e68 100644 --- a/admin.module +++ b/admin.module @@ -282,6 +282,8 @@ function admin_get_settings($key = NULL) { $settings = variable_get('admin_toolbar', array()) + array( 'layout' => 'vertical', 'position' => 'nw', + 'keyboard' => true, + 'hotkeys' => 'Alt+space Ctrl+space', 'behavior' => 'df', 'blocks' => admin_get_default_blocks(), 'expanded' => isset($cookie['expanded']) ? check_plain($cookie['expanded']) : 0, @@ -367,8 +369,13 @@ function admin_page_build(&$page) { } if (!admin_suppress(FALSE) && $blocks = admin_get_admin_blocks()) { $path = drupal_get_path('module', 'admin'); + if (admin_get_settings('keyboard')) { + $settings = array('hotkeys' => preg_split('/\s+/', admin_get_settings('hotkeys'))); + drupal_add_js(array('admintoolbar' => $settings), 'setting'); + } drupal_add_js("misc/jquery.cookie.js"); drupal_add_js("{$path}/includes/jquery.drilldown.js"); + drupal_add_js("{$path}/includes/jquery.hotkeys.js"); drupal_add_js("{$path}/includes/admin.toolbar.js"); drupal_add_js("{$path}/includes/admin.menu.js"); drupal_add_css("{$path}/includes/admin.toolbar.base.css"); diff --git a/includes/admin.menu.css b/includes/admin.menu.css index 1ec02722dccdf6f45073590161c8dddab009c223..8e4092a5a4a93db380ad7c7674eb3af0076ca33d 100644 --- a/includes/admin.menu.css +++ b/includes/admin.menu.css @@ -109,6 +109,7 @@ div#admin-toolbar.horizontal a.menu-hover span.menu-description { #admin-toolbar ul.drilldown-active-menu li { height:25px; } #admin-toolbar ul.menu li a.active { background:#222; } + #admin-toolbar ul.menu li.keys-active > a, #admin-toolbar ul.menu li a:hover { color:#000; background:url(../images/bleeds.png) 0px -30px repeat-x; diff --git a/includes/admin.toolbar.js b/includes/admin.toolbar.js index 1d0b64c5645df651f8a06d56d7c0ac56605010b6..f67a3b424c1a167150cbb8efcce9b480d6db15a7 100644 --- a/includes/admin.toolbar.js +++ b/includes/admin.toolbar.js @@ -15,6 +15,9 @@ Drupal.behaviors.adminToolbar.attach = function(context) { // Admin tabs. $('div.admin-tab', this).click(function() { Drupal.adminToolbar.tab(toolbar, $(this), true); }); + // Initialize the keyboard control. + Drupal.adminToolbar.keys.init(toolbar); + $(document).bind('drupalOverlayLoad', {adminToolbar: Drupal.adminToolbar, toolbar: toolbar}, function(event) { var body = $(document.body); var adminToolbar = event.data.adminToolbar; @@ -91,6 +94,115 @@ Drupal.adminToolbar.init = function (toolbar) { } }; + +/** + * Set up keyboard control. + */ + +Drupal.adminToolbar.keys = {}; + +Drupal.adminToolbar.keys.init = function (toolbar) { + if (Drupal.settings && Drupal.settings.admintoolbar && Drupal.settings.admintoolbar.hotkeys) { + + for (var i = 0; i < Drupal.settings.admintoolbar.hotkeys.length; i++) { + console.log(Drupal.settings.admintoolbar.hotkeys[i]); + $(document).bind('keydown', Drupal.settings.admintoolbar.hotkeys[i], function() { + return Drupal.adminToolbar.keys.toggle(toolbar); + }); + } + + $(document).bind('keydown', 'tab', function() {return Drupal.adminToolbar.keys.press('tab', toolbar)}); + $(document).bind('keydown', 'up', function() {return Drupal.adminToolbar.keys.press('up', toolbar)}); + $(document).bind('keydown', 'down', function() {return Drupal.adminToolbar.keys.press('down', toolbar)}); + $(document).bind('keydown', 'right', function() {return Drupal.adminToolbar.keys.press('right', toolbar)}); + $(document).bind('keydown', 'left', function() {return Drupal.adminToolbar.keys.press('left', toolbar)}); + $(document).bind('keydown', 'return', function() {return Drupal.adminToolbar.keys.press('enter', toolbar)}); + } +}; + +Drupal.adminToolbar.keys.toggle = function(toolbar) { + // If the admin toolbar is expanded but keyboard control has not been activated just activate key control. + if ($(document.body).is('.admin-expanded') && !$(document.body).is('.admin-key-control')) { + Drupal.adminToolbar.keys.activate(toolbar); + return false; + } + else { + return Drupal.adminToolbar.toggle(toolbar); + } +}; + +Drupal.adminToolbar.keys.activate = function (toolbar) { + if ($('.admin-active .drilldown-active-menu > .keys-active').length == 0) { + $('.admin-active .drilldown-active-menu > li:first', toolbar).eq(0).addClass('keys-active'); + } + $(document.body).addClass('admin-key-control'); +}; + +Drupal.adminToolbar.keys.deactivate = function (toolbar) { + $(document.body).removeClass('admin-key-control'); +}; + +Drupal.adminToolbar.keys.press = function(key, toolbar) { + if ($(document.body).is('.admin-expanded') && $(document.body).is('.admin-key-control') && jQuery.isFunction(Drupal.adminToolbar.keys[key])) { + $('input', toolbar).blur(); + Drupal.adminToolbar.keys[key](toolbar); + return false; + } +}; + +Drupal.adminToolbar.keys.tab = function (toolbar) { + if ($('.admin-tab-active', toolbar).next().length) { + $('.admin-tab-active', toolbar).next().click(); + } + else { + $('.admin-tab').eq(0).click(); + } + $('.keys-active', toolbar).removeClass('keys-active'); + $('.admin-active .drilldown-active-menu > li:first', toolbar).eq(0).addClass('keys-active'); +}; + +Drupal.adminToolbar.keys.up = function (toolbar) { + if ($('.admin-active .drilldown-active-menu .keys-active', toolbar).prev().length == 0) { + $('.admin-active .drilldown-active-menu .keys-active', toolbar).removeClass('keys-active'); + $('.admin-active .drilldown-active-menu > li:last', toolbar).eq(0).addClass('keys-active'); + } + else { + $('.admin-active .drilldown-active-menu .keys-active', toolbar).removeClass('keys-active').prev().eq(0).addClass('keys-active'); + } +}; + +Drupal.adminToolbar.keys.down = function (toolbar) { + if ($('.admin-active .drilldown-active-menu .keys-active').next().length == 0) { + $('.admin-active .drilldown-active-menu .keys-active', toolbar).removeClass('keys-active'); + $('.admin-active .drilldown-active-menu > li:first', toolbar).eq(0).addClass('keys-active'); + } + else { + $('.admin-active .drilldown-active-menu .keys-active', toolbar).removeClass('keys-active').next().eq(0).addClass('keys-active'); + } +}; + +Drupal.adminToolbar.keys.right = function (toolbar) { + if ($('.admin-active .drilldown-active-menu .keys-active').is('.expanded')) { + $('.admin-active .drilldown-active-menu .keys-active').click(); + $('.admin-active .drilldown-active-menu > li:first', toolbar).eq(0).addClass('keys-active'); + } +}; + +Drupal.adminToolbar.keys.left = function (toolbar) { + if ($('.drilldown-trail a').length > 1) { + $('.admin-active .drilldown-active-menu .keys-active', toolbar).removeClass('keys-active'); + $('.drilldown-trail a').eq(-2).click(); + } +}; + +Drupal.adminToolbar.keys.enter = function (toolbar) { + var url = $('.admin-active .drilldown-active-menu .keys-active a', toolbar).eq(0).attr('href'); + if (url) { + document.location = url; + } +}; + + /** * Set the active tab. */ @@ -182,6 +294,7 @@ Drupal.adminToolbar.toggle = function (toolbar) { this.setOverlayState(toolbar, false, false); } this.setState('expanded', 0); + Drupal.adminToolbar.keys.deactivate(toolbar); } else { size = this.SIZE + 'px'; @@ -210,6 +323,7 @@ Drupal.adminToolbar.toggle = function (toolbar) { } else { this.setState('expanded', 1); + Drupal.adminToolbar.keys.activate(toolbar); } } }; diff --git a/includes/jquery.hotkeys.js b/includes/jquery.hotkeys.js new file mode 100644 index 0000000000000000000000000000000000000000..6aa566f8816ac5f02d14eff35f9addd27a9157f9 --- /dev/null +++ b/includes/jquery.hotkeys.js @@ -0,0 +1,99 @@ +/* + * jQuery Hotkeys Plugin + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * + * Based upon the plugin by Tzury Bar Yochay: + * http://github.com/tzuryby/hotkeys + * + * Original idea by: + * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ + */ + +(function (jQuery) { + + jQuery.hotkeys = { + version:"0.8", + + specialKeys:{ + 8:"backspace", 9:"tab", 13:"return", 16:"shift", 17:"ctrl", 18:"alt", 19:"pause", + 20:"capslock", 27:"esc", 32:"space", 33:"pageup", 34:"pagedown", 35:"end", 36:"home", + 37:"left", 38:"up", 39:"right", 40:"down", 45:"insert", 46:"del", + 96:"0", 97:"1", 98:"2", 99:"3", 100:"4", 101:"5", 102:"6", 103:"7", + 104:"8", 105:"9", 106:"*", 107:"+", 109:"-", 110:".", 111:"/", + 112:"f1", 113:"f2", 114:"f3", 115:"f4", 116:"f5", 117:"f6", 118:"f7", 119:"f8", + 120:"f9", 121:"f10", 122:"f11", 123:"f12", 144:"numlock", 145:"scroll", 191:"/", 224:"meta" + }, + + shiftNums:{ + "`":"~", "1":"!", "2":"@", "3":"#", "4":"$", "5":"%", "6":"^", "7":"&", + "8":"*", "9":"(", "0":")", "-":"_", "=":"+", ";":": ", "'":"\"", ",":"<", + ".":">", "/":"?", "\\":"|" + } + }; + + function keyHandler(handleObj) { + // Only care when a possible input has been specified + if (typeof handleObj.data !== "string") { + return; + } + + var origHandler = handleObj.handler, + keys = handleObj.data.toLowerCase().split(" "); + + handleObj.handler = function (event) { + // Don't fire in text-accepting inputs that we didn't directly bind to + if (this !== event.target && (/textarea|select/i.test(event.target.nodeName) || + event.target.type === "text")) { + return; + } + + // Keypress represents characters, not special keys + var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ], + character = String.fromCharCode(event.which).toLowerCase(), + key, modif = "", possible = {}; + + // check combinations (alt|ctrl|shift+anything) + if (event.altKey && special !== "alt") { + modif += "alt+"; + } + + if (event.ctrlKey && special !== "ctrl") { + modif += "ctrl+"; + } + + // TODO: Need to make sure this works consistently across platforms + if (event.metaKey && !event.ctrlKey && special !== "meta") { + modif += "meta+"; + } + + if (event.shiftKey && special !== "shift") { + modif += "shift+"; + } + + if (special) { + possible[ modif + special ] = true; + + } else { + possible[ modif + character ] = true; + possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true; + + // "$" can be triggered as "Shift+4" or "Shift+$" or just "$" + if (modif === "shift+") { + possible[ jQuery.hotkeys.shiftNums[ character ] ] = true; + } + } + + for (var i = 0, l = keys.length; i < l; i++) { + if (possible[ keys[i] ]) { + return origHandler.apply(this, arguments); + } + } + }; + } + + jQuery.each([ "keydown", "keyup", "keypress" ], function () { + jQuery.event.special[ this ] = { add:keyHandler }; + }); + +})(jQuery); \ No newline at end of file