#! /usr/bin/env python
# todiscgui

"""A Tkinter graphical user interface for todisc.

Each Frame class has a grouping of todisc options in tkinter widget form.
Frame subclasses define a getOptions method that appends command-line
options to a Command, raising exceptions if required options are missing.
"""

import os
import time
import re
from libtovid.cli import Command
from libtovid.gui.meta import *
from libtovid import log

try:
    from Tkinter import *
    from tkFileDialog import *
    from tkMessageBox import *
except:
    import traceback
    traceback.print_exc()
    print "Could not import Tkinter. You may need to do the following:"
    print "  Debian: Install 'python-tk'"
    print "  Gentoo: Add 'tk' to python USE flags"
    print "  Fedora: Install 'tkinter'"
    sys.exit()


WIDGETS = {
    'showcase':
        (Filename, 'Showcase', 'load', 'Select an image or video file.'),
    'background':
        (Filename, 'Background', 'load', 'Select an image or video file'),
    'menu-font':
        (Optional, Font, 'Menu title font'),
    'menu-fontsize':
        (Optional, Number, 'Menu title font size', 0, 80, 'scale'),
    'thumb-fontsize':
        (Optional, Number, 'Video title(s) font size', 0, 80, 'scale'),
    'menu-fade':
        (Flag, 'Fade in menu', False),
    'thumb-shape':
        (Optional, Choice, 'Thumb shape', 'normal|oval|plectrum|egg'),
    'thumb-font':
        (Optional, Font, 'Video title(s) font'),
    'submenu-stroke-color':
        (Optional, Color, 'Submenu stroke color'),
    'submenu-title-color':
        (Optional, Color, 'Submenu title color'),
    'submenu-titles':
        (PlainLabel, 'Note: for submenu titles option please use custom box on main pane'),
    'button-style':
        (Optional, Choice, 'Button style', 'rect|text|text-rect'),
    'rotate':
        (Optional, Number, 'Rotate Showcase thumb', -30, 30, 'spin', 5),
    'rotate-thumbs':
        (Optional, ListEntry, 'Rotate Thumbs (list)'),
    'showcase-framestyle':
        (Optional, Choice, 'Showcase frame style', 'none|glass'),
    'title-color':
        (Optional, Color, 'Title color'),
    'stroke-color':
        (Optional, Color, 'Stroke color'),
    'highlight-color':
        (Optional, Color, 'Highlight color'),
    'select-color':
        (Optional, Color, 'Selection color'),
    'text-mist':
        (Flag, 'Text mist', False),
    'text-mist-color':
        (Optional, Color, 'Text mist color'),
    'text-mist-opacity':
        (Optional, Number, 'Text mist opacity', 1, 100, 'spin', 60),
    'opacity':
        (Optional, Number, 'Thumbnail opacity', 1, 100, 'spin', 100),
    'blur':
        (Optional, Number, 'Blur', 1, 5, 'spin', 4),
    'thumb-mist-color':
        (Optional, Color, 'Thumb mist color [white]'),
    'thumb-mist-color-info':
        (PlainLabel, 'Use "-thumb-mist-color none" in the custom box for no mist'),
    'thumb-text-color':
        (Optional, Color, 'Video title(s) color'),
    'showcase-titles-align':
        (Optional, Choice, 'Video(s) title alignment', 'west|east|center'),
    'menu-title-geo':
        (Optional, Choice, 'Menu title position', 'north|south|west|east|center'),
    'menu-title-offset':
        (Optional, LabelEntry, 'Offset for menu title position', '+0+0'),
    'align':
        (Optional, Choice, 'Montage alignment', 'north|south|east|west'),
    'bgaudio':
        (Filename, 'Audio', 'load', 'Select an audio file'),
    'menu-audio-length':
        (Optional, Number, 'Menu audio length', 0, 120, 'scale'),
    'menu-audio-fade':
        (Optional, Number, 'Menu audio fade', 0, 10, 'scale', 1),
    'submenu-audio':
        (Optional, Filename, 'Submenu audio file', 'load', 'Select an audio file, or video file with audio'),
    'submenu-audio-info':
        (PlainLabel, 'Note: for multiple -submenu-audio files please use custom box on main pane'),
    'submenu-audio-length':
        (Optional, Number, 'Submenu audio length', 0, 120, 'scale'),
    'submenu-audio-fade':
        (Optional, Number, 'Submenu audio fade', 0, 10, 'scale', 1),
    'submenus':
        (Flag, 'Create submenus'),
    'ani-submenus':
        (Flag, 'Animated submenus (takes more time)'),
    'static':
        (Flag, 'Static menus (takes less time)'),
    '3dthumbs':
        (Flag, 'Create 3D thumbs'),
    'tile3x1':
        (Flag, 'Arrange thumb montage in 1 row of 3 thumbs'),
    'wave':
        (Optional, LabelEntry, 'Wave effect for showcase thumb', 'default'),
    'menu-title':
        (LabelEntry, 'Menu title'),
    'menu-length':
        (Number, 'Menu length', 0, 120, 'scale'),
    'intro':
        (Filename, 'Intro video', 'load', 'Select a video file'),
    'seek':
        (Optional, ListEntry, 'Seek time', 'Single value or list'),
    'bgaudio-seek':
        (Optional, Number, 'Background audio seek time', 0, 3600, 'scale', 2),
    'bgvideo-seek':
        (Optional, Number, 'Background video seek time', 0, 3600, 'scale', 2),
    'showcase-seek':
        (Optional, Number, 'Showcase video seek time', 0, 3600, 'scale', 2),
    'showcase-geo':
        (Optional, LabelEntry, 'Showcase image position (XxY'),
    'text-start':
        (Optional, Number, 'Start Textmenu titles at: (pixels)', 0, 460, 'spin', 2),
    'title-gap':
        (Optional, Number, 'Space between Textmenu titles (pixels)', 0, 400, 'spin', 2),
    'loop':
        (Optional, Number, 'Loop', 0, 30, 'scale'),
    'playall':
        (Flag, '"Play all" button', False),
    'chapters':
        (Optional, ListEntry, 'Number of Chapters', 'Single value or list'),
    'chain-videos':
        (Optional, ListEntry, 'Chain videos together', ' See "man todisc" for details'),
    'group':
        (PlainLabel, 'Note: for -group option please use custom box on main pane'),
    'audio-lang':
        (Optional, ListEntry, 'Default audio language', "Single value or list"),
    'subtitles':
        (Optional, ListEntry, 'Default subtitle language', "Single value or list"),
    'outlinewidth':
        (Optional, Number, 'Outlinewidth for spumux buttons', 0, 20, 'scale'),
    'aspect':
        (Optional, Choice, 'Aspect ratio', '4:3|16:9'),
    'widescreen':
        (Optional, Choice, 'Widescreen', 'nopanscan|noletterbox'),
    'tovidopts':
        (Optional, LabelEntry, 'Custom tovid options'),
    'use-makemenu':
        (Flag, 'Use makemenu script instead of todisc, and exit', False),
    'no-ask':
        (Flag, 'No prompts for questions', False),
    'no-warn':
        (Flag, 'Do not pause at warnings', False),
    'keep-files':
        (Flag, 'Keep useful intermediate files on exit', False),
    'showcaseonlylabel':
        (PlainLabel, 'The following options apply to Showcase style only'),
    'defaultonlylabel':
        (PlainLabel, 'The following options apply to the Default style only'),
    'textmenuonlylabel':
        (PlainLabel, 'The following options apply to the Textmenu style only'),
    'showcaseOrTextmenulabel':
        (PlainLabel, 'The following options apply to Showcase and Textmenu style only'),
    'spacer':
        (PlainLabel, '')
    }

