#!/usr/bin/env python
#
# Copyright (C) 2001-2002 Leonardo J. Milano (lmilano@udel.edu)
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# You can also get a copy of  the license at:
# http://www.gnu.org/copyleft/gpl.html 

#### imports

from FileDialog import *
from Tkinter import *
import tkMessageBox
import getopt
import os
import pickle
import shutil 
import string
import glob
import re 
import types

#### Environment variables

try:
    LATEX2SLIDES_HOME = os.path.abspath(os.environ['LATEX2SLIDES_HOME'])+'/' 
except:
    LATEX2SLIDES_HOME = ''

try:
    HOME = os.path.abspath(os.environ['HOME'])+'/'
except:
    print 'Enviroment variable HOME is not set !, using current directory'
    HOME = os.path.abspath(os.curdir)+'/' 

#### Constants

__version__      = '0.8.1'
VERSION_INFO     = 'latex2slides '+__version__
AUTHOR           = '2001-2002 Leonardo J. Milano - License: GPL'
USER_DIR         = HOME+'.latex2slides/'
LOG_FILE         = os.path.abspath(USER_DIR+'latex2slides.log')
PREFERENCES_FILE = os.path.abspath(USER_DIR+'.preferences')
INSTALL_DIRS     = ['/usr/local/share/latex2slides/latex2slides-'+__version__+'/',\
                   USER_DIR+'latex2slides-'+__version__+'/',\
                   LATEX2SLIDES_HOME,\
                   os.path.abspath(os.curdir)+'/'] 
FILE_TYPES       = ['autodetect','latex','tex','dvi','ps','pdf']
ORIENTATIONS     = ['portrait' ,'portrait-upside-down', 
                    'landscape','landscape-upside-down']
IMAGE_FORMATS    = ['png','jpg']
MAX_MAGNIFICATION= '1000'
MAX_RESOLUTION   = '300'
NAV_STYLES       = ['graphical','text']
NAV_POSITIONS    = ['top','bottom','top and bottom']
INFO_BAR_POSITIONS = ['top','bottom','top and bottom','none']
DEFAULT_TITLE    = 'Slides Presentation'

#### Basic Classes 

class Preferences:

  def __init__(self):
      self.version       = __version__ 
      self.resolution    = '95'
      self.magnification = '100'
      self.browser       = 'netscape'
      self.css_file      = 'default'
      self.output_dir    = USER_DIR
      self.slides_author = ''
      self.orientation   = ORIENTATIONS[0] 
      self.image_format  = IMAGE_FORMATS[0]
      self.nav_style     = NAV_STYLES[0]
      self.nav_position  = NAV_POSITIONS[0]
      self.info_bar_position = INFO_BAR_POSITIONS[1] #FIXME : need a better name
      self.slides_header = ''
      self.slides_footer = ''

  def check(self):
      'check whether values in self are valid or not'
      strip_string_attributes(self)
      res = ''
      if  check_if_in_range(self.magnification, 1, MAX_MAGNIFICATION) == 'no':
          res=res+'Magnification must be between 1 and '+MAX_MAGNIFICATION+'\n'
      if  check_if_in_range(self.resolution, 1, MAX_RESOLUTION) == 'no':
          res=res+'Resolution must be between 1 and '+MAX_RESOLUTION+'\n'
      if  not os.access(self.output_dir, os.F_OK):
          try:
              os.makedirs(os.path.abspath(self.output_dir))
          except:
              res=res+'Could not create output directory '+self.output_dir+'\n'
          else:
              show_msg('Making directory '+self.output_dir)
      if  self.orientation not in ORIENTATIONS:
          res=res+'Unrecognized orientation: '+self.orientation+'\n'
      if  self.nav_position not in NAV_POSITIONS:
          res=res+'Unrecognized navigation bar position: '+\
              self.nav_position+'\n'
      if  self.info_bar_position not in INFO_BAR_POSITIONS:
          res=res+'Unrecognized info bar position: '+\
              self.info_bar_position+'\n'
      if  self.nav_style not in NAV_STYLES:
          res=res+'Unrecognized navigation bar style: '+self.nav_style+'\n'
      res=string.rstrip(res)                       # remove last line feed
      return res

  def save(self):
      'write user preferences to file'
      f=open(PREFERENCES_FILE, 'w')
      pickle.dump(self,f)
      f.close()

  def load(self):
      'read user preferences from file'
      try:
          f=open(PREFERENCES_FILE, 'r')
          q=pickle.load(f)
          f.close()
      except:
          'something went wrong, use program defaults'
      else:
          copy_attributes(q,self)

