[Laszlo-checkins] r11414 - openlaszlo/trunk/WEB-INF/lps/lfc/services

bargull@openlaszlo.org bargull at openlaszlo.org
Fri Oct 10 12:49:35 PDT 2008


Author: bargull
Date: 2008-10-10 12:49:32 -0700 (Fri, 10 Oct 2008)
New Revision: 11414

Modified:
   openlaszlo/trunk/WEB-INF/lps/lfc/services/LzFocus.lzs
Log:
Change 20081006-bargull-QmQ by bargull at dell--p4--2-53 on 2008-10-06 23:34:01
    in /home/Admin/src/svn/openlaszlo/trunk
    for http://svn.openlaszlo.org/openlaszlo/trunk

Summary: focusmanager bug fixes

New Features: LPP-7050

Bugs Fixed: LPP-7029, LPP-7092, LPP-7128, LPP-7130, LPP-7131

Technical Reviewer: max
QA Reviewer: (pending)
Doc Reviewer: (pending)

Documentation:

Release Notes:

Details:
bug fixes for LzFocusService:
- LPP-7029: account movedir before dispatching "onescapefocus"-event
- LPP-7131: don't send "onescapefocus" when calling "moveSelSubview()" from getNext() or getPrev()
- LPP-7130: recheck focus-change condition when updating newsel (if newsel was not focusable) "if (this.cseldest == newsel)"
- LPP-7128: only re-set focus if selection changed "if (this.__LZsfnextfocus != newsel)" 
- LPP-7092: set "blurring" to false after focus has changed

Removed "setNextKey()" and "setPrevKey()", both methods aren't supported since LPS3.1

Re-indented lines if necessary, added typing and used common service-classes structure.
    

Tests:
testcases are attached at the bugreports



Modified: openlaszlo/trunk/WEB-INF/lps/lfc/services/LzFocus.lzs
===================================================================
--- openlaszlo/trunk/WEB-INF/lps/lfc/services/LzFocus.lzs	2008-10-10 19:39:28 UTC (rev 11413)
+++ openlaszlo/trunk/WEB-INF/lps/lfc/services/LzFocus.lzs	2008-10-10 19:49:32 UTC (rev 11414)
@@ -20,53 +20,67 @@
   * See <a href="${dguide}input-devices.html">The Software Developer's Guide</a> for a discussion of keyboard focus.
   * </p>
   * <p>Note that the view.getNextSelection() and view.getPrevSelection() methods can be overridden to change the tab order</p>
-  * 
+  *
   * @shortdesc Handles keyboard focus.
   */
