#!/usr/bin/env python

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

# external modules

import os,math
import sets
import Tkinter
from Tkinter import *
import Pmw
import distutils.spawn 
import traceback
import pymol
from pymol import cmd

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

# SimTk modules

import UtilitiesSimTk
import LogSimTk 
from IsimGridFileSimTk import *
from IsimDisplaySimTk import *
from ApbsGridSimTk import *

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

# global macromolecule name

moleculeLoadedIntoPymol = None
globalStatProfiles      = None

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

# base class for interfaces -- contains
# common fields/methods

class IsimInterface( Pmw.Group ): # {

   def __init__( self, *args, **kwargs ):

      Pmw.Group.__init__( self, *args, **kwargs )

      self.show_ms                         = False
      self.show_pi                         = False
      self.show_ni                         = False
      self._isimDisplay                    = None
      self._mapSet                         = None
      self._macromoleculeSet               = None
      self._maxMaps                        = 3
      self._mapTypeDict                    = None
      self._mapTypeOrderedList             = None
      self._mapTypeDictInverse             = None
      self._stepList                       = None
      self._pymolDeleteDict                = None
      self._singletButtons                 = 0
      self._fileNameMatchList              = None
      self._ionAbbreviations               = {}
      self._observableStats                = {}

      self._moleculeCheckButton            = None
      self._potentialCheckButton           = None

      self._moleculeCheckButtonSetting     = None
      self._potentialCheckButtonSetting    = None

      self._colorRampName                  = None

      self._mapTypeMenus                   = None
      self._mapStepMenus                   = None
      self._mapCheckButtonSetting          = None
      self._mapCheckButton                 = None
      self._mapLowerThresoldEntry          = None
      self._mapMiddleThresoldEntry         = None
      self._mapUpperThresoldEntry          = None
      self._mapLevelEntry                  = None
      self._mapStatButtonEntry             = None
      self._unitTypeMenus                  = None
      self._AbsoluteValue                  = 'Absolute Value'
      self._PotentialValue                 = 'kJ/e*mol'
      self._NumberDensityValue             = 'N/A**3'
      self._ChargeDensityValue             = 'e/A**3'
      self._StdDevUnits                    = 'Units of Std. Dev.'
      self._BulkNumberDensityUnits         = 'Bulk Density'
      self._kTUnits                        = 'kT/e'

      self._numberDensityUnitTypeList      = [ self._NumberDensityValue, self._StdDevUnits, self._BulkNumberDensityUnits, ]
      self._espUnitTypeList                = [ self._PotentialValue, self._StdDevUnits, self._kTUnits, ]
      self._chargeDensityUnitTypeList      = [ self._ChargeDensityValue, self._StdDevUnits ]

      self._defaultUnitsOfStdDev           = 1.0

      self._SurfaceProfile                 = 'Surface'
      self._BulkProfile                    = 'Bulk'

      self._IsimPotentialStats             = None
      self._IsimChargeDensityStats         = None
      self._IsimNumberDensityStats         = []
      for ii in range( 4 ):
         self._IsimNumberDensityStats.append( None )

      self._ApbsPotential                  = -1
      self._IsimPotential                  = -1
      self._ChargeDensity                  = -1
      self._NumberDensity                  = -1

      # should be lower case

      self._bgColor                        = 'white'
      self._isSimple                       = 0

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

   # set accessor for IsimDisplay

   def setIsimDisplay( self, isimDisplay ): # {
      """Set accessor for IsimDisplay """
      self._isimDisplay = isimDisplay 
   # } end of setIsimDisplay

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

   # get accessor for IsimDisplay

   def getIsimDisplay( self ): # {
      """Get accessor for IsimDisplay """
      return self._isimDisplay;
   # } end of getIsimDisplay

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

   # get accessor for conversion from kJ/mol to kT/e units

   def getkJTokTConversionFactor( self ): # {
      """Get accessor for conversion from kT/e to kJ/mol"""

      # return self._isimDisplay.getTemperature()*1.355e-03;

      return self._isimDisplay.getTemperature()*8.2e-03;

   # } end of getkJTokTConversionFactor 

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

   # get file type based on menu selection

   def getFileTypeIndexGivenSelectionString( self, selectionString ): # {

      return self._mapTypeDict[selectionString]
   
   # } end of getFileTypeIndexGivenSelectionString

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

   def getFilePrefixGivenFileTypeIndex( self, fileIndex ): # {
      """Get file prefix given index into fileMap"""
      if fileIndex is self._ApbsPotential:
         return 0
      else:
         if fileIndex is self._IsimPotential:
            filePrefix = IsimPotentialFileSimTk.getFilePrefix()
         elif fileIndex is self._ChargeDensity:
            filePrefix = IsimChargeDensityFileSimTk.getFilePrefix()
         elif fileIndex >= self._NumberDensity:
            filePrefix = IsimNumberDensityFileSimTk.getFilePrefix()
         else:
            raise ValueError, "Unrecognized selection=" + fileIndex + "\n"

         return filePrefix

   # } end of getFilePrefixGivenFileTypeIndex

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

   def getFileIonTypeGivenFileTypeIndex( self, fileIndex ): # {
      """Get file ion index given index into fileMap"""
      if self._NumberDensity < 0 or fileIndex < self._NumberDensity:
         return None
      else:
         return fileIndex - self._NumberDensity + 1

   # } end of getFileIonTypeGivenFileTypeIndex 

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

   def getFileIonNameGivenFileTypeIndex( self, fileIndex ): # {
      """Get file ion name given index into fileMap"""
      if self._NumberDensity < 0 or fileIndex < self._NumberDensity:
         return None
      else:
         ionIndex = fileIndex - self._NumberDensity + 1
         return self._isimDisplay.getIonNameGivenIonIndex( ionIndex )

   # } end of getFileIonNameGivenFileTypeIndex 

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

   def _addMapTypeEntry( self, entry, mapIndex ): # {
      """Helper method to add map types"""

      self._mapTypeOrderedList.append( entry )
      self._mapTypeDict[self._mapTypeOrderedList[-1]] = mapIndex
      mapIndex += 1
      return mapIndex

   # } end of _addMapTypeEntry

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

   def setMapTypes( self, allTypes ): # {
      """Set map types"""

      self._mapTypeDict                            = {}
      self._mapTypeOrderedList                     = []

      # mapIndex incremented on each call to _addMapTypeEntry

      mapIndex                                     = 0

      self._IsimPotential  = mapIndex
      mapIndex = self._addMapTypeEntry( 'Isim Potential(w/ ions)', mapIndex )

      self._ApbsPotential  = mapIndex
      mapIndex = self._addMapTypeEntry( 'Apbs Potential(no ions)', mapIndex )
       
      if allTypes > 0:
         self._ChargeDensity  = mapIndex
         mapIndex = self._addMapTypeEntry( 'Charge density', mapIndex )
   
         # number density handled differently since no. ions determined at run time
   
         self._NumberDensity  = mapIndex
   
         isimDisplay = self.getIsimDisplay()
         for ionIndex in range ( isimDisplay.getNumberOfIons() ):
            ionName                                             = isimDisplay.getIonNameGivenIonIndex( ionIndex + 1 )
            ionAbbreviation                                     = isimDisplay.getIonAbbreviationGivenIonName( ionName )
            key                                                 = ionAbbreviation + ' NumberDensity'
            index                                               = self._NumberDensity + ionIndex
            self._mapTypeDict[key]                              = index
            self._ionAbbreviations[index]                       = ionAbbreviation
            self._mapTypeOrderedList.append( key )
      else:
         self._NumberDensity  = -1
         self._ChargeDensity  = -1
   
      self._mapTypeDictInverse                           = {}
      for key, value in self._mapTypeDict.iteritems():
         self._mapTypeDictInverse[value] = key

   # } end of setMapTypes

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

   def getMapTypes( self, allTypes ): # {
      """Get map types"""

      if self._mapTypeDict is None:
         self.setMapTypes( allTypes )

      return self._mapTypeOrderedList

   # } end of getMapTypes

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

   def loadMacromolecules( self ): # {

      """Load macromolecules"""

      # load in molecule from isim.in file and any others present in Pymol

      if self._macromoleculeSet is None:
         self._macromoleculeSet = sets.Set()
      else:
         self._macromoleculeSet.clear()

      self.getSetOfPymolObjectsByType( 'object:molecule', self._macromoleculeSet )
     
      isimMolecule = self.getIsimDisplay().getMacromoleculeFileName( True )
      if isimMolecule in self._macromoleculeSet:
         self._macromoleculeSet.remove( isimMolecule )

      macromoleculeList = [ ii for ii in self._macromoleculeSet ]
      macromoleculeList.insert( 0, isimMolecule )

      return macromoleculeList

   # } end of loadMacromolecules

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

   def isObjectLoadedInPymol( self, objectName, objectType ): # {

      """Load macromolecules from isim.in file and any that are loaded in Pymol"""

      # load in molecule from isim.in file and any others present in Pymol

      matchSet = sets.Set()
      self.getSetOfPymolObjectsByType( 'object:' + objectType, matchSet )
     
      if objectName in matchSet:
         return 1
      else:
         return 0

   # } end of loadMacromolecules

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

   def loadMoleculeIntoPymol( self ): # {

      """Load macromolecule into Pymol -- once only"""

      global moleculeLoadedIntoPymol

      moleculeNameList          = self.getMoleculeName()
      isimMoleculeWithSuffix    = moleculeNameList[0]
      isimMoleculeWithoutSuffix = moleculeNameList[1]

      if moleculeLoadedIntoPymol == None or not self.isObjectLoadedInPymol( isimMoleculeWithoutSuffix, 'molecule' ):

         fullFileName = self.getIsimDisplay().getWorkingDirectoryName() + \
                        self.getIsimDisplay().getFilePathSeparator()    + \
                        isimMoleculeWithSuffix

         moleculeLoadedIntoPymol = isimMoleculeWithoutSuffix
         self.loadFileIntoPymol( fullFileName, isimMoleculeWithoutSuffix, 'object:molecule' )

      self.addToPymolDeleteList( moleculeLoadedIntoPymol, 0 )

      return moleculeLoadedIntoPymol

   # } end of loadMoleculeIntoPymol

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

   def getMoleculeName( self ): # {

      """Get macromolecule name w/ and w/o suffix"""

      isimMoleculeWithSuffix = self.getIsimDisplay().getMacromoleculeFileName( False )
      return [ isimMoleculeWithSuffix, UtilitiesSimTk.UtilitiesSimTk.removeSuffix( isimMoleculeWithSuffix ) ]

   # } end of getMoleculeName

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

   def loadFileIntoPymol( self, fileName, objectName = None, objectType = None ): # {

      """Load file into Pymol, if not already loaded"""

      # get object name used by Pymol

      if objectName is None:
         filePartsList = UtilitiesSimTk.UtilitiesSimTk.parseFileName( fileName )
         objectName    = filePartsList[1]

      # get object type used by Pymol
      
      if objectType is None:
         filePartsList = UtilitiesSimTk.UtilitiesSimTk.parseFileName( fileName )
         if filePartsList[2] == 'pdb' or filePartsList[2] == 'pqr':
            objectType = 'object:molecule'
         elif filePartsList[2] == 'dx':
            objectType = 'object:map'

      # check if object already loaded

      objectLoaded = 0
      for ii in pymol.cmd.get_names():
         if ii == objectName and (objectType is None or objectType == pymol.cmd.get_type(ii) ):
            objectLoaded = 1
            break

      # load object, if not loaded

      if not objectLoaded:
         pymol.cmd.load( fileName, objectName )

   # } end of loadMacromolecules

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

   def loadMaps( self ): # {

      """Load maps from isim.in file and any that are loaded in Pymol"""

      # load in molecule from isim.in file

      if self._mapSet is None:
         self._mapSet = sets.Set()
      else:
         self._mapSet.clear()

      # load any others present, but make sure only one copy of each is returned

      self.getSetOfPymolObjectsByType( 'object:map', self._mapSet )

      apbsFileName = self.getIsimDisplay().getApbsPotentialFileName( True )
      if apbsFileName in self._mapSet:
         self._mapSet.remove( apbsFileName )

      mapList = [ ii for ii in self._mapSet ]
      mapList.insert( 0, apbsFileName )

      return mapList

   # } end of loadMaps

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

   def getDxFileCommentList( self, fileName ): # {

      """Read header of dx files and store results"""

      stats = self.readDxFileStats( fileName )
      return stats['CommentList']

   # } end of getDxFileCommentList

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

   def readDxFileStats( self, fileName ): # {

      """Read header of dx files and store in a dictionary
         The dictionary entries are referenced by file name;
         The fields in each dictionary include:
                CommentList     -- unparsed lines
                DefaultSet       
                Names           -- names of different stats (Bulk, Surface)
                Average         -- average of sample
                Stddev          -- std dev of sample
                Count           -- total number of elements in histogram 
                Min             -- min sampled value
                Max             -- max sampled value
                H1-H20          -- histogram counts for bins 1-20
      """


      if not self._observableStats.has_key( fileName ):

         apbsGrid                        = ApbsGridSimTk()
         apbsGrid.readDx( fileName, 1, 1 )
         commentList                     = apbsGrid.getCommentList()
         self._observableStats[fileName] = {}
        
         # print fileName

         stats                           = self._observableStats[fileName]
         stats['CommentList']            = commentList
         stats['DefaultSet']             = 0 
         stats['Names']                  = [] 
         stats['FileName']               = fileName 
         bulkDensity                     = None
         dataStatsAvailable              = 0
         regExMatchNumber                = re.compile( 'NUMBER' )
         if commentList is not None:

            # parse DataStat lines

            tokenizer            = re.compile( '\s+' )
            dataStat             = re.compile( 'DataStat' )
            currentDict          = None
            for commentLine in commentList:
               wordList = tokenizer.split( commentLine )
               if len( wordList ) > 3 and dataStat.match( wordList[1] ):

                  dataStatsAvailable = 1

                  # check for name of profile (Bulk, Surface)

                  # print 'QQQ ' + commentLine + ' ' +  wordList[2] + '=<' +  str( wordList[3] ) + '>';
                  if wordList[2].find( 'Name' ) >= 0:

                     # print 'QQQ Name match' + commentLine + ' ' +  wordList[2] + '=<' +  str( wordList[3] ) + '>';
                     stats[wordList[3]] = {}
                     currentDict        = stats[wordList[3]]
                     stats['Names'].append( wordList[3] )

                  # version field -- included in case format/content of dx-file changes

                  elif wordList[2].find( 'Version' ) >= 0:
                     # handle version id when it becomes as issue
                     pass
                  else:

                     # wordList[2] = Average, StdDev, Count, ...
                     # wordList[3] = corresponding value

                     # print 'QQQ ' + commentLine + ' ' +  wordList[2] + '=<' +  str( wordList[3] ) + '>';
                     if currentDict is not None:
                        try:
                           currentDict[wordList[2]] = float( wordList[3] ) 
                        except:
                           currentDict[wordList[2]] = 0.0
                     else:
                        try:
                           stats[wordList[2]] = float( wordList[3] ) 
                        except:
                           currentDict[wordList[2]] = 0.0
   
         if dataStatsAvailable == 0:
            return None

         if bulkDensity is None and stats.has_key( 'BulkNumberDensity/A**3' ):
            bulkDensity = stats['BulkNumberDensity/A**3']

         # get default profile name

         statProfileName = None
         if stats.has_key( 'Surface' ):
            statProfileName = 'Surface'
         elif stats.has_key( 'Bulk' ):
            statProfileName = 'Bulk'
         elif stats.has_key( 'Names' ) and len( stats['Names'] ) > 0:
            statProfileNameKey = stats['Names'][0]
            if stats.has_key( statProfileNameKey ):
              statProfileName = statProfileNameKey

         if statProfileName is not None:
            stats['DefaultStatsProfileName'] = statProfileName
            stats['DefaultStatsProfile']     = stats[statProfileName]

         # initialize settings
         # cascade through different approaches until one is activated
         # code can be moved in blocks to change how levels computed

         if stats.has_key( 'Names' ):
            statProfileList = stats['Names']
            for statProfileName in statProfileList:
               statProfile = stats[statProfileName]

               if bulkDensity is not None and not statProfile.has_key( 'BulkNumberDensity/A**3' ):
                  statProfile['BulkNumberDensity/A**3'] = bulkDensity

               levelSet    = 0
               if levelSet == 0 and (statProfile.has_key( 'TrimmedAverage' ) or statProfile.has_key( 'Average' ) ):

                  if statProfile.has_key( 'TrimmedAverage' ):
                     average                      = statProfile['TrimmedAverage']           
                     stddev                       = statProfile['TrimmedStdDev']           
                  else:
                     average                      = statProfile['Average']           
                     stddev                       = statProfile['StdDev']           

                  if fileName and regExMatchNumber.search( fileName ):

                     statProfile['Low']           = average - self._defaultUnitsOfStdDev*stddev

                     if statProfile['Low'] <= 0.0:

                        if stddev <= bulkDensity and bulkDensity > 0.0:
                           factor = bulkDensity
                        else:
                           factor = stddev 

                        if factor <= 1.0e-06:
                           factor = 1.0e-05

                        statProfile['Low']        = factor
                        statProfile['Medium']     = 2.0*factor
                        statProfile['High']       = 3*factor

                     else:
                        statProfile['Medium']     = average + stddev
                        statProfile['High']       = average + self._defaultUnitsOfStdDev*stddev

                  else:
                     statProfile['Low']           = average - self._defaultUnitsOfStdDev*stddev
                     statProfile['Medium']        = average
                     statProfile['High']          = average + self._defaultUnitsOfStdDev*stddev

                  levelSet                     = 1

               if levelSet == 0 and statProfile.has_key( 'Bins' ):

                  numberOfBins                 = statProfile['Bins'] 

                  lowBin                       = str( int( 0.25*numberOfBins ) )
                  midBin                       = str( int( 0.50*numberOfBins ) )
                  highBin                      = str( int( 0.75*numberOfBins ) )

                  if statProfile.has_key( lowBin ):
                    statProfile['Low']           = self.convertToTruncatedReal( statProfile[lowBin] )
                    statProfile['Medium']        = self.convertToTruncatedReal( statProfile[midBin] )
                    statProfile['High']          = self.convertToTruncatedReal( statProfile[highBin] )
                    levelSet                     = 1

               # no hope -- use hardwired default values

               if levelSet == 0:
                  statProfile['Low']           = -1.0
                  statProfile['Medium']        =  0.0
                  statProfile['High']          =  1.0
                  levelSet                     =  1
      
               if fileName and regExMatchNumber.search( fileName ):
                  statProfile['LowStdDev']        = 1.0
                  statProfile['MediumStdDev']     = 2.0 
                  statProfile['HighStdDev']       = 3.0
               else:
                  statProfile['LowStdDev']        = -1.0
                  statProfile['MediumStdDev']     = 0 
                  statProfile['HighStdDev']       = 1.0

               statProfile['LowBulkCon']       = 1.0
               statProfile['MediumBulkCon']    = 5.0 
               statProfile['HighBulkCon']      = 10.0
   
               statProfile['LowkT']            = -1.0
               statProfile['MediumkT']         = 0.0 
               statProfile['HighkT']           = 1.0
   
            statProfile['Low']    = self.convertToTruncatedReal( statProfile['Low'], 0.001, 2 )
            statProfile['Medium'] = self.convertToTruncatedReal( statProfile['Medium'], 0.001, 2 )
            statProfile['High']   = self.convertToTruncatedReal( statProfile['High'], 0.001, 2 )

      return self._observableStats[fileName]

   # } end of readDxFileStats

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

   def getDxFileStats( self, fileName, statType='DefaultStatsProfile' ): # {

      """Get specified stat profile (Surface, Bulk, or DefaultStatsProfile;
         return 0 if requested profile unavailable"""

      if not self._observableStats.has_key( fileName ):
         stats = self.readDxFileStats( fileName )
      else:
         stats = self._observableStats[fileName]

      # if dx file stats unavailable for this particular file (e.g., stats only
      # collected for the last MC step), then try to get stats from other file
      # of same type (potential, ionType, charge density )

      message = 'Searching for stats for ' + fileName + ' type=' + statType + ' -- '
      if stats is None:
         message += 'Using substitue dx stats'
         globalStatProfiles = self.getGlobalDxFileStats()
         for key in globalStatProfiles:

            # number density files are indexed by ionNumber; hence
            # look for _1_, _2_, ... to identify file type for ion
            # other file types (GRID_POTENTIAL and GRID_CHARGE_DENSITY_) can 

            if key.isdigit():
               regExMatch = re.compile( '_' + str( key ) + '_' )
            else:
               regExMatch = re.compile( key )

            if regExMatch.search( fileName ):
               stats = globalStatProfiles[key]
               self._observableStats[fileName] = stats
               break
      else:
         message += 'Found stats'
   
      # stats may include surface/bulk/... stat groupings
      # return profile specified by calling method

      returnValue = None
      if stats is not None and stats.has_key( statType ):
         returnValue = stats[statType]
         message = ' found type -- life is good'
      else:
         if stats is None:
            message = ' No stats/substitute stats available'
         else:
            keyss = stats.keys()
            message = statType + ' missing from stat for keys:( ' + str( keyss ) + ')'

      # log results

      logReference = self._isimDisplay.getLogReference()
      if logReference:
         logReference.info( message )

      return returnValue

   # } end of getDxFileStats

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

   def getGlobalDxFileStats( self ): # {

      """Try to find dx files w/ a stat entry -- for each file type (potential, number density, ..)
         loop over all available MC steps until find a file w/ stats; start from last MC step and work back
         This process is only done once; subsequent calls will use cached values or
         return an empty cache
      """

      global globalStatProfiles

      if globalStatProfiles is None:

         isimDisplay                           = self._isimDisplay
         numberOfIons                          = isimDisplay.getNumberOfIons()
         stepList                              = self.getStepList()
         fileSuffix                            = '.dx'
         globalStatProfiles                    = {}
   
         # if no dx files, return now
   
         if stepList is None:
            return None
   
         # try MC steps in reverse order

         # for each file type, quit searching for that type once a hit 
         # (file w/ stats data) is made
   
         potentialHit       = 0
         chargeDensityHit   = 0
         numberDensityHit   = []
         for ii in range( numberOfIons ):
            numberDensityHit.append( 0 )

         totalHits          = 0
         message            = 'GlobalFileStats:\n'
         for mcStep in stepList:

            # potential

            if potentialHit == 0:
               if self._IsimPotentialStats is None:
                  isimPotentialFileName     = isimDisplay.buildFullFileName( IsimPotentialFileSimTk.getFilePrefix(),
                                                                                         mcStep, fileSuffix )
                  if isimPotentialFileName is not None:
                     self._IsimPotentialStats  = self.readDxFileStats( isimPotentialFileName )
                     if self._IsimPotentialStats is not None:
                        potentialHit = 1
                        totalHits   += 1
                        globalStatProfiles[IsimPotentialFileSimTk.getFilePrefix()] = self._IsimPotentialStats
                        message     += ' Stats obtained from PotentialFile=<' + isimPotentialFileName + '>\n'
                     else:
                        message     += '  PotentialFile stats unavailable.\n'
                  else:
                     message     += '  PotentialFile not set.\n'

            # charge density

            if chargeDensityHit == 0:
               if self._IsimChargeDensityStats is None:
                  isimChargeDensityFileName     = isimDisplay.buildFullFileName( IsimChargeDensityFileSimTk.getFilePrefix(),
                                                                               mcStep, fileSuffix )
                  self._IsimChargeDensityStats  = self.readDxFileStats( isimChargeDensityFileName )
               if self._IsimChargeDensityStats is not None:
                  chargeDensityHit = 1
                  totalHits   += 1
                  globalStatProfiles[IsimChargeDensityFileSimTk.getFilePrefix()] = self._IsimChargeDensityStats
                  message     += '  ChargeDensityFile=<' + isimChargeDensityFileName + '>\n'

            # number density

            for ii in range( numberOfIons ):
               if numberDensityHit[ii] == 0:
                  if self._IsimNumberDensityStats[ii] is None:
                     isimNumberDensityFileName = isimDisplay.buildFullFileName( IsimNumberDensityFileSimTk.getFilePrefix(),
                                                                              mcStep, fileSuffix, ii+1 )
                     isimNumberDensityStats    = self.readDxFileStats( isimNumberDensityFileName )
                  if isimNumberDensityStats is not None:
                     numberDensityHit[ii]     = 1
                     totalHits   += 1
                     globalStatProfiles[str(ii+1)] = self._IsimChargeDensityStats
                     message     += '  NumberDensityFile=<' + isimNumberDensityFileName + '>\n'

            # check if done

            if totalHits == (numberOfIons + 2):
               break

         # log results
   
         logReference = self._isimDisplay.getLogReference()
         if logReference:
            logReference.info( message )
   
      return globalStatProfiles

   # } end of getGlobalDxFileStats

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

   def convertToTruncatedReal( self, value, threshold = 0.001, desiredSignificantDigits = 1 ): # {

      """Covert a real number x significant digits if its absolute value is below a threshold  
      """

      if abs( value ) < threshold: 
         formatString = '%.' + str( desiredSignificantDigits ) + 'e'
         valueString  = formatString % value
         value        = float( valueString )

      return value

   # } end of convertToTruncatedReal


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

   def getDefaultMap( self ): # {

      """Load maps from isim.in file and any that are loaded in Pymol"""

      # load in molecule from isim.in file

      if self._mapTypeDictInverse.has_key( self._IsimPotential ):
         return self._mapTypeDictInverse[self._IsimPotential]

      mapList = self.loadMaps()
      return mapList[0]

   # } end of loadMaps

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

   def getSetOfPymolObjectsByType( self, type, typeSet=None ): # {

      """Load object of specific types into set; if set is not provided, then a new one is created.
         Static method
      """

      if typeSet is None:
         typeSet = sets.Set()

      # load any others present, but make sure only one copy of each is returned

      for ii in pymol.cmd.get_names():
         if pymol.cmd.get_type(ii) == type:
            typeSet.add( ii )

      return typeSet

   # } end of getSetOfPymolObjectsByType

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

   def getStepList( self ): # {

      # get MC step list sorted in reverse order

      if self._stepList is None:
         self._stepList = IsimGridFileSimTk.getAvailableMcStepsForFileOfSpecifiedType( 
                     self._isimDisplay.getWorkingDirectoryName(), IsimPotentialFileSimTk.getFilePrefix() )

         if self._stepList is None or len( self._stepList ) < 1:

            logReference = self._isimDisplay.getLogReference()
            message      = 'Step list is empty -- is run directory set correctly or are grid files missing?'
            if logReference:
               logReference.info( message )
               print message
            else:
               print message
            return None

         elif len( self._stepList ) > 1:
            # self._stepList.sort( cmp=(lambda x,y: int(y)-int(x)) )
            self._stepList.sort( (lambda x,y: int(y)-int(x)) )

      return self._stepList

   # } end of getStepList

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

   def initialize( self, isimDisplay ): # {

      self.setIsimDisplay( isimDisplay )
      self.refresh()

   # } end of initialize

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

   def getFileInfo( self, rowIndex ): # {

      """Create a dictionary w/ all pertinent file-related info"""

      fileType      = self._mapTypeMenus[rowIndex].getvalue()
      fileTypeIndex = self.getFileTypeIndexGivenSelectionString( fileType )
      filePrefix    = self.getFilePrefixGivenFileTypeIndex( fileTypeIndex )
      fileIonIndex  = self.getFileIonTypeGivenFileTypeIndex( fileTypeIndex )

      mcStep        = self._mapStepMenus[rowIndex ].getvalue()
      fullFileName  = self.getObservableFullFileName( fileType, mcStep )

      fileInfo                  = {}
      fileInfo['fileTypeIndex'] = fileTypeIndex
      fileInfo['filePrefix']    = filePrefix
      fileInfo['fileIonIndex']  = fileIonIndex
      fileInfo['fileType']      = fileType
      fileInfo['mcStep']        = mcStep
      fileInfo['fullFileName']  = fullFileName

      return fileInfo

   # } end of getFileInfo

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

   def setupCheckButton( self, rowIndex, columnIndex, parent, isOn = -1 ): # {

      """Create checkButton variable and CheckButton object
         if isOn > -1, then use that setting for the default on/off value;
         otherwise, set the default setting to 1 if rowIndex == 1 and
         0 otherwise.
      """

      if self._mapCheckButtonSetting is None:
         self._mapCheckButtonSetting = []

      self._mapCheckButtonSetting.append( IntVar() )
      if isOn > -1:
         self._mapCheckButtonSetting[rowIndex].set( isOn )
      else:
         if rowIndex:
            self._mapCheckButtonSetting[rowIndex].set( 0 )
         else:
            self._mapCheckButtonSetting[rowIndex].set( 1 )

      if self._mapCheckButton is None:
         self._mapCheckButton = []

      callbackObject = CheckButtonCallback()
      callbackObject.setData( 'Observable', rowIndex, self )
      self._mapCheckButton.append( Checkbutton( parent, variable = self._mapCheckButtonSetting[rowIndex], 
                                   command=callbackObject.handleButtonEvent ) )
      self._mapCheckButton[rowIndex].grid( row = rowIndex, column = columnIndex )
      columnIndex += 1;

      return columnIndex

   # } end of setupCheckButton

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

   def setupMapType( self, rowIndex, columnIndex, parent, mapTypes, defaultMapType ): # {

      """Create pulldown menu for map files w/ callback"""

      callbackObject = MapMenuCallback()
      callbackObject.setData( 'Observable', rowIndex, self )
      if rowIndex == 0:
         typeLabelText       = 'Observable'
      else:
         typeLabelText       = None

      # object list of menus (indexed by row)

      if self._mapTypeMenus is None:
         self._mapTypeMenus = []

      self._mapTypeMenus.append(
                                 Pmw.OptionMenu(
                                             parent,
                                             labelpos          = 'n',
                                             label_text        = typeLabelText,
                                             items             = mapTypes,
                                             initialitem       = defaultMapType,
                                             command           = callbackObject.handleMenuUpdate,
                                             menubutton_width  = 25

                                               )
                                 )

      self._mapTypeMenus[rowIndex].grid( row=rowIndex, column=columnIndex )
      columnIndex += 1;

      return columnIndex

   # } end of setupMapType

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

   def setupMcStep( self, rowIndex, columnIndex, parent, stepList, initialStep = None ): # {

      """Create pulldown menu for MC steps w/ callback"""

      callbackObject = MapMenuCallback()
      callbackObject.setData( 'MC Step', rowIndex, self )

      if rowIndex == 0:
         stepLabelText = 'MC Step'
      else:
         stepLabelText = None

      # step list sorted in reverse order (last MC step first)

      if initialStep is None:
         initialStep = stepList[0]

      # object list of menus (indexed by row)

      if self._mapStepMenus is None:
         self._mapStepMenus = []

      self._mapStepMenus.append(
                                 Pmw.OptionMenu(
                                             parent,
                                             labelpos          = 'n',
                                             label_text        = stepLabelText,
                                             items             = stepList,
                                             initialitem       = initialStep,
                                             command           = callbackObject.handleMenuUpdate,
                                             menubutton_width  = 6,
                                                )
                                )

      self._mapStepMenus[rowIndex].grid( row=rowIndex, column=columnIndex )
      columnIndex += 1;
      return columnIndex

   # } end of setupMcStep

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

   def setupUnits( self, parent, rowIndex, columnIndex, rowFrame, unitsText ): # {

      """Create pulldown menu for level units (absolute or units of stddev ); includes callback"""

      callbackObject = UnitMenuCallback()
      callbackObject.setData( 'Units', rowIndex, self )

      if self._unitTypeMenus is None:
         self._unitTypeMenus = []

      self._unitTypeMenus.append(
                                 Pmw.OptionMenu(
                                             parent,
                                             labelpos          = 'n',
                                             label_text        = unitsText,
                                             items             = self._espUnitTypeList ,
                                             initialitem       = self._espUnitTypeList[0],
                                             command           = callbackObject.handleMenuUpdate,
                                             menubutton_width  = 15,
                                                )
                                )

      self._unitTypeMenus[rowIndex].grid( row=rowIndex, column=columnIndex )
      columnIndex += 1;

      return columnIndex

   # } end of setupUnits

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

   def setupThreshold( self, entryList, rowIndex, columnIndex, parent, threshold, thresholdText  ): # {

      """Create entry field for level; value entered must be real"""

      entryList.append( Pmw.EntryField( parent, labelpos = 'n', label_text = thresholdText,
                                        value = threshold, validate = {'validator' : 'real' },
                                        entry_width = 10,) )
 
      entryList[rowIndex].grid( row = rowIndex, column = columnIndex )
      columnIndex += 1;
      return columnIndex

   # } end of setupThreshold

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

   def setupStatButton( self, entryList, rowIndex, columnIndex, parent ): # {

      """Create button that when activated shows stats for dx file specied by 
         the other entries in the row"""

      if rowIndex:
         label = None
      else:
         label = 'Stats'

      label = None
      entryList.append( Pmw.ButtonBox( parent, labelpos = 'n', label_text = label ) )
      callbackObject = StatButtonCallback()

      callbackObject.setData( 'Observable', rowIndex, self )
      entryList[rowIndex].add( 'Stat', command=callbackObject.handleButtonEvent )

      entryList[rowIndex].grid( row = rowIndex, column = columnIndex )
      columnIndex += 1;
      return columnIndex

   # } end of setupStatButton

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

   def setupOptionMenu( self, entryList, rowIndex, columnIndex, parent, labelText, optionList,
                        intitialOption = None, menuButtonWidth = None, callbackCommand = None ): # {

      """General method for creating pulldown menu"""

      if intitialOption == None:
         intitialOption = optionList[0]

      # set menu button width, if not provided

      if menuButtonWidth == None:
         maxLength = -1
         for ii in optionList:
            if len( ii ) > maxLength:
               maxLength = len( ii )
         menuButtonWidth = maxLength + 2

         # 

      entryList.append( Pmw.OptionMenu( parent, labelpos = 'n', label_text = labelText,
                                        items = optionList, initialitem = intitialOption,
                                        menubutton_width  = menuButtonWidth,) )

      entryList[rowIndex].grid( row = rowIndex, column = columnIndex )
      columnIndex += 1;
      return columnIndex

   # } end of setupOptionMenu

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

   def clear( self, clearFlag = 0 ): # {

      """Clear field in delete/remove list/hash _pymolDeleteDict
         according to clearFlag value; all entries w/ a hash
         value <= clearFlag are deleted/removed from Pymol
      """

      global moleculeLoadedIntoPymol

      if self._pymolDeleteDict is not None:
         for itemInList in self._pymolDeleteDict.keys():
            if self._pymolDeleteDict[itemInList] <= clearFlag:

               if self._pymolDeleteDict[itemInList] == 0:
                  pymol.cmd.delete( itemInList )
               else:
                  pymol.cmd.disable( itemInList )

               del self._pymolDeleteDict[itemInList]

   # } end of clear

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

   def getFileNameRegEx( self ): # {

      """List of compiled regex entries used to set Pymol map name -- maps file type to
         Pymol map abbreviation
      """ 

      if self._fileNameMatchList is None:
         self._fileNameMatchList           = []
         self._fileNameMatchList.append( ( re.compile( IsimNumberDensityFileSimTk.getFilePrefix() ), 'ND' ) )
         self._fileNameMatchList.append( ( re.compile( IsimChargeDensityFileSimTk.getFilePrefix() ), 'CD' ) )
         self._fileNameMatchList.append( ( re.compile( IsimPotentialFileSimTk.getFilePrefix() ), 'Ptl' ) )

      return self._fileNameMatchList

   # } end of getFileNameRegEx

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

   def getMapName( self, baseName ): # {

      """Return Pymol map name based on file basename (GRID_NUMBER_DENSITIES, ...);
         return input basename if no match is found
      """

      nameMatchList = self.getFileNameRegEx()
      for entryList in nameMatchList:
         if entryList[0].match( baseName ):
            return entryList[1]

      return baseName

   # } end of getMapName

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

   def addToPymolDeleteList( self, objectName, clearFlag = 0 ): # {

      """Add Pymol object name to list of entries to be cleared
         The value of clearFlag signals when the object should be cleared. Generally if clearFlag
         is zero, then the object is always deleted when a clear() command is issued; typically
         this class of objects includes color maps, mesh levels, ... If clearFlag == 1, then the
         object is only removed from Pymol on a hard clear; objects like dx-files and molecules
         fall into this class -- minimizes file reads
      """

      if self._pymolDeleteDict is None:
         self._pymolDeleteDict = {}
      self._pymolDeleteDict[objectName] = clearFlag

   # } end of addToPymolDeleteList

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

   def getObservableFullFileName( self, selectionString, mcStep ): # {
      
      """Get full file name (absolute path) given pulldown menu selection"""

      isimDisplay      = self.getIsimDisplay()

      fileTypeIndex    = self.getFileTypeIndexGivenSelectionString( selectionString )
      filePrefix       = self.getFilePrefixGivenFileTypeIndex( fileTypeIndex )
      fileIonType      = self.getFileIonTypeGivenFileTypeIndex( fileTypeIndex )
      fileSuffix       = '.dx'

      # branch based on whether file is the Apbs file or
      # an Isim generated file

      if filePrefix:

         # for an Isim file, build full path name based on MC step and ionType (if
         # number density file)

         fullFileName     = isimDisplay.buildFullFileName( filePrefix, mcStep, fileSuffix, fileIonType )

      else:

         # Apbs file -- name obtained from isim.in file

         fullFileName     = isimDisplay.getApbsPotentialFullPathFileName()

      return fullFileName

   # } end of getObservableFullFileName

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

   def getObservableMapName( self, selectionString, mcStep ): # {
      
      """Get Pymol name given pulldown selection and MC step"""

      isimDisplay      = self.getIsimDisplay()

      fileTypeIndex    = self.getFileTypeIndexGivenSelectionString( selectionString )
      filePrefix       = self.getFilePrefixGivenFileTypeIndex( fileTypeIndex )
      fileIonType      = self.getFileIonTypeGivenFileTypeIndex( fileTypeIndex )
      if fileIonType is not None:
         ionAbbreviation = self._ionAbbreviations[fileTypeIndex]
      else:
         ionAbbreviation = None

      # branch based on whether file is the Apbs file or
      # an Isim generated file

      if filePrefix:

         # for an Isim file create an abbreviated
         # name to use as a Pymol handle

         if ionAbbreviation:
            mapName = ionAbbreviation
         else:
            mapName = self.getMapName( filePrefix )

         mapName += str( mcStep )

      else:

         # Apbs file

         # mapName     = isimDisplay.getApbsPotentialFileName( 1 )
         mapName     = 'Apbs'

      return mapName

   # } end of getObservableMapName

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

   def isApbsFile( self, selectionString ): # {
      
      """Return true if the pulldown selection is an Apbs file"""

      isimDisplay      = self.getIsimDisplay()

      fileTypeIndex    = self.getFileTypeIndexGivenSelectionString( selectionString )
      filePrefix       = self.getFilePrefixGivenFileTypeIndex( fileTypeIndex )
      if filePrefix:
         return 0;
      else:
         return 1;

   # } end of isApbsFile

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

   def setDisplayLevels( self, rowIndex, statProfile = None ): # {

      """Set levels based on values in statsProfile
         The stats entry is read once from file. The default
         level values are set in the readDxFileStats() method.
      """

      fileInfo      = self.getFileInfo( rowIndex )
      fullFileName  = fileInfo['fullFileName']

      # update levels/thresholds based on default values or last settings
      # made by user

      if statProfile is None:
         statProfile = self._BulkProfile;

      statsProfile   = self.getDxFileStats( fullFileName, statProfile )
      if statsProfile is not None:

         # set level type (absolute or units of stddev)

         if self._unitTypeMenus[rowIndex] is not None:
            levelUnitType          = self._unitTypeMenus[rowIndex].getvalue()
         else:
            levelUnitType          = self._AbsoluteValue

         LowKey     = 'Low'
         MediumKey  = 'Medium'
         HighKey    = 'High'

         addToKey   = None
         if levelUnitType == self._StdDevUnits:
            addToKey = 'StdDev'
         elif levelUnitType == self._BulkNumberDensityUnits:
            addToKey = 'BulkCon'
         elif levelUnitType == self._kTUnits:
            addToKey = 'kT'

         if addToKey is not None:
            LowKey     += addToKey
            MediumKey  += addToKey
            HighKey    += addToKey

         print "QQQ key " + str( MediumKey )
         print "QQQ medium " + str( statsProfile[MediumKey] )

         statsProfile[LowKey]    = self.convertToTruncatedReal( statsProfile[LowKey], 0.001, 2 )
         statsProfile[MediumKey] = self.convertToTruncatedReal( statsProfile[MediumKey], 0.001, 2 )
         statsProfile[HighKey]   = self.convertToTruncatedReal( statsProfile[HighKey], 0.001, 2 )

         if self._mapLevelEntry is not None:
            self._mapLevelEntry[rowIndex].setvalue( statsProfile[MediumKey] )

         if self._mapLowerThresoldEntry is not None:
            self._mapLowerThresoldEntry[rowIndex].setvalue( statsProfile[LowKey] )
            self._mapMiddleThresoldEntry[rowIndex].setvalue( statsProfile[MediumKey] )
            self._mapUpperThresoldEntry[rowIndex].setvalue( statsProfile[HighKey] )

   # } end of setDisplayLevels

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

   def getDisplayLevels( self, rowIndex, fullFileName, statProfile = None ): # {

      # get thresholds for surface color map and build color ramp

      if self._unitTypeMenus[rowIndex] is not None:
         levelUnitType          = self._unitTypeMenus[rowIndex].getvalue()
      else:
         levelUnitType          = self._AbsoluteValue

      if statProfile is None:
         statProfile = self._BulkProfile;

      # The first if-block is for the mesh surface display, 
      # where a single level (the medium threshold) is required

      # The second block (the elif section) is for molecular surface display
      # where a color ramp is used and hence 3 values (low, med, high) are returned

      if self._mapLevelEntry is not None and self._mapLevelEntry[rowIndex] is not None:

         mediumThreshold     = float( self._mapLevelEntry[rowIndex].getvalue() )
         lowThreshold        = mediumThreshold*0.8 
         highThreshold       = mediumThreshold*1.2

      elif self._mapLowerThresoldEntry is not None and self._mapLowerThresoldEntry[rowIndex] is not None:

         lowThreshold        = float( self._mapLowerThresoldEntry[rowIndex].getvalue() )
         mediumThreshold     = float( self._mapMiddleThresoldEntry[rowIndex].getvalue() )
         highThreshold       = float( self._mapUpperThresoldEntry[rowIndex].getvalue() )

      stats                  = self.getDxFileStats( fullFileName, statProfile )

      if stats is not None:

         # if the units are stddev units, then transform the values
         # based on the number of stddev units

         if levelUnitType == self._StdDevUnits and stats.has_key( 'TrimmedAverage' ) and stats.has_key( 'TrimmedStdDev' ):
   
            average                = stats['TrimmedAverage']           
            stddev                 = stats['TrimmedStdDev']           
   
            # save stddev units so that next time this file is viewed, the default values 
            # are the same as the user has selected for the current display

            stats['LowStddev']     = lowThreshold
            stats['MediumStdDev']  = mediumThreshold
            stats['HighStdDev']    = highThreshold
   
            lowThreshold           = average + lowThreshold*stddev
            mediumThreshold        = average + mediumThreshold*stddev
            highThreshold          = average + highThreshold*stddev
   
         elif levelUnitType == self._BulkNumberDensityUnits and stats.has_key( 'BulkNumberDensity/A**3' ):
   
            bulkNumberDensity      = stats['BulkNumberDensity/A**3']           
            print "bulkNumberDensity=" + str( bulkNumberDensity )
   
            # save stddev units so that next time this file is viewed, the default values 
            # are the same as the user has selected for the current display

            stats['LowBulkCon']    = lowThreshold
            stats['MediumBulkCon'] = mediumThreshold
            stats['HighBulkCon']   = highThreshold
   
            lowThreshold           = lowThreshold*bulkNumberDensity
            mediumThreshold        = mediumThreshold*bulkNumberDensity
            highThreshold          = highThreshold*bulkNumberDensity
   
            print "bulkNumberDensity=" + str( bulkNumberDensity )
            print "threshold=" + str( mediumThreshold )

         elif levelUnitType == self._kTUnits:
   
            kTe = self.getkJTokTConversionFactor()
   
            # save stddev units so that next time this file is viewed, the default values 
            # are the same as the user has selected for the current display

            stats['LowkT']         = lowThreshold
            stats['MediumkT']      = mediumThreshold
            stats['HighKT']        = highThreshold
   
            lowThreshold           = lowThreshold*kTe
            mediumThreshold        = mediumThreshold*kTe
            highThreshold          = highThreshold*kTe
   
            print "ZZZ conversion from kT/e to kJ/mol=" + str( kTe )
            print "threshold=" + str( mediumThreshold )

         else:
   
            # save thresholds so that next time this file is viewed, default values 
            # are the same as the user has selected

            stats['Low']           = lowThreshold
            stats['Medium']        = mediumThreshold
            stats['High']          = highThreshold
      
 
      lowThreshold    = self.convertToTruncatedReal( lowThreshold, 0.001, 2 )
      mediumThreshold = self.convertToTruncatedReal( mediumThreshold, 0.001, 2 )
      highThreshold   = self.convertToTruncatedReal( highThreshold, 0.001, 2 )

      return [ lowThreshold, mediumThreshold, highThreshold ]

   # } end of getDisplayLevels

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

