# Lightweight ET Viewer using PyMOL
# Script/plugin by Rhonald Lua (lua@bcm.edu)
# Some parts of GUI code based on apbs_tools.py by Michael Lerner.
#
# THE AUTHOR(S) DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.  IN
# NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
# USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
# ----------------------------------------------------------------------

# Possible locations of the PyMOL plugins directory:
# On Windows, this is usually:
#   C:\Program Files\DeLano Scientific\PyMOL\modules\pmg_tk\startup
# On Macintosh, with the X11/Hybrid version, the location is probably:
#   PyMOLX11Hybrid.app/pymol/modules/pmg_tk/startup
# On a linux machine, it might be under:
#   /var/lib/python-support/python2.4/pmg_tk/startup/


from pymol import cmd  # This launches pymol
from tkinter import *
import os, fnmatch, time, math, colorsys, string, urllib.request, urllib.parse, urllib.error
import tkinter, Pmw, tkinter.colorchooser
# from tkSimpleDialog import *
from threading import Thread, Event
import collections

# Square of cutoff distance defining adjacency or contact
# between residues
CONTACT_DISTANCE2 = 16.0

# Enable or disable multithreading on certain operations
_ET_THREADING = 0


def _getPyMOLstructuresequence(self, model, structurename, chainindicator):
    three2one = {
        "ALA": 'A',
        "ARG": 'R',
        "ASN": 'N',
        "ASP": 'D',
        "CYS": 'C',
        "GLN": 'Q',
        "GLU": 'E',
        "GLY": 'G',
        "HIS": 'H',
        "ILE": 'I',
        "LEU": 'L',
        "LYS": 'K',
        "MET": 'M',
        "PHE": 'F',
        "PRO": 'P',
        "SER": 'S',
        "THR": 'T',
        "TRP": 'W',
        "TYR": 'Y',
        "VAL": 'V',
        "A": "A",
        "G": "G",
        "T": "T",
        "U": "U",
        "C": "C", }

    # print 'Comparing residue numbers of structure \'%s\' and ranks \'%s\'...' % (structurename+chainindicator,
    #                                                                             rankspath)
    # Check that the residue numbers in the model
    # and in the ranks file are identical
    resnumDict = {}
    resnums = []
    restypes = []
    # chainup=chainindicator.upper()
    # Case of chain indicator is relevant
    if len(chainindicator) > 0:
        for i in range(len(model.atom)):
            atomobj = model.atom[i]
            if chainindicator == atomobj.chain:
                try:  # Does not just look for CA atom name, so we get several atoms with the same residue number and name, hence the dictionary
                    aa1 = three2one[atomobj.resn]  # Consider only residues with standard amino acids
                    try:
                        rrr = resnumDict[atomobj.resi]
                    except KeyError:
                        resnumDict[atomobj.resi] = 1
                        resnums.append(atomobj.resi)
                        restypes.append(aa1)
                except KeyError:
                    pass
    else:
        for i in range(len(model.atom)):
            try:
                atomobj = model.atom[i]
                aa1 = three2one[atomobj.resn]  # Consider only residues with standard amino acids
                try:
                    rrr = resnumDict[atomobj.resi]
                except KeyError:
                    resnumDict[atomobj.resi] = 1
                    resnums.append(atomobj.resi)
                    restypes.append(aa1)
            except KeyError:
                pass
    return restypes, resnums


#######################################################
# Functions useful for calculating clustering z-score #
#######################################################
def _et_calcDist(self, atoms1, atoms2):
    """return smallest distance (squared) between two groups of atoms"""
    # (not distant by more than ~100 A)
    # mind2=CONTACT_DISTANCE2+100
    c1 = atoms1[0]  # atoms must not be empty
    c2 = atoms2[0]
    mind2 = (c1[0] - c2[0]) * (c1[0] - c2[0]) + \
            (c1[1] - c2[1]) * (c1[1] - c2[1]) + \
            (c1[2] - c2[2]) * (c1[2] - c2[2])
    for c1 in atoms1:
        for c2 in atoms2:
            d2 = (c1[0] - c2[0]) * (c1[0] - c2[0]) + \
                 (c1[1] - c2[1]) * (c1[1] - c2[1]) + \
                 (c1[2] - c2[2]) * (c1[2] - c2[2])
            if d2 < mind2:
                mind2 = d2
    return mind2  # Square of distance between most proximate atoms


def _et_computeAdjacency(self, structurename):
    """Compute the pairs of contacting residues
    A(i,j) implemented as a hash of hash of residue numbers"""
    # self.adjacencyOK=False
    try:
        model = cmd.get_model(structurename)
    except Exception:
        print("Structure \'%s\' does not exist!" % structurename)
        return None, None

    three2one = {
        "ALA": 'A',
        "ARG": 'R',
        "ASN": 'N',
        "ASP": 'D',
        "CYS": 'C',
        "GLN": 'Q',
        "GLU": 'E',
        "GLY": 'G',
        "HIS": 'H',
        "ILE": 'I',
        "LEU": 'L',
        "LYS": 'K',
        "MET": 'M',
        "PHE": 'F',
        "PRO": 'P',
        "SER": 'S',
        "THR": 'T',
        "TRP": 'W',
        "TYR": 'Y',
        "VAL": 'V',
        "A": "A",
        "G": "G",
        "T": "T",
        "U": "U",
        "C": "C", }

    ResAtoms = {}  # List of atom coordinates indexed by residue number
    # atom.resi is a string, convert it to int
    for i in range(len(model.atom)):
        atom = model.atom[i]
        try:
            # 07/12/10 Test for standard amino acids
            aa = three2one[atom.resn]
        except KeyError:
            continue
        try:
            ResAtoms[int(atom.resi)].append(atom.coord)
        except KeyError:
            ResAtoms[int(atom.resi)] = [atom.coord]

    A = {}
    A_recip = {}
    for resi in list(ResAtoms.keys()):
        for resj in list(ResAtoms.keys()):
            if resi < resj:
                if (self.calcDist(ResAtoms[resi],
                                  ResAtoms[resj]) < CONTACT_DISTANCE2):
                    try:
                        A[resi][resj] = 1
                    except KeyError:
                        A[resi] = {resj: 1}
                    # 12/21/11 This addition should not break anything(?). 07/24/12 It did, the z-scores will be wrong.
                    try:
                        A_recip[resj][resi] = 1
                    except KeyError:
                        A_recip[resj] = {resi: 1}

    # self._A=A
    # self.adjacencyOK=True
    return A, ResAtoms, A_recip


def _et_calcZScore(self, reslist, L, A, bias=1):
    """Calculate z-score (z_S) for residue selection reslist=[1,2,...]
    z_S = (w-<w>_S)/sigma_S
    The steps are:
    1. Calculate Selection Clustering Weight (SCW) 'w'
    2. Calculate mean SCW (<w>_S) in the ensemble of random
    selections of len(reslist) residues
    3. Calculate mean square SCW (<w^2>_S) and standard deviation (sigma_S)
    Reference: Mihalek, Res, Yao, Lichtarge (2003)

    reslist - a list of int's of protein residue numbers, e.g. ET residues
    L - length of protein
    A - the adjacency matrix implemented as a dictionary. The first key is related to the second key by resi<resj.
    bias - option to calculate with bias or nobias (j-i factor)"""
    # A=self._A
    # Calculate w
    w = 0
    if bias == 1:
        for resi in reslist:
            for resj in reslist:
                if resi < resj:
                    try:
                        Aij = A[resi][resj]  # A(i,j)==1
                        w += (resj - resi)
                    except KeyError:
                        pass
    elif bias == 0:
        for resi in reslist:
            for resj in reslist:
                if resi < resj:
                    try:
                        Aij = A[resi][resj]  # A(i,j)==1
                        w += 1
                    except KeyError:
                        pass

    # Calculate <w>_S and <w^2>_S.
    # Use expressions (3),(4),(5),(6) in Reference.
    M = len(reslist)
    # L=self.ranksObj1.getSize()
    pi1 = M * (M - 1.0) / (L * (L - 1.0))
    pi2 = pi1 * (M - 2.0) / (L - 2.0)
    pi3 = pi2 * (M - 3.0) / (L - 3.0)
    w_ave = 0
    w2_ave = 0
    if bias == 1:
        for resi, neighborsj in list(A.items()):
            for resj in neighborsj:
                w_ave += (resj - resi)
                for resk, neighborsl in list(A.items()):
                    for resl in neighborsl:
                        if (resi == resk and resj == resl) or \
                                (resi == resl and resj == resk):
                            w2_ave += pi1 * (resj - resi) * (resl - resk)
                        elif (resi == resk) or (resj == resl) or \
                                (resi == resl) or (resj == resk):
                            w2_ave += pi2 * (resj - resi) * (resl - resk)
                        else:
                            w2_ave += pi3 * (resj - resi) * (resl - resk)
    elif bias == 0:
        for resi, neighborsj in list(A.items()):
            w_ave += len(neighborsj)
            for resj in neighborsj:
                # w_ave+=1
                for resk, neighborsl in list(A.items()):
                    for resl in neighborsl:
                        if (resi == resk and resj == resl) or \
                                (resi == resl and resj == resk):
                            w2_ave += pi1
                        elif (resi == resk) or (resj == resl) or \
                                (resi == resl) or (resj == resk):
                            w2_ave += pi2
                        else:
                            w2_ave += pi3
    w_ave = w_ave * pi1
    # print "w_ave", w_ave
    sigma = math.sqrt(w2_ave - w_ave * w_ave)
    # print "sigma", sigma

    if sigma == 0:  # Response to Bioinformatics reviewer 08/24/10
        return 'NA'

    return (w - w_ave) / sigma


def _et_computeInterface(self, structurename1, structurename2, dist2):
    """Get the interface residues between structurename1 and structurename2"""

    try:
        model1 = cmd.get_model(structurename1)
    except Exception:
        print("Structure \'%s\' does not exist!" % structurename1)
        return None, None, None, None

    try:
        model2 = cmd.get_model(structurename2)
    except Exception:
        print("Structure \'%s\' does not exist!" % structurename2)
        return None, None, None, None

    ResAtoms1 = {}  # List of atom coordinates indexed by residue number
    # atom.resi is a string, leave it as string
    for i in range(len(model1.atom)):
        atom = model1.atom[i]
        try:
            ResAtoms1[int(atom.resi)].append(atom.coord)
        except KeyError:
            ResAtoms1[int(atom.resi)] = [atom.coord]

    ResAtoms2 = {}  # List of atom coordinates indexed by residue number
    for i in range(len(model2.atom)):
        atom = model2.atom[i]
        try:
            ResAtoms2[int(atom.resi)].append(atom.coord)
        except KeyError:
            ResAtoms2[int(atom.resi)] = [atom.coord]

    A1 = {}
    A2 = {}
    for res1 in list(ResAtoms1.keys()):
        for res2 in list(ResAtoms2.keys()):
            if (self.calcDist(ResAtoms1[res1],
                              ResAtoms2[res2]) < dist2):
                try:
                    A1[res1].append(res2)
                except KeyError:
                    A1[res1] = [res2]
                try:
                    A2[res2].append(res1)
                except KeyError:
                    A2[res2] = [res1]

    return A1, A2, ResAtoms1, ResAtoms2


def _et_computeCoupling(self, reslist1, reslist2, L1, L2, A):
    """Calculate coupling z-score (z_S) for residue selection reslist1=[1,2,...] and reslist2
    z_S = (w-<w>_S)/sigma_S
    The steps are:
    1. Calculate the number of couples ('w') between residues in reslist1 and reslist2
    2. Calculate mean number of couples (<w>_S) in the ensemble of random
    selections of len(reslist) residues on each protein partner
    3. Calculate mean square number of couples (<w^2>_S) and standard deviation (sigma_S)

    reslist1 and reslist2 - lists of int's of protein residue numbers, e.g. ET residues
    L1 and L2 - lengths of protein partners
    A - the adjacency matrix of interface residues implemented as a dictionary"""
    w = 0
    for resi in reslist1:
        for resj in reslist2:
            try:
                if resj in A[resi]:  # A is a dictionary of lists of neighbors
                    w += 1
            except KeyError:
                pass

    # Calculate <w>_S and <w^2>_S.
    M1 = len(reslist1)
    M2 = len(reslist2)
    c1 = M1 * 1.0 / L1
    c2 = M2 * 1.0 / L2
    SiSk = c1 * (M1 - 1.0) / (L1 - 1.0)
    SjSl = c2 * (M2 - 1.0) / (L2 - 1.0)
    w_ave = 0
    w2_ave = 0
    for resi, neighborsj in list(A.items()):
        w_ave += len(neighborsj)  # total number of interface couples when the loop ends
        for resj in neighborsj:
            for resk, neighborsl in list(A.items()):
                for resl in neighborsl:
                    if resi == resk:
                        SiSjSkSl = c1
                    else:
                        SiSjSkSl = SiSk
                    if resj == resl:
                        SiSjSkSl *= c2
                    else:
                        SiSjSkSl *= SjSl
                    w2_ave += SiSjSkSl
    w_ave = w_ave * c1 * c2
    # print "w_ave", w_ave
    sigma = math.sqrt(w2_ave - w_ave * w_ave)
    # print "sigma", sigma

    return w, w_ave, sigma


#################################################################

##try:
##    PYMOL_PATH=os.environ['PYMOL_PATH']
##except KeyError:
##    PYMOL_PATH='.'

def __init__(self):
    self.menuBar.addmenuitem('Plugin', 'command',
                             'Launch PyETV',
                             label='PyETV version - 1.3',
                             command=lambda s=self: ETTools(s))


class FileDialogButtonClassFactory:
    def get(fn, filter='*'):
        """This returns a FileDialogButton class that will
        call the specified function with the resulting file.
        """

        class FileDialogButton(tkinter.Button):
            # This is just an ordinary button with special colors.

            def __init__(self, master=None, cnf={}, **kw):
                '''when we get a file, we call fn(filename)'''
                self.fn = fn
                self.__toggle = 0
                tkinter.Button.__init__(*(self, master, cnf), **kw)
                self.configure(command=self.set)

            def set(self):
                fd = PmwFileDialog(self.master, filter=filter)
                fd.title('Please choose a file')
                n = fd.askfilename()
                if n is not None:
                    self.fn(n)

        return FileDialogButton

    get = staticmethod(get)


#
# The classes PmwFileDialog and PmwExistingFileDialog and the _errorpop function
# are taken from the Pmw contrib directory.  The attribution given in that file
# is:
################################################################################
# Filename dialogs using Pmw
#
# (C) Rob W.W. Hooft, Nonius BV, 1998
#
# Modifications:
#
# J. Willem M. Nissink, Cambridge Crystallographic Data Centre, 8/2002
#    Added optional information pane at top of dialog; if option
#    'info' is specified, the text given will be shown (in blue).
#    Modified example to show both file and directory-type dialog
#
# No Guarantees. Distribute Freely.
# Please send bug-fixes/patches/features to <r.hooft@euromail.com>
#
################################################################################
# import os,fnmatch,time
# import Tkinter,Pmw
# Pmw.setversion("0.8.5")

##def _errorpop(master,text):
##    d=Pmw.MessageDialog(master,
##                        title="Error",
##                        message_text=text,
##                        buttons=("OK",))
##    d.component('message').pack(ipadx=15,ipady=15)
##    d.activate()
##    d.destroy()

class PmwFileDialog(Pmw.Dialog):
    """File Dialog using Pmw"""

    def __init__(self, parent=None, **kw):
        # Define the megawidget options.
        optiondefs = (
            ('filter', '*', self.newfilter),
            ('directory', os.getcwd(), self.newdir),
            ('filename', '', self.newfilename),
            ('historylen', 10, None),
            ('command', None, None),
            ('info', None, None),
        )
        self.defineoptions(kw, optiondefs)
        # Initialise base class (after defining options).
        Pmw.Dialog.__init__(self, parent)

        self.withdraw()

        # Create the components.
        interior = self.interior()

        if self['info'] is not None:
            rowoffset = 1
            dn = self.infotxt()
            dn.grid(row=0, column=0, columnspan=2, padx=3, pady=3)
        else:
            rowoffset = 0

        dn = self.mkdn()
        dn.grid(row=0 + rowoffset, column=0, columnspan=2, padx=3, pady=3)
        del dn

        # Create the directory list component.
        dnb = self.mkdnb()
        dnb.grid(row=1 + rowoffset, column=0, sticky='news', padx=3, pady=3)
        del dnb

        # Create the filename list component.
        fnb = self.mkfnb()
        fnb.grid(row=1 + rowoffset, column=1, sticky='news', padx=3, pady=3)
        del fnb

        # Create the filter entry
        ft = self.mkft()
        ft.grid(row=2 + rowoffset, column=0, columnspan=2, padx=3, pady=3)
        del ft

        # Create the filename entry
        fn = self.mkfn()
        fn.grid(row=3 + rowoffset, column=0, columnspan=2, padx=3, pady=3)
        fn.bind('<Return>', self.okbutton)
        del fn

        # Buttonbox already exists
        bb = self.component('buttonbox')
        bb.add('OK', command=self.okbutton)
        bb.add('Cancel', command=self.cancelbutton)
        del bb

        Pmw.alignlabels([self.component('filename'),
                         self.component('filter'),
                         self.component('dirname')])

    def infotxt(self):
        """ Make information block component at the top """
        return self.createcomponent(
            'infobox',
            (), None,
            tkinter.Label, (self.interior(),),
            width=51,
            relief='groove',
            foreground='darkblue',
            justify='left',
            text=self['info']
        )

    def mkdn(self):
        """Make directory name component"""
        return self.createcomponent(
            'dirname',
            (), None,
            Pmw.ComboBox, (self.interior(),),
            entryfield_value=self['directory'],
            entryfield_entry_width=40,
            entryfield_validate=self.dirvalidate,
            selectioncommand=self.setdir,
            labelpos='w',
            label_text='Directory:')

    def mkdnb(self):
        """Make directory name box"""
        return self.createcomponent(
            'dirnamebox',
            (), None,
            Pmw.ScrolledListBox, (self.interior(),),
            label_text='directories',
            labelpos='n',
            hscrollmode='dynamic',  # 'none' originally
            dblclickcommand=self.selectdir)

    def mkft(self):
        """Make filter"""
        return self.createcomponent(
            'filter',
            (), None,
            Pmw.ComboBox, (self.interior(),),
            entryfield_value=self['filter'],
            entryfield_entry_width=40,
            selectioncommand=self.setfilter,
            labelpos='w',
            label_text='Filter:')

    def mkfnb(self):
        """Make filename list box"""
        return self.createcomponent(
            'filenamebox',
            (), None,
            Pmw.ScrolledListBox, (self.interior(),),
            label_text='files',
            labelpos='n',
            hscrollmode='dynamic',  # 'none' originally. 'dynamic' is supposed to be the default
            selectioncommand=self.singleselectfile,
            dblclickcommand=self.selectfile)

    def mkfn(self):
        """Make file name entry"""
        return self.createcomponent(
            'filename',
            (), None,
            Pmw.ComboBox, (self.interior(),),
            entryfield_value=self['filename'],
            entryfield_entry_width=40,
            entryfield_validate=self.filevalidate,
            selectioncommand=self.setfilename,
            labelpos='w',
            label_text='Filename:')

    def dirvalidate(self, string):
        if os.path.isdir(string):
            return Pmw.OK
        else:
            return Pmw.PARTIAL

    def filevalidate(self, string):
        if string == '':
            return Pmw.PARTIAL
        elif os.path.isfile(string):
            return Pmw.OK
        elif os.path.exists(string):
            return Pmw.PARTIAL
        else:
            return Pmw.OK

    def okbutton(self):
        """OK action: user thinks he has input valid data and wants to
               proceed. This is also called by <Return> in the filename entry"""
        fn = self.component('filename').get()
        self.setfilename(fn)
        if self.validate(fn):
            self.canceled = 0
            self.deactivate()

    def cancelbutton(self):
        """Cancel the operation"""
        self.canceled = 1
        self.deactivate()

    def tidy(self, w, v):
        """Insert text v into the entry and at the top of the list of
               the combobox w, remove duplicates"""
        if not v:
            return
        entry = w.component('entry')
        entry.delete(0, 'end')
        entry.insert(0, v)
        list = w.component('scrolledlist')
        list.insert(0, v)
        index = 1
        while index < list.index('end'):
            k = list.get(index)
            if k == v or index > self['historylen']:
                list.delete(index)
            else:
                index = index + 1
        w.checkentry()

    def setfilename(self, value):
        if not value:
            return
        value = os.path.join(self['directory'], value)
        dir, fil = os.path.split(value)
        self.configure(directory=dir, filename=value)

        c = self['command']
        if isinstance(c, collections.Callable):

            c()

    def newfilename(self):
        """Make sure a newly set filename makes it into the combobox list"""
        self.tidy(self.component('filename'), self['filename'])

    def setfilter(self, value):
        self.configure(filter=value)

    def newfilter(self):
        """Make sure a newly set filter makes it into the combobox list"""
        self.tidy(self.component('filter'), self['filter'])
        self.fillit()

    def setdir(self, value):
        self.configure(directory=value)

    def newdir(self):
        """Make sure a newly set dirname makes it into the combobox list"""
        self.tidy(self.component('dirname'), self['directory'])
        self.fillit()

    def singleselectfile(self):
        """Single click in file listbox. Move file to "filename" combobox"""
        cs = self.component('filenamebox').curselection()
        if cs != ():
            value = self.component('filenamebox').get(cs)
            self.setfilename(value)

    def selectfile(self):
        """Take the selected file from the filename, normalize it, and OK"""
        self.singleselectfile()
        value = self.component('filename').get()
        self.setfilename(value)
        if value:
            self.okbutton()

    def selectdir(self):
        """Take selected directory from the dirnamebox into the dirname"""
        cs = self.component('dirnamebox').curselection()
        if cs != ():
            value = self.component('dirnamebox').get(cs)
            dir = self['directory']
            if not dir:
                dir = os.getcwd()
            if value:
                if value == '..':
                    dir = os.path.split(dir)[0]
                else:
                    dir = os.path.join(dir, value)
            self.configure(directory=dir)
            self.fillit()

    def askfilename(self, directory=None, filter=None):
        """The actual client function. Activates the dialog, and
           returns only after a valid filename has been entered
               (return value is that filename) or when canceled (return
               value is None)"""
        if directory != None:
            self.configure(directory=directory)
        if filter != None:
            self.configure(filter=filter)
        self.fillit()
        self.canceled = 1  # Needed for when user kills dialog window
        self.activate()
        if self.canceled:
            return None
        else:
            return self.component('filename').get()

    lastdir = ""
    lastfilter = None
    lasttime = 0

    def fillit(self):
        """Get the directory list and show it in the two listboxes"""
        # Do not run unnecesarily
        if self.lastdir == self['directory'] and self.lastfilter == self['filter'] and self.lasttime > \
                os.stat(self.lastdir)[8]:
            return
        self.lastdir = self['directory']
        self.lastfilter = self['filter']
        self.lasttime = time.time()
        dir = self['directory']
        if not dir:
            dir = os.getcwd()
        dirs = ['..']
        files = []
        try:
            fl = os.listdir(dir)
            fl.sort()
        except os.error as arg:
            if arg[0] in (2, 20):
                return
            raise
        for f in fl:
            if os.path.isdir(os.path.join(dir, f)):
                dirs.append(f)
            else:
                filter = self['filter']
                if not filter:
                    filter = '*'
                if fnmatch.fnmatch(f, filter):
                    files.append(f)
        self.component('filenamebox').setlist(files)
        self.component('dirnamebox').setlist(dirs)

    def validate(self, filename):
        """Validation function. Should return 1 if the filename is valid,
               0 if invalid. May pop up dialogs to tell user why. Especially
               suited to subclasses: i.e. only return 1 if the file does/doesn't
               exist"""
        return 1


##class PmwExistingFileDialog(PmwFileDialog):
##    def filevalidate(self,string):
##        if os.path.isfile(string):
##            return Pmw.OK
##        else:
##            return Pmw.PARTIAL

##    def validate(self,filename):
##        if os.path.isfile(filename):
##            return 1
##        elif os.path.exists(filename):
##            _errorpop(self.interior(),"This is not a plain file")
##            return 0
##        else:
##            _errorpop(self.interior(),"Please select an existing file")
##            return 0

##################### EA Evolutionary Action of a substitution/mutation ###########################

# Class representing a list of EA ranks per substitution "R36A   22.66"
class EARanks:

    def __init__(self):
        self.ranksloaded = False

    # This method gets called inside mapRanks, after PyMOL structure has been checked and determined 08/25/14
    def readRanks(self, rankspath):
        """Read residue rankings from an EA .predictions file,
        Substitution on the first column (R36A) and EA coverage on the second column."""
        self.resnums = []
        self.ranks = []
        self.coverages = []
        self.ranksloaded = False
        self.rankspath = rankspath
        self.resnum_mutpred = {}  # Key: the residue number; value: dictionary of mutations and predicted EA scores

        if True:  # rankspath[-7:]=='.simple':
            FILE = open(rankspath, 'r')
            types = []  # .simple file has no list of amino acid types
            try:
                for line in FILE:
                    if line.find('#') == 0:  # comment
                        continue
                    cols = line.split()
                    if len(cols) < 2:
                        continue
                    ara = cols[0].strip()  # "R36A"
                    resnum = ara[1:-1]  # "36"
                    # aresnum=ara[0:-1] #"R36"
                    atype = ara[0].upper()
                    muta = ara[-1]
                    rho = cols[1].strip()
                    try:
                        self.resnum_mutpred[resnum][muta] = float(rho)
                    except KeyError:
                        self.resnum_mutpred[resnum] = {muta: float(rho)}
                        self.resnums.append(resnum)
                        types.append(atype)
            finally:
                FILE.close()

        if len(self.resnums) > 0:
            self.sequence = "".join(types)
            self.ranks = [0] * len(self.resnums)
            self.coverages = [0] * len(self.resnums)
            atypesmax = []
            atypesmin = []
            # Find the maximum and minimum EA score for each residue number
            for r in self.resnums:
                maxscore = -1
                minscore = 1000
                atypemax = 'X'
                atypemin = 'X'
                mutpred = self.resnum_mutpred[r]
                for a, s in list(mutpred.items()):
                    if s > maxscore:
                        maxscore = s
                        atypemax = a
                    if s < minscore:
                        minscore = s
                        atypemin = a
                if maxscore > -1:
                    mutpred['MAX'] = maxscore
                    atypesmax.append(atypemax)
                else:
                    print("Warning in finding EA maxscore for residue %s!" % r)
                if minscore < 1000:
                    mutpred['MIN'] = minscore
                    atypesmin.append(atypemin)
                else:
                    print("Warning in finding EA minscore for residue %s!" % r)
            # self.computeCoverage()
            # self.ranksloaded=True
            self.atypesmax = "".join(atypesmax)
            self.atypesmin = "".join(atypesmin)
            print('***** AMINO ACID SEQUENCE *****')
            print('ORIGINAL               :', self.sequence)
            print('')
            print('MAXIMUM ACTION MUTATION:', self.atypesmax)
            print('')
            print('MINIMUM ACTION MUTATION:', self.atypesmin)
            print('')

    # Panos EA scores are already in a form of a coverage: 0 - neutral, 100 - deleterious/has effect on phenotype
    def assignRanks(self, option):
        numres = self.getSize()
        if option == 0:
            for i in range(numres):  # EA scores of alanine mutations e.g. R36A
                try:  # TODO What to do if the native type is already Alanine?
                    self.ranks[i] = self.resnum_mutpred[self.resnums[i]]['A']
                except KeyError:
                    self.ranks[i] = 1.0
                    print('WARNING: Did not find an Alanine substitution in your input Actions file')
                self.coverages[i] = 100.0 - self.ranks[i]
            # self.computeCoverage()
            self.ranksloaded = True
        elif option == 1:
            for i in range(numres):  # Maximum EA scores for each residue
                self.ranks[i] = self.resnum_mutpred[self.resnums[i]]['MAX']
                self.coverages[i] = 100.0 - self.ranks[i]
            # self.computeCoverage()
            self.ranksloaded = True
        elif option == 2:
            for i in range(numres):  # Minimum EA scores for each residue
                self.ranks[i] = self.resnum_mutpred[self.resnums[i]]['MIN']
                self.coverages[i] = 100.0 - self.ranks[i]
            # self.computeCoverage()
            self.ranksloaded = True

    def computeCoverage(self):
        numres = self.getSize()
        for i in range(numres):
            self.coverages[i] = 0
            for j in range(numres):
                if self.ranks[i] <= self.ranks[
                    j]:  # TODO Make sure the inequality is correct for the sense of coverage used
                    self.coverages[i] += 1
            self.coverages[i] = self.coverages[i] * 100.0 / numres

    def getSize(self):
        return len(self.resnums)

    def ranksLoaded(self):
        return self.ranksloaded

    def getResnumCoverageRankTuple(self, i):
        return self.resnums[i], self.coverages[i], self.ranks[i]

    def getResnum(self, i):
        return self.resnums[i]

    def alignToAnotherSequence(self, restypesstring, resnums):
        """align the sequence in the actions or ranks file to an
        external sequence e.g. the sequence in a PyMOL structure.
        Associate the action or rank with the aligned residue in the external sequence """
        from Bio import pairwise2
        from Bio.pairwise2 import format_alignment
        from Bio.SubsMat import MatrixInfo as matlist
        matrix = matlist.blosum62
        gap_open = -10  # TODO
        gap_extend = -0.5  # TODO
        # for a in pairwise2.align.globaldx(self.sequence,restypesstring,matrix):
        #    print(format_alignment(*a))
        # topalign=pairwise2.align.globaldx(self.sequence,restypesstring,matrix)[0]
        topalign = pairwise2.align.globalds(self.sequence, restypesstring, matrix, gap_open, gap_extend)[0]
        print(
            "The pairwise alignment with matrix=blosum62 and gap_open=%lf and gap_extend=%lf:" % (gap_open, gap_extend))
        print((format_alignment(*topalign)))
        a1 = topalign[0]
        a2 = topalign[1]
        gap = '-'
        shiftedresnums1 = [None] * len(a1)
        i = 0
        for idx, a in enumerate(a1):
            if a != gap:
                shiftedresnums1[idx] = self.resnums[i]
                i += 1
        shiftedresnums2 = [None] * len(a2)
        i = 0
        for idx, a in enumerate(a2):
            if a != gap:
                shiftedresnums2[idx] = resnums[i]
                i += 1
        self._resnums = []
        self._resnum_mutpred = {}
        self._resnum1_resnum2 = {}  # Mapping of residue numbers from the first sequence to the second (e.g. structure)
        for idx, a in enumerate(a1):
            print(shiftedresnums1[idx], a, shiftedresnums2[idx], a2[idx])
            if shiftedresnums1[idx] is not None and shiftedresnums2[idx] is not None:
                self._resnums.append(shiftedresnums2[idx])
                self._resnum_mutpred[shiftedresnums2[idx]] = self.resnum_mutpred[shiftedresnums1[idx]]
                self._resnum1_resnum2[shiftedresnums1[idx]] = shiftedresnums2[idx]
        self._coverages = [0] * len(self._resnums)
        self._ranks = [0] * len(self._resnums)
        # print self._resnum_mutpred

        # TEST TODO check zscores
        # Replacing the original residue numbers
        self.resnums = self._resnums
        self.resnum_mutpred = self._resnum_mutpred
        self.coverages = self._coverages
        self.ranks = self._ranks

    def mapResnum1toResnum2(self, resnum):
        return self._resnum1_resnum2[resnum]