class RunTimeStrings:

  def __init__(self):
      self.slides_title   = DEFAULT_TITLE
      self.file_type      = FILE_TYPES[0]
      self.file           = ''
      self.run_in_console = 'no'
      self.one_slide      = 'no'
      self.n_slides       = -1

  def check(self):
      'check whether values in self are ok (allowed) or not'
      strip_string_attributes(self)
      res = '' 
      if  self.file_type  not in FILE_TYPES:
          res=res+'Unrecognized source file type: '+self.file_type+'\n'
      if  not os.access(self.file, os.F_OK):
          if  self.file != '':
              res=res+'File '+self.file+' does not exist \n'
      res=string.rstrip(res)                       # remove last line feed
      return res

  def set_file(self,filename):
      if  os.access(filename, os.F_OK):
          self.__init__()                          # resetting self
          self.file = os.path.abspath(filename)
          os.chdir(os.path.dirname(self.file))
          self.set_slides_title()
          write_to_log_file('Opened '+filename)
          try: 
              main.show_file_status()
          except: 
              pass

  def set_slides_title(self):
      "autodetects title from latex source and set it"
      if  self.get_file_type() == "latex":
          title = get_latex_title(self.file)
          if  title != "":
              self.slides_title=title 
              show_msg("Autodetected presentation title: "+title)

  def get_file_type(self):
      if  self.file_type=="autodetect":
          file_extension = os.path.splitext(os.path.basename(self.file))[1]
          if  string.lower(file_extension)   == '.tex':
              return is_latex_or_tex(self.file)
          elif string.lower(file_extension) == '.dvi':
              return 'dvi'
          elif string.lower(file_extension) == '.ps':
              return 'ps'
          elif string.lower(file_extension) == '.pdf':
              return 'pdf'
          else:
              show_msg('File format not recognized')
              return ''
      else:
          return self.file_type

  def latex_cmd(self,file_type):
      return file_type + ' ' + os.path.basename(self.file)

  def dvips_cmd(self,P,dvi_file,ps_file):
      cmd = 'dvips '
      if  P.orientation in [ORIENTATIONS[2],ORIENTATIONS[3]]:
          cmd = cmd + ' -t landscape '
      if  self.one_slide == "yes":
          cmd = cmd + ' -n 1 '
      cmd = cmd + ' -o ' + ps_file +' '+ dvi_file
      return cmd

  def convert_cmd(self,P,input_file):
      cmd = 'convert -verbose -antialias -crop 0x0 -density '+P.resolution
      cmd = cmd + ' -geometry '+P.magnification+'%% ' 
      if  P.orientation == ORIENTATIONS[1]:       # 'portrait-upside-down':
          cmd = cmd + ' -rotate 180 '
      if  P.orientation == ORIENTATIONS[2]:       # 'landscape':
          cmd = cmd + ' -rotate 270 '
      if  P.orientation == ORIENTATIONS[3]:       # 'landscape-upside-down':
          cmd = cmd + ' -rotate 90 '
      if  self.one_slide == "yes":
          input_file = input_file + '[0]'
      cmd = cmd +' '+ input_file + ' '+ P.output_dir+'tmp_%03d.'+ p.image_format
      return cmd

#### Global functions 

def dirpath(dir_name):
    'returns a proper path name for directory dir_name'
    dir_name = os.path.abspath(dir_name)+'/'
    return dir_name

def rm(path_to_file):
    " path_to_file may include wildcards (asterix *) "
    files_list = glob.glob(path_to_file)
    for file in files_list:  
        try:
            os.remove(file)
        except: 
            show_msg("Couldn't remove "+file)
    if  len(files_list) == 0:
        write_to_log_file("Couldn't remove "+path_to_file+" (file not found)")

def path_to_installation(filename):
    for dir in INSTALL_DIRS:
        tmp_path=dirpath(dir)+filename
        if  os.access(tmp_path, os.F_OK):
            return os.path.abspath(tmp_path)
    return ''

def str3(n):
    'return a 3 digits string of number n'
    res=repr(n)
    if  len(res) == 1: res='00'+res 
    if  len(res) == 2: res='0'+res 
    return res

def check_if_in_range(x, x_min, x_max):
    'check if x>=x_min and x<=xmax'
    result='yes'
    try:
        x=float(x)
    except:
        result='no' 
    else:
        if  x<float(x_min):
            result='no'
        if  x>float(x_max):
            result='no'
    return result

def copy_attributes(source, target):
    "it copies contents in data atributes from source-object to target-object"
    for attribute in dir(source):
        value = getattr(source,attribute)
        if  type(value) not in [types.NoneType,types.MethodType]:
            try: 
                setattr(target,attribute,value)
            except:
                'this attribute does not exist in self, dont use it'
                pass

def strip_string_attributes(source):
    "strip string attributes in source-object"
    for attribute in dir(source):
        value = getattr(source,attribute)
        if  type(value) == types.StringType:
            setattr(source,attribute,string.strip(value))

def write_to_log_file(message):
    LOG=open(LOG_FILE, 'a')  
    LOG.write(message+'\n')
    LOG.close()                 

def system_call(command):
    if  os.name in ['nt','dos']:         # a little experiment in windoze
        command = string.replace(command,'&','') 
    write_to_log_file(command)
    os.system(command)

def show_msg(message=''):
    try:
        main.status.set(message)
        main.master.update()
    except:
        print message
    write_to_log_file(message)
     
