[gnome-shell] Magnifier: Implement focus and caret tracking



commit 9d8f30f955fcd15a509a19310c0e946f13eca3a6
Author: Magdalen Berns <thisMagpie live com>
Date:   Wed Sep 4 18:53:36 2013 +0100

    Magnifier: Implement focus and caret tracking
    
    A11y users who use the magnifier may have trouble
    focusing when they're typing or trying to keynav.
    Implement a new system so that they can have the
    magnifier track the caret and focus instead instead
    of just the mouse.
    
    Bug https://bugzilla.gnome.org/show_bug.cgi?id=647074

 js/Makefile.am             |    1 +
 js/ui/focusCaretTracker.js |   68 +++++++++++++++++++
 js/ui/magnifier.js         |  155 ++++++++++++++++++++++++++++++++++++++------
 3 files changed, 204 insertions(+), 20 deletions(-)
---
diff --git a/js/Makefile.am b/js/Makefile.am
index f9a1474..7df786f 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -55,6 +55,7 @@ nobase_dist_js_DATA =         \
        ui/extensionSystem.js   \
        ui/extensionDownloader.js \
        ui/environment.js       \
+       ui/focusCaretTracker.js\
        ui/ibusCandidatePopup.js\
        ui/grabHelper.js        \
        ui/iconGrid.js          \
diff --git a/js/ui/focusCaretTracker.js b/js/ui/focusCaretTracker.js
new file mode 100644
index 0000000..c4c4d7f
--- /dev/null
+++ b/js/ui/focusCaretTracker.js
@@ -0,0 +1,68 @@
+/** -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright 2012 Inclusive Design Research Centre, OCAD University.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author:
+ *   Joseph Scheuhammer <clown alum mit edu>
+ * Contributor:
+ *   Magdalen Berns <m berns sms ed ac uk>
+ */
+
+const Atspi = imports.gi.Atspi;
+const Lang = imports.lang;
+const Signals = imports.signals;
+
+const CARETMOVED        = 'object:text-caret-moved';
+const STATECHANGED      = 'object:state-changed';
+
+const FocusCaretTracker = new Lang.Class({
+    Name: 'FocusCaretTracker',
+
+    _init: function() {
+        Atspi.init();
+        this._atspiListener = Atspi.EventListener.new(Lang.bind(this, this._onChanged));
+    },
+
+    _onChanged: function(event) {
+        let update = null;
+
+        if (event.type.indexOf(STATECHANGED) == 0)
+            update = 'focus-changed';
+        else if (event.type == CARETMOVED)
+            update = 'caret-moved';
+        if(update != null)
+            this.emit(update, event);
+    },
+
+    registerFocusListener: function() {
+        return this._atspiListener.register(STATECHANGED + ':focused') &&
+               this._atspiListener.register(STATECHANGED + ':selected');
+    },
+
+    registerCaretListener: function() {
+        return this._atspiListener.register(CARETMOVED);
+    },
+
+    deregisterFocusListener: function() {
+        return this._atspiListener.deregister(STATECHANGED + ':focused') &&
+               this._atspiListener.deregister(STATECHANGED + ':selected');
+    },
+
+    deregisterCaretListener: function() {
+        return this._atspiListener.deregister(CARETMOVED);
+    }
+});
+Signals.addSignalMethods(FocusCaretTracker.prototype);
diff --git a/js/ui/magnifier.js b/js/ui/magnifier.js
index 7c68ea8..6f7fc56 100644
--- a/js/ui/magnifier.js
+++ b/js/ui/magnifier.js
@@ -1,5 +1,6 @@
 // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
 
+const Atspi = imports.gi.Atspi;
 const Clutter = imports.gi.Clutter;
 const GDesktopEnums = imports.gi.GDesktopEnums;
 const Gio = imports.gi.Gio;
@@ -10,6 +11,7 @@ const Mainloop = imports.mainloop;
 const Meta = imports.gi.Meta;
 const Signals = imports.signals;
 
+const FocusCaretTracker = imports.ui.focusCaretTracker;
 const Main = imports.ui.main;
 const MagnifierDBus = imports.ui.magnifierDBus;
 const Params = imports.misc.params;
@@ -37,6 +39,8 @@ const CONTRAST_BLUE_KEY         = 'contrast-blue';
 const LENS_MODE_KEY             = 'lens-mode';
 const CLAMP_MODE_KEY            = 'scroll-at-edges';
 const MOUSE_TRACKING_KEY        = 'mouse-tracking';
+const FOCUS_TRACKING_KEY        = 'focus-tracking';
+const CARET_TRACKING_KEY        = 'caret-tracking';
 const SHOW_CROSS_HAIRS_KEY      = 'show-cross-hairs';
 const CROSS_HAIRS_THICKNESS_KEY = 'cross-hairs-thickness';
 const CROSS_HAIRS_COLOR_KEY     = 'cross-hairs-color';
