[orca] Add option to present page summary upon load, and include landmarks in summary



commit 4e9734203eb76383d10eda48f0e06c4203b39af7
Author: Joanmarie Diggs <jdiggs igalia com>
Date:   Sun Feb 14 20:34:52 2016 -0500

    Add option to present page summary upon load, and include landmarks in summary

 help/C/preferences_gecko.page               |   11 +++++
 src/orca/guilabels.py                       |    5 ++
 src/orca/messages.py                        |   34 ++++++++++++--
 src/orca/scripts/apps/Thunderbird/script.py |   13 +++++
 src/orca/scripts/web/script.py              |   19 +++++++-
 src/orca/scripts/web/script_utilities.py    |   66 +++++++++++++++++---------
 src/orca/scripts/web/speech_generator.py    |   25 +++--------
 7 files changed, 125 insertions(+), 48 deletions(-)
---
diff --git a/help/C/preferences_gecko.page b/help/C/preferences_gecko.page
index a3ec260..2286357 100644
--- a/help/C/preferences_gecko.page
+++ b/help/C/preferences_gecko.page
@@ -120,6 +120,17 @@
       </p>
     </section>
     <section>
+      <title>Present summary of a page when it is first loaded</title>
+      <p>
+        If this checkbox is checked, <app>Orca</app> will summarize details about the
+        newly opened web page or email, such as the number of headings, landmarks,
+        and links.
+      </p>
+      <p>
+        Default value: checked for Firefox; not checked for Thunderbird
+      </p>
+    </section>
+    <section>
       <title>Enable layout mode for content</title>
       <p>
         If this checkbox is checked, <app>Orca</app>'s caret navigation will respect
diff --git a/src/orca/guilabels.py b/src/orca/guilabels.py
index 6818ea5..42af135 100644
--- a/src/orca/guilabels.py
+++ b/src/orca/guilabels.py
@@ -674,6 +674,11 @@ PAGE_NAVIGATION = _("Page Navigation")
 READ_PAGE_UPON_LOAD = \
     _("Automatically start speaking a page when it is first _loaded")
 
+# Translators: When the user loads a new web page, they can optionally have Orca
+# automatically summarize details about the page, such as the number of elements
+# (landmarks, forms, links, tables, etc.).
+PAGE_SUMMARY_UPON_LOAD = _("_Present summary of a page when it is first loaded")
+
 # Translators: Different speech systems and speech engines work differently when
 # it comes to handling pauses (e.g. sentence boundaries). This property allows
 # the user to specify whether speech should be sent to the speech synthesis
diff --git a/src/orca/messages.py b/src/orca/messages.py
index 5e5f409..c7e5f6f 100644
--- a/src/orca/messages.py
+++ b/src/orca/messages.py
@@ -2013,11 +2013,17 @@ def filesFound(count):
     # a result of a search.
     return ngettext("%d file found", "%d files found", count) % count
 
-def formCount(count):
+def formCount(count, onlyIfFound=True):
+    if not count and onlyIfFound:
+        return ""
+
     # Translators: This message presents the number of forms in a document.
     return ngettext("%d form", "%d forms", count) % count
 
-def headingCount(count):
+def headingCount(count, onlyIfFound=True):
+    if not count and onlyIfFound:
+        return ""
+
     # Translators: This message presents the number of headings in a document.
     return ngettext("%d heading", "%d headings", count) % count
 
@@ -2026,6 +2032,15 @@ def itemCount(count):
     # or table.
     return ngettext("%d item", "%d items", count) % count
 
+def landmarkCount(count, onlyIfFound=True):
+    if not count and onlyIfFound:
+        return ""
+
+    # Translators: This message presents the number of landmarks in a document.
+    # ARIA role landmarks are the W3C defined HTML tag attribute 'role' used to
+    # identify important part of webpage like banners, main context, search etc.
+    return ngettext("%d landmark", "%d landmarks", count) % count
+
 def itemsFound(count):
     # Translators: Orca has several commands that search for, and present a list
     # of, objects based on one or more criteria. This is a message that will be
@@ -2131,7 +2146,10 @@ def tabsCount(count):
     # tab characters in a string.
     return ngettext("%d tab", "%d tabs", count) % count
 
