This series of articles explores how to use Python to create scripts for GNOME desktops, screenlet frameworks, and Nautilus to provide a highly productive environment. The drag-and-drop feature is enabled for scripts on the desktop to quickly access frequently used information and services. This section describes how to use the screenlet toolkit to build desktop applications.
Developing applications for Linux desktops usually requires some Graphical User interfaces (GUI) framework as the basis for building. Options include GTK + for GNOME Desktop and Qt for K Desktop environments (KDE. These two platforms provide everything developers need to build GUI applications, including libraries and layout tools to create windows that users can see. This article shows you how to build a desktop productivity application based on the screenlet widget kit.
Some existing applications will be categorized into desktop productivity categories, including GNOME Do and Tomboy. These applications usually allow users to interact directly with them from the desktop by dragging and dropping through a specific key combination or from another application, such as Mozilla Firefox. Tomboy is used as a desktop note tool and supports text drag and drop from other windows.
Getting started with Screenlet
You need to install some programs to start developing screenlet. First, install the screenlets package using the Ubuntu Software Center or command line. In the Ubuntu Software Center, type screenlets in the Search box. You should see two options: the main package and the independent installation package in the document.
You can use Python to program screenlet in Python and Ubuntu. The basic installation of Ubuntu 10.04 includes Python 2.6, because many utilities depend on it. You may need other libraries, depending on your application requirements. For this article, I installed and tested everything on Ubuntu 10.04. Next, download the screenlet source code from the screenlets.org site. The Test screenlet is located in the src/share/screenlets/Test folder and uses Cairo and GTK, which also need to be installed. The entire source code of the test program is located in the TestScreenlet. py file. Open this file in your favorite editor to view the screenlet infrastructure.
Python is highly object-oriented, SO class keywords are used to define objects. In this example, the class is named TestScreenlet and has some defined methods. In TestScreenlet. py, pay attention to the 42nd lines in the following code:
def __init__(self, **keyword_args):
Python uses the underscore (_) to identify system functions through predefined behaviors. In this example, the __init _ function has all the intent and purpose for the class constructor, and contains any number of initialization steps to be executed when creating a new instance of the object. By convention, the first parameter of each class method is a reference to the current instance of the class and is named as self. With this behavior, you can easily use self to reference the methods and attributes of its instance:
self.theme_name = "default"
The Screenlet Framework defines naming conventions and standards, which are outlined on the developer page at screenlets.org. There is also a link to the source code of the screenlet package and the Application Programming Interface (API) documentation. The Code also gives you an insight into what each function can do by calling parameters and what is returned.
Write simple screenlet
The basic components of Screenlet include the icon file, source code file, and topic folder. The topic folder contains additional folders for different themes. You will find the sample template and the files and folders required for getting started on screenlets.org.
For the first example, use the provided template to create a basic "Hello World" application. The code for this basic application is shown in Listing 1.
Listing 1. Python code for Hello World screenlet
#!/usr/bin/env pythonimport screenletsclass HelloWorldScreenlet(screenlets.Screenlet):__name__ = 'HelloWorld'__version__ = '0.1'__author__ = 'John Doe'__desc__ = 'Simple Hello World Screenlet'def __init__(self, **kwargs):# Customize the width and height.screenlets.Screenlet.__init__(self, width=180, height=50, **kwargs)def on_draw(self, ctx):# Change the color to white and fill the screenlet.ctx.set_source_rgb(255, 255, 255)self.draw_rectangle(ctx, 0, 0, self.width, self.height)# Change the color to black and write the message.ctx.set_source_rgb(0, 0, 0)text = 'Hello World!'self.draw_text(ctx, text, 10, 10, "Sans 9" , 20, self.width)if __name__ == "__main__":import screenlets.sessionscreenlets.session.create_session(HelloWorldScreenlet)
Each application must import the screenlet framework and create a new session. There are also some other minimum requirements, including any initialization steps and Basic Drawing functions, to present widgets on the screen. The TestScreenlet. py example has the _ init _ method used to initialize the object. In this example, you will see a line containing the call to the _ init _ method of screenlet, which sets the initial width and height of the window to be created for this application.
The only other function required by this application is the on_draw method. This routine sets the background color of the box to white and draws a rectangle using the previously defined dimension. It sets the text color to black and the source text to "Hello World !", Then draw the text. Figure 1 shows what you should see when running this screenlet. This basic structure persists when you create more useful applications on these simple blocks.
Figure 1. Basic screenlet Structure
Reuse code in a more complex screenlet
One benefit of writing screenlet is that it can reuse code from other applications. Through a wide range of open-source projects based on the Python language, code reuse opens up infinite possibilities. Although each screenlet has the same basic structure, more methods are defined to process different behaviors. Listing 2 shows the sample application named TimeTrackerScreenlet.
Listing 2. Python code of Time Tracker screenlet
#!/usr/bin/env pythonimport screenletsimport cairoimport datetimeclass TimeTrackerScreenlet(screenlets.Screenlet):__name__ = 'TimeTrackerScreenlet'__version__ = '0.1'__author__ = 'John Doe'__desc__ = 'A basic time tracker screenlet.'theme_dir = 'themes/default'image = 'start.png'def __init__(self, **keyword_args):screenlets.Screenlet.__init__(self, width=250, height=50, **keyword_args)self.add_default_menuitems()self.y = 25self.theme_name = 'default'self.on = Falseself.started = Nonedef on_draw(self, ctx):self.draw_scaled_image(ctx, 0, 0, self.theme_dir + '/' +self.image, self.width, self.height)def on_mouse_down(self, event):if self.on:self.started = datetime.datetime.now()self.image = 'stop.png'self.on = Falseelse:if self.started:length = datetime.datetime.now() - self.startedscreenlets.show_message(None, '%s seconds' %length.seconds, 'Time')self.started = Noneself.image = 'start.png'self.on = Truedef on_draw_shape(self, ctx):self.on_draw(ctx)ctx.rectangle(0, 0, self.width, self.height)ctx.fill()if __name__ == "__main__":import screenlets.sessionscreenlets.session.create_session(TimeTrackerScreenlet)
This example introduces several concepts that you need to understand before you start building any useful programs. All screenlet applications have the ability to respond to specific user operations or events (such as mouse clicks or drag-and-drop operations. In this example, the mouse-down event is used as a trigger to change the icon status. The start.png image is displayed when the screenlet is running. Click the image to change it to stop.png and record the start time in self. started. Click Stop image to change the image back to start.png and display the time elapsed since the first image was clicked.
Responding to events is another key feature that makes it possible to build any number of different applications. Although this example only uses the mouse_down event, you can use the same method for other events generated by the screenlet framework or system events (such as timers. The second concept introduced here is the persistent state. Because your application runs continuously, you can keep track of the project in memory by waiting for the event to trigger some operations, such as clicking the start image time. If necessary, you can save the information to the disk for future retrieval.
Use screenlet to automate tasks
Now that you have understood the concepts behind developing screenlet, let's put all these together. Most users now use the Really Simple Syndication (RSS) reader to read blogs and news feeds. For this final example, you will build a configurable screenlet that monitors specific feeds to find keywords and display any major news in the text box. The result is a clickable link that can be opened in your default Web browser. Listing 3 shows the source code of the RSS Search screenlet.
Listing 3. Python code of RSS Search screenlet
#!/usr/bin/env pythonfrom screenlets.options import StringOption, IntOption, ListOptionimport xml.dom.minidomimport webbrowserimport screenletsimport urllib2import gobjectimport pangoimport cairoclass RSSSearchScreenlet(screenlets.Screenlet):__name__ = 'RSSSearch'__version__ = '0.1'__author__ = 'John Doe'__desc__ = 'An RSS search screenlet.'topic = 'Windows Phone 7'feeds = ['http://www.engadget.com/rss.xml','http://feeds.gawker.com/gizmodo/full']interval = 10__items = []__mousesel = 0__selected = Nonedef __init__(self, **kwargs):# Customize the width and height.screenlets.Screenlet.__init__(self, width=250, height=300, **kwargs)self.y = 25def on_init(self):# Add options.self.add_options_group('Search Options','RSS feeds to search and topic to search for.')self.add_option(StringOption('Search Options','topic',self.topic,'Topic','Topic to search feeds for.'))self.add_option(ListOption('Search Options','feeds',self.feeds,'RSS Feeds','A list of feeds to search for a topic.'))self.add_option(IntOption('Search Options','interval',self.interval,'Update Interval','How frequently to update (in seconds)'))self.update()def update(self):"""Search selected feeds and update results."""self.__items = []# Go through each feed.for feed_url in self.feeds:# Load the raw feed and find all item elements.raw = urllib2.urlopen(feed_url).read()dom = xml.dom.minidom.parseString(raw)items = dom.getElementsByTagName('item')for item in items:# Find the title and make sure it matches the topic.title = item.getElementsByTagName('title')[0].firstChild.dataif self.topic.lower() not in title.lower(): continue# Shorten the title to 30 characters.if len(title) > 30: title = title[:27]+'...'# Find the link and save the item.link = item.getElementsByTagName('link')[0].firstChild.dataself.__items.append((title, link))self.redraw_canvas()# Set to update again after self.interval.self.__timeout = gobject.timeout_add(self.interval * 1000, self.update)def on_draw(self, ctx):"""Called every time the screenlet is drawn to the screen."""# Draw the background (a gradient).gradient = cairo.LinearGradient(0, self.height * 2, 0, 0)gradient.add_color_stop_rgba(1, 1, 1, 1, 1)gradient.add_color_stop_rgba(0.7, 1, 1, 1, 0.75)ctx.set_source(gradient)self.draw_rectangle_advanced (ctx, 0, 0, self.width - 20,self.height - 20,rounded_angles=(5, 5, 5, 5),fill=True, border_size=1,border_color=(0, 0, 0, 0.25),shadow_size=10,shadow_color=(0, 0, 0, 0.25))# Make sure we have a pango layout initialized and updated.if self.p_layout == None :self.p_layout = ctx.create_layout()else:ctx.update_layout(self.p_layout)# Configure fonts.p_fdesc = pango.FontDescription()p_fdesc.set_family("Free Sans")p_fdesc.set_size(10 * pango.SCALE)self.p_layout.set_font_description(p_fdesc)# Display our text.pos = [20, 20]ctx.set_source_rgb(0, 0, 0)x = 0self.__selected = Nonefor item in self.__items:ctx.save()ctx.translate(*pos)# Find if the current item is under the mouse.if self.__mousesel == x and self.mouse_is_over:ctx.set_source_rgb(0, 0, 0.5)self.__selected = item[1]else:ctx.set_source_rgb(0, 0, 0)self.p_layout.set_markup('%s' % item[0])ctx.show_layout(self.p_layout)pos[1] += 20ctx.restore()x += 1def on_draw_shape(self, ctx):ctx.rectangle(0, 0, self.width, self.height)ctx.fill()def on_mouse_move(self, event):"""Called whenever the mouse moves over the screenlet."""x = event.x / self.scaley = event.y / self.scaleself.__mousesel = int((y -10 )/ (20)) -1self.redraw_canvas()def on_mouse_down(self, event):"""Called when the mouse is clicked."""if self.__selected and self.mouse_is_over:webbrowser.open_new(self.__selected)if __name__ == "__main__":import screenlets.sessionscreenlets.session.create_session(RSSSearchScreenlet)
Based on the concepts of the first two examples, this screenlet uses some new concepts, including the config page. In the on_init routine, add three options for the user to specify: the RSS feed list for tracking, the topic of interest for search, and the update interval. Then update the routine to use all of the content at runtime.
Python is a good language for such tasks. The standard library includes everything you need to load an Extensible Markup Language (XML) from an RSS feed to a searchable list. In Python, this task only requires three lines of code:
raw = urllib2.urlopen(feed_url).read()dom = xml.dom.minidom.parseString(raw)items = dom.getElementsByTagName('item')
The libraries used in these three rows include urllib2 and xml. In the first line, the complete content found on the feed_url address is read to the string line. Next, because you know that the string contains XML, you can use the dom. minidom. parseString method of the Python XML library to create a document object composed of node objects.
Finally, create a list of element objects corresponding to a single XML element named item. You can then iterate this list to search for your target topic. Using the for keyword, Python has a good way to iterate the project list, as shown in the following code snippet:
for item in items:# Find the title and make sure it matches the topic.title = item.getElementsByTagName('title')[0].firstChild.dataif self.topic.lower() not in title.lower(): continue
Add each project that matches your criteria to the currently displayed list, which is associated with the screenlet instance. You can use this method to run multiple instances with the same screenlet. Different topics can be searched for each instance. The last part of the update function re-draws the text with the updated list and triggers a new update timer based on the interval on the config page. By default, the timer is triggered every 10 seconds, but you can change any value that the timer wants. This timer mechanism comes from the gobject library and is part of the GTK framework.
This application greatly extends the on_draw method to adapt to your new features. Both Cairo and Pango databases allow you to create effects for text windows. Gradually make the background of the widget beautiful and have rounded corners and translucent. Use Pango to add functions to the layout to easily save and store the current context. It also provides a way to generate a scalable font based on the current screenlet size.
The most tricky part of the on_draw method is to process a project that the user hovers over the list. By using the for keyword, You Can iterate projects in the screenlet to check whether users are hovering over a specific project. If you hover over a specific item, set the selected property and change the color to provide visual feedback. You can also set Link Attributes to bold with a single flag-it may not be the most delicate or effective way to handle the problem, but it is useful. When a user clicks the link in the box, the Web browser with the target URL starts. You can see this function in the on_mouse_down function. Python and its libraries allow a line of code to start the default browser to display the desired page.
Figure 2. Sample screenlet
Conclusion
Building useful desktop applications with Python and screenlet is not a difficult task. The biggest obstacle is adapting to the screenlet API and passing controls between different functions. Although this document may not be easy to read, it contains the information you need to use different functions. A better way to get started quickly is to modify the existing screenlet that is close to your requirements.