From 3ad47de76cd051ac1dd75e82a5346bb10d77fe85 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 24 Aug 2017 14:58:04 +0200 Subject: [PATCH 01/27] * Created a new locationHelper module * Added the ClientToScreen function from user32.dll to the winUser module * Use locationHelper for object locations --- source/NVDAObjects/IAccessible/MSHTML.py | 7 +- source/NVDAObjects/IAccessible/__init__.py | 3 +- .../NVDAObjects/IAccessible/sysListView32.py | 3 +- source/NVDAObjects/JAB/__init__.py | 3 +- source/NVDAObjects/UIA/__init__.py | 3 +- source/NVDAObjects/window/__init__.py | 3 +- source/appModules/powerpnt.py | 5 +- source/displayModel.py | 3 +- source/locationHelper.py | 402 ++++++++++++++++++ source/textInfos/__init__.py | 4 +- source/winUser.py | 6 + tests/unit/test_locationHelper.py | 85 ++++ 12 files changed, 512 insertions(+), 15 deletions(-) create mode 100644 source/locationHelper.py create mode 100644 tests/unit/test_locationHelper.py diff --git a/source/NVDAObjects/IAccessible/MSHTML.py b/source/NVDAObjects/IAccessible/MSHTML.py index 62e7355fe8a..b520e3c8fbc 100644 --- a/source/NVDAObjects/IAccessible/MSHTML.py +++ b/source/NVDAObjects/IAccessible/MSHTML.py @@ -27,6 +27,7 @@ from .. import InvalidNVDAObject from ..window import Window from NVDAObjects.UIA import UIA, UIATextInfo +from locationHelper import Rect IID_IHTMLElement=comtypes.GUID('{3050F1FF-98B5-11CF-BB82-00AA00BDCE0B}') @@ -571,11 +572,7 @@ def _get_location(self): top=int(r.top*yFactor) right=int(r.right*xFactor) bottom=int(r.bottom*yFactor) - width=right-left - height=bottom-top - p=ctypes.wintypes.POINT(x=left,y=top) - ctypes.windll.user32.ClientToScreen(self.windowHandle,ctypes.byref(p)) - return (p.x,p.y,width,height) + return Rect(left,top,right,bottom).toScreen(self.windowHandle).toLocation() return None def _get_TextInfo(self): diff --git a/source/NVDAObjects/IAccessible/__init__.py b/source/NVDAObjects/IAccessible/__init__.py index 8dafaebd122..78b9dbacf02 100644 --- a/source/NVDAObjects/IAccessible/__init__.py +++ b/source/NVDAObjects/IAccessible/__init__.py @@ -31,6 +31,7 @@ import NVDAObjects.JAB import eventHandler from NVDAObjects.behaviors import ProgressBar, Dialog, EditableTextWithAutoSelectDetection, FocusableUnfocusableContainer, ToolTip, Notification +from locationHelper import Location def getNVDAObjectFromEvent(hwnd,objectID,childID): try: @@ -890,7 +891,7 @@ def _get_childCount(self): def _get_location(self): try: - return self.IAccessibleObject.accLocation(self.IAccessibleChildID) + return Location(*self.IAccessibleObject.accLocation(self.IAccessibleChildID)) except COMError: return None diff --git a/source/NVDAObjects/IAccessible/sysListView32.py b/source/NVDAObjects/IAccessible/sysListView32.py index 1faa05c6de3..7c27aae6157 100644 --- a/source/NVDAObjects/IAccessible/sysListView32.py +++ b/source/NVDAObjects/IAccessible/sysListView32.py @@ -23,6 +23,7 @@ import watchdog from NVDAObjects.behaviors import RowWithoutCellObjects, RowWithFakeNavigation import config +from locationHelper import toLocation #Window messages LVM_FIRST=0x1000 @@ -304,7 +305,7 @@ def _getColumnLocationRaw(self,index): winKernel.virtualFreeEx(processHandle,internalRect,0,winKernel.MEM_RELEASE) windll.user32.ClientToScreen(self.windowHandle,byref(localRect)) windll.user32.ClientToScreen(self.windowHandle,byref(localRect,8)) - return (localRect.left,localRect.top,localRect.right-localRect.left,localRect.bottom-localRect.top) + return toLocation(localRect) def _getColumnLocation(self,column): return self._getColumnLocationRaw(self.parent._columnOrderArray[column - 1]) diff --git a/source/NVDAObjects/JAB/__init__.py b/source/NVDAObjects/JAB/__init__.py index 23bc1e641d1..78ce1874b5b 100644 --- a/source/NVDAObjects/JAB/__init__.py +++ b/source/NVDAObjects/JAB/__init__.py @@ -9,6 +9,7 @@ import textInfos.offsets from logHandler import log from .. import InvalidNVDAObject +from locationHelper import Location JABRolesToNVDARoles={ "alert":controlTypes.ROLE_DIALOG, @@ -301,7 +302,7 @@ def _get_description(self): return re_simpleXmlTag.sub(" ", self._JABAccContextInfo.description) def _get_location(self): - return (self._JABAccContextInfo.x,self._JABAccContextInfo.y,self._JABAccContextInfo.width,self._JABAccContextInfo.height) + return Location(self._JABAccContextInfo.x,self._JABAccContextInfo.y,self._JABAccContextInfo.width,self._JABAccContextInfo.height) def _get_hasFocus(self): if controlTypes.STATE_FOCUSED in self.states: diff --git a/source/NVDAObjects/UIA/__init__.py b/source/NVDAObjects/UIA/__init__.py index f4695571e68..e5757dd4104 100644 --- a/source/NVDAObjects/UIA/__init__.py +++ b/source/NVDAObjects/UIA/__init__.py @@ -31,6 +31,7 @@ from NVDAObjects.behaviors import ProgressBar, EditableTextWithoutAutoSelectDetection, Dialog, Notification, EditableTextWithSuggestions import braille import time +from locationHelper import Location class UIATextInfo(textInfos.TextInfo): @@ -1227,7 +1228,7 @@ def _get_location(self): top=int(r[1]) width=int(r[2]) height=int(r[3]) - return left,top,width,height + return Location(left,top,width,height) def _get_value(self): val=self._getUIACacheablePropertyValue(UIAHandler.UIA_RangeValueValuePropertyId,True) diff --git a/source/NVDAObjects/window/__init__.py b/source/NVDAObjects/window/__init__.py index b1e0caa369b..12b38903d0c 100644 --- a/source/NVDAObjects/window/__init__.py +++ b/source/NVDAObjects/window/__init__.py @@ -17,6 +17,7 @@ from NVDAObjects import NVDAObject from NVDAObjects.behaviors import EditableText, LiveText import watchdog +from locationHelper import toLocation re_WindowsForms=re.compile(r'^WindowsForms[0-9]*\.(.*)\.app\..*$') re_ATL=re.compile(r'^ATL:(.*)$') @@ -189,7 +190,7 @@ def _get_windowControlID(self): def _get_location(self): r=ctypes.wintypes.RECT() ctypes.windll.user32.GetWindowRect(self.windowHandle,ctypes.byref(r)) - return (r.left,r.top,r.right-r.left,r.bottom-r.top) + return toLocation(r) def _get_displayText(self): """The text at this object's location according to the display model for this object's window.""" diff --git a/source/appModules/powerpnt.py b/source/appModules/powerpnt.py index 04653de40dd..7844f2aad2c 100644 --- a/source/appModules/powerpnt.py +++ b/source/appModules/powerpnt.py @@ -33,6 +33,7 @@ import controlTypes from logHandler import log import scriptHandler +from locationHelper import Rect # Window classes where PowerPoint's object model should be used # These also all request to have their (incomplete) UI Automation implementations disabled. [MS Office 2013] @@ -735,9 +736,7 @@ def _get_location(self): top=self.documentWindow.ppObjectModel.pointsToScreenPixelsY(pointTop) right=self.documentWindow.ppObjectModel.pointsToScreenPixelsX(pointLeft+pointWidth) bottom=self.documentWindow.ppObjectModel.pointsToScreenPixelsY(pointTop+pointHeight) - width=right-left - height=bottom-top - return (left,top,width,height) + return Rect(left,top,right,bottom).toLocation() def _get_ppShapeType(self): """Fetches and caches the type of this shape.""" diff --git a/source/displayModel.py b/source/displayModel.py index f398fcd3206..2d346a94b86 100644 --- a/source/displayModel.py +++ b/source/displayModel.py @@ -19,6 +19,7 @@ import watchdog from logHandler import log import windowUtils +from locationHelper import Rect def wcharToInt(c): i=ord(c) @@ -178,7 +179,7 @@ def getWindowTextInRect(bindingHandle, windowHandle, left, top, right, bottom,mi characterLocations = [] cpBufIt = iter(cpBuf) for cp in cpBufIt: - characterLocations.append((wcharToInt(cp), wcharToInt(next(cpBufIt)), wcharToInt(next(cpBufIt)), wcharToInt(next(cpBufIt)))) + characterLocations.append(Rect(wcharToInt(cp), wcharToInt(next(cpBufIt)), wcharToInt(next(cpBufIt)), wcharToInt(next(cpBufIt)))) return text, characterLocations def getFocusRect(obj): diff --git a/source/locationHelper.py b/source/locationHelper.py new file mode 100644 index 00000000000..f8e6a45c5d0 --- /dev/null +++ b/source/locationHelper.py @@ -0,0 +1,402 @@ +#locationHelper.py +#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) 2017 NV Access Limited, Babbage B.V. + +"""Classes and helper functions for working with rectangles and coordinates.""" + +from collections import namedtuple +import windowUtils +import winUser +from ctypes.wintypes import RECT, SMALL_RECT, POINT +import textInfos +import wx + +class Point(namedtuple("Point",("x","y"))): + """Represents a point on the screen.""" + + def __add__(self,other): + if not isinstance(other,_POINT_CLASSES): + return NotImplemented + return Point((self.x+other.x),(self.y+other.y)) + + def __radd__(self,other): + return self.__add__(other) + + def __lt__(self, other): + """ + Returns whether self is less than other. + This first compares y, than x coordinates. + For example: (x=4,y=3) < (x=3,y=4) because self.y is less than other.y. + To compare in opposite order (i.e. compare x, than y), use tuple(self) < tuple(other) + """ + if not isinstance(other,_POINT_CLASSES): + try: + other=toPoint(other) + except ValueError: + return False + return (self.y, self.x) < (other.y, other.x) + + def __le__(self, other): + """ + Returns whether self is less than or equal to other. + This first compares y, than x coordinates. + For example: (x=4,y=3) < (x=3,y=4) because self.y is less than other.y. + To compare in opposite order (i.e. compare x, than y), use tuple(self) <= tuple(other) + """ + if not isinstance(other,_POINT_CLASSES): + try: + other=toPoint(other) + except ValueError: + return False + return (self.y, self.x) <= (other.y, other.x) + + def __gt__(self, other): + """ + Returns whether self is greater than other. + This first compares y, than x coordinates. + For example: (x=3,y=4) > (x=4,y=3) because self.y is greater than other.y. + To compare in opposite order (i.e. compare x, than y), use tuple(self) > tuple(other) + """ + if not isinstance(other,_POINT_CLASSES): + try: + other=toPoint(other) + except ValueError: + return False + return (self.y, self.x) > (other.y, other.x) + + def __ge__(self, other): + """ + Returns whether self is greater than or equal to other. + This first compares y, than x coordinates. + For example: (x=3,y=4) > (x=4,y=3) because self.y is greater than other.y. + To compare in opposite order (i.e. compare x, than y), use tuple(self) >= tuple(other) + """ + if not isinstance(other,_POINT_CLASSES): + try: + other=toPoint(other) + except ValueError: + return False + return (self.y, self.x) >= (other.y, other.x) + + def __eq__(self, other): + if not isinstance(other,_POINT_CLASSES): + try: + other=toPoint(other) + except ValueError: + return False + return self.x==other.x and self.y==other.y + + def __neq__(self, other): + if not isinstance(other,_POINT_CLASSES): + try: + other=toPoint(other) + except ValueError: + return False + return self.x!=self.x or self.y!=other.y + + def __sub__(self,other): + if not isinstance(other,_POINT_CLASSES): + return NotImplemented + return Point((self.x-other.x),(self.y-other.y)) + + def __rsub__(self,other): + return self.__sub__(other) + + def toPOINT(self): + """Converts self to a L{ctypes.wintypes.POINT}""" + return POINT(self.x,self.y) + + def toLogical(self, hwnd): + """Converts self from physical to logical coordinates and returns a new L{Point}.""" + return Point(*windowUtils.physicalToLogicalPoint(hwnd, *self)) + + def toPhysical(self, hwnd): + """Converts self from logical to physical coordinates and returns a new L{Point}""" + return Point(*windowUtils.logicalToPhysicalPoint(hwnd, *self)) + + def toClient(self, hwnd): + """Converts self from screen to client coordinates and returns a new L{Point}""" + return Point(*winUser.ScreenToClient(hwnd, *self)) + + def toScreen(self, hwnd): + """Converts self from client to screen coordinates and returns a new L{Point}""" + return Point(*winUser.ClientToScreen(hwnd, *self)) + +class _RectMixin: + """Mix-in class for properties shared between Location and Rect classes""" + + def toRECT(self): + """Converts self to a L{ctypes.wintypes.RECT}""" + return RECT(self.left,self.top,self.right,self.bottom) + + def toLogical(self, hwnd): + left,top=self.topLeft.toLogical(hwnd) + right,bottom=self.bottomRight.toLogical(hwnd) + if type(self) is Location: + return Location(left,top,right-left,bottom-top) + return Rect(left,top,right,bottom) + + def toPhysical(self, hwnd): + left,top=self.topLeft.toPhysical(hwnd) + right,bottom=self.bottomRight.toPhysical(hwnd) + if type(self) is Location: + return Location(left,top,right-left,bottom-top) + return Rect(left,top,right,bottom) + + def toClient(self, hwnd): + left, top =self.topLeft.toClient(hwnd) + if type(self) is Location: + return Location(left, top, self.width, self.height) + return Rect(left, top, left+self.width, top+self.height) + + def toScreen(self, hwnd): + left,top=self.topLeft.toScreen(hwnd) + if type(self) is Location: + return Location(left, top, self.width, self.height) + return Rect(left, top, left+self.width, top+self.height) + + @property + def topLeft(self): + return Point(self.left,self.top) + + @property + def bottomRight(self): + return Point(self.right,self.bottom) + + @property + def center(self): + return Point((self.left+self.right)/2, (self.top+self.bottom)/2) + + def __contains__(self,other): + """Returns whether other is a part of self.""" + if isinstance(other,_POINT_CLASSES): + return other.x >= self.left < self.right and other.y >= self.top < self.bottom + if isinstance(other,_RECT_CLASSES): + return self>other + try: + other=toRect(other) + except: + return False + return self>other + + def __lt__(self, other): + """Returns whether self is a subset of other (i.e. whether all points in self are contained by other).""" + if not isinstance(other,_RECT_CLASSES): + try: + other=toRect(other) + except ValueError: + return False + return self<=other and self!=other + + def __le__(self, other): + """Returns whether self is a subset of or equal to other.""" + if not isinstance(other,_RECT_CLASSES): + try: + other=toRect(other) + except ValueError: + return False + return self.left >= other.left and self.top >= other.top and self.right <= other.right and self.bottom <= other.bottom + + def __gt__(self, other): + """Returns whether self is a superset of other (i.e. whether all points of other are contained by self).""" + if not isinstance(other,_RECT_CLASSES): + try: + other=toRect(other) + except ValueError: + return False + return self>=other and self!=other + + def __ge__(self, other): + """Returns whether self is a superset of or equal to other.""" + if not isinstance(other,_RECT_CLASSES): + try: + other=toRect(other) + except ValueError: + return False + return other.left >= self.left and other.top >= self.top and other.right <= self.right and other.bottom <= self.bottom + + def __eq__(self, other): + if not isinstance(other,_RECT_CLASSES): + try: + other=toRect(other) + except ValueError: + return False + return other.left == self.left and other.top == self.top and other.right == self.right and other.bottom == self.bottom + + def __neq__(self, other): + if not isinstance(other,_RECT_CLASSES): + try: + other=toRect(other) + except ValueError: + return False + return not (other.left == self.left and other.top == self.top and other.right == self.right and other.bottom == self.bottom) + + def __sub__(self,other): + if not isinstance(other,_RECT_CLASSES): + return NotImplemented + left,top,right,bottom=self.left-other.left,self.top-other.top,self.right-other.right,self.bottom-other.bottom + if type(self) is Location: + return Location(left,top,right-left,bottom-top) + return Rect(left,top,right,bottom) + + def __rsub__(self,other): + return self.__sub__(other) + + def __and__(self,other): + """Returns the intersection of self and other. + For example, if self = Rect(left=10,top=10,right=25,bottom=25) and other = Rect(left=20,top=20,right=35,bottom=35), + this results in Rect(left=20,top=20,right=25,bottom=25). + No intersect results in a rectangle with zeroed coordinates. + """ + if not isinstance(other,_RECT_CLASSES): + try: + other=toRect(other) + except ValueError: + return NotImplemented + left=max(self.left,other.left) + top=max(self.top,other.top) + right=min(self.right,other.right) + bottom=min(self.bottom,other.bottom) + if left>right or top>bottom: + left=top=right=bottom=0 + if type(self) is Location: + return Location(left,top,right-left,bottom-top) + return Rect(left,top,right,bottom) + + def __rand__(self,other): + return self.__and__(other) + +class Location(_RectMixin, namedtuple("Location",("left","top","width","height"))): + """Represents a rectangle on the screen, based on left and top coordinates, width and height.""" + + @property + def right(self): + return self.left+self.width + + @property + def bottom(self): + return self.top+self.height + + def toRect(self): + return Rect(self.left,self.top,self.right,self.bottom) + +class Rect(_RectMixin, namedtuple("Rect",("left","top","right","bottom"))): + """Represents a rectangle on the screen. + By convention, the right and bottom edges of the rectangle are normally considered exclusive. + """ + + @property + def width(self): + return self.right-self.left + + @property + def height(self): + return self.bottom-self.top + + def toLocation(self): + return Location(self.left,self.top,self.width,self.height) + +def toRect(*params): + """ + Converts the given input to L{Rect}. + Input should be one of the following types: + * One of l{_RECT_CLASSES}. + * One of L{_POINT_CLASSES}: converted to L{Rect} square of one pixel. + * List or tuple of integers: 4 treated as L{Rect}, 2 treated as L{Point}. + * List or tuple of mixed types from L{_RECT_CLASSES} or L{_POINT_CLASSES}: converted to L{Rect} containing all input. + """ + if len(params)==0: + raise TypeError("This function takes at least 1 argument (0 given)") + if len(params)==1: + param=params[0] + if isinstance(param,Rect): + return param + if isinstance(param,_RECT_CLASSES): + return Rect(param.left,param.top,param.right,param.bottom) + if isinstance(param,_POINT_CLASSES): + # Right and bottom edges of the resulting rectangle are considered exclusive + x,y=point.x,point.y + return Rect(x,y,x+1,y+1) + if isinstance(param,(tuple,list)): + # One indexable in another indexable doesn't make sence, so treat the inner indexable as outer indexable + params=param + if all(isinstance(param,(int,long)) for param in params): + if len(params)==4: + # Assume that we are converting from a tuple rectangle. + # To convert from a tuple location, use L{toLocation} instead. + # To convert from a tuple rectangle to L{Location}, use this function and execute L{toLocation} on the resulting object. + return Rect(*params) + elif len(params)==2: + x,y=params + return Rect(x,y,x+1,y+1) + elif len(params) in (1,3) or len(params)>4: + raise ValueError("When providing integers, this function requires either 2 or 4 arguments (%d given)"%len(params)) + xs=[] + ys=[] + for param in params: + if isinstance(param,_RECT_CLASSES): + xs.extend((param.left,param.right)) + ys.extend((param.top,param.bottom)) + elif isinstance(param,_POINT_CLASSES): + xs.append(param.x) + ys.append(param.y) + else: + raise ValueError("Unexpected parameter %s"%str(param)) + left=min(xs) + top=min(ys) + right=max(xs) + bottom=max(ys) + return Rect(left,top,right,bottom) + +def toLocation(*params): + """ + Converts the given input to L{Location}. + Input should be one of the following types: + * One of l{_RECT_CLASSES}. + * One of L{_POINT_CLASSES}: converted to L{Location} square of one pixel. + * List or tuple of integers: 4 treated as L{Rect}, 2 treated as L{Point}. + * List or tuple of mixed types from L{_RECT_CLASSES} or L{_POINT_CLASSES}: converted to L{Location} containing all input. + """ + if len(params)==0: + raise TypeError("This function takes at least 1 argument (0 given)") + if len(params)==1: + param=params[0] + if isinstance(param,Location): + return param + if not isinstance(param,_RECT_CLASSES+_POINT_CLASSES) and isinstance(param,(tuple,list)): + # One indexable in another indexable doesn't make sence, so treat the inner indexable as outer indexable + params=param + if len(params)==4 and all(isinstance(param,(int,long)) for param in params): + # Assume that we are converting from a tuple location. + # To convert from a tuple rectangle, use L{toRectangle} instead. + # To convert from a tuple location to L{Rect}, use this function and execute L{toRect} on the resulting location. + return Location(*params) + return toRect(*params).toLocation() + +def toPoint(*params): + """ + Converts the given input to L{Point}. + Input should either be one of L{_POINT_CLASSES}, 2 integers or one double word. + """ + if not len(params) in (1,2): + raise TypeError("This function takes 1 or 2 arguments (%d given)"%len(params)) + if len(params)==1: + param=params[0] + if isinstance(param,Point): + return param + if isinstance(param,_POINT_CLASSES): + return Point(param.x,param.y) + if isinstance(param,(int,long)): + return Point(winUser.GET_X_LPARAM(param),winUser.GET_Y_LPARAM(param)) + if all(isinstance(param,(int,long)) for param in params) and len(params)==2: + return Point(*params) + raise ValueError("Unexpected parameter(s) %s"%params) + +#: Classes which support conversion to locationHelper Points using their x and y properties. +#: type: tuple +_POINT_CLASSES=(Point, POINT, textInfos.Point, wx.Point) +#: Classes which support conversion to locationHelper Rects and Locations using their left, top, right and bottom properties. +#: type: tuple +_RECT_CLASSES=(Rect, Location, RECT, SMALL_RECT, textInfos.Rect, wx.Rect) diff --git a/source/textInfos/__init__.py b/source/textInfos/__init__.py index 188e5e21f4a..9c0f295f3ac 100755 --- a/source/textInfos/__init__.py +++ b/source/textInfos/__init__.py @@ -132,6 +132,7 @@ def __repr__(self): class Point(object): """Represents a point on the screen. This is used when associating a point on the screen with a piece of text. + @Deprecated: use L{locationHelper.Point} instead. """ def __init__(self,x,y): @@ -145,7 +146,8 @@ def __init__(self,x,y): self.y=y class Rect(object): - """Represents a rectangle on the screen.""" + """Represents a rectangle on the screen. + @Deprecated: use L{locationHelper.Rect} instead.""" def __init__(self, left, top, right, bottom): """ diff --git a/source/winUser.py b/source/winUser.py index 985538aeefa..deab9f8b2f7 100644 --- a/source/winUser.py +++ b/source/winUser.py @@ -521,6 +521,12 @@ def ScreenToClient(hwnd, x, y): user32.ScreenToClient(hwnd, byref(point)) return point.x, point.y +def ClientToScreen(hwnd, x, y): + point = POINT(x, y) + user32.ClientToScreen(hwnd, byref(point)) + return point.x, point.y + + class STICKYKEYS(Structure): _fields_ = ( ("cbSize", DWORD), diff --git a/tests/unit/test_locationHelper.py b/tests/unit/test_locationHelper.py new file mode 100644 index 00000000000..60eb3273e59 --- /dev/null +++ b/tests/unit/test_locationHelper.py @@ -0,0 +1,85 @@ +#tests/unit/test_locationHelper.py +#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) 2017 NV Access Limited, Babbage B.V. + +"""Unit tests for the locationHelper module. +""" + +import unittest +from locationHelper import * + +class TestRectOperators(unittest.TestCase): + + def test_and(self): + self.assertEqual(Rect(left=2,top=2,right=4,bottom=4) & Rect(left=3,top=3,right=5,bottom=5),Rect(left=3,top=3,right=4,bottom=4)) + self.assertEqual(Rect(left=2,top=2,right=4,bottom=4) & Rect(left=5,top=5,right=7,bottom=7),Rect(left=0,top=0,right=0,bottom=0)) + + def test_gt(self): + self.assertGreater(Rect(left=2,top=2,right=6,bottom=6),Rect(left=2,top=2,right=4,bottom=4)) + self.assertGreaterEqual(Rect(left=2,top=2,right=6,bottom=6),Rect(left=2,top=2,right=4,bottom=4)) + + def test_lt(self): + self.assertLess(Rect(left=2,top=2,right=4,bottom=4),Rect(left=2,top=2,right=6,bottom=6)) + self.assertLessEqual(Rect(left=2,top=2,right=4,bottom=4),Rect(left=2,top=2,right=6,bottom=6)) + + def test_in(self): + self.assertIn(Rect(left=2,top=2,right=4,bottom=4),Rect(left=2,top=2,right=6,bottom=6)) + self.assertIn(Point(x=2,y=6),Rect(left=2,top=2,right=6,bottom=6)) + + def test_sub(self): + self.assertEqual(Rect(left=2,top=2,right=4,bottom=4) - Rect(left=3,top=3,right=5,bottom=5),Rect(left=-1,top=-1,right=-1,bottom=-1)) + self.assertEqual(Rect(left=2,top=2,right=4,bottom=4) - Rect(left=5,top=5,right=8,bottom=8),Rect(left=-3,top=-3,right=-4,bottom=0-4)) + +class TestToRect(unittest.TestCase): + + def test_collection(self): + rect=Rect(left=10,top=15,right=500,bottom=1000) + self.assertEqual(toRect( + rect.topLeft, + rect.bottomRight, + rect.center, + Point(15,15), + Point(20,20), + Point(50,50), + Point(400,400), + Rect(left=450,top=450,right=490,bottom=990) + ), rect) + + def test_integers(self): + self.assertEqual(Rect(left=10,top=10,right=20,bottom=20),toRect(10,10,20,20)) + +class TestToLocation(unittest.TestCase): + + def test_collection(self): + location=Location(left=10,top=15,width=500,height=1000) + self.assertEqual(toLocation( + location.topLeft, + location.bottomRight, + location.center, + Point(15,15), + Point(20,20), + Point(50,50), + Point(400,400), + Rect(left=450,top=450,right=505,bottom=1010) + ), location) + + def test_integers(self): + self.assertEqual(Location(left=10,top=10,width=20,height=20),toLocation(10,10,20,20)) + +class TestPointOperators(unittest.TestCase): + + def test_add(self): + self.assertEqual(Point(x=2,y=4)+Point(x=2,y=4),Point(x=4,y=8)) + + def test_sub(self): + self.assertEqual(Point(x=2,y=4)-Point(x=4,y=8),Point(x=-2,y=-4)) + + def test_gt(self): + self.assertGreater(Point(x=3,y=4), Point(x=4,y=3)) + + def test_lt(self): + self.assertLess(Point(x=4,y=3), Point(x=3,y=4)) + + From fb646bd311aab5efff05789336a23ed219637cfb Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Sat, 23 Sep 2017 13:54:02 +0200 Subject: [PATCH 02/27] Use isinstance instead of type(self) is cls --- source/locationHelper.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/locationHelper.py b/source/locationHelper.py index f8e6a45c5d0..e6fafb9afdf 100644 --- a/source/locationHelper.py +++ b/source/locationHelper.py @@ -134,26 +134,26 @@ def toRECT(self): def toLogical(self, hwnd): left,top=self.topLeft.toLogical(hwnd) right,bottom=self.bottomRight.toLogical(hwnd) - if type(self) is Location: + if isinstance(self, Location): return Location(left,top,right-left,bottom-top) return Rect(left,top,right,bottom) def toPhysical(self, hwnd): left,top=self.topLeft.toPhysical(hwnd) right,bottom=self.bottomRight.toPhysical(hwnd) - if type(self) is Location: + if isinstance(self, Location): return Location(left,top,right-left,bottom-top) return Rect(left,top,right,bottom) def toClient(self, hwnd): left, top =self.topLeft.toClient(hwnd) - if type(self) is Location: + if isinstance(self, Location): return Location(left, top, self.width, self.height) return Rect(left, top, left+self.width, top+self.height) def toScreen(self, hwnd): left,top=self.topLeft.toScreen(hwnd) - if type(self) is Location: + if isinstance(self, Location): return Location(left, top, self.width, self.height) return Rect(left, top, left+self.width, top+self.height) @@ -237,7 +237,7 @@ def __sub__(self,other): if not isinstance(other,_RECT_CLASSES): return NotImplemented left,top,right,bottom=self.left-other.left,self.top-other.top,self.right-other.right,self.bottom-other.bottom - if type(self) is Location: + if isinstance(self, Location): return Location(left,top,right-left,bottom-top) return Rect(left,top,right,bottom) @@ -261,7 +261,7 @@ def __and__(self,other): bottom=min(self.bottom,other.bottom) if left>right or top>bottom: left=top=right=bottom=0 - if type(self) is Location: + if isinstance(self, Location): return Location(left,top,right-left,bottom-top) return Rect(left,top,right,bottom) From 8de022d1aaa4ec875bd673349c8527885be06975 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Mon, 25 Sep 2017 11:22:08 +0200 Subject: [PATCH 03/27] Review actions --- source/locationHelper.py | 146 +++++++++++++++++++-------------------- 1 file changed, 71 insertions(+), 75 deletions(-) diff --git a/source/locationHelper.py b/source/locationHelper.py index e6fafb9afdf..7925ce3e99c 100644 --- a/source/locationHelper.py +++ b/source/locationHelper.py @@ -17,92 +17,89 @@ class Point(namedtuple("Point",("x","y"))): """Represents a point on the screen.""" def __add__(self,other): - if not isinstance(other,_POINT_CLASSES): + """Returns a new L{Point} with its coordinates representing the additions of the original x and y coordinates.""" + if not isinstance(other,POINT_CLASSES): return NotImplemented return Point((self.x+other.x),(self.y+other.y)) def __radd__(self,other): + """Returns a new L{Point} with x = self.x + other.x and y = self.y + other.y.""" return self.__add__(other) - def __lt__(self, other): + def __sub__(self,other): + if not isinstance(other,POINT_CLASSES): + return NotImplemented + return Point((self.x-other.x),(self.y-other.y)) + + def __rsub__(self,other): + return self.__sub__(other) + + def __lt__(self,other): """ Returns whether self is less than other. - This first compares y, than x coordinates. + This first compares y, then x coordinates. For example: (x=4,y=3) < (x=3,y=4) because self.y is less than other.y. - To compare in opposite order (i.e. compare x, than y), use tuple(self) < tuple(other) + To compare in opposite order (i.e. compare x, then y), use tuple(self) < tuple(other) """ - if not isinstance(other,_POINT_CLASSES): + if not isinstance(other,POINT_CLASSES): try: other=toPoint(other) except ValueError: return False return (self.y, self.x) < (other.y, other.x) - def __le__(self, other): + def __le__(self,other): """ Returns whether self is less than or equal to other. - This first compares y, than x coordinates. + This first compares y, then x coordinates. For example: (x=4,y=3) < (x=3,y=4) because self.y is less than other.y. - To compare in opposite order (i.e. compare x, than y), use tuple(self) <= tuple(other) + To compare in opposite order (i.e. compare x, then y), use tuple(self) <= tuple(other) """ - if not isinstance(other,_POINT_CLASSES): + if not isinstance(other,POINT_CLASSES): try: other=toPoint(other) except ValueError: return False return (self.y, self.x) <= (other.y, other.x) - def __gt__(self, other): + def __gt__(self,other): """ Returns whether self is greater than other. - This first compares y, than x coordinates. + This first compares y, then x coordinates. For example: (x=3,y=4) > (x=4,y=3) because self.y is greater than other.y. - To compare in opposite order (i.e. compare x, than y), use tuple(self) > tuple(other) + To compare in opposite order (i.e. compare x, then y), use tuple(self) > tuple(other) """ - if not isinstance(other,_POINT_CLASSES): + if not isinstance(other,POINT_CLASSES): try: other=toPoint(other) except ValueError: return False return (self.y, self.x) > (other.y, other.x) - def __ge__(self, other): + def __ge__(self,other): """ Returns whether self is greater than or equal to other. - This first compares y, than x coordinates. + This first compares y, then x coordinates. For example: (x=3,y=4) > (x=4,y=3) because self.y is greater than other.y. - To compare in opposite order (i.e. compare x, than y), use tuple(self) >= tuple(other) + To compare in opposite order (i.e. compare x, then y), use tuple(self) >= tuple(other) """ - if not isinstance(other,_POINT_CLASSES): + if not isinstance(other,POINT_CLASSES): try: other=toPoint(other) except ValueError: return False return (self.y, self.x) >= (other.y, other.x) - def __eq__(self, other): - if not isinstance(other,_POINT_CLASSES): + def __eq__(self,other): + if not isinstance(other,POINT_CLASSES): try: other=toPoint(other) except ValueError: return False return self.x==other.x and self.y==other.y - def __neq__(self, other): - if not isinstance(other,_POINT_CLASSES): - try: - other=toPoint(other) - except ValueError: - return False - return self.x!=self.x or self.y!=other.y - - def __sub__(self,other): - if not isinstance(other,_POINT_CLASSES): - return NotImplemented - return Point((self.x-other.x),(self.y-other.y)) - - def __rsub__(self,other): - return self.__sub__(other) + def __neq__(self,other): + return not (self==other) def toPOINT(self): """Converts self to a L{ctypes.wintypes.POINT}""" @@ -170,10 +167,10 @@ def center(self): return Point((self.left+self.right)/2, (self.top+self.bottom)/2) def __contains__(self,other): - """Returns whether other is a part of self.""" - if isinstance(other,_POINT_CLASSES): + """Returns whether other is a part of this rectangle.""" + if isinstance(other,POINT_CLASSES): return other.x >= self.left < self.right and other.y >= self.top < self.bottom - if isinstance(other,_RECT_CLASSES): + if isinstance(other,RECT_CLASSES): return self>other try: other=toRect(other) @@ -181,60 +178,55 @@ def __contains__(self,other): return False return self>other - def __lt__(self, other): - """Returns whether self is a subset of other (i.e. whether all points in self are contained by other).""" - if not isinstance(other,_RECT_CLASSES): + def __lt__(self,other): + """Returns whether this rectangle is a subset of other (i.e. whether all points in this rectangle are contained by other).""" + if not isinstance(other,RECT_CLASSES): try: other=toRect(other) except ValueError: return False return self<=other and self!=other - def __le__(self, other): - """Returns whether self is a subset of or equal to other.""" - if not isinstance(other,_RECT_CLASSES): + def __le__(self,other): + """Returns whether this rectangle is a subset of or equal to other.""" + if not isinstance(other,RECT_CLASSES): try: other=toRect(other) except ValueError: return False return self.left >= other.left and self.top >= other.top and self.right <= other.right and self.bottom <= other.bottom - def __gt__(self, other): - """Returns whether self is a superset of other (i.e. whether all points of other are contained by self).""" - if not isinstance(other,_RECT_CLASSES): + def __gt__(self,other): + """Returns whether this rectangle is a superset of other (i.e. whether all points of other are contained by this rectangle).""" + if not isinstance(other,RECT_CLASSES): try: other=toRect(other) except ValueError: return False return self>=other and self!=other - def __ge__(self, other): + def __ge__(self,other): """Returns whether self is a superset of or equal to other.""" - if not isinstance(other,_RECT_CLASSES): + if not isinstance(other,RECT_CLASSES): try: other=toRect(other) except ValueError: return False return other.left >= self.left and other.top >= self.top and other.right <= self.right and other.bottom <= self.bottom - def __eq__(self, other): - if not isinstance(other,_RECT_CLASSES): + def __eq__(self,other): + if not isinstance(other,RECT_CLASSES): try: other=toRect(other) except ValueError: return False return other.left == self.left and other.top == self.top and other.right == self.right and other.bottom == self.bottom - def __neq__(self, other): - if not isinstance(other,_RECT_CLASSES): - try: - other=toRect(other) - except ValueError: - return False - return not (other.left == self.left and other.top == self.top and other.right == self.right and other.bottom == self.bottom) + def __neq__(self,other): + return not (self==other) def __sub__(self,other): - if not isinstance(other,_RECT_CLASSES): + if not isinstance(other,RECT_CLASSES): return NotImplemented left,top,right,bottom=self.left-other.left,self.top-other.top,self.right-other.right,self.bottom-other.bottom if isinstance(self, Location): @@ -250,7 +242,7 @@ def __and__(self,other): this results in Rect(left=20,top=20,right=25,bottom=25). No intersect results in a rectangle with zeroed coordinates. """ - if not isinstance(other,_RECT_CLASSES): + if not isinstance(other,RECT_CLASSES): try: other=toRect(other) except ValueError: @@ -269,7 +261,10 @@ def __rand__(self,other): return self.__and__(other) class Location(_RectMixin, namedtuple("Location",("left","top","width","height"))): - """Represents a rectangle on the screen, based on left and top coordinates, width and height.""" + """ + Represents a rectangle on the screen, based on left and top coordinates, width and height. + To represent a rectangle using left, top, right and bottom coordinates, use L{Rect}. + """ @property def right(self): @@ -285,6 +280,7 @@ def toRect(self): class Rect(_RectMixin, namedtuple("Rect",("left","top","right","bottom"))): """Represents a rectangle on the screen. By convention, the right and bottom edges of the rectangle are normally considered exclusive. + To represent a rectangle based on width and height instead, use L{Location}. """ @property @@ -302,10 +298,10 @@ def toRect(*params): """ Converts the given input to L{Rect}. Input should be one of the following types: - * One of l{_RECT_CLASSES}. - * One of L{_POINT_CLASSES}: converted to L{Rect} square of one pixel. + * One of l{RECT_CLASSES}. + * One of L{POINT_CLASSES}: converted to L{Rect} square of one pixel. * List or tuple of integers: 4 treated as L{Rect}, 2 treated as L{Point}. - * List or tuple of mixed types from L{_RECT_CLASSES} or L{_POINT_CLASSES}: converted to L{Rect} containing all input. + * List or tuple of mixed types from L{RECT_CLASSES} or L{POINT_CLASSES}: converted to L{Rect} containing all input. """ if len(params)==0: raise TypeError("This function takes at least 1 argument (0 given)") @@ -313,9 +309,9 @@ def toRect(*params): param=params[0] if isinstance(param,Rect): return param - if isinstance(param,_RECT_CLASSES): + if isinstance(param,RECT_CLASSES): return Rect(param.left,param.top,param.right,param.bottom) - if isinstance(param,_POINT_CLASSES): + if isinstance(param,POINT_CLASSES): # Right and bottom edges of the resulting rectangle are considered exclusive x,y=point.x,point.y return Rect(x,y,x+1,y+1) @@ -336,10 +332,10 @@ def toRect(*params): xs=[] ys=[] for param in params: - if isinstance(param,_RECT_CLASSES): + if isinstance(param,RECT_CLASSES): xs.extend((param.left,param.right)) ys.extend((param.top,param.bottom)) - elif isinstance(param,_POINT_CLASSES): + elif isinstance(param,POINT_CLASSES): xs.append(param.x) ys.append(param.y) else: @@ -354,10 +350,10 @@ def toLocation(*params): """ Converts the given input to L{Location}. Input should be one of the following types: - * One of l{_RECT_CLASSES}. - * One of L{_POINT_CLASSES}: converted to L{Location} square of one pixel. + * One of l{RECT_CLASSES}. + * One of L{POINT_CLASSES}: converted to L{Location} square of one pixel. * List or tuple of integers: 4 treated as L{Rect}, 2 treated as L{Point}. - * List or tuple of mixed types from L{_RECT_CLASSES} or L{_POINT_CLASSES}: converted to L{Location} containing all input. + * List or tuple of mixed types from L{RECT_CLASSES} or L{POINT_CLASSES}: converted to L{Location} containing all input. """ if len(params)==0: raise TypeError("This function takes at least 1 argument (0 given)") @@ -365,7 +361,7 @@ def toLocation(*params): param=params[0] if isinstance(param,Location): return param - if not isinstance(param,_RECT_CLASSES+_POINT_CLASSES) and isinstance(param,(tuple,list)): + if not isinstance(param,RECT_CLASSES+POINT_CLASSES) and isinstance(param,(tuple,list)): # One indexable in another indexable doesn't make sence, so treat the inner indexable as outer indexable params=param if len(params)==4 and all(isinstance(param,(int,long)) for param in params): @@ -378,7 +374,7 @@ def toLocation(*params): def toPoint(*params): """ Converts the given input to L{Point}. - Input should either be one of L{_POINT_CLASSES}, 2 integers or one double word. + Input should either be one of L{POINT_CLASSES}, 2 integers or one double word. """ if not len(params) in (1,2): raise TypeError("This function takes 1 or 2 arguments (%d given)"%len(params)) @@ -386,7 +382,7 @@ def toPoint(*params): param=params[0] if isinstance(param,Point): return param - if isinstance(param,_POINT_CLASSES): + if isinstance(param,POINT_CLASSES): return Point(param.x,param.y) if isinstance(param,(int,long)): return Point(winUser.GET_X_LPARAM(param),winUser.GET_Y_LPARAM(param)) @@ -396,7 +392,7 @@ def toPoint(*params): #: Classes which support conversion to locationHelper Points using their x and y properties. #: type: tuple -_POINT_CLASSES=(Point, POINT, textInfos.Point, wx.Point) +POINT_CLASSES=(Point, POINT, textInfos.Point, wx.Point) #: Classes which support conversion to locationHelper Rects and Locations using their left, top, right and bottom properties. #: type: tuple -_RECT_CLASSES=(Rect, Location, RECT, SMALL_RECT, textInfos.Rect, wx.Rect) +RECT_CLASSES=(Rect, Location, RECT, SMALL_RECT, textInfos.Rect, wx.Rect) From b9c04c5e7b9ffb4ba922ac457011ae7143fafd06 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Sat, 6 Jan 2018 18:20:07 +0100 Subject: [PATCH 04/27] Class renames --- source/NVDAObjects/IAccessible/MSHTML.py | 4 +- source/NVDAObjects/IAccessible/__init__.py | 4 +- .../NVDAObjects/IAccessible/sysListView32.py | 4 +- source/NVDAObjects/JAB/__init__.py | 4 +- source/NVDAObjects/UIA/__init__.py | 4 +- source/NVDAObjects/window/__init__.py | 4 +- source/appModules/powerpnt.py | 4 +- source/displayModel.py | 4 +- source/locationHelper.py | 122 +++++++++--------- tests/unit/test_locationHelper.py | 40 +++--- 10 files changed, 97 insertions(+), 97 deletions(-) diff --git a/source/NVDAObjects/IAccessible/MSHTML.py b/source/NVDAObjects/IAccessible/MSHTML.py index d176078bf07..8e36e8bf9c8 100644 --- a/source/NVDAObjects/IAccessible/MSHTML.py +++ b/source/NVDAObjects/IAccessible/MSHTML.py @@ -27,7 +27,7 @@ from .. import InvalidNVDAObject from ..window import Window from NVDAObjects.UIA import UIA, UIATextInfo -from locationHelper import Rect +from locationHelper import RectLTRB IID_IHTMLElement=comtypes.GUID('{3050F1FF-98B5-11CF-BB82-00AA00BDCE0B}') @@ -587,7 +587,7 @@ def _get_location(self): top=int(r.top*yFactor) right=int(r.right*xFactor) bottom=int(r.bottom*yFactor) - return Rect(left,top,right,bottom).toScreen(self.windowHandle).toLocation() + return RectLTRB(left,top,right,bottom).toScreen(self.windowHandle).toLTWH() return None def _get_TextInfo(self): diff --git a/source/NVDAObjects/IAccessible/__init__.py b/source/NVDAObjects/IAccessible/__init__.py index 78b9dbacf02..2a2a40ae159 100644 --- a/source/NVDAObjects/IAccessible/__init__.py +++ b/source/NVDAObjects/IAccessible/__init__.py @@ -31,7 +31,7 @@ import NVDAObjects.JAB import eventHandler from NVDAObjects.behaviors import ProgressBar, Dialog, EditableTextWithAutoSelectDetection, FocusableUnfocusableContainer, ToolTip, Notification -from locationHelper import Location +from locationHelper import RectLTWH def getNVDAObjectFromEvent(hwnd,objectID,childID): try: @@ -891,7 +891,7 @@ def _get_childCount(self): def _get_location(self): try: - return Location(*self.IAccessibleObject.accLocation(self.IAccessibleChildID)) + return RectLTWH(*self.IAccessibleObject.accLocation(self.IAccessibleChildID)) except COMError: return None diff --git a/source/NVDAObjects/IAccessible/sysListView32.py b/source/NVDAObjects/IAccessible/sysListView32.py index 0a72736994a..0ef3851c9bf 100644 --- a/source/NVDAObjects/IAccessible/sysListView32.py +++ b/source/NVDAObjects/IAccessible/sysListView32.py @@ -23,7 +23,7 @@ import watchdog from NVDAObjects.behaviors import RowWithoutCellObjects, RowWithFakeNavigation import config -from locationHelper import toLocation +from locationHelper import toRectLTWH #Window messages LVM_FIRST=0x1000 @@ -308,7 +308,7 @@ def _getColumnLocationRaw(self,index): winKernel.virtualFreeEx(processHandle,internalRect,0,winKernel.MEM_RELEASE) windll.user32.ClientToScreen(self.windowHandle,byref(localRect)) windll.user32.ClientToScreen(self.windowHandle,byref(localRect,8)) - return toLocation(localRect) + return toRectLTWH(localRect) def _getColumnLocation(self,column): return self._getColumnLocationRaw(self.parent._columnOrderArray[column - 1]) diff --git a/source/NVDAObjects/JAB/__init__.py b/source/NVDAObjects/JAB/__init__.py index 78ce1874b5b..51130f8dc96 100644 --- a/source/NVDAObjects/JAB/__init__.py +++ b/source/NVDAObjects/JAB/__init__.py @@ -9,7 +9,7 @@ import textInfos.offsets from logHandler import log from .. import InvalidNVDAObject -from locationHelper import Location +from locationHelper import RectLTWH JABRolesToNVDARoles={ "alert":controlTypes.ROLE_DIALOG, @@ -302,7 +302,7 @@ def _get_description(self): return re_simpleXmlTag.sub(" ", self._JABAccContextInfo.description) def _get_location(self): - return Location(self._JABAccContextInfo.x,self._JABAccContextInfo.y,self._JABAccContextInfo.width,self._JABAccContextInfo.height) + return RectLTWH(self._JABAccContextInfo.x,self._JABAccContextInfo.y,self._JABAccContextInfo.width,self._JABAccContextInfo.height) def _get_hasFocus(self): if controlTypes.STATE_FOCUSED in self.states: diff --git a/source/NVDAObjects/UIA/__init__.py b/source/NVDAObjects/UIA/__init__.py index b0e952f3586..ba6f37a4684 100644 --- a/source/NVDAObjects/UIA/__init__.py +++ b/source/NVDAObjects/UIA/__init__.py @@ -31,7 +31,7 @@ from NVDAObjects.behaviors import ProgressBar, EditableTextWithoutAutoSelectDetection, Dialog, Notification, EditableTextWithSuggestions import braille import time -from locationHelper import Location +from locationHelper import RectLTWH class UIATextInfo(textInfos.TextInfo): @@ -1226,7 +1226,7 @@ def _get_location(self): top=int(r[1]) width=int(r[2]) height=int(r[3]) - return Location(left,top,width,height) + return RectLTWH(left,top,width,height) def _get_value(self): val=self._getUIACacheablePropertyValue(UIAHandler.UIA_RangeValueValuePropertyId,True) diff --git a/source/NVDAObjects/window/__init__.py b/source/NVDAObjects/window/__init__.py index 12b38903d0c..ef3a2a77f68 100644 --- a/source/NVDAObjects/window/__init__.py +++ b/source/NVDAObjects/window/__init__.py @@ -17,7 +17,7 @@ from NVDAObjects import NVDAObject from NVDAObjects.behaviors import EditableText, LiveText import watchdog -from locationHelper import toLocation +from locationHelper import toRectLTWH re_WindowsForms=re.compile(r'^WindowsForms[0-9]*\.(.*)\.app\..*$') re_ATL=re.compile(r'^ATL:(.*)$') @@ -190,7 +190,7 @@ def _get_windowControlID(self): def _get_location(self): r=ctypes.wintypes.RECT() ctypes.windll.user32.GetWindowRect(self.windowHandle,ctypes.byref(r)) - return toLocation(r) + return toRectLTWH(r) def _get_displayText(self): """The text at this object's location according to the display model for this object's window.""" diff --git a/source/appModules/powerpnt.py b/source/appModules/powerpnt.py index 70a41175151..585961028e6 100644 --- a/source/appModules/powerpnt.py +++ b/source/appModules/powerpnt.py @@ -33,7 +33,7 @@ import controlTypes from logHandler import log import scriptHandler -from locationHelper import Rect +from locationHelper import RectLTRB from NVDAObjects.window._msOfficeChart import OfficeChart # Window classes where PowerPoint's object model should be used @@ -739,7 +739,7 @@ def _get_location(self): top=self.documentWindow.ppObjectModel.pointsToScreenPixelsY(pointTop) right=self.documentWindow.ppObjectModel.pointsToScreenPixelsX(pointLeft+pointWidth) bottom=self.documentWindow.ppObjectModel.pointsToScreenPixelsY(pointTop+pointHeight) - return Rect(left,top,right,bottom).toLocation() + return RectLTRB(left,top,right,bottom).toLTWH() def _get_ppShapeType(self): """Fetches and caches the type of this shape.""" diff --git a/source/displayModel.py b/source/displayModel.py index 2d346a94b86..09f20fd2121 100644 --- a/source/displayModel.py +++ b/source/displayModel.py @@ -19,7 +19,7 @@ import watchdog from logHandler import log import windowUtils -from locationHelper import Rect +from locationHelper import RectLTRB def wcharToInt(c): i=ord(c) @@ -179,7 +179,7 @@ def getWindowTextInRect(bindingHandle, windowHandle, left, top, right, bottom,mi characterLocations = [] cpBufIt = iter(cpBuf) for cp in cpBufIt: - characterLocations.append(Rect(wcharToInt(cp), wcharToInt(next(cpBufIt)), wcharToInt(next(cpBufIt)), wcharToInt(next(cpBufIt)))) + characterLocations.append(RectLTRB(wcharToInt(cp), wcharToInt(next(cpBufIt)), wcharToInt(next(cpBufIt)), wcharToInt(next(cpBufIt)))) return text, characterLocations def getFocusRect(obj): diff --git a/source/locationHelper.py b/source/locationHelper.py index 7925ce3e99c..4326ac01149 100644 --- a/source/locationHelper.py +++ b/source/locationHelper.py @@ -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) 2017 NV Access Limited, Babbage B.V. +#Copyright (C) 2017-2018 NV Access Limited, Babbage B.V. """Classes and helper functions for working with rectangles and coordinates.""" @@ -122,7 +122,7 @@ def toScreen(self, hwnd): return Point(*winUser.ClientToScreen(hwnd, *self)) class _RectMixin: - """Mix-in class for properties shared between Location and Rect classes""" + """Mix-in class for properties shared between RectLTRB and RectLTWH classes""" def toRECT(self): """Converts self to a L{ctypes.wintypes.RECT}""" @@ -131,28 +131,28 @@ def toRECT(self): def toLogical(self, hwnd): left,top=self.topLeft.toLogical(hwnd) right,bottom=self.bottomRight.toLogical(hwnd) - if isinstance(self, Location): - return Location(left,top,right-left,bottom-top) - return Rect(left,top,right,bottom) + if isinstance(self, RectLTWH): + return RectLTWH(left,top,right-left,bottom-top) + return RectLTRB(left,top,right,bottom) def toPhysical(self, hwnd): left,top=self.topLeft.toPhysical(hwnd) right,bottom=self.bottomRight.toPhysical(hwnd) - if isinstance(self, Location): - return Location(left,top,right-left,bottom-top) - return Rect(left,top,right,bottom) + if isinstance(self, RectLTWH): + return RectLTWH(left,top,right-left,bottom-top) + return RectLTRB(left,top,right,bottom) def toClient(self, hwnd): left, top =self.topLeft.toClient(hwnd) - if isinstance(self, Location): - return Location(left, top, self.width, self.height) - return Rect(left, top, left+self.width, top+self.height) + if isinstance(self, RectLTWH): + return RectLTWH(left, top, self.width, self.height) + return RectLTRB(left, top, left+self.width, top+self.height) def toScreen(self, hwnd): left,top=self.topLeft.toScreen(hwnd) - if isinstance(self, Location): - return Location(left, top, self.width, self.height) - return Rect(left, top, left+self.width, top+self.height) + if isinstance(self, RectLTWH): + return RectLTWH(left, top, self.width, self.height) + return RectLTRB(left, top, left+self.width, top+self.height) @property def topLeft(self): @@ -173,7 +173,7 @@ def __contains__(self,other): if isinstance(other,RECT_CLASSES): return self>other try: - other=toRect(other) + other=toRectLTRB(other) except: return False return self>other @@ -182,7 +182,7 @@ def __lt__(self,other): """Returns whether this rectangle is a subset of other (i.e. whether all points in this rectangle are contained by other).""" if not isinstance(other,RECT_CLASSES): try: - other=toRect(other) + other=toRectLTRB(other) except ValueError: return False return self<=other and self!=other @@ -191,7 +191,7 @@ def __le__(self,other): """Returns whether this rectangle is a subset of or equal to other.""" if not isinstance(other,RECT_CLASSES): try: - other=toRect(other) + other=toRectLTRB(other) except ValueError: return False return self.left >= other.left and self.top >= other.top and self.right <= other.right and self.bottom <= other.bottom @@ -200,7 +200,7 @@ def __gt__(self,other): """Returns whether this rectangle is a superset of other (i.e. whether all points of other are contained by this rectangle).""" if not isinstance(other,RECT_CLASSES): try: - other=toRect(other) + other=toRectLTRB(other) except ValueError: return False return self>=other and self!=other @@ -209,7 +209,7 @@ def __ge__(self,other): """Returns whether self is a superset of or equal to other.""" if not isinstance(other,RECT_CLASSES): try: - other=toRect(other) + other=toRectLTRB(other) except ValueError: return False return other.left >= self.left and other.top >= self.top and other.right <= self.right and other.bottom <= self.bottom @@ -217,7 +217,7 @@ def __ge__(self,other): def __eq__(self,other): if not isinstance(other,RECT_CLASSES): try: - other=toRect(other) + other=toRectLTRB(other) except ValueError: return False return other.left == self.left and other.top == self.top and other.right == self.right and other.bottom == self.bottom @@ -229,9 +229,9 @@ def __sub__(self,other): if not isinstance(other,RECT_CLASSES): return NotImplemented left,top,right,bottom=self.left-other.left,self.top-other.top,self.right-other.right,self.bottom-other.bottom - if isinstance(self, Location): - return Location(left,top,right-left,bottom-top) - return Rect(left,top,right,bottom) + if isinstance(self, RectLTWH): + return RectLTWH(left,top,right-left,bottom-top) + return RectLTRB(left,top,right,bottom) def __rsub__(self,other): return self.__sub__(other) @@ -244,7 +244,7 @@ def __and__(self,other): """ if not isinstance(other,RECT_CLASSES): try: - other=toRect(other) + other=toRectLTRB(other) except ValueError: return NotImplemented left=max(self.left,other.left) @@ -253,17 +253,17 @@ def __and__(self,other): bottom=min(self.bottom,other.bottom) if left>right or top>bottom: left=top=right=bottom=0 - if isinstance(self, Location): - return Location(left,top,right-left,bottom-top) - return Rect(left,top,right,bottom) + if isinstance(self, RectLTWH): + return RectLTWH(left,top,right-left,bottom-top) + return RectLTRB(left,top,right,bottom) def __rand__(self,other): return self.__and__(other) -class Location(_RectMixin, namedtuple("Location",("left","top","width","height"))): +class RectLTWH(_RectMixin, namedtuple("RectLTWH",("left","top","width","height"))): """ Represents a rectangle on the screen, based on left and top coordinates, width and height. - To represent a rectangle using left, top, right and bottom coordinates, use L{Rect}. + To represent a rectangle using left, top, right and bottom coordinates, use L{RectLTRB}. """ @property @@ -274,13 +274,13 @@ def right(self): def bottom(self): return self.top+self.height - def toRect(self): - return Rect(self.left,self.top,self.right,self.bottom) + def toLTRB(self): + return RectLTRB(self.left,self.top,self.right,self.bottom) -class Rect(_RectMixin, namedtuple("Rect",("left","top","right","bottom"))): +class RectLTRB(_RectMixin, namedtuple("RectLTRB",("left","top","right","bottom"))): """Represents a rectangle on the screen. By convention, the right and bottom edges of the rectangle are normally considered exclusive. - To represent a rectangle based on width and height instead, use L{Location}. + To represent a rectangle based on width and height instead, use L{RectLTWH}. """ @property @@ -291,42 +291,42 @@ def width(self): def height(self): return self.bottom-self.top - def toLocation(self): - return Location(self.left,self.top,self.width,self.height) + def toLTWH(self): + return RectLTWH(self.left,self.top,self.width,self.height) -def toRect(*params): +def toRectLTRB(*params): """ - Converts the given input to L{Rect}. + Converts the given input to L{RectLTRB}. Input should be one of the following types: * One of l{RECT_CLASSES}. * One of L{POINT_CLASSES}: converted to L{Rect} square of one pixel. - * List or tuple of integers: 4 treated as L{Rect}, 2 treated as L{Point}. - * List or tuple of mixed types from L{RECT_CLASSES} or L{POINT_CLASSES}: converted to L{Rect} containing all input. + * List or tuple of integers: 4 treated as L{RectLTRB}, 2 treated as L{Point}. + * List or tuple of mixed types from L{RECT_CLASSES} or L{POINT_CLASSES}: converted to L{RectLTRB} containing all input. """ if len(params)==0: raise TypeError("This function takes at least 1 argument (0 given)") if len(params)==1: param=params[0] - if isinstance(param,Rect): + if isinstance(param,RectLTRB): return param if isinstance(param,RECT_CLASSES): - return Rect(param.left,param.top,param.right,param.bottom) + return RectLTRB(param.left,param.top,param.right,param.bottom) if isinstance(param,POINT_CLASSES): # Right and bottom edges of the resulting rectangle are considered exclusive x,y=point.x,point.y - return Rect(x,y,x+1,y+1) + return RectLTRB(x,y,x+1,y+1) if isinstance(param,(tuple,list)): # One indexable in another indexable doesn't make sence, so treat the inner indexable as outer indexable params=param if all(isinstance(param,(int,long)) for param in params): if len(params)==4: - # Assume that we are converting from a tuple rectangle. - # To convert from a tuple location, use L{toLocation} instead. - # To convert from a tuple rectangle to L{Location}, use this function and execute L{toLocation} on the resulting object. - return Rect(*params) + # Assume that we are converting from a tuple rectangle (RectLTRB). + # To convert from a tuple location (RectLTWH), use L{toRectLTWH} instead. + # To convert from a tuple rectangle to L{RectLTWH}, use this function and execute L{toLTWH} on the resulting object. + return RectLTRB(*params) elif len(params)==2: x,y=params - return Rect(x,y,x+1,y+1) + return RectLTRB(x,y,x+1,y+1) elif len(params) in (1,3) or len(params)>4: raise ValueError("When providing integers, this function requires either 2 or 4 arguments (%d given)"%len(params)) xs=[] @@ -344,32 +344,32 @@ def toRect(*params): top=min(ys) right=max(xs) bottom=max(ys) - return Rect(left,top,right,bottom) + return RectLTRB(left,top,right,bottom) -def toLocation(*params): +def toRectLTWH(*params): """ - Converts the given input to L{Location}. + Converts the given input to L{RectLTWH}. Input should be one of the following types: * One of l{RECT_CLASSES}. - * One of L{POINT_CLASSES}: converted to L{Location} square of one pixel. - * List or tuple of integers: 4 treated as L{Rect}, 2 treated as L{Point}. - * List or tuple of mixed types from L{RECT_CLASSES} or L{POINT_CLASSES}: converted to L{Location} containing all input. + * One of L{POINT_CLASSES}: converted to L{RectLTWH} square of one pixel. + * List or tuple of integers: 4 treated as L{RectLTWH}, 2 treated as L{Point}. + * List or tuple of mixed types from L{RECT_CLASSES} or L{POINT_CLASSES}: converted to L{RectLTWH} containing all input. """ if len(params)==0: raise TypeError("This function takes at least 1 argument (0 given)") if len(params)==1: param=params[0] - if isinstance(param,Location): + if isinstance(param,RectLTWH): return param if not isinstance(param,RECT_CLASSES+POINT_CLASSES) and isinstance(param,(tuple,list)): # One indexable in another indexable doesn't make sence, so treat the inner indexable as outer indexable params=param if len(params)==4 and all(isinstance(param,(int,long)) for param in params): - # Assume that we are converting from a tuple location. - # To convert from a tuple rectangle, use L{toRectangle} instead. - # To convert from a tuple location to L{Rect}, use this function and execute L{toRect} on the resulting location. - return Location(*params) - return toRect(*params).toLocation() + # Assume that we are converting from a tuple location (RectLTWH). + # To convert from a tuple rectangle (RectLTRB), use L{toRectLTRB} instead. + # To convert from a tuple location to L{RectLTRB}, use this function and execute L{toLTRB} on the resulting object. + return RectLTWH(*params) + return toRectLTRB(*params).toLTWH() def toPoint(*params): """ @@ -393,6 +393,6 @@ def toPoint(*params): #: Classes which support conversion to locationHelper Points using their x and y properties. #: type: tuple POINT_CLASSES=(Point, POINT, textInfos.Point, wx.Point) -#: Classes which support conversion to locationHelper Rects and Locations using their left, top, right and bottom properties. +#: Classes which support conversion to locationHelper RectLTRB/LTWH using their left, top, right and bottom properties. #: type: tuple -RECT_CLASSES=(Rect, Location, RECT, SMALL_RECT, textInfos.Rect, wx.Rect) +RECT_CLASSES=(RectLTRB, RectLTWH, RECT, SMALL_RECT, textInfos.Rect, wx.Rect) diff --git a/tests/unit/test_locationHelper.py b/tests/unit/test_locationHelper.py index 60eb3273e59..58858c9003a 100644 --- a/tests/unit/test_locationHelper.py +++ b/tests/unit/test_locationHelper.py @@ -13,30 +13,30 @@ class TestRectOperators(unittest.TestCase): def test_and(self): - self.assertEqual(Rect(left=2,top=2,right=4,bottom=4) & Rect(left=3,top=3,right=5,bottom=5),Rect(left=3,top=3,right=4,bottom=4)) - self.assertEqual(Rect(left=2,top=2,right=4,bottom=4) & Rect(left=5,top=5,right=7,bottom=7),Rect(left=0,top=0,right=0,bottom=0)) + self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4) & RectLTRB(left=3,top=3,right=5,bottom=5),RectLTRB(left=3,top=3,right=4,bottom=4)) + self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4) & RectLTRB(left=5,top=5,right=7,bottom=7),RectLTRB(left=0,top=0,right=0,bottom=0)) def test_gt(self): - self.assertGreater(Rect(left=2,top=2,right=6,bottom=6),Rect(left=2,top=2,right=4,bottom=4)) - self.assertGreaterEqual(Rect(left=2,top=2,right=6,bottom=6),Rect(left=2,top=2,right=4,bottom=4)) + self.assertGreater(RectLTRB(left=2,top=2,right=6,bottom=6),RectLTRB(left=2,top=2,right=4,bottom=4)) + self.assertGreaterEqual(RectLTRB(left=2,top=2,right=6,bottom=6),RectLTRB(left=2,top=2,right=4,bottom=4)) def test_lt(self): - self.assertLess(Rect(left=2,top=2,right=4,bottom=4),Rect(left=2,top=2,right=6,bottom=6)) - self.assertLessEqual(Rect(left=2,top=2,right=4,bottom=4),Rect(left=2,top=2,right=6,bottom=6)) + self.assertLess(RectLTRB(left=2,top=2,right=4,bottom=4),RectLTRB(left=2,top=2,right=6,bottom=6)) + self.assertLessEqual(RectLTRB(left=2,top=2,right=4,bottom=4),RectLTRB(left=2,top=2,right=6,bottom=6)) def test_in(self): - self.assertIn(Rect(left=2,top=2,right=4,bottom=4),Rect(left=2,top=2,right=6,bottom=6)) - self.assertIn(Point(x=2,y=6),Rect(left=2,top=2,right=6,bottom=6)) + self.assertIn(RectLTRB(left=2,top=2,right=4,bottom=4),RectLTRB(left=2,top=2,right=6,bottom=6)) + self.assertIn(Point(x=2,y=6),RectLTRB(left=2,top=2,right=6,bottom=6)) def test_sub(self): - self.assertEqual(Rect(left=2,top=2,right=4,bottom=4) - Rect(left=3,top=3,right=5,bottom=5),Rect(left=-1,top=-1,right=-1,bottom=-1)) - self.assertEqual(Rect(left=2,top=2,right=4,bottom=4) - Rect(left=5,top=5,right=8,bottom=8),Rect(left=-3,top=-3,right=-4,bottom=0-4)) + self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4) - RectLTRB(left=3,top=3,right=5,bottom=5),RectLTRB(left=-1,top=-1,right=-1,bottom=-1)) + self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4) - RectLTRB(left=5,top=5,right=8,bottom=8),RectLTRB(left=-3,top=-3,right=-4,bottom=0-4)) -class TestToRect(unittest.TestCase): +class TestToRectLTRB(unittest.TestCase): def test_collection(self): - rect=Rect(left=10,top=15,right=500,bottom=1000) - self.assertEqual(toRect( + rect=RectLTRB(left=10,top=15,right=500,bottom=1000) + self.assertEqual(toRectLTRB( rect.topLeft, rect.bottomRight, rect.center, @@ -44,17 +44,17 @@ def test_collection(self): Point(20,20), Point(50,50), Point(400,400), - Rect(left=450,top=450,right=490,bottom=990) + RectLTRB(left=450,top=450,right=490,bottom=990) ), rect) def test_integers(self): - self.assertEqual(Rect(left=10,top=10,right=20,bottom=20),toRect(10,10,20,20)) + self.assertEqual(RectLTRB(left=10,top=10,right=20,bottom=20),toRectLTRB(10,10,20,20)) -class TestToLocation(unittest.TestCase): +class TestToRectLTWH(unittest.TestCase): def test_collection(self): - location=Location(left=10,top=15,width=500,height=1000) - self.assertEqual(toLocation( + location=RectLTWH(left=10,top=15,width=500,height=1000) + self.assertEqual(toRectLTWH( location.topLeft, location.bottomRight, location.center, @@ -62,11 +62,11 @@ def test_collection(self): Point(20,20), Point(50,50), Point(400,400), - Rect(left=450,top=450,right=505,bottom=1010) + RectLTRB(left=450,top=450,right=505,bottom=1010) ), location) def test_integers(self): - self.assertEqual(Location(left=10,top=10,width=20,height=20),toLocation(10,10,20,20)) + self.assertEqual(RectLTWH(left=10,top=10,width=20,height=20),toRectLTWH(10,10,20,20)) class TestPointOperators(unittest.TestCase): From 1aaa5c04b88badebb8b4c2a78c562a99a42baaf9 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Mon, 8 Jan 2018 07:58:07 +0100 Subject: [PATCH 05/27] Fix __radd__ operator function for Points --- source/locationHelper.py | 2 ++ tests/unit/test_locationHelper.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/source/locationHelper.py b/source/locationHelper.py index 4326ac01149..514992a8041 100644 --- a/source/locationHelper.py +++ b/source/locationHelper.py @@ -24,6 +24,8 @@ def __add__(self,other): def __radd__(self,other): """Returns a new L{Point} with x = self.x + other.x and y = self.y + other.y.""" + if other == 0: + return self return self.__add__(other) def __sub__(self,other): diff --git a/tests/unit/test_locationHelper.py b/tests/unit/test_locationHelper.py index 58858c9003a..085dbdacba5 100644 --- a/tests/unit/test_locationHelper.py +++ b/tests/unit/test_locationHelper.py @@ -73,6 +73,10 @@ class TestPointOperators(unittest.TestCase): def test_add(self): self.assertEqual(Point(x=2,y=4)+Point(x=2,y=4),Point(x=4,y=8)) + def test_sum(self): + point=Point(x=2,y=4) + self.assertEqual(sum((point, point, point)), Point(x=6,y=12)) + def test_sub(self): self.assertEqual(Point(x=2,y=4)-Point(x=4,y=8),Point(x=-2,y=-4)) From e3f8f3a96be8ad4b1dd4645d18b49ce6b0bdd250 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Mon, 8 Jan 2018 08:21:19 +0100 Subject: [PATCH 06/27] Added unit test for rect center --- tests/unit/test_locationHelper.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/unit/test_locationHelper.py b/tests/unit/test_locationHelper.py index 085dbdacba5..0a71c04a98f 100644 --- a/tests/unit/test_locationHelper.py +++ b/tests/unit/test_locationHelper.py @@ -32,6 +32,11 @@ def test_sub(self): self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4) - RectLTRB(left=3,top=3,right=5,bottom=5),RectLTRB(left=-1,top=-1,right=-1,bottom=-1)) self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4) - RectLTRB(left=5,top=5,right=8,bottom=8),RectLTRB(left=-3,top=-3,right=-4,bottom=0-4)) +class TestRectUtilities(unittest.TestCase): + + def test_center(self): + self.assertEqual(RectLTRB(left=-5,top=-5,right=5,bottom=5).center, Point(x=0,y=0)) + class TestToRectLTRB(unittest.TestCase): def test_collection(self): From 7308287e7295676ce2b59aa7cce3f5a019e6a4cd Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Mon, 8 Jan 2018 16:19:03 +0100 Subject: [PATCH 07/27] Rename rectangle operators, remove the sub operator as it does not make much sense --- source/locationHelper.py | 45 +++++-------------------------- tests/unit/test_locationHelper.py | 20 +++++--------- 2 files changed, 13 insertions(+), 52 deletions(-) diff --git a/source/locationHelper.py b/source/locationHelper.py index 514992a8041..0502544dd03 100644 --- a/source/locationHelper.py +++ b/source/locationHelper.py @@ -172,25 +172,15 @@ def __contains__(self,other): """Returns whether other is a part of this rectangle.""" if isinstance(other,POINT_CLASSES): return other.x >= self.left < self.right and other.y >= self.top < self.bottom - if isinstance(other,RECT_CLASSES): - return self>other - try: - other=toRectLTRB(other) - except: - return False - return self>other - - def __lt__(self,other): - """Returns whether this rectangle is a subset of other (i.e. whether all points in this rectangle are contained by other).""" if not isinstance(other,RECT_CLASSES): try: other=toRectLTRB(other) - except ValueError: + except: return False - return self<=other and self!=other + return self.isSuperset(other) and self!=other - def __le__(self,other): - """Returns whether this rectangle is a subset of or equal to other.""" + def isSubset(self,other): + """Returns whether this rectangle is a subset of other (i.e. whether all points in this rectangle are contained by other).""" if not isinstance(other,RECT_CLASSES): try: other=toRectLTRB(other) @@ -198,17 +188,8 @@ def __le__(self,other): return False return self.left >= other.left and self.top >= other.top and self.right <= other.right and self.bottom <= other.bottom - def __gt__(self,other): + def isSuperset(self,other): """Returns whether this rectangle is a superset of other (i.e. whether all points of other are contained by this rectangle).""" - if not isinstance(other,RECT_CLASSES): - try: - other=toRectLTRB(other) - except ValueError: - return False - return self>=other and self!=other - - def __ge__(self,other): - """Returns whether self is a superset of or equal to other.""" if not isinstance(other,RECT_CLASSES): try: other=toRectLTRB(other) @@ -227,18 +208,7 @@ def __eq__(self,other): def __neq__(self,other): return not (self==other) - def __sub__(self,other): - if not isinstance(other,RECT_CLASSES): - return NotImplemented - left,top,right,bottom=self.left-other.left,self.top-other.top,self.right-other.right,self.bottom-other.bottom - if isinstance(self, RectLTWH): - return RectLTWH(left,top,right-left,bottom-top) - return RectLTRB(left,top,right,bottom) - - def __rsub__(self,other): - return self.__sub__(other) - - def __and__(self,other): + def intersection(self,other): """Returns the intersection of self and other. For example, if self = Rect(left=10,top=10,right=25,bottom=25) and other = Rect(left=20,top=20,right=35,bottom=35), this results in Rect(left=20,top=20,right=25,bottom=25). @@ -259,9 +229,6 @@ def __and__(self,other): return RectLTWH(left,top,right-left,bottom-top) return RectLTRB(left,top,right,bottom) - def __rand__(self,other): - return self.__and__(other) - class RectLTWH(_RectMixin, namedtuple("RectLTWH",("left","top","width","height"))): """ Represents a rectangle on the screen, based on left and top coordinates, width and height. diff --git a/tests/unit/test_locationHelper.py b/tests/unit/test_locationHelper.py index 0a71c04a98f..337494f73e5 100644 --- a/tests/unit/test_locationHelper.py +++ b/tests/unit/test_locationHelper.py @@ -12,26 +12,20 @@ class TestRectOperators(unittest.TestCase): - def test_and(self): - self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4) & RectLTRB(left=3,top=3,right=5,bottom=5),RectLTRB(left=3,top=3,right=4,bottom=4)) - self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4) & RectLTRB(left=5,top=5,right=7,bottom=7),RectLTRB(left=0,top=0,right=0,bottom=0)) + def test_intersection(self): + self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4).intersection(RectLTRB(left=3,top=3,right=5,bottom=5)), RectLTRB(left=3,top=3,right=4,bottom=4)) + self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4).intersection(RectLTRB(left=5,top=5,right=7,bottom=7)), RectLTRB(left=0,top=0,right=0,bottom=0)) - def test_gt(self): - self.assertGreater(RectLTRB(left=2,top=2,right=6,bottom=6),RectLTRB(left=2,top=2,right=4,bottom=4)) - self.assertGreaterEqual(RectLTRB(left=2,top=2,right=6,bottom=6),RectLTRB(left=2,top=2,right=4,bottom=4)) + def test_superset(self): + self.assertTrue(RectLTRB(left=2,top=2,right=6,bottom=6).isSuperset(RectLTRB(left=2,top=2,right=4,bottom=4))) - def test_lt(self): - self.assertLess(RectLTRB(left=2,top=2,right=4,bottom=4),RectLTRB(left=2,top=2,right=6,bottom=6)) - self.assertLessEqual(RectLTRB(left=2,top=2,right=4,bottom=4),RectLTRB(left=2,top=2,right=6,bottom=6)) + def test_subset(self): + self.assertTrue(RectLTRB(left=2,top=2,right=4,bottom=4).isSubset(RectLTRB(left=2,top=2,right=6,bottom=6))) def test_in(self): self.assertIn(RectLTRB(left=2,top=2,right=4,bottom=4),RectLTRB(left=2,top=2,right=6,bottom=6)) self.assertIn(Point(x=2,y=6),RectLTRB(left=2,top=2,right=6,bottom=6)) - def test_sub(self): - self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4) - RectLTRB(left=3,top=3,right=5,bottom=5),RectLTRB(left=-1,top=-1,right=-1,bottom=-1)) - self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4) - RectLTRB(left=5,top=5,right=8,bottom=8),RectLTRB(left=-3,top=-3,right=-4,bottom=0-4)) - class TestRectUtilities(unittest.TestCase): def test_center(self): From 070bb36338b52b8d286e48690c1a5065a9a75f05 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Mon, 8 Jan 2018 17:14:42 +0100 Subject: [PATCH 08/27] Rename Point operators --- source/locationHelper.py | 84 ++++++++++++++++++++++++------- tests/unit/test_locationHelper.py | 16 ++++-- 2 files changed, 77 insertions(+), 23 deletions(-) diff --git a/source/locationHelper.py b/source/locationHelper.py index 0502544dd03..ffb216ef528 100644 --- a/source/locationHelper.py +++ b/source/locationHelper.py @@ -36,12 +36,11 @@ def __sub__(self,other): def __rsub__(self,other): return self.__sub__(other) - def __lt__(self,other): + def yWiseLessThan(self,other): """ - Returns whether self is less than other. - This first compares y, then x coordinates. + Returns whether self is less than other, first comparing y, then x coordinates. For example: (x=4,y=3) < (x=3,y=4) because self.y is less than other.y. - To compare in opposite order (i.e. compare x, then y), use tuple(self) < tuple(other) + To compare in opposite order (i.e. compare x, then y), use L{xWiseLessThan} """ if not isinstance(other,POINT_CLASSES): try: @@ -50,12 +49,24 @@ def __lt__(self,other): return False return (self.y, self.x) < (other.y, other.x) - def __le__(self,other): + def xWiseLessThan(self,other): """ - Returns whether self is less than or equal to other. - This first compares y, then x coordinates. - For example: (x=4,y=3) < (x=3,y=4) because self.y is less than other.y. - To compare in opposite order (i.e. compare x, then y), use tuple(self) <= tuple(other) + Returns whether self is less than other, first comparing x, then y coordinates. + For example: (x=3,y=4) < (x=4,y=3) because self.x is less than other.x. + To compare in opposite order (i.e. compare y, then x), use L{yWiseLessThan} + """ + if not isinstance(other,POINT_CLASSES): + try: + other=toPoint(other) + except ValueError: + return False + return (self.x, self.y) < (other.x, other.y) + + def yWiseLessOrEq(self,other): + """ + Returns whether self is less than or equal to other, first comparing y, then x coordinates. + For example: (x=4,y=3) <= (x=3,y=4) because self.y is less than or equal to other.y. + To compare in opposite order (i.e. compare x, then y), use L{xWiseLessOrEq} """ if not isinstance(other,POINT_CLASSES): try: @@ -64,12 +75,24 @@ def __le__(self,other): return False return (self.y, self.x) <= (other.y, other.x) - def __gt__(self,other): + def xWiseLessOrEq(self,other): + """ + Returns whether self is less than or equal to other, first comparing x, then y coordinates. + For example: (x=3,y=4) <= (x=4,y=3) because self.x is less than or equal to other.x. + To compare in opposite order (i.e. compare y, then x), use L{yWiseLessOrEq} + """ + if not isinstance(other,POINT_CLASSES): + try: + other=toPoint(other) + except ValueError: + return False + return (self.x, self.y) <= (other.x, other.y) + + def yWiseGreaterThan(self,other): """ - Returns whether self is greater than other. - This first compares y, then x coordinates. + Returns whether self is greater than other, first comparing y, then x coordinates. For example: (x=3,y=4) > (x=4,y=3) because self.y is greater than other.y. - To compare in opposite order (i.e. compare x, then y), use tuple(self) > tuple(other) + To compare in opposite order (i.e. compare x, then y), use L{xWiseGreaterThan} """ if not isinstance(other,POINT_CLASSES): try: @@ -78,12 +101,24 @@ def __gt__(self,other): return False return (self.y, self.x) > (other.y, other.x) - def __ge__(self,other): + def xWiseGreaterThan(self,other): """ - Returns whether self is greater than or equal to other. - This first compares y, then x coordinates. - For example: (x=3,y=4) > (x=4,y=3) because self.y is greater than other.y. - To compare in opposite order (i.e. compare x, then y), use tuple(self) >= tuple(other) + Returns whether self is greater than other, first comparing x, then y coordinates. + For example: (x=4,y=3) > (x=3,y=4) because self.x is greater than other.x. + To compare in opposite order (i.e. compare y, then x), use L{yWiseGreaterThan} + """ + if not isinstance(other,POINT_CLASSES): + try: + other=toPoint(other) + except ValueError: + return False + return (self.x, self.y) > (other.x, other.y) + + def yWiseGreaterOrEq(self,other): + """ + Returns whether self is greater than or equal to other, first comparing y, then x coordinates. + For example: (x=3,y=4) >= (x=4,y=3) because self.y is greater than or equal to other.y. + To compare in opposite order (i.e. compare x, then y), use L{xWiseGreaterOrEq} """ if not isinstance(other,POINT_CLASSES): try: @@ -92,6 +127,19 @@ def __ge__(self,other): return False return (self.y, self.x) >= (other.y, other.x) + def xWiseGreaterOrEq(self,other): + """ + Returns whether self is greater than or equal to other, first comparing x, then y coordinates. + For example: (x=4,y=3) >= (x=3,y=4) because self.x is greater than or equal to other.x. + To compare in opposite order (i.e. compare y, then x), use L{yWiseGreaterOrEq} + """ + if not isinstance(other,POINT_CLASSES): + try: + other=toPoint(other) + except ValueError: + return False + return (self.x, self.y) >= (other.x, other.y) + def __eq__(self,other): if not isinstance(other,POINT_CLASSES): try: diff --git a/tests/unit/test_locationHelper.py b/tests/unit/test_locationHelper.py index 337494f73e5..faf9faf51c6 100644 --- a/tests/unit/test_locationHelper.py +++ b/tests/unit/test_locationHelper.py @@ -79,10 +79,16 @@ def test_sum(self): def test_sub(self): self.assertEqual(Point(x=2,y=4)-Point(x=4,y=8),Point(x=-2,y=-4)) - def test_gt(self): - self.assertGreater(Point(x=3,y=4), Point(x=4,y=3)) - - def test_lt(self): - self.assertLess(Point(x=4,y=3), Point(x=3,y=4)) + def test_greaterThan(self): + self.assertTrue(Point(x=3,y=4).yWiseGreaterThan(Point(x=4,y=3))) + self.assertTrue(Point(x=4,y=3).xWiseGreaterThan(Point(x=3,y=4))) + self.assertTrue(Point(x=3,y=4).yWiseGreaterOrEq(Point(x=4,y=3))) + self.assertTrue(Point(x=4,y=3).xWiseGreaterOrEq(Point(x=3,y=4))) + + def test_lessThan(self): + self.assertTrue(Point(x=4,y=3).yWiseLessThan(Point(x=3,y=4))) + self.assertTrue(Point(x=3,y=4).xWiseLessThan(Point(x=4,y=3))) + self.assertTrue(Point(x=4,y=3).yWiseLessOrEq(Point(x=3,y=4))) + self.assertTrue(Point(x=3,y=4).xWiseLessOrEq(Point(x=4,y=3))) From c2e29fd1a15ede9d59735c976667051f1493df78 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Tue, 9 Jan 2018 11:34:23 +0100 Subject: [PATCH 09/27] Added many more tests, fixed operator overload (__neq__ should be __ne__) --- source/locationHelper.py | 30 ++++++++++++---- tests/unit/test_locationHelper.py | 57 ++++++++++++++++++++++++++----- 2 files changed, 73 insertions(+), 14 deletions(-) diff --git a/source/locationHelper.py b/source/locationHelper.py index ffb216ef528..1421553ad24 100644 --- a/source/locationHelper.py +++ b/source/locationHelper.py @@ -146,10 +146,15 @@ def __eq__(self,other): other=toPoint(other) except ValueError: return False - return self.x==other.x and self.y==other.y + return self.x == other.x and self.y == other.y - def __neq__(self,other): - return not (self==other) + def __ne__(self,other): + if not isinstance(other,POINT_CLASSES): + try: + other=toPoint(other) + except ValueError: + return True + return self.x != other.x or self.y != other.y def toPOINT(self): """Converts self to a L{ctypes.wintypes.POINT}""" @@ -208,6 +213,14 @@ def toScreen(self, hwnd): def topLeft(self): return Point(self.left,self.top) + @property + def topRight(self): + return Point(self.right,self.top) + + @property + def bottomLeft(self): + return Point(self.left,self.bottom) + @property def bottomRight(self): return Point(self.right,self.bottom) @@ -219,7 +232,7 @@ def center(self): def __contains__(self,other): """Returns whether other is a part of this rectangle.""" if isinstance(other,POINT_CLASSES): - return other.x >= self.left < self.right and other.y >= self.top < self.bottom + return self.left <= other.x < self.right and self.top <= other.y < self.bottom if not isinstance(other,RECT_CLASSES): try: other=toRectLTRB(other) @@ -253,8 +266,13 @@ def __eq__(self,other): return False return other.left == self.left and other.top == self.top and other.right == self.right and other.bottom == self.bottom - def __neq__(self,other): - return not (self==other) + def __ne__(self,other): + if not isinstance(other,RECT_CLASSES): + try: + other=toRectLTRB(other) + except ValueError: + return True + return other.left != self.left or other.top != self.top or other.right != self.right or other.bottom != self.bottom def intersection(self,other): """Returns the intersection of self and other. diff --git a/tests/unit/test_locationHelper.py b/tests/unit/test_locationHelper.py index faf9faf51c6..240c5eb27ee 100644 --- a/tests/unit/test_locationHelper.py +++ b/tests/unit/test_locationHelper.py @@ -9,6 +9,7 @@ import unittest from locationHelper import * +from ctypes.wintypes import RECT, POINT class TestRectOperators(unittest.TestCase): @@ -18,18 +19,36 @@ def test_intersection(self): def test_superset(self): self.assertTrue(RectLTRB(left=2,top=2,right=6,bottom=6).isSuperset(RectLTRB(left=2,top=2,right=4,bottom=4))) + self.assertTrue(RectLTRB(left=2,top=2,right=6,bottom=6).isSuperset(RectLTRB(left=2,top=2,right=6,bottom=6))) def test_subset(self): self.assertTrue(RectLTRB(left=2,top=2,right=4,bottom=4).isSubset(RectLTRB(left=2,top=2,right=6,bottom=6))) + self.assertTrue(RectLTRB(left=2,top=2,right=6,bottom=6).isSubset(RectLTRB(left=2,top=2,right=6,bottom=6))) + self.assertFalse(RectLTRB(left=2,top=2,right=6,bottom=6).isSubset(RectLTRB(left=2,top=2,right=4,bottom=4))) def test_in(self): - self.assertIn(RectLTRB(left=2,top=2,right=4,bottom=4),RectLTRB(left=2,top=2,right=6,bottom=6)) - self.assertIn(Point(x=2,y=6),RectLTRB(left=2,top=2,right=6,bottom=6)) + self.assertIn(RectLTRB(left=2,top=2,right=4,bottom=4), RectLTRB(left=2,top=2,right=6,bottom=6)) + self.assertNotIn(RectLTRB(left=2,top=2,right=4,bottom=4), RectLTRB(left=2,top=2,right=4,bottom=4)) + self.assertIn(Point(x=2,y=2), RectLTRB(left=2,top=2,right=6,bottom=6)) + self.assertIn(Point(x=4,y=4), RectLTRB(left=2,top=2,right=6,bottom=6)) + self.assertNotIn(Point(x=2,y=6), RectLTRB(left=2,top=2,right=6,bottom=6)) + self.assertNotIn(Point(x=6,y=6), RectLTRB(left=2,top=2,right=6,bottom=6)) + + def test_equal(self): + self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4), RectLTRB(left=2,top=2,right=4,bottom=4)) + self.assertNotEqual(RectLTRB(left=2,top=2,right=4,bottom=4), RectLTRB(left=2,top=2,right=6,bottom=6)) + self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4), RectLTWH(left=2,top=2,width=2,height=2)) + self.assertNotEqual(RectLTRB(left=2,top=2,right=4,bottom=4), RectLTWH(left=2,top=2,width=4,height=4)) class TestRectUtilities(unittest.TestCase): - def test_center(self): - self.assertEqual(RectLTRB(left=-5,top=-5,right=5,bottom=5).center, Point(x=0,y=0)) + def test_points(self): + rect = RectLTRB(left=-5,top=-5,right=5,bottom=5) + self.assertEqual(rect.center, Point(x=0,y=0)) + self.assertEqual(rect.topLeft, Point(x=-5,y=-5)) + self.assertEqual(rect.topRight, Point(x=5,y=-5)) + self.assertEqual(rect.bottomLeft, Point(x=-5,y=5)) + self.assertEqual(rect.bottomRight, Point(x=5,y=5)) class TestToRectLTRB(unittest.TestCase): @@ -43,11 +62,16 @@ def test_collection(self): Point(20,20), Point(50,50), Point(400,400), - RectLTRB(left=450,top=450,right=490,bottom=990) + POINT(15,15), + POINT(20,20), + POINT(50,50), + POINT(400,400), + RectLTRB(left=450,top=450,right=490,bottom=990), + RECT(450,450,490,990) ), rect) def test_integers(self): - self.assertEqual(RectLTRB(left=10,top=10,right=20,bottom=20),toRectLTRB(10,10,20,20)) + self.assertEqual(RectLTRB(left=10,top=10,right=20,bottom=20), toRectLTRB(10,10,20,20)) class TestToRectLTWH(unittest.TestCase): @@ -61,7 +85,12 @@ def test_collection(self): Point(20,20), Point(50,50), Point(400,400), - RectLTRB(left=450,top=450,right=505,bottom=1010) + POINT(15,15), + POINT(20,20), + POINT(50,50), + POINT(400,400), + RectLTRB(left=450,top=450,right=505,bottom=1010), + RECT(450,450,490,990) ), location) def test_integers(self): @@ -81,14 +110,26 @@ def test_sub(self): def test_greaterThan(self): self.assertTrue(Point(x=3,y=4).yWiseGreaterThan(Point(x=4,y=3))) + self.assertFalse(Point(x=3,y=4).xWiseGreaterThan(Point(x=4,y=3))) self.assertTrue(Point(x=4,y=3).xWiseGreaterThan(Point(x=3,y=4))) + self.assertFalse(Point(x=4,y=3).yWiseGreaterThan(Point(x=3,y=4))) self.assertTrue(Point(x=3,y=4).yWiseGreaterOrEq(Point(x=4,y=3))) + self.assertFalse(Point(x=3,y=4).xWiseGreaterOrEq(Point(x=4,y=3))) self.assertTrue(Point(x=4,y=3).xWiseGreaterOrEq(Point(x=3,y=4))) + self.assertFalse(Point(x=4,y=3).yWiseGreaterOrEq(Point(x=3,y=4))) def test_lessThan(self): self.assertTrue(Point(x=4,y=3).yWiseLessThan(Point(x=3,y=4))) + self.assertFalse(Point(x=4,y=3).xWiseLessThan(Point(x=3,y=4))) self.assertTrue(Point(x=3,y=4).xWiseLessThan(Point(x=4,y=3))) + self.assertFalse(Point(x=3,y=4).yWiseLessThan(Point(x=4,y=3))) self.assertTrue(Point(x=4,y=3).yWiseLessOrEq(Point(x=3,y=4))) + self.assertFalse(Point(x=4,y=3).xWiseLessOrEq(Point(x=3,y=4))) self.assertTrue(Point(x=3,y=4).xWiseLessOrEq(Point(x=4,y=3))) + self.assertFalse(Point(x=3,y=4).yWiseLessOrEq(Point(x=4,y=3))) - + def test_equal(self): + self.assertEqual(Point(x=4,y=3), Point(x=4,y=3)) + self.assertEqual(POINT(x=4,y=3), Point(x=4,y=3)) + self.assertNotEqual(Point(x=3,y=4), Point(x=4,y=3)) + self.assertNotEqual(POINT(x=3,y=4), Point(x=4,y=3)) From 2c9ef2460dcb30cf88f729ef1cc1ed8845ad13a5 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Tue, 9 Jan 2018 12:10:44 +0100 Subject: [PATCH 10/27] More unit tests for ctypes, fix __rsub__ for Points --- source/locationHelper.py | 8 +++- tests/unit/test_locationHelper.py | 63 ++++++++++++++++++++++++++----- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/source/locationHelper.py b/source/locationHelper.py index 1421553ad24..32973fa98ae 100644 --- a/source/locationHelper.py +++ b/source/locationHelper.py @@ -26,7 +26,9 @@ def __radd__(self,other): """Returns a new L{Point} with x = self.x + other.x and y = self.y + other.y.""" if other == 0: return self - return self.__add__(other) + elif not isinstance(other,POINT_CLASSES): + return NotImplemented + return Point((other.x+self.x),(other.y+self.y)) def __sub__(self,other): if not isinstance(other,POINT_CLASSES): @@ -34,7 +36,9 @@ def __sub__(self,other): return Point((self.x-other.x),(self.y-other.y)) def __rsub__(self,other): - return self.__sub__(other) + if not isinstance(other,POINT_CLASSES): + return NotImplemented + return Point((other.x-self.x),(other.y-self.y)) def yWiseLessThan(self,other): """ diff --git a/tests/unit/test_locationHelper.py b/tests/unit/test_locationHelper.py index 240c5eb27ee..6e229c6e4c6 100644 --- a/tests/unit/test_locationHelper.py +++ b/tests/unit/test_locationHelper.py @@ -40,6 +40,20 @@ def test_equal(self): self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4), RectLTWH(left=2,top=2,width=2,height=2)) self.assertNotEqual(RectLTRB(left=2,top=2,right=4,bottom=4), RectLTWH(left=2,top=2,width=4,height=4)) + def test_ctypesRECT(self): + # Intersection + self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4).intersection(RECT(left=3,top=3,right=5,bottom=5)), RectLTRB(left=3,top=3,right=4,bottom=4)) + # Superset + self.assertTrue(RectLTRB(left=2,top=2,right=6,bottom=6).isSuperset(RECT(left=2,top=2,right=4,bottom=4))) + # Subset + self.assertTrue(RectLTRB(left=2,top=2,right=4,bottom=4).isSubset(RECT(left=2,top=2,right=6,bottom=6))) + # in + self.assertIn(RECT(left=2,top=2,right=4,bottom=4), RectLTRB(left=2,top=2,right=6,bottom=6)) + self.assertNotIn(RECT(left=2,top=2,right=4,bottom=4), RectLTRB(left=2,top=2,right=4,bottom=4)) + # Equality + self.assertEqual(RECT(left=2,top=2,right=4,bottom=4), RectLTRB(left=2,top=2,right=4,bottom=4)) + self.assertNotEqual(RECT(left=2,top=2,right=4,bottom=4), RectLTRB(left=2,top=2,right=6,bottom=6)) + class TestRectUtilities(unittest.TestCase): def test_points(self): @@ -62,10 +76,10 @@ def test_collection(self): Point(20,20), Point(50,50), Point(400,400), - POINT(15,15), - POINT(20,20), - POINT(50,50), - POINT(400,400), + POINT(x=15,y=15), + POINT(x=20,y=20), + POINT(x=50,y=50), + POINT(x=400,y=400), RectLTRB(left=450,top=450,right=490,bottom=990), RECT(450,450,490,990) ), rect) @@ -85,10 +99,10 @@ def test_collection(self): Point(20,20), Point(50,50), Point(400,400), - POINT(15,15), - POINT(20,20), - POINT(50,50), - POINT(400,400), + POINT(x=15,y=15), + POINT(x=20,y=20), + POINT(x=50,y=50), + POINT(x=400,y=400), RectLTRB(left=450,top=450,right=505,bottom=1010), RECT(450,450,490,990) ), location) @@ -130,6 +144,35 @@ def test_lessThan(self): def test_equal(self): self.assertEqual(Point(x=4,y=3), Point(x=4,y=3)) - self.assertEqual(POINT(x=4,y=3), Point(x=4,y=3)) self.assertNotEqual(Point(x=3,y=4), Point(x=4,y=3)) - self.assertNotEqual(POINT(x=3,y=4), Point(x=4,y=3)) + + def test_ctypesPOINT(self): + # Add + self.assertEqual(Point(x=2,y=4)+POINT(x=2,y=4),Point(x=4,y=8)) + self.assertEqual(POINT(x=2,y=4)+Point(x=2,y=4),Point(x=4,y=8)) + # Sum + self.assertEqual(sum((Point(x=2,y=4), POINT(x=2,y=4), Point(x=2,y=4))), Point(x=6,y=12)) + # Subtract + self.assertEqual(Point(x=2,y=4)-POINT(x=4,y=8),Point(x=-2,y=-4)) + self.assertEqual(POINT(x=2,y=4)-Point(x=4,y=8),Point(x=-2,y=-4)) + # Greater than + self.assertTrue(Point(x=3,y=4).yWiseGreaterThan(POINT(x=4,y=3))) + self.assertFalse(Point(x=3,y=4).xWiseGreaterThan(POINT(x=4,y=3))) + self.assertTrue(Point(x=4,y=3).xWiseGreaterThan(POINT(x=3,y=4))) + self.assertFalse(Point(x=4,y=3).yWiseGreaterThan(POINT(x=3,y=4))) + self.assertTrue(Point(x=3,y=4).yWiseGreaterOrEq(POINT(x=4,y=3))) + self.assertFalse(Point(x=3,y=4).xWiseGreaterOrEq(POINT(x=4,y=3))) + self.assertTrue(Point(x=4,y=3).xWiseGreaterOrEq(POINT(x=3,y=4))) + self.assertFalse(Point(x=4,y=3).yWiseGreaterOrEq(POINT(x=3,y=4))) + # Less than + self.assertTrue(Point(x=4,y=3).yWiseLessThan(POINT(x=3,y=4))) + self.assertFalse(Point(x=4,y=3).xWiseLessThan(POINT(x=3,y=4))) + self.assertTrue(Point(x=3,y=4).xWiseLessThan(POINT(x=4,y=3))) + self.assertFalse(Point(x=3,y=4).yWiseLessThan(POINT(x=4,y=3))) + self.assertTrue(Point(x=4,y=3).yWiseLessOrEq(POINT(x=3,y=4))) + self.assertFalse(Point(x=4,y=3).xWiseLessOrEq(POINT(x=3,y=4))) + self.assertTrue(Point(x=3,y=4).xWiseLessOrEq(POINT(x=4,y=3))) + self.assertFalse(Point(x=3,y=4).yWiseLessOrEq(POINT(x=4,y=3))) + # Equality + self.assertEqual(POINT(x=4,y=3), Point(x=4,y=3)) + self.assertNotEqual(POINT(x=3,y=4), Point(x=4,y=3)) \ No newline at end of file From 3dbfc75d070ac8e754a6fb0ca246403aac5eca69 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Tue, 9 Jan 2018 12:29:12 +0100 Subject: [PATCH 11/27] Fix for getting a center point for a rectangle, including proper rounding according to mathematical rules --- source/locationHelper.py | 2 +- tests/unit/test_locationHelper.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/source/locationHelper.py b/source/locationHelper.py index 32973fa98ae..50a39b25110 100644 --- a/source/locationHelper.py +++ b/source/locationHelper.py @@ -231,7 +231,7 @@ def bottomRight(self): @property def center(self): - return Point((self.left+self.right)/2, (self.top+self.bottom)/2) + return Point(int(round(self.left+self.width/2.0)), int(round(self.top+self.height/2.0))) def __contains__(self,other): """Returns whether other is a part of this rectangle.""" diff --git a/tests/unit/test_locationHelper.py b/tests/unit/test_locationHelper.py index 6e229c6e4c6..0c234941cf7 100644 --- a/tests/unit/test_locationHelper.py +++ b/tests/unit/test_locationHelper.py @@ -58,11 +58,16 @@ class TestRectUtilities(unittest.TestCase): def test_points(self): rect = RectLTRB(left=-5,top=-5,right=5,bottom=5) - self.assertEqual(rect.center, Point(x=0,y=0)) self.assertEqual(rect.topLeft, Point(x=-5,y=-5)) self.assertEqual(rect.topRight, Point(x=5,y=-5)) self.assertEqual(rect.bottomLeft, Point(x=-5,y=5)) self.assertEqual(rect.bottomRight, Point(x=5,y=5)) + self.assertEqual(rect.center, Point(x=0,y=0)) + # Specifically test some other edge cases for center + self.assertEqual(RectLTRB(left=10,top=10,right=20,bottom=20).center, Point(x=15,y=15)) + self.assertEqual(RectLTRB(left=-20,top=-20,right=-10,bottom=-10).center, Point(x=-15,y=-15)) + self.assertEqual(RectLTRB(left=10,top=10,right=21,bottom=21).center, Point(x=16,y=16)) + self.assertEqual(RectLTRB(left=-21,top=-21,right=-10,bottom=-10).center, Point(x=-16,y=-16)) class TestToRectLTRB(unittest.TestCase): From 81d3f987908167d77578fc6d705763c63acba9a1 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Tue, 9 Jan 2018 12:40:25 +0100 Subject: [PATCH 12/27] Last edge case tests --- tests/unit/test_locationHelper.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/unit/test_locationHelper.py b/tests/unit/test_locationHelper.py index 0c234941cf7..e5d13b59a66 100644 --- a/tests/unit/test_locationHelper.py +++ b/tests/unit/test_locationHelper.py @@ -19,20 +19,23 @@ def test_intersection(self): def test_superset(self): self.assertTrue(RectLTRB(left=2,top=2,right=6,bottom=6).isSuperset(RectLTRB(left=2,top=2,right=4,bottom=4))) + self.assertFalse(RectLTRB(left=2,top=2,right=4,bottom=4).isSuperset(RectLTRB(left=2,top=2,right=6,bottom=6))) self.assertTrue(RectLTRB(left=2,top=2,right=6,bottom=6).isSuperset(RectLTRB(left=2,top=2,right=6,bottom=6))) def test_subset(self): self.assertTrue(RectLTRB(left=2,top=2,right=4,bottom=4).isSubset(RectLTRB(left=2,top=2,right=6,bottom=6))) - self.assertTrue(RectLTRB(left=2,top=2,right=6,bottom=6).isSubset(RectLTRB(left=2,top=2,right=6,bottom=6))) self.assertFalse(RectLTRB(left=2,top=2,right=6,bottom=6).isSubset(RectLTRB(left=2,top=2,right=4,bottom=4))) + self.assertTrue(RectLTRB(left=2,top=2,right=6,bottom=6).isSubset(RectLTRB(left=2,top=2,right=6,bottom=6))) def test_in(self): - self.assertIn(RectLTRB(left=2,top=2,right=4,bottom=4), RectLTRB(left=2,top=2,right=6,bottom=6)) - self.assertNotIn(RectLTRB(left=2,top=2,right=4,bottom=4), RectLTRB(left=2,top=2,right=4,bottom=4)) - self.assertIn(Point(x=2,y=2), RectLTRB(left=2,top=2,right=6,bottom=6)) - self.assertIn(Point(x=4,y=4), RectLTRB(left=2,top=2,right=6,bottom=6)) - self.assertNotIn(Point(x=2,y=6), RectLTRB(left=2,top=2,right=6,bottom=6)) - self.assertNotIn(Point(x=6,y=6), RectLTRB(left=2,top=2,right=6,bottom=6)) + rect = RectLTRB(left=2,top=2,right=6,bottom=6) + self.assertIn(RectLTRB(left=2,top=2,right=4,bottom=4), rect) + self.assertNotIn(rect, rect) + self.assertIn(Point(x=2,y=2), rect) + self.assertIn(Point(x=4,y=4), rect) + self.assertNotIn(Point(x=2,y=6), rect) + self.assertNotIn(Point(x=6,y=2), rect) + self.assertNotIn(Point(x=6,y=6), rect) def test_equal(self): self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4), RectLTRB(left=2,top=2,right=4,bottom=4)) From 0ba335a097e75ff8ddc9600abb0b967bc6cabb78 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Wed, 10 Jan 2018 12:14:45 +0100 Subject: [PATCH 13/27] Another implementation for subset and superset, doc string for collection test --- source/locationHelper.py | 4 ++-- tests/unit/test_locationHelper.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/source/locationHelper.py b/source/locationHelper.py index 50a39b25110..aafce2fe682 100644 --- a/source/locationHelper.py +++ b/source/locationHelper.py @@ -251,7 +251,7 @@ def isSubset(self,other): other=toRectLTRB(other) except ValueError: return False - return self.left >= other.left and self.top >= other.top and self.right <= other.right and self.bottom <= other.bottom + return other.left<=self.left<=self.right<=other.right and other.top<=self.top<=self.bottom<=other.bottom def isSuperset(self,other): """Returns whether this rectangle is a superset of other (i.e. whether all points of other are contained by this rectangle).""" @@ -260,7 +260,7 @@ def isSuperset(self,other): other=toRectLTRB(other) except ValueError: return False - return other.left >= self.left and other.top >= self.top and other.right <= self.right and other.bottom <= self.bottom + return self.left<=other.left<=other.right<=self.right and self.top<=other.top<=other.bottom<=self.bottom def __eq__(self,other): if not isinstance(other,RECT_CLASSES): diff --git a/tests/unit/test_locationHelper.py b/tests/unit/test_locationHelper.py index e5d13b59a66..d6960cbbf44 100644 --- a/tests/unit/test_locationHelper.py +++ b/tests/unit/test_locationHelper.py @@ -75,6 +75,7 @@ def test_points(self): class TestToRectLTRB(unittest.TestCase): def test_collection(self): + """Tests whether a collection of several rectangle and point types convert to the expected L{RectLTRB}.""" rect=RectLTRB(left=10,top=15,right=500,bottom=1000) self.assertEqual(toRectLTRB( rect.topLeft, @@ -98,6 +99,7 @@ def test_integers(self): class TestToRectLTWH(unittest.TestCase): def test_collection(self): + """Tests whether a collection of several rectangle and point types convert to the expected L{RectLTWH}.""" location=RectLTWH(left=10,top=15,width=500,height=1000) self.assertEqual(toRectLTWH( location.topLeft, From e49888c4fc4fb3baef169b6a0a86aeb9a87dd7a7 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Wed, 10 Jan 2018 12:15:31 +0100 Subject: [PATCH 14/27] Remove wx.Rect from supported classes --- source/locationHelper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/locationHelper.py b/source/locationHelper.py index aafce2fe682..f3bc1aa9c46 100644 --- a/source/locationHelper.py +++ b/source/locationHelper.py @@ -434,4 +434,4 @@ def toPoint(*params): POINT_CLASSES=(Point, POINT, textInfos.Point, wx.Point) #: Classes which support conversion to locationHelper RectLTRB/LTWH using their left, top, right and bottom properties. #: type: tuple -RECT_CLASSES=(RectLTRB, RectLTWH, RECT, SMALL_RECT, textInfos.Rect, wx.Rect) +RECT_CLASSES=(RectLTRB, RectLTWH, RECT, SMALL_RECT, textInfos.Rect) From c6d147fec85b4964c3619f66f40374aa850f9e98 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Tue, 3 Apr 2018 16:05:24 +0200 Subject: [PATCH 15/27] Raise ValueError when left>right and top>bottom for RectLTRB --- source/locationHelper.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/source/locationHelper.py b/source/locationHelper.py index f3bc1aa9c46..d4bee6f1eee 100644 --- a/source/locationHelper.py +++ b/source/locationHelper.py @@ -322,6 +322,13 @@ class RectLTRB(_RectMixin, namedtuple("RectLTRB",("left","top","right","bottom") To represent a rectangle based on width and height instead, use L{RectLTWH}. """ + def __new__(cls, left, top, right, bottom): + if left>right: + raise ValueError("left=%d is greather than right=%d, which is not allowed"%(left,right)) + if top>bottom: + raise ValueError("top=%d is greather than bottom=%d, which is not allowed"%(top,bottom)) + return super(RectLTRB, cls).__new__(cls, left, top, right, bottom) + @property def width(self): return self.right-self.left From 7f2cb6ff4b0161dd185291fd98bf24c0609391cc Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Tue, 3 Apr 2018 16:15:10 +0200 Subject: [PATCH 16/27] Add additional tests, improve code style for tests --- tests/unit/test_locationHelper.py | 213 +++++++++++++++--------------- 1 file changed, 108 insertions(+), 105 deletions(-) diff --git a/tests/unit/test_locationHelper.py b/tests/unit/test_locationHelper.py index d6960cbbf44..90971008a15 100644 --- a/tests/unit/test_locationHelper.py +++ b/tests/unit/test_locationHelper.py @@ -14,175 +14,178 @@ class TestRectOperators(unittest.TestCase): def test_intersection(self): - self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4).intersection(RectLTRB(left=3,top=3,right=5,bottom=5)), RectLTRB(left=3,top=3,right=4,bottom=4)) - self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4).intersection(RectLTRB(left=5,top=5,right=7,bottom=7)), RectLTRB(left=0,top=0,right=0,bottom=0)) + self.assertEqual(RectLTRB(left=2, top=2, right=4, bottom=4).intersection(RectLTRB(left=3, top=3, right=5, bottom=5)), RectLTRB(left=3, top=3, right=4, bottom=4)) + self.assertEqual(RectLTRB(left=2, top=2, right=4, bottom=4).intersection(RectLTRB(left=5, top=5, right=7, bottom=7)), RectLTRB(left=0, top=0, right=0, bottom=0)) def test_superset(self): - self.assertTrue(RectLTRB(left=2,top=2,right=6,bottom=6).isSuperset(RectLTRB(left=2,top=2,right=4,bottom=4))) - self.assertFalse(RectLTRB(left=2,top=2,right=4,bottom=4).isSuperset(RectLTRB(left=2,top=2,right=6,bottom=6))) - self.assertTrue(RectLTRB(left=2,top=2,right=6,bottom=6).isSuperset(RectLTRB(left=2,top=2,right=6,bottom=6))) + self.assertTrue(RectLTRB(left=2, top=2, right=6, bottom=6).isSuperset(RectLTRB(left=2, top=2, right=4, bottom=4))) + self.assertFalse(RectLTRB(left=2, top=2, right=4, bottom=4).isSuperset(RectLTRB(left=2, top=2, right=6, bottom=6))) + self.assertTrue(RectLTRB(left=2, top=2, right=6, bottom=6).isSuperset(RectLTRB(left=2, top=2, right=6, bottom=6))) def test_subset(self): - self.assertTrue(RectLTRB(left=2,top=2,right=4,bottom=4).isSubset(RectLTRB(left=2,top=2,right=6,bottom=6))) - self.assertFalse(RectLTRB(left=2,top=2,right=6,bottom=6).isSubset(RectLTRB(left=2,top=2,right=4,bottom=4))) - self.assertTrue(RectLTRB(left=2,top=2,right=6,bottom=6).isSubset(RectLTRB(left=2,top=2,right=6,bottom=6))) + self.assertTrue(RectLTRB(left=2, top=2, right=4, bottom=4).isSubset(RectLTRB(left=2, top=2, right=6, bottom=6))) + self.assertFalse(RectLTRB(left=2, top=2, right=6, bottom=6).isSubset(RectLTRB(left=2, top=2, right=4, bottom=4))) + self.assertTrue(RectLTRB(left=2, top=2, right=6, bottom=6).isSubset(RectLTRB(left=2, top=2, right=6, bottom=6))) def test_in(self): - rect = RectLTRB(left=2,top=2,right=6,bottom=6) - self.assertIn(RectLTRB(left=2,top=2,right=4,bottom=4), rect) + rect = RectLTRB(left=2, top=2, right=6, bottom=6) + self.assertIn(RectLTRB(left=2, top=2, right=4, bottom=4), rect) self.assertNotIn(rect, rect) - self.assertIn(Point(x=2,y=2), rect) - self.assertIn(Point(x=4,y=4), rect) - self.assertNotIn(Point(x=2,y=6), rect) - self.assertNotIn(Point(x=6,y=2), rect) - self.assertNotIn(Point(x=6,y=6), rect) + self.assertIn(Point(x=2, y=2), rect) + self.assertIn(Point(x=4, y=4), rect) + self.assertNotIn(Point(x=2, y=6), rect) + self.assertNotIn(Point(x=6, y=2), rect) + self.assertNotIn(Point(x=6, y=6), rect) def test_equal(self): - self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4), RectLTRB(left=2,top=2,right=4,bottom=4)) - self.assertNotEqual(RectLTRB(left=2,top=2,right=4,bottom=4), RectLTRB(left=2,top=2,right=6,bottom=6)) - self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4), RectLTWH(left=2,top=2,width=2,height=2)) - self.assertNotEqual(RectLTRB(left=2,top=2,right=4,bottom=4), RectLTWH(left=2,top=2,width=4,height=4)) + self.assertEqual(RectLTRB(left=2, top=2, right=4, bottom=4), RectLTRB(left=2, top=2, right=4, bottom=4)) + self.assertNotEqual(RectLTRB(left=2, top=2, right=4, bottom=4), RectLTRB(left=2, top=2, right=6, bottom=6)) + self.assertEqual(RectLTRB(left=2, top=2, right=4, bottom=4), RectLTWH(left=2, top=2, width=2, height=2)) + self.assertNotEqual(RectLTRB(left=2, top=2, right=4, bottom=4), RectLTWH(left=2, top=2, width=4, height=4)) def test_ctypesRECT(self): # Intersection - self.assertEqual(RectLTRB(left=2,top=2,right=4,bottom=4).intersection(RECT(left=3,top=3,right=5,bottom=5)), RectLTRB(left=3,top=3,right=4,bottom=4)) + self.assertEqual(RectLTRB(left=2, top=2, right=4, bottom=4).intersection(RECT(left=3, top=3, right=5, bottom=5)), RectLTRB(left=3, top=3, right=4, bottom=4)) # Superset - self.assertTrue(RectLTRB(left=2,top=2,right=6,bottom=6).isSuperset(RECT(left=2,top=2,right=4,bottom=4))) + self.assertTrue(RectLTRB(left=2, top=2, right=6, bottom=6).isSuperset(RECT(left=2, top=2, right=4, bottom=4))) # Subset - self.assertTrue(RectLTRB(left=2,top=2,right=4,bottom=4).isSubset(RECT(left=2,top=2,right=6,bottom=6))) + self.assertTrue(RectLTRB(left=2, top=2, right=4, bottom=4).isSubset(RECT(left=2, top=2, right=6, bottom=6))) # in - self.assertIn(RECT(left=2,top=2,right=4,bottom=4), RectLTRB(left=2,top=2,right=6,bottom=6)) - self.assertNotIn(RECT(left=2,top=2,right=4,bottom=4), RectLTRB(left=2,top=2,right=4,bottom=4)) + self.assertIn(RECT(left=2, top=2, right=4, bottom=4), RectLTRB(left=2, top=2, right=6, bottom=6)) + self.assertNotIn(RECT(left=2, top=2, right=4, bottom=4), RectLTRB(left=2, top=2, right=4, bottom=4)) # Equality - self.assertEqual(RECT(left=2,top=2,right=4,bottom=4), RectLTRB(left=2,top=2,right=4,bottom=4)) - self.assertNotEqual(RECT(left=2,top=2,right=4,bottom=4), RectLTRB(left=2,top=2,right=6,bottom=6)) + self.assertEqual(RECT(left=2, top=2, right=4, bottom=4), RectLTRB(left=2, top=2, right=4, bottom=4)) + self.assertNotEqual(RECT(left=2, top=2, right=4, bottom=4), RectLTRB(left=2, top=2, right=6, bottom=6)) class TestRectUtilities(unittest.TestCase): def test_points(self): - rect = RectLTRB(left=-5,top=-5,right=5,bottom=5) - self.assertEqual(rect.topLeft, Point(x=-5,y=-5)) - self.assertEqual(rect.topRight, Point(x=5,y=-5)) - self.assertEqual(rect.bottomLeft, Point(x=-5,y=5)) - self.assertEqual(rect.bottomRight, Point(x=5,y=5)) - self.assertEqual(rect.center, Point(x=0,y=0)) + rect = RectLTRB(left=-5, top=-5, right=5, bottom=5) + self.assertEqual(rect.topLeft, Point(x=-5, y=-5)) + self.assertEqual(rect.topRight, Point(x=5, y=-5)) + self.assertEqual(rect.bottomLeft, Point(x=-5, y=5)) + self.assertEqual(rect.bottomRight, Point(x=5, y=5)) + self.assertEqual(rect.center, Point(x=0, y=0)) # Specifically test some other edge cases for center - self.assertEqual(RectLTRB(left=10,top=10,right=20,bottom=20).center, Point(x=15,y=15)) - self.assertEqual(RectLTRB(left=-20,top=-20,right=-10,bottom=-10).center, Point(x=-15,y=-15)) - self.assertEqual(RectLTRB(left=10,top=10,right=21,bottom=21).center, Point(x=16,y=16)) - self.assertEqual(RectLTRB(left=-21,top=-21,right=-10,bottom=-10).center, Point(x=-16,y=-16)) + self.assertEqual(RectLTRB(left=10, top=10, right=20, bottom=20).center, Point(x=15, y=15)) + self.assertEqual(RectLTRB(left=-20, top=-20, right=-10, bottom=-10).center, Point(x=-15, y=-15)) + self.assertEqual(RectLTRB(left=10, top=10, right=21, bottom=21).center, Point(x=16, y=16)) + self.assertEqual(RectLTRB(left=-21, top=-21, right=-10, bottom=-10).center, Point(x=-16, y=-16)) class TestToRectLTRB(unittest.TestCase): def test_collection(self): """Tests whether a collection of several rectangle and point types convert to the expected L{RectLTRB}.""" - rect=RectLTRB(left=10,top=15,right=500,bottom=1000) + rect=RectLTRB(left=10, top=15, right=500, bottom=1000) self.assertEqual(toRectLTRB( rect.topLeft, rect.bottomRight, rect.center, - Point(15,15), - Point(20,20), - Point(50,50), - Point(400,400), - POINT(x=15,y=15), - POINT(x=20,y=20), - POINT(x=50,y=50), - POINT(x=400,y=400), - RectLTRB(left=450,top=450,right=490,bottom=990), - RECT(450,450,490,990) + Point(15, 15), + Point(20, 20), + Point(50, 50), + Point(400, 400), + POINT(x=15, y=15), + POINT(x=20, y=20), + POINT(x=50, y=50), + POINT(x=400, y=400), + RectLTRB(left=450, top=450, right=490, bottom=990), + RECT(450, 450, 490, 990) ), rect) def test_integers(self): - self.assertEqual(RectLTRB(left=10,top=10,right=20,bottom=20), toRectLTRB(10,10,20,20)) + self.assertEqual(RectLTRB(left=10, top=10, right=20, bottom=20), toRectLTRB(10, 10, 20, 20)) + + def test_valueErrorForUnsuportedInput(self): + self.assertRaises(ValueError, RectLTRB, left=10, top=10, right=9, bottom=9) class TestToRectLTWH(unittest.TestCase): def test_collection(self): """Tests whether a collection of several rectangle and point types convert to the expected L{RectLTWH}.""" - location=RectLTWH(left=10,top=15,width=500,height=1000) + location=RectLTWH(left=10, top=15, width=500, height=1000) self.assertEqual(toRectLTWH( location.topLeft, location.bottomRight, location.center, - Point(15,15), - Point(20,20), - Point(50,50), - Point(400,400), - POINT(x=15,y=15), - POINT(x=20,y=20), - POINT(x=50,y=50), - POINT(x=400,y=400), - RectLTRB(left=450,top=450,right=505,bottom=1010), - RECT(450,450,490,990) + Point(15, 15), + Point(20, 20), + Point(50, 50), + Point(400, 400), + POINT(x=15, y=15), + POINT(x=20, y=20), + POINT(x=50, y=50), + POINT(x=400, y=400), + RectLTRB(left=450, top=450, right=505, bottom=1010), + RECT(450, 450, 490, 990) ), location) def test_integers(self): - self.assertEqual(RectLTWH(left=10,top=10,width=20,height=20),toRectLTWH(10,10,20,20)) + self.assertEqual(RectLTWH(left=10, top=10, width=20, height=20),toRectLTWH(10, 10, 20, 20)) class TestPointOperators(unittest.TestCase): def test_add(self): - self.assertEqual(Point(x=2,y=4)+Point(x=2,y=4),Point(x=4,y=8)) + self.assertEqual(Point(x=2, y=4)+Point(x=2, y=4),Point(x=4, y=8)) def test_sum(self): - point=Point(x=2,y=4) - self.assertEqual(sum((point, point, point)), Point(x=6,y=12)) + point=Point(x=2, y=4) + self.assertEqual(sum((point, point, point)), Point(x=6, y=12)) def test_sub(self): - self.assertEqual(Point(x=2,y=4)-Point(x=4,y=8),Point(x=-2,y=-4)) + self.assertEqual(Point(x=2, y=4)-Point(x=4, y=8),Point(x=-2, y=-4)) def test_greaterThan(self): - self.assertTrue(Point(x=3,y=4).yWiseGreaterThan(Point(x=4,y=3))) - self.assertFalse(Point(x=3,y=4).xWiseGreaterThan(Point(x=4,y=3))) - self.assertTrue(Point(x=4,y=3).xWiseGreaterThan(Point(x=3,y=4))) - self.assertFalse(Point(x=4,y=3).yWiseGreaterThan(Point(x=3,y=4))) - self.assertTrue(Point(x=3,y=4).yWiseGreaterOrEq(Point(x=4,y=3))) - self.assertFalse(Point(x=3,y=4).xWiseGreaterOrEq(Point(x=4,y=3))) - self.assertTrue(Point(x=4,y=3).xWiseGreaterOrEq(Point(x=3,y=4))) - self.assertFalse(Point(x=4,y=3).yWiseGreaterOrEq(Point(x=3,y=4))) + self.assertTrue(Point(x=3, y=4).yWiseGreaterThan(Point(x=4, y=3))) + self.assertFalse(Point(x=3, y=4).xWiseGreaterThan(Point(x=4, y=3))) + self.assertTrue(Point(x=4, y=3).xWiseGreaterThan(Point(x=3, y=4))) + self.assertFalse(Point(x=4, y=3).yWiseGreaterThan(Point(x=3, y=4))) + self.assertTrue(Point(x=3, y=4).yWiseGreaterOrEq(Point(x=4, y=3))) + self.assertFalse(Point(x=3, y=4).xWiseGreaterOrEq(Point(x=4, y=3))) + self.assertTrue(Point(x=4, y=3).xWiseGreaterOrEq(Point(x=3, y=4))) + self.assertFalse(Point(x=4, y=3).yWiseGreaterOrEq(Point(x=3, y=4))) def test_lessThan(self): - self.assertTrue(Point(x=4,y=3).yWiseLessThan(Point(x=3,y=4))) - self.assertFalse(Point(x=4,y=3).xWiseLessThan(Point(x=3,y=4))) - self.assertTrue(Point(x=3,y=4).xWiseLessThan(Point(x=4,y=3))) - self.assertFalse(Point(x=3,y=4).yWiseLessThan(Point(x=4,y=3))) - self.assertTrue(Point(x=4,y=3).yWiseLessOrEq(Point(x=3,y=4))) - self.assertFalse(Point(x=4,y=3).xWiseLessOrEq(Point(x=3,y=4))) - self.assertTrue(Point(x=3,y=4).xWiseLessOrEq(Point(x=4,y=3))) - self.assertFalse(Point(x=3,y=4).yWiseLessOrEq(Point(x=4,y=3))) + self.assertTrue(Point(x=4, y=3).yWiseLessThan(Point(x=3, y=4))) + self.assertFalse(Point(x=4, y=3).xWiseLessThan(Point(x=3, y=4))) + self.assertTrue(Point(x=3, y=4).xWiseLessThan(Point(x=4, y=3))) + self.assertFalse(Point(x=3, y=4).yWiseLessThan(Point(x=4, y=3))) + self.assertTrue(Point(x=4, y=3).yWiseLessOrEq(Point(x=3, y=4))) + self.assertFalse(Point(x=4, y=3).xWiseLessOrEq(Point(x=3, y=4))) + self.assertTrue(Point(x=3, y=4).xWiseLessOrEq(Point(x=4, y=3))) + self.assertFalse(Point(x=3, y=4).yWiseLessOrEq(Point(x=4, y=3))) def test_equal(self): - self.assertEqual(Point(x=4,y=3), Point(x=4,y=3)) - self.assertNotEqual(Point(x=3,y=4), Point(x=4,y=3)) + self.assertEqual(Point(x=4, y=3), Point(x=4, y=3)) + self.assertNotEqual(Point(x=3, y=4), Point(x=4, y=3)) def test_ctypesPOINT(self): # Add - self.assertEqual(Point(x=2,y=4)+POINT(x=2,y=4),Point(x=4,y=8)) - self.assertEqual(POINT(x=2,y=4)+Point(x=2,y=4),Point(x=4,y=8)) + self.assertEqual(Point(x=2, y=4)+POINT(x=2, y=4),Point(x=4, y=8)) + self.assertEqual(POINT(x=2, y=4)+Point(x=2, y=4),Point(x=4, y=8)) # Sum - self.assertEqual(sum((Point(x=2,y=4), POINT(x=2,y=4), Point(x=2,y=4))), Point(x=6,y=12)) + self.assertEqual(sum((Point(x=2, y=4), POINT(x=2, y=4), Point(x=2, y=4))), Point(x=6, y=12)) # Subtract - self.assertEqual(Point(x=2,y=4)-POINT(x=4,y=8),Point(x=-2,y=-4)) - self.assertEqual(POINT(x=2,y=4)-Point(x=4,y=8),Point(x=-2,y=-4)) + self.assertEqual(Point(x=2, y=4)-POINT(x=4, y=8),Point(x=-2, y=-4)) + self.assertEqual(POINT(x=2, y=4)-Point(x=4, y=8),Point(x=-2, y=-4)) # Greater than - self.assertTrue(Point(x=3,y=4).yWiseGreaterThan(POINT(x=4,y=3))) - self.assertFalse(Point(x=3,y=4).xWiseGreaterThan(POINT(x=4,y=3))) - self.assertTrue(Point(x=4,y=3).xWiseGreaterThan(POINT(x=3,y=4))) - self.assertFalse(Point(x=4,y=3).yWiseGreaterThan(POINT(x=3,y=4))) - self.assertTrue(Point(x=3,y=4).yWiseGreaterOrEq(POINT(x=4,y=3))) - self.assertFalse(Point(x=3,y=4).xWiseGreaterOrEq(POINT(x=4,y=3))) - self.assertTrue(Point(x=4,y=3).xWiseGreaterOrEq(POINT(x=3,y=4))) - self.assertFalse(Point(x=4,y=3).yWiseGreaterOrEq(POINT(x=3,y=4))) + self.assertTrue(Point(x=3, y=4).yWiseGreaterThan(POINT(x=4, y=3))) + self.assertFalse(Point(x=3, y=4).xWiseGreaterThan(POINT(x=4, y=3))) + self.assertTrue(Point(x=4, y=3).xWiseGreaterThan(POINT(x=3, y=4))) + self.assertFalse(Point(x=4, y=3).yWiseGreaterThan(POINT(x=3, y=4))) + self.assertTrue(Point(x=3, y=4).yWiseGreaterOrEq(POINT(x=4, y=3))) + self.assertFalse(Point(x=3, y=4).xWiseGreaterOrEq(POINT(x=4, y=3))) + self.assertTrue(Point(x=4, y=3).xWiseGreaterOrEq(POINT(x=3, y=4))) + self.assertFalse(Point(x=4, y=3).yWiseGreaterOrEq(POINT(x=3, y=4))) # Less than - self.assertTrue(Point(x=4,y=3).yWiseLessThan(POINT(x=3,y=4))) - self.assertFalse(Point(x=4,y=3).xWiseLessThan(POINT(x=3,y=4))) - self.assertTrue(Point(x=3,y=4).xWiseLessThan(POINT(x=4,y=3))) - self.assertFalse(Point(x=3,y=4).yWiseLessThan(POINT(x=4,y=3))) - self.assertTrue(Point(x=4,y=3).yWiseLessOrEq(POINT(x=3,y=4))) - self.assertFalse(Point(x=4,y=3).xWiseLessOrEq(POINT(x=3,y=4))) - self.assertTrue(Point(x=3,y=4).xWiseLessOrEq(POINT(x=4,y=3))) - self.assertFalse(Point(x=3,y=4).yWiseLessOrEq(POINT(x=4,y=3))) + self.assertTrue(Point(x=4, y=3).yWiseLessThan(POINT(x=3, y=4))) + self.assertFalse(Point(x=4, y=3).xWiseLessThan(POINT(x=3, y=4))) + self.assertTrue(Point(x=3, y=4).xWiseLessThan(POINT(x=4, y=3))) + self.assertFalse(Point(x=3, y=4).yWiseLessThan(POINT(x=4, y=3))) + self.assertTrue(Point(x=4, y=3).yWiseLessOrEq(POINT(x=3, y=4))) + self.assertFalse(Point(x=4, y=3).xWiseLessOrEq(POINT(x=3, y=4))) + self.assertTrue(Point(x=3, y=4).xWiseLessOrEq(POINT(x=4, y=3))) + self.assertFalse(Point(x=3, y=4).yWiseLessOrEq(POINT(x=4, y=3))) # Equality - self.assertEqual(POINT(x=4,y=3), Point(x=4,y=3)) - self.assertNotEqual(POINT(x=3,y=4), Point(x=4,y=3)) \ No newline at end of file + self.assertEqual(POINT(x=4, y=3), Point(x=4, y=3)) + self.assertNotEqual(POINT(x=3, y=4), Point(x=4, y=3)) \ No newline at end of file From 4487aa76b0500507fbbaa27b234c2d103011dfaa Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Tue, 15 May 2018 17:02:14 +0200 Subject: [PATCH 17/27] Remove locationHelper implementation from sysListView32 --- source/NVDAObjects/IAccessible/sysListView32.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/NVDAObjects/IAccessible/sysListView32.py b/source/NVDAObjects/IAccessible/sysListView32.py index fd27901fdd9..8e090353b54 100644 --- a/source/NVDAObjects/IAccessible/sysListView32.py +++ b/source/NVDAObjects/IAccessible/sysListView32.py @@ -23,7 +23,6 @@ import watchdog from NVDAObjects.behaviors import RowWithoutCellObjects, RowWithFakeNavigation import config -from locationHelper import toRectLTWH #Window messages LVM_FIRST=0x1000 @@ -309,7 +308,7 @@ def _getColumnLocationRaw(self,index): winKernel.virtualFreeEx(processHandle,internalRect,0,winKernel.MEM_RELEASE) windll.user32.ClientToScreen(self.windowHandle,byref(localRect)) windll.user32.ClientToScreen(self.windowHandle,byref(localRect,8)) - return toRectLTWH(localRect) + return (localRect.left,localRect.top,localRect.right-localRect.left,localRect.bottom-localRect.top) def _getColumnLocation(self,column): return self._getColumnLocationRaw(self.parent._columnOrderArray[column - 1]) From 7e9452243b795f6b5267838916bccf139029cf6c Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 7 Jun 2018 07:17:12 +0200 Subject: [PATCH 18/27] Syslistview32: validate the rectangle before creating a RectLTWH instance --- source/NVDAObjects/IAccessible/sysListView32.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/source/NVDAObjects/IAccessible/sysListView32.py b/source/NVDAObjects/IAccessible/sysListView32.py index 8e090353b54..2bde55d165b 100644 --- a/source/NVDAObjects/IAccessible/sysListView32.py +++ b/source/NVDAObjects/IAccessible/sysListView32.py @@ -17,12 +17,12 @@ import api import eventHandler import winKernel -import winUser from . import IAccessible, List from ..window import Window import watchdog from NVDAObjects.behaviors import RowWithoutCellObjects, RowWithFakeNavigation import config +from locationHelper import RectLTRB #Window messages LVM_FIRST=0x1000 @@ -306,9 +306,13 @@ def _getColumnLocationRaw(self,index): winKernel.readProcessMemory(processHandle,internalRect,byref(localRect),sizeof(localRect),None) finally: winKernel.virtualFreeEx(processHandle,internalRect,0,winKernel.MEM_RELEASE) - windll.user32.ClientToScreen(self.windowHandle,byref(localRect)) - windll.user32.ClientToScreen(self.windowHandle,byref(localRect,8)) - return (localRect.left,localRect.top,localRect.right-localRect.left,localRect.bottom-localRect.top) + # ##8268: this might be a malformed rectangle + # (i.e. with a left coordinate that is greather than the right coordinate). + left = min(localRect.left, localRect.right) + top = min(localRect.top, localRect.bottom) + right = max(localRect.left, localRect.right) + bottom = max(localRect.top, localRect.bottom) + return RectLTRB(left, top, right, bottom).toScreen(self.windowHandle).toLTWH() def _getColumnLocation(self,column): return self._getColumnLocationRaw(self.parent._columnOrderArray[column - 1]) From 3d351f2f0028657403380432ce7b281b72e0492a Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 7 Jun 2018 13:58:32 +0200 Subject: [PATCH 19/27] Review actions --- source/NVDAObjects/IAccessible/sysListView32.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/source/NVDAObjects/IAccessible/sysListView32.py b/source/NVDAObjects/IAccessible/sysListView32.py index 2bde55d165b..bacb4effe20 100644 --- a/source/NVDAObjects/IAccessible/sysListView32.py +++ b/source/NVDAObjects/IAccessible/sysListView32.py @@ -53,6 +53,15 @@ LVIF_GROUPID=0x100 LVIF_COLUMNS=0x200 +#GETSUBITEMRECT flags +# Returns the bounding rectangle of the entire item, including the icon and label +LVIR_BOUNDS = 0 +# Returns the bounding rectangle of the icon or small icon. +LVIR_ICON = 1 +# Returns the bounding rectangle of the entire item, including the icon and label. +# This is identical to LVIR_BOUNDS. +LVIR_LABEL = 2 + #Item states LVIS_FOCUSED=0x01 LVIS_SELECTED=0x02 @@ -298,7 +307,11 @@ class ListItem(RowWithFakeNavigation, RowWithoutCellObjects, ListItemWithoutColu def _getColumnLocationRaw(self,index): processHandle=self.processHandle - localRect=RECT(left=2,top=index) + # LVM_GETSUBITEMRECT requires a pointer to a RECT structure that will receive the subitem bounding rectangle information. + localRect=RECT( + left=LVIR_LABEL, # Returns the bounding rectangle of the entire item, including the icon and label + top=index # The one-based index of the subitem + ) internalRect=winKernel.virtualAllocEx(processHandle,None,sizeof(localRect),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE) try: winKernel.writeProcessMemory(processHandle,internalRect,byref(localRect),sizeof(localRect),None) From 25896badbd79cda137adb7bf9c1962d109632ada Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 14 Jun 2018 07:08:19 +0200 Subject: [PATCH 20/27] Fix mistake in toRectLTRB function --- source/locationHelper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/locationHelper.py b/source/locationHelper.py index d4bee6f1eee..125408ca2b6 100644 --- a/source/locationHelper.py +++ b/source/locationHelper.py @@ -359,7 +359,7 @@ def toRectLTRB(*params): return RectLTRB(param.left,param.top,param.right,param.bottom) if isinstance(param,POINT_CLASSES): # Right and bottom edges of the resulting rectangle are considered exclusive - x,y=point.x,point.y + x,y=param.x,param.y return RectLTRB(x,y,x+1,y+1) if isinstance(param,(tuple,list)): # One indexable in another indexable doesn't make sence, so treat the inner indexable as outer indexable From e407922e62ca01f1e09e94512c6205a27167f8d7 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Mon, 25 Jun 2018 11:24:38 +0200 Subject: [PATCH 21/27] Review actions for SysListView32 --- source/NVDAObjects/IAccessible/sysListView32.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/NVDAObjects/IAccessible/sysListView32.py b/source/NVDAObjects/IAccessible/sysListView32.py index bacb4effe20..fdf80bb7020 100644 --- a/source/NVDAObjects/IAccessible/sysListView32.py +++ b/source/NVDAObjects/IAccessible/sysListView32.py @@ -306,6 +306,7 @@ def event_stateChange(self): class ListItem(RowWithFakeNavigation, RowWithoutCellObjects, ListItemWithoutColumnSupport): def _getColumnLocationRaw(self,index): + assert index>0, "INvalid index: %d" % index processHandle=self.processHandle # LVM_GETSUBITEMRECT requires a pointer to a RECT structure that will receive the subitem bounding rectangle information. localRect=RECT( @@ -319,7 +320,7 @@ def _getColumnLocationRaw(self,index): winKernel.readProcessMemory(processHandle,internalRect,byref(localRect),sizeof(localRect),None) finally: winKernel.virtualFreeEx(processHandle,internalRect,0,winKernel.MEM_RELEASE) - # ##8268: this might be a malformed rectangle + # #8268: this might be a malformed rectangle # (i.e. with a left coordinate that is greather than the right coordinate). left = min(localRect.left, localRect.right) top = min(localRect.top, localRect.bottom) From 3679aacf05ff0d6382692fed12ac00a26be5ee69 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Mon, 2 Jul 2018 08:59:45 +0200 Subject: [PATCH 22/27] When a rectangle is created from only one point, make its width and height 0 --- source/locationHelper.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/source/locationHelper.py b/source/locationHelper.py index 125408ca2b6..36bf240b1ff 100644 --- a/source/locationHelper.py +++ b/source/locationHelper.py @@ -345,7 +345,7 @@ def toRectLTRB(*params): Converts the given input to L{RectLTRB}. Input should be one of the following types: * One of l{RECT_CLASSES}. - * One of L{POINT_CLASSES}: converted to L{Rect} square of one pixel. + * One of L{POINT_CLASSES}: converted to L{Rect} with a width and height of 0. * List or tuple of integers: 4 treated as L{RectLTRB}, 2 treated as L{Point}. * List or tuple of mixed types from L{RECT_CLASSES} or L{POINT_CLASSES}: converted to L{RectLTRB} containing all input. """ @@ -358,9 +358,8 @@ def toRectLTRB(*params): if isinstance(param,RECT_CLASSES): return RectLTRB(param.left,param.top,param.right,param.bottom) if isinstance(param,POINT_CLASSES): - # Right and bottom edges of the resulting rectangle are considered exclusive x,y=param.x,param.y - return RectLTRB(x,y,x+1,y+1) + return RectLTRB(x,y,x,y) if isinstance(param,(tuple,list)): # One indexable in another indexable doesn't make sence, so treat the inner indexable as outer indexable params=param @@ -372,7 +371,7 @@ def toRectLTRB(*params): return RectLTRB(*params) elif len(params)==2: x,y=params - return RectLTRB(x,y,x+1,y+1) + return RectLTRB(x,y,x,y) elif len(params) in (1,3) or len(params)>4: raise ValueError("When providing integers, this function requires either 2 or 4 arguments (%d given)"%len(params)) xs=[] From 80de1328d254acd8b05e9b4c130af9bddd9fbe69 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 5 Jul 2018 15:49:00 +0200 Subject: [PATCH 23/27] Add fromNonInts factory function --- source/locationHelper.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/source/locationHelper.py b/source/locationHelper.py index 36bf240b1ff..ad55383fb0b 100644 --- a/source/locationHelper.py +++ b/source/locationHelper.py @@ -183,6 +183,14 @@ def toScreen(self, hwnd): class _RectMixin: """Mix-in class for properties shared between RectLTRB and RectLTWH classes""" + @classmethod + def fromNonInts(cls, *nonInts): + """Creates an instance from parameters that aren't integers, such as floats. + The provided parameters will be converted to ints automatically. + @raise ValueError: If one of the input parameters can't be converted to int. + """ + return cls(*map(int, nonInts)) + def toRECT(self): """Converts self to a L{ctypes.wintypes.RECT}""" return RECT(self.left,self.top,self.right,self.bottom) From 4b4adf385d22133c2763a0bac3dbbe02d346db71 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 5 Jul 2018 15:51:11 +0200 Subject: [PATCH 24/27] Use fromNonInts factory function for UIA locations, which are floats --- source/NVDAObjects/UIA/__init__.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/source/NVDAObjects/UIA/__init__.py b/source/NVDAObjects/UIA/__init__.py index b237cac53e1..cd6c66e01bd 100644 --- a/source/NVDAObjects/UIA/__init__.py +++ b/source/NVDAObjects/UIA/__init__.py @@ -1290,12 +1290,7 @@ def _get_location(self): if r is None: return # r is a tuple of floats representing left, top, width and height. - # However, most NVDA code expecs location coordinates to be ints - left=int(r[0]) - top=int(r[1]) - width=int(r[2]) - height=int(r[3]) - return RectLTWH(left,top,width,height) + return RectLTWH.fromNonInts(*r) def _get_value(self): val=self._getUIACacheablePropertyValue(UIAHandler.UIA_RangeValueValuePropertyId,True) From 7a053876139d41a351e8e70010ae6d78d5fa4f43 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 5 Jul 2018 20:41:53 +0200 Subject: [PATCH 25/27] Refactoring --- source/NVDAObjects/window/__init__.py | 4 +- source/locationHelper.py | 257 ++++++++++---------------- tests/unit/test_locationHelper.py | 38 ++-- 3 files changed, 113 insertions(+), 186 deletions(-) diff --git a/source/NVDAObjects/window/__init__.py b/source/NVDAObjects/window/__init__.py index 600fb168783..2d22d84084a 100644 --- a/source/NVDAObjects/window/__init__.py +++ b/source/NVDAObjects/window/__init__.py @@ -17,7 +17,7 @@ from NVDAObjects import NVDAObject from NVDAObjects.behaviors import EditableText, LiveText import watchdog -from locationHelper import toRectLTWH +from locationHelper import RectLTWH re_WindowsForms=re.compile(r'^WindowsForms[0-9]*\.(.*)\.app\..*$') re_ATL=re.compile(r'^ATL:(.*)$') @@ -193,7 +193,7 @@ def _get_windowControlID(self): def _get_location(self): r=ctypes.wintypes.RECT() ctypes.windll.user32.GetWindowRect(self.windowHandle,ctypes.byref(r)) - return toRectLTWH(r) + return RectLTWH.fromCompatibleType(r) def _get_displayText(self): """The text at this object's location according to the display model for this object's window.""" diff --git a/source/locationHelper.py b/source/locationHelper.py index ad55383fb0b..981f097fbda 100644 --- a/source/locationHelper.py +++ b/source/locationHelper.py @@ -6,16 +6,41 @@ """Classes and helper functions for working with rectangles and coordinates.""" -from collections import namedtuple +from collections import namedtuple, Sequence import windowUtils import winUser -from ctypes.wintypes import RECT, SMALL_RECT, POINT +from ctypes.wintypes import RECT, POINT, DWORD import textInfos import wx class Point(namedtuple("Point",("x","y"))): """Represents a point on the screen.""" + @classmethod + def fromNonInts(cls, *nonInts): + """Creates an instance from parameters that aren't integers, such as floats. + The provided parameters will be converted to ints automatically. + @raise ValueError: If one of the input parameters can't be converted to int. + """ + return cls(*map(int, nonInts)) + + @classmethod + def fromCompatibleType(cls, point): + """Creates an instance from a compatible type. + Compatible types are defined in L{POINT_CLASSES}. + """ + if isinstance(point,POINT_CLASSES): + return cls(point.x, point.y) + raise TypeError("point should be one of %s" % ", ".join(cls.__module__+"."+cls.__name__ for cls in POINT_CLASSES)) + + @classmethod + def fromDWORD(cls, dwPoint): + if isinstance(dwPoint,DWORD): + dwPoint = dwPoint.value + if not isinstance(dwPoint,(int,long)): + raise TypeError("dwPoint should be one of int, long or ctypes.wintypes.DWORD (ctypes.ulong)") + return Point(winUser.GET_X_LPARAM(dwPoint),winUser.GET_Y_LPARAM(dwPoint)) + def __add__(self,other): """Returns a new L{Point} with its coordinates representing the additions of the original x and y coordinates.""" if not isinstance(other,POINT_CLASSES): @@ -47,10 +72,7 @@ def yWiseLessThan(self,other): To compare in opposite order (i.e. compare x, then y), use L{xWiseLessThan} """ if not isinstance(other,POINT_CLASSES): - try: - other=toPoint(other) - except ValueError: - return False + return NotImplemented return (self.y, self.x) < (other.y, other.x) def xWiseLessThan(self,other): @@ -60,10 +82,7 @@ def xWiseLessThan(self,other): To compare in opposite order (i.e. compare y, then x), use L{yWiseLessThan} """ if not isinstance(other,POINT_CLASSES): - try: - other=toPoint(other) - except ValueError: - return False + return NotImplemented return (self.x, self.y) < (other.x, other.y) def yWiseLessOrEq(self,other): @@ -73,10 +92,7 @@ def yWiseLessOrEq(self,other): To compare in opposite order (i.e. compare x, then y), use L{xWiseLessOrEq} """ if not isinstance(other,POINT_CLASSES): - try: - other=toPoint(other) - except ValueError: - return False + return NotImplemented return (self.y, self.x) <= (other.y, other.x) def xWiseLessOrEq(self,other): @@ -86,10 +102,7 @@ def xWiseLessOrEq(self,other): To compare in opposite order (i.e. compare y, then x), use L{yWiseLessOrEq} """ if not isinstance(other,POINT_CLASSES): - try: - other=toPoint(other) - except ValueError: - return False + return NotImplemented return (self.x, self.y) <= (other.x, other.y) def yWiseGreaterThan(self,other): @@ -99,10 +112,7 @@ def yWiseGreaterThan(self,other): To compare in opposite order (i.e. compare x, then y), use L{xWiseGreaterThan} """ if not isinstance(other,POINT_CLASSES): - try: - other=toPoint(other) - except ValueError: - return False + return NotImplemented return (self.y, self.x) > (other.y, other.x) def xWiseGreaterThan(self,other): @@ -112,10 +122,7 @@ def xWiseGreaterThan(self,other): To compare in opposite order (i.e. compare y, then x), use L{yWiseGreaterThan} """ if not isinstance(other,POINT_CLASSES): - try: - other=toPoint(other) - except ValueError: - return False + return NotImplemented return (self.x, self.y) > (other.x, other.y) def yWiseGreaterOrEq(self,other): @@ -125,10 +132,7 @@ def yWiseGreaterOrEq(self,other): To compare in opposite order (i.e. compare x, then y), use L{xWiseGreaterOrEq} """ if not isinstance(other,POINT_CLASSES): - try: - other=toPoint(other) - except ValueError: - return False + return NotImplemented return (self.y, self.x) >= (other.y, other.x) def xWiseGreaterOrEq(self,other): @@ -138,26 +142,17 @@ def xWiseGreaterOrEq(self,other): To compare in opposite order (i.e. compare y, then x), use L{yWiseGreaterOrEq} """ if not isinstance(other,POINT_CLASSES): - try: - other=toPoint(other) - except ValueError: - return False + return NotImplemented return (self.x, self.y) >= (other.x, other.y) def __eq__(self,other): if not isinstance(other,POINT_CLASSES): - try: - other=toPoint(other) - except ValueError: - return False + return NotImplemented return self.x == other.x and self.y == other.y def __ne__(self,other): if not isinstance(other,POINT_CLASSES): - try: - other=toPoint(other) - except ValueError: - return True + return NotImplemented return self.x != other.x or self.y != other.y def toPOINT(self): @@ -191,6 +186,57 @@ def fromNonInts(cls, *nonInts): """ return cls(*map(int, nonInts)) + @classmethod + def fromCompatibleType(cls, rect): + """Creates an instance from a compatible type. + Compatible types are defined in L{RECT_CLASSES}. + """ + if isinstance(rect,cls): + return rect + if isinstance(rect,RECT_CLASSES): + if cls is RectLTWH: + return cls(rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top) + return cls(rect.left, rect.top, rect.right, rect.bottom) + raise TypeError("rect should be one of %s" % ", ".join(cls.__module__+"."+cls.__name__ for cls in RECT_CLASSES)) + + @classmethod + def fromPoint(cls, point): + """Creates an instance from a compatible point type with a width and height of 0.""" + if isinstance(point,POINT_CLASSES): + if cls is RectLTWH: + return cls(point.x, point.y, 0, 0) + return cls(point.x, point.y, point.x, point.y) + raise TypeError("point should be one of %s" % ", ".join(cls.__module__+"."+cls.__name__ for cls in POINT_CLASSES)) + + @classmethod + def fromCollection(cls, *items): + """Creates a bounding rectangle for the provided collection of items. + The highest coordinates found in the collection are considered exclusive. + For example, if you provide Point(x=1,y=1) and point(x=2,y=2), + The resulting rectangle coordinates are left=1,top=1,right=2,bottom=2. + Input could be of mixed types from either L{RECT_CLASSES} or L{POINT_CLASSES}. + """ + if len(items)==0: + raise TypeError("This function takes at least 1 argument (0 given)") + xs=set() + ys=set() + for item in items: + if isinstance(item,RECT_CLASSES): + xs.update((item.left,item.right)) + ys.update((item.top,item.bottom)) + elif isinstance(item,POINT_CLASSES): + xs.add(item.x) + ys.add(item.y) + else: + raise ValueError("Unexpected parameter %s"%str(item)) + left=min(xs) + top=min(ys) + right=max(xs) + bottom=max(ys) + if cls is RectLTWH: + return cls(left, top, right-left, bottom-top) + return cls(left, top, right, bottom) + def toRECT(self): """Converts self to a L{ctypes.wintypes.RECT}""" return RECT(self.left,self.top,self.right,self.bottom) @@ -246,44 +292,29 @@ def __contains__(self,other): if isinstance(other,POINT_CLASSES): return self.left <= other.x < self.right and self.top <= other.y < self.bottom if not isinstance(other,RECT_CLASSES): - try: - other=toRectLTRB(other) - except: - return False + return NotImplemented return self.isSuperset(other) and self!=other def isSubset(self,other): """Returns whether this rectangle is a subset of other (i.e. whether all points in this rectangle are contained by other).""" if not isinstance(other,RECT_CLASSES): - try: - other=toRectLTRB(other) - except ValueError: - return False + return NotImplemented return other.left<=self.left<=self.right<=other.right and other.top<=self.top<=self.bottom<=other.bottom def isSuperset(self,other): """Returns whether this rectangle is a superset of other (i.e. whether all points of other are contained by this rectangle).""" if not isinstance(other,RECT_CLASSES): - try: - other=toRectLTRB(other) - except ValueError: - return False + return NotImplemented return self.left<=other.left<=other.right<=self.right and self.top<=other.top<=other.bottom<=self.bottom def __eq__(self,other): if not isinstance(other,RECT_CLASSES): - try: - other=toRectLTRB(other) - except ValueError: - return False + return NotImplemented return other.left == self.left and other.top == self.top and other.right == self.right and other.bottom == self.bottom def __ne__(self,other): if not isinstance(other,RECT_CLASSES): - try: - other=toRectLTRB(other) - except ValueError: - return True + return NotImplemented return other.left != self.left or other.top != self.top or other.right != self.right or other.bottom != self.bottom def intersection(self,other): @@ -293,10 +324,7 @@ def intersection(self,other): No intersect results in a rectangle with zeroed coordinates. """ if not isinstance(other,RECT_CLASSES): - try: - other=toRectLTRB(other) - except ValueError: - return NotImplemented + return NotImplemented left=max(self.left,other.left) top=max(self.top,other.top) right=min(self.right,other.right) @@ -348,104 +376,9 @@ def height(self): def toLTWH(self): return RectLTWH(self.left,self.top,self.width,self.height) -def toRectLTRB(*params): - """ - Converts the given input to L{RectLTRB}. - Input should be one of the following types: - * One of l{RECT_CLASSES}. - * One of L{POINT_CLASSES}: converted to L{Rect} with a width and height of 0. - * List or tuple of integers: 4 treated as L{RectLTRB}, 2 treated as L{Point}. - * List or tuple of mixed types from L{RECT_CLASSES} or L{POINT_CLASSES}: converted to L{RectLTRB} containing all input. - """ - if len(params)==0: - raise TypeError("This function takes at least 1 argument (0 given)") - if len(params)==1: - param=params[0] - if isinstance(param,RectLTRB): - return param - if isinstance(param,RECT_CLASSES): - return RectLTRB(param.left,param.top,param.right,param.bottom) - if isinstance(param,POINT_CLASSES): - x,y=param.x,param.y - return RectLTRB(x,y,x,y) - if isinstance(param,(tuple,list)): - # One indexable in another indexable doesn't make sence, so treat the inner indexable as outer indexable - params=param - if all(isinstance(param,(int,long)) for param in params): - if len(params)==4: - # Assume that we are converting from a tuple rectangle (RectLTRB). - # To convert from a tuple location (RectLTWH), use L{toRectLTWH} instead. - # To convert from a tuple rectangle to L{RectLTWH}, use this function and execute L{toLTWH} on the resulting object. - return RectLTRB(*params) - elif len(params)==2: - x,y=params - return RectLTRB(x,y,x,y) - elif len(params) in (1,3) or len(params)>4: - raise ValueError("When providing integers, this function requires either 2 or 4 arguments (%d given)"%len(params)) - xs=[] - ys=[] - for param in params: - if isinstance(param,RECT_CLASSES): - xs.extend((param.left,param.right)) - ys.extend((param.top,param.bottom)) - elif isinstance(param,POINT_CLASSES): - xs.append(param.x) - ys.append(param.y) - else: - raise ValueError("Unexpected parameter %s"%str(param)) - left=min(xs) - top=min(ys) - right=max(xs) - bottom=max(ys) - return RectLTRB(left,top,right,bottom) - -def toRectLTWH(*params): - """ - Converts the given input to L{RectLTWH}. - Input should be one of the following types: - * One of l{RECT_CLASSES}. - * One of L{POINT_CLASSES}: converted to L{RectLTWH} square of one pixel. - * List or tuple of integers: 4 treated as L{RectLTWH}, 2 treated as L{Point}. - * List or tuple of mixed types from L{RECT_CLASSES} or L{POINT_CLASSES}: converted to L{RectLTWH} containing all input. - """ - if len(params)==0: - raise TypeError("This function takes at least 1 argument (0 given)") - if len(params)==1: - param=params[0] - if isinstance(param,RectLTWH): - return param - if not isinstance(param,RECT_CLASSES+POINT_CLASSES) and isinstance(param,(tuple,list)): - # One indexable in another indexable doesn't make sence, so treat the inner indexable as outer indexable - params=param - if len(params)==4 and all(isinstance(param,(int,long)) for param in params): - # Assume that we are converting from a tuple location (RectLTWH). - # To convert from a tuple rectangle (RectLTRB), use L{toRectLTRB} instead. - # To convert from a tuple location to L{RectLTRB}, use this function and execute L{toLTRB} on the resulting object. - return RectLTWH(*params) - return toRectLTRB(*params).toLTWH() - -def toPoint(*params): - """ - Converts the given input to L{Point}. - Input should either be one of L{POINT_CLASSES}, 2 integers or one double word. - """ - if not len(params) in (1,2): - raise TypeError("This function takes 1 or 2 arguments (%d given)"%len(params)) - if len(params)==1: - param=params[0] - if isinstance(param,Point): - return param - if isinstance(param,POINT_CLASSES): - return Point(param.x,param.y) - if isinstance(param,(int,long)): - return Point(winUser.GET_X_LPARAM(param),winUser.GET_Y_LPARAM(param)) - if all(isinstance(param,(int,long)) for param in params) and len(params)==2: - return Point(*params) - raise ValueError("Unexpected parameter(s) %s"%params) - #: Classes which support conversion to locationHelper Points using their x and y properties. #: type: tuple POINT_CLASSES=(Point, POINT, textInfos.Point, wx.Point) #: Classes which support conversion to locationHelper RectLTRB/LTWH using their left, top, right and bottom properties. #: type: tuple -RECT_CLASSES=(RectLTRB, RectLTWH, RECT, SMALL_RECT, textInfos.Rect) +RECT_CLASSES=(RectLTRB, RectLTWH, RECT, textInfos.Rect) diff --git a/tests/unit/test_locationHelper.py b/tests/unit/test_locationHelper.py index 90971008a15..24e25e4ac24 100644 --- a/tests/unit/test_locationHelper.py +++ b/tests/unit/test_locationHelper.py @@ -72,15 +72,13 @@ def test_points(self): self.assertEqual(RectLTRB(left=10, top=10, right=21, bottom=21).center, Point(x=16, y=16)) self.assertEqual(RectLTRB(left=-21, top=-21, right=-10, bottom=-10).center, Point(x=-16, y=-16)) -class TestToRectLTRB(unittest.TestCase): - def test_collection(self): """Tests whether a collection of several rectangle and point types convert to the expected L{RectLTRB}.""" rect=RectLTRB(left=10, top=15, right=500, bottom=1000) - self.assertEqual(toRectLTRB( - rect.topLeft, - rect.bottomRight, - rect.center, + self.assertEqual(RectLTRB.fromCollection( + rect.topLeft, + rect.bottomRight, + rect.center, Point(15, 15), Point(20, 20), Point(50, 50), @@ -93,21 +91,11 @@ def test_collection(self): RECT(450, 450, 490, 990) ), rect) - def test_integers(self): - self.assertEqual(RectLTRB(left=10, top=10, right=20, bottom=20), toRectLTRB(10, 10, 20, 20)) - - def test_valueErrorForUnsuportedInput(self): - self.assertRaises(ValueError, RectLTRB, left=10, top=10, right=9, bottom=9) - -class TestToRectLTWH(unittest.TestCase): - - def test_collection(self): - """Tests whether a collection of several rectangle and point types convert to the expected L{RectLTWH}.""" location=RectLTWH(left=10, top=15, width=500, height=1000) - self.assertEqual(toRectLTWH( - location.topLeft, - location.bottomRight, - location.center, + self.assertEqual(RectLTWH.fromCollection( + location.topLeft, + location.bottomRight, + location.center, Point(15, 15), Point(20, 20), Point(50, 50), @@ -120,8 +108,14 @@ def test_collection(self): RECT(450, 450, 490, 990) ), location) - def test_integers(self): - self.assertEqual(RectLTWH(left=10, top=10, width=20, height=20),toRectLTWH(10, 10, 20, 20)) + def test_fromNonInts(self): + self.assertEqual(RectLTRB(left=10, top=10, right=20, bottom=20), RectLTRB.fromNonInts(10.0, 10.0, 20.0, 20.0)) + self.assertEqual(RectLTRB(left=10, top=10, right=20, bottom=20), RectLTRB.fromNonInts("10", "10", "20", "20")) + self.assertEqual(RectLTWH(left=10, top=10, width=20, height=20), RectLTWH.fromNonInts(10.0, 10.0, 20.0, 20.0)) + self.assertEqual(RectLTWH(left=10, top=10, width=20, height=20), RectLTWH.fromNonInts("10", "10", "20", "20")) + + def test_valueErrorForUnsuportedInput(self): + self.assertRaises(ValueError, RectLTRB, left=10, top=10, right=9, bottom=9) class TestPointOperators(unittest.TestCase): From 5dfca2f8e06925c78d837290e064db216c415bec Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Mon, 23 Jul 2018 10:56:23 +0200 Subject: [PATCH 26/27] Add expandOrShrink helper method to rectangles, useful for focus highlight --- source/locationHelper.py | 25 ++++++++++++++++++++++--- tests/unit/test_locationHelper.py | 12 ++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/source/locationHelper.py b/source/locationHelper.py index 981f097fbda..e1e76d62992 100644 --- a/source/locationHelper.py +++ b/source/locationHelper.py @@ -298,13 +298,13 @@ def __contains__(self,other): def isSubset(self,other): """Returns whether this rectangle is a subset of other (i.e. whether all points in this rectangle are contained by other).""" if not isinstance(other,RECT_CLASSES): - return NotImplemented + return False return other.left<=self.left<=self.right<=other.right and other.top<=self.top<=self.bottom<=other.bottom def isSuperset(self,other): """Returns whether this rectangle is a superset of other (i.e. whether all points of other are contained by this rectangle).""" if not isinstance(other,RECT_CLASSES): - return NotImplemented + return False return self.left<=other.left<=other.right<=self.right and self.top<=other.top<=other.bottom<=self.bottom def __eq__(self,other): @@ -324,7 +324,7 @@ def intersection(self,other): No intersect results in a rectangle with zeroed coordinates. """ if not isinstance(other,RECT_CLASSES): - return NotImplemented + raise TypeError("other should be one of %s" % ", ".join(cls.__module__+"."+cls.__name__ for cls in RECT_CLASSES)) left=max(self.left,other.left) top=max(self.top,other.top) right=min(self.right,other.right) @@ -335,6 +335,25 @@ def intersection(self,other): return RectLTWH(left,top,right-left,bottom-top) return RectLTRB(left,top,right,bottom) + def expandOrShrink(self, margin): + """Expands or shrinks the boundaries of the rectangle with the given margin. + For example, if self = Rect(left=10,top=10,right=25,bottom=25) and margin = 10, + this results in Rect(left=0,top=0,right=35,bottom=35). + If self = Rect(left=10,top=10,right=25,bottom=25) and margin = -5, + this results in Rect(left=15,top=15,right=20,bottom=20). + """ + if not isinstance(margin, int): + raise TypeError("Margin should be an integer") + left=self.left-margin + top=self.top-margin + right=self.right+margin + bottom=self.bottom+margin + if left>right or top>bottom: + raise RuntimeError("The provided margin of %d would result in a rectangle with a negative width or height, which is not allowed"%margin) + if isinstance(self, RectLTWH): + return RectLTWH(left,top,right-left,bottom-top) + return RectLTRB(left,top,right,bottom) + class RectLTWH(_RectMixin, namedtuple("RectLTWH",("left","top","width","height"))): """ Represents a rectangle on the screen, based on left and top coordinates, width and height. diff --git a/tests/unit/test_locationHelper.py b/tests/unit/test_locationHelper.py index 24e25e4ac24..a46a2d58853 100644 --- a/tests/unit/test_locationHelper.py +++ b/tests/unit/test_locationHelper.py @@ -117,6 +117,18 @@ def test_fromNonInts(self): def test_valueErrorForUnsuportedInput(self): self.assertRaises(ValueError, RectLTRB, left=10, top=10, right=9, bottom=9) + def test_expandOrShrink(self): + """Tests the expand or shrink functionality to resize a rectangle given a specified margin.""" + rect = RectLTRB(left=10, top=10, right=20, bottom=20) + self.assertEqual(rect.expandOrShrink(10), RectLTRB(left=0, top=0, right=30, bottom=30)) + self.assertEqual(rect.expandOrShrink(-2), RectLTRB(left=12, top=12, right=18, bottom=18)) + self.assertRaises(RuntimeError, rect.expandOrShrink, -10) + + location = RectLTWH(left=10, top=10, width=10, height=10) + self.assertEqual(location.expandOrShrink(10), RectLTWH(left=0, top=0, width=30, height=30)) + self.assertEqual(location.expandOrShrink(-2), RectLTWH(left=12, top=12, width=6, height=6)) + self.assertRaises(RuntimeError, location.expandOrShrink, -10) + class TestPointOperators(unittest.TestCase): def test_add(self): From f055c9cfdb06214c9ef0fb125a63b18d060f5069 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Mon, 30 Jul 2018 09:17:08 +0200 Subject: [PATCH 27/27] Review actions --- .../NVDAObjects/IAccessible/sysListView32.py | 2 +- source/NVDAObjects/UIA/__init__.py | 2 +- source/locationHelper.py | 30 ++++++++++++------- tests/unit/test_locationHelper.py | 8 ++--- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/source/NVDAObjects/IAccessible/sysListView32.py b/source/NVDAObjects/IAccessible/sysListView32.py index fdf80bb7020..802e5f3a154 100644 --- a/source/NVDAObjects/IAccessible/sysListView32.py +++ b/source/NVDAObjects/IAccessible/sysListView32.py @@ -306,7 +306,7 @@ def event_stateChange(self): class ListItem(RowWithFakeNavigation, RowWithoutCellObjects, ListItemWithoutColumnSupport): def _getColumnLocationRaw(self,index): - assert index>0, "INvalid index: %d" % index + assert index>0, "Invalid index: %d" % index processHandle=self.processHandle # LVM_GETSUBITEMRECT requires a pointer to a RECT structure that will receive the subitem bounding rectangle information. localRect=RECT( diff --git a/source/NVDAObjects/UIA/__init__.py b/source/NVDAObjects/UIA/__init__.py index a660e8a4d7f..3730a78a39e 100644 --- a/source/NVDAObjects/UIA/__init__.py +++ b/source/NVDAObjects/UIA/__init__.py @@ -1297,7 +1297,7 @@ def _get_location(self): if r is None: return # r is a tuple of floats representing left, top, width and height. - return RectLTWH.fromNonInts(*r) + return RectLTWH.fromFloatCollection(*r) def _get_value(self): val=self._getUIACacheablePropertyValue(UIAHandler.UIA_RangeValueValuePropertyId,True) diff --git a/source/locationHelper.py b/source/locationHelper.py index e1e76d62992..11dc55f45eb 100644 --- a/source/locationHelper.py +++ b/source/locationHelper.py @@ -17,12 +17,14 @@ class Point(namedtuple("Point",("x","y"))): """Represents a point on the screen.""" @classmethod - def fromNonInts(cls, *nonInts): - """Creates an instance from parameters that aren't integers, such as floats. + def fromFloatCollection(cls, *floats): + """Creates an instance from float parameters. The provided parameters will be converted to ints automatically. - @raise ValueError: If one of the input parameters can't be converted to int. + @raise TypeError: If one of the input parameters isn't a float. """ - return cls(*map(int, nonInts)) + if not all(isinstance(f, float) for f in floats): + raise TypeError("All input parameters must be of type float") + return cls(*map(int, floats)) @classmethod def fromCompatibleType(cls, point): @@ -179,12 +181,14 @@ class _RectMixin: """Mix-in class for properties shared between RectLTRB and RectLTWH classes""" @classmethod - def fromNonInts(cls, *nonInts): - """Creates an instance from parameters that aren't integers, such as floats. + def fromFloatCollection(cls, *floats): + """Creates an instance from float parameters. The provided parameters will be converted to ints automatically. - @raise ValueError: If one of the input parameters can't be converted to int. + @raise TypeError: If one of the input parameters isn't a float. """ - return cls(*map(int, nonInts)) + if not all(isinstance(f, float) for f in floats): + raise TypeError("All input parameters must be of type float") + return cls(*map(int, floats)) @classmethod def fromCompatibleType(cls, rect): @@ -196,7 +200,8 @@ def fromCompatibleType(cls, rect): if isinstance(rect,RECT_CLASSES): if cls is RectLTWH: return cls(rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top) - return cls(rect.left, rect.top, rect.right, rect.bottom) + elif cls is RectLTRB: + return cls(rect.left, rect.top, rect.right, rect.bottom) raise TypeError("rect should be one of %s" % ", ".join(cls.__module__+"."+cls.__name__ for cls in RECT_CLASSES)) @classmethod @@ -205,7 +210,10 @@ def fromPoint(cls, point): if isinstance(point,POINT_CLASSES): if cls is RectLTWH: return cls(point.x, point.y, 0, 0) - return cls(point.x, point.y, point.x, point.y) + elif cls is RectLTRB: + return cls(point.x, point.y, point.x, point.y) + else: + raise RuntimeError("%s is not known as a valid subclass of _RectMixin" % cls.__name__) raise TypeError("point should be one of %s" % ", ".join(cls.__module__+"."+cls.__name__ for cls in POINT_CLASSES)) @classmethod @@ -304,7 +312,7 @@ def isSubset(self,other): def isSuperset(self,other): """Returns whether this rectangle is a superset of other (i.e. whether all points of other are contained by this rectangle).""" if not isinstance(other,RECT_CLASSES): - return False + raise TypeError("other should be one of %s" % ", ".join(cls.__module__+"."+cls.__name__ for cls in RECT_CLASSES)) return self.left<=other.left<=other.right<=self.right and self.top<=other.top<=other.bottom<=self.bottom def __eq__(self,other): diff --git a/tests/unit/test_locationHelper.py b/tests/unit/test_locationHelper.py index a46a2d58853..c14c8e11fd2 100644 --- a/tests/unit/test_locationHelper.py +++ b/tests/unit/test_locationHelper.py @@ -108,11 +108,9 @@ def test_collection(self): RECT(450, 450, 490, 990) ), location) - def test_fromNonInts(self): - self.assertEqual(RectLTRB(left=10, top=10, right=20, bottom=20), RectLTRB.fromNonInts(10.0, 10.0, 20.0, 20.0)) - self.assertEqual(RectLTRB(left=10, top=10, right=20, bottom=20), RectLTRB.fromNonInts("10", "10", "20", "20")) - self.assertEqual(RectLTWH(left=10, top=10, width=20, height=20), RectLTWH.fromNonInts(10.0, 10.0, 20.0, 20.0)) - self.assertEqual(RectLTWH(left=10, top=10, width=20, height=20), RectLTWH.fromNonInts("10", "10", "20", "20")) + def test_fromFloatCollection(self): + self.assertEqual(RectLTRB(left=10, top=10, right=20, bottom=20), RectLTRB.fromFloatCollection(10.0, 10.0, 20.0, 20.0)) + self.assertEqual(RectLTWH(left=10, top=10, width=20, height=20), RectLTWH.fromFloatCollection(10.0, 10.0, 20.0, 20.0)) def test_valueErrorForUnsuportedInput(self): self.assertRaises(ValueError, RectLTRB, left=10, top=10, right=9, bottom=9)