2011-12-17

PyGTK Alignment Class

In order to understand the layout management in PyGTK, I built a form to experiment:
 
 
It is just a simple form with two buttons at the bottom right corner.  The following is the code:


import gtk
class App(gtk.Window):

    def __init__(self):
        super(App, self).__init__()

        self.set_title('Alignment')
        self.set_size_request(320, 200)
        self.set_position(gtk.WIN_POS_CENTER)

        ok = gtk.Button('OK')
        close = gtk.Button('Close')

        hbox = gtk.HBox(True) # homogeneous children
        hbox.add(ok)
        hbox.add(close)

        align = gtk.Alignment(1, 1, 0, 0) # push to right bottom
        align.add(hbox)
       
        vbox = gtk.VBox()
        vbox.add(align)
        self.add(vbox)

        self.connect('destroy', gtk.main_quit)
        self.show_all()
 
App()
gtk.main()


The layout is managed by a HBox inside an Alignment widget inside a VBox:


The Alignment widget controls the alignment and size of its child.  Let's look at the Alignment widget creation:

    align = gtk.Alignment(1, 1, 0, 0) # push to right bottom


The first "1" effectively pushes the HBox to the right, while the second "1" push the containing HBox to the bottom.

The gtk.Alignment class reference can be found here.

2011-11-28

Email reminder

Oftentimes while on the road, I think of something I need to do and would like to remind myself by sending an email to myself with the task name as the email subject.

Doing that with my Android phone is not difficult, but I prefer to write a Python script to streamline it, so that I don't have to go through the hassle of selecting myself as the recipient, etc.  Here is the script:

import android
droid = android.Android()
task = droid.dialogGetInput("ToDo", "Task?", "").result
if task:
    droid.sendEmail('myself@example.com', '[ToDo] ' + task, task)

A simple five lines script can boost my productivity and also bring me back the good old feeling of programming my calculator!



2011-11-18

Analog Clock in PyGTK

I was searching the web for some PyGTK tutorial and came across this one to build an analog clock.  However only the clock face is done.  The hour, minute and second hands are yet to be implemented.


Then I came across this tutorial which implements the clock hands but it is done in gtkmm (C++).  So I translated the C++ code to PyGTK in the draw() method:


# compute the angles in radians of the hands of the clock
now = datetime.datetime.now()
hours = now.hour * math.pi / 6
minutes = now.minute * math.pi / 30
seconds = now.second * math.pi / 30

context.set_line_cap(cairo.LINE_CAP_ROUND)

# draw the hours hand
context.save()
context.set_source_rgba(0.337, 0.612, 0.117, 0.9) # green
context.set_line_width(7)
context.move_to(x, y)
context.line_to(x + math.sin(hours + minutes/12) * (radius * 0.5),
                y - math.cos(hours + minutes/12) * (radius * 0.5))
context.stroke()
context.restore()

# draw the minutes hand
context.save()
context.set_source_rgba(0.117, 0.337, 0.612, 0.9) # blue
context.set_line_width(5)
context.move_to(x, y)
context.line_to(x + math.sin(minutes + seconds/60) * (radius * 0.8),
                y - math.cos(minutes + seconds/60) * (radius * 0.8))
context.stroke()
context.restore()

# draw the seconds hand
context.save()
context.set_source_rgba(0.7, 0.7, 0.7, 0.8) # gray
context.set_line_width(3)
context.move_to(x, y)
context.line_to(x + math.sin(seconds) * (radius * 0.9),
                y - math.cos(seconds) * (radius * 0.9))
context.stroke()
context.restore()



At this point the clock hands are not moving yet.  We need a timer to move the clock hands every second.  I need some extra help to translate the gtkmm timer code to PyGTK, and this tutorial just did that.  What it does is to set up a timer in __init__():

gobject.timeout_add_seconds(1, self.on_timeout)

to invalidate the clock face every second:

def on_timeout(self):
    win = self.get_window()
    rect = self.get_allocation()
    win.invalidate_rect(rect, False)
    return True


