#!/usr/bin/env python
"""A class for the graphical interface used to trace the RNA backbone."""

# Copyright 2010, 2011 Kevin Keating
# 
# Licensed under the Educational Community License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
# 
# http://www.osedu.org/licenses/ECL-2.0
# 
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an "AS IS"
# BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
# or implied. See the License for the specific language governing
# permissions and limitations under the License.

from coot import imol_refinement_map, new_generic_object_number, set_display_generic_object, close_generic_object, graphics_draw, set_rotation_centre, to_generic_object_add_line, rotation_centre_position, is_closed_generic_object_p, show_select_map_dialog, zoom_factor, screen_vectors_py, add_status_bar_text
screen_vectors = screen_vectors_py
def rotation_centre():   return [rotation_centre_position(0), rotation_centre_position(1), rotation_centre_position(2)]
    #rotation_centre is defined in coot_utils.py, but you have to exec coot_utils.py (rather than import it)
    #since we only need this one function, it's easier to redefine it here
import gtk
import os
from copy import deepcopy
#from pprint import pprint   #for debugging
#from cPickle import dump    #for debugging
#from time import sleep      #for debugging

from nextPhos import NextPhos
from strucCalc import plus, minus, rotateAtoms, dist
from rotamerSeq import determineRotamerSeq, determinePucker
from calcCoords import calcCoords, recalcCoords, buildOnlyPhosOxy
from reviewSuitesGui import ReviewSuitesGui
from guiUtils import buttonWithIcon, HBOX_SPACING, VBOX_SPACING, BUTTON_SPACING, createRCraneWindowObject
from pseudoMolecule import PseudoMolecule
from selectMapDialog import selectMapDialog
from citationPopup import createCitationPopup
from pseudoMolecule import CONNECTED_DISTANCE_CUTOFF
from reportInputErrors import STANDARD_BASES

srcPath = os.path.dirname(os.path.abspath(__file__))

CROSS_LENGTH_PHOS = 0.3
CROSS_COLOR_PHOS  = "green"
CROSS_LENGTH_C1   = 0.2
CROSS_COLOR_C1    = "cyan"
CANDIDATE_PHOS_TITLE = "Candidate phosphate positions"
CANDIDATE_C1_TITLE = "Candidate C1' positions"
BOX_LENGTH = 0.3
BATON_COLOR = "orange"
BATON_TITLE = "Currently Selected Peak"
ADJUST_PHOS_TITLE = "Manually adjusted phosphate position"
CONNECTED_DISTANCE_CUTOFF_PHOS = 1.5
    #how close do two phosphates have to be before we consider them the "same" phosphate and join them
    #when doing an extend chain