"""
Not yet fitted into above dictionary:

textmenu
files
titles
submneu-titles
"""

#[Font style options]
text_options = [
    'spacer',
    'menu-font',
    'thumb-font',
    'menu-fontsize',
    'thumb-fontsize',
    'title-color',
    'submenu-title-color',
    'thumb-text-color',
    'text-mist',
    'text-mist-color',
    'text-mist-opacity',
    'menu-title-geo',
    'menu-title-offset',
    'stroke-color',
    'submenu-stroke-color',
    'spacer',
    'textmenuonlylabel',
    'title-gap',
    'text-start']

#[Thumb style options]
thumb_options = [
    'spacer',
    '3dthumbs',
    'thumb-shape',
    'opacity',
    'blur',
    'rotate-thumbs',
    'spacer',
    'showcaseonlylabel',
    'wave',
    'rotate',
    'spacer',
    'defaultonlylabel',
    'thumb-mist-color',
    'thumb-mist-color-info',
    'tile3x1']

#[Dvdauthor options]
author_options = [
    'spacer',
    'chapters',
    'chain-videos',
    'widescreen',
    'aspect',
    'highlight-color',
    'select-color',
    'button-style',
    'audio-lang',
    'subtitles',
    'outlinewidth',
    'loop',
    'playall',
    'spacer',
    'group']