Now we have an analog clock written in PyGTK.  Here is the complete source code:

import cairo
import datetime
import gobject
import gtk
import math

class ClockFace(gtk.DrawingArea):
    def __init__(self):
        gtk.DrawingArea.__init__(self)
        self.connect("expose_event", self.expose)
        gobject.timeout_add_seconds(1, self.on_timeout)
       
    def expose(self, widget, event):
        self.context = widget.window.cairo_create()
        self.context.rectangle(event.area.x, event.area.y,
                               event.area.width, event.area.height)
        self.context.clip()
        self.draw(self.context)
        return False

    def on_timeout(self):
        win = self.get_window()
        rect = self.get_allocation()
        win.invalidate_rect(rect, False)
        return True

    def draw(self, context):
        rect = self.get_allocation()
        x = rect.x + rect.width / 2
        y = rect.y + rect.height / 2
        radius = min(rect.width / 2, rect.height / 2) - 5

        # clock background
        context.arc(x, y, radius, 0, 2 * math.pi)
        context.set_source_rgb(1, 1, 1)
        context.fill_preserve()
        context.set_source_rgb(0, 0, 0)
        context.stroke()

        # clock ticks
        for i in range(12):
            context.save()
            if i % 3 == 0:
                inset = 0.2 * radius
            else:
                inset = 0.1 * radius
                context.set_line_width(0.5 * context.get_line_width())

            context.move_to(x + (radius-inset) * math.cos(i * math.pi/6),
                                 y + (radius-inset) * math.sin(i * math.pi/6))
            context.line_to(x + radius * math.cos(i * math.pi/6),
                                 y + radius * math.sin(i * math.pi/6))
            context.stroke()
            context.restore()

        # compute the angles in radians of the hands of the clock
        now = datetime.datetime.now()
        hours = now.hour * math.pi / 6
        minutes = now.minute * math.pi / 30
        seconds = now.second * math.pi / 30

        context.set_line_cap(cairo.LINE_CAP_ROUND)

        # draw the hours hand
        context.save()
        context.set_source_rgba(0.337, 0.612, 0.117, 0.9) # green
        context.set_line_width(7)
        context.move_to(x, y)
        context.line_to(x + math.sin(hours + minutes/12) * (radius * 0.5),
                        y - math.cos(hours + minutes/12) * (radius * 0.5))
        context.stroke()
        context.restore()

        # draw the minutes hand
        context.save()
        context.set_source_rgba(0.117, 0.337, 0.612, 0.9) # blue
        context.set_line_width(5)
        context.move_to(x, y)
        context.line_to(x + math.sin(minutes + seconds/60) * (radius * 0.8),
                        y - math.cos(minutes + seconds/60) * (radius * 0.8))
        context.stroke()
        context.restore()

        # draw the seconds hand
        context.save()
        context.set_source_rgba(0.7, 0.7, 0.7, 0.8) # gray
        context.set_line_width(3)
        context.move_to(x, y)
        context.line_to(x + math.sin(seconds) * (radius * 0.9),
                        y - math.cos(seconds) * (radius * 0.9))
        context.stroke()
        context.restore()

def main():
    window = gtk.Window()
    clock = ClockFace()

    window.add(clock)
    window.connect("destroy", gtk.main_quit)
    window.show_all()

    gtk.main()

if __name__ == '__main__':
    main()

2011-11-06

Kasio - Find Button

Continue from the last post, we are going to implement the Find button and wrap up the project.

We are going to use regular expression, so let's add an import statement:

import re

Next we implement findnext() as below:

def findnext(self, pattern):
    '''search pattern from cursor position onward.'''
    if not pattern: # ask if no pattern specified
        dlg = wx.TextEntryDialog(self, "Find?", "Attention", "",
                                 style=wx.OK|wx.CANCEL)
        if dlg.ShowModal() == wx.ID_OK:
            pattern = dlg.GetValue()
    if pattern:
        self.lastfind = pattern
        curpos = self.textbox.GetSelection()[1] # end of current selection
        endpos = self.textbox.GetLastPosition() # eof
        # retrieve the content in the text area from current position onward
        content = self.textbox.GetRange(curpos, endpos)
        # find the next match using regexp & case insensitive
        mo = re.search(pattern, content, re.I)
        # select the match and scroll to it
        if mo:
            self.textbox.SetSelection(curpos + mo.start(), curpos + mo.end())
            self.textbox.ShowPosition(curpos + mo.start())
        else:
            dlg = wx.MessageDialog(None, 'Not found.', 'Attention', wx.OK)
            dlg.ShowModal()

What it does is to ask the user for the search pattern if it is not provided, and then search the text area from the current position onward until it reaches the end.  If a match is found, select it and scroll to it.  It will also save the search pattern to an instance variable lastfind for later use.

Next we write the Find button event handler, which simply calls findnext() and then set the focus back to the text area:

def onFind(self, event):
    self.findnext('')
    self.textbox.SetFocus()


Next we implement Find Next (F3).  First we need to initialize the lastfind instance variable to an empty string, as well as binding the Key Press event in __init__():

    self.lastfind = ''
    textbox.Bind(wx.EVT_KEY_DOWN, self.onKeyPress)

and implement the event handler as:

def onKeyPress(self, event):
    keycode = event.GetKeyCode()
    if keycode == wx.WXK_F3:
        self.findnext(self.lastfind)
    else:
        event.Skip()

It simply calls findnext() with the instance variable lastfind.  That means if this is the first time doing a find, the user will be asked to enter a search pattern because the instance variable lastfind is empty.

That's it.  Both Find and Find Next are now implemented!

To wrap up the project, I add a feature to change the default directory on startup if a particular environmental variable is set with the following code in __init__():

    # change to default directory
    if os.environ.has_key('KASIO_PATH'):
        os.chdir(os.environ['KASIO_PATH'])


Below is the complete source code for the GUI part of this project, with additional keyboard shortcuts for Open, Save, Find and Quit.  Also in order to make it runs seamlessly, focus is switched back to the text area whenever a button is clicked.

# kasio.py

import re
import os
import sys

import wx

import filecrypto