class EAControlGroup:
    # Reuse some functions useful for calculating clustering z-score
    # (Assignments made outside of a method such as __init__  make 'self' the first parameter)
    calcDist = _et_calcDist
    computeAdjacency = _et_computeAdjacency
    calcZScore = _et_calcZScore
    getPyMOLstructuresequence = _getPyMOLstructuresequence

    def __init__(self,
                 page,
                 groupname='Action',
                 defaultstructurename='',
                 defaultrankspath='.pred',
                 defaultslidercoverage=30,
                 defaultetoption=3,
                 defaultqualitymeasure=0,
                 defaultchain='',
                 defaultactionoption=0,
                 etcolor='red',
                 noetcolor='green',
                 et_colorramp=None,
                 et_colorPrism=None,
                 et_messagebox=None):
        self.etcolor = etcolor
        self.noetcolor = noetcolor

        self.et_colorramp = et_colorramp
        self.et_colorPrism = et_colorPrism
        self.et_messagebox = et_messagebox

        # group = Pmw.Group(page,tag_text=groupname)
        # Changing to scrollable frame as a response to Bioinformatics reviewer 08/24/10
        group = Pmw.ScrolledFrame(page,
                                  labelpos='nw',
                                  label_text=groupname)
        self.groupname = groupname
        self.group = group
        # self.groupscrolled=group
        group.pack(fill='both', expand=1, padx=10, pady=5)
        self.balloon = Pmw.Balloon(group.interior())

        self.structureframe = tkinter.Frame(group.interior())
        # Field for entering name of structure or model
        self.structure = Pmw.EntryField(self.structureframe,
                                        labelpos='w',
                                        label_text='structure to use: ',
                                        value=defaultstructurename,
                                        )

        self.chain = Pmw.EntryField(self.structureframe,
                                    labelpos='w',
                                    label_text='chain: ',
                                    value=defaultchain,
                                    )
        self.structure.grid(column=0, row=0)
        self.chain.grid(column=1, row=0)

        def quickFileValidation(s):
            if s == '':
                return Pmw.PARTIAL
            elif os.path.isfile(s):
                return Pmw.OK
            elif os.path.exists(s):
                return Pmw.PARTIAL
            else:
                return Pmw.PARTIAL

        self.checksequence_var = IntVar()
        self.checksequence_var.set(1)
        self.checksequence_checkbutton = Checkbutton(group.interior(),
                                                     text="check sequence",
                                                     variable=self.checksequence_var)

        self.checkalignsequences_var = IntVar()
        self.checkalignsequences_var.set(1)
        self.checkalignsequences_checkbutton = Checkbutton(group.interior(),
                                                           text="align input sequence to the structure",
                                                           variable=self.checkalignsequences_var)

        self.selectalign = Pmw.EntryField(group.interior(),
                                          labelpos='w',
                                          label_text='Select residues: '
                                          # value=defaultchain,
                                          )

        # Button for assigning ranks to structure
        self.map_buttonbox = Pmw.ButtonBox(group.interior(), padx=0)
        self.map_buttonbox.add('Map Action to structure', command=self.mapRanks)

        # Field for entering ranks file
        self.ranksfile = Pmw.EntryField(group.interior(),
                                        labelpos='w',
                                        label_pyclass=FileDialogButtonClassFactory.get(self.setRanksFileLocation,
                                                                                       '*.pred'),
                                        validate={'validator': quickFileValidation, },
                                        value=defaultrankspath,
                                        # entry_width=50, #This value is set for the sole purpose of making the width of the plugin dialog box large enough
                                        label_text='EA predictions file path:')

        # Tkinter Slider Tkinterfor changing ET residue coverage.
        # TODO: Use a Pmw implementation?
        self.slider = Scale(group.interior(),
                            # from_=self.min, to=self.max,
                            from_=0, to=100,
                            showvalue=1,
                            length=200,
                            orient=HORIZONTAL, resolution=0.01,
                            tickinterval=0,
                            # repeatinterval=500, #Dunno what repeatinterval is for...
                            repeatdelay=500
                            )
        self.slider.set(defaultslidercoverage)
        # self.slider.grid()
        self.slider.bind("<ButtonRelease-1>", self.slider_button_release)
        # self.slider.bind("<Button-1>", self.mousebutton_click)
        self.slider.bind("<Button-2>", self.mousebutton_click)
        # For Windows
        self.slider.bind("<MouseWheel>", self.mousewheel_roll)
        # For Linux (for Mac?)
        self.slider.bind("<Button-4>", self.mousewheel_roll)
        self.slider.bind("<Button-5>", self.mousewheel_roll)
        # self.slider.bind("<B1-Motion>", self.slider_button_release)
        ##                              Triggered when the mouse enters a widget
        ##<Any-Enter>
        ##                              Triggered when a left click is done in a widget
        ##<Button-1>, <1>
        ##                              Triggered when the mouse is dragged over
        ##<B1-Motion>
        ##                              the widget with the left mouse button being
        ##                              held down.
        ##                              Triggered when a widget is double-clicked
        ##<Double-Button-1>
        ##                              with the left mouse button
        ##                              Triggered when the key producing the letter
        ##<Key-A>, <KeyPress-A>, <A>, A
        ##                              A (caps) is pressed.

        # Field for displaying ET coverage
        self.frame1 = tkinter.Frame(group.interior())
        self.percentcoverage = Pmw.EntryField(self.frame1,  # group.interior(),
                                              labelpos='w',
                                              label_text='Percent coverage: ',
                                              value='',
                                              )
        self.rankvalue = Pmw.EntryField(self.frame1,  # group.interior(),
                                        labelpos='w',
                                        label_text='Action: ',
                                        value='',
                                        )
        self.percentcoverage.grid(column=0, row=0)
        self.rankvalue.grid(column=1, row=0)

        self.frame2 = tkinter.Frame(group.interior())
        self.et_options_tuple = ('Colors only(' + self.etcolor + ')',
                                 'as Spheres',
                                 'as Spheres (C-alpha only)',
                                 'Red_to_Green',
                                 'as Sticks',
                                 'Map -1 to +1 to blue-white-red',
                                 'Greyscale',
                                 'Prismatic (Gobstopper)')
        self.show_et_options = Pmw.OptionMenu(self.frame2,  # group.interior(),
                                              labelpos='w',
                                              label_text='Show EA residues',
                                              items=self.et_options_tuple,
                                              initialitem=self.et_options_tuple[defaultetoption],
                                              command=self.slider_button_release
                                              )
        ##        self.show_et_var=IntVar()
        ##        self.show_et_var.set(0)
        ##        self.show_et_checkbutton = Checkbutton(group.interior(),
        ##                                               text = "Show EA residues (as spheres)",
        ##                                               variable = self.show_et_var)

        self.select_et_var = IntVar()
        self.select_et_var.set(0)
        self.select_et_checkbutton = Checkbutton(self.frame2,  # group.interior(),
                                                 text="Select EA residues",
                                                 variable=self.select_et_var)

        self.show_et_options.grid(column=0, row=0)
        self.select_et_checkbutton.grid(column=1, row=0)

        # self.ETcolor_buttonbox = Pmw.ButtonBox(group.interior(), padx=0) #06/10/11 Experimenting with tkColorChooser
        # self.ETcolor_buttonbox.add('Choose ET color',command=self.chooseETcolor)

        self.qualitymeasure_options_tuple = ('3D nobias',
                                             '3D bias (j-i)')
        self.show_qualitymeasure_options = Pmw.OptionMenu(group.interior(),
                                                          labelpos='w',
                                                          label_text='quality measure: ',
                                                          items=self.qualitymeasure_options_tuple,
                                                          initialitem=self.qualitymeasure_options_tuple[
                                                              defaultqualitymeasure],
                                                          )

        self.zscore_buttonbox = Pmw.ButtonBox(group.interior(), padx=0)
        self.zscore_buttonbox.add('Compute z-scores', command=self.showZScores)
        # self.zscore_buttonbox.add('Average neighbor ranks',command=self.averageNeighborRanks)
        self.zscore_et = Pmw.EntryField(group.interior(),
                                        labelpos='w',
                                        label_text='z-score EA residues: ',
                                        value='',
                                        )
        self.zscore_notet = Pmw.EntryField(group.interior(),
                                           labelpos='w',
                                           label_text='z-score non-EA residues: ',
                                           value='',
                                           )

        self.action_options_tuple = ('Alanine substitution',
                                     'Maximum impact',
                                     'Minimum impact')
        self.show_action_options = Pmw.OptionMenu(group.interior(),
                                                  labelpos='w',
                                                  label_text='Select Evolutionary Action: ',
                                                  items=self.action_options_tuple,
                                                  initialitem=self.action_options_tuple[defaultactionoption],
                                                  command=self.reassignEAranks
                                                  )

        for entry in (self.structureframe,
                      self.ranksfile,
                      self.checksequence_checkbutton,
                      self.checkalignsequences_checkbutton,
                      self.selectalign,
                      self.map_buttonbox,
                      self.slider,
                      self.frame1,
                      self.frame2,
                      self.show_action_options,
                      self.zscore_buttonbox,
                      self.show_qualitymeasure_options,
                      self.zscore_et,
                      self.zscore_notet):
            entry.pack(fill='x', padx=4, pady=1)  # vertical

        # Tool tips (help) for user-interface components and boxes
        self.balloon.bind(self.structure, "Enter a name from the list of objects in the PyMOL Viewer")
        self.balloon.bind(self.ranksfile, "Click to open a box for navigating and choosing a file")
        self.balloon.bind(self.checksequence_checkbutton,
                          "Check this box to verify if the sequence in the structure and EA predictions file match after mapping Action to structure")
        self.balloon.bind(self.checkalignsequences_checkbutton,
                          "Check this box to align the amino acid sequence in your actions file with the sequence of the PyMOL structure. Requires Biopython installation.")
        self.balloon.bind(self.selectalign,
                          "Select the residues in the structure that align with these residue numbers (use a comma to separate e.g. 7,23,25)")
        self.balloon.bind(self.map_buttonbox, "Click to map Action data onto the structure.")
        self.balloon.bind(self.slider, "Drag slider to vary EA residue selection and coverage of the structure")
        self.balloon.bind(self.percentcoverage, "Maximum percent-rank (or percentile-rank) in the EA residue selection")
        self.balloon.bind(self.rankvalue, "Minimum raw Action score in the EA residue selection")
        self.balloon.bind(self.show_et_options,
                          "Select display style to distinguish EA residue selection from the rest of the structure")
        self.balloon.bind(self.select_et_checkbutton, "Check box to create a PyMOL selection of the EA residues")
        self.balloon.bind(self.zscore_buttonbox, "Compute the clustering z-score of the EA residue selection")
        self.balloon.bind(self.show_qualitymeasure_options, "Select from several measures of clustering quality")
        self.balloon.bind(self.show_action_options,
                          "Select the substitution to use in assigning an Evolutionary Action score to a residue")

        self.ranksObj = EARanks()
        self.ranksStructureOK = False
        self.adjacencyOK = False

    def setRanksFileLocation(self, value):
        self.ranksfile.setvalue(value)

    def mapRanks(self):
        structurename = self.structure.getvalue().strip()
        supper = structurename.upper()
        for n in cmd.get_names('all'):
            if supper == n.upper():
                break
        else:
            print('structure name must be in PyMOL viewer list:')
            print(cmd.get_names('all'))
            return
        chainindicator = self.chain.getvalue().strip()
        self.ranksStructureOK = False
        self.adjacencyOK = False
        # TODO Is this redundant? See cmd.get_names above
        if len(structurename) < 1:
            print('Provide structure name!')
            return
        try:
            model = cmd.get_model(structurename)
        except Exception:
            print("Structure \'%s\' does not exist!" % structurename)
            return

        try:
            rankspath = self.ranksfile.getvalue().strip()
            if len(rankspath) < 1:
                print('Provide path to predictions file!')
                return

            self.ranksObj.readRanks(rankspath)

            if self.checkalignsequences_var.get() == 1:
                (pymolrestypes, pymolresnums) = self.getPyMOLstructuresequence(model, structurename, chainindicator)
                print("Aligning the sequence in %s with the sequence in the PyMOL structure %s %s" % (
                rankspath, structurename, chainindicator))
                # print pymolrestypes, 'n', pymolresnums
                self.ranksObj.alignToAnotherSequence("".join(pymolrestypes), pymolresnums)
                selectresstring = self.selectalign.getvalue().strip()
                if len(selectresstring) > 0:
                    print("Your list of residues aligned with these residues in the structure:")
                    selectreslist1 = selectresstring.split(',')
                    selectreslist2 = []
                    for resnum in selectreslist1:
                        try:
                            resnum2 = self.ranksObj.mapResnum1toResnum2(resnum.strip())
                            selectreslist2.append(resnum2)
                            print("%5s -> %5s" % (resnum.strip(), resnum2))
                        except KeyError:
                            print("%5s -> %5s" % (resnum.strip(), '-'))
                    if len(selectreslist2) > 0:
                        print("The residue numbers ", selectreslist2,
                              " were selected on the structure, based on your input selection of ", selectreslist1)
                        if len(chainindicator) > 0:
                            cmd.select('selalign_%s%s_PyETV' % (structurename, chainindicator),
                                       '%s and chain %s and resi %s' % (
                                       structurename, chainindicator, "+".join(selectreslist2)))
                        else:
                            cmd.select('selalign_%s%s_PyETV' % (structurename),
                                       '%s and resi %s' % (structurename, "+".join(selectreslist2)))

            if self.show_action_options.getvalue() == self.action_options_tuple[0]:
                self.ranksObj.assignRanks(0)
            elif self.show_action_options.getvalue() == self.action_options_tuple[1]:
                self.ranksObj.assignRanks(1)
            elif self.show_action_options.getvalue() == self.action_options_tuple[2]:
                self.ranksObj.assignRanks(2)
            if self.ranksObj.ranksLoaded():
                print("Predictions file \'%s\' loaded" % rankspath)
            else:
                print("Error loading predictions file \'%s\'!" % rankspath)
                return

            if self.checksequence_var.get() == 1:
                self.checkSequence(model, structurename, chainindicator, rankspath)
        except Exception as e:
            print("Exception encountered in EA mapRanks", e)

        self.structurename = structurename
        self.chainindicator = chainindicator
        self.ranksStructureOK = True

        self.slider_button_release(None)

    # This check uses the residue numbers, not the amino acid types
    def checkSequence(self, model, structurename, chainindicator, rankspath):
        three2one = {
            "ALA": 'A',
            "ARG": 'R',
            "ASN": 'N',
            "ASP": 'D',
            "CYS": 'C',
            "GLN": 'Q',
            "GLU": 'E',
            "GLY": 'G',
            "HIS": 'H',
            "ILE": 'I',
            "LEU": 'L',
            "LYS": 'K',
            "MET": 'M',
            "PHE": 'F',
            "PRO": 'P',
            "SER": 'S',
            "THR": 'T',
            "TRP": 'W',
            "TYR": 'Y',
            "VAL": 'V',
            "A": "A",
            "G": "G",
            "T": "T",
            "U": "U",
            "C": "C", }

        print('Comparing residue numbers of structure \'%s\' and ranks \'%s\'...' % (structurename + chainindicator,
                                                                                     rankspath))
        # Check that the residue numbers in the model
        # and in the ranks file are identical
        resnumDict = {}
        if len(chainindicator) > 0:
            # chainup=chainindicator.upper()
            # Case of chain indicator is relevant
            for i in range(len(model.atom)):
                atomobj = model.atom[i]
                if chainindicator == atomobj.chain:
                    try:
                        aa1 = three2one[atomobj.resn]  # Consider only residues with standard amino acids
                        resnumDict[atomobj.resi] = 1
                    except KeyError:
                        pass
        else:
            for i in range(len(model.atom)):  # TODO check for standard amino acids like the one right above 08/25/14
                resnumDict[model.atom[i].resi] = 1

        print('Sequence lengths:')
        print('structure: ', len(resnumDict))
        print('ranks: ', self.ranksObj.getSize())
        if len(resnumDict) < 1:
            print('Structure \'%s\' has no residues!' % (structurename + chainindicator))
        if len(resnumDict) != self.ranksObj.getSize():
            print("Sequence lengths of \'%s\' and \'%s\' are not the same!" % (structurename + chainindicator,
                                                                               rankspath))
        missing = 0
        for i in range(self.ranksObj.getSize()):
            try:
                r = resnumDict[self.ranksObj.getResnum(i)]
            except KeyError:
                missing += 1
        if missing > 0:
            print("%d residue numbers in \'%s\' missing in \'%s\'!" % (
                missing, rankspath, structurename + chainindicator))

        print('...Done.')

    def mousewheel_roll(self, event):
        slideval = self.slider.get()
        # See http://www.daniweb.com/code/snippet217059.html
        if event.num == 4 or event.delta == -120:
            self.slider.set(slideval + 1)
            self.slider_button_release(None)
        if event.num == 5 or event.delta == 120:
            self.slider.set(slideval - 1)
            self.slider_button_release(None)
        # print event.num, event.delta, slideval

    def mousebutton_click(self, event):
        # slideval=self.slider.get()
        if event.num == 1 or event.num == 2:
            self.slider_button_release(None)
        # print event.num, event.delta, slideval

    def reassignEAranks(self, event):
        if self.ranksStructureOK:
            if self.show_action_options.getvalue() == self.action_options_tuple[0]:
                self.ranksObj.assignRanks(0)
            elif self.show_action_options.getvalue() == self.action_options_tuple[1]:
                self.ranksObj.assignRanks(1)
            elif self.show_action_options.getvalue() == self.action_options_tuple[2]:
                self.ranksObj.assignRanks(2)
            self.slider_button_release(event)

    def slider_button_release(self, event):
        if self.ranksStructureOK:
            self.et_messagebox.show()
            maxcov = 0
            maxrank = 0
            slideval = self.slider.get()
            # The following does not work. The colors _et_prism_colorXX are defined only in PyMOL,
            # not Pmw/Tkinter
            # self.slider.configure(background=('_et_prism_color%d' % (slideval-0.000001)))
            if len(self.chainindicator) > 0:
                structurename = '%s and chain %s' % (self.structurename, self.chainindicator)
            else:
                structurename = self.structurename

            if self.show_et_options.getvalue() == self.et_options_tuple[2]:
                cmd.hide('spheres', structurename)  # Need to hide spheres in case non-CA atoms are displayed
                for i in range(self.ranksObj.getSize()):
                    (resnum, cov, rank) = self.ranksObj.getResnumCoverageRankTuple(i)
                    if slideval >= cov:
                        if cov > maxcov:
                            maxcov = cov
                            maxrank = rank
                        cmd.color(self.etcolor, '%s and resi %s' % (structurename, resnum))
                        cmd.show('spheres', '%s and resi %s and name CA' % (structurename, resnum))
                    else:
                        # TODO: Color only the spheres?
                        cmd.color(self.noetcolor, '%s and resi %s' % (structurename, resnum))
                        cmd.hide('spheres', '%s and resi %s' % (structurename, resnum))
            # if self.show_et_var.get()==1:
            elif self.show_et_options.getvalue() == self.et_options_tuple[1]:
                # Rong's suggestion
                # cmd.hide('spheres',structurename)
                # cmd.show('cartoon',structurename)
                # cmd.color(self.noetcolor,structurename)
                for i in range(self.ranksObj.getSize()):
                    (resnum, cov, rank) = self.ranksObj.getResnumCoverageRankTuple(i)
                    if slideval >= cov:
                        if cov > maxcov:
                            maxcov = cov
                            maxrank = rank
                        cmd.color(self.etcolor, '%s and resi %s' % (structurename, resnum))
                        cmd.show('spheres', '%s and resi %s' % (structurename, resnum))
                    else:
                        # TODO: Color only the spheres?
                        cmd.color(self.noetcolor, '%s and resi %s' % (structurename, resnum))
                        cmd.hide('spheres', '%s and resi %s' % (structurename, resnum))
            elif self.show_et_options.getvalue() == self.et_options_tuple[3]:  # Do red green
                for i in range(self.ranksObj.getSize()):
                    (resnum, cov, rank) = self.ranksObj.getResnumCoverageRankTuple(i)
                    if slideval >= cov:
                        if cov > maxcov:
                            maxcov = cov
                            maxrank = rank
                        cmd.color('_et_rg_color%d' % (int(cov - 0.000001)),
                                  '%s and resi %s' % (structurename, resnum))
                    else:
                        cmd.color('white', '%s and resi %s' % (structurename, resnum))
                if self.et_colorramp:
                    # print 'show red green ramp'
                    self.et_colorramp.show()
            elif self.show_et_options.getvalue() == self.et_options_tuple[4]:
                for i in range(self.ranksObj.getSize()):
                    (resnum, cov, rank) = self.ranksObj.getResnumCoverageRankTuple(i)
                    if slideval >= cov:
                        if cov > maxcov:
                            maxcov = cov
                            maxrank = rank
                        cmd.color(self.etcolor, '%s and resi %s' % (structurename, resnum))
                        cmd.show('sticks', '%s and resi %s' % (structurename, resnum))
                    else:
                        cmd.color(self.noetcolor, '%s and resi %s' % (structurename, resnum))
                        cmd.hide('sticks', '%s and resi %s' % (structurename, resnum))
            elif self.show_et_options.getvalue() == self.et_options_tuple[5]:
                # Special mapping for difference ET coverage differentials suggested by Angela
                for i in range(self.ranksObj.getSize()):
                    (resnum, cov, rank) = self.ranksObj.getResnumCoverageRankTuple(i)
                    if cov > maxcov:
                        maxcov = cov
                        maxrank = rank
                    if rank > 0.99999:
                        rank = 0.99999
                    if rank < -0.99999:
                        rank = -0.99999
                    if rank > 0.2:
                        cmd.color('_et_red_color%s' % int(10 * (rank - 0.2) / 0.8),
                                  '%s and resi %s' % (structurename, resnum))
                    elif rank < -0.2:
                        cmd.color('_et_blue_color%s' % int(10 * (-rank - 0.2) / 0.8),
                                  '%s and resi %s' % (structurename, resnum))
                    else:
                        cmd.color('white', '%s and resi %s' % (structurename, resnum))
            elif self.show_et_options.getvalue() == self.et_options_tuple[6]:  # Do Greyscale 09/02/14
                for i in range(self.ranksObj.getSize()):
                    (resnum, cov, rank) = self.ranksObj.getResnumCoverageRankTuple(i)
                    if slideval >= cov:
                        if cov > maxcov:
                            maxcov = cov
                            maxrank = rank
                        cmd.color('_et_greyscale_color%d' % (int(cov - 0.000001)),
                                  '%s and resi %s' % (structurename, resnum))
                    else:
                        cmd.color('orange', '%s and resi %s' % (structurename, resnum))
            elif self.show_et_options.getvalue() == self.et_options_tuple[7]:  # Do Prismatic
                for i in range(self.ranksObj.getSize()):
                    (resnum, cov, rank) = self.ranksObj.getResnumCoverageRankTuple(i)
                    if slideval >= cov:
                        if cov > maxcov:
                            maxcov = cov
                            maxrank = rank
                        cmd.color('_et_prism_color%d' % (int(cov - 0.000001)),
                                  '%s and resi %s' % (structurename, resnum))
                    else:
                        cmd.color('white', '%s and resi %s' % (structurename, resnum))
                if self.et_colorPrism:
                    # print 'show prismatic ramp'
                    self.et_colorPrism.show()
            else:
                # It turns out that calling cmd.color or cmd.select with a list of residues, e.g. 'resi 1+2+...', can crash pymol
                # if the list is too big (>300)
                for i in range(self.ranksObj.getSize()):
                    (resnum, cov, rank) = self.ranksObj.getResnumCoverageRankTuple(i)
                    if slideval >= cov:
                        if cov > maxcov:
                            maxcov = cov
                            maxrank = rank
                        cmd.color(self.etcolor, '%s and resi %s' % (structurename, resnum))
                    else:
                        cmd.color(self.noetcolor, '%s and resi %s' % (structurename, resnum))
                # See if this is faster
                # if len(etlist)>0:
                #    cmd.color(self.etcolor,'%s and resi %s' % (structurename,string.join(etlist,'+')))
                # if len(notetlist)>0:
                #    cmd.color(self.noetcolor,'%s and resi %s' % (structurename,string.join(noetlist,'+')))
            self.percentcoverage.setvalue(maxcov)
            self.rankvalue.setvalue(maxrank)
            # Make a selection of the ET residues
            if self.select_et_var.get() == 1:
                resnumlist = []
                for i in range(self.ranksObj.getSize()):
                    (resnum, cov, rank) = self.ranksObj.getResnumCoverageRankTuple(i)
                    if slideval >= cov:
                        resnumlist.append(resnum)
                    else:
                        # You might want to make a selection for non-ET residues also
                        pass
                if len(resnumlist) > 0:
                    cmd.select('et_%s_PyETV' % (self.structurename + self.chainindicator),
                               '%s and resi %s' % (structurename, "+".join(resnumlist)))
                else:
                    cmd.select('et_%s_PyETV' % (self.structurename + self.chainindicator), 'none')

            self.et_messagebox.withdraw()

    def showZScores(self):
        if self.ranksStructureOK:
            try:
                self.et_messagebox.show()
                slideval = self.slider.get()  # Get the slider value (ET threshold) early
                if self.show_qualitymeasure_options.getvalue() == self.qualitymeasure_options_tuple[0]:
                    bias = 0
                elif self.show_qualitymeasure_options.getvalue() == self.qualitymeasure_options_tuple[1]:
                    bias = 1
                if not self.adjacencyOK:
                    if len(self.chainindicator) > 0:
                        structurename = '%s and chain %s' % (self.structurename, self.chainindicator)
                    else:
                        structurename = self.structurename
                    print("Computing adjacency matrix A(i,j)...")
                    self._A, ResAtoms, self._Ar = self.computeAdjacency(structurename)
                    if self._A == None:
                        self.zscore_et.setvalue('')
                        self.zscore_notet.setvalue('')
                        self.et_messagebox.withdraw()
                        return
                    else:
                        self.adjacencyOK = True
                    print("... done")

                # Get lists of ET residues
                et1 = []
                notet = []
                # slideval=self.slider.get()
                for i in range(self.ranksObj.getSize()):
                    (resnum1, cov1, rank1) = self.ranksObj.getResnumCoverageRankTuple(i)
                    if slideval >= cov1:
                        et1.append(int(resnum1))
                    else:
                        notet.append(int(resnum1))

                print("ET residue selection lists:")
                print(et1)
                print("Computing z-scores...")
                if len(et1) > 0:
                    z1 = self.calcZScore(et1, self.ranksObj.getSize(), self._A, bias)
                else:
                    z1 = 'NA'
                if len(notet) > 0:
                    z0 = self.calcZScore(notet, self.ranksObj.getSize(), self._A, bias)
                else:
                    z0 = 'NA'

                print("...done")

                self.zscore_et.setvalue(z1)
                self.zscore_notet.setvalue(z0)

                # if len(notet)>0:
                #    print 'Non-ET residue z-score: ', self.calcZScore(notet,self.ranksObj.getSize(),self._A)
            except Exception as detail:
                print('An exception occurred in showZScores!')
                print(detail)
            self.et_messagebox.withdraw()


###########################################################################

################################# ET ######################################

# Class representing a list of ET ranks (rho), computed coverages,
# and residue numbers.
class ETRanks:

    def __init__(self):
        self.ranksloaded = False

    def readRanks(self, rankspath):
        """Read residue rankings from an ET .ranks file,
        or a .simple file with residue numbers in the
        first column and ranks on the second column."""
        self.resnums = []
        self.ranks = []
        self.coverages = []
        self.ranksloaded = False
        self.rankspath = rankspath

        if rankspath[-7:] == '.simple':
            FILE = open(rankspath, 'r')
            types = []  # .simple file has no list of amino acid types
            try:
                for line in FILE:
                    if line.find('#') == 0:  # comment
                        continue
                    cols = line.split()
                    if len(cols) < 2:
                        continue
                    resnum = cols[0].strip()
                    rho = cols[1].strip()
                    if resnum == '-':
                        pass  # gap
                    else:
                        self.resnums.append(resnum)
                        self.ranks.append(float(rho))
                        self.coverages.append(0)
            finally:
                FILE.close()
        else:
            FILE = open(rankspath, 'r')
            types = []
            split_tup = os.path.splitext(rankspath)
            if split_tup[-1].lower() == '.ranks':
                print("ranks file")
                splitRangeStart = 72
                splitRangeEnd = 82
            else:
                print("cov file")
                splitRangeStart = 82
                splitRangeEnd = 92
            try:
                for line in FILE:
                    if line.find('%') == 0:
                        continue
                    if len(line.rstrip()) > 81:
                        resnum = line[10:20].strip()
                        if resnum == '-':
                            pass  # gap
                        else:
                            rho = line[splitRangeStart:splitRangeEnd].strip()
                            self.resnums.append(resnum)
                            types.append(line[29])  # One-letter amino acid code
                            self.ranks.append(float(rho))
                            self.coverages.append(0)
                            # print resnum,rho
            finally:
                FILE.close()

        if len(self.resnums) > 0:
            self.sequence = "".join(types)
            self.computeCoverage()
            self.ranksloaded = True

    # PyMOL already has "spectrum" that can indicate b-factors
    def getBfactors(self, model):
        self.resnums = []
        self.ranks = []
        self.coverages = []
        self.ranksloaded = False
        self.rankspath = "B-factors"

        resnumBdict = {}
        for i in range(len(model.atom)):
            atomobj = model.atom[i]
            # if atomobj.hetatm==1:
            #    continue
            try:
                resnumBdict[atomobj.resi].append(atomobj.b)
            except KeyError:
                resnumBdict[atomobj.resi] = [atomobj.b]
                self.resnums.append(atomobj.resi)

        for i in range(len(self.resnums)):
            r = self.resnums[i]
            bsum = 0
            for b in resnumBdict[r]:
                bsum += b
            self.ranks.append(bsum * 1.0 / len(resnumBdict[r]))
            self.coverages.append(0)

        if len(self.resnums) > 0:
            # self.sequence=string.join(types,'')
            self.computeCoverage()
            self.ranksloaded = True

    def computeCoverage(self):
        numres = self.getSize()
        for i in range(numres):
            self.coverages[i] = 0  # 12/21/11
            for j in range(numres):
                if self.ranks[i] >= self.ranks[j]:
                    self.coverages[i] += 1
            self.coverages[i] = self.coverages[i] * 100.0 / numres

    def getSize(self):
        return len(self.resnums)

    def ranksLoaded(self):
        return self.ranksloaded

    def getResnumCoverageRankTuple(self, i):
        return self.resnums[i], self.coverages[i], self.ranks[i]

    def getResnum(self, i):
        return self.resnums[i]

    def alignToAnotherSequence(self, restypesstring, resnums):
        """align the sequence in the actions or ranks file to an
        external sequence e.g. the sequence in a PyMOL structure.
        Associate the action or rank with the aligned residue in the external sequence """
        from Bio import pairwise2
        from Bio.pairwise2 import format_alignment
        from Bio.SubsMat import MatrixInfo as matlist
        matrix = matlist.blosum62
        gap_open = -10  # TODO
        gap_extend = -0.5  # TODO
        # for a in pairwise2.align.globaldx(self.sequence,restypesstring,matrix):
        #    print(format_alignment(*a))
        topalign = pairwise2.align.globalds(self.sequence, restypesstring, matrix, gap_open, gap_extend)[0]
        print(
            "The pairwise alignment with matrix=blosum62 and gap_open=%lf and gap_extend=%lf:" % (gap_open, gap_extend))
        print((format_alignment(*topalign)))
        a1 = topalign[0]
        a2 = topalign[1]
        gap = '-'
        shiftedresnums1 = [None] * len(a1)
        shiftedcoverages1 = [None] * len(a1)
        shiftedranks1 = [None] * len(a1)
        i = 0
        for idx, a in enumerate(a1):
            if a != gap:
                shiftedresnums1[idx] = self.resnums[i]
                shiftedcoverages1[idx] = self.coverages[i]
                shiftedranks1[idx] = self.ranks[i]
                i += 1
        shiftedresnums2 = [None] * len(a2)
        i = 0
        for idx, a in enumerate(a2):
            if a != gap:
                shiftedresnums2[idx] = resnums[i]
                i += 1
        self._resnums = []
        self._coverages = []
        self._ranks = []
        for idx, a in enumerate(a1):
            print(shiftedresnums1[idx], a, shiftedresnums2[idx], a2[idx])
            if shiftedresnums1[idx] is not None and shiftedresnums2[idx] is not None:
                self._resnums.append(shiftedresnums2[idx])
                self._coverages.append(shiftedcoverages1[idx])
                self._ranks.append(shiftedranks1[idx])

        # TEST TODO check zscores
        # Replacing the original residue numbers
        self.resnums = self._resnums
        self.coverages = self._coverages
        self.ranks = self._ranks