# not fit in yet ( dvdauthor options )
#    'subtitles',  # ???
#    'audio-lang', # ???

#[Menu style options]
menu_options = [
    'spacer',
    'ani-submenus',
    'menu-fade',
    'seek',
    'bgvideo-seek',
    'bgaudio-seek',
    'showcase-seek',
    'align',
    'intro',
    'spacer',
    'showcaseOrTextmenulabel',
    'showcase-titles-align',
    'spacer',
    'showcaseonlylabel',
    'showcase-framestyle',
    'showcase-geo',
    'spacer',
    'submenu-titles']

#[Audio Options]
audio_options = [
    'menu-audio-length',
    'menu-audio-fade',
    'submenu-audio',
    'submenu-audio-length',
    'submenu-audio-fade',
    'submenu-audio-info']

#[General Options]
general_options = [
    'spacer',
    'keep-files',
    'no-ask',
    'no-warn',
    'use-makemenu',
    'tovidopts']

group_options = ""

### --------------------------------------------------------------------
### Exceptions
### --------------------------------------------------------------------

class MissingOption (Exception):
    """Raised when a required command-line option was not specified.

        message: Brief description of the missing option
        widget: A tkinter Widget where the option can be set
    """
    def __init__(self, message, widget=None):
        self.message = message
        self.widget = widget


### --------------------------------------------------------------------
### Helper functions
### --------------------------------------------------------------------

def getWidget(master, option):
    """Create a widget with the given master, appropriate for the given option.
    Return the new widget.
    """
    if option not in WIDGETS:
        raise NotImplementedError("No widget found for option: %s" % option)
    widget = WIDGETS[option][0]
    args = WIDGETS[option][1:]
    return widget(master, *args)

def pretty_todisc(command):
    """Return a prettified version of a given todisc Command."""
    assert isinstance(command, Command)
    result = ['%s' % command.args[0]]
    opts = command.args[1:]
    while opts:
        arg = str(opts.pop(0))
        if arg.startswith('-') and not re.match('^[-+]?[0-9]+$', arg):
            result.append(arg)
        else:
            result[-1] += ' ' + arg
    return '\n'.join(result)

def blink(widget):
    """Cause a widget to "blink" by briefly changing its background color.
    """
    if widget == None:
        return
    assert isinstance(widget, Widget)
    widget.config(background='#C0C0F0')
    widget.update()
    time.sleep(1)
    widget.config(background='white')


### --------------------------------------------------------------------
### Frames containing related control widgets
### --------------------------------------------------------------------