-def tableCount(count):
+def tableCount(count, onlyIfFound=True):
+    if not count and onlyIfFound:
+        return ""
+
     # Translators: This message presents the number of tables in a document.
     return ngettext("%d table", "%d tables", count) % count
 
@@ -2147,12 +2165,18 @@ def tableSize(nRows, nColumns):
 
     return rowString + " " + colString
 
-def unvisitedLinkCount(count):
+def unvisitedLinkCount(count, onlyIfFound=True):
+    if not count and onlyIfFound:
+        return ""
+
     # Translators: This message presents the number of unvisited links in a
     # document.
     return ngettext("%d unvisited link", "%d unvisited links", count) % count
 
-def visitedLinkCount(count):
+def visitedLinkCount(count, onlyIfFound=True):
+    if not count and onlyIfFound:
+        return ""
+
     # Translators: This message presents the number of visited links in a
     # document.
     return ngettext("%d visited link", "%d visited links", count) % count
diff --git a/src/orca/scripts/apps/Thunderbird/script.py b/src/orca/scripts/apps/Thunderbird/script.py
index abe23fd..ef85697 100644
--- a/src/orca/scripts/apps/Thunderbird/script.py
+++ b/src/orca/scripts/apps/Thunderbird/script.py
@@ -65,6 +65,8 @@ class Script(Gecko.Script):
 
         if _settingsManager.getSetting('sayAllOnLoad') == None:
             _settingsManager.setSetting('sayAllOnLoad', False)
+        if _settingsManager.getSetting('pageSummaryOnLoad') == None:
+            _settingsManager.setSetting('pageSummaryOnLoad', False)
 
         Gecko.Script.__init__(self, app)
 
@@ -94,6 +96,8 @@ class Script(Gecko.Script):
 
         self._sayAllOnLoadCheckButton.set_active(
             _settingsManager.getSetting('sayAllOnLoad'))
+        self._pageSummaryOnLoadCheckButton.set_active(
+            _settingsManager.getSetting('pageSummaryOnLoad'))
 
         spellcheck = self.spellcheck.getAppPreferencesGUI()
         grid.attach(spellcheck, 0, len(grid.get_children()), 1, 1)
@@ -106,6 +110,7 @@ class Script(Gecko.Script):
 
         prefs = Gecko.Script.getPreferencesFromGUI(self)
         prefs['sayAllOnLoad'] = self._sayAllOnLoadCheckButton.get_active()
+        prefs['pageSummaryOnLoad'] = self._pageSummaryOnLoadCheckButton.get_active()
         prefs.update(self.spellcheck.getPreferencesFromGUI())
 
         return prefs
@@ -356,6 +361,14 @@ class Script(Gecko.Script):
         [obj, offset] = self.utilities.findFirstCaretContext(documentFrame, 0)
         self.utilities.setCaretPosition(obj, offset)
         self.updateBraille(obj)
+
+        if _settingsManager.getSetting('pageSummaryOnLoad'):
+            msg = "THUNDERBIRD: Getting page summary for obj %s" % obj
+            debug.println(debug.LEVEL_INFO, msg, True)
+            summary = self.utilities.getPageSummary(obj)
+            if summary:
+                self.presentMessage(summary)
+
         if not _settingsManager.getSetting('sayAllOnLoad'):
             msg = "THUNDERBIRD: SayAllOnLoad is False. Presenting line."
             debug.println(debug.LEVEL_INFO, msg, True)
diff --git a/src/orca/scripts/web/script.py b/src/orca/scripts/web/script.py
index d8a4fe9..f787731 100644
--- a/src/orca/scripts/web/script.py
+++ b/src/orca/scripts/web/script.py
@@ -83,12 +83,15 @@ class Script(default.Script):
             _settingsManager.setSetting('caretNavigationEnabled', True)
         if _settingsManager.getSetting('sayAllOnLoad') == None:
             _settingsManager.setSetting('sayAllOnLoad', True)
+        if _settingsManager.getSetting('pageSummaryOnLoad') == None:
+            _settingsManager.setSetting('pageSummaryOnLoad', True)
 
         self._changedLinesOnlyCheckButton = None
         self._controlCaretNavigationCheckButton = None
         self._minimumFindLengthAdjustment = None
         self._minimumFindLengthLabel = None
         self._minimumFindLengthSpinButton = None