class ETControlGroup:
    # Reuse some functions useful for calculating clustering z-score
    # (Assignments made outside of a method such as __init__  make 'self' the first parameter)
    calcDist = _et_calcDist
    computeAdjacency = _et_computeAdjacency
    calcZScore = _et_calcZScore
    computeInterface = _et_computeInterface
    getPyMOLstructuresequence = _getPyMOLstructuresequence

    def __init__(self,
                 page,
                 groupname='Trace',
                 defaultstructurename='(pdb)',
                 defaultrankspath='.ranks',
                 defaultslidercoverage=10,
                 defaultetoption=3,
                 defaultqualitymeasure=0,
                 defaultchain='',
                 defaultpartnerstructurename='',
                 etcolor='red',
                 noetcolor='salmon',
                 et_loader_on=True,
                 interface_on=True,
                 et_colorramp=None,
                 et_colorPrism=None,
                 et_messagebox=None):
        self.etcolor = etcolor
        self.noetcolor = noetcolor

        self.et_colorramp = et_colorramp
        self.et_colorPrism = et_colorPrism
        self.et_messagebox = et_messagebox

        group = Pmw.Group(page,tag_text=groupname)
        # Changing to scrollable frame as a response to Bioinformatics reviewer 08/24/10
        '''group = Pmw.ScrolledFrame(page,
                                  labelpos='nw',
                                  label_text=groupname)'''
        self.groupname = groupname
        self.group = group
        # self.groupscrolled=group
        group.pack(fill='both', expand=1, padx=10, pady=5)
        self.balloon = Pmw.Balloon(group.interior())

        self.structureframe = tkinter.Frame(group.interior())
        # Field for entering name of structure or model
        self.structure = Pmw.EntryField(self.structureframe,
                                        labelpos='w',
                                        label_text='structure to use: ',
                                        value=defaultstructurename,
                                        )

        self.chain = Pmw.EntryField(self.structureframe,
                                    labelpos='w',
                                    label_text='chain: ',
                                    value=defaultchain,
                                    )
        self.structure.grid(column=0, row=0)
        self.chain.grid(column=1, row=0)

        def quickFileValidation(s):
            if s == '':
                return Pmw.PARTIAL
            elif os.path.isfile(s):
                return Pmw.OK
            elif os.path.exists(s):
                return Pmw.PARTIAL
            else:
                return Pmw.PARTIAL

        if et_loader_on:
            # Put ET Loader capability directly into ETControlGroup (03/08/10)
            self.loadergroup = Pmw.Group(group.interior(), tag_text='(optional) Load structure and precomputed trace')
            self.pdbnamebox = Pmw.EntryField(self.loadergroup.interior(),
                                             labelpos='w',
                                             label_text='Enter a 4-digit pdb code + chain id (e.g. 2phyA): ',
                                             value='2phyA',

                                             )
            # Button for assigning ranks to structure
            self.load_buttonbox = Pmw.ButtonBox(self.loadergroup.interior(), padx=0)
            self.load_buttonbox.add('Load trace', command=self.loadETVX)
            self.load_buttonbox.add('View ET Server Page', command=self.viewETServerPage)
            self.pdbnamebox.pack(fill='x', padx=4, pady=1)  # vertical
            self.load_buttonbox.pack(fill='x', padx=4, pady=1)  # vertical

        self.checksequence_var = IntVar()
        self.checksequence_var.set(1)
        self.checksequence_checkbutton = Checkbutton(group.interior(),
                                                     text="check sequence",
                                                     variable=self.checksequence_var)

        self.usebfactor_var = IntVar()
        self.usebfactor_var.set(0)
        self.usebfactor_checkbutton = Checkbutton(group.interior(),
                                                  text="Use b-factor as ranks",
                                                  variable=self.usebfactor_var)

        self.checkalignsequences_var = IntVar()
        self.checkalignsequences_var.set(1)
        self.checkalignsequences_checkbutton = Checkbutton(group.interior(),
                                                           text="align input sequence to the structure",
                                                           variable=self.checkalignsequences_var)
        # Button for assigning ranks to structure
        self.map_buttonbox = Pmw.ButtonBox(group.interior(), padx=0)
        self.map_buttonbox.add('Map ranks to structure', command=self.mapRanks)

        # Field for entering ranks file
        self.ranksfile = Pmw.EntryField(group.interior(),
                                        labelpos='w',
                                        label_pyclass=FileDialogButtonClassFactory.get(self.setRanksFileLocation,'*'),
                                        validate={'validator': quickFileValidation, },
                                        value=defaultrankspath,
                                        # entry_width=50, #This value is set for the sole purpose of making the width of the plugin dialog box large enough
                                        label_text='ET ranks/Cov file path:')

        # Tkinter Slider Tkinterfor changing ET residue coverage.
        # TODO: Use a Pmw implementation?
        self.slider = Scale(group.interior(),
                            # from_=self.min, to=self.max,
                            from_=0, to=100,
                            showvalue=1,
                            length=175,
                            orient=HORIZONTAL, resolution=0.01,
                            tickinterval=0,
                            # repeatinterval=500, #Dunno what repeatinterval is for...
                            repeatdelay=500
                            )
        self.slider.set(defaultslidercoverage)
        # self.slider.grid()
        self.slider.bind("<ButtonRelease-1>", self.slider_button_release)
        # self.slider.bind("<Button-1>", self.mousebutton_click)
        self.slider.bind("<Button-2>", self.mousebutton_click)
        # For Windows
        self.slider.bind("<MouseWheel>", self.mousewheel_roll)
        # For Linux (for Mac?)
        self.slider.bind("<Button-4>", self.mousewheel_roll)
        self.slider.bind("<Button-5>", self.mousewheel_roll)
        # self.slider.bind("<B1-Motion>", self.slider_button_release)
        ##                              Triggered when the mouse enters a widget
        ##<Any-Enter>
        ##                              Triggered when a left click is done in a widget
        ##<Button-1>, <1>
        ##                              Triggered when the mouse is dragged over
        ##<B1-Motion>
        ##                              the widget with the left mouse button being
        ##                              held down.
        ##                              Triggered when a widget is double-clicked
        ##<Double-Button-1>
        ##                              with the left mouse button
        ##                              Triggered when the key producing the letter
        ##<Key-A>, <KeyPress-A>, <A>, A
        ##                              A (caps) is pressed.

        # Field for displaying ET coverage
        self.frame1 = tkinter.Frame(group.interior())
        self.percentcoverage = Pmw.EntryField(self.frame1,  # group.interior(),
                                              labelpos='w',
                                              label_text='Percent coverage: ',
                                              value='',
                                              )
        self.rankvalue = Pmw.EntryField(self.frame1,  # group.interior(),
                                        labelpos='w',
                                        label_text='Rank/Cov: ',
                                        value='',
                                        )
        self.percentcoverage.grid(column=0, row=0)
        self.rankvalue.grid(column=1, row=0)

        self.frame2 = tkinter.Frame(group.interior())
        self.et_options_tuple = ('Colors only(' + self.etcolor + ')',
                                 'as Spheres',
                                 'as Spheres (C-alpha only)',
                                 'Red_to_Green',
                                 'as Sticks',
                                 'Map -1 to +1 to blue-white-red',
                                 'Greyscale',
                                 'Prismatic (Gobstopper)')
        self.show_et_options = Pmw.OptionMenu(self.frame2,  # group.interior(),
                                              labelpos='w',
                                              label_text='Show ET residues',
                                              items=self.et_options_tuple,
                                              initialitem=self.et_options_tuple[defaultetoption],
                                              command=self.slider_button_release
                                              )
        ##        self.show_et_var=IntVar()
        ##        self.show_et_var.set(0)
        ##        self.show_et_checkbutton = Checkbutton(group.interior(),
        ##                                               text = "Show ET residues (as spheres)",
        ##                                               variable = self.show_et_var)

        self.select_et_var = IntVar()
        self.select_et_var.set(0)
        self.select_et_checkbutton = Checkbutton(self.frame2,  # group.interior(),
                                                 text="Select ET residues",
                                                 variable=self.select_et_var)

        self.show_et_options.grid(column=0, row=0)
        self.select_et_checkbutton.grid(column=1, row=0)

        # self.ETcolor_buttonbox = Pmw.ButtonBox(group.interior(), padx=0) #06/10/11 Experimenting with tkColorChooser
        # self.ETcolor_buttonbox.add('Choose ET color',command=self.chooseETcolor)

        self.qualitymeasure_options_tuple = ('3D nobias',
                                             '3D bias (j-i)')
        self.show_qualitymeasure_options = Pmw.OptionMenu(group.interior(),
                                                          labelpos='w',
                                                          label_text='quality measure: ',
                                                          items=self.qualitymeasure_options_tuple,
                                                          initialitem=self.qualitymeasure_options_tuple[
                                                              defaultqualitymeasure],
                                                          )

        self.zscore_buttonbox = Pmw.ButtonBox(group.interior(), padx=0)
        self.zscore_buttonbox.add('Compute z-scores', command=self.showZScores)
        self.zscore_buttonbox.add('Average neighbor ranks', command=self.averageNeighborRanks)
        self.zscore_et = Pmw.EntryField(group.interior(),
                                        labelpos='w',
                                        label_text='z-score ET residues: ',
                                        value='',
                                        )
        self.zscore_notet = Pmw.EntryField(group.interior(),
                                           labelpos='w',
                                           label_text='z-score non-ET residues: ',
                                           value='',
                                           )

        if interface_on:
            # Put Interface/binding site search capability into ETControlGroup (05/18/10)
            self.interfacegroup = Pmw.Group(group.interior(),
                                            tag_text='Find interface with the next structure/molecule')
            self.partnerstructureframe = tkinter.Frame(self.interfacegroup.interior())
            # Field for entering name of structure or model
            self.partnerstructure = Pmw.EntryField(self.partnerstructureframe,
                                                   labelpos='w',
                                                   label_text='structure: ',
                                                   value=defaultpartnerstructurename,
                                                   )
            self.partnerchain = Pmw.EntryField(self.partnerstructureframe,
                                               labelpos='w',
                                               label_text='chain: ',
                                               value='',
                                               )
            self.partnerstructure.grid(column=0, row=0)
            self.partnerchain.grid(column=1, row=0)
            self.distance = Pmw.EntryField(self.interfacegroup.interior(),
                                           labelpos='w',
                                           label_text='distance to use for interface: ',
                                           validate={'validator': 'real', 'min': 1, 'max': 20},
                                           value=4,
                                           )
            self.mark_buttonbox = Pmw.ButtonBox(self.interfacegroup.interior(), padx=0)
            self.mark_buttonbox.add('Mark interface', command=self.markInterface)

            self.interface_options_tuple = (  # 'Colors only('+self.interfacecolor1+')',
                'as Spheres',
                'as Sticks',
                'as Selection',
                'None')
            self.show_interface_options = Pmw.OptionMenu(self.interfacegroup.interior(),
                                                         labelpos='w',
                                                         label_text='Show interface residues',
                                                         items=self.interface_options_tuple,
                                                         initialitem=self.interface_options_tuple[0],
                                                         command=self.updateInterface
                                                         # Need two arguments for this method
                                                         )
            self.partnerstructureframe.pack(fill='x', padx=4, pady=1)
            self.distance.pack(fill='x', padx=4, pady=1)
            self.mark_buttonbox.pack(fill='x', padx=4, pady=1)
            self.show_interface_options.pack(fill='x', padx=4, pady=1)

        if et_loader_on:
            self.loadergroup.pack(fill='x', padx=4, pady=1)
        for entry in (self.structureframe,
                      self.ranksfile,
                      self.checksequence_checkbutton,
                      self.usebfactor_checkbutton,
                      self.checkalignsequences_checkbutton,
                      self.map_buttonbox,
                      self.slider,
                      self.frame1,
                      self.frame2,
                      # self.percentcoverage,
                      # self.rankvalue,
                      # self.show_et_checkbutton,
                      # self.show_et_options,
                      # self.select_et_checkbutton,
                      # self.ETcolor_buttonbox,
                      self.zscore_buttonbox,
                      self.show_qualitymeasure_options,
                      self.zscore_et,
                      self.zscore_notet):
            entry.pack(fill='x', padx=4, pady=1)  # vertical
        if interface_on:
            self.interfacegroup.pack(fill='x', padx=4, pady=1)

        # Tool tips (help) for user-interface components and boxes
        self.balloon.bind(self.structure, "Enter a name from the list of objects in the PyMOL Viewer")
        self.balloon.bind(self.ranksfile, "Click to open a box for navigating and choosing a file")
        self.balloon.bind(self.checksequence_checkbutton,
                          "Check this box to verify if the sequence in the structure and ranks file match after mapping ranks to structure")
        self.balloon.bind(self.usebfactor_checkbutton,
                          "Check this box to use the B factor or the temperature factor, averaged over the residue's atoms, as ET ranks")
        self.balloon.bind(self.checkalignsequences_checkbutton,
                          "Check this box to align the amino acid sequence in your ranks file with the sequence of the PyMOL structure. Requires Biopython installation.")
        self.balloon.bind(self.map_buttonbox,
                          "Click to map rank data onto the structure. Done automatically when doing 'Load trace'")
        self.balloon.bind(self.slider, "Drag slider to vary ET residue selection and coverage of the structure")
        self.balloon.bind(self.percentcoverage, "Maximum percent-rank (or percentile-rank) in the ET residue selection")
        self.balloon.bind(self.rankvalue, "Maximum raw ET rank score in the ET residue selection")
        self.balloon.bind(self.show_et_options,
                          "Select display style to distinguish ET residue selection from the rest of the structure")
        self.balloon.bind(self.select_et_checkbutton, "Check box to create a PyMOL selection of the ET residues")
        self.balloon.bind(self.zscore_buttonbox, "Compute the clustering z-score of the ET residue selection")
        self.balloon.bind(self.show_qualitymeasure_options, "Select from several measures of clustering quality")
        if et_loader_on:
            self.balloon.bind(self.loadergroup,
                              "Use this to download a PDB chain and precomputed ET rank data")
            self.balloon.bind(self.load_buttonbox.button(0),
                              "Click to start download")
            self.balloon.bind(self.load_buttonbox.button(1),
                              "Click to view the ET server entry for this structure if it exists")
        if interface_on:
            self.balloon.bind(self.partnerstructure,
                              "A structure with this name must exist from the list of objects in the PyMOL Viewer!")
            self.balloon.bind(self.distance,
                              "Maximum inter-residue atom-atom distance (Angstroms)")
            self.balloon.bind(self.mark_buttonbox,
                              "Click to start interface calculation and display")
            self.balloon.bind(self.show_interface_options,
                              "Select display style to distinguish interface residues from the rest of the structure")

        self.ranksObj = ETRanks()
        self.ranksStructureOK = False
        self.adjacencyOK = False
        self.interfaceComputed = False
        self.et_loader_on = et_loader_on

        self.zscore_button_bg = self.zscore_buttonbox.button(0).cget('background')
        self.zscore_button_state = self.zscore_buttonbox.button(0).cget('state')
        # The defaults for zscore_buttonbox could instead be used
        self.mark_button_bg = self.mark_buttonbox.button(0).cget('background')
        self.mark_button_state = self.mark_buttonbox.button(0).cget('state')

    def setRanksObj(self, ranksObj_external):
        """Ranks file has been read without calling mapRanks. Use the externally-created ranksObj"""
        structurename = self.structure.getvalue().strip()
        chainindicator = self.chain.getvalue().strip()
        self.structurename = structurename
        self.chainindicator = chainindicator
        self.ranksStructureOK = True

        self.ranksObj = ranksObj_external
        # self.setRanksFileLocation(self.ranksObj.rankspath)

        # self.slider_button_release(None)

    # 06/10/11 Experimenting with tkColorChooser
    def chooseETcolor(self):
        if self.ranksStructureOK:
            print(tkinter.colorchooser.askcolor(title="Pick a color for ET residues", parent=self.group.interior()))

    def mapRanks(self):
        structurename = self.structure.getvalue().strip()
        supper = structurename.upper()
        for n in cmd.get_names('all'):
            if supper == n.upper():
                break
        else:
            print('structure name must be in PyMOL viewer list:')
            print(cmd.get_names('all'))
            return
        chainindicator = self.chain.getvalue().strip()
        self.ranksStructureOK = False
        self.adjacencyOK = False
        if len(structurename) < 1:
            print('Provide structure name!')
            return
        try:
            model = cmd.get_model(structurename)
        except Exception:
            print("Structure \'%s\' does not exist!" % structurename)
            return


        if self.usebfactor_var.get() == 1:
            print("Using %s B-factors as ranks" % structurename)
            self.ranksObj.getBfactors(model)
        else:
            rankspath = self.ranksfile.getvalue().strip()
            if len(rankspath) < 1:
                print('Provide path to ranks file!')
                return

            self.ranksObj.readRanks(rankspath)
            if self.ranksObj.ranksLoaded():
                print("Ranks file \'%s\' loaded" % rankspath)
            else:
                print("Error loading ranks file \'%s\'!" % rankspath)
                return

            if self.checksequence_var.get() == 1:
                self.checkSequence(model, structurename, chainindicator, rankspath)

        # TODO TEST 08/28/14
        if self.checkalignsequences_var.get() == 1:
            (pymolrestypes, pymolresnums) = self.getPyMOLstructuresequence(model, structurename, chainindicator)
            print("Aligning the sequence in %s with the sequence in the PyMOL structure %s %s" % (
            rankspath, structurename, chainindicator))
            # print pymolrestypes, 'n', pymolresnums
            try:  # DEBUG
                self.ranksObj.alignToAnotherSequence("".join(pymolrestypes), pymolresnums)
            except Exception as detail:
                print("An exception occurred while aligning sequences!")
                print(detail)
                return

        self.structurename = structurename
        self.chainindicator = chainindicator
        self.ranksStructureOK = True

        self.recomputeInterface = True  # For CouplingGroup

        self.slider_button_release(None)

    # This check uses the residue numbers, not the amino acid types
    def checkSequence(self, model, structurename, chainindicator, rankspath):
        three2one = {
            "ALA": 'A',
            "ARG": 'R',
            "ASN": 'N',
            "ASP": 'D',
            "CYS": 'C',
            "GLN": 'Q',
            "GLU": 'E',
            "GLY": 'G',
            "HIS": 'H',
            "ILE": 'I',
            "LEU": 'L',
            "LYS": 'K',
            "MET": 'M',
            "PHE": 'F',
            "PRO": 'P',
            "SER": 'S',
            "THR": 'T',
            "TRP": 'W',
            "TYR": 'Y',
            "VAL": 'V',
            "A": "A",
            "G": "G",
            "T": "T",
            "U": "U",
            "C": "C", }

        print('Comparing residue numbers of structure \'%s\' and ranks \'%s\'...' % (structurename + chainindicator,
                                                                                     rankspath))
        # Check that the residue numbers in the model
        # and in the ranks file are identical
        resnumDict = {}
        if len(chainindicator) > 0:
            # chainup=chainindicator.upper()
            # Case of chain indicator is relevant
            for i in range(len(model.atom)):
                atomobj = model.atom[i]
                if chainindicator == atomobj.chain:
                    try:
                        aa1 = three2one[atomobj.resn]  # Consider only residues with standard amino acids
                        resnumDict[atomobj.resi] = 1
                    except KeyError:
                        pass
        else:
            for i in range(len(model.atom)):
                resnumDict[model.atom[i].resi] = 1

        print('Sequence lengths:')
        print('structure: ', len(resnumDict))
        print('ranks: ', self.ranksObj.getSize())
        if len(resnumDict) < 1:
            print('Structure \'%s\' has no residues!' % (structurename + chainindicator))
        if len(resnumDict) != self.ranksObj.getSize():
            print("Sequence lengths of \'%s\' and \'%s\' are not the same!" % (structurename + chainindicator,
                                                                               rankspath))
        missing = 0
        for i in range(self.ranksObj.getSize()):
            try:
                r = resnumDict[self.ranksObj.getResnum(i)]
            except KeyError:
                missing += 1
        if missing > 0:
            print("%d residue numbers in \'%s\' missing in \'%s\'!" % (
            missing, rankspath, structurename + chainindicator))

        print('...Done.')

    def mousewheel_roll(self, event):
        slideval = self.slider.get()
        # See http://www.daniweb.com/code/snippet217059.html
        if event.num == 4 or event.delta == -120:
            self.slider.set(slideval + 1)
            self.slider_button_release(None)
        if event.num == 5 or event.delta == 120:
            self.slider.set(slideval - 1)
            self.slider_button_release(None)
        # print event.num, event.delta, slideval

    def mousebutton_click(self, event):
        # slideval=self.slider.get()
        if event.num == 1 or event.num == 2:
            self.slider_button_release(None)
        # print event.num, event.delta, slideval

    def slider_button_release(self, event):
        if self.ranksStructureOK:
            self.et_messagebox.show()
            maxcov = 0
            maxrank = 0
            slideval = self.slider.get()
            # The following does not work. The colors _et_prism_colorXX are defined only in PyMOL,
            # not Pmw/Tkinter
            # self.slider.configure(background=('_et_prism_color%d' % (slideval-0.000001)))
            if len(self.chainindicator) > 0:
                structurename = '%s and chain %s' % (self.structurename, self.chainindicator)
            else:
                structurename = self.structurename

            if self.show_et_options.getvalue() == self.et_options_tuple[2]:
                cmd.hide('spheres', structurename)  # Need to hide spheres in case non-CA atoms are displayed
                for i in range(self.ranksObj.getSize()):
                    (resnum, cov, rank) = self.ranksObj.getResnumCoverageRankTuple(i)
                    if slideval >= cov:
                        if cov > maxcov:
                            maxcov = cov
                            maxrank = rank
                        cmd.color(self.etcolor, '%s and resi %s' % (structurename, resnum))
                        cmd.show('spheres', '%s and resi %s and name CA' % (structurename, resnum))
                    else:
                        # TODO: Color only the spheres?
                        cmd.color(self.noetcolor, '%s and resi %s' % (structurename, resnum))
                        cmd.hide('spheres', '%s and resi %s' % (structurename, resnum))
            # if self.show_et_var.get()==1:
            elif self.show_et_options.getvalue() == self.et_options_tuple[1]:
                # Rong's suggestion
                # cmd.hide('spheres',structurename)
                # cmd.show('cartoon',structurename)
                # cmd.color(self.noetcolor,structurename)


                for i in range(self.ranksObj.getSize()):
                    (resnum, cov, rank) = self.ranksObj.getResnumCoverageRankTuple(i)
                    if slideval >= cov:
                        if cov > maxcov:
                            maxcov = cov
                            maxrank = rank
                        cmd.color(self.etcolor, '%s and resi %s' % (structurename, resnum))
                        cmd.show('spheres', '%s and resi %s' % (structurename, resnum))
                    else:
                        # TODO: Color only the spheres?
                        cmd.color(self.noetcolor, '%s and resi %s' % (structurename, resnum))
                        cmd.hide('spheres', '%s and resi %s' % (structurename, resnum))
            elif self.show_et_options.getvalue() == self.et_options_tuple[3]:  # Do red green
                for i in range(self.ranksObj.getSize()):
                    (resnum, cov, rank) = self.ranksObj.getResnumCoverageRankTuple(i)
                    if slideval >= cov:
                        if cov > maxcov:
                            maxcov = cov
                            maxrank = rank
                        cmd.color('_et_rg_color%d' % (int(cov - 0.000001)),
                                  '%s and resi %s' % (structurename, resnum))
                    else:
                        cmd.color('white', '%s and resi %s' % (structurename, resnum))
                if self.et_colorramp:
                    # print 'show red green ramp'
                    self.et_colorramp.show()
            elif self.show_et_options.getvalue() == self.et_options_tuple[4]:
                for i in range(self.ranksObj.getSize()):
                    (resnum, cov, rank) = self.ranksObj.getResnumCoverageRankTuple(i)
                    if slideval >= cov:
                        if cov > maxcov:
                            maxcov = cov
                            maxrank = rank
                        cmd.color(self.etcolor, '%s and resi %s' % (structurename, resnum))
                        cmd.show('sticks', '%s and resi %s' % (structurename, resnum))
                    else:
                        cmd.color(self.noetcolor, '%s and resi %s' % (structurename, resnum))
                        cmd.hide('sticks', '%s and resi %s' % (structurename, resnum))
            elif self.show_et_options.getvalue() == self.et_options_tuple[5]:
                # Special mapping for difference ET coverage differentials suggested by Angela
                for i in range(self.ranksObj.getSize()):
                    (resnum, cov, rank) = self.ranksObj.getResnumCoverageRankTuple(i)
                    if cov > maxcov:
                        maxcov = cov
                        maxrank = rank
                    if rank > 0.99999:
                        rank = 0.99999
                    if rank < -0.99999:
                        rank = -0.99999
                    if rank > 0.2:
                        cmd.color('_et_red_color%s' % int(10 * (rank - 0.2) / 0.8),
                                  '%s and resi %s' % (structurename, resnum))
                    elif rank < -0.2:
                        cmd.color('_et_blue_color%s' % int(10 * (-rank - 0.2) / 0.8),
                                  '%s and resi %s' % (structurename, resnum))
                    else:
                        cmd.color('white', '%s and resi %s' % (structurename, resnum))
            elif self.show_et_options.getvalue() == self.et_options_tuple[6]:  # Do Greyscale 09/02/14
                for i in range(self.ranksObj.getSize()):
                    (resnum, cov, rank) = self.ranksObj.getResnumCoverageRankTuple(i)
                    if slideval >= cov:
                        if cov > maxcov:
                            maxcov = cov
                            maxrank = rank
                        cmd.color('_et_greyscale_color%d' % (int(cov - 0.000001)),
                                  '%s and resi %s' % (structurename, resnum))
                    else:
                        cmd.color('orange', '%s and resi %s' % (structurename, resnum))
            elif self.show_et_options.getvalue() == self.et_options_tuple[7]:  # Do Prismatic
                for i in range(self.ranksObj.getSize()):
                    (resnum, cov, rank) = self.ranksObj.getResnumCoverageRankTuple(i)
                    if slideval >= cov:
                        if cov > maxcov:
                            maxcov = cov
                            maxrank = rank
                        cmd.color('_et_prism_color%d' % (int(cov - 0.000001)),
                                  '%s and resi %s' % (structurename, resnum))
                    else:
                        cmd.color('white', '%s and resi %s' % (structurename, resnum))
                if self.et_colorPrism:
                    # print 'show prismatic ramp'
                    self.et_colorPrism.show()
            else:
                # It turns out that calling cmd.color or cmd.select with a list of residues, e.g. 'resi 1+2+...', can crash pymol
                # if the list is too big (>300)
                for i in range(self.ranksObj.getSize()):
                    (resnum, cov, rank) = self.ranksObj.getResnumCoverageRankTuple(i)
                    if slideval >= cov:
                        if cov > maxcov:
                            maxcov = cov
                            maxrank = rank
                        cmd.color(self.etcolor, '%s and resi %s' % (structurename, resnum))
                    else:
                        cmd.color(self.noetcolor, '%s and resi %s' % (structurename, resnum))
                # See if this is faster
                # if len(etlist)>0:
                #    cmd.color(self.etcolor,'%s and resi %s' % (structurename,string.join(etlist,'+')))
                # if len(notetlist)>0:
                #    cmd.color(self.noetcolor,'%s and resi %s' % (structurename,string.join(noetlist,'+')))
            self.percentcoverage.setvalue(maxcov)
            self.rankvalue.setvalue(maxrank)
            # Make a selection of the ET residues
            if self.select_et_var.get() == 1:
                resnumlist = []
                for i in range(self.ranksObj.getSize()):
                    (resnum, cov, rank) = self.ranksObj.getResnumCoverageRankTuple(i)
                    if slideval >= cov:
                        resnumlist.append(resnum)
                    else:
                        # You might want to make a selection for non-ET residues also
                        pass
                if len(resnumlist) > 0:
                    cmd.select('et_%s_PyETV' % (self.structurename + self.chainindicator),
                               '%s and resi %s' % (structurename, "+".join(resnumlist)))
                else:
                    cmd.select('et_%s_PyETV' % (self.structurename + self.chainindicator), 'none')

            self.et_messagebox.withdraw()

    def setRanksFileLocation(self, value):
        self.ranksfile.setvalue(value)

    def getETselection(self):
        """Get selection of ET residues at the current slider (percentile) setting"""
        et1 = []
        if self.ranksStructureOK:
            slideval = self.slider.get()
            for i in range(self.ranksObj.getSize()):
                (resnum1, cov1, rank1) = self.ranksObj.getResnumCoverageRankTuple(i)
                if slideval >= cov1:
                    et1.append(int(resnum1))
                else:
                    pass
                    # notet.append(int(resnum1))
        return et1  # list of ints of residue numbers

    # Threading as a response to reviewers 08/24/10
    if _ET_THREADING:
        def showZScores(self):
            if self.ranksStructureOK:
                self.zscore_buttonbox.button(0).configure(state=DISABLED)
                if self.et_loader_on:
                    self.load_buttonbox.button(0).configure(state=DISABLED,
                                                            background='red')
                self.map_buttonbox.button(0).configure(state=DISABLED,
                                                       background='red')
                self.zscoreBusyDoneEvent = Event()
                self.zscoreDoneEvent = Event()
                tm = Thread(target=self.zscoreBusyMessage)
                tm.start()
                tz = Thread(target=self.showZScores_run)
                tz.start()
    else:
        def showZScores(self):
            if self.ranksStructureOK:
                try:
                    self.et_messagebox.show()
                    slideval = self.slider.get()  # Get the slider value (ET threshold) early
                    if self.show_qualitymeasure_options.getvalue() == self.qualitymeasure_options_tuple[0]:
                        bias = 0
                    elif self.show_qualitymeasure_options.getvalue() == self.qualitymeasure_options_tuple[1]:
                        bias = 1
                    if not self.adjacencyOK:
                        if len(self.chainindicator) > 0:
                            structurename = '%s and chain %s' % (self.structurename, self.chainindicator)
                        else:
                            structurename = self.structurename
                        print("Computing adjacency matrix A(i,j)...")
                        self._A, ResAtoms, self._Ar = self.computeAdjacency(structurename)
                        self._L = len(
                            ResAtoms)  # 10/24/12: Peg this to the number of amino acids in the structure, not the number in the ranks file!
                        if self._A == None:
                            self.zscore_et.setvalue('')
                            self.zscore_notet.setvalue('')
                            self.et_messagebox.withdraw()
                            return
                        else:
                            self.adjacencyOK = True
                        print("... done")

                    # Get lists of ET residues
                    et1 = []
                    notet = []
                    # slideval=self.slider.get()
                    for i in range(self.ranksObj.getSize()):
                        (resnum1, cov1, rank1) = self.ranksObj.getResnumCoverageRankTuple(i)
                        if slideval >= cov1:
                            et1.append(int(resnum1))
                        else:
                            notet.append(int(resnum1))

                    # Temporary for grant 10/24/12
                    ##                    et1=[44,128,298,73,291,292,272,300,
                    ##                         75,307,134,250,138,226,139,230,51,136,55,110,78,
                    ##                         135,215,223,267,268,296,306,302,
                    ##                         303,79,265,80,121,113,125,124,161,171,174,167,295]
                    print("ET residue selection lists:")
                    print(et1)
                    print("Computing z-scores...")
                    if len(et1) > 0:
                        # z1=self.calcZScore(et1,self.ranksObj.getSize(),self._A,bias)
                        z1 = self.calcZScore(et1, self._L, self._A, bias)
                    else:
                        z1 = 'NA'
                    if len(notet) > 0:
                        # z0=self.calcZScore(notet,self.ranksObj.getSize(),self._A,bias)
                        z0 = self.calcZScore(notet, self._L, self._A, bias)
                    else:
                        z0 = 'NA'

                    print("...done")

                    self.zscore_et.setvalue(z1)
                    self.zscore_notet.setvalue(z0)

                    # if len(notet)>0:
                    #    print 'Non-ET residue z-score: ', self.calcZScore(notet,self.ranksObj.getSize(),self._A)
                except Exception as detail:
                    print('An exception occurred in showZScores!')
                    print(detail)
                self.et_messagebox.withdraw()

    ####################### 12/21/11
    def averageNeighborRanks(self):
        if self.ranksStructureOK:
            try:
                self.et_messagebox.show()
                if not self.adjacencyOK:
                    if len(self.chainindicator) > 0:
                        structurename = '%s and chain %s' % (self.structurename, self.chainindicator)
                    else:
                        structurename = self.structurename
                    print("Computing adjacency matrix A(i,j)...")
                    self._A, ResAtoms, self._Ar = self.computeAdjacency(structurename)
                    if self._A == None:
                        self.et_messagebox.withdraw()
                        return
                    else:
                        self.adjacencyOK = True
                    print("... done")

                self.calcAverageNeighborRanks()

                print("...done")

            except Exception as detail:
                print('An exception occurred in averageNeighborRanks!')
                print(detail)
            self.et_messagebox.withdraw()

    def calcAverageNeighborRanks(self):
        # Build dictionary (resnum,i)
        resindex = {}
        tmpranks = [0] * self.ranksObj.getSize()
        for i in range(self.ranksObj.getSize()):
            (resnum1, cov1, rank1) = self.ranksObj.getResnumCoverageRankTuple(i)
            resindex[int(resnum1)] = i
            tmpranks[i] = rank1
        for i in range(self.ranksObj.getSize()):
            ranki = tmpranks[i]
            resi = int(self.ranksObj.getResnum(i))
            try:
                neighborsj = self._A[resi]
                for resj in neighborsj:
                    j = resindex[resj]
                    rankj = tmpranks[j]
                    ranki += rankj
            except KeyError:
                neighborsj = []
            try:
                neighborsj2 = self._Ar[resi]
                for resj in neighborsj2:
                    j = resindex[resj]
                    rankj = tmpranks[j]
                    ranki += rankj
            except KeyError:
                neighborsj2 = []
            nall = len(neighborsj) + len(neighborsj2)
            if nall > 0:
                self.ranksObj.ranks[i] = ranki / (nall + 1.0)
        # Update coverage
        self.ranksObj.computeCoverage()
        # Update display
        self.slider_button_release(None)
        print("ET ranks have been modified!")

    #######################

    def zscoreBusyMessage(self):
        tog = True
        # Loop until showZScores_run finishes with calculation
        while not self.zscoreDoneEvent.isSet():
            if tog:
                self.zscore_buttonbox.button(0).configure(background='green')
                # self.zscore_et.setvalue('PROCESSING...')
                # self.zscore_notet.setvalue('PROCESSING...')
            else:
                self.zscore_buttonbox.button(0).configure(background=self.zscore_button_bg)
                # self.zscore_et.setvalue('')
                # self.zscore_notet.setvalue('')
            tog = not tog
            time.sleep(0.5)
        self.zscore_buttonbox.button(0).configure(background=self.zscore_button_bg)
        # Notify showZScores_run that pulsing message has ended
        self.zscoreBusyDoneEvent.set()

    def showZScores_run(self):
        try:
            if 1:  # if self.ranksStructureOK:
                slideval = self.slider.get()  # Get the slider value (ET threshold) early
                if self.show_qualitymeasure_options.getvalue() == self.qualitymeasure_options_tuple[0]:
                    bias = 0
                elif self.show_qualitymeasure_options.getvalue() == self.qualitymeasure_options_tuple[1]:
                    bias = 1
                if not self.adjacencyOK:
                    if len(self.chainindicator) > 0:
                        structurename = '%s and chain %s' % (self.structurename, self.chainindicator)
                    else:
                        structurename = self.structurename
                    print("Computing adjacency matrix A(i,j)...")
                    self._A, ResAtoms, self._Ar = self.computeAdjacency(structurename)
                    if self._A == None:
                        self.zscoreDoneEvent.set()
                        self.zscoreBusyDoneEvent.wait(5)
                        self.zscore_et.setvalue('')
                        self.zscore_notet.setvalue('')
                        self.zscore_buttonbox.button(0).configure(state=self.zscore_button_state,
                                                                  background=self.zscore_button_bg)
                        if self.et_loader_on:
                            self.load_buttonbox.button(0).configure(state=self.zscore_button_state,
                                                                    background=self.zscore_button_bg)
                        self.map_buttonbox.button(0).configure(state=self.zscore_button_state,
                                                               background=self.zscore_button_bg)
                        return
                    else:
                        self.adjacencyOK = True
                    print("... done")

                # Get lists of ET residues
                et1 = []
                notet = []
                # slideval=self.slider.get()
                for i in range(self.ranksObj.getSize()):
                    (resnum1, cov1, rank1) = self.ranksObj.getResnumCoverageRankTuple(i)
                    if slideval >= cov1:
                        et1.append(int(resnum1))
                    else:
                        notet.append(int(resnum1))

                print("ET residue selection lists:")
                print(et1)
                print("Computing z-scores...")
                if len(et1) > 0:
                    z1 = self.calcZScore(et1, self.ranksObj.getSize(), self._A, bias)
                else:
                    z1 = 'NA'
                if len(notet) > 0:
                    z0 = self.calcZScore(notet, self.ranksObj.getSize(), self._A, bias)
                else:
                    z0 = 'NA'

                print("...done")

                self.zscoreDoneEvent.set()
                # Make sure zscoreBusyMessage ends so z-scores
                # on text/entry fields don't get overwritten
                self.zscoreBusyDoneEvent.wait(5)

                self.zscore_et.setvalue(z1)
                self.zscore_notet.setvalue(z0)

                # if len(notet)>0:
                #    print 'Non-ET residue z-score: ', self.calcZScore(notet,self.ranksObj.getSize(),self._A)
        except Exception as detail:
            print('An exception occurred in showZScores_run!')
            print(detail)
            self.zscoreDoneEvent.set()

        # Place inside a "finally" clause?
        self.zscore_buttonbox.button(0).configure(state=self.zscore_button_state,
                                                  background=self.zscore_button_bg)
        if self.et_loader_on:
            self.load_buttonbox.button(0).configure(state=self.zscore_button_state,
                                                    background=self.zscore_button_bg)
        self.map_buttonbox.button(0).configure(state=self.zscore_button_state,
                                               background=self.zscore_button_bg)

    def loadETVX(self):
        self.et_messagebox.show()
        etvxurl = 'http://mammoth.bcm.tmc.edu/ETserver2/pdbeasytrace/'  # self.etvxurlbox.getvalue().strip()
        pdb_chain = self.pdbnamebox.getvalue().strip()[:5]
        print('Retrieving ' + pdb_chain + '.etvx from ' + etvxurl)
        try:
            tmpetvxpath = urllib.request.urlretrieve(etvxurl + pdb_chain + '.etvx')[0]
        except Exception:
            print('Error in retrieving trace data! Please check your internet connection.')
            self.et_messagebox.withdraw()
            return
        print(tmpetvxpath)
        if os.path.getsize(tmpetvxpath) == 0:
            print(etvxurl + pdb_chain + '.etvx not found')
            self.et_messagebox.withdraw()
            return
        print('Extracting query pdb structure')
        pdbfilename = self.extractPDB(tmpetvxpath, pdb_chain)
        if pdbfilename == None:
            print('Missing structure.', etvxurl + pdb_chain + '.etvx likely non-existent')
            self.et_messagebox.withdraw()
            return
        print(pdbfilename)
        print('Extracting ranks file')
        ranksfilename = self.extractRanks(tmpetvxpath, pdb_chain)
        print(ranksfilename)
        cmd.load(pdbfilename, object=pdb_chain)
        self.setRanksFileLocation(ranksfilename)
        self.structure.setvalue(pdb_chain)
        self.chain.setvalue('')
        # Let PyETV (or ET Tools) know about this trace...
        # cmd._et_tools_structurename=pdb_chain
        # cmd._et_tools_rankspath1=ranksfilename
        self.mapRanks()
        self.et_messagebox.withdraw()

    def extractPDB(self, etvxpath, pdb_chain):
        # Get the ATOM records
        FILE = open(etvxpath, 'r')
        data = ''
        try:
            for line in FILE:
                if line[:4] == 'ATOM':
                    data += line
                # TODO: Stop when pdb section is exhausted?
        finally:
            FILE.close()

        if len(data) == 0:
            return None

        # Write ATOM lines to file
        pdbfilename = os.path.dirname(etvxpath) + os.sep + 'query_' + pdb_chain + '.pdb'
        FILE_PDB = open(pdbfilename, 'w')
        FILE_PDB.write(data)
        FILE_PDB.close()

        return pdbfilename

    def extractRanks(self, etvxpath, pdb_chain):
        # Get the ranks section
        FILE = open(etvxpath, 'r')
        data = ''
        ready = False
        try:
            for line in FILE:
                if line[:5] == '~tree':
                    break
                if ready:
                    data += line
                if line[:9] == '~ET_ranks':
                    ready = True
        finally:
            FILE.close()

        # Write ranks section to file
        ranksfilename = os.path.dirname(etvxpath) + os.sep + pdb_chain + '.ranks'
        FILE_RANKS = open(ranksfilename, 'w')
        FILE_RANKS.write(data)
        FILE_RANKS.close()

        return ranksfilename

    def viewETServerPage(self):
        tv = Thread(target=self.viewETServerPage_run)
        tv.start()

    def viewETServerPage_run(self):
        pdb = self.pdbnamebox.getvalue().strip()[:4]
        import webbrowser
        webbrowser.open('http://mammoth.bcm.tmc.edu/cgi-bin/report_maker_ls/traceServerResults.pl?identifier=%s' % pdb)

    if _ET_THREADING:
        def markInterface(self):
            if self.ranksStructureOK:
                self.mark_buttonbox.button(0).configure(state=DISABLED)
                self.markBusyDoneEvent = Event()
                self.markDoneEvent = Event()
                tm = Thread(target=self.markBusyMessage)
                tm.start()
                ti = Thread(target=self.markInterface_run)
                ti.start()
    else:
        def markInterface(self):
            if self.ranksStructureOK:
                try:
                    chainindicator1 = self.chain.getvalue().strip()
                    structname = self.structure.getvalue().strip()
                    supper = structname.upper()
                    for n in cmd.get_names('all'):
                        if supper == n.upper():
                            break
                    else:
                        print('structure name must be in PyMOL viewer list:')
                        print(cmd.get_names('all'))
                        return
                    if len(chainindicator1) > 0:
                        structurename1 = structname + ' and chain %s' % chainindicator1
                    else:
                        structurename1 = structname
                    chainindicator2 = self.partnerchain.getvalue().strip()
                    pstructname = self.partnerstructure.getvalue().strip()
                    psupper = pstructname.upper()
                    for n in cmd.get_names('all'):
                        if psupper == n.upper():
                            break
                    else:
                        print('partner structure name must be in PyMOL viewer list:')
                        print(cmd.get_names('all'))
                        return
                    if len(chainindicator2) > 0:
                        structurename2 = pstructname + ' and chain %s' % chainindicator2
                    else:
                        structurename2 = pstructname

                    dist = float(self.distance.getvalue().strip())

                    self.et_messagebox.show()

                    # In self.interface1, each residue indexes into an array of neighboring residues in the other structure
                    (self.interface1,
                     self.interface2,
                     self.ResAtoms1,
                     self.ResAtoms2) = self.computeInterface(structurename1, structurename2, dist * dist)

                    if (self.interface1 is None) or (self.interface2 is None):
                        print('Interfaces not computed')
                        self.interfaceComputed = False
                        self.et_messagebox.withdraw()
                        return

                    self.interfaceComputed = True
                    print("%d interface residues in %s" % (len(self.interface1), structurename1))
                    print("%d interface residues in %s" % (len(self.interface2), structurename2))
                    print("Adjacency lists:")
                    print(structurename1, self.interface1)
                    print(structurename2, self.interface2)

                    self.updateInterface(None)
                except Exception as detail:
                    print('An exception occurred in markInterface!')
                    print(detail)
                self.et_messagebox.withdraw()

    def markBusyMessage(self):
        tog = True
        # Loop until markInterface_run finishes with calculation
        while not self.markDoneEvent.isSet():
            if tog:
                self.mark_buttonbox.button(0).configure(background='green')
            else:
                self.mark_buttonbox.button(0).configure(background=self.mark_button_bg)
            tog = not tog
            time.sleep(0.5)
        self.mark_buttonbox.button(0).configure(background=self.mark_button_bg)
        # Notify markInterface_run that pulsing message has ended
        self.markBusyDoneEvent.set()

    def markInterface_run(self):
        # Response to reviewer 08/24/10
        try:
            chainindicator1 = self.chain.getvalue().strip()
            structname = self.structure.getvalue().strip()
            supper = structname.upper()
            for n in cmd.get_names('all'):
                if supper == n.upper():
                    break
            else:
                print('structure name must be in PyMOL viewer list:')
                print(cmd.get_names('all'))
                self.markDoneEvent.set()
                self.markBusyDoneEvent.wait(5)
                self.mark_buttonbox.button(0).configure(state=self.mark_button_state,
                                                        background=self.mark_button_bg)
                return
            if len(chainindicator1) > 0:
                structurename1 = structname + ' and chain %s' % chainindicator1
            else:
                structurename1 = structname
            chainindicator2 = self.partnerchain.getvalue().strip()
            pstructname = self.partnerstructure.getvalue().strip()
            psupper = pstructname.upper()
            for n in cmd.get_names('all'):
                if psupper == n.upper():
                    break
            else:
                print('partner structure name must be in PyMOL viewer list:')
                print(cmd.get_names('all'))
                self.markDoneEvent.set()
                self.markBusyDoneEvent.wait(5)
                self.mark_buttonbox.button(0).configure(state=self.mark_button_state,
                                                        background=self.mark_button_bg)
                return
            if len(chainindicator2) > 0:
                structurename2 = pstructname + ' and chain %s' % chainindicator2
            else:
                structurename2 = pstructname

            dist = float(self.distance.getvalue().strip())

            # These are redundant. 08/29/10
            ##            if len(self.structure.getvalue().strip())<1:
            ##                print 'Provide structure 1 name!'
            ##                self.mark_buttonbox.button(0).configure(state=self.mark_button_state,
            ##                                                        background=self.mark_button_bg)
            ##                return
            ##            if len(self.partnerstructure.getvalue().strip())<1:
            ##                print 'Provide structure 2 name!'
            ##                self.mark_buttonbox.button(0).configure(state=self.mark_button_state,
            ##                                                        background=self.mark_button_bg)
            ##                return

            # In self.interface1, each residue indexes into an array of neighboring residues in the other structure
            (self.interface1,
             self.interface2,
             self.ResAtoms1,
             self.ResAtoms2) = self.computeInterface(structurename1, structurename2, dist * dist)

            if (self.interface1 is None) or (self.interface2 is None):
                print('Interfaces not computed')
                self.interfaceComputed = False
                self.markDoneEvent.set()
                self.markBusyDoneEvent.wait(5)
                self.mark_buttonbox.button(0).configure(state=self.mark_button_state,
                                                        background=self.mark_button_bg)
                return

            self.interfaceComputed = True
            print("%d interface residues in %s" % (len(self.interface1), structurename1))
            print("%d interface residues in %s" % (len(self.interface2), structurename2))
            print("Adjacency lists:")
            print(structurename1, self.interface1)
            print(structurename2, self.interface2)

            self.updateInterface(None)
        except Exception as detail:
            print('An exception occurred in markInterface_run!')
            print(detail)
            # self.markDoneEvent.set()

        self.markDoneEvent.set()
        self.markBusyDoneEvent.wait(5)
        self.mark_buttonbox.button(0).configure(state=self.mark_button_state,
                                                background=self.mark_button_bg)

    def updateInterface(self, event):
        # TODO: Make this method more aware of changes in the viewer
        if self.interfaceComputed == False:
            return

        s1 = self.structure.getvalue().strip()
        s2 = self.partnerstructure.getvalue().strip()
        c1 = self.chain.getvalue().strip()
        c2 = self.partnerchain.getvalue().strip()

        if len(c1) > 0:
            structurename1 = s1 + ' and chain %s' % c1
        else:
            structurename1 = s1
        if len(c2) > 0:
            structurename2 = s2 + ' and chain %s' % c2
        else:
            structurename2 = s2

        ##        if self.show_interface_options1.getvalue()==self.interface_options_tuple[0]:
        ##            for res in self.interface1.keys():
        ##                cmd.color(self.interfacecolor1,'%s and resi %s' % (structurename1,res))
        if self.show_interface_options.getvalue() == self.interface_options_tuple[0]:
            for res in list(self.interface1.keys()):
                cmd.show('spheres', '%s and resi %s' % (structurename1, res))
        elif self.show_interface_options.getvalue() == self.interface_options_tuple[1]:
            for res in list(self.interface1.keys()):
                cmd.show('sticks', '%s and resi %s' % (structurename1, res))
        elif self.show_interface_options.getvalue() == self.interface_options_tuple[2]:
            # Make a selection of the ET residues
            if len(self.interface1) > 0:
                cmd.select('%s_%s_PyETV' % (s1 + c1, s2 + c2),
                           '%s and resi %s' % (structurename1, "+".join(list(map(str, self.interface1)))))
            else:
                cmd.select('%s_%s_PyETV' % (s1 + c1, s2 + c2), 'none')