class MenuChoice (Frame):
    """A frame for setting which Menu template will be used"""
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.style = StringVar()     # Menu arrangement variable
        self.drawWidgets()

    def drawWidgets(self):
        group = LabelFrame(self, text="Menu arrangement")
        # TODO: Disable showcase file widget for default style
        self.showcase = {}
        self.showcase['default'] = \
            Radiobutton(group, text="Default style", value="default",
                        variable=self.style, command=self.changeStyle)
        self.showcase['showcase'] = \
            Radiobutton(group, text="Showcase style", value="showcase",
                        variable=self.style, command=self.changeStyle)
        self.showcase['textmenu'] = \
            Radiobutton(group, text="Textmenu style", value="textmenu",
                        variable=self.style, command=self.changeStyle)
        for style in self.showcase:
            self.showcase[style].pack(anchor=W)

        self.filename = Filename(group, "Showcase file", 'load',
                                "Select an image or video file.")
        self.filename.pack(anchor=W, fill=X, expand=YES)

        self.colsize = Number(group, "Textmenu column size", 2, 15)
        self.colsize.set(15)
        self.colsize.pack(anchor=W)

        self.style.set('default')
        self.changeStyle()

        group.pack(fill=X, expand=YES)

    def changeStyle(self):
        """Change showcase styles, and enable suitable widgets."""
        style = self.style.get()
        if style == 'default':
            self.filename.disable()
            self.colsize.disable()
        else:
            self.filename.enable()
            if style == 'textmenu':
                self.colsize.enable()
            else:
                self.colsize.disable()

    def setOptions(self, command):
        """Add relevant todisc options to the given Command."""
        style = self.style.get()
        filename = self.filename.get()
        colsize = self.colsize.get()
        if colsize == 15:
            colsize = ''
        if style in ['showcase', 'textmenu']:
            if filename:
                command.add('-showcase', filename)
            else:
                if style == 'showcase':
                    command.add('-showcase')
        if style == "textmenu":
            command.add('-textmenu', colsize)


class Format (Frame):
    """A frame with a choice of formats (DVD, SVCD)
    """
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.format = Choice(self, "Disc format", ['DVD', 'SVCD'])
        self.format.pack()
        
    def setOptions(self, command):
        """Add relevant todisc options to the given Command."""
        format = str(self.format.get()).lower()
        command.add('-%s' % format)

### --------------------------------------------------------------------

class Tvsys (Frame):
    """A frame with a choice of TV system (PAL, NTSC)
    """
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.tvsys = Choice(self, "TV system", ['NTSC', 'PAL'])
        self.tvsys.pack()

    def setOptions(self, command):
        tvsys = str(self.tvsys.get()).lower()
        command.add('-%s' % tvsys)
    
### --------------------------------------------------------------------

