##---------------------------------------------------------------------------##
##
## pyChing -- a Python program to cast and interpret I Ching hexagrams
##
## Copyright (C) 1999,2000 Stephen M. Gava
##
## 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
## (at your option) any later version.
##
## This program is distributed in the hope that it will be of some
## interest to somebody, 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; see the file COPYING or COPYING.txt. If not, 
##  write to the Free Software Foundation, Inc.,
## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
## The license can also be found at the GNU/FSF website: http://www.gnu.org
##
## Stephen M. Gava
## <elguavas@users.sourceforge.net>
## http://pyching.sourgeforge.net
##
##---------------------------------------------------------------------------##
"""
tkinter interface module for pyching
"""

#python library imports
import string, sys, os, time, copy, htmllib, formatter

#tkinter imports
from Tkinter import *
import tkFileDialog, tkMessageBox, tkColorChooser

#pyChing source specific imports
import pyching_engine, pyching_cimages, pyching_idimage_data
#import pyching_inhtx_data, pyching_hlhtx_data 

#smg library module imports
sys.path.append('./smgTkLib')
from smgDialog import smgDialog
from smgHtmlView import smgHtmlView
from smgAbout import smgAbout

class WidgetColors:
  """
  colours for widgets in the reading display area
  """
  def __init__(self):
    #maybe colour defaults for all platforms
    #and platform defaults if started with a --mono switch (?)
    #if pyching.osType in ('posix','nt'): #non-default values for X & win32 style tk widgets
    #self.BgReading = '#7e7e7e'
    #self.bgReading = '#cd5c5c'
    self.bgReading = '#323c4a'
    self.bgLabelHint = '#FFE4B5'
    self.fgLabelHint = '#000000'
    #self.fgLabelPlaces = '#D3D3D3'
    #self.fgLabelPlaces = '#F08080'
    self.fgLabelPlaces = '#FFA07A'
    self.fgLabelHexTitles = '#FFFFFF'
    self.fgLabelLines = '#FFFFFF'
    self.fgMessageQuestion = '#FFFFFF'
    self.lineBody = '#DAA520'
    self.lineHighlight = '#EEE8AA'
    self.lineShadow = '#B8860B'
    #self.bgHtmlViewer = '#e8e8e8'
    #self.fgHtmlViewer = '#000000'
    #else: #values default to the platform standards
    # self.bgReading = None
    # self.bgLabelHint = None
    # self.fgLabelHint = None
    # self.fgLabelPlaces = None
    # self.fgLabelHexTitles = None
    # self.fgLabelLines = None
    # self.fgMessageQuestion = None
    # self.lineBody = 'Gray'
    # self.lineHighlight = 'LightGray'
    # self.lineShadow = 'DarkGray'

class WidgetFonts:
  """
  fonts for widgets in the main window
  """
  def __init__(self):
    if pyching.osType == 'posix': #non-default values for X style tk widgets
      self.menu = '-*-Helvetica-Normal-R-*--*-120-*-*-*-*-ISO8859-1'
      self.button = '-*-Helvetica-Normal-R-*--*-120-*-*-*-*-ISO8859-1'
      self.label = '-*-Helvetica-Normal-R-*--*-120-*-*-*-*-ISO8859-1'
      self.labelHexTitles = '-*-Helvetica-Bold-R-*--*-140-*-*-*-*-ISO8859-1'
      self.labelLineHint = '-*-Helvetica-Normal-R-*--*-120-*-*-*-*-ISO8859-1'
    elif pyching.osType == 'nt': #values for win32 style tk widgets
      self.menu = None
      self.button = None
      self.label = None
      self.labelHexTitles = '-*-Helvetica-Bold-R-*--*-150-*-*-*-*-ISO8859-1'
      self.labelLineHint = None
    else: #use default values for any other (untested :-) platform
      self.menu = None
      self.button = None
      self.label = None
      self.labelHexTitles = None
      self.labelLineHint = None