#####################################################################
# Assembly
# 1. Get multimer structure from PISA
# 2. Get ranks files of every unique chain from mammoth pdbeasytrace
# 3. Map ranks files to the correct chains in the multimer
class ETAssemblyControlGroup:
    def __init__(self,
                 page,
                 groupname='Load Biological Unit',
                 defaultslidercoverage=30,
                 defaultetoption=0,
                 defaultqualitymeasure=0,
                 et_colorramp=None,
                 et_colorPrism=None,
                 et_messagebox=None
                 ):
        group = Pmw.Group(page, tag_text=groupname)
        group.pack(fill='both', expand=1, padx=10, pady=5)

        self.et_colorramp = et_colorramp
        self.et_colorPrism = et_colorPrism
        self.et_messagebox = et_messagebox

        self.slavelist = []

        if 1:
            self.pdbnamebox = Pmw.EntryField(group.interior(),
                                             labelpos='w',
                                             label_text='Enter a 4-digit pdb code (e.g. 3gcb): ',
                                             value='1got',
                                             )
            # Button for assigning ranks to structure
            self.load_buttonbox = Pmw.ButtonBox(group.interior(), padx=0)
            self.load_buttonbox.add('Load Assembly', command=self.load)
            self.load_buttonbox.add('View PISA Page', command=self.viewPage)
            self.pdbnamebox.pack(fill='x', padx=4, pady=1)  # vertical
            self.load_buttonbox.pack(fill='x', padx=4, pady=1)  # vertical

        # Tkinter Slider Tkinterfor changing ET residue coverage.
        # TODO: Use a Pmw implementation?
        self.slider = Scale(group.interior(),
                            # from_=self.min, to=self.max,
                            from_=0, to=100,
                            showvalue=1,
                            length=200,
                            orient=HORIZONTAL, resolution=0.01,
                            tickinterval=0,
                            # repeatinterval=500, #Dunno what repeatinterval is for...
                            repeatdelay=500
                            )
        self.slider.set(defaultslidercoverage)
        # self.slider.grid()
        # These additional mouse operations might be too costly for large assemblies
        self.slider.bind("<ButtonRelease-1>", self.slider_button_release)
        ##        #self.slider.bind("<Button-1>", self.mousebutton_click)
        ##        self.slider.bind("<Button-2>", self.mousebutton_click)
        ##        #For Windows
        ##        self.slider.bind("<MouseWheel>", self.mousewheel_roll)
        ##        #For Linux (for Mac?)
        ##        self.slider.bind("<Button-4>", self.mousewheel_roll)
        ##        self.slider.bind("<Button-5>", self.mousewheel_roll)
        # self.slider.bind("<B1-Motion>", self.slider_button_release)
        ##                              Triggered when the mouse enters a widget
        ##<Any-Enter>
        ##                              Triggered when a left click is done in a widget
        ##<Button-1>, <1>
        ##                              Triggered when the mouse is dragged over
        ##<B1-Motion>
        ##                              the widget with the left mouse button being
        ##                              held down.
        ##                              Triggered when a widget is double-clicked
        ##<Double-Button-1>
        ##                              with the left mouse button
        ##                              Triggered when the key producing the letter
        ##<Key-A>, <KeyPress-A>, <A>, A
        ##                              A (caps) is pressed.

        self.frame2 = tkinter.Frame(group.interior())
        # This list must be the same as in ETControlGroup
        self.et_options_tuple = ('Colors only',
                                 'as Spheres',
                                 'as Spheres (C-alpha only)',
                                 'Red_to_Green',

                                 'as Sticks')
        self.show_et_options = Pmw.OptionMenu(self.frame2,  # group.interior(),
                                              labelpos='w',
                                              label_text='Show ET residues',
                                              items=self.et_options_tuple,
                                              initialitem=self.et_options_tuple[defaultetoption],
                                              command=self.change_options
                                              )

        self.show_et_options.grid(column=0, row=0)

        self.notebook = Pmw.NoteBook(group.interior())

        self.balloon = Pmw.Balloon(group.interior())
        self.balloon.bind(self.slider,
                          "Use this slider to vary the selection of ET residues for all chains in the assembly.")
        self.balloon.bind(self.load_buttonbox.button(0),
                          "Click to start downloading an assembly and rank data and creating widgets below")
        self.balloon.bind(self.load_buttonbox.button(1), "Click to view the PISA entry")
        self.balloon.bind(self.show_et_options,
                          "Select display style to distinguish ET residue selections from the rest of the structure")
        self.balloon.bind(self.pdbnamebox,
                          "Enter a single PDB code (e.g. 1got) or a PDB code with PISA assembly numbers (1got:1,1)")

        for entry in (self.pdbnamebox,
                      self.load_buttonbox,
                      self.slider,
                      self.frame2):
            entry.pack(fill='x', padx=4, pady=1)  # vertical
        self.notebook.pack(fill='x', expand=1, padx=4, pady=1)

        self.three2one = {
            "ALA": 'A',
            "ARG": 'R',
            "ASN": 'N',
            "ASP": 'D',
            "CYS": 'C',
            "GLN": 'Q',
            "GLU": 'E',
            "GLY": 'G',
            "HIS": 'H',
            "ILE": 'I',
            "LEU": 'L',
            "LYS": 'K',
            "MET": 'M',
            "PHE": 'F',
            "PRO": 'P',
            "SER": 'S',
            "THR": 'T',
            "TRP": 'W',
            "TYR": 'Y',
            "VAL": 'V'}

        self.load_button_bg = self.load_buttonbox.button(0).cget('background')
        self.load_button_state = self.load_buttonbox.button(0).cget('state')

    if _ET_THREADING:
        # Threading as a response to reviewers 08/24/10
        def load(self):
            self.load_buttonbox.button(0).configure(state=DISABLED)
            # Got errors when the next two loops were placed in load_run and ran
            # on a new thread
            for slave in self.slavelist:
                del slave
            for name in self.notebook.pagenames():
                self.notebook.delete(name)  # Clear the previous tabs
            self.loadBusyDoneEvent = Event()
            self.loadDoneEvent = Event()
            tm = Thread(target=self.loadBusyMessage)
            tm.start()
            tl = Thread(target=self.load_run)
            tl.start()
    else:  # Not threaded
        def load(self):
            try:
                self.et_messagebox.show()
                for slave in self.slavelist:
                    del slave
                for name in self.notebook.pagenames():
                    self.notebook.delete(name)  # Clear the previous tabs

                params = self.pdbnamebox.getvalue().strip()
                if len(params) < 4:
                    print('Enter valid pdb code!')
                    self.et_messagebox.withdraw()
                    return
                elif len(params) == 4:
                    params = params + ':1,1'
                else:
                    # User might specify different assembly set, e.g. '3cgb:2,1'
                    pass
                PISA_url = 'http://www.ebi.ac.uk/msd-srv/pisa/cgi-bin/multimer.pdb?' + params
                # import urllib
                try:
                    pdbpath = urllib.request.urlretrieve(PISA_url)[0]
                except Exception:
                    print('Error in retrieving PISA structure data! Please check your internet connection.')
                    raise Exception()
                structurename = params[:4]
                cmd.load(pdbpath, structurename)
                # Check if structure/pdb is empty
                try:
                    model = cmd.get_model(structurename)
                except Exception:
                    print("Structure \'%s\' does not exist!" % structurename)
                    self.et_messagebox.withdraw()
                    return
                if len(model.atom) == 0:
                    print("Structure \'%s\' has no PISA quaternary structure!" % structurename)
                    self.et_messagebox.withdraw()
                    return

                self.getRanks(params[:4])
                self.matchChaintoRanks(structurename)
                # Create ET control groups mapping a trace to each chain
                self.slavelist = []
                for c, ranksObj in list(self.chain_ranks_dict.items()):
                    page = self.notebook.add(c)
                    slave = ETControlGroup(page,
                                           groupname=structurename + c,
                                           defaultstructurename=structurename,
                                           defaultchain=c,
                                           defaultrankspath=ranksObj.rankspath,
                                           # defaultetoption=self.show_et_options.index(Pmw.SELECT),
                                           defaultslidercoverage=self.slider.get(),
                                           # etcolor=etcolor_,
                                           # noetcolor=noetcolor_,
                                           defaultpartnerstructurename=structurename,
                                           et_loader_on=False,
                                           et_colorramp=self.et_colorramp,
                                           et_colorPrism=self.et_colorPrism,
                                           et_messagebox=self.et_messagebox)
                    slave.setRanksObj(ranksObj)
                    self.slavelist.append(slave)
                # Add Zcoupling and Interface computation to Assembly tabs (05/20/11)
                if len(self.chain_ranks_dict) > 1:
                    page = self.notebook.add('Zcoupling')
                    self.couplingCalculatorAssembly = CouplingGroup2(page,
                                                                     groupname='Compute ET coupling z-score',
                                                                     tracelist=self.slavelist,
                                                                     et_messagebox=self.et_messagebox
                                                                     )
                    self.InterfaceGroupAssembly = InterfaceControlGroup2(page,
                                                                         groupname='Mark interface between structures 1 and 2',
                                                                         tracelist=self.slavelist,
                                                                         defaultinterfaceoption1=1,
                                                                         defaultinterfaceoption2=1,
                                                                         et_messagebox=self.et_messagebox
                                                                         )

                self.notebook.pack(fill='both', expand=1, padx=10, pady=10)
                # self.notebook.setnaturalsize()

                self.change_options(None)

            except Exception as detail:
                print('An exception occurred in load!')
                print(detail)

            self.et_messagebox.withdraw()

    def loadBusyMessage(self):
        tog = True
        # Loop until load_run finishes with calculation
        while not self.loadDoneEvent.isSet():
            if tog:
                self.load_buttonbox.button(0).configure(background='green')
            else:
                self.load_buttonbox.button(0).configure(background=self.load_button_bg)
            tog = not tog
            time.sleep(0.5)
        # Notify load_run that pulsing message has ended
        self.loadBusyDoneEvent.set()

    def load_run(self):
        """Download multimer pdb from PISA"""

        ##        for slave in self.slavelist:
        ##            #slave.groupscrolled.configure(hscrollmode='none')
        ##            del slave.groupscrolled

        try:
            params = self.pdbnamebox.getvalue().strip()
            if len(params) < 4:
                print('Enter valid pdb code!')
                self.loadDoneEvent.set()
                self.loadBusyDoneEvent.wait(5)
                self.load_buttonbox.button(0).configure(state=self.load_button_state,
                                                        background=self.load_button_bg)
                return
            elif len(params) == 4:
                params = params + ':1,1'
            else:
                # User might specify different assembly set, e.g. '3cgb:2,1'
                pass
            PISA_url = 'http://www.ebi.ac.uk/msd-srv/pisa/cgi-bin/multimer.pdb?' + params
            # import urllib
            pdbpath = urllib.request.urlretrieve(PISA_url)[0]
            structurename = params[:4]
            cmd.load(pdbpath, structurename)
            # Check if structure/pdb is empty
            try:
                model = cmd.get_model(structurename)
            except Exception:
                print("Structure \'%s\' does not exist!" % structurename)
                self.loadDoneEvent.set()
                self.loadBusyDoneEvent.wait(5)
                self.load_buttonbox.button(0).configure(state=self.load_button_state,
                                                        background=self.load_button_bg)
                return
            if len(model.atom) == 0:
                print("Structure \'%s\' has no PISA quaternary structure!" % structurename)
                self.loadDoneEvent.set()
                self.loadBusyDoneEvent.wait(5)
                self.load_buttonbox.button(0).configure(state=self.load_button_state,
                                                        background=self.load_button_bg)
                return

            self.getRanks(params[:4])
            self.matchChaintoRanks(structurename)
            # Create ET control groups mapping a trace to each chain
            self.slavelist = []
            for c, ranksObj in list(self.chain_ranks_dict.items()):
                page = self.notebook.add(c)
                slave = ETControlGroup(page,
                                       groupname=structurename + c,
                                       defaultstructurename=structurename,
                                       defaultchain=c,
                                       defaultrankspath=ranksObj.rankspath,
                                       # defaultetoption=self.show_et_options.index(Pmw.SELECT),
                                       defaultslidercoverage=self.slider.get(),
                                       # etcolor=etcolor_,
                                       # noetcolor=noetcolor_,
                                       defaultpartnerstructurename=structurename,
                                       et_loader_on=False,
                                       et_colorramp=self.et_colorramp,
                                       et_colorPrism=self.et_colorPrism,
                                       et_messagebox=self.et_messagebox)
                slave.setRanksObj(ranksObj)
                self.slavelist.append(slave)

            self.notebook.pack(fill='both', expand=1, padx=10, pady=10)
            # self.notebook.setnaturalsize()

            self.change_options(None)

        except Exception as detail:
            print('An exception occurred in load_run!')
            print(detail)
            # self.loadDoneEvent.set()

        self.loadDoneEvent.set()
        self.loadBusyDoneEvent.wait(5)
        self.load_buttonbox.button(0).configure(state=self.load_button_state,
                                                background=self.load_button_bg)

    def viewPage(self):
        tv = Thread(target=self.viewPage_run)
        tv.start()

    def viewPage_run(self):
        import webbrowser
        # http://www.ebi.ac.uk/msd-srv/prot_int/pistart.html
        # http://www.ebi.ac.uk/msd-srv/prot_int/cgi-bin/piserver
        # http://www.ebi.ac.uk/msd-srv/pisa/cgi-bin/piserver?qa=1stm
        params = self.pdbnamebox.getvalue().strip()[:4]
        PISA_url = 'http://www.ebi.ac.uk/msd-srv/pisa/cgi-bin/piserver?qa=' + params
        webbrowser.open(PISA_url)  # This call blocks in debian linux

    def matchChaintoRanks(self, structurename):
        """Get the amino acid sequences in the structure sorted by chain"""

        try:
            model = cmd.get_model(structurename)
        except Exception:
            print("Structure \'%s\' does not exist!" % structurename)
            return

        chain_sequence_dict = {}
        for i in range(len(model.atom)):
            atomobj = model.atom[i]
            if atomobj.name == 'CA':  # Caution: Sometimes the CA atom may not be present in a pdb residue
                try:
                    aa1 = self.three2one[atomobj.resn]  # Assume the amino acids are standard
                except KeyError:
                    pass  # Not a standard amino acid
                else:
                    try:
                        chain_sequence_dict[atomobj.chain].append(aa1)
                    except KeyError:
                        chain_sequence_dict[atomobj.chain] = [aa1]

        self.chain_ranks_dict = {}
        for c, s in list(chain_sequence_dict.items()):
            seq = "".join(s)
            for ranksObj in self.ranksObjlist:
                if seq.find(
                        ranksObj.sequence) != -1:  # Find a 'soft' match. Sequence in trace must be a substring of the PDB chain sequence.
                    self.chain_ranks_dict[c] = ranksObj
                    break

        print("Structure \'%s\' has %d protein chains:" % (structurename, len(chain_sequence_dict)),
              list(chain_sequence_dict.keys()))
        print("Found traces for %d chains" % (len(self.chain_ranks_dict)))

    def getRanks(self, pdbcode):
        """Gather all ranks files for this pdbcode from mammoth"""

        self.ranksObjlist = []

        # TODO: This strategy looks like a hack. Get the list of unique chains from the pml file, which contains:
        # load http://mammoth.bcm.tmc.edu/ETserver2/pdbeasytrace/1ewyA.etvx, 1ewyA
        # load http://mammoth.bcm.tmc.edu/ETserver2/pdbeasytrace/1ewyC.etvx, 1ewyC
        # zoom
        # cmd._et_tools_structurenamelist=['1ewyA', '1ewyC']
        # cmd._et_tools_etvxurllist=['http://mammoth.bcm.tmc.edu/ETserver2/pdbeasytrace/1ewyA.etvx', 'http://mammoth.bcm.tmc.edu/ETserver2/pdbeasytrace/1ewyC.etvx']
        # cmd._et_tools_selectpage='1ewyA'
        pml_url = "http://mammoth.bcm.tmc.edu/ETserver2/pdbeasytrace/pmlFiles/%s.pml" % pdbcode
        try:
            pmlpath = urllib.request.urlretrieve(pml_url)[0]
        except Exception:
            print('Error retrieving trace (pml) data! Please check your internet connection.')
            raise Exception()
        FILE = open(pmlpath, 'r')
        for line in FILE:
            cols = line.split()
            if len(cols) == 3:
                if cols[0] == 'load':
                    etvxurl = cols[1].strip(' ,')
                    chain = etvxurl[-1]
                    try:
                        etvxpath = urllib.request.urlretrieve(etvxurl)[0]
                    except Exception:
                        print('Error retrieving trace data! Please check your internet connection.')
                        raise Exception()
                    ranksfilename = self.extractRanks(etvxpath, cols[2].strip())
                    ranksObj = ETRanks()
                    ranksObj.readRanks(ranksfilename)
                    self.ranksObjlist.append(ranksObj)
        FILE.close()

    def extractRanks(self, etvxpath, pdb_chain):
        # Get the ranks section
        FILE = open(etvxpath, 'r')
        data = ''
        ready = False
        try:
            for line in FILE:
                if line[:5] == '~tree':
                    break
                if ready:
                    data += line
                if line[:9] == '~ET_ranks':
                    ready = True
        finally:
            FILE.close()

        # Write ranks section to file
        ranksfilename = os.path.dirname(etvxpath) + os.sep + pdb_chain + '.ranks'
        FILE_RANKS = open(ranksfilename, 'w')
        FILE_RANKS.write(data)
        FILE_RANKS.close()

        return ranksfilename

    def mousewheel_roll(self, event):
        slideval = self.slider.get()
        # See http://www.daniweb.com/code/snippet217059.html
        if event.num == 4 or event.delta == -120:
            self.slider.set(slideval + 1)
            self.slider_button_release(None)
        if event.num == 5 or event.delta == 120:
            self.slider.set(slideval - 1)
            self.slider_button_release(None)
        # print event.num, event.delta, slideval

    def mousebutton_click(self, event):
        # slideval=self.slider.get()
        if event.num == 1 or event.num == 2:
            self.slider_button_release(None)
        # print event.num, event.delta, slideval

    def slider_button_release(self, event):
        slideval = self.slider.get()
        for slave in self.slavelist:
            slave.slider.set(slideval)
            slave.slider_button_release(None)

    def change_options(self, event):
        for slave in self.slavelist:
            slave.show_et_options.setvalue(self.show_et_options.getvalue())
            slave.slider_button_release(None)


#####################################################################
# Complex/multiple chains
class ETMasterControlGroup:
    def __init__(self,
                 page,
                 groupname='ALL',
                 slavelist=[],
                 defaultslidercoverage=30,
                 defaultetoption=0,
                 defaultqualitymeasure=0):
        group = Pmw.Group(page, tag_text=groupname)
        group.pack(fill='both', expand=1, padx=10, pady=5)

        self.slavelist = slavelist

        # Tkinter Slider Tkinterfor changing ET residue coverage.
        # TODO: Use a Pmw implementation?
        self.slider = Scale(group.interior(),
                            # from_=self.min, to=self.max,
                            from_=0, to=100,
                            showvalue=1,
                            length=200,
                            orient=HORIZONTAL, resolution=0.01,
                            tickinterval=0,
                            # repeatinterval=500, #Dunno what repeatinterval is for...
                            repeatdelay=500
                            )
        self.slider.set(defaultslidercoverage)
        # self.slider.grid()
        self.slider.bind("<ButtonRelease-1>", self.slider_button_release)
        # These mouse operations might be too costly for large assemblies
        ##        #self.slider.bind("<Button-1>", self.mousebutton_click)
        ##        self.slider.bind("<Button-2>", self.mousebutton_click)
        ##        #For Windows
        ##        self.slider.bind("<MouseWheel>", self.mousewheel_roll)
        ##        #For Linux (for Mac?)
        ##        self.slider.bind("<Button-4>", self.mousewheel_roll)
        ##        self.slider.bind("<Button-5>", self.mousewheel_roll)
        # self.slider.bind("<B1-Motion>", self.slider_button_release)
        ##                              Triggered when the mouse enters a widget
        ##<Any-Enter>
        ##                              Triggered when a left click is done in a widget
        ##<Button-1>, <1>
        ##                              Triggered when the mouse is dragged over
        ##<B1-Motion>
        ##                              the widget with the left mouse button being
        ##                              held down.
        ##                              Triggered when a widget is double-clicked
        ##<Double-Button-1>
        ##                              with the left mouse button
        ##                              Triggered when the key producing the letter
        ##<Key-A>, <KeyPress-A>, <A>, A
        ##                              A (caps) is pressed.

        self.frame2 = tkinter.Frame(group.interior())
        # This list must be the same as in ETControlGroup
        self.et_options_tuple = ('Colors only',
                                 'as Spheres',
                                 'as Spheres (C-alpha only)',
                                 'Red_to_Green',

                                 'as Sticks')
        self.show_et_options = Pmw.OptionMenu(self.frame2,  # group.interior(),
                                              labelpos='w',
                                              label_text='Show ET residues',
                                              items=self.et_options_tuple,
                                              initialitem=self.et_options_tuple[defaultetoption],
                                              command=self.change_options
                                              )

        self.show_et_options.grid(column=0, row=0)

        self.notebook = Pmw.NoteBook(group.interior())

        self.balloon = Pmw.Balloon(group.interior())
        self.balloon.bind(self.slider,
                          "Use this slider to vary the selection of ET residues for all chains in the complex.")
        self.balloon.bind(self.show_et_options,
                          "Select display style to distinguish ET residue selections from the rest of the structure")

        for entry in (self.slider,
                      self.frame2):
            entry.pack(fill='x', padx=4, pady=1)  # vertical
        self.notebook.pack(fill='x', expand=1, padx=4, pady=1)

    def mousewheel_roll(self, event):
        slideval = self.slider.get()
        # See http://www.daniweb.com/code/snippet217059.html
        if event.num == 4 or event.delta == -120:
            self.slider.set(slideval + 1)
            self.slider_button_release(None)
        if event.num == 5 or event.delta == 120:
            self.slider.set(slideval - 1)
            self.slider_button_release(None)
        # print event.num, event.delta, slideval

    def mousebutton_click(self, event):
        # slideval=self.slider.get()
        if event.num == 1 or event.num == 2:
            self.slider_button_release(None)
        # print event.num, event.delta, slideval

    def slider_button_release(self, event):
        slideval = self.slider.get()
        for slave in self.slavelist:
            slave.slider.set(slideval)
            slave.slider_button_release(None)

    def change_options(self, event):
        for slave in self.slavelist:
            slave.show_et_options.setvalue(self.show_et_options.getvalue())
            slave.slider_button_release(None)