class FileList (Frame):
    """A frame containing a list of filenames, and controls to add or delete
    files from the list.
    """
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.curtitle = StringVar()  # Text of the currently selected title
        self.varFiles = Variable()   # List of current files
        self.varTitles = Variable()  # List of current titles
        self.varUsage = StringVar()  # String describing current space usage
        self.curindex = 0
        self.drawWidgets()

    def drawWidgets(self):
        """Draw all the widgets in this frame."""
        # Scrollbar to control both listboxes
        self.scrollbar = Scrollbar(self, orient=VERTICAL)
        self.scrollbar.grid(row=1, column=3, sticky=N+S)
        self.scrollbar.config(command=self.scroll)
        # File list box and add/remove buttons
        self.lblFiles = Label(self, text="Files")
        self.lblFiles.grid(row=0, column=0, columnspan=2, sticky=W)
        self.lstFiles = Listbox(self, width=30,
                                listvariable=self.varFiles,
                                yscrollcommand=self.scrollbar.set)
        self.lstFiles.bind('<Button-1>', self.selectListitem)
        self.lstFiles.bind('<B1-Motion>', self.dragListitem)
        self.lstFiles.bind('<ButtonRelease-1>', self.onDrop)
        self.lstFiles.grid(row=1, column=0, columnspan=2)
        self.btnAdd = Button(self, text="Add...", command=self.addFiles)
        self.btnAdd.grid(row=2, column=0, sticky=E+W)
        self.btnRemove = Button(self, text="Remove",
                                   command=self.removeFiles)
        self.btnRemove.grid(row=2, column=1, sticky=E+W)
        # Title list box and editing field
        self.lblTitles = Label(self, text="Titles")
        self.lblTitles.grid(row=0, column=2, sticky=W)
        self.lstTitles = Listbox(self, width=30,
                                 listvariable=self.varTitles,
                                 yscrollcommand=self.scrollbar.set)
        self.lstTitles.bind('<Button-1>', self.selectListitem)
        self.lstTitles.bind('<B1-Motion>', self.dragListitem)
        self.lstTitles.bind('<ButtonRelease-1>', self.onDrop)
        self.lstTitles.grid(row=1, column=2)
        self.entTitle = Entry(self, width=30,
                              textvariable=self.curtitle)
        self.entTitle.bind('<Return>', self.setTitle)
        self.entTitle.grid(row=2, column=2)
        # Disc usage total
        self.lblUsage = Label(self, textvariable=self.varUsage)
        self.lblUsage.grid(row=0, column=1, columnspan=2, sticky=E)
        self.updateUsage()

    def scroll(self, *args):
        """Event handler when scrollbar is moved."""
        apply(self.lstFiles.yview, args)
        apply(self.lstTitles.yview, args)
        
    def selectListitem(self, event):
        """Event handler when a filename or title in the list is selected.
        Set the title box for editing and change the mouse cursor."""
        self.curindex = self.lstFiles.nearest(event.y)
        self.curtitle.set(self.lstTitles.get(self.curindex))
        self.config(cursor="double_arrow")
    
    def onDrop(self, event):
        """Event handler called when an item is "dropped" (mouse-release).
        Change the mouse cursor back to the default arrow.
        """
        self.config(cursor="")
    
    def dragListitem(self, event):
        """Event handler to move a file/title to another position in the list"""
        loc = self.lstFiles.nearest(event.y)
        if loc != self.curindex:
            file = self.lstFiles.get(self.curindex)
            title = self.lstTitles.get(self.curindex)
            self.lstFiles.delete(self.curindex)
            self.lstTitles.delete(self.curindex)
            self.lstFiles.insert(loc, file)
            self.lstTitles.insert(loc, title)
            self.curindex = loc

    def setTitle(self, event):
        """Event handler when Enter is pressed after editing a title."""
        newtitle = self.curtitle.get()
        log.debug("Setting title to '%s'" % newtitle)
        self.lstTitles.delete(self.curindex)
        self.lstTitles.insert(self.curindex, newtitle)
        
    def addFiles(self):
        """Event handler for adding files to the list box"""
        files = askopenfilenames(parent=self, title='Add files')
        for file in files:
            log.debug("Adding '%s' to the file list" % file)
            self.lstFiles.insert(END, file)
            # Add a dummy title (with pathname and extension removed)
            title = os.path.basename(file)[0:-4]
            self.lstTitles.insert(END, title)
        self.updateUsage()

    def getUsage(self):
        """Return the total size, in bytes, consumed by the current list
        of files."""
        total = 0
        for file in self.varFiles.get():
            total += os.path.getsize(file)
        return total
    
    def updateUsage(self):
        """Update the disc space usage label."""
        usage = self.getUsage() / (1024 * 1024)
        self.varUsage.set("%s MB used" % usage)

    def removeFiles(self):
        """Event handler for removing files from the list box"""
        selection = self.lstFiles.curselection() \
                  or self.lstTitles.curselection()
        # Using reverse order prevents reflow from messing up indexing
        for line in reversed(selection):
            log.debug("Removing '%s' from the file list" %\
                      self.lstFiles.get(line))
            self.lstFiles.delete(line)
            self.lstTitles.delete(line)
        self.updateUsage()
    
    def setOptions(self, command):
        """Add relevant todisc options to the given Command."""
        files = self.varFiles.get()
        titles = self.varTitles.get()
        if len(files) != len(titles):
            # Should never happen, if the listboxes are properly in sync
            raise Exception, "Number of files and titles do not match"
        if len(files) == 0:
            raise MissingOption("File list (-files)", self.lstFiles)
        if len(titles) == 0:
            raise MissingOption("Title list (-titles)", self.lstTitles)
        command.add('-files', *files)
        command.add('-titles', *titles)

### --------------------------------------------------------------------

class Backgrounds (Frame):
    """Widgets for setting background image or video and audio"""
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.drawWidgets()

    def drawWidgets(self):
        """Draw all the widgets in this frame."""
        label = Label(self, text="Menu backgrounds")
        label.pack(anchor=W)
        self.visual = Filename(self, "Image or video", 'load',
                                "Select an image or video file")
        self.audio = Filename(self, "Audio", 'load',
                               "Select an audio file")
        self.visual.pack(anchor=E, fill=X, expand=YES)
        self.audio.pack(anchor=E, fill=X, expand=YES)
    
    def setOptions(self, command):
        """Add relevant todisc options to the given Command."""
        visual = self.visual.get()
        audio = self.audio.get()
        if visual:
            command.add('-background', visual)
        if audio:
            command.add('-bgaudio', audio)

