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