def OnPaint(self, evt): dc = wx.PaintDC(self) #dc.Clear() w, h = dc.GetSizeTuple() dc.SetPen(wx.RED_PEN) cr = ContextFromDC(dc) cr.scale(w, h) cr.set_source_rgb(1, 0, 1) cr.set_line_width(0.002) path = self.path.send(cr) x, y = self.data.next() #dc.DrawLines(numpy.array([x*w,y*h]).T) cairo_tools.polyline(cr, x, y) cairo_tools.stamp_at(cr, path, x, y) cr.stroke()
class TextEdit(DNApyBaseDrawingClass): ''' new Text editor that allows to display dna and complementary dna in a usefull manner Also it can and should show: - features (done, buggy) - restriction enzymes (partly) - dna modifications (todo) - primer (todo) ''' def __init__(self, parent, id): #parent self.parent = parent # get data self.dna = genbank.gb.GetDNA() self.cdna = genbank.gb.GetDNA() # settings for the editor self.sY = 60 # offset top self.dX = 8 # border left and right self.textSpacing = 80 # seperation of lines self.fontsize = 9 # line size self.lineGap = 6 # interactive cursor self.PositionPointer = 1 self.PositionPointerEnd = False # initial minimal height of the editor self.minHeight = 200 # storage dict, to prevent recalculating to much self.cairoStorage = { 'features' : None, # feature Object 'fPaths' : [], # path of alls features as List 'cursorPath': None, 'cursor' : None, 'enzymes' : {}, # enzymes object 'ePaths' : [], # enzymes drawn 'ticks' : None, # ticks object --> self.dna 'tPaths' : [] # ticks drawn } # call window DNApyBaseDrawingClass.__init__(self, parent, wx.ID_ANY) # bind events self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDouble) self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) self.Bind(wx.EVT_MOTION, self.OnMotion) # drag and drop, selection etc. self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPress) #This is important for controlling the input into the editor ####### Modify methods from base calss to fit current needs ######### def update_globalUI(self): ''' Method should be modified as to update other panels in response to changes in own panel. ''' MSG_CHANGE_TEXT = "change.text" pub.sendMessage(MSG_CHANGE_TEXT, text="DNA view says update!") def update_ownUI(self): """ This would get called if the drawing needed to change, for whatever reason. The idea here is that the drawing is based on some data generated elsewhere in the system. If that data changes, the drawing needs to be updated. This code re-draws the buffer, then calls Update, which forces a paint event. """ ##################### # remove buffered features, to force redraw: self.cairoStorage['features'] = None # The Buffer init is done here, to make sure the buffer is always # the same size as the Window wC, hC = self.parent.GetClientSize() wP, hP = self.parent.GetSize() self.SetSize(wx.Size(wC, self.minHeight)) self.parent.SetVirtualSize(wx.Size(wC-30, self.minHeight)) # Make new offscreen bitmap: this bitmap will always have the # current drawing in it, so it can be used to save the image to # a file, or whatever. # if wC != 0 and hC !=0: # self._Buffer = wx.EmptyBitmap(wC, self.minHeight) # else: # self._Buffer = wx.EmptyBitmap(wP, hP) ########################## # start dc dc = wx.MemoryDC() dc.SelectObject(self._Buffer) dc.SetBackground(wx.Brush("White")) dc.Clear() # make a cairo context self.ctx = ContextFromDC(dc) # start pango as a font backend self.pango = pangocairo.CairoContext(self.ctx) self.pango.set_antialias(cairo.ANTIALIAS_SUBPIXEL) # reload dna self.dna = genbank.gb.GetDNA() if self.dna != None: self.cdna = dna.C(self.dna) else: self.dna = '' # no dna, if there is nothing self.cdna = '' # empty cdna # render the text self.displayText() # create colorful features here self.drawFeatures() # draw enzymes self.drawEnzymes() # draw a cursor self.drawCursor() # draw Tics above all! self.drawTicks() # end of canvas handling dc.SelectObject(wx.NullBitmap) # need to get rid of the MemoryDC before Update() is called. self.Refresh() self.Update() return None def displayText(self): ''' main function for the editors text capabilities here the text gets positioned and all its color. Also the selection highlight is done here''' # get window size to wrap text width, height = self.parent.GetVirtualSize() if width > 30: w = width - self.dX else: w = width # save with for other functions self.w = w # start layout layout = self.pango.create_layout() layout.set_wrap(pango.WRAP_WORD_CHAR) layout.set_width(pango.SCALE * w) layout.set_spacing(pango.SCALE *self.textSpacing) layout.set_alignment(pango.ALIGN_LEFT) # new attr list for the dna dnaAttr = pango.AttrList() # color gray = 56 * 65535/255 fg_color = pango.AttrForeground(gray,gray,gray, 0, len(self.dna)) spacing = pango.AttrLetterSpacing(1500, 0, len(self.dna)) # add attr dnaAttr.insert(fg_color) dnaAttr.insert(spacing) # set attr layout.set_attributes(dnaAttr) #set font font = pango.FontDescription("monospace normal "+ "1") font.set_size(pango.SCALE * self.fontsize) layout.set_font_description(font) # font and color are prepared for the DNA # get selection, if any start, end, zero = self.get_selection() if end != -1: # correct direktion of selection if start > end: s = end - 1 e = start else: s = start - 1 e = end # make markup selection if abs(end-start) >= 0 and zero == -1 and end != -1 : senseText = self.dna[0:s] + '<span background="#0082ED">'+self.dna[s:e] + '</span>' + self.dna[e:] antiText = self.cdna[0:s] + '<span background="#0082ED">'+self.cdna[s:e] + '</span>' + self.cdna[e:] elif abs(end-start) >= 0 and zero == 1 and end != -1: senseText = '<span background="#0082ED">'+self.dna[0:s] + '</span>' + self.dna[s:e] + '<span background="#0082ED">'+self.dna[e:] + '</span>' antiText = '<span background="#0082ED">'+self.cdna[0:s] + '</span>' + self.cdna[s:e] + '<span background="#0082ED">'+self.cdna[e:] + '</span>' else: senseText = self.dna antiText = self.cdna # output dna self.ctx.move_to(self.dX,self.sY) layout.set_markup(senseText) self.pango.update_layout(layout) self.pango.show_layout(layout) # copy sense layout self.senseLayout = layout.copy() # determine the length and height of a line self.lineLength = layout.get_line(0).length x1, y1, w1, h1 = self.senseLayout.index_to_pos(1) x2, y2, w2, h2 = self.senseLayout.index_to_pos(1+self.lineLength) self.lineHeight = abs(y1 - y2) / pango.SCALE # print anti sense strain self.ctx.move_to(self.dX,self.sY + self.fontsize + self.lineGap) layout.set_markup(antiText) self.pango.update_layout(layout) self.pango.show_layout(layout) # update the height of the context, based on the inserted dna w, h = layout.get_pixel_size() self.minHeight = h + 2*self.sY # resize window # w,h = self.GetSize() # prevents missfomration on resize # self.SetSize(wx.Size(w,self.minHeight)) # prevents missfomration on resize return None def drawFeatures(self): features = genbank.gb.get_all_feature_positions() paths = {} # (color, path) featureheight = 10 if self.cairoStorage['features'] != features : # something changed, we have to draw the paths new self.drawn_fw_locations = [] self.drawn_rv_locations = [] # font settings # draw the name somewhere at the start nameheight = 9 # px self.ctx.select_font_face('Arial', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) self.ctx.set_font_size(nameheight) for feature in features: featuretype, complement, start, finish, name, index = feature if featuretype != "source": # get names hname = self.hitName(name, index) name = self.nameBeautiful(name) #get position xs, ys, ws, hs = self.senseLayout.index_to_pos(start) xf, yf, wf, hf = self.senseLayout.index_to_pos(finish+1) # linecount: # if ys and yf are not the same, we have feature over multiple lines! dy = abs(ys - yf) / pango.SCALE ySteps = dy / self.lineHeight # number of lines! xs = xs / pango.SCALE xf = xf / pango.SCALE ys = (ys / pango.SCALE) + self.sY yf = yf / pango.SCALE # first sense strain over the text if complement == False: self.drawn_fw_locations, l = self.find_overlap(self.drawn_fw_locations, (start, finish)) level = l * (featureheight+6) xbearing, ybearing, TextWidth, TextHeight, xadvance, yadvance = self.ctx.text_extents(name) ys = ys - level - 5 self.ctx.move_to(xs + 10, ys ) self.ctx.text_path(name) textpath = self.ctx.copy_path() self.ctx.new_path() # end name # box for name self.ctx.move_to(xs, ys) self.ctx.rectangle(xs, ys-featureheight, TextWidth + 20 , nameheight + 5) # lines above the dna while ySteps >= 0: if ySteps == 0: xE = xf else: xE = self.w + 5 # TODO: figure out, why we need to add 5 px, to make it look correct # make a box-path self.ctx.move_to(xs, ys) self.ctx.rectangle(xs, ys-5, xE-xs , 5) # the second x start is always 8px, or what we will opt for xs = self.dX ys = ys + self.lineHeight ySteps = ySteps - 1 # path fillpath = self.ctx.copy_path() self.ctx.new_path() # get color color = eval(featuretype)['fw'] #get the color of feature (as string) else: self.drawn_rv_locations, l = self.find_overlap(self.drawn_rv_locations, (start, finish)) level = l * (featureheight+6) # set y ys = ys + 2*self.fontsize + self.lineGap + 9 + level # lines below the dna while ySteps >= 0: if ySteps == 0: xE = xf else: xE = self.w # make the path #self.ctx.move_to(xs, ys) #self.ctx.line_to(xE, ys) self.ctx.move_to(xs, ys) self.ctx.rectangle(xs, ys+5, xE-xs , 5) xs = self.dX ys = ys + self.lineHeight ySteps = ySteps - 1 # change y: ys = ys - self.lineHeight # buffer path bufferPath = self.ctx.copy_path() self.ctx.new_path() # name at the end: xbearing, ybearing, TextWidth, TextHeight, xadvance, yadvance = self.ctx.text_extents(name) self.ctx.move_to(xf - TextWidth - 10, ys + 10 ) self.ctx.text_path(name) textpath = self.ctx.copy_path() self.ctx.new_path() # end name # box for name self.ctx.append_path(bufferPath) self.ctx.move_to(xf - TextWidth, ys + featureheight) self.ctx.rectangle(xf - TextWidth - 20, ys, TextWidth + 20 , nameheight + 5) # get path fillpath = self.ctx.copy_path() self.ctx.new_path() # get color color = eval(featuretype)['fw'] #get the color of feature (as string) paths[hname] = (color,fillpath ,textpath) self.ctx.new_path() # clear canvas # save the paths self.cairoStorage['fPaths'] = paths # update storage status self.cairoStorage['features'] = features # draw the beautiful features for a in self.cairoStorage['fPaths']: color, path, tpath = self.cairoStorage['fPaths'][a] # get color assert type(color) == str r,g,b = colcol.hex_to_rgb(color) r = float(r)/255 g = float(g)/255 b = float(b)/255 # append and draw path self.ctx.append_path(path) self.ctx.set_line_width(1) self.ctx.set_source_rgba(r ,g ,b , 1) # Solid color #self.ctx.stroke_preserve() self.ctx.fill() # write text self.ctx.append_path(tpath) self.ctx.set_line_width(1) self.ctx.set_source_rgba(1 ,1 ,1 , 1) # Solid color self.ctx.fill() return None def drawEnzymes(self): # load enzymes enzymes = genbank.gb.restrictionEnzymes.selection # create hash of selection and DNA hashable = "%s%s" % (frozenset(enzymes), genbank.gb.gbfile['dna']) e1 = hashlib.md5(hashable).hexdigest() if e1 != self.cairoStorage['enzymes']: nameheight = 9 # px self.ctx.select_font_face('Arial', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) self.ctx.set_font_size(nameheight) dna = self.dna length = len(dna) index = 0 # same as in plasmid_GUI.py enzymesPaths = {} # loop enzymes ordered dict for e in enzymes: for site in enzymes[e].restrictionSites: name, start, end, cut51, cut52, dnaMatch = site startRef = start - 1 hitname = self.hitName(name, index) # get position to draw xs, ys, ws, hs = self.senseLayout.index_to_pos(startRef) xs = xs / pango.SCALE + self.dX ys = ys / pango.SCALE + self.sY hs = hs / pango.SCALE # move there, but above the line y = ys# - hs self.ctx.move_to(xs,y) # print text as path self.ctx.text_path(name) textpath = self.ctx.copy_path() self.ctx.new_path() # make a line, representing the cut xC51, yC51, wC51, hC51 = self.senseLayout.index_to_pos(startRef + enzymes[e].c51) xC31, yC31, wC31, hC31 = self.senseLayout.index_to_pos(startRef + enzymes[e].c31) xC51 = xC51 / pango.SCALE + self.dX yC51 = yC51 / pango.SCALE + self.sY tHeight = hC51 / pango.SCALE xC31 = xC31 / pango.SCALE + self.dX yC31 = yC31 / pango.SCALE + self.sY if yC51 == yC31: self.ctx.move_to(xC51, yC51) self.ctx.line_to(xC51, yC51 + tHeight + 0.25 * self.lineGap) self.ctx.line_to(xC31, yC31 + tHeight + 0.25 * self.lineGap) self.ctx.line_to(xC31, yC31 + 2 * tHeight + 0.5* self.lineGap) else: if yC51 <= yC31: lineend = self.dX + self.w linestart = self.dX else: # switch linestart = self.dX + self.w lineend = self.dX # split the drawing in two parts self.ctx.move_to(xC51, yC51) self.ctx.line_to(xC51, yC51 + tHeight + 0.25 * self.lineGap) self.ctx.line_to(lineend, yC51 + tHeight + 0.25 * self.lineGap) # part 2 self.ctx.move_to(linestart, yC31 + tHeight + 0.25 * self.lineGap) self.ctx.line_to(xC31, yC31 + tHeight + 0.25 * self.lineGap) self.ctx.line_to(xC31, yC31 + tHeight + 0.25 * self.lineGap) self.ctx.line_to(xC31, yC31 + 2 * tHeight + 0.5* self.lineGap) linepath = self.ctx.copy_path() self.ctx.new_path() enzymesPaths[hitname] = (textpath, linepath) # raise index index = index + 1 # update storage self.cairoStorage['enzymes'] = e1 self.cairoStorage['ePaths'] = enzymesPaths # draw enzymes: self.ctx.set_line_width(1) self.ctx.set_source_rgba(0.6,0.3,0.3 , 1) # Solid color for e in self.cairoStorage['ePaths']: path, pathc = self.cairoStorage['ePaths'][e] self.ctx.append_path(path) self.ctx.fill() self.ctx.append_path(pathc) self.ctx.stroke() def drawTicks(self): ''' draw the nice infos about the dna position in each line''' nameheight = 9 # px self.ctx.set_font_size(nameheight) self.ctx.select_font_face('Arial', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) # number of lines: if len(self.dna) != 0 and self.cairoStorage['ticks'] != self.dna: # count the lines: nLines = math.ceil((len(self.dna)-1) / self.lineLength) # empty paths: self.cairoStorage['tPaths'] = [] n = 0 # counter y = self.sY -2 #- self.fontsize - 2 # clear paths self.ctx.new_path() while n <= nLines: self.ctx.move_to(self.dX + 3, y) # make the tick text = "%d" % (n * self.lineLength + 1) xbearing, ybearing, TextWidth, TextHeight, xadvance, yadvance = self.ctx.text_extents(text) self.ctx.text_path(text) textpath = self.ctx.copy_path() self.ctx.new_path() # make a box boxPath = self.carioRoundBox(self.dX,y - TextHeight - 2, TextWidth + 6, TextHeight + 4, 0.6) self.ctx.new_path() #save paths self.cairoStorage['tPaths'].append((textpath, boxPath)) # raise counter y = y + self.lineHeight n = n + 1 # set storage: self.cairoStorage['ticks'] = self.dna # print the ticks: self.ctx.set_line_width(0) for i in self.cairoStorage['tPaths']: textpath, boxPath = i # draw box self.ctx.set_source_rgba(1,1,1, 0.6) # white, transparent self.ctx.append_path(boxPath) self.ctx.fill() # draw position self.ctx.set_source_rgba(0,0,0,1) # dark black self.ctx.append_path(textpath) self.ctx.fill() return None def carioRoundBox(self, x, y, width=10, height=10, aspect=0.8): ''' draw a box with rounded corners ''' # from http://cairographics.org/samples/rounded_rectangle/ corner_radius = height / 10.0 radius = corner_radius / aspect degrees = math.pi / 180.0 self.ctx.new_sub_path() self.ctx.arc( x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees) self.ctx.arc( x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees) self.ctx.arc(x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees) self.ctx.arc(x + radius, y + radius, radius, 180 * degrees, 270 * degrees) self.ctx.close_path() path = self.ctx.copy_path() self.ctx.new_path() return path def drawCursor(self): ''' draw and display cursor in the textfield''' indexO = self.cairoStorage['cursor'] index = self.PositionPointer pos1, pos2, zero = self.get_selection() if indexO != index and pos2 == -1: index = index - 1 # we save the genetic curors 1-len(), but mean the cursor 0-len()-1 # turn index to x,y x, y, w, h = self.senseLayout.index_to_pos(index) x = x / pango.SCALE + self.dX y = (y / pango.SCALE) + self.sY y1 = y y2 = y + 0.5 * self.lineHeight # move cursor if its the line end: if self.PositionPointerEnd == True: x = x + self.dX self.ctx.move_to(x,y1) self.ctx.line_to(x,y2) self.cairoStorage['cursorPath'] = self.ctx.copy_path() self.ctx.new_path() elif pos2 != -1: # there is a selection, so no cursor: self.cairoStorage['cursorPath'] = None if self.cairoStorage['cursorPath'] != None: # draw cursor: self.ctx.append_path(self.cairoStorage['cursorPath']) self.ctx.set_line_width(0.3) self.ctx.set_source_rgba(0,0,0, 1) # Solid color self.ctx.stroke() ###################################################### def OnLeftDown(self, event): # remove selection: self.set_dna_selection() # get position x, y = self.ScreenToClient(wx.GetMousePosition()) xp = (x - self.dX) * pango.SCALE yp = int(y - self.sY - self.fontsize) * pango.SCALE self.LeftDownIndex = None index = self.senseLayout.xy_to_index(xp,yp) if index != False: # click can be left or right of the center of the char if index[1] == 0: self.LeftDownIndex = index[0] + 1 else: self.LeftDownIndex = index[0] + 2 # update cursor self.PositionPointer = self.LeftDownIndex self.set_cursor_position() event.Skip() #very important to make the event propagate and fulfill its original function def OnLeftUp(self, event): # get selection pos1, pos2, zero = self.get_selection() if pos2 == -1: # reset selection #self.set_dna_selection() # maybe we cliked on a feature, label or a line? hit = self.HitTest() if hit != None: self.hit = hit # we hit one, so we need to draw the selection! # wich feature was hit? featurelist = genbank.gb.get_all_feature_positions() for i in range(0,len(featurelist)): # load all the feature infos featuretype, complement, start, finish, name, index = featurelist[i] hName = self.hitName(name, index) if hit == hName: # a feature with start smaller then finish, is a featur laying over zero if start > finish: zero = 1 # 1 --> feature starts left and ends right of +1 # set selection self.set_dna_selection((start,finish, zero)) self.set_cursor_position(start) else: self.hit = None # update the UI maybe? self.update_ownUI() self.update_globalUI() event.Skip() #very important to make the event propagate and fulfill its original function def OnLeftDouble(self, event): '''doubleclick aktion''' # remove selection: self.set_dna_selection() # if we hit a feature, wen can open a dialog hit = self.HitTest() if hit != None: # wich feature was hit? featurelist = genbank.gb.get_all_feature_positions() for i in range(0,len(featurelist)): # load all the feature infos featuretype, complement, start, finish, name, index = featurelist[i] hName = self.hitName(name, index) if hit == hName: genbank.feature_selection = copy.copy(index) dlg = featureedit_GUI.FeatureEditDialog(None, 'Edit Feature') # creation of a dialog with a title''' dlg.ShowModal() dlg.Center() # update the UI maybe? self.update_ownUI() self.update_globalUI() def OnMotion(self, event): if event.Dragging() and event.LeftIsDown(): oldSel = genbank.dna_selection # get position x, y = self.ScreenToClient(wx.GetMousePosition()) xp = (x - self.dX) * pango.SCALE yp = int(y - self.sY - self.fontsize) * pango.SCALE self.LeftMotionIndex = None index = self.senseLayout.xy_to_index(xp,yp) # get motion cursor if index != False: if index[1] == 0: self.LeftMotionIndex = index[0] + 1 else: self.LeftMotionIndex = index[0] + 2 # update cursor if self.LeftMotionIndex >= self.LeftDownIndex: self.PositionPointer = self.LeftMotionIndex + 1 else: self.PositionPointer = self.LeftMotionIndex # end of line excepions if (self.PositionPointer - 1) % self.lineLength == 0 and self.PositionPointer >= self.lineLength : self.PositionPointerEnd = True self.PositionPointer = self.PositionPointer -1 else: self.PositionPointerEnd = False # get selection if self.LeftDownIndex <= self.LeftMotionIndex: selection = (self.LeftDownIndex, self.LeftMotionIndex, -1) else: selection = (self.LeftMotionIndex , self.LeftDownIndex , -1) if oldSel != selection: self.set_dna_selection(selection) #update the varable keeping track of DNA selection self.update_ownUI() self.update_globalUI() event.Skip() #very important to make the event propagate and fulfill its original function # def OnRightUp(self, event): # event.Skip() #very important to make the event propagate and fulfill its original function #### rendering #################################################################### def find_overlap(self, drawn_locations, new_range): ''' Takes two ranges and determines whether the new range has overlaps with the old one. If there are overlaps the overlap locations are returned. This is used when drawing features. If two features overlap I want them drawn on different levels. ''' assert type(drawn_locations) == list assert type(new_range) == tuple if drawn_locations == []: drawn_locations.append([new_range]) return drawn_locations, 0 else: i = 0 while i < len(drawn_locations): overlap_found = False for n in range(0,len(drawn_locations[i])): if drawn_locations[i][n][0]<=new_range[0]<=drawn_locations[i][n][1] or drawn_locations[i][n][0]<=new_range[1]<=drawn_locations[i][n][1]: #if they overlap overlap_found = True elif new_range[0]<=drawn_locations[i][n][0]<=new_range[1] or new_range[0]<=drawn_locations[i][n][1]<=new_range[1]: #if they overlap overlap_found = True if overlap_found == False: drawn_locations[i].append(new_range) return drawn_locations, i break elif i+1==len(drawn_locations): drawn_locations.append([new_range]) return drawn_locations, i+1 break i += 1 def hitName(self, name, index): # unique and reproducible id for each feature name = self.nameBeautiful(name) seq = "%s%s" % (name, index) name = ''.join(seq.split()) return name def HitTest(self): '''Tests whether the mouse is over any feature or a enzyme''' hit = None # get the mouse positions x, y = self.ScreenToClient(wx.GetMousePosition()) x2, y2 = self.ctx.device_to_user(x,y) # list of all the paths: loop = [self.cairoStorage['fPaths'], # features self.cairoStorage['ePaths']] # enzymes # loop over all possible paths to find the one we have under the mouse for paths in loop: for i in paths: path = paths[i] if type(path) == list or type(path) == tuple: path = path[1] # load the path self.ctx.append_path(path) self.ctx.set_line_width(0.1) # reduce linewith, so we can have nice clicking detection inFill = self.ctx.in_fill(x2,y2) inStroke = self.ctx.in_stroke(x2,y2) # check if this path is hit if inFill == True or inStroke == True: hit = i self.ctx.new_path() return hit def nameBeautiful(self, name): # remove any '"' in front or at the end if name[0:1] == '"': name = name[1:] if name[-1:] == '"': name = name[:-1] return name def set_cursor_position(self, pointer=False): if pointer == False: pointer = self.PositionPointer else: self.PositionPointer = pointer # set position of cursor for status bar: genbank.cursor_position = pointer def set_dna_selection(self, selection = (1,-1, -1)): # standart selection ''' Updates DNA selection. ''' ##selection = self.get_selection() a, b, none = selection # new to enable selections over 0 # if start after end, swap them if a > b and b != -1: a,b = b,a selection = (a, b , -1) genbank.dna_selection = selection #self.update_globalUI() #self.update_ownUI() def moveCursor(self, offset, linejump = False): ### handle cursor movement: # offset +-int # linejump False/up/down index = self.PositionPointer topology = genbank.gb.gbfile['locus']['topology'] length = len(genbank.gb.GetDNA()) if linejump == False: # check if we moved to a Line end # if so, we first do not increase the counter: if self.PositionPointerEnd == False and index % self.lineLength == 0: self.PositionPointerEnd = True elif self.PositionPointerEnd == True and index % self.lineLength == 0: self.PositionPointerEnd = False index = index + offset else: index = index + offset elif linejump == "up": # jump to the last row, but keep index to the left: #TODO, not correctly behaving if jumping between start and end index = index - self.lineLength elif linejump == "down": index = index + self.lineLength if topology == 'circular': if index < 1: index = index + length elif index > length: index = index - length else: if index < 1: index = 1 elif index > length - 1: index = length - 1 self.PositionPointer = index self.set_cursor_position() def OnKeyPress(self, evt): key = evt.GetUniChar() index = self.PositionPointer shift = evt.ShiftDown() # is shift down? if key in [97, 65, 116, 84, 99, 67, 103, 71]: # [a, A, t, T, c, C, g, G'] start, finish, null = self.get_selection() if finish != -1: # if a selection, delete it, then paste genbank.gb.Delete(start+1, finish, visible=False) self.PositionPointer = start if shift == True: genbank.gb.Paste(index, chr(key).upper()) elif shift == False: genbank.gb.Paste(index, chr(key).lower()) # move pointer self.PositionPointer = self.PositionPointer + 1 if finish != -1: self.set_dna_selection() self.update_ownUI() self.update_globalUI() elif key == 8: #backspace start, finish, null = self.get_selection() if finish != -1: # if a selection, delete it genbank.gb.Delete(start+1, finish) self.set_dna_selection() # remove selection self.PositionPointer = start self.update_ownUI() self.update_globalUI() else: genbank.gb.Delete(index-1, index-1) self.PositionPointer = self.PositionPointer - 1 # set pointer back self.update_ownUI() self.update_globalUI() #self.stc.SetSelection(start-1, start-1) elif key == 127: #delete start, finish, null = self.get_selection() if finish != -1: # if a selection, delete it genbank.gb.Delete(start, finish) self.PositionPointer = start else: genbank.gb.Delete(index, index) self.set_dna_selection() # remove selection self.update_ownUI() self.update_globalUI() elif key == 314 and shift == False: #left self.moveCursor(-1) self.set_dna_selection() # remove selection self.update_ownUI() self.update_globalUI() elif key == 314 and shift == True: #left + select start, finish, null = self.get_selection() # start a selection if finish == -1: start = self.PositionPointer - 1 finish = self.PositionPointer if finish == self.PositionPointer: self.moveCursor(-1) finish = self.PositionPointer else: self.moveCursor(-1) start = self.PositionPointer self.set_dna_selection((start, finish, null)) # remove selection self.update_ownUI() self.update_globalUI() elif key == 316 and shift == False: #right self.moveCursor(1)#self.PositionPointer = self.PositionPointer + 1 self.set_dna_selection() # remove selection self.update_ownUI() self.update_globalUI() elif key == 316 and shift == True: #right + select start, finish, null = self.get_selection() # start a selection if finish == -1: start = self.PositionPointer finish = self.PositionPointer if finish == self.PositionPointer: self.moveCursor(1) finish = self.PositionPointer else: self.moveCursor(1) start = self.PositionPointer self.set_dna_selection((start, finish, null)) # remove selection self.update_ownUI() self.update_globalUI() elif key == 315 and shift == False: #up self.moveCursor(1, "up") self.update_ownUI() self.update_globalUI() elif key == 315 and shift == True: #up + select print("select with keyboard") elif key == 317 and shift == False: #down self.moveCursor(1, "down") self.update_ownUI() self.update_globalUI() elif key == 317 and shift == True: #down + select print("select with keyboard") #self.set_dna_selection() #update the varable keeping track of DNA selection self.set_cursor_position() # udpate cursor position, just in case ########################## #def make_outputpopup(self): # '''Creates a popup window in which output can be printed''' # self.outputframe = wx.Frame(None, title="Output Panel") # creation of a Frame with a title # self.output = output.create(self.outputframe, style=wx.VSCROLL|wx.HSCROLL) # creation of a richtextctrl in the frame ######################################### # def dna_output(self, featurelist): # '''Prints output to the output panel''' # self.make_outputpopup() # tabtext = str(self.stc.GetPageText(self.stc.GetSelection())) # DNA = featurelist[0] # self.output.write('%s | DNA in clipboard, %d bp' % (tabtext, len(DNA))+'\n', 'File') # self.output.write(DNA+'\n', 'DNA') # if len(featurelist) > 1: # self.output.write('With features: ', 'Text') # for i in range(1, len(featurelist)): # feature = featurelist[i] # self.output.write(('>%s "%s", ' % (feature['key'], feature['qualifiers'][0].split('=')[1])), 'Text') # self.output.write('\n', 'Text') # self.outputframe.Show() ################ genbank methods ############### def select_all(self): '''Select the entire dna sequence''' start = 1 finish = len(genbank.gb.GetDNA())+1 # TODO figure the coorect way to select. Write it in some dokumentation self.set_dna_selection((start, finish, -1)) #self.set_dna_selection() self.update_ownUI() self.update_globalUI() def get_selection(self): '''Gets the text editor selection and adjusts it to DNA locations.''' '''start, finish = self.stc.GetSelection() if start == finish: #not a selection finish = 0 elif start > finish: #the selection was made backwards start, finish = finish, start selection = (start+1, finish)''' return genbank.dna_selection def uppercase(self): '''Change selection to uppercase''' start, finish, zero = self.get_selection() if finish == -1: raise ValueError('Cannot modify an empty selection') else: genbank.gb.Upper(start, finish) self.update_ownUI() self.set_dna_selection() def lowercase(self): '''Change selection to lowercase''' start, finish, zero = self.get_selection() if finish == -1: raise ValueError('Cannot modify an empty selection') else: genbank.gb.Lower(start, finish) self.update_ownUI() self.set_dna_selection() def reverse_complement_selection(self): '''Reverse-complement current selection''' start, finish, zero = self.get_selection() if finish == -1: raise ValueError('Cannot modify an empty selection') else: genbank.gb.RCselection(start, finish) self.update_ownUI() self.update_globalUI() self.set_dna_selection() def delete(self): '''Deletes a selection and updates dna and features''' start, finish, zero = self.get_selection() if finish == -1: raise ValueError('Cannot delete an empty selection') else: genbank.gb.Delete(start, finish) self.update_ownUI() self.update_globalUI() self.set_dna_selection() def cut(self): '''Cut DNA and store it in clipboard together with any features present on that DNA''' start, finish, zero = self.get_selection() if finish == -1: raise ValueError('Cannot cut an empty selection') else: genbank.gb.Cut(start, finish) self.set_cursor_position(start) self.update_ownUI() self.update_globalUI() self.set_dna_selection() def cut_reverse_complement(self): '''Cut reverse complement of DNA and store it in clipboard together with any features present on that DNA''' start, finish, zero = self.get_selection() if finish == -1: raise ValueError('Cannot cut an empty selection') else: genbank.gb.CutRC(start, finish) self.update_ownUI() self.update_globalUI() self.set_dna_selection() def paste(self): '''Paste DNA and any features present on that DNA''' start, finish, zero = self.get_selection() if finish == -1: pass else: #If a selection, remove sequence, reposition pointer genbank.gb.Delete(start, finish, visible=False) self.set_cursor_position(start) genbank.gb.RichPaste(genbank.cursor_position) self.update_ownUI() self.update_globalUI() self.set_dna_selection() # reset selection def paste_reverse_complement(self): '''Paste reverse complement of DNA and any features present on that DNA''' start, finish, zero = self.get_selection() if finish == -1: pass else: #If a selection, remove sequence, reposition pointer genbank.gb.Delete(start, finish, visible=False) self.set_cursor_position(start) genbank.gb.PasteRC(genbank.cursor_position) self.update_ownUI() self.update_globalUI() self.set_dna_selection() # reset selection def copy(self): '''Copy DNA and features into clipboard''' start, finish, zero = self.get_selection() if finish == -1: raise ValueError('Cannot copy an empty selection') else: #genbank.gb.Copy(start, finish) # try the new copy: genbank.gb.RichCopy(start, finish) def copy_reverse_complement(self): '''Copy reverse complement of DNA''' start, finish, zero = self.get_selection() if finish == -1: raise ValueError('Cannot copy an empty selection') else: genbank.gb.CopyRC(start, finish) ####################################################### ####### Protein functions ####### def translate_output(self, protein, DNA, info): '''Generate output in the output.panel''' # tabtext = str(self.stc.GetPageText(self.stc.GetSelection())) self.make_outputpopup() self.output.write('Translate %s\n' % (info), 'File') self.output.write(('%d AA from %d bases, %d bases left untranslated' % (len(protein), len(DNA), len(DNA)%3))+'\n', 'Text') self.output.write(protein, 'Protein') self.outputframe.Show() def translate_selection(self): '''Translate selected DNA''' start, finish = self.get_selection() if finish == -1: raise ValueError('Cannot translate an empty selection') else: DNA = genbank.gb.GetDNA(start, finish) protein = dna.Translate(DNA) self.translate_output(protein, DNA, 'leading strand') def translate_selection_reverse_complement(self): '''Translate reverse-complement of selected DNA''' start, finish = self.get_selection() if finish == -1: raise ValueError('Cannot translate an empty selection') else: DNA = genbank.gb.GetDNA(start, finish) protein = dna.Translate(dna.RC(DNA)) self.translate_output(protein, DNA, 'complement strand') #update this one... def translate_feature(self): '''Translate specified feature''' feature = genbank.gb.allgbfeatures[2] DNA = genbank.gb.getdnaforgbfeature(feature[4]) protein = dna.Translate(DNA) self.translate_output(protein, DNA, 'feature "%s"' % feature[4][7:]) ################ other functions ############################### def OnCloseWindow(self, e): self.close_all("") foo=self.GetSize() ###except for the window size of file if(self.IsMaximized()==0): file=open(files['size'], "w") file.write(str(foo[0])+"\n"+str(foo[1])) file.close() self.Destroy() self.update_ownUI() #refresh everything def mouse_position(self, event): '''Get which features are at a given position''' xposition, yposition = self.stc.ScreenToClient(wx.GetMousePosition()) if xposition > 1 and yposition > 1: mposition = self.stc.CharPositionFromPoint(xposition, yposition) # #which feature corresponds to this pos? Feature = genbank.gb.get_featurename_for_pos(mposition) return mposition, Feature else: return None, None
class TextEdit(DNApyBaseDrawingClass): ''' new Text editor that allows to display dna and complementary dna in a usefull manner Also it can and should show: - features (done, buggy) - restriction enzymes (partly) - dna modifications (todo) - primer (todo) ''' def __init__(self, parent, id): #parent self.parent = parent # get data self.dna = genbank.gb.GetDNA() self.cdna = genbank.gb.GetDNA() # settings for the editor self.sY = 60 # offset top self.dX = 8 # border left and right self.textSpacing = 80 # seperation of lines self.fontsize = 9 # line size self.lineGap = 6 # interactive cursor self.PositionPointer = 1 self.PositionPointerEnd = False # initial minimal height of the editor self.minHeight = 200 # storage dict, to prevent recalculating to much self.cairoStorage = { 'features': None, # feature Object 'fPaths': [], # path of alls features as List 'cursorPath': None, 'cursor': None, 'enzymes': {}, # enzymes object 'ePaths': [], # enzymes drawn 'ticks': None, # ticks object --> self.dna 'tPaths': [] # ticks drawn } # call window DNApyBaseDrawingClass.__init__(self, parent, wx.ID_ANY) # bind events self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDouble) self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) self.Bind(wx.EVT_MOTION, self.OnMotion) # drag and drop, selection etc. self.Bind( wx.EVT_KEY_DOWN, self.OnKeyPress ) #This is important for controlling the input into the editor ####### Modify methods from base calss to fit current needs ######### def update_globalUI(self): ''' Method should be modified as to update other panels in response to changes in own panel. ''' MSG_CHANGE_TEXT = "change.text" pub.sendMessage(MSG_CHANGE_TEXT, text="DNA view says update!") def update_ownUI(self): """ This would get called if the drawing needed to change, for whatever reason. The idea here is that the drawing is based on some data generated elsewhere in the system. If that data changes, the drawing needs to be updated. This code re-draws the buffer, then calls Update, which forces a paint event. """ ##################### # remove buffered features, to force redraw: self.cairoStorage['features'] = None # The Buffer init is done here, to make sure the buffer is always # the same size as the Window wC, hC = self.parent.GetClientSize() wP, hP = self.parent.GetSize() self.SetSize(wx.Size(wC, self.minHeight)) self.parent.SetVirtualSize(wx.Size(wC - 30, self.minHeight)) # Make new offscreen bitmap: this bitmap will always have the # current drawing in it, so it can be used to save the image to # a file, or whatever. # if wC != 0 and hC !=0: # self._Buffer = wx.EmptyBitmap(wC, self.minHeight) # else: # self._Buffer = wx.EmptyBitmap(wP, hP) ########################## # start dc dc = wx.MemoryDC() dc.SelectObject(self._Buffer) dc.SetBackground(wx.Brush("White")) dc.Clear() # make a cairo context self.ctx = ContextFromDC(dc) # start pango as a font backend self.pango = pangocairo.CairoContext(self.ctx) self.pango.set_antialias(cairo.ANTIALIAS_SUBPIXEL) # reload dna self.dna = genbank.gb.GetDNA() if self.dna != None: self.cdna = dna.C(self.dna) else: self.dna = '' # no dna, if there is nothing self.cdna = '' # empty cdna # render the text self.displayText() # create colorful features here self.drawFeatures() # draw enzymes self.drawEnzymes() # draw a cursor self.drawCursor() # draw Tics above all! self.drawTicks() # end of canvas handling dc.SelectObject( wx.NullBitmap ) # need to get rid of the MemoryDC before Update() is called. self.Refresh() self.Update() return None def displayText(self): ''' main function for the editors text capabilities here the text gets positioned and all its color. Also the selection highlight is done here''' # get window size to wrap text width, height = self.parent.GetVirtualSize() if width > 30: w = width - self.dX else: w = width # save with for other functions self.w = w # start layout layout = self.pango.create_layout() layout.set_wrap(pango.WRAP_WORD_CHAR) layout.set_width(pango.SCALE * w) layout.set_spacing(pango.SCALE * self.textSpacing) layout.set_alignment(pango.ALIGN_LEFT) # new attr list for the dna dnaAttr = pango.AttrList() # color gray = 56 * 65535 / 255 fg_color = pango.AttrForeground(gray, gray, gray, 0, len(self.dna)) spacing = pango.AttrLetterSpacing(1500, 0, len(self.dna)) # add attr dnaAttr.insert(fg_color) dnaAttr.insert(spacing) # set attr layout.set_attributes(dnaAttr) #set font font = pango.FontDescription("monospace normal " + "1") font.set_size(pango.SCALE * self.fontsize) layout.set_font_description(font) # font and color are prepared for the DNA # get selection, if any start, end, zero = self.get_selection() if end != -1: # correct direktion of selection if start > end: s = end - 1 e = start else: s = start - 1 e = end # make markup selection if abs(end - start) >= 0 and zero == -1 and end != -1: senseText = self.dna[ 0:s] + '<span background="#0082ED">' + self.dna[ s:e] + '</span>' + self.dna[e:] antiText = self.cdna[ 0:s] + '<span background="#0082ED">' + self.cdna[ s:e] + '</span>' + self.cdna[e:] elif abs(end - start) >= 0 and zero == 1 and end != -1: senseText = '<span background="#0082ED">' + self.dna[ 0:s] + '</span>' + self.dna[ s:e] + '<span background="#0082ED">' + self.dna[ e:] + '</span>' antiText = '<span background="#0082ED">' + self.cdna[ 0:s] + '</span>' + self.cdna[ s:e] + '<span background="#0082ED">' + self.cdna[ e:] + '</span>' else: senseText = self.dna antiText = self.cdna # output dna self.ctx.move_to(self.dX, self.sY) layout.set_markup(senseText) self.pango.update_layout(layout) self.pango.show_layout(layout) # copy sense layout self.senseLayout = layout.copy() # determine the length and height of a line self.lineLength = layout.get_line(0).length x1, y1, w1, h1 = self.senseLayout.index_to_pos(1) x2, y2, w2, h2 = self.senseLayout.index_to_pos(1 + self.lineLength) self.lineHeight = abs(y1 - y2) / pango.SCALE # print anti sense strain self.ctx.move_to(self.dX, self.sY + self.fontsize + self.lineGap) layout.set_markup(antiText) self.pango.update_layout(layout) self.pango.show_layout(layout) # update the height of the context, based on the inserted dna w, h = layout.get_pixel_size() self.minHeight = h + 2 * self.sY # resize window # w,h = self.GetSize() # prevents missfomration on resize # self.SetSize(wx.Size(w,self.minHeight)) # prevents missfomration on resize return None def drawFeatures(self): features = genbank.gb.get_all_feature_positions() paths = {} # (color, path) featureheight = 10 if self.cairoStorage['features'] != features: # something changed, we have to draw the paths new self.drawn_fw_locations = [] self.drawn_rv_locations = [] # font settings # draw the name somewhere at the start nameheight = 9 # px self.ctx.select_font_face('Arial', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) self.ctx.set_font_size(nameheight) for feature in features: featuretype, complement, start, finish, name, index = feature if featuretype != "source": # get names hname = self.hitName(name, index) name = self.nameBeautiful(name) #get position xs, ys, ws, hs = self.senseLayout.index_to_pos(start) xf, yf, wf, hf = self.senseLayout.index_to_pos(finish + 1) # linecount: # if ys and yf are not the same, we have feature over multiple lines! dy = abs(ys - yf) / pango.SCALE ySteps = dy / self.lineHeight # number of lines! xs = xs / pango.SCALE xf = xf / pango.SCALE ys = (ys / pango.SCALE) + self.sY yf = yf / pango.SCALE # first sense strain over the text if complement == False: self.drawn_fw_locations, l = self.find_overlap( self.drawn_fw_locations, (start, finish)) level = l * (featureheight + 6) xbearing, ybearing, TextWidth, TextHeight, xadvance, yadvance = self.ctx.text_extents( name) ys = ys - level - 5 self.ctx.move_to(xs + 10, ys) self.ctx.text_path(name) textpath = self.ctx.copy_path() self.ctx.new_path() # end name # box for name self.ctx.move_to(xs, ys) self.ctx.rectangle(xs, ys - featureheight, TextWidth + 20, nameheight + 5) # lines above the dna while ySteps >= 0: if ySteps == 0: xE = xf else: xE = self.w + 5 # TODO: figure out, why we need to add 5 px, to make it look correct # make a box-path self.ctx.move_to(xs, ys) self.ctx.rectangle(xs, ys - 5, xE - xs, 5) # the second x start is always 8px, or what we will opt for xs = self.dX ys = ys + self.lineHeight ySteps = ySteps - 1 # path fillpath = self.ctx.copy_path() self.ctx.new_path() # get color color = eval(featuretype)[ 'fw'] #get the color of feature (as string) else: self.drawn_rv_locations, l = self.find_overlap( self.drawn_rv_locations, (start, finish)) level = l * (featureheight + 6) # set y ys = ys + 2 * self.fontsize + self.lineGap + 9 + level # lines below the dna while ySteps >= 0: if ySteps == 0: xE = xf else: xE = self.w # make the path #self.ctx.move_to(xs, ys) #self.ctx.line_to(xE, ys) self.ctx.move_to(xs, ys) self.ctx.rectangle(xs, ys + 5, xE - xs, 5) xs = self.dX ys = ys + self.lineHeight ySteps = ySteps - 1 # change y: ys = ys - self.lineHeight # buffer path bufferPath = self.ctx.copy_path() self.ctx.new_path() # name at the end: xbearing, ybearing, TextWidth, TextHeight, xadvance, yadvance = self.ctx.text_extents( name) self.ctx.move_to(xf - TextWidth - 10, ys + 10) self.ctx.text_path(name) textpath = self.ctx.copy_path() self.ctx.new_path() # end name # box for name self.ctx.append_path(bufferPath) self.ctx.move_to(xf - TextWidth, ys + featureheight) self.ctx.rectangle(xf - TextWidth - 20, ys, TextWidth + 20, nameheight + 5) # get path fillpath = self.ctx.copy_path() self.ctx.new_path() # get color color = eval(featuretype)[ 'fw'] #get the color of feature (as string) paths[hname] = (color, fillpath, textpath) self.ctx.new_path() # clear canvas # save the paths self.cairoStorage['fPaths'] = paths # update storage status self.cairoStorage['features'] = features # draw the beautiful features for a in self.cairoStorage['fPaths']: color, path, tpath = self.cairoStorage['fPaths'][a] # get color assert type(color) == str r, g, b = colcol.hex_to_rgb(color) r = float(r) / 255 g = float(g) / 255 b = float(b) / 255 # append and draw path self.ctx.append_path(path) self.ctx.set_line_width(1) self.ctx.set_source_rgba(r, g, b, 1) # Solid color #self.ctx.stroke_preserve() self.ctx.fill() # write text self.ctx.append_path(tpath) self.ctx.set_line_width(1) self.ctx.set_source_rgba(1, 1, 1, 1) # Solid color self.ctx.fill() return None def drawEnzymes(self): # load enzymes enzymes = genbank.gb.restrictionEnzymes.selection # create hash of selection and DNA hashable = "%s%s" % (frozenset(enzymes), genbank.gb.gbfile['dna']) e1 = hashlib.md5(hashable).hexdigest() if e1 != self.cairoStorage['enzymes']: nameheight = 9 # px self.ctx.select_font_face('Arial', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) self.ctx.set_font_size(nameheight) dna = self.dna length = len(dna) index = 0 # same as in plasmid_GUI.py enzymesPaths = {} # loop enzymes ordered dict for e in enzymes: for site in enzymes[e].restrictionSites: name, start, end, cut51, cut52, dnaMatch = site startRef = start - 1 hitname = self.hitName(name, index) # get position to draw xs, ys, ws, hs = self.senseLayout.index_to_pos(startRef) xs = xs / pango.SCALE + self.dX ys = ys / pango.SCALE + self.sY hs = hs / pango.SCALE # move there, but above the line y = ys # - hs self.ctx.move_to(xs, y) # print text as path self.ctx.text_path(name) textpath = self.ctx.copy_path() self.ctx.new_path() # make a line, representing the cut xC51, yC51, wC51, hC51 = self.senseLayout.index_to_pos( startRef + enzymes[e].c51) xC31, yC31, wC31, hC31 = self.senseLayout.index_to_pos( startRef + enzymes[e].c31) xC51 = xC51 / pango.SCALE + self.dX yC51 = yC51 / pango.SCALE + self.sY tHeight = hC51 / pango.SCALE xC31 = xC31 / pango.SCALE + self.dX yC31 = yC31 / pango.SCALE + self.sY if yC51 == yC31: self.ctx.move_to(xC51, yC51) self.ctx.line_to(xC51, yC51 + tHeight + 0.25 * self.lineGap) self.ctx.line_to(xC31, yC31 + tHeight + 0.25 * self.lineGap) self.ctx.line_to( xC31, yC31 + 2 * tHeight + 0.5 * self.lineGap) else: if yC51 <= yC31: lineend = self.dX + self.w linestart = self.dX else: # switch linestart = self.dX + self.w lineend = self.dX # split the drawing in two parts self.ctx.move_to(xC51, yC51) self.ctx.line_to(xC51, yC51 + tHeight + 0.25 * self.lineGap) self.ctx.line_to(lineend, yC51 + tHeight + 0.25 * self.lineGap) # part 2 self.ctx.move_to(linestart, yC31 + tHeight + 0.25 * self.lineGap) self.ctx.line_to(xC31, yC31 + tHeight + 0.25 * self.lineGap) self.ctx.line_to(xC31, yC31 + tHeight + 0.25 * self.lineGap) self.ctx.line_to( xC31, yC31 + 2 * tHeight + 0.5 * self.lineGap) linepath = self.ctx.copy_path() self.ctx.new_path() enzymesPaths[hitname] = (textpath, linepath) # raise index index = index + 1 # update storage self.cairoStorage['enzymes'] = e1 self.cairoStorage['ePaths'] = enzymesPaths # draw enzymes: self.ctx.set_line_width(1) self.ctx.set_source_rgba(0.6, 0.3, 0.3, 1) # Solid color for e in self.cairoStorage['ePaths']: path, pathc = self.cairoStorage['ePaths'][e] self.ctx.append_path(path) self.ctx.fill() self.ctx.append_path(pathc) self.ctx.stroke() def drawTicks(self): ''' draw the nice infos about the dna position in each line''' nameheight = 9 # px self.ctx.set_font_size(nameheight) self.ctx.select_font_face('Arial', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) # number of lines: if len(self.dna) != 0 and self.cairoStorage['ticks'] != self.dna: # count the lines: nLines = math.ceil((len(self.dna) - 1) / self.lineLength) # empty paths: self.cairoStorage['tPaths'] = [] n = 0 # counter y = self.sY - 2 #- self.fontsize - 2 # clear paths self.ctx.new_path() while n <= nLines: self.ctx.move_to(self.dX + 3, y) # make the tick text = "%d" % (n * self.lineLength + 1) xbearing, ybearing, TextWidth, TextHeight, xadvance, yadvance = self.ctx.text_extents( text) self.ctx.text_path(text) textpath = self.ctx.copy_path() self.ctx.new_path() # make a box boxPath = self.carioRoundBox(self.dX, y - TextHeight - 2, TextWidth + 6, TextHeight + 4, 0.6) self.ctx.new_path() #save paths self.cairoStorage['tPaths'].append((textpath, boxPath)) # raise counter y = y + self.lineHeight n = n + 1 # set storage: self.cairoStorage['ticks'] = self.dna # print the ticks: self.ctx.set_line_width(0) for i in self.cairoStorage['tPaths']: textpath, boxPath = i # draw box self.ctx.set_source_rgba(1, 1, 1, 0.6) # white, transparent self.ctx.append_path(boxPath) self.ctx.fill() # draw position self.ctx.set_source_rgba(0, 0, 0, 1) # dark black self.ctx.append_path(textpath) self.ctx.fill() return None def carioRoundBox(self, x, y, width=10, height=10, aspect=0.8): ''' draw a box with rounded corners ''' # from http://cairographics.org/samples/rounded_rectangle/ corner_radius = height / 10.0 radius = corner_radius / aspect degrees = math.pi / 180.0 self.ctx.new_sub_path() self.ctx.arc(x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees) self.ctx.arc(x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees) self.ctx.arc(x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees) self.ctx.arc(x + radius, y + radius, radius, 180 * degrees, 270 * degrees) self.ctx.close_path() path = self.ctx.copy_path() self.ctx.new_path() return path def drawCursor(self): ''' draw and display cursor in the textfield''' indexO = self.cairoStorage['cursor'] index = self.PositionPointer pos1, pos2, zero = self.get_selection() if indexO != index and pos2 == -1: index = index - 1 # we save the genetic curors 1-len(), but mean the cursor 0-len()-1 # turn index to x,y x, y, w, h = self.senseLayout.index_to_pos(index) x = x / pango.SCALE + self.dX y = (y / pango.SCALE) + self.sY y1 = y y2 = y + 0.5 * self.lineHeight # move cursor if its the line end: if self.PositionPointerEnd == True: x = x + self.dX self.ctx.move_to(x, y1) self.ctx.line_to(x, y2) self.cairoStorage['cursorPath'] = self.ctx.copy_path() self.ctx.new_path() elif pos2 != -1: # there is a selection, so no cursor: self.cairoStorage['cursorPath'] = None if self.cairoStorage['cursorPath'] != None: # draw cursor: self.ctx.append_path(self.cairoStorage['cursorPath']) self.ctx.set_line_width(0.3) self.ctx.set_source_rgba(0, 0, 0, 1) # Solid color self.ctx.stroke() ###################################################### def OnLeftDown(self, event): # remove selection: self.set_dna_selection() # get position x, y = self.ScreenToClient(wx.GetMousePosition()) xp = (x - self.dX) * pango.SCALE yp = int(y - self.sY - self.fontsize) * pango.SCALE self.LeftDownIndex = None index = self.senseLayout.xy_to_index(xp, yp) if index != False: # click can be left or right of the center of the char if index[1] == 0: self.LeftDownIndex = index[0] + 1 else: self.LeftDownIndex = index[0] + 2 # update cursor self.PositionPointer = self.LeftDownIndex self.set_cursor_position() event.Skip( ) #very important to make the event propagate and fulfill its original function def OnLeftUp(self, event): # get selection pos1, pos2, zero = self.get_selection() if pos2 == -1: # reset selection #self.set_dna_selection() # maybe we cliked on a feature, label or a line? hit = self.HitTest() if hit != None: self.hit = hit # we hit one, so we need to draw the selection! # wich feature was hit? featurelist = genbank.gb.get_all_feature_positions() for i in range(0, len(featurelist)): # load all the feature infos featuretype, complement, start, finish, name, index = featurelist[ i] hName = self.hitName(name, index) if hit == hName: # a feature with start smaller then finish, is a featur laying over zero if start > finish: zero = 1 # 1 --> feature starts left and ends right of +1 # set selection self.set_dna_selection((start, finish, zero)) self.set_cursor_position(start) else: self.hit = None # update the UI maybe? self.update_ownUI() self.update_globalUI() event.Skip( ) #very important to make the event propagate and fulfill its original function def OnLeftDouble(self, event): '''doubleclick aktion''' # remove selection: self.set_dna_selection() # if we hit a feature, wen can open a dialog hit = self.HitTest() if hit != None: # wich feature was hit? featurelist = genbank.gb.get_all_feature_positions() for i in range(0, len(featurelist)): # load all the feature infos featuretype, complement, start, finish, name, index = featurelist[ i] hName = self.hitName(name, index) if hit == hName: genbank.feature_selection = copy.copy(index) dlg = featureedit_GUI.FeatureEditDialog( None, 'Edit Feature') # creation of a dialog with a title''' dlg.ShowModal() dlg.Center() # update the UI maybe? self.update_ownUI() self.update_globalUI() def OnMotion(self, event): if event.Dragging() and event.LeftIsDown(): oldSel = genbank.dna_selection # get position x, y = self.ScreenToClient(wx.GetMousePosition()) xp = (x - self.dX) * pango.SCALE yp = int(y - self.sY - self.fontsize) * pango.SCALE self.LeftMotionIndex = None index = self.senseLayout.xy_to_index(xp, yp) # get motion cursor if index != False: if index[1] == 0: self.LeftMotionIndex = index[0] + 1 else: self.LeftMotionIndex = index[0] + 2 # update cursor if self.LeftMotionIndex >= self.LeftDownIndex: self.PositionPointer = self.LeftMotionIndex + 1 else: self.PositionPointer = self.LeftMotionIndex # end of line excepions if ( self.PositionPointer - 1 ) % self.lineLength == 0 and self.PositionPointer >= self.lineLength: self.PositionPointerEnd = True self.PositionPointer = self.PositionPointer - 1 else: self.PositionPointerEnd = False # get selection if self.LeftDownIndex <= self.LeftMotionIndex: selection = (self.LeftDownIndex, self.LeftMotionIndex, -1) else: selection = (self.LeftMotionIndex, self.LeftDownIndex, -1) if oldSel != selection: self.set_dna_selection( selection ) #update the varable keeping track of DNA selection self.update_ownUI() self.update_globalUI() event.Skip( ) #very important to make the event propagate and fulfill its original function # def OnRightUp(self, event): # event.Skip() #very important to make the event propagate and fulfill its original function #### rendering #################################################################### def find_overlap(self, drawn_locations, new_range): ''' Takes two ranges and determines whether the new range has overlaps with the old one. If there are overlaps the overlap locations are returned. This is used when drawing features. If two features overlap I want them drawn on different levels. ''' assert type(drawn_locations) == list assert type(new_range) == tuple if drawn_locations == []: drawn_locations.append([new_range]) return drawn_locations, 0 else: i = 0 while i < len(drawn_locations): overlap_found = False for n in range(0, len(drawn_locations[i])): if drawn_locations[i][n][0] <= new_range[ 0] <= drawn_locations[i][n][1] or drawn_locations[ i][n][0] <= new_range[1] <= drawn_locations[i][ n][1]: #if they overlap overlap_found = True elif new_range[0] <= drawn_locations[i][n][0] <= new_range[ 1] or new_range[0] <= drawn_locations[i][n][ 1] <= new_range[1]: #if they overlap overlap_found = True if overlap_found == False: drawn_locations[i].append(new_range) return drawn_locations, i break elif i + 1 == len(drawn_locations): drawn_locations.append([new_range]) return drawn_locations, i + 1 break i += 1 def hitName(self, name, index): # unique and reproducible id for each feature name = self.nameBeautiful(name) seq = "%s%s" % (name, index) name = ''.join(seq.split()) return name def HitTest(self): '''Tests whether the mouse is over any feature or a enzyme''' hit = None # get the mouse positions x, y = self.ScreenToClient(wx.GetMousePosition()) x2, y2 = self.ctx.device_to_user(x, y) # list of all the paths: loop = [ self.cairoStorage['fPaths'], # features self.cairoStorage['ePaths'] ] # enzymes # loop over all possible paths to find the one we have under the mouse for paths in loop: for i in paths: path = paths[i] if type(path) == list or type(path) == tuple: path = path[1] # load the path self.ctx.append_path(path) self.ctx.set_line_width( 0.1 ) # reduce linewith, so we can have nice clicking detection inFill = self.ctx.in_fill(x2, y2) inStroke = self.ctx.in_stroke(x2, y2) # check if this path is hit if inFill == True or inStroke == True: hit = i self.ctx.new_path() return hit def nameBeautiful(self, name): # remove any '"' in front or at the end if name[0:1] == '"': name = name[1:] if name[-1:] == '"': name = name[:-1] return name def set_cursor_position(self, pointer=False): if pointer == False: pointer = self.PositionPointer else: self.PositionPointer = pointer # set position of cursor for status bar: genbank.cursor_position = pointer def set_dna_selection(self, selection=(1, -1, -1)): # standart selection ''' Updates DNA selection. ''' ##selection = self.get_selection() a, b, none = selection # new to enable selections over 0 # if start after end, swap them if a > b and b != -1: a, b = b, a selection = (a, b, -1) genbank.dna_selection = selection #self.update_globalUI() #self.update_ownUI() def moveCursor(self, offset, linejump=False): ### handle cursor movement: # offset +-int # linejump False/up/down index = self.PositionPointer topology = genbank.gb.gbfile['locus']['topology'] length = len(genbank.gb.GetDNA()) if linejump == False: # check if we moved to a Line end # if so, we first do not increase the counter: if self.PositionPointerEnd == False and index % self.lineLength == 0: self.PositionPointerEnd = True elif self.PositionPointerEnd == True and index % self.lineLength == 0: self.PositionPointerEnd = False index = index + offset else: index = index + offset elif linejump == "up": # jump to the last row, but keep index to the left: #TODO, not correctly behaving if jumping between start and end index = index - self.lineLength elif linejump == "down": index = index + self.lineLength if topology == 'circular': if index < 1: index = index + length elif index > length: index = index - length else: if index < 1: index = 1 elif index > length - 1: index = length - 1 self.PositionPointer = index self.set_cursor_position() def OnKeyPress(self, evt): key = evt.GetUniChar() index = self.PositionPointer shift = evt.ShiftDown() # is shift down? if key in [97, 65, 116, 84, 99, 67, 103, 71]: # [a, A, t, T, c, C, g, G'] start, finish, null = self.get_selection() if finish != -1: # if a selection, delete it, then paste genbank.gb.Delete(start + 1, finish, visible=False) self.PositionPointer = start if shift == True: genbank.gb.Paste(index, chr(key).upper()) elif shift == False: genbank.gb.Paste(index, chr(key).lower()) # move pointer self.PositionPointer = self.PositionPointer + 1 if finish != -1: self.set_dna_selection() self.update_ownUI() self.update_globalUI() elif key == 8: #backspace start, finish, null = self.get_selection() if finish != -1: # if a selection, delete it genbank.gb.Delete(start + 1, finish) self.set_dna_selection() # remove selection self.PositionPointer = start self.update_ownUI() self.update_globalUI() else: genbank.gb.Delete(index - 1, index - 1) self.PositionPointer = self.PositionPointer - 1 # set pointer back self.update_ownUI() self.update_globalUI() #self.stc.SetSelection(start-1, start-1) elif key == 127: #delete start, finish, null = self.get_selection() if finish != -1: # if a selection, delete it genbank.gb.Delete(start, finish) self.PositionPointer = start else: genbank.gb.Delete(index, index) self.set_dna_selection() # remove selection self.update_ownUI() self.update_globalUI() elif key == 314 and shift == False: #left self.moveCursor(-1) self.set_dna_selection() # remove selection self.update_ownUI() self.update_globalUI() elif key == 314 and shift == True: #left + select start, finish, null = self.get_selection() # start a selection if finish == -1: start = self.PositionPointer - 1 finish = self.PositionPointer if finish == self.PositionPointer: self.moveCursor(-1) finish = self.PositionPointer else: self.moveCursor(-1) start = self.PositionPointer self.set_dna_selection((start, finish, null)) # remove selection self.update_ownUI() self.update_globalUI() elif key == 316 and shift == False: #right self.moveCursor( 1) #self.PositionPointer = self.PositionPointer + 1 self.set_dna_selection() # remove selection self.update_ownUI() self.update_globalUI() elif key == 316 and shift == True: #right + select start, finish, null = self.get_selection() # start a selection if finish == -1: start = self.PositionPointer finish = self.PositionPointer if finish == self.PositionPointer: self.moveCursor(1) finish = self.PositionPointer else: self.moveCursor(1) start = self.PositionPointer self.set_dna_selection((start, finish, null)) # remove selection self.update_ownUI() self.update_globalUI() elif key == 315 and shift == False: #up self.moveCursor(1, "up") self.update_ownUI() self.update_globalUI() elif key == 315 and shift == True: #up + select print("select with keyboard") elif key == 317 and shift == False: #down self.moveCursor(1, "down") self.update_ownUI() self.update_globalUI() elif key == 317 and shift == True: #down + select print("select with keyboard") #self.set_dna_selection() #update the varable keeping track of DNA selection self.set_cursor_position() # udpate cursor position, just in case ########################## #def make_outputpopup(self): # '''Creates a popup window in which output can be printed''' # self.outputframe = wx.Frame(None, title="Output Panel") # creation of a Frame with a title # self.output = output.create(self.outputframe, style=wx.VSCROLL|wx.HSCROLL) # creation of a richtextctrl in the frame ######################################### # def dna_output(self, featurelist): # '''Prints output to the output panel''' # self.make_outputpopup() # tabtext = str(self.stc.GetPageText(self.stc.GetSelection())) # DNA = featurelist[0] # self.output.write('%s | DNA in clipboard, %d bp' % (tabtext, len(DNA))+'\n', 'File') # self.output.write(DNA+'\n', 'DNA') # if len(featurelist) > 1: # self.output.write('With features: ', 'Text') # for i in range(1, len(featurelist)): # feature = featurelist[i] # self.output.write(('>%s "%s", ' % (feature['key'], feature['qualifiers'][0].split('=')[1])), 'Text') # self.output.write('\n', 'Text') # self.outputframe.Show() ################ genbank methods ############### def select_all(self): '''Select the entire dna sequence''' start = 1 finish = len( genbank.gb.GetDNA() ) + 1 # TODO figure the coorect way to select. Write it in some dokumentation self.set_dna_selection((start, finish, -1)) #self.set_dna_selection() self.update_ownUI() self.update_globalUI() def get_selection(self): '''Gets the text editor selection and adjusts it to DNA locations.''' '''start, finish = self.stc.GetSelection() if start == finish: #not a selection finish = 0 elif start > finish: #the selection was made backwards start, finish = finish, start selection = (start+1, finish)''' return genbank.dna_selection def uppercase(self): '''Change selection to uppercase''' start, finish, zero = self.get_selection() if finish == -1: raise ValueError('Cannot modify an empty selection') else: genbank.gb.Upper(start, finish) self.update_ownUI() self.set_dna_selection() def lowercase(self): '''Change selection to lowercase''' start, finish, zero = self.get_selection() if finish == -1: raise ValueError('Cannot modify an empty selection') else: genbank.gb.Lower(start, finish) self.update_ownUI() self.set_dna_selection() def reverse_complement_selection(self): '''Reverse-complement current selection''' start, finish, zero = self.get_selection() if finish == -1: raise ValueError('Cannot modify an empty selection') else: genbank.gb.RCselection(start, finish) self.update_ownUI() self.update_globalUI() self.set_dna_selection() def delete(self): '''Deletes a selection and updates dna and features''' start, finish, zero = self.get_selection() if finish == -1: raise ValueError('Cannot delete an empty selection') else: genbank.gb.Delete(start, finish) self.update_ownUI() self.update_globalUI() self.set_dna_selection() def cut(self): '''Cut DNA and store it in clipboard together with any features present on that DNA''' start, finish, zero = self.get_selection() if finish == -1: raise ValueError('Cannot cut an empty selection') else: genbank.gb.Cut(start, finish) self.set_cursor_position(start) self.update_ownUI() self.update_globalUI() self.set_dna_selection() def cut_reverse_complement(self): '''Cut reverse complement of DNA and store it in clipboard together with any features present on that DNA''' start, finish, zero = self.get_selection() if finish == -1: raise ValueError('Cannot cut an empty selection') else: genbank.gb.CutRC(start, finish) self.update_ownUI() self.update_globalUI() self.set_dna_selection() def paste(self): '''Paste DNA and any features present on that DNA''' start, finish, zero = self.get_selection() if finish == -1: pass else: #If a selection, remove sequence, reposition pointer genbank.gb.Delete(start, finish, visible=False) self.set_cursor_position(start) genbank.gb.RichPaste(genbank.cursor_position) self.update_ownUI() self.update_globalUI() self.set_dna_selection() # reset selection def paste_reverse_complement(self): '''Paste reverse complement of DNA and any features present on that DNA''' start, finish, zero = self.get_selection() if finish == -1: pass else: #If a selection, remove sequence, reposition pointer genbank.gb.Delete(start, finish, visible=False) self.set_cursor_position(start) genbank.gb.PasteRC(genbank.cursor_position) self.update_ownUI() self.update_globalUI() self.set_dna_selection() # reset selection def copy(self): '''Copy DNA and features into clipboard''' start, finish, zero = self.get_selection() if finish == -1: raise ValueError('Cannot copy an empty selection') else: #genbank.gb.Copy(start, finish) # try the new copy: genbank.gb.RichCopy(start, finish) def copy_reverse_complement(self): '''Copy reverse complement of DNA''' start, finish, zero = self.get_selection() if finish == -1: raise ValueError('Cannot copy an empty selection') else: genbank.gb.CopyRC(start, finish) ####################################################### ####### Protein functions ####### def translate_output(self, protein, DNA, info): '''Generate output in the output.panel''' # tabtext = str(self.stc.GetPageText(self.stc.GetSelection())) self.make_outputpopup() self.output.write('Translate %s\n' % (info), 'File') self.output.write(('%d AA from %d bases, %d bases left untranslated' % (len(protein), len(DNA), len(DNA) % 3)) + '\n', 'Text') self.output.write(protein, 'Protein') self.outputframe.Show() def translate_selection(self): '''Translate selected DNA''' start, finish = self.get_selection() if finish == -1: raise ValueError('Cannot translate an empty selection') else: DNA = genbank.gb.GetDNA(start, finish) protein = dna.Translate(DNA) self.translate_output(protein, DNA, 'leading strand') def translate_selection_reverse_complement(self): '''Translate reverse-complement of selected DNA''' start, finish = self.get_selection() if finish == -1: raise ValueError('Cannot translate an empty selection') else: DNA = genbank.gb.GetDNA(start, finish) protein = dna.Translate(dna.RC(DNA)) self.translate_output(protein, DNA, 'complement strand') #update this one... def translate_feature(self): '''Translate specified feature''' feature = genbank.gb.allgbfeatures[2] DNA = genbank.gb.getdnaforgbfeature(feature[4]) protein = dna.Translate(DNA) self.translate_output(protein, DNA, 'feature "%s"' % feature[4][7:]) ################ other functions ############################### def OnCloseWindow(self, e): self.close_all("") foo = self.GetSize() ###except for the window size of file if (self.IsMaximized() == 0): file = open(files['size'], "w") file.write(str(foo[0]) + "\n" + str(foo[1])) file.close() self.Destroy() self.update_ownUI() #refresh everything def mouse_position(self, event): '''Get which features are at a given position''' xposition, yposition = self.stc.ScreenToClient(wx.GetMousePosition()) if xposition > 1 and yposition > 1: mposition = self.stc.CharPositionFromPoint(xposition, yposition) # #which feature corresponds to this pos? Feature = genbank.gb.get_featurename_for_pos(mposition) return mposition, Feature else: return None, None
class drawPlasmid(DNApyBaseDrawingClass): ''' This class handle the drawing of a plasmid''' ''' it should draw: - Plasmid - Features - feature names - RestriktionSites - Names of Restriction sites - Selection - higlights It has to Calculate in this order: - The Size of the Plasmid - The angular positon of the features - If the Label of a feature can be drawn inside - the angular positions of all Restrictionsites - The arrangements of the labels - featurelabels - Restriktionsite Labels Possible Interactions: - select dna - drag and drop select - click on feature - zoom in and out - double click feature Each cycle must be something like this: 1. Check if we have to calcaulate the length of the features self.calc.fl = True if true: check if this changes anything for the other labels (do labels have to be drawn outside or inside compared to before) 2. Check if we have to calcaulate Label Positions again: self.calc.lp = true 3. If self.calc.lp and fl == False: Just redraw the previous plasmid else: recalculate the desired stuff x. Highlight and interaction''' def __init__(self, parent, id): # object to store our cacluations over time self.plasmidstore = plasmidstore() self.radius = 25 # cairo unit self.radiusI = self.radius - 0.013 * self.radius # cairo unit self.radiusO = self.radius + 0.013 * self.radius # cairo unit self.radiusLabels = self.radius + 17 # cairo unit self.arrowWidth = 1.8 # cairo unit self.labelHeight = 12 # pixel self.Highlight = None # store who to highlight self.enzymeGap = "-|-" # seperator to make unique hitname less unique #self.EnzymeMonitor = Monitor() # start the window DNApyBaseDrawingClass.__init__(self, parent, wx.ID_ANY) # bind events self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) self.Bind(wx.EVT_MOTION, self.OnMotion) self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDouble) return None def update_globalUI(self): ''' Method should be modified as to update other panels in response to changes in own panel. ''' MSG_CHANGE_TEXT = "change.text" pub.sendMessage(MSG_CHANGE_TEXT, text="Plasmid view says update!") def update_ownUI(self): """ This would get called if the drawing needed to change, for whatever reason. The idea here is that the drawing is based on some data generated elsewhere in the system. If that data changes, the drawing needs to be updated. This code re-draws the buffer, then calls Update, which forces a paint event. """ # update selection from editor self.updateSelection() self.updatePosition() # start the calculations if any. self.checkFeatureCalculations() # changed features may change labels also self.checkEnzymeCalculations() # check for restriction enzymes self.checkLabelCalculations() # changed features may change labels also # check if we even have to redraw anything: dc = wx.MemoryDC() dc.SelectObject(self._Buffer) dc.SetBackground(wx.Brush("White")) # start the DC and clear it dc.Clear() # make sure you clear the bitmap! self.ctx = ContextFromDC(dc) # load it into cairo # seletion starts here self.checkSelectionCalculations() # check and make a selection arc # glyphs #self.pango = pangocairo.CairoContext(self.ctx) #self.pango.set_antialias(cairo.ANTIALIAS_SUBPIXEL) self.draw() dc.SelectObject(wx.NullBitmap) # need to get rid of the MemoryDC before Update() is called. self.Refresh() self.Update() return None def set_dna_selection(self, selection): '''Receives requests for DNA selection and then sends it.''' assert type(selection) == tuple, 'Error, dna selection must be a tuple' selection = (int(selection[0]), int(selection[1]), int(selection[2])) genbank.dna_selection = selection self.plasmidstore.interaction["selection"] = selection self.update_globalUI() def set_cursor_position(self, position): # set position of cursor for status bar: genbank.cursor_position = position self.plasmidstore.interaction["position"] = position def updateSelection(self): ''' get selection from editor and update own store ''' selection = genbank.dna_selection assert type(selection) == tuple, 'Error, dna selection must be a tuple' selection = (int(selection[0]), int(selection[1]), int(selection[2])) self.plasmidstore.interaction["selection"] = selection def updatePosition(self): ''' get position from editor and update own store ''' position = genbank.cursor_position self.plasmidstore.interaction["position"] = position def hitName(self, name, index): # unique and reproducible id for each feature name = self.nameBeautiful(name) seq = "%s%s" % (name, index) name = ''.join(seq.split()) return name def HitTest(self): '''Tests whether the mouse is over any feature or label''' hit = None # get the mouse positions x, y = self.ScreenToClient(wx.GetMousePosition()) x2, y2 = self.ctx.device_to_user(x,y) # list of all the paths: loop = [self.plasmidstore.drawnfeatures, # features self.plasmidstore.drawnlabels, # featurelabes self.plasmidstore.labelBoxes, # featurelabels self.plasmidstore.drawnlabelsE, # enzymes self.plasmidstore.labelBoxesE] # enzymes # loop over all possible paths to find the one we have under the mouse for paths in loop: for i in paths: path = paths[i] if type(path) == list: path = path[0] # load the path self.ctx.append_path(path) self.ctx.set_line_width(0.1) # reduce linewith, so we can have nice clicking detection inFill = self.ctx.in_fill(x2,y2) inStroke = self.ctx.in_stroke(x2,y2) # check if this path is hit if inFill == True or inStroke == True: hit = i self.ctx.new_path() return hit def HitTestSelection(self): '''Check if the selection area (area where selection is possible) was hit''' # get the mouse position x, y = self.ScreenToClient(wx.GetMousePosition()) x2, y2 = self.ctx.device_to_user(x,y) self.ctx.append_path(self.plasmidstore.interaction["markerArea1"]) inFill1 = self.ctx.in_fill(x2,y2) self.ctx.stroke() self.ctx.append_path(self.plasmidstore.interaction["markerArea2"]) inFill2 = self.ctx.in_fill(x2,y2) self.ctx.stroke() # check if the path is hit for selection if inFill1 == True and inFill2 == False: return True else: return False def saveSelection(self, start=0, finish=0, zero=-1, featureHit=False): # feature to highlight or drag and drop? if featureHit != False: featurelist = genbank.gb.get_all_feature_positions() for i in range(0,len(featurelist)): # load all the feature infos featuretype, complement, start, finish, name, index = featurelist[i] hName = self.hitName(name, index) if featureHit == hName: # a feature with start smaller then finish, is a featur laying over zero if start > finish: zero = 1 # 1 --> feature starts left and ends right of +1 # set selection for real! self.set_dna_selection((start,finish, zero)) else: self.set_dna_selection((start,finish, zero)) return None def nameBeautiful(self, name): # remove any '"' in front or at the end if name[0:1] == '"': name = name[1:] if name[-1:] == '"': name = name[:-1] return name def find_overlap(self, drawn_locations, new_range): ''' Takes two ranges and determines whether the new range has overlaps with the old one. If there are overlaps the overlap locations are returned. This is used when drawing features. If two features overlap I want them drawn on different levels. ''' assert type(drawn_locations) == list assert type(new_range) == tuple if drawn_locations == []: drawn_locations.append([new_range]) return drawn_locations, 0 else: i = 0 while i < len(drawn_locations): overlap_found = False for n in range(0,len(drawn_locations[i])): if drawn_locations[i][n][0]<=new_range[0]<=drawn_locations[i][n][1] or drawn_locations[i][n][0]<=new_range[1]<=drawn_locations[i][n][1]: #if they overlap overlap_found = True elif new_range[0]<=drawn_locations[i][n][0]<=new_range[1] or new_range[0]<=drawn_locations[i][n][1]<=new_range[1]: #if they overlap overlap_found = True if overlap_found == False: drawn_locations[i].append(new_range) return drawn_locations, i break elif i+1==len(drawn_locations): drawn_locations.append([new_range]) return drawn_locations, i+1 break i += 1 ############### Inteaction with mosue and keyboard ################ def OnLeftUp(self, event): ''' handle left ouseclick up''' # get selection pos1, pos2, zero = self.plasmidstore.interaction["selection"] if self.plasmidstore.interaction["leftDown"] == True and pos2 != -1: # selection is finish self.plasmidstore.interaction["leftDown"] = False # save the mouse position x, y = self.ScreenToClient(wx.GetMousePosition()) x2, y2 = self.ctx.device_to_user(x,y) pos = self.cartesian2position(x2,y2) self.plasmidstore.interaction["selection"] = (pos1, pos, zero) elif pos2 == -1: # reset selection self.plasmidstore.interaction["selection"] = (1, -1, -1) # maybe we cliked on a feature, label or a line? hit = self.HitTest() if hit != None: self.plasmidstore.interaction["hit"] = hit # we hit one, so we need to draw the selection! # wich feature was hit? self.saveSelection(1, -1, -1, hit) else: self.plasmidstore.interaction["hit"] = None # update the UI maybe? self.update_ownUI() def OnLeftDown(self, event): # remove the position cursor: self.set_cursor_position(0) selection = self.HitTestSelection() if selection: # save the mouse position x, y = self.ScreenToClient(wx.GetMousePosition()) x2, y2 = self.ctx.device_to_user(x,y) pos = self.cartesian2position(x2,y2) self.plasmidstore.interaction["leftDown"] = True self.plasmidstore.interaction["selection"] = (pos, -1, -1) else: # remove selection self.plasmidstore.interaction["hit"] = None self.saveSelection(1, -1, -1, False) return None def OnLeftDouble(self, event): '''When left button is double clicked, launch the feature edit dialog.''' hit = self.HitTest() #this does not get the "true" feature index. Some featues are split and this is an index that accounts for that. if hit is not False: # get the index of this feature featurelist = genbank.gb.get_all_feature_positions() for i in range(0,len(featurelist)): # load all the feature infos featuretype, complement, start, finish, name, index = featurelist[i] hName = self.hitName(name, index) if hit == hName: genbank.feature_selection = copy.copy(index) dlg = featureedit_GUI.FeatureEditDialog(None, 'Edit Feature') # creation of a dialog with a title dlg.ShowModal() dlg.Center() def OnMotion(self, event): '''When mouse is moved with the left button down determine the DNA selection from angle generated at mouse down and mouse move event.''' if event.Dragging() and event.LeftIsDown() and self.plasmidstore.interaction["leftDown"] == True: # save the mouse position x, y = self.ScreenToClient(wx.GetMousePosition()) x2, y2 = self.ctx.device_to_user(x,y) pos = self.cartesian2position(x2,y2) pos1, pos2, zero = self.plasmidstore.interaction["selection"] self.plasmidstore.interaction["selection"] = (pos1, pos, zero) # if we have a sudden increase, we might have moven over zero if abs(pos2 - pos) > self.dnaLength/2 and pos2 != -1: zero = zero * -1 # turn zero around self.plasmidstore.interaction["selection"] = (pos1, pos, zero) #set genebank selection self.saveSelection(pos1, pos, zero) self.update_ownUI() # check if something is beneath the mousecursor oldhit = self.Highlight hit = self.HitTest() # save the state self.Highlight = hit # only update after change if oldhit != hit: self.update_ownUI() #elif len(self.selectionDrawing) < 2: # only hover position, if none is selected --> maybe use two variables to make both possible? # no feature to highlight, but maybe selection hover? # self.Highlight = None # check if we are in the "hover area" # inArea = self.HitTestMarkerArea() # if inArea == True: # # get the mouse position # x, y = self.ScreenToClient(wx.GetMousePosition()) # x2, y2 = self.ctx.device_to_user(x,y) # self.selectionDrawing = [] # empty it # self.selectionDrawing.append(self.cartesian2radial(x2,y2)) # self.update_ownUI() # elif oldhit != hit: # self.update_ownUI() # elif len(self.selectionDrawing) == 1: # TODO # self.selectionDrawing = [] # empty it so the hover line wont be seen all the time # self.update_ownUI() ############### Setting methods for converting stuff ############## def radial2cartesian(self,radius, angle, cx=0,cy=0): # angle in radial! radius = float(radius) angle = float(angle) x = radius * math.cos(angle) + cx y = radius * math.sin(angle) + cy return x,y def cartesian2radial(self,x, y): x = -x # cairo somehow swaps the x axis diff = -math.radians(90) # remove some radians, because 0 is on top r = math.atan2(x,y) + 1 * math.pi + diff return r def position2angle(self, position): radMult = float(2*math.pi/self.dnaLength) angle = (position - 0.5 * self.dnaLength) * radMult + math.pi/2 return angle def position2cartesian(self, position, radius): angle = self.position2angle(position) x,y = self.radial2cartesian(radius, angle) return x, y def cartesian2position(self, x,y): angle = self.cartesian2radial(x,y) radMult = float(2*math.pi/self.dnaLength) pos = 0.5 * self.dnaLength + (angle - math.pi/2)/radMult pos = int(round(pos, 0)) return pos def merge_dicts(self, *dict_args): ''' Given any number of dicts, shallow copy and merge into a new dict, precedence goes to key value pairs in latter dicts. ''' result = {} for dictionary in dict_args: result.update(dictionary) return result ######################################## # caclcualting functions def checkFeatureCalculations(self): ''' this functions takes the current positions and calcualtions of the features and checks fast if it has to recalculated anything''' redrawfeatures = False # compare the features to the previously calculated ones featuresOld = self.plasmidstore.features featuresNew = genbank.gb.get_all_feature_positions() if featuresNew != None: # get the length once self.dnaLength = float(len(genbank.gb.GetDNA())) else: self.dnaLength = 1 # or 0 but this might cause problems if there is a division x/length # compare old and new if featuresOld != featuresNew: # print ("DO recalc features") # we only have to redraw if the length, the color or direction changed # calculate and save all the features allFeatures = {} # storing list # some basic info we have to update: self.drawn_fw_locations = [] self.drawn_rv_locations = [] labelFeature = [] # labels for features outside of the arrow # save as [[middle, name, index, hitname, y],[...]] # loop the features and get the path for feature in featuresNew: featuretype, complement, start, finish, name, index = feature hitname = self.hitName(name, index) # do not display the annoying source if featuretype != "source": # path creation allFeatures[hitname] = [self.cairoFeature(feature), featuretype] # label handling length = self.dnaLength middle = start + (finish-start)/2 % length name = self.nameBeautiful(name) y = None # to be populated labelFeature.append([middle, name, index, hitname, y]) # save the new paths in the fancy storing class for further processing self.plasmidstore.drawnfeatures = allFeatures # save label drawn outside as text self.plasmidstore.LoF = labelFeature self.plasmidstore.features = featuresNew # everything is over and the new are the current (old) return redrawfeatures def cairoFeature(self, feature): featurepath = None featuretype, complement, start, finish, name, index = feature name = self.nameBeautiful(name) hname = self.hitName(name, index) # create the correct path for foreward and reverse if complement == False: self.drawn_fw_locations, levelMult = self.find_overlap(self.drawn_fw_locations, (start, finish)) arrowMult = 1 # to account for the different direction of addition radiusAdd = levelMult * (self.arrowWidth + 0.5) + 0.4 radiusO = self.radiusO + radiusAdd + self.arrowWidth radiusI = self.radiusO + radiusAdd else: self.drawn_rv_locations, levelMult = self.find_overlap(self.drawn_rv_locations, (start, finish)) arrowMult = -1 # to account for the different direction of addition radiusAdd = levelMult * (self.arrowWidth + 0.5) + 0.4 radiusO = self.radiusI - radiusAdd - self.arrowWidth radiusI = self.radiusI - radiusAdd # calculate the radians for end and start s = self.position2angle(start) e = self.position2angle(finish) # large features get a arrow head: arrowHead = False if abs(e - s) > math.radians(5): # more then x degrees arrowHead = True if arrowHead == True and complement == False: # arrow head has x radians r = radiusI + arrowMult * self.arrowWidth/2 xA, yA = self.radial2cartesian(r, e) e = e - math.radians(2) # new end self.ctx.new_path() self.ctx.arc(0,0,radiusI, s,e) # draw arrow head sometimes: self.ctx.line_to(xA, yA) #self.ctx.move_to(cx1, cy1) self.ctx.arc_negative(0,0,radiusO, e,s) self.ctx.close_path() featurepath = self.ctx.copy_path() self.ctx.new_path() # clear canvas elif arrowHead == True and complement == True: # arrow head has x radians r = radiusI + arrowMult * self.arrowWidth/2 xA, yA = self.radial2cartesian(r, s) s = s + math.radians(2) # new "start" self.ctx.new_path() self.ctx.arc_negative(0,0,radiusI, e,s) self.ctx.line_to(xA, yA) self.ctx.arc(0,0,radiusO, s,e) self.ctx.close_path() featurepath = self.ctx.copy_path() self.ctx.new_path() # clear canvas else: self.ctx.new_path() self.ctx.arc(0,0,radiusI, s,e) self.ctx.arc_negative(0,0,radiusO, e,s) self.ctx.close_path() featurepath = self.ctx.copy_path() self.ctx.new_path() # clear canvas # save the outer radius to allow draing of the lines: if complement == False: self.plasmidstore.radiusOuter[hname] = radiusO else: self.plasmidstore.radiusOuter[hname] = radiusI return featurepath def caironameLabels(self, labels, site, kind): ''' create paths for labels given''' if kind == "features": self.ctx.select_font_face('Arial', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) else: self.ctx.select_font_face('Arial', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) self.ctx.set_font_size(self.lH) textPaths = {} linePaths = {} underlinePaths = {} boxPaths = {} shift = 4 # shift the labels closer to the plasmid, as the have a bigger radius for l in labels: name = l[1] y = l[4] if kind == "enzymes": name2 = "%s%s" %(name, self.enzymeGap) else: name2 = name hname = self.hitName(name2, l[2]) xbearing, ybearing, TextWidth, TextHeight, xadvance, yadvance = self.ctx.text_extents(name) if site == "left": x = - math.cos(math.asin(y/self.radiusLabels)) * self.radiusLabels - TextWidth + shift else: x = math.cos(math.asin(y/self.radiusLabels)) * self.radiusLabels - shift self.ctx.move_to(x, y); self.ctx.text_path(name) # save label: path = self.ctx.copy_path() textPaths[hname] = path self.ctx.new_path() # clear canvas # make a box behind the text, so we can select more easys: self.ctx.rectangle(x, y - self.lH, TextWidth, self.lH) boxPaths[hname] = self.ctx.copy_path() self.ctx.new_path() # featurs have a underline: if kind == "features": self.ctx.move_to(x, y) self.ctx.line_to(x + TextWidth, y) path = self.ctx.copy_path() underlinePaths[hname] = path self.ctx.new_path() # clear canvas # make a line from text to plasmid: if site == "left": x = x + TextWidth else: x = x # get the radius to where the line should lead: try: r = self.plasmidstore.radiusOuter[hname] except: r = self.radiusI y1 = y - self.lH/2 + 0.3 self.ctx.move_to(x, y1) x2, y2 = self.position2cartesian(l[0], self.radiusO + 7) x3, y3 = self.position2cartesian(l[0], r) self.ctx.line_to(x2,y2) self.ctx.line_to(x3,y3) linePaths[hname] = self.ctx.copy_path() # save line path self.ctx.new_path() # clear canvas return textPaths, linePaths, boxPaths, underlinePaths def checkLabelCalculations(self): ''' this functions takes the current positions and calcualtions of the labels and checks fast if it has to recalculated anything''' # labels to handle: LoF = self.plasmidstore.LoF LoE = self.plasmidstore.LoE # check if the labels changed, onyl recalculate everything, if they did if LoF != self.plasmidstore.LoFold or LoE != self.plasmidstore.LoEold: # print("DO recalculate label") # sort labels for their middle position: LoF = sorted(LoF, key=lambda x: x[0]) LoE = sorted(LoE, key=lambda x: x[0]) # group the labels # TODO, should be interactive anyway # now split in two sites: half = self.dnaLength/2 # Features LoF1 = [] # first half LoF2 = [] # second half LoF1I = [] # indexing LoF2I = [] # indexing for l in LoF: if l[0] < half: LoF1.append(l) LoF1I.append(l[3]) else: LoF2.append(l) LoF2I.append(l[3]) # Enzymes LoE1 = [] # first half LoE2 = [] # second half LoE1I = [] # indexing LoE2I = [] # indexing for l in LoE: if l[0] < half: LoE1.append(l) LoE1I.append(l[3]) else: LoE2.append(l) LoE2I.append(l[3]) # then check the numbers LoF1, LoE1 = self.checkNumberOfLabels(LoF1, LoE1) # arrange and draw labels: Labels1 = self.arrangeLabels(LoF1, LoE1, -1) Labels2 = self.arrangeLabels(LoF2, LoE2, +1) # sperate into features and enzymes... # this must be, so we can order them together, but color them # different LabelsFeatures1 = [] LabelsFeatures2 = [] LabelsEnzymes1 = [] LabelsEnzymes2 = [] for label in Labels1: hit = label[3] if hit in LoF1I: LabelsFeatures1.append(label) if hit in LoE1I: LabelsEnzymes1.append(label) for label in Labels2: hit = label[3] if hit in LoF2I: LabelsFeatures2.append(label) if hit in LoE2I: LabelsEnzymes2.append(label) # done serperating the labels agai # FEATURES: make the paths for features laP1, liP1, bP1, UlP1 = self.caironameLabels(LabelsFeatures1, "right", "features") laP2, liP2, bP2, UlP2 = self.caironameLabels(LabelsFeatures2, "left", "features") # FEATURES: save paths for displaying self.plasmidstore.drawnlabels = self.merge_dicts(laP1, laP2) self.plasmidstore.drawnLLines = self.merge_dicts(liP1, liP2) self.plasmidstore.labelBoxes = self.merge_dicts(bP1, bP2) self.plasmidstore.labelULine = self.merge_dicts(UlP1, UlP2) # ENZYMES: make the paths for enzymes laP1, liP1, bP1, UlP1 = self.caironameLabels(LabelsEnzymes1, "right", "enzymes") laP2, liP2, bP2, UlP2 = self.caironameLabels(LabelsEnzymes2, "left", "enzymes") # ENZYMES: save paths for displaying self.plasmidstore.drawnlabelsE = self.merge_dicts(laP1, laP2) self.plasmidstore.drawnLLinesE = self.merge_dicts(liP1, liP2) self.plasmidstore.labelBoxesE = self.merge_dicts(bP1, bP2) # save the new status quo of the labels self.plasmidstore.LoFold = self.plasmidstore.LoF self.plasmidstore.LoEold = self.plasmidstore.LoE return None else: # print("NOT recalculate label") return None def checkNumberOfLabels(self, LoF=None, LoE=None): ''' this function is called before label sorting''' # determine height of label displaying area height = self.radius * 2.2 # divide by the height of each label lH = self.ctx.device_to_user_distance(self.labelHeight,0)[0] # label height lH = round(lH, 3) self.lH = lH # for other functions to use # round down, to make it work nL = math.trunc(height/lH) # number of labels to handle nFL = len(LoF) nEL = len(LoE) if nFL + nEL <= nL: # no problem here return LoF, LoE # return feature Label and Text Label elif nFL <= nL: # reduce the nTL so, that it may work. # reduce most populated areas # print("group some Restrictionsite labels agressivly") # call function and check again if we can fit everything in # if it did work--> fine # else show it anyway --> or figure something out return LoF, LoE # return feature Label and Text Label elif nFL > nL: # we have to many feature labels. # so we hide all TL and remove the labels of the smallest features! # print("delete some Restrictionsite labels") return LoF, LoE # return feature Label and Text Label else: # print("check Number of labels: undefined") return LoF, LoE # return feature Label and Text Label #def groupLabels(self, labels): # ''' restrictionenzymelabels can be grouped # as they somtimes occure at the very same place ''' # lastPos = None # i = 0 # for l in labels: # pos = l[0] # # if pos == lastPos: # # two labels at the very same position # # we merge the names # cname = l[1] # oname = labels[i][1] # name = "%s, %s" # i = i + 1 # # we can group labels of same type and same position # return None def groupAgressivly(self): # we can group some close restrictionlabels # and only show the position indicating by a number return None def arrangeLabels(self, F, E, index): ''' this function does the actual positioning of all labels''' # now they are equal # enzymes, features etc: merge Labels = F + E Labels = sorted(Labels, key=lambda x: x[0]) # find the most horiziontal one horzI = None horzN = self.dnaLength if index == -1: target = self.dnaLength / 4 * 1 else: target = self.dnaLength / 4 * 3 i = 0 # start by putting the initial position for l in Labels: # append a new entry with the current y coordinate: x,y = self.position2cartesian(l[0], self.radiusLabels - 2) l[4] = round(y, 3) # most horizontal one: if abs(l[0] - target) < horzN: horzN = abs(l[0] - target) # save new differenze horzI = i # save index i = i + 1 # create series for loop for better positioning # this way we start positioning in the hotizontal position and # arrange the labels in both directions more accurate h = 1 loop = [horzI] newI = horzI switch = 1 both = True while h < len(Labels): if newI > 0 and newI < len(Labels)- 1 and both == True: newI = newI + switch * h switch = -1 * switch elif newI == 0: switch = 1 newI = newI + switch * h both = False elif newI == len(Labels) - 1: switch = -1 newI = newI + switch * h both = False else: newI = newI + switch * 1 loop.append(newI) h = h + 1 # now iterate n times and check if x[i] overlaps with either x[i+1] or x[i-1] # this is the key element of the label positioning algorythm # n is critical for performance, higher n = more steps # dy is the step size in px dy = self.ctx.device_to_user_distance(5,0)[0] # amount of movement in pixel i = 0 # just a counter n = 100 # n of steps overlapp = 1 # initial overlap, so it is not 0 while i < n and overlapp > 0: overlapp = 0 #for l in Labels: for a in loop: l = Labels[a] # for those labels with one upper and lower label y = l[4] # y U = y + self.lH # upper border L = y # lower border # get the correct upper and lower bound of the adjacend features or the end of the radius if index > 0: if a == 0: # next label nL = +self.radiusLabels else: # next label nextLabel = Labels[a - 1] nL = nextLabel[4] # next lower if a == len(Labels)-1: lU = -self.radiusLabels else: # previous label prevLabel = Labels[a + 1] lU = prevLabel[4] + self.lH # last upper else: if a == len(Labels)-1: # next label nL = self.radiusLabels else: # next label nextLabel = Labels[a + 1] nL = nextLabel[4] # next lower if a == 0: lU = -self.radiusLabels else: # previous label prevLabel = Labels[a - 1] lU = prevLabel[4] + self.lH # last upper # check upper bound dU = U - nL # check lower bound dL = lU - L # move the item up or down, according to its overlapping if dU > 0 or dL > 0: # we need to move somewhere if dU > 0 and dL <= 0: # move down y = y - dy # raise the overlap overlapp = overlapp + dU elif dU <= 0 and dL > 0: # we need to move up y = y + dy # raise the overlap overlapp = overlapp + dL elif dU > 0 and dL > 0: # we need to make them even y = y + (dU - (dU + dL)/2) # raise the overlap overlapp = overlapp + dU + dL # prevent to large y if y > self.radiusLabels: y = self.radiusLabels elif y < -self.radiusLabels: y = - self.radiusLabels # save y: l[4] = y # print("DO reposition labels, Round: ", i) i = i + 1 # after n cycles stop calculations and return the positions return Labels def checkEnzymeCalculations(self): # load enzymes enzymes = genbank.gb.restrictionEnzymes.selection # create hash of selection and DNA hashable = "%s%s" % (frozenset(enzymes), genbank.gb.gbfile['dna']) e1 = hashlib.md5(hashable).hexdigest() labels = [] if e1 !=self.plasmidstore.enzymes: # we have new enzymes index = 0 for e in enzymes: for site in enzymes[e].restrictionSites: name, start, end, cut51, cut52, dnaMatch = site length = self.dnaLength middle = start + (end-start)/2 % length name = self.nameBeautiful(name) name2 = "%s%s" %(name, self.enzymeGap) hitname = self.hitName(name2, index) y = None # to be populated labels.append([middle, name, index, hitname, y]) index = index + 1 self.plasmidstore.enzymes= e1 self.plasmidstore.LoE = labels return None def checkSelectionCalculations(self): ''' only chnage the arc if we changed something ''' # selection selOld = self.plasmidstore.interaction["selectionOld"] sel = self.plasmidstore.interaction["selection"] if sel != selOld: if sel[1] == -1: # remove selection arc self.plasmidstore.drawnSelection = None else: # draw selection arc pos1, pos2, zero = sel a1 = self.position2angle(pos1) a2 = self.position2angle(pos2) if (pos1 < pos2 and zero == -1) or (pos2 < pos1 and zero == 1): self.ctx.arc(0,0,self.radius,a1,a2) else : self.ctx.arc_negative(0,0,self.radius,a1,a2) path = self.ctx.copy_path() # save line path self.ctx.new_path() # clear canvas self.plasmidstore.drawnSelection = path self.plasmidstore.interaction["selectionOld"] = sel # draw the line for indicating the position position = self.plasmidstore.interaction["position"] positionOld = self.plasmidstore.interaction["positionOld"] if position != positionOld: if position < self.dnaLength and position > 0: # get the coordinates for the line x1, y1 = self.position2cartesian(position, self.radiusI - 1) x2, y2 = self.position2cartesian(position, self.radiusO + 1) # draw the actual line self.ctx.move_to(x1,y1) self.ctx.line_to(x2,y2) # copy, save and delete path = self.ctx.copy_path() self.plasmidstore.drawnPosition = path self.ctx.new_path() # save settings else: self.plasmidstore.drawnPosition = None self.plasmidstore.interaction["positionOld"] = position return None ############################################ # drawing functions def draw(self): ''' # print the stored elements on th canvas''' # prepare the canvas width, height = self.GetVirtualSize() if width < height: ratio = float(width)/float(height) # only resize the canvas if software is loaded. This prevents error messages if width != 0 and height != 0 and width > 100 and height > 100: self.ctx.scale(width, height*ratio) # Normalizing the canvas self.ctx.scale(0.01, 0.01) # make it 100*ratiox100 self.ctx.translate (50,50/ratio) # set center to 0,0 if width >= height: ratio = float(height)/float(width) # only resize the canvas if software is loaded. This prevents error messages if width != 0 and height != 0 and width > 100 and height > 100: self.ctx.scale(width*ratio, height) # Normalizing the canvas self.ctx.scale(0.01, 0.01) # make it 100*ratiox100 self.ctx.translate (50/ratio,50) # set center to 0,0 self.drawFeatures() self.drawLabels() self.drawSelection() return None def drawFeatures(self): # print("DO output path feature") # only resize the canvas if software is loaded. This prevents error messages # draw the helper plain white circle to make selection possible: self.ctx.arc(0,0,self.radiusO+1,0,2*math.pi) # outer circle self.ctx.set_source_rgb (1, 1, 1) # white self.ctx.set_line_width (0) # no line self.plasmidstore.interaction["markerArea1"] = self.ctx.copy_path() # copy for later self.ctx.new_path() # remove self.ctx.arc(0,0,self.radiusI-1,0,2*math.pi) # inner circle self.ctx.set_source_rgb (1,1,1) # Solid white self.ctx.set_line_width (0) # no line self.plasmidstore.interaction["markerArea2"] = self.ctx.copy_path() # copy for later self.ctx.new_path() # remove # draw the plasmid (two circles with different radius) radius=10 self.ctx.arc(0,0,self.radiusO,0, 2*math.pi) # circle self.ctx.set_source_rgb (0, 0, 0) # Solid color self.ctx.set_line_width (0.24) # line width self.ctx.stroke() # stroke only no fill! # inner plasmid self.ctx.arc(0,0,self.radiusI,0,2*math.pi) # inner circle self.ctx.stroke() # stroke the same color and settings # draw the buffered featrues: i = 0 for a in self.plasmidstore.drawnfeatures: path = self.plasmidstore.drawnfeatures[a][0] # get color featuretype = self.plasmidstore.drawnfeatures[a][1] color = eval(featuretype)['fw'] #get the color of feature (as string) assert type(color) == str r,g,b = colcol.hex_to_rgb(color) r = float(r)/255 g = float(g)/255 b = float(b)/255 # put the path on the canvas and fill #self.ctx.new_path() self.ctx.append_path(path) self.ctx.set_source_rgba (r,g,b,1.0) # Solid color self.ctx.fill_preserve() # outline for the feature that is beneeth the mouse: if a == self.Highlight: #self.ctx.set_source_rgb (1, 0, 0) self.ctx.set_line_width(0.3) self.ctx.stroke() else: # remove path self.ctx.new_path() i = i + 1 return None def drawLabels(self): ''' function to put the labels on the canvas''' # FEATURES self.ctx.set_source_rgb (0, 0, 0) # Solid color self.ctx.set_line_width(0.1) # draw feature label names for a in self.plasmidstore.drawnlabels: path = self.plasmidstore.drawnlabels[a] self.ctx.append_path(path) # darker color for the feature that is beneeth the mouse: if a == self.Highlight: self.ctx.set_source_rgba (0, 0, 0, 1) else: self.ctx.set_source_rgba (0, 0, 0, 0.7) self.ctx.fill() # draw the lines for a in self.plasmidstore.drawnLLines: path = self.plasmidstore.drawnLLines[a] self.ctx.append_path(path) # darker color for the feature that is beneeth the mouse: if a == self.Highlight: self.ctx.set_source_rgba (0, 0, 0, 1) else: self.ctx.set_source_rgba (0, 0, 0, 0.3) self.ctx.stroke() # draw the underlines #for a in self.plasmidstore.labelULine: # path = self.plasmidstore.labelULine[a] # self.ctx.append_path(path) # self.ctx.set_line_width(2) # # # darker color for the feature that is beneeth the mouse: # if a == self.Highlight: ## self.ctx.set_source_rgba (0, 0, 0, 1) # else: # self.ctx.set_source_rgba (0, 0, 0, 0.3) # self.ctx.stroke() # ENZYMES self.ctx.set_source_rgb (0, 0, 0) # Solid color self.ctx.set_line_width(0.1) # draw feature label names for a in self.plasmidstore.drawnlabelsE: path = self.plasmidstore.drawnlabelsE[a] self.ctx.append_path(path) # darker color for the feature that is beneeth the mouse: if self.Highlight != None and a.split(self.enzymeGap)[0] == self.Highlight.split(self.enzymeGap)[0]: self.ctx.set_source_rgba (0, 0, 0, 1) else: self.ctx.set_source_rgba (0, 0, 0, 0.7) self.ctx.fill() # draw the lines for a in self.plasmidstore.drawnLLinesE: path = self.plasmidstore.drawnLLinesE[a] self.ctx.append_path(path) # darker color for the feature that is beneeth the mouse: if self.Highlight != None and a.split(self.enzymeGap)[0] == self.Highlight.split(self.enzymeGap)[0]: self.ctx.set_source_rgba (0, 0, 0, 1) else: self.ctx.set_source_rgba (0, 0, 0, 0.3) self.ctx.stroke() def drawSelection(self): ''' draw arc for selection ''' if self.plasmidstore.drawnSelection != None: #0082ED r,g,b = colcol.hex_to_rgb('#0082ED') r = float(r)/255 g = float(g) /255 b = float(b) /255 self.ctx.set_source_rgba (r,g,b, 0.6) # Solid color self.ctx.set_line_width(3) path = self.plasmidstore.drawnSelection self.ctx.append_path(path) self.ctx.stroke() if self.plasmidstore.drawnSelection == None and self.plasmidstore.drawnPosition != None: self.ctx.set_source_rgba (0,0,0) # Solid color self.ctx.set_line_width(0.2) path = self.plasmidstore.drawnPosition self.ctx.append_path(path) self.ctx.stroke()