### --------------------------------------------------------------------

class Outfile (Frame):
    """A frame containing an output-file text entry/browser box."""
    def __init__(self, master=None):
        Frame.__init__(self, master, relief=GROOVE)
        self.drawWidgets()
    
    def drawWidgets(self):
        """Draw all the widgets in this frame."""
        self.outfile = Filename(self, "Output file", 'save',
                                 "Select an output name.")
        self.outfile.pack(side=LEFT, pady=2, fill=X, expand=YES)

    def setOptions(self, command):
        """Add relevant todisc options to the given Command."""
        outfile = self.outfile.get()
        if not outfile:
            log.warning("No output file selected")
            raise MissingOption("Output filename (-out)", self.outfile.entry)
        command.add('-out', outfile)

### --------------------------------------------------------------------

class Title (Frame):
    """A frame containing options for customizing titles"""
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.drawWidgets()
        
    def drawWidgets(self):
        """Draw all the widgets in this frame."""
        self.title = LabelEntry(self, "Menu title")
        self.title.pack(side=RIGHT, pady=4, fill=X, expand=YES)
        self.title.set("My video collection")

    def setOptions(self, command):
        """Add relevant todisc options to the given Command."""
        title = self.title.get()
        if title:
            command.add('-menu-title', title)
        else:
            command.add('-menu-title', ' ')

### --------------------------------------------------------------------

class MiscOptions (Frame):
    """A frame containing miscellaneous options"""
    # TODO: Categorize these as needed and separate into new Frame subclasses
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.drawWidgets()
    
    def drawWidgets(self):
        """Draw all the widgets in this frame."""
        self.static = Flag(self, "Static menus (takes less time)", False)
        self.submenus = Flag(self, "Create submenus (takes more time)", False)
        self.custom = LabelEntry(self, "Custom todisc options")
        self.static.pack(anchor=W)
        self.submenus.pack(anchor=W)
        self.custom.pack(anchor=W, fill=X, expand=YES)

    def setOptions(self, command):
        """Add relevant todisc options to the given Command."""
        if self.static.get():
            command.add('-static')
        if self.submenus.get():
            command.add('-submenus')
        # Hack alert: Splitting on ' ' may not work in some cases
        if self.custom.get():
            options = self.custom.get().split(' ')
            for opt in options:
                command.add(opt)

### --------------------------------------------------------------------

class Advanced (OptionFrame):
    """A frame containing controls for setting advanced options.
    """
    def __init__(self, master=None, options="", label_text="Advanced Options"):
        Frame.__init__(self, master, borderwidth=5)
        self.update_idletasks()
        group = LabelFrame(self, text=label_text, padx=8, pady=8,
                           width=root.winfo_width(),
                           height=root.winfo_height())
        group.pack(ipadx=6, ipady=6)
        group.pack_propagate(0)
        self.widgets = {}
        for option in options:
            self.widgets[option] = getWidget(group, option)
            self.widgets[option].pack(anchor=NW, pady=1, fill=X)
        spacer = Label(group, text='', width=65)
        spacer.pack(fill=BOTH, expand=YES)

    def setOptions(self, command):
        """Add relevant todisc options to the given Command."""
        for option, widget in self.widgets.items():
            value = widget.get()
            if value == True:
                command.add('-%s' % option)
            elif type(value) == list:
                command.add('-%s' % option, *value)
            elif value:
                command.add('-%s' % option, value)

### --------------------------------------------------------------------
### Main application window
### --------------------------------------------------------------------

