# --- UCSF Chimera Copyright --- # Copyright (c) 2000 Regents of the University of California. # All rights reserved. This software provided pursuant to a # license agreement containing restrictions on its disclosure, # duplication and use. This notice must be embedded in or # attached to all copies, including partial copies, of the # software or any revisions or derivations thereof. # --- UCSF Chimera Copyright --- # # $Id: __init__.py 40621 2015-05-29 18:22:16Z pett $ from OpenGL import GL from PythonModel.PythonModel import PythonModel import chimera from SimpleSession import SAVE_SESSION import threading _uidLock = threading.RLock() from Label import Label _ilabelModel = None def LabelsModel(create=True): global _ilabelModel if not _ilabelModel: if not create: return None IlabelModel() return _ilabelModel AUTO_ID_PREFIX = "label2d_id_" class IlabelModel(PythonModel): def __init__(self): # setting _ilabelModel has to be here instead of in # LabelsModel() for backwards compatibility with old sessions global _ilabelModel if _ilabelModel: raise RuntimeError("Attempt to create second" " IlabelModel instance") _ilabelModel = self PythonModel.__init__(self) self.labels = [] self.labelMap = {} self._UID = 0 self.curLabel = None chimera.openModels.add([self], baseId=-1, hidden=True) self._handlerIDs = {} for trigName, handler in [(SAVE_SESSION, self._saveSession), (chimera.SCENE_TOOL_SAVE, self._saveScene), (chimera.SCENE_TOOL_RESTORE, self._restoreScene), (chimera.ANIMATION_TRANSITION_START, self._animStart), (chimera.ANIMATION_TRANSITION_FINISH, self._animFinish)]: self._handlerIDs[trigName] = chimera.triggers.addHandler(trigName, handler, None) def destroy(self, *args): for label in self.labels: label.destroy() for trigName, handlerID in self._handlerIDs.items(): chimera.triggers.deleteHandler(trigName, handlerID) PythonModel.destroy(self, True) global _ilabelModel _ilabelModel = None def computeBounds(self, sphere, bbox): return False def validXform(self): return False def draw(self, lens, viewer, passType): if passType != chimera.LensViewer.Overlay2d: return w, h = viewer.windowSize scale = chimera.LODControl.get().fontAdjust() curFont = None for label in self.labels: # TODO: handle justify # style (justify is only left/center/right) # any changes here need to mirror in pickLabel # and x3dWrite if not unicode(label).strip() or not label.shown: continue labelLines = label.lines[:] while labelLines and not labelLines[0]: labelLines.pop(0) while labelLines and not labelLines[-1]: labelLines.pop() if not labelLines: continue maxWidth = 0.0 textTop, textBottom = None, None baseX, baseY = label.pos[0]*w, label.pos[1]*h # looks less fuzzy on integer boundaries baseX = int(baseX + 0.5) baseY = int(baseY + 0.5) for line in labelLines: width = 0.0 for c in line: # font setup/teardown is expensive # so minimize it as much as possible if curFont is None or c.font != curFont: if curFont is not None: curFont.cleanup() curFont = c.font curFont.setup() text = unicode(c) width += curFont.width(text) if label.background: if line == labelLines[0]: u, d = curFont.height(text) if textTop is None or baseY + u > textTop: textTop = baseY + u if line == labelLines[-1]: u, d = curFont.height(text) if textBottom is None or baseY + d < textBottom: textBottom = baseY + d if curFont: fsize = curFont.size() else: fsize = 24 baseY -= fsize * scale maxWidth = max(maxWidth, width) if label.background: scaledMargin = label.margin * scale x1 = baseX - scaledMargin x2 = baseX + maxWidth + scaledMargin y1 = textBottom - scaledMargin y2 = textTop + scaledMargin bkgrdColor = label.background[:3] + (label.background[3] * label.opacity,) if label.outline > 0: outlineColor = contrastWith(bkgrdColor)[:3] + (label.opacity,) scaledOutline = label.outline * scale ox1 = x1 - scaledOutline ox2 = x2 + scaledOutline oy1 = y1 - scaledOutline oy2 = y2 + scaledOutline GL.glPushMatrix() GL.glBegin(GL.GL_QUADS) GL.glColor4f(*outlineColor) #top GL.glVertex2f(ox1, oy2) GL.glVertex2f(x1, y2) GL.glVertex2f(x2, y2) GL.glVertex2f(ox2, oy2) # right GL.glVertex2f(x2, y2) GL.glVertex2f(ox2, oy2) GL.glVertex2f(ox2, oy1) GL.glVertex2f(x2, y1) # bottom GL.glVertex2f(ox1, oy1) GL.glVertex2f(x1, y1) GL.glVertex2f(x2, y1) GL.glVertex2f(ox2, oy1) # left GL.glVertex2f(ox1, oy1) GL.glVertex2f(x1, y1) GL.glVertex2f(x1, y2) GL.glVertex2f(ox1, oy2) GL.glEnd() # to sidestep backface culling, draw another quad # in the other order GL.glBegin(GL.GL_QUADS) GL.glColor4f(*outlineColor) #top GL.glVertex2f(ox2, oy2) GL.glVertex2f(x2, y2) GL.glVertex2f(x1, y2) GL.glVertex2f(ox1, oy2) # right GL.glVertex2f(x2, y1) GL.glVertex2f(ox2, oy1) GL.glVertex2f(ox2, oy2) GL.glVertex2f(x2, y2) # bottom GL.glVertex2f(ox2, oy1) GL.glVertex2f(x2, y1) GL.glVertex2f(x1, y1) GL.glVertex2f(ox1, oy1) # left GL.glVertex2f(ox1, oy2) GL.glVertex2f(x1, y2) GL.glVertex2f(x1, y1) GL.glVertex2f(ox1, oy1) GL.glEnd() GL.glPopMatrix() GL.glPushMatrix() GL.glBegin(GL.GL_QUADS) GL.glColor4f(*bkgrdColor) GL.glVertex2f(x1, y1) GL.glVertex2f(x2, y1) GL.glVertex2f(x2, y2) GL.glVertex2f(x1, y2) GL.glEnd() # to sidestep backface culling, draw another quad # in the other order GL.glBegin(GL.GL_QUADS) GL.glColor4f(*bkgrdColor) GL.glVertex2f(x1, y2) GL.glVertex2f(x2, y2) GL.glVertex2f(x2, y1) GL.glVertex2f(x1, y1) GL.glEnd() GL.glPopMatrix() baseX, baseY = label.pos[0]*w, label.pos[1]*h # looks less fuzzy on integer boundaries baseX = int(baseX + 0.5) baseY = int(baseY + 0.5) for line in labelLines: width = 0.0 for c in line: GL.glPushMatrix() GL.glTranslatef(baseX + width, baseY, 0.0) # font setup/teardown is expensive # so minimize it as much as possible if curFont is None or c.font != curFont: if curFont is not None: curFont.cleanup() curFont = c.font curFont.setup() if label.opacity < 1.0: rgba = c.rgba[:3] + (label.opacity * c.rgba[3],) else: rgba = c.rgba curFont.setColor(*rgba) text = unicode(c) curFont.draw(text) width += curFont.width(text) GL.glPopMatrix() if curFont: fsize = curFont.size() else: fsize = 24 baseY -= fsize * scale if curFont is not None: curFont.cleanup() def x3dNeeds(self, scene): if not self.labels: return scene.needComponent(chimera.X3DScene.Text, 1) for label in self.labels: if label.background: # for ColorRGBA scene.needComponent(chimera.X3DScene.Rendering, 4) break def x3dWrite(self, indent, scene): if not self.labels: return # repeat draw code, but output x3d w, h = chimera.viewer.windowSize prefix = ' ' * indent curFont = None fontCount = 0 output = [] FONTDEF = "\n" ENDFONTDEF = "\n" FONTUSE = "\n" FILENAME = "\n" TEXT = "\n" ENDTEXT = "\n" TRANS = "\n" TRANSCALE = "\n" ENDTRANS = "\n" SHAPE = "\n" ENDSHAPE = "\n" APPEAR = "\n" ENDAPPEAR = "\n" MAT = "\n" MATTRANS = "\n" OVERLAY = "\n" # translate to hither plane and scale to match pixels cam = chimera.viewer.camera view = 0 eyePos = cam.eyePos(view) left, right, bottom, top, hither, yon, focal = cam.window(view) scale = (right - left) / w xlate_scale = (eyePos[0] + left, eyePos[1] + bottom, eyePos[2] - hither, scale, scale, 1) output.extend([ prefix, OVERLAY, prefix, TRANSCALE % xlate_scale ]) hasLabels = False for label in self.labels: if not unicode(label) or not label.shown: continue labelLines = label.lines[:] while labelLines and not labelLines[0]: labelLines.pop(0) while labelLines and not labelLines[-1]: labelLines.pop() if not labelLines: continue hasLabels = True maxWidth = 0.0 textTop, textBottom = None, None baseX, baseY = label.pos[0]*w, label.pos[1]*h # looks less fuzzy on integer boundaries baseX = int(baseX + 0.5) baseY = int(baseY + 0.5) for line in labelLines: width = 0.0 for c in line: # font setup/teardown is expensive # so minimize it as much as possible if curFont is None or c.font != curFont: if curFont is not None: curFont.cleanup() curFont = c.font curFont.setup() curFont.setColor(*c.rgba) text = unicode(c) ##curFont.draw(text) width += curFont.width(text) if label.background: if line == labelLines[0]: u, d = curFont.height(text) if textTop is None or baseY + u > textTop: textTop = baseY + u if line == labelLines[-1]: u, d = curFont.height(text) if textBottom is None or baseY + d < textBottom: textBottom = baseY + d if curFont: fsize = curFont.size() else: fsize = 24 baseY -= fsize maxWidth = max(maxWidth, width) if curFont is not None: curFont.cleanup() curFont = None if label.background: x1 = baseX - label.margin x2 = baseX + maxWidth + label.margin y1 = textBottom - label.margin y2 = textTop + label.margin bkgrdColor = label.background[:3] + (label.background[3] * label.opacity,) self.x3dRect(output, prefix, bkgrdColor, x1, y1, x2, y1, x2, y2, x1, y2) # to sidestep backface culling, draw another quad # in the other order self.x3dRect(output, prefix, bkgrdColor, x1, y2, x2, y2, x2, y1, x1, y1) if label.outline > 0: outlineColor = contrastWith(bkgrdColor)[:3] + (label.opacity,) ox1 = x1 - label.outline ox2 = x2 + label.outline oy1 = y1 - label.outline oy2 = y2 + label.outline #top self.x3dRect(output, prefix, outlineColor, ox1, oy2, x1, y2, x2, y2, ox2, oy2) # right self.x3dRect(output, prefix, outlineColor, x2, y2, ox2, oy2, ox2, oy1, x2, y1) # bottom self.x3dRect(output, prefix, outlineColor, ox1, oy1, x1, y1, x2, y1, ox2, oy1) # left self.x3dRect(output, prefix, outlineColor, ox1, oy1, x1, y1, x1, y2, ox1, oy2) # to sidestep backface culling, draw another quad # in the other order #top self.x3dRect(output, prefix, outlineColor, ox2, oy2, x2, y2, x1, y2, ox1, oy2) # right self.x3dRect(output, prefix, outlineColor, x2, y1, ox2, oy1, ox2, oy2, x2, y2) # bottom self.x3dRect(output, prefix, outlineColor, ox2, oy1, x2, y1, x1, y1, ox1, oy1) # left self.x3dRect(output, prefix, outlineColor, ox1, oy2, x1, y2, x1, y1, ox1, oy1) baseX, baseY = label.pos[0]*w, label.pos[1]*h # looks less fuzzy on integer boundaries baseX = int(baseX + 0.5) baseY = int(baseY + 0.5) for line in label.lines: width = 0.0 for c in line: output.extend([prefix, ' ', TRANS % (baseX + width, baseY, 0)]) output.extend([prefix, ' ', SHAPE]) output.extend([prefix, ' ', APPEAR]) if c.rgba[3] != 1: rgbt = c.rgba[0:3] + (1 - c.rgba[3],) output.extend([prefix, ' ', MATTRANS % rgbt]) else: output.extend([prefix, ' ', MAT % c.rgba[0:3]]) output.extend([prefix, ' ', ENDAPPEAR]) text = unicode(c) output.extend([prefix, ' ', TEXT % chimera.xml_quote(text)]) if curFont is None or c.font != curFont: fontCount += 1 curFont = c.font output.extend([ prefix, ' ', FONTDEF % (fontCount, curFont.x3dFamily(), curFont.size(), curFont.x3dStyle()), prefix, ' ', FILENAME % curFont.filename(), prefix, ' ', ENDFONTDEF ]) else: output.extend([prefix, ' ', FONTUSE % fontCount]) width += curFont.width(text) output.extend([prefix, ' ', ENDTEXT]) output.extend([prefix, ' ', ENDSHAPE]) output.extend([prefix, ' ', ENDTRANS]) baseY -= curFont.size() output.extend([prefix, ENDTRANS]) if not hasLabels: return "" return ''.join(output) def x3dRect(self, output, prefix, color, *coords): SHAPE = "\n" ENDSHAPE = "\n" APPEAR = "\n" ENDAPPEAR = "\n" TRIANGLESTRIP = "\n" ENDTRIANGLESTRIP = "\n" COLOR = "\n" COORD = "\n" MAT = "\n" output.extend([ prefix, ' ', SHAPE, prefix, ' ', APPEAR, prefix, ' ', MAT, prefix, ' ', ENDAPPEAR]) output.extend([prefix, ' ', TRIANGLESTRIP]) output.extend([prefix, ' ', COLOR]) for i in range(4): output.extend(["%g " % c for c in color]) output.extend([prefix, ' ', ENDCOLOR]) output.extend([prefix, ' ', COORD]) p1, p2, p3, p4 = [(coords[i], coords[i+1]) for i in range(0, 8, 2)] for p in (p2, p1, p3, p4): output.append("%g %g 0.0 " % p) output.extend([prefix, ' ', ENDCOORD]) output.extend([prefix, ' ', ENDTRIANGLESTRIP]) output.extend([prefix, ' ', ENDSHAPE]) def changeLabel(self, newText): self.curLabel.set(newText) self.setMajorChange() def changeToLabel(self, nextLabel): self.curLabel = nextLabel def moveLabel(self, pos): self.curLabel.pos = pos self.setMajorChange() def _getLabelID(self): ## start critical section _uidLock.acquire() id = self._UID self._UID += 1 _uidLock.release() ## end critical section return AUTO_ID_PREFIX + "%s" % id def newLabel(self, pos, labelID=None): if labelID: if labelID in self.labelMap.keys(): raise RuntimeError("Already have a label " \ "with ID '%s'" % labelID ) else: l_id = labelID else: l_id = self._getLabelID() newLabel = Label(pos, self) self.labels.append(newLabel) self.labelMap[l_id] = newLabel return newLabel def pickLabel(self, pos, w, h): slop = 2 targetX, targetY = pos[0]*w, pos[1]*h for label in self.labels: if not unicode(label) or not label.shown: continue if label.background: extra = max(label.margin + label.outline, 0) + slop else: extra = slop baseX, baseY = label.pos[0]*w, label.pos[1]*h font = None for line in label.lines: width = 0.0 for c in line: font = c.font lx = baseX + width - extra text = unicode(c) width += font.width(text) rx = baseX + width + extra above, below = font.height(text) uy = baseY + above + extra ly = baseY + below - extra if targetX >= lx and targetX <= rx \ and targetY >= ly and targetY <= uy: moveOffset = ( targetX - baseX, targetY - label.pos[1]*h ) break else: if font: baseY -= font.size() else: baseY -= 24 continue break else: continue break else: label = None moveOffset = (0, 0) return label, moveOffset def removeLabel(self, label): self.labels.remove(label) for k,v in self.labelMap.items(): if v == label: del self.labelMap[k] break if label == self.curLabel: self.curLabel = None if unicode(label): self.setMajorChange() label.destroy() def _animStart(self, trigName, myData, transition): if transition.scene().saveStateVer == 1: # labels restored by Animate return # deregister from scene-restore trigger while animation # ongoing to avoid duplicative state restoration tn = chimera.SCENE_TOOL_RESTORE chimera.triggers.deleteHandler(tn, self._handlerIDs[tn]) del self._handlerIDs[tn] from Arrows import ArrowsModel self._handlerIDs[chimera.ANIMATION_TRANSITION_STEP] = chimera.triggers.addHandler( chimera.ANIMATION_TRANSITION_STEP, self._animStep, (self.sessionInfo(), ArrowsModel().getRestoreInfo())) self._alphasCached = False ArrowsModel()._animStart(transition) def _animStep(self, trigName, startData, transition): labelStartData, arrowStartData = startData animMapping = self._establishAnimMapping(labelStartData, transition) where = transition.frameCount / float(transition.frames) endData = transition.scene().tool_settings.get("2D Labels (labels)", None) for label, change in animMapping.items(): start, end = change if start is not None: start = labelStartData['labels'][labelStartData['labelIDs'].index(start)] if end is not None: end = endData['labels'][endData['labelIDs'].index(end)] label.animInterp(start, where, end) if self.labels: self.setMajorChange() from Arrows import ArrowsModel ArrowsModel()._animStep(arrowStartData, transition) def _animFinish(self, trigName, myData, transition): if transition.scene().saveStateVer == 1: # labels restored by Animate return endData = transition.scene().tool_settings.get("2D Labels (labels)", None) if endData: endIDs = set(endData['labelIDs']) else: endIDs = set() removals = [] for labelID, label in self.labelMap.items(): if labelID not in endIDs: removals.append(label) for label in removals: self.removeLabel(label) if endData: endDataMap = dict(zip(endData['labelIDs'], endData['labels'])) for labelID, label in self.labelMap.items(): labelEndData = endDataMap[labelID] label.shown = labelEndData["shown"] label.opacity = labelEndData["opacity"] from Arrows import ArrowsModel ArrowsModel()._animFinish(transition) tn = chimera.ANIMATION_TRANSITION_STEP chimera.triggers.deleteHandler(tn, self._handlerIDs[tn]) del self._handlerIDs[tn] self._handlerIDs[chimera.SCENE_TOOL_RESTORE] = chimera.triggers.addHandler( chimera.SCENE_TOOL_RESTORE, self._restoreScene, None) if not chimera.nogui: updateGUI() def _establishAnimMapping(self, startData, transition): animMapping = startData.setdefault('animMapping', {}) startIDs = dict(zip(startData['labelIDs'], startData['labels'])) endData = transition.scene().tool_settings.get("2D Labels (labels)", None) if endData: endIDs = dict(zip(endData['labelIDs'], endData['labels'])) else: endIDs = {} deletions = [] for curID, curLabel in self.labelMap.items(): if curID not in startIDs and curID not in endIDs: deletions.append(curLabel) for delLabel in deletions: self.removeLabel(delLabel) nearStart = transition.frameCount < 0.5 * transition.frames for startID, labelData in startIDs.items(): if startID in endIDs: change = (startID, startID) else: change = (startID, None) try: label = self.labelMap[startID] except KeyError: label = self.newLabel(*labelData["args"], labelID=startID) if nearStart or startID not in endIDs: label.restoreSession(labelData) else: label.restoreSession(endIDs[startID]) animMapping[label] = change for endID, labelData in endIDs.items(): if endID in startIDs: continue try: label = self.labelMap[endID] except KeyError: label = self.newLabel(*labelData["args"], labelID=endID) label._restoreSession(labelData) animMapping[label] = (None, endID) return animMapping def _restoreScene(self, trigName, myData, scene): if scene.saveStateVer == 1: # labels restored by Animate return self.destroy() il = LabelsModel() il.restoreSession(scene.tool_settings.get('2D Labels (labels)', None)) from Arrows import ArrowsModel ArrowsModel()._restoreScene(scene) if chimera.nogui: return updateGUI() from chimera import dialogs, openModels from gui import IlabelDialog if '2D Labels (gui)' in scene.tool_settings: dlg = dialogs.display(IlabelDialog.name) dlg._restoreScene(scene) else: dlg = dialogs.find(IlabelDialog.name) if dlg: openModels.close([dlg.keyModel]) dlg.Close() dlg.destroy() def restoreSession(self, info): if info is None: # scene with no labels return for labelInfo in info["labels"]: new_label = Label(*(labelInfo["args"] + (self,)), **labelInfo.get('kw', {})) self.labels.append(new_label) self.labels[-1]._restoreSession(labelInfo) if info.has_key("labelUID"): self._UID = info["labelUID"] for idx,label in enumerate(self.labels): if info.has_key("labelIDs"): uid = info["labelIDs"][idx] else: uid = self._getLabelID() self.labelMap[uid] = label if info["curLabel"] is None: self.curLabel = None else: self.curLabel = self.labels[info["curLabel"]] # backwards compatibility... _restoreSession = restoreSession def _saveScene(self, trigName, myData, scene): scene.tool_settings['2D Labels (labels)'] = self.sessionInfo() def _saveSession(self, triggerName, myData, sessionFile): print>>sessionFile, """ try: import Ilabel il = Ilabel.LabelsModel(create=False) if il: il.destroy() il = Ilabel.LabelsModel() il.restoreSession(%s) del Ilabel, il except: reportRestoreError("Error restoring IlabelModel instance in session") """ % repr(self.sessionInfo()) def sessionInfo(self): info = {} info["labels"] = [l._sessionInfo() for l in self.labels] id_list = [] for l in self.labels: ## try to find the label as a value in the labelMap, ## and add the key to id_list for k,v in self.labelMap.items(): if v == l: id_list.append(k) break ## this case should never happen, but if labelMap ## somehow became corrupted, come up with a new ## id for this label, and add append it to id_list else: id_list.append(self._getLabelID()) info["labelIDs"] = id_list info["labelUID"] = self._UID if self.curLabel: info["curLabel"] = self.labels.index(self.curLabel) else: info["curLabel"] = None return info def processLabel2DCmd(action, itemID, text=None, color=None, size=None, style=None, typeface=None, xpos=None, ypos=None, visibility=None, frames=None, start=None, end=None, weight=None, head=None, bgColor=False, outline=None, margin=None): from Midas import MidasError if itemID == "*": # operate on all existing import inspect argNames, varargName, varKwName, vals = inspect.getargvalues(inspect.currentframe()) recurseKw = dict([(n, vals[n]) for n in argNames if n not in ('action', 'itemID')]) if action[0] == "a": from Arrows import ArrowsModel, Arrow aModel = ArrowsModel() itemIDs = aModel.arrows else: model = LabelsModel() # create if necessary itemIDs = model.labelMap.keys() for itemID in itemIDs[:]: processLabel2DCmd(action, itemID, **recurseKw) return if action[0] == "a": from Arrows import ArrowsModel, Arrow aModel = ArrowsModel() else: if type(xpos) == list or type(ypos) == list: raise MidasError("Multiple occurances of 'xpos' or 'ypos' keywords") try: if xpos: xpos + 0 if ypos: ypos + 0 except: raise MidasError("xpos/ypos values must be numeric") model = LabelsModel() # create if necessary if text is not None: if isinstance(text, basestring): if not isinstance(text, unicode): # text directly from command line will be # unicode, but text from a script file # may be UTF-8 (if non-ASCII label) text = text.decode('utf8') else: # text might have evaluated as an integer or something text = unicode(text) # backslash interpretation... if '\\' in text: if '"' not in text: text = eval('u"' + text + '"') elif "'" not in text: text = eval("u'" + text + "'") if bgColor not in (False, None): bgColor = bgColor.rgba() labelKw = {} if bgColor is not False: labelKw['background'] = bgColor if margin is not None: labelKw['margin'] = margin if outline is not None: labelKw['outline'] = outline if action == "create": ## get the position if xpos==None: xpos = .5 if ypos==None: ypos = .5 if not color: color = chimera.Color.lookup('white') if not size: size = 24 if not style: style = "normal" if not typeface: typeface = "sans serif" ## make a new label try: label = model.newLabel( (xpos,ypos), itemID ) except RuntimeError, what: raise MidasError, what ## set it's content label.set(text or '') ## color and size the characters label.changeAttrs(size, color, styleLookup(style), typefaceLookup(typeface), **labelKw) if visibility == "hide": label.shown = False if not chimera.nogui: from chimera.tkgui import app app.rapidAccess.shown = False elif action == "change": if not model.labelMap.has_key(itemID): raise MidasError, "No such label with ID '%s'" % itemID label = model.labelMap[itemID] if text!=None: cur_size = None cur_color = None cur_style = None cur_typeface = None if (len(label.lines) > 0) and \ (len(label.lines[0]) > 0): c = label.lines[0][0] cur_color = c.rgba cur_size = c.size cur_style = c.style cur_typeface = c.fontName label.set(text) if cur_color: cur_color = chimera.MaterialColor(*cur_color) label.changeAttrs(cur_size, cur_color, cur_style, cur_typeface, **labelKw) if color or size or style or typeface or labelKw: if style: style = styleLookup(style) if typeface: typeface = typefaceLookup(typeface) label.changeAttrs(size, color, style, typeface, **labelKw) if (xpos!=None) or (ypos!=None): cur_xpos, cur_ypos = label.pos if xpos==None: xpos = cur_xpos if ypos==None: ypos = cur_ypos label.pos = (xpos, ypos) if visibility: from Midas import _addMotionHandler global _tickMotionHandler from Midas import _tickMotionHandler param = {'command':'fade','label':label, 'frames':frames} ## need step, frames if visibility == 'show': if not frames: label.shown = True else: ## should show start from 0? Is wierd ## when someone 'shows' a label that is ## already shown label.changeAttrs(opacity=0.0) label.shown = True step = 1.0 / frames param['step'] = step _addMotionHandler(_fade, param) elif visibility == 'hide': if not frames: label.shown = False #label.changeAttrs(opacity=0.0) else: step = 1.0 / frames param['step'] = -(step) _addMotionHandler(_fade, param) else: raise MidasError, "No such visibility mode '%s'" % visibility elif action == "delete": if not model.labelMap.has_key(itemID): raise MidasError, "No such label with ID '%s'" % itemID label = model.labelMap[itemID] model.removeLabel(label) elif action == "read": readFiles([itemID]) elif action == "write": writeFile(itemID) elif action in ["acreate", "arrowcreate"]: if isinstance(itemID, Arrow): raise MidasError("Cannot use '*' as ID in arrowcreate") if itemID in [a.ident for a in aModel.arrows]: raise MidasError("Arrow with ID %s already exists!" % itemID) kw = {'ident': itemID} args = [] for arg in ("start", "end"): args.append(_getArrowStartEnd(eval(arg))) if not color: color = chimera.Color.lookup('white') kw['color'] = color if weight is not None: try: weight = float(weight) except ValueError: raise MidasError("Weight value must be numeric") kw['weight'] = weight if head is not None: if head not in Arrow.headStyles: raise MidasError("Arrowhead style must be one of: %s" ", ".join(Arrow.headStyles)) kw['head'] = head if visibility == 'hide': kw['shown'] = False aModel.addArrow(*args, **kw) if not chimera.nogui: from chimera.tkgui import app app.rapidAccess.shown = False elif action in ["achange", "arrowchange"]: if isinstance(itemID, Arrow): arrow = itemID else: for arrow in aModel.arrows: if arrow.ident == itemID: break else: raise MidasError("No such arrow with ID '%s'" % itemID) if color: try: arrow.color = color except ValueError, v: raise MidasError(str(v)) if weight is not None: try: arrow.weight = weight except ValueError, v: raise MidasError(str(v)) if head is not None: try: arrow.head = head except ValueError, v: raise MidasError(str(v)) if start is not None: arrow.start = _getArrowStartEnd(start) if end is not None: arrow.end = _getArrowStartEnd(end) if visibility: from Midas import _addMotionHandler global _tickMotionHandler from Midas import _tickMotionHandler param = {'command': 'fade', 'arrow': arrow, 'frames': frames} ## need step if visibility == 'show': if not frames: arrow.shown = True else: ## should show start from 0? Is wierd ## when someone 'shows' an arrow that is ## already shown arrow.opacity = 0.0 arrow.shown = True step = 1.0 / frames param['step'] = step _addMotionHandler(_fadeArrow, param) elif visibility == 'hide': if not frames: arrow.shown = False else: step = 1.0 / frames param['step'] = -(step) _addMotionHandler(_fadeArrow, param) else: raise MidasError("No such visibility mode '%s'" % visibility) elif action in ["adelete", "arrowdelete"]: if isinstance(itemID, Arrow): arrow = itemID else: for arrow in aModel.arrows: if arrow.ident == itemID: break else: raise MidasError("No such arrow with ID '%s'" % itemID) aModel.removeArrow(arrow) else: raise MidasError("Unknown 2dlabels action '%s'" % action) if action[0] != "a": model.setMajorChange() if not chimera.nogui: updateGUI() def _getArrowStartEnd(arg): rawVal = arg if rawVal.count(',') != 1: raise MidasError("start/end values must be two comma-separated" " numbers (with no spaces)") try: x, y = [float(v) for v in rawVal.split(',')] except: raise MidasError("start/end values must be two comma-separated" " numbers (with no spaces)") return (x,y) def updateGUI(): from gui import IlabelDialog from chimera import dialogs dlg = dialogs.find(IlabelDialog.name) if dlg: dlg.updateGUI("command") def contrastWith(rgb): if rgb[0]*2 + rgb[1]*3 + rgb[2] < 0.417: return (1, 1, 1) else: return (0, 0, 0) def contrastWithBG(): bg = chimera.viewer.background if bg: bgColor = bg.rgba() else: bgColor = (0, 0, 0) return contrastWith(bgColor) def readFiles(fileNames, clear=True): from Arrows import _arrowsModel, ArrowsModel, Arrow if clear: lm = LabelsModel(create=False) if lm: for label in lm.labels[:]: lm.removeLabel(label) lm.setMajorChange() am = ArrowsModel(create=False) if am: for arrow in am.arrows[:]: am.removeArrow(arrow) from chimera import UserError for fileName in fileNames: from OpenSave import osOpen f = osOpen(fileName) for ln, line in enumerate(f): lineNum = ln + 1 if not line.strip() or line.strip().startswith('#'): # skip blank lines / comments continue if line.lower().startswith("label"): labelID = line[5:].strip() LabelsModel().setMajorChange() text = label = None itemType = "label" continue if line.lower().startswith("arrow"): arrowArgs = [] arrowKw = {} arrowID = line[5:].strip() if arrowID: arrowKw['ident'] = arrowID itemType = "arrow" continue if line[0] != '\t': f.close() raise UserError("%s, line %d: line must start with 'Label'," " 'Arrow', or tab" % (fileName, lineNum)) try: semi = line.index(':') except ValueError: f.close() raise UserError("%s, line %d: line must have semi-colon" % (fileName, lineNum)) name = line[1:semi].lower() value = line[semi+1:].strip() if itemType == "label": if not label and name != "(x,y)": f.close() raise UserError("%s, line %d: xy position must immediately" " follow 'Label' line" % (fileName, lineNum)) if label and text is None and name != "text": f.close() raise UserError("%s, line %d: text must immediately" " follow xy position" % (fileName, lineNum)) if name == "(x,y)": text = None try: pos = eval(value) except: f.close() raise UserError("%s, line %d: could not parse xy value" % (fileName, lineNum)) if labelID: label = LabelsModel().newLabel(pos, labelID=labelID) else: label = LabelsModel().newLabel(pos) elif name == "text": try: text = eval(value) except: f.close() LabelsModel().removeLabel(label) raise UserError("%s, line %d: could not parse 'text' value" % (fileName, lineNum)) label.set(text) elif name == "shown": if name == "shown": try: label.shown = eval(value.capitalize()) except: f.close() LabelsModel().removeLabel(label) raise UserError("%s, line %d: could not parse 'shown'" " value" % (fileName, lineNum)) else: chars = [] for l in label.lines: chars.extend(l) if '),' in value: values = value.split('),') for i, v in enumerate(values[:-1]): values[i] = v + ')' elif ',' in value and not value.strip().startswith('('): values = value.split(',') else: values = [value] * len(chars) if len(values) != len(chars): f.close() raise UserError("%s, line %d: number of values not equal" " to numbers of characters in text (and not a single" " value)" % (fileName, lineNum)) if name.startswith("font size"): try: values = [eval(v) for v in values] except: f.close() LabelsModel().removeLabel(label) raise UserError("%s, line %d: could not parse" " 'font size' value(s)" % (fileName, lineNum)) for c, v in zip(chars, values): c.size = v elif name.startswith("font style"): for c, v in zip(chars, values): try: c.style = styleLookup(v.strip().lower()) except: f.close() LabelsModel().removeLabel(label) raise UserError("%s, line %d: could not parse" " 'font style' value(s)" % (fileName, lineNum)) elif name.startswith("font typeface"): for c, v in zip(chars, values): try: c.fontName = typefaceLookup(v.strip().lower()) except: f.close() LabelsModel().removeLabel(label) raise UserError("%s, line %d: could not parse" " 'font typeface' value(s)" % (fileName, lineNum)) elif name.startswith("color"): try: values = [eval(v) for v in values] except: f.close() LabelsModel().removeLabel(label) raise UserError("%s, line %d: could not parse" " 'color' value(s)" % (fileName, lineNum)) for c, v in zip(chars, values): c.rgba = v else: LabelsModel().removeLabel(label) raise UserError("%s, line %d: unknown label attribute '%s'" % (fileName, lineNum, name)) else: # arrow if name in ["start", "end"]: if len(arrowArgs) > 1: raise UserError("%s, line %d: start/end values already" " given for this arrow" % (fileName, lineNum)) try: x, y = [float(v) for v in eval(value)] except: raise UserError("%s, line %d: could not parse" " %s xy value" % (fileName, lineNum, name)) if name == "start": if arrowArgs: raise UserError("%s, line %d: start value already" " given for this arrow" % (fileName, lineNum)) arrowArgs.append((x,y)) else: if not arrowArgs: raise UserError("%s, line %d: no start value yet" " given for this arrow" % (fileName, lineNum)) arrowArgs.append((x,y)) arrow = ArrowsModel().addArrow(*arrowArgs, **arrowKw) else: if len(arrowArgs) != 2: raise UserError("%s, line %d: start/end values not yet" " both given for this arrow" % (fileName, lineNum)) if name == "shown": try: shown = eval(value.capitalize()) except: raise UserError("%s, line %d: could not parse %s value" % (fileName, lineNum, name)) arrow.shown = shown elif name == "weight": try: weight = float(value) except ValueError: raise UserError("%s, line %d: value for weight ('%s')" " for arrow %s must be numeric." % (fileName, lineNum, value, name)) arrow.weight = weight elif name == "head": if value not in Arrow.headStyles: raise UserError("%s, line %d: value for head style" " ('%s') for arrow %s must be one of %s." % (fileName, lineNum, value, name, ", ".join(Arrow.headStyles))) arrow.head = value elif name == "color": try: r,g,b,a = [float(v) for v in eval(value)] except: raise UserError("%s, line %d: could not parse" " %s value" % (fileName, lineNum, name)) arrow.color = (r,g,b,a) else: raise UserError("%s, line %d: unknown arrow attribute '%s'" % (fileName, lineNum, name)) f.close() if chimera.nogui: dlg = None else: from gui import IlabelDialog from chimera import dialogs dlg = dialogs.find(IlabelDialog.name) if dlg: dlg.updateGUI("file") FONT_STYLE_LABELS = ["normal", "italic", "bold", "bold italic"] oglFont = chimera.OGLFont FONT_STYLE_VALUES = [oglFont.normal, oglFont.italic, oglFont.bold, oglFont.bold | oglFont.italic] def styleLookup(label): try: return FONT_STYLE_VALUES[FONT_STYLE_LABELS.index(label)] except ValueError: from Midas import MidasError raise MidasError("No known font style '%s'; choices are: %s" % (label, ", ".join(FONT_STYLE_LABELS))) FONT_TYPEFACE_LABELS = ["sans serif", "serif", "fixed"] FONT_TYPEFACE_VALUES = ["Sans Serif", "Serif", "Fixed"] def typefaceLookup(label): try: return FONT_TYPEFACE_VALUES[FONT_TYPEFACE_LABELS.index(label)] except ValueError: from Midas import MidasError raise MidasError("No known font typeface '%s'; choices are: %s" % (label, ", ".join(FONT_TYPEFACE_LABELS))) def writeFile(fileName): from OpenSave import osOpen f = osOpen(fileName, 'w') lm = LabelsModel(create=False) if lm: labelIDs = lm.labelMap.keys() labelIDs.sort() for labelID in labelIDs: if labelID.startswith(AUTO_ID_PREFIX): print>>f, "Label" else: print>>f, "Label %s" % labelID label = lm.labelMap[labelID] print>>f, "\t(x,y): %s" % repr(label.pos) print>>f, "\ttext: %s" % repr(unicode(label)) print>>f, "\tshown: %s" % label.shown print>>f, "\tfont size(s): %s" % _valuesString([str(c.size) for l in label.lines for c in l]) print>>f, "\tfont style(s): %s" % _valuesString([ FONT_STYLE_LABELS[FONT_STYLE_VALUES.index(c.style)] for l in label.lines for c in l]) print>>f, "\tfont typeface(s): %s" % _valuesString([ FONT_TYPEFACE_LABELS[FONT_TYPEFACE_VALUES.index(c.fontName)] for l in label.lines for c in l]) print>>f, "\tcolor(s): %s" % _valuesString([repr(c.rgba) for l in label.lines for c in l]) from Arrows import _arrowsModel, ArrowsModel if _arrowsModel: for arrow in ArrowsModel().arrows: if arrow.ident: print>>f, "Arrow %s" % arrow.ident else: print>>f, "Arrow" print>>f, "\tstart: %s" % repr(arrow.start) print>>f, "\tend: %s" % repr(arrow.end) print>>f, "\tshown: %s" % arrow.shown print>>f, "\tweight: %g" % arrow.weight print>>f, "\thead: %s" % arrow.head print>>f, "\tcolor: %s" % repr(arrow.color) f.close() def _fade(trigger, param, triggerData): if trigger: _tickMotionHandler(param) step = param['step'] label = param['label'] label.changeAttrs(opacity=label.opacity+step) LabelsModel().setMajorChange() if param['frames'] == 0: if step < 0: ## means this is the last iteration of a fade to black ## after you fade it, need to set it back to former opacity !! label.shown = False label.opacity = 1.0 if not chimera.nogui: updateGUI() def _fadeArrow(trigger, param, triggerData): if trigger: _tickMotionHandler(param) step = param['step'] arrow = param['arrow'] arrow.opacity += step if step < 0 and param['frames'] == 0: arrow.shown = False arrow.opacity = 1.0 # change GUI shown status also from gui import IlabelDialog from chimera import dialogs dlg = dialogs.find(IlabelDialog.name) if dlg: dlg.arrowTable.refresh() def _valuesString(values): if len(set(values)) == 1: return values[0] return ", ".join(values) def keyCmd(cmdName, args): # syntax: makekey llx,lly urx,ury kw1 val1 kw2 val2 ... label1 color1 label2 color2 ... from Midas import MidasError, convertColor from Midas.midas_text import parseColorName from ColorKey import KeyModel from ColorKey import getKeyModel km = getKeyModel() if cmdName.startswith("un"): # remove key km.setKeyPosition(None) return try: xy1, xy2, args = args.split(None, 2) except ValueError: raise MidasError("Not enough arguments; type 'help %s' for more info" % cmdName) xys = [] for xy in [xy1, xy2]: try: x, y = xy.split(',') except ValueError: raise MidasError("Key corner positions must be specified as x,y" " with no space after comma") try: x, y = float(x), float(y) except ValueError: raise MidasError("Key corner xy positions must be numeric") if not (0 <= x <= 1 and 0 <= y <= 1): raise MidasError("Key corner xy positions must be in the range 0-1") xys.append((x,y)) keyAttrInfo = [] while args: try: kw, valPlusArgs = args.split(None, 1) except ValueError: raise MidasError("Not enough arguments;" " type 'help %s' for more info" % cmdName) origKw = kw kw = kw.lower() attrNames = ("borderColor", "borderWidth", "colorTreatment", "fontSize", "fontStyle", "fontTypeface", "justification", "labelColor", "labelOffset", "labelSide", "numLabelSpacing", "tickLength", "tickMarks", "tickThickness") colorAttrs = set(["borderColor", "labelColor"]) intAttrs = set(["borderWidth", "fontSize", "labelOffset", "tickLength", "tickThickness"]) boolAttrs = set(["tickMarks"]) enumAttrs = { "colorTreatment": (KeyModel.colorTreatmentValues, KeyModel.colorTreatmentValues), "fontStyle": (FONT_STYLE_LABELS, FONT_STYLE_VALUES), "fontTypeface": (FONT_TYPEFACE_LABELS, FONT_TYPEFACE_VALUES), "justification": (KeyModel.justificationValues, KeyModel.justificationValues), "labelSide": (KeyModel.labelSideValues, KeyModel.labelSideValues), "numLabelSpacing": (KeyModel.numLabelSpacingValues, KeyModel.numLabelSpacingValues) } matches = [] for attrName in attrNames: if attrName.lower().startswith(kw): matches.append(attrName) if not matches: break elif len(matches) > 1: raise MidasError("Keyword '" + origKw + "' not long enough;" " could be either " + ", ".join(matches[:-1]) + " or " + matches[-1]) kw = matches[0] if kw in colorAttrs: cn, args = parseColorName(valPlusArgs) val = convertColor(cn) if val: val = val.rgba() elif kw in intAttrs: try: iv, args = valPlusArgs.split(None, 1) except ValueError: raise MidasError("No labels/colors given for key") try: val = int(iv) except ValueError: raise MidasError("Value for %s keyword must be an integer" % kw) elif kw in boolAttrs: try: bv, args = valPlusArgs.split(None, 1) except ValueError: raise MidasError("No labels/colors given for key") if bv.lower() not in ["true", "false"]: raise MidasError("Value for '" + kw + "' keyword must be" " 'true' or 'false'") val = eval(bv.capitalize()) elif kw in enumAttrs: labels, values = enumAttrs[kw] try: fv, args = valPlusArgs.split(None, 1) except ValueError: raise MidasError("No labels/colors given for key") for label, val in zip(labels, values): undashed = fv.replace('-', ' ') if undashed == label: break else: raise MidasError("No such value for " + kw + " keyword '" + kw + "';" + " choices are: " + ", ".join( [l.replace(' ', '-') for l in labels])) else: raise AssertionError("Unexpected non-match for keyword '%s'" % kw) keyAttrInfo.append((kw, val)) if not args: raise MidasError("No labels/colors given for key") from Midas.midas_text import parseColorName, findQuoted from Midas import convertColor rgbasLabels = [] while args: if args[0] in ['"', "'"]: label, args = findQuoted(args) label = label[1:-1] if not args: raise MidasError("No color specified for label '%s'" % label) else: try: label, args = args.split(None, 1) except ValueError: raise MidasError("No color specified for label '%s'" % args) cn, args = parseColorName(args) color = convertColor(cn, noneOkay=False) rgbasLabels.append((color.rgba(), label)) if len(rgbasLabels) < 2: raise MidasError("Must provide at least two labels and corresponding" " colors") km.reset() for kw, val in keyAttrInfo: setFunc = 'set' + kw[0].upper() + kw[1:] getattr(km, setFunc)(val) km.setRgbasAndLabels(rgbasLabels) km.setKeyPosition(xys)