+        self._pageSummaryOnLoadCheckButton = None
         self._sayAllOnLoadCheckButton = None
         self._skipBlankCellsCheckButton = None
         self._speakCellCoordinatesCheckButton = None
@@ -325,11 +328,17 @@ class Script(default.Script):
         self._sayAllOnLoadCheckButton.set_active(value)
         generalGrid.attach(self._sayAllOnLoadCheckButton, 0, 4, 1, 1)
 
+        label = guilabels.PAGE_SUMMARY_UPON_LOAD
+        value = _settingsManager.getSetting('pageSummaryOnLoad')
+        self._pageSummaryOnLoadCheckButton = Gtk.CheckButton.new_with_mnemonic(label)
+        self._pageSummaryOnLoadCheckButton.set_active(value)
+        generalGrid.attach(self._pageSummaryOnLoadCheckButton, 0, 5, 1, 1)
+
         label = guilabels.CONTENT_LAYOUT_MODE
         value = _settingsManager.getSetting('layoutMode')
         self._layoutModeCheckButton = Gtk.CheckButton.new_with_mnemonic(label)
         self._layoutModeCheckButton.set_active(value)
-        generalGrid.attach(self._layoutModeCheckButton, 0, 5, 1, 1)
+        generalGrid.attach(self._layoutModeCheckButton, 0, 6, 1, 1)
 
         tableFrame = Gtk.Frame()
         grid.attach(tableFrame, 0, 1, 1, 1)
@@ -436,6 +445,7 @@ class Script(default.Script):
             'findResultsVerbosity': verbosity,
             'findResultsMinimumLength': self._minimumFindLengthSpinButton.get_value(),
             'sayAllOnLoad': self._sayAllOnLoadCheckButton.get_active(),
+            'pageSummaryOnLoad': self._pageSummaryOnLoadCheckButton.get_active(),
             'structuralNavigationEnabled': self._structuralNavigationCheckButton.get_active(),
             'structNavTriggersFocusMode': self._autoFocusModeStructNavCheckButton.get_active(),
             'caretNavigationEnabled': self._controlCaretNavigationCheckButton.get_active(),
@@ -1093,6 +1103,13 @@ class Script(default.Script):
 
         self.utilities.clearCachedObjects()
 
+        if _settingsManager.getSetting('pageSummaryOnLoad'):
+            msg = "WEB: Getting page summary for obj %s" % obj
+            debug.println(debug.LEVEL_INFO, msg, True)
+            summary = self.utilities.getPageSummary(obj)
+            if summary:
+                self.presentMessage(summary)
+
         obj, offset = self.utilities.getCaretContext()
 
         try:
diff --git a/src/orca/scripts/web/script_utilities.py b/src/orca/scripts/web/script_utilities.py
index ebd0517..406283c 100644
--- a/src/orca/scripts/web/script_utilities.py
+++ b/src/orca/scripts/web/script_utilities.py
@@ -168,13 +168,13 @@ class Utilities(script_utilities.Utilities):
             return None
 
         if self.isDocument(obj):
-            msg = "WEB: %s is document" % obj
-            debug.println(debug.LEVEL_INFO, msg, True)
             return obj
 
         document = pyatspi.findAncestor(obj, self.isDocument)
-        msg = "WEB: Document for %s is %s" % (obj, document)
-        debug.println(debug.LEVEL_INFO, msg, True)
+        if not document:
+            msg = "WEB: Could not find document for %s" % obj
+            debug.println(debug.LEVEL_INFO, msg, True)
+
         return document
 
     def _getDocumentsEmbeddedBy(self, frame):
@@ -2852,19 +2852,23 @@ class Utilities(script_utilities.Utilities):
 
         return self.isLiveRegion(event.source)
 
-    def getPageSummary(self, obj):
+    def getPageObjectCount(self, obj):
+        result = {'landmarks': 0,
+                  'headings': 0,
+                  'forms': 0,
+                  'tables': 0,
+                  'visitedLinks': 0,
+                  'unvisitedLinks': 0}
+
         docframe = self.documentFrame(obj)
         col = docframe.queryCollection()