#####################################################################
# Group for computing the ET coupling z-score
# This control interacts with two ETControlGroup pages (ET1 and ET2)
class CouplingGroup:
    # Reuse some functions useful for calculating coupling z-score
    calcDist = _et_calcDist
    computeInterface = _et_computeInterface
    computeCoupling = _et_computeCoupling

    def __init__(self,
                 page,
                 trace1,
                 trace2,
                 groupname='Coupling z-score',
                 et_messagebox=None):
        group = Pmw.Group(page, tag_text=groupname)
        self.et_messagebox = et_messagebox
        self.trace1 = trace1
        self.trace2 = trace2
        group.pack(fill='both', expand=1, padx=10, pady=5)
        self.zscore_buttonbox = Pmw.ButtonBox(group.interior(), padx=0)
        self.zscore_buttonbox.add('Update interface', command=self.computeInterface_cb)
        self.zscore_buttonbox.add('Compute coupling z-score', command=self.showZScores)
        self.scw = Pmw.EntryField(group.interior(),
                                  labelpos='w',
                                  label_text='Number of ET couples, c: ',
                                  value='',
                                  )
        self.scw_ave = Pmw.EntryField(group.interior(),
                                      labelpos='w',
                                      label_text='<c> average: ',
                                      value='',
                                      )
        self.scw_stdev = Pmw.EntryField(group.interior(),
                                        labelpos='w',
                                        label_text='c stdev: ',
                                        value='',
                                        )
        self.zscore = Pmw.EntryField(group.interior(),
                                     labelpos='w',
                                     label_text='z-score: ',
                                     value='',
                                     )

        for entry in (self.zscore_buttonbox,
                      self.zscore,
                      self.scw,
                      self.scw_ave,
                      self.scw_stdev):
            entry.pack(fill='x', padx=4, pady=1)  # vertical

        self.balloon = Pmw.Balloon(group.interior())
        self.balloon.bind(self.zscore_buttonbox.button(0),
                          "Recompute the interface between structures in ET1 and ET2")
        self.balloon.bind(self.zscore_buttonbox.button(1),
                          "Click to start computing the ET coupling z-score using ET residue selections in ET1 and ET2")
        self.balloon.bind(self.zscore, "(c-<c>)/stdev")

        self.interfaceOK = False  # Deprecated

        self.zscore_buttonbox.button(0).configure(state=DISABLED)  # Disable Update interface for now
        self.zscore_button_bg = self.zscore_buttonbox.button(1).cget('background')
        self.zscore_button_state = self.zscore_buttonbox.button(1).cget('state')

    def computeInterface_cb(self):
        # self.interfaceOK=False
        if self.trace1.ranksStructureOK and self.trace2.ranksStructureOK:
            if len(self.trace1.chainindicator) > 0:
                structurename1 = '%s and chain %s' % (self.trace1.structurename,
                                                      self.trace1.chainindicator)
            else:
                structurename1 = self.trace1.structurename
            if len(self.trace2.chainindicator) > 0:
                structurename2 = '%s and chain %s' % (self.trace2.structurename,
                                                      self.trace2.chainindicator)
            else:
                structurename2 = self.trace2.structurename
            print("Computing adjacency matrix for protein-protein interface A(i,j)...")
            self._A1, self._A2, ResAtoms1, ResAtoms2 = self.computeInterface(structurename1,
                                                                             structurename2,
                                                                             CONTACT_DISTANCE2)
            if self._A1 == None:
                return
            else:
                # self.interfaceOK=True
                print("%d interface residues in %s" % (len(self._A1), structurename1))
                print("%d interface residues in %s" % (len(self._A2), structurename2))
                print("Adjacency lists:")
                print(structurename1, self._A1)
                print(structurename2, self._A2)
            print("... done")
        else:
            print("Must provide structures and map trace results first!")

    if _ET_THREADING:
        # Threading as a response to reviewers 08/24/10
        def showZScores(self):
            if self.trace1.ranksStructureOK and self.trace2.ranksStructureOK:
                self.zscore.setvalue('')
                self.scw.setvalue('')
                self.scw_ave.setvalue('')
                self.scw_stdev.setvalue('')
                self.zscore_buttonbox.button(1).configure(state=DISABLED)
                # TODO: These may get enabled by another thread, like
                # the z-score computations in ET1 or ET2
                self.trace1.map_buttonbox.button(0).configure(state=DISABLED,
                                                              background='red')
                self.trace1.load_buttonbox.button(0).configure(state=DISABLED,
                                                               background='red')
                self.trace2.map_buttonbox.button(0).configure(state=DISABLED,
                                                              background='red')
                self.trace2.load_buttonbox.button(0).configure(state=DISABLED,
                                                               background='red')
                self.zscoreBusyDoneEvent = Event()
                self.zscoreDoneEvent = Event()
                tm = Thread(target=self.zscoreBusyMessage)
                tm.start()
                tz = Thread(target=self.showZScores_run)
                tz.start()
            else:
                self.zscore.setvalue('')
                self.scw.setvalue('')
                self.scw_ave.setvalue('')
                self.scw_stdev.setvalue('')
                print("Must provide structures and map trace results in ET1 and ET2!")
    else:
        def showZScores(self):
            if self.trace1.ranksStructureOK and self.trace2.ranksStructureOK:
                self.zscore.setvalue('')
                self.scw.setvalue('')
                self.scw_ave.setvalue('')
                self.scw_stdev.setvalue('')

                try:
                    self.et_messagebox.show()
                    # if not self.interfaceOK:
                    if self.trace1.recomputeInterface == False and \
                            self.trace2.recomputeInterface == False:
                        # Interface between ET1 and ET2 structures was previously computed
                        # after a "Map ranks to structure" on ET1 and ET2
                        pass
                    else:
                        self.trace1.recomputeInterface = False
                        self.trace2.recomputeInterface = False
                        self.computeInterface_cb()

                    print("Computing coupling z-scores using structures \'%s\' and \'%s\'..." % (
                        self.trace1.structurename + self.trace1.chainindicator,
                    self.trace2.structurename + self.trace2.chainindicator))
                    # et1 and et2 are list of ints of residue numbers
                    et1 = self.trace1.getETselection()
                    et2 = self.trace2.getETselection()
                    print("ET selections", et1, et2)
                    if 1:  # if len(et1)>0 and len(et2)>0:
                        L1 = self.trace1.ranksObj.getSize()
                        L2 = self.trace2.ranksObj.getSize()
                        w, w_ave, w_stdev = self.computeCoupling(et1, et2, L1, L2, self._A1)
                        self.scw.setvalue(w)
                        self.scw_ave.setvalue(w_ave)
                        self.scw_stdev.setvalue(w_stdev)
                        if w_stdev > 0:
                            self.zscore.setvalue((w - w_ave) / w_stdev)
                        else:
                            self.zscore.setvalue('NA')

                    print("...done")

                except Exception as detail:
                    print('An exception occurred in showZScores!')
                    print(detail)

                self.et_messagebox.withdraw()
            else:
                self.zscore.setvalue('')
                self.scw.setvalue('')
                self.scw_ave.setvalue('')
                self.scw_stdev.setvalue('')
                print("Must provide structures and map trace results in ET1 and ET2!")

    def zscoreBusyMessage(self):
        tog = True
        # Loop until showZScores_run finishes with calculation
        while not self.zscoreDoneEvent.isSet():
            if tog:
                self.zscore_buttonbox.button(1).configure(background='green')
                # self.zscore.setvalue('PROCESSING...')
            else:
                self.zscore_buttonbox.button(1).configure(background=self.zscore_button_bg)
                # self.zscore.setvalue('')
            tog = not tog
            time.sleep(0.5)
        # Notify showZScores_run that pulsing message has ended
        self.zscoreBusyDoneEvent.set()

    def showZScores_run(self):
        # if self.trace1.ranksStructureOK and self.trace2.ranksStructureOK:
        try:
            # if not self.interfaceOK:
            if self.trace1.recomputeInterface == False and \
                    self.trace2.recomputeInterface == False:
                # Interface between ET1 and ET2 structures was previously computed
                # after a "Map ranks to structure" on ET1 and ET2
                pass
            else:
                self.trace1.recomputeInterface = False
                self.trace2.recomputeInterface = False
                self.computeInterface_cb()
            ##                print "Computing adjacency matrix for protein-protein interface A(i,j)..."
            ##                self._A1,self._A2,ResAtoms1,ResAtoms2=self.computeInterface(self.trace1.structurename,
            ##                                                                            self.trace2.structurename,
            ##                                                                            CONTACT_DISTANCE2) #(4 A)^2
            ##                if self._A1==None:
            ##                    return
            ##                else:
            ##                    self.interfaceOK=True
            ##                    print "%d interface residues in %s" % (len(self._A1),self.trace1.structurename)
            ##                    print "%d interface residues in %s" % (len(self._A2),self.trace2.structurename)
            ##                    print "Adjacency lists:"
            ##                    print self.trace1.structurename, self._A1
            ##                    print self.trace2.structurename, self._A2
            ##                print "... done"

            print("Computing coupling z-scores using structures \'%s\' and \'%s\'..." % (
            self.trace1.structurename + self.trace1.chainindicator,
            self.trace2.structurename + self.trace2.chainindicator))
            # et1 and et2 are list of ints of residue numbers
            et1 = self.trace1.getETselection()
            et2 = self.trace2.getETselection()
            print("ET selections", et1, et2)
            if 1:  # if len(et1)>0 and len(et2)>0:
                L1 = self.trace1.ranksObj.getSize()
                L2 = self.trace2.ranksObj.getSize()
                w, w_ave, w_stdev = self.computeCoupling(et1, et2, L1, L2, self._A1)
                self.scw.setvalue(w)
                self.scw_ave.setvalue(w_ave)
                self.scw_stdev.setvalue(w_stdev)
                if w_stdev > 0:
                    self.zscore.setvalue((w - w_ave) / w_stdev)
                else:
                    self.zscore.setvalue('NA')

            print("...done")

        except Exception as detail:
            print('An exception occurred in showZScores_run!')
            print(detail)
            # self.markDoneEvent.set()

        self.zscoreDoneEvent.set()
        self.zscoreBusyDoneEvent.wait(5)
        ##        self.zscore.setvalue('')
        ##        self.scw.setvalue('')
        ##        self.scw_ave.setvalue('')
        ##        self.scw_stdev.setvalue('')
        self.zscore_buttonbox.button(1).configure(state=self.zscore_button_state,
                                                  background=self.zscore_button_bg)
        self.trace1.map_buttonbox.button(0).configure(state=self.zscore_button_state,
                                                      background=self.zscore_button_bg)
        self.trace1.load_buttonbox.button(0).configure(state=self.zscore_button_state,
                                                       background=self.zscore_button_bg)
        self.trace2.map_buttonbox.button(0).configure(state=self.zscore_button_state,
                                                      background=self.zscore_button_bg)
        self.trace2.load_buttonbox.button(0).configure(state=self.zscore_button_state,
                                                       background=self.zscore_button_bg)


# Modify CouplingGroup to have selectable list of traces
class CouplingGroup2:
    # Reuse some functions useful for calculating coupling z-score
    calcDist = _et_calcDist
    computeInterface = _et_computeInterface
    computeCoupling = _et_computeCoupling

    def __init__(self,
                 page,
                 tracelist=[],
                 groupname='Coupling z-score',
                 et_messagebox=None):
        group = Pmw.Group(page, tag_text=groupname)
        self.et_messagebox = et_messagebox
        self.tracelist = tracelist
        group.pack(fill='both', expand=1, padx=10, pady=5)
        self.zscore_buttonbox = Pmw.ButtonBox(group.interior(), padx=0)
        self.zscore_buttonbox.add('Update interface', command=self.computeInterface_cb)
        self.zscore_buttonbox.add('Compute coupling z-score', command=self.showZScores)
        self.scw = Pmw.EntryField(group.interior(),
                                  labelpos='w',
                                  label_text='Number of ET couples, c: ',
                                  value='',
                                  )
        self.scw_ave = Pmw.EntryField(group.interior(),
                                      labelpos='w',
                                      label_text='<c> average: ',
                                      value='',
                                      )
        self.scw_stdev = Pmw.EntryField(group.interior(),
                                        labelpos='w',
                                        label_text='c stdev: ',
                                        value='',
                                        )
        self.zscore = Pmw.EntryField(group.interior(),
                                     labelpos='w',
                                     label_text='z-score: ',
                                     value='',
                                     )

        self.tracesframe = tkinter.Frame(group.interior())
        self.traces_tuple = tuple([t.groupname for t in self.tracelist])
        self.show_traces_options1 = Pmw.OptionMenu(self.tracesframe,  # group.interior(),
                                                   labelpos='w',
                                                   label_text='Select 1st trace',
                                                   items=self.traces_tuple,
                                                   initialitem=self.traces_tuple[0],
                                                   # command=self.slider_button_release
                                                   )
        self.show_traces_options2 = Pmw.OptionMenu(self.tracesframe,  # group.interior(),
                                                   labelpos='w',
                                                   label_text='Select 2nd trace',
                                                   items=self.traces_tuple,
                                                   initialitem=self.traces_tuple[1],
                                                   # command=self.slider_button_release
                                                   )
        self.show_traces_options1.grid(column=0, row=0)
        self.show_traces_options2.grid(column=1, row=0)

        for entry in (self.tracesframe,
                      self.zscore_buttonbox,
                      self.zscore,
                      self.scw,
                      self.scw_ave,
                      self.scw_stdev):
            entry.pack(fill='x', padx=4, pady=1)  # vertical

        self.balloon = Pmw.Balloon(group.interior())
        self.balloon.bind(self.zscore_buttonbox.button(0),
                          "Recompute the interface between structures")
        self.balloon.bind(self.zscore_buttonbox.button(1),
                          "Click to start computing the ET coupling z-score using ET residue selections in 1st and 2nd trace")
        self.balloon.bind(self.zscore, "(c-<c>)/stdev")

        self.interfaceOK = False  # Deprecated

        self.zscore_buttonbox.button(0).configure(state=DISABLED)  # Disable Update interface for now
        self.zscore_button_bg = self.zscore_buttonbox.button(1).cget('background')
        self.zscore_button_state = self.zscore_buttonbox.button(1).cget('state')

    def computeInterface_cb(self):
        # self.interfaceOK=False
        self.trace1 = self.tracelist[self.show_traces_options1.index(Pmw.SELECT)]
        self.trace2 = self.tracelist[self.show_traces_options2.index(Pmw.SELECT)]
        if self.trace1 == self.trace2:
            print("Cannot compute the coupling z-score for identical structures!")
            return
        if self.trace1.ranksStructureOK and self.trace2.ranksStructureOK:
            if len(self.trace1.chainindicator) > 0:
                structurename1 = '%s and chain %s' % (self.trace1.structurename,
                                                      self.trace1.chainindicator)
            else:
                structurename1 = self.trace1.structurename
            if len(self.trace2.chainindicator) > 0:
                structurename2 = '%s and chain %s' % (self.trace2.structurename,
                                                      self.trace2.chainindicator)
            else:
                structurename2 = self.trace2.structurename
            print("Computing adjacency matrix for protein-protein interface A(i,j)...")
            self._A1, self._A2, ResAtoms1, ResAtoms2 = self.computeInterface(structurename1,
                                                                             structurename2,
                                                                             CONTACT_DISTANCE2)
            if self._A1 == None:
                return
            else:
                # self.interfaceOK=True
                print("%d interface residues in %s" % (len(self._A1), structurename1))
                print("%d interface residues in %s" % (len(self._A2), structurename2))
                print("Adjacency lists:")
                print(structurename1, self._A1)
                print(structurename2, self._A2)
            print("... done")
        else:
            print("Must provide structures and map trace results first!")

    def showZScores(self):
        self.trace1 = self.tracelist[self.show_traces_options1.index(Pmw.SELECT)]
        self.trace2 = self.tracelist[self.show_traces_options2.index(Pmw.SELECT)]
        if self.trace1 == self.trace2:
            print("Cannot compute the coupling z-score for identical structures!")
            return
        if self.trace1.ranksStructureOK and self.trace2.ranksStructureOK:
            self.zscore.setvalue('')
            self.scw.setvalue('')
            self.scw_ave.setvalue('')
            self.scw_stdev.setvalue('')

            try:
                self.et_messagebox.show()
                # if not self.interfaceOK:
                ##                if self.trace1.recomputeInterface==False and \
                ##                   self.trace2.recomputeInterface==False:
                ##                    #Interface between ET1 and ET2 structures was previously computed
                ##                    #after a "Map ranks to structure" on ET1 and ET2
                ##                    pass
                ##                else:
                ##                    self.trace1.recomputeInterface=False
                ##                    self.trace2.recomputeInterface=False
                if 1:
                    self.computeInterface_cb()

                print("Computing coupling z-scores using structures \'%s\' and \'%s\'..." % (
                self.trace1.structurename + self.trace1.chainindicator,
                self.trace2.structurename + self.trace2.chainindicator))
                # et1 and et2 are list of ints of residue numbers
                et1 = self.trace1.getETselection()
                et2 = self.trace2.getETselection()
                print("ET selections", et1, et2)
                if 1:  # if len(et1)>0 and len(et2)>0:
                    L1 = self.trace1.ranksObj.getSize()
                    L2 = self.trace2.ranksObj.getSize()
                    w, w_ave, w_stdev = self.computeCoupling(et1, et2, L1, L2, self._A1)
                    self.scw.setvalue(w)
                    self.scw_ave.setvalue(w_ave)
                    self.scw_stdev.setvalue(w_stdev)
                    if w_stdev > 0:
                        self.zscore.setvalue((w - w_ave) / w_stdev)
                    else:
                        self.zscore.setvalue('NA')

                print("...done")

            except Exception as detail:
                print('An exception occurred in showZScores!')
                print(detail)

            self.et_messagebox.withdraw()
        else:
            self.zscore.setvalue('')
            self.scw.setvalue('')
            self.scw_ave.setvalue('')
            self.scw_stdev.setvalue('')
            print("Must provide structures and map trace results in 1st and 2nd trace!")

    def showZScores_run(self):
        # if self.trace1.ranksStructureOK and self.trace2.ranksStructureOK:
        try:
            # if not self.interfaceOK:
            if self.trace1.recomputeInterface == False and \
                    self.trace2.recomputeInterface == False:
                # Interface between ET1 and ET2 structures was previously computed
                # after a "Map ranks to structure" on ET1 and ET2
                pass
            else:
                self.trace1.recomputeInterface = False
                self.trace2.recomputeInterface = False
                self.computeInterface_cb()
            ##                print "Computing adjacency matrix for protein-protein interface A(i,j)..."
            ##                self._A1,self._A2,ResAtoms1,ResAtoms2=self.computeInterface(self.trace1.structurename,
            ##                                                                            self.trace2.structurename,
            ##                                                                            CONTACT_DISTANCE2) #(4 A)^2
            ##                if self._A1==None:
            ##                    return
            ##                else:
            ##                    self.interfaceOK=True
            ##                    print "%d interface residues in %s" % (len(self._A1),self.trace1.structurename)
            ##                    print "%d interface residues in %s" % (len(self._A2),self.trace2.structurename)
            ##                    print "Adjacency lists:"
            ##                    print self.trace1.structurename, self._A1
            ##                    print self.trace2.structurename, self._A2
            ##                print "... done"

            print("Computing coupling z-scores using structures \'%s\' and \'%s\'..." % (
            self.trace1.structurename + self.trace1.chainindicator,
            self.trace2.structurename + self.trace2.chainindicator))
            # et1 and et2 are list of ints of residue numbers
            et1 = self.trace1.getETselection()
            et2 = self.trace2.getETselection()
            print("ET selections", et1, et2)
            if 1:  # if len(et1)>0 and len(et2)>0:
                L1 = self.trace1.ranksObj.getSize()
                L2 = self.trace2.ranksObj.getSize()
                w, w_ave, w_stdev = self.computeCoupling(et1, et2, L1, L2, self._A1)
                self.scw.setvalue(w)
                self.scw_ave.setvalue(w_ave)
                self.scw_stdev.setvalue(w_stdev)
                if w_stdev > 0:
                    self.zscore.setvalue((w - w_ave) / w_stdev)
                else:
                    self.zscore.setvalue('NA')

            print("...done")

        except Exception as detail:
            print('An exception occurred in showZScores_run!')
            print(detail)
            # self.markDoneEvent.set()

        self.zscoreDoneEvent.set()
        self.zscoreBusyDoneEvent.wait(5)
        ##        self.zscore.setvalue('')
        ##        self.scw.setvalue('')
        ##        self.scw_ave.setvalue('')
        ##        self.scw_stdev.setvalue('')
        self.zscore_buttonbox.button(1).configure(state=self.zscore_button_state,
                                                  background=self.zscore_button_bg)
        self.trace1.map_buttonbox.button(0).configure(state=self.zscore_button_state,
                                                      background=self.zscore_button_bg)
        self.trace1.load_buttonbox.button(0).configure(state=self.zscore_button_state,
                                                       background=self.zscore_button_bg)
        self.trace2.map_buttonbox.button(0).configure(state=self.zscore_button_state,
                                                      background=self.zscore_button_bg)
        self.trace2.load_buttonbox.button(0).configure(state=self.zscore_button_state,
                                                       background=self.zscore_button_bg)


###########################################################################

# Group for computing the z-score of the
# Selection Clustering Weight (SCW) (Mihalek,Res,Yao,Lichtarge,JMB,2003)
class SCWGroup:
    # Reuse some functions useful for calculating clustering z-score
    calcDist = _et_calcDist
    computeAdjacency = _et_computeAdjacency

    # calcZScore=_et_calcZScore

    def __init__(self,
                 page,
                 groupname='Selection Clustering Weight (SCW)',
                 defaultstructurename='(pdb)',
                 defaultselectionname='(sele)',
                 defaultqualitymeasure=0):
        group = Pmw.Group(page, tag_text=groupname)
        group.pack(fill='both', expand=1, padx=10, pady=5)
        # Field for entering name of structure or model
        self.structure = Pmw.EntryField(group.interior(),
                                        labelpos='w',
                                        label_text='structure to use: ',
                                        value=defaultstructurename,
                                        )
        self.selection = Pmw.EntryField(group.interior(),
                                        labelpos='w',
                                        label_text='residue selection to use: ',
                                        value=defaultselectionname,
                                        )
        self.qualitymeasure_options_tuple = ('3D nobias',
                                             '3D bias (j-i)')
        self.show_qualitymeasure_options = Pmw.OptionMenu(group.interior(),
                                                          labelpos='w',
                                                          label_text='quality measure: ',
                                                          items=self.qualitymeasure_options_tuple,
                                                          initialitem=self.qualitymeasure_options_tuple[
                                                              defaultqualitymeasure],
                                                          )

        self.zscore_buttonbox = Pmw.ButtonBox(group.interior(), padx=0)
        self.zscore_buttonbox.add('Compute adjacency', command=self.computeAdjacency_cb)
        self.zscore_buttonbox.add('Compute z-score', command=self.showZScores)
        self.scw = Pmw.EntryField(group.interior(),
                                  labelpos='w',
                                  label_text='SCW: ',
                                  value='',
                                  )
        self.scw_ave = Pmw.EntryField(group.interior(),
                                      labelpos='w',
                                      label_text='<SCW> average: ',
                                      value='',
                                      )
        self.scw_stdev = Pmw.EntryField(group.interior(),
                                        labelpos='w',
                                        label_text='SCW stdev: ',
                                        value='',
                                        )
        self.zscore = Pmw.EntryField(group.interior(),
                                     labelpos='w',
                                     label_text='z-score: ',
                                     value='',
                                     )

        for entry in (self.structure,
                      self.selection,
                      self.show_qualitymeasure_options,
                      self.zscore_buttonbox,
                      self.zscore,
                      self.scw,
                      self.scw_ave,
                      self.scw_stdev):
            entry.pack(fill='x', padx=4, pady=1)  # vertical

        self.adjacencyOK = False

    def computeAdjacency_cb(self):
        self.adjacencyOK = False
        self.structurename = self.structure.getvalue().strip()
        print("Computing adjacency matrix A(i,j)...")
        self._A, ResAtoms, self._Ar = self.computeAdjacency(self.structurename)
        if self._A == None:
            return
        else:
            self.adjacencyOK = True
            self.numres = len(ResAtoms)
        print("... done")

    def showZScores(self):
        if 1:
            structurename = self.structure.getvalue().strip()
            if self.show_qualitymeasure_options.getvalue() == self.qualitymeasure_options_tuple[0]:
                bias = 0
            elif self.show_qualitymeasure_options.getvalue() == self.qualitymeasure_options_tuple[1]:
                bias = 1
            if not self.adjacencyOK or structurename != self.structurename:
                self.structurename = structurename
                self.adjacencyOK = False
                print("Computing adjacency matrix A(i,j)...")
                self._A, ResAtoms, self._Ar = self.computeAdjacency(structurename)
                if self._A == None:
                    return
                else:
                    self.adjacencyOK = True
                    self.numres = len(ResAtoms)
                print("... done")

            # Get the list of selected residues
            selectionname = self.selection.getvalue().strip()
            try:
                selemodel = cmd.get_model(selectionname)
            except Exception:
                print("Selection \'%s\' does not exist!" % selectionname)
                # return #Temporary? So we can have "4ch9 and chain B" in the structurename 09/02/14


            SeleResAtoms = {}  # List of atom coordinates indexed by residue number
            # atom.resi is a string, convert it to int
            for i in range(len(selemodel.atom)):
                atom = selemodel.atom[i]
                try:
                    SeleResAtoms[int(atom.resi)].append(atom.coord)
                except KeyError:
                    SeleResAtoms[int(atom.resi)] = [atom.coord]

            print("Computing z-scores using structure \'%s\' and selection \'%s\'..." % (structurename,
                                                                                         selectionname))
            print("Residue list = ", list(SeleResAtoms.keys()))
            zscore, w, w_ave, w_stdev = self.calcZScore(list(SeleResAtoms.keys()), self.numres, bias)
            self.scw.setvalue(w)
            self.scw_ave.setvalue(w_ave)
            self.scw_stdev.setvalue(w_stdev)
            self.zscore.setvalue(zscore)
            print("...done")

    # Calculate z-score (z_S) for residue selection reslist=[1,2,...]
    # z_S = (w-<w>_S)/sigma_S
    # The steps are:
    # 1. Calculate Selection Clustering Weight (SCW) 'w'
    # 2. Calculate mean SCW (<w>_S) in the ensemble of random
    #   selections of len(reslist) residues
    # 3. Calculate mean square SCW (<w^2>_S) and standard deviation (sigma_S)
    # Reference: Mihalek, Res, Yao, Lichtarge (2003)
    def calcZScore(self, reslist, L, bias=1):
        A = self._A
        # Calculate w
        w = 0
        if bias == 1:
            for resi in reslist:
                for resj in reslist:
                    if resi < resj:
                        try:
                            Aij = A[resi][resj]  # A(i,j)==1
                            w += (resj - resi)
                        except KeyError:
                            pass
        elif bias == 0:
            for resi in reslist:
                for resj in reslist:
                    if resi < resj:
                        try:
                            Aij = A[resi][resj]  # A(i,j)==1
                            w += 1
                        except KeyError:
                            pass

        # Calculate <w>_S and <w^2>_S.
        # Use expressions (3),(4),(5),(6) in Reference.
        M = len(reslist)
        # L=self.numres
        pi1 = M * (M - 1.0) / (L * (L - 1.0))
        pi2 = pi1 * (M - 2.0) / (L - 2.0)
        pi3 = pi2 * (M - 3.0) / (L - 3.0)
        w_ave = 0
        w2_ave = 0
        if bias == 1:
            for resi, neighborsj in list(A.items()):
                for resj in neighborsj:
                    w_ave += (resj - resi)
                    for resk, neighborsl in list(A.items()):
                        for resl in neighborsl:
                            if (resi == resk and resj == resl) or \
                                    (resi == resl and resj == resk):
                                w2_ave += pi1 * (resj - resi) * (resl - resk)
                            elif (resi == resk) or (resj == resl) or \
                                    (resi == resl) or (resj == resk):
                                w2_ave += pi2 * (resj - resi) * (resl - resk)
                            else:
                                w2_ave += pi3 * (resj - resi) * (resl - resk)
        elif bias == 0:
            for resi, neighborsj in list(A.items()):
                w_ave += len(neighborsj)
                for resj in neighborsj:
                    # w_ave+=1
                    for resk, neighborsl in list(A.items()):
                        for resl in neighborsl:
                            if (resi == resk and resj == resl) or \
                                    (resi == resl and resj == resk):
                                w2_ave += pi1
                            elif (resi == resk) or (resj == resl) or \
                                    (resi == resl) or (resj == resk):
                                w2_ave += pi2
                            else:
                                w2_ave += pi3
        w_ave = w_ave * pi1
        # print "w_ave", w_ave
        sigma = math.sqrt(w2_ave - w_ave * w_ave)
        # print "sigma", sigma

        return (w - w_ave) / sigma, w, w_ave, sigma


class InterfaceControlGroup:
    computeInterface = _et_computeInterface
    calcDist = _et_calcDist

    def __init__(self,
                 page,
                 groupname='Protein-protein interface',
                 defaultstructurename1='(pdb)',
                 defaultstructurename2='(pdb)',
                 defaultchain1='',
                 defaultchain2='',
                 defaultdistance=4,
                 interfacecolor1='red',
                 interfacecolor2='red',
                 defaultinterfaceoption1=0,
                 defaultinterfaceoption2=0,
                 et_messagebox=None):
        self.interfacecolor1 = interfacecolor1
        self.interfacecolor2 = interfacecolor2
        self.et_messagebox = et_messagebox

        group = Pmw.Group(page, tag_text=groupname)
        group.pack(fill='both', expand=1, padx=10, pady=5)

        self.structureframe1 = tkinter.Frame(group.interior())
        # Field for entering name of structure or model
        self.structure1 = Pmw.EntryField(self.structureframe1,
                                         labelpos='w',
                                         label_text='structure 1: ',
                                         value=defaultstructurename1,
                                         )
        self.chain1 = Pmw.EntryField(self.structureframe1,
                                     labelpos='w',
                                     label_text='chain: ',
                                     value=defaultchain1,
                                     )
        self.structure1.grid(column=0, row=0)
        self.chain1.grid(column=1, row=0)

        self.structureframe2 = tkinter.Frame(group.interior())
        self.structure2 = Pmw.EntryField(self.structureframe2,
                                         labelpos='w',
                                         label_text='structure 2: ',
                                         value=defaultstructurename2,
                                         )
        self.chain2 = Pmw.EntryField(self.structureframe2,
                                     labelpos='w',
                                     label_text='chain: ',
                                     value=defaultchain2,
                                     )
        self.structure2.grid(column=0, row=0)
        self.chain2.grid(column=1, row=0)

        self.distance = Pmw.EntryField(group.interior(),
                                       labelpos='w',
                                       label_text='distance to use for interface: ',
                                       validate={'validator': 'real', 'min': 1, 'max': 20},
                                       value=defaultdistance,
                                       )

        self.mark_buttonbox = Pmw.ButtonBox(group.interior(), padx=0)
        self.mark_buttonbox.add('Mark interface', command=self.markInterface)

        self.interface_options_tuple1 = ('Colors only(' + self.interfacecolor1 + ')',
                                         'as Spheres',
                                         'as Sticks',
                                         'as Selection',
                                         'None')
        self.interface_options_tuple2 = ('Colors only(' + self.interfacecolor2 + ')',
                                         'as Spheres',
                                         'as Sticks',
                                         'as Selection',
                                         'None')
        self.show_interface_options1 = Pmw.OptionMenu(group.interior(),
                                                      labelpos='w',
                                                      label_text='Show interface residues (structure 1)',
                                                      items=self.interface_options_tuple1,
                                                      initialitem=self.interface_options_tuple1[
                                                          defaultinterfaceoption1],
                                                      command=self.updateInterface  # Need two arguments for this method
                                                      )
        self.show_interface_options2 = Pmw.OptionMenu(group.interior(),
                                                      labelpos='w',
                                                      label_text='Show interface residues (structure 2)',
                                                      items=self.interface_options_tuple2,
                                                      initialitem=self.interface_options_tuple2[
                                                          defaultinterfaceoption2],
                                                      command=self.updateInterface
                                                      )

        for entry in (self.structureframe1,
                      self.structureframe2,
                      self.distance,
                      self.mark_buttonbox,
                      self.show_interface_options1,
                      self.show_interface_options2,
                      ):
            entry.pack(fill='x', padx=4, pady=1)  # vertical

        self.balloon = Pmw.Balloon(group.interior())
        self.balloon.bind(self.structure1,
                          "Enter a name from the list of objects in the PyMOL Viewer")
        self.balloon.bind(self.structure2,
                          "Enter a name from the list of objects in the PyMOL Viewer")
        self.balloon.bind(self.distance,
                          "Maximum inter-residue atom-atom distance (Angstroms)")
        self.balloon.bind(self.mark_buttonbox,
                          "Click to start interface calculation and display")
        self.balloon.bind(self.show_interface_options1,
                          "Select display style to distinguish interface residues from the rest of structure 1")
        self.balloon.bind(self.show_interface_options2,
                          "Select display style to distinguish interface residues from the rest of structure 2")

        self.interfaceComputed = False

        self.mark_button_bg = self.mark_buttonbox.button(0).cget('background')
        self.mark_button_state = self.mark_buttonbox.button(0).cget('state')

    if _ET_THREADING:
        # Threading as a response to reviewers 08/24/10
        def markInterface(self):
            self.interfaceComputed = False
            supper1 = self.structure1.getvalue().strip().upper()
            supper2 = self.structure2.getvalue().strip().upper()
            for n in cmd.get_names('all'):
                if supper1 == n.upper():
                    break
            else:
                print('structure 1 name must be in PyMOL viewer list:')
                print(cmd.get_names('all'))
                return
            for n in cmd.get_names('all'):
                if supper2 == n.upper():
                    break
            else:
                print('structure 2 name must be in PyMOL viewer list:')
                print(cmd.get_names('all'))
                return

            if 1:
                self.mark_buttonbox.button(0).configure(state=DISABLED)
                self.markBusyDoneEvent = Event()
                self.markDoneEvent = Event()
                tm = Thread(target=self.markBusyMessage)
                tm.start()
                tz = Thread(target=self.markInterface_run)
                tz.start()
    else:
        def markInterface(self):
            try:
                self.interfaceComputed = False
                supper1 = self.structure1.getvalue().strip().upper()
                supper2 = self.structure2.getvalue().strip().upper()
                for n in cmd.get_names('all'):
                    if supper1 == n.upper():
                        break
                else:
                    print('structure 1 name must be in PyMOL viewer list:')
                    print(cmd.get_names('all'))
                    return
                for n in cmd.get_names('all'):
                    if supper2 == n.upper():
                        break
                else:
                    print('structure 2 name must be in PyMOL viewer list:')
                    print(cmd.get_names('all'))
                    return

                chainindicator1 = self.chain1.getvalue().strip()
                self.s1 = self.structure1.getvalue().strip()  # Save these
                self.c1 = chainindicator1
                if len(chainindicator1) > 0:
                    structurename1 = self.s1 + ' and chain %s' % chainindicator1
                else:
                    structurename1 = self.s1
                chainindicator2 = self.chain2.getvalue().strip()
                self.s2 = self.structure2.getvalue().strip()
                self.c2 = chainindicator2
                if len(chainindicator2) > 0:
                    structurename2 = self.s2 + ' and chain %s' % chainindicator2
                else:
                    structurename2 = self.s2

                dist = float(self.distance.getvalue().strip())

                if len(self.structure1.getvalue().strip()) < 1:
                    print('Provide structure 1 name!')
                    return
                if len(self.structure2.getvalue().strip()) < 1:
                    print('Provide structure 2 name!')
                    return

                self.et_messagebox.show()

                # In self.interface1, each residue indexes into an array of neighboring residues in the other structure
                (self.interface1,
                 self.interface2,
                 self.ResAtoms1,
                 self.ResAtoms2) = self.computeInterface(structurename1, structurename2, dist * dist)

                if (self.interface1 is None) or (self.interface2 is None):
                    self.interfaceComputed = False
                    self.et_messagebox.withdraw()
                    return

                self.interfaceComputed = True
                print("%d interface residues in %s" % (len(self.interface1), structurename1))
                print("%d interface residues in %s" % (len(self.interface2), structurename2))
                print("Adjacency lists:")
                print(structurename1, self.interface1)
                print(structurename2, self.interface2)

                self.updateInterface(None)

            except Exception as detail:
                print('An exception occurred in markInterface!')
                print(detail)
            self.et_messagebox.withdraw()

    def markBusyMessage(self):
        tog = True
        # Loop until markInterface_run finishes with calculation
        while not self.markDoneEvent.isSet():
            if tog:
                self.mark_buttonbox.button(0).configure(background='green')
            else:
                self.mark_buttonbox.button(0).configure(background=self.mark_button_bg)
            tog = not tog
            time.sleep(0.5)
        # Notify markInterface_run that pulsing message has ended
        self.markBusyDoneEvent.set()

    def markInterface_run(self):
        try:
            chainindicator1 = self.chain1.getvalue().strip()
            self.s1 = self.structure1.getvalue().strip()  # Save these
            self.c1 = chainindicator1
            if len(chainindicator1) > 0:
                structurename1 = self.s1 + ' and chain %s' % chainindicator1
            else:
                structurename1 = self.s1
            chainindicator2 = self.chain2.getvalue().strip()
            self.s2 = self.structure2.getvalue().strip()
            self.c2 = chainindicator2
            if len(chainindicator2) > 0:
                structurename2 = self.s2 + ' and chain %s' % chainindicator2
            else:
                structurename2 = self.s2

            dist = float(self.distance.getvalue().strip())

            if len(self.structure1.getvalue().strip()) < 1:
                print('Provide structure 1 name!')
                self.markDoneEvent.set()
                self.markBusyDoneEvent.wait(5)
                self.mark_buttonbox.button(0).configure(state=self.mark_button_state,
                                                        background=self.mark_button_bg)
                return
            if len(self.structure2.getvalue().strip()) < 1:
                print('Provide structure 2 name!')
                self.markDoneEvent.set()
                self.markBusyDoneEvent.wait(5)
                self.mark_buttonbox.button(0).configure(state=self.mark_button_state,
                                                        background=self.mark_button_bg)
                return

            # In self.interface1, each residue indexes into an array of neighboring residues in the other structure
            (self.interface1,
             self.interface2,
             self.ResAtoms1,
             self.ResAtoms2) = self.computeInterface(structurename1, structurename2, dist * dist)

            if (self.interface1 is None) or (self.interface2 is None):
                self.interfaceComputed = False
                self.markDoneEvent.set()
                self.markBusyDoneEvent.wait(5)
                self.mark_buttonbox.button(0).configure(state=self.mark_button_state,
                                                        background=self.mark_button_bg)
                return

            self.interfaceComputed = True
            print("%d interface residues in %s" % (len(self.interface1), structurename1))
            print("%d interface residues in %s" % (len(self.interface2), structurename2))
            print("Adjacency lists:")
            print(structurename1, self.interface1)
            print(structurename2, self.interface2)

            self.updateInterface(None)

        except Exception as detail:
            print('An exception occurred in markInterface_run!')
            print(detail)
            # self.markDoneEvent.set()

        self.markDoneEvent.set()
        self.markBusyDoneEvent.wait(5)
        self.mark_buttonbox.button(0).configure(state=self.mark_button_state,
                                                background=self.mark_button_bg)

    def updateInterface(self, event):
        # TODO: Make this method more aware of changes in the viewer
        if self.interfaceComputed == False:
            return

        ##        s1=self.structure1.getvalue().strip()
        ##        s2=self.structure2.getvalue().strip()
        ##        c1=self.chain1.getvalue().strip()
        ##        c2=self.chain2.getvalue().strip()
        s1 = self.s1
        s2 = self.s2
        c1 = self.c1
        c2 = self.c2

        if len(c1) > 0:
            structurename1 = s1 + ' and chain %s' % c1
        else:
            structurename1 = s1
        if len(c2) > 0:
            structurename2 = s2 + ' and chain %s' % c2
        else:
            structurename2 = s2

        if self.show_interface_options1.getvalue() == self.interface_options_tuple1[0]:
            for res in list(self.interface1.keys()):
                cmd.color(self.interfacecolor1, '%s and resi %s' % (structurename1, res))
        elif self.show_interface_options1.getvalue() == self.interface_options_tuple1[1]:
            for res in list(self.interface1.keys()):
                cmd.show('spheres', '%s and resi %s' % (structurename1, res))
        elif self.show_interface_options1.getvalue() == self.interface_options_tuple1[2]:
            for res in list(self.interface1.keys()):
                cmd.show('sticks', '%s and resi %s' % (structurename1, res))
        elif self.show_interface_options1.getvalue() == self.interface_options_tuple1[3]:
            # Make a selection of the ET residues
            if len(self.interface1) > 0:
                cmd.select('%s_%s_PyETV' % (s1 + c1, s2 + c2),
                           '%s and resi %s' % (structurename1, "+".join(list(map(str, self.interface1)))))
            else:
                cmd.select('%s_%s_PyETV' % (s1 + c1, s2 + c2), 'none')

        if self.show_interface_options2.getvalue() == self.interface_options_tuple2[0]:
            for res in list(self.interface2.keys()):
                cmd.color(self.interfacecolor2, '%s and resi %s' % (structurename2, res))
        elif self.show_interface_options2.getvalue() == self.interface_options_tuple2[1]:
            for res in list(self.interface2.keys()):
                cmd.show('spheres', '%s and resi %s' % (structurename2, res))
        elif self.show_interface_options2.getvalue() == self.interface_options_tuple2[2]:
            for res in list(self.interface2.keys()):
                cmd.show('sticks', '%s and resi %s' % (structurename2, res))
        elif self.show_interface_options2.getvalue() == self.interface_options_tuple2[3]:
            # Make a selection of the ET residues
            if len(self.interface2) > 0:
                cmd.select('%s_%s_PyETV' % (s2 + c2, s1 + c1),
                           '%s and resi %s' % (structurename2, "+".join(list(map(str, self.interface2)))))
            else:
                cmd.select('%s_%s_PyETV' % (s2 + c2, s1 + c1), 'none')