# } end of IsimInterface class        

class IsimSimpleInterface( IsimInterface ): # {

   def __init__( self, *args, **kwargs ):

        IsimInterface.__init__( self, *args, **kwargs )
        self._colorRampName             = 'ClrRmp'
        self._maxMaps                   = 1
        self._isSimple                  = 1
        self._moleculeToggle            = 'MolToggle'
        self._potentialToggle           = 'PtlToggle'
        self._defaultSurfaceStatProfile = self._SurfaceProfile
        self._defaultBulkStatProfile    = self._BulkProfile
        self._colorRamp                 = None

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

   def initialize( self, isimDisplay ): # {

      self.setIsimDisplay( isimDisplay )
      return self.refreshInterface()

   # } end of initialize

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

   def refreshInterface( self ): # {

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

      isimDisplay                           = self._isimDisplay
      potentialFilePrefix                   = IsimPotentialFileSimTk.getFilePrefix()
      numberDensityFilePrefix               = IsimNumberDensityFileSimTk.getFilePrefix()
      stepList                              = self.getStepList()
      fileSuffix                            = '.dx'

      # if no dx files, return now

      if stepList is None:
         logReference = isimDisplay.getLogReference()
         message      = 'Step list is empty -- is run directory set correctly or are grid files missing?'
         if logReference:
            logReference.info( message )
            print message
         else:
            print message
         return None

      # last available MC step (list is sorted high -> low)

      mcStep                                = stepList[0]
      
      self._IsimPotentialFileName           = isimDisplay.buildFullFileName( IsimPotentialFileSimTk.getFilePrefix(), mcStep, fileSuffix )
      self._IsimPotentialStats              = self.getDxFileStats( self._IsimPotentialFileName )

      # color list linked to whether ion is a cation/anion

      self._warmColorList = [ 'green', 'yellow', 'orange' ]
      self._coolColorList = [ 'purple', 'black' ]
      warmIndex           = 0
      coolIndex           = 0

      # set colors, ion abbreviations, ...

      self._numberDensityFileList           = []
      self._ionAbbreviationList             = []
      self._ionChargeList                   = []
      self._ionColorList                    = []
      self._ionStatList                     = []
      self._numberOfIons                    = isimDisplay.getNumberOfIons()
      for ionIndex in range ( 1, (self._numberOfIons + 1) ):

         ionName                       = isimDisplay.getIonNameGivenIonIndex( ionIndex )
         ionAbbreviation               = isimDisplay.getIonAbbreviationGivenIonName( ionName )
         ionCharge                     = isimDisplay.getIonChargeGivenIonIndex( ionIndex )
         self._ionChargeList.append( ionCharge )

         if ionCharge > 0.0:
            color      = self._warmColorList[warmIndex]
            warmIndex += 1
         else:
            color      = self._coolColorList[coolIndex]
            coolIndex += 1

         self._ionAbbreviationList.append( ionAbbreviation )
         self._ionColorList.append( color )

         self._numberDensityFileList.append( isimDisplay.buildFullFileName( IsimNumberDensityFileSimTk.getFilePrefix(),
                                                                            mcStep, fileSuffix, ionIndex ) )
         self._ionStatList.append( self.getDxFileStats( self._numberDensityFileList[ionIndex-1], self._BulkProfile ) )

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

      # diagnostics

      message      = 'PotentialFile=' + self._IsimPotentialFileName + '\n'
      for ii in range( self._numberOfIons ):
         message += self._ionAbbreviationList[ii] + ' q=' + str( self._ionChargeList[ii] ) 
         message += ' color=' + self._ionColorList[ii] + ' ' + self._numberDensityFileList[ii]
         message += '\n'
         
      logReference = isimDisplay.getLogReference()
      if logReference:
         logReference.info( message )
         print message
      else:
         print message

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

      # build interface

      rowFrame               = Frame( self.interior() )
      rowFrame.pack()
      columnIndex            = 0
      rowIndex               = 0
      fixedWidth             = 30

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

      # molecule toggle

      self._moleculeCheckButtonSetting = IntVar()
      self._moleculeCheckButtonSetting.set( 1 )
      moleculeNameList       = self.getMoleculeName() 
      self._moleculeName     = moleculeNameList[1]

      callbackObject         = SimplifiedInterfaceCheckButtonCallback()
      callbackObject.setData( self._moleculeToggle, 0, self )
      self._moleculeCheckButton = Checkbutton( rowFrame, variable = self._moleculeCheckButtonSetting, text=moleculeNameList[1],
                                               anchor='w',  width=fixedWidth, command=callbackObject.handleToggleUpdate ) 

      self._moleculeCheckButton.grid( row = rowIndex, column = columnIndex )
      rowIndex += 1

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

      # surface potential toggle

      self._potentialFileCheckButtonSetting = IntVar()
      self._potentialFileCheckButtonSetting.set( 1 )

      callbackObject         = SimplifiedInterfaceCheckButtonCallback()
      callbackObject.setData( self._potentialToggle, 0, self )
      self._potentialFileCheckButton = Checkbutton( rowFrame, variable = self._potentialFileCheckButtonSetting, text='Surface Potential',
                                            anchor='w',  width=fixedWidth, command=callbackObject.handleToggleUpdate ) 

      self._potentialFileCheckButton.grid( row = rowIndex, column = columnIndex )
      rowIndex += 1

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

      # number density toggles

      self._numberDensityCheckButtonSetting = []
      self._numberDensityCheckButtonList    = []
      for ii in range( self._numberOfIons ):

         self._numberDensityCheckButtonSetting.append( IntVar() )
         numberDensityLevel = "%.2e/A**3" % self.getNumberDensityLevel( ii )

         callbackObject         = SimplifiedInterfaceCheckButtonCallback()
         callbackObject.setData( ('IonToggle' + str( ii+1)), (ii+1), self )

         self._numberDensityCheckButtonList.append( Checkbutton( rowFrame, variable = self._numberDensityCheckButtonSetting[ii],
                                                    text=(self._ionAbbreviationList[ii] + ' Number Density @' + numberDensityLevel),
                                                    selectcolor=self._ionColorList[ii],
                                                    foreground=self._ionColorList[ii],
                                                    anchor='w', padx=2, width=fixedWidth,
                                                    command=callbackObject.handleToggleUpdate,
                                                    ) )

         self._numberDensityCheckButtonList[ii].grid( row = rowIndex, column = columnIndex )
         rowIndex += 1

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

      self.interior().pack( fill = 'both', expand = 1, padx = 4, pady = 5, side=TOP )

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

      return 0

   # } end of refreshInterface

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

   def getNumberDensityLevel( self, rowIndex ): # {

         isimDisplay    = self.getIsimDisplay()
         logReference   = isimDisplay.getLogReference()

         stats          = self._ionStatList[rowIndex]

         message        = 'getNumberDensityLevel: retrieved stats for rowIndex=' + str( rowIndex )
         message       += str( dir( stats ) )
         logReference.info( message )

         if stats is None or not stats.has_key( 'Average' ):
            message = 'getNumberDensityLevel: Missing stats for rowIndex=' + str( rowIndex )
            logReference.info( message )
            return 1.0e-04;
          
         stdDevOffset   = 3
         statPlan       = 2
         message        = 'Setting level for ' + self._ionAbbreviationList[rowIndex] + ' Plan=' + str( statPlan )
         message       += (" Ave=%.2e StdDev=%.2e Offset=%d " ) % ( stats['Average'], stats['StdDev'], stdDevOffset )
         if statPlan == 1:
            average  = stats['Average']
            stddev   = stats['StdDev']
            level    = average + stddev
         elif statPlan == 2 and stats.has_key( 'Bins' ):
            offset  = int( stats['Bins']*0.9 )
            offsetS = str( offset )
            if stats.has_key( offsetS ):
               message += " BinOffset=" + offsetS;
               level   = stats[offsetS]
            else:
               level   =  stats['Average'] + stdDevOffset*stats['StdDev']

            # if level is really small, try finding more acceptable
            # value in tail of distribution -- if level is too small, a 
            # unuseful glop is displayed

            if level < 1.0e-05:
               tailList = [ '-1000', '-500', '-400', '-300', '-200', '-100', '-50' ]
               while level in tailList: 
                  if stats.has_key( level ):
                     try:
                        level = float( stats[level] )
                        if level > 0.0:
                           break
                     except:
                        pass

            if stats.has_key( 'BulkNumberDensity/A**3' ):
               bulkDensity = stats['BulkNumberDensity/A**3']
               if level < 10.0*bulkDensity:
                  level = 10*bulkDensity

            # no luck -- force level to nonzero value

            if level < 1.0e-05:
               level = 1.0e-05

         else:

            average  = stats['Average']
            stddev   = stats['StdDev']
            level    = average + stdDevOffset*stddev

         message       += 'Level=%.2e' % ( level )
         logReference.info( message )

         return level

   # } end of getNumberDensityLevel

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

   def update( self ):

      """Update Pymol window based on activated entries in the 'Molecular Surface' interface
      """

      diagnostics    = 1
      hasActiveEntry = 0

      isimDisplay    = self.getIsimDisplay()
      logReference   = isimDisplay.getLogReference()

      # read in Isim molecule once

      if self._moleculeCheckButtonSetting.get():
         moleculeName = self.loadMoleculeIntoPymol()
         self.addToPymolDeleteList( moleculeName, 1 )
      else:
         moleculeName = None

      if self._bgColor is not None:
         pymol.cmd.bg_color( color=self._bgColor )

      # issue required Pymol commands

      self._mapName = 'FinalPtl'
      if self._potentialFileCheckButtonSetting.get():

         hasActiveEntry += 1
         self.addToPymolDeleteList( self._colorRampName, 0 )
         self.addToPymolDeleteList( self._mapName, 1 )

         self.loadFileIntoPymol( self._IsimPotentialFileName, self._mapName, 'object:map' )

         # set color ramp: [ -range, 0, range ]

         if self._colorRamp is None:
            stats           = self._IsimPotentialStats 
            if stats is not None:
               if stats.has_key( 'TrimmedStdDev' ):
                  rampRange       = int( self._defaultUnitsOfStdDev*stats['TrimmedStdDev'] ) + 1
               elif stats.has_key( 'StdDev' ):
                  rampRange       = int( self._defaultUnitsOfStdDev*stats['StdDev'] ) + 1
               else:
                  rampRange       = 5
            else:
               rampRange       = 5
            self._colorRamp = [ -rampRange, 0, rampRange ]

         pymol.cmd.ramp_new( self._colorRampName, self._mapName, self._colorRamp )

         # color surface

         if moleculeName is not None:
            pymol.cmd.set( 'surface_color', self._colorRampName, moleculeName )
            pymol.cmd.show( 'surface', moleculeName )

      if self._potentialFileCheckButtonSetting.get():

         hasActiveEntry += 1
         self.addToPymolDeleteList( self._colorRampName, 0 )
         self.addToPymolDeleteList( self._mapName, 1 )

         self.loadFileIntoPymol( self._IsimPotentialFileName, self._mapName, 'object:map' )

         # set color ramp: [ -range, 0, range ]

         if self._colorRamp is None:
            stats           = self._IsimPotentialStats 
            rampRange       = int( self._defaultUnitsOfStdDev*stats['TrimmedStdDev'] ) + 1
            self._colorRamp = [ -rampRange, 0, rampRange ]

         pymol.cmd.ramp_new( self._colorRampName, self._mapName, self._colorRamp )

      else:

         # show sticks

         if moleculeName is not None:
            pymol.cmd.hide( 'surface', moleculeName )
            pymol.cmd.show( 'sticks', moleculeName )

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

      # handle number densities

      for ii in range ( self._numberOfIons ):
         if self._numberDensityCheckButtonSetting[ii].get():

            hasActiveEntry          += 1
            numberDensityLevel       = self.getNumberDensityLevel( ii )
            numberDensityLevelStr    = "%.2e A**3" % numberDensityLevel
            fullFileName             = self._numberDensityFileList[ii]
            stats                    = self._ionStatList[ii]
            color                    = self._ionColorList[ii]
            color                    = color.strip().lower()

            mapName                  = self._ionAbbreviationList[ii]
            meshName                 = 'Msh' + self._ionAbbreviationList[ii]

            # diagnostics

            if diagnostics:
               message  = 'SimpleI: row ' + str( ii ) + ' is active\n'
               message += 'fullFileName=' + fullFileName + '\n'
               message += 'level= ' + numberDensityLevelStr + '\n'
               message += 'ColorRamp=' + str( self._colorRamp ) + '\n'
               message += 'Color=' + color + '\n'
               if logReference:
                  logReference.info( message )
               print message

            # issue required Pymol commands

            self.loadFileIntoPymol( fullFileName, mapName, 'object:map' )

            self.addToPymolDeleteList( mapName, 1 )
            self.addToPymolDeleteList( meshName, 0 )

            pymol.cmd.isosurface( meshName, mapName, numberDensityLevel )
            pymol.cmd.color( color, meshName )

      # return nonzero value if Pymol screen needs to be refreshed

      return hasActiveEntry

   # } end of update
        
   # -------------------------------------------------------------------------------------------------

   def toggleButtons( self, toggleState = 0 ): # {

      """Update Pymol window based on activated entries in the 'Molecular Surface' interface
      """

      self._potentialFileCheckButtonSetting.set( toggleState )
      for ii in range ( self._numberOfIons ):
         self._numberDensityCheckButtonSetting[ii].set( toggleState )

      moleculeName = self.loadMoleculeIntoPymol()
      pymol.cmd.hide( 'sticks', moleculeName )
      if self._moleculeCheckButtonSetting.get():
         self.addToPymolDeleteList( moleculeName, 1 )
         pymol.cmd.show( 'surface', moleculeName )
      else:
         pymol.cmd.hide( 'surface', moleculeName )

      return 0

   # } end of toggleButtons
        
