######################################################
#
# AnnoCryst for PyMOL
#
# Anna Gerber
# email: agerber@itee.uq.edu.au
#
# Copyright 2008 eResearch, ITEE,
# The University of Queensland
######################################################
import Pmw, sys, urllib2, urllib, string, webbrowser
from idlelib.TreeWidget import TreeItem, TreeNode
from Tkinter import PhotoImage
from pymol import cmd
from urllib2 import URLError, HTTPError
from xml.dom.minidom import parseString
from datetime import datetime
from user import home
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):
parent = app.root
self.parent = parent
filepath = home
if filepath == '' or filepath == None:
filepath = "C:\\"
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.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.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 = 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%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= string.split(string.split(str(pdbURL),"/")[-1],".")[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, 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 += "\n"
anno += "\n"
anno += "\n"
if type == 'Keyword':
anno += "\n"
else:
anno += "\n" % type
anno += "\n" % annoURI
anno += ""
if view != '' and view != None:
anno += "view:%s;" % view
if context != '' and context != None:
anno += "ids:%s" % context
anno += "\n"
#if context != '' and context != None and view != '' and view != None:
# anno += "%s" % repr(view)
# anno += "%s" % context
# TODO: store view data in annotation properly
#anno += "\n"
#anno += "\n" % annoURI
#if view != '' and view != None:
# anno += "%s\n" % view
#anno += "\n"
if title != '' and title != None:
anno += "%s\n" % title
if creator != '' and creator != None:
anno += "%s\n" % creator
anno += "%s\n" % language
if created != '' and created != None:
anno += "%s\n" % created
if date != '' and date != None:
anno += "%s\n" % date
if body != '' and body != None:
anno += "\ntext/html\n"
anno += "%s" % length
anno += "\n"
anno += "\n"
anno += "%s\n" % title
anno += "%s\n" % body
anno += "\n"
if extRef != '' and extRef != None:
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 += "" % (self.keywordOntologyNamespace.getvalue(), keyword)
else:
if keyword != '':
print "Warning: Invalid keyword \'%s\' not added to annotation" % keyword
anno += ""
return anno
def refreshAnnotationView(self, page = None):
if page == None or (page == 'Browse Annotations' and not self.annotationsLoaded):
self.showAllAnnotations()
def clearAnnotations(self):
self.tree_item = AnnotationTreeItem("",isTopLevel=True)
self.node = AnnotationTreeNode(self.sc.component('canvas'),
None, self.tree_item)
self.node.update()
self.node.expand()
def showAllAnnotations(self, url=''):
# get annotations for the current model, and display in tree
# looks for an url first as a param, then from the url ui field (if it's loaded),
# finally from current graphical selection
annoIDs = cmd.index(self.selection)
self.clearAnnotations()
if url == '' or url == None:
uiURL = self.remoteURL.getvalue()
if uiURL in self.loadedModels.values():
url = uiURL
if url == '' and len(annoIDs) > 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, e:
self.status.setvalue("No annotations found")
except HTTPError, 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, as):
self.annotationservice = as
#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 = 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 = 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, as):
self.annotationservice = as
# 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 = string.split(text,";")
if len(contextsplit) > 0:
view = contextsplit[0]
view = string.replace(view,"view:","")
view = string.split(view,",")
viewfloats = []
for v in view:
viewfloats.append(float(v))
if len(contextsplit) > 1:
context = contextsplit[1]
context = context.replace("ids:","")
contextids = string.split(context,",")
#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