class MainFrame(wx.Frame):
    def __init__(self, parent=None, id=(-1), title='kasio', size=(800,600)):
        wx.Frame.__init__(self, parent, id, title, size=size)
        self.panel = wx.Panel(self, wx.ID_ANY)

        textbox = wx.TextCtrl(self.panel, -1, style=wx.TE_MULTILINE)
        font = wx.Font(10, wx.FONTFAMILY_MODERN, wx.NORMAL, wx.NORMAL)
        textbox.SetFont(font)
        btnOpen = wx.Button(self.panel, -1, 'Open')
        btnSave = wx.Button(self.panel, -1, 'Save')
        btnFind = wx.Button(self.panel, -1, 'Find')
        btnExit = wx.Button(self.panel, -1, 'Exit')

        self.Bind(wx.EVT_BUTTON, self.onOpen, btnOpen)
        self.Bind(wx.EVT_BUTTON, self.onSave, btnSave)
        self.Bind(wx.EVT_BUTTON, self.onFind, btnFind)
        self.Bind(wx.EVT_BUTTON, self.onExit, btnExit)

        topSizer = wx.BoxSizer(wx.VERTICAL)
        btnSizer = wx.BoxSizer(wx.HORIZONTAL)

        btnSizer.Add(btnOpen, 0, wx.ALL, 1)
        btnSizer.Add(btnSave, 0, wx.ALL, 1)
        btnSizer.Add(btnFind, 0, wx.ALL, 1)
        btnSizer.Add(btnExit, 0, wx.ALL, 1)

        topSizer.Add(textbox, 1, wx.ALL|wx.EXPAND, 1)
        topSizer.Add(wx.StaticLine(self.panel), 0, wx.ALL|wx.EXPAND, 3)
        topSizer.Add(btnSizer, 0, wx.ALL|wx.ALIGN_RIGHT, 3)
      
        self.panel.SetSizer(topSizer)
        #topSizer.Fit(self) # minimum size to show all controls
        self.Centre() # center the frame in desktop
        btnOpen.SetDefault()

        self.filename = ''
        self.password = ''
        self.textbox = textbox

        self.lastfind = ''
        textbox.Bind(wx.EVT_KEY_DOWN, self.onKeyPress)
        textbox.SetFocus()

        # change to default directory
        if os.environ.has_key('KASIO_PATH'):
            os.chdir(os.environ['KASIO_PATH'])

    def onOpen(self, event):
        try:
            # ask for file
            dlg = wx.FileDialog(self, "Choose a file", os.getcwd(), "", "*",
                wx.OPEN|wx.CHANGE_DIR)
            if dlg.ShowModal() == wx.ID_OK:
                self.filename = dlg.GetPath()
            else:
                return
            # ask for password
            dlg = wx.TextEntryDialog(self, "Password?", "Attention", "",
                style=wx.OK|wx.CANCEL|wx.TE_PASSWORD)
            if dlg.ShowModal() == wx.ID_OK:
                self.password = dlg.GetValue()
            else:
                return
            # create file cipher
            fc = filecrypto.FileCrypto()
            # read & decrypt file
            plaintext = fc.load(self.filename, self.password)
            # convert 8-bit string in utf-8 encoding to unicode string
            plaintext = plaintext.decode('utf-8')
            # populate text control
            self.textbox.SetValue(plaintext)
        except:
            self.filename = ''
            self.password = ''
            self.textbox.SetValue('')
            errmsg = str(sys.exc_info()[1])
            print(errmsg)
            dlg = wx.MessageDialog(None, 'Password Incorrect!', 'Attention', wx.OK | wx.ICON_EXCLAMATION)
            dlg.ShowModal()
        finally:
            self.textbox.SetFocus()

    def onSave(self, event):
        try:
            # if no filename, ask filename and password
            if len(self.filename) == 0:
                # ask for filename
                dlg = wx.FileDialog(self, "Choose a file", os.getcwd(), "", "*",
                    wx.SAVE|wx.OVERWRITE_PROMPT|wx.CHANGE_DIR)
                if dlg.ShowModal() == wx.ID_OK:
                    self.filename = dlg.GetPath()
                else:
                    return
                # ask for password
                dlg = wx.TextEntryDialog(self, "Password?", "Attention", "",
                    style=wx.OK|wx.CANCEL|wx.TE_PASSWORD)
                if dlg.ShowModal() == wx.ID_OK:
                    self.password = dlg.GetValue()
                else:
                    return
            # create file cipher
            fc = filecrypto.FileCrypto()
            # get the text
            plaintext = self.textbox.GetValue()
            # convert unicode string to 8-bit string in utf-8 encoding
            plaintext = plaintext.encode('utf-8')
            # encrypt and write to file
            fc.save(self.filename, self.password, plaintext)
        except:
            errmsg = str(sys.exc_info()[1])
            dlg = wx.MessageDialog(None, errmsg, 'Attention', wx.OK | wx.ICON_EXCLAMATION)
            dlg.ShowModal()
            print(errmsg)
        finally:
            self.textbox.SetFocus()

    def onExit(self, event):
        self.Close()

    def onFind(self, event):
        self.findnext('')
        self.textbox.SetFocus()

    def onKeyPress(self, event):
        keycode = event.GetKeyCode()
        if keycode == wx.WXK_F3:
            self.findnext(self.lastfind)
        elif keycode == ord('O') and event.ControlDown(): # Control-O
            self.onOpen(None)
        elif keycode == ord('S') and event.ControlDown(): # Control-S
            self.onSave(None)
        elif keycode == ord('F') and event.ControlDown(): # Control-F
            self.onFind(None)
        elif keycode == ord('Q') and event.ControlDown(): # Control-Q
            self.onExit(None)
        else:
            event.Skip()
       
    def findnext(self, pattern):
        '''search pattern from cursor position onward.'''
        if not pattern: # ask if no pattern specified
            dlg = wx.TextEntryDialog(self, "Find?", "Attention", "", style=wx.OK|wx.CANCEL)
            if dlg.ShowModal() == wx.ID_OK:
                pattern = dlg.GetValue()
        if pattern:
            self.lastfind = pattern
            curpos = self.textbox.GetSelection()[1] # end of current selection
            endpos = self.textbox.GetLastPosition() # eof
            # retrieve the content in the text area from current position onward
            content = self.textbox.GetRange(curpos, endpos)
            # find the next match using regexp & case insensitive
            mo = re.search(pattern, content, re.I)
            # select the match and scroll to it
            if mo:
                self.textbox.SetSelection(curpos + mo.start(), curpos + mo.end())
                self.textbox.ShowPosition(curpos + mo.start())
            else:
                dlg = wx.MessageDialog(None, 'Not found.', 'Attention', wx.OK)
                dlg.ShowModal()

