UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

617 lines (489 loc) 15.3 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2004-2008 1&1 Internet AG, Germany, http://www.1und1.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Sebastian Werner (wpbasti) * Andreas Ecker (ecker) ************************************************************************ */ /** * Each focus root delegates the focus handling to instances of the FocusHandler. */ qx.Class.define("qx.ui.core.FocusHandler", { extend : qx.core.Object, type : "singleton", /* ***************************************************************************** CONSTRUCTOR ***************************************************************************** */ construct : function() { this.base(arguments); // Create data structure this.__roots = {}; }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members : { __roots : null, __activeChild : null, __focusedChild : null, __currentRoot : null, /** * Connects to a top-level root element (which initially receives * all events of the root). This are normally all page and application * roots, but no inline roots (they are typically sitting inside * another root). * * @param root {qx.ui.root.Abstract} Any root */ connectTo : function(root) { // this.debug("Connect to: " + root); root.addListener("keypress", this.__onKeyPress, this); root.addListener("focusin", this._onFocusIn, this, true); root.addListener("focusout", this._onFocusOut, this, true); root.addListener("activate", this._onActivate, this, true); root.addListener("deactivate", this._onDeactivate, this, true); }, /** * Registers a widget as a focus root. A focus root comes * with an separate tab sequence handling. * * @param widget {qx.ui.core.Widget} The widget to register */ addRoot : function(widget) { // this.debug("Add focusRoot: " + widget); this.__roots[widget.$$hash] = widget; }, /** * Deregisters a previous added widget. * * @param widget {qx.ui.core.Widget} The widget to deregister */ removeRoot : function(widget) { // this.debug("Remove focusRoot: " + widget); delete this.__roots[widget.$$hash]; }, /** * Get the active widget * * @return {qx.ui.core.Widget|null} The active widget or <code>null</code> * if no widget is active */ getActiveWidget : function() { return this.__activeChild; }, /** * Whether the given widget is the active one * * @param widget {qx.ui.core.Widget} The widget to check * @return {Boolean} <code>true</code> if the given widget is active */ isActive : function(widget) { return this.__activeChild == widget; }, /** * Get the focused widget * * @return {qx.ui.core.Widget|null} The focused widget or <code>null</code> * if no widget has the focus */ getFocusedWidget : function() { return this.__focusedChild; }, /** * Whether the given widget is the focused one. * * @param widget {qx.ui.core.Widget} The widget to check * @return {Boolean} <code>true</code> if the given widget is focused */ isFocused : function(widget) { return this.__focusedChild == widget; }, /** * Whether the given widgets acts as a focus root. * * @param widget {qx.ui.core.Widget} The widget to check * @return {Boolean} <code>true</code> if the given widget is a focus root */ isFocusRoot : function(widget) { return !!this.__roots[widget.$$hash]; }, /* --------------------------------------------------------------------------- EVENT HANDLER --------------------------------------------------------------------------- */ /** * Internal event handler for activate event. * * @param e {qx.event.type.Focus} Focus event */ _onActivate : function(e) { var target = e.getTarget(); this.__activeChild = target; //this.debug("active: " + target); var root = this.__findFocusRoot(target); if (root != this.__currentRoot) { this.__currentRoot = root; } }, /** * Internal event handler for deactivate event. * * @param e {qx.event.type.Focus} Focus event */ _onDeactivate : function(e) { var target = e.getTarget(); if (this.__activeChild == target) { this.__activeChild = null; } }, /** * Internal event handler for focusin event. * * @param e {qx.event.type.Focus} Focus event */ _onFocusIn : function(e) { var target = e.getTarget(); if (target != this.__focusedChild) { this.__focusedChild = target; target.visualizeFocus(); } }, /** * Internal event handler for focusout event. * * @param e {qx.event.type.Focus} Focus event */ _onFocusOut : function(e) { var target = e.getTarget(); if (target == this.__focusedChild) { this.__focusedChild = null; target.visualizeBlur(); } }, /** * Internal event handler for TAB key. * * @param e {qx.event.type.KeySequence} Key event */ __onKeyPress : function(e) { if (e.getKeyIdentifier() != "Tab") { return; } if (!this.__currentRoot) { return; } // Stop all key-events with a TAB keycode e.stopPropagation(); e.preventDefault(); // Support shift key to reverse widget detection order var current = this.__focusedChild; if (!e.isShiftPressed()) { var next = current ? this.__getWidgetAfter(current) : this.__getFirstWidget(); } else { var next = current ? this.__getWidgetBefore(current) : this.__getLastWidget(); } // If there was a widget found, focus it if (next) { next.tabFocus(); } }, /* --------------------------------------------------------------------------- UTILS --------------------------------------------------------------------------- */ /** * Finds the next focus root, starting with the given widget. * * @param widget {qx.ui.core.Widget} The widget to find a focus root for. * @return {qx.ui.core.Widget|null} The focus root for the given widget or * <code>true</code> if no focus root could be found */ __findFocusRoot : function(widget) { var roots = this.__roots; while (widget) { if (roots[widget.$$hash]) { return widget; } widget = widget.getLayoutParent(); } return null; }, /* --------------------------------------------------------------------------- TAB SUPPORT IMPLEMENTATION --------------------------------------------------------------------------- */ /** * Compares the order of two widgets * * @param widget1 {qx.ui.core.Widget} Widget A * @param widget2 {qx.ui.core.Widget} Widget B * @return {Integer} A sort() compatible integer with values * small than 0, exactly 0 or bigger than 0. */ __compareTabOrder : function(widget1, widget2) { if (widget1 === widget2) { return 0; } // Sort-Check #1: Tab-Index var tab1 = widget1.getTabIndex() || 0; var tab2 = widget2.getTabIndex() || 0; if (tab1 != tab2) { return tab1 - tab2; } // Computing location var el1 = widget1.getContentElement().getDomElement(); var el2 = widget2.getContentElement().getDomElement(); var Location = qx.bom.element.Location; var loc1 = Location.get(el1); var loc2 = Location.get(el2); // Sort-Check #2: Top-Position if (loc1.top != loc2.top) { return loc1.top - loc2.top; } // Sort-Check #3: Left-Position if (loc1.left != loc2.left) { return loc1.left - loc2.left; } // Sort-Check #4: zIndex var z1 = widget1.getZIndex(); var z2 = widget2.getZIndex(); if (z1 != z2) { return z1 - z2; } return 0; }, /** * Returns the first widget. * * @return {qx.ui.core.Widget} Returns the first (positioned) widget from * the current root. */ __getFirstWidget : function() { return this.__getFirst(this.__currentRoot, null); }, /** * Returns the last widget. * * @return {qx.ui.core.Widget} Returns the last (positioned) widget from * the current root. */ __getLastWidget : function() { return this.__getLast(this.__currentRoot, null); }, /** * Returns the widget after the given one. * * @param widget {qx.ui.core.Widget} Widget to start with * @return {qx.ui.core.Widget} The found widget. */ __getWidgetAfter : function(widget) { var root = this.__currentRoot; if (root == widget) { return this.__getFirstWidget(); } while (widget && widget.getAnonymous()) { widget = widget.getLayoutParent(); } if (widget == null) { return []; } var result = []; this.__collectAllAfter(root, widget, result); result.sort(this.__compareTabOrder); var len = result.length; return len > 0 ? result[0] : this.__getFirstWidget(); }, /** * Returns the widget before the given one. * * @param widget {qx.ui.core.Widget} Widget to start with * @return {qx.ui.core.Widget} The found widget. */ __getWidgetBefore : function(widget) { var root = this.__currentRoot; if (root == widget) { return this.__getLastWidget(); } while (widget && widget.getAnonymous()) { widget = widget.getLayoutParent(); } if (widget == null) { return []; } var result = []; this.__collectAllBefore(root, widget, result); result.sort(this.__compareTabOrder); var len = result.length; return len > 0 ? result[len - 1] : this.__getLastWidget(); }, /* --------------------------------------------------------------------------- INTERNAL API USED BY METHODS ABOVE --------------------------------------------------------------------------- */ /** * Collects all widgets which are after the given widget in * the given parent widget. Append all found children to the * <code>list</code>. * * @param parent {qx.ui.core.Widget} Parent widget * @param widget {qx.ui.core.Widget} Child widget to start with * @param result {Array} Result list */ __collectAllAfter : function(parent, widget, result) { var children = parent.getLayoutChildren(); var child; for (var i=0, l=children.length; i<l; i++) { child = children[i]; // Filter spacers etc. if (!(child instanceof qx.ui.core.Widget)) { continue; } if (!this.isFocusRoot(child) && child.isEnabled() && child.isVisible()) { if (child.isTabable() && this.__compareTabOrder(widget, child) < 0) { result.push(child); } this.__collectAllAfter(child, widget, result); } } }, /** * Collects all widgets which are before the given widget in * the given parent widget. Append all found children to the * <code>list</code>. * * @param parent {qx.ui.core.Widget} Parent widget * @param widget {qx.ui.core.Widget} Child widget to start with * @param result {Array} Result list */ __collectAllBefore : function(parent, widget, result) { var children = parent.getLayoutChildren(); var child; for (var i=0, l=children.length; i<l; i++) { child = children[i]; // Filter spacers etc. if (!(child instanceof qx.ui.core.Widget)) { continue; } if (!this.isFocusRoot(child) && child.isEnabled() && child.isVisible()) { if (child.isTabable() && this.__compareTabOrder(widget, child) > 0) { result.push(child); } this.__collectAllBefore(child, widget, result); } } }, /** * Find first (positioned) widget. (Sorted by coordinates, zIndex, etc.) * * @param parent {qx.ui.core.Widget} Parent widget * @param firstWidget {qx.ui.core.Widget?null} Current first widget * @return {qx.ui.core.Widget} The first (positioned) widget */ __getFirst : function(parent, firstWidget) { var children = parent.getLayoutChildren(); var child; for (var i=0, l=children.length; i<l; i++) { child = children[i]; // Filter spacers etc. if (!(child instanceof qx.ui.core.Widget)) { continue; } // Ignore focus roots completely if (!this.isFocusRoot(child) && child.isEnabled() && child.isVisible()) { if (child.isTabable()) { if (firstWidget == null || this.__compareTabOrder(child, firstWidget) < 0) { firstWidget = child; } } // Deep iteration into children hierarchy firstWidget = this.__getFirst(child, firstWidget); } } return firstWidget; }, /** * Find last (positioned) widget. (Sorted by coordinates, zIndex, etc.) * * @param parent {qx.ui.core.Widget} Parent widget * @param lastWidget {qx.ui.core.Widget?null} Current last widget * @return {qx.ui.core.Widget} The last (positioned) widget */ __getLast : function(parent, lastWidget) { var children = parent.getLayoutChildren(); var child; for (var i=0, l=children.length; i<l; i++) { child = children[i]; // Filter spacers etc. if (!(child instanceof qx.ui.core.Widget)) { continue; } // Ignore focus roots completely if (!this.isFocusRoot(child) && child.isEnabled() && child.isVisible()) { if (child.isTabable()) { if (lastWidget == null || this.__compareTabOrder(child, lastWidget) > 0) { lastWidget = child; } } // Deep iteration into children hierarchy lastWidget = this.__getLast(child, lastWidget); } } return lastWidget; } }, /* ***************************************************************************** DESTRUCTOR ***************************************************************************** */ destruct : function() { this._disposeMap("__roots"); this.__focusedChild = this.__activeChild = this.__currentRoot = null; } });