diff --git a/README.es-ES.md b/README.es-ES.md new file mode 100644 index 0000000..5fdc6a7 --- /dev/null +++ b/README.es-ES.md @@ -0,0 +1,235 @@ + + +# LabelAutofinderCore + +Este es un módulo para NVDA. Útil en otros complementos, no hace nada por sí solo. + +El módulo implementa varias técnicas para asociar a un objeto elegido la etiqueta (u otra información) en pantalla, según la posición visual más cercana. + +Los escenarios admitidos son: + +* páginas web, por ejemplo, los campos en un formulario que no están etiquetados correctamente; +* programas (incluidas aplicaciones UWP) con etiquetas como objetos (recuperables con la revisión de objetos), a veces asociados a elementos de interfaz incorrectos; +* programas con etiquetas como texto (recuperables con la revisión de pantalla), donde la asociación con objetos podría haberse perdido por completo. + +¿TL;DR? Salta a la sección "Script para pruebas". + +## Uso + +El método principal para importar y llamar es `getLabel`, generalmente desde dentro de `event_*` o `chooseNVDAObjectOverlayClasses`. + +En el mejor de los casos, con la configuración predeterminada, simplemente puedes hacer algo como: + +``` +import appModuleHandler +from .labelAutofinderCore import getLabel + +class AppModule(appModuleHandler.AppModule): + + def event_gainFocus(self, obj, nextHandler): + if not obj.name: # and other checks you want + obj.name = getLabel(obj) + nextHandler() +``` + +Nota: todos los ejemplos aquí se presentarán como `AppModule`, es decir, un contexto donde este módulo puede tener comportamientos más predecibles. Pero nada te impide usarlo en un `GlobalPlugin`, si restringes adecuadamente su acción (por ejemplo: solo en campos de formulario en páginas web). + +## Configuración predeterminada y cómo personalizarla + +A veces, la búsqueda de etiquetas con la configuración predeterminada falla, pero puedes personalizarla para capturar mejor la etiqueta correcta. + +Puedes proporcionar los siguientes parámetros a `SearchConfig`: + +* `obj`: objeto a etiquetar (si deseas pasar solo la configuración a `getLabel`);\ +Predeterminado: objeto con foco (u objeto navegador para web) recuperado a través de `api`; debido al procesamiento de eventos o la construcción de objetos, se recomienda encarecidamente pasar el objeto (vía `getLabel` o `config`); +* `strategy`: puede ser "auto", "obj", "text", "uwp" o "web" (pero especificarlo debería tener un impacto mínimo en el rendimiento, es principalmente para comportamientos internos);\ +Predeterminado: "auto"; +* `labelContainer`: el objeto que contiene la etiqueta (solo para estrategias de texto y web);\ +Predeterminado: None (el algoritmo sube en el árbol de ancestros, en orden de abajo hacia arriba); +* `maxParent`: el límite superior debajo del cual recuperar las etiquetas;\ +Predeterminado: objeto en primer plano (o None para web, lo que prácticamente significa el primer objeto con el rol DOCUMENT); +* `directions`: constantes de la clase `SearchDirections` (LEFT, TOP, RIGHT, BOTTOM, HORIZONTAL, VERTICAL, LEFT_TOP, ALL), o cualquier tupla definida como `(*SearchDirections.LEFT, *SearchDirections.TOP, *SearchDirections.BOTTOM)`;\ +Predeterminado: `SearchDirections.LEFT_TOP`; +* `maxHorizontalDistance`: distancia horizontal máxima entre el punto izquierdo/derecho del objeto a etiquetar y el punto relativo de la etiqueta;\ +Predeterminado: 150 para uwp, 100 para obj y web, 8 para la estrategia de texto; si se establece en `sys.maxsize`, será 10000 para la estrategia de texto, el ancho del objeto en primer plano en caso contrario; +* `maxVerticalDistance`: distancia vertical máxima entre el punto superior/inferior del objeto a etiquetar y el punto relativo de la etiqueta;\ +Predeterminado: 150 para uwp, 100 para obj y web, None para la estrategia de texto (fuerza a usar la altura del carácter); si se establece en `sys.maxsize`, será 10000 para la estrategia de texto, la altura del objeto en primer plano en caso contrario. + +Además, también puedes derivar una configuración a partir de una configuración anterior, construyéndola como `SearchConfig(oldConfig=prevConfig)`. + +Así, si tu etiqueta está en la parte inferior, en lugar de la izquierda o superior predeterminadas, puedes hacer: + +``` +import appModuleHandler +from .labelAutofinderCore import getLabel, SearchConfig, SearchDirections + +class AppModule(appModuleHandler.AppModule): + + def event_gainFocus(self, obj, nextHandler): + if not obj.name: # and other checks you want + config = SearchConfig(directions=SearchDirections.BOTTOM) + obj.name = getLabel(obj, config) + nextHandler() +``` + +Ten en cuenta que, con `LEFT_TOP` predeterminado o múltiples direcciones, el módulo siempre devuelve una etiqueta, es decir, la etiqueta con la distancia mínima al objeto pasado entre las encontradas en las direcciones especificadas. + +## Script para pruebas + +Para comprender y explorar mejor tu situación, puede resultar útil usar un script como este: + +``` +import api +import globalPluginHandler +import ui +from scriptHandler import script +from .labelAutofinderCore import getLabel, SearchConfig, SearchDirections + +class GlobalPlugin(globalPluginHandler.GlobalPlugin): + + scriptCategory = "Testing LabelAutofinder module" + + @script( + description=_("tries to find and reports a label for current focused object") + ) + def script_findLabel(self, gesture): + tempObj = api.getNavigatorObject() + if tempObj.treeInterceptor: + obj = tempObj + else: + obj = api.getFocusObject() + labelTuples = [] + baseConfig = SearchConfig(obj=obj) + for direction in ("left", "top", "right", "bottom"): + searchDirection = getattr(SearchDirections, direction.upper()) + directionConfig = SearchConfig(oldConfig=baseConfig, directions=searchDirection) + # overview=True to get distance, in addition to label + distanceAndLabel = getLabel(config=directionConfig, overview=True) + if distanceAndLabel: + labelTuples.append((direction, *distanceAndLabel,)) + if not labelTuples: + ui.message(_("Unable to find any label")) + return + # sort for distance + labelTuples.sort(key=lambda i: i[1]) + labelMsgs = [] + for direction, distance, label in labelTuples: + labelMsg = "{distance} on {direction}: {label}".format(distance=distance, direction=direction, label=label) + labelMsgs.append(labelMsg) + msg = '; '.join(labelMsgs) + ui.message(msg) +``` + +## ...¡y otra información! + +Aunque nació para etiquetas, al desarrollar este módulo me alegró descubrir que puede usarse en una pequeña cantidad de otros casos. + +Uno de ellos son los deslizadores (sliders). Usualmente van del 0 al 100%, en pantalla, de hecho, podrían presentarse con un rango completamente diferente, por ejemplo, de KB/s, Decibelios, y así sucesivamente. + +Ahora puedes hacer algo como: + +``` +import appModuleHandler +from controlTypes import Role as roles +from NVDAObjects.IAccessible import IAccessible +from .labelAutofinderCore import getLabel, SearchConfig, SearchDirections + +class AppModule(appModuleHandler.AppModule): + + def chooseNVDAObjectOverlayClasses(self, obj, clsList): + if not obj.name and obj.role == roles.SLIDER: + clsList.insert(0, SliderWithUnit) + +class SliderWithUnit(IAccessible): + + def _get_name(self): + name = getLabel(self) + return name + + def _get_value(self): + config = SearchConfig(directions=SearchDirections.RIGHT) # or any other direction in your situation + value = getLabel(self, config) + return value +``` + +## Notas y sugerencias + +### Incluir como submódulo de Git + +Si incluyes este módulo en tu complemento, la mejor forma es probablemente agregarlo como un submódulo de Git, bajo la ruta apropiada. Por ejemplo: + +``` +>git submodule add https://github.com/ABuffEr/labelAutofinderCore addon/appModules/labelAutofinderCore +>git submodule init +>git submodule update +>git commit -m "Added labelAutofinderCore as submodule" +>git push --all +``` + +Si tienes problemas al ejecutar `git submodule update` las siguientes veces, intenta agregar la opción "--remote" o consulta [aquí.](https://stackoverflow.com/questions/3336995/git-will-not-init-sync-update-new-submodules) + +Independientemente de la ruta, mantén el nombre de la última carpeta como "labelAutofinderCore", para garantizar una "comprobación de compatibilidad" por parte de otros complementos (especialmente plugins globales). + +Además, ¡me hará mucho gusto que cites este trabajo en tu readme! + +### Cuando el texto desaparece + +Cuando se requiere la estrategia de texto (encuentras etiquetas solo con la revisión de pantalla), es posible que notes un comportamiento extraño al reiniciar NVDA y en otras situaciones: el texto desaparece por completo, para volver a aparecer si minimizas o cierras y vuelves a abrir el programa/ventana. + +No es causado por este módulo, que sin embargo proporciona una solución. + +Usa algo como esto: + +``` +import appModuleHandler +from controlTypes import Role as roles +from .labelAutofinderCore import refreshTextContent + +class AppModule(appModuleHandler.AppModule): + + def event_foreground(self, obj, nextHandler): + # to fix text disappearing + if obj.role == roles.PANE: # or similar, but anyway the role of object containing text + refreshTextContent(obj) + nextHandler() +``` + +Para conocer la razón y un método alternativo (pero menos confiable en mi experiencia), consulta [este mensaje de Emil Hesmyr.](https://nvda-addons.groups.io/g/nvda-addons/message/25970) + +### Combobox y combobox editable + +Puedes encontrarte con situaciones con combobox y combobox editables sin etiquetar (me refiero, con otro objeto secundario como campo editable). + +Sugiero distinguir mediante `event_gainFocus` y `event_focusEntered` para evitar una doble etiquetado. + +Algo como esto: + +``` +import appModuleHandler +from controlTypes import Role as roles +from .labelAutofinderCore import getLabel + +class AppModule(appModuleHandler.AppModule): + + def event_gainFocus(self, obj, nextHandler): + # to label simple edit and combo boxes + if ( + (not obj.name) + and + (obj.role == roles.COMBOBOX or (obj.role == roles.EDITABLETEXT and obj.simpleParent.role != roles.COMBOBOX)) + ): + obj.name = getLabel(obj) + nextHandler() + + def event_focusEntered(self, obj, nextHandler): + # to label combo with edit boxes + if not obj.name and obj.role == roles.COMBOBOX: + obj.name = getLabel(obj) + nextHandler() +``` + +### Evitar cajas de edición "grandes" + +Existen cajas de edición anónimas que tienen toda la razón para serlo, como informes de registro, ventana principal de un editor de texto, y así sucesivamente. + +Una forma rápida de excluir estos objetos puede ser referirse a `obj.location.width`, estableciendo un límite superior o inferior razonable (que puede variar según la resolución de pantalla, aunque), o a la presencia de MULTILINE en los estados.