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/extensionDownloader.js \
|
||||
ui/environment.js \
|
||||
ui/focusCaretTracker.js\
|
||||
ui/ibusCandidatePopup.js\
|
||||
ui/grabHelper.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 -*-
|
||||
|
||||
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();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -732,6 +795,30 @@ const ZoomRegion = new Lang.Class({
|
||||
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
|
||||
* Sets the position and size of the ZoomRegion on screen.
|
||||
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user