# } end of IsimSimpleInterface class        

class IsimMolecularSurfaceInterface( IsimInterface ): # {

   def __init__( self, *args, **kwargs ):

        IsimInterface.__init__( self, *args, **kwargs )
        self._colorRampName   = 'ClrRmp'
        self._singletButtons  = 1
        self._maxMaps         = 3
        self._MolecularSurface = 'Molecular'
        self._SAS              = 'Solvent-accessible'

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

   def initialize( self, isimDisplay ): # {

      self.setIsimDisplay( isimDisplay )
      self.refreshInterface()

   # } end of initialize

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

   def refreshInterfaceNew( self ): # {

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

#      self._moleculeGroup    = Pmw.Group( self.interior() )
#      moleculeList           = self.loadMacromolecules()
#      if len( moleculeList ) == 1:
#         self._moleculeOptMenu  = Pmw.LabeledWidget( self._moleculeGroup.interior(),
#                                                     labelpos        = 'w',
#                                                     label_text      = moleculeList[0]
#                                                   )
#                                        
#      else:
#         self._moleculeOptMenu  = Pmw.OptionMenu( self._moleculeGroup.interior(),
#                                                  labelpos        = 'w',
#                                                  label_text      = 'Molecules',
#                                                  items           = moleculeList,
#                                                  initialitem     = moleculeList[0]
#                                                )
#                                        
#      self._moleculeOptMenu.pack( padx=4,side=LEFT )
#      self._moleculeGroup.pack( padx=4,side=TOP )

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

      # self._mapGroup               = Pmw.Group( self.interior() )
      self._mapGroup               = self
      mapList                      = self.loadMaps()
      mapTypes                     = self.getMapTypes( 0 )
      defaultMapType               = self.getDefaultMap()
      stepList                     = self.getStepList()

      self._mapLowerThresoldEntry  = []
      self._mapMiddleThresoldEntry = []
      self._mapUpperThresoldEntry  = []
      self._mapStatButtonEntry     = []
      surfaceTypeList              = [ self._MolecularSurface, self._SAS ]

      lowerThreshold               = -1.0
      middleThreshold              =  0.0
      upperThreshold               =  1.0

      lowerThresholdText           = None
      middleThresholdText          = None
      upperThresholdText           = None
      surfaceText                  = None

      rowFrame                     = Frame( self._mapGroup.interior() )
      rowFrame.pack()
      columnIndex                  = 0

      labelWidget                  = Pmw.LabeledWidget( rowFrame, labelpos = 'n', label_text='Observable')
      labelWidget.grid( row = 1, column = columnIndex )
      columnIndex += 1

      labelWidget                  = Pmw.LabeledWidget( rowFrame, labelpos = 'n', label_text='MC Step')
      labelWidget.grid( row = 1, column = columnIndex )
      columnIndex += 1

      labelWidget                  = Pmw.LabeledWidget( rowFrame, labelpos = 'n', label_text='Low')
      labelWidget.grid( row = 1, column = columnIndex )
      columnIndex += 1

      labelWidget                  = Pmw.LabeledWidget( rowFrame, labelpos = 'n', label_text='Middle')
      labelWidget.grid( row = 1, column = columnIndex )
      columnIndex += 1

      labelWidget                  = Pmw.LabeledWidget( rowFrame, labelpos = 'n', label_text='High')
      labelWidget.grid( row = 1, column = columnIndex )
      columnIndex += 1

      labelWidget                  = Pmw.LabeledWidget( rowFrame, labelpos = 'n', label_text='Surface')
      labelWidget.grid( row = 1, column = columnIndex )
      columnIndex += 1

      for ii in range ( self._maxMaps ):

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

         rowFrame               = Frame( self._mapGroup.interior() )
         rowFrame.pack()
         columnIndex            = 0

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

         # check button

         columnIndex = self.setupCheckButton( ii, columnIndex, rowFrame )

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

         # file type

         columnIndex = self.setupMapType( ii, columnIndex, rowFrame, mapTypes, defaultMapType )

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

         # MC step

         columnIndex = self.setupMcStep( ii, columnIndex, rowFrame, stepList, stepList[0] )

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

         # lower threshold 

         columnIndex = self.setupThreshold( self._mapLowerThresoldEntry, ii, columnIndex,
                                            rowFrame, lowerThreshold, lowerThresholdText )

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

         # middle threshold 

         columnIndex = self.setupThreshold( self._mapMiddleThresoldEntry, ii, columnIndex, 
                                            rowFrame, middleThreshold, middleThresholdText )

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

         # upper threshold 

         columnIndex = self.setupThreshold( self._mapUpperThresoldEntry, ii, columnIndex,
                                            rowFrame, upperThreshold, upperThresholdText )

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

         # surface type

         self._surfaceType      = Pmw.OptionMenu( rowFrame,
                                                  labelpos          = 'n',
                                                  label_text        = surfaceText,
                                                  items             = surfaceTypeList,
                                                  initialitem       = surfaceTypeList[0],
                                                  menubutton_width  = 18 )

         self._surfaceType.grid( row = ii, column = columnIndex )
         columnIndex += 1

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

         # stat results

         columnIndex = self.setupStatButton( self._mapStatButtonEntry, ii, columnIndex,
                                             rowFrame )

      self._mapGroup.pack( fill = 'both', expand = 1, padx = 4, pady = 5, side=TOP )

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

      # set initial values based on default selection

      fileInfo                     = self.getFileInfo( 0 )
      fullFileName                 = fileInfo['fullFileName']
      stats                        = self.getDxFileStats( fullFileName, self._SurfaceProfile )
      for entry in  self._mapLowerThresoldEntry:
         entry.setvalue( stats['Low'] )

      for entry in  self._mapMiddleThresoldEntry:
         entry.setvalue( stats['Medium'] )

      for entry in  self._mapUpperThresoldEntry:
         entry.setvalue( stats['High'] )

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

   # } end of refreshInterface

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

   def refreshInterface( self ): # {

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

