예제 #1
0
 def __init__(self, alignment=None, master=None, name=None, **kw):
     self.name = name
     self.xspace = 10
     self.yspace = 20
     self.selection = ()
     self.selectionTags = []
     self.colors = residueColors
     self.colors['default'] = 'black',
     self.colors['selection'] = 'yellow'
     self.colors['|'] = 'magenta'
     #somewhere to store any tagspecifc colors
     self.colors['special'] = {}
     self.Master = master
     if alignment:
         self.alignment = alignment
         self.name = alignment.name
     else:
         self.alignment = Alignment()
     self.createGUI()
예제 #2
0
class AlignmentEditor:
    """
    GUI for editing sequence alignments. Note to self (and anyone
    else who cares...): the top thing on the window is bottom of the
    displayList..."""
    def pack(self, *args, **kw):
        self.master.pack(args, **kw)

    def grid(self, *args, **kw):
        self.master.grid(args, **kw)

    def bind(self, *args, **kw):
        self.master.bind(args, **kw)

    def __init__(self, alignment=None, master=None, name=None, **kw):
        self.name = name
        self.xspace = 10
        self.yspace = 20
        self.selection = ()
        self.selectionTags = []
        self.colors = residueColors
        self.colors['default'] = 'black',
        self.colors['selection'] = 'yellow'
        self.colors['|'] = 'magenta'
        #somewhere to store any tagspecifc colors
        self.colors['special'] = {}
        self.Master = master
        if alignment:
            self.alignment = alignment
            self.name = alignment.name
        else:
            self.alignment = Alignment()
        self.createGUI()

    def createGUI(self):
        #self.widgetArea = Tkinter.Frame(self, borderwidth=2, relief='sunken')
        #if self.hasGUI:
        #    return
        if self.Master is None:
            self.Master = master = tkinter.Toplevel(self.Master)
            master.title('Alignment Editor')
        else:
            #    master = Tkinter.Toplevel(self.Master)
            master = self.Master

        #Tkinter.Frame.__init__(self ,master)
        #Tkinter.Pack.config(self, expand=1, fill=Tkinter.BOTH)
        self.createMenus()
        self.canvasFrame = tkinter.Frame(self)
        self.canvas = Pmw.ScrolledCanvas(self.canvasFrame,
                                         usehullsize=1,
                                         hull_width=600,
                                         hull_height=200,
                                         hscrollmode='dynamic',
                                         vscrollmode='dynamic',
                                         canvasmargin=1)
        self.canvas.pack(side=tkinter.LEFT, expand=1, fill=tkinter.BOTH)
        self.canvasFrame.pack(side=tkinter.LEFT, expand=1, fill=tkinter.BOTH)
        self.canvas._canvas.bind("<ButtonPress-1>", self.mouseDown)
        self.canvas._canvas.bind("<Button1-Motion>", self.mouseMotion)
        self.canvas._canvas.bind("<Button1-ButtonRelease>", self.mouseUp)
        self.canvas._canvas.bind("<Shift-ButtonPress-1>", self.startSelection)
        self.canvas._canvas.bind("<Shift-Button1-Motion>",
                                 self.continueSelection)
        self.canvas._canvas.bind("<Shift-Button1-ButtonRelease>",
                                 self.mouseSelect)
        self.canvas._canvas.bind("<Control-ButtonPress-1>",
                                 self.startSelection)
        self.canvas._canvas.bind("<Control-Button1-Motion>",
                                 self.continueSelection)
        self.canvas._canvas.bind("<Control-Button1-ButtonRelease>",
                                 self.mouseDeselect)
        self.fillCanvas()
        #self.hasGUI = 1

    def startSelection(self, event=None):
        #print 'In startSelection'
        self.x0 = self.canvas.canvasx(event.x)
        self.y0 = self.canvas.canvasy(event.y)

    def continueSelection(self, event=None):
        #print 'In continueSelection'
        self.x1 = self.canvas.canvasx(event.x)
        self.y1 = self.canvas.canvasy(event.y)
        self.clearSelBox()
        self.canvas.create_line(self.x0,
                                self.y0,
                                self.x1,
                                self.y0,
                                tags=('selBox'))
        self.canvas.create_line(self.x0,
                                self.y0,
                                self.x0,
                                self.y1,
                                tags=('selBox'))
        self.canvas.create_line(self.x0,
                                self.y1,
                                self.x1,
                                self.y1,
                                tags=('selBox'))
        self.canvas.create_line(self.x1,
                                self.y0,
                                self.x1,
                                self.y1,
                                tags=('selBox'))

    def clearSelBox(self):
        #print 'In clearSelBox'
        items = self.canvas.find_withtag('selBox')
        for item in items:
            self.canvas.delete(item)

    def mouseSelect(self, event=None, deselect=0):
        """ deselect=1 if removing selection
        """
        #print 'In select'
        self.x1 = self.canvas.canvasx(event.x)
        self.y1 = self.canvas.canvasy(event.y)
        self.clearSelBox()
        items = self.canvas.find_overlapping(self.x0, self.y0, self.x1,
                                             self.y1)
        #print items
        self.select(items, deselect)

    def select(self, items, deselect=0):
        if not items:
            return
        self.lastSelect = [deselect, []]
        for item in items:
            resTag, seqTag, uniqTag = self.canvas.gettags(item)[:3]
            if not deselect:
                if item not in self.selection:
                    self.canvas.itemconfig(item, fill=self.colors['selection'])
                    self.canvas.addtag_withtag('selected', uniqTag)
            else:
                if item in self.selection:
                    self.canvas.itemconfig(item, fill=self.colors['default'])
                    self.canvas.dtag(item, 'selected')
            self.lastSelect[1].append(item)
        self.selection = self.canvas.find_withtag('selected')
        self.rebuildSelectionTags()

    def rebuildSelectionTags(self):
        #print 'In rebuildSelectionTags'
        self.selectionTags = []
        for item in self.selection:
            self.selectionTags.append(self.canvas.gettags(item))

    def mouseDeselect(self, event=None):
        #print 'In deselect'
        self.mouseSelect(event=event, deselect=1)

    def clearSelection(self):
        #print 'In clearSelection'
        self.updateColor(self.selectionTags, self.colors['default'])
        self.canvas.dtag('all', 'selected')
        self.selection = ()
        self.selectionTags = []

    def mouseDown(self, event=None):
        #print 'In mouseDown'
        # this method has to figure out where we are on the canvas when the button is pressed
        tags = self.canvas.gettags('current')
        #markers for the mousemotion
        self.x0 = self.canvas.canvasx(event.x)
        self.y0 = self.canvas.canvasy(event.y)
        #return
        if tags:
            self.resTag = tags[0]
            self.seqTag = tags[1]
            self.uniqTag = tags[2]
            self.currentResidue = self.canvas.find_withtag(self.uniqTag)
            self.currentSeq = self.canvas.find_withtag(self.seqTag)
            self.findAllToRight()
            self.findToLeft()
            self.findNeighborSequences()

    def findNeighborSequences(self):
        currTag = self.seqTag
        currIndex = self.alignment.seqNames.index(currTag)
        if currIndex != len(self.alignment.seqNames) - 1:
            nextTag = self.alignment.seqNames[currIndex + 1]
            self.nextSeq = self.canvas.find_withtag(nextTag)
        else:
            self.nextSeq = ()
        if currIndex != 0:
            prevTag = self.alignment.seqNames[currIndex - 1]
            self.prevSeq = self.canvas.find_withtag(prevTag)
        else:
            self.prevSeq = ()

    def findAllToRight(self):
        #print 'In findAllToRight'
        currentSeq = list(self.currentSeq)
        index = currentSeq.index(self.currentResidue[0])
        self.allToRight = tuple(currentSeq[index:])

    def findToLeft(self):
        #print 'In findToLeft'
        currentResidue = self.currentResidue
        if currentResidue[0] == self.canvas.find_all()[0]:
            return None
        prevResidue = self.canvas.find_below(currentResidue)
        tags = self.canvas.gettags(prevResidue)
        self.toLeft = prevResidue

    def mouseMotion(self, event=None):
        #print 'In mouseMotion'
        self.x1 = self.canvas.canvasx(event.x)
        self.y1 = self.canvas.canvasy(event.y)
        tags = self.canvas.gettags(self.currentResidue)
        #dragging to left closes a gap. Can't be done if there is no gap.
        if (self.x1 < self.x0 - self.xspace
                and self.canvas.itemcget(self.toLeft, 'text') == '-'
                and 'movable' in tags):
            self.closeGap()
        #dragging to right opens a gap. Can always be done.
        if (self.x1 > self.x0 + self.xspace and 'movable' in tags):
            self.openGap()
        #dragging up swaps current and previous sequences
        if self.y1 > self.y0 + self.yspace:
            self.swapSequences(self.currentSeq, self.nextSeq, direction='down')
        if self.y1 < self.y0 - self.yspace:
            self.swapSequences(self.prevSeq, self.currentSeq, direction='up')

    def swapSequences(self, topSequence, bottomSequence, direction):
        #print 'In swapSequences'
        #check both sequences exist:
        if not (topSequence and bottomSequence):
            return
        currTag = self.seqTag
        seqIndex = self.alignment.seqNames.index(currTag)
        #move the sequences up and down:
        for item in topSequence:
            self.canvas.move(item, 0, self.yspace)
        for item in bottomSequence:
            self.canvas.move(item, 0, -self.yspace)
        #reset the starting coordinates and update the
        #displaylist. Also update the order of the alignment sequences
        if direction == 'down':
            #swapping currentSeq and nextSeq
            nextTag = self.alignment.seqNames[seqIndex + 1]
            self.alignment.seqNames[seqIndex] = nextTag
            self.alignment.seqNames[seqIndex + 1] = currTag
            self.y0 = self.y0 + self.yspace
            items = list(self.currentSeq)
            items.reverse()
            for item in items:
                self.canvas._canvas.lift(item, (self.nextSeq[-1], ))
        else:
            #swapping prevSeq and currentSeq
            prevTag = self.alignment.seqNames[seqIndex - 1]
            self.alignment.seqNames[seqIndex] = prevTag
            self.alignment.seqNames[seqIndex - 1] = currTag
            self.y0 = self.y0 - self.yspace
            items = list(self.prevSeq)
            items.reverse()
            for item in items:
                self.canvas._canvas.lift(item, (self.currentSeq[-1], ))
        self.findToLeft()
        self.findNeighborSequences()

    def closeGap(self):
        # delete the gap item
        self.canvas.delete(self.toLeft)
        # update self.toLeft
        self.findToLeft()
        # move current item to left
        for item in self.allToRight:
            self.canvas.move(item, -self.xspace, 0)
        #need to tag this sequence as edited
        tags = self.canvas.gettags(self.currentResidue)
        if 'edited' not in tags:
            self.canvas.addtag_withtag('edited', tags[1])
        #then need to reset x0, so it can be repeated:
        self.x0 = self.x0 - self.xspace

    def openGap(self):
        #find out where we are so we know where to insert
        coordx, coordy = self.canvas.coords(self.currentResidue)
        #move everything that is to the right, even further to the right
        for item in self.allToRight:
            self.canvas.move(item, self.xspace, 0)
        #insert a gap at the current coordinates
        tags = self.canvas.gettags(self.currentResidue)
        restag, seqtag, uniqtag = tags[:3]
        restag = 'gap' + str(self.gapnum)
        self.gapnum = self.gapnum + 1
        uniqtag = seqtag + '_' + restag
        #need to tag this particular sequence as edited
        if 'edited' not in tags:
            self.canvas.addtag_withtag('edited', seqtag)
        #need to add the new gap, also tagged as edited
        self.canvas.create_text(coordx,
                                coordy,
                                text='-',
                                tags=(restag, seqtag, uniqtag, 'movable',
                                      'edited'))
        #need to resize scroll region to accomodate new gap
        self.canvas.resizescrollregion()
        #then need to move things around in the displaylist so that the new item
        #is just below the old one (i.e just above toLeft)
        newItem = self.canvas.find_all()[-1]
        self.canvas._canvas.lift(newItem, self.toLeft)
        #then need to redefine the current sequence
        self.currentSeq = self.canvas.find_withtag(self.seqTag)
        #need to find the new toLeft
        self.findToLeft()
        #then need to reset x0, so it can be repeated:
        self.x0 = self.x0 + self.xspace

    def mouseUp(self, event=None):
        self.remakeAlignment()
        #self.redraw()
        return

    def remakeAlignment(self):
        """Replaces the edited sequences in the underlying alignment.
        """
        seqStr = None
        edited = self.canvas.find_withtag('edited')
        if edited == ():
            return
        for item in edited:
            tags = self.canvas.gettags(item)
            if tags[0] == 'name':
                if seqStr:
                    sequence = Sequence(name=sequenceName, sequence=seqStr)
                    self.alignment.deleteSequence(sequenceName)
                    self.alignment.addSequence(sequence, index)
                sequenceName = tags[1]
                index = self.alignment.seqNames.index(sequenceName)
                seqStr = ''
            else:
                seqStr = seqStr + self.canvas.itemcget(item, 'text')
        sequence = Sequence(name=sequenceName,
                            sequence=seqStr)  #tag on the final sequence
        self.alignment.deleteSequence(sequenceName)
        self.alignment.addSequence(sequence, index)
        self.canvas.dtag('all', 'edited')

    def createMenus(self):
        #print 'In createMenus'
        self.mBar = tkinter.Frame(self, relief=tkinter.RAISED, borderwidth=2)
        self.mBar.pack(fill=tkinter.X)
        self.menuButtons = {}
        self.makeFileMenu()
        self.makeEditMenu()
        self.mBar.tk_menuBar(*list(self.menuButtons.values()))
        self.title = tkinter.Label(self.mBar, text=self.name)
        self.title.pack(side=tkinter.RIGHT)

    def makeFileMenu(self):
        #print 'In makeFileMenu'
        File_button = tkinter.Menubutton(self.mBar, text='File', underline=0)
        self.menuButtons['File'] = File_button
        File_button.pack(side=tkinter.LEFT, padx='1m')
        File_button.menu = tkinter.Menu(File_button)
        File_button.menu.add_command(label='Load...',
                                     underline=0,
                                     command=self.loadFile)
        File_button.menu.add_command(label='Write...',
                                     underline=0,
                                     command=self.writeFile)
        File_button.menu.add_command(label='Exit...',
                                     underline=0,
                                     command=self.exit)
        File_button['menu'] = File_button.menu

    def loadFile(self):
        #print 'In loadFile'
        title = 'Read CLUSTAL formatted alignment file'
        types = [('CLUSTAL files', '*.aln')]
        file = tkinter.filedialog.askopenfilename(filetypes=types, title=title)
        if file:
            self.alignment.read(file)
        self.redraw()

    def writeFile(self):
        #print 'In writeFile'
        #self.remakeAlignment() # always done in mouseUp
        #self.redraw() # horribly expensive
        title = 'Save CLUSTAL formatted alignment file'
        types = [('CLUSTAL files', '*.aln')]
        file = tkinter.filedialog.asksaveasfilename(filetypes=types,
                                                    title=title)
        if file and self.alignment:
            self.alignment.write(file)

    def fillCanvas(self):
        #print 'Filling Canvas'
        yCoord = 0
        sequences = self.alignment.sequences
        seqNames = self.alignment.seqNames
        seqCount = 0
        self.gapnum = gapnum = 0
        for seqName in seqNames:
            seqCount = seqCount + 1
            seqTag = seqName
            sequence = sequences[seqName].sequence
            numbers = sequences[seqName].gappednumbers
            resTag = 'name'
            #print seqTag,resTag
            uniqTag = seqTag + '_' + resTag
            self.canvas.create_text(0,
                                    yCoord,
                                    text=seqName,
                                    tags=(resTag, seqTag, uniqTag))
            for xCoord in range(len(self.alignment)):
                resName = sequence[xCoord]
                resTag = numbers[xCoord]
                #need unique tags for gaps too
                if resTag == '':
                    resTag = 'gap' + str(gapnum)
                    gapnum = gapnum + 1
                uniqtag = seqTag + '_' + resTag
                try:
                    fillColor = self.colors[resName]
                except:
                    fillColor = self.colors['default']
                self.canvas.create_text(100 + xCoord * self.xspace,
                                        yCoord,
                                        text=resName,
                                        fill=fillColor,
                                        tags=(resTag, seqTag, uniqtag,
                                              'movable'))
            yCoord = yCoord + self.yspace
        self.canvas.resizescrollregion()
        print('updating colors')
        self.updateColor(self.selectionTags, 'yellow')
        self.updateSpecialColor()
        print('Done')

    def updateSpecialColor(self):
        if self.colors['special'] == {}:
            return
        for tag in list(self.colors['special'].keys()):
            item = self.canvas.find_withtag(tag)[0]
            self.canvas.itemconfig(item, fill=self.colors['special'][tag])

    def updateColor(self, tags, color):
        #print 'In updateColor'
        if not tags:
            return
        #print tags
        for tag in tags:
            item = self.canvas.find_withtag(tag[2])
            self.canvas.itemconfig(item, fill=color)

    def exit(self, event=None):
        if self.Master is not None:
            self.master.withdraw()
        else:
            self.master.destroy()

    def makeEditMenu(self, event=None):
        #print 'In makeEditMenu'
        Edit_button = tkinter.Menubutton(self.mBar, text='Edit', underline=0)
        self.menuButtons['Edit'] = Edit_button
        Edit_button.pack(side=tkinter.LEFT, padx='1m')
        Edit_button.menu = tkinter.Menu(Edit_button)
        Edit_button.menu.add_command(label='Redraw',
                                     underline=0,
                                     command=self.redraw)
        Edit_button.menu.add_command(label='Clear Selection',
                                     underline=0,
                                     command=self.clearSelection)
        Edit_button.menu.add_command(label='Delete Selected Sequences',
                                     underline=0,
                                     command=self.deleteSelectedSequences)
        #Edit_button.menu.add_command(label='Delete Selected Residues', underline=0,
        #                            command = self.deleteSelectedResidues)
        Edit_button.menu.add_command(label='Trim Gaps',
                                     underline=0,
                                     command=self.trim)
        Edit_button['menu'] = Edit_button.menu

    def trim(self):
        self.alignment.trim()
        self.redraw()

    def deleteSelectedSequences(self):
        if self.selectionTags == []:
            return
        while self.selection:
            firstSeqName = self.canvas.gettags(self.selection[0])[1]
            self.deleteSequence(firstSeqName)
        self.redraw()

    def deleteSequence(self, seqName):
        self.alignment.deleteSequence(seqName)
        items = self.canvas.find_withtag(seqName)
        if len(items):
            for item in items:
                tags = self.canvas.gettags(item)
                self.canvas.delete(item)
                try:
                    del self.colors['special'][tags[2]]
                except:
                    continue
            self.selection = self.canvas.find_withtag('selected')
            self.rebuildSelectionTags()

    def deleteSelectedResidues(self):
        #print 'In deleteSelection'
        #if selection is empty get out
        if self.selectionTags == []:
            return
        #get hold of a list of selected residues
        selSeq = [x[1] for x in self.selectionTags]
        #uniquify it
        uniqSelSeq = [selSeq[0]]
        for seq in selSeq[1:]:
            if seq != uniqSelSeq[-1]:
                uniqSelSeq.append(seq)
        #for each sequence, build a new sequence minus the selected tags, and
        #update the alignment
        for seqName in uniqSelSeq:
            sequence = []
            residues = self.canvas.find_withtag(seqName)
            for residue in residues[1:]:
                tags = self.canvas.gettags(residue)
                if 'selected' not in tags:
                    sequence.append(self.canvas.itemcget(residue, 'text'))
            sequence = Sequence(name=seqName, sequence=sequence)
            index = self.alignment.seqNames.index(seqName)
            self.alignment.deleteSequence(seqName)
            self.alignment.addSequence(sequence, index)
        self.selectionTags = [
        ]  # can't have any tags if the selection is all gone...
        self.redraw()

    def redraw(self, event=None):
        self.canvas.delete('all')
        self.fillCanvas()