Kjetil's Information Center: A Blog About My Projects

Python Menu System

To fit my specific requirements for a media center PC, I made a simple custom menu system. I also used the opportunity to learn some Python in the process. Basically, it reads a textual configuration file and sets up a graphical menu using Tk. The menu entries will execute a specific command when clicked, and can be wrapped in a file or numeric selection mechanism, if required.

Sorry for not using objects in the code, and I will admit that it's ugly with global variables in Python. But I'm a C-guy, and old habits die hard. :-)

#!/usr/bin/python

import sys
import os
import Tkinter
import tkFileDialog



_num_value = 1
_num_label = None
_num_top = None



def read_config_file(path):
  try:
    fh = open(path, "r")
    list = []
    for s in fh:
      list.append(s.split("#"))
    return list
  except IOError:
    print "Error: Unable to open config file for reading!"
    return None



def menu_direct(command):
  print "Info: Direct execute: " + command
  os.system(command)



def num_increment():
  global _num_value, _num_label
  _num_value += 1
  _num_label.config(text=str(_num_value))

def num_decrement():
  global _num_value, _num_label
  _num_value -= 1
  _num_label.config(text=str(_num_value))

def num_exec(command):
  global _num_value, _num_top
  new = command.replace("$", str(_num_value))
  print "Info: Num execute: " + new
  os.system(new)
  _num_top.destroy()

def menu_num_select(command):
  global _num_top, _num_label, _num_value

  _num_top = Tkinter.Tk()

  label = Tkinter.Label(_num_top, text="Number:")
  label.pack(fill="x", expand=True)

  plus = Tkinter.Button(_num_top, text="+", command=num_increment)
  plus.pack(fill="x", expand=True)

  _num_label = Tkinter.Label(_num_top, text=str(_num_value))
  _num_label.pack(fill="x", expand=True)

  plus = Tkinter.Button(_num_top, text="-", command=num_decrement)
  plus.pack(fill="x", expand=True)

  f = lambda x = command: num_exec(x)
  go = Tkinter.Button(_num_top, text="GO!", command=f)
  go.pack(fill="x", expand=True)

  Tkinter.mainloop()
 


def menu_file_select(command):
  file = tkFileDialog.askopenfilename()
  if file == ():
    return
  new = command.replace("$", str(file))
  print "Info: File execute: " + new
  os.system(new)



def main():
  if len(sys.argv) != 2:
    print "Usage: %s <config file>" % sys.argv[0]
    sys.exit(1)

  list = read_config_file(sys.argv[1])
  if list == None:
    sys.exit(1)

  top = Tkinter.Tk()
  for sublist in list:
    if sublist[0] == "Direct":
      # NOTE: This does not work:
      # f = lambda: menu_direct(sublist[2])
      f = lambda x = sublist[2]: menu_direct(x)
    elif sublist[0] == "NumSelect":
      f = lambda x = sublist[2]: menu_num_select(x)
    elif sublist[0] == "FileSelect":
      f = lambda x = sublist[2]: menu_file_select(x)
    else:
      print "Error: Unknown menu type!"
      sys.exit(1)
    button = Tkinter.Button(top, text=sublist[1], command=f)
    button.pack(fill="x", expand=True)

  Tkinter.mainloop()



if __name__ == "__main__":
  main()
          


Here is a typical config file (not very unlike the one I currently use):

FileSelect#Play File#/usr/bin/xine -pfq "$"
NumSelect#Play DVD#/usr/local/bin/mplayer -fs -zoom dvd://$
Direct#Power Off#/sbin/poweroff
          


I used to use tabulators instead of '#' characters to separate the columns, but this really messes up any copy/paste/transfer, etc, so I just had to pick a character that is unlikely to be used in a command. Please just change the code if you don't like it. The '$' characters, are interpolated with the value from the file or numeric selection mechanism.

The final graphical representation of the menu will actually depend heavily on what version of Tk is installed, what window manager is used and font is used as default. Here is how it looks on one of my systems:

Python menu in fluxbox.


Topic: Scripts and Code, by Kjetil @ 03/05-2009, Article Link