#      self._moleculeGroup    = Pmw.Group( self.interior() )
#      moleculeList           = self.loadMacromolecules()
#      if len( moleculeList ) == 1:
#         self._moleculeOptMenu  = Pmw.LabeledWidget( self._moleculeGroup.interior(),
#                                                     labelpos        = 'w',
#                                                     label_text      = moleculeList[0]
#                                                   )
#                                        
#      else:
#         self._moleculeOptMenu  = Pmw.OptionMenu( self._moleculeGroup.interior(),
#                                                  labelpos        = 'w',
#                                                  label_text      = 'Molecules',
#                                                  items           = moleculeList,
#                                                  initialitem     = moleculeList[0]
#                                                )
#                                        
#      self._moleculeOptMenu.pack( padx=4,side=LEFT )
#      self._moleculeGroup.pack( padx=4,side=TOP )

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

      # self._mapGroup               = Pmw.Group( self.interior() )
      self._mapGroup               = self
      mapList                      = self.loadMaps()
      mapTypes                     = self.getMapTypes( 0 )
      defaultMapType               = self.getDefaultMap()
      stepList                     = self.getStepList()

      self._mapLowerThresoldEntry  = []
      self._mapMiddleThresoldEntry = []
      self._mapUpperThresoldEntry  = []
      self._mapStatButtonEntry     = []
      surfaceTypeList              = [ self._MolecularSurface, self._SAS ]

      lowerThreshold               = -1.0
      middleThreshold              =  0.0
      upperThreshold               =  1.0

      for ii in range ( self._maxMaps ):

         if ii == 0:
            lowerThresholdText  = 'Low'
            middleThresholdText = 'Medium'
            upperThresholdText  = 'High'
            unitsText           = 'Units'
            surfaceText         = 'Surface'
         else:
            lowerThresholdText  = None
            middleThresholdText = None
            upperThresholdText  = None
            unitsText           = None
            surfaceText         = None

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

         rowFrame               = Frame( self._mapGroup.interior() )
         rowFrame.pack()
         columnIndex            = 0

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

         # check button

         columnIndex = self.setupCheckButton( ii, columnIndex, rowFrame )

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

         # file type

         columnIndex = self.setupMapType( ii, columnIndex, rowFrame, mapTypes, defaultMapType )

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

         # MC step

         columnIndex = self.setupMcStep( ii, columnIndex, rowFrame, stepList, stepList[0] )

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

         # lower threshold 

         columnIndex = self.setupThreshold( self._mapLowerThresoldEntry, ii, columnIndex,
                                            rowFrame, lowerThreshold, lowerThresholdText )

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

         # middle threshold 

         columnIndex = self.setupThreshold( self._mapMiddleThresoldEntry, ii, columnIndex, 
                                            rowFrame, middleThreshold, middleThresholdText )

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

         # upper threshold 

         columnIndex = self.setupThreshold( self._mapUpperThresoldEntry, ii, columnIndex,
                                            rowFrame, upperThreshold, upperThresholdText )

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

         # units 

         columnIndex = self.setupUnits( rowFrame,  ii, columnIndex, rowFrame, unitsText )

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

         # surface type

         self._surfaceType      = Pmw.OptionMenu( rowFrame,
                                                  labelpos          = 'n',
                                                  label_text        = surfaceText,
                                                  items             = surfaceTypeList,
                                                  initialitem       = surfaceTypeList[0],
                                                  menubutton_width  = 18 )

         self._surfaceType.grid( row = ii, column = columnIndex )
         columnIndex += 1

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

         # stat results

         columnIndex = self.setupStatButton( self._mapStatButtonEntry, ii, columnIndex,
                                             rowFrame )

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

      self._mapGroup.pack( fill = 'both', expand = 1, padx = 4, pady = 5, side=TOP )

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

      # set initial values based on default selection

      fileInfo                     = self.getFileInfo( 0 )
      fullFileName                 = fileInfo['fullFileName']
      stats                        = self.getDxFileStats( fullFileName, self._BulkProfile )
      for entry in  self._mapLowerThresoldEntry:
         entry.setvalue( stats['Low'] )

      for entry in  self._mapMiddleThresoldEntry:
         entry.setvalue( stats['Medium'] )

      for entry in  self._mapUpperThresoldEntry:
         entry.setvalue( stats['High'] )

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

   # } end of refreshInterface

   def update( self ): # {

      """Update Pymol window based on activated entries in the 'Molecular Surface' interface
      """

      diagnostics  = 1

      isimDisplay  = self.getIsimDisplay()
      logReference = isimDisplay.getLogReference()

      # read in Isim molecule once

      moleculeName = self.loadMoleculeIntoPymol()

      if self._bgColor is not None:
         pymol.cmd.bg_color( color=self._bgColor )

      # loop over rows in menu, only handling ones
      # with the checkButton set

      hasActiveEntry = 0
      try:
         for ii in range ( self._maxMaps ):
            if self._mapCheckButtonSetting[ii].get():
   
               fileType               = self._mapTypeMenus[ii].getvalue()
               mcStep                 = self._mapStepMenus[ii].getvalue()
               fullFileName           = self.getObservableFullFileName( fileType, mcStep )
               parsedFileNameList     = UtilitiesSimTk.UtilitiesSimTk.parseFileName( fullFileName );
               mapName                = self.getObservableMapName( fileType, mcStep )
   
               # get thresholds for surface color map and build color ramp
   
               colorRamp              = self.getDisplayLevels( ii, fullFileName, self._SurfaceProfile )
               print "\nGot ramp"
   
               # diagnostics
   
               if diagnostics:
                  message  = 'Surface: row ' + str( ii ) + ' is active\n'
                  message += 'fileType=<' + fileType + '>\n'
                  message += 'mcStep=' + str( mcStep ) + '\n'
                  message += 'mapName=<' + mapName + '>\n'
                  message += 'fullFileName=' + fullFileName + '\n'
                  message += 'colorRamp=%.2e %.2e %.2e\n' % ( colorRamp[0], colorRamp[1], colorRamp[2] )
                  message += 'surface=' + self._surfaceType.getvalue() + '\n'
                  if logReference:
                     logReference.info( message )
                  print message
   
               # issue required Pymol commands
   
               self.addToPymolDeleteList( self._colorRampName, 0 )
               self.addToPymolDeleteList( mapName, 1 )
   
               self.loadFileIntoPymol( fullFileName, mapName, 'object:map' )
              
               pymol.cmd.ramp_new( self._colorRampName, mapName, colorRamp )
               pymol.cmd.set( 'surface_color', self._colorRampName, moleculeName )
   
               if self._surfaceType.getvalue() == self._MolecularSurface:
                  pymol.cmd.set('surface_solvent', 0, moleculeName)
                  pymol.cmd.set('surface_ramp_above_mode', 1, moleculeName)
               else:
                  pymol.cmd.set('surface_solvent', 1, moleculeName)
                  pymol.cmd.set('surface_ramp_above_mode', 0, moleculeName)
   
               pymol.cmd.show( 'surface', moleculeName )
   
               hasActiveEntry = 1
      except:
         print "Pymol error! " + sys.exc_info()[0] + "\n"
   
      # return nonzero value if Pymol screen needs to be refreshed

      return hasActiveEntry

   # } end of update
        
