Writing Applets With AWNLib

From AWN Wiki

Jump to: navigation, search

AWNLib is library that wraps around awn.AppletSimple. It provides methods to easily manage the title, the icon (including using Cairo context as the icon or themed icons), dialogs, settings, timers, and more. The following paragraphs explain the various parts of writing an applet with AWNLib. Applets like cairo-clock, volume-control, show-desktop and quit use AWNLib.

Contents

[edit] License

Your applet must provide a license. The following is an example:

# Copyright (C) 2008  YOUR_NAME <YOUR_EMAIL_ADDRESS>
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 2 of the License.
# 
# This program 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 General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

[edit] Imports

Next come some imports:

# Here some imports like re, os, sys, etc.

import pygtk
pygtk.require('2.0')
import gtk
from awn.extras import AWNLib

[edit] Definitions

Next come some definitions. The definitions will be used later to construct the AboutDialog (accessible when you right-click on the applet's icon). Note that the value of applet_theme_logo is in this case the name of an icon that should be provided by one of the icon themes on your system. If you want to use your own icon, use a file path.

applet_name = "Hello World"
applet_version = "0.2.8"
applet_description = "An applet that says hello to the world"

# Themed logo of the applet, used as the applet's icon and shown in the GTK About dialog
applet_theme_logo = "desktop"

# Or provide the path to your own logo:
applet_logo = os.path.join(os.path.dirname(__file__), "your-applets-logo.svg")

[edit] Initializing your applet

When you use AWNLib, you basically define a class that will set an icon, a title, and connect some signals to some callback methods. Then, you start the applet via AWNLib:

class HelloWorldApplet:
    """ An applet that says hello to the world """

    def __init__(self, applet):
        self.applet = applet

        applet.title.set("Hello World!")


if __name__ == "__main__":
    applet = AWNLib.initiate({"name": applet_name, "short": "hello-world",
        "version": applet_version,
        "description": applet_description,
        "theme": applet_theme_logo,
        "author": "YOUR_NAME",
        "copyright-year": 2008,
        "authors": ["YOUR_NAME <YOUR_EMAIL_ADDRESS>"]})
    HelloWorldApplet(applet)
    AWNLib.start(applet)

Import to note is that the applet will automatically show and hide the title when you hover over the icon, so you don't have to set up signal handlers. AWNLib will also automatically create a context menu that will show an image menu item to show the AboutDialog. The keys "name", "copyright-year", and "author" are required if you want the AboutDialog. It is recommended for consistency to provide enough information so that all applets have an AboutDialog.

Another import thing is that by providing the "theme" key, AWNLib will automatically use this themed icon as the applet's icon and as the logo in the AboutDialog. When you use a themed icon, a user can drag-and-drop a different icon onto the applet's current icon to change it.

[edit] Your own icon instead of a themed icon

If you want to use your own icon, then use the "logo" key instead of "theme". In this case, the logo will be used in the AboutDialog, but NOT as the applet's icon. See the next example to see how to set the applet's icon in __init__:

    def __init__(self, applet):
        self.applet = applet

        # Set title here

        height = applet.get_height()
        applet.icon.set(gdk.pixbuf_new_from_file_at_size(icon, height, height), True)

        applet.connect("height-changed", self.height_changed_cb)

    def height_changed_cb(self, widget, event):
        """ Updates the applet's icon to reflect the new height """

        # Re-set the icon here

The example below also connected a callback to the "height-changed" signal. If you don't do this, AWN will scale the icon, but because it doesn't know where the icon came from (it only has the Pixbuf you provided), the icon will look very ugly when you increase the height of the AWN panel.

[edit] Using a Cairo context as your applet's icon

Using a Cairo context as your applet's icon is fairly simple: just pass the context to icon.set():

height = self.applet.get_height()

surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, height, height)
context = cairo.Context(surface)

# Scale the dimensions of the SVG image to the size of the applet
svg_width, svg_height = map(float, self.clock_face.get_dimension_data()[:2])
context.scale(height / svg_width, height / svg_height)

# Perform some Cairo operations here

self.applet.icon.set(context) # AWNLib will automatically delete old contexts

[edit] Settings and your "Hello World Preferences" window

AWNLib can also create Preferences dialog for you. At the moment of writing (2008-07-21) you have to insert an image menu item into the context menu yourself, but that's not too difficult. In __init__, add the following call:

self.setup_context_menu()