def usage():
  print 'Usage: latex2slides [OPTION] [FILE]'
  print ''
  print 'Options:'
  print "  -a, --author NAME      set slides author's NAME"
  print '  -c, --console          run in console, do not open graphic interface'
  print '  -C, --css CSSFILE      set CSSFILE as the cascading style sheet'
  print '  -h, --help             display this help and exit'
  print '  -m, --magnification M  set M as image magnification (default=100)'
  print '  -O, --orientation OPT  where OPT is one of the following:' 
  for orientation in ORIENTATIONS:
      print '                         '+orientation
  print '  -o, --output DIRECTORY  set output DIRECTORY for slides'
  print '  -r, --resolution R     set R as postscript resolution (default=95)'
  print '  -s, --source S         where S is one of the following:'
  for source in FILE_TYPES:
      print '                         '+source
  print '  -t, --title TITLE      set presentation TITLE'
  print '  -v, --version          display version information and exit'
  print ''
  print 'Report bugs to <lmilano@udel.edu>'

def exit(message):
    show_msg(message)
    show_msg('Please try "latex2slides --help" ')
    sys.exit(2)
    
def parse_command_line():
    long_opts  = ["author=","console","css=","help","magnification=",\
                  "orientation=","output=","resolution=","source=","title=",\
                  "version"]
    short_opts =  "a:cC:hm:O:o:r:s:t:v"
    try:
        opts, args = getopt.getopt(sys.argv[1:],short_opts,long_opts)
    except getopt.error: 
        usage()
        sys.exit(2)
    # We define auxiliary P,S objects to check for errors in command line
    P = Preferences() 
    S = RunTimeStrings()
    copy_attributes(p,P) 
    copy_attributes(s,S) 
    if  len(args) >= 1:
        S.set_file(os.path.abspath(args[0]))
    for o, a in opts:
        if  o in ("-v", "--version"):
            print VERSION_INFO
            sys.exit()
        if  o in ("-h", "--help"):
            usage()
            sys.exit()
        if  o in ("-a", "--author"):
            P.slides_author = a
        if  o in ("-c", "--console"):
            S.run_in_console = "yes"
        if  o in ("-C", "--css"):
            P.css_file = os.path.abspath(a)
        if  o in ("-m", "--magnification"):
            P.magnification = a
        if  o in ("-O", "--orientation"):
            P.orientation = a
        if  o in ("-o", "--output"):
            P.output_dir = dirpath(a)
        if  o in ("-r", "--resolution"):
            P.resolution = a
        if  o in ("-s", "--source"):
            S.file_type = a
        if  o in ("-t", "--title"):
            S.slides_title = a
    check_result   = P.check()+S.check()    # check for errors
    if  check_result == '':
        copy_attributes(P,p) 
        copy_attributes(S,s) 
    else:
        exit(check_result)

def is_latex_or_tex(filename):
    if  os.access(filename, os.F_OK):
        f=open(filename, "r")
        file_contents=f.read()
        file_contents=string.replace(file_contents,' ','')
        file_contents=string.replace(file_contents,'\n','')
        try:
            tmp=string.index(file_contents,'begin{document}')
        except ValueError:
            return "tex"
        else:
            return "latex"

def get_latex_title(filename):
    "returns a tring with the title name - parses ' title{}' in latex doc"
    # only lines of the type "  \title { simple text title } " are matched
    # we dont match things like " \title{ \bf{ commands }  inside the title"
    if  os.access(filename, os.F_OK):
        f=open(filename, "r")
        file_contents=f.read()
        obj =re.search("^\s*?\\\\title\s*?{[^\\\\{]*?}",file_contents,re.S|re.M)
        if  obj:
            # a match was found
            str=obj.group()            # convert obj to string
            str=string.replace(str,'\n',' ')
            str=string.replace(str,'{','')
            str=string.replace(str,'}','')
            str=string.replace(str,'\\title','')
            str=string.strip(str)
        else:
           # No match was found
            str=''
            return str
        return str

def make_slide_images():
    if not os.access(s.file, os.F_OK): 
       show_msg('Open a file first')
       return 

    # cleanup output dir
    rm(p.output_dir+'*.html')
    for format in IMAGE_FORMATS:
        rm(p.output_dir+'slide*.'+format)

    # some important strings
    file_stripped = os.path.splitext(os.path.basename(s.file))[0]
    file_dir      = os.path.dirname(s.file)
    os.chdir(file_dir)
    file_type     = s.get_file_type()

    # make images depending on file type 
    if  file_type in ('tex','latex'):
        latex_cmd     = s.latex_cmd(file_type)
        show_msg('Calling '+file_type+' ...')
        system_call(latex_cmd)                                       # tex->dvi
        if  file_type == 'latex':    
            system_call(latex_cmd)
            rm(file_stripped+'.aux')
            rm(file_stripped+'.log')
        system_call(s.dvips_cmd(p,file_stripped,file_stripped+'.ps'))# dvi -> ps
        show_msg('Making slide images, please wait ...')
        system_call(s.convert_cmd(p,file_stripped+'.ps'))            # ps -> jpg
        rm(file_stripped+'.dvi')
        rm(file_stripped+'.ps')
    elif file_type == 'dvi':
        system_call(s.dvips_cmd(p,file_stripped,file_stripped+'.ps'))# dvi -> ps
        show_msg('Making slide images, please wait ...')
        system_call(s.convert_cmd(p,file_stripped+'.ps'))            # ps -> jpg
        rm(file_stripped+'.ps')
    elif file_type in ('ps','pdf'):
        show_msg('Making slide images, please wait ...')
        system_call(s.convert_cmd(p,s.file))               # ps (or pdf) -> jpg

    n_slides = len(glob.glob(p.output_dir+'tmp_*'+p.image_format))
    s.n_slides = n_slides

    # rename images, i.e. tmp_000.img slide_001.img and so on ...
    for i in range(n_slides):
        os.rename(p.output_dir+'tmp_'  +str3(i)+'.'+p.image_format,\
                  p.output_dir+'slide_'+str3(i+1)+'.'+p.image_format)
        