@@ -449,6 +453,14 @@ const Magnifier = new Lang.Class({
             if (aPref)
                 zoomRegion.setMouseTrackingMode(aPref);
 
+            aPref = this._settings.get_enum(FOCUS_TRACKING_KEY);
+            if (aPref)
+                zoomRegion.setFocusTrackingMode(aPref);
+
+            aPref = this._settings.get_enum(CARET_TRACKING_KEY);
+            if (aPref)
+                zoomRegion.setCaretTrackingMode(aPref);
+
             aPref = this._settings.get_boolean(INVERT_LIGHTNESS_KEY);
             if (aPref)
                 zoomRegion.setInvertLightness(aPref);
@@ -488,6 +500,10 @@ const Magnifier = new Lang.Class({
                                Lang.bind(this, this._updateClampMode));
         this._settings.connect('changed::' + MOUSE_TRACKING_KEY,
                                Lang.bind(this, this._updateMouseTrackingMode));
+        this._settings.connect('changed::' + FOCUS_TRACKING_KEY,
+                               Lang.bind(this, this._updateFocusTrackingMode));
+        this._settings.connect('changed::' + CARET_TRACKING_KEY,
+                               Lang.bind(this, this._updateCaretTrackingMode));
 
         this._settings.connect('changed::' + INVERT_LIGHTNESS_KEY,
                                Lang.bind(this, this._updateInvertLightness));
@@ -585,6 +601,24 @@ const Magnifier = new Lang.Class({
         }
     },
 
+    _updateFocusTrackingMode: function() {
+        // Applies only to the first zoom region.
+        if (this._zoomRegions.length) {
+            this._zoomRegions[0].setFocusTrackingMode(
+                this._settings.get_enum(FOCUS_TRACKING_KEY)
+            );
+        }
+    },
+
+    _updateCaretTrackingMode: function() {
+        // Applies only to the first zoom region.
+        if (this._zoomRegions.length) {
+            this._zoomRegions[0].setCaretTrackingMode(
+                this._settings.get_enum(CARET_TRACKING_KEY)
+            );
+        }
+    },
+
     _updateInvertLightness: function() {
         // Applies only to the first zoom region.
         if (this._zoomRegions.length) {
@@ -623,7 +657,7 @@ const Magnifier = new Lang.Class({
             contrast.b = this._settings.get_double(CONTRAST_BLUE_KEY);
             this._zoomRegions[0].setContrast(contrast);
         }
-    },
+    }
 });
 Signals.addSignalMethods(Magnifier.prototype);
 
@@ -632,8 +666,11 @@ const ZoomRegion = new Lang.Class({
 
     _init: function(magnifier, mouseSourceActor) {
         this._magnifier = magnifier;
+        this._focusCaretTracker = new FocusCaretTracker.FocusCaretTracker();
 
         this._mouseTrackingMode = GDesktopEnums.MagnifierMouseTrackingMode.NONE;
+        this._focusTrackingMode = GDesktopEnums.MagnifierFocusTrackingMode.NONE;
+        this._caretTrackingMode = GDesktopEnums.MagnifierCaretTrackingMode.NONE;
         this._clampScrollingAtEdges = false;
         this._lensMode = false;
         this._screenPosition = GDesktopEnums.MagnifierScreenPosition.FULL_SCREEN;
@@ -659,9 +696,35 @@ const ZoomRegion = new Lang.Class({
         this._xMagFactor = 1;
         this._yMagFactor = 1;
         this._followingCursor = false;
+        this._xFocus = 0;
+        this._yFocus = 0;
+        this._xCaret = 0;
+        this._yCaret = 0;
 
         Main.layoutManager.connect('monitors-changed',
                                    Lang.bind(this, this._monitorsChanged));
+        this._focusCaretTracker.connect('caret-moved',
+                                    Lang.bind(this, this._updateCaret));
+        this._focusCaretTracker.connect('focus-changed',
+                                    Lang.bind(this, this._updateFocus));
+    },
+
+    _updateFocus: function(caller, event) {
+        let component = event.source.get_component_iface();
+        if (!component || event.detail1 != 1)
+            return;
+        let extents = component.get_extents(Atspi.CoordType.SCREEN);
+        [this._xFocus, this._yFocus] = [extents.x, extents.y]
+        this._centerFromFocusPosition();
+    },
+
+    _updateCaret: function(caller, event) {
+        let text = event.source.get_text_iface();
+        if (!text)
+            return;
+        let extents = text.get_character_extents(text.get_caret_offset(), 0);
+        [this._xCaret, this._yCaret] = [extents.x, extents.y];
+        this._centerFromCaretPosition();
     },
 
     /**
@@ -733,6 +796,30 @@ const ZoomRegion = new Lang.Class({
     },
 
     /**
+     * setFocusTrackingMode
+     * @mode:     One of the enum FocusTrackingMode values.
+     */
+    setFocusTrackingMode: function(mode) {
+        this._focusTrackingMode = mode;
+        if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.NONE)
+            this._focusCaretTracker.deregisterFocusListener();
+        else
+            this._focusCaretTracker.registerFocusListener();
+    },
+
+    /**
+     * setCaretTrackingMode
+     * @mode:     One of the enum CaretTrackingMode values.
+     */
+    setCaretTrackingMode: function(mode) {
+        this._caretTrackingMode = mode;
+        if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.NONE)
+            this._focusCaretTracker.deregisterCaretListener();
+        else
+            this._focusCaretTracker.registerCaretListener();
+    },
+
+    /**
      * setViewPort
      * Sets the position and size of the ZoomRegion on screen.
      * @viewPort:   Object defining the position and size of the view port.
@@ -1243,19 +1330,47 @@ const ZoomRegion = new Lang.Class({
         let yMouse = this._magnifier.yMouse;
 
         if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PROPORTIONAL) {
-            return this._centerFromMouseProportional(xMouse, yMouse);
+            return this._centerFromPointProportional(xMouse, yMouse);
         }
         else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PUSH) {
-            return this._centerFromMousePush(xMouse, yMouse);
+            return this._centerFromPointPush(xMouse, yMouse);
         }
         else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.CENTERED) {
-            return this._centerFromMouseCentered(xMouse, yMouse);
+            return this._centerFromPointCentered(xMouse, yMouse);
         }
 
         return null; // Should never be hit
     },
 
-    _centerFromMousePush: function(xMouse, yMouse) {
+    _centerFromCaretPosition: function() {
+        let xCaret = this._xCaret;
+        let yCaret = this._yCaret;
+
+        if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.PROPORTIONAL)
+            [xCaret, yCaret] = this._centerFromPointProportional(xCaret, yCaret);
+        else if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.PUSH)
+            [xCaret, yCaret] = this._centerFromPointPush(xCaret, yCaret);
+        else if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.CENTERED)
+            [xCaret, yCaret] = this._centerFromPointCentered(xCaret, yCaret);
+
+        this.scrollContentsTo(xCaret, yCaret);
+    },
+
+    _centerFromFocusPosition: function() {
+        let xFocus = this._xFocus;
+        let yFocus = this._yFocus;
+
+        if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.PROPORTIONAL)
+            [xFocus, yFocus] = this._centerFromPointProportional(xFocus, yFocus);
+        else if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.PUSH)
+            [xFocus, yFocus] = this._centerFromPointPush(xFocus, yFocus);
+        else if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.CENTERED)
+            [xFocus, yFocus] = this._centerFromPointCentered(xFocus, yFocus);
+
+        this.scrollContentsTo(xFocus, yFocus);
+    },
+
+    _centerFromPointPush: function(xPoint, yPoint) {
         let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI();
         let [cursorWidth, cursorHeight] = this._mouseSourceActor.get_size();
         let xPos = xRoi + widthRoi / 2;
@@ -1263,20 +1378,20 @@ const ZoomRegion = new Lang.Class({
         let xRoiRight = xRoi + widthRoi - cursorWidth;
         let yRoiBottom = yRoi + heightRoi - cursorHeight;
 
-        if (xMouse < xRoi)
-            xPos -= (xRoi - xMouse);
-        else if (xMouse > xRoiRight)
-            xPos += (xMouse - xRoiRight);
+        if (xPoint < xRoi)
+            xPos -= (xRoi - xPoint);
+        else if (xPoint > xRoiRight)
+            xPos += (xPoint - xRoiRight);
 
-        if (yMouse < yRoi)
-            yPos -= (yRoi - yMouse);
-        else if (yMouse > yRoiBottom)
-            yPos += (yMouse - yRoiBottom);
+        if (yPoint < yRoi)
+            yPos -= (yRoi - yPoint);
+        else if (yPoint > yRoiBottom)
+            yPos += (yPoint - yRoiBottom);
 
         return [xPos, yPos];
     },
 
-    _centerFromMouseProportional: function(xMouse, yMouse) {
+    _centerFromPointProportional: function(xPoint, yPoint) {
         let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI();
         let halfScreenWidth = global.screen_width / 2;
         let halfScreenHeight = global.screen_height / 2;
@@ -1285,16 +1400,16 @@ const ZoomRegion = new Lang.Class({
         let unscaledPadding = Math.min(this._viewPortWidth, this._viewPortHeight) / 5;
         let xPadding = unscaledPadding / this._xMagFactor;
         let yPadding = unscaledPadding / this._yMagFactor;
-        let xProportion = (xMouse - halfScreenWidth) / halfScreenWidth;   // -1 ... 1
-        let yProportion = (yMouse - halfScreenHeight) / halfScreenHeight; // -1 ... 1
-        let xPos = xMouse - xProportion * (widthRoi / 2 - xPadding);
-        let yPos = yMouse - yProportion * (heightRoi /2 - yPadding);
+        let xProportion = (xPoint - halfScreenWidth) / halfScreenWidth;   // -1 ... 1
+        let yProportion = (yPoint - halfScreenHeight) / halfScreenHeight; // -1 ... 1
+        let xPos = xPoint - xProportion * (widthRoi / 2 - xPadding);
+        let yPos = yPoint - yProportion * (heightRoi /2 - yPadding);
 
         return [xPos, yPos];
     },
 
-    _centerFromMouseCentered: function(xMouse, yMouse) {
-        return [xMouse, yMouse];
+    _centerFromPointCentered: function(xPoint, yPoint) {
+        return [xPoint, yPoint];
     },
 
     _screenToViewPort: function(screenX, screenY) {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]