1<?xml version="1.0" encoding="utf-8"?> 2<page xmlns="http://projectmallard.org/1.0/" type="topic" id="howto_write_a_plugin" xml:lang="ca"> 3 <info> 4 <link type="guide" xref="index#advanced"/> 5 <title type="sort">3. Escriure connectors nous</title> 6 <link type="next" xref="preferences"/> 7 <desc> 8 Extending <app>Accerciser</app> with your desired functionalities 9 </desc> 10 <credit type="author"> 11 <name>Eitan Isaacson</name> 12 <email>eitan@ascender.com</email> 13 </credit> 14 <credit type="author"> 15 <name>Peter Parente</name> 16 <email>pparent@us.ibm.com</email> 17 </credit> 18 <credit type="author"> 19 <name>Aline Bessa</name> 20 <email>alibezz@gmail.com</email> 21 </credit> 22 <license> 23 <p>Creative Commons Compartir Igual 3.0</p> 24 </license> 25 26 <mal:credit xmlns:mal="http://projectmallard.org/1.0/" type="translator copyright"> 27 <mal:name>Joan Duran</mal:name> 28 <mal:email>jodufi@gmail.com</mal:email> 29 <mal:years>2007-2009</mal:years> 30 </mal:credit> 31 </info> 32 <title>Escriure connectors per l'<app>Accerciser</app></title> 33 <p> 34 Extending <app>Accerciser</app> with new plugins became much simpler 35 since <link href="https://wiki.gnome.org/Apps/Accerciser/PluginTutorial">this tutorial</link> 36 was written. Given that it provides a very detailed explanation about the topic, 37 this section consists basically of its original content - only simple editions 38 and updates were done. 39 </p> 40 <p> 41 <app>Accerciser</app> supports three basic types of plugins: 42 </p> 43 <list> 44 <item> 45 <p> 46 Base plugins - These plugins are derived from the Plugin base class. They do not provide 47 a visible interface, but could provide additional functionality to <app>Accerciser</app>. 48 </p> 49 </item> 50 <item> 51 <p> 52 Console plugins - These plugins provide simple console output into a text area in a plugin 53 tab. Not to be confused with the packaged <link xref="ipython_plugin">IPython Console Plugin</link>. 54 </p> 55 </item> 56 <item> 57 <p> 58 Viewport plugins - The majority of <app>Accerciser</app> default plugins. 59 They provide a custom graphical interface in a tab. 60 </p> 61 </item> 62 </list> 63 64 <section id="base_plugin"> 65 <title>Creating a Base Plugin</title> 66 <p> 67 We will create a simplified version of the <link xref="quick_select_plugin">Quick Select Plugin</link>. 68 This plugin will select the last focused accessible when pressing <keyseq><key>ctrl</key><key>alt</key><key>e</key></keyseq>. 69 </p> 70 <p> 71 First off, the import lines we will use are: 72 </p> 73 <code> 74 from accerciser.plugin import Plugin 75 import gtk 76 import pyatspi 77 </code> 78 <p> 79 Next we will derive a new class from the Plugin base class, 80 and assign some mandatory class attributes: 81 </p> 82 <code> 83 class FocusSelect(Plugin): 84 plugin_name = 'Focus Select' 85 plugin_description = 'Allows selecting last focused accessible.' 86 </code> 87 <p> 88 We will now override the init method, in which we will set a global key 89 action for selecting the last focused accessible item, register an event 90 listener for the "focus" event, and set the <cmd>last_focused</cmd> instance 91 variable to <cmd>None</cmd>. 92 </p> 93 <code> 94 def init(self): 95 pyatspi.Registry.registerEventListener(self.accEventFocusChanged, 'focus') 96 self.global_hotkeys = [('Inspect last focused accessible', 97 self.inspectLastFocused, 98 gtk.keysyms.e, 99 gtk.gdk.CONTROL_MASK | gtk.gdk.MOD1_MASK)] 100 self.last_focused = None 101 </code> 102 <p> 103 Notice that the <cmd>global_hotkeys</cmd> instance variable is a list of 104 tuples. Each tuple is a global hotkey action, composed by an action description, 105 a desired method to call, a key symbol of keypress, and a key modifier mask. 106 </p> 107 <p> 108 In the "focus" event callback, we assign the <cmd>last_focused</cmd> instance 109 variable with the accessible item that has just emitted the "focus" event. 110 </p> 111 <code> 112 def accEventFocusChanged(self, event): 113 if not self.isMyApp(event.source): 114 self.last_focused = event.source 115 </code> 116 <p> 117 In the hotkey action callback, we update the application wide node with the 118 last focused accessible item, if we have recorded it: 119 </p> 120 <code> 121 def inspectLastFocused(self): 122 if self.last_focused: 123 self.node.update(self.last_focused) 124 </code> 125 </section> 126 <section id="console_plugin"> 127 <title>Creating a Console Plugin</title> 128 <p> 129 We will create a console plugin to display focus changes emitted by an accessible 130 item with a "push button" role - remember that it is easy to check what is the role 131 of any item with <app>Accerciser</app>; you can verify it in the <link xref="desktop_tree_view">Application Tree View</link>, 132 for example. 133 </p> 134 <p> 135 The needed import lines are: 136 </p> 137 <code> 138 from accerciser.plugin import ConsolePlugin 139 import pyatspi 140 </code> 141 <p> 142 Then we add a class definition, with a plugin name and description: 143 </p> 144 <code> 145 class PushButtonFocus(ConsolePlugin): 146 plugin_name = 'Push Button Focus' 147 plugin_description = 'Print event when pushbutton get\'s focus.' 148 </code> 149 <p> 150 We override the init method adding a register listener: 151 </p> 152 <code> 153 def init(self): 154 pyatspi.Registry.registerEventListener(self.accEventFocusChanged, 'focus') 155 </code> 156 <p> 157 In the callback method, all push button events are printed. 158 </p> 159 <code> 160 def accEventFocusChanged(self, event): 161 if event.source.getRole() == pyatspi.ROLE_PUSH_BUTTON: 162 self.appendText(str(event)+'\n') 163 </code> 164 </section> 165 <section id="viewport_plugin"> 166 <title>Creating a Viewport Plugin</title> 167 <p> 168 We will create a viewport plugin that allows quick testing of the 169 "click" action in accessible items that support the AT-SPI Action interface 170 and have an action named "click". It will be a simple button that, once 171 clicked, does the "click" action in the accessible. 172 </p> 173 <p> 174 First off, some mandatory import lines: 175 </p> 176 <code> 177 import gtk 178 from accerciser.plugin import ViewportPlugin 179 </code> 180 <p> 181 Next, a class definition, with a name and description: 182 </p> 183 <code> 184 class Clicker(ViewportPlugin): 185 plugin_name = 'Clicker' 186 plugin_description = 'Test the "click" action in relevant accessibles.' 187 </code> 188 <p> 189 We override the init method with some UI building, and connecting a callback 190 to a signal for the button. We use the alignment container to allow the button 191 to be centered in the plugin tab, and not monstrously take up the entire plugin 192 space. Notice that the <cmd>plugin_area</cmd> instance variable contains a gtk.Frame 193 that could be populated with all the plugin's widgets. 194 </p> 195 <code> 196 def init(self): 197 alignment = gtk.Alignment(0.5,0.5,0,0) 198 self.click_button = gtk.Button('Click me!') 199 alignment.add(self.click_button) 200 self.plugin_area.add(alignment) 201 202 self.click_button.connect('clicked', self.onClick) 203 204 self.show_all() 205 </code> 206 <p> 207 We also created a convenience method that returns a list of supported actions 208 of the currently selected accessible item - if it does not support the Action 209 interface, it returns an empty list: 210 </p> 211 <code> 212 def accSupportedActions(self): 213 try: 214 ai = self.node.acc.queryAction() 215 except NotImplementedError: 216 action_names = [] 217 else: 218 action_names = [ai.getName(i) for i in xrange(ai.nActions)] 219 return action_names 220 </code> 221 <p> 222 The base plugin class has a method call onAccChanged that is called everytime the 223 target application's selected accessible item changes. We will override it setting 224 the button to be sensitive only when the current accessible item has the "click" action: 225 </p> 226 <code> 227 def onAccChanged(self, acc): 228 has_click = 'click' in self.accSupportedActions() 229 self.click_button.set_sensitive(has_click) 230 </code> 231 <p> 232 The callback method for button "clicked" performs the "click" action on the accessible item. 233 Since this callback could only be called when the button is sensitive, we don't need to worry 234 about checking if the current accessible has the "click" action? 235 </p> 236 <code> 237 def onClick(self, button): 238 ai = self.node.acc.queryAction() 239 action_names = [ai.getName(i) for i in xrange(ai.nActions)] 240 ai.doAction(action_names.index('click')) 241 </code> 242 </section> 243</page> 244