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
This commit is contained in:
parent
420db828e9
commit
9d8f30f955
@ -55,6 +55,7 @@ nobase_dist_js_DATA = \
|
|||||||
ui/extensionSystem.js \
|
ui/extensionSystem.js \
|
||||||
ui/extensionDownloader.js \
|
ui/extensionDownloader.js \
|
||||||
ui/environment.js \
|
ui/environment.js \
|
||||||
|
ui/focusCaretTracker.js\
|
||||||
ui/ibusCandidatePopup.js\
|
ui/ibusCandidatePopup.js\
|
||||||
ui/grabHelper.js \
|
ui/grabHelper.js \
|
||||||
ui/iconGrid.js \
|
ui/iconGrid.js \
|
||||||
|
68
js/ui/focusCaretTracker.js
Normal file
68
js/ui/focusCaretTracker.js
Normal file
@ -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);
|
@ -1,5 +1,6 @@
|
|||||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
|
||||||
|
const Atspi = imports.gi.Atspi;
|
||||||
const Clutter = imports.gi.Clutter;
|
const Clutter = imports.gi.Clutter;
|
||||||
const GDesktopEnums = imports.gi.GDesktopEnums;
|
const GDesktopEnums = imports.gi.GDesktopEnums;
|
||||||
const Gio = imports.gi.Gio;
|
const Gio = imports.gi.Gio;
|
||||||
@ -10,6 +11,7 @@ const Mainloop = imports.mainloop;
|
|||||||
const Meta = imports.gi.Meta;
|
const Meta = imports.gi.Meta;
|
||||||
const Signals = imports.signals;
|
const Signals = imports.signals;
|
||||||
|
|
||||||
|
const FocusCaretTracker = imports.ui.focusCaretTracker;
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
const MagnifierDBus = imports.ui.magnifierDBus;
|
const MagnifierDBus = imports.ui.magnifierDBus;
|
||||||
const Params = imports.misc.params;
|
const Params = imports.misc.params;
|
||||||
@ -37,6 +39,8 @@ const CONTRAST_BLUE_KEY = 'contrast-blue';
|
|||||||
const LENS_MODE_KEY = 'lens-mode';
|
const LENS_MODE_KEY = 'lens-mode';
|
||||||
const CLAMP_MODE_KEY = 'scroll-at-edges';
|
const CLAMP_MODE_KEY = 'scroll-at-edges';
|
||||||
const MOUSE_TRACKING_KEY = 'mouse-tracking';
|
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 SHOW_CROSS_HAIRS_KEY = 'show-cross-hairs';
|
||||||
const CROSS_HAIRS_THICKNESS_KEY = 'cross-hairs-thickness';
|
const CROSS_HAIRS_THICKNESS_KEY = 'cross-hairs-thickness';
|
||||||
const CROSS_HAIRS_COLOR_KEY = 'cross-hairs-color';
|
const CROSS_HAIRS_COLOR_KEY = 'cross-hairs-color';
|
||||||
@ -449,6 +453,14 @@ const Magnifier = new Lang.Class({
|
|||||||
if (aPref)
|
if (aPref)
|
||||||
zoomRegion.setMouseTrackingMode(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);
|
aPref = this._settings.get_boolean(INVERT_LIGHTNESS_KEY);
|
||||||
if (aPref)
|
if (aPref)
|
||||||
zoomRegion.setInvertLightness(aPref);
|
zoomRegion.setInvertLightness(aPref);
|
||||||
@ -488,6 +500,10 @@ const Magnifier = new Lang.Class({
|
|||||||
Lang.bind(this, this._updateClampMode));
|
Lang.bind(this, this._updateClampMode));
|
||||||
this._settings.connect('changed::' + MOUSE_TRACKING_KEY,
|
this._settings.connect('changed::' + MOUSE_TRACKING_KEY,
|
||||||
Lang.bind(this, this._updateMouseTrackingMode));
|
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,
|
this._settings.connect('changed::' + INVERT_LIGHTNESS_KEY,
|
||||||
Lang.bind(this, this._updateInvertLightness));
|
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() {
|
_updateInvertLightness: function() {
|
||||||
// Applies only to the first zoom region.
|
// Applies only to the first zoom region.
|
||||||
if (this._zoomRegions.length) {
|
if (this._zoomRegions.length) {
|
||||||
@ -623,7 +657,7 @@ const Magnifier = new Lang.Class({
|
|||||||
contrast.b = this._settings.get_double(CONTRAST_BLUE_KEY);
|
contrast.b = this._settings.get_double(CONTRAST_BLUE_KEY);
|
||||||
this._zoomRegions[0].setContrast(contrast);
|
this._zoomRegions[0].setContrast(contrast);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
Signals.addSignalMethods(Magnifier.prototype);
|
Signals.addSignalMethods(Magnifier.prototype);
|
||||||
|
|
||||||
@ -632,8 +666,11 @@ const ZoomRegion = new Lang.Class({
|
|||||||
|
|
||||||
_init: function(magnifier, mouseSourceActor) {
|
_init: function(magnifier, mouseSourceActor) {
|
||||||
this._magnifier = magnifier;
|
this._magnifier = magnifier;
|
||||||
|
this._focusCaretTracker = new FocusCaretTracker.FocusCaretTracker();
|
||||||
|
|
||||||
this._mouseTrackingMode = GDesktopEnums.MagnifierMouseTrackingMode.NONE;
|
this._mouseTrackingMode = GDesktopEnums.MagnifierMouseTrackingMode.NONE;
|
||||||
|
this._focusTrackingMode = GDesktopEnums.MagnifierFocusTrackingMode.NONE;
|
||||||
|
this._caretTrackingMode = GDesktopEnums.MagnifierCaretTrackingMode.NONE;
|
||||||
this._clampScrollingAtEdges = false;
|
this._clampScrollingAtEdges = false;
|
||||||
this._lensMode = false;
|
this._lensMode = false;
|
||||||
this._screenPosition = GDesktopEnums.MagnifierScreenPosition.FULL_SCREEN;
|
this._screenPosition = GDesktopEnums.MagnifierScreenPosition.FULL_SCREEN;
|
||||||
@ -659,9 +696,35 @@ const ZoomRegion = new Lang.Class({
|
|||||||
this._xMagFactor = 1;
|
this._xMagFactor = 1;
|
||||||
this._yMagFactor = 1;
|
this._yMagFactor = 1;
|
||||||
this._followingCursor = false;
|
this._followingCursor = false;
|
||||||
|
this._xFocus = 0;
|
||||||
|
this._yFocus = 0;
|
||||||
|
this._xCaret = 0;
|
||||||
|
this._yCaret = 0;
|
||||||
|
|
||||||
Main.layoutManager.connect('monitors-changed',
|
Main.layoutManager.connect('monitors-changed',
|
||||||
Lang.bind(this, this._monitorsChanged));
|
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();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -732,6 +795,30 @@ const ZoomRegion = new Lang.Class({
|
|||||||
return this._mouseTrackingMode;
|
return this._mouseTrackingMode;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* setViewPort
|
||||||
* Sets the position and size of the ZoomRegion on screen.
|
* Sets the position and size of the ZoomRegion on screen.
|
||||||
@ -1243,19 +1330,47 @@ const ZoomRegion = new Lang.Class({
|
|||||||
let yMouse = this._magnifier.yMouse;
|
let yMouse = this._magnifier.yMouse;
|
||||||
|
|
||||||
if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PROPORTIONAL) {
|
if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PROPORTIONAL) {
|
||||||
return this._centerFromMouseProportional(xMouse, yMouse);
|
return this._centerFromPointProportional(xMouse, yMouse);
|
||||||
}
|
}
|
||||||
else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PUSH) {
|
else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PUSH) {
|
||||||
return this._centerFromMousePush(xMouse, yMouse);
|
return this._centerFromPointPush(xMouse, yMouse);
|
||||||
}
|
}
|
||||||
else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.CENTERED) {
|
else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.CENTERED) {
|
||||||
return this._centerFromMouseCentered(xMouse, yMouse);
|
return this._centerFromPointCentered(xMouse, yMouse);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null; // Should never be hit
|
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 [xRoi, yRoi, widthRoi, heightRoi] = this.getROI();
|
||||||
let [cursorWidth, cursorHeight] = this._mouseSourceActor.get_size();
|
let [cursorWidth, cursorHeight] = this._mouseSourceActor.get_size();
|
||||||
let xPos = xRoi + widthRoi / 2;
|
let xPos = xRoi + widthRoi / 2;
|
||||||
@ -1263,20 +1378,20 @@ const ZoomRegion = new Lang.Class({
|
|||||||
let xRoiRight = xRoi + widthRoi - cursorWidth;
|
let xRoiRight = xRoi + widthRoi - cursorWidth;
|
||||||
let yRoiBottom = yRoi + heightRoi - cursorHeight;
|
let yRoiBottom = yRoi + heightRoi - cursorHeight;
|
||||||
|
|
||||||
if (xMouse < xRoi)
|
if (xPoint < xRoi)
|
||||||
xPos -= (xRoi - xMouse);
|
xPos -= (xRoi - xPoint);
|
||||||
else if (xMouse > xRoiRight)
|
else if (xPoint > xRoiRight)
|
||||||
xPos += (xMouse - xRoiRight);
|
xPos += (xPoint - xRoiRight);
|
||||||
|
|
||||||
if (yMouse < yRoi)
|
if (yPoint < yRoi)
|
||||||
yPos -= (yRoi - yMouse);
|
yPos -= (yRoi - yPoint);
|
||||||
else if (yMouse > yRoiBottom)
|
else if (yPoint > yRoiBottom)
|
||||||
yPos += (yMouse - yRoiBottom);
|
yPos += (yPoint - yRoiBottom);
|
||||||
|
|
||||||
return [xPos, yPos];
|
return [xPos, yPos];
|
||||||
},
|
},
|
||||||
|
|
||||||
_centerFromMouseProportional: function(xMouse, yMouse) {
|
_centerFromPointProportional: function(xPoint, yPoint) {
|
||||||
let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI();
|
let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI();
|
||||||
let halfScreenWidth = global.screen_width / 2;
|
let halfScreenWidth = global.screen_width / 2;
|
||||||
let halfScreenHeight = global.screen_height / 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 unscaledPadding = Math.min(this._viewPortWidth, this._viewPortHeight) / 5;
|
||||||
let xPadding = unscaledPadding / this._xMagFactor;
|
let xPadding = unscaledPadding / this._xMagFactor;
|
||||||
let yPadding = unscaledPadding / this._yMagFactor;
|
let yPadding = unscaledPadding / this._yMagFactor;
|
||||||
let xProportion = (xMouse - halfScreenWidth) / halfScreenWidth; // -1 ... 1
|
let xProportion = (xPoint - halfScreenWidth) / halfScreenWidth; // -1 ... 1
|
||||||
let yProportion = (yMouse - halfScreenHeight) / halfScreenHeight; // -1 ... 1
|
let yProportion = (yPoint - halfScreenHeight) / halfScreenHeight; // -1 ... 1
|
||||||
let xPos = xMouse - xProportion * (widthRoi / 2 - xPadding);
|
let xPos = xPoint - xProportion * (widthRoi / 2 - xPadding);
|
||||||
let yPos = yMouse - yProportion * (heightRoi /2 - yPadding);
|
let yPos = yPoint - yProportion * (heightRoi /2 - yPadding);
|
||||||
|
|
||||||
return [xPos, yPos];
|
return [xPos, yPos];
|
||||||
},
|
},
|
||||||
|
|
||||||
_centerFromMouseCentered: function(xMouse, yMouse) {
|
_centerFromPointCentered: function(xPoint, yPoint) {
|
||||||
return [xMouse, yMouse];
|
return [xPoint, yPoint];
|
||||||
},
|
},
|
||||||
|
|
||||||
_screenToViewPort: function(screenX, screenY) {
|
_screenToViewPort: function(screenX, screenY) {
|
||||||
|
Loading…
Reference in New Issue
Block a user