Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Incubates #6962 (issue #6945): Fix input gesture identifier normaliza…
…tion.
  • Loading branch information
jcsteh committed Mar 14, 2017
2 parents c79684a + d1dd24c commit 6da1646
Show file tree
Hide file tree
Showing 11 changed files with 69 additions and 73 deletions.
4 changes: 2 additions & 2 deletions source/baseObject.py
@@ -1,6 +1,6 @@
#baseObject.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2006-2007 NVDA Contributors <http://www.nvda-project.org/>
#Copyright (C) 2007-2017 NV Access Limited
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.

Expand Down Expand Up @@ -209,7 +209,7 @@ def getScript(self,gesture):
@return: The script function or C{None} if none was found.
@rtype: script function
"""
for identifier in gesture.identifiers:
for identifier in gesture.normalizedIdentifiers:
try:
# Convert to instance method.
return self._gestureMap[identifier].__get__(self, self.__class__)
Expand Down
4 changes: 2 additions & 2 deletions source/braille.py
Expand Up @@ -2,7 +2,7 @@
#A part of NonVisual Desktop Access (NVDA)
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
#Copyright (C) 2008-2016 NV Access Limited, Joseph Lee
#Copyright (C) 2008-2017 NV Access Limited, Joseph Lee

import sys
import itertools
Expand Down Expand Up @@ -1701,7 +1701,7 @@ def _get_id(self):
routingIndex = None

def _get_identifiers(self):
ids = [u"br({source}):{id}".format(source=self.source, id=self.id).lower()]
ids = [u"br({source}):{id}".format(source=self.source, id=self.id)]
import brailleInput
if isinstance(self, brailleInput.BrailleInputGesture):
ids.extend(brailleInput.BrailleInputGesture._get_identifiers(self))
Expand Down
8 changes: 4 additions & 4 deletions source/brailleDisplayDrivers/alvaBC6.py
Expand Up @@ -158,18 +158,18 @@ def __init__(self, keys):
super(InputGesture, self).__init__()
self.keyCodes = set(keys)

self.keyNames = names = set()
self.keyNames = names = []
for group, number in self.keyCodes:
if group == ALVA_CR_GROUP:
if number & ALVA_2ND_CR_MASK:
names.add("routing2")
names.append("routing2")
self.routingIndex = number & ~ALVA_2ND_CR_MASK
else:
names.add("routing")
names.append("routing")
self.routingIndex = number
else:
try:
names.add(ALVA_KEYS[group][number])
names.append(ALVA_KEYS[group][number])
except (KeyError, IndexError):
log.debugWarning("Unknown key with group %d and number %d" % (group, number))

Expand Down
10 changes: 5 additions & 5 deletions source/brailleDisplayDrivers/baum.py
Expand Up @@ -3,7 +3,7 @@
#A part of NonVisual Desktop Access (NVDA)
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
#Copyright (C) 2010-2016 NV Access Limited
#Copyright (C) 2010-2017 NV Access Limited

import time
from collections import OrderedDict
Expand Down Expand Up @@ -333,7 +333,7 @@ def __init__(self, keysDown):
super(InputGesture, self).__init__()
self.keysDown = dict(keysDown)

self.keyNames = names = set()
self.keyNames = names = []
for group, groupKeysDown in keysDown.iteritems():
if group == BAUM_BRAILLE_KEYS and len(keysDown) == 1 and not groupKeysDown & 0xfc:
# This is braille input.
Expand All @@ -344,14 +344,14 @@ def __init__(self, keysDown):
for index in xrange(braille.handler.display.numCells):
if groupKeysDown & (1 << index):
self.routingIndex = index
names.add("routing")
names.append("routing")
break
elif group == BAUM_ROUTING_KEY:
self.routingIndex = groupKeysDown - 1
names.add("routing")
names.append("routing")
else:
for index, name in enumerate(KEY_NAMES[group]):
if groupKeysDown & (1 << index):
names.add(name)
names.append(name)

self.id = "+".join(names)
8 changes: 4 additions & 4 deletions source/brailleDisplayDrivers/brailliantB.py
Expand Up @@ -2,7 +2,7 @@
#A part of NonVisual Desktop Access (NVDA)
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
#Copyright (C) 2012-2015 NV Access Limited
#Copyright (C) 2012-2017 NV Access Limited

import os
import _winreg
Expand Down Expand Up @@ -269,7 +269,7 @@ def __init__(self, keys):
super(InputGesture, self).__init__()
self.keyCodes = set(keys)

self.keyNames = names = set()
self.keyNames = names = []
isBrailleInput = True
for key in self.keyCodes:
if isBrailleInput:
Expand All @@ -283,11 +283,11 @@ def __init__(self, keys):
self.dots = 0
self.space = False
if key >= FIRST_ROUTING_KEY:
names.add("routing")
names.append("routing")
self.routingIndex = key - FIRST_ROUTING_KEY
else:
try:
names.add(KEY_NAMES[key])
names.append(KEY_NAMES[key])
except KeyError:
log.debugWarning("Unknown key with id %d" % key)

Expand Down
4 changes: 2 additions & 2 deletions source/brailleDisplayDrivers/freedomScientific.py
Expand Up @@ -2,7 +2,7 @@
#A part of NonVisual Desktop Access (NVDA)
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
#Copyright (C) 2008-2011 Michael Curran <mick@kulgan.net>, James Teh <jamie@jantrid.net>
#Copyright (C) 2008-2017 NV Access Limited

from ctypes import *
from ctypes.wintypes import *
Expand Down Expand Up @@ -284,7 +284,7 @@ def __init__(self,keyBits, extendedKeyBits):
super(KeyGesture,self).__init__()
keys=[self.keyLabels[num] for num in xrange(24) if (keyBits>>num)&1]
extendedKeys=[self.extendedKeyLabels[num] for num in xrange(4) if (extendedKeyBits>>num)&1]
self.id="+".join(set(keys+extendedKeys))
self.id="+".join(keys+extendedKeys)
# Don't say is this a dots gesture if some keys either from dots and space are pressed.
if not extendedKeyBits and not keyBits & ~(0xff | (1 << 0xf)):
self.dots = keyBits & 0xff
Expand Down
4 changes: 2 additions & 2 deletions source/gui/settingsDialogs.py
@@ -1,7 +1,7 @@
# -*- coding: UTF-8 -*-
#settingsDialogs.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2006-2016 NV Access Limited, Peter Vágner, Aleksey Sadovoy, Rui Batista, Joseph Lee, Heiko Folkerts, Zahari Yurukov, Leonard de Ruijter, Derek Riemer
#Copyright (C) 2006-2017 NV Access Limited, Peter Vágner, Aleksey Sadovoy, Rui Batista, Joseph Lee, Heiko Folkerts, Zahari Yurukov, Leonard de Ruijter, Derek Riemer
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.

Expand Down Expand Up @@ -2025,7 +2025,7 @@ def addGestureCaptor(gesture):
inputCore.manager._captureFunc = addGestureCaptor

def _addCaptured(self, treeGes, scriptInfo, gesture):
gids = gesture.identifiers
gids = gesture.normalizedIdentifiers
if len(gids) > 1:
# Multiple choices. Present them in a pop-up menu.
menu = wx.Menu()
Expand Down
60 changes: 37 additions & 23 deletions source/inputCore.py
Expand Up @@ -2,7 +2,7 @@
#A part of NonVisual Desktop Access (NVDA)
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
#Copyright (C) 2010-2016 NV Access Limited, Babbage B.V.
#Copyright (C) 2010-2017 NV Access Limited, Babbage B.V.

"""Core framework for handling input from the user.
Every piece of input from the user (e.g. a key press) is represented by an L{InputGesture}.
Expand Down Expand Up @@ -65,37 +65,49 @@ class InputGesture(baseObject.AutoPropertyObject):