# } end of IsimMolecularSurfaceInterface class        

class IsimMeshSurfaceInterface( IsimInterface ): # {

   def __init__( self, *args, **kwargs ): # {
      IsimInterface.__init__( self, *args, **kwargs )
   # } end of __init__

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

   def initialize( self, isimDisplay ): # {

      self.setIsimDisplay( isimDisplay )
      self.refreshInterface()

   # } end of initialize

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

   def refreshInterface( self ): # {

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

      self._mapGroup               = Pmw.Group( self.interior() )
      mapList                      = self.loadMaps()
      mapTypes                     = self.getMapTypes( 1 )
      defaultMapType               = self.getDefaultMap()
      stepList                     = self.getStepList()

      self._mapLevelEntry          = []
      self._colorMenu              = []
      self._surfaceTypeMenu        = []
      self._mapStatButtonEntry     = []

      colorList                    = [ 'Green', 'White', 'Yellow', 'Red', 'Blue', 'Black' ]

      # remove background color

      if self._bgColor is not None:
         newColorList = []
         for color in colorList:
            if color.lower().find( self._bgColor ) == -1:
               newColorList.append( color )
         colorList = newColorList

      surfaceTypeList              = [ 'Isosurface', 'Isomesh', 'Isodot' ]
      initialLevel                 = -3.0

      for ii in range ( self._maxMaps ):

         if ii == 0:
            typeLabelText       = 'Observable'
            stepLabelText       = 'MC Step'
            levelText           = 'Level'
            unitsText           = 'Units'
            colorText           = 'Color'
            surfaceTypeText     = 'Surface '
            thresholdText       = 'Calculate'
         else:
            typeLabelText       = None
            stepLabelText       = None
            levelText           = None
            unitsText           = None
            colorText           = None
            surfaceTypeText     = None
            thresholdText       = None

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

         rowFrame               = Frame( self._mapGroup.interior() )

         rowFrame.pack()
         columnIndex            = 0

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

         # check button

         columnIndex = self.setupCheckButton( ii, columnIndex, rowFrame, 0 )

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

         # file type

         columnIndex = self.setupMapType( ii, columnIndex, rowFrame, mapTypes, defaultMapType )

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

         # file step

         columnIndex = self.setupMcStep( ii, columnIndex, rowFrame, stepList, stepList[0] )

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

         # level threshold 

         columnIndex = self.setupThreshold( self._mapLevelEntry, ii, columnIndex, rowFrame,
                                            initialLevel, levelText )

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

         # units 

         columnIndex = self.setupUnits( rowFrame,  ii, columnIndex, rowFrame, unitsText )

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

         # color

         columnIndex = self.setupOptionMenu( self._colorMenu, ii, columnIndex, rowFrame, colorText, colorList,
                                             colorList[0], 6 )

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

         # surface type

         columnIndex = self.setupOptionMenu( self._surfaceTypeMenu, ii, columnIndex, rowFrame,
                                             surfaceTypeText, surfaceTypeList, surfaceTypeList[0], 10 )

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

         # stat results

         columnIndex = self.setupStatButton( self._mapStatButtonEntry, ii, columnIndex,
                                             rowFrame )

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

      self._mapGroup.pack( fill = 'both', expand = 1, padx = 4, pady = 5, side=TOP )

      # set initial default level

      fileInfo                     = self.getFileInfo( 0 )
      fullFileName                 = fileInfo['fullFileName']
      stats                        = self.getDxFileStats( fullFileName, self._BulkProfile )
      for entry in  self._mapLevelEntry:
            entry.setvalue( stats['Medium'] )

   # } end of refresh

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

   def update( self ): # {

      """Update Pymol window based on activated entries in the 'Molecular Surface' interface
      """

      diagnostics  = 1

      isimDisplay  = self.getIsimDisplay()
      logReference = isimDisplay.getLogReference()

      # read in Isim molecule once

      moleculeName = self.loadMoleculeIntoPymol()

      if self._bgColor is not None:
         pymol.cmd.bg_color( color=self._bgColor )

      # loop over rows in menu, only handling ones
      # with the checkButton set

      hasActiveEntry = 0
      for ii in range ( self._maxMaps ):
         if self._mapCheckButtonSetting[ii].get():

            # get full file name

            fileType               = self._mapTypeMenus[ii].getvalue()
            mcStep                 = self._mapStepMenus[ii].getvalue()
            fullFileName           = self.getObservableFullFileName( fileType, mcStep )
            parsedFileNameList     = UtilitiesSimTk.UtilitiesSimTk.parseFileName( fullFileName );
            mapName                = self.getObservableMapName( fileType, mcStep )

            # get thresholds for surface color map and build color ramp

            level                  = float( self._mapLevelEntry[ii].getvalue() )
            levels                 = self.getDisplayLevels( ii, fullFileName, self._BulkProfile )
            level                  = levels[1]
            color                  = self._colorMenu[ii].getvalue()
            color                  = color.strip().lower()
            surfaceType            = self._surfaceTypeMenu[ii].getvalue()
            meshName               = 'Msh' + mapName + '_Lvl' + str( level )

            # diagnostics

            if diagnostics:
               message  = "Mesh: row " + str( ii ) + ' is active\n'
               message += 'FileType=<' + fileType + '>\n'
               message += 'mcStep=' + str( mcStep ) + '\n'
               message += 'fullFileName=<' + fullFileName + '>\n'
               message += 'Level=' + str( level ) + '\n'
               message += 'Color=' + color + '\n'
               message += 'Type=' + surfaceType + '\n'
               if logReference:
                  logReference.info( message )
               print message

            # issue required Pymol commands

            self.loadFileIntoPymol( fullFileName, mapName, 'object:map' )

            self.addToPymolDeleteList( mapName, 1 )
            self.addToPymolDeleteList( meshName, 0 )

            if surfaceType.lower() == 'isomesh':
               pymol.cmd.isomesh( meshName, mapName, level )
            elif surfaceType.lower() == 'isosurface':
               pymol.cmd.isosurface( meshName, mapName, level )
            elif surfaceType.lower() == 'isodot':
               pymol.cmd.isodot( meshName, mapName, level )

            pymol.cmd.color( color, meshName )
            hasActiveEntry = 1

      # return nonzero value if Pymol screen needs to be refreshed

      return hasActiveEntry

   # } end of update

