From cc8f0f6e08b631acbac35408188892e1d626e5b3 Mon Sep 17 00:00:00 2001 From: Nikita Voronchev Date: Mon, 24 Jan 2022 20:50:01 +0300 Subject: [PATCH 1/2] Refactoring: Move CV features into `cv` submodule --- pikuli/__init__.py | 66 +-- pikuli/_functions.py | 77 +-- pikuli/_helpers.py | 3 +- pikuli/{logger.py => _logger.py} | 6 +- .../{_SettingsClass.py => _settings_class.py} | 5 +- pikuli/cv/__init__.py | 10 + pikuli/{File.py => cv/file.py} | 3 +- pikuli/{Match.py => cv/match.py} | 32 +- pikuli/{Pattern.py => cv/pattern.py} | 55 +- pikuli/cv/region_cv_methods.py | 339 +++++++++++ pikuli/{Screen.py => cv/screen.py} | 25 +- pikuli/cv_element.py | 53 -- pikuli/geom/__init__.py | 2 + pikuli/geom/location.py | 7 +- pikuli/geom/region.py | 534 ++---------------- pikuli/geom/simple_types.py | 39 ++ pikuli/geom/vector.py | 26 +- pikuli/hwnd/hwnd_element.py | 32 +- pikuli/input/helper_types.py | 1 - pikuli/uia/control_wrappers/check_box.py | 3 +- pikuli/uia/control_wrappers/combo_box.py | 3 +- pikuli/uia/control_wrappers/mixin.py | 3 +- pikuli/uia/uia_element.py | 3 +- pikuli/utils.py | 1 + 24 files changed, 571 insertions(+), 757 deletions(-) rename pikuli/{logger.py => _logger.py} (88%) rename pikuli/{_SettingsClass.py => _settings_class.py} (97%) create mode 100644 pikuli/cv/__init__.py rename pikuli/{File.py => cv/file.py} (98%) rename pikuli/{Match.py => cv/match.py} (68%) rename pikuli/{Pattern.py => cv/pattern.py} (67%) create mode 100644 pikuli/cv/region_cv_methods.py rename pikuli/{Screen.py => cv/screen.py} (61%) delete mode 100644 pikuli/cv_element.py create mode 100644 pikuli/geom/simple_types.py diff --git a/pikuli/__init__.py b/pikuli/__init__.py index 2fe96ba..3a71465 100644 --- a/pikuli/__init__.py +++ b/pikuli/__init__.py @@ -1,61 +1,29 @@ # -*- coding: utf-8 -*- -''' Пока что этот модуль -- прослойка для Sikuli. - В перспективе мы сможем отказаться от Sikuli, дописывая только этот модуль - -Doc pywin32: - http://timgolden.me.uk/pywin32-docs/modules.html - -Особенности использования памяти: - -- При создании объекта Pattern, от сделает - self._cv2_pattern = cv2.imread(self.getFilename()) - -''' - -#SUPPORT_UIA = True - import os import sys -from .logger import logger -from .utils import wait_while, wait_while_not +from ._logger import logger +from ._settings_class import settings -from ._SettingsClass import SettingsClass -Settings = SettingsClass() - -from ._exceptions import FailExit, FindFailed -from ._functions import * # TODO: remove it +from ._exceptions import PikuliError, FailExit, FindFailed from .geom.vector import Vector, RelativeVec from .geom.region import Region from .geom.location import Location, LocationF -from .Screen import Screen -from .Match import Match -from .Pattern import Pattern - -if os.name == 'nt': - from .hwnd.hwnd_element import HWNDElement - -#if SUPPORT_UIA: -# from .uia import UIAElement # , AutomationElement -# from .uia.control_wrappers import RegisteredControlClasses -# RegisteredControlClasses._register_all() - +__addImagePath_err_msg = "The directory of the ran py-file cann't be set as images path: {}" try: - Settings.addImagePath( - os.path.dirname(os.path.abspath(sys.modules['__main__'].__file__))) -except Exception as e: - logger.exception(e) - -__all__ = [ - 'Settings', - 'Region', - 'Screen', - 'Match', - 'Location', - 'LocationF', - 'Pattern', - 'FailExit', - 'FindFailed', -] # TODO: shorter this list + __main_module = sys.modules['__main__'] + try: + __main_module_file = __main_module.__file__ + except Exception as __ex1: + logger.warning(__addImagePath_err_msg.format(__ex1)) + else: + settings.addImagePath( + os.path.dirname(os.path.abspath(__main_module_file))) +except Exception as __ex: + logger.exception(__addImagePath_err_msg.format(__ex)) + +from . import uia +from . import cv diff --git a/pikuli/_functions.py b/pikuli/_functions.py index 1464746..0016b7c 100644 --- a/pikuli/_functions.py +++ b/pikuli/_functions.py @@ -3,11 +3,13 @@ ''' Файл содержит вспомогательные функции, используемые в pikuli. ''' +from distutils import extension import os import time import logging +from typing import Any + import mss -from io import BytesIO from PIL import Image if os.name == 'nt': @@ -15,12 +17,8 @@ import win32gui import win32con -import numpy as np - -import pikuli -from ._exceptions import FailExit, FindFailed -from pikuli import logger - +from .geom.simple_types import Rectangle +from . import FailExit, logger, settings # Константа отсутствует в win32con, но есть в http://userpages.umbc.edu/~squire/download/WinGDI.h: CAPTUREBLT = 0x40000000 @@ -39,7 +37,7 @@ def verify_timeout_argument(timeout, allow_None=False, err_msg='pikuli.verify_ti def addImagePath(path): - pikuli.Settings.addImagePath(path) + settings.addImagePath(path) def get_hwnd_by_location(x, y): ''' @@ -48,7 +46,7 @@ def get_hwnd_by_location(x, y): return win32gui.WindowFromPoint((x, y)) def setFindFailedDir(path): - pikuli.Settings.setFindFailedDir(path) + settings.setFindFailedDir(path) def _monitor_hndl_to_screen_n(m_hndl): ''' Экраны-мониторы нуменруются от 1. Нулевой экран -- это полный вирутальный. ''' @@ -126,47 +124,50 @@ def _thread_function(x, y, w, h, delay): #threading.Thread(target=_thread_function, args=(x, y, w, h, delay), name='highlight_region %s' % str((x, y, w, h, delay))).start() return +class SimpleImage: + + def __init__(self, img_src: Any, pil_img: Image): + self.img_src = img_src + self._pil_img = pil_img + + @classmethod + def from_cv2(cls, img_src: Any, cv2_img): + return Image.fromarray(cv2_img, mode="RGB") -def _take_screenshot(x, y, w, h, hwnd=None): + @property + def pillow_img(self) -> Image: + return self._pil_img + + def save(self, filename, loglevel=logging.DEBUG): + path = os.path.abspath(filename) + logger.log(loglevel, f'Save {self.img_src} to {path}') + + dir_path = os.path.dirname(path) + if not os.path.exists(dir_path): + os.makedirs(dir_path) + + _, extension = os.path.splitext(path) + self._pil_img.save(path, format=extension.lstrip('.')) + +def take_screenshot(rect: Rectangle) -> SimpleImage: ''' Получаем скриншот области: x, y -- верхний левый угол прямоуголника в системе координат виртуального рабочего стола w, h -- размеры прямоуголника - # + + # TODO: Fix multi-monitor configuration!!! ''' with mss.mss() as sct: monitor = sct.monitors[0] max_x = monitor["width"] max_y = monitor["height"] # проверка выхода заданного значения width за допустимый диапозон - w = w if x + w < max_x else max_x - x + w = rect.w if rect.x + rect.w < max_x else max_x - rect.x # проверка выхода заданного значения height за допустимый диапозон - h = h if y + h < max_y else max_y - y - sct_img = sct.grab(dict(left=x, top=y, height=h, width=w)) - scr = mss.tools.to_png(sct_img.rgb, sct_img.size, output="") - return np.array(Image.open(BytesIO(scr)).convert('RGB')) - - -"""def _scr_num_of_point(x, y): - ''' Вернет номер (от нуля) того экрана, на котором располоржен левый верхний угол текущего Region. ''' - m_tl = win32api.MonitorFromPoint((x, y), win32con.MONITOR_DEFAULTTONULL) - if m_tl is None: - raise FailExit('top-left corner of the Region is out of visible area of sreens (%s, %s)' % (str(x), str(y))) - return _monitor_hndl_to_screen_n(m_tl)""" - -""" -def __check_reg_in_single_screen(self): - ''' Проверяем, что Region целиком на одном экране. Экран -- это просто один из мониторав, которые существуют по мнению Windows. ''' - m_tl = win32api.MonitorFromPoint((self._x, self._y), win32con.MONITOR_DEFAULTTONULL) - # Do "-1" to get the edge pixel belonget to the Region. The next pixel (over any direction) is out of the Region: - m_br = win32api.MonitorFromPoint((self._x + self._w - 1, self._y + self._h - 1), win32con.MONITOR_DEFAULTTONULL) - if m_tl is None or m_br is None: - raise FailExit('one or more corners of region out of visible area of sreens') - if m_tl != m_br: - raise FailExit('region occupies more than one screen') - return Screen(_monitor_hndl_to_screen_n(m_tl)) -""" - + h = rect.h if rect.y + rect.h < max_y else max_y - rect.y + sct_img = sct.grab(dict(left=rect.x, top=rect.y, height=h, width=w)) + pil_img = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX") + return SimpleImage(rect, pil_img) def pixel_color_at(x, y, monitor_number=1): return pixels_colors_at([(x, y)], monitor_number)[0] diff --git a/pikuli/_helpers.py b/pikuli/_helpers.py index fa1d12d..bb7cec3 100644 --- a/pikuli/_helpers.py +++ b/pikuli/_helpers.py @@ -2,8 +2,7 @@ import os -from pikuli import logger - +from . import logger class NotImplemetedDummyBase(object): err_msg = None diff --git a/pikuli/logger.py b/pikuli/_logger.py similarity index 88% rename from pikuli/logger.py rename to pikuli/_logger.py index 702ae6b..8686956 100644 --- a/pikuli/logger.py +++ b/pikuli/_logger.py @@ -3,7 +3,7 @@ import sys import logging -def basic_logger_config(loglevel=logging.INFO): +def basic_logger_config(logger, loglevel=logging.INFO): if logger.handlers: logger.info('Pikuli logger already configured. Skip `pikuli.utils.basic_logger_config()`.') return @@ -19,6 +19,6 @@ def basic_logger_config(loglevel=logging.INFO): logger.addHandler(handler) logger.debug('Pikuli logger has been configured basicaly') - logger = logging.getLogger('axxon.pikuli') -basic_logger_config() + +basic_logger_config(logger) diff --git a/pikuli/_SettingsClass.py b/pikuli/_settings_class.py similarity index 97% rename from pikuli/_SettingsClass.py rename to pikuli/_settings_class.py index 1a35074..aa130aa 100644 --- a/pikuli/_SettingsClass.py +++ b/pikuli/_settings_class.py @@ -3,8 +3,7 @@ import os import tempfile - -class SettingsClass(object): +class SettingsClass: __def_IMG_ADDITION_PATH = [] # Пути, кроме текущего и мб еще какого-то подобного __def_MinSimilarity = 0.995 # Почти устойчиво с 0.995, но однажны не нашел узелок для контура. 0.700 -- будет найдено в каждом пикселе (порог надо поднимать выше). @@ -52,3 +51,5 @@ def setPatternURLTemplate(self, GetPattern_URLTemplate): def getPatternURLTemplate(self): return self.GetPattern_URLTemplate + +settings = SettingsClass() \ No newline at end of file diff --git a/pikuli/cv/__init__.py b/pikuli/cv/__init__.py new file mode 100644 index 0000000..a7259dd --- /dev/null +++ b/pikuli/cv/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- + +from .region_cv_methods import RegionCVMethods +from ..geom import Region as __Region +__Region._make_cv_methods_class_instance = RegionCVMethods + +from .file import File +from .match import Match +from .pattern import Pattern +from .screen import Screen diff --git a/pikuli/File.py b/pikuli/cv/file.py similarity index 98% rename from pikuli/File.py rename to pikuli/cv/file.py index 6db998b..38c2fc4 100644 --- a/pikuli/File.py +++ b/pikuli/cv/file.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- - import os -class File(object): +class File: def __init__(self, path): if path is None: self._path = None diff --git a/pikuli/Match.py b/pikuli/cv/match.py similarity index 68% rename from pikuli/Match.py rename to pikuli/cv/match.py index c36631a..48da3b8 100644 --- a/pikuli/Match.py +++ b/pikuli/cv/match.py @@ -6,11 +6,11 @@ ''' import traceback -from . import FailExit -from . import Region +from .. import FailExit +from ..geom import Region -class Match(Region): +class Match: ''' Как потомок Region, класс Match сможет хранить в себе картинку в формате numpy.array; сравнивать сохраненную картинку с тем, что сейчас в области (x, y, w, h) отображается на экране. Будем по @@ -24,31 +24,31 @@ def __init__(self, x, y, w, h, pattern, score): score -- число, показывающее достоверность совпадения шаблона с изображение на экране ''' try: - super(Match, self).__init__(x, y, w, h) - if not( score is None or (isinstance(score, float) and score > 0.0 and score <= 1.0) ): + if not( (score is None) or (isinstance(score, float) and score > 0.0 and score <= 1.0) ): raise FailExit('not( score is None or (isinstance(score, float) and score > 0.0 and score <= 1.0) ):') + self._region = Region(x, y, w, h) + self._img = self._region.cv.take_screenshot() self._score = score self._pattern = pattern - self.store_current_image() except FailExit: raise FailExit('\nNew stage of %s\n[error] Incorect \'Match\' constructor call:\n\tx = %s\n\ty = %s\n\tw = %s\n\th = %s\n\tscore = %s\n\t' % (traceback.format_exc(), str(w), str(y), str(w), str(h), str(score))) def __str__(self): - return ('' % (str(self._pattern.getFilename()), self._x, self._y, self._w, self._h, self._score)) + return repr(self) def __repr__(self): - return ('' % (str(self._pattern.getFilename()), self._x, self._y, self._w, self._h, self._score)) + return f'' - def getScore(self): + @property + def region(self): + return self._region + + @property + def score(self): ''' Sikuli: Get the similarity score the image or pattern was found. The value is between 0 and 1. ''' return self._score - def getTarget(self): - ''' Sikuli: Get the 'location' object that will be used as the click point. - Typically, when no offset was specified by Pattern.targetOffset(), the click point is the center of the matched region. - If an offset was given, the click point is the offset relative to the center. ''' - raise NotImplementedError - - def getPattern(self): + @property + def pattern(self): return self._pattern diff --git a/pikuli/Pattern.py b/pikuli/cv/pattern.py similarity index 67% rename from pikuli/Pattern.py rename to pikuli/cv/pattern.py index c44f836..7bc24b6 100644 --- a/pikuli/Pattern.py +++ b/pikuli/cv/pattern.py @@ -1,17 +1,17 @@ # -*- coding: utf-8 -*- import os -import logging import cv2 -import pikuli -from .File import File -from ._exceptions import FailExit -from pikuli import logger - +from .file import File +from .._functions import SimpleImage +from .._exceptions import FailExit +from ..geom import Region +from .. import logger, settings class Pattern(File): """ Represents images being searched for on display. """ + def __init__(self, img_pattern, similarity=None): """ :param img_pattern: Имя файла, объект Pattern или Region. @@ -19,14 +19,14 @@ def __init__(self, img_pattern, similarity=None): """ self.__similarity = None - if isinstance(img_pattern, pikuli.Region): + if isinstance(img_pattern, Region): super(Pattern, self).__init__(None) self._cv2_pattern = img_pattern.get_raw_screenshot() else: if isinstance(img_pattern, Pattern): if similarity is None: - similarity = img_pattern.getSimilarity() + similarity = img_pattern.similarity img_path = str(img_pattern.getFilename(full_path=False)) else: img_path = str(img_pattern) @@ -38,7 +38,7 @@ def __init__(self, img_pattern, similarity=None): if os.path.exists(path) and os.path.isfile(path): self._path = path else: - for path in pikuli.Settings.listImagePath(): + for path in settings.listImagePath(): path = os.path.join(path, img_path) if os.path.exists(path) and os.path.isfile(path): self._path = path @@ -46,22 +46,21 @@ def __init__(self, img_pattern, similarity=None): if self._path is None: raise FailExit('image file not found') - if similarity is None: - self.__similarity = pikuli.Settings.MinSimilarity + self.__similarity = settings.MinSimilarity elif isinstance(similarity, float) and similarity > 0.0 and similarity <= 1.0: self.__similarity = similarity else: raise FailExit('error around \'similarity\' parameter : %s' % str(similarity)) except FailExit as e: - raise FailExit('[error] Incorect \'Pattern\' class constructor call:\n\timg_path = %s\n\tabspath(img_path) = %s\n\tsimilarity = %s\n\tadditional comment: -{ %s }-\n\tlistImagePath(): %s' % (str(img_path), str(self._path), str(similarity), str(e), str(list(pikuli.Settings.listImagePath())))) + raise FailExit('[error] Incorect \'Pattern\' class constructor call:\n\timg_path = %s\n\tabspath(img_path) = %s\n\tsimilarity = %s\n\tadditional comment: -{ %s }-\n\tlistImagePath(): %s' % ( + str(img_path), str(self._path), str(similarity), str(e), str(list(settings.listImagePath())))) self._cv2_pattern = cv2.imread(self._path) - - self.w = self._w = int(self._cv2_pattern.shape[1]) - self.h = self._h = int(self._cv2_pattern.shape[0]) + self._w = int(self._cv2_pattern.shape[1]) + self._h = int(self._cv2_pattern.shape[0]) def __str__(self): return '' % (self._path, self.__similarity) @@ -75,24 +74,22 @@ def similar(self, similarity): def exact(self): return Pattern(self._path, 1.0) - def getSimilarity(self): + @property + def similarity(self): return self.__similarity - def getW(self): - self.w, self.h = self._w, self._h + @property + def w(self): return self._w - def getH(self): - self.w, self.h = self._w, self._h + @property + def h(self): return self._h - def get_image(self): - return self._cv2_pattern + @property + def image(self) -> SimpleImage: + return SimpleImage.from_cv2(self._cv2_pattern) - def save_as_png(self, full_filename): - path = os.path.abspath(full_filename) - logger.info('pikuli.Pattern.save_as_png:\n\tfull path: %s' % path) - dir_path = os.path.dirname(full_filename) - if not os.path.exists(dir_path): - os.makedirs(dir_path) - cv2.imwrite(full_filename, self._cv2_pattern) + @property + def filename(self): + return os.path.basename(self._path) diff --git a/pikuli/cv/region_cv_methods.py b/pikuli/cv/region_cv_methods.py new file mode 100644 index 0000000..3f648e0 --- /dev/null +++ b/pikuli/cv/region_cv_methods.py @@ -0,0 +1,339 @@ +# -*- coding: utf-8 -*- + +import time + +import numpy as np +import cv2 + +from pikuli import settings, FindFailed, FailExit, logger +from pikuli._functions import verify_timeout_argument +from .pattern import Pattern +from .file import File +from .match import Match + +# Время в [c] между попытками распознования графического объекта +DELAY_BETWEEN_CV_ATTEMPT = 1.0 +DEFAULT_FIND_TIMEOUT = 3.1 + +def _get_list_of_patterns(ps, failExitText): + if not isinstance(ps, list): + ps = [ps] + for i, p in enumerate(ps): + try: + ps[i] = Pattern(p) + except Exception as ex: + raise FailExit(failExitText + '\n\t' + ' ' * 20 + str(ex)) + return ps + +class RegionCVMethods: + + ''' + find_timeout -- Значение по умолчанию, которове будет использоваться, если метод find() (и подобные) этого класса вызван без явного указания timeout. + Если не передается конструктуру, то берется из переменной модуля DEFAULT_FIND_TIMEOUT. + Будет наслодоваться ко всем объектам, которые возвращаются методами этого класса. + ''' + + def __init__(self, reg_owner): #: Region): + self._reg_owner = reg_owner + self._find_timeout = DEFAULT_FIND_TIMEOUT + + def take_screenshot(self): + return np.array(self._reg_owner.take_screenshot().pillow_img) + + def __find(self, ps, field): + # cv2.imshow('field', field) + # cv2.imshow('pattern', ps._cv2_pattern) + # cv2.waitKey(3*1000) + # cv2.destroyAllWindows() + + CF = 0 + try: + if CF == 0: + res = cv2.matchTemplate(field, ps._cv2_pattern, cv2.TM_CCORR_NORMED) + loc = np.where(res > ps.similarity) # 0.995 + elif CF == 1: + res = cv2.matchTemplate(field, ps._cv2_pattern, cv2.TM_SQDIFF_NORMED) + loc = np.where(res < 1.0 - ps.similarity) # 0.005 + except cv2.error as ex: + raise FindFailed('OpenCV ERROR: ' + str(ex), patterns=ps, field=field, cause=FindFailed.OPENCV_ERROR) + + # for pt in zip(*loc[::-1]): + # cv2.rectangle(field, pt, (pt[0] + self._w, pt[1] + self._h), (0, 0, 255), 2) + # cv2.imshow('field', field) + # cv2.imshow('pattern', ps._cv2_pattern) + # cv2.waitKey(0) + # cv2.destroyAllWindows() + + # 'res' -- Матрица, где каждый элемент содержит корреляуию кусочка "поля" с шаблоном. Каждый элемент + # матрицы соответствует пикселю из "поля". Индексация, вестимо, от нуля. + # 'loc' -- структура вида (array([264, 284, 304]), array([537, 537, 537])) где три пары индексов элементов матрицы 'res', + # для которых выполняется условие. Если из zip'нуть, то получиться [(264, 537), (284, 537), (304, 537)]. + # Т.о. каждый tuple в zip'е ниже будет иметь три элемента: индекс по 'x', индекс по 'y' и 'score'. + + '''x_arr = map(lambda x: int(x) + self._x, loc[1]) + y_arr = map(lambda y: int(y) + self._y, loc[0]) + s_arr = map(lambda s: float(s), res[loc[0], loc[1]]) + return zip(x_arr, y_arr, s_arr)''' + + return map(lambda x, y, s: (int(x) + self._reg_owner.x, int(y) + self._reg_owner.y, float(s)), loc[1], loc[0], res[loc[0], loc[1]]) + + def findAll(self, ps, delay_before=0): + ''' + Если ничего не найдено, то вернется пустой list, и исключения FindFailed не возникнет. + ''' + err_msg_template = '[error] Incorect \'findAll()\' method call:\n\tps = %s\n\ttypeOf ps=%s\n\tdelay_before = %s\n\tadditional comment: %%s' % (str(ps),type(ps), str(delay_before)) + + try: + delay_before = float(delay_before) + except ValueError: + raise FailExit(err_msg_template % 'delay_before is not float') + + ps = _get_list_of_patterns(ps, err_msg_template % 'bad \'ps\' argument; it should be a string (path to image file) or \'Pattern\' object') + + time.sleep(delay_before) + (pts, self._last_match) = ([], []) + try: + for p in ps: + pts.extend( self.__find(p, self.take_screenshot()) ) + self._last_match.extend( map(lambda pt: Match(pt[0], pt[1], p._w, p._h, p, pt[2]), pts) ) + + except FindFailed as ex: + dt = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') + + fn_field = os.path.join(settings.getFindFailedDir(), 'Region-findAll-field-' + dt + '-' + '+'.join([Pattern(p).getFilename(full_path=False) for p in ps]) + '.jpg') + cv2.imwrite(fn_field, ex.field, [cv2.IMWRITE_JPEG_QUALITY, 70]) + + fn_pattern = [] + for p in ex.patterns: + fn_pattern += [os.path.join(settings.getFindFailedDir(), 'Region-findAll-pattern-' + dt + '-' + p.getFilename(full_path=False) + '.jpg')] + cv2.imwrite(fn_pattern[-1], p._cv2_pattern, [cv2.IMWRITE_JPEG_QUALITY, 70]) + + logger.info('pikuli.Region.findAll: FindFailed; ps = {}' + '\n\tField stored as:\n\t\t[[f]]' + '\n\tPatterns strored as:\n\t\t{}'.format(ps, '\b\t\t'.join(['[[f]]'] * len(fn_pattern))), + extra={'f': [File(fn_field)] + [File(f) for f in fn_pattern]}) + + raise ex + else: + scores = '[' + ', '.join(['%.2f'%m.score for m in self._last_match]) + ']' + logger.info('pikuli.findAll: total found {} matches of <{}> in {}; scores = {}'.format( + len(self._last_match), str(ps), str(self), scores)) + return self._last_match + + def _wait_for_appear_or_vanish(self, ps, timeout, aov, exception_on_find_fail=None): + ''' + ps может быть String или List: + -- Если ps - это список (list) и aov == 'appear', то возвращается первый найденный элемент. Это можно использвоать, если требуется найти любое из переданных изображений. + -- Если ps - это список (list) и aov == 'vanish', то функция завершается, когда не будет найден хотя бы один из шаблонов. + + exception_on_find_fail -- необязательный аргумент True|False. Здесь нужен только для кастопизации вывода в лог в случае ненахождения паттерна. + ''' + ps = _get_list_of_patterns(ps, 'bad \'ps\' argument; it should be a string (path to image file) or \'Pattern\' object: %s' % str(ps)) + + if self.w == 0: + raise FailExit('bad rectangular area: self.w == 0') + if self.h == 0: + raise FailExit('bad rectangular area: self.h == 0') + + if timeout is None: + timeout = self._find_timeout + else: + try: + timeout = float(timeout) + if timeout < 0: + raise ValueError + except ValueError: + raise FailExit('bad argument: timeout = \'%s\'' % str(timeout)) + + prev_field = None + elaps_time = 0 + while True: + field = self.take_screenshot() + + if prev_field is None or (prev_field != field).all(): + for _ps_ in ps: + pts = self.__find(_ps_, field) + if aov == 'appear': + if len(pts) != 0: + # Что-то нашли. Выберем один вариант с лучшим 'score'. Из несольких с одинаковыми 'score' будет первый при построчном проходе по экрану. + pt = max(pts, key=lambda pt: pt[2]) + logger.info( 'pikuli.%s.: %s has been found' % (type(self).__name__, _ps_.getFilename(full_path=False))) + return Match(pt[0], pt[1], _ps_._w, _ps_._h, _ps_, pt[2]) + elif aov == 'vanish': + if len(pts) == 0: + logger.info( 'pikuli.%s.: %s has vanished' % (type(self).__name__, _ps_.getFilename(full_path=False))) + return + else: + raise FailExit('unknown \'aov\' = \'%s\'' % str(aov)) + + time.sleep(DELAY_BETWEEN_CV_ATTEMPT) + elaps_time += DELAY_BETWEEN_CV_ATTEMPT + if elaps_time >= timeout: + logger.info( 'pikuli.%s.: %s hasn\'t been found' % (type(self).__name__, _ps_.getFilename(full_path=False)) + + ', but exception was disabled.' if exception_on_find_fail is not None and not exception_on_find_fail else '' ) + #TODO: Какие-то ту ошибки. Да и следует передавать, наверно, картинки в FindFailed(), а где-то из модулей робота сохранять, если надо. + + failedImages = ', '.join(map(lambda p: p.getFilename(full_path=True), ps)) + raise FindFailed( + "Unable to find '{}' in {} after {} secs of trying".format(failedImages, self, elaps_time), + patterns=ps, field=field + ) + + def find(self, ps, timeout=None, exception_on_find_fail=True, save_img_file_at_fail=None): + ''' + Ждет, пока паттерн не появится. + + timeout определяет время, в течение которого будет повторяься неудавшийся поиск. Возможные значения: + timeout = 0 -- однократная проверка + timeout = None -- использование дефолтного значения + timeout = <число секунд> + + Возвращает Region, если паттерн появился. Если нет, то: + a. исключение FindFailed при exception_on_find_fail = True + b. возвращает None при exception_on_find_fail = False. + + save_img_file_at_fail -- Сохранять ли картинки при ошибке поиска: True|False|None. None -- значение берется из exception_on_find_fail. + ''' + try: + self._last_match = self._wait_for_appear_or_vanish(ps, timeout, 'appear', exception_on_find_fail=exception_on_find_fail) + + except FailExit: + self._last_match = None + raise FailExit('\nNew stage of %s\n[error] Incorect \'find()\' method call:\n\tself = %s\n\tps = %s\n\ttimeout = %s' % (traceback.format_exc(), str(self), str(ps), str(timeout))) + + except FindFailed as ex: + if save_img_file_at_fail or save_img_file_at_fail is None and exception_on_find_fail: + if not isinstance(ps, list): + ps = [ps] + dt = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') + + fn_field = os.path.join(settings.getFindFailedDir(), 'Region-find-field-' + dt + '-' + '+'.join([Pattern(p).getFilename(full_path=False) for p in ps]) + '.jpg') + cv2.imwrite(fn_field, ex.field, [cv2.IMWRITE_JPEG_QUALITY, 70]) + + fn_pattern = [] + for p in ex.patterns: + fn_pattern += [os.path.join(settings.getFindFailedDir(), 'Region-find-pattern-' + dt + '-' + p.getFilename(full_path=False) + '.jpg')] + cv2.imwrite(fn_pattern[-1], p._cv2_pattern, [cv2.IMWRITE_JPEG_QUALITY, 70]) + + logger.info('pikuli.Region.find: FindFailed; ps = {}' + '\n\tField stored as:\n\t\t[[f]]' + '\n\tPatterns strored as:\n\t\t{}'.format(ps, '\b\t\t'.join(['[[f]]'] * len(fn_pattern))), + extra={'f': [File(fn_field)] + [File(f) for f in fn_pattern]}) + + else: + logger.info('pikuli.Region.find: FindFailed; exception_on_find_fail = %s; ps = %s' % (str(exception_on_find_fail), str(ps))) + + if exception_on_find_fail or ex.cause != FindFailed.NOT_FOUND_ERROR: + raise ex + else: + return None + + else: + return self._last_match + + def waitVanish(self, ps, timeout=None): + ''' Ждет, пока паттерн не исчезнет. Если паттерна уже не было к началу выполнения процедуры, то завершается успешно. + timeout может быть положительным числом или None. timeout = 0 означает однократную проверку; None -- использование дефолтного значения.''' + try: + self._wait_for_appear_or_vanish(ps, timeout, 'vanish') + except FailExit: + raise FailExit('\nNew stage of %s\n[error] Incorect \'waitVanish()\' method call:\n\tself = %s\n\tps = %s\n\ttimeout = %s' % (traceback.format_exc(), str(self), str(ps), str(timeout))) + except FindFailed: + logger.info(str(ps)) + return False + else: + return True + finally: + self._last_match = None + + def exists(self, ps): + self._last_match = None + try: + self._last_match = self._wait_for_appear_or_vanish(ps, 0, 'appear') + except FailExit: + raise FailExit('\nNew stage of %s\n[error] Incorect \'exists()\' method call:\n\tself = %s\n\tps = %s' % (traceback.format_exc(), str(self), str(ps))) + except FindFailed: + logger.info(str(ps)) + return False + else: + return True + + def wait(self, ps=None, timeout=None): + ''' Для совместимости с Sikuli. Ждет появления паттерна или просто ждет. + timeout может быть положительным числом или None. timeout = 0 означает однократную проверку; None -- использование дефолтного значения.''' + if ps is None: + if timeout is not None: + time.sleep(timeout) + else: + try: + self._last_match = self._wait_for_appear_or_vanish(ps, timeout, 'appear') + except FailExit: + self._last_match = None + raise FailExit('\nNew stage of %s\n[error] Incorect \'wait()\' method call:\n\tself = %s\n\tps = %s\n\ttimeout = %s' % (traceback.format_exc(), str(self), str(ps), str(timeout))) + else: + return self._last_match + + def getLastMatch(self): + ''' Возвращает результаты последнего поиска. ''' + if self._last_match is None or self._last_match == []: + raise FindFailed('getLastMatch() is empty') + return self._last_match + + def set_find_timeout(self, timeout): + if timeout is None: + self._find_timeout = DEFAULT_FIND_TIMEOUT + else: + self._find_timeout = verify_timeout_argument(timeout, err_msg='[error] Incorect Region.set_find_timeout() method call') + + def get_find_timeout(self): + return self._find_timeout + + def find_all_solid_markers_by_piece(self, ps): + ''' + Ищет все почти solid-color маркеры. Выделяется группа найденных как один маркер -- + это нужно, т.к. шаблон ненмого меньше маркера в картинке и поэтому поиск находит несолько + в почти одном и том же месте + + Алгоритм: все найденные перекрывающиеся маркеры одного вида -- это один маркер. Его центр -- + это среднее между центрами всех найденных "фантомов". Если два маркера не перекрываются + между собой, но оба перекрываются с третьтим -- всех троих группируем в один. + + Если ничего не найдено, то возвращается пустой список. + ''' + + if not isinstance(ps, list): + ps = [ps] + + matches = [] # Список списков. В него будут помещаться + for p in ps: + unsorted_matches = self.findAll(p) # Несгруппированные вхождения шаблона + grouped_matches = [] + while len(unsorted_matches) > 0: + next_match = unsorted_matches.pop(0) + + # Добавим next_match в существующую гурппу ... + logger.info(grouped_matches) + for g in grouped_matches: + for m in g: + if abs(m.x - next_match.x) < next_match.w and \ + abs(m.y - next_match.y) < next_match.h: + g.append(next_match) + next_match = None + break + if next_match is None: + break + + # ... или созданим для него новую группу. + if next_match is not None: + grouped_matches.append([next_match]) + + matches.extend(grouped_matches) + + # Замени группы совпадений на итоговые классы Match: + for i in range(len(matches)): + sum_score = sum( [m.score for m in matches[i]] ) + x = sum( [m.x*m.score for m in matches[i]] ) / sum_score + y = sum( [m.y*m.score for m in matches[i]] ) / sum_score + matches[i] = Match(x, y, matches[i][0].w, matches[i][0].h, matches[i][0].pattern, sum_score/len(matches[i])) + + return matches diff --git a/pikuli/Screen.py b/pikuli/cv/screen.py similarity index 61% rename from pikuli/Screen.py rename to pikuli/cv/screen.py index f664eb2..dc09497 100644 --- a/pikuli/Screen.py +++ b/pikuli/cv/screen.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- -from . import _functions, FailExit, geom +from .. import FailExit +from .._functions import _screen_n_to_mon_descript +from ..geom import Region - -class Screen(geom.region.Region): +class Screen: """ Represents physical computer display screen. x, y -- left upper corner coords relative to virtual desktop. w, h -- screen rectangle area dimensions. @@ -19,14 +20,18 @@ def __init__(self, n): # For each monitor found, returns a handle to the monitor, # device context handle, and intersection rectangle: # (hMonitor, hdcMonitor, PyRECT). - self.__mon_hndl, _, mon_rect = \ - _functions._screen_n_to_mon_descript(self.n) + self.__mon_hndl, _, mon_rect = _screen_n_to_mon_descript(self.n) + + self._region = Region( + mon_rect[0], + mon_rect[1], + mon_rect[2] - mon_rect[0], + mon_rect[3] - mon_rect[1], + title='Screen ({})'.format(self.n)) - super(Screen, self).__init__(mon_rect[0], - mon_rect[1], - mon_rect[2] - mon_rect[0], - mon_rect[3] - mon_rect[1], - title='Screen ({})'.format(self.n)) + @property + def region(self): + return self._region def __repr__(self): return ''.format( diff --git a/pikuli/cv_element.py b/pikuli/cv_element.py deleted file mode 100644 index 0d188f8..0000000 --- a/pikuli/cv_element.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- - -from . import Region -from . import Location - - -class CVElement(object): - ''' - В конструктор класса передаются именно координаты области или ее центра. - В первом случае, область опеределна полностью, во втором -- достраеивается. - Достраивать можно просто из предположения о заранее известном размере контрола - в пикселях. Т.о., CVElement-класс как бы "натягивается" поверх нарисованных на - экране контролов в уже звестных местах -- внешняя функция должна распознать - компьюетрным зрение опорные изображения и вычислить координаты области, наокторую - "натягивать" это класс. Этим будут заниматься функции из wg-файлов. - - С дургой стороны, CVElement можно (да и правильно это будет) снабдить статическим - методом конструирования -- если его вызвать, то он вернет объект(ы) соответствующего - дочернего к CVElement класса, соответствующие искомым контролам. - - Но не будем эти классы использовать для построение иерархии контролов -- для этого - у нас есть wdb. Будем этого рода классы использовать именно для удобного получения - к функциям контрола (изменить поле, нажать конктреную кнопку и т.п.) - ''' - - def __init__(self, where_it_is): - if isinstance(where_it_is, Region): - self._reg = where_it_is - where_it_is = where_it_is.center - if not isinstance(where_it_is, Location): - raise Exception( - 'pikuli.cv_element.CVElement.__init__(): ' - 'input argument must be pikuli.Region or ' - 'pikuli.Location treating as a center ' - 'of the control:\n\twhere_it_is = %s' % str(where_it_is)) - self._center = where_it_is - if not hasattr(self, '_reg'): - self._reg = Region(self._center.x, self._center.y, 1, 1) - if self._reg.x == 0 or self._reg.y == 0: - raise Exception( - 'pikuli.cv_element.CVElement.__init__(): ' - 'you try to create \'%s\' with icvorrect width ' - 'or height:\n\t self._reg = %s' % (type(self).__name__, str(self._reg))) - - def reg(self): - return self._reg - - @classmethod - def find_us_in(cls, reg): - raise NotImplementedError( - 'pikuli.cv_element.CVElement.find_us_in(): ' - '[INTERNAL ERROR] You shoud implement this method ' - 'in child classes!') diff --git a/pikuli/geom/__init__.py b/pikuli/geom/__init__.py index a250cf7..fe329cb 100644 --- a/pikuli/geom/__init__.py +++ b/pikuli/geom/__init__.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +from .simple_types import Rectangle, Point + from .vector import Vector from .location import Location from .region import Region diff --git a/pikuli/geom/location.py b/pikuli/geom/location.py index 280c040..ae586c0 100644 --- a/pikuli/geom/location.py +++ b/pikuli/geom/location.py @@ -3,14 +3,12 @@ Представляет любую точку на экране. Содержит методы для перемещения точки на экране, перемещения курсора в точку на экране, эмуляции пользовательских действий (клики, ввод текста). ''' -import logging import time from collections import namedtuple from contextlib import contextmanager from pikuli.input import InputEmulator, KeyModifier, Key, ScrollDirection, ButtonCode -from pikuli._exceptions import PostMoveCheck -from pikuli._functions import FailExit, _take_screenshot +from pikuli._functions import FailExit, pixel_color_at from .vector import Vector, RelativeVec @@ -106,8 +104,7 @@ def _y_int(self): return int(round(self._y)) def get_color(self): - arr = _take_screenshot(self._x_int, self._y_int, 1, 1) - return Color(*arr.reshape(3)[::-1]) + return Color(*pixel_color_at(self._x_int, self._y_int)) def mouse_move(self, delay=0): """ diff --git a/pikuli/geom/region.py b/pikuli/geom/region.py index 8b8a75f..746a6ee 100644 --- a/pikuli/geom/region.py +++ b/pikuli/geom/region.py @@ -6,52 +6,27 @@ Контент может быть определен с поомощью методов Region.find() или Region.findAll(), которым передается объект класса Pattern (прямоугольная пиксельная область). Эти методы возвращают объект класса Match (потомок Region), имеющим те же свойства и методы, что и Region. Размеры Match равны размерам Pattern, используемого для поиска. ''' -import time -import traceback -import datetime import os -import logging +import traceback from collections import namedtuple -import cv2 -import numpy as np +from pikuli.geom.simple_types import Rectangle if os.name == 'nt': import win32gui -import pikuli -from pikuli import Settings, FindFailed, FailExit, File +from pikuli import FailExit, PikuliError, logger +from pikuli._functions import take_screenshot, highlight_region, SimpleImage -from pikuli._functions import _take_screenshot, verify_timeout_argument, highlight_region -from pikuli.Pattern import Pattern - -from .vector import RelativeVec from .location import Location - -#from Match import * -#from Screen import * - +from .vector import RelativeVec +from .simple_types import Rectangle RELATIONS = ['top-left', 'center'] -# Время в [c] между попытками распознования графического объекта -DELAY_BETWEEN_CV_ATTEMPT = 1.0 -DEFAULT_FIND_TIMEOUT = 3.1 +class Region(Rectangle): -from pikuli import logger - -def _get_list_of_patterns(ps, failExitText): - if not isinstance(ps, list): - ps = [ps] - for i, p in enumerate(ps): - try: - ps[i] = Pattern(p) - except Exception as ex: - raise FailExit(failExitText + '\n\t' + ' ' * 20 + str(ex)) - return ps - - -class Region(object): + _make_cv_methods_class_instance = None def __eq__(self, other): return (self.x, self.y, self.w, self.h) == (other.x, other.y, other.w, other.h) @@ -103,7 +78,7 @@ def __ge__(self, other): def __str__(self): return ''.format(self.x, self.y, self.w, self.h) - def __init__(self, *args, **kwargs): # relation='top-left', title=None): + def __init__(self, *args, **kwargs): ''' - Конструктор области. - @@ -128,10 +103,6 @@ def __init__(self, *args, **kwargs): # relation='top-left', title=None): main_window_hwnd -- Если не указан, но этот регион наследуется от другого региона, то пробуем взять оттуда. Если нет ничего, то определям hwnd главного окна (сразу после рабочего стола в деревер окон) под цетром прямоуголника. Если прямоугольник поверх рабочего стола, то будет hwnd = 0. - find_timeout -- Значение по умолчанию, которове будет использоваться, если метод find() (и подобные) этого класса вызван без явного указания timeout. - Если не передается конструктуру, то берется из переменной модуля DEFAULT_FIND_TIMEOUT. - Будет наслодоваться ко всем объектам, которые возвращаются методами этого класса. - Дополнительная справка: Внутренние поля класса: _x, _y -- левый верхнйи угол; будут проецироваться на x, y @@ -151,9 +122,9 @@ def __init__(self, *args, **kwargs): # relation='top-left', title=None): self.drag_location = None # "Объявляем" переменные, которые будут заданы ниже через self.setRect(...): - (self._x, self._y, self._w, self._h) = (None, None, None, None) + super(Region, self).__init__(None, None, None, None) self._last_match = None - self._image_at_some_moment = None + self.__stored_image = None self._title = None # Идентификатор для человека. if 'title' in kwargs: @@ -171,17 +142,12 @@ def __init__(self, *args, **kwargs): # relation='top-left', title=None): self.setRect(*args, **kwargs) except FailExit: raise FailExit('\nNew stage of %s\n[error] Incorect \'Region\' class constructor call:\n\targs = %s\n\tkwargs = %s' % (traceback.format_exc(), str(args), str(kwargs))) - self._find_timeout = verify_timeout_argument(kwargs.get('find_timeout', DEFAULT_FIND_TIMEOUT), err_msg='pikuli.%s.__init__()' % type(self).__name__) # Перезапишет, если создавали объект на основе существующего Region - - if os.name == 'nt': - self._main_window_hwnd = kwargs.get('main_window_hwnd', None) - if self._main_window_hwnd is None and len(args) == 1: - self._main_window_hwnd = args[0]._main_window_hwnd - if self._main_window_hwnd is None : - w = win32gui.WindowFromPoint((self._x + self._w // 2, self._y + self._h // 2)) - self._main_window_hwnd = pikuli.hwnd.hwnd_element._find_main_parent_window(w) - else: - self._main_window_hwnd = None + + @property + def cv(self): + if not self._make_cv_methods_class_instance: + raise PikuliError('Computer Vision (CV) methods aren\'t avaiable.') + return self._make_cv_methods_class_instance(self) def get_id(self): return self._id @@ -233,7 +199,7 @@ def set_h(self, h, relation='top-left'): def setRect(self, *args, **kwargs): try: - if len(args) == 1 and (isinstance(args[0], Region) or isinstance(args[0], Screen)): + if len(args) == 1 and isinstance(args[0], Region): self.__set_from_Region(args[0]) elif len(args) == 4: @@ -275,23 +241,6 @@ def __set_from_Region(self, reg): self._y = reg.y self._w = reg.w self._h = reg.h - self._find_timeout = reg._find_timeout - - @property - def x(self): - return self._x - - @property - def y(self): - return self._y - - @property - def w(self): - return self._w - - @property - def h(self): - return self._h def offset(self, *args, **kwargs): ''' @@ -306,9 +255,9 @@ def offset(self, *args, **kwargs): raise FailExit('[error] Unknown keys in kwargs = %s' % str(kwargs)) if len(args) == 2 and (isinstance(args[0], int) or isinstance(args[0], float)) and (isinstance(args[1], int) or isinstance(args[1], float)): - return Region(self._x + int(args[0]), self._y + int(args[1]), self._w, self._h, find_timeout=self._find_timeout) + return Region(self._x + int(args[0]), self._y + int(args[1]), self._w, self._h) elif len(args) == 1 and isinstance(args[0], Location): - return Region(self._x + args[0]._x, self._y + args[0]._y, self._w, self._h, find_timeout=self._find_timeout) + return Region(self._x + args[0]._x, self._y + args[0]._y, self._w, self._h) else: raise FailExit('[error] Incorect \'offset()\' method call:\n\targs = %s' % str(args)) @@ -317,9 +266,9 @@ def right(self, l=None): try: if l is None: scr = Screen('virt') - reg = Region(self._x + self._w, self._y, (scr.x + scr.w - 1) - (self._x + self._w) + 1, self._h, find_timeout=self._find_timeout) + reg = Region(self._x + self._w, self._y, (scr.x + scr.w - 1) - (self._x + self._w) + 1, self._h) elif isinstance(l, int) and l > 0: - reg = Region(self._x + self._w, self._y, l, self._h, find_timeout=self._find_timeout) + reg = Region(self._x + self._w, self._y, l, self._h) # elif isinstance(l, Region): -- TODO: до пересечения с ... Если внутри или снаружи. else: raise FailExit('type of \'l\' is %s; l = %s', (str(type(l)), str(l))) @@ -332,9 +281,9 @@ def left(self, l=None): try: if l is None: scr = Screen('virt') - reg = Region(scr.x, self._y, (self._x - 1) - scr.x + 1, self._h, find_timeout=self._find_timeout) + reg = Region(scr.x, self._y, (self._x - 1) - scr.x + 1, self._h) elif isinstance(l, int) and l > 0: - reg = Region(self._x - l, self._y, l, self._h, find_timeout=self._find_timeout) + reg = Region(self._x - l, self._y, l, self._h) # elif isinstance(l, Region): -- TODO: до пересечения с ... Если внутри или снаружи. else: raise FailExit() @@ -347,9 +296,9 @@ def above(self, l=None): try: if l is None: scr = Screen('virt') - reg = Region(self._x, scr.y, self._w, (self._y - 1) - scr.y + 1, find_timeout=self._find_timeout) + reg = Region(self._x, scr.y, self._w, (self._y - 1) - scr.y + 1) elif isinstance(l, int) and l > 0: - reg = Region(self._x, self._y - l, self._w, l, find_timeout=self._find_timeout) + reg = Region(self._x, self._y - l, self._w, l) # elif isinstance(l, Region): -- TODO: до пересечения с ... Если внутри или снаружи. else: raise FailExit() @@ -362,9 +311,9 @@ def below(self, l=None): try: if l is None: scr = Screen('virt') - reg = Region(self._x, self._y + self._h, self._w, (scr.y + scr.h - 1) - (self._y + self._h) + 1, find_timeout=self._find_timeout) + reg = Region(self._x, self._y + self._h, self._w, (scr.y + scr.h - 1) - (self._y + self._h) + 1) elif isinstance(l, int) and l > 0: - reg = Region(self._x, self._y + self._h, self._w, l, find_timeout=self._find_timeout) + reg = Region(self._x, self._y + self._h, self._w, l) # elif isinstance(l, Region): -- TODO: до пересечения с ... Если внутри или снаружи. else: raise FailExit() @@ -377,7 +326,7 @@ def nearby(self, l=0): try: if isinstance(l, int): if (l >= 0) or (l < 0 and (-2*l) < self._w and (-2*l) < self._h): - reg = Region(self._x - l, self._y - l, self._w + 2*l, self._h + 2*l, find_timeout=self._find_timeout) + reg = Region(self._x - l, self._y - l, self._w + 2*l, self._h + 2*l) else: raise FailExit() else: @@ -415,368 +364,11 @@ def center(self): return Location(self._x + self._w/2, self._y + self._h/2) @property - def wh(self): + def size(self): return (self.w, self.h) - def __get_field_for_find(self): - return self.get_raw_screenshot() - - def get_current_image(self): - ''' Возвращает текущий скриншот региона в виде картинки. В душе -- это np.array. ''' - return self.__get_field_for_find() - - def is_image_equal_to(self, img): - ''' Проверяет, что текущее изображение на экране в области (x,y,w,h) совпадет с - картинкой img в формате np.array, передаваеймо в функцию как рагумент. ''' - return np.array_equal(self.__get_field_for_find(), img) - - def store_current_image(self): - ''' Сохраняет в поле класса картинку с экрана из области (x,y,w,h). Формат -- numpu.array. ''' - self._image_at_some_moment = self.__get_field_for_find() - - def clear_sored_image(self): - ''' Очищает сохраненную в классе картинку. ''' - self._image_at_some_moment = None # TODO: вставить delete ??? - - def is_image_changed(self, rewrite_stored_image=False): - ''' Изменилась ли картинка на экране в регионе с момента последнего вызова этой фукнции - или self.store_current_image()? Отвечаем на этот вопрос путем сравнения сохраненной в классе - картинки с тем, что сейчас изоюражено на экране. - В зависости от аргумента rewrite_stored_image обновим или нет картинку, сохраненную в классе. ''' - img = self.__get_field_for_find() - eq = (self._image_at_some_moment is not None) and np.array_equal(img, self._image_at_some_moment) - if rewrite_stored_image: - self._image_at_some_moment = img - return (not eq) - - def _save_as_prep(self, full_filename, format_, msg, msg_loglevel): - if format_ not in ['jpg', 'png']: - logger.error('[INTERNAL] Unsupported format_={!r} at call of ' - 'Region._save_as_prep(...). Assume \'png\''.format(format_)) - format_ = 'png' - if msg_loglevel not in [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR]: - logger.warning('[INTERNAL] Unavailable msg_loglevel={!r} at call of ' - 'Region.save_as_{}(...). Assume INFO level.'.format(msg_loglevel, format_)) - msg_loglevel = logging.INFO - - path = os.path.abspath(full_filename) - - full_msg = 'pikuli.Region.save_as_{}:\n\tinput: {}\n\tfull path: [[f]]'.format(format_, self) - if msg: - full_msg = msg + '\n' + full_msg - logger.log(msg_loglevel, full_msg, extra={'f': File(path)}) - - dir_path = os.path.dirname(full_filename) - if not os.path.exists(dir_path): - os.makedirs(dir_path) - - return path - - def save_as_jpg(self, full_filename, msg='', msg_loglevel=logging.INFO): - path = self._save_as_prep(full_filename, 'jpg', msg, msg_loglevel) - cv2.imwrite(path, self.get_raw_screenshot(), [cv2.IMWRITE_JPEG_QUALITY, 70]) - - def save_as_png(self, full_filename, msg='', msg_loglevel=logging.INFO): - path = self._save_as_prep(full_filename, 'png', msg, msg_loglevel) - cv2.imwrite(path, self.get_raw_screenshot()) - - def save_in_findfailed(self, format_='jpg', msg='', msg_loglevel=logging.INFO): - assert format_ in ['png', 'jpg'] - - file_name = os.path.join( - pikuli.Settings.getFindFailedDir(), - 'ManuallyStored-{dt}-{reg}.{format}'.format( - dt=datetime.datetime.now().strftime('%Y_%m_%d-%H_%M_%S'), - reg='({},{},{},{})'.format(self._x, self._y, self._w, self._h), - format=format_)) - - if msg: - msg += ' ({} has been stored manually)'.format(self) - else: - msg = 'Mmanually storing of {}.'.format(self) - if format_ == 'jpg': - self.save_as_jpg(file_name, msg=msg, msg_loglevel=msg_loglevel) - else: - self.save_as_png(file_name, msg=msg, msg_loglevel=msg_loglevel) - - @property - def geometry(self): - return self._x, self._y, self._w, self._h - - def get_raw_screenshot(self): - """Returns Region screenshot as a 2D numpy array of int8 - """ - return _take_screenshot(*(self.geometry + (self._main_window_hwnd,))) - - def __find(self, ps, field): - # cv2.imshow('field', field) - # cv2.imshow('pattern', ps._cv2_pattern) - # cv2.waitKey(3*1000) - # cv2.destroyAllWindows() - - CF = 0 - try: - if CF == 0: - res = cv2.matchTemplate(field, ps._cv2_pattern, cv2.TM_CCORR_NORMED) - loc = np.where(res > ps.getSimilarity()) # 0.995 - elif CF == 1: - res = cv2.matchTemplate(field, ps._cv2_pattern, cv2.TM_SQDIFF_NORMED) - loc = np.where(res < 1.0 - ps.getSimilarity()) # 0.005 - except cv2.error as ex: - raise FindFailed('OpenCV ERROR: ' + str(ex), patterns=ps, field=field, cause=FindFailed.OPENCV_ERROR) - - # for pt in zip(*loc[::-1]): - # cv2.rectangle(field, pt, (pt[0] + self._w, pt[1] + self._h), (0, 0, 255), 2) - # cv2.imshow('field', field) - # cv2.imshow('pattern', ps._cv2_pattern) - # cv2.waitKey(0) - # cv2.destroyAllWindows() - - # 'res' -- Матрица, где каждый элемент содержит корреляуию кусочка "поля" с шаблоном. Каждый элемент - # матрицы соответствует пикселю из "поля". Индексация, вестимо, от нуля. - # 'loc' -- структура вида (array([264, 284, 304]), array([537, 537, 537])) где три пары индексов элементов матрицы 'res', - # для которых выполняется условие. Если из zip'нуть, то получиться [(264, 537), (284, 537), (304, 537)]. - # Т.о. каждый tuple в zip'е ниже будет иметь три элемента: индекс по 'x', индекс по 'y' и 'score'. - - '''x_arr = map(lambda x: int(x) + self._x, loc[1]) - y_arr = map(lambda y: int(y) + self._y, loc[0]) - s_arr = map(lambda s: float(s), res[loc[0], loc[1]]) - return zip(x_arr, y_arr, s_arr)''' - - #t = time.time() - #cv2.imwrite('c:\\tmp\\%i-%06i-field.png' % (int(t), (t-int(t))*10**6), field) - #cv2.imwrite('c:\\tmp\\%i-%06i-pattern.png' % (int(t), (t-int(t))*10**6), ps._cv2_pattern) - - return map(lambda x, y, s: (int(x) + self._x, int(y) + self._y, float(s)), loc[1], loc[0], res[loc[0], loc[1]]) - - - def findAll(self, ps, delay_before=0): - ''' - Если ничего не найдено, то вернется пустой list, и исключения FindFailed не возникнет. - ''' - err_msg_template = '[error] Incorect \'findAll()\' method call:\n\tps = %s\n\ttypeOf ps=%s\n\tdelay_before = %s\n\tadditional comment: %%s' % (str(ps),type(ps), str(delay_before)) - - try: - delay_before = float(delay_before) - except ValueError: - raise FailExit(err_msg_template % 'delay_before is not float') - - ps = _get_list_of_patterns(ps, err_msg_template % 'bad \'ps\' argument; it should be a string (path to image file) or \'Pattern\' object') - - time.sleep(delay_before) - (pts, self._last_match) = ([], []) - try: - for p in ps: - pts.extend( self.__find(p, self.__get_field_for_find()) ) - self._last_match.extend( map(lambda pt: Match(pt[0], pt[1], p._w, p._h, p, pt[2]), pts) ) - - except FindFailed as ex: - dt = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') - - fn_field = os.path.join(pikuli.Settings.getFindFailedDir(), 'Region-findAll-field-' + dt + '-' + '+'.join([Pattern(p).getFilename(full_path=False) for p in ps]) + '.jpg') - cv2.imwrite(fn_field, ex.field, [cv2.IMWRITE_JPEG_QUALITY, 70]) - - fn_pattern = [] - for p in ex.patterns: - fn_pattern += [os.path.join(pikuli.Settings.getFindFailedDir(), 'Region-findAll-pattern-' + dt + '-' + p.getFilename(full_path=False) + '.jpg')] - cv2.imwrite(fn_pattern[-1], p.get_image(), [cv2.IMWRITE_JPEG_QUALITY, 70]) - - logger.info('pikuli.Region.findAll: FindFailed; ps = {}' - '\n\tField stored as:\n\t\t[[f]]' - '\n\tPatterns strored as:\n\t\t{}'.format(ps, '\b\t\t'.join(['[[f]]'] * len(fn_pattern))), - extra={'f': [File(fn_field)] + [File(f) for f in fn_pattern]}) - - raise ex - else: - scores = '[' + ', '.join(['%.2f'%m.getScore() for m in self._last_match]) + ']' - logger.info('pikuli.findAll: total found {} matches of <{}> in {}; scores = {}'.format( - len(self._last_match), str(ps), str(self), scores)) - return self._last_match - - - def _wait_for_appear_or_vanish(self, ps, timeout, aov, exception_on_find_fail=None): - ''' - ps может быть String или List: - -- Если ps - это список (list) и aov == 'appear', то возвращается первый найденный элемент. Это можно использвоать, если требуется найти любое из переданных изображений. - -- Если ps - это список (list) и aov == 'vanish', то функция завершается, когда не будет найден хотя бы один из шаблонов. - - exception_on_find_fail -- необязательный аргумент True|False. Здесь нужен только для кастопизации вывода в лог в случае ненахождения паттерна. - ''' - ps = _get_list_of_patterns(ps, 'bad \'ps\' argument; it should be a string (path to image file) or \'Pattern\' object: %s' % str(ps)) - - if self.w == 0: - raise FailExit('bad rectangular area: self.w == 0') - if self.h == 0: - raise FailExit('bad rectangular area: self.h == 0') - - if timeout is None: - timeout = self._find_timeout - else: - try: - timeout = float(timeout) - if timeout < 0: - raise ValueError - except ValueError: - raise FailExit('bad argument: timeout = \'%s\'' % str(timeout)) - - prev_field = None - elaps_time = 0 - while True: - field = self.__get_field_for_find() - - if prev_field is None or (prev_field != field).all(): - for _ps_ in ps: - pts = self.__find(_ps_, field) - if aov == 'appear': - if len(pts) != 0: - # Что-то нашли. Выберем один вариант с лучшим 'score'. Из несольких с одинаковыми 'score' будет первый при построчном проходе по экрану. - pt = max(pts, key=lambda pt: pt[2]) - logger.info( 'pikuli.%s.: %s has been found' % (type(self).__name__, _ps_.getFilename(full_path=False))) - return Match(pt[0], pt[1], _ps_._w, _ps_._h, _ps_, pt[2]) - elif aov == 'vanish': - if len(pts) == 0: - logger.info( 'pikuli.%s.: %s has vanished' % (type(self).__name__, _ps_.getFilename(full_path=False))) - return - else: - raise FailExit('unknown \'aov\' = \'%s\'' % str(aov)) - - time.sleep(DELAY_BETWEEN_CV_ATTEMPT) - elaps_time += DELAY_BETWEEN_CV_ATTEMPT - if elaps_time >= timeout: - logger.info( 'pikuli.%s.: %s hasn\'t been found' % (type(self).__name__, _ps_.getFilename(full_path=False)) + - ', but exception was disabled.' if exception_on_find_fail is not None and not exception_on_find_fail else '' ) - #TODO: Какие-то ту ошибки. Да и следует передавать, наверно, картинки в FindFailed(), а где-то из модулей робота сохранять, если надо. - #t = time.time() - #cv2.imwrite(os.path.join(pikuli.Settings.getFindFailedDir, '%i-%06i-pattern.png' % (int(t), (t-int(t))*10**6)), ps[0]._cv2_pattern) - #cv2.imwrite(os.path.join(pikuli.Settings.getFindFailedDir, '%i-%06i-field.png' % (int(t), (t-int(t))*10**6)), field) - - #t = time.time() - #cv2.imwrite('d:\\tmp\\%i-%06i-pattern.png' % (int(t), (t-int(t))*10**6), ps[0]._cv2_pattern) - #cv2.imwrite('d:\\tmp\\%i-%06i-field.png' % (int(t), (t-int(t))*10**6), field) - #cv2.imwrite('c:\\tmp\\FindFailed-pattern.png', ps[0]._cv2_pattern) - #cv2.imwrite('c:\\tmp\\FindFailed-field.png', field) - - failedImages = ', '.join(map(lambda p: p.getFilename(full_path=True), ps)) - raise FindFailed( - "Unable to find '{}' in {} after {} secs of trying".format(failedImages, self, elaps_time), - patterns=ps, field=field - ) - - - def find(self, ps, timeout=None, exception_on_find_fail=True, save_img_file_at_fail=None): - ''' - Ждет, пока паттерн не появится. - - timeout определяет время, в течение которого будет повторяься неудавшийся поиск. Возможные значения: - timeout = 0 -- однократная проверка - timeout = None -- использование дефолтного значения - timeout = <число секунд> - - Возвращает Region, если паттерн появился. Если нет, то: - a. исключение FindFailed при exception_on_find_fail = True - b. возвращает None при exception_on_find_fail = False. - - save_img_file_at_fail -- Сохранять ли картинки при ошибке поиска: True|False|None. None -- значение берется из exception_on_find_fail. - ''' - #logger.info('pikuli.find: try to find %s' % str(ps)) - try: - self._last_match = self._wait_for_appear_or_vanish(ps, timeout, 'appear', exception_on_find_fail=exception_on_find_fail) - - except FailExit: - self._last_match = None - raise FailExit('\nNew stage of %s\n[error] Incorect \'find()\' method call:\n\tself = %s\n\tps = %s\n\ttimeout = %s' % (traceback.format_exc(), str(self), str(ps), str(timeout))) - - except FindFailed as ex: - if save_img_file_at_fail or save_img_file_at_fail is None and exception_on_find_fail: - if not isinstance(ps, list): - ps = [ps] - dt = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') - #self.save_as_jpg(os.path.join(pikuli.Settings.getFindFailedDir(), 'Region-find-' + dt + '_' + '+'.join([Pattern(p).getFilename(full_path=False) for p in ps]) + '.jpg')) - - fn_field = os.path.join(pikuli.Settings.getFindFailedDir(), 'Region-find-field-' + dt + '-' + '+'.join([Pattern(p).getFilename(full_path=False) for p in ps]) + '.jpg') - cv2.imwrite(fn_field, ex.field, [cv2.IMWRITE_JPEG_QUALITY, 70]) - - fn_pattern = [] - for p in ex.patterns: - fn_pattern += [os.path.join(pikuli.Settings.getFindFailedDir(), 'Region-find-pattern-' + dt + '-' + p.getFilename(full_path=False) + '.jpg')] - cv2.imwrite(fn_pattern[-1], p.get_image(), [cv2.IMWRITE_JPEG_QUALITY, 70]) - - logger.info('pikuli.Region.find: FindFailed; ps = {}' - '\n\tField stored as:\n\t\t[[f]]' - '\n\tPatterns strored as:\n\t\t{}'.format(ps, '\b\t\t'.join(['[[f]]'] * len(fn_pattern))), - extra={'f': [File(fn_field)] + [File(f) for f in fn_pattern]}) - - else: - logger.info('pikuli.Region.find: FindFailed; exception_on_find_fail = %s; ps = %s' % (str(exception_on_find_fail), str(ps))) - - if exception_on_find_fail or ex.cause != FindFailed.NOT_FOUND_ERROR: - raise ex - else: - return None - - else: - return self._last_match - - def waitVanish(self, ps, timeout=None): - ''' Ждет, пока паттерн не исчезнет. Если паттерна уже не было к началу выполнения процедуры, то завершается успешно. - timeout может быть положительным числом или None. timeout = 0 означает однократную проверку; None -- использование дефолтного значения.''' - try: - self._wait_for_appear_or_vanish(ps, timeout, 'vanish') - except FailExit: - raise FailExit('\nNew stage of %s\n[error] Incorect \'waitVanish()\' method call:\n\tself = %s\n\tps = %s\n\ttimeout = %s' % (traceback.format_exc(), str(self), str(ps), str(timeout))) - except FindFailed: - logger.info(str(ps)) - return False - else: - return True - finally: - self._last_match = None - - - def exists(self, ps): - self._last_match = None - try: - self._last_match = self._wait_for_appear_or_vanish(ps, 0, 'appear') - except FailExit: - raise FailExit('\nNew stage of %s\n[error] Incorect \'exists()\' method call:\n\tself = %s\n\tps = %s' % (traceback.format_exc(), str(self), str(ps))) - except FindFailed: - logger.info(str(ps)) - return False - else: - return True - - - def wait(self, ps=None, timeout=None): - ''' Для совместимости с Sikuli. Ждет появления паттерна или просто ждет. - timeout может быть положительным числом или None. timeout = 0 означает однократную проверку; None -- использование дефолтного значения.''' - if ps is None: - if timeout is not None: - time.sleep(timeout) - else: - try: - self._last_match = self._wait_for_appear_or_vanish(ps, timeout, 'appear') - except FailExit: - self._last_match = None - raise FailExit('\nNew stage of %s\n[error] Incorect \'wait()\' method call:\n\tself = %s\n\tps = %s\n\ttimeout = %s' % (traceback.format_exc(), str(self), str(ps), str(timeout))) - else: - return self._last_match - - - def getLastMatch(self): - ''' Возвращает результаты последнего поиска. ''' - if self._last_match is None or self._last_match == []: - raise FindFailed('getLastMatch() is empty') - return self._last_match - - - def set_find_timeout(self, timeout): - if timeout is None: - self._find_timeout = DEFAULT_FIND_TIMEOUT - else: - self._find_timeout = verify_timeout_argument(timeout, err_msg='[error] Incorect Region.set_find_timeout() method call') - - def get_find_timeout(self): - return self._find_timeout + def take_screenshot(self) -> SimpleImage: + return take_screenshot(self) def click(self, after_click_delay=0, p2c_notif=True): self.center.click(after_cleck_delay=after_click_delay, p2c_notif=False) @@ -795,11 +387,12 @@ def doubleClick(self, after_cleck_delay=0, p2c_notif=True): def type(self, text, modifiers=None, click=True, press_enter=False, p2c_notif=True): ''' Не как в Sikuli ''' - self.center.type(text, - modifiers=modifiers, - press_enter=press_enter, - click=click, - p2c_notif=False) + self.center.type( + text, + modifiers=modifiers, + press_enter=press_enter, + click=click, + p2c_notif=False) if p2c_notif: logger.info('pikuli.%s.type(): \'%s\' was typed in center of %s; click=%s, modifiers=%s' % (type(self).__name__, repr(text), str(self), str(click), str(modifiers))) @@ -891,54 +484,3 @@ def abs2rel(self, *args): def is_visible(self): return not (isinstance(self.x, float) and isinstance(self.y, float) and isinstance(self.h, float) and isinstance(self.w, float)) - - def find_all_solid_markers_by_piece(self, ps): - ''' - Ищет все почти solid-color маркеры. Выделяется группа найденных как один маркер -- - это нужно, т.к. шаблон ненмого меньше маркера в картинке и поэтому поиск находит несолько - в почти одном и том же месте - - Алгоритм: все найденные перекрывающиеся маркеры одного вида -- это один маркер. Его центр -- - это среднее между центрами всех найденных "фантомов". Если два маркера не перекрываются - между собой, но оба перекрываются с третьтим -- всех троих группируем в один. - - Если ничего не найдено, то возвращается пустой список. - ''' - - if not isinstance(ps, list): - ps = [ps] - - matches = [] # Список списков. В него будут помещаться - for p in ps: - unsorted_matches = self.findAll(p) # Несгруппированные вхождения шаблона - grouped_matches = [] - while len(unsorted_matches) > 0: - next_match = unsorted_matches.pop(0) - - # Добавим next_match в существующую гурппу ... - logger.info(grouped_matches) - for g in grouped_matches: - for m in g: - if abs(m.x - next_match.x) < next_match.w and \ - abs(m.y - next_match.y) < next_match.h: - g.append(next_match) - next_match = None - break - if next_match is None: - break - - # ... или созданим для него новую группу. - if next_match is not None: - grouped_matches.append([next_match]) - - matches.extend(grouped_matches) - - # Замени группы совпадений на итоговые классы Match: - for i in range(len(matches)): - sum_score = sum( [m.getScore() for m in matches[i]] ) - x = sum( [m.x*m.getScore() for m in matches[i]] ) / sum_score - y = sum( [m.y*m.getScore() for m in matches[i]] ) / sum_score - matches[i] = Match(x, y, matches[i][0].w, matches[i][0].h, matches[i][0].getPattern(), sum_score/len(matches[i])) - - return matches - diff --git a/pikuli/geom/simple_types.py b/pikuli/geom/simple_types.py new file mode 100644 index 0000000..3e16b61 --- /dev/null +++ b/pikuli/geom/simple_types.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +class Rectangle: + + def __init__(self, x, y, w, h): + self._x = x + self._y = y + self._w = w + self._h = h + + @property + def x(self): + return self._x + + @property + def y(self): + return self._y + + @property + def w(self): + return self._w + + @property + def h(self): + return self._h + +class Point: + + def __init__(self, x, y): + self._x = x + self._y = y + + @property + def x(self): + return self._x + + @property + def y(self): + return self._y \ No newline at end of file diff --git a/pikuli/geom/vector.py b/pikuli/geom/vector.py index b674595..2fb39bb 100644 --- a/pikuli/geom/vector.py +++ b/pikuli/geom/vector.py @@ -2,24 +2,26 @@ from math import sqrt +from .simple_types import Point -class Vector(object): +class Vector(Point): def __init__(self, *args): """ Координаты -- вещественые числа. Или :class:`Vector`. """ if len(args) == 2: - self._x = float(args[0]) - self._y = float(args[1]) - elif len(args) == 1 and isinstance(args[0], Vector): - self._x = float(args[0]._x) - self._y = float(args[0]._y) + x = float(args[0]) + y = float(args[1]) + elif len(args) == 1 and isinstance(args[0], Point): + x = float(args[0].x) + y = float(args[0].y) elif len(args) == 1 and isinstance(args[0], (list, tuple)): - self._x = float(args[0][0]) - self._y = float(args[0][1]) + x = float(args[0][0]) + y = float(args[0][1]) else: raise Exception('{}'.format(args)) + super(Vector, self).__init__(x, y) def __add__(self, other): """ @@ -87,14 +89,6 @@ def hinv(self): """ return self.__class__(1.0 / self.x, 1.0 / self.y) - @property - def x(self): - return self._x - - @property - def y(self): - return self._y - @property def xy(self): return tuple(self) diff --git a/pikuli/hwnd/hwnd_element.py b/pikuli/hwnd/hwnd_element.py index 7c97b21..0c7f4ad 100644 --- a/pikuli/hwnd/hwnd_element.py +++ b/pikuli/hwnd/hwnd_element.py @@ -2,10 +2,8 @@ ''' Субмодуль работы с контролами через win32api. ''' -import os import re import types -import logging import psutil @@ -19,14 +17,10 @@ import comtypes import comtypes.client - - -import pikuli from pikuli._exceptions import FindFailed from pikuli.geom import Region -from pikuli import wait_while, wait_while_not -from pikuli import logger - +from pikuli.uia import UIAElement +from pikuli import wait_while, wait_while_not, logger ''' !!! TODO: !!! @@ -225,7 +219,7 @@ def EnumChildWindows_callback(hwnd, extra): if not extra['res']: raise Exception('pikuli.HWNDElement: constructor error: hwnd = %s is not child for main window %s' % (str(hwnd), str(self.hwnd_main_win)))''' - elif len(args) == 1 and isinstance(args[0], pikuli.uia.UIAElement): + elif len(args) == 1 and isinstance(args[0], UIAElement): if args[0].hwnd is None or args[0].hwnd == 0: raise Exception('pikuli.HWNDElement: constructor error: args[0].hwnd is None or args[0].hwnd == 0:; args = %s' % str(args)) self.hwnd = args[0].hwnd @@ -503,23 +497,3 @@ def get_combobox_text(self): def get_parent(self): ''' Вернет HWNDElement для родительского окна (в широком виндовом смысле "окна"). ''' return HWNDElement(GetParent(self.hwnd)) - - - - -""" -import pywinauto -win32defines = pywinauto.win32defines - -''' --= Tree View: =- - pywinauto.controls.common_controls.TreeViewWrapper (https://github.com/pywinauto/pywinauto) - "About Tree-View Controls" (https://msdn.microsoft.com/en-us/en-en/library/windows/desktop/bb760017(v=vs.85).aspx) - "Using Tree-View Controls" (https://msdn.microsoft.com/en-us/en-en/library/windows/desktop/bb773409(v=vs.85).aspx) -''' - -def _treeview_element__reg(self): - rect = self.Rectangle() - return geom.Region(rect.left, rect.top, rect.width, rect.height) -setattr(pywinauto.controls.common_controls._treeview_element, 'reg', _treeview_element__reg) -""" diff --git a/pikuli/input/helper_types.py b/pikuli/input/helper_types.py index 1c3f5e1..dadf240 100644 --- a/pikuli/input/helper_types.py +++ b/pikuli/input/helper_types.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -import os import traceback from collections import namedtuple diff --git a/pikuli/uia/control_wrappers/check_box.py b/pikuli/uia/control_wrappers/check_box.py index 94c3372..d97888e 100644 --- a/pikuli/uia/control_wrappers/check_box.py +++ b/pikuli/uia/control_wrappers/check_box.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- - -from pikuli import wait_while_not, wait_while +from pikuli.utils import wait_while_not, wait_while from pikuli.uia.adapter import Enums, STATE_SYSTEM from . import CONTROL_CHECK_TIMEOUT from .uia_control import UIAControl diff --git a/pikuli/uia/control_wrappers/combo_box.py b/pikuli/uia/control_wrappers/combo_box.py index bd7a997..23136f1 100644 --- a/pikuli/uia/control_wrappers/combo_box.py +++ b/pikuli/uia/control_wrappers/combo_box.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- -from pikuli import FindFailed, wait_while_not +from pikuli import FindFailed +from pikuli.utils import wait_while_not from . import CONTROL_CHECK_TIMEOUT from .uia_control import UIAControl diff --git a/pikuli/uia/control_wrappers/mixin.py b/pikuli/uia/control_wrappers/mixin.py index d93a616..bf2ab8a 100644 --- a/pikuli/uia/control_wrappers/mixin.py +++ b/pikuli/uia/control_wrappers/mixin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- -from pikuli import logger, wait_while_not +from pikuli import logger +from pikuli.utils import wait_while_not from pikuli.input import Key, KeyModifier, InputEmulator, Clipboard from pikuli.uia import UIAElement diff --git a/pikuli/uia/uia_element.py b/pikuli/uia/uia_element.py index 33ccb82..23c83f3 100644 --- a/pikuli/uia/uia_element.py +++ b/pikuli/uia/uia_element.py @@ -6,7 +6,6 @@ import traceback import sys import re -import logging import json import os @@ -22,7 +21,7 @@ import pikuli.uia -from pikuli import wait_while +from pikuli.utils import wait_while from pikuli.geom import Region from pikuli._functions import verify_timeout_argument from pikuli._exceptions import FindFailed, FailExit diff --git a/pikuli/utils.py b/pikuli/utils.py index 469958e..9b18bfe 100644 --- a/pikuli/utils.py +++ b/pikuli/utils.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +import time from datetime import datetime From c75c6b97b272f4d7e933f98033b85d844b2c862f Mon Sep 17 00:00:00 2001 From: Nikita Voronchev Date: Mon, 24 Jan 2022 20:50:59 +0300 Subject: [PATCH 2/2] Refactoring: Remove extra empty lines --- pikuli/_exceptions.py | 4 -- pikuli/_functions.py | 6 --- pikuli/_helpers.py | 1 - pikuli/geom/location.py | 2 - pikuli/geom/region.py | 1 - pikuli/geom/vector.py | 2 - pikuli/hwnd/hwnd_element.py | 16 -------- pikuli/input/constants.py | 1 - pikuli/input/helper_types.py | 3 -- pikuli/input/input_emulator.py | 3 -- pikuli/input/keys.py | 5 --- pikuli/input/linux_evdev/input_emulator.py | 8 ---- pikuli/input/linux_gtk3/clipboard.py | 1 - pikuli/input/linux_gtk3/input_emulator.py | 1 - pikuli/input/linux_x11/input_emulator.py | 3 -- pikuli/input/platform_init.py | 2 - pikuli/input/windows/clipboard.py | 1 - pikuli/input/windows/input_emulator.py | 6 --- pikuli/mouse_cursor.py | 3 -- pikuli/uia/__init__.py | 1 - pikuli/uia/adapter/adapter.py | 2 - pikuli/uia/adapter/adapter_base.py | 1 - pikuli/uia/adapter/dotnet/adapter.py | 3 -- .../uia/adapter/dotnet/automation_element.py | 1 - pikuli/uia/adapter/dotnet/condition.py | 1 - pikuli/uia/adapter/dotnet/pattern_factory.py | 2 - pikuli/uia/adapter/dotnet/tree_walker.py | 1 - pikuli/uia/adapter/dotnet/value_converters.py | 1 - pikuli/uia/adapter/helper_types.py | 4 -- pikuli/uia/adapter/identifer_names.py | 3 -- pikuli/uia/adapter/oleacc_h.py | 1 - pikuli/uia/adapter/pattern_description.py | 3 -- .../uia/adapter/patterns_plain_description.py | 1 - pikuli/uia/adapter/platform_init.py | 1 - .../uia/adapter/property_value_converter.py | 3 -- pikuli/uia/adapter/property_value_types.py | 1 - pikuli/uia/adapter/sdk_enums.py | 40 ------------------- pikuli/uia/adapter/win_native/adapter.py | 3 -- .../adapter/win_native/automation_element.py | 1 - pikuli/uia/adapter/win_native/condition.py | 1 - .../uia/adapter/win_native/pattern_factory.py | 1 - pikuli/uia/adapter/win_native/tree_walker.py | 1 - .../adapter/win_native/value_converters.py | 1 - pikuli/uia/control_wrappers/button.py | 1 - pikuli/uia/control_wrappers/check_box.py | 2 - pikuli/uia/control_wrappers/combo_box.py | 1 - pikuli/uia/control_wrappers/custom_control.py | 1 - pikuli/uia/control_wrappers/data_grid.py | 2 - pikuli/uia/control_wrappers/data_item.py | 1 - pikuli/uia/control_wrappers/desktop.py | 1 - pikuli/uia/control_wrappers/document.py | 1 - pikuli/uia/control_wrappers/group.py | 1 - pikuli/uia/control_wrappers/header.py | 2 - pikuli/uia/control_wrappers/header_item.py | 1 - pikuli/uia/control_wrappers/hyperlink.py | 1 - pikuli/uia/control_wrappers/image.py | 1 - pikuli/uia/control_wrappers/list.py | 1 - pikuli/uia/control_wrappers/list_item.py | 1 - pikuli/uia/control_wrappers/menu.py | 1 - pikuli/uia/control_wrappers/menu_bar.py | 1 - pikuli/uia/control_wrappers/menu_item.py | 1 - pikuli/uia/control_wrappers/mixin.py | 6 --- pikuli/uia/control_wrappers/pane.py | 1 - pikuli/uia/control_wrappers/progress_bar.py | 1 - pikuli/uia/control_wrappers/property_grid.py | 3 -- pikuli/uia/control_wrappers/radio_button.py | 1 - .../registred_control_classes.py | 1 - pikuli/uia/control_wrappers/scroll_bar.py | 1 - pikuli/uia/control_wrappers/separator.py | 1 - pikuli/uia/control_wrappers/spinner.py | 1 - pikuli/uia/control_wrappers/split_button.py | 1 - pikuli/uia/control_wrappers/status_bar.py | 1 - pikuli/uia/control_wrappers/tab.py | 1 - pikuli/uia/control_wrappers/tab_item.py | 1 - pikuli/uia/control_wrappers/text.py | 1 - pikuli/uia/control_wrappers/thumb.py | 2 - pikuli/uia/control_wrappers/title_bar.py | 1 - pikuli/uia/control_wrappers/tool_bar.py | 1 - pikuli/uia/control_wrappers/tree.py | 1 - pikuli/uia/control_wrappers/tree_item.py | 1 - pikuli/uia/control_wrappers/uia_control.py | 1 - pikuli/uia/control_wrappers/window.py | 1 - pikuli/uia/exceptions.py | 2 - pikuli/uia/pattern.py | 1 - pikuli/uia/pattern_method.py | 1 - pikuli/uia/uia_element.py | 4 -- pikuli/utils.py | 3 -- 87 files changed, 208 deletions(-) diff --git a/pikuli/_exceptions.py b/pikuli/_exceptions.py index 2ff3495..c98a2f7 100644 --- a/pikuli/_exceptions.py +++ b/pikuli/_exceptions.py @@ -2,19 +2,15 @@ import traceback - class PikuliError(RuntimeError): pass - class PostMoveCheck(PikuliError): pass - class FailExit(PikuliError): pass - class FindFailed(PikuliError): """ This exception is raised when an image pattern is not found on the screen. diff --git a/pikuli/_functions.py b/pikuli/_functions.py index 0016b7c..ced726c 100644 --- a/pikuli/_functions.py +++ b/pikuli/_functions.py @@ -23,7 +23,6 @@ # Константа отсутствует в win32con, но есть в http://userpages.umbc.edu/~squire/download/WinGDI.h: CAPTUREBLT = 0x40000000 - def verify_timeout_argument(timeout, allow_None=False, err_msg='pikuli.verify_timeout_argument()'): if timeout is None and allow_None: return None @@ -35,7 +34,6 @@ def verify_timeout_argument(timeout, allow_None=False, err_msg='pikuli.verify_ti raise FailExit('%s: wrong timeout = \'%s\' (%s)' % (str(err_msg), str(timeout), str(ex))) return timeout - def addImagePath(path): settings.addImagePath(path) @@ -56,12 +54,10 @@ def _monitor_hndl_to_screen_n(m_hndl): raise FailExit('can not obtaen Screen number from win32api.GetMonitorInfo() = %s' % str(minfo)) return screen_n - def _screen_n_to_monitor_name(n): ''' Экраны-мониторы нуменруются от 1. Нулевой экран -- это полный вирутальный. ''' return r'\\.\DISPLAY%i' % n - def _screen_n_to_mon_descript(n): ''' Returns a sequence of tuples. For each monitor found, returns a handle to the monitor, device context handle, and intersection rectangle: (hMonitor, hdcMonitor, PyRECT) ''' @@ -79,7 +75,6 @@ def _screen_n_to_mon_descript(n): raise FailExit('wrong screen number \'%s\'' % str(n)) return m - def highlight_region(x, y, w, h, delay=0.5): def _cp_boundary(dest_dc, dest_x0, dest_y0, src_dc, src_x0, src_y0, w, h): win32gui.BitBlt(dest_dc, dest_x0+0, dest_y0+0, w, 1, src_dc, src_x0, src_y0, win32con.SRCCOPY) @@ -172,7 +167,6 @@ def take_screenshot(rect: Rectangle) -> SimpleImage: def pixel_color_at(x, y, monitor_number=1): return pixels_colors_at([(x, y)], monitor_number)[0] - def pixels_colors_at(coords_tuple_list, monitor_number=1): with mss.mss() as sct: sct_img = sct.grab(sct.monitors[monitor_number]) # некст по умолчанию выводится на первый монитор diff --git a/pikuli/_helpers.py b/pikuli/_helpers.py index bb7cec3..6308858 100644 --- a/pikuli/_helpers.py +++ b/pikuli/_helpers.py @@ -7,7 +7,6 @@ class NotImplemetedDummyBase(object): err_msg = None - class NotImplemetedDummyFactory(object): class _AttrPlaceholder(object): diff --git a/pikuli/geom/location.py b/pikuli/geom/location.py index ae586c0..1285e75 100644 --- a/pikuli/geom/location.py +++ b/pikuli/geom/location.py @@ -12,7 +12,6 @@ from .vector import Vector, RelativeVec - DRAGnDROP_MOVE_DELAY = 0.005 DRAGnDROP_MOVE_STEP = 6 @@ -399,7 +398,6 @@ def distance_to(self, *args): loc = Location(*args) return abs(self - loc) - class Location(LocationF): def __init__(self, *args, **kwargs): diff --git a/pikuli/geom/region.py b/pikuli/geom/region.py index 746a6ee..740bb04 100644 --- a/pikuli/geom/region.py +++ b/pikuli/geom/region.py @@ -196,7 +196,6 @@ def set_h(self, h, relation='top-left'): else: raise FailExit('[error] Incorect Region.set_h(...) method call:\n\th = %s, %s\n\trelation = %s' % (str(h), type(h), str(relation))) - def setRect(self, *args, **kwargs): try: if len(args) == 1 and isinstance(args[0], Region): diff --git a/pikuli/geom/vector.py b/pikuli/geom/vector.py index 2fb39bb..07c1b67 100644 --- a/pikuli/geom/vector.py +++ b/pikuli/geom/vector.py @@ -122,8 +122,6 @@ def targer(): yield self.y return targer() - - class RelativeVec(Vector): """ Вектор, координаты которого изменяются в интервале [0.0; 100.0]. diff --git a/pikuli/hwnd/hwnd_element.py b/pikuli/hwnd/hwnd_element.py index 0c7f4ad..653e1eb 100644 --- a/pikuli/hwnd/hwnd_element.py +++ b/pikuli/hwnd/hwnd_element.py @@ -42,15 +42,12 @@ CHECKED = 0x000010 FOCUSED = 0x000004 - # # Словарь "системых" title'ов. Если title не строка, а число отсюда, то title интерпретируется не просто как заголвок окна или текст лейбла, а как указание на какой-то объект. # SYS_TITLES = {'main_window': 0} - def _hwnd2wf(hwnd): return HWNDElement(hwnd) - def _is_visible(hwnd0): ''' Определяет свойство visible окна hwnd, а также проверяет наследвоание этого свойства от всех родительских окон. ''' def _iv(hwnd): @@ -61,7 +58,6 @@ def _iv(hwnd): return _iv(GetParent(hwnd)) return _iv(hwnd0) - def _find_main_parent_window(child_hwnd, child_pid=None): ''' Для указанного (дочеренего) окна ищет самое-самое родительское. Если child_pid=None, то родительским будет объявлено то, у кого родитель -- "рабочий стол" (hwnd = 0). Если указан child_pid, то при проверке родительских @@ -83,7 +79,6 @@ def _fmpw(child_hwnd, child_pid): return 0 return _fmpw(child_hwnd, child_pid) - def _find_all_windows_by_pid(pid): ''' @@ -111,7 +106,6 @@ def EnumWindows_callback(hwnd, extra): # Callback на пе return extra['hwnds'] - def _find_window_by_process_name_and_title(proc_name, in_title): ''' По имени exe-файла и текстут в заголовке (in_title -- список строк, искомых в заголовке) ищет окно. Это может быть как обычное окно, так и любой другой windows-контрол. ''' @@ -147,7 +141,6 @@ def EnumWindows_callback(hwnd, extra): # Callback на пе return (extra['pid'], extra['hwnd']) - class HWNDElement(object): ''' Доступные поля: @@ -247,7 +240,6 @@ def set_id(self, id): def title(self): return GetWindowText(self.hwnd) - '''def _hwnd2reg(self, hwnd, title=None): # полчение размеров клменскй области окна (_, _, wc, hc) = GetClientRect(hwnd) @@ -260,15 +252,12 @@ def title(self): reg.winctrl = HWNDElement(hwnd) return reg''' - def is_empty(self): return (self.proc_name is None) - def find_all(self, win_class, title, process_name=False, title_regexp=False, max_depth_level=None, depth_level=None): return self.find(win_class, title, process_name=process_name, title_regexp=title_regexp, find_all=True, max_depth_level=max_depth_level, depth_level=depth_level) - def find(self, win_class, title, process_name=False, title_regexp=False, find_all=False, max_depth_level=None, depth_level=None): #, timeout=None): ''' Поиск дочернего окна-объекта любого уровня вложенности. Под окном пнимается любой WinForms-элемент любого класса. @@ -365,7 +354,6 @@ def EnumChildWindows_callback(hwnd, extra): else: raise FindFailed('pikuli.HWNDElement.find: window %s with win_class = \'%s\' and title = \'%s\' has visible = False.' % (hex(extra['hwnds'][0]), str(win_class), str(title))) - def reg(self, force_new_reg=False): ''' Возвращает Region для self-элемента HWNDElement. ''' if self.is_empty(): @@ -443,7 +431,6 @@ def wait_for_button_unchecked(self, timeout=5): if not wait_while(self.is_button_checked, timeout): raise Exception('pikuli.HWNDElement: wait_for_button_unchecked(...) of %s was failed' % str(self)) - def is_button_marked(self): ''' В первую очередь, речь идет о кнопке Applay. @@ -467,7 +454,6 @@ def wait_for_button_unmarked(self, timeout=5): if not wait_while(self.is_button_marked, timeout): raise Exception('pikuli.HWNDElement: wait_for_button_unmarked(...) of %s was failed' % str(self)) - def get_editbox_text(self): ''' Вернет текст поля ввода ''' if self.is_empty(): @@ -480,7 +466,6 @@ def get_editbox_text(self): else: raise Exception('TODO') - def get_combobox_text(self): ''' Вернет текст поля combobox ''' if self.is_empty(): @@ -493,7 +478,6 @@ def get_combobox_text(self): else: raise NotImplementedError - def get_parent(self): ''' Вернет HWNDElement для родительского окна (в широком виндовом смысле "окна"). ''' return HWNDElement(GetParent(self.hwnd)) diff --git a/pikuli/input/constants.py b/pikuli/input/constants.py index af83c2c..eaf6fc0 100644 --- a/pikuli/input/constants.py +++ b/pikuli/input/constants.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- - DELAY_KBD_KEY_PRESS = 0.02 DELAY_KBD_KEY_RELEASE = 0.02 diff --git a/pikuli/input/helper_types.py b/pikuli/input/helper_types.py index dadf240..ba6ef6b 100644 --- a/pikuli/input/helper_types.py +++ b/pikuli/input/helper_types.py @@ -6,10 +6,8 @@ from pikuli import logger from pikuli._helpers import NotImplemetedDummyFactory - WindowsButtonCode = namedtuple('WindowsButtonCode', ['event_down', 'event_up']) - class _HookedClassInitMeta(type): HOOKED_INIT_CLASS_METHODNAME = '__hooked_class_init' @@ -64,6 +62,5 @@ def get_private_name(cls, attr_name): cls_name=cls.__name__, attr_name=attr_name) - class _HookedClassInit(metaclass=_HookedClassInitMeta): pass diff --git a/pikuli/input/input_emulator.py b/pikuli/input/input_emulator.py index 68fae32..27c1e6f 100644 --- a/pikuli/input/input_emulator.py +++ b/pikuli/input/input_emulator.py @@ -13,7 +13,6 @@ from .keys import InputSequence, Key, KeyModifier from .platform_init import ButtonCode, KeyCode, OsKeyboardMixin, OsMouseMixin - class KeyboardMixin(object): #_PrintableChars = set(string.printable) - set(????) @@ -104,7 +103,6 @@ def _do_modifier_keys_action(cls, modifiers, action): for m in modifiers: action(m.key_code) - class MouseMixin(object): @classmethod @@ -162,7 +160,6 @@ def scroll(cls, direction, count=1, step=1): cls._do_scroll(direction, step=step) time.sleep(DELAY_MOUSE_SCROLL) - class InputEmulator( KeyboardMixin, MouseMixin, OsKeyboardMixin, OsMouseMixin): diff --git a/pikuli/input/keys.py b/pikuli/input/keys.py index cc4de5f..0e32036 100644 --- a/pikuli/input/keys.py +++ b/pikuli/input/keys.py @@ -2,7 +2,6 @@ from .platform_init import KeyCode, ScrollDirection - class InputSequence(object): def __init__(self, obj): @@ -57,14 +56,12 @@ def __iter__(self): def is_empty(self): return len(self._container) == 0 - class KeyMeta(EnumMeta): def __new__(mcs, name, bases, dct): for e in KeyCode: dct[e.name] = e.value return super(KeyMeta, mcs).__new__(mcs, name, bases, dct) - class KeyBaseEnum(int, Enum): @property @@ -94,7 +91,6 @@ def _duplicate(self, times): raise ValueError("Operand should be int: {!r}".format(times)) return InputSequence(self)._repeat(times) - class Key(KeyBaseEnum, metaclass=KeyMeta): """ Коды специальных клавиш. Позволяют легко добавлять их к строкам в коде. К примеру: @@ -104,7 +100,6 @@ class Key(KeyBaseEnum, metaclass=KeyMeta): """ pass - class KeyModifier(KeyBaseEnum): """ Аргумент modifiers функции type_text(). diff --git a/pikuli/input/linux_evdev/input_emulator.py b/pikuli/input/linux_evdev/input_emulator.py index bca04a4..374a1e8 100644 --- a/pikuli/input/linux_evdev/input_emulator.py +++ b/pikuli/input/linux_evdev/input_emulator.py @@ -7,7 +7,6 @@ from evdev import ecodes from evdev.uinput import UInput - class EvdevKeyCodes(int, Enum): """ `evdev` key codes (see `linux/include/uapi/linux/input-event-codes.h`) @@ -42,18 +41,15 @@ class EvdevKeyCodes(int, Enum): F11 = ecodes.KEY_F11 F12 = ecodes.KEY_F12 - class EvdevButtonCode(int, Enum): LEFT = ecodes.BTN_LEFT RIGHT = ecodes.BTN_RIGHT MIDDLE = ecodes.BTN_MIDDLE - class EvdevScrollDirection(int, Enum): UP = 1 DOWN = -1 - def _TEMP_parse_dumpkeys_output(): """ TODO: use bindings to `keymap` @@ -92,7 +88,6 @@ def collect_lines(file_name, startswith): return keycode_and_shift_by_ascii - class InputMixin(object): @staticmethod @@ -100,7 +95,6 @@ class InputMixin(object): def block_input(): yield - class EvdevBase(object): _uinput_dev = UInput( @@ -110,7 +104,6 @@ class EvdevBase(object): }, name='pikuli-evdev-uinput') - class EvdevKeyboardMixin(EvdevBase, InputMixin): _keycode_and_shift_by_ascii = _TEMP_parse_dumpkeys_output() @@ -140,7 +133,6 @@ def _do_release_key(cls, key_code): cls._uinput_dev.write(ecodes.EV_KEY, key_code, 0) cls._uinput_dev.syn() - class EvdevMouseMixin(EvdevBase, InputMixin): @classmethod diff --git a/pikuli/input/linux_gtk3/clipboard.py b/pikuli/input/linux_gtk3/clipboard.py index 9a8d363..60aaf95 100644 --- a/pikuli/input/linux_gtk3/clipboard.py +++ b/pikuli/input/linux_gtk3/clipboard.py @@ -7,7 +7,6 @@ from pikuli import logger - class GtkClipboard(object): _clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) diff --git a/pikuli/input/linux_gtk3/input_emulator.py b/pikuli/input/linux_gtk3/input_emulator.py index d386d45..39382b5 100644 --- a/pikuli/input/linux_gtk3/input_emulator.py +++ b/pikuli/input/linux_gtk3/input_emulator.py @@ -6,7 +6,6 @@ from ..linux_evdev.input_emulator import EvdevKeyboardMixin - class GtkKeyboardMixin(EvdevKeyboardMixin): """ NOT WORKS AT ALL YET! diff --git a/pikuli/input/linux_x11/input_emulator.py b/pikuli/input/linux_x11/input_emulator.py index 02da66a..5b75cca 100644 --- a/pikuli/input/linux_x11/input_emulator.py +++ b/pikuli/input/linux_x11/input_emulator.py @@ -13,7 +13,6 @@ def __hooked_class_init(cls): cls._display = Display() cls._root_window = cls._display.screen().root - ''' class X11KeyboardMixin(X11Base): """ @@ -35,7 +34,6 @@ def _do_release_key(cls, key_code): """ `key_code` is ... """ ''' - class InputMixin(object): @staticmethod @@ -43,7 +41,6 @@ class InputMixin(object): def block_input(): yield - class X11MouseMixin(X11Base, InputMixin): __hooked_class_init_overriding = { diff --git a/pikuli/input/platform_init.py b/pikuli/input/platform_init.py index 679b0c7..0a2cabb 100644 --- a/pikuli/input/platform_init.py +++ b/pikuli/input/platform_init.py @@ -7,7 +7,6 @@ from pikuli import logger from pikuli._helpers import NotImplemetedDummyFactory - Method = namedtuple('Method', [ 'KeyCode', 'ButtonCode', @@ -17,7 +16,6 @@ 'Clipboard' ]) - class EmulatorMethod(object): """ One can use this class to switch input emulation method in two ways: permanent or temporary. diff --git a/pikuli/input/windows/clipboard.py b/pikuli/input/windows/clipboard.py index 9abe99d..2b1aad4 100644 --- a/pikuli/input/windows/clipboard.py +++ b/pikuli/input/windows/clipboard.py @@ -3,7 +3,6 @@ import win32clipboard from pikuli import logger - class WinClipboard(object): @classmethod diff --git a/pikuli/input/windows/input_emulator.py b/pikuli/input/windows/input_emulator.py index 9a8ce78..7632eb5 100644 --- a/pikuli/input/windows/input_emulator.py +++ b/pikuli/input/windows/input_emulator.py @@ -8,7 +8,6 @@ from ..helper_types import WindowsButtonCode - class WinVirtKeyCodes(int, Enum): """ Virtual-key codes of some special keys. @@ -46,18 +45,15 @@ class WinVirtKeyCodes(int, Enum): F11 = win32con.VK_F11 F12 = win32con.VK_F12 - class WinButtonCode(WindowsButtonCode, Enum): LEFT = (win32con.MOUSEEVENTF_LEFTDOWN, win32con.MOUSEEVENTF_LEFTUP) RIGHT = (win32con.MOUSEEVENTF_RIGHTDOWN, win32con.MOUSEEVENTF_RIGHTUP) MIDDLE = (win32con.MOUSEEVENTF_MIDDLEDOWN, win32con.MOUSEEVENTF_MIDDLEUP) - class WinScrollDirection(int, Enum): UP = 1 DOWN = -1 - class InputMixin(object): @staticmethod @@ -67,7 +63,6 @@ def block_input(): yield windll.user32.BlockInput(False) - class WinKeyboardMixin(InputMixin): @classmethod @@ -87,7 +82,6 @@ def _do_press_key(cls, key_code): def _do_release_key(cls, key_code): win32api.keybd_event(key_code, 0, win32con.KEYEVENTF_EXTENDEDKEY | win32con.KEYEVENTF_KEYUP, 0) # win32con.KEYEVENTF_EXTENDEDKEY - class WinMouseMixin(InputMixin): @classmethod diff --git a/pikuli/mouse_cursor.py b/pikuli/mouse_cursor.py index 863644f..1b48d5b 100644 --- a/pikuli/mouse_cursor.py +++ b/pikuli/mouse_cursor.py @@ -7,7 +7,6 @@ IDC_HELP, IDC_IBEAM, IDC_ICON, IDC_NO, IDC_SIZE, IDC_SIZEALL, IDC_SIZENESW, IDC_SIZENS, IDC_SIZENWSE, IDC_SIZEWE, IDC_UPARROW, IDC_WAIT] - class MouseCursor(object): @classmethod @@ -25,5 +24,3 @@ def from_handle(cls, handle): def __init__(self, cursor_type, handle): self.type = cursor_type self.handle = handle - - diff --git a/pikuli/uia/__init__.py b/pikuli/uia/__init__.py index 0d33bae..5c1e83f 100644 --- a/pikuli/uia/__init__.py +++ b/pikuli/uia/__init__.py @@ -6,7 +6,6 @@ from pikuli._helpers import NotImplemetedDummyBase, NotImplemetedDummyFactory from .exceptions import AdapterException - try: from .adapter import Adapter as __Adapter assert not issubclass(__Adapter, NotImplemetedDummyBase), __Adapter.err_msg diff --git a/pikuli/uia/adapter/adapter.py b/pikuli/uia/adapter/adapter.py index 28661b3..0341b5d 100644 --- a/pikuli/uia/adapter/adapter.py +++ b/pikuli/uia/adapter/adapter.py @@ -5,7 +5,6 @@ from .pattern_description import PatternDescriptions from .platform_init import OsAdapterMixin - class AdapterMeta(type): def __new__(mcls, name, bases, dct): @@ -32,7 +31,6 @@ def __new__(mcls, name, bases, dct): return cls - class Adapter(OsAdapterMixin, metaclass=AdapterMeta): known_element_property_names = element_property_names diff --git a/pikuli/uia/adapter/adapter_base.py b/pikuli/uia/adapter/adapter_base.py index bb7f3de..828ae98 100644 --- a/pikuli/uia/adapter/adapter_base.py +++ b/pikuli/uia/adapter/adapter_base.py @@ -4,7 +4,6 @@ from pikuli.uia import AdapterException from pikuli.utils import class_property - class AdapterBase(object): @class_property diff --git a/pikuli/uia/adapter/dotnet/adapter.py b/pikuli/uia/adapter/dotnet/adapter.py index 02db42f..30d1644 100644 --- a/pikuli/uia/adapter/dotnet/adapter.py +++ b/pikuli/uia/adapter/dotnet/adapter.py @@ -26,14 +26,12 @@ def _load_uia_assemblies_by_name_type(name_type): for assm_name in _uia_assembly_names[name_type]: clr.AddReference(assm_name) # contains System.Windows.Automation - clr.AddReference("System.Runtime") try: _load_uia_assemblies_by_name_type("short") except: _load_uia_assemblies_by_name_type("full_v4") - import System.Windows.Automation from System.Windows.Automation import ( AutomationElement as AutomationElement_clr, @@ -76,7 +74,6 @@ def _load_uia_assemblies_by_name_type(name_type): from ..helper_types import Enums from ..sdk_enums import _get_sdk_enums - class DotNetAdapter(AdapterBase): _AUTOMATION_PATTERN_PROGRAMMATIC_NAME_FORMAT = re.compile(r"(?P\w+)Identifiers\.Pattern") diff --git a/pikuli/uia/adapter/dotnet/automation_element.py b/pikuli/uia/adapter/dotnet/automation_element.py index 039af3c..630bc3d 100644 --- a/pikuli/uia/adapter/dotnet/automation_element.py +++ b/pikuli/uia/adapter/dotnet/automation_element.py @@ -3,7 +3,6 @@ from pikuli.utils import class_property from .adapter import AutomationElement_clr - class AutomationElement(object): @class_property diff --git a/pikuli/uia/adapter/dotnet/condition.py b/pikuli/uia/adapter/dotnet/condition.py index 3df628a..7f2c9a3 100644 --- a/pikuli/uia/adapter/dotnet/condition.py +++ b/pikuli/uia/adapter/dotnet/condition.py @@ -3,7 +3,6 @@ from pikuli.utils import class_property from .adapter import Condition_clr - class Condition(object): @class_property diff --git a/pikuli/uia/adapter/dotnet/pattern_factory.py b/pikuli/uia/adapter/dotnet/pattern_factory.py index d96e5ae..0885acc 100644 --- a/pikuli/uia/adapter/dotnet/pattern_factory.py +++ b/pikuli/uia/adapter/dotnet/pattern_factory.py @@ -4,7 +4,6 @@ from ..pattern_description import PatternDescriptions - class _PatternWrapper(object): def __init__(self, dotnet_pattern_obj): @@ -35,7 +34,6 @@ def _split_attr_name(self, attr_name): return (mode, attr_name[len(mode):]) return (None, None) - class PatternFactory(object): _lazzy_created_wrappers = {} diff --git a/pikuli/uia/adapter/dotnet/tree_walker.py b/pikuli/uia/adapter/dotnet/tree_walker.py index a843e85..17da470 100644 --- a/pikuli/uia/adapter/dotnet/tree_walker.py +++ b/pikuli/uia/adapter/dotnet/tree_walker.py @@ -3,7 +3,6 @@ from pikuli.utils import class_property from .adapter import TreeWalker_clr - class TreeWalker(object): def __init__(self, condition): diff --git a/pikuli/uia/adapter/dotnet/value_converters.py b/pikuli/uia/adapter/dotnet/value_converters.py index a5f0c3d..86cb826 100644 --- a/pikuli/uia/adapter/dotnet/value_converters.py +++ b/pikuli/uia/adapter/dotnet/value_converters.py @@ -2,7 +2,6 @@ from pikuli.uia.adapter.property_value_types import Rectangle - class DotNetPropertyValueConverter(object): @classmethod diff --git a/pikuli/uia/adapter/helper_types.py b/pikuli/uia/adapter/helper_types.py index 0107191..a4eda52 100644 --- a/pikuli/uia/adapter/helper_types.py +++ b/pikuli/uia/adapter/helper_types.py @@ -2,7 +2,6 @@ from enum import Enum, EnumMeta - class ApiEnumAutoval(Enum): def __new__(cls, default_val): @@ -14,7 +13,6 @@ def __new__(cls, default_val): def _c_name(self): return self.name - class ApiEnumExplicit(int, Enum): @property @@ -27,7 +25,6 @@ def _c_name(self): name = self.name if self.name != 'None_' else 'None' return name - class Enums(object): def _add(self, enum): @@ -45,7 +42,6 @@ def is_enum(cls, obj): def __str__(self): return str(self.get_collection().keys()) - class IdNameMap(object): def __init__(self, map_builder, names): diff --git a/pikuli/uia/adapter/identifer_names.py b/pikuli/uia/adapter/identifer_names.py index 3ca9719..687bc28 100644 --- a/pikuli/uia/adapter/identifer_names.py +++ b/pikuli/uia/adapter/identifer_names.py @@ -8,7 +8,6 @@ #http://msdn.microsoft.com/en-us/library/windows/desktop/dd318521(v=vs.85).aspx ############################### - #Automation Element Property Identifiers element_property_names = ( "AcceleratorKey", @@ -51,7 +50,6 @@ "RuntimeId", ) - # Automation Control Type Identifiers (https://msdn.microsoft.com/en-us/library/windows/desktop/ee671198(v=vs.85).aspx) control_type_names = ( "AppBar", @@ -97,7 +95,6 @@ "Window", ) - """ #Control Pattern Property Identifiers control_pattern_property_names = ( diff --git a/pikuli/uia/adapter/oleacc_h.py b/pikuli/uia/adapter/oleacc_h.py index 399db5c..367dd7c 100644 --- a/pikuli/uia/adapter/oleacc_h.py +++ b/pikuli/uia/adapter/oleacc_h.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- - ''' "Object State Constants" (https://msdn.microsoft.com/en-us/library/windows/desktop/dd373609(v=vs.85).aspx) Константы, описывающие состояние WinForms кнопок, checkbox и прочих. Эти константы относятся к LegacyAccessible-паттерну, diff --git a/pikuli/uia/adapter/pattern_description.py b/pikuli/uia/adapter/pattern_description.py index 74f6ffb..e26af06 100644 --- a/pikuli/uia/adapter/pattern_description.py +++ b/pikuli/uia/adapter/pattern_description.py @@ -4,7 +4,6 @@ from .patterns_plain_description import METHOD, PROPERTY, patterns_plain_description from ..exceptions import AdapterException - class PatternDescriptions(): """ Accumulates Patterns known by Pikuli and available in the current system. @@ -49,11 +48,9 @@ def get_all_known_names(cls): """ return patterns_plain_description.keys() - def _unpack_member_description(member_type, member_name, *member_args): return member_type, member_name, member_args - class _PattDesc(object): def __init__(self, adapter, pattern_name, interface_description): diff --git a/pikuli/uia/adapter/patterns_plain_description.py b/pikuli/uia/adapter/patterns_plain_description.py index bcf3407..fb2272a 100644 --- a/pikuli/uia/adapter/patterns_plain_description.py +++ b/pikuli/uia/adapter/patterns_plain_description.py @@ -3,7 +3,6 @@ PROPERTY = 'property' METHOD = 'method' - patterns_plain_description = { "AnnotationPattern" : [ (PROPERTY, 'CurrentAnnotationTypeId', diff --git a/pikuli/uia/adapter/platform_init.py b/pikuli/uia/adapter/platform_init.py index 4e3b03c..7fe62bd 100644 --- a/pikuli/uia/adapter/platform_init.py +++ b/pikuli/uia/adapter/platform_init.py @@ -4,7 +4,6 @@ from pikuli.uia.settings import UIA_FORCE_DOTNET - if os.name == 'nt' and not UIA_FORCE_DOTNET: from .win_native.adapter import WinAdapter as OsAdapterMixin from .win_native.automation_element import AutomationElement diff --git a/pikuli/uia/adapter/property_value_converter.py b/pikuli/uia/adapter/property_value_converter.py index 1590320..4cef503 100644 --- a/pikuli/uia/adapter/property_value_converter.py +++ b/pikuli/uia/adapter/property_value_converter.py @@ -4,10 +4,8 @@ from .platform_init import OsPropertyValueConverter - CONV_METHOD_NAME_PREFIX = 'convert_' - class PropertyValueConverterMeta(type): def __new__(mcls, name, bases, dct): @@ -20,7 +18,6 @@ def __new__(mcls, name, bases, dct): } return new_cls - class PropertyValueConverter(OsPropertyValueConverter, metaclass=PropertyValueConverterMeta): _converters = {} diff --git a/pikuli/uia/adapter/property_value_types.py b/pikuli/uia/adapter/property_value_types.py index 4419672..82ff523 100644 --- a/pikuli/uia/adapter/property_value_types.py +++ b/pikuli/uia/adapter/property_value_types.py @@ -2,5 +2,4 @@ from collections import namedtuple - Rectangle = namedtuple('Rectangle', ['x', 'y', 'w', 'h']) diff --git a/pikuli/uia/adapter/sdk_enums.py b/pikuli/uia/adapter/sdk_enums.py index e46329c..07d4169 100644 --- a/pikuli/uia/adapter/sdk_enums.py +++ b/pikuli/uia/adapter/sdk_enums.py @@ -4,10 +4,8 @@ Enums below are got from Windows SDK 8.1. """ - from .helper_types import ApiEnumAutoval, ApiEnumExplicit, Enums - # # See `UIAutomationCoreApi.h` # @@ -19,7 +17,6 @@ 'Completed' ]) - AutomationIdentifierType = ApiEnumAutoval('AutomationIdentifierType', [ 'Property', @@ -29,7 +26,6 @@ 'TextAttribute' ]) - class ConditionType(ApiEnumExplicit): TRUE = 0 FALSE = 1 @@ -38,7 +34,6 @@ class ConditionType(ApiEnumExplicit): Or = 4 Not = 5 - EventArgsType = ApiEnumAutoval('EventArgsType', [ 'Simple', @@ -49,7 +44,6 @@ class ConditionType(ApiEnumExplicit): 'TextEditTextChanged' ]) - NormalizeState = ApiEnumAutoval('NormalizeState', [ 'None', # Don't normalize @@ -57,7 +51,6 @@ class ConditionType(ApiEnumExplicit): 'Custom' # Normalize against supplied condition ]) - ProviderType = ApiEnumAutoval('ProviderType', [ 'BaseHwnd', @@ -65,7 +58,6 @@ class ConditionType(ApiEnumExplicit): 'NonClientArea' ]) - # # See `UIAutomationClient.h` # @@ -78,17 +70,14 @@ class TreeScope(ApiEnumExplicit): Ancestors = 0x10 Subtree = (Element | Children) | Descendants - class PropertyConditionFlags(ApiEnumExplicit): None_ = 0x00 IgnoreCase = 0x01 - class AutomationElementMode(ApiEnumExplicit): None_ = 0 Full = 1 - # # See `UIAutomationCore.h` # @@ -99,7 +88,6 @@ class NavigateDirection(ApiEnumExplicit): FirstChild = 3 LastChild = 4 - class ProviderOptions(ApiEnumExplicit): """ TODO: ??? DEFINE_ENUM_FLAG_OPERATORS(ProviderOptions) """ ClientSideProvider = 0x001 @@ -112,7 +100,6 @@ class ProviderOptions(ApiEnumExplicit): HasNativeIAccessible = 0x080 UseClientCoordinates = 0x100 - class StructureChangeType(ApiEnumExplicit): ChildAdded = 0 ChildRemoved = 1 @@ -121,20 +108,17 @@ class StructureChangeType(ApiEnumExplicit): ChildrenBulkRemoved = 4 ChildrenReordered = 5 - class TextEditChangeType(ApiEnumExplicit): None_ = 0 AutoCorrect = 1 Composition = 2 CompositionFinalized = 3 - class OrientationType(ApiEnumExplicit): None_ = 0 Horizontal = 1 Vertical = 2 - class DockPosition(ApiEnumExplicit): Top = 0 Left = 1 @@ -143,14 +127,12 @@ class DockPosition(ApiEnumExplicit): Fill = 4 None_ = 5 - class ExpandCollapseState(ApiEnumExplicit): Collapsed = 0 Expanded = 1 PartiallyExpanded = 2 LeafNode = 3 - class ScrollAmount(ApiEnumExplicit): LargeDecrement = 0 SmallDecrement = 1 @@ -158,25 +140,21 @@ class ScrollAmount(ApiEnumExplicit): LargeIncrement = 3 SmallIncrement = 4 - class RowOrColumnMajor(ApiEnumExplicit): RowMajor = 0 ColumnMajor = 1 Indeterminate = 2 - class ToggleState(ApiEnumExplicit): Off = 0 On = 1 Indeterminate = 2 - class WindowVisualState(ApiEnumExplicit): Normal = 0 Maximized = 1 Minimized = 2 - class SynchronizedInputType(ApiEnumExplicit): """ TODO: ??? DEFINE_ENUM_FLAG_OPERATORS(SynchronizedInputType) """ KeyUp = 0x1 @@ -186,7 +164,6 @@ class SynchronizedInputType(ApiEnumExplicit): RightMouseUp = 0x10 RightMouseDown = 0x20 - class WindowInteractionState(ApiEnumExplicit): Running = 0 Closing = 1 @@ -194,7 +171,6 @@ class WindowInteractionState(ApiEnumExplicit): BlockedByModalWindow = 3 NotResponding = 4 - class TextUnit(ApiEnumExplicit): Character = 0 Format = 1 @@ -204,41 +180,34 @@ class TextUnit(ApiEnumExplicit): Page = 5 Document = 6 - class TextPatternRangeEndpoint(ApiEnumExplicit): Start = 0 End = 1 - class SupportedTextSelection(ApiEnumExplicit): None_ = 0 Single = 1 Multiple = 2 - class LiveSetting(ApiEnumExplicit): Off = 0 Polite = 1 Assertive = 2 - class ActiveEnd(ApiEnumExplicit): None_ = 0 Start = 1 End = 2 - class CaretPosition(ApiEnumExplicit): Unknown = 0 EndOfLine = 1 BeginningOfLine = 2 - class CaretBidiMode(ApiEnumExplicit): LTR = 0 RTL = 1 - class ZoomUnit(ApiEnumExplicit): NoAmount = 0 LargeDecrement = 1 @@ -246,7 +215,6 @@ class ZoomUnit(ApiEnumExplicit): LargeIncrement = 3 SmallIncrement = 4 - class AnimationStyle(ApiEnumExplicit): None_ = 0 LasVegasLights = 1 @@ -257,7 +225,6 @@ class AnimationStyle(ApiEnumExplicit): Shimmer = 6 Other = -1 - class BulletStyle(ApiEnumExplicit): None_ = 0 HollowRoundBullet = 1 @@ -267,7 +234,6 @@ class BulletStyle(ApiEnumExplicit): DashBullet = 5 Other = -1 - class CapStyle(ApiEnumExplicit): None_ = 0 SmallCap = 1 @@ -278,21 +244,18 @@ class CapStyle(ApiEnumExplicit): Titling = 6 Other = -1 - class FlowDirections(ApiEnumExplicit): Default = 0 RightToLeft = 1 BottomToTop = 2 Vertical = 4 - class HorizontalTextAlignment(ApiEnumExplicit): Left = 0 Centered = 1 Right = 2 Justified = 3 - class OutlineStyles(ApiEnumExplicit): None_ = 0 Outline = 1 @@ -300,7 +263,6 @@ class OutlineStyles(ApiEnumExplicit): Engraved = 4 Embossed = 8 - class TextDecorationLineStyle(ApiEnumExplicit): None_ = 0 Single = 1 @@ -322,7 +284,6 @@ class TextDecorationLineStyle(ApiEnumExplicit): ThickLongDash = 18 Other = -1 - class UIAutomationType(ApiEnumExplicit): Int = 0x00001 Bool = 0x00002 @@ -355,6 +316,5 @@ class UIAutomationType(ApiEnumExplicit): OutRectArray = (Rect | Array) | Out OutElementArray = (Element | Array) | Out - def _get_sdk_enums(): return [v for v in globals().values() if Enums.is_enum(v)] diff --git a/pikuli/uia/adapter/win_native/adapter.py b/pikuli/uia/adapter/win_native/adapter.py index 9b2003f..33b682d 100644 --- a/pikuli/uia/adapter/win_native/adapter.py +++ b/pikuli/uia/adapter/win_native/adapter.py @@ -7,15 +7,12 @@ from ..helper_types import Enums from ..sdk_enums import _get_sdk_enums - UIA_type_lib_IID = '{944DE083-8FB8-45CF-BCB7-C477ACB2F897}' - def _get_enum_element_full_name(elem): return '{enum_name}_{elem_name}'.format( enum_name=elem.__class__.__name__, elem_name=elem._c_name) - class WinAdapter(AdapterBase): _UIA_wrapper = GetModule((UIA_type_lib_IID, 1, 0)) # 'UIAutomationCore.dll' diff --git a/pikuli/uia/adapter/win_native/automation_element.py b/pikuli/uia/adapter/win_native/automation_element.py index 5b7427e..4fe1d01 100644 --- a/pikuli/uia/adapter/win_native/automation_element.py +++ b/pikuli/uia/adapter/win_native/automation_element.py @@ -3,7 +3,6 @@ import pikuli.uia.adapter from pikuli.utils import class_property - class AutomationElement(object): @class_property diff --git a/pikuli/uia/adapter/win_native/condition.py b/pikuli/uia/adapter/win_native/condition.py index 5785185..6bea733 100644 --- a/pikuli/uia/adapter/win_native/condition.py +++ b/pikuli/uia/adapter/win_native/condition.py @@ -3,7 +3,6 @@ from pikuli.utils import class_property import pikuli.uia.adapter - class Condition(object): @class_property diff --git a/pikuli/uia/adapter/win_native/pattern_factory.py b/pikuli/uia/adapter/win_native/pattern_factory.py index 279e989..3ff4ace 100644 --- a/pikuli/uia/adapter/win_native/pattern_factory.py +++ b/pikuli/uia/adapter/win_native/pattern_factory.py @@ -5,7 +5,6 @@ from pikuli.uia.exceptions import AdapterException from ..pattern_description import PatternDescriptions - class PatternFactory(object): _pattern_interfaces_map = {} diff --git a/pikuli/uia/adapter/win_native/tree_walker.py b/pikuli/uia/adapter/win_native/tree_walker.py index 277bc72..f11b889 100644 --- a/pikuli/uia/adapter/win_native/tree_walker.py +++ b/pikuli/uia/adapter/win_native/tree_walker.py @@ -2,7 +2,6 @@ import pikuli.uia.adapter - class TreeWalker(object): def __init__(self, condition): diff --git a/pikuli/uia/adapter/win_native/value_converters.py b/pikuli/uia/adapter/win_native/value_converters.py index c820ae1..52194a0 100644 --- a/pikuli/uia/adapter/win_native/value_converters.py +++ b/pikuli/uia/adapter/win_native/value_converters.py @@ -2,7 +2,6 @@ from pikuli.uia.adapter.property_value_types import Rectangle - class WinPropertyValueConverter(object): @classmethod diff --git a/pikuli/uia/control_wrappers/button.py b/pikuli/uia/control_wrappers/button.py index 059acb2..0717ab6 100644 --- a/pikuli/uia/control_wrappers/button.py +++ b/pikuli/uia/control_wrappers/button.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from .uia_control import UIAControl - class Button(UIAControl): CONTROL_TYPE = 'Button' diff --git a/pikuli/uia/control_wrappers/check_box.py b/pikuli/uia/control_wrappers/check_box.py index d97888e..dfc81b0 100644 --- a/pikuli/uia/control_wrappers/check_box.py +++ b/pikuli/uia/control_wrappers/check_box.py @@ -5,14 +5,12 @@ from . import CONTROL_CHECK_TIMEOUT from .uia_control import UIAControl - class CheckBox(UIAControl): CONTROL_TYPE = 'CheckBox' REQUIRED_PATTERNS = {} - def _state(self, method): """ Получаем состояние CheckBox (установлена ли галочка). diff --git a/pikuli/uia/control_wrappers/combo_box.py b/pikuli/uia/control_wrappers/combo_box.py index 23136f1..a902db5 100644 --- a/pikuli/uia/control_wrappers/combo_box.py +++ b/pikuli/uia/control_wrappers/combo_box.py @@ -7,7 +7,6 @@ from .uia_control import UIAControl from .mixin import _ValuePattern_methods, _Enter_Text_method - class ComboBox(UIAControl, _ValuePattern_methods, _Enter_Text_method): """ diff --git a/pikuli/uia/control_wrappers/custom_control.py b/pikuli/uia/control_wrappers/custom_control.py index eae038c..4cdce42 100644 --- a/pikuli/uia/control_wrappers/custom_control.py +++ b/pikuli/uia/control_wrappers/custom_control.py @@ -2,7 +2,6 @@ from ..uia_element import UIAElement - class CustomControl(UIAElement): """ Cunstom (Graphic, for example) control. It does not support LegacyIAccessiblePattern, because this patternt diff --git a/pikuli/uia/control_wrappers/data_grid.py b/pikuli/uia/control_wrappers/data_grid.py index d88abde..55548e9 100644 --- a/pikuli/uia/control_wrappers/data_grid.py +++ b/pikuli/uia/control_wrappers/data_grid.py @@ -3,7 +3,6 @@ from pikuli.uia.control_wrappers.data_item import DataItem from .uia_control import UIAControl - class DataGrid(UIAControl): CONTROL_TYPE = 'DataGrid' @@ -41,4 +40,3 @@ def _find_row_precisely(obj, nested_name, exact_level): found_elem = _find_row_precisely(self, row_name, 1) return found_elem - diff --git a/pikuli/uia/control_wrappers/data_item.py b/pikuli/uia/control_wrappers/data_item.py index 48bf6c4..75270ed 100644 --- a/pikuli/uia/control_wrappers/data_item.py +++ b/pikuli/uia/control_wrappers/data_item.py @@ -4,7 +4,6 @@ from .mixin import _ValuePattern_methods, _Enter_Text_method from .uia_control import UIAControl - class DataItem(UIAControl, _ValuePattern_methods, _Enter_Text_method): CONTROL_TYPE = 'DataItem' diff --git a/pikuli/uia/control_wrappers/desktop.py b/pikuli/uia/control_wrappers/desktop.py index 8d99d57..7e131d8 100644 --- a/pikuli/uia/control_wrappers/desktop.py +++ b/pikuli/uia/control_wrappers/desktop.py @@ -2,7 +2,6 @@ from ..uia_element import UIAElement - class Desktop(UIAElement): ''' Represents the Desktop. Creating an instance of this class is equal to UIAElement(0). diff --git a/pikuli/uia/control_wrappers/document.py b/pikuli/uia/control_wrappers/document.py index d297209..8a09362 100644 --- a/pikuli/uia/control_wrappers/document.py +++ b/pikuli/uia/control_wrappers/document.py @@ -2,7 +2,6 @@ from ..uia_element import UIAElement - class Document(UIAElement): CONTROL_TYPE = 'Document' diff --git a/pikuli/uia/control_wrappers/group.py b/pikuli/uia/control_wrappers/group.py index b1af9b7..d7c3372 100644 --- a/pikuli/uia/control_wrappers/group.py +++ b/pikuli/uia/control_wrappers/group.py @@ -1,7 +1,6 @@ from .uia_control import UIAControl - class Group(UIAControl): CONTROL_TYPE = 'Group' diff --git a/pikuli/uia/control_wrappers/header.py b/pikuli/uia/control_wrappers/header.py index faa660f..5770040 100644 --- a/pikuli/uia/control_wrappers/header.py +++ b/pikuli/uia/control_wrappers/header.py @@ -2,9 +2,7 @@ from .uia_control import UIAControl from .mixin import _ValuePattern_methods - class Header(UIAControl, _ValuePattern_methods): CONTROL_TYPE = 'Header' - diff --git a/pikuli/uia/control_wrappers/header_item.py b/pikuli/uia/control_wrappers/header_item.py index ef55752..f7bad03 100644 --- a/pikuli/uia/control_wrappers/header_item.py +++ b/pikuli/uia/control_wrappers/header_item.py @@ -1,5 +1,4 @@ from .uia_control import UIAControl - class HeaderItem(UIAControl): CONTROL_TYPE = "HeaderItem" diff --git a/pikuli/uia/control_wrappers/hyperlink.py b/pikuli/uia/control_wrappers/hyperlink.py index 22b3f55..804e1ed 100644 --- a/pikuli/uia/control_wrappers/hyperlink.py +++ b/pikuli/uia/control_wrappers/hyperlink.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from .uia_control import UIAControl - class Hyperlink(UIAControl): CONTROL_TYPE = 'Hyperlink' diff --git a/pikuli/uia/control_wrappers/image.py b/pikuli/uia/control_wrappers/image.py index 7737776..c14c9a6 100644 --- a/pikuli/uia/control_wrappers/image.py +++ b/pikuli/uia/control_wrappers/image.py @@ -2,7 +2,6 @@ from .mixin import _ValuePattern_methods from .uia_control import UIAControl - class Image(UIAControl, _ValuePattern_methods): CONTROL_TYPE = 'Image' diff --git a/pikuli/uia/control_wrappers/list.py b/pikuli/uia/control_wrappers/list.py index 9d34363..a49539c 100644 --- a/pikuli/uia/control_wrappers/list.py +++ b/pikuli/uia/control_wrappers/list.py @@ -2,7 +2,6 @@ from .uia_control import UIAControl - class List(UIAControl): ''' Некий список из ListItem'ов. ''' diff --git a/pikuli/uia/control_wrappers/list_item.py b/pikuli/uia/control_wrappers/list_item.py index 49a8ee7..a080e56 100644 --- a/pikuli/uia/control_wrappers/list_item.py +++ b/pikuli/uia/control_wrappers/list_item.py @@ -3,7 +3,6 @@ from .uia_control import UIAControl - class ListItem(UIAControl): ''' Элементы списка ListItem. ''' diff --git a/pikuli/uia/control_wrappers/menu.py b/pikuli/uia/control_wrappers/menu.py index a37be65..4711a85 100644 --- a/pikuli/uia/control_wrappers/menu.py +++ b/pikuli/uia/control_wrappers/menu.py @@ -2,7 +2,6 @@ from .uia_control import UIAControl - class Menu(UIAControl): ''' Контекстное меню, к примеру. Состоит из MenuItem. ''' diff --git a/pikuli/uia/control_wrappers/menu_bar.py b/pikuli/uia/control_wrappers/menu_bar.py index 9d4746d..eaf19e9 100644 --- a/pikuli/uia/control_wrappers/menu_bar.py +++ b/pikuli/uia/control_wrappers/menu_bar.py @@ -2,7 +2,6 @@ from ..uia_element import UIAElement - class MenuBar(UIAElement): CONTROL_TYPE = 'MenuBar' diff --git a/pikuli/uia/control_wrappers/menu_item.py b/pikuli/uia/control_wrappers/menu_item.py index 11830c8..89b6788 100644 --- a/pikuli/uia/control_wrappers/menu_item.py +++ b/pikuli/uia/control_wrappers/menu_item.py @@ -2,7 +2,6 @@ from .uia_control import UIAControl - class MenuItem(UIAControl): ''' Контекстное меню, к примеру. ''' diff --git a/pikuli/uia/control_wrappers/mixin.py b/pikuli/uia/control_wrappers/mixin.py index bf2ab8a..a40f58d 100644 --- a/pikuli/uia/control_wrappers/mixin.py +++ b/pikuli/uia/control_wrappers/mixin.py @@ -7,10 +7,8 @@ from . import CONTROL_CHECK_TIMEOUT - TEXT_CLEAN_METHODS = ['uia_api', 'end&backspaces', 'home&deletes', 'single_backspace'] - class _Enter_Text_method(UIAElement): REQUIRED_METHODS = {'get_value': ['type_text', 'enter_text'], 'set_value_api': ['type_text', 'enter_text']} @@ -79,7 +77,6 @@ def type_text(self, text, modifiers=None, chck_text=False, click=True, check_tim if p2c_notif: logger.info('pikuli.%s.type_text(): type \'%s\' in %s' % (type(self).__name__, repr(text), str(self))) - def enter_text(self, text, method='click', clean_method=None, check_timeout=CONTROL_CHECK_TIMEOUT, p2c_notif=True): ''' Перезапишет текст в контроле. @@ -120,7 +117,6 @@ def enter_text(self, text, method='click', clean_method=None, check_timeout=CONT # logger.info('pikuli.%s.enter_text(): \'%s\' is alredy in %s' % (type(self).__name__, repr(text), str(self))) # return changed - class _ValuePattern_methods(UIAElement): REQUIRED_PATTERNS = {'ValuePattern': ['get_value', 'set_value_api', 'is_readoly']} @@ -154,7 +150,6 @@ def set_value_api(self, text, check_timeout=CONTROL_CHECK_TIMEOUT, p2c_notif=Tru def is_read_only(self): return bool(self.get_pattern('ValuePattern').CurrentIsReadOnly) - class _LegacyIAccessiblePattern_state_methods(UIAElement): def is_unavailable(self): @@ -166,7 +161,6 @@ def is_available(self): def is_focused(self): return bool(self.get_pattern('LegacyIAccessiblePattern').CurrentState & STATE_SYSTEM['FOCUSED']) - class _LegacyIAccessiblePattern_value_methods(UIAElement): def get_value(self): diff --git a/pikuli/uia/control_wrappers/pane.py b/pikuli/uia/control_wrappers/pane.py index 61dfe42..a922edd 100644 --- a/pikuli/uia/control_wrappers/pane.py +++ b/pikuli/uia/control_wrappers/pane.py @@ -2,7 +2,6 @@ from .uia_control import UIAControl - class Pane(UIAControl): CONTROL_TYPE = 'Pane' diff --git a/pikuli/uia/control_wrappers/progress_bar.py b/pikuli/uia/control_wrappers/progress_bar.py index dfda5ae..e5d9d4e 100644 --- a/pikuli/uia/control_wrappers/progress_bar.py +++ b/pikuli/uia/control_wrappers/progress_bar.py @@ -1,7 +1,6 @@ from .mixin import _ValuePattern_methods from .uia_control import UIAControl - class ProgressBar(UIAControl, _ValuePattern_methods): CONTROL_TYPE = 'ProgressBar' diff --git a/pikuli/uia/control_wrappers/property_grid.py b/pikuli/uia/control_wrappers/property_grid.py index 1110611..d2dc5c1 100644 --- a/pikuli/uia/control_wrappers/property_grid.py +++ b/pikuli/uia/control_wrappers/property_grid.py @@ -6,7 +6,6 @@ from .uia_control import UIAControl from .mixin import _LegacyIAccessiblePattern_value_methods, _Enter_Text_method - class ANPropGrid_Table(UIAControl): ''' Таблица настроек в AxxonNext ничего не поддерживает, кроме Legacy-паттерна. @@ -57,7 +56,6 @@ def _find_row_precisely(obj, nested_name, exact_level): # logger.debug('pikuli.ANPropGrid_Table.find_row: \'%s\' has been found: %s' % (str(row_name), repr(found_elem))) return found_elem - class ANPropGrid_Row(UIAControl, _LegacyIAccessiblePattern_value_methods, _Enter_Text_method): ''' Таблица настроек в AxxonNext ничего не поддерживает, кроме Legacy-паттерна. Каждая строка может группировать нижеидущие строки, но в UIA они "сестры", а не "родитель-потомки". Каждая строка можеть иметь или не иметь значения. ''' @@ -111,4 +109,3 @@ def collapse(self): def value(self): return self.get_pattern('ValuePattern').CurrentValue - diff --git a/pikuli/uia/control_wrappers/radio_button.py b/pikuli/uia/control_wrappers/radio_button.py index e1cdb18..1ba0e12 100644 --- a/pikuli/uia/control_wrappers/radio_button.py +++ b/pikuli/uia/control_wrappers/radio_button.py @@ -1,6 +1,5 @@ from .uia_control import UIAControl - class RadioButton(UIAControl): CONTROL_TYPE = 'RadioButton' diff --git a/pikuli/uia/control_wrappers/registred_control_classes.py b/pikuli/uia/control_wrappers/registred_control_classes.py index f2c3ae6..f336fec 100644 --- a/pikuli/uia/control_wrappers/registred_control_classes.py +++ b/pikuli/uia/control_wrappers/registred_control_classes.py @@ -5,7 +5,6 @@ from ..adapter.oleacc_h import ROLE_SYSTEM, ROLE_SYSTEM_rev - class RegisteredControlClasses: """ TODO: Improme registration machinery and criteria structure. diff --git a/pikuli/uia/control_wrappers/scroll_bar.py b/pikuli/uia/control_wrappers/scroll_bar.py index 8a47fb6..d60269a 100644 --- a/pikuli/uia/control_wrappers/scroll_bar.py +++ b/pikuli/uia/control_wrappers/scroll_bar.py @@ -1,7 +1,6 @@ from .uia_control import UIAControl - class ScrollBar(UIAControl): CONTROL_TYPE = 'ScrollBar' diff --git a/pikuli/uia/control_wrappers/separator.py b/pikuli/uia/control_wrappers/separator.py index fb68ef0..b3d24ad 100644 --- a/pikuli/uia/control_wrappers/separator.py +++ b/pikuli/uia/control_wrappers/separator.py @@ -2,7 +2,6 @@ from ..uia_element import UIAElement - class Separator(UIAElement): CONTROL_TYPE = 'Separator' diff --git a/pikuli/uia/control_wrappers/spinner.py b/pikuli/uia/control_wrappers/spinner.py index 00ba7a4..f23aaa2 100644 --- a/pikuli/uia/control_wrappers/spinner.py +++ b/pikuli/uia/control_wrappers/spinner.py @@ -2,7 +2,6 @@ from pikuli.uia.control_wrappers.mixin import _ValuePattern_methods from pikuli.uia.control_wrappers.uia_control import UIAControl - class Spinner(UIAControl, _ValuePattern_methods): CONTROL_TYPE = 'Spinner' diff --git a/pikuli/uia/control_wrappers/split_button.py b/pikuli/uia/control_wrappers/split_button.py index 1113caa..2d2926d 100644 --- a/pikuli/uia/control_wrappers/split_button.py +++ b/pikuli/uia/control_wrappers/split_button.py @@ -1,7 +1,6 @@ from .uia_control import UIAControl - class SplitButton(UIAControl): CONTROL_TYPE = 'SplitButton' diff --git a/pikuli/uia/control_wrappers/status_bar.py b/pikuli/uia/control_wrappers/status_bar.py index c7cef49..b5fb385 100644 --- a/pikuli/uia/control_wrappers/status_bar.py +++ b/pikuli/uia/control_wrappers/status_bar.py @@ -1,7 +1,6 @@ from .uia_control import UIAControl - class StatusBar(UIAControl): CONTROL_TYPE = 'StatusBar' diff --git a/pikuli/uia/control_wrappers/tab.py b/pikuli/uia/control_wrappers/tab.py index 55757b5..ab91605 100644 --- a/pikuli/uia/control_wrappers/tab.py +++ b/pikuli/uia/control_wrappers/tab.py @@ -1,6 +1,5 @@ from .uia_control import UIAControl - class Tab(UIAControl): CONTROL_TYPE = 'Tab' diff --git a/pikuli/uia/control_wrappers/tab_item.py b/pikuli/uia/control_wrappers/tab_item.py index 1f67a2e..41d1066 100644 --- a/pikuli/uia/control_wrappers/tab_item.py +++ b/pikuli/uia/control_wrappers/tab_item.py @@ -1,6 +1,5 @@ from .uia_control import UIAControl - class TabItem(UIAControl): CONTROL_TYPE = 'TabItem' diff --git a/pikuli/uia/control_wrappers/text.py b/pikuli/uia/control_wrappers/text.py index 8332dfb..e10b6a4 100644 --- a/pikuli/uia/control_wrappers/text.py +++ b/pikuli/uia/control_wrappers/text.py @@ -3,7 +3,6 @@ from .uia_control import UIAControl from .mixin import _ValuePattern_methods - class Text(UIAControl, _ValuePattern_methods): CONTROL_TYPE = 'Text' diff --git a/pikuli/uia/control_wrappers/thumb.py b/pikuli/uia/control_wrappers/thumb.py index b2111cb..cea9c67 100644 --- a/pikuli/uia/control_wrappers/thumb.py +++ b/pikuli/uia/control_wrappers/thumb.py @@ -1,9 +1,7 @@ # -*- coding: utf-8 -*- from .uia_control import UIAControl - class Thumb(UIAControl): CONTROL_TYPE = 'Thumb' - diff --git a/pikuli/uia/control_wrappers/title_bar.py b/pikuli/uia/control_wrappers/title_bar.py index a6dc664..5e7b748 100644 --- a/pikuli/uia/control_wrappers/title_bar.py +++ b/pikuli/uia/control_wrappers/title_bar.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from .uia_control import UIAControl - class TitleBar(UIAControl): CONTROL_TYPE = 'TitleBar' diff --git a/pikuli/uia/control_wrappers/tool_bar.py b/pikuli/uia/control_wrappers/tool_bar.py index 6fd963e..470a288 100644 --- a/pikuli/uia/control_wrappers/tool_bar.py +++ b/pikuli/uia/control_wrappers/tool_bar.py @@ -2,7 +2,6 @@ from ..uia_element import UIAElement - class ToolBar(UIAElement): CONTROL_TYPE = 'ToolBar' diff --git a/pikuli/uia/control_wrappers/tree.py b/pikuli/uia/control_wrappers/tree.py index 7936233..1fc22fa 100644 --- a/pikuli/uia/control_wrappers/tree.py +++ b/pikuli/uia/control_wrappers/tree.py @@ -6,7 +6,6 @@ from .uia_control import UIAControl - class Tree(UIAControl): CONTROL_TYPE = 'Tree' diff --git a/pikuli/uia/control_wrappers/tree_item.py b/pikuli/uia/control_wrappers/tree_item.py index 0f0efcd..82f6818 100644 --- a/pikuli/uia/control_wrappers/tree_item.py +++ b/pikuli/uia/control_wrappers/tree_item.py @@ -7,7 +7,6 @@ from .uia_control import UIAControl from .check_box import CheckBox - class TreeItem(CheckBox, UIAControl): """ Наследование от :class:`CheckBox` здесь чисто утилитарное -- нужные его методы. diff --git a/pikuli/uia/control_wrappers/uia_control.py b/pikuli/uia/control_wrappers/uia_control.py index 813a617..ccee786 100644 --- a/pikuli/uia/control_wrappers/uia_control.py +++ b/pikuli/uia/control_wrappers/uia_control.py @@ -3,7 +3,6 @@ from pikuli import logger from ..uia_element import UIAElement - class UIAControl(UIAElement): CONTROL_TYPE = None diff --git a/pikuli/uia/control_wrappers/window.py b/pikuli/uia/control_wrappers/window.py index ddb7be3..50cc9cf 100644 --- a/pikuli/uia/control_wrappers/window.py +++ b/pikuli/uia/control_wrappers/window.py @@ -2,7 +2,6 @@ from .uia_control import UIAControl - class Window(UIAControl): CONTROL_TYPE = 'Window' diff --git a/pikuli/uia/exceptions.py b/pikuli/uia/exceptions.py index d5a8d9b..6360c31 100644 --- a/pikuli/uia/exceptions.py +++ b/pikuli/uia/exceptions.py @@ -2,7 +2,6 @@ import os - # TODO: Temporary solution. Mono doesn't throw `COMError` exceptions if os.name == 'nt': import _ctypes @@ -10,6 +9,5 @@ else: class COMError(Exception): pass - class AdapterException(Exception): pass diff --git a/pikuli/uia/pattern.py b/pikuli/uia/pattern.py index 16f2516..7acbcc8 100644 --- a/pikuli/uia/pattern.py +++ b/pikuli/uia/pattern.py @@ -4,7 +4,6 @@ from .adapter.pattern_description import PatternDescriptions from .pattern_method import UiaPatternMethod - class UiaPattern(object): ''' Wrapper class for UIA pattern interface diff --git a/pikuli/uia/pattern_method.py b/pikuli/uia/pattern_method.py index 2d658b0..edbef3c 100644 --- a/pikuli/uia/pattern_method.py +++ b/pikuli/uia/pattern_method.py @@ -3,7 +3,6 @@ from .exceptions import AdapterException - class UiaPatternMethod(object): ''' Wrapper class for UIA pattern method diff --git a/pikuli/uia/uia_element.py b/pikuli/uia/uia_element.py index 23c83f3..01cf71e 100644 --- a/pikuli/uia/uia_element.py +++ b/pikuli/uia/uia_element.py @@ -364,7 +364,6 @@ def find(self, **kwargs): ''' self._test4readiness() - # Обработка воходных аргументов: find_first_only = kwargs.pop('find_first_only', True) max_descend_level = kwargs.pop('max_descend_level', None) @@ -498,7 +497,6 @@ def _search_with_method(start_automation_element, method_f): next_automation_element = method_f(next_automation_element) return found_automation_element_arr_local - ''' # Поиск по веткам элементов: def _descendants_range_level(walker, automation_element, level=0): @@ -552,12 +550,10 @@ def _goto_next_level(): found_automation_element_arr.append( elem ) _add_to_next_level_todo(elem) - (current_level_todo_arr, next_level_todo_arr, level) = _goto_next_level() return found_automation_element_arr - def _descendants_exact_level(walker, automation_element, level=0): if level < exact_level: # exact_level > 0; level от вызова к вызову +1 (растет от 0). found_automation_element_arr = [] diff --git a/pikuli/utils.py b/pikuli/utils.py index 9b18bfe..2c6ad00 100644 --- a/pikuli/utils.py +++ b/pikuli/utils.py @@ -3,7 +3,6 @@ import time from datetime import datetime - class class_property(property): def __init__(self, method): @@ -13,7 +12,6 @@ def __get__(self, cls, owner): p = self.fget.__get__(cls, owner) return p() - def wait_while(f_logic, timeout, warning_timeout=None, warning_text=None, delay_between_attempts=0.5): """ Внутренний цик выполняется, пока вычисление `f_logic()` трактуется как `True`. @@ -43,7 +41,6 @@ def wait_while(f_logic, timeout, warning_timeout=None, warning_text=None, delay_ time.sleep(delay_between_attempts) - def wait_while_not(f_logic, timeout, warning_timeout=None, delay_between_attempts=0.5): """ Внутренний цик выполняется, пока вычисление `f_logic()` трактуется как `False`.