class WindowMain:
  """
  main application window
  """
  def __init__(self, master): 
    self.master = master
    self.master.resizable(height=FALSE,width=FALSE)
    #self.master.colormapwindows([self.master])#debug, does this solve the 256 color problem??
    self.images = pyching_cimages.CoinImages()
    try:
      self.master.iconbitmap(bitmap='@'+pyching.execPath+'/icon.xbm')
    except TclError:
      #sys.stderr.write("can't load icon bitmap")
      pass #just ignore this
    self.master.title(pyching.title + '  ' + pyching.version)
    
    #application-wide event bindings
    self.master.bind('<F1>',self.HelpBinding) #help key #bind_all
    self.master.bind('<Alt-c>',self.CastButtonBinding) #cast button
    self.master.bind('<Alt-v>',self.ViewHex1InfoButtonBinding) #view hex 1 info button
    self.master.bind('<Alt-i>',self.ViewHex2InfoButtonBinding) #view hex 2 info button
    
    #application-wide protocol bindings
    self.master.protocol("WM_DELETE_WINDOW", self.Quit)

    #configuration attributes
    self.showPlaces = BooleanVar()
    self.showPlaces.set(TRUE)
    self.showLineHints = BooleanVar()
    self.showLineHints.set(TRUE)
    self.castAll = BooleanVar()
    self.castAll.set(TRUE)
    #instantiate default colour and font values
    self.colors = WidgetColors() 
    self.fonts = WidgetFonts() 

    #load configuration file (if any)
    self.LoadSettings()

    self.MakeMenus(self.master)

    self.MakeStatusBar(self.master)

    #main frame
    frameMainBevel = Frame(self.master,borderwidth=2,relief=SUNKEN,highlightthickness=0)
    frameMainBevel.pack(expand=TRUE,fill=BOTH,padx=4)#used as a bevel for the main frame
    self.frameMain = Frame(frameMainBevel,bg=self.colors.bgReading)#highlightthickness=4,borderwidth=4,relief=SUNKEN)#,borderwidth=1,relief=SOLID
    self.frameMain.pack(expand=TRUE,fill=BOTH)
    
    self.MakeCastDisplay(self.frameMain)
    
    self.MakeQuestionDisplay(self.frameMain)

    self.MakeHexDisplay(self.frameMain)

    self.hexes = None #so we can test if a reading has been performed yet
  
  def Quit(self):
    #print 'bye now'#debug
    self.master.quit()
    
  def __AddMenu(self,parent,title):
    if title == 'Help': menuName = 'help'#name option to right justify help menu under X
    else: menuName = None
    _menu = Menu(parent,name=menuName,tearoff=0,font=self.fonts.menu)#create the menu
    if parent == self.master:#this is the menubar itself
      parent.configure(menu=_menu)#make _menu the menubar
      _menu.configure(borderwidth=0)
    else: #this is a dropdown from the menubar  
      parent.add_cascade(label=title,underline=0,menu=_menu)#add the menu to the menubar
    return _menu

  def MakeMenus(self, parent):
    self.menuMain = self.__AddMenu(parent,'')#create the menubar
    self.menuMainFile = self.__AddMenu(self.menuMain,'File')#create the file menu 
    self.menuMainSettings = self.__AddMenu(self.menuMain,'Settings')#create the settings menu 
    self.menuMainHelp = self.__AddMenu(self.menuMain,'Help')#create the help menu 
    
    def AddMenuItems(menu, items):
      for item in items:
        if item[0] == 's':#add a separator
          menu.add_separator()
        elif item[0] == 'c':#add a command  
          menu.add_command(label=item[1],underline=item[2],command=item[3])
        elif item[0] == 'k':#add a checkbutton
          menu.add_checkbutton(label=item[1],underline=item[2],command=item[3],variable=item[4])  
        elif item[0] == 'r':#add a radiobutton
          menu.add_radiobutton(label=item[1],underline=item[2],command=item[3],variable=item[4],value=item[5])  
        #elif item[0] == 'm':#add a submenu
        # pass #not implemented 
    
    AddMenuItems(self.menuMainFile,(('c','Load Reading...',0,self.LoadReading),
                          ('c','Save Reading...',0,self.SaveReading),('s',),
                          ('c','Save Reading As Text...',16,self.SaveReadingAsText),('s',),
                          ('c','Exit',1,self.Quit)) )
    AddMenuItems(self.menuMainSettings,(('k','Show Places',5,self.__ToggleLabelsPlaces,self.showPlaces),
      ('k','Show Line Hints',10,None,self.showLineHints),('s',),
      ('r','Cast Each Line Separately',10,None,self.castAll,FALSE),
      ('r','Cast Entire Hexagram Automatically',12,None,self.castAll,TRUE),('s',),
      ('c','Configure Colors...',10,self.SetColors),('s',),
      ('c','Save Settings',0,self.SaveSettings)) )
    AddMenuItems(self.menuMainHelp,(
      ('c','Using '+pyching.title,0,self.ShowHelpUsingPyching),
      ('c','Introduction to the I Ching',0,self.ShowHelpIChingIntro),('s',),
      ('c','About '+pyching.title+'...',0,self.ShowAbout)) )
    #self.ShowText(title='Help - Using '+pyching.title,textFile='help.txt')),
    self.menuMainFile.entryconfigure(1,state=DISABLED)#disable save item by default
    self.menuMainFile.entryconfigure(3,state=DISABLED)#disable save as text item by default
    
  def HelpBinding(self,event):
    #main window binding for the F1 key
    #try:
    self.ShowHelpUsingPyching()
    #except KeyError:
    # pass #this error is raised while pressing F1 over a tk file dialog, ignore it

  def CastButtonBinding(self,event):
    #binding for the Meta-c hotkey on buttonCast
    if self.buttonCast.cget('state') == 'normal': #if the button isn't disabled
      self.buttonCast.invoke()
  
  def ViewHex1InfoButtonBinding(self, event):
    #binding for the Meta-v hotkey on buttonViewHex1Info
    if self.buttonViewHex1Info.cget('state') == 'normal': #if the button isn't disabled
      self.buttonViewHex1Info.invoke()

  def ViewHex2InfoButtonBinding(self, event):
    #binding for the Meta-i hotkey on buttonViewHex1Info
    if self.buttonViewHex2Info.cget('state') == 'normal': #if the button isn't disabled
      self.buttonViewHex2Info.invoke()

  def ShowHelpUsingPyching(self):
    self.ShowHtml(title=pyching.title + ' - Help',
        htmlSource='pyching_hlhtx_data.hlHelpData()',
        index='pyching_hlhtx_data.hlHelpData()')

  def ShowHelpIChingIntro(self):
    #self.ShowHtml(title=pyching.title + ' - Help',
    #    htmlSource='pyching_hlhtx_data.hlIntroData()',
    #    index='pyching_hlhtx_data.hlHelpData()')
    self.ShowHtml(title=pyching.title + ' - Help',
        htmlSource='pyching_hlhtx_data.hlIntroData()',
        index='pyching_hlhtx_data.hlHelpData()')
    
  
  def ShowText(self,title=None,textFile=None):
    #dialogTxt = DialogShowHtml(self.master,title=title,htmlFile=textFile,
    #    plainText=1)
    dialogTxt = smgHtmlView(self.master,title=title,htmlSource=textFile,
        plainText=1)

  def ShowHtml(self,title=None,htmlSource=None,index=None):
    #dialoghtml = DialogShowHtml(self.master,title=title,htmlFile=textFile,
    #    indexFile=indexFile)
    dialoghtml = smgHtmlView(self.master,title=title,htmlSource=htmlSource,
        internalLink=None,index=index)
    
  def ShowAbout(self):
    #dialogAbout = DialogAbout(self.master,currentColors=self.colors)
    aboutPicData="""R0lGODlhMgAyAKEAAAICBP4CBAIC/P///yH+Dk1hZGUgd2l0aCBHSU1QACH5BAEKAAMALAAA
AAAyADIAAAL+nI+ZwO0Ko1SOhYvvmzw2YWUi1nTmwAgqMLYic0rp6tYkEC+AStt+AMvNeKyf
L2ga9oxHXEe5ZNqQnh0v2nIwqZQrcXot/pwQqxdUM3+NZAR0nT2Lm90zNqO+pxd2+CiPJtUG
GAbmx3bw5jX31yB1Y0C4+EjpJDlJuRXZh5k5hsIp58l2acc42hhqeoqqUcoZ0nqjGuooq0i7
GJuJmyvHOvbqGwacJjxcKHiMfDgFyuxbjLcJnStNQl1New0UuUzcG2icyDxXKj2IfHqJzhf9
B7un8/4Sn9VGbg3fB4zvRq+hVpYJ4cC9WudvnjZRLxJS+KaKEReCEPk1zIGiIkMfVxgTaVwy
EWNBU5A6lhkJgkUJkxRxVXDIssrLkCYKAAA7"""
    dialogAbout = smgAbout(self.master,title='About '+pyching.title, 
        appTitle=pyching.title,
        version='Version: '+pyching.version,
        copyright='Copyright (c) 1999,2000 Stephen M. Gava',
        licence='Released under the GNU General Public Licence',
        email='email:  '+pyching.emailAddress,
        www='web:  '+pyching.webAddress,
        pictureData=aboutPicData,
	# for Debian -forcer
        licenceFile='/usr/share/common-licenses/GPL',
        creditsFile=pyching.execPath+'CREDITS',
        fontAppTitle=self.fonts.labelHexTitles,  
        fontText=self.fonts.label,
        fg=self.colors.fgLabelHexTitles,
        bg=self.colors.bgReading )
  
  def SetColors(self):
    dialogSetColors = DialogSetColors(self.master,currentColors=copy.copy(self.colors))
    if dialogSetColors.result: #user didn't cancel
      self.RepaintColors(dialogSetColors.result)    

  def SaveSettings(self):
    if os.path.expanduser('~') != '~': #unix-style home directories
      if not os.path.exists(pyching.configPath): #failsafe if user deleted ~/.pyching while program running :)
        os.mkdir(pyching.configPath)#make the config dir
    castAllValue = self.castAll.get()
    showPlacesValue = self.showPlaces.get()
    showLineHintsValue = self.showLineHints.get()
    configData = (pyching.version,self.colors,castAllValue,showPlacesValue,showLineHintsValue)
    try:
        pyching_engine.Storage(pyching.configFile, data=configData)
    except IOError:
      #print '\n error: unable to write config file', pyching.configFile
      tkMessageBox.showerror(title='File Error',
              message='Unable to write configuration file:\n'+pyching.configFile)
    else:
      #print '\n saved file:', fileName
      self.labelStatus.configure(text='saved settings')

  def LoadSettings(self):
    if os.path.exists(pyching.configFile): #if a saved configuration exists
      try:
          configData= pyching_engine.Storage(pyching.configFile, data=None)
      except IOError: #just silently let this past??
        #print '\n error: unable to read configuration file', pyching.configFile
        sys.stderr.write('\n error (IOError): unable to read configuration file'+pyching.configFile+'\n')
        #tkMessageBox.showerror(title='File Error',
        #       message='Unable to read configuration file:\n'+pyching.configFile)
        pass
      except 'pychingUnpickleError': #just silently let this past??
        #print '\n error: invalid configuration file', pyching.configFile
        sys.stderr.write('\n error (pychingUnpickleError): invalid configuration file'+pyching.configFile+'\n')
        #tkMessageBox.showerror(title='File Error',
        #       message='Invalid configuration file:\n'+pyching.configFile)
        pass
      else:
        #version can be tested against pyching.version for config file compatability
        version,self.colors,castAllValue,showPlacesValue,showLineHintsValue = configData
        self.castAll.set(castAllValue)
        self.showPlaces.set(showPlacesValue)
        self.showLineHints.set(showLineHintsValue)
        #print '\n loaded config file:', pyching.configFile
    
  def __HideLabel(self,label):
    label.configure(fg=label.cget('bg'))#fg=bg to hide label  
    
  def __ToggleLabelsPlaces(self):
    if self.hexes: #if there's been a reading yet
      for label in self.labelsHexPlaces:
        if label.cget('fg') == self.colors.fgLabelPlaces:
          self.__HideLabel(label)
        else:
          label.configure(fg=self.colors.fgLabelPlaces)
  
  def ClearReading(self): 
    self.HideInfoButtons()#get rid of any info buttons  
    for coin in self.labelsCoins:#clear coins display
      coin.configure(image=self.images.coinFrames[16])
    #blank both hexagram displays
    for i in range(2):
      for line in self.hexLines[i]:
        line.Draw(linetype=None)
    for label in self.labelsHexPlaces:
      self.__HideLabel(label)
    for key in self.labelsNoMovingLines.keys():
      self.labelsNoMovingLines[key].lower()#hide the 'no moving lines' labels
    self.__HideLabel(self.labelBecomes)     
    self.labelH1Title.configure(text='')
    self.labelH2Title.configure(text='')
    self.messageQuestion.configure(text='')
    #wiped stuff still appears on win32?? - try below
    self.master.update()
  
  def CastHexes(self):
    self.labelLineHint.show = 0 #disable line hints
    #get the question
    questionDialog = DialogGetQuestion(self.master)
    self.master.update()#makes sure the main app window gets redrawn properly (seems only to be a problem on win32)
    if questionDialog.result: #the user didn't cancel
      self.ClearReading()
      for coin in self.labelsCoins:#initialise coins display
        coin.configure(image=self.images.coinFrames[0])
      self.hexes = pyching_engine.Hexagrams('coin')
      self.hexes.question = questionDialog.result
      self.ShowQuestion()
      if self.castAll.get():#cast all lines automatically
        self.CastAllLines()
      else:#cast 1 line at a time
        for menuItem in range(3,5):#disable cast-type changing while casting
          self.menuMainSettings.entryconfigure(menuItem,state=DISABLED)
        self.buttonCast.configure(text='Cast Line 1 of 6',command=self.CastNextLine)
        self.labelStatus.configure(text='Waiting to cast line 1 of 6 ...')
    else: #the user cancelled
      self.labelLineHint.show = 1 #re-enable line hints
    
  def CastNextLine(self):
    self.buttonCast.configure(state=DISABLED)
    self.CastLine()
    self.buttonCast.configure(state=NORMAL)
    if self.hexes.hex1.lineValues[5] == 0:#if hex1 is'nt fully built yet
      self.buttonCast.configure(text='Cast Line '+`self.hexes.currentLine+1`+' of 6')
      self.labelStatus.configure(text='Waiting to cast line '+`self.hexes.currentLine+1`+ ' of 6 ...')
    else:#hex1 is fully built now
      for menuItem in range(3,5):#re-enable cast-type changing
        self.menuMainSettings.entryconfigure(menuItem,state=NORMAL)
      self.buttonCast.configure(text='Create 2nd Hexagram',command=self.BuildHex2)
      self.labelStatus.configure(text='Waiting to create 2nd hexagram ...')
      
  def CastAllLines(self,loadingSaveFile=0):
    self.buttonCast.configure(state=DISABLED)
    if loadingSaveFile: 
      self.hexes.currentLine = 0
      self.ShowQuestion()
    for line in self.hexLines[0]:#build and display hexagram 1
      if loadingSaveFile: #increment the current line
        self.hexes.currentLine = self.hexes.currentLine + 1
      self.CastLine(loadingFromFile=loadingSaveFile)
      self.master.update_idletasks()
      if not loadingSaveFile: time.sleep(1)#pause to let user see the thrown coins
    self.BuildHex2()#show second hexagram
    self.buttonCast.configure(state=NORMAL)
    #self.master.update_idletasks()

  def CastLine(self,loadingFromFile=0):
    if not loadingFromFile:
      self.hexes.NewLine()
      self.labelStatus.configure(text='Casting Line '+`self.hexes.currentLine`+' of 6 ...')
      #for spins in range(3):
      for spins in range(2):
          for frameNum in range(14):
            for coin in self.labelsCoins:
              coin.configure(image=self.images.coinFrames[frameNum])
              self.master.update_idletasks()
            #time.sleep(0.06)
            #time.sleep(0.04)
            time.sleep(0.02)

      for i in range(3):
        self.labelsCoins[i].configure(image=self.images.coinFrames[self.hexes.currentOracleValues[i]+12])
        self.master.update_idletasks()

    #self.hexes.hex1.lineValues[self.hexes.currentLine-1] = 8#debug!!
    if self.showPlaces.get():#if places should be shown
      self.labelsHexPlaces[self.hexes.currentLine-1].configure(fg=self.colors.fgLabelPlaces)#show the place name
      #self.__ShowLabel(self.labelsHexPlaces[self.hexes.currentLine-1])#show the place name
    self.hexLines[0][self.hexes.currentLine-1].Draw(self.hexes.hex1.lineValues[self.hexes.currentLine-1])     
    #if loadingFromFile: self.hexLines[0][self.hexes.currentLine-1].update()
    #self.master.update_idletasks()
  
  def BuildHex2(self):
    #if self.hexes.hex1.lineValues[5] != 0:#hex1 is fully built
    self.labelH1Title.configure(text=self.hexes.hex1.number+'.  '+self.hexes.hex1.name)
    if self.hexes.hex2.lineValues[0] != 0:#if there were moving lines
      self.labelBecomes.configure(fg=self.colors.fgLabelLines)#show the 'becomes' label
      #self.__ShowLabel(self.labelBecomes)#show the 'becomes' label
      self.labelH2Title.configure(text=self.hexes.hex2.number+'.  '+self.hexes.hex2.name)
    #self.hexes.hex2.lineValues = [0,0,0,0,0,0]#debug
    i = 0#a counter
    for line in self.hexLines[1]:#display hexagram 2
      if self.hexes.hex2.lineValues[i] == 0:#empty hexagram 2
        #for lines 3,4,5 display 'no, moving, lines' message
        if self.labelsNoMovingLines.has_key(i):
          self.labelsNoMovingLines[i].tkraise()
          #self.labelsNoMovingLines[i].update()
          #print self.labelsNoMovingLines[i].cget('text') #debug
      else:#hexagram2 has lines
        line.Draw(self.hexes.hex2.lineValues[i])
      self.master.update_idletasks()
      i = i+1#increment counter

    self.labelStatus.configure(text='')
    self.labelLineHint.show = 1 #enable line hints

    for coin in self.labelsCoins:#blank coins display
      coin.configure(image=self.images.coinFrames[16])

    if not self.castAll.get():#if we were casting a line at a time
      self.buttonCast.configure(text='Cast New Hexagram',command=self.CastHexes)
    
    self.ShowInfoButtons()#show the info buttons

    self.menuMainFile.entryconfigure(1,state=NORMAL)#enable save menuitem
    self.menuMainFile.entryconfigure(3,state=NORMAL)#enable save as text menuitem

  def MakeCastDisplay(self, parent):
    self.frameCast = Frame(parent,bg=self.colors.bgReading)
    self.frameCast.pack(anchor=NW,side=TOP)#,padx=20,pady=20
    
    self.buttonCast = Button(self.frameCast,text='Cast New Hexagram',underline=0,
            width=20,bg=None,fg=None,font=self.fonts.button,highlightthickness=0,
            takefocus=FALSE,command=self.CastHexes)
    #self.buttonCast.focus_set()
    self.buttonCast.grid(column=0,row=0,sticky=NW,padx=20,pady=20)

    self.labelsCoins = []
    for i in range(3):
      self.labelsCoins.append(Label(self.frameCast,image=self.images.coinFrames[16],
              bg=self.colors.bgReading) )
      self.labelsCoins[i].grid(column=i+1,row=0,padx=10,pady=10)

    #the following widgets are not initially shown or enabled
    self.frameInfoButtons = Frame(self.frameCast,bg=self.colors.bgReading)
    self.buttonViewHex1Info = Button(self.frameInfoButtons,text=None,underline=0,
            width=30,bg=None,fg=None,font=self.fonts.button,highlightthickness=0,
            takefocus=FALSE,state=DISABLED,command=self.ViewHex1Info)
    self.buttonViewHex2Info = Button(self.frameInfoButtons,text=None,underline=1,
            width=30,bg=None,fg=None,font=self.fonts.button,highlightthickness=0,
            takefocus=FALSE,state=DISABLED,command=self.ViewHex2Info)
        
  def ShowInfoButtons(self):
    #show, setup and enable the required info buttons
    textStub = 'View information on:  '
    if self.hexes.hex2.lineValues[0] != 0:#there is a hex 2
      self.buttonViewHex2Info.configure(text=textStub+self.hexes.hex2.number+
              '. '+self.hexes.hex2.name,state=NORMAL)
      self.buttonViewHex2Info.grid(column=0,row=1)
      button1Pad = 5
    else:
      self.buttonViewHex2Info.grid_forget()
      button1Pad = 15

    self.buttonViewHex1Info.configure(text=textStub+self.hexes.hex1.number+
              '. '+self.hexes.hex1.name,state=NORMAL)
    self.buttonViewHex1Info.grid(column=0,row=0,pady=button1Pad)

    self.frameInfoButtons.grid(column=1,row=0,columnspan=3,sticky=NW,pady=5)
    #self.frameInfoButtons.lift()

  def HideInfoButtons(self):
    #hide and disable the info buttons
    self.frameInfoButtons.grid_forget()
    self.buttonViewHex1Info.configure(state=DISABLED)
    self.buttonViewHex2Info.configure(state=DISABLED)

  def ViewHex1Info(self):
    self.ShowHtml(title='Hexagram Information - '+self.hexes.hex1.number+'. '+self.hexes.hex1.name,
              htmlSource=self.hexes.hex1.infoSource)

  def ViewHex2Info(self):
    self.ShowHtml(title='Hexagram Information - '+self.hexes.hex2.number+'. '+self.hexes.hex2.name,
              htmlSource=self.hexes.hex2.infoSource)

  def MakeHexDisplay(self, parent):
    self.frameHexes = Frame(parent,bg=self.colors.bgReading)
    self.frameHexes.pack(anchor=NW,side=TOP,padx=20)
            
    self.frameSpacerC4R1 = Frame(self.frameHexes,height=8,bg=self.colors.bgReading)
    self.frameSpacerC4R1.grid(column=4,row=1)
    
    self.labelsHexPlaces = []
    labelsTexts = ('topmost','fifth','fourth','third','second','first')
    for labelNum in range(6):
      self.labelsHexPlaces.append(Label(self.frameHexes,text=labelsTexts[labelNum],
              bg=self.colors.bgReading,fg=self.colors.bgReading,font=self.fonts.label) )#fg=bg because label starts off hidden
      self.__HideLabel(self.labelsHexPlaces[labelNum])
      self.labelsHexPlaces[labelNum].grid(column=0,row=labelNum+2,sticky=E)
    self.labelsHexPlaces.reverse()#puts the labels in order of appearance

    self.labelH1Title = Label(self.frameHexes,text='',bg=self.colors.bgReading,
            fg=self.colors.fgLabelHexTitles,font=self.fonts.labelHexTitles)
    self.labelH1Title.grid(column=1,row=0)
    
    self.labelH2Title = Label(self.frameHexes,text='',bg=self.colors.bgReading,
            fg=self.colors.fgLabelHexTitles,font=self.fonts.labelHexTitles)
    self.labelH2Title.grid(column=3,row=0)

    self.labelBecomes = Label(self.frameHexes,text='becomes',bg=self.colors.bgReading,
            fg=self.colors.bgReading,font=self.fonts.label)#fg=bg because label starts off hidden
    #self.__HideLabel(self.labelBecomes)
    self.labelBecomes.grid(column=2,row=4)
    
    #lineTexts = ('no','moving','lines')#lineTexts[key-3]
    self.labelsNoMovingLines = {3:'no',4:'moving',5:'lines'}
    for key in self.labelsNoMovingLines.keys():
      self.labelsNoMovingLines[key] = Label(self.frameHexes,
            text=self.labelsNoMovingLines[key],bg=self.colors.bgReading,
            fg=self.colors.fgLabelLines,font=self.fonts.label)
      self.labelsNoMovingLines[key].grid(row=key,column=3)
      
    self.hexLines = ([],[])
    
    for hexNum in range(2):
      for lineNum in range(6):
        self.hexLines[hexNum].append(HexLine(self.frameHexes,
                bindingEnter=self.ShowLineDetails,bindingLeave=self.ClearLineDetails,
                currentColors=self.colors))
        if hexNum == 0: colNum = 1 
        else: colNum = 3 #set the corect grid column
        self.hexLines[hexNum][lineNum].grid(column=colNum,row=lineNum+2,padx=20,pady=1)
      self.hexLines[hexNum].reverse()#puts the labels in hex filling order
    
    self.frameSpacerC4R7 = Frame(self.frameHexes,width=20,bg=self.colors.bgReading)
    self.frameSpacerC4R7.grid(column=4,row=8)

    self.labelLineHint = Label(self.master,text=None,bg=self.colors.bgLabelHint,
                      fg=self.colors.fgLabelHint,font=self.fonts.labelLineHint,
                      borderwidth=1,relief=SOLID,padx=6)
    self.labelLineHint.place_forget() 
    #add an attribute to the hint label to indicate if it can currently be
    #shown or not (eg. hints should not be shown during hex building). 
    #this is independent of the user preference held in self.showLineHints .
    self.labelLineHint.show = 0 
  
  def ShowLineDetails(self,event):
    if  self.labelLineHint.show and event.widget.hint: #if hints can be shown and there is a hint for the line
      self.labelStatus.configure(text=event.widget.hint)#show line details on the status line
      if self.showLineHints.get():#if the user wants to see line details hints, show them
        self.labelLineHint.configure(text=event.widget.hint)
        self.labelLineHint.place(relx=0.1,rely=1.0,x=2,y=2,in_=event.widget)
        #self.labelLineHint.tkraise()
        self.labelLineHint.update()
        
  def ClearLineDetails(self,event):
    if event.widget.hint: #if there's a hint for the line
      self.labelStatus.configure(text='')
      if self.showLineHints.get():
        self.labelLineHint.configure(text=None)
        self.labelLineHint.place_forget() 

  def MakeQuestionDisplay(self,parent):
    #print self.frameMain.winfo_width()#cget('width')#.winfo_width()#cget(key)
    self.frameQuestion=Frame(parent,bg=self.colors.bgReading,borderwidth=2)
    self.frameQuestion.pack(anchor=SW,side=BOTTOM,expand=TRUE,fill=X,padx=10,pady=5)
    self.messageQuestion = Message(self.frameQuestion,width=20,text=None,justify=LEFT,
                         bg=self.colors.bgReading,fg=self.colors.fgMessageQuestion,
                         font=self.fonts.label)
    self.messageQuestion.pack(anchor=W)

  def ShowQuestion(self):
    #self.frameQuestion.configure(relief=GROOVE)
    self.messageQuestion.configure(width=self.frameQuestion.winfo_width(),
                         text=self.hexes.question)
  def HideQuestion(self):
    #self.frameQuestion.configure(relief=FLAT)
    self.messageQuestion.configure(width=20,text=None)

  def MakeStatusBar(self, parent):
    self.frameStatusBar = Frame(parent,borderwidth=2,relief=SUNKEN,highlightthickness=2)
    self.frameStatusBar.pack(anchor=SW,side=BOTTOM,fill=X,padx=2)#,expand=TRUE
    self.labelStatus = Label(self.frameStatusBar,
            text=pyching.title + '  ' + pyching.version,font=self.fonts.label)
    self.labelStatus.pack()

    #button for debug/testing purposes only
    #self.buttonDebug = Button(self.frameStatusBar,text='DEBUG',command=self.DEBUG)
    #self.buttonDebug.pack(anchor=E)
 
  def DEBUG(self):
    #run any routines in test or debug from here
    print 'begin debug\n'
    #
    #
    print '\nend debug'
    
  def RepaintColors(self,newColors):
    oldColors=self.colors #save them for comparison below
    self.colors=newColors
    #these are always repainted
    self.frameMain.configure(bg=self.colors.bgReading)
    self.frameCast.configure(bg=self.colors.bgReading)
    for i in range(3):
      self.labelsCoins[i].configure(bg=self.colors.bgReading)
    self.frameHexes.configure(bg=self.colors.bgReading)
    self.frameInfoButtons.configure(bg=self.colors.bgReading)
    self.frameSpacerC4R1.configure(bg=self.colors.bgReading)
    self.frameSpacerC4R7.configure(bg=self.colors.bgReading)
    self.frameQuestion.configure(bg=self.colors.bgReading)
    self.labelLineHint.configure(bg=self.colors.bgLabelHint,fg=self.colors.fgLabelHint)
    self.messageQuestion.configure(bg=self.colors.bgReading,fg=self.colors.fgMessageQuestion)
    self.labelH1Title.configure(bg=self.colors.bgReading,fg=self.colors.fgLabelHexTitles)
    self.labelH2Title.configure(bg=self.colors.bgReading,fg=self.colors.fgLabelHexTitles)
    for key in self.labelsNoMovingLines.keys():
      self.labelsNoMovingLines[key].configure(bg=self.colors.bgReading,fg=self.colors.fgLabelLines)
    #the backgrounds of these are always repainted
    for hexNum in range(2):
      for lineNum in range(6):
        self.hexLines[hexNum][lineNum].configure(bg=self.colors.bgReading)
        self.hexLines[hexNum][lineNum].colors = self.colors #so they will draw correctly next time
    #self.frameMain.update()
    #the backgrounds of these are always repainted, and fg=bg to hide them as
    #fg will be repainted in the correct color below if required
    for labelNum in range(6):
      self.labelsHexPlaces[labelNum].configure(bg=self.colors.bgReading,fg=self.colors.bgReading)
    self.labelBecomes.configure(bg=self.colors.bgReading,fg=self.colors.bgReading)
    #the foreground of these are only repainted here if a reading has been performed
    #otherwise they will paint in the new colors next time they are shown
    if self.hexes: #a reading has been performed
      if self.showPlaces.get():#if places should be shown
        for labelNum in range(6):
          self.labelsHexPlaces[labelNum].configure(fg=self.colors.fgLabelPlaces)
      if self.hexes.hex2.lineValues[0] != 0: #if there is a second hexagram
        self.labelBecomes.configure(fg=self.colors.fgLabelLines)
      #only redraw all lines now if their fg colors have changed
      if (oldColors.lineBody != self.colors.lineBody) or \
              (oldColors.lineHighlight != self.colors.lineHighlight) or \
              (oldColors.lineShadow != self.colors.lineShadow):
        for hexNum in range(2):
          for lineNum in range(6):
            self.hexLines[hexNum][lineNum].Draw(self.hexLines[hexNum][lineNum].value)
      elif oldColors.bgReading != self.colors.bgReading: #but if only the bg color has changed
        for hexNum in range(2): #repaint any 9 lines, to give the correct color in their inner circle
          for lineNum in range(6):
            if self.hexLines[hexNum][lineNum].value == 9:
              self.hexLines[hexNum][lineNum].Draw(self.hexLines[hexNum][lineNum].value)
        
  def SaveReading(self):
    fileName = tkFileDialog.asksaveasfilename(parent=self.master,
            title='Save Reading',defaultextension=pyching.saveFileExt,
            filetypes=[(pyching.title+' save files','*'+pyching.saveFileExt)],
            initialdir=pyching.savePath)
    #print fileName #debug
    if not fileName: return #user cancelled so get out
  
    #if not self.hexes: #we need an instance to work with
    # self.hexes = pyching_engine.Hexagrams()
    try: 
      #pass
      self.hexes.Save(fileName)
    except IOError:
      #print '\n error: unable to write save file', fileName
      tkMessageBox.showerror(title='File Error',
              message='Unable to write save file:\n'+fileName)
    else:
      #print '\n saved file:', fileName
      self.labelStatus.configure(text='saved reading: '+fileName)
      
  def LoadReading(self):
    self.labelLineHint.show = 0 #disable line hints
    fileName = tkFileDialog.askopenfilename(parent=self.master,
            title='Load Saved Reading',defaultextension=pyching.saveFileExt,
            filetypes=[(pyching.title+' save files','*'+pyching.saveFileExt)],
            initialdir=pyching.savePath)
    #print fileName #debug
    if not fileName: 
      self.labelLineHint.show = 1 #re-enable line hints
      return #user cancelled so get out

    tempHexes = pyching_engine.Hexagrams()
    try: 
      saveFileID = tempHexes.Load(fileName)
    except IOError:
      #print '\n error: unable to read save file', fileName
      tkMessageBox.showerror(title='File Error',
              message='Unable to load save file:\n'+fileName)
    except 'pychingUnpickleError': #the file couldn't be unpickled, most likely it isn't a pickled file
      #print '\n error: unable to unpickle file', fileName
      tkMessageBox.showerror(title='Not A Save File',
              message='The file you attempted to load:\n\n'+fileName+\
                      '\n\nis not a '+pyching.title+' save file.')
    else:
      if not saveFileID[0] == pyching.saveFileID[0]: #this isn't a valid pyching savefile
        #print '\n invalid save file:', fileName
        tkMessageBox.showerror(title='Not A Save File',
                message='The file you attempted to load:\n\n'+fileName+\
                        '\n\nis not a '+pyching.title+' save file.')
      #elif not saveFileID[1] == pyching.saveFileID[1]: #savefile fails version check
      # pass #handle any savefile version issues here
      else:     
        self.hexes = tempHexes
        self.ClearReading()
        self.CastAllLines(loadingSaveFile=1)
        self.labelStatus.configure(text='loaded reading: '+fileName)
    
    self.labelLineHint.show = 1 #re-enable line hints

  def SaveReadingAsText(self):
    fileName = tkFileDialog.asksaveasfilename(parent=self.master,
            title='Save Reading As Text',defaultextension='.txt',
            filetypes=[('text files','*.txt')],
            initialdir=pyching.savePath)
    if not fileName: return #user cancelled so get out
    
    textData = self.hexes.ReadingAsText()
    #print textData #debug
    try:
      textFile = open(fileName, 'w')
    except IOError: 
      #print '\n error: unable to create text file', fileName
      tkMessageBox.showerror(title='File Error',
              message='Unable to create text file:\n'+fileName)
    else: #no exception, so proceed
      try:
        try:
          textFile.write(textData)
        except IOError:
          #print '\n error: unable to write text file', fileName
          tkMessageBox.showerror(title='File Error',
                  message='Unable to write text file:\n'+fileName)
      finally:
        textFile.close()

