diff --git a/docs/en_US/keyboard_shortcuts.rst b/docs/en_US/keyboard_shortcuts.rst index 6b193fdda..360efb322 100644 --- a/docs/en_US/keyboard_shortcuts.rst +++ b/docs/en_US/keyboard_shortcuts.rst @@ -193,8 +193,8 @@ When using the Debugger, the following shortcuts are available: | Alt + Shift + q | Option + Shift + q | Enter or Edit values in Grid | +--------------------------+--------------------+-----------------------------------+ -Inner Panel Navigation -********************** +Inner Tab and Panel Navigation +****************************** When using the Query Tool and Debugger, the following shortcuts are available for inner panel navigation: @@ -203,13 +203,15 @@ for inner panel navigation: :class: longtable :widths: 2 2 3 - +--------------------------+---------------------+------------------------------+ - | Shortcut (Windows/Linux) | Shortcut (Mac) | Function | - +==========================+=====================+==============================+ - | Alt + Shift + Right | Alt + Shift + Right | Move to next inner panel | - +--------------------------+---------------------+------------------------------+ - | Alt + Shift + Left | Alt + Shift + Left | Move to previous inner panel | - +--------------------------+---------------------+------------------------------+ + +--------------------------+---------------------------+------------------------------------+ + | Shortcut (Windows/Linux) | Shortcut (Mac) | Function | + +==========================+===========================+====================================+ + | Alt + Shift + ] | Alt + Shift + ] | Move to next tab within a panel | + +--------------------------+---------------------------+------------------------------------+ + | Alt + Shift + [ | Alt + Shift + [ | Move to previous tab within a panel| + +--------------------------+---------------------------+------------------------------------+ + | Alt + Shift + Tab | Alt + Shift + Tab | Move between inner panels | + +--------------------------+---------------------------+------------------------------------+ Access Key ********** diff --git a/docs/en_US/release_notes_4_9.rst b/docs/en_US/release_notes_4_9.rst index ab1497ced..45cad052f 100644 --- a/docs/en_US/release_notes_4_9.rst +++ b/docs/en_US/release_notes_4_9.rst @@ -14,6 +14,7 @@ Bug fixes ********* | `Bug #4171 `_ - Fix issue where reverse engineered SQL was failing for foreign tables, if it had "=" in the options. +| `Bug #4195 `_ - Fix keyboard navigation in "inner" tabsets such as the Query Tool and Debugger. | `Bug #4253 `_ - Fix issue where new column should be created with Default value. | `Bug #4255 `_ - Prevent the geometry viewer grabbing key presses when not in focus under Firefox, IE and Edge. | `Bug #4320 `_ - Fix issue where SSH tunnel connection using password is failing, it's regression of Master Password. \ No newline at end of file diff --git a/web/pgadmin/static/js/keyboard_shortcuts.js b/web/pgadmin/static/js/keyboard_shortcuts.js index 017529b73..c565b862c 100644 --- a/web/pgadmin/static/js/keyboard_shortcuts.js +++ b/web/pgadmin/static/js/keyboard_shortcuts.js @@ -9,6 +9,7 @@ import $ from 'jquery'; import gettext from 'sources/gettext'; +import { getMod } from 'sources/utils'; const PERIOD_KEY = 190, FWD_SLASH_KEY = 191, @@ -116,9 +117,52 @@ function validateShortcutKeys(user_defined_shortcut, event) { user_defined_shortcut.key.key_code == keyCode; } +// Finds the desired panel on which user wants to navigate to +function focusDockerPanel(docker, op) { + if(!docker) { + return; + } + + // If no frame in focus, focus the first one + if(!docker._focusFrame) { + if(docker._frameList.length == 0 && docker._frameList[0]._panelList.length == 0) { + return; + } + docker._frameList[0]._panelList[docker._frameList[0]._curTab].focus(); + } + + let focus_frame = docker._focusFrame, + focus_id = 0, + flash = false; + + // Mod is used to cycle the op + if (op == 'switch') { + let i = 0, total_frames = docker._frameList.length; + + for(i = 0; i < total_frames; i++) { + if(focus_frame === docker._frameList[i]) break; + } + focus_frame = docker._frameList[getMod(i+1,total_frames)]; + focus_id = focus_frame._curTab; + flash = true; + } else if (op == 'left') { + focus_id = getMod(focus_frame._curTab-1, focus_frame._panelList.length); + flash = false; + } else if (op == 'right') { + focus_id = getMod(focus_frame._curTab+1, focus_frame._panelList.length); + flash = false; + } + + let focus_panel = focus_frame._panelList[focus_id]; + + focus_panel.$container.find('*[tabindex]:not([tabindex="-1"])').trigger('focus'); + focus_panel.focus(flash); + return focus_panel._type; +} + /* Debugger: Keyboard Shortcuts handling */ -function keyboardShortcutsDebugger($el, event, preferences) { - let panel_id, panel_content, $input; +function keyboardShortcutsDebugger($el, event, preferences, docker) { + let panel_type = '', panel_content, $input; if(this.validateShortcutKeys(preferences.edit_grid_values, event)) { this._stopEventPropagation(event); @@ -132,54 +176,32 @@ function keyboardShortcutsDebugger($el, event, preferences) { } } else if(this.validateShortcutKeys(preferences.move_next, event)) { this._stopEventPropagation(event); - panel_id = this.getInnerPanel($el, 'right'); + panel_type = focusDockerPanel(docker, 'right'); } else if(this.validateShortcutKeys(preferences.move_previous, event)) { this._stopEventPropagation(event); - panel_id = this.getInnerPanel($el, 'left'); + panel_type = focusDockerPanel(docker, 'left'); + } else if(this.validateShortcutKeys(preferences.switch_panel, event)) { + this._stopEventPropagation(event); + panel_type = focusDockerPanel(docker, 'switch'); } - return panel_id; -} - -// Finds the desired panel on which user wants to navigate to -function getInnerPanel($el, direction) { - if(!$el || !$el.length) - return false; - - let total_panels = $el.find('.wcPanelTab'); - // If no panels found OR if single panel - if (!total_panels.length || total_panels.length == 1) - return false; - - let active_panel = $(total_panels).filter('.wcPanelTabActive'), - id = parseInt($(active_panel).attr('id')), - fist_panel = 0, - last_panel = total_panels.length - 1; - - // Find desired panel - if (direction == 'left') { - if(id > fist_panel) - id--; - } else { - if (id < last_panel) - id++; - } - return id; + return panel_type; } /* Query tool: Keyboard Shortcuts handling */ function keyboardShortcutsQueryTool( - sqlEditorController, queryToolActions, event + sqlEditorController, queryToolActions, event, docker ) { if (sqlEditorController.isQueryRunning()) { return; } - let keyCode = event.which || event.keyCode, panel_id; + let keyCode = event.which || event.keyCode, panel_type = ''; let executeKeys = sqlEditorController.preferences.execute_query; let explainKeys = sqlEditorController.preferences.explain_query; let explainAnalyzeKeys = sqlEditorController.preferences.explain_analyze_query; let downloadCsvKeys = sqlEditorController.preferences.download_csv; - let nextPanelKeys = sqlEditorController.preferences.move_next; - let previousPanelKeys = sqlEditorController.preferences.move_previous; + let nextTabKeys = sqlEditorController.preferences.move_next; + let previousTabKeys = sqlEditorController.preferences.move_previous; + let switchPanelKeys = sqlEditorController.preferences.switch_panel; let toggleCaseKeys = sqlEditorController.preferences.toggle_case; let commitKeys = sqlEditorController.preferences.commit_transaction; let rollbackKeys = sqlEditorController.preferences.rollback_transaction; @@ -236,12 +258,15 @@ function keyboardShortcutsQueryTool( && $(event.target).closest('.dropdown-submenu').length > 0) { $(event.target).closest('.dropdown-submenu').find('.dropdown-menu').removeClass('show'); } - } else if(this.validateShortcutKeys(nextPanelKeys, event)) { + } else if(this.validateShortcutKeys(nextTabKeys, event)) { this._stopEventPropagation(event); - panel_id = this.getInnerPanel(sqlEditorController.container, 'right'); - } else if(this.validateShortcutKeys(previousPanelKeys, event)) { + panel_type = focusDockerPanel(docker, 'right'); + } else if(this.validateShortcutKeys(previousTabKeys, event)) { this._stopEventPropagation(event); - panel_id = this.getInnerPanel(sqlEditorController.container, 'left'); + panel_type = focusDockerPanel(docker, 'left'); + } else if(this.validateShortcutKeys(switchPanelKeys, event)) { + this._stopEventPropagation(event); + panel_type = focusDockerPanel(docker, 'switch'); } else if(keyCode === UP_KEY || keyCode === DOWN_KEY) { /*Apply only for dropdown*/ if($(event.target).closest('.dropdown-menu').length > 0) { @@ -293,13 +318,13 @@ function keyboardShortcutsQueryTool( } } - return panel_id; + return panel_type; } export { keyboardShortcutsDebugger as processEventDebugger, keyboardShortcutsQueryTool as processEventQueryTool, - getInnerPanel, validateShortcutKeys, + focusDockerPanel, validateShortcutKeys, _stopEventPropagation, isMac, isKeyCtrlAlt, isKeyAltShift, isKeyCtrlShift, isKeyCtrlAltShift, isAltShiftBoth, isCtrlShiftBoth, isCtrlAltBoth, shortcut_key, shortcut_title, shortcut_accesskey_title, diff --git a/web/pgadmin/static/js/utils.js b/web/pgadmin/static/js/utils.js index 6b1ed9ad7..1c58a9eba 100644 --- a/web/pgadmin/static/js/utils.js +++ b/web/pgadmin/static/js/utils.js @@ -79,3 +79,7 @@ export function getGCD(inp_arr) { return result; } + +export function getMod(no, divisor) { + return ((no % divisor) + divisor) % divisor; +} diff --git a/web/pgadmin/tools/debugger/__init__.py b/web/pgadmin/tools/debugger/__init__.py index 44d55da8c..12e043348 100644 --- a/web/pgadmin/tools/debugger/__init__.py +++ b/web/pgadmin/tools/debugger/__init__.py @@ -184,8 +184,8 @@ class DebuggerModule(PgAdminModule): 'shift': True, 'control': False, 'key': { - 'key_code': 37, - 'char': 'ArrowLeft' + 'key_code': 219, + 'char': '[' } }, category_label=gettext('Keyboard shortcuts'), @@ -202,8 +202,26 @@ class DebuggerModule(PgAdminModule): 'shift': True, 'control': False, 'key': { - 'key_code': 39, - 'char': 'ArrowRight' + 'key_code': 221, + 'char': ']' + } + }, + category_label=gettext('Keyboard shortcuts'), + fields=shortcut_fields + ) + + self.preference.register( + 'keyboard_shortcuts', + 'switch_panel', + gettext('Switch Panel'), + 'keyboardshortcut', + { + 'alt': True, + 'shift': True, + 'control': False, + 'key': { + 'key_code': 9, + 'char': 'Tab' } }, category_label=gettext('Keyboard shortcuts'), diff --git a/web/pgadmin/tools/debugger/static/js/direct.js b/web/pgadmin/tools/debugger/static/js/direct.js index 23b61c4c4..77ee45eae 100644 --- a/web/pgadmin/tools/debugger/static/js/direct.js +++ b/web/pgadmin/tools/debugger/static/js/direct.js @@ -1513,17 +1513,17 @@ define([ controller.Step_into(pgTools.DirectDebug.trans_id); }, keyAction: function (event) { - var $el = this.$el, panel_id, actual_panel, - self = this; + let panel_type=''; - panel_id = keyboardShortcuts.processEventDebugger( - $el, event, self.preferences + panel_type = keyboardShortcuts.processEventDebugger( + this.$el, event, this.preferences, pgTools.DirectDebug.docker ); - // Panel navigation - if(!_.isUndefined(panel_id) && !_.isNull(panel_id)) { - actual_panel = panel_id + 1; - pgTools.DirectDebug.docker.findPanels()[actual_panel].focus(); + + if(!_.isNull(panel_type) && !_.isUndefined(panel_type) && panel_type != '') { + setTimeout(function() { + pgBrowser.Events.trigger(`pgadmin:debugger:${panel_type}:focus`); + }, 100); } }, }); @@ -1856,6 +1856,10 @@ define([ } }); + pgBrowser.Events.on('pgadmin:debugger:code:focus', ()=>{ + self.editor.focus(); + }); + // On loading the docker, register the callbacks var onLoad = function() { self.docker.finishLoading(100); @@ -1906,6 +1910,16 @@ define([ // Create the toolbar view for debugging the function this.toolbarView = new DebuggerToolbarView(); + /* wcDocker focuses on window always, and all our shortcuts are + * bind to editor-panel. So when we use wcDocker focus, editor-panel + * loses focus and events don't work. + */ + $(window).on('keydown', (e)=>{ + if(self.toolbarView.keyAction) { + self.toolbarView.keyAction(e); + } + }); + /* Cache may take time to load for the first time * Keep trying till available */ diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js index 9442595b6..6af098b4b 100644 --- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js +++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js @@ -195,8 +195,8 @@ define('tools.querytool', [ 'filename': 'css', }), theme: 'webcabin.overrides.css', - }); - + } + ); // Create the panels var sql_panel = new pgAdmin.Browser.Panel({ @@ -216,7 +216,7 @@ define('tools.querytool', [ isCloseable: false, isPrivate: true, extraClasses: 'hide-vertical-scrollbar', - content: '
', + content: '
', }); var explain = new pgAdmin.Browser.Panel({ @@ -226,7 +226,7 @@ define('tools.querytool', [ height: '100%', isCloseable: false, isPrivate: true, - content: '
', + content: '
', }); var messages = new pgAdmin.Browser.Panel({ @@ -236,7 +236,7 @@ define('tools.querytool', [ height: '100%', isCloseable: false, isPrivate: true, - content: '
', + content: '
', }); var history = new pgAdmin.Browser.Panel({ @@ -246,7 +246,7 @@ define('tools.querytool', [ height: '33%', isCloseable: false, isPrivate: true, - content: '
', + content: '
', }); var scratch = new pgAdmin.Browser.Panel({ @@ -256,7 +256,7 @@ define('tools.querytool', [ height: '33%', isCloseable: true, isPrivate: false, - content: '
', + content: '
', }); var notifications = new pgAdmin.Browser.Panel({ @@ -266,7 +266,7 @@ define('tools.querytool', [ height: '100%', isCloseable: false, isPrivate: true, - content: '
', + content: '
', }); var geometry_viewer = new pgAdmin.Browser.Panel({ @@ -277,7 +277,7 @@ define('tools.querytool', [ isCloseable: true, isPrivate: true, isLayoutMember: false, - content: '
', + content: '
', }); // Load all the created panels @@ -317,8 +317,8 @@ define('tools.querytool', [ self.render_history_grid(); queryToolNotifications.renderNotificationsGrid(self.notifications_panel); - var text_container = $(''); - var output_container = $('
').append(text_container); + var text_container = $(''); + var output_container = $('
').append(text_container); self.sql_panel_obj.$container.find('.pg-panel-content').append(output_container); self.query_tool_obj = CodeMirror.fromTextArea(text_container.get(0), { @@ -342,6 +342,10 @@ define('tools.querytool', [ scrollbarStyle: 'simple', }); + pgBrowser.Events.on('pgadmin:query_tool:sql_panel:focus', ()=>{ + self.query_tool_obj.focus(); + }); + if (!self.preferences.new_browser_tab) { // Listen on the panel closed event and notify user to save modifications. _.each(window.top.pgAdmin.Browser.docker.findPanels('frm_datagrid'), function(p) { @@ -1852,23 +1856,19 @@ define('tools.querytool', [ }, keyAction: function(event) { - var panel_id, self = this; - panel_id = keyboardShortcuts.processEventQueryTool( - this.handler, queryToolActions, event + var panel_type=''; + + panel_type = keyboardShortcuts.processEventQueryTool( + this.handler, queryToolActions, event, this.docker ); - // If it return panel id then focus it - if(!_.isNull(panel_id) && !_.isUndefined(panel_id)) { - // Returned panel index, by incrementing it by 1 we will get actual panel - panel_id++; - this.docker.findPanels()[panel_id].focus(); - // We set focus on history tab so we need to set the focus on - // editor explicitly - if(panel_id == 3) { - setTimeout(function() { self.query_tool_obj.focus(); }, 100); - } + if(!_.isNull(panel_type) && !_.isUndefined(panel_type) && panel_type != '') { + setTimeout(function() { + pgBrowser.Events.trigger(`pgadmin:query_tool:${panel_type}:focus`); + }, 100); } }, + // Callback function for the commit button click. on_commit_transaction: function() { queryToolActions.executeCommit(this.handler); @@ -2126,6 +2126,16 @@ define('tools.querytool', [ // Render the header self.gridView.render(); + /* wcDocker focuses on window always, and all our shortcuts are + * bind to editor-panel. So when we use wcDocker focus, editor-panel + * loses focus and events don't work. + */ + $(window).on('keydown', (e)=>{ + if(self.gridView.keyAction) { + self.gridView.keyAction(e); + } + }); + if (self.is_query_tool) { // Fetch the SQL for Scripts (eg: CREATE/UPDATE/DELETE/SELECT) // Call AJAX only if script type url is present diff --git a/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py b/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py index 8fbc88ab5..ca09eaecf 100644 --- a/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py +++ b/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py @@ -354,8 +354,8 @@ def RegisterQueryToolPreferences(self): 'shift': True, 'control': False, 'key': { - 'key_code': 37, - 'char': 'ArrowLeft' + 'key_code': 219, + 'char': '[' } }, category_label=gettext('Keyboard shortcuts'), @@ -372,8 +372,26 @@ def RegisterQueryToolPreferences(self): 'shift': True, 'control': False, 'key': { - 'key_code': 39, - 'char': 'ArrowRight' + 'key_code': 221, + 'char': ']' + } + }, + category_label=gettext('Keyboard shortcuts'), + fields=shortcut_fields + ) + + self.preference.register( + 'keyboard_shortcuts', + 'switch_panel', + gettext('Switch Panel'), + 'keyboardshortcut', + { + 'alt': True, + 'shift': True, + 'control': False, + 'key': { + 'key_code': 9, + 'char': 'Tab' } }, category_label=gettext('Keyboard shortcuts'), diff --git a/web/pgadmin/utils/preferences.py b/web/pgadmin/utils/preferences.py index ec484d2e1..1530c1a87 100644 --- a/web/pgadmin/utils/preferences.py +++ b/web/pgadmin/utils/preferences.py @@ -78,7 +78,7 @@ class _Preference(object): # Look into the configuration table to find out the id of the specific # preference. res = PrefTable.query.filter_by( - name=name + name=name, cid=cid ).first() if res is None: diff --git a/web/regression/javascript/common_keyboard_shortcuts_spec.js b/web/regression/javascript/common_keyboard_shortcuts_spec.js index abe5bac5d..a74f3ce55 100644 --- a/web/regression/javascript/common_keyboard_shortcuts_spec.js +++ b/web/regression/javascript/common_keyboard_shortcuts_spec.js @@ -8,6 +8,7 @@ ////////////////////////////////////////////////////////////////////////// import * as keyboardShortcuts from 'sources/keyboard_shortcuts'; +import $ from 'jquery'; describe('the keyboard shortcuts', () => { const F1_KEY = 112; @@ -45,9 +46,30 @@ describe('the keyboard shortcuts', () => { }); describe('when user wants to goto next panel', function () { - - it('returns panel id', function () { - expect(keyboardShortcuts.getInnerPanel(debuggerElementSpy, 'right')).toEqual(false); + let dockerSpy = { + '_focusFrame': { + '_curTab': 0, + '_panelList': [ + {$container: $(''), '_type': 'type1', 'focus': function() {return true;}}, + {$container: $(''), '_type': 'type2', 'focus': function() {return true;}}, + ], + }, + }; + it('right key', function () { + dockerSpy._focusFrame._curTab = 0; + expect(keyboardShortcuts.focusDockerPanel(dockerSpy, 'right')).toEqual('type2'); + }); + it('left key', function () { + dockerSpy._focusFrame._curTab = 1; + expect(keyboardShortcuts.focusDockerPanel(dockerSpy, 'left')).toEqual('type1'); + }); + it('left key cycle', function () { + dockerSpy._focusFrame._curTab = 0; + expect(keyboardShortcuts.focusDockerPanel(dockerSpy, 'left')).toEqual('type2'); + }); + it('right key cycle', function () { + dockerSpy._focusFrame._curTab = 1; + expect(keyboardShortcuts.focusDockerPanel(dockerSpy, 'left')).toEqual('type1'); }); }); diff --git a/web/regression/javascript/pgadmin_utils_spec.js b/web/regression/javascript/pgadmin_utils_spec.js index 39fe87dfb..02bd54780 100644 --- a/web/regression/javascript/pgadmin_utils_spec.js +++ b/web/regression/javascript/pgadmin_utils_spec.js @@ -7,7 +7,7 @@ // ////////////////////////////////////////////////////////////// -import { getEpoch, getGCD } from 'sources/utils'; +import { getEpoch, getGCD, getMod } from 'sources/utils'; describe('getEpoch', function () { it('should return non zero', function () { @@ -33,3 +33,21 @@ describe('getGCD', function () { expect(getGCD(nos)).toEqual(3); }); }); + +describe('getMod', function () { + it('complete divisible', function () { + expect(getMod(5,5)).toEqual(0); + }); + + it('incomplete divisible less divisor', function () { + expect(getMod(7,5)).toEqual(2); + }); + + it('incomplete divisible greater divisor', function () { + expect(getMod(5,7)).toEqual(5); + }); + + it('negative number', function () { + expect(getMod(-7,5)).toEqual(3); + }); +});