# Compute interface between traces from a selectable list
class InterfaceControlGroup2:
    computeInterface = _et_computeInterface
    calcDist = _et_calcDist

    def __init__(self,
                 page,
                 groupname='Protein-protein interface',
                 tracelist=[],
                 defaultstructurename1='(pdb)',
                 defaultstructurename2='(pdb)',
                 defaultchain1='',
                 defaultchain2='',
                 defaultdistance=4,
                 interfacecolor1='red',
                 interfacecolor2='red',
                 defaultinterfaceoption1=0,
                 defaultinterfaceoption2=0,
                 et_messagebox=None):
        self.interfacecolor1 = interfacecolor1
        self.interfacecolor2 = interfacecolor2
        self.et_messagebox = et_messagebox

        group = Pmw.Group(page, tag_text=groupname)
        group.pack(fill='both', expand=1, padx=10, pady=5)

        self.structureframe1 = tkinter.Frame(group.interior())
        # Field for entering name of structure or model
        self.structure1 = Pmw.EntryField(self.structureframe1,
                                         labelpos='w',
                                         label_text='structure 1: ',
                                         value=defaultstructurename1,
                                         )
        self.chain1 = Pmw.EntryField(self.structureframe1,
                                     labelpos='w',
                                     label_text='chain: ',
                                     value=defaultchain1,
                                     )
        self.structure1.grid(column=0, row=0)
        self.chain1.grid(column=1, row=0)

        self.structureframe2 = tkinter.Frame(group.interior())
        self.structure2 = Pmw.EntryField(self.structureframe2,
                                         labelpos='w',
                                         label_text='structure 2: ',
                                         value=defaultstructurename2,
                                         )
        self.chain2 = Pmw.EntryField(self.structureframe2,
                                     labelpos='w',
                                     label_text='chain: ',
                                     value=defaultchain2,
                                     )
        self.structure2.grid(column=0, row=0)
        self.chain2.grid(column=1, row=0)

        self.distance = Pmw.EntryField(group.interior(),
                                       labelpos='w',
                                       label_text='distance to use for interface: ',
                                       validate={'validator': 'real', 'min': 1, 'max': 20},
                                       value=defaultdistance,
                                       )

        self.mark_buttonbox = Pmw.ButtonBox(group.interior(), padx=0)
        self.mark_buttonbox.add('Mark interface', command=self.markInterface)

        self.interface_options_tuple1 = ('Colors only(' + self.interfacecolor1 + ')',
                                         'as Spheres',
                                         'as Sticks',
                                         'as Selection',
                                         'None')
        self.interface_options_tuple2 = ('Colors only(' + self.interfacecolor2 + ')',
                                         'as Spheres',
                                         'as Sticks',
                                         'as Selection',
                                         'None')
        self.show_interface_options1 = Pmw.OptionMenu(group.interior(),
                                                      labelpos='w',
                                                      label_text='Show interface residues (structure 1)',
                                                      items=self.interface_options_tuple1,
                                                      initialitem=self.interface_options_tuple1[
                                                          defaultinterfaceoption1],
                                                      command=self.updateInterface  # Need two arguments for this method
                                                      )
        self.show_interface_options2 = Pmw.OptionMenu(group.interior(),
                                                      labelpos='w',
                                                      label_text='Show interface residues (structure 2)',
                                                      items=self.interface_options_tuple2,
                                                      initialitem=self.interface_options_tuple2[
                                                          defaultinterfaceoption2],
                                                      command=self.updateInterface
                                                      )

        self.tracelist = tracelist
        self.tracesframe = tkinter.Frame(group.interior())
        self.traces_tuple = tuple([t.groupname for t in self.tracelist])
        self.show_traces_options1 = Pmw.OptionMenu(self.tracesframe,  # group.interior(),
                                                   labelpos='w',
                                                   label_text='Select 1st trace',
                                                   items=self.traces_tuple,
                                                   initialitem=self.traces_tuple[0],
                                                   # command=self.slider_button_release
                                                   )
        self.show_traces_options2 = Pmw.OptionMenu(self.tracesframe,  # group.interior(),
                                                   labelpos='w',
                                                   label_text='Select 2nd trace',
                                                   items=self.traces_tuple,
                                                   initialitem=self.traces_tuple[1],
                                                   # command=self.slider_button_release
                                                   )
        self.show_traces_options1.grid(column=0, row=0)
        self.show_traces_options2.grid(column=1, row=0)

        for entry in (self.tracesframe,
                      # self.structureframe1,
                      # self.structureframe2,
                      self.distance,
                      self.mark_buttonbox,
                      self.show_interface_options1,
                      self.show_interface_options2,
                      ):
            entry.pack(fill='x', padx=4, pady=1)  # vertical

        self.balloon = Pmw.Balloon(group.interior())
        self.balloon.bind(self.structure1,
                          "Enter a name from the list of objects in the PyMOL Viewer")
        self.balloon.bind(self.structure2,
                          "Enter a name from the list of objects in the PyMOL Viewer")
        self.balloon.bind(self.distance,
                          "Maximum inter-residue atom-atom distance (Angstroms)")
        self.balloon.bind(self.mark_buttonbox,
                          "Click to start interface calculation and display")
        self.balloon.bind(self.show_interface_options1,
                          "Select display style to distinguish interface residues from the rest of structure 1")
        self.balloon.bind(self.show_interface_options2,
                          "Select display style to distinguish interface residues from the rest of structure 2")

        self.interfaceComputed = False

        self.mark_button_bg = self.mark_buttonbox.button(0).cget('background')
        self.mark_button_state = self.mark_buttonbox.button(0).cget('state')

    def markInterface(self):
        try:
            self.interfaceComputed = False

            self.trace1 = self.tracelist[self.show_traces_options1.index(Pmw.SELECT)]
            self.trace2 = self.tracelist[self.show_traces_options2.index(Pmw.SELECT)]
            if self.trace1 == self.trace2:
                print("Cannot compute the interface for identical structures!")
                return
            supper1 = self.trace1.structurename.upper()
            supper2 = self.trace2.structurename.upper()
            # supper1=self.structure1.getvalue().strip().upper()
            # supper2=self.structure2.getvalue().strip().upper()
            for n in cmd.get_names('all'):
                if supper1 == n.upper():
                    break
            else:
                print('structure 1 name must be in PyMOL viewer list:')
                print(cmd.get_names('all'))
                return
            for n in cmd.get_names('all'):
                if supper2 == n.upper():
                    break
            else:
                print('structure 2 name must be in PyMOL viewer list:')
                print(cmd.get_names('all'))
                return

            chainindicator1 = self.trace1.chainindicator
            self.s1 = self.trace1.structurename
            # chainindicator1=self.chain1.getvalue().strip()
            # self.s1=self.structure1.getvalue().strip() #Save these
            self.c1 = chainindicator1
            if len(chainindicator1) > 0:
                structurename1 = self.s1 + ' and chain %s' % chainindicator1
            else:
                structurename1 = self.s1
            chainindicator2 = self.trace2.chainindicator
            self.s2 = self.trace2.structurename
            # chainindicator2=self.chain2.getvalue().strip()
            # self.s2=self.structure2.getvalue().strip()
            self.c2 = chainindicator2
            if len(chainindicator2) > 0:
                structurename2 = self.s2 + ' and chain %s' % chainindicator2
            else:
                structurename2 = self.s2

            dist = float(self.distance.getvalue().strip())

            if len(self.structure1.getvalue().strip()) < 1:
                print('Provide structure 1 name!')
                return
            if len(self.structure2.getvalue().strip()) < 1:
                print('Provide structure 2 name!')
                return

            self.et_messagebox.show()

            # In self.interface1, each residue indexes into an array of neighboring residues in the other structure
            (self.interface1,
             self.interface2,
             self.ResAtoms1,
             self.ResAtoms2) = self.computeInterface(structurename1, structurename2, dist * dist)

            if (self.interface1 is None) or (self.interface2 is None):
                self.interfaceComputed = False
                self.et_messagebox.withdraw()
                return

            self.interfaceComputed = True
            print("%d interface residues in %s" % (len(self.interface1), structurename1))
            print("%d interface residues in %s" % (len(self.interface2), structurename2))
            print("Adjacency lists:")
            print(structurename1, self.interface1)
            print(structurename2, self.interface2)

            self.updateInterface(None)

        except Exception as detail:
            print('An exception occurred in markInterface!')
            print(detail)
        self.et_messagebox.withdraw()

    def markInterface_run(self):
        try:
            chainindicator1 = self.chain1.getvalue().strip()
            self.s1 = self.structure1.getvalue().strip()  # Save these
            self.c1 = chainindicator1
            if len(chainindicator1) > 0:
                structurename1 = self.s1 + ' and chain %s' % chainindicator1
            else:
                structurename1 = self.s1
            chainindicator2 = self.chain2.getvalue().strip()
            self.s2 = self.structure2.getvalue().strip()
            self.c2 = chainindicator2
            if len(chainindicator2) > 0:
                structurename2 = self.s2 + ' and chain %s' % chainindicator2
            else:
                structurename2 = self.s2

            dist = float(self.distance.getvalue().strip())

            if len(self.structure1.getvalue().strip()) < 1:
                print('Provide structure 1 name!')
                self.markDoneEvent.set()
                self.markBusyDoneEvent.wait(5)
                self.mark_buttonbox.button(0).configure(state=self.mark_button_state,
                                                        background=self.mark_button_bg)
                return
            if len(self.structure2.getvalue().strip()) < 1:
                print('Provide structure 2 name!')
                self.markDoneEvent.set()
                self.markBusyDoneEvent.wait(5)
                self.mark_buttonbox.button(0).configure(state=self.mark_button_state,
                                                        background=self.mark_button_bg)
                return

            # In self.interface1, each residue indexes into an array of neighboring residues in the other structure
            (self.interface1,
             self.interface2,
             self.ResAtoms1,
             self.ResAtoms2) = self.computeInterface(structurename1, structurename2, dist * dist)

            if (self.interface1 is None) or (self.interface2 is None):
                self.interfaceComputed = False
                self.markDoneEvent.set()
                self.markBusyDoneEvent.wait(5)
                self.mark_buttonbox.button(0).configure(state=self.mark_button_state,
                                                        background=self.mark_button_bg)
                return

            self.interfaceComputed = True
            print("%d interface residues in %s" % (len(self.interface1), structurename1))
            print("%d interface residues in %s" % (len(self.interface2), structurename2))
            print("Adjacency lists:")
            print(structurename1, self.interface1)
            print(structurename2, self.interface2)

            self.updateInterface(None)

        except Exception as detail:
            print('An exception occurred in markInterface_run!')
            print(detail)
            # self.markDoneEvent.set()

        self.markDoneEvent.set()
        self.markBusyDoneEvent.wait(5)
        self.mark_buttonbox.button(0).configure(state=self.mark_button_state,
                                                background=self.mark_button_bg)

    def updateInterface(self, event):
        # TODO: Make this method more aware of changes in the viewer
        if self.interfaceComputed == False:
            return

        ##        s1=self.structure1.getvalue().strip()
        ##        s2=self.structure2.getvalue().strip()
        ##        c1=self.chain1.getvalue().strip()
        ##        c2=self.chain2.getvalue().strip()
        s1 = self.s1
        s2 = self.s2
        c1 = self.c1
        c2 = self.c2

        if len(c1) > 0:
            structurename1 = s1 + ' and chain %s' % c1
        else:
            structurename1 = s1
        if len(c2) > 0:
            structurename2 = s2 + ' and chain %s' % c2
        else:
            structurename2 = s2

        if self.show_interface_options1.getvalue() == self.interface_options_tuple1[0]:
            for res in list(self.interface1.keys()):
                cmd.color(self.interfacecolor1, '%s and resi %s' % (structurename1, res))
        elif self.show_interface_options1.getvalue() == self.interface_options_tuple1[1]:
            for res in list(self.interface1.keys()):
                cmd.show('spheres', '%s and resi %s' % (structurename1, res))
        elif self.show_interface_options1.getvalue() == self.interface_options_tuple1[2]:
            for res in list(self.interface1.keys()):
                cmd.show('sticks', '%s and resi %s' % (structurename1, res))
        elif self.show_interface_options1.getvalue() == self.interface_options_tuple1[3]:
            # Make a selection of the ET residues
            if len(self.interface1) > 0:
                cmd.select('%s_%s_PyETV' % (s1 + c1, s2 + c2),
                           '%s and resi %s' % (structurename1, "+".join(list(map(str, self.interface1)))))
            else:
                cmd.select('%s_%s_PyETV' % (s1 + c1, s2 + c2), 'none')

        if self.show_interface_options2.getvalue() == self.interface_options_tuple2[0]:
            for res in list(self.interface2.keys()):
                cmd.color(self.interfacecolor2, '%s and resi %s' % (structurename2, res))
        elif self.show_interface_options2.getvalue() == self.interface_options_tuple2[1]:
            for res in list(self.interface2.keys()):
                cmd.show('spheres', '%s and resi %s' % (structurename2, res))
        elif self.show_interface_options2.getvalue() == self.interface_options_tuple2[2]:
            for res in list(self.interface2.keys()):
                cmd.show('sticks', '%s and resi %s' % (structurename2, res))
        elif self.show_interface_options2.getvalue() == self.interface_options_tuple2[3]:
            # Make a selection of the ET residues
            if len(self.interface2) > 0:
                cmd.select('%s_%s_PyETV' % (s2 + c2, s1 + c1),
                           '%s and resi %s' % (structurename2, "+".join(list(map(str, self.interface2)))))
            else:
                cmd.select('%s_%s_PyETV' % (s2 + c2, s1 + c1), 'none')


class DiffETControlGroup:
    # Reuse some functions useful for calculating clustering z-score
    calcDist = _et_calcDist
    computeAdjacency = _et_computeAdjacency
    calcZScore = _et_calcZScore

    def __init__(self,
                 page,
                 groupname='Trace',
                 defaultstructurename='(pdb)',
                 defaultchain='',
                 defaultrankspath1='.ranks',
                 defaultrankspath2='.ranks',
                 defaultqualitymeasure=0,
                 etcolor1='blue',
                 etcolor2='red',
                 etcolor12='orange',
                 noetcolor='white',
                 et_messagebox=None):
        self.etcolor1 = etcolor1
        self.etcolor2 = etcolor2
        self.etcolor12 = etcolor12
        self.noetcolor = noetcolor
        self.et_messagebox = et_messagebox
        group = Pmw.Group(page, tag_text=groupname)
        group.pack(fill='both', expand=1, padx=10, pady=5)
        self.balloon = Pmw.Balloon(group.interior())

        self.structureframe = tkinter.Frame(group.interior())
        # Field for entering name of structure or model
        self.structure = Pmw.EntryField(self.structureframe,
                                        labelpos='w',
                                        label_text='structure to use: ',
                                        value=defaultstructurename,
                                        )

        self.chain = Pmw.EntryField(self.structureframe,
                                    labelpos='w',
                                    label_text='chain: ',
                                    value=defaultchain,
                                    )
        self.structure.grid(column=0, row=0)
        self.chain.grid(column=1, row=0)

        def quickFileValidation(s):
            if s == '':
                return Pmw.PARTIAL
            elif os.path.isfile(s):
                return Pmw.OK
            elif os.path.exists(s):
                return Pmw.PARTIAL
            else:
                return Pmw.PARTIAL

        # Button for assigning ranks to structure
        self.map_buttonbox = Pmw.ButtonBox(group.interior(), padx=0)
        self.map_buttonbox.add('Map ranks to structure', command=self.mapRanks)

        # Field for entering ranks file
        self.ranksfile1 = Pmw.EntryField(group.interior(),
                                         labelpos='w',
                                         label_pyclass=FileDialogButtonClassFactory.get(self.setRanksFileLocation1,
                                                                                        '*.cov'),

                                         validate={'validator': quickFileValidation, },
                                         value=defaultrankspath1,
                                         label_text='Superfamily ranks file path:')

        self.ranksfile2 = Pmw.EntryField(group.interior(),
                                         labelpos='w',
                                         label_pyclass=FileDialogButtonClassFactory.get(self.setRanksFileLocation2,
                                                                                        '*.cov'),

                                         validate={'validator': quickFileValidation, },
                                         value=defaultrankspath2,
                                         label_text='Subfamily ranks file path:')

        self.subgroup1 = Pmw.Group(group.interior(), tag_text='Superfamily (blue+orange)')

        # Tkinter Slider Tkinter for changing ET residue coverage.
        # TODO: Use a Pmw implementation?
        self.slider1 = Scale(self.subgroup1.interior(),
                             # from_=self.min, to=self.max,
                             from_=0, to=100,
                             showvalue=1,
                             length=200,
                             orient=HORIZONTAL, resolution=0.01,
                             tickinterval=0,
                             # repeatinterval=500, #Dunno what repeatinterval is for...
                             repeatdelay=500
                             )
        self.slider1.set(30)
        # self.slider.grid()
        self.slider1.bind("<ButtonRelease-1>", self.slider_button_release)
        # self.slider1.bind("<Button-1>", self.mousebutton_click_slider1)
        self.slider1.bind("<Button-2>", self.mousebutton_click_slider1)
        # For Windows
        self.slider1.bind("<MouseWheel>", self.mousewheel_roll_slider1)
        # For Linux (for Mac?)
        self.slider1.bind("<Button-4>", self.mousewheel_roll_slider1)
        self.slider1.bind("<Button-5>", self.mousewheel_roll_slider1)

        # Field for displaying ET coverage
        self.frame1 = tkinter.Frame(self.subgroup1.interior())
        self.percentcoverage1 = Pmw.EntryField(self.frame1,  # group.interior(),
                                               labelpos='w',
                                               label_text='Percent coverage: ',
                                               value='',
                                               )
        self.rankvalue1 = Pmw.EntryField(self.frame1,  # group.interior(),
                                         labelpos='w',
                                         label_text='Rank: ',
                                         value='',
                                         )
        self.percentcoverage1.grid(column=0, row=0)
        self.rankvalue1.grid(column=1, row=0)

        self.slider1.pack(fill='x', padx=4, pady=1)  # vertical
        # self.balloon.bind(self.slider1,"Superfamily ranks slider")
        self.frame1.pack(fill='x', padx=4, pady=1)  # vertical

        # Use options menu (combobox) instead of checkboxes
        self.et_options_tuple = ('Colors only(' + self.etcolor1 + ',' + self.etcolor12 + ',' + self.etcolor2 + ')',
                                 'as Spheres',
                                 'as Spheres (C-alpha only)',
                                 'Coverage differentials')
        self.show_et_options = Pmw.OptionMenu(group.interior(),
                                              labelpos='w',
                                              label_text='Show ET residues',
                                              items=self.et_options_tuple,
                                              initialitem=self.et_options_tuple[0],
                                              command=self.slider_button_release
                                              )
        ##        self.show_et_var=IntVar()
        ##        self.show_et_var.set(0)
        ##        self.show_et_checkbutton = Checkbutton(group.interior(),
        ##                                               text = "Show ET residues (as spheres)",
        ##                                               variable = self.show_et_var)
        self.sync_sliders_var = IntVar()
        self.sync_sliders_var.set(1)
        self.sync_sliders_checkbutton = Checkbutton(group.interior(),
                                                    text="Synchronize Subfamily ranks slider to Superfamily",
                                                    variable=self.sync_sliders_var)

        self.subgroup2 = Pmw.Group(group.interior(), tag_text='Subfamily (red+orange)')

        self.slider2 = Scale(self.subgroup2.interior(),
                             # from_=self.min, to=self.max,
                             from_=0, to=100,
                             showvalue=1,
                             length=200,
                             orient=HORIZONTAL, resolution=0.01,
                             tickinterval=0,
                             # repeatinterval=500, #Dunno what repeatinterval is for...
                             repeatdelay=500
                             )
        self.slider2.set(30)
        # self.slider.grid()
        self.slider2.bind("<ButtonRelease-1>", self.slider_button_release)
        # self.slider2.bind("<Button-1>", self.mousebutton_click_slider2)
        self.slider2.bind("<Button-2>", self.mousebutton_click_slider2)
        # For Windows
        self.slider2.bind("<MouseWheel>", self.mousewheel_roll_slider2)
        # For Linux (for Mac?)
        self.slider2.bind("<Button-4>", self.mousewheel_roll_slider2)
        self.slider2.bind("<Button-5>", self.mousewheel_roll_slider2)

        # Field for displaying ET coverage
        self.frame2 = tkinter.Frame(self.subgroup2.interior())
        self.percentcoverage2 = Pmw.EntryField(self.frame2,  # group.interior(),
                                               labelpos='w',
                                               label_text='Percent coverage: ',
                                               value='',
                                               )
        self.rankvalue2 = Pmw.EntryField(self.frame2,  # group.interior(),
                                         labelpos='w',
                                         label_text='Rank: ',
                                         value='',
                                         )
        self.percentcoverage2.grid(column=0, row=0)
        self.rankvalue2.grid(column=1, row=0)

        self.slider2.pack(fill='x', padx=4, pady=1)  # vertical
        # self.balloon.bind(self.slider2,"Subfamily ranks slider")
        self.frame2.pack(fill='x', padx=4, pady=1)  # vertical

        self.qualitymeasure_options_tuple = ('3D nobias',
                                             '3D bias (j-i)')
        self.show_qualitymeasure_options = Pmw.OptionMenu(group.interior(),
                                                          labelpos='w',
                                                          label_text='quality measure: ',
                                                          items=self.qualitymeasure_options_tuple,
                                                          initialitem=self.qualitymeasure_options_tuple[
                                                              defaultqualitymeasure],
                                                          )

        self.zscore_buttonbox = Pmw.ButtonBox(group.interior(), padx=0)
        self.zscore_buttonbox.add('Compute z-scores', command=self.showZScores)
        self.zscore1only = Pmw.EntryField(group.interior(),
                                          labelpos='w',
                                          label_text='z-score superfamily only (blue): ',
                                          value='',
                                          )
        self.zscore12 = Pmw.EntryField(group.interior(),
                                       labelpos='w',
                                       label_text='z-score common (orange): ',
                                       value='',
                                       )
        self.zscore2only = Pmw.EntryField(group.interior(),
                                          labelpos='w',
                                          label_text='z-score subfamily only (red): ',
                                          value='',
                                          )

        for entry in (self.structureframe,
                      self.ranksfile1,
                      self.ranksfile2,
                      self.map_buttonbox,
                      self.subgroup1,
                      # self.slider1,
                      # self.frame1,
                      # self.percentcoverage1,
                      # self.rankvalue1,
                      self.show_et_options,
                      # self.show_et_checkbutton,
                      self.sync_sliders_checkbutton,
                      self.subgroup2,
                      # self.slider2,
                      # self.frame2,
                      # self.percentcoverage2,
                      # self.rankvalue2,
                      self.zscore_buttonbox,
                      self.show_qualitymeasure_options,
                      self.zscore1only,
                      self.zscore12,
                      self.zscore2only):
            entry.pack(fill='x', padx=4, pady=1)  # vertical

        self.balloon.bind(self.structure, "Enter a name from the list of objects in the PyMOL Viewer")
        self.balloon.bind(self.map_buttonbox, "Click once before operating the controls below")
        self.balloon.bind(self.slider1,
                          "Drag superfamily ranks slider to vary ET residue selection and coverage (blue+orange) of the structure")
        self.balloon.bind(self.slider2,
                          "Drag subfamily ranks slider to vary ET residue selection and coverage (red+orange) of the structure")
        self.balloon.bind(self.show_et_options,
                          "Select display style to distinguish ET residue selection from the rest of the structure")
        self.balloon.bind(self.zscore_buttonbox, "Compute the clustering z-scores of the ET residue selections")
        self.balloon.bind(self.show_qualitymeasure_options, "Select from several measures of clustering quality")

        self.ranksObj1 = ETRanks()
        self.ranksObj2 = ETRanks()
        self.ranksStructureOK = False
        self.adjacencyOK = False

    def mapRanks(self):
        structurename = self.structure.getvalue().strip()
        chainindicator = self.chain.getvalue().strip()
        self.ranksStructureOK = False
        self.adjacencyOK = False
        if len(structurename) < 1:
            print('Provide structure name!')
            return
        try:
            model = cmd.get_model(structurename)
        except Exception:
            print("Structure \'%s\' does not exist!" % structurename)
            return

        rankspath1 = self.ranksfile1.getvalue().strip()
        if len(rankspath1) < 1:
            print('Provide path to superfamily ranks file!')
            return

        self.ranksObj1.readRanks(rankspath1)
        if self.ranksObj1.ranksLoaded():
            print("Superfamily ranks file \'%s\' loaded" % rankspath1)
        else:
            print("Error loading ranks file \'%s\'!" % rankspath1)
            return

        rankspath2 = self.ranksfile2.getvalue().strip()
        if len(rankspath2) < 1:
            print('Provide path to subfamily ranks file!')
            return

        self.ranksObj2.readRanks(rankspath2)
        if self.ranksObj2.ranksLoaded():
            print("Subfamily ranks file \'%s\' loaded" % rankspath2)
        else:
            print("Error loading ranks file \'%s\'!" % rankspath2)
            return

        if self.ranksObj1.getSize() != self.ranksObj2.getSize():
            print("Residue numbers in \'%s\' and \'%s\' are not the same!" % (rankspath1, rankspath2))
            return

        # TODO: This sanity check is too restrictive.
        for i in range(self.ranksObj1.getSize()):
            if self.ranksObj1.getResnum(i) != self.ranksObj2.getResnum(i):
                print("Residue numbers in \'%s\' and \'%s\' are not the same!" % (rankspath1, rankspath2))
                return

                # TODO: Make this optional
                # Check that the residue numbers in the model
                # and in the ranks file are identical
                ##        resnumDict={}
                ##        for i in range(len(model.atom)):
                ##            if chaindicator==atom[i].chain:
                ##                resnumDict[model.atom[i].resi]=1
                ##        if len(resnumDict)<1:
                ##            print 'Structure \'%s\' has no residues!' % structurename
                ##            return
                ##        if len(resnumDict)!=self.ranksObj1.getSize():
                ##            print "Residue numbers in \'%s\' and \'%s\' are not the same!" % (structurename, rankspath1)
                ##            return
                ##        for i in range(self.ranksObj1.getSize()):
                ##            try:
                ##                r=resnumDict[self.ranksObj1.getResnum(i)]
                ##            except KeyError:
                ##                print "Residue numbers in \'%s\' and \'%s\' do not match!" % (structurename, rankspath1)
                return

        self.structurename = structurename
        self.chainindicator = chainindicator
        self.ranksStructureOK = True
        # print 'Residue numbers checked with structure \'%s\'' % structurename

        self.slider_button_release(None)

    # TODO: Use this method to compare the sequences between structure and ranks files
    # This check uses the residue numbers, not the amino acid types
    def checkSequence(self, model, structurename, chainindicator, rankspath):
        three2one = {
            "ALA": 'A',
            "ARG": 'R',
            "ASN": 'N',
            "ASP": 'D',
            "CYS": 'C',
            "GLN": 'Q',
            "GLU": 'E',
            "GLY": 'G',
            "HIS": 'H',
            "ILE": 'I',
            "LEU": 'L',
            "LYS": 'K',
            "MET": 'M',
            "PHE": 'F',
            "PRO": 'P',
            "SER": 'S',
            "THR": 'T',
            "TRP": 'W',
            "TYR": 'Y',
            "VAL": 'V',
            "A": "A",
            "G": "G",
            "T": "T",
            "U": "U",
            "C": "C", }

        print('Comparing residue numbers of structure \'%s\' and ranks \'%s\'...' % (structurename + chainindicator,
                                                                                     rankspath))
        # Check that the residue numbers in the model
        # and in the ranks file are identical
        resnumDict = {}
        if len(chainindicator) > 0:
            # chainup=chainindicator()
            # Case of chain indicator is relevant
            for i in range(len(model.atom)):
                atomobj = model.atom[i]
                if chainindicator == atomobj.chain:
                    try:
                        aa1 = three2one[atomobj.resn]  # Consider only residues with standard amino acids
                        resnumDict[atomobj.resi] = 1
                    except KeyError:
                        pass
        else:
            for i in range(len(model.atom)):
                resnumDict[model.atom[i].resi] = 1

        print('Sequence lengths:')
        print('structure: ', len(resnumDict))
        print('ranks: ', self.ranksObj.getSize())
        if len(resnumDict) < 1:
            print('Structure \'%s\' has no residues!' % (structurename + chainindicator))
        if len(resnumDict) != self.ranksObj.getSize():
            print("Sequence lengths of \'%s\' and \'%s\' are not the same!" % (structurename + chainindicator,
                                                                               rankspath))
        missing = 0
        for i in range(self.ranksObj.getSize()):
            try:
                r = resnumDict[self.ranksObj.getResnum(i)]
            except KeyError:
                missing += 1
        if missing > 0:
            print("%d residue numbers in \'%s\' missing in \'%s\'!" % (
            missing, rankspath, structurename + chainindicator))

        print('...Done.')

    def mousewheel_roll_slider1(self, event):
        slideval = self.slider1.get()
        # See http://www.daniweb.com/code/snippet217059.html
        if event.num == 4 or event.delta == -120:
            self.slider1.set(slideval + 1)
            self.slider_button_release(None)
        if event.num == 5 or event.delta == 120:
            self.slider1.set(slideval - 1)
            self.slider_button_release(None)
        # print event.num, event.delta, slideval

    def mousebutton_click_slider1(self, event):
        # slideval=self.slider.get()
        if event.num == 1 or event.num == 2:
            self.slider_button_release(None)
        # print event.num, event.delta, slideval

    def mousewheel_roll_slider2(self, event):
        slideval = self.slider2.get()
        # See http://www.daniweb.com/code/snippet217059.html
        if event.num == 4 or event.delta == -120:
            self.slider2.set(slideval + 1)
            self.slider_button_release(None)
        if event.num == 5 or event.delta == 120:
            self.slider2.set(slideval - 1)
            self.slider_button_release(None)
        # print event.num, event.delta, slideval

    def mousebutton_click_slider2(self, event):
        # slideval=self.slider.get()
        if event.num == 1 or event.num == 2:
            self.slider_button_release(None)
        # print event.num, event.delta, slideval

    def slider_button_release(self, event):
        if self.ranksStructureOK:
            self.et_messagebox.show()

            if len(self.chainindicator) > 0:
                structurename = '%s and chain %s' % (self.structurename, self.chainindicator)
            else:
                structurename = self.structurename

            maxcov1 = 0
            maxrank1 = 0
            maxcov2 = 0
            maxrank2 = 0
            slideval1 = self.slider1.get()
            if self.sync_sliders_var.get() == 1:
                self.slider2.set(slideval1)
                slideval2 = slideval1
            else:
                slideval2 = self.slider2.get()
            # if self.show_et_var.get()==1:
            if self.show_et_options.getvalue() == self.et_options_tuple[3]:
                for i in range(self.ranksObj1.getSize()):
                    (resnum1, cov1, rank1) = self.ranksObj1.getResnumCoverageRankTuple(i)
                    (resnum2, cov2, rank2) = self.ranksObj2.getResnumCoverageRankTuple(i)
                    if cov1 > maxcov1:
                        maxcov1 = cov1
                        maxrank1 = rank1
                    if cov2 > maxcov2:
                        maxcov2 = cov2
                        maxrank2 = rank2
                    cd = (cov1 - cov2) / (cov1 + cov2)  # SuperfamilyCoverage-SubfamilyCoverage
                    if cd > 0.2:
                        cmd.color('_et_red_color%s' % int(10 * (cd - 0.2) / 0.8),
                                  '%s and resi %s' % (structurename, resnum1))
                    elif cd < -0.2:
                        cmd.color('_et_blue_color%s' % int(10 * (-cd - 0.2) / 0.8),
                                  '%s and resi %s' % (structurename, resnum1))
                    else:
                        cmd.color('white', '%s and resi %s' % (structurename, resnum1))
            elif self.show_et_options.getvalue() == self.et_options_tuple[2]:
                cmd.hide('spheres', structurename)  # Need to hide spheres in case non-CA atoms are displayed
                for i in range(self.ranksObj1.getSize()):
                    (resnum1, cov1, rank1) = self.ranksObj1.getResnumCoverageRankTuple(i)
                    (resnum2, cov2, rank2) = self.ranksObj2.getResnumCoverageRankTuple(i)
                    # assert(resnum1==resnum2)
                    if slideval1 >= cov1:
                        et1 = True
                        if cov1 > maxcov1:
                            maxcov1 = cov1
                            maxrank1 = rank1
                    else:
                        et1 = False
                    if slideval2 >= cov2:
                        et2 = True
                        if cov2 > maxcov2:
                            maxcov2 = cov2
                            maxrank2 = rank2
                    else:
                        et2 = False
                    if et1 and not et2:
                        cmd.color(self.etcolor1, '%s and resi %s' % (structurename, resnum1))
                        cmd.show('spheres', '%s and resi %s and name CA' % (structurename, resnum1))
                    elif et1 and et2:
                        cmd.color(self.etcolor12, '%s and resi %s' % (structurename, resnum1))
                        cmd.show('spheres', '%s and resi %s and name CA' % (structurename, resnum1))
                    elif et2 and not et1:
                        cmd.color(self.etcolor2, '%s and resi %s' % (structurename, resnum1))
                        cmd.show('spheres', '%s and resi %s and name CA' % (structurename, resnum1))
                    else:
                        cmd.color(self.noetcolor, '%s and resi %s' % (structurename, resnum1))
                        cmd.hide('spheres', '%s and resi %s' % (structurename, resnum1))
            elif self.show_et_options.getvalue() == self.et_options_tuple[1]:
                for i in range(self.ranksObj1.getSize()):
                    (resnum1, cov1, rank1) = self.ranksObj1.getResnumCoverageRankTuple(i)
                    (resnum2, cov2, rank2) = self.ranksObj2.getResnumCoverageRankTuple(i)
                    # assert(resnum1==resnum2)
                    if slideval1 >= cov1:
                        et1 = True
                        if cov1 > maxcov1:
                            maxcov1 = cov1
                            maxrank1 = rank1
                    else:
                        et1 = False
                    if slideval2 >= cov2:
                        et2 = True
                        if cov2 > maxcov2:
                            maxcov2 = cov2
                            maxrank2 = rank2
                    else:
                        et2 = False
                    if et1 and not et2:
                        cmd.color(self.etcolor1, '%s and resi %s' % (structurename, resnum1))
                        cmd.show('spheres', '%s and resi %s' % (structurename, resnum1))
                    elif et1 and et2:
                        cmd.color(self.etcolor12, '%s and resi %s' % (structurename, resnum1))
                        cmd.show('spheres', '%s and resi %s' % (structurename, resnum1))
                    elif et2 and not et1:
                        cmd.color(self.etcolor2, '%s and resi %s' % (structurename, resnum1))
                        cmd.show('spheres', '%s and resi %s' % (structurename, resnum1))
                    else:
                        # Still assign the right color even we hide it, in case the user makes
                        # this part visible
                        cmd.color(self.noetcolor, '%s and resi %s' % (structurename, resnum1))
                        cmd.hide('spheres', '%s and resi %s' % (structurename, resnum1))
            # elif self.show_et_options.getvalue()==self.et_options_tuple[0]:
            else:
                for i in range(self.ranksObj1.getSize()):
                    (resnum1, cov1, rank1) = self.ranksObj1.getResnumCoverageRankTuple(i)
                    (resnum2, cov2, rank2) = self.ranksObj2.getResnumCoverageRankTuple(i)
                    # assert(resnum1==resnum2)
                    if slideval1 >= cov1:
                        et1 = True
                        if cov1 > maxcov1:
                            maxcov1 = cov1
                            maxrank1 = rank1
                    else:
                        et1 = False
                    if slideval2 >= cov2:
                        et2 = True
                        if cov2 > maxcov2:
                            maxcov2 = cov2
                            maxrank2 = rank2
                    else:
                        et2 = False
                    if et1 and not et2:
                        cmd.show
                        cmd.color(self.etcolor1, '%s and resi %s' % (structurename, resnum1))
                    elif et1 and et2:
                        cmd.color(self.etcolor12, '%s and resi %s' % (structurename, resnum1))
                    elif et2 and not et1:
                        cmd.color(self.etcolor2, '%s and resi %s' % (structurename, resnum1))
                    else:
                        cmd.color(self.noetcolor, '%s and resi %s' % (structurename, resnum1))

            self.percentcoverage1.setvalue(maxcov1)
            self.rankvalue1.setvalue(maxrank1)
            self.percentcoverage2.setvalue(maxcov2)
            self.rankvalue2.setvalue(maxrank2)

            self.et_messagebox.withdraw()

    def setRanksFileLocation1(self, value):
        self.ranksfile1.setvalue(value)

    def setRanksFileLocation2(self, value):
        self.ranksfile2.setvalue(value)

    def showZScores(self):
        if self.ranksStructureOK:
            try:
                self.et_messagebox.show()
                if self.show_qualitymeasure_options.getvalue() == self.qualitymeasure_options_tuple[0]:
                    bias = 0
                elif self.show_qualitymeasure_options.getvalue() == self.qualitymeasure_options_tuple[1]:
                    bias = 1
                if not self.adjacencyOK:
                    if len(self.chainindicator) > 0:
                        structurename = '%s and chain %s' % (self.structurename, self.chainindicator)
                    else:
                        structurename = self.structurename
                    print("Computing adjacency matrix A(i,j)...")
                    self._A, ResAtoms, self._Ar = self.computeAdjacency(structurename)
                    if self._A == None:
                        self.et_messagebox.show()
                        return
                    else:
                        self.adjacencyOK = True
                    print("... done")

                # Get lists of ET residues
                et1only = []
                et12 = []
                et2only = []
                notet = []
                slideval1 = self.slider1.get()
                slideval2 = self.slider2.get()
                for i in range(self.ranksObj1.getSize()):
                    (resnum1, cov1, rank1) = self.ranksObj1.getResnumCoverageRankTuple(i)
                    (resnum2, cov2, rank2) = self.ranksObj2.getResnumCoverageRankTuple(i)
                    # assert(resnum1==resnum2)
                    if slideval1 >= cov1:
                        et1 = True
                    else:
                        et1 = False
                    if slideval2 >= cov2:
                        et2 = True
                    else:
                        et2 = False
                    if et1 and not et2:
                        et1only.append(int(resnum1))
                    elif et1 and et2:
                        et12.append(int(resnum1))
                    elif et2 and not et1:
                        et2only.append(int(resnum1))
                    else:
                        notet.append(int(resnum1))

                print("ET residue selection lists:")
                print(et1only)
                print(et12)
                print(et2only)
                print("Computing z-scores...")
                if len(et1only) > 0:
                    z1only = self.calcZScore(et1only, self.ranksObj1.getSize(), self._A, bias)
                else:
                    z1only = 'NA'
                if len(et12) > 0:
                    z12 = self.calcZScore(et12, self.ranksObj1.getSize(), self._A, bias)
                else:
                    z12 = 'NA'
                if len(et2only) > 0:
                    z2only = self.calcZScore(et2only, self.ranksObj1.getSize(), self._A, bias)
                else:
                    z2only = 'NA'

                self.zscore1only.setvalue(z1only)
                self.zscore12.setvalue(z12)
                self.zscore2only.setvalue(z2only)

                if len(notet) > 0:
                    print('Non-ET residue z-score: ', self.calcZScore(notet, self.ranksObj1.getSize(), self._A, bias))

                # Test
                # Residues with L>0.090000:
                # cnet=['54', '50', '52', '110', '81', '85', '12', '42']

                # Residues with L>0.080000:
                # cnet=['25', '21', '54', '50', '52', '110', '80', '81', '85', '12', '19', '42', '1', '76']

                # Residues with L>0.070000:
                # cnet=['24', '25', '21', '23', '120', '124', '54', '50', '52', '110', '80', '81', '85', '31', '69', '12', '19', '48', '44', '42', '43', '1', '76']

                # Residues with L>0.050000:
                # cnet=['24', '25', '21', '23', '29', '120', '122', '124', '58', '54', '50', '52', '110', '80', '81', '85', '31', '61', '64', '69', '98', '12', '19', '48', '44', '42', '43', '1', '76']

                # Residues with L>0.030000:
                # cnet=['24', '25', '20', '21', '22', '23', '29', '120', '121', '122', '123', '124', '58', '55', '54', '57', '56', '50', '53', '52', '117', '111', '110', '112', '80', '81', '119', '118', '85', '106', '31', '60', '61', '64', '65', '67', '69', '99', '98', '12', '15', '19', '89', '48', '46', '44', '42', '43', '1', '9', '76', '74', '70', '78']

                # print 'Conductivity network z-score: ', self.calcZScore(cnet)
                # print cnet, len(cnet)

                print("...done")
            except Exception as detail:
                print('An exception occurred in showZScores!')
                print(detail)

            self.et_messagebox.withdraw()