def get_icons():
    "copy icons from installation_dir to output_dir"
    "return a dictionary of icons"
    icons = {'prev'  : 'prev.' +p.image_format,\
             'next'  : 'next.' +p.image_format,\
             'index' : 'index.'+p.image_format,\
             'first' : 'first.'+p.image_format,\
             'last'  : 'last.' +p.image_format}
    for key in icons.keys():
        icon_path=path_to_installation('icons/'+icons[key]) 
        if  icon_path == "":
            return {}
        else:
            shutil.copy(icon_path, p.output_dir)
    return icons

def make_slide_htmls():
  'create one HTML page for each slide'
  if  not s.n_slides > 0:
      return

  icons = get_icons()
  if  icons == {}: 
      show_msg("Couldn't find icons. Switching to text style navigation.")
      p.nav_style='text'

  show_msg('Making HTML pages ...')
  for i in range(1,s.n_slides+1):
      prev=i-1
      next=i+1
      if  prev < 1: prev=1
      if  next > s.n_slides: next=s.n_slides
      # info bar string
      str_info_bar = '<div class="info-bar"> \n'
      if  s.slides_title != DEFAULT_TITLE:
          str_info_bar = str_info_bar + s.slides_title + ' - \n'
      str_info_bar = str_info_bar + str(i)+'/'+str(s.n_slides) +'\n'
      str_info_bar = str_info_bar + '</div> \n\n'
      # navigation bar string
      if  p.nav_style == "graphical":
          str_nav = \
          '<div class="nav-bar"> \n'+\
          '<table border=0 cellpadding=4 cellspacing=0 width="100%"> \n'+\
          '  <tr valign="middle"> \n'+\
          '  <td align="left"> \n'+\
          '  <a class="nav" href="slide_'+str3(prev)+'.html"> \n'+\
          '  <img alt="<" src="' +icons['prev']+ '" border="0"> \n'+\
          '  </a> \n'+\
          '  <a class="nav" href="slide_'+str3(next)+'.html"> \n'+\
          '  <img alt=">" src="' +icons['next']+ '" border="0"> \n'+\
          '  </a> \n'+\
          '  </td><td align="center"> \n' +\
          '  <a class="nav" href="index.html"> \n'+\
          '  <img alt="^" src="' +icons['index']+ '" border="0"> \n'+\
          '  </a> \n'+\
          '  </td><td align="right"> \n'+\
          '  <a class="nav" href="slide_'+str3(1)+'.html"> \n'+\
          '  <img alt="<<" src="' +icons['first']+ '" border="0"> \n'+\
          '  </a> \n'+\
          '  <a class="nav" href="slide_'+str3(s.n_slides)+'.html"> \n'+\
          '  <img alt=">>" src="' +icons['last']+ '" border="0"> \n'+\
          '  </a> \n'+\
          '  </td></tr> \n'+\
          '</table> \n' +\
          '</div> \n\n'
      else:
          # nav string text mode
          str_nav = \
	 	 '<div class="nav-bar"> \n'+\
          '<table border=0 cellpadding=4 cellspacing=0 width="100%"> \n'+\
          '  <tr><td class="nav" align="left"> \n' + \
          '  <a class="nav" href="slide_'+str3(prev)+'.html">Prev</a> | ' +\
          '  <a class="nav" href="slide_'+str3(next)+'.html">Next</a> \n' + \
          '  </td><td class="nav" align="center"> \n' +\
          '  <a class="nav" href="index.html">Index</a> \n'+\
          '  </td><td align="right"> \n'+\
          '  <a class="nav" href="slide_'+str3(1)+'.html">First</a> | \n'+\
          '  <a class="nav" href="slide_'+str3(s.n_slides)+'.html">Last</a>\n'+\
          '  </td></tr> \n'+\
          '</table> \n'+\
          '</div> \n\n'
 
      f=open( p.output_dir+'slide_'+str3(i)+'.html', 'w') 
      # html open
      f.write(\
      '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> \n'+\
      '<html> \n'+\
      '<head> \n'+\
      '  <title> '+s.slides_title+' - Slide '+str(i)+ ' </title>\n'+\
      '  <meta name="generator" content="latex2slides '+__version__+'"> \n'+\
      '  <link rel=StyleSheet href="style.css" type="text/css" '+\
      'media=screen> \n'+\
      '</head> \n'+\
      '<body> \n\n'\
      )
      # header
      if  p.slides_header != "":
          f.write(p.slides_header+'\n\n')
      # navigation bar
      if  p.nav_position in [NAV_POSITIONS[0],NAV_POSITIONS[2]]:
          f.write(str_nav)
      # info bar
      if  p.info_bar_position in [INFO_BAR_POSITIONS[0],INFO_BAR_POSITIONS[2]]:
          f.write(str_info_bar)
      # slide 
      f.write('<div class="slide">\n')
      f.write('<img src="slide_'+str3(i)+'.'+p.image_format+'" alt="slide">\n')
      f.write('</div>\n\n')
      # info bar
      if  p.info_bar_position in [INFO_BAR_POSITIONS[1],INFO_BAR_POSITIONS[2]]:
          f.write(str_info_bar)
      # navigation bar
      if  p.nav_position in [NAV_POSITIONS[1],NAV_POSITIONS[2]]:
          f.write(str_nav)
      # footer
      if  p.slides_footer != "":
          f.write(p.slides_footer+'\n\n')
      # html close
      f.write('</body> \n')
      f.write('</html> \n')
      f.close()