-dynamic class LzFocusService extends LzEventable {
+public dynamic final class LzFocusService extends LzEventable {
 
     /** Sent when the focus changes, with the argument being the view
      * that was just focused. If nothing is focused, this event is sent with null.
      * @access public
-     * @lzxtype event 
+     * @lzxtype event
      */
-    var onfocus = LzDeclaredEvent;
+    var onfocus :LzDeclaredEventClass = LzDeclaredEvent;
 
     /** Sent when the last focusable object has been reached.
      * @access public
-     * @lzxtype event 
+     * @lzxtype event
      */
-    var onescapefocus = LzDeclaredEvent;
+    var onescapefocus :LzDeclaredEventClass = LzDeclaredEvent;
 
     /** A reference to the last view that held the focus
      * @type LzView
      * @keywords readonly
      */
-    var lastfocus = null;
+    var lastfocus :LzView = null;
     /** @access private */
-    var csel = null;
+    var csel :LzView = null;
     /** @access private */
-    var cseldest = null;
+    var cseldest :LzView = null;
 
-    /** @access private */
+    /** The focus service. Also available as the global
+     * <code>lz.Focus</code>.
+     *
+     * @type LzFocusService
+     * @keywords readonly
+     * @devnote this should be a public getter to enforce readonly
+     */
+    public static const LzFocus:LzFocusService;
+
+    /** @access private
+     * @devnote AS3 does not allow private constructors, so we need the
+     * error
+     */
     function LzFocusService () {
+        super();
+        //    if (LzFocusService.LzFocus) {
+        //      throw new Error("There can be only one LzFocus");
+        //    }
+        this.upDel = new LzDelegate(this, "gotKeyUp", lz.Keys, "onkeyup");
+        this.downDel = new LzDelegate(this, "gotKeyDown", lz.Keys, "onkeydown");
+        // This must be registered here after both lz.Focus and lz.Keys are initted
+        this.lastfocusDel = new LzDelegate(lz.Keys, "gotLastFocus", this, "onescapefocus");
     }
 
-    /** @access private */
-    function initialize () {
-        this.upDel = new LzDelegate( lz.Focus , "gotKeyUp", lz.Keys, "onkeyup");
-        this.downDel = new LzDelegate( lz.Focus , "gotKeyDown", lz.Keys, "onkeydown");
-        // This must be registered here after both lz.Focus and lz.Keys are initted 
-        this.lastfocusDel = new LzDelegate(lz.Keys, 'gotLastFocus', lz.Focus, 'onescapefocus');
-    }
+    // Create the singleton
+    LzFocusService.LzFocus = new LzFocusService();
 
-
     /** @access private */
-    var upDel;
+    var upDel :LzDelegate;
     /** @access private */
-    var downDel;
+    var downDel :LzDelegate;
     /** @access private */
-    var lastfocusDel;
+    var lastfocusDel :LzDelegate;
 
     /** This attribute is set to true when the focus has moved
      * because the user has hit the next or prev focus key (usually 'tab' and
@@ -76,32 +90,31 @@
      * @type Boolean
      * @keywords readonly
      */
-    var focuswithkey = false;
+    var focuswithkey :Boolean = false;
 
     /** @access private */
-    var __LZskipblur = false;
+    var __LZskipblur :Boolean = false;
     /** @access private */
-    var __LZsfnextfocus = -1;
+    var __LZsfnextfocus :* = -1;
     /** @access private */
-    var __LZsfrunning = false;
+    var __LZsfrunning :Boolean = false;
 
     /**
      * @access private
      */
-    function gotKeyUp( kC ){
-        //Debug.write("gotKeyUp "+kC);
-        if (this.csel && this.csel.onkeyup.ready) this.csel.onkeyup.sendEvent( kC );
+    function gotKeyUp (kC) :void {
+        if (this.csel && this.csel.onkeyup.ready)
+            this.csel.onkeyup.sendEvent( kC );
     }
 
     /**
      * @access private
      */
-    function gotKeyDown( kC ){
-        //Debug.write("gotKeyDown ", kC, lz.Keys.isKeyDown('shift'));
+    function gotKeyDown (kC) :void {
         if (this.csel && this.csel.onkeydown.ready)
             this.csel.onkeydown.sendEvent( kC );
-        if ( kC == lz.Keys.keyCodes.tab ){
-            if ( lz.Keys.isKeyDown( 'shift' ) ){
+        if (kC == lz.Keys.keyCodes.tab) {
+            if (lz.Keys.isKeyDown( 'shift' )) {
                 this.prev();
             } else {
                 this.next();
@@ -110,34 +123,16 @@
     }
 
     /**
+     * Called from lz.ModeManager#handleMouseEvent() on "onmousedown"
      * @access private
      */
-    function setNextKey( k ){
-        if ( $debug ){
-            Debug.error( 'Next key can no longer be set.');
+    function __LZcheckFocusChange (v:LzView) :void {
+        if (v.focusable) {
+            this.setFocus( v, false );
         }
     }
 
     /**
-     * @access private
-     */
-    function setPrevKey( k ){
-        if ( $debug ){
-            Debug.error( 'Prev key can no longer be set.');
-        }
-    }
-
-    /**
-     * @access private
-     */
-    function __LZcheckFocusChange ( v ){
-        //    if ( 'focusable' in v && v.focusable ){
-        if ( v && v.focusable ){
-            this.setFocus( v , false );
-        }
-    }
-
-    /**
      * Set the focus to the given view.  If this is not the currently
      * focused view, an onblur event is sent to the currently focused view,
      * and an onfocus event is sent to the new view. When setFocus is called as the
@@ -149,92 +144,128 @@
      * The state of the view may be unknown during the blur/focus process. When
      * a view loses focus, its blurring variable is set to true during the
      * process.
-     * 
+     *
      * @param LzView newsel: The view to focus or null to clear focus
      */
-    function setFocus  ( newsel, fwkey = null ){
+    function setFocus (newsel:LzView, fwkey:* = null) :void {
         //undocumented attribute focuswithkey. If the second argument to this
         //method is defined, it sets the value of this.focuswithkey to the given
         //value.
 
-        if ( this.__LZsfrunning ){
+        if (this.__LZsfrunning) {
+            // another focus-change is currently running, 
+            // remember new selection and return
             this.__LZsfnextfocus = newsel;
             return;
         }
 
-        if ( this.cseldest == newsel ) {
+        if (this.cseldest == newsel) {
+            // don't change focus if focused view didn't change
             return;
         }
 
-        if ( this.csel && this.csel.shouldYieldFocus && !this.csel.shouldYieldFocus() ) {
+        var prevsel:LzView = this.csel;
+        if (prevsel && !prevsel.shouldYieldFocus()) {
+            // current selection does not allow a focus change
             return;
         }
 
-        var prevsel = this.csel;
+        if (newsel && !newsel.focusable) {
+            newsel = this.getNext(newsel);
+            if (this.cseldest == newsel) {
+                // don't change focus if focused view didn't change
+                return;
+            }
+        }
+
         if (prevsel) {
             // Give the view warning that it will be losing focus
             prevsel.blurring = true;
         }
 
+        // acquire focus lock
         this.__LZsfnextfocus = -1;
         this.__LZsfrunning = true;
-
-        if ( newsel && !newsel.focusable) {
-            newsel = this.getNext(newsel);
-        }
         this.cseldest = newsel;
 
-        if ( fwkey != null ){
-            this.focuswithkey  = fwkey;
+        if (fwkey != null) {
+            this.focuswithkey = !!fwkey; //coerce to boolean
         }
 
-        if ( !this.__LZskipblur ){
+        // check skipblur-flag to prevent multiple "onblur" events
+        if (! this.__LZskipblur) {
             this.__LZskipblur = true;
-            if (this.csel && this.csel.onblur.ready)
-                this.csel.onblur.sendEvent( newsel );
-            if ( this.__LZsfnextfocus != -1 ) {
-                //we've been called again
-                this.__LZsfrunning = false;
-                this.setFocus( this.__LZsfnextfocus );
-                return;
+            if (prevsel && prevsel.onblur.ready) {
+                prevsel.onblur.sendEvent( newsel );
+                var next:* = this.__LZsfnextfocus;
+                if (next != -1) {
+                    // we've been called again because of dispatching "onblur"
+                    if (next && !next.focusable) {
+                        next = this.getNext(next);
+                    }
+                    if (next != newsel) {
+                        // but only re-set focus if selection did change
+                        this.__LZsfrunning = false;
+                        this.setFocus( next );
+                        return;
+                    }
+                }
             }
         }
 
         //now focus changes
-        this.lastfocus = this.csel;
+        this.lastfocus = prevsel;
         this.csel = newsel;
         this.__LZskipblur = false;
-    
 
+        if (prevsel) {
+            // The focus is changed.
+            prevsel.blurring = false;
+        }
+
         if (newsel && newsel.onfocus.ready) {
             newsel.onfocus.sendEvent( newsel );
+            var next:* = this.__LZsfnextfocus;
+            if (next != -1) {
+                // we've been called again because of dispatching "onfocus"
+                if (next && !next.focusable) {
+                    next = this.getNext(next);
+                }
+                if (next != newsel) {
+                    // but only re-set focus if selection did change
+                    this.__LZsfrunning = false;
+                    this.setFocus( next );
+                    return;
+                }
+            }
         }
-        if ( this.__LZsfnextfocus != -1 ) {
-            //we've been called again
-            this.__LZsfrunning = false;
-            this.setFocus( this.__LZsfnextfocus );
-            return;
+
+        if (this.onfocus.ready) {
+            this.onfocus.sendEvent( newsel );
+            var next:* = this.__LZsfnextfocus;
+            if (next != -1) {
+                // we've been called again because of dispatching "onfocus" (global)
+                if (next && !next.focusable) {
+                    next = this.getNext(next);
+                }
+                if (next != newsel) {
+                    // but only re-set focus if selection did change
+                    this.__LZsfrunning = false;
+                    this.setFocus( next );
+                    return;
+                }
+            }
         }
 
-        if (this.onfocus.ready) this.onfocus.sendEvent( newsel );
+        // release focus lock
         this.__LZsfrunning = false;
-        if ( this.__LZsfnextfocus != -1 ) {
-            //we've been called again
-            this.setFocus( this.__LZsfnextfocus );
-            return;
-        }
-
-        if (prevsel) {
-            // The focus is changed.
-            prevsel.blurring = false;
-        }
     }
 
     /**
      * Remove the focus from the currently focused view (if there is one).
      * An 'onblur' event is first sent to the view.
      */
-    function clearFocus ( ){
+    function clearFocus () :void {
         this.setFocus( null );
     }
 
@@ -242,87 +273,79 @@
      * Get the currently focused view.
      * @return LzView: The view that has the focus, or null if none does.
      */
-    function getFocus (){
+    function getFocus () :LzView {
         return this.csel;
     }
 
     /**
      * Move the focus to the next focusable view.
      */
-    function next (){
+    function next () :void {
         this.genMoveSelection( 1 );
     }
 
     /**
+     * Move the focus to the previous focusable view.
+     */
+    function prev () :void {
+        this.genMoveSelection( -1 );
+    }
+
+    /**
      * Returns the next focusable view.
-     * @param LzView focusview: optional starting view. By default focusview 
+     * @param LzView focusview: optional starting view. By default focusview
      * is the current focus.
      * @return LzView: The view that would be the next focus.
      */
-    function getNext ( focusview ){
-        if ( !focusview ) focusview = this.csel;
-        return this.moveSelSubview( focusview , 1 ) ;
+    function getNext (focusview:LzView = null) :LzView {
+        return this.moveSelSubview( focusview || this.csel, 1, false );
     }
 
     /**
      * Returns the previous focusable view.
-     * @param LzView focusview: optional starting view. By default focusview 
+     * @param LzView focusview: optional starting view. By default focusview
      * is the current focus.
-     * @return LzView: The view that would be the focus if you shift tabbed 
+     * @return LzView: The view that would be the focus if you shift tabbed
      */
-    function getPrev ( focusview ){
-        if ( !focusview ) focusview = this.csel;
-        return this.moveSelSubview( focusview , -1 ) ;
+    function getPrev (focusview:LzView = null) :LzView {
+        return this.moveSelSubview( focusview || this.csel, -1, false );
     }
 
-
     /**
-     * Move the focus to the previous focusable view.
-     */
-    function prev (){
-        this.genMoveSelection( -1 );
-    }
-
-    /**
      * @access private
      */
-    function genMoveSelection ( movedir  ){
-        var sel = this.csel;
-        var check = sel;
-
-        while ( sel && check != canvas ){
-            if (!check.visible ) {
+    function genMoveSelection (movedir:int) :void {
+        var sel:LzView = this.csel;
+        var check:LzView = sel;
+        while (sel && check != canvas) {
+            if (! check.visible) {
                 sel = null;
             }
             check = check.immediateparent;
         }
 
-        if ( sel == null ){
+        if (sel == null) {
             sel = lz.ModeManager.getModalView();
         }
-
-        var v = null;
-        if (sel && sel[ "get"+(movedir == 1 ? "Next" : "Prev")+"Selection" ]) v = sel[ "get"+(movedir == 1 ? "Next" : "Prev")+"Selection" ]();
-        if ( v == null ){
-            v = this.moveSelSubview( sel , movedir ) ;
+        
+        var meth:String = "get" + (movedir == 1 ? "Next" : "Prev") + "Selection";
+        var v:LzView = sel ? sel[meth]() : null;
+        if (v == null) {
+            v = this.moveSelSubview( sel, movedir, true );
         }
-
-        if ( !lz.ModeManager.__LZallowFocus( v ) ){
-            return;
+        if (lz.ModeManager.__LZallowFocus( v )) {
+            this.setFocus( v, true );
         }
-
-        this.setFocus( v , true );
     }
 
-
     /**
      * Append those of v and its descendants that are focusable, to accum.
      * Always include 'include', and include focus traps but don't descend
      * into them unless this is the outermost call (and top=true).
-     * 
+     *
      * @access private
      */
-    function accumulateSubviews(accum, v, includep, top=false) {
+    function accumulateSubviews (accum:Array, v:LzView, includep:LzView, top:Boolean) :void {
         // Always include the current view, even if it's not focusable,
         // since its index is used to find a focusable neighbor.
         if (v == includep || (v.focusable && v.visible))
@@ -330,20 +353,19 @@
         // Don't descend into focus traps, except always consider children
         // of the outermost call.
         if (top || (!v.focustrap && v.visible))
-            for (var i = 0; i < v.subviews.length; i++)
+            for (var i:int = 0; i < v.subviews.length; i++)
                 this.accumulateSubviews(accum, v.subviews[i], includep, false);
     }
 
-
     /**
      * Return an item in the same focus group as v, either preceding it (mvdir==-1)
      * or following it (mvdir==1) in preorder.
-     * 
+     *
      * @access private
      */
-    function moveSelSubview ( v , mvdir ){
+    function moveSelSubview (v:LzView, mvdir:int, sendEsc:Boolean) :LzView {
         // Find the closest parent that doesn't cross a focus trap boundary.
-        var root = v || canvas;
+        var root:LzView = v || canvas;
         // If v is a focus trap, make sure that we at least step up to its
         // parent, in order to tab to its siblings.
         // I don't think this is right, but I'm not 100% sure, so leaving comment
@@ -358,25 +380,33 @@
             root = root.immediateparent;
         // collect selectable children into focusgroup
         var focusgroup:Array = [];
+        // TODO: [20081006 anba] this is slow, we need to come up with a faster alternative
         this.accumulateSubviews(focusgroup, root, v, true);
 
         // set index to the index of v within the current focus group.
         var index:int = -1;
-        for (var i:int = 0; i < focusgroup.length; ++i)
-            if (focusgroup[i] == v) {
+        var fglen:int = focusgroup.length;
+        var escape:Boolean = false;
+        for (var i:int = 0; i < fglen; ++i) {
+            if (focusgroup[i] === v) {
+                escape = (mvdir == -1 && i == 0) || (mvdir == 1 && i == fglen - 1);
                 index = i;
                 break;
             }
+        }
 
-        if (index == focusgroup.length - 1) {
+        if (sendEsc && escape) {
             this.onescapefocus.sendEvent();
         }
+
         // If the current focus group doesn't include v, mvdir==1 should select
         // the first item and mvdir==-1 should select the last item.  index==-1
         // and mvdir==1 will already work for the first case.  Fix the second:
         if (index == -1 && mvdir == -1)
             index = 0;
-        index = (index + mvdir + focusgroup.length) % focusgroup.length;
+        // remember this is no modular arithmetic in a mathematical sense,
+        // so we need to add 'focusgroup.length'
+        index = (index + mvdir + fglen) % fglen;
         return focusgroup[index];
     }
 }
@@ -384,9 +414,8 @@
 
 /**
   * lz.Focus is a shortcut for <link linkend="LzFocusService">LzFocusService.LzFocus</link>
-  * This service manages the keyboard focus. At any time, at most one view has the keyboard focus. This is the view that receives key events when a key is pressed. 
+  * This service manages the keyboard focus. At any time, at most one view has the keyboard focus. This is the view that receives key events when a key is pressed.
   *
   * @shortdesc Handles keyboard focus.
   */
-lz.Focus= new LzFocusService();
-lz.Focus.initialize();
+lz.Focus = LzFocusService.LzFocus;



More information about the Laszlo-checkins mailing list