''' See more at: http://www.pymolwiki.org/index.php/annocryst ###################################################### # # AnnoCryst for PyMOL # # Anna Gerber # email: agerber@itee.uq.edu.au # # Copyright 2008 eResearch, ITEE, # The University of Queensland ###################################################### ''' from __future__ import absolute_import from __future__ import print_function import Pmw import sys import webbrowser from pymol import cmd from xml.dom.minidom import parseString from datetime import datetime import platform import os home = os.path.expanduser('~') if sys.version_info[0] < 3: import urllib2 from urllib2 import URLError, HTTPError from idlelib.TreeWidget import TreeItem, TreeNode from Tkinter import PhotoImage else: import urllib.request as urllib2 from urllib.error import URLError, HTTPError from idlelib.tree import TreeItem, TreeNode from tkinter import PhotoImage def __init__(self): self.annotationService = None self.menuBar.addmenuitem('Plugin', 'command', 'AnnoCryst', label='AnnoCryst', command=lambda s=self: createAnnotationService(s)) cmd.extend('annotate', lambda s=self: annotateFromCmd(s)) cmd.extend('annotations', lambda model, s=self: showAllAnnotations(model, s)) cmd.extend('remoteurl', lambda pdbURL, s=self: readRemoteURL(pdbURL, s)) cmd.extend('remotepdb', lambda pdbCode, s=self: readRemotePDB(pdbCode, s)) def createAnnotationService(app): if (app.annotationService == None): app.annotationService = AnnotationService(app) else: app.annotationService.dialog.show() def showAllAnnotations(model, app): if (app.annotationService == None): createAnnotationService(app) app.annotationService.showAllAnnotations(model) def annotateFromCmd(app): # selection, type, description, if (app.annotationService == None): createAnnotationService(app) app.annotationService.annotate() # Load a remote file: specify the full URL def readRemoteURL(pdbURL, app): if (app.annotationService == None): createAnnotationService(app) app.annotationService.openRemote(pdbURL) # Load a remote file: specify the pdb code only def readRemotePDB(pdbCode, app): if (app.annotationService == None): createAnnotationService(app) app.annotationService.openRemoteByPDBCode(pdbCode) class AnnotationService: def __init__(self, app): print("\nWrite the following in the PyMOL command window:") print("remotepdb 3ait") parent = app.root self.parent = parent filepath = home if platform.system() == 'Windows': if filepath == '' or filepath == None: filepath = "C:\\" if filepath[-1] != "\\": filepath += "\\" if platform.system() == 'Linux': if filepath == '' or filepath == None: filepath = "~/" if filepath[-1] != "/": filepath += "/" self.settingsFile = "%sannocryst-settings.xml" % filepath self.createSettingsDialog() self.createMainWindow() self.selection = "sele" if (len(cmd.get_names('selections')) == 0): cmd.select("all") self.loadedOntology = None self.loadedModels = {} self.selectedText = "" self.selectedAnno = None self.annotationsLoaded = False def createMainWindow(self): # constructs the main AnnoCryst window self.dialog = Pmw.Dialog(self.parent, buttons=('AnnoCryst Settings', 'Exit'), title = 'AnnoCryst', command = self.handleMainWindowButtons) self.notebook = Pmw.NoteBook(self.dialog.interior(), raisecommand=self.refreshAnnotationView) self.notebook.component('hull').configure(height=380, width=400) self.notebook.pack(fill='both', expand=1, padx=10, pady=10) self.status = Pmw.ScrolledText(self.dialog.interior(), labelpos='w', label_text='Status: ', usehullsize=1, hull_width=380, hull_height=20) self.status.configure(text_state='disabled') # self.status.component('text').configure(relief='flat', # background='SystemMenu') self.status.component('text').configure(relief='flat', background='white') self.status.pack(padx=5, pady=5, fill='both', expand=1) page = self.notebook.add('Open Model') self.remoteURL = Pmw.EntryField(page, labelpos='nw', label_text='URL of the model to open in AnnoCryst:', value='') self.remoteURL.pack(fill='x', padx=4, pady=1) openButtonBox = Pmw.ButtonBox(page, hull_width=100, hull_height=20) openButtonBox.pack() openButtonBox.add('Open', command=self.openRemote) # openButtonBox.setdefault('Open') page = self.notebook.add("Browse Annotations") self.current = Pmw.ScrolledText(page, usehullsize=1, hull_width=380, hull_height=30) self.current.configure(text_state='disabled') # self.current.component('text').configure(relief='flat', # background='SystemMenu') self.current.component('text').configure(relief='flat', background='white') self.current.pack(padx=5, pady=5, fill='both', expand=1) self.tree_item = AnnotationTreeItem("", isTopLevel=True) self.sc = Pmw.ScrolledCanvas(page, borderframe=1, usehullsize=1, hull_width=400, hull_height=270) self.sc.pack(fill='x', padx=4, pady=1) self.sc.interior().config(bg="white") self.node = AnnotationTreeNode(self.sc.component('canvas'), None, self.tree_item) self.node.update() self.node.expand() self.browseButtonBox = Pmw.ButtonBox(page, hull_width=200, hull_height=20) self.browseButtonBox.add('Refresh annotations', command=self.refreshAnnotationView) self.browseButtonBox.add('Copy text to clipboard', command=self.copyText) self.browseButtonBox.add('Delete annotation', command=self.deleteAnnotation) self.browseButtonBox.pack() page = self.notebook.add('Annotate') group = Pmw.Group(page, tag_text='Title and Type') group.pack(fill='both', expand=1, padx=10, pady=5) self.title = Pmw.EntryField(group.interior(), labelpos='w', label_text='Title: ') self.title.pack(fill='x', padx=4, pady=1) self.type = Pmw.ComboBox(group.interior(), labelpos='w', label_text='Type:', scrolledlist_items=('Comment', 'Rating', 'Question', \ 'SeeAlso', 'Feedback', 'Reference', 'Keyword'), selectioncommand = self.updateDescriptionUI, entryfield_entry_state="readonly") self.type.pack(fill='x', padx=4, pady=1) self.type.selectitem('Comment') self.descgroup = Pmw.Group(page, tag_text='Description') self.descgroup.pack(fill='both', expand=1, padx=10, pady=5) # TODO: support for policies #group = Pmw.Group(page,tag_text='Access Control (optional)') #group.pack(fill = 'both', expand = 1, padx = 10, pady = 5) self.description = Pmw.ScrolledText(self.descgroup.interior(), usehullsize=1, hull_width=400, hull_height=100) self.kwdescription = Pmw.ScrolledText(self.descgroup.interior(), usehullsize=1, hull_width=400, hull_height=30) self.ontologyBrowser = Pmw.ScrolledCanvas(self.descgroup.interior(), usehullsize=1, hull_width=250, hull_height=100, borderframe=1) self.ontologyBrowser.interior().config(bg="white") self.addKeywordButtonBox = Pmw.ButtonBox(self.descgroup.interior(), hull_width=50, hull_height=20) self.addKeywordButtonBox.add('Add Keyword', command=self.addKeyword) self.keyword = Pmw.ScrolledText(self.descgroup.interior(), labelpos='w', label_text='Keywords: ', usehullsize=1, hull_height=40, hull_width=380) self.seeAlsoNotebook = Pmw.NoteBook(self.descgroup.interior()) self.seeAlsoNotebook.component('hull').configure(height=100, width=400) seeAlsoPage = self.seeAlsoNotebook.add('External') self.seeAlsoExternalURL = Pmw.EntryField(seeAlsoPage, labelpos='w', label_text='URL: ') self.seeAlsoExternalURL.pack(fill='x', padx=4, pady=1) seeAlsoPage = self.seeAlsoNotebook.add('Local') self.seeAlsoLocalFile = Pmw.EntryField(seeAlsoPage, labelpos='w', label_text='File: ', value='File upload not yet implemented') self.seeAlsoLocalFile.pack(fill='x', padx=4, pady=1) self.updateDescriptionUI(self.type) self.annotateButtonBox = Pmw.ButtonBox(page, hull_height=20, hull_width=200) self.annotateButtonBox.pack() self.annotateButtonBox.add('Reset', command=self.reset) self.annotateButtonBox.add('Annotate', command=self.annotate) # self.annotateButtonBox.setdefault('Annotate') self.parent.focus_set() self.annotateButtonBox.alignbuttons() self.dialog.show() def createSettingsDialog(self): # constructs a dialog for changing the settings self.loadSettings() self.settingsDialog = Pmw.Dialog(self.parent, buttons=('Cancel', 'Save'), defaultbutton = 'Save', title = 'AnnoCryst Settings', command = self.saveSettings, deactivatecommand = self.saveSettings) attrs = list(self.settings.keys()) attrs.sort() for att in attrs: entryfield = Pmw.EntryField(self.settingsDialog.interior(), labelpos='w', label_text='%s: ' % att, value=self.settings[att], entry_width=80) entryfield.pack(fill='x', padx=4, pady=1) setattr(self, att, entryfield) self.settingsDialog.withdraw() def saveSettings(self, result='Cancel'): if result == 'Save': try: settingsStr = "\n" for k in self.settings.keys(): newvalue = getattr(self, k).getvalue() self.settings[k] = newvalue settingsStr += "<%s>%s\n" % (k, newvalue, k) settingsStr += "\n" settingsfile = open(self.settingsFile, 'w') settingsfile.write(settingsStr) settingsfile.close() print("Settings saved in %s" % self.settingsFile) except: print("Unable to save settings") else: for k in self.settings.keys(): entryfield = getattr(self, k) entryfield.setvalue(self.settings[k]) self.settingsDialog.withdraw() def loadSettings(self): # default settings self.settings = {\ 'keywordOntologyURL': "http://maenad.itee.uq.edu.au/agerber/po.owl", 'keywordOntologyNamespace': "http://www.proteinontology.info/po.owl", 'annotationServerURL': "http://maenad.itee.uq.edu.au:8080/Annotea/AnnoteaServlet", 'uploadServerURL': "http://maenad.itee.uq.edu.au:8080/Annotea/FileUploadServlet", 'username': "Anonymous", 'pdbRepositoryURL': "http://maenad.itee.uq.edu.au:8080/harvanapdb/au.edu.uq.itee.eresearch.harvana.gwt.Main/pdb/" } try: settings = open(self.settingsFile, "r") dom = parseString(settings.read()) for k in self.settings.keys(): elems = dom.getElementsByTagName(k) if len(elems) > 0 and len(elems[0].childNodes) > 0: self.settings[k] = elems[0].childNodes[0].nodeValue except: print("Unable to read settings from %s, using AnnoCryst defaults" % self.settingsFile) def handleMainWindowButtons(self, result): # hide or show UI dialogs if result == 'AnnoCryst Settings': self.settingsDialog.show() else: self.dialog.withdraw() def openRemoteByPDBCode(self, pdbCode=''): pdbURL = self.pdbRepositoryURL.getvalue() + pdbCode + ".pdb" self.openRemote(pdbURL) # button actions for open page def openRemote(self, pdbURL=''): # load a model from a URL self.status.setvalue("Loading model, please wait...") try: if pdbURL == '': pdbURL = self.remoteURL.getvalue() else: self.remoteURL.setvalue(pdbURL) httpRequest = urllib2.Request(url=pdbURL) pdbHttpHandle = urllib2.urlopen(httpRequest) pdbStr = pdbHttpHandle.read() modelName = str(pdbURL).split("/")[-1].split(".")[0] cmd.read_pdbstr(pdbStr, modelName) self.status.setvalue(modelName + " loaded") self.loadedModels[modelName] = pdbURL cmd.disable('all') cmd.enable(modelName) except: self.status.setvalue("Unable to load model " + pdbURL) print("Unable to load model %s:" % pdbURL, sys.exc_info()[0]) # button actions for browse page def copyText(self): copyButton = self.browseButtonBox.button("Copy text to clipboard") copyButton.clipboard_clear() copyButton.clipboard_append(self.selectedText, type='STRING') def deleteAnnotation(self): anno = self.selectedAnno if anno != None and anno != '': try: req = RequestWithMethod(anno, method="DELETE") response = urllib2.urlopen(req) self.showAllAnnotations() self.status.setvalue("Annotation deleted") except: print("Unable to delete annotation") self.status.setvalue("Unable to delete annotation") # button actions for annotate page def reset(self): # clears annotation fields self.title.setvalue('') self.description.setvalue('') self.kwdescription.setvalue('') self.keyword.setvalue('') def annotate(self): # create and post annotation of current selection self.annoIDs = [] self.annoView = cmd.get_view() self.annoIDs = cmd.index(self.selection) contextStr = '' viewStr = '' contextModel = '' dateStr = self.makeDateString() for i in self.annoIDs: # TODO: raise an error if multiple models are selected # currently annotates only the model from the atom first in the selection list if contextModel == '': contextModel = i[0] contextStr += "%i" % i[1] else: if i[0] == contextModel: contextStr += ",%i" % i[1] for i in self.annoView: if viewStr == '': viewStr = "%f" % i else: viewStr += ",%f" % i # if there is a selection or a model visible, annotate that, if not, # if the URL in the open page UI has been loaded, it is the most recent model, so annotate that uiURL = self.remoteURL.getvalue() annoURI = '' if uiURL in self.loadedModels.values(): annoURI = uiURL if (annoURI == '' or annoURI == None) and contextModel != '': annoURI = self.loadedModels[contextModel] if annoURI != None and annoURI != '': annoType = self.type.getvalue()[0] annoCreator = self.username.getvalue() annoTitle = self.title.getvalue() annoDesc = self.description.getvalue() if annoType == "Keyword": annoDesc = self.kwdescription.getvalue() keywords = self.keyword.getvalue().split(",") anno = self.createAnnotationXML(annoURI, type=annoType, \ creator=annoCreator, title=annoTitle,\ context=contextStr, body=annoDesc, \ keywords=keywords, view=viewStr, date=dateStr) elif annoType == "SeeAlso": annoExtURL = self.seeAlsoExternalURL.getvalue() annoLocalFile = self.seeAlsoLocalFile.getvalue() if annoExtURL.find("http") == 0: anno = self.createAnnotationXML(annoURI, type=annoType, \ creator=annoCreator, title=annoTitle, date=dateStr,\ context=contextStr, extRef=annoExtURL, view=viewStr) # elif annoLocalFile != None and annoLocalFile != '': # TODO: do file upload else: self.status.setvalue("Unable to create annotation: invalid external URL") return else: anno = self.createAnnotationXML(annoURI, type=annoType, \ creator=annoCreator, title=annoTitle, date=dateStr,\ context=contextStr, body=annoDesc, view=viewStr) try: req = urllib2.Request(self.annotationServerURL.getvalue(), anno) response = urllib2.urlopen(req) except HTTPError as e: if e.code == 201: self.showAllAnnotations() self.status.setvalue("Annotation created") def makeDateString(self): date = datetime.utcnow() dateStr = "%s-%s-%sT%s:%s:%sZ" % (date.year,\ self.makeDatePartString(date.month), self.makeDatePartString(date.day),\ self.makeDatePartString(date.hour), self.makeDatePartString(date.minute),\ self.makeDatePartString(date.second)) return dateStr def makeDatePartString(self, part): partStr = "%i" % part if part < 10: partStr = "0" + partStr return partStr def createAnnotationXML(self, annoURI, type='Comment', context='', title='', \ creator='', language='en', created='', date='', length='', body='', \ view='', keywords='', extRef=''): # construct the XML for the annotation to be sent to the Annotea server anno = "" anno += "" % extRef if keywords != '' and keywords != None: for k in keywords: keyword = k.strip() # TODO: don't add the keyword if it is not from the loaded ontology # if keyword in self.ontology_tree_item.keywords: if True: anno += " 0: contextModel = annoIDs[0][0] url = self.loadedModels[contextModel] if url == '' or url == None: self.current.setvalue("No model loaded in AnnoCryst") return self.current.setvalue("Showing annotations for: %s" % url) annotea = "%s?w3c_annotates=%s" % (self.annotationServerURL.getvalue(), url) try: httpRequest = urllib2.Request(url=annotea) httpHandle = urllib2.urlopen(httpRequest) #self.printData = 0 #self.tmpAnnotation = {} annoteaRdfXml = httpHandle.read() dom = parseString(annoteaRdfXml) self.tree_item = AnnotationTreeItem(dom.documentElement, isTopLevel=True) self.node = AnnotationTreeNode(self.sc.component('canvas'), None, self.tree_item) self.node.setannotationservice(self) self.node.update() # expand all nodes in tree self.node.expand() for child in self.node.children: child.expand() self.status.setvalue("Annotations loaded") self.annotationsLoaded = True except URLError as e: self.status.setvalue("No annotations found") except HTTPError as e: print("Unable to load annotations") self.status.setvalue("Unable to load annotations") def updateDescriptionUI(self, result): # changes the UI depending on the type of annotation being created self.keyword.pack_forget() self.ontologyBrowser.pack_forget() self.addKeywordButtonBox.pack_forget() self.description.pack_forget() self.kwdescription.pack_forget() self.seeAlsoNotebook.pack_forget() if result == 'Keyword': self.kwdescription.pack(fill='x', padx=4, pady=1) self.ontologyBrowser.pack(fill='x', padx=4, pady=1) self.addKeywordButtonBox.pack() self.keyword.pack(fill='x', padx=4, pady=1) self.loadOntology() elif result == 'SeeAlso': self.seeAlsoNotebook.pack(fill='both', expand=1, padx=10, pady=10) else: self.description.pack(fill='x', padx=4, pady=1) def loadOntology(self): # retrieves the ontology and displays classes in ontology browser ontologyCanvas = self.ontologyBrowser.component('canvas') ontURL = self.keywordOntologyURL.getvalue() if self.loadedOntology == ontURL: return if ontURL != '' and ontURL != None: try: ontReq = urllib2.Request(url=ontURL) ontHandle = urllib2.urlopen(ontReq) ontContent = ontHandle.read() ontDom = parseString(ontContent) self.ontology_tree_item = OntologyTreeItem(ontDom.documentElement) self.ontology_tree_node = OntologyTreeNode(ontologyCanvas, None, self.ontology_tree_item) self.ontology_tree_node.setannotationservice(self) self.ontology_tree_node.update() self.ontology_tree_node.expand() self.loadedOntology = ontURL except: print("Unable to load ontology: %s" % ontURL) self.status.setvalue("Unable to load ontology") def addKeyword(self): # copies selected keyword from ontology viewer to keyword field kw = self.selectedKeyword if kw != '' and kw != None: oldval = self.keyword.getvalue().strip() if oldval == None: oldval = '' if oldval != '': oldval += ',' self.keyword.setvalue(oldval + kw) def selectAnnotation(self, context_ids, view): # highlights the context of the selected annotation select_str = "none" cmd.select(self.selection, 'none') cmd.set_view(view) # only add one id at a time to selection to avoid crashing PyMOL with large selection for id in context_ids: if id != "": select_str = self.selection + " or id %s" % id try: cmd.select(self.selection, select_str) cmd.indicate(self.selection) self.current.setvalue("Showing context for annotation") except: print("Unable to select context") def deselectAnnotation(self): cmd.indicate("none") url = self.remoteURL.getvalue() if url == '' or url == None: self.current.setvalue("No model loaded in AnnoCryst") else: self.current.setvalue("Showing annotations for %s" % url) # tree widget classes for displaying ontology keywords class OntologyTreeNode(TreeNode): def __init__(self, canvas, parent, item): TreeNode.__init__(self, canvas, parent, item) self.classicon = """ R0lGODdhEAAQAOMPAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD/ /////ywAAAAAEAAQAAAEI/DJSau9+IXN9+2g1VFexWVkiWoqeq5sAMfi974miIvh7GMRADs= """ # overload to load the icon from a string instead of from an image file def geticonimage(self, name): if name == "none": return None if name != "Class": return TreeNode.geticonimage(self, name) try: return self.iconimages[name] except KeyError: pass image = PhotoImage(master=self.canvas, data=self.classicon) self.iconimages[name] = image return image def setannotationservice(self, as1): self.annotationservice = as1 # overload to set annotation service for children def draw(self, x, y): result = TreeNode.draw(self, x, y) for child in self.children: child.setannotationservice(self.annotationservice) return result # overload select to send selected keyword to annotation service def select(self, event=None): TreeNode.select(self, event) if self.annotationservice != None: self.annotationservice.selectedKeyword = self.item.GetText() class OntologyTreeItem(TreeItem): def __init__(self, node, class_dict=None): self.children = [] self.node = node if node.nodeType == node.ELEMENT_NODE: self.tag = node.nodeName if self.tag == ("rdf:RDF"): allclasses = node.getElementsByTagNameNS(\ 'http://www.w3.org/2002/07/owl#', 'Class') self.class_dict = {} self.equiv_dict = {} tmpchildren = {} # find all OWL subclass relationships and store in class_dict # TODO: deal with equivalent classes for c in allclasses: cID = c.getAttributeNS('http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'ID') cSuperElems = c.getElementsByTagNameNS(\ 'http://www.w3.org/2000/01/rdf-schema#', 'subClassOf') if len(cSuperElems) > 0: cSuper = cSuperElems[0].getAttributeNS(\ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'resource').replace("#", "") if cSuper == None or cSuper == '': cSuperClassElem = cSuperElems[0].getElementsByTagNameNS(\ 'http://www.w3.org/2002/07/owl#', 'Class') if len(cSuperClassElem) > 0: cSuper = cSuperClassElem[0].getAttributeNS(\ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'about').replace("#", "") if cSuper == None or cSuper == '': cSuper = cSuperClassElem[0].getAttributeNS(\ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'ID') if cSuper == None or cSuper == '': # can't get the super class - might be a restriction etc if cID: tmpchildren[cID] = c else: if not cSuper in self.class_dict: self.class_dict[cSuper] = [c] else: self.class_dict[cSuper].append(c) else: if cID: tmpchildren[cID] = c # save list of keywords self.keywords = list(tmpchildren.keys()) # iterate over all classes that have super classes, # remove them from the children of the top element for childList in self.class_dict.values(): for child in childList: childId = child.getAttributeNS(\ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'ID') if childId == None or childId == '': childId = child.getAttributeNS(\ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'about').replace("#", "") if childId in tmpchildren: del tmpchildren[childId] self.children = list(tmpchildren.values()) elif self.tag.find("Class") != -1: # look up the subclasses (children) of this class self.class_dict = class_dict cID = self.GetText() if cID in class_dict: self.children = class_dict.get(cID) def GetLabelText(self): if self.tag == "rdf:RDF": return "Select keywords:" else: return "" def GetText(self): # return the ID of the class that this node represents if self.tag == "rdf:RDF": return " " text = self.node.getAttributeNS('http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'ID') if text == None or text == '': text = self.node.getAttributeNS(\ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'about').replace("#", "") return text def IsExpandable(self): return len(self.children) > 0 def GetSubList(self): parent = self.node itemlist = [OntologyTreeItem(node, self.class_dict) for node in self.children] return itemlist def GetIconName(self): if self.tag == "rdf:RDF": return "none" else: return "Class" # tree widget classes for displaying annotations class AnnotationTreeNode(TreeNode): def __init__(self, canvas, parent, item): TreeNode.__init__(self, canvas, parent, item) self.annotationservice = None self.comment_icon = """ R0lGODdhEAAQAOeZAERERFJSUkxaZ1hbXU1eb1xcXF1dXVJgbmVlZVRthWtra3BwcG14g2l+k3Z7 gHN9h3V9hGGFq2uFnnaMom6QsXSQq5CQkJGRkXyZtXObw4GZsXyevpmZmYmiuYakw5+fn4elwoqk wYWmxJCluKGhoYmpxpmntZynspKvz5K33LGxsbW1tam4yqm5yKC71p2/3qq/07u7u6vA1qDD5J/E 6qnD3KXF5LDG3rnFz7LH3bjG1LXH2a3K5cXFxb/J0qrO86/N7bfN48nT3czX4rjd/73c/Lre/9fZ 3MTh+7/j/8Xj/8Dn/97e3sbl/9Hi9srk/8fo/83m/+Di49zj6svo/83n/+Li4s7o/9rl8OPj483q /8/p/9Dq/9Pp/+Xl5dHr/9Xq/9Tr/+Do8OLo7dTs/9br/9nt/93s++jr7tzu/+vr69rw/97u/+js 7+zr6+zs7Ojt8t3x/+Hw+uzt79/x/+ru8t7y/+jv9unv9Orv9Obx/O/v7+zw9ePz/+Xy//Dw8O3x 9ef0//Ly8uf2/+T4//Pz8+f4/+v3/+n5/+33//b19ff39/n5+Pr59vn5+e/+//j6/Pr6+vv7+/b/ //78+vj///z+//v///3///////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////ywAAAAAEAAQAAAI3wAzCYRU p42iRozyjEETSaBDOHMoOXTIR8gbh2LcTNwISAeTTHekWLq00SEmLCasDMEzKVHJTJUQyZHBwYee Q34CDbLzKFAgQ4bMrMnhAMeZPmzS0FlKxxChLlyu3IDAwkmcMmDCaNVa5QkVJC4GwNhB5kuULVDS QlGipAkPDACmjAiihYpatUmAgDiAQFKLDjWKGBlshIgRGx4SGFCRSdCJCiFQvPixZIYICgQKfLiY 6U+MBw0mlKCxQUCABSrUTHTkpQcJDSkkKFhhZdHLSIWyZIhwQfXLiUcYWNgzMSAAOw== """ self.question_icon = """ R0lGODdhEAAQAOfAAAAASQAAVwAAXAAAZAAfcAAdjQ80iBE6nQ87pyE+hRlGpURERDZOkFJSUkxa Z1hbXU1eb1xcXF1dXVJgbmVlZVRthWtra01xuXBwcEd1yG14g156q2l+k3Z7gHN9h3V9hGGFq2uF nmCJy2qIvXaMom6QsXSQq5CQkJGRkXyZtXObw4GZsX+ZxYWYvXyevoSaxZmZmYKgzomiuYakw5+f n4elwoqkwYWmxJCluKGhoYmpxoip05mntZynspenwpKvz52z0pK33JO247GxsZm23LW1tam4yqm5 yKC71p2/3p+/6Kq/07u7u6vA1qDD5J/E6qnD3KXF5LDG3rLF4rnFz7LH3bjG1LXH2a3K5cXFxb/J 0qrO86/N7bfN47bR8bjS7b7T68nT3czX4rjd/73c/Lre/9fZ3Lzh/8Th+7/j/8Xj/8Dn/97e3sbl /9Hi9srk/8fo/83m/+Di49zj6svo/83n/+Li4s7o/9rl8OPj483q/8/p/9Dq/8/r/9Pp/+Xl5dHr /9Xq/9Tr/+Do8OLo7c7v/9Ts/9br/9Lv/9nt/93s+9bv/+jr7tzu/+vr69rw/97u/+js7+zr6+zs 7Ojt8tf0/93x/+Hw+uzt79/x/+ru8t7y/+jv9unv9Orv9Obx/O/v7+zw9ePz/+Xy//Dw8O3x9ef0 //Ly8uf2/+T4//Pz8+f4/+v3/+n5/+33/+b7//b19ff39+n///n5+Pr59vn5+e/+//j6/Pr6+vv7 +/b///78+vj///z+//v///3///////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////ywAAAAAEAAQAAAI+wCBCbyl KRIsWrM8EWKES6BDSph2OXQYKswkh4MkOXxxgYEPgaWssAHGSU4vX8BaCJiSIAAQYL/w8LAjppMu V8BYFACzYcAIXq0uNYGh5ROrUaZQbbJlAMGXVYkeVelARZEoSI0y8YqhoE8qP3zuSPlgxI2lQ4EE vRKhRFadN3TQIHmw5IohQHH2LCLiBZEaNW2wpFgwB0cXPXQq7QBA4AycNFxqTKCQ64gMKGQKCTmQ YUyZKDMqSBgC7FQPEzZ+JNmyxsmNEhAi0LgIjBQTDxxI6HjiwkEDDEMcTaz1J0uOFUFCWChiJ9ZE h7hU5VEBAoXw59jNaDgBamJAADs= """ self.semantic_icon = """ R0lGODdhEAAQAOeHAAoYbERERFJSUkxaZ1hbXU1eb1xcXF1dXVJgbmVlZVRthWtra3BwcG14g2l+ k3Z7gHN9h3V9hGGFq2uFnnaMom6QsXSQq5CQkJGRkXyZtXObw4GZsXyevpmZmYmiuYakw5+fn4el woqkwYWmxJCluKGhoYmpxpmntZynspKvz5K33LGxsbW1tam4yqm5yKC71p2/3qq/07u7u6vA1qDD 5J/E6qnD3KXF5LDG3rnFz7LH3bjG1LXH2cXFxb/J0qrO86/N7bfN48nT3czX4rjd/73c/Lre/9fZ 3L/j/8Xj/8Dn/97e3sbl/9Hi9srk/8fo/+Di49zj6svo/+Li4trl8OPj483q/8/p/9Dq/9Pp/+Xl 5dTr/+Do8OLo7dbr/93s++jr7uvr6+js7+zr6+zs7Ojt8t3x/+zt79/x/+ru8ujv9unv9Orv9Obx /O/v7+zw9eXy//Dw8O3x9ef0//Ly8uf2//Pz8+f4/+n5//b19ff39/n5+Pr59vn5+e/+//j6/Pr6 +vv7+/78+vj///z+//v///3///////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////ywAAAAAEAAQAAAI3AAPCfyT RkwePnvYdAEDSKDDMmcEOXT4RggZh1zGTNwoZ8eSQ2qgECq00aEhKiemDFkDoOWhlgBeDsIDYEYH H20AwJlTB4CfOXNgAtDxIMcXmGhaorkDIAsWADgitGhixguALVe3YHUiBcALAjF4wLwC8wmAJEwA ZAgQhUQQK1KeyJ2LBEgIBAkCufBgo4iRv0aIGLnxQcGBFYfooLAgIgWMH0pojKhQwACIi4fiyIDg gIKJGhwGCGCwIszEPlp6lNigYsICFlP0lDwEyE4VDRIwmJ498UiDC24mBgQAOw== """ self.seealso_icon = """ R0lGODdhEAAQAOezABAoQBAoUCs6WCk+YTBIYDlIZkRSbUJVZ0BYYEVTbklXZkRZbUlXdTBocEBo UDB4QEBocE5hdFJfe1JgeyCIMFNhfFNhfVBogFdndVBwYFBokFB4YCCYQCCQgGRxfkCAkCCgQGR0 gzCYUEB40ECIgGd1giCgYGB4kGd4iDCQoDCgUG13i2CAgECYYECgQFCA0G9/jlCYYGmCm3CAoGuE nVCI4ECgkGCQkGCQoECY0ECwUGCI0GCYgECwYGCI4HCQoHyJpX6LmX2KpmCQ4ISMnmCwQHCQwHCg cGCY0IaQmWCgsICQsHCQ4IeQomCY8ICYoGC4UGCY/3CogHCgsICYsHCY4I6VpXCY8I+Yn4yXr4Cg oIqYs4uYtIacsmDIMIyatZGbpWDIQIqfs4qftJCctZado3DAUJScsnCg/5SeqJWcspKet5efpZWg q3DIQJSjspajsJ2ippWksoC4kJCowJ+nrpSqwZ6ovKCowJ+qvpC4sKGsw5+vvqqssqCvv6CwwKCw 0IDYYKmzvaq0vqq1vqu1v4DYgLC1ubC4v5DYcKy40ZDQoLK5v7C40LW6vpDQsJDgcJDYoLa+xbDA 0JDggLq/xLy/wr7Bw8HBw6DggL/EybDI4KDgkMHGy8DHy8jJxqDosMDQ4MDQ8M/PztDS09DY4LDw wNDY8NjZ2Nra2Nzc2t/c19Dg8NDw0ODo/9D44ND48PDw//D4//////////////////////////// //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////ywAAAAAEAAQAAAI+wBnzUql SdCbM3ccjRLIUCCpQXoeRaJk5gYYSw1nqSI0x5CpV1ISFfmQ5FPDTlq8QDLVChYoHjlIxGlYCAeU QJwyLVLipIYIGA3lMIkRxs2RKUiGpADBoKEaNCxc6LhRZUgNExwUNPRz5UqGFj1s+Hjh4EGIhpWM RBmyQYWKDiMgIMDSEFWaHUae/DhBoYEGIWO2wDkkkBGeUrJiTZpxgQ5ixUD6zOLDKogAAlQA0Wlk 50CENn9WzOoyawAmRbNkuTolCsUlMaEKzMoTKguNNQEA6F4gg8weKhhmefoCKNSmJRWscCl+XEIZ gZKaWEhQos4qREQmGPDAZlZAADs= """ def setannotationservice(self, as1): self.annotationservice = as1 # overload to load the annotation icon data from strings def geticonimage(self, name): if name == "none": return None if name != "Comment" and name != "Question" and name != "Semantic" \ and name != "SeeAlso" and name != "Reference" and \ name != "Feedback" and name != "Rating": return TreeNode.geticonimage(self, name) try: return self.iconimages[name] except KeyError: pass icondata = self.comment_icon if name == "Question": icondata = self.question_icon elif name == "Semantic": icondata = self.semantic_icon elif name == "SeeAlso": icondata = self.seealso_icon image = PhotoImage(master=self.canvas, data=icondata) self.iconimages[name] = image return image def select(self, event=None): TreeNode.select(self, event) text = self.item.GetText() if self.annotationservice != None: # notify the selected annotation id - for delete function self.annotationservice.selectedAnno = self.item.annoID # notify annotationservice of text - for copy text function self.annotationservice.selectedText = text # notify annotationservice to highlight context in graphical view if self.item.label == "context": contextsplit = text.split(";") if len(contextsplit) > 0: view = contextsplit[0] view = view.replace("view:", "") view = view.split(",") viewfloats = [] for v in view: viewfloats.append(float(v)) if len(contextsplit) > 1: context = contextsplit[1] context = context.replace("ids:", "") contextids = context.split(",") # print "context: " + repr(contextids) # print "view: " + repr(view) self.annotationservice.selectAnnotation(contextids, view) else: self.annotationservice.deselectAnnotation() if self.item.label == "body": # launch URL of SeeAlso body in browser if text.find("http") == 0: webbrowser.open(text) # overload to set annotation service for context highlighting def draw(self, x, y): result = TreeNode.draw(self, x, y) for child in self.children: child.setannotationservice(self.annotationservice) return result class AnnotationTreeItem(TreeItem): def __init__(self, annotation, isTopLevel=False, label=None, id=None): if annotation != "": self.anno = annotation else: self.anno = "" self.isTopLevel = isTopLevel self.label = label self.isLeaf = False if label == None and not isTopLevel: self.label = self.GetText() if self.label == "context" or self.label == "date" or self.label == "creator" \ or self.label == "created" or self.label == "title" \ or self.label == "identifier" or self.label == "language": self.isLeaf = True self.annoID = id if annotation != "" and annotation.nodeType == annotation.ELEMENT_NODE and \ annotation.nodeName == "rdf:Description": self.annoID = annotation.getAttributeNS('http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'about') def GetText(self): node = self.anno if node == "": return " " elif self.isLeaf: text = "" for child in node.childNodes: if child.nodeType == node.TEXT_NODE: text += child.nodeValue return text elif self.label == "body" or self.label == "term": bodyurl = self.anno.getAttributeNS('http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'resource') # get body content if it's a resource stored on the Annotation Server, otherwise show url if bodyurl.find('AnnoteaServlet') != -1: return self.getAnnotationBody(self.anno) else: return bodyurl else: if node.nodeType == node.ELEMENT_NODE: nName = node.nodeName if nName == "rdf:RDF": nName = "Annotations" elif nName == "rdf:Description": annoType = self.GetIconName() if annoType != 'none': nName = self.GetIconName() + " Annotation" else: nName = "Annotation" return nName elif node.nodeType == node.TEXT_NODE: return node.nodeValue def IsExpandable(self): if self.isLeaf or self.label == "body": return False if self.anno == "": return self.isTopLevel == 1 else: return self.anno.hasChildNodes() def GetSubList(self): if self.anno == "" or self.isLeaf: return None parent = self.anno children = parent.childNodes prelist = [AnnotationTreeItem(node, id=self.annoID) for node in children \ if node.nodeName != "rdf:type" and node.nodeName != "policy" \ and node.nodeName != "annotates" and node.nodeName != "language"] itemlist = [item for item in prelist if item.GetText().strip()] return itemlist def GetIconName(self): if (self.label == None) or (self.label == "term") or \ (self.label == "body") or (self.label == "title") or \ (self.label == "creator") or (self.label == "context") or \ (self.label == "created") or (self.label == "description") or\ (self.label == "identifier") or (self.label == "date"): return "none" node = self.anno for typeNode in node.getElementsByTagNameNS(\ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'type'): typeStr = typeNode.getAttributeNS(\ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'resource').replace(\ "http://www.w3.org/2000/10/annotationType#", "") if typeStr == "Question" or typeStr == "Rating" or \ typeStr == "SeeAlso" or typeStr == "Feedback" or typeStr == "Reference": return typeStr if typeStr == "http://metadata.net/wannotea/semantic-annotation.owl#SemanticAnnotation": return "Semantic" return "Comment" def GetLabelText(self): if self.label != "Annotation": return self.label def getAnnotationBody(self, node): bodyContentStr = "" try: bodyReq = urllib2.Request(url=node.getAttributeNS(\ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'resource')) bodyHandle = urllib2.urlopen(bodyReq) bodyContent = bodyHandle.read() bodyDom = parseString(bodyContent) bodyElem = bodyDom.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "body") for body in bodyElem: for child in body.childNodes: if child.nodeType == node.TEXT_NODE: bodyContentStr += child.nodeValue except: print("Unable to read annotation body") return bodyContentStr.strip() # override urllib2 Request to support HTTP DELETE request class RequestWithMethod(urllib2.Request): def __init__(self, url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None): urllib2.Request.__init__(self, url, data, headers, origin_req_host, unverifiable) self.method = method def get_method(self): if self.method == None: if self.data != None: return "POST" else: return "GET" else: return self.method