def make_index_html():
  if not s.n_slides > 0:
     return

  f=open(p.output_dir+'index.html', 'w') 
  f.write(
  '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> \n'+\
  '<html> \n'+\
  '<head> \n'+\
  '  <title> '+s.slides_title+' </title>\n'+\
  '  <meta name="generator" content="latex2slides '+__version__+'"> \n'+\
  '  <link rel=StyleSheet href="style.css" type="text/css" media=screen> \n'+\
  '</head> \n'+\
  '<body> \n\n'+\
  '<h1>'+s.slides_title+'</h1> \n'+\
  '<h2>Index of Contents</h2> \n\n'+\
  '<ul> \n'\
  )
  for i in range(1,s.n_slides+1):
      f.write('  <li><a href="slide_'+str3(i)+'.html">Slide '+\
              str(i)+'</a></li> \n')
  f.write('</ul> \n\n')
  f.write('<hr> \n')
  f.write('<i> \n')
  f.write('Slides made with <a href="http://udel.edu/~lmilano/latex2slides/">'\
          '\nlatex2slides '+__version__+' </a> <br>\n')
  if p.slides_author != '':
     f.write('Presentation by '+p.slides_author+'\n')
  f.write('</i> \n\n')
  f.write('</body> \n')
  f.write('</html> \n')
  f.close()

CSS_STR="""
/* latex2slides style sheet - modify it as you please */ 

body {background-color: #ffffff} 
div  {width: 100%}
h1   {color: #777777;  border: solid #777777; text-align: center} 
h2   {color: #777777; text-decoration: none } 

a:link        { color: blue    ; text-decoration: none } 
a:visited     { color: #5000a0 ; text-decoration: none } 
a:hover       { color: red     ; text-decoration: none } 
a:active      { color: red     ; text-decoration: none } 

a.nav:link    { color: blue    ; text-decoration: none } 
a.nav:visited { color: #5000a0 ; text-decoration: none } 
a.nav:hover   { color: red     ; text-decoration: none } 
a.nav:active  { color: red     ; text-decoration: none } 

div.nav-bar { 
    background-color: #dddddd; 
    text-align: center; 
    border: solid 1px
}
div.info-bar { 
    background-color: #ffffff; 
    text-align: center; 
    border: solid 1px
} 
div.slide { 
    text-align: center; 
    border: none;
    padding: 2em 0em 
}
"""

def make_css():
  if not s.n_slides > 0:
     return

  if os.access(p.css_file, os.F_OK):
     show_msg('Using style sheet: '+p.css_file)
     shutil.copy(p.css_file,p.output_dir+'style.css') 
  else:
     show_msg('Using default style sheet')
     f=open(p.output_dir+'style.css', 'w') 
     f.write(CSS_STR)
     f.close()

def make_slides():
  "Here is where slides are actually made"

  write_to_log_file('Making slides:')
  write_to_log_file('    Source file: '+s.file)
  write_to_log_file('    Output dir:  '+p.output_dir)

  make_slide_images()
  if  s.n_slides > 0:
      make_slide_htmls()
      make_index_html()
      make_css()
      show_msg('Done ! - Slides @ '+p.output_dir)
  else:
      print s.n_slides
      show_msg('Failed to make slides')

#### GUI Classes
 