-        headings = 0
-        forms = 0
-        tables = 0
-        vlinks = 0
-        uvlinks = 0
-        percentRead = None
-
         stateset = pyatspi.StateSet()
-        roles = [pyatspi.ROLE_HEADING, pyatspi.ROLE_LINK, pyatspi.ROLE_TABLE,
-                 pyatspi.ROLE_FORM]
+        roles = [pyatspi.ROLE_HEADING,
+                 pyatspi.ROLE_LINK,
+                 pyatspi.ROLE_TABLE,
+                 pyatspi.ROLE_FORM,
+                 pyatspi.ROLE_SECTION, # We can nuke this when Firefox correcly maps landmarks
+                 pyatspi.ROLE_LANDMARK]
         rule = col.createMatchRule(stateset.raw(), col.MATCH_NONE,
                                    "", col.MATCH_NONE,
                                    roles, col.MATCH_ANY,
@@ -2876,18 +2880,34 @@ class Utilities(script_utilities.Utilities):
         for obj in matches:
             role = obj.getRole()
             if role == pyatspi.ROLE_HEADING:
-                headings += 1
+                result['headings'] += 1
             elif role == pyatspi.ROLE_FORM:
-                forms += 1
+                result['forms'] += 1
             elif role == pyatspi.ROLE_TABLE and not self.isLayoutOnly(obj):
-                tables += 1
+                result['tables'] += 1
             elif role == pyatspi.ROLE_LINK:
-                if obj.getState().contains(pyatspi.STATE_VISITED):
-                    vlinks += 1
-                else:
-                    uvlinks += 1
-
-        return [headings, forms, tables, vlinks, uvlinks, percentRead]
+                if self.isLink(obj):
+                    if obj.getState().contains(pyatspi.STATE_VISITED):
+                        result['visitedLinks'] += 1
+                    else:
+                        result['unvisitedLinks'] += 1
+            elif self.isLandmark(obj):
+                result['landmarks'] += 1
+
+        return result
+
+    def getPageSummary(self, obj, onlyIfFound=True):
+        result = []
+        counts = self.getPageObjectCount(obj)
+        result.append(messages.landmarkCount(counts.get('landmarks', 0), onlyIfFound))
+        result.append(messages.headingCount(counts.get('headings', 0), onlyIfFound))
+        result.append(messages.formCount(counts.get('forms', 0), onlyIfFound))
+        result.append(messages.tableCount(counts.get('tables', 0), onlyIfFound))
+        result.append(messages.visitedLinkCount(counts.get('visitedLinks', 0), onlyIfFound))
+        result.append(messages.unvisitedLinkCount(counts.get('unvisitedLinks', 0), onlyIfFound))
+        result = filter(lambda x: x, result)
+
+        return ", ".join(result)
 
     def _getCtrlShiftSelectionsStrings(self):
         """Hacky and to-be-obsoleted method."""
diff --git a/src/orca/scripts/web/speech_generator.py b/src/orca/scripts/web/speech_generator.py
index 832193f..8f4b7bd 100644
--- a/src/orca/scripts/web/speech_generator.py
+++ b/src/orca/scripts/web/speech_generator.py
@@ -317,25 +317,12 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
         if not self._script.utilities.inDocumentContent(obj):
             return []
 
-        result = []
-        acss = self.voice(speech_generator.DEFAULT)
-        headings, forms, tables, vlinks, uvlinks, percent = \
-            self._script.utilities.getPageSummary(obj)
-        if headings:
-            result.append(messages.headingCount(headings))
-        if forms:
-            result.append(messages.formCount(forms))
-        if tables:
-            result.append(messages.tableCount(tables))
-        if vlinks:
-            result.append(messages.visitedLinkCount(vlinks))
-        if uvlinks:
-            result.append(messages.unvisitedLinkCount(uvlinks))
-        if percent is not None:
-            result.append(messages.percentRead(percent))
+        string = self._script.utilities.getPageSummary(obj)
+        if not string:
+            return []
 
-        if result:
-            result.extend(acss)
+        result = [string]
+        result.extend(self.voice(speech_generator.SYSTEM))
         return result
 
     def _generateSiteDescription(self, obj, **args):


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]