>From f4dd8a742e9b74c8f0be1e1de7ae56ecee047ad7 Mon Sep 17 00:00:00 2001 From: Marcus Habermehl Date: Mon, 31 Jan 2011 23:26:30 +0100 Subject: [PATCH] Patch for testing purpose only #2 - this patch adds an object navigator as pop-up menu --- po/POTFILES.in | 1 + src/orca/Makefile.am | 1 + src/orca/object_navigator.py | 298 +++++++++++++++++++++++++++++ src/orca/scripts/toolkits/Gecko/script.py | 50 +++++ 4 files changed, 350 insertions(+), 0 deletions(-) create mode 100644 src/orca/object_navigator.py diff --git a/po/POTFILES.in b/po/POTFILES.in index a96dd12..8986fd5 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -20,6 +20,7 @@ src/orca/keynames.py src/orca/liveregions.py src/orca/mag.py src/orca/notification_messages.py +src/orca/object_navigator.py [type: gettext/glade]src/orca/orca-advanced-magnification.ui src/orca/orca_console_prefs.py [type: gettext/glade]src/orca/orca-find.ui diff --git a/src/orca/Makefile.am b/src/orca/Makefile.am index 891ecac..8df5ba9 100644 --- a/src/orca/Makefile.am +++ b/src/orca/Makefile.am @@ -41,6 +41,7 @@ orca_python_PYTHON = \ mag.py \ mouse_review.py \ notification_messages.py \ + object_navigator.py \ orca.py \ orca_console_prefs.py \ orca_gtkbuilder.py \ diff --git a/src/orca/object_navigator.py b/src/orca/object_navigator.py new file mode 100644 index 0000000..d1b77c1 --- /dev/null +++ b/src/orca/object_navigator.py @@ -0,0 +1,298 @@ +# Orca +# +# Copyright 2010 Marcus Habermehl +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 2 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 +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library 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. + +"""Displays a list of objects for structural navigation.""" + +__id__ = "$Id$" +__version__ = "$Revision$" +__date__ = "$Date$" +__copyright__ = "Copyright (c) 2011 Marcus Habermehl" +__license__ = "LGPL" + +import glib +import gtk +import pyatspi + +import orca + +from orca_i18n import _ + +OBJECT_TYPES = { + "anchor": _("_Anchors"), + "blockquote": _("Block_quotes"), + "formField": _("_Form fields"), + "heading": _("_Headings"), + "landmark": _("Land_marks"), + "link": _("_Links"), + "list": _("_Lists"), + "listItem": _("List _items"), + "liveRegion": _("Live re_gions"), + "paragraph": _("_Paragraphs"), + "table": _("_Tables") +} + +class ObjectNavigator(gtk.Menu): + def __init__(self, script, enabledObjects): + """Initialize the Object Navigator menu. + """ + + gtk.Menu.__init__(self) + + self._enabledObjects = [] + self._formIndex = 1 + self._menuBuilded = False + + self._script = script + self._speechGenerator = self._script.speechGenerator + self._utilities = self._script.utilities + self._structuralNavigation = self._script.structuralNavigation + + self._convertEnabledObjects(enabledObjects) + + def _buildMenu(self, document): + """Build the menu. + """ + + self._menuBuilded = False + + orca.speech.speak( + # Translators: this message is spoken if the object navigator, a + # list of structural navigation objects, is created. This should + # be the reason after a document is loaded or modified. + # + _("The Object Navigator is created."), + orca.settings.voices.get(orca.settings.SYSTEM_VOICE)) + + # Remove all children before rebuilding the menu. + for i in self.get_children(): + while gtk.events_pending(): + gtk.main_iteration() + + self.remove(i) + + while gtk.events_pending(): + gtk.main_iteration() + + objects = self._getObjects(document) + + for (objType, predicate, present) in self._enabledObjects: + if objType in objects and objects[objType]: + objectMenu = gtk.Menu() + objectMenuItem = gtk.MenuItem(objType) + objectMenuItem.set_submenu(objectMenu) + self.append(objectMenuItem) + + if objType == OBJECT_TYPES["formField"]: + for (form, fields) in objects[objType]: + formMenu = gtk.Menu() + + while gtk.events_pending(): + gtk.main_iteration() + + formMenuItem = gtk.MenuItem( + "_%s" % self._generateLabel(form)) + formMenuItem.set_submenu(formMenu) + objectMenu.append(formMenuItem) + + for obj in fields: + while gtk.events_pending(): + gtk.main_iteration() + objectItem = gtk.MenuItem( + "_%s" % self._generateLabel(obj)) + objectItem.connect( + "activate", self._onActivation, obj, present) + formMenu.append(objectItem) + else: + for obj in objects[objType]: + while gtk.events_pending(): + gtk.main_iteration() + objectItem = gtk.MenuItem( + "_%s" % self._generateLabel(obj)) + objectItem.connect( + "activate", self._onActivation, obj, present) + objectMenu.append(objectItem) + + self._menuBuilded = True + + orca.speech.speak( + # Translators: this message is spoken if the creation of the object + # navigator, a list of structural navigation objects, is done. + # + _("The Object Navigator is ready for use."), + orca.settings.voices.get(orca.settings.SYSTEM_VOICE)) + + return False + + def buildMenu(self, document): + """Build the menu if document is showing. + """ + + state = document.getState() + + if not state.contains(pyatspi.STATE_BUSY) and \ + state.contains(pyatspi.STATE_FOCUSED): + glib.idle_add(self._buildMenu, document) + else: + print document, state.getStates() + + def _convertEnabledObjects(self, enabledObjects): + """Convert the enabledObjects dict to a list to sort the + enabledObjects after i18n. + """ + + self._enabledObjects = [] + + for key in enabledObjects: + if key in OBJECT_TYPES: + self._enabledObjects.append( + [OBJECT_TYPES[key], enabledObjects[key].predicate, + enabledObjects[key].present]) + + if "unvisitedLink" in enabledObjects or "visitedLink" in enabledObjects: + self._enabledObjects.append( + [OBJECT_TYPES["link"], self._script.utilities.isLink, + self._structuralNavigation._visitedLinkPresentation]) + + self._enabledObjects.sort(self._sortEnabledObjects) + + def _generateLabel(self, obj): + """Create a label for a menu item. + """ + + label = [] + + # Translators: this is the name for unlabeled/unnamed objects in the + # object navigator menu. + # + unnamed = _("Unnamed") + + for i in self._speechGenerator.generate(obj, alreadyFocused = False, + formatType = "unfocused", + forceTutorial = True)[:-2]: + if i and type(i) in [str, unicode]: + label.append(i) + + if self._structuralNavigation._formFieldPredicate(obj): + if obj.getRole() != pyatspi.ROLE_PUSH_BUTTON: + if not self._speechGenerator._generateLabel(obj): + guess = self._script.guessTheLabel(obj, False) + + label.insert(0, guess or unnamed) + + if not label: + if self._isForm(obj): + # Translators: this is the label for unnamed forms in the + # object navigator menu. + # + label.append(_("Form %d") % self._formIndex) + self._formIndex += 1 + else: + label.append(unnamed) + + # Don't overload the labels + if len(label[0]) > 50: + label[0] = label[0][:50] + "..." + + return " ".join(label) + + def _getObjects(self, document): + """Get the structural objects that are contained in document. + """ + + objects = {} + + for (objType, predicate, present) in self._enabledObjects: + objects[objType] = [] + + for obj in pyatspi.findAllDescendants(document, bool): + for (objType, predicate, present) in self._enabledObjects: + if objType != OBJECT_TYPES["formField"] and predicate(obj): + objects[objType].append(obj) + + # Form fields are shown in different submenus. + for form in pyatspi.findAllDescendants(document, self._isForm): + formFields = pyatspi.findAllDescendants(form, self._isFormField) + objects[OBJECT_TYPES["formField"]].append([form, formFields]) + + return objects + + def _isForm(self, obj): + """Is obj a form or not. + """ + + return obj.getRole() == pyatspi.ROLE_FORM + + def _isFormField(self, obj): + """Is obj a form field or not. + """ + + return self._structuralNavigation._formFieldPredicate(obj) + + def _isLink(self, obj): + """Is obj a link or not. + """ + + return self._structuralNavigation._unvisitedLinkPredicate(obj) \ + or self._structuralNavigation._visitedLinkPredicate(obj) + + def _onActivation(self, menuItem, obj, present): + """This will be executed if an item is activated from the menu. + """ + + # Be sure the menu is destroyed + while gtk.events_pending(): + gtk.main_iteration() + + present(obj) + + def showMenu(self, timestamp): + """Show the menu and select the first selectable item. + """ + + while gtk.events_pending(): + gtk.main_iteration() + + if self._menuBuilded: + self.show_all() + self.select_first(True) + self.popup(None, None, None, 0, timestamp) + else: + orca.speech.speak( + # Translators: this message is spoken if the object navigator, a + # list of structural navigation objects, is requested but still + # not yet created. + # + _("The Object Navigator is under construction."), + orca.settings.voices.get(orca.settings.SYSTEM_VOICE)) + + def _sortEnabledObjects(self, a, b): + """Sort the list of enabled objects basing on the label, but + without the mnemonic indicator. + """ + + nameA = a[0].replace("_", "") + nameB = b[0].replace("_", "") + + if nameA < nameB: + return -1 + elif nameA == nameB: + return 0 + elif nameA > nameB: + return 1 + diff --git a/src/orca/scripts/toolkits/Gecko/script.py b/src/orca/scripts/toolkits/Gecko/script.py index 17f3548..49043a4 100644 --- a/src/orca/scripts/toolkits/Gecko/script.py +++ b/src/orca/scripts/toolkits/Gecko/script.py @@ -79,6 +79,8 @@ from orca.orca_i18n import _ from orca.speech_generator import Pause from orca.acss import ACSS +import orca.object_navigator as object_navigator + _settingsManager = getattr(orca, '_settingsManager') ######################################################################## @@ -283,6 +285,8 @@ class Script(default.Script): self.preMouseOverContext = [None, -1] self.inMouseOverObject = False + self.objectNavigator = None + def activate(self): """Called when this script is activated.""" self.savedEnabledBrailledTextAttributes = \ @@ -299,6 +303,12 @@ class Script(default.Script): _settingsManager.getSetting('allTextAttributes') _settingsManager.setSetting('allTextAttributes', self.allTextAttributes) + if not self.objectNavigator: + self.objectNavigator = object_navigator.ObjectNavigator( + self, self.structuralNavigation.enabledObjects) + self.objectNavigator.buildMenu( + self.structuralNavigation._getDocument()) + default.Script.activate(self) def deactivate(self): @@ -384,6 +394,15 @@ class Script(default.Script): self.inputEventHandlers.update(\ self.structuralNavigation.inputEventHandlers) + self.inputEventHandlers["showObjectNavigator"] = \ + input_event.InputEventHandler( + self.showObjectNavigator, + # Translators: the Object Navigator is a pop-up menu that + # presents the structural navigation objects within a + # document as menu with submenus. + # + _("Show the Object Navigator.")) + # Debug only. # self.inputEventHandlers["dumpContentsHandler"] = \ @@ -657,8 +676,18 @@ class Script(default.Script): for keyBinding in bindings.keyBindings: keyBindings.add(keyBinding) + keyBindings.add( + keybindings.KeyBinding( + "Menu", + settings.defaultModifierMask, + settings.ORCA_MODIFIER_MASK, + self.inputEventHandlers["showObjectNavigator"])) + return keyBindings + def showObjectNavigator(self, script, event): + self.objectNavigator.showMenu(event.timestamp) + def getAppPreferencesGUI(self): """Return a GtkVBox contain the application unique configuration GUI items for the current application. @@ -1450,6 +1479,11 @@ class Script(default.Script): such example would be the programmatic insertion of a tooltip or alert dialog.""" + if not self._loadingDocumentContent and self.inDocumentContent(obj) \ + and not event.andy_data: + self.objectNavigator.buildMenu( + self.structuralNavigation._getDocument()) + # If children are being added or removed, trash our saved guessed # labels to be on the safe side. # @@ -1507,6 +1541,10 @@ class Script(default.Script): def onDocumentLoadComplete(self, event): """Called when a web page load is completed.""" + + self.objectNavigator.buildMenu( + self.structuralNavigation._getDocument()) + # We care about the main document and we'll ignore document # events from HTML iframes. # @@ -1518,6 +1556,10 @@ class Script(default.Script): def onDocumentLoadStopped(self, event): """Called when a web page load is interrupted.""" + + self.objectNavigator.buildMenu( + self.structuralNavigation._getDocument()) + # We care about the main document and we'll ignore document # events from HTML iframes. # @@ -1531,6 +1573,11 @@ class Script(default.Script): Arguments: - event: the Event """ + + if not self._loadingDocumentContent and event.source_name != event.any_data: + self.objectNavigator.buildMenu( + self.structuralNavigation._getDocument()) + if event.source.getRole() == pyatspi.ROLE_FRAME: self.liveMngr.flushMessages() @@ -1862,6 +1909,9 @@ class Script(default.Script): if not obj: return + self.objectNavigator.buildMenu( + self.structuralNavigation._getDocument()) + # When a document tab is tabbed to, we will just present the # line where the caret currently is. # -- 1.7.1