""" CCP4ProjectWidget.py: CCP4 GUI Project Copyright (C) 2010 University of York This library is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 3, modified in accordance with the provisions of the license to address the requirements of UK law. You should have received a copy of the modified GNU Lesser General Public License along with this library. If not, copies may be downloaded from http://www.ccp4.ac.uk/ccp4license.php This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. """ """ Liz Potterton April 2011 - list database """ ##@package CCP4ProjectWidget View a project from PyQt4 import QtGui,QtCore,QtSvg from CCP4Modules import WEBBROWSER,PROJECTSMANAGER,MIMETYPESHANDLER,QTAPPLICATION,LAUNCHER,PREFERENCES from CCP4TaskManager import TASKMANAGER from CCP4ErrorHandling import * import CCP4DbApi, CCP4StyleSheet, CCP4Utils, CCP4File import os,sys, time _PROJECTMODEL = {} _BROWSERMODE = 0 DRAGICONSIZE=16 _JOBICON = {} def PROJECTMODEL(projectId): if not _PROJECTMODEL.has_key(projectId): _PROJECTMODEL[projectId] = CProjectModel(parent=QTAPPLICATION(),projectId=projectId) return _PROJECTMODEL[projectId] def loadSvg(fileName,size=24): svg = QtSvg.QSvgRenderer() svg.load(fileName) pixmap = QtGui.QPixmap(size,size) pixmap.fill(QtGui.QColor(0,0,0,0)) painter = QtGui.QPainter(pixmap) svg.render(painter) painter.end() return pixmap def jobIcon(style='job'): if not _JOBICON.has_key(style): import CCP4Utils fileName = os.path.normpath(os.path.join(CCP4Utils.getCCP4I2Dir(),'qticons',style)) if os.path.exists(fileName+'.png'): _JOBICON[style] = QtGui.QIcon(QtGui.QPixmap(fileName+'.png')) #print 'jobIcon',fileName+'.png' elif os.path.exists(fileName+'.svg'): _JOBICON[style] = QtGui.QIcon(loadSvg(fileName+'.svg')) #print 'jobIcon',fileName+'.svg' elif os.path.exists(fileName+'.gif'): movie = QtGui.QMovie(fileName+'.gif'); label = QtGui.QLabel() label.setMovie(movie) movie.start() _JOBICON[style] = label else: fileName = os.path.normpath(os.path.join(CCP4Utils.getCCP4I2Dir(),'qticons','evaluations',style)) if os.path.exists(fileName+'.png'): _JOBICON[style] = QtGui.QIcon(QtGui.QPixmap(fileName+'.png')) elif os.path.exists(fileName+'.svg'): _JOBICON[style] = QtGui.QIcon(loadSvg(fileName+'.svg')) #print 'CCP4ProjectModel.jobIcon',JOBICON return _JOBICON[style] class CTreeItem: def __init__(self,parent=None): self._parent = parent self.name = '' def getName(self,jobNumber=False): if isinstance(self.name,str): return self.name else: return self.name.toString().__str__() def parent(self): return self._parent def setParent(self,parent): self._parent = parent def columnCount(self): return CProjectModel.NCOLUMNS def child(self,row): return None def childCount(self): return 0 def canFetchMore(self): return False def isJob(self): return False def isFile(self): return False def isProject(self): return False def isFolder(self): return False def root(self): p = self while not isinstance(p.parent(),QtCore.QAbstractItemModel): p = p.parent() return p def model(self): p = self while not isinstance(p,QtCore.QAbstractItemModel): p = p.parent() return p class CTreeItemFolder(CTreeItem): ERROR_CODES = { 101 : { 'description' : 'Failed deleting project from folder' } } def __init__(self,parent=None,info={}): self._parent = parent self.name = info.get('name') self.childProjects = [] self.childFolders = [] def isFolder(self): return True def data(self,column,role): if role == QtCore.Qt.DisplayRole: if column == 0: return QtCore.QVariant(self.name) elif role == QtCore.Qt.DecorationRole: if column == 0: return jobIcon('fileopen') return QtCore.QVariant() def child(self,row): if row6: self.jobNumber = infoList[6] else: self.jobNumber = '' self.identifier = QtCore.QVariant( self.fileId ) try: # Beware this could be broken if using older version of code mimeType = CCP4DbApi.FILETYPES_TEXT[self.fileType] if self.ifImport: self.icon = MIMETYPESHANDLER().icon(mimeType,modifier='import') else: self.icon = MIMETYPESHANDLER().icon(mimeType) except: pass self.setName(infoList[5],maxChar=maxChar,displayJobNumber=displayJobNumber) def data(self,column,role): if role == QtCore.Qt.DisplayRole: if column == self.displayColumn: return self.name elif role == QtCore.Qt.UserRole: if column == 0: return self.identifier elif role == QtCore.Qt.DecorationRole: if column == self.displayColumn: return self.icon elif role == QtCore.Qt.EditRole: if column == 0: return True return QtCore.QVariant() def setName(self,annotation,mimeType=None,maxChar=40,displayJobNumber=False): if displayJobNumber: text = self.jobNumber + ' ' else: text = '' if annotation is not None and len(annotation)>0: text = text + annotation else: if mimeType is None: try: mimeType = CCP4DbApi.FILETYPES_TEXT[self.fileType] except: pass if mimeType is not None: text = text + MIMETYPESHANDLER().getMimeTypeInfo(mimeType,'description') else: text = text + self.fileName self.name = QtCore.QVariant( text[0:maxChar] ) def row(self): ii = self._parent.len(self._parent.childInFiles) - 1 for item in self._parent.childOutFiles: ii += 1 if item == self: return ii return -1 def getJobId(self): return self._parent.jobId def getFileId(self): return self.fileId def getFilename(self): return self.fileName def isJob(self): return False def isFile(self): return True def mimeData(self): from lxml import etree urlList = [] mimeType = CCP4DbApi.FILETYPES_CLASS[self.fileType] root,err = PROJECTSMANAGER().db().getFileEtree(fileId=self.fileId) info = PROJECTSMANAGER().db().getFileInfo(fileId=self.fileId,mode='projectid') #print 'CTreeItemFile.mimeData projectId',projectId relPath = root.find('relPath') print 'CTreeItemFile.mimeData projectId',info,PROJECTSMANAGER().getProjectDirectory(projectId=info['projectid']),relPath.text relPath.text = os.path.normpath(os.path.join(PROJECTSMANAGER().getProjectDirectory(projectId=info['projectid']),str(relPath.text))) dragText = etree.tostring(root,pretty_print=False) urlList = [QtCore.QUrl()] urlList[0].setPath( PROJECTSMANAGER().db().getFullPath(fileId=self.fileId ) ) #print 'CProjectModel.mimeData',mimeType,dragText encodedData = QtCore.QByteArray() encodedData.append(dragText) mimeData = QtCore.QMimeData() mimeData.setData(mimeType,encodedData) if len(urlList)>0: mimeData.setUrls(urlList) return mimeData def formatDate(intTime): if intTime is None: return QtCore.QVariant() try: date = time.strftime(CTreeItemJob.DATE_FORMAT,time.localtime(intTime)) if date == CTreeItemJob.TODAY: date = time.strftime(CTreeItemJob.TIME_FORMAT,time.localtime(intTime)) elif date.split(' ')[-1] == CTreeItemJob.THISYEAR: date=date.rsplit(' ',1)[0] else: date=date.split(' ',1)[1] return QtCore.QVariant(date) except: return QtCore.QVariant() class CTreeItemJob(CTreeItem): statusList = ['Finished','Interrupted','Failed','Running','File holder','To delete','Unsatisfactory','Running remotely'] statusIconList = ['job','job_interrupted','sad','running','fileopen','list_delete','unsatisfactory','running'] boldFont = None italicFont = None TIME_FORMAT = '%H:%M' DATE_FORMAT = '%a %d %b %y' TODAY = None THISYEAR = None def __init__(self,parent=None,info={}): CTreeItem.__init__(self,parent=parent) #import traceback #print 'CTreeItemJob',info.get('jobnumber',None),info.get('taskname',None) #traceback.print_stack() #print '\n\n' self.childJobs = [] self.childInFiles = [] self.childOutFiles = [] self.highlight = False self.jobId = None self.taskName = info.get('taskname') if len(info)==0: return self.jobId = info.get('jobid') self.jobNumber = info.get('jobnumber') #self.identifier = QtCore.QVariant( info.get('jobnumber') ) self.identifier = QtCore.QVariant( self.jobId ) self.performance = None if info.get('performance',None) is not None: performanceClass = TASKMANAGER().getPerformanceClass(self.taskName) if performanceClass is not None: self.performance = performanceClass() self.performance.set(info['performance']) #print 'CTreeItemJob.__init__ jobtitle',info.get('jobtitle',None), TASKMANAGER().getShortTitle( info.get('taskname'),substitute=False) self.setName(info['jobtitle']) if info['parentjobid'] is not None: self.evaluation = QtCore.QVariant() self.followFrom = QtCore.QVariant() self.top=False else: self.evaluation = jobIcon(info.get('evaluation')) self.followFrom = jobIcon('greendot') self.top=True self.statusStr = info.get('status') if info.get('status') in CTreeItemJob.statusList: self.status = jobIcon(CTreeItemJob.statusIconList[CTreeItemJob.statusList.index(info.get('status'))]) status = info['status']+ ' ' else: self.status = jobIcon('Unknown') status = 'Job pending ' toolTip =TASKMANAGER().getTitle( info.get('taskname')) if info.get('jobtitle',None) is not None and info['jobtitle'] != toolTip: toolTip = "Job "+str(self.jobNumber)+' '+toolTip + '\n' + info['jobtitle'] + '\n' else: toolTip = "Job "+str(self.jobNumber)+' '+toolTip + '\n' if self.performance is not None: toolTip = toolTip + self.performance.__str__() + '\n' self.dateTime = formatDate(info['finishtime']) self.toolTip = QtCore.QVariant( toolTip + status + '\nRight mouse click for options' ) #print 'CTreeItemJob.__init__',self.name.toString().__str__() if CTreeItemJob.boldFont is None: CTreeItemJob.boldFont = QtGui.QFont() CTreeItemJob.boldFont.setItalic(True) CTreeItemJob.boldFont.setBold(True) CTreeItemJob.italicFont = QtGui.QFont() CTreeItemJob.italicFont.setItalic(True) def setName(self,jobTitle=None): #import traceback #traceback.print_stack(limit=5) if jobTitle is not None and len(jobTitle)>0: #shortTitle = TASKMANAGER().getShortTitle( self.taskName,substitute=False) #if len(shortTitle)>0: # self.name = QtCore.QVariant( self.jobNumber +' '+shortTitle + ': ' + jobTitle) #else: self.name = QtCore.QVariant( self.jobNumber +' '+ jobTitle ) else: self.name = QtCore.QVariant( self.jobNumber +' '+ TASKMANAGER().getShortTitle( self.taskName ) ) def update(self,info={}): #print 'CTreeItemJob.update',info if info['parentjobid'] is None: self.evaluation = jobIcon(info.get('evaluation')) if info.get('status') in CTreeItemJob.statusList: self.status = jobIcon(CTreeItemJob.statusIconList[CTreeItemJob.statusList.index(info.get('status'))]) else: #print 'CTreeItemJob.update status unknown',info.get('status') self.status = jobIcon('Unknown') self.setName(info.get('jobtitle',None)) def set(self,key,value): #print 'CTreeItemJob.set',key,value key = key.lower() if key == 'evaluation': value = CCP4DbApi.JOB_EVALUATION_TEXT[value] self.evaluation = jobIcon(value) elif key == 'status': if value in CTreeItemJob.statusList: #print 'CTreeItemJob.set status',value,CTreeItemJob.statusIconList[CTreeItemJob.statusList.index(value)] self.statusStr = value self.status = jobIcon(CTreeItemJob.statusIconList[CTreeItemJob.statusList.index(value)]) else: self.status = jobIcon('Unknown') elif key == 'followfrom': if value: self.followFrom = jobIcon('greenarrowsup') else: self.followFrom = jobIcon('greendot') elif key == 'jobtitle': self.setName(value) elif key == 'highlight': self.highlight = value elif key == 'performance': cls = TASKMANAGER().getPerformanceClass(self.taskName) if cls is not None: self.performance = cls() self.performance.set(value) elif key == 'finishtime': self.dateTime = formatDate(value) def getName(self,jobNumber=True): if isinstance(self.name,str): name = self.name else: name = self.name.toString().__str__() if jobNumber: return name else: return name[len(self.jobNumber)+1:] def appendChildJob(self,child): self.childJobs.append(child) def removeChildJob(self,row): if row>=0 and row < len(self.childJobs): del self.childJobs[row] def removeChildFile(self,row=None,fileNode=None): if row is not None: if row < len(self.childInFiles): del self.childInFiles[row] else: row = row - len(self.childInFiles) if row < len(self.childOutFiles): del self.childOutFiles[row] else: return False elif fileNode is not None: if self.childInFiles.count(fileNode): ii = self.childInFiles.index(fileNode) del self.childInFiles[ii] elif self.childOutFiles.count(fileNode): ii = self.childOutFiles.index(fileNode) del self.childOutFiles[ii] else: return False else: return False def removeChildOutputFiles(self): for indx in range(len(self.childOutFiles)-1,-1,-1): del self.childOutFiles[indx] def removeChildJobs(self): for indx in range(len(self.childJobs)-1,-1,-1): del self.childJobs[indx] def appendChildInputFile(self,child): self.childInFiles.append(child) def appendChildOutputFile(self,child): self.childOutFiles.append(child) def insertChildJob(self,pos,child): self.childJobs.insert(pos,child) def child(self,row): if row0: urlList = [QtCore.QUrl()] urlList[0].setPath( sceneFiles[0] ) #print 'CProjectModel.mimeData',mimeType,dragText encodedData = QtCore.QByteArray() encodedData.append(dragText) mimeData = QtCore.QMimeData() mimeData.setData(mimeType,encodedData) if len(urlList)>0: mimeData.setUrls(urlList) return mimeData class CProjectModel(QtCore.QAbstractItemModel): CONTRAST_MODE = 2 NCOLUMNS = 5 COLUMNS = ['followfrom','evaluation','name','performance','dateTime'] COLUMNHEADERS = { 'name':'Job/File', 'taskname':'Taskname', 'time':'Time', 'evaluation': None, 'jobtitle':'Comments', 'followfrom': None, 'performance': 'Evaluation', 'dateTime' : 'Finished' } # performance needs to be last in this list due to workings of CDbApi.jobInfoDict() JOBINFOITEMS = ['jobid','jobnumber','jobtitle','status','evaluation','taskname','parentjobid','finishtime','performance'] def __init__(self, parent=None, projectId=None): QtCore.QAbstractItemModel.__init__(self,parent) self._projectId = projectId self.parents=[] self.highlightJob = None self.rootItem = CTreeItemJob(self) self.rootItem.taskName = 'root' self.setupModelData() self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobFinished'),self.updateFinishedJob) self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobUpdated'),self.updateJob) self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobCreated'),self.createJob) self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobStarted'),self.createJob) self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobDeleted'),self.deleteJob) self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('fileUpdated'),self.updateFile) self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('projectReset'),self.resetAll) self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('followFromJobChanged'),self.updateFollowFrom) self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('setJobToImport'),self.setJobToImport) def resetAll(self,args): #print 'CProjectModel.resetAll',args if not args['projectId'] == self._projectId: return #print 'CProjectModel.resetAll resetting' self.beginResetModel() self.rootItem = CTreeItemJob(self) self.rootItem.taskName = 'root' self.setupModelData() self.endResetModel() #print 'from resetAll' def setData(self, index, value, role): if index.isValid() and role == QtCore.Qt.EditRole: prev_value = self.getValue(index) item = index.internalPointer() item.setData(unicode(value.toString())) return True else: return False def removeRows(self, position=0, count=1, parent=QtCore.QModelIndex()): node = self.nodeFromIndex(parent) self.beginRemoveRows(parent, position, position + count - 1) node.childItems.pop(position) self.endRemoveRows() def nodeFromIndex(self, index): if index is not None and index.isValid(): #print 'nodeFromIndex',index,index.internalPointer() return index.internalPointer() else: return self.rootItem def modelIndexFromJob(self,jobId=None): indexList = self.match(self.index(0,0,QtCore.QModelIndex()),QtCore.Qt.UserRole,QtCore.QVariant(jobId),1) #print 'modelIndexFromJob',jobId,indexList if len(indexList)>0: return indexList[0] else: jobPath = PROJECTSMANAGER().db().getJobAncestors(jobId=jobId) if len(jobPath)<=1: return None modelIndex = QtCore.QModelIndex() for jid in jobPath: indexList = self.match(self.index(0,0,modelIndex),QtCore.Qt.UserRole,QtCore.QVariant(jid),1) if len(indexList)==0: return None modelIndex = indexList[0] return modelIndex def modelIndexFromFile(self,fileId=None,jobId=None,jobIndex=None): if jobIndex is None: if jobId is None: jobId = PROJECTSMANAGER().db().getFileInfo(fileId=fileId,mode='jobid') jobIndex = self.modelIndexFromJob(jobId) #print 'modelIndexFromFile jobIndex',jobId,jobIndex if jobIndex is None: return None indexList = self.match(self.index(0,0,jobIndex),QtCore.Qt.UserRole,QtCore.QVariant(fileId),1) #print 'modelIndexFromFile',fileId,indexList if len(indexList)>0: return indexList[0] else: return None def getValue(self, index, role): item = index.internalPointer() return item.data(index.column(),role) def columnCount(self, parent): return CProjectModel.NCOLUMNS def data(self, index, role): if not index.isValid(): return None item = index.internalPointer() return item.data(index.column(),role) def flags(self,modelIndex): if not modelIndex.isValid(): return QtCore.Qt.NoItemFlags ic = modelIndex.column() #print 'CProjectModel.flags',ic if ic == 2: return QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsDragEnabled|QtCore.Qt.ItemIsEditable else: return QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled def headerData(self,section,orientation,role=QtCore.Qt.DisplayRole): if orientation == QtCore.Qt.Horizontal: if role==QtCore.Qt.DisplayRole: if self.COLUMNHEADERS[self.COLUMNS[section]] is not None: return QtCore.QVariant(self.COLUMNHEADERS[self.COLUMNS[section]]) elif role==QtCore.Qt.DecorationRole: if self.COLUMNS[section] == 'evaluation': return jobIcon('thought') return QtCore.QVariant() def index(self, row, column, parent): if row < 0 or column < 0 or row >= self.rowCount(parent) or column >= self.columnCount(parent): return QtCore.QModelIndex() if not parent.isValid(): parentItem = self.rootItem else: parentItem = parent.internalPointer() childItem = parentItem.child(row) #print 'CProjectModel.index',row, column, parent,childItem.getName() if childItem: return self.createIndex(row, column, childItem) else: return QtCore.QModelIndex() def parent(self, index): if not index.isValid(): return QtCore.QModelIndex() childItem = index.internalPointer() parentItem = childItem.parent() if parentItem == self.rootItem: return QtCore.QModelIndex() return self.createIndex(parentItem.row(), 0, parentItem) def rowCount(self, parent): #if parent.column() > 0: # return 0 if not parent.isValid(): parentItem = self.rootItem else: parentItem = parent.internalPointer() return parentItem.childCount() def setupModelData(self): CTreeItemJob.TODAY = time.strftime(CTreeItemJob.DATE_FORMAT,time.localtime()) CTreeItemJob.THISYEAR = time.strftime('%y',time.localtime()) jobInfoList = PROJECTSMANAGER().db().getProjectJobListInfo(mode=self.JOBINFOITEMS,projectId=self._projectId) for jobInfo in jobInfoList: if jobInfo.get('parentjobid',None) is None: item = CTreeItemJob(parent=self.rootItem,info=jobInfo) #self.rootItem.appendChildJob(item) self.rootItem.insertChildJob(0,item) else: parent = self.getJobTreeItem(jobInfo['parentjobid'],self.rootItem) if parent is not None: item = CTreeItemJob(parent=parent,info=jobInfo) parent.appendChildJob(item) #print 'setupModelData',parent.getName(),item.getName() fileList = PROJECTSMANAGER().db().getProjectFiles(projectId=self._projectId,topLevelOnly=False) for infoList in fileList: #jid,fid,impid,ftype,fname,annotation = infoList parent = self.getJobTreeItem(infoList[0],self.rootItem) if parent is not None: item = CTreeItemFile(parent=parent,infoList=infoList) if infoList[2] is not None: parent.appendChildInputFile(item) else: parent.appendChildOutputFile(item) followFrom = PROJECTSMANAGER().db().getProjectFollowFromJobId(projectId=self._projectId) if followFrom is not None: self.updateFollowFrom([self._projectId,None,followFrom]) def getJobTreeItem(self,jobId,parentTreeItem): for item in parentTreeItem.childJobs: if item.jobId == jobId: return item elif len(item.childJobs)>0: rv = self.getJobTreeItem(jobId,item) if rv is not None: return rv return None def currentProjectId(self): return self._projectId def createJob(self,args): #print 'CProjectModel.createJob',argList if args['projectId'] != self._projectId: return if self.getJobTreeItem(args['jobId'],self.rootItem) is not None: return jobInfo = PROJECTSMANAGER().db().getJobInfo(jobId=args['jobId'],mode=CProjectModel.JOBINFOITEMS) #print 'CProjectModel.createJob',jobId,jobInfo if jobInfo['parentjobid'] is None: self.beginInsertRows(QtCore.QModelIndex(),0,0) item = CTreeItemJob(self.rootItem,jobInfo) self.rootItem.insertChildJob(0,item) self.endInsertRows() jobIndex = self.index(0,0,QtCore.QModelIndex()) else: parentIndex = self.modelIndexFromJob(jobInfo['parentjobid']) if parentIndex is None: # Assume parent job started externally and not finished so not reported # by CCP4DbApi.getRecentlyFinishedJobs() - need to do a createJob() on it parentIndex = self.createJob({'jobId':jobInfo['parentjobid'],'projectId':args['projectId']}) parentNode = self.nodeFromIndex(parentIndex) parentRows = parentNode.childCount() self.beginInsertRows(parentIndex,parentRows,parentRows) item = CTreeItemJob(parentNode,jobInfo) parentNode.appendChildJob(item) self.endInsertRows() jobIndex = self.index(parentRows,0,parentIndex) self.createImportFiles(args['jobId'],jobIndex,item) self.createOutputFiles(args['jobId'],jobIndex,item) #print 'from createJob' return jobIndex def createChildJobs(self,argList): #print 'createJob',jobId jobId,projectId = argList if projectId != self._projectId: return childJobs = PROJECTSMANAGER().db().getChildJobs(jobId,descendents=True) #print 'createChildJobs',childJobs def recurseCreateJob(jobList,model): for jid,childJobList in jobList: model.createJob({'jobId':jid,'projectId':projectId}) if len(childJobList)>0: recurseCreateJob(childJobList,model) recurseCreateJob(childJobs,self) def createImportFiles(self,jobId=None,jobIndex=None,jobNode=None): # Get imported files fileInfoList = PROJECTSMANAGER().db().getJobImportFiles(jobId=jobId) jobIndex = None for fileInfo in fileInfoList: #jobid,fileid,importid,filetype,filename,annotation if jobIndex is None: jobIndex = self.modelIndexFromJob(jobId) jobNode = self.nodeFromIndex(jobIndex) index = self.modelIndexFromFile(fileInfo[1],jobIndex=jobIndex) #print 'CProjectModel.createImportFiles',fileInfoList,index if index is None: item = CTreeItemFile(parent=jobNode,infoList=fileInfo) if fileInfo[2] is not None: nFiles = jobNode.childCount('infiles') self.beginInsertRows(jobIndex,nFiles,nFiles) jobNode.appendChildInputFile(item) else: nFiles = jobNode.childCount('outfiles') self.beginInsertRows(jobIndex,nFiles,nFiles) jobNode.appendChildOutputFile(item) self.endInsertRows() def createOutputFiles(self,jobId=None,jobIndex=None,jobNode=None): # Get list of fileIds fileInfoList = PROJECTSMANAGER().db().getJobFiles(jobId=jobId,mode='all') #print 'CProjectsModel.createOutputFiles',jobId,fileInfoList if len(fileInfoList) ==0 : return if jobIndex is None: jobIndex = self.modelIndexFromJob(jobId) if jobNode is None: jobNode = self.nodeFromIndex(jobIndex) # Move any files we already know about (assume this is pipeline job adopting an output file from child job) # Assume it is a child of a child job newFileList = [] for fileInfo in fileInfoList: fileIndex = self.modelIndexFromFile(fileInfo[1],jobId=fileInfo[0]) #print 'CProjectsModel.createOutputFiles',jobId,fileInfo,fileIndex if fileIndex is not None: #print 'CProjectsModel.createOutputFiles parent',fileIndex.parent().data(QtCore.Qt.UserRole).toString().__str__() if fileIndex.parent().data(QtCore.Qt.UserRole).toString().__str__() != fileInfo[0]: fileNode = self.nodeFromIndex(fileIndex) fromJobNode = self.nodeFromIndex(fileIndex.parent()) fromRowIndex = fromJobNode.childFileIndex(self.nodeFromIndex(fileIndex)) toRowIndex = jobNode.childCount(mode='outfiles') #print 'CProjectModel.createOutputFiles moving file from',fromJobNode.jobNumber,'to',jobId self.beginMoveRows(fileIndex.parent(),fromRowIndex,fromRowIndex,jobIndex,toRowIndex) try: fromParentNode.removeChildFile(fileNode=fileNode) except: #print 'createJobOutputFiles error removing fileId from old job' pass newFileNode= CTreeItemFile(jobNode,fileInfo) jobNode.appendChildOutputFile(newFileNode) self.endMoveRows() else: newFileList.append(fileInfo) #print 'createJobOutputFiles',jobId,newFileIdList if len(newFileList)==0: return nFiles = jobNode.childCount('outfiles') self.beginInsertRows(jobIndex,nFiles,nFiles+len(newFileList)-1) for fileInfo in newFileList: item = CTreeItemFile(parent=jobNode,infoList=fileInfo) jobNode.appendChildOutputFile(item) self.endInsertRows() def deleteJob(self,args): if args['projectId'] != self._projectId: return #print 'CProjectModel.deleteJob',args #print 'deleteJob in _jobIndexList',len(self._jobIndexList),self._jobIndexList modelIndex = self.modelIndexFromJob(jobId=args['jobId']) if modelIndex is None: return node=self.nodeFromIndex(modelIndex) row=modelIndex.row() parent = node.parent() self.beginRemoveRows(QtCore.QModelIndex(),row,row) parent.removeChildJob(row) self.endRemoveRows() #print 'from deleteJob' def setJobToImport(self,args): #print 'setJobToImport',args if args['projectId'] != self._projectId: return modelIndex = self.modelIndexFromJob(jobId=args['jobId']) if modelIndex is None: return node=self.nodeFromIndex(modelIndex) #print 'CProjectModel.setJobToImport',node,node.childCount('infiles'),node.childCount() self.beginRemoveRows(modelIndex,node.childCount('infiles'),node.childCount()) node.removeChildOutputFiles() node.removeChildJobs() self.endRemoveRows() #node.taskName = 'import_files' #print 'from setJobToImport' def updateFinishedJob(self,args): #print 'CProjectModel.updateFinishedJob',args newArgs = {} newArgs.update(args) newArgs['value'] = args['status'] newArgs['key'] = 'status' self.updateJob(newArgs) def updateJob(self,args): #print 'CProjectModel.updateJob',args if args.get('projectId','') != self._projectId: return jobId = args.get('jobId') key = args.get('key') value = args.get('value') #print 'CProjectModel.updateJob',jobId,key,value index = self.modelIndexFromJob(jobId) if index is None: index = self.createJob({'jobId':jobId,'projectId':args['projectId']}) else: if key == 'status' and isinstance(value,int): value = CCP4DbApi.JOB_STATUS_TEXT[value] node = self.nodeFromIndex(index) #print 'CProjectModel.updateJob node',node,key,value if node is not None: if key == 'jobtitle': node.setName( value ) else: node.set(key,value) if key == 'status': if value in ['Queued']: self.createImportFiles(jobId) elif value in ['Finished','Failed','Interrupted','To delete']: self.createOutputFiles(jobId) if value in ['Finished','Interrupted']: # What about evaluation? perfDict=PROJECTSMANAGER().db().getJobInfo(jobId=jobId,mode='performance') #print 'CProjectModel.updateJob perfDict',perfDict if perfDict is not None: node.set('performance',perfDict) # If there is jobTitle info (from CDbApi jobFinished signal) then update title if args.get('jobTitle',None) is not None: node.setName( args['jobTitle'] ) if args.get('finishTime',None) is not None: node.set('finishtime',args['finishTime']) self.emit(QtCore.SIGNAL('dataChanged(const QModelIndex&,const QModelIndex&)'),index,index) self.redraw() #print 'from updateJob' def updateFile(self,args): #print 'CProjectModel.updateFile',args if args['projectId'] != self._projectId or args['key'] != 'annotation' : return index = self.modelIndexFromFile(args['fileId'],jobId=args['jobId']) if index is None: return node = self.nodeFromIndex(index) node.setName(args['value']) self.emit(QtCore.SIGNAL('dataChanged(const QModelIndex&,const QModelIndex&)'),index,index) self.redraw() #print 'from updateFile' def updateFollowFrom(self,args): #print 'CProjectModel.updateFollowFrom',args projectId,previousFollowFromJobId,jobId = args if projectId != self._projectId: return if previousFollowFromJobId is not None: index = self.modelIndexFromJob(jobId=previousFollowFromJobId) #print 'CProjectModel.updateFollowFrom previousFollowFromJobId index',index if index is not None: node = self.nodeFromIndex(index) if node is not None: node.set('followFrom',False) #ffindex = index.sibling(index.row(),0) self.emit(QtCore.SIGNAL('dataChanged(const QModelIndex&,const QModelIndex&)'),index,index) if jobId is not None: index = self.modelIndexFromJob(jobId=jobId) if index is not None: node = self.nodeFromIndex(index) if node is not None: node.set('followFrom',True) #ffindex = index.sibling(index.row(),0) self.emit(QtCore.SIGNAL('dataChanged(const QModelIndex&,const QModelIndex&)'),index,index) self.redraw() #print 'from updateFollowFrom' def updateHighlight(self,jobId=None): for jid,highlight in [ [ self.highlightJob,False ] , [ jobId , True ] ]: if jid is not None: index = self.modelIndexFromJob(jobId=jid) if index is not None: node = self.nodeFromIndex(index) node.set('highlight',highlight) self.emit(QtCore.SIGNAL('dataChanged(const QModelIndex&,const QModelIndex&)'),index,index) self.highlightJob = jobId self.redraw() def fileLabel(self,modelIndex=None,maxLength=None): node = self.nodeFromIndex(modelIndex) if node is None: return '' #print 'CProjectModel.fileLabel node',node return node.getName() def redraw(self): pass #QtCore.QAbstractItemModel.reset(self) self.emit(QtCore.SIGNAL('redraw')) class CProjectView(QtGui.QTreeView): def __init__(self,parent=None): QtGui.QTreeView.__init__(self,parent) self.setObjectName('projectWidget') self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) self.setDragEnabled(True) #self.setAcceptDrops(True) self.setDragDropMode(QtGui.QAbstractItemView.DragOnly) self.setExpandsOnDoubleClick(False) self.setRootIsDecorated(True) self.setIconSize(QtCore.QSize(16,16)) self.setEditTriggers(QtGui.QAbstractItemView.EditKeyPressed) self.setItemDelegateForColumn(2,CJobEditDelegate(self)) #self.setFocusPolicy(QtCore.Qt.NoFocus) self.setToolTip('Right mouse click for options to view jobs and files') self.setAlternatingRowColors(PREFERENCES().TABLES_ALTERNATING_COLOR) self.connect(PREFERENCES().TABLES_ALTERNATING_COLOR,QtCore.SIGNAL('dataChanged'),self.resetAlternatingRowColors) self.forceUpdate = sys.platform.count('inux') or sys.platform.count('arwin') #print 'CProjectView.__init__ forceUpdate',self.forceUpdate def update(self): if not self.forceUpdate: return #print 'CProjectView.update' #QtGui.QTreeView.update(self) # Why am I doing this??? This is broken on Windows # Maybe this fixes the failure to update on Linux frameRect = self.frameRect() self.setDirtyRegion(QtGui.QRegion(frameRect.x(),frameRect.y(),frameRect.width(),frameRect.height())) #print 'from CProjectView.update' def resetAlternatingRowColors(self): self.setAlternatingRowColors(PREFERENCES().TABLES_ALTERNATING_COLOR) def keyPressEvent(self,event=None): ''' From Qt Docs (../Qt/html/qt.html#KeyboardModifier-enum) Note: On Mac OS X, the ControlModifier value corresponds to the Command keys on the Macintosh keyboard, and the MetaModifier value corresponds to the Control keys. The KeypadModifier value will also be set when an arrow key is pressed as the arrow keys are considered part of the keypad. Note: On Windows Keyboards, Qt::MetaModifier and Qt::Key_Meta are mapped to the Windows key. Other possible values: QtCore.Qt.NoModifier modifier test by: event.modifiers() == QtCore.Qt.ControlModifier ''' #print 'CProjectView.keyPressEvent',event.key() if event.key() == 16777265: # mapFromGlobal() seems to not allow for the header so need to subtract header height modelIndex = self.indexAt(self.mapFromGlobal(self.cursor().pos()-QtCore.QPoint(0,self.header().height()))) #print 'CProjectView.keyPressEvent modelIndex',modelIndex,modelIndex.isValid(),modelIndex.row() if modelIndex.isValid() and modelIndex.column()==2: event.accept() self.edit(modelIndex) return QtGui.QTreeView.keyPressEvent(self,event) def mousePressEvent(self,event=None): #print 'mousePressEvent' if event.button() == QtCore.Qt.RightButton: self.emit(QtCore.SIGNAL('rightMousePress'),event) event.accept() return else: mousePressX = event.x() modelIndex = self.indexAt(event.pos()) r = self.visualRect(modelIndex) if r.isValid(): # Weird: changed how handle selection and now don't seem to need to allow for indent! #indent = self.nestedLevel(modelIndex)*self.indentation() #print 'mousePressEvent indent',indent indent = 0 #print 'mousePressEvent mousePressX',mousePressX,'left',r.left(),(r.left() + self.iconSize().width() + 4) if mousePressX >r.left()+indent and mousePressX < (r.left() + self.iconSize().width() + 4 + indent): self.startDrag(modelIndex=modelIndex) event.accept() return node = self.model().nodeFromIndex(modelIndex) if node.isJob(): self.emit(QtCore.SIGNAL('jobClicked'),modelIndex) else: self.emit(QtCore.SIGNAL('fileClicked'),modelIndex) event.accept() return #print 'calling QTreeView.mousePressEvent' QtGui.QTreeView.mousePressEvent(self,event) def nestedLevel(self,modelIndex): if not modelIndex.isValid(): return 0 level = -1 while level<5: level = level + 1 modelIndex = modelIndex.parent() if not modelIndex.isValid(): return level return level def nodeFromEvent(self,event): modelIndex = self.indexAt(QtCore.QPoint(event.x(),event.y())) col = self.model().COLUMNS[modelIndex.column()] return modelIndex,self.model().nodeFromIndex(modelIndex),col return [None,None,None,indx] def selectRow(self,modelIndex=None): #print 'CProjectView.selectRow',modelIndex.row() return sel = QtGui.QItemSelection( modelIndex.sibling(modelIndex.row(),0) , modelIndex.sibling(modelIndex.row(),CProjectModel.NCOLUMNS) ) self.selectionModel().select(sel,QtGui.QItemSelectionModel.ClearAndSelect) #print 'CProjectView.selectRow DONE' def startDrag(self,dropActions=None,modelIndex=None): if modelIndex is None: modelIndex = self.currentIndex() if modelIndex is None: return node = self.model().nodeFromIndex(modelIndex) if isinstance(node,CTreeItemFile) or node.getStatus() in ['Finished','noStatus']: drag = QtGui.QDrag(self) drag.setMimeData(node.mimeData()) icon = node.data(CProjectModel.COLUMNS.index('name'),QtCore.Qt.DecorationRole) if icon is not None: try: pixmap = icon.pixmap(18,18) drag.setHotSpot(QtCore.QPoint(9,9)) drag.setPixmap(pixmap) except: pass drag.exec_(QtCore.Qt.CopyAction) def getSelectedJobs(self): selectedRows = self.selectionModel().selectedRows() selectedJobs = [] for row in selectedRows: node = self.model().nodeFromIndex(row) #print 'getSelectedJobs', row,node if node is not None: selectedJobs.append(node.getJobId()) #print 'getSelectedJobs',selectedJobs return selectedJobs class CProjectWidget(QtGui.QFrame): MARGIN = 0 ERROR_CODES = { 100 : { 'description' : 'Error copying file' } } def __init__(self,parent=None,projectId=None): QtGui.QFrame.__init__(self) self.projectId = projectId layout = QtGui.QVBoxLayout() layout.setMargin(CProjectWidget.MARGIN) layout.setContentsMargins(CProjectWidget.MARGIN,CProjectWidget.MARGIN, CProjectWidget.MARGIN,CProjectWidget.MARGIN) layout.setSizeConstraint(QtGui.QLayout.SetMinAndMaxSize) self.setLayout(layout) self.tab = QtGui.QTabWidget(self) self.layout().addWidget(self.tab) model = PROJECTMODEL(projectId=projectId) self.projectView=CProjectView(self) self.projectView.setModel(model) self.connect(model,QtCore.SIGNAL('redraw'),self.projectView.update) self.projectView.model().reset() self.projectView.setColumnWidth(model.COLUMNS.index('name'),300) #self.projectView.setColumnWidth(model.COLUMNS.index('jobtitle'),300) self.projectView.setColumnWidth(model.COLUMNS.index('evaluation'),35) self.projectView.setColumnWidth(model.COLUMNS.index('followfrom'),35) self.projectView.setColumnWidth(model.COLUMNS.index('performance'),200) #self.layout().addWidget(self.projectView) self.connect(self.projectView,QtCore.SIGNAL('rightMousePress'),self.showJobListPopup) self.tab.addTab(self.projectView,'Job list') self.dirModel = CProjectDirModel(self,projectId) self.dirView = CProjectDirView(self) self.dirView.setModel(self.dirModel) projectDir = PROJECTSMANAGER().db().getProjectDirectory(projectId=self.projectId) #print 'CProjectDirModel',projectName,projectDir modelIndex = self.dirModel.index(os.path.join(projectDir,'CCP4_JOBS')) if modelIndex.isValid(): self.dirView.setRootIndex(modelIndex) #self.connect(self.projectView,QtCore.SIGNAL('jobClicked'),self.handleJobClicked) self.connect(self.projectView,QtCore.SIGNAL('fileClicked'),self.handleFileClicked) self.connect(self.projectView.selectionModel(),QtCore.SIGNAL('selectionChanged (const QItemSelection& , const QItemSelection&)'),self.handleSelectionChanged) self.connect(self.projectView,QtCore.SIGNAL('doubleClicked(const QModelIndex &)'),self.handleDoubleClick) self.connect(self.dirView,QtCore.SIGNAL('doubleClicked(const QModelIndex &)'),self.showFileFromDirView) self.tab.addTab(self.dirView,'Project directory') self.popupMenu = QtGui.QMenu(self) self.doubleClicked = None def model(self): return self.projectView.model() def setForceUpdate(self,value): self.projectView.forceUpdate = value def showJobListPopup(self,event): import functools,os from CCP4TaskManager import TASKMANAGER modelIndex,node,column = self.projectView.nodeFromEvent(event) #print 'showJobListPopup',node.getName(),column position = QtCore.QPoint(event.globalX(),event.globalY()) if node is None: return itemName = node.getName() selectedJobs = [] if column in ['name','performance','dateTime']: if node.isJob(): try: selectedJobs = self.projectView.getSelectedJobs() except: selectedJobs = [] try: taskname = node.getTaskName() jobStatus = node.getStatus() jobId=node.jobId importFiles = node.childCount('infiles') except: return if jobId is None: return #print 'showJobListPopup',taskname,type(taskname),jobStatus self.popupMenu.setTitle(itemName) self.popupMenu.clear() if taskname == 'import_files': action = self.popupMenu.addAction('Delete job and imported files') self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'Delete+import',jobId,itemName,modelIndex,None,selectedJobs)) else: if jobStatus == 'Running': popupStopSubMenu = self.popupMenu.addMenu('Stop job') menuList = [['Interrupt safely','interruptJob'],['Kill immediately','killJob'],['Kill and delete','killDeleteJob'],['Mark as failed','markFailedJob'],['Mark as finished','markFinishedJob']] if not TASKMANAGER().getTaskAttribute(taskName=taskname,attribute='INTERRUPTABLE',default=False,script=True): menuList.pop(0) if taskname == 'coot_rebuild' : menuList.pop(0); menuList.pop(0) for menuItem,signal in menuList: action = popupStopSubMenu.addAction(menuItem) self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL(signal),jobId)) else: if len(selectedJobs)>1 and jobId in selectedJobs: action = self.popupMenu.addAction('Delete selected jobs') self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'Delete+selected',jobId,itemName,modelIndex,None,selectedJobs)) elif importFiles == 0: action = self.popupMenu.addAction('Delete') self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'Delete',jobId,itemName,modelIndex,None,selectedJobs)) else: popupDelMenu = self.popupMenu.addMenu('Delete') for label,mode in [['Delete job - save imported files','Delete'],['Delete job - delete imported files','Delete+import']]: action = popupDelMenu.addAction(label) self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,mode,jobId,itemName,modelIndex,None,selectedJobs)) # Clone - disable if not CLONEABLE action = self.popupMenu.addAction('Clone') self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'Clone',jobId,itemName,modelIndex,None,selectedJobs)) action.setEnabled(TASKMANAGER().getTaskAttribute(taskName=taskname,attribute='CLONEABLE',default=True) ) action = self.popupMenu.addAction('Edit label') self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'Edit label',jobId,itemName,modelIndex,position,selectedJobs)) popupOpenSubMenu = self.popupMenu.addMenu('Open') for frame,label in [['input','Input'],['output','Output']]: action = popupOpenSubMenu.addAction(label) action.setEnabled(jobStatus != CCP4DbApi.JOB_STATUS_FILE_HOLDER) self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('openFrame'),frame,jobId)) action = popupOpenSubMenu.addAction('Comments') self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('openFrame'),'status',jobId)) popupViewSubMenu = self.popupMenu.addMenu('View') action = popupViewSubMenu.addAction('Job report') #print 'showJobListPopup jobStatus',jobStatus if jobStatus in ['Finished','Failed','Interrupted']: self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'Job report',jobId,itemName,modelIndex,None,selectedJobs)) else: action.setEnabled(False) action = popupViewSubMenu.addAction('Command file') if os.path.exists(PROJECTSMANAGER().makeFileName(jobId,'COM')): self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'Command file', jobId,itemName,modelIndex,None,selectedJobs)) else: action.setEnabled(False) action = popupViewSubMenu.addAction('Log file') action1 = popupViewSubMenu.addAction('Log graphs') logFile = PROJECTSMANAGER().makeFileName(jobId,'LOG') #print 'showJobListPopup logFile',logFile if os.path.exists(logFile) or os.path.exists(logFile+'.html'): self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'Log file',jobId,itemName,modelIndex,None,selectedJobs)) self.connect(action1,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'Log graphs',jobId,itemName,modelIndex,None,selectedJobs)) else: action.setEnabled(False) action1.setEnabled(False) action = popupViewSubMenu.addAction('Diagnostic') if jobStatus in ['Finished','Failed','Interrupted']: #diagFile = PROJECTSMANAGER().makeFileName(jobId,'DIAGNOSTIC') #if os.path.exists(diagFile): self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'Diagnostic',jobId,itemName,modelIndex,None,selectedJobs)) else: action.setEnabled(False) for label,prog in [['CCP4mg','ccp4mg'],['Coot','coot']]: action = popupViewSubMenu.addAction('In '+label) if jobStatus in ['Finished','Failed','Interrupted']: self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,prog,jobId,itemName,modelIndex,None,selectedJobs)) else: action.setEnabled(False) action = popupViewSubMenu.addAction('Job directory') self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'job_dir',jobId,itemName,modelIndex,None,selectedJobs)) action = popupViewSubMenu.addAction('Database entry') self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'job_db',jobId,itemName,modelIndex,None,selectedJobs)) action = popupViewSubMenu.addAction('Bibliography') self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'job_references',jobId,itemName,modelIndex,None,selectedJobs)) action.setEnabled(True) ''' action = popupViewSubMenu.addAction('Preceeding') self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'preceeding_jobs',jobId,itemName,modelIndex,None,selectedJobs)) ''' popupPurgeMenu = self.popupMenu.addMenu('Cleanup files') action = popupPurgeMenu.addAction('Delete temporary files') self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('purge'),jobId,'temporary')) action = popupPurgeMenu.addAction('Delete temporary and sub-job data files') self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('purge'),jobId,'intermediate')) if jobStatus in ['Finished','Interrupted','Failed'] : popupViewSubMenu = self.popupMenu.addMenu('Export') action = popupViewSubMenu.addAction('All job files - compressed' ) self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('export'),'job',jobId)) if jobStatus in ['Finished','Interrupted'] : exportMenu = TASKMANAGER().exportJobFiles(taskname,jobId=jobId) for item in exportMenu: action = popupViewSubMenu.addAction(item[1]) self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('export'),item,jobId)) popupViewSubMenu = self.popupMenu.addMenu('Next task..') # What next... nextOptionList = TASKMANAGER().whatNext(taskname,jobId=jobId) for nextTask,nextTitle,nextDefFile in nextOptionList: action = popupViewSubMenu.addAction(nextTitle) self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.emit,QtCore.SIGNAL('nextJob'),nextTask,jobId,nextDefFile)) popupViewSubMenu.addSeparator() action = popupViewSubMenu.addAction('Help..') self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'What next?',jobId,itemName,modelIndex,None,selectedJobs)) elif node.isFile(): fileId = node.fileId self.popupMenu.setTitle(itemName) self.popupMenu.clear() popupViewSubMenu = self.popupMenu.addMenu('View') #ext = node.getFilename().split('.')[-1] if node.fileType == 2: subMenuDef = [['text','As text'],['ccp4mg','In CCP4mg'],['coot','In Coot'],['db','Database entry']] elif node.fileType in [4,5,6,10,11,12,13,16]: subMenuDef = [['viewhkl','In ViewHKL'],['db','Database entry'],['coot','In Coot']] elif node.fileType in [ 13]: subMenuDef = [['viewhkl','In ViewHKL'],['ccp4mg','In CCP4mg'],['coot','In Coot'],['db','Database entry']] elif node.fileType == 1: subMenuDef = [['text','As text'],['db','Database entry']] else: subMenuDef = [['text','As text'],['db','Database entry']] for alias,label in subMenuDef: action = popupViewSubMenu.addAction(label) self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'file_'+alias,fileId,itemName,modelIndex,None,selectedJobs)) action = self.popupMenu.addAction('Edit label') self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'file_edit_label',fileId,itemName,modelIndex,position,selectedJobs)) if node.fileType in [4,5,6,10,11,12,13,16]: popupExportMenu = self.popupMenu.addMenu('Export file') action = popupExportMenu.addAction('Only this file') self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'file_export',fileId,itemName,modelIndex,None,selectedJobs)) action = popupExportMenu.addAction('All input/output exptal data for this job') self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'file_exptdata_export',fileId,itemName,modelIndex,None,selectedJobs)) action = popupExportMenu.addAction('Select data to export') self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'file_select_exptdata_export',fileId,itemName,modelIndex,None,selectedJobs)) else: action = self.popupMenu.addAction('Export file') self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleJobListPopup,'file_export',fileId,itemName,modelIndex,None,selectedJobs)) self.popupMenu.popup(QtCore.QPoint(event.globalX(),event.globalY())) elif column in ['followfrom']: jobId=node.jobId self.popupMenu.setTitle(itemName) self.popupMenu.clear() for menuItem,icon in [['Follow from',jobIcon('greenarrowsup')],['Clear',jobIcon('greendot')]]: action = self.popupMenu.addAction(icon,menuItem) self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleFollowFromPopup,menuItem,jobId)) self.popupMenu.popup(position) elif column in ['evaluation']: jobId=node.jobId self.popupMenu.setTitle(itemName) self.popupMenu.clear() for item in CCP4DbApi.JOB_EVALUATION_TEXT: action = self.popupMenu.addAction(jobIcon(item),item) self.connect(action,QtCore.SIGNAL('triggered(bool)'),functools.partial(self.handleEvaluationPopup,item,jobId)) self.popupMenu.popup(position) def handleEvaluationPopup(self,evaluation,jobId,triggerBool=None): #print 'handleEvaluationPopup',evaluation,jobId try: PROJECTSMANAGER().db().updateJob(jobId,key='evaluation',value=evaluation) import CCP4DbUtils CCP4DbUtils.makeJobBackup(jobId=jobId) except: print 'ERROR in handleEvaluationPopup' def handleFollowFromPopup(self,action,jobId,triggerBool=None): #print 'handleFollowFromPopup',action,jobId if action == 'Follow from': PROJECTSMANAGER().db().setProjectFollowFromJobId(self.model()._projectId,jobId) elif action == 'Clear': PROJECTSMANAGER().db().setProjectFollowFromJobId(self.model()._projectId,jobId,clear=True) def handleJobListPopup(self,action,jobId,itemName,modelIndex=None,position=None,selectedJobs=None,triggerBool=None): #print 'CProjectWidget.handleJobListPopup',action,jobId,selectedJobs #print 'handleJobListPopup current', self.projectView.selectionModel().currentIndex().row(), self.projectView.selectionModel().selectedRows(),self.projectView.selectionModel().selectedIndexes() if action == 'Delete': self.emit(QtCore.SIGNAL('deleteJob'),[jobId],False) elif action == 'Delete+import': self.emit(QtCore.SIGNAL('deleteJob'),[jobId],True) elif action == 'Delete+selected': #print 'handleJobListPopup Delete+selected' self.emit(QtCore.SIGNAL('deleteJob'),selectedJobs,True) elif action == 'Stop job': self.emit(QtCore.SIGNAL('stopJob'),jobId) elif action == 'Clone': self.emit(QtCore.SIGNAL('cloneTask'),jobId) elif action == 'Edit label': #import CCP4Widgets #d = CCP4Widgets.CEditFileLabel(parent=self,jobId=jobId) #d.move(position-QtCore.QPoint(20,20)) #print 'handleJobListPopup modelIndex',modelIndex.row(),modelIndex.column() self.projectView.edit(modelIndex) elif action == 'Command file': comFile = PROJECTSMANAGER().makeFileName(jobId,'COM') if os.path.exists(comFile): WEBBROWSER().openFile(comFile,toFront=True) elif action == 'Log file': logFile = PROJECTSMANAGER().makeFileName(jobId,'LOG') #print 'CProjectViewer.handleJobListPopup logFile',logFile if os.path.exists(logFile): LAUNCHER().launch(viewer='logview',argList=[logFile]) #WEBBROWSER().openFile(logFile,format="text/plain",toFront=True) elif os.path.exists(logFile+'.html'): WEBBROWSER().openFile(logFile+'.html',toFront=True) elif action == 'Log graphs': logFile = PROJECTSMANAGER().makeFileName(jobId,'LOG') if os.path.exists(logFile): LAUNCHER().launch(viewer='loggraph',argList=[logFile]) elif action == 'Job report': reportFile = PROJECTSMANAGER().makeFileName(jobId,'REPORT') if os.path.exists(reportFile): WEBBROWSER().openFile(reportFile,toFront=True) else: try: import CCP4ReportGenerator jobNumber = PROJECTSMANAGER().db().getJobInfo(jobId=jobId,mode='jobnumber') generator = CCP4ReportGenerator.CReportGenerator(jobId=jobId,jobStatus='Finished',jobNumber=jobNumber) reportFile, newPageOrNewData = generator.makeReportFile() except CException as e: print e.report() except Exception as e: print e if os.path.exists(reportFile): webView = WEBBROWSER().openFile(reportFile,toFront=True) if webView is not None: webView.connect(generator,QtCore.SIGNAL('FinishedPictures'),webView.attemptImageLoad) elif action == 'Diagnostic': #diagFile = PROJECTSMANAGER().makeFileName(jobId,'DIAGNOSTIC') jobInfo = PROJECTSMANAGER().db().getJobInfo(jobId=jobId,mode=['jobnumber','status']) import CCP4ReportGenerator generator = CCP4ReportGenerator.CReportGenerator(jobId=jobId,jobStatus=jobInfo['status'],jobNumber=jobInfo['jobnumber']) reportFile = generator.makeFailedReportFile(redo=False) WEBBROWSER().openFile(reportFile,toFront=True) elif action in ['ccp4mg','coot']: if action == 'coot': action='coot_job' LAUNCHER().openInViewer(viewer=action.lower(),jobId=jobId,projectId=self.projectView.model()._projectId,guiParent=self) elif action == 'job_dir': self.dirView.focusOn(jobNumber=itemName) self.tab.setCurrentIndex(1) elif action == 'job_db': self.showDatabaseEntry(jobId=jobId) elif action == 'job_references': self.emit(QtCore.SIGNAL('showBibliography'),jobId) elif action == 'preceeding_jobs': preJobsTree = PROJECTSMANAGER().db().getPreceedingJobs(jobId) def printPreceedingJobs(jTree,level): print 'job ',level,jTree[0],jTree[1] for item in jTree[2]: printPreceedingJobs(item,level+1) elif action == 'What next?': self.emit(QtCore.SIGNAL('showWhatNextPage'),jobId) elif action[0:4] == 'file': fileId = jobId fileName = PROJECTSMANAGER().db().getFullPath(fileId) if fileName is not None: if action == 'file_text': WEBBROWSER().openFile(fileName,toFront=True) elif action == 'file_db': self.showDatabaseEntry(fileId=fileId) elif action in ['file_ccp4mg','file_coot','file_viewhkl']: if action == 'file_coot': action='file_coot_job' LAUNCHER().openInViewer(viewer=action[5:],fileName=str(fileName),projectId=self.projectView.model()._projectId,guiParent=self) elif action == 'file_edit_label': self.projectView.edit(modelIndex) #fileLabel = self.projectView.model().fileLabel(modelIndex=modelIndex,maxLength=None) #import CCP4Widgets #d = CCP4Widgets.CEditFileLabel(parent=self,fileId=fileId) #d.move(position-QtCore.QPoint(20,20)) elif action in ['file_export','file_exptdata_export']: fileInfo = PROJECTSMANAGER().db().getFileInfo(fileId=fileId,mode=['filetype','jobid','jobnumber']) filters = MIMETYPESHANDLER().getMimeTypeInfo(fileInfo['filetype'],'filter') defaultSuffix = MIMETYPESHANDLER().getMimeTypeInfo(fileInfo['filetype'],'fileExtensions')[0] if action == 'file_export': title = 'Export '+fileName else: title = 'Export all experimental data associated with job '+str(fileInfo['jobnumber']) #print 'file_export',fileType,filters,defaultSuffix import CCP4FileBrowser,functools fileBrowser = CCP4FileBrowser.CFileDialog(parent=self, title=title, filters = [filters], defaultSuffix = defaultSuffix, fileMode = QtGui.QFileDialog.AnyFile) if action == 'file_export': self.connect(fileBrowser,QtCore.SIGNAL('selectFile'),functools.partial(self.exportData,fileName,fileId,None)) else: self.connect(fileBrowser,QtCore.SIGNAL('selectFile'),functools.partial(self.exportData,fileName,None,fileInfo['jobid'])) fileBrowser.show() elif action == 'file_select_exptdata_export': self.emit(QtCore.SIGNAL('openMergeMtz'),{'fileName':fileName,'fileId':fileId,'jobId':jobId}) else: pass #print 'CProjectWidget.handleJobListPopup DONE' def launchViewer(self,filePath): import CCP4Modules format = MIMETYPESHANDLER().formatFromFileExt(fileName=filePath) viewerList = MIMETYPESHANDLER().getViewers(format) #print 'CProjectWidget.launchViewer',filePath,format,viewerList if len(viewerList)<=0: CCP4Modules.WEBBROWSER().openFile(filePath,toFront=True) elif isinstance(viewerList[0],str): CCP4Modules.LAUNCHER().openInViewer(viewer=viewerList[0],fileName=filePath,projectId=self.projectView.model()._projectId,guiParent=self) else: import CCP4WebBrowser CCP4WebBrowser.OPENFILE(filePath,toFront=True) def showDatabaseEntry(self,jobId=None,fileId=None): import copy win = QtGui.QDialog(self) win.setWindowTitle('Database entry') win.setLayout(QtGui.QVBoxLayout()) label = QtGui.QTextEdit() win.layout().addWidget(label) importInfo = {} importOrder = ['sourcefilename','annotation'] if jobId is not None: order = copy.deepcopy(PROJECTSMANAGER().db().JOBITEMS) order.sort() order.extend(['relpath','projectid','projectname','parentjobnumber','childjobs']) info = PROJECTSMANAGER().db().getJobInfo(jobId=jobId,mode=order) title = 'job id: '+str(jobId) else: order = copy.deepcopy(PROJECTSMANAGER().db().FILEITEMS) order.sort() order.extend(['jobnumber','taskname','projectname','projectid']) info = PROJECTSMANAGER().db().getFileInfo(fileId=fileId,mode=order) title = 'file id: '+str(fileId) try: importInfo = PROJECTSMANAGER().db().getImportFileInfo(fileId=fileId,mode=importOrder) except: pass text = 'Database entry for '+title+'\n' for item in order: try: text = text + item+': '+str(info[item])+'\n' except: pass if len(importInfo)>0: for item in importOrder: try: text = text + item+': '+str(importInfo[item])+'\n' except: pass label.setPlainText(text) label.setReadOnly(True) label.setMinimumHeight(300) label.setMinimumWidth(500) win.show() win.raise_() def showFileFromDirView(self,modelIndex): filePath = str(self.dirModel.filePath(modelIndex)) self.launchViewer(filePath) def exportData(self,myFileName,fileId,jobId,exportFileName): #print 'exportData',myFileName,fileId,jobId,exportFileName if fileId is not None or (jobId is not None and os.path.exists(os.path.join(PROJECTSMANAGER().db().jobDirectory(jobId=jobId),'hklout.mtz'))) : if os.path.splitext(exportFileName) != os.path.splitext(myFileName): exportFileName = os.path.splitext(exportFileName)[0] + os.path.splitext(myFileName)[1] if fileId is None: myFileName = os.path.join(PROJECTSMANAGER().db().jobDirectory(jobId=jobId),'hklout.mtz') import shutil try: shutil.copyfile(myFileName,exportFileName) except: e = CException(self.__class__,100,'From: '+str(myFileName)+' to: '+str(exportFileName)) e.warningMessage('Copying file',parent=self) else: PROJECTSMANAGER().db().createExportFile(fileId=fileId,exportFilename=exportFileName) fileInfo = PROJECTSMANAGER().db().getFileInfo(fileId=fileId,mode=['jobid','projectname']) import CCP4DbUtils CCP4DbUtils.makeJobBackup(jobId=fileInfo['jobid'],projectName=fileInfo['projectname']) elif jobId is not None: # Use the mergeMtz plugin to merge all input and output data objects import CCP4TaskManager,CCP4DbApi taskObj = CCP4TaskManager.TASKMANAGER().getPluginScriptClass('mergeMtz')(self) #print 'CProjectWidget.exportData taskObj',taskObj taskObj.container.outputData.HKLOUT.setFullPath(exportFileName) for role in [CCP4DbApi.FILE_ROLE_IN,CCP4DbApi.FILE_ROLE_OUT]: fileIdList = PROJECTSMANAGER().db().getJobFiles(jobId=jobId,role=role,fileTypes=CCP4DbApi.MINIMTZFILETYPES) #print 'CProjectWidget.exportData getJobFiles',role,fileIdList for fileId in fileIdList: name = PROJECTSMANAGER().db().getFullPath(fileId=fileId) taskObj.container.inputData.MINIMTZINLIST.append({'fileName' : name }) taskObj.container.inputData.MINIMTZINLIST[-1].columnTag.set(PROJECTSMANAGER().db().getFileInfo(fileId=fileId,mode='JobParamName')) taskObj.container.inputData.MINIMTZINLIST[-1].setColumnNames(mode='applyTag') #print 'CProjectWidget.exportData MINIMTZINLIST',taskObj.container.inputData.MINIMTZINLIST taskObj.process() #print 'CProjectWidget.exportData',exportFileName,os.path.exists(exportFileName) def handleFileClicked(self,modelIndex): node = self.projectView.model().nodeFromIndex(modelIndex) filePath = PROJECTSMANAGER().db().getFullPath(node.fileId) self.launchViewer(filePath) def handleJobClicked(self,modelIndex): node = self.projectView.model().nodeFromIndex(modelIndex) try: jobId = node.jobId except: return pipelineJobNode = node.getTopJob() self.emit(QtCore.SIGNAL('currentJobChanged'),None,jobId,pipelineJobNode.jobId,False) def handleSelectionChanged(self,selected,deselected): indices = selected.indexes() #print 'CProjectWidget.handleSelectionChanged',indices if len(indices)>0: self.handleJobClicked(indices[0]) def handleDoubleClick(self,modelIndex): #print 'CProjectWidget.handleDoubleClick',double node = self.projectView.model().nodeFromIndex(modelIndex) if node.isJob(): fileId = None jobId = node.jobId pipelineJobNode = node.getTopJob() else: fileId = node.fileId jobId = node.parent().jobId pipelineJobNode = node.parent().getTopJob() if fileId is None: #if not double: self.projectView.model().setHighlightRow(currentCacheIndex) # Add state of 'double' which indicates job view should be detatched self.emit(QtCore.SIGNAL('currentJobChanged'),fileId,jobId,pipelineJobNode.jobId,True) #elif double and fileId is not None: else: filePath = PROJECTSMANAGER().db().getFullPath(fileId) self.launchViewer(filePath) #print 'done handleClick' def getHeaderState(self): return str(self.projectView.header().saveState().data()) def setHeaderState(self,state): stateByteArray = QtCore.QByteArray() stateByteArray.append(state) self.projectView.header().restoreState(stateByteArray) def selectJob(self,jobId,selectionFlags=QtGui.QItemSelectionModel.ClearAndSelect): #print 'CProjectWidget.selectJob',jobId modelIndex = self.projectView.model().modelIndexFromJob(jobId) if modelIndex is not None: sel = QtGui.QItemSelection( modelIndex.sibling(modelIndex.row(),0) , modelIndex.sibling(modelIndex.row(),CProjectModel.NCOLUMNS-1) ) self.projectView.selectionModel().select(sel,selectionFlags) class CFileIconProvider(QtGui.QFileIconProvider): def icon(self,fileInfo): #print 'CFileIconProvider.icon',fileInfo if isinstance(fileInfo,QtCore.QFileInfo): suffix = str(fileInfo.completeSuffix()) #print 'CFileIconProvider',suffix if suffix == 'mtz': content = fileInfo.baseName().__str__().split(CCP4File.CDataFile.SEPARATOR)[-1] #print 'CFileIconProvider content',content mimeType=MIMETYPESHANDLER().formatFromFileExt(ext=suffix,contentLabel=content) else: mimeType=MIMETYPESHANDLER().formatFromFileExt(ext=suffix) #print 'CFileIconProvider.icon',suffix,mimeType if mimeType is not None: icon = MIMETYPESHANDLER().icon(mimeType) if icon is not None: #print 'CFileIconProvider providing',icon return icon return QtGui.QIcon() class CProjectDirModel(QtGui.QFileSystemModel): def __init__(self,parent,projectId): QtGui.QFileSystemModel.__init__(self,parent) self.projectId = projectId projectDir = PROJECTSMANAGER().db().getProjectDirectory(projectId=self.projectId) self.setRootPath(projectDir) self.setIconProvider(CFileIconProvider()) #self.sort(0,QtCore.Qt.DescendingOrder) lookup = PROJECTSMANAGER().db().getTaskNameLookup(projectId=self.projectId) self.jobNoList = [] self.taskNameList = [] for j,t in lookup: self.jobNoList.append(j) self.taskNameList.append(TASKMANAGER().getShortTitle(t)) self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobCreated'),self.updateLookup) self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobFinished'),self.updateLookup1) self.connect(PROJECTSMANAGER().db(),QtCore.SIGNAL('jobDeleted'),self.updateLookup2) def updateLookup(self,args): if args['projectId'] != self.projectId: return info = PROJECTSMANAGER().db().getJobInfo(jobId=args['jobId'],mode=['jobnumber','taskname']) #print 'CProjectDirModel.updateLookup',info self.jobNoList.append(info['jobnumber']) self.taskNameList.append(TASKMANAGER().getShortTitle(info['taskname'])) def updateLookup1(self,args): if args.get('projectId') != self.projectId: return info = PROJECTSMANAGER().db().getJobInfo(jobId=args.get('jobId'),mode=['jobnumber','taskname']) #print 'CProjectDirModel.updateLookup1',info if not info['jobnumber'] in self.jobNoList: self.jobNoList.append(info['jobnumber']) self.taskNameList.append(TASKMANAGER().getShortTitle(info['taskname'])) def updateLookup2(self,args): if args.get('projectId') != self.projectId: return if args['jobNumber'] in self.jobNoList: idx = self.jobNoList.index(args['jobNumber']) self.jobNoList.pop(idx) self.taskNameList.pop(idx) def data(self,modelIndex,role): ret = QtGui.QFileSystemModel.data(self,modelIndex,role) if role != QtCore.Qt.DisplayRole: return ret retStr = ret.toString().__str__() if retStr[0:4] != 'job_': return ret userData = QtGui.QFileSystemModel.data(self,modelIndex,QtCore.Qt.UserRole) if userData.isNull(): try: pathSplit = (str(self.filePath(modelIndex))).split('/') jobNumber = pathSplit[pathSplit.index('CCP4_JOBS')+1][4:] for item in pathSplit[pathSplit.index('CCP4_JOBS')+2:]: jobNumber = jobNumber + '.' + item[4:] #print 'CProjectDirModel.data path',pathSplit,jobNumber if self.jobNoList.count(jobNumber)>0: taskName = self.taskNameList[self.jobNoList.index(jobNumber)] QtGui.QFileSystemModel.setData(self,modelIndex, QtCore.QVariant(taskName),QtCore.Qt.UserRole) else: return ret except: return ret else: taskName = userData.toString.__str__() #print 'CProjectDirModel.data',retStr,taskName return QtCore.QVariant(retStr+' '+taskName) def supportedDragActions(self): return QtCore.Qt.CopyAction def mimeTypes(self): typesList = QtCore.QStringList() for item in MIMETYPESHANDLER().mimeTypes.keys(): typesList.append(item) #print 'CProjectDirModel.mimeTypes',typesList return typesList def projectRootIndex(self): import os return self.index(os.path.join(str(self.rootPath()),'CCP4_JOBS'),0) def jobNumberToModelIndex(self,jobNumber): jobNumber = str(jobNumber) if jobNumber[0] != 'j': jobNumber = 'job_'+jobNumber startIndex = self.index(0,0,self.projectRootIndex()) modelIndexList = self.match(startIndex,QtCore.Qt.DisplayRole,QtCore.QVariant(jobNumber)) #print 'jobNumberToModelIndex',jobNumber,modelIndexList if len(modelIndexList)>0: return modelIndexList[0] else: return QtCore.QModelIndex() ''' def mimeData(self,modelIndexList): modelIndex = modelIndexList[0] qFileInfo = self.fileInfo(modelIndex) path = str(qFileInfo.absoluteFilePath()) mimeType = MIMETYPESHANDLER().formatFromFileExt(fileName=path) if mimeType is None: return None import CCP4File fileObj = CCP4File.CDataFile(fullPath=path) dragText = fileObj.xmlText(pretty_print=False) print 'mimeData dragText',mimeType,dragText encodedData = QtCore.QByteArray() encodedData.append(dragText) mime = QtCore.QMimeData() # With mime type as text the data can be dropped on desktop # but the type of the data is lost #mimeData.setData('text/plain',data) mime.setData(mimeType,encodedData) return mime ''' class CProjectDirView(QtGui.QTreeView): def __init__(self,parent): QtGui.QTreeView.__init__(self,parent) self.setDragEnabled(True) self.setDragDropMode(QtGui.QAbstractItemView.DragOnly) self._initLayout= True self.setAlternatingRowColors(PREFERENCES().TABLES_ALTERNATING_COLOR) self.connect(PREFERENCES().TABLES_ALTERNATING_COLOR,QtCore.SIGNAL('dataChanged'),self.resetAlternatingRowColors) def resetAlternatingRowColors(self): self.setAlternatingRowColors(PREFERENCES().TABLES_ALTERNATING_COLOR) def reset(self): QtGui.QTreeView.reset(self) if self._initLayout: self.setColumnWidth(0,300) self._initLayout=False def focusOn(self,jobNumber=None): modelIndex = self.model().jobNumberToModelIndex(jobNumber) if modelIndex.isValid(): self.setExpanded(modelIndex,True) self.scrollTo(modelIndex,QtGui.QAbstractItemView.PositionAtTop) def startDrag(self,dropActions=None,modelIndex=None): if modelIndex is None: modelIndex = self.currentIndex() #print 'CProjectDirView.startDrag',dropActions,modelIndex,str(modelIndex.data(QtCore.Qt.DisplayRole).toString()) if modelIndex is None: return fileName = '' indx = modelIndex while indx.isValid(): fileName = os.path.normpath(os.path.join(str(indx.data(QtCore.Qt.DisplayRole).toString()),fileName)) indx = indx.parent() if len(fileName)<=0: return fileName = fileName[0:-1] fileId,jobId = PROJECTSMANAGER().db().matchFileName(fileName=fileName) #print 'CProjectDirView.startDrag fileName',fileName,fileId if fileId is None: return from lxml import etree mimeType = PROJECTSMANAGER().db().getFileInfo(fileId=fileId,mode='fileclass') root,err = PROJECTSMANAGER().db().getFileEtree(fileId=fileId) dragText = etree.tostring(root) #print 'CProjectDirView.startDrag',mimeType,dragText encodedData = QtCore.QByteArray() encodedData.append(dragText) mimeData = QtCore.QMimeData() # With mime type as text the data can be dropped on desktop # but the type of the data is lost #mimeData.setData('text/plain',data) mimeData.setData(mimeType,encodedData) url = QtCore.QUrl() url.setPath(fileName) mimeData.setUrls([url]) drag = QtGui.QDrag(self) drag.setMimeData(mimeData) iconVar = modelIndex.data(QtCore.Qt.DecorationRole) icon = iconVar.toPyObject() pixmap = icon.pixmap(18,18) drag.setHotSpot(QtCore.QPoint(9,9)) drag.setPixmap(pixmap) #print 'CProjectDirView.startDrag exec_' drag.exec_(QtCore.Qt.CopyAction) class CEvaluationDelegate(QtGui.QItemDelegate): def __init__(self,parent=None): QtGui.QItemDelegate. __init__(self,parent) def createEditor(self,parent,option,modelIndex): cacheIndex = self.parent().model().cacheFromModelIndex(modelIndex) if self.parent().model()._dataCache[cacheIndex].has_key('evaluation'): iconCombo = QtGui.QComboBox(parent) iconCombo.setEditable(False) for item in CCP4DbApi.JOB_EVALUATION_TEXT: iconCombo.addItem(jobIcon(item),item) iconCombo.show() return iconCombo else: return None def setEditorData(self,editor,modelIndex): cacheIndex = self.parent().model().cacheFromModelIndex(modelIndex) data = self.parent().model()._dataCache[cacheIndex]['evaluation'] #print 'CEvaluationDelegate.setEditorData',data editor.setCurrentIndex(CCP4DbApi.JOB_EVALUATION_TEXT.index(data)) def setModelData(self,editor,model,modelIndex): evaluation = str(editor.currentText()) cacheIndex = self.parent().model().cacheFromModelIndex(modelIndex) self.parent().model()._dataCache[cacheIndex]['evaluation'] = evaluation jobid = self.parent().model()._dataCache[cacheIndex]['jobid'] try: PROJECTSMANAGER().db().updateJob(jobid,key='evaluation',value=evaluation) except: pass def updateEditorGeometry(self,editor,option,modelIndex): r = option.rect #print 'CItemDelegate.updateEditorGeometry',r.width(),r.height() r.setHeight(editor.geometry().height()) r.setWidth(editor.geometry().width()) #print 'CItemDelegate.updateEditorGeometry',r.width(),r.height() editor.setGeometry(r) class CJobEditLineEdit(QtGui.QLineEdit): def focusOutEvent(self,event): #print 'CJobEditLineEdit.focusOutEvent' self.emit(QtCore.SIGNAL('loosingFocus')) QtGui.QLineEdit.focusOutEvent(self,event) class CJobEditDelegate(QtGui.QStyledItemDelegate): def __init__(self,parent=None): QtGui.QStyledItemDelegate. __init__(self,parent) #print 'CJobEditDelegate.__init__' self.editorWidget = None def createEditor(self,parent,option,modelIndex): #if self.parent().model().data(modelIndex,QtCore.Qt.DisplayRole): #print 'CJobEditDelegate.createEditor' window = QtGui.QDialog(parent) window.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Dialog) layout = QtGui.QVBoxLayout() layout.setMargin(CProjectWidget.MARGIN) layout.setContentsMargins(CProjectWidget.MARGIN,CProjectWidget.MARGIN, CProjectWidget.MARGIN,CProjectWidget.MARGIN) window.setLayout(layout) editor = CJobEditLineEdit(window) editor.setMinimumWidth(350) editor.setObjectName('editor') window.layout().addWidget(editor) self.editorWidget = window self.editorWidget.setFocus() self.connect(editor,QtCore.SIGNAL('loosingFocus'),self.closeEditor) return window def closeEditor(self): #print 'CJobEditDelegate.closeEditor',self.editorWidget if self.editorWidget is not None: self.emit(QtCore.SIGNAL('commitData(QWidget*)'),self.editorWidget) self.emit(QtCore.SIGNAL('closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)'),self.editorWidget,QtGui.QAbstractItemDelegate.NoHint) self.editorWidget.deleteLater() self.editorWidget = None def setEditorData(self,editor,modelIndex): data = self.parent().model().nodeFromIndex(modelIndex).getName(jobNumber=False) if data is not None: editor.findChild(QtGui.QLineEdit,'editor').setText(data) def setModelData(self,editor,model,modelIndex): #print 'CJobEditDelegate.setModelData' text = str(editor.findChild(QtGui.QLineEdit,'editor').text()) node = self.parent().model().nodeFromIndex(modelIndex) if node.isJob(): try: PROJECTSMANAGER().db().updateJob(node.getJobId(),key='jobtitle',value=text) except: pass elif node.isFile(): try: PROJECTSMANAGER().db().updateFile(node.getFileId(),key='annotation',value=text) except: pass def updateEditorGeometry(self,editor,option,modelIndex): #Put the edit line in the right place editor.show() view = editor.parent().parent() # mapToGlobal() does not allow for the header - need to add header height # and want to not overlap the icon (and the job number for jobs) node = self.parent().model().nodeFromIndex(modelIndex) rightShift = 25 + node.isJob()*25 pos=view.mapToGlobal(view.visualRect(modelIndex).topLeft()) editor.move(pos+QtCore.QPoint(rightShift,20)) class CFollowFromDelegate(QtGui.QItemDelegate): def __init__(self,parent=None): QtGui.QItemDelegate. __init__(self,parent) def createEditor(self,parent,option,modelIndex): cacheIndex = self.parent().model().cacheFromModelIndex(modelIndex) if self.parent().model()._dataCache[cacheIndex].has_key('status'): iconCombo = QtGui.QComboBox(parent) iconCombo.setEditable(False) for text,icon in [['Unused','greendot'],['Current','greenarrowsup']]: iconCombo.addItem(jobIcon(icon),text) iconCombo.show() return iconCombo else: return None def setEditorData(self,editor,modelIndex): cacheIndex = self.parent().model().cacheFromModelIndex(modelIndex) if self.parent().model()._followFromCacheId == cacheIndex: editor.setCurrentIndex(1) else: editor.setCurrentIndex(0) def setModelData(self,editor,model,modelIndex): status = str(editor.currentText()) cacheIndex = self.parent().model().cacheFromModelIndex(modelIndex) if status == 'Current': if cacheIndex == self.parent().model()._followFromCacheId: return else: jobid = self.parent().model()._dataCache[cacheIndex]['jobid'] try: PROJECTSMANAGER().db().setProjectFollowFromJobId(self.parent().model()._projectId,jobid) except: print 'Error setting db setProjectFollowFromJobId' def updateEditorGeometry(self,editor,option,modelIndex): r = option.rect #print 'CItemDelegate.updateEditorGeometry',r.width(),r.height() r.setHeight(editor.geometry().height()) r.setWidth(editor.geometry().width()) #print 'CItemDelegate.updateEditorGeometry',r.width(),r.height() editor.setGeometry(r)