<?xml version="1.0" encoding="utf-8"?>
<page xmlns="http://projectmallard.org/1.0/" type="topic" id="howto_write_a_plugin" xml:lang="de">
  <info>
    <link type="guide" xref="index#advanced"/>
    <title type="sort">3. Neue Plugins schreiben</title>
    <link type="next" xref="preferences_setting"/>
    <desc><app>Accerciser</app> um gewünschte Funktionalitäten erweitern</desc>
    <credit type="author">
      <name>Eitan Isaacson</name>
      <email>eitan@ascender.com</email>
    </credit>
    <credit type="author">
      <name>Peter Parente</name>
      <email>pparent@us.ibm.com</email>
    </credit>
    <credit type="author">
      <name>Aline Bessa</name>
      <email>alibezz@gmail.com</email>
    </credit>
    <license>
      <p>Creative Commons Share Alike 3.0</p>
    </license>
  
    <mal:credit xmlns:mal="http://projectmallard.org/1.0/" type="translator copyright">
      <mal:name>Mario Blättermann</mal:name>
      <mal:email>mariobl@freenet.de</mal:email>
      <mal:years>2009, 2011</mal:years>
    </mal:credit>
  
    <mal:credit xmlns:mal="http://projectmallard.org/1.0/" type="translator copyright">
      <mal:name>Max Orxy</mal:name>
      <mal:email>maxorxy@googlemail.com</mal:email>
      <mal:years>2012, 2013</mal:years>
    </mal:credit>
  
    <mal:credit xmlns:mal="http://projectmallard.org/1.0/" type="translator copyright">
      <mal:name>Christian Kirbach</mal:name>
      <mal:email>christian.kirbach@gmail.com</mal:email>
      <mal:years>2012, 2013</mal:years>
    </mal:credit>
  </info>
  <title>Plugins für <app>Accerciser</app> schreiben</title>
  <p>
    Extending <app>Accerciser</app> with new plugins became much simpler
    since <link href="https://wiki.gnome.org/Apps/Accerciser/PluginTutorial">this tutorial</link>
    was written. Given that it provides a very detailed explanation about the topic,
    this section consists basically of its original content - only simple editions
    and updates were done.
  </p>
  <p><app>Accerciser</app> unterstützt drei grundlegende Plugin-Typen:</p>
  <list>
    <item>
      <p>Basis-Plugins - Diese Plugins stammen von der Basis-Plugin-Klasse. Sie stellen keine sichtbare Oberfläche zur Verfügung, aber können <app>Accerciser</app> zusätzliche Funktionalität bieten.</p>
    </item>
    <item>
      <p>Konsolen-Plugins - Diese Plugins stellen eine einfache Konsolenausgabe in ein Textfeld in einem Plugin-Reiter zur Verfügung. Verwechseln Sie es nicht mit dem Paket <link xref="ipython_plugin">IPython Konsolen-Plugin</link>.</p>
    </item>
    <item>
      <p>Ansichtsplugins - Das ist der Mehrheit der Standardplugins von <app>Accerciser</app>. Sie stellen eine spezifische grafische Oberfläche in einem Reiter zur Verfügung.</p>
    </item>
  </list>

  <section id="base_plugin">
    <title>Erstellen eines Basis-Plugins</title>
    <p>Wir werden noch eine einfachere Version des <link xref="quick_select_plugin">Schnellauswahl-Plugins</link> erstellen. Mit diesem Plugin können Sie das zuletzt fokussierte Accessible auswählen, indem Sie <key>Strg</key>+<key>alt</key>+<key>e</key> drücken.</p>
    <p>Zunächst verwenden wir folgende Import-Zeilen:</p>
    <code>
      from accerciser.plugin import Plugin
      import gtk
      import pyatspi
    </code>
    <p>Anschließend leiten wir eine neue Klasse aus der Basis-Plugin-Klasse ab und ordnen einige obligatorische Klassenattribute zu:</p>
    <code>
      class FocusSelect(Plugin):
        plugin_name = 'Focus Select'
        plugin_description = 'Allows selecting last focused accessible.'
    </code>
    <p>Wir überschreiben nun die Anfangsmethode, und setzen ein globales Tastenkürzel, um das zuletzt angewählte barrierefreie Objekt auszuwählen, wir setzen einen Event-Listener für das »focus«-Ereignis und setzen die Instanzvariable <cmd>last_focused</cmd> auf <cmd>None</cmd>.</p>
    <code>
      def init(self):
        pyatspi.Registry.registerEventListener(self.accEventFocusChanged, 'focus')
        self.global_hotkeys = [('Inspect last focused accessible',
                                self.inspectLastFocused,
                                gtk.keysyms.e,
                                gtk.gdk.CONTROL_MASK | gtk.gdk.MOD1_MASK)]
        self.last_focused = None
    </code>
    <p>Hierbei werden Sie feststellen, dass die Instanzvariable <cmd>global_hotkeys</cmd> eine Liste von Tupeln ist. Jedes der Tupel ist ein Tastenkürzel, was aus einer Beschreibung der Aktion, der gewünschten Methode, die aufgerufen werden soll, einem Tastenkürzel, und einer Maske für die Änderung des Tastenkürzels besteht.</p>
    <p>In der Callback-Funktion des »focus«-Ereignisses setzen wir die Variable <cmd>last_focused</cmd> auf die barrierefreie Anwendung, welche gerade das »focus«-Ereignis ausgelöst hat.</p>
    <code>
      def accEventFocusChanged(self, event):
        if not self.isMyApp(event.source):
          self.last_focused = event.source
    </code>
    <p>
       In the hotkey action callback, we update the application wide node with the
       last focused accessible item, if we have recorded it:
    </p>
    <code>
      def inspectLastFocused(self):
        if self.last_focused:
          self.node.update(self.last_focused)
    </code>
  </section>
  <section id="console_plugin">
    <title>Erstellen eines Konsolen-Plugins</title>
    <p>Wir erstellen nun ein Konsolen-Plugin, um die Veränderungen des Fokus anzuzeigen, welche durch ein barrierefreies Objekt durch den »push button« ausgelöst werden - vergessen Sie dabei nicht, dass es mithilfe von <app>Accerciser</app> einfach ist festzustellen, welche die Funktion eines Objekts ist; das können Sie z.B. auch in der <link xref="desktop_tree_view">Anwendungsbaumansicht</link> überprüfen.</p>
    <p>Folgende Import-Zeilen werden benötigt:</p>
    <code>
      from accerciser.plugin import ConsolePlugin
      import pyatspi
    </code>
    <p>Dann definieren wir eine Klasse, mit dem Namen des Plugins und einer Beschreibung:</p> 
    <code>
      class PushButtonFocus(ConsolePlugin):
        plugin_name = 'Push Button Focus'
        plugin_description = 'Print event when pushbutton get\'s focus.'
    </code>
    <p>
      We override the init method adding a register listener: 
    </p>
    <code>
       def init(self):
         pyatspi.Registry.registerEventListener(self.accEventFocusChanged, 'focus')
    </code>
    <p>
      In the callback method, all push button events are printed. 
    </p>
    <code>
      def accEventFocusChanged(self, event):
        if event.source.getRole() == pyatspi.ROLE_PUSH_BUTTON:
          self.appendText(str(event)+'\n')
    </code>
  </section>
  <section id="viewport_plugin">
    <title>Erstellen eines Ansicht-Plugins</title>
    <p>
      We will create a viewport plugin that allows quick testing of the
      "click" action in accessible items that support the AT-SPI Action interface
      and have an action named "click". It will be a simple button that, once
      clicked, does the "click" action in the accessible. 
    </p>
    <p>
      First off, some mandatory import lines:
    </p>
    <code>
      import gtk
      from accerciser.plugin import ViewportPlugin
    </code>
    <p>Danach folgt eine Klassendefinition mit Name und Beschreibung:</p>
    <code>
      class Clicker(ViewportPlugin):
        plugin_name = 'Clicker'
        plugin_description = 'Test the "click" action in relevant accessibles.'
    </code>
    <p>
      We override the init method with some UI building, and connecting a callback
      to a signal for the button. We use the alignment container to allow the button
      to be centered in the plugin tab, and not monstrously take up the entire plugin
      space. Notice that the <cmd>plugin_area</cmd> instance variable contains a gtk.Frame
      that could be populated with all the plugin's widgets. 
    </p>
    <code>
       def init(self):
         alignment = gtk.Alignment(0.5,0.5,0,0)
         self.click_button = gtk.Button('Click me!')
         alignment.add(self.click_button)
         self.plugin_area.add(alignment)

         self.click_button.connect('clicked', self.onClick)

         self.show_all()
    </code>
    <p>
       We also created a convenience method that returns a list of supported actions
       of the currently selected accessible item - if it does not support the Action
       interface, it returns an empty list: 
    </p> 
    <code>
       def accSupportedActions(self):
       try:
         ai = self.node.acc.queryAction()
       except NotImplementedError:
         action_names = []
       else:
         action_names = [ai.getName(i) for i in xrange(ai.nActions)]
       return action_names
    </code>
    <p>
      The base plugin class has a method call onAccChanged that is called everytime the
      target application's selected accessible item changes. We will override it setting
      the button to be sensitive only when the current accessible item has the "click" action: 
    </p>
    <code>
       def onAccChanged(self, acc):
         has_click = 'click' in self.accSupportedActions()
         self.click_button.set_sensitive(has_click)
    </code>
    <p>
      The callback method for button "clicked" performs the "click" action on the accessible item. 
      Since this callback could only be called when the button is sensitive, we don't need to worry 
      about checking if the current accessible has the "click" action?
    </p>
    <code>
      def onClick(self, button):
        ai = self.node.acc.queryAction()
        action_names = [ai.getName(i) for i in xrange(ai.nActions)]
        ai.doAction(action_names.index('click'))
    </code>
   </section>
</page>