# } end of IsimMeshSurfaceInterface class        

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

class IsimCallbackClass: # {

   def __init__( self ):
      self._whoIsCalling = None
      self._rowIndex     = None

   def setRowIndex( self, rowIndex ):
      self._rowIndex = rowIndex

   def getRowIndex( self ):
      return self._rowIndex

   def setWhoIsCalling( self, whoIsCalling ):
      self._whoIsCalling = whoIsCalling

   def getWhoIsCalling( self ):
      return self._whoIsCalling

   def setIsimMainInterface( self, isimMainInterface ):
      self._isimMainInterface = isimMainInterface

   def getIsimMainInterface( self ):
      return self._isimMainInterface

   def setData( self, whoIsCalling, rowIndex, isimMainInterface ):
      self.setWhoIsCalling( whoIsCalling )
      self.setRowIndex( rowIndex )
      self.setIsimMainInterface( isimMainInterface )

# } end of class IsimCallbackClass

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

class CheckButtonCallback( IsimCallbackClass ): # {

   def __init__( self ): # {
      IsimCallbackClass.__init__( self )
   # end of __init__

   def handleButtonEvent( self ):

#     print 'In callback ' + str( self._rowIndex ) + \
#            str( self._isimMainInterface._mapCheckButtonSetting[self._rowIndex].get() )

      
      if self._isimMainInterface._singletButtons:
         currentValue = self._isimMainInterface._mapCheckButtonSetting[self._rowIndex].get( )
         for ii in range ( self._isimMainInterface._maxMaps ):
            self._isimMainInterface._mapCheckButtonSetting[ii].set( 0 )
         self._isimMainInterface._mapCheckButtonSetting[self._rowIndex].set( currentValue )

   # } end of handleButtonEvent