class MainApp(wx.App):
    def OnInit(self):
        frame = MainFrame()
        frame.Show(True)
        self.SetTopWindow(frame)
        return True

def main():
    app = MainApp()
    app.MainLoop()

if __name__=='__main__':
    main()



2011-10-31

Kasio - Open & Save

Continue from the last post, we are going to write the event handler for the Open and Save buttons this time.  We will also make use of the filecrypto module we wrote in an earlier post.

First we will have to add a few more imports:

import os
import sys
import wx
import filecrypto

Click the Open button to select a file and enter the password to decrypt the file into the text area.

def onOpen(self, event):
    try:
        # ask for file
        dlg = wx.FileDialog(self, "Choose a file", os.getcwd(), "", "*",
            wx.OPEN|wx.CHANGE_DIR)
        if dlg.ShowModal() == wx.ID_OK:
            self.filename = dlg.GetPath()
        else:
            return
        # ask for password
        dlg = wx.TextEntryDialog(self, "Password?", "Attention", "",
            style=wx.OK|wx.CANCEL|wx.TE_PASSWORD)
        if dlg.ShowModal() == wx.ID_OK:
            self.password = dlg.GetValue()
        else:
            return
        # create file cipher
        fc = filecrypto.FileCrypto()
        # read & decrypt file
        plaintext = fc.load(self.filename, self.password)
        # convert 8-bit string in utf-8 encoding to unicode string
        plaintext = plaintext.decode('utf-8')
        # populate text control
        self.textbox.SetValue(plaintext)
    except:
        self.filename = ''
        self.password = ''
        self.textbox.SetValue('')
        errmsg = str(sys.exc_info()[1])
        dlg = wx.MessageDialog(None, errmsg, 'Attention', wx.OK | wx.ICON_EXCLAMATION)
        dlg.ShowModal()
        print(errmsg)


Click the Save button to save the content in the text area to a file.  If you are saving a new file, you will have to enter a password to encrypt the content.  If you are saving an existing file, the content will be encrypted using the password you entered to open the file in the first place.

def onSave(self, event):
    try:
        # if no filename, ask filename and password
        if len(self.filename) == 0:
            # ask for filename
            dlg = wx.FileDialog(self, "Choose a file", os.getcwd(), "", "*",
                wx.SAVE|wx.OVERWRITE_PROMPT|wx.CHANGE_DIR)
            if dlg.ShowModal() == wx.ID_OK:
                self.filename = dlg.GetPath()
            else:
                return
            # ask for password
            dlg = wx.TextEntryDialog(self, "Password?", "Attention", "",
                style=wx.OK|wx.CANCEL|wx.TE_PASSWORD)
            if dlg.ShowModal() == wx.ID_OK:
                self.password = dlg.GetValue()
            else:
                return
        # create file cipher
        fc = filecrypto.FileCrypto()
        # get the text
        plaintext = self.textbox.GetValue()
        # convert unicode string to 8-bit string in utf-8 encoding
        plaintext = plaintext.encode('utf-8')
        # encrypt and write to file
        fc.save(self.filename, self.password, plaintext)
    except:
        errmsg = str(sys.exc_info()[1])
        dlg = wx.MessageDialog(None, errmsg, 'Attention', wx.OK | wx.ICON_EXCLAMATION)
        dlg.ShowModal()
        print(errmsg)