class Application (Frame):
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.AdvThumbShown=False
        self.AdvMenuShown=False
        self.AdvDvdShown=False
        self.AdvFontShown=False
        self.AdvAudioShown=False
        self.AdvGeneralShown=False
        self.advanced_visible = False
        self.pack()
        self.drawWidgets()

    def drawWidgets(self):
        """Draw all the widgets in this frame."""
        group = Frame(self)
        self.title = Title(group)
        self.files = FileList(group)
        self.format = Format(group)
        self.tvsys = Tvsys(group)
        self.menuformat = MenuChoice(group)
        self.backgrounds = Backgrounds(group)
        self.misc = MiscOptions(group)
        self.outfile = Outfile(group)
        group.pack(side=LEFT, padx=6, pady=6)
        # Pack widgets in main frame
        self.title.pack(fill=X, expand=YES)
        self.files.pack(anchor=W)
        self.format.pack(anchor=W)
        self.tvsys.pack(anchor=W)
        self.menuformat.pack(anchor=W, fill=X, expand=YES)
        self.backgrounds.pack(anchor=W, fill=X, expand=YES)
        self.misc.pack(anchor=W, fill=X, expand=YES)
        self.outfile.pack(anchor=W, fill=X, expand=YES)
        # Add the menu
        self.makeMenu()

        # Create advanced frames but don't pack them yet
        self.fraAdvMenu = Advanced(self, menu_options,
                                   "Advanced Menu Options")
        self.fraAdvThumb = Advanced(self, thumb_options,
                                    "Advanced Thumb Options")
        self.fraAdvAudio = Advanced(self, audio_options,
                                    "Advanced Audio Options")
        self.fraAdvFont = Advanced(self, text_options,
                                   "Advanced text and font Options")
        self.fraAdvDvd = Advanced(self, author_options,
                                  "Advanced Dvdauthor and Spumux Options")
        self.fraAdvGeneral = Advanced(self, general_options,
                                      "Advanced General Options")

    def showitem(self, widget_list, _label):
        """show frame, and set vars so we know it is shown, hide previously
        shown frame if exists
        """
        if _label == "menu":
            self.hideAdvMenu(_label)
            self.fraAdvMenu.pack(anchor=NW, side=LEFT)
            self.AdvMenuShown=True
        elif _label == "thumb":                
            self.hideAdvMenu(_label)
            self.fraAdvThumb.pack(anchor=NW, side=LEFT)
            self.AdvThumbShown=True
        elif _label == "audio":                
            self.hideAdvMenu(_label)
            self.fraAdvAudio.pack(anchor=NW, side=LEFT)
            self.AdvAudioShown=True
        elif _label == "font":                
            self.hideAdvMenu(_label)
            self.fraAdvFont.pack(anchor=NW, side=LEFT)
            self.AdvFontShown=True
        elif _label == "dvd":                
            self.hideAdvMenu(_label)
            self.fraAdvDvd.pack(anchor=NW, side=LEFT)
            self.AdvDvdShown=True
        elif _label == "general":                
            self.hideAdvMenu(_label)
            self.fraAdvGeneral.pack(anchor=NW, side=LEFT)
            self.AdvGeneralShown=True
        elif _label == "hide":                
            self.hideAdvMenu(_label)

    def makeMenu(self):
        # create a menubar
        menubar = Menu(self)
        # create a pulldown menu, and add it to the menu bar
        filemenu = Menu(menubar, tearoff=0)
        filemenu.add_separator()
        filemenu.add_command(label="Exit", command=root.quit)
        menubar.add_cascade(label="File", menu=filemenu)

        # create more pulldown menus
        editmenu = Menu(menubar, tearoff=1)
        
        editmenu.add_separator()
        editmenu.add_command(label="Menu style options",
                             command=lambda:self.showitem(menu_options, "menu"))
        editmenu.add_command(label="Thumb style options",
                             command=lambda:self.showitem(thumb_options, "thumb"))
        editmenu.add_command(label="Text and font options",
                             command=lambda:self.showitem(text_options, "font"))
        editmenu.add_command(label="Audio options",
                             command=lambda:self.showitem(audio_options, "audio")) 
        editmenu.add_command(label="Dvdauthor options",
                             command=lambda:self.showitem(author_options, "dvd"))
        editmenu.add_command(label="General options",
                             command=lambda:self.showitem(general_options, "general"))
        editmenu.add_separator()
        editmenu.add_command(label="Hide advanced pane",
                             command=lambda:self.showitem("", "hide"))
        menubar.add_cascade(label="Advanced", menu=editmenu)

        runmenu = Menu(menubar, tearoff=0)
        runmenu.add_command(label="Run todisc now !", command=self.runCommand)
        menubar.add_cascade(label="Run", menu=runmenu)
        root.config(menu=menubar)