# } end of class CheckButtonCallback

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

class SimplifiedInterfaceCheckButtonCallback( IsimCallbackClass ): # {

   def __init__( self ): # {
      IsimCallbackClass.__init__( self )
   # end of __init__

   def handleToggleUpdate( self ):

      whoIsCalling = self.getWhoIsCalling()
      if whoIsCalling is self._isimMainInterface._moleculeToggle and self._isimMainInterface._moleculeCheckButtonSetting.get() == 0:
         self._isimMainInterface._potentialFileCheckButtonSetting.set( 0 )
         self._isimMainInterface.clear( 1 )

      if whoIsCalling is self._isimMainInterface._potentialToggle and self._isimMainInterface._potentialFileCheckButtonSetting.get( ) == 1:
         self._isimMainInterface._moleculeCheckButtonSetting.set( 1 )

      if self._isimMainInterface._moleculeCheckButtonSetting.get( ) == 1:
         pymol.cmd.enable( self._isimMainInterface._moleculeName )

      if self._isimMainInterface._potentialFileCheckButtonSetting.get( ) == 1:
         pymol.cmd.enable( self._isimMainInterface._mapName )

      self._isimMainInterface.clear( 0 )
      self._isimMainInterface._updateMain = 1

      self._isimMainInterface.update()
      self._isimMainInterface._updateMain = 0
      pymol.cmd.refresh()
      pymol.cmd.recolor()

   # } end of handleToggleUpdate

