This site has been retired. For up to date information, see handbook.gnome.org or gitlab.gnome.org.


[Home] [TitleIndex] [WordIndex

general perk design

The design of a perk is fairly straightforward. There is some boilerplate code that is generated by LSR when you ask it to generate a perk skeleton. This boilerplate code takes care of any initialization, module importation and finally registration of the tasks in your perk.

The remaining part of the perk is a set of "tasks", which are classes derived from the TaskTools.Task class. Each task is meant to respond to a different type of event. You may override one or more functions in your task classes. These functions are called when the event in question is received and needs to be handled by your task. For example, the CaretChange task has the functions (among others) executeInserted() and executeDeleted(). These are called when a CaretChange event is sent from your application and the event has a subtype of insert or delete. These functions are called with a number of parameters, including the item for which the event is relevant (the so-called POR or Point-of-Regard) and the text that was changed in the item. Other types of events will have other parameters passed as is relevant.

The executeXXX() methods that you override in your task classes have a simple purpose. They must gather information from the application and use it to synthesize some sort of output. A lot of the information necessary comes in the parameters. Going back to the CaretChange event, the text from the textbox that received the event is given as a parameter. In most cases, this is all that is needed. Nevertheless, LSR provides some functions that allow you to query any widget in the application and find out nearly all properties available. See the LSR documentation for the specifics. Once you have gathered the information, there may need to be some processing.

The final step is to have the screenreader say something. LSR provides several built-in functions for saying text. The functions cause the speech engine to use a different voice based on context. If you need the screenreader to say information about an item, use the sayItem() function. If you need the screen reader to give general information, use the sayInfo() function. And so on. These functions are about as easy to use as the print statement.

gedit perk design

Our gedit perk aims to fix the behavior of two dialogs: check spelling and document statistics. Let's look at the check spelling dialog first.

Without our perk, when the check spelling dialogue comes up, several things are read, including the window title, a button, and the first suggested spelling to replace the misspelled word, but what is NOT read is the misspelled word as found in the document. To fix this we need a class that listens for focus change events, in this case executeGained(), and when we see one we test to see whether or not it's the check spelling dialog. If it is, we read the misspelled word.

How do we test to see if we've got the right dialog? We look at several accessibles (in this case a label, button and text field) and check them against the known paths for the accessibles in the check spelling dialog. We found these earlier with Accerciser. Once we have a match, we try to spell out the word, but we'll find that the spelling gets clobbered by LSR reading other things based events that come in after the focus change. To suppress this we played around returning false and blockNTasks, finally settling on the following:

   1 class HandleGeditViewChange(Task.ViewTask):
   2   '''
   3   This class monitors for whether the check spelling dialogue is up. If it
   4   comes up, it reads the misspelled word. 
   5   '''
   6   def executeGained(self, por, **kwargs):
   7     por = self.getRootAcc()
   8     #for check spelling dialog
   9     checkLabel = self.getAccFromPath(por, 0,0,1)
  10     checkButton = self.getAccFromPath(por, 0,0,0,1)
  11     checkTextField = self.getAccFromPath(por, 0,0,0,0)
  12     misspelled_word_por = self.getAccFromPath(por, 0,0,2)
  13     misspelled_word_text = self.getAccName(misspelled_word_por)
  14     if self.hasAccRole('label', checkLabel):
  15       if self.hasAccRole('push button', checkButton): 
  16         if self.hasAccRole('text', checkTextField):
  17           self.inhibitMayStop()
  18           self.sayItem(text='misspelled word')
  19           for i in misspelled_word_text:
  20             self.sayItem(text=i)
  21           #block further output that was clobbering the misspelled word
  22           self.blockNTasks(1,Task.SelectorTask)
  23           return False

There's more to the gedit perk in terms of that dialogue, but it all starts there. Next up we look at the document statistics dialog.

For the document statistics dialog the dialog comes up, reads the title of the window, the title and then a button, but skips all the content (the statistics). On top of that, the text itself is in a bunch of accessibles that were ordered oddly, so most of our work was assembling the accessibles into a list that could be read in a sensible order. You can get a sense of this from the last snippet of that class:

   1     ItemList.append(self.getAccFromPath(por, 0,0,1,1,10))
   2     ItemList.append(self.getAccFromPath(por, 0,0,1,1,9))
   3     ItemList.append(self.getAccFromPath(por, 0,0,1,2,5))
   4     txtThree = self.getItemText(lblThree)
   5     if self.hasAccRole('label', lblOne):
   6       if self.hasAccRole('label', lblTwo):
   7         if self.hasAccRole('label', lblThree):
   8           for i in ItemList:
   9             self.inhibitMayStop()
  10             self.sayItem(i)
  11           self.blockNTasks(1, Task.SelectorTask)
  12           return False

If you're interested in more of the details of the check spelling dialog (eg. getting the context line for the misspelled word), have a look at the full source available here


2024-10-23 10:59