banner
Previous Page
PCLinuxOS Magazine
PCLinuxOS
Article List
Disclaimer
Next Page

Casual Python, Part 4


by critter


Appsearch

This application is more complex than the previous examples. It will take more time to explain, and even more time to understand, as there a lot of new ideas coming all at once. When you can understand (most of) this example, you will have a good understanding of basic python. Some of the code in here is admittedly not basic stuff, so if it is a bit too much to 'get your head around,' just accept that it works for now. Eventually, you will grasp it.



I have a lot of applications installed on my system. Some I don't use very often, but still find useful. When I need one of these rarely used applications, I often cannot remember the name and sometimes, when searching through the menu, the name doesn't readily reflect the purpose or function of the application. The next utility that I am going to build goes some way to solving this problem by providing real-time searching on any word and single click launching of the selected application. It will be based on the template application and looks like the snapshot above.

Suppose that I need to use a HTML editor. I know that I have at least one installed, but cannot remember the name.

As I begin to type 'editor' in the box, the search is refined, at first showing every possibility that contains the letter 'e', then 'ed'. By the time that I have typed in 'edi,' I have found two possibilities -- Bluefish and Blue Griffon (scrolling down reveals no more than the two shown in the screenshot). Clicking anywhere in the respective text closes the appsearch utility and launches the chosen HTML editor. This is much quicker than searching the menu.


How it works

Most Linux desktops adhere to a standard defined by freedesktop.org. When an application is installed, a file with the name of the application and the extension '.desktop' is placed in the /usr/share/applications directory. This file describes the applications name and features, usually in many languages, and consists of several sections so the entire file can be quite long. For the purposes of this application, I am going to restrict its' use to the default English language and to five sections:

  • Name           the application's name.
  • Genericname           this might be something like editor
  • Comment           some descriptIon of the application
  • Keywords           words that might be used to search for the application
  • Exec           what you would type at the command line to launch the application

For the bluegriffon.desktop file this reduces to:

Name=BlueGriffon
GenericName=Web Editor
Comment=Create Web Pages
Exec=bluegriffon

There is no 'Keywords' entry in this file. This is the information that was displayed by the search in the snapshot.

The application will read each of the '.desktop' files in /usrshare/applications and create a list of this information for each of the files. It will then search for whatever is currently in the text search box in these lists and display matches, highlighting the matched part. The highlighting is helpful as in the example above 'edi' is found in Media but we are looking for editors, so this line can be ignored. The number on the left is added by the application, and is an index into the generated lists. Clicking on the text for bluegriffon sends the index 007 to the application, and the Exec field of the list tells how to launch the selected application.


Creating the new application

Create a new directory called qt5_appsearch and copy the following files from the qt5_template directory into this new directory renaming them as follows:

Also place a suitable icon in there.

Edit update_res.sh and change both occurrences of template to appsearch.

Next, we have to re-work the user interface file so open designer. Select open, and navigate to our new directory. Open qt5_appsearch.ui.

You will now see the template user interface. Change the form to look like the example above, using the same methods as before. You may need to right click on the background and select layout - break layout. Make sure that your QTextEdit widget is a QTextEdit, not a QPlainTextEdit (this is necessary to show the highlighting). The only widgets that we shall be interacting with directly are the line edit, textedit and button, so make sure that their object names are exactly:

lineEdit, textEdit and btn_Quit

as this is how the code recognizes them. When you are happy with the appearance, check it out with a control-r preview, and then save it. Run update_res.sh to generate the python code for the user interface.


Designing the code

I have already said that the application depends on data found in freedesktop.org standard files, and as I am using the blugriffon editor as an example. Here is the bluegriffon.desktop file in full with the lines of interest highlighted:

To use the top down design method it is necessary to determine the 'top' which here is to launch an application.

    Launch the application

Before we can do that we have to choose the application.

   Choose an application
   Launch the application

To be able to make our choice we need to display a list of applications and their descriptions.

   Display a list of applications and their descriptions
   Choose an application
   Launch the application

In order that we may generate a list of suitable applications we have to get some requirements from the user.

   get search criteria from the user
   Display a list of applications and their descriptions
   Choose an application
   Launch the application

The very first thing we need is a list of all available applications and their descriptions.

   Create a database of avAilable applications and their descriptions
   Interrogate the user for search criteria
   Display a list of applications and their descriptions
   Choose an application
   Launch the application

That then is our design.

The structure of the code will be:

some imports
a class definition which contains 8 method definitions
   __init__
   buildDb
   addCount
   showResults
   findText
   MousePressed
   KeyPressEvent
   exitApplication
finally our standard code that starts; if __name__ = '__main__':

The imports:
import sys
import os
import glob
import subprocess
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import appsearch_ui

The first item in the design is to create a database of application information for all available applications.

This can be broken down into steps as above but with more detail:

Each application may have up to five items of data: Name, GenericName, Comment, Keywords and Exec. We can easily store these in a python list of strings. However, not all applications have all sections completed (Bluegriffon has no Keywords section), so we should start with a list of five empty strings. In the final code, it looks like this, with comments.