###############################################################

class InterproteinPairETRanks:
    def __init__(self):
        self.dataloaded = False
        self.maxpairs = 200

    def dataLoaded(self):
        return self.dataloaded

    def readData(self, datapath):
        self.network = {}  # (resnum1,resnum2)
        self.coverages = {}
        self.dataloaded = False
        FILE = open(datapath, 'r')
        numpairs = 0
        try:
            for line in FILE:
                if line.find('%') > -1:
                    continue
                if len(line.rstrip()) >= 50:
                    ##                    resnum1=line[36:40].strip()
                    ##                    resnum2=line[50:54].strip()
                    ##                    ETpair=float(line[18:29].strip())
                    resnum1 = line[20:25].strip()  # Using new file format of Angela (08/03/11)
                    resnum2 = line[30:35].strip()
                    ETpair = float(line[40:52].strip())
                    cvg = float(line[52:64].strip())
                    self.network[(resnum1, resnum2)] = ETpair
                    self.coverages[(resnum1, resnum2)] = cvg
                    print(resnum1, resnum2, ETpair, cvg)
                    numpairs += 1
                    if numpairs == self.maxpairs:
                        break
        finally:
            FILE.close()
        if len(self.network) > 0:
            ##            for respair1, value1 in self.network.items():
            ##                for respair2, value2 in self.network.items():
            ##                    if value1>=value2:
            ##                        self.coverages[respair1]+=1
            ##                self.coverages[respair1]=self.coverages[respair1]*100.0/numpairs
            self.dataloaded = True


class InterproteinPairRankControlGroup:
    def __init__(self,
                 page,
                 groupname='Interprotein ET Pair Ranks Network',
                 defaultstructurename1='(pdb)',
                 defaultstructurename2='(pdb)',
                 defaultrankspath='.ranks',
                 etcolor1='red',
                 noetcolor1='wheat',
                 etcolor2='blue',
                 noetcolor2='palecyan',
                 defaultetoption=2,
                 defaultslidercoverage=0.05):

        self.etcolor1 = etcolor1
        self.noetcolor1 = noetcolor1
        self.etcolor2 = etcolor2
        self.noetcolor2 = noetcolor2

        group = Pmw.Group(page, tag_text=groupname)
        group.pack(fill='both', expand=1, padx=10, pady=5)
        # Field for entering name of structure or model
        self.structure1 = Pmw.EntryField(group.interior(),
                                         labelpos='w',
                                         label_text='structure to use (chain 1): ',
                                         value=defaultstructurename1,
                                         )
        self.structure2 = Pmw.EntryField(group.interior(),
                                         labelpos='w',
                                         label_text='structure to use (chain 2): ',
                                         value=defaultstructurename2,
                                         )

        def quickFileValidation(s):
            if s == '':
                return Pmw.PARTIAL
            elif os.path.isfile(s):
                return Pmw.OK
            elif os.path.exists(s):
                return Pmw.PARTIAL
            else:
                return Pmw.PARTIAL

        # Button for assigning ranks to structure
        self.map_buttonbox = Pmw.ButtonBox(group.interior(), padx=0)
        self.map_buttonbox.add('Map pair ranks to structure', command=self.mapRanks)

        # Field for entering ranks file
        self.ranksfile = Pmw.EntryField(group.interior(),
                                        labelpos='w',
                                        label_pyclass=FileDialogButtonClassFactory.get(self.setRanksFileLocation, '*'),
                                        validate={'validator': quickFileValidation, },
                                        value=defaultrankspath,
                                        label_text='pair ranks file path:')

        # Tkinter Slider Tkinterfor changing ET residue coverage.
        # TODO: Use a Pmw implementation?
        self.slider = Scale(group.interior(),
                            # from_=self.min, to=self.max,
                            from_=0, to=1,
                            showvalue=1,
                            length=200,
                            orient=HORIZONTAL, resolution=0.001,
                            tickinterval=0,
                            # repeatinterval=500, #Dunno what repeatinterval is for...
                            repeatdelay=500
                            )
        self.slider.set(defaultslidercoverage)
        # self.slider.grid()
        self.slider.bind("<ButtonRelease-1>", self.slider_button_release)
        # self.slider.bind("<B1-Motion>", self.slider_button_release)

        # Field for displaying ET coverage
        self.frame1 = tkinter.Frame(group.interior())
        self.percentcoverage = Pmw.EntryField(self.frame1,  # group.interior(),
                                              labelpos='w',
                                              label_text='Percent coverage: ',
                                              value='',
                                              )
        self.rankvalue = Pmw.EntryField(self.frame1,  # group.interior(),
                                        labelpos='w',
                                        label_text='Rank: ',
                                        value='',
                                        )
        self.percentcoverage.grid(column=0, row=0)
        self.rankvalue.grid(column=1, row=0)

        self.frame2 = tkinter.Frame(group.interior())
        self.et_options_tuple = ('Colors only(' + self.etcolor1 + ',' + self.etcolor2 + ')',
                                 'as Spheres',
                                 'as Spheres (C-alpha only)',
                                 'Red_to_Green')  # TODO Prismatic

        self.show_et_options = Pmw.OptionMenu(self.frame2,  # group.interior(),
                                              labelpos='w',
                                              label_text='Show ET residues',
                                              items=self.et_options_tuple,
                                              initialitem=self.et_options_tuple[defaultetoption],
                                              command=self.slider_button_release
                                              )
        ##        self.show_et_var=IntVar()
        ##        self.show_et_var.set(0)
        ##        self.show_et_checkbutton = Checkbutton(group.interior(),
        ##                                               text = "Show ET residues (as spheres)",
        ##                                               variable = self.show_et_var)

        self.pair_et_var = IntVar()
        self.pair_et_var.set(0)
        self.pair_et_checkbutton = Checkbutton(self.frame2,  # group.interior(),
                                               text='Show pair (CA) distances',
                                               variable=self.pair_et_var)

        self.show_et_options.grid(column=0, row=0)
        self.pair_et_checkbutton.grid(column=1, row=0)

        for entry in (self.structure1,
                      self.structure2,
                      self.ranksfile,
                      self.map_buttonbox,
                      self.slider,
                      self.frame1,
                      self.frame2):
            entry.pack(fill='x', padx=4, pady=1)  # vertical

        self.dataObj = InterproteinPairETRanks()
        self.ranksStructureOK = False

    def mapRanks(self):
        structurename1 = self.structure1.getvalue().strip()
        self.ranksStructureOK = False
        if len(structurename1) < 1:
            print('Provide structure name!')
            return
        try:
            model1 = cmd.get_model(structurename1)
        except Exception:
            print("Structure \'%s\' does not exist!" % structurename1)
            return

        structurename2 = self.structure2.getvalue().strip()
        # self.ranksStructureOK=False
        if len(structurename2) < 1:
            print('Provide structure name!')
            return
        try:
            model2 = cmd.get_model(structurename2)
        except Exception:
            print("Structure \'%s\' does not exist!" % structurename2)
            return

        rankspath = self.ranksfile.getvalue().strip()
        if len(rankspath) < 1:
            print('Provide path to pair ranks file!')
            return

        self.dataObj.readData(rankspath)
        if self.dataObj.dataLoaded():
            print("Pair ranks file \'%s\' loaded" % rankspath)
        else:
            print("Error loading pair ranks file \'%s\'!" % rankspath)
            return
        # Check that the residue numbers in the model
        # and in the ranks file are identical
        ##        resnumDict={}
        ##        for i in range(len(model.atom)):
        ##            resnumDict[model.atom[i].resi]=1
        ##        if len(resnumDict)<1:
        ##            print 'Structure \'%s\' has no residues!' % structurename
        ##            return
        ##        if len(resnumDict)!=self.ranksObj.getSize():
        ##            print "Residue numbers in \'%s\' and \'%s\' are not the same!" % (structurename, rankspath)
        ##            return
        ##        for i in range(self.ranksObj.getSize()):
        ##            try:
        ##                r=resnumDict[self.ranksObj.getResnum(i)]
        ##            except KeyError:
        ##                print "Residue numbers in \'%s\' and \'%s\' do not match!" % (structurename, rankspath)
        ##                return

        self.structurename1 = structurename1
        self.structurename2 = structurename2
        self.ranksStructureOK = True
        ##        print 'Residue numbers checked with structure \'%s\'' % structurename

        self.slider_button_release(None)

    def slider_button_release(self, event):
        if self.ranksStructureOK:
            maxcov = 0
            maxrank = 0
            slideval = self.slider.get()
            if self.show_et_options.getvalue() == self.et_options_tuple[2]:
                cmd.hide('spheres', self.structurename1)  # Need to hide spheres in case non-CA atoms are displayed
                cmd.hide('spheres',
                         self.structurename2)  # Also need to hide spheres first because a residue may appear in more than one pair
                cmd.color(self.noetcolor1, '%s' % (self.structurename1))
                cmd.color(self.noetcolor2, '%s' % (self.structurename2))
                for respair, rank in list(self.dataObj.network.items()):
                    cov = self.dataObj.coverages[respair]
                    if slideval >= cov:
                        if cov > maxcov:
                            maxcov = cov
                            maxrank = rank
                        cmd.color(self.etcolor1, '%s and resi %s' % (self.structurename1, respair[0]))
                        cmd.show('spheres', '%s and resi %s and name CA' % (self.structurename1, respair[0]))
                        cmd.color(self.etcolor2, '%s and resi %s' % (self.structurename2, respair[1]))
                        cmd.show('spheres', '%s and resi %s and name CA' % (self.structurename2, respair[1]))
            elif self.show_et_options.getvalue() == self.et_options_tuple[1]:
                cmd.hide('spheres', '%s' % (self.structurename1))
                cmd.hide('spheres', '%s' % (self.structurename2))
                cmd.color(self.noetcolor1, '%s' % (self.structurename1))
                cmd.color(self.noetcolor2, '%s' % (self.structurename2))
                for respair, rank in list(self.dataObj.network.items()):
                    cov = self.dataObj.coverages[respair]
                    if slideval >= cov:
                        if cov > maxcov:
                            maxcov = cov
                            maxrank = rank
                        cmd.color(self.etcolor1, '%s and resi %s' % (self.structurename1, respair[0]))
                        cmd.show('spheres', '%s and resi %s' % (self.structurename1, respair[0]))
                        cmd.color(self.etcolor2, '%s and resi %s' % (self.structurename2, respair[1]))
                        cmd.show('spheres', '%s and resi %s' % (self.structurename2, respair[1]))
            ##            elif self.show_et_options.getvalue()==self.et_options_tuple[3]: #Do Prismatic
            ##                for i in range(self.ranksObj.getSize()):
            ##                    (resnum,cov,rank)=self.ranksObj.getResnumCoverageRankTuple(i)
            ##                    if slideval>=cov:
            ##                        if cov>maxcov:
            ##                            maxcov=cov
            ##                            maxrank=rank
            ##                        cmd.color('_et_prism_color%d'%(int(cov-0.000001)),'%s and resi %s' % (self.structurename,resnum))
            ##                    else:
            ##                        cmd.color('white','%s and resi %s' % (self.structurename,resnum))
            ##            else:
            ##                #It turns out that calling cmd.color or cmd.select with a list of residues, e.g. 'resi 1+2+...', can crash pymol
            ##                #if the list is too big (>300)
            ##                for i in range(self.ranksObj.getSize()):
            ##                    (resnum,cov,rank)=self.ranksObj.getResnumCoverageRankTuple(i)
            ##                    if slideval>=cov:
            ##                        if cov>maxcov:
            ##                            maxcov=cov
            ##                            maxrank=rank
            ##                        cmd.color(self.etcolor,'%s and resi %s' % (self.structurename,resnum))
            ##                    else:
            ##                        cmd.color(self.noetcolor,'%s and resi %s' % (self.structurename,resnum))
            # Draw lines between residues
            if self.pair_et_var.get() == 1:
                pair_distance_name = 'et_pair_%s_%s_PyETV' % (self.structurename1, self.structurename2)
                cmd.delete(pair_distance_name)
                for respair, rank in list(self.dataObj.network.items()):
                    cov = self.dataObj.coverages[respair]
                    if slideval >= cov:
                        cmd.distance(pair_distance_name,
                                     '%s and resi %s and name CA' % (self.structurename1, respair[0]),
                                     '%s and resi %s and name CA' % (self.structurename2, respair[1]))

            self.percentcoverage.setvalue(maxcov)
            self.rankvalue.setvalue(maxrank)

    def setRanksFileLocation(self, value):
        self.ranksfile.setvalue(value)


#######################################################################

# Take the minimum pair rank coverage as the score of a residue
# This class is patterned after ConductivityNetwork.
class PairETRanks:
    def __init__(self):
        self.dataloaded = False

    def dataLoaded(self):
        return self.dataloaded

    def buildCluster(self, seed, neighbors, pcov):
        remain = neighbors[:]
        tmpcluster = [seed]
        for r in neighbors:
            for t in tmpcluster:
                try:
                    L = self.network[(r, t)]
                    if L <= pcov:
                        tmpcluster.append(r)
                        remain.remove(r)
                        break
                except KeyError:
                    pass
                try:
                    L = self.network[(t, r)]
                    if L <= pcov:
                        tmpcluster.append(r)
                        remain.remove(r)
                        break
                except KeyError:
                    pass
        return tmpcluster, remain

    def clusters(self, pcov):
        resnumlist = []
        for resnum, pcov_ in list(self.resnumsMaxL.items()):
            if pcov_ <= pcov:
                resnumlist.append(resnum)
        if len(resnumlist) == 0:
            return None
        clusterlist = []
        remain = resnumlist
        while len(remain) > 0:
            seed = remain[0]
            tmpcluster, remain = self.buildCluster(seed, remain[1:], pcov)
            clusterlist.append(tmpcluster)

        return clusterlist

    def readData(self, datapath):
        self.network = {}  # (resnum1,resnum2): L value (resnum1<resnum2)
        self.resnumsMaxL = {}  # resnum: max L value -> (maps to) minimum pair coverage value
        self.dataloaded = False
        FILE = open(datapath, 'r')
        try:
            for line in FILE:
                if line.find('%') > -1:
                    continue
                if len(line.rstrip()) >= 50:
                    ##                    resnum1=line[5:10].strip()
                    ##                    resnum2=line[15:20].strip()
                    ##                    ETpair=float(line[25:37].strip())
                    ##                    L=float(line[37:49].strip()) #Angela computed coverage
                    ####                    self.network[(resnum1,resnum2)]=ETpair
                    ##                    self.network[(resnum1,resnum2)]=L

                    resnum1 = line[19:25].strip()
                    resnum2 = line[33:39].strip()
                    L = float(line[5:19].strip())  # cvg(ETP/Ave)
                    self.network[(resnum1, resnum2)] = L
                    try:
                        tmpL = self.resnumsMaxL[resnum1]
                        if tmpL > L:
                            self.resnumsMaxL[resnum1] = L
                    except KeyError:
                        self.resnumsMaxL[resnum1] = L
                    try:
                        tmpL = self.resnumsMaxL[resnum2]
                        if tmpL > L:
                            self.resnumsMaxL[resnum2] = L
                    except KeyError:
                        self.resnumsMaxL[resnum2] = L
        finally:
            FILE.close()
        if len(self.network) > 0:
            self.dataloaded = True


# This control group preceded InterproteinPairRankControlGroup
class PairETControlGroup:
    def __init__(self,
                 page,
                 groupname='Pair ET Ranks Network',
                 defaultstructurename='(pdb)',
                 defaultrankspath='.ranks',
                 etcolor='red',
                 noetcolor='white'):

        self.etcolor = etcolor
        self.noetcolor = noetcolor

        group = Pmw.Group(page, tag_text=groupname)
        group.pack(fill='both', expand=1, padx=10, pady=5)
        # Field for entering name of structure or model
        self.structure = Pmw.EntryField(group.interior(),
                                        labelpos='w',
                                        label_text='structure to use: ',
                                        value=defaultstructurename,
                                        )

        def quickFileValidation(s):
            if s == '':
                return Pmw.PARTIAL
            elif os.path.isfile(s):
                return Pmw.OK
            elif os.path.exists(s):
                return Pmw.PARTIAL
            else:
                return Pmw.PARTIAL

        # Button for assigning ranks to structure
        self.map_buttonbox = Pmw.ButtonBox(group.interior(), padx=0)
        self.map_buttonbox.add('Map pair ranks to structure', command=self.mapRanks)

        # Field for entering ranks file
        self.ranksfile = Pmw.EntryField(group.interior(),
                                        labelpos='w',
                                        label_pyclass=FileDialogButtonClassFactory.get(self.setRanksFileLocation,
                                                                                       '*.dat'),
                                        validate={'validator': quickFileValidation, },
                                        value=defaultrankspath,
                                        label_text='pair ranks file path:')

        # Tkinter Slider Tkinterfor changing ET residue coverage.
        # TODO: Use a Pmw implementation?
        self.slider = Scale(group.interior(),
                            # from_=self.min, to=self.max,
                            from_=0, to=2,
                            showvalue=1,
                            length=200,
                            orient=HORIZONTAL, resolution=0.01,
                            tickinterval=0,
                            # repeatinterval=500, #Dunno what repeatinterval is for...
                            repeatdelay=500
                            )
        self.slider.set(0.01)
        # self.slider.grid()
        self.slider.bind("<ButtonRelease-1>", self.slider_button_release)
        # self.slider.bind("<B1-Motion>", self.slider_button_release)

        # Field for displaying ET coverage
        ##        self.percentcoverage = Pmw.EntryField(group.interior(),
        ##                                              labelpos='w',
        ##                                              label_text='Percent coverage: ',
        ##                                              value='',
        ##                                              )
        self.rankvalue = Pmw.EntryField(group.interior(),
                                        labelpos='w',
                                        label_text='pair percent coverage: ',
                                        value='',
                                        )

        self.et_options_tuple = ('Colors only(' + self.etcolor + ')',
                                 'as Spheres',
                                 'as Spheres (C-alpha only)',
                                 'cvg(ETP/Ave) clusters')
        self.show_et_options = Pmw.OptionMenu(group.interior(),
                                              labelpos='w',
                                              label_text='Show ET residues',
                                              items=self.et_options_tuple,
                                              initialitem=self.et_options_tuple[0],
                                              command=self.slider_button_release
                                              )
        ##        self.show_et_var=IntVar()
        ##        self.show_et_var.set(0)
        ##        self.show_et_checkbutton = Checkbutton(group.interior(),
        ##                                               text = "Show conductivity network residues (as spheres)",
        ##                                               variable = self.show_et_var)

        for entry in (self.structure,
                      self.ranksfile,
                      self.map_buttonbox,
                      self.slider,
                      ##                      self.percentcoverage,
                      self.rankvalue,
                      # self.show_et_checkbutton
                      self.show_et_options):
            entry.pack(fill='x', padx=4, pady=1)  # vertical

        self.dataObj = PairETRanks()
        self.ranksStructureOK = False

    def mapRanks(self):
        structurename = self.structure.getvalue().strip()
        self.ranksStructureOK = False
        if len(structurename) < 1:
            print('Provide structure name!')
            return
        try:
            model = cmd.get_model(structurename)
        except Exception:
            print("Structure \'%s\' does not exist!" % structurename)
            return

        rankspath = self.ranksfile.getvalue().strip()
        if len(rankspath) < 1:
            print('Provide path to pair ranks file!')
            return

        self.dataObj.readData(rankspath)
        if self.dataObj.dataLoaded():
            print("Pair ranks file \'%s\' loaded" % rankspath)
        else:
            print("Error loading pair ranks file \'%s\'!" % rankspath)
            return
        # Check that the residue numbers in the model
        # and in the ranks file are identical
        ##        resnumDict={}
        ##        for i in range(len(model.atom)):
        ##            resnumDict[model.atom[i].resi]=1
        ##        if len(resnumDict)<1:
        ##            print 'Structure \'%s\' has no residues!' % structurename
        ##            return
        ##        if len(resnumDict)!=self.ranksObj.getSize():
        ##            print "Residue numbers in \'%s\' and \'%s\' are not the same!" % (structurename, rankspath)
        ##            return
        ##        for i in range(self.ranksObj.getSize()):
        ##            try:
        ##                r=resnumDict[self.ranksObj.getResnum(i)]
        ##            except KeyError:
        ##                print "Residue numbers in \'%s\' and \'%s\' do not match!" % (structurename, rankspath)
        ##                return

        self.structurename = structurename
        self.ranksStructureOK = True
        ##        print 'Residue numbers checked with structure \'%s\'' % structurename

        self.slider_button_release(None)

    def slider_button_release(self, event):
        if self.ranksStructureOK:
            # reslist=[]
            maxcov = 0
            slideval = self.slider.get()
            if self.show_et_options.getvalue() == self.et_options_tuple[3]:
                #
                clusters = self.dataObj.clusters(slideval)
                if clusters:
                    print(clusters)
                    print("Number of cvg(ETP/Ave) clusters", len(clusters))
                    cmd.hide('spheres', self.structurename)
                    # Get largest cluster
                    cluster1 = []
                    size1 = 0
                    for clust in clusters:
                        if len(clust) > size1:
                            size1 = len(clust)
                            cluster1 = clust
                    if len(cluster1) > 0:
                        clusters.remove(cluster1)
                    # Get second largest cluster
                    cluster2 = []
                    size2 = 0
                    for clust in clusters:
                        if len(clust) > size2:
                            size2 = len(clust)
                            cluster2 = clust
                    if len(cluster2) > 0:
                        clusters.remove(cluster2)
                    # Get third largest cluster
                    cluster3 = []
                    size3 = 0
                    for clust in clusters:
                        if len(clust) > size3:
                            size3 = len(clust)
                            cluster3 = clust
                    cmd.hide('spheres', self.structurename)
                    cmd.color('white', self.structurename)
                    for r in cluster1:
                        cmd.color('red', '%s and resi %s' % (self.structurename, r))
                        cmd.show('spheres', '%s and resi %s' % (self.structurename, r))
                    for r in cluster2:
                        cmd.color('green', '%s and resi %s' % (self.structurename, r))
                        cmd.show('spheres', '%s and resi %s' % (self.structurename, r))
                    for r in cluster3:
                        cmd.color('blue', '%s and resi %s' % (self.structurename, r))
                        cmd.show('spheres', '%s and resi %s' % (self.structurename, r))
                else:
                    cmd.hide('spheres', self.structurename)
                    cmd.color('white', self.structurename)
            elif self.show_et_options.getvalue() == self.et_options_tuple[2]:
                cmd.hide('spheres', self.structurename)
                for resnum, cov in list(self.dataObj.resnumsMaxL.items()):
                    if slideval >= cov:
                        # reslist.append(resnum)
                        if cov > maxcov:
                            maxcov = cov
                        cmd.color(self.etcolor, '%s and resi %s' % (self.structurename, resnum))
                        cmd.show('spheres', '%s and resi %s and name CA' % (self.structurename, resnum))
                    else:
                        # TODO: Color only the spheres?
                        cmd.color(self.noetcolor, '%s and resi %s' % (self.structurename, resnum))
                        cmd.hide('spheres', '%s and resi %s' % (self.structurename, resnum))
            # if self.show_et_var.get()==1:
            elif self.show_et_options.getvalue() == self.et_options_tuple[1]:
                for resnum, cov in list(self.dataObj.resnumsMaxL.items()):
                    if slideval >= cov:
                        # reslist.append(resnum)
                        if cov > maxcov:
                            maxcov = cov
                        cmd.color(self.etcolor, '%s and resi %s' % (self.structurename, resnum))
                        cmd.show('spheres', '%s and resi %s' % (self.structurename, resnum))
                    else:
                        # TODO: Color only the spheres?
                        cmd.color(self.noetcolor, '%s and resi %s' % (self.structurename, resnum))
                        cmd.hide('spheres', '%s and resi %s' % (self.structurename, resnum))
            else:
                # print self.dataObj.resnumsMaxL
                for resnum, cov in list(self.dataObj.resnumsMaxL.items()):
                    if slideval >= cov:
                        # reslist.append(resnum)
                        if cov > maxcov:
                            maxcov = cov
                        cmd.color(self.etcolor, '%s and resi %s' % (self.structurename, resnum))
                    else:
                        cmd.color(self.noetcolor, '%s and resi %s' % (self.structurename, resnum))
            ##            self.percentcoverage.setvalue(maxcov)
            self.rankvalue.setvalue(maxcov)
            # print "Residues with L>%f:" % (slideval)
            # print reslist

    def setRanksFileLocation(self, value):
        self.ranksfile.setvalue(value)


###################################################################

class ConductivityNetwork:
    def __init__(self):
        self.dataloaded = False

    def dataLoaded(self):
        return self.dataloaded

    def readData(self, datapath):
        self.network = {}  # (resnum1,resnum2): L value (resnum1<resnum2)
        self.resnumsMaxL = {}  # resnum: max L value
        self.dataloaded = False
        FILE = open(datapath, 'r')
        try:
            for line in FILE:
                if len(line.rstrip()) >= 27:
                    resnum1 = line[0:5].strip()
                    resnum2 = line[5:11].strip()
                    # Conductivity values between residues
                    # next to each other in sequence are usually
                    # large, so ignore these.
                    if int(resnum2) == int(resnum1) + 1 or \
                            int(resnum1) == int(resnum2) + 1:
                        continue
                    L = float(line[11:27].strip())
                    self.network[(resnum1, resnum2)] = L
                    try:
                        tmpL = self.resnumsMaxL[resnum1]
                        if tmpL < L:
                            self.resnumsMaxL[resnum1] = L
                    except KeyError:
                        self.resnumsMaxL[resnum1] = L
                    try:
                        tmpL = self.resnumsMaxL[resnum2]
                        if tmpL < L:
                            self.resnumsMaxL[resnum2] = L
                    except KeyError:
                        self.resnumsMaxL[resnum2] = L
        finally:
            FILE.close()
        if len(self.network) > 0:
            self.dataloaded = True