class HexLine(Canvas):
  """
  creates a hexagram line object
  """
  def __init__(self,parent,bindingEnter=None,bindingLeave=None,currentColors=None):
    self.value=None
    self.hint=None
    if currentColors: self.colors = currentColors
    else: self.colors = WidgetColors()
    Canvas.__init__(self,parent,height=25,width=145,bg=self.colors.bgReading,
            takefocus=FALSE,highlightthickness=0)
    #self.tag_bind('all','<Enter>',bindingEnter)#when the mouse enters the drawing
    self.bind('<Enter>',bindingEnter)#when the mouse enters the canvas
    self.bind('<Leave>',bindingLeave)#when the mouse leaves the canvas

  def Draw(self,linetype=None):
    #draw the required linetype and set the relevant value and hint
    hints = {6: 'line value = 6 (moving yin)',7: 'line value = 7 (yang)',
            8: 'line value = 8 (yin)',9: 'line value = 9 (moving yang)'}
    self.configure(bg=self.colors.bgReading)
    self.update()
    if linetype == 6:
      self.value=linetype
      self.hint=hints[linetype]
      self.DrawBevelRectangle(origin=(1,6),height=13,width=60)
      self.DrawBevelRectangle(origin=(84,6),height=13,width=60)
      self.DrawBevelX(origin=(62,2),height=20,width=20)
    elif linetype == 7:
      self.value=linetype
      self.hint=hints[linetype]
      self.DrawBevelRectangle(origin=(2,6),height=13,width=141)
    elif linetype == 8:
      self.value=linetype
      self.hint=hints[linetype]
      self.DrawBevelRectangle(origin=(2,6),height=13,width=60)
      self.DrawBevelRectangle(origin=(83,6),height=13,width=60)
    elif linetype == 9:
      self.value=linetype
      self.hint=hints[linetype]
      self.DrawBevelRectangle(origin=(2,6),height=13,width=60)
      self.DrawBevelRectangle(origin=(83,6),height=13,width=60)
      self.DrawBevelO(origin=(62,2),height=20,width=20)
    else: #blank the canvas (clear any existing drawing)  
      self.value=None
      self.hint=None
      self.delete('all')
  
  def DrawBevelRectangle(self,origin=(0,0),height=13,width=0,bevel=2):
    
    color=self.colors.lineBody
    highlightcolor=self.colors.lineHighlight,
    shadowcolor=self.colors.lineShadow
        
    #fill with fg colour
    self.create_rectangle( ((origin[0],origin[1]),
            (origin[0]+width-1,origin[1]+height-1)),width=0,
            outline=color,fill=color,tags='BODY')

    #draw bevels
    for i in range(bevel):
      #draw highlight bevel
      if pyching.osType == 'nt': #allow for differences in tk canvas on win32
        self.create_line( ((origin[0]+i,origin[1]+height-i-1),
                (origin[0]+i,origin[1]+i),
                (origin[0]+width-i,origin[1]+i)),width=1,fill=highlightcolor,tags='HIGHLIGHT')
      else: #just draw without above modifications
        self.create_line( ((origin[0]+i,origin[1]+height-i),
                (origin[0]+i,origin[1]+i),
                (origin[0]+width-i,origin[1]+i)),width=1,fill=highlightcolor,tags='HIGHLIGHT')
      
      #draw shadow bevel
      if pyching.osType == 'nt': #allow for differences in tk canvas on win32
        self.create_line(  ( (origin[0]+i+1,origin[1]+((height-i)-1)),
                              (origin[0]+(width-i),origin[1]+((height-i)-1)) ),
                              width=1,fill=shadowcolor,tags='SHADOW')
        self.create_line(  ( (origin[0]+(width-i)-1,origin[1]+((height-i)-1)),
                              (origin[0]+(width-i)-1,origin[1]+i) ),
                              width=1,fill=shadowcolor,tags='SHADOW')
      else: #just draw without above modifications
        self.create_line(  ( (origin[0]+i+1,origin[1]+((height-i)-1)),
                              (origin[0]+(width-i),origin[1]+((height-i)-1)) ),
                              width=1,fill=shadowcolor,tags='SHADOW')
        self.create_line(  ( (origin[0]+(width-i)-1,origin[1]+((height-i)-1)),
                              (origin[0]+(width-i)-1,origin[1]+i+1) ),
                              width=1,fill=shadowcolor,tags='SHADOW')

  def DrawBevelX(self,origin=(0,0),height=0,width=0,thickness=4):
    
    color=self.colors.lineBody
    highlightcolor=self.colors.lineHighlight,
    shadowcolor=self.colors.lineShadow
        
    #filled cross with shadowcolor outline
    centre = ( origin[0]+width/2 , origin[1]+height/2 )
    iCO = thickness/2+2 #insideCornerOffset
    self.create_polygon(( ( origin[0]+thickness , origin[1] ),
                            ( centre[0] , centre[1]-iCO ),
                            ( origin[0]+width-thickness , origin[1] ),
                            ( origin[0]+width , origin[1]+thickness ),
                            ( centre[0]+iCO , centre[1] ),
                            ( origin[0]+width , origin[1]+height-thickness ),
                            ( origin[0]+width-thickness , origin[1]+height ),
                            ( centre[0] , centre[1]+iCO ),
                            ( origin[0]+thickness , origin[1]+height ),
                            ( origin[0] , origin[1]+height-thickness ),
                            ( centre[0]-iCO , centre[1] ),
                            ( origin[0] , origin[1]+thickness ) ),
                            outline=shadowcolor,width=1,fill=color,tags='BODY')     
    #pad the shadow to bevel width
    self.create_line( ( ( origin[0] +2, origin[1]+thickness +1),
                          ( centre[0]-iCO +1, centre[1] ) ),
                          width=1,fill=shadowcolor,tags='SHADOW')
    self.create_line( ( ( centre[0]+iCO , centre[1] -1),
                          ( origin[0]+width -1, origin[1]+thickness) ),
                          width=1,fill=shadowcolor,tags='SHADOW')
    self.create_line( ( ( origin[0] +2, origin[1]+height-thickness +1),
                          ( origin[0]+thickness , origin[1]+height -1),
                          ( centre[0] , centre[1]+iCO -1),
                          ( origin[0]+width-thickness , origin[1]+height -1),
                          ( origin[0]+width -1, origin[1]+height-thickness) ),
                          width=1,fill=shadowcolor,tags='SHADOW')
    #add the highlight
    self.create_line( ( ( origin[0] , origin[1]+height-thickness ),
                          ( centre[0]-iCO +1  , centre[1] -1 ) ),
                          width=1,fill=highlightcolor,tags='HIGHLIGHT')
    self.create_line( ( ( origin[0] +1, origin[1]+height-thickness ),
                          ( centre[0]-iCO +1  +1, centre[1] -1 ) ),
                          width=1,fill=highlightcolor,tags='HIGHLIGHT')
    self.create_line( ( ( centre[0]+iCO , centre[1] ),
                          ( origin[0]+width +1 , origin[1]+height-thickness +1 ) ),
                          width=1,fill=highlightcolor,tags='HIGHLIGHT')
    self.create_line( ( ( centre[0]+iCO -1, centre[1] ),
                          ( origin[0]+width, origin[1]+height-thickness +1) ),
                          width=1,fill=highlightcolor,tags='HIGHLIGHT')
    self.create_line( ( ( origin[0] , origin[1]+thickness ),
                          ( origin[0]+thickness , origin[1] ),
                          ( centre[0] , centre[1]-iCO ),
                          ( origin[0]+width-thickness , origin[1] ),
                          ( origin[0]+width +1 , origin[1]+thickness +1 ) ),
                          width=1,fill=highlightcolor,tags='HIGHLIGHT')
    self.create_line( ( ( origin[0] +1 , origin[1]+thickness ),
                          ( origin[0]+thickness , origin[1] +1),
                          ( centre[0] , centre[1]-iCO +1),
                          ( origin[0]+width-thickness , origin[1] +1),
                          ( origin[0]+width , origin[1]+thickness +1 ) ),
                          width=1,fill=highlightcolor,tags='HIGHLIGHT')

  def DrawBevelO(self,origin=(0,0),height=0,width=0,thickness=4):
    color=self.colors.lineBody
    highlightcolor=self.colors.lineHighlight,
    shadowcolor=self.colors.lineShadow
        
    #draw filled O with shadowcolor border
    self.create_oval( ( ( origin[0] , origin[1] ), 
                          ( origin[0]+width , origin[1]+height ) ), 
                          outline=shadowcolor,width=1,fill=shadowcolor)
    self.create_oval( ( ( origin[0]+1 , origin[1]+1 ), 
                          ( origin[0]+width-1 , origin[1]+height-1 ) ), 
                          outline=shadowcolor,width=1,fill=color,tags='BODY')#,fill=color
    #draw central O with shadowcolor border
    self.create_oval( ( ( origin[0]+thickness+1 , origin[1]+thickness+1 ), 
                          ( origin[0]+width-thickness-1 , origin[1]+height-thickness-1) ), 
                          outline=shadowcolor,width=1,fill=shadowcolor)
    self.create_oval( ( ( origin[0]+thickness+2 , origin[1]+thickness+2 ), 
                          ( origin[0]+width-thickness-2 , origin[1]+height-thickness-2) ), 
                          outline=shadowcolor,width=1,fill=self.cget('bg'),tags='SHADOW')#,fill='DarkGray'
    #add highlights
    #top
    self.create_arc(  ( ( origin[0] , origin[1] ), 
                          ( origin[0]+width , origin[1]+height ) ),
                          start=40,extent=180,
                          style='arc',width=1,outline=highlightcolor,tags='HIGHLIGHT')#'red' 
    self.create_arc(  ( ( origin[0]+1 , origin[1]+1 ), 
                          ( origin[0]+width-1 , origin[1]+height-1 ) ),
                          start=35,extent=180,
                          style='arc',width=1,outline=highlightcolor,tags='HIGHLIGHT')#'black' highlightcolor

    #lower inside
    self.create_arc(  ( ( origin[0]+thickness+1 , origin[1]+thickness+1 ), 
                          ( origin[0]+width-thickness-1 , origin[1]+height-thickness-1) ),
                          start=220,extent=180,
                          style='arc',width=1,outline=highlightcolor,tags='HIGHLIGHT')#'red'
    self.create_arc(  ( ( origin[0]+thickness+2 , origin[1]+thickness+2 ), 
                          ( origin[0]+width-thickness-2 , origin[1]+height-thickness-2) ),
                          start=215,extent=180,
                          style='arc',width=1,outline=highlightcolor,tags='HIGHLIGHT')#'black'
    #patch the highlight drawing to compensate for 
    #differences(!) in behaviour of tk canvas on win32
    if pyching.osType == 'nt':
      self.create_line( ( (origin[0]+1 , origin[1]+13),
                            (origin[0]+1 , origin[1]+6), 
                            (origin[0]+6 , origin[1]+1),
                            (origin[0]+8 , origin[1]+1),
                            (origin[0]+14 , origin[1]+1),
                            (origin[0]+16 , origin[1]+3) ),
                            width=1,fill=highlightcolor,tags='HIGHLIGHT')
      self.create_line( ( (origin[0]+7 , origin[1]+13),
                            (origin[0]+8 , origin[1]+14), 
                            (origin[0]+12 , origin[1]+14),
                            (origin[0]+15 , origin[1]+11) ),
                            width=1,fill=highlightcolor,tags='HIGHLIGHT')