Seems that wxPython likes unicode while the filecrypto module likes 8-bit string.  Hence you can see there are encode() and decode() calls in the onSave() and onLoad() event handlers to keep them both happy.

Let's implement the Find button in the next post.





2011-10-25

File Encryption GUI

Continue from my last post, I will build a GUI front end for the file encryption utility in wxPython.

The GUI is very simple, just a text editing area and four buttons: Open, Save, Find, Exit.



The source code for the GUI is listed below.  We use a vertical BoxSizer to separate the text area and the button area, and a horizontal BoxSizer for the four buttons.  We align the buttons to the right, and create dummy button click event handlers to be implemented in the next post.  Lastly, center the window on screen and set default to the Open button.

import wx

class MainFrame(wx.Frame):
    def __init__(self, parent=None, id=(-1), title='kasio', size=(800,600)):
        wx.Frame.__init__(self, parent, id, title, size=size)
        self.panel = wx.Panel(self, wx.ID_ANY)

        textbox = wx.TextCtrl(self.panel, -1, style=wx.TE_MULTILINE)
        font = wx.Font(10, wx.FONTFAMILY_MODERN, wx.NORMAL, wx.NORMAL)
        textbox.SetFont(font)
        btnOpen = wx.Button(self.panel, -1, 'Open')
        btnSave = wx.Button(self.panel, -1, 'Save')
        btnFind = wx.Button(self.panel, -1, 'Find')
        btnExit = wx.Button(self.panel, -1, 'Exit')

        self.Bind(wx.EVT_BUTTON, self.onOpen, btnOpen)
        self.Bind(wx.EVT_BUTTON, self.onSave, btnSave)
        self.Bind(wx.EVT_BUTTON, self.onFind, btnFind)
        self.Bind(wx.EVT_BUTTON, self.onExit, btnExit)

        topSizer = wx.BoxSizer(wx.VERTICAL)
        btnSizer = wx.BoxSizer(wx.HORIZONTAL)

        btnSizer.Add(btnOpen, 0, wx.ALL, 1)
        btnSizer.Add(btnSave, 0, wx.ALL, 1)
        btnSizer.Add(btnFind, 0, wx.ALL, 1)
        btnSizer.Add(btnExit, 0, wx.ALL, 1)

        topSizer.Add(textbox, 1, wx.ALL|wx.EXPAND, 1)
        topSizer.Add(wx.StaticLine(self.panel), 0, wx.ALL|wx.EXPAND, 3)
        topSizer.Add(btnSizer, 0, wx.ALL|wx.ALIGN_RIGHT, 3)
      
        self.panel.SetSizer(topSizer)
        self.Centre() # center the frame in desktop
        btnOpen.SetDefault()

        self.filename = ''
        self.password = ''
        self.textbox = textbox

    def onOpen(self, event):
        pass

    def onSave(self, event):
        pass

    def onFind(self, event):
        pass

    def onExit(self, event):
        self.Close()

class MainApp(wx.App):
    def OnInit(self):
        frame = MainFrame()
        frame.Show(True)
        self.SetTopWindow(frame)
        return True

def main():
    app = MainApp()
    app.MainLoop()

if __name__=='__main__':
    main()

We will hook up the filecrypto module to the GUI and implement some of the buttons logic in the next post.

2011-10-17

File Encryption Utility

I would like to write a small utility in Python to manage some private and confidential information in text format, and save it to a file with encryption.