class MainWindow:

  def __init__(self, parent):

      master = self.master = parent 
      self.master.title('latex2slides')
      self.master.protocol("WM_DELETE_WINDOW", self.exit)

      self.status       = StringVar()
      self.main_message = StringVar()

      # menus
      menu = Menu(master)
      master.config(menu=menu)

      file_menu = Menu(menu)
      menu.add_cascade(label='File', menu=file_menu)
      file_menu.add_command(label='Open ...', command=self.open_file)
      file_menu.add_command(label='Open new ...', command=self.open_new_file)
      file_menu.add_command(label='Open sample ...', command=\
                            self.open_sample_file)
      file_menu.add_command(label='Exit', command=self.exit)

      slides_menu = Menu(menu)
      menu.add_cascade(label='Slides', menu=slides_menu)
      slides_menu.add_command(label='Make', command=make_slides)
      slides_menu.add_command(label='Make one', command=self.make_one_slide)
      slides_menu.add_command(label='View', command=self.show_slides)

      view_menu = Menu(menu, name='view')
      menu.add_cascade(label='View', menu=view_menu)
      view_menu.add_command(label='Slides', command=self.show_slides)
      view_menu.add_command(label='Log file', command=self.view_log_file)

      settings_menu = Menu(menu)
      menu.add_cascade(label='Settings', menu=settings_menu)
      settings_menu.add_command(label='Configure ...', command=self.configure)

      help_menu = Menu(menu, name='help')
      menu.add_cascade(label='Help', menu=help_menu)
      help_menu.add_command(label='Documentation', \
               command=self.documentation)
      help_menu.add_command(label='Web site', command=self.web_page)
      help_menu.add_command(label='About', command=self.about)

      # widgets
      self.l0 = Label(master, text="", bd=1, \
                relief=FLAT, width=60, height=4, justify=CENTER )
      self.l1 = Label(master, textvariable=self.main_message, bd=1, anchor=W, \
                relief=FLAT, justify=CENTER, fg="#000000", padx=10)
      self.l2 = Label(master, textvariable=self.status, bd=1, anchor=W, \
                relief=SUNKEN, justify=LEFT, fg="#000000", bg="#ffffff",padx=10)
      # geometry
      self.l0.grid(row=1, sticky=W+E)
      self.l1.grid(row=2, sticky=W+E)
      self.l2.grid(row=4, sticky=W+E)

      # default messages
      self.main_message.set('')
      self.show_file_status()

  def show_file_status(self):
      #I cannot use "show_msg()" in the constructor, "main" is still undefined
      if os.access(s.file, os.F_OK):
         self.status.set('You may now make slides or configure settings')
      else:
         self.status.set('Open a file to proceed')
      self.main_message.set('File: '+s.file+'  ')

  def make_one_slide(self):
      s.one_slide = 'yes'
      make_slides()
      s.one_slide = 'no'

  def documentation(self):
      doc_file = path_to_installation('doc/documentation.html')
      if  doc_file == '':
          show_msg('documentation is not properly installed')
      else:
          show_msg('Launching '+p.browser+' to display documentation')
          system_call(p.browser+' '+doc_file+' &')
         
  def web_page(self):
      show_msg('Launching '+p.browser+' to visit latex2slides web site')
      system_call(p.browser+' '+'http://udel.edu/~lmilano/latex2slides/ &')
      
  def view_log_file(self):
      show_msg('Launching '+p.browser+' to display log file')
      system_call(p.browser+' '+LOG_FILE+' &')

  def about(self):
      show_msg(VERSION_INFO+' - '+AUTHOR)

  def file_dialog(self,title='latex2slides file dialog'):
      try:
          file = LoadFileDialog(self.master,title).go(pattern='*')
          if  file:
              return file
      except KeyboardInterrupt:
          self.exit()

  def open_file(self):
      file = self.file_dialog('latex2slides: open a file')
      if  file:
          s.set_file(file)

  def open_new_file(self):
      file = self.file_dialog('latex2slides: open a new file')
      if  file:
          s.set_file(file)
          p.load()                      # reload user preferred params

  def open_sample_file(self):
      samples_dir = path_to_installation('samples/')
      if  samples_dir == '':
          show_msg('Samples are not properly installed')
          return
      else:
          os.chdir(samples_dir)
          file = self.file_dialog('latex2slides: open a sample file')
          if  file:
              shutil.copy(file,p.output_dir)
              s.set_file(p.output_dir+os.path.basename(file))

  def configure(self):
      show_msg('Configuring latex2slides ... ')
      aux=ConfigureDialog(self.master)

  def show_slides(self):
      index_html = p.output_dir+'index.html'
      if  not os.access(index_html, os.F_OK): 
          show_msg('There are no slides in the output directory')
          return
      else:
          system_call(p.browser+' '+index_html+' &')
          show_msg('Calling '+p.browser+' ... ')

  def exit(self):
      show_msg('Exiting latex2slides')
      self.master.destroy()