class DialogSetColors(smgDialog):
  """
  display a colour configuration dialog
  """
  def __init__(self,parent,currentColors=None):
    if currentColors: self.colors=currentColors
    else: self.colors=WidgetColors()
    self.fonts=WidgetFonts()
    smgDialog.__init__(self,parent,title='Configure Colors',
          buttons=[{'name':'buttonOk','title':'Ok','binding':'Ok','underline':None,'hotKey':'<Return>'},
                {'name':'buttonCancel','title':'Cancel','binding':'Cancel','underline':None,'hotKey':'<Escape>'}],
          buttonsDef=-1, buttonsPos='BOTTOM',buttonsWidth=0,buttonsPad=5, 
          resizeable=0, transient=1, wait=1)

  def Body(self,master):
    master.configure(borderwidth=2,relief=SUNKEN,highlightthickness=4)
    
    self.frameDemo = Frame(master,bg=self.colors.bgReading,borderwidth=2,relief=FLAT)
    self.frameDemo.grid(row=1,column=0,padx=0,pady=10)
    self.frameDemo.bind('<ButtonPress-1>',self.SetColorExampleDetails)

    self.labelTitleDemo = Label(self.frameDemo,text='Hexagram Title',
            font=self.fonts.labelHexTitles,borderwidth=0,
            fg=self.colors.fgLabelHexTitles,bg=self.colors.bgReading)
    self.labelTitleDemo.grid(row=0,column=0,pady=5)
    self.labelTitleDemo.bind('<ButtonPress-1>',self.SetColorExampleDetails)
    
    self.lineDemos = []
    for i in range(4):
      self.lineDemos.append(HexLine(self.frameDemo,currentColors=self.colors))
      #self.lineDemos[i].colors = self.colors
      self.lineDemos[i].Draw(linetype=i +6)  
      self.lineDemos[i].grid(row=i+1,column=0,padx=10)  
      self.lineDemos[i].bind('<ButtonPress-1>',self.SetColorExampleDetails)
      #self.tag_bind('BODY','<ButtonPress-1>',self.SetColorExampleDetails)
      #self.tag_bind('HIGHLIGHT','<ButtonPress-1>',self.SetColorExampleDetails)
      #self.tag_bind('SHADOW','<ButtonPress-1>',self.SetColorExampleDetails)
    
    self.labelQuestionDemo = Label(self.frameDemo,text='Reading Question',
            font=self.fonts.label,borderwidth=0,
            fg=self.colors.fgMessageQuestion,bg=self.colors.bgReading)
    self.labelQuestionDemo.grid(row=5,column=0,pady=5)
    self.labelQuestionDemo.bind('<ButtonPress-1>',self.SetColorExampleDetails)

    self.labelPlaceDemo = Label(self.frameDemo,text='place name',
            font=self.fonts.label,borderwidth=0,
            fg=self.colors.fgLabelPlaces,bg=self.colors.bgReading)
    self.labelPlaceDemo.grid(row=1,column=1,padx=5)
    self.labelPlaceDemo.bind('<ButtonPress-1>',self.SetColorExampleDetails)

    self.labelBecomesDemo = Label(self.frameDemo,text='becomes',
            font=self.fonts.label,borderwidth=0,
            fg=self.colors.fgLabelLines,bg=self.colors.bgReading)
    self.labelBecomesDemo.grid(row=2,column=1,padx=5)
    self.labelBecomesDemo.bind('<ButtonPress-1>',self.SetColorExampleDetails)

    self.labelNoMovingDemo = Label(self.frameDemo,text='no moving lines',
            font=self.fonts.label,borderwidth=0,
            fg=self.colors.fgLabelLines,bg=self.colors.bgReading)
    self.labelNoMovingDemo.grid(row=3,column=1,padx=5)
    self.labelNoMovingDemo.bind('<ButtonPress-1>',self.SetColorExampleDetails)

    self.frameHintBgDemo = Label(self.frameDemo,text='line hint',
            font=self.fonts.labelLineHint,relief=SOLID,borderwidth=1,
            bg=self.colors.bgLabelHint)
    self.frameHintBgDemo.grid(row=4,column=1,sticky=(N,S,E,W),padx=5)#padx=5
    self.frameHintBgDemo.bind('<ButtonPress-1>',self.SetColorExampleDetails)

    self.labelHintDemo = Label(self.frameHintBgDemo,text='line hint',
            font=self.fonts.labelLineHint,borderwidth=0,
            fg=self.colors.fgLabelHint,bg=self.colors.bgLabelHint)
    self.labelHintDemo.grid(row=0,column=0)
    self.labelHintDemo.bind('<ButtonPress-1>',self.SetColorExampleDetails)
    
    self.SetColorButtonDetails()

    self.colorExampleDetails = { 'name': self.colorButtonDetails[0][0],
                          'color': self.colorButtonDetails[0][1] }

    self.frameColorSelect = Frame(master,bg=self.colorExampleDetails['color'],
            relief=SOLID,borderwidth=1)
    self.frameColorSelect.grid(row=0,column=0,padx=10,pady=10,ipadx=10,ipady=10)
    
    self.buttonGetColor =  Button(self.frameColorSelect,text='Set Color of:',
            underline=0,highlightthickness=0,font=self.fonts.button,
            takefocus=FALSE,command=self.GetColor)
    self.buttonGetColor.grid(row=0,column=0,padx=2)

    self.menubuttonOptions = Menubutton(self.frameColorSelect,width=32,
            text=self.colorButtonDetails[0][0],font=self.fonts.menu,indicatoron=TRUE,
            underline=string.find(self.colorButtonDetails[0][0],'e'),
            relief=RAISED,highlightthickness=0)
    self.menubuttonOptions.grid(row=0,column=1,padx=2,sticky=(N,S))#sticky=(N,S) makes the menubutton the smae height as buttonGetCOlor
    self.menuOptions = Menu(self.menubuttonOptions,tearoff=0,font=self.fonts.menu)
    self.menubuttonOptions.configure(menu=self.menuOptions)
    for item in self.colorButtonDetails: #build the menu
      self.menuOptions.add_command(label=item[0],command=self.SetColorExample)
    #self.menuOptions.bind('<<MenuSelect>>',self.SetColorExampleDetails)

    self.buttonDefaults = Button(master,
            text='Reset All Colors To '+pyching.title+' Defaults',
            underline=0,highlightthickness=0,font=self.fonts.button,
            takefocus=FALSE,command=self.SetDefaultColors)
    self.buttonDefaults.grid(row=2,column=0,padx=10,pady=10,sticky=(W,E))

    #return self.menubuttonOptions #control for initial focus

    #hot key bindings for this dialog
    self.bind('<Alt-s>',self.SetColorButtonBinding) #set color button
    self.bind('<Alt-e>',self.ScreenElementButtonBinding) #screen element selection button
    self.bind('<Alt-r>',self.ResetColorsButtonBinding) #reset default colors button

  def SetColorButtonBinding(self,event):
    self.buttonGetColor.invoke()

  def ScreenElementButtonBinding(self,event):
    #self.menubuttonOptions.invoke()
    #print self.menubuttonOptions.winfo_rootx(),self.menubuttonOptions.winfo_rooty(),\
    #   self.menubuttonOptions.winfo_height() #debug
    self.menuOptions.post(self.menubuttonOptions.winfo_rootx(),
        self.menubuttonOptions.winfo_rooty()+self.menubuttonOptions.winfo_height())
    #self.menuOptions.tk_mbPost()

  def ResetColorsButtonBinding(self,event):
    self.buttonDefaults.invoke()

  def SetColorButtonDetails(self):
    self.colorButtonDetails=( 
        ('Reading Background',self.colors.bgReading),
        ('Hexagram Titles',self.colors.fgLabelHexTitles),
        ('Reading Question',self.colors.fgMessageQuestion),
        ('Place Names',self.colors.fgLabelPlaces),
        ("'becomes' & 'no moving lines'",self.colors.fgLabelLines),
        ('Line Hint Background',self.colors.bgLabelHint),
        ('Line Hint Text',self.colors.fgLabelHint),
        ('Hexagram Line Body',self.colors.lineBody),
        ('Hexagram Line Highlight',self.colors.lineHighlight),
        ('Hexagram Line Shadow',self.colors.lineShadow) )

  def SetColorExampleDetails(self,event):
    if event.widget == self.frameDemo:
      self.colorExampleDetails['name']=self.colorButtonDetails[0][0]
      self.colorExampleDetails['color']=self.colorButtonDetails[0][1]
    elif event.widget == self.labelTitleDemo:
      self.colorExampleDetails['name']=self.colorButtonDetails[1][0]
      self.colorExampleDetails['color']=self.colorButtonDetails[1][1]
    elif event.widget == self.labelQuestionDemo:
      self.colorExampleDetails['name']=self.colorButtonDetails[2][0]
      self.colorExampleDetails['color']=self.colorButtonDetails[2][1]
    elif event.widget == self.labelPlaceDemo:
      self.colorExampleDetails['name']=self.colorButtonDetails[3][0]
      self.colorExampleDetails['color']=self.colorButtonDetails[3][1]
    elif event.widget == self.labelBecomesDemo:
      self.colorExampleDetails['name']=self.colorButtonDetails[4][0]
      self.colorExampleDetails['color']=self.colorButtonDetails[4][1]
    elif event.widget == self.labelNoMovingDemo:
      self.colorExampleDetails['name']=self.colorButtonDetails[4][0]
      self.colorExampleDetails['color']=self.colorButtonDetails[4][1]
    elif event.widget == self.frameHintBgDemo:
      self.colorExampleDetails['name']=self.colorButtonDetails[5][0]
      self.colorExampleDetails['color']=self.colorButtonDetails[5][1]
    elif event.widget == self.labelHintDemo:
      self.colorExampleDetails['name']=self.colorButtonDetails[6][0]
      self.colorExampleDetails['color']=self.colorButtonDetails[6][1]
    elif event.widget in self.lineDemos:
      tags = string.split(event.widget.itemcget('current','tags'))
      #print tags
      if 'SHADOW' in tags:
        self.colorExampleDetails['name']=self.colorButtonDetails[9][0]
        self.colorExampleDetails['color']=self.colorButtonDetails[9][1]
      elif 'HIGHLIGHT' in tags:
        self.colorExampleDetails['name']=self.colorButtonDetails[8][0]
        self.colorExampleDetails['color']=self.colorButtonDetails[8][1]
      elif 'BODY' in tags:
        self.colorExampleDetails['name']=self.colorButtonDetails[7][0]
        self.colorExampleDetails['color']=self.colorButtonDetails[7][1]
      else: #background clicked
        self.colorExampleDetails['name']=self.colorButtonDetails[0][0]
        self.colorExampleDetails['color']=self.colorButtonDetails[0][1]

    self.SetColorExample()
  
  def SetColorExample(self):
    #print 'active menuitem is:',self.menuOptions.index('active') #debug
    if self.menuOptions.index('active') != None: #if this routine was called by self.menuOptions
      self.colorExampleDetails['name']=self.colorButtonDetails[self.menuOptions.index('active')][0]
      self.colorExampleDetails['color']=self.colorButtonDetails[self.menuOptions.index('active')][1]
    
    self.menubuttonOptions.configure(text=self.colorExampleDetails['name'],
        underline=string.find(self.colorExampleDetails['name'],'e'))
    self.frameColorSelect.configure(bg=self.colorExampleDetails['color'])
    
  def GetColor(self):
    rgbTuplet, colorString = tkColorChooser.askcolor(parent=self,
            title='Pick a new colour for: '+self.colorExampleDetails['name'],
            initialcolor=self.colorExampleDetails['color'])#._root()
    if colorString: #user didn't cancel
      self.frameDemo.update() #redraw after dialog
      #print 'GetColor colorString =',colorString
      self.colorExampleDetails['color']=colorString
      self.frameColorSelect.configure(bg=colorString)
      self.UpdateColors()
      self.RepaintColors()
      #self.frameDemo.update() #redraw new colors
  
  def UpdateColors(self):
    if self.colorExampleDetails['name'] == self.colorButtonDetails[0][0]: 
      self.colors.bgReading = self.colorExampleDetails['color']
    elif self.colorExampleDetails['name'] == self.colorButtonDetails[1][0]: 
      self.colors.fgLabelHexTitles = self.colorExampleDetails['color']
    elif self.colorExampleDetails['name'] == self.colorButtonDetails[2][0]: 
      self.colors.fgMessageQuestion = self.colorExampleDetails['color']
    elif self.colorExampleDetails['name'] == self.colorButtonDetails[3][0]: 
      self.colors.fgLabelPlaces = self.colorExampleDetails['color']
    elif self.colorExampleDetails['name'] == self.colorButtonDetails[4][0]: 
      self.colors.fgLabelLines = self.colorExampleDetails['color']
    elif self.colorExampleDetails['name'] == self.colorButtonDetails[5][0]: 
      self.colors.bgLabelHint = self.colorExampleDetails['color']
    elif self.colorExampleDetails['name'] == self.colorButtonDetails[6][0]: 
      self.colors.fgLabelHint = self.colorExampleDetails['color']
    elif self.colorExampleDetails['name'] == self.colorButtonDetails[7][0]: 
      self.colors.lineBody = self.colorExampleDetails['color']
    elif self.colorExampleDetails['name'] == self.colorButtonDetails[8][0]: 
      self.colors.lineHighlight = self.colorExampleDetails['color']
    elif self.colorExampleDetails['name'] == self.colorButtonDetails[9][0]: 
      self.colors.lineShadow = self.colorExampleDetails['color']
  
  def RepaintColors(self):
    #print 'ConfigureWidgetCustomColors frameDemo bg was =',self.frameDemo.cget('bg') 
    self.frameDemo.configure(bg=self.colors.bgReading)
    #print 'ConfigureWidgetCustomColors frameDemo bg is =',self.frameDemo.cget('bg') 
    self.labelTitleDemo.configure(fg=self.colors.fgLabelHexTitles,bg=self.colors.bgReading)
    self.labelQuestionDemo.configure(fg=self.colors.fgMessageQuestion,bg=self.colors.bgReading)
    self.labelPlaceDemo.configure(fg=self.colors.fgLabelPlaces,bg=self.colors.bgReading)
    self.labelBecomesDemo.configure(fg=self.colors.fgLabelLines,bg=self.colors.bgReading)
    self.labelNoMovingDemo.configure(fg=self.colors.fgLabelLines,bg=self.colors.bgReading)
    self.frameHintBgDemo.configure(bg=self.colors.bgLabelHint)
    self.labelHintDemo.configure(fg=self.colors.fgLabelHint,bg=self.colors.bgLabelHint)
    self.frameDemo.update()
    for item in self.lineDemos: 
      #item.configure(bg=self.colors.bgReading)
      item.colors = self.colors
      item.Draw(linetype=item.value)  

    self.SetColorButtonDetails()

  def SetDefaultColors(self):
    self.colors=WidgetColors()
    self.RepaintColors()
    self.colorExampleDetails['name']=self.colorButtonDetails[0][0]
    self.colorExampleDetails['color']=self.colorButtonDetails[0][1]
    self.SetColorExample()

  def Apply(self):
    self.result = self.colors