class TraceGui:
    """A class for the graphical interface used to trace the RNA backbone."""
    
    def __init__(self, direction = 3, curCoords = None, existingMolecule = None, resIndexToExtend = None):
        """Initialize a TraceGui object and create a GUI for tracing a molecule        
        
        OPTIONAL ARGUMENTS:
            direction      - which direction to trace the chain: 3 implies 5'->3'
                                                                 5 implies 3'->5'
                             defaults to 3 (5'->3')
            curCoords      - the current (x,y,z) coordinates for where to start building
                             if not given, the screen center is used
            existingMolecule - an existing molecule to extend
                               if given, must also give resToExtend
            resIndexToExtend - the residue of existingMolecule to extend from
                               if given, must also give existingMolecule
        RETURNS:
            an initialized gui object
        EFFECTS:
            Creates and displays an RCrane dialog box
        """
        
        #create the citation pop-up if necessary
        if existingMolecule is not None:
            #if this is an extendChain call, then the user has already been shown the citation pop-up
            createCitationPopup()
        
        if curCoords is None:
            curCoords = rotation_centre()

        #initialize the object
        if existingMolecule:
            self.__pseudoMolecule = existingMolecule
            self.__resIndexToExtend = resIndexToExtend
        else:
            self.__pseudoMolecule = PseudoMolecule()
            self.__resIndexToExtend = None
        
        self.__direction = direction
        self.__nextPhos = NextPhos(phosDistFilename  = os.path.join(srcPath, "data/phosDistSmoothed.csv"),
                                   phosAngleFilename = os.path.join(srcPath, "data/phosAngleSmoothed.csv"),
                         sugarPhosSugarAngleFilename = os.path.join(srcPath, "data/sugarPhosSugarAngleSmoothed.csv"),
                                       basesFilename = os.path.join(srcPath, "data/bases.pdb"),
                                   pseudoChiFilename = os.path.join(srcPath, "data/pseudoChiSmoothed.csv"))
        
        self.__candidatePhosphates   = None
        self.__currentPhosphateIndex = 0
        
        self.__candidateC1s     = None
        self.__currentC1Index   = 0
        
        #a list of base locations in the format self.__candidateBases[phosphateIndex][C1Index] = [base type, base coords]
        #base type is stored for each coordinate because bases are mutated lazily
        #(i.e we only mutate the base that is currently displayed, so when we display a base, we may have to mutate it
        #if the base type in the self.__candidateBases array is different than self.__baseType)
        self.__candidateBases = None
        self.__baseType = "C"
        
        #Coot drawing objects
        self.__candidatePhosObject  = None
        self.__candidateC1Object    = None
        self.__batonObject          = None
        
        #coordinates that have been set by the user (via "Manually Adjust" or "Anti/Syn Flip" buttons)
        self.__customPhosphate = None
        #self.__customC1        = None
        self.__customBase      = None
        
        #drawing objects for manually adjusting coordinates
        self.__customPhosObject = None
        
        #coordinates before the user begins manually adjusting them
        self.__adjustPhosOrig = None
        self.__adjustPhosWindow = None
        
        self.__adjustBaseOrig = None
        self.__adjustBaseWindow = None
        
        self.__translatedPhosCoords = None
        self.__adjustedBaseCoords = None
        
        self.__previousPhosTranslateValue = [0,0,0]
        self.__previousBaseTranslateValue = [0,0,0]
        self.__previousBaseRotateValue = [0,0,0]
        self.__previousBaseRotateValueChi = 0
        
        self.__deleteHandlerID = None
        
        self.__tracedInitialPhos = False
        
        if existingMolecule is None:
            #create GUI for selecting the initial atom (using nextPhos.firstPhos)
            self.selectInitialPhos(curCoords)
        else:
            self.__extendChainSetup()
    
    
    def selectInitialPhos(self, curCoords):
        """Create a GUI for building the initial phosphate
        
        ARGUMENTS:
           curCoords - the current (x,y,z) coordinates for where to start building
        RETURNS:
           none
        EFFECTS:
           creates a GUI for building the initial phosphate for a chain trace
        REQUIREMENTS:
           a refinement map must be selected (i.e. imol_refinement_map must return a valid map ID)
               otherwise this function will display a map selection dialog and then return
        """
        
        #get the map that Coot is currently using
        mapNum = imol_refinement_map()
        
        #if there's no selected map, then show the select map dialog and return
        if mapNum == -1:
            print "No refinement map set for RCrane building"
            #add_status_bar_text("No refinement map set for RCrane building")
            #show_select_map_dialog()
            selectMapDialog(lambda: TraceGui(curCoords))
            return
        
        #create a window for selecting peaks
        window = createRCraneWindowObject()
        self.__deleteHandlerID = window.connect("delete_event", self.__initialPhosCloseWin)
        
        #initially, simply use the window to inform the user that peak selection is currently being done (since it will probably take a while)
        waitLabel = gtk.Label("\n  Calculating potential phosphate coordinates...  \n\n  Please wait...  \n")
        waitLabel.set_justify(gtk.JUSTIFY_CENTER)
        window.add(waitLabel)
        window.show_all()
        
        #make sure the window gets drawn before we start searching for phosphates
        while gtk.events_pending():
            gtk.main_iteration(False)
        
        
        #find all nearby electron density peaks
        self.__candidatePhosphates = self.__nextPhos.firstPhos(mapNum, curCoords)
        #self.__candidatePhosphates.append(curCoords)
        
        #draw crosses for each candidate peak
        self.__drawPhosPeaks()
        
        #highlight the first peak
        self.__drawBox(self.__candidatePhosphates[0])
        
        #create all buttons and labels
        topLabel        = gtk.Label     ("  Select starting phophate location:  ")
        nextButton      = buttonWithIcon("  Next  ",        "gtk-media-next")
        previousButton  = buttonWithIcon("  Previous  ",    "gtk-media-previous")
        adjustButton    = gtk.Button    ("  Manually Adjust  ")
        acceptButton    = buttonWithIcon("  Accept  ",      "gtk-ok")
        cancelButton    = buttonWithIcon("  Cancel  ",      "gtk-cancel")
        
        #connect the buttons to the appropriate function
        nextButton.connect      ("clicked", self.__initialPhosNext)
        previousButton.connect  ("clicked", self.__initialPhosPrevious)
        adjustButton.connect    ("clicked", self.__createAdjustInitialPhosDialog, window)
        acceptButton.connect    ("clicked", self.__initialPhosAccept, window)
        cancelButton.connect    ("clicked", self.__initialPhosCancel, window)
        
        #pack the buttons and lables into the window
        windowBox = gtk.VBox(False, 5)
        window.remove(waitLabel)
        window.add(windowBox)
        window.resize(1,1) #remove any size constraints that were caused by the waiting message
        
        frame = gtk.Frame()
        windowBox.pack_start(frame, False, False, 2)
        
        frameBox = gtk.VBox(False, 0)
        frame.add(frameBox)
        
        frameBox.pack_start(topLabel, False, False, HBOX_SPACING)
        nextPrevBox = gtk.HBox(True, HBOX_SPACING)
        nextPrevBox.pack_start(previousButton, True, True, BUTTON_SPACING)
        nextPrevBox.pack_start(nextButton, True, True, BUTTON_SPACING)
        frameBox.pack_start(nextPrevBox, False, False, BUTTON_SPACING)
        
        adjustBox = gtk.HBox(False, 0)
        adjustBox.pack_start(adjustButton, True, False, BUTTON_SPACING)
        frameBox.pack_start(adjustBox, False, False, BUTTON_SPACING)
        
        separator = gtk.HSeparator()
        windowBox.pack_start(separator)
        
        acceptCancelBox = gtk.HBox(False, HBOX_SPACING)
        acceptCancelBox.pack_start(acceptButton, True, False, BUTTON_SPACING)
        acceptCancelBox.pack_start(cancelButton, True, False, BUTTON_SPACING)
        windowBox.pack_start(acceptCancelBox, False, False, BUTTON_SPACING)
        
        #make the accept button the default focus
        acceptButton.grab_focus()
        
        #display the new window
        window.show_all()
    
    
    def selectNextNt(self, window = None, initialNtCoords = None, initialNtType = None, initialPhos = None):
        """Create a GUI for building the next nucleotide
        
        OPTIONAL ARGUMENTS:
           window          - a pyGTK+ window object (from the previous iteration of selectNextNt, if there was one)
           initialNtCoords - a dictionary of atom coordinates indicating what the initially selected nucleotide should be
                             This is typically used if the user has just clicked "Previous Nt"
                             If not given, the the most likely predicted base location is used
           initialNtType   - what type of base (i.e. A, C, G, or T) should be initially selected
                             this is typically used if the user has just clicked "Previous Nt"
                             If not given, then self.__baseType is used.
                             If initialNtCoords is given, then this argument MUST match the base type described by initialNtCoords.
           initialPhos     - the coordinates for the initially selected phosphate should be
                             If this is given, then initialNtCoords and must *NOT* be given
        RETURNS:
           none
        EFFECTS:
           creates a GUI for building the next nucleotide of a chain trace
        REQUIREMENTS:
           a refinement map must be selected (i.e. imol_refinement_map must return a valid map ID)
        """
        
        #get the map that Coot is currently using
        mapNum = imol_refinement_map()
        
        #create a window if we don't already have one
        if window is None:
            window = createRCraneWindowObject()
        else:
            #if we do have one, them empty it
            windowChild = window.get_child()
            window.remove(windowChild)
            window.disconnect(self.__deleteHandlerID)
        
        #make sure that closing the window will invoke the proper function
        self.__deleteHandlerID = window.connect("delete_event", self.__nextNtCloseWin)
        
        #initially, simply use the window to inform the user that peak selection is currently being done (since it will probably take a while)
        waitLabel = gtk.Label("\n  Calculating potential phosphate coordinates...  \n\n  Please wait...  \n")
        waitLabel.set_justify(gtk.JUSTIFY_CENTER)
        window.add(waitLabel)
        window.show_all()
        
        #make sure the window gets drawn before we start searching for phosphates
        while gtk.events_pending():
            gtk.main_iteration(False)
        
        
        #figure out the coordinates needed to predict the next phosphate locations
        if self.__resIndexToExtend is None:
            if self.__direction == 3:
                resIndexToExtend = self.__pseudoMolecule.getNumNts() - 1
            else: #self.__direction == 5
                resIndexToExtend = 0
        else:
            resIndexToExtend = self.__pseudoMolecule.resInsertionPoint
        
        prevPhos = None
        prevSugar = None
        prevPrevPhos = None
        
        if self.__direction == 3:
            prevPhos = self.__pseudoMolecule.getPhosCoordsFromIndex(resIndexToExtend)
            
            if self.__pseudoMolecule.connectedToPrevFromIndex(resIndexToExtend) or self.__numNtsTraced() > 1:
                prevSugar    = self.__pseudoMolecule.getSugarCoordsFromIndex(resIndexToExtend - 1)
                prevPrevPhos = self.__pseudoMolecule.getPhosCoordsFromIndex(resIndexToExtend - 1)
                
        else: #self.__direction == 5
            prevPhos = self.__pseudoMolecule.getPhosCoordsFromIndex(resIndexToExtend)
            
            if self.__pseudoMolecule.connectedToNextFromIndex(resIndexToExtend) or self.__numNtsTraced() > 1:
                prevSugar    = self.__pseudoMolecule.getSugarCoordsFromIndex(resIndexToExtend)
                    #we can calculate prevSugar even if this nucleotide isn't connected to the next one, but it won't do us any good since we won't have
                    #coordinates for prevPrevPhos 
                prevPrevPhos = self.__pseudoMolecule.getPhosCoordsFromIndex(resIndexToExtend + 1)
        
        #print "prevPhos     = ", prevPhos    
        #print "prevSugar    = ", prevSugar   
        #print "prevPrevPhos = ", prevPrevPhos
        
        if prevPrevPhos is None:
            (self.__candidatePhosphates, self.__candidateC1s) = self.__nextPhos.secondPhos(mapNum, prevPhos, self.__direction)
        else:
            (self.__candidatePhosphates, self.__candidateC1s) = self.__nextPhos.nextPhos  (mapNum, prevPhos, prevPrevPhos, prevSugar, self.__direction)
        
        ##find candidate phosphate positions
        #if self.__pseudoMolecule.getNumNts() == 1:
        #    #if this is the second nucleotide, then we can't factor in phosphate angles
        #    (self.__candidatePhosphates, self.__candidateC1s) = self.__nextPhos.secondPhos(mapNum, prevPhos, self.__direction)
        #elif self.__direction == 3:
        #    (self.__candidatePhosphates, self.__candidateC1s) = self.__nextPhos.nextPhos  (mapNum, prevPhos, self.__pseudoMolecule.getPhosCoords(-2), self.__pseudoMolecule.getSugarCoords(-2), self.__direction)
        #else: #self.__direction == 5
        #    (self.__candidatePhosphates, self.__candidateC1s) = self.__nextPhos.nextPhos  (mapNum, prevPhos, self.__pseudoMolecule.getPhosCoords(2), self.__pseudoMolecule.getSugarCoords(0), self.__direction)
        
        #initialize the __currentC1Index array
        self.__currentC1Index = [0] * len(self.__candidatePhosphates)
        
        #initialize the __candidateBases array
        self.__candidateBases = []
        for curC1s in self.__candidateC1s:
            self.__candidateBases.append([None] * len(curC1s))
        
        #draw the candidate phosphate peaks
        self.__drawPhosPeaks()
        
        self.__customPhosphate = None
        self.__customBase = None
        if initialNtType is not None:
            self.__baseType = initialNtType
        
        self.__currentPhosphateIndex = 0
        
        if initialNtCoords is not None or initialPhos is not None:
            if initialPhos is not None:
                initialPhosCoords = initialPhos
            else:
                initialPhosCoords = initialNtCoords["P"]
            
            #determine if it was a custom phosphate position or not
            for (index, coords) in enumerate(self.__candidatePhosphates):
                if coords[0] == initialPhosCoords[0] and coords[1] == initialPhosCoords[1] and coords[2] == initialPhosCoords[2]:
                    #Note that here (and below in the C1' loop) we're comparing floating point numbers using == and expecting sane results.
                    #This is normally a terrible idea due to rounding errors; however, the numbers we're comparing here have all
                    #come out of the exact same deterministic Coot function, so any values that are supposed to be equal
                    #should be exactly equal, not just equal in their relevant significant figures.
                    self.__currentPhosphateIndex = index
                    #print "Using phosphate index " + str(index)
                    break
            else:
                #if we haven't found a matching phosphate, then the user manually set these phosphate coordinates
                self.__customPhosphate = initialPhosCoords
                
                #which means that we don't know which C1' coordinates were used
                self.__customBase = [self.__baseType, deepcopy(initialNtCoords)]
                del self.__customBase[1]["P"] #we don't want the phoshate coordinate in the __customBase dictionary
                
                #print "Using custom phosphate"
            
            
            #if this function was called with initialPhos (rather than initialNtCoords) then we don't have to worry about
            #initial base coordinates
            #otherwise, if the phosphate was a standard one (i.e. non-custom coordaintes), then we should 
            #determine if there was a standard or custom base position
            if initialPhos is None and self.__customPhosphate is None:
                initialC1Coords = initialNtCoords["C1'"]
                
                for (index, coords) in enumerate(self.__candidateC1s[self.__currentPhosphateIndex]):
                    if coords[0] == initialC1Coords[0] and coords[1] == initialC1Coords[1] and coords[2] == initialC1Coords[2]:
                        self.__currentC1Index[self.__currentPhosphateIndex] = index
                        #print "Using C1' index " + str(index)
                        break
                    #else:
                    #    print "C1' doesn't match:"
                    #    pprint(coords)
                else:
                    #if we haven't found a matching phosphate, then the user manually set the base coordinates
                    self.__customBase = [self.__baseType, deepcopy(initialNtCoords)]
                    del self.__customBase[1]["P"] #we don't want the phoshate coordinate in the __customBase dictionary
                    #print "Using custom C1'"
                    #pprint(self.__customBase)
                
                #draw the C1' peaks
                self.__drawC1Peaks()
        else:
            #draw the C1' peaks
            self.__drawC1Peaks()
        
        
        #these variables are just to save typing
        phosIndex = self.__currentPhosphateIndex
        c1index = self.__currentC1Index[self.__currentPhosphateIndex]
        
        #calculate a base location for the current phosphate and C1' combination
        self.__candidateBases[phosIndex][c1index] = self.__nextPhos.findBase(mapNum, self.__candidateC1s[phosIndex][c1index], prevPhos, self.__candidatePhosphates[phosIndex], self.__baseType, self.__direction)
        
        #if we've been given an initialNtCoords, find out if the user set a custom base position but didn't move C1'
        if initialNtCoords is not None and self.__customBase is None:
            for (atomName, coords) in self.__candidateBases[phosIndex][c1index][1].iteritems():
                if initialNtCoords[atomName][0] != coords[0] or initialNtCoords[atomName][1] != coords[1] or initialNtCoords[atomName][2] != coords[2]:
                    #there is a custom base position
                    self.__customBase = [self.__baseType, deepcopy(initialNtCoords)]
                    del self.__customBase[1]["P"] #we don't want the phoshate coordinate in the __customBase dictionary
                    #print "Using custom base because of atom " + atomName
                    break
        
        
        #determine what coordinates we should draw
        curPhosCoords = None
        curC1Coords = None
        curBaseCoords = None
        
        #determine which phosphate coordinates to display initially
        if self.__customPhosphate is None:
            curPhosCoords = self.__candidatePhosphates[phosIndex]
        else:
            curPhosCoords = self.__customPhosphate
            #if we're starting with custom phosphate coordinates, then we need to draw a custom phosphate cross
            self.__drawAdjustPhosCross(curPhosCoords)
        
        #determine base and C1' coordinates
        if self.__customBase is None:
            curC1Coords = self.__candidateC1s[phosIndex][c1index]
            curBaseCoords = self.__candidateBases[phosIndex][c1index]
        else:
            curC1Coords = self.__customBase[1]["C1'"]
            curBaseCoords = self.__customBase
        
        #draw the selected candidate nucleotide
        self.__drawNt(prevPhos, curC1Coords, curPhosCoords, curBaseCoords)
        
        #create all buttons and labels
        topLabel            = gtk.Label     ("Trace the next nucleotide:")
        nextPhosButton      = buttonWithIcon("  Next Phos  ",        "gtk-media-next")
        prevPhosButton      = buttonWithIcon("  Previous Phos ",    "gtk-media-previous")
        adjustPhosButton    = gtk.Button    ("  Manually Adjust  ")
        
        nextC1Button      = buttonWithIcon(u"  Next C1\N{prime}  ",        "gtk-media-next")
        prevC1Button  = buttonWithIcon(u"  Previous C1\N{prime}  ",    "gtk-media-previous")
        adjustBaseButton    = gtk.Button    ("  Manually Adjust  ")
        
        baseSelect = gtk.combo_box_new_text()
        baseSelect.append_text("Adenine (A)")
        baseSelect.append_text("Cytosine (C)")
        baseSelect.append_text("Guanine (G)")
        baseSelect.append_text("Uracil (U)")
        
        #set the current base selection based on self.__baseType
        baseTypeNum = {"A":0, "C":1, "G":2, "U":3}[self.__baseType]
        baseSelect.set_active(baseTypeNum)
        
        flipBaseButton = gtk.Button("  Anti/Syn Flip  ")
        
        acceptButton        = buttonWithIcon("  Accept Nt  ",      "gtk-ok")
        prevNtButton        = buttonWithIcon("  Previous Nt  ",    "gtk-cancel")
        buildBackboneButton = buttonWithIcon("  Build Backbone  ", "gtk-apply")
        closeButton         = buttonWithIcon("  Delete Trace  ",   "gtk-close")
        
        #connect the buttons to the appropriate functions
        nextPhosButton.connect("clicked", self.__nextNtNextPhos)
        prevPhosButton.connect("clicked", self.__nextNtPrevPhos)
        adjustPhosButton.connect("clicked", self.__createAdjustPhosDialog, window)
        
        nextC1Button.connect("clicked", self.__nextNtNextC1)
        prevC1Button.connect("clicked", self.__nextNtPrevC1)
        adjustBaseButton.connect("clicked", self.__createAdjustBaseDialog, window)
        
        acceptButton.connect("clicked", self.__nextNtAccept, window)
        prevNtButton.connect("clicked", self.__nextNtPrevNt, window)
        buildBackboneButton.connect("clicked", self.__nextNtBuildBackbone, window)
        closeButton.connect("clicked", self.__nextNtClose, window)
        
        baseSelect.connect("changed", self.__nextNtBaseSelect)
        flipBaseButton.connect("clicked", self.__flipBase)
        
        
        if self.__direction == 3:
            directionString = "5' -> 3'"
        else:
            directionString = "3' -> 5'"
        
        numNts = self.__numNtsTraced()
        if numNts == 1 and self.__resIndexToExtend is None:
            #only display a switch direction button if this is the first nucleotide being traced and we're not extending a chain
            directionLabel = gtk.Label("Tracing " + directionString)
            switchDirectionButton = buttonWithIcon("  Switch  ", "gtk-refresh")
            switchDirectionButton.connect("clicked", self.__switchDirection, prevPhos, window)
        else:
            directionLabel = gtk.Label("%i nucleotide%s traced (%s)" % ((numNts-1), "s" if numNts > 2 else "", directionString))
        
        
        #pack the buttons and label into the window
        windowBox = gtk.VBox(False, VBOX_SPACING)
        window.remove(waitLabel)
        window.add(windowBox)
        window.resize(1,1) #remove any size constraints that were caused by the waiting message
        
        topBox = gtk.VBox(False, 0)
        windowBox.pack_start(topBox, False, False, 6)
        topBox.pack_start(topLabel, False, False, 0)
        
        directionBox = gtk.HBox(False, HBOX_SPACING)
        topBox.pack_start(directionBox, False, False, VBOX_SPACING)
        
        if self.__pseudoMolecule.getNumNts() == 1:
            directionLabelAlign = gtk.Alignment(xalign=1, yalign=0.5)
            directionLabelAlign.add(directionLabel)
            
            switchDirectionAlign = gtk.Alignment(xalign=0, yalign=0.5)
            switchDirectionAlign.add(switchDirectionButton)
            
            directionBox.pack_start(directionLabelAlign, True, True, BUTTON_SPACING)
            directionBox.pack_start(switchDirectionAlign, True, True, BUTTON_SPACING)
        else:
            directionBox.pack_start(directionLabel, True, False, 0)
            #self.__directionBoxHeight was initially set when we created this window for tracing the first full nucleotide
            #i.e. when there was a switch direction button at the top of the window
            #We don't want to resize the window in between the first and second nucleotide, since that can
            #move buttons out from underneath the user's cursor.  To avoid this, we make sure that
            #directionBox is the same height regardless of whether or not the switch direction button is present.
            try:
                directionBox.set_size_request(-1, self.__directionBoxHeight)
            except AttributeError:
                #if we're doing an extendChain, then __directionBoxHeight won't be defined and will raise an AttributeError
                #However, window will never have contained a switch direction button, so we don't need to worry about
                #preserving the height of the direction box
                pass
        
        
        #the phosphate frame
        phosFrame = gtk.Frame("Phosphate")
        phosFrame.set_label_align(0.5, 0.5)
        windowBox.pack_start(phosFrame, False, False, 2)
        
        phosFrameBox = gtk.VBox(False, VBOX_SPACING)
        phosFrame.add(phosFrameBox)
        
        phosPrevNextBox = gtk.HBox(True, HBOX_SPACING)
        phosPrevNextBox.pack_start(prevPhosButton, True, True, BUTTON_SPACING)
        phosPrevNextBox.pack_start(nextPhosButton, True, True, BUTTON_SPACING)
        phosFrameBox.pack_start(phosPrevNextBox, False, False, BUTTON_SPACING)
        adjustPhosBox = gtk.HBox(False, 0)
        adjustPhosBox.pack_start(adjustPhosButton, True, False, BUTTON_SPACING)
        phosFrameBox.pack_start(adjustPhosBox, False, False, BUTTON_SPACING)
        
        #the C1' frame
        C1Frame = gtk.Frame("Base")
        C1Frame.set_label_align(0.5, 0.5)
        windowBox.pack_start(C1Frame, False, False, 2)
        
        C1FrameBox = gtk.VBox(False, VBOX_SPACING)
        C1Frame.add(C1FrameBox)
        
        C1PrevNextBox = gtk.HBox(True, HBOX_SPACING)
        C1PrevNextBox.pack_start(prevC1Button, True, True, BUTTON_SPACING)
        C1PrevNextBox.pack_start(nextC1Button, True, True, BUTTON_SPACING)
        C1FrameBox.pack_start(C1PrevNextBox, False, False, BUTTON_SPACING)
        baseBox = gtk.HBox(False, 0)
        baseBox.pack_start(baseSelect, True, True, BUTTON_SPACING)
        baseBox.pack_start(flipBaseButton, True, True, BUTTON_SPACING)
        C1FrameBox.pack_start(baseBox, False, False, BUTTON_SPACING)
        adjustC1Box = gtk.HBox(False, 0)
        adjustC1Box.pack_start(adjustBaseButton, True, False, BUTTON_SPACING)
        C1FrameBox.pack_start(adjustC1Box, False, False, BUTTON_SPACING)
        
        
        #the separator and accept, cancel, and close buttons
        separator = gtk.HSeparator()
        windowBox.pack_start(separator)
        
        acceptCancelBox = gtk.HBox(True, HBOX_SPACING)
        acceptCancelBox.pack_start(acceptButton, True, True, BUTTON_SPACING)
        acceptCancelBox.pack_start(prevNtButton, True, True, BUTTON_SPACING)
        windowBox.pack_start(acceptCancelBox, False, False, BUTTON_SPACING)
        buildCloseHbox = gtk.HBox(True, VBOX_SPACING)
        windowBox.pack_start(buildCloseHbox, False, False, BUTTON_SPACING)
        buildCloseBox = gtk.VBox(True, VBOX_SPACING)
        buildCloseHbox.pack_start(buildCloseBox, False, False, BUTTON_SPACING)
        buildCloseBox.pack_start(buildBackboneButton, True, False, BUTTON_SPACING)
        buildCloseBox.pack_start(closeButton, True, False, BUTTON_SPACING)
        
        acceptButton.grab_focus() #make the accept button the default focus
        
        #display the new window
        window.show_all()
        
        #record the height of the directionBox so that it always stays the same height
        #We don't want it to change between tracing the first nucleotide (when we have a switch
        #direction button) and the second nucleotide (when we don't have the button)
        self.__directionBoxHeight = directionBox.size_request()[1]
    
    
    def __mutateBase(self):
        """Mutate the currently selected base to self.__baseType
        
        ARGUMENTS:
            None
        RETURNS:
            None
        EFFECTS:
            the current base is mutated to self.__baseType and redrawn
        """
        
        #if there is a Manually Adjust window open, click accept
        #Technically, we don't need to do this.  We could make it so the manual adjustment knows about the base mutation,
        #but just closing the window is far simpler, and should be good enough.
        if self.__adjustPhosWindow is not None:
            self.__adjustPhosAccept(None, self.__adjustPhosWindow)
        
        if self.__adjustBaseWindow is not None:
            self.__adjustBaseAccept(None, self.__adjustBaseWindow)
        
        #get the current base coordinates
        phosIndex = self.__currentPhosphateIndex
        c1Index = self.__currentC1Index[phosIndex]
        curBase = None
        c1loc = None
        if self.__customBase is not None:
            curBase = self.__customBase
            c1loc = self.__customBase[1]["C1'"]
        else:
            curBase = self.__candidateBases[phosIndex][c1Index]
            c1loc = self.__candidateC1s[phosIndex][c1Index]
        
        #if the base we're mutating to is the same as the base we are now, don't do anything
        if curBase[0] == self.__baseType:
            return
        
        newBase = self.__nextPhos.mutateBase(curBase, self.__baseType)
        if self.__customBase is not None:
            self.__customBase = newBase
        else:
            self.__candidateBases[phosIndex][c1Index] = newBase
        
        #determine the current phosphate location so we know where to redrawing it
        curPhosCoords = None
        if self.__customPhosphate is None:
            curPhosCoords = self.__candidatePhosphates[phosIndex]
        else:
            curPhosCoords = self.__customPhosphate
        
        self.__drawNt(self.__getPrevPhosCoords(), c1loc, curPhosCoords, newBase)
        
        
    def __flipBase(self, widget=None):
        """Perform an anti/syn rotation of the base.
        
        OPTIONAL ARGUMENTS:
            widget - the widget that called this function.  This is passed by pyGTK, but is ignored.
        RETURNS:
            None
        EFFECTS:
            Rotates the current base and redraws it
        NOTE:
            With pyrimidines, this function simply rotates the base by 180 about chi
            However, for purines, the base is moved slightly within the plane so that it occupies roughly the same
                location in space as it did before the rotation.
        """
        
        #if there is a Manually Adjust window open, click accept
        #Technically, we don't need to do this.  We could make it so the manual adjustment knows about the base flip,
        #but just closing the window is far simpler, and should be good enough.
        if self.__adjustPhosWindow is not None:
            self.__adjustPhosAccept(None, self.__adjustPhosWindow)
        
        if self.__adjustBaseWindow is not None:
            self.__adjustBaseAccept(None, self.__adjustBaseWindow)
        
        #get the current base coordinates
        phosIndex = self.__currentPhosphateIndex
        c1Index = self.__currentC1Index[phosIndex]
        
        c1loc = None
        if self.__customBase is not None:
            curBase = self.__customBase
            c1loc = self.__customBase[1]["C1'"]
        else:
            curBase = self.__candidateBases[phosIndex][c1Index]
            c1loc = self.__candidateC1s[phosIndex][c1Index]
        
        newBase = self.__nextPhos.flipBase(curBase)
        self.__customBase = newBase
        
        #determine the current phosphate location
        curPhosCoords = None
        if self.__customPhosphate is None:
            curPhosCoords = self.__candidatePhosphates[phosIndex]
        else:
            curPhosCoords = self.__customPhosphate
        
        self.__drawNt(self.__getPrevPhosCoords(), c1loc, curPhosCoords, newBase)
    
    
    ##########################################################################################
    #   Functions for the adjust phosphate dialog
    ##########################################################################################
    
    def __createAdjustPhosDialog(self, widget, parentWindow, sliderFunc = None, acceptFunc = None, cancelFunc = None, closeWinFunc = None):
        """Create a dialog box that allows the user to manually change the phosphate position.
        ARGUMENTS:
            widget - the widget that called this function.  Ignored.
            parentWindow - the RCrane window.  The newly created dialog box is set as a transient for parentWindow
        OPTIONAL ARGUMENTS:
            By default, this function assumes that it is being used to move a next phosphate, not an initial phosphate
            By provided all these arguments, a dialog appropriate for moving the initial phosphate can be created
            sliderFunc   - the function to call when moving the sliders
            acceptFunc   - the function to call when the user clicks OK
            cancelFunc   - the function to call when the user clicks Cancel
            closeWinFunc - the function to call when the user closes the window
        RETURNS:
            None
        EFFECTS:
            Creates and displays a dialog box for adjusting the phosphate location
        """
        
        #if there is still a Manually Adjust Base window open, click accept
        if self.__adjustBaseWindow is not None:
            self.__adjustBaseAccept(None, self.__adjustBaseWindow)
        
        #by default, use fuctions for the nextNt dialog (not the initialNt)
        if sliderFunc is None:
            sliderFunc = self.__adjustPhos
        if acceptFunc is None:
            acceptFunc = self.__adjustPhosAccept
        if cancelFunc is None:
            cancelFunc = self.__adjustPhosCancel
        if closeWinFunc is None:
            closeWinFunc = self.__adjustPhosCloseWin
        
        #create the adjustment dialog
        dialogWindow = createRCraneWindowObject("Adjust Phosphate")
        dialogWindow.set_transient_for(parentWindow)
        
        dialogWindow.connect("delete_event", closeWinFunc)
        
        windowBox = gtk.VBox(False, VBOX_SPACING)
        dialogWindow.add(windowBox)
        
        xFrame = gtk.Frame("X translation")
        windowBox.pack_start(xFrame, False, False, 2)
        
        xAdjustment = gtk.Adjustment(0, -180, 360, 0.1, 1.0)
        xScale = gtk.HScale(xAdjustment)
        xScale.set_draw_value(False)
        xFrame.add(xScale)
        
        
        yFrame = gtk.Frame("Y translation")
        windowBox.pack_start(yFrame, False, False, 2)
        
        yAdjustment = gtk.Adjustment(0, -180, 360, 0.1, 1.0)
        yScale = gtk.HScale(yAdjustment)
        yScale.set_draw_value(False)
        yFrame.add(yScale)
        
        
        zFrame = gtk.Frame("Z translation")
        windowBox.pack_start(zFrame, False, False, 2)
        
        zAdjustment = gtk.Adjustment(0, -180, 360, 0.1, 1.0)
        zScale = gtk.HScale(zAdjustment)
        zScale.set_draw_value(False)
        zFrame.add(zScale)
        
        
        separator = gtk.HSeparator()
        windowBox.pack_start(separator)
        
        okButton        = buttonWithIcon("  OK  ",      "gtk-ok")
        cancelButton    = buttonWithIcon("  Cancel  ",  "gtk-cancel")
        
        okCancelBox = gtk.HBox(True, HBOX_SPACING)
        okCancelBox.pack_start(okButton, True, True, BUTTON_SPACING)
        okCancelBox.pack_start(cancelButton, True, True, BUTTON_SPACING)
        windowBox.pack_start(okCancelBox, False, False, BUTTON_SPACING)
        
        #connect the sliders to the __adjustPhos function
        xAdjustment.connect("value_changed", sliderFunc, 0)
        yAdjustment.connect("value_changed", sliderFunc, 1)
        zAdjustment.connect("value_changed", sliderFunc, 2)
        
        okButton.connect("clicked", acceptFunc, dialogWindow)
        cancelButton.connect("clicked", cancelFunc, dialogWindow)
        
        #remember the original phopshate coordinates
        if self.__customPhosphate is not None:
            self.__adjustPhosOrig = deepcopy(self.__customPhosphate)
        else:
            self.__adjustPhosOrig = self.__candidatePhosphates[self.__currentPhosphateIndex]
        
        self.__translatedPhosCoords = deepcopy(self.__adjustPhosOrig)
        
        #reset the previous slider values
        self.__previousPhosTranslateValue = [0,0,0]
        
        #store the window so that we can destroy it if the user starts clicking buttons in the main RCrane window
        self.__adjustPhosWindow = dialogWindow
        
        #display the dialog
        dialogWindow.show_all()
    
    
    def __adjustPhos(self, adjustment, dim):
        """Move a next (i.e. not initial) phosphate atom in response to the user moving a slider in the adjust phosphate dialog
        
        ARGUMENTS:
            adjustment - the adjustment object
            dim        - which dimension to move the phosphate in
                         0 for the screen x-axis, 1 for the screen y-axis, and 2 for the screen z-axis
        RETURNS:
            None
        EFFECTS:
            Moves and redraws the phosphate
        """
        
        #figure out how much we moved since the last time this function was called
        adjDiff = adjustment.value - self.__previousPhosTranslateValue[dim]
        self.__previousPhosTranslateValue[dim] = adjustment.value #store the new value
        
        screenVectors = screen_vectors()
        
        #fetch and store the zoom factor, so we don't have to call the function three times
        #(since each call causes the creation and destruction of a C++ object, so it's faster to cache the result)
        zoom = zoom_factor()
        
        #determine how much to move the phosphate by
        #using the same formula as Coot's Rotate/Translate Zone function
        #(including multiplying y translations by -1)
        movement = [None, None, None]
        scaleFactor = 0.002
        if dim == 1:
            scaleFactor *= -1
        movement[0] = screenVectors[dim][0] * adjDiff * scaleFactor * zoom
        movement[1] = screenVectors[dim][1] * adjDiff * scaleFactor * zoom
        movement[2] = screenVectors[dim][2] * adjDiff * scaleFactor * zoom
        
        #calculate the new phosphate coordinates
        self.__translatedPhosCoords = plus(self.__translatedPhosCoords, movement)
        
        phosIndex = self.__currentPhosphateIndex
        c1Index = self.__currentC1Index[phosIndex]
        
        self.__drawAdjustPhosCross(self.__translatedPhosCoords)
        self.__drawNt(self.__getPrevPhosCoords(), self.__candidateC1s[phosIndex][c1Index], self.__translatedPhosCoords, self.__candidateBases[phosIndex][c1Index])
    
    
    def __adjustPhosCloseWin(self, window, widget):
        """Respond to the user closing the adjust phosphate dialog box
        
        ARUGMENTS:
            window - the window to close
            widget - the widget used to close the window
        NOTES:
            This function simply calls __adjustPhosCancel with a reversed argument list
        """
        self.__adjustPhosCancel(widget, window)
    
    
    def __adjustPhosCancel(self, widget, window):
        """Respond to the user clicking Cancel in the adjust phosphate dialog box
        
        ARUGMENTS:
            widget - the cancel button
            window - the dialog box window
        RETURNS:
            None
        EFFECTS:
            Closes the adjust phosphate dialog box and reverts the phosphate to its original coordinates
        """
        
        #destroy the AdjustPhosCross
        #(if we haven't moved the phosphate at all, then there's nothing to destroy)
        if self.__customPhosObject is not None:
            close_generic_object(self.__customPhosObject)
        
        #redraw the original version of the nucleotide
        phosIndex = self.__currentPhosphateIndex
        c1Index = self.__currentC1Index[phosIndex]
        
        origPhosCoords = None
        if self.__customPhosphate is not None:
            origPhosCoords = self.__customPhosphate
            self.__drawAdjustPhosCross(self.__customPhosphate)
        else:
            origPhosCoords = self.__candidatePhosphates[phosIndex]
        
        self.__drawNt(self.__getPrevPhosCoords(), self.__candidateC1s[phosIndex][c1Index], origPhosCoords, self.__candidateBases[phosIndex][c1Index])
        #self.__drawNt calls graphics_draw(), so we don't need to call that explicitly after closing self.__customPhosObject
        
        window.destroy()
        self.__adjustPhosWindow = None
        #if we don't explicitely do this, the __adjustPhosWindow stays as a GTK window object
        #which leads to problems in __nextNtAccept
    
    
    def __adjustPhosAccept(self, widget, window):
        """Respond to the user clicking OK in the adjust phosphate dialog box
        
        ARUGMENTS:
            widget - the OK button
            window - the dialog box window
        RETURNS:
            None
        EFFECTS:
            Closes the adjust phosphate dialog box and stores the coordinates that the user has selected
        """
        
        #store the current coordinates
        self.__customPhosphate = self.__translatedPhosCoords
        
        #destroy the C1' crosses
        close_generic_object(self.__candidateC1Object)
        graphics_draw()
        
        window.destroy()
        self.__adjustPhosWindow = None
        #if we don't explicitely do this, the __adjustPhosWindow stays as a GTK window object
        #which leads to problems in __nextNtAccept
    
    
    ##########################################################################################
    #   Functions for the adjust base dialog
    ##########################################################################################
    
    def __createAdjustBaseDialog(self, widget, parentWindow):
        """Creates a dialog that allows the user to manually change the base position.
        
        ARGUMENTS:
            widget 
        ARGUMENTS:
            widget - the widget that called this function.  Ignored.
            parentWindow - the RCrane window.  The newly created dialog box is set as a transient for parentWindow
        RETURNS:
            None
        EFFECTS:
            Creates and displays a dialog box for adjusting the base location
        """
        
        #if there is still a Manually Adjust Phosphate window open, click accept
        if self.__adjustPhosWindow is not None:
            self.__adjustPhosAccept(None, self.__adjustPhosWindow)
        
        #create the adjustment dialog
        dialogWindow = createRCraneWindowObject("Adjust Base")
        dialogWindow.set_transient_for(parentWindow)
        
        dialogWindow.connect("delete_event", self.__adjustBaseCancel)
        
        windowBox = gtk.VBox(False, VBOX_SPACING)
        dialogWindow.add(windowBox)
        
        xFrame = gtk.Frame("X translation")
        windowBox.pack_start(xFrame, False, False, 2)
        xAdjustment = gtk.Adjustment(0, -180, 360, 0.1, 1.0)
        xScale = gtk.HScale(xAdjustment)
        xScale.set_draw_value(False)
        xFrame.add(xScale)
        
        
        yFrame = gtk.Frame("Y translation")
        windowBox.pack_start(yFrame, False, False, 2)
        yAdjustment = gtk.Adjustment(0, -180, 360, 0.1, 1.0)
        yScale = gtk.HScale(yAdjustment)
        yScale.set_draw_value(False)
        yFrame.add(yScale)
        
        
        zFrame = gtk.Frame("Z translation")
        windowBox.pack_start(zFrame, False, False, 2)
        zAdjustment = gtk.Adjustment(0, -180, 360, 0.1, 1.0)
        zScale = gtk.HScale(zAdjustment)
        zScale.set_draw_value(False)
        zFrame.add(zScale)
        
        xRotFrame = gtk.Frame("X Rotation")
        windowBox.pack_start(xRotFrame, False, False, 2)
        xRotAdjustment = gtk.Adjustment(0, -180, 360, 0.1, 1.0)
        xRotScale = gtk.HScale(xRotAdjustment)
        xRotScale.set_draw_value(False)
        xRotFrame.add(xRotScale)
        
        yRotFrame = gtk.Frame("Y Rotation")
        windowBox.pack_start(yRotFrame, False, False, 2)
        yRotAdjustment = gtk.Adjustment(0, -180, 360, 0.1, 1.0)
        yRotScale = gtk.HScale(yRotAdjustment)
        yRotScale.set_draw_value(False)
        yRotFrame.add(yRotScale)
        
        zRotFrame = gtk.Frame("Z Rotation")
        windowBox.pack_start(zRotFrame, False, False, 2)
        zRotAdjustment = gtk.Adjustment(0, -180, 360, 0.1, 1.0)
        zRotScale = gtk.HScale(zRotAdjustment)
        zRotScale.set_draw_value(False)
        zRotFrame.add(zRotScale)
        
        chiRotFrame = gtk.Frame("Chi Rotation")
        #chiRotFrame = gtk.Frame(u"\N{greek small letter chi} Rotation") #this works, but the chi looks too much like an x
        windowBox.pack_start(chiRotFrame, False, False, 2)
        chiRotAdjustment = gtk.Adjustment(0, -180, 360, 0.1, 1.0)
        chiRotScale = gtk.HScale(chiRotAdjustment)
        chiRotScale.set_draw_value(False)
        chiRotFrame.add(chiRotScale)
        
        separator = gtk.HSeparator()
        windowBox.pack_start(separator)
        
        okButton        = buttonWithIcon("  OK  ",      "gtk-ok")
        cancelButton    = buttonWithIcon("  Cancel  ",  "gtk-cancel")
        
        okCancelBox = gtk.HBox(True, HBOX_SPACING)
        okCancelBox.pack_start(okButton, True, True, BUTTON_SPACING)
        okCancelBox.pack_start(cancelButton, True, True, BUTTON_SPACING)
        windowBox.pack_start(okCancelBox, False, False, BUTTON_SPACING)
        
        #connect the sliders to the __adjustPhos function
        xAdjustment.connect("value_changed", self.__translateBase, 0)
        yAdjustment.connect("value_changed", self.__translateBase, 1)
        zAdjustment.connect("value_changed", self.__translateBase, 2)
        
        xRotAdjustment.connect("value_changed", self.__rotateBase, 0)
        yRotAdjustment.connect("value_changed", self.__rotateBase, 1)
        zRotAdjustment.connect("value_changed", self.__rotateBase, 2)
        chiRotAdjustment.connect("value_changed", self.__rotateBaseChi)
        
        okButton.connect("clicked", self.__adjustBaseAccept, dialogWindow)
        cancelButton.connect("clicked", self.__adjustBaseCancel, dialogWindow)
        
        #remember the original phopshate coordinates
        if self.__customBase is not None:
            self.__adjustBaseOrig = deepcopy(self.__customBase)
        else:
            phosIndex = self.__currentPhosphateIndex
            c1Index = self.__currentC1Index[phosIndex]
            self.__adjustBaseOrig = self.__candidateBases[phosIndex][c1Index]
        
        self.__adjustedBaseCoords = deepcopy(self.__adjustBaseOrig)
        
        #reset the previous slider values
        self.__previousBaseTranslateValue = [0,0,0]
        self.__previousBaseRotateValue = [0,0,0]
        self.__previousBaseRotateValueChi = 0
        
        #store the window so that we can destroy it if the user starts clicking buttons in the main RCrane window
        self.__adjustBaseWindow = dialogWindow
        
        #display the dialog
        dialogWindow.show_all()
    
    
    def __translateBase(self, adjustment, dim):
        """Move a base in response to the user moving a slider in the adjust base dialog.
        
        ARGUMENTS:
            adjustment - the adjustment object
            dim        - which dimension to move the base in
                         0 for the screen x-axis, 1 for the screen y-axis, and 2 for the screen z-axis
        RETURNS:
            None
        EFFECTS:
            Moves and redraws the base
        """
        
        #figure out how much we moved since the last time this function was called
        adjDiff = adjustment.value - self.__previousBaseTranslateValue[dim]
        self.__previousBaseTranslateValue[dim] = adjustment.value #store the new value
        
        #create a dummy screenVector object
        #screenVectors = [[1,0,0], [0,1,0], [0,0,1]]
        screenVectors = screen_vectors()
        
        #fetch and store the zoom factor, so we don't have to call the function three times
        #(since each call causes the creation and distruction of a C++ object, so it's faster to cache the result)
        zoom = zoom_factor()
        
        #determine how much to move the base by
        #using the same formula as Coot's Rotate/Translate Zone function
        #(including multiplying y translations by -1)
        movement = [None, None, None]
        scaleFactor = 0.002
        if dim == 1:
            scaleFactor *= -1
        movement[0] = screenVectors[dim][0] * adjDiff * scaleFactor * zoom
        movement[1] = screenVectors[dim][1] * adjDiff * scaleFactor * zoom
        movement[2] = screenVectors[dim][2] * adjDiff * scaleFactor * zoom
        
        #calculate the new base coordinates
        for curAtom, prevCoords in self.__adjustedBaseCoords[1].iteritems():
            self.__adjustedBaseCoords[1][curAtom] = plus(prevCoords, movement)
        
        #figure out the current phosphate coordinates so we know what to draw
        phosCoords = None
        if self.__customPhosphate is None:
            phosCoords  = self.__candidatePhosphates[self.__currentPhosphateIndex]
        else:
            phosCoords  = self.__customPhosphate
        
        self.__drawNt(self.__getPrevPhosCoords(), self.__adjustedBaseCoords[1]["C1'"], phosCoords, self.__adjustedBaseCoords)
    
    def __rotateBase(self, adjustment, dim):
        """Rotates a base in response to the user moving a slider in the adjust base dialog.
        
        ARGUMENTS:
            adjustment - the adjustment object
            dim        - which dimension to rotate the base about
                         0 for the screen x-axis, 1 for the screen y-axis, and 2 for the screen z-axis
        RETURNS:
            None
        EFFECTS:
            Rotates and redraws the base
        """
        
        #figure out how much we moved since the last time this function was called
        adjDiff = adjustment.value - self.__previousBaseRotateValue[dim]
        self.__previousBaseRotateValue[dim] = adjustment.value #store the new value
        
        #calculate the rotation axis
        screenVectors = screen_vectors()
        axis = screenVectors[dim] #the screen_vectors function returns unit vector, so we don't need to worry about that
        
        #translate the base to the origin
        c1loc = self.__adjustedBaseCoords[1]["C1'"]
        translatedCoords = dict()
        for curAtom, prevCoords in self.__adjustedBaseCoords[1].iteritems():
            translatedCoords[curAtom] = minus(prevCoords, c1loc)
        
        #do the rotation
        self.__adjustedBaseCoords[1] = rotateAtoms(translatedCoords, axis, adjDiff, c1loc)
        
        #figure out the current phosphate coordinates so we know what to draw
        phosCoords = None
        if self.__customPhosphate is None:
            phosCoords  = self.__candidatePhosphates[self.__currentPhosphateIndex]
        else:
            phosCoords  = self.__customPhosphate
        
        self.__drawNt(self.__getPrevPhosCoords(), c1loc, phosCoords, self.__adjustedBaseCoords)
    
    def __rotateBaseChi(self, adjustment):
        """Rotates a base about chi in response to the user moving a slider in the adjust base dialog.
        
        ARGUMENTS:
            adjustment - the adjustment object
        RETURNS:
            None
        EFFECTS:
            Rotates and redraws the base
        """
        
        #figure out how much we moved since the last time this function was called
        adjDiff = adjustment.value - self.__previousBaseRotateValueChi
        self.__previousBaseRotateValueChi = adjustment.value #store the new value
        
        #translate the base to the origin
        c1loc = self.__adjustedBaseCoords[1]["C1'"]
        translatedCoords = dict()
        for curAtom, prevCoords in self.__adjustedBaseCoords[1].iteritems():
            translatedCoords[curAtom] = minus(prevCoords, c1loc)
        
        axis = None
        if translatedCoords.has_key("N9"):
            axis = translatedCoords["N9"]
        else:
            axis = translatedCoords["N1"]
        
        #do the rotation
        self.__adjustedBaseCoords[1] = rotateAtoms(translatedCoords, axis, adjDiff, c1loc)
        
        #figure out the current phosphate coordinates so we know what to draw
        phosCoords = None
        if self.__customPhosphate is None:
            phosCoords  = self.__candidatePhosphates[self.__currentPhosphateIndex]
        else:
            phosCoords  = self.__customPhosphate
        
        self.__drawNt(self.__getPrevPhosCoords(), c1loc, phosCoords, self.__adjustedBaseCoords)
    
    def __adjustBaseCloseWin(self, window, widget):
        """Respond to the user closing the adjust base dialog box
        
        ARUGMENTS:
            window - the window to close
            widget - the widget used to close the window
        NOTES:
            This function simply calls __adjustBaseCancel with a reversed argument list
        """
        
        self.__adjustBaseCancel(widget, window)
    
    def __adjustBaseCancel(self, widget, window):
        """Respond to the user clicking Cancel in the adjust base dialog box
        
        ARUGMENTS:
            widget - the cancel button
            window - the dialog box window
        RETURNS:
            None
        EFFECTS:
            Closes the adjust base dialog box and reverts the base to its original coordinates
        """
        
        #redraw the original version of the nucleotide
        phosIndex = self.__currentPhosphateIndex
        c1Index = self.__currentC1Index[phosIndex]
        
        #figure out the current phosphate coordinates so we know what to draw
        phosCoords = None
        if self.__customPhosphate is None:
            phosCoords  = self.__candidatePhosphates[self.__currentPhosphateIndex]
        else:
            phosCoords  = self.__customPhosphate
        
        self.__drawNt(self.__getPrevPhosCoords(), self.__adjustBaseOrig[1]["C1'"], phosCoords, self.__adjustBaseOrig)
        #self.__drawNt calls graphics_draw(), so we don't need to call that explicitly after closing self.__customPhosObject
        
        window.destroy()
        self.__adjustBaseWindow = None
        #if we don't explicitely do this, the __adjustBaseWindow stays as a GTK window object
        #which leads to problems in __nextNtAccept
    
    def __adjustBaseAccept(self, widget, window):
        """Respond to the user clicking OK in the adjust base dialog box
        
        ARUGMENTS:
            widget - the OK button
            window - the dialog box window
        RETURNS:
            None
        EFFECTS:
            Closes the adjust base dialog box and stores the coordinates that the user has selected
        """
        
        #store the current coordinates
        self.__customBase = self.__adjustedBaseCoords
        
        window.destroy()
        self.__adjustBaseWindow = None
        #if we don't explicitely do this, the __adjustBaseWindow stays as a GTK window object
        #which leads to problems in __nextNtAccept
    
    
    ##########################################################################################
    #   Functions for buttons in the initial phosphate GUI (generated by selectInitialPhos)
    ##########################################################################################
    
    def __initialPhosNext(self, widget):
        """Select the next phosphate in the initial phosphate GUI.
        
        ARGUMENTS:
            widget - the button used to call this function.  Ignored.
        RETURNS:
            None
        EFFECTS:
            Highlights the next phosphate
        """
        
        #if we don't have a custom phophsate position, increment the phosphate index
        if self.__customPhosphate is None:
            self.__currentPhosphateIndex += 1
            if self.__currentPhosphateIndex >= len(self.__candidatePhosphates):
                self.__currentPhosphateIndex = 0
        else:
            self.__customPhosphate = None
            close_generic_object(self.__customPhosObject)
        
        #if there is still a Manually Adjust Phosphate window open, close it
        if self.__adjustPhosWindow is not None:
            self.__adjustPhosWindow.destroy()
        
        #forget about any coordinate adjustments the user has made via the "Manually Adjust" button
        self.__customPhosphate = None
        
        self.__drawBox(self.__candidatePhosphates[self.__currentPhosphateIndex])
    
    def __initialPhosPrevious(self, widget):
        """Select the previous phosphate in the initial phosphate GUI.
        
        ARGUMENTS:
            widget - the button used to call this function.  Ignored.
        RETURNS:
            None
        EFFECTS:
            Highlights the previous phosphate
        """
        
        #if we don't have a custom phophsate position, decrement the phosphate index
        if self.__customPhosphate is None:
            self.__currentPhosphateIndex -= 1
            if self.__currentPhosphateIndex < 0 :
                self.__currentPhosphateIndex = len(self.__candidatePhosphates) - 1
        else:
            self.__customPhosphate = None
            close_generic_object(self.__customPhosObject)
        
        #if there is still a Manually Adjust Phosphate window open, close it
        if self.__adjustPhosWindow is not None:
            self.__adjustPhosWindow.destroy()
        
        #forget about any coordinate adjustments the user has made via the "Manually Adjust" button
        self.__customPhosphate = None
        
        self.__drawBox(self.__candidatePhosphates[self.__currentPhosphateIndex])
    
    
    def __createAdjustInitialPhosDialog(self, widget, window):
        """Creates a dialog to adjusting the initial phosphate coordinates.
        
        ARGUMENTS:
            widget - the button used to call this function.  Ignored.
            window - the RCrane window
        RETURNS:
            None
        EFFECTS:
            Creates and displays the adjust phosphate dialog
        """
        
        self.__createAdjustPhosDialog(widget, window, self.__adjustInitialPhos, self.__adjustInitialPhosAccept, self.__adjustInitialPhosCancel, self.__adjustInitialPhosCloseWin)
    
    def __adjustInitialPhos(self, adjustment, dim):
        """Move an initial phosphate atom in response to the user moving a slider in the adjust phosphate dialog
        
        ARGUMENTS:
            adjustment - the adjustment object
            dim        - which dimension to move the phosphate in
                         0 for the screen x-axis, 1 for the screen y-axis, and 2 for the screen z-axis
        RETURNS:
            None
        EFFECTS:
            Moves and redraws the phosphate
        """
        
        #figure out how much we moved since the last time this function was called
        adjDiff = adjustment.value - self.__previousPhosTranslateValue[dim]
        self.__previousPhosTranslateValue[dim] = adjustment.value #store the new value
        
        screenVectors = screen_vectors()
        
        #fetch and store the zoom factor, so we don't have to call the function three times
        #(since each call causes the creation and distruction of a C++ object, so it's faster to cache the result)
        zoom = zoom_factor()
        
        #determine how much to move the phosphate by
        #using the same formula as Coot's Rotate/Translate Zone function
        #(including multiplying y translations by -1)
        movement = [None, None, None]
        scaleFactor = 0.002
        if dim == 1:
            scaleFactor *= -1
        movement[0] = screenVectors[dim][0] * adjDiff * scaleFactor * zoom
        movement[1] = screenVectors[dim][1] * adjDiff * scaleFactor * zoom
        movement[2] = screenVectors[dim][2] * adjDiff * scaleFactor * zoom
        
        #calculate the new phosphate coordinates
        self.__translatedPhosCoords = plus(self.__translatedPhosCoords, movement)
        
        self.__drawAdjustPhosCross(self.__translatedPhosCoords)
        self.__drawBox(self.__translatedPhosCoords)
    
    def __adjustInitialPhosCloseWin(self, window, widget):
        """Respond to the user closing the adjust initial phosphate dialog box
        
        ARUGMENTS:
            window - the window to close
            widget - the widget used to close the window
        NOTES:
            This function simply calls __adjustPhosCancel with a reversed argument list
        """
        
        self.__adjustInitialPhosCancel(widget, window)
    
    def __adjustInitialPhosCancel(self, widget, window):
        """Respond to the user clicking Cancel in the adjust initial phosphate dialog box
        
        ARUGMENTS:
            widget - the cancel button
            window - the dialog box window
        RETURNS:
            None
        EFFECTS:
            Closes the adjust phosphate dialog box and reverts the phosphate to its original coordinates
        """
        
        #destroy the AdjustPhosCross
        #(if we haven't moved the phosphate at all, then there's nothing to destroy)
        if self.__customPhosObject is not None:
            close_generic_object(self.__customPhosObject)
        
        #redraw the original version of the nucleotide
        phosIndex = self.__currentPhosphateIndex
        
        origPhosCoords = None
        if self.__customPhosphate is not None:
            origPhosCoords = self.__customPhosphate
            self.__drawAdjustPhosCross(self.__customPhosphate)
        else:
            origPhosCoords = self.__candidatePhosphates[phosIndex]
        
        self.__drawBox(origPhosCoords)
        
        window.destroy()
        self.__adjustPhosWindow = None
        #if we don't explicitely do this, the __adjustPhosWindow stays as a GTK window object
        #which leads to problems in __initialPhosAccept
    
    def __adjustInitialPhosAccept(self, widget, window):
        """Respond to the user clicking OK in the adjust initial phosphate dialog box
        
        ARUGMENTS:
            widget - the OK button
            window - the dialog box window
        RETURNS:
            None
        EFFECTS:
            Closes the adjust phosphate dialog box and stores the coordinates that the user has selected
        """
        
        #store the current coordinates
        self.__customPhosphate = self.__translatedPhosCoords
        
        window.destroy()
        self.__adjustPhosWindow = None
        #if we don't explicitely do this, the __adjustPhosWindow stays as a GTK window object
        #which leads to problems in __initialPhosAccept
    
    def __initialPhosCancel(self, widget, window):
        """Cancel the build and erase all objects
        
        ARUGMENTS:
            widget - the cancel button
            window - the RCrane window
        RETURNS:
            None
        EFFECTS:
            Closes the RCrane dialog and destroys all RCrane objects
        """
        #ARGUMENTS:
        #   window - the GUI window object
        
        close_generic_object(self.__candidatePhosObject)
        close_generic_object(self.__batonObject)
        self.__pseudoMolecule.closeBatonObject()
        
        if self.__customPhosObject is not None:
            close_generic_object(self.__customPhosObject)
        
        if self.__adjustPhosWindow is not None:
            self.__adjustPhosWindow.destroy()
        
        graphics_draw()
        window.destroy()
    
    def __initialPhosCloseWin(self, window, widget):
        """Cancel the build and erase all objects
        ARGUMENTS:
            window - the RCrane window
            widget - the cancel button
        RETURNS:
            None
        NOTE:
            This function simply calls __initialPhosCancel with a reversed argument list
            It is used when the window manager issues a close signal (rather than the user
            clicking on the cancel button).
        """
        self.__initialPhosCancel(widget, window)
    
    def __initialPhosAccept(self, widget, window):
        """Accept the coordinates of the currently selected phosphate
        ARGUMENTS:
            widget - the button used to invoke this function
            window - the RCrane window
        RETURNS:
            None
        EFFECTS:
            Creates a new Coot molecule using the accepting phospahate and creates a next nucleotide dialog
        """ 
        
        #if there is still a Manually Adjust window open, click accept in them
        if self.__adjustPhosWindow is not None:
            self.__adjustInitialPhosAccept(None, self.__adjustPhosWindow)
        
        #erase the selection objects
        close_generic_object(self.__candidatePhosObject)
        close_generic_object(self.__batonObject)
        if self.__customPhosObject is not None:
            close_generic_object(self.__customPhosObject)
        graphics_draw()
        #window.destroy()
        
        acceptedPoint = None
        if self.__customPhosphate is None:
            #if there are no manually set coordinates, use the appropriate phosphate index
            acceptedPoint = self.__candidatePhosphates[self.__currentPhosphateIndex][0:3]
        else:
            #if the user has used Manually Adjust, use that
            acceptedPoint = self.__customPhosphate
        
        #add the atom to the pseudoMolecule
        if self.__resIndexToExtend is None or self.__direction == 3:
            self.__pseudoMolecule.addPhos(acceptedPoint)
        else:
            self.__pseudoMolecule.addPhos5p(acceptedPoint)
        
        set_rotation_centre(acceptedPoint[0], acceptedPoint[1], acceptedPoint[2])
        
        self.__tracedInitialPhos = True #if this is an extend chain, we need to remember that we've added an initial phosphate
            #in case the user wants to build just the phosphate (since we don't want to allow the user to build nothing)
        
        #if we're doing an extend chain in the 3' direction, lie about where we started the extend chain
        #so we don't have to set up minimizations differently depending on if we starting by adding
        #a phosphate or not
        if self.__direction == 3 and self.__resIndexToExtend is not None:
            self.__resIndexToExtend += 1
            self.__pseudoMolecule.origResInsertionPoint += 1
            #self.__pseudoMolecule.resInsertionPoint was already incremented in addPhos()
            #print "self.__pseudoMolecule.origResInsertionPoint =", self.__pseudoMolecule.origResInsertionPoint
            #print "self.__pseudoMolecule.resInsertionPoint =", self.__pseudoMolecule.resInsertionPoint
            
        self.selectNextNt(window)
    
    ##########################################################################################
    #   Functions for buttons in the next nucleotide GUI (generated by selectNextNt)
    ##########################################################################################
    def __nextNtNextPhos(self, widget):
        """Select the next phosphate in the next nucleotide GUI.
        
        ARGUMENTS:
            widget - the button used to call this function.  Ignored.
        RETURNS:
            None
        EFFECTS:
            Highlights the next phosphate
        """
        
        #if we don't have a custom phosphate position, increment the phosphate index
        if self.__customPhosphate is None:
            self.__currentPhosphateIndex += 1
            if self.__currentPhosphateIndex >= len(self.__candidatePhosphates):
                self.__currentPhosphateIndex = 0
        else:
            self.__customPhosphate = None
            close_generic_object(self.__customPhosObject)
        
        #if there is still a Manually Adjust Phosphate window open, close it
        if self.__adjustPhosWindow is not None:
            self.__adjustPhosWindow.destroy()
        
        #forget about any coordinate adjustments the user has made via the "Manually Adjust" or "Anti/Syn Flip" buttons
        #we must do this before (potentially) calling self.__mutateBase(), otherwise __mutateBase will mutate the customBase
        self.__customPhosphate = None
        self.__customBase      = None
        
        phosIndex = self.__currentPhosphateIndex
        
        prevPhos = self.__getPrevPhosCoords()
        
        #make sure we have C1' predictions for the current phosphate
        if self.__candidateC1s[phosIndex] is None:
            #if we get here, something has gone horribly wrong
            print "WARNING: CANDIDATE C1'S HAVE NOT BEEN PRE-CALCULATED"
            mapNum = imol_refinement_map()
            self.__candidateC1s[phosIndex] = self.__nextPhos.findSugar(mapNum, prevPhos, self.__candidatePhosphates[phosIndex])
        
        c1Index = self.__currentC1Index[phosIndex]
        
        #make sure a base location has been calculated
        if self.__candidateBases[phosIndex][c1Index] is None:
            mapNum = imol_refinement_map()
            self.__candidateBases[phosIndex][c1Index] = self.__nextPhos.findBase(mapNum, self.__candidateC1s[phosIndex][c1Index], prevPhos, self.__candidatePhosphates[phosIndex], self.__baseType, self.__direction)
        else:
            #if it has, make sure that the base is the same as self.__baseType
            self.__mutateBase()
        
        #draw candidate C1' peaks
        self.__drawC1Peaks()
        
        self.__drawNt(prevPhos, self.__candidateC1s[phosIndex][c1Index], self.__candidatePhosphates[phosIndex], self.__candidateBases[phosIndex][c1Index])
    
    def __nextNtPrevPhos(self, widget):
        """Select the previous phosphate in the next nucleotide GUI.
        
        ARGUMENTS:
            widget - the button used to call this function.  Ignored.
        RETURNS:
            None
        EFFECTS:
            Highlights the previous phosphate
        """
        
        #if we don't have a custom phophsate position, decrement the phosphate index
        if self.__customPhosphate is None:
            self.__currentPhosphateIndex -= 1
            if self.__currentPhosphateIndex < 0 :
                self.__currentPhosphateIndex = len(self.__candidatePhosphates) - 1
        else:
            self.__customPhosphate = None
            close_generic_object(self.__customPhosObject)
        
        #if there is still a Manually Adjust Phosphate window open, close it
        if self.__adjustPhosWindow is not None:
            self.__adjustPhosWindow.destroy()
        
        #forget about any coordinate adjustments the user has made via the "Manually Adjust" or "Anti/Syn Flip" buttons
        #we must do this before (potentially) calling self.__mutateBase(), otherwise __mutateBase will mutate the customBase
        self.__customPhosphate = None
        #self.__customC1        = None
        self.__customBase      = None
        
        phosIndex = self.__currentPhosphateIndex
        prevPhos = self.__getPrevPhosCoords()
        
        #make sure we have C1' predictions for the current phosphate
        if self.__candidateC1s[phosIndex] is None:
            #if we get here, something has gone horribly wrong
            print "WARNING: CANDIDATE C1'S HAVE NOT BEEN PRE-CALCULATED"
            mapNum = imol_refinement_map()
            self.__candidateC1s[phosIndex] = self.__nextPhos.findSugar(mapNum, prevPhos, self.__candidatePhosphates[phosIndex])
        
        c1Index = self.__currentC1Index[phosIndex]
        
        #make sure a base location has been calculated
        if self.__candidateBases[phosIndex][c1Index] is None:
            mapNum = imol_refinement_map()
            self.__candidateBases[phosIndex][c1Index] = self.__nextPhos.findBase(mapNum, self.__candidateC1s[phosIndex][c1Index], prevPhos, self.__candidatePhosphates[phosIndex], self.__baseType, self.__direction)
        else:
            #if it has, make sure that the base is the same as self.__baseType
            self.__mutateBase()
        
        #draw candidate C1' peaks
        self.__drawC1Peaks()
        
        self.__drawNt(prevPhos, self.__candidateC1s[phosIndex][c1Index], self.__candidatePhosphates[phosIndex], self.__candidateBases[phosIndex][c1Index])
    
    def __nextNtNextC1(self, widget):
        """Select the next C1' in the next nucleotide GUI.
        
        ARGUMENTS:
            widget - the button used to call this function.  Ignored.
        RETURNS:
            None
        EFFECTS:
            Highlights the next C1'
        """
        
        #if the user has set a custom phosphate, then there's no list of C1' candidates, so don't do anything
        if self.__customPhosphate is not None:
            return
        
        #if there is still a Manually Adjust Phosphate window open, close it
        if self.__adjustPhosWindow is not None:
            self.__adjustPhosWindow.destroy()
        
        #if there is still a Manually Adjust Base window open, close it
        if self.__adjustBaseWindow is not None:
            self.__adjustBaseWindow.destroy()
        
        phosIndex = self.__currentPhosphateIndex
        
        if self.__customBase is None:
            self.__currentC1Index[phosIndex] += 1
            if self.__currentC1Index[phosIndex]>= len(self.__candidateC1s[phosIndex]):
                self.__currentC1Index[phosIndex] = 0
        else:
            self.__customBase = None
        
        prevPhos = self.__getPrevPhosCoords()
        
        c1Index = self.__currentC1Index[phosIndex]
        
        #make sure a base location has been calculated
        if self.__candidateBases[phosIndex][c1Index] is None:
            mapNum = imol_refinement_map()
            self.__candidateBases[phosIndex][c1Index] = self.__nextPhos.findBase(mapNum, self.__candidateC1s[phosIndex][c1Index], prevPhos, self.__candidatePhosphates[phosIndex], self.__baseType, self.__direction)
        else:
            #if it has, make sure that the base is the same as self.__baseType
            self.__mutateBase()
        
        self.__drawNt(prevPhos, self.__candidateC1s[phosIndex][c1Index], self.__candidatePhosphates[phosIndex], self.__candidateBases[phosIndex][c1Index])
    
    def __nextNtPrevC1(self, widget):
        """Select the previous C1' in the next nucleotide GUI.
        
        ARGUMENTS:
            widget - the button used to call this function.  Ignored.
        RETURNS:
            None
        EFFECTS:
            Highlights the previous C1'
        """
        
        #if the user has set a custom phosphate, then there's no list of C1' candidates, so don't do anything
        if self.__customPhosphate is not None:
            return
        
        #if there is still a Manually Adjust Phosphate window open, close it
        if self.__adjustPhosWindow is not None:
            self.__adjustPhosWindow.destroy()
        
        #if there is still a Manually Adjust Base window open, close it
        if self.__adjustBaseWindow is not None:
            self.__adjustBaseWindow.destroy()
        
        phosIndex = self.__currentPhosphateIndex
        
        if self.__customBase is None:
            self.__currentC1Index[phosIndex] -= 1
            if self.__currentC1Index[phosIndex] < 0:
                self.__currentC1Index[phosIndex] = len(self.__candidateC1s[phosIndex]) - 1
        else:
            self.__customBase = None
        
        prevPhos = self.__getPrevPhosCoords()
        
        c1Index = self.__currentC1Index[phosIndex]
        
        #make sure a base location has been calculated
        if self.__candidateBases[phosIndex][c1Index] is None:
            mapNum = imol_refinement_map()
            self.__candidateBases[phosIndex][c1Index] = self.__nextPhos.findBase(mapNum, self.__candidateC1s[phosIndex][c1Index], prevPhos, self.__candidatePhosphates[phosIndex], self.__baseType, self.__direction)
        else:
            #if it has, make sure that the base is the same as self.__baseType
            self.__mutateBase()
        
        self.__drawNt(prevPhos, self.__candidateC1s[phosIndex][c1Index], self.__candidatePhosphates[phosIndex], self.__candidateBases[phosIndex][c1Index])
    
    def __nextNtAccept(self, widget, window):
        """Accept the coordinates of the currently selected nucleotide
        
        ARGUMENTS:
            widget - the button used to invoke this function
            window - the RCrane window
        RETURNS:
            None
        EFFECTS:
            Adds the accepted nucleotide coordinates to the Coot molecule and begins tracing the next nucleotide
        """ 
        
        #if there is still a Manually Adjust window open, click accept
        if self.__adjustPhosWindow is not None:
            self.__adjustPhosAccept(None, self.__adjustPhosWindow)
        
        if self.__adjustBaseWindow is not None:
            self.__adjustBaseAccept(None, self.__adjustBaseWindow)
        
        #erase the selection objects
        close_generic_object(self.__candidatePhosObject)
        close_generic_object(self.__candidateC1Object)
        close_generic_object(self.__batonObject)
        if self.__customPhosObject is not None:
            close_generic_object(self.__customPhosObject)
        
        graphics_draw()
        
        phosIndex = self.__currentPhosphateIndex
        c1Index = self.__currentC1Index[phosIndex]
        
        phosCoords = None
        if self.__customPhosphate is None:
            phosCoords  = self.__candidatePhosphates[phosIndex]
        else:
            phosCoords  = self.__customPhosphate
        #c1coords    = self.__candidateC1s[phosIndex][c1Index]
        
        baseType, baseCoords = (None, None)
        if self.__customBase:
            (baseType, baseCoords) = self.__customBase
        else:
            (baseType, baseCoords) = self.__candidateBases[phosIndex][c1Index]
        
        #add the atom to the pseudoMolecule
        if self.__direction == 3:
            self.__pseudoMolecule.addBaseAndPhos(baseType, baseCoords, phosCoords)
        else: #self.__direction == 5
            self.__pseudoMolecule.addBaseAndPhos5p(baseType, baseCoords, phosCoords)
        
        set_rotation_centre(phosCoords[0], phosCoords[1], phosCoords[2])
        
        #forget about any coordinate adjustments the user has made via the "Manually Adjust" or "Anti/Syn Flip" buttons
        self.__customPhosphate = None
        self.__customBase      = None
        
        self.selectNextNt(window)
    
    def __nextNtClose(self, widget, window):
        """Close the next nucleotide dialog and delete the molecule
        
        ARGUMENTS:
            widget - the button used to invoke this function
            window - the RCrane window
        RETURNS:
            None
        EFFECTS:
            Destroys the next nucleotide dialog and the Coot molecule
        """
        
        if self.__pseudoMolecule.hasSavedMoleculeState():
            self.__pseudoMolecule.restoreMoleculeState()
        else:
            self.__pseudoMolecule.deleteMolecule()
        self.__nextNtFinished(window)
        

    def __nextNtFinished(self, window):
        """Close the next nucleotide window without deleting the molecule
        
        ARGUMENTS:
            window - the RCrane window
        RETURNS:
            None
        EFFECTS:
            Destroys the next nucleotide dialog
        NOTE:
            This function is called by __nextNtClose and __nextNtBuildBackbone.
        """
        
        close_generic_object(self.__candidatePhosObject)
        close_generic_object(self.__candidateC1Object)
        close_generic_object(self.__batonObject)
        if self.__customPhosObject is not None:
            close_generic_object(self.__customPhosObject)
        self.__pseudoMolecule.closeBatonObject()
        graphics_draw()
        
        #if there is still a Manually Adjust Phosphate window open, close it
        if self.__adjustPhosWindow is not None:
            self.__adjustPhosWindow.destroy()
            
        #if there is still a Manually Adjust Base window open, close it
        if self.__adjustBaseWindow is not None:
            self.__adjustBaseWindow.destroy()
        
        
        window.destroy()
    
    def __nextNtBuildBackbone(self, widget, window):
        """Finish tracing the backbone and calculate all-atom backbone coordinates.
        
        ARGUMENTS:
            widget - the button used to invoke this function
            window - the RCrane window
        RETURNS:
            None
        EFFECTS:
            Calculates an initial set of backbone coordinates and creates a review suites GUI dialog
        """
        
        #pre-declare prevTorsions
        prevTorsions = None
        
        #if there is still a Manually Adjust Phosphate window open, close it
        if self.__adjustPhosWindow is not None:
            self.__adjustPhosWindow.destroy()
            
        #if there is still a Manually Adjust Base window open, close it
        if self.__adjustBaseWindow is not None:
            self.__adjustBaseWindow.destroy()
        
        
        #print "self.__numNtsTraced() =", self.__numNtsTraced()
        
        if self.__numNtsTraced() < 2:
            if self.__resIndexToExtend is None:
                #if the user has only built a phosphate, then we can't build any backbone
                #so just close the window and leave the phosphate atom
                self.__nextNtFinished(window)
                
            elif self.__tracedInitialPhos:
                #if we're doing an extend chain and have only traced a phosphate, then build the non-bridging oxygens
                buildOnlyPhosOxy(self.__pseudoMolecule, self.__resIndexToExtend, self.__direction)
                self.__nextNtFinished(window)
                
            else:
                #if we're doing an extend chain and we haven't even traced a phosphate, then the user probably clicked by accident
                #so don't do anything
                print "No nucleotides traced, so there is nothing to build."
                add_status_bar_text("No nucleotides traced, so there is nothing to build.")
                return False
            
        elif self.__numNtsTraced() == 2 and self.__resIndexToExtend is None:
            #close the graphics for the nucleotide that we haven't built yet
            close_generic_object(self.__candidatePhosObject)
            close_generic_object(self.__candidateC1Object)
            close_generic_object(self.__batonObject)
            
            #if there are two nucleotides, then we have a single sugar with both a 5' and a 3' phosphate
            #we can't determine a conformer, but we can predict the sugar pucker
            builtChain = self.__pseudoMolecule.createChainObject()
            pucker = determinePucker(builtChain.nucleotides[0].pperp())
            #window.disconnect(self.__deleteHandlerID)
            calcCoords(builtChain, pucker, self.__pseudoMolecule, window)
            self.__nextNtFinished(window)
            
        else:
            #close the graphics for the nucleotide that we haven't built yet
            close_generic_object(self.__candidatePhosObject)
            close_generic_object(self.__candidateC1Object)
            close_generic_object(self.__batonObject)
            
            if self.__resIndexToExtend is None:
                builtChain = self.__pseudoMolecule.createChainObject()
            else:
                
                #figure out the starting and ending residues
                #the +/- 1 is because we want to rebuild the nucleotide right before the extension
                #since we now have enough information to do a proper rotamer prediction for it
                if self.__direction == 3:
                    startingResIndex = self.__resIndexToExtend - 1
                    endingResIndex   = self.__pseudoMolecule.resInsertionPoint
                    #print "startingResIndex, endingResIndex =", startingResIndex, endingResIndex
                    
                    joined = self.__extendChainJoined(endingResIndex)
                    if joined == 2:
                        endingResIndex += 1
                    
                else:
                    startingResIndex = self.__pseudoMolecule.resInsertionPoint
                    endingResIndex   = self.__pseudoMolecule.origResInsertionPoint
                    #print "startingResIndex, endingResIndex =", startingResIndex, endingResIndex
                    
                    joined = self.__extendChainJoined(startingResIndex)
                    if joined == 1:
                        endingResIndex -= 1
                        startingResIndex -= 2
                    elif joined == 2:
                        startingResIndex -= 1
                    
                #print "self.__extendChainJoined(startingResIndex) returned", joined
                #print "startingResIndex, endingResIndex =", startingResIndex, endingResIndex
                #print "startingRes fullNum =", self.__pseudoMolecule.resNumFull(startingResIndex)
                #print "endingRes fullNum =", self.__pseudoMolecule.resNumFull(endingResIndex)
                #from time import sleep; sleep(5)
                
                (builtChain, resNum5p, resNum3p) = self.__pseudoMolecule.createPartialChainObjectFromIndex(startingResIndex, endingResIndex, addFlankingAtoms = True)
            
            builtChainOrig = deepcopy(builtChain)
            #for curNuc in builtChain.nucleotides: pprint(curNuc.atoms)
            (bestPath, predictedProbs) = determineRotamerSeq(builtChain)
            print "Suite string: " + "".join(bestPath)
            
            #FOR DEBUGGING
            #print "OPENING foo3.txt"
            #output = open("foo3.txt", 'wb')
            #from cPickle import dump
            #dump([builtChain, bestPath, predictedProbs, self.__pseudoMolecule._PseudoMolecule__molecule[0][0][1], builtChainOrig], output)
            #output.close()
            #sleep(3)
            
            if self.__resIndexToExtend is None:
                (intermediateAtomLocs, minimizationScores) = calcCoords(builtChain, bestPath, self.__pseudoMolecule, window)
            else:
                
                if resNum5p is None:
                    #if we're at the start of the chain, add in a None rotamer as the previous suite
                    rebuildRots = [None]+bestPath
                    prevTorsions = None
                else:
                    #if we're not at the start of the chain, record the current 5' torsions so they can be used as restraints in minimization
                    prevTorsions = self.__pseudoMolecule.calcSuiteTorsionsFromIndex(startingResIndex)
                    rebuildRots = [prevTorsions]+bestPath
                
                #if we're not at the end of the chain, record the current 3' torsions so that they can be used as restraints in minimization
                if resNum3p is None:
                    rebuildRots = rebuildRots+[None]
                    nextTorsions = None
                else:
                    #print "resNum3p =", resNum3p
                    nextTorsions = self.__pseudoMolecule.calcSuiteTorsions(resNum3p)
                    rebuildRots = rebuildRots+[nextTorsions]
                
                startResNum = self.__pseudoMolecule.resNumFull(startingResIndex)
                #endResNum   = self.__pseudoMolecule.resNumFull(endingResIndex-1) #we subtract 1 because the final residue is just a phosphate group
                #endResNum   = self.__pseudoMolecule.resNumFull(endingResIndex)
                
                #determine the final residue to minimize, excluding any 3' phosphate group
                if self.__pseudoMolecule.isOnlyPhosGroupFromIndex(endingResIndex):
                    endResNum   = self.__pseudoMolecule.resNumFull(endingResIndex-1)
                else:
                    endResNum   = self.__pseudoMolecule.resNumFull(endingResIndex)
                
                self.__pseudoMolecule.setExtraBondRange(resNum5p or startResNum, resNum3p or endResNum)
                
                #reset the nucleotide just before the extend chain so that we can rebuild it
                if self.__direction == 3:
                    self.__pseudoMolecule.resetNucs(startResNum, self.__pseudoMolecule.resNumFull(self.__resIndexToExtend))
                else:
                    self.__pseudoMolecule.resetNucs(self.__pseudoMolecule.resNumFull(self.__resIndexToExtend), endResNum)
                    
                #if the newly traced segment has joined an existing segment, then
                #reset the nucleotide at the end as well so we can rebuild that
                if joined:
                    if self.__direction == 3:
                        self.__pseudoMolecule.resetNucs(self.__pseudoMolecule.resNumFull(self.__resIndexToExtend), endResNum)
                    else:
                        self.__pseudoMolecule.resetNucs(startResNum, self.__pseudoMolecule.resNumFull(self.__resIndexToExtend))
                
                (intermediateAtomLocs, minimizationScores) = recalcCoords(startResNum, endResNum, rebuildRots, builtChain, self.__pseudoMolecule, window)
            
            #print "minimizationScores =", minimizationScores
            #for curNuc in builtChain.nucleotides: pprint(curNuc.atoms)
            
            self.__pseudoMolecule.closeBatonObject()
            window.disconnect(self.__deleteHandlerID)
            ReviewSuitesGui(predictedProbs, bestPath, self.__pseudoMolecule, builtChainOrig, intermediateAtomLocs, minimizationScores, window, prevTorsions = prevTorsions)
            #self.__nextNtFinished(window)
        
        
    
    def __nextNtCloseWin(self, window, widget):
        """Respond to the user closing the next nucleotide window 
        
        ARUGMENTS:
            window - the window to close
            widget - the widget used to close the window
        NOTES:
            This function simply calls __nextNtClose with a reversed argument list
        """
        
        #note the different order of arguments between this and __nextNtClose
        self.__nextNtClose(widget, window)
    
    def __nextNtBaseSelect(self, combobox):
        """Mutate the base in response to the user selecting a base type from the drop-down
        
        ARGUMENTS:
            combobox - the drop down object used to select the new base type
        RETURNS:
            None
        EFFECTS:
            the current base is mutated to self.__baseType and redrawn
        """
        
        #determine the base type
        selectionNum = combobox.get_active()
        baseType = ["A", "C", "G", "U"][selectionNum]
        
        self.__baseType = baseType
        self.__mutateBase()
    
    def __nextNtPrevNt(self, widget, window):
        """Cancels building the current nucleotide and moves back one nucleotide
        
        ARGUMENTS:
            widget - the button used to invoke this function
            window - the RCrane window
        RETURNS:
            None
        EFFECTS:
            Undraws the current nucleotide and prompts the user to rebuild the previous nucleotide
        """
        
        #make sure we're not at the start of an extend chain
        if self.__resIndexToExtend is not None and self.__pseudoMolecule.origResInsertionPoint == self.__pseudoMolecule.resInsertionPoint:
            #if we are, then don't do anything
            print "Can't go back any further."
            add_status_bar_text("Can't go back any further.")
            return
        
        #remove the last nucleotide from the pseudoMolecule object
        if self.__direction == 3:
            (prevNtType, prevNtCoords) = self.__pseudoMolecule.removeLastBaseAndPhos()
        else:
            (prevNtType, prevNtCoords) = self.__pseudoMolecule.removeFirstBaseAndPhos()
        #pprint(prevNtType)
        #pprint(prevNtCoords)
        
        #make sure that we're not on the first nucleotide
        if prevNtCoords is None:
            #if we are, then don't do anything
            print "Can't go back any further."
            add_status_bar_text("Can't go back any further.")
            return
        
        #close the current drawing objects
        close_generic_object(self.__candidatePhosObject)
        close_generic_object(self.__candidateC1Object)
        close_generic_object(self.__batonObject)
        if self.__customPhosObject is not None:
            close_generic_object(self.__customPhosObject)
        
        #forget about any coordinate adjustments the user has made via the "Manually Adjust" or "Anti/Syn Flip" buttons
        self.__customPhosphate = None
        self.__customBase      = None
        
        #shift the zoom back
        prevPhos = self.__getPrevPhosCoords()
        set_rotation_centre(prevPhos[0], prevPhos[1], prevPhos[2])
        
        self.selectNextNt(window, prevNtCoords, prevNtType)
        
        
    
    
    ##########################################################################################
    #   Drawing functions
    ########################################################################################## 
    
    def __drawCross(self, coords, crossLength, crossColor, object):
        """Draw a cross of the specified size and color
        
        ARGUMENTS:
            coords      - the coordinates for the center of the cross
            crossLength - the size of one "arm" of the cross (i.e. the distance from the center to the end, not end-to-end)
            crossColor  - the color of the cross
            object      - the Coot drawing object to add this cross to
        RETURNS:
            None
        EFFECTS:
            Adds the specified cross to object
        """
        
        to_generic_object_add_line(object, crossColor, 6, coords[0]-crossLength, coords[1],             coords[2],             coords[0]+crossLength, coords[1],             coords[2])
        to_generic_object_add_line(object, crossColor, 6, coords[0],             coords[1]-crossLength, coords[2],             coords[0],             coords[1]+crossLength, coords[2])
        to_generic_object_add_line(object, crossColor, 6, coords[0],             coords[1],             coords[2]-crossLength, coords[0],             coords[1],             coords[2]+crossLength)
        
    def __drawPhosCross(self, coords):
        """Draw a cross for a phosphate candidate
        
        ARGUMENTS:
            coords - the coordinates for the center of the cross
        RETURNS:
            None
        EFFECTS:
            Adds the cross to self.__candidatePhosObject
            Note that this function does not issue a graphics_draw() command
        """
        self.__drawCross(coords, CROSS_LENGTH_PHOS, CROSS_COLOR_PHOS, self.__candidatePhosObject)
    
    def __drawC1Cross(self, coords):
        """Draw a cross for a C1' candidate
        
        ARGUMENTS:
            coords - the coordinates for the center of the cross
        RETURNS:
            None
        EFFECTS:
            Adds the cross to self.__candidateC1Object
            Note that this function does not issue a graphics_draw() command
        """
        self.__drawCross(coords, CROSS_LENGTH_C1, CROSS_COLOR_C1, self.__candidateC1Object)
    
    def __drawAdjustPhosCross(self, coords):
        """Draw a cross for the manually adjusted phosphate
        
        ARGUMENTS:
            coords - the coordinates for the center of the cross
        RETURNS:
            None
        EFFECTS:
            Deletes the current manually adjusted phosphate cross if one is present and creates a new cross
            Note that this function does not issue a graphics_draw() command
        """
        
        if self.__customPhosObject:
            close_generic_object(self.__customPhosObject)
        
        self.__customPhosObject = new_generic_object_number(ADJUST_PHOS_TITLE)
        set_display_generic_object(self.__customPhosObject, 1)
        
        self.__drawCross(coords, CROSS_LENGTH_PHOS, CROSS_COLOR_PHOS, self.__customPhosObject)
    
    def __drawBox(self, coords):
        """Draw a new box around the specified point
        
        ARGUMENTS:
            coords - the coordinates for the center of the box
        RETURNS:
            None
        EFFECTS:
            Deletes the current box if one is present and creates a new box
            Then issues a graphics_draw() command
        """
        
        #delete the previous box if there is one
        if self.__batonObject:
            close_generic_object(self.__batonObject)
        
        #create a new object for the box
        self.__batonObject = new_generic_object_number(BATON_TITLE)
        set_display_generic_object(self.__batonObject, 1)
        
        to_generic_object_add_line(self.__batonObject, BATON_COLOR, 6, coords[0]-BOX_LENGTH, coords[1]-BOX_LENGTH, coords[2]-BOX_LENGTH,   coords[0]-BOX_LENGTH, coords[1]+BOX_LENGTH, coords[2]-BOX_LENGTH)
        to_generic_object_add_line(self.__batonObject, BATON_COLOR, 6, coords[0]-BOX_LENGTH, coords[1]-BOX_LENGTH, coords[2]-BOX_LENGTH,   coords[0]-BOX_LENGTH, coords[1]-BOX_LENGTH, coords[2]+BOX_LENGTH)
        to_generic_object_add_line(self.__batonObject, BATON_COLOR, 6, coords[0]-BOX_LENGTH, coords[1]-BOX_LENGTH, coords[2]-BOX_LENGTH,   coords[0]+BOX_LENGTH, coords[1]-BOX_LENGTH, coords[2]-BOX_LENGTH)
        
        to_generic_object_add_line(self.__batonObject, BATON_COLOR, 6, coords[0]-BOX_LENGTH, coords[1]+BOX_LENGTH, coords[2]+BOX_LENGTH,   coords[0]-BOX_LENGTH, coords[1]+BOX_LENGTH, coords[2]-BOX_LENGTH)
        to_generic_object_add_line(self.__batonObject, BATON_COLOR, 6, coords[0]-BOX_LENGTH, coords[1]+BOX_LENGTH, coords[2]+BOX_LENGTH,   coords[0]-BOX_LENGTH, coords[1]-BOX_LENGTH, coords[2]+BOX_LENGTH)
        to_generic_object_add_line(self.__batonObject, BATON_COLOR, 6, coords[0]-BOX_LENGTH, coords[1]+BOX_LENGTH, coords[2]+BOX_LENGTH,   coords[0]+BOX_LENGTH, coords[1]+BOX_LENGTH, coords[2]+BOX_LENGTH)
        
        to_generic_object_add_line(self.__batonObject, BATON_COLOR, 6, coords[0]+BOX_LENGTH, coords[1]+BOX_LENGTH, coords[2]-BOX_LENGTH,   coords[0]-BOX_LENGTH, coords[1]+BOX_LENGTH, coords[2]-BOX_LENGTH)
        to_generic_object_add_line(self.__batonObject, BATON_COLOR, 6, coords[0]+BOX_LENGTH, coords[1]+BOX_LENGTH, coords[2]-BOX_LENGTH,   coords[0]+BOX_LENGTH, coords[1]-BOX_LENGTH, coords[2]-BOX_LENGTH)
        to_generic_object_add_line(self.__batonObject, BATON_COLOR, 6, coords[0]+BOX_LENGTH, coords[1]+BOX_LENGTH, coords[2]-BOX_LENGTH,   coords[0]+BOX_LENGTH, coords[1]+BOX_LENGTH, coords[2]+BOX_LENGTH)
        
        to_generic_object_add_line(self.__batonObject, BATON_COLOR, 6, coords[0]+BOX_LENGTH, coords[1]-BOX_LENGTH, coords[2]+BOX_LENGTH,   coords[0]+BOX_LENGTH, coords[1]-BOX_LENGTH, coords[2]-BOX_LENGTH)
        to_generic_object_add_line(self.__batonObject, BATON_COLOR, 6, coords[0]+BOX_LENGTH, coords[1]-BOX_LENGTH, coords[2]+BOX_LENGTH,   coords[0]+BOX_LENGTH, coords[1]+BOX_LENGTH, coords[2]+BOX_LENGTH)
        to_generic_object_add_line(self.__batonObject, BATON_COLOR, 6, coords[0]+BOX_LENGTH, coords[1]-BOX_LENGTH, coords[2]+BOX_LENGTH,   coords[0]-BOX_LENGTH, coords[1]-BOX_LENGTH, coords[2]+BOX_LENGTH)
        
        
        graphics_draw()
    
    
    def __drawNt(self, phos5, c1, phos3, base):
        """Draw batons for a new nucleotide candidate
        
        ARGUMENTS:
            phos5 - the coordinates of the 5' phosphate
            c1    - the coordinates of the C1'
            phos3 - the coordinates of the 3' phosphate
            base  - a 2 item list consisting of (baseType, baseCoords)
                    baseType is either C, U, G, or A
                    baseCoords is a dictionary of atom type => coordinates
        EFFECTS:
            Draws the specified nucleotide
            Issues a graphics_draw() command
        """
        
        #delete the previous batonObject if there is one
        if self.__batonObject:
            close_generic_object(self.__batonObject)
        
        #create a new object for the nt
        self.__batonObject = new_generic_object_number(BATON_TITLE)
        set_display_generic_object(self.__batonObject, 1)
        
        to_generic_object_add_line(self.__batonObject, BATON_COLOR, 6, phos5[0], phos5[1], phos5[2], c1[0], c1[1], c1[2])
        to_generic_object_add_line(self.__batonObject, BATON_COLOR, 6, c1[0], c1[1], c1[2], phos3[0], phos3[1], phos3[2])
        
        #draw the base
        (baseType, baseCoords) = base
        
        bondList = None
        if baseType == "C":
            bondList = [["C1'","N1"], ["N1","C2"], ["C2","N3"], ["N3","C4"], ["C4","C5"], ["C5","C6"], ["C6","N1"], ["C2","O2"], ["C4","N4"]]
        elif baseType == "U":
            bondList = [["C1'","N1"], ["N1","C2"], ["C2","N3"], ["N3","C4"], ["C4","C5"], ["C5","C6"], ["C6","N1"], ["C2","O2"], ["C4","O4"]]
        elif baseType == "G":
            bondList = [["C1'","N9"], ["N9","C8"], ["C8","N7"], ["N7","C5"], ["C5","C4"], ["C4","N3"], ["N3","C2"], ["C2","N1"], ["N1","C6"], ["C6","C5"], ["C4","N9"], ["C2","N2"], ["C6","O6"]]
        elif baseType == "A":
            bondList = [["C1'","N9"], ["N9","C8"], ["C8","N7"], ["N7","C5"], ["C5","C4"], ["C4","N3"], ["N3","C2"], ["C2","N1"], ["N1","C6"], ["C6","C5"], ["C4","N9"], ["C6","N6"]]
        
        for bondAtoms in bondList:
            (atom1, atom2) = bondAtoms
            atom1Coords = baseCoords[atom1]
            atom2Coords = baseCoords[atom2]
            to_generic_object_add_line(self.__batonObject, BATON_COLOR, 6, atom1Coords[0], atom1Coords[1], atom1Coords[2], atom2Coords[0], atom2Coords[1], atom2Coords[2])
        
        graphics_draw()
    
    
    def __drawPhosPeaks(self):
        """Draw crosses for all phosphate candidates
        
        ARGUMENTS:
            None
        RETURNS:
            None
        EFFECTS:
            Draws crosses for each point in self.__candidatePhosphates
            Note that this function does not issue a graphics_draw() command
        """
        #create an object to display the candidate peaks
        self.__candidatePhosObject = new_generic_object_number(CANDIDATE_PHOS_TITLE)
        set_display_generic_object(self.__candidatePhosObject, 1)
        
        #draw crosses for each candidate peak
        for curPeak in self.__candidatePhosphates:
            self.__drawPhosCross(curPeak)
    
    def __drawC1Peaks(self):
        """Draw crosses for all C1' candidates for the currently selected phosphate
        
        ARGUMENTS:
            None
        RETURNS:
            None
        EFFECTS:
            Draws crosses for each point in self.__candidateC1s for the current phosphate
            Note that this function does not issue a graphics_draw() command
        """
        
        #close the previous C1' candidate object if it exists
        if self.__candidateC1Object is not None and not (is_closed_generic_object_p(self.__candidateC1Object)):
            close_generic_object(self.__candidateC1Object)
        
        #create an object to display the candidate peaks
        self.__candidateC1Object = new_generic_object_number(CANDIDATE_C1_TITLE)
        set_display_generic_object(self.__candidateC1Object, 1)
        
        #draw crosses for each candidate peak
        for curPeak in self.__candidateC1s[self.__currentPhosphateIndex]:
            self.__drawC1Cross(curPeak)
    
    
    def __switchDirection(self, widget, prevPhos, window):
        """Switch the direction of the trace ( from 3'->5' to 5'->3', or vice-versa)
        ARGUMENTS:
            widget   - the button used to call this function
            prevPhos - the coordinates of the previous phosphate atom
            window   - the RCrane window
        RETURNS:
            None
        """
        
        if self.__direction == 3:
            self.__direction = 5
        else:
            self.__direction = 3
        
        
        #get the currently selected phosphate coordinates so we can preserve them after the switch
        phosCoords = None
        if self.__customPhosphate is None:
            phosCoords  = self.__candidatePhosphates[self.__currentPhosphateIndex]
        else:
            phosCoords  = self.__customPhosphate
        #note that we can't pass the phosphate index itself, since the phosphates will likely be ranked differently
        #after the direction switch.
        #We could pass information about whether it was a custom phosphate position or not, but that wouldn't likely
        #save any significant amount of time, and it could lead to problems later if I ever implement a more clever
        #phosphate finding algorithm where potential phosphate locations may be filtered before being presented to the user
        
        
        #if there is still a Manually Adjust window open, click accept
        if self.__adjustPhosWindow is not None:
            self.__adjustPhosAccept(None, self.__adjustPhosWindow)
        
        if self.__adjustBaseWindow is not None:
            self.__adjustBaseAccept(None, self.__adjustBaseWindow)
        
        #erase any drawing objects
        close_generic_object(self.__candidatePhosObject)
        close_generic_object(self.__candidateC1Object)
        close_generic_object(self.__batonObject)
        if self.__customPhosObject is not None:
            close_generic_object(self.__customPhosObject)
        graphics_draw()
        
        
        self.selectNextNt(window, initialPhos = phosCoords)
    
    
    def __getPrevPhosCoords(self):
        """Get the coordinates of the previous phosphate
        
        ARGUMENTS:
            None
        RETURNS:
            the coordinates of the previous phosphate (phosphate -1 if tracing 5'->3', phosphate 1 if tracing 3'->5')
        """
        
        if self.__resIndexToExtend is None:
            if self.__direction == 3:
                prevResIndex = -1
            else:
                prevResIndex = 0
        else:
            if self.__direction == 3:
                prevResIndex = self.__pseudoMolecule.resInsertionPoint
            else:
                prevResIndex = self.__resIndexToExtend
        
        return self.__pseudoMolecule.getPhosCoordsFromIndex(prevResIndex)
    
    
    def __extendChainSetup(self):
        """Start an extend chain call
        
        ARGUMENTS:
            None
        RETURNS:
            False if we could not find any atoms to use for starting coordinates (if this happens, something has gone very wrong)
        """
        
        if ((self.__direction == 5 and self.__pseudoMolecule.getPhosCoordsFromIndex(self.__resIndexToExtend) is not None) or
            (self.__direction == 3 and self.__pseudoMolecule.isOnlyPhosGroupFromIndex(self.__resIndexToExtend))):
            #if we have a phosphate, then skip the phosphate selecting step
            self.selectNextNt()
        
        else:
            
            #if we don't have a phosphate, then figure out what the 3'- or 5'-most atom is
            if self.__direction == 3:
                atomOrder = ["O3'", "C3'", "C2'", "O2'", "C1'"]
            else:
                atomOrder = ["O5'", "C5'", "C4'", "O4'", "C1'"]
            
            startCoords = None
            for curAtom in atomOrder:
                #this is certainly not the most efficient way to get atomic coordinates, but it's a very quick search regardless, so it doesn't really matter
                startCoords = self.__pseudoMolecule.getAtomCoordsFromIndex(curAtom, self.__resIndexToExtend)
                if startCoords is not None:
                    #print "Using", curAtom, "for phosphate search start coordinates:", startCoords
                    break
            else:
                #this should never happen, since we should always have at least a C1' atom, but handle it anyways
                print "No suitable atoms found to start phosphate search."
                return False
            
            self.selectInitialPhos(startCoords)
    
    
    def __numNtsTraced(self):
        """Calculate the number of nucleotides that have been traced thus far
        
        ARGUMENTS:
            None
        RETURNS:
            The number of nucleotides traced
        """
        
        if self.__resIndexToExtend is None:
            return self.__pseudoMolecule.getNumNts()
        else:
            return (abs(self.__pseudoMolecule.resInsertionPoint - self.__pseudoMolecule.origResInsertionPoint) + 1)
    
    
    def __extendChainJoined(self, endingResIndex):
        """If we're doing an extend chain, figure out if we've joined up to the next segment of the molecule.
        (i.e. if we're tracing 5'->3', has the 3' end of the newly traced segment met up with the next residue)
        If we are, then set up the molecule to prepare for the join (i.e. remove redundant phosphates)
        
        ARGUMENTS:
            endingResIndex - the residue index of the last residue of the newly traced segment
                we could determine this ourselves, but __nextNtBuildBackbone will have just finished determining
                it when this function is called
        RETURNS:
            False if the new segment was not joined
            1 if the segment was joined and a residue containing only a phosphate was deleted or merged into another residue
                (i.e. the residue numbering has changed by 1
            2 if the segment was joined no residues were deleted
        """
        
        newSegmentPhosCoord = self.__pseudoMolecule.getPhosCoordsFromIndex(endingResIndex)
        #print "newSegmentPhosCoord =", newSegmentPhosCoord
        
        #figure out the coordinates of the next phosphate or next O3'/O5' atom
        nextPhos = None
        nextOxy  = None
        if self.__direction == 3:

            #print "endingResIndex =", endingResIndex
            #print "self.__pseudoMolecule.getNumNts() =", self.__pseudoMolecule.getNumNts()
            if endingResIndex + 1 >= self.__pseudoMolecule.getNumNts():
                #if there is no next nucleotide, then we certainly can't join up with it
                #print "There is no next 3' nucleotide"
                return False
            else:
                nextPhos = self.__pseudoMolecule.getPhosCoordsFromIndex(endingResIndex + 1)
                nextOxy = self.__pseudoMolecule.getAtomCoordsFromIndex("O5'", endingResIndex + 1)
        
        else: #self.__direction == 5
            if endingResIndex == 0:
                #print "There is no next 5'nucleotide"
                return False
            else:
                nextPhos = self.__pseudoMolecule.getPhosCoordsFromIndex(endingResIndex - 1)
                nextOxy = self.__pseudoMolecule.getAtomCoordsFromIndex("O3'", endingResIndex - 1)
        
        #print "nextPhos =", nextPhos
        #print "nextOxy  =", nextOxy
        
        #figure out if the phosphates are close
        if nextPhos is not None and dist(newSegmentPhosCoord, nextPhos) <= CONNECTED_DISTANCE_CUTOFF_PHOS:
            nextPhosIsClose = True
        else:
            nextPhosIsClose = False
        
        #figure out if the phosphate and bridging oxygen are close
        if nextOxy is not None and dist(newSegmentPhosCoord, nextOxy) <= CONNECTED_DISTANCE_CUTOFF:
            nextOxyIsClose = True
        else:
            nextOxyIsClose = False
        
        #if we've joined up to a new segment, then make sure that the nucleotide we're joining to passes a sanity check
        #if it doesn't, then we can't join
        cantCalcPseudoTors = None
        if nextPhosIsClose or nextOxyIsClose:
            #print "In __extendChainJoined, nextPhosIsClose or nextOxyIsClose"
            if self.__direction == 3:
                nextResIndex = endingResIndex + 1
            else:
                nextResIndex = endingResIndex - 1
            #print "nextResIndex =", nextResIndex
            #print "self.__pseudoMolecule.resNumFull(nextResIndex) =", self.__pseudoMolecule.resNumFull(nextResIndex)            
            
            #if the next nt is more than just a phosphate group
            if not self.__pseudoMolecule.isOnlyPhosGroupFromIndex(nextResIndex):
                #make sure it has a C1' or is just a phosphate group
                if self.__pseudoMolecule.getSugarCoordsFromIndex(nextResIndex) is None:
                    #print "next residue has no C1'"
                    cantCalcPseudoTors = True
                
                #make sure it isn't a modified nucleotide
                nextResType = self.__pseudoMolecule.resTypeFromIndex(nextResIndex)
                if nextResType not in STANDARD_BASES:
                    #print "next residue is non-standard base"
                    cantCalcPseudoTors = True
            
            #make sure it doesn't have an insertion code
            if self.__pseudoMolecule.resNum(nextResIndex)[1] != "":
                #print "next residue has insertion code:", self.__pseudoMolecule.resNum(nextResIndex)[1]
                return False
            
            #make sure that the next residue has a phosphate (otherwise, we can't calculate pseudotorsions)
            if self.__direction == 5 and self.__pseudoMolecule.getPhosCoordsFromIndex(nextResIndex) is None:
                #print "next 5' residue has no phos"
                #from time import sleep; sleep(3)
                cantCalcPseudoTors = True
            elif self.__direction == 3 and not self.__pseudoMolecule.connectedToNextFromIndex(nextResIndex):
                #print "next 3' residue has no phos"
                #from time import sleep; sleep(3)
                cantCalcPseudoTors = True
                
            #if the next residue doesn't have a sugar or a phosphate, we may still need to merge residues, so don't return False yet
            #wait until after we've deleted or merged residues
            #hence, we set cantCalcPseudoTors to True
        
        #print "In __extendChainJoined, nucleotide to be joined passes sanity check"
        
        if nextPhosIsClose:
            #if we have duplicate phosphates that we need to worry about,
            #then delete the residue that contains only the phosphate group
            
            if self.__direction == 3:
                #delete the phosphate group from the new trace
                self.__pseudoMolecule.deleteResFromIndex(endingResIndex)
            else:
                #delete the phosphate group from the existing molecule
                self.__pseudoMolecule.deleteResFromIndex(endingResIndex - 1)
            
            if cantCalcPseudoTors:
                return False
            else:
                return 1
        
        elif nextOxyIsClose and self.__direction == 3:
            #we don't have duplicate phosphates, but the newly created 3' phosphate should be part of the
            #next residue (which contains the O5')
            self.__pseudoMolecule.mergeRes(endingResIndex, endingResIndex + 1)
            
            if cantCalcPseudoTors:
                return False
            else:
                return 1
        
        elif nextOxyIsClose:
            #we want to join to the next segment, but we don't have to erase any atoms to do so
            if cantCalcPseudoTors:
                return False
            else:
                return 2
        
        else:
            return False
        