# A residue is part of the conductivity network if one of its
# links passes the conductivity threshold
# (rankspath -> datapath)
class ConductivityViewerGroup:
    def __init__(self,
                 page,
                 groupname='Conductivity Network',
                 defaultstructurename='(pdb)',
                 defaultrankspath='.dat',
                 etcolor='red',
                 noetcolor='white'):

        self.etcolor = etcolor
        self.noetcolor = noetcolor

        group = Pmw.Group(page, tag_text=groupname)
        group.pack(fill='both', expand=1, padx=10, pady=5)
        # Field for entering name of structure or model
        self.structure = Pmw.EntryField(group.interior(),
                                        labelpos='w',
                                        label_text='structure to use: ',
                                        value=defaultstructurename,
                                        )

        def quickFileValidation(s):
            if s == '':
                return Pmw.PARTIAL
            elif os.path.isfile(s):
                return Pmw.OK
            elif os.path.exists(s):
                return Pmw.PARTIAL
            else:
                return Pmw.PARTIAL

        # Button for assigning ranks to structure
        self.map_buttonbox = Pmw.ButtonBox(group.interior(), padx=0)
        self.map_buttonbox.add('Map conductivity to structure', command=self.mapRanks)

        # Field for entering ranks file
        self.ranksfile = Pmw.EntryField(group.interior(),
                                        labelpos='w',
                                        label_pyclass=FileDialogButtonClassFactory.get(self.setRanksFileLocation,
                                                                                       '*.dat'),
                                        validate={'validator': quickFileValidation, },
                                        value=defaultrankspath,
                                        label_text='Conductivity data file path:')

        # Tkinter Slider Tkinterfor changing ET residue coverage.
        # TODO: Use a Pmw implementation?
        self.slider = Scale(group.interior(),
                            # from_=self.min, to=self.max,
                            from_=0, to=2,
                            showvalue=1,
                            length=200,
                            orient=HORIZONTAL, resolution=0.01,
                            tickinterval=0,
                            # repeatinterval=500, #Dunno what repeatinterval is for...
                            repeatdelay=500
                            )
        self.slider.set(0.01)
        # self.slider.grid()
        self.slider.bind("<ButtonRelease-1>", self.slider_button_release)
        # self.slider.bind("<B1-Motion>", self.slider_button_release)

        # Field for displaying ET coverage
        ##        self.percentcoverage = Pmw.EntryField(group.interior(),
        ##                                              labelpos='w',
        ##                                              label_text='Percent coverage: ',
        ##                                              value='',
        ##                                              )
        self.rankvalue = Pmw.EntryField(group.interior(),
                                        labelpos='w',
                                        label_text='Minimum conductivity: ',
                                        value='',
                                        )

        self.et_options_tuple = ('Colors only(' + self.etcolor + ')',
                                 'as Spheres',
                                 'as Spheres (C-alpha only)')
        self.show_et_options = Pmw.OptionMenu(group.interior(),
                                              labelpos='w',
                                              label_text='Show network residues',
                                              items=self.et_options_tuple,
                                              initialitem=self.et_options_tuple[0],
                                              command=self.slider_button_release
                                              )
        ##        self.show_et_var=IntVar()
        ##        self.show_et_var.set(0)
        ##        self.show_et_checkbutton = Checkbutton(group.interior(),
        ##                                               text = "Show conductivity network residues (as spheres)",
        ##                                               variable = self.show_et_var)

        for entry in (self.structure,
                      self.ranksfile,
                      self.map_buttonbox,
                      self.slider,
                      ##                      self.percentcoverage,
                      self.rankvalue,
                      # self.show_et_checkbutton
                      self.show_et_options):
            entry.pack(fill='x', padx=4, pady=1)  # vertical

        self.dataObj = ConductivityNetwork()
        self.ranksStructureOK = False

    def mapRanks(self):
        structurename = self.structure.getvalue().strip()
        self.ranksStructureOK = False
        if len(structurename) < 1:
            print('Provide structure name!')
            return
        try:
            model = cmd.get_model(structurename)
        except Exception:
            print("Structure \'%s\' does not exist!" % structurename)
            return

        rankspath = self.ranksfile.getvalue().strip()
        if len(rankspath) < 1:
            print('Provide path to conductivity data file!')
            return

        self.dataObj.readData(rankspath)
        if self.dataObj.dataLoaded():
            print("Conductivity data file \'%s\' loaded" % rankspath)
        else:
            print("Error loading conductivity data file \'%s\'!" % rankspath)
            return
        # Check that the residue numbers in the model
        # and in the ranks file are identical
        ##        resnumDict={}
        ##        for i in range(len(model.atom)):
        ##            resnumDict[model.atom[i].resi]=1
        ##        if len(resnumDict)<1:
        ##            print 'Structure \'%s\' has no residues!' % structurename
        ##            return
        ##        if len(resnumDict)!=self.ranksObj.getSize():
        ##            print "Residue numbers in \'%s\' and \'%s\' are not the same!" % (structurename, rankspath)
        ##            return
        ##        for i in range(self.ranksObj.getSize()):
        ##            try:
        ##                r=resnumDict[self.ranksObj.getResnum(i)]
        ##            except KeyError:
        ##                print "Residue numbers in \'%s\' and \'%s\' do not match!" % (structurename, rankspath)
        ##                return

        self.structurename = structurename
        self.ranksStructureOK = True
        ##        print 'Residue numbers checked with structure \'%s\'' % structurename

        self.slider_button_release(None)

    def slider_button_release(self, event):
        if self.ranksStructureOK:
            reslist = []
            ##            maxcov=0
            minL = 100
            slideval = self.slider.get()
            if self.show_et_options.getvalue() == self.et_options_tuple[2]:
                cmd.hide('spheres', self.structurename)
                for resnum, L in list(self.dataObj.resnumsMaxL.items()):
                    if slideval < L:
                        reslist.append(resnum)
                        if L < minL:
                            minL = L
                        cmd.color(self.etcolor, '%s and resi %s' % (self.structurename, resnum))
                        cmd.show('spheres', '%s and resi %s and name CA' % (self.structurename, resnum))
                    else:
                        # TODO: Color only the spheres?
                        cmd.color(self.noetcolor, '%s and resi %s' % (self.structurename, resnum))
                        cmd.hide('spheres', '%s and resi %s' % (self.structurename, resnum))
            # if self.show_et_var.get()==1:
            elif self.show_et_options.getvalue() == self.et_options_tuple[1]:
                for resnum, L in list(self.dataObj.resnumsMaxL.items()):
                    if slideval < L:
                        reslist.append(resnum)
                        if L < minL:
                            minL = L
                        cmd.color(self.etcolor, '%s and resi %s' % (self.structurename, resnum))
                        cmd.show('spheres', '%s and resi %s' % (self.structurename, resnum))
                    else:
                        # TODO: Color only the spheres?
                        cmd.color(self.noetcolor, '%s and resi %s' % (self.structurename, resnum))
                        cmd.hide('spheres', '%s and resi %s' % (self.structurename, resnum))
            else:
                # print self.dataObj.resnumsMaxL
                for resnum, L in list(self.dataObj.resnumsMaxL.items()):
                    if slideval < L:
                        reslist.append(resnum)
                        if L < minL:
                            minL = L
                        cmd.color(self.etcolor, '%s and resi %s' % (self.structurename, resnum))
                    else:
                        cmd.color(self.noetcolor, '%s and resi %s' % (self.structurename, resnum))
            ##            self.percentcoverage.setvalue(maxcov)
            self.rankvalue.setvalue(minL)
            print("Residues with L>%f:" % (slideval))
            print(reslist)

    def setRanksFileLocation(self, value):
        self.ranksfile.setvalue(value)


###################################################################

class ETTools:

    def __init__(self, app):

        showmode = 1  # 0 - simple interface, for public. 1 - show (everything) more.

        if '_et_tools_pyetv_opened' not in cmd.__dict__:
            cmd._et_tools_pyetv_opened = False  # Value tells the plugin if it has been opened before
        numPrismColors = 100
        self.createPrismColors(numPrismColors)  # 100 is hardcoded into the Prismatic options above
        self.createRedGreenColors(numPrismColors) # 100 is hardcoded into the Prismatic options above
        self.createGreyscaleColors(numPrismColors)  # 100 is hardcoded into the Prismatic options above
        self.createDiffETColors(10)

        # Check if these members have been defined previously (e.g. by a .pml file)
        # These are used in the first Difference ET control group to load
        # structures and ranks files from a URL.
        if '_et_tools_structurename' not in cmd.__dict__:
            # cmd._et_tools_structurename='(pdb)'
            cmd._et_tools_structurename = ''
        if '_et_tools_structureurl' not in cmd.__dict__:
            cmd._et_tools_structureurl = ''
        if '_et_tools_rankspath1' not in cmd.__dict__:
            cmd._et_tools_rankspath1 = ''
        if '_et_tools_rankspath2' not in cmd.__dict__:
            cmd._et_tools_rankspath2 = '.ranks'
        if '_et_tools_ranksurl1' not in cmd.__dict__:
            cmd._et_tools_ranksurl1 = ''
        if '_et_tools_ranksurl2' not in cmd.__dict__:
            cmd._et_tools_ranksurl2 = ''
        # New options
        if '_et_tools_selectpage' not in cmd.__dict__:
            # This flag tells the plugin to select a particular page
            cmd._et_tools_selectpage = 'ET'
        if '_et_tools_structurename2' not in cmd.__dict__:
            cmd._et_tools_structurename2 = ''

        if len(cmd._et_tools_ranksurl1.strip()) > 0 and \
                len(cmd._et_tools_ranksurl2.strip()) > 0:
            # import urllib
            tmprankspath1 = urllib.request.urlretrieve(cmd._et_tools_ranksurl1)[0]
            tmprankspath2 = urllib.request.urlretrieve(cmd._et_tools_ranksurl2)[0]
            if os.path.getsize(tmprankspath1) > 0 and \
                    os.path.getsize(tmprankspath2) > 0:
                cmd._et_tools_rankspath1 = tmprankspath1
                cmd._et_tools_rankspath2 = tmprankspath2
            cmd._et_tools_ranksurl1 = ''
            cmd._et_tools_ranksurl2 = ''

        parent = app.root
        self.parent = parent

        self.dialog3 = Pmw.Dialog(parent,
                                  buttons=('Close',),
                                  title='PyETV: Processing, please wait...',
                                  command=self.close3)
        self.dialog3.withdraw()
        msg = tkinter.Label(self.dialog3.interior(),
                            text='            PyETV processing your request, please wait... Done      ',
                            # background = 'black',
                            # foreground = 'white',
                            # pady = 20,
                            )
        msg.pack()

        # Create another window for displaying Red_to_Green color ramp as legend
        self.dialog2 = Pmw.Dialog(parent,
                                  buttons=('Close',),
                                  title='PyETV Red_to_Green scale',

                                  command=self.close2)
        self.dialog2.withdraw()
        ramplength = 400
        rampthick = 40
        canvas = tkinter.Canvas(self.dialog2.interior(),
                                width=ramplength,
                                height=rampthick)
        canvas.pack()
        rampstripthick = ramplength / 100
        for i in range(numPrismColors):
            canvas.create_rectangle(i * rampstripthick,
                                    0,
                                    (i + 1) * rampstripthick,
                                    rampthick / 2,
                                    outline='grey',
                                    fill=("#%02x%02x%02x" % cmd._et_tools_prism_rg[i]))
        canvas.create_text(rampstripthick,
                           3 * rampthick / 4,
                           text='Important (low ET score)',
                           anchor='w')
        canvas.create_text(ramplength - rampstripthick,
                           3 * rampthick / 4,
                           text='Unimportant',
                           anchor='e')

        # Create another window for displaying gobstopper/prismatic color ramp as legend
        self.dialog1 = Pmw.Dialog(parent,
                                  buttons=('Close',),
                                  title='PyETV Prismatic/Gobstopper legend',
                                  command=self.close1)
        self.dialog1.withdraw()
        ramplength = 600
        rampthick = 40
        canvas = tkinter.Canvas(self.dialog1.interior(),
                                width=ramplength,
                                height=rampthick)
        canvas.pack()
        rampstripthick = ramplength / 100
        for i in range(numPrismColors):
            canvas.create_rectangle(i * rampstripthick,
                                    0,
                                    (i + 1) * rampstripthick,
                                    rampthick / 2,
                                    outline='grey',
                                    fill=("#%02x%02x%02x" % cmd._et_tools_prism_rgb[i]))
        canvas.create_text(rampstripthick,
                           3 * rampthick / 4,
                           text='Important (low ET percent coverage)',
                           anchor='w')
        canvas.create_text(ramplength - rampstripthick,
                           3 * rampthick / 4,
                           text='Unimportant',
                           anchor='e')

        # Create the main dialog.
        self.dialog = Pmw.Dialog(parent,
                                 buttons=('Exit PyETV',),
                                 title='PyETV PyMOL Evolutionary Trace Viewer 1.3',
                                 command=self.close)
        self.dialog.withdraw()
        Pmw.setbusycursorattributes(self.dialog.component('hull'))

        self.notebook = Pmw.NoteBook(self.dialog.interior())
        # self.notebook.pack(fill='both',expand=1,padx=10,pady=10)

        #####################################################################
        # Create as many ETControlGroup's as unique chains in a pdb structure
        # e.g. 1gotA, 1gotB, 1gotG
        #####################################################################
        #
        # First option:
        # Expect a list of structurenames
        # cmd._et_tools_structurenamelist
        # and URLs of ranks files
        # cmd._et_tools_ranksurllist
        ##        if cmd.__dict__.has_key('_et_tools_structurenamelist'):
        ##            import urllib
        ##            self.tracelist=[]
        ##            numstructures=len(cmd._et_tools_structurenamelist)
        ##            for i in range(numstructures):
        ##                structurename=cmd._et_tools_structurenamelist[i]
        ##                page=self.notebook.add(structurename)
        ##                try:
        ##                    rankspath=urllib.urlretrieve(cmd._et_tools_ranksurllist[i])[0]
        ##                except KeyError:
        ##                    rankspath=''
        ##                self.tracelist.append(ETControlGroup(page,
        ##                                       groupname=structurename,
        ##                                       defaultstructurename=structurename,
        ##                                       defaultrankspath=rankspath,
        ##                                       etcolor='red',
        ##                                       noetcolor='salmon'))
        ##                self.tracelist[-1].mapRanks()

        # Second option:
        # Expect a python list of structure names
        # cmd._et_tools_structurenamelist
        # and the URL of the directory containing the .zip of the trace results
        # cmd._et_tools_zipurl

        # Third option:
        # Expect a python list of structure names
        # cmd._et_tools_structurenamelist
        # and a list of URL's of etvx files
        # cmd._et_tools_etvxurllist
        # the ranks section will be extracted from the etvx file
        # We want the structure to show up immediately
        # after launching PyMOL. This can't be done using plugins.
        # The pdb structures may be loaded from the etvx files, or by
        # loading the full structure and selecting and creating objects
        # representing the required chains
        #
        if '_et_tools_structurenamelist' in cmd.__dict__:
            # import urllib
            self.tracelist = []
            numstructures = len(cmd._et_tools_structurenamelist)
            # Create a control group that controls all sliders
            if numstructures > 1:
                first_structure = cmd._et_tools_structurenamelist[0]
                last_structure = cmd._et_tools_structurenamelist[-1]
                if type(first_structure) == tuple:
                    firsttabname = first_structure[0] + first_structure[1]  # structure + chain indicator
                else:
                    firsttabname = first_structure
                if type(last_structure) == tuple:
                    lasttabname = last_structure[0] + last_structure[1]  # structure + chain indicator
                else:
                    lasttabname = last_structure
                # Sometimes, we get a name that ends with an '_', e.g. '1tem_'.
                # Underscores are not allowed in tab names for a notebook, so we have to get rid of it.
                mastergroupname = 'Control sliders from %s to %s' % (
                firsttabname.replace('_', ' '), lasttabname.replace('_', ' '))
                page = self.notebook.add('Complex')
                self.mastercontrol = ETMasterControlGroup(page,
                                                          groupname=mastergroupname,
                                                          slavelist=self.tracelist,
                                                          defaultetoption=3,
                                                          defaultslidercoverage=100)
                cmd._et_tools_selectpage = 'Complex'
            for i in range(numstructures):
                # This could be either a name of a PyMOL object ('1finA'), or a tuple of a PyMOL object and a chain indicator (('1fin','A'))
                structure_chain = cmd._et_tools_structurenamelist[i]
                if type(structure_chain) == tuple:
                    structurenameonly = structure_chain[0]
                    structurechain = structure_chain[1]
                    tabname = structurenameonly + structurechain
                else:
                    structurenameonly = structure_chain  # Assume a string
                    structurechain = ''
                    tabname = structurenameonly
                if numstructures > 1:
                    page = self.mastercontrol.notebook.add(tabname.replace('_', ' '))
                else:
                    page = self.notebook.add(tabname.replace('_', ' '))
                rankspath = ''
                try:
                    filepath = cmd._et_tools_etvxurllist[i].strip()
                    # Consider loading from URL or local file
                    if filepath.find('http') == 0:
                        # Check if file is .etvx or .ranks
                        if filepath[-5:] == '.etvx':
                            tmpetvxpath = urllib.request.urlretrieve(filepath)[0]
                            if os.path.getsize(tmpetvxpath) > 0:
                                rankspath = self.extractRanks(tmpetvxpath, tabname)
                        else:  # Assume file is in .ranks format
                            tmprankspath = urllib.request.urlretrieve(filepath)[0]
                            if os.path.getsize(tmprankspath) > 0:
                                rankspath = os.path.dirname(tmprankspath) + os.sep + tabname + '.ranks'
                                try:
                                    os.rename(tmprankspath, rankspath)
                                except OSError:
                                    # rankspath exists, try removing it first
                                    os.remove(rankspath)
                                    os.rename(tmprankspath, rankspath)
                    else:
                        # Check if file is .etvx or .ranks
                        if filepath[-5:] == '.etvx':
                            rankspath = self.extractRanks(filepath, tabname)
                        else:  # Assume file is in .ranks format
                            rankspath = filepath
                except KeyError:
                    pass

                if '_et_tools_colors' in cmd.__dict__:
                    try:
                        (etcolor_, noetcolor_) = cmd._et_tools_colors[cmd._et_tools_structurenamelist[i]]
                    except KeyError:
                        etcolor_ = 'red'
                        noetcolor_ = 'salmon'
                else:
                    etcolor_ = 'red'
                    noetcolor_ = 'salmon'
                # cmd.load(pdbpath,object=structurename)
                self.tracelist.append(ETControlGroup(page,
                                                     groupname=tabname,
                                                     defaultstructurename=structurenameonly,
                                                     defaultchain=structurechain,
                                                     defaultrankspath=rankspath,
                                                     defaultetoption=3,
                                                     defaultslidercoverage=100,
                                                     etcolor=etcolor_,
                                                     noetcolor=noetcolor_,
                                                     et_loader_on=False,
                                                     et_colorramp=self.dialog2,
                                                     et_colorPrism=self.dialog1,
                                                     et_messagebox=self.dialog3
                                                     ))
                self.tracelist[-1].mapRanks()
                # TODO: Next time the plugin is opened, should the etvx/ranks files be downloaded again?
            if numstructures > 1:
                page = self.mastercontrol.notebook.add('Zcoupling')
                self.couplingCalculatorComplex = CouplingGroup2(page,
                                                                groupname='Compute ET coupling z-score',
                                                                tracelist=self.tracelist,
                                                                et_messagebox=self.dialog3
                                                                )
                self.InterfaceGroupComplex = InterfaceControlGroup2(page,
                                                                    groupname='Mark interface between structures 1 and 2',
                                                                    tracelist=self.tracelist,
                                                                    defaultinterfaceoption1=1,
                                                                    defaultinterfaceoption2=1,
                                                                    et_messagebox=self.dialog3
                                                                    )
                self.mastercontrol.notebook.pack(fill='both', padx=4, pady=1)

        # Set up a page (aka tab or folder)
        page = self.notebook.add('ET')
        self.traceA = ETControlGroup(page,
                                     groupname='View Evolutionary Trace',
                                     defaultstructurename=cmd._et_tools_structurename,
                                     defaultrankspath=cmd._et_tools_rankspath1,
                                     etcolor='red',
                                     noetcolor='salmon',
                                     # interface_on=False
                                     et_colorramp=self.dialog2,
                                     et_colorPrism=self.dialog1,
                                     et_messagebox=self.dialog3
                                     )
        # self.traceA._group.grid(column=0,row=0)
        # commented by Hari
        '''if 1:  # showmode==1:
            page = self.notebook.add('ET2')
            self.traceB = ETControlGroup(page,
                                         groupname='View Evolutionary Trace 2',
                                         defaultstructurename=cmd._et_tools_structurename2,
                                         defaultrankspath=cmd._et_tools_rankspath2,
                                         etcolor='blue',
                                         noetcolor='lightblue',
                                         # interface_on=False,
                                         et_colorramp=self.dialog2,
                                         et_messagebox=self.dialog3
                                         )
            # self.traceB._group.grid(column=1,row=0)'''
        
        # Suggested to me and Panos by OL, 08/XX/12
        page = self.notebook.add('EA')
        try:  # TODO
            self.EvoAct = EAControlGroup(page,
                                         groupname='View Evolutionary Action',
                                         # defaultstructurename=cmd._et_tools_structurename,
                                         # defaultrankspath=cmd._et_tools_rankspath1,
                                         etcolor='red',
                                         noetcolor='green',
                                         # interface_on=False
                                         et_colorramp=self.dialog2,
                                         et_colorPrism=self.dialog1,
                                         et_messagebox=self.dialog3
                                         )
        except Exception as e:
            print(e)
        
        # commented by Hari
        '''if 1:  # showmode==1:
            page = self.notebook.add('Zcoupling')
            self.couplingCalculator1 = CouplingGroup(page,
                                                     groupname='Compute ET coupling z-score between Trace 1 and Trace 2',
                                                     trace1=self.traceA,
                                                     trace2=self.traceB,
                                                     et_messagebox=self.dialog3
                                                     )

            self.InterfaceGroup = InterfaceControlGroup(page,
                                                        groupname='Mark interface between structures 1 and 2',
                                                        defaultinterfaceoption1=0,
                                                        defaultinterfaceoption2=0,
                                                        et_messagebox=self.dialog3
                                                        )

        page = self.notebook.add('Assembly')
        self.AssemblyControl = ETAssemblyControlGroup(page,
                                                      groupname='Load Biological Unit from PISA',
                                                      defaultetoption=3,
                                                      defaultslidercoverage=100,
                                                      et_colorramp=self.dialog2,
                                                      et_messagebox=self.dialog3
                                                      )'''

        ##        page = self.notebook.add('More ET')
        ##        #Make as many of these as you like
        ##        self.traceC=ETControlGroup(page,
        ##                                   groupname='Trace C',
        ##                                   #defaultstructurename='query_1finA',
        ##                                   #defaultrankspath='ET_1finA.ranks',
        ##                                   etcolor='magenta',
        ##                                   noetcolor='pink')
        ##        self.traceD=ETControlGroup(page,
        ##                                   groupname='Trace D',
        ##                                   #defaultstructurename='query_1finB',
        ##                                   #defaultrankspath='ET_1finB.ranks',
        ##                                   etcolor='green',
        ##                                   noetcolor='palegreen')

        if showmode == 1:
            # Make as many of these as you like
            page = self.notebook.add('Diff ET')
            self.difftrace1 = DiffETControlGroup(page,
                                                 groupname='Difference trace',
                                                 defaultstructurename=cmd._et_tools_structurename,
                                                 defaultrankspath1=cmd._et_tools_rankspath1,
                                                 defaultrankspath2=cmd._et_tools_rankspath2,
                                                 et_messagebox=self.dialog3
                                                 )
        ##        page = self.notebook.add('Diff ET 2')
        ##        #Make as many of these as you like
        ##        self.difftrace2=DiffETControlGroup(page,
        ##                                           groupname='Diff trace',
        ##                                           )
        ##        page = self.notebook.add('Conductivity')
        ##        #Make as many of these as you like
        ##        self.network1=ConductivityViewerGroup(page,
        ##                                              groupname='Conductivity Network 1',
        ##                                              defaultstructurename='query_2phyA',
        ##                                              defaultrankspath='pyp-conductivity.dat',
        ##                                              etcolor='orange',
        ##                                              noetcolor='white')
        ##        self.network2=ConductivityViewerGroup(page,
        ##                                              groupname='Conductivity Network 2',
        ##                                              defaultstructurename='query_2phyA',
        ##                                              defaultrankspath='2phy_MI.dat',
        ##                                              etcolor='cyan',
        ##                                              noetcolor='white')
        ##        page = self.notebook.add('Pair ET')
        ##        #Make as many of these as you like
        ##        self.pairET=PairETControlGroup(page,
        ##                                         groupname='Pair ET',
        ##                                         defaultstructurename='query_2phyA',
        ##                                         defaultrankspath='2phyA.cvg_change',
        ##                                         etcolor='red',
        ##                                         noetcolor='white')

        # commented by Hari
        '''if showmode == 2:
            page = self.notebook.add('Interprotein ET')
            # Make as many of these as you like
            self.interproteintrace = InterproteinPairRankControlGroup(page,
                                                                      groupname='Interprotein ET Pair Network',
                                                                      defaultstructurename1='query_1f6mF',
                                                                      defaultstructurename2='query_1f6mH',
                                                                      defaultrankspath='1f6mFH.pp_rank_sorted')
        ##                                         defaultstructurename1='query_1finA',
        ##                                         defaultstructurename2='query_1finB',
        ##                                         defaultrankspath='1finAB.pp_rank_sorted')
        if showmode == 1:  # Possibly need this for EA work (09/01/14)
            page = self.notebook.add('Compute')
            # Make as many of these as you like
            self.SCWGroup = SCWGroup(page,
                                     groupname='Clustering z-score',
                                     # defaultstructurename='query_2phyA'
                                     )'''

        page = self.notebook.add('Credits')
        group = Pmw.Group(page, tag_text='Credits')
        group.pack(fill='both', expand=1, padx=10, pady=5)
        creditstext = '''
        PyETV PyMOL Evolutionary Trace Viewer
        Created by Rhonald Lua
        Modified by Harikumar Govindarajan
        Lichtarge Lab, Baylor College of Medicine
        Houston, Texas

        Protein interfaces, surfaces and assemblies service PISA
        at European Bioinformatics Institute
        (http://www.ebi.ac.uk/msd-srv/prot_int/pistart.html),
        authored by E. Krissinel and K. Henrick

        The code for creating a file selection dialog box was taken from
        apbs_tools.py of M. Lerner.
        Thank you to W. L. DeLano for PyMOL, and to the developers of
        apbs_tools.py and remote_pdb_load.py for providing these examples
        on how to write a PyMOL plugin, and to others who made their
        python scripts available on the Web.

        '''
        credits = tkinter.Label(group.interior(),
                                text=creditstext,
                                background='black',
                                foreground='white',
                                # pady = 20,
                                )
        credits.pack(expand=1, fill='both', padx=4, pady=4)

        # Test
        # cmd.load('query_1finA.pdb')
        # cmd.load('query_1finB.pdb')

        # if cmd._et_tools_selectpage=='Diff ET 1':
        #    self.notebook.selectpage('Diff ET 1')
        # elif cmd._et_tools_selectpage=='ET':
        #    self.notebook.selectpage('ET')

        if cmd._et_tools_selectpage == 'Diff ET 1':  # Backward compatibility
            self.notebook.selectpage('Diff ET')

        # Sometimes, we get a name that ends with an '_', e.g. '1tem_'.
        # Underscores are not allowed in tab names for a notebook, so we have to get rid of it.
        if cmd._et_tools_selectpage.replace('_', ' ') in self.notebook.pagenames():
            self.notebook.selectpage(cmd._et_tools_selectpage.replace('_', ' '))

        self.notebook.pack(fill='both', expand=1, padx=10, pady=10)
        self.notebook.setnaturalsize()
        self.dialog.show()

        cmd._et_tools_pyetv_opened = True

    def close(self, result):
        if 1:
            # if result=='Exit ET tools':
            if __name__ == '__main__':
                #
                # dies with traceback, but who cares
                #
                self.parent.destroy()
            else:
                # self.dialog.deactivate(result)
                self.dialog.withdraw()  # This just hides the window, does not destroy and free resources?
                self.dialog1.withdraw()
                self.dialog2.withdraw()
                self.dialog3.withdraw()

    def close1(self, result):
        if 1:
            if 0:  # __name__ == '__main__':
                #
                # dies with traceback, but who cares
                #
                self.parent.destroy()
            else:
                # self.dialog.deactivate(result)
                self.dialog1.withdraw()  # This just hides the window, does not destroy and free resources?
    def close2(self, result):
        if 1:
            if 0:  # __name__ == '__main__':
                #
                # dies with traceback, but who cares
                #
                self.parent.destroy()
            else:
                # self.dialog.deactivate(result)
                self.dialog2.withdraw()  # This just hides the window, does not destroy and free resources?

    def close3(self, result):
        if 1:
            if 0:  # __name__ == '__main__':
                #
                # dies with traceback, but who cares
                #
                self.parent.destroy()
            else:
                # self.dialog.deactivate(result)
                self.dialog3.withdraw()  # This just hides the window, does not destroy and free resources?

    def createPrismColors(self, numcolors):
        if cmd._et_tools_pyetv_opened:
            return  # Do the following only once
        cmd._et_tools_prism_rgb = []
        # hexlist=[]
        for i in range(numcolors):
            hsv = (i * 0.8 / numcolors, 1.0, 1.0)
            # David's color scheme
            # reversegray=False #True or False
            # if reversegray:
            #    hsv=(0,0,(i+1)*1.0/numcolors)
            # else:
            #    hsv=(0,0,(numcolors-i)*1.0/numcolors)
            rgb = colorsys.hsv_to_rgb(hsv[0], hsv[1], hsv[2])
            cmd._et_tools_prism_rgb.append((int(255 * rgb[0]),
                                            int(255 * rgb[1]),
                                            int(255 * rgb[2])))  # For creating a color ramp in Tkinter.Canvas
            # Make a perl list of prismatic colors in hex
            # hexlist.append("\"%.2x%.2x%.2x\"" % (int(255*rgb[0]), int(255*rgb[1]), int(255*rgb[2])))
            cmd.set_color('_et_prism_color%d' % i, rgb)
        # print string.join(hexlist,',')

    def createRedGreenColors(self, numcolors):
        if cmd._et_tools_pyetv_opened:
            return  # Do the following only once
        cmd._et_tools_prism_rg = []
        # hexlist=[]
        for i in range(numcolors):
            hsv = (i * 0.33 / numcolors, 1.0, 1.0)

            # reversegray=False #True or False
            # if reversegray:
            #    hsv=(0,1,(i+1)*1.0/numcolors)
            # else:
            #    hsv=(0,1,(numcolors-i)*1.0/numcolors)
            rg = colorsys.hsv_to_rgb(hsv[0], hsv[1], hsv[2])
            cmd._et_tools_prism_rg.append((int(255 * rg[0]),
                                            int(255 * rg[1]),
                                            int(255 * rg[2])))  # For creating a color ramp in Tkinter.Canvas
            # Make a perl list of prismatic colors in hex
            # hexlist.append("\"%.2x%.2x%.2x\"" % (int(255*rgb[0]), int(255*rgb[1]), int(255*rgb[2])))
            cmd.set_color('_et_rg_color%d' % i, rg)
        # print string.join(hexlist,',')

    def createGreyscaleColors(self, numcolors):
        if cmd._et_tools_pyetv_opened:
            return  # Do the following only once
        cmd._et_tools_greyscale_rgb = []
        # David's color scheme
        reversegray = False  # True or False
        for i in range(numcolors):
            if reversegray:
                hsv = (0, 0, (i + 1) * 1.0 / numcolors)
                # hsv=(1,1,(i+1)*1.0/numcolors) #Hue=0 or 1 for red
                # hsv=(0.33,1,(i+1)*1.0/numcolors)
                # hsv=(0.66,1,(i+1)*1.0/numcolors)
            else:
                hsv = (0, 0, (numcolors - i) * 1.0 / numcolors)
                # hsv=(1,1,(numcolors-i)*1.0/numcolors)
                # hsv=(0.33,1,(numcolors-i)*1.0/numcolors)
                # hsv=(0.66,1,(numcolors-i)*1.0/numcolors)
            rgb = colorsys.hsv_to_rgb(hsv[0], hsv[1], hsv[2])
            cmd._et_tools_greyscale_rgb.append((int(255 * rgb[0]),
                                                int(255 * rgb[1]),
                                                int(255 * rgb[2])))  # For creating a color ramp in Tkinter.Canvas
            cmd.set_color('_et_greyscale_color%d' % i, rgb)

    def createDiffETColors(self, numcolors):
        # Make red and blue spectrum of colors for difference ET
        for i in range(numcolors):
            # hsv=(0.0,(i+1)*1.0/numcolors,1.0) #red
            hsv = (0.3, (i + 1) * 1.0 / numcolors, 1.0)  # red
            rgb = colorsys.hsv_to_rgb(hsv[0], hsv[1], hsv[2])
            cmd.set_color('_et_red_color%d' % i, rgb)
            hsv = (0.6, (i + 1) * 1.0 / numcolors, 1.0)  # blue(ish)
            rgb = colorsys.hsv_to_rgb(hsv[0], hsv[1], hsv[2])
            cmd.set_color('_et_blue_color%d' % i, rgb)

    def extractRanks(self, etvxpath, pdb_chain):
        # Get the ranks section
        FILE = open(etvxpath, 'r')
        data = ''
        ready = False
        try:
            for line in FILE:
                if line[:5] == '~tree':
                    break
                if ready:
                    data += line
                if line[:9] == '~ET_ranks':
                    ready = True
        finally:
            FILE.close()

        # Write ranks section to file
        ranksfilename = os.path.dirname(etvxpath) + os.sep + pdb_chain + '.ranks'
        FILE_RANKS = open(ranksfilename, 'w')
        FILE_RANKS.write(data)
        FILE_RANKS.close()

        return ranksfilename


# Create demo in root window for testing.
if __name__ == '__main__':
    class App:
        def my_show(self, *args, **kwargs):
            pass


    app = App()
    app.root = Tk()
    Pmw.initialise(app.root)
    app.root.title('Exit button for __main__')

    widget = ETTools(app)
    exitButton = Button(app.root, text='Exit', command=app.root.destroy)
    exitButton.pack()
    app.root.mainloop()

# Problem: command line scripts (e.g. pymol xxx.pml) are executed
# before this plugin file is fully read.
##def Set_Diff_ET_Input(structurename,rankspath1,rankspath2):
##    cmd._et_tools_structurename=structurename
##    cmd._et_tools_rankspath1=rankspath1
##    cmd._et_tools_rankspath2=rankspath2

##def Set_Diff_ET_Input_URL(structurename,ranksurl1,ranksurl2):
##    import urllib #Learned this trick from remove_pdb
##    rankspath1 = urllib.urlretrieve(ranksurl1)[0]
##    rankspath2 = urllib.urlretrieve(ranksurl2)[0]
##    cmd._et_tools_structurename=structurename
##    cmd._et_tools_rankspath1=rankspath1
##    cmd._et_tools_rankspath2=rankspath2

# cmd.extend('Set_Diff_ET_Input', Set_Diff_ET_Input)
# cmd.extend('Set_Diff_ET_Input_URL', Set_Diff_ET_Input_URL)