#!/usr/local/bin/python2.7

"GUI interface for the :Cue:Cat scanner. xcuecat.py [config] to run."

import sys, os, cPickle

from Tkinter import *
from tkFileDialog import asksaveasfilename, askopenfilename
import Pmw

import cuecat, barcode

class Config:
    "Just a thing to hold the config information."

    def __init__(my, balloon = 1, beep = 1, command = "", filter = "None"):
        "Initialize my values, thank you."

        my.Balloon, my.Beep, my.Command = balloon, beep, command
        my.Filter = filter

class Scanner(Tk, cuecat.cuecat):
    "CueCat scanner dialog box."

    envName = "/usr/bin/env"

    def __init__(my, file = None):
        "Create the dialog box."

        # Create the window and it's components.
        Tk.__init__(my, baseName = ":Cue:Cat")
        my.messageBar = Pmw.MessageBar(my)
        my.messageBar.pack(fill = X, expand = 1, side = BOTTOM)
        my.balloonHelp = Pmw.Balloon(my,
                                     statuscommand = my.messageBar.helpmessage)
        bb = Pmw.ButtonBox(my)
        bb.pack(fill = X, expand = 1, side = BOTTOM)

        # Build the barcodes group
        g = Pmw.Group(my, tag_text = "Bar code")
        g.pack(fill = BOTH, expand = 1, padx = 5, pady = 5)
        i = g.interior()
        my.scanBox = Pmw.EntryField(i, command = my.display,
                                    labelpos = W, label_text = "Scan:")
        my.balloonHelp.bind(my.scanBox, "Scanner input",
                            "Activate and scan into this field.")
        my.codeBox = Pmw.EntryField(i, entry_state = DISABLED,
                                    labelpos = W, label_text = "Code:",
                                    entry_relief = RIDGE)
        my.balloonHelp.bind(my.codeBox, "Bar code display",
                            "Displays the bar code for the most recent scan.")
        my.typeBox = Pmw.EntryField(i, entry_state = DISABLED,
                                    labelpos = W, label_text = "Type:",
                                    entry_relief = RIDGE)
        my.balloonHelp.bind(my.typeBox, "Type display",
                      "Displays the type of the most recent bar code scanned.")

        widgets = (my.scanBox, my.codeBox, my.typeBox)
        for w in widgets:
            w.pack(fill = X, expand = 1)
        Pmw.alignlabels(widgets)

        # Build the Code group
        g = Pmw.Group(my, tag_text = "Filter")
        g.pack(fill = BOTH, expand = 1, padx = 5, pady = 5)
        my.filterType = Pmw.RadioSelect(g.interior(), buttontype = RADIOBUTTON)
        my.filterType.pack(fill = X, expand = 1)
        my.balloonHelp.bind(my.filterType.add("None"), "Use all codes",
                            "All bar codes are acted on as EAN13.")
        my.balloonHelp.bind(my.filterType.add("ISBN"), "Use book numbers",
                       "Only International Serial Book Numbers are acted on.")
        my.balloonHelp.bind(my.filterType.add("UPC"), "Use product codes",
                            "Only Universal Product Codes are acted on.")
        my.filterType.invoke(0)

        # Build the State group
        g = Pmw.Group(my, tag_text = "State")
        g.pack(fill = BOTH, expand = 1, padx = 5, pady = 5)
        i = g.interior()
        f = Frame(i)
        f.pack(fill = X, expand = 1)
        my.beep = IntVar()
        b = Checkbutton(f, text = "Beep after scan", variable = my.beep)
        b.pack(side = LEFT)
        my.balloonHelp.bind(b, None, "Beep after a successful scan.")
        my.balloon = IntVar()
        b = Checkbutton(f, text = "Balloon help", variable = my.balloon,
                        command = my.setBalloon)
        b.pack(side = RIGHT)
        my.balloonHelp.bind(b, None, "Display balloon help.")
        my.commandBox = Pmw.EntryField(i, labelpos = W, label_text = "Command",
                                       entry_width = 60)
        my.balloonHelp.bind(my.commandBox, "Command to execute",
                            "Command to execute after a successful scan.")
        my.commandBox.pack(fill = X, expand = 1)

        # Add the command buttons.
        my.balloonHelp.bind(bb.add("Load", command = my.Load),
                            "Load configuration",
                            "Load a new configuration file.")
        my.balloonHelp.bind(bb.add("Save", command = my.Save),
                            "Save configuration",
                            "Save the current configuration to a file.")
        my.balloonHelp.bind(bb.add("Exit", command = my.Exit), "Save and Exit",
                        "Save the current configuration to the file and exit.")

        my.scanBox.component('entry').focus_set()

        # Now, deal with the configuration file
        my.File = file
        my.config(my.loadConfig())

    def display(my):
        "Read the value from the scanner, and udpate the dispaly for it."

        # Translate the raw ascii
        try: my.Scan(ascii = my.scanBox.get())
        except ValueError: return Pmw.PARTIAL
        type = my.Type()

        # apply filters.
        filter = my.filterType.getcurselection()
        if filter == "ISBN":
            if type[:2] == 'IB': bar = barcode.ISBN
            else: bar = None
        elif filter == "UPC":
            if type[:2] == 'UP': bar = barcode.UPC
            else: bar = None
        else:
            bar = barcode.EAN13

        # Convert to a bar code
        if not bar:
            my.messageBar.message('userevent', "Bar code filtered.")
            code = None
        else:
            try:
                code = bar(my.Code()).Code
            except ValueError, report:
                my.messageBar.message('userevent', report)
                code = None

        # On errors, clear the world.
        my.scanBox.clear()
        if not code:
            my.codeBox.clear()
            my.typeBox.clear()
            my.bar = None
            return Pmw.ERROR
            
        # If ok, set the output and run the actions.
        my.codeBox.setentry(code)
        my.codeBox.selection_range(0, END)
        my.typeBox.setentry(type)
        if my.beep.get(): my.messageBar.message('usererror', "Scanned.")
        else: my.messageBar.message('userevent', "Scanned.")
        command = my.commandBox.get()
        if command: os.system(command % code)
        return Pmw.OK

    def setBalloon(my):
        "Enable or disable balloon help, based on the state of the checkbox."

        my.balloonHelp['state'] = ('status', 'both')[my.balloon.get()]

    # Configuration management.
    def config(my, config):
        "Configure the world as specified."

        my.beep.set(config.Beep)
        my.commandBox.setentry(config.Command)
        my.filterType.invoke(config.Filter)
        my.balloon.set(config.Balloon)
        my.setBalloon()

    def saveConfig(my, name = None):
        "Save my configuration information to a file."

        if not name: name = my.File
        if name:
            file = open(name, 'w')
            file.write("#!%s %s\n" % (my.envName,
                                      os.path.basename(sys.argv[0])))
            cPickle.dump(Config(beep = my.beep.get(),
                                balloon = my.balloon.get(),
                                command = my.commandBox.get(),
                                filter = my.filterType.getcurselection()),
                         file)
            file.close()
            my.messageBar.message('userevent', "File %s saved." % name)
            os.chmod(name, 0755)

    def loadConfig(my, name = None):
        "Load my configuration information to a file."

        if not name: name = my.File
        if not name:
            out = Config()
        else:
            try:
                file = open(name, 'r')
            except:
                out = Config()
            else:
                file.readline()		# Skip the #! line
                out = cPickle.load(file)
                my.messageBar.message('userevent', "File %s loaded." % name)
                file.close()
        return out
        

    # Button actions
    def Load(my):
        "Load my state."

        dir, file = os.path.split(my.File)
        load = askopenfilename(initialfile = file, initialdir = dir)
        if file:
            my.config(my.loadConfig(load))
            my.File = load

    def Save(my):
        "Save my state."

        dir, file = os.path.split(my.File)
        saveto = asksaveasfilename(initialfile = file, initialdir = dir)
        if saveto:
            my.saveConfig(saveto)
            my.File = saveto


    def Exit(my):
        "Save my state, then exit."

        my.saveConfig()
        my.destroy()


if __name__ == "__main__":
    file = None
    if len(sys.argv) == 1:
        if os.environ.has_key('HOME'):
            file = os.path.join(os.environ['HOME'], '.xcuecat')
    elif len(sys.argv) == 2:
        file = sys.argv[1]
    else:
        sys.stderr.write("usage: %s [file]\n" % sys.argv[0])
        sys.exit()

    Scanner(file).mainloop()