First I will need a utility class to:
1.  encrypt some plain text and save it to a disk file
2.  open the encrypted file and decrypt it into plaintext

With the help of the Python Cryptography Toolkit, I can do that easily:

# filecrypto.py

import os

from Crypto.Hash import SHA256 as HASH
from Crypto.Cipher import Blowfish as CIPHER

class FileCrypto(object):

    def _create_cipher(self, password):
        h = HASH.new()
        h.update(password)
        key = h.digest()
        h.update(key)
        iv = h.digest()[:8]
        return CIPHER.new(key, CIPHER.MODE_CFB, iv)

    def load(self, filename, password):
        'decrypt a file into a string of plain text'
        if not os.path.isfile(filename):
            return ""
        with open(filename, 'rb') as f:
            ciphertext = f.read()
        cipher = self._create_cipher(password)
        return cipher.decrypt(ciphertext)

    def save(self, filename, password, plaintext):
        'encrypt a string of plain text into a file'
        cipher = self._create_cipher(password)
        ciphertext = cipher.encrypt(plaintext)
        with open(filename, 'wb') as f:
            f.write(ciphertext)


I pick Blowfish as the encryption algorithm because it allows variable key size.  CFB (Cipher FeedBack) mode is used to that block size padding is not necessary.

For basic unit testing, I add the following to the module:

def unittest():
    import random
    import string

    # test 1 - encrypt 1024 printable chars
    filename = "".join( [random.choice(string.hexdigits) for i in range(8)] )
    password = "".join( [random.choice(string.printable) for i in range(256)] )
    message = "".join( [random.choice(string.printable) for i in range(1024)] )
    if os.path.isfile(filename):
        os.remove(filename)
    fc = FileCrypto()
    assert "" == fc.load(filename, password)
    fc.save(filename, password, message)
    assert message == fc.load(filename, password)
    fc = None
    if os.path.isfile(filename):
        os.remove(filename)

    # test 2 - encrypt byte values 0 to 255
    filename = "".join( [random.choice(string.hexdigits) for i in range(8)] )
    password = "".join( [random.choice(string.printable) for i in range(256)] )
    message = "".join( [chr(x) for x in range(256)] )
    if os.path.isfile(filename):
        os.remove(filename)
    fc = FileCrypto()
    assert "" == fc.load(filename, password)
    fc.save(filename, password, message)
    assert message == fc.load(filename, password)
    fc = None
    if os.path.isfile(filename):
        os.remove(filename)

if __name__ == '__main__':
    unittest()

In the next posting, I will build a GUI using this module to encrypt/decrypt a file.

2011-10-11

FAB call list

Here is a short SL4A Python script to pop up a list of names.  Clicking a name will call the number directly.

I use this script to keep track of the FAB numbers registered with my mobile carrier that I can call free of charge.  The best thing is that I can edit those numbers directly on my phone!


# fabcall.py

fab = [
     'My Friend #1: 111-111-1111',
     'My Friend #2: 222-222-222',
     'My Friend #3: 333-333-3333',
]

droid = android.Android()

if __name__ == '__main__':
    title = 'FAB'
    droid.dialogCreateAlert(title)
    droid.dialogSetItems([x.split(':')[0].strip() for x in fab])
    droid.dialogShow()
    result = droid.dialogGetResponse().result
    if result.has_key('item'):
        index = result['item']
        name, tel = fab[index].split(':')
        droid.makeToast('Dialing ' + name.strip())
        droid.phoneCallNumber(tel.strip())


2011-10-02

Deleting lines in Vim

QUESTION

In Vim, turn the following:

function add(x, y)
    return x + y
endfunc

function subtract(x, y)
    return x - y
endfunc

function multiple(x, y)
    return x * y
endfunc

into:

function add(x, y)
function subtract(x, y)
function multiple(x, y)

ANSWER

:g!/function/d

EXPLANATION

: - go into command mode
g - search the entire file
! - negate the following search pattern
/function/ - look for the word "function"
d - delete the line containing the selection