# } end of class SimplifiedInterfaceCheckButtonCallback

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

class StatButtonCallback( IsimCallbackClass ): # {

   def __init__( self ): # {
      IsimCallbackClass.__init__( self )
   # end of __init__

   def handleButtonEvent( self ):

      print 'In StatButtonCallback ' + str( self._rowIndex )

      fileType               = self._isimMainInterface._mapTypeMenus[self._rowIndex].getvalue()
      mcStep                 = self._isimMainInterface._mapStepMenus[self._rowIndex ].getvalue()
      fullFileName           = self._isimMainInterface.getObservableFullFileName( fileType, mcStep )
      filePartsList          = UtilitiesSimTk.UtilitiesSimTk.parseFileName( fullFileName )
      baseName               = filePartsList[1]

      self._textDialog       = Pmw.TextDialog( self._isimMainInterface.interior(), title = 'Stats' )

      if self._isimMainInterface.isApbsFile( fileType ):
         self._textDialog.insert( 'end', baseName + ' is an Apbs file -- no stats are currently available' )
         self._textDialog.insert( 'end', 'for files of this type.' )
      else:
         commentList = self._isimMainInterface.getDxFileCommentList( fullFileName )
         allLines    = '\n\n\n'
         statMatch   = re.compile( '# DataStat ' )
         for commentLine in commentList:
            match = statMatch.match( commentLine )
            if match:
               allLines += commentLine[match.end():-1]
               allLines += '\n'
   
         self._textDialog.insert( 'end', 'Statistics for the grid values contained in '+ baseName + ' step=' + str( mcStep ) )
         self._textDialog.insert( 'end', '\n\nSee the text at the bottom of the dialog for an explanation of' )
         self._textDialog.insert( 'end', '\nthe entries and how they were calculated.\n' )
         self._textDialog.insert( 'end', allLines )
         self._textDialog.insert( 'end', self.getExplanationText() )

      self._textDialog.configure( text_state = 'disabled' )
      self._textDialog.withdraw()
      self._textDialog.show()

   # } end of handleButtonEvent

   def getExplanationText( self ): # {

      explainText = '\n\n'
      explainText  += 'Glossary/Explanation of fields:\n\n'
      explainText  += 'Version is a tag used to allow the format of the statistics section of the grid files\n'
      explainText  += 'to evolve; it can be ignored by the user.\n\n'
      explainText  += 'Temperature is the simulation temperature in degrees Kelvin\n\n'
      explainText  += 'Name is used to demarcate statistics for grid points on or very close to the molecular surface\n'
      explainText  += '(Name=Surface), and statistics for grid points within ~15A (including surface) of biomolecule (Name=Bulk)\n'
      explainText  += 'Statistics for both sets of points are included; the Surface statistics are used for setting values\n'
      explainText  += 'at the molecular surface (first worksheet), whereas the Bulk statistics are used for\n'
      explainText  += 'displaying observable isosurfaces in the bulk (second worksheet).\n\n'
      explainText  += 'Count is the number of grid points sampled (surface or bulk)\n\n'
      explainText  += 'Average and StdDev are the average and standard deviation of the sampled points.\n\n'
      explainText  += 'TrimmedAverage and TrimmedStdDev are the average and standard deviation of the sampled points\n'
      explainText  += 'with the lowest 5% and highest 5% of values excluded in the calculation of the average/stddev\n\n'
      explainText  += 'Min/Max are the minimum and maximum of the sampled points.\n\n'
      explainText  += 'Bins is the number of bins used in generating a histogram of the grid values.\n'
      explainText  += "The points 1-'Bins' following the 'Bins' line are the histogram values. For example, if the entries are as follows,\n"
      explainText  += 'where we assume there are 20 bins,\n\n'
      explainText  += '   1 -15.41\n'
      explainText  += '   2  -8.54\n'
      explainText  += '   ... \n'
      explainText  += '   19 11.1\n\n'
      explainText  += 'then 5% of the grid values are less than -15.41, 10% are less than -8.54, and 95% are less than 11.1\n\n'
      explainText  += 'The DistributionTail provides a finer-grained histogram into the grid values at the high-end of the distribution --\n'
      explainText  += "this is often the region of most interest. If 'DistributionTail' is 3 for example, then there are 3 'fine-grained' entries that\n"
      explainText  += "follow the 'DistributionTail' line. Assuming the lines are as follows\n\n"
      explainText  += '-5    4.713\n'
      explainText  += '-10   0.992\n'
      explainText  += '-100 -2.52\n\n'
      explainText  += 'then 5 grid values are greater than 4.713, 10 grid values are greater than 0.992 and 100 grid values are greater than -2.52\n'
      explainText  += "The number of 'DistributionTail' entries is dependent on the number of sampled grid points; currently the program \n"
      explainText  += "will include histogram values ranging from 5 to 3000 grid points from the distribution maximum in a quasi-logrithmic fashion.\n"

      return explainText

   # } end of getExplanationText

# } end of class StatButtonCallback

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

class MapMenuCallback( IsimCallbackClass ): # {

   def __init__( self ): # {
      IsimCallbackClass.__init__( self )
   # } end of __init__

   def handleMenuUpdate( self, selectionString ): # {

      print "Called by <" + self._whoIsCalling + '> at row index=' + str( self._rowIndex ) + ' selection=<' + selectionString + '>'

      fileInfo      = self._isimMainInterface.getFileInfo( self._rowIndex )

      fileTypeIndex = fileInfo['fileTypeIndex']
      filePrefix    = fileInfo['filePrefix']
      fileIonIndex  = fileInfo['fileIonIndex']
      fileType      = fileInfo['fileType']
      mcStep        = fileInfo['mcStep']
      fullFileName  = fileInfo['fullFileName']

      if filePrefix:

         # Isim selection

         stepList = IsimGridFileSimTk.getAvailableMcStepsForFileOfSpecifiedType( 
                     self._isimMainInterface._isimDisplay.getWorkingDirectoryName(), filePrefix, fileIonIndex )
      else:

         # Apbs selection

         stepList = [ 0 ]

      # set step menu

      self._isimMainInterface._mapStepMenus[self.getRowIndex()].setitems( stepList )
      if self._whoIsCalling.find( 'MC Step' ) == -1:
         self._isimMainInterface._mapStepMenus[self.getRowIndex()].setvalue( stepList[0] )

      # set menu options for units

      regExMatchNumber = re.compile( 'NUMBER' )
      regExMatchCharge = re.compile( 'CHARGE' )
      menuReset        = None 
      if filePrefix and regExMatchNumber.search( filePrefix ):
         menuReset = self._isimMainInterface._numberDensityUnitTypeList
      elif filePrefix and regExMatchCharge.search( filePrefix ):
         menuReset = self._isimMainInterface._chargeDensityUnitTypeList
      else:
         menuReset = self._isimMainInterface._espUnitTypeList

      if menuReset is not None:
         self._isimMainInterface._unitTypeMenus[self.getRowIndex()].setitems( menuReset )
         self._isimMainInterface._unitTypeMenus[self.getRowIndex()].setvalue( menuReset[0] )

      # get fileInfo for new file -- step list may have changed 

      fileInfo      = self._isimMainInterface.getFileInfo( self._rowIndex )
      fullFileName  = fileInfo['fullFileName']

      if self._isimMainInterface._colorRampName is not None:
         self._isimMainInterface.addToPymolDeleteList( self._isimMainInterface._colorRampName, 0 )
         self._isimMainInterface._colorRamp = None

      # update levels/thresholds

      if self._isimMainInterface._mapLevelEntry is not None:
         self._isimMainInterface.setDisplayLevels( self._rowIndex, self._isimMainInterface._BulkProfile )

      if self._isimMainInterface._mapLowerThresoldEntry is not None:
         self._isimMainInterface.setDisplayLevels( self._rowIndex, self._isimMainInterface._SurfaceProfile )

   # } end of handleMenuUpdate 

   def handleButtonUpdate( self ):
      print "Button: Called by " + self._whoIsCalling + ' at row index=' + str( self._rowIndex )

# } end of class MapMenuCallback

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

class UnitMenuCallback( IsimCallbackClass ): # {

   def __init__( self ): # {
      IsimCallbackClass.__init__( self )
   # } end of __init__

   def handleMenuUpdate( self, selectionString ): # {

      print "Called by <" + self._whoIsCalling + '> at row index=' + str( self._rowIndex ) + ' selection=<' + selectionString + '>'

      self._isimMainInterface.setDisplayLevels( self._rowIndex )

   # } end of handleMenuUpdate 

   def handleButtonUpdate( self ):
      print "Button: Called by " + self._whoIsCalling + ' at row index=' + str( self._rowIndex )

# } end of class UnitMenuCallback