class ConfigureDialog:

  def __init__(self, parent):

      master = self.master = Toplevel(parent)
      master.title('latex2slides: Settings')
      self.master.grab_set()
      self.master.protocol("WM_DELETE_WINDOW", self.cancel)

      self.file_type     = StringVar()
      self.output_dir    = StringVar()
      self.resolution    = StringVar()
      self.magnification = StringVar()
      self.browser       = StringVar()
      self.slides_author = StringVar()
      self.slides_title  = StringVar()
      self.orientation   = StringVar()
      self.image_format  = StringVar()
      self.css_file      = StringVar()
      self.nav_style     = StringVar()
      self.nav_position  = StringVar()
      self.info_bar_position = StringVar()

      # local copies of p and s
      self.p = Preferences()
      self.s = RunTimeStrings()
      copy_attributes(p,self.p) 
      copy_attributes(s,self.s) 

      # widgets
      self.t1 = Label(master, text = 'Files and directories',\
                bg="#777777", fg="white", bd=2, relief=RIDGE)
      self.t2 = Label(master, text = 'Slide options',\
                bg="#777777", fg="white", bd=2, relief=RIDGE)
      self.t3 = Label(master, text = 'Slides layout',\
                bg="#777777", fg="white", bd=2, relief=RIDGE)
      self.t4 = Label(master, text = 'Misc. options',\
                bg="#777777", fg="white", bd=2, relief=RIDGE)
      self.t5 = Label(master, text = 'Action',\
                bg="#777777", fg="white", bd=2, relief=RIDGE)

      self.l1 = Label(master, text='Source file type:')
      self.r1 = apply (OptionMenu, (master, self.file_type) + 
                tuple(FILE_TYPES))

      self.l2 = Label(master, text='Output directory:')
      self.r2 = Button(master, textvariable=self.output_dir, \
                       command=self.choose_output_dir)

      self.l3 = Label(master, text='Cascading Style Sheet:')
      self.r3 = Button(master, textvariable=self.css_file, \
                       command=self.choose_css_file)

      self.l4 = Label(master, text='Image resolution (pixels per inch):')
      self.r4 = Entry(master, textvariable=self.resolution)

      self.l5 = Label(master, text='Image magnification (%):')
      self.r5 = Entry(master, textvariable=self.magnification)

      self.l6 = Label(master, text='Presentation title:')
      self.r6 = Entry(master, textvariable=self.slides_title)

      self.l7 = Label(master, text='Presentation author:')
      self.r7 = Entry(master, textvariable=self.slides_author)

      self.l9 = Label(master, text='Orientation:')
      self.r9 = apply (OptionMenu, (master, self.orientation ) + 
                tuple(ORIENTATIONS))
      self.l10= Label(master, text='Image format:')
      self.r10= apply (OptionMenu, (master, self.image_format) + 
                tuple(IMAGE_FORMATS))

      self.l12= Label(master, text='Navigation bar style:')
      self.r12= apply (OptionMenu, (master, self.nav_style) + 
                tuple(NAV_STYLES))

      self.l13= Label(master, text='Navigation bar position:')
      self.r13= apply (OptionMenu, (master, self.nav_position) + 
                tuple(NAV_POSITIONS))

      self.l14= Label(master,text='Info bar position:')
      self.r14= apply (OptionMenu, (master, self.info_bar_position) + 
                tuple(INFO_BAR_POSITIONS))

      self.l15= Label(master, text='Header (you may use html):')
      self.slides_header = Text(master, height=3,width=40, wrap=NONE)

      self.l18= Label(master, text='Footer (you may use html):')
      self.slides_footer = Text(master, height=3,width=40, wrap=NONE)

      self.l22= Label(master, text='Web browser:')
      self.r22= Entry(master, textvariable=self.browser)

      self.b1 = Button(master, text='Apply',  fg="blue",  
                       command=self.apply_and_quit)
      self.b2 = Button(master, text='Save',   fg="blue",  
                       command=self.save_and_quit)
      self.b3 = Button(master, text='Reset',  fg="blue",  
                       command=self.reset)
      self.b4 = Button(master, text='Cancel', fg="blue", 
                       command=self.cancel)

      self.sep= Label(master, text='', bd=2, relief=RAISED)   # separator

      # geometry
      self.t1.grid(row=0, columnspan=2, sticky=W+E, pady=5) 

      self.l1.grid(row=1, sticky=W)
      self.r1.grid(row=1, column=1, sticky=W+E)

      self.l2.grid(row=2, sticky=W)
      self.r2.grid(row=2, column=1, sticky=W+E)

      self.l3.grid(row=3, sticky=W)
      self.r3.grid(row=3, column=1, sticky=W+E)

      self.t2.grid(row=4, columnspan=2, sticky=W+E, pady=5) 

      self.l4.grid(row=5, sticky=W)
      self.r4.grid(row=5, column=1, sticky=W+E)

      self.l5.grid(row=6, sticky=W)
      self.r5.grid(row=6, column=1, sticky=W+E)

      self.l6.grid(row=7, sticky=W)
      self.r6.grid(row=7, column=1, sticky=W+E)

      self.l7.grid(row=8, sticky=W)
      self.r7.grid(row=8, column=1, sticky=W+E)

      self.l9.grid(row=9, sticky=W)
      self.r9.grid(row=9, column=1, sticky=W+E)

      self.l10.grid(row=10, sticky=W)
      self.r10.grid(row=10, column=1, sticky=W+E)

      self.t3.grid(row=11, columnspan=2, sticky=W+E, pady=5) 

      self.l12.grid(row=12, sticky=W)
      self.r12.grid(row=12, column=1, sticky=W+E)

      self.l13.grid(row=13, sticky=W)
      self.r13.grid(row=13, column=1, sticky=W+E)

      self.l14.grid(row=14, sticky=W)
      self.r14.grid(row=14, column=1, sticky=W+E)

      self.l15.grid(row=15, sticky=W)
      self.slides_header.grid(row=15, column=1, sticky=W+E)

      self.l18.grid(row=18, sticky=W)
      self.slides_footer.grid(row=18, column=1, sticky=W+E)

      self.t4.grid(row=21, columnspan=2, sticky=W+E, pady=5) 

      self.l22.grid(row=22, sticky=W)
      self.r22.grid(row=22, column=1, sticky=W+E)

      self.t5.grid(row=0, column=3, sticky=W+E, pady=5) 

      self.b1.grid(row=1, column=3, sticky=W+E)
      self.b2.grid(row=2, column=3, sticky=W+E)
      self.b3.grid(row=3, column=3, sticky=W+E)
      self.b4.grid(row=4, column=3, sticky=W+E)

      self.sep.grid(row=0, rowspan=23, column=2, sticky=N+S, padx=0)

      self.show_defaults()
     
  def show_defaults(self):
      self.resolution.set(self.p.resolution) 
      self.magnification.set(self.p.magnification) 
      self.browser.set(self.p.browser) 
      self.file_type.set(self.s.file_type) 
      self.output_dir.set(self.p.output_dir) 
      self.slides_author.set(self.p.slides_author) 
      self.slides_title.set(self.s.slides_title) 
      self.orientation.set(self.p.orientation) 
      self.image_format.set(self.p.image_format) 
      self.css_file.set(self.p.css_file) 
      self.nav_style.set(self.p.nav_style) 
      self.nav_position.set(self.p.nav_position) 
      self.info_bar_position.set(self.p.info_bar_position) 
      self.slides_header.delete(1.0,END)
      self.slides_header.insert(1.0,self.p.slides_header) 
      self.slides_footer.delete(1.0,END)
      self.slides_footer.insert(1.0,self.p.slides_footer) 

  def apply(self):
      self.p.resolution   = self.resolution.get()
      self.p.magnification= self.magnification.get()
      self.p.browser      = self.browser.get()
      self.s.file_type    = self.file_type.get()
      self.p.output_dir   = self.output_dir.get() 
      self.p.slides_author= self.slides_author.get()
      self.s.slides_title = self.slides_title.get()
      self.p.orientation  = self.orientation.get()
      self.p.image_format = self.image_format.get()
      self.p.css_file     = self.css_file.get() 
      self.p.nav_style    = self.nav_style.get() 
      self.p.nav_position = self.nav_position.get() 
      self.p.info_bar_position = self.info_bar_position.get() 
      self.p.slides_header= self.slides_header.get(1.0,END) 
      self.p.slides_footer= self.slides_footer.get(1.0,END) 
      # check input values
      check_result        = self.p.check()+self.s.check()
      if  check_result == '':
          copy_attributes(self.p,p) 
          copy_attributes(self.s,s) 
          main.__init__(root)   # refresh main window (FIXME: this is ugly)
      else:
          try:
              tkMessageBox.showwarning("latex2slides: error", check_result,\
              parent=self.master)
          except KeyboardInterrupt:
              main.exit()
      return check_result

  def exit(self):
      self.master.destroy()

  def cancel(self):
      show_msg('Nothing changed in configuration')
      self.exit()

  def apply_and_quit(self):
      result = self.apply()
      if  result == '':
          show_msg('Changes in configuration were applied')
          self.exit()

  def save_and_quit(self):
      result = self.apply()
      if  result == '':
          show_msg('Changes in configuration were applied and saved')
          p.save()
          self.exit()

  def reset(self):
      self.p.__init__()
      self.s.__init__()
      self.show_defaults()

  def choose_css_file(self):
      try:
          file = LoadFileDialog(self.master,'latex2slides: choose a CSS ').\
                 go(pattern='*.css')
          if file:
             self.css_file.set(os.path.abspath(file))
      except KeyboardInterrupt:
          main.exit()

  def choose_output_dir(self):
      try:
          dir = FileDialog(self.master,'Choose a directory for your slides')\
                .go(pattern='-')
          try:
              if  os.access(dir, os.F_OK):
                  self.output_dir.set(dirpath(dir)) 
          except:
              pass
      except KeyboardInterrupt:
          main.exit()

#### Initialize stuff

if  not os.access(USER_DIR, os.F_OK):  
    try:
        os.makedirs(os.path.abspath(USER_DIR))   
    except:
        print "Fatal Error: Couldn't make dir "+USER_DIR
        print "Make sure the HOME environment variable is properly set ..."
        sys.exit()

s=RunTimeStrings()         # initialize run time strings - do not rename "s"
p=Preferences()            # get default options -  please do not rename "p"
p.load()                   # load user preferred options from file
LOG=open(LOG_FILE, 'w').close()    # initialize log file
parse_command_line()       # read command line options 

#### Tests

#### Run 

if s.run_in_console == "yes":
   make_slides()
else:
   try:
      root = Tk()
      main = MainWindow(root)
      main.master.mainloop()
   except KeyboardInterrupt:
      main.exit()
