[orca] Create new, uniform spellcheck support and implement for Gedit and Thunderbird
- From: Joanmarie Diggs <joanied src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [orca] Create new, uniform spellcheck support and implement for Gedit and Thunderbird
- Date: Mon, 17 Feb 2014 20:51:59 +0000 (UTC)
commit 4948d54f263fe58371fef8b9e04b1708fc2d033f
Author: Joanmarie Diggs <jdiggs igalia com>
Date: Mon Feb 17 15:41:33 2014 -0500
Create new, uniform spellcheck support and implement for Gedit and Thunderbird
po/POTFILES.in | 1 -
src/orca/Makefile.am | 1 +
src/orca/guilabels.py | 22 ++
src/orca/script.py | 5 +
src/orca/scripts/apps/Thunderbird/Makefile.am | 3 +-
src/orca/scripts/apps/Thunderbird/script.py | 135 ++++++---
src/orca/scripts/apps/Thunderbird/spellcheck.py | 76 +++++
src/orca/scripts/apps/gedit/Makefile.am | 3 +-
src/orca/scripts/apps/gedit/script.py | 353 +++++++----------------
src/orca/scripts/apps/gedit/spellcheck.py | 59 ++++
src/orca/scripts/default.py | 10 +-
src/orca/settings.py | 7 +
src/orca/spellcheck.py | 296 +++++++++++++++++++
13 files changed, 668 insertions(+), 303 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index d7ecc16..afd46a7 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -19,7 +19,6 @@ src/orca/object_properties.py
[type: gettext/glade]src/orca/orca-setup.ui
src/orca/phonnames.py
src/orca/scripts/apps/evolution/speech_generator.py
-src/orca/scripts/apps/gedit/script.py
src/orca/scripts/apps/gnome-mud/script.py
src/orca/scripts/apps/liferea/script.py
src/orca/scripts/apps/metacity/script.py
diff --git a/src/orca/Makefile.am b/src/orca/Makefile.am
index 3c195a1..5b7de18 100644
--- a/src/orca/Makefile.am
+++ b/src/orca/Makefile.am
@@ -61,6 +61,7 @@ orca_python_PYTHON = \
settings_manager.py \
sound.py \
speech.py \
+ spellcheck.py \
speechdispatcherfactory.py \
speech_generator.py \
speechserver.py \
diff --git a/src/orca/guilabels.py b/src/orca/guilabels.py
index c262a0e..3a34912 100644
--- a/src/orca/guilabels.py
+++ b/src/orca/guilabels.py
@@ -636,6 +636,28 @@ SPEECH_VOICE_TYPE_UPPERCASE = C_("VoiceType", "Uppercase")
# system. (http://devel.freebsoft.org/speechd)
SPEECH_DISPATCHER = _("Speech Dispatcher")
+# Translators: This is a label for a group of options related to Orca's behavior
+# when presenting an application's spell check dialog.
+SPELL_CHECK = C_("OptionGroup", "Spell Check")
+
+# Translators: This is a label for a checkbox associated with an Orca setting.
+# When this option is enabled, Orca will spell out the current error in addition
+# to speaking it. For example, if the misspelled word is "foo," enabling this
+# setting would cause Orca to speak "f o o" after speaking "foo".
+SPELL_CHECK_SPELL_ERROR = _("Spell _error")
+
+# Translators: This is a label for a checkbox associated with an Orca setting.
+# When this option is enabled, Orca will spell out the current suggestion in
+# addition to speaking it. For example, if the misspelled word is "foo," and
+# the first suggestion is "for" enabling this setting would cause Orca to speak
+# "f o r" after speaking "for".
+SPELL_CHECK_SPELL_SUGGESTION = _("Spell _suggestion")
+
+# Translators: This is a label for a checkbox associated with an Orca setting.
+# When this option is enabled, Orca will present the context (surrounding text,
+# typically the sentence or line) in which the mistake occurred.
+SPELL_CHECK_PRESENT_CONTEXT = _("Present _context of error")
+
# Translators: This is a label for an option to tell Orca whether or not it
# should speak the coordinates of the current spread sheet cell. Coordinates are
# the row and column position within the spread sheet (i.e. A1, B1, C2 ...)
diff --git a/src/orca/script.py b/src/orca/script.py
index 927fd24..3d7488a 100644
--- a/src/orca/script.py
+++ b/src/orca/script.py
@@ -115,6 +115,7 @@ class Script:
self.eventCache = {}
self.whereAmI = self.getWhereAmI()
self.bookmarks = self.getBookmarks()
+ self.spellcheck = self.getSpellCheck()
self.voices = settings.voices
self.tutorialGenerator = self.getTutorialGenerator()
@@ -228,6 +229,10 @@ class Script:
"""
return None
+ def getSpellCheck(self):
+ """Returns the spellcheck support for this script."""
+ return None
+
def getUtilities(self):
"""Returns the utilites for this script.
"""
diff --git a/src/orca/scripts/apps/Thunderbird/Makefile.am b/src/orca/scripts/apps/Thunderbird/Makefile.am
index db1d54d..1825bbd 100644
--- a/src/orca/scripts/apps/Thunderbird/Makefile.am
+++ b/src/orca/scripts/apps/Thunderbird/Makefile.am
@@ -4,7 +4,8 @@ orca_python_PYTHON = \
script.py \
script_settings.py \
script_utilities.py \
- speech_generator.py
+ speech_generator.py \
+ spellcheck.py
orca_pythondir=$(pkgpythondir)/scripts/apps/Thunderbird
diff --git a/src/orca/scripts/apps/Thunderbird/script.py b/src/orca/scripts/apps/Thunderbird/script.py
index 6f42e55..ad85705 100644
--- a/src/orca/scripts/apps/Thunderbird/script.py
+++ b/src/orca/scripts/apps/Thunderbird/script.py
@@ -38,6 +38,7 @@ from orca.orca_i18n import _
from .formatting import Formatting
from .speech_generator import SpeechGenerator
+from .spellcheck import SpellCheck
from .script_utilities import Utilities
from . import script_settings
@@ -66,11 +67,6 @@ class Script(Gecko.Script):
Gecko.Script.__init__(self, app)
- # This will be used to cache a handle to the Thunderbird text area for
- # spell checking purposes.
-
- self.textArea = None
-
def getFormatting(self):
"""Returns the formatting strings for this script."""
return Formatting(self)
@@ -80,6 +76,11 @@ class Script(Gecko.Script):
return SpeechGenerator(self)
+ def getSpellCheck(self):
+ """Returns the spellcheck support for this script."""
+
+ return SpellCheck(self)
+
def getUtilities(self):
"""Returns the utilites for this script."""
@@ -95,6 +96,10 @@ class Script(Gecko.Script):
#
self.sayAllOnLoadCheckButton.set_active(script_settings.sayAllOnLoad)
+ spellcheck = self.spellcheck.getAppPreferencesGUI()
+ grid.attach(spellcheck, 0, len(grid.get_children()), 1, 1)
+ grid.show_all()
+
return grid
def setAppPreferences(self, prefs):
@@ -116,6 +121,17 @@ class Script(Gecko.Script):
prefs.writelines("%s.sayAllOnLoad = %s\n" % (prefix, value))
script_settings.sayAllOnLoad = value
+ self.spellcheck.setAppPreferences(prefs)
+
+ def doWhereAmI(self, inputEvent, basicOnly):
+ """Performs the whereAmI operation."""
+
+ if self.spellcheck.isActive():
+ self.spellcheck.presentErrorDetails(not basicOnly)
+ return
+
+ Gecko.Script.doWhereAmI(self,inputEvent, basicOnly)
+
def onFocusedChanged(self, event):
"""Callback for object:state-changed:focused accessibility events."""
@@ -126,12 +142,18 @@ class Script(Gecko.Script):
self.pointOfReference['lastAutoComplete'] = None
obj = event.source
+ if self.spellcheck.isAutoFocusEvent(event):
+ orca.setLocusOfFocus(event, event.source, False)
+
+ if obj.parent == self.spellcheck.getSuggestionsList():
+ self.spellcheck.presentSuggestionListItem()
+ return
+
if not self.inDocumentContent(obj):
default.Script.onFocusedChanged(self, event)
return
if self.isEditableMessage(obj):
- self.textArea = obj
default.Script.onFocusedChanged(self, event)
return
@@ -155,11 +177,39 @@ class Script(Gecko.Script):
self.speakMessage(obj.name)
self._presentMessage(obj)
+ def onCaretMoved(self, event):
+ """Callback for object:text-caret-moved accessibility events."""
+
+ if self.isEditableMessage(event.source):
+ if event.detail1 == -1:
+ return
+ self.spellcheck.setDocumentPosition(event.source, event.detail1)
+
+ Gecko.Script.onCaretMoved(self, event)
+
def onChildrenChanged(self, event):
"""Callback for object:children-changed accessibility events."""
default.Script.onChildrenChanged(self, event)
+ def onSelectionChanged(self, event):
+ """Callback for object:state-changed:showing accessibility events."""
+
+ # We present changes when the list has focus via focus-changed events.
+ if event.source == self.spellcheck.getSuggestionsList():
+ return
+
+ Gecko.Script.onSelectionChanged(self, event)
+
+ def onSensitiveChanged(self, event):
+ """Callback for object:state-changed:sensitive accessibility events."""
+
+ if event.source == self.spellcheck.getChangeToEntry() \
+ and self.spellcheck.presentCompletionMessage():
+ return
+
+ Gecko.Script.onSensitiveChanged(self, event)
+
def onShowingChanged(self, event):
"""Callback for object:state-changed:showing accessibility events."""
@@ -204,6 +254,9 @@ class Script(Gecko.Script):
if role == pyatspi.ROLE_LABEL and parentRole == pyatspi.ROLE_STATUS_BAR:
return
+ if len(event.any_data) > 1 and obj == self.spellcheck.getChangeToEntry():
+ return
+
isSystemEvent = event.type.endswith("system")
# Try to stop unwanted chatter when a message is being replied to.
@@ -240,14 +293,26 @@ class Script(Gecko.Script):
def onTextSelectionChanged(self, event):
"""Callback for object:text-selection-changed accessibility events."""
+ obj = event.source
+ spellCheckEntry = self.spellcheck.getChangeToEntry()
+ if obj == spellCheckEntry:
+ return
+
+ if self.isEditableMessage(obj) and self.spellcheck.isActive():
+ text = obj.queryText()
+ selStart, selEnd = text.getSelection(0)
+ self.spellcheck.setDocumentPosition(obj, selStart)
+ self.spellcheck.presentErrorDetails()
+ return
+
default.Script.onTextSelectionChanged(self, event)
def onNameChanged(self, event):
- """Called whenever a property on an object changes.
+ """Callback for object:property-change:accessible-name events."""
- Arguments:
- - event: the Event
- """
+ if event.source.name == self.spellcheck.getMisspelledWord():
+ self.spellcheck.presentErrorDetails()
+ return
obj = event.source
@@ -269,39 +334,6 @@ class Script(Gecko.Script):
self.setCaretPosition(obj, offset)
return
- # If we get a "object:property-change:accessible-name" event for
- # the first item in the Suggestions lists for the spell checking
- # dialog, then speak the first two labels in that dialog. These
- # will by the "Misspelled word:" label and the currently misspelled
- # word. See bug #535192 for more details.
- #
- rolesList = [pyatspi.ROLE_LIST_ITEM,
- pyatspi.ROLE_LIST,
- pyatspi.ROLE_DIALOG,
- pyatspi.ROLE_APPLICATION]
- if self.utilities.hasMatchingHierarchy(obj, rolesList):
- dialog = obj.parent.parent
-
- # Translators: this is what the name of the spell checking
- # dialog in Thunderbird begins with. The translated form
- # has to match what Thunderbird is using. We hate keying
- # off stuff like this, but we're forced to do so in this case.
- #
- if dialog.name.startswith(_("Check Spelling")):
- if obj.getIndexInParent() == 0:
- badWord = self.utilities.displayedText(dialog[1])
-
- if self.textArea != None:
- # If we have a handle to the Thunderbird message text
- # area, then extract out all the text objects, and
- # create a list of all the words found in them.
- #
- allTokens = []
- text = self.utilities.substring(self.textArea, 0, -1)
- tokens = text.split()
- allTokens += tokens
- self.speakMisspeltWord(allTokens, badWord)
-
def _presentMessage(self, documentFrame):
"""Presents the first line of the message, or the entire message,
depending on the user's sayAllOnLoad setting."""
@@ -383,3 +415,20 @@ class Script(Gecko.Script):
return False
return Gecko.Script.useCaretNavigationModel(self, keyboardEvent)
+
+ def onWindowActivated(self, event):
+ """Callback for window:activate accessibility events."""
+
+ Gecko.Script.onWindowActivated(self, event)
+ if not self.spellcheck.isCheckWindow(event.source):
+ return
+
+ self.spellcheck.presentErrorDetails()
+ orca.setLocusOfFocus(None, self.spellcheck.getChangeToEntry(), False)
+
+ def onWindowDeactivated(self, event):
+ """Callback for window:deactivate accessibility events."""
+
+ Gecko.Script.onWindowDeactivated(self, event)
+ if self.spellcheck.isCheckWindow(event.source):
+ self.spellcheck.deactivate()
diff --git a/src/orca/scripts/apps/Thunderbird/spellcheck.py b/src/orca/scripts/apps/Thunderbird/spellcheck.py
new file mode 100644
index 0000000..4099a32
--- /dev/null
+++ b/src/orca/scripts/apps/Thunderbird/spellcheck.py
@@ -0,0 +1,76 @@
+# Orca
+#
+# Copyright 2014 Igalia, S.L.
+#
+# Author: Joanmarie Diggs <jdiggs igalia com>
+#
+# 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.
+
+"""Customized support for spellcheck in Thunderbird."""
+
+__id__ = "$Id$"
+__version__ = "$Revision$"
+__date__ = "$Date$"
+__copyright__ = "Copyright (c) 2014 Igalia, S.L."
+__license__ = "LGPL"
+
+import pyatspi
+
+import orca.orca_state as orca_state
+import orca.spellcheck as spellcheck
+
+class SpellCheck(spellcheck.SpellCheck):
+
+ def __init__(self, script):
+ super(SpellCheck, self).__init__(script)
+
+ def isAutoFocusEvent(self, event):
+ if event.source != self._changeToEntry:
+ return False
+
+ locusOfFocus = orca_state.locusOfFocus
+ if not locusOfFocus:
+ return False
+
+ role = locusOfFocus.getRole()
+ if not role == pyatspi.ROLE_PUSH_BUTTON:
+ return False
+
+ lastKey, mods = self._script.utilities.lastKeyAndModifiers()
+ keys = self._script.utilities.mnemonicShortcutAccelerator(locusOfFocus)
+ for key in keys:
+ if key.endswith(lastKey.upper()):
+ return True
+
+ return False
+
+ def _isCandidateWindow(self, window):
+ return window and window.getRole() == pyatspi.ROLE_DIALOG
+
+ def _findChangeToEntry(self, root):
+ isEntry = lambda x: x and x.getRole() == pyatspi.ROLE_ENTRY \
+ and x.getState().contains(pyatspi.STATE_SINGLE_LINE)
+ return pyatspi.findDescendant(root, isEntry)
+
+ def _findErrorWidget(self, root):
+ isError = lambda x: x and x.getRole() == pyatspi.ROLE_LABEL \
+ and not ":" in x.name and not x.getRelationSet()
+ return pyatspi.findDescendant(root, isError)
+
+ def _findSuggestionsList(self, root):
+ isList = lambda x: x and x.getRole() == pyatspi.ROLE_LIST \
+ and 'Selection' in x.get_interfaces()
+ return pyatspi.findDescendant(root, isList)
diff --git a/src/orca/scripts/apps/gedit/Makefile.am b/src/orca/scripts/apps/gedit/Makefile.am
index 2076568..4728a7d 100644
--- a/src/orca/scripts/apps/gedit/Makefile.am
+++ b/src/orca/scripts/apps/gedit/Makefile.am
@@ -1,6 +1,7 @@
orca_python_PYTHON = \
__init__.py \
- script.py
+ script.py \
+ spellcheck.py
orca_pythondir=$(pkgpythondir)/scripts/apps/gedit
diff --git a/src/orca/scripts/apps/gedit/script.py b/src/orca/scripts/apps/gedit/script.py
index ff2caa2..e76dc10 100644
--- a/src/orca/scripts/apps/gedit/script.py
+++ b/src/orca/scripts/apps/gedit/script.py
@@ -27,272 +27,98 @@ __license__ = "LGPL"
import pyatspi
-import orca.debug as debug
+import orca.orca as orca
import orca.orca_state as orca_state
import orca.scripts.toolkits.gtk as gtk
-
-from orca.orca_i18n import _
+from .spellcheck import SpellCheck
class Script(gtk.Script):
def __init__(self, app):
- """Creates a new script for the given application.
-
- Arguments:
- - app: the application to create a script for.
- """
+ """Creates a new script for the given application."""
gtk.Script.__init__(self, app)
- # Set the debug level for all the methods in this script.
- #
- self.debugLevel = debug.LEVEL_FINEST
-
- # This will be used to cache a handle to the gedit text area for
- # spell checking purposes.
-
- self.textArea = None
-
- # The following variables will be used to try to determine if we've
- # already handled this misspelt word (see readMisspeltWord() for
- # more details.
-
- self.lastCaretPosition = -1
- self.lastBadWord = ''
- self.lastEventType = ''
-
- def readMisspeltWord(self, event, panel):
- """Speak/braille the current misspelt word plus its context.
- The spell check dialog contains a "paragraph" which shows the
- context for the current spelling mistake. After speaking/brailling
- the default action for this component, that a selection of the
- surronding text from that paragraph with the misspelt word is also
- spoken.
-
- Arguments:
- - event: the event.
- - panel: the panel in the check spelling dialog containing the label
- with the misspelt word.
- """
-
- # Braille the default action for this component.
- #
- self.updateBraille(orca_state.locusOfFocus)
-
- # Look for the label containing the misspelled word.
- # There will be three labels in the top panel in the Check
- # Spelling dialog. Look for the one that isn't a label to
- # another component.
- #
- allLabels = self.utilities.descendantsWithRole(
- panel, pyatspi.ROLE_LABEL)
- for label in allLabels:
- # Translators: these are labels from the gedit spell checking
- # dialog and must be the same strings gedit uses. We hate
- # keying off stuff like this, but we're forced to do so in
- # in this case.
- #
- if label.name.startswith(_("Change to:")) or \
- label.name.startswith(_("Misspelled word:")):
- continue
- else:
- badWord = label.name
- break
-
- # Note that we often get two or more of these focus or property-change
- # events each time there is a new misspelt word. We extract the
- # current text caret position and the misspelt word and compare
- # them against the values saved from the last time this routine
- # was called. If they are the same then we ignore it.
-
- if self.textArea != None:
- allText = self.utilities.descendantsWithRole(
- self.textArea, pyatspi.ROLE_TEXT)
- caretPosition = allText[0].queryText().caretOffset
-
- debug.println(self.debugLevel, \
- "gedit.readMisspeltWord: type=%s word=%s caret position=%d" \
- % (event.type, badWord, caretPosition))
-
- if (caretPosition == self.lastCaretPosition) and \
- (badWord == self.lastBadWord) and \
- (event.type == self.lastEventType):
- return
-
- # The indication that spell checking is complete is when the
- # "misspelt" word is set to "Completed spell checking". Ugh!
- # Try to detect this and let the user know.
- #
- # Translators: this string must be the same that is used by
- # gedit. We hate keying off stuff like this, but we're
- # forced to do so in this case.
- #
- if badWord == _("Completed spell checking"):
- utterance = _("Spell checking is complete.")
- self.presentMessage(utterance)
- utterance = _("Press Tab and Return to terminate.")
- self.presentMessage(utterance)
- return
-
- # If we have a handle to the gedit text area, then extract out
- # all the text objects, and create a list of all the words found
- # in them.
- #
- allTokens = []
- for i in range(0, len(allText)):
- text = self.utilities.substring(allText[i], 0, -1)
- tokens = text.split()
- allTokens += tokens
-
- self.speakMisspeltWord(allTokens, badWord)
-
- # Save misspelt word information for comparison purposes
- # next time around.
- #
- self.lastCaretPosition = caretPosition
- self.lastBadWord = badWord
- self.lastEventType = event.type
-
- def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus):
- """Called when the visual object with focus changes.
-
- Arguments:
- - event: if not None, the Event that caused the change
- - oldLocusOfFocus: Accessible that is the old locus of focus
- - newLocusOfFocus: Accessible that is the new locus of focus
- """
-
- details = debug.getAccessibleDetails(self.debugLevel, event.source)
- debug.printObjectEvent(self.debugLevel, event, details)
-
- # 1) Text area (for caching handle for spell checking purposes).
- #
- # This works in conjunction with code in section 2). Check to see if
- # focus is currently in the gedit text area. If it is, then, if this
- # is the first time, save a pointer to the scroll pane which contains
- # the text being editted.
- #
- # Note that this drops through to then use the default event
- # processing in the parent class for this "focus:" event.
-
- rolesList = [pyatspi.ROLE_TEXT,
- pyatspi.ROLE_SCROLL_PANE,
- pyatspi.ROLE_FILLER,
- pyatspi.ROLE_PAGE_TAB,
- pyatspi.ROLE_PAGE_TAB_LIST,
- pyatspi.ROLE_SPLIT_PANE]
- if self.utilities.hasMatchingHierarchy(event.source, rolesList):
- debug.println(self.debugLevel,
- "gedit.locusOfFocusChanged - text area.")
-
- self.textArea = event.source.parent
- # Fall-thru to process the event with the default handler.
-
- # 2) check spelling dialog.
- #
- # Check to see if the Spell Check dialog has just appeared and got
- # focus. If it has, then speak/braille the current misspelt word
- # plus its context.
- #
- # Note that in order to make sure that this focus event is for the
- # check spelling dialog, a check is made of the localized name of the
- # option pane. Translators for other locales will need to ensure that
- # their translation of this string matches what gedit uses in
- # that locale.
-
- rolesList = [pyatspi.ROLE_TEXT,
- pyatspi.ROLE_FILLER,
- pyatspi.ROLE_PANEL,
- pyatspi.ROLE_FILLER,
- pyatspi.ROLE_FRAME]
- if self.utilities.hasMatchingHierarchy(event.source, rolesList):
- tmp = event.source.parent.parent
- frame = tmp.parent.parent
- # Translators: this is the name of the "Check Spelling" window
- # in gedit and must be the same as what gedit uses. We hate
- # keying off stuff like this, but we're forced to do so in this
- # case.
- #
- if frame.name.startswith(_("Check Spelling")):
- debug.println(self.debugLevel,
- "gedit.locusOfFocusChanged - check spelling dialog.")
-
- self.readMisspeltWord(event, event.source.parent.parent)
- # Fall-thru to process the event with the default handler.
-
- # For everything else, pass the focus event onto the parent class
- # to be handled in the default way.
-
- gtk.Script.locusOfFocusChanged(self, event,
- oldLocusOfFocus, newLocusOfFocus)
-
- # If we are doing a Print Preview and we are focused on the
- # page number text area, also speak the "of n" labels to the
- # right of this area. See bug #133275 for more details.
- #
- rolesList = [pyatspi.ROLE_TEXT,
- pyatspi.ROLE_FILLER,
- pyatspi.ROLE_PANEL,
- pyatspi.ROLE_TOOL_BAR,
- pyatspi.ROLE_FILLER,
- pyatspi.ROLE_FILLER,
- pyatspi.ROLE_PAGE_TAB,
- pyatspi.ROLE_PAGE_TAB_LIST]
- if self.utilities.hasMatchingHierarchy(event.source, rolesList):
- parent = event.source.parent
- label1 = self.utilities.displayedText(parent[1])
- label2 = self.utilities.displayedText(parent[2])
- items = [label1, label2]
- self.presentItemsInSpeech(items)
- self.presentItemsInBraille(items)
-
- # This method tries to detect and handle the following cases:
- # 1) check spelling dialog.
+ def getSpellCheck(self):
+ """Returns the spellcheck for this script."""
+
+ return SpellCheck(self)
+
+ def getAppPreferencesGUI(self):
+ """Returns a GtkGrid containing the application unique configuration
+ GUI items for the current application."""
+
+ from gi.repository import Gtk
+
+ grid = Gtk.Grid()
+ grid.set_border_width(12)
+ grid.attach(self.spellcheck.getAppPreferencesGUI(), 0, 0, 1, 1)
+ grid.show_all()
+
+ return grid
+
+ def setAppPreferences(self, prefs):
+ """Write out the application specific preferences lines and set the
+ new values."""
+
+ self.spellcheck.setAppPreferences(prefs)
+
+ def doWhereAmI(self, inputEvent, basicOnly):
+ """Performs the whereAmI operation."""
+
+ if self.spellcheck.isActive():
+ self.spellcheck.presentErrorDetails(not basicOnly)
+ return
+
+ gtk.Script.doWhereAmI(self,inputEvent, basicOnly)
+
+ def onActiveDescendantChanged(self, event):
+ """Callback for object:active-descendant-changed accessibility events."""
+
+ if event.source == self.spellcheck.getSuggestionsList():
+ return
+
+ gtk.Script.onActiveDescendantChanged(self, event)
+
+ def onCaretMoved(self, event):
+ """Callback for object:text-caret-moved accessibility events."""
+
+ state = event.source.getState()
+ if state.contains(pyatspi.STATE_MULTI_LINE):
+ self.spellcheck.setDocumentPosition(event.source, event.detail1)
+
+ gtk.Script.onCaretMoved(self, event)
+
+ def onFocusedChanged(self, event):
+ """Callback for object:state-changed:focused accessibility events."""
+
+ if not event.detail1:
+ return
+
+ if event.source.parent == self.spellcheck.getSuggestionsList():
+ self.spellcheck.presentSuggestionListItem()
+ return
+
+ gtk.Script.onFocusedChanged(self, event)
def onNameChanged(self, event):
- """Called whenever a property on an object changes.
-
- Arguments:
- - event: the Event
- """
-
- details = debug.getAccessibleDetails(self.debugLevel, event.source)
- debug.printObjectEvent(self.debugLevel, event, details)
-
- # 1) check spelling dialog.
- #
- # Check to see if if we've had a property-change event for the
- # accessible name for the label containing the current misspelt
- # word in the check spelling dialog.
- # This (hopefully) means that the user has just corrected a
- # spelling mistake, in which case, speak/braille the current
- # misspelt word plus its context.
- #
- # Note that in order to make sure that this event is for the
- # check spelling dialog, a check is made of the localized name of the
- # frame. Translators for other locales will need to ensure that
- # their translation of this string matches what gedit uses in
- # that locale.
-
- rolesList = [pyatspi.ROLE_LABEL,
- pyatspi.ROLE_PANEL,
- pyatspi.ROLE_FILLER,
- pyatspi.ROLE_FRAME]
- if self.utilities.hasMatchingHierarchy(event.source, rolesList):
- frame = event.source.parent.parent.parent
- # Translators: this is the name of the "Check Spelling" window
- # in gedit and must be the same as what gedit uses. We hate
- # keying off stuff like this, but we're forced to do so in this
- # case.
- #
- if frame.name.startswith(_("Check Spelling")):
- debug.println(self.debugLevel,
- "gedit.onNameChanged - check spelling dialog.")
-
- self.readMisspeltWord(event, event.source.parent)
- # Fall-thru to process the event with the default handler.
-
- gtk.Script.onNameChanged(self, event)
+ """Callback for object:property-change:accessible-name events."""
+
+ if not self.spellcheck.isActive():
+ gtk.Script.onNameChanged(self, event)
+ return
+
+ if event.source.name == self.spellcheck.getMisspelledWord():
+ self.spellcheck.presentErrorDetails()
+
+ def onSensitiveChanged(self, event):
+ """Callback for object:state-changed:sensitive accessibility events."""
+
+ if event.source == self.spellcheck.getChangeToEntry() \
+ and self.spellcheck.presentCompletionMessage():
+ return
+
+ gtk.Script.onSensitiveChanged(self, event)
def onTextSelectionChanged(self, event):
"""Callback for object:text-selection-changed accessibility events."""
@@ -310,3 +136,20 @@ class Script(gtk.Script):
return
self.sayLine(event.source)
+
+ def onWindowActivated(self, event):
+ """Callback for window:activate accessibility events."""
+
+ gtk.Script.onWindowActivated(self, event)
+ if not self.spellcheck.isCheckWindow(event.source):
+ return
+
+ self.spellcheck.presentErrorDetails()
+ orca.setLocusOfFocus(None, self.spellcheck.getChangeToEntry(), False)
+
+ def onWindowDeactivated(self, event):
+ """Callback for window:deactivate accessibility events."""
+
+ gtk.Script.onWindowDeactivated(self, event)
+ if self.spellcheck.isCheckWindow(event.source):
+ self.spellcheck.deactivate()
diff --git a/src/orca/scripts/apps/gedit/spellcheck.py b/src/orca/scripts/apps/gedit/spellcheck.py
new file mode 100644
index 0000000..342afed
--- /dev/null
+++ b/src/orca/scripts/apps/gedit/spellcheck.py
@@ -0,0 +1,59 @@
+# Orca
+#
+# Copyright 2014 Igalia, S.L.
+#
+# Author: Joanmarie Diggs <jdiggs igalia com>
+#
+# 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.
+
+"""Customized support for spellcheck in Gedit."""
+
+__id__ = "$Id$"
+__version__ = "$Revision$"
+__date__ = "$Date$"
+__copyright__ = "Copyright (c) 2014 Igalia, S.L."
+__license__ = "LGPL"
+
+import pyatspi
+import orca.spellcheck as spellcheck
+
+class SpellCheck(spellcheck.SpellCheck):
+
+ def __init__(self, script):
+ super(SpellCheck, self).__init__(script)
+
+ def _isCandidateWindow(self, window):
+ return window and window.getRole() == pyatspi.ROLE_FRAME
+
+ def _findChangeToEntry(self, root):
+ isEntry = lambda x: x and x.getRole() == pyatspi.ROLE_TEXT \
+ and x.getState().contains(pyatspi.STATE_SINGLE_LINE)
+ return pyatspi.findDescendant(root, isEntry)
+
+ def _findErrorWidget(self, root):
+ isPanel = lambda x: x and x.getRole() == pyatspi.ROLE_PANEL
+ panel = pyatspi.findAncestor(self._changeToEntry, isPanel)
+ if not panel:
+ return None
+
+ isError = lambda x: x and x.getRole() == pyatspi.ROLE_LABEL \
+ and not ":" in x.name and not x.getRelationSet()
+ return pyatspi.findDescendant(panel, isError)
+
+ def _findSuggestionsList(self, root):
+ isTable = lambda x: x and x.getRole() == pyatspi.ROLE_TABLE \
+ and 'Selection' in x.get_interfaces()
+ return pyatspi.findDescendant(root, isTable)
diff --git a/src/orca/scripts/default.py b/src/orca/scripts/default.py
index 4468e23..a0ca272 100644
--- a/src/orca/scripts/default.py
+++ b/src/orca/scripts/default.py
@@ -549,6 +549,8 @@ class Script(script.Script):
self.onExpandedChanged
listeners["object:state-changed:selected"] = \
self.onSelectedChanged
+ listeners["object:state-changed:sensitive"] = \
+ self.onSensitiveChanged
listeners["object:text-attributes-changed"] = \
self.onTextAttributesChanged
listeners["object:text-selection-changed"] = \
@@ -1363,10 +1365,10 @@ class Script(script.Script):
for (charIndex, character) in enumerate(itemString):
if character.isupper():
- speech.speak(character,
+ speech.speakCharacter(character,
self.voices[settings.UPPERCASE_VOICE])
else:
- speech.speak(character)
+ speech.speakCharacter(character)
def _reviewCurrentItem(self, inputEvent, targetCursorCell=0,
speechType=1):
@@ -2405,6 +2407,10 @@ class Script(script.Script):
orca.setLocusOfFocus(event, child)
break
+ def onSensitiveChanged(self, event):
+ """Callback for object:state-changed:sensitive accessibility events."""
+ pass
+
def onFocus(self, event):
"""Callback for focus: accessibility events."""
diff --git a/src/orca/settings.py b/src/orca/settings.py
index b1778a8..466f0dd 100644
--- a/src/orca/settings.py
+++ b/src/orca/settings.py
@@ -109,6 +109,9 @@ userCustomizableSettings = [
"presentTimeFormat",
"activeProfile",
"startingProfile",
+ "spellcheckSpellError",
+ "spellcheckSpellSuggestion",
+ "spellcheckPresentContext",
]
excludeKeys = ["pronunciations",
@@ -823,3 +826,7 @@ presentDateFormat = DATE_FORMAT_LOCALE
# Default tty to pass along to brlapi.
tty = 7
+
+spellcheckSpellError = True
+spellcheckSpellSuggestion = True
+spellcheckPresentContext = True
diff --git a/src/orca/spellcheck.py b/src/orca/spellcheck.py
new file mode 100644
index 0000000..962303a
--- /dev/null
+++ b/src/orca/spellcheck.py
@@ -0,0 +1,296 @@
+# Orca
+#
+# Copyright 2014 Igalia, S.L.
+#
+# Author: Joanmarie Diggs <jdiggs igalia com>
+#
+# 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.
+
+"""Script-customizable support for application spellcheckers."""
+
+__id__ = "$Id$"
+__version__ = "$Revision$"
+__date__ = "$Date$"
+__copyright__ = "Copyright (c) 2014 Igalia, S.L."
+__license__ = "LGPL"
+
+import pyatspi
+import re
+
+from orca import guilabels
+from orca import messages
+from orca import settings_manager
+
+_settingsManager = settings_manager.getManager()
+
+class SpellCheck:
+
+ def __init__(self, script, hasChangeToEntry=True):
+ self._script = script
+ self._hasChangeToEntry = hasChangeToEntry
+ self._clearState()
+
+ self.spellErrorCheckButton = None
+ self.spellSuggestionCheckButton = None
+ self.presentContextCheckButton = None
+
+ def activate(self, window):
+ if not self._isCandidateWindow(window):
+ return False
+
+ if self._hasChangeToEntry:
+ self._changeToEntry = self._findChangeToEntry(window)
+ if not self._changeToEntry:
+ return False
+
+ self._errorWidget = self._findErrorWidget(window)
+ if not self._errorWidget:
+ return False
+
+ self._suggestionsList = self._findSuggestionsList(window)
+ if not self._suggestionsList:
+ return False
+
+ self._window = window
+ self._activated = True
+ return True
+
+ def deactivate(self):
+ self._clearState()
+
+ def getDocumentPosition(self):
+ return self._documentPosition
+
+ def setDocumentPosition(self, obj, offset):
+ self._documentPosition = obj, offset
+
+ def getErrorWidget(self):
+ return self._errorWidget
+
+ def getMisspelledWord(self):
+ if not self._errorWidget:
+ return ""
+
+ return self._script.utilities.displayedText(self._errorWidget)
+
+ def getCompletionMessage(self):
+ if not self._errorWidget:
+ return ""
+
+ return self._script.utilities.displayedText(self._errorWidget)
+
+ def getChangeToEntry(self):
+ return self._changeToEntry
+
+ def getSuggestionsList(self):
+ return self._suggestionsList
+
+ def isActive(self):
+ return self._activated
+
+ def isCheckWindow(self, window):
+ if window and window == self._window:
+ return True
+
+ return self.activate(window)
+
+ def isComplete(self):
+ try:
+ state = self._changeToEntry.getState()
+ except:
+ return False
+ return not state.contains(pyatspi.STATE_SENSITIVE)
+
+ def isAutoFocusEvent(self, event):
+ return False
+
+ def presentContext(self):
+ if not self.isActive():
+ return False
+
+ obj, offset = self._documentPosition
+ if not (obj and offset >= 0):
+ return False
+
+ try:
+ text = obj.queryText()
+ except:
+ return False
+
+ # This should work, but some toolkits are broken.
+ boundary = pyatspi.TEXT_BOUNDARY_SENTENCE_START
+ string, start, end = text.getTextAtOffset(offset, boundary)
+
+ if not string:
+ boundary = pyatspi.TEXT_BOUNDARY_LINE_START
+ string, start, end = text.getTextAtOffset(offset, boundary)
+ sentences = re.split(r'(?:\.|\!|\?)', string)
+ word = self.getMisspelledWord()
+ if string.count(word) == 1:
+ match = list(filter(lambda x: x.count(word), sentences))
+ string = match[0]
+
+ if not string:
+ return False
+
+ self._script.speakMessage(messages.MISSPELLED_WORD_CONTEXT % string)
+ return True
+
+ def presentCompletionMessage(self):
+ if not (self.isActive() and self.isComplete()):
+ return False
+
+ self._script.presentMessage(self.getCompletionMessage())
+ return True
+
+ def presentErrorDetails(self, detailed=False):
+ if self.isComplete():
+ return False
+
+ if self.presentMistake(detailed):
+ self.presentSuggestion(detailed)
+ if detailed or _settingsManager.getSetting('spellcheckPresentContext'):
+ self.presentContext()
+ return True
+
+ return False
+
+ def presentMistake(self, detailed=False):
+ if not self.isActive():
+ return False
+
+ word = self.getMisspelledWord()
+ if not word:
+ return False
+
+ self._script.speakMessage(messages.MISSPELLED_WORD % word)
+ if detailed or _settingsManager.getSetting('spellcheckSpellError'):
+ self._script.spellCurrentItem(word)
+
+ return True
+
+ def presentSuggestion(self, detailed=False):
+ if not self._hasChangeToEntry:
+ return self.presentSuggestionListItem(detailed)
+
+ if not self.isActive():
+ return False
+
+ entry = self._changeToEntry
+ if not entry:
+ return False
+
+ label = self._script.utilities.displayedLabel(entry)
+ string = self._script.utilities.substring(entry, 0, -1)
+ self._script.speakMessage("%s %s" % (label, string))
+ if detailed or _settingsManager.getSetting('spellcheckSpellSuggestion'):
+ self._script.spellCurrentItem(string)
+
+ return True
+
+ def presentSuggestionListItem(self, detailed=False):
+ if not self.isActive():
+ return False
+
+ suggestions = self._suggestionsList
+ if not suggestions:
+ return False
+
+ items = self._script.utilities.selectedChildren(suggestions)
+ if not len(items) == 1:
+ return False
+
+ string = items[0].name
+ self._script.speakMessage(string)
+ if detailed or _settingsManager.getSetting('spellcheckSpellSuggestion'):
+ self._script.spellCurrentItem(string)
+
+ return True
+
+ def _clearState(self):
+ self._window = None
+ self._errorWidget = None
+ self._changeToEntry = None
+ self._suggestionsList = None
+ self._activated = False
+ self._documentPosition = None, -1
+
+ def _isCandidateWindow(self, window):
+ return False
+
+ def _findChangeToEntry(self, root):
+ return None
+
+ def _findErrorWidget(self, root):
+ return None
+
+ def _findSuggestionsList(self, root):
+ return None
+
+ def getAppPreferencesGUI(self):
+
+ from gi.repository import Gtk
+
+ frame = Gtk.Frame()
+ label = Gtk.Label(label="<b>%s</b>" % guilabels.SPELL_CHECK)
+ label.set_use_markup(True)
+ frame.set_label_widget(label)
+
+ alignment = Gtk.Alignment.new(0.5, 0.5, 1, 1)
+ alignment.set_padding(0, 0, 12, 0)
+ frame.add(alignment)
+
+ grid = Gtk.Grid()
+ alignment.add(grid)
+
+ label = guilabels.SPELL_CHECK_SPELL_ERROR
+ value = _settingsManager.getSetting('spellcheckSpellError')
+ self.spellErrorCheckButton = Gtk.CheckButton.new_with_mnemonic(label)
+ self.spellErrorCheckButton.set_active(value)
+ grid.attach(self.spellErrorCheckButton, 0, 0, 1, 1)
+
+ label = guilabels.SPELL_CHECK_SPELL_SUGGESTION
+ value = _settingsManager.getSetting('spellcheckSpellSuggestion')
+ self.spellSuggestionCheckButton = Gtk.CheckButton.new_with_mnemonic(label)
+ self.spellSuggestionCheckButton.set_active(value)
+ grid.attach(self.spellSuggestionCheckButton, 0, 1, 1, 1)
+
+ label = guilabels.SPELL_CHECK_PRESENT_CONTEXT
+ value = _settingsManager.getSetting('spellcheckPresentContext')
+ self.presentContextCheckButton = Gtk.CheckButton.new_with_mnemonic(label)
+ self.presentContextCheckButton.set_active(value)
+ grid.attach(self.presentContextCheckButton, 0, 2, 1, 1)
+
+ return frame
+
+ def setAppPreferences(self, prefs):
+
+ prefix = "orca.settings"
+
+ value = self.spellErrorCheckButton.get_active()
+ _settingsManager.setSetting('spellcheckSpellError', value)
+ prefs.writelines("\n")
+ prefs.writelines("%s.spellcheckSpellError = %s\n" % (prefix, value))
+
+ value = self.spellSuggestionCheckButton.get_active()
+ _settingsManager.setSetting('spellcheckSpellSuggestion', value)
+ prefs.writelines("\n")
+ prefs.writelines("%s.spellcheckSpellSuggestion = %s\n" % (prefix, value))
+
+ value = self.presentContextCheckButton.get_active()
+ _settingsManager.setSetting('spellcheckPresentContext', value)
+ prefs.writelines("\n")
+ prefs.writelines("%s.spellcheckPresentContext = %s\n" % (prefix, value))
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]