This follows the detailed breakdown quite closely. A list of files is created, which is then sorted, Uppercase first, and stored in a list. Another empty list named data is created, ready to store the final extracted information.

The for loop runs through each file, which is then opened for reading (the default), and a new list of five empty strings is created to store the information about each application. Another for loop then steps through each line of the current file, and uses string slicing to compare the start of the line to a required pattern. If found, the line is split at the '=' sign, the 'end of line character' (\n) removed, and the rest of the line stored in the first element of the field_list list.

If there is no match, then the line is compared to the next heading of interest. This is repeated for each of the five fields of interest. In the 'Exec' string, there are often some characters that would confuse our code, and these, if present, are replaced by blanks. The completed field_list is appended to the data list.

Finally, when all of the files have been processed and added to the data list of lists, another routine (method) is called to index the database. This indexing code has been separated into another method, as the buildDb routine was already quite complex and smaller 'chunks' of code are easier to manage and may also be re-used in other applications.

The addCount method which does the indexing is next, the comments explain it:

Each application gets a number. Bluegriffon's entry now looks like this:

['BlueGriffon', 'Web Editor', 'Create Web Pages', '', 'bluegriffon', '007']

Then we have the showResults method:

Remove any text in the textEdit widget.

For each line of data in the apptext database, we look for the typed in text, which we are calling 't' here. The if construct compares 't' to the lowercase version of each element of the current line. If a match is found, then we build a string called 'found', starting with the index number and adding each element of the matched line, separated by spaces, which we add to the textEdit. Finally, from here we call the findText method, which does the highlighting for us, and has been separated out into a new definition.

This method is quite complex, so I am not going to explain it all. This is a method that highlights all text in a textEdit widget that matches text in a lineEdit widget. We can re-use this anytime we need to highlight matching text without understanding it.

The mousePressed method is very similar to the one we used in the docz application.

There is one difference. If the command includes a space, then the Popen method needs the shell=True option to interpret it. This is what the if/else construct is for.

The last two methods, keyPressEvent and exitApplication are the same as previous ones.

In the final part of the code we create an instance of the class

And to center the form on screen we could add this:

And move form.show() to the line below it.

The final code in geany, with the code I have already shown in detail, collapsed looks like this:




Variables

As you know by now, variables are the names we use to reference our objects. So what is a valid variable name?

  • A variable should start with an underscore or letter
  • The remainder of the variable may consist of letters, numbers and underscores
  • All uppercase is conventionally used for constants
  • Avoid lowercase o (oh), l (ell) and uppercase I (eye). These can be confused with other characters

For other conventions see pep8.


The range function

Python provides a range function that produces a range of values.

range(start, stop, step).

The start, stop and step arguments mean the same as for list indices an the stop value is not included. The function returns an iterable range object not a list of numbers.



Tuples

Lists are extremely useful, and will be found in lots of python code. Their main disadvantage is that they are mutable, and so can be changed from under you. When you need an immutable list, use a tuple. They are not quite as flexible as lists, but you can rely on their contents being as expected.

A tuple is an ordered, immutable sequence of comma separated items. Parentheses are often used surrounding the items to avoid ambiguity. A tuple with a single value must have a trailing comma: single_tuple = (‘a’,) Without the comma python will create an instance of the native data type: str, int etc. An empty pair of parentheses will give an empty tuple. All of these are valid tuples.

Tuple indices and ranges are started in square brackets [] as in lists.

Slicing a tuple creates a new tuple, but tuples cannot be changed in any way.

Tuples are faster than lists.

An empty tuple is boolean False, and all other tuples are True regardless of value.

You can convert between tuples and lists using the list() and tuple() functions.

Multiple assignment can be achieved using tuples. This is known as unpacking.

Tuples may also be used to return multiple values from a function or method.

Tuple concatenation and repetition are also supported as for lists. Refer to that section for a description.

The following list methods are not supported by tuples:

append, clear, copy, extend, insert, pop, remove, reverse & sort.


Dictionaries

A dictionary is an unordered collection of key-value pairs, often compared to associative arrays in other languages. Each key must be unique and have a value. A dictionary is a mapping, not a sequence. An empty dictionary is False, while all other dictionaries are True.

Note that as a dictionary is an unordered collection, the key order is not maintained so that numerical index as with lists is not possible. However, in a for loop for example, each key,value pair will be visited.

The keys in a dictionary must be unique and of an immutable type. The values may be of any type.

Unlike lists, which need to use the append method to grow in size, a dictionary may be freely added to and may be changed in-place. A dictionary may be created by literal assignment, dynamic assignment, keyword argument format or by assigning a list of key,value tuples.

Values may be referenced by key but not keys by value which return None.


Modifying a dictionary

A dictionary may be added to or modified in-place.


Length of a dictionary

The following dictionary has a length of two. There are 2 keys, 'deutsch' and 'francais', and each key is mapped to a list.


Deleting an element


Membership


List out the items, keys or values



Previous Page              Top              Next Page