#!/usr/bin/env python """A class for the graphical interface used to review the predicted conformers.""" # Copyright 2010, 2011, 2012 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. import gtk import pango from copy import deepcopy #from pprint import pprint #for debugging #from cPickle import dump #for debugging import os #import sys from time import sleep #for debugging from coot import set_rotation_centre, add_atom_label, remove_atom_label, graphics_draw, close_generic_object, set_display_generic_object from puckerList import puckerListFullName, puckerList from calcCoords import calcCoords, recalcCoords from guiUtils import buttonWithIcon, HBOX_SPACING, VBOX_SPACING, BUTTON_SPACING, createRCraneWindowObject from rotamerSeq import determineAlternateConf DIFFERENT_PUCKER_COLOR = "pink" BEST_PATH_COLOR = "palegreen" BOLD_WEIGHT = pango.WEIGHT_HEAVY class ReviewSuitesGui: """A class for the graphical interface used to review the predicted conformers.""" def __init__(self, predictedProbs, bestPath, pseudoMol, origCoords, intermediateAtomLocs, minimizationScores, window = None, useResNums = True, oldCoordsObj = None, ignoreDensity = False, prevTorsions = None, nextTorsions = None): """Initialize a ReviewSuitesGui object and create a GUI for reviewing the predicted suites ARGUMENTS: predictedProbs - the probabilities for each rotamer calculated for each suite formatted as a list of suites, where each suite is represented as a dictionary of rotamer => probability bestPath - a list of the most likely rotamers for each suite This is typically calculed by rotamerSeq using an HMM, so it may differ slightly from simply taking all the maxima from predictedProbs pseudoMol - a PseudoMolecule object for the built molecule origCoords - a copy of the coordinates contained in pseudoMol as a Chain object intermediateAtomLocs - phosphate, O3', and C3' coordinates for each nucleotide immediately before minimization of that nucleotide was started minimizationScores - the minimization scores for each nucleotide OPTIONAL ARGUMENTS: window - a window to put the GUI into If not provided, a new window will be created. useResNums - whether or not to display the rotamer numbers (as opposed to just the indices) in the GUI Defaults to True oldCoordsObj - a Coot generic object for drawing the coordinates we are potentially replacing when running rotamerize If not provided, then the toggle checkbox will not be displayed and the generic object will not be destroyed when closing the window. ignoreDensity - ignore the density when performing the minimization defaults to False prevTorsions - the pre-rotamerize torsions for the suite before the start of origCoords. These torsions will be used as restraints when rebuilding the first suite defaults to None nextTorsions - the pre-rotamerize torsions for the suite after the end of origCoords. These torsions will be used as restraints when rebuilding the first suite defaults to None RETURNS: an initialized ReviewSuitesGui object EFFECTS: Displays an RCrane dialog box for reviewing the predicted suites NOTE: If the pseudoMol object has savedCoordinates, then an an "Accept New Coordinates" and a "Restore Original Coordinates" will be displayed. Otherwise, only a "Done" button will be displayed """ self.__predictedProbs = predictedProbs self.__bestPath = bestPath self.__pseudoMolecule = pseudoMol self.__origCoords = origCoords self.__intermediateAtomLocs = intermediateAtomLocs self.__window = window self.__useResNums = useResNums self.__oldCoordsObj = oldCoordsObj self.__minimizationScores = minimizationScores self.__ignoreDensity = ignoreDensity self.__prevTorsions = prevTorsions self.__nextTorsions = nextTorsions #print minimizationScores self.__curPath = deepcopy(bestPath) self.__curSuite = 1 self.__numNucs = len(self.__bestPath) + 2 self.__treeViewChangedHandlerID = None #if the initial nucleotide doesn't have a C1' atom, then we can't rebuilt it #(This happens when we're rotamerizing. The origCoords object contains a few 5' atoms.) #as a result we set __skipFirstNuc so we know to ignore the first nucleotide if origCoords.nucs[0].hasAtom("C1'"): self.__skipFirstNuc = 0 else: self.__skipFirstNuc = 1 #use clearCache() to initialize the cache variables self.__clearCache() #print origCoords #output = open("foo2.txt", 'wb') #dump([predictedProbs, bestPath, pseudoMol._PseudoMolecule__molecule[0][0][1], origCoords, intermediateAtomLocs, minimizationScores], output) #output.close() #create the review suite GUI self.__reviewSuites() def __reviewSuites(self): """Create the review suites GUI ARGUMENTS: None RETURNS: None EFFECTS: Displays an RCrane dialog box for reviewing the predicted suites """ if self.__window is None: self.__window = createRCraneWindowObject() elif self.__window is not None: #remove the current contents of the window windowChild = self.__window.get_child() if windowChild is not None: self.__window.remove(windowChild) self.__window.resize(1,1) #remove any previous size constraints #connect the close window signal self.__window.connect("delete_event", self.__closeWin) # Create the buttons prevConfButton = buttonWithIcon(" Previous Conformer ", "gtk-go-up") nextConfButton = buttonWithIcon(" Next Conformer ", "gtk-go-down") prevSuiteButton = buttonWithIcon(" Previous Suite ", "gtk-media-previous") nextSuiteButton = buttonWithIcon(" Next Suite ", "gtk-media-next") if self.__pseudoMolecule.hasSavedCoordinates(): doneButton = buttonWithIcon(" Accept New Coordinates ", "gtk-ok") cancelButton = buttonWithIcon(" Restore Original Coordinates ", "gtk-cancel") else: doneButton = buttonWithIcon(" Done ", "gtk-ok") #create the suiteFrame, since that needs to be passed to the previous and next suite buttons suiteNum = self.__origCoords.nucs[1 + self.__skipFirstNuc].resNum if self.__useResNums: frameTitle = "Suite %s (1 of %i)" % (str(suiteNum), len(self.__predictedProbs)) else: frameTitle = "Suite 1 of %i" % len(self.__predictedProbs) suiteFrame = gtk.Frame(frameTitle) #create the TreeView objects (confListStore, initialConfNum) = self.__createConfListStore(1) (confTreeView, confTreeSelection, rowHeight) = self.__createConfTreeView(confListStore, initialConfNum) if len(self.__bestPath) > 1: overviewTreeView = self.__createOverviewTreeView(confTreeView, suiteFrame) else: overviewTreeView = None self.__treeViewChangedHandlerID = confTreeSelection.connect("changed", self.__changed, overviewTreeView) #create the ScrolledWindow objects used to display the TreeViews (treeViewScroll, treeViewScrollWidth) = self.__createConfScrolledWindow(confTreeView, rowHeight) if len(self.__bestPath) > 1: (overviewScroll, wideOverview) = self.__createOverviewScrolledWindow(overviewTreeView, prevConfButton, treeViewScrollWidth) #connect the buttons prevConfButton.connect("clicked", self.__prevConf, confTreeSelection) nextConfButton.connect("clicked", self.__nextConf, confTreeSelection) #we don't next previous and next suite buttons if there's only a single suite if len(self.__bestPath) > 1: prevSuiteButton.connect("clicked", self.__prevSuite, confTreeView, suiteFrame, overviewTreeView) nextSuiteButton.connect("clicked", self.__nextSuite, confTreeView, suiteFrame, overviewTreeView) doneButton.connect("clicked", self.__accept) if self.__pseudoMolecule.hasSavedCoordinates(): cancelButton.connect("clicked", self.__restoreOldCoords) #put the new contents into the window windowBox = gtk.VBox(False, VBOX_SPACING) #create the suite overview frame if there is more than one suite if len(self.__bestPath) > 1: overviewFrame = gtk.Frame("Overview") overviewBox = gtk.HBox(True, 0) overviewBox.pack_start(overviewScroll, True, wideOverview, 0) overviewFrame.add(overviewBox) windowBox.pack_start(overviewFrame, False, True, BUTTON_SPACING) suiteFrameHBox = gtk.HBox(False, HBOX_SPACING) suiteFrameVBox = gtk.VBox(False, VBOX_SPACING) suiteFrame.add(suiteFrameVBox) suiteFrameVBox.pack_start(suiteFrameHBox, True, True, BUTTON_SPACING) windowBox.pack_start(suiteFrame, True, True, BUTTON_SPACING) prevNextConfBox = gtk.VBox(True, VBOX_SPACING) prevNextConfAlignment = gtk.Alignment(xalign=0.5, yalign=0.5) prevNextConfAlignment.add(prevNextConfBox) suiteFrameHBox.pack_start(prevNextConfAlignment, False, False, BUTTON_SPACING) prevNextConfBox.pack_start(prevConfButton, True, True, BUTTON_SPACING) prevNextConfBox.pack_start(nextConfButton, True, True, BUTTON_SPACING) #create a checkbox for displaying the old coordinates if we have a generic object if self.__oldCoordsObj is not None: oldCoordsCheckButton = gtk.CheckButton("Display old coordinates") oldCoordsCheckButton.set_active(True) oldCoordsCheckButton.connect("toggled", self.__toggleOldCoords) oldCoordsAlignment = gtk.Alignment(xalign = 0.5) oldCoordsAlignment.add(oldCoordsCheckButton) prevNextConfBox.pack_start(oldCoordsAlignment, True, True, BUTTON_SPACING) suiteFrameHBox.pack_start(treeViewScroll, True, False, BUTTON_SPACING) #we don't next previous and next suite buttons if there's only a single suite if len(self.__bestPath) > 1: prevNextSuiteBox = gtk.HBox(True, HBOX_SPACING) prevNextSuiteAlignment = gtk.Alignment(xalign=0.5) prevNextSuiteAlignment.add(prevNextSuiteBox) prevNextSuiteBox.pack_start(prevSuiteButton, True, True, BUTTON_SPACING) prevNextSuiteBox.pack_start(nextSuiteButton, True, True, BUTTON_SPACING) #If this is not a rotamerize call, then we put the previous and next suite buttons outside of the "Suite X of X" #frame. If it is a rotamerize call, then the buttons go inside of the frame. This is inconsistant, but it makes #things look a little more balanced (and it's a minor enough thing that I doubt anyone will ever notice the #inconsistancy) if self.__pseudoMolecule.hasSavedCoordinates(): suiteFrameVBox.pack_start(prevNextSuiteAlignment, False, False, BUTTON_SPACING) else: windowBox.pack_start(prevNextSuiteAlignment, False, False, BUTTON_SPACING) #if this is a rotamerize call, then we pack the "Accept New Coordinates" and the "Restore Original Coordinates" #buttons if self.__pseudoMolecule.hasSavedCoordinates(): doneBox = gtk.HBox(True, HBOX_SPACING) doneAlignment = gtk.Alignment(xalign = 0.5) doneAlignment.add(doneBox) windowBox.pack_start(doneAlignment, False, False, BUTTON_SPACING) doneBox.pack_start(doneButton, True, True, BUTTON_SPACING) doneBox.pack_start(cancelButton, True, True, BUTTON_SPACING) #if this isn't a rotamerize call, then we just pack the "Done" button else: doneAlignment = gtk.Alignment(xalign = 0.5) doneAlignment.add(doneButton) windowBox.pack_start(doneAlignment, False, False, BUTTON_SPACING) #set the current rot self.__selectedConf = self.__curPath[0] #go to the center of the first suite firstPhos = self.__pseudoMolecule.getPhosCoords(suiteNum) set_rotation_centre(*firstPhos) add_atom_label(self.__pseudoMolecule.molNum(), self.__pseudoMolecule.chain, int(suiteNum), " P ") #label the phosphate #Note that add_atom_label can't handle insertion codes, so this command will label the wrong atom if we are #trying to center on a suite with an insertion code graphics_draw() #display the new window self.__window.add(windowBox) self.__window.show_all() if len(self.__bestPath) > 1: nextSuiteButton.grab_focus() #make the Next Suite button the default focus if it exists else: nextConfButton.grab_focus() #otherwise, make the Next Conformer button the default focus def __changed(self, confTreeSelection, overviewTreeView): """Respond to the user selecting a different rotamer ARGUMENTS: confTreeSeletion - the TreeSelection object for the conformer TreeView RETURNS: None EFFECTS: Recalculates and reminimizes coordinates using the newly selected conformer for the current suite """ #figure out what nucleotides we need to cache startingNucIndex = self.__curSuite + self.__skipFirstNuc - 1 if startingNucIndex <= 0: startingNucIndex = 0 hasPrevSuite = False #there's no suite before this one, so we don't have to worry about changes to the starting pucker else: hasPrevSuite = True endingNucIndex = self.__curSuite + self.__skipFirstNuc + 1 #if endingNuc > self.__numNucs: endingNuc = self.__numNucs startingNucNum = self.__origCoords.nucs[startingNucIndex].resNum endingNucNum = self.__origCoords.nucs[endingNucIndex].resNum #print "\n\n\n*****************__changed called\n*************************\n**************************" #print "startingNucIndex =", startingNucIndex #print "endingNucIndex =", endingNucIndex #print "hasPrevSuite =", hasPrevSuite #print "startingNucNum =", startingNucNum #print "endingNucNum =", endingNucNum #print "self.__prevTorsions = ", self.__prevTorsions #from time import sleep; sleep(3) previousConf = self.__selectedConf #determine if we need to store the previous nucleotide when cacheing the structure so that we can store its O3' position #(the O3' generally doesn't move much, but if a minimization fails horribly (i.e. we don't get anything chemically reasonable), #then the O3' will often be far out of position) if startingNucIndex >= 2: cacheStartingNucNum = self.__origCoords.nucs[startingNucIndex-1].resNum else: cacheStartingNucNum = startingNucNum #if need be, cache the existing structure if not(self.__structureCache.has_key(previousConf)): #print "Storing structure for", previousConf self.__structureCache[previousConf] = self.__pseudoMolecule.getCootNucs(cacheStartingNucNum, endingNucNum) #figure out what the newly selected conformer is (confListStore, selectedConfIter) = confTreeSelection.get_selected() selectedConf = confListStore.get_value(selectedConfIter, 0) print "Conformer %s was selected" % selectedConf self.__selectedConf = selectedConf #see if we're changing the starting pucker (and we're not at the first suite or the first suite of a rotamerize) if hasPrevSuite and puckerList[previousConf][0] != puckerList[selectedConf][0] and not (startingNucIndex == 1 and self.__prevTorsions): preStartingNucNum = self.__origCoords.nucs[startingNucIndex-1].resNum if startingNucIndex >= 2: #if there's a nucleotide before the pre-starting nucleotide, then cache that too #since we may wind up changing the O3' atom preStartingNucNum = self.__origCoords.nucs[startingNucIndex-2].resNum #print "preStartingNucNum now includes previous previous suite" #print "preStartingNucNum =", startingNucNum #if we are, then see if we need to cache the current structure if not self.__startingPuckerStructureCache.has_key(puckerList[previousConf][0]): self.__startingPuckerStructureCache[puckerList[previousConf][0]] = self.__pseudoMolecule.getCootNucs(preStartingNucNum, startingNucNum) #then see if we have the structure we need in the cache if self.__startingPuckerStructureCache.has_key(puckerList[selectedConf][0]): self.__pseudoMolecule.setCootNucs(preStartingNucNum, startingNucNum, self.__startingPuckerStructureCache[puckerList[selectedConf][0]]) changingStartingPucker = False else: changingStartingPucker = puckerList[selectedConf][0] #see if we've calculated the appropriate conformer for the previous suite if len(self.__previousSuiteConf) == 2: previousSuiteConf = self.__previousSuiteConf[puckerList[selectedConf][0]] else: #if not, then calculate it now self.__previousSuiteConf[puckerList[previousConf][0]] = self.__curPath[self.__curSuite-2] previousSuiteLeadingPucker = puckerList[self.__curPath[self.__curSuite-2]][0] previousSuiteConf = determineAlternateConf(previousSuiteLeadingPucker, puckerList[selectedConf][0], self.__curSuite-2, self.__predictedProbs) self.__previousSuiteConf[puckerList[selectedConf][0]] = previousSuiteConf else: changingStartingPucker = False if self.__curSuite > 1: previousSuiteConf = self.__curPath[self.__curSuite-2] elif self.__prevTorsions: previousSuiteConf = self.__prevTorsions else: previousSuiteConf = None #print "changingStartingPucker =", changingStartingPucker #print "previousSuiteConf =", previousSuiteConf #print "self.__previousSuiteConf = ", self.__previousSuiteConf #see if we're changing the ending pucker #print "endingNuc =", endingNuc #print "self.__numNucs =", self.__numNucs if endingNucIndex == (self.__numNucs - 1 + self.__skipFirstNuc): #if this is the last suite, then we don't really care what the ending pucker is when dealing with caching the next nucleotide #so we're only going to store one cached structure for both 2' and 3' ending pucker #to see if we've already stored a structure, we check to see if __endingPuckerStructureCache is a dictionary (since it gets reset to an empty dictionary in __nextSuite) #we also only need to cache a single nucleotide, since there's no endingNuc+1 nucleotide if isinstance(self.__endingPuckerStructureCache, dict): self.__endingPuckerStructureCache = self.__pseudoMolecule.getCootNucs(endingNucNum, endingNucNum) else: #if __endingPuckerStructureCache isn't a dictionary, then we've already stored a structure there and we should restore it self.__pseudoMolecule.setCootNucs(endingNucNum, endingNucNum, self.__endingPuckerStructureCache) changingEndingPucker = False if self.__nextTorsions: nextSuiteConf = self.__nextTorsions else: nextSuiteConf = None elif puckerList[previousConf][1] != puckerList[selectedConf][1]: postEndingNucNum = self.__origCoords.nucs[endingNucIndex+1].resNum #print "postEndingNucNum =", postEndingNucNum #if we're changing the ending pucker and this isn't the last suite, then see if we need to cache the current structure if not self.__endingPuckerStructureCache.has_key(puckerList[previousConf][1]): self.__endingPuckerStructureCache[puckerList[previousConf][1]] = self.__pseudoMolecule.getCootNucs(endingNucNum, postEndingNucNum) #then see if we have the structure we need in the cache if self.__endingPuckerStructureCache.has_key(puckerList[selectedConf][1]): self.__pseudoMolecule.setCootNucs(endingNucNum, postEndingNucNum, self.__endingPuckerStructureCache[puckerList[selectedConf][1]]) changingEndingPucker = False else: changingEndingPucker = puckerList[selectedConf][1] #see if we've calculated the appropriate conformer for the next suite if len(self.__nextSuiteConf) == 2: nextSuiteConf = self.__nextSuiteConf[puckerList[selectedConf][1]] else: #if not, then calculate it now self.__nextSuiteConf[puckerList[previousConf][1]] = self.__curPath[self.__curSuite] nextSuiteEndingPucker = puckerList[self.__curPath[self.__curSuite]][1] nextSuiteConf = determineAlternateConf(puckerList[selectedConf][1], nextSuiteEndingPucker, self.__curSuite, self.__predictedProbs) self.__nextSuiteConf[puckerList[selectedConf][1]] = nextSuiteConf else: postEndingNucNum = self.__origCoords.nucs[endingNucIndex+1].resNum #print "postEndingNucNum =", postEndingNucNum #even if we're not changing the ending pucker, we should still reset the structure of the next nucleotide back to how it was before we started changing rotamers if self.__endingPuckerStructureCache.has_key(puckerList[previousConf][1]): self.__pseudoMolecule.setCootNucs(endingNucNum, postEndingNucNum, self.__endingPuckerStructureCache[puckerList[previousConf][1]]) else: #if the structure hasn't been cached, then store the current structure. That means that this is the first new conformer we've selected, so we the structure is already how we want it self.__endingPuckerStructureCache[puckerList[previousConf][1]] = self.__pseudoMolecule.getCootNucs(endingNucNum, postEndingNucNum) changingEndingPucker = False if self.__curSuite < (self.__numNucs - 2): nextSuiteConf = self.__curPath[self.__curSuite] elif self.__nextTorsions: nextSuiteConf = self.__nextTorsions else: nextSuiteConf = None #print "" #print "changingEndingPucker =", changingEndingPucker #print "nextSuiteConf =", nextSuiteConf #print "self.__nextSuiteConf = ", self.__nextSuiteConf #pprint(self.__previousSuiteConf) #see if we have the structure for this conformer in the cache if self.__structureCache.has_key(selectedConf): #if we do, then just use that structure #print "Restoring structure for", selectedConf #print "startingNucNum =", startingNucNum #print "cacheStartingNucNum =", cacheStartingNucNum #print "endingNucNum =", endingNucNum self.__pseudoMolecule.setCootNucs(cacheStartingNucNum, endingNucNum, self.__structureCache[selectedConf]) self.__pseudoMolecule.drawExtraBonds() else: #if we don't, then we have to calculate one rotList = [previousSuiteConf, selectedConf, nextSuiteConf] #note that startingNuc and endingNuc start numbering at 0 (whereas Coot starts at 1, so we have to add one when we send these variables to recalcCoords) minimizationStartingNucIndex = self.__curSuite + self.__skipFirstNuc - 1 minimizationEndingNucIndex = self.__curSuite + self.__skipFirstNuc #print "minimizationStartingNucIndex = ", minimizationStartingNucIndex #print "minimizationEndingNucIndex = ", minimizationEndingNucIndex #print "changingStartingPucker = ", changingStartingPucker #print "changingEndingPucker = ", changingEndingPucker #see if we need to change any suites other than the current one if changingStartingPucker: rotList.insert(0, self.__curPath[self.__curSuite-3]) minimizationStartingNucIndex -= 1 if changingEndingPucker: if endingNucIndex < (self.__numNucs - 2 + self.__skipFirstNuc): rotList.append(self.__curPath[self.__curSuite+1]) else: rotList.append(None) minimizationEndingNucIndex += 1 minimizationStartingNucNum = self.__origCoords.nucs[minimizationStartingNucIndex].resNum minimizationEndingNucNum = self.__origCoords.nucs[minimizationEndingNucIndex].resNum #reset the necessary nucleotides so that we can restart the minimization self.__pseudoMolecule.resetNucs(minimizationStartingNucNum, minimizationEndingNucNum, self.__origCoords, self.__intermediateAtomLocs) self.__pseudoMolecule.clearExtraBonds() recalcCoords(minimizationStartingNucNum, minimizationEndingNucNum, rotList, self.__origCoords, self.__pseudoMolecule, self.__window, self.__ignoreDensity) self.__pseudoMolecule.drawExtraBonds() #if necessary, restore the B-factors for the rebuilt atoms if self.__pseudoMolecule.hasSavedBfacs(): self.__pseudoMolecule.restoreSavedBfacs(minimizationStartingNucNum, minimizationEndingNucNum) #update the conformers on the suite overview table if overviewTreeView is not None: self.__changeOverviewConformer(overviewTreeView, self.__curSuite, selectedConf) if isinstance(previousSuiteConf, basestring): #update the overview table if we've changed the previous conformer #previousSuiteConf can also be a list of torsions if we're changing the first nt of a rotamerization #but that shouldn't be reflected in the overview table self.__changeOverviewConformer(overviewTreeView, self.__curSuite-1, previousSuiteConf) if isinstance(nextSuiteConf, basestring): #update the overview table if we've changed the next conformer (ignoring lists as above) self.__changeOverviewConformer(overviewTreeView, self.__curSuite+1, nextSuiteConf) def __prevConf(self, widget, confTreeSelection): """Respond to the user clicking on the previous conformer button ARGUMENTS: widget - the button used to invoke this function confTreeSeletion - the TreeSelection object for the conformer TreeView RETURNS: None EFFECTS: Highlights the previous conformer (which will implicitly invoke __changed) """ (confListStore, selectedConfIter) = confTreeSelection.get_selected() #there is no iter_prev, so we have to jump through some hoops to get the previous row curPos = confListStore.get_path(selectedConfIter)[0] if curPos != 0: newIter = confListStore.get_iter((curPos-1)) confTreeSelection.select_iter(newIter) confTreeView = confTreeSelection.get_tree_view() confTreeView.scroll_to_cell((curPos-1)) def __nextConf(self, widget, confTreeSelection): """Respond to the user clicking on the next conformer button ARGUMENTS: widget - the button used to invoke this function confTreeSeletion - the TreeSelection object for the conformer TreeView RETURNS: None EFFECTS: Highlights the next conformer (which will implicitly invoke __changed) """ (confListStore, selectedConfIter) = confTreeSelection.get_selected() newIter = confListStore.iter_next(selectedConfIter) if newIter is not None: confTreeSelection.select_iter(newIter) confTreeView = confTreeSelection.get_tree_view() confTreeView.scroll_to_cell(confListStore.get_path(newIter)) def __createConfListStore(self, curSuite): """Creates a ListStore containing data about the specified suite ARGUMENTS: curSuite - the suite to create a ListStore for RETURNS: confListStore - a ListStore containing data about curSuite initialConfNum - the most likely suite (i.e. the row of confListStore to initially highlight) """ curSuite -= 1 #the arrays start at 0, not 1 confListStore = gtk.ListStore(str, str, str, str, 'gboolean', 'gboolean', 'gboolean', int) conformerList = self.__predictedProbs[curSuite].items() conformerList.sort(key=lambda(x): x[1], reverse=True) (defaultLeadingPucker, defaultEndingPucker) = puckerList[self.__bestPath[curSuite]] i = 0 for (conf, score) in conformerList: confListStore.append([conf, "%4.2f" % score] + puckerListFullName[conf] + [(puckerList[conf][0] != defaultLeadingPucker), (puckerList[conf][1] != defaultEndingPucker), (conf == self.__bestPath[curSuite]), i+1]) if conf == self.__curPath[curSuite]: initialConfNum = i i += 1 return (confListStore, initialConfNum) def __createConfTreeView(self, confListStore, initialConfNum): """Create the TreeView object for the list of conformers ARGUMENTS: confListStore - the ListStore object to attach to the TreeView initialConfNum - the most likely suite (i.e. the row of confListStore to initially highlight) RETURNS: confTreeView - the newly created TreeView object confTreeSelection - the TreeSelection object associated with the TreeView rowHeight - the height of a single row in the TreeView (used to determine how high to size the ScrolledWindow) """ #then create the columns and pack them into the tree view object confTreeView = gtk.TreeView(confListStore) confTreeView.set_enable_search(False) renderRank = gtk.CellRendererText() renderRank.set_property('xalign', 0.5) renderRank.set_property('cell-background-gdk', self.__window.get_style().bg[gtk.STATE_NORMAL]) columnRank = gtk.TreeViewColumn("Rank", renderRank, text=7) confTreeView.append_column(columnRank) renderConf = gtk.CellRendererText() renderConf.set_property('xalign', 0.5) renderConf.set_property('cell-background', BEST_PATH_COLOR) columnConf = gtk.TreeViewColumn("Conformer", renderConf, text=0, cell_background_set=6) confTreeView.append_column(columnConf) renderScore = gtk.CellRendererText() renderScore.set_property('xalign', 0.5) renderScore.set_property('cell-background', BEST_PATH_COLOR) columnScore = gtk.TreeViewColumn("Score", renderScore, text=1, cell_background_set=6) confTreeView.append_column(columnScore) renderLeadingPucker = gtk.CellRendererText() renderLeadingPucker.set_property('xalign', 0.5) columnLeadingPucker = gtk.TreeViewColumn("Leading Pucker", renderLeadingPucker, text=2) columnLeadingPucker.set_cell_data_func(renderLeadingPucker, self.__setPuckerBg, (4, 6)) confTreeView.append_column(columnLeadingPucker) renderEndingPucker = gtk.CellRendererText() renderEndingPucker.set_property('xalign', 0.5) columnEndingPucker = gtk.TreeViewColumn("Ending Pucker", renderEndingPucker, text=3) columnEndingPucker.set_cell_data_func(renderEndingPucker, self.__setPuckerBg, (5, 6)) confTreeView.append_column(columnEndingPucker) #select the appropriate row of the TreeView confTreeSelection = confTreeView.get_selection() confTreeSelection.select_iter(confListStore.get_iter((initialConfNum))) #figure out the height of a row (including the line dividing two rows) rowHeight = columnLeadingPucker.cell_get_size()[4] + confTreeView.style_get_property("vertical-separator") return (confTreeView, confTreeSelection, rowHeight) def __createConfScrolledWindow(self, confTreeView, rowHeight): """Create a ScrolledWindow object to display the conformer TreeView ARGUMENTS: confTreeView - the TreeView object used to display the list of conformers rowHeight - the height of a row in confTreeView RETURNS: treeViewScroll - the newly created ScrolledWindow object treeViewScrollWidth - the width of treeViewScroll (used to figure out how wide to make the overview ScrolledWindow) """ treeViewScroll = gtk.ScrolledWindow() treeViewScroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) #we make sure that the window is big enough that it should never need a horizontal scrollbar, but sometimes GTK #insists on adding one, so we disallow it here treeViewScroll.set_shadow_type(gtk.SHADOW_ETCHED_IN) treeViewScroll.add(confTreeView) #figure out the size of the treeview + vertical scrollbar is so we can set the scrolled window to the appropriate size (tableWidthReq, tableHeightReq) = confTreeView.size_request() try: vScrollWidthReq = treeViewScroll.get_vscrollbar().size_request()[0] except AttributeError: #get_vscrollbar only exists in PyGTK 2.8 or above #if we're using an older version of PyGTK, then just check the default size of a vertical scrollbar vScrollWidthReq = gtk.VScrollbar().size_request()[0] scrollbarSpacing = treeViewScroll.style_get_property("scrollbar-spacing") #rowHeight = columnLeadingPucker.cell_get_size()[4] + confTreeView.style_get_property("vertical-separator") headerHeight = tableHeightReq - len(puckerList)*rowHeight treeViewScrollWidth = tableWidthReq + vScrollWidthReq + scrollbarSpacing + 4 treeViewScroll.set_size_request(treeViewScrollWidth, headerHeight + rowHeight * 3 + 4) #the "+ 4"'s are for the shadow, since there doesn't seem to be a GTK property corresponding to shadow size return (treeViewScroll, treeViewScrollWidth) def __createOverviewTreeView(self, confTreeView, suiteFrame): """Create a TreeView object for the suite overview ARGUMENTS: confTreeView - the TreeView object for the list of conformers suiteFrame - the Frame object containing information about the current suite RETURNS: overviewTreeView - the newly created TreeView object """ #create the overview list store typeList = [str] * len(self.__curPath) overviewListStore = gtk.ListStore(*typeList) resNumList = self.__origCoords.resNumList()[1+self.__skipFirstNuc:-1] overviewListStore.append(resNumList) overviewListStore.append([conf.replace("&", "&") for conf in self.__curPath]) #we need to escape the ampersands since we're using Pango markup #decide how wide to make the columns #they should be as wide as the longest residue number or conformer name (conformer names are always 2 characters) columnWidthChars = max([len(resNum) for resNum in resNumList] + [2]) overviewTreeView = gtk.TreeView(overviewListStore) overviewTreeView.set_enable_search(False) overviewTreeView.set_headers_visible(False) overviewTreeView.get_selection().set_mode(gtk.SELECTION_NONE) #set_grid_lines() only exists in Gtk 2.10 or newer try: overviewTreeView.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_VERTICAL) except AttributeError: #in Gtk's older than 2.10, there doesn't seem to be a way to set the gridlines to be vertical only #without modifying the theme, which we probably don't want to do #so we just leave the gridlines out #(The "enable-grid-lines" property can only be set to True or False, not vertical-only) pass setColumn = len(self.__curPath) #this column is False for the suite number row and True for the conformer row #we use it to selectively apply formatting changes #create columns and renderers for each suite for (colNum, conf) in enumerate(self.__curPath): #print "(colNum, conf) = ", colNum, conf renderer = gtk.CellRendererText() renderer.set_property('xalign', 0.5) renderer.set_property('width-chars', columnWidthChars+2) #the +2 ensures that we have enough width for bolding #renderer.set_property('width-chars', 8) #for debugging if colNum == 0: renderer.set_property('weight', BOLD_WEIGHT) #figure out the ratio of the best conformer score to the second best conformer score (bestProb, secondBestProb) = sorted(self.__predictedProbs[colNum].values(), reverse=True)[0:2] try: ratio = bestProb / secondBestProb except ZeroDivisionError: ratio = float('inf') #retrieve the minimization score for the second nucleotide of the suite #for the first suite, use either the first or the second nucleotide, whichever is worse if colNum == 0: minimizationScore = max(self.__minimizationScores[0], self.__minimizationScores[1]) else: minimizationScore = self.__minimizationScores[colNum+1] #set the background color based on how sure we are about the prediction or how well the minimization worked (whichever is worse) if ratio <= 1.2 or minimizationScore >= 30: renderer.set_property('cell-background', 'red') elif ratio <= 1.75 or minimizationScore >= 20: renderer.set_property('cell-background', 'orange') elif ratio <= 2.5 or minimizationScore >= 10: renderer.set_property('cell-background', 'yellow') #otherwise, leave the background as white #store the column number as the title so we can easily retrieve it later column = gtk.TreeViewColumn(str(colNum+1), renderer, markup=colNum) column.set_expand(True) overviewTreeView.append_column(column) #make all cells the same height #otherwise the height of the second row will change when the font switches from bold to bold+italics if colNum == 0: #we want the cells to be the same height that the first column starts as, since that one starts bold colHeight = column.cell_get_size()[4] - confTreeView.style_get_property("vertical-separator") renderer.set_property("height", colHeight) overviewTreeView.connect("button-press-event", self.__overviewClick, confTreeView, suiteFrame) return overviewTreeView def __createOverviewScrolledWindow(self, overviewTreeView, prevConfButton, treeViewScrollWidth): """Create a ScrolledWindow object to display the suite overview TreeView ARGUMENTS: overviewTreeView - the TreeView object used to display the suite overview prevConfButton - the previous conformer button (so we can calculate its width) treeViewScrollWidth - the width of the TreeView used to display the conformer list RETURNS: overviewScroll - the newly created ScrolledWindow object wideOverview - whether or not the ScrolledWindow takes up the full window width """ #If the overview table is wider than the suite frame (i.e. wider than the window is going to be), then we want to #limit the overview table width to the size of the suite frame and include a horizontal scrollbar. #To know if the overview table is wider or narrower than the suite frame , first we have to determine the width of the suite frame. #Since the suite frame isn't realized (i.e. drawn on the screen) yet, we can just call suite_frame.size_request(), #so we have to add up the size requests for all of the lowest level objects. overviewScroll = gtk.ScrolledWindow() overviewScroll.set_shadow_type(gtk.SHADOW_ETCHED_IN) overviewScroll.add(overviewTreeView) (overviewTableWidthReq, overviewTableHeightReq) = overviewTreeView.size_request() #determine the width of the previous conformer button prevConfButtonHBox = prevConfButton.get_child() #add up the width of the icon and the text prevConfButtonWidth = sum([child.size_request()[0] for child in prevConfButtonHBox.get_children()]) #add on the width of the icon and text padding (the 2* is for left and right padding for each) prevConfButtonWidth += (2 * prevConfButtonHBox.query_child_packing(prevConfButtonHBox.get_children()[0])[2] + 2 * prevConfButtonHBox.query_child_packing(prevConfButtonHBox.get_children()[1])[2]) suiteFrameWidth = treeViewScrollWidth + prevConfButtonWidth + BUTTON_SPACING + 3*HBOX_SPACING if overviewTableWidthReq <= suiteFrameWidth: overviewScroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER) overviewScroll.set_size_request(overviewTableWidthReq + 4, overviewTableHeightReq + 4) wideOverview = False else: #figure out how tall the scrollbar is try: hScrollHeightReq = overviewScroll.get_hscrollbar().size_request()[1] except AttributeError: #get_hscrollbar only exists in PyGTK 2.8 or above #if we're using an older version of PyGTK, then just check the default size of a horizontal scrollbar hScrollHeightReq = gtk.HScrollbar().size_request()[1] scrollbarSpacing = overviewScroll.style_get_property("scrollbar-spacing") overviewScroll.set_policy(gtk.POLICY_ALWAYS, gtk.POLICY_NEVER) overviewScroll.set_size_request(suiteFrameWidth, hScrollHeightReq + scrollbarSpacing + overviewTableHeightReq + 4) wideOverview = True return (overviewScroll, wideOverview) def __setPuckerBg(self, column, renderer, confListStore, iter, columnList): """Appropriately set the background of a cell listing sugar pucker ARGUMENTS: column - the TreeViewColumn to be changed renderer - the CellRenderer for the column confListStore - the ListStore containing the displayed data iter - a TreeIter pointing to the specified row of the TreeView columnList - a list of (the number of the column to be changed, the number of the column containing the best conformer) RETURNS: None EFFECTS: Sets the background color for the specified cell to: DIFFERENT_PUCKER_COLOR if it's different than the pucker for the best path BEST_PATH_COLOR is it's the best conformer None otherwise """ (puckerColumn, bestPathColumn) = columnList isDifferentPucker = confListStore.get_value(iter, puckerColumn) if isDifferentPucker: renderer.set_property('cell-background', DIFFERENT_PUCKER_COLOR) else: isBestPath = confListStore.get_value(iter, bestPathColumn) if isBestPath: renderer.set_property('cell-background', BEST_PATH_COLOR) else: renderer.set_property('cell-background', None) def __prevSuite(self, widget, confTreeView, suiteFrame, overviewTreeView): """Select the previous suite ARGUMENTS: widget - the button used to invoke this function. Ignored. confTreeView - the TreeView used to display data about conformers for the current suite suiteFrame - the GTK frame containing the "Suite X of Y" label overviewTreeView - the TreeView used to display the suite overview RETURNS: None EFFECTS: Recenters the screen on the previous suite and updates the RCrane GUI to reflect that suite. """ if self.__curSuite > 1: self.__prevOrNextSuite(confTreeView, suiteFrame, overviewTreeView, self.__curSuite, self.__curSuite-1) def __nextSuite(self, widget, confTreeView, suiteFrame, overviewTreeView): """Select the next suite ARGUMENTS: widget - the button used to invoke this function. Ignored. confTreeView - the TreeView used to display data about conformers for the current suite suiteFrame - the GTK frame containing the "Suite X of Y" label overviewTreeView - the TreeView used to display the suite overview RETURNS: None EFFECTS: Recenters the screen on the next suite and updates the RCrane GUI to reflect that suite. """ if self.__curSuite < len(self.__predictedProbs): self.__prevOrNextSuite(confTreeView, suiteFrame, overviewTreeView, self.__curSuite, self.__curSuite+1) def __prevOrNextSuite(self, confTreeView, suiteFrame, overviewTreeView, oldSuiteIndex, newSuiteIndex): """Select the specified suite ARGUMENTS: confTreeView - the TreeView used to display data about conformers for the current suite suiteFrame - the GTK frame containing the "Suite X of Y" label overviewTreeView - the TreeView used to display the suite overview oldSuiteIndex - the suite we are moving from newSuiteIndex - the suite we are moving to RETURNS: None EFFECTS: Recenters the screen on newSuite and updates the RCrane GUI to reflect that suite. """ #store the newly selected conformer self.__curPath[oldSuiteIndex-1] = self.__selectedConf #if we've switched puckers, store the previous or next conformers if len(self.__previousSuiteConf) == 2: #print "Previous suite was", self.__curPath[oldSuiteIndex-2] self.__curPath[oldSuiteIndex-2] = self.__previousSuiteConf[puckerList[self.__selectedConf][0]] #print "Setting previous suite to", self.__curPath[oldSuiteIndex-2] if len(self.__nextSuiteConf) == 2: #print "Next suite was", self.__curPath[oldSuiteIndex] self.__curPath[oldSuiteIndex ] = self.__nextSuiteConf [puckerList[self.__selectedConf][1]] #print "Setting next suite to", self.__curPath[oldSuiteIndex] #go to the center of the next suite oldSuiteNum = self.__origCoords.nucs[oldSuiteIndex + self.__skipFirstNuc].resNum newSuiteNum = self.__origCoords.nucs[newSuiteIndex + self.__skipFirstNuc].resNum newPhos = self.__pseudoMolecule.getPhosCoords(newSuiteNum) set_rotation_centre(*newPhos) remove_atom_label(self.__pseudoMolecule.molNum(), self.__pseudoMolecule.chain, int(oldSuiteNum), " P ") add_atom_label(self.__pseudoMolecule.molNum(), self.__pseudoMolecule.chain, int(newSuiteNum), " P ") #label the phosphate graphics_draw() #update the suite label if self.__useResNums: frameTitle = "Suite %s (%i of %i)" % (str(newSuiteNum), newSuiteIndex, len(self.__predictedProbs)) else: frameTitle = "Suite %i of %i" % (newSuiteIndex, len(self.__predictedProbs)) suiteFrame.set_label(frameTitle) (newListStore, initialConfNum) = self.__createConfListStore(newSuiteIndex) confTreeSelection = confTreeView.get_selection() confTreeSelection.disconnect(self.__treeViewChangedHandlerID) confTreeView.set_model(newListStore) confTreeSelection.select_iter(newListStore.get_iter((initialConfNum))) confTreeView.scroll_to_cell((initialConfNum), use_align=True, row_align=1.0) self.__treeViewChangedHandlerID = confTreeSelection.connect("changed", self.__changed, overviewTreeView) #update the suite overview table #move the bolding to the new column overviewTreeView.get_column(oldSuiteIndex-1).get_cell_renderers()[0].set_property('weight', pango.WEIGHT_NORMAL) overviewTreeView.get_column(newSuiteIndex-1).get_cell_renderers()[0].set_property('weight', BOLD_WEIGHT) #scroll the treeview if necessary overviewTreeView.scroll_to_cell((0,), overviewTreeView.get_column(newSuiteIndex-1)) #force a redraw of both rows of the overview stable so that the boldness gets updated properly overviewModel = overviewTreeView.get_model() overviewModel.row_changed((0,), overviewModel.get_iter((0,))) overviewModel.row_changed((1,), overviewModel.get_iter((1,))) #set up the cache variables self.__selectedConf = self.__curPath[newSuiteIndex-1] self.__curSuite = newSuiteIndex self.__clearCache() def __clearCache(self): """Reset all data about the current suite (appropriate for when we've just switched suites) ARGUMENTS: None RETURNS: None EFFECTS: Resets (or initializes) all cache variables """ self.__structureCache = {} self.__startingPuckerStructureCache = {} self.__endingPuckerStructureCache = {} self.__previousSuiteConf = {} self.__nextSuiteConf = {} self.__intermediateAtomLocsStartingPuckerCache = {} self.__intermediateAtomLocsEndingPuckerCache = {} self.__intermediateAtomLocsCache = {} def __closeWin(self, window, widget): """Close the RCrane window ARUGMENTS: window - the window to close widget - the widget used to close the window NOTES: This function simply calls __accept with the appropriate argument list """ #self.__close(widget) self.__accept(widget) def __close(self, widget = None): """Close the RCrane window ARUGMENTS: widget - the widget used to close the window RETURNS: None EFFECTS: Closes the RCrane window """ self.__pseudoMolecule.clearExtraBonds() if self.__oldCoordsObj is not None: close_generic_object(self.__oldCoordsObj) graphics_draw() self.__window.destroy() def __accept(self, widget = None): """Accept the new molecule and close the RCrane window ARUGMENTS: widget - the widget used to close the window RETURNS: None NOTE: This function re-orders atoms in the pseudomolecule (that's the "accept" part) and then calls __close """ #perform the final molecule cleanup (reorder atoms and, if we just did a rotamerize, fix segids) self.__pseudoMolecule.finalMoleculeCleanup(fixSegids = bool(self.__pseudoMolecule.hasSavedCoordinates())) self.__close(widget) def __toggleOldCoords(self, widget): """Toggle display of the old coordinates object ARGUMENTS: widget - the check button that was toggled RETURNS: None """ set_display_generic_object(self.__oldCoordsObj, int(widget.get_active())) def __restoreOldCoords(self, widget): """Set the coordinates back to the way they were before we called rotamerize ARGUMENTS: widget - the cancel button that was clicked to call this function. Ignored RETURNS: None """ self.__pseudoMolecule.restoreSavedCoordinates() self.__close() def __overviewClick(self, overviewTreeView, event, confTreeView, suiteFrame): """Respond to the user clicking on the overview TreeView object ARGUMENTS: overviewTreeView - the TreeView object for the suite overview event - the click event confTreeView - the TreeView object for the list of conformers suiteFrame - the Frame object containing information about the current suite RETURNS: None """ #figure out which column was just clicked on clickedColumn = overviewTreeView.get_path_at_pos(int(event.x), int(event.y))[1] #The suite index for each column is stored as the column title #(since there seems to be no easy way to get that information from the column object itself) newSuite = int(clickedColumn.get_title()) if newSuite == self.__curSuite: #if the user clicked on the current suite, then just re-center newSuiteNum = self.__origCoords.nucs[newSuite + self.__skipFirstNuc].resNum newPhos = self.__pseudoMolecule.getPhosCoords(newSuiteNum) set_rotation_centre(*newPhos) else: #if the user clicked on a new suite #print "Changing suites in __overviewClick" self.__prevOrNextSuite(confTreeView, suiteFrame, overviewTreeView, self.__curSuite, newSuite) def __changeOverviewConformer(self, overviewTreeView, suiteNum, newConf): """Change the conformer listed in the overview table for the specified suite ARGUMENTS: overviewTreeView - the TreeView object for the suite overview suiteNum - the number of the suite to change newConf - the new conformer to list in the overview table RETURNS: None """ suiteNum -= 1 #we need the suite numbers to start at 0, not 1 overviewListStore = overviewTreeView.get_model() if newConf == self.__bestPath[suiteNum]: overviewListStore.set_value(overviewListStore.get_iter((1,)), suiteNum, newConf.replace("&", "&")) else: overviewListStore.set_value(overviewListStore.get_iter((1,)), suiteNum, "" + newConf.replace("&", "&") + "")