Then add the following code to your HelloWorldApplet class:

    def setup_context_menu(self):
        pref_dialog = self.applet.dialog.new("preferences") # AWNLib will automatically add an item 'Preferences' to the context menu

        self.setup_dialog_settings(pref_dialog.vbox)

The preferences dialog will automatically use your own icon or the themed icon (depends on which one you provided) as the icon of the window. The title of will be "APPLET_NAME Preferences". In this case "Hello World Preferences". It will also add a gtk.VBox and a "Close" button. The nicest thing is that the user of your applet can adjust settings and will immediately see the results of those changes. Try NOT to use a "Save" and a "Cancel" button.

If you want to add additional items, you could add them between the separator and an extra separator that is positioned above "Preferences" (or "About" if you don't have "Preferences"). You can add more items like this:

        menu = self.applet.dialog.menu

        self.mute_item = gtk.CheckMenuItem("Mu_te")
        self.mute_item.connect("toggled", self.backend.mute_toggled_cb)
        menu.insert(self.mute_item, 3)

        # Separate the extra items from "Preferences" and "About" ("About" is added if enough meta data is available)
        menu.insert(gtk.SeparatorMenuItem(), 4)

You can use self.applet.settings as a standard Python dictionary to store and read values. If you have many settings, see a little below to see how to read those easily.

    def setup_dialog_settings(self, vbox):
        """ Loads the settings """

        if "hello" not in self.applet.settings:
            self.applet.settings["hello"] = "Hello!"
        self.hello_setting = self.applet.settings["hello"]

        # Create some GTK+ widgets and use self.hello_setting somehow

        # Add a callback that will store the new value like this: self.applet.settings["hello"] = self.hello_setting = ... new value

If you have multiple settings, you can give AWNLib a dictionary that contains the default values. Overridden values will be replaced in the dictionary. Defaults (if not overridden) will be pushed to the settings backend by default.

self.settings = {
    "hello": "Hello!",
    "world": "World!"
}
self.applet.settings.load(self.settings)

# Retrieve values like this: self.settings["hello"] or self.applet.settings["hello"]

AWNLib will automatically update the loaded dictionary when you set values via self.applet.settings[<KEY>] = <VALUE>. Because reading a setting from self.applet.settings will cause a lookup to the settings backend, you should use (in this case) self.settings when possible.

[edit] Settings per applet instance

By default settings are shared among all instances of your applet. If you want your settings to be per instance, so that you can add multiple instances of your applet to the AWN panel with different settings, then add the following list as a new parameter after the meta dictionary (See: Initializing your applet):

["settings-per-instance"]

[edit] Main dialog

Creating a dialog that opens and hides when you click on the applet's icon and hides when you upon focus loss, is very easy:

dialog = self.applet.dialog.new("main")

# Add widgets to the dialog

If you want to do something extra when a user clicks on the icon, just connect a button-press-event in the __init__ method of your class:

    # More __init__ stuff here

    applet.connect("button-press-event", self.button_press_event_cb)

def button_press_event_cb(self, widget, event):
    if event.button == 1:
        # Do something when a user lef-clicks on the icon

You can do something with the title when a user hovers over the icon by connecting an enter-notify-event:

applet.connect("enter-notify-event", self.update_something_cb)

To test in the update_something_cb method if the title is really visible, do:

if self.applet.title.is_visible():
    # Update the title

[edit] Timers

Timers are functions that run periodically. AWNLib provides methods to start or stop such a function, or to change the interval between two successive calls. The next example shows how to call the self.update_value_cb function every 2 seconds. By default your function is started, which means that the first time it will run is at the end of the first interval. If you don't want the callback to be invoked automatically until you call start() on it, then add False as the third parameter to register(), like in the next example:

# Call self.update_value_cb function every 2 seconds. This number can also be a float
self.update_callback = self.applet.timing.register(self.update_value_cb, 2, False) # Remove False if you want it to start automatically

print self.update_callback.is_started() # Should print False

# Perform some operations here

self.update_callback.start() # Not necessary if third parameter of register() is not used or is True

print self.update_callback.is_started() # Should now print True

Use an integer instead of a float when possible. AWNLib will internally use a different GObject method which may save a little power.

If you want to change the interval (while it has been started or not), use change_interval():

# If the callback was started, then it will run the first time 3.5 seconds after this method
self.update_callback.change_interval(3.5)

Personal tools