class DialogGetQuestion(smgDialog):
  """
  gets the question for a reading
  """ 
  def __init__(self,parent):
    smgDialog.__init__(self,parent,title='Enter Question',
          buttons=[{'name':'buttonOk','title':'Ok','binding':'Ok','underline':None,'hotKey':'<Return>'},
                {'name':'buttonCancel','title':'Cancel','binding':'Cancel','underline':None,'hotKey':'<Escape>'}],
          buttonsDef=-1, buttonsPos='BOTTOM',buttonsWidth=0,buttonsPad=5, 
          resizeable=0, transient=1, wait=1)

  def Body(self,master):
    labelPrompt = Label(master,text='Enter a question to ask the I Ching (maximum 70 characters):',
            ).grid(column=0,row=0,sticky=W,padx=5,pady=5)
    self.questionText = StringVar()
    self.questionText.set('Tell me about my current circumstances.')
    self.entryQuestion = Entry(master,textvariable=self.questionText,width=70)
    self.entryQuestion.grid(column=0,row=1,sticky=W,padx=5)
    return self.entryQuestion
  
  def Apply(self):
    self.result = self.questionText.get()
     
  def Validate(self):
    if len(self.questionText.get()) > 70:#question too long
      tkMessageBox.showerror(title='Question Too Long',
                        message='The question you have entered is longer than 70 characters.')
      return 0
    elif len(string.strip(self.questionText.get())) == 0:#null question
      tkMessageBox.showerror(title='No Question Entered',
                        message='You have entered a blank question.')
      return 0
    else:
      return 1
    
#create an instance of the app details for use throughout this module
pyching = pyching_engine.PychingAppDetails()

windowRoot = Tk()
windowMain = WindowMain(windowRoot)
windowRoot.mainloop()