#        editmenu.entryconfig(1, state=DISABLED)

    def hideAdvMenu(self, _label):
        if self.AdvMenuShown:
            self.fraAdvMenu.pack_forget()
            self.AdvMenuShown=False
        if self.AdvThumbShown:
            self.fraAdvThumb.pack_forget()
            self.AdvThumbShown=False
        if self.AdvAudioShown:
            self.fraAdvAudio.pack_forget()
            self.AdvAudioShown=False
        if self.AdvFontShown:
            self.fraAdvFont.pack_forget()
            self.AdvFontShown=False
        if self.AdvDvdShown:
            self.fraAdvDvd.pack_forget()
            self.AdvDvdShown=False
        if self.AdvGeneralShown:
            self.fraAdvGeneral.pack_forget()
            self.AdvGeneralShown=False

    def getCommand(self):
        """Return the complete todisc command."""
        cmd = Command('todisc')
        frames = [
            self.title,
            self.files,
            self.format,
            self.tvsys,
            self.menuformat,
            self.backgrounds,
            self.misc,
            self.outfile,
            self.fraAdvThumb,
            self.fraAdvFont,
            self.fraAdvAudio,
            self.fraAdvDvd,
            self.fraAdvGeneral,
            self.fraAdvMenu]
        for frame in frames:
            try:
                frame.setOptions(cmd)
            except MissingOption, err:
                log.error("Missing a required option: %s" % err.message)
                raise
        return cmd

    def runCommand(self):
        """Run the todisc command."""
        try:
            cmd = self.getCommand()
        except MissingOption, err:
            showerror("Missing option",
                      "Missing a required option: %s" % err.message)
            blink(err.widget)
            return
        # Show pretty-printed command
        pretty_cmd = pretty_todisc(cmd)
        log.info("Running command:")
        log.info(pretty_cmd)
        # Verify with user
        if askyesno(message="Run todisc now?"):
            root.withdraw()
            try:
                cmd.run()
            except KeyboardInterrupt:
                showerror(message="todisc was interrupted!")
            else:
                showinfo(message="todisc finished running!")
            root.deiconify()


def setTheme(theme='default'):
    """Set widget styles to match a theme. This must be called before
    widgets are created, and does not apply to widgets already created.
    """
    if theme == 'light':
        log.info("Using 'light' theme")
        log.info("(run 'todiscgui tk' for alternate theme)")
        root.option_add("*Entry.relief", GROOVE)
        root.option_add("*Spinbox.relief", GROOVE)
        root.option_add("*Listbox.relief", GROOVE)
        root.option_add("*Button.relief", GROOVE)
        root.option_add("*Menu.relief", GROOVE)
        root.option_add("*font", ("Helvetica", 12))
        root.option_add("*Radiobutton.selectColor", "#8888FF")
        root.option_add("*Checkbutton.selectColor", "#8888FF")
        # Mouse-over effects
        root.option_add("*Button.overRelief", RAISED)
        root.option_add("*Checkbutton.overRelief", GROOVE)
        root.option_add("*Radiobutton.overRelief", GROOVE)
    else:  # theme == 'tk' or anything else
        log.info("Using 'tk' theme")
        log.info("(run 'todiscgui light' for alternate theme)")
        root.option_clear()
    # These apply to all themes
    root.option_add("*Scale.troughColor", 'white')
    root.option_add("*Spinbox.background", 'white')
    root.option_add("*Entry.background", 'white')
    root.option_add("*Listbox.background", 'white')
    
### --------------------------------------------------------------------
### Entry point
### --------------------------------------------------------------------

root = Tk()
if __name__ == '__main__':
    # Single argument: theme name 'default' or 'light'
    theme = 'light'
    if len(sys.argv) > 1:
        theme = sys.argv[1]

    root.title("todisc GUI")
    #root.iconbitmap('@//work/svn/tovid/src/tovid_bw_32.xbm')
    setTheme(theme)
    app = Application(root)
    root.update_idletasks()
    root.mainloop()

