# Orca # # Copyright 2005-2008 Sun Microsystems Inc. # Copyright 2016-2023 Igalia, S.L. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the # Free Software Foundation, Inc., Franklin Street, Fifth Floor, # Boston MA 02110-1301 USA. # pylint: disable=too-many-lines # pylint: disable=wrong-import-position # pylint: disable=c-extension-no-member # pylint: disable=too-many-public-methods """Module for flat-review commands""" # This has to be the first non-docstring line in the module to make linters happy. from __future__ import annotations __id__ = "$Id$" __version__ = "$Revision$" __date__ = "$Date$" __copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc." \ "Copyright (c) 2016-2023 Igalia, S.L." __license__ = "LGPL" import time from typing import TYPE_CHECKING import gi gi.require_version("Gtk", "3.0") from gi.repository import Gtk from . import braille from . import cmdnames from . import dbus_service from . import debug from . import flat_review from . import focus_manager from . import guilabels from . import input_event from . import keybindings from . import messages from . import script_manager from . import settings_manager from . import settings from . import speech_and_verbosity_manager from .ax_event_synthesizer import AXEventSynthesizer from .ax_object import AXObject from .ax_text import AXText if TYPE_CHECKING: gi.require_version("Atspi", "2.0") from gi.repository import Atspi from .scripts import default class FlatReviewPresenter: """Provides access to on-screen objects via flat-review.""" def __init__(self) -> None: self._context: flat_review.Context | None = None self._current_contents: str = "" self._restrict: bool = \ settings_manager.get_manager().get_setting("flatReviewIsRestricted") or False self._handlers: dict[str, input_event.InputEventHandler] = self.get_handlers(True) self._desktop_bindings: keybindings.KeyBindings = keybindings.KeyBindings() self._laptop_bindings: keybindings.KeyBindings = keybindings.KeyBindings() self._gui: FlatReviewContextGUI | None = None msg = "FLAT REVIEW PRESENTER: Registering D-Bus commands." debug.print_message(debug.LEVEL_INFO, msg, True) controller = dbus_service.get_remote_controller() controller.register_decorated_module("FlatReviewPresenter", self) def is_active(self) -> bool: """Returns True if the flat review presenter is active.""" return self._context is not None def get_or_create_context(self, script: default.Script | None = None) -> flat_review.Context: """Returns the flat review context, creating one if necessary.""" if not self._context: msg = f"FLAT REVIEW PRESENTER: Creating new context. Restrict: {self._restrict}" debug.print_message(debug.LEVEL_INFO, msg, True) if self._restrict: mode, obj = focus_manager.get_manager().get_active_mode_and_object_of_interest() self._context = flat_review.Context(script, root=obj) else: self._context = flat_review.Context(script) focus_manager.get_manager().emit_region_changed( self._context.get_current_object(), mode=focus_manager.FLAT_REVIEW) return self._context msg = f"FLAT REVIEW PRESENTER: Using existing context. Restrict: {self._restrict}" debug.print_message(debug.LEVEL_INFO, msg, True) # If we are in unrestricted mode, update the context as below. # If the context already exists, but the active mode is not flat review, update # the flat review location to that of the object of interest -- if the object of # interest is in the flat review context (which means it's on screen). In some # cases the object of interest will not be in the flat review context because it # is represented by descendant text objects. set_current_to_zone_with_object checks # for this condition and if it can find a zone whose ancestor is the object of # interest, it will set the current zone to the descendant, causing Orca to # present the text at the location of the object of interest. mode, obj = focus_manager.get_manager().get_active_mode_and_object_of_interest() obj = obj or focus_manager.get_manager().get_locus_of_focus() if mode != focus_manager.FLAT_REVIEW and obj != self._context.get_current_object() \ and not self._restrict: tokens = ["FLAT REVIEW PRESENTER: Attempting to update location from", self._context.get_current_object(), "to", obj] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._context.set_current_to_zone_with_object(obj) # If we are restricting, and the current mode is not flat review, calculate a new context if self._restrict and mode != focus_manager.FLAT_REVIEW: msg = "FLAT REVIEW PRESENTER: Creating new restricted context." debug.print_message(debug.LEVEL_INFO, msg, True) self._context = flat_review.Context(script, obj) return self._context def get_bindings( self, refresh: bool = False, is_desktop: bool = True ) -> keybindings.KeyBindings: """Returns the flat-review-presenter keybindings.""" if refresh: msg = "FLAT REVIEW PRESENTER: Refreshing bindings." debug.print_message(debug.LEVEL_INFO, msg, True) self._laptop_bindings.remove_key_grabs("FLAT REVIEW PRESENTER: Refreshing bindings.") self._desktop_bindings.remove_key_grabs("FLAT REVIEW PRESENTER: Refreshing bindings.") self._setup_bindings() elif is_desktop and self._desktop_bindings.is_empty(): self._setup_bindings() elif not is_desktop and self._laptop_bindings.is_empty(): self._setup_bindings() if is_desktop: return self._desktop_bindings return self._laptop_bindings def get_braille_bindings(self) -> dict[int, input_event.InputEventHandler]: """Returns the flat-review-presenter braille bindings.""" bindings = {} try: bindings[braille.brlapi.KEY_CMD_LNUP] = \ self._handlers["reviewAboveHandler"] bindings[braille.brlapi.KEY_CMD_LNDN] = \ self._handlers["reviewBelowHandler"] bindings[braille.brlapi.KEY_CMD_FREEZE] = \ self._handlers["toggleFlatReviewModeHandler"] bindings[braille.brlapi.KEY_CMD_TOP_LEFT] = \ self._handlers["reviewHomeHandler"] bindings[braille.brlapi.KEY_CMD_BOT_LEFT] = \ self._handlers["reviewBottomLeftHandler"] except AttributeError as error: tokens = ["FLAT REVIEW PRESENTER: Exception getting braille bindings:", error] debug.print_tokens(debug.LEVEL_INFO, tokens, True) return {} return bindings def get_handlers(self, refresh: bool = False) -> dict[str, input_event.InputEventHandler]: """Returns the flat-review-presenter handlers.""" if refresh: msg = "FLAT REVIEW PRESENTER: Refreshing handlers." debug.print_message(debug.LEVEL_INFO, msg, True) self._setup_handlers() return self._handlers def _setup_bindings(self) -> None: """Sets up the flat-review-presenter key bindings.""" self._setup_desktop_bindings() self._setup_laptop_bindings() def _setup_handlers(self) -> None: """Sets up the flat-review-presenter input event handlers.""" self._handlers = {} self._handlers["toggleFlatReviewModeHandler"] = \ input_event.InputEventHandler( self.toggle_flat_review_mode, cmdnames.TOGGLE_FLAT_REVIEW) self._handlers["reviewHomeHandler"] = \ input_event.InputEventHandler( self.go_home, cmdnames.REVIEW_HOME) self._handlers["reviewEndHandler"] = \ input_event.InputEventHandler( self.go_end, cmdnames.REVIEW_END) self._handlers["reviewBottomLeftHandler"] = \ input_event.InputEventHandler( self.go_bottom_left, cmdnames.REVIEW_BOTTOM_LEFT) self._handlers["reviewPreviousLineHandler"] = \ input_event.InputEventHandler( self.go_previous_line, cmdnames.REVIEW_PREVIOUS_LINE) self._handlers["reviewCurrentLineHandler"] = \ input_event.InputEventHandler( self.present_line, cmdnames.REVIEW_CURRENT_LINE) self._handlers["reviewNextLineHandler"] = \ input_event.InputEventHandler( self.go_next_line, cmdnames.REVIEW_NEXT_LINE) self._handlers["reviewSpellCurrentLineHandler"] = \ input_event.InputEventHandler( self.spell_line, cmdnames.REVIEW_SPELL_CURRENT_LINE) self._handlers["reviewPhoneticCurrentLineHandler"] = \ input_event.InputEventHandler( self.phonetic_line, cmdnames.REVIEW_PHONETIC_CURRENT_LINE) self._handlers["reviewEndOfLineHandler"] = \ input_event.InputEventHandler( self.go_end_of_line, cmdnames.REVIEW_END_OF_LINE) self._handlers["reviewPreviousItemHandler"] = \ input_event.InputEventHandler( self.go_previous_item, cmdnames.REVIEW_PREVIOUS_ITEM) self._handlers["reviewCurrentItemHandler"] = \ input_event.InputEventHandler( self.present_item, cmdnames.REVIEW_CURRENT_ITEM) self._handlers["reviewNextItemHandler"] = \ input_event.InputEventHandler( self.go_next_item, cmdnames.REVIEW_NEXT_ITEM) self._handlers["reviewSpellCurrentItemHandler"] = \ input_event.InputEventHandler( self.spell_item, cmdnames.REVIEW_SPELL_CURRENT_ITEM) self._handlers["reviewPhoneticCurrentItemHandler"] = \ input_event.InputEventHandler( self.phonetic_item, cmdnames.REVIEW_PHONETIC_CURRENT_ITEM) self._handlers["reviewPreviousCharacterHandler"] = \ input_event.InputEventHandler( self.go_previous_character, cmdnames.REVIEW_PREVIOUS_CHARACTER) self._handlers["reviewCurrentCharacterHandler"] = \ input_event.InputEventHandler( self.present_character, cmdnames.REVIEW_CURRENT_CHARACTER) self._handlers["reviewSpellCurrentCharacterHandler"] = \ input_event.InputEventHandler( self.spell_character, cmdnames.REVIEW_SPELL_CURRENT_CHARACTER) self._handlers["reviewUnicodeCurrentCharacterHandler"] = \ input_event.InputEventHandler( self.unicode_current_character, cmdnames.REVIEW_UNICODE_CURRENT_CHARACTER) self._handlers["reviewNextCharacterHandler"] = \ input_event.InputEventHandler( self.go_next_character, cmdnames.REVIEW_NEXT_CHARACTER) self._handlers["reviewCurrentAccessibleHandler"] = \ input_event.InputEventHandler( self.present_object, cmdnames.REVIEW_CURRENT_ACCESSIBLE) self._handlers["reviewAboveHandler"] = \ input_event.InputEventHandler( self.go_above, cmdnames.REVIEW_ABOVE) self._handlers["reviewBelowHandler"] = \ input_event.InputEventHandler( self.go_below, cmdnames.REVIEW_BELOW) self._handlers["showContentsHandler"] = \ input_event.InputEventHandler( self.show_contents, cmdnames.FLAT_REVIEW_SHOW_CONTENTS) self._handlers["flatReviewCopyHandler"] = \ input_event.InputEventHandler( self.copy_to_clipboard, cmdnames.FLAT_REVIEW_COPY) self._handlers["flatReviewAppendHandler"] = \ input_event.InputEventHandler( self.append_to_clipboard, cmdnames.FLAT_REVIEW_APPEND) self._handlers["flatReviewSayAllHandler"] = \ input_event.InputEventHandler( self.say_all, cmdnames.SAY_ALL_FLAT_REVIEW) self._handlers["flatReviewToggleRestrictHandler"] = \ input_event.InputEventHandler( self.toggle_restrict, cmdnames.TOGGLE_RESTRICT_FLAT_REVIEW) msg = "FLAT REVIEW PRESENTER: Handlers set up." debug.print_message(debug.LEVEL_INFO, msg, True) def _setup_desktop_bindings(self) -> None: """Sets up the flat-review-presenter desktop key bindings.""" self._desktop_bindings = keybindings.KeyBindings() self._desktop_bindings.add( keybindings.KeyBinding( "KP_Subtract", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["toggleFlatReviewModeHandler"])) self._desktop_bindings.add( keybindings.KeyBinding( "KP_Add", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["flatReviewSayAllHandler"], 2)) self._desktop_bindings.add( keybindings.KeyBinding( "KP_Home", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewHomeHandler"])) self._desktop_bindings.add( keybindings.KeyBinding( "KP_Home", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["reviewPreviousLineHandler"])) self._desktop_bindings.add( keybindings.KeyBinding( "KP_Up", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["reviewCurrentLineHandler"], 1)) self._desktop_bindings.add( keybindings.KeyBinding( "KP_Up", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["reviewSpellCurrentLineHandler"], 2)) self._desktop_bindings.add( keybindings.KeyBinding( "KP_Up", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["reviewPhoneticCurrentLineHandler"], 3)) self._desktop_bindings.add( keybindings.KeyBinding( "KP_Page_Up", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["reviewNextLineHandler"])) self._desktop_bindings.add( keybindings.KeyBinding( "KP_Page_Up", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewEndHandler"])) self._desktop_bindings.add( keybindings.KeyBinding( "KP_Left", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["reviewPreviousItemHandler"])) self._desktop_bindings.add( keybindings.KeyBinding( "KP_Left", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewAboveHandler"])) self._desktop_bindings.add( keybindings.KeyBinding( "KP_Begin", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["reviewCurrentItemHandler"], 1)) self._desktop_bindings.add( keybindings.KeyBinding( "KP_Begin", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["reviewSpellCurrentItemHandler"], 2)) self._desktop_bindings.add( keybindings.KeyBinding( "KP_Begin", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["reviewPhoneticCurrentItemHandler"], 3)) self._desktop_bindings.add( keybindings.KeyBinding( "KP_Begin", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewCurrentAccessibleHandler"])) self._desktop_bindings.add( keybindings.KeyBinding( "KP_Right", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["reviewNextItemHandler"])) self._desktop_bindings.add( keybindings.KeyBinding( "KP_Right", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewBelowHandler"])) self._desktop_bindings.add( keybindings.KeyBinding( "KP_End", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["reviewPreviousCharacterHandler"])) self._desktop_bindings.add( keybindings.KeyBinding( "KP_End", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewEndOfLineHandler"])) self._desktop_bindings.add( keybindings.KeyBinding( "KP_Down", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["reviewCurrentCharacterHandler"], 1)) self._desktop_bindings.add( keybindings.KeyBinding( "KP_Down", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["reviewSpellCurrentCharacterHandler"], 2)) self._desktop_bindings.add( keybindings.KeyBinding( "KP_Down", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["reviewUnicodeCurrentCharacterHandler"], 3)) self._desktop_bindings.add( keybindings.KeyBinding( "KP_Page_Down", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["reviewNextCharacterHandler"])) self._desktop_bindings.add( keybindings.KeyBinding( "", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["showContentsHandler"])) self._desktop_bindings.add( keybindings.KeyBinding( "", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["flatReviewCopyHandler"])) self._desktop_bindings.add( keybindings.KeyBinding( "", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["flatReviewAppendHandler"])) self._desktop_bindings.add( keybindings.KeyBinding( "", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["flatReviewToggleRestrictHandler"])) msg = "FLAT REVIEW PRESENTER: Desktop bindings set up." debug.print_message(debug.LEVEL_INFO, msg, True) def _setup_laptop_bindings(self) -> None: """Sets up the flat-review-presenter laptop key bindings.""" self._laptop_bindings = keybindings.KeyBindings() self._laptop_bindings.add( keybindings.KeyBinding( "p", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["toggleFlatReviewModeHandler"])) self._laptop_bindings.add( keybindings.KeyBinding( "semicolon", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["flatReviewSayAllHandler"], 2)) self._laptop_bindings.add( keybindings.KeyBinding( "u", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewPreviousLineHandler"])) self._laptop_bindings.add( keybindings.KeyBinding( "u", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_CTRL_MODIFIER_MASK, self._handlers["reviewHomeHandler"])) self._laptop_bindings.add( keybindings.KeyBinding( "i", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewCurrentLineHandler"], 1)) self._laptop_bindings.add( keybindings.KeyBinding( "i", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewSpellCurrentLineHandler"], 2)) self._laptop_bindings.add( keybindings.KeyBinding( "i", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewPhoneticCurrentLineHandler"], 3)) self._laptop_bindings.add( keybindings.KeyBinding( "o", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewNextLineHandler"])) self._laptop_bindings.add( keybindings.KeyBinding( "o", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_CTRL_MODIFIER_MASK, self._handlers["reviewEndHandler"])) self._laptop_bindings.add( keybindings.KeyBinding( "j", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewPreviousItemHandler"])) self._laptop_bindings.add( keybindings.KeyBinding( "j", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_CTRL_MODIFIER_MASK, self._handlers["reviewAboveHandler"])) self._laptop_bindings.add( keybindings.KeyBinding( "k", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewCurrentItemHandler"], 1)) self._laptop_bindings.add( keybindings.KeyBinding( "k", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewSpellCurrentItemHandler"], 2)) self._laptop_bindings.add( keybindings.KeyBinding( "k", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewPhoneticCurrentItemHandler"], 3)) self._laptop_bindings.add( keybindings.KeyBinding( "k", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_CTRL_MODIFIER_MASK, self._handlers["reviewCurrentAccessibleHandler"])) self._laptop_bindings.add( keybindings.KeyBinding( "l", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewNextItemHandler"])) self._laptop_bindings.add( keybindings.KeyBinding( "l", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_CTRL_MODIFIER_MASK, self._handlers["reviewBelowHandler"])) self._laptop_bindings.add( keybindings.KeyBinding( "m", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewPreviousCharacterHandler"])) self._laptop_bindings.add( keybindings.KeyBinding( "m", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_CTRL_MODIFIER_MASK, self._handlers["reviewEndOfLineHandler"])) self._laptop_bindings.add( keybindings.KeyBinding( "comma", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewCurrentCharacterHandler"], 1)) self._laptop_bindings.add( keybindings.KeyBinding( "comma", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewSpellCurrentCharacterHandler"], 2)) self._laptop_bindings.add( keybindings.KeyBinding( "comma", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewUnicodeCurrentCharacterHandler"], 3)) self._laptop_bindings.add( keybindings.KeyBinding( "period", keybindings.DEFAULT_MODIFIER_MASK, keybindings.ORCA_MODIFIER_MASK, self._handlers["reviewNextCharacterHandler"])) self._laptop_bindings.add( keybindings.KeyBinding( "", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["showContentsHandler"])) self._laptop_bindings.add( keybindings.KeyBinding( "", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["flatReviewCopyHandler"])) self._laptop_bindings.add( keybindings.KeyBinding( "", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["flatReviewAppendHandler"])) self._laptop_bindings.add( keybindings.KeyBinding( "", keybindings.DEFAULT_MODIFIER_MASK, keybindings.NO_MODIFIER_MASK, self._handlers["flatReviewToggleRestrictHandler"])) msg = "FLAT REVIEW PRESENTER: Laptop bindings set up." debug.print_message(debug.LEVEL_INFO, msg, True) def start( self, script: default.Script | None = None, event: input_event.InputEvent | None = None ) -> None: """Starts flat review.""" if self._context: msg = "FLAT REVIEW PRESENTER: Already in flat review" debug.print_message(debug.LEVEL_INFO, msg, True) return msg = "FLAT REVIEW PRESENTER: Starting flat review" debug.print_message(debug.LEVEL_INFO, msg, True) if script is None: script = script_manager.get_manager().get_active_script() assert script is not None self.get_or_create_context(script) if event is None: return if settings_manager.get_manager().get_setting('speechVerbosityLevel') \ != settings.VERBOSITY_LEVEL_BRIEF: script.present_message(messages.FLAT_REVIEW_START) self._item_presentation(script, event) def quit( self, script: default.Script | None = None, event: input_event.InputEvent | None = None ) -> None: """Quits flat review.""" if self._context is None: msg = "FLAT REVIEW PRESENTER: Not in flat review" debug.print_message(debug.LEVEL_INFO, msg, True) return msg = "FLAT REVIEW PRESENTER: Quitting flat review" debug.print_message(debug.LEVEL_INFO, msg, True) self._context = None focus = focus_manager.get_manager().get_locus_of_focus() focus_manager.get_manager().emit_region_changed(focus, mode=focus_manager.FOCUS_TRACKING) if event is None or script is None: return if settings_manager.get_manager().get_setting('speechVerbosityLevel') \ != settings.VERBOSITY_LEVEL_BRIEF: script.present_message(messages.FLAT_REVIEW_STOP) script.update_braille(focus) @dbus_service.command def toggle_flat_review_mode( self, script: default.Script | None = None, event: input_event.InputEvent | None = None, notify_user: bool = False ) -> bool: """Toggles between flat review mode and focus tracking mode.""" tokens = ["FLAT REVIEW PRESENTER: toggle_flat_review_mode. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) if self.is_active(): self.quit(script, event) return True self.start(script, event) return True @dbus_service.command def go_home( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Moves to the top left of the current window.""" tokens = ["FLAT REVIEW PRESENTER: go_home. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._context = self.get_or_create_context(script) self._context.go_to_start_of(flat_review.Context.WINDOW) self.present_line(script, event) return True @dbus_service.command def go_end( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Moves to the bottom right of the current window.""" tokens = ["FLAT REVIEW PRESENTER: go_end. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._context = self.get_or_create_context(script) self._context.go_to_end_of(flat_review.Context.WINDOW) self.present_line(script, event) return True @dbus_service.command def go_bottom_left( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Moves to the bottom left of the current window.""" tokens = ["FLAT REVIEW PRESENTER: go_bottom_left. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._context = self.get_or_create_context(script) self._context.go_to_end_of(flat_review.Context.WINDOW) self._context.go_to_start_of(flat_review.Context.LINE) self.present_line(script, event) return True @dbus_service.command def go_previous_line( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Moves to the previous line.""" tokens = ["FLAT REVIEW PRESENTER: go_previous_line. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._context = self.get_or_create_context(script) if self._context.go_previous_line(): self.present_line(script, event) return True @dbus_service.command def present_line( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Presents the current line.""" tokens = ["FLAT REVIEW PRESENTER: present_line. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._line_presentation(script, event, 1) return True @dbus_service.command def go_next_line( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Moves to the next line.""" tokens = ["FLAT REVIEW PRESENTER: go_next_line. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._context = self.get_or_create_context(script) if self._context.go_next_line(): self.present_line(script, event) return True @dbus_service.command def spell_line( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Presents the current line letter by letter.""" tokens = ["FLAT REVIEW PRESENTER: spell_line. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._line_presentation(script, event, 2) return True @dbus_service.command def phonetic_line( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Presents the current line letter by letter phonetically.""" tokens = ["FLAT REVIEW PRESENTER: phonetic_line. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._line_presentation(script, event, 3) return True @dbus_service.command def go_start_of_line( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Moves to the beginning of the current line.""" tokens = ["FLAT REVIEW PRESENTER: go_start_of_line. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._context = self.get_or_create_context(script) self._context.go_to_start_of(flat_review.Context.LINE) self.present_character(script, event) return True @dbus_service.command def go_end_of_line( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Moves to the end of the line.""" tokens = ["FLAT REVIEW PRESENTER: go_end_of_line. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._context = self.get_or_create_context(script) self._context.go_to_end_of(flat_review.Context.LINE) self.present_character(script, event) return True @dbus_service.command def go_previous_item( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Moves to the previous item or word.""" tokens = ["FLAT REVIEW PRESENTER: go_previous_item. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._context = self.get_or_create_context(script) if self._context.go_previous_word(): self.present_item(script, event) return True @dbus_service.command def present_item( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Presents the current item/word.""" tokens = ["FLAT REVIEW PRESENTER: present_item. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._item_presentation(script, event, 1) return True @dbus_service.command def go_next_item( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Moves to the next item or word.""" tokens = ["FLAT REVIEW PRESENTER: go_next_item. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._context = self.get_or_create_context(script) if self._context.go_next_word(): self.present_item(script, event) return True @dbus_service.command def spell_item( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Presents the current item/word letter by letter.""" tokens = ["FLAT REVIEW PRESENTER: spell_item. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._item_presentation(script, event, 2) return True @dbus_service.command def phonetic_item( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Presents the current word letter by letter phonetically.""" tokens = ["FLAT REVIEW PRESENTER: phonetic_item. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._item_presentation(script, event, 3) return True @dbus_service.command def go_previous_character( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Moves to the previous character.""" tokens = ["FLAT REVIEW PRESENTER: go_previous_character. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._context = self.get_or_create_context(script) if self._context.go_previous_character(): self.present_character(script, event) return True @dbus_service.command def present_character( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Presents the current character.""" tokens = ["FLAT REVIEW PRESENTER: present_character. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._character_presentation(script, event, 1) return True @dbus_service.command def go_next_character( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Moves to the next character.""" tokens = ["FLAT REVIEW PRESENTER: go_next_character. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._context = self.get_or_create_context(script) if self._context.go_next_character(): self.present_character(script, event) return True @dbus_service.command def spell_character( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Presents the current character phonetically.""" tokens = ["FLAT REVIEW PRESENTER: spell_character. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._character_presentation(script, event, 2) return True @dbus_service.command def unicode_current_character( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Presents the current character's unicode value.""" tokens = ["FLAT REVIEW PRESENTER: unicode_current_character. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._character_presentation(script, event, 3) return True @dbus_service.command def go_above( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Moves to the character above.""" tokens = ["FLAT REVIEW PRESENTER: go_above. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._context = self.get_or_create_context(script) if self._context.go_up(): self.present_item(script, event) return True @dbus_service.command def go_below( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Moves to the character below.""" tokens = ["FLAT REVIEW PRESENTER: go_below. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._context = self.get_or_create_context(script) if self._context.go_down(): self.present_item(script, event) return True @dbus_service.command def get_current_object( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> Atspi.Accessible: """Returns the current accessible object.""" tokens = ["FLAT REVIEW PRESENTER: get_current_object. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._context = self.get_or_create_context(script) return self._context.get_current_object() @dbus_service.command def present_object( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Presents the current accessible object.""" tokens = ["FLAT REVIEW PRESENTER: present_object. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._context = self.get_or_create_context(script) if not isinstance(event, input_event.BrailleEvent): script.present_object(self._context.get_current_object(), speechonly=True) focus_manager.get_manager().emit_region_changed( self._context.get_current_object(), mode=focus_manager.FLAT_REVIEW) return True @dbus_service.command def left_click_on_object( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Attempts to synthesize a left click on the current accessible.""" tokens = ["FLAT REVIEW PRESENTER: left_click_on_object. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._context = self.get_or_create_context(script) obj = self._context.get_current_object() offset = self._context.get_current_text_offset() if offset >= 0 and AXEventSynthesizer.click_character(obj, offset, 1): return True return AXEventSynthesizer.click_object(obj, 1) @dbus_service.command def right_click_on_object( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Attempts to synthesize a right click on the current accessible.""" tokens = ["FLAT REVIEW PRESENTER: right_click_on_object. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._context = self.get_or_create_context(script) obj = self._context.get_current_object() offset = self._context.get_current_text_offset() if offset >= 0 and AXEventSynthesizer.click_character(obj, offset, 3): return True return AXEventSynthesizer.click_object(obj, 3) @dbus_service.command def route_pointer_to_object( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Routes the mouse pointer to the current accessible.""" tokens = ["FLAT REVIEW PRESENTER: route_pointer_to_object. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._context = self.get_or_create_context(script) obj = self._context.get_current_object() offset = self._context.get_current_text_offset() if offset >= 0 and AXEventSynthesizer.route_to_character(obj, offset): return True return AXEventSynthesizer.route_to_object(obj) def _update_braille(self, script: default.Script) -> None: """Obtains the braille regions for the current flat review line and displays them.""" if not settings_manager.get_manager().get_setting("enableBraille") \ and not settings_manager.get_manager().get_setting("enableBrailleMonitor"): debug.print_message(debug.LEVEL_INFO, "BRAILLE: update review disabled", True) return self._context = self.get_or_create_context(script) regions, focused_region = self._context.get_current_braille_regions() if not regions: braille.refresh(True) return line = braille.Line() line.add_regions(regions) braille.set_lines([line]) braille.setFocus(focused_region) braille.refresh(True) # TODO - JD: See what adjustments might be needed for the pan_amount parameter def pan_braille_left( self, script: default.Script, _event: input_event.InputEvent | None = None, pan_amount: int = 0 ) -> bool: """Pans the braille display left.""" self._context = self.get_or_create_context(script) if braille.beginningIsShowing: self._context.go_to_start_of(flat_review.Context.LINE) self._context.go_previous_character() self._update_braille(script) return True braille.panLeft(pan_amount) braille_region, offset_in_zone = braille.getRegionAtCell(1) while braille_region and not hasattr(braille_region, "zone"): if not braille.panLeft(1): break braille_region, offset_in_zone = braille.getRegionAtCell(1) if braille_region and not hasattr(braille_region, "zone"): self._context.go_to_start_of(flat_review.Context.LINE) self._context.go_previous_character() self._update_braille(script) return True if braille_region and hasattr(braille_region, "zone"): self._context.set_current_zone(braille_region.zone, offset_in_zone) self._update_braille(script) return True # TODO - JD: See what adjustments might be needed for the pan_amount parameter def pan_braille_right( self, script: default.Script, _event: input_event.InputEvent | None = None, pan_amount: int = 0 ) -> bool: """Pans the braille display right.""" self._context = self.get_or_create_context(script) if braille.endIsShowing: self._context.go_next_line() self._update_braille(script) return True braille.panRight(pan_amount) braille_region, offset_in_zone = braille.getRegionAtCell(1) while braille_region and not hasattr(braille_region, "zone"): if not braille.panRight(1): break braille_region, offset_in_zone = braille.getRegionAtCell(1) if braille_region and not hasattr(braille_region, "zone"): self._context.go_next_line() self._update_braille(script) return True if braille_region and hasattr(braille_region, "zone"): self._context.set_current_zone(braille_region.zone, offset_in_zone) self._update_braille(script) return True def _get_all_lines( self, script: default.Script, _event: input_event.InputEvent | None = None ) -> tuple[list[str], tuple[int, int, int, int]]: """Returns a (textual lines, current location) tuple.""" lines = [] self._context = self.get_or_create_context(script) location = self._context.get_current_location() self._context.go_to_start_of(flat_review.Context.WINDOW) string = self._context.get_current_line_string() while string is not None: lines.append(string.rstrip("\n")) if not self._context.go_next_line(): break string = self._context.get_current_line_string() return lines, location @dbus_service.command def say_all( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Speaks the contents of the entire window.""" tokens = ["FLAT REVIEW PRESENTER: say_all. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) for string in self._get_all_lines(script, event)[0]: if not string.isspace(): script.speak_message(string, script.speech_generator.voice(string=string)) return True @dbus_service.command def show_contents( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Displays the entire flat review contents in a text view.""" tokens = ["FLAT REVIEW PRESENTER: show_contents. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) lines, location = self._get_all_lines(script, event) text = "\n".join(lines) title = guilabels.FLAT_REVIEW_CONTENTS self._gui = FlatReviewContextGUI(script, title, text, location) self._gui.show_gui() return True @dbus_service.command def copy_to_clipboard( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Copies the string just presented to the clipboard.""" tokens = ["FLAT REVIEW PRESENTER: copy_to_clipboard. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) if not self.is_active(): if notify_user: script.present_message(messages.FLAT_REVIEW_NOT_IN) return True script.get_clipboard_presenter().set_text(self._current_contents.rstrip("\n")) if notify_user: script.present_message(messages.FLAT_REVIEW_COPIED) return True @dbus_service.command def append_to_clipboard( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """Appends the string just presented to the clipboard.""" tokens = ["FLAT REVIEW PRESENTER: append_to_clipboard. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) if not self.is_active(): if notify_user: script.present_message(messages.FLAT_REVIEW_NOT_IN) return True script.get_clipboard_presenter().append_text(self._current_contents.rstrip("\n")) if notify_user: script.present_message(messages.FLAT_REVIEW_APPENDED) return True @dbus_service.getter def get_is_restricted(self) -> bool: """Returns whether flat review is restricted to the current object.""" return settings_manager.get_manager().get_setting("flatReviewIsRestricted") @dbus_service.setter def set_is_restricted(self, value: bool) -> bool: """Sets whether flat review is restricted to the current object.""" msg = f"FLAT REVIEW PRESENTER: Setting is-restricted to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) settings_manager.get_manager().set_setting("flatReviewIsRestricted", value) return True @dbus_service.command def toggle_restrict( self, script: default.Script, event: input_event.InputEvent | None = None, notify_user: bool = True ) -> bool: """ Toggles the restricting of flat review to the current object. """ tokens = ["FLAT REVIEW PRESENTER: toggle_restrict. Script:", script, "Event:", event, "notify_user:", notify_user] debug.print_tokens(debug.LEVEL_INFO, tokens, True) self._restrict = not self._restrict settings_manager.get_manager().set_setting("flatReviewIsRestricted", self._restrict) if self._restrict: if notify_user: script.present_message(messages.FLAT_REVIEW_RESTRICTED) else: if notify_user: script.present_message(messages.FLAT_REVIEW_UNRESTRICTED) if self.is_active(): # Reset the context self._context = None self.start() return True def _line_presentation( self, script: default.Script, event: input_event.InputEvent | None = None, speech_type: int = 1 ) -> bool: """Presents the current line.""" self._context = self.get_or_create_context(script) line_string = self._context.get_current_line_string() voice = script.speech_generator.voice(string=line_string) if not isinstance(event, input_event.BrailleEvent): if not line_string or line_string == "\n": script.speak_message(messages.BLANK) elif line_string.isspace(): script.speak_message(messages.WHITE_SPACE) elif line_string.isupper() and (speech_type < 2 or speech_type > 3): script.speak_message(line_string, voice) elif speech_type == 2: script.spell_item(line_string) elif speech_type == 3: script.spell_phonetically(line_string) else: manager = speech_and_verbosity_manager.get_manager() line_string = manager.adjust_for_presentation( self._context.get_current_object(), line_string) script.speak_message(line_string, voice) focus_manager.get_manager().emit_region_changed( self._context.get_current_object(), mode=focus_manager.FLAT_REVIEW) self._update_braille(script) self._current_contents = line_string return True def _item_presentation( self, script: default.Script, event: input_event.InputEvent | None = None, speech_type: int = 1 ) -> bool: """Presents the current item/word.""" self._context = self.get_or_create_context(script) word_string = self._context.get_current_word_string() voice = script.speech_generator.voice(string=word_string) if not isinstance(event, input_event.BrailleEvent): if not word_string or word_string == "\n": script.speak_message(messages.BLANK) else: line_string = self._context.get_current_line_string() if line_string == "\n": script.speak_message(messages.BLANK) elif word_string.isspace(): script.speak_message(messages.WHITE_SPACE) elif word_string.isupper() and speech_type == 1: script.speak_message(word_string, voice) elif speech_type == 2: script.spell_item(word_string) elif speech_type == 3: script.spell_phonetically(word_string) elif speech_type == 1: manager = speech_and_verbosity_manager.get_manager() word_string = manager.adjust_for_presentation( self._context.get_current_object(), word_string) script.speak_message(word_string, voice) focus_manager.get_manager().emit_region_changed( self._context.get_current_object(), mode=focus_manager.FLAT_REVIEW) self._update_braille(script) self._current_contents = word_string return True def _character_presentation( self, script: default.Script, event: input_event.InputEvent | None = None, speech_type: int = 1 ) -> bool: """Presents the current character.""" focus = focus_manager.get_manager().get_locus_of_focus() if not self._context and AXObject.supports_text(focus): char_string = AXText.get_character_at_offset(focus)[0] else: self._context = self.get_or_create_context(script) char_string = self._context.get_current_character_string() if not isinstance(event, input_event.BrailleEvent): if not char_string: script.speak_message(messages.BLANK) else: if char_string == "\n" and speech_type != 3: script.speak_message(messages.BLANK) elif speech_type == 3: script.speak_message(messages.UNICODE % f"{ord(char_string):04x}") elif speech_type == 2: script.spell_phonetically(char_string) else: script.speak_character(char_string) if not self._context: return True focus_manager.get_manager().emit_region_changed( self._context.get_current_object(), mode=focus_manager.FLAT_REVIEW) self._update_braille(script) self._current_contents = char_string return True class FlatReviewContextGUI: """Presents the entire flat review context in a text view""" def __init__( self, script: default.Script, title: str, text: str, location: tuple[int, int, int, int] ) -> None: self._script: default.Script = script self._gui: Gtk.Dialog = self._create_dialog(title, text, location) def _create_dialog( self, title: str, text: str, location: tuple[int, int, int, int] ) -> Gtk.Dialog: """Creates the dialog.""" dialog = Gtk.Dialog(title, None, Gtk.DialogFlags.MODAL, (Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)) dialog.set_default_size(800, 600) scrolled_window = Gtk.ScrolledWindow() scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrolled_window.set_hexpand(True) scrolled_window.set_vexpand(True) textbuffer = Gtk.TextBuffer() textbuffer.set_text(text) line_index, _zone_index, word_index, char_index = location start_iter = textbuffer.get_start_iter() start_iter.forward_lines(line_index) for _ in range(word_index): start_iter.forward_word_end() # This is needed to get past any punctuation. Note that we cannot check starts_word # here. Example: The letter after an apostrophe is reported as a word start. while not start_iter.get_char().isspace(): if not start_iter.forward_char(): break # The text iter word can start with one or more spaces. Move to the beginning of the flat # review word before advancing by character. while not start_iter.starts_word(): if not start_iter.forward_char(): break start_iter.forward_chars(char_index) textbuffer.place_cursor(start_iter) textview = Gtk.TextView(buffer=textbuffer) textview.set_wrap_mode(Gtk.WrapMode.WORD) scrolled_window.add(textview) # pylint: disable=no-member dialog.get_content_area().pack_start(scrolled_window, True, True, 0) dialog.connect("response", self.on_response) return dialog def on_response(self, _dialog: Gtk.Dialog, response: Gtk.ResponseType) -> None: """Handler for the 'response' signal of the dialog.""" if response == Gtk.ResponseType.CLOSE: self._gui.destroy() def show_gui(self) -> None: """Shows the dialog.""" self._gui.show_all() # pylint: disable=no-member self._gui.present_with_time(time.time()) _presenter: FlatReviewPresenter = FlatReviewPresenter() def get_presenter() -> FlatReviewPresenter: """Returns the Flat Review Presenter""" return _presenter