def _get_identifiers(self):
"""The identifier(s) which will be used in input gesture maps to represent this gesture.
These identifiers will be looked up in order until a match is found.
These identifiers will be normalized and looked up in order until a match is found.
A single identifier should take the form: C{source:id}
where C{source} is a few characters representing the source of this gesture
and C{id} is the specific gesture.
If C{id} consists of multiple items with indeterminate order,
they should be separated by a + sign and they should be in Python set order.
Also, the entire identifier should be in lower case.
An example identifier is: C{kb(desktop):nvda+1}
An example identifier is: C{kb(desktop):NVDA+1}
This property should not perform normalization itself.
However, please note the following regarding normalization.
If C{id} contains multiple chunks separated by a + sign, they are considered to be ordered arbitrarily
and may be reordered when normalized.
Normalization also ensures that the entire identifier is lower case.
For example, NVDA+control+f1 and control+nvda+f1 will match when normalized.
See L{normalizeGestureIdentifier} for more details.
Subclasses must implement this method.
@return: One or more identifiers which uniquely identify this gesture.
@rtype: list or tuple of str
@rtype: list or tuple of basestring
"""
raise NotImplementedError

def _get_normalizedIdentifiers(self):
"""The normalized identifier(s) for this gesture.
This just normalizes the identifiers returned in L{identifiers}.
These normalized identifiers can be directly looked up in input gesture maps.
Subclasses should not override this method.
@return: One or more normalized identifiers which uniquely identify this gesture.
@rtype: list of basestring
"""
return [normalizeGestureIdentifier(identifier) for identifier in self.identifiers]

def _get_logIdentifier(self):
"""A single identifier which will be logged for this gesture.
This identifier should be usable in input gesture maps, but should be as readable as possible to the user.
For example, it might sort key names in a particular order
and it might contain mixed case.
This is in contrast to L{identifiers}, which must be normalized.
The base implementation returns the first identifier from L{identifiers}.
"""@deprecated: Use L{InputGesture.identifiers}[0] instead.
"""
return self.identifiers[0]

def _get_displayName(self):
"""The name of this gesture as presented to the user.
Subclasses must implement this method.
The base implementation calls L{getDisplayTextForIdentifier} for the first identifier.
Subclasses need not override this unless they wish to provide a more optimal implementation.
@return: The display name.
@rtype: str
"""
return self.getDisplayTextForIdentifier(self.identifiers[0])[1]
return self.getDisplayTextForIdentifier(self.normalizedIdentifiers[0])[1]

#: Whether this gesture should be reported when reporting of command gestures is enabled.
#: @type: bool
Expand Down Expand Up @@ -421,7 +433,7 @@ def executeGesture(self, gesture):
queueHandler.queueFunction(queueHandler.eventQueue, speech.pauseSpeech, speechEffect == gesture.SPEECHEFFECT_PAUSE)

if log.isEnabledFor(log.IO) and not gesture.isModifier:
log.io("Input: %s" % gesture.logIdentifier)
log.io("Input: %s" % gesture.identifiers[0])

if self._captureFunc:
try:
Expand Down Expand Up @@ -472,7 +484,7 @@ def _handleInputHelp(self, gesture, onlyLog=False):
textList = [gesture.displayName]
script = gesture.script
runScript = False
logMsg = "Input help: gesture %s"%gesture.logIdentifier
logMsg = "Input help: gesture %s"%gesture.identifiers[0]
if script:
scriptName = scriptHandler.getScriptName(script)
logMsg+=", bound to script %s" % scriptName
Expand Down Expand Up @@ -684,17 +696,19 @@ def className(self):

def normalizeGestureIdentifier(identifier):
"""Normalize a gesture identifier so that it matches other identifiers for the same gesture.
Any items separated by a + sign after the source are considered to be of indeterminate order
and are reordered into Python set ordering.
Then the entire identifier is converted to lower case.
First, the entire identifier is converted to lower case.
Then, any items separated by a + sign after the source prefix are considered to be of indeterminate order
and are sorted by character.
"""
identifier = identifier.lower()
prefix, main = identifier.split(":", 1)
main = main.split("+")
# The order of the parts doesn't matter as far as the user is concerned,
# but we need them to be in a determinate order so they will match other gesture identifiers.
# We use Python's set ordering.
main = "+".join(frozenset(main))
return u"{0}:{1}".format(prefix, main).lower()
# We sort them by character.
main.sort()
main = "+".join(main)
return u"{0}:{1}".format(prefix, main)

#: Maps registered source prefix strings to L{InputGesture} classes.
gestureSources = weakref.WeakValueDictionary()
Expand Down
17 changes: 5 additions & 12 deletions source/keyboardHandler.py
Expand Up @@ -3,7 +3,7 @@
#A part of NonVisual Desktop Access (NVDA)
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
#Copyright (C) 2006-2015 NV Access Limited, Peter Vágner, Aleksey Sadovoy
#Copyright (C) 2006-2017 NV Access Limited, Peter Vágner, Aleksey Sadovoy

"""Keyboard support"""

Expand Down Expand Up @@ -369,22 +369,17 @@ def _get_mainKeyName(self):
return winUser.getKeyNameText(self.scanCode, self.isExtended)

def _get_modifierNames(self):
modTexts = set()
modTexts = []
for modVk, modExt in self.generalizedModifiers:
if isNVDAModifierKey(modVk, modExt):
modTexts.add("NVDA")
modTexts.append("NVDA")
else:
modTexts.add(self.getVkName(modVk, None))

modTexts.append(self.getVkName(modVk, None))
return modTexts

def _get__keyNamesInDisplayOrder(self):
return tuple(self.modifierNames) + (self.mainKeyName,)

def _get_logIdentifier(self):
return u"kb({layout}):{key}".format(layout=self.layout,
key="+".join(self._keyNamesInDisplayOrder))

def _get_displayName(self):
return "+".join(
# Translators: Reported for an unknown key press.
Expand All @@ -393,9 +388,7 @@ def _get_displayName(self):
else localizedKeyLabels.get(key.lower(), key) for key in self._keyNamesInDisplayOrder)

def _get_identifiers(self):
keyNames = set(self.modifierNames)
keyNames.add(self.mainKeyName)
keyName = "+".join(keyNames).lower()
keyName = "+".join(self._keyNamesInDisplayOrder)
return (
u"kb({layout}):{key}".format(layout=self.layout, key=keyName),
u"kb:{key}".format(key=keyName)
Expand Down
4 changes: 2 additions & 2 deletions source/scriptHandler.py
@@ -1,6 +1,6 @@
#scriptHandler.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2006-2016 NVDA Contributors <http://www.nvda-project.org/>
#Copyright (C) 2007-2017 NV Access Limited, Babbage B.V.
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.

Expand Down Expand Up @@ -73,7 +73,7 @@ def findScript(gesture):
if globalMap:
globalMaps.append(globalMap)
for globalMap in globalMaps:
for identifier in gesture.identifiers:
for identifier in gesture.normalizedIdentifiers:
globalMapScripts.extend(globalMap.getScriptsForGesture(identifier))

# Gesture specific scriptable object.
Expand Down
19 changes: 4 additions & 15 deletions source/touchHandler.py
Expand Up @@ -2,7 +2,7 @@
#A part of NonVisual Desktop Access (NVDA)
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
#Copyright (C) 2012 NV Access Limited
#Copyright (C) 2012-2017 NV Access Limited

import wx
import threading
Expand Down Expand Up @@ -149,7 +149,7 @@ def __init__(self,preheldTracker,tracker,mode):
self.x=tracker.x
self.y=tracker.y

def _get__rawIdentifiers(self):
def _get_identifiers(self):
IDs=[]
for includeHeldFingers in ([True,False] if self.preheldTracker else [False]):
ID=""
Expand All @@ -160,22 +160,11 @@ def _get__rawIdentifiers(self):
if self.tracker.actionCount>1:
ID+="%s_"%self.counterNames[min(self.tracker.actionCount,4)-1]
ID+=self.tracker.action
IDs.append("TS(%s):%s"%(self.mode,ID))
# "ts" is the gesture identifier source prefix for "touch screen".
IDs.append("ts(%s):%s"%(self.mode,ID))
IDs.append("ts:%s"%ID)
return IDs

def _get_logIdentifier(self):
return self._rawIdentifiers[0]

def _get_identifiers(self):
identifiers=[]
for identifier in self._rawIdentifiers:
t,i=identifier.split(':')
# Force the ID in to Python set order so they are always comparable
i="+".join(set(i.split("+")))
identifiers.append("%s:%s"%(t.lower(),i.lower()))
return identifiers

RE_IDENTIFIER = re.compile(r"^ts(?:\((.+?)\))?:(.*)$")

@classmethod
Expand Down

0 comments on commit 6